|
||
|
GP Mailing List
ATXGPSIG List
|
[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index] More how-to: The Windows BMP File Format
================================================================= To SUBSCRIBE or UNSUBSCRIBE please visit: http://gameprogrammer.com/mailinglist.html ================================================================= The Windows BMP File Format I read lots of comments on the "one-dimensional" stuff I put in before. That's the reason why I put it in here. Thanks for all your comments. As you will see, future testing will show you when multiplication is faster and when bit shiting is faster. With today's processors, it's almost impossible to predict. I'll get to that later. OK, this time I'm focusing on the Windows BMP File Format. It's a common enough format and relatively easy to program, in comparison to GIF and JPEG, which still give me nightmares. Many compilers can open up and read these files for you, but I'm more interested in what exactly the compiler does when it reads those files. And, if possible, I'd like to read it faster than the compiler can (I've beaten some BMP readers, but others I've only equalled. I've never surpassed the Windows 98 BMP reading routine. Of course, my code isn't that much optimized...). This is again, easy step-by-step work. If you are familiar with the concepts I'm about to deal with, you probably don't need to read this e-mail. Besides, this e-mail is LONG. My advice to you if you want to read this: don't read it all at once. You'll go crazy that way. Once you open a file, you must be familiar with it's format. Most files have headers, bodies, and some have footers. Headers store information about the file, and often times have identification (make sure that the BMP file you're reading is actually in BMP format). The body (or bodies, depending on the format) consists of the information we're most interested in: the picture we want to load. Some formats also have a footer, telling you that the file is done. Footers are most useful in multi-body files, and they also may store information not required to display the file (TGA comes to mind...). That's all great and nice, but what is the BMP header composed of? Well, first of all, the BMP file format has two headers: BITMAPFILEHEADER, and BITMAPINFOHEADER. BITMAPFILEHEADER is the first thing encountered in the file, and it contains BMP identification and a pointer to where the information begins. BITMAPINFOHEADER immediately follows the first header and contains information about the image: resolution, type, compression type, etc. Here is what they look like: Windows BITMAPFILEHEADER file header Offset Size Name Description 0 2 bfType ASCII "BM" 2 4 bfSize Size in bytes of the file 6 2 bfReserved1 Zero 8 2 bfReserved2 Zero 10 4 bfOffBits Byte offset in files where image begins *Quick note: Size of 2 refers to word (unsigned short) and size of 4 refers to double word (unsigned long). This header starts out with BMP identification, bfType. These are just the ASCII letters B and M. This is useful if you want to load a graphics file, but you don't know what format it is. The "BM" marker can be used to tell you that it is a BMP/DIB file. I have learned through experience to not use bfSize. Many applications set this to some weird number or zero. I always set bfSize correctly whenever I write any BMPs using my applications, it's just that I don't use bfSize for loading purposes - it could be way off. bfReserved1 and bfReserved2 are reserved for future versions of the BMP file. Don't assume that it is zero - since it is not used for anything, many applications set bfReserved1 and bfReserved2 to a random number. I remember staying up late for weeks because my BMP identification routine didn't work - it seemed simple enough, it was composed of three tests: 1) Does it have a BM at the beginning? 2) Is it Windows or OS/2 Version (get to that later)? 3) Are bfReserved1 and bfReserved 2 zero? The last one was my error, for the reserved spots were just random numbers. Don't make the same mistake. Here's the next header: Windows BITMAPINFOHEADER image header Offset Size Name Description 14 4 biSize Size of this header, 40 bytes 18 4 biWidth Image width in pixels 22 4 biHeight Image height in pixels 26 2 biPlanes Number of image planes (must be 1) 28 2 biBitCount Bits per pixel: 1, 4, 8, 24 30 4 biCompression Compression type (below) 34 4 biSizeImage Size in bytes of compressed image, or zero 38 4 biXPelsPerMeter Horizontal resolution in pixels/meter 42 4 biYPelsPerMeter Vertical resolution in pixels/meter 46 4 biClrUsed Number of colors used (below) 50 4 biClrImportant Number of "important" colors 54 4*N bmiColors Color map biSize is set to 40 when the BMP is the Windows version, and set to 12 when the BMP is the OS/2 version. I'll get to the differences between the Windows BMP and the OS/2 BMP later. The resolution of the header is defined in biWidth and biHeight. biPlanes requires some explanation. Back in the days of 4-bit and 1-bit BMPs, this variable may have been set to something other than one. Basically, you can group pixels not only into width (x) and height (y), but also planes. Say you divided your bitmap into four planes. This way, you can divide your bitmap into four equally sized chunks. The only use of this that I have every used was to divide a large image into 64K chunks (planes) for faster loading into VESA. However, for our purposes, always set this to one. Another possible use of this would be to divide a 24-bit image into three planes: Red, Green, and Blue. I've never used it that way, and besides - BMP has its own kooky way of storing 24-bit images. biBitCount is set in the following manner: 1 = 2 color, 4 = 16 color, 8 = 256 color, and 24 = 16.7 million color. The reason the numbers are so weird have to do with the bits (a better explanation will be given in a future e-mail). biCompression is 0 for uncompressed images, and 1 for RLE compressed images (more on that later). biSizeImage is useful if you want to load the BMP into memory before processing it. Of course, like biSize, this number can be very inaccurate. I usually don't trust biSizeImage. biXPelsPerMeter and biYPelsPerMeter I never use. In fact, those would only be useful in printing. Many applications set these two to zero, as they are often neglected. biClrUsed tells how many colors are defined in the color map. This is 0 for 24-bit images, for they have no color map. Many applications will set this to 256 in 8-bit graphics even if less colors are used. biClrImportant was meant to be used for adapters that couldn't display 8-bit graphics when an 8-bit BMP file was given. This tells how many of the colors are "important," or are required in order to view the image correctly. Of course, this assumes that the color map starts with the most important colors and ends with the least important - not an easy thing to do. I usually set this equal to biClrUsed. The color map I'll get into later. Another thing to note is that OS/2's second header is slightly different. Here's its definition: OS/2 BITMAPCOREINFO image header Offset Size Name Description 14 4 bcSize Size of this header, 12 bytes. 18 2 bcWidth Image width in pixels. 20 2 bcHeight Image height in pixels. 22 2 bcPlanes Number of planes (must be 1) 24 2 bcBitCount Bits per pixel (1, 4, 8, or 24) 26 3*N bmciColors Color map It's pretty self-explanatory. As you can see, OS/2 headers (and color maps) are a lot more compact. However, there is a downside: OS/2 BMPs are always uncompressed. OK, that's all nice and good, but how do you load a header? Here's a code chunk that does exactly that. Don't worry, I'll go over it: struct Picture { unsigned short x,y; unsigned char *picture; unsigned char *palette; } Pict; struct BMP1 { /******FILE HEADER****************/ unsigned short bfType; // Must be set to 'BM' unsigned long bfSize; // The size of the file in bytes unsigned short bfReserved1; // Reserved for future expansion unsigned short bfReserved2; // Reserved for future expansion unsigned long bfOffBits; // The number of bytes before bitmap }; struct BMP1 BMPFILEHEADER; struct BMP2 { /******BITMAP HEADER**************/ unsigned long biSize; // The size of this header: 40 unsigned long biWidth; // The width in pixels unsigned long biHeight; // The height in pixels unsigned short biPlanes; // The number of planes (1) unsigned short biBitCount; // The number of bits per pixel unsigned long biCompression; // The type of compression unsigned long biSizeImage; // Size of image in bytes when compressed unsigned long biXPelsPerMeter; // The horizontal number of pix/meter unsigned long biYPelsPerMeter; // The vertical number of pix/meter unsigned long biClrUsed; // The number of colors that are used unsigned long biClrImportant; // The number of important colors } BMPINFOHEADER; struct BMP3 { /******OS/2 BITMAP HEADER*********/ unsigned long bcSize; // The size of this header: 12 unsigned short bcWidth; // The width in pixels unsigned short bcHeight; // The height in pixels unsigned short bcPlanes; // The number of planes (1) unsigned short bcBitCount; // The number of bits per pixel } BMPCOREINFO; int LoadBMP(char *file, Picture *P) { FILE *fp; long index; int x,type,cnt,c,d; // type: 0=OS/2, 1=Windows double a,b; unsigned char reed; if((fp=fopen(file,"rb"))==NULL) { return 1; // Error opening file } fread(&BMPFILEHEADER,sizeof(BMPFILEHEADER),1,fp); // <------note 1 fread(&BMPINFOHEADER.biSize,sizeof(BMPINFOHEADER.biSize),1,fp); if(BMPINFOHEADER.biSize==40) { // Windows fread(&BMPINFOHEADER.biWidth,36,1,fp); // 36 = 40 - 4 P->x=BMPINFOHEADER.biWidth; P->y=BMPINFOHEADER.biHeight; type=1; } if(BMPINFOHEADER.biSize==12) { // OS/2 BMPCOREINFO.bcSize=BMPINFOHEADER.biSize; // 12 fread(&BMPCOREINFO.bcWidth,8,1,fp); // 8 = 12 - 4 P->x=BMPCOREINFO.bcWidth; P->y=BMPCOREINFO.bcHeight; type=0; } if((BMPINFOHEADER.biSize!=40)&&(BMPINFOHEADER.biSize!=12)) return 5; // Unknown BMP type or corrupted header... As you can see, I defined a few structs known as BMPFILEHEADER, BMPINFOHEADER, and BMPCOREINFO, which are the three various types of headers. At note 1, the first header, BMPFILEHEADER, is read directly from the file. Now, we have a dilemma: is it a Windows BMP or an OS/2 BMP? We can't tell until we read biSize (or bcSize, if it is OS/2). Therefore, before reading the entire second header, we read biSize. If it is 40, we proceed with reading the rest of the header. If it is 12, we copy biSize into bcSize, then copy the rest of the header. Note that when the rest of the second header is read, it starts with biWidth/bcWidth, since biSize/bcSize is already read. I've also declared a struct known as Picture, which I use to store the picture. The resolution is stored in P->x and P->y. If biSize/bcSize is NOT equal to 40 or 12, the procedure returns with an error condition of 5. OK, that was easy. Now for color-maps. Color maps define the palette of the image. I will only be dealing with 24-bit files (which have no color map) and 8-bit files (which do) to simplify things. Besides, why would you ever use a 4-bit image nowadays? Never mind. The color map directly follows the second header. For 8-bit images, since there are 256 colors, 256 entries are entered (or the number stored in biClrUsed). The Windows and OS/2 methods of describing color maps are slightly different. Here they are: Windows RGBQUAD color map entry Offset Name Description 0 rgbBlue Blue value for color map entry. 1 rgbGreen Green value for color map entry. 2 rgbRed Red value for color map entry. 3 rgbReserved Zero OS/2 RGBTRIPLE color map entry Offset Name Description 0 rgbtBlue Blue value for color map entry. 1 rgbtGreen Green value for color map entry. 2 rgbtRed Red value for color map entry. As you can see, each variable is a byte (unsigned short) in length. However, there are a few problems. When writing palettes to the DAC (Digital to Analog Converter - to the screen), you have to write it in the order or Red, Green, and Blue. So, if you're going to write directly to the palette, you need to reverse the order (Blue, Green, Red to Red, Green, Blue). Another problem: BMP color intensities (the values stored in the BMP) range from 0 to 255. VGA color intensities range from 0 to 63 (unless you have a VESA that supports 0 to 255), so you need to fix that as well. Another thing: when I made the code, I stored the palette into a 256*3 byte array. Since palettes can be considered two-dimensional arrays (R/G/B and color entry number), I converted it into a one-dimensional array. Essentially, this is what the array looks like: [R1 G1 B1 R2 G2 B2... R255 G255 B255] <---- those ranging from 0 to 63. This is all done in the following code fragment: if((P->palette=new unsigned char[256*3])==NULL) { fclose(fp); return 4; // Not enough memory for palette } for(index=0;index<256;index++) { P->palette[(int)(index*3+2)]=fgetc(fp)>>2; P->palette[(int)(index*3+1)]=fgetc(fp)>>2; P->palette[(int)(index*3+0)]=fgetc(fp)>>2; if(type) x=fgetc(fp); } The first four lines declare memory for the palette, or, failing that, returns an error code of 4. This is done for if the new command fails, it is equal to NULL, and the if command is true. The palette-reading routine assumes 256 colors. the ">>2" converts the range of the BMP palette (0 to 255) to the allowable range on a normal VGA DAC (0 to 63). If you want to understand how the point definitions work, remember your one-dimensional arrays: index is y (the 2nd dimension that we're converting), and x is the number constant that is added (2, 1, or 0). This rather odd pointer system is required for we're converting the BMP palette entry (order: Blue, Green, Red) to the palette order (Red, Green, Blue). The last if command requires some explanation. type is a variable I use as a flag. If it is 0, then we are dealing with an OS/2 BMP. If it is 1, then we are dealing with a Windows BMP. This was defined in the header code from above. Remember that Windows palette entries have an extra, reserved byte. OS/2 does not. Therefore, if type is not 0 (it is 1), then we are dealing with a Windows palette entry, where we have an extra byte we can scrap. However, if type IS 0, then we are dealing with OS/2 palette entry, where we don't have an extra byte to scrap, so the command "x=fgetc(fp);" isn't executed. Immediately following the color map (if there is one) is the image data. The following applies to both 8-bit and 24-bit images, compressed or non-compressed images: images are stored in the order left to right, bottom to top. When drawing directly to video memory, this poses a problem, for images on the screen are stored left to right, but top to bottom. BMP'S ARE SCREWED UP!!! (Sorry, that's just my personal opinion.) Therefore, if we are to load the image in the way that we would write an image to the screen, we would have to unscramble the image! This isn't THAT hard to deal with. Once again, with one-dimensional arrays, just have the counter of x go from 0 to P->x (maximum x resolution), and y go from P->y (maximum y resolution) to 0. Yes, this is one way to do it, but I have a much faster way to do it. Here's how you load 8-bit, uncompressed images: for(index=(P->y-1)*P->x;index>=0;index-=P->x) for(x=0;x<P->x;x++) P->picture[(index+x)]=(unsigned char)fgetc(fp); That wasn't THAT bad. The pointer may throw you off. Basically, I've simplified (y*P->x+x) into (index+x), if index=(y*P->x). Let's say you have a 320x200 image. In this case, P->x=320, and P->y=200. The first y position you will calculate will be 199 (0 to 199 = 200 rows). 199*P->x, or 199*320 = the last row on the screen, with no x offset. That is added (index+x) later. However, the last row on the screen is the first one defined in the BMP file. This y value is decremented in order to fit the order in the BMP file. By the way, I skipped the definition of P->picture. It is defined as a one-dimensional array of size P->x*P->y earlier in the file. Loading 24-bit images can be done in the same way, except that each pixel is now 3 bytes (24 bits). Like the palette, each pixel is stored backwards - in the order Blue, Green Red. Each pixel is side by side, and like 8-bit images, it goes left to right, top to bottom. This time, I did a straight conversion from two to one-dimensional arrays: for(index=P->y;index>=0;index--) for(x=0;x<P->x;x++) { P->picture[(index*P->x+x)*3+2]=fgetc(fp); P->picture[(index*P->x+x)*3+1]=fgetc(fp); P->picture[(index*P->x+x)*3+0]=fgetc(fp); } I didn't define a "y" variable, so I just used index. If it helps, substitute index with y. The offset to the memory location where the pixel definition starts is (index*P->x+x)*3. This position is the red position, a byte later is the blue position, and another byte later is the green position, hence the backward definitions. This is what the array ends up looking like: [R1 G1 B1 R2 G2 B2... B(P->x*P->y)] I guess if you wanted to be completely accurate, I converted a three-dimensional (color attribute, x, y) array into a one-dimensional. Strange. For those of you who don't know: 24-bit images are where 3 bytes define each and every pixel. Each byte defines a certain attribute of the color. There are three attributes (one for each byte): red, green, and blue. Every color can be made by mixing and matching different amounts of these colors. That is how 24-bit images work. If you didn't know that before, this should be helpful in understanding the above. Now, I did install support for 8-bit RLE compression, so I'll go over it. RLE compression is (as far as I know) the only compression supported by the BMP version, and is unavailable in the OS/2 version. RLE stands for Run Length Encoding and is perhaps the simplest (and least effective) compression available for graphics file formats. It takes advantages of repeated pixels in bitmaps. For example, let's say we needed to load the following numbers in this order: 57 69 17 49 49 49 49 49 10 For the first three pixels, there would be no compression. However, the next five numbers are exactly the same, so RLE compression takes advantage of this. The stream would end up looking like this: 57 69 17 <<05 49>> 10 Where the <<'d text is is the compression data. This is a significant improvement - what took 9 numbers to define fit in 5 numbers. The first number represents how many times the number is repeated, and the second number is the number to be repeated. However, it isn't quite this easy. How do you know when you're supposed to repeat a number and when you're not supposed to? In the above example, the << and >> point out the compression data. There is no such thing in the BMP language. You could easily see the 57 and 69 and say that you're supposed to repeat the number 69 a total of 57 times. Obviously, you need some sort of signal to tell you when you're dealing with literal (uncompressed) numbers, or RLE-compressed numbers. Well, let's assume that the program always assumes that you have RLE compressed numbers. So the following stream: 5 18 8 12 3 73 decompresses to: 18 18 18 18 18 12 12 12 12 12 12 12 12 73 73 73 Now what if you wanted a few literal numbers tagged on? What if you wanted to add the numbers 48 29 17 to the above, but only once each? Well, if you use 0 as a first number, this can be used as a signal for literal numbers. Why does this work? An RLE count number of 0 would be meaningless - you would be defining a number that is repeated zero times. So, such a thing would never be defined. Therefore, since it isn't used in the RLE compression itself, it can be used as a signal for a set of literal numbers. The following stream: 5 18 8 12 3 73 0 3 48 29 17 decompresses to: 18 18 18 18 18 12 12 12 12 12 12 12 12 73 73 73 48 29 17 This can be confusing, but look. You have your old familiar RLE stream: 5 18 8 12 3 73. Then you have your literal marker (0). The number following it is the total number of literal numbers you want to define (3 of them). Following that, you have your three numbers. So, let's look at this again: * The first number tells how many times to repeat the second number, IF the first number isn't zero. * If the first number is zero, the second number tells how many literal numbers follow, then the literal numbers follow the second number. This is essentially how RLE compression works in BMP files. There are a few special exceptions (I think Microsoft made them to confuse the programmer). There are 3 special codes. If the first AND second number are 0, this signals the end of the row. If the first number is 0 and the second number is 1, then this signals the end of the bitmap. If the first number is 0 and the second number is 2, then this signals what is referred to as a position delta. This special marker is followed by two more numbers: xx and yy. It means that the bitmap is to continue xx pixels to the right and yy pixels down. This makes a number of requirements. You can only RLE compress one line at a time. For example, if you have a single color bitmap that has the resolution 10x10, you cannot say 100 1, meaning that you would repeat the single color 100 times. This would be useful, but it isn't supported in RLE compression. You have to make a declaration for each line (10 1 10 1 10 1 10 1 10 1...) with 00 00 (the end of row marker) in between each declaration. Also, since the special codes are 0, 1, and 2, you MUST declare that you have more than 2 pixels to define as literal (correct me if I'm wrong). Look at the following compressed line: 00 02 47 53 This means that you're going to define two literal numbers, which are 47 and 53, right? WRONG. 00 02 is a position delta marker, so you would actually continue the bitmap 47 pixels to the right and 53 pixels down. Don't make this mistake when you write your own BMP-making programs. Here's a rather important note: all RLE compressed data is 8-bit (one byte, or unsigned short) in length. You cannot say 1024 74 (repeat the number 74 a total of 1,024 times) for 1,024 is not an 8-bit number. So, you are limited to numbers between 0 and 255. This is how RLE compression works in BMP files. There's one more quirk in the literal mess. In the BMP file format, all definitions are even-byte padded. This means that each and every literal/RLE definition must be composed of a total of bytes that is divisible by 2. This is not a problem with RLE definitions (you have two bytes, the count and the number to be repeated, and two is an even number). However, take the following literal definition: 00 03 13 57 69 You have a total of five bytes, and five is NOT an even number. You need to stick in another byte. So you NEED to do the following: 00 03 13 57 69 00. Now you have a six-byte definition, and six is an even number. The extra zero at the end does NOT signal another literal/special definition. It is just added so that the definition is even-byte padded. WHY did Microsoft do this? Well, I can make a guess. If you read the file in two-byte (word) chunks, it would be easier if the beginning of each definition was at the beginning of a chunk. Since I programmed this the way I did, it's just another obstacle. Here's a code snipit that handles the RLE mess: if((type)&&(BMPINFOHEADER.biCompression)) { index=(P->y-1)*P->x; while(index>=0) { x=0; while(x<P->x) { reed=fgetc(fp); if(!reed) { // Literal or special c=fgetc(fp); if(c==2) { x+=fgetc(fp); index-=(P->x*fgetc(fp)); } if((c!=0)&&(c!=1)&&(c!=2)) { for(cnt=0;cnt<c;cnt++) { P->picture[index+x]=(unsigned char)fgetc(fp); x++; } a=(float)c; if(modf(a/2,&b)) { d=fgetc(fp); // even byte padding } } } else { // RLE d=fgetc(fp); // what to store for(cnt=0;cnt<reed;cnt++) { P->picture[index+x]=(unsigned char)d; x++; } } } index-=P->x; } The first line brings up an important point: compression is only available in Windows BMPs (type==1). Then, I made a few while loops that work much like the 8-bit for-loops that read the palette and uncompressed images. index again is the offset to the correct row, and x is the x offset into that row. A byte is read into reed (I named it that to avoid confusion with the command "read"). Now, if reed is not equal to zero, we have a RLE number (the number that says how many times to repeat the following byte). However, if it is zero, we have either a literal marker or a special marker. Let's look into the literal/special loop (!reed). The next byte is written into c. If c is 0, 1, or 2, we have a special marker. No action is done if c is the special marker 0 or 1 (if you get a 1 before the end of the bitmap, then something is wrong with the bitmap - 1 signifies the end of the bitmap). If c is the special marker of 2, then we have a position delta situation. The next byte is the x offset, and the byte after that is the y offset. The x offset is added directly to x and the y offset is calculated for y. However, if c is NOT 0, 1, or 2, we have a literal situation. The number in c already has the number of literal pixels we have to define, so the pixels are read directly into P->picture. Since neither RLE compression nor literal definitions can go beyond a row, only the x position is added (in a correctly defined BMP, there is at least one RLE/literal definitions per row). The modf() test that follows tests to see if c (which is now stored in the float variable "a") is divisible by two. Now think: modf returns the decimal part of a number (.25 in 1.25). So, if a number (like the number of bytes in the definition) is divisible by two, then it has no decimal part (6/2 is 3.00). Therefore, the if command is false - the definition is already even-byte padded, there is no need to read an extra byte so that it is even-byte padded. However, if that number is NOT divisble by two, then it has a decimal part of .5 (7/2 is 3.5) - the if command is true - the definition is NOT even-byte padded, there IS a need to read an extra byte. That's the end of the literal/special loop, and the RLE loop begins. The number to be repeated is stored in d, and the times to repeat the number is already stored in reed, so a simple for-loop uncompresses the RLE encoded data. And voila! You have read a BMP! I have attached the full version of BMP.H. It has a number of limitations: * it only loads BMPs - I didn't include any way to write BMPs, but you should be able to figure out how to do that. I suppose if enough people wanted me to, I could send out a WriteBMP procedure. * it was designed for a DOS 16-bit compiler - This means that is was designed for files less than 64K in size. This doesn't mean that it can't be used on a 32-bit compiler. In fact, it can be easily transcribed for use with ANY C++ compiler. * it's not fast - I designed the routine NOT for speed or size. I designed it so that the concepts I pointed out would be clear (or as clear as they can be in source code). * there is no 'BM' test - remember this? The first two bytes of a BMP always reads 'BM', and I didn't test for this. I just assumed that you would not feed into it any GIF files or anything else like that. Other than that, have fun with the code. You probably don't need it with your automatic BMP readers, but it's good to know what those do. Until next time! -Ender Wiggin ================================================================= The GameProgrammer.Com mailing list is for the open discussion of any topic related to the art, science, and business of programming games. This list is especially tolerant of beginners. We were all beginners once To SUBSCRIBE or UNSUBSCRIBE please visit: http://gameprogrammer.com/mailinglist.html
|
|