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