2013-07-30 11 views
9

Das könnte seltsam klingen, aber ich möchte meine Diagrammbilder auf der Serverseite mit JavaFX generieren. Weil JavaFX über eine nette Canvas-API verfügt, um Bildtransformationen, Joins und Positionierungen durchzuführen.JavaFX für serverseitige Bilderzeugung

Insbesondere habe ich einen Frühling MVC Service, um meine Diagramme als Bilder zu erzeugen. Das Hauptproblem ist, wie man javaFX API von einer bequemen Frühlingsbohne anruft. Wenn ich versuche nur javafx Code von Java-Anwendung ausführen (nicht JavaFX-Anwendung Klasse erweitert) bekomme ich

java.lang.IllegalStateException: Toolkit not initialized 

Haben Sie Vorschläge/Ideen, wie dieses Problem zu lösen?

Antwort

6

Also nach einigen Recherchen habe ich umgesetzt Leinwand mit JavaFX ziehen und hier ist ein vereinfachtes Beispiel:

Zuerst habe ich die JavaFX Antrag gestellt, die in einem separaten Thread gestartet wird (ich benutze Frühling taskExecutor aber eine reine Java Faden kann verwendet werden).

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

} 

Dann rufe ich die initialize() -Methode, wenn der Frühling Anwendung gestartet wird:

@Autowired private TaskExecutor taskExecutor; 

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

Diese Lösung von cource kann auf eine nicht-Spring-Anwendung portiert werden.

Dies ist eine single-threaded-Lösung (in meinem Fall ist es genug), aber ich denke, es könnte zu Multithread-Nutzung angenommen werden (vielleicht RMI verwenden, um Draw-Methode aufrufen).

Auch diese Lösung funktioniert „as is“ auf meinem Windows-Workstation aber auf Linux-Server-Umgebung einige zusätzliche Aktionen aufgerufen werden soll:

  1. Sie nicht JavaFX auf OpenJDK verwenden können (Stand: August 2013) - haben zu wechseln Oracle JDK muss
  2. Java-Version nicht weniger als Java 7u6
  3. Die komplexeste - Sie virtuelle Anzeige zu machen JavaFX laufen auf headless-Umgebungen verwenden:

    apt-get install xvfb

    // dann auf Anwendungsserver Start:

    export DISPLAY = "99"

    Start-Stopp-Daemon --start --background --user Anlegesteg --exec „/ usr/bin/sudo“- -u Anlegesteg/usr/bin/Xvfb: 99 -Bildschirm 0 1024x768x24


PSSie können mit dieser Lösung auch andere JavaFX-Funktionen auf Serverseite verwenden (z. B. HTML in Image exportieren).

+1

Das ist cool. Ich bin so aufgeregt, es auszuprobieren. Danke für die Bemühung! – GGrec

0

Vielleicht wäre etwas ähnlich zu dieser Lösung hilfreich?

JavaFX 2.1: Toolkit not initialized

Ansonsten würde ich erwägen, einen Dienst zu schaffen und um das Bild zu einem Datenspeicher drücken und es in Ihrem Frühjahr Anwendung abgerufen werden.

Hoffe, dass bietet zumindest ein wenig Hilfe!

2

Falls andere Leute danach suchen, ist dies ein viel einfacherer Weg. Mit JavaFX 2.2 konnte ich die folgenden Operationen durchführen.

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

Es ist nicht notwendig, den Knoten zu einer Gruppe hinzuzufügen. Von dort aus können Sie jede gewünschte Operation mit dem Bild ausführen.

Der FxPlatformExecutor stammt aus einer JME3-JFX-Bibliothek, die ich für mein Projekt verwende. Siehe: https://github.com/empirephoenix/JME3-JFX/blob/master/src/main/java/com/jme3x/jfx/FxPlatformExecutor.java

Sie können problemlos die runOnFxApplication()-Methode erstellen oder die FxPlatformExecutor-Klasse erstellen.

Hier ist der 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); 
     } 
    } 
} 

Ich habe diesen Code nicht geschrieben, der GitHub-Link ist oben.

+0

Dies ist ein unvollständiges Beispiel ohne Verweis auf die Klasse FxPlatformExecutor - die nicht gefunden werden kann. –

+0

Vielen Dank, dass Sie darauf hingewiesen haben. Der Import ist eine Drittanbieterbibliothek in meinem Projekt, siehe [github link] (https://github.com/empirephoenix/JME3-JFX/blob/master/src/main/java/com/jme3x/jfx/FxPlatformExecutor. Java). Es könnte leicht implementiert werden. Ich werde es der Antwort hinzufügen. – nucklehead