How to copy contents of one canvas to another?

Have a Canvas. Draw something (in my case, several red lines).

I want to quite literally copy the contents of this canvas to another. This is what I do:

SnapshotParameters params = new SnapshotParameters();
params.setFill(Color.TRANSPARENT);         
WritableImage image = firstCanvas.snapshot(params, null);
secondCanvas.getGraphicsContext2D().drawImage(image, 0, 0);

And this is what you get in the second canvas:

It is blurred. It is antialiased I guess.

I imagine it may be because I am using a Macbook Pro, Retina Display.

What can I do to properly copy the contents of one canvas to another?

Answers


Here's example code which lets you paint on the left side and which mirrors the canvas at runtime on the right side.

import java.util.Random;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.geometry.Point2D;
import javafx.scene.Node;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.image.Image;
import javafx.scene.image.WritableImage;
import javafx.scene.input.MouseEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.paint.Color;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class Main extends Application {

    private static double SCENE_WIDTH = 1280;
    private static double SCENE_HEIGHT = 720;

    static Random random = new Random();

    Canvas canvas;
    Canvas copyCanvas;
    GraphicsContext graphicsContext;
    GraphicsContext copyGraphicsContext;

    AnimationTimer loop;

    Point2D mouseLocation = new Point2D( 0, 0);
    boolean mousePressed = false;
    Point2D prevMouseLocation = new Point2D( 0, 0);

    Scene scene;

    Image brush = createBrush( 30.0, Color.CHOCOLATE);
    double brushWidthHalf = brush.getWidth() / 2.0;
    double brushHeightHalf = brush.getHeight() / 2.0;



    @Override
    public void start(Stage primaryStage) {

        BorderPane root = new BorderPane();

        canvas = new Canvas( SCENE_WIDTH / 2, SCENE_HEIGHT);
        graphicsContext = canvas.getGraphicsContext2D();

        copyCanvas = new Canvas( SCENE_WIDTH / 2, SCENE_HEIGHT);
        copyGraphicsContext = canvas.getGraphicsContext2D();

        HBox hBox = new HBox();
        hBox.getChildren().addAll(canvas, copyCanvas);

        root.setCenter(hBox);

        scene = new Scene(root, SCENE_WIDTH, SCENE_HEIGHT);

        primaryStage.setScene(scene);
        primaryStage.show();

        addListeners();

        startAnimation();


    }

    private void startAnimation() {

        loop = new AnimationTimer() {

            @Override
            public void handle(long now) {

                if( mousePressed) {

                    // try this
                    // graphicsContext.drawImage( brush, mouseLocation.getX() - brushWidthHalf, mouseLocation.getY() - brushHeightHalf);

                    // then this
                    bresenhamLine( prevMouseLocation.getX(), prevMouseLocation.getY(), mouseLocation.getX(), mouseLocation.getY());

                }

                prevMouseLocation = new Point2D( mouseLocation.getX(), mouseLocation.getY());

                copyCanvas();
            }
        };

        loop.start();

    }

    private void copyCanvas() {

        SnapshotParameters params = new SnapshotParameters();
        params.setFill(Color.TRANSPARENT);         
        WritableImage image = canvas.snapshot(params, null);
        copyCanvas.getGraphicsContext2D().drawImage(image, 0, 0);

    }

    // https://de.wikipedia.org/wiki/Bresenham-Algorithmus
    private void bresenhamLine(double x0, double y0, double x1, double y1)
    {
      double dx =  Math.abs(x1-x0), sx = x0<x1 ? 1. : -1.;
      double dy = -Math.abs(y1-y0), sy = y0<y1 ? 1. : -1.;
      double err = dx+dy, e2; /* error value e_xy */

      while( true){
        graphicsContext.drawImage( brush, x0 - brushWidthHalf, y0 - brushHeightHalf);
        if (x0==x1 && y0==y1) break;
        e2 = 2.*err;
        if (e2 > dy) { err += dy; x0 += sx; } /* e_xy+e_x > 0 */
        if (e2 < dx) { err += dx; y0 += sy; } /* e_xy+e_y < 0 */
      }
    }


    private void addListeners() {

        scene.addEventFilter(MouseEvent.ANY, e -> {

            mouseLocation = new Point2D(e.getX(), e.getY());

            mousePressed = e.isPrimaryButtonDown();

        });


    }


    public static Image createImage(Node node) {

        WritableImage wi;

        SnapshotParameters parameters = new SnapshotParameters();
        parameters.setFill(Color.TRANSPARENT);

        int imageWidth = (int) node.getBoundsInLocal().getWidth();
        int imageHeight = (int) node.getBoundsInLocal().getHeight();

        wi = new WritableImage(imageWidth, imageHeight);
        node.snapshot(parameters, wi);

        return wi;

    }


    public static Image createBrush( double radius, Color color) {

        // create gradient image with given color
        Rectangle brush = new Rectangle(0,0,1,1);
        brush.setStroke(Color.RED);
        brush.setFill(Color.RED);

        // create image
        return createImage(brush);

    }


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

Your code is in copyCanvas().

Tested with JavaFX 8u40, Win7. Although the antialiasing isn't as intense, the copy isn't a 1:1 copy. If you compare 2 lines on a pixel-level, you'll get this:

If you remove the

params.setFill(Color.TRANSPARENT);  

you'll get a 1:1 copy:

So it seems to have something to do with the transparent fill color in the SnapshotParameters.


Need Your Help