DirectX:DirectDraw:Tutorials:VB:DX7:Loading Surfaces from Custom Resource Files
Allow me introduce you to your new best friend, StretchDIBits:
Declare Function StretchDIBits Lib "gdi32" (ByVal hdc As Long, ByVal x As Long, _
ByVal y As Long, ByVal dx As Long, ByVal dy As Long, ByVal SrcX As Long, ByVal _ SrcY As Long, ByVal wSrcWidth As Long, ByVal wSrcHeight As Long, lpBits As Any, _ lpBitsInfo As BITMAPINFO, ByVal wUsage As Long, ByVal dwRop As Long) As Long
And his sidekicks..
Global Const SRCCOPY = &HCC0020
Global Const DIB_RGB_COLORS = 0
As you can see by examining the StretchDIBits API call, it accepts an hDC, a bunch of X and Y values, a Long pointer to your raw bitmap data, a Long pointer to a BITMAPINFO structure, and a few flags. It will take the raw data and BITMAPINFO you provide it, stretch it according to the source and destination coordinates you pass, and blit it to the DC you give. Nifty, eh? This can be used with pictureboxes (they have DCs) as well as DirectDraw surfaces. With DDraw, you must use the DirectDrawSurface7.GetDC method to obtain the DC, and DirectDrawSurface7.ReleaseDC afterward to release it. The releasing is VERY important.. go ahead, leave it unreleased and watch your computer barf Automation Errors all over you!
How do you get this raw data and BITMAPINFO, you ask? Cut and paste these functions, that's how:
'Bitmap file format structures
bfType As Integer bfSize As Long bfReserved1 As Integer bfReserved2 As Integer bfOffBits As Long
End Type Type BITMAPINFOHEADER
biSize As Long biWidth As Long biHeight As Long biPlanes As Integer biBitCount As Integer biCompression As Long biSizeImage As Long biXPelsPerMeter As Long biYPelsPerMeter As Long biClrUsed As Long biClrImportant As Long
End Type Type RGBQUAD
rgbBlue As Byte rgbGreen As Byte rgbRed As Byte rgbReserved As Byte
End Type Type BITMAPINFO
bmiHeader As BITMAPINFOHEADER bmiColors(0 To 255) As RGBQUAD
Global gudtBMPFileHeader As BITMAPFILEHEADER 'Holds the file header Global gudtBMPInfo As BITMAPINFO 'Holds the bitmap info Global gudtBMPData() As Byte 'Holds the pixel data
Sub ExtractData(strFileName As String, lngOffset As Long)
Dim intBMPFile As Integer Dim i As Integer
'Init variables Erase gudtBMPInfo.bmiColors 'Open the bitmap intBMPFile = FreeFile() Open strFileName For Binary Access Read Lock Write As intBMPFile 'Fill the File Header structure Get intBMPFile, lngOffset, gudtBMPFileHeader 'Fill the Info structure Get intBMPFile, , gudtBMPInfo.bmiHeader If gudtBMPInfo.bmiHeader.biClrUsed <> 0 Then For i = 0 To gudtBMPInfo.bmiHeader.biClrUsed - 1 Get intBMPFile, , gudtBMPInfo.bmiColors(i).rgbBlue Get intBMPFile, , gudtBMPInfo.bmiColors(i).rgbGreen Get intBMPFile, , gudtBMPInfo.bmiColors(i).rgbRed Get intBMPFile, , gudtBMPInfo.bmiColors(i).rgbReserved Next i ElseIf gudtBMPInfo.bmiHeader.biBitCount = 8 Then Get intBMPFile, , gudtBMPInfo.bmiColors End If 'Size the BMPData array If gudtBMPInfo.bmiHeader.biBitCount = 8 Then ReDim gudtBMPData(FileSize(gudtBMPInfo.bmiHeader.biWidth, gudtBMPInfo.bmiHeader.biHeight)) Else ReDim gudtBMPData(gudtBMPInfo.bmiHeader.biSizeImage - 1) End If 'Fill the BMPData array Get intBMPFile, , gudtBMPData 'Ensure info is correct If gudtBMPInfo.bmiHeader.biBitCount = 8 Then gudtBMPFileHeader.bfOffBits = 1078 gudtBMPInfo.bmiHeader.biSizeImage = FileSize(gudtBMPInfo.bmiHeader.biWidth, gudtBMPInfo.bmiHeader.biHeight) gudtBMPInfo.bmiHeader.biClrUsed = 0 gudtBMPInfo.bmiHeader.biClrImportant = 0 gudtBMPInfo.bmiHeader.biXPelsPerMeter = 0 gudtBMPInfo.bmiHeader.biYPelsPerMeter = 0 End If Close intBMPFile
Private Function FileSize(lngWidth As Long, lngHeight As Long) As Long
'Return the size of the image portion of the bitmap If lngWidth Mod 4 > 0 Then FileSize = ((lngWidth \ 4) + 1) * 4 * lngHeight - 1 Else FileSize = lngWidth * lngHeight - 1 End If
Hm... this'll take some explaining! Lets start at the top. The structures you see declared are the standard components of any bitmap file. They are used in the ExtractData subroutine to piece together the bitmap from within the binary file.
ExtractData accepts two arguments, the first (strFileName) is the name of the file we'll be extracting from, the second (lngOffset) is the offset within the file where our desired bitmap's data begins. This function can be used on custom binary resources AND standard bitmap files, it can't really differentiate between the two so long as you pass the correct offset (lngOffset = 1 for standard bitmaps).
Once we have these two pieces of information, we can go ahead and open up the file and extract the gudtBMPFileHeader information from the lngOffset location given. Next, gudtBMPInfo.bmiHeader is extracted in a similar fashion, but after that things get dicey. We'd like to obtain the colour table data (if this is an 8bit bitmap), but not all programs format this data in the same fashion. Some will store only a set number of entries, as indicated by the gudtBMPInfo.bmiHeader.biClrUsed variable. If this value is zero however, we can assume a full complement (256, if 8bit) of colours and can extract merrily. Otherwise we have to loop through each value and extract it manually :(
Next on the agenda is the raw bitmap data. We must size our gudtBMPData byte array appropriately, and this can be tricky with 8 bit bitmaps. You see, ALL bitmap scan lines (horizontal lines of pixels) must end on a 32bit boundary, so they are sometimes padded with blank bits. To account for this quirk, I created the FileSize function that'll calculate a bitmap's true size based on its percieved width and height. 24bit bitmaps don't give us this trouble, and for them we can assume that the gudtBMPInfo.bmiHeader.biSizeImage value is accurate.
All that remains is to Get our gbytBMPData array, and voila! We have a bitmap in memory! You may notice I perform a few other functions on 8bit bitmaps after the final Get. This is simply to correct for those silly programs that create 8bit bitmaps with fewer than 256 colours. Such bitmaps are slightly slower to load, so my function modifies them so they are of the faster format.
The hard work is done! We can reap the rewards:
StretchDIBits lngDC, 0, 0, gudtBMPInfo.bmiHeader.biWidth, gudtBMPInfo.bmiHeader.biHeight, _
0, 0, gudtBMPInfo.bmiHeader.biWidth, gudtBMPInfo.bmiHeader.biHeight, gudtBMPData(0), _ gudtBMPInfo, DIB_RGB_COLORS, SRCCOPY
Get your DC (from your DDraw surface or picturebox), place it in lngDC and the above line of code will handle the rest. It blits (with no stretching) the bitmap you've loaded into memory onto the DC you've passed it! No more DirectDraw7.CreateSurfaceFromFile for you!
If you are thoroughly confused, check out this sample source code.
ADVANCED: If you wish to load the resources straight out of memory (when the bitmap is held in a byte array) then you may find this modified source code helpful.