it-swarm.com.de

Schnelle Arbeit mit Bitmaps in C #

Ich muss auf jedes Pixel einer Bitmap zugreifen, damit arbeiten und sie dann in einer Bitmap speichern.

Mit Bitmap.GetPixel() und Bitmap.SetPixel() läuft mein Programm langsam.

Wie kann ich schnell Bitmap in byte[] und zurück konvertieren?

Ich brauche einen byte[] mit length = (4 * width * height), der RGBA-Daten jedes Pixels enthält.

38
AndreyAkinshin

Sie können es auf verschiedene Arten tun. Sie können unsafe verwenden, um direkten Zugriff auf die Daten zu erhalten, oder Sie können das Marshalling verwenden, um die Daten hin und her zu kopieren. Der unsichere Code ist schneller, aber für das Marshallen wird kein unsicherer Code benötigt. Hier ist ein Leistungsvergleich Ich habe es vor einiger Zeit getan.

Hier ist ein vollständiges Beispiel mit 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;
}

Hier ist das Gleiche, aber mit Marshalling:

/*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

Sie können die Bitmap.LockBits-Methode verwenden. Wenn Sie die parallele Taskausführung verwenden möchten, können Sie auch die Parallel-Klasse im Namespace System.Threading.Tasks verwenden. Die folgenden Links enthalten einige Beispiele und Erklärungen.

4
turgay

Sie wollen LockBits . Sie können dann die gewünschten Bytes aus dem BitmapData-Objekt extrahieren, das Sie erhalten.

3
David Seiler

Es gibt einen anderen Weg, der schneller und bequemer ist. Wenn Sie sich die Bitmap-Konstruktoren ansehen, finden Sie einen, der IntPtr als letzten Parameter übernimmt. Dieses IntPtr dient zum Halten von Pixeldaten. Wie verwendest du es?

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)

Was Sie jetzt haben, ist ein einfaches Integer-Array und eine Bitmap, die auf denselben Speicher verweist. Alle Änderungen, die Sie am Integer-Array vornehmen, wirken sich direkt auf die Bitmap aus. Versuchen wir es mit einer einfachen Helligkeitstransformation.

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

Sie stellen möglicherweise fest, dass ich einen kleinen Trick verwendet habe, um zu vermeiden, dass Gleitkomma-Berechnungen innerhalb der Schleife durchgeführt werden. Dies verbessert die Leistung erheblich ... und wenn Sie fertig sind, müssen Sie natürlich ein wenig aufräumen ...

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

Ich habe die Alphakomponente hier ignoriert, aber Sie können diese auch verwenden. Ich habe auf diese Weise viele Bitmap-Bearbeitungswerkzeuge zusammengeworfen. Es ist viel schneller und zuverlässiger als Bitmap.LockBits () und das Beste ist, dass zum Kopieren der Bitmap kein Speicherplatz benötigt wird.

2
Daniklad

Aufbauend auf der Antwort von @notJim (und mit Hilfe von http://www.bobpowell.net/lockingbits.htm ) entwickelte ich Folgendes, das mein Leben viel einfacher macht, da ich mit einem Array von Arrays ende Dadurch kann ich durch die x- und y-Koordinaten zu einem Pixel springen. Natürlich muss die x-Koordinate um die Anzahl der Bytes pro Pixel korrigiert werden. Dies ist jedoch eine einfache Erweiterung.

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

Versuchen Sie diese C # -Lösung.

Erstellen Sie eine Winforms-App zum Testen.

Fügen Sie eine Schaltfläche und eine PictureBox sowie ein Klickereignis und ein Formular zum Schließen hinzu.

Verwenden Sie den folgenden Code für Ihr Formular:

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;

    }

}

Wenn Sie Änderungen am Array vornehmen, wird die Bitmap automatisch aktualisiert.

Sie müssen die Aktualisierungsmethode in der picturebox aufrufen, um diese Änderungen zu sehen.

0