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?

Saturn
  • 17,888
  • 49
  • 145
  • 271

1 Answers1

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.

Roland
  • 18,114
  • 12
  • 62
  • 93
  • The right side looks thicker than the left side. You can even see it in your image. – Saturn Nov 30 '15 at 06:30
  • 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? – Roland Nov 30 '15 at 06:55
  • 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. – Saturn Nov 30 '15 at 08:20