Upcoming Events
Unite 2010
11/10 - 11/12 @ Montréal, Canada

GDC China
12/5 - 12/7 @ Shanghai, China

Asia Game Show 2010
12/24 - 12/27  

GDC 2011
2/28 - 3/4 @ San Francisco, CA

More events...
Quick Stats
80 people currently visiting GDNet.
2406 articles in the reference section.

Help us fight cancer!
Join SETI Team GDNet!
Link to us Events 4 Gamers
Intel sponsors gamedev.net search:

Contents
 Loading Bitmaps
 Using the Blitter
 Color Keys
 IDirectDrawClipper
 Interface


 Get the source
 Printable version
 Discuss this article
 in the forums



The Series
 Beginning Windows
 Programming

 Using Resources
 in Win32 Programs

 Tracking Your
 Window/Using GDI

 Introduction
 to DirectX

 Palettes and Pixels
 in DirectDraw

 Bitmapped Graphics
 in DirectDraw

 Developing the
 Game Structure

 Basic Tile Engines
 Adding Characters
 Tips and Tricks

Introduction

At last, the good stuff! You already know enough to make a fully functioning Windows game, but it would have to use GDI. Today we're going to go over the DirectX implementations of everything you know how to do using GDI, and quite a bit more. I'm going to cover loading bitmaps, using the blitter to fill a surface with a color, and copying bitmaps like lightning, with clipping, color keys, and a host of other effects.

None of the material we'll be covering today will draw on the most recent article, so it's not necessary that you've read it. However, the section on pixel formats was very important, and I'll allude to it from time to time, so at least read that part. Aside from that, I assume you've read the first four articles in the series, and have the DirectX 7 SDK. All right? Fire up your compilers, ladies and gentlemen. :)

Loading Bitmaps

Believe it or not, you already know almost everything you need to load a bitmap onto a DirectDraw surface. How can that be? Well, the method you used under Windows GDI will work in DirectDraw as well, with only one little change. To refresh your memory a little bit, what we did was to retrieve a handle to the bitmap using LoadImage(), select the bitmap into a memory device context, and then use BitBlt() to copy the image from the memory DC to another DC, which was a display device context we had gotten with a call to GetDC(). If that destination DC was a device context to a DirectDraw surface, we'd have our DirectX bitmap loader all done! Thankfully, the IDirectDrawSurface7 interface provides a simple function for retrieving a device context:

HRESULT GetDC(HDC FAR *lphDC);

The return type is the same as for any DirectDraw function, and the parameter is just a pointer to an HDC which will be initialized with the device context handle if the function succeeds. Is that cool or what? This article just started and we can already load a bitmap onto one of our surfaces! Just remember that when you're all done with the surface device context, you need to release it. As you've probably guessed, this is achieved with the ReleaseDC() method of the surface interface:

HRESULT ReleaseDC(HDC hDC);

Just so you don't have to go rooting through the old articles, looking for the GDI bitmap loader, I'll show the modified version to you here. The only difference is that instead of taking a device context as a parameter, it takes a pointer to a DirectDraw surface. Then the function gets the device context from the surface, uses it to copy the image, and releases the device context.

int LoadBitmapResource(LPDIRECTDRAWSURFACE7 lpdds, int xDest, int yDest, int nResID)
{
  HDC hSrcDC;           // source DC - memory device context
  HDC hDestDC;          // destination DC - surface device context
  HBITMAP hbitmap;      // handle to the bitmap resource
  BITMAP bmp;           // structure for bitmap info
  int nHeight, nWidth;  // bitmap dimensions

  // first load the bitmap resource
  if ((hbitmap = (HBITMAP)LoadImage(hinstance, MAKEINTRESOURCE(nResID),
                                    IMAGE_BITMAP, 0, 0,
                                    LR_CREATEDIBSECTION)) == NULL)
    return(FALSE);

  // create a DC for the bitmap to use
  if ((hSrcDC = CreateCompatibleDC(NULL)) == NULL)
    return(FALSE);

  // select the bitmap into the DC
  if (SelectObject(hSrcDC, hbitmap) == NULL)
  {
    DeleteDC(hSrcDC);
    return(FALSE);
  }

  // get image dimensions
  if (GetObject(hbitmap, sizeof(BITMAP), &bmp) == 0)
  {
    DeleteDC(hSrcDC);
    return(FALSE);
  }

  nWidth = bmp.bmWidth;
  nHeight = bmp.bmHeight;

  // retrieve surface DC
  if (FAILED(lpdds->GetDC(&hDestDC)))
  {
    DeleteDC(hSrcDC);
    return(FALSE);
  }

  // copy image from one DC to the other
  if (BitBlt(hDestDC, xDest, yDest, nWidth, nHeight, hSrcDC, 0, 0,
             SRCCOPY) == NULL)
  {
    lpdds->ReleaseDC(hDestDC);
    DeleteDC(hSrcDC);
    return(FALSE);
  }

  // kill the device contexts
  lpdds->ReleaseDC(hDestDC);
  DeleteDC(hSrcDC);

  // return success
  return(TRUE);
}

This function is set to load from a resource, but you can easily modify it to load from an external file. Or better still, you could have it try to load a resource, and if the call fails, it retries as a file. Just remember to include the LR_LOADFROMFILE flag in the call to LoadImage(). The nicest thing about this function is that BitBlt() performs all the conversions on the pixel formats. That is, you can load a 24-bit bitmap into the memory device context, and blit it to a 16-bit surface, and all the colors will show up correctly, regardless of whether the pixel format is 565 or 555. Convenient, hey?

If you want to manipulate the actual bitmap data manually instead of simply using a function to copy, you have two options. First, you can use a modified version of the function above, and use the bmBits member of the BITMAP structure, which is an LPVOID pointing to the bits making up the image. Second, if you really want to have control of how the load is performed, you can write a function that opens the file and reads it in manually, using standard file I/O functions. To do that, you need to know the structure of a bitmap file. I'm not going to go through developing the whole function, since we already have the functionality we need, but I'll show you everything you need to do so.

The Bitmap File Format

The nice thing about writing a bitmap loader is that there are Win32 structures designed to hold the bitmap headers, so loading all the header info as simple as making a few calls to fread(). First up in a bitmap file is the bitmap file header, which contains general information about the bitmap. Not surprisingly, the structure that holds this header is called BITMAPFILEHEADER. Here's what it looks like:

typedef struct tagBITMAPFILEHEADER { // bmfh
  WORD    bfType;       // file type - must be "BM" for bitmap
  DWORD   bfSize;       // size in bytes of the bitmap file
  WORD    bfReserved1;  // must be zero
  WORD    bfReserved2;  // must be zero
  DWORD   bfOffBits;    // offset in bytes from the BITMAPFILEHEADER
                          // structure to the bitmap bits
} BITMAPFILEHEADER;

I'm not going to detail all the members; I've commented the structure to give you an idea of what everything is. Just use a call to fread() to read this in, and check the bfType member to make sure it is equal to the characters "BM" to ensure you're dealing with a valid bitmap. After that, there's another header file to read, called the info header. It contains image data like the dimensions, compression type, etc. Here's the structure:

typedef struct tagBITMAPINFOHEADER{ // bmih
  DWORD  biSize;           // number of bytes required by the structure
  LONG   biWidth;          // width of the image in pixels
  LONG   biHeight;         // height of the image in pixels
  WORD   biPlanes;         // number of planes for target device - must be 1
  WORD   biBitCount;       // bits per pixel - 1, 4, 8, 16, 24, or 32
  DWORD  biCompression;    // type of compression - BI_RGB for uncompressed
  DWORD  biSizeImage;      // size in bytes of the image
  LONG   biXPelsPerMeter;  // horizontal resolution in pixels per meter
  LONG   biYPelsPerMeter;  // vertical resolution in pixels per meter
  DWORD  biClrUsed;        // number of colors used
  DWORD  biClrImportant;   // number of colors that are important
} BITMAPINFOHEADER;

A few of the fields need some explanation. First, a note on compression. Most bitmaps that you'll be dealing with will be uncompressed. The most common type of compression for bitmaps is run-length encoding (RLE), but this is only used for 4-bit or 8-bit images, in which case the biCompression member will be BI_RLE4 or BI_RLE8, respectively. I'm not going to go into run-length encoding, but it's a fairly straightforward method of compression, so it's not too hard to deal with if you come across it.

Second, biClrUsed and biClrImportant will usually be set to zero in high-color bitmaps, so don't worry about them too much. The biSizeImage field may also be set to zero in some BI_RGB uncompressed bitmaps. Finally, the resolution fields are also unimportant for our purposes. Mainly the only things you're interested in from this structure are the width, height, and color depth of the image.

After you've read in the info header, if the bitmap has eight bits per pixel or less, it is palettized, and the palette information immediately follows the info header. The palette information, however, is not stored in PALETTEENTRY structures, but rather in RGBQUAD structures. An RGBQUAD looks like this:

typedef struct tagRGBQUAD { // rgbq
  BYTE    rgbBlue; 
  BYTE    rgbGreen; 
  BYTE    rgbRed; 
  BYTE    rgbReserved; 
} RGBQUAD; 

Don't ask me why the red, green, and blue intensities are stored in reverse order, but they are. Just read in the RGBQUADs, and transfer the data into an array full of PALETTEENTRYs to create a DirectDraw palette with. Remember to set the peFlags member of each PALETTEENTRY to PC_NOCOLLAPSE as you do so.

After the palette, or immediately following the info header if there is no palette, you'll find the actual image bits. You'll probably want to simply create a pointer and allocate enough memory to it to hold the image data, and then read it in. Just to be clear, I'll show you the code for doing this, assuming the info header is stored in a BITMAPINFOHEADER structure called info, and your file pointer is called fptr:

UCHAR* buffer = (UCHAR*)malloc(info.biSizeImage);
fread(buffer, sizeof(UCHAR), info.biSizeImage, fptr);

Just remember that Microsoft warns that biSizeImage may be set to zero in some cases, so check it before running code like the above. If it is set to zero, you'll have to calculate the size of the image by figuring out how many pixels comprise the image, and how many bytes are required for each pixel.

Writing your own bitmap loader isn't too bad, but if you want to avoid it, you can always use the code we developed back in the GDI article and modified at the beginning of this one. Now that that's over with, let's get into what DirectDraw's all about: using the blitter!




Next : Using the Blitter