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.