BMP FILES
  This article will discuss how to read and display BMP files. BMP files
  can be 2, 16, 256 or 16.7M colors. We are going to deal with the
  256 color versions only. Because they are the easiest to show on a 256
  color VGA card.

BMP FILE LAYOUT
  As with almost all graphics files the first part of the BMP file is
  called the header. The header contain all the information about the
  image. After the header is the palette (if there is one). Finally comes
  the image information itself.

HEADER LAYOUT
  Here is the layout of the BMP header as seen by Turbo Pascal:

Type
  TBMPHeader = Record
    ID: Array[0..1] of Char;  { Must be 'BM' }
    BMPFileSize: LongInt;     { Size of this file }
    Reserved: LongInt;        { ??? }
    HeaderSize: LongInt;      { Size of header }
    InfoSize: LongInt;        { Size of info that follows header }
    Width, Height: LongInt;   { Width and Height of image }
    biPlanes, Bits: Integer;  { Bits can be 1, 4, 8, or 24 }
    biCompression, biSizeImage,       { We don't care about anything else }
    biXPelsPerMeter, biYPelsPerMeter,
    biClrUsed, biClrImportant: LongInt;
  End;

  Looks we have quite a bit of information about the image. Want we really
  care about is the width and height and the bits.

  Here is the code to read the header:
----------------- SNIP HERE --------------------
Program BMPHead;

Type
  TBMPHeader = Record
    ID: Array[0..1] of Char;  { Must be 'BM' }
    BMPFileSize: LongInt;     { Size of this file }
    Reserved: LongInt;        { ??? }
    HeaderSize: LongInt;      { Size of header }
    InfoSize: LongInt;        { Size of info that follows header }
    Width, Height: LongInt;   { Width and Height of image }
    biPlanes, Bits: Integer;  { Bits can be 1, 4, 8, or 24 }
    biCompression, biSizeImage,       { We don't care about anything else }
    biXPelsPerMeter, biYPelsPerMeter,
    biClrUsed, biClrImportant: LongInt;
  End;

Var
  BMPFile: File;
  BMPHeader: TBMPHeader;

Begin
  Assign(BMPFile, 'BABY.BMP'); { NOTE: Change 'TEST.BMP' to another name }
  Reset(BMPFile, 1);           { We want to read the file 1 byte at a time }
  BlockRead(BMPFile, BMPHeader, SizeOf(BMPHeader)); { Read the header }
  Close(BMPFile);
  WriteLn('Image Width= ', BMPHeader.Width);
  WriteLn('Image Height=', BMPHeader.Height);
  WriteLn('Image Bits=  ', BMPHeader.Bits);
End.
----------------- SNIP HERE --------------------
IMAGES WITH PALETTES

  If the bits value in the header is 8 then that means that the image is
  a 256 color image that uses a palette. In this case we must get the
  palette from the BMP file. Luckily the palette is right after the header.
  For some reason microsoft made each palette entry 4 bytes long, even
  though we only need 3 bytes (RGB) for the VGA card. The palette of course
  will have 256 entries. Not only is the BMP palette entry 4 bytes long, but
  the first three are in the reverse order. Also the color values range from
  0 to 255, but the VGA card excepts only 0 to 63, so we have to divide each
  of them by 4. Here is how to read the palette from the BMP file:
----------------- SNIP HERE --------------------
Program BMPPal;

Type
  TBMPHeader = Record
    ID: Array[0..1] of Char;  { Must be 'BM' }
    BMPFileSize: LongInt;     { Size of this file }
    Reserved: LongInt;        { ??? }
    HeaderSize: LongInt;      { Size of header }
    InfoSize: LongInt;        { Size of info that follows header }
    Width, Height: LongInt;   { Width and Height of image }
    biPlanes, Bits: Integer;  { Bits can be 1, 4, 8, or 24 }
    biCompression, biSizeImage,       { We don't care about anything else }
    biXPelsPerMeter, biYPelsPerMeter,
    biClrUsed, biClrImportant: LongInt;
  End;

Var
  BMPFile: File;
  BMPHeader: TBMPHeader;
  BMPPaletteEntry: Array[0..3] of Byte; { BMP file 4 byte palette entry }
  VGAPalette: Array[0..255, 0..2] of Byte; { Complete VGA palette }
  I: Integer;

Begin
  Assign(BMPFile, 'BABY.BMP'); { NOTE: Change 'TEST.BMP' to another name }
  Reset(BMPFile, 1);           { We want to read the file 1 byte at a time }
  BlockRead(BMPFile, BMPHeader, SizeOf(BMPHeader)); { Read the header }
  If BMPHeader.Bits = 8 Then
  Begin
    For I:=0 to 255 Do
    Begin
      BlockRead(BMPFile, BMPPaletteEntry, SizeOf(BMPPaletteEntry));
      VGAPalette[I, 0]:=BMPPaletteEntry[2] Div 4; { Note reverse order 2,1,0}
      VGAPalette[I, 1]:=BMPPaletteEntry[1] Div 4;
      VGAPalette[I, 2]:=BMPPaletteEntry[0] Div 4;
    End;
  End;
  Close(BMPFile);
  WriteLn('Image Width= ', BMPHeader.Width);
  WriteLn('Image Height=', BMPHeader.Height);
  WriteLn('Image Bits=  ', BMPHeader.Bits);
End.
----------------- SNIP HERE --------------------
SETTING THE VGA PALETTE
  Now we have the image's palette now we have to set the VGA palette to the
  image palette. Since the palette has 256 entries first we must get the
  VGA card into a mode that supports 256 colors. The standard mode is
  320x200 with 256 colors. To set this mode is very easy. However, we must
  call the video bios directly since TP doesn't support this mode.
  Here is what we do to set mode 13:

  Asm
    MOV AX,$0013
    INT $10
  End;

  Now that we are in a 256 color mode let's set the palette. If you don't
  set the palette the image will show using the default palette which is
  terrible. We will use the direct hardware access method to set the palette:

  For I:=0 to 255 Do
  Begin
    Port[$3C8]:=I;
    Port[$03C9]:=VGAPalette[I, 0];
    Port[$03C9]:=VGAPalette[I, 1];
    Port[$03C9]:=VGAPalette[I, 2];
  End;

  To use this method just set port $03C8 to the color you want to set.
  Then set port $03C9 with the Red, Green and Blue values.

PLOTTING 256 COLOR PIXELS

  Ok, so now we are in 256 color mode and we have the palette set, now how
  do we set pixels ? Well this video mode is very popular because it is so
  easy to set pixels. First we must know that the video memory for this
  mode starts at $A000:0000 in memory. Also each pixel is 1 byte. The pixel
  in the upper left hand corner is at the start of video memory. Then next
  pixel to the right is next and so on across the top row. The next byte
  in video memory after the last pixel in the top row is the first pixel
  in the next row down.

    0   ....   319
  320   ....   639
  640   ....   959

  And so on for 200 vertical lines. To make is easy on ourselves lets just
  declare and array like this:

  Var
    VGAMemory: Array[0..199, 0..319] of Byte Absolute $A000:$0000;

  Notice that the y range is the first dimension and the x is the second.
  If you have never used the Absolute keyword basicly it just mean to place
  the variable as a specific place in memory instead of at the next free
  place.

  Now setting pixel is a piece of cake. Just do this:

  VGAMemory[Y, X]:=Color;

  Here is a program that does the following:
    Reads the BMP header.
    Reads the BMP palette. (needs to be a 256 color image)
    Set the VGA card for mode 13.
    Set the VGA palette to the image palette.
    Draws 256 vertical lines, one in each palette color.
----------------- SNIP HERE --------------------
Program ShowPal;

Uses Crt{TextMode};

Type
  TBMPHeader = Record
    ID: Array[0..1] of Char;  { Must be 'BM' }
    BMPFileSize: LongInt;     { Size of this file }
    Reserved: LongInt;        { ??? }
    HeaderSize: LongInt;      { Size of header }
    InfoSize: LongInt;        { Size of info that follows header }
    Width, Height: LongInt;   { Width and Height of image }
    biPlanes, Bits: Integer;  { Bits can be 1, 4, 8, or 24 }
    biCompression, biSizeImage,       { We don't care about anything else }
    biXPelsPerMeter, biYPelsPerMeter,
    biClrUsed, biClrImportant: LongInt;
  End;

Var
  VGAMemory: Array[0..199, 0..319] of Byte Absolute $A000:$0000;

  BMPFile: File;
  BMPHeader: TBMPHeader;
  BMPPaletteEntry: Array[0..3] of Byte; { BMP file 4 byte palette entry }
  VGAPalette: Array[0..255, 0..2] of Byte; { Complete VGA palette }
  I, X, Y: Integer;

Begin
  Assign(BMPFile, 'BABY.BMP'); { NOTE: Change 'TEST.BMP' to another name }
  Reset(BMPFile, 1);           { We want to read the file 1 byte at a time }
  BlockRead(BMPFile, BMPHeader, SizeOf(BMPHeader)); { Read the header }
  If BMPHeader.Bits = 8 Then
  Begin
    For I:=0 to 255 Do
    Begin
      BlockRead(BMPFile, BMPPaletteEntry, SizeOf(BMPPaletteEntry));
      VGAPalette[I, 0]:=BMPPaletteEntry[0];
      VGAPalette[I, 1]:=BMPPaletteEntry[1];
      VGAPalette[I, 2]:=BMPPaletteEntry[2];
    End;
  End
  Else
  Begin
    WriteLn('Need to use a 256 color BMP file for this program.');
    Close(BMPFile);
    Halt;
  End;
  Close(BMPFile);

  { Put VGA card in mode 13 }
  Asm
    MOV AX,$0013
    INT $10
  End;

  { Set all palette entries }
  For I:=0 to 255 Do
  Begin
    Port[$03C8]:=I;
    Port[$03C9]:=VGAPalette[I, 0];
    Port[$03C9]:=VGAPalette[I, 1];
    Port[$03C9]:=VGAPalette[I, 2];
  End;

  { Draw vertical lines }
  For X:=0 to 255 Do
  Begin
    For Y:=0 to 199 Do VGAMemory[Y, X]:=X;
  End;

  { Wait for user to press enter }
  ReadLn;
  TextMode(CO80);
End.
----------------- SNIP HERE --------------------
  Well, that's everything except showing the image. The image data follows
  the header and palette. Microsoft throws another monkey wrench in here too.
  Each line of the image is padded to be an even multiple of 4. For example
  if the image is 398 bytes wide. Each line in the file will have 400 bytes.
  So we have to calculate filewidth like this:

  FileWidth:=((BMPHeader.Width + 3) Div 4) * 4);

  And just for kicks, microsoft make the bottom line in the image the
  first line in the BMP file. (Someone had a little too much Jolt(R) cola
  when they were writting the layout). To get around this problem I just
  calculate where the lines start and seek to the first line, read it, then
  seek to the second line. It's a slow method but it works.

IMAGES WITHOUT A PALETTE (TRUECOLOR)
  Can you imagine 16.7 Million colors! Well that's how many colors are
  possible with a 24-bit truecolor image. Of course if you made an image
  that had all 16.7 million colors is would need over 50 megs! Another
  problem, how do you display an image that can have 16.7 million colors
  on a vga screen that can only show 256 colors? Well, we have to cheat.
  Look at it this way a 24-bit uses 8-bits for each of the Red, Green and
  Blue values. We have only 8-bits. So to split them up we give Red 3 bits,
  we give green 3 bits, and blue 2 bits. This is because our eyes are less
  sensitive to blue light. We lose 2/3 of the color information but the
  images are not too bad, you can at least tell what it is. There are much
  better ways to handle this problem, but this is just some code to show
  you how to read true color images. Here again microsoft is up to it's
  tricks, the colors are stored in reverse order for EACH pixel of the image.
  This mean the color are stored as Blue, Green, Red. If anyone knows why
  this was done please let me know as I can see absolutly no reason for it.

HERE'S THE CODE
----------------- SNIP HERE --------------------
Program ShowBMP;

Uses Crt{TextMode};

Type
  TBMPHeader = Record
    ID: Array[0..1] of Char;  { Must be 'BM' }
    BMPFileSize: LongInt;     { Size of this file }
    Reserved: LongInt;        { ??? }
    HeaderSize: LongInt;      { Size of header }
    InfoSize: LongInt;        { Size of info that follows header }
    Width, Height: LongInt;   { Width and Height of image }
    biPlanes, Bits: Integer;  { Bits can be 1, 4, 8, or 24 }
    biCompression, biSizeImage,       { We don't care about anything else }
    biXPelsPerMeter, biYPelsPerMeter,
    biClrUsed, biClrImportant: LongInt;
  End;
  { This is the structure of the BMP truecolor data }
  TPixelBGR = Record Blue, Green, Red: Byte; End;

Var
  { Map an array to the VGA screen memory }
  VGAMemory: Array[0..199, 0..319] of Byte Absolute $A000:$0000;
  BMPFile: File;
  BMPHeader: TBMPHeader;
  BMPPaletteEntry: Array[0..3] of Byte; { BMP file 4 byte palette entry }
  VGAPalette: Array[0..255, 0..2] of Byte; { Complete VGA palette }
  I, X, Y: Integer;
  FileWidth: Word;
  ShowHeight: Integer;
  ShowWidth: Integer;
  Pixel: Byte;
  PixelBGR: TPixelBGR;
Begin
  Assign(BMPFile, 'C:\BABYTC.BMP'); { NOTE: Change 'TEST.BMP' to another name }
  Reset(BMPFile, 1);           { We want to read the file 1 byte at a time }
  BlockRead(BMPFile, BMPHeader, SizeOf(BMPHeader)); { Read the header }
  { Check if image is 8 or 24 bits }
  If (BMPHeader.Bits <> 8) and (BMPHeader.Bits <> 24) Then
  Begin
    WriteLn('Need to use a 256 or 16.7M color BMP file for this program.');
    Close(BMPFile);
    Halt;
  End;
  { If a 256 color image then read palette }
  If BMPHeader.Bits = 8 Then
  Begin
    { Read the palette }
    For I:=0 to 255 Do
    Begin
      BlockRead(BMPFile, BMPPaletteEntry, SizeOf(BMPPaletteEntry));
      VGAPalette[I, 0]:=BMPPaletteEntry[2] SHR 2;
      VGAPalette[I, 1]:=BMPPaletteEntry[1] SHR 2;
      VGAPalette[I, 2]:=BMPPaletteEntry[0] SHR 2;
    End;
  End
  Else { Must be a 24 bit image to setup palette }
  Begin
    { Setup mapping palette }
    For I:=0 to 255 Do
    Begin
      VGAPalette[I, 0]:=(I AND 7) * 8 + 7;
      VGAPalette[I, 1]:=((I SHR 3) AND 7) * 8 + 7;
      VGAPalette[I, 2]:=(I SHR 6) * 16 + 15;
    End;
  End;
  { Put VGA card in mode 13 }
  Asm
    MOV AX,$0013
    INT $10
  End;
  { Set all palette entries }
  For I:=0 to 255 Do
  Begin
    Port[$03C8]:=I;
    Port[$03C9]:=VGAPalette[I, 0];
    Port[$03C9]:=VGAPalette[I, 1];
    Port[$03C9]:=VGAPalette[I, 2];
  End;

  If BMPHeader.Bits = 8 Then
   FileWidth:=((BMPHeader.Width +3) Div 4) * 4
  Else
   FileWidth:=((BMPHeader.Width * 3 + 3) Div 4) * 4;

  ShowHeight:=BMPHeader.Height;
  If ShowHeight > 200 Then ShowHeight:=200; { Can only show 200 lines }

  ShowWidth:=BMPHeader.Width;
  If ShowWidth > 320 Then ShowWidth:=320;  { Can only show 320 pixels }
  { Y loops through each vertical line of the image }
  For Y:=0 to ShowHeight -1 Do
  Begin
    { Position file to line Y }
    Seek(BMPFile, BMPHeader.HeaderSize + FileWidth * (BMPHeader.Height - Y - 1));
    For X:=0 to ShowWidth -1 Do
    Begin
      If BMPHeader.Bits = 8 Then { Handle 8-bit pixel }
       BlockRead(BMPFile, Pixel, SizeOf(Pixel))
      Else
      Begin { Handle 24-bit pixel }
        BlockRead(BMPFile, PixelBGR, SizeOf(PixelBGR));
        { Map RGB to our much smaller palette index }
        Pixel:=PixelBGR.Red SHR 5 + ((PixelBGR.Green SHR 5) SHL 3) +
         (PixelBGR.Blue AND 192);
      End;
      { Set the pixel on the screen }
      VGAMemory[Y, X]:=Pixel;
    End;
  End;

  { Wait for user to press enter }
  ReadLn;

  { Return to text mode }
  TextMode(CO80);
End.
----------------- SNIP HERE --------------------

  There that wasn't too hard. There is quite a bit of data moving around in
  this program. It tends to be rather slow. But, again this is just for
  demonstration. Next article will be how to print BMP images in gray
  scale on a PCL printer (that's Laserjet/Deskjet printers).
  Bean, thitt@igateway.com




    Source: geocities.com/SiliconValley/2926/tpsrc

               ( geocities.com/SiliconValley/2926)                   ( geocities.com/SiliconValley)