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
97 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
 Device Contexts
 Tracking
 Paint Message
 Closing Your
 Application

 Plotting Pixels
 GDI Text Functions
 Displaying Bitmaps
 One Last Thing

 Demo program
 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

Displaying Bitmaps With GDI

Remember when I told you that bitmaps are easy to work with, because they're native to Windows? Well now we're going to find out just how easy it is. :) There are four basic steps to displaying a bitmap with GDI:

  1. Get a device context to your window.
  2. Obtain a handle to the bitmap.
  3. Create a device context for the bitmap.
  4. Copy the image from one device context to the other.

You already know how to do the first step. I alluded to the second one last time, but didn't go over it. I said that there was a function called LoadBitmap() that retrieves a handle to a bitmap resource. However, this function is obsolete now; it has been superseded by LoadImage(), which is much more flexible. So that's what we'll be using. Here she is:

HANDLE LoadImage( HINSTANCE hinst, // instance containing the image LPCTSTR lpszName, // name or identifier of image UINT uType, // type of image int cxDesired, // desired width int cyDesired, // desired height UINT fuLoad // load flags );

The function returns NULL if it fails. Otherwise, you get a handle to the bitmap, which can either be loaded from a resource or from an external file. Notice that since this function can be used for bitmaps, cursors, or icons, the return type is simply HANDLE. In Visual C++ 6.0, you'll need to include a typecast to HBITMAP or the compiler will become angry with you. Here are the parameters for the function:

HINSTANCE hinst: This should be the instance of your application if you're loading a resource, or NULL if you want to load from an external file.

LPCTSTR lpszName: This is either the resource identifier -- remember to use MAKEINTRESOURCE() if you're using numerical constants -- or the full filename of the image you want to load.

UINT uType: Depending on what you want to load, this should be set to either IMAGE_BITMAP, IMAGE_CURSOR, or IMAGE_ICON.

int cxDesired, cyDesired: These are the desired dimensions for the image to be loaded. If you set them to zero, the image's actual dimensions will be used.

UINT fuLoad: Like everything else we've done today, this is one or more of a series of flags which can be logically combined with the | operator. Here are the useful flags:

LR_CREATEDIBSECTION If uType is IMAGE_BITMAP, this causes the function to return a DIB section rather than a compatible bitmap. (DIB stands for device-independent bitmap.) This basically means to use all of the bitmap's own properties rather than making it conform to the properties of the display device.
LR_DEFAULTSIZE For icons and cursors, if cxDesired and cyDesired are set to 0, this flag causes the system metric values for icons and cursors to be used, rather than the actual dimensions of the image.
LR_LOADFROMFILE You must specify this flag if you want to load from a file rather than a resource.

For loading bitmaps you should use LR_CREATEDIBSECTION, and LR_LOADFROMFILE if it is appropriate. Now that you have obtained a handle to your image, you must create a device context and load the bitmap into it. The first step is taken by calling CreateCompatibleDC(), as follows:

HDC CreateCompatibleDC(HDC hdc);

The parameter is a DC with which to make the new DC compatible. If you pass NULL, the DC will be made compatiable with the display screen, which is what we want. The return value is a handle to a memory device context -- not a display device context! This means that the contents of this DC won't be visible. If the function fails, the return value is NULL. Now, to get the bitmap into the memory device context, we use this:

HGDIOBJ SelectObject( HDC hdc, // handle to device context HGDIOBJ hgdiobj // handle to object );

The type HGDIOBJ is more general than our HBITMAP, so never fear, they're compatible without any tricks on our part. Here are the parameters:

HDC hdc: This is a handle to the device context which we want to fill with an object. For loading bitmaps, this must be a memory device context.

HGDIOBJ hgdiobj: And this is a handle to that object. This function is used with bitmaps, brushes, fonts, pens, and regions; but the only one that concerns us is bitmaps.

The return value is a handle to the object that is being replaced in the DC, or NULL if an error occurs. The return values are different for regions, but like I said, we don't care about regions. :)

Now you've got a bitmap loaded into a DC, and you need only take the last step: copying the contents of the memory device context to our display device context. However, it's necessary to obtain some information about the bitmap, such as its dimensions, which must be used in the function call that will display the image. For that, we need the GetObject() function, which is used for obtaining information about graphical objects such as bitmaps.

int GetObject( HGDIOBJ hgdiobj, // handle to graphics object of interest int cbBuffer, // size of buffer for object information LPVOID lpvObject // pointer to buffer for object information );

The return value is the number of bytes successfully obtained, or 0 for function failure. The parameters for the function are the following:

HGDIOBJ hgdiobj: The handle to the graphics object we want information on. In this case, pass the handle to the bitmap we loaded.

int cbBuffer: This is the size of the structure receiving the information. In the case of loading bitmaps, the receiving structure is of type BITMAP, so set this to sizeof(BITMAP).

LPVOID lpvObject: Pass the address of the structure receiving the information.

You need to define a variable of type BITMAP, and with a quick call to the GetObject() function, you'll have the information you need. Since the BITMAP structure is new to us, I'll show you what it looks like:

typedef struct tagBITMAP { // bm LONG bmType; LONG bmWidth; LONG bmHeight; LONG bmWidthBytes; WORD bmPlanes; WORD bmBitsPixel; LPVOID bmBits; } BITMAP;

There aren't too many members to this thing, and we're really only interested in two of them, but I'll list them all here anyway.

LONG bmType: This is the bitmap type and must be set to zero. Useful, isn't it?

LONG bmWidth, bmHeight: These are the two we're after -- the width and height of the bitmap, in pixels.

LONG bmWidthBytes: Specifies the number of bytes in each line of the bitmap. Note that the number of bytes per pixel can be obtained by dividing this value by bmWidth.

LONG bmPlanes: This is the number of color planes.

LONG bmBitsPixel: This is the number of bits required to represent one pixel. It would appear that the note I left about figuring this out is useless. :)

LPVOID bmBits: If you want to access the actual image data, this is a pointer to the bit values for the bitmap.

All right, almost done! Now we have the bitmap in a memory device context, and we know its dimensions. All we have to do is copy it from one DC to the other, and that only takes a single function call. See, I told you bitmaps were easy to deal with. There are actually two options you can use here. I'll show you both of them.

BOOL BitBlt( HDC hdcDest, // handle to destination device context int nXDest, // x-coordinate of destination rectangle's upper-left corner int nYDest, // y-coordinate of destination rectangle's upper-left corner int nWidth, // width of destination rectangle int nHeight, // height of destination rectangle HDC hdcSrc, // handle to source device context int nXSrc, // x-coordinate of source rectangle's upper-left corner int nYSrc, // y-coordinate of source rectangle's upper-left corner DWORD dwRop // raster operation code );

The return value is TRUE or FALSE based on whether the function succeeds. You've seen that plenty of times before. There are a lot of parameters, but most of them are pretty easy to figure out.

HDC hdcDest: The destination device context handle. In our case, this will be the display device context for our window.

int nXDest, nYDest: The coordinates of the upper-left hand corner of the region where the bitmap will end up. Remember that for our DC, these coordinates are client coordinates -- relative to the client area of our window.

int nWidth, nHeight: These are the width and height of the destination and source rectangles, since this function doesn't perform scaling. Pass the dimensions of the bitmap.

HDC hdcSrc: This is the source device context handle. In our case, this is the memory device context that our bitmap is currently residing in.

int nXSrc, nYSrc: The x- and y- coordinates of the source rectangle's upper-left corner. In this case you would use (0, 0), but this may not always be the case, depending on what you're using the source DC for, or if you only want to copy a part of the image.

DWORD dwRop: There are a lot of operation codes you can use here, most of them dealing with Boolean operations on the data in the two device contexts. The only one we're interested in is SRCCOPY, which copies the contents of the source DC directly to the destination DC.

That's about all there is to it! The other option you have is to use StretchBlt(), which requires you to specify the width and height for both the source and destination rectangles. StretchBlt() then scales the image in the source DC to fit in the rectangle specified on the destination DC. This can be useful for scaling images, but as always, since BitBlt() has fewer capabilities to worry about, it's faster. Here is the prototype for StretchBlt():

BOOL StretchBlt( HDC hdcDest, // handle to destination device context int nXOriginDest, // x-coordinate of upper-left corner of dest. rectangle int nYOriginDest, // y-coordinate of upper-left corner of dest. rectangle int nWidthDest, // width of destination rectangle int nHeightDest, // height of destination rectangle HDC hdcSrc, // handle to source device context int nXOriginSrc, // x-coordinate of upper-left corner of source rectangle int nYOriginSrc, // y-coordinate of upper-left corner of source rectangle int nWidthSrc, // width of source rectangle int nHeightSrc, // height of source rectangle DWORD dwRop // raster operation code );

The parameters are basically the same as those in BitBlt(), so I shouldn't have to go through them again. The raster operation codes for StretchBlt() are the same as those for BitBlt(); just set it to SRCCOPY. Now, the last thing you need to know is a little bit of cleanup. Creating a DC like we did for this example is not quite like getting a DC for the display device. Instead of calling ReleaseDC(), you must call DeleteDC(). It looks basically the same:

BOOL DeleteDC(HDC hdc);

The parameter is simply the device context to delete, and the return value is a BOOL again, and we all know what that means, right? All right, are you feeling pretty good about all this stuff? Just for the sake of tying all these steps together, I'll show you a function you can use for loading and displaying a bitmap resource using all GDI functions. For this example, I'm assuming you have saved the handle to the instance of the application in a global called hinstance.

int ShowBitmapResource(HDC hDestDC, int xDest, int yDest, int nResID) { HDC hSrcDC; // source DC - memory 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) return(FALSE); // get image dimensions if (GetObject(hbitmap, sizeof(BITMAP), &bmp) == 0) return(FALSE); nWidth = bmp.bmWidth; nHeight = bmp.bmHeight; // copy image from one DC to the other if (BitBlt(hDestDC, xDest, yDest, nWidth, nHeight, hSrcDC, 0, 0, SRCCOPY) == NULL) return(FALSE); // kill the memory DC DeleteDC(hSrcDC); // return success! return(TRUE); }



Next : One Last Thing