Java 8 graphics glitch when stroking sub-pixel coordinates on Linux
Asked Answered
V

2

20

It seems that stroking on sub-pixel coordinates became broken in Java 8.

I have three sets of cases, shown on screenshots (columns represent cases, rows represent different stroke widths) :

Java 7u51 (400% scale)
Screenshot on Java 7u51 (400% scale)
Java 8u60 (400% scale)
Screenshot on Java 8u60 (400% scale)

  1. Fill and stroke on the same coordinates. Works as intended, stroked area is larger than the filling area.
  2. Stroking is shrunk (by stroke width) and centered (by half of the width) to be inside bounds of the filling region. This part is broken in Java 8 for 1px stroke, where painting occurs on a sub-pixel coordinate (first row); 3px stroke doesn't have this problem (third row). It seems that 0.5 is rounded up for the 1px stroke.
  3. Filling rectangle is shrunk an centered the same way of case 2. I need this on graphics, which support sub-pixel drawing, to make non-overlapping fill when cells are overlapping. Here you can see that fill operation rounds down 0.5 to 0, so it's only stroking problem.

The code is below:

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);
        }
    }
}

As I tested, Java 7 doesn't have this problem (tried on 7u51), Windows (8u77) and Mac (8u60) too. Tried on Ubuntu (8u60 and 8u77) and Linux Mint (8u60) on different machines and the bug was here.

Did anyone faced such issue? Is there any general workaround?

I cannot simply handle 1px case in places where stroking is used. It's because there is a lot of places and I'm working with different Graphics2D implementations and it seems, that from what I've used the issue reproduces only on SunGraphics2D. This means I need to use instanceOf in these places to not break common logic.

Vocable answered 14/4, 2016 at 15:33 Comment(3)
StackOverflow is not a bug reporting mechanism. To report an issue with Java visit bugreport.java.com and include your demo class and anything else they need to replicate the problem.Stefaniestefano
I'm voting to close this question as off-topic because it's a bug report.Stefaniestefano
I've submitted bug to bugtracker, but I don't hope to see the fix any soon. Because it's a bug in graphics. I've submitted another bug almost half-year ago and still got no notification about its progress. I'd rather need a workaround to live with this issue until it's fixed.Vocable
V
1

From bug report discussion:

It is an xrender bug -Dsun.java2d.xrender=false cures it.

I haven't checked this solution myself as I didn't received any notifications from bug reporting system besides that it was reviewed. And as it was Linux-only problem it was decided to wait for official fixes as there was not so many Linux users among our customers.

Vocable answered 3/2, 2017 at 16:9 Comment(0)
S
0

I would suggest you extend Graphics2D and add correctional measures there on the places where needed.

That way you can keep your main flow consistent, and add all other "logic" errors can be handled where they need to be handled.

This saves you the overhead of modifying N files and undoing those changes if it gets fixed, and keeping the logic where it should be.

Sharpeyed answered 1/2, 2017 at 12:5 Comment(1)
Actually I'm using different Graphics objects (to render to different devices), which are set implicitly. And as I remember SunGraphics2D, which is used internally, cannot be extended. Thanks to you, I've checked the bug report and workaround from Oracle staff.Vocable

© 2022 - 2024 — McMap. All rights reserved.