Modifying the VGA palette
Basic DAC Operations
Working with indexed colors
VGA modes are "indexed color modes," as opposed to modern "direct color modes." It means that instead of directly picking an RGB color and applying it to the screen, we refer to an index in a predefined palette instead. The length of the palette depends on the graphic mode employed. In EGA, we only have 16 free slots (or attributes) we can work with, while VGA offers up to 256 programmable attributes. The index of the first attribute is always 0 (it's a good idea to fill this attribute with black as the video buffer is usually cleared with zeros.)
Just like direct color modes, each attribute in the palette is composed of three primary colors: red, green and blue (in that order.) In standard VGA, each RGB component is defined as a 6-bit intensity ranging from 0 (mute) to 63 (very intense.) It means that we have 262,144 different RGB combinations (although only 256 of them can be used at once in Mode 13.) As a side note, the length in bits of primaries can be cranked up in VESA and SVGA modes, while modern hardware usually defines intensities with a full byte (ranging from 0 to 255). Some engines and libraries even go one step further by using normalized components (0.0 to 1.0) to unify the color definition process.
It's important to understand that color indices are not actually colors, but reference to attributes in the palette. Think of it as a paint-by-the-number system. Some advanced palette techniques will either change colors within the palette without changing the numbers, while others will keep the palette intact and modify numbers as needed.
That's all you've got?
When it comes to modifying the palette, there are only two built-in color manipulation instructions in QuickBASIC: PALETTE (which tweaks one attribute with RGB triplets), and PALETTE USING (which tweaks the entirety of the 256-color palette via a 1024 bytes long array.) As far as I know, there's no built-in function to retrieve the RGB components for a given attribute. Virtually no one uses those functions because honestly, they are a bit shit (slow, cumbersome, not flexible enough;) thus, finding example code for these two abominations is borderline impossible, so allow me to take one for the team before we move on to...
Setting up a custom palette
Every tutorial out there will tell you the exact same thing: "Why don't you feed your colors to the DAC (Digital/Analog Converter)? It's super fast!" And I'm going to tell you the exact same thing too. But how do we do that exactly? First things first, let's have a look at the different ports we'll be meddling with:
The Palette Mask Register is used to mask the attribute indices we can access via the DAC Control Read Register and DAC Control Write Register. To make sure we can access all attributes, we'll send it the value &hFF (you may only need to do this once as soon as you change graphic mode.) DAC Control Read Register and DAC Control Write Register take the attribute index we want to access for reading and writing respectively. To get or set the color primaries (RGB triplet), we use the DAC Control Data Register three times in a row (once for red, once for green, and once more for blue.) This port is read and write.
Sending information directly to the DAC is so easy and common I still have to see one QuickBASIC game using the built-in PALETTE function instead. Like explained previously, we specify which color we want to access by feeding its index to the DAC Control Write Register, and then we call the DAC Control Data Register three times in a row to pass each primary.
You've noticed that we never explicitly state which primary color we're feeding the Data Register; We don't have to (and can't) because the DAC is smart enough to keep track of what data still needs to be provided. Furthermore, it will reset its queue upon invoking the DAC Control Write Register. In other words, every time we send data to &h3C8, the DAC will assume the next call to &h3C9 will be red (and then green, and blue.) But wait! There's more!
The attribute index also automatically increments after the DAC Control Data Register has been called three times, so we don't even have to specify the color index within the loop!
Reading palette information
Since there's no built-in function to retrieve palette information, we'll use the DAC Control Read Register and write one! This register is the counter-part of the DAC Control Write Register and works the exact same way too: invoking &h3C7 forces the DAC to expect red in next &h3C9 call, and the color index increments with every three consecutive calls to &h3C9. And with that, we can retrieve RGB intensities of any given attribute. Before we move to more interesting things, let's write basic save and load routines.
For this next trick, we'll need to modify colors a bit. So let's use two well-known formulas to do it. The first turns color to grayscale: gray = r * .299 + g * .587 + b * .114... and the second turns colors to sepia: r2 = r * .393 + g * .769 + b * .189, g2 = r * .349 + g * .686 + b *.168, b2 = r * .272 + g * .534 + b * .131 (values must be capped to prevent overflow.) Now, let's use them to tweak the existing palette.
The initial code is a little bloated. We'll fix it by making a distinction between palette color transformations and DAC operations. Now, differentiating these two steps may seem like unnecessary complexity, but it will ultimately provide much more flexibility (I've said it before and I'll say it again: "it really depends on what you're trying to achieve.")
Consider palette fading for a moment. In most programs I've seen, two routines (I swear I searched, but only could find a fade out routine) would exist to handle palette fading, even though both routines do the exact same thing: they gradually modify each color in the source palette so they become the color in the target palette. In fact, it's even simpler than that since the source is always the palette currently being used.
Doing a distinction between the palette data and its application allows us to modify color information on the side, and then apply the end result (either directly, or progressively,) like so.
With the palette fading effect, we've seen that once a color attribute is modified, the result instantly appears on the screen. The same way, palette shifting (also called "color cycling") relies entirely on the DAC and is (almost) instantaneous since it doesn't require writing to (or reading from) video memory. It is especially useful for animating special effects such as fire, smoke, water ripples, moving mist, twinkling stars, etc. In short, a few consecutive attributes are filled with a mirrored color gradient and are "shifted" at a regular time interval. Here's some sample code using the standard VGA palette to produce palette shifting.
The two following screenshots of "Sam & Max Hit The Road" show how multiple asynchronous palette shifts can be used to achieve amazing water effects. In the first screenshot, only six consecutive frames were captured, which makes the animation jerky except for some ripples to the left of Sam's face. The second screenshot attempts to get a better feel for the animation, but some shifts are still misaligned (see the wave under the black bush to the left of the alligator's hat, or the water in the shadow of his tail.)
If you ever played early Mortal Kombat games, you've noticed that ninjas are using the exact same sprite set, only drawn in different colors. Maybe you've seen the same effect in the various flavors of Liztroops in Duke Nukem 3D or in Super Mario Bros. on the NES (the clouds and bushes, Mario and Luigi, the various powerup effects are all palette swaps.)
This effect isn't obtained via palette manipulation (there's no programming of the DAC involved,) but via filters instead. While the palette contains RGB triplets for each attribute available (256 in mode 13), the filter doesn't contain actual colors but rather attribute indices. Since indexed-color modes are "paint-by-the-number" systems, placing a filter between pixel indices and palette attributes will allow us to remap indices (those contained within the sprite) to any color. Here's an example showing how to do that.
Transparency and color blending
Pixel color manipulation is much easier in direct color modes (at least 16 bits per pixel) because the color of each pixel can be set individually, and so, new colors can be created as needed. In indexed color modes however, the palette must be built to make blending possible (it should have smooth color gradients, visually similar tones, etc.) The second part of the job is the creation of a lookup table, which will both insure that all colors can be blended together, but also improve performances since there's no computation required at run-time.
The lookup table is similar to the filter we used for palette swap, except it's two-dimensional instead of linear (two indices are used to retrieve one result, unlike palette swap which only needs one index.) Since they are two-dimensional, lookup tables require more memory: for a 256 color palette, you'd need to store 256x256 bytes of information! To keep things in check, this example only generates a 16x16 transparency lookup table using all 256 colors available in the palette (any of the first 16 colors of the palette can be blended together.)
That's all folks
There are others things that could be touched on, such as ordering the palette to create faster effects (like blur, dynamic fire and grayscale transparency) or technical specifications of palette files... but I'll leave it at that for now. For good measure, I'm dropping this file, which contains what I would consider the most helpful code snippets on this topic (there's no error check, use your brain,) have fun.
Other pages you might find useful: