|
Einzelne Pixel setzen
|
|
|
Autor/Einsender: Datum: |
|
Stephan Kirchmaier 06/2001 |
|
|
|
Es gibt mehrere Möglichkeiten, um einzelne Pixel zu setzen. Jedoch sind die meisten dieser Methoden
ziemlich langsam. Ich werde in diesem Tutorial insgesamt vier Möglichkeiten vorstellen. Außerdem
gibt es ein Beispielprogramm, das die einzelnen Methoden vergleicht. Ich werde mich hier auf
24Bit-Bilder beschränken.
|
Zuerst sollte ein Bild in eine PictureBox geladen werden und die ScaleMode-Eigenschaft der PictureBox
muss 3 (vbPixels) sein.
|
Set Picture1.Picture = LoadPicture(<Pfad zur Datei>)
|
|
|
Bei Fragen zu diesem Tutorial nutzen Sie bitte unser VB-/VBA-Forum. |
|
|
Point liest einen Wert von einer angegebenen Position aus. PSet
setzt einen Pixel in der angegebenen Farbe auf der angegebenen
Position. Nun können wir das ganze Bild verändern.
|
|
|
For i = 0 To Picture1.ScaleWidth
For j = 0 To Picture1.ScaleHeight
Col = Picture1.Point(i, j)
Col = Abs(Col) \ 2
Picture1.PSet (i, j), Col
Next j
Next i
|
|
|
Das Programm geht hier von der linken oberen Ecke Spaltenweise runter, liest jeden Pixel, halbiert die
Farbe und setzt ihn wieder mit der neuen Farbe. Abs(Col) wird verwendet weil die Farbe auch
-1 sein kann. Das bedeutet, dass die Farbe des Pixels nicht verfügbar ist.
|
|
|
Das sind zwei API-Funktionen. Beide machen im Prinzip nichts anderes als PSet und Point aber sie
sind um einiges schneller. Hier die Deklarationen.
|
|
|
Declare Function GetPixel Lib "gdi32" Alias "GetPixel" ( _
ByVal hdc As Long, ByVal x As Long, ByVal y As Long) As Long
Declare Function SetPixel Lib "gdi32" Alias "SetPixel" ( _
ByVal hdc As Long, ByVal x As Long, ByVal y As Long, _
ByVal crColor As Long) As Long
|
|
|
Für diese zwei Funktionen braucht man den DeviceContext der PictureBox. Diese ist in der
hDC-Eigenschaft der PictureBox gespeichert.
|
|
|
For i = 0 To Picture1.ScaleWidth
For j = 0 To Picture1.ScaleHeight
Col = Picture1.hdc(i, j)
Col = Abs(Col) \ 2
SetPixel Picture1.hdc i, j, Col
Next j
Next i
|
|
|
Dieser Code wird jetzt um einiges schneller ausgeführt. Jedoch ist er für richtige Anwendungen noch
immer zu langsam.
|
Ein bisschen langsamer ist der Einsatz der SetPixelV-Funktion anstatt der
SetPixel-Funktion. Diese arbeitet sonst gleich wie die SetPixel-Funktion. Jedoch wird
nicht die richtige Farbe angezeigt, sondern eine Annäherung an die Farbe. Man merkt den Unterschied
jedoch nicht wirklich.
|
|
|
Declare Function SetPixelV Lib "gdi32" Alias "SetPixelV" ( _
ByVal hdc As Long, ByVal x As Long, ByVal y As Long, _
ByVal y As Long) As Long
|
|
|
|
SetPixel und GetPixel mit selbst erzeugten DC
|
[ Top ]
|
|
Das ist ein ziemlich komplizierter Weg, der den Vorgang jedoch wieder um einige Millisekunden beschleunigt.
Zuerst erzeugt man einen DeviceContext (DC) und ein kompatibles Bitmap. Dann selektiert man das Bitmap in den
DeviceContext. Das bedeutet, dass auf dieses Bitmap über den DeviceContext zugegriffen werden
kann. Dann kopiert man das gesamte Bild in das Bitmap und modifiziert es dort. Danach kopiert man das
geänderte Bitmap wieder zurück. Dieser Weg ist schon wieder schneller aber schwieriger einzubauen.
|
Diese API-Funktionen werden benötigt.
|
|
|
Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long, _
ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, _
ByVal nHeight As Long, ByVal hSrcDC As Long, _
ByVal xSrc As Long, ByVal ySrc As Long, _
ByVal dwRop As Long) As Long
Declare Function CreateCompatibleBitmap Lib "gdi32" ( _
ByVal hdc As Long, ByVal nWidth As Long, _
ByVal nHeight As Long) As Long
Declare Function CreateCompatibleDC Lib "gdi32" ( _
ByVal hdc As Long) As Long
Declare Function SelectObject Lib "gdi32" ( _
ByVal hdc As Long, ByVal hObject As Long) As Long
Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
Declare Function DeleteObject Lib "gdi32" ( _
ByVal hObject As Long) As Long
|
|
|
BitBlt
| kopiert die Bilder hin und wieder zurück.
|
CreateCompatibleBitmap
| erzeugt im Speicher Platz für ein neues Bitmap.
|
CreateCompatibleDC
| erzeugt den DeviceContext.
|
SelectObject
| selektiert das Bitmap in den DeviceContext.
|
DeleteDC
| gibt den erzeugten DeviceContext wieder frei.
|
DeleteObject
|
gibt den Speicher, den das Bitmap belegt, wieder frei.
|
|
|
Dim mDC, mBMP
mDC = CreateCompatibleDC(Picture1.hdc)
mBMP = CreateCompatibleBitmap(Picture1.hdc, _
Picture1.ScaleWidth, Picture1.ScaleHeight)
SelectObject mDC, mBMP
BitBlt mDC, 0, 0, sw, sh, Picture1.hdc, 0, 0, vbSrcCopy
For i = 0 To Picture1.ScaleWidth
For j = 0 To Picture1.ScaleHeight
Col = Picture1.hdc(i, j)
Col = Abs(Col) \ 2
SetPixel Picture1.hdc i, j, Col
Next j
Next i
BitBlt Picture1.hdc, 0, 0, Picture1.ScaleWidth, _
Picture1.ScaleHeight, mDC, 0, 0, vbSrcCopy
DeleteObject mBMP
DeleteDC mDC
|
|
|
Auch hier kann man die SetPixelV-Funktion anstatt SetPixel verwenden.
|
|
|
Zuerst brauchen wir ein paar Deklarationen.
|
|
|
Type SAFEARRAYBOUND
cElements As Long
lLbound As Long
End Type
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
Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
End Type
'für VB 6!
Declare Function VarPtrArray Lib "msvbvm60.dll" Alias "VarPtr" ( _
ByRef Ptr() As Any) As Long
'für VB 5!
Declare Function VarPtrArray Lib "msvbvm50.dll" Alias "VarPtr" ( _
ByRef Ptr() As Any) As Long
Declare Sub CopyMemory Lib "kernel32" Alias "RtlMoveMemory" ( _
ByRef pDst As Any, ByRef pSrc As Any, ByVal ByteLen As Long)
Declare Function GetObjectAPI Lib "gdi32" Alias "GetObjectA" ( _
ByVal hObject As Long, ByVal nCount As Long, _
ByRef lpObject As Any) As Long
|
|
|
SAFEARRAY2D
|
Dieser UDT wird intern von Visual Basic benutzt um mehrdimensionale Arrays zu verwalten.
|
BITMAP
|
Dieser UDT wird ein paar Informationen über das Bild bereithalten.
|
VarPtrArray
|
gibt die interne Speicheradresse eines Arrays zurück.
|
CopyMemory
|
kopiert blitzschnell Speicherblöcke von einer Position zur anderen.
|
GetObjectAPI
|
gibt uns Informationen über das Bitmap, die dann im UDT BITMAP gespeichert werden.
|
|
|
Dim pic() As Byte
Dim sa As SAFEARRAY2D
Dim bmp As BITMAP
Dim r As Long, g As Long, b As Long
|
|
|
Diese Variablen müssen deklariert werden. In pic() wird das gesamte Bild gespeichert. sa wird
verwendet um Visual Basic einen Array vorzutäuschen. In bmp werden die Informationen über das
Bild gespeichert. r, g und b beinhalten die Rot-, Grün- und Blauwerte, die aus pic()
ausgelesen werden.
|
|
|
GetObjectAPI Picture1.Picture, Len(bmp), bmp
|
|
|
Jetzt haben wir die Informationen über das Bild gespeichert.
|
|
|
With sa
.cbElements = 1
.cDims = 2
.Bounds(0).lLbound = 0
.Bounds(0).cElements = bmp.bmHeight
.Bounds(1).lLbound = 0
.Bounds(1).cElements = bmp.bmWidthBytes
.pvData = bmp.bmBits
End With
|
|
|
Nun füllen wir sa. Es wird hier ein zweidimensionales Array erstellt. Die obere Grenze der
ersten Dimension ist die Höhe des Bild in Pixel. Die obere Grenze der zweiten Dimension ist drei
mal die Breite des Bildes. Man muss bedenken, dass die Farben der Pixel sich aus drei Teilen
(RGB)
zusammensetzen. sa.pvData zeigt jetzt auf die Daten des Bitmaps.
|
|
|
CopyMemory ByVal VarPtrArray(pic), VarPtr(sa), 4
|
|
|
Jetzt muss man pic() mit dem selbstgemachten Array überschreiben.
|
|
|
For i = 0 To UBound(pic, 1)
For j = 0 To UBound(pic, 2)
pic(i, j)= 255 - pic(i, j)
Next j
Next i
|
|
|
Jetzt braucht man zwei Schleifen, um den Array zu verändern. Das obere Beispiel invertiert das Bild.
Wollen Sie die Rot-, Grün- und Blau-Werte separat bearbeiten müssen Sie so vorgehen.
|
|
|
For i = 0 To UBound(pic, 1)
For j = 0 To UBound(pic, 2)
r = pic(i + 2, j)
g = pic(i + 1, j)
b = pic(i, j)
r = ((g * b) \ 128)
g = ((r * b) \ 128)
b = ((r * g) \ 128)
If r > 255 Then r = 255
If r < 0 Then r = 0
If g > 255 Then g = 255
If g < 0 Then g = 0
If b > 255 Then b = 255
If b < 0 Then b = 0
pic(i, j) = b
pic(i + 1, j) = g
pic(i + 2, j) = r
Next j
Next i
|
|
|
Interessant ist jetzt noch wie die Bilddaten im Array gespeichert werden. Diese werden von der
linken unteren Ecke zur rechten oberen gespeichert. Die folgende Tabelle verdeutlicht das.
|
1
| B
| G
| R
| B
| G
| R
| B
| G
| R
|
0
| B
| G
| R
| B
| G
| R
| B
| G
| R
|
| 0
| 1
| 2
| 3
| 4
| 5
| 6
| 7
| 8
|
|
pic(0, 0) beinhaltet also den Blauwert des linken unteren Pixels.
|
RGB (pic(0, 2), pic(0,1), pic(0,0)) ist die Farbe des linken unteren Pixels
|
|
|
CopyMemory ByVal VarPtrArray(pic), 0&, 4
|
|
|
Nachdem man den Array bearbeitet hat sollte man diesen mit der oben genannten Zeile löschen.
|
|
|
Da man das Bild direkt im Speicher verändert hat, muss man noch das Bild neu zeichnen lassen.
|
Mit dieser Methode ist richtig schnelle Manipulation an einzelne Pixel eines Bildes möglich. Man kann
diese Methode sogar in Spielen verwenden.
|
Ich habe hier eine Tabelle erstellt, die den Geschwindigkeitsunterschied zeigen soll. Das Bild wurde
immer invertiert und ist 433x263 Pixel groß. Mein PC ist ein P2 MMX mit 300 MHz und 96 MB RAM. Jeder
Test wurde fünfmal wiederholt und dann der Durchschnitt errechnet.
|
PSet und Point
|
3737,6 ms
|
GetPixel und SetPixel
|
3133,4 ms
|
GetPixel und SetPixelV
|
3210,4 ms
|
GetPixel und SetPixel mit selbsterstelltem DC
|
2032,4 ms
|
GetPixel und SetPixelV mit selbsterstelltem DC
|
1936,0 ms
|
Pointer
|
222,0 ms
|
|
Man sieht, dass die Pointer-Methode um einiges schneller ist als alle anderen. Deshalb sollten alle
Programme, die das benötigen mit dieser Methode arbeiten.
|
|
|
Download (73
kB)
|
Downloads bisher: [ 3195 ]
|
|
|