Reputation: 335
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
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
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
Reputation: 29
use Canvas' snapshot(...) method to create a WritableImage from the Canvas' content. ^^ Works fine for me.
Upvotes: 2