The X Color Context: Color Management For (Almost) Any Occasion, Part I

by John Cwikla

Copyright © 1995,1996,1997,1998,1999,2000,2001,2002 John Cwikla, All Rights Reserved.

OK, let's admit it -- using color is much harder in X than it should be. Six different visual classes, a variety of depths and read-only and read-write colorcell allocation stack the odds not only against your application running on different servers, but against coexisting peacefully with other clients that want to grab their own share of color resources. Plus, unlike most other windowing systems, X does not have a real color management system. Asking for a color from the server does not always guarantee a valid pixel will be returned. Each program must handle its own color management, and limit consumption of non-shareable color resources.

Read-Only vs. Read-Write Colormaps

The six different visuals can be divided into two groups: those with read only colormaps, and those with read-write colormaps.

A read-only colormap is intialized with immutable color values when the colormap is created. StaticGray, StaticColor and TrueColor visuals all have read-only colormaps. On the plus side, XAllocColor() (or XAllocNamedColor()) always succeeds returning a pixel which will be the closest match the server finds in the colormap. Additionaly, colorcells are implicitely shared -- no client can change any of the colorcell values, so all clients can use all colors in a colormap. On the down side, since the colormap entries cannot be changed, visuals with a small number of planes have less of a chance of returning a pixel whose value exactly matches a client's requested color.

Since any call to XAllocColor() returns a valid pixel, programs should be able to run under any of these 3 visuals without much effort. The problem remaining is limiting calls to XAllocColor() for each color change, each call which requires a roundtrip to the server needlessly slowing down your application.

For GrayScale, PseudoColor and DirectColor visuals (with read-write colormaps) colors can be allocated read-only or read-write. Allocation continues until all the available entries in the colormap are used up, after which, any new attempts will fail. Read-only allocation with XAllocColor() allows color entries to be shared among clients requesting the exact same color.

For clients to share a readonly color, the red, green and blue components must be the same. If your colormap has run out of free entries, XAllocColor() will not return successfully if there does not already exist an entry with the same color you are requesting. For read-write colormaps, any entries allocated (using XAllocColorCells() or XAllocColorPlanes()) are exclusively for the use of the requesting client, and that pixel will not be returned to another client.

The main problem with read-write colormaps is that they can become filled quickly when multiple clients are running since each client has its own idea of what its best colors are. Once this happens your client must deal with color allocation failures. Some applications avoid this problem by creating a private colormap allowing the program to use all the entries without the worry that other clients may have allocated some unknown number of color cells.

Remember, when a program uses a private colormap, it is up to the window manager to install that colormap, if necessary, when the application gets the input focus. (Some high end servers allow multiple colormaps to be installed at once, though most midrange and lowend servers only can have one colormap in hardware at a time.) When this happens you will experience the "techno-flash" effect. Applications will show false colors when their colormap becomes uninstalled and a different colormap is swapped in.

To help alleviate this problem X has "standard colormaps". Some client allocates a contiguous range of entries in the colormap and stores a ramp of colors representing an RGB cube into these entries. The client uses XSetCloseDownMode() with RetainPermanent so when the client exits these cells are still marked as allocated by the server, leaving the colors permanently stored. A property is then stored on the root window describing the RGB cube. A standard colormap is defined by the structure:


typedef struct {
    Colormap colormap; 
    unsigned long red_max; 
    unsigned long red_mult;
    unsigned long green_max;
    unsigned long green_mult;
    unsigned long blue_max;
    unsigned long blue_mult;
    unsigned long base_pixel;
    VisualID visualid;
    XID killid;      
} XStandardColormap;

Fields of the XStandardColormap:

	colormap: colormap (ID) used by the standard colormap 
	  	(it does not have to be the default colormap.)

	red_max, green_max, blue_max: maximum value for red/green/blue

	red_mult, green_mult, blue_mult: multipler for red/green/blue values
	
	base_pixel: base value to add to resulting calculated pixel

	visualid: ID of the visual used to create the colormap

	killid: ID used to free the allocated cells in colormap. 
		(May be an ID to use with XKillClient() or ReleaseByFreeingColormap
		indicating XFreeColormap() should be called on the colormap ID)

Instead of calling a color allocation function, the property is retrieved into an XStandardColormap structure. All pixel calculations are done using the XStandardColormap structure directly. For example, to calculate a pixel value from an RGB triplet (each component in the range 0-65535):


	pixel = stdCmap.base_pixel +
			(((wantRed * stdCmap.red_max)/65535) * stdCmap.red_mult) +
			(((wantGreen * stdCmap.green_max)/65535) * stdCmap.green_mult) +
			(((wantBlue * stdCmap.blue_max)/65535) * stdCmap.blue_mult);

This does not always return a pixel with the exact wantRed, wantGreen and wantBlue values, but instead gives the closest color within our RGB cube. Now, like with read-only colormaps, given an RGB triplet, we can always get back a valid pixel. As the size of our cube grows, so does the accuracy of our "closest" pixel. Plus, since standard colormaps are described in a property on the root window, multiple clients can share the same read-write color cells without doing any explicit allocation and no server trips need to be made.

Standard colormaps are a good thing. A common setup for midrange hardware has an X server running with a default eight bit PseudoColor visual. Most programs allocate colors, and with each allocation we lose an entry in our colormap. With only 256 entries available this quickly becomes a problem if you have more than one color intensive application running. For resource specifications this is not bad due to the color name database "rgb.txt". "rgb.txt" defines a mapping from names to rgb color triplets of the form:

	255 248 220     cornsilk

This way client colors can be specified by "cornsilk" instead of an RGB triplet, and any other client requesting "cornsilk" will be given a pixel to the same color entry (sharing).

However, once a program needs to mathematically allocate colors "rgb.txt" no longer is of any use. For these midrange servers, standard colormaps are a great solution. Allocating a range of entries for the standard colormap, the user has control over how large the cube should be, and can even leave entries for other programs that are oblivious to standard colormaps.

More Pieces to the Puzzle

We now have everything we need to get a pixel for any RGB triplet -- well, not quite. Each visual class requires some special consideration. StaticGray contains grayscale values ranging from black to white. Unfortunately, no order, such as black in pixel 0 and white in the last colormap entry, is defined to exist.

GrayScale also contains grayscale values, but these values must first be allocated by clients. Since GrayScale has a read-write colormap, there is the possibility of allocation failure. StaticColor is initialized with some set of colors, though what colors and what entries they are in is undefined.

PseudoColor has a read-write colormap that clients can fill by their color requests. PseudoColor has the possibility of allocation failure. TrueColor has well defined colormap entries according to the bits defined by its visual. The client must manipulate these bits to obtain the pixel with the color they require.

DirectColor is like TrueColor, but with an initially undefined colormap. Clients allocate colors as needed, using the bits specified by the visual to calculate pixel values. DirectColor has the possibility of allocation failure, and is generally the most difficult of all the visuals to correctly use. Our color management problem only seems to be getting worse, but there is hope!

First, if a standard colormap property exists, we would like to use it. If the visualid of the standard colormap matches the id of our client's visual, we can use the previous calculation to get our pixel value, and all is well. StaticGray and GrayScale colormaps, except for one being uninitialzed, could be treated the same. If we could somehow order the entries in both types of colormap, we could mathematically calculate pixel values. This is possible if a client side lookup table is used. Since the values are grayscale we can allocate a ramp of grays from white to black, then for any RGB triplet we can calculate the intensity, scale it to our table, and get a pixel.

StaticColor and PseudoColor colormaps could be the same except PseudoColor colormaps are unitialized. We cannot just ramp colors int the same way we could with grays. Instead, consider the standard colormap structure. If colors are allocated to fit a standard colormap specification we can then use our standard colormap calculation to get pixel values.

Conclusion

Finally we come to DirectColor and TrueColor. TrueColor requires shifting the bits of an RGB triplet appropriately and bitwise OR'ing them together, the result being our pixel. DirectColor requires a little more thought. For DirectColor a pixel is decomposed into three values used to index into three separate tables, one for red, one for green and one for blue, the RGB color component being the value of the entry at that index. Unlike TrueColor, there is no explicit relationship between the bits of an RGB triplet and a pixel (since DirectColor is read-write, the entries can be allocated in any order).

The trick to DirectColor is in creating such a relationship. Notice for TrueColor pixels, the more bits that are set in a component, the more intense that component (all bits set in each component yields the color white, all bits unset in each component yields black). If we allocate our DirectColor entries such that the pixel with the least bits available is black and the most bits is white (ramping each table in between) we effectively create a TrueColor colormap. We can then use the same code (bit shifts and bitwise OR'ing) for both classes.

With appropriate initialization, the number of cases we need to worry about drops from six to three. Since visuals that have read-write colormaps can now be treated as their read-only equivalents, we can get a "closest" pixel for any RGB triplet.

Next Month

I have glanced over some issues in the last section, notably the initial color allocation, handling allocation failures, and avoiding server round trips. In next month's column, I continue my look at a client color management system, and present a hard coded solution, the X Color Context.

John L. Cwikla is responsible for X user interface development at Wolfram Research, Inc., and is known as the keeper of the Xt Widget FAQs on the Internet. He can be reached via email at cwikla@wri.com or you can drop by his home page at . You are also invited to visit The Xlopedia or view the Widget FAQs.