First off, let me give credit to Adam Hoult (wherever he may be!) for first bringing this API call to my attention. Adam, I worship you :)
Now, 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
Ok, yes, I know that VB already has a vbSrcCopy constant, but I feel safer declaring my own, ok? Shutup.
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! (Classy Lucky.. classy.)
How do you get this raw data and BITMAPINFO, you ask? Cut and paste my 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. Use it well :) 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 my sample source.
And for my next trick...