|
||||||||||||||||||
Add Forum to Favorites | Send Topic To a Friend | View Forum FAQ | Track this topic Page: 1 2 »» |
Last Thread Next Thread ![]() |
| How to Load a Bitmap |
|
![]() voodoo_john Member since: 5/2/2001 From: Glasgow, United Kingdom |
||||
|
|
||||
| Nice reference, but I'd like to see it more x-platform. For example, knock out the windows.h and substitute the appropriate types instead of DWORD etc Good work! :D [edited by - voodoo_john on July 23, 2003 1:23:21 PM] |
||||
|
||||
![]() Pipo DeClown Member since: 2/16/2002 From: Amsterdam, Netherlands |
||||
|
|
||||
| Nice article indeed. Just when I needed one .lick |
||||
|
||||
![]() krez GDNet+ Member since: 10/10/2001 From: NJ - The Garbage State |
||||
|
|
||||
| it isn't hard to look up what a DWORD really is... i wrote my own bitmap-loading structs in about 2 minutes (from specs i found on the internet), to avoid platform dependencies ("windows.h"). |
||||
|
||||
![]() voodoo_john Member since: 5/2/2001 From: Glasgow, United Kingdom |
||||
|
|
||||
quote: Well good for you. I was meaning as a reference it would be nice to see it platform neutral. |
||||
|
||||
![]() rodzilla Member since: 1/10/2002 From: France |
||||
|
|
||||
quote: More x-platform ? #include <SDL/SDL-image.h> And things will be MUCH easier for you ! BTW, who needs another BMP loader ? |
||||
|
||||
![]() voodoo_john Member since: 5/2/2001 From: Glasgow, United Kingdom |
||||
|
|
||||
| Those that want to know how things work rather than use something blindly. SDL_image is crap, code your own routines and learn something. |
||||
|
||||
![]() Anonymous Poster |
||||
|
||||
| Many bitmap files are RLE compressed, but this loader doesn't deal with it. Also, the structs have no packing information; depending on compiler vendor, or even compiler version or settings/switches, those structs may or may not work without extra code (i e, __attrib__(align) or #pragma pack or whatever). Also, if I want a quick-and-dirty loader, I use TGA: it's 9 "short" values, and item 6 and 7 are width/height. If you define that you only support 32-bit data, it's all cake from there.
unsigned short header[9];
FILE * f = fopen("file.tga", "rb" Of course, well factored code doesn't do the texture upload and the file reading in the same spot -- you'd want to be able to load DDS files into OpenGL, and to load TGA files into DirectX, without re-writing all of it, you know. |
||||
|
||||
![]() DarkWhoppy Member since: 7/26/2003 From: USA |
||||
|
|
||||
| Excellent...this helped me alot. Although im still not sure of how to make my own "Loading Image" code but since im in the need of one for testing things this will do...I'm not doing anything great but if I do i'll be sure to add Mark Bernard (aka Captain Jester) to the credits. |
||||
|
||||
![]() rodzilla Member since: 1/10/2002 From: France |
||||
|
|
||||
quote: OK, if I wanna know how things work, I'll just find the specifications for the BMP file format and try to write my own loader. You can stare at someone else's code and think "Hey, cool !", but that will not improve your skills. And if I wanna things to be done, I'll reuse existing code whenever possible. quote: Could you find at least one reason why this piece of code is better than the BMP loader included in the SDL library (no need for SDL_image for BMP, but SDL_image provides readers for other formats) ? - SDL can read all flavours of BMP, this piece of code doesn't. - SDL separates correctly the "read" and "decode" parts so you can read a BMP structure from a file or from memory (or network, ZIP files if you write the appropriate SDL_RWops wrappers). This loader doesn't. |
||||
|
||||
![]() CaptainJester Member since: 11/29/2001 From: Ottawa, Canada |
||||
|
|
||||
| Thanks for all the feedback guys. To answer some of the comments: This was intended as an article for beginners. I see it asked hundreds times by beginners on how to load a bitmap and a lot of people say search the forums. Well, to make it easier, they can just look at the article in the reference section instead. To all the people that said "I'll just look at the specs and write my own." That is what I did and I applaud you. However, it doesn't come that easily to some people, especially beginners, so it is nice to have some code to see how it works. To the anonymous poster, while RLE is in the spec, I have never seen a bitmap that has actually used it. So I decided not to put it in the article to keep it simple. That is why I put in the conclusion that I left the less used parts of the spec out. To the same anonymous poster, you are right on the struct alignment issue. To be honest, I didn't even think about that. However, if you include the windows.h header outside any pack instruction, then there should be no problem. About the cross platform issure. I agree, I should have taken the time to break down the structures into a more generic form. But since this is my first article, I will chalk that up to experience. Thanks again for all the comments. Keep them coming. First make it work, then make it fast. --Brian Kernighan "I’m happy to share what I can, because I’m in it for the love of programming. The Ferraris are just gravy, honest!" --John Carmack: Forward to Graphics Programming Black Book |
||||
|
||||
![]() Anonymous Poster |
||||
|
||||
| Very good,but I'd like to something which are base on X platform. |
||||
|
||||
![]() CaptainJester Member since: 11/29/2001 From: Ottawa, Canada |
||||
|
|
||||
For those looking for a more cross platform solution, remove #include <windows.h> and add the following to the front of the header file:
//File information header
//provides general information about the file
typedef struct tagBITMAPFILEHEADER {
unsigned short bfType;
unsigned long bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned long bfOffBits;
} BITMAPFILEHEADER, *PBITMAPFILEHEADER;
//Bitmap information header
//provides information specific to the image data
typedef struct tagBITMAPINFOHEADER{
unsigned long biSize;
long biWidth;
long biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned long biCompression;
unsigned long biSizeImage;
long biXPelsPerMeter;
long biYPelsPerMeter;
unsigned long biClrUsed;
unsigned long biClrImportant;
} BITMAPINFOHEADER, *PBITMAPINFOHEADER;
//Colour palette
typedef struct tagRGBQUAD {
unsigned char rgbBlue;
unsigned char rgbGreen;
unsigned char rgbRed;
unsigned char rgbReserved;
} RGBQUAD;
First make it work, then make it fast. --Brian Kernighan "I’m happy to share what I can, because I’m in it for the love of programming. The Ferraris are just gravy, honest!" --John Carmack: Forward to Graphics Programming Black Book |
||||
|
||||
![]() Anonymous Poster |
||||
|
||||
| Those structs have exactly the same alignment problems -- only worse, as they won't even align right on Windows, much less on GCC/Linux, or MacOS. The best way to parse a header you read from disk, is to read it as a block of bytes, and doing explicit bit shifting yourself. That way, you get size right on all compilers, and you get byte order right on all architectures!
// assume little-endian storage on disk
struct NotAligned {
unsigned short a;
unsigned long b;
};
char buf[ sizeof( NotAligned ) ];
fread( buf, 1, sizeof( NotAligned ), f );
NotAligned out;
out.a = buf[0] | (buf[1]<<8);
out.b = buf[2] | (buf[3]<<8) | (buf[4]<<16) | (buf[5]<<24);
|
||||
|
||||
![]() CaptainJester Member since: 11/29/2001 From: Ottawa, Canada |
||||
|
|
||||
| I can't argue with on the alignment issue. As I said earlier, I didn't even think of that. However, they are not worse that the original ones, because they are the original ones with the typedefs removed for BYTE, WORD, DWORD and LONG. First make it work, then make it fast. --Brian Kernighan "I’m happy to share what I can, because I’m in it for the love of programming. The Ferraris are just gravy, honest!" --John Carmack: Forward to Graphics Programming Black Book |
||||
|
||||
![]() easlern Member since: 7/22/2001 From: USA |
||||
|
|
||||
| Thanks for the reference. Very handy! |
||||
|
||||
![]() Evangelion Member since: 11/24/2002 |
||||
|
|
||||
| I'm working on a bitmap library, and I wrote a save bitmap function. The problem is the save function doesn't work. Any ideas, code, ect? |
||||
|
||||
![]() Anonymous Poster |
||||
|
||||
int j==size-3;
offset=padWidth-byteWidth;
for(int i=0;i<size;i+=3) {
if((i+1)%padWidth==0) {
i+=offset;
}
*(pixelData+j+2)=*(tempPixelData+i);
*(pixelData+j+1)=*(tempPixelData+i+1);
*(pixelData+j)=*(tempPixelData+i+2);
j++;
}
Isn't there something wrong with the j++? You will go past the boundaries of pixelData[size]. Also don't you want (i + 1) % byteWidth == 0? |
||||
|
||||
![]() random_acts Member since: 2/3/2002 From: Chicago, IL, United States |
||||
|
|
||||
| This all looks good, and is an excellent starting point for someone like me. But I have a few questions: diff=width*height*RGB_BYTE_SIZE; //allocate the buffer for the final image data data=new char[diff]; The height might be negative right? So couldn't this produce a negative result, which you're then using to allocate memory, and wouldn't this be bad? Finally, what's all the talk about struct alignment? Is that making sure the data you read from the disk is put into the appropriate members of the struct? For instance we are assuming that a DWORD (unsigned long int) is 4 bytes. Thanks for the help. [edited by - random_acts on August 12, 2003 12:20:50 PM] |
||||
|
||||
![]() CaptainJester Member since: 11/29/2001 From: Ottawa, Canada |
||||
|
|
||||
| AP: Yes, that does not look right. I wrote the article when and then found the code didn't work right, so I fixed the code and then tried to correct the article. That is something that I may have missed. random_acts: That slipped through the cracks. In my testing, I didn't run across a bitmap file with a negative height. The struct alignment issue has to do with how the compiler packs the data. It may pack variables it on a 1 byte boundary, or 2 byte or 4 byte. Try this little test program and you will see what I mean.
//change pack(1) to 2 and 4 to see the difference
//different architectures may react differently as well.
#pragma pack(1)
#include <iostream>
using namespace std;
struct test {
int a;
int b;
char c;
};
int main() {
test t;
t.a=0;
cout << "size=" << sizeof(t) << endl;
return 0;
}
Thanks guys, I will make those corrections. First make it work, then make it fast. --Brian Kernighan "I’m happy to share what I can, because I’m in it for the love of programming. The Ferraris are just gravy, honest!" --John Carmack: Forward to Graphics Programming Black Book |
||||
|
||||
![]() squirrel_of_death Member since: 4/27/2003 From: New york, NY, United States |
||||
|
|
||||
| Hi there, really great tutorial, I did appreciate the code. I spent a lot of time checking out the BMP format, and made some changes to your code. I thought I would put it up for further comments, and hopefully help some others out. I expect that everyone uses a kind of BMP 'manager' object, when loading files. Thus, the Bitmap class as it stands is only good for one file at a time, particularly with the cout statements. I changed it so that you / manager object pass an ostream& into the object, with the filename, so that if you choose, you can save all the information to a .txt file of your BMP loads, instead of couting to the screen. I also changed the reset() function to init(), and added a reset() of my own, which is just re-organizing what was already written. But I think it's more intuitive. I also changed the log output to follow the BMP spec order. I also did a lot of re-formatting and / tidying up of identifyiers / comments. Hope it's useful, and let me know what you think. And thanks again Mark for starting me off. Here is the header :
#ifndef _BITMAP_H
#define _BITMAP_H
// Original code by Mark Bernard ( e-mail: mark.bernard@rogers.com )
// modified by David Henry ( e-mail: strad1238@hotmail.com )
// Copyright 2003
#include <iostream>
#include <cstdio>
#include <string>
using namespace std;
const short BITMAP_MAGIC_NUMBER = 19778;
const int RGB_BYTE_SIZE = 3;
#pragma pack(push, bitmap_data, 1)
// these structures make the class less platform dependent
//------------------------------------------
typedef struct tagRGBQuad
{
char rgbBlue;
char rgbGreen;
char rgbRed;
char rgbReserved;
} RGBQuad;
//------------------------------------------
typedef struct tagBitmapFileHeader
{
unsigned short bfType;
unsigned int bfSize;
unsigned short bfReserved1;
unsigned short bfReserved2;
unsigned int bfOffBits;
} BitmapFileHeader;
//------------------------------------------
typedef struct tagBitmapInfoHeader
{
unsigned int biSize;
int biWidth;
int biHeight;
unsigned short biPlanes;
unsigned short biBitCount;
unsigned int biCompression;
unsigned int biSizeImage;
int biXPelsPerMeter;
int biYPelsPerMeter;
unsigned int biClrUsed;
unsigned int biClrImportant;
} BitmapInfoHeader;
//------------------------------------------
#pragma pack(pop, bitmap_data)
//--------------------------------------------------------------------------------------
class Bitmap
{
public:
Bitmap();
Bitmap(char *fileName, ostream& logFile);
~Bitmap();
bool loadBMP(char * fileName, ostream& logFile);
RGBQuad *colours; // the color pallete for 8-bit bitmaps
char *data; // Here is the BMP data
bool loaded; // loaded or not
int width; // width and height
int height;
unsigned short bpp; // bits per pixel
string error; // a field for error message
private:
void init(); // initializes the main data fields
void reset(); // resets all data in object
bool convert24(char *tempData); // convert to 24bit RGB bottom up data
bool convert8(char *tempData); // convert to 24bit RGB bottom up data
void writeLog(ostream& OS, char *fileName); // write log to file
BitmapFileHeader bmfh; // file header
BitmapInfoHeader bmih; // information header
int byteWidth; // the width in bytes of the image
int padWidth; // the width in bytes of the padded image
unsigned int dataSize; // size of the data in the file
};
//--------------------------------------------------------------------------------------
#endif //_BITMAP_H
and here is the .cpp :
// Original code by Mark Bernard ( e-mail: mark.bernard@rogers.com )
// modified by David Henry ( e-mail: strad1238@hotmail.com )
// Copyright 2003
#include "Bitmap.h"
////////////////////////////////////////////////////////////////////////////////////
Bitmap::Bitmap() // basic constructor
{
init();
}
////////////////////////////////////////////////////////////////////////////////////
Bitmap::Bitmap(char *file, ostream& logFile) // constructor loads the bitmap
{
init();
loadBMP(file, logFile);
}
////////////////////////////////////////////////////////////////////////////////////
Bitmap::~Bitmap() // destructor
{
reset();
}
////////////////////////////////////////////////////////////////////////////////////
bool Bitmap::loadBMP(char *fileName, ostream& logFile) // loads a bitmap into memory
{
FILE *in; // file stream for reading
char *tempData; // temp storage for image data
int numColours; // total available colours
loaded = false; // bitmap is not loaded yet
in = fopen(fileName, "rb"); // open the file for reading in binary mode
if(in == NULL) // if the file does not exist return in error
{
error = "File not found";
fclose(in);
return false;
}
//========== read in the BITMAPFILEHEADER ==========//
fread(&bmfh, sizeof(BitmapFileHeader), 1, in);
if(bmfh.bfType != BITMAP_MAGIC_NUMBER) // check magic number that says this is a bitmap
{
error = "File is not in DIB format";
fclose(in);
return false;
}
//========== read in the BITMAPINFOHEADER =========//
fread(&bmih, sizeof(BitmapInfoHeader), 1, in);
writeLog(logFile, fileName); // write data to file
width = bmih.biWidth; // set object data fields
height = bmih.biHeight;
bpp = bmih.biBitCount;
if(bpp < 8) // test for correct bit-per-pixel depth
{
error = "File is not 8 or 24 bits per pixel";
fclose(in);
return false;
}
// calculate the size of image data + padding
dataSize = (width * height * (unsigned int)(bpp / 8.0) );
if(bpp == 8) // load the color palette for 8 bits per pixel
{
numColours = 1 << bpp; // calculate the number of available colours
colours = new RGBQuad[numColours];
fread(colours, sizeof(RGBQuad), numColours, in); // read in the color palette
}
tempData = new char[dataSize]; // set up the temporary buffer for the image data
if(tempData == NULL) // exit if there is not enough memory
{
error = "Not enough memory to allocate a temporary buffer";
fclose(in);
return false;
}
fread(tempData, sizeof(char), dataSize, in); // read in the entire image
fclose(in); // close the file now that we have all the info
// set both values to the byte total of the file, without padding
byteWidth = padWidth = (int)( (float)width * (float)bpp / 8.0 );
while(padWidth % 4 != 0) // adjust the width for padding as necessary
{
padWidth++;
}
if(bpp == 8) // change format from GBR to RGB
{
loaded = convert8(tempData);
}
else if(bpp == 24)
{
loaded = convert24(tempData);
}
delete [] tempData; // clean up memory
error = "Bitmap loaded"; // bitmap is now loaded
return loaded; // return success
}
////////////////////////////////////////////////////////////////////////////////////
void Bitmap::init(void) // function to set the inital values
{
loaded = false;
colours = 0;
data = 0;
error = "";
}
////////////////////////////////////////////////////////////////////////////////////
void Bitmap::reset(void)
{
if(colours) // clear the color pallete if required
{
delete [] colours;
}
if(data) // clear the data if required
{
delete [] data;
}
loaded = false;
error = "";
}
////////////////////////////////////////////////////////////////////////////////////
bool Bitmap::convert24(char* tempData)
{
int offset; // padding between data
int dataSize; // the size of the bitmap data
dataSize = abs(width * height * RGB_BYTE_SIZE);
data = new char[dataSize]; // allocate the buffer for the final image data
if(data == NULL) // exit if there is not enough memory
{
error = "Not enough memory to allocate an image buffer";
delete [] data;
return false;
}
if(height > 0)
{
offset = padWidth - byteWidth; // find the difference
for(int i = 0; i < dataSize; i += 3) // count backwards until at front of image
{
if((i+1) % padWidth == 0) // jump over the padding
{
i += offset;
}
*(data+i+2) = *(tempData+i); // transfer the data in reverse order
*(data+i+1) = *(tempData+i+1); // in 3-color blocks of RGB
*(data+i) = *(tempData+i+2);
}
}
else // image parser for a forward image
{
offset = padWidth - byteWidth;
int j = dataSize - 3;
for(int i = 0; i < dataSize; i += 3, j -= 3)
{
if((i+1) % padWidth == 0) // jump over padding
{
i += offset;
}
*(data+j+2) = *(tempData+i); // transfer the data ||NOTE||
*(data+j+1) = *(tempData+i+1); // that final data is stored backwards now
*(data+j) = *(tempData+i+2); // due to the difference of i and j
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////
bool Bitmap::convert8(char* tempData)
{
int offset; // padding between data
int dataSize; // the size of the bitmap data
dataSize = abs(width * height * RGB_BYTE_SIZE); // total memory required
data = new char[dataSize]; // allocate the buffer for the final image data
if(data == NULL) // exit if there is not enough memory
{
error = "Not enough memory to allocate an image buffer";
delete [] data;
return false;
}
if(height > 0)
{
offset = padWidth - byteWidth;
int j = 0;
//count backwards so you start at the front of the image
for(int i = 0; i < dataSize * RGB_BYTE_SIZE; i += 3, j++)
{
if((i+1) % padWidth == 0) // jump over padding
{
i += offset;
}
*(data+i) = colours[*(tempData+j)].rgbRed; // transfer the data
*(data+i+1) = colours[*(tempData+j)].rgbGreen;
*(data+i+2) = colours[*(tempData+j)].rgbBlue;
}
}
else // image parser for a forward image
{
offset = padWidth - byteWidth;
int j = dataSize - 1;
//count backwards until start at front of image
for(int i = 0; i < dataSize * RGB_BYTE_SIZE; i += 3, j--)
{
if((i+1) % padWidth == 0) // jump over padding
{
i += offset;
}
*(data+i) = colours[*(tempData+j)].rgbRed; // transfer the data,
*(data+i+1) = colours[*(tempData+j)].rgbGreen; // this time getting it
*(data+i+2) = colours[*(tempData+j)].rgbBlue; // from the color table
}
}
return true;
}
////////////////////////////////////////////////////////////////////////////////////
void Bitmap::writeLog(ostream& OS, char* fileName) // write log to file
{
OS << "name of file = " << fileName << endl;
OS << "FileHeader Size = " << sizeof(BitmapFileHeader) << endl;
OS << "InfoHeader Size = " << sizeof(BitmapInfoHeader) << endl;
OS << "Size = " << bmih.biSize << endl;
OS << "Width = " << bmih.biWidth << endl;
OS << "Height = " << bmih.biHeight << endl;
OS << "Planes = " << bmih.biPlanes << endl;
OS << "BitCount = " << bmih.biBitCount << endl;
OS << "Compression = " << bmih.biCompression << endl;
OS << "ClrImportant = " << bmih.biClrImportant << endl;
OS << "ClrUsed = " << bmih.biClrUsed << endl;
OS << "SizeImage = " << bmih.biSizeImage << endl;
OS << "PixelsPerMeter = " << bmih.biXPelsPerMeter << endl;
OS << "PixelsPerMeter = " << bmih.biYPelsPerMeter << endl;
OS << endl;
}
cheers all. |
||||
|
||||
![]() Anonymous Poster |
||||
|
||||
| --------------------Configuration: Bitmap - Win32 Debug-------------------- Linking... LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main Debug/Bitmap.exe : fatal error LNK1120: 1 unresolved externals Error executing link.exe. Bitmap.exe - 2 error(s), 0 warning(s) ------> Why the program has two errors ??when i build the program.(How to Load a Bitmap )-_-; |
||||
|
||||
![]() Anonymous Poster |
||||
|
||||
| --------------------Configuration: Bitmap - Win32 Debug-------------------- Linking... LIBCD.lib(crt0.obj) : error LNK2001: unresolved external symbol _main Debug/Bitmap.exe : fatal error LNK1120: 1 unresolved externals Error executing link.exe. Bitmap.exe - 2 error(s), 0 warning(s) ------> Why the program has two errors ??when i build the program.(How to Load a Bitmap )-_-; |
||||
|
||||
![]() Anonymous Poster |
||||
|
||||
It doesn't compile like that because there is no main function/method. Add this to the Bitmap.cpp file:int main(int argc, char* argv[]) { if(argc < 2) { printf("Usage: %s bmpInput.bmp\n", argv[0]); exit(0); } //pull the file name off the command line char *fileName = argv[1]; //re-direct ostream to cout std::ostream& my_cout = cout; //read the given bmp Bitmap(fileName,my_cout); } It runs just fine for me now on Linux Whitebox using the g++ compiler. g++ -c Bitmap.cpp g++ -o fileName Bitmap.o ./fileName imageFileName.bmp |
||||
|
||||
![]() Halsafar Member since: 8/29/2004 From: Saskatoon, Canada |
||||
|
|
||||
| HELP I've been struglin for 2 days now on how to get the results of this article into a HBITMAP to load into a DX Surface. I cannot even get the char* bitmap data into a HBITMAP to be used with BitBlt. |
||||
|
||||
|
Page: 1 2 »» All times are ET (US) ![]() |
Last Thread Next Thread ![]() |
|