24 Bit .BMP Files


Copyright (C) Dr. William T. Verts, April 19, 1996

In this document I describe the 24 bit .BMP format supported by Microsoft. The basic .BMP format can support 1, 4, 8, or 24 bits per pixel. The formats smaller than 24 bits include a palette as part of the .BMP file, but since 24 bit images do not require a palette the file is correspondingly simpler (and the appropriate entries in the header record are set to zero). Similarly, the format supports rudimentary compression, which we will ignore.


For the purposes of the main procedure I assume that the following constant and procedure definitions have been made.

Const Zero = 0 ; Procedure Lswap (Var N1,N2:LongInt) ; Var T1, T2 : LongInt ; Begin T1 := N1 ; T2 := N2 ; N1 := T2 ; N2 := T1 ; End ;
The basic definition of the bitmap header is as follows. The names are from the Programmers PC Sourcebook, and are derived from Microsoft's field names. This header is really a composite of two headers, packed for simplicity's sake. Some fields are not used at all, some are not used by the 24 bit format, and some don't seem to be used correctly by a few of the programs I've tested. A Char is a 1-byte character, a Word is a 2-byte unsigned integer, and a LongInt is a 4-byte signed integer.

Type BMP_Header = Record bfType1 : Char ; (* "B" *) bfType2 : Char ; (* "M" *) bfSize : LongInt ; (* Size of File *) bfReserved1 : Word ; (* Zero *) bfReserved2 : Word ; (* Zero *) bfOffBits : LongInt ; (* Offset to beginning of BitMap *) biSize : LongInt ; (* Number of Bytes in Structure *) biWidth : LongInt ; (* Width of BitMap in Pixels *) biHeight : LongInt ; (* Height of BitMap in Pixels *) biPlanes : Word ; (* Planes in target device = 1 *) biBitCount : Word ; (* Bits per Pixel 1, 4, 8, or 24 *) biCompression : LongInt ; (* BI_RGB = 0, BI_RLE8, BI_RLE4 *) biSizeImage : LongInt ; (* Size of Image Part (often ignored) *) biXPelsPerMeter : LongInt ; (* Always Zero *) biYPelsPerMeter : LongInt ; (* Always Zero *) biClrUsed : LongInt ; (* # of Colors used in Palette *) biClrImportant : LongInt ; (* # of Colors that are Important *) End ;
Somewhere in your code you probably have a procedure to pick up the color at a particular pixel from the screen. This procedure might look like the following code, and is used in the main routine to save bitmaps. Procedure Get_Pixel (X,Y:Integer ; Var R,G,B:Byte) ; Begin { Returns color at pixel <X,Y>, returns 0 if off-screen } End ;
Now comes the tricky bits of code. This main routine takes in the name of the file to receive the bitmap, and the coordinates of the corners of the box on screen to save. I am using some pretty Turbo-Pascal-specific code here, which you may have to modify to get to work on your system. Those specific sections are described after the end of this code block.

Procedure Save_24_Bit_BMP (Filename:String ; X1,Y1,X2,Y2:LongInt) ; Const Zero_Array : Array [1..4] Of Byte = (0, 0, 0, 0) ; Var Outfile : File ; File_OK : Boolean ; Header : BMP_Header ; Bytes_Per_Raster: LongInt ; Raster_Pad : Integer ; X, Y : LongInt ; R, G, B : Byte ; Begin (*----------------------------------------------------------*) (* Normalize the rectangle coordinates *) (*----------------------------------------------------------*) If (X1 > X2) Then Lswap(X1, X2) ; If (Y1 > Y2) Then Lswap(Y1, Y2) ; (*----------------------------------------------------------*) (* Compute the number of bytes per raster. 24 bit .BMP *) (* files require that each raster be a multiple of 32 bits *) (* (4 bytes) in length, so depending on the number of pixels*) (* in each raster line, we may have to write out between 0 *) (* and 3 zero bytes to pad the line properly. *) (*----------------------------------------------------------*) Bytes_Per_Raster := (X2 - X1 + 1) * 3 ; If Bytes_Per_Raster Mod 4 = 0 Then Raster_Pad := Zero Else Raster_Pad := 4 - (Bytes_Per_Raster Mod 4) ; Bytes_Per_Raster := Bytes_Per_Raster + Raster_Pad ; (*----------------------------------------------------------*) (* Set up the header of the file using current image values *) (*----------------------------------------------------------*) With Header Do Begin bfType1 := 'B' ; (* Always 'B' *) bfType2 := 'M' ; (* Always 'M' *) bfSize := Zero ; (* Size of File, Computed below *) bfReserved1 := Zero ; (* Always Zero *) bfReserved2 := Zero ; (* Always Zero *) bfOffbits := SizeOf(Header) ; (* Pointer to Image Start *) biSize := 40 ; (* Bytes in bi Section *) biWidth := X2 - X1 + 1 ; (* Width of Image *) biHeight := Y2 - Y1 + 1 ; (* Height of Image *) biPlanes := 1 ; (* One Plane (all colors packed)*) biBitCount := 24 ; (* Bits per Pixel *) biCompression := Zero ; (* No Compression *) biSizeImage := Bytes_Per_Raster * biHeight; (* Size of Image *) biXPelsPerMeter := Zero ; (* Always Zero *) biYPelsPerMeter := Zero ; (* Always Zero *) biClrUsed := Zero ; (* No Palette in 24 bit mode *) biClrImportant := Zero ; (* No Palette in 24 bit mode *) bfSize := SizeOf(Header) + biSizeImage ; End ; (*----------------------------------------------------------*) (* Open the file for writing. The $I- and $I+ directives *) (* disable file I/O checks around the area that the file is *) (* opened in order to explicitly trap errors (such as errors*) (* in the filename). Any errors cause the procedure to exit*) (* immediately without writing anything. *) (*----------------------------------------------------------*) {$I-} Assign (Outfile, Filename) ; Rewrite (Outfile, 1) ; File_OK := (IOresult = Zero) ; If Not File_OK Then Exit ; {$I+} (*----------------------------------------------------------*) (* Write out the header record of the bitmap to the file. *) (*----------------------------------------------------------*) BlockWrite (Outfile, Header, SizeOf(Header)) ; (*----------------------------------------------------------*) (* Write out the main image to the file. .BMP files are *) (* stored greatest scan-line first, and in Blue-Green-Red *) (* order. If Raster_Pad > 0, then that many zeroes are *) (* tacked onto the end of each raster line. *) (*----------------------------------------------------------*) For Y := Y2 DownTo Y1 Do Begin For X := X1 To X2 Do Begin Get_Pixel (X, Y, R, G, B) ; BlockWrite(Outfile, B, 1) ; BlockWrite(Outfile, G, 1) ; BlockWrite(Outfile, R, 1) ; End ; If Raster_Pad > Zero Then BlockWrite (Outfile, Zero_Array, Raster_Pad) ; End ; (*----------------------------------------------------------*) (* Close the file, ignoring any I/O errors. *) (*----------------------------------------------------------*) {$I-} Close (Outfile) ; File_OK := (IOresult = Zero) ; {$I+} End ;
Here are the weirdnesses that I used in the procedure just above:
Back to the CS32 Page

Back to Dr. Bill's Home Page