如何在 JavaFX 中创建填充动画(逐像素)canvas?
How to create filling animation in JavaFX(pixel by pixel) canvas?
我正在努力完成有关数字图像拓扑的学校项目。目标是编写一个应用程序,它有一个矩形,一条线穿过它,应用程序需要填充线下方的字段。它需要使用特定类型的像素邻域逐个像素地填充它。
我设法编写了一些应用程序来填充该行下方的字段,但它会在几秒钟内将其全部填满。我无法逐个像素地完成它。我试过停止线程,但你不能停止应用程序线程,你不能从应用程序以外的线程更新 UI。 Platform.runLater 或任务也不起作用。我希望 canvas 在每次添加新像素时刷新,所以我有这个动画展示了这个算法是如何工作的。
我希望我的解释足够了。您不必专注于我的项目,有人可以向我解释如何逐像素填充 canvas?
这是控制器的代码:
@FXML
private Canvas canvas;
@FXML
private ComboBox<String> optionsType;
@FXML
private Button drawBtn;
@FXML
private ComboBox<String> optionsNeighbourhood;
WritableImage wi;
PixelReader pr;
GraphicsContext gc;
(...)
public void drawLine() {
gc.strokeLine(0, 0, canvas.getWidth(), canvas.getHeight());
}
private void fillShape(Neighbourhood type, int startX, int startY) {
int width = (int) canvas.getWidth();
int height = (int) canvas.getHeight();
LinkedList<Point> list = new LinkedList<>();
list.addFirst(new Point(startX, startY));
while (!list.isEmpty()) {
Point point = list.removeLast();
int x = point.getX();
int y = point.getY();
if (x >= 0 && x < width && y >= 0 && y < height && isEmpty(x, y)) {
gc.fillRect(x, y, 1, 1);
gc.fill();
canvas.snapshot(null, wi);
list.addFirst(new Point(x + 1, y));
list.addFirst(new Point(x, y + 1));
list.addFirst(new Point(x, y - 1));
list.addFirst(new Point(x - 1, y));
if (type == Neighbourhood.SIX || type == Neighbourhood.EIGHT) {
if (isEmpty(x - 1, y) || isEmpty(x, y - 1)) {
list.add(new Point(x - 1, y - 1));
}
if (isEmpty(x + 1, y) || isEmpty(x, y + 1)) {
list.add(new Point(x + 1, y + 1));
}
}
if (type == Neighbourhood.EIGHT) {
if (isEmpty(x - 1, y) || isEmpty(x, y + 1)) {
list.add(new Point(x - 1, y + 1));
}
if (isEmpty(x + 1, y) || isEmpty(x, y + 1)) {
list.add(new Point(x + 1, y - 1));
}
}
}
}
}
private boolean isEmpty(int x, int y) {
if (x < 0 || x >= canvas.getWidth() || y < 0 || y >= canvas.getHeight()) {
return false;
}
if (!pr.getColor(x, y).toString().equals("0xffffffff")) {
return false;
}
return true;
}
}
现在我知道代码一开始可能看起来不太明显。该算法无关紧要,因为它似乎确实有效,但主要问题是:我如何逐个像素地填充这个字段(就像动画一样(是的,我也试过 AnimationTimer)。
有几种方法,一种是像这样使用 AnimationTimer:
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
private static double SCENE_WIDTH = 800;
private static double SCENE_HEIGHT = 600;
static Random random = new Random();
Canvas canvas;
GraphicsContext graphicsContext;
AnimationTimer loop;
Scene scene;
@Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
canvas = new Canvas(SCENE_WIDTH, SCENE_HEIGHT);
graphicsContext = canvas.getGraphicsContext2D();
Pane layerPane = new Pane();
layerPane.getChildren().addAll(canvas);
root.setCenter(layerPane);
scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
startAnimation();
}
private void startAnimation() {
loop = new AnimationTimer() {
double startX = 100;
double endX = 200;
double y = 100;
double x = startX;
double speed = 0.2;
@Override
public void handle(long now) {
graphicsContext.fillOval(x, y, 5,5);
x+=speed;
if( x >= endX) {
loop.stop();
}
}
};
loop.start();
}
public static void main(String[] args) {
launch(args);
}
}
我正在努力完成有关数字图像拓扑的学校项目。目标是编写一个应用程序,它有一个矩形,一条线穿过它,应用程序需要填充线下方的字段。它需要使用特定类型的像素邻域逐个像素地填充它。
我设法编写了一些应用程序来填充该行下方的字段,但它会在几秒钟内将其全部填满。我无法逐个像素地完成它。我试过停止线程,但你不能停止应用程序线程,你不能从应用程序以外的线程更新 UI。 Platform.runLater 或任务也不起作用。我希望 canvas 在每次添加新像素时刷新,所以我有这个动画展示了这个算法是如何工作的。
我希望我的解释足够了。您不必专注于我的项目,有人可以向我解释如何逐像素填充 canvas?
这是控制器的代码:
@FXML
private Canvas canvas;
@FXML
private ComboBox<String> optionsType;
@FXML
private Button drawBtn;
@FXML
private ComboBox<String> optionsNeighbourhood;
WritableImage wi;
PixelReader pr;
GraphicsContext gc;
(...)
public void drawLine() {
gc.strokeLine(0, 0, canvas.getWidth(), canvas.getHeight());
}
private void fillShape(Neighbourhood type, int startX, int startY) {
int width = (int) canvas.getWidth();
int height = (int) canvas.getHeight();
LinkedList<Point> list = new LinkedList<>();
list.addFirst(new Point(startX, startY));
while (!list.isEmpty()) {
Point point = list.removeLast();
int x = point.getX();
int y = point.getY();
if (x >= 0 && x < width && y >= 0 && y < height && isEmpty(x, y)) {
gc.fillRect(x, y, 1, 1);
gc.fill();
canvas.snapshot(null, wi);
list.addFirst(new Point(x + 1, y));
list.addFirst(new Point(x, y + 1));
list.addFirst(new Point(x, y - 1));
list.addFirst(new Point(x - 1, y));
if (type == Neighbourhood.SIX || type == Neighbourhood.EIGHT) {
if (isEmpty(x - 1, y) || isEmpty(x, y - 1)) {
list.add(new Point(x - 1, y - 1));
}
if (isEmpty(x + 1, y) || isEmpty(x, y + 1)) {
list.add(new Point(x + 1, y + 1));
}
}
if (type == Neighbourhood.EIGHT) {
if (isEmpty(x - 1, y) || isEmpty(x, y + 1)) {
list.add(new Point(x - 1, y + 1));
}
if (isEmpty(x + 1, y) || isEmpty(x, y + 1)) {
list.add(new Point(x + 1, y - 1));
}
}
}
}
}
private boolean isEmpty(int x, int y) {
if (x < 0 || x >= canvas.getWidth() || y < 0 || y >= canvas.getHeight()) {
return false;
}
if (!pr.getColor(x, y).toString().equals("0xffffffff")) {
return false;
}
return true;
}
}
现在我知道代码一开始可能看起来不太明显。该算法无关紧要,因为它似乎确实有效,但主要问题是:我如何逐个像素地填充这个字段(就像动画一样(是的,我也试过 AnimationTimer)。
有几种方法,一种是像这样使用 AnimationTimer:
import java.util.Random;
import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;
public class Main extends Application {
private static double SCENE_WIDTH = 800;
private static double SCENE_HEIGHT = 600;
static Random random = new Random();
Canvas canvas;
GraphicsContext graphicsContext;
AnimationTimer loop;
Scene scene;
@Override
public void start(Stage primaryStage) {
BorderPane root = new BorderPane();
canvas = new Canvas(SCENE_WIDTH, SCENE_HEIGHT);
graphicsContext = canvas.getGraphicsContext2D();
Pane layerPane = new Pane();
layerPane.getChildren().addAll(canvas);
root.setCenter(layerPane);
scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT);
primaryStage.setScene(scene);
primaryStage.show();
startAnimation();
}
private void startAnimation() {
loop = new AnimationTimer() {
double startX = 100;
double endX = 200;
double y = 100;
double x = startX;
double speed = 0.2;
@Override
public void handle(long now) {
graphicsContext.fillOval(x, y, 5,5);
x+=speed;
if( x >= endX) {
loop.stop();
}
}
};
loop.start();
}
public static void main(String[] args) {
launch(args);
}
}