Mein LockBits-Tutorial fange ich mal mit einer Gegenüberstellung an: Verschiedene Verfahren werden verwendet um eine Anzahl von Pixeln zu färben.
GDI+ (.fillRectangle):....Sehr langsam (Hinweis: GDI ist schnell, hier nur absolut ungeeignet!)
Bitmap.setPixel:..........Sehr langsam
LockBits:.................Verdammt schnell
Damit ist auch gleich die erste Frage beantwortet:
Wer sollte Lockbits benutzen? Derjenige, der viele Pixel einzeln setzen oder abfragen möchte.
Grundlagen
Schaffen wir nun die Grundlage um LockBits zu verstehen:
Farben: RGB, ARGB und Color
Was eine Farbe ist weiß jeder - auch das .NET-Framework. Es stellt u.A. die
Jede dieser 4 Zahlen ist ein Byte und kann Werte von 0 bis 255 annehmen.
PixelFormat: 32bpp-Bitmap und 24bpp-Bitmap
Die Abkürzung bpp steht für BitsPerPixel. Wir erinnern uns daran, dass 1Byte=8Bits entspricht - 32bbp bedeutet also, dass 4 Byte auf einen Pixel kommen, bei 24bpp nur 3 Bytes. Im Endeffekt muss man nur wissen, dass bei 24bbp der Alpha-Kanal 'wegfällt' (=> Farben haben keine Tranzparenz, A=255) und nur die RGB-Werte relevant sind. Bei 32bpp kann man sich zusätzlich des Alpha-Kanals erfreuen.
Hinweis: Es gibt auch weitere PixelFormate - für alle funktioniert LockBits ein wenig anders
BitShifting
Sehr nützlich können die BitShift-Operatoren werden. Eine Color kann durch 4 Bytes repräsentiert werden - ein Integer besteht auch aus 4 Bytes. Man kann jede ARGB-Farbe durch einen Integer darstellen (die einzelnen Farbkanäle zu einem Integer zusammen-shiften) oder aus einem Integer die einzelnen Kanäle herausfiltern. Es kann also durchaus sinnvoll werden einzelne Bits zu schubsen - das ist schnell und man spart sich den Weg über die langsamere Color-Struktur.
Das ist aber alles Optimieren auf höchstem Niveau. Wer sich das noch nicht antun möchte kann auch die herkömmlichen Color-Methoden benutzen:
Was macht das LockBits-Verfahren?
Anschaulich gesagt: Das LockBits-Verfahren nimmt die Ziel-Bitmap und 'zerschneidet' sie in Streifen. Anschließend reiht es die Streifen aneinander. Aus einem ehemals zweidimensionalen Gebilde ist ein einfaches eindimensionales Byte-Array entstanden. Einzelne Bytes in einem Array zu lesen/setzen ist einfach und schnell. Der Clou: Das Byte-Array kann ganz einfach wieder zur Bitmap zusammengesetzt werden. Dieser Vorgang nennt sich UnlockBits.
Praktisch gesehen passiert das meiste 'von alleine', denn die Bitmap samt der ganzen Bytes befinden sich schon im Hauptspeicher. Man muss sie nur rausfischen und später wieder zurückschieben.
1. LockBits
Durch LockBits wird die Bitmap gesperrt, sodass man an das zugrunde liegende Byte-Array heran kommt. Wichtig: Während die Bitmap gesperrt ist kann man sie nur eingeschränkt verwenden!
Zu verstehen gibt es hier nicht viel. Eine Bitmap geht rein, das Byte-Array rgbValues kommt raus. Viel wichtiger ist:
2. Manipulieren: Wie ist das Byte-Array aufgebaut und wie findet man die richtigen Bytes?
3. UnlockBits
Nun soll das Byte-Array wieder zu der Bitmap zurück geschoben werden.
Das war es auch schon, eigentlich garkeine Zauberei^^
Im Showroom gibt es btw fertige Libs die .GetPixel und .SetPixel auf LockBits-Grundlage implementiert haben.
lg
GDI+ (.fillRectangle):....Sehr langsam (Hinweis: GDI ist schnell, hier nur absolut ungeeignet!)
Bitmap.setPixel:..........Sehr langsam
LockBits:.................Verdammt schnell
Damit ist auch gleich die erste Frage beantwortet:
Wer sollte Lockbits benutzen? Derjenige, der viele Pixel einzeln setzen oder abfragen möchte.
Grundlagen
Schaffen wir nun die Grundlage um LockBits zu verstehen:
Farben: RGB, ARGB und Color
Was eine Farbe ist weiß jeder - auch das .NET-Framework. Es stellt u.A. die
Color
-Struktur bereit um Farben handhabbar zu machen. Dabei wird auf den RGB-Farbraum zurückgegriffen. Eine Farbe wird durch vier Zahlen dargestellt: die drei Grundfarben (Rot, Grün und Blau), sowie den Alpha-Kanal welcher die Transparenz regelt. Jede dieser 4 Zahlen ist ein Byte und kann Werte von 0 bis 255 annehmen.
PixelFormat: 32bpp-Bitmap und 24bpp-Bitmap
Die Abkürzung bpp steht für BitsPerPixel. Wir erinnern uns daran, dass 1Byte=8Bits entspricht - 32bbp bedeutet also, dass 4 Byte auf einen Pixel kommen, bei 24bpp nur 3 Bytes. Im Endeffekt muss man nur wissen, dass bei 24bbp der Alpha-Kanal 'wegfällt' (=> Farben haben keine Tranzparenz, A=255) und nur die RGB-Werte relevant sind. Bei 32bpp kann man sich zusätzlich des Alpha-Kanals erfreuen.
Hinweis: Es gibt auch weitere PixelFormate - für alle funktioniert LockBits ein wenig anders
BitShifting
Sehr nützlich können die BitShift-Operatoren werden. Eine Color kann durch 4 Bytes repräsentiert werden - ein Integer besteht auch aus 4 Bytes. Man kann jede ARGB-Farbe durch einen Integer darstellen (die einzelnen Farbkanäle zu einem Integer zusammen-shiften) oder aus einem Integer die einzelnen Kanäle herausfiltern. Es kann also durchaus sinnvoll werden einzelne Bits zu schubsen - das ist schnell und man spart sich den Weg über die langsamere Color-Struktur.
Das ist aber alles Optimieren auf höchstem Niveau. Wer sich das noch nicht antun möchte kann auch die herkömmlichen Color-Methoden benutzen:
Was macht das LockBits-Verfahren?
Anschaulich gesagt: Das LockBits-Verfahren nimmt die Ziel-Bitmap und 'zerschneidet' sie in Streifen. Anschließend reiht es die Streifen aneinander. Aus einem ehemals zweidimensionalen Gebilde ist ein einfaches eindimensionales Byte-Array entstanden. Einzelne Bytes in einem Array zu lesen/setzen ist einfach und schnell. Der Clou: Das Byte-Array kann ganz einfach wieder zur Bitmap zusammengesetzt werden. Dieser Vorgang nennt sich UnlockBits.
Praktisch gesehen passiert das meiste 'von alleine', denn die Bitmap samt der ganzen Bytes befinden sich schon im Hauptspeicher. Man muss sie nur rausfischen und später wieder zurückschieben.
1. LockBits
Durch LockBits wird die Bitmap gesperrt, sodass man an das zugrunde liegende Byte-Array heran kommt. Wichtig: Während die Bitmap gesperrt ist kann man sie nur eingeschränkt verwenden!
VB.NET-Quellcode
- Dim input As Bitmap = eineBitmap '/new Bitmap(800,600) / etc
- Dim rect As New Rectangle(0, 0, input.Width, input.Height)
- Dim bmpData As System.Drawing.Imaging.BitmapData = input.LockBits(rect, Drawing.Imaging.ImageLockMode.WriteOnly, input.PixelFormat)
- Dim ptr As IntPtr = bmpData.Scan0
- Dim bytes As Integer = Math.Abs(bmpData.Stride) * input.Height
- Dim rgbValues(bytes - 1) As Byte
- System.Runtime.InteropServices.Marshal.Copy(ptr, rgbValues, 0, bytes)
Zu verstehen gibt es hier nicht viel. Eine Bitmap geht rein, das Byte-Array rgbValues kommt raus. Viel wichtiger ist:
2. Manipulieren: Wie ist das Byte-Array aufgebaut und wie findet man die richtigen Bytes?
- Positionen gezielt anspringen
Sehr häufig will man gezielt Pixel setzten oder auszulesen. Das Ziel ist also für gegebene x,y-Koordinaten die passenden Indizes zu finden. Schaut man sich an wie die Bitmap 'zerschnitten' wird (Abbildung oben) kommt man schnell auf eine einfache Formel um das Start-Bytes des zugehörigen Pixels ausfindig zu machen:
x und y sind dabei die Koordinaten des Pixels, bytesPerPixel gibt an wie viele Bytes auf einen Pixel kommen (32bpp = 4 Bytes => ARGB) bzw (24bpp = 3 Bytes => RGB) und width ist die Breite der Bitmap.
Eine Eigenheit: im Byte-Array sind ARGB bzw RGB genau umgekehrt hinterlegt.
Hier mal anhand des Arrays einer 32bpp-Bitmap: B,G,R,A,B,G,R,A,B,G,R,A,B,G,R,A,B,G,R,A,...
Ob man nun auslesen oder setzten möchte ist egal - einfach das Byte-Array manipulieren.
- Array mit XY-Schleife durchlaufen
Muss man jede Position genau einmal anspringen (z.B. um die Durchschnittsfarbe auszurechnen oder eine 2D-Kollisionsmap eines Spiels grafisch darzustellen) ist dieses Vorgehen vorteilhaft, da man nicht ständig den Index im Array berechnen muss und die Koordinaten quasi geschenkt bekommt.
Wenn es noch schneller gehen muss kann man sogar parallel arbeitende Schleifen verwenden!
Auch hier gilt: einfach das Byte-Array manipulieren, egal ob man Bytes setzt oder liest.
3. UnlockBits
Nun soll das Byte-Array wieder zu der Bitmap zurück geschoben werden.
Das war es auch schon, eigentlich garkeine Zauberei^^
Im Showroom gibt es btw fertige Libs die .GetPixel und .SetPixel auf LockBits-Grundlage implementiert haben.
lg