JavaFX - 在 Canvas 上以高 dpi 使用矩形绘图时避免模糊

JavaFX - avoid blur when drawing with rect on Canvas with highdpi

我想在 JavaFX 上绘图 Canvas。 canvas 的坐标由双精度值提供,但我知道为了 "snap" 到像素网格,我需要整数以避免绘制 "inbetween" 像素,这将导致blur/smoothing 在边上。

考虑这个最小的例子:

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

}

这有效并且会产生清晰的边缘,考虑这张图片:

现在假设启用了 HighDPI,即考虑我们是否将图形缩放 Windows 10% 到 125%。这将导致比例因子为 1.25(我们可以通过 getOutputScaleX/Y 获得)。

然而,整个 canvas 会根据该因子进行缩放,我们会变得模糊。查看附加的图像(但你必须缩放,并且可能在图形程序而不是浏览器中查看它们)。

然后我想到我们可以调整原始坐标,使缩放后的坐标结果为整数。例如,为了确保我们在缩放后的图像上命中偏移量为 9 * 1.25 = 11.25 的整数,让我们尝试以 11.0 为目标,即更改此行

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

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

但这仍然导致边缘模糊。

我们如何解决这个问题?理想情况下,我想为 canvas 完全关闭 dpi 缩放,并进行我自己的(像素完美)计算。这可能吗?还有其他解决方案吗?

drawImage有一个参数setSmoothing,但是直接画形状(比如rect)就没有了。

您可以通过消除将 Canvas 缩放到较小尺寸所使用的缩放来解决此问题。这是一个示例(请注意,在 JavaFX 中绘制线条 "in between" 像素,因此要使它们清晰,您需要添加 0.5)。

此示例还在缩放的 Canvas 周围放置了一个 Group。这个需要在布局的时候考虑到比例,但是对渲染也有一定的影响。此示例在 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()));
  }
}

这是它在 150% 显示器上的样子(左侧放大了 5 倍,因此您可以看到没有模糊):

下面是更新后的示例在​​ 125% 时的样子: