Version 2.0!
Features
Tutorials
Files
Glossary
Projects
Contact
Links
Message Board
Extras
LuckyCam
Old News
Sign Guestbook
View Guestbook
VB Horoscope
VB Photo Album
.
ATTENTION READERS! Lucky's VB Gaming Site is no longer active. For updated game programming information and tutorials, please visit The Game Programming Wiki!

Pixel plotting

(This tutorial is translated by Lorenz "Paladin" Barnkow)

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.

CopyMemory ByVal VarPtrArray(pic), VarPtrArray(sa), 4

We now overwrite 'pic()', with our 'sa' array.

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.


Any questions or suggestion? Mail to: VB_Empire@gmx.at