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


Date this article was posted to GameDev.net: 7/16/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:
Michael Abrash's Articles

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!