Paintual, un outil de sélection

English ?  see below after the video frame 🙂

Et une aventure !

Je n’avais pas pensé que quelque chose d’aussi simple en apparence puisse demander autant de travail : du code pour créer un cadre de sélection rectangulaire, le déplacer et le redimensionner. J’avais pourtant pris soin de bien réfléchir avant de commencer, tenter d’entrevoir les écueils possibles. Et pourtant…

Mais j’ai réussi. En premier j’ai eu la bonne idée que ce cadre de sélection soit dessiné sur un canevas différent de celui sur lequel se trouve l’image, laissant ainsi le soin à WPF de redessiner l’image lorsque le cadre est déplacé. C’était une bonne affaire, d’autant que WPF peut redessiner beaucoup plus vite que si j’avais écrit tout le code à cet effet. Le canevas sur lequel je dessine le cadre de sélection s’appelle “SelectionGlass” avec comme idée principale d’appliquer virtuellement une plaque de verre sur l’image et de tracer par-dessus. Cette plaque aurait les mêmes dimensions que l’image et serait positionnée au même endroit. Ça aussi c’était facile.

J’avais pensé qu’en cliquant avec la souris et en maintenant le bouton enfoncé j’amorçais le travail de traçage du cadre de sélection. Au fur et à mesure que la souris se déplace, les dimensions du cadre sont mises à jour en temps réel. Ça aussi, ça allait bien. Je pouvais calculer la position des quatre coins du cadre et leur superposer des poignées de modification.

Une fois le cadre dessiné, il me fallait ajouter du code pour pouvoir le déplacer. Facile aussi : le cadre est une instance de System.Windows.Rectangle, une classe graphique qui contient tous les événements gérables de la souris, du stylet ou “touch”. Donc simplement réagir à MouseDown, MouseMove et MouseUp et ça allait bien. Tout en déplaçant le rectangle, les poignées suivaient, pas automatiquement, mais 4 lignes de code et c’était tout ce qu’il fallait.

Redimensionner le cadre. Les petites poignées étant aussi des instances de System.Windows.Rectangle je pensais que simplement d’utiliser une tactique MouseDown, MouseMove et MouseUp devrait suffire. Mais c’était sans compter le fait que même si on clique sur une poignée et qu’on commence à la déplacer, si le code de gestion de retraçage du rectangle et de positionnement des autres poignées n’est pas assez rapide, la souris vient à quitter la poignée et puis ça ne fait plus le travail comme on veut. D’étranges bogues apparaissent. Pour conserver la fluidité, il faut passer le contrôle de la poignée à la “SelectionGlass” qui prend, elle, toute la surface et donc garde contact avec la souris. Étape réussie.

Mais si l’utilisateur diminue la dimension du cadre au point où sa taille devient nulle ou qu’il se trouve renversé (effet miroir) là c’est du sérieux. Parce que chaque poignée est responsable d’un ensemble de modifications possibles (ex.: la poignée du coin inférieur droit peut agrandir le cadre vers la droite et vers le bas ou le rapetisser vers la gauche ou le haut ; le coin opposé en diagonale ne bouge pas tandis que les deux autres sont repositionnés en accord avec la poignée en mouvement, ouf !), il m’aurait fallu gérer quatre cas, chacun avec ses petites particularités.

Il m’est venu l’idée de considérer chaque poignée comme étant tout simplement une rotation du cadre. Ainsi si l’utilisateur déplaçait la poignée supérieure gauche, il suffisait de créer une rotation virtuelle dans le code (dont l’effet est invisible pour l’utilisateur) de manière à pouvoir travailler la transformation comme si c’était le code de la poignée  du coin inférieure droit. Vous êtes un peu perdu ? Moi aussi j’ai été perdu, et plus d’une fois.

Pour bien réussir ces transformations virtuelles, rien de tel que de travailler avec des vecteurs et des matrices. Si vous comprenez l’anglais, cette série de vidéos est très claire :  https://www.youtube.com/watch?v=fNk_zzaMoSs. Le .NET Framework propose des classes pour travailler vecteurs et matrices mais au même moment je suis tombé sur cette librairie Accord.Net qui offre une quantité de fonctionnalités diverses vraiment impressionnante. Alors, bon on ajoute la librairie au projet et on peut travailler en moins de deux avec vecteurs et matrices. Ça a bien fonctionné mais je me suis emberlificoté avec les positions, les séquences, les inversions, etc.

C’était long, mais j’ai réussi, et le code est assez clair. Une dernière chose : le cadre de sélection doit se repositionner de lui-même lorsqu’on zoome l’image et aussi lorsqu’on joue avec les barres de défilement. Détails qui se sont réglés en moins de deux (quoique mes barres de défilement nécessitent un peu de polissage).

Il me reste à faire le code qui permet la sauvegarde des sélections d’images.

Au bout du compte, j’aurai travaillé pas mal d’heures pour me faire un outil qui devrait me permettre de sauver beaucoup de temps à court terme : ouvrir une image, sélectionner une partie, cliquer sur “Entrée” ou même simplement double cliquer sur la sélection et sauvegarder avec un nom de fichier séquentiel généré automatiquement. Répéter.

* * *

And an adventure!

I did not anticipate that something so simple in appearance could require so much work: code to create a rectangular selection box, move and resize it. Though I was careful to have a work plan before starting in order to foresee any pitfalls, well…

But I succeeded. At first I had the good idea that this selection frame be drawn on a different canvas from the one on which the image is, thus leaving it to WPF to redraw the image when the selection frame is moved. It was a good deal, especially since WPF can redraw much faster than if I had written all the code for that purpose. The canvas on which I draw the selection frame is called SelectionGlass with the main idea to virtually apply a glass plate on the image and draw a selection over it. This plate would have the same dimensions as the image and would be positioned at the same place. That was easy too.

I thought that by clicking with the mouse and holding down the button I can start drawing the selection frame. As the mouse moves, the frame dimensions are updated in real time. That was good too. I could calculate the position of the four corners of the frame and superimpose them with resizing handles.

Once the frame was drawn, I had to add code to move it. Easy too: the frame is an instance of System.Windows.Rectangle, a graphics class that contains all the manageable events of the mouse, stylus or “touch.” So I just needed to add code to react to MouseDown, MouseMove and MouseUp and it was fine. While moving the rectangle, the handles followed, not automatically, but 4 lines of code sufficed.

Resizing the frame. The small handles being also instances of System.Windows.Rectangle I thought that simply using MouseDown, MouseMove and MouseUp event handlers should suffice. But it was without considering the fact that even if one clicks on a handle and starts to move it, if the management code of tracing and positioning the selection rectangle is not fast enough, the mouse gets to leave the handle and then it does not do the job any more. Strange bugs appear. To maintain fluidity, it is necessary to pass the control of the handle to the “SelectionGlass” which occupies all the surface and thus keeps in touch with the mouse. Successful step.

But if the user decreases the size of the selection frame to the point where its size becomes zero or is reversed (mirror effect) then there’s a problem. Because each handle is responsible for a set of possible modifications (eg: the handle on the bottom right corner can enlarge the frame to the right and down or shrink it to the left or the top, the opposite corner diagonally does not move while the other two are repositioned in accordance with the moving handle, phew!), I would have had to manage four cases, each with its peculiarities, and the reverse/mirror effect.

It occurred to me that if I consider each handle as four variants of the same movement and modification by virtually rotating the rectangle, I could simplify the code since I would have only one case to manage instead of four. That required to calculate transformations on the background while showing the adapted result for each handle. Something not easy to explain this in a few words and also not easy to code as I was not experienced with thinking those transformations.

To succeed in calculating these virtual transformations with a little help with vectors and matrices. This video series is very clear on the topic: https://www.youtube.com/watch?v=fNk_zzaMoSs. The .NET Framework offers classes to work with vectors and matrices, but at the same time I came across this Accord.Net library which offers an impressive number of features. So, I added the library to the project and could work with vectors and matrices in no time. It worked well but I got all mixed up more than once with positions, sequences, inversions, etc.

It took quite some time, but I succeeded, and the code is pretty clear. One last thing: the selection frame must reposition and resize itself when zooming the image and also when playing with the scroll bars. Details that I could settle easily (although my scroll bars require some fine tuning).

I still have to make the code that allows saving the image selections.

In the end, I will have worked many hours to build a tool that should allow me to save a lot of time in the short term: open an image, make a selection, click on “Enter” or even double click on the selection which is to be saved with an automatically generated sequential file name. Repeat.

Deux sujets

En premier, Paintual est en train d’avoir un outil de sélection. Encore bien primitif mais je suis satisfait de sa progression. L’image apparaît sur un panneau nommé Canvas. Quand vient le temps de faire une sélection, un nouveau panneau invisible est créé par dessus. Ce panneau reçoit les instructions de dessin et de mouvement d’une figure représentation une sélection.

Il me reste à copier les dimensions et coordonnées du rectangle de sélection pour aller extraire la portion d’image correspondante en dessous. Évidemment si on tient compte du reste de l’application, ça ne sera pas si simple : il faut connecter l’outil de sélection côté interface au Visual Property Page pour qu’il reçoive les dimensions et coordonnées pour les envoyer au gestionnaire principal (Workflow) qui, lui, coordonne l’ensemble des activités. J’ai aussi à créer des propriétés à l’outil de sélection pour pouvoir indiquer dans quel répertoire sauvegarder la portion d’image sélectionnée. Il y a aussi à coordonner le tout quand on déplace l’image ou qu’on la zoome. Et quelques autres choses à considérer encore. Mais une étape à la fois. Celle-ci, réalisée en assez peu de temps, fait bien mon bonheur comme je le mentionnais plus avant. J’en ai même fait une toute petite vidéo :

 

Ensuite, sur Twitter je suis tombé sur Robbie Barrat (https://twitter.com/DrBeef_) qui propose des peintures générées par intelligence artificielle. Les résultats sont vraiment épatants. C’est un peu dans cette veine que je pensais créer Paintual. En fait l’idée m’est venue (je l’ai peut-être déjà mentionné) en voyant les résultats souvent spectaculaires de https://prisma-ai.com/ . Comme on peut voir j’ai de quoi m’occuper pour très longtemps.

* * *

First, Paintual is in the process of having a selection tool. Still very primitive but I am satisfied with the progress. The image appears on a panel named Canvas. When it’s time to make a selection, a new invisible panel is created on top of the Canvas. This panel receives the drawing and movement instructions of a figure representing a selection.

I still have to copy the dimensions and coordinates of the selection rectangle to extract the corresponding image portion underneath. Obviously considering the rest of the application, it will not be so simple: I have to connect the selection tool at the UI level to the Visual Property Page so that it receives the dimensions and coordinates to send them to the main application manager (Workflow) which coordinates all the activities. I also have to create properties to the selection tool to be able to indicate in which directory to save the selected portion of an image. I also need to coordinate everything when moving the image or zooming it. And a few more things. But one step at a time. This one, accomplished in quite a short time, makes me happy I mentioned earlier. I even made a very small video (see above).

Then, on Twitter I came across Robbie Barrat (https://twitter.com/DrBeef_) who offers paintings generated by artificial intelligence. The results are really amazing. It is a little in this vein that I thought to create Paintual. In fact the idea came to me (I may have already mentioned it) looking at the often spectacular results of https://prisma-ai.com/. This proves I’ve got a lot to keep me busy for a very long time.