Click Here to Install Silverlight*
United StatesChange|All Microsoft Sites
MSDN
|Developer Centers|Library|Downloads|Code Center|Subscriptions|MSDN Worldwide
Search for


Advanced Search
MSDN Home > MSJ > October 1997
October 1997

Microsoft Systems Journal Homepage

Wicked Code

Code for this article: PalGenCode.exe (26KB)
Jeff Prosise is the author of Programming Windows 95 with MFC (Microsoft Press, 1996). He also teaches Visual C++®/MFC programming seminars. For more information, visit http://www.solsem.com.

Sometimes the code you write is only as good as the tools you have to help you. A case in point is Windows®-based code that displays static bitmaps. On 256-color display devices, a bitmap containing a wide range of colors looks good only if you create a palette to go with it. But that raises a question that's as old as computer graphics itself: how do you know what colors to put in the palette?
One solution to the problem of picking palette colors is to use a color quantization algorithm like the one I presented in this column a year ago (August, 1996). Color quantization is the process of finding the optimal n colors with which to display an image that contains m colors, where m is greater than n. The August 1996 column introduced a Win32
® implementation of the ultracool Gervautz-Purgathofer octree color quantization algorithm that, given a bitmap handle, quantizes the bitmap's colors and returns a handle to a GDI palette representing the best combination of colors for displaying the image.
The CreateOctreePalette function presented in that column is great for generating palettes for arbitrary bitmapped images at runtime, but it leaves a little to be desired when you know ahead of time what images you'll be displaying. Suppose you write an application that displays a colorful splash screen as it's starting up. Furthermore, suppose that the splash screen is a bitmap and that the bitmap contains more than just the 20 static colors in the Windows system palette. Unless your application will run only on adapters that support 16 or 24-bit color, you need a palette to display the splash screen's full range of colors. You could use CreateOctreePalette to generate a palette on-the-fly, but since color quantization is a time-consuming operation, doing it this way could delay the startup of your application by several seconds.
The better solution is to pick the palette colors at design time and write them into your code. Then there's no delay waiting for the image to be quantized. The only question is how—I've yet to see a programming tool that will take an arbitrary bitmap and create an optimized color table formatted so that it can be plugged right into a C or C++ program. For lack of tools, many a developer has been reduced to using a graphics editor to compute palette colors and manually transposing the resulting RGB color values into source code.
Maybe you like to do it the hard way, but I don't. That's why I wrote PalGen, a handy palette-generating utility that takes as input one or more BMP files and outputs a text file containing a table of RGB color values. The table is a two-dimensional array of BYTEs that can be pasted into a C or C++ program. PalGen makes it trivial to compute static color palettes for the bitmaps your applications display. And because the input to the program can consist of multiple BMP files, you can easily create composite palettes suitable for displaying two or more images.

The PalGen Utility

When PalGen starts, you'll see the window pictured in Figure 1. From there, you can generate a set of RGB color values in four easy steps:
  1. Enter the names of (and, optionally, the paths to) one or more BMP files in the box labeled Input. You can type the path names yourself or click the Browse button and pick from a list. Multiple file names should be separated with commas or semicolons.
  2. Enter the name of the output file in the Output box.
  3. Pick the maximum number of colors for the palette you wish to create. The default is 236; the allowable range is 8 to 1,024. Why 236? Because that's how many entries are left in the hardware palette of a 256-color video adapter after Windows programs in the 20 static colors.
  4. Click the Generate Palette button.
Figure 1 PalGen
Figure 1 PalGen

PalGen's text output file contains a color table formatted in the following manner:
 // 236 entries
 BYTE byVals[236][3] = {
     255, 240,  36,
     180,  64, 192,
     [...]
 };
Each row in the table identifies one palette color. The first number in the row is the color's red component, the second number is the color's green component, and the third number is the color's blue component. Note that the actual number of colors in the table may be less than the maximum number you specified. If it's substantially less—for example, if you set the maximum to 236 and PalGen outputs only 128 colors—that probably means the bitmap contains fewer colors than you realized.
How do you convert PalGen's text output into a GDI palette? Easy. Suppose the output file contains the following statements:

 // 8 entries
 BYTE byVals[8][3] = {
     255, 240,  36,
     180,  64, 192,
       4,  32, 200,
     224,  85,  80,
     224, 128,   0,
      16,   0, 244,
     122,   7, 114,
      78, 255, 225      
 };
First, paste these statements into a source code file. Then create a LOGPALETTE structure containing one PALETTEENTRY structure for each color in the palette, copy the color values from the byVals array to the corresponding PALETTEENTRYs, and call CreatePalette with a pointer to the initialized LOGPALETTE structure. In MFC, that's CPalette::CreatePalette. Figure 2 shows how it looks in code. Note that the number of PALETTEENTRY structures declared in the pal structure is one fewer than the number of colors in the palette. That's because LOGPALETTE contains one PALETTEENTRY structure already.
PalGen uses the same octree color quantization algorithm I described in Wicked Code a year ago. I reused much of my original code, but I also made two enhancements. First, I added code to handle 1, 4, and 8-bit DIBs. The code I presented last year worked only with 16, 24, and 32-bit formats. Second, I wrapped the palette generation algorithm in a handy C++ class named CQuantizer and generalized the code so that color values are returned as RGB triples. (The original implementation, which was written in C rather than C++, returned a palette handle. If you wanted RGB color values, you had to extract them from the palette.)
CQuantizer is an extremely useful class to have around if you do much graphics programming, because it understands all the Windows DIB section formats and also knows how to quantize colors. You can see how PalGen uses CQuantizer by peeking at PalGen's source code, which you can download along with PalGen.exe from the MSJ Web site at http://www.microsoft.com/msj/. The remainder of this column describes how to use CQuantizer in your own applications.

The CQuantizer Class

CQuantizer makes generating custom color palettes a snap. The following statements create a CQuantizer object named q and a custom palette containing up to 128 colors from the image whose handle is stored in hImage:

 CQuantizer q (128, 6);
 q.ProcessImage (hImage);
The first value passed to CQuantizer's constructor is the maximum number of colors permitted in the palette. The second specifies the number of significant bits in each 8-bit color component. Setting this value to 6 tells the octree algorithm to ignore the lower two bits of each red, green, or blue color value. A setting of 5 or 6 generally produces a palette that is pleasing to the eye while keeping the octree's memory requirements to a reasonable minimum.
You can call ProcessImage as many times as you'd like. To create a composite palette that will serve three different images, for example, call ProcessImage three times, each time with a different image handle. Handles passed to the ProcessImage function should refer to DIB sections, not device-dependent bitmaps (DDBs). For BMP files, this means that you should use the Win32 LoadImage function rather than LoadBitmap to load images from disk. Be sure to include a LR_CREATEDIBSECTION flag so LoadImage will know that you want to create a DIB section, as illustrated here:

 HANDLE hImage = ::LoadImage (NULL, 
               "C:\\Images\\Fish.bmp", 
               IMAGE_BITMAP, 0, 0, 
               LR_LOADFROMFILE | LR_CREATEDIBSECTION);
In certain cases—specifically, when it's called upon to process 1, 4, or 8-bit per pixel images on systems with palettized display adapters—ProcessImage can produce incorrect results if it's passed a handle to a DDB.
After the final call to ProcessImage, use the CQuantizer::GetColorCount function to find out how many colors are in the palette, and use CQuantizer::GetColorTable to retrieve the colors. Given a pointer to an array of RGBQUAD structures, GetColorTable copies the palette colors to the array, as shown here:

 UINT nNumColors = q.GetColorCount ();
 RGBQUAD* prgb = new RGBQUAD[nNumColors];
 q.GetColorTable (prgb);
Once the colors are retrieved, it's a simple matter to convert them into a GDI palette, or to do as PalGen does and output the RGB color values to a file.
CQuantizer's source code is reproduced in Figure 3. If you read the August 1996 column, much of the source code will look familiar to you. The most notable change (other than the fact that all the code is now part of a C++ class) is the code added to ProcessImage to support 1, 4, and 8-bit DIBs. Rather than reading the raw bitmap bits, ProcessImage uses the Win32 GetDIBits function to convert the DIB's pixels to 24-bit RGB format one scan line at a time. Then it reads individual pixels by scanning the line from left to right using the same logic it uses to read 24-bit DIBs. The chief advantage to this technique is that CQuantizer doesn't have to understand run-length encoded (RLE) bitmap formats. GetDIBits handles the unencoding, and ProcessImage neither knows nor cares whether the original bitmap data was stored in a compressed format.
There you have it: two new tools to add to your toolbox. Use PalGen to pick palette colors for images whose identities are known at design time, and CQuantizer to build palettes at runtime. With only a few extra lines of code, your bitmaps can look as good on 256-color screens as they do on true-color output devices.

Your Needs, Your Ideas

Are there tough Win32 programming questions you'd like to see answered in this column, or tools you'd like to see developed? If so, send me email at the address listed below. I regret that time doesn't permit me to respond individually to all questions, but rest assured that I'll read each and every one and consider all for inclusion in a future installment of Wicked Code.

Have a tricky issue dealing with Windows? Send your questions via email to Jeff Prosise: 72241.44@compuserve.com

From the October 1997 issue of Microsoft Systems Journal. Get it at your local newsstand, or better yet, subscribe.

© 1997 Microsoft Corporation. All rights reserved.
Terms of Use
.

© 2014 Microsoft Corporation. All rights reserved. Contact Us |Terms of Use |Trademarks |Privacy & Cookies
Microsoft