Syndication : Atom 1.0  RSS 2.0
Blogs des développeurs   »   < bouye : blog />

01/04/2012

Permalink 09:36:38, Catégories: JavaFX 2.x, 825 mots   French (FR) , bouye

Petit programme pour tester ColorAdjust

Ces derniers temps, dans la version 2.0, mon app j'ai commencé à intégrer des icônes monochrome dans le style Metro. Ce n'est pas vraiment pour essayer de faire une UI pour Windows 8 (BEURK !) mais plutôt pour continuer à utiliser un type de design que certaines de nos icônes utilisaient déjà dans la version 1.0 (programmée en JavaFX Script). D'un autre coté le style est simple, avec peu ou pas de couleur et on peut rapidement en créer des nouvelles sans se casser à tête (ou la souris) à passer des heures et des heures à dessiner un truc sans être content du résultat (ce qui est gonflant quand à la base on est ni artiste ni graphic designer).

De base, toutes les icônes sont blanches sur fond transparent (avec quelques rares fois, des tons de gris), mais parfois il y a besoin de faire des changements de couleur notamment pour indiquer qu'il y a des erreurs ou qu'au contraire tout est OK. C'est là justement qu'arrive l'effet ColorAdjust qui permet de faire varier les couleurs d'un nœud en modifiant leurs valeurs HSBC (hue - saturation - brightness - contrast).

Et donc, avant de pouvoir mettre les bonnes valeurs dans le code, il me fallait coder un petit test qui permet de voir rapidement les valeurs à mettre dans les différents paramètres de l'effet. Chaque paramètre peut voir sa valeur varier entre [-1, 1], une valeur de 0 signifiant que le paramètre n'est pas modifié par l'effet.
Voilà une bonne petite occasion de mettre en place des bindings simple avec une GUI très légère :

 
/** 
 * 
 * @author Fabrice 
 */ 
public class Main extends Application { 
 
  /** 
  * @param args the command line arguments 
  */ 
  public static void main(String[] args) { 
  launch(args); 
  } 
 
  @Override 
  public void start(Stage primaryStage) { 
  ClassLoader classLoader = getClass().getClassLoader(); 
  URL imageURL = classLoader.getResource("test/resources/Error.png"); 
  Image image = new Image(imageURL.toExternalForm()); 
  final ImageView imageView = ImageViewBuilder.create().image(image).build(); 
  ColorAdjust colorAdjust = ColorAdjustBuilder.create().build(); 
  imageView.setEffect(colorAdjust); 
  // 
  Label saturationLabel = LabelBuilder.create().text("Saturation").style("-fx-text-fill: white;").build(); 
  GridPane.setConstraints(saturationLabel, 0, 0); 
  Slider saturationSlider = SliderBuilder.create().value(50).build(); 
  colorAdjust.saturationProperty().bind(saturationSlider.valueProperty().divide(50).subtract(1)); 
  GridPane.setConstraints(saturationSlider, 1, 0); 
  GridPane.setHgrow(saturationSlider, Priority.ALWAYS); 
  Label saturationValueLabel = LabelBuilder.create().style("-fx-text-fill: white;").minWidth(75).maxWidth(75).build(); 
  saturationValueLabel.textProperty().bind(colorAdjust.saturationProperty().multiply(100).asString("%.2f%%")); 
  GridPane.setConstraints(saturationValueLabel, 2, 0); 
  // 
  Label hueLabel = LabelBuilder.create().text("Hue").style("-fx-text-fill: white;").build(); 
  GridPane.setConstraints(hueLabel, 0, 1); 
  Slider hueSlider = SliderBuilder.create().value(50).build(); 
  colorAdjust.hueProperty().bind(hueSlider.valueProperty().divide(50).subtract(1)); 
  GridPane.setConstraints(hueSlider, 1, 1); 
  GridPane.setHgrow(hueSlider, Priority.ALWAYS); 
  Label hueValueLabel = LabelBuilder.create().style("-fx-text-fill: white;").minWidth(75).maxWidth(75).build(); 
  hueValueLabel.textProperty().bind(colorAdjust.hueProperty().multiply(100).asString("%.2f%%")); 
  GridPane.setConstraints(hueValueLabel, 2, 1); 
  // 
  Label brightnessLabel = LabelBuilder.create().text("Brightness").style("-fx-text-fill: white;").build(); 
  GridPane.setConstraints(brightnessLabel, 0, 2); 
  Slider brightnessSlider = SliderBuilder.create().value(50).build(); 
  colorAdjust.brightnessProperty().bind(brightnessSlider.valueProperty().divide(50).subtract(1)); 
  GridPane.setConstraints(brightnessSlider, 1, 2); 
  GridPane.setHgrow(brightnessSlider, Priority.ALWAYS); 
  Label brightnessValueLabel = LabelBuilder.create().style("-fx-text-fill: white;").minWidth(75).maxWidth(75).build(); 
  brightnessValueLabel.textProperty().bind(colorAdjust.brightnessProperty().multiply(100).asString("%.2f%%")); 
  GridPane.setConstraints(brightnessValueLabel, 2, 2); 
  // 
  Label contrastLabel = LabelBuilder.create().text("Contrast").style("-fx-text-fill: white;").build(); 
  GridPane.setConstraints(contrastLabel, 0, 3); 
  Slider contrastSlider = SliderBuilder.create().value(50).build(); 
  colorAdjust.contrastProperty().bind(contrastSlider.valueProperty().divide(50).subtract(1)); 
  GridPane.setConstraints(contrastSlider, 1, 3); 
  GridPane.setHgrow(contrastSlider, Priority.ALWAYS); 
  Label contrastValueLabel = LabelBuilder.create().style("-fx-text-fill: white;").minWidth(75).maxWidth(75).build(); 
  contrastValueLabel.textProperty().bind(colorAdjust.contrastProperty().multiply(100).asString("%.2f%%")); 
  GridPane.setConstraints(contrastValueLabel, 2, 3); 
  //  
  GridPane sliderGrid = GridPaneBuilder.create().children(saturationLabel, saturationSlider, saturationValueLabel, 
  hueLabel, hueSlider, hueValueLabel, 
  brightnessLabel, brightnessSlider, brightnessValueLabel, 
  contrastLabel, contrastSlider, contrastValueLabel).build(); 
  MenuItem openItem = MenuItemBuilder.create().text("Open").onAction(new EventHandler<ActionEvent>() { 
 
  @Override 
  public void handle(ActionEvent arg0) { 
  FileChooser dialog = FileChooserBuilder.create().build(); 
  File file = dialog.showOpenDialog(imageView.getScene().getWindow()); 
  if (file != null) { 
  URI uri = file.toURI(); 
  Image image = new Image(uri.toString()); 
  imageView.setImage(image); 
  } 
  } 
  }).build(); 
  Menu fileMenu = MenuBuilder.create().text("File").items(openItem).build(); 
  MenuBar menuBar = MenuBarBuilder.create().menus(fileMenu).build(); 
  BorderPane root = BorderPaneBuilder.create().top(menuBar).center(imageView).bottom(sliderGrid).build(); 
  primaryStage.setTitle("Test_ColorAdjust"); 
  primaryStage.setScene(new Scene(root, 500, 500, Color.BLACK)); 
  primaryStage.show(); 
  } 

Vous devez être identifié pour poster un commentaire.

28/03/2012

Permalink 07:07:48, Catégories: JavaFX 2.x, 3128 mots   French (FR) , bouye

Convertir des Chart en image

Note : la solution présentée ici permet de convertir des instances de Chart en BufferedImage, car il s'agissait spécifiquement du problème que j'essayais de résoudre mais, en fait, elle peut être facilement étendue pour imprimer des Control, Layout ou Node. Également cette solution repose sur Swing, il devrait être possible d'utiliser SWT aussi (mais je n'ai pas assez pratique pour savoir si ça marcherait ou pas).

L'un des épineux problèmes qui me taraudent depuis la sortie de JavaFX c'est qu'il n'y a pas pour le moment de support qui permette d'obtenir aisément et rapidement une version bitmap d'un nœud. On peut faire plein de chose quand on a une bitmap sous les mains : on peut la sauvegarder dans un fichier, l'imprimer, la mettre dans un PDF, l'utiliser pour créer un thumbnail/aperçu ou encore un fantôme d'un nœud lors qu'on fait des opérations de drag'n drop sans pour autant trainer un objet vectoriel hautement complexe à dessiner à l’écran avec soit tout au long du traitement (non, souvent juste mettre cache = true ne suffit pas pour obtenir de meilleures performances).

En JavaFX 1.x, 1.1.x, ça permettait de gagner pas mal de performances sur des petites configs et c’était très sympa de la part de Rakesh Menon (qui travaillait alors pour Oracle) d'avoir partage cette astuce avec nous. En JavaFX 1.2.x et 1.3.x, Rakesh Menon nous avait une fois de plus fourni le correctif (il y avait eut des changements dans les API privées) à utiliser pour que l'export fonctionne a nouveau mais c’était déjà devenu un peu plus compliqué : souvent, un Control n'avait pas de taille tant qu'il n’était pas affiché une première fois à l’écran, mais en plus souvent il ne se dessinait pas non-plus ce qui empêchait toute composition offscreen. Donc du coup il fallait jongler avec des stages auxiliaires (la classe XDialog de JFXtras) pour faire popup une boite de dialogue a l’écran quelques secondes pour que le nœud apparaisse et si on allait trop vite, on avait une image toute vide...

Le problème est encore différent en 2.x et 2.1.x car personne n'est venu cette fois nous révéler les secrets de l'API privée et donc on se retrouve un peu sans savoir quoi faire mise a part une brève mention est faite sur le JIRA qu'il existe un moyen mais sans plus de précision quant a sa nature. Et puis le support de l'export en bitmap ne sera pas prévu avant au moins la 2.2 si ce n'est la 3.0 (en croisant fortement les doigts). Le sujet revient de temps a autre sur les forums OTN, c'est donc qu'il y a un besoin. Et puis même j'ajouterai, ici, nous nous en avons besoin (bonjour la tête de mon boss quand je lui ait annoncé qu'il ne pourrait pas exporter ses graphes dans la prochaine version du soft...).

Mais ouf il existe une manière détournée, qui a défaut d’être parfaite, semble fonctionner pour le moment : utiliser ce bon vieux Java2D. En effet, depuis la 2.0, il est possible d'inclure une Scene JavaFX dans une interface Swing grâce au composant JFXPanel qui est fourni dans l'API JavaFX.

*TILT* !

Or si on a un Component sous la main... on peut très bien lui demander de se dessiner dans un Graphics2D en appelant sa méthode paint() comme au bon vieux temps ! Ce qui peut nous permettre d'avoir l'affichage du contenu du JFXPanel dans une BufferedImage ! Ainsi il est possible de se créer une Task qui génère des images d'un nœud !

Cela va demander de se retrouver un peu les manches pour pouvoir imprimer ces Chart (dans mon cas précis) :

  • Il va falloir utiliser pas moins de 3 threads:

    • La thread de la Task.
    • La thread javaFX (puisqu'une Scene doit être crée sur la thread de JavaFX).
    • L'EDT (pour les composants Swing).

    et synchroniser le tout sur la fin (je préférerai éviter que ma tache ne se finisse avant que la conversion ne soit entièrement effectuée et puis comme cela, ça permet de faire remonter les erreurs).

  • Il va quand même falloir faire popup un JDialog à l'écran et ce pour les mêmes raisons que précédemment.
  • Dans mon cas, je vais parfois avoir besoin de générer plusieurs dizaines d'images et donc ça m'ennuierai un peu que ma tache retourne juste une List<BufferedImage> que je vais conserver en mémoire pendant des lustres. j'ai donc opté pour une approche dans laquelle je fourni à la tache une interface ImageHandler qui permet d'effectuer une action sur chaque image nouvellement générée.

 
/** 
 * Allows to provide an action to the {@code Chart2ImageTask} class. 
 * @author Fabrice Bouyé (fabriceb@spc.int) 
 */ 
public interface ImageHandler { 
 
  /** 
  * Call to handle the chart image we've just created. 
  * @param chart The source chart. 
  * @param image The image of the chart. 
  * @param chartIndex The index of the chart. 
  * @param chartNumber The number of charts. 
  * @throws Exception In case of errors. 
  */ 
  public void handle(Chart chart, BufferedImage image, int chartIndex, int chartNumber) throws Exception; 

Note : la classe java.lang.Void qui rappelera des souvenirs a ceux ayant fait du JavaFX 1.x est une classe de l'API standard dont la seule et unique valeur valide est null.

 
/** 
 * A task that helps exporting a chart to an image. 
 * <br/>Note: as of JavaFX 2.1, there is still no support for image export so we have to use a hack in which we display the chart in a {@code JFXPanel} and render this panel in a {@code Graphics2D}. 
 * @author Fabrice Bouyé (fabriceb@spc.int) 
 */ 
public class Chart2ImageTask extends Task<Void> { 
 
  /** 
  * The charts to export. 
  */ 
  private List<Chart> charts; 
  /** 
  * Target image width. 
  */ 
  private int imageWidth; 
  /** 
  * Target image height. 
  */ 
  private int imageHeight; 
  /** 
  * Map chart -> processed flag. 
  * <br/>Indicates whether a chart has already been processed. 
  */ 
  private final Map<Chart, Boolean> processedMap = new HashMap<>(); 
  /** 
  * Map chart -> error. 
  * <br/>Indicates whether a chart export has produced an error. 
  */ 
  private final Map<Chart, Throwable> errorMap = new HashMap<>(); 
 
  /** 
  * Creates a new instance. 
  * @param charts The charts to export. 
  */ 
  public Chart2ImageTask(Chart... charts) { 
  this(0, 0, charts); 
  } 
 
  /** 
  * Creates a new instance. 
  * @param charts The charts to export. 
  * @param panelWidth Target image width. 
  * @param panelHeight Target image height. 
  */ 
  public Chart2ImageTask(int imageWidth, int imageHeight, Chart... charts) { 
  super(); 
  this.charts = Arrays.asList(charts); 
  this.imageWidth = imageWidth; 
  this.imageHeight = imageHeight; 
  for (Chart chart : charts) { 
  processedMap.put(chart, false); 
  } 
  } 
 
  /** 
  * Test if all charts have been converted. 
  * @return {@code True} if all charts have been converted, {@code false} otherwise. 
  */ 
  private boolean testConversionDone() { 
  boolean result = true; 
  synchronized (processedMap) { 
  for (Boolean processed : processedMap.values()) { 
  if (!processed) { 
  result = false; 
  break; 
  } 
  } 
  } 
  return result; 
  } 
 
  /** 
  * {@inheritDoc} 
  */ 
  @Override 
  protected synchronized Void call() throws Exception { 
  for (Chart chart : charts) { 
  final Chart currentChart = chart; 
  // Scenes need to be creates on FX thread. 
  Platform.runLater(new Runnable() { 
 
  @Override 
  public void run() { 
  initializeScene(currentChart); 
  } 
  }); 
  } 
  while (!testConversionDone()) { 
  wait(); 
  } 
  // Generate a new global error for this task (if needed). 
  if (!errorMap.isEmpty()) { 
  IllegalStateException ise = new IllegalStateException(MessageFormat.format("Task failed with {0} error(s).", errorMap.size())); 
  for (Throwable error : errorMap.values()) { 
  ise.addSuppressed(error); 
  } 
  throw ise; 
  } 
  return null; 
  } 
 
  /** 
  * Returns a list of all errors that happened during this task. 
  * @return A non-modifiable {@code List<Throwable>}, this list will be empty is the task succeeded. 
  */ 
  public List<Throwable> getExceptions() { 
  return Collections.unmodifiableList(new ArrayList<>(errorMap.values())); 
  } 
 
  /** 
  * Initialize the scene to which the chart is attached. 
  * <br/>This method is called on FX thread. 
  * @param chart The chart to export. 
  */ 
  private void initializeScene(final Chart chart) { 
  // Set default width. 
  int width = imageWidth; 
  if (width <= 0) { 
  width = (int) Math.ceil(chart.getWidth()); 
  } 
  if (width <= 0) { 
  width = (int) Math.ceil(chart.getPrefWidth()); 
  } 
  if (width <= 0) { 
  width = (int) Math.ceil(chart.getMinWidth()); 
  } 
  if (width <= 0) { 
  width = 500; 
  } 
  // Set default height. 
  int height = imageHeight; 
  if (height <= 0) { 
  height = (int) Math.ceil(chart.getHeight()); 
  } 
  if (height <= 0) { 
  height = (int) Math.ceil(chart.getPrefHeight()); 
  } 
  if (height <= 0) { 
  height = (int) Math.ceil(chart.getMinHeight()); 
  } 
  if (height <= 0) { 
  height = 500; 
  } 
  final Scene scene = new Scene(chart, width, height); 
  // Dialog needs to be created on EDT. 
  SwingUtilities.invokeLater(new Runnable() { 
 
  @Override 
  public void run() { 
  initializeDialog(chart, scene); 
  } 
  }); 
  } 
 
  /** 
  * Initialize, display and export the {@code JFXPanel} renderer. 
  * <br/>This method is called on EDT. 
  * @param chart The chart to export. 
  * @param scene The scene in which the chart is attached. 
  */ 
  private void initializeDialog(final Chart chart, Scene scene) { 
  final int chartIndex = charts.indexOf(chart); 
  final JFXPanel jfxPanel = new JFXPanel(); 
  int panelWidth = (int) Math.ceil(scene.getWidth()); 
  int panelHeight = (int) Math.ceil(scene.getHeight()); 
  Dimension size = new Dimension(panelWidth, panelHeight); 
  jfxPanel.setSize(size); 
  jfxPanel.setPreferredSize(size); 
  jfxPanel.setMinimumSize(size); 
  jfxPanel.setMaximumSize(size); 
  jfxPanel.setScene(scene); 
  final JDialog dialog = new JDialog(); 
  dialog.setDefaultCloseOperation(JDialog.DO_NOTHING_ON_CLOSE); 
  dialog.addComponentListener(new ComponentAdapter() { 
 
  @Override 
  public void componentShown(ComponentEvent e) { 
  Timer pause = new Timer(200, new ActionListener() { 
 
  @Override 
  public void actionPerformed(ActionEvent e) { 
  exportAsImage(chart, jfxPanel, chartIndex); 
  dialog.dispose(); 
  } 
  }); 
  pause.setRepeats(false); 
  pause.start(); 
  } 
  }); 
  dialog.setContentPane(jfxPanel); 
  dialog.pack(); 
  dialog.setVisible(true); 
  } 
  /** 
  * The taskProgress of the task. 
  */ 
  private int taskProgress = 0; 
 
  /** 
  * Export the {@code JFXPanel} renderer as an image. 
  * @param chart The source chart. 
  * @param delegated The {@code JFXPanel} renderer. 
  * @param chartIndex The index of the current chart. 
  */ 
  private void exportAsImage(Chart chart, JFXPanel delegated, int chartIndex) { 
  int chartNumber = charts.size(); 
  try { 
  Dimension size = delegated.getSize(); 
  BufferedImage image = new BufferedImage(size.width, size.height, BufferedImage.TYPE_INT_ARGB); 
  Graphics2D graphics = image.createGraphics(); 
  try { 
  delegated.paint(graphics); 
  } finally { 
  graphics.dispose(); 
  } 
  // Provide image to handler. 
  ImageHandler imageHandler = getImageHandler(); 
  if (imageHandler != null) { 
  imageHandler.handle(chart, image, chartIndex, chartNumber); 
  } 
  } catch (Throwable t) { 
  errorMap.put(chart, t); 
  } 
  synchronized (processedMap) { 
  processedMap.put(chart, true); 
  } 
  synchronized (this) { 
  taskProgress++; 
  updateProgress(taskProgress, chartNumber); 
  notify(); 
  } 
  } 
  // 
  /** 
  * The object that will handle the image after its creation. 
  */ 
  private ImageHandler imageHandler; 
 
  /** 
  * Sets the image handler. 
  * @param value The new value. 
  * @see #getImageHandler()  
  */ 
  public final void setImageHandler(ImageHandler value) { 
  imageHandler = value; 
  } 
 
  /** 
  * Gets the image handler. 
  * @return A {@code ImageHandler} instance, may be {@code null}. 
  * @see #setImageHandler(ImageHandler)  
  */ 
  public final ImageHandler getImageHandler() { 
  return imageHandler; 
  } 

Je ne garanti pas que c'est sans bug ou deadlock car il y a trop longtemps que je n'ai pas manipulé des threads à un niveau aussi bas mais pour le moment, chez moi cela marche plutôt correctement.
Voici le programme de test utilisé ; ici le ImageHandler créé permet de sauvegarder les images produites sur le disque dans le répertoire home de l'utilisateur.

 
/** 
 * Test program for the {@code Chart2ImageTask} class. 
 * @author Fabrice Bouyé (fabriceb@spc.int) 
 */ 
public class Test_Chart2ImageTask extends Application { 
 
  /** 
  * Create test line chart. 
  * @param base The base number. 
  * @return A {@code LineChart} instance, never {@code null}. 
  */ 
  public static LineChart createTestChart(int base) { 
  double maxX = 10; 
  double maxY = Math.pow(base, maxX); 
  NumberAxis xAxis = new NumberAxis(0, maxX, maxX / 5); 
  NumberAxis yAxis = new NumberAxis(0, maxY, maxY / 5); 
  yAxis.setLabel("y = " + base + "^x;"); 
  LineChart.Series lineSeries = new LineChart.Series<>(); 
  lineSeries.setName("Power of " + base); 
  for (int x = 0; x <= maxX; x++) { 
  LineChart.Data lineData = new LineChart.Data<>(x, Math.pow(base, x)); 
  lineSeries.getData().add(lineData); 
  } 
  LineChart lineChart = new LineChart(xAxis, yAxis, FXCollections.observableArrayList(lineSeries)); 
  return lineChart; 
  } 
 
  /** 
  * @param args the command line arguments 
  */ 
  public static void main(String[] args) { 
  launch(args); 
  } 
 
  /** 
  * {@inheritDoc} 
  */ 
  @Override 
  public void start(Stage primaryStage) { 
  VBox chartVBox = VBoxBuilder.create().build(); 
  for (int base = 0; base < 4; base++) { 
  LineChart lineChart = createTestChart(base); 
  VBox.setVgrow(lineChart, Priority.ALWAYS); 
  chartVBox.getChildren().add(lineChart); 
  } 
  // 
  EventHandler<ActionEvent> exportHandler = new EventHandler<ActionEvent>() { 
 
  @Override 
  public void handle(ActionEvent event) { 
  // In the real program here, we should make copy of existing charts: 
  // - a same node cannot be addded in 2 different scenes/parent. 
  // If we keep the same object, we would need to remove it from parent and re-add it after export. 
  // - we probably want to change to a more simple CSS when printing and exporting chart. 
  final List<Chart> chartList = new ArrayList<>(); 
  for (int base = 0; base < 4; base++) { 
  LineChart lineChart = createTestChart(base); 
  chartList.add(lineChart); 
  } 
  exportCharts(chartList); 
  } 
  }; 
  Button exportButton = ButtonBuilder.create().text("Export").onAction(exportHandler).build(); 
  ToolBar toolBar = ToolBarBuilder.create().items(exportButton).build(); 
  BorderPane root = BorderPaneBuilder.create().top(toolBar).center(chartVBox).build(); 
  // 
  primaryStage.setTitle("Test_Chart2ImageTask"); 
  // Sometimes, AWT thread continue running after the window has been closed. 
  // May be due to uncaught exceptions in earlier developments. 
  // @todo Check if this still happens. 
  primaryStage.setOnCloseRequest(new EventHandler<WindowEvent>() { 
 
  @Override 
  public void handle(WindowEvent event) { 
  Platform.exit(); 
  } 
  }); 
  primaryStage.setScene(new Scene(root, 800, 800)); 
  primaryStage.show(); 
  } 
 
  private void exportCharts(final List<Chart> charts) { 
  // Handler that allow to save produced image as a PNG file. 
  final ImageHandler imageHandler = new ImageHandler() { 
 
  @Override 
  public void handle(Chart chart, BufferedImage image, int chartIndex, int chartNumber) throws Exception { 
  System.out.println(MessageFormat.format("Handling chart #{0}/{1}.", chartIndex, chartNumber)); 
  File home = new File(System.getProperty("user.home")); 
  String format = "png"; 
  File file = new File(home, MessageFormat.format("Test_{0}.{1}", chartIndex, format)); 
  ImageIO.write(image, format, file); 
  } 
  }; 
  // The image generator task. 
  final Chart2ImageTask exportTask = new Chart2ImageTask(charts.toArray(new Chart[0])); 
  exportTask.setImageHandler(imageHandler); 
  // And finally the service. 
  final Service<Void> exportService = new Service<Void>() { 
 
  @Override 
  protected Task<Void> createTask() { 
  return exportTask; 
  } 
  }; 
  exportService.setOnSucceeded(new EventHandler<WorkerStateEvent>() { 
 
  @Override 
  public void handle(WorkerStateEvent arg0) { 
  System.out.println("Task succeeded"); 
  } 
  }); 
  exportService.setOnFailed(new EventHandler<WorkerStateEvent>() { 
 
  @Override 
  public void handle(WorkerStateEvent arg0) { 
  System.out.println("Task failed with " + exportService.getException()); 
  System.out.println(exportTask.getExceptions()); 
  } 
  }); 
  exportService.start(); 
  } 

À partir de là, on pourra fournir des ImageHandler appropriés pour faire telle ou telle tache spécifique comme exporter chacun des graphes dans un fichier PDF separé ou les imprimer (next step : finally trying to figure out how the hell Java print service work... yeah, more fun!). Attention cependant, par exemple dans le cas où tous les graphes doivent être inclus dans le même PDF, comme c'est du multi-threads, je ne sais pas encore si l'ordre d'appel de la méthode du ImageHandler est garanti ou pas.

Vous devez être identifié pour poster un commentaire.

13/01/2012

Permalink 03:59:06, Catégories: JavaFX 2.x, JavaFX 1.x, 1452 mots   French (FR) , bouye

FXD -> FXML

Après une dizaine de jours de C# et de taches administratives, un bref retour sur du FX. Oracle a un peu laisser tomber ses plans initiaux de fournir des convertisseurs de code et de projet permettant une migration de FX 1.x vers FX 2.x. C'est d'autant plus ennuyeux que tant la Production Suite, avec ses convertisseurs pour SVG, Illustrator et Photoshop, que le JavaFXComposer permettaient de faire des trucs sympas avec le format FXD/FXZ.

S'il a été assez rapide de recoder à la main certaines UI en 2.0 (pas la peine de se forcer à faire du FXML pour ça tant que l’éditeur visuel n'est pas disponible), il n'en est pas de même pour les SVG.

Une idée serait de partir du SVG originel et de le transformer directement en FXML. Déjà, on se souvient que le convertisseur SVG -> FXD de la ProductionSuite n’était pas parfait : le boulot était tout à fait honorable mais certains choses comme les gradients circulaires transformés n’étaient pas vraiment bien supportés (voir pas supportés du tout).
En plus, il se trouve que les SVG produits par Inkscape ne sont pas totalement propres, par exemple certaines formes ou groupes contiennent des matrices de transformations au lieu de leur positionnement absolu dans le design et donc il leur faut appliquer ces transformations pour avoir leurs vraies coordonnées. Et je n'ai pas vraiment envie de ressortir les bouquins de math de terminale ou de DEUG sur les calculs matriciels, rien que d’évoquer ces souvenirs est suffisamment pénible en soit. En plus pour peu que l'on ait fait un peu joujou avec Photoshop, on aura pas forcement de SVG sous la main (et tout le monde n'a pas forcement envie de savoir comment un fichier PSD c'est fait). Et puis bon... avoir le support du SVG en Java pur, ça veut dire Batik et tout un tas de libs plus ou moins lourdes à télécharger ou à utiliser. Donc pour porter certaines parties de nos projets vers FX 2.0, il va falloir transformer tout cela en FXML, mais sans pour autant devoir code un monstre au niveau du convertisseur (si Oracle n'y arrive pas, moi j'ai peu de chances...)

Donc, autant s'attacher a faire un convertisseur un peu plus "universel", mais aussi plus léger.
Apparemment, je ne suis pas le seul a avoir ce genre de problèmes lors du portage d'anciens projets, et il y a plusieurs topics dans les forums d'Oracle sur ce genre de conversion. L’astuce suggérée est assez simple à mettre en place : il faut tout simplement charger le fichier FXD/FXZ dans un projet écrit en FX 1.x, récupérer le nœud racine, parcourir arborescence de son contenu et générer le code XML approprié pour chaque nœud rencontré, ainsi que ses attributs (pour ceux qui ont des équivalents en 2.0).

Ça donne un truc comme ça qui peut servir de base de départ pour faire un convertisseur supportant plus de type de nœuds (ici seuls Group et SVGPath sont convertis) :
 
def file: File = new File(...); 
def path: String = file.toURI().toString(); 
def fxdNode: com.sun.javafx.tools.fxd.FXD = FXDLoader.load(path) as com.sun.javafx.tools.fxd.FXD; 
def charset: String = "UTF-8"; 
def output: PrintWriter = new PrintWriter("{file.getName().substring(0, file.getName().lastIndexOf('.'))}.fxml", charset); 
 
try { 
  output.println("<?xml version=\"1.0\" encoding=\"{charset}\"?>"); 
  output.println(); 
  output.println("<?import javafx.scene.*?>"); 
  output.println("<?import javafx.scene.layout.*?>"); 
  output.println("<?import javafx.scene.control.*?>"); 
  output.println("<?import javafx.scene.image.*?>"); 
  output.println("<?import javafx.scene.paint.*?>"); 
  output.println("<?import javafx.scene.shape.*?>"); 
  convert(fxdNode, output, ""); 
} finally { 
  output.close(); 

 
 
function convert(node: Node, output: PrintWriter, suffix: String): Void { 
  println("{node.id}\t{node.getClass()}"); 
  if (node instanceof com.sun.javafx.tools.fxd.FXD) { 
  convertFXD(node as com.sun.javafx.tools.fxd.FXD, output, suffix); 
  } else if (node instanceof Group) { 
  convertGroup(node as Group, output, suffix); 
  } else if (node instanceof SVGPath) { 
  convertSVGPath(node as SVGPath, output, suffix); 
  } 

 
function convertFXD(fxd: com.sun.javafx.tools.fxd.FXD, output: PrintWriter, suffix: String): Void { 
  output.print("{suffix}<Group"); 
  convertNodeAttributes(fxd, output); 
  output.println(">"); 
  output.println("{suffix} <children>"); 
  for (child in fxd.content) { 
  convert(child, output, "{suffix} "); 
  } 
  output.println("{suffix} </children>"); 
  output.println("{suffix}</Group>"); 

 
function convertGroup(group: Group, output: PrintWriter, suffix: String): Void { 
  output.print("{suffix}<Group"); 
  convertNodeAttributes(group, output); 
  output.println(">"); 
  output.println("{suffix} <children>"); 
  for (child in group.content) { 
  convert(child, output, "{suffix} "); 
  } 
  output.println("{suffix} </children>"); 
  output.println("{suffix}</Group>"); 

 
function convertSVGPath(svgPath: SVGPath, output: PrintWriter, suffix: String): Void { 
  output.print("{suffix}<SVGPath"); 
  convertNodeAttributes(svgPath, output); 
  convertShapeAttributes(svgPath, output); 
  output.print(" content=\"{svgPath.content}\""); 
  output.println("/>"); 

 
function convertNodeAttributes(node: Node, output: PrintWriter): Void { 
  if (node.id != null) { 
  output.print(" id=\"{node.id}\""); 
  } 
  if (node.styleClass != null) { 
  output.print(" styleClass=\"{node.styleClass}\""); 
  } 
  if (node.style != null) { 
  output.print(" style=\"{node.style}\""); 
  } 
  if (not node.blocksMouse) { 
  output.print(" mouseTransparent=\"true\""); 
  } 
  if (node.opacity != 1) { 
  output.print(" opacity=\"{node.opacity}\""); 
  } 
  if (not node.visible) { 
  output.print(" visible=\"false\""); 
  } 
  if (node.disable) { 
  output.print(" disable=\"true\""); 
  } 
  if (node.translateX != 0) { 
  output.print(" translateX=\"{node.translateX}\""); 
  } 
  if (node.translateY != 0) { 
  output.print(" translateY=\"{node.translateY}\""); 
  } 
  if (node.translateZ != 0) { 
  output.print(" translateZ=\"{node.translateZ}\""); 
  } 
  if (node.rotate != 0) { 
  output.print(" rotate=\"{node.rotate}\""); 
  } 
  if (node.scaleX != 1) { 
  output.print(" scaleX=\"{node.scaleX}\""); 
  } 
  if (node.scaleY != 1) { 
  output.print(" scaleY=\"{node.scaleY}\""); 
  } 
  if (node.scaleZ != 1) { 
  output.print(" scaleZ=\"{node.scaleZ}\""); 
  } 

 
function convertShapeAttributes(shape: Shape, output: PrintWriter): Void { 
  if (shape.fill != null) { 
  output.print(" fill=\"{paint2FXML(shape.fill)}\""); 
  } 
  if (shape.stroke != null) { 
  output.print(" stroke=\"{paint2FXML(shape.stroke)}\""); 
  } 
  if (shape.strokeWidth != 0) { 
  output.print(" strokeWidth=\"{shape.strokeWidth}\""); 
  } 

 
function paint2FXML(paint: Paint): String { 
  return "black"; 
}

Je n'a pas encore lu comment sont déclarées les Paint en FXML donc pour le moment ça sort tout en noir ; un de ces quatre il me faudra aller lire les specs....
Il faudra faire un peu plus de boulot pour adapter des concepts plus complexes du FXZ comme le fait qu'il peut embarquer des fichiers images ou des media et extraire ces donnés dans des fichiers accessibles par le FXML via le CLASSPATH.
Par chance, ce n'est le cas d'aucun des fichiers qu'il me faut convertir. En tout cas, pour ce simples SVGPath, le code rapide à écrire et la conversion est efficace.

Évidement dans le cas de contrôles, tout n'est pas convertible à 100%, mais le résultat fourni est une bonne base de travail (et puis comme indiqué plus haut, on pourra corriger cela via l’éditeur FXML quand il sera disponible).

Il reste à espérer qu'un jour Oracle fournisse ENFIN un convertisseur de SVG vers FXML (pour le moment pas d’équivalent à la Production Suite à l'horizon) et surtout rajoute directement le support du SVG dans JavaFX 2.0.
Apparemment on peut afficher du SVG via WebView (pas vraiment pu tester pour le moment) mais même si cela marche, c'est loin d'offrir le même confort d'usage que d'avoir un accès direct aux divers nœud graphiques du design.

Même si je fais encore faire de la maintenance de code FX 1.x pendant quelques temps, c'est probablement l'un des derniers projets que j'ai créé pour ce framework et ce langage. Ça fait bizarre...

Vous devez être identifié pour poster un commentaire.

21/12/2011

Permalink 23:27:14, Catégories: JavaFX 2.x, 22 mots   French (FR) , bouye

JavaFX 2.1 early access pour Windows et MacOS

La première bêta publique de la version 2.1 est désormais disponible sous Windows et MacOS.

Téléchargez JavaFX 2.1 b06

Vous devez être identifié pour poster un commentaire.

05/12/2011

Permalink 00:14:14, Catégories: JavaFX 2.x, 466 mots   French (FR) , bouye

Passage a NetBeans 7.1 RC1, CSS & JavaFX 2.0.2, 2.1

Un collègue m'a fait remarqué la semaine dernière que la version RC1 de NetBeans 7.1 est disponible. Étant donné qu'il y avait pas mal de bugs dans la version Beta (celle qui a été publiée durant la JavaOne 2011 pour la sortie de JavaFX 2.0), c'était intéressant de changer.

Comme de bien entendu, les projets précédemment créés ne sont pas compatibles avec la nouvelle version. Il faut donc en recréer des nouveaux, ce qui est toujours une perte de temps monstrueuses surtout quand il s'agit de recréer les liaisons et dépendances inter-projets ou encore aller fouiller dans ses scripts ANT pour trouver des bouts de code customisés et vérifier qu'ils fonctionnent encore.

Il y a eut un petit changement dans le packaging des applications JavaFX. Ains, désormais, les CSS sont compilés lorsqu'une application est packagée dans un JAR.

Par exemple le code suivant:

 
// CSS. 
ClassLoader loader = getClass().getClassLoader(); 
URL cssURL = loader.getResource(__DIR__ + "resources/MonApp.css");  
scene.getStylesheets().add(cssURL.toExternalForm()); 

Fonctionne lorsque l'application est directement lancée depuis NetBeans (car NetBeans lance désormais la version non-packagée des classes qui se trouvent dans le répertoire build du projet) mais plus lorsqu'on exécute le JAR ou que ce projet est inclus dans un autre (NullPointerException sur cssURL.toExternalForm()).

Note : ici __DIR__ est une classe custom qui permet de lier un répertoire de ressource a un package donné (comme l'ancienne variable de script __DIR__ en JavaFX 1.x) ce qui permet d’éviter d’écrire des chemins d’accès en dur dans le code, évitant ainsi de s'arracher les cheveux en cas de refactoring de packages.

Une rapide inspection du JAR montrera que le fichier CSS est absent et a été remplacé par un fichier bss du même nom.
Ainsi il faudra remplacer le code plus haut par :

 
// CSS. 
ClassLoader loader = getClass().getClassLoader(); 
URL cssURL = loader.getResource(__DIR__ + "resources/MonApp.css"); // text CSS. 
if (cssURL == null) { 
  cssURL = loader.getResource(__DIR__ + "resources/MonApp.bss"); // binary CSS. 

scene.getStylesheets().add(cssURL.toExternalForm()); 

Il est toutefois possible de configurer cela dans les options du projet en décochant la case Properties->Build->Packaging->Binary Encode JavaFX CSS File.

La compilation dans NetBeans produit des messages d'avertissements inintéressants, ainsi on trouvera :

Warning: offlineAllowed not supported by this version of JavaFX SDK deployment Ant task. Please upgrade JavaFX to 2.0.2 or higher.

Or bien sur cette version n'est pas encore disponible pour le moment.

À noter que le post de Richard Bair concernant l'open-sourcing des contrôles fait état qu'une version 2.1 est en route également.

The following query exposes the list of bugs and features for UI controls presently targeted at 2.1.

Vous devez être identifié pour poster un commentaire.

11/10/2011

Permalink 04:40:26, Catégories: JavaOne, JavaOne 2011, 72 mots   French (FR) , bouye

Le contenu de la JavaOne chez Parleys

Comme mentionné dans mes notes sur 3eme KeyNote, Oracle a conclu un accord avec Parleys.com pour mettre en ligne progressivement, et ce durant toute l’année à venir, le contenu de la JavaOne sous forme d'enregistrements vidéos pour les KeyNote mais aussi de slides synchronisés avec des enregistrement audio pour les sessions.

Les premières sont dors et déjà disponibles sur http://www.parleys.com/#st=4&id=102979

Vous devez être identifié pour poster un commentaire.

07/10/2011

Permalink 02:18:16, Catégories: JavaOne, JavaOne 2011, 142 mots   French (FR) , bouye

It's a wrap!

Après 5 jours de pluie, les Clouds* se sont enfin dissipés et le concert de clôture gracieusement offert par Oracle a actuellement lieux dans les jardins du Yerba Buena, derrière le Moscone Center North. La bouffe est bonne et le groupe Berlin va bientôt commencer à jouer... Je ne peux cependant m'empêcher de remarquer qu'il y a plus de badges OpenWorld que de badges JavaOne là où je me trouve (en fait, ce sont principalement des employés d'Oracle).

La garden party d'Oracle.

*Toute la partie JEE de la conference a été dominée par des considérations sur le Cloud computing.

Ps : quand au fait que des avions de chasse ont survolé la ville tout l'apres-midi, cela vient du fait que la Fleet Week a commencé, la flotte du Pacifique est de retour et San Francisco grouille de marins désormais.

Vous devez être identifié pour poster un commentaire.

Permalink 01:16:13, Catégories: JavaOne, JavaOne 2011, 277 mots   French (FR) , bouye

JavaFX and its Front-End Ticket to the Theater of War

La dernière session du jour et de la JavaOne 2011 en fait !

#Steven Koucouthakis, Robert Stout - Sierra Nevada Corporation
-> compagnie aéronautique et acquisition de données

Apres avoir envisagé de nombreuses technologies, ils ont choisi JavaFX (1.3) pour développer une application agréable. L'application est pour le moment 1.3
La BD est MySQL (facile à mettre en placevet à customiser) et les transactions sont en Hibernate.

Ils ont du modifier les contrôles par défaut pour les rendre redimensionnables plus aisément. 

Les données EXIF des images contiennent les informations géographiques nécessaires positionner correctement ces images sur une carte GoogleMap.
Support des media pour pouvoir visionner jusqu'à 4 video de surveillance à la fois.

Etant donné qu'ils avaient déjà l'expérience de produits similaires, ils ont pu se focaliser sur l'UI et faire une application très graphique et intuitive.

Il faut cependant apprendre à rester sobre en ce qui concerne l'eye candy et les animations.
Donc le layout de base (sous la décoration graphique) reste très simple avec des VBox et HBox imbriquées. Les vues devaient être redimentionnables dès le départ.

Issues :
Problèmes de synchronisation et d'integrité des données :
-> création d'un modèle intermédiaire qui sert de cache etre l'UI et Hibernate.
-> l'intégrité est assurée par une combinaison de binding et de trigger onReplace.
Trop de binding ralentissait trop le programme.
Problèmes de layout (tu m'étonnes)
Des ralentissements lors que trop d'images sont chargées en même temps. Idem quand ils chargeaient des images trop grosses.
Problème également lors des affichages en miniatures à cause de leur ratio.

Le logiciel de télésurveillance en JavaFX 1.3.1

Vous devez être identifié pour poster un commentaire.

06/10/2011

Permalink 23:51:29, Catégories: JavaOne, JavaOne 2011, 269 mots   French (FR) , bouye

Visualisation of Geomaps and Topicmaps using JavaFX 2.0

Avant la session, j'ai eu la chance de parler à Stephen Chin (de JFXtras, à ne pas confondre avec celui qui bosse à Intel et qui s'est exprimé durant le KeyNote de mardi) et j'ai pu le remercier pour le boulot qu'il a fait pour nous simplifier la vie dans JavaFX 1.x.

#Johan Vos, James Weaver
Parti d'une discussion sur comment rendre les cartes progressives et surtout mémorables et attrayantes pour les utilisateurs.

La présentation est également réalisée en JavaFX (James Weaver n'aime pas PowerPoint). Les concepts utilisés dans ce module de présentation sont les mêmes concepts que ceux d'un SIG -> apparition progressive des données + niveau de détails.

Utilisation de WebView pour appeler GoogleMap -> facile mais peu flexible (limité par ce que Google autorise).

Rendu par tile en découpant la carte en carrés de 256x256 agencés dans une structure de quad-tree.

Cube 3D : normalement le bug d'ordonacement sur l'axe Z qui existait dans JavaFX 1.3.x a été corrigé. L'ombrage est réalisé artificiellement cependant (la carte est translucide et James Weaver change la couleur du rectangle qui se trouve derrière).

FreeBase.com : ils ont créé une app de Topicmaps qui fait des query sur ce site. Les données retournées sont sous format JSON.

Optimisation de la mémoire avec des soft références. Il faut retirer de la mémoire les noeuds qui ne sont plus visibles pour libérer de la mémoire des que possible et les soft references aident à faire cela facilement.

Presentation humoristique encore comme dans l'habitude de James Weaver.

James Weaver et Yohan Vos se préparant pour leur présentation.

Vous devez être identifié pour poster un commentaire.

Permalink 22:19:21, Catégories: JavaOne, JavaOne 2011, 103 mots   French (FR) , bouye

Fin d'OpenWorld ?

Des piles et des piles de bagages s'entassent désormais dans le hall du Hilton et des bus Oracle attendent à l'exterieur probablement pour reconduire les participants à l'aéroport.

La réception du Hilton encombrée par des valises en tout genre...

Bien que la JavaOne ne soit pas encore officiellement finie avant 16h30 (et qu'il y a le concert de clôture après), des employés commencent déjà à replier certaines décorations pour les empiler au café de rue dans Mason street (il aura au final peu servi puisqu'il pleuvait depuis lundi).

Un avion de chasse est passé il y a peu au dessus du centre-ville ce qui a surpris pas mal de monde.

Vous devez être identifié pour poster un commentaire.

Permalink 21:39:56, Catégories: JavaOne, JavaOne 2011, 117 mots   French (FR) , bouye

JavaFX 2.0: Develop a User-Friendly Graphical Dashboard of Rich Elements

# Patrice Goutin (aussi un Français),  Briab Oliver
Il s'agit d'un atelier de 2h dans lequel les participants vont créer une application avec JavaFX 2.0 b34 pour se connecter à une base de données distribuées tournant sous Oracle Coherence.

-> exemple simples d'introduction aux BarChart, LineChart, PieChart. 
-> lancement de Coherence et interface Swing pour vérifier le contenu.
-> modification des graphes FX pour afficher le contenu de Coherence.

Les exemples sont un peu rudimentaires et se basent sur une ancienne beta (les exemples doivent être soumis longtemps à l'avance pour être validés pour la JavaOne). Par exemple, ici la mise à jour des graphes est faite par une TimeLine plutôt que par un Service.

Le matériel fourni pour le labo.

Vous devez être identifié pour poster un commentaire.

Permalink 19:43:10, Catégories: JavaOne, JavaOne 2011, 262 mots   French (FR) , bouye

Java Community KeyNote

Le dernier KeyNote de la JavaOne, IBM sera le dernier des 3 Diamond Sponsors à s'exprimer. 

#Jason McGee, IBM
 
Cloud.contains(Java); 
Java.equals(IBM); 

La KeyNote d'IBM. IBM compte bien continuer à s'impliquer à fond dans la communauté.

Java doit pouvoir s'adapter aux rapides changements de configuration sur le Cloud. 
-> comment utiles moins de ressources, moins de mémoire : modularité-> plus de mémoire dispo
-> plus de partage de code, classes, entre plusieurs JVM, etc.
-> meilleure communication entre Java et l'infrastructure sous-jacente.
-> meilleure intégration du déploiement.

Balanced GC pour la gestion des gros tas de mémoire disponible sur les JVM IBM depuis septembre 2011.

Problème de Scaling -> Elastic Data Grid

Problèmes de deploiement
-> IBM Workload Deployer

IBM continuera à s'impliquer fortement dans Java.
 
IBM.says("Thank you!"); 

->Shara Chander
Hommage à Steve Jobs.

L'hommage à la mémoire de Steve Jobs.

JCP

Les dernières nominations au JCP.

Problème d'égalité des genres dans la communauté.

Il faut encore faire des progrès concernant l'égalité des genres dans la communauté Java.

Remerciements aux aides venant des JUG Pour l'organisation de la JavaOne.

Un gros merci à tous ceux qui ont aidé pour cette JavaOne.

-> Donald Smith
Java Community Roundtable

La table ronde a été l'occasion de faire des sondages par SMS auprès des participants dans la salle. Steve Chin de JFXtras.

-> Stephan Janssen
Le contenu de la JavaOne sera disponible sur Parleys.com.
Tout sera mis en ligne progressivement à partir d'aujourd'hui et tout au long de l'année.

-> ?, Tim Biernat - Rockwell Automation

Duke Choice Awards

Les Duke Choice Awards.

-> ?, Andreas Stefik - Sodbeans
Language pour permettre aux aveugles de programmer avec des indications audio

-> Vinicius Senger - jHome
Domotique en Java EE.
GlassFish, BlueTooth, JSF, Enterprise JavaBeans, JavaFX, etc.

-> The Java posse

=> Il faut que la communauté dirige le futur contenu de la JavaOne 2012.

Il faut que la communauté se fasse entendre d'Oracle !

Le KeyNote se conclut par la rediffusion du clip "Java Life - Code hard in my cubicle".

Vous devez être identifié pour poster un commentaire.

« Page Précédente 1 2 3 4 5 Page suivante »

Liste des blogs

< bouye : blog />

Catégories


Rechercher

<  Avril 2012  >
Lun Mar Mer Jeu Ven Sam Dim
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30

Syndiquez ce blog XML

Articles :

Commentaires :

 
 
 
 
Partenaires

Hébergement Web