(Voir
l'épisode précédent).
Nouvelle version de Galatee, mon petit projet de navigation dans une collection d'image en Java:
Galatee_0.3.jar. Le projet est sous licence
GPL.
(Attention: Je n'ai pas vérifié que l'application fonctionne bien sous d'autres systèmes d'exploitation (comme windows), je développe exclusivement sous GNU/Linux, et j'avoue que par exemple je n'utilise pas
System.getProperty("path.separator")).
- correction de bugs,
- ajout et suppression de colonnes à la table d'affichage avec les touches '+' et '-',
- activation du modèle de table dynamique (nombre de colonnes dans la table d'affichage fonction de la largeur de la fenêtre),
- activation de la navigation dans la liste d'images avec pagination.
Pour tester le jar:
java -jar Galatee-0.3.jar $path
L'application test est codée pour lancer la commande
display de ImageMagick pour les images double-clickées.
Il reste encore un petit problème que je voudrais régler: une queue bornée pour les images chargées en mémoire (avec une gestion multi-pages, i.e. prenant en considération la pagination des documents), mais une stratégie intelligente (et la plus légère possible) pour cela, n'est pas facile à écrire.
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:
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.