IntroductionToday 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 PaletteBefore 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:
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:
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. |
|||||||||||||||||||||