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


just set the high byte to 0. The next eight bits are the intensity of red, the eight following that are for green, and the low byte is for blue.

A pixel in 32-bit color needs to be 32 bits in size, and so the variable type we use to hold one is a UINT, which is an unsigned integer. Usually I use macros to convert RGB data into the correct pixel format, so let me show you one here. Hopefully if you're a little confused at this point, this will clear things up a bit:

#define RGB_32BIT ((r << 16) | (g << 8) | (b))

As you can see, this macro creates a pixel value by shifting the bytes representing red, green, and blue to their appropriate positions. Is it starting to make sense? To create a 32-bit pixel, you can just call this macro. Since red, green, and blue have eight bits each, they can range from 0 to 255. To create a white pixel, you would do this:

UINT white_pixel = RGB_32BIT(255, 255, 255);

24-bit color is just about the same. As a matter of fact, it is the same, except without the alpha information. The pixel format looks like this:

RRRR RRRR GGGG GGGG BBBB BBBB

So red, green, and blue still have eight bits each. This means that 24-bit color and 32-bit color actually have the same number of colors available to them, but 32-bit color just has some added information for transparency. So if you don't need the extra info, 24-bit is better than 32-bit, right? Well, not exactly. It's actually kind of a pain to deal with, because there's no data type that's 24 bits. So to write a pixel, instead of just writing in one value, you have to write red, green, and blue each individually. Working in 32-bit color is probably faster on most machines, even though it requires more memory. In fact, many video cards don't support 24-bit color at all, because having each pixel take up three bytes is just too inconvenient.

Now, 16-bit color is a bit tricky, because not every video card uses the same pixel format! There are two formats supported. One of them, which is by far more common, has five bits for red, six bits for green, and five bits for blue. The other format has five bits for each, and the high bit is unused. This is used mostly on older video cards. So the two formats look like this:

565 format: RRRR RGGG GGGB BBBB
555 format: 0RRR RRGG GGGB BBBB

When you're working in a 16-bit color depth, you'll need to determine whether the video card uses 565 or 555 format, and then apply the appropriate technique. It's kind of a pain, but there's no way around it if you're going to use 16-bit color. Since there are two different formats, you'd write two separate macros:

#define RGB_16BIT565 ((r << 11) | (g << 5) | (b))
#define RGB_16BIT555 ((r << 10) | (g << 5) | (b))


In the case of 565 format, red and blue can each range from 0 to 31, and green ranges from 0 to 63. In 555 format, all three components range from 0 to 31. So setting a pixel to white in each mode would look like this:

USHORT white_pixel_565 = RGB_16BIT565(31, 63, 31);
USHORT white_pixel_555 = RGB_15BIT555(31, 31, 31);


The USHORT data type is an unsigned short integer, which is 16 bits long. This whole business of having two pixel formats makes things a bit confusing, but when we actually get into putting a game together, you'll see that it's not as bad as it seems at first. By the way, sometimes 555 format is referred to as 15-bit color. So if I call it that sometime later on, you'll know what I'm talking about instead of thinking I made a typo. :)

Here is probably a good place to show you just how exactly how determine whether a machine is using the 555 or 565 format when you're running in 16-bit color. The easiest way to do it is to call the GetPixelFormat() method of the IDirectDrawSurface7 interface. Its prototype looks like this:

HRESULT GetPixelFormat(LPDDPIXELFORMAT lpDDPixelFormat);

The parameter is a pointer to a DDPIXELFORMAT structure. Just declare one, initialize it, and pass its address. The structure itself is huge, so I'm not going to list it here. Instead, I'll just tell you about three of its fields. The members in question are all of type DWORD, and they are dwRBitMask, dwGBitMask, and dwBBitMask. They are bit masks that you logically AND with a pixel value in order to extract the bits for red, green, or blue, respectively. You can also use them to determine what pixel format you are dealing with. If the video card uses 565, dwGBitMask will be 0x07E0. If it uses 555 format, dwGBitMask will be 0x03E0.

Now that we've seen all the pixel formats you can encounter, we can get into actually showing graphics in DirectX. About time, wouldn't you say? Before we can manipulate the actual pixels on a surface, though, we need to lock the surface, or at least a part of it. Locking the surface will return a pointer to the memory the surface represents, so then we can do whatever we want with it.

Locking Surfaces
Not surprisingly, the function we'll use to do this is IDirectDrawSurface7::Lock(). Let's take a look at it.

HRESULT Lock(
    LPRECT lpDestRect,                
    LPDDSURFACEDESC lpDDSurfaceDesc,
    DWORD dwFlags,
    HANDLE hEvent
);


Remember to check the function call for success or failure, or it could lead to problems. If the lock fails, then the pointer to the surface won't be correct, and who knows what part of memory you'd be messing with then? The parameters for this function are:

LPRECT lpDestRect: This is a RECT that represents the area on the surface we want to lock. If you want to lock the entire surface, simply pass NULL.

LPDDSURFACEDESC2 lpDDSurfaceDesc: This is a pointer to a DDSURFACEDESC2 structure, which is the big baddie we covered last time. All you need to do is initialize the structure, then pass the pointer. If Lock() succeeds, it fills in some of the members of the structure for you to use.

DWORD dwFlags: What kind of DirectX function would this be if it didn't have a list of flags do go along with it? Here are the most useful ones, which you can combine in the usual way:

DDLOCK_READONLY Indicates that the surface being locked will only be read from, not written to.
DDLOCK_SURFACEMEMORYPTR Indicates that a valid memory pointer to the upper-left corner of the specified RECT should be returned in the DDSURFACEDESC2 structure. Again, if no RECT is specified, the pointer will be to the upper-left corner of the surface.
DDLOCK_WAIT If a lock cannot be obtained because a blit is in progress, this flag indicates to keep retrying until a lock is obtained, or a different error occurs.
DDLOCK_WRITEONLY Indicates that the surface being locked will only be written to, not read from.

Since we'll be using the lock to manipulate pixels, you'll always want to use DDLOCK_SURFACEMEMORYPTR. And even though we haven't gotten to using the blitter yet, it's usually a good idea to include DDLOCK_WAIT as well.

HANDLE hEvent: This parameter is not used and should be set to NULL.

Once we lock the surface, we need to take a look at the DDSURFACEDESC2 structure to get some information about the surface. We went over all of the members of this structure last time, but there are only two that we need to concern ourselves with at this point. Since both are very important, I'll list those two here again.

LONG lPitch: The lPitch member represents the number of bytes in each display line. You'd think this would be obvious. For example, in 640x480x16, there are 640 pixels in each line, and each one requires 2 bytes for color information, so the pitch (also called the "stride") should be 1280 bytes, right? Well, on some video cards, it will be greater than 1280. The extra memory on each line doesn't hold any graphical data, but sometimes it's there because the video card can't create a perfectly linear memory mode for the display mode. This will happen on a very small percentage of video cards, but you need to take it into account.

LPVOID lpSurface: This is a pointer to the memory represented by the surface. No matter what display mode you're using, DirectDraw creates a linear addressing mode you can use to manipulate the pixels of the surface.

The lpSurface pointer is pretty easy to understand, but are you following me on this whole pitch thing? It's an important value to remember, because you'll have to use it to calculate the offset to

Page : << Previous 2  Next >>