//
// Copyright (c) 1997,1998, 1999 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)
//
//  Date:    April 15, 1999
//
//  Version: 3
//
//  Description:
//
//    Windows BMP Encoder
//
//  Revision History:
//
#include <windows.h>

#include <fstream>
#include <climits>
#include <string>

#include "bmpdecoder.h"
#include "databuffer.h"
#include "systemspecific.h"

using namespace std ;

namespace
{
//
//  This function takes a contiguous bit mask and determines the
//  offset and size of the bit mask.
//
void FindMask (UBYTE4 mask, UBYTE4 &offset, UBYTE4 &size)
{
  offset = 0 ;
  while ((mask & (1 << offset)) == 0 && offset < 32)
    ++ offset ;
  size = 0 ;
  while (offset + size < 32 && (mask & (1 << (offset + size))) != 0)
    ++ size ;
  return ;
}

}

namespace Colosseum
{

//
//  Description:
//
//    Class default constructor
//

//
//  Description:
//
//    Default Constructor
//
BmpDecoder::BmpDecoder ()
{
  return ;
}

//
//  Description:
//
//    Class Copy Constructor
//
BmpDecoder::BmpDecoder (const BmpDecoder &source)
: BitmapImageDecoder (source)
{
  return ;
}

//
//  Description:
//
//    Class Destructor
//
BmpDecoder::~BmpDecoder ()
{
  return ;
}

//
//  Description:
//
//    Assignment operator.
//
//  Parameters:
//   source: The object to copy
//
BmpDecoder &BmpDecoder::operator=(const BmpDecoder &source)
{
  BitmapImageDecoder::operator=(source) ;
  return *this ;
}


//
//  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)
{
  callProgressFunction (0) ;

  // We need this because MSVC++ does not follow standard scoping
  // rules in for statements.
  unsigned int ii ;

  unsigned int bytesread = 0 ;

  char fileheader [14] ;
  UBYTE4 &bfSize = *reinterpret_cast<UBYTE4*>(&fileheader [2]) ;
  UBYTE4 &bfOffBits = *reinterpret_cast<UBYTE4*>(&fileheader [10]) ;

  strm.read (reinterpret_cast<char *>(&fileheader), sizeof (fileheader)) ;
  if (strm.gcount () != sizeof(fileheader))
    throw BmpError ("Premature End of Stream") ;
  bytesread += sizeof (fileheader) ;

  if (fileheader [0] != 'B' || fileheader [1] != 'M')
    throw BmpError ("Not a BMP Image") ;

  // The header can come in one of two flavors.  They both
  // begin with a 4-byte headersize.

  UBYTE4 headersize ;
  strm.read (reinterpret_cast<char *>(&headersize), sizeof (headersize)) ;
  if (strm.gcount () != sizeof(headersize))
    throw BmpError ("Premature End of Stream") ;

  headersize = LittleEndianToSystem (headersize) ;
  DataBuffer<UBYTE1> headerbuffer (headersize) ;
  strm.read (reinterpret_cast<char*>(&headerbuffer [sizeof (headersize)]),
             headersize - sizeof (headersize)) ;
  bytesread += headersize ;

  unsigned long width ;
  long height ;
  unsigned int bitcount ;
  unsigned int compression ;
  color_table_size = 0 ;
  if (headersize == sizeof (BITMAPCOREHEADER))
  {
    BITMAPCOREHEADER header = reinterpret_cast<BITMAPCOREHEADER&>(headerbuffer [0]) ;
    width = LittleEndianToSystem (header.bcWidth) ;
    height = LittleEndianToSystem (header.bcHeight) ;
    bitcount = LittleEndianToSystem (header.bcBitCount) ;

    color_table_size = 1 << bitcount ;
    bytesread += readOs2ColorTable (strm, color_table_size) ;
    compression = BI_RGB ;
  }
  else if (headersize >= sizeof (BITMAPINFOHEADER))
  {
    BITMAPINFOHEADER &header = reinterpret_cast<BITMAPINFOHEADER&>(headerbuffer [0]) ;
    compression = LittleEndianToSystem (static_cast<UBYTE4>(header.biCompression)) ;

    width = LittleEndianToSystem (static_cast<UBYTE4>(header.biWidth)) ;
    height = LittleEndianToSystem (static_cast<UBYTE4>(header.biHeight)) ;
    bitcount = LittleEndianToSystem (static_cast<UBYTE4>(header.biBitCount)) ;

    color_table_size = LittleEndianToSystem (static_cast<UBYTE4>(header.biClrUsed)) ;
    if (color_table_size == 0 && bitcount  < 16)
    {
      // If the colors used field is non-zero, it gives the size of the
      // color table. Otherwise, the number of bits per pixel gives the size.
      color_table_size = 1 << bitcount ;
    }

    // Validate Compression
    switch (bitcount)
    {
    case 1:
      if (compression != BI_RGB)
        throw BmpError ("Unsupported Compression") ;
      break ;
    case 4:
      if (compression != BI_RGB && compression != BI_RLE4)
        throw BmpError ("Unsupported Compression") ;
      break ;
    case 8:
      if (compression != BI_RGB &&  compression != BI_RLE8)
        throw BmpError ("Unsupported Compression") ;
      break ;
    case 24:
      if (compression != BI_RGB)
        throw BmpError ("Unsupported Compression") ;
      break ;
    case 16: case 32:
      if (compression != BI_RGB && compression != BI_BITFIELDS)
        throw BmpError ("Unsupported Compression") ;
      break ;
    default:
      {
        char buffer [32] ;
        itoa (bitcount, buffer, 10) ;
        string msg = "Unsupported bit count: " ;
        msg += buffer ;
        throw BmpError (msg) ;
      }
    }
    if (color_table_size != 0)
      bytesread += readColorTable (strm, color_table_size) ;
  }
  else
  {
    throw BmpError ("Invalid header size") ;
  }

  // Allocate storage for the image.
  if (height >= 0)
    image.setSize (width, height) ;
  else
    image.setSize (width, -height) ;

  // 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 > bfOffBits)
    throw BmpError ("Corrupt File") ;

  for (ii = bytesread ; ii < bfOffBits ; ++ ii)
  {
    UBYTE1 data ;
    strm.read (reinterpret_cast<char *>(&data), sizeof (data)) ;
  }

  switch (bitcount)
  {
  case 1: case 2: case 4:
    if (compression == BI_RGB)
      readFractionalByteData (strm, bitcount, height, width, image) ;
    else
      readRle4 (strm, height, width, image) ;
    break ;
  case 8:
    if (compression == BI_RGB)
      readFractionalByteData (strm, bitcount, height, width, image) ;
    else
      readRle8 (strm, height, width, image) ;
    break ;
  case 16:
    {
      BITMAPV4HEADER &header = reinterpret_cast<BITMAPV4HEADER&>(headerbuffer [0]) ;
      unsigned long redmask = LittleEndianToSystem (static_cast<UBYTE4>(header.bV4RedMask)) ;
      if (redmask == 0)
        redmask = 0x7C00U ;
      unsigned long greenmask = LittleEndianToSystem (static_cast<UBYTE4>((header.bV4GreenMask))) ;
      if (greenmask == 0)
        greenmask = 0x3E0U ;
      unsigned long bluemask = LittleEndianToSystem (static_cast<UBYTE4>(header.bV4BlueMask)) ;
      if (bluemask == 0)
        bluemask = 0x1FU ;
      unsigned long alphamask = 0 ;
      if (headersize >= sizeof (BITMAPV4HEADER))
        alphamask = LittleEndianToSystem (static_cast<UBYTE4>(header.bV4AlphaMask)) ;
      read16BitData (strm, height, width, image,
                     redmask, greenmask, bluemask, alphamask) ;
    }
    break ;
  case 24:
    read24BitData (strm, height, width, image) ;
    break ;
  case 32:
    {
      BITMAPV4HEADER &header = reinterpret_cast<BITMAPV4HEADER&>(headerbuffer [0]) ;
      unsigned long redmask = LittleEndianToSystem (static_cast<UBYTE4>(header.bV4RedMask)) ;
      if (redmask == 0)
        redmask = 0xFF0000U ;
      unsigned long greenmask = LittleEndianToSystem (static_cast<UBYTE4>(header.bV4GreenMask)) ;
      if (greenmask == 0)
        greenmask = 0xFF00U ;
      unsigned long bluemask = LittleEndianToSystem (static_cast<UBYTE4>(header.bV4BlueMask)) ;
      if (bluemask == 0)
        bluemask = 0xFFU ;
      unsigned long alphamask = 0 ;
      if (headersize >= sizeof (BITMAPV4HEADER))
        alphamask = LittleEndianToSystem (static_cast<UBYTE4>(header.bV4AlphaMask)) ;
      read32BitData (strm, height, width, image,
                     redmask, greenmask, bluemask, alphamask) ;
    }
    break ;
  }

  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 == 0)
    return ;
  bool cancel = false ;
  progress_function (*this, progress_data, 1, 1, percent, cancel) ;
  if (cancel)
    throw GraphicsAbort () ;
  return ;
}

void BmpDecoder::readImageFile (const std::string &filename, BitmapImage &image)
{
  ifstream inputstream (filename.c_str (), ios::binary) ;
  if (! inputstream)
  {
    string msg = "Can't open input file '" ;
    msg += filename + "'\n" + strerror (errno) ;
    throw BmpError (msg) ;
  }
  readImage (inputstream, image) ;
  return ;
}

void BmpDecoder::readFractionalByteData (std::istream &strm,
                                         unsigned int bitcount,
                                         int height, unsigned int width,
                                         BitmapImage &image)
{
  // 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 + CHAR_BIT - 1)/CHAR_BIT ;
  // 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 ;

  DataBuffer<UBYTE1> buffer (physicalrowsize) ;

  // 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) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      // The pixel rows are stored in reverse order.
      unsigned int index = (height - ii - 1) * width ;
      BitmapImage::Pixel *pixel = &image [index] ;
      for (unsigned int jj = 0 ; jj < width ; ++ jj, ++ pixel)
      {
         unsigned int color = (buffer [jj/(CHAR_BIT/bitcount)]
                             >> (bitcount * ((CHAR_BIT/bitcount-1) - jj % (CHAR_BIT/bitcount))))
                             & ((1<<bitcount) - 1) ;
         pixel->red = color_table [color].rgbRed ;
         pixel->green = color_table [color].rgbGreen ;
         pixel->blue = color_table [color].rgbBlue ;
      }
    }
  }
  else
  {
    BitmapImage::Pixel *pixel = &image [0] ;
    for (unsigned int ii = 0 ; ii < - height ; ++ ii)
    {
      callProgressFunction (ii * 100 / height) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      for (unsigned int jj = 0 ; jj < width ; ++ jj, ++ pixel)
      {
         unsigned int color = (buffer [jj/(CHAR_BIT/bitcount)]
                            >> (bitcount * ((CHAR_BIT/bitcount-1) - jj % (CHAR_BIT/bitcount))))
                            & ((1<<bitcount) - 1) ;
         pixel->red = color_table [color].rgbRed ;
         pixel->green = color_table [color].rgbGreen ;
         pixel->blue = color_table [color].rgbBlue ;
      }
    }
  }
  return ;
}

void BmpDecoder::read8BitData (std::istream &strm,
                               int height, int width,
                               BitmapImage &image)
{
  // This is is rowwidth rounded up to the nearest 4 bytes.
  unsigned int physicalrowsize = (width + 0x3) & ~0x3 ;

  DataBuffer<UBYTE1> buffer (physicalrowsize) ;

  // 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) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      // The pixel rows are stored in reverse order.
      unsigned int index = (height - ii - 1) * width ;
      BitmapImage::Pixel *pixel = &image [index] ;
      for (unsigned int jj = 0 ; jj < width ; ++ jj, ++ pixel)
      {
         unsigned int color = buffer [jj] ;
         pixel->red = color_table [color].rgbRed ;
         pixel->green = color_table [color].rgbGreen ;
         pixel->blue = color_table [color].rgbBlue ;
      }
    }
  }
  else
  {
    BitmapImage::Pixel *pixel = &image [0] ;
    for (unsigned int ii = 0 ; ii < - height ; ++ ii)
    {
      callProgressFunction (ii * 100 / height) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      for (unsigned int jj = 0 ; jj < width ; ++ jj, ++ pixel)
      {
         unsigned int color = buffer [jj] ;
         pixel->red = color_table [color].rgbRed ;
         pixel->green = color_table [color].rgbGreen ;
         pixel->blue = color_table [color].rgbBlue ;
      }
    }
  }
  return ;
}

void BmpDecoder::read16BitData (std::istream &strm ,
                                int height, int width,
                                BitmapImage &image,
                                unsigned long redmask, unsigned long greenmask,
                                unsigned long bluemask, unsigned long alphamask)
{
  unsigned long allbits = redmask | greenmask | bluemask | alphamask ;
  if ((allbits & redmask) != redmask || (allbits & greenmask) != greenmask
      || (allbits & bluemask) != bluemask || (allbits & alphamask) != alphamask)
  {
    throw BmpError ("Overlapping component mask") ;
  }

  UBYTE4 redsize, redoffset ;
  UBYTE4 greensize, greenoffset ;
  UBYTE4 bluesize, blueoffset ;
  UBYTE4 alphasize, alphaoffset ;

  FindMask (redmask, redoffset, redsize) ;
  FindMask (greenmask, greenoffset, greensize) ;
  FindMask (bluemask, blueoffset, bluesize) ;
  FindMask (alphamask, alphaoffset, alphasize) ;

  unsigned int physicalrowsize = (sizeof (UBYTE2) * width + 0x3) & ~0x3 ;
  DataBuffer<UBYTE2> buffer (physicalrowsize / sizeof (UBYTE2)) ;

  // 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) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      // The pixel rows are stored in reverse order.
      unsigned int index = (height - ii - 1) * width ;
      BitmapImage::Pixel *pixel = &image [index] ;
      for (unsigned int jj = 0 ; jj < width ; ++ jj, ++ pixel)
      {
         UBYTE2 color = LittleEndianToSystem (buffer [jj]) ;
         pixel->red   = ((color & redmask) >> (redoffset + redsize - 1)) & 0xFFU ;
         pixel->green = ((color & greenmask) >> (greenoffset + greensize - 1)) & 0xFFU ;
         pixel->blue  = ((color & bluemask) >> (blueoffset + bluesize - 1)) & 0xFFU ;
         pixel->alpha = ((color & alphamask) >> (alphaoffset + alphasize - 1)) & 0xFFU ;
      }
    }
  }
  else
  {
    BitmapImage::Pixel *pixel = &image [0] ;
    for (unsigned int ii = 0 ; ii < - height ; ++ ii)
    {
      callProgressFunction (ii * 100 / height) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      for (unsigned int jj = 0 ; jj < width ; ++ jj, ++ pixel)
      {
         UBYTE2 color = LittleEndianToSystem (buffer [jj]) ;
         pixel->red   = ((color & redmask) >> (redoffset + redsize - 1)) & 0xFFU ;
         pixel->green = ((color & greenmask) >> (greenoffset + greensize - 1)) & 0xFFU ;
         pixel->blue  = ((color & bluemask) >> (blueoffset + bluesize - 1)) & 0xFFU ;
         pixel->alpha = ((color & alphamask) >> (alphaoffset + alphasize - 1)) & 0xFFU ;
      }
    }
  }
  return ;
}

void BmpDecoder::read24BitData (std::istream &strm,
                                int height, int width,
                                BitmapImage &image)
{
  // This is is rowwidth rounded up to the nearest 4 bytes.
  unsigned int physicalrowsize = (3 * width + 0x3) & ~0x3 ;

  DataBuffer<UBYTE1> buffer (physicalrowsize) ;

  // 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) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      // The pixel rows are stored in reverse order.
      unsigned int index = (height - ii - 1) * width ;
      BitmapImage::Pixel *pixel = &image [index] ;
      for (unsigned int jj = 0, kk = 0  ; jj < width ; ++ jj, ++ pixel)
      {
         pixel->blue = buffer [kk] ; ++ kk ;
         pixel->green = buffer [kk] ; ++ kk ;
         pixel->red = buffer [kk] ; ++ kk ;
      }
    }
  }
  else
  {
    BitmapImage::Pixel *pixel = &image [0] ;
    for (unsigned int ii = 0 ; ii < - height ; ++ ii)
    {
      callProgressFunction (ii * 100 / height) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      for (unsigned int jj = 0, kk = 0  ; jj < width ; ++ jj, ++ pixel)
      {
         pixel->blue = buffer [kk] ; ++ kk ;
         pixel->green = buffer [kk] ; ++ kk ;
         pixel->red = buffer [kk] ; ++ kk ;
      }
    }
  }
  return ;
}
void BmpDecoder::read32BitData (std::istream &strm,
                                int height, int width,
                                BitmapImage &image,
                                unsigned long redmask,
                                unsigned long greenmask,
                                unsigned long bluemask,
                                unsigned long alphamask)
{
  unsigned long allbits = redmask | greenmask | bluemask | alphamask ;
  if ((allbits & redmask) != redmask || (allbits & greenmask) != greenmask
      || (allbits & bluemask) != bluemask || (allbits & alphamask) != alphamask)
  {
    throw BmpError ("Overlapping component mask") ;
  }

  UBYTE4 redsize, redoffset ;
  UBYTE4 greensize, greenoffset ;
  UBYTE4 bluesize, blueoffset ;
  UBYTE4 alphasize, alphaoffset ;

  FindMask (redmask, redoffset, redsize) ;
  FindMask (greenmask, greenoffset, greensize) ;
  FindMask (bluemask, blueoffset, bluesize) ;
  FindMask (alphamask, alphaoffset, alphasize) ;

  unsigned int physicalrowsize = sizeof (UBYTE4) * width ;
  DataBuffer<UBYTE4> buffer (width) ;

  // 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) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      // The pixel rows are stored in reverse order.
      unsigned int index = (height - ii - 1) * width ;
      BitmapImage::Pixel *pixel = &image [index] ;
      for (unsigned int jj = 0 ; jj < width ; ++ jj, ++ pixel)
      {
         UBYTE4 color = LittleEndianToSystem (buffer [jj]) ;
         pixel->red   = ((color & redmask) >> (redoffset + redsize - CHAR_BIT)) & 0xFFU ;
         pixel->green = ((color & greenmask) >> (greenoffset + greensize - CHAR_BIT)) & 0xFFU ;
         pixel->blue  = ((color & bluemask) >> (blueoffset + bluesize - CHAR_BIT)) & 0xFFU ;
         pixel->alpha = ((color & alphamask) >> (alphaoffset + alphasize - CHAR_BIT)) & 0xFFU ;
      }
    }
  }
  else
  {
    BitmapImage::Pixel *pixel = &image [0] ;
    for (unsigned int ii = 0 ; ii < - height ; ++ ii)
    {
      callProgressFunction (ii * 100 / height) ;
      strm.read (reinterpret_cast<char *>(&buffer [0]), physicalrowsize) ;
      if (strm.gcount () != physicalrowsize)
        throw BmpError ("Premature End of Stream") ;

      for (unsigned int jj = 0 ; jj < width ; ++ jj, ++ pixel)
      {
         UBYTE4 color = LittleEndianToSystem (buffer [jj]) ;
         pixel->red   = ((color & redmask) >> (redoffset + redsize - CHAR_BIT)) & 0xFFU ;
         pixel->green = ((color & greenmask) >> (greenoffset + greensize - CHAR_BIT)) & 0xFFU ;
         pixel->blue  = ((color & bluemask) >> (blueoffset + bluesize - CHAR_BIT)) & 0xFFU ;
         pixel->alpha = ((color & alphamask) >> (alphaoffset + alphasize - CHAR_BIT)) & 0xFFU ;
      }
    }
  }
  return ;

}

unsigned int BmpDecoder::readOs2ColorTable (std::istream &strm,
                                    unsigned int colorcount)
{
  for (unsigned int ii = 0 ; ii < colorcount ; ++ ii)
  {
    RGBTRIPLE color ;
    strm.read (reinterpret_cast<char *>(&color), sizeof (color)) ;
    color_table [ii].rgbRed   = color.rgbtRed ;
    color_table [ii].rgbBlue  = color.rgbtBlue ;
    color_table [ii].rgbGreen = color.rgbtGreen ;
  }
  return colorcount * sizeof (RGBTRIPLE) ;
}

unsigned int BmpDecoder::readColorTable (std::istream &strm,
                                          unsigned int colorcount)
{
  unsigned int bytes = sizeof (RGBQUAD) * colorcount ;
  strm.read (reinterpret_cast<char *>(&color_table [0]), bytes) ;
  return bytes ;
}

void BmpDecoder::readRle4 (std::istream &strm , int height, int width, BitmapImage &image)
{
  if (height < 0)
    throw BmpError ("Negative height not allowed in an RLE image") ;

  // 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 ;
  BitmapImage::Pixel *pixel = &image [row * width] ;
  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 (reinterpret_cast<char *>(&opcode), sizeof (opcode)) ;
    if (opcode.count == 0)
    {
      switch (opcode.command)
      {
      case 0:    // Advance to next pixel row
        -- row ;
        pixel = &image [row * width] ;
        col = 0 ;
        break ;
      case 1:    // Image complete
        done = true ;
        break ;
      case 2:   // Move to relative location the image.
        {
          UBYTE1 dx ;
          UBYTE1 dy ;
          strm.read (reinterpret_cast<char *>(&dx), sizeof (dx)) ;
          strm.read (reinterpret_cast<char *>(&dy), sizeof (dy)) ;
          col += dx ;
          row -= dy ;
          pixel = &image [row * width] ;
        }
        break ;
      default:
        {
          UBYTE1 data ;
   		    UBYTE1 hi ;
          UBYTE1 lo ;
          if (row >= height || col + opcode.command > width)
            throw BmpError ("Corrupt Data") ;
          for (unsigned int ii = 0 ; ii < opcode.command ; ++ ii)
          {
            if ((ii & 1) == 0)
            {
              strm.read (reinterpret_cast<char *>(&data), sizeof (data)) ;
              lo = data & 0xF ;
              hi = (data & 0xF0) >> 4 ;
            }
            if ((ii & 1) == 0)
            {
              pixel [col].red = color_table [hi].rgbRed ;
              pixel [col].green = color_table [hi].rgbGreen ;
              pixel [col].blue = color_table [hi].rgbBlue ;
            }
            else
            {
              pixel [col].red = color_table [lo].rgbRed ;
              pixel [col].green = color_table [lo].rgbGreen ;
              pixel [col].blue = color_table [lo].rgbBlue ;
            }
            ++ 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 (reinterpret_cast<char *>(&data), sizeof (data)) ;
            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 BmpError ("Corrupt Data") ;

      for (unsigned int ii = 0 ; ii < opcode.count ; ++ ii)
      {
        if ((ii & 1) == 0)
        {
          pixel [col].red = color_table [hi].rgbRed ;
          pixel [col].green = color_table [hi].rgbGreen ;
          pixel [col].blue = color_table [hi].rgbBlue ;
        }
        else
        {
          pixel [col].red = color_table [lo].rgbRed ;
          pixel [col].green = color_table [lo].rgbGreen ;
          pixel [col].blue = color_table [lo].rgbBlue ;
        }
        ++ col ;
      }
    }
  }
  if (! done)
    throw BmpError ("Corrupt Data") ;
  return ;
}

void BmpDecoder::readRle8 (std::istream &strm , int height, int width, BitmapImage &image)
{
  if (height < 0)
    throw BmpError ("Negative height not allowed in an RLE image") ;

  unsigned int row = height - 1 ;  // Current row
  unsigned int col = 0 ;           // Current column
  BitmapImage::Pixel *pixel = &image [row * width] ;
  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 (reinterpret_cast<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 ;
        pixel = &image [row * width] ;
        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 (reinterpret_cast<char *>(&dx), sizeof (dx)) ;
          strm.read (reinterpret_cast<char *>(&dy), sizeof (dy)) ;
          col += dx ;
          row -= dy ;
          pixel = &image [row * width] ;
        }
        break ;
      default:
        {
          // Store absolute data. The command code is the
          // number of absolute bytes to store.
          if (row >= height || col + opcode.command > width)
              throw BmpError ("Corrupt Data") ;
          UBYTE1 data ;
          for (unsigned int ii = 0 ; ii < opcode.command ; ++ ii)
          {
            strm.read (reinterpret_cast<char *>(&data), sizeof (data)) ;
            pixel [col].red = color_table [data].rgbRed ;
            pixel [col].green = color_table [data].rgbGreen ;
            pixel [col].blue = color_table [data].rgbBlue ;
            ++ col ;
          }
          // An odd number of bytes is followed by a pad byte.
          if ((opcode.command & 1) != 0)
            strm.read (reinterpret_cast<char *>(&data), sizeof (data)) ;
        }
        break ;
      }
    }
    else
    {
      // Store a run of the same color value.
      if (row >= height || col + opcode.count > width)
        throw BmpError ("Corrupt Data") ;
      for (unsigned int ii = 0 ; ii < opcode.count ; ++ ii)
      {
        pixel [col].red = color_table [opcode.command].rgbRed ;
        pixel [col].green = color_table [opcode.command].rgbGreen ;
        pixel [col].blue = color_table [opcode.command].rgbBlue ;
        ++ col ;
      }
    }
  }
  if (! done)
    throw BmpError ("Corrupt Data") ;

  return ;
}

} // End Namespace Colosseum
