JDK-8156100 : xrender: Subpixel drawing bug in Java 8
  • Type: Bug
  • Component: client-libs
  • Sub-Component: 2d
  • Affected Version: 8,9
  • Priority: P3
  • Status: Open
  • Resolution: Unresolved
  • OS: linux_ubuntu
  • CPU: x86_64
  • Submitted: 2016-05-04
  • Updated: 2018-09-05
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
Description
FULL PRODUCT VERSION :


ADDITIONAL OS VERSION INFORMATION :
Linux 3.13.0-85-generic #129-Ubuntu SMP x86_64 x86_64 x86_64 GNU/Linux

A DESCRIPTION OF THE PROBLEM :
When drawing (stroking) 1px line on a sub-pixel coordinates in Java 8, its coordinates are rounded up, but in Java 7 they were rounded down. Only affects 1px lines.

Tried on early 1.8 version, 8u60 and 8u77. 
Doesn't reproduce on Windows/Mac.


EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
http://i.stack.imgur.com/6egcI.png
ACTUAL -
http://i.stack.imgur.com/pNJlB.png

REPRODUCIBILITY :
This bug can be reproduced always.

---------- BEGIN SOURCE ----------
import static java.awt.BasicStroke.*;

import java.awt.*;
import java.awt.geom.*;

import javax.swing.*;

public class TestCase
{
    public static void main(String[] args)
    {
        JFrame frame = new JFrame("Test case");
        frame.setSize(115, 115);
        frame.setDefaultCloseOperation(WindowConstants.EXIT_ON_CLOSE);

        frame.getContentPane().add(new TestPanel());

        frame.setVisible(true);
    }

    private static class TestPanel extends JPanel
    {
        TestPanel()
        {
            setOpaque(true);
        }

        @Override
        protected void paintComponent(Graphics g)
        {
            Graphics2D g2 = (Graphics2D) g;
            g2.setColor(Color.white);
            g2.fill(getBounds());

            Rectangle2D rect = new Rectangle2D.Double();
            Color background = new Color(0, 255, 255);
            Color border = new Color(255, 0, 0, 128);
            Stroke STROKE_1PX = new BasicStroke(1, CAP_SQUARE, JOIN_MITER);
            Stroke STROKE_2PX = new BasicStroke(2, CAP_SQUARE, JOIN_MITER);
            Stroke STROKE_3PX = new BasicStroke(3, CAP_SQUARE, JOIN_MITER);
            g2.translate(10, 10);

            /**
             * Filling and stroking by original coordinates
             */
            rect.setRect(0, 0, 25, 25);
            g2.setColor(background);
            g2.fill(rect);
            g2.setColor(border);
            g2.setStroke(STROKE_1PX);
            g2.draw(rect);
            g2.translate(0, 35);
            g2.setColor(background);
            g2.fill(rect);
            g2.setColor(border);
            g2.setStroke(STROKE_2PX);
            g2.draw(rect);
            g2.translate(0, 35);
            g2.setColor(background);
            g2.fill(rect);
            g2.setColor(border);
            g2.setStroke(STROKE_3PX);
            g2.draw(rect);

            /**
             * Stroking is shrunk to be inside the filling rect
             */
            g2.translate(35, -70);
            rect.setRect(0, 0, 25, 25);
            g2.setColor(background);
            g2.fill(rect);
            rect.setRect(0.5, 0.5, 24, 24);
            g2.setColor(border);
            g2.setStroke(STROKE_1PX);
            g2.draw(rect);
            g2.translate(0, 35);
            rect.setRect(0, 0, 25, 25);
            g2.setColor(background);
            g2.fill(rect);
            rect.setRect(1, 1, 23, 23);
            g2.setColor(border);
            g2.setStroke(STROKE_2PX);
            g2.draw(rect);
            g2.translate(0, 35);
            rect.setRect(0, 0, 25, 25);
            g2.setColor(background);
            g2.fill(rect);
            rect.setRect(1.5, 1.5, 22, 22);
            g2.setColor(border);
            g2.setStroke(STROKE_3PX);
            g2.draw(rect);

            /**
             * Filling rect is additionally shrunk and centered
             */
            g2.translate(35, -70);
            rect.setRect(0.5, 0.5, 24, 24);
            g2.setColor(background);
            g2.fill(rect);
            g2.setColor(border);
            g2.setStroke(STROKE_1PX);
            g2.draw(rect);
            g2.translate(0, 35);
            rect.setRect(1, 1, 23, 23);
            g2.setColor(background);
            g2.fill(rect);
            g2.setColor(border);
            g2.setStroke(STROKE_2PX);
            g2.draw(rect);
            g2.translate(0, 35);
            rect.setRect(1.5, 1.5, 22, 22);
            g2.setColor(background);
            g2.fill(rect);
            g2.setColor(border);
            g2.setStroke(STROKE_3PX);
            g2.draw(rect);
        }
    }
}
---------- END SOURCE ----------


Comments
Testcase to demonstrate the issue when rendering to a BufferedImage
07-11-2016

It took me quite some time to figure out what is going on here. The test actually expects fill and draw calls with different strokes to align, as the fill call is performed first and only after the fill call the stroke is set for the draw operation. For Software/D3D/OGL this seems to work out by accident, because those pipelines implement the ParallelogramPipe which does not seem to suffer form this artifact. However, as soon as a primitive is used which can not be handled by the ParallelogramPipe, all pipelines exhibit the issue reported here - namely that the stroke set influences fills.
07-11-2016

This bug can be re-targeted to 9 before making a fix push. Since this is non-reg-9, retargeted to tbd_major now
02-11-2016

Clemens, is this bug fix still in your plans to meet ZBB (Feb 16, 2017)? http://openjdk.java.net/projects/jdk9/
19-10-2016

not regression for 9
19-10-2016

The issue seems to be caused by different rounding applied by ShapeSpanIterator (used for fill()) and ProcessPath (used for draw()). I've sent an email to 2d-dev, describing the issue in more detail, including a testcase: http://pastebin.com/hy5zCkPR
25-09-2016

Thank you for the report - I'll take a look at it when I am on vacation in June.
10-05-2016

Yes, all JDK 8 bugs affect 9 unless they were fixed. This reproduces on 8 and 9. It is an xrender bug -Dsun.java2d.xrender=false cures it.
05-05-2016

does it affect 9?
05-05-2016