ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!
Hello! This tutorial is written by Stephan
Kirchmaier. The original File can be found at http://www.vb-empire.de.vu/.
There are several ways to plot single pixels, but most of them
are very slow. In this tutorial I'm going to show you four
different ways. There's also a sample project, that compares all
these methods. I'll only describe everything for 24 bit
colordepth.
First we load a Picture into PictureBox and set the ScaleMode
property to 3 (vbPixels):
Set
Picture1.Picture = LoadPicture(<Path of your File>)
Let's start:
1) PSet and Point
'Point' reads a color value from a given
position. 'PSet' plots a pixel to a given position in any given
color value. Now we can alter the whole picture:
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
This code reads every pixel from the top left
corner, halfs the color value, and paints it again with new
color. We use 'Abs(Col)' because the color value can alos be
'-1'. This means the color value of this pixel is not available.
2) SetPixel and GetPixel
These are just two API functions. They both work
the same way as 'PSet' and 'Point', but they are much faster.
Here are the declarations:
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
We'll need the 'DeviceContext' of our PictureBox
for these functions. You can get the DC from the hDC property of
the PictureBox.
For i
= 0 To Picture1.ScaleWidth
For j = 0 To Picture1.ScaleHeight
col =
GetPixel(Picture1.hdc, i, j)
col = Abs(col) \ 2
SetPixel Picture1.hdc,
i, j, col
Next j
Next i
This Code will be executed somewhat faster than
before, but it is still too slow for any real application. A bit
slower is the usage of the 'SetPixel' function. It works the same
way as the 'SetPixel' function. It doesn't display the correct
color, but a color similar to the original one (you may not
recognize the difference).
Declare
Function SetPixelV Lib "gdi32" Alias
"SetPixelV" (ByVal hdc As Long, ByVal x As Long, ByVal
y As Long, ByVal crColor As Long) As Long
3) SetPixel and GetPixel with a created DC
This is a pretty complex way, but it'll speed up
everything some milliseconds. First we creat a DeviceContext and
a compatible bitmap. Then you select the bitmap over its DC. This
means, that now you cann access your bitmap by using this DC.
Then you copy your whole picture into that bitmap, and modify it
there. After this you copy it back. This is faster but also
harder to deal with:
We'll need these API functions:
Private
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
Private Declare Function CreateCompatibleBitmap Lib
"gdi32" (ByVal hdc As Long, ByVal nWidth As Long, ByVal
nHeight As Long) As Long
Private Declare Function CreateCompatibleDC Lib "gdi32"
(ByVal hdc As Long) As Long
Private Declare Function SelectObject Lib "gdi32"
(ByVal hdc As Long, ByVal hObject As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal
hdc As Long) As Long
Private Declare Function DeleteObject Lib "gdi32"
(ByVal hObject As Long) As Long
'BitBlt' copies the Picture into the bitmap and
back.
'CreateCompatibleBitmap' reserves memory for our bitmap.
'CreateCompatibleDC' creates the DeviceContext.
'SelectObject' selects the bitmap into the DeviceContext.
'DeleteDC' releases the DeviceContext.
'DeleteObject' releases the bitmap memory.
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 = GetPixel(mDC, i,
j)
col = Abs(col) \ 2
SetPixel mDC, i, j,
col
Next j
Next i
BitBlt Picture1.hdc, 0, 0, Picture1.ScaleWidth,
Picture1.ScaleHeight, mDC, 0, 0, vbSrcCopy
DeleteObject mBMP
DeleteDC mDC
As above you may use the 'SetPixelV' function
instead of 'SetPixel'.
4) Use a Pointer
First some declarations:
Option
Explicit
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
Declare Function VarPtrArray Lib "msvbvm50.dll" Alias
"VarPtr" (Ptr() As Any) As Long
Declare Sub CopyMemory Lib "kernel32" Alias
"RtlMoveMemory" (pDst As Any, pSrc As Any, ByVal
ByteLen As Long)
Declare Function GetObjectAPI Lib "gdi32" Alias
"GetObjectA" (ByVal hObject As Long, ByVal nCount As
Long, lpObject As Any) As Long
The user defined type 'SAFEARRAY2D' is used by
Visual Basic for internal management multiple dimension arrays.
The user defined type 'BITMAP' will keep some information about
our picture.
'VarPtrArray' returns the memory address of an array.
'CopyMemory' copies blocks in memory from one position to another
(extremely fast).
'GetObjectAPI' returns information about our bitmap, that will be
written into our user defined type 'BITMAP'.
Dim
pic() As Byte
Dim sa As SAFEARRAY2D
Dim bmp As BITMAP
Dim r As Long, g As Long, b As Long
We'll need these variables. Our whole picture
will be saved to 'pic()'. With 'sa' we make Visual Basic think we
got an array. 'bmp' will be filled with the information about our
Picture. 'r', 'g' and 'b' will contain the color values of each
channel, which are taken from 'pic()'.
GetObjectAPI
Picture1.Picture, Len(bmp), bmp
Now we got the information about our bitmap, and
stored it into 'bmp'.
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
Now we fill 'sa' with data. A two dimensional
array is created. The upper bound of the first dimension is the
picture height in pixels. The upper bound of the second dimension
is three time the picture width, since every pixel is created of
three colors. 'sa.pvData' now points to the bimap data.
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
We'll use to loops, to alter our array. The code
shown above inverts the picture. If you want to change the red,
green, and blue values separately, you could do it like this:
For i
= 0 To UBound(pic, 1) - 3 Step 3
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
It also may be interesting how the data is stored in our
array. Well, they're stored from the bottom left to the upper
right corner, as this table shows you:
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
So pic(0, 0) contains the blue value of the bottom left
pixel.
RGB(pic(0, 2), pic(0,1), pic(0,0)) will return its color.
CopyMemory ByVal
VarPtrArray(pic), 0&, 4
You should delete your array after your changes, like in the
line shown above.
Picture1.Refresh
Now you can refresh you PictureBox, since all changes have
been accomplished in the system memory.
This method allows really fast pixel manipulation, that could
also be used for games.
I also created a table to show you the speed differences. The
picture was 433 by 263 pixels large and has been inverted every
time. My PC is a P2 MMX at 300 MHz and 96 MB RAM. Every test has
been repeated five times. Then I calculated a middle of all the
five tests, and here they are:
PSet and Point
3737,6
ms
GetPixel and
SetPixel
3133,4
ms
GetPixel and
SetPixelV
3210,4
ms
GetPixel and
SetPixel with created DC
2032,4
ms
GetPixel and
SetPixelV with created DC
1936,0
ms
Pointer
222,0
ms
As you can see, the 'Pointer' method a lot
faster than any other. That's why you should use it, when ever
you need pixel plotting in you programs.