Un meilleur “undo”

Undo

N’ayant que peu de temps pour réellement replonger dans Paintual, j’ai tout de même tenté de tester mon idée exprimée au billet précédent concernant l’encodage/décodage des images dans la fonctionnalité  undo (retour arrière j’imagine en français). En tournant les coins ronds, j’ai simplement spécifié à Imagemgick d’encoder l’image en Png32, donc de conserver les quatre canaux (R G B et A) pour qu’en relecture il n’y ait pas le problème d’image qui disparaît.

Ça fonctionne. Mais Undo reste capricieux : quelques fois deux étapes sont retirées de l’historique d’un coup. Ça sera à étudier.

amp.h

Dans un autre chapitre, j’ai mis à jour Visual Studio, toujours 2017 mais c’est maintenant la version 15.8.4 et avec elle… le fichier amp.h et son bogue est revenu. Il m’a fallu donc retrouver la page sur Internet où on nous donne si gentiment le correctif à apporter au fichier. Un correctif que vous aurez à apporter aussi si vous utiliser la version code source du projet (parce que amp.h est un “include”, il ne fait pas partie de la solution mise sur GitHub).

Ah oui! amp.h est le coeur de C++ AMP (Accelerated Massive Parallelism), ce qui me permet d’utiliser le GPU dans Paintual.

Glic

J’ai passé du temps avec Glic, je n’ai réussi qu’à transposer le code des différents “color spaces”. C’est tout un morceau à convertir mais je le fais dans un projet à part pour ne pas briser outre mesure Paintual.

* * *

Undo

Having only a little time to actually work on Paintual, I tried nevertheless to test my idea expressed in the previous post about the encoding/decoding of images in the undo feature. I used a quick shortcut and simply told Imagemgick to encode the image using explicitly Png32, preserving the four channels (R G B and A) so when reloaded the images don’t seem to disappear.

It works. But Undo remains capricious: sometimes two steps are removed at once from the history stack. I guess ti needs more of my attention.

amp.h

Something else: I updated Visual Studio, still 2017 but it is now version 15.8.4 and with it … the file amp.h and its bug is back. So I had to find the page on the Internet where is so kindly given the edit to repair the file. A fix that you will have to apply also if you use the source code of the project (because amp.h is an “include”, it is not part of the solution I put on GitHub).

Ah yes! amp.h is the heart of C ++ AMP (Accelerated Massive Parallelism), which allows me to use the GPU in Paintual.

Glic

I spent some time with Glic, I managed to convert the code of the different “color spaces”. Glic will be a big thing to convert but I do it in a separate project so as not to break Paintual unnecessarily.

“Undo”… presque

Ah zut! Un problème que j’avais remarqué quelque temps auparavant vient me mordre au mollet avec le “undo” grrrrr. Il s’agit d’un pépin dans mon code qui ne peut lire correctement un fichier image en mode noir et blanc! Bizarre peut-on penser? Pas tant que ça.

Les images couleur sont sauvegardées en utilisant 4 bytes, un par canal couleur (bleu, vert, rouge) et un pour le degré de transparence, du moins c’est ce que je peux utiliser en récupérant la lecture d’un fichier JPG au moyen de la librairie ImageMagick . En contrepartie, les images en noir et blanc n’utilisent que 3 bytes. Dans la version précédente de Paintual (celle qui utilisait WinForms pour modèle d’interface utilisateur) j’avais déjà réglé le problème mais en créant une nouvelle version pour WPF, je n’ai importé que des segments de code, au fur et à mesure des besoins… résultat: à nouveau le problème de lecture/conversion.

Et comment ce problème peut-il être relié au “undo”? Parce que je sauvegarde l’image sur disque à chaque changement. En revenant en arrière d’une étape, je recharge l’image précédente et paf! L’image est chargée en mémoire et présentée à l’écran mais on ne voit rien.

Je dis tout ça et je n’ai pas été réellement vérifier dans le code, d’autres projets me tiennent assez occupé pour l’instant, c’est l’intuition que j’ai en observant les “symptômes” de mon application.

La suite bientôt. Entre temps, petite vidéo où on peut voir la nouvelle fonctionnalité fonctionner à moitié (parce que finalement, les erreurs c’est drôle et c’est souvent inspirant).

* * *

Ah damn! A problem that I noticed some time ago comes to bite me back with the “undo” grrrrr. This is a glitch in my code that can not correctly decode an image file in black and white! Bizarre? Not that much.

Color images are saved using 4 bytes, one per color channel (blue, green, red) and one for the level of transparency, at least that’s what I can use from a JPG or PNG file using the ImageMagick library. In return, the black and white images use only 3 bytes. In the previous version of Paintual (the one that used WinForms for UI model) I had already fixed the problem but by creating a new version for WPF, I only imported segments of code as needed resulting in, again, the read/convert problem.

And how can this be related to the “undo”? Because I save the image on disk with each edit. To go back one step, I reload the previous image and pow! The image is loaded into memory and, if not decoded properly, nothing is displayed (although the image ‘space’ is there as we can draw over it).

I say all this and I have not really been checking in the code, other projects keep me busy enough for now; it is by intuition that I have come up to this conclusion, observing the “symptoms” of my application.

More to come soon. In the meantime, a little video where you can see the new half-feature (because in the end mistakes are funny and it’s often inspiring).

À la conquête du “Undo”

Une application de dessin sans le “undo” (annuler ou retour arrière ? en fait je sais pas trop, je n’utilise jamais mon ordi en français) c’est très handicapant, d’autant que je conçois Paintual comme une plateforme d’essai, ce qui implique forcément faire et défaire.

J’ai révisé tout le code des grands cycles de l’application:
– workflow (qui contenait VIOME [le gestionnaire direct des actions de l’utilisateur] qui a disparu depuis) qui détermine les actions de l’application selon les séquences d’actions de l’utilisateur (clics de souris, déplacements, sélection d’outils, etc.);
– le DrawingBoard (le plan de travail) en deux parties : la surface de dessin (PaintualCanvas) comme telle et son enveloppe (le DrawingBoard) avec les barres de défilement et le contrôle de zoom;
– et la VisualPropertyPage, le panneau d’édition des propriétés des outils de dessin, généré dynamiquement selon l’exposition des paramètres/propriétés des effets et outils au moyen d’attributs et de la réflexion.

Trois cycles qui s’imbriquent et qui me donnent souvent des maux de tête. C’est un peu bête que ça soit si compliqué puisque ce sont trois concepts que j’ai créés et développés progressivement au fur et à mesure que je voulais ajouter des fonctionnalités à l’application. Petit ménage, donc, qui a fait du bien et qui m’a réconforté quant à leur relative complexité/simplicité selon le point de vue.

Alors ça devenait facile d’ajouter le si nécessaire Undo : suffit d’une pile d’historique de travail qui enregistre les différentes actions de l’utilisateur. Pour simplifier, une action consiste soit en un tracé complet avec la souris ou le stylet (cliquer, glisser, relâcher l’outil) ou l’application d’un effet. Cliquer sur Undo reviendrait simplement à afficher l’image d’avant modification et d’enlever une action de la pile d’historique de travail.

Au travail : construction de la pile de type LIFO (Last In, First Out, c’est-à-dire que la série des entrées est séquentielle et chronologique [du plus ancien au plus récent] mais que le retrait ne peut se faire que du plus récent vers le plus ancien)… ça existe déjà tout fait dans le .NET Framework, tant mieux. Créer une classe d’item d’historique qui contient l’image entière avant modification et le type d’outil utilisé pour la modification.

Simple. Ça ne marche pas.

À la première action correspond une première entrée dans la pile, c’est assez évident. Si on clique sur Undo, mais j’avais fait le code de sorte qu’en enlevant un élément de la pile je réaffichais l’image contenue dans l’élément précédent de la pile. Hors, il n’y a pas d’élément précédent. Donc, ajout d’un élément d’historique de départ, un canevas vide.

Erreur de code où je créais deux éléments d’historique : un pour le tracé et un autre pour la sélection d’un outil ou d’un effet. Perdu une heure là-dessus.

Autre erreur : l’accumulation indéfinie de copies d’images “clichés en cours de travail” gonfle dans la mémoire de l’ordi à un rythme stupéfiant (en mémoire les images sont décompressées; le moindre petit jpg est en fait un gros monstre). Bon, il faut pour l’instant limiter la pile à, disons, 10 degrés de retour. Mais ça signifie que je dois retirer le plus vieil élément mais la pile n’est pas conçue comme ça. Sans compter que le premier élément doit rester puisque c’est l’état d’origine du dessin avant toute modification.

Moins simple qu’il n’y paraît mais c’est à peu près fonctionnel.

J’ai à repenser les détails. La beauté de la chose, cependant, c’est l’intégration incroyablement facile de l’historique dans le Workflow comme si, dès le départ, j’avais prévu le coup, ce qui n’est pas du tout le cas !

À mettre en place aussi c’est la compression des images de chaque étape. Deux choses à appliquer:
– déterminer les zones qui ont changé et n’enregistrer que des portions d’images. Déjà là il y a une réduction considérable de la taille mais la détection prend du temps, donc il faut transférer le travail dans une queue de thread pour ne pas bloquer l’interface, ce qui veut dire aussi savoir détecter la fin du travail dans le thread pour savoir quand on peut retirer un élément de l’historique après qu’il ait été effectivement enregistré;
– sauvegarder les images d’étapes dans des fichiers temporaires sur disque afin de libérer la précieuse mémoire de l’ordi (avec 32 GB de RAM j’ai la paix pour un bout avec de petites images, mais avec les formats de 6000 x 6000 pixels qui me permettent une finesse de détails intéressante, la mémoire se sature rapidement).

Donc je focus sur cette fonctionnalité jusqu’à satisfaction.

Entre temps, je vous laisse avec GLIC , qui roule sous Processing, et dont je veux incorporer certaines fonctionnalités dans Paintual avec, éventuellement si possible, de pouvoir “GLICquer” certaines portions d’une image avec des outils de dessin, soit du glitch-painting. Pour avoir parcouru le code de GLIC, ça sera un assez long travail.