43 votes

Travailler rapidement avec des bitmaps en C#

J'ai besoin d'accéder à chaque pixel d'une image bitmap, de travailler avec eux, puis de les enregistrer dans une image bitmap.

Utilisation Bitmap.GetPixel() y Bitmap.SetPixel() mon programme fonctionne lentement.

Comment puis-je convertir rapidement Bitmap à byte[] et retour ?

J'ai besoin d'un byte[] avec length = (4 * width * height) contenant les données RGBA de chaque pixel.

1 votes

Le lien suivant présente une comparaison des méthodes d'accès aux pixels des images bitmap. csharpexamples.com/fast-image-processing-c

89voto

notJim Points 5142

Vous pouvez procéder de différentes manières. Vous pouvez utiliser unsafe pour accéder directement aux données, ou vous pouvez utiliser le marshaling pour copier les données dans les deux sens. Le code non sécurisé est plus rapide, mais le marshaling ne nécessite pas de code non sécurisé. Voici un exemple de comparaison des performances Je l'ai fait il y a quelque temps.

Voici un exemple complet d'utilisation d'antivols :

/*Note unsafe keyword*/
public unsafe Image ThresholdUA(float thresh)
{
    Bitmap b = new Bitmap(_image);//note this has several overloads, including a path to an image

    BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);

    byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);

    /*This time we convert the IntPtr to a ptr*/
    byte* scan0 = (byte*)bData.Scan0.ToPointer();

    for (int i = 0; i < bData.Height; ++i)
    {
        for (int j = 0; j < bData.Width; ++j)
        {
            byte* data = scan0 + i * bData.Stride + j * bitsPerPixel / 8;

            //data is a pointer to the first byte of the 3-byte color data
            //data[0] = blueComponent;
            //data[1] = greenComponent;
            //data[2] = redComponent;
        }
    }

    b.UnlockBits(bData);

    return b;
}

Voici la même chose, mais avec le marshaling :

/*No unsafe keyword!*/
public Image ThresholdMA(float thresh)
{
    Bitmap b = new Bitmap(_image);

    BitmapData bData = b.LockBits(new Rectangle(0, 0, _image.Width, _image.Height), ImageLockMode.ReadWrite, b.PixelFormat);

    /* GetBitsPerPixel just does a switch on the PixelFormat and returns the number */
    byte bitsPerPixel = GetBitsPerPixel(bData.PixelFormat);

    /*the size of the image in bytes */
    int size = bData.Stride * bData.Height;

    /*Allocate buffer for image*/
    byte[] data = new byte[size];

    /*This overload copies data of /size/ into /data/ from location specified (/Scan0/)*/
    System.Runtime.InteropServices.Marshal.Copy(bData.Scan0, data, 0, size);

    for (int i = 0; i < size; i += bitsPerPixel / 8 )
    {
        double magnitude = 1/3d*(data[i] +data[i + 1] +data[i + 2]);

        //data[i] is the first of 3 bytes of color

    }

    /* This override copies the data back into the location specified */
    System.Runtime.InteropServices.Marshal.Copy(data, 0, bData.Scan0, data.Length);

    b.UnlockBits(bData);

    return b;
}

2 votes

Merci, et je n'ai même pas eu besoin de harceler qui que ce soit :)

12 votes

Vous devez savoir que l'ordre des canaux de couleur peut être différent selon le format de pixel que vous utilisez. Par exemple, avec les bitmaps 24 bits, le premier octet du pixel est le canal bleu, suivi du vert, puis du rouge, contrairement à l'ordre rouge-vert-bleu généralement attendu.

0 votes

Merci pour vos efforts !

6voto

turgay Points 45

Vous pouvez utiliser la méthode Bitmap.LockBits. De même, si vous souhaitez utiliser l'exécution de tâches en parallèle, vous pouvez utiliser la classe Parallel dans l'espace de noms System.Threading.Tasks. Les liens suivants contiennent des exemples et des explications.

4voto

David Seiler Points 6212

Vous voulez LockBits . Vous pouvez alors extraire les octets que vous voulez de l'objet BitmapData qu'il vous donne.

0 votes

Je pense qu'il est plus rapide d'utiliser la fonction BitmapData renvoyé par LockBits à l'intérieur d'un unsafe avec un cast de pointeur (NOTE : je n'ai aucune idée si c'est plus rapide ou non, mais j'essaie de pousser quelqu'un d'autre à faire une analyse comparative).

1 votes

Cela vous éviterait les copies dans et hors du BitmapData, ce qui est appréciable. Je ne suis pas sûr que cela permette de réaliser des économies dans la pratique ; memcpy() est très rapide de nos jours.

1voto

cjbarth Points 838

En s'appuyant sur la réponse de @notJim (et avec l'aide de https://web.archive.org/web/20141229164101/http://bobpowell.net/lockingbits.aspx ), j'ai mis au point ce qui suit, qui me facilite grandement la vie en ce sens que je me retrouve avec un tableau de tableaux qui me permet de sauter à un pixel par son x y y coordonnées. Bien entendu, les x doit être corrigée par le nombre d'octets par pixel, mais il s'agit d'une extension facile.

Dim bitmapData As Imaging.BitmapData = myBitmap.LockBits(New Rectangle(0, 0, myBitmap.Width, myBitmap.Height), Imaging.ImageLockMode.ReadOnly, myBitmap.PixelFormat)

Dim size As Integer = Math.Abs(bitmapData.Stride) * bitmapData.Height
Dim data(size - 1) As Byte

Marshal.Copy(bitmapData.Scan0, data, 0, size)

Dim pixelArray(myBitmap.Height)() As Byte

'we have to load all the opacity pixels into an array for later scanning by column
'the data comes in rows
For y = myBitmap.Height - 1 To 0 Step -1
    Dim rowArray(bitmapData.Stride) As Byte
    Array.Copy(data, y * bitmapData.Stride, rowArray, 0, bitmapData.Stride)
    'For x = myBitmap.Width - 1 To 0 Step -1
    '   Dim i = (y * bitmapData.Stride) + (x * 4)
    '   Dim B = data(i)
    '   Dim G = data(i + 1)
    '   Dim R = data(i + 2)
    '   Dim A = data(i + 3)
    'Next
    pixelArray(y) = rowArray
Next

1voto

Daniel Points 16

Il existe un autre moyen, beaucoup plus rapide et pratique. Si vous regardez les constructeurs de Bitmap, vous en trouverez un qui prend un IntPtr comme dernier paramètre. Cette IntPtr sert à contenir les données des pixels. Comment l'utiliser ?

Dim imageWidth As Integer = 1920
Dim imageHeight As Integer = 1080

Dim fmt As PixelFormat = PixelFormat.Format32bppRgb
Dim pixelFormatSize As Integer = Image.GetPixelFormatSize(fmt)

Dim stride As Integer = imageWidth * pixelFormatSize
Dim padding = 32 - (stride Mod 32)
If padding < 32 Then stride += padding

Dim pixels((stride \ 32) * imageHeight) As Integer
Dim handle As GCHandle = GCHandle.Alloc(pixels, GCHandleType.Pinned)
Dim addr As IntPtr = Marshal.UnsafeAddrOfPinnedArrayElement(pixels, 0)

Dim bitmap As New Bitmap(imageWidth, imageHeight, stride \ 8, fmt, addr)

Ce que vous avez maintenant, c'est un simple tableau d'entiers et une image bitmap référençant la même mémoire. Toute modification apportée au tableau d'entiers affectera directement le bitmap. Essayons ceci avec une simple transformation de la luminosité.

Public Sub Brightness(ByRef pixels() As Integer, ByVal scale As Single)
    Dim r, g, b As Integer
    Dim mult As Integer = CInt(1024.0f * scale)
    Dim pixel As Integer

    For i As Integer = 0 To pixels.Length - 1
        pixel = pixels(i)
        r = pixel And 255
        g = (pixel >> 8) And 255
        b = (pixel >> 16) And 255

        'brightness calculation
        'shift right by 10 <=> divide by 1024
        r = (r * mult) >> 10
        g = (g * mult) >> 10
        b = (b * mult) >> 10

        'clamp to between 0 and 255
        If r < 0 Then r = 0
        If g < 0 Then g = 0
        If b < 0 Then b = 0
        r = (r And 255)
        g = (g And 255)
        b = (b And 255)

        pixels(i) = r Or (g << 8) Or (b << 16) Or &HFF000000
    Next
End Sub

Vous remarquerez peut-être que j'ai utilisé une petite astuce pour éviter de faire des calculs en virgule flottante dans la boucle. Cela améliore considérablement les performances. Et lorsque vous avez terminé, vous devez bien sûr faire un peu de ménage...

addr = IntPtr.Zero
If handle.IsAllocated Then
    handle.Free()
    handle = Nothing
End If
bitmap.Dispose()
bitmap = Nothing
pixels = Nothing

J'ai ignoré la composante alpha ici, mais vous êtes libre de l'utiliser également. J'ai créé de nombreux outils d'édition d'images bitmap de cette manière. C'est beaucoup plus rapide et plus fiable que Bitmap.LockBits() et surtout, cela ne nécessite aucune copie de mémoire pour commencer à éditer votre bitmap.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X