Bitmap (.bmp) File Format Explained
by bkenwright@xbdev.net
One of the most popular...and of course simplest file formats today, is the
bmp file format. It comes in a number of flavours...which include 1bit.. 4
bit...16 bit...32bit..etc There's is also a compressed version of the bmp
format, using run length encoding. But don't worry, we'll explain all that
as we go along.
Lets start by explaining a simple picture, and how its broken up....
If your reading this tutorial on your pc screen...well your well familiar
that your screen is made up of pixels....where each pixel has a Red, Green Blue
value (RGB) which makes up the colour of that pixel. You have a lot of
pixels on that screen!....lots and lots.... lets pick a nice number.... 640x480
pixels...
Once you have a grasp of what those things....I mean pixels are...its just a
matter of understanding how the bitmap file stores them.
The great thing about the bmp format is how it arranges these pixels....there
is a header part...which is the first few bytes...that tells us the width,
height etc of the image...then we would just have the raw data following
this....simple eh? Well its a little more complex than that if you use the
encoded version or the palette version.. but for the 32 and 24 bit versions its
exactly that.
Which type of bmp should we start with?... Well I think the best way is to
start simple....which is the simplest? Its the 24bit version of the bmp
file format...well I think it is.. But don't worry, we'll do the other
types...1 bit....8 bit etc.
Lets do a bit of code to get a feel for this bmp stuff....
Code: main.cpp |
////////////////////////////////////////////////////////////////////////////////////////
//
//
// File: main.cpp
//
// Author: bkenwright@xbdev.net
//
//
//
////////////////////////////////////////////////////////////////////////////////////////
#include
<stdio.h> // So we can
use fopen and fread
// Simple Debug Feedback - writes to a simple text file located where the
exe is.
void
debug(char* str)
{
FILE* fp = fopen("info.txt", "a+");
fprintf(fp, "%s\n", str);
fclose(fp);
}//
End of debug(..)
char
buf[500]; // Temp buffer for text output
// Program Entry Point - Where we always start!
void
main()
{
FILE* fp = fopen("cross.bmp", "rb");
char readbuf[2];
// Temp Buffer
// +- Buffer to put the data in
// | +- Size of each value read in bytes
(1 byte each)
// | | +- How many times to read in
the value (e.g. twice read in 1 byte)
// | | | +- Stream souce
// | | | |
fread(readbuf, 1, 2, fp);
debug("First Two Bytes:");
sprintf(buf, "%c %c", readbuf[0], readbuf[1]);
debug(buf);
fclose(fp);
}//
End of main() |
Output: info.txt |
First Two Bytes:
B M |
What on earth...well its simple...and it gives us a starting code from which
we can explore the bmp file format. All that I've done is read in the
first two bytes of any bmp file and write them to an output file...in the above
case called 'info.txt'. As all bitmap files have two bytes with 'B' and
'M' at the start of the file....so that where sure its a bmp file. You can
also open the bmp file in notepad and view it in there....it will be all
jibberish...but you'll notice that the first two bytes are B and M.
Starting from common ground is a good think....now rather than just use any
random bmp file...I think its best to create a custom one and then you know what
your looking for when you read in the data...that way if the bytes or bits arn't
aligned, then you'll now about it right away. So open up your image editor
and create a 24 bit bitmap that's 8x8 pixels wide as shown below:
Image: cross.bmp |
|
Of course you can use your own demo image...but I think its best to start
small :) We can always improve and alter it as we go along.
Code: main.cpp |
#include
<stdio.h> // So we can
use fopen and fread
// Simple Debug Feedback - writes to a simple text file located where the
exe is.
void
debug(char* str)
{
FILE* fp = fopen("info.txt", "a+");
fprintf(fp, "%s\n", str);
fclose(fp);
}//
End of debug(..)
char
buf[500]; // Temp buffer for text output
// Program Entry Point - Where we always start!
void
main()
{
FILE* fp = fopen("cross.bmp", "rb");
char typeBM[2]; //
Temp Buffer
fread(typeBM, 1, 2, fp);
sprintf(buf, "typeBM: %c %c", typeBM[0], typeBM[1]);
debug(buf);
unsigned int
iSize; // 4 Bytes
fread(&iSize, 4, 1, fp);
sprintf(buf, "iSize: %d", iSize);
debug(buf);
fclose(fp);
}//
End of main() |
Output: info.txt |
typeBM: B M
iSize: 246 |
Ooooo.....so if we read in the next two bytes from our file, as I've done
before....they represent our exact file size....if you right click on the
bitmap, and check its size in bytes...you'll see that its 246bytes. Where
getting there slowly...
Now bit by bit we read in the header information in...which tells us all the
information about the bitmap...things like what type of bitmap it is...filesize...image
width...offset to the image bits.... now rather than do it one by one... you can
put the data into a structure and read it in, in one go. Let me show you
the definitions of the header data, as its broken up into two parts.
BitmapFileHeader |
Name |
Size in Bytes |
Small Description |
bfType |
2 |
Bitmap File Type Signature |
bfSize |
4 |
Whole size of the bitmap file on your pc |
bfReserved1 |
2 |
Unused - just ignore |
bfReserved2 |
2 |
Unused - just ignore |
bfOffSetBits |
4 |
Offset from the start of the file to our pixel data |
BitmapInfoHeader |
Name |
Size in Bytes |
Small Description |
biSize |
4 |
Size of this Header - 40 bytes |
biWidth |
4 |
Image Width |
biHeight |
4 |
Image Height |
biPlanes |
2 |
|
biBitCount |
2 |
Bits per pixel - 1,4,8,16,24 or 32 |
biCompression |
4 |
Compression type - 0=RGB(No Compression), 1=RLE8, 2=RLE4,
3=BITFIELDS |
biSizeImage |
4 |
Size of image |
biXPelsPerMeter |
4 |
Preferred resolution in pixels per meter |
biYPelsPerMeter |
4 |
Preferred resolution in pixels per meter |
biClrUsed |
4 |
Number of entries in the colour map that are actually used |
biClrImportant |
4 |
Number of significant colours |
There you go.... a full description of the header information for a bitmap
file - so from the first 54 bytes we can determine the file size...image
format..width...height etc...which can be extremely useful. So lets create
a structure to represent all this data and read it in...and spit it out into our
text file and see what we have:
Code: main.cpp |
#include
<stdio.h> // So we can
use fopen and fread
// Simple Debug Feedback - writes to a simple text file located where the
exe is.
void
debug(char* str)
{
FILE* fp = fopen("info.txt", "a+");
fprintf(fp, "%s\n", str);
fclose(fp);
}//
End of debug(..)
char
buf[500]; // Temp buffer for text output
// All our data structures will be packed on a 1 byte boundary
#pragma
pack(1)
struct
stBMFH // BitmapFileHeader
{
char bmtype[2];
// 2 bytes - 'B' 'M'
unsigned int
iFileSize; // 4 bytes
short int
reserved1; // 2 bytes
short int
reserved2; // 2 bytes
unsigned int
iOffsetBits; // 4 bytes
};//
End of stBMFH structure - size of 18 bytes
#pragma
pack()
#pragma
pack(1)
struct
stBMIF // BitmapInfoHeader
{
unsigned int
iSizeHeader; // 4 bytes - 40
unsigned int
iWidth; // 4 bytes
unsigned int
iHeight; // 4 bytes
short int
iPlanes; // 2 bytes
short int
iBitCount; // 2 bytes
unsigned int
Compression; // 4 bytes
unsigned int
iSizeImage; // 4 bytes
unsigned int
iXPelsPerMeter; // 4 bytes
unsigned int
iYPelsPerMeter; // 4 bytes
unsigned int
iClrUsed; // 4 bytes
unsigned int
iClrImportant; // 4 bytes
};//
End of stBMIF structure - size 40 bytes
#pragma
pack()
// Program Entry Point - Where we always start!
void
main()
{
// Read in our header information
FILE* fp = fopen("cross.bmp", "rb");
stBMFH BMHF;
stBMIF BMINFO;
fread(&BMHF, 1, sizeof(stBMFH), fp);
fread(&BMINFO, 1, sizeof(stBMIF), fp);
fclose(fp);
// Dump or read in data to our txt file.
// First the File Header Part
debug("BITMAPFILEHEADER");
sprintf(buf, "bmtype: %c %c", BMHF.bmtype[0], BMHF.bmtype[1]);
debug(buf);
sprintf(buf, "iFileSize: %d", BMHF.iFileSize);
debug(buf);
sprintf(buf, "iOffsetBits: %d", BMHF.iOffsetBits);
debug(buf);
// Now the Bitmap File Info Header Section
debug("BITMAPINFOHEADER");
sprintf(buf, "iSizeHeader: %d", BMINFO.iSizeHeader);
debug(buf);
sprintf(buf, "iWidth: %d", BMINFO.iWidth);
debug(buf);
sprintf(buf, "iHeight: %d", BMINFO.iHeight);
debug(buf);
sprintf(buf, "iPlanes: %d", BMINFO.iPlanes);
debug(buf);
sprintf(buf, "iBitCount: %d", BMINFO.iBitCount);
debug(buf);
sprintf(buf, "Compression: %d", BMINFO.Compression);
debug(buf);
}//
End of main() |
Output: info.txt |
BITMAPFILEHEADER
bmtype: B M
iFileSize: 246
iOffsetBits: 54
BITMAPINFOHEADER
iSizeHeader: 40
iWidth: 8
iHeight: 8
iPlanes: 1
iBitCount: 24
Compression: 0 |
Well there you have it.... all the header information has been read in... its
not so bad when you use a structure and just read it in, in one big go :)
We can see from our output file that we have an image which is 8 pixels wide and
8 pixels height....its compression value is 0...which tells us that there is no
compression. Using this information we can determine what bmp file we have
on our hands. Of couse we created the custom 'cross.bmp' file here....so
this is just confirming what we already know...but try it with some other bitmap
files...it will give you its secret info of what it is.
On the coding front...those who are sort of new to coding will notice a few
lines which will scare you...those are the lines which contain
'#pragam pack(1)'...which makes sure our structure
data follows each other...so exactly after we do a character data, the following
data will be exactly after it.....aligning on 1 byte boundaries.
Let me give you an example:
struct
stNotPacked
{
char a;
int z;
};
// sizeof(stNotPacked) == 8 bytes
|
#pragma
pack(1)
struct
stPacked
{
char a;
int z;
};
#pragma
pack()
// sizeof(stPacked) == 5 bytes |
As the default for structures in C is to align the data to the following data
type, so in the above example where we would have a char (1 byte) followed by a
int (4 bytes)... the char is padded with 3 empty spaces so the int is aligned on
a 4 byte boundary.
Note: The alignment of data is compiler and platform
dependent - so be sure to check if you compiling with OS or Linux etc for
example.
Hmmm...well all we have to do now is read in the pixel data....those red,
green, blue value things....you haven't forgot have you...hehe.. But how
do we find in the file where they are? Well the "iOffsetBits" value we
read in from our header data tells us exactly that. It tells us the
location of the colour bytes. In the example above note that its 54
bytes...and remember or header data is also 54 bytes...which tells us that our
image data comes exactly after or header data in this case.
Where should we put all this data though? Well I thought I'd create a
structure called stImage which we can use to hold or image data....and it will
keep hold of only the essential information. Storing an array of rgb
values.... but I'm going to store the colour information with 8 bits (or 1 byte)
per index colour (red, green and blue = 24 bytes). So if we read in a 16
bit bitmap later on, and we decide to store the image in here, we'll convert it.
Common Errors: |
// Allocate some memory for our image bits
ImageBits.pARGB = new
unsigned int[
ImageBits.iWidth * ImageBits.iHeight * 3 ];
// ERROR ERROR ERROR - Data is DWORD aligned on
the width - as when data is saved to disk, the
// the width is padded with 0's so it aligned to 32 bits....or 4 bytes...so
you have to be
// careful of this
fread(&(ImageBits.pARGB), 1, ImageBits.iWidth * ImageBits.iHeight * 3, fp);
|
Be careful with the above error, as it won't show in our example as its 8x8
pixels, so it aligns on the 4 byte boundary - but on occasion you'll open an
image which isn't aligned, and you'll wonder why! But don't worry I've
added 2 lines in which checks for it, using the % modulus operation - which
tells us how many remainders there are
Code: main.cpp
(DownloadSourceCode) |
#include
<stdio.h> // So we can
use fopen and fread
#include
<assert.h> // Few
simple debugging tests
// Simple Debug Feedback - writes to a simple text file located where the
exe is.
void
debug(char* str)
{
FILE* fp = fopen("info.txt", "a+");
fprintf(fp, "%s", str);
fclose(fp);
}//
End of debug(..)
char
buf[500]; // Temp buffer for text output
// All our data structures will be packed on a 1 byte boundary
#pragma
pack(1)
struct
stBMFH // BitmapFileHeader
{
char bmtype[2];
// 2 bytes - 'B' 'M'
unsigned int
iFileSize; // 4 bytes
short int
reserved1; // 2 bytes
short int
reserved2; // 2 bytes
unsigned int
iOffsetBits; // 4 bytes
};//
End of stBMFH structure - size of 18 bytes
#pragma
pack()
#pragma
pack(1)
struct
stBMIF // BitmapInfoHeader
{
unsigned int
iSizeHeader; // 4 bytes - 40
unsigned int
iWidth; // 4 bytes
unsigned int
iHeight; // 4 bytes
short int
iPlanes; // 2 bytes
short int
iBitCount; // 2 bytes
unsigned int
Compression; // 4 bytes
unsigned int
iSizeImage; // 4 bytes
unsigned int
iXPelsPerMeter; // 4 bytes
unsigned int
iYPelsPerMeter; // 4 bytes
unsigned int
iClrUsed; // 4 bytes
unsigned int
iClrImportant; // 4 bytes
};//
End of stBMIF structure - size 40 bytes
#pragma
pack()
struct
stImage
{
unsigned int
iWidth;
unsigned int
iHeight;
unsigned int*
pARGB; // Alpha Red Green Blue
};//
End of stImage
// Program Entry Point - Where we always start!
void
main()
{
// Read in our header information
FILE* fp = fopen("cross.bmp", "rb");
stBMFH BMFH;
stBMIF BMINFO;
fread(&BMFH, 1, sizeof(stBMFH), fp);
fread(&BMINFO, 1, sizeof(stBMIF), fp);
// Dump or read in data to our txt file.
// First the File Header Part
debug("BITMAPFILEHEADER");
sprintf(buf, "bmtype: %c %c\n", BMFH.bmtype[0], BMFH.bmtype[1]);
debug(buf);
sprintf(buf, "iFileSize: %d\n", BMFH.iFileSize);
debug(buf);
sprintf(buf, "iOffsetBits: %d\n", BMFH.iOffsetBits);
debug(buf);
// Now the Bitmap File Info Header Section
debug("BITMAPINFOHEADER");
sprintf(buf, "iSizeHeader: %d\n", BMINFO.iSizeHeader);
debug(buf);
sprintf(buf, "iWidth: %d\n", BMINFO.iWidth);
debug(buf);
sprintf(buf, "iHeight: %d\n", BMINFO.iHeight);
debug(buf);
sprintf(buf, "iPlanes: %d\n", BMINFO.iPlanes);
debug(buf);
sprintf(buf, "iBitCount: %d\n", BMINFO.iBitCount);
debug(buf);
sprintf(buf, "Compression: %d\n", BMINFO.Compression);
debug(buf);
// Read in our image data
// +- File Stream
// | +- Offset to our data
// | | +- Where or offset
is from (SEEK_CUR, SEEK_END
// | |
| or SEEK_SET)
// | | |
fseek( fp, BMFH.iOffsetBits, SEEK_SET);
stImage ImageBits;
ImageBits.iWidth = BMINFO.iWidth;
ImageBits.iHeight = BMINFO.iHeight;
// Allocate some memory for our image bits
ImageBits.pARGB = new
unsigned int[
ImageBits.iWidth * ImageBits.iHeight];
assert( !(ImageBits.pARGB == NULL) && "Error allocating memory for image" );
int iNumPaddedBytes = (ImageBits.iWidth * 3)
% 4;
sprintf(buf, "iNumPaddedBytes: %d\n\n", iNumPaddedBytes);
debug(buf);
// For example 8*3 = 24...which is 24 bytes....
24/4=6 exactly
// but if our image was 9 pixels wide, then we'd
have:
// 9*3 = 27, and 27/4= 24 remainder 3, so we would
have 1 padded byte
for(unsigned
int h=0; h<ImageBits.iHeight; h++)
{
for(unsigned
int w=0; w<ImageBits.iWidth; w++)
{
// So in this loop - if our data
isn't aligned to 4 bytes, then its been padded
// in the file so it aligns...so
we check for this and skip over the padded 0's
// Note here, that the data is
read in as b,g,r and not rgb as you'd think!
unsigned
char r,g,b;
fread(&b, 1, 1, fp);
fread(&g, 1, 1, fp);
fread(&r, 1, 1, fp);
ImageBits.pARGB[ w + h*ImageBits.iWidth ] = (r<<16 | g<<8
| b);
}// End of for loop w
//If there are any padded bytes - we
skip over them here
if( iNumPaddedBytes != 0 )
{
unsigned
char skip[4];
fread(skip, 1, 4 - iNumPaddedBytes, fp);
}// End of if reading padded bytes
}//
End of for loop h
// Now to proove that we have all our bytes read
in, and of course that they are the
// bytes that they are suppose to be, we'll write
them out to a text file - of course
// you would only do this for a small bitmap -
e.g. our demo cross.bmp bitmap :)
for(h=0; h<ImageBits.iHeight; h++)
{
for(unsigned
int w=0; w<ImageBits.iWidth; w++)
{
unsigned
char r = (ImageBits.pARGB[ w + h*ImageBits.iWidth
] >> 16) & 0xff;
unsigned
char g = (ImageBits.pARGB[ w + h*ImageBits.iWidth
] >> 8) & 0xff;
unsigned
char b = (ImageBits.pARGB[ w + h*ImageBits.iWidth
] >> 0) & 0xff;
sprintf(buf, "%.2x%.2x%.2x ", r,g,b);
debug(buf);
}// End of for loop w
debug("\n");
}//
End of for loop h
delete[] ImageBits.pARGB;
fclose(fp);
}//
End of main() |
Output: info.txt |
BITMAPFILEHEADERbmtype: B M
iFileSize: 246
iOffsetBits: 54
BITMAPINFOHEADER iSizeHeader: 40
iWidth: 8
iHeight: 8
iPlanes: 1
iBitCount: 24
Compression: 0
iNumPaddedBytes: 0
ff0000 000000 000000 000000 000000 000000 000000 ff0000
000000 ff0000 000000 000000 000000 000000 ff0000 000000
000000 000000 ff0000 000000 000000 ff0000 000000 000000
000000 000000 000000 ff0000 ff0000 000000 000000 000000
000000 000000 000000 ff0000 ff0000 000000 000000 000000
000000 000000 ff0000 000000 000000 ff0000 000000 000000
000000 ff0000 000000 000000 000000 000000 ff0000 000000
ff0000 000000 000000 000000 000000 000000 000000 ff0000 |
Can you see the resemblance in our raw text output with our cross.bmp image?
Well its only to check that where making progress and that we haven't made any
drastic mistakes.
Whahooo...well we have our image more or less, there is one more detail about
flipping which I'll mention in a sec.... but with what we have here we could
create a nice re-usable function called Load24BitBitmap(..) ....
bool Load24BitBitmap(stImage* pImage, char* szFileName);
Where it would return bool true if all went okay, and false if an error
occured. Of course you'd add error checking to your image loading function
so you would check the bitcount and that the image starts with 'B' and 'M'
etc... but we'll get there.
Upside down? You can't really see it in the txt output that I've shown
above... but our image is in fact flipped upside down... so the very first pixel
we read in, is the bottom, and the last row pixel bytes we read in, is the top
of our image. So to fix this, all we have to do is flip our y value as we
read in our data and it will fix it, as show:
for(unsigned
int h=0; h<ImageBits.iHeight; h++)
{
for(unsigned
int w=0; w<ImageBits.iWidth; w++)
{
// So in this loop - if our data
isn't aligned to 4 bytes, then its been padded
// in the file so it aligns...so
we check for this and skip over the padded 0's
// Note here, that the data is
read in as b,g,r and not rgb as you'd think!
unsigned
char r,g,b;
fread(&b, 1, 1, fp);
fread(&g, 1, 1, fp);
fread(&r, 1, 1, fp);
//** Flips our data so its the
correct way around - note the off by one **//
//** as arrays go from 0 to
n-1. **//
int hInverted =
(ImageBits.iHeight-1) - h;;
ImageBits.pARGB[ w + hInverted*ImageBits.iWidth ] = (r<<16
| g<<8 | b);
}// End of for loop w
//If there are any padded bytes - we
skip over them here
if( iNumPaddedBytes != 0 )
{
unsigned
char skip[4];
fread(skip, 1, 4 - iNumPaddedBytes, fp);
}// End of if reading padded bytes
}//
End of for loop h |
So with that one change added we can throw all the code into a nice function
and give it a run.... Putting our bitmap reading code into bitmap24.h/.cpp we
find that our main.cpp function suddenly becomes really empty....with the
exception of our debug code it would only be a few lines long.
Shall we take a look at our 24 bit bmp loader function code:
Code: main.cpp
(DownloadSourceCode) |
#include
<stdio.h> // So we can
use fopen and fread
#include
"bitmap24.h" // Our
function that reads in our 24 bit
//
bitmap
// Simple Debug Feedback - writes to a simple text file located where the
exe is.
void
debug(char* str)
{
FILE* fp = fopen("info.txt", "a+");
fprintf(fp, "%s", str);
fclose(fp);
}//
End of debug(..)
char
buf[500]; // Temp buffer for text output
// Program Entry Point - Where we always start!
void
main()
{
stImage Image;
Load24BitBitmap(&Image, "cross.bmp");
// These lines are nothing more than debug output
--------------------------------
sprintf(buf, "Width: %d, Height: %d\n\n", Image.iWidth, Image.iHeight);
debug(buf);
// Now to proove that we have all our bytes read
in, and of course that they are the
// bytes that they are suppose to be, we'll write
them out to a text file - of course
// you would only do this for a small bitmap -
e.g. our demo cross.bmp bitmap :)
for(unsigned
int h=0; h<Image.iHeight; h++)
{
for(unsigned
int w=0; w<Image.iWidth; w++)
{
unsigned
char r = (Image.pARGB[ w + h*Image.iWidth ]
>> 16) & 0xff;
unsigned
char g = (Image.pARGB[ w + h*Image.iWidth ]
>> 8) & 0xff;
unsigned
char b = (Image.pARGB[ w + h*Image.iWidth ]
>> 0) & 0xff;
sprintf(buf, "%.2x%.2x%.2x ", r,g,b);
debug(buf);
}// End of for loop w
debug("\n");
}//
End of for loop h
delete[] Image.pARGB;
}//
End of main() |
Output: Info.txt |
Width: 8, Height: 8
00ff00 000000 000000 000000 000000 000000 000000 ff0000
000000 ff0000 000000 000000 000000 000000 ff0000 000000
000000 000000 ff0000 000000 000000 ff0000 000000 000000
000000 000000 000000 ff0000 ff0000 000000 000000 000000
000000 000000 000000 ff0000 ff0000 000000 000000 000000
000000 000000 ff0000 000000 000000 ff0000 000000 000000
000000 ff0000 000000 000000 000000 000000 ff0000 000000
ff0000 000000 000000 000000 000000 000000 000000 ff0000 |
Its sweet isn't it.... we can now do a 1 bit....4 bit version....and work
towards creating a full library... But before we continue, I think there's
one thing we should do...we should make a GUI windows version where we can open
a 24 bit bitmap and render it to the screen in its full glory. Hopefully
it won't require to much overhead code, but it will help you believe how much
power this gives us.
DownloadSourceCode
By adding in some Win32 MFC code we able to create a simple Bitmap Loader
application, which we can use to test our 24 Bit Bitmap Loader Function we
created earlier. A screen shot of the application can be seen on the
right. |
|
1 Bit Bitmap
Our next mission....if you haven't ran off..hehe....is to work on our 1 Bit
Loader code.....we've done most of the hard work now...its just a matter of
building from our previous errors. It would almost be a task of changing a
few lines from our previous code here...but for one little thing.... 1,4 and 8
bit bitmaps also store palette information, which is stored as an array of
RGBQUAD values.... the number of palette values is 2bitcount.
struct RGBQUAD // 4 bytes
{
unsigned char alpha, red, green,
blue;
};
This palette information, follows our header file data immediately.... For
our 1 bit file format here...we should have 2 palette entries...which will be
referenced by our image data. Remember our 1 bit image doesn't have to be
black and white...it can be green and blue for example....any two colours.
So the two starting points of our code are following on from before.... but
this time we'll create a 1 bit Loading function.
code: DownloadSourceCode |
bool
Load1BitBitmap(stImage* pImage, char*
szFileName)
{
}//
Load1BitBitmap(..)
// Program Entry Point - Where we always start!
int
__stdcall WinMain(HINSTANCE, HINSTANCE,
LPSTR, int)
{
stImage Image;
Load1BitBitmap(&Image, "cross.bmp");
}//
End of main() |
In creating our new Load1BitBitmap(..) function....the two main
conciderations are the alignment of data so its width is on a 32 bit
boundary...and we skip any padded bytes....second, is the reading in of our
palette data, and using it for a reference.
Now I read the data in, and store it as a 32Bit ARGB value.....so after we've
read in each pixel value and discovered its colour, we can delete the palette
data in this case. The first part's the same...reading in this header
data... but then straight after this, we get our palette data. The tricky
bit of code to really step through...or maybe print out and look at for a while,
is the inner loop, which does a bit of bit shifting so that we take each bit and
get its value.... as we can't read in 1 bit at a time...we the smallest value we
can read in, is 1 byte (8 bits)...and hence for each 8 bits...we do bit wise
operations to get the data.
code:
DownloadSouceCode |
bool
Load1BitBitmap(stImage* pImage, char*
szFileName)
{
//
Read in our header information
FILE* fp = fopen(szFileName, "rb");
stBMFH BMFH;
stBMIF BMINFO;
fread(&BMFH, 1, sizeof(stBMFH), fp);
fread(&BMINFO, 1, sizeof(stBMIF), fp);
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW **
NEW ** NEW ** NEW ** NEW ** NEW ** NEW
// ** NEW ** NEW ** NEW ** NEW ** NEW ** NEW **
NEW ** NEW ** NEW ** NEW ** NEW ** NEW
// Now straight after our header information,
follows our Palette Info, which
// we'll use as we read in our data.
// 1 bit -> 2^1 = 2 ARGB Palette Entries to read
in
// Allocate memory for our palette
unsigned int*
pARGB = new
unsigned int[2];
// Read in all the palette values
fread(pARGB, 2, sizeof(unsigned
int), fp);
// Now read in our image data
// +- File Stream
// | +- Offset to our data
// | | +- Where or offset
is from (SEEK_CUR, SEEK_END
// | |
| or SEEK_SET)
// | | |
fseek( fp, BMFH.iOffsetBits, SEEK_SET);
pImage->iWidth = BMINFO.iWidth;
pImage->iHeight = BMINFO.iHeight;
// Allocate some memory for our image bits
pImage->pARGB = new
unsigned int[ pImage->iWidth * pImage->iHeight];
assert( !(pImage->pARGB == NULL) && "Error allocating memory for image" );
int iNumPaddedBits = (32-(pImage->iWidth *
1)) % (4*8);
int iNumPaddedBytes = iNumPaddedBits/8;
// Bit tricker to work out the number of padded
bytes..as remember..
// where working with bits...so 8 bits are a byte.
// So I calculate how many bits will pad the
end...bits..not bytes..
// then I convert it to bytes, by dividing by 8.
// Something worth thinking about if your new to
C/C++...is the rounding
// of digits....as notice if we do:
// 15/8...we get 1
// 16/8...we get 2
// As we don't hold decimal places in int's
// We round down the numbers! Why...well you
really have to look at the code
// carefully...but if we only use 7 bits for the
width...then we read in
// groups of 8 bits...so we would read in 8
bits...leaving 24 for the padding
for(unsigned
int h=0; h<pImage->iHeight; h++)
{
for(unsigned
int w=0; w<pImage->iWidth; w+=8 )
// 8 pixels at a time
{
// Well the smallest value we
can read in, is 1 byte...so we read it in, and
// convert the 8 bits to there
seperate values
unsigned
char value;
fread(&value, 1, 1, fp);
int i = pImage->iWidth - w
;
if( i>8)
i=8;
for(int
j=0; j<i; j++)
{
//** Flips our data so its
the correct way around - note the off by one **//
//** as arrays go from 0
to n-1.
int hInverted = (pImage->iHeight-1)
- h;;
pImage->pARGB[ (w+j) + hInverted * pImage->iWidth ]
= pARGB[(value>>j)&0x01];
// w+j is because in this
little inside loop, where reading in
// bit at a time....so we
read in 8 bits...and we have to share them
}// End for loop j
}// End of for loop w
//If there are any padded bytes - we
skip over them here
if( iNumPaddedBytes != 0 )
{
unsigned
char skip[4];
fread(skip, 1, iNumPaddedBytes, fp);
}// End of if reading padded bytes
}//
End of for loop h
delete[] pARGB;
//
Delete allocated memory for the palette data
fclose(fp);
return true;
// All went okay :)
}//
Load1BitBitmap(..) |
For the demo's...I did a simple square black and white demo image...and I
also tested the code with a 33x8 black and white image....just to make sure the
alignment code was okay. Again, because I'm using simple test images, it
means I can single step through the code, and compare with what values I
expect...and what values I have.
From 1 bit to 4 bit isn't so bad....and 8 bit should be easy....as we read in
1 byte at a time...so where in fact reading in a pixel at a time and finding its
palette value as we go along.
{Under Construction}
|