How to copy contents of one canvas to another?
Asked Answered
R

1

2

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

enter image description here

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:

enter image description here

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?

Rew answered 29/11, 2015 at 21:53 Comment(0)
D
4

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().

enter image description here

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:

enter image description here

If you remove the

params.setFill(Color.TRANSPARENT);  

you'll get a 1:1 copy:

enter image description here

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

Dorn answered 30/11, 2015 at 6:19 Comment(3)
The right side looks thicker than the left side. You can even see it in your image.Rew
You're right, I didn't notice, it's not as blurry as yours. You see it at a zoomed in pixel level. I modified the answer. Looks like the problem is the transparent fill color in the SnapshotParameters. If you don't use it, does it copy properly on your system?Dorn
That's right, it works now. Although it's a shame because I do require the transparent fill color for some cases. Anyway, I changed project approach so I don't quite need this anymore. Thanks.Rew

© 2022 - 2024 — McMap. All rights reserved.