JavaFX для генерации изображений на стороне сервера
Это может показаться странным, но я хочу генерировать изображения своих диаграмм на стороне сервера, используя JavaFX. Потому что JavaFX имеет хороший API-интерфейс Canvas для преобразования изображений и позиционирования.
В частности, у меня есть весенний сервис MVC для генерации моих диаграмм в виде изображений. Основная проблема заключается в том, как вызвать API javaFX из удобного компонента Spring. Если я пытаюсь просто запустить код javafx из приложения java (не расширяя класс приложения javaFX), я получаю
java.lang.IllegalStateException: Toolkit not initialized
Есть ли у вас какие-либо предложения / идеи, как решить эту проблему?
3 ответа
Итак, после некоторых исследований я реализовал Canvas Draw с помощью JavaFX, и вот упрощенный пример:
Сначала я создал приложение JavaFX, которое запускается в отдельном потоке (я использую Spring taskExecutor, но можно использовать простой поток Java).
public class ChartGenerator extends Application {
private static Canvas canvas;
private static volatile byte[] result;
public static void initialize(TaskExecutor taskExecutor) {
taskExecutor.execute(new Runnable() {
@Override
public void run() {
launch(ChartGenerator.class);
}
});
}
public static synchronized byte[] generateChart(final Object... params) {
Platform.runLater(new Runnable() {
@Override
public void run() {
ByteArrayOutputStream baos = null;
try {
GraphicsContext gc = canvas.getGraphicsContext2D();
gc.clearRect(0, 0, canvas.getWidth(), canvas.getHeight());
/**
* Do the work with canvas
**/
final SnapshotParameters snapshotParameters = new SnapshotParameters();
snapshotParameters.setFill(Color.TRANSPARENT);
WritableImage image = canvas.snapshot(snapshotParameters, null);
BufferedImage bImage = SwingFXUtils.fromFXImage(image, null);
baos = new ByteArrayOutputStream();
ImageIO.write(bImage, chartType.outputFormat, baos);
result = baos.toByteArray();
} catch (InstantiationException e) {
throw new ChartGenerationException(e);
} catch (IllegalAccessException e) {
throw new ChartGenerationException(e);
} catch (NoSuchMethodException e) {
throw new ChartGenerationException(e);
} catch (InvocationTargetException e) {
throw new ChartGenerationException(e);
} catch (IOException e) {
throw new ChartGenerationException(e);
} finally {
IOUtils.closeQuietly(baos);
}
}
});
while (result == null) {
//wait
}
byte[] ret = result;
result = null;
return ret;
}
@Override
public void start(Stage stage) {
canvas = new Canvas();
}
public static class ChartGenerationException extends RuntimeException {
public ChartGenerationException(String message) {
super(message);
}
public ChartGenerationException(Throwable cause) {
super(cause);
}
}
}
Затем я вызываю метод initialize() при запуске приложения Spring:
@Autowired private TaskExecutor taskExecutor;
@PostConstruct private void initChartGenerator() {
ChartGenerator.initialize(taskExecutor);
}
Это решение конечно может быть перенесено в приложение, отличное от Spring.
Это однопоточное решение (в моем случае этого достаточно), но я думаю, что оно может быть адаптировано к многопоточному использованию (возможно, используйте RMI для вызова метода draw).
Также это решение работает "как есть" на моей рабочей станции Windows, но в среде Linux-сервера должны быть выполнены некоторые дополнительные действия:
- Вы не можете использовать JavaFX в OpenJDK (по состоянию на август 2013 г.) - нужно переключиться на Oracle JDK
- Версия Java должна быть не меньше, чем Java 7u6
Самое сложное - вам нужно использовать виртуальный дисплей, чтобы JavaFX работал в автономных средах:
apt-get установить xvfb
// затем при запуске сервера приложений:
экспорт DISPLAY = ": 99"
start-stop-daemon --start --background --user jetty --exec "/ usr / bin / sudo" - -u jetty /usr/bin/Xvfb:99 -экран 0 1024x768x24
PS С этим решением вы также можете использовать другие возможности JavaFX на стороне сервера (например, экспортировать HTML в изображение).
В случае, если другие люди ищут это, это гораздо проще. Используя JavaFX 2.2, я смог выполнить следующие операции.
waitForInit = new Semaphore(0);
root = new Group();
root.getChildren().add(jfxnode);
FxPlatformExecutor.runOnFxApplication(() -> {
snapshot = jfxnode.snapshot(new SnapshotParameters(), null);
waitForInit.release();
});
waitForInit.acquireUninterruptibly();
BufferedImage bi = SwingFXUtils.fromFXImage(snapshot, null);
Нет необходимости добавлять узел в группу. Оттуда вы можете сделать любую операцию с изображением.
FxPlatformExecutor из библиотеки JME3-JFX, которую я использую для своего проекта. Смотрите: https://github.com/empirephoenix/JME3-JFX/blob/master/src/main/java/com/jme3x/jfx/FxPlatformExecutor.java
Вы можете легко создать runOnFxApplication()
метод или создать класс FxPlatformExecutor.
Вот код
package com.jme3x.jfx;
import javafx.application.Platform;
/**
* TODO This Class should be replaced by some Workmanager implemntation
* in the future
* @author Heist
*/
public class FxPlatformExecutor {
public static void runOnFxApplication(Runnable task) {
if (Platform.isFxApplicationThread()) {
task.run();
} else {
Platform.runLater(task);
}
}
}
Я не писал этот код, ссылка на github выше.
Возможно, что-то похожее на это решение будет полезно?
JavaFX 2.1: инструментарий не инициализирован
В противном случае я хотел бы рассмотреть возможность создания службы и передачи изображения в хранилище данных и его извлечения в приложении Spring.
Надеюсь, что это хоть немного поможет!