//
// PImageNativeLoaderPng.cc for pekwm
// Copyright (C) 2005 Claes Nasten <pekdon{@}pekdon{.}net>
//
// This program is licensed under the GNU GPL.
// See the LICENSE file for more information.
//

#include "../config.h"

#ifdef HAVE_IMAGE_PNG

#include "PImageNativeLoaderPng.hh"

#include <iostream>

extern "C" {
#include <png.h>
}

using std::string;
using std::cerr;
using std::endl;

const int PImageNativeLoaderPng::PNG_SIG_BYTES = 8;

//! @brief PImageNativeLoaderPng constructor.
PImageNativeLoaderPng::PImageNativeLoaderPng(void)
	: PImageNativeLoader("PNG")
{
}

//! @brief PImageNativeLoaderPng destructor.
PImageNativeLoaderPng::~PImageNativeLoaderPng(void)
{
}

//! @brief Loads file into data.
//! @param file File to load data from.
//! @param width Set to the width of image.
//! @param height Set to the height of image.
//! @param alpha Set to wheter image has alpha channel.
//! @return Pointer to data on success, else NULL.
uchar*
PImageNativeLoaderPng::load(const std::string &file, uint &width, uint &height,
                            bool &alpha)
{
  FILE *fp;

  fp = fopen (file.c_str (), "rb");
  if (!fp)
    {
      cerr << " *** WARNING: unable to open " << file << " for reading!" << endl;
      return NULL;
    }

  if (!checkSignature (fp))
    {
      cerr << " *** WARNING: " << file << " not a PNG file!" << endl;
      fclose (fp);
      return NULL;
    }

  // Start PNG loading.
  png_structp png_ptr;
  png_infop info_ptr;

  png_ptr = png_create_read_struct (PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if (!png_ptr)
    {
      cerr << " *** ERROR: out of memory, png_create_read_struct failed!" << endl;
      fclose (fp);
      return NULL;
    }
  info_ptr = png_create_info_struct (png_ptr);
  if (!info_ptr)
    {
      cerr << " *** ERROR: out of memory, png_create_info_struct failed!" << endl;
      png_destroy_read_struct (&png_ptr, NULL, NULL);
      fclose (fp);
      return NULL;
    }

  // Setup error handling.
  if (setjmp (png_jmpbuf (png_ptr)))
    {
      png_destroy_read_struct (&png_ptr, &info_ptr, NULL);
      fclose (fp);
      return NULL;
    }

  png_init_io (png_ptr, fp);
  png_set_sig_bytes (png_ptr, PImageNativeLoaderPng::PNG_SIG_BYTES);
  png_read_info (png_ptr, info_ptr);

  int color_type, bpp;
  png_uint_32 png_width = 1, png_height = 1;
  png_get_IHDR (png_ptr, info_ptr, &png_width, &png_height,
                &bpp, &color_type, NULL, NULL, NULL);

  width = png_width;
  height = png_height;

  // Setup read information, we want to make sure data get read in
  // 16 bit RGB(A)

  // palette -> RGB mode
  if (color_type == PNG_COLOR_TYPE_PALETTE)
    png_set_palette_to_rgb(png_ptr);

  // gray -> 8 bit gray
  if (color_type == PNG_COLOR_TYPE_GRAY && (bpp < 8))
    png_set_gray_1_2_4_to_8 (png_ptr);

  if (png_get_valid (png_ptr, info_ptr, PNG_INFO_tRNS))
    png_set_tRNS_to_alpha (png_ptr);

  if (bpp == 16)
    png_set_strip_16 (png_ptr);

  // gray, gray alpha -> to RGB
  if ((color_type == PNG_COLOR_TYPE_GRAY)
      || (color_type == PNG_COLOR_TYPE_GRAY_ALPHA))
    {
      png_set_gray_to_rgb (png_ptr);
    }

  // Now load image data.
  uchar *data;
  png_uint_32 rowbytes, channels;
  png_bytepp row_pointers;

  png_read_update_info (png_ptr, info_ptr);

  rowbytes = png_get_rowbytes (png_ptr, info_ptr);
  channels = png_get_channels (png_ptr, info_ptr);

  data = new uchar[rowbytes * height / sizeof(uchar)];
  row_pointers = new png_bytep[height];

  for (png_uint_32 y = 0; y < height; ++y)
    row_pointers[y] = data + y * rowbytes;

  png_read_image(png_ptr, row_pointers);

  delete [] row_pointers;

  png_read_end (png_ptr, NULL);

  // Clean up.
  png_destroy_read_struct (&png_ptr, &info_ptr, NULL);

  fclose (fp);

  alpha = (channels == 4);

  return data;
}

//! @brief Checks file signature to see if it's a PNG file.
//! @param fp Pointer to open (rb) FILE.
//! @return true if fp is a PNG file, else false.
bool
PImageNativeLoaderPng::checkSignature(FILE *fp)
{
	int status;
	uchar sig[PImageNativeLoaderPng::PNG_SIG_BYTES];

	status = fread(sig, 1, PImageNativeLoaderPng::PNG_SIG_BYTES, fp);
	if (status == PImageNativeLoaderPng::PNG_SIG_BYTES) {
		return (png_check_sig(sig, PImageNativeLoaderPng::PNG_SIG_BYTES) != 0);
	}
	return false;
}

#endif // HAVE_IMAGE_PNG


syntax highlighted by Code2HTML, v. 0.9.1