//
// 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
//

//
//  Title:  PNG Encoder Class Implementation
//
//  Author:  John M. Miano miano@colosseumbuilders.com
//

#include <string>
#include "pngencoder.h"
#include "pngpvt.h"
#include "pngoutputfilestream.h"

using namespace std ;
using namespace ColosseumPrivate ;

namespace Colosseum
{

//
//  Description:
//
//    Class Default Constructor
//
PngEncoder::PngEncoder ()
: row_buffer (0),
  software_string ("Colosseum Builders Image Library"),
  filter_mask ((1 << FILTERNONE)),
  use_alpha_channel (false),
  processing_image (false)
{
  memset (filter_buffers, 0, sizeof (filter_buffers)) ;
  return ;
}

//
//  Description:
//
//    Class Copy Constructor
//
PngEncoder::PngEncoder (const PngEncoder &source)
: BitmapImageEncoder (source),
  title_string (source.title_string),
  author_string (source.author_string),
  description_string (source.description_string),
  copyright_string (source.copyright_string),
  software_string (source.software_string),
  disclaimer_string (source.disclaimer_string),
  warning_string (source.warning_string),
  source_string (source.source_string),
  comment_string (source.comment_string),
  filter_mask (source.filter_mask),
  row_buffer (0),
  use_alpha_channel (source.use_alpha_channel),
  processing_image (false)
{
  memset (filter_buffers, 0, sizeof (filter_buffers)) ;
  return ;
}

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

//
//  Descriptor:
//
//    Assignment Operator
//
//  Parameters:
//    source: The object to copy
//
PngEncoder &PngEncoder::operator=(const PngEncoder &source)
{
  if (this == &source)
    return *this ;

  title_string = source.title_string ;
  author_string = source.author_string ;
  description_string = source.description_string ;
  copyright_string = source.copyright_string ;
  software_string = source.software_string ;
  disclaimer_string = source.disclaimer_string ;
  warning_string = source.warning_string ;
  source_string = source.source_string ;
  comment_string = source.comment_string ;
  filter_mask = source.filter_mask ;
  use_alpha_channel = source.use_alpha_channel ;
  BitmapImageEncoder::operator=(source) ;
  return *this ;
}

//
//  Description:
//
//    This function writes an image to a PNG stream.
//
//  Parameters:
//    strm:  The output stream
//    image:  The image to output
//
void PngEncoder::writeImage (PngOutputStream &outputstream, const BitmapImage &image)
{
  filter_mask |= (1 << FILTERNONE) ; // Require the none filter.
  processing_image = true ;
  try
  {
    doWrite (outputstream, image) ;
  }
  catch (DeflateEncoder::DeflateError &error)
  {
    processing_image = false ;
    freeBuffers () ;
    throw PngError (error.what ()) ;
  }
  catch (...)
  {
    processing_image = false ;
    freeBuffers () ;
    throw ;
  }
  processing_image = false ;
  freeBuffers () ;
  return ;
}

//
//  Description:
//
//    This function frees the buffers allowed during the encoding
//    process.
//
void PngEncoder::freeBuffers ()
{
  for (unsigned int ii = 0 ; ii < FILTERBUFFERCOUNT ; ++ ii)
  {
    delete [] filter_buffers [ii] ; filter_buffers [ii] = 0 ;
  }
  delete [] row_buffer ; row_buffer = 0 ;
  return ;
}

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

  if (percent > 100)
    percent = 100 ;

  bool cancel = false ;
  progress_function (*this, progress_data, 1, 1,
                     percent, cancel) ;
  return ;
}

//
//  Description:
// 
//    This function creates a tEXt chunk.
//
//  Parameters:
//    keyword:  The chunk keyword
//    value:  The keyword value
//
void PngEncoder::writeText (PngOutputStream &outputstream,
                            const std::string &keyword,
                            const std::string &value)
{
  if (keyword.length () > 79)
    throw PngError ("tEXt Keyword Too Long") ;

  outputstream.startChunk ("tEXt") ;
  outputstream.write (keyword.c_str (), keyword.length ()) ;
  outputstream.writeByte (0) ;
  if (value.length () > outputstream.remainingBufferSpace ())
    throw PngError ("tEXt Value Too Long") ;

  outputstream.write (value.c_str (), value.length ()) ;
  outputstream.endChunk () ;
  return ;
}

//
//  Description:
//
//    This function outputs tEXt blocks for any keywords
//    that have values assigned to them.
//
//  Parameters:
//
//    outputstream : The stream to write to
//
void PngEncoder::writeTextBlocks (PngOutputStream &outputstream)
{
  if (title_string != "")
    writeText (outputstream, "Title", title_string) ;
  if (author_string != "")
    writeText (outputstream, "Author", author_string) ;
  if (description_string != "")
    writeText (outputstream, "Description", description_string) ;
  if (copyright_string != "")
    writeText (outputstream, "Copyright", copyright_string) ;
  if (software_string != "")
    writeText (outputstream, "Software", software_string) ;
  if (disclaimer_string != "")
    writeText (outputstream, "Disclaimer", disclaimer_string) ;
  if (warning_string != "")
    writeText (outputstream, "Warning", warning_string) ;
  if (source_string != "")
    writeText (outputstream, "Source", source_string) ;
  if (comment_string != "")
    writeText (outputstream, "Comment", comment_string) ;
  return ;
}

//
//  Description:
//
//    This function sets the compression level used.  The compression level
//    determines the depth to which hash chains are searched.
//
//  Parameters:
//
//    value:  The new compression level
//
void PngEncoder::setCompressionLevel (DeflateEncoder::CompressionLevel value)
{
  deflate_encoder.setCompressionLevel (value) ;
  return ;
}

//
//  Description:
//
//    The function returns the current compression level.
//
//  Return Value:
//
//    The compresion level.
//
DeflateEncoder::CompressionLevel PngEncoder::getCompressionLevel () const
{
  DeflateEncoder::CompressionLevel result =   deflate_encoder.getCompressionLevel () ;
  return result ;
}

//
//  Description:
//
//    This function performs the actual output of the image.
//
//  Parameters:
//
//    outputstream : The stream to write the image to
//    image : The image to output
//
void PngEncoder::doWrite (PngOutputStream &outputstream, const BitmapImage &image)
{
  // Needed because MSVC++ does not follow standard scoping rules
  // in for statements.
  unsigned int ii ;

  if (use_alpha_channel)
  {
    row_width = 4 * image.getWidth () ;
    filter_width = 4 ;
  }
  else
  {
    row_width = 3 * image.getWidth () ;
    filter_width = 3 ;
  }
  row_buffer = new UBYTE1 [row_width] ;

  for (ii = 0 ; ii < FILTERBUFFERCOUNT ; ++ ii)
  {
    if (((1 << ii) & filter_mask) != 0)
      filter_buffers [ii] = new UBYTE1 [row_width] ;
  }
  fill (filter_buffers [FILTERNONE], filter_buffers [FILTERNONE] + row_width, 0) ;

  // Fill in the image header.
  PngImageHeader header ;
  header.width = SystemToBigEndian (static_cast<UBYTE4>(image.getWidth ())) ;
  header.height = SystemToBigEndian (static_cast<UBYTE4>(image.getHeight ())) ;
  header.bitdepth = 8 ;
  if (use_alpha_channel)
    header.colortype = RGBAlpha ;
  else
    header.colortype = RGB ;

  header.compressionmethod = 0 ;
  header.filtermethod = 0 ;
  header.interlacemethod = 0 ;

  // Output the PNG Signature and header.
  outputstream.writeRaw (reinterpret_cast<const char*>(PngSignature), PngSignatureSize) ;
  outputstream.startChunk ("IHDR") ;
  outputstream.write (reinterpret_cast<const char*>(&header), sizeof (header)) ;
  outputstream.endChunk () ;

  // Output any text blocks with keywords defined.
  writeTextBlocks (outputstream) ;

  writeImageData (outputstream, image) ;

  // Write the IEND marker.
  outputstream.startChunk ("IEND") ;
  outputstream.endChunk () ;

  return ;
}

//
//  Description:
//
//    This function filters an image row.
//
//  Implicit Inputs
//
//      row_buffer contains the row to filter
//      filter_buffers [FILTERNONE] contains the last row
//
void PngEncoder::filterRow ()
{
  ASSERT (filter_mask != 0) ;

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

  unsigned int mask = (1 << FILTERNONE) ;

  // Filter for each type in the filter_mask.
  if ((filter_mask & (1 << FILTERSUB)) != 0)
  {
    mask |= (1 << FILTERSUB) ;

    UBYTE1 last ;
    for (unsigned int ii = 0 ; ii < row_width ; ++ ii)
    {
      if (ii >= filter_width)
        last = row_buffer [ii-filter_width] ;
      else
        last = 0 ;

      filter_buffers [FILTERSUB][ii] = row_buffer [ii] - last ;
    }
  }

  if ((filter_mask & (1 << FILTERUP)) != 0)
  {
    mask |= (1 << FILTERUP) ;

    for (unsigned int ii = 0 ; ii < row_width ; ++ ii)
    {
      filter_buffers [FILTERUP][ii] = row_buffer [ii] - filter_buffers [FILTERNONE][ii] ;
    }
  }

  if ((filter_mask & (1 << FILTERAVERAGE)) != 0)
  {
    mask |= (1 << FILTERAVERAGE) ;

    UBYTE1 last ;
    UBYTE1 above ;
    for (unsigned int ii = 0 ; ii < row_width ; ++ ii)
    {
      if (ii >= filter_width)
        last = row_buffer [ii - filter_width] ;
      else
        last = 0 ;
      above = filter_buffers [FILTERNONE][ii] ;

      filter_buffers [FILTERAVERAGE][ii] 
        = row_buffer [ii] - (above + last) / 2 ;
    }
  }

  if ((filter_mask & (1 << FILTERPAETH)) != 0)
  {
    mask |= (1 << FILTERPAETH) ;

    UBYTE1 last ;
    UBYTE1 lastabove ;
    UBYTE1 above ;
    for (unsigned int ii = 0 ; ii < row_width ; ++ ii)
    {
      if (ii >= filter_width)
      {
        last = row_buffer [ii-filter_width] ;
        lastabove = filter_buffers [FILTERNONE][ii - filter_width] ;
      }
      else
      {
        last = 0 ;
        lastabove = 0 ;
      }
      above = filter_buffers [FILTERNONE][ii] ;
      filter_buffers [FILTERPAETH][ii] 
        = row_buffer [ii] - PaethPredictor (last, above, lastabove) ;
    }
  }


  // Filter None
  // THIS MUST BE THE LAST FILTER!!!!!!!!!! We save the value
  // here to be used in the next call with the filters that require data from the
  // previous row.
BILLSELLSPOOP
  for (unsigned int ii = 0 ; ii < row_width ; ++ ii)
    filter_buffers [FILTERNONE][ii] = row_buffer [ii] ;
ENDBILLSELLSPOOP

  // If we only performed FilterNone then we do not need to proceed
  // any further.  
  current_filter = FILTERNONE ;
  if (mask == (1 << FILTERNONE))
    return ;

  // Find the best filter. We do a simple test for the
  // longest runs of the same value.

  unsigned int longestrun = 0 ;
  for (ii = 0 ; ii < FILTERBUFFERCOUNT ; ++ ii)
  {
    if ((mask & (1<<ii)) != 0)
    {
      unsigned int run = 0 ;
      for (unsigned int jj = 4 ; jj < row_width ; ++ jj)
      {
        if (filter_buffers [ii][jj] == filter_buffers [ii][jj-1]
            && filter_buffers [ii][jj] == filter_buffers [ii][jj-2]
            && filter_buffers [ii][jj] == filter_buffers [ii][jj-3]
            && filter_buffers [ii][jj] == filter_buffers [ii][jj-4])
        {
          ++ run ;
        }
      }

      if (run > longestrun)
      {
        current_filter = ii ;
        longestrun = run ;
      }
    }
  }
  return ;
}

//
//  Description:
//
//    This function specifies whether or not filters are used.
//
//  Parameters:
//
//    value: true=>Use Filters, false=>Don't use filters
//
void PngEncoder::setUseFilters (bool value)
{
  if (value)
    filter_mask = 0xFFFFFFFF ;
  else
    filter_mask = (1<<FILTERNONE) ;
}

//
//  Description:
//
//   This function tells if filters are being used.
//
//  Return Value:
//    true=>Filters are being used
//    false=>Filters are not being used
//
bool PngEncoder::getUseFilters () const
{
  if (filter_mask == 0x1)
    return false ;
  else
    return true ;
}

//
//  Description:
//
//    This function writes the image data to the output stream.
//
//  Parameters:
//
//    outputstream : The stream to write to
//    image : The image to output
//
void PngEncoder::writeImageData (PngOutputStream &outputstream, 
                                 const BitmapImage &image)
{
  outputstream.startChunk ("IDAT") ;
  deflate_encoder.startCompressedStream (outputstream) ;
  callProgressFunction (0) ;


  const BitmapImage::Pixel *pixel = &image [0] ;
  for (unsigned int ii = 0 ; ii < image.getHeight () ; ++ ii)
  {
    for (unsigned int jj = 0, col = 0 ; jj < image.getWidth () ; ++ jj, ++ pixel)
    {
      row_buffer [col] = pixel->red ; ++ col ;
      row_buffer [col] = pixel->green ; ++ col ;
      row_buffer [col] = pixel->blue ; ++ col ;
      if (use_alpha_channel)
      {
        row_buffer [col] = pixel->alpha ;
        ++ col ;
      }
    }
    filterRow () ;
    UBYTE1 filter = current_filter ;
    deflate_encoder.compressData (outputstream,
                                  reinterpret_cast<const char*>(&filter),
                                  sizeof (filter)) ;
    deflate_encoder.compressData (outputstream,
                                  reinterpret_cast<const char*>(filter_buffers [current_filter]),
                                  row_width) ;
    callProgressFunction (100 * ii / image.getHeight ()) ;
  }
  deflate_encoder.endCompressedStream (outputstream) ;
  outputstream.endChunk () ;
  callProgressFunction (100) ;
  return ;
}

//
//  Description:
//
//    This function writes a image to a PNG file.
//
//  Parameters:
//
//    filename : The name of the output file
//    image : The image to compress
//
void PngEncoder::writeImageFile (const std::string &filename, const BitmapImage &image) 
{
  PngOutputFileStream outputstream ;
  outputstream.open (filename.c_str ()) ;
  if (! outputstream)
    throw PngError ("Can't open output file") ;
  writeImage (outputstream, image) ;
  return ;
}
//
//  Description:
//
//    This function defines the block size used by the Deflate encoder.
//
//  Parameters:
//
//    value : The new block size
//
void PngEncoder::setBlockSize (unsigned long value)
{
  deflate_encoder.setBlockSize (value) ;
  return ;
}

void PngEncoder::setTitle (const std::string &value)
{
  title_string = value ;
  return ;
}

std::string PngEncoder::getTitle () const
{
  return title_string ;
}

void PngEncoder::setAuthor (const std::string &value)
{
  author_string = value ;
  return ;
}

std::string PngEncoder::getAuthor () const
{
  return author_string ;
}

void PngEncoder::setDescription (const std::string &value)
{
  description_string = value ;
  return ;
}

std::string PngEncoder::getDescription () const
{
  return description_string ;
}

void PngEncoder::setCopyright (const std::string &value)
{
  copyright_string = value ;
  return ;
}

std::string PngEncoder::getCopyright () const
{
  return copyright_string ;
}

void PngEncoder::setSoftware (const std::string &value)
{
  software_string = value ;
  return ;
}

std::string PngEncoder::getSoftware () const
{
  return software_string ;
}

void PngEncoder::setDisclaimer (const std::string &value)
{
  disclaimer_string = value ;
  return ;
}

std::string PngEncoder::getDisclaimer () const
{
  return disclaimer_string ;
}

void PngEncoder::setWarning (const std::string &value)
{
  warning_string = value ;
  return ;
}

std::string PngEncoder::getWarning () const
{
  return warning_string ;
}

void PngEncoder::setSource (const std::string &value)
{
  source_string = value ;
  return ;
}

std::string PngEncoder::getSource () const
{
  return source_string ;
}

void PngEncoder::setComment (const std::string &value)
{
  comment_string = value ;
  return ;
}

std::string PngEncoder::getComment () const
{
  return comment_string ;
}

bool PngEncoder::getUseAlphaChannel () const
{
  return use_alpha_channel ;
}

void PngEncoder::setUseAlphaChannel (bool value)
{
  if (processing_image)
    throw PngError ("Attempt to change parameters during compression") ;

  use_alpha_channel = value ;
}


} // End Namespace Colosseum
