Colorizing
Volume Number: 7
Issue Number: 2
Column Tag: Developer's Notes
Colorizing the Mac
By Hugh Fisher, Page, Australia
Colorizing the Macintosh
In August I began converting a black and white Macintosh game, Fire-Brigade, to
run in color on the Mac II. One of the requirements for the new version was for a map to
be displayed in a specific set of colors, even on 16 color cards; and for the colors on
this map to change to show the different seasons. “No problem,” I said to El Presidente,
“Apple set up the Palette Manager for that kind of thing - I read it in Inside Mac Five.
I’ll design the code over the weekend and put it on the machine Monday.”
After a statement like that it went as you would expect: six weeks of struggle,
panic, and despair before finally getting the thing to work. Along the way I learned many
interesting things about the Color and Palette Managers.
CLUTs and CopyBits
A quick recap on color graphics: the average color graphics card stores 2, 4, or
8 bit pixel values in memory rather than a 24 or 48 bit RGB color. When the screen is
drawn, these pixel values are used as indexes into a color lookup table (CLUT) which
stores actual RGB entries. Usually you change the color of something by keeping the
table the same and redrawing with a new pixel value. Color cycling, or color table
animation, instead stores a new RGB color into an entry in the table, immediately
changing the color of every pixel with that value. This is often thought of as just an
arcade game technique, but is also essential for manipulating digitized images of all
sorts. Sorry if this is all elementary stuff to you.
Each monitor attached to a Mac II has a device CLUT, of type CTabHandle. This is a
handle to a table of ColorSpecs, each of which is an RGB color and a 16 bit pixel value.
The device CLUT is stored as the pmTable field for the PixMap representing the device
video RAM. The pixel values in a device table are used by the Color Manager for various
purposes, and it is most likely just a shadow copy of the real hardware table anyway, so
it should not be altered directly.
Offscreen pixmaps also have a pixmap CLUT, usually copied from a device. These
pixmaps never change depth and have no hardware to worry about, so every pixel value
in the table is equal to its index and every entry is valid. New RGB colors can be
assigned directly to table entries - a pixel value means whatever you want it to.
Color Quickdraw often needs to check if two color tables correspond. Instead of
comparing the two entry by entry it just compares the ctSeed fields and assumes that if
the seeds match, so does everything else. Device seeds are maintained and updated by the
Color Manager. Pixmap tables copied from a device start with the same seed value. If
you are going to change the RGB colors in an offscreen table it needs a unique seed of its
own, so call GetCTSeed when it is first created. The seed does not need to change every
time the RGB colors change, just be unique.
When CopyBits is called with PixMap parameters, it checks the seed values of
the source and destination. If they are the same, the pixel values can be just blitted
across directly. If the seeds are different, CopyBits translates each source pixel value
into the best RGB match available in the destination table. The translation overhead is
not detectable, but despite this I saw a letter in MacTutor recommending setting the
source and destination seeds equal before a CopyBits. Don’t do it! If the seeds are
already the same, you gain nothing. If they are different, your source image will be
randomly recolored in the destination.
Down and Dirty Color Cycling
Color cycling an offscreen image is easy - you just store the new RGB color in
the table. Onscreen color cycling can be done through the Color Manager routines
Color2Index and SetEntries (code example below.) This method of color cycling is
frowned upon by the User Interface Thought Police because it is device dependent.
{1}
procedure colorCycle (oldRGB, newRGB : RGBColor);
var
newColor : ColorSpec;
colorPtr : ^CSpecArray;
devIndex : Integer;
begin
devIndex := Color2Index (oldRGB);
newColor.rgb := newRGB;
colorPtr := @newColor;
SetEntries (devIndex, 0, colorPtr^);
end;
The Palette Manager
The primary function of the Palette Manager is arbitrating between competing
demands when the number of colors is limited. It does this well, and every color
application should use it. The second function is device independent color table
animation, which has problems and is described in the next section.
The types of Palette Manager color reflect this division. Courteous and Tolerant
colors are for applications working in RGB space, where you specify a color and let the
hardware handle the details. Animated and Explicit colors are for applications that work
with pixel values and CLUTs.
A palette is a set of colors assigned to a window. When there are not enough colors
in the device CLUT to satisfy all the visible windows, the Palette Manager gives
priority to the frontmost window and then works back. Every time a new window,
including dialogs and alerts, is brought forward the Palette Manager reshuffles the
priorities and if necessary alters the device CLUT, causing those distracting changes in
the background.
Even a window without a palette can cause a change in the device CLUT on a 16
color system, which is quite irritating to watch. As per Tech Note #211 you can avoid