Topic : Palettes and Pixels in DirectDraw
Author : Joseph Farrell
Page : << Previous 4  Next >>
Go to page :


Direct3D, which supports alpha-blending, to set each frame as a texture, then set the transparency level. Or, even easier, you can use the DirectDraw color/gamma controls. But if you only want to fade part of the screen, or fade to a color besides black, and you're not a Direct3D expert -- I'm certainly not! -- then knowing how to do the manual transform might come in handy.

Basically all you do is to read each pixel and break it up into red, green, and blue. Then you multiply each of the three values by the level of fading to apply, recombine the RGB values, and write the new color back out to the buffer. Sound complicated? It's not too bad. Take a look at this (highly unoptimized) sample code. It applies a fade to a viewport of size 200x200 in the upper-left corner of the display, in a 16-bit color depth using the 565 pixel format.

void ApplyFade16_565(float pct, USHORT* buffer, int pitch)
{
    int x, y;
    UCHAR r, g, b;
    USHORT color;

    for (y=0; y<200; y++)
    {
        for (x=0; x<200; x++)
        {
            // first, get the pixel
            color = buffer[y*pitch + x];

            // now extract red, green, and blue
            r = (color & 0xF800) >> 11;
            g = (color & 0x0730) >> 5;
            b = (color & 0x001F);

            // apply the fade
            r = (UCHAR)((float)r * pct);
            g = (UCHAR)((float)g * pct);
            b = (UCHAR)((float)b * pct);

            // write the new color back to the buffer
            buffer[y*pitch + x] = RGB_16BIT565(r, g, b);
        }
    }
}


Now, there are a number of things wrong with this function. First, not only is the calculation of the pixel location inside the inner loop, it's in there twice! You can get away with calculating it once in the entire function, but in this example it's calculated 80,000 times. :) Here's what you should do. At the very top of the function, you should declare another USHORT*, and set it equal to buffer. Inside the inner loop, increment the pointer by one to get to the next pixel. And inside the outer loop, increment it by whatever it takes to get down to the next line. Here's the considerably better example:

void ApplyFade16_565(float pct, USHORT* buffer, int pitch)
{
    int x, y;
    UCHAR r, g, b;
    USHORT color;
    USHORT* temp = buffer;
    int jump = pitch - 200;

    for (y=0; y<200; y++)
    {
        for (x=0; x<200; x++, temp++) // move pointer to next pixel each time
        {
            // first, get the pixel
            color = *temp;

            // now extract red, green, and blue
            r = (color & 0xF800) >> 11;
            g = (color & 0x0730) >> 5;
            b = (color & 0x001F);

            // apply the fade
            r = (UCHAR)((float)r * pct);
            g = (UCHAR)((float)g * pct);
            b = (UCHAR)((float)b * pct);

            // write the new color back to the buffer
            *temp = RGB_16BIT565(r, g, b);
        }

        // move pointer to beginning of next line
        temp+=jump;
    }
}


This is better. The value of jump is the number of USHORTs from the end of a line on the 200-pixel-wide viewport to the beginning of the next line. Still, there's not much you can do to speed up those floating-point multiplies and all the extracting/recombining of colors. Or is there? Take a look at this:

USHORT clut[65536][20];

If you asked a DOS programmer to put an array that size into one of his programs, he'd probably cry out in pain. He might even drop dead on the spot, or spontaneously combust. But in Windows, it's not really a problem if you need to do it, because you have all the system's free RAM available to you. Wouldn't it be nice if you could replace the entire inner loop with this one line?

*temp = clut[*temp][index];

That would speed things up a bit, right? :) Instead of passing a float to the function, you could pass an integer that's between 0 and 100. If the value is 100, no fade is needed, so return without doing anything. If the value is 0, just use a ZeroMemory() call to do all the work. Otherwise, divide the value by 5, and use it for the second subscript into the giant lookup table.

If you're wondering where I got the dimensions for the lookup table, 65536 is 2^16, so that's how many colors there are in a 16-bit color depth. Since our color values are unsigned values, they range from 0 to 65535. The 20 is just the number of increments you'd be using for a fade. It seemed a decent choice to me, considering the memory involved.

For 24-bit and 32-bit color, you obviously can't create a lookup table to span every color combination, so what you could do is use three smaller tables:

UCHAR red[256];
UCHAR green[256];
UCHAR blue[256];


Then, when you read each pixel, split it up into its color components, look each component up in its table, and recombine them into the resulting color. There are all sorts of ways to do things like this. The best way to determine what works well for your purposes is just to throw some code together, and then play around with it for awhile. With that in mind, I will briefly go over one other transformation you might find useful.

Basic Transparency
Overlaying a transparent image on an opaque one is not something you can use a lookup table for, because it would have to have 65,536 entries in both dimensions. It's going to be awhile before the average computer has the 8.6 GB of RAM it takes to hold that monster. :) So you'll have to do all the calculations for each pixel. I'll give you the basic idea. Suppose you want to place image A on top of image B, and image A is to have a transparency percentage of pct, which is a floating-point number between 0 and 1, where 0 is fully transparent (invisible) and 1 is fully opaque. Then, let's call a pixel in image A pixelA, and its counterpart in image B we'll call pixelB. You would apply the following equation:

color = (pixelA * pct) + (pixelB * (1-pct));

Basically, this is just a weighted average of the two pixel colors. By multiplying the colors, I mean to multiply the intensities for red, green, and blue. So you're actually looking at six floating-point multiplications per pixel. You can use some small lookup tables to lighten the workload a little. Experiment with it!

The other thing you might want to do is to create a window of a solid color that's partially transparent. If you've seen a demo or screenshots of my upcoming RPG, Terran, you know what I mean. An effect like that can be done entirely with a lookup table, because in Terran's case, I just needed to provide a

Page : << Previous 4  Next >>