Version 0.0.8 !

Threading

Ça n’était pas prévu mais je me suis mis à chercher pourquoi les effets dans Paintual n’étaient visible qu’une fois le travail complété alors que le dessin se met à jour sur le canevas en temps réel (ou à peu près). J’ai trouvé mais ça été assez compliqué à mettre en oeuvre.

En gros, la façon dont le .NET Framework est conçu, tout ce qui concerne le visuel est contenu dans un “thread“. On peut passer tous les calculs et opérations de l’engin de l’application (les calculs de dessin) à un ou plusieurs “threads” pour bénéficier du travail en concurrence sur les différents coeurs du processeur. Mais on ne peut pas facilement appeler des méthodes d’un thread à l’autre, il faut passer par des délégués et une gestion d’événements (enfin c’est ce que j’ai trouvé à date).

J’avais résolu le problème pour le dessin : un thread s’occupe de calculer les opérations de dessin et les inscrire sur le canevas et le thread principal où réside l’interface utilisateur demandait un rafraîchissement de l’image à toutes les 35 millisecondes. Puisque le code de calcul du dessin pouvait modifier le fichier image sans rien demander à l’interface et que l’interface avait accès au fichier image en même temps, la mise à jour ne posait aucun problème particulier. Pour que le dessin s’affiche en “temps réel” (ou à peu près), il fallait un autre thread qui contenait une horloge et à chaque 35 millisecondes, le thread envoyait un signal à l’interface pour le rafraîchissement.

Pour les effets je n’avais tout simplement pas mis la mécanique en place. Mais il y a une différence majeure. Les effets travaillent sur une copie de l’image, l’originale — toujours visible de l’interface — ne changeait pas tout au long du processus d’exécution de l’effet. Une fois l’effet terminé, la copie remplaçait l’originale et ensuite l’interface était mis à jour.

En déplaçant le code d’exécution de l’effet sur un autre thread, il n’y avait plus moyen à partir de celui-ci de pouvoir communiquer à l’interface qu’il devait y avoir un dernier rafraîchissement de l’image une fois la copie de travail remplaçait l’image d’origine, ce qui laissait une image incomplète à l’écran. Bien embêtant car l’utilisateur attendrait inutilement.

La solution que j’ai mise en place est de demander à l’interface d’écouter l’effet émettre un événement de fin de travail et de procéder à la mise à jour finale de l’image. Le code n’est pas élégant du tout : difficile à suivre (une chance que j’ai mis quelques commentaires). Cette nuit j’ai pensé à un système qui centraliserait mieux ces requêtes entre threads inspiré de ceci https://github.com/domenic/extensions/blob/master/WindowsFormsInvokingExtensions.cs  mais j’étais trop enthousiaste et fébrile de jouer avec le nouvel effet basé sur des particules.

Particules

J’ai probablement mentionné une de ces vidéos précédemment :
– https://www.youtube.com/watch?v=BjoM9oKOAKY
– https://www.youtube.com/watch?v=sor1nwNIP9A

Elles font aussi référence à d’autres vidéos (les liens sont sur les pages Youtubes). Toutes ensemble on y apprend comment jouer avec des particules, des vecteurs et des “vector flow fields” (que je pense que ça se traduit par champs de vecteurs).

En gros l’idée est que chaque particule contient des informations sur sa localisation sur le dessin à produire ainsi qu’une direction de déplacement entre chaque opération de traçage.

Quand j’ai commencé à coder tout ça je craignais tomber dans un truc délirant et compliqué pour finalement me rendre compte que c’était finalement assez simple. On peut trouver quantité d’exemples de code sur Internet : je voulais cependant construire mon propre petit engin de particules.

Les différents paramètres que j’ai imaginés devaient me permettre de créer ce genre d’effet :

watercolor sample

À la place j’ai obtenu des effets filetés, des cheveux, des racines. C’est bien joli quand même et j’ai poursuivi mon code.

radial 02

Scanner Glitch

J’ai pensé utiliser cette technique pour donner l’impression des scans glitches dont j’ai parlé auparavant. Pour l’instant le résultat est approximatif à ce que j’avais imaginé quoique, encore, bien joli en soi. Il y a tout un domaine de paramètres à explorer, ce que la vidéo ci-dessous veut montrer.

Les déviations des particules sont le résultat d’addition de vecteurs propres aux particules et ceux du vector flowfield généré à partir d’une image de bruit de Perlin aux dimensions identiques à l’image à traiter. L’intensité lumineuse des pixels de bruit détermine un angle de déviation des particules. Les propriétés de la VisualPropertyPage “Seed”, “Frequency” et “Octaves” déterminent la qualité et l’intensité du bruit. “Angular Spread” (en degrés) permet à la particule de dévier plus ou moins selon l’ouverture de l’angle en plus de la déviation imposée par le vector flowfield. “Steps” est le nombre de fois que toute l’image est balayée par les particules. Avec une valeur de 1, un seul jet de particules (de gauche à droite) est émis. Chaque particule suit les directives des vecteurs à leur pleine intensité. Lorsque le nombre est plus élevé, chaque étape produit aussi autant de particules mais leur transparence et l’intensité des vecteurs est diminué en rapport avec le nombre d’étapes pour donner une composition plus floue par superpositions successives, liant l’image d’origine avec le tracé des particules de la dernière étape.

Combiné avec le fait que maintenant Paintual peut présenter les effets en construction en temps réel, l’ensemble rend ma petite application beaucoup plus attrayante.

Radial

Revenant à l’idée de diffusion des particules dans un esprit de diffusion de l’aquarelle sur le papier, Radial propose comme je l’ai mentionné plus tôt, des cheveux ou des racines selon les valeurs des paramètres.

En gros on a “Seed”, “Frequency” et “Octaves” puisque ici aussi le bruit de Perlin sert à générer des déviations. On a aussi “Steps” mais contrairement à l’effet Scanner Glitch, la transparence reste la même pour chaque étape, l’intensité des vecteurs n’est pas affectée. Il n’y a que superpositions. Les variations sont déterminées par des valeurs aléatoires attachées à chaque particule. Ces valeurs aléatoires étant mal prise en charge (c’est mon code qui est défectueux) j’ai ajouté une propriété “Expansion” qui ouvre l’angle de dispersion davantage. Il y a aussi “Life”, la durée de vie de chaque particule. Plus la valeur de vie est élevée, plus la particule va voyager sur le canevas.

Une couleur fixe est choisie au départ et elle est modifiée avec des valeurs aléatoires au cours du traçage. Contrairement à Scanner Glitch, les couleurs des particules ne sont pas extraites de l’image. C’est juste un choix de programmation pour l’instant : des paramètres à ajouter plus tard.

Bogues

Tout ça ne va pas sans ajouter son lot de bogues à ceux qui étaient déjà là. Il faut à nouveau être bien indulgent avec cette petite créature de code.

Il y aussi quelque chose que je peux mettre dans la catégorie bogues : aucun indicateur qui permet à l’utilisateur de savoir si les opérations sont terminées. En mode développement, on peut voir le graphique des ressources du processeur changer et nous donner une indication précise. Quelque chose à ajouter dans une prochaine version.

Vidéo

Pour terminer, une autre vidéo, faite ce matin pour illustrer le nouveau terreau fertile dans lequel j’ai plongé avec les particules et les vecteurs.

Performance

Les effets avec particules se prêtent particulièrement bien au parallélisme (multi-threading) puisque les actions de chacune sont très limitées dans l’espace du canevas (un pixel modifié par particule).

GitHub

Le projet est maintenant disponible sur GitHub, code source et version installable 0.0.8  .