/*
 * from OpenVRML
 *
 * Copyright (C) 1998  Chris Morley
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */ 

#include <stdio.h>
#include <stdlib.h>

#include "config.h"

#include "pngLoad.h"

#ifdef HAVE_LIBPNG
#include <png.h>
#include <string.h>

static double get_gamma_exp( void );
static int pngreadstr( FILE *fp,
                       int *w, int *h, int *nc,
                       png_structp png_ptr,
                       png_infop info_ptr,
                       unsigned char **pixels,
                       unsigned char ***rows );

unsigned char *pngread (FILE *fp, int *w, int *h, int *nc)
{
  png_structp png_ptr;
  png_infop info_ptr;
  unsigned char *pixels = 0, **rows = 0;

  /* Create and initialize the png_struct with the desired error handler
    * functions.  If you want to use the default stderr and longjump method,
    * you can supply NULL for the last three parameters.  We also supply the
    * the compiler header file version, so that we know if the application
    * was compiled with a compatible version of the library.  REQUIRED
    */
  png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);

  if (png_ptr == NULL) return NULL;
 
   /* Allocate/initialize the memory for image information.  REQUIRED. */
  info_ptr = png_create_info_struct(png_ptr);
  if (info_ptr == NULL)
    {
      png_destroy_read_struct(&png_ptr, (png_infopp)NULL, (png_infopp)NULL);
      return 0;
    }

  /* png_ptr and info_ptr are freed in pngreadstr */
  if (! pngreadstr( fp, w, h, nc, png_ptr, info_ptr, &pixels, &rows ))
    {
      /* Free all of the memory associated with the png_ptr and info_ptr */
      png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);

      if (pixels) free(pixels);
      if (rows) free(rows);
      return 0;
    }

  if (rows) free(rows);
  return pixels;
}

static unsigned char* pngdata;
static int pnglen;

int pngOpen(const char *filename, int *width, int *height, 
                          int *components)
   {
   FILE* f;
   f=fopen(filename,"rb");
   if (f==NULL)
      return 0;
   pngdata=pngread(f,width,height,components);
   pnglen=*width**height**components;
   if (pngdata==NULL)
      return 0;
   else 
      return 1;
   }

void pngRead(unsigned char *data)
   {
   memcpy(data,pngdata,pnglen);
   }

jmp_buf pngenv;

#ifndef HAVE_NO_PNG_HANDLE_UNKNOWN
# ifndef HAVE_OLPC
#  ifndef MACOSX
#   ifndef WIN32
#    ifndef __alpha
#     ifndef __hppa
void png_handle_unknown PNGARG((png_structp png_ptr, png_infop info_ptr, 
                               png_uint_32 length))
   {
   longjmp(pngenv,1);
   }
#     endif
#    endif
#   endif
#  endif
# endif
#endif

/* The png_jmpbuf() macro, used in error handling, became available in 
  * libpng version 1.0.6.   If you want to be able to run your code with older 
  * versions of libpng, you must define the macro yourself (but only if it 
  * is not already defined by libpng!). 
  */ 

#ifndef png_jmpbuf 
#define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf) 
#endif 

/* This is broken out into a separate function so the setjmp
 * can't clobber pixels and rows.
 */

static int pngreadstr( FILE *fp,
                       int *w, int *h, int *nc,
                       png_structp png_ptr,
                       png_infop info_ptr,
                       unsigned char **ppixels,
                       unsigned char ***prows )
{
  png_uint_32 width, height;
  int bit_depth, color_type, interlace_type;
  int bytes_per_row, row;
  int gray_palette;
  unsigned char *pixels, **rows;


  /* Set error handling if you are using the setjmp/longjmp method (this is
    * the normal method of doing things with libpng).  REQUIRED unless you
    * set up your own error handlers in the png_create_read_struct() earlier.
    */
  if (setjmp(png_jmpbuf((png_ptr))))
    {
      /* If we get here, we had a problem reading the file */
      return 0;
    }
  if (setjmp(pngenv)!=0)
    return(0);
 
  /* Set up the input control if you are using standard C streams */
#if !defined(PNG_NO_STDIO)
  png_init_io(png_ptr, fp);
#endif
  /* The call to png_read_info() gives us all of the information from the
   * PNG file before the first IDAT (image data chunk).  REQUIRED
   */
  png_read_info(png_ptr, info_ptr);
 
  png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
               &interlace_type, NULL, NULL);
 
  *nc = png_get_channels(png_ptr, info_ptr);
  /*printf("color_type %d, nc = %d ", color_type, *nc);*/

/**** Set up the data transformations you want.  Note that these are all
 **** optional.  Only call them if you want/need them.  Many of the
 **** transformations only work on specific types of images, and many
 **** are mutually exclusive.
 ****/
 
  /* tell libpng to strip 16 bit/color files down to 8 bits/color */
  png_set_strip_16(png_ptr);

  /* Extract multiple pixels with bit depths of 1, 2, and 4 from a single
   * byte into separate bytes (useful for paletted and grayscale images).
   */
  png_set_packing(png_ptr);

  /* Expand paletted colors into true RGB triplets */
  if (color_type == PNG_COLOR_TYPE_PALETTE)
    {
      /* Even gray paletted images will get expanded here */
      png_set_expand(png_ptr);
      *nc = 3;
    }
 
  /* Expand grayscale images to the full 8 bits from 1, 2, or 4 bits/pixel */
  if (color_type == PNG_COLOR_TYPE_GRAY && bit_depth < 8)
    png_set_expand(png_ptr);
 
  /* Expand paletted or RGB images with transparency to full alpha channels
   * so the data will be available as RGBA quartets.
   */
  if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
    {
      png_set_expand(png_ptr);
      ++(*nc);
    }

  gray_palette = 0;
  if (color_type == PNG_COLOR_TYPE_PALETTE)
    {
      int n, num_palette;
      png_colorp palette;
      if (png_get_PLTE(png_ptr, info_ptr, &palette, &num_palette))
        {
          gray_palette = 1;
          for (n=0; n<num_palette; ++n)
            if (palette[n].red != palette[n].green ||
                palette[n].blue != palette[n].green)
              {
                gray_palette = 0;
                break;
              }
        }
    }

  /* set gamma */
  {
    double file_gamma, default_exponent = get_gamma_exp();
    if (png_get_gAMA(png_ptr, info_ptr, &file_gamma))
      png_set_gamma(png_ptr, default_exponent, file_gamma);
    else
      png_set_gamma(png_ptr, default_exponent, 0.45455);
  }

  /* Get updated info */
  png_read_update_info(png_ptr, info_ptr);
  png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
               &interlace_type, NULL, NULL);
 
  /* Allocate the memory to hold the image using the fields of info_ptr. */
  bytes_per_row = *nc * width;

  *ppixels = (unsigned char*) malloc(bytes_per_row * height);
  *prows = (unsigned char**) malloc(height * sizeof(char *));
  if (*ppixels == 0 || *prows == 0)
    {
      /* Free all of the memory associated with the png_ptr and info_ptr */
      png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
      return 0;
    }

  pixels = *ppixels;
  rows = *prows;

  
  /* read in "upside down" because opengl says the
   * texture origin is lower left 
   */
  /*for (row=0; row<(int)height; ++row)*/
  /*rows[row] = &pixels[row * bytes_per_row];*/
  for (row=0; row<(int)height; ++row)
    rows[row] = &pixels[(height-row-1) * bytes_per_row];

  /* Now it's time to read the image.  One of these methods is REQUIRED */
  /* Read the entire image in one go */
  png_read_image(png_ptr, rows);
 
  /* read rest of file, and get additional chunks in info_ptr - REQUIRED */
  png_read_end(png_ptr, info_ptr);
 
  /* clean up after the read, and free any memory allocated - REQUIRED */
  png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);

  /* Reduce gray palette images down to intensity or intensity/alpha */
  if (gray_palette)
    {
      int n, np = width * height;
      if (*nc == 3)
        {
          for (n=1; n<np; ++n)
            pixels[n] = pixels[3*n];
          *nc = 1;
        }
      else if (*nc == 4)
        {
          for (n=0; n<np; ++n) {
            pixels[2*n] = pixels[4*n];
            pixels[2*n+1] = pixels[4*n+3];
          }
          *nc = 2;
        }
    }

  *w = width;
  *h = height;
  return 1;
}

/* From Greg Roelofs */

static double get_gamma_exp()
{
  static double default_exponent = 2.2;
  static int set = 0;
#if defined(sgi)
  FILE *infile;
#endif

  if (! set)
    {

#if defined(NeXT)
      default_exponent = 1.0;   /* 2.2/next_gamma for 3rd-party utils */

#elif defined(sgi)
      default_exponent = 1.7;   /* default == 2.2 / 1.7 */
      /* 
       * there doesn't seem to be any documented function to get the
       * "gamma" value, so we do it the hard way 
       */
      if (infile = fopen("/etc/config/system.glGammaVal", "rb")) {
        double sgi_gamma;
        char fooline[80];
        fgets(fooline, sizeof(fooline), infile);
        fclose(infile);
        sgi_gamma = atof(fooline);
        if (sgi_gamma > 0.0)
          default_exponent = 2.2 / sgi_gamma;
      }

#elif defined(Macintosh)
      default_exponent = 1.5;   /* default == (1.8/2.61) * 2.2 */
      /*
        if (mac_gamma = some_mac_function_that_returns_gamma())
        default_exponent = (mac_gamma/2.61) * 2.2;
        */

#endif

      set = 1;
    }

  return default_exponent;
}

#else

int 
pngOpen(const char *filename, int *width, int *height,int *nc)
{
    return 0;
}

void pngRead(unsigned char *data)
   {      
   }
   
#endif

