/******************************************************************************
flt_PNG.cpp : Defines the entry point for the DLL application.

Copyright John Paul Chacha. All Rights Reserved.

This source code file, which has been provided by John Paul Chacha as part of 
his software product for use only by licensed users of that product, includes 
proprietary information of John Paul Chacha.

USE OF THIS SOFTWARE IS GOVERNED BY THE TERMS AND CONDITIONS OF THE LICENSE 
AGREEMENT FURNISHED WITH THE PRODUCT.

IN PARTICULAR, JOHN PAUL CHACHA SHALL BE FREE FROM ANY CLAIMS OR LIABILITIES 
ARISING OUT OF THE USE OR MISUSE OF THIS FILE AND/OR ITS CONTENTS.
******************************************************************************/
#include "../common/system.h"
#include "../common/plugin.h"
#include "../common/suite_info.h"
#include "../common/win_util.h"
#include "../common/str_util.h"
#include "../common/markup.h"
#include "../lib/libpng/png.h"
#include "../lib/zlib/zlib.h"
#include "resource.h"
#include "flt_PNG.h"


//=============================================================================
#define PLUGIN_NAME		L"PNG File-format Plug-in"
#define PLUGIN_AUTHOR	SUITE_CREATOR
#define PLUGIN_VERSION	SUITE_VER_INT32

#define MY_STORE_NAME	L"flt_PNG.dll"
#define MY_STORE_UUID	(*((LPDWORD)("_PNG")))

//-----------------------------------------------------------------------------
#define SETTINGS_VERSION ((1<<16)|(1))
struct plugin_config
{
	unsigned long	version;
	bool			always_apply_bKGD;
};

//-----------------------------------------------------------------------------
HINSTANCE api_inst;
plugin_config settings={0,};

LRESULT CALLBACK msg_dlg_about (HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar);
LRESULT CALLBACK msg_dlg_config(HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar);

//=============================================================================
//=============================================================================



/******************************************************************************
WINDOWS (Win32) ENTRY POINT FOR THE DYNAMIC-LINK LIBRARY
******************************************************************************/
BOOL APIENTRY DllMain(
	HANDLE hModule,DWORD ul_reason_for_call,LPVOID lpReserved)
{
	//-------------------------------------------------------------------------
	switch(ul_reason_for_call)
	{
	case DLL_PROCESS_ATTACH:
		api_inst=(HINSTANCE)hModule;
		break;
	case DLL_THREAD_ATTACH:
		break;
	case DLL_THREAD_DETACH:
		break;
	case DLL_PROCESS_DETACH:
		break;
	}
	//-------------------------------------------------------------------------
	UNREFERENCED_PARAMETER(lpReserved);
	return TRUE;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
This function is called to query plugin's name and type
******************************************************************************/
int plg_GetInfo(plg_INFO* info)
{
	info->api_type=PLUGIN_APITYPE_FILE;
	info->api_version=PLUGIN_INTERFACE_VERSION;
	info->plugin_version=PLUGIN_VERSION;
	wcsncpy(info->plugin_name,PLUGIN_NAME,63);
	info->plugin_name[63]=0;
	wcsncpy(info->plugin_author,PLUGIN_AUTHOR,63);
	info->plugin_author[63]=0;
	return 1;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
This function is called to display plugin dialog boxes
******************************************************************************/
int plg_ShowDialog(plg_DIALOG* data)
{
	int i;

	if(!data)
	{
		return PLUGIN_ERR_BAD_PARAM;
	}
	memset(&settings,0,sizeof(plugin_config));
	if(data->pi_StateStore)
	{
		if(data->pi_StateStore->version>=PI_STATESTORE_VERSION)
		{
			data->pi_StateStore->perm_read(
				MY_STORE_NAME,&settings,sizeof(plugin_config));
			if(settings.version!=SETTINGS_VERSION)
			{
				memset(&settings,0,sizeof(plugin_config));
				settings.version=SETTINGS_VERSION;
			}
		}
	}

	switch(data->dialog)
	{
	case PLG_DIALOG_ABOUT:
		DialogBoxParam(api_inst,(LPCTSTR)IDD_ABOUT,data->hwnd,
			(DLGPROC)msg_dlg_about,0);
		return PLUGIN_OKAY;
	case PLG_DIALOG_CONFIG:
		i=DialogBoxParam(api_inst,(LPCTSTR)IDD_CONFIG,data->hwnd,
			(DLGPROC)msg_dlg_config,0);
		if(i>0)
		{
			settings.version=SETTINGS_VERSION;
			i=data->pi_StateStore->perm_write(
				MY_STORE_NAME,&settings,sizeof(plugin_config));
			if(!i)
			{
				MessageBox(data->hwnd,
					L"Error saving configuration data",
					L"Internal Error",MB_OK|MB_ICONERROR);
				break;
			}
		}
		return 1;
	case PLG_DIALOG_HELP:
		return PLUGIN_ERR_NO_SUPPORT;
	}
	return PLUGIN_ERR_BAD_PARAM;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
I'm sure the nice fellows at ->PNG had a good reason for not
using the time-tested 'magic-number' method.
******************************************************************************/
#ifndef png_jmpbuf
#  define png_jmpbuf(png_ptr) ((png_ptr)->jmpbuf)
#endif

int check_if_png(char *file_name, FILE **fp)
{
   char buf[4];
   if((*fp = fopen(file_name, "rb"))==NULL) return 0;
   if(fread(buf,1,4,*fp)!=4) return 0;
   return(!png_sig_cmp((PBYTE)buf,(png_size_t)0,4));
}

wchar_t str_error_list[4096];
void my_png_error(png_structp ppng, png_const_charp error)
{
	wchar_t werror[1024];
	mbstowcs(werror,error,1024);
	wcscat(str_error_list,werror);
	wcscat(str_error_list,L"\r\n");
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
This is the plugin DLL function that Chasys Draw IES calls to check the load 
operation support level for a specified file extension
******************************************************************************/
int flt_CheckLoad(wchar_t* extension)
{
	if(_wcsicmp(extension,L".png")==0) return 100;
	return 0;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
This is the plugin DLL function that Chasys Draw IES calls to check the save 
operation support level for a specified file extension
******************************************************************************/
int flt_CheckSave(wchar_t* extension)
{
	if(_wcsicmp(extension,L".png")==0) return 100;
	return 0;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
This is the plugin DLL function that Chasys Draw IES calls to load images
******************************************************************************/
int flt_LoadImage(flt_IMAGE_L* data)
{
	int bit_depth,color_type,interlace_type,last_error;
	unsigned int row,sig_read=0;
	png_uint_32 width,height;
	png_structp png_ptr;
	png_infop info_ptr;
	png_color_16 my_background;
	png_color_16 *image_background;
	FILE *fp;

	//-------------------------------------------------------------------------
	if(!data)
	{
		return PLUGIN_ERR_BAD_HOST;
	}
	if(!data->pi_BasicImage)
	{
		return PLUGIN_ERR_BAD_HOST;
	}
	if(data->pi_BasicImage->version<PI_BASICIMAGE_VERSION)
	{
		return PLUGIN_ERR_BAD_HOST;
	}

	//-------------------------------------------------------------------------
	memset(&settings,0,sizeof(plugin_config));
	if(data->pi_StateStore)
	{
		if(data->pi_StateStore->version>=PI_STATESTORE_VERSION)
		{
			data->pi_StateStore->perm_read(
				MY_STORE_NAME,&settings,sizeof(plugin_config));
			if(settings.version!=SETTINGS_VERSION)
			{
				memset(&settings,0,sizeof(plugin_config));
				settings.version=SETTINGS_VERSION;
			}
		}
	}

	//-------------------------------------------------------------------------
	png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0);
	if(!png_ptr)
	{
		return PLUGIN_ERR_INTERNAL;
	}

	png_ptr->error_fn=my_png_error;
	str_error_list[0]=0;
	info_ptr=png_create_info_struct(png_ptr);
	if(!info_ptr)
	{
		png_destroy_read_struct(&png_ptr,&info_ptr,png_infopp_NULL);
		return PLUGIN_ERR_INTERNAL;
	}

	last_error=PLUGIN_ERR_INTERNAL;
	if(setjmp(png_jmpbuf(png_ptr)))
	{
		goto err_internal;
	}

	//-------------------------------------------------------------------------
	fp=_wfopen(data->file,L"rb");
	if(!fp)
	{
		return PLUGIN_ERR_FILE_OPEN_R;
	}

	//-------------------------------------------------------------------------
	last_error=PLUGIN_ERR_NO_SUPPORT;
	png_init_io(png_ptr,fp);
	png_set_sig_bytes(png_ptr,sig_read);
	png_read_info(png_ptr,info_ptr);
	png_get_IHDR(png_ptr,info_ptr,&width,&height,
		&bit_depth,&color_type,&interlace_type,int_p_NULL,int_p_NULL);

	//-------------------------------------------------------------------------
	last_error=PLUGIN_ERR_INTERNAL;

	//swap bytes of 16 bit files to small endian
	png_set_swap(png_ptr);

	//strip 16 bit/color files down to 8 bits/color
	png_set_strip_16(png_ptr);

	//extract multiple pixels with bit depths of 1, 2 and 4 from a single byte 
	//into separate bytes (useful for palette and grayscale).
	png_set_packing(png_ptr);

	//invert monochrome to have 0 as white and 1 as black
	png_set_invert_mono(png_ptr);

	//expand paletted colors into true RGB triplets
	if(color_type==PNG_COLOR_TYPE_PALETTE)
	{
		png_set_palette_to_rgb(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_gray_1_2_4_to_8(png_ptr);
	}

	//expand grayscale images to the full 8RGB
	if(color_type==PNG_COLOR_TYPE_GRAY)
	{
		png_set_gray_to_rgb(png_ptr);
	}

	//flip the RGB pixels to BGR (or RGBA to BGRA)
	if(color_type&PNG_COLOR_MASK_COLOR)
	{
		png_set_bgr(png_ptr);
	}

	//expand palette or RGB with tRNS to full alpha channels so the data will 
	//be available as RGBA quartets
	if(png_get_valid(png_ptr,info_ptr,PNG_INFO_tRNS))
	{
		png_set_tRNS_to_alpha(png_ptr);
	}

	//invert the alpha channel
	png_set_invert_alpha(png_ptr);

	//add filler/alpha byte for non-alpha files
	png_set_filler(png_ptr,0,PNG_FILLER_AFTER);

	//-------------------------------------------------------------------------
	last_error=PLUGIN_ERR_INTERNAL;

	my_background.red=0xffff;
	my_background.green=0xffff;
	my_background.blue=0xffff;
	my_background.gray=0xffff;
	my_background.index=0;
	if( (settings.always_apply_bKGD)&&
		(png_get_bKGD(png_ptr,info_ptr,&image_background)))
	{
		png_set_background(png_ptr,image_background,PNG_BACKGROUND_GAMMA_FILE,
			1,1.0);
	}
	else
	{
		png_set_background(png_ptr,&my_background,PNG_BACKGROUND_GAMMA_UNKNOWN,
			0,1.0);
	}

	//-------------------------------------------------------------------------
	//optional call to gamma correct and add the background to
	//the palette and update info structure
	png_read_update_info(png_ptr,info_ptr);

	//-------------------------------------------------------------------------
	//create layer
	last_error=PLUGIN_ERR_INTERNAL;

	data->width=width;
	data->height=height;
	data->meta_data=0;
	if(data->create(data)==0)
	{
		last_error=PLUGIN_ERR_OUT_OF_MEM;
		goto err_internal;
	}

	//insert MARK attachment
	if(info_ptr->valid&PNG_INFO_pHYs)
	{
		if(data->meta_write)
		{
			int ppi_x,ppi_y;
			char markup[3072];
			ppi_x=info_ptr->x_pixels_per_unit;
			ppi_y=info_ptr->y_pixels_per_unit;
			switch(info_ptr->phys_unit_type)
			{
			case PNG_RESOLUTION_METER:
				ppi_x=(int)((((double)ppi_x)*0.0254)+0.5);
				ppi_y=(int)((((double)ppi_y)*0.0254)+0.5);
				break;
			}
			_snprintf(markup,3072,
				"image.ppi=%i,%i;\r\n",
				ppi_x,ppi_y);
			data->meta_data=(unsigned char*)markup;
			data->meta_write(data,"MARK",strlen(markup)+1);
		}
	}

	//-------------------------------------------------------------------------
	//read the image
	png_bytep row_pointers[32*1024];
	for(row=0;row<height;row++)
	{
		row_pointers[row]=(unsigned char*)(data->lp_pix+(row*data->pitch));
	}
	png_read_image(png_ptr,row_pointers);

	//read rest of file, and get additional chunks in info_ptr
	png_read_end(png_ptr,info_ptr);

	//clean up after the read, and free any memory allocated
	png_destroy_read_struct(&png_ptr,&info_ptr,png_infopp_NULL);

	//-------------------------------------------------------------------------
	//close the file
	fclose(fp);
	data->img_type=PLUGIN_IMGTYPE_LAYERED;
	data->img_subtype=PLUGIN_IMGSUBTYPE_NORMAL;
	return 1;

	//-------------------------------------------------------------------------
err_internal:
	png_destroy_read_struct(&png_ptr,&info_ptr,png_infopp_NULL);
	fclose(fp);
	if(wcslen(str_error_list))
	{
		_snwprintf(data->error_hint,PLUGIN_ERROR_HINT_SIZE,L"%s",
			str_error_list);
	}
	return last_error;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
This is the plugin DLL function that Chasys Draw IES calls to save images
******************************************************************************/
int flt_SaveImage(flt_IMAGE_S* data)
{
	FILE *fp;
	unsigned int i,j;
	png_infop info_ptr;
	png_structp png_ptr;
	unsigned long ul,*pMem=0,*pSrc,*pDst;

	//-------------------------------------------------------------------------
	if(!data)
	{
		return PLUGIN_ERR_BAD_HOST;
	}
	if(!data->pi_BasicImage)
	{
		return PLUGIN_ERR_BAD_HOST;
	}
	if(data->pi_BasicImage->version<PI_BASICIMAGE_VERSION)
	{
		return PLUGIN_ERR_BAD_HOST;
	}

	//-------------------------------------------------------------------------
	if(!(flt_CheckSave(wcsrchr(data->file,'.'))))
	{
		return PLUGIN_ERR_NO_SUPPORT;
	}
	if(data->layer_count>1)
	{
		return PLUGIN_ERR_NOT_FLAT;
	}

	//-------------------------------------------------------------------------
	memset(&settings,0,sizeof(plugin_config));
	if(data->pi_StateStore)
	{
		if(data->pi_StateStore->version>=PI_STATESTORE_VERSION)
		{
			data->pi_StateStore->perm_read(
				MY_STORE_NAME,&settings,sizeof(plugin_config));
			if(settings.version!=SETTINGS_VERSION)
			{
				memset(&settings,0,sizeof(plugin_config));
				settings.version=SETTINGS_VERSION;
			}
		}
	}

	//-------------------------------------------------------------------------
	//open the file stream for binary writing
	fp=_wfopen(data->file,L"wb");
	if(!fp)
	{
		return PLUGIN_ERR_FILE_OPEN_W;
	}

	//-------------------------------------------------------------------------
	//create and initialize the png_struct
	png_ptr=png_create_write_struct(PNG_LIBPNG_VER_STRING,0,0,0);
	if(!png_ptr)
	{
		fclose(fp);
		return PLUGIN_ERR_INTERNAL;
	}

	//-------------------------------------------------------------------------
	//allocate, initialize image information.
	png_ptr->error_fn=my_png_error;
	str_error_list[0]=0;
	info_ptr=png_create_info_struct(png_ptr);
	if(!info_ptr)
	{
		goto err_internal;
	}
	info_ptr->bit_depth=8;
	info_ptr->width=data->width;
	info_ptr->height=data->height;
	info_ptr->color_type=PNG_COLOR_TYPE_RGBA;
	info_ptr->interlace_type=PNG_INTERLACE_NONE;
	info_ptr->compression_type=PNG_COMPRESSION_TYPE_DEFAULT;
	info_ptr->rowbytes=data->pitch*4;
	info_ptr->num_palette=info_ptr->num_trans=0;
	info_ptr->filter_type=PNG_FILTER_TYPE_DEFAULT;

	if(data->meta_read)
	{
		if(data->meta_read(data,"MARK"))
		{
			if(data->meta_data)
			{
				double ppi_x,ppi_y;
				if(markup_read_PixPerInch_f((char*)data->meta_data,&ppi_x,&ppi_y))
				{
					info_ptr->x_pixels_per_unit=(int)((ppi_x/0.0254)+0.5);
					info_ptr->y_pixels_per_unit=(int)((ppi_y/0.0254)+0.5);
					info_ptr->phys_unit_type=PNG_RESOLUTION_METER;
					info_ptr->valid|=PNG_INFO_pHYs;
				}
			}
		}
	}

	ul=info_ptr->height*sizeof(void*);
	info_ptr->row_pointers=(png_bytepp)png_malloc(png_ptr,ul);
	if(!info_ptr->row_pointers)
	{
		png_destroy_write_struct(&png_ptr,&info_ptr);
		fclose(fp);
		return PLUGIN_ERR_OUT_OF_MEM;
	}

	//-------------------------------------------------------------------------
	//get the bitmap data, load it into buffer
	ul=data->width*data->height*4;
	pMem=(unsigned long*)GlobalAlloc(GMEM_FIXED,ul);
	if(!pMem)
	{
		png_free(png_ptr,info_ptr->row_pointers);
		png_destroy_write_struct(&png_ptr,&info_ptr);
		fclose(fp);
		return PLUGIN_ERR_OUT_OF_MEM;
	}
	for(j=0;j<data->height;j++)
	{
		pDst=pMem+(data->width*j);
		pSrc=data->lp_pix+(data->pitch*j);
		for(i=0;i<data->width ;i++)
		{
			pDst[i]=
				((pSrc[i]>>16)&0x000000ff)|
				( pSrc[i]     &0x0000ff00)|
				((pSrc[i]<<16)&0x00ff0000)|
				(( ~(pSrc[i]))&0xff000000);
		}
	}
	for(i=0;i<info_ptr->height;i++)
	{
		j=data->width*i;
		info_ptr->row_pointers[i]=(unsigned char*)(&pMem[j]);
	}

	//-------------------------------------------------------------------------
	//set error handling using C library setjump()
	if(setjmp(png_jmpbuf(png_ptr)))
	{
		goto err_internal;
	}

	//-------------------------------------------------------------------------
	//output the file
	png_init_io(png_ptr,fp);
	png_set_IHDR(
		png_ptr,info_ptr,info_ptr->width,info_ptr->height,8,
		PNG_COLOR_TYPE_RGBA,PNG_INTERLACE_NONE,
		PNG_COMPRESSION_TYPE_BASE,PNG_FILTER_TYPE_BASE);
	//png_set_gAMA(png_ptr,info_ptr,gamma);
	png_write_info(png_ptr,info_ptr);
	//png_set_bgr(png_ptr);//flip BGR pixels to RGB
	png_write_image(png_ptr,info_ptr->row_pointers);
	png_write_end(png_ptr,info_ptr);

	//-------------------------------------------------------------------------
	//clean up, free any memory,close the file
	if(pMem)
	{
		GlobalFree(pMem);
		pMem=0;
	}
	if(info_ptr)
	{
		if(info_ptr->row_pointers)
		{
			png_free(png_ptr,info_ptr->row_pointers);
		}
	}
	png_destroy_write_struct(&png_ptr,&info_ptr);
	fclose(fp);
	return 1;

	//=========================================================================
err_internal:
	if(pMem)
	{
		GlobalFree(pMem);
		pMem=0;
	}
	if(info_ptr)
	{
		if(info_ptr->row_pointers)
		{
			png_free(png_ptr,info_ptr->row_pointers);
		}
	}
	png_destroy_write_struct(&png_ptr,&info_ptr);
	fclose(fp);
	if(wcslen(str_error_list))
	{
		_snwprintf(data->error_hint,PLUGIN_ERROR_HINT_SIZE,L"%s",
			str_error_list);
	}
	return PLUGIN_ERR_INTERNAL;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
This function processes messages for about dialog
******************************************************************************/
LRESULT CALLBACK msg_dlg_about(HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar)
{
	wchar_t* pstr;

	//-------------------------------------------------------------------------
	switch(msg)
	{
	case WM_INITDIALOG:
		SetWindowText(hDlg,L"About " PLUGIN_NAME);
		pstr=string_ReadTextResource(api_inst,(LPCTSTR)IDR_README,L"TEXT");
		if(pstr)
		{
			SetDlgItemText(hDlg,IDC_EDIT1,pstr);
			GlobalFree(pstr);
		}
		window_Center(hDlg,GetParent(hDlg));
		return 1|lpar;
	case WM_COMMAND:
		switch(LOWORD(wpar))
		{
		case IDOK    :
			EndDialog(hDlg,1);
			break;
		case IDCANCEL:
			EndDialog(hDlg,0);
			break;
		}
		return 1;
	}
	return 0;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////


/******************************************************************************
This function processes messages for config dialog
******************************************************************************/
LRESULT CALLBACK msg_dlg_config(HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar)
{
	switch(msg)
	{
	case WM_INITDIALOG:
		window_Center(hDlg,GetParent(hDlg));
		SetWindowText(hDlg,L"Configure " PLUGIN_NAME);
		CheckDlgButton(hDlg,IDC_CHECK1,settings.always_apply_bKGD);
		return 1|lpar;
	case WM_COMMAND:
		switch(LOWORD(wpar))
		{
		case IDOK:
			settings.version=SETTINGS_VERSION;
			settings.always_apply_bKGD=(IsDlgButtonChecked(hDlg,IDC_CHECK1)!=0);
			EndDialog(hDlg,1);
			break;
		case IDCANCEL:
			EndDialog(hDlg,0);
			break;
		}
		return 1;
	}
	return 0;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////