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 , 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:
- The String type is not especially standard, but is in
both Turbo Pascal and Code Warrior Pascal. Strings are declared
as arrays of up to 255 characters, where index 0 contains the actual
number of characters in use by the string.
- The Const Zero_Array is a "typed constant" in Turbo that allows
predefined values to be inserted into a structure at compile time.
The term Const is really incorrect, as the structures aren't truly
constant, but may be modified (at the programmer's peril) at run-time.
The array is used as the parameter to BlockWrite for the zeros
to pad on the end of a raster line (BlockWrite needs a variable
in that parameter position, and won't accept a true constant).
- The Outfile : File declaration is to declare that Outfile
is an untyped file, meaning that we can use BlockWrite to
dump any old group of bytes to the file, as long as we know how
many bytes to write.
- The Reset (Outfile, 1) is to declare that the basic
storage format written out to the file is one byte long.
- The BlockWrite (Outfile, Header, SizeOf(Header)) statement
writes the entire header record out to the file. The SizeOf
function returns the number of bytes in the header, which you can
calculate yourself if you are careful.
- Similarly, the BlockWrite (Outfile, B, 1) statements
each write out a single byte to the file. This is dreadfully
inefficient, as the output file has only a small buffer. To speed up the
process, I would (in real life) send each byte to a very large array
as an internal buffer, then BlockWrite the array to the file when
it gets full, and BlockWrite the piece of the array that is full
when the entire image has been processed.
- The BlockWrite (Outfile, Zero_Array, Raster_Pad) statement
writes out 1, 2, or 3 bytes from the Zero_Array to the file, depending
on the current value of Raster_Pad. Again, these bytes could be dumped
into an internal buffer for efficiency's sake.
Back to the CS32 Page
Back to Dr. Bill's Home Page