Feedback Search Top Backward Forward
EDM/2

The Unofficial guide to the Palette Manager

Written by Raja Thiagarajan

 

The Guide

IMPORTANT NOTE: If you aren't going to read this whole article, at the very least please read about the FATAL ET4000 bug (search for FATAL ET4000 BUG if you're reading this as ASCII).

Credit where it is due: When Windows 3.0 first came out with its Palette Manager in 256-color mode, I fell in love. Finally, a GUI that understood palettes and "did the right thing" when two programs tried to use the palette simultaneously. I was elated to find out that when OS/2 2.0 came out, it would also have a Palette Manager. My elation turned to disgust when I discovered the following paragraph in the READ.ME file for the OS/2 Programmer's Toolkit: Palette Manager functions are available, but no devices currently allow the physical palette to be changed. This means applications attempting to put customized colors into the palette will get the nearest colors from the default palette.

As far as I'm concerned, this is tantamount to saying, "You can do anything you want ... as long as you don't want to do anything!" Fortunately, when the Service Pack came out later in 1992, the 32-bit XGA, Trident, and ET4000 drivers all included good Palette Manager support (though with a few bugs, one of them serious). Sadly, as of this writing, I believe that people with other 256-color displays (such as 8514/A's and clones) still don't have decent Palette Manager support.

So what's Palette Manager, and why should you care? VGA-standard adapters can display any of 262,144 different colors. Unfortunately, you can't display them all at once; you can only see 256 at a time. Fortunately, you can pick any 256 of these colors at once (unlike the early CGA, where you had a choice of two sets of four fixed colors). The 256 colors that your adapter is showing at any time make up your physical palette. Under Presentation Manager, your program creates a logical palette. For example, you may want eight shades of red. You ask Palette Manager for eight colors, specifying the RGB values that you want. Palette Manager then gives you eight spaces in the physical palette and sets them to the RGB values you specified. If it runs out of space in the physical palette, then it looks at the colors it couldn't get for you and returns the colors in the physical palette that come closest to matching them. In either case, you don't have to worry about which spots in the physical palette you got; you just draw lines in color 3 (for instance), confident that they will be medium red.

(At this point, some of you might say, "Couldn't you do this with a Logical Color Table?" I think the answer is yes [I've never used LCTs myself], but I think Palette Manager is easier to use and more flexible. Also, I'm sort of getting the impression that LCTs are being phased out.)

There are other things you can do with Palette Manager. Broadly speaking, there are four different types of tasks you can carry out using Palette Manager:

  • Examining (but not changing) the physical palette
  • Creating a logical palette for drawing (e.g., using GpiBox)
  • Creating a logical palette for displaying a bitmap with special color needs
  • Creating and animating a logical palette

In an ideal world, the third task would just be a special case of the second. However, bugs in the Palette Manager require you to do a few special things to get it to work, so I'll treat it separately.

My program PHYSCOLO demonstrates the first task, SCALE demonstrates the second, GRAYBITM demonstrates the third, and ZOOM demonstrates the fourth. Although these programs do very different things, they have many similarities. They (and any other program that uses Palette Manager) carry out the following steps:

  1. Make sure Palette Manager is available.
  2. Create a PS (and DC)
  3. Create the logical palette
  4. Select the palette into the PS
  5. Realize the palette
  6. Use the palette
  7. Clean up by deselecting the palette, destroying it, and destroying the PS.

Let's look at how we carry out these tasks. At this point, I strongly suggest that you follow along with a copy of PHYSCOLO.C. Either print it out or open it up in another window.

Step 1: Make Sure Palette Manager is Available

In the "main" part of PHYSCOLO, you'll find the following block:


LONG sColors;
HDC hdcScr;
hdcScr = GpiQueryDevice(WinGetPS (HWND_DESKTOP));
DevQueryCaps (hdcScr,
              CAPS_ADDITIONAL_GRAPHICS,
              1,
              &hasPalMan);
hasPalMan &= CAPS_PALETTE_MANAGER;

(There are two more lines in the actual listing that check how many colors are available on your computer. While all existing drivers with Palette Manager support have exactly 256 colors, this could change in the future.)

After running this block, hasPalMan will be zero if the computer doesn't support Palette Manager. PHYSCOLO just keeps going, but it might be more appropriate to give an error message and exit. (See ZOOM, for instance.)

So if hasPalMan is nonzero, are you okay? Not necessarily. Remember the OS/2 Toolkit READ.ME: OS/2 may report that you have Palette Manager support even if the device won't allow your physical palette to change. Unfortunately, I don't know any way to tell if you "really" have Palette Manager support; if anyone knows, I'd like to hear from them.

Step 2: Create a PS (and DC)

Here's the First Commandment of using Palette Manager:

IF YOU'RE GOING TO USE PALETTES, !!DON'T!! USE A CACHED MICRO-PS!

If someone had told me this, it would have saved me a couple of weeks. If you only learn two things from this article, learn this and the Fatal ET4000 bug (below).

In other words, don't do the following when handling WM_PAINT:


    case WM_PAINT:
       {
          HPS hps;
          hps = WinBeginPaint (...);
          ...
       }

Why? When you create a cached micro-PS, it "forgets" everything between WM_PAINT messages. While that's fine for simple graphics, it's not appropriate when you're using a palette, because the cached micro-PS will forget the palette!

Instead, we create an uncached micro-PS. Looking at the "ClientWinProc" procedure in PHYSCOLO, we see the following lines:


    static HPS hps;
    static HDC hdc;

(These are static, of course, since they have to be remembered between calls to ClientWinProc.) Later on, we see the rest of the lines for creating the PS:


    case WM_CREATE:
       {
          SIZEL sizl;
          sizl.cx = sizl.cy = 0;
          hdc = WinOpenWindowDC (hwnd);
          hps = GpiCreatePS (hab,
                             hdc,
                             &sizl,
                             PU_PELS |
                               GPIF_DEFAULT |
                               GPIT_MICRO |
                               GPIA_ASSOC);
       }

Step 3: Create the Palette

This step (and Step Five) will differ depending on which task you carry out (e.g., whether the logical palette is for examining the physical palette or for palette animation). In the case of PHYSCOLO, we do the following:


    static HPAL hpal;
    ...
    WM_CREATE:
       {
          ...
          ULONG tbl [MAX_PAL_SIZE];
          INT   j;
          for (j = 0; j < colorsToShow; j++) {
             tbl [j] = PC_EXPLICIT * 16777216 + j;
          }
          hpal = GpiCreatePalette (hab,
                                   0L,
                                   LCOLF_CONSECRGB,
                                   colorsToShow,
                                   tbl);
       }

Four things worth pointing out:

  1. Ordinarily, it's a good idea to sort the palette, with the most important RGB values closer to the beginning of tbl. Here, since PHYSCOLO merely examines the physical palette, we don't sort entries.
  2. The PC_EXPLICIT flag in each tbl entry means, "This number represents an entry in the physical palette, and NOT a color." So, for instance, PC_EXPLICIT * 16777216 + 3 means the third entry in the physical palette.
  3. The LCOLF_CONSECRGB flag means that tbl is an array of RGB values, each of which is 4 bytes long (which is why we can "fake" it with ULONGs). Currently, you must use this flag for all calls to GpiCreatePalette (and your table must be an array of RGBs / ULONGs).
  4. The 0L parameter is appropriate for PHYSCOLO, since all we're doing is examining the physical palette. Later, I'll try to convince you that it's appropriate for every other use of GpiCreatePalette, too.

Step 4: Select the Palette Into the PS

This is easy:


      GpiSelectPalette (hps, hpal)

Step 5: Realize the Palette

You realize the palette when your program changes its logical palette or in response to OS/2's new WM_REALIZEPALETTE message. From what I've seen, WM_REALIZEPALETTE is sent to a Presentation Manager program when that program moves into the foreground, or when some other program changes an (unreserved) palette entry. (Reserved palette entries are ones used for palette animation; see below.)

If your program doesn't start in the foreground, it should realize the logical palette before it first draws using that palette. Otherwise, the client area will be drawn in black. That's what the


    STATIC BOOL firstPaint = TRUE;
    ...
    if (hasPalMan && firstPaint {
       ...
       WinRealizePalette (hwnd, hps, &palSize);
    }

stuff in the WM_PAINT case is for.

The parameters to WinRealizePalette are straightforward, though I wish I understood why it's necessary to pass a pointer to the palette size rather than the palette size itself.

The other time PHYSCOLO calls WinRealizePalette is when handling WM_REALIZEPALETTE. When PHYSCOLO receives a WM_REALIZEPALETTE message, it merely calls WinRealizePalette and ignores the return value. This is a little atypical, as I'll explain below.

Step 6: Use the Palette

This is easy. Looking at PHYSCOLO's listing, we see the line


    GpiSetColor (hps, j);

What this means is, use the color of the j-th logical palette entry for your next drawing operation. That's all there is to it.

Step 7: Clean up by Deselecting the Palette, Destroying It, and Destroying the PS

When you're completely finished with your palette, you should deselect it and destroy it. When you're completely finished with your PS, you should destroy it. Usually, this will be when your program is ending, which is why all these tasks are handled in the WM_DESTROY code for PHYSCOLO:


    if (hasPalMan) {
       GpiSelectPalette (hps, NULLHANDLE);
       GpiDestroyPalette (hpal);
    }
    GpiDestroyPS (hps);

This should be completely straightforward. Be sure you get rid of every palette and PS when you're done with it.

Other Uses of Palette Manager

We've gone through PHYSCOLO and seen how to examine the physical palette. What changes do we have to make to create a custom logical palette for drawing, or for displaying a bitmap, or for animating a palette? The answer is, not many. Most of the changes are in Steps Three and Five.

Scale

This program draws a grayscale, or a redscale, or a cyanscale, or .... See SCALE.DOC for details.

If we consider how it uses the Palette Manager, SCALE isn't really all that different from PHYSCOLO. It does Steps One and Two (checking for the Palette Manager and creating the PS and DC) the same way that PHYSCOLO does. When we reach Step Three, things are slightly different. In SCALE, the entries in tbl represent RGB triples, and they take the form:


tbl [j] = red * 65536 + green * 256 + blue * 1;

where "red," "green," and "blue" are the RGB values for the color you desire in logical palette entry j. These values range from 0 (meaning none of that primary) to 255 (meaning the maximum amount of that primary). Here's an example: Bright cyan is formed by mixing the maximum amount of green with the maximum amount of blue. Numerically, this would be 255 * 256 + 255 * 1, or 65535. So, for example, if tbl[3] had a value of 65535, this would mean that the third entry in the logical palette would be bright cyan. (We don't use the PC_EXPLICIT flag since we want to create colors, rather than examine physical palette entries.)

Note that I still use 0L in the call to GpiCreatePalette. Why? Well, there are two possible alternative options (which can even be OR'd together):

LCOL_PURECOLOR. According to the online docs (in the OS/2 Toolkit), "If this option is set, only pure colors are used and no dithering is done." Well, I've never used this option and I've never seen any dithering. In my honest opinion, Palette Manager does the closest match instead of dithering, since dithering wouldn't work for narrow lines (or single pixels!), and since it'd be too computationally expensive for filled areas. Anyway, you can try this flag if you want; I don't use it and don't plan to.

LCOL_OVERRIDE_DEFAULT_COLORS. OS/2 Presentation Manager sets aside about 20 colors for the user interface (e.g., for the color of Window title bars). This means that you can't ever really get 256 colors. Unless you use this flag, that is. However, using this flag will screw up the look of the user interface. In my honest opinion, it's not worth it; sort your palette and live with the fact that you'll "only" get 236 distinct colors.

Step Four is the same old "GpiSelectPalette (hps, hpal)."

Step Five is half the same (in handling firstPaint), but half different. In the handling of WM_REALIZEPALETTE, we actually use the return value of WinRealizePalette:


       ULONG palSize = colorsToShow;
       if (WinRealizePalette (hwnd, hps, &palSize)) {
          WinInvalidateRect (hwnd, NULL, FALSE);
       }

What does this do? Basically, when you call WinRealizePalette, it returns the number of palette entry requests that it could NOT meet. If you asked for a palette with 16 colors and it could only give you 13, the other 3 will be matched to the closest available colors, and WinRealizePalette will return 3. In this case, you should redraw your client area, and that's what the "WinInvalidateRect" call does.

Step Six, using the palette, is much the same as it was for PHYSCOLO. Notice, however, that SCALE also has a routine to handle things if the machine doesn't even pay lip service to supporting Palette Manager. That's what the "GpiQueryColorIndex" line is about. On a system without Palette Manager, this returns the argument to GpiSetColor that will return the closest match to the RGB value you want.

Finally, Step Seven is the same for SCALE as it was for PHYSCOLO.

Graybitm

This program creates a small bitmap with grayscale shapes and displays it on the screen (stretching it to fit the client area). It's pretty much the same as SCALE. There are a couple of differences: It creates the palette before creating a PS, and it uses the logical palette in two different PSes; the one that's used to create the bitmap (hpsMem in the "CreateBitmap" procedure), and the one that's used to display the bitmap (hps in "ClientWinProc"). These differences aren't really that important, though the latter shows that you can "share" palettes by sharing the HPAL.

Notice that handling WM_PAINT is simplicity itself: We just call WinDrawBitmap. But why didn't we use GpiBitBlt? And why do the colors get screwed up when we resize (e.g., maximize) the GRAYBITM window? We'll tackle these issues when we talk about the bugs in the Palette Manager.

Zoom

This program does palette animation. What's that? Well, suppose you do the following:

  • Create a logical palette with space for four colors, with all four palette entries set to the window background color.
  • Draw four non-overlapping filled circles using each of the four colors, with the circle that uses color 0 on the left, circle 1 to the right of it, and so on.

Okay, so at this point you've drawn four "invisible" circles. Now suppose you set the first entry in the logical palette to blue (or any color that isn't the background). Boom, a circle appears on the left. After a brief pause, change the first color in the logical palette back to the background, and change the second color to blue. Then change the second color to the background and the third color to blue. And so on. If you time these well, this makes it look like an animated sequence of a circle moving from left to right across your client area. But the thing worth pointing out is, you don't have to do any fancy drawing or undrawing in real-time; all you're doing is changing palette entries. Since you're only changing palette entries, this sort of animation can go very fast, once you've finished the complicated initial drawing.

ZOOM does something similar: It draws 16 overlapping squares, creates a palette with spaces for 16 colors, and then animates. The result is a little like moving down a long corridor. (For a real cheap thrill, run TESTZOOM.CMD, CTRL-ESC to get the Window List, select all the Zoom sessions, and Tile them. If one of these windows has the focus, click on the desktop to "get rid" of it.)

From a programmer's standpoint, ZOOM is yet another straightforward use of Palette Manager. It carries out Steps One and Two in the usual way (though it gives an error message and dies instead of just "trudging on" if it can't find Palette Manager -- since this program exists only to demonstrate palette animation, there's no point in having it continue.)

In Step Three, things are slightly different:


    tbl [j] = PC_RESERVED * 16777216 + ...

What's this PC_RESERVED flag? Well, as I said before, if Palette Manager can't give you all the colors you requested, it will use the closest match among the physical palette. (That's why we do a WinInvalidateRect if WinRealizePalette returns a nonzero value.) However, if you use the PC_RESERVED flag, Palette Manager will not match anything with that particular palette entry. This is important if the RGB values of the palette entry will be changing rapidly (the overhead of keeping the closest match up-to-date would be too great). Since changing RGB values rapidly is the heart of palette animation, you should always use the PC_RESERVED flag for palette animation.

Step Four is still the same. Steps Five and Six are different. In fact, I'd say that Steps Five and Six are now intertwined, since the way you "use" a palette for animation is to constantly change it, and you have to realize the palette every time you change it. Hence:


    ULONG tmp = tbl [0];
    ...
    tbl [j] = tbl [j + 1];
    tbl [shadesToShow] = tmp;
    GpiAnimatePalette (hpal,
                       LCOL_CONSECRGB,
                       0,
                       shadesToShow,
                       tbl);

GpiAnimatePalette is, naturally enough, the function that actually animates (ie, changes the RGB values of) the logical palette. Its arguments are the handle to the palette to change, the ubiquitous LCOL_CONSECRGB, the first index to change, the number of indexes to change, and the table that holds the new RGB values. The call above changes all the palette entries; to change only entries 3 through 7, change the "0" above to "3", and the "shadesToShow" to "5". (The values in tbl would still be the same.)

Finally, Step Seven is the same as usual.

Palette Manager Bugs

There are several reasons why this guide is unofficial rather than official. One reason is that it describes what I've discovered in the school of hard knocks, which is sometimes quite different from what the official documents say. Another reason is that I don't have any access to official IBM folks, so there may well be errors in this article. I've described how I've used Palette Manager to the best of my ability, but it could be that there are simpler, more elegant ways to carry out some of these tasks. I doubt it, but we'll never know until the IBM documentation improves.

(Another reason that this is unofficial is that I have no compunction against slamming IBM when I think they deserve it.)

Finally, one of the important reasons why this is an unofficial guide is that I'm going to tell you that the current incarnations of Palette Manager have some real bugs, including one that's very serious. Namely:

THE FATAL ET4000 BUG (or, How To Hang Your Computer Using Palette Manager)

This is the most important fact in this article; if you forget everything else, remember this. And warn anyone who uses your program about it!

If you're running one of the 256-color ET4000 SuperVGA drivers from the Service Pack, AND IF a program changes the physical palette (either an OS/2 program that uses Palette Manager or a seamless Win-OS/2 program), THEN !!DON'T MOVE AN ICON ON THE OS/2 DESKTOP!! IF YOU DO, YOUR COMPUTER WILL HANG!

Your computer won't be safe until the physical palette has reverted to normal. This won't happen until after the program(s) that changed the palette are finished. And even then, it can take a while; one way to force the palette back is to start PHYSCOLO (or move it to the foreground) and then click on the desktop. The palette will revert to normal.

TRIDENT USERS BEWARE! THIS BUG MAY ALSO OCCUR FOR YOU (I couldn't get verification by press time).

Fortunately, this bug does NOT occur in the XGA drivers. Neither does it occur in the latest 256-color drivers from the OS/2 2.1 beta (version 6.479), so ET4000 users may want to switch to the beta. I hope the fact that the bug isn't in the beta means that IBM has fixed it, but I haven't heard anything official from them, so take care.

Unfortunately, this isn't the only bug in Palette Manager. I know of three others:

THE UNIVERSAL BUG

Start a program that uses Palette Manager to draw in its window (e.g., SCALE or GRAYBITM). Drop down the system menu over the client area. Click elsewhere to get rid of the menu. The area under the menu won't redraw correctly. This bug occurs on every platform that's known to support Palette Manager, though it's less obvious on XGAs and 2.1 beta-using ET4000s than on Tridents and ET4000s with the Service Pack drivers.

THE GPIBITBLT BUG

If you use GpiBitBlt to render a bitmap that uses a logical palette, it'll be drawn with the wrong colors on ET4000s and Tridents. Guaranteed. However, it'll be drawn fine on XGAs.

THE WINDRAWBITMAP SCALING BUG

If you use WinDrawBitmap with parameters that require the bitmap to stretch or shrink, it'll be drawn with the wrong colors on ET4000s and Tridents. Again, though, it'll look fine on XGAs.

I have sent bug reports from IBM on all of these, as well as sample programs that demonstrate the GpiBitBlt bug. However, I'm just one voice crying in the wilderness: If you want to see these bugs fixed, PLEASE get the file OS2PROB.TXT from the /pub/os2/2.0/info directory on ftp-os2.nmsu.edu, fill it out, and e-mail it to 76711.610@compuserve.com. Make sure that you only report ONE bug on each form; otherwise, you'll just get a note back from IBM asking you to resubmit your report on several forms. As long as I am the only person asking for these bugs to be fixed, they'll probably be a low priority for IBM.

Final Thoughts

Don't let these bugs get you down; Palette Manager is a fine subsystem that lets you accomplish a lot of great effects. My hat's off to IBM for giving us such a simple and elegant way to use palettes, and my hat's off to YOU for the fine Palette-Manager-using programs that I hope you'll be writing soon ;-).