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
 4 | Gestion des images au format 32 bits
Les images au format 32 bits sont assez facilement manipulables. Sous ce format, un pixel possède quatre composantes : le bleu, le vert, le rouge, la couche alpha. Cette dernière permet de gérer la transparence. Chaque composante se trouve alors codée en 8 bits, c'est à dire un octet, en c# un « byte », en VB.NET un « Byte ».
Sous le format 32 bits, 4 octets sont donc nécessaire pour coder un pixel. En c#, on utilisera par exemple un « uint » ou un « int » si l'on souhaite récupérer la valeur d'un pixel sans le décomposer en diverses composantes. En VB.NET, on utilisera un « Integer » (On ne peut utiliser l'« UInteger » comme nous le verrons un peu plus loin).
Si l'on souhaite modifier une composante particulière d'un pixel, soit on décomposera l'« uint » ou l'« int » (ou « Integer » en VB.NET) soit on fera le choix de récupérer individuellement des « bytes ».
Nous pourrions prendre l'exemple d'une image satellite dont la composante rouge représente le canal infrarouge de la caméra. La végétation donne une forte réponse à l'infrarouge. Pour nous simples observateurs, les zones de végétations apparaissent donc en rouge sur ce type de photos. En intervertissant les composantes rouge et verte, on rend rapidement l'image plus « lisible » à nous, néophytes. Comme les véritables photos satellites Spot ne nous sont pas accessibles gratuitement à moins d'être étudiant ou faire partie du monde enseignant, nous nous contenterons de transformer un être humain en martien ;-)
Voici l'image originale :
 
/content/8d3eb481-8a98-42a6-8033-e851c797aa60/image1.jpg
 
Voici l'image traitée.
 
/content/8d3eb481-8a98-42a6-8033-e851c797aa60/image2.jpg
 
Si nous accédons aux composantes du pixel directement en pointant par « byte », voici le code qu'il est nécessaire d'écrire en C# :

int width = bitmap.Width;

int height = bitmap.Height;

unsafe

{

    BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, width,

    height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

 

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

    byte swap;

    for (int y = 0; y < height; y++)

        for (int x = 0; x < width; x++)

        {

            swap = newPixel[2];

            newPixel[2] = newPixel[1];

            newPixel[1] = swap;

            newPixel += 4;

        }

    bitmap.UnlockBits(bmpData);

}

Le code VB.NET a été donné un peu plus haut.Nous stockons donc la valeur de la composante rouge dans une variable temporaire. Puis, nous affectons à la composante rouge « newPixel[2] » du pixel, la valeur de la composante verte « newPixel[1] ». Les composantes bleue « newPixel[0] » et alpha « newpixel[3] » ne sont pas affectées. Une fois les données modifiées, on libère la zone de donnée de la Bitmap par la méthode « UnLockBits ».
Nous venons de manipuler les pixels par octets (byte), essayons de voir comment modifier un pixel en pointant l'ensemble des données de celui-ci, c'est à dire par 4 octets. L'utilisation de « uint » ou bien d'un « int » est appropriée.
Code C# :

int width = bitmap.Width;

int height = bitmap.Height;

unsafe

{

    BitmapData bmpData = bitmap.LockBits(new Rectangle(0, 0, width,

    height), ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb);

    uint* newPixel = (uint*)(void*)bmpData.Scan0;

    for (int y = 0; y < height; y++)

        for (int x = 0; x < width; x++)

        {

            newPixel[0] = (uint)((newPixel[0] & 0x000000ff) |

            (newPixel[0] & 0x0000ff00) << 8 | (newPixel[0] & 0x00ff0000) >> 8 |

            (newPixel[0] & 0xff000000));

 

            newPixel++;

        }

    bitmap.UnlockBits(bmpData);

}

Notons bien que lorsque nous faisions un accès par octet, la succession des composantes étaient Bleue, verte, Rouge et Alpha. En accédant aux données du pixel en pointant une seule fois, les octets se trouvent inversés dans l'« uint », ceci est dû au fait que les octets de faibles poids apparaissent en premier. Si nous avions utilisé un « int », les octets n'auraient pas été inversés. Des opérations binaires nous permettent de modifier les composantes d'un pixel. Grâce à l'opérateur bit à bit « & », nous pouvons réaliser un masque permettant d'isoler l'octet d'une composante. Le masque « 0xff000000 » appliqué à l'« uint » ne permet pas d'isoler la composante bleue, comme nous venons de le préciser plus haut, mais bien la composante Alpha. Le masque « 0x00ff0000 » est utilisé pour le rouge, « 0x0000ff00 » pour le vert, « 0x000000ff » pour le bleu. Les opérations de décalage bit à bit permettent de décaler un nombre défini de bits. Pour décaler d'un octet, on décale de 8 bits. Les opérateurs « << » et « >> » permettent de préciser le sens du décalage. Les valeurs des composantes Rouge et Verte n'étant voisin que d'un octet, on décale chaque valeur de ces deux composantes de 8 bits. L'opérateur bit à bit « | », permet de reconstituer le nouvel « uint » qui sera donc la nouvelle valeur affectée au pixel.
VB.NET :

Dim width As Integer = bitmap.Width

Dim height As Integer = bitmap.Height

 

Dim bmpData As BitmapData = bitmap.LockBits(New Rectangle(0, 0, width, height) _

, System.Drawing.Imaging.ImageLockMode.ReadWrite, PixelFormat.Format32bppArgb)

 

Dim newPixel(width * height - 1) As Integer

 

Marshal.Copy(bmpData.Scan0, newPixel, 0, newPixel.Length)

 

Dim temp As Integer

 

For y = 0 To height - 1

 

    For x = 0 To width - 1

 

        temp = newPixel(width * y + x)

 

        newPixel(width * y + x) _

        = temp And &HFF Or (temp And &HFF00) << 8 _

        Or (temp And &HFF0000) >> 8 Or (temp And &HFF000000)

 

    Next

 

Next

 

Marshal.Copy(newPixel, 0, bmpData.Scan0, newPixel.Length)

 

bitmap.UnlockBits(bmpData)

Puisque nous utilisons la méthode « Copy » et que celle-ci ne fournit qu'un tableau d'« Integer » et non pas d'« UInteger », les opérations bit à bit « And » appliquent successivement le masque « &HFF » pour la composante Bleue, « &HFF00 » pour la composante verte, « &HFF0000 » pour la composante rouge et « &HFF000000 » pour la composante alpha. Pour décaler d'un octet, on décale de 8 bits. Les opérateurs « << » et « >> » permettent de préciser le sens du décalage. On inverse ainsi les composantes rouge et verte. L'opérateur bit à bit « Or » permet de reconstituer le nouvel « Integer » affecté au pixel.
Vous vous demandez sans doute pourquoi nous présentons cette dernière méthode d'accès au pixel par 4 octets, bien plus complexe que la méthode d'accès par octet. C'est simplement que celle-ci est plus rapide en exécution.
Pour le code C#, ceci s'explique assez simplement : la première méthode nécessite de changer le « pointeur » quatre fois au lieu d'une fois dans la seconde méthode. Le déplacement du pointeur en une seule fois ainsi que les opérations binaires de la seconde méthode représentent un coût beaucoup plus faible que l'affectation (variable temporaire) et le déplacement du pointeur trois fois de plus dans la première méthode.
Pour le code VB.NET, on réalise trois affections dans la première méthode au lieu de deux dans la deuxième méthode.Faut-il pour autant en conclure qu'il vaut mieux pointer un « uint » (ou un « int ») qu'un « byte » en C#, manipuler un tableau d'« Integer » qu'un tableau de « Byte » ? La réponse n'est pas si simple qu'il y paraît. Il vous faudra profiler votre code à chaque implémentation d'un nouvel algorithme de traitement des données de l'image.
Vous avez pu vous apercevoir qu'il existe plusieurs formats en 32 Bits :
  1. Format32bppArgb : ce format permet de gérer la couche alpha. Si nous modifions la composante Alpha, la transparence de l'image en est affectée. Une valeur de 255 indique que la couleur est opaque. Une valeur de 100 par exemple, que la couleur est semi-transparente. Avec une valeur alpha à zéro, nous sommes en présence d'un pixel totalement transparent.
  2. Format32bppPArgb : il s'agit du format prémultiplié. On entend par là que les composantes Rouge, Verte et bleue sont prémultipliés par la composante Alpha. En fait, l'approximation suivante (ou précalcul) donne la valeur d'une composante dans ce mode : C' = (alpha * C)>>8. Lorsqu'on doit composer deux images semi-transparentes, on comprend l'intérêt que les valeurs des composantes puissent être précalculées. En effet, la composition d'une image A et B au format non-précalculé est donné approximativement par la formule : C = (alphaB B + (((255 – alphaB) alphaA A)>>8)) >>8 où les décalages de 8 approximent astucieusement la valeur 255. Par le précalcul des composantes pour les deux images, on arrive à la formule : C = B' + ((255 – alphaB)A')>>8 où B' et A' sont les images aux composantes précalculées. Le mode pré-multiplié économise donc du temps dans la composition d'images semi-transparentes.
  3. Format32bppRgb : ce format présente les composantes Rouge, verte et bleue. La couche Alpha n'est pas prise en compte.
 
» 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).