0

Updated my question to meet the minimal reproducible example. I'm creating a simple drawing app, where user can do free drawings.

I want the user to be able to undo the last drawing on canvas. For that purpose I am saving the mouseclicked and mousedragged positions (x, y) in two ArrayLists, then using the saved positions to clear it via graphics context's clearRect() function. But clearRect() is not clearing everything. I tried saving coordinates in a HashMap and double[][] but I am getting the same result. I'll try a different approach (layered canvas) for this issue, but I really want to understand this problem. I attached below the screenshots of what is drawn and what is cleared and is redrawn back with Redo button. Related code:

import java.util.ArrayList;

import javafx.application.Application;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.Pane;
import javafx.stage.Stage;

public class DrawOnCanvas extends Application {

    Scene scene;
    Pane canvasPane, buttonPane;
    BorderPane mainPane;
    Canvas canvas;
    GraphicsContext gxc;
    Button undoButton, redoButton;
    private ArrayList<Double> xPos = new ArrayList<>();
    private ArrayList<Double> yPos = new ArrayList<>();

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

    @Override
    public void start(Stage stage) throws Exception {
        mainPane = new BorderPane();
        canvasPane = new Pane();
        buttonPane = new Pane();
        canvas = new Canvas(500, 500);
        canvasPane.setStyle("-fx-border-color: black");
        gxc = canvas.getGraphicsContext2D();
        gxc.setLineWidth(2);
        undoButton = new Button("Undo");
        redoButton = new Button("Redo");
        redoButton.relocate(200, 0);
        canvasPane.getChildren().add(canvas);
        buttonPane.getChildren().addAll(undoButton, redoButton);
        mainPane.setCenter(canvasPane);
        mainPane.setBottom(buttonPane);
        scene = new Scene(mainPane);
        startDrawing(gxc, canvas);
        undoDrawing(gxc, undoButton);
        redoDrawing(gxc, redoButton);
        stage = new Stage();
        stage.setScene(scene);
        stage.show();
    }

    public void startDrawing(GraphicsContext gc, Canvas can) {
        can.setOnMousePressed(e -> {
            gc.beginPath();
            gc.moveTo(e.getX(), e.getY());
            gc.stroke();
            xPos.add(e.getX());
            yPos.add(e.getY());
        });
        can.setOnMouseDragged(e -> {
            gc.lineTo(e.getX(), e.getY());
            gc.stroke();
            gc.closePath();
            gc.beginPath();
            gc.moveTo(e.getX(), e.getY());
            xPos.add(e.getX());
            yPos.add(e.getY());
        });
    }

    public void undoDrawing(GraphicsContext gCon, Button undo) {
        undo.setOnMouseClicked(e -> {
            for (int i = 0; i < xPos.size(); i++) {
                System.out.println(xPos.get(i) + " " + yPos.get(i));
                gCon.clearRect(xPos.get(i) - 2, yPos.get(i) - 2, 5, 5);
            }
        });
    }

    public void redoDrawing(GraphicsContext gCon, Button undo) {
        undo.setOnMouseClicked(e -> {
            for (int i = 0; i < xPos.size(); i++) {
                gCon.fillRect(xPos.get(i) - 2, yPos.get(i) - 2, 5, 5);
                System.out.println(xPos.get(i) + " " + yPos.get(i));
            }
        });
    }
}

Drawn areas on canvas: drawn

Cleared areas on canvas: cleared

Redrawn areas on canvas: redrawn

sumu00
  • 49
  • 7
  • 2
    [mcve] please .. – kleopatra Jun 30 '22 at 11:11
  • Possible duplicate of [_Erasing Antialiased Shapes from a JavaFX Canvas_](https://stackoverflow.com/q/70634272/230513). – trashgod Jun 30 '22 at 12:22
  • Maybe try a different approach. Implement a [layered canvas](https://docs.oracle.com/javase/8/javafx/graphics-tutorial/canvas.htm). Draw the last user interaction on the top canvas and save the commands for drawing it. When another action occurs, commit the previous action by replaying the commands for it to the lower canvas, then clearing the top canvas before playing the new commands to the top canvas and storing them. On undo, throw away the latest command and clear the top canvas. Just a thought and complicated I know, but that is how I would consider doing this. – jewelsea Jun 30 '22 at 23:50
  • To simplify my suggested approach, you could use a snapshot of the layers and render the snapshot image to the bottom layer (e.g. a Canvas on top of an ImageView where the Canvas just renders the latest interactions and the ImageView contains the committed view). – jewelsea Jun 30 '22 at 23:57
  • 1
    Thank you @kleopatra, I created a smaller version of my program to reproduce the problem. Can you please suggest? – sumu00 Jul 01 '22 at 11:54
  • @jewelsea, thank you, I was thinking about that too. I will implement that solution over the weekend. In the meantime, I really would like to understand what is going on with this code. Why is it not doing what I think it should do? Thanks. – sumu00 Jul 01 '22 at 11:55

0 Answers0