Loading image files into DirectDraw Surfaces

Started by
5 comments, last by Paradigm Shift 2000 22 years, 6 months ago
Hi everyone! I'm certainly not qualified to tutor anyone in data representation, but by the request of emileej, I'm starting this thread to open a discussion on how to load image files onto DirectDraw surfaces and blit the backgrounds, sprites or font characters out of the templates and onto the back buffer. This is an open forum so Comments, Critiques and Suggestions are absolutely necessary! I personally know that my own code isn't perfect, so by pooling the knowledge, we all become better. I'm only doing this to help so if anyone thinks I'm a windbag, say so and I won't clutter up the forums with useless posts! I'm going to start by presenting the code that I created from scratch for loading in a standard, uncompressed, RGB-encoded Windows Bitmap in either 8-bit palettized mode or 24-bit RGB mode. I created a container class to do this so that I could insulate myself from having to regenerate this code everytime I wanted to load a bitmap image file into my program. After the code listing, I'll explain what each section means, and does, and why I did it that way.
            
// bmpload.h -- This is the header file for my bitmap loader

// ------------ which defines the container class that will

// ------------ hold the ".bmp" file data.

// ------------ Paradigm Shift 2000


// defend against multiple includes

#ifndef BMPLOAD_H   

#define BMPLOAD_H

// necessary include files


#define WIN32_LEAN_AND_MEAN

#include <windows.h>

#include <windowsx.h>

// class protoype

typedef class tagWinBitmapFileObject
{
   protected:
      // an integer error code for fatal run-time exceptions

      int ErrCode;
      // a boolean to tell if the file is loaded

      bool LoadBit;
      // the bitmap file header

      BITMAPFILEHEADER FileHeader;
      // a bitmap information header

      BITMAPINFOHEADER InfoHeader;
      // the color palette as an array of RGBQUAD

      RGBQUAD * ColorPalette;
      // the image as an array of unsigned char

      unsigned char * Image;
   public:
      // default constructor

      tagWinBitmapFileObject();
      // constructor taking a filename

      tagWinBitmapFileObject(const char * filename);
      // destructor

      ~tagWinBitmapFileObject();
      // loads and unloads file data explicitly

      void LoadWinBitmapFile(const char * filename);
      void UnloadWinBitmapFile(void);
      // various get functions to get data from the class

      // note they're all inline

      inline bool IsLoaded() { return Loadbit;}
      inline unsigned long GetFileSize() {return
                                           FileHeader.bfSize;}
      inline RGBQUAD * GetPalettePtr(){return ColorPalette;}
      inline unsigned char * GetImagePtr(){return Image;}
      inline long GetWidth(){return InfoHeader.biWidth;}
      inline long GetHeight(){return InfoHeader.biHeight;}
      inline int GetColorDepth(){return InfoHeader.biBitCount;}
      inline unsigned long GetImageSize(){return
                                       InfoHeader.biSizeImage;}
      inline int GetObjectStatus(){return ErrCode;}
} WinBitmapFile;

// File type and error code defines

#define WINBMPTYPE          0x4d42   // Windows bitmap ID


#define WINBFO_OK           0        // errcode: OK


#define WINBFO_CREATEFAIL   1        // errcode: out of memory


#define WINBFO_NOTLOADED    2        // errcode: object not


                                     //    loaded with data


#define WINBFO_NOTBMP       3        // errcode: file is not


                                     //    a ".bmp" file


#define WINBFO_LOADFAIL     4        // errcode: file not found


#define WINBFO_NOTSUPPORTED 5        // errcode: compression not


                                     //    supported



#endif   // end defend against multiple includes


// end of file

  
This is actually an older header file that I had before I moved my file loaders into a DLL(which was probably stupid on my part), but it still works. I'll go through the entire header. First off, I included "windows.h" and "windowsx.h" because they contain the prototypes for the structures BITMAPFILEHEADER and BITMAPINFOHEADER. See, the internals of a Windows Bitmap file look like this: BITMAPFILEHEADER BITMAPINFOHEADER Array of RGBQUAD * 256 (only for 8-bit palettized images) Array of unsigned char * image size So I needed to generate the structures to read this data in properly. Next, I defined the class with these structures and arrays in mind, plus a couple of extra things to make my life easier and curb run-time exceptions. ErrCode is a standard integer error as defined in the defines following the class prototype. LoadBit determines whether the object is loaded with valid data, or is just an empty object. The next 4 variables correspond to the Window Bitmap file structures as I said above. Then, the public functions are my interface to the data stored in the class. The default constructor creates an empty object which initializes all of the variables to 0, false, or NULL(which are all really just 0's but nobody refers to a 0 pointer or boolean). The filename constructor takes the filename and calls LoadWinBitmapFile() to actually get the file data off of the disk. UnloadWinBitmapFile() empties the object, deallocates dynamic memory and sets the pointers to NULL, and the destructor calls UnloadWinBitmapFile() before the object is destroyed. The BITMAPFILEHEADER and BITMAPINFOHEADER structures are nasty and I wanted to insulate myself from this garbage, so I created a bunch of "Get" functions to get only the information I needed to load the data onto a DirectDraw Surface. IsLoaded() returns true if the object is loaded, or false if not. A call to GetObjectStatus() will return an integer error code in the case the object was unable to load the specified file. GetWidth(), GetHeight() and GetColorDepth() return these values in pixels, respectively. GetPalettePtr() and GetImagePtr() return *unsafe* pointers to the ColorPalette[] and Image[] arrays. These pointers are unsafe because they're not write protected, and allow a programmer to mangle palette entries and image data. This was an oversight I was going to correct but never got a chance to. GetImageSize() returns the size of the image, and GetFileSize() returns the total size of the file. Armed with this data, I could copy the data from the Image[] array, onto a DirectDraw surface and Blt() rectangles from the surface onto the screen. The constructors, destructor and UnloadWinBitmapFile() functions are trivial, but the real workhorse of this class lies in the LoadWinBitmapFile() function. Loading the bitmap data into the class is *not* a trivial matter because Windows Bitmap files are stored in a strange way. I'm going to present the code I used to finish off this class and load in the data:
                    
// bmpload.cpp -- The methods for the tagWinBitmapFileObject

// -------------- Paradigm Shift 2000


// necessary includes


#include "bmpload.h"  // for the class prototype


#include <fstream.h>  // for file I/O functions


// default constructor simply initializes the object

// to safe values

tagWinBitmapFileObject::tagWinBitmapFileObject()
{
   ErrCode = WINBFO_NOTLOADED;
   Loadbit = false;
   ZeroMemory(&FileHeader, sizeof(BITMAPFILEHEADER));
   ZeroMemory(&InfoHeader, sizeof(BITMAPINFOHEADER));
   ColorPalette = NULL;
   Image = NULL;
}

// the filename constructor calls the LoadWinBitmapFile()

// function to load the object with data

tagWinBitmapFileObject::tagWinBitmapFileObject(const char *
                                                filename)
{
   Loadbit = false;
   LoadWinBitmapFile(filename);
}

// the destructor calls the UnloadWinBitmapFile() function

// to deallocate dynamic resources

tagWinBitmapFileObject::~tagWinBitmapFileObject()
{
   UnloadWinBitmapFile();
}

// UnloadWinBitmapFile() empties the object and deallocates

// dynamic resources

void tagWinBitmapFileObject::UnloadWinBitmapFile(void)
{
   if(!Loadbit)   // if the object isn't loaded

      return;     // then just return 


   // this is called a Safe Release of a pointer, NEVER

   // apply delete[] to a NULL pointer

   if(ColorPalette)
   {
      delete[] ColorPalette;
      ColorPalette = NULL;
   }
   if(Image)
   {
      delete[] Image;
      Image = NULL;
   }

   // reset all values to default

   ErrCode = WINBFO_NOTLOADED;
   ZeroMemory(&FileHeader, sizeof(BITMAPFILEHEADER));
   ZeroMemory(&InfoHeader, sizeof(BITMAPINFOHEADER));
   Loadbit = false;
}

// LoadWinBitmapFile() loads the bitmap file information from

// the disk and into the object

void tagWinBitmapFileObject::LoadWinBitmapFile(const char *
                                                filename)
{
   if(Loadbit)               // if a file is loaded, then

      UnloadWinBitmapFile(); // unload it

   fstream fin;  // interface to filestream I/O

   try           // try reading in the file and bail on errors

   {
      // set the loadbit to true

      Loadbit = true;

      // open the file for binary reading and bail if it doesn't

      // exist

      fin.open(filename, ios::in | ios::binary | ios::nocreate);
      if(!(fin.is_open()))
         throw(WINBFO_LOADFAIL);
      // first read in the file header

      fin.read((unsigned char *)(&FileHeader),
                                 sizeof(BITMAPFILEHEADER));

      // the file type is embedded in the file header, bail

      // if it isn't the Windows Bitmap ID

      if(FileHeader.bfType != WINBMPTYPE)
         throw(WINBFO_NOTBMP);
      // next, read in the info header

      fin.read((unsigned char *)(&InfoHeader),
                                 sizeof(BITMAPINFOHEADER));

      // the compression method and color depth are embedded

      // in the info header, bail if the image is compressed

      // or not in 8-bit or 24-bit color depth

      if((InfoHeader.biCompression != BI_RGB) ||
         (InfoHeader.biBitCount != 8  &&
          InfoHeader.biBitCount != 24))
         throw(WINBFO_NOTBMP);

      // if the color depth is 8-bit, need to read in the 

      // color palette

      if(InfoHeader.biBitCount == 8)
      {
         // create a new array of RGBQUAD and bail if

         // the heap allocation fails

         ColorPalette = new RGBQUAD[256];
         if(ColorPalette == NULL)
            throw(WINBFO_CREATEFAIL);
         fin.read((unsigned char *)(ColorPalette), 
                                 (sizeof(RGBQUAD)<<8));
      }
      // else the color depth is 24-bit and there is no

      // color palette

      else
         ColorPalette = NULL;

      // create a new array of unsigned char large enough

      // to hold the entire image, and bail if the heap

      // allocation fails

      Image = new unsigned char[InfoHeader.biSizeImage];
      if(Image == NULL)
         throw(WINBFO_CREATEFAIL);
      
      // read in the image and set an _OK status

      fin.read(Image, InfoHeader.biSizeImage);
      ErrCode = WINBFO_OK;
  
      // finally close the filestream, very necessary since

      // windows has a limited number of file handles

      fin.close();
   }
   
   // if the above code in the try block throws an error

   // the catch block will catch it, and set the error

   // code in the object so that the program doesn't crash

   catch(int errcode)
   {
      fin.close();
      UnloadWinBitmapFile();
      ErrCode = errcode;
   }
}

// end of file

  
That's basically it, I commented the code heavily to clarify exactly what it's doing. Both the "bmpload.h" and "bmpload.cpp" listed are valid and tested code I use, so anyone can use this code to load in their files directly. There are, however, several problems with transferring the data in the class onto a DirectDraw surface: 1. Windows Bitmaps are normally stored upside-down, so when copying the data from the Image[] array to the DirectDraw surface, it needs to be inverted. Exception: Very small bitmaps will be stored normally, but GetHeight() will return a negative value. This needs to be checked because a simple memcpy() can be problematic. 2. Windows Bitmaps are padded on 4-byte boundaries. What this means is that each line in the image must fall on a 4-byte boundary. Thus, if you have an 8-bit image and the width of the image is 17 pixels, Windows will pad the image with 3 more bytes per line without telling you, which can play havoc when you memcpy() the bitmap to the surface. Note, the padding is in BYTES not pixels, so a 24-bit image with a width of 17 pixels will have 17*3=51 bytes on a line and a padding of only 1 byte to bring the count to 52 which is the next 4-byte boundary. 3. If you draw your sprites and fonts in templates, you need to calculate the rectangle to scan out of the image for Blt() every time, which can cause a performance hit if you're calling Blt() several times per frame. A sample source to illustrate how you use this to copy the image data to a DirectDraw Surface:
                    
WinBitmapFile bmpfile("foo.bmp");
DDSURFACEDESC ddsd;
ZeroMemory(&ddsd, sizeof(DDSURFACEDESC));
ddsd.dwSize = sizeof(DDSURFACEDESC);
unsigned char * videobuffer = NULL;
unsigned char * filebuffer = bmpfile.GetImagePtr();
lpddsurface->Lock(NULL, &ddsd, DDLOCK_SURFACEMEMORYPTR |
                  DDLOCK_WAIT, NULL);
videobuffer = (unsigned char *)(ddsd.lpSurface);
int pointerscale = (bmpfile.GetColorDepth())>>3;
// determine the padding

unsigned int padding = 4 - ((bmpfile.GetWidth()*pointerscale) 
                                                           % 4);
if(padding == 4)
   padding = 0;
// determine the pitch of the image from the padding

unsigned long linesize = (bmpfile.GetWidth()*pointerscale) +
                                                       padding;
int j = 0;

// must check if the image is or isn't inverted

if(bmpfile.GetHeight > 0)
{
   // then the image is inverted and need to re-invert

   for(int i = bmpfile.GetHeight() - 1; i >= 0; i--)
   {
      memcpy(&videobuffer[j*ddsd.lPitch],
             &filebuffer[i*linesize],
             bmpfile.GetWidth()*pointerscale);
      j++;
   }
}
else
{
   // then the image is normal, but reverse the height

   unsigned long height = -bmpfile.GetHeight();
   for(int i = 0; i < bmpfile.GetHeight(); i++)
   {
      memcpy(&videobuffer[i*ddsd.lPitch],
             &filebuffer[i*linesize],
             bmpfile.GetWidth()*pointerscale);
   }
}

lpddsurface->Unlock(videobuffer);
  
That's a really messy way just to get a stupid image onto a DirectDraw surface. So, to solve these three problems and make my code more normal looking, I created my own file format I call the DirectDraw Bitmap (".ddb"), which is *so* much easier to use than this, and created a converter to convert a ".bmp" file to a ".ddb" file which inverts the image, strips the padding out and(here's the best part) analyzes the image to check if it's a background, or a sprite template and automatically calculates the cell rectangles for me. If anyone wants to know how I did this, just say so and I'll post my code for my ".ddb" file format and the converter. Otherwise, since I've finished explaining how to get a ".bmp" file into memory and onto a DirectDraw surface, I'm done for now. Questions, comments, suggestions, whatever, are welcome :-) Paradigm Shift 2000 Edited by - Paradigm Shift 2000 on September 24, 2001 4:21:05 PM
"I am Locutus of Borg. Resistance is Futile." -- Locutus of Borg
Advertisement
GAH! I''m sorry about the format problems... I think I have it fixed now.

Paradigm Shift 2000
"I am Locutus of Borg. Resistance is Futile." -- Locutus of Borg
Woo!
Looks promising Anyways - bedtime (scool 2morrow) here its 22:42. Ill look at it 2morrow when i get home (or in scool *lol*).
Over ''n out 4 2day...

-------------
E-)mil
http://eej.dk

- Just another crazy dane
Emil Johansen- SMMOG AI designerhttp://smmog.com
>>Paradigm
I havent had that much time for this topic (lot of scool projects), but i have now understood the whole code (hi think *lol*)
I would like to add, that the code that checks if the bitmap is inversed, should be a part of the class, and should be callde, form the load function...

-------------
E-)mil
http://eej.dk

- Just another crazy dane
Emil Johansen- SMMOG AI designerhttp://smmog.com
You're right, and that's why I created the ".ddb" file, to encapsulate that inversion and padding messiness. I had contemplated building it directly into the tagWinBitmapFileObject code originally, but I was going to code an algorithm to analyze the image, precalculate the template rectangles for me, and save the data in a new format anyway, so it was just easier for me when I was designing it to put all the inverting and padding code into the .ddb file converter, especially since I didn't want to use .bmp files directly because of the reasons you gave earlier. You could add the functionality to the class if you want, and then you can copy the data directly from the Image[] array to your DirectDraw surfaces, but you'll still have to calculate the rectangles to Blt() out of the image yourself so you can do text and sprite animation.

Paradigm Shift 2000


Edited by - Paradigm Shift 2000 on September 26, 2001 9:46:44 AM
"I am Locutus of Borg. Resistance is Futile." -- Locutus of Borg
While loading a bitmap, i check for errors using GetObjectStatus();. During that check, my program detects, that something is wrong (WINBFO_OK is NOT the value returned).
To pick up wich value is returned, i wrote a simple error log function, but the value stored in the txt file, is not a number, but a box.

This is my small error log function:

  void EEJLogError(char error){	fstream errorLog;	errorLog.open("ErrorLog.txt", ios::out);	errorLog << error << '\n';	errorLog.close();}  


-------------
E-)mil
http://eej.dk

- Just another crazy dane

Edited by - emileej on September 30, 2001 10:28:11 AM
Emil Johansen- SMMOG AI designerhttp://smmog.com
You could do it that way The way I usually do it is with a string table that is mapped to values of the error codes. You could then pass a char * to your error log function and output a string which tells you exactly what went wrong

    const char * WINBFOERR[6] = {                    "Window Bitmap OK",                    "Out of memory",                    "Bitmap file not loaded",                    "Bitmap file is not a .bmp",                    "File not found",                    "Compression not supported"};void Initialize(void) // or wherever you're using this{   WinBitmapFile bmpfile("Foo.bmp");   if(!(bmpfile.IsLoaded()))   {      EEJLogError(WINBFOERR[bmpfile.GetObjectStatus()]);      exit(1);   }}  


Just make sure to change the argument in your error log function from a char to a char*

Or, even better yet you could just do this provided you defined the string table as a global:

  void EEJLogError(unsigned int errcode){	   fstream errorLog;   errorLog.open("ErrorLog.txt", ios::out);   errorLog << WINBFOERR[errcode] << '\n';   errorLog.close();}    


and pass the return value of GetObjectStatus() to your error log function directly.

Paradigm Shift 2000

Edited by - Paradigm Shift 2000 on September 30, 2001 11:14:38 AM
"I am Locutus of Borg. Resistance is Futile." -- Locutus of Borg

This topic is closed to new replies.

Advertisement