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% 时的样子:
我想在 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% 时的样子: