/*
	Simple Image Loading Library (loads PNG and JPEG images from files)
	Jason A. Petrasko, Public Domain 
*/

#include "sill.h"
#include <math.h>
#include <stdlib.h>
#include <stdio.h>
#include <png/png.h>
#include <jpeg/jpeglib.h>

unsigned int sill_uid = 0;

// error jmp handling for libjpeg
struct sill_error_mgr {
  struct jpeg_error_mgr pub;	/* "public" fields */

  jmp_buf setjmp_buffer;	/* for return to caller */
};
typedef struct sill_error_mgr * sill_error_ptr;
METHODDEF(void)
sill_error_exit (j_common_ptr cinfo)
{
  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
  sill_error_ptr myerr = (sill_error_ptr) cinfo->err;
  /* Return control to the setjmp point */
  longjmp(myerr->setjmp_buffer, 1);
}

int sillLoadPNG(sillImage *p, const char *src)
{
	FILE *fp = fopen(src,"rb");
	if (!fp) { p->Error = "bad or missing file"; return 0; }
	
	png_structp png_ptr = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	if (!png_ptr) { p->Error = "sill internal error (png_ptr construction)"; fclose(fp); return 0; }
	png_infop info_ptr = png_create_info_struct(png_ptr);
	if (!info_ptr) { p->Error = "sill internal error (info_ptr construction)"; fclose(fp); return 0; }
	png_infop end_info = png_create_info_struct(png_ptr);
	if (!end_info) { p->Error = "sill internal error (end_info construction)"; fclose(fp); return 0; }
	if (setjmp(png_jmpbuf(png_ptr)))
	{
		png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
		p->Error = "png read error";
		fclose(fp); 
		return 0;
	}
	
	png_init_io(png_ptr, fp);
	png_set_sig_bytes(png_ptr, 0);
	png_read_png(png_ptr, info_ptr, PNG_TRANSFORM_STRIP_16 | PNG_TRANSFORM_EXPAND, png_voidp_NULL);

	// ok, let get the image data
	switch (info_ptr->color_type)
	{
		case PNG_COLOR_TYPE_GRAY: p->Format = SIF_GREYSCALE; break;
		case PNG_COLOR_TYPE_GRAY_ALPHA: p->Format = SIF_GREYSCALE_ALPHA; break;
		case PNG_COLOR_TYPE_RGB: p->Format = SIF_COLOR; break;
		case PNG_COLOR_TYPE_RGB_ALPHA: p->Format = SIF_COLOR_ALPHA; break;
		default: p->Format = SIF_INVALID; break;
	}

	p->Width = info_ptr->width;
	p->Height = info_ptr->height;

	png_bytep *row;
	int l = 0;
	unsigned int comp = p->Format / 8, stride = p->Width * comp;
	unsigned char *target;
	row = png_get_rows(png_ptr, info_ptr);


	// get the pixels into our pixel buffer based on the type
	p->Pixels = (unsigned char*)malloc(p->Width * p->Height * comp);
	memset(p->Pixels,0,p->Width * p->Height * comp);
	target = p->Pixels;
	while (l < p->Height)
	{
		memcpy(target,row[l++],stride);
		target += stride;
	}

	png_destroy_read_struct(&png_ptr, &info_ptr, png_infopp_NULL);
	p->Source = strdup(src);
	p->Error = 0;
	return 1;
}

int sillLoadJPEG(sillImage *p, const char *src)
{
	FILE *fp = fopen(src,"rb");
	if (!fp) { p->Error = "bad or missing file"; return 0; }
	
	struct jpeg_decompress_struct cinfo;
	struct sill_error_mgr jerr;
	JSAMPARRAY buffer;		/* Output row buffer */
	int row_stride;			/* physical row width in output buffer */

	cinfo.err = jpeg_std_error(&jerr.pub);
	jerr.pub.error_exit = sill_error_exit;
	if (setjmp(jerr.setjmp_buffer)) {
		jpeg_destroy_decompress(&cinfo);
		p->Error = "jpeg read error";
		fclose(fp);
		return 0;
	}
	jpeg_create_decompress(&cinfo);
	jpeg_stdio_src(&cinfo, fp);
	jpeg_read_header(&cinfo, TRUE);
	cinfo.out_color_space = JCS_RGB;
	jpeg_start_decompress(&cinfo);
	row_stride = cinfo.output_width * cinfo.output_components;

		/* Make a one-row-high sample array that will go away when done with image */
	buffer = (*cinfo.mem->alloc_sarray)((j_common_ptr) &cinfo, JPOOL_IMAGE, row_stride, 1);

	int w = cinfo.output_width;
	int h = cinfo.output_height;
	// find our power of two
	p->Format = SIF_COLOR;
	p->Pixels = (unsigned char*)malloc(w * h * 3);
	memset(p->Pixels,0,w*h*3);

	while (cinfo.output_scanline < cinfo.output_height)
	{
		if (jpeg_read_scanlines(&cinfo, buffer, 1) > 0)
			memcpy(&p->Pixels[(cinfo.output_scanline-1)*w*3],buffer[0],row_stride);
	}
	jpeg_finish_decompress(&cinfo);
	jpeg_destroy_decompress(&cinfo);

	p->Width = w;
	p->Height = h;
	p->Source = strdup(src);
	p->Error = 0;
	return 1;
}

sillImage *sillLoadFile(const char *src)
{
	sillImage *ret = (sillImage*)malloc(sizeof(sillImage));
	if (ret == 0) return NULL;
	memset(ret,0,sizeof(sillImage));

	if (sillLoadPNG(ret,src)) return ret;
	 else
	if (sillLoadJPEG(ret,src)) return ret;
	 else
	 {
		 ret->Error = "corrupt or unsupported file";
		 return ret;
	 }	 
}

int sillIsValid(sillImage *p)
{
	if (!p) return 0;
	if (p->Format == SIF_INVALID) return 0;
	return 1;
}

sillImage *sillBlank(int w, int h, int f)
{
	sillImage *ret = (sillImage*)malloc(sizeof(sillImage));
	if (ret == 0) return NULL;	
	memset(ret,0,sizeof(sillImage));
	ret->UniqueId = sill_uid++;
	ret->Width = w;
	ret->Height = h;
	ret->Format = f;
	ret->Pixels = (unsigned char*)malloc((f/8)*w*h);
	ret->Source = strdup("unnamed.png");
	memset(ret->Pixels,0,(f/8)*w*h);
	return ret;
}

sillImage *sillDuplicate(sillImage *p)
{
	sillImage *ret = (sillImage*)malloc(sizeof(sillImage));
	if (ret == 0) return NULL;	
	memset(ret,0,sizeof(sillImage));
	ret->UniqueId = sill_uid++;
	ret->Width = p->Width;
	ret->Height = p->Height;
	ret->Format = p->Format;
	ret->Pixels = (unsigned char*)malloc((p->Format/8)*p->Width*p->Height);
	memcpy(ret->Pixels,p->Pixels,(p->Format/8)*p->Width*p->Height);
	if (p->Source) ret->Source = strdup(p->Source);
	 else ret->Source = NULL;
	ret->Error = p->Error;
	ret->UserFlags = p->UserFlags;
	return ret;
}

void sillFree(sillImage **p)
{
	if (*p == NULL) return;
	if ((*p)->Source) free((*p)->Source);
	if ((*p)->Pixels) free((*p)->Pixels);
	free(*p);
	*p = NULL;
}

void sillSize(sillImage *p, int nw, int nh)
{
	sillImage *pn = sillBlank(nw,nh,p->Format);
	if (pn == 0) return;
	unsigned char *dst = pn->Pixels, *src = p->Pixels;
	int lines = nh;
	unsigned int cs, ds, ss;
	
	ds = pn->Width * (pn->Format / 8);
	ss = p->Width * (p->Format / 8);
	cs = ds;
	
	if (cs > ss) cs = ss;
	if (lines > p->Height) lines = p->Height;
	// now, lets move some pixels
	while (lines--)
	{
		memcpy(dst,src,cs);
		dst += ds;
		src += ss;
	}
	
	pn->Source = p->Source;
	pn->Error = p->Error;
	pn->UserFlags = p->UserFlags;
	// destroy the old image and insert the new one into the target
	sillFree(&p);
	p = (sillImage*)malloc(sizeof(sillImage));
	memcpy(p,pn,sizeof(sillImage));
}

void sillForceAlpha(sillImage *p)
{
	if (p->Format == SIF_GREYSCALE)
	{
		// rebuild into SIF_GREYSCALE_ALPHA
		unsigned char *np = (unsigned char*)malloc(p->Width*p->Height*2);
		unsigned char *src = p->Pixels, *dst = np;
		unsigned char *end = p->Pixels + (p->Width * p->Height);
		
		while (src != end)
		{
			*dst = *src;
			dst++; src++;
			*dst = 255;
			dst++;
		}
		
		free(p->Pixels);
		p->Pixels = np;
		p->Format = SIF_GREYSCALE_ALPHA;
	} else if (p->Format == SIF_COLOR)
	{
		// rebuild into SIF_COLOR_ALPHA
		unsigned char *np = (unsigned char*)malloc(p->Width*p->Height*4);
		unsigned char *src = p->Pixels, *dst = np;
		unsigned char *end = p->Pixels + (p->Width * p->Height * 3);
		
		while (src != end)
		{
			*dst = *src;
			dst++; src++;
			*dst = *src;
			dst++; src++;
			*dst = *src;
			dst++; src++;
			*dst = 255;
			dst++;
		}
		
		free(p->Pixels);
		p->Pixels = np;
		p->Format = SIF_COLOR_ALPHA;	
	}
}

void sillAlphaByLuminance(sillImage *p, float vpow)
{
	sillForceAlpha(p);
	
	if (p->Format == SIF_GREYSCALE_ALPHA)
	{
		register unsigned char *src = p->Pixels, *end = p->Pixels + (p->Width * p->Height * 2);
		while (src != end)
		{
			register float f = (float)*src * 0.003921568627450980392156862745098f; 
			src++;
			f = pow(f,vpow);
			*src = (unsigned char)(f * 255.0f);
			src++;
		}
	} else if (p->Format == SIF_COLOR_ALPHA)
	{
		register unsigned char *src = p->Pixels, *end = p->Pixels + (p->Width * p->Height * 4);
		while (src != end)
		{
			float r = (float)*src * 0.003921568627450980392156862745098f; 
			src++;
			float g = (float)*src * 0.003921568627450980392156862745098f; 
			src++;
			float b = (float)*src * 0.003921568627450980392156862745098f; 
			src++;
			*src = (unsigned char)(pow(r*0.3f + g*0.59f+ b*0.11f,vpow) * 255.0f);
			src++;
		}		
	}
}

const char *sillSavePng(sillImage *p, const char *dst)
{
	if (p->Format == SIF_INVALID) return "image is invalid";
	if (dst == 0) dst = p->Source;
	
	FILE *fp = fopen(dst, "wb");
    if (!fp) return "bad filename or disk full";
    
    png_structp png_ptr = png_create_write_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
    if (!png_ptr) { fclose(fp); return "internal png error (png_ptr)"; }
    png_infop info_ptr = png_create_info_struct(png_ptr);
    if (!info_ptr)
    {
       png_destroy_write_struct(&png_ptr,(png_infopp)NULL);
       fclose(fp);
       return "internal png error (info_ptr)";
    }
    if (setjmp(png_jmpbuf(png_ptr)))
    {
       png_destroy_write_struct(&png_ptr, &info_ptr);
       fclose(fp);
       return "png write error";
    }

    int color_type;
    switch (p->Format)
    {
	    case SIF_GREYSCALE: color_type = PNG_COLOR_TYPE_GRAY; break;
	    case SIF_GREYSCALE_ALPHA: color_type = PNG_COLOR_TYPE_GRAY_ALPHA; break;
	    case SIF_COLOR: color_type = PNG_COLOR_TYPE_RGB; break;
	    case SIF_COLOR_ALPHA: color_type = PNG_COLOR_TYPE_RGB_ALPHA; break;	    
    }
    
    // configure it all
    png_init_io(png_ptr, fp);
	png_set_IHDR(png_ptr, info_ptr, p->Width, p->Height, 8, color_type, PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT, PNG_FILTER_TYPE_DEFAULT);
   
	// fill the rows
	unsigned int stride = p->Width * (p->Format / 8);
	int i;
	png_bytep *row_pointers;
	row_pointers = png_malloc(png_ptr,p->Height*sizeof(png_bytep));
   	for (i=0; i < p->Height; i++) row_pointers[i] = (png_bytep)&p->Pixels[stride*i];
   	png_set_rows(png_ptr, info_ptr, row_pointers);
   
	// write the png
	png_write_png(png_ptr, info_ptr, PNG_TRANSFORM_IDENTITY, NULL);
	
	png_free(png_ptr, row_pointers);
	png_destroy_write_struct(&png_ptr, &info_ptr);
	fclose(fp);
    return NULL;
}

void sillForceGrey(sillImage *p)
{
	if ((p->Format == SIF_GREYSCALE) || (p->Format == SIF_GREYSCALE_ALPHA)) return;
	
	if (p->Format == SIF_COLOR)
	{
		// rebuild into SIF_GREYSCALE
		unsigned char *np = (unsigned char*)malloc(p->Width*p->Height);
		unsigned char *src = p->Pixels, *dst = np;
		unsigned char *end = p->Pixels + (p->Width * p->Height*3);
		
		while (src != end)
		{
			unsigned char r = *src; src++;
			unsigned char g = *src; src++;
			unsigned char b = *src; src++;
			*dst = (unsigned char)((float)r*0.3f + (float)g*0.59f+ (float)b*0.11f);
			dst++;
		}
		
		free(p->Pixels);
		p->Pixels = np;
		p->Format = SIF_GREYSCALE;
	} else if (p->Format == SIF_COLOR_ALPHA)
	{
		// rebuild into SIF_GREYSCALE_ALPHA
		unsigned char *np = (unsigned char*)malloc(p->Width*p->Height*2);
		unsigned char *src = p->Pixels, *dst = np;
		unsigned char *end = p->Pixels + (p->Width * p->Height*4);
		
		while (src != end)
		{
			unsigned char r = *src; src++;
			unsigned char g = *src; src++;
			unsigned char b = *src; src++;
			*dst = (unsigned char)((float)r*0.3f + (float)g*0.59f+ (float)b*0.11f);
			dst++;
			*dst = *src;
			dst++; src++;
		}
		
		free(p->Pixels);
		p->Pixels = np;
		p->Format = SIF_GREYSCALE_ALPHA;		
	}
}

void sillForceColor(sillImage *p)
{
	if ((p->Format == SIF_COLOR) || (p->Format == SIF_COLOR_ALPHA)) return;
	
	if (p->Format == SIF_GREYSCALE)
	{
		// rebuild into SIF_COLOR
		unsigned char *np = (unsigned char*)malloc(p->Width*p->Height*3);
		unsigned char *src = p->Pixels, *dst = np;
		unsigned char *end = p->Pixels + (p->Width * p->Height);
		
		while (src != end)
		{
			unsigned char v = *src;
			*dst = v; dst++;
			*dst = v; dst++;
			*dst = v; dst++;
			src++;
		}
		
		free(p->Pixels);
		p->Pixels = np;
		p->Format = SIF_COLOR;		
	} else if (p->Format == SIF_GREYSCALE_ALPHA)
	{
		// rebuild into SIF_COLOR_ALPHA
		unsigned char *np = (unsigned char*)malloc(p->Width*p->Height*4);
		unsigned char *src = p->Pixels, *dst = np;
		unsigned char *end = p->Pixels + (p->Width * p->Height * 2);
		
		while (src != end)
		{
			unsigned char v = *src;
			*dst = v; dst++;
			*dst = v; dst++;
			*dst = v; dst++;
			src++;
			*dst = *src;
			dst++; src++;
		}
		
		free(p->Pixels);
		p->Pixels = np;
		p->Format = SIF_COLOR_ALPHA;		
	}
}


