user1498611
user1498611

Reputation: 335

JavaFX : Canvas to Image in non GUI Thread

I have to visualize lot of data (real-time) and I am using JavaFX 2.2. So I have decided to "pre-visualize" data before they are inserted into GUI thread.

In my opinion the fastest way to do it (with antialliasing etc.) is let some NON GUI thread to generate image/bitmap and then put in GUI thread (so the UI is still responsive for user).

But I can't find way how to conver Canvas to Image and then use:

Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);

Platform.runLater(new Runnable() {

            @Override
            public void run() {
                canvas.getGraphicsContext2D().drawImage(imageToDraw, data.offsetX, data.offsetY);
            }
        }); 

Thx for some usable answers. :-)

btw: I have made test app to show my problem.

package canvasandthreads02;

import java.util.Random;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.canvas.Canvas;
import javafx.scene.canvas.GraphicsContext;
import javafx.scene.control.Button;
import javafx.scene.image.Image;
import javafx.scene.layout.AnchorPane;
import javafx.scene.paint.Color;
import javafx.stage.Stage;

public class CanvasAndThreads02 extends Application {

@Override
public void start(Stage primaryStage) {
    Button btn = new Button();
    btn.setText("Paint");


    final AnchorPane root = new AnchorPane();
    final Canvas canvas = new Canvas(900, 800);
    canvas.setLayoutX(50);
    canvas.setLayoutY(50);
    root.getChildren().add(canvas);
    root.getChildren().add(btn);

    Scene scene = new Scene(root, 900, 800);

    primaryStage.setTitle("Painting in JavaFX");
    primaryStage.setScene(scene);
    primaryStage.show();

    btn.setOnAction(new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            System.out.println("Start painting");
            /**
             * Start Thread where some data will be visualized
             */
            new Thread(new PainterThread(canvas, new DataToPaint())).start();
        }
    });
}

private class PainterThread implements Runnable{
    private final DataToPaint data;
    private final Canvas canvas;
    public PainterThread(Canvas canvas, DataToPaint data){
        this.canvas = canvas;
        this.data = data;
    }

    @Override
    public void run() {
        long currentTimeMillis = System.currentTimeMillis();

        Canvas tmpCanvas = new Canvas(data.width, data.height);
        GraphicsContext graphicsContext2D = tmpCanvas.getGraphicsContext2D();
        graphicsContext2D.setFill(data.color;);
        for (int i = 0; i < data.height; i++) {
            for (int j = 0; j < data.width; j++) {
                graphicsContext2D.fillRect(j, i, 1, 1); //draw 1x1 rectangle
            }
        }

        /**
         * And now I need still in this Thread convert tmpCanvas to Image,
         * or use some other method to put result to Main GIU Thread using Platform.runLater(...);
         */
        final Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);

        System.out.println("Canvas painting: " + (System.currentTimeMillis()-currentTimeMillis));
        Platform.runLater(new Runnable() {

            @Override
            public void run() {
                //Start painting\n Canvas painting: 430 \n Time to convert:62
                //long currentTimeMillis1 = System.currentTimeMillis();
                //Image imageToDraw = tmpCanvas.snapshot(null, null);
                //System.out.println("Time to convert:" + (System.currentTimeMillis()-currentTimeMillis1));
                canvas.getGraphicsContext2D().drawImage(imageToDraw, data.offsetX, data.offsetY);
            }
        });     
    }
}

private class DataToPaint{
    double offsetX = 0;
    double offsetY = 0;
    Color color;
    int width = 500;
    int height = 250;

    public DataToPaint(){
        Random rand = new Random();
        color = new Color(rand.nextDouble(), rand.nextDouble(), rand.nextDouble(), rand.nextDouble());
        offsetX = rand.nextDouble() * 20;
        offsetY = rand.nextDouble() * 20;
    }
}

/**
 * @param args the command line arguments
 */
public static void main(String[] args) {
    launch(args);
}
}

Upvotes: 13

Views: 9613

Answers (3)

selami tastan
selami tastan

Reputation: 102

it is old issue, but It might be useful to someone in the future.

    public static Image layeredImageNotInFXThread(List<Image> images){
    if(images == null || images.isEmpty()){
        return null;
    }
    int w = 0, h = 0;
    List<BufferedImage> bufferedImages = new ArrayList<>();
    for(Image pImage : images){
        BufferedImage bImage = SwingFXUtils.fromFXImage(pImage, null);
        bufferedImages.add(bImage);
        // create the new image, canvas size is the max. of both image sizes
        w = Math.max(w, bImage.getWidth());
        h = Math.max(h, bImage.getHeight());
    }
    BufferedImage bufferedImage = layeredBufferedImage(bufferedImages, w, h);
    Image image = SwingFXUtils.toFXImage(bufferedImage, null);
    return image;
}

/**
 * Must be run Java FX tThread
 * @param images
 * @return
 */
public static Image layeredImage(List<Image> images){
    if(images == null || images.isEmpty()){
        return null;
    }
    Image image = null;
    Canvas canvas = new Canvas(images.get(0).getWidth(), images.get(0).getHeight());
    //WritableImage writableImage = new WritableImage((int)images.get(0).getWidth(), (int)images.get(0).getHeight());
    //writableImage.getPixelWriter();
   // writableImage
    //canvas.snapshot(null, writableImage);
    GraphicsContext gc = canvas.getGraphicsContext2D();
    for(Image pImage : images){
        gc.drawImage(pImage, 0, 0);
    }
    SnapshotParameters snapshotParameters = new SnapshotParameters();
    snapshotParameters.setFill(Color.TRANSPARENT);
    image = canvas.snapshot(snapshotParameters, null);
    return image;
}

public static BufferedImage layeredBufferedImage(List<BufferedImage> images, int width, int height){
    //BufferedImage bImage = SwingFXUtils.fromFXImage(view.getImage(), null);
    if(images == null || images.isEmpty()){
        return null;
    }
    BufferedImage bufferedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_ARGB);
    // paint both images, preserving the alpha channels
    Graphics gc = bufferedImage.getGraphics();
    for(BufferedImage pImage : images){
        gc.drawImage(pImage, 0, 0, null);
    }
    gc.dispose();
    // Save as new image
    //ImageIO.write(bufferedImage, "PNG", new File(path, "bufferedImage.png"));
    return bufferedImage;
}

public static void main(String[] args) {
    new JFXPanel(); //  this will prepare JavaFX toolkit and environment
    Image image1 = new Image("/icons/drone.png");
    Image image2 = new Image("/icons/icons8_lock_100px.png");
    List<Image> images = new ArrayList<>();
    images.add(image1);
    images.add(image2);
    //Image layeredImage = layeredImage(images);
    Image layeredImage = layeredImageNotInFXThread(images);
    // Write snapshot to file system as a .png image
    File outFile = new File("D://tmp//layeredImage.png");
    try {
        ImageIO.write(SwingFXUtils.fromFXImage(layeredImage, null),
                "png", outFile);
    } catch (IOException ex) {
        System.out.println(ex.getMessage());
    }
}

Upvotes: 1

EzPizza
EzPizza

Reputation: 1160

I know this is a really old question, but just for anyone who cares: There is now a second version of canvas.snapshot that takes a callback and works asynchronously!

public void snapshot(Callback<SnapshotResult,Void> callback,
                     SnapshotParameters params,
                     WritableImage image)

Upvotes: 3

Wyte
Wyte

Reputation: 29

use Canvas' snapshot(...) method to create a WritableImage from the Canvas' content. ^^ Works fine for me.

Upvotes: 2

Related Questions