//
// Copyright (c) 1997,1998 Colosseum Builders, Inc.
// All rights reserved.
//
// Colosseum Builders, Inc. makes no warranty, expressed or implied
// with regards to this software. It is provided as is.
//
// See the README.TXT file that came with this software for restrictions
// on the use and redistribution of this file or send E-mail to
// info@colosseumbuilders.com
//

//
// BMP Decoder Library.
//
// Title:   BmpDecoder Class Implementation
//
// Author: John M. Miano  miano@colosseumbuilders.com
//
//

#include <windows.h>

#include "bmpdecod.h"

//
//  Description:
//
//    Class default constructor
//

BmpDecoder::BmpDecoder ()
{
  return ;
}

//
//  Description:
//
//    Class Copy Constructor
//
BmpDecoder::BmpDecoder (const BmpDecoder &source)
{
  Initialize () ;
  DoCopy (source) ;
  return ;
}

//
//  Description:
//
//    Class Destructor
//
BmpDecoder::~BmpDecoder ()
{
  return ;
}

//
//  Description:
//
//    Assignment operator.
//
//  Parameters:
//   source: The object to copy
//
BmpDecoder &BmpDecoder::operator=(const BmpDecoder &source)
{
  DoCopy (source) ;
  return *this ;
}

//
//  Description:
//
//    Common class initialization function for use by constructors.
//
void BmpDecoder::Initialize ()
{
  return ;
}

//
//  Description:
//
//    Common class copy function.
//
//  Parameters:
//    source:  The object to copy.
//
void BmpDecoder::DoCopy (const BmpDecoder &source)
{
  BitmapImageDecoder::DoCopy (source) ;
  return ;
}

//
//  Description:
//
//    This function reads an image from a Windows BMP stream.
//
//  Parameters:
//    strm: The input stream
//    image: The image to be read
//    
void BmpDecoder::ReadImage (std::istream &strm, BitmapImage &image)
{
  bool os2format ;

  // We need this because MSVC++ does not follow standard scoping
  // rules in for statements.
  unsigned int ii ;

  unsigned int bytesread = 0 ;

  BITMAPFILEHEADER fileheader ;
  strm.read ((char *) &fileheader, sizeof (fileheader)) ;
  if (strm.gcount () != sizeof(fileheader))
    throw EBmpFileReadError () ;
  bytesread += sizeof (fileheader) ;

  const UBYTE2 signature = 'B' | ('M' << 8) ;
  if (fileheader.bfType != signature)
    throw EBmpNotABmpFile () ;

  // The header can come in one of two flavors.  They both
  // begin with a 4-byte headersize.

  UBYTE4 headersize ;
  strm.read ((char *) &headersize, sizeof (headersize)) ;

  unsigned long width ;
  long height ;
  unsigned int bitcount ;
  unsigned int compression ;
  unsigned int colorcount = 0 ;
  if (headersize == sizeof (BITMAPCOREHEADER))
  {
    // OS/2 Format Header

    BITMAPCOREHEADER header ;
    header.bcSize = headersize ;
    strm.read ((char *) &header.bcWidth,
          sizeof (header) - sizeof (headersize)) ;
    bytesread += sizeof (header) ;

    width = header.bcWidth ;
    height = header.bcHeight ;
    bitcount = header.bcBitCount ;

    compression = BI_RGB ;

    os2format = true ;
  }
  else if (headersize >= sizeof (BITMAPINFOHEADER))
  {
    BITMAPINFOHEADER header ;
    header.biSize = headersize ;
    strm.read ((char *) &header.biWidth,
           sizeof (header) - sizeof (headersize)) ;
    bytesread += sizeof (header) ;
    compression = header.biCompression ;

    width = header.biWidth ;
    height = header.biHeight ;
    bitcount = header.biBitCount ;

    for (unsigned int ii = 0 ;
         ii < headersize - sizeof (BITMAPINFOHEADER) ;
         ++ ii)
    {
      ++ bytesread ;
      UBYTE1 data ;
      strm.read ((char *) &data, 1) ;
    }
    os2format = false ;
    colorcount = header.biClrUsed ;
  }
  else
  {
    throw EBmpCorruptFile () ;
  }

  if (colorcount == 0 && bitcount != 24)
    colorcount = 1 << bitcount ;

  // Validate the compression mode.
  switch (bitcount)
  {
  case 1:
    if (compression != BI_RGB)
      throw EBmpNotSupported () ;
    break ;
  case 4:
    if (compression != BI_RGB && compression != BI_RLE4)
      throw EBmpNotSupported () ;
    break ;
  case 8:
    if (compression != BI_RGB &&  compression != BI_RLE8)
      throw EBmpNotSupported () ;
    break ;
  case 24:
    if (compression != BI_RGB)
      throw EBmpNotSupported () ;
    break ;
  default:
    throw EBmpNotSupported () ;
  }

  // Allocate storage for the image.
  image.SetSize (colorcount, bitcount, width, height) ;

  // Read the color map.
  if (os2format)
  {
    for (ii = 0 ; ii < colorcount ; ++ ii)
    {
      RGBTRIPLE color ;
      strm.read ((char *) &color, sizeof (color)) ;
      image.ColorMap (ii).red = color.rgbtRed ;
      image.ColorMap (ii).blue = color.rgbtBlue ;
      image.ColorMap (ii).green = color.rgbtGreen ;

      bytesread += sizeof (color) ;
    }
  }
  else
  {
    for (ii = 0 ; ii < colorcount ; ++ ii)
    {
      RGBQUAD color ;
      strm.read ((char *) &color, sizeof (color)) ;
      image.ColorMap (ii).red = color.rgbRed ;
      image.ColorMap (ii).blue = color.rgbBlue ;
      image.ColorMap (ii).green = color.rgbGreen ;

      bytesread += sizeof (color) ;
    }
  }

  // It is poss ible to have a file where the image data does not
  // immediately follow the color map (or headers). If there is
  // padding we skip over it.
  if (bytesread > fileheader.bfOffBits)
    throw EBmpCorruptFile () ;
  for (ii = bytesread ; ii < fileheader.bfOffBits ; ++ ii)
  {
    UBYTE1 data ;
    strm.read ((char *) &data, 1) ;
  }

  // Read the image data.
  CallProgressFunction (0) ;
  if (bitcount != 24)
  {
    // In this block we handle images that use a color map.

    if (compression == BI_RGB)
    {
      // Simplest case -- No compression. We can just read the
      // raw data from the file.

      // Number of bits required for each pixel row.
      unsigned int bitwidth = bitcount * width ;
      // Number of bytes need to store each pixel row.
      unsigned int rowwidth = (bitwidth + 7)/8 ;
      // Number of bytes used to store each row in the BMP file.
      // This is is rowwidth rounded up to the nearest 4 bytes.
      unsigned int physicalrowsize = (rowwidth + 0x3) & ~0x3 ;
      // The number of pad bytes for each row in the BMP file.
      unsigned int padsize = physicalrowsize -  rowwidth ;

      // Images with positive heights are stored bottom up. Images with
      // negative height are stored top down.
      if (height > 0)
      {
        for (unsigned int ii = 0 ; ii < height ; ++ ii)
        {
          CallProgressFunction (ii * 100 / height) ;
        
          // The pixel rows are stored in reverse order.
          unsigned int index = (height - ii - 1) ;
          strm.read ((char *)&image [index][0], rowwidth) ;
          if (strm.gcount () != rowwidth)
            throw EBmpFileReadError () ;

          // Skip over the pad bytes.
          static char pad [4] ;
          strm.read (pad, padsize) ;
        }
      }
      else
      {
        for (unsigned int ii = 0 ; ii < - height ; ++ ii)
        {
          CallProgressFunction (ii * 100 / - height) ;
        
          strm.read ((char *)&image [ii][0], rowwidth) ;
          if (strm.gcount () != rowwidth)
            throw EBmpFileReadError () ;

          // Skip over the pad bytes.
          static char pad [4] ;
          strm.read (pad, padsize) ;
        }
      }

    } //
    else if (compression == BI_RLE8)
    {
      // Handle the case of 8-bit Run-Length Encoding.

      unsigned int row = height - 1 ;  // Current row
      unsigned int col = 0 ;           // Current column
      bool done = false ;
      while (! strm.eof () && ! done)
      {
        CallProgressFunction ((height - row - 1) * 100 / height) ;

        // Structure for reading RLE commands.
        struct
        {
          UBYTE1 count ;
          UBYTE1 command ;
        } opcode ;

        strm.read ((char *) &opcode, sizeof (opcode)) ;
        if (opcode.count == 0)
        {
          // A byte count of zero means that this is a special
          // instruction.
          switch (opcode.command)
          {
          case 0: // 0 => Move to next row
            -- row ;
            col = 0 ;
            break ;
          case 1: // 1 => Image is finished
            done = true ;
            break ;
          case 2: // 2 => Move to a new relative position.
            {
              // Read the relative position.
              UBYTE1 dx ;
              UBYTE1 dy ;
              strm.read ((char *) &dx, 1) ;
              strm.read ((char *) &dy, 1) ;
              col += dx ;
              row -= dy ;
            }
            break ;
          default:
            {
              // Store absolute data. The command code is the
              // number of absolute bytes to store.
              if (row >= height || col + opcode.command > width)
                  throw EBmpCorruptFile () ;
              UBYTE1 data ;
              for (unsigned int ii = 0 ; ii < opcode.command ; ++ ii)
              {
                strm.read ((char *) &data, 1) ;
                image [row][col] = data ;
                ++ col ;
              }
              // An odd number of bytes is followed by a pad byte.
              if ((opcode.command & 1) != 0)
                strm.read ((char *) &data, 1) ;
            }
            break ;
          }
        }
        else
        {
          // Store a run of the same color value.
          if (row >= height || col + opcode.count > width)
            throw EBmpCorruptFile () ;
          for (unsigned int ii = 0 ; ii < opcode.count ; ++ ii)
          {
            image [row][col] = opcode.command ;
            ++ col ;
          }
        }
      }
      if (! done)
        throw EBmpCorruptFile () ;
    }
    else if (compression == BI_RLE4)
    {
      // In this block we handle 4-bit Run-Length Encoded images.

      // The mechanism here is the same as for BI_RLE8 with two
      // exceptions. Here we are dealing with 4-bit nibbles rather
      // than whole bytes. This results in some extra work. In
      // addition, the coding of runs includes two color values.

      unsigned int row = height - 1 ;
      unsigned int col = 0 ;
      bool done = false ;
      while (! strm.eof () && ! done)
      {
        CallProgressFunction ((height - row - 1) * 100 / height) ;

        struct
        {
          UBYTE1 count ;
          UBYTE1 command ;
        } opcode ;

        strm.read ((char *) &opcode, sizeof (opcode)) ;
        if (opcode.count == 0)
        {
          switch (opcode.command)
          {
          case 0:    // Advance to next pixel row
            -- row ;
            col = 0 ;
            break ;
          case 1:    // Image complete
            done = true ;
            break ;
          case 2:   // Move to relative location the image.
            {
              UBYTE1 dx ;
              UBYTE1 dy ;
              strm.read ((char *) &dx, 1) ;
              strm.read ((char *) &dy, 1) ;
              col += dx ;
              row -= dy ;
            }
            break ;
          default:
            {
              UBYTE1 data ;
       		  UBYTE1 hi ;
              UBYTE1 lo ;
              if (row >= height || col + opcode.command > width)
                  throw EBmpCorruptFile () ;
              for (unsigned int ii = 0 ; ii < opcode.command ; ++ ii)
              {
                if ((ii & 1) == 0)
                {
                  strm.read ((char *) &data, 1) ;
                  lo = data & 0xF ;
                  hi = (data & 0xF0) >> 4 ;
                }
                if ((col & 1) == 0)
                {
                  if ((ii & 1) == 0)
                  {
                    image [row][col/2] = hi << 4 ;
                  }
                  else
                  {
                    image [row][col/2] = lo << 4 ;
                  }
                }
                else
                {
                  if ((ii & 1) == 0)
                  {
                   image [row][col/2] |= hi  ;
                  }
                  else
                  {
                    image [row][col/2] |= lo ;
                  }
                }
                ++ col ;
              }
              // If the number of bytes used in this instruction
              // is odd then there is a padding byte.
              switch (opcode.command & 0x3)
              {
              case 1: case 2:
                strm.read ((char *) &data, 1) ;
                break ;
              }
            }
            break ;
          }
        }
        else
        {
          // Process a run of the same color value pairs.
          UBYTE1 hi = opcode.command >> 4 ;
          UBYTE1 lo = opcode.command & 0xF ;
          if (row >= height || col + opcode.count > width)
            throw EBmpCorruptFile () ;

          for (unsigned int ii = 0 ; ii < opcode.count ; ++ ii)
          {
            if ((col & 1) == 0)
            {
              if ((ii & 1) == 0)
              {
                image [row][col/2] = hi << 4 ;
              }
              else
              {
                image [row][col/2] = lo << 4 ;
              }
            }
            else
            {
              if ((ii & 1) == 0)
              {
               image [row][col/2] |= hi  ;
              }
              else
              {
                image [row][col/2] |= lo ;
              }
            }
            ++ col ;
          }
        }
      }
      if (! done)
        throw EBmpCorruptFile () ;
    }
    else
    {
      // Invalid compression type
      throw EBmpCorruptFile () ;
    }
  }
  else
  {
    // Read the data for a 24-bit image.

    // Number of bytes used to store each pixel row in the
    // the file. This value is rounded up to the nearest
    // multiple of four.
    unsigned int physicalrowsize = (3 * width + 0x3) & ~0x3 ;
    // Size of the padding for each row.
    unsigned int padsize = physicalrowsize -  3 * width ;

    // Images with positive heights are stored bottom up. Images with
    // negative height are stored top down.
    if (height > 0)
    {
      for (unsigned int yy = 0 ; yy < height ; ++ yy)
      {
        CallProgressFunction (yy * 100 / height) ;

        unsigned int index = height - yy - 1 ;
        // Have to read the sample values separately because the colors
        // are in reverse order: BGR. If you are on Windows you could
        // red the whole thing a row at a time.
        for (unsigned int xx = 0 ; xx < 3 * width ; xx += 3)
        {
          strm.read ((char *)&image[index][xx + BitmapImage::BlueOffset], 1) ;
          strm.read ((char *)&image[index][xx + BitmapImage::GreenOffset], 1) ;
          strm.read ((char *)&image[index][xx + BitmapImage::RedOffset], 1) ;
        }
        static char pad [4] ;
        strm.read (pad, padsize) ;
      }
    }
    else
    {
      for (unsigned int yy = 0 ; yy < -height ; ++ yy)
      {
        CallProgressFunction (yy * 100 / -height) ;

        // Have to read the sample values separately because the colors
        // are in reverse order: BGR. If you are on Windows you could
        // red the whole thing a row at a time.
        for (unsigned int xx = 0 ; xx < 3 * width ; xx += 3)
        {
          strm.read ((char *)&image[ii][xx + BitmapImage::BlueOffset], 1) ;
          strm.read ((char *)&image[ii][xx + BitmapImage::GreenOffset], 1) ;
          strm.read ((char *)&image[ii][xx + BitmapImage::RedOffset], 1) ;
        }
        static char pad [4] ;
        strm.read (pad, padsize) ;
      }
    }
  }
  CallProgressFunction (100) ;
  return ;
}

//
//  Description:
//
//    This function is used to call the progres function.
//
//  Parameters:
//    percent:  The percent complete (0..100)
//
void BmpDecoder::CallProgressFunction (unsigned int percent)
{
  if (progress_function == NULL)
    return ;
  bool cancel = false ;
  progress_function (*this, progress_data, 1, 1, percent, cancel) ;
  if (cancel)
    throw EGraphicsAbort () ;
  return ;
}

