/*	
 *	clippng.c
 *
 *	PNG Decoder
 *
 *	CLiP - Common Library for P/ECE
 *	Copyright (C) 2001-2009 Naoyuki Sawa
 *
 *	* Sat Mar 28 20:39:38 JST 2009 Naoyuki Sawa
 *	- 1st リリース。
 */
#include "clip.h"

/****************************************************************************
 *	
 ****************************************************************************/

void
png_decode(const void* _p,
	void (*size_proc)(int w, int h, void* user_data),
	void (*draw_proc)(int x, int y, int r, int g, int b, int a, void* user_data),
	void* user_data)
{
	static const unsigned char PNG_file_signature[8] = { 137, 80, 78, 71, 13, 10, 26, 10 };
	static const unsigned char Adam7[8/*0=no interlace,1..7=Adam7 interlace*/][2/*Width,Height*/][2] = {
		{ { 1, 0 }, { 1, 0 } }, /* no interlace */
		{ { 8, 0 }, { 8, 0 } }, /* Interlaced image 1 */
		{ { 8, 4 }, { 8, 0 } }, /* Interlaced image 2 */
		{ { 4, 0 }, { 8, 4 } }, /* Interlaced image 3 */
		{ { 4, 2 }, { 4, 0 } }, /* Interlaced image 4 */
		{ { 2, 0 }, { 4, 2 } }, /* Interlaced image 5 */
		{ { 2, 1 }, { 2, 0 } }, /* Interlaced image 6 */
		{ { 1, 0 }, { 2, 1 } }, /* Interlaced image 7 */
	};
	const unsigned char* p = _p;
	struct {
		int Width;
		int Height;
		int Bit_depth;
		int Color_type;
		//int Compression_method;
		//int Filter_method;
		int Interlace_method;
	} IHDR;
	const unsigned char* PLTE = NULL;
	const unsigned char* tRNS = NULL;
	int Compressed_image_bytes = 0;
	unsigned char* Compressed_image_data = NULL;
	int Image_bytes = 0;
	unsigned char* Image_data;
	int Samples_per_pixel;
	struct {
		int Width;
		int Height;
		int Width_bytes;
	} Interlaced_image[8/*0=no interlace,1..7=Adam7 interlace*/];
	int i;

	if(memcmp(p, PNG_file_signature, 8/*PNG_file_signature*/)) {
		DIE();
	}
	p += 8/*PNG_file_signature*/;
	for(;;) {
		int Length;
		const unsigned char* Chunk_Type;
		const unsigned char* Chunk_Data;
		int CRC;
		Length = BEWORD(p);
		p += 4/*Length*/;
		Chunk_Type = p;
		p += 4/*Chunk_Type*/;
		Chunk_Data = p;
		p += Length/*Chunk_Data*/;
		CRC = BEWORD(p);
		p += 4/*CRC*/;
		if(zip_crc32(Chunk_Type, 4/*Chunk_Type*/ + Length/*Chunk_Data*/) != CRC) {
			DIE();
		}
		/* IHDR */
		if(!memcmp(Chunk_Type, "IHDR", 4/*Chunk_Type*/)) {
			IHDR.Width = BEWORD(Chunk_Data);
			Chunk_Data += 4/*IHDR.Width*/;
			IHDR.Height = BEWORD(Chunk_Data);
			Chunk_Data += 4/*IHDR.Height*/;
			IHDR.Bit_depth = BEBYTE(Chunk_Data);
			Chunk_Data += 1/*IHDR.Bit_depth*/;
			IHDR.Color_type = BEBYTE(Chunk_Data);
			Chunk_Data += 1/*IHDR.Color_type*/;
			//IHDR.Compression_method = BEBYTE(Chunk_Data);
			Chunk_Data += 1/*IHDR.Compression_method*/;
			//IHDR.Filter_method = BEBYTE(Chunk_Data);
			Chunk_Data += 1/*IHDR.Filter_method*/;
			IHDR.Interlace_method = BEBYTE(Chunk_Data);
			continue;
		}
		/* PLTE */
		if(!memcmp(Chunk_Type, "PLTE", 4/*Chunk_Type*/)) {
			PLTE = Chunk_Data;
			continue;
		}
		/* tRNS */
		if(!memcmp(Chunk_Type, "tRNS", 4/*Chunk_Type*/)) {
			tRNS = Chunk_Data;
			continue;
		}
		/* IDAT */
		if(!memcmp(Chunk_Type, "IDAT", 4/*Chunk_Type*/)) {
			Compressed_image_data = realloc(Compressed_image_data, Compressed_image_bytes + Length);
			if(!Compressed_image_data) {
				DIE();
			}
			memcpy(Compressed_image_data + Compressed_image_bytes, Chunk_Data, Length);
			Compressed_image_bytes += Length;
			continue;
		}
		/* IEND */
		if(!memcmp(Chunk_Type, "IEND", 4/*Chunk_Type*/)) {
			break;
		}
	}

	switch(IHDR.Color_type) {
	case 0: /* Color type 0 (grayscale) */
	case 3: /* Color type 3 (indexed color) */
		Samples_per_pixel = 1;
		break;
	case 4: /* Color type 4 (grayscale with alpha channel) */
		Samples_per_pixel = 2;
		break;
	case 2: /* Color type 2 (truecolor) */
		Samples_per_pixel = 3;
		break;
	case 6: /* Color type 6 (truecolor with alpha channel) */
		Samples_per_pixel = 4;
		break;
	default:
		DIE();
	}
	for(i = IHDR.Interlace_method/*0 or 1*/; i < 8/*Interlaced_image*/; i++) {
		Interlaced_image[i].Width  = (IHDR.Width  + Adam7[i][0][0] - Adam7[i][0][1] - 1) / Adam7[i][0][0];
		Interlaced_image[i].Height = (IHDR.Height + Adam7[i][1][0] - Adam7[i][1][1] - 1) / Adam7[i][1][0];
		Interlaced_image[i].Width_bytes = (Interlaced_image[i].Width * IHDR.Bit_depth * Samples_per_pixel + 7) / 8 + 1/*Filter_type*/;
		Image_bytes += Interlaced_image[i].Height * Interlaced_image[i].Width_bytes;
		if(!i) { /* no interlace */
			break;
		}
	}
	Image_data = malloc(Image_bytes);
	if(!Image_data) {
		DIE();
	}
	if(zlib_uncompress(Image_data, Image_bytes, Compressed_image_data, Compressed_image_bytes) != Image_bytes) {
		DIE();
	}
	free(Compressed_image_data);

	if(size_proc) {
		size_proc(IHDR.Width, IHDR.Height, user_data);
	}
	if(draw_proc) {
		unsigned char* q = Image_data;
		int Sample_Mask = (1 << IHDR.Bit_depth) - 1;
		int Pixel_bytes = (IHDR.Bit_depth * Samples_per_pixel + 7) / 8;
		int w, x, y, z;
		for(i = IHDR.Interlace_method/*0 or 1*/; i < 8/*Interlaced_image*/; i++) {
			for(y = 0; y < Interlaced_image[i].Height; y++) {
				int Filter_type = *q++;
				unsigned char* r = q;
				for(x = 0; x < Interlaced_image[i].Width_bytes - 1/*Filter_type*/; x++, q++) {
					int a = (x >= Pixel_bytes     ) ? *(q - Pixel_bytes                                  ) : 0; /* left */
					int b = (                    y) ? *(q               - Interlaced_image[i].Width_bytes) : 0; /* above */
					int c = (x >= Pixel_bytes && y) ? *(q - Pixel_bytes - Interlaced_image[i].Width_bytes) : 0; /* upper left */
					int pa, pb, pc;
					switch(Filter_type) {
					case 0: /* Filter type 0 (None) */
						break;
					case 1: /* Filter type 1 (Sub) */
						*q += a;
						break;
					case 2: /* Filter type 2 (Up) */
						*q += b;
						break;
					case 3: /* Filter type 3 (Average) */
						*q += (a + b) / 2;
						break;
					case 4: /* Filter type 4 (Paeth) */
						pa = abs(a + b - c - a);
						pb = abs(a + b - c - b);
						pc = abs(a + b - c - c);
						*q += (pa <= pb && pa <= pc) ? a : (pb <= pc) ? b : c;
						break;
					default:
						DIE();
					}
				}
				for(x = 0; x < Interlaced_image[i].Width; x++) {
					int Sample[4];
					for(z = 0; z < Samples_per_pixel; z++) {
						int s = IHDR.Bit_depth * (x * Samples_per_pixel + z);
						unsigned char* t = r + s / 8;
						s = (8 - s - IHDR.Bit_depth) & 7;
						if(IHDR.Bit_depth == 16) {
							Sample[z] = (t[0] << 8) | t[1];
						} else { /* Bit_depth 1,2,4,8 */
							Sample[z] = (t[0] >> s) & Sample_Mask;
						}
					}
					switch(IHDR.Color_type) {
					case 0: /* Color type 0 (grayscale) */
					case 2: /* Color type 2 (truecolor) */
						if(tRNS) {
							for(z = 0; z < Samples_per_pixel; z++) {
								if(Sample[z] != BEHALF(tRNS + z * sizeof(unsigned short))) { /* Transparent color */
									break;
								}
							}
							Sample[Samples_per_pixel/*Alpha*/] = (z == Samples_per_pixel) ? 0 : Sample_Mask;
						} else {
							Sample[Samples_per_pixel/*Alpha*/] = Sample_Mask;
						}
						/* FALLTHRU */
					case 4: /* Color type 4 (grayscale with alpha channel) */
					case 6: /* Color type 6 (truecolor with alpha channel) */
						if(!(IHDR.Color_type & 2)) { /* Color type 0 (grayscale), 4 (grayscale with alpha channel) */
							Sample[3] = Sample[1]; /* Alpha */
							Sample[2] = Sample[0]; /* Blue */
							Sample[1] = Sample[0]; /* Green */
						}
						for(z = 0; z < 4/*Red,Green,Blue,Alpha*/; z++) {
							for(w = IHDR.Bit_depth/*1,2,4,8,16*/; w < 16; w <<= 1) {
								Sample[z] |= Sample[z] << w;
							}
							Sample[z] >>= 8;
						}
						break;
					case 3: /* Color type 3 (indexed color) */
						w = Sample[0]; /* Palette index */
						for(z = 0; z < 3/*Red,Green,Blue*/; z++) {
							Sample[z] = PLTE[w * 3 + z];
						}
						if(tRNS && w < (int)BEWORD(tRNS - 4 - 4)/*Length*/) {
							Sample[3/*Alpha*/] = tRNS[w]; /* Alpha for palette index */
						} else {
							Sample[3/*Alpha*/] = 255;
						}
						break;
					default:
						DIE();
					}
					draw_proc(x * Adam7[i][0][0] + Adam7[i][0][1],
					          y * Adam7[i][1][0] + Adam7[i][1][1], Sample[0], Sample[1], Sample[2], Sample[3], user_data);
				}
			}
			if(!i) { /* no interlace */
				break;
			}
		}
	}
	free(Image_data);
}

