JDK-8341381 : Random lines appear in graphic causing by the fix of JDK-8297230
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 20,21,23,24
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: generic
  • CPU: generic
  • Submitted: 2024-09-30
  • Updated: 2025-05-16
The Version table provides details related to the release that this issue/RFE will be addressed.

Unresolved : Release in which this issue/RFE will be addressed.
Resolved: Release in which this issue/RFE has been resolved.
Fixed : Release in which this issue/RFE has been fixed. The release containing this fix may be available for download as an Early Access Release or a General Availability Release.

To download the current JDK release, click here.
Other
tbdUnresolved
Related Reports
Relates :  
Relates :  
Description
ADDITIONAL SYSTEM INFORMATION :
This issue was reproduced on Windows10 but also in Linux environments.
Tested locally: the bug does NOT appear in jdk20+24 but appears as of jdk20+25 and upper versions JDK21 and on as well.

A DESCRIPTION OF THE PROBLEM :
Random lines - segments and straight lines - , that seem to be tangents of original expected lines, appear when painting basic strokes.
It can lead to some complex graphics not being readable anymore and mislead users in the interpretation of the images.

I identified this commit as the potential root cause for this bug: https://github.com/openjdk/jdk/commit/5b3d86f2296ec011f70cebe80a221b8a6f926912

REGRESSION : Last worked in version 19

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :

With jdk20+25 compile it and run net.sf.jcgm.examples.Viewer.
In the GUI of the app, paste the directory containing the CGM file in the "Test Folder" and press enter.
Zoom and zoom out, play around with the size the panel: unexpected straight lines and segments will appear and disappear randomly.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
As when running with jdk20+24 or older: no random line should appear when zooming in or out in the application.
ACTUAL -
Random lines, some being segments, other longer straight lines, appear on boarder of curves. They look like being tangent.

CUSTOMER SUBMITTED WORKAROUND :
None.

FREQUENCY : always



Comments
Seems similar problem with cubic bezier curve offsetting... prone to numerical accuracy problems
09-10-2024

I will investigate this problem, related to cubic bezier curves...
03-10-2024

The observations on Window 11: JDK 20ea+24: Passed, no random line observed JDK 20ea+25: Failed, random line observed JDK 21: Failed. JDK 23: Failed. JDK 24ea+1: Failed.
02-10-2024

Additional information from the submitter: Here is what you asked for: just play around with the size of the frame, unexpected tangents will appear. Ensure that you use JDK20+25 or later. With JDK20+24 or older you’ll see that this issue does not appear. This is why I suspect this implementation to be part of the regression: https://bugs.openjdk.org/browse/JDK-8297230. Moreover you will find enclosed an animated GIF with the issue reproduced minutes ago, with the code below. import javax.swing.*; import java.awt.*; import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.geom.AffineTransform; import java.awt.geom.CubicCurve2D; import java.awt.geom.Point2D; import java.util.List; public class Main { public static void main(String[] args) { Viewer viewer = new Viewer(); viewer.pack(); viewer.setVisible(true); viewer.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); } } class Viewer extends JFrame { public Viewer() { JPanel panel = new JPanel(new BorderLayout()); JScrollPane scrollPane = new JScrollPane(new CanvasPanel()); panel.add(scrollPane, BorderLayout.CENTER); setContentPane(panel); } } class CanvasPanel extends JPanel { private final BugDisplay bugDisplay; private int width = 0, height = 0; private double dpi = 96; private final double dpiStep = 75; public CanvasPanel() { this.bugDisplay = new BugDisplay(); this.addMouseListener(new MouseAdapter() { @Override public void mouseReleased(MouseEvent e) { if (e.getButton() == MouseEvent.BUTTON1) { // zoom in zoomIn(); revalidate(); } else if (e.getButton() == MouseEvent.BUTTON3) { // zoom out zoomOut(); revalidate(); } } }); } @Override public void paint(Graphics g) { int w0 = getSize().width; int h0 = getSize().height; if (w0 != this.width || h0 != this.height || !this.bugDisplay.isScaled) { this.width = w0; this.height = h0; this.bugDisplay.scale(g, this.width, this.height); } this.bugDisplay.paint(g); } @Override public Dimension getPreferredSize() { if (this.bugDisplay == null) { return new Dimension(200, 200); } return this.bugDisplay.getSize(this.dpi); } public void zoomIn() { this.dpi += this.dpiStep; } public void zoomOut() { this.dpi -= this.dpiStep; } } class BugDisplay { private Graphics2D g2d; boolean isScaled = false; private int canvasWidth = 739; private int canvasHeight = 951; // polybezier lines that will lead to the bug private final java.util.List<CubicCurve2D.Double> curves1 = List.of( new CubicCurve2D.Double(2191.0, 7621.0, 2191.0, 7619.0, 2191.0, 7618.0, 2191.0, 7617.0), new CubicCurve2D.Double(2191.0, 7617.0, 2191.0, 7617.0, 2191.0, 7616.0, 2191.0, 7615.0), new CubicCurve2D.Double(2198.0, 7602.0, 2200.0, 7599.0, 2203.0, 7595.0, 2205.0, 7590.0), new CubicCurve2D.Double(2205.0, 7590.0, 2212.0, 7580.0, 2220.0, 7571.0, 2228.0, 7563.0), new CubicCurve2D.Double(2228.0, 7563.0, 2233.0, 7557.0, 2239.0, 7551.0, 2245.0, 7546.0), new CubicCurve2D.Double(2245.0, 7546.0, 2252.0, 7540.0, 2260.0, 7534.0, 2267.0, 7528.0), new CubicCurve2D.Double(2267.0, 7528.0, 2271.0, 7526.0, 2275.0, 7524.0, 2279.0, 7521.0), new CubicCurve2D.Double(2279.0, 7521.0, 2279.0, 7520.0, 2280.0, 7520.0, 2281.0, 7519.0) ); private final java.util.List<CubicCurve2D.Double> curves2 = List.of( new CubicCurve2D.Double(2281.0, 7519.0, 2282.0, 7518.0, 2282.0, 7517.0, 2283.0, 7516.0), new CubicCurve2D.Double(2283.0, 7516.0, 2284.0, 7515.0, 2284.0, 7515.0, 2285.0, 7514.0), new CubicCurve2D.Double(2291.0, 7496.0, 2292.0, 7495.0, 2292.0, 7494.0, 2291.0, 7493.0), new CubicCurve2D.Double(2291.0, 7493.0, 2290.0, 7492.0, 2290.0, 7492.0, 2289.0, 7492.0), new CubicCurve2D.Double(2289.0, 7492.0, 2288.0, 7491.0, 2286.0, 7492.0, 2285.0, 7492.0), new CubicCurve2D.Double(2262.0, 7496.0, 2260.0, 7497.0, 2259.0, 7497.0, 2257.0, 7498.0), new CubicCurve2D.Double(2257.0, 7498.0, 2254.0, 7498.0, 2251.0, 7499.0, 2248.0, 7501.0), new CubicCurve2D.Double(2248.0, 7501.0, 2247.0, 7501.0, 2245.0, 7502.0, 2244.0, 7503.0), new CubicCurve2D.Double(2207.0, 7523.0, 2203.0, 7525.0, 2199.0, 7528.0, 2195.0, 7530.0), new CubicCurve2D.Double(2195.0, 7530.0, 2191.0, 7534.0, 2186.0, 7538.0, 2182.0, 7541.0) ); private final java.util.List<CubicCurve2D.Double> curves3 = List.of( new CubicCurve2D.Double(2182.0, 7541.0, 2178.0, 7544.0, 2174.0, 7547.0, 2170.0, 7551.0), new CubicCurve2D.Double(2170.0, 7551.0, 2164.0, 7556.0, 2158.0, 7563.0, 2152.0, 7569.0), new CubicCurve2D.Double(2152.0, 7569.0, 2148.0, 7573.0, 2145.0, 7577.0, 2141.0, 7582.0), new CubicCurve2D.Double(2141.0, 7582.0, 2138.0, 7588.0, 2134.0, 7595.0, 2132.0, 7602.0), new CubicCurve2D.Double(2132.0, 7602.0, 2132.0, 7605.0, 2131.0, 7608.0, 2131.0, 7617.0), new CubicCurve2D.Double(2131.0, 7617.0, 2131.0, 7620.0, 2131.0, 7622.0, 2131.0, 7624.0), new CubicCurve2D.Double(2131.0, 7624.0, 2131.0, 7630.0, 2132.0, 7636.0, 2135.0, 7641.0), new CubicCurve2D.Double(2135.0, 7641.0, 2136.0, 7644.0, 2137.0, 7647.0, 2139.0, 7650.0), new CubicCurve2D.Double(2139.0, 7650.0, 2143.0, 7658.0, 2149.0, 7664.0, 2155.0, 7670.0), new CubicCurve2D.Double(2155.0, 7670.0, 2160.0, 7676.0, 2165.0, 7681.0, 2171.0, 7686.0) ); private final java.util.List<CubicCurve2D.Double> curves4 = List.of( new CubicCurve2D.Double(2171.0, 7686.0, 2174.0, 7689.0, 2177.0, 7692.0, 2180.0, 7694.0), new CubicCurve2D.Double(2180.0, 7694.0, 2185.0, 7698.0, 2191.0, 7702.0, 2196.0, 7706.0), new CubicCurve2D.Double(2196.0, 7706.0, 2199.0, 7708.0, 2203.0, 7711.0, 2207.0, 7713.0), new CubicCurve2D.Double(2244.0, 7734.0, 2245.0, 7734.0, 2247.0, 7735.0, 2248.0, 7736.0), new CubicCurve2D.Double(2248.0, 7736.0, 2251.0, 7738.0, 2254.0, 7739.0, 2257.0, 7739.0), new CubicCurve2D.Double(2257.0, 7739.0, 2259.0, 7739.0, 2260.0, 7739.0, 2262.0, 7740.0), new CubicCurve2D.Double(2285.0, 7745.0, 2286.0, 7745.0, 2288.0, 7745.0, 2289.0, 7745.0), new CubicCurve2D.Double(2289.0, 7745.0, 2290.0, 7745.0, 2290.0, 7744.0, 2291.0, 7743.0), new CubicCurve2D.Double(2291.0, 7743.0, 2292.0, 7742.0, 2292.0, 7741.0, 2291.0, 7740.0), new CubicCurve2D.Double(2285.0, 7722.0, 2284.0, 7721.0, 2284.0, 7721.0, 2283.0, 7720.0), new CubicCurve2D.Double(2283.0, 7720.0, 2282.0, 7719.0, 2282.0, 7719.0, 2281.0, 7718.0), new CubicCurve2D.Double(2281.0, 7718.0, 2280.0, 7717.0, 2279.0, 7716.0, 2279.0, 7716.0), new CubicCurve2D.Double(2279.0, 7716.0, 2275.0, 7712.0, 2271.0, 7710.0, 2267.0, 7708.0), new CubicCurve2D.Double(2267.0, 7708.0, 2260.0, 7702.0, 2252.0, 7697.0, 2245.0, 7691.0), new CubicCurve2D.Double(2245.0, 7691.0, 2239.0, 7685.0, 2233.0, 7679.0, 2228.0, 7673.0), new CubicCurve2D.Double(2228.0, 7673.0, 2220.0, 7665.0, 2212.0, 7656.0, 2205.0, 7646.0), new CubicCurve2D.Double(2205.0, 7646.0, 2203.0, 7641.0, 2200.0, 7637.0, 2198.0, 7634.0) ); static final Point2D.Double[] extent = {new Point2D.Double(0.0, 0.0), new Point2D.Double(7777.0, 10005.0)}; public void paint(Graphics g) { this.g2d = (Graphics2D) g; this.g2d.setColor(Color.WHITE); this.g2d.fillRect(0, 0, this.canvasWidth, this.canvasHeight); this.g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); // ------ scale double minX = extent[0].x, maxX = extent[1].x; double minY = extent[0].y, maxY = extent[1].y; // we're scaling and respecting the proportions, check which scale to use double sx = this.canvasWidth / Math.abs(maxX - minX); double sy = this.canvasHeight / Math.abs(maxY - minY); double s = Math.min(sx, sy); double m00, m11, m02, m12; if (minX < maxX) { m00 = s; m02 = -s * minX; } else { // inverted X axis m00 = -s; m02 = this.canvasWidth + s * maxX; } if (minY < maxY) { m11 = s; m12 = -s * minY; } else { // inverted Y axis m11 = -s; m12 = this.canvasHeight + s * maxY; } // scale to the available view port AffineTransform scaleTransform = new AffineTransform(m00, 0, 0, m11, m02, m12); // invert the Y axis since (0, 0) is at top left for AWT AffineTransform invertY = new AffineTransform(1, 0, 0, -1, 0, this.canvasHeight); invertY.concatenate(scaleTransform); this.g2d.transform(invertY); // ------ this.g2d.setColor(Color.BLACK); this.g2d.setStroke(new BasicStroke(5.0f, BasicStroke.CAP_BUTT, BasicStroke.JOIN_BEVEL, 10.0f, new float[]{100, 0}, 0.0f)); this.curves1.forEach(this.g2d::draw); this.curves2.forEach(this.g2d::draw); this.curves3.forEach(this.g2d::draw); this.curves4.forEach(this.g2d::draw); } public Dimension getSize(double dpi) { double metricScalingFactor = 0.02539999969303608; // 1 inch = 25,4 millimeter double factor = dpi * metricScalingFactor / 25.4; int width = (int) Math.ceil(Math.abs(extent[1].x - extent[0].x) * factor); int height = (int) Math.ceil(Math.abs(extent[1].y - extent[0].y) * factor); return new Dimension(width, height); } public void scale(Graphics g, int w, int h) { this.g2d = (Graphics2D) g; if (extent == null) return; double extentWidth = Math.abs(extent[1].x - extent[0].x); double extentHeight = Math.abs(extent[1].y - extent[0].y); double fx = w / extentWidth; if (fx * extentHeight > h) { fx = h / extentHeight; } this.canvasWidth = (int) (fx * extentWidth); this.canvasHeight = (int) (fx * extentHeight); this.isScaled = true; } }
02-10-2024

Requested a standalone test program from the submitter.
01-10-2024