JavaFX:非 GUI 线程中将 Canvas 转为图像

13

我需要实时可视化大量数据,我正在使用JavaFX 2.2。所以在将数据插入GUI线程之前,我决定“预先可视化”它们。

在我看来,最快的方法(带有抗锯齿等)是让一些非GUI线程生成图像/位图,然后将其放入GUI线程中(因此UI仍对用户响应)。

但我找不到将Canvas转换为Image,然后使用的方法:

Image imageToDraw = convert_tmpCanvasToImage(tmpCanvas);

Platform.runLater(new Runnable() {

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

谢谢提供一些有用的答案。 :-)

顺便说一句:我已经创建了一个测试应用来展示我的问题。

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);
}
}

我很好奇您是否解决了这个问题,因为我在生成离线图表时遇到了类似的问题。 - Joachim H. Skeie
你可以看一下这个方法,使用Task<Canvas>;注意,GraphicsContext::getPixelWriter也可能会有用。 - trashgod
或者,您可以像这里所示,在“BufferedImage”中进行组合。 - trashgod
2个回答

2

使用Canvas的snapshot(...)方法从Canvas的内容创建一个WritableImage。^^ 对我来说很有效。


4
当然我试过了。但问题在于,快照只能在画布被渲染后才能进行拍摄,并且只能在应用程序(GUI)线程中完成。因此我不会节省任何时间。 - user1498611

2

我知道这是一个非常古老的问题,但是对于任何关心的人: 现在有第二个版本的canvas.snapshot,它采用回调函数并以异步方式工作!

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接