Loading palettes
Let’s go back to the LoadBitmapPalette function. It illustrates two ways of retrieving resources. One is to ask Windows to decode the resource. The other is to decode it yourself. Here’s the first part where the data is gathered:
Function LoadBitmapPalette(ByVal hMod As Long, vResource As Variant, _
hPal As Long) As Long
‘ Make null in case of failure
Dim hBmp As Long
hPal = hNull
Dim hRes As Long, hmemRes As Long, cRes As Long
Dim pRes As Long, abRes() As Byte
If VarType(vResource) = vbString Then
hBmp = LoadImage(hMod, CStr(vResource), IMAGE_BITMAP, _
0, 0, LR_CREATEDIBSECTION)
hRes = FindResourceStrId(hMod, CStr(vResource), RT_BITMAP)
Else
hBmp = LoadImageID(hMod, CLng(vResource), IMAGE_BITMAP, _
0, 0, LR_CREATEDIBSECTION)
hRes = FindResourceIdId(hMod, CLng(vResource), RT_BITMAP)
End If
‘ If bitmap found, return it
If hBmp = hNull Then Exit Function
LoadBitmapPalette = hBmp
§
In the Windows API, resource names and resource IDs are handled with string pointers that can be numeric or string, as described in “Resource callbacks,” page 447. Since this is a Visual Basic function, a Variant parameter can accomplish the same thing. The hPal parameter is received by reference so that it can return the new palette. You have to initialize it to 0. All you do to get the bitmap is call LoadImage. Windows does all the work.
The palette is a different matter. You have to get the bitmap data using techniques similar to the ones shown earlier for ShowData. The first step is to call the appropriate alias for the FindResource API. It returns a handle to a resource, which you process like this:
BugAssert hRes <> hNull ‘ Shouldn’t fail here
‘ Allocate memory block, and get its size
hmemRes = LoadResource(hMod, hRes)
cRes = SizeofResource(hMod, hRes)
‘ Lock it to get pointer
pRes = LockResource(hmemRes)
Dim bmpi As BITMAPINFO256
If cRes > LenB(bmpi) Then cRes = LenB(bmpi)
‘ Copy memory block to array
CopyMemory bmpi, ByVal pRes, cRes
‘ Free resource (no need to unlock)
Call FreeResource(hmemRes)
Dim lpal As LOGPALETTE256, cColors As Long, cBits As Long, i As Long
cColors = bmpi.bmiHeader.biClrUsed
cBits = bmpi.bmiHeader.biBitCount
‘ Like VB, we only return 256-color palettes
If cBits <> 8 Then Exit Function
If cColors = 0 Then cColors = 256
‘ RGBQUAD in BITMAPINFO has different format from PALETTEENTRY
‘ in LOGPALETTE, so can’t use CopyMemory
For i = 0 To cColors - 1
‘ Copy and translate colors
lpal.palPalEntry(i).peRed = bmpi.bmiColors(i).rgbRed
lpal.palPalEntry(i).peGreen = bmpi.bmiColors(i).rgbGreen
lpal.palPalEntry(i).peBlue = bmpi.bmiColors(i).rgbBlue
lpal.palPalEntry(i).peFlags = 0
Next
lpal.palNumEntries = cColors
lpal.palVersion = &H300
‘ Create and return the palette through a reference
hPal = CreatePalette(lpal)
End Function
First convert the resource to a pointer, and then copy part of it to a structure called a BITMAPINFO256. If you search Windows documentation, you won’t find any such structure. Instead, you’ll find a BITMAPINFO and a BITMAPINFOHEADER. A BITMAPINFO looks like this in C:
typedef struct tagBITMAPINFO {
BITMAPINFOHEADER bmiHeader;
RGBQUAD bmiColors[1];
} BITMAPINFO;
There’s a fixed size BITMAPINFOHEADER member, followed by what looks like an array of one RGBQUAD. In fact, the bmiColors field represents a C trick for representing a dynamic array. The array might actually contain 0, 2, 16, or 256 entries. The C language provides a way of typecasting and allocating such arrays at run time, depending on the appropriate size, but Visual Basic provides no such thing and a BITMAPINFO UDT that translated literally would be completely worthless. Instead, I define the BITMAPINFO256 UDT to be the maximum (and in this case the most commonly used) version. It’s actually in the Windows API type library, but, written in Visual Basic, it would look like this:
Type BITMAPINFO256
bmiHeader As BITMAPINFOHEADER
bmiColors(0 To 255) As RGBQUAD
End Type
Whenever you use a dynamic array, you have to specify someplace in the call (in a separate parameter or a UDT field) what the actual size is. If I needed to use this structure for a smaller array, I could specify a smaller size. The extra memory would be wasted but would do little harm, especially if it was local data allocated on the stack. In this particular case, the code is written to read only 256-color palettes.
The resource actually contains the bits of the bitmap after the color array, but we don’t care about them. We’ve already got the bitmap handle with LoadImage and don’t need to prove that we could get the whole bitmap from raw data (although it wouldn’t be much harder).
The biClrUsed field of the BITMAPINFOHEADER UDT returns the number of colors used in a bitmap resource, or 0 if the bitmap is rude enough to use the whole palette (256 colors). Either way, we simply loop through all the color entries and convert them to the slightly different format of the LOGPALETTE (logical palette) type. This type follows the same pattern—it is dynamically sized, but my LOGPALETTE256 version has 256 entries. We might not use them all, but the palNumEntries field tells how many are used. The CreatePalette function converts the logical palette array into a palette handle.
Now you might wonder (as I did) why there aren’t any API calls to just look up the palette handle from the bitmap handle. After all, it is in there. Well, the problem is that a bitmap always stores its palette in the size handled by the current device context. That means that a 44-color logical palette usually gets loaded into a 256-color palette. Most of those colors are black, but there is no trace left of the original size stored in the bitmap file or resource. In fact, if you load a 44-color bitmap with Paint and use the Save As command to save a copy, you’ll end up with a 256-color bitmap. Actually, it is possible to algorithmically generate a palette from a bitmap handle by walking through the 256-color palette and eliminating all the black colors. The GetBitmapPalette function does this, and it usually gets close, if not always exact. Check it out in PALTOOL.BAS.