Frédéric Mélantois
Manipulation d'images en VB.NET et C#
Nous étudierons les différentes manières d'accéder rapidement aux pixels d'une image, aussi bien en C# qu'en VB.NET. Un passage en revue des principaux formats d'images vous sera proposé.
Par Frédéric Mélantois publié le 18/10/2006 à 22:27, lu 14932 fois, 9 pages
 2 | Principe d'accès aux pixels
Si le framework .NET permet d'accéder à un pixel d'une image, grâce aux méthodes « GetPixel » et « SetPixel » de la classe Bitmap, ceci n'est pas sans conséquence sur la performance de votre application. Imaginez de repeindre une petite image de 320 pixels par 200 en ajoutant un peu de bleu à chaque pixel. Il nous faudra faire appel 64000 fois à la méthode « GetPixel » pour obtenir la couleur du pixel puis 64000 fois à la méthode « SetPixel » pour réaffecter la couleur original avec une addition de bleu.
Un petit test rapide montre que cette façon de procéder est très lente. Les raisons de cette lenteur sont simples : à chaque fois que l'on accède à un pixel que ce soit en lecture ou en écriture, on passe par un certain nombre de couches.
L'idée est donc de réduire au maximum le nombre de ces couches. Les concepteurs du framework .NET ne pouvaient que laisser la possibilité de produire du code non-managé, c'est à dire non géré par la CLR). Avec l'exemple précédent, on se doute que les couches de hauts niveaux de .NET font obstacles à la performance.
Autant on peut accepter que l'affichage d'un composant puisse être plus lent de quelques millisecondes pour l'utilisateur, ceci à des fins de simplification de développement et de réutilisation, autant il est inconcevable de perdre la moindre milliseconde sur le traitement d'un pixel d'une image, car celui-ci se trouve répété.
La classe Bitmap offre donc deux méthodes permettant d'ouvrir une sorte de fenêtre sur les données de notre image dans le monde non-managé. Ces deux méthodes sont « LockBits » et « UnLockBits ». Les données de l'image ne sont pas gérées en mémoire par la CLR. Elles ne sont donc pas soumises aux actions du Garbage Collector qui peut déplacer les objets en mémoire. Les données sont donc fixées dans leur localisation mémoire. Elles sont protégées des accès simultannées en écriture grâce à la méthode « LockBits ». On libère l'accès des données via la méthode « UnLockBits ».
Nous allons pouvoir « pointer » les données en mémoire directement. Etudions comment accéder à cette fenêtre sur le monde non-managé. Pour cela mettons en oeuvre la méthode « LockBits » :

unsafe

            {

                BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, bitmap.Width, bitmap.Height),

                    ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

L'accès aux données se faisant dans un contexte non-managé, il nous faut l'indiquer par le mot-clé « unsafe », si nous souhaitons pointer en mémoire la totalité ou une partie de l'image. A noter, qu'il ne faut pas oublier de modifier les propriétés de votre projet .NET en allouant le « code unsafe ».
La zone pointée de l'image est précisée par la définition d'un rectangle avec ses coordonnées. On précise par l'énumération « ImageLockMode » le mode d'accès (ici, en lecture / écriture) aux données.
Le format de l'image doit être renseigné. Il dépend de la façon dont sont organisées les données et de la taille occupée pour la représentation d'un pixel. Cette taille varie de 1 bit à 32 bits et même plus. Il paraît évident que l'occupation mémoire est fonction de cette taille. La conséquence est que l'on cherchera à occuper le moins de place mémoire sachant que certains formats sont plus adaptés pour obtenir les meilleures performances, comme nous le verrons un peu plus tard.
La méthode « LockBits » nous renvoie un objet « BitmapData » possédant les propriétés suivantes :
  1. « Width » est la largeur de l'image ou de la portion d'image manipulée
  2. « Height » est la longueur de l'image ou de la portion d'image manipulée
  3. « PixelFormat » est le format dans lequel se trouvent les données de l'image
  4. « Scan0 » est l'adresse mémoire des données
  5. « Stride » est le nombre d'octets occupé par une largeur de l'image
Le contexte « unsafe » va nous permettre d'utiliser les pointeurs pour le langage C#. Nous verrons un peu plus loin comment aborder l'accès au pixel en VB.NET.

byte* pixel = (byte*)(void*)bmpData.Scan0;

                //syntaxe equivalente

                //byte* pixel = (byte*)bmpData.Scan0.ToPointer();

                pixel[0] = 255;

                pixel += 4;

                pixel[0] = 0;

Dans le code précédent, nous fixons un pointeur sur le premier octet de l'image. Ensuite, nous portons la valeur de cet octet à 255. Nous déplaçons ensuite notre pointeur de 4 octets. Donc au 5ème octet de l'image, nous portons la valeur à 0.
Nous venons de montrer les bases élémentaires d'accès aux pixels. Bien évidemment, le format étant déterminant pour la taille réservée au stockage d'un pixel, il faut déterminer le format de l'image. Une fois le format de l'image connu, on peut donc estimer la taille de l'image et en particulier savoir où se termine la zone de données pour ne pas modifier une zone non voulue de la mémoire.
Pour une image en noir et blanc, un octet permet de représenter 8 pixels. 4 octets seront nécessaires pour stocker 1 pixel d'une image en 32bits. 1 pixel d'une image 24 bits sera stocké sur 3 octets. Pour une image 16 bits, c'est sur 2 octets.
Les données de l'image sont stockées en rangées virtuelles. Si les données n'occupent pas totalement une rangée, on se retrouve avec des octets en surplus n'appartenant pas à l'image, dont le nombre est appelé Offset.Si pour une image 32 bits, de part la structure de 4 octets, l'Offset est nul, il n'en est pas de même lorsque le format de l'image est différent.
Nous passerons en revue l'accès aux pixels de l'image sous différents formats. Mais avant, regardons comment accéder aux pixels à partir de VB.NET.
 
» Démarrer une discussion
 
Discussion démarée par vattic le 11/04/2011 à 11:02, 1 commentaire(s).
Discussion démarée par olibara le 18/08/2008 à 00:12, 2 commentaire(s).