JavaFX for server-side image generation

后端 未结 3 1750
野的像风
野的像风 2020-12-08 23:25

This could sound strange but I want to generate my chart images on server side using JavaFX. Because JavaFX has nice canvas API to perform image transformations joins and p

相关标签:
3条回答
  • 2020-12-09 00:08

    In case other people are looking for this, this is a much simpler way. Using JavaFX 2.2 i was able to perform the following operations.

        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);
    

    There is no need to add the node to a group. From there you can do any operation you want with the image.

    The FxPlatformExecutor is from a JME3-JFX library I am using for my project. See: https://github.com/empirephoenix/JME3-JFX/blob/master/src/main/java/com/jme3x/jfx/FxPlatformExecutor.java

    You can easily create the runOnFxApplication() method or create the FxPlatformExecutor class.

    Here is the code.

    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);
            }
        }
    }
    

    I did not write this code, the github link is above.

    0 讨论(0)
  • 2020-12-09 00:13

    So after some research I've implemented canvas draw with JavaFX and here is a simplified example:

    First I made the JavaFX application which is being launched in a separate thread (I use Spring taskExecutor but a plain java thread can be used).

    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);
            }
        }
    
    }
    

    Then I call the initialize() method when the Spring application is started:

    @Autowired private TaskExecutor taskExecutor;
    
    @PostConstruct private void initChartGenerator() {
        ChartGenerator.initialize(taskExecutor);
    }
    

    This solution of cource can be ported to a non-Spring application.

    This is a single-threaded solution (in my case it's enough) but I think it could be adopted to multithreaded usage (maybe use RMI to invoke draw method).

    Also this solution works "as is" on my windows workstation but on linux server environment some additional actions should be invoked:

    1. You cannot use JavaFX on OpenJDK (as of Aug 2013) - have to switch to Oracle JDK
    2. Java version must be no less than Java 7u6
    3. The most complex - you have to use virtual display to make JavaFX run on headless environments:

      apt-get install xvfb

      // then on application server start:

      export DISPLAY=":99"

      start-stop-daemon --start --background --user jetty --exec "/usr/bin/sudo" -- -u jetty /usr/bin/Xvfb :99 -screen 0 1024x768x24


    P.S. You can also use other JavaFX capabilities on server side (e.g. export html to image) with this solution.

    0 讨论(0)
  • 2020-12-09 00:21

    Perhaps something similar to this solution would be helpful?

    JavaFX 2.1: Toolkit not initialized

    Otherwise, I would consider creating a service and pushing the image to a datastore and retrieving it in your spring application.

    Hope that provides at least a little help!

    0 讨论(0)
提交回复
热议问题