简体   繁体   中英

How to create filling animation in JavaFX(pixel by pixel) canvas?

I am struggling with my school-project on Topology of Digital Images. The goal is to write an app that will have a rectangle, with a line crossing through it and the app needs to fill the field below the line. It needs to fill it pixel by pixel using a certain type of neigbourhood of the pixel.

I managed to write some app that fills the field below the line, but it fills it all in a sec. I can't manage to have it done pixel by pixel. I tried stopping the thread, but you can't stop the application thread, you cannot update the UI from the thread other than application. Platform.runLater or Task doesn't work either. I want the canvas to refresh everytime new pixels are added, so I has this animation showing of how this algorithm works.

I hope my explanation will be sufficient. You don't have to focus on my project, just can someone explain to me how to do this pixel by pixel filling in canvas?

Here's a code of a controller:

@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;
}

}

Now I know the code may doesn't seem pretty obvious at first. The algorithm doesn't matter, because it does seem to work, but the main question is: how do I get this field filled pixel by pixel(like an animation(yes, I tried AnimationTimer as well).

There are several ways, one would be to use an AnimationTimer like this:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM