Topic : Pixel Plotting
Author : Ron Hiler
Page : 1 Next >>
Go to page :

Bit Depth Independent Pixel Plotting in Direct Draw
by Ron Hiler

Why Another Article on Direct Draw Pixel Plotting?

Do we really need yet another article on how to draw a pixel on the screen? Yes, I think we do. Consider the following possibility: You are writing a game for Windows. You need speed (and who doesnít?), so you are using DirectDraw surfaces to display your graphics. At this point, you have two options, Exclusive Mode or Windowed Mode. If you are running your application in Exclusive Mode, you can set the bit depth to any value you want. In that case, you donít have to worry about a general routine to plot pixels. You know exactly what bit depth the screen is in, and can write a routine specifically for that case.

But letís assume that youíre writing your application in Windowed Mode. In general, when you are in this mode, you cannot change the bit depth without a system reboot. Your end-users can be in any mode, from 8 to 32 bpp, and you have to support them all. Normally, end-users will get pretty upset if you force them to be in a specific bit depth. No one wants to have to reboot their computer just to play your game. This is exactly the situation I found myself in. I began to write a general pixel drawing routine. Then I got stuck, looked in vain for help on the web, asked a lot of questions, banged my head against the screen a lot, and finally, eventually, coded a working routine. I hope this article will prevent others from having to go through the same ordeal.

In this article, I will explain one possible method for correctly plotting a pixel, regardless of what bit depth the end-user is using, thereby keeping them happy, and making sure your game has a long and prosperous career on their hard drives. I am not, however, going to discuss 8 bpp mode. 8 bpp mode (or palletized mode) is a whole different beast than the higher bit modes. It is complicated, ungainly, and, for 256 colors, simply isnít worth the trouble. Further, this mode is quickly becoming obsolete, as users are moving to 16+ bpp modes. Most commercial games no longer support 8 bpp mode, and, in this day and age, it is perfectly reasonable to request your end user switch to a higher mode if 8 bpp or lower is detected.

What I am Assuming

I will assume you have some experience using DirectDraw, know how to link into the DirectX libraries, and can initialize and set up surfaces. If you are unclear on how to do this, there is a wealth of information on this subject available on the web. Also, I am assuming you are using a C++ compiler. If you are using a C compiler, you will need to modify all DirectDraw function calls to reference their method through a pointer to the object's vtable. For example, in C++, you might have:


while in C, you would modify this to read:

lpSurf->lpVtbl->GetPixelFormat(lpSurf, &DDpf);

The Problems Associated with Bit Depth Independent Pixel Plotting

The problem of putting a pixel on the screen via the DirectDraw Lock() function can be split into three distinct tasks. Each of these tasks is somewhat complicated by the fact that different values will be generated, depending on the userís current bit depth (for example, the same RGB value will generate different compiled pixel values in 16 bpp mode vs. 32 bpp mode).

The three tasks to plot a pixel are:

Compiling the pixel value from a RGB value.
Calculating the surface address.
Writing the pixel value to the surface.
I will discuss each of these tasks individually.

Compiling the Pixel Value

When Windows programmers deal with colors, they are typically used to handling RGB triplets. A RGB triplet is simply a series of three BYTE values representing the intensity of the red, green, and blue components of the color. Because these are BYTE values, each can range from 0-255. Unfortunately, the DirectDraw surface does not work in RGB. So, the first step in drawing a pixel to the screen is to convert your RGB triplet into a value the DirectDraw surface can understand.

When we talk about bits per pixel (bpp), what we mean is that for each pixel shown on the screen, that many bits are set aside on the drawing surface for the storage of the value representing that pixel. When a user is in 16 bpp, 2 bytes are reserved on the surface for each pixel. Similarly, if they are in 24 or 32 bpp, there are 3 or 4 bytes reserved (respectively). Because the bit depth of the surface can vary from machine to machine, we need a routine which will determine the current bit depth, and convert the RGB triplet into a value with the appropriate number of bytes.

A typical pixel compiler routine returns the compiled pixel value as a DWORD. This is reasonable, since the maximum number of bytes in a pixel is 4 (when the user is in 32 bpp). In lower bpp modes, the more significant bits are ignored, and are usually set to 0. However, this method presents a problem when trying to transfer that value to a 2, 3, or 4 bpp surface. A WORD typecast could be used for 2 byte surface, but that still leaves the 3 byte surface as problematic. For this reason, the routine I am going to use first compiles the pixel into a DWORD value, then splits up the four bytes into separate variables. In this manner, the surface can be accessed using a BYTE pointer, and each byte of the pixel written separately.

The first step in compiling a pixel value is to determine which bits on the DirectDraw surface correspond to which color intensities of the desired pixel. Fortunately, DirectDraw helps us out with this with the GetPixelFormat() function. GetPixelFormat() fills in a structure (DDPIXELFORMAT) with quite a bit of information about the DirectDraw surface, including the individual color component bitmasks. A bitmask is nothing more than a series of flags showing which bits control which color components of the pixel. This sounds a lot more complicated than it actually is, and an example should serve to quickly clear up any confusion. Assume the userís machine is set at 24 bpp, with 8 bits each for each of the three colors (which is typically the case at 24 bpp). If you implemented the following code (with your surface called lpSurf):


    ZeroMemory (&DDpf, sizeof(DDpf));
    DDpf.dwSize = sizeof(DDpf);

you would get the following values stored in the structure

    DDpf.dwRBitMask   0b 0000 0000 1111 1111 0000 0000 0000 0000 (or 0x 00ff 0000)
    DDpf.dwGBitMask   0b 0000 0000 0000 0000 1111 1111 0000 0000 (or 0x 0000 ff00)
    DDpf.dwBBitMask   0b 0000 0000 0000 0000 0000 0000 1111 1111 (or 0x 0000 00ff)

on a different machine, set at 16 bpp (with 5-6-5 bits for red, green, and blue, respectively), you would get

    DDpf.dwRBitMask   0b 0000 0000 0000 0000 1111 1000 0000 0000 (or 0x 0000 f800)
    DDpf.dwGBitMask   0b 0000 0000 0000 0000 0000 0111 1110 0000 (or 0x 0000 07e0)
    DDpf.dwBBitMask   0b 0000 0000 0000 0000 0000 0000 0001 1111 (or 0x 0000 001f)

this is the key to differentiating between different user's surface depths with a single routine. Notice the first set stores all of the color values in 3 bytes, while the second set stores the same color in 2.

The following routine uses the color's bitmasks to calculate values which we will use to compile the pixel value. Assuming you pass a particular colorís bitmask to the routine, it will give you both the shift value (i.e. the number of zeros on the right of the bitmask), and the precision (the number of ones in the bitmask). We will use this routine to calculate our compiled pixel later on.

int GetMaskInfo (DWORD Bitmask, int* lpShift)
    int Precision, Shift;

    Precision = Shift = 0;
    //count the zeros on right hand side
    while (!(Bitmask & 0x01L))
        Bitmask >>= 1;
    //count the ones on right hand side
    while (Bitmask & 0x01L)
        Bitmask >>= 1;
    *lpShift = Shift;
    return Precision;

At this point, this routine should be fairly self explanatory. If

Page : 1 Next >>