Color-modeling in 256-color mode
Journal: Dr. Dobb's Journal August 1992 v17 n8 p149(7)
-----------------------------------------------------------------------------
Title: Color-modeling in 256-color mode. (program development techniques
for VGA graphics) (Graphics Programming) (Column)
Author: Abrash, Michael.
AttFile: Program: GP-AUG92.ASC Source code listing.
Program: XSHRP21.ZIP X-Sharp library for 3D graphics.
Abstract: When developing computer graphics software, it is necessary to
define a set of colors that fit within the 256-color limit of the
VGA display standard. The first step is to select a working color
model. RGB is a good choice for modeling colors that are
generated by light sources. Colors are defined in terms of a
triplet code that specifies one of 256 levels of intensity of red,
blue and green. While there are over 16 million possible
combinations of color, the programmer must define a palette of 256
colors for use with VGA. One method of defining a palette is to
program an application that utilizes a subset of RGB space,
biasing the the VGA palette towards that subspace. Another method
is to define colors by utilizing only two primaries. Programmers
can also use one part of the palette for a range of colors and use
the rest of the palette for fixed colors of non-shaded objects.
While 256-color VGA presents limits, careful programming around
these limitations can yield nearly 24-bit color quality.
-----------------------------------------------------------------------------
Descriptors..
Topic: VGA Standard
Modeling
RGB
Program Development Techniques
Computer Graphics.
Feature: illustration
chart
program.
-----------------------------------------------------------------------------
Full Text:
Lately, my daughter has wanted some fairly sophisticated books read to her.
Wind in the Willows. Little House on the Prairie. Pretty heady stuff for a
six year old, and sometimes I wonder how much of it she really understands.
As an experiment, during today's reading I stopped whenever I came to a word
I thought she might not know, and asked her what it meant. One such word was
"mulling."
"Do you know what 'mulling' means?" I asked.
She thought about it for a while, then said, "Pondering."
"Very good!" I said, more than a little surprised.
She smiled and said, "But, Dad, how do you know that I know what 'pondering'
means?"
"Okay," I said, "What does 'pondering' means?"
"Mulling," she said.
What does this anecdote tell us about the universe in which we live? Well,
it certainly indicates that this universe is inhabited by at least one
comedian and one good straight man. Beyond that, though, it can be construed
as a parable about the difficulty of defining things properly; for example,
consider the complications inherent in the definition of color on a 256-color
display adapter such as the VGA. Coincidentally, VGA color modeling just
happens to be this month's topic, and the place to start is with color
modeling in general.
A Color Model
We've been developing X-Sharp, a realtime 3-D animation package, for several
months now. Last month, we added illumination sources and shading; that
addition makes it necessary for us to have a general-purpose color model, so
that we can display the gradations of color intensity necessary to render
illuminated surfaces properly. In other words, when a bright light is
shining straight at a green surface, we need to be able to display bright
green, and as that light dims or tilts to strike the surface at a shallower
angle, we need to be able to display progressively dimmer shades of green.
The first thing to do is to select a color model in which to perform our
shading calculations, the dot product-based stuff I discussed last month.
The approach we'll take is to select an ideal representation of the full
color space and do our calculations there, as if we really could display
every possible color; only as a final step will we map each desired color
into the limited 256-color set of the VGA, or the color range of whatever
adapter we happen to be working with. There are a number of color models
that we might choose to work with, but I'm going to go with the one that's
both most familiar and, in my opinion, simplest: RGB (red, green, blue). In
the RGB model, a given color is modeled as the mix of specific fractions of
full intensities of each of the three color primaries. For example, the
brightest possible pure blue is 0.0*R, 0.0*G, 1.0*B. Half-bright cyan is
0.0*R, 0.5*G, 0.5*B. Quarter-bright gray is 0.25*R, 0.25*G, 0.25*B. You can
think of RGB color space as being a cube, as shown in Figure 1, with any
particular color lying somewhere inside or on the cube.
RGB is good for modeling colors generated by light sources, because red,
green, and blue are the additive primaries; that is, all other colors can be
generated by mixing red, green, and blue light sources. They're also the
primaries for color computer displays, and the RGB model maps beautifully
onto the display capabilities of 15- and 24-bpp display adapters, which tend
to represent pixels as RGB combinations in display memory.
How, then, are RGB colors represented in X-Sharp? Each color is represented
as an RGB triplet, with eight bits each of red, green, and blue resolution,
using the structure shown in Listing One (page 166). That is, each color is
described by three color components--on each for red, green, and blue--and
each primary color component is represented by eight bits. Zero intensity of
a color component is represented by the value O, and full intensity is
represented by the value 255. This gives us 256 levels of each primary color
component, and a total of 16,772,216 possible colors.
Holy cow! Isn't 16,000,000-plus colors a bit of overkill?
Actually, no, it isn't. At the eighth Annual Computer Graphics Show in New
York, this past January, Sheldon Linker, of Linker Systems, related an
interesting tale about color perception research at the Jet Propulsion Lab
back in the '70s. The JPL color research folks had the capability to print
more than 50,000,000 distinct and very precise colors on paper. As a test,
they tried printing our words in various colors, with each word printed on a
background that differed by only one color index from the word's color. No
one expected the human eye to be able to differentiate between two colors,
out of 50,000,000-plus, that were so similar. It turned out, through, that
everyone could read the words with no trouble at all; the human eye is
surprisingly sensitive to color gradations, and also happens to be wonderful
at detecting edges.
When the JPL team went to test the eye's sensitivity to color on the screen,
they found that only about 16,000,000 colors could be distinguished, because
the color-sensing mechanism of the human eye is more compatible with
reflective sources such as paper and ink than with emissive sources such as
CRTs. Still, the human eye can distinguish about 16,000,000 colors on the
screen. That's not so hard to believe, if you think about it; the eye senses
each primary color separately, so we're really only talking about detecting
256 levels of intensity per primary here. It's the brain that does the
amazing part; the 16,000,000-plus color capability actually comes not from
extraordinary sensitivity in the eye, but rather from the brain's ability to
distinguish between all the mixes of 256 levels of each of three primaries.
So it's perfectly reasonable to maintain 24 bits of color resolution, and
X-Sharp represents colors internally as ideal, device-independent 24-bit RGB
triplets. All shading calculations are performed on these triplets, with
24-bit color precision. It's only after the final 24-bit RGB drawing color
is calculated that the display adapter's color capabilities come into play,
as the X-Sharp function ModelColorToColorIndex() is called to map the desired
RGB color to the closest match the adapter is capable of displaying. Of
course, that mapping is adapter dependent. On a 24-bpp device, it's pretty
obvious how the internal RGB color format maps to displayed pixel colors:
directly. On VGAs with 15-bpp Sierra Hicolor DACs, the mapping is equally
simple, with the five upper bits of each color component mapping straight to
display pixels. But how on earth do we map those 16,000,000-plus RGB colors
into the 256-color space of a standard VGA?
This is the "color definition" problem I mentioned at the start of the
column. The VGA palette is arbitrarily programmable to any set of 256
colors, with each color defined by six bits each of red, green, and blue
intensity. In X-Sharp, the function InitializePalette() can be customized to
set up the palette however we wish; this gives us nearly complete flexibility
in defining the working color set. Even with infinite flexibility, however,
256 out of 16,000,000 or so possible colors is a pretty puny selection. It's
easy to set up the palette to give yourself a good selection of just blue
intensities, or of just greens; but for general color modeling there's simply
not enough palette to go around.
One way to deal with the limited simultaneous color capabilities of the VGA
is to build an application that uses only a subset of RGB space, then bias
the VGA's palette toward that subspace. This is the approach used in the
DEMO1 sample program in X-Sharp; Listings Two and Three (page 166) show the
versions of InitializePalette() and ModelColorToColorIndex() that set up and
perform the color mapping for DEMO1. In DEMO1, three-quarters of the palette
is set up with 64 intensity levels of each of the three pure primary colors
(red, green, and blue), and then most drawing is done with only pure primary
colors. The resulting rendering quality is very good, because there are so
many levels of each primary.
The downside is that this excellent quality is available for only three
colors: red, green, and blue. What about all the other colors that are mixes
of the primaries, like, say, cyan or yellow, to say nothing of gray? In the
DEMO1 color model, any RGB color that is not a pure primary is mapped into a
2-2-2 RGB space that the remaining quarter of the VGA's palette is set up to
display; that is, there are exactly two bits of precision for each color
component, or 64 general RGB colors in all. This is genuinely lousy color
resolution, being only 1/64th of the resolution we really need for each color
component. In this model, a staggering 262,144 colors from the 24-bit RGB
cube map to each color in the 2-2-2 VGA palette. The results are not
impressive; the colors of mixed-primary surfaces jump abruptly, badly
damaging the illusion of real illumination. To see how poor a 2-2-2 RGB
selection can look, run DEMO1, and press the '2' key to turn on spotlight 2,
the blue spotlight. Because the ambient lighting is green, turning on the
blue spotlight causes mixed-primary colors to be displayed--and the result
looks terrible, because there just isn't enough color resolution.
Unfortunately. 2-2-2 RGB is close to the best general color resolution the
VGA can display.
Another approach would be to set up the palette with reasonably good mixes of
two primaries but no mixes of three primaries, then use only two-primary
colors in your applications (no grays or whites or other three-primary
mixes). Or you could choose to shade only selected objects, using part of
the palette for a good range of the colors of those objects, and reserving
the rest of the palette for the fixed colors of the other, nonshaded objects.
Jim Kent, author of Autodesk Animator, suggests dynamically adjusting the
palette to the needs of each frame, for example by allocating the colors for
each frame on a first-come, first-served basis. That wouldn't be trivial to
do in real time, but it would make for extremely efficient use of the
palette.
The sad truth is that the VGA's 256-color palette is an inadequate resource
for general RGB shading. The good news is that clever workarounds can make
VGA graphics look nearly as good as 24-bpp graphics; the burden falls on you,
the programmer, to design your applications and color mapping to compensate
for the VGA's limitations. To experiment with a different 256-color model in
X-Sharp, just change InitializePalette() to set up the desired palette and
ModelColorToColorIndex() to map 24-bit RGB triplets into the palette you've
set up. It's that simple, and the results can be striking indeed.
Where to Get X-Sharp
The full source for X-Sharp is available in the file XSHP n.ARC in the DDJ
Forum on CompuServe, and as XSHARPn .ZIP in both the programming/graphics
conference on M&T Online and the graphic.disp conference on Bix.
Alternatively, you can send me a 360K or 720K formatted diskette and an
addressed, stamped diskette mailer, care of X-Sharp, DDJ, 411 Borel Ave., San
Mateo, CA 94402, and I'll send you the latest copy of X-Sharp. There's no
charge, but it'd be very much appreciated if you'd slip in a dollar or so to
help out the folks at the Vermont Association for the Blind and Visually
Impaired.
I'm available on a daily basis to discuss X-Sharp on M&T Online and Bix (user
name mabrash in both cases).
Fast VGA Text
This next item comes from The BitMan. (That's how he asked to be described;
don't ask me why.) The BitMan passed along a nifty application of the VGA's
under-appreciated write mode 3 that is, under the proper circumstances, the
fastest possible way to draw text in any 16-color VGA mode.
The task at hand is illustrated by Figure 2. We want to draw what's known as
solid text, in which the effect is the same as if the cell around each
character was drawn in the background color, and then each character was
drawn on top of the background box. (This is in contrast to transparent
text, where each character is drawn in the foreground color without
disturbing the background.) Assume that each character fits in an eight-wide
cell (as is the case with the standard VGA fonts), and that we're drawing
text at byte-aligned locations in display memory.
Solid text is useful for drawing menus, text areas, and the like; basically,
it can be used whenever you want to display text on a solid-color background.
The obvious way to implement solid text is to fill the rectangle representing
the background box, then draw transparent text on top of the background box.
However, there are two problems with doing solid text this way. First,
there's some flicker, because for a little while the box is there but the
text hasn't yet arrived. More important is that the
background-followed-by-foreground approach accesses display memory three
times for each byte of font data: once to draw the background box, once to
read display memory to load the latches, and once to actually draw the font
pattern. Display memory is incredibly slow, so we'd like to reduce the
number of accesses as much as possible. With The BitMan's approach, we can
reduce the number of accesses to just one per font byte, and eliminate
flicker, too.
The keys to fast solid text are the latches and write mode 3. The latches,
as you may recall from earlier discussions in this column, are four internal
VGA registers that hold the last bytes read from the VGA's four planes; every
read from VGA memory loads the latches with the values stored at that display
memory address across the four planes. Whenever a write is performed to VGA
memory, the latches can provide some, none, or all of the bits written to
memory, depending on the bit mask, which selects between the latched data and
the drawing data on a bit-by-bit basis. The latches solve half our problem;
we can fill the latches with the background color, then use them to draw the
background box. The trick now is drawing the text pixels in the foreground
color at the same time.
This is where it gets a little complicated. In write mode 3 (which
incidentally is not available on the EGA), each byte value that the CPU
writes to the VGA does not get written to display memory. Instead, it turns
into the bit mask. (Actually, it's ANDed with the Bit Mask register, and the
result becomes the bit mask, but we'll leave the Bit Mask register set to
0xFF, so the CPU value will become the bit mask.) The bit mask selects, on a
bit-by-bit basis, between the data in the latches for each plane (the
previously loaded background color, in this case) and the foreground color.
Where does the foreground color come from, if not from the CPU? From the
Set/Reset register, as shown in Figure 3. Thus, each byte written by the CPU
(font data, presumably) selects foreground or background color for each of
eight pixels, all done with a single write to display memory.
I know this sounds pretty esoteric, but think of it this way. The latches
hold the background color in a form suitable for writing eight background
pixels (one full byte) at a pop. Write mode 3 allows each CPU byte to punch
holes in the background color provided by the latches, holes through which
the foreground color from the Set/Reset register can flow. The result is
that a single write draws exactly the combination of foreground and
background pixels described by each font byte written by the CPU. It may
help to look at Listing Four (page 167), which shows The BitMan's technique
in action. And yes, this technique is absolutely worth the trouble; it's
about three times faster than the fill-then-draw approach described above,
and about twice as fast as transparent text. So far as I know, there is no
faster way to draw text on a VGA.
It's important to note that The BitMan's technique only works on full bytes
of display memory. There's no way to clip to finer precision; the background
color will inevitably flood all of the eight destination pixels that aren't
selected as foreground pixels. This makes The BitMan's technique most
suitable for monospaced fonts with characters that are multiples of eight
pixels in width, and for drawing to byte-aligned addresses; the technique can
be used in other situations, but is considerably more difficult to apply.
At this point, some of you are no doubt nodding your heads and saying, "Yes,
I see how that would work." Others are probably muttering, "Well, heck, I
knew that; tell me something new." Then there are the rest of you, the VGA
neophytes, the ones with glazed eyes, who think this technique sounds
interesting, but understand maybe 30 percent of what you just read. Where
can you turn for help?
Read on.
Useful VGA Reading
For years, I've recommended Richard Wilton's Programmer's Guide to PC and
PS/2 Video Systems (Microsoft Press, 1987, $24.95, ISBN 1-55615-103-9) as a
VGA-programming reference, not because it's perfect, but because it was the
only VGA book I knew of that was good enough to be useful. I've added
another book to my good-enough-to-get list: Programmer's Guide to the EGA
and VGA Cards, Second Edition, by Richard Ferraro (Addison-Wesley, 1990,
$29.95, ISBN 0-201-57025-4). This 1000-plus page tome has a wide variety of
valuable VGA information, ranging from registers to BIOS functions to the
specifics of seven manufacturers' Super-VGA implementations, and it has
plenty of good figures. This is, without question, a useful book. However,
it is not (sigh), the ultimate VGA reference I've awaited for five years.
The book is not error-free, especially regrettable in a second edition; for
example, the polarity of the bits in the Color Don't Care register are
reversed in the discussion of read mode 1. Also, although write mode 3 and
the latches are covered, they're discussed in considerably less detail than
I'd like to see; you'd have a tough time figuring out why The BitMan's
technique works from this book alone. And surely Ferraro knows that you have
to read display memory to load the latches before the bit mask can do its job
of protecting selected pixels within a destination byte, because he shows
line-drawing code that does just that. Still, he keeps saying that the bit
mask keeps destination pixels from being modified, as if that happens even if
you don't read display memory first. Nonetheless, I've found this book
useful when I've reached for it, and I've found myself reaching for it
increasingly often, and that's the real test of any reference. If you're a
PC graphics programmer, you should probably have this book on your shelf.
[BACK] Back
Discuss this article in the forums
See Also: © 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
|