JDK-8247541 : Blur when drawing on Canvas with HighDPI enabled
  • Type: Bug
  • Component: javafx
  • Sub-Component: graphics
  • Affected Version: openjfx11,openjfx15
  • Priority: P4
  • Status: Open
  • Resolution: Unresolved
  • OS: windows_10
  • CPU: x86_64
  • Submitted: 2020-06-14
  • Updated: 2020-06-18
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 :  
Description
ADDITIONAL SYSTEM INFORMATION :
JavaFX 11 + JDK 11.0.6

A DESCRIPTION OF THE PROBLEM :
Suppose we draw on a JavaFX Canvas. Coordinates for the canvas are provided by double values, but in order to "snap" to the pixel grid, we need integer numbers to avoid drawing "inbetween" pixels, which will result in a blur/smoothing on edges.

The supplied example runs well with clear edges on 100% dpi scaling with Windows 10, as well as 150% dpi scaling. However with certain fractions - like 125% scaling, there is one line of blur around the rectangle.

cf. https://stackoverflow.com/questions/62246642/javafx-avoid-blur-when-drawing-with-rect-on-canvas-with-highdpi/62326515

This bug might be related: https://bugs.openjdk.java.net/browse/JDK-8220484

STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
Compile and run the supplied source code. A dark grey rectangle upon a light gray rectangle will appear. In Windows 10 enable high dpi scaling  (Windows Settings app -> System -> Display -> Scale and layout section -> Change the size of text, apps, and other icons) and run the proram each with 100%, 125% and 150%.

EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
There should be a clear edge between the dark and light gray box. 
ACTUAL -
At 125% scaling, there is one pixel line of "blur" between the light and dark gray box. It is one pixel line with an interpolated color.

---------- BEGIN SOURCE ----------
public class App extends Application {

    @Override
    public void start(Stage stage) {

        var vbox = new VBox();
        var canvas = new Canvas(600, 400);
        var gc = canvas.getGraphicsContext2D();

        // draw a background
        gc.beginPath();
        gc.setFill(Color.rgb(200,200,200));
        gc.rect(0, 0, 600, 400);
        gc.fill();

        // draw a smaller square
        gc.beginPath();
        gc.setFill(Color.rgb(100,100,100));
        gc.rect(9.0, 9.0, 50, 50);  // snap to grid
        gc.fill();

        vbox.getChildren().addAll(canvas);

        var scene = new Scene(vbox, 640, 480);
        stage.setScene(scene);
        stage.show();
    }

    public static void main(String[] args) {
        launch();
    }

}
---------- END SOURCE ----------

CUSTOMER SUBMITTED WORKAROUND :
There is no workaround. A naive way would be to "snap to a grid", i.e. get the ourputScale Factor via outputScaleX/Y, and divide the pixel size, i.e. consider change gc.rect(9.0, 9.0,....). Since 9.0 * 1.25 = 11.25. The fraction is probably the cause of the interpolation. We could try to "hit" an even pixel, i.e. 11.0 by chaning the line to gc.rect(11.0/1.25, 11.0/1.25, 50, 50); but that still results in a blur. The same hold if one tries to deactivate automatic scaling for the whole canvas, by i.e. canvas.scaleXProperty().bind(new SimpleDoubleProperty(1.0).divide(scene.getWindow().outputScaleXProperty()));

FREQUENCY : always



Comments
I can reproduce it, although it isn't really a bug. By default, the layout containers "snap to pixel" when laying out their children. Likewise, the UI controls will use "snap to pixel" when drawing their graphics elements. This snap-to-pixel functionality takes screen scale into account so that coordinates end up on an integer pixel boundary when they are rendered. Canvas, on the other hand, provides a pixel API that doesn't do any snapping to pixel (and really can't). The application would need to adjust their coordinates if they want to snap to pixel. We might still have a bug, since I tried doing a simple snap to pixel and it wasn't effective.
18-06-2020