/**************************************************************
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 one
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 "resource.h"
#include "../lib/libpng/png.h"
#include "../lib/zlib/zlib.h"
#include "../common/plugin.h"
#include "../common/suite_info.h"
#include "../common/win_util.h"
#include "../common/markup.h"
#include "flt_PNG.h"


//=============================================================
#define PLUGIN_NAME L"PNG Import/Export Filter"
#define PLUGIN_VERSION (((SUITE_VER_HI)<<16)|(SUITE_VER_LO))

//-------------------------------------------------------------
struct png_plugin_config
{
	bool always_apply_bKGD;
};

//-------------------------------------------------------------
HINSTANCE api_inst;
png_plugin_config png_settings={0,};

//-------------------------------------------------------------
//-------------------------------------------------------------



/**************************************************************
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 loads the settings from a file
**************************************************************/
int api_load_settings(void)
{
	HANDLE hf;
	unsigned long ul;
	wchar_t str[MAX_PATH],*pstr;

	//---------------------------------------------------------
	//generate the filename of the script
	if(!(GetModuleFileName(api_inst,str,MAX_PATH))) return 0;
	pstr=wcsrchr(str,(int)'.');
	if(!(pstr)) return 0;
	wcscpy(pstr,L".cfg");

	//---------------------------------------------------------
	//open the script
	hf=CreateFile(str,GENERIC_READ,FILE_SHARE_READ,0,
		OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,0);
	if(hf==INVALID_HANDLE_VALUE) return 0;
	else SetFilePointer(hf,0,0,FILE_BEGIN);

	//---------------------------------------------------------
	//read the script
	ReadFile(hf,&png_settings,sizeof(png_settings),&ul,0);
	if(ul!=sizeof(png_settings)) goto err;

	//---------------------------------------------------------
	//close and exit
	CloseHandle(hf);
	return 1;
	//---------------------------------------------------------
err:
	CloseHandle(hf);
	return 0;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////


/**************************************************************
This function saves the settings to a file
**************************************************************/
int api_save_settings(void)
{
	HANDLE hf;
	unsigned long ul;
	wchar_t str[MAX_PATH],*pstr;

	//---------------------------------------------------------
	//generate the filename of the script
	if(!(GetModuleFileName(api_inst,str,MAX_PATH))) return 0;
	pstr=wcsrchr(str,(int)'.');
	if(!(pstr)) return 0;
	wcscpy(pstr,L".cfg");

	//---------------------------------------------------------
	//create the script
	hf=CreateFile(str,GENERIC_WRITE,FILE_SHARE_READ,0,
		CREATE_ALWAYS,FILE_ATTRIBUTE_NORMAL,0);
	if(hf==INVALID_HANDLE_VALUE) return 0;
	else SetFilePointer(hf,0,0,FILE_BEGIN);

	//---------------------------------------------------------
	//write the script
	WriteFile(hf,&png_settings,sizeof(png_settings),&ul,0);
	if(ul!=sizeof(png_settings)) goto err;

	//---------------------------------------------------------
	//close and exit
	CloseHandle(hf);
	return 1;
	//---------------------------------------------------------
err:
	CloseHandle(hf);
	return 0;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////


/**************************************************************
This function processes messages for about dialog
**************************************************************/
LRESULT CALLBACK msg_dlg_about(
	HWND hDlg,UINT msg,WPARAM wpar,LPARAM lpar)
{
	//---------------------------------------------------------
	switch (msg)
	{
	//---------------------------------------------------------
	case WM_INITDIALOG:
		SetWindowText(hDlg,L"About " PLUGIN_NAME);
		SetDlgItemText(hDlg,IDC_STATIC1,
			PLUGIN_NAME L" (" SUITE_LOGODATE L")\n\n"
			L"Created by John Paul Chacha's Lab. "
			L"Tested with "SUITE_FULLNAME L".\n\n"
			L"This image plugin is 100% FREE, and is "
			L"based in part on the work of the PNG "
			L"Development Group.\n\n"
			L"No warranties are made or implied.");
		window_Center(hDlg,GetParent(hDlg));
		return 1|lpar;
	//---------------------------------------------------------
	case WM_COMMAND:
		switch(LOWORD(wpar))
		{
		case IDOK:
			EndDialog(hDlg, 1);
			break;
		case IDCANCEL:
			EndDialog(hDlg,-1);
			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:
		SetWindowText(hDlg,L"Configure "PLUGIN_NAME);
		CheckDlgButton(hDlg,IDC_CHECK1,
			png_settings.always_apply_bKGD);
		window_Center(hDlg,GetParent(hDlg));
		return 1|lpar;
	//---------------------------------------------------------
	case WM_COMMAND:
		//-----------------------------------------------------
		switch(LOWORD(wpar))
		{
		//-----------------------------------------------------
		case IDOK:
			png_settings.always_apply_bKGD=
				(IsDlgButtonChecked(hDlg,IDC_CHECK1)!=0);
			api_save_settings();
			EndDialog(hDlg,1);
			break;
		//-----------------------------------------------------
		case IDCANCEL:
			EndDialog(hDlg,-1);
			break;
		}
		return 1;
	}
	//---------------------------------------------------------
	return 0;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////


/**************************************************************
This function is called to display plugin's about dialog box
**************************************************************/
int plg_About(HWND parent)
{
	DialogBox(api_inst,
		(LPCTSTR)IDD_ABOUT,parent,(DLGPROC)msg_dlg_about);
	return 1;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////


/**************************************************************
This function is called to display plugin's config dialog box
**************************************************************/
int plg_Configure(HWND parent)
{
	DialogBox(api_inst,(LPCTSTR)IDD_CONFIG,
		parent,(DLGPROC)msg_dlg_config);
	return 1;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////


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


/**************************************************************
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 dll function that Chasys Draw IES calls to load
images using this plugin. It is declared as
	int (__cdecl* flt_LoadImage)(flt_IMAGE_L*)
The only parameter to this function, flt_IMAGE_L is a
bi-directional communication channel, providing both load data
and a mechanism to allocate a bitmap through the create()
member function.
The JpDRAW2 engine used by Chasys Draw IES may require that
the plugin resizes the image locally.
This will be indicated by the create() member function reseting
the width and height members to sizes other than the requested
size and returning a negative number.

The plugin must apply alpha data, using 0x00 for opaque and
0xff for transparent.
The HWND member will be zero if Chasys Draw IES doesn't want
you to display any dialogs.
**************************************************************/
int flt_LoadImage(flt_IMAGE_L* data)
{
	int bit_depth,color_type,interlace_type;
	unsigned int row,sig_read=0;
	png_uint_32 width,height;
	png_structp png_ptr;
	png_infop info_ptr;
	FILE *fp;

	//---------------------------------------------------------
	if(!data) return 1;
	api_load_settings();

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

	//---------------------------------------------------------
	png_ptr=png_create_read_struct(PNG_LIBPNG_VER_STRING,0,0,0);
	if(png_ptr==NULL)
	{
		fclose(fp);
		return 0;
	}

	//---------------------------------------------------------
	png_ptr->error_fn=my_png_error;
	str_error_list[0]=0;
	info_ptr=png_create_info_struct(png_ptr);
	if(info_ptr==NULL)
	{
		png_destroy_read_struct(
			&png_ptr,png_infopp_NULL,png_infopp_NULL);
		fclose(fp);
		return 0;
	}

	//---------------------------------------------------------
	if(setjmp(png_jmpbuf(png_ptr)))
	{
		png_destroy_read_struct(
			&png_ptr,&info_ptr,png_infopp_NULL);
		fclose(fp);
		return 0;
	}

	//---------------------------------------------------------
	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);

	//---------------------------------------------------------
	//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);

	//---------------------------------------------------------
	png_color_16 my_background,*image_background;

	my_background.red=0xffff;
	my_background.green=0xffff;
	my_background.blue=0xffff;
	my_background.gray=0xffff;
	my_background.index=0;
	if( (png_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
	data->width=width;
	data->height=height;
	if(data->create(data)==0)
	{
		goto err_exit;
	}

	//insert MARK attachment
	if(info_ptr->valid&PNG_INFO_pHYs)
	{
		if(data->meta_write)
		{
			int dpi_x,dpi_y;
			char markup[3072];
			dpi_x=info_ptr->x_pixels_per_unit;
			dpi_y=info_ptr->y_pixels_per_unit;
			switch(info_ptr->phys_unit_type)
			{
			case PNG_RESOLUTION_METER:
				dpi_x=(int)(((double)dpi_x)*0.0254);
				dpi_y=(int)(((double)dpi_y)*0.0254);
				break;
			}
			_snprintf(markup,3072,
				"image.dpi=%i,%i;\r\n",
				dpi_x,dpi_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);
	return 1;

	//---------------------------------------------------------
err_exit:
	png_destroy_read_struct(&png_ptr,&info_ptr,png_infopp_NULL);
	fclose(fp);
	return 0;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////


/**************************************************************
This function is called to check if this image filter plugin
uses the specified filename extension. Plugins MUST RETURN ZERO
if the filename extension differs even slightly from what's
considered the standard.
The extension always begins with ".".
**************************************************************/
int flt_CheckSaveExt(wchar_t* extension)
{
	if(_wcsicmp(extension,L".png")==0) return 1;
	return 0;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////


/**************************************************************
This is the dll function that Chasys Draw IES calls to save
images using this plugin. It is declared as
	int (__cdecl* flt_SaveImage)(flt_IMAGE_S*)
The only parameter to this function, flt_IMAGE_S provides all
the info needed to save the image data, which is an array of
32-bit pixel values.
The plugin, if capable of storing alpha data, should check for
transparency, which is indicated by non-zero alpha values.
0x00 is used for opaque and 0xff for transparent.
The HWND member will be zero if Chasys Draw IES doesn't want
you to display any dialogs.
This function should return non-zero if it succeeds or zero if
it fails.
**************************************************************/
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 0;
	if(data->layer_count>1) return 0;
	if(!(flt_CheckSaveExt(wcsrchr(data->file,'.')))) return 0;

	//---------------------------------------------------------
	//open the file stream for binary writing
	fp=_wfopen(data->file,L"wb");
	if(!fp) goto err_exit;

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

	//---------------------------------------------------------
	//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)
	{
		png_destroy_write_struct(&png_ptr,&info_ptr);
		fclose(fp);
		goto err_exit;
	}
	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"))&&(data->meta_data))
		{
			double dpi_x,dpi_y;
			char tag[256],*mark,*pstr;
			mark=(char*)data->meta_data;
			if(markup_GetTag(mark,"image.dpi",tag,16,0))
			{
				dpi_x=(double)atoi(tag);
				dpi_y=dpi_x;
				pstr=strchr(tag,',');
				if(pstr)
				{
					pstr++;
					dpi_y=(double)atoi(pstr);
				}
				info_ptr->x_pixels_per_unit=(int)(dpi_x/0.0254);
				info_ptr->y_pixels_per_unit=(int)(dpi_y/0.0254);
				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);
		goto err_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);
		goto err_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)))
	{
		if(pMem) GlobalFree(pMem);
		pMem=0;
		png_free(png_ptr,info_ptr->row_pointers);
		png_destroy_write_struct(&png_ptr,&info_ptr);
		fclose(fp);
		goto err_exit;
	}

	//---------------------------------------------------------
	//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;
	png_free(png_ptr,info_ptr->row_pointers);
	png_destroy_write_struct(&png_ptr,&info_ptr);
	fclose(fp);
	return 1;

	//=========================================================
err_mem:
	if(IsWindow(data->hwnd))
	{
		MessageBox(data->hwnd,L"Out of Memory",
			L"PNG Save Error",MB_OK|MB_ICONWARNING);
	}
	return 0;
err_exit:
	if(IsWindow(data->hwnd))
	{
		MessageBox(data->hwnd,str_error_list,
			L"PNG Save Error",MB_OK|MB_ICONWARNING);
	}
	return 0;
}
///////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////