1

How to create WritableImage object using snapshot methods on javafx.scene.shape.Rectangle or javafx.scene.chart.BarChart without extending javafx.application.Application

Getting following exception

Exception in thread "main" java.lang.IllegalStateException: Not on FX application thread; currentThread = main
    at com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:238)
    at com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:400)
    at javafx.scene.Node.snapshot(Node.java:1698)
public class Example1 {
    public static void main(String[] args) throws Exception {
        new Example1 ().manipulatePdf();
    }

    protected void manipulatePdf() throws Exception {

        image1(); 
        image2();
    }

    private WritableImage image1() {

        Rectangle rectangle = new Rectangle();
        rectangle.setX(50);
        rectangle.setY(50);
        rectangle.setWidth(300);
        rectangle.setHeight(20);

        rectangle.setStroke(Color.WHITE);

        LinearGradient linearGrad = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE,           
                new Stop(0f, Color.rgb(255, 0, 0, 1)), new Stop(0.25f,
                        Color.rgb(255, 255, 0, 1)), new Stop(0.5f, Color.rgb(
                        255, 255, 255, 1)), new Stop(0.75f, Color.rgb(124, 252,
                        0, 1)), new Stop(1.0f, Color.rgb(0, 255, 0, 1)));
        rectangle.setFill(linearGrad);

        WritableImage img = new WritableImage((int) rectangle.getWidth(),
                (int) rectangle.getHeight());
        rectangle.snapshot(null, img);
        return img;

    }

    private WritableImage image2 () {

        BarChart chart = new BarChart(new CategoryAxis(), new NumberAxis());
        Random rng = new Random();
        Series series = new Series();
        series.setName("Data");
        for (int i = 1; i <= 10; i++) {
            series.getData().add(new Data("Group " + i, rng.nextDouble()));
        }
        chart.getData().add(series);
        WritableImage img = chart.snapshot(null, null);
        return img;

    }
}

did not extend javafx.application.Application, planing to create image objects using javafx

James_D
  • 201,275
  • 16
  • 291
  • 322
Venkat Roo
  • 13
  • 6

2 Answers2

1

I don't know your code, but according to the exception you get, your code is run in the wrong thread. Your code needs to be run in the JavaFX application thread:

Platform.runLater(new Runnable() {
        @Override
        public void run() {
            // your code goes here
        }
    });
user7291698
  • 1,972
  • 2
  • 15
  • 30
  • Rectangle rectangle = new Rectangle(); rectangle.setX(50); rectangle.setY(50); rectangle.setWidth(300); rectangle.setHeight(20); rectangle.setStroke(Color.WHITE); //rectangle.setFill(linearGrad); WritableImage img = new WritableImage((int) rectangle.getWidth(), (int) rectangle.getHeight()); rectangle.snapshot(null, img); – Venkat Roo Jan 17 '17 at 17:41
  • did not extend javafx.application.Application, planing to create image objects using javafx – Venkat Roo Jan 17 '17 at 17:45
  • @VenkatRoo Code is unreadable in comments, please edit the question to provide further information. – James_D Jan 17 '17 at 17:48
  • @VenkatRoo Also, it is completely unclear what your comment has to do with this (correct) answer. – James_D Jan 17 '17 at 18:13
  • Planning to create WritableImage object using Rectangle or bar chart objects in javafx, I want to store these images (WritableImage) on physical location. My standalone java class is not extends from javafx.application.Application. I hope you are clear now – Venkat Roo Jan 17 '17 at 18:22
  • Its giving "Exception in thread "main" java.lang.IllegalStateException: Toolkit not initialized" – Venkat Roo Jan 17 '17 at 18:36
  • 1
    There are a couple of ways around the issue. The first is to invoke the Application.launch() method, or simply instantiate a new JFXPanel() class (even if it isn’t used for anything). Either action will cause the JavaFX Runtime to start and avoid initialization exception when the application is started. https://www.google.com/amp/s/rterp.wordpress.com/2015/04/04/javafx-toolkit-not-initialized-solved/amp/?client=safari – purring pigeon Jan 17 '17 at 19:20
  • Please see i have edited with full code, i need to create image object and i will use image object some other purpose... – Venkat Roo Jan 17 '17 at 20:50
  • @purringpigeon Using `JFXPanel` works, but it feels like a bit of a hack. It requires *also* using the AWT toolkit, which is a fairly large additional requirement. Calling `Application.launch()` is difficult, but probably better overall. – James_D Jan 17 '17 at 21:52
1

As stated in the documentation for Node.snapshot(...):

Throws:

IllegalStateException - if this method is called on a thread other than the JavaFX Application Thread.

So you can only execute this method on the FX Application Thread. That means that you must either call snapshot() from a method that is already being executed on the FX Application Thread, or you must wrap the call to snapshot() in Platform.runLater(...). The latter option will, of course, only work if the FX Application Thread is running, which means the FX toolkit must be initialized.

Consequently, you cannot do this in a "headless" mode as you are trying to do. You need a full implementation of the JavaFX toolkit and for it to be initialized and still running.

The way to start the FX Application toolkit is to call Application.launch(...), either from a subclass of Application, or specifying a concrete subclass of Application.

Note that there are limitations on how you can call Application.launch()

First, it will block until the FX application exits, so you need to call it in a background thread. This will involve calling launch() on the background thread, then blocking until the FX Toolkit initialization has completed before continuing.

And secondly, you can only call launch() once in the lifetime of the JVM. This means that if your application requires the FX toolkit, you should probably just start it on application startup and exit it on application shutdown.

Finally, also note that according to the documentation for snapshot(...) linked above,

In order for CSS and layout to function correctly, the node must be part of a Scene (the Scene may be attached to a Stage, but need not be).

A solution to the problem of starting the FX toolkit "manually" was posted in response to this question, which I'll repeat in this context.

Note that executing the application below will write a couple of .png files to your drive (in the working directory, i.e. wherever you are running this from). You can use these to validate that it is working correctly.

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.FutureTask;

import javax.imageio.ImageIO;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.chart.BarChart;
import javafx.scene.chart.CategoryAxis;
import javafx.scene.chart.NumberAxis;
import javafx.scene.chart.XYChart.Data;
import javafx.scene.chart.XYChart.Series;
import javafx.scene.image.WritableImage;
import javafx.scene.layout.Pane;
import javafx.scene.paint.Color;
import javafx.scene.paint.CycleMethod;
import javafx.scene.paint.LinearGradient;
import javafx.scene.paint.Stop;
import javafx.scene.shape.Rectangle;
import javafx.stage.Stage;

public class StandaloneSnapshot {
    public static void main(String[] args) throws Exception {

        // start FX toolkit in background thread:
        new Thread(() -> Application.launch(FXStarter.class)).start();

        // block until FX toolkit initialization is complete:
        FXStarter.awaitFXToolkit();

        new  StandaloneSnapshot().manipulatePdf();
        
        // exit JavaFX toolkit:
        Platform.exit();
    }

    public static class FXStarter extends Application {

        private static final CountDownLatch latch = new CountDownLatch(1);
    
        public static void awaitFXToolkit() throws InterruptedException {
           latch.await();
        }
    
        @Override
        public void init() {
            latch.countDown();
        }
    
        @Override
        public void start(Stage primaryStage) {
            // no-op
        }
    }

    protected void manipulatePdf() throws Exception {

        WritableImage img1 = image1(); 
        // do something with the image:
        BufferedImage bImg1 = SwingFXUtils.fromFXImage(img1, new BufferedImage((int)img1.getWidth(), (int) img1.getHeight(), BufferedImage.TYPE_INT_ARGB));
        ImageIO.write(bImg1, "png", new File("rect.png"));

        WritableImage img2 = image2();
        // do something with the image:
        BufferedImage bImg2 = SwingFXUtils.fromFXImage(img2, new BufferedImage((int)img2.getWidth(), (int) img2.getHeight(), BufferedImage.TYPE_INT_ARGB));
        ImageIO.write(bImg2, "png", new File("chart.png"));
    }

    private WritableImage image1() throws Exception {

        Rectangle rectangle = new Rectangle();
        rectangle.setX(50);
        rectangle.setY(50);
        rectangle.setWidth(300);
        rectangle.setHeight(20);

        rectangle.setStroke(Color.WHITE);

        LinearGradient linearGrad = new LinearGradient(0, 0, 1, 0, true, CycleMethod.NO_CYCLE,           
                new Stop(0f, Color.rgb(255, 0, 0, 1)), new Stop(0.25f,
                        Color.rgb(255, 255, 0, 1)), new Stop(0.5f, Color.rgb(
                        255, 255, 255, 1)), new Stop(0.75f, Color.rgb(124, 252,
                        0, 1)), new Stop(1.0f, Color.rgb(0, 255, 0, 1)));
        rectangle.setFill(linearGrad);

        FutureTask<WritableImage> task = new FutureTask<>(() -> {
            new Scene(new Pane(rectangle));
            WritableImage img = new WritableImage((int) rectangle.getWidth(),
                (int) rectangle.getHeight());
            rectangle.snapshot(null, img);
            return img;
        });

        Platform.runLater(task);

        // wait for FX Application Thread to return image, and return the result:
        return task.get();

    }

    private WritableImage image2 () throws Exception {

        BarChart<String, Number> chart = new BarChart<>(new CategoryAxis(), new NumberAxis());
        Random rng = new Random();
        Series<String, Number> series = new Series<>();
        series.setName("Data");
        for (int i = 1; i <= 10; i++) {
            series.getData().add(new Data<>("Group " + i, rng.nextDouble()));
        }
        chart.getData().add(series);

        FutureTask<WritableImage> task = new FutureTask<>(() -> {
            new Scene(chart);
            WritableImage img = chart.snapshot(null, null);
            return img;
        });

        Platform.runLater(task);
        return task.get();
    }
}

The images generated are

enter image description here

and

enter image description here

Community
  • 1
  • 1
James_D
  • 201,275
  • 16
  • 291
  • 322
  • Thanks James, Its working fine if i use as a standalone. If I call these methods from other classes, @backend, thread is not stopping (javaw.exe), it’s still running. I want use these methods at server side. – Venkat Roo Feb 03 '17 at 02:53