Bonjour à tous,
Je développe en JavaFX un petit programme de retouche d'images. J'essaie de faire en sorte que ces retouches n'apparaissent pas d'un coup à l'utilisateur-final, mais de manière progressive : pixel par pixel, afin de rendre visible l'exécution des algorithmes de retouche.
Le souci c'est que cet affichage progressif des pixels retouchés se bloque, parfois en début d'image, parfois au milieu… De plus, également de manière aléatoire, une exception est lancée (ou pas du coup) .
Notez que je vous fournis les extraits (pertinents) de code à la fin de ce message, mais aussi que j'ai créé un Gist contenant toutes les sources : https://gist.github.com/anonymous/f978ce6f3157429944ea07b0b7fdc18f
L'exception retournée est (attention, elle est longue) :
Exception in thread "JavaFX Application Thread" java.lang.ArrayIndexOutOfBoundsException: -1
at java.util.ArrayList.elementData(ArrayList.java:418)
at java.util.ArrayList.get(ArrayList.java:431)
at com.sun.javafx.collections.ObservableListWrapper.get(ObservableListWrapper.java:89)
at com.sun.javafx.collections.VetoableListDecorator.get(VetoableListDecorator.java:306)
at javafx.scene.Parent.updateCachedBounds(Parent.java:1591)
at javafx.scene.Parent.recomputeBounds(Parent.java:1535)
at javafx.scene.Parent.impl_computeGeomBounds(Parent.java:1388)
at javafx.scene.layout.Region.impl_computeGeomBounds(Region.java:3078)
at javafx.scene.Node.updateGeomBounds(Node.java:3577)
at javafx.scene.Node.getGeomBounds(Node.java:3530)
at javafx.scene.Node.getLocalBounds(Node.java:3478)
at javafx.scene.Node.updateTxBounds(Node.java:3641)
at javafx.scene.Node.getTransformedBounds(Node.java:3424)
at javafx.scene.Parent.getChildTransformedBounds(Parent.java:1732)
at javafx.scene.Parent.updateCachedBounds(Parent.java:1596)
at javafx.scene.Parent.recomputeBounds(Parent.java:1535)
at javafx.scene.Parent.impl_computeGeomBounds(Parent.java:1388)
at javafx.scene.layout.Region.impl_computeGeomBounds(Region.java:3078)
at javafx.scene.Node.updateGeomBounds(Node.java:3577)
at javafx.scene.Node.getGeomBounds(Node.java:3530)
at javafx.scene.Node.updateBounds(Node.java:564)
at javafx.scene.Parent.updateBounds(Parent.java:1719)
at javafx.scene.SceneSYMBOLEDOLLARScenePulseListener.pulse(Scene.java:2404)
at com.sun.javafx.tk.Toolkit.lambdaSYMBOLEDOLLARrunPulseSYMBOLEDOLLAR30(Toolkit.java:355)
at java.security.AccessController.doPrivileged(Native Method)
at com.sun.javafx.tk.Toolkit.runPulse(Toolkit.java:354)
at com.sun.javafx.tk.Toolkit.firePulse(Toolkit.java:381)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:510)
at com.sun.javafx.tk.quantum.QuantumToolkit.pulse(QuantumToolkit.java:490)
at com.sun.javafx.tk.quantum.QuantumToolkit.lambdaSYMBOLEDOLLARrunToolkitSYMBOLEDOLLAR404(QuantumToolkit.java:319)
at com.sun.glass.ui.InvokeLaterDispatcherSYMBOLEDOLLARFuture.run(InvokeLaterDispatcher.java:95)
at com.sun.glass.ui.win.WinApplication._runLoop(Native Method)
at com.sun.glass.ui.win.WinApplication.lambdaSYMBOLEDOLLARnullSYMBOLEDOLLAR148(WinApplication.java:191)
at java.lang.Thread.run(Thread.java:745)
Solutions que j'ai apportées (aucune n'ayant fonctionné)
J'ai tenté 3 choses pour résoudre ce problème.
Pour commencer, sachez que j'ai 3 classes.
Les trois classes de base (quelle que soit la solution testée)
-
Launcher.java
, qui hérite deApplication
, qui instancieGui
+ fait appel àstage.setScene(gui)
+ fait appel àstage.show()
(du JavaFX quoi, rien de fou !) ; -
Gui.java
, qui hérite deScene
. Elle gère l'événement "Clic sur le bouton INVERSER L'IMAGE". Auquel cas, elle fait appel à son objetgraphicEngine
pour exécuter l'algo de retouche. -
GraphicEngine.java
, qui hérite deService
. Ainsi implémente-t-elle la méthodecreateTask
, qui retourne uneTask
implémentant elle-mêmecall
dans laquelle l'algorithme de retouche est écrit.
Voilà pour l'introduction. Maintenant, voyons plus en détails les 3 solutions que j'ai essayées mais qui n'ont pas fonctionné.
Les trois solutions (aucune n'a fonctionné)
-
Au début, la classe
GraphicEngine
, qui contient les algorithmes de retouche, possédait unPixelWriter
. L'algorithme de retouche de laTask
utilisaitPixelWriter
et ça suffisait pour afficher les pixels retouchés au fur et à mesure du déroulement de l'algo. M'enfin toujours avec ce blocage problématique . On m'a dit qu'il ne fallait pas que le thread-background (doncGraphicEngine
) modifie lui-même l'image, mais qu'il ne doit que travailler sur des données. Apparemment ça violerait les règles de concurrence de JavaFX. -
Du coup, deuxième solution. J'ai créé une
ArrayList
de pixels (x
,y
,Color
) dans maGui
et que je donne, depuis cette dernière, àGraphicEngine
. Par ailleurs,GraphicEngine
n'utilise plusPixelWriter
, ne dessine donc plus rien (donc ne modifie plus l'image), et calcule juste les pixels retouchés. Il ajoute ces derniers à l'ArrayList
. Et dansGui
, j'ai utilisé unChangeListener
qui mettait à jour l'image grâce aux pixels contenus dans l'ArrayList
. Mais bon, ça n'a pas marché. -
Du coup là, à l'instant, j'ai essayé de faire presque la même chose qu'avec l'
ArrayList
, mais sans elle (parce que je pensais qu'il était sûrement un peu trop lourd de mettre à jour l'image dansGui
en devant parcourir chaque élément de l'ArrayList
, tout en sachant que laTask
deGraphicEngine
ajoutait en même temps de nouveaux pixels dedans !). A la place de l'ArrayList
, j'ai un simple pixel (une classeObservable
contenantx
,y
etColor
).Gui
implémenteObserver
. Enfin bref le modèleObserver
quoi ! Donc dès que le pixel est modifié parGraphicEngine
(ses 3 composantes), le pixel notifieGui
qui dessine alors CE pixel. La mise à jour de l'image est donc beaucoup plus légère, puisqu'elle se fait pixel par pixel, il n'y a cette fois-ci pas de parcours d'ArrayList
(qui n'existe pas, remplacée par le pixel).
Aucune de ces 3 solutions ne m'a permis de résoudre ce gros problème.
Les sources
Les extraits de code que je vais vous montrer sont ceux de la dernière solution.
Launcher.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | public class Launcher extends Application { public static void main(String args[]) { launch(args); } public void start(Stage stage) { stage.setWidth(700); stage.setHeight(700); stage.setMaximized(true); Gui gui = new Gui(stage, new BorderPane(), 700, 500); gui.fill(); stage.setScene(gui); stage.show(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class Gui extends Scene implements Observer { public Gui(Stage stage, BorderPane border_pane, int width, int height) { this.graphic_engine = new GraphicEngine(); this.pixel_buffer = new Pixel(); this.pixel_buffer.addObserver(this); } private void bindGraphicEngineEvents() { this.button_reverse_image.setOnAction(button -> { this.graphic_engine.restart(); }); } public void update(Observable o, Object ob) { if(o instanceof Pixel) { draw(); } } public void draw() { this.pixel_writer.setColor(pixel_buffer.x, pixel_buffer.y, pixel_buffer.color); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class Pixel extends Observable { public int x, y; public Color color; public Pixel() { } public Pixel(int x, int y, Color color) { this.x = x; this.y = y; this.color = color; } public void setAttributes(int x, int y, Color color) { this.x = x; this.y = y; this.color = color; this.setChanged(); this.notifyObservers(this.color); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | public class GraphicEngine extends Service<Void> { Pixel pixel_buffer; public int image_width, image_height; private PixelReader pixel_reader; public Task<Void> createTask() { return new Task<Void>() { protected Void call() { double r, g, b; for (int x = 0; x < image_width; x++) { for (int y = 0; y < image_height; y++) { r = pixel_reader.getColor(x, y).getRed() * 255; g = pixel_reader.getColor(x, y).getGreen() * 255; b = pixel_reader.getColor(x, y).getBlue() * 255; pixel_buffer.setAttributes(x, y, Color.rgb((int) (255 - r), (int) (255 - g), (int) (255 - b))); } } return null; } }; } |
Gist
Enfin, si vous êtes chauds pour essayer mon code, le bidouiller et tout, afin de voir par vous-même ce qui se passe, voici un Gist qui vient de sortir du four : https://gist.github.com/anonymous/f978ce6f3157429944ea07b0b7fdc18f
Voilà, j'espère avoir été clair dans mes explications. Merci d'avance !