Desktop Ants

Voici un petit programme que j’ai créé il y a quelques années. Son principe est très simple : il permet de faire apparaître une fourmi (représentée par un pixel) se déplaçant à l’écran selon un algorithme défini.

Desktop Ants

Desktop Ants

 

Téléchargement : Exécutable v. 1.0, Fichiers source VS 2008

 

Présentation du projet

 

Cette idée me vient en fait d’un vieux numéro du magazine Sciences & Vie (le numéro 931 d’avril 1995), à l’époque où ils présentaient encore des petits programmes QBasic dans une rubrique nommée « Informatique amusante ». Presque 15 ans déjà, ça ne me rajeunit pas… L’intérêt de cet algorithme était de montrer, avec le déplacement d’un petit pixel, que l’ordre pouvait surgir du chaos. Le parcours que suivait le point semblait en fait totalement chaotique, en dessinant une figure ressemblant à une fourmilière (d’où le nom du programme), jusqu’au moment où, brusquement, il se mettait à tracer une forme parfaitement régulière.

Etape 1

Etape 1


Etape 2

Etape 2

 

J’ai réadapté ce programme pour tester quelques fonctionnalités du .NET Framework, notamment la possibilité d’utiliser les anciennes librairies de Windows avec l’espace de nom System.Runtime.InteropServices permettant d’accéder à certaines fonctions graphiques qui n’étaient pas disponible dans le Framework. Il permet donc de dessiner et de déplacer plusieurs fourmis à l’écran, chacune d’elle étant gérée par un thread. La fourmi va construire son chemin en utilisant les pixels de l’écran, ses choix concernant la direction à prendre étant déterminés par une couleur clé que l’on peut définir dans les options.

On peut à tout moment nettoyer l’écran (et tout le bureau Windows) à l’aide d’un bouton de l’interface. Celle-ci propose aussi une fonctionnalité nommée « Flip screen », qui permet en fait d’inverser horizontalement l’écran grâce à une astuce que je détaillerai un peu plus bas. Cela ne sert évidemment strictement à rien à part perturber la personne qui aura eu la curiosité de cliquer dessus ! Pour l’annuler, il suffira d’appuyer sur le bouton « Clean screen », à condition cependant de le retrouver, l’application se cachant malicieusement dans la barre des tâches juste avant d’effectuer le retournement de l’écran. Pas de panique cependant, un simple survol de la souris dans la zone inférieure de l’écran, ou un ALT+TAB auront vite raison de ce défi !

Options

Options

 

Mais soyons un peu plus sérieux pour revenir aux points intéressants du programme, qui permet donc de trouver des exemples aux cas suivants :

  • Charger et utiliser une Dll externe non managée
  • Obtenir et changer la couleur d’un pixel de l’écran (ou de n’importe quelle fenêtre)
  • Utiliser les fonctions graphiques pour dessiner directement sur le bureau ou l’écran
  • Forcer l’ensemble du bureau Windows à se redessiner
  • Créer des threads paramétrés
  • Réaliser une capture d’écran

 

Fonctionnement

 

Algorithme de la fourmi

Le déplacement de la fourmi est régi par un algorithme très simple. Tout d’abord il faut définir :

  • La couleur de la fourmi
  • Sa position initiale
  • Sa direction initiale
  • Une couleur clé, différente de la couleur de la fourmi

On positionne la fourmi à son emplacement initial. L’algorithme à suivre ensuite est le suivant :

  1. Si la couleur du pixel courant correspond à celle de la couleur clé,
    • alors on colorie le pixel de la couleur de la fourmi et on tourne à droite
    • sinon on colorie le pixel avec la couleur clé et on tourne à gauche
  2. On déplace la fourmi d’un pixel dans la nouvelle direction
  3. On recommence
Déplacement

Déplacement

 

Veillez donc à bien choisir la couleur clé car elle conditionne le déplacement de la fourmi. Elle ne doit pas être égale à celle de la fourmi (noire par défaut) et idéalement, choisir une valeur fortement représentée à l’écran comme le blanc, permettra d’avoir des tracés qui donneront l’impression de suivre les principaux contours des fenêtres.

 

Dessin de la fourmi

Si l’on exclue la possibilité de dessiner n’importe où sur l’écran, on peut penser que ce programme aurait facilement pu se passer des librairies natives de Windows (GDI32). Cependant, aussi surprenant que cela puisse paraître, il n’est pas évident du tout de réussir à changer la couleur d’un seul pixel et d’en obtenir sa couleur avec les fonctions présentes dans le .NET Framework, et plus particulièrement l’espace de nom System.Drawing. Il n’y a que la classe Bitmap à la rigueur qui offre les méthodes GetPixel et SetPixel, ce qui est plutôt restrictif. Avec WPF, c’est encore pire malheureusement, notamment pour tout ce qui concerne le dessin brut (travaillé au pixel près), car l’environnement est plutôt prévu pour du dessin vectoriel.

Ce programme utilise les Dlls gdi32 et user32 de Windows pour les principales opérations graphiques. Pour charger une dll externe non managée (sans .NET Framework), il faut utiliser le mot-clé extern ainsi que l’attribut DllImport, disponible dans l’espace de nom System.Runtime.InteropServices. Cette assembly fourni aussi d’autres attributs pour assurer la compatibilité des structures et énumérations entre l’application .NET et la Dll native.

[DllImport("gdi32")]
public static extern int SetPixel(IntPtr hDC, int x, int y, int color);

 

La plupart des signatures des méthodes de ces Dlls se récupèrent assez facilement sur Internet, avec la liste des structures et énumérations à ajouter. Vous pouvez aussi jeter un œil avec Reflector dans les assemblys .NET, en particulier dans l’espace de nom System.Drawing, qui effectue de nombreux appels à des classes internes natives. Pour cette application, elles ont toutes été rassemblées dans la classe WinNativesMethods.

Globalement le principe est simple : on doit fournir aux méthodes SetPixel et GetPixel du GDI32 un pointeur vers la fenêtre sur laquelle on doit dessiner (ou obtenir la couleur). Le .NET Framework permet la manipulation et l’envoi de pointeurs Windows au moyen de la structure IntPtr. Pour désigner le bureau, il n’y a même pas besoin de récupérer le pointeur d’une fenêtre, il suffit juste de spécifier IntPtr.Zero. Celui-ci nous permet ensuite de récupérer le Display Device Context (DC) associé, à l’aide de la méthode native GetDC, pour pouvoir appeler les méthodes SetPixel et GetPixel. Pensez à bien libérer les ressources à la fin du traitement à l’aide de la méthode ReleaseDC.

Les fonctions ci-dessous montrent comment récupérer un Display Device Context (DC) et l’objet Graphics associé afin de pouvoir dessiner dans une fenêtre extérieure à l’application (pour redessiner la capture d’écran inversée par exemple) :

IntPtr hdc = WinNativesMethods.GetDC(IntPtr.Zero);
Graphics g = Graphics.FromHdc(hdc);

WinNativesMethods.ReleaseDC(this.screenHandle, hdc);

 

Pour les fonctions graphiques il faut faire attention à la façon dont la couleur est stockée en mémoire, qui diffère entre les fonctions natives de Windows et le .NET Framework. A ce sujet vous pouvez consulter l’article sur la classe ColorTranslator : Conversion de couleurs entre GDI+ et GDI32 (et autres) :

int pixColor = WinNativesMethods.GetPixel(hdc,
		this.location.X, this.location.Y);

WinNativesMethods.SetPixel(hdc, this.location.X, this.location.Y,
		ColorTranslator.ToWin32(this.antColor));

 

Pour finir, chaque fourmi est créée dans un thread séparé, dont le point d’entrée est la méthode Animate() de la classe Ant.

 

Nettoyage de l’écran

Pour nettoyer l’écran, il suffit en fait de demander au bureau, ainsi qu’à toutes ses fenêtres, de se redessiner. On peut réaliser cela grâce à la méthode native RedrawWindow, à laquelle il faut préciser les options RDW_UPDATENOW, RDW_INVALIDATE et RDW_ALLCHILDREN.

WinNativesMethods.RedrawWindow(handle, IntPtr.Zero, IntPtr.Zero,
	(int)(WinNativesMethods.RedrawWindowOptions.RDW_ALLCHILDREN
		| WinNativesMethods.RedrawWindowOptions.RDW_UPDATENOW
		| WinNativesMethods.RedrawWindowOptions.RDW_INVALIDATE));

La variable handle est de type IntPtr. Elle correspond au pointeur vers la fenêtre parente que l’on souhaite redessiner. Dans notre cas, comme pour le reste des fonctions graphiques, on utilisera le pointeur IntPtr.Zero pour désigner le bureau Windows.

 

Capture d’écran

Pas besoin des fonctions natives pour réaliser une capture d’écran : celle-ci est disponible directement à partir d’un objet Graphics grâce à la méthode CopyFromScreen.
Pour cela il faut simplement créer un Bitmap de la taille de l’écran (disponible dans System.Windows.Forms.SystemInformation.VirtualScreen), puis créer un objet Graphics à partir de ce Bitmap à l’aide de la méthode statique FromImage avant d’appeler CopyFromScreen :

Bitmap bmp = new Bitmap(SystemInformation.VirtualScreen.Width,
			SystemInformation.VirtualScreen.Height)
Graphics gbmp = Graphics.FromImage(bmp);

gbmp.CopyFromScreen(0, 0, 0, 0, bmp.Size);
bmp.Save("Screenshot.jpg", System.Drawing.Imaging.ImageFormat.Jpeg);

gbmp.Dispose();
bmp.Dispose();

 

Le retournement horizontal est une des transformations disponibles directement dans la classe Bitmap.

A noter qu’il est possible de se passer de l’utilisation du GDI32 si l’on souhaite transformer l’application en écran de veille : il suffirait dans ce cas de réaliser une capture d’écran, puis de la mettre dans une classe Bitmap. Celle-ci serait ancrée dans une Winform s’affichant en plein écran, de façon à masquer complètement le vrai bureau. Pour dessiner le trajet des fourmis, on pourrait alors utiliser les fonctions SetPixel et GetPixel de la classe Bitmap.