当前位置: 首页 > 知识库问答 >
问题:

从 JavaFX 画布中擦除抗锯齿形状

狄晟睿
2023-03-14

我继承了一个模拟程序来扩展新功能。原文是使用AWT图形库编写的小程序。在添加新功能之前,我想使程序适应桌面并使用JavaFX而不是AWT。

模拟每秒绘制数百或数千个对象数十次,然后擦除它们并在新位置重新绘制它们,从而有效地对它们进行动画处理。我正在为UI的该部分使用Canvas对象。擦除是通过用背景色重新绘制对象来完成的。然而,我所看到的是擦除对象是不完整的。不过,一种“光环”被抛在了脑后。

下面的程序说明了这个问题。单击“绘制”按钮会使其使用前景色在画布上绘制数百个圆圈。绘制后,再次单击按钮将通过以背景颜色重新绘制圆圈来擦除圆圈。多次绘制/擦除循环将建立“幽灵”图像的可见背景。

package com.clartaq.antialiasingghosts;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.util.Random;

public class Main extends Application {

    static final int NUM_CIRCLES = 500;
    static final int CIRCLE_DIAMETER = 10;
    static final double PANEL_WIDTH = 75.0;
    static final double PANEL_HEIGHT = 40.0;
    static final Color FG_COLOR = Color.rgb(10, 0, 200);
    static final Color BG_COLOR = Color.rgb(255, 255, 255);
    static final double BUTTON_WIDTH = 50.0;

    GraphicsContext gc;

    Random rand = new Random();

    double[] px = new double[NUM_CIRCLES];
    double[] py = new double[NUM_CIRCLES];

    void randomizeParticlePositions() {
        for (int i = 0; i < NUM_CIRCLES; i++) {
            px[i] = rand.nextDouble() * PANEL_WIDTH;
            py[i] = rand.nextDouble() * PANEL_HEIGHT;
        }
    }

    void drawCircles(Color color) {
        gc.setFill(color);
        for (int i = 0; i < NUM_CIRCLES; i++) {
            var screenX = px[i] * CIRCLE_DIAMETER;
            var screenY = py[i] * CIRCLE_DIAMETER;
            gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
        }
    }

    @Override
    public void start(Stage stage) {
        String javaVersion   = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");

        stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");

        Label versionLabel = new Label("JavaFX " + javafxVersion
                + ", running on Java " + javaVersion + ".");

        double canvasWidth  = (PANEL_WIDTH * CIRCLE_DIAMETER);
        double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
        Canvas canvasRef    = new Canvas(canvasWidth, canvasHeight);
        gc = canvasRef.getGraphicsContext2D();

        Button deBtn = new Button("Draw");
        deBtn.setPrefWidth(BUTTON_WIDTH);
        deBtn.setOnAction(e -> {
            String txt = deBtn.getText();
            switch (txt) {
                case "Draw" -> {
                    randomizeParticlePositions();
                    drawCircles(FG_COLOR);
                    deBtn.setText("Erase");
                }
                case "Erase" -> {
                    drawCircles(BG_COLOR);
                    deBtn.setText("Draw");
                }
                default -> Platform.exit();
            }
        });

        Button exBtn = new Button("Exit");
        exBtn.setPrefWidth(BUTTON_WIDTH);
        exBtn.setOnAction(e -> Platform.exit());

        TilePane tp = new TilePane();
        tp.setAlignment(Pos.CENTER);
        tp.setHgap(10);
        tp.getChildren().addAll(deBtn, exBtn);

        VBox root = new VBox();
        root.setPadding(new Insets(7));
        root.setSpacing(10);
        root.setAlignment(Pos.CENTER);
        root.getChildren().addAll(versionLabel, canvasRef, tp);

        StackPane      sp = new StackPane(root);
        BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
        Background     bg = new Background(bf);
        sp.setBackground(bg);

        Scene scene = new Scene(sp, 640.0, 480.0);

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

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

}

擦除时,我可以通过将圆圈的直径扩大2个像素来获得良好的擦除效果。当然,这也会影响附近的形状。

此外,使用fillRect方法擦除整个画布似乎是合理的,但这意味着如果必须重新绘制任何内容,则必须重新绘制所有内容。我想可以通过擦除和重新绘制画布的较小部分来优化重新绘制,但如果没有必要,我不想这样做。

放大程序显示的部分显示,这确实是一种抗锯齿效果。用场景化构建场景。禁用的参数似乎没有任何效果。

尝试关闭此问题中建议的图像平滑没有帮助。

是否可以通过用背景色重新绘制来擦除画布上绘制的单个形状?

我正在使用Java 17.0.1,JavaFX 17.0.1和5K Mac显示器(如果相关)。

共有1个答案

都建树
2023-03-14

为了方便起见,请注意图形上下文中 fillOvalstrokeOval() 之间的区别。您可以有条件地擦除 drawCircles() 中的轮廓,将其作为合适布尔值的函数:

if (stroke) {
    gc.setStroke(BG_COLOR);
    gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
}

尝试一些有代表性的形状,例如< code>fillRect,以验证所需的结果。

一个更好的选择,IMO,是追求擦除 -

经测试的权宜之计:

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.*;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

import java.util.Random;

public class Main extends Application {

    static final int NUM_CIRCLES = 500;
    static final int CIRCLE_DIAMETER = 10;
    static final double PANEL_WIDTH = 75.0;
    static final double PANEL_HEIGHT = 40.0;
    static final Color FG_COLOR = Color.rgb(10, 0, 200);
    static final Color BG_COLOR = Color.rgb(255, 255, 255);
    static final double BUTTON_WIDTH = 50.0;

    GraphicsContext gc;

    Random rand = new Random();
    private boolean stroke;

    double[] px = new double[NUM_CIRCLES];
    double[] py = new double[NUM_CIRCLES];

    void randomizeParticlePositions() {
        for (int i = 0; i < NUM_CIRCLES; i++) {
            px[i] = rand.nextDouble() * PANEL_WIDTH;
            py[i] = rand.nextDouble() * PANEL_HEIGHT;
        }
    }

    void drawCircles(Color color) {
        gc.setFill(color);
        for (int i = 0; i < NUM_CIRCLES; i++) {
            var screenX = px[i] * CIRCLE_DIAMETER;
            var screenY = py[i] * CIRCLE_DIAMETER;
            gc.fillOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
            if (stroke) {
                gc.setStroke(BG_COLOR);
                gc.strokeOval(screenX, screenY, CIRCLE_DIAMETER, CIRCLE_DIAMETER);
            }
        }
    }

    @Override
    public void start(Stage stage) {
        String javaVersion = System.getProperty("java.version");
        String javafxVersion = System.getProperty("javafx.version");

        stage.setTitle("AntiAliasingGhosts -- erasing objects leaves ghosts in JavaFX");

        Label versionLabel = new Label("JavaFX " + javafxVersion
            + ", running on Java " + javaVersion + ".");

        double canvasWidth = (PANEL_WIDTH * CIRCLE_DIAMETER);
        double canvasHeight = (PANEL_HEIGHT * CIRCLE_DIAMETER);
        Canvas canvasRef = new Canvas(canvasWidth, canvasHeight);
        gc = canvasRef.getGraphicsContext2D();

        Button deBtn = new Button("Draw");
        deBtn.setPrefWidth(BUTTON_WIDTH);
        deBtn.setOnAction(e -> {
            String txt = deBtn.getText();
            switch (txt) {
                case "Draw" -> {
                    randomizeParticlePositions();
                    drawCircles(FG_COLOR);
                    deBtn.setText("Erase");
                    stroke = true;
                }
                case "Erase" -> {
                    drawCircles(BG_COLOR);
                    deBtn.setText("Draw");
                    stroke = false;
                }
                default ->
                    Platform.exit();
            }
        });

        Button exBtn = new Button("Exit");
        exBtn.setPrefWidth(BUTTON_WIDTH);
        exBtn.setOnAction(e -> Platform.exit());

        TilePane tp = new TilePane();
        tp.setAlignment(Pos.CENTER);
        tp.setHgap(10);
        tp.getChildren().addAll(deBtn, exBtn);

        VBox root = new VBox();
        root.setPadding(new Insets(7));
        root.setSpacing(10);
        root.setAlignment(Pos.CENTER);
        root.getChildren().addAll(versionLabel, canvasRef, tp);

        StackPane sp = new StackPane(root);
        BackgroundFill bf = new BackgroundFill(BG_COLOR, CornerRadii.EMPTY, Insets.EMPTY);
        Background bg = new Background(bf);
        sp.setBackground(bg);

        Scene scene = new Scene(sp, 640.0, 480.0);

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

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

}
 类似资料:
  • 目前我正在使用JavaFX开发一个2D游戏,游戏是像素艺术。不幸的是,像素艺术是模糊的,这是由抗锯齿引起的。 有没有办法在JavaFX画布上禁用抗锯齿?我试过用SceneAntialiasing。残废了,没用。我找不到其他方法来关闭它。

  • 在学习渲染的旅途中,你可能会时不时遇到模型边缘有锯齿的情况。这些锯齿边缘(Jagged Edges)的产生和光栅器将顶点数据转化为片段的方式有关。在下面的例子中,你可以看到,我们只是绘制了一个简单的立方体,你就能注意到它存在锯齿边缘了: 可能不是非常明显,但如果你离近仔细观察立方体的边缘,你就应该能够看到锯齿状的图案。如果放大的话,你会看到下面的图案: 这很明显不是我们想要在最终程序中所实现的效果

  • 问题内容: 我正在创建一些形状,尽管没有应用效果,但所有内容似乎都变得模糊了,例如抗锯齿。 例如,在具有1像素宽度的黑色背景上绘制的白线呈现为灰色!将宽度更改为2px会导致白色,但定义不清晰。 搜索时,返回了形状的方法,但是调用它没有区别。 我应该怎么更改或停用或? 问题答案: 请参阅形状文档: 大多数节点倾向于仅对它们应用整数转换,并且通常还使用整数坐标定义它们。对于这种常见情况,具有直线边缘的

  • 本文向大家介绍Android编程画图之抗锯齿解决方法,包括了Android编程画图之抗锯齿解决方法的使用技巧和注意事项,需要的朋友参考一下 本文实例分析了Android编程画图之抗锯齿解决方法。分享给大家供大家参考,具体如下: 在画图的时候,图片如果旋转或缩放之后,总是会出现那些华丽的锯齿。其实Android自带了解决方式。 方法一:给Paint加上抗锯齿标志。然后将Paint对象作为参数传给ca

  • 问题内容: 对于PIL中的线条和椭圆,图像是粗糙的。 我发现仅在调整大小和缩略图中使用了抗锯齿功能。 绘制直线或椭圆形时,有什么方法可以进行抗锯齿吗? 问题答案: 本地执行此操作的唯一方法是使用超级采样。以所需大小的倍数渲染图像,然后使用进行渲染。

  • 我在我的项目中使用了InkCanvas,我注意到每当我画东西时,笔划都非常尖锐,一旦我松开鼠标按钮,它就会变得模糊。 有没有什么方法可以使笔划与绘制时的样子保持一致? 现在,我正在检查从图像中获得的阵列,并删除任何与颜色不完全匹配的像素。红色(即ARGB:255255,0,0)。。。我相信有一种更聪明的方法可以做到这一点! 提前谢谢。