www.xbdev.net
xbdev - software development
Thursday April 25, 2024
home | contact | Support | Image File Format... Storing Graphics using Bits and Bytes.. | Image Formats A single picture is worth a thousand words...

     
 

Image Formats

A single picture is worth a thousand words...

 

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}

 

 

 
Advert (Support Website)

 
 Visitor:
Copyright (c) 2002-2024 xbdev.net - All rights reserved.
Designated articles, tutorials and software are the property of their respective owners.