The BMP File Format

When is a standard file format not a standard file format? When it is the well-known BMP image format. As Marv points out, the BMP format is actually a sheaf of formats bundled under the same name. In this article, he examines BMP's image format incarnations and presents techniques for encapsulating them, using C++.


September 01, 1994
URL:http://www.drdobbs.com/cpp/the-bmp-file-format/184409305

Figure 1


Copyright © 1994, Dr. Dobb's Journal

Figure 1


Copyright © 1994, Dr. Dobb's Journal

SEP94: The BMP File Format

The BMP File Format

When a standard isn't necessarily a standard

Marv Luse

Marv is president of Autumn Hill Software and author of Bitmapped Graphics Programming in C++ (Addison-Wesley, 1994). Marv can be contacted at 303-494-8865.


Over the last few years, the BMP format has become an important graphics file standard. This is not surprising as it is the native graphics file format of both OS/2 and Windows. To most developers, however, the BMP format is still something of a stranger, albeit one we know by name. This was true in my own case, and it was only after writing a book on graphics file formats (Bitmapped Graphics Programming in C++, Addison-Wesley, 1994) that I came to appreciate the format's many vagaries.

As it turns out, the BMP format is actually a sheaf of formats bundled under the same name. Under OS/2, for example, the format is used to store images, icons, cursors, pointers, and image arrays (and I'm not entirely sure this list is complete). Here, I'll look primarily at one of these variants--the BMP image format.

There are currently two versions of the BMP image format for Windows and two for OS/2. Think of these as "old" and "new" versions on each platform. The two old versions are identical, reflecting the common ancestry of OS/2 and Windows. The two new versions, however, are different. This means that an application that wishes to handle any valid instance of the BMP image format must be prepared to deal with three format variants.

Few commercial applications actually support all three variants. You may have had the experience of an image editor or word processor trying to import a BMP file, only to be notified that the file is invalid. In such cases, it is likely that the BMP file originated on OS/2, but the application only knows how to handle the Windows-format variants. Another possibility is that the file actually contains something other than an image, such as an icon or cursor. And, of course, if the application is sufficiently old, it is possible that it only knows about the common version of the format.

From an application-development perspective, a program should be prepared to deal with any of the three valid image formats, and the program's error-handling logic should recognize three distinct situations: a valid BMP image file, a valid BMP file that contains something other than an image, and an invalid file of undetermined format. Anything less is likely to generate unnecessary and annoying technical-support calls (and if you are like me, anything that reduces technical-support calls is nothing less than manna from heaven!).

In this article, I'll examine the format itself and present techniques for encapsulating it using C++. The latter will present a class design that implements the strategy I've just described.

The BMP Image Format

The BMP image format is a general-purpose format designed to accommodate images of any size and possessing from 1 to 24 bits of color information. The bitmap-handling machinery of OS/2 and Windows prefers a chunky (nonplanar) pixel format; this is considered the norm for the format. Although multiplanar images can be accommodated, I have never encountered a BMP file that contained one. The format also supports RLE compression under Windows and RLE and Huffman 1D encoding under OS/2. Again, I've never encountered a compressed BMP file. One unfortunate canon of the format is that images are stored as scanlines ordered from the bottom up. This is never a problem when dealing only with the Windows or OS/2 Presentation Manager (PM) APIs, but if you must deal with the bitmap data directly (say, to perform a format conversion or to print the image on a dot-matrix printer), then the bottom-up ordering is a real pain.

As noted previously, the domain of the BMP image format is generated from two format versions on each of two operating environments and contains three distinct variants; see Figure 1. In each case, a BMP file contains, in the following order, a file header, bitmap header, optional palette, and bitmap. Format variances are confined to the bitmap header and palette.

Under Windows 3.1, the old format is represented by the structures BITMAPCOREHEADER, BITMAPCOREINFO, and RGBTRIPLE, while the newer format comprises BITMAPINFOHEADER, BITMAPINFO, and RGBQUAD. Both versions also include the BITMAPFILEHEADER structure. It should also be noted that a "COREINFO" structure consists of a "COREHEADER" followed by one or more RGBTRIPLE instances; similarly, an "INFO" structure consists of an "INFOHEADER" followed by one or more RGBQUAD instances. (I'm using the convention of paraphrasing names by dropping the BITMAP prefix and placing the remaining name in quotes.)

The corresponding OS/2 structure names are a bit more logical by themselves and a bit less logical when you lump them with the Windows names. The old format consists of BITMAPINFOHEADER, BITMAPINFO, and RGB, while version 2 names are BITMAPINFOHEADER2, BITMAPINFO2, and RGB2. As with the Windows versions, both structure sets also include a BITMAPFILEHEADER structure. The file-header structures are identical under both operating environments.

You may have noticed that the old OS/2 names are the same as the new Windows names. A second complication is that the structures overlap in both environments. Thus, a "COREINFO" consists of a "COREHEADER" and an RGBTRIPLE array, and an "INFO" consists of an "INFOHEADER" and an RGBQUAD array (using Windows in this example). If this all seems confusing, don't be alarmed--it is!

Palette entries in the old form of the format consist of three bytes indicating a blue, green, and red intensity, respectively. The newer versions add a fourth byte so that the palette can be read as an array of longs. Note that the traditional RGB order is evident when read into a long value on an Intel processor and then notated in hex.

The bitmap of a BMP file is organized as a series of scanlines and is presented beginning with the bottom row of the image and proceeding up. A second requirement is that scanlines are always padded, if necessary, so that they occupy an even number of 32-bit double-words. Given an image w pixels wide where each pixel is d bits deep, the number of bytes per scanline is calculated as rowbytes=((w*d+31)/32)*4.

BMP Structure Items

Generally speaking, unused or unimportant fields can safely be set to 0 in all situations. (In the following discussion, the names of the OS/2 structure items precede Windows names.)

A BMP-Format Class Design

There are many possible approaches to handling the BMP format from an application, all of which depend upon the requirements and operational domain of the application. For example, if you are writing for Windows 3.1 only, then dealing only with the newer Windows version of the format is an acceptable option. On the other hand, we are seeing more and more emphasis on cross-platform capabilities these days, and from this standpoint it seems desirable to handle the format no matter what--no excuses, no exceptions. This is obviously the most desirable strategy in any case, but also the one requiring the most work and presenting the most headache. However, once a suitable black box has been constructed and determined to function properly, it will never again have to be pondered; at least, not until somebody deems that a version 3 of the format is necessary.

The approach I took in my own development was to base a BMP class on the OS/2 2.1 format, which represents, in effect, a superset of all other format variants. When another variant is encountered on input, it is treated as if it were an incomplete form of the OS/2 format, one where default values are supplied for missing items. Conversely, if it is required to output a different format variant, it is a simple matter to omit extraneous items and to supply reformatting where necessary. A second aspect of the design is that I elected to treat BMP files as consisting of two components only: a large header followed by a bitmap. Thus, in place of a file header, a bitmap header, and a color array there is simply a header. This goes against conventional design wisdom to a certain extent, in that it is less modular. However, the interdependence of the various data structures tends to neutralize the benefits of a modular implementation.

When it is necessary to supply data components from the format for an instance of a BITMAPFILEHEADER alone, the BMP class provides member functions that return void pointers that can then be cast to the appropriate type. And if the required type is formatted differently than the class's "native" type, a reformatted version can be constructed on the fly in a temporary buffer and a pointer to the buffer returned.

With this background, a suitable, skeletal class definition would look something like Example 1. For more details, see Listing One and Two, page 82.

Final Thoughts

At this point you probably have a good idea of the complexity and capabilities of the BMP format. What is more difficult to convey is the format's importance and how to access and use it within a PM or Windows application. You might start by examining those functions in either platform's API that use BMP-format components as arguments and experimenting with them. BMPTEST.CPP, available electronically in both source and executable form (see "Availability," page 3) is a program that can serve as a starting point, in this case for my platform of choice, OS/2. Eventually you should find this familiar stranger to be a bit more familiar, and hopefully, a bit less strange!

Figure 1 BMP format domains.

Table 1: BMP file header usType (OS/2) values.

    Hex Value   Characters   Meaning

    4142        BA           Bitmap array
    4D42        BM           Bitmap
    4943        CI           Color icon
    5043        CP           Color pointer (mouse cursor)
    4349        IC           Icon
    5450        PT           Pointer (mouse cursor)

Example 1: A skeletal class definition for encapsulating BMP files.

class BmpImage
{
   public:
   enum BmpVersions
   {
      BMPOS2NEW,
      BMPOS2OLD,
      BMPWINNEW,
      BMPWINOLD
   };
   BmpImage( );
   ~BmpImage( );
   int read( char *path );
   int write( char * path, int version=BMPOS2NEW );
   void * filehdr( int version=BMPOS2NEW );
   void * bmaphdr( int version=BMPOS2NEW );
   void * palette( int version=BMPOS2NEW );
   void * bits( );
}    ;

Listing One


//------------------------------------------------------------------//
//  File:  BMP.H  -- Classes for encapsulating the BMP format       //
//  Copr:  Copyright (c) 1994 by Marv Luse                          //
//------------------------------------------------------------------//

#ifndef _BMP_H_
#define _BMP_H_

//.......Useful types
typedef unsigned char  uchar;
typedef unsigned short ushort;
typedef unsigned long  ulong;

//.......Useful constants
enum FileOrigins
{
   FILEBGN = SEEK_SET,
   FILECUR = SEEK_CUR,
   FILEEND = SEEK_END,
};
enum FileStates
{
   FILEOKAY,
   FILEENDOFFILE,
   FILENOTFOUND,
   FILEINVALID,
   FILENOTBMP,
   FILENOTBMPIMG,
   FILEERROR,
   FILENOMEMORY,
};
enum BmpVersions
{
   BMPWINOLD,
   BMPOS2OLD,
   BMPWINNEW,
   BMPOS2NEW,
};
enum BmpSizes
{
   BMPFILEHDRSIZE   = 14,
   BMPOLDANYHDRSIZE = 12,
   BMPNEWWINHDRSIZE = 40,
   BMPNEWOS2HDRSIZE = 64,
};
enum BmpTypes
{
   BMPARRAY       = 0x4142,    // 'BA'
   BMPBITMAP      = 0x4D42,    // 'BM'
   BMPCLRICON     = 0x4943,    // 'CI'
   BMPCLRPOINTER  = 0x5043,    // 'CP'
   BMPICON        = 0x4349,    // 'IC'
   BMPPOINTER     = 0x5450,    // 'PT'
};
//.......A class for performing binary input
class BinaryInput
{
   private:
   FILE * inp;
   public:
   BinaryInput( char * path );
   ~BinaryInput( );
   //.....read various types
   int  byte( );
   int  word( );
   long dword( );
   int  block( void * blk, int nbytes );
   //.....file management members
   int  ok( );
   int  error( );
   int  seek( long ofs, int org );
   long tell( );
};
//.......A class for a BMP header and bitmap
class BmpImage
{
   private:
   char  *bmBits;              // bitmap data
   ulong  bmNumColors;         // size of palette
   int    fiBmpStatus;         // a status code

   char  *tmpfilehdr;          // temporaries
   char  *tmpbmaphdr;
   char  *tmppalette;

   public:

   ushort fiType;              // type - 'BM' for bitmaps
   ulong  fiSizeFile;          // file size in bytes
   ushort fiXhot;              // 0 or x hotspot
   ushort fiYhot;              // 0 or y hotspot
   ulong  fiOffBits;           // offset to bitmap
   ulong  bmSizeHeader;        // size of this data - 64
   ulong  bmWidth;             // bitmap width in pixels
   ulong  bmHeight;            // bitmap height in pixels
   ushort bmPlanes;            // num planes - always 1
   ushort bmBitCount;          // bits per pixel
   ulong  bmCompression;       // compression flag
   ulong  bmSizeImage;         // image size in bytes
   long   bmXPelsPerMeter;     // horz resolution
   long   bmYPelsPerMeter;     // vert resolution
   ulong  bmClrUsed;           // 0 -> color table size
   ulong  bmClrImportant;      // important color count
   ushort bmUnits;             // units of measure
   ushort bmReserved;          // reserved
   ushort bmRecording;         // recording algorithm
   ushort bmRendering;         // halftoning algorithm
   ulong  bmSize1;             // size value 1
   ulong  bmSize2;             // size value 2
   ulong  bmIdentifier;        // for application use
   ulong  bmPalette[256];      // image palette

   BmpImage( char * path );
   ~BmpImage( );

   //.....member functions - query
   long   width( );            // bitmap width in pixels
   long   height( );           // bitmap height in pixels
   long   depth( );            // bitmap depth in bits
   long   rowbytes( );         // scan line width in bytes
   long   size( );             // bitmap size in bytes
   int    planes( );           // number of planes
   int    bits( );             // bits per plane
   int    compression( );      // compression type
   int    xres( );             // x res as pels/meter
   int    xdpi( );             // x res dots/inch
   int    yres( );             // y res as pels/meter
   int    ydpi( );             // y res dots/inch
   void * filehdr( int vers ); // ptr to bmp file header
   void * bmaphdr( int vers ); // ptr to bmp info header
   void * palhdr( int vers );  // ptr to bmp rgb array
   void * bitmap( int vers );  // ptr to bmp bitmap data
   int    status( );           // image/file status code
};
#endif

Listing Two

//-------------------------------------------------------------------//
//  File:  BMP.CPP -- Classes for encapsulating the BMP format       //
//  Copr:  Copyright (c) 1994 by Marv Luse                          //
//------------------------------------------------------------------//
// Notes... (1) Only BMP input is illustrated here, and in general, the code 
// is intended as a model only. (2) No size typing is performed on pointers or
// the objects to which they point (i.e., near, far, huge, etc). This is 
// normal for OS/2, but since Windows is 16-bit, the code will need to be 
// modified slightly for that environment. In particular, if the entire bitmap
// is to be accessible through a single pointer, that pointer should be 
// declared huge. (3) The code was tested under OS/2 2.1 using the Borland
// 1.0 OS/2 compiler. Tweaking may be necessary with other environment mixes.

#include "stdlib.h"
#include "stdio.h"
#include "string.h"
#include "bmp.h"

//.......A class for performing binary input
BinaryInput::BinaryInput( char * path )
{
   inp = fopen( path, "rb" );
}
BinaryInput::~BinaryInput( )
{
   if( inp ) fclose( inp );
}
int BinaryInput::byte( )
{
   return fgetc( inp );
}
int BinaryInput::word( )
{
   short s;
   fread( &s, sizeof(short), 1, inp );
   return s;
}
long BinaryInput::dword( )
{
   long l;
   fread( &l, sizeof(long), 1, inp );
   return l;
}
int BinaryInput::block( void * blk, int nbytes )
{
   return fread( blk, nbytes, 1, inp );
}

int BinaryInput::ok( )
{
   return ((inp==0) || ferror(inp) || feof(inp)) ? 0 : 1;
}
int BinaryInput::error( )
{
   if( inp == 0 )    return FILENOTFOUND;
   if( feof(inp) )   return FILEENDOFFILE;
   if( ferror(inp) ) return FILEERROR;
   return FILEOKAY;
}
int BinaryInput::seek( long ofs, int org )
{
   return inp ? fseek( inp, ofs, org ) : FILEERROR;
}
long BinaryInput::tell( )
{
   return inp ? ftell( inp ) : -1;
}
//.......A class for a BMP header and bitmap
BmpImage::BmpImage( char * path )
{
   //.....initialize nonformat items
   bmBits = 0;
   bmNumColors = 0;
   tmpfilehdr = tmpbmaphdr = tmppalette = 0;
   //.....the remaining items constitute a valid OS/2 2.x BMP header set
   memset( &fiType,0,BMPFILEHDRSIZE + BMPNEWOS2HDRSIZE + sizeof(long) * 256 );
   //.....instantiate the input stream
   BinaryInput inB( path );
   if( ! inB.ok() )
   {
      fiBmpStatus = inB.error( );
      return;
   }
   //.....get the file header type field and verify
   fiType = (ushort) inB.word( );
   switch( fiType )
   {
      case BMPBITMAP:
           break;
      case BMPARRAY:
      case BMPCLRICON:
      case BMPCLRPOINTER:
      case BMPICON:
      case BMPPOINTER:
           fiBmpStatus = FILENOTBMPIMG;
           return;
      default:
           fiBmpStatus = FILENOTBMP;
           return;
   }
   //.....read rest of file hdr, which isn't versn dependent
   fiSizeFile = inB.dword( );
   fiXhot     = (ushort) inB.word( );
   fiYhot     = (ushort) inB.word( );
   fiOffBits  = inB.dword( );
   //.....get the bitmap header size field and verify
   bmSizeHeader = inB.dword( );
   switch( bmSizeHeader )
   {
      case BMPOLDANYHDRSIZE:
      case BMPNEWWINHDRSIZE:
      case BMPNEWOS2HDRSIZE:
           break;
      default:
           if( (bmSizeHeader < BMPOLDANYHDRSIZE) ||
               (bmSizeHeader > BMPNEWOS2HDRSIZE) )
           {
               fiBmpStatus = FILENOTBMP;
               return;
           }
           break;
   }
   //.....read the rest of the bitmap header and palette
   if( bmSizeHeader == BMPOLDANYHDRSIZE )
   {
      bmWidth     = inB.word( );
      bmHeight    = inB.word( );
      bmPlanes    = (ushort) inB.word( );
      bmBitCount  = (ushort) inB.word( );
      bmNumColors = (fiOffBits - bmSizeHeader - BMPFILEHDRSIZE) / 3;
      bmSizeImage = rowbytes( ) * bmHeight;
      for( int i=0; i<bmNumColors; i++ )
      {
         long blu = inB.byte( );
         long grn = inB.byte( );
         long red = inB.byte( );
         bmPalette[i] = (red << 16) | (grn << 8) | blu;
      }
   }
   else
   {
      long nbytes = bmSizeHeader - 4;
      inB.block( &bmWidth, nbytes );
      bmNumColors = (fiOffBits - bmSizeHeader - BMPFILEHDRSIZE) / 4;
      if( bmNumColors > 0 )
         inB.block( bmPalette, bmNumColors * 4 );
   }
   //.....read the bitmap. Works only for bitmaps 64K or smaller under Windows.
   bmBits = new char [ rowbytes() * bmHeight ];
   if( bmBits )
   {
      inB.block( bmBits, rowbytes() * bmHeight );
      fiBmpStatus = inB.ok( ) ? FILEOKAY : inB.error( );
   }
   else
      fiBmpStatus = FILENOMEMORY;
}
BmpImage::~BmpImage( )
{
   delete [] tmpfilehdr;
   delete [] tmpbmaphdr;
   delete [] tmppalette;
   delete [] bmBits;
}
long BmpImage::width( )
{
   return bmWidth;
}
long BmpImage::height( )
{
   return bmHeight;
}

long BmpImage::depth( )
{
   return bmPlanes * bmBitCount;
}
long BmpImage::rowbytes( )
{
   return (((bmPlanes*bmBitCount*bmWidth) + 31) / 32) * 4;
}
long BmpImage::size( )
{
   return bmSizeImage ? bmSizeImage : rowbytes() * bmHeight;
}
int BmpImage::planes( )
{
   return bmPlanes;
}
int BmpImage::bits( )
{
   return bmBitCount;
}
int BmpImage::compression( )
{
   return bmCompression;
}
int BmpImage::xres( )
{
   return bmXPelsPerMeter;
}
int BmpImage::xdpi( )
{
   return (int) ((bmXPelsPerMeter * 100) / 3937);
}
int BmpImage::yres( )
{
   return bmYPelsPerMeter;
}
int BmpImage::ydpi( )
{
   return (int) ((bmYPelsPerMeter * 100) / 3937);
}
void * BmpImage::filehdr( int vers )
{
   // file header is not version dependent
   return (void *) &fiType;
}
void * BmpImage::bmaphdr( int vers )
{
   // the first 40 bytes of the new OS/2 header is the same as the new Windows
   // header, except for the length value (40 versus 64); the old headers,
   // however, requires reformatting
   if( vers == BMPOS2NEW )
      return &bmSizeHeader;
   // allocate space for worst case - 40 bytes
   if( tmpfilehdr == 0 )
      tmpfilehdr = new char [ BMPNEWWINHDRSIZE ];
   if( (vers == BMPWINNEW) && (tmpfilehdr != 0) )
   {
      memcpy( tmpfilehdr, &bmSizeHeader, BMPNEWWINHDRSIZE );
      *((ulong *) tmpfilehdr) = BMPNEWWINHDRSIZE;
   }
   else if( ((vers == BMPWINOLD) || (vers == BMPOS2OLD)) &&
            (tmpfilehdr != 0) )
   {
      // this is ugly, but safe and functional!
      *((ulong *) tmpfilehdr) = BMPOLDANYHDRSIZE;
      short * hdr = (short *) tmpfilehdr;
      hdr[2] = (short) bmWidth;
      hdr[3] = (short) bmHeight;
      hdr[4] = bmPlanes;
      hdr[5] = bmBitCount;
   }
   return (void *) tmpfilehdr;
}
void * BmpImage::palhdr( int vers )
{
   // The palette format is the same for both new
   // format versions, but the old format requires reformatting.
   if( (vers == BMPOS2NEW) || (vers == BMPWINNEW) )
      return (bmNumColors > 0) ? (void *) &bmPalette[0] : 0;
   // allocate space for old palette
   if( (tmppalette == 0) && (bmNumColors > 0) )
   {
      tmppalette = new char [ bmNumColors * 3 ];
      if( tmppalette != 0 )
      {
         char * s = (char *) &bmPalette[0];
         char * d = (char *) tmppalette;
         for( int i=0; i<bmNumColors; i++ )
         {
            *d++ = *s++;
            *d++ = *s++;
            *d++ = *s++;
            s++;
         }
      }
   }
   return tmppalette;
}
void * BmpImage::bitmap( int vers )
{
   // bitmap is not version dependent
   return (void *) bmBits;
}
int BmpImage::status( )
{
   return fiBmpStatus;
}

Copyright © 1994, Dr. Dobb's Journal

Terms of Service | Privacy Statement | Copyright © 2024 UBM Tech, All rights reserved.