16-Bit Pixels
by Sam Christiansen

After learning a few directx basics, I decided I wanted to learn more about 16-bit surfaces, and, more specifically, a routine to plot a pixel. After a bit of searching, I came upon two different articles that helped me understand the basics of 16 bit pixels. For reference, the first article was written by Chris Barnes of Angelic-Coders.com, and it was titled "16 Bit Pixel Plotting." The second article, "16 Bit With Directdraw," was written by Hugh Hunkin. I would like to thank Mr. Barnes and Mr. Hunkin for their articles, and for allowing me to use some of their source code in this article. I decided to write this new article about 16 bit pixels, because I found that a combination of the techniques provided by Mr. Barnes and Mr. Hunkin was perhaps easier to understand.

Pixel Formats

First, I'd like to say a few words about 16-bit directdraw surfaces. Each 16-bit pixel on the surface holds red, green, and blue values. Since 16 is not evenly divided by 3, the most common way to hold pixel information is in the 5-6-5 format. This means the red value gets the first 5 bits, green the next 6 bits, and blue the next 5 bits. Using the 5-6-5 format, we have a total of 65,536 different colors (2^16). We have 32 shades of red, 64 shades of green, and 32 shades of blue. If the 5-6-5 format was universal, things would be much easier. However, we have to accomodate for the possibility that their is a different pixel format.

A Word About Bitmasks and Shifting

Many of you may already know about bitmasks and shifting. If so, skip to the next section. If you are confused by bitmasks, or have no idea what I'm talking about, read on. A bitmask is a variable that will allow us to extract the red, green, or blue component from our 16 bit value. We use logical operations such as OR (|) or AND (&) on the two variables to create a new variable with the color component. For example, suppose we have a 5-6-5 pixel encoded like this:

red:    5 bits,    00011
green:  6 bits,    001011
blue:   5 bits,    00101

All together, our 16-bit pixel will look like this:

0001 1001 0110 0101		(note the spacing is just to make it easier to read)

That looks pretty confusing. We can see where the colors are, but how do we extract them? Well, this is where our bitmask comes in. Since red is 5 bits, we can use the binary number

1111 1000 0000 0000

and a logical AND operation to extract the 5 bits representing the red value. Here is the work:

0001 1001 0110 0101 &
1111 1000 0000 0000
0001 1000 0000 0000

Now we can see that the new 16-bit value clearly holds our red value, but you'll notice that it isn't in the right place. If we were to read the value now, it would not be a number between 1 and 31 as we would expect. We need to use shifting (>> or <<) to move the 5-bit value so that its least significant digit is in the right spot. You'll notice that there are 11 zeroes tp the right of our least significant digit. So, we shift the variable to the right by 11. Here is the work:

0001 1000 0000 0000 >> 11 =
0000 0000 0000 0011

Now we finally have the red value of our pixel stored so we can read it. Here's some sample psuedo code for what we just did.

// suppose "pixel" contains the value above
BYTE red;
red = ((pixel & 0xF800) >> 11);

Now red will hold the 5 bit value for the red component of the pixel. Notice our bitmask, 0xF800 is encoded in hexidecimel. This is just so we don't have to write out all those zeroes.

The important thing to see here, is that we can use bitmasks and shifting to decode a pixel, and to encode a pixel. Sample code for both of these operations is given below.

How Do We Find Out The Pixel Format?

Chris Barnes suggests in his article to use a GetRGB16 function. I've modified this function in a way that I think is simpler to understand. Basically, this procedure takes as input a directdraw surface and uses the directdraw function GetSurfaceDesc() to get a surface description, and returns the pixel format to us. An important note: the procedures shown here and below ASSUME that the structure of the pixel is red-green-blue. While the majority of video cards will use the RGB format, it is also possible that a video card could use any possible combination of RGB (ie, BGR, GRB, BRG, etc). If you want to make your code work on 100% of the video cards out there, you will have to modify the GetRGB16() function and the Plot_Pixel() function.

Here is my GetRGB16 function:

// you need this struct for the getrgb16 proc
// note:  you could even yank the bitmask variables out of this
// if you find you aren't using them in any functions of your own
typedef struct
    DWORD dwRBitMask,
    RGBQUAD Position;   //  RGBQUAD is a data structure included with MSVC++5.0
} RGB16;

    //get a surface description
    ddsd.dwSize = sizeof( ddsd );
    ddsd.dwFlags = DDSD_PIXELFORMAT;
    if (Surface->GetSurfaceDesc ( &ddsd ) != DD_OK )
	return FALSE;

    // will increment r,g, or b for each 1 in it's bitmask, which will give
    // us the number of bits used for each color
    char r=0, g=0, b=0;
    for(int n = 1; n<65536; N<<=1)
        IF(ddsd.ddpfPixelFormat.dwRBitMask & N)
        IF(ddsd.ddpfPixelFormat.dwRBitMask & N)
        IF(ddsd.ddpfPixelFormat.dwRBitMask & N)

    RGB16->dwRBitMask = ddsd.ddpfPixelFormat.dwRBitMask;
    rgb16->Position.rgbRed = g + b;

    rgb16->dwGBitMask = ddsd.ddpfPixelFormat.dwGBitMask;
    rgb16->Position.rgbGreen = b;

    rgb16->dwBBitMask = ddsd.ddpfPixelFormat.dwBBitMask;
    rgb16->Position.rgbBlue = 0;

    return TRUE;

Now that we have our format taken care of, here is my Plot_Pixel routine:

// Must have the surface locked, pass the surface description 
// that gets returned from the lock call
void PlotRGB(int x, int y, int r, int g, int b, DDSURFACEDESC *ddsd, RGB16 *rgb16)
    WORD Pixel;
    Pixel = (r << RGB16->Position.rgbRed) |
        (g << RGB16->Position.rgbGreen) |
        (b);    //  once again, assuming format is RGB

    WORD *pixels = (WORD *)ddsd->lpSurface;
    DWORD pitch = ddsd->dwWidth;
    pixels[y*pitch + x] = Pixel; 

This PlotPixel assumes that dwWidth is equal to the pitch of the surface. Mr. Barnes uses

DWORD pitch = ddsd->lPitch >> 1;

instead of

DWORD pitch = ddsd->dwWidth;

Which will produce the exact results on most surfaces. I thought that for this example, dwWidth made a little more sense for this example. Just keep that in mind if you use this function.


How do we speed up this function? Well, the function takes the RGB values and encodes them into a 16-bit value. Suppose we have already encoded a 16-bit value in the pixel format. For example, suppose in your particle engine, the pixels you are plotting don't change color. Then it may definitely be worth it to encode the color once and store it as a 16-bit value, rather than sending RGB values to the PlotPixel function. Here's the function:

// Must have the surface locked, pass the surface description 
// that gets returned from the lock call
void PlotPixel(int x, int y, WORD RGB, DDSURFACEDESC *ddsd)
    WORD *pixels = (WORD *)ddsd->lpSurface;
    DWORD pitch = ddsd->dwWidth;
    pixels[y*pitch + x] = RGB; 

One final easy optimization I see would be to get rid of the y*pitch. Most of the time, we have a well defined pitch. If we KNEW our app was running in 640x480, and we were only writing to the screen, we could make a table of pitch values like this:

int ytable[480];

for (int i=0;i<480;I++) YTABLE[I]="640" * I;

Assuming ytable was a global, we simply change the last line of the function:

pixels[ytable[y] + x] = RGB;

I hope this has cleared up any 16 bit pixel questions you had. I don't claim that this code is the fastest or best, I just present it as something that runs adequately and can be used to get started. Here is my 16-bit pixel libray: pixel.h, pixel.cpp. Here is a simple program I wrote that takes advantage of the pixel library. Please, feel free to use, distribute, and improve my code. Just send me an e-mail, and give me credit where it's due. If you have any questions, comments, or if you want to put this article on your web site, please e-mail me.

1998 Sam Christiansen

Reprinted with permission

Discuss this article in the forums

Date this article was posted to GameDev.net: 7/25/1999
(Note that this date does not necessarily correspond to the date the article was written)

See Also:

© 1999-2011 Gamedev.net. All rights reserved. Terms of Use Privacy Policy
Comments? Questions? Feedback? Click here!