VB Graphics Programming: Part 3 (Advanced API)

Advanced API Pixel Routines

Next comes two advanced ways of getting and setting pixels in Visual Basic: the API routines of GetBitmapBits/SetBitmapBits and GetDIBits/StretchDIBits.  If you haven’t already, I strongly recommend reading the previous two tutorials, “Pure VB Pixel Routines” and “Basic API Pixel Routines,” as they provide the foundation for the advanced graphics principles discussed in this section.

Assuming that you now understand how to use both Visual Basic and the API to get per-pixel data, it’s time to move to the next – and most difficult – section of these tutorials.  Next we are going to discuss the API routines of GetBitmapBits/SetBitmapBits and GetDIBits/StretchDIBits.  Both sets of routines are very fast and very powerful, but they come with a strong disclaimer – pay close attention to any red warnings on this page.  Because these API routines directly interface the heap (dynamically allocated memory), you can easily crash both the VB IDE and/or Windows with a page fault or worse if you use them incorrectly.  Believe me – it’s not a pretty sight to watch your entire machine freeze because you accidentally allocated your array to the wrong size.  (But it gives you a good taste of programming in non-BASIC languages, heh heh.)

But on the happy side of things, these are about as fast as graphics get in VB.  There are ways to use CopyMemory, in-line assembly language, and other freakish routines to get slightly faster effects, but they are not designed specifically for graphics programming so I’m going to avoid them here.

On to the programming!

I – Declaring the Necessary API Functions

At this point in your VB career you are probably used to interacting with images one pixel at a time (using something like the afore-taught .Point/.PSet or GetPixel/SetPixel/V).  These functions are simple to use, but that ease comes at the cost of speed. Many things cause these functions to be slow (as discussed in the first page of this tutorial), so you must be wondering – is there a way to remove some of those speed barriers?

Enter GetBitmapBits and SetBitmapBits.  The big advantage of these two API calls is this: rather than extracting or setting each pixel in an image individually, we pass each of these functions an array and let them fill the whole thing at once with the picture’s pixel data (or set the picture’s pixel data all at once with the information in the array).  This is obviously much more efficient.  The trade-off, of course, is that these techniques involve a little more programming and significantly more risk.  If the array dimensions are off by a mere 1 byte all kinds of things can happen – the picture won’t appear at all, your program will shut itself down, or VB will freeze.  But these only happen if you’re careless and don’t heed my warnings, so pay close attention and you’ll be fine.

We start by declaring a whole bunch of things:

Private 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

Private Declare Function GetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As Long, _
  ByVal nCount As Long, ByRef lpObject As Any) As Long

Private Declare Function GetBitmapBits Lib "gdi32" (ByVal hBitmap As Long, ByVal dwCount As Long, _
  ByRef lpBits As Any) As Long

Private Declare Function SetBitmapBits Lib "gdi32" (ByVal hBitmap As Long, ByVal dwCount As Long, _
  ByRef lpBits As Any) As Long

This might seem a little extreme, so let me go through each of these one at a time.

The Bitmap type is required for the GetObject call – if you’ll look at the GetObject declaration, you’ll notice that the last parameter is of type Any. This is where we will be passing our Bitmap object. As for the individual elements of the Bitmap type, typical graphics programming only cares about four out of the seven variables. They are:

  • bmWidth – the width of the bitmap, in pixels
  • bmHeight – the height of the bitmap, in pixels
  • bmWidthBytes – the width of a bitmap in bytes. If a bitmap is 24 bits-per-pixel (bpp), that means that each pixel occupies 3 bytes. So in this color mode, bmWidthBytes would be (bmWidth * 3). If a bitmap were 16 bpp, each pixel would occupy 2 bytes. In that color mode, bmWidthBytes would be (bmWidth * 2).
  • bmBitsPixel – the number of bits per pixel in the image. In 24bpp mode, the number is 24. In 16bpp mode, the number is 16. Pretty straightforward. Divide this number by 8 to get the number of bytes per pixel.

The other three variables aren’t needed for getting and setting pixels; we simply include them to ensure that our Bitmap type matches the Windows Bitmap type.

Next we have the GetObject call.  The purpose of this API call is to…well, get an object.  You’ll see how this works in a moment.

  • hObject is an object (containing a picture) that we want to get information about – most likely PictureBox.Image or Form.Image.  The properties of hObject will be transferred into lpObject (see below).
  • nCount is the size of the type that is going to receive the information (in our case, the size of the Bitmap type).
  • lpObject is the variable that is going to hold all of the information that we get from hObject (a variable of the Bitmap type we’ve just declared, in fact).  Notice that it is passed ByRef – this allows the API call to edit that variable directly.

GetBitmapBits and SetBitmapBits have identical parameters, which in turn are almost identical to the GetObject parameters.

  • hBitmap represents an object containing a picture (like hObject above, most likely PictureBox.Image)
  • dwCount is the total size of the array holding the image’s pixel data
  • lpBits is the starting address of the place in memory where we want to place the image data (almost always the first spot of an array). Notice, again, that it is declared as ByRef – this allows the API call to edit the array directly (which is a good thing, because that’s how we get the image data!).

Okay – that’s a whole lot of information in a small space, so take a quick break to make sure you understand those declarations.  If some of this is a little hazy, that’s okay, because we’re about to see how they work.

II – Getting Pixels Using GetBitmapBits

Ready?  If so, here’s how we use GetBitmapBits:

Dim bm As Bitmap

GetObject PictureBox.Image, Len(bm), bm

Dim ImageData() as Byte

ReDim ImageData(0 To (bm.bmBitsPixel \ 8) - 1, 0 To bm.bmWidth - 1, 0 To bm.bmHeight - 1)

GetBitmapBits PictureBox.Image, bm.bmWidthBytes * bm.bmHeight, ImageData(0, 0, 0)

Surprisingly, this procedure is very straightforward.  First, we declare a Bitmap object and call the GetObject function.  When called, GetObject will analyze the specified PictureBox and assign the appropriate values to our Bitmap object, which we can then use to prepare our array to receive the image’s pixel data.

Once we have all the picture’s information available to us in the form of a Bitmap object, we declare an array. This ImageData array is of critical importance – we’re going to use it to hold all of the picture’s pixel information.  To make sure it is the right size, we use ReDim to make its dimensions just perfect:

  • The first dimension will contain the values of each pixel’s red, green, and blue values. (bm.bmBitsPixel should equal 24 because your computer is in 24 bit color mode; thus we get 24/8 = 3 bytes per pixel. Because we start the dimension at zero, we subtract one from the upper bound to give us three total spots: 0, 1, and 2, which correspond to red (2), green (1), and blue (0))
  • The second dimension will be used to address the x coordinates of the image’s pixels.
  • The third dimension will used to address the y coordinates of the image’s pixels.

SIDE NOTE ABOUT ‘GETOBJECT’: You may be wondering why we use the GetObject call at all – couldn’t we just resize the ImageData array using the picture box’s ScaleWidth and ScaleHeight properties?  In theory, you could.  However, VB5 does strange things to the ScaleWidth and ScaleHeight properties depending on what is stored there.  For example, the same image might report different ScaleWidth and ScaleHeight properties at different execution times.  In my experience, JPEGs are notoriously bad at this – when you load one, VB5 sometimes thinks that the picture’s width is one pixel less in the picture box than it is in memory.  Honestly, I have no idea as to why VB5 has this problem.  VB6 seems to work fine.  GetObject is always accurate so I use it instead, and I recommend that you do too.  There is no measurable speed difference between these two mechanisms.

SIDE NOTE ABOUT DECLARING YOUR ARRAY: Arrays supplied to GetBitmapBits (and later in this tutorial, GetDIBits) must have a width that is a multiple of 4, e.g. 4, 8, 16, 256, 360, etc. If your image has a width that is a multiple of four, no worries – but if it is not a multiple of four, you will need to adjust your code accordingly. See the bottom of this page for details (in the section titled “VI – OPTIONAL: The Infamous 4-Byte Alignment Issue”).

ANOTHER SIDE NOTE ABOUT DECLARING YOUR ARRAY: You can use any number of dimensions in your array, so long as the total size is accurate. For example, you could also do something like ReDim ImageData(0 to bm.bmWidth * bm.bmHeight * 3) and the function would still work fine. The API call could care less about how the array is declared – all it gets is the address of the first element in the array and the number of bytes that it’s allowed to work with. The way the array is dimensioned is only for your convenience.  I like the above way because it makes editing the image very easy. This issue will be discussed further in the next section of this tutorial.

WARNING!! This ReDim statement is where you can really screw your computer.  If ImageData is too small, GetBitmapBits will attempt to put the picture data in unallocated memory – causing a general protection fault, a page fault, or some other nasty illegal operation.  Make sure that ImageData is the right size!

The GetBitmapBits call itself is very straightforward: it takes the array (in this case, ImageData()) and fills it with the pixel data located in PictureBox.  Now you can edit the values any way you want.  For example, the following loop would invert all of the pixels in the image:

'First, get the image data using the above code section

Dim X as long, Y as long

For X = 0 to PictureBox.ScaleWidth - 1
For Y = 0 to PictureBox.ScaleHeight - 1

   'Invert the R value
   ImageData(2, X, Y) = 255 - ImageData(2, X, Y)
   'Invert the G value
   ImageData(1, X, Y) = 255 - ImageData(1, X, Y)
   'Invert the B value
   ImageData(0, X, Y) = 255 - ImageData(0, X, Y)
Next Y
Next X

Really, GetBitmapBits is as easy as GetPixel if you understand the API structure.

III – Setting Pixels Using SetBitmapBits

SetBitmapBits is almost identical to GetBitmapBits:

Dim bm As Bitmap

GetObject PictureBox.Image, Len(bm), bm

SetBitmapBits PictureBox.Image, bm.bmWidthBytes * bm.bmHeight, ImageData(0, 0, 0)

If PictureBox.AutoRedraw = True Then
   PictureBox.Picture = PictureBox.Image
   PictureBox.Refresh
End If

Everything is the same as GetBitmapBits, except that we aren’t resizing the array (because it is already the right size and resizing it would erase all of its information!).  The last If/Then statement is included because SetBitmapBits won’t automatically initialize the AutoRedraw event, so we have to tell it to replace the Picture property (what is shown on the screen) with the Image property (what is stored in memory).

I hope you’re finding this easier than expected!  In fact, it’s almost too easy… so of course, there is a slight problem with this method: both GetBitmapBits and SetBitmapBits only work in 24/32-bit color mode (16.7 million colors). Actually, they work in 16 and 8 bit color modes too, but the image data no longer occupies 3bpp (bpp = bits per pixel) so editing the image data is significantly more complicated.  It can be done, but you have to write a function to translate 2 bits into 3 as well as transferring the data into a separate array while you edit it.  Then, to draw it, you have to translate the 3 bits back into 2 bits and then transfer your editing array back into the original one.  It’s messy and time-intensive, so I wouldn’t recommend this method

So of course, someone is going to ask “but how can I do fast graphics in 16 or 8 bit color mode?” That is what DIB sections are for, so if you want to know about them then keep reading.

(Personally, I would recommend using DIB sections for all of your graphics programs because you’ll never get unexpected color-mode errors with them, and it’s a great way to add additional functionality to your graphics program.  I have only discussed BitmapBits because they make for an excellent introduction to DIB sections.)

IV – A Crash Course in Declaring DIB Sections

DIB section stands for ‘Device Independent Bitmap.’  The name is pretty self-explanatory: DIBs are simply a way of interacting with bitmaps in any color mode or on any computer and getting consistent results.  There are actually two varieties of DIBs – OS/2 encoded and Windows encoded, so I guess “device independent’ isn’t totally accurate… but that’s okay.

DIBs share many characteristics with BitmapBits.  The calls share certain parameters and the underlying logic is very much the same.  However, DIB sections have several major differences you need to be aware of: they’re slightly more confusing to use, they require more code, and they return the image data upside-down.  The most important difference, however, is that DIB sections work in any color mode – and, as a bonus, the StretchDIBits call is much more powerful than SetBitmapBits. Below are the required DIB section declarations:

Private 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

Private Declare Function GetObject Lib "gdi32" Alias "GetObjectA" (ByVal hObject As Long, _
  ByVal nCount As Long, ByRef lpObject As Any) As Long

Private Type RGBQUAD
   rgbBlue As Byte
   rgbGreen As Byte
   rgbRed As Byte
   rgbAlpha As Byte
End Type

Private Type BITMAPINFOHEADER
   bmSize As Long
   bmWidth As Long
   bmHeight As Long
   bmPlanes As Integer
   bmBitCount As Integer
   bmCompression As Long
   bmSizeImage As Long
   bmXPelsPerMeter As Long
   bmYPelsPerMeter As Long
   bmClrUsed As Long
   bmClrImportant As Long
End Type

Private Type BITMAPINFO
   bmHeader As BITMAPINFOHEADER
   bmColors(0 To 255) As RGBQUAD
End Type

Private Declare Function GetDIBits Lib "gdi32" (ByVal hDC As Long, ByVal hBitmap As Long, _
  ByVal nStartScan As Long, ByVal nNumScans As Long, lpBits As Any, lpBI As BITMAPINFO, _
  ByVal wUsage As Long) As Long

Private Declare Function StretchDIBits Lib "gdi32" (ByVal hDC As Long, ByVal x As Long, _
  ByVal y As Long, ByVal dWidth As Long, ByVal dHeight As Long, ByVal SrcX As Long, _
  ByVal SrcY As Long, ByVal SrcWidth As Long, ByVal SrcHeight As Long, lpBits As Any, _
  lpBI As BITMAPINFO, ByVal wUsage As Long, ByVal RasterOp As Long) As Long

Quite the mess of declarations, isn’t it? You should notice some similarities between these declarations and the BitmapBits ones. Here’s a quick explanation of the DIBits calls:

  • The first type and declaration are just the same old GetObject stuff – you already know all about that.
  • The next type, RGBQuad, represents basic pixel data – red, green, and blue values, along with an alpha channel.

SIDE NOTE ON ALPHA CHANNELS: For those who are interested: both DIBs and regular bitmaps can contain transparency information.  If you have (what used to be, heh) an expensive monitor and video card and run them at 32-bit color mode, that extra byte contains transparency data (a number from 0-255, 0 being opaque and 255 being transparent).  So technically, 32-bit bitmaps and DIBs could be used like GIFs or PNGs and displayed transparently.  There’s actually an API call with Win2K/ME/XP called AlphaBlend that’s similar to BitBlt/StretchBlt except that it utilizes an alpha channel (this method is very similar to DirectX and a transparent key color); you can read all about the AlphaBlend call at MSDN.

  • The BITMAPINFOHEADER type contains all of a particular bitmap’s information, unlike the stripped-down version we use for GetObject.  This is what makes DIB sections “Device Independent” – by knowing all of that extra information about the bitmap we can display it accurately on any device at any color resolution.
  • Lastly, the BITMAPINFO class combines a header and an array of RGBQUADs (used for the palette in 8-bit images).  This header is capable of holding data for DIB sections of any color depth – from 1bpp to 32bpp.  Again, this is part of making DIB sections “Device Independent.”  It also comes in handy when using 8bpp color modes because you can directly edit the palette for super-fast graphics effects.

The GetDIBits call is somewhat more complicated than the GetBitmapBits one, so let’s go through it one part at a time.

  • hDC is the same as it was in GetPixel – it is the handle/”address” of the device we want to get the pixel data from. Most likely, this is PictureBox.hDC or Form.hDC.
  • hBitmap is the location of the pixel data itself. This is most commonly PictureBox.Image or Form.Image
  • nStartScan is the line we want to start reading the pixel data from.  This will always be 0, unless for some odd reason you want to read the data from the middle of the image.
  • nNumScans is the number of horizontal lines that you want to read from the image.  This will always be the height of the image (in pixels), unless you want to extract every-other-line or something strange like that.  (Scanlines also come in handy for doing fast image rotations, but I’m not going to discuss those here ;) )
  • lpBits is the same as it was in GetBitmapBits.  This is the array that the image’s data will be copied into.
  • lpBI is a BitmapInfo variable that contains all of the desired information of the bitmap we are want to get.  This includes the width, height, and – important! – the color depth.  This is how we let Windows know that even though the computer may be in 16bpp or 8bpp color modes, we want the bitmap information to be in 24bpp mode (color depth is handled via the .bmBitCount property).
  • Last is the wUsage variable, which is related to referencing source-DC-based palettes. Because this tutorial focuses only on 24-bit (i.e. non-paletted) image processing, we’re going to ignore this variable completely – just always leave it as zero.

We’re almost done!

Unfortunately, if you thought GetDIBits was long-winded, you’re not going to like StretchDIBitsStretchDIBits is a very powerful call, but this means there are a lot of parameters.  If you are familiar with BitBlt and/or StretchBlt this part will probably make a lot of sense to you.  The StretchDIBits parameters are:

  • hDC: same as GetDIBits
  • x: the x coordinate of the top-left corner you want to draw the pixels to
  • y: the y coordinate of the top-left corner you want to draw the pixels to
  • dWidth: the desired width of the destination image.  This is usually the same size as the image you got the data from, although you can stretch or shrink the destination size if you wish.
  • dHeight: the desired height of the destination image.  Refer to the dWidth notes.
  • SrcX: the x coordinate of the top-left pixel in the source array (for us, ImageData()) that you want to start taking the pixels from. Usually zero, but you can change this value to only take a portion of the source image.
  • SrcY: the y coordinate of the top-left pixel in the source array that you want to start taking pixel data from.  Refer to the SrcX notes.
  • SrcWidth: the desired width of the pixel selection from the source image. This is usually the same size as the original image, although you can stretch or shrink the source size if you wish.
  • SrcHeight: the desired height of the pixel selection from the source image. Refer to the SrcWidth notes.
  • lpBits: same as GetDIBits. This is the array that the pixel data will be taken from (again, ImageData())
  • lpBi: same as GetDIBits. This is the BitmapInfo variable that contains all of the correct parameters for our pixel data.
  • wUsage: same as GetDIBits – leave it as zero.
  • RasterOp: a raster operation, exactly the same as you would use for BitBlt or StretchBlt. There is a complete list of RasterOp constants listed in the VB help files (or MSDN collection), but the most common one is vbSrcCopy, which will simply copy the pixels from the source array to the destination picture box.

V – Using DIB Sections

Now that your brain has had some time to digest all of those declarations, let’s demonstrate the GetDIBits call.  Here’s a full-blown example of how to get an image’s data using the GetDIBits call, minus the declarations above:

'Routine to get an image's pixel information into an array dimensioned (rgb, x, y)
Public Sub GetImageData(ByRef SrcPictureBox As PictureBox, ByRef ImageData() As Byte)

'Declare variables of the necessary bitmap types
Dim bm As Bitmap
Dim bmi As BITMAPINFO

'Now we fill up the bmi (Bitmap information variable) with all the necessary data
bmi.bmHeader.bmSize = 40 'Size, in bytes, of the header (always 40)
bmi.bmHeader.bmPlanes = 1 'Number of planes (always one)
bmi.bmHeader.bmBitCount = 24 'Bits per pixel (always 24 for image processing)
bmi.bmHeader.bmCompression = 0 'Compression: none or RLE (always zero)

'Calculate the size of the bitmap type (in bytes)
Dim bmLen As Long
bmLen = Len(bm)

'Get the picture box information from SrcPictureBox and put it into our 'bm' variable
GetObject SrcPictureBox.Image, bmLen, bm

'Build a correctly sized array.
ReDim ImageData(0 To 2, 0 To bm.bmWidth - 1, 0 To bm.bmHeight - 1)

'Finish building the 'bmi' variable we want to pass to the GetDIBits call
bmi.bmHeader.bmWidth = bm.bmWidth
bmi.bmHeader.bmHeight = bm.bmHeight

'Now that we've filled the 'bmi' variable, we use GetDIBits to take the data from SrcPictureBox and put
'  it into the ImageData() array using the settings specified in 'bmi'
GetDIBits SrcPictureBox.hDC, SrcPictureBox.Image, 0, bm.bmHeight, ImageData(0, 0, 0), bmi, 0

End Sub

We’re almost done with DIB sections – all that’s left is StretchDIBits.

The procedure required to set up our variables for StretchDIBits is almost identical to the procedure we used for GetDIBits. In fact, everything up to the actual GetDIBits call is the same – everything except the ReDim ImageData() line, of course.  (If we ReDimmed the array before setting it, we would erase all of the pixel data!).  Here’s a full example:

'Routine to set an image's pixel information from an array dimensioned (rgb, x, y)
Public Sub SetImageData(ByRef DstPictureBox As PictureBox, ByRef ImageData() As Byte)

'Variables for the necessary bitmap types
Dim bm As Bitmap
Dim bmi As BITMAPINFO

'Fill the bmi (Bitmap information variable) with appropriate values
bmi.bmHeader.bmSize = 40
bmi.bmHeader.bmPlanes = 1
bmi.bmHeader.bmBitCount = 24
bmi.bmHeader.bmCompression = 0

'Calculate the size of the bitmap type (in bytes)
Dim bmLen As Long
bmLen = Len(bm)

'Get the picture box information from DstPictureBox and put it into our 'bm' variable
GetObject DstPictureBox.Image, bmLen, bm

'Now that we know the object's size, finish building the temporary header to pass to StretchDIBits
bmi.bmHeader.bmWidth = bm.bmWidth
bmi.bmHeader.bmHeight = bm.bmHeight

'Use StretchDIBits to take the data from the ImageData() array and put it into SrcPictureBox using
'  the settings specified in 'bmi'
StretchDIBits DstPictureBox.hDC, 0, 0, bm.bmWidth, bm.bmHeight, 0, 0, bm.bmWidth, bm.bmHeight, _
  ImageData(0, 0, 0), bmi, 0, vbSrcCopy

'Since this doesn't automatically initialize AutoRedraw, we have to do it manually
'Note: set AutoRedraw to 'True' when using DIB sections. Otherwise, you may get unpredictable results.
If DstPictureBox.AutoRedraw = True Then
   DstPictureBox.Picture = DstPictureBox.Image
   DstPictureBox.Refresh
End If

End Sub

And there you have it – a complete explanation of how to get image data from any picture in any color mode and how to set that same data back into a picture once you’re done editing it.

VI – DIB Sections in Action

This .zip file shows these exact routines – cut and pasted out of this tutorial into a form – being used to adjust the brightness of an image.  Quite a bit better than GetPixel and SetPixel or .Point and .PSet, aren’t they?

It may surprise you, but in the next tutorial we’re going to get this program running even faster – we’re going to use some DIB section tricks to make it run in real-time.

Before we continue, however, I am including an optional section regarding the infamous 4-bit alignment issue associated with DIB sections.  If you are interested in using DIB sections only casually, this issue may not apply to you.  If, however, you plan on using DIB sections extensively, this issue is critical.  DIB sections have trouble if you use them on an image whose width is not a multiple of 4.  We’ll discuss how to deal with this problem in the optional section below.

CONTINUE TO “TANNER’S TOP 10 LIST OF GRAPHICS OPTIMIZATIONS”

VI – OPTIONAL: The Infamous 4-Byte Alignment Issue

As you may know, all versions of Windows at the time of this writing (95, 98, ME, NT4, 2000, XP) are 32-bit operating systems.  This means that memory within Windows is split up into 32-bit, or 4-byte, chunks.  Normally this means very little to a VB6 programmer, but when using DIB sections we must take this into account.

Because DIB sections are designed to be fast, they’re optimized for speed in many ways.  One such way is that they require any arrays associated with them to be 4-bytes (or 32 bits) wide.  This allows the arrays to line up in memory exactly – without any trailing bytes – which in turn allows Windows to access the information more quickly, since it doesn’t have to re-align each horizontal line of the image to match up with 32-bit memory spaces.

As it turns out, this isn’t a problem for images whose width is already a multiple of 4: 800x600, 32x32, 1920x1080, etc.  All standard image/screen sizes are multiples of 4 for a reason.

Sometimes, however, it’s necessary to work with images whose width may not be divisible by 4 (e.g. 29, 30, and 31 instead of 32).  This creates serious problems with DIB sections – try plugging such an image into the code above and you’ll see what I mean.

So how do we solve such a problem?  There’s no good way, to be honest.  Two main options exist:

1) Resize the image to make it have a width that’s a multiple of 4.

2) Manually force the array containing the image data to have a width that’s a multiple of 4.

The first option is preferable, but if that’s not available to us then we have to do some hardcore adjusting of the array we use (ImageData() in this tutorial).  This requires CopyMemory and a For loop – both of which are time killers, which is contrary to the whole point of this tutorial.

So in the interest of space and brevity, I’m not going to go through the intricacies of this method in this tutorial.  Instead, you can download a modified brightness program that demonstrates how to do this.  Robert Rayment – a profilic VB programmer in his own right – put it together, and all credit for the modifications go to him.  Please shower him with praise.

An easier fix – and my preferred method – is to use a 2-dimensional array to receive the image data instead of a 3-dimensional one, like so:

ArrayWidth = (bm.bmWidth * 3) - 1
ArrayWidth = ArrayWidth + (bm.bmWidth Mod 4)
ArrayHeight = bm.bmHeight
ReDim ImageData(0 To ArrayWidth, 0 To ArrayHeight) As Byte

This method works well, but accessing individual pixels is slightly more cumbersome:

  • Red is in location (x * 3 + 2, y)
  • Green is in location (x * 3 + 1, y)
  • Blue is in location (x * 3, y)

Using a temp variable to store the x * 3 value actually makes this faster than the 3d array used in the tutorials.  As such, this is my favorite method. It is also the method I use in PhotoDemon.

Streams, mentioned in the next tutorial, also need to be 4-byte aligned.

CONTINUE TO “TANNER’S TOP 10 LIST OF GRAPHICS OPTIMIZATIONS”

Similar Posts:

 

This site - and its many free downloads - are 100% funded by donations. Please consider a small contribution to fund server costs and to help me support my family. Even $1.00 helps. Thank you!

23 thoughts on “VB Graphics Programming: Part 3 (Advanced API)”

  1. This tutorial is really helping me. Thanks very much!

    My only comment is that there is a big red warning about the ReDim statement being where you can screw your computer up, but in the code snippet, the ReDim statement is wrong.

    ReDim ImageData(0 To (bm.bmBitsPixel 8) – 1, 0 To bm.bmWidth – 1, 0 To bm.bmHeight – 1)

    Should be:

    ReDim ImageData(0 To (bm.bmBitsPixel / 8) – 1, 0 To bm.bmWidth – 1, 0 To bm.bmHeight – 1)

  2. Yikes! You’re right! When I transferred this tutorial from plain HTML to WordPress, WordPress must have considered the backslash an escape character. It’s now fixed.

    Glad the tutorial has been useful, and thanks again for the tip!

  3. I wish someone could help me. I have been porting my windows application to Linux native program that will run as a stand alone not within Wine programs. Part of my program is supposed to print Forms or windows. It fails and fews procedures or functions are not supported but are under Microsoft – GetDIBitSizes, GetDIBits…I am frustrated. I have been looking for some other ways of prints the forms programmatically, but I have been coming empty handed. They are all
    located in winapih.inc. Delphi to Lazarus.

    1. Hi Steven: I realize this is much too late for you, but if others are wondering – DIB support was added to Wine in version 1.4 (March 2012). Per my own testing, the code above will now work under Wine without needing modifications.

  4. Hi Tenner.
    Very good and very helpful turorial.
    A few comments

    in this part you wrote “rotate the data 180 degrees” it doesn’t rotate it. It is reflected on the X axis. So the picture point(i,j) in the memory is
    point(i,picture1.scaleheight-j).

    About the color if you want to update the RGB of pixel you to do it BRG.

    Because if i have 32 bit computer the array of array 3D di in DIB is a little bit chaotic. So you change one pixel easily because you don’t know it’s RGB.

    Good tip about the 2D array.

    Thank you.

  5. This is a very good technique. I am using this Advanced API for my program, and it works really fast. I am only using an old laptop with Pentium 4 2.40 GHZ, a very standard graphic card, and only 500 MB RAM with Windows XP Operating System, so I consider my computer is quite slow compared to computers today.

    I am developing a program for textile design which involved calculations on how many yarns used, how each colors of the yarns are configured in the finished fabric, what type of weaving pattern to use, etc. In short, there are a lot of calculations involved just to put a single pixel in a picture box, but I can display a complete 575 x 487 pixels image in less then 20 milliseconds using the Advanced API.

    Thank you Tanner.

  6. i have 2 questions:
    1 – (speaking on pixels)what is more faster on Graphics? is hdc variable(long array using hdc) or DIB’s?
    2 – can you have 1 toturial for do some effects and strecht in the image with nice effects(like on emulatores)?
    thanks for all

    1. Hi Joaquim,

      I’m not sure what your first question is asking. An hDC is just a Long-type variable that points to a device context (in VB, it’s typically PictureBox.hDC or Form.hDC). hDCs should not be used in an array. You’ll notice in the tutorial above that hDCs are required for DIBs – they appear in the GetDIBits and StretchDIBits calls.

      For your second question, you can find a huge amount of image effects at this link: http://www.tannerhelland.com/programming-directory/

  7. Is there a way to load a screen capture with bitblt into an array. I copied the screen to a picturebox using bitblt then used the getbitmapbits method that you showed to place it in the array but the array gets the grey background of the picturebox.

    Or any other fast way to screen capture to array.

    Thanks.

    1. Hi Rusty,

      Two things – before you copy the screen capture into a picture box, make sure to resize the picture box to be the same size as the screen capture. (Also, make sure the .ScaleMode property is set to Pixels.) Once the picture box is resized properly, copy the screen capture into it, then use the following line:

      PictureBox.Picture = PictureBox.Image

      Substituting the name of the picture box for “PictureBox”, obviously. Once you’ve done that, you can use the methods described in this tutorial to copy the screen capture contents into an array.

  8. How do I convert the image in ImageData() array to grayscale, I’m using getbitmapbits and I tried this:

    For x = 0 To TempWidth
    For Y = 0 To TempHeight

    arrayGrayscale(0, x, Y) = (ImageData(2, x, Y) + ImageData(1, x, Y) + ImageData(0, x, Y)) / 3

    Next Y
    Next x

    and this:

    For x = 0 To TempWidth
    For Y = 0 To TempHeight

    R = ImageData(2, x, Y)
    G = ImageData(1, x, Y)
    B = ImageData(0, x, Y)
    gray = (R + G + B) / 3
    ImageData(2, x, Y) = gray
    ImageData(1, x, Y) = gray
    ImageData(0, x, Y) = gray

    Next Y
    Next x

    I tried to get the code from your grayscale tutorial but that doesn’t use getbitmapbits so it didn’t work

    Thanks

    1. Rusty: The first set of code won’t work because it’s only changing one of the color values – not all three.

      The second set should work. That said, I *strongly* recommend using the DIB method instead of GetBitmapBits. If your display is running in 32-bit or 16-bit color mode, GetBitmapBits won’t work as you expect, because it will return the color data in a different format.

      Additionally, if your image has a width or height that is not a multiple of 4, the code will need to be modified per the last section of this tutorial.

      For ease of use, I recommend using the FastDrawing class in the grayscale example on this site. (All my graphics examples use that same class.) It provides the code in this tutorial in an easy-to-use way that is already protected against problems like those mentioned above.

  9. I’m writing a power monitoring system, basically its plotting 11 graphs from incoming data off a PIC, voltage and 10 wattage’s.
    I’m using the ‘bmp’ method to redraw the graph, moving it to the left 1 pixel every second then drawing the new pieces of lines.
    I would like to be able to HIDE any of the plotted graphed lines and restore as necessary.
    I have used the ‘MakeTransparent’ function to hide a color by picking a point of that color, but I am unable to restore the color, but of course if I did this to more than 1 color they would all be restored the nominated color. even just changing the color to something less prominent would be ok.
    so basically I would like to ‘hide’ and ‘show’ specific color in a bmp.
    can you help please.

    1. Hi Graham. I’m not overly familiar with VB.Net, but here’s my general advice.

      On projects like this, the preferable approach is to render each graph to its own image (BMP, or even better, DIB). Then, each second, render all the graph DIBs to the main screen. To exclude a graph, just don’t render it to the main screen on the next refresh. I think you will find this to be a much simpler (and faster) implementation, rather than attempting to scan the graph pixel-by-pixel and erase colored pixels.

      1. thanks for advice Tannar.
        vb 2012 is much the same as vb6.
        I have tried one picturebox on top of another, they loose transparency every time you do something to it, but as I am putting a bmp into it, how do I make the multiple bmps transparent? in fact how do I get multiple bmps into 1 picturebox?
        I thought maybe I could set the one bmp to have multiple layers, is this possible?
        great tutorials, learning a lot.

      2. Hi Graham. Sorry it is difficult to explain these items without pictures. But I will try. Here are the steps I would take if I was solving the problem in VB6.

        – Create 11 containers for the 11 graphs. These containers can be picture boxes, or they can be in-memory DIBs created by something like the CreateDIBSection API call. These containers should be invisible to the user. Each one will be relatively small (the size of one graph).
        – Create one large container to which you will draw all the finished graphs. This container should be visible to the user. This container will be large (the size of ALL the graphs stacked together).
        – Every second, redraw the images inside the containers. You can do this the way you already described – shift the current image to the left, then draw the new line on the right-most pixel.
        – After all graphs have been updated, erase the image currently in the large container.
        – Draw each of the 11 graphs to the large container, using code similar to:
        For i = 0 to 11
        If Not smallContainer.Ignore Then
        BitBlt bigContainer.hDC, 0, i * smallContainer.Height, smallContainer.Width, smallContainer.Height, smallContainer.hDC, 0, 0, vbSrcCopy
        End If
        Next i
        – Refresh the main container (e.g. paint it to the screen)

      3. hi Tanner, did this with success. I created an array of 11 pictureboxes and an array of 11 bitmaps, plotted in each pb and put them all into the visible pb.
        I’m using drawimage now but want to convert to bitblt. thanks for your suggestion.

  10. Thank you, a well-written and helpful tutorial. In working with the code in your zip for my own purposes I noted what may be an oversight that could cause others confusion. I wanted to use a second window for the output of the adjustment and it worked fine for the DIB button but not for the Bit one- each successive press of the Bit button did an additional brightness adjustment on the destination Picture. I noted that in DrawBitmapBitsBrightness GetObject DstPicture.Image was used, and DstPicture was also used again below for Redim, width and height calculations. Once I changed these to Src everything works as desired. I think this might have been your original intent, but since the example uses a single Picturebox the effect is not obvious.

    Here’s how that snippet looks with those changes:


    upper code for DrawBitmapBitsBrightness not here

    ‘Create a bitmap variable and copy the basic information from ‘PictureBox.Image’ into it
    Dim bm As BITMAP
    ‘ORIGINAL ->> GetObject DstPicture.Image, Len(bm), bm
    GetObject SrcPicture.Image, Len(bm), bm
    ‘Create an array of bytes and fill it with the information from ‘bm’ (i.e. PictureBox.image)
    Dim ImageData() As Byte
    ReDim ImageData(0 To (bm.bmBitsPixel \ 8) – 1, 0 To bm.bmWidth – 1, 0 To bm.bmHeight – 1)
    ‘ORIGINAL ->> GetBitmapBits DstPicture.Image, bm.bmWidthBytes * bm.bmHeight, ImageData(0, 0, 0)
    GetBitmapBits SrcPicture.Image, bm.bmWidthBytes * bm.bmHeight, ImageData(0, 0, 0)

    ‘Temporary width and height variables are faster than accessing the Scale properties over and over again
    Dim TempWidth As Long, TempHeight As Long
    ‘ORIGINAL ->> TempWidth = DstPicture.ScaleWidth – 1
    ” not positive about these if Dst is not identical in size to Src, but why wouldn’t it be?
    TempWidth = SrcPicture.ScaleWidth – 1
    ‘ORIGINAL ->> TempHeight = DstPicture.ScaleHeight – 1
    TempHeight = SrcPicture.ScaleHeight – 1
    ‘run a loop through the picture to change every pixel

    … original code continues

    An additional note for anyone else following the same idea I had of using separate Pictureboxes- pay attention to the properties (autoredraw, autosize, scalewidth, scaleheight and maybe others) for the second Picturebox. If you don’t get it all right the transfer won’t happen, won’t happen correctly or you’ll be out the GPF/maybe even Blue Screen exit. Save things before you run it.

    Warning- rant follows. What I wanted to do was in VB4; the corrections I suggest work in both VB4 and VB6. I program in 3, 4, 6, VBA (Studio 6 C, C++, and even in FORTRAN and PDP-11 Macro for that matter) but I refuse to tolerate the stupidity of the NET distraction once VB6 and XP are no longer supported by Microsoft. The sheer size of the distributions for a ‘Hello World’ program using the oblique, bloated monstrosity that is NET is just unnecessary for what most of us want to accomplish with our software. I’m not writing a user interface and database for United Airlines or a POS system for Walmart. Anyone who wants to ‘keep it small, keep it simple, keep it fast’ will be just fine with VB4. My VB4 .EXEs run on Windows 8, and your source required NO changes for VB4 compatibility; of course since it’s not backwards-compatible I did have to move the code manually and create the form and controls myself but the code worked fine.

    Thanks again!
    Randy

Leave a Reply