/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%                            BBBB   M   M  PPPP                               %
%                            B   B  MM MM  P   P                              %
%                            BBBB   M M M  PPPP                               %
%                            B   B  M   M  P                                  %
%                            BBBB   M   M  P                                  %
%                                                                             %
%                                                                             %
%                    Read/Write ImageMagick Image Format.                     %
%                                                                             %
%                                                                             %
%                              Software Design                                %
%                                John Cristy                                  %
%                                 July 1992                                   %
%                                                                             %
%                                                                             %
%  Copyright 1999 E. I. du Pont de Nemours and Company                        %
%                                                                             %
%  Permission is hereby granted, free of charge, to any person obtaining a    %
%  copy of this software and associated documentation files ("ImageMagick"),  %
%  to deal in ImageMagick without restriction, including without limitation   %
%  the rights to use, copy, modify, merge, publish, distribute, sublicense,   %
%  and/or sell copies of ImageMagick, and to permit persons to whom the       %
%  ImageMagick is furnished to do so, subject to the following conditions:    %
%                                                                             %
%  The above copyright notice and this permission notice shall be included in %
%  all copies or substantial portions of ImageMagick.                         %
%                                                                             %
%  The software is provided "as is", without warranty of any kind, express or %
%  implied, including but not limited to the warranties of merchantability,   %
%  fitness for a particular purpose and noninfringement.  In no event shall   %
%  E. I. du Pont de Nemours and Company be liable for any claim, damages or   %
%  other liability, whether in an action of contract, tort or otherwise,      %
%  arising from, out of or in connection with ImageMagick or the use or other %
%  dealings in ImageMagick.                                                   %
%                                                                             %
%  Except as contained in this notice, the name of the E. I. du Pont de       %
%  Nemours and Company shall not be used in advertising or otherwise to       %
%  promote the sale, use or other dealings in ImageMagick without prior       %
%  written authorization from the E. I. du Pont de Nemours and Company.       %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%
*/

/*
  Include declarations.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "magick.h"
#include "defines.h"

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   D e c o d e I m a g e                                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method DecodeImage unpacks the packed image pixels into runlength-encoded
%  pixel packets.
%
%  The format of the DecodeImage method is:
%
%      unsigned int DecodeImage(Image *image,const unsigned int compression,
%        const unsigned int number_columns,const unsigned int number_rows,
%        unsigned char *pixels)
%
%  A description of each parameter follows:
%
%    o status:  Method DecodeImage returns True if all the pixels are
%      uncompressed without error, otherwise False.
%
%    o image: The address of a structure of type Image.
%
%    o compression:  A value of 1 means the compressed pixels are runlength
%      encoded for a 256-color bitmap.  A value of 2 means a 16-color bitmap.
%
%    o number_columns:  An integer value that is the number of columns or
%      width in pixels of your source image.
%
%    o number_rows:  An integer value that is the number of rows or
%      heigth in pixels of your source image.
%
%    o pixels:  The address of a byte (8 bits) array of pixel data created by
%      the decoding process.
%
%
*/
static unsigned int DecodeImage(Image *image,const unsigned int compression,
  const unsigned int number_columns,const unsigned int number_rows,
  unsigned char *pixels)
{
  int
    byte,
    count;

  register int
    i,
    x,
    y;

  register unsigned char
    *q;

  assert(image != (Image *) NULL);
  assert(pixels != (unsigned char *) NULL);
  for (i=0; i < (int) (number_columns*number_rows); i++)
    pixels[i]=0;
  byte=0;
  x=0;
  q=pixels;
  for (y=0; y < (int) number_rows; )
  {
    count=ReadByte(image);
    if (count == EOF)
      return(False);
    if (count != 0)
      {
        /*
          Encoded mode.
        */
        byte=ReadByte(image);
        for (i=0; i < count; i++)
        {
          if (compression == 1)
            *q++=(unsigned char) byte;
          else
            *q++=(i & 0x01) ? (byte & 0x0f) : ((byte >> 4) & 0x0f);
          x++;
        }
      }
    else
      {
        /*
          Escape mode.
        */
        count=ReadByte(image);
        if (count == 0x01)
          return(True);
        switch (count)
        {
          case 0x00:
          {
            /*
              End of line.
            */
            x=0;
            y++;
            q=pixels+y*number_columns;
            break;
          }
          case 0x02:
          {
            /*
              Delta mode.
            */
            x+=ReadByte(image);
            y+=ReadByte(image);
            q=pixels+y*number_columns+x;
            break;
          }
          default:
          {
            /*
              Absolute mode.
            */
            for (i=0; i < count; i++)
            {
              if (compression == 1)
                *q++=ReadByte(image);
              else
                {
                  if ((i & 0x01) == 0)
                    byte=ReadByte(image);
                  *q++=(i & 0x01) ? (byte & 0x0f) : ((byte >> 4) & 0x0f);
                }
              x++;
            }
            /*
              Read pad byte.
            */
            if (compression == 1)
              {
                if (count & 0x01)
                  (void) ReadByte(image);
              }
            else
              if (((count & 0x03) == 1) || ((count & 0x03) == 2))
                (void) ReadByte(image);
            break;
          }
        }
      }
  }
  return(True);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   E n c o d e I m a g e                                                     %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method EncodeImage compresses pixels using a runlength encoded format.
%
%  The format of the EncodeImage method is:
%
%      status=EncodeImage(pixels,number_columns,number_rows,
%        compressed_pixels)
%
%  A description of each parameter follows:
%
%    o status:  Method EncodeImage returns the number of bytes in the
%      runlength encoded compress_pixels array.
%
%    o pixels:  The address of a byte (8 bits) array of pixel data created by
%      the compression process.
%
%    o number_columns:  An integer value that is the number of columns or
%      width in pixels of your source image.
%
%    o number_rows:  An integer value that is the number of rows or
%      heigth in pixels of your source image.
%
%    o compressed_pixels:  The address of a byte (8 bits) array of compressed
%      pixel data.
%
%
*/
static unsigned int EncodeImage(const unsigned char *pixels,
  const unsigned int number_columns,const unsigned int number_rows,
  unsigned char *compressed_pixels)
{
  register const unsigned char
    *p;

  register int
    i,
    x,
    y;

  register unsigned char
    *q;

  unsigned int
    bytes_per_line;

  /*
    Runlength encode pixels.
  */
  assert(pixels != (unsigned char *) NULL);
  assert(compressed_pixels != (unsigned char *) NULL);
  p=pixels;
  q=compressed_pixels;
  i=0;
  bytes_per_line=number_columns+(number_columns % 2 ? 1 : 0);
  for (y=0; y < (int) number_rows; y++)
  {
    for (x=0; x < (int) bytes_per_line; x+=i)
    {
      /*
        Determine runlength.
      */
      for (i=1; ((x+i) < (int) bytes_per_line); i++)
        if ((*(p+i) != *p) || (i == 255))
          break;
      *q++=(unsigned char) i;
      *q++=(*p);
      p+=i;
    }
    /*
      End of line.
    */
    *q++=0;
    *q++=0x00;
  }
  /*
    End of bitmap.
  */
  *q++=0;
  *q++=0x01;
  return((unsigned int) (q-compressed_pixels));
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   R e a d B M P I m a g e                                                   %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method ReadBMPImage reads a Microsoft Windows bitmap image file and
%  returns it.  It allocates the memory necessary for the new Image structure
%  and returns a pointer to the new image.
%
%  The format of the ReadBMPImage method is:
%
%      image=ReadBMPImage(image_info)
%
%  A description of each parameter follows:
%
%    o image:  Method ReadBMPImage returns a pointer to the image after
%      reading.  A null image is returned if there is a memory shortage or
%      if the image cannot be read.
%
%    o image_info: Specifies a pointer to an ImageInfo structure.
%
%
*/
Export Image *ReadBMPImage(const ImageInfo *image_info)
{
  typedef struct _BMPHeader
  {
    unsigned long
      file_size;

    unsigned short
      reserved[2];

    unsigned long
      offset_bits,
      size;

    long
      width,
      height;

    unsigned short
      planes,
      bits_per_pixel;

    unsigned long
      compression,
      image_size,
      x_pixels,
      y_pixels,
      number_colors,
      colors_important;

    unsigned short
      red_mask,
      green_mask,
      blue_mask,
      alpha_mask;

    long
      colorspace;

    PointInfo
      red_primary,
      green_primary,
      blue_primary,
      gamma_scale;
  } BMPHeader;

  BMPHeader
    bmp_header;

  Image
    *image;

  long
    start_position;

  register int
    bit,
    i,
    x,
    y;

  register RunlengthPacket
    *q;

  register unsigned char
    *p;

  unsigned char
    *bmp_pixels,
    magick[12];

  unsigned int
    bytes_per_line,
    image_size,
    status;

  /*
    Allocate image structure.
  */
  image=AllocateImage(image_info);
  if (image == (Image *) NULL)
    return((Image *) NULL);
  /*
    Open image file.
  */
  status=OpenBlob(image_info,image,ReadBinaryType);
  if (status == False)
    fprintf(stderr,"FileOpenWarning: Unable to open file\n");
  /*
    Determine if this is a BMP file.
  */
  status=ReadBlob(image,2,(char *) magick);
  do
  {
    /*
      Verify BMP identifier.
    */
    start_position=TellBlob(image)-2;
    if ((status == False) || (strncmp((char *) magick,"BM",2) != 0))
      ReaderExit(CorruptImageWarning,"Not a BMP image file",image);
    bmp_header.file_size=LSBFirstReadLong(image);
    bmp_header.reserved[0]=LSBFirstReadShort(image);
    bmp_header.reserved[1]=LSBFirstReadShort(image);
    bmp_header.offset_bits=LSBFirstReadLong(image);
    bmp_header.size=LSBFirstReadLong(image);
    if (image->filesize)
      if ((int) (bmp_header.file_size-bmp_header.size) > image->filesize)
        ReaderExit(CorruptImageWarning,"Not a BMP image file",image);
    if (bmp_header.size == 12)
      {
        /*
          OS/2 BMP image file.
        */
        bmp_header.width=LSBFirstReadShort(image);
        bmp_header.height=LSBFirstReadShort(image);
        bmp_header.planes=LSBFirstReadShort(image);
        bmp_header.bits_per_pixel=LSBFirstReadShort(image);
        bmp_header.x_pixels=0;
        bmp_header.y_pixels=0;
        bmp_header.number_colors=0;
        bmp_header.compression=0;
        bmp_header.image_size=0;
      }
    else
      {
        /*
          Microsoft Windows BMP image file.
        */
        bmp_header.width=LSBFirstReadLong(image);
        bmp_header.height=LSBFirstReadLong(image);
        bmp_header.planes=LSBFirstReadShort(image);
        bmp_header.bits_per_pixel=LSBFirstReadShort(image);
        bmp_header.compression=LSBFirstReadLong(image);
        bmp_header.image_size=LSBFirstReadLong(image);
        bmp_header.x_pixels=LSBFirstReadLong(image);
        bmp_header.y_pixels=LSBFirstReadLong(image);
        bmp_header.number_colors=LSBFirstReadLong(image);
        bmp_header.colors_important=LSBFirstReadLong(image);
        for (i=0; i < ((int) bmp_header.size-40); i++)
          (void) ReadByte(image);
        if ((bmp_header.compression == 3) &&
            ((bmp_header.bits_per_pixel == 16) ||
             (bmp_header.bits_per_pixel == 32)))
          {
            bmp_header.red_mask=LSBFirstReadShort(image);
            bmp_header.green_mask=LSBFirstReadShort(image);
            bmp_header.blue_mask=LSBFirstReadShort(image);
            if (bmp_header.size > 40)
              {
                /*
                  Read color management information.
                */
                bmp_header.alpha_mask=LSBFirstReadShort(image);
                bmp_header.colorspace=LSBFirstReadLong(image);
                bmp_header.red_primary.x=LSBFirstReadLong(image);
                bmp_header.red_primary.y=LSBFirstReadLong(image);
                bmp_header.red_primary.z=LSBFirstReadLong(image);
                bmp_header.green_primary.x=LSBFirstReadLong(image);
                bmp_header.green_primary.y=LSBFirstReadLong(image);
                bmp_header.green_primary.z=LSBFirstReadLong(image);
                bmp_header.blue_primary.x=LSBFirstReadLong(image);
                bmp_header.blue_primary.y=LSBFirstReadLong(image);
                bmp_header.blue_primary.z=LSBFirstReadLong(image);
                bmp_header.gamma_scale.x=LSBFirstReadShort(image);
                bmp_header.gamma_scale.y=LSBFirstReadShort(image);
                bmp_header.gamma_scale.z=LSBFirstReadShort(image);
              }
          }
      }
    image->matte=bmp_header.bits_per_pixel == 32;
    image->columns=(unsigned int) bmp_header.width;
    image->rows=(unsigned int) AbsoluteValue(bmp_header.height);
    if ((bmp_header.number_colors != 0) || (bmp_header.bits_per_pixel < 16))
      {
        image->class=PseudoClass;
        image->colors=(unsigned int) bmp_header.number_colors;
        if (image->colors == 0)
          image->colors=1 << bmp_header.bits_per_pixel;
      }
    if (image_info->ping)
      {
        CloseBlob(image);
        return(image);
      }
    if (image->class == PseudoClass)
      {
        /*
          Allocate image colormap.
        */
        image->colormap=(ColorPacket *)
          AllocateMemory(image->colors*sizeof(ColorPacket));
        if (image->colormap == (ColorPacket *) NULL)
          ReaderExit(ResourceLimitWarning,"Memory allocation failed",image);
        for (i=0; i < (int) image->colors; i++)
        {
          image->colormap[i].red=(Quantum)
            ((long) (MaxRGB*i)/(image->colors-1));
          image->colormap[i].green=(Quantum)
            ((long) (MaxRGB*i)/(image->colors-1));
          image->colormap[i].blue=(Quantum)
            ((long) (MaxRGB*i)/(image->colors-1));
        }
        if (bmp_header.bits_per_pixel < 16)
          {
            unsigned char
              *bmp_colormap;

            unsigned int
              packet_size;

            /*
              Read BMP raster colormap.
            */
            bmp_colormap=(unsigned char *)
              AllocateMemory(4*image->colors*sizeof(unsigned char));
            if (bmp_colormap == (unsigned char *) NULL)
              ReaderExit(ResourceLimitWarning,"Memory allocation failed",
                image);
            packet_size=4;
            if (bmp_header.size == 12)
              packet_size=3;
            (void) ReadBlob(image,packet_size*image->colors,
              (char *) bmp_colormap);
            p=bmp_colormap;
            for (i=0; i < (int) image->colors; i++)
            {
              image->colormap[i].blue=UpScale(*p++);
              image->colormap[i].green=UpScale(*p++);
              image->colormap[i].red=UpScale(*p++);
              if (bmp_header.size != 12)
                p++;
            }
            FreeMemory((char *) bmp_colormap);
          }
      }
    while (TellBlob(image) < (int) (start_position+bmp_header.offset_bits))
      (void) ReadByte(image);
    /*
      Read image data.
    */
    if (bmp_header.compression == 2)
      bmp_header.bits_per_pixel<<=1;
    bytes_per_line=((image->columns*bmp_header.bits_per_pixel+31)/32)*4;
    image_size=bytes_per_line*image->rows;
    bmp_pixels=(unsigned char *)
      AllocateMemory(image_size*sizeof(unsigned char));
    if (bmp_pixels == (unsigned char *) NULL)
      ReaderExit(ResourceLimitWarning,"Memory allocation failed",image);
    if ((bmp_header.compression == 0) || (bmp_header.compression == 3))
      (void) ReadBlob(image,image_size,(char *) bmp_pixels);
    else
      {
        /*
          Convert run-length encoded raster pixels.
        */
        status=DecodeImage(image,(unsigned int) bmp_header.compression,
          (unsigned int) bmp_header.width,image->rows,bmp_pixels);
        if (status == False)
          ReaderExit(CorruptImageWarning,"not enough image pixels",image);
      }
    /*
      Initialize image structure.
    */
    image->units=PixelsPerCentimeterResolution;
    image->x_resolution=bmp_header.x_pixels/100.0;
    image->y_resolution=bmp_header.y_pixels/100.0;
    image->packets=image->columns*image->rows;
    image->pixels=(RunlengthPacket *)
      AllocateMemory(image->packets*sizeof(RunlengthPacket));
    if (image->pixels == (RunlengthPacket *) NULL)
      ReaderExit(ResourceLimitWarning,"Memory allocation failed",image);
    SetImage(image);
    /*
      Convert BMP raster image to runlength-encoded packets.
    */
    switch (bmp_header.bits_per_pixel)
    {
      case 1:
      {
        /*
          Convert bitmap scanline to runlength-encoded color packets.
        */
        for (y=image->rows-1; y >= 0; y--)
        {
          p=bmp_pixels+(image->rows-y-1)*bytes_per_line;
          q=image->pixels+(y*image->columns);
          for (x=0; x < ((int) image->columns-7); x+=8)
          {
            for (bit=0; bit < 8; bit++)
            {
              q->index=((*p) & (0x80 >> bit) ? 0x01 : 0x00);
              q->length=0;
              q++;
            }
            p++;
          }
          if ((image->columns % 8) != 0)
            {
              for (bit=0; bit < (int) (image->columns % 8); bit++)
              {
                q->index=((*p) & (0x80 >> bit) ? 0x01 : 0x00);
                q->length=0;
                q++;
              }
              p++;
            }
        }
        break;
      }
      case 4:
      {
        /*
          Convert PseudoColor scanline to runlength-encoded color packets.
        */
        for (y=image->rows-1; y >= 0; y--)
        {
          p=bmp_pixels+(image->rows-y-1)*bytes_per_line;
          q=image->pixels+(y*image->columns);
          for (x=0; x < ((int) image->columns-1); x+=2)
          {
            q->index=(*p >> 4) & 0xf;
            q->length=0;
            q++;
            q->index=(*p) & 0xf;
            q->length=0;
            p++;
            q++;
          }
          if ((image->columns % 2) != 0)
            {
              q->index=(*p >> 4) & 0xf;
              q->length=0;
              q++;
              p++;
            }
        }
        break;
      }
      case 8:
      {
        /*
          Convert PseudoColor scanline to runlength-encoded color packets.
        */
        if ((bmp_header.compression == 1) || (bmp_header.compression == 2))
          bytes_per_line=image->columns;
        for (y=image->rows-1; y >= 0; y--)
        {
          p=bmp_pixels+(image->rows-y-1)*bytes_per_line;
          q=image->pixels+(y*image->columns);
          for (x=0; x < (int) image->columns; x++)
          {
            q->index=(*p++);
            q->length=0;
            q++;
          }
        }
        break;
      }
      case 16:
      {
        unsigned char
          h,
          l;

        /*
          Convert PseudoColor scanline to runlength-encoded color packets.
        */
        if (bmp_header.compression == 1)
          bytes_per_line=image->columns << 1;
        for (y=image->rows-1; y >= 0; y--)
        {
          p=bmp_pixels+(image->rows-y-1)*bytes_per_line;
          q=image->pixels+(y*image->columns);
          for (x=0; x < (int) image->columns; x++)
          {
            h=(*p++);
            l=(*p++);
            q->red=(Quantum) ((MaxRGB*((int) (l & 0x7c) >> 2))/31);
            q->green=(Quantum)
              ((MaxRGB*(((int) (l & 0x03) << 3)+((int) (h & 0xe0) >> 5)))/31);
            q->blue=(Quantum) ((MaxRGB*((int) (h & 0x1f)))/31);
            q->index=0;
            q->length=0;
            q++;
          }
        }
        break;
      }
      case 24:
      case 32:
      {
        /*
          Convert DirectColor scanline to runlength-encoded color packets.
        */
        for (y=image->rows-1; y >= 0; y--)
        {
          p=bmp_pixels+(image->rows-y-1)*bytes_per_line;
          q=image->pixels+(y*image->columns);
          for (x=0; x < (int) image->columns; x++)
          {
            q->blue=UpScale(*p++);
            q->green=UpScale(*p++);
            q->red=UpScale(*p++);
            q->index=0;
            if (image->matte)
              q->index=Opaque-UpScale(*p++);
            q->length=0;
            q++;
          }
        }
        break;
      }
      default:
        ReaderExit(CorruptImageWarning,"Not a BMP image file",image);
    }
    FreeMemory((char *) bmp_pixels);
    if (image->class == PseudoClass)
      SyncImage(image);
    CondenseImage(image);
    if (bmp_header.height < 0)
      {
        Image
          *flipped_image;

        /*
          Correct image orientation.
        */
        flipped_image=FlipImage(image);
        if (flipped_image != (Image *) NULL)
          {
            DestroyImage(image);
            image=flipped_image;
          }
      }
    /*
      Proceed to next image.
    */
    if (image_info->subrange != 0)
      if (image->scene >= (image_info->subimage+image_info->subrange-1))
        break;
    status=ReadBlob(image,2,(char *) magick);
    if ((status == True) && (strncmp((char *) magick,"BM",2) == 0))
      {
        /*
          Allocate next image structure.
        */
        AllocateNextImage(image_info,image);
        if (image->next == (Image *) NULL)
          {
            DestroyImages(image);
            return((Image *) NULL);
          }
        image=image->next;
      }
  } while ((status == True) && (strncmp((char *) magick,"BM",2) == 0));
  while (image->previous != (Image *) NULL)
    image=image->previous;
  CloseBlob(image);
  return(image);
}

/*
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%                                                                             %
%                                                                             %
%                                                                             %
%   W r i t e B M P I m a g e                                                 %
%                                                                             %
%                                                                             %
%                                                                             %
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
%
%  Method WriteBMPImage writes an image in Microsoft Windows bitmap encoded
%  image format.
%
%  The format of the WriteBMPImage method is:
%
%      unsigned int WriteBMPImage(const ImageInfo *image_info,Image *image)
%
%  A description of each parameter follows.
%
%    o status: Method WriteBMPImage return True if the image is written.
%      False is returned is there is a memory shortage or if the image file
%      fails to write.
%
%    o image_info: Specifies a pointer to an ImageInfo structure.
%
%    o image:  A pointer to a Image structure.
%
%
*/
Export unsigned int WriteBMPImage(const ImageInfo *image_info,Image *image)
{
  typedef struct _BMPHeader
  {
    unsigned long
      file_size;

    unsigned short
      reserved[2];

    unsigned long
      offset_bits,
      size,
      width,
      height;

    unsigned short
      planes,
      bit_count;

    unsigned long
      compression,
      image_size,
      x_pixels,
      y_pixels,
      number_colors,
      colors_important;
  } BMPHeader;

  BMPHeader
    bmp_header;

  register int
    i,
    j,
    x,
    y;

  register RunlengthPacket
    *p;

  register unsigned char
    *q;

  unsigned char
    *bmp_data,
    *bmp_pixels;

  unsigned int
    bytes_per_line,
    scene,
    status;

  /*
    Open output image file.
  */
  status=OpenBlob(image_info,image,WriteBinaryType);
  if (status == False)
    WriterExit(FileOpenWarning,"Unable to open file",image);
  scene=0;
  do
  {
    /*
      Initialize BMP raster file header.
    */
    TransformRGBImage(image,RGBColorspace);
    bmp_header.file_size=14+40;
    bmp_header.offset_bits=14+40;
    if ((strcmp(image_info->magick,"BMP24") == 0) ||
        (!IsPseudoClass(image) && !IsGrayImage(image)))
      {
        /*
          Full color BMP raster.
        */
        image->class=DirectClass;
        bmp_header.number_colors=0;
        bmp_header.bit_count=image->matte ? 32 : 24;
        bytes_per_line=4*((image->columns*bmp_header.bit_count+31)/32);
      }
    else
      {
        /*
          Colormapped BMP raster.
        */
        bmp_header.bit_count=8;
        bytes_per_line=image->columns+(image->columns % 2 ? 1 : 0);
        if (IsMonochromeImage(image))
          {
            bmp_header.bit_count=1;
            bytes_per_line=4*((image->columns*bmp_header.bit_count+31)/32);
          }
        bmp_header.file_size+=4*(1 << bmp_header.bit_count);
        bmp_header.offset_bits+=4*(1 << bmp_header.bit_count);
        bmp_header.number_colors=1 << bmp_header.bit_count;
      }
    bmp_header.reserved[0]=0;
    bmp_header.reserved[1]=0;
    bmp_header.size=40;
    bmp_header.width=image->columns;
    bmp_header.height=image->rows;
    bmp_header.planes=1;
    bmp_header.compression=0;
    bmp_header.image_size=bytes_per_line*image->rows;
    bmp_header.file_size+=bmp_header.image_size;
    bmp_header.x_pixels=75*39;
    bmp_header.y_pixels=75*39;
    if (image->units == PixelsPerInchResolution)
      {
        bmp_header.x_pixels=(unsigned long) (100.0*image->x_resolution/2.54);
        bmp_header.y_pixels=(unsigned long) (100.0*image->y_resolution/2.54);
      }
    if (image->units == PixelsPerCentimeterResolution)
      {
        bmp_header.x_pixels=(unsigned long) (100.0*image->x_resolution);
        bmp_header.y_pixels=(unsigned long) (100.0*image->y_resolution);
      }
    bmp_header.colors_important=bmp_header.number_colors;
    /*
      Convert MIFF to BMP raster pixels.
    */
    bmp_pixels=(unsigned char *)
      AllocateMemory(bmp_header.image_size*sizeof(unsigned char));
    if (bmp_pixels == (unsigned char *) NULL)
      WriterExit(ResourceLimitWarning,"Memory allocation failed",image);
    x=0;
    y=image->rows-1;
    switch (bmp_header.bit_count)
    {
      case 1:
      {
        register unsigned char
          bit,
          byte,
          polarity;

        /*
          Convert PseudoClass image to a BMP monochrome image.
        */
        p=image->pixels;
        polarity=Intensity(image->colormap[0]) < (MaxRGB >> 1);
        if (image->colors == 2)
          polarity=
            Intensity(image->colormap[1]) > Intensity(image->colormap[0]);
        bit=0;
        byte=0;
        q=bmp_pixels+y*bytes_per_line;
        for (i=0; i < (int) image->packets; i++)
        {
          for (j=0; j <= ((int) p->length); j++)
          {
            byte<<=1;
            if (p->index == polarity)
              byte|=0x01;
            bit++;
            if (bit == 8)
              {
                *q++=byte;
                bit=0;
                byte=0;
              }
            x++;
            if (x == (int) image->columns)
              {
                /*
                  Advance to the next scanline.
                */
                if (bit != 0)
                  *q++=byte << (8-bit);
                bit=0;
                byte=0;
                x=0;
                y--;
                q=bmp_pixels+y*bytes_per_line;
             }
          }
          p++;
        }
        break;
      }
      case 8:
      {
        /*
          Convert PseudoClass packet to BMP pixel.
        */
        p=image->pixels;
        q=bmp_pixels+y*bytes_per_line;
        for (i=0; i < (int) image->packets; i++)
        {
          for (j=0; j <= ((int) p->length); j++)
          {
            *q++=p->index;
            x++;
            if (x == (int) image->columns)
              {
                x=0;
                y--;
                q=bmp_pixels+y*bytes_per_line;
              }
          }
          p++;
        }
        break;
      }
      case 24:
      case 32:
      {
        /*
          Convert DirectClass packet to BMP RGB pixel.
        */
        p=image->pixels;
        q=bmp_pixels+y*bytes_per_line;
        for (i=0; i < (int) image->packets; i++)
        {
          for (j=0; j <= ((int) p->length); j++)
          {
            *q++=DownScale(p->blue);
            *q++=DownScale(p->green);
            *q++=DownScale(p->red);
            if (image->matte)
              *q++=Opaque-DownScale(p->index);
            x++;
            if (x == (int) image->columns)
              {
                x=0;
                y--;
                q=bmp_pixels+y*bytes_per_line;
              }
          }
          p++;
        }
        break;
      }
    }
    if (bmp_header.bit_count == 8)
      if (image_info->compression != NoCompression)
        {
          unsigned int
            packets;

          /*
            Convert run-length encoded raster pixels.
          */
          packets=(unsigned int)
            ((bytes_per_line*(bmp_header.height+2)+1) << 1);
          bmp_data=(unsigned char *)
            AllocateMemory(packets*sizeof(unsigned char));
          if (bmp_pixels == (unsigned char *) NULL)
            {
              FreeMemory((char *) bmp_pixels);
              WriterExit(ResourceLimitWarning,"Memory allocation failed",
                image);
            }
          bmp_header.file_size-=bmp_header.image_size;
          bmp_header.image_size=
            EncodeImage(bmp_pixels,image->columns,image->rows,bmp_data);
          bmp_header.file_size+=bmp_header.image_size;
          FreeMemory((char *) bmp_pixels);
          bmp_pixels=bmp_data;
          bmp_header.compression=1;
        }
    /*
      Write BMP header.
    */
    (void) WriteBlob(image,2,"BM");
    (void) LSBFirstWriteLong(image,bmp_header.file_size);
    (void) LSBFirstWriteShort(image,bmp_header.reserved[0]);
    (void) LSBFirstWriteShort(image,bmp_header.reserved[1]);
    (void) LSBFirstWriteLong(image,bmp_header.offset_bits);
    (void) LSBFirstWriteLong(image,bmp_header.size);
    (void) LSBFirstWriteLong(image,bmp_header.width);
    (void) LSBFirstWriteLong(image,bmp_header.height);
    (void) LSBFirstWriteShort(image,bmp_header.planes);
    (void) LSBFirstWriteShort(image,bmp_header.bit_count);
    (void) LSBFirstWriteLong(image,bmp_header.compression);
    (void) LSBFirstWriteLong(image,bmp_header.image_size);
    (void) LSBFirstWriteLong(image,bmp_header.x_pixels);
    (void) LSBFirstWriteLong(image,bmp_header.y_pixels);
    (void) LSBFirstWriteLong(image,bmp_header.number_colors);
    (void) LSBFirstWriteLong(image,bmp_header.colors_important);
    if (image->class == PseudoClass)
      {
        unsigned char
          *bmp_colormap;

        /*
          Dump colormap to file.
        */
        bmp_colormap=(unsigned char *)
          AllocateMemory(4*(1 << bmp_header.bit_count)*sizeof(unsigned char));
        if (bmp_colormap == (unsigned char *) NULL)
          WriterExit(ResourceLimitWarning,"Memory allocation failed",image);
        q=bmp_colormap;
        for (i=0; i < (int) image->colors; i++)
        {
          *q++=DownScale(image->colormap[i].blue);
          *q++=DownScale(image->colormap[i].green);
          *q++=DownScale(image->colormap[i].red);
          *q++=(Quantum) 0x0;
        }
        for ( ; i < (int) (1 << bmp_header.bit_count); i++)
        {
          *q++=(Quantum) 0x0;
          *q++=(Quantum) 0x0;
          *q++=(Quantum) 0x0;
          *q++=(Quantum) 0x0;
        }
        (void) WriteBlob(image,4*(1 << bmp_header.bit_count),
          (char *) bmp_colormap);
        FreeMemory((char *) bmp_colormap);
      }
    (void) WriteBlob(image,bmp_header.image_size,(char *) bmp_pixels);
    FreeMemory((char *) bmp_pixels);
    if (image->next == (Image *) NULL)
      break;
    image->next->file=image->file;
    image=image->next;
  } while (image_info->adjoin);
  if (image_info->adjoin)
    while (image->previous != (Image *) NULL)
      image=image->previous;
  CloseBlob(image);
  return(True);
}
