Palette Awareness

Ron Gery
Microsoft Developer Network Technology Group

Created: April 15, 1992
Revised: September 29, 1992

Click to open or copy the files in the MULTIPAL sample application for this technical article.

Abstract

This article discusses basic palette awareness—what it takes for an application to use palettes correctly in the Microsoft® Windows™ graphical environment—by covering these areas:

The reader should be familiar with basic palette use for painting operations. This article is not aboutthe Palette Manager, and the level of detail is limited to the very basics of the palette managing process. A sample application, MULTIPAL, is included on the Microsoft Developer Network CD to illustrate the use of multiple palettes in a single application.

Introduction to the Basics

Any application can use palettes, but to become truly palette-aware, an application must be able to respond properly to changes in the system palette and to effectively manage its palette use. A previous article, "Using DIBs with Palettes," discusses how to create, select, and realize a palette to display a device-independent bitmap (DIB) on the screen, but it does not discuss effective management of the palette in the larger picture. The information that follows tells that story.

Before delving into the details, let's summarize some basic Windows Palette Manager terminology. All of the palette lingo becomes meaningful only on a palette device, but the general methodology is device independent. Here are some terms and brief definitions:

Message Response

Palette-aware applications need to handle two palette-related messages: WM_QUERYNEWPALETTE and WM_PALETTECHANGED. The Palette Manager sends the WM_QUERYNEWPALETTE message to indicate that the application has been activated and has an opportunity to realize a palette in the foreground. The WM_PALETTECHANGED message indicates that the system palette has changed; this is the perfect time for an application to re-realize its palette and perhaps repaint its client area so that the colors used are mapped to the new colors in the system palette. The following is a good start for simple palette messaging support:

case WM_PALETTECHANGED:
    if (wParam == hWnd)         // Responding to own message.
                                // Nothing to do.
        break;
case WM_QUERYNEWPALETTE:
    hDC = GetDC(hWnd);
    hOldPal = SelectPalette(hDC, hPalCurrent, FALSE);
    iTemp = RealizePalette(hDC);         // Realize drawing palette.
    SelectPalette(hDC, hOldPal, TRUE);
    RealizePalette(hDC);
    ReleaseDC(hWnd, hDC);

    if (iTemp)                            // Did the realization
                                          // change?
        InvalidateRect(hWnd, NULL, TRUE); // Yes, so force a 
                                          // repaint.
    return(iTemp);

case WM_PAINT:
    hDC = BeginPaint(hWnd, &ps);
    hOldPal = SelectPalette(hDC, hPalCurrent, TRUE);
    RealizePalette(hDC);
    // Paint the client area.
    SelectPalette(hDC, hOldPal, TRUE);
    RealizePalette(hDC);
    EndPaint (hWnd, &ps);
    break;

In this sample, both messages are handled in a very similar manner. The current palette is selected and realized, and the client area is invalidated to force a complete repaint if the palette realization has changed since the last realization. The palette is selected without forcing it into the background (the bForceBackground parameter to SelectPalette is FALSE) so that the palette is realized as a foreground palette if the application is the active application or as a background palette if not.

To prevent responding to a system palette change that is caused by the application itself, an extra check is added for handling the WM_PALETTECHANGED message.

Notice that no drawing is done while handling these messages; all drawing is performed in response to the WM_PAINT message. The palette messages provide a way for realizing a palette and establishing its realization priority in the system. The color mapping computed at this point remains valid until a different foreground palette is realized. During the drawing process, when the application realizes the palette again, its mapping has already been computed, and the system palette is not affected, only referenced. Remember that to draw relative to a palette, the palette must be selected into the destination device context (DC) and realized.

To reiterate, the WM_QUERYNEWPALETTE message informs the application that it is gaining foreground palette status, and the WM_PALETTECHANGED message indicates a possible need to adjust to changes in the system palette. In either case, if palette realization indicates a change in mapping (RealizePalette returns a nonzero value), the application should repaint for best results.

Bitmaps

Bitmaps of the device-dependent variety are afforded a heading in this article mostly as a reminder of their peculiarities on palette devices. The key to using bitmaps on palette devices is to understand that a bitmap is meaningless without the palette on which it is based. Monochrome bitmaps do not have this limitation. A palette device stores bitmaps as a collection of indexes that reference a certain logical palette. To blt the bitmap to the screen or to draw to the bitmap, the application must first select and realize the proper palette.

A pattern brush made with a color bitmap also has the same dependence on a palette. DIBs stored with the DIB_PAL_COLORS option are relative to a palette, but because the color table indexes are accessible to the application, the mapping is controlled by the application.

The MULTIPAL sample application uses bitmaps to display images, as opposed to using StretchDIBits with the original DIB form. The application tracks every bitmap/palette combination as a pair and uses the palette to display the bitmap.

Managing Multiple Palettes

An application has several ways to deal with the problem of displaying multiple palettes at once. Each method has its own merits and limitations.

Probably the simplest approach is to combine all of the palettes into a single large palette. The ordering of the colors in this palette determines their relative importance. This type of approach tends to be quite limiting because adding new colors involves creating a new palette, and changing the ordering of the colors also necessitates rebuilding the palette. Rebuilding palettes is generally a bad idea because the application then needs to recreate any bitmaps that are based on those palettes to preserve the color information.

Instead of simply combining the palettes into a single palette, the application can analyze the way all of the colors are used and build an optimum palette that has sufficient colors to display whatever images are present without substantial color loss. The meaningful size of this optimal palette is limited to the number of usable entries in the system palette. This type of approach can be done with varying levels of accuracy and complexity, depending on how color-intense the application wants to be. For most applications, the work involved in building this palette outweighs the possible benefits. Using a single palette, regardless of how optimal it is, still has a flexibility problem: Adding or removing colors requires a complete rebuild of the palette and all related bitmaps.

A good workable compromise that is flexible and easy to implement is to manage the multiple palettes as separate entities. The application realizes all of the palettes, but it controls the relative priority of those palettes through careful ordering of the realizations. The application chooses one palette to be the dominant palette, and this palette is realized in the foreground. All of the other palettes are forced to realize in the background:

// To select the chosen dominant palette:
SelectPalette(hDC, hDominantPalette, FALSE);

// To select a lower priority palette:
SelectPalette(hDC, hLowlyPalette, TRUE);

Actually, the foreground palette needs to be realized in the foreground only in response to the WM_QUERYNEWPALETTE message to establish it in the system palette.

The application must be careful to ensure that it realizes all of its palettes in response to the WM_PALETTECHANGED message before letting another application respond to the message. This way the application ensures that all of its palettes get the first shot at any available room in the system palette. Notice that realizing the foreground palette could cause the Palette Manager to send the application the WM_PALETTECHANGED message; if the application is using more than one palette, it needs to respond to this message even if it is the application that caused the system palette to change. By forcing the palettes that are realized in response to this message into the background, the application avoids a circular messaging scenario. Below is a rudimentary example of message handling for multiple palettes. (The MULTIPAL sample application uses the multiple document interface [MDI] and messaging to achieve the same effects.)

case WM_QUERYNEWPALETTE:
    hDC = GetDC(hWnd);
    hOldPal = SelectPalette(hDC, hDominantPal, FALSE);
    i = RealizePalette(hDC);     // Realize dominant palette.
    SelectPalette(hDC, hOldPal, TRUE);
    RealizePalette(hDC);
    ReleaseDC(hWnd, hDC);

    if (!i)   // No realization change, but let secondaries realize.
        SendMessage(hWnd, WM_PALETTECHANGED, hWnd, NULL);
    else     // Palette did change. Repaint dominant image.
             // (The WM_PALETTECHANGED message is sent by Palette
             //  Manager.)
        InvalidateRect(hWnd, lpDominantRect, TRUE);
    return(i);

// This message is received for 3 reasons:
//  - Result of dominant palette realization (wParam == hWnd)
//  - Dominant palette did not actually change realization 
//    (wParam == hWnd)
//  - Some other application changed system palette (wParam != hWnd)
case WM_PALETTECHANGED:
    if (wParam == hWnd)  {  // Dominant palette realization caused
                            // this.
        // Realize the secondary palettes, forcing them into the
        // background.
        // If the realization changes the system palette mapping,
        // then force an appropriate repaint.
        hDC = GetDC(hWnd);
        hOldPal = SelectPalette(hDC, hLowlyPalette, TRUE);
        if (RealizePalette(hDC))
            InvalidateRect(hWnd, lpLowlyRect, TRUE);
        SelectPalette(hDC, hOldPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(hWnd, hDC);
}
    else  {
        // Normal palette adjustment/repaint. Dominant palette
        // needs to be realized here as well.
        hDC = GetDC(hWnd);
        hOldPal = SelectPalette(hDC, hDominantPalette, TRUE);
        if (RealizePalette(hDC))
            InvalidateRect(hWnd, lpDominantRect, TRUE);
        SelectPalette(hDC, hLowlyPalette, TRUE);
        if (RealizePalette(hDC))
            InvalidateRect(hWnd, lpLowlyRect, TRUE);
        SelectPalette(hDC, hOldPal, TRUE);
        RealizePalette(hDC);
        ReleaseDC(hWnd, hDC);
    }
    break;

The net effect of palette prioritizing is the same as having several palette-using applications running under the Palette Manager, only all of the palettes are controlled by a single application. The application can easily change the priority of the palettes and add or remove palettes from the list. Because each palette is its own entity, changes in other palettes do not require any recalculation on the part of the application, and palette-based bitmaps are not affected (although their display is affected by the priority of their palette).

To alter the ordering of the palettes, the application can send itself the WM_QUERYNEWPALETTE message and start the palette realizations again with the new ordering.

The MULTIPAL Sample Application

The MULTIPAL sample application supports multiple palettes by selecting a dominant palette and prioritizing the other palettes in the background. All of the prioritizing work is done in the handling and sending of the palette messages.

MULTIPAL is an MDI extension to the DIBIT sample program, which is included on the Microsoft Developer Network CD. MULTIPAL allows for the display of multiple DIBs with multiple palettes and handles palette messaging from both the system perspective and the multiple-palettes-in-a-single-application perspective. The basic MDI skeleton is taken from the BLANDMDI sample, which is also included on the Developer Network CD. MULTIPAL achieves device independence by using palettes at all times, regardless of the type of device being used.

Each MDI child is responsible for displaying one DIB. For quicker repaints, the DIBs are converted to device-dependent bitmaps when loaded and are BitBlted to the screen. This requires maintaining a palette per window that is used to create and display the bitmap. Thus each MDI child window has associated with it a DIB, a palette, and a bitmap that is created with that palette.

The priority ordering of the MDI children is based on their Z-ordering. The currently active MDI child is the one that is told to realize its palette in the foreground or, rather, not to force its palette to the background. This is accomplished by sending the WM_QUERYNEWPALETTE message to only this one child. The window's response to this message is to select the palette with the bForceBackground parameter set to FALSE. All other palette selections, including those used by the PaintDIB painting routine, are forced into the background.

The application handles palette messages that are sent by the system (when the application is activated or when the system palette changes) by passing the messages to the child windows. The application sends WM_QUERYNEWPALETTE messages only to the active child window and the WM_PALETTECHANGED message to all the child windows. The application also sends the WM_QUERYNEWPALETTE message to itself in response to changes in the MDI activation in order to achieve a change of the palette priority within the application. In effect, the application simulates the messaging subsystem of the Palette Manager.

A good way to see the application in action is to open up several color-intensive 8-bit DIBs at the same time and change activation between them. Also, running another palette-using application (another instance of MULTIPAL will do) shows interaction with the rest of the system.

Clipboard Data and Palettes

The best way to place a color bitmap in the Clipboard is to use the DIB format (CF_DIB). This format provides a device-independent mechanism that is not based on palettes or necessarily limited by the color resolution of the current device. Unfortunately, numerous applications do not yet support DIBs as extensively as they could, so this format is not always available or requested.

An alternative to the DIB format is to use device-dependent bitmaps (CF_BITMAP). If the bitmap is based on a palette when it's created, the palette must be placed in the Clipboard as well (CF_PALETTE). The bitmap is meaningless without the palette. The one exception is a bitmap that is created using the default palette (no explicit palette is selected by the application); in this case, the palette is defined implicitly. It is the responsibility of the pasting application to use the palette to display the bitmap, so a palette-aware application must always look for the accompanying palette when pasting a bitmap on a palette device. Note that a copy of the palette object placed in the Clipboard may not produce the same results as using the original object. The bitmap is based on the original palette object and its realization, not simply on its color information.

One more option, which is especially useful for applications that are not palette-aware, is to use a metafile (CF_METAFILEPICT) that utilizes palette-based records to display the bitmap. For example, the following sequence of records in a metafile displays a palette-based bitmap:

  1. Create a matching palette.

  2. Select the palette.

  3. Realize the palette.

  4. StretchDIBits a DIB version of the bitmap using equal source and destination extents (or BitBlt a device-dependent bitmap that is then automatically recorded using a DIB).

An application does not have to explicitly handle palettes to successfully display this palette-based image. For applications that are palette-aware, however, pasting data with the CF_DIB format is usually more useful in the long run.

Metafiles

Metafiles that use palettes have a very big problem in that an application cannot easily determine what the palettes are or what colors they use. The only way to determine this kind of information is to enumerate the metafile's records and find the ones that pertain to colors (that is, records for creating and changing palettes and records with DIB-based information that has color tables).

Adding to this limitation is a bug in the playback of the META_SELECTPALETTE record that always sets the bForceBackground parameter of SelectPalette to be FALSE during playback. This means that playing a metafile with palette processing could realize a foreground palette and blow away palette work already done by the application. For example, if the MULTIPAL application has one of the MDI children blindly play a metafile containing a palette, that window would gain foreground priority even though that was not the desired effect. The only way to work around this problem is to enumerate the metafile for playback, trap the META_SELECTPALETTE record, and play it manually using the desired setting for the bForceBackground parameters as follows:

// In application's EnumFunc for metafile enumeration...,

case META_SELECTPALETTE:
   // force the palette selection into the background.
    SelectPalette(hDC, lpHTable[(lpMFR->rdParm[0])], TRUE);
    break;

This prevents a pasted metafile from doing unexpected damage. The other metafile records can be played normally using the PlayMetaFileRecord function.