web-dev-qa-db-fra.com

Travail rapide avec les bitmaps en C #

Je dois accéder à chaque pixel d'un bitmap, travailler avec eux, puis les enregistrer dans un bitmap.

En utilisant Bitmap.GetPixel() et Bitmap.SetPixel(), mon programme s'exécute lentement.

Comment convertir rapidement Bitmap en byte[] et vice-versa?

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

38
AndreyAkinshin

Vous pouvez le faire de différentes manières. Vous pouvez utiliser unsafe pour obtenir un accès direct aux données ou 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 une comparaison performance que j'ai faite il y a longtemps.

Voici un exemple complet utilisant lockbits:

/*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;
}
71
davidtbernal

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

4
turgay

Vous voulez LockBits . Vous pouvez ensuite extraire les octets souhaités de l’objet BitmapData qu’il vous donne.

3
David Seiler

Il existe un autre moyen plus rapide et beaucoup plus pratique. Si vous regardez les constructeurs Bitmap, vous en trouverez un qui prend et IntPtr comme dernier paramètre. Cet IntPtr est destiné à contenir des données de pixels. Alors, comment l'utilisez-vous?

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 est un simple tableau Integer et un bitmap référençant la même mémoire. Toute modification apportée au tableau Integer aura une incidence directe sur le bitmap. Essayons cela avec une simple transformation de 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é un petit truc pour éviter de faire des calculs en virgule flottante dans la boucle. Cela améliore un peu les performances… .. Et lorsque vous avez terminé, vous devez nettoyer un peu, bien sûr….

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

J'ai ignoré le composant alpha ici, mais vous êtes libre de l'utiliser également. J'ai rassemblé de nombreux outils d'édition de bitmap de cette façon. Il est beaucoup plus rapide et fiable que Bitmap.LockBits () et, mieux encore, il ne nécessite aucune copie de mémoire pour commencer à modifier votre bitmap.

2
Daniklad

En me basant sur la réponse @notJim (et avec l’aide de http://www.bobpowell.net/lockingbits.htm ), j’ai développé les éléments suivants qui me facilitent grandement la vie car j’ai un tableau de tableaux cela me permet de passer à un pixel avec ses coordonnées x et y. Bien sûr, la coordonnée x doit être corrigée du nombre d'octets par pixel, mais c'est 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
2
cjbarth

Essayez cette solution C #.

Créez une application winforms pour les tests.

Ajoutez un bouton et un PictureBox, ainsi qu'un événement click et un événement de clôture de formulaire.

Utilisez le code suivant pour votre formulaire:

public partial class Form1 : Form
{
    uint[] _Pixels { get; set; }

    Bitmap _Bitmap { get; set; }

    GCHandle _Handle { get; set; }

    IntPtr _Addr { get; set; }


    public Form1()
    {
        InitializeComponent();

        int imageWidth = 100; //1920;

        int imageHeight = 100; // 1080;

        PixelFormat fmt = PixelFormat.Format32bppRgb;

        int pixelFormatSize = Image.GetPixelFormatSize(fmt);

        int stride = imageWidth * pixelFormatSize;

        int padding = 32 - (stride % 32);

        if (padding < 32)
        {
            stride += padding;
        }

        _Pixels = new uint[(stride / 32) * imageHeight + 1];

         _Handle = GCHandle.Alloc(_Pixels, GCHandleType.Pinned);

        _Addr = Marshal.UnsafeAddrOfPinnedArrayElement(_Pixels, 0);

        _Bitmap = new Bitmap(imageWidth, imageHeight, stride / 8, fmt, _Addr);

        pictureBox1.Image = _Bitmap;

    }

    private void button1_Click(object sender, EventArgs e)
    {
        for (int i = 0; i < _Pixels.Length; i++)
        {
            _Pixels[i] = ((uint)(255 | (255 << 8) | (255 << 16) | 0xff000000));

        }

    }

    private void Form1_FormClosing(object sender, FormClosingEventArgs e)
    {
        _Addr = IntPtr.Zero;

        if (_Handle.IsAllocated)
        {
            _Handle.Free();

        }

        _Bitmap.Dispose();

        _Bitmap = null;

        _Pixels = null;

    }

}

Désormais, toute modification apportée au tableau mettra automatiquement à jour le bitmap.

Vous devrez appeler la méthode d'actualisation sur la boîte à images pour voir ces modifications.

0