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-сервера должны быть выполнены некоторые дополнительные действия:

  1. Вы не можете использовать JavaFX в OpenJDK (по состоянию на август 2013 г.) - нужно переключиться на Oracle JDK
  2. Версия Java должна быть не меньше, чем Java 7u6
  3. Самое сложное - вам нужно использовать виртуальный дисплей, чтобы 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.

Надеюсь, что это хоть немного поможет!

Другие вопросы по тегам