| Zurück zur ursprünglichen Idee. Wir wollten ein Bytearray vorbereiten, dessen Deskriptor auf den in der BITMAP-Struktur referenzierten Speicherbereich verweist. Dafür muß in erster Linie das Feld richtig dimensioniert werden. Da nicht nur die Arraygrenze sondern auch der Zeiger .pvData geändert werden muß, kann die BASIC-Funktion ReDim nicht zum Einsatz kommen. Der Arraydeskriptor ist selbst zu konstruieren. Weil ein zweidimensionales Feld notwendig ist, sollte jetzt die Struktur SAFEARRAY2D verwendet werden: Private Type SAFEARRAY2D
cDims As Integer
fFeatures As Integer
cbElements As Long
cLocks As Long
pvData As Long
Bounds(0 To 1) As SAFEARRAYBOUND
End Type Listing 13 Durch Call GetObject(Picture1.Picture, Len(Bmp), Bmp) Listing 14 ist die BITMAP-Struktur zu erhalten. Nun wird der Deskriptor entsprechend zusammengesetzt: With SafeArray
.cDims = 2
.cbElements = 1
.fFeatures = 0
.cLocks = 0
.pvData = Bmp.bmBits
.Bounds(0).lLbound = 0
.Bounds(0).cElements = Bmp.bmHeight
.Bounds(1).lLbound = 0
.Bounds(1).cElements = Bmp.bmWidthBytes
End With Listing 15 .cDims erhält den Wert 2, da es sich ja um ein zweidimensionales Feld handeln soll. Der Datentyp der Feldelemente ist Byte. Dieser benötigt pro Element nur je eine Speicherzelle, daher ist .cbElements auf 1 zu setzten. Der Datentyp Byte ist ein numerischer, woraus folgt, daß .fFeatures logischerweise den Wert 0 erhält. Sperrungen gibt es bisher keine, weshalb .cLocks auch gleich 0 ist. Jetzt kommt der eigentliche Trick: .pvData = Bmp.bmBits .pvData würde normalerweise auf den von Visual Basic zugewiesenen Speicherblock des Bytearrays verweisen. Bmp.bmBits referenziert hingegen ebenfalls ein Bytearray, nämlich das der Bitmap. Indem .pvData jetzt Bmp.bmBits zugewiesen wird, ist erreicht, daß unser Array gleich der in der PictureBox befindlichen Bitmap ist. Also kurzum: Wenn der neue Deskriptor zugewiesen wird, befindet sich die gesamte Bitmap auf einen Schlag in einem Visual Basic Bytearray! Doch weiter. Was jetzt noch fehlt sind die Arraygrenzen. Da ja ein zweidimensionales Feld konstruiert werden soll, sind auch zwei Bounds zu berücksichtigen. Damit die Sache zu Beginn so einfach wie möglich gehalten wird, erstellen wir ein nullbasierters Array. Deshalb sind .Bounds(1).lLbound und .Bounds(1).lLbound auf Null zu setzen. Das Array muß die Bedingung BMP-Höhe_in_Pixel * (BMP-Breite_in_Pixel * 3) erfüllen. Die Breite ist mit 3 zu multiplizieren, da eine 24-Bit Bitmap vorliegt und dort jedes Pixel 3 Byte benötigt. Die Höhe ist leicht ermittelt, liegt sie doch im Member Bmp.bmHeight der BITMAP-Struktur vor. .BmWidth ist zwar ebenfalls vorhanden, doch können die Operationen der Umrechungen erspart bleiben, wenn wir uns die Beschreibung des Members Bmp.bmWidthBytes nochmals anschauen: bmWidthBytes Spezifiziert die Anzahl der Bytes pro ScanLine. Dieser Wert muß durch zwei teilbar sein, damit Windows Werte aus einem Array mit word-Ausrichtung annehmen kann. Die Scanline ist in unserem Falle eine Zeile der Bitmap, also genau das was benötigt wird, daher können wir dieses Member zuweisen. Es ergibt sich für die Bounds: .Bounds(0).lLbound = 0
.Bounds(0).cElements = Bmp.bmHeight
.Bounds(1).lLbound = 0
.Bounds(1).cElements = Bmp.bmWidthBytes Listing 16 Jetzt gilt es nur noch den fertigen, neuen Deskriptor wie folgt anzubringen: Call CopyMemory(ByVal VarPtrArray(MyArray), VarPtr(SafeArray), 4&) Listing 17 Der Zeiger auf unsere gerade erst definierte Struktur, wird auf den ersten Zeiger des Doppelpointer kopiert. Das war alles. Durch das Umkopieren eines 4 Byte langen Pointers kann in Mikrosekunden eine Bitmap beliebiger Größe in ein Bytearray kopiert werden. Schneller geht es nicht. Aber es kommt noch besser. Wir haben jetzt die Bitmap in ein Bytearray eingebettet und daher wirkt sich jede Änderungen eines Wertes im Feld direkt auf die Bitmap aus. Das heißt, ändern wir drei nebeneinander liegende Bytes mit je dem Wert &HFF auf den Wert &H00, wechselt auch das angesprochene Pixel seine Farbe unmittelbar von ehemals weiß in schwarz. Da wir mit der beschriebenen Technik an der .Image- und nicht der .Picture-Property der PictureBox werkeln, ist die Visualisierung der angewendeten Manipulationen mit Picture1.Refresh in den Vordergrund zu bringen. Einmal als Array vorliegend, lassen sich mit dieser Methode in erstaunlichen Geschwindigkeiten Bilder in fast [abhängig von der Bildgröße] Echtzeit beliebig manipulieren. Es sei noch der Hinweis gegeben, daß nach Beendigung der grafischen Operationen mit der Zeile Call CopyMemory(ByVal VarPtrArray(MyArray), 0&, 4&) Listing 18 der Deskriptor des generierten Bytearray auf Null gesetzt werden sollte. Unter Win9x ist dies zwar überflüssig, bei NT-Systemen hingegen kann die nicht Verwendung dieser Zeile zu unangenehmen Nebenwirkungen führen. Um die verschiedensten Effekte, wie Emboss, Ripple, Schärfen, Rotieren, sowie Ändern von Kontrast, Helligkeit und RGB-Verhältnissen, zu erzielen, schauen Sie bitte in die diesem Artikel beiliegende Beispiel-Sammlung. Die verwendete Methodik ist bei allen die gleiche, nämlich die hier besprochene. Es variieren lediglich die grafischen Algorithmen zwecks Erzielung der verschiedenen Effekte. Nachteil dieser durch Matthew Curland und Francesco Balena erstmalig 1998 vorgestellten Zeigermethode, ist, daß nur Grafiken manipuliert werden können, die auch ein Bitmaphandle besitzen und das ist leider nicht immer gegeben. Ausweichmöglichkeit bietet das Arrangieren einer Grafik in einer DIB [Device Independent Bitmap]. Als Ergebnis liegt die Bitmap zwar wieder als Bytearray vor, hat aber nicht den harten und daher schnellen Kontakt zur Grafik. Für statische Bilder mag das keine Rolle spielen, bei raschem dynamischen Wechsel, sprich einer Animation, kann es aber schnell zu einer unflüssigen Darstellung kommen. 8 Beispiele plus dem Artikel als Download |