/*------------------------------------------------------------------------------
 src/plugin/djImageIoPng/save.cxx
 
 Copyright (c) 2004-2006 Darby Johnston.
 All rights reserved.
 Use is subject to license terms.
------------------------------------------------------------------------------*/

//#define DJ_DBG
#include "io.h"
#include <djMath.h>

extern "C" {
const dj::List<dj::ImageSave *> djImageSave()
{
  dj::List<dj::ImageSave *> out;
  out += new djImageIoPng::Save;
  return out;
}
}

namespace djImageIoPng {

Save::Save() :
  _f(0), _png(0), _pngInfo(0)
{}

Save::~Save()
{
  imageClose();
}

bool Save::open(const Str & fileName, const ImageIoInfo & info)
{
  DJ_DBG_FNC("djImageIoPng::Save::open");
  DJ_DBG_PRN("file name = " << fileName);
  
  _fileInfo.set(fileName);
  _info.size(info.size());
  _info.format(info.format());
  switch (info.type()) {
    case PIXEL_U8: _info.type(PIXEL_U8); break;
    case PIXEL_U16:
    case PIXEL_F32: _info.type(PIXEL_U16); break;
  }
  
  return true;
}

static bool _open(
  FILE * f, png_structp png, png_infop * pngInfo,
  const PixelDataInfo & info, const ImageTagList & tagList
) {
  if (setjmp(png_jmpbuf(png))) return false;

  *pngInfo = png_create_info_struct(png);
  if (! *pngInfo) return false;
  
  png_init_io(png, f);
  int colorType = 0, bitDepth = 0;
  switch (info.format()) {
    case PIXEL_L: colorType = PNG_COLOR_TYPE_GRAY; break;
    case PIXEL_LA: colorType = PNG_COLOR_TYPE_GRAY_ALPHA; break;
    case PIXEL_RGB: colorType = PNG_COLOR_TYPE_RGB; break;
    case PIXEL_RGBA: colorType = PNG_COLOR_TYPE_RGB_ALPHA; break;
  }
  switch (info.type()) {
    case PIXEL_U8: bitDepth = 8; break;
    case PIXEL_U16: bitDepth = 16; break;
    case PIXEL_F32: break;
  }
  png_set_IHDR(
    png, *pngInfo,
    info.width(), info.height(),
    bitDepth, colorType,
    PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT
  );
  png_write_info(png, *pngInfo);
  
  return true;
}

bool Save::imageOpen(const Str & fileName, const Image & in)
{
  DJ_DBG_FNC("djImageIoPng::Save::imageOpen");
  DJ_DBG_PRN("file name = " << fileName);
  
  imageClose();
  
  // Initialize libpng.
  //
  _png = png_create_write_struct(
    PNG_LIBPNG_VER_STRING, 0, pngError, pngWarning
  );
  if (! _png) return false;
  
  // Open file.
  //
  _f = fopen(fileName.toAscii(), "wb");
  if (! _f) return false;
  if (! _open(_f, _png, &_pngInfo, _info, in.tagList())) return false;
  
  return true;
}

void Save::imageClose()
{
  if (_png || _pngInfo) {
    png_destroy_write_struct(
      _png ? &_png : 0,
      _pngInfo ? &_pngInfo : 0
    );
    _png = 0;
    _pngInfo = 0;
  }
  if (_f) {
    fclose(_f);
    _f = 0;
  }
}

bool Save::imageBegin(const Image & in, int frame)
{
  DJ_DBG_FNC("djImageIoPng::Save::imageBegin");
  DJ_DBG_PRN("in = " << in);
  DJ_DBG_PRN("frame = " << frame);
  
  if (
    ! imageOpen(_fileInfo.get(frame == -1 ? _fileInfo.start() : frame), _info)
  ) return false;

  return true;
}

static bool _scanline(png_structp png, const uint8_t * in)
{
  if (setjmp(png_jmpbuf(png))) return false;
  png_write_row(png, (png_byte *)in);
  return true;
}

static bool _end(png_structp png, png_infop pngInfo)
{
  if (setjmp(png_jmpbuf(png))) return false;
  png_write_end(png, pngInfo);
  return true;
}

bool Save::imageSet(const PixelData & in)
{
  DJ_DBG_FNC("djImageIoPng::Save::imageSet");
  DJ_DBG_PRN("in = " << in);
  
  // Convert.
  //
  PixelData framebuf(_info);
  framebuf.zero();
  pixelCopy(in, &framebuf);
  
  // Write.
  //
  int y = 0;
  for (; y < _info.height(); ++y)
    if (! _scanline(_png, framebuf.data(0, _info.height() - 1 - y))) break;
  
  bool r = _info.height() == y;
  r &= _end(_png, _pngInfo);
  imageClose();
  return r;
}

}
