DirectX:DirectDraw:Tutorials:VB:DX7:Loading Surfaces from Custom Resource Files

From GPWiki
Jump to: navigation, search

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 Type BITMAPFILEHEADER

   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

End Type

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

End Sub

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

End Function

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.