Combine pf4Bit Bitmaps  Lab Report
Combine three pf4Bit Bitmaps with Different Palettes into a pf24Bit Bitmap,
or into a pf8Bit Bitmap with a Combined Palette
ScreenCombinePf4bit.gif (9322 bytes)

The purpose of this program is to demonstrate how to combine three pf4bit bitmaps into a single bitmap. When combined into a pf8bit bitmap, a combined palette must be created. When combined into a pf24bit bitmap, a combined palette can be ignored (except in 256-color display mode).  Display of the individual and combined bitmaps in both 256-color and high color display modes is discussed.

Combining multiple bitmaps into a single bitmap is relatively easy when working only with pf24bit bitmaps in high or true color display modes.  Let's assume we have three pf4bit bitmaps of the same height and width that we want "stacked" into a single bitmap.  So starting with three individual bitmaps, H tall by W wide,  such as shown next,


we want a combined bitmap, 3H tall by W wide:


Since each pixel contains all the color information, Canvas.Draw method works well with a pf24bit bitmap:

// Combine three pf4bit bitmaps into a single pf24bit bitmap
Bitmap := TBitmap.Create;
  Bitmap.Width := Bitmap1.Width;           // All 3 same width
  Bitmap.Height := 3 * Bitmap1.Height;  // All 3 same height
  Bitmap.PixelFormat := pf24Bit;

  Bitmap.Canvas.Draw(0,0, Bitmap1);
  Bitmap.Canvas.Draw(0,Bitmap1.Height, Bitmap2);
  Bitmap.Canvas.Draw(0,Bitmap1.Height*2, Bitmap3);

  Image.Picture.Graphic := Bitmap; // Display the pf24bit


But let's assume that pf4bit  Bitmap1 has 10 unique colors, Bitmap2 has 15 unique colors and Bitmap3 has 15 unique colors (all via unique palettes in the pf4bit bitmaps).  Let's say we know there should be 30 unique colors in the combined bitmap.  The above code will result in a Bitmap24.BMP file with 30 unique colors in it.   (Note:  The Show Image program can be used to count the number of colors in an image.)  The bitmap will display correctly in high or true color display modes, and is almost right in 256-color display mode.

If the PixelFormat assignment were changed to Bitmap.PixelFormat := pf8Bit; how many colors are in the resulting combined bitmap?  Your results may vary by video adapter and display mode, but on two separate machines I saw 25 colors if the program were run in 256-color display mode and only 19 colors if the program were run in high color mode.  (Many of the colors are similar.  You must use Show Image to verify the exact RGB color components.)

My initial solution was to use GetPaletteEntries to get each palette, combine the palettes, and then re-assign Scanline values based on the new palette.  This worked fine if run in high color mode, but when run in 256-color mode, the palettes for all pf4bit bitmaps were the "standard" VGA palette and the combined image was wrong.   I'm not sure if this is a "bug" or a MS Windows/Delphi feature I don't appreciate.

Danny Thorpe's (Borland R&D) comments:

It's a defensive measure by Delphi to protect against non-conforming VGA hardware palettes provided by some video drivers, mostly in Win 3.1 but occasionally showing up in your less reputable Win32 device drivers.  The standard 16 VGA system palette colors are (supposed to be) defined so that the XOR of a palette index A will give you a palette index B whose color pal[B] is the XOR inverse of the pal[A].  This is critical for creating mono masks based on a single color in the bitmap.  The fastest way to do that is to use ternary raster ops that xor and copy bits in one pass.  When you use raster ops in Bitblt and so forth, GDI applies the ROP to the contents of the bitmap.  The contents of 4 bpp and 8 bpp bitmaps are palette indices, not colors.   So, ROPs operate on palette indices rather than actual colors in those pixel formats.  Easy enough to justify after the explanation, but completely counterintuitive.

The other half of this issue is that the "standard" VGA 16 color palette isn't identical across all VGA hardware or drivers.  The actual RGB values for things like fucsia or olive can vary slightly from one VGA card to the next.   This is fine when you're doing image stuff in memory, but it's a problem when you go to write bitmaps out to a file.  That vendor's quirky VGA color palette will be written out with the bitmap.  If the vendor's quirky VGA palette is so bad that it isn't even XOR symmetric, then bitmaps created on that machine will not mask correctly on that machine or any other, because the palette in the 4bpp bitmap is wrong.

The solution we were forced to adopt in Delphi was to ignore the color information in 4bpp BMPs.  We always load 4bpp bitmaps to reference the current machine's VGA 16 color palette.

Through experimentation I discovered I could use the GetDIBColorTable API call to get the real palette attached to each of the pf4bit bitmaps.   Switching from GetPaletteEntries to GetDIBColorTable to get the color information allowed the program to work regardless of display mode.

Now that the files were being written with the correct colors, the display was OK in high color mode but still wasn't right in 256 color mode!  The original pf4bit bitmaps were not displaying correctly since they  had the "standard" VGA palette (even though I could work with their colors correctly in forming a combined bitmap in memory).  The pf24bit bitmap was not displaying correctly in 256-color mode since it didn't have a palette!  The pf8bit bitmap did appear to be correct, however.

The "Fix Palette" checkbox was added to the application so this palette issue could be seen both ways.  When unchecked, the default Windows/Delphi palette behavior can be observed.  But when checked, new palettes are attached to each of the bitmaps for correct display.  (Display of pf24bit bitmaps in 256 color mode has always been a problem in Windows.  See the ShowDemoOne Lab Report for details).

Materials and Equipment

Software Requirements
Windows 95/98/NT
Delphi 3 or 4 (to recompile)
Files:  2644.BMP, 2645.BMP and 2649.BMP (included with ZIP)

Hardware Requirements
VGA Display


  1. Double click on the CombinePf4bit.EXE icon to start the program.
  2. Observe the following:
  1. Toggle the "Fix Palette" checkbox and observe the palette values and display of bitmaps with the "wrong" palettes.

The FormCreate, the Process key and the Fix Palette checkbox all  invoke the UpdateEverything method.   This method  loads the pf4bit bitmaps and combines the bitmaps in the way described above.

With a palletized bitmap, the Scanline "pixel" value represents the index into the palette table. (On the other hand, pf24bit bitmaps have no palettes since each Scanline TRGBTriple represents the exact color.) When the new combined palette was formed, some Scanline values had to be changed to reflect the new indices into the palette table.

This example assumes that the combined new palette would have less than 256 entries. (Actually, this number should be 236 or less since Windows reserves 20 colors for display of buttons, icons, etc.) If the number of palette entries exceeds 256, the options are to use a pf24bit bitmap, or to use color reduction techniques, such as shown in the Show Demo One Lab Report.

pf4bit bitmaps can have different palettes (I once thought they only had the "standard" VGA 16-color palette). pf4bit bitmaps can be combined most easily into a pf24bit bitmap since palettes can be ignored.. With a little work to form a combined palette and re-assign palette index values, the pf4bit bitmaps can be combined into a new pf8bit bitmap.  Palettes may need to  be re-assigned to pf4bit bitmaps for proper display in 256-color mode.  A pf24bit bitmap often needs its own palette for proper display in 256 color mode.

Even when displaying bitmaps with identical colors, there seems to be slight color shifts in the display of some colors in the pf8bit versus the pf24bit bitmaps -- even in high color display mode.  The cause of this slight difference is not known.

Scanline, PixelFormat, pf4bit, pf8bit, pf24bit, Windows Palette, TMaxLogPalette, Canvas.Draw, pByteArray, palPalEntry, GetPaletteEntries, GetColorTable, IsPaletteDevice function, GetDeviceCaps, RASTERCAPS, RC_PALETTE, Canvas.Draw

Delphi 3/4 Source and EXE (128 KB): CombinePf4bit.ZIP
Compiles in Delphi 4 without any changes.

Size of EXEs by Compiler Version

Delphi 3 231 KB
Delphi 4 330 KB

Updated 11 Jul 2009
Since 1 Nov 1998