// libpngrewrite.c - part of pngrewrite
// Copyright (C) 2010 by Jason Summers

#if defined(_WIN32) && !defined(__GNUC__) && !defined(PNGRW_WINDOWS)
#define PNGRW_WINDOWS
#endif

#ifdef PNGRW_WINDOWS
#include <tchar.h>
#include <strsafe.h>
#endif

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

#ifdef PNGRW_WINDOWS
#include <search.h> /* for qsort */
#endif

#include <png.h>

#include "libpngrewrite.h"

#ifdef PNGRW_WINDOWS
#define PNGRW_TEXT _T
#else
#define PNGRW_TEXT(x) x
#define _tfopen fopen
#define _ftprintf fprintf
#endif

#define PNGREWRITEVERSION  PNGRW_TEXT("1.4.0")

struct errstruct {
	jmp_buf jbuf;
	PNGRW_CHAR errmsg[200];
};

struct pal_entry_info {
	unsigned char red;
	unsigned char green;
	unsigned char blue;
	unsigned char alpha;
	unsigned int  count;
};

struct pngrw_ctx {

	pngrw_print_fn_type printfn;
	pngrw_print_fn_type errorfn;
	void *userdata;
	int pal_sort_by_frequency;

	struct pal_entry_info pal[256];
	int pal_used;
	int new_bit_depth;
	int valid_gray;
	int gray_trns_orig_palentry;
	int gray_trns_target_palentry; // index into the grayscale fake palette
	unsigned char gray_trns_shade;
	struct pal_entry_info bkgd;             /* RGB background color */
	unsigned char bkgd_pal;    /* new background color palette entry */
	int ori_pal_size;

	unsigned char *image1, *image2;
	unsigned char **row_pointers1;
	unsigned char **row_pointers2;
	int rowbytes, channels;

	png_uint_32 width, height;
	int bit_depth, color_type, interlace_type;

	int has_gAMA;
	int has_bKGD;
	int has_sRGB;
	int has_tIME;
	int has_pHYs;

	png_time savechunk_time; /*  S.A. */
	double image_gamma;
	int srgb_intent;
	png_uint_32 res_x,res_y;
	int res_unit_type;

	int prev_entry;
	int prev_entry_valid;
	unsigned char prev_r;
	unsigned char prev_g;
	unsigned char prev_b;
	unsigned char prev_a;

	PNGRW_CHAR errmsg[200];
};

static void pngrw_StringCchCopy(PNGRW_CHAR *dst, int dst_len, const PNGRW_CHAR *src)
{
#ifdef PNGRW_WINDOWS
	StringCchCopy(dst,dst_len,src);
#else
	strncpy(dst,src,dst_len);
	dst[dst_len-1]='\0';
#endif
}

static void pngrw_StringCchVPrintf(PNGRW_CHAR *dst, int dst_len, const PNGRW_CHAR *fmt, va_list ap)
{
#ifdef PNGRW_WINDOWS
	StringCchVPrintf(dst,dst_len,fmt,ap);
#else
	vsnprintf(dst,dst_len,fmt,ap);
	dst[dst_len-1]='\0';
#endif
}

static void pngrw_StringCchPrintf(PNGRW_CHAR *dst, int dst_len, const PNGRW_CHAR *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	pngrw_StringCchVPrintf(dst,dst_len,fmt,ap);
	va_end(ap);
}


static void pngrw_print_error(struct pngrw_ctx *ctx, const PNGRW_CHAR *fmt, ...)
{
	va_list ap;
	PNGRW_CHAR buf[1000];

	if(!ctx->errorfn) return;
	va_start(ap, fmt);
	pngrw_StringCchVPrintf(buf,1000,fmt,ap);
	va_end(ap);
	(*ctx->errorfn)(ctx,buf);
}

static void pngrw_print_info(struct pngrw_ctx *ctx, const PNGRW_CHAR *fmt, ...)
{
	va_list ap;
	PNGRW_CHAR buf[1000];

	if(!ctx->printfn) return;
	va_start(ap, fmt);
	pngrw_StringCchVPrintf(buf,1000,fmt,ap);
	va_end(ap);
	(*ctx->printfn)(ctx,buf);
}

static void my_png_error_fn(png_structp png_ptr, const char *err_msg)
{
	struct errstruct *errinfop;

	errinfop = (struct errstruct *)png_get_error_ptr(png_ptr);

	pngrw_StringCchPrintf(errinfop->errmsg,200,PNGRW_TEXT("%s"),err_msg);

	longjmp(errinfop->jbuf, -1);
}

static void my_png_warning_fn(png_structp png_ptr, const char *warn_msg)
{
	return;
}

static void pngrw_read_tIME_chunk(struct pngrw_ctx *ctx,
    png_structp png_ptr, png_infop info_ptr)
{
	png_timep in_time;  /*  a struct POINTER  */

	/* S.A.  .................................  */
	if(!ctx->has_tIME &&
		png_get_valid(png_ptr,info_ptr,PNG_INFO_tIME))
	{
		if(png_get_tIME(png_ptr, info_ptr, &in_time) == PNG_INFO_tIME) {
			ctx->savechunk_time = *in_time;
			ctx->has_tIME = 1;
		}
	}
}

static void pngrw_record_ancillary_chunks_beforeimage(struct pngrw_ctx *ctx,
    png_structp png_ptr, png_infop info_ptr)
{
	if (png_get_gAMA(png_ptr, info_ptr, &ctx->image_gamma)) {
		ctx->has_gAMA=1;
	}

	if (png_get_sRGB(png_ptr, info_ptr, &ctx->srgb_intent)) {
		ctx->has_sRGB=1;
	}

	if (png_get_valid(png_ptr,info_ptr,PNG_INFO_pHYs)) {
		ctx->has_pHYs=1;
		png_get_pHYs(png_ptr,info_ptr,&ctx->res_x,&ctx->res_y,&ctx->res_unit_type);
		if(ctx->res_x<1 || ctx->res_y<1) ctx->has_pHYs=0;
	}

	pngrw_read_tIME_chunk(ctx,png_ptr,info_ptr);
}

static void pngrw_record_ancillary_chunks_afterimage(struct pngrw_ctx *ctx,
    png_structp png_ptr, png_infop info_ptr)
{
	pngrw_read_tIME_chunk(ctx,png_ptr,info_ptr);
}

static int pngrw_read_png(struct pngrw_ctx *ctx, FILE *infp)
{
	png_structp png_ptr = NULL;
	png_infop info_ptr = NULL;
	png_colorp ori_pal;
	int ori_bpp;
	struct errstruct errinfo;
	int retval = 0;
	int i;
	png_color_16 *image_background;

	pngrw_StringCchCopy(errinfo.errmsg,200,PNGRW_TEXT(""));

	png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING,
	    (void*)(&errinfo),my_png_error_fn, my_png_warning_fn);

	if(!png_ptr) {
		pngrw_print_error(ctx,PNGRW_TEXT("Error creating read_struct"));
		goto done;
	}

	info_ptr = png_create_info_struct(png_ptr);
	if(!info_ptr) {
		pngrw_print_error(ctx,PNGRW_TEXT("Error creating read_info_struct"));
		goto done;
	}

	if (setjmp(errinfo.jbuf)) {
		pngrw_print_error(ctx,PNGRW_TEXT("PNG decode error: %s"), errinfo.errmsg);
		goto done;
	}

	png_init_io(png_ptr, infp);

	png_read_info(png_ptr, info_ptr);

	png_get_IHDR(png_ptr, info_ptr, &ctx->width, &ctx->height, &ctx->bit_depth, &ctx->color_type,
		&ctx->interlace_type, NULL, NULL);

	if(ctx->bit_depth>8) {
		// TODO: Some (rare?) 16-bpp images *can* be perfectly converted to palette images.
		pngrw_print_error(ctx,PNGRW_TEXT("This image can't be converted because it has 16 bits/sample."));
		goto done;
	}


    if (ctx->color_type == PNG_COLOR_TYPE_PALETTE) {
		ctx->ori_pal_size=0;
		ori_bpp = ctx->bit_depth;

		/* we only do this to get the palette size so we can print it */
		png_get_PLTE(png_ptr,info_ptr,&ori_pal,&ctx->ori_pal_size);

		pngrw_print_info(ctx, PNGRW_TEXT("original palette size:   %3d, %2d bpp"),ctx->ori_pal_size,ori_bpp);

        png_set_expand(png_ptr);
	}
	else {
		/* figure out bits per pixel so we can print it */
		ori_bpp= ctx->bit_depth*png_get_channels(png_ptr,info_ptr);
		pngrw_print_info(ctx, PNGRW_TEXT("original palette size: [n/a], %2d bpp"),ori_bpp);
	}

    if (ctx->color_type == PNG_COLOR_TYPE_GRAY && ctx->bit_depth < 8)
        png_set_expand(png_ptr);
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        png_set_expand(png_ptr);
	/* if (bit_depth == 16)
        png_set_strip_16(png_ptr); */
    if (ctx->color_type == PNG_COLOR_TYPE_GRAY ||
        ctx->color_type == PNG_COLOR_TYPE_GRAY_ALPHA) {
        png_set_gray_to_rgb(png_ptr);
	}

	if (png_get_bKGD(png_ptr, info_ptr, &image_background)) {
		ctx->has_bKGD=1;
		ctx->bkgd.red=   (unsigned char)image_background->red;
		ctx->bkgd.green= (unsigned char)image_background->green;
		ctx->bkgd.blue=  (unsigned char)image_background->blue;
		ctx->bkgd.alpha=255;
	}

	pngrw_record_ancillary_chunks_beforeimage(ctx,png_ptr,info_ptr);

    png_read_update_info(png_ptr, info_ptr);

	ctx->rowbytes=png_get_rowbytes(png_ptr, info_ptr);
	ctx->channels=(int)png_get_channels(png_ptr, info_ptr);

	if(ctx->channels<3 || ctx->channels>4) {
		pngrw_print_error(ctx,PNGRW_TEXT("internal error: channels=%d"),ctx->channels);
		goto done;
	}

	pngrw_print_info(ctx,PNGRW_TEXT("Image size is %dx%d  memory required=%.3fMB"),
		(int)ctx->width, (int)ctx->height,
		(ctx->height*ctx->rowbytes + ctx->height*sizeof(unsigned char*) + ctx->width*ctx->height) / (1024. * 1024.) );

	ctx->image1= (unsigned char*)malloc(ctx->height*ctx->rowbytes);
	ctx->row_pointers1 = (unsigned char**)malloc(ctx->height * sizeof(unsigned char*));
	if(!ctx->image1 || !ctx->row_pointers1) {
		pngrw_print_error(ctx,PNGRW_TEXT("Unable to allocate memory for image"));
		goto done;
	}

	for(i=0;i<(int)ctx->height;i++) {
		ctx->row_pointers1[i] = &ctx->image1[ctx->rowbytes*i];
	}

	png_read_image(png_ptr, ctx->row_pointers1);

	png_read_end(png_ptr, info_ptr);

	pngrw_record_ancillary_chunks_afterimage(ctx,png_ptr,info_ptr);

	retval = 1;

done:
	if(png_ptr) {
		png_destroy_read_struct(&png_ptr, &info_ptr, (png_infopp)NULL);
	}

	return retval;
}

static void pngrw_write_ancillary_chunks(struct pngrw_ctx *ctx,
	png_structp png_ptr, png_infop info_ptr)
{
	if(ctx->has_gAMA) png_set_gAMA(png_ptr, info_ptr, ctx->image_gamma);
	if(ctx->has_sRGB) png_set_sRGB(png_ptr, info_ptr, ctx->srgb_intent);
	if(ctx->has_pHYs) png_set_pHYs(png_ptr, info_ptr, ctx->res_x, ctx->res_y, ctx->res_unit_type);

	/*  S.A.  ............................  */
	if(ctx->has_tIME) {
		png_set_tIME(png_ptr, info_ptr, &ctx->savechunk_time);
	}
}

static int pngrw_write_new_png(struct pngrw_ctx *ctx, FILE *outfp)
{
    png_structp png_ptr = NULL;
    png_infop  info_ptr = NULL;
	png_color palette[256];
	unsigned char trans[256];
	int num_trans;
	int i;
	png_color_16 newtrns;
	png_color_16 newbackground;
	struct errstruct errinfo;
	int retval = 0;

	memset(&newtrns      ,0,sizeof(png_color_16));
	memset(&newbackground,0,sizeof(png_color_16));

	pngrw_StringCchCopy(errinfo.errmsg,200,PNGRW_TEXT(""));

    png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING,
		(void*)(&errinfo),my_png_error_fn, my_png_warning_fn);
	if (!png_ptr) {
		pngrw_print_error(ctx,PNGRW_TEXT("Error creating write_struct"));
		goto done;
	}

    info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr) {
		pngrw_print_error(ctx,PNGRW_TEXT("Error creating write_info_struct"));
        goto done;
    }

	if (setjmp(errinfo.jbuf)) {
		pngrw_print_error(ctx,PNGRW_TEXT("PNG encode error: %s"), errinfo.errmsg);
		goto done;
	}

	png_set_compression_level(png_ptr,9);
	png_set_compression_buffer_size(png_ptr, 1024*1024);
	png_init_io(png_ptr, outfp);

	if( ctx->valid_gray ) {
	    png_set_IHDR(png_ptr, info_ptr, ctx->width, ctx->height, ctx->new_bit_depth,
			 PNG_COLOR_TYPE_GRAY, ctx->interlace_type,
			 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

		if(ctx->gray_trns_target_palentry>=0) {
			newtrns.gray = ctx->gray_trns_target_palentry;
			png_set_tRNS(png_ptr, info_ptr, NULL, 1, &newtrns);
		}

	}
	else {
	    png_set_IHDR(png_ptr, info_ptr, ctx->width, ctx->height, ctx->new_bit_depth,
			 PNG_COLOR_TYPE_PALETTE, ctx->interlace_type,
			 PNG_COMPRESSION_TYPE_BASE, PNG_FILTER_TYPE_BASE);

		/* ... set palette colors ... */

		num_trans=0;  /* number of (fully or partially) transparent palette entries */

		for(i=0;i<ctx->pal_used;i++) {
			palette[i].red=ctx->pal[i].red;
			palette[i].green=ctx->pal[i].green;
			palette[i].blue=ctx->pal[i].blue;

			trans[i]=ctx->pal[i].alpha;
			if(trans[i]<255) num_trans=i+1;
		}
		png_set_PLTE(png_ptr, info_ptr, palette, ctx->pal_used);

		if(num_trans>0) {
			png_set_tRNS(png_ptr, info_ptr, trans, num_trans, 0);
		}
	}

	if(ctx->has_bKGD) {
		if(ctx->valid_gray)
			newbackground.gray = ctx->bkgd_pal;
		else
			newbackground.index = ctx->bkgd_pal;
		png_set_bKGD(png_ptr, info_ptr, &newbackground);
	}

	pngrw_write_ancillary_chunks(ctx,png_ptr,info_ptr);

	png_write_info(png_ptr, info_ptr);

	png_set_packing(png_ptr);

	png_write_image(png_ptr, ctx->row_pointers2);

	png_write_end(png_ptr, info_ptr);

	retval = 1;

done:

	if(png_ptr) {
		png_destroy_write_struct(&png_ptr, &info_ptr);
	}
	return retval;
}


/* set ignorealpha to 1 if you don't care if the alpha value matches
 * (for the background) */
static int
add_to_palette(struct pngrw_ctx *ctx, const struct pal_entry_info *color, int ignorealpha)
{
	if(ctx->pal_used>=256) {
		pngrw_print_error(ctx,PNGRW_TEXT("This image can't be converted because it has more than 256 colors."));
		return 0;
	}
	ctx->pal[ctx->pal_used].red   = color->red;
	ctx->pal[ctx->pal_used].green = color->green;
	ctx->pal[ctx->pal_used].blue  = color->blue;
	ctx->pal[ctx->pal_used].alpha = ignorealpha?255:color->alpha;
	ctx->pal[ctx->pal_used].count = 1;
	ctx->pal_used++;
	return 1;
}

#if 0
static void debug_print_pal(struct pngrw_ctx *ctx)
{
	int i;
	for(i=0;i<ctx->pal_used;i++) {
		_ftprintf(stderr, PNGRW_TEXT("%d. %d %d %d %d\n"),i,
			ctx->pal[i].red,ctx->pal[i].green,ctx->pal[i].blue,ctx->pal[i].alpha);
	}
}
#endif


/* Sort the palette to put transparent colors first,
 * then sort by how frequently the color is used.  Sorting by frequency
 * will often help the png filters make the image more compressible.
 * It also makes it easier for people to see which colors aren't used much
 * and allow them to manually reduce the color palette. */
static int palsortfunc_internal(const void* p1, const void* p2, int byfreq)
{
	struct pal_entry_info *e1,*e2;
	int s1,s2;

	e1=(struct pal_entry_info*)p1;
	e2=(struct pal_entry_info*)p2;

	if(e1->alpha==255 && e2->alpha<255) return 1;
	if(e1->alpha<255 && e2->alpha==255) return -1;

	if(byfreq) {
		if(e1->count<e2->count) return 1;
		if(e1->count>e2->count) return -1;
	}

	s1=e1->red+e1->green+e1->blue;
	s2=e2->red+e2->green+e2->blue;
	if(s1>s2) return 1;
	if(s1<s2) return -1;
	return 0;
}

static int palsortfunc(const void* p1, const void* p2)
{
	return palsortfunc_internal(p1,p2,0);
}

static int palsortfunc_byfreq(const void* p1, const void* p2)
{
	return palsortfunc_internal(p1,p2,1);
}

static void reset_pal_entry_cache(struct pngrw_ctx *ctx)
{
	ctx->prev_entry_valid = 0;
}

#define PNGRW_ALPHA_MUST_MATCH  0
#define PNGRW_IGNORE_ALPHA      1

static int find_pal_entry(struct pngrw_ctx *ctx, unsigned char r,
     unsigned char g, unsigned char b, unsigned char a, int ignorealpha)
{
	int i;

	if(ctx->prev_entry_valid && ctx->prev_r==r && ctx->prev_g==g  &&
	   ctx->prev_b==b && ctx->prev_a==a)
	{
	    return ctx->prev_entry;
	}

	for(i=0;i<ctx->pal_used;i++) {
		if(ctx->pal[i].red==r && ctx->pal[i].green==g && ctx->pal[i].blue==b
		   && (ctx->pal[i].alpha==a || ignorealpha)) {

		    ctx->prev_r = r;
		    ctx->prev_g = g;
		    ctx->prev_b = b;
		    ctx->prev_a = a;
		    ctx->prev_entry = i;

		    return i;
		}
	}
	return (-1);
}

// Same as find_pal_entry(), but with different parameters.
static int find_pal_entry_p(struct pngrw_ctx *ctx, struct pal_entry_info *e,
							int ignorealpha)
{
	return find_pal_entry(ctx,e->red,e->green,e->blue,e->alpha,ignorealpha);
}

// Call this when encountering a pixel or background color.
// Adds the color to the palette if necessary, and does other
// bookkeeping.
static int pngrw_process_new_color(struct pngrw_ctx *ctx, struct pal_entry_info *e,
		int ignorealpha)
{
	int palent;

	palent = find_pal_entry_p(ctx,e,ignorealpha);
	if(palent<0) {
		if(!add_to_palette(ctx,e,ignorealpha))
			return 0;
	}
	else
		ctx->pal[palent].count++;

	return 1;
}

// Scans the pixels to figure out what colors are used, and
// adds the colors found to the target palette.
// Returns 0 on failure (e.g. too many colors for the palette).
static int pngrw_process_pixels(struct pngrw_ctx *ctx)
{
	int x,y;
	unsigned char *p;
	struct pal_entry_info thispix;

	for(y=0;y<(int)ctx->height;y++) {
		for(x=0;x<(int)ctx->width;x++) {
			p=&ctx->row_pointers1[y][x*ctx->channels];
			thispix.red=p[0];
			thispix.green=p[1];
			thispix.blue=p[2];
			if(ctx->channels==4) thispix.alpha=p[3];
			else thispix.alpha=255;

			if(!pngrw_process_new_color(ctx,&thispix,0)) return 0;
		}
	}
	return 1;
}

static int pngrw_process_bkgd_color(struct pngrw_ctx *ctx)
{
	if(!ctx->has_bKGD) return 1;
	if(!pngrw_process_new_color(ctx,&ctx->bkgd,1)) return 0;
	return 1;
}

// Figure out if this is a valid grayscale image.
// If it is returns nonzero, and may set ctx->gray_trns_orig_palentry.
static int pngrw_is_valid_grayscale(struct pngrw_ctx *ctx)
{
	int i;
	unsigned char gray_shade; // Gray shade of the palette entry being examined.

	ctx->gray_trns_orig_palentry = -1;

	for( i = 0; i < ctx->pal_used; i++ ) {

	    if( ctx->pal[i].red != ctx->pal[i].green
		   || ctx->pal[i].red != ctx->pal[i].blue)
	    {
			// Found a pixel that's not gray.
			return 0;
	    }

		gray_shade = ctx->pal[i].red;

		// Check for partial transparency, which isn't supported in grayscale images
		// (we don't support writing grayscale+alpha images).
		if(ctx->pal[i].alpha!=255 && ctx->pal[i].alpha!=0) {
			return 0;
		}

		// Check for binary transparency.
		if(ctx->pal[i].alpha == 0) {
			if(ctx->gray_trns_orig_palentry != -1) {
				// Multiple transparent colors.
				return 0;
			}
			ctx->gray_trns_orig_palentry = i;  // binary transparency ok (so far)
			ctx->gray_trns_shade = gray_shade;
		}

		// Check if the gray shade is one that can be represented at the
		// target bit depth.
		switch(ctx->new_bit_depth) {
		case 1:
			if(gray_shade%255) return 0;
			break;
		case 2:
			if(gray_shade%85) return 0;
			break;
		case 4:
			if(gray_shade%17) return 0;
		}
	}

	// One thing the above doesn't check for is a nontransparent
	// grayscale pixel that's the same as the transparent grayscale color.
	// In this case we have to use palette color.
	if(ctx->gray_trns_orig_palentry != -1) {
		if(-1 != find_pal_entry(ctx,ctx->gray_trns_shade,ctx->gray_trns_shade,ctx->gray_trns_shade,255,PNGRW_ALPHA_MUST_MATCH)) {
			return 0;
		}
	}
	return 1;
}

static int pngrw_create_grayscale_palette(struct pngrw_ctx *ctx)
{
	int i;
	int step=1;

	switch(ctx->new_bit_depth) {
	case 1:  step=255; ctx->pal_used=2;   break;
	case 2:  step=85;  ctx->pal_used=4;   break;
	case 4:  step=17;  ctx->pal_used=16;  break;
	default: step=1;   ctx->pal_used=256;
	}

	for(i=0;i<ctx->pal_used;i++) {
		ctx->pal[i].red = ctx->pal[i].green = ctx->pal[i].blue = i*step;
		ctx->pal[i].alpha = 255;
	}

	// Translate the binary transparency setting to the new palette.
	if(ctx->gray_trns_orig_palentry != -1) {
		ctx->gray_trns_target_palentry=find_pal_entry(ctx,ctx->gray_trns_shade,ctx->gray_trns_shade,ctx->gray_trns_shade,255,PNGRW_ALPHA_MUST_MATCH);
		if(ctx->gray_trns_target_palentry == -1) {
			pngrw_print_error(ctx,PNGRW_TEXT("internal error: can't find transparent grayscale color"));
			return 0;
		}
		ctx->pal[ctx->gray_trns_target_palentry].alpha = 0;
	}

	return 1;
}

// The core function that creates the new image.
// Allocates and writes to ctx->image2, and sets ctx->bkgd_pal.
static int pngrw_generate_optimized_image(struct pngrw_ctx *ctx)
{
	unsigned char *p;
	int x,y;
	int palent;

	ctx->image2 = (unsigned char*)malloc(ctx->width*ctx->height);
	ctx->row_pointers2 = (unsigned char**)malloc(ctx->height * sizeof(unsigned char*));
	if(!ctx->image2 || !ctx->row_pointers2) {
		pngrw_print_error(ctx,PNGRW_TEXT("out of memory"));
		return 0;
	}

	for(y=0;y<(int)ctx->height;y++) {
		ctx->row_pointers2[y]= &ctx->image2[y*ctx->width];
	}

	for(y=0;y<(int)ctx->height;y++) {
		for(x=0;x<(int)ctx->width;x++) {
			p=&ctx->row_pointers1[y][x*ctx->channels];

			palent = find_pal_entry(ctx,p[0],p[1],p[2],(unsigned char)((ctx->channels==4)?p[3]:255),PNGRW_ALPHA_MUST_MATCH);
			if(palent<0) {
				pngrw_print_error(ctx,PNGRW_TEXT("internal error: can't locate palette entry"));
				return 0;
			}
			ctx->row_pointers2[y][x] = (unsigned char)palent;
		}
	}

	if(ctx->has_bKGD) {
		palent = find_pal_entry_p(ctx,&ctx->bkgd,PNGRW_IGNORE_ALPHA);
		if(palent<0) {
			pngrw_print_error(ctx,PNGRW_TEXT("internal error: can't locate palette entry for bkgd"));
			return 0;
		}
		ctx->bkgd_pal = (unsigned char)palent;
	}

	return 1;
}
int pngrw_optimize_image(struct pngrw_ctx *ctx)
{
	ctx->gray_trns_target_palentry = -1;
	ctx->gray_trns_shade=0;
	ctx->pal_used=0;

	if(!pngrw_process_pixels(ctx)) return 0;

	if(!pngrw_process_bkgd_color(ctx)) return 0;

	/* Decide on the target bit depth. */
	ctx->new_bit_depth=8;
	if(ctx->pal_used<=16) ctx->new_bit_depth=4;
	if(ctx->pal_used<=4) ctx->new_bit_depth=2;
	if(ctx->pal_used<=2) ctx->new_bit_depth=1;

	/* figure out if this is a valid grayscale image */
	ctx->valid_gray = pngrw_is_valid_grayscale(ctx);

	if(ctx->valid_gray) {
		// If grayscale, create a "fake" palette consisting of all
		// available gray shades.
		if(!pngrw_create_grayscale_palette(ctx)) return 0;

	}
	else {
		// If color, put the palette in a good order.
		qsort((void*)ctx->pal,ctx->pal_used,sizeof(struct pal_entry_info),
			ctx->pal_sort_by_frequency?palsortfunc_byfreq:palsortfunc);
	}

	if(ctx->valid_gray)
		pngrw_print_info(ctx, PNGRW_TEXT("saving as grayscale:          %2d bpp"),ctx->new_bit_depth);
	else
		pngrw_print_info(ctx, PNGRW_TEXT("new palette size:        %3d, %2d bpp"),ctx->pal_used,ctx->new_bit_depth);

	reset_pal_entry_cache(ctx);

	/* debug_print_pal(ctx); */

	if(!pngrw_generate_optimized_image(ctx)) return 0;

	return 1;
}

// Sets default values for everything.
static void init_ctx(struct pngrw_ctx *ctx)
{
	memset(ctx,0,sizeof(struct pngrw_ctx));
	ctx->pal_sort_by_frequency=1;
}

static void free_ctx_contents(struct pngrw_ctx *ctx)
{
	if(ctx->row_pointers1) free(ctx->row_pointers1);
	if(ctx->row_pointers2) free(ctx->row_pointers2);
	if(ctx->image1) free(ctx->image1);
	if(ctx->image2) free(ctx->image2);
}

struct pngrw_ctx *pngrw_create(void)
{
	struct pngrw_ctx *ctx;
	ctx = (struct pngrw_ctx*)calloc(sizeof(struct pngrw_ctx),1);
	if(!ctx) return NULL;
	init_ctx(ctx);
	return ctx;
}

void pngrw_destroy(struct pngrw_ctx *ctx)
{
	if(ctx) {
		free_ctx_contents(ctx);
		free(ctx);
	}
}

int pngrw_read_stdio(struct pngrw_ctx *ctx, FILE *infp)
{
	return pngrw_read_png(ctx,infp);
}

int pngrw_write_stdio(struct pngrw_ctx *ctx, FILE *outfp)
{
	return pngrw_write_new_png(ctx,outfp);
}

int pngrw_read_filename(struct pngrw_ctx *ctx, const PNGRW_CHAR *in_filename)
{
	FILE *infp = NULL;
	int ret;

	infp = _tfopen(in_filename, PNGRW_TEXT("rb"));
	if(!infp) {
		pngrw_print_error(ctx,PNGRW_TEXT("Failed to open file for reading"));
		return 0;
	}

	ret = pngrw_read_png(ctx,infp);
	fclose(infp);
	return ret;
}

int pngrw_write_filename(struct pngrw_ctx *ctx, const PNGRW_CHAR *out_filename)
{
	FILE *outfp = NULL;
	int ret;

	outfp = _tfopen(out_filename, PNGRW_TEXT("wb"));
	if(!outfp) {
		pngrw_print_error(ctx,PNGRW_TEXT("Failed to open file for writing"));
		return 0;
	}

	ret = pngrw_write_new_png(ctx,outfp);
	fclose(outfp);
	return ret;
}

void pngrw_set_print_fn(struct pngrw_ctx *ctx, pngrw_print_fn_type prntfn)
{
	ctx->printfn = prntfn;
}

void pngrw_set_error_fn(struct pngrw_ctx *ctx, pngrw_print_fn_type errfn)
{
	ctx->errorfn = errfn;
}

void pngrw_set_userdata(struct pngrw_ctx *ctx, void *u)
{
	ctx->userdata = u;
}

void *pngrw_get_userdata(struct pngrw_ctx *ctx)
{
	return ctx->userdata;
}

void pngrw_set_sort_by_frequency(struct pngrw_ctx *ctx, int n)
{
	ctx->pal_sort_by_frequency = n;
}

const PNGRW_CHAR *pngrw_get_version_string(void)
{
	return PNGREWRITEVERSION;
}
