June 95 - Custom Color Search Procedures
Custom Color Search Procedures
JIM WINTERMYRE
Color QuickDraw can be customized for specific tasks in many ways, most commonly
by replacing the "bottleneck" procedures at its heart. But another, often overlooked
way of customizing Color QuickDraw is by writing and installing custom color search
procedures. These procedures are very useful for color separation and other color
processing tasks, and for modifying QuickDraw's default drawing behavior to solve
particular problems. This article reviews some Color QuickDraw basics, explores
how color search procedures work, and presents a sample search procedure.
It's 2 A.M., and you're finally ready to draw your carefully constructed offscreen
GWorld to a window. The GWorld is 32 bits deep and has been set up to contain a color
ramp using 100 shades of red. You've already created a palette containing the 100
shades of red you need and attached it to your window, so the exact colors will be
available on your 256-color screen. You plunk in your call to CopyBits, recompile,
and . . . Ack! Instead of the expected smooth red ramp, you get an image with 16 distinct
bands of color (see Figure 1 on the inside back cover of this issue).
What happened? How can you get the results you want? This article attempts to
answer both of these questions, and a few others along the way. What happened has to do
with the way Color QuickDraw converts colors to pixel values, so we'll start with a
brief review of how this works. As for getting the results you want, one way is to use a
custom color search procedure, which is the main subject of this article.
A QUICK REVIEW OF COLOR IN QUICKDRAW
Before delving into custom color search procedures, let's pause for a quick review of
how QuickDraw converts between colors and pixel values. If you're already familiar
with this, feel free to skip ahead to the section "Drawbacks of Inverse Tables.
How QuickDraw converts colors to pixel values and vice versa is discussed
in Inside Macintosh: Imaging With QuickDraw , and in the Color Manager chapter of
Inside Macintosh: Advanced Color Imaging(available on this issue's CD in draft form).
Only a brief overview of this complex topic is provided here.*
DIRECT AND INDEXED COLOR
When an application does any drawing with Color QuickDraw, the ultimate result is to
change some pixel values in a pixel map somewhere. Color QuickDraw in System 7
(and 32-Bit QuickDraw in earlier systems) supports two distinct types of color pixel
maps:direct and indexed . In direct pixel maps (those with pixel depths of 16 or 32
bits) the pixel values in memory specify RGB color information for the pixel directly.
For example, the 32-bit direct pixel value $00AABBCC specifies a red component of
$AA, a green component of $BB, and a blue component of $CC -- 8 bits of color
information each for the red, green, and blue components. (A 16-bit pixel value
contains 5 bits of color information for each component.)
Figure 2. Indexed color
In indexed pixel maps (those with pixel depths up to 8 bits) the pixel values in
memory don't directly specify the colors at all; instead they specify positions in a
table of the available colors, called thecolor lookup table or just color table
(sometimes called aCLU T ). Figure 2 shows an example; in this case, the 8-bit pixel
value $1C in memory actually represents the RGB color $AAAA BBBB CCCC, found at
position $1C in the color table.
Typically, when an application wants to draw in a particular color, it specifies the
desired color directly using an RGBColor record, and never deals with pixel values at
all. Color QuickDraw and the Color Manager convert between RGBColors and pixel
values as needed. If the application is drawing to a direct pixel map, the color
information itself is used to build the pixel value, and no color table is involved. On the
other hand, if the application is drawing to an indexed pixel map, Color QuickDraw
uses the index of the closest-matching color in the color table as the pixel value (this
process is calledcolor mapping ). But searching the entire color table for a match
every time a pixel value is needed would be far too time-consuming, so the Color
Manager uses something called an inverse table to speed up the lookup process.
INVERSE TABLES
An inverse table is something like a "reverse" color table: whereas a color table is
used to convert an index to a color, an inverse table is used to convert a given color to
an index into a color table. The conversion operation goes like this: You take some of
the most significant bits of each color component and concatenate them, then use the
resulting number as an index into the inverse table. The entry at that location in the
inverse table holds, in turn, the index of the closest-matching available color in the
corresponding color table. Figure 3 illustrates the process. Note that the
closest-matching color returned by this process need not match the original color
exactly, since only a few of the most significant bits were used (the default is 4 bits).
Figure 3. Inverse table with 4-bit resolution
Inverse tables are described in the Color Manager chapter of Inside Macintosh:
Advanced Color Imaging. *
The number of bits each color component contributes to the inverse-table index is
called theresolution of the inverse table. Higher resolutions would give you greater
accuracy in color mapping, but also greatly increase the memory needed to hold the
inverse table, so a maximum of 5-bit resolution is allowed. (Since there are three
color components, each additional bit of resolution multiplies the size of the table
eightfold.) You can use the Color Manager routine MakeITable to create inverse tables
with resolutions of 3, 4, or 5 bits per component.
As an aside, Listing 1 shows how to temporarily change the resolution of the current
graphics device's inverse table to 5 bits. (To permanently change the inverse table
resolution, set the gdResPref field of the GDevice record, set the iTabSeed field of
gdITable to the result of GetCTabSeed, and call GDeviceChanged.)
Listing 1. Temporarily changing the resolution of the inverse table
VAR
gdh: GDHandle;
oldITabRes: INTEGER;
{ Get current graphics device. }
gdh := GetGDevice;
{ Get resolution of current inverse table. }
oldITabRes := gdh^^.gdITable^^.iTabRes;
{ Create a new inverse table at 5-bit resolution. }
MakeITable(NIL, NIL, 5);
{ Draw into a port on this device. }
...
{ Reconstruct inverse table at original resolution. }
MakeITable(NIL, NIL, oldITabRes);
Note that inverse tables aren't found in pixel maps or color graphics ports. They're
instead associated withgraphics devices (astute readers may have noticed that the color
table in Figure 3 was labeled "Graphics device color table" -- this is why). So when
converting RGBColors to indexed pixel values, the Color Manager uses the inverse
table in thecurrent graphics device . The implications of this are discussed in "The
Importance of the Current Graphics Device.
DRAWBACKS OF INVERSE TABLES
The main problem with using inverse tables for color mapping is that because of their
limited resolution, different colors can map to the same inverse table index. Inverse
tables actually include some extra, undocumented information to allow the Color
Manager to resolve such "hidden colors" -- but examining this extra information is
time-consuming, so some speed-sensitive QuickDraw routines don't always use it. One
of these routines happens to be CopyBits, which is what accounts for our "100 shades
of red" problem.
Let's look at the problem in more detail. The offscreen GWorld holding our image is 32
bits deep, allowing the pixel values to specify RGB colors directly, with a precision of
8 bits per component. When we copy the image to a window on an indexed graphics
device, CopyBits uses an inverse table to convert these pixel values from direct to
indexed. If our inverse table has a resolution of 4 bits (the default), it can only
distinguish 24 = 16 shades of red! (For example, all shades of red from RGB $0000
0000 0000 to $0FFF 0000 0000 will map to the same inverse-table index.) Soeven
if all 100 shades are available in the destination device's color table, only 16 of them
will actually be found and get drawn on the screen. This is why the actual result in
Figure 1 has 16 bands of red instead of a continuum of shades.
The various depth conversion cases are discussed in the book Programming
QuickDraw (see "Related Reading" at the end of this article) beginning on page 338. *
One way to deal with this problem would be to increase the resolution of the inverse
table to 5 bits, which would give us 32 bands of red instead 16. Another approach
would be to use the ditherCopy transfer mode in CopyBits. Both of these methods give
better results but don't really solve the problem. After all, since wedo have all the
shades of red available, shouldn't there be some way to match the colors exactly?
INTRODUCING COLOR SEARCH PROCEDURES
Knowing that inverse tables might not be adequate for some applications, the
QuickDraw engineers designed in a "hook" to allow developers to provide their own
color-mapping code. Each GDevice record has its own linked list of customcolor search
procedures; there can be any number of such procedures installed for a given graphics
device. As defined in the Color Manager chapter ofInside Macintosh: Advanced Color
Imaging , a search procedure has the following interface:
FUNCTION SearchProc
(VAR rgb: RGBColor; VAR position: LONGINT): BOOLEAN;
The rgb parameter is now always a VAR parameter. This was not true for
direct-color destinations in 32- Bit QuickDraw prior to System 7. Also, note that
Inside Macintosh Volume V incorrectly declared rgb as a value parameter.*
The Color Manager calls the search procedure with the RGB color it's trying to match,
and expects the search procedure to do one of three things:
• Match the color -- In this case, the search procedure returns thepixel
value for the color in the position parameter, and a result of TRUE. On an
indexed graphics device, the position parameter should contain the index of
the appropriate color in the graphics device's color table. On a direct graphics
device, this parameter should be set to the appropriate direct-color value.
• Modify the color -- In this case, the search procedure modifies the rgb
parameter and returns a result of FALSE. Color QuickDraw ignores the