Author: Adam Hoult
In this tutorial we will cover the basics on how to load and display an image on our newly opened DirectDraw screen, created in the tutorial Opening a DirectDraw Device. For this example we will build on that code and add just a few lines of code to display a background picture. We will assume you already know how to create a project in VB, and know a little about using forms. You will also need to have gone through the last tutorial because we will only add to that, we will not create a whole new application. Now onto the tutorial. NOTE: This tutorial is designed for those of you with VB5. If you find any discrepancies with this Tutorial, please mail me and I will correct it. NOTE: the decriptions of the various pieces of source code appear underneath it. So don't get mixed up with the various explanations.
Step 1 - Loading the Project
First thing you will need to do is to load the source code from the last tutorial.
Step 2 - General Declarations and What They Mean
Ok. For this next tutorial we will need to add a load of new General Declarations, and a few API calls. These API calls basically let us load in the image, and place it onto a DirectX Surface. For those of you who dont know what a surface is, it's an area of memory (normally system memory) which stores an image, which we can then BLT (or copy) to the screen. Here is a list of all the new declarations we need to make, and what they all mean.
'Standard Consts which need to be assigned
'for the API calls to work.
Const LR_LOADFROMFILE = &H10
Const LR_CREATEDIBSECTION = &H2000
Const SRCCOPY = &HCC0020
These constants are assigned to be used by the API calls which you will find below. The LR_LOADFROMFILE and LR_CREATEDIBSECTION constants are used by the LoadImage API call. They basically specify that we want to load the Image from a file, and store it in memory. The SRCCOPY constant is used by the StretchBlt API call in this tutorial, and what it basically means is that the image will be copied from the Source (in this case a handle pointing to an area of memory) and copied to the Specified Destination Surface. You will see more about this later in the tutorial.
'This structure is needed for the LoadImage
'API call, the information about the bitmap
'will be returned into a variable defined as
Private Type BITMAP
bmType As Long
bmWidth As Long
bmHeight As Long
bmWidthBytes As Long
bmPlanes As Integer
bmBitsPixel As Integer
bmBits As Long
When we load an image using the LoadImage API call we pass it a variable, defined as a BITMAP type. The bitmap must be defined as above. If the call to the LoadImage function is successful, all the information about the bitmap will be returned into this variable. The only bits we will be interested in during this tutorial will be; Bitmap Width (bmWidth) and Bitmap Height (bmHeight). These will store the width and height of the bitmap which we load.
NOTE: In these various functions you may see that a line will be split into two with a "_" symbol. This will work in Visual Basic, but you may want to remove them. Simply delete the Underscore and move the second line onto the first. The reason why I have done this is because internet Explorer will wrap the text, and it starts to look confusing.
' Various Standard API Functions for
manipulation of DCs and Bitmaps
Private Declare Function CreateCompatibleDC Lib "gdi32" (ByVal hdc _
As Long) As Long
Private Declare Function DeleteDC Lib "gdi32" (ByVal hdc As Long) _
Private Declare Function DeleteObject Lib "gdi32" (ByVal hObject _
As Long) As Long
Private Declare Function GetObject Lib "gdi32" Alias "GetObjectA" _
(ByVal hObject As Long, ByVal nCount As Long, _
lpObject As Any) As Long
Private Declare Function SelectObject Lib "gdi32" (ByVal hdc As Long, _
ByVal hObject As Long) As Long
Private Declare Function GetDC Lib "user32" (ByVal hwnd As Long) _
Private Declare Function LoadImage Lib "user32" Alias "LoadImageA" _
(ByVal hInst As Long, ByVal lpsz As String, _
ByVal un1 As Long, ByVal n1 As Long, ByVal n2 _
As Long, ByVal un2 As Long) As Long
Private Declare Function StretchBlt Lib "gdi32" (ByVal hdc 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 nSrcWidth As Long, _
ByVal nSrcHeight As Long, ByVal dwRop As Long) _
I won't explain what all these API functions do in this section, just copy them in as you see them, and I will explain them in the sections that use them.
Dim BackDrop As DirectDrawSurface2 'BackGround Picture
Next we need to Dimension a new surface, we will use this surface to store the picture that we are going to display, for this example we will load and display a 640x480 background picture. We will load the picture into this surface later on in the tutorial.
'Used to store the size of the picture so that when
'we Blt it to the surface, we can specify how big the
Dim BackDropRect As RECT
Once we have loaded the picture we need to store its dimensions. To do this we need to create a RECT object. This object has the following properties; Left , Right, Top and Bottom. We will set these values once we load the bitmap. The reason why we have to set these RECT objects, is because the BltFast function only accepts this as a valid set of coordinates. I will explain more underneath the BltFast function in Form_Load.
Step 3 - On Form Load
' Attach the surface ddsBack to the
' Primary surface as the back buffer.
ddsFront.GetAttachedSurface ddCaps, ddsBack
' Show the form
Do you remember in the last Tutorial, near the bottom of the Form_Load we attached the Back Buffer to the Primary Surface, and loaded the form. You can see that above this explanation. Well in between those two functions, we now need to load in the picture to our BackDrop Surface, which we dimensioned earlier. So insert the following code between the two above functions.
'Load in the bitmap and paste it onto the
'BackDrop surface we specified earlier.
Set BackDrop = LoadBitmapIntoDXS(dd, App.Path & "\background.bmp")
'Set the rect with the bitmap's dimensions this
'is used when we BLT the image.
BackDropRect.Left = 0
BackDropRect.Top = 0
BackDropRect.Right = 640
BackDropRect.bottom = 480
Now, as soon as we Attached the back buffer we now need to load in the picture, and place it onto our temporary surface. We do that as shown in the source code above. As there is no LoadBitmap function currently implemented into the Type Library, we need to do it manually. So we have to create a new function. (See Step 4 to create the LoadBitmapIntoDXS function). NOTE: for this particular example to work, your image must have the dimensions 640x480 and must be name background.bmp and placed in the same directory as the source code. As we dimensioned our BackDrop surface earlier we can now Set it using the LoadBitmapIntoDXS function, which will place the specified image onto the surface for use later. We need to pass this function two parameters. The first paramater is a valid Direct Draw Device, which we defined earlier. In this case it's the dd variable. The second of the parameters is a valid Windows Bitmap file. In this case we pass it App.Path & "\background.bmp" .This will pass it the directory where this program is running from, and the name of a file. NOTE: the App.Path function does not return a trailing slash. After we have loaded the BitMap, we need to store its dimensions (shown above) to pass to the Source Coordinates section of the BltFast function (Explained Later).
Do Until Running = False
' Get any key presses
'BLT what is on the BackDrop surface
'onto the back buffer.
ddsBack.BltFast 0, 0, BackDrop, BackDropRect, DDBLTFAST_WAIT
Remember our rendering loop ?? Well now we need to add somethings to it other then the DoEvents function. First thing we need to do is Copy the image from our temporary BackDrop surface, onto the back buffer. We do this by using the BltFast function. This function can be called from any surface, but you need to call it from the surface you want to copy the image TO. So an explanation of the BltFast function. The first and second paramters which we have to pass to this function are X and Y coordinates. These coordinates specify the position on the DESTINATION surface you want the Top Left point of the SOURCE image to be copied. The next parameter is the actual source surface you want copying to the surface calling the BltFast function. In this case we pass it our BackDrop surface. The third parameter is the RECT object which we defined earlier. This rect is the area of the Source image which will be copied. For example if we specified the following coordinates in our BackDropRect object; BackDropRect.Left = 0, BackDropRect.Top = 0, BackDropRect.Right = 100, BackDropRect.Bottom = 100, then a section of 100x100 would be copied from the Source Surface. The final parameter is the method of Copying. By specifiying DDBLTFAST_WAIT we are asking the system to wait until we have finished copying the image to the Destination Surface, before we let the program carry on executing.
'This next section flips
the back buffer
'and sends it to the front. We need to do
'a check to see if the surface has been lost
'if it has restore the area of memory assigned
On Error Resume Next
ddsFront.Flip Nothing, 0
If Err.Number = DDERR_SURFACELOST Then
ddsFront.Restore: Err.Number = 0
Loop Until Err.Number = 0
This section of the code, swaps what we rendered on the Back Buffer, and Flips it to the Primary surface. When we flip it, we need to check to make sure that the Surface Wasn't lost when it was flipped. If it was we need to restore it and flip it again. We need to keep repeating this until the flip is successful.
'Quit out now.
The final thing we need to add is the Unload Me piece of code. This has to be moved from the KeyPress function which we specified in the last tutorial. The reason for this is that , before, we weren't flipping the Back Buffer, so we could unload everything before we ended the rendering loop. If we leave the unloading code in the KeyPress function, the rendering loop will not have chance to exit out before we unload all the surfaces. This means that the program will get stuck in the Rendering loop constantly erroring, because the surface is LOST. Trust me, you need to do it even if you dont understand why :) .
Step 4 - New Function to Load The Image
As I explained above, we need to write a new function which will load in the image. This is the main reason why we need to specify all of those API calls in Step 2. In this section we will completely write a function to do this. To use it you will need to use some code similar to this. Set BackDrop = LoadBitmapIntoDXS(dd, App.Path & "\background.bmp"). I would advise you to just copy and paste the function from the Example Source Code which accompanies this tutorial, and just to use this section of the tutorial as a reference to the various functions. Remember that you only have to write this function once, and then whenever you want to load an image simply call this function. DO NOT write this code, everytime you want to load an image, that would be very silly :).
Private Function LoadBitmapIntoDXS(DXObject
As DirectDraw2, ByVal BMPFile As String) As DirectDrawSurface2
' You can reuse this function and modify it to your needs
' you will always need a function such as this to load bitmaps
' onto your surface, so if your a beginner to API calls etc
' you need pay little attention to them. They will rarely need
' to be changed.
Dim hBitmap As Long ' Stores the handle of the loaded bitmap
Dim dBitmap As BITMAP ' Stores all the info about the Bitmap
Dim TempDXD As DDSURFACEDESC ' Temporary surface description
Dim TempDXS As DirectDrawSurface2 ' Temporary surface, to render the bitmap.
Dim dcBitmap As Long ' Another handle to the image
Dim dcDXS As Long ' Handle to surface context
Dim ddck As DDCOLORKEY ' Colorkeys, We will look at these in the next tutorial.
'For now set the color keys to black
'We will use these properly in the tutorial
'Displaying Transparent Images.
ddck.dwColorSpaceLowValue = 0
ddck.dwColorSpaceHighValue = 0
I think that these are mainly explained as well as they need to be, just look at the comments to see what they mean. The colorkey section will be explained in the next tutorial.
' Load the bitmap using standard API calls.
hBitmap = LoadImage(ByVal 0&, BMPFile, 0, 0, 0, LR_LOADFROMFILE Or LR_CREATEDIBSECTION)
The LoadImage function, will load the file (which passed to this function via the BMPFile parameter), and return the handle to that image. We will then use the handle for the various functions below. These parameters will mainly always stay the same, the only parameters you may need some explanation, are the following. The second parameter must be passed the Path and Filename of the image you wish to load. The sixth parameter will really always need to be LR_LOADFROMFILE Or LR_CREATEDIBSECTION. These parameters specify that you want to load the image from the file (specified in the second parameter) and that you want it to store the picture in the format we need it in.
' Get bitmap information
GetObject hBitmap, Len(dBitmap), dBitmap
This piece of code will return all the information about the bitmap we just loaded, and store it into the dBitmap object. We dimensioned the dBitmap object earlier on this function, and defined it as being a BITMAP type. We defined the structure of this type in Step 2. The first parameter is the handle of the bitmap which was loaded. This is the handle which was returned by the LoadImage function. The second parameter is the Length of the dBitmap object, this is just so the GetObject function knows how many bytes of memory to read, when it is retrieving the information it needs. The final parameter is the actual object that we want the information to be stored. In this case it is the dBitmap object.
' Fill in DX surface description
'(see the tutorial for more info on this)
.dwSize = Len(TempDXD)
.dwFlags = DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH
.DDSCAPS.dwCaps = DDSCAPS_OFFSCREENPLAIN Or DDSCAPS_SYSTEMMEMORY
.dwWidth = dBitmap.bmWidth
.dwHeight = dBitmap.bmHeight
I think an explanation of all of these flags etc are in order. This is how we specify what type of surface we want to create (also explained in The Opening a DirectDraw device tutorial). The dwSize property is fairly simple all we need to pass it here, is the size of the our surfaces description object. In this case we are using TempDXD which was defined earlier on in this function. Next is the dwFlags property. To create this type of surface (i.e one which just stores an image, and isn't a BackBuffer or Primary surface) we only need to specify the DDSD_CAPS Or DDSD_HEIGHT Or DDSD_WIDTH constants. These basically mean that only the DDSCAPS, dwWidth and dwHeight properties will be read when creating the surface. Next is the DDSCAPS property. This is where we specify what type this surface will be, again for this type of surface we need to specify DDSCAPS_OFFSCREENPLAIN Or DDSCAPS_SYSTEMMEMORY. These basically mean that the surface will be a standard surface (not a BackBuffer or Primary surface) and that it will be stored in system memory not Video memory (otherwise you would need a 32meg graphics card ). In the dwWidth and dwHeight properties, we need to specify how big this surface will be. In this case we pass it the dimensions of the Bitmap which we loaded earlier. If you didn't read the section about loading an image (just above this one), then your naughty, go read it.
' Create Temporary DX surface to render too
DXObject.CreateSurface TempDXD, TempDXS, Nothing
Again, here is where we create this surface, we need to pass it the Description object (which we have just defined) and the actual surface, which will be created. Note: this surface must be already defined, this function simply allocates the area of memory to that surface. We dimensioned it at the top of this function.
' Create API memory DC
dcBitmap = CreateCompatibleDC(ByVal 0&)
This section and the next ones will handle the actual copying of the Loaded image and place them onto the surface. This piece of code, creates a compatible Device Context (i'm not sure what that exactly means but it sounds good, and you have to do it :). We need this so that we can copy the image data and select it.
' Select the bitmap into API memory DC
SelectObject dcBitmap, hBitmap
This function will select the data stored in the handle which was returned from the LoadImage function above, and will store this selection in the dcBitmap object which we created above.
' Restore the memory for the temporary
' assign the bitmaps dc to the surface dc
' Blit BMP from API DC into DX DC using standard API BitBlt
StretchBlt dcDXS, 0, 0, TempDXD.dwWidth, TempDXD.dwHeight, dcBitmap, 0, 0, dBitmap.bmWidth, dBitmap.bmHeight, SRCCOPY
These are the final pieces of code which handle the final copy of the Loaded image onto the temporary surface. First of all we restore the area of memory allocated to our temporary surface, just in case it was lost. Next we need to tell the Temporary Surface, that whatever is placed into our Temporary Device context will also be what is copied to itself (this is the best way i could describe what its doing in simple terms.). We then do the final copy by using the StretchBlt function (i know you could use the BitBlt function, but this makes it easier if you want to stretch a bitmap to fit a bigger surface). The first parameter we need to pass it is the temporary Device Context (dcDXS) which we also assigned to our Surface (TempDXS). These were both defined and created earlier. The next two are the X and Y coordinates of the destination Surface (or in this case Device Context). These specify the Top Left hand corner where the Image will be copied. The next two are the Width and Height of the destination surface (i.e how big is the surface we are copying to). The next parameter is the Source Surface ( or Device Context) which we are copying from. Again the next two specify the Top Left Corner of the Source Image. The next two parameters specify the width and height of the Source bitmap. and the final parameter just specifies that we want to Copy The source image to the Destination one. I know that this is a bit sketchy, but you will rarely need to change this function. If you really do want a better explanation please feel free to e-mail me and I will be happy to go through it with you.
'Again we will show this later in the next
TempDXS.SetColorKey DDCKEY_SRCBLT, ddck
No need to worry about this yet, It will be explained in the next tutorial.
' Return created DX surface
Set LoadBitmapIntoDXS = TempDXS
Next we need to pass the Temporary surface in which we were doing all the rendering, back to whatever was calling it. We do this by using the above code. NOTE: you must use the Set command, otherwise it will fail.
Set TempDXS = Nothing
Finally we finish off by freeing all the memory we used, and returning control of certain Device Contexts back to the operating system.
Step 5 - Did We Press Escape ??
Private Sub Form_KeyDown(KeyCode As
Integer, Shift As Integer)
'When we press escape, Stop the rendering loop
'(rendering loop is in Form_Load) NOTE: in this example
'we have to remove the Unload Me line and move it
'to underneath the rendering loop, otherwise we cause
'an error and the program will not quit.
If KeyCode = vbKeyEscape Then
Running = False
Basically, every time a key is pressed, this Sub is called by the program. We do a quick check to see if the Key which was pressed was escape. vbKeyEscape is a Constant defined within VB5, a list of all the keycodes are shown in the Visual Basic Help. If we did press escape, we set the Running to False. This will end the Rendering Loop started in the Form_Load event. Note that we need to remove the Unload Me, which used to be here, and move it to underneath the rendering loop. I explained why in Step 3.
Step 6 - I Want to Exit, What Now?
Set BackDrop = Nothing
This is the only thing which we need to add to the Form_Unload function. We need to place it just before we unload the Back Buffer. This piece of code simply frees the memory and stack space which was allocated to this surface. Remember if we don't do this, we will start to lose memory, and you program may crash.
Step 7 - Closing Comments
Excellent, you got through loading an image and displaying it. Now you've done this, do you fancy loading in an image, and displaying it as transparent ?? We'll mosey on to the next tutorial and have a look. The results a quite cool. Thanks again for reading all the way through this (I know because you wouldnt be reading this other wise, Maybe).