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 12649 fois, 9 pages
 5 | La gestion des images au format 24 bits.
Les images en 24 bits sont très utilisées aussi bien dans nos applications que sur le web. Ceci vient du fait que nous n'avons pas toujours besoin de la transparence et que les différentes applications ne gèrent pas la couche Alpha. Mais ceci est en train de changer. En attendant, il faut savoir manipuler ces images dont chaque pixel est constitué de trois composantes : le bleu, le vert, le rouge. La couche alpha n'est évidemment pas réprésentée.
Etudions le stockage de l'image. Celle-ci possède des rangées d'« Uint » ou d'« int » (C#) ou d'« Integer » (VB.NET). Puisque chaque rangée de l'image possède un nombre multiple de 4 octets et qu'un pixel est représenté par trois octets (le bleu, le vert et le rouge), on se retrouve dans des situations où des octets ne représentent aucune information de l'image en fin de rangée.Le nombre de « byte » non utilisé dans la rangée définit l'offset, c'est à dire le décalage qu'il sera nécessaire de faire pour passer à la rangée suivante lors du parcours des pixels de l'image. Vous ne serez pas surpris de savoir que ces octets inutiles de fin de rangée peuvent servir à stocker de l'information, par exemple un message crypté.
Calculons cet offset. A partir de la largeur de l'image « width », nous obtenons : width modulo 4. On peut utiliser pour calculer l'offset la formule suivante : Stride – (width*3), « Stride » correspondant à la propriété de la classe BitmapData. La première méthode étant constitué de moins d'instructions, celle-ci est la plus rapide.
Comme exemple, essayons de produire un négatif d'une photo. La valeur de chaque composante sera modifiée de la façon suivante : 255 – composante.
Code VB.NET :

Dim x As Integer

Dim y As Integer

 

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.Format24bppRgb)

 

Dim offset = width Mod 4

 

Dim stride = width * 3 + offset

 

Dim newPixel(stride * height - 1) As Byte

 

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

 

For y = 0 To height - 1

 

    For x = 0 To width * 3 - 1

 

        newPixel(stride * y + x) = 255 - newPixel(stride * y + x)

 

    Next

 

Next

 

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

 

bitmap.UnlockBits(bmpData)

Nous réalisons un « Marshal.Copy » permettant d'obtenir un tableau d'octets avec lequel nous pouvons manipuler chaque composante. Bien que simple, nous pouvons améliorer la performance de notre code. Sachant que les opérations de multiplication sont très coûteuses, cherchez à optimiser ce code. Voici une réponse possible :

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.Format24bppRgb)

 

Dim offset = width Mod 4

Dim stride = width * 3 + offset

 

Dim newPixel(stride * height - 1) As Byte

 

Dim num As Integer

 

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

 

For y = 0 To height - 1

 

    For x = 0 To width * 3 - 1

 

        num = stride * y + x

 

        newPixel(num) = 255 - newPixel(num)

 

    Next

 

Next

 

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

 

bitmap.UnlockBits(bmpData)

Grâce à la variable « num », nous réalisons l'opération de multiplication une seule fois au lieu de deux. Cette simplification nous fait gagner 40% de temps ! Je vous suggère de gagner encore du temps en remplaçant la soustraction « 255 – Composante » par une affectation. Comment pouvez-vous réaliser ceci ? En créant au préalable un tableau d'octets où chaque entrée du tableau contient le résultat de la soustraction. Votre tableau va posséder 256 entrées, vous aurez donc réalisé 256 soustractions au lieu des « largeur * hauteur * 3 » opérations de soustractions dans le code ci-dessus. Le coût d'affectation d'une valeur au lieu d'une soustraction étant bien moindre, vous réaliserez encore un gain important dans votre traitement. Il ne faut donc pas négliger les opérations que l'on peut effectuer dans la boucle parcourant chaque composante de pixel. Un « profiler » doit vous permettre de savoir où vous perdez le plus de temps dans votre traitement.
Voyons comment procéder en C# :

int width = bitmap.Width;

int height = bitmap.Height;

int offset = width % 4;

 

//Table de pre-calcul

byte[] tab = new byte[256];

for (int i = 0; i <= 255; i++)

{

    tab[i] = (byte)(255 - i);

}

 

unsafe

{

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

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

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

 

    width *= 3;

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

    {

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

        {

            newPixel[0] = tab[newPixel[0]];

            newPixel++;

        }

        newPixel += offset;

    }

    bitmap.UnlockBits(bmpData);

}

La création d'une table de pré-calcul pour l'opération « 255 – Composante » nous permet de gagner beaucoup de temps dans la boucle de traitement des composantes des pixels. Nous parcourons pour toutes les rangées chaque composante via un pointeur. A chaque fin de rangée, puisque nous atteignons les octets inutiles, notre pointeur est déplacé de « offset » octets pour pouvoir atteindre la rangée suivante.
 
» Démarrer une discussion
 
Discussion démarée par olibara le 18/08/2008 à 00:12, 2 commentaire(s).