/*
  XBubble - loadpng.c

  Copyright (C) 2002  Ivan Djelic <ivan@savannah.gnu.org>
  
  This program is free software; you can redistribute it and/or modify
  it under the terms of the GNU General Public License as published by
  the Free Software Foundation; either version 2 of the License, or
  (at your option) any later version.
  
  This program 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 General Public License for more details.
  
  You should have received a copy of the GNU General Public License
  along with this program; 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 <png.h>

#include "gettext.h"
#include "loadpng.h"

/*
 * load_png_file() allocates a byte array and fills it with
 * a contiguous sequence of 4-byte pixels in RGBA format:
 * byte 0: red channel
 * byte 1: green channel
 * byte 2: blue channel
 * byte 3: alpha channel / filler byte if no alpha layer
 *
 * load_png_file returns NULL upon failure.
 */

unsigned char * load_png_file( const char *file, 
			       int *width,
			       int *height,
			       int *has_alpha ) {
  FILE *fd;
  unsigned char *data;
  unsigned char header[8];
  int  bit_depth, color_type, i;
  png_uint_32  png_width, png_height, rowbytes;
  png_structp png_ptr;
  png_infop info_ptr;
  png_bytep *row_pointers;

  fd = fopen( file, "rb" );
  if ( fd == NULL ) {
    perror(file);
    return NULL;
  }
  /* ensure that we opened a PNG file */
  fread( header, 1, 8, fd );
  if ( ! png_check_sig( header, 8 ) ) {
    fclose(fd);
    fprintf(stderr,_("File %s does not have a valid PNG signature.\n"), file);
    return NULL;
  }
  /* create PNG structs */
  png_ptr = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
  if ( ! png_ptr ) {
    fclose(fd);
    return NULL;
  }
  info_ptr = png_create_info_struct(png_ptr);
  if ( ! info_ptr ) {
    png_destroy_read_struct( &png_ptr, (png_infopp)NULL, (png_infopp)NULL);
    fclose(fd);
    return NULL;
  }
  /* libpng does a longjmp here when it encounters an error */
  if ( setjmp( png_ptr->jmpbuf ) ) {
    png_destroy_read_struct( &png_ptr, &info_ptr, NULL);
    fclose(fd);
    return NULL;
  }
  png_init_io( png_ptr, fd );
  png_set_sig_bytes( png_ptr, 8);
  png_read_info( png_ptr, info_ptr);
  png_get_IHDR( png_ptr, info_ptr, &png_width, &png_height, &bit_depth, 
		&color_type, NULL, NULL, NULL);
  *width = (int) png_width;
  *height = (int) png_height;

  /* convert image format to RGB(A) */
  if ( color_type == PNG_COLOR_TYPE_PALETTE )
    png_set_expand(png_ptr);

  if (( color_type == PNG_COLOR_TYPE_GRAY )||
      ( color_type == PNG_COLOR_TYPE_GRAY_ALPHA ))
    png_set_gray_to_rgb(png_ptr);

  /* detect alpha layer */
  if (( info_ptr->color_type & PNG_COLOR_MASK_ALPHA )||
      ( png_get_valid( png_ptr, info_ptr, PNG_INFO_tRNS )))
    *has_alpha = 1;
  else
    *has_alpha = 0;

  /* we only handle 8 bits per channel */
  if ( bit_depth == 16 )
    png_set_strip_16(png_ptr);
  /* insert filling bytes if necessary */
  png_set_filler(png_ptr, 0xff, PNG_FILLER_AFTER);

  /* update info data */
  png_read_update_info( png_ptr, info_ptr);

  /* allocate space for data and row pointers */
  rowbytes = png_get_rowbytes( png_ptr, info_ptr);
  data = (unsigned char *) malloc( rowbytes*(*height) );
  row_pointers = (png_bytep *) malloc( (*height)*sizeof(png_bytep));
  if (( data == NULL )||( row_pointers == NULL )) {
    png_destroy_read_struct( &png_ptr, &info_ptr, NULL);
    free(data);
    free(row_pointers);
    return NULL;
  }
  /* initialize row pointers */
  for ( i = 0;  i < *height; i++ )
    row_pointers[i] = data + i*rowbytes;
  /* read all image data */
  png_read_image( png_ptr, row_pointers );
  png_read_end( png_ptr, NULL);
  /* cleanup */
  free(row_pointers);
  png_destroy_read_struct( &png_ptr, &info_ptr, NULL);
  fclose(fd);
  /* return an array of RGB(A) values */
  return data;
}
