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

and
