Plotting Pixels in Non-palettized Direct Draw Surfaces

To plot a pixel in an 8-bit surface is simple. Lock the surface to get the surface pointer and equate the point you're working with to the colour supplied. SurfPtr[ X + Y * SurfacePitch ] = Colour.

But plotting a pixel to a non-palettized surface takes a little more work and thought. First you need to know what the pixel format of the surface is. The pixel format is just a way of saying what bits represent Red/Green/Blue in the surface. For example, common 16-bit surfaces include 0555 and 565, where in the first, the first bit is 0, and the remaining 15 bits represent red/green/blue, with 5 bits used for each. Because of the varying pixel formats, and the fact that you should handle all formats, it's useful to find this information during DirectDraw Initialization and store it for easy access any time you need to plot a pixel or draw a line, etc...

Finding the Pixel Format:


// These variables are set in StorePixelFormat and should be defined
// elsewhere(global, member variable, etc...)
WORD NumberRedBits, NumberGreenBits, NumberBlueBits;
WORD LowRedBit, LowGreenBit, LowBlueBit;

BOOL StorePixelFormat()
{
   // Define a PixelFormat variable, clear it and set the dwSize variable, as usual.
   DDPIXELFORMAT DDPixelFormat;
   ZeroMemory( &DDPixelFormat, sizeof( DDPixelFormat ));
   DDPixelFormat.dwSize = sizeof( DDPixelFormat );

   // Fill the PixelFormat from the information for the Primary Surface
   if( FAILED( PrimarySurface->GetPixelFormat( &DDPixelFormat)))
   {
      // Fatal error. The program should exit nicely
      return FALSE;
   }

   // Save the Low Bit and Number of Bits
   ProcessBits( DDPixelFormat.dwRBitMask, &LowRedBit, &NumberRedBits);
   ProcessBits( DDPixelFormat.dwGBitMask, &LowGreenBit, &NumberGreenBits);
   ProcessBits( DDPixelFormat.dwBBitMask, &LowBlueBit, &NumberBlueBits);

   return TRUE;
}

// Small utility function to find the LowBit and Number of Bits in a supplied Mask
void ProcessBits( DWORD Mask, WORD* LowBit, WORD* NumBits )
{
   DWORD TestMask = 1;
   for( *LowBit = 0; *LowBit < 32; ( *LowBit )++ )
   {
      if( Mask & TestMask )
         break;
      TestMask <<= 1;
   }

   TestMask <<= 1;
   for( *NumBits = 1; *NumBits < 32; ( *NumBits )++ )
   {
      if( !( Mask & TestMask ))
         break;
      TestMask <<= 1;
   }
}

You might wonder why I would want to save the Low Bit and Number of Bits for each colour. Well that's so that we can convert an RGB value to a Colour value with This function.

Converting RGB Values to Pixel Formatted Colour:


// Converts the supplied RGB values to a DWORD Pixel Formatted Colour
DWORD RGBToColour( DWORD Red, DWORD Green, DWORD Blue )
{
   // Supplied Values(0 - 255) are converted to Pixel Formatted Values
   // If 565 format and Red = 128, RedVal will become 16 since 128 / 256 == 16 / 32(5 bits)
   DWORD RedVal = ( Red * ( 1 << NumRedBits )) / 256;
   DWORD GreenVal = ( Green * ( 1 << NumGreenBits )) / 256;
   DWORD BlueVal = ( Blue * ( 1 << NumBlueBits )) / 256;

   // Convert the Values to the appropriate Location in the returned colour value
   RedVal <<= LowRedBit;
   GreenVal <<= LowGreenBit;
   BlueVal <<= LowBlueBit;

   return RedVal | GreenVal | BlueVal;
}

The RGBToColour function first converts a value from 0 - 255 to 0 - ( 1 << NumColourBits ). This is used most for 16 bit colour and possibly an alpha version of a 24-bit format. In a 24-bit 888 Pixel Format this is totally unnecessary since it would be converting from 0 - 255 to 0 - 255.

NOTE: I read on a mailing list that there might exist a 32-bit format such as 11-12-11. I have no idea if such a format exists, but it's possible. This function would not handle such a case howerver.

And now on to the whole point of this article, the plotting of the pixel. Since all this setup has given us a way to determine the colour to output, given an RGB value, the plotting is simple. To plot the pixel you need to lock a surface, change the value of a pixel and unlock the surface.

Plotting a Pixel:


void PutPixel( LPDIRECTDRAWSURFACE TempSurf, int X, int Y, int Red, int Green, int Blue )
{
   DDSURFACEDESC DDSurfDesc;
   BYTE* SurfPtr;

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

   TempSurf->Lock( 0, &DDSurfDesc, DDLOCK_WAIT, 0 );
   SurfPtr = ( BYTE* )DDSurfDesc.lpSurface;

   SurfPtr[ X + Y * DDSurfDesc.lPitch ] = RGBToColour( Red, Green, Blue );

   TempSurf->Unlock( SurfPtr );
}

NOTE/RANT: This method is VERY slow and wasteful. Some, but not all reasons include the following. Locking/unlocking can be slow and should be done as few times as possible. You could lock/unlock outside this function and supply the surface pointer instead. If you are doing this, and using a separate function that is also wasteful, since the only line in the function would be the SurfPtr[ ... ] = Colour line. Just throw it in your code instead. Lastly, if your colour is not changing with every new pixel, don't calculate it with every new pixel. If you use a similiar method for a line, you would only need one colour(supposedly).

NOTE: To change this to work with newer versions of DirectDraw, just change the Structures accordingly. For example, DX6 uses LPDIRECTDRAWSURFACE4 and DDSURFACEDESC2.

Well I hope this has helped someone. I've seen a bunch of newsgroup and mailing list messages about this topic and decided to put this together to help anyone that might be having problems. If you notice anything wrong with this example, or you have comments, let me know at alamar@netinc.ca

Thanks,
Nathan Davies

References: DirectX SDKs, Microsoft, and Stan Trujillo's High Performance Windows Graphics Programming, Coriolis Group Books.

Discuss this article in the forums


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

See Also:
DirectDraw

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