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


Introduction
Today we're going to go over basic DirectDraw graphics using both palettized and RGB modes. What's the difference? Well, a palettized screen mode is probably what you're used to if you've been programming in DOS. To plot pixels, you write a single byte to the video memory. That byte is an index into a lookup table full of colors, which is called a palette. RGB modes are different, because there's no lookup table. To plot a pixel in an RGB display mode, you write the values for red, green, and blue directly into the video memory. Any color depth higher than 8-bit is RGB instead of palettized. But now we're getting ahead of ourselves!

In writing this article, I assume you have either read my previous entries in the series, or are otherwise familiar with setting up DirectDraw and creating surfaces. We'll be using version 7 of DirectX, which contains the most recent DirectDraw interfaces. Actually, on a side note, the DirectDraw interfaces in DirectX 7 will be the last ones released! Don't worry, future versions of DirectX will still be backwards-compatible, but future implementations will have DirectDraw and Direct3D integrated into one component. But let's not worry about that now. :)

One last thing I will mention before we get started: the information on palettes is not necessary for anything I'll be covering later in the series, so if you're not interested in palettized modes, feel free to skip over the first part of this article and pick up when we get to the Pixel Formats section. Now that my disclaimers are at an end, let's get going!

Creating a DirectDraw Palette
Before working with graphics in a color depth of 8-bit or less, you must create a palette, which is simply a table full of colors. More specifically, in DirectX, a palette is a table full of PALETTEENTRY structures. To create one, there are three steps we need to take:

Create the color lookup table.
Get a pointer to an IDirectDrawPalette interface.
Attach the palette to a DirectDraw surface.
I'll assume that we're using 8-bit color for the rest of this section; if you wanted to write a game with 16 colors (or less), you wouldn't be reading up on all this crazy Windows stuff, right? Anyway, in 8-bit color depth, you have a palette with 256 entries. So to create our lookup table, we'll need 256 of these:

typedef struct tagPALETTEENTRY { // pe
    BYTE peRed;
    BYTE peGreen;
    BYTE peBlue;
    BYTE peFlags;
} PALETTEENTRY;


The first three members are fairly obvious; they are the intensities of red, green, and blue in the given color. Each one can range from 0 to 255, as BYTE is an unsigned data type. The last member of the structure is a control flag and should be set to PC_NOCOLLAPSE. Basically what this does is guarantees that your colors will be preserved as they are, rather than being matched to an existing system palette. We've all seen what MS Paint does when you try to save a picture as an 8-bit image. You want that happening to your game? I didn't think so. :)

Now, as I said, we're going to need a bunch of these structures, so the logical thing to do is to declare an array that we'll use for the lookup table. It would look like this:

PALETTEENTRY palette[256];

So now that we've got our array, you can load in your colors. When I'm working in a palettized mode, I'll usually have my colors saved in an external file, and use something like this to load the colors in:

FILE* file_ptr;
int x;

if ((file_ptr = fopen("palette.dat", "rb")) != NULL)
{
    fread(palette, sizeof(PALETTEENTRY), 256, file_ptr);
    fclose(file_ptr);
}


All right, that does it for step one. Now we need to get the palette interface. This is accomplished by a call to IDirectDraw7::CreatePalette(), which looks like this:

HRESULT CreatePalette(
    DWORD dwFlags,                          
    LPPALETTEENTRY lpColorTable,            
    LPDIRECTDRAWPALETTE FAR *lplpDDPalette,  
    IUnknown FAR *pUnkOuter
);


The return type is the standard HRESULT we're used to, so you can use the FAILED() and SUCCEEDED() macros to test whether or not the call succeeds. The parameters are as follows:

DWORD dwFlags: Any number of a series of flags describing the palette object. As usual, you can combine the values with the | operator. The valid flags are:

DDPCAPS_1BIT 1-bit color; this corresponds to a 2-color palette.
DDPCAPS_2BIT 2-bit color; this corresponds to a 4-color palette.
DDPCAPS_4BIT 4-bit color; this corresponds to a 16-color palette.
DDPCAPS_8BIT 8-bit color; this corresponds to a 256-color palette.
DDPCAPS_8BITENTRIES Indicates that the index refers to an 8-bit color index. That is, each color entry is itself an index to a destination surface's 8-bit palette. It must be combined with DDPCAPS_1BIT, DDPCAPS_2BIT, or DDPCAPS_4BIT. This is called an indexed palette. Weird, hey?
DDPCAPS_ALPHA Indicates that the peFlags member of each PALETTEENTRY should be interpreted as an alpha value. Palettes created with this flag can only be attached to a Direct3D texture surface, since DirectDraw itself doesn't support alpha-blending.
DDPCAPS_ALLOW256 Allows all 256 entries of an 8-bit palette to be used. Normally, index 0 is reserved for black, and index 255 is reserved for white.
DDPCAPS_INITIALIZE Indicates that the palette should be initialized with the values of an array of PALETTEENTRYs that is passed in another parameter of CreatePalette().
DDPCAPS_PRIMARYSURFACE Indicates that the palette will be attached to the primary surface, so that altering it immediately changes the colors of the display.
DDPCAPS_VSYNC Forces palette changes to take place only during the vertical blank, minimizing the odd effects normally associated with palette-changing during a drawing cycle. This is not fully supported.

For the most part, you'll want to use DDPCAPS_8BIT | DDPCAPS_INITIALIZE, though you can take out the latter if you just want to create an empty palette and set it up later. Or you may want to add DDPCAPS_ALLOW256 if you really want to change the two normally reserved entries.

LPPALETTEENTRY lpColorTable: This is a pointer to the lookup table we created, so just pass the name of the array.

LPDIRECTDRAWPALETTE FAR *lplpDDPalette: This is the address of a pointer to an IDirectDrawPalette interface, which will be initialized if the call succeeds.

IUnkown FAR *pUnkOuter: As always, this is for advanced COM stuff and should be set to NULL.

That's not too bad, so now we can go ahead and create our palette object. The last step is to attach that palette to a surface, which requires only a simple function call to IDirectDrawSurface7::SetPalette(). The function is shown below:

HRESULT SetPalette(LPDIRECTDRAWPALETTE lpDDPalette);

Pretty straightforward, isn't it? All you do is pass the interface pointer we got in the last step. So, to put a quick example together, let's assume we already have a lookup table created in an array called palette, like we did above. To create a DirectDraw palette to hold the colors, and attach it to our primary surface (which has also been created already), we do the following:

LPDIRECTDRAWPALETTE lpddpal;

// create the palette object
if (FAILED(lpdd7->CreatePalette(DDPCAPS_8BIT | DDPCAPS_INITIALIZE,
                                palette, &lpddpal, NULL)))
{
    // error-handling code here
}

// attach to primary surface
if (FAILED(lpddsPrimary->SetPalette(lpddpal)))
{
    // error-handling code here
}


And that's all there is to it. Once your palette is set up, plotting pixels is not too different from the way it's done in RGB modes. From this point on, I'll be covering both RGB and palettized modes. Before we get into actually displaying graphics, though, I need to show you what RGB pixel formats are like.

Pixel Formats
As I said earlier, hen you're writing a pixel into memory in a palettized mode, you write one byte at a time, and each byte represents an index into the color lookup table. In RGB modes, however, you write the actual color descriptors right into memory, and you need more than one byte for each color. The size of the memory write is equivalent to the color depth; that is, for 16-bit color, you write two bytes (16 bits) for each pixel, and so on. Let's start out at the top, because it's easiest to understand. 32-bit color uses a pixel format like this, where each letter is one bit:

AAAA AAAA RRRR RRRR GGGG GGGG BBBB BBBB

The As are for "alpha," which is a value representing transparency. Those are for Direct3D though; like I said, DirectDraw doesn't support alpha blending. So when you're creating a 32-bit color for DirectDraw,

Page : 1 Next >>