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.

Paintual 0.0.13.0

L’effet “Flow” est un peu plus rapide, juste un peu. J’ai commencé à pousser l’exécution du code vers le GPU (accélération matérielle). Pour l’instant et encore pour un bon bout de temps, tout le code de “Flow” ne peut être facilement accéléré parce toutes les opérations ne sont pas parallélisables, c’est-à-dire exécutées en même temps, puisqu’il y a de nombreuses dépendances sur les résultats des opérations : telle couleur doit être calculée avant de procéder au calcul du vecteur de déplacement, etc. J’ai à repenser mon algorithme. Cependant, dans l’état actuel de la version 0.0.13.0 c’est quand même un peu plus rapide que la version précédente.

Plusieurs, peut-être trop ? d’options s’offrent à nous lorsque vient le temps de travailler avec le GPU ; chacune a ses qualités, ses limitations. J’ai opté pour une option “morte” de Microsoft : AMP (Accelerated Massive Parallelism) ; morte parce qu’elle ne semble plus être en développement actif. Mais pour ce dont j’ai besoin en ce moment c’est amplement suffisant (effectuer des centaines de calculs simples en parallèle). Il fallait cependant communiquer avec C++ (mon projet est en C#). Pas simple, mais pas si mal.

Ensuite, il a fallu corriger un fichier de Microsoft (amp.h) selon les instructions ici pour que ça fonctionne avec Visual Studio 2017.

Il existe quantité de pages web dédiées à C++ AMP, sans compter les vidéos : une simple recherche Google vous mettra sur la voie.

Pourquoi ai-je choisi cette option plutôt qu’une autre ? Simplement parce qu’elle était déjà disponible sur mon ordi, que j’avais en main un bout de code qui indiquait la marche à suivre avec AMP et que ça correspondait exactement à mon besoin immédiat. Finalement, AMP est censé rouler sur toutes les cartes graphiques (à tout le moins ça fonctionne avec NVIDIA Quadro K2200 et AMD Radeon Vega 8). Pour l’instant c’est aussi la simplicité (relative) d’utilisation qui prime bien davantage que la pleine prise en charge de la puissance de la carte graphique.

Plus tard, évidemment, je me tournerai vers une option plus standard mais je n’ai pas encore fixé mon choix. CUDA est exclu parce que cela restreint mon application à ne rouler que sur un ordi qui a une carte NVIDIA, alors que mon laptop et mon Microsoft Surface sont équipés différemment. Je prévois créer un contrôleur “touch base” sur Surface pour changer les paramètres de l’application en cours d’utilisation (couleurs, traits, effets, etc.) et un ou + ordis pour permettre à plus d’une personne de pouvoir travailler sur la même image en même temps. C’est encore très loin mais c’est dans les plans.

En attendant, on a la version 0.0.13.0 code source et la version installable sous Windows.

geographie nouvelle

Paintual 0.0.12.0

Flous et fluides, deux nouveau effets. “Flow” est loin d’être aussi fluide que ce que je voulais obtenir mais ça reste une belle réussite. Le code inclus dans Paintual n’a pour l’instant que peu à voir avec l’exemple que j’avais trouvé, non que l’exemple n’était pas bon mais je n’arrivais pas à cerner les échelles de valeurs entre force, vélocité et différence de luminance. J’ai donc reconstruit partiellement le code à partir de zéro et ai pris une tangente différente. Pour l’instant le code est encore très lent malgré le multithreading en place. Il est temps de passer à l’accélération via GPU, une toute autre aventure.

Le code et l’installable Windows.

Ci-dessous, petite galerie.

fuild dynamics 04fuild dynamics 05a test 2a testbug

* * *

Blur and Flow, two new effects. Flow is far from being as fluid as what I wanted but it’s still convincing. The code included in Paintual has now very little to do with the example I had found, not that the example was bad but I could not identify the scales of values between force velocity and luminance difference. So I built the code from scratch and took a different tangent. For now the code is still very slow despite the multithreading in place. It’s time to move to GPU acceleration, another adventure.

Above, a sample gallery.

Source code and Windows Installer.

Dynamique des fluides

Presque intéressant. Vidéo accélérée parce que, pour l’instant, le code est lent. À remarquer qu’on aperçoit un effet de grille ; plus la grille est fine, plus l’effet semble naturel même si pour l’instant la fluidité est linéaire.

* * *

Almost interesting. This video shows the evolution at 3x speed because the code is not optimized. There is a grid effect. The finer the grid, the more natural the overall effect looks even if for now fluid direction is just linear.