//<copyright>
//
// Copyright (c) 1995-96
// Institute for Information Processing and Computer Supported New Media (IICM),
// Graz University of Technology, Austria.
//
//</copyright>


//<file>
//
//
// Name :       pngimage.C
//
// Purpose :    Implementation of class PngImage
//
// Created :    01 Sep 95    Bernhard Marschall
//
// $Id: pngimage.C,v 1.8 1997/02/19 09:20:14 bmarsch Exp $
//
// Description:
//
//</file>
//
// $Log: pngimage.C,v $
// Revision 1.8  1997/02/19 09:20:14  bmarsch
// Changed return value of HgRasterCallback::getData()
//
// Revision 1.7  1996/10/02 13:29:20  bmarsch
// Removed debugging output
//
// Revision 1.6  1996/10/02 13:28:52  bmarsch
// verbose.h was moved from hyperg to utils
//
// Revision 1.5  1996/03/01 14:22:23  bmarsch
// Adjusted to new alpha channel handling of ivRaster
//
// Revision 1.4  1996/02/29 12:42:32  bmarsch
// Always call decodedInterlaced() callback after a pass (also
// in finishDecode())
//


#include "hgrastercb.h"
#include "pngimage.h"

#include "../png/png.h"

//#define VERBOSE
#include <hyperg/utils/verbose.h>

// ************************************************************************
// bmarsch: these static variables are needed for the C (PNG library)
// to C++ (callback) interface

static HgRasterCallback* the_callback = nil;


// ************************************************************************
// PngImpl:

class PngImpl {
public:
  png_structp png_ptr_;
  png_infop info_ptr_;
  unsigned char* buf_;
  int num_passes_;
  int passes_left_;
};

// ************************************************************************
// Error handling functinos

void png_error_function(png_structp png_ptr, png_const_charp message)
{
  // bmarsch 19950906: let the callback handle the error message
  if (the_callback)
    the_callback->error(message);
#ifdef USE_FAR_KEYWORD
  {
    jmp_buf jmpbuf;
    png_memcpy(jmpbuf,png_ptr->jmpbuf,sizeof(jmp_buf));
    longjmp(jmpbuf, 1);
  }
#else
  longjmp(png_ptr->jmpbuf, 1);
#endif
}

void png_warning_function(png_structp png_ptr, png_const_charp message)
{
  if (!png_ptr)
    return;

  // bmarsch 19950906: let the callback handle the warning message
  if (the_callback)
    the_callback->error(message);
}

// ************************************************************************
// My read function

void png_read_function(png_structp png_ptr, png_bytep data, png_uint_32 length)
{
   png_uint_32 check = 0;

   /* bmarsch 19950906: use callback to get more data during loading */
   png_bytef* buf = data;
   png_uint_32 len = length;
   while (check != length) {
     png_uint_32 read = fread(buf, 1, (size_t)len, png_ptr->fp);
     buf += read;
     check += read;
     len -= read;
     if (check != length) {
       if (the_callback && the_callback->loading()) {
         if (!the_callback->getData())
           png_error(png_ptr, "Read Error");
       }
       else {
         png_error(png_ptr, "Read Error");
       }
     }
   }
}

// ************************************************************************

PngImage::PngImage(FILE* fp, const char* fname, Raster*& raster,
                   HgRasterCallback* cb, boolean dither)
: RasterImage(fp, fname, raster, cb, dither)
{
  DEBUGNL("PI::PI");

  the_callback = cb;

  // create and allocate png data structures
  impl_ = new PngImpl();
  impl_->png_ptr_ = new png_struct;
  impl_->info_ptr_ = new png_info;
  png_structp& png_ptr = impl_->png_ptr_;
  png_infop& info_ptr = impl_->info_ptr_;

  // set up error handling
  png_set_message_fn(png_ptr, NULL, png_error_function, png_warning_function);
  if (setjmp(png_ptr->jmpbuf)) {
    png_read_destroy(png_ptr, info_ptr, (png_info*)nil);
    error_ = true;
    return;
  }

  // initialize structures
  png_info_init(info_ptr);
  png_read_init(png_ptr);

  // set up input handling
  png_init_io(png_ptr, fp);
  png_set_read_fn(png_ptr, NULL, png_read_function);

  // read file information
  png_read_info(png_ptr, info_ptr);
  int width = info_ptr->width;
  int height = info_ptr->height;
  setSize(width, height);

  // always output "true color" (ie. 24 bit per pixel)
  if (info_ptr->color_type == PNG_COLOR_TYPE_PALETTE)
    png_set_expand(png_ptr);

  // check for alpha channel
  boolean alpha_channel = info_ptr->valid & PNG_INFO_tRNS;
  if (alpha_channel)
    raster_->useAlphaTransparency();

  // allocate buffer for decoded image
  int row_width = alpha_channel ? width*4 : width*3;
  impl_->buf_ = new unsigned char[row_width];

  // get number of passes required to decode image
  impl_->num_passes_ = impl_->passes_left_ = png_set_interlace_handling(png_ptr);
}

// ************************************************************************

PngImage::~PngImage()
{
  delete impl_->png_ptr_;
  delete impl_->info_ptr_;
  delete impl_->buf_;
  delete impl_;
}

// ************************************************************************

void PngImage::decode(int numrows)
{
  DEBUGNL("PI::decode(" << numrows << "); decoded_ = " << decoded_);

  // do nothing if an error has occured
  if (error_) return;

  png_structp& png_ptr = impl_->png_ptr_;
  png_infop& info_ptr = impl_->info_ptr_;
  unsigned char*& buf = impl_->buf_;
  int& passes_left = impl_->passes_left_;
  boolean interlaced = info_ptr->interlace_type;

  // set entry point for recovery from error
  if (setjmp(png_ptr->jmpbuf)) {
    DEBUGNL("**** long jumped");
    png_read_destroy(png_ptr, info_ptr, (png_info*)nil);
    error_ = 1;
    return;
  }

  int width = info_ptr->width;
  int maxrow = decoded_ + numrows;
  if (maxrow >= info_ptr->height)
    maxrow = info_ptr->height;
  float incp = 1.0 / (info_ptr->height * impl_->num_passes_);

  Byte* dest = (Byte*) data() + decoded_ * width * 3;
  boolean alpha_channel = info_ptr->valid & PNG_INFO_tRNS;
  Byte* alpha_ptr = (Byte*) alpha() + decoded_ * width;
  while (decoded_ < maxrow) {
    if (callback_)
      callback_->increaseProgress(incp);

    // get current row
    png_byte* png = buf;
    Byte* raster = dest;
    Byte* alpha = alpha_ptr;
    int i;
    for (i = 0; i < width; i++) {
      *png++ = (Byte) *raster++;
      *png++ = (Byte) *raster++;
      *png++ = (Byte) *raster++;
      if (alpha_channel)
        *png++ = *alpha++;
    }

    // decode row
//    png_read_row(png_ptr, buf, nil);   // "sparkle" effekt
    png_read_row(png_ptr, nil, buf);   // "rectangle" effect

    // save decoded row
    png_byte* src = buf;
    for (i = 0; i < width; i++) {
      *dest++ = (Byte) *src++;
      *dest++ = (Byte) *src++;
      *dest++ = (Byte) *src++;
      if (alpha_channel)
        *alpha_ptr++ = *src++;
    }

    decoded_++;
    // live display of sequential PNGs
    if (callback_ && !interlaced && decoded_ % 20 == 0)
      callback_->decodedSequential(20);
  }

  // redraw when a pass has been finished
  if (decoded_ == info_ptr->height) {
    if (interlaced) {
      if (callback_) callback_->decodedInterlaced();
    }
    else {
      if (callback_) callback_->decodedSequential(decoded_ % 20);
    }
    if (--passes_left) decoded_ = 0;
  }
}

// ************************************************************************

void PngImage::finishDecode()
{
  DEBUGNL("PI::finishDecode()");

  png_structp& png_ptr = impl_->png_ptr_;
  png_infop& info_ptr = impl_->info_ptr_;

  // decode remaining rows of current pass
  int h = height();
  int left_lines = h - decoded_;
  if (left_lines > 0) decode(left_lines);

  // decode remaining passes
  int& passes_left = impl_->passes_left_;
  while (passes_left)
    decode(h);

  // do nothing if an error has occured
  if (error_) return;

  // cleanup
  png_read_end(png_ptr, info_ptr);
  png_read_destroy(png_ptr, info_ptr, (png_info*) nil);
}

// ************************************************************************

void PngImage::abortDecode()
{
  DEBUGNL("PI::abortDecode");

  png_structp& png_ptr = impl_->png_ptr_;
  png_infop& info_ptr = impl_->info_ptr_;
  png_read_destroy(png_ptr, info_ptr, (png_info*) nil);
}

// ************************************************************************

int PngImage::not_live() const
{
  return 0;
}
