May 14th, 2009 @ 6:33pm | njames | Categories Development tools, Java | 2 Comments »
Dans ce billet, je parlais d'une application SWT basée sur le widget SWT Nebula Gallery pour naviguer dans une collection en Java/SWT.
J'avais codé un thread de chargement pour avoir un load-when-you-see, et le tout fonctionne plutôt bien.

MAIS, c'est du SWT et cela m'embête un brin. Le but de ce projet est de naviguer dans de grande collection d'images, et il faut aussi que je puisse afficher un certain nombre de metadata associées à chaque image, ainsi je voulais afficher un petit ScrollPane pour chaque image contenant les metadata sous forme textuelle, et faire cela en SWT est apparemment une chose ardue.
C'est ainsi que je me suis mis à coder un widget similaire pour swing, j'ai renommé mon projet utilisant SWT Nebula Gallery en SWTGalatee, et mon nouveau projet swing devient Galatee.
Je pensais utiliser pour cela une JTable et un custom renderer pour les cellules de cette table. Ce custom renderer prendrait son rendu sur un JPanel qui contiendrait une image et un JScrollPane, mais c'est sans savoir comment fonctionne un Renderer en swing (voir ça), en fait un renderer ne produit que des images de composant swing: il ne faut pas s'attendre à une réponse d'événements souris ou clavier sur une image de composant.
Et un JScrollPane sans interaction via la souris, c'est inutile !

Solution adoptée: ne pas utiliser de JScrollPane pour afficher mes metadata, mais plutôt faire un JTextArea de la hauteur nécessaire pour afficher toutes les metadata.
Ainsi, la JTable à des lignes de hauteurs différentes, chaque ligne est de la hauteur de la cellule sur cette ligne qui a la hauteur max.

Le résultat sur la base MIRFlickr:

screenshot-iidf-browser


Plusieurs points sont à préciser tout de même:
  • pour le moment, je conserve en mémoire les thumbnails calculées pour le rendu, en fonction de leurs tailles, on peut vite être confronté à un java.lang.OutOfMemoryError: Java heap space.
    Pour le moment, utiliser les paramètres de la JVM -Xms<initial heap size> -Xmx<maximum heap size> est une solution à court terme. Une amélioration pourrait être d'améliorer le thread d'affichage en utilisant une liste bornée en taille pour les items chargés en mémoire.
  • le widget swing est plus lent que le widget SWT Nebula Gallery (sauf pour le temps de chargement du widget en lui même, swing est beaucoup plus rapide), sur deux points:
    • pour le calcul des thumbnails: dans un premier temps, j'ai utilisé BufferedImage.scaleInstance(...), puis dans un second temps JAI (j'ai testé avec et sans la library C, d'ailleurs étonnamment la library native de JAI est plus lente que la library pure java). Dans les deux cas SWT fait appel à des routines plus rapides.
    • pour l'affichage, la JTable est un widget assez rigide, il y a peut être des paramètres pour assouplir le scrolling mais je ne les connais pas. J'ai quand même positionné l'unité d'incrémentation du scroll (via sp.getVerticalScrollBar().setUnitIncrement(42), sp étant le JScrollPane de ma JTable, pour que le scrolling ne se fasse pas row par row mais sur une quantité de pixels.
  • le layout: avec SWT Nebula Gallery, le layout est dynamique, si la largeur de la fenêtre change le nombre de colonne de la grille d'images s'adapte pour afficher autant de colonnes que possible. Dans mon widget, la JTable empêche ce genre de chose.
    Je pourrais faire écouter le redimensionnement de la JTable et changer son modèle en fonction, mais j'ai peur que cela alourdisse cruellement l'affichage.
    Une solution à adopter serait peut être de changer le modèle sur demande de l'utilisateur, par exemple avec les touches '+' et '-', pour ajouter ou retirer des colonnes au modèle.
    edit 09-05-16: j'ai ajouté les deux comportements (1) '+' et '-' pour modifier le nombre de colonne, et (2) un modèle de table dynamique en fonction de la taille du JScrollPane contenant la JTable, ça se fait bien en codant futé pour modifier au minimum le modèle.
    Ainsi j'ai dû coder aussi l'update de la cellule sélectionnée dans le nouveau modèle (i.e. nouveau modèle car redimensionnement de la table, il faut que la cellule qui était sélectionnée dans l'ancien modèle soit toujours une cellule sélectionnée dans le nouveau modèle), et un scroll de la JTable pour que la cellule sélectionnée soit affichée (un scroll du rendu du nouveau modèle pour que la cellule sélectionnée soit affichée).

Archives:
  • Galatee-0.1.jar
  • edit 09-05-17: nouvelle version avec l'edit 09-05-16 (voir plus haut): Galatee-0.2.jar. Ajout également d'une interface GalateeListener à implémenter pour répondre aux événements sur la grille d'image:
    public interface GListener extends EventListener {
    	public void itemClicked(GEvent e);
     
    	public void itemDoubleClicked(GEvent e);
     
    	public void selectionChanged(GEvent e);
     
    	public void focusChanged(GEvent e);
    }
  • SWTGalatee-0.1.jar

Autres approches éventuellement possibles (mais que je n'ai pas investigué) pour réaliser un tel widget:
  • utiliser un Panel avec un GridLayout, spécifier la taille de la grille du layout en fonction du nombre d'images. Trouver un moyen pour savoir quand une cellule du panel doit être affichée et quand elle n'est plus sur l'écran. Quand elle doit être affichées faire un setComponentAt(...) de la cellule avec un custom panel pour mon image+metadata. Dès que la cellule quitte l'affichage de la fenêtre (par un scrolling), faire un setComponentAt(..., ..., null).
  • ne pas utiliser une table mais une succession de JList, ce qui permettrait de ne pas avoir des lignes de tailles fixes (et pas de ligne du tout d'ailleurs), cependant ce n'est peut être pas visuellement très louable.
April 21st, 2009 @ 9:51pm | njames | Categories Java | 2 Comments »
Ces derniers temps, je cherchais un widget graphique en Java pour naviguer dans une collection d'image (éventuellement assez grande, genre 25.000 documents), quelque chose à la GThumb (en stand alone, pas une application web), avec un chargement dynamique et qui ne vampirise pas le thread d'affichage.
Mais je n'ai rien trouvé qui me convienne vraiment, car de plus, je voudrais pouvoir afficher près de chaque image, des métadonnées issues de traitement d'image (des informations stockées dans un fichier XML à la mpeg-7).

Dans le toolkit SWT, il y a le Nebula project qui a été lancé pour apporter des customs widgets à SWT: CalendarCombo, CDateTime, CollapsibleButtons, CompositeTable, DateChooser, DateChooserCombo, FormattedText, Gallery, Grid, GanttChart, PGroup, PShelf.

Le widget Gallery est très intéressant:

Nebula Gallery SWT widget

En partant d'un snippet pour afficher une collection d'image, j'ai fait un petit projet nommé Galatee, affichant les images avec un chargement dynamique des documents load-when-you-see.
J'ai codé un ImageLoaderThreadPoolThread qui est un thread Java classique, qui vide une queue d'images à charger, et qui démarre un thread SWT GUI de mise à jour de l'affichage une fois qu'une image est chargée (et redimensionnée pour son affichage).

L'application sur le jeu de donnée MIRFlickr:

screenshot-iidf-browser


Un problème cependant: l'intégration de widgets graphique en SWT est une chose un brin... problématique: je voudrais utiliser un scrollpane dans chaque cellule de ma grille d'images, et ajouter la liste de mes métadonnées dans ce scrollpane. En swing on peut faire cela avec un custom components (le widget est instancié une seule fois mais dessiné plusieurs fois, dans un JTableRenderer), mais en SWT c'est plus délicat car les widgets sont des widgets système...


 Valid XHTML 1.0 Transitional Valid CSS! WordPress