/*

			Slide Show Screen Saver for Windows

					PNG Image Loader

*/

#include "screensv.h"
#include "png.h"

#include <setjmp.h>


//	PNG_WARNING  --  Ignore warnings from PNG library

static void PNG_warning(png_structp png_ptr, png_const_charp msg)
{
}

//	PNG_ERROR  --  Save error string from PNG library

static void PNG_error(png_structp png_ptr, png_const_charp msg)
{
	static char s[256];

	strcpy(s, msg);
	loadError = s;
	longjmp(png_ptr->jmpbuf, 1);
}

//  LOADPNGIMAGE  --  Load PNG image into memory

int loadPNGimage(FILE *input_file, HGLOBAL *LimageBitmap,
				 int *LsizeX, int *LsizeY)
{
	int i;
	LPBITMAPINFOHEADER bh;
	DWORD bmpsize;
	LPBYTE pix, px;
	int linewid;
	int pixbytes;
	png_structp png_ptr;
	png_infop info_ptr;
	png_uint_32 width, height;
	int bit_depth, color_type, interlace_type, rowbytes;

	static HGLOBAL imageBitmap = NULL;		// In-memory bitmap
	static png_bytep *row_pointers = NULL;

	imageBitmap = NULL;

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
				NULL, PNG_error, PNG_warning);

	if (png_ptr == NULL) {
		loadError = "Cannot initialise PNG library";
		return FALSE;
	}

	// Allocate/initialise the memory for image information.

	info_ptr = png_create_info_struct(png_ptr);
	if (info_ptr == NULL) {
		png_destroy_read_struct(&png_ptr, (png_infopp) NULL, (png_infopp) NULL);
		loadError = "Cannot allocate PNG info structure";
		return FALSE;
	}

	//	Set up to catch errors within the PNG package

	if (setjmp(png_ptr->jmpbuf) != 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);

		/*	Since we arrive here via longjmp() from
			parts unknown, we may have allocated
			storage which must be released before we
			return emptyhanded.  */

		if (imageBitmap != NULL) {
			GlobalFree(imageBitmap);
			imageBitmap = NULL;
		}
		if (row_pointers != NULL) {
			GlobalFree(row_pointers);
			row_pointers = NULL;
		}
		return FALSE;
	}

	png_init_io(png_ptr, input_file);	// Set up to read from input_file
	png_read_info(png_ptr, info_ptr);	// Read image header
	png_get_IHDR(png_ptr, info_ptr, &width, &height, &bit_depth, &color_type,
		&interlace_type, NULL, NULL);

	pixbytes = (color_type == PNG_COLOR_TYPE_PALETTE) ? 1 : 3;

	// Strip 16 bit/colour files down to 8 bits/colour
	png_set_strip_16(png_ptr);

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

	// Expand paletted colors into true RGB triplets.
	/* Obsolete now that we retain palette information
	   into BMP format.  */
//	if (!usePalette && (color_type == PNG_COLOR_TYPE_PALETTE)) {
//		png_set_expand(png_ptr);
//	}

	// 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 greyscale images to RGB

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

	// Flip the RGB pixels to BGR (or RGBA to BGRA)
	png_set_bgr(png_ptr);

	//	Apply transformation changes to header
	png_read_update_info(png_ptr, info_ptr);

	rowbytes = png_get_rowbytes(png_ptr, info_ptr);

	//	Allocate and initialise row pointer array

	linewid = (((rowbytes + (sizeof(LONG) - 1)) / sizeof(LONG)) * sizeof(LONG));
	bmpsize = sizeof(BITMAPINFOHEADER) +
		((pixbytes == 1) ? (256 * sizeof(RGBQUAD)) : 0) +
		(linewid * height);

	imageBitmap = GlobalAlloc(GMEM_FIXED, bmpsize);
	if (imageBitmap == NULL) {
		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
		loadError = "Cannot allocate bitmap for decoded PNG image";
		return FALSE;
	}

	//	Plug in header fields with information from cinfo

	bh = (LPBITMAPINFOHEADER) imageBitmap;
	pix = ((LPBYTE) imageBitmap) + sizeof(BITMAPINFOHEADER) +
			((pixbytes == 1) ? (256 * sizeof(RGBQUAD)) : 0);
	bh->biSize = sizeof(BITMAPINFOHEADER);
	bh->biWidth = width;
	bh->biHeight = height;
	bh->biPlanes = 1;
	bh->biBitCount = (pixbytes == 1) ? 8 : 24;
	bh->biCompression = BI_RGB;
	bh->biSizeImage = 0;
	bh->biXPelsPerMeter = bh->biYPelsPerMeter = 2835;
	bh->biClrUsed = 0;
	bh->biClrImportant = 0;

	/*	Construct the palette from the colour map optimised
		for the PNG file.  */

	if (pixbytes == 1) {
		png_colorp pal;
		png_uint_32 png_ncolours;

		png_get_PLTE(png_ptr, info_ptr, &pal, &png_ncolours);
		for (i = 0; i < (int) png_ncolours; i++) {
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[i])->rgbRed = pal[i].red;
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[i])->rgbGreen = pal[i].green;
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[i])->rgbBlue = pal[i].blue;
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[i])->rgbReserved = 0;
		}

		/*	Plug black and our text colour in the last two slots
			of the palette so we can be sure they're available.  */

		if (png_ncolours < 255) {
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[254])->rgbRed = GetRValue(skycolour);
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[254])->rgbGreen = GetGValue(skycolour);
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[254])->rgbBlue = GetBValue(skycolour);
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[254])->rgbReserved = 0;
		}

		if (png_ncolours < 256) {	
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[255])->rgbRed = 0;
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[255])->rgbGreen = 0;
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[255])->rgbBlue = 0;
			((LPRGBQUAD) &((LPBITMAPINFO) bh)->bmiColors[255])->rgbReserved = 0;
		}
	}

	//	Allocate and initialise row pointer array

	row_pointers = GlobalAlloc(GMEM_FIXED, height * sizeof(png_bytep));
	if (row_pointers == NULL) {
		GlobalFree(imageBitmap);
		imageBitmap = NULL;
		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);
		loadError = "Cannot allocate PNG row pointer array";
		return FALSE;
	}

	px = pix + (linewid * (bh->biHeight - 1));
	for (i = 0; i < (int) height; i++) {
		row_pointers[i] = px;
		px -= linewid;
	}

	//	Decode the image

	png_read_image(png_ptr, row_pointers);
	png_read_end(png_ptr, info_ptr);
	png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp) NULL);

	GlobalFree(row_pointers);

	/*	If the display is 256 colour or the user has explicitly
		requested a palette-mapped image, transform full colour
		bitmaps into 256 colour replacements.  */

	if (usePalette && (bh->biBitCount > 8)) {
		HGLOBAL newBitmap = quantifyBMP(imageBitmap);

		GlobalFree(imageBitmap);
		if (newBitmap == NULL) {
			loadError = "Cannot create colour palette for true colour PNG file.";
			return FALSE;
		}
		imageBitmap = newBitmap;
		bh = (LPBITMAPINFOHEADER) imageBitmap;
	}

//#define SNAP_BMP
#ifdef SNAP_BMP
{
	FILE *o_file = fopen("d:/slidescr/snap.bmp", "wb");

	BITMAPFILEHEADER bf;

	memcpy(&bf.bfType , "BM", 2);
	bf.bfSize = (sizeof bf) + bmpsize;
	bf.bfReserved1 = bf.bfReserved2 = 0;
	bf.bfOffBits = (sizeof bf) + sizeof(BITMAPINFOHEADER) + ((bh->biBitCount <= 8) * 256 * sizeof(RGBQUAD));
	fwrite(&bf, sizeof bf, 1, o_file);
	fwrite(bh, bmpsize, 1, o_file);
	fclose(o_file);
}
#endif
	 
	*LsizeX = (int) bh->biWidth;
	*LsizeY = (int) bh->biHeight;
	*LimageBitmap = imageBitmap;
	return TRUE;
}
