Loading 256 Color Bitmaps Manually with Delphi
Posted: (EET/GMT+2)
The Delphi TBitmap object is great. It allows you to load your bitmaps, no matter where they are, and how they are coded. It handles 16 and 256 colors easily, and even allows you to access the individual pixels of the image. However, sometimes, when writing a small and efficient application, for example a screen saver, you don't necessarily want to bloat your application if all you want to do is display a 256 color bitmap.
In this technical How To you will learn how to correctly load and display a RGB coded 256 color bitmap. We assume that the image resides in the EXE file, ie. it has been linked in as a resource. Note also that the procedure presented here is not a general routine. It only accepts bitmaps which have exactly 256 colors defined and have no compression. For example, the routine crashes if you throw a 16 color bitmap at it.
In order to fully understand the code, you need to know a bit about 256 color bitmaps (that is, device independent bitmaps, or DIBs for short). They consist of three parts: header data, color (palette) information, and the actual bitmap bits. The header defines general information about the bitmap, such as its dimensions. Palette data defines the palette used by the bitmap, as simple RGB data. Bitmap bits are simply raw palette indexed data defining the actual image.
The code to load the bitmap is as follows:
Function LoadResourceBitmap(ResName : String; Var Palette : hPalette;
Var Width,Height : Integer) : hBitmap;
Var
Resource : hRsrc; { resource handle }
Memory : hGlobal; { memory for the bitmap }
InfoHeader : PBitmapInfoHeader;
DC : hDC; { device context used to create the bitmap }
BitmapBits : Pointer;
Begin
Resource := FindResource(hInstance,PChar(ResName),rt_Bitmap);
Memory := LoadResource(hInstance,Resource);
InfoHeader := PBitmapInfoHeader(LockResource(Memory));
Width := InfoHeader^.biWidth; Height := InfoHeader^.biHeight;
DC := GetDC(0);
Palette := CreateDIBPalette(PBitmapInfo(InfoHeader));
SelectPalette(DC,Palette,False);
RealizePalette(DC);
BitmapBits := Pointer(Integer(InfoHeader)+InfoHeader^.biSize+
256*SizeOf(TRGBQuad));
Result := CreateDIBitmap(DC,InfoHeader,cbm_Init,BitmapBits,
PBitmapInfo(InfoHeader),DIB_RGB_Colors);
ReleaseDC(0,DC);
UnlockResource(Memory);
FreeResource(Memory);
End;
Here, we first use the Win32 API functions FindResource and LoadResource to load our bitmap from the EXE file. (I'll will tell you later how to link your bitmap to the EXE.) LockResource locks the bitmap into memory, and returns a pointer to the bitmap data.
We cast this data to the type TBitmapInfoHeader, which is the record structure defining the bitmap header. As noted above, the header contains for example the width and height of the bitmap. After the header comes the palette data, which is handled by our CreateDIBPalette function (defined later). When we have the palette handle, we simply select and realize it to a temporary device context (DC), and the call the API function CreateDIBitmap. After this, we simply release the DC and memory, and we are done.
Look also at the line that calculates the location of the actual bitmap bits in memory. We do a rather simple typecast, first to integers and the back to a pointer. In flat memory addressing of Win32 this is pretty easy!
Note that if you try to compile the above code fragment, you will get a type mismatch error on the CreateDIBitmap call. This is because the original function (defined in the Windows unit) uses Var parameters. For our purposes this isn't a good thing because we use pointers. Instead, we redefine the API function to use pointers. This works because, internally, Var parameters are passed in as ordinary pointers.
Function CreateDIBitmap(DC: HDC; InfoHeader: PBitmapInfoHeader;
dwUsage: DWORD; InitBits: PChar;
InitInfo: PBitmapInfo;
wUsage: UINT): HBITMAP; StdCall;
External GDI32 Name 'CreateDIBitmap';
Now back to the bitmap palette. The CreateDIBPalette is as follows:
Function CreateDIBPalette(BMI : PBitmapInfo) : hPalette;
Const PaletteDataSize = SizeOf(TLogPalette)+(255*SizeOf(TPaletteEntry));
Var
Palette : PLogPalette;
I : Integer;
Begin
GetMem(Palette,PaletteDataSize);
With Palette^ do Begin
palVersion := $300;
palNumEntries := 256;
For I := 0 to 255 do Begin
{$R-}
palPalEntry[I].peRed := BMI^.bmiColors[I].rgbRed;
palPalEntry[I].peGreen := BMI^.bmiColors[I].rgbGreen;
palPalEntry[I].peBlue := BMI^.bmiColors[I].rgbBlue;
palPalEntry[I].peFlags := 0;
{$R+}
End;
End;
Result := CreatePalette(Palette^);
FreeMem(Palette,PaletteDataSize);
End;
Here, the BMI parameter passed to the function points to the beginning of the bitmap data. Note that we had to do a pointer typecast from PBitmapInfoHeader to PBitmapInfo. This really is safe, because the TBitmapInfo simply has a TBitmapInfoHeader record as its first field.
When we have a valid pointer, we can simply start to create the palette data. First, we allocate the correct amount of memory for 256 palette entries. Next, we simply loop through all the palette entries. Here you should note two things. Firstly, we can't do a simple memory copy, because, sadly enough, the record structures differ in order of color definition (other uses RGB format, other BGR). Also, I have used the $R compiler directive. This is because the TLogPalette defines the palette data as array [0..0]. If the range checking was on, we would get a run-time error. $R- avoids this.
After we have the palette array defined, we simply use an API call to get a handle to the palette.
After this, everything is ready and we can simply draw to bitmap to the screen. This is quite simple:
procedure TForm1.Button1Click(Sender: TObject);
Var
ScreenDC : hDC;
MemDC : hDC;
BitmapHandle : hBitmap;
OldBitmap : hBitmap;
PaletteHandle : hPalette;
W,H : Integer;
Begin
BitmapHandle := LoadResourceBitmap('MYBITMAP',PaletteHandle,W,H);
ScreenDC := PaintBox1.Canvas.Handle;
MemDC := CreateCompatibleDC(ScreenDC);
SelectPalette(ScreenDC,PaletteHandle,False);
RealizePalette(ScreenDC); { force screen to use our palette }
SelectPalette(MemDC,PaletteHandle,False);
RealizePalette(MemDC); { memory DC must also use same palette }
OldBitmap := SelectObject(MemDC,BitmapHandle);
BitBlt(ScreenDC,0,0,W,H,MemDC,0,0,SrcCopy); { draw to screen }
DeleteObject(SelectObject(MemDC,OldBitmap));
DeleteDC(MemDC); { no need to release our screen DC }
DeleteObject(PaletteHandle);
end;
Here, we simply use our function to load the bitmap and get the palette for it. Next, we simply create the necessary DCs, realize the palette to both the screen and the memory DC (this is important), and the use BitBlt to draw it. After this, we only need to do some cleanup, and we're done!
Note that I won't describe all the API calls used. I just assume that you know them. Or, if you don't, just consult your Win32 API help file. The information is all there - and it's free ;-)
One simple tip still. You might now wonder how to link your bitmap into your EXE. This is done with the the $R compiler directive (no, not the range check directive!). For example:
{$R BITMAP.RES Link our resource - allows loading the bitmap }
After this you only need to be sure that you have the file "bitmap.res" in your project directory. You can create your RES file with for example Delphi's Image Editor, but I personally find it a mediocore tool. Thus I wrote a very simple resource script, "bitmap.rc", like this:
MYBITMAP BITMAP "test.bmp"
Then I simply used the Resource Compiler (BRCC, located in your \Delphi\Bin directory) to compile it. And all this took 5 seconds.
After you've got this far, I hope that you now undestand how to load a 256 color bitmap. Of course, you will most often use the TBitmap object (using it is trivial), but just sometimes you might need a more compact solution.