简体   繁体   English

JavaFX - 在 Canvas 上使用 highdpi 绘制矩形时避免模糊

[英]JavaFX - avoid blur when drawing with rect on Canvas with highdpi

I'd like to draw on a JavaFX Canvas.我想画一个 JavaFX Canvas。 Coordinates for the canvas are provided by double values, but I understand that in order to "snap" to the pixel grid, I need integer numbers to avoid drawing "inbetween" pixels, which will result in a blur/smoothing on edges. canvas 的坐标由 double 值提供,但我知道为了“捕捉”到像素网格,我需要 integer 数字以避免绘制“中间”像素,这将导致边缘模糊/平滑。

Consider this minimal example:考虑这个最小的例子:

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

}

This works and will result in a clear edge, consider this image:这有效并且会产生清晰的边缘,考虑这个图像:

在此处输入图像描述

Now suppose HighDPI is enabled, ie consider if we scale graphics in Windows 10 to 125 percent.现在假设启用了 HighDPI,即考虑我们是否将 Windows 中的图形缩放 10% 到 125%。 This will result in a scale factor of 1.25 (we can obtain this via getOutputScaleX/Y).这将导致比例因子为 1.25(我们可以通过 getOutputScaleX/Y 获得)。

However then the whole canvas is scaled with that factor, and we get a blur.然而,然后整个 canvas 用该因子进行缩放,我们得到了模糊。 See the attached images (but you have to zoom, and maybe view them in a graphics program and not in a browser).查看附加的图像(但您必须缩放,并且可能在图形程序而不是浏览器中查看它们)。

在此处输入图像描述

Then I thought that we can adjust the original coordinates such that the scaled coordinates will result in integers.然后我想我们可以调整原始坐标,使缩放后的坐标产生整数。 For example to make sure we hit an integer with offset 9 * 1.25 = 11.25 on the scaled image, let's try to target 11.0 instead, ie change this line例如,为了确保我们在缩放后的图像上命中偏移量为 9 * 1.25 = 11.25 的 integer,让我们尝试以 11.0 为目标,即更改此行

gc.rect(9.0, 9.0, 50, 50); 

to

gc.rect(11.0/1.25, 11.0/1.25, 50, 50); 

But this still results in a blur at the edge.但这仍然会导致边缘模糊。

How can we work around this?我们如何解决这个问题? Ideally I would like to turn off dpi scaling completely for the canvas, and do my own (pixel-perfect) calculations.理想情况下,我想完全关闭 canvas 的 dpi 缩放,并进行自己的(像素完美)计算。 Is this possible or is there any other solution?这是可能的还是有其他解决方案?

For drawImage there is a parameter setSmoothing, but there is nothing when drawing shapes directly (like rect).对于drawImage有一个参数setSmoothing,但是直接绘制形状的时候(比如rect)什么都没有。

You can solve this by eliminating the scaling used by scaling the Canvas to a smaller size.您可以通过消除将 Canvas 缩放到更小的尺寸所使用的缩放来解决此问题。 Here is a sample (note that lines are drawn "in between" pixels in JavaFX, so to get them sharp you need to add 0.5).这是一个示例(请注意,线条是在 JavaFX 中的像素“中间”绘制的,因此要使它们变得清晰,您需要添加 0.5)。

This example also places a Group around the scaled Canvas .此示例还在缩放后的Canvas周围放置了一个Group This is required to take the scale into account when doing layout, but also has some effect on rendering.这需要在做布局时考虑到比例,但对渲染也有一定的影响。 This example was tested at 100%, 125% and 150%.此示例在 100%、125% 和 150% 下进行了测试。

import javafx.application.Application;
import javafx.beans.property.SimpleDoubleProperty;
import javafx.scene.Group;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class CanvasTest extends Application {

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

  @Override
  public void start(Stage stage) throws Exception {
    HBox hbox = new HBox();

    Canvas canvas = new Canvas(100,100);

    GraphicsContext g2d = canvas.getGraphicsContext2D();

    g2d.fillRect(15, 15, 60, 10);
    g2d.fillRect(15.5, 30, 60, 10);  // supposed to be blurry
    g2d.fillRect(16, 45, 60, 10);
    g2d.fillRect(16.5, 60, 60, 10);  // supposed to be blurry

    g2d.setStroke(Color.RED);
    g2d.strokeLine(13.5, 13.5, 13.5, 93.5);
    g2d.strokeLine(13.5, 13.5, 93.5, 93.5);
    hbox.getChildren().add(new Group(canvas));

    Scene scene = new Scene(hbox);

    stage.setScene(scene);
    stage.show();

    canvas.scaleXProperty().bind(new SimpleDoubleProperty(1.0).divide(scene.getWindow().outputScaleXProperty()));
    canvas.scaleYProperty().bind(new SimpleDoubleProperty(1.0).divide(scene.getWindow().outputScaleYProperty()));
  }
}

Here is how it looks on a 150% monitor (left side is scaled up 5 times so you can see there is no blur):这是它在 150% 显示器上的样子(左侧放大了 5 倍,因此您可以看到没有模糊):

在此处输入图像描述

And here is how it looks at 125% with the updated example:以下是更新后的示例显示为 125% 的样子:

在此处输入图像描述

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM