2013-07-30 8 views
9

Questo potrebbe sembrare strano ma voglio generare le mie immagini grafiche sul lato server utilizzando JavaFX. Perché JavaFX ha una bella API canvas per eseguire join e posizionamenti di trasformazioni di immagine.JavaFX per generazione di immagini lato server

In particolare, ho un servizio MVC di primavera per generare i miei grafici come immagini. Il problema principale è come richiamare l'API javaFX da un comodo bean Spring. Se provo a funzionare solo il codice JavaFX dalla applicazione java (che non si estende JavaFX classe Application) ottengo

java.lang.IllegalStateException: Toolkit not initialized 

Avete suggerimenti/idee come risolvere questo problema?

risposta

6

tela Così, dopo qualche ricerca ho implementato disegnare con JavaFX e qui è un esempio semplificato:

Per prima cosa ho fatto l'applicazione JavaFX, che è in fase di lancio in un thread separato (io uso Primavera TaskExecutor ma un java pianura filo può essere usato).

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

} 

Poi chiamo il metodo initialize() all'avvio dell'applicazione Spring:

@Autowired private TaskExecutor taskExecutor; 

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

Questa soluzione di cource può essere portato su un'applicazione non molla.

Questa è una soluzione a thread singolo (nel mio caso è sufficiente) ma penso che potrebbe essere adottata per l'utilizzo multithread (magari utilizzare RMI per richiamare il metodo draw).

Anche questa soluzione funziona "così com'è" sulla mia workstation Windows, ma in ambiente server linux alcune azioni supplementari dovrebbero essere invocate:

  1. Non è possibile utilizzare JavaFX su OpenJDK (a partire da agosto 2013) - deve passare a Oracle JDK
  2. versione Java deve essere non meno di Java 7u6
  3. La più complessa - è necessario utilizzare display virtuale per fare JavaFX eseguito su ambienti senza testa:

    apt-get install Xvfb

    // quindi all'avvio del server delle applicazioni:

    export DISPLAY = ": 99"

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


PSÈ inoltre possibile utilizzare altre funzionalità JavaFX sul lato server (ad esempio, esportare html in immagine) con questa soluzione.

+1

Questo è bello. Sono così entusiasta di provarlo. Grazie per lo sforzo! – GGrec

0

Forse qualcosa di simile a questa soluzione sarebbe utile?

JavaFX 2.1: Toolkit not initialized

In caso contrario, vorrei prendere in considerazione la creazione di un servizio di e spingendo l'immagine in un archivio dati e il recupero è nell'applicazione di primavera.

Spero che fornisca almeno un piccolo aiuto!

2

Nel caso in cui altre persone stiano cercando questo, questo è un modo molto più semplice. Utilizzo di JavaFX 2.2 Sono stato in grado di eseguire le seguenti operazioni.

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

Non è necessario aggiungere il nodo a un gruppo. Da lì puoi fare qualsiasi operazione tu desideri con l'immagine.

FxPlatformExecutor proviene da una libreria JME3-JFX che sto utilizzando per il mio progetto. Vedere: https://github.com/empirephoenix/JME3-JFX/blob/master/src/main/java/com/jme3x/jfx/FxPlatformExecutor.java

È possibile creare facilmente il metodo runOnFxApplication() o creare la classe FxPlatformExecutor.

Ecco il codice.

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

Non ho scritto questo codice, il link github è sopra.

+0

Questo è un esempio incompleto senza riferimento alla classe FxPlatformExecutor - che non può essere trovata. –

+0

Grazie per averlo indicato. L'importazione è una libreria di terze parti nel mio progetto vedere il [collegamento github] (https://github.com/empirephoenix/JME3-JFX/blob/master/src/main/java/com/jme3x/jfx/FxPlatformExecutor. Giava). Potrebbe facilmente essere implementato. Lo aggiungerò alla risposta. – nucklehead