
//
//  Title:  PNG Decoder class implementation
//
//  Copyright 1997-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.
//
//  Author:  John M. Miano (miano@colosseumbuilders.com)
//
//  Date:    March 31, 1999
//
//  Version: 2
//
//  Description:
//
//    This class is a decoder for PNG images
//
//  Revision History:
//
//

#include <iomanip>
#include <fstream>
#include <climits>
#include <algorithm>
#include <cstring>
#include <string>

#include "adler32.h"
#include "pngdecoder.h"
#include "pngpvt.h"
#include "pnginputstream.h"
#include "pnginputfilestream.h"
#include "pnginputmapstream.h"

#define VERYVERBOSE

using namespace std ;
using namespace ColosseumPrivate ;

namespace
{
inline bool MatchChunks (const UBYTE1 *operand1, const char *operand2)
{
  if (memcmp (operand1, operand2, 4) == 0)
    return true ;
  else
    return false ;
}

}

namespace Colosseum
{

// These values define the Adam7 interlace pattern for each pass.
const PngDecoder::InterlaceInfo PngDecoder::interlace_values [InterlacePasses] =
  {
    { 8, 8, 0, 0, },
    { 8, 8, 0, 4, },
    { 8, 4, 4, 0, },
    { 4, 4, 0, 2, },
    { 4, 2, 2, 0, },
    { 2, 2, 0, 1, },
    { 2, 1, 1, 0, },
  } ;


//
// Class Constructor
//
// Nothing gets done here except initializing variables to a known state.
//
PngDecoder::PngDecoder ()
: verbose_flag (false)
{
  row_buffers [0] = 0 ;
  row_buffers [1] = 0 ;
  return ;
}

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

//
// Description:
//
//   This function frees all of the dynamically allocated data.
//
void PngDecoder::freeData ()
{
  delete [] row_buffers [0] ; row_buffers [0] = 0 ;
  delete [] row_buffers [1] ; row_buffers [1] = 0 ;
  return ;
}

//
//  Description:
//
//    This function reads a PNG image.
//
//  Parameters:
//
//    strm:  The input stream
//    image:  The output image
//
void PngDecoder::readImage (PngInputStream &inputstream, BitmapImage &image)
{
  try
  {
    reading_pixel_data = false ;
    data_read = false ;
    header_read = false ;
    end_read = false ;
    palette_read = false ;

    palette_size = 0 ;

    // At the start of the image we should find
    // 1. PNG Signature
    // 2. IHDR Chunk
    // After that almost anything goes.

    readSignature (inputstream) ;

    inputstream.getNextChunk () ;
    if (! MatchChunks (inputstream.getChunkType (), "IHDR"))
      throw BadPngStream ("Missing IHDR Chunk") ;
    processChunk (inputstream, image) ;

    // Now chunks can come in almost any order.
    while (! end_read && inputstream.getNextChunk ())
    {
      processChunk (inputstream, image) ;
    }

    freeData () ;
  }
  catch (...)
  {
    freeData () ;
    throw ;
  }

  if (! data_read)
    throw BadPngStream ("Image Contains no Pixel Data") ;
  if (! end_read)
    throw BadPngStream ("Image Contains no IEND chunk") ;
  return ;
}


//
//  Description:
//
//    This function reads the PNG signature from the input stream. It
//    throws an exception if the stream does not have a valid signature.
//
//  Parameters:
//
//    inputstream : The stream to read the signature from.
//
void PngDecoder::readSignature (PngInputStream &inputstream)
{
  UBYTE1 *sigbuf ;

  inputstream.readRaw (PngSignatureSize, sigbuf) ;
  if (memcmp (sigbuf, PngSignature, PngSignatureSize) != 0)
    throw BadPngStream ("Not a PNG file") ;
  return ;
}

//
//  Description:
//
//    This function reads a complete chunk from the input stream. A chunk
//    consists of:
//
//      Chunk Length:  4 Bytes
//      Chunk Type:    4 Bytes
//      Chunk Data:    "Chunk Length" Bytes
//      Chunk CRC:     4 Bytes
//
//    The chunk CRC value is calculated from the type and data fields.
//
//  Parameters:
//
//    inputstream : The stream to read the image from
//    image : The output image object
//
void PngDecoder::processChunk (PngInputStream &inputstream, BitmapImage &image)
{
  if (verbose_flag)
  {
    char type [5] = { 0, 0, 0, 0, 0, } ;
    memcpy (type, inputstream.getChunkType (), 4) ;
    cout << "{" << endl ;
    cout << " Block Type: " << type << endl ;
    cout << " Length: " << (inputstream.getChunkDataLength () + 4) << endl ;
  }

  const UBYTE1 *chunktype = inputstream.getChunkType () ;
  if (MatchChunks (chunktype, "IHDR")) processHeader (inputstream, image) ;
  else if (MatchChunks (chunktype, "IDAT")) readPixelData (inputstream, image) ;
  else if (MatchChunks (chunktype, "PLTE")) processPalette (inputstream) ;
  else if (MatchChunks (chunktype, "IEND")) end_read = true ;
  else if (MatchChunks (chunktype, "bKGD")) processBackground (inputstream) ;
  else if (MatchChunks (chunktype, "gAMA")) processGamma (inputstream) ;
  else if (MatchChunks (chunktype, "cHRM")) processChromaticities (inputstream) ;
  else if (MatchChunks (chunktype, "hIST")) processHistogram (inputstream) ;
  else if (MatchChunks (chunktype, "sBIT")) processSignificantBits (inputstream) ;
  else if (MatchChunks (chunktype, "tRNS")) processTransparency (inputstream) ;
  else if (MatchChunks (chunktype, "pHYs")) processPhysicalPixelDimensions (inputstream) ;
  else if (MatchChunks (chunktype, "tIME")) processImageTime (inputstream) ;
  else if (MatchChunks (chunktype, "tEXt")) processTextualData (inputstream) ;
  else if (MatchChunks (chunktype, "zEXt")) processCompressedText (inputstream) ;
  else if ((chunktype [0] & (1 << 5)) == 0)
  {
    string msg = string ("Unknown critical chunk") ;
    throw BadPngStream (msg) ;
  }

  if (verbose_flag)
    cout << "}" << endl ;
  return ;
}

//
// Description:
//
//   This function processes an IHDR chuck. This chunk contains the image
//   header which defines the dimensions and color type.
//
//   The main functions here are to allocate space in the image object and
//   to create the color table for grayscale images.
//
//  Parameters:
//
//    inputstream : The input stream to read the header from
//    image : The image to output
//
void PngDecoder::processHeader (PngInputStream &inputstream, BitmapImage &image)
{
  if (header_read)
    throw BadPngStream ("Duplicate IHDR Block") ;
  header_read = true ;

  if (inputstream.getChunkDataLength () != sizeof (PngImageHeader))
    throw BadPngStream ("Invalid Header Length") ;

  // Save the data from the image header.
  PngImageHeader *header = (PngImageHeader *) inputstream.getChunkData () ;
  image_height = BigEndianToSystem (header->height) ;
  image_width = BigEndianToSystem (header->width) ;
  image_depth = header->bitdepth ;
  image_color_type = (PngColorType) header->colortype ;
  image_compression_method = header->compressionmethod ;
  image_filter_method = header->filtermethod ;
  image_interlace_method = header->interlacemethod ;

  if (verbose_flag)
  {
    cout << " Image Dimensions: " << image_height << " x " << image_width << endl ;
    cout << " Bit Depth: " << (int) image_depth << endl ;
    cout << " Color Type: (" << (int) image_color_type << ") " ;
    switch (image_color_type)
    {
    case Grayscale:       cout << "grayscale" << endl ; break ;
    case RGB:             cout << "RGB" << endl ; break ;
    case Palette:         cout << "palette" << endl ; break ;
    case GrayscaleAlpha:  cout << "grayscale/alpha" << endl ; break ;
    case RGBAlpha:        cout << "RGB/alpha" << endl ; break ;
    default:              cout << "invalid" << endl ; break ;
    }
    cout << " Compression Method: " << (int) image_compression_method << endl ;
    cout << " Filter Method: " << (int) image_filter_method << endl ;
    cout << " Interlace Method: (" << (int) image_interlace_method << ") " ;
    switch (image_interlace_method)
    {
    case 0:  cout << "none" << endl ; break ;
    case 1:  cout << "Adam7" << endl ; break ;
    default: cout << "unknown" << endl ; break ;
    }
  }

  // Ensure the that the values in the image header are valid.
  if (image_compression_method != 0)
    throw BadPngStream ("Invalid Compression Method") ;

  if (image_filter_method != 0)
    throw BadPngStream ("Invalid Filter Method") ;

  if (image_interlace_method != 0 && image_interlace_method != 1)
    throw BadPngStream ("Invalid Interlace Method") ;
  switch (image_depth)
  {
  case 1: case 2: case 4: case 8: case 16: break ;
  default: throw BadPngStream ("Invalid Bit Depth") ;
  }

  // Ensure that the color type and bit depth values are consistent.
  switch (image_color_type)
  {
  case Grayscale: break ; // All bit depths are legal for grayscale.
  case RGB: case RGBAlpha: case GrayscaleAlpha:
    if (image_depth != 8 && image_depth != 16)
      throw BadPngStream ("Invalid Bit Depth for Color Type") ;
    break ;
  case Palette:
    if (image_depth == 16)
      throw BadPngStream ("Invalid Bit Depth for Color Type") ;
    break ;
  default: throw BadPngStream ("Invalid Color Type") ;
  }

  // For grayscale images of less than 16 bpp we create a fixed palette.
  if (image_color_type == Grayscale || image_color_type == GrayscaleAlpha)
  {
    unsigned int maxindex ;
    if (image_depth < 8)
      maxindex = (1 << image_depth) - 1 ;
    else
      maxindex = 0xFF ;
    for (int ii = 0 ; ii <= maxindex ; ++ ii)
    {
      color_palette [ii].red   = ii * 0xFF / maxindex ;
      color_palette [ii].green = ii * 0xFF / maxindex  ;
      color_palette [ii].blue  = ii * 0xFF / maxindex  ;
    }
  }

  // Allocate space space in the image object.
  image.setSize (image_width, image_height) ;
  return ;
}

//
//  Description:
//
//    This function processes the data in a PLTE block. A PLTE block
//    defines the color palette for the image.
//
//  Parameters:
//
//    inputstream : The stream to read the palette from
//    image : The image to output
//
void PngDecoder::processPalette (PngInputStream &inputstream)
{
  // There can only be one palette for the image.
  if (palette_read)
    BadPngStream EPngError ("Duplicate PLTE block") ;
  palette_read = true ;

  // Grayscale images are not allowed to have an palette.
  if (image_color_type == Grayscale || image_color_type == GrayscaleAlpha)
    throw BadPngStream ("Grayscale image contains a PLTE block") ;

  // The palette size must be divisable by 3.
  UBYTE4 chunklength = inputstream.getChunkDataLength () ;
  if (chunklength  % 3 != 0)
    throw BadPngStream ("PLTE chunk length not divisible by 3") ;
  if (chunklength > 3 * 256)
    throw BadPngStream ("PLTE chunk length too large") ;

  palette_size = chunklength / 3 ;

  // PLTE chunks are permitted with RGB images to suggest colors
  // for quantization.  Our implementation does not do anything
  // with this information.
  if (image_color_type == RGB || image_color_type == RGBAlpha)
    return ;

  // Store the palette in the image.
  const UBYTE1 *chunkdata = inputstream.getChunkData () ;
  for (unsigned int ii = 0, jj = 0 ; ii < palette_size ; ++ ii, jj += 3)
  {
    color_palette [ii].red = chunkdata [jj] ;
    color_palette [ii].green = chunkdata [jj+1] ;
    color_palette [ii].blue = chunkdata [jj+2] ;
  }

  return ;
}


//
//  Description:
//
//    This function processes an IDAT data stream. It decompresses the
//    pixel data and stores it in the image.
//
//    The Deflate compression system supports many options that are not
//    permitted in PNG. This is the reason for checking for specific
//    parameter values.
//
//  Parameters:
//
//    inputstream : The read to read the pixel data form
//    image : The output image
//
void PngDecoder::readPixelData (PngInputStream &inputstream, BitmapImage &image)
{
  // Ensure that we only have one IDAT stream in an image stream.
  if (data_read)
    throw BadPngStream ("Image Contains Multiple Data Segments") ;
  data_read = true ;

  // If the image requires a palette then it must have been read by
  // the time we get here.
  if (image_color_type == Palette && ! palette_read)
    throw BadPngStream ("PLTE block must occur before IDAT") ;

  // This block determines the number of bytes needed to store each
  // data row of the image. Then allocate two buffers to hold row
  // data.  We need to row buffers to support filtering.
  unsigned int rowbits ; // The number of bits of data per row.
  switch (image_color_type)
  {
  case Grayscale: case Palette:
    rowbits = image_width * image_depth ;
    row_buffer_width = (rowbits + CHAR_BIT - 1) / CHAR_BIT ;
    break ;
  case GrayscaleAlpha:
    row_buffer_width = 2 * image_width * image_depth / CHAR_BIT ;
    break;
  case RGB:
    row_buffer_width = image_width * 3 * image_depth / CHAR_BIT ;
    break ;
  case RGBAlpha:
    row_buffer_width = image_width * 4 * image_depth / CHAR_BIT ;
    break ;
  default:
    throw BadPngStream ("Invalid Color Type") ;
  }
  row_buffers [0] = new UBYTE1 [row_buffer_width] ;
  row_buffers [1] = new UBYTE1 [row_buffer_width] ;

  inflate_decoder.openStream (inputstream) ;

  // Read the image data.
  if (image_interlace_method == 0)
  {
    readNoninterlaced (inputstream, image) ;
  }
  else
  {
    for (interlace_pass = 0 ;
         interlace_pass < InterlacePasses ;
         ++ interlace_pass)
    {
      readInterlaced (inputstream, image) ;
    }
  }
  UBYTE1 tmp ;
  if (inflate_decoder.isMoreData () && inflate_decoder.decode (inputstream, sizeof (tmp), &tmp) != 0)
    BadPngStream ("Extra bytes in compressed data") ;

  reading_pixel_data = false ;
  return ;
}

//
//  Description:
//
//    This function filters a row of data.
//
//  Parameters:
//
//    filter:  The type of filter
//
void PngDecoder::filterRow (unsigned int filter)
{
  int lastrow = ! current_row_buffer ; // Index of the previous row
  int col ;                            // Current Column
  int offset ;                         // Pixel width

  // Filtering is done on corresponding items within a record. Determine
  // the number of bytes between corresponding items.
  switch (image_color_type)
  {
  case Grayscale: case Palette: image_depth == 16 ? offset = 2 : offset = 1 ; break ;
  case RGB:                     offset = 3 * image_depth / CHAR_BIT ; break ;
  case GrayscaleAlpha:          offset = 2 * image_depth / CHAR_BIT ; break ;
  case RGBAlpha:                offset = 4 * image_depth / CHAR_BIT ; break ;
  default: throw BadPngStream ("Invalid Color Type") ;
  }

  // Filter the row based upon the filter type.
  switch (filter)
  {
  case FILTERNONE: break ;
  case FILTERSUB:
    // The value is the difference from the value to the left.
    for (col = offset ; col < row_buffer_width ; ++ col)
    {
      row_buffers [current_row_buffer][col] =
           (row_buffers [current_row_buffer][col] +
            row_buffers [current_row_buffer][col-offset]) & 0xFF ;
    }
    break ;
  case FILTERUP:
    // The value is the difference from the value in the previous row.
    for (col = 0 ; col < row_buffer_width ; ++ col)
    {
      row_buffers [current_row_buffer][col] =
           (row_buffers [current_row_buffer][col]
            + row_buffers [lastrow][col]) & 0xFF ;
    }
    break ;

  case FILTERAVERAGE:
    for (col = 0 ; col < row_buffer_width ; ++ col)
    {
      int left ;
      int above = row_buffers [lastrow][col] ;
      if (col < offset)
        left = 0 ;
      else
        left = row_buffers [current_row_buffer][col-offset] ;

      row_buffers [current_row_buffer][col] =
            (row_buffers [current_row_buffer][col]
             + (left + above) / 2) & 0xFF ;
    }
    break ;

  case FILTERPAETH:
    for (col = 0 ; col < row_buffer_width ; ++ col)
    {
      UBYTE1 left, above, aboveleft ;
      above = row_buffers [lastrow][col] ;
      if (col < offset)
      {
        left = 0 ;
        aboveleft = 0 ;
      }
      else
      {
        left = row_buffers [current_row_buffer][col-offset] ;
        aboveleft = row_buffers [lastrow][col-offset] ;
      }
      int vv = (int) row_buffers [current_row_buffer][col] ;
      int pp = PaethPredictor (left, above, aboveleft) ;
      row_buffers [current_row_buffer][col] = (pp + vv) & 0xFF ;
    }
    break ;

  default:
    throw BadPngStream ("Invalid Filter Method") ;
  }
  return ;
}

//
//  Description:
//
//    This function copies the data from a non-interlaced row to the image.
//
//  Parameters:
//
//    row :  The output row number
//    image : The image to output
//
void PngDecoder::copyNoninterlacedRowToImage (unsigned int row, BitmapImage &image)
{
  BitmapImage::Pixel *pixel = &image [row * image_width] ;
  switch (image_color_type)
  {
  case Grayscale: case Palette:
    {
      switch (image_depth)
      {
      case 1:
        {
          // We convert 2 bits to 8 bits/pixel.
          for (unsigned int ii = 0 ; ii < image_width ; ++ ii)
          {
            unsigned int byteoffset = ii / 8 ;
            unsigned int bitoffset = ii % 8 ;

            unsigned int rawvalue = row_buffers [current_row_buffer][byteoffset] ;
            unsigned int index = (rawvalue >> (CHAR_BIT - 1 - bitoffset)) & 0x1U ;
            pixel [ii].red   = color_palette [index].red ;
            pixel [ii].green = color_palette [index].green ;
            pixel [ii].blue  = color_palette [index].blue ;
            pixel [ii].alpha = 0xFFU ;
          }
        }
        break ;
      case 2:
        {
          // We convert 2 bits to 8 bits/pixel.
          for (unsigned int ii = 0 ; ii < image_width ; ++ ii)
          {
            unsigned int byteoffset = ii / (CHAR_BIT / 2) ;
            unsigned int bitoffset = 2 * (ii % (CHAR_BIT / 2)) ;

            unsigned int rawvalue = row_buffers [current_row_buffer][byteoffset] ;
            unsigned int index = (rawvalue >> (CHAR_BIT - bitoffset - 2)) & 0x3 ;
            pixel [ii].red = color_palette [index].red ;
            pixel [ii].green = color_palette [index].green ;
            pixel [ii].blue = color_palette [index].blue ;
            pixel [ii].alpha = 0xFFU ;
          }
        }
        break ;
      case 4:
        {
          for (unsigned int ii = 0 ; ii < image_width ; ++ ii)
          {
            unsigned int byteoffset = ii / 2 ;

            unsigned int rawvalue = row_buffers [current_row_buffer][byteoffset] ;
            unsigned int index ;
            if (ii % 2 == 0)
              index = (rawvalue >> 4) & 0xF ;
            else
              index = rawvalue & 0xF ;
            pixel [ii].red = color_palette [index].red ;
            pixel [ii].green = color_palette [index].green ;
            pixel [ii].blue = color_palette [index].blue ;
            pixel [ii].alpha = 0xFFU ;
          }
        }
        break ;
      case 8:
        {
          for (unsigned int ii = 0 ; ii < row_buffer_width ; ++ ii)
          {
            unsigned int index = row_buffers [current_row_buffer][ii] ;
            pixel [ii].red   = color_palette [index].red ;
            pixel [ii].green = color_palette [index].green ;
            pixel [ii].blue  = color_palette [index].blue ;
            pixel [ii].alpha = 0xFFU ;
          }
        }
        break ;
      case 16:
        {
          for (unsigned int col = 0, ii = 0 ; ii < image_width ; ++ ii, col += 2)
          {
            UBYTE1 value = row_buffers [current_row_buffer][col] ;
            pixel [ii].red   = value ;
            pixel [ii].green = value ;
            pixel [ii].blue  = value ;
            pixel [ii].alpha = 0xFF ;
          }
        }
        break ;
      default:
        throw BadPngStream ("Invalid Bit Depth") ;
      }
    }
    break ;

  case RGB:
    {
      switch (image_depth)
      {
      case 8:
        for (unsigned int ii = 0, col = 0 ; ii < image_width ; ++ ii)
        {
          pixel [ii].red   = row_buffers [current_row_buffer][col] ; ++ col ;
          pixel [ii].green = row_buffers [current_row_buffer][col] ; ++ col ;
          pixel [ii].blue  = row_buffers [current_row_buffer][col] ; ++ col ;
          pixel [ii].alpha = 0xFFU ;
        }
        break ;
      case 16:
        for (unsigned int ii = 0, col = 0 ; ii < image_width ; ++ ii)
        {
          pixel [ii].red   = row_buffers [current_row_buffer][col] ; col += 2 ;
          pixel [ii].green = row_buffers [current_row_buffer][col] ; col += 2 ;
          pixel [ii].blue  = row_buffers [current_row_buffer][col] ; col += 2 ;
          pixel [ii].alpha = 0xFFU ;
        }
        break ;
      default:
        throw BadPngStream ("Invalid Image Depth") ;
      }
    }
    break ;

  case GrayscaleAlpha:
    {
      // For 8-bit samples each sample interval is 2 bytes. For 16-bit
      // samples it is four bytes.

      if (image_depth == 8 || image_depth == 16)
      {
        BitmapImage::Pixel *pixel = &image [row * image_width] ;
        for (unsigned int ii = 0, col = 0 ; ii < image_width ; ++ ii)
        {
          unsigned int index = row_buffers [current_row_buffer][col] ;
          col += image_depth / CHAR_BIT ;
          pixel [ii].red   = color_palette [index].red ;
          pixel [ii].green = color_palette [index].green ;
          pixel [ii].blue  = color_palette [index].blue ;
          pixel [ii].alpha = row_buffers [current_row_buffer][col] ;
          col += image_depth / CHAR_BIT ;
        }
      }
      else
      {
        throw BadPngStream ("Invalid Image Depth for Grayscale/Alpha") ;
      }
    }
    break ;

  case RGBAlpha:
    {
      if (image_depth == 8 || image_depth == 16)
      {
        BitmapImage::Pixel *pixel = &image [row * image_width] ;
        for (unsigned int ii = 0, col = 0 ; ii < image_width ; ++ ii)
        {
          pixel [ii].red   = row_buffers [current_row_buffer][col] ; col += image_depth / CHAR_BIT ;
          pixel [ii].green = row_buffers [current_row_buffer][col] ; col += image_depth / CHAR_BIT ;
          pixel [ii].blue  = row_buffers [current_row_buffer][col] ; col += image_depth / CHAR_BIT ;
          pixel [ii].alpha = row_buffers [current_row_buffer][col] ; col += image_depth / CHAR_BIT ;
        }
      }
      else
      {
        throw BadPngStream ("Invalid Image Depth for RGB/Alpha") ;
      }
    }
    break ;

  default:
    throw BadPngStream ("Invalid Color Type") ;
  }
  return ;
}

//
//  Description:
//
//    This function reads the pixel data for a non-interlaced image.
//
//  Parameters:
//
//    inputstream : The input stream to read the image from.
//    image : The image to output
//
void PngDecoder::readNoninterlaced (PngInputStream &inputstream, BitmapImage &image)
{
  // Initialize the input buffers.
  current_row_buffer = 0 ;
  fill (row_buffers [0], row_buffers [0] + row_buffer_width, 0) ;
  fill (row_buffers [1], row_buffers [1] + row_buffer_width, 0) ;

  callProgressFunction (0) ;
  for (unsigned int row = 0 ; row < image_height ; ++ row)
  {
    UBYTE1 filterbuffer ;
    inflate_decoder.decode (inputstream, sizeof (filterbuffer), &filterbuffer) ;
    PngFilterType filter = static_cast <PngFilterType> (filterbuffer) ;
    inflate_decoder.decode (inputstream,
                            row_buffer_width,
                            row_buffers [current_row_buffer]) ;
    filterRow (filter) ;
    copyNoninterlacedRowToImage (row, image) ;
    current_row_buffer = ! current_row_buffer ; // Switch buffers
    callProgressFunction (100 * row / image_height) ;
  }
  callProgressFunction (100) ;
  return ;
}

//
//  Description:
//
//    This function reads all the rows for an interlaced pass.
//
//  Parameters:
//
//    inputstream : The input stream to read the image from
//    image : The image object to return
//
void PngDecoder::readInterlaced (PngInputStream &inputstream, BitmapImage &image)
{
  const InterlaceInfo *info = &interlace_values [interlace_pass] ;

  // If the image is too small we may not have to do any work.
  if (info->start_row >= image_height || info->start_col  >= image_width)
    return ;

  current_row_buffer = 0 ;
  fill (row_buffers [0], row_buffers [0] + row_buffer_width, 0) ;
  fill (row_buffers [1], row_buffers [1] + row_buffer_width, 0) ;

  unsigned int pixelsthisrow = (image_width - info->start_col
                                + info->col_interval - 1)
                               / info->col_interval ;
  unsigned int rowbytes ;
  switch (image_color_type)
  {
  case Grayscale: case Palette:
    rowbytes = (pixelsthisrow * image_depth + CHAR_BIT - 1) / CHAR_BIT ;
    break ;
  case RGB:
    rowbytes = pixelsthisrow * 3 * image_depth / CHAR_BIT ;
    break ;
  case RGBAlpha:
    rowbytes = pixelsthisrow * 4 * image_depth / CHAR_BIT ;
    break ;
  case GrayscaleAlpha:
    rowbytes = pixelsthisrow * 2 * image_depth / CHAR_BIT ;
    break ;
  default:
    throw BadPngStream ("Invalid Color Type") ;
  }
  for (unsigned int destrow = info->start_row ;
       destrow < image_height ;
       destrow += info->row_interval)
  {

    UBYTE1 filterbuffer ;
    // The filter type precedes the row data.
    inflate_decoder.decode (inputstream, sizeof (filterbuffer), &filterbuffer) ;
    PngFilterType filter = static_cast <PngFilterType>(filterbuffer) ;
    // Read the row data.
    inflate_decoder.decode (inputstream, rowbytes, row_buffers [current_row_buffer]) ;
    // Filter the data
    filterRow (filter) ;

    copyInterlacedRowToImage (destrow, rowbytes, image) ;

    callProgressFunction (100 * destrow / image_height) ;
    current_row_buffer = ! current_row_buffer ;  // Switch buffers
  }

  callProgressFunction (100) ;
  return ;
}

//
//  Description:
//
//    This function copies an interlaced row to the output image.
//
//  Parameters:
//
//    row: The output row number
//    rowwidth: The number of bytes of data in the row
//    image : The image to output
//
void PngDecoder::copyInterlacedRowToImage (unsigned int row,
                                           unsigned int rowwidth,
                                           BitmapImage &image)
{
  const InterlaceInfo *info = &interlace_values [interlace_pass] ;

  BitmapImage::Pixel *pixel = &image [row * image_width] ;
  switch (image_color_type)
  {
  case Grayscale: case Palette:
    switch (image_depth)
    {
    case 1:
      {
        for (unsigned int col = 0, destcol = info->start_col ;
             col < rowwidth ;
             ++ col)
        {
          for (int ii = CHAR_BIT - 1 ;
               ii >= 0 && destcol < image_width ;
               -- ii, destcol += info->col_interval)
          {
            UBYTE1 value = (row_buffers [current_row_buffer][col] >> ii) & 0x1 ;
            pixel [destcol].red   = color_palette [value].red ;
            pixel [destcol].green = color_palette [value].green ;
            pixel [destcol].blue  = color_palette [value].blue ;
            pixel [destcol].alpha = 0xFF ;
          }
        }
      }
      break ;

    case 2:
      {
        // 2 Bits per pixel gets converted to 8 Bits.
        for (unsigned int col = 0, destcol = info->start_col ;
             col < rowwidth ;
             ++ col)
        {
          for (int ii = 3 * CHAR_BIT/4 ;
               ii >= 0 && destcol < image_width ;
               ii -= CHAR_BIT/4 , destcol += info->col_interval)
          {
            UBYTE1 value = (row_buffers [current_row_buffer][col] >> ii) & 0x3 ;
            pixel [destcol].red   = color_palette [value].red ;
            pixel [destcol].green = color_palette [value].green ;
            pixel [destcol].blue  = color_palette [value].blue ;
            pixel [destcol].alpha = 0xFF ;
          }
        }
      }
      break ;

    case 4:
      {
        for (unsigned int col = 0, destcol = info->start_col ;
             col < rowwidth ;
             ++ col)
        {
          for (int ii = CHAR_BIT/2 ;
               ii >= 0 && destcol < image_width ;
               ii -= CHAR_BIT/2, destcol += info->col_interval)
          {
            UBYTE1 value = (row_buffers [current_row_buffer][col] >> ii) & 0xF ;
            pixel [destcol].red   = color_palette [value].red ;
            pixel [destcol].green = color_palette [value].green ;
            pixel [destcol].blue  = color_palette [value].blue ;
            pixel [destcol].alpha = 0xFF ;
          }
        }
      }
      break ;

    case 8:
      {
        for (unsigned int col = 0, destcol = info->start_col ;
             col < rowwidth ;
             ++ col, destcol += info->col_interval)
        {
          UBYTE1 value = row_buffers [current_row_buffer][col] ;
          pixel [destcol].red   = color_palette [value].red ;
          pixel [destcol].green = color_palette [value].green ;
          pixel [destcol].blue  = color_palette [value].blue ;
          pixel [destcol].alpha = 0xFF ;
        }
      }
      break ;
    case 16:
      {
        for (unsigned int col = 0, destcol = info->start_col ;
             col < rowwidth ;
             col += 2, destcol += info->col_interval)
        {
          UBYTE1 value = row_buffers [current_row_buffer][col] ;
          pixel [destcol].red   = color_palette [value].red ;
          pixel [destcol].green = color_palette [value].green ;
          pixel [destcol].blue  = color_palette [value].blue ;
          pixel [destcol].alpha = 0xFF ;
        }
      }
      break ;

    default:
      throw BadPngStream ("Invalid Image Depth") ;
    }
    break ;

  case RGB:
    {
      if (image_depth == 8)
      {
        for (unsigned int col = 0, destcol = info->start_col ;
             col < rowwidth ;
             destcol += info->col_interval)
        {
          pixel [destcol].red   = row_buffers [current_row_buffer][col] ; ++ col ;
          pixel [destcol].green = row_buffers [current_row_buffer][col] ; ++ col ;
          pixel [destcol].blue  = row_buffers [current_row_buffer][col] ; ++ col ;
          pixel [destcol].alpha = 0xFF ;
        }
      }
      else if (image_depth == 16)
      {
        for (unsigned int col = 0, destcol = info->start_col ;
             col < rowwidth ;
             destcol += info->col_interval)
        {
          pixel [destcol].red   = row_buffers [current_row_buffer][col] ; col += 2 ;
          pixel [destcol].green = row_buffers [current_row_buffer][col] ; col += 2 ;
          pixel [destcol].blue  = row_buffers [current_row_buffer][col] ; col += 2 ;
          pixel [destcol].alpha = 0xFF ;
        }
      }
      else
      {
        throw BadPngStream ("Invalid Image Depth") ;
      }
    }
    break ;

  case RGBAlpha:
    {
      for (unsigned int col = 0, destcol = info->start_col ;
           col < rowwidth ;
           destcol += info->col_interval)
      {
        pixel [destcol].red = row_buffers [current_row_buffer][col] ;
        col += image_depth / CHAR_BIT ;
        pixel [destcol].green = row_buffers [current_row_buffer][col] ;
        col += image_depth / CHAR_BIT ;
        pixel [destcol].blue = row_buffers [current_row_buffer][col] ;
        col += image_depth / CHAR_BIT ;
        pixel [destcol].alpha = row_buffers [current_row_buffer][col] ;
        col += image_depth / CHAR_BIT ;
      }
    }
    break ;
  case GrayscaleAlpha:
    {
      for (unsigned int col = 0, destcol = info->start_col ;
           col < rowwidth ;
           destcol += info->col_interval)
      {
        pixel [destcol].red = color_palette [row_buffers [current_row_buffer][col]].red ;
        pixel [destcol].green = color_palette [row_buffers [current_row_buffer][col]].green ;
        pixel [destcol].blue = color_palette [row_buffers [current_row_buffer][col]].blue ;
        col += image_depth / CHAR_BIT ;
        pixel [destcol].alpha = row_buffers [current_row_buffer][col] ;
        col += image_depth / CHAR_BIT ;
      }
    }
    break ;
  default:
    throw BadPngStream ("Invalid Color Type") ;
  }
  return ;
}

//
//  Description:
//
//    This function processes a bKGD chuck. This chunk defines the background
//    color for the image. We only use this for Alpha channel processing.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processBackground (PngInputStream &inputstream)
{
  const UBYTE1 *chunkdata = inputstream.getChunkData () ;
  switch (image_color_type)
  {
  case Grayscale: case GrayscaleAlpha:  case Palette:
    if (inputstream.getChunkDataLength () == 0)
      throw BadPngStream ("bKGD Chunk too small") ;
    background_gray = chunkdata [0] ;
    if (background_gray >= (1<<image_depth))
      throw BadPngStream ("bKGD palette index too large") ;
    if (verbose_flag)
    {
      cout << "Background Grayscale: " << background_gray << endl ;
    }
    break ;

  case RGB: case RGBAlpha:
    if (inputstream.getChunkDataLength () < 6)
      throw BadPngStream ("bKGD Chunk too small") ;
    background_red = chunkdata [0] ;
    background_green = chunkdata [2] ;
    background_blue = chunkdata [4] ;
    if (verbose_flag)
    {
      cout << "Background RGB: " << background_red << " " << background_green
           << background_blue << endl ;
    }
    break ;

  default:
    throw BadPngStream ("Invalid Color Type") ;
  }
  return ;
}

//
//  Description:
//
//    This function processes a gAMA chunk. The game value is stored
//    as a 4-byte integer which is the Gamma value times 100,000.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processGamma (PngInputStream &inputstream)
{
  if (palette_read)
    throw BadPngStream ("gAMA chunk may not occur before PLTE") ;
  if (data_read)
    throw BadPngStream ("gAMA chunk may not occur before IDAT") ;

  UBYTE4 value ;
  if (inputstream.getChunkDataLength () < 4)
    throw BadPngStream ("gAMA chunk too small") ;
  const UBYTE1 *chunkdata = inputstream.getChunkData () ;
  value = chunkdata [0] | (chunkdata [1] << CHAR_BIT)
                        | (chunkdata [2] << (2*CHAR_BIT))
                        | (chunkdata [3] << (3*CHAR_BIT)) ;
  value = BigEndianToSystem (value) ;
  file_gamma = (double) value / 100000.0 ;
  if (verbose_flag)
    cout << " Gamma: " << file_gamma << endl ;
  return ;
}

//
//  Description:
//
//    This function processes a cHRM chunk. This chunk defines
//    precise color values for the image.
//
//    We do nothing with this chunk but dump its contents.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processChromaticities (PngInputStream &inputstream)
{
  if (palette_read)
    throw BadPngStream ("cHRM chunk may not occur after PLTE") ;
  if (data_read)
    throw BadPngStream ("cHRM chunk may not occur after IDAT") ;

  if (inputstream.getChunkDataLength () != sizeof (PngChromaticitiesData))
    throw BadPngStream ("Invalid cHRM chunk length") ;

  const PngChromaticitiesData *data =
    reinterpret_cast <const PngChromaticitiesData *> (inputstream.getChunkData ()) ;

  UBYTE4 whitepointx = BigEndianToSystem (data->whitepointx) ;
  UBYTE4 whitepointy = BigEndianToSystem (data->whitepointy) ;
  UBYTE4 redx = BigEndianToSystem (data->redx) ;
  UBYTE4 redy = BigEndianToSystem (data->redy) ;
  UBYTE4 greenx = BigEndianToSystem (data->greenx) ;
  UBYTE4 greeny = BigEndianToSystem (data->greeny) ;
  UBYTE4 bluex = BigEndianToSystem (data->bluex) ;
  UBYTE4 bluey = BigEndianToSystem (data->bluey) ;

  if (verbose_flag)
  {
    cout << "  White Point X: " << (double) whitepointx / 100000.0
         << endl ;
    cout << "  White Point y: " << (double) whitepointy / 100000.0
         << endl ;
    cout << "  Red X:         " << (double) redx / 100000.0 << endl ;
    cout << "  Red Y:         " << (double) redy / 100000.0 << endl ;
    cout << "  Green X:       " << (double) greenx / 100000.0 << endl ;
    cout << "  Green Y:       " << (double) greeny / 100000.0 << endl ;
    cout << "  Blue X:        " << (double) bluex / 100000.0 << endl ;
    cout << "  Blue Y:        " << (double) bluey / 100000.0 << endl ;
  }
  return ;
}

//
//  Description:
//
//    Tais function processes an hIST chunk. This chunk defines the frequency
//    that each color in the palette is used within the imamge. This
//    information can be used to assist in quantization.
//
//    We do nothing with this chunk but dump its contents.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processHistogram (PngInputStream &inputstream)
{
  if (! palette_read)
    throw BadPngStream ("hIST chunk may not appear before PLTE") ;
  if (data_read)
    throw BadPngStream ("hIST chunk must appear before IDAT") ;

  if (inputstream.getChunkDataLength ()  != 2 * palette_size)
    throw BadPngStream ("Bad size for hIST chunk") ;

  const UBYTE2 *values = reinterpret_cast <const UBYTE2 *> (inputstream.getChunkData ()) ;

  if (verbose_flag)
  {
    for (unsigned int ii = 0 ; ii < palette_size ; ++ ii)
    {
      cout << "  " << ii << ") " << values [ii] << endl ;
    }
  }

  return ;
}

//
//  Description:
//
//    This function processes the pHYs chunk. This chunk defines the
//    dimensions for the image pixels.
//
//    We do nothing with this chunk except print its contents.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processPhysicalPixelDimensions (PngInputStream &inputstream)
{
  if (data_read)
    throw BadPngStream ("pHYS chunk must come before IDAT chunks") ;

  if (inputstream.getChunkDataLength () != sizeof (PngPixelDimensions))
    throw BadPngStream ("pHYs chunk size invalid") ;

  const PngPixelDimensions *pd 
          = reinterpret_cast <const PngPixelDimensions *> (inputstream.getChunkData ()) ;
  if (pd->unit > 1)
    throw BadPngStream ("pHYs contains an invalid unit") ;

  static const char *unitstrings [2] = { "unknown unit", "meter" } ;
  if (verbose_flag)
  {
    cout << "  " << pd->pixelsx << " per " << unitstrings [pd->unit] << endl ;
    cout << "  " << pd->pixelsy << " per " << unitstrings [pd->unit] << endl ;
    cout << "  Unit: " << pd->unit << endl ;
  }
  return ;
}

//
//  Description:
//
//    This function processes the sBIT chunk. This chunk can be used to
//    set the number of significant bits used in color values.
//
//    We do nothing with this chunk but print its contents.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processSignificantBits (PngInputStream &inputstream)
{
  if (data_read)
    throw BadPngStream ("sBIT chunk must occur before IDAT chunks") ;
  if (palette_read)
    throw BadPngStream ("sBIT chunk must occur before PTLE chunk") ;

  const UBYTE1 *chunkdata = inputstream.getChunkData () ;

  switch (image_color_type)
  {
  case Grayscale:
    {
      if (inputstream.getChunkDataLength () < 1)
        throw BadPngStream ("sBIT chunk length invalid") ;

      if (verbose_flag)
        cout << "  Significant Bits: " << static_cast <int>(chunkdata [0]) << endl ;
    }
    break ;
  case Palette: case RGB:
    {
      if (inputstream.getChunkDataLength () < 3)
        throw BadPngStream ("sBIT chunk length invalid") ;

      if (verbose_flag)
      {
        cout << " Significant Red Bits: " << static_cast <int>(chunkdata [0]) << endl ;
        cout << " Significant Green Bits: " << static_cast <int>(chunkdata [1]) << endl ;
        cout << " Significant Blue Bits: " << static_cast <int>(chunkdata [2]) << endl ;
      }
    }
    break ;
  case GrayscaleAlpha:
    {
      if (inputstream.getChunkDataLength () < 2)
        throw BadPngStream ("sBIT chunk length invalid") ;

      if (verbose_flag)
      {
        cout << " Significant Bits: " << static_cast <int>(chunkdata [0]) << endl ;
        cout << " Significant Alpha Bits: " << static_cast <int>(chunkdata [1]) << endl ;
      }
    }
    break ;
  case RGBAlpha:
    {
      if (inputstream.getChunkDataLength () < 4)
        throw BadPngStream ("sBIT chunk length invalid") ;

      if (verbose_flag)
      {
        cout << " Significant Red Bits: " << static_cast <int>(chunkdata [0]) << endl ;
        cout << " Significant Green Bits: " << static_cast <int>(chunkdata [1]) << endl ;
        cout << " Significant Blue Bits: " << static_cast <int>(chunkdata [2]) << endl ;
        cout << " Significant Alpha Bits: " << static_cast <int>(chunkdata [3]) << endl ;
      }
    }
    break ;
  default:
    throw BadPngStream ("Invalid Color Type") ;
  }
  return ;
}

//
//  Description:
//
//    This function processes the tEXt chunk. This chunk stores text
//    information in the image.
//
//    We do nothing with the chunk except print its contents.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processTextualData (PngInputStream &inputstream)
{
  bool end_found = false ;
  unsigned int offset ;
  unsigned int datalength = inputstream.getChunkDataLength () ;
  const UBYTE1 *chunkdata = inputstream.getChunkData () ;
  for (offset = 0 ;
       offset < datalength && offset < 80 ;
       ++ offset)
  {
    if (chunkdata [offset] == '\000')
    {
      end_found = true ;
      break ;
    }
  }
  if (! end_found)
    throw BadPngStream ("tEXt keyword not found") ;

  if (verbose_flag)
  {
    cout << " Keyword: '" << chunkdata << "'" << endl ;
    cout << " Value: '" ;
    for (unsigned int ii = offset + 1 ; ii < datalength ; ++ ii)
      cout << chunkdata [ii] ;
    cout << "'" << endl ;
  }
  return ;
}

//
//  Description:
//
//    This function processes the tIME chunk. This chunk stores the last time
//    the image was modified.
//
//    We do nothing with the chunk except print its contents.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processImageTime (PngInputStream &inputstream)
{
  if (inputstream.getChunkDataLength () != sizeof (PngTimeData))
    throw BadPngStream ("tIME chunk size invalid") ;

  if (verbose_flag)
  {
    const PngTimeData *td = reinterpret_cast<const PngTimeData *>(inputstream.getChunkData ()) ;

    cout << "  Year: " << td->year << endl ;
    cout << "  Month: " << td->month << endl ;
    cout << "  Day: " << td->day << endl ;
    cout << "  Hour: " << td->hour << endl ;
    cout << "  Minute: " << td->minute << endl ;
    cout << "  Second: " << td->second << endl ;
  }
  return ;
}

//
//  Description:
//
//    This function processes the tRNS chunk. This chunk allows transparency
//    to be define for color types without an alpha channel.
//
//    We do nothing with the chunk except print its contents.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processTransparency (PngInputStream &inputstream)
{
  if (data_read)
    throw BadPngStream ("tRNS chunk cannot occur before IDAT chunks") ;

  const UBYTE1 *chunkdata = inputstream.getChunkData () ;
  const UBYTE2 *wordvalues = reinterpret_cast <const UBYTE2 *> (chunkdata) ;

  if (verbose_flag)
  {
    switch (image_color_type)
    {
    case Palette:
      {
        if (! palette_read)
          throw BadPngStream ("tRNS chunk must occur after PLTE chunk") ;

        if (palette_size != inputstream.getChunkDataLength ())
          throw BadPngStream ("tRNS block length invalid") ;

        for (unsigned int ii = 0 ; ii < inputstream.getChunkDataLength () ; ++ ii)
          cout << ii << ") " << chunkdata [ii] << endl ;
      }
      break ;
    case Grayscale:
      {
        if (inputstream.getChunkDataLength () < 2)
          throw BadPngStream ("tRNS chunk length invalid") ;
        cout << "  Transparency: " << BigEndianToSystem (wordvalues [0]) << endl ;
      }
      break ;
    case RGB:
      {
        if (inputstream.getChunkDataLength () < 6)
          throw BadPngStream ("tRNS chunk length invalid") ;
        cout << "  Red Transparency: "
             << BigEndianToSystem (wordvalues [0]) << endl ;
        cout << "  Green Transparency: "
             << BigEndianToSystem (wordvalues [1]) << endl ;
        cout << "  Blue Transparency: "
             << BigEndianToSystem (wordvalues [2]) << endl ;
      }
      break ;
    default:
      throw BadPngStream ("Invalid Color Type of tRNS chunk") ;
    }
  }
  return ;
}

//
//  Description:
//
//    This function processes the zTXt chunk. This chunk stores text
//     information.  This chunk is similar to a tEXt chunk except that the
//    data is compressed.
//
//    We do nothing with the chunk except print its contents.
//
//  Parameters:
//
//    inputstream : The stream to read the chunk data from
//    image : The output image
//
void PngDecoder::processCompressedText (PngInputStream &inputstream)
{
  bool end_found = false ;
  unsigned int offset ;
  const UBYTE1 *chunkdata = inputstream.getChunkData () ;
  unsigned int datalength = inputstream.getChunkDataLength () ;

  for (offset = 0 ;
       offset < datalength && offset < 80 ;
       ++ offset)
  {
    if (chunkdata [offset] != '\000')
    {
      end_found = true ;
      break ;
    }
  }
  if (! end_found)
    throw BadPngStream ("zEXt keyword not found") ;

  if (verbose_flag)
  {
    cout << "Keyword: '" << chunkdata << "'" << endl ;
    cout << "Compression Method: " << chunkdata [offset + 1] << endl ;
  }
  return ;
}

//
//  Description:
//
//    This function reads a PNG file.
//
//  Parameters:
//
//    filename : The name of the file to read.
//    image : The image object to read into.
//
void PngDecoder::readImageFile (const std::string &filename, BitmapImage &image)
{
#if ! defined (USEMAP)
  PngInputFileStream input ;
#else
  PngInputMapStream input ;
#endif
  input.open (filename) ;
  if (! input)
  {
    string msg = string ("Can't open input file '") + filename + string ("'\n")
               + string (strerror (errno)) ;
    throw PngError (msg.c_str ()) ;
  }
  readImage (input, image) ;
  return ;
}

void PngDecoder::setVerbose (bool value)
{
  verbose_flag = value ;
  return ;
}

bool PngDecoder::getVerbose () const
{
  return verbose_flag ;
}

//
//  Description:
//
//    This function calls the progress function if it has been
//    defined.
//
//  Parameters:
//    percent:  The completion percentage (0..100)
//
void PngDecoder::callProgressFunction (unsigned int percent)
{
  if (progress_function == 0)
    return ;

  if (percent > 100)
    percent = 100 ;

  bool cancel = false ;
  if (image_interlace_method == 0)
  {
    progress_function (*this, progress_data, 1, 1,
                       percent, cancel) ;
  }
  else
  {
    if (interlace_pass == InterlacePasses)
    {
      progress_function (*this, progress_data, interlace_pass,
                         InterlacePasses,
                         percent, cancel) ;
    }
    else
    {
      progress_function (*this, progress_data, interlace_pass + 1,
                         InterlacePasses,
                         percent, cancel) ;
    }
  }
  return ;
}

} // End Namespace Colosseum
