BitBlt and GDI32 for the Thick Headed
In this tutorial you will learn...
- Howto load an image file into memory
- Howto create a back-buffer
- The BitBlt API explained in plain english
- Howto use double-buffering techniques to achieve fast, flickerless graphics (faster than AutoRedraw)
This tutorial should NOT take more then 15 minutes to
read - If you read every line and follow every instruction, almost
everything you need to know (about BitBlt) is here!Welcome
to my tutorial which I have titled "BitBlt for the Thick Headed". If
you want to go through this tutorial quickly, all the essential
parts are in BOLD. For the record, I mean no offence to anyone on
the PSC community, I was going to call it BitBlt for Dummies like the
popular For Dummies books, but didn't want to get into copyright
complications with book publishers. The goal of this tutorial is to
step-by-step explain howto use BitBlt and some other Win32 GDI
functions, to accomplish tasks such as double buffering and loading
sprites from files - All in a relatively short reading-time (basically
i'll try not to ramble on too much) Anyways, let's get started...
The first thing your going to do
obviously is create a form (so you can follow along with this
tutorial), set the ScaleMode to '3 - Pixel', I suggest you
always set the scalemode to Pixels if your going to be using the form
with any sort of API to prevent confusion. Next Increase the form's size until the ScaleWidth is 320, and the ScaleHeight is 256. We
will be using the form as our practice surface, note that the form
property called "HasDC" must be set to TRUE - Actually it doesn't
seem to matter, but it's good practice. Also, for many of you who fell
in love with using AutoRedraw, we will NOT need AutoRedraw because we
are going to be using Double Buffering which is ALOT faster, and more
professional. So make sure the form property AutoRedraw is
set to false. The next step is to declare the API calls
that we will need, as shown below. So copy and paste the code
below into the very top of your form's code - in the
(general)(declarations) section of your form. - 'The following API calls are for:
- 'blitting
- Private Declare Function BitBlt Lib "gdi32" (ByVal hDestDC As Long,ByVal x As Long, ByVal y As Long, ByVal nWidth As Long, ByVal nHeightAs Long, ByVal hSrcDC As Long, ByVal xSrc As Long, ByVal ySrc As Long,ByVal dwRop As Long) As Long
- 'code timer
- Private Declare Function GetTickCount Lib "kernel32" () As Long
- 'creating buffers / loading sprites
- Private Declare Function CreateCompatibleBitmap Lib "gdi32" (ByVal hdcAs 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 GetDC Lib "user32" (ByVal hwnd As Long) As Long
- 'loading sprites
- Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, ByVal hObject As Long) As Long
- 'cleanup
- Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject As Long) As Long
- Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) As Long
- 'end of copy-paste here...
Q & AQ. What is a DC (Device Context aka. hDC)? A.
A Device Context is a number that is assigned to any peice of
information stored in memory, like a name or handle, so we can call
upon that information with ease, when using BitBlt, we point to the
handle of images that we have loaded into memory.* If you dont know what API is, then you should do some research about it, before you even try to figure this tutorial out! ;-) Next,
we need to store the addresses of the DC's that we are creating. DC's
addresses are Long values so we will Declare Public Variables to
store the DC's memory address as shown below. (copy and paste
into general declarations) - 'our Buffer's DC
- Public myBackBuffer As Long
- Public myBufferBMP As Long
- 'The DC of our sprite/graphic
- Public mySprite As Long
- 'coordinates of our sprite/graphic on the screen
- Public SpriteX As Long
- Public SpriteY As Long
- 'end of copy-paste here...
Q & AQ. What is Double Buffering and why do we use it?
A. If you were to blit sprites directly to the form, you would soon
notice that everything "flickers" alot, this is because of the slight
time difference between blitting different sprites, they dont actually
appear on the screen at the exact same time, but rather, one after
another. Turning AutoRedraw ON and using a Form.Refresh call AFTER
blitting is one way of fixing this problem - BUT, AutoRedraw is VERY
slow, so we use a technique called Double Buffering to fix the problem
and give our game FAST, FLICKERLESS graphics. Double buffering works
like this:
- We create a buffer - a buffer can be thought of as a blank
peice of paper, it is a blank bitmap stored in memory, not seen
directly on the screen.
- We then blit all of our sprites and graphics onto the buffer,
instead of directly onto the form where things would flicker - the
buffer is only in memory though, so we wont see what is being done to
the buffer yet.
- Finally, we blit the buffer to the screen (the form), where
the final product of everything being mixed to gether on the buffer is
seen, without the flickering.
Now we have the foundation of our code, we have all the API
declarations we'll be needing, and all the variables we'll be using in
this example. The next thing we're gunna do is create a function
that loads graphics into memory, it makes working with the API alot
simpler...
- Device Contexts - TIP: One thing that is important to
understand is that a device context alone has no graphical data in it.
A device context needs to have a bitmap loaded into it, whether that be
a bitmap file, or a blank bitmap to use as a canvas to draw on (which
is how you create a back buffer).
Copy and paste the function below, but be sure to read all the comments so you understance the concept. - Public Function LoadGraphicDC(sFileName As String) As Long
- 'cheap error handling
- On Error Resume Next
- 'temp variable to hold our DC address
- Dim LoadGraphicDCTEMP As Long
- 'create the DC address compatible with
- 'the DC of the screen
- LoadGraphicDCTEMP = CreateCompatibleDC(GetDC(0))
- 'load the graphic file into the DC...
- SelectObject LoadGraphicDCTEMP, LoadPicture(sFileName)
- 'return the address of the file
- LoadGraphicDC = LoadGraphicDCTEMP
- End Function
- 'end of copy-paste here...
Ok, so this is what our function does... It creates a Device Context
compatible with the screen, it then loads the specified graphics file
into the device context... We're going to be using the function
in our example code... but before we go any further with the example
project, I'm going to explain the BitBlt API from start to finish - in
plain English:
The BitBlt API...
BitBlt is a function in the DLL "gdi32".
- Technical Definition: it performs a bit-block transfer of
the color data corresponding to a rectangle of pixels from the
specified source device context into a destination device context. In
Plain English... This basically means that it copys graphical data from
one graphics surface (a bitmap) to another graphics surface (the
screen, or a form).
Now lets take a look at the API declaration itself... The API
declaration should be placed in the "General Declarations" section of a
form or module. Here's what it looks like: - Declare Function BitBlt Lib "gdi32" Alias "BitBlt" _
- (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
The first part of this code, the first line (in this example) says that
we're accessing the BitBlt function from the gdi32 DLL. the following
lines are parameters that we have to input in order to use the function
in our program. Here's a rundown of what each of these parameters is:
- hDestDC - The hDC of the destination surface (this could
be a form.hDC if you want to blit to a form, or it could be the address
of a backbuffer that we've created).
- x - The X (horizontal position) coordinate of where we want the graphic to appear.
- y - The Y (vertical position) coordinate of where we want the graphic to appear.
- nWidth - The width of our graphic.
- nHeight - The width of our graphic.
- hSrcDC - The hDC of the source graphic, for example the DC address of a sprite that we loaded into memory.
- xSrc - The X (horizontal) offset, 0 if you want to blit
from the very left edge of the source graphic, if you want to
start the blit from 18 and over then you would make this value 18, etc.
- ySrc - The Y (vertical) offset, same idea as xSrc, except vertically
- dwRop - The drawmode we want to use when blitting our
graphic, also known as Raster Operations or ROPs. This parameter is
explained below.
The last parameter - dwRop - determines how the graphic is drawn, often called the drawmode... Drawmodes,
or Raster Operations/ROPs available are as follows, each of these is a
reserved constant in VB, so any one of these words (in italic) can be
used in the dwRop parameter to acheive different effects
- vbSrcCopy - Copy the source image data directly onto the destination, replacing it completely.
- vbSrcPaint - ORs the source and destination image data, giving a pseudo-alphablending effect.
- vbSrcAnd - ANDs the source and destination image data, giving a pseudo-gamma effect.
- vbSrcInvert - XORs the source and destination image data.
- vbSrcErase - Inverts the destination image data then ANDs with the source image data.
- vbDstInvert - Inverts the destination image data, and ignores the source image data completely.
- vbNotSrcCopy - Inverts the source image data and copies directly onto the destination, replacing it completely.
- vbNotSrcErase - ORs the source and destination image data and inverts the result.
- 'An example of using BitBlt
- BitBlt Form1.hDC, PlayerX, PlayerY, 48, 48, picPlayer.hDC, 0, 0, vbSrcCopy
On with our example project... Next in
our example project (this is the final part), we're going to use BitBlt
in a loop much like you would in a game. Here's what you need to
do:
- Save the project file (and form file) in its own Directory.
- Create a bitmap (BMP) file sprite1.bmp, make it 32 X 32 pixels. And save it in the same directory as the project.
- Create a command button, rename it to cmdTest.
- Move the command button to the bottom right of the form.
- Double click on the command button to bring-up its sub in the
code-window, so we can enter code to be executed when it is pushed.
Copy and paste this code into the command button's Click-Event subroutine.
READ ALL THE COMMENTS, to understand the code... - '=== THIS CODE GOES IN CMDTEXT_CLICK EVENT ===
- 'Timer variables...
- Dim T1 As Long, T2 As Long
- 'create a compatable DC for the back buffer..
- myBackBuffer = CreateCompatibleDC(GetDC(0))
- 'create a compatible bitmap surface for the DC
- 'that is the size of our form.. (320 X 256)
- 'NOTE - the bitmap will act as the actua ' l graphics surface inside the DC
- 'because without a bitmap in the DC, the ' DC cannot hold graphical data..
- myBufferBMP = CreateCompatibleBitmap(GetDC(0), 320, 256)
- 'final step of making the back buffer...
- 'load our created blank bitmap surface into our buffer
- '(this will be used as our canvas to draw-on off screen)
- SelectObject myBackBuffer, myBufferBMP
- 'before we can blit to the buffer, we should fill it with black
- BitBlt myBackBuffer, 0, 0, 320, 256, 0, 0, 0, vbWhiteness
- 'load our sprite (using the function we made)
- mySprite = LoadGraphicDC(App.Path & "sprite1.bmp")
- 'MsgBox Dir$(App.Path & "sprite1.bmp")
- 'ok now all the graphics are loaded so
- 'lets start our main loop..
- 'Disable cmdTest, because if the graphics are
- 'reloaded there will be memory leaks...
- cmdTest.Enabled = False
- '== START MAIN LOOP ==
- 'get current tickcount (this is used as a code timer)
- T2 = GetTickCount
- Do
- DoEvents 'DoEvents makes sure that our mouse and keyboard dont freeze-up
- T1 = GetTickCount
- 'if 15MS has gone by, execute our next frame
- If (T1 - T2) >= 15 Then
- 'clear the place where the sprite used to be...
- '(we do this by filling in the old sprites place
- 'with black... but in games you'll probably have
- 'a background tile that you would blit here)
- BitBlt myBackBuffer, SpriteX - 1, SpriteY - 1, _
- 32, 32, 0, 0, 0, vbBlackness
- 'blit sprites to the back-buffer ***
- 'You could blit multiple sprites to the ' backbuffer,
- 'but in our example we only blit on...
- BitBlt myBackBuffer, SpriteX, SpriteY, 32, 32, _
- mySprite, 0, 0, vbSrcCopy
- 'now blit the backbuffer to the form...
- BitBlt Me.hdc, 0, 0, 320, 256, myBackBuffer, _
- 0, 0, vbSrcCopy
- 'move our sprite down on a diagonal...
- 'Me.Caption = SpriteX & ", " & SpriteY
- SpriteX = SpriteX + 1
- SpriteY = SpriteY + 1
- 'update timer
- T2 = GetTickCount
- End If
- 'loop it until our sprite is off the screen...
- Loop Until SpriteX = 320
- 'end of copy-paste here...
DONT RUN THE PROGRAM YET, we need to write the cleanup code...The
cleanup code is just some code that we add that clears the memory that
was occupied by the graphics that we loaded, and the backbuffer that we
created (see above code). This code should usually go in the
Form_Unload event, so that it is executed when the form
unloads... Copy and Paste the code below into the form's module - Private Sub Form_Unload(Cancel As Integer)
- 'this clears up the memory we used to hold
- 'the graphics and the buffers we made
- 'Delete the bitmap surface that was in the backbuffer
- DeleteObject myBufferBMP
- 'Delete the backbuffer HDC
- DeleteDC myBackBuffer
- 'Delete the Sprite/Graphic HDC
- DeleteDC mySprite
- End Sub
- 'end of copy-paste here...
That's it! We're
done! Run the program, click the button and you will see the sprite
move from the top-left of the form to the bottom right, without any
flickering...Click Here to download completed project code
13 comments
|
Tutorial Console
Tutorial by:
Tim Miron
Date: 2004 Apr 18
13 comments
Latest comment by: ocobot
hi, how can i use the bitbtl api to load an array to a picturebox?
sorry i don´t speak english, but i will try to understand your answer
thanks
alberto
Post a Comment
Printer Friendly
|
|