javafx - 从 JavaFX Canvas 中删除抗锯齿形状

标签 javafx canvas

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

模拟每秒数十次绘制数百或数千个对象,然后删除它们并在新位置重新绘制它们,有效地为它们设置动画。我正在为 UI 的那部分使用 Canvas 对象。删除是通过用背景颜色重新绘制对象来完成的。我所看到的是删除对象是不完整的。尽管如此,还是留下了一种“光环”。

下面的程序说明了这个问题。单击“绘制”按钮会导致它使用前景色在 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方法删除整个Canvas似乎是合理的,但这意味着如果要重绘任何东西都必须重绘。我想可以通过删除和重新绘制 Canvas 的一小部分来优化重新绘制,但如果没有必要,我不想这样做。

程序显示的放大部分表明它确实具有抗锯齿效果。使用 SceneAntialiasing.DISABLED 参数构建场景似乎没有任何效果。

尝试按照 this question 中的建议关闭图像平滑没有帮助。

是否可以通过在背景颜色中重新绘制来删除 Canvas 上绘制的单个形状?

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

最佳答案

为方便起见,请注意 GraphicsContextfillOvalstrokeOval() 之间的区别.您可以根据合适的 bool 值有条件地删除 drawCircles() 中的轮廓:

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

尝试一些有代表性的形状,例如fillRect,验证想要的结果。

在我看来,更好的选择是采用erase -> render 策略。查看完整示例 herehere可以帮助您确定该方法是否可扩展到您的用例。另见此相关 examination重采样工件。

权宜之计,经过测试:

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 - 从 JavaFX Canvas 中删除抗锯齿形状,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/70634272/

相关文章:

java - 将类实现接口(interface)传递给另一个实现相同接口(interface)的构造函数?

css - TableView - RowFactory 不应用 CSS 样式

javascript - fillRect(0,0,0,1) 和 clearRect() 有什么区别

javascript - 如何解决 HTML-canvas 大小为奇数的问题?

java - JavaFX ObservableList 或 ReactFX LiveList 的实时映射

css - 从哪里获得所有 JavaFX CSS 属性名称的列表?

java - 如何让我的弹跳球移动?

javascript - FabricJS 动画 :selected object on canvas during mouse:up and :down

javascript - JS Canvas getContext然后transferControlToOffscreen

javascript - html5 Canvas 圆形调色板