Developing a Bitmap Class


Although I could provide you with a handful of bitmap functions and set you on your way, bitmaps provide a perfect opportunity to take advantage of object-oriented programming. More specifically , you can create a class that includes all the code required to load and draw bitmaps, and then use the class to create bitmap objects that are much easier to use than if you had created several functions. Knowing this, the next couple of sections are devoted to the design and development of a bitmap class that you will use throughout the book in just about every example program from here on. Although the bitmap class is admittedly a little tricky in places, once created it is unbelievably easy to use. So, the idea is to create the class once with the knowledge that it will make your life incredibly easier in coming hours.

How It Will Work

The idea behind the Bitmap class is to provide a means of loading bitmaps from a file or resource, as well as drawing bitmaps to a device context. By incorporating these capabilities into a class, you'll be able to create Bitmap objects in your games that are very easy to use and that hide the messy aspects of working with bitmaps. The Bitmap class has the following requirements:

  • Loads a bitmap from a file

  • Loads a bitmap from a resource

  • Creates a blank bitmap

  • Draws a bitmap to a device context

  • Obtains the width and height of a bitmap

The first two requirements are fairly obvious from the earlier discussion. The third requirement isn't terribly important just yet, but you might find yourself wanting to create a blank bitmap for some reason, so why not have that capability? Besides, later in Hour 10, "Making Things Move with Sprites," you'll see how bitmaps are important for solving a flicker problem associated with animation; the solution requires blank bitmaps. The final requirement isn't terribly important, but you might encounter a situation in which it's necessary to determine the width and height of a bitmap. The information is readily available when you load a bitmap, so you might as well make it accessible from the Bitmap class.

Putting the Code Together

The code for the Bitmap class is admittedly complex in places, so I'm not suggesting that you expect to understand every nuance of it immediately. In fact, my goal isn't really to make you a bitmap expert, which is practically a requirement in order to fully understand the Bitmap class. Even so, I don't like the idea of accepting code at face value without any explanation, so I'd like for you to have a general understanding of what is going on in the Bitmap class. If I gloss over a section of code, just understand that the goal here is to get you using bitmaps in as little time as possible so that you can quickly move on to placing them in games.

The Class Definition

Win32 defines several data structures that pertain to bitmaps, so you'll see several different ones throughout the Bitmap class code. One of these structures is BITMAPINFOHEADER , which is a structure that stores the header information associated with a bitmap. When you read a bitmap from a file or resource, you will store its header in a BITMAPINFOHEADER structure. Another important bitmap- related structure is RGBQUAD , which is used to store the four 8-bit components of a 32-bit color . Even though you're working with 8-bit images, it's still necessary to use the RGBQUAD structure for loading bitmaps.

Speaking of 8-bit images, you know that an 8-bit bitmap requires up to 256 colors. The following code is for a custom structure named BITMAPINFO_256 that stores the header and a color table for a bitmap with 256 colors:

 struct BITMAPINFO_256 {   BITMAPINFOHEADER  bmiHeader;   RGBQUAD           bmiColors[256]; }; 

This structure will come in quite handy in a moment when you begin loading bitmaps. Before we get into that, however, let's take a look at the Bitmap class definition, which is shown in Listing 5.1.

Listing 5.1 The Bitmap Class Definition Includes the Member Variables and Methods Used to Manage Bitmaps
 1: class Bitmap  2: {  3: protected:  4:   // Member Variables  5:   HBITMAP m_hBitmap;  6:   int     m_iWidth, m_iHeight;  7:  8:   // Helper Methods  9:   void Free(); 10: 11: public: 12:   // Constructor(s)/Destructor 13:   Bitmap(); 14:   Bitmap(HDC hDC, LPTSTR szFileName); 15:   Bitmap(HDC hDC, UINT uiResID, HINSTANCE hInstance); 16:   Bitmap(HDC hDC, int iWidth, int iHeight, COLORREF crColor = 17:     RGB(0, 0, 0)); 18:   virtual ~Bitmap(); 19: 20:   // General Methods 21:   BOOL Create(HDC hDC, LPTSTR szFileName); 22:   BOOL Create(HDC hDC, UINT uiResID, HINSTANCE hInstance); 23:   BOOL Create(HDC hDC, int iWidth, int iHeight, COLORREF crColor); 24:   void Draw(HDC hDC, int x, int y); 25:   int  GetWidth() { return m_iWidth; }; 26:   int  GetHeight() { return m_iHeight; }; 27: }; 

This listing reveals the overall makeup of the Bitmap class, including its member variables, constructors, destructor, and methods. The m_hBitmap member variable is used to store a handle to the bitmap, which is extremely important for drawing the bitmap to a device context using the GDI (line 5). The m_iWidth and m_iHeight member variables store the width and height of the bitmap, respectively (line 6).

The Constructors and Destructor

Notice in Listing 5.1 that there are three constructors in the Bitmap class in addition to the default constructor, each of which corresponds to one of the different approaches to creating bitmaps (lines 14 “16)). Each Bitmap() constructor has a corresponding Create() method that is used to handle the work of loading the bitmap data and creating it as a GDI object (lines 20 “22). The Free() method is a helper method used within the Bitmap class to free up the memory associated with the bitmap (line 9). The Draw() method shouldn't be too surprising because it provides a means of drawing the bitmap to a device context (line 23). And finally, the GetWidth() and GetHeight() methods are simply used to obtain the width and height of the bitmap, respectively (lines 25 and 26).

The CD-Rom for this book contains the code for all the Bitmap() constructors, as well as the destructor and the Free() method.

The Bitmap() constructors are all very simple in that they call a corresponding Create() function to carry out the specifics of creating a bitmap based on a file, a resource, or a solid color. The default constructor doesn't do much of anything other than initialize member variables; the idea behind this constructor is that you will eventually call the Create() method directly to load the Bitmap object with data. The destructor calls the Free() method to release the memory associated with the bitmap and clear the bitmap handle.

The Free() method first checks to see if the bitmap handle, m_hBitmap , is valid ”in which case, it deletes the GDI bitmap object and clears out the handle. This is all that's required to free the memory associated with the bitmap.

Three Ways to Create a Bitmap

There are three Create() methods in the Bitmap class that are fairly long, but you can access them from the CD-ROM. The first Create() method is responsible for loading a bitmap from a file.

The method starts out by calling Free() to make sure that any previous bitmap data is cleaned up; this would apply if the same Bitmap object was being reused for a different bitmap. The file is then opened, and the resulting file handle is checked to make sure that the open proceeded without a hitch. The file header for the bitmap is then read from the file, and some checks are made to ensure that it was read without any problems. The file header contains information about the bitmap file itself, whereas the info header contains information about the bitmap. The info header is read next, and another error check is performed.

With the headers properly read from the file, the Create() method is ready to get down to the real business of reading the bitmap data. The color table for the bitmap is read and followed by the image data. It is worth pointing out the usage of the CreateDIBSection() Win32 function, which is used to obtain a handle to a GDI bitmap object from raw bitmap data. This function ultimately makes it possible to read a bitmap and prepare it for use with the GDI. The last step in the Create() method is to free the bitmap memory if an error occurred while reading its data.

The second Create() method supported by the Bitmap class is used to load a bitmap from a resource.

This method follows roughly the same pattern as the first Create() method ”except in this case, the bitmap information is retrieved from a resource in memory, as opposed to being read from a file. The first step is to find the bitmap resource, load the resource into memory, and then lock the resource so that you can access its raw data. You don't have to worry about the color table in this case because it is already included in the bitmap information. The width and height of the bitmap are stored next, and the image data is copied and used as the basis for obtaining a bitmap handle using CreateDIBSection() . Finally, the method concludes by performing some clean up in case an error occurred.

The last of the Create() methods is the one that creates a blank bitmap in a solid color.

This method is considerably different from the other two Create() methods because it has the luxury of not having to involve itself with any existing bitmap data. Instead, it uses a Win32 function called CreateCompatibleBitmap() to create an entirely new bitmap based on the supplied device context. Because the width and height are provided as arguments to the method, they are easy to store. Most of the work in the method has to do with filling the bitmap with a solid color. A compatible device context is first created to hold the bitmap for drawing, and then a solid brush in the specified color is created. The bitmap is then selected into the device context and filled with the solid brush. The graphics objects are then cleaned up to finish the job of this Create() method.

Drawing the Bitmap

The three Create() methods are by far the toughest parts of the Bitmap class, so the Draw() method will be a welcome relief.

The Draw() method is actually similar to the third Create() method because it involves drawing a bitmap. However, in this case the bitmap is being drawn to an outside device context, as opposed to you drawing on the bitmap itself. The Draw() method accepts a device context and an XY coordinate as arguments. This reveals how simple it is to use the method to draw a bitmap. The first step in drawing the bitmap is ensuring that the bitmap handle is valid. If the handle checks out okay, a compatible device context is created to temporarily store the bitmap, and the bitmap is selected into the device context. This is an important step because drawing images always takes place from one device context to another.

The bitmap is actually drawn using the Win32 BitBlt() function, which draws an image from a source device context to a specified location on a destination device context. Drawing a bitmap image is sometimes referred to as blitting , which is where the name BitBlt() comes from; you are blitting the image bits when you draw the image. The Draw() method finishes up by cleaning up the temporary device context.

graphics/book.gif

The x and y arguments to the Bitmap::Draw() method are specified relative to the device context the bitmap is being drawn on, and they indicate the upper left corner of the bitmap image.


You now have a complete, fully working Bitmap class that is ready to be used in games to load and draw bitmap images. Let's move on and take a look at a program example that puts the Bitmap class through its paces.



Sams Teach Yourself Game Programming in 24 Hours
Sams Teach Yourself Game Programming in 24 Hours
ISBN: 067232461X
EAN: 2147483647
Year: 2002
Pages: 271

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net