/*<<<***************************************************************************
The MIT License (MIT)

Copyright(C) 2013 - 2015 Marc Mirti Mancinelli, Christophe Lardau and Philippe Pamart (xdcube.org)

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

!! IMPORTANT !!
This plugin/software uses and integrates the following sources which are 
obviously not covered/excluded from this license: 
- Independent JPEG Group's software library 6b used for jpeg support 
	(folder jpeg-6b, see README.txt for license)
- Glenn Randers- Pehrson's libpng-1.2.8
	(folder libpng-128, see LICENSE.txt)
- Jean-loup Gailly/Mark Adler's zlib-1.2.3 for png support and gzip compression
	(folder zlib-123, see README.txt for license)

For this source, please refer to their own license.
////////////////////////////////////////////////////////////////////////////////
#Title: xdcPicPngPlug
#Project: xdcPicStd
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"
#include "xdcPicStd.h"
#include "xdcPicPngPlug.h"

/**************************************************************************************
* pngPlug
**************************************************************************************/
void pngPlug::throwEnd(const xbString &strEnd,const xbString &strCtx) const
{
	if (g_PicStdFact.isUnloadAsked())
		xbThrow xbEhotPlug(E_ABORT,strEnd+", plugin in destruction (png plugin)",xbEdebug("pngPlug::"+strCtx+""));
	xbThrow gdiEcompressUser(E_ABORT,strEnd+", operation cancelled by application (png plugin)",xbEdebug("pngPlug::"+strCtx+""));
}

pngPlug::pngPlug()
{
}

void pngPlug::askClose(LPCSTR pReason)
{
	{
		xbSectLock lck(&m_sectPtr);
		if (pReason && m_closeReason.IsEmpty())
			m_closeReason = pReason;
	}
	Destroy();
}
xbString pngPlug::getCloseReason() const
{
	xbSectLock lck(&m_sectPtr);
	xbString ret(m_closeReason);
	ret.OwnData();
	return ret;
}

void pngPlug::onDestruction(bool /*bInDestroy*/)
{
}

// IgdiCompressPlug methods
bool pngPlug::CanWorkWithType(LPCSTR pszType, IxbInputStream *pStm, xbRefPtr<IgdiDecompressor> *pprDecomp, IgdiImage *pImg) const
{
	if (pszType && stricmp(pszType,"png") == 0)
	{
		if (pprDecomp || pImg)
		{
			((pngPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}
	//TODO
	return false;
}
const uchar png_signature[8] = {137, 80, 78, 71, 13, 10, 26, 10};

bool pngPlug::CanWorkWithType(LPCSTR pszType, const uchar *pStmData, int lgData, int *pLgDataMin, xbRefPtr<IgdiDecompressor> *pprDecomp, IgdiImage *pImg) const
{
	if (pszType && !lgData && stricmp(pszType,"png") == 0)
	{
		if (pprDecomp || pImg)
		{
			((pngPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}
	if (lgData < sizeof(png_signature))
	{
		*pLgDataMin = sizeof(png_signature);
		return false;
	}

	if (memcmp(pStmData,png_signature,sizeof(png_signature)) == 0)
	{
		if (pprDecomp || pImg)
		{
			((pngPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}

	return false;
}

bool pngPlug::Decompress(ulong caps, xbRefPtr<IgdiDecompressor> *pprUncomp,xbOpMode reqMode)
{
	xbAssert(caps == 0);
	pngDecompressor::CreateObject(m_pFact,pprUncomp);
	return true;
}

bool pngPlug::Compress(ulong caps, xbRefPtr<IgdiCompressor> *pprComp,xbOpMode reqMode)
{
	xbAssert(caps == 0);
	pngCompressor::CreateObject(m_pFact,pprComp);
	return true;
}

bool pngPlug::EnumDecompressSetting(int &no, gdiSettingInfo *pInfo) const
{
	//TODO
	return false;
}

xbString pngPlug::GetCompressVersion(bool bShort,LCID lcid) const
{
	return "xdcPicture png-1.2.8 Plugin";
}

xbString pngPlug::GetCompressLabel(bool bShort,LCID lcid) const
{
	return "PNG images";
}

xbString pngPlug::GetDefaultExtension() const
{
	return "png";
}

bool pngPlug::GetCompressLevelInfo(gdiSettingInfo *pInfo) const
{
	pInfo->dfltVal = pngZ_DEFAULT_COMPRESSION;
	pInfo->maxVal = 9;
	pInfo->minVal = 0;
	pInfo->label = "Compression level";
	pInfo->name = "CompressLevel";
	pInfo->help = "9 is the best and slowest compression";

	return true;
}

bool pngPlug::EnumCompressSetting(int &no, gdiSettingInfo *pInfo) const
{
	//TODO
	return false;
}

gdiCompressCaps pngPlug::GetCaps() const
{
	return gdiCompressCaps(gdiCompressCaps_ReadWrite | gdiCompressCaps_CompressWithNoQualityLost | gdiCompressCaps_AlphaOpacity);
}

/**************************************************************************************
* pngCompressor
**************************************************************************************/
void pngCompressor::CreateObject(xbHotPlugFactory *pFactory,xbRefPtr<IgdiCompressor> *ppObj)
{
	pngCompressor *plg = xbDnew pngCompressor();
	try
	{
		plg->setFactory(pFactory);
		plg->Create();
		
		(*ppObj) = plg;
		
		plg->Release();
	}
	catch(...)
	{
		plg->Release();
		throw;
	}
}

void pngCompressor::throwEnd(const xbString &strEnd,const xbString &strCtx) const
{
	if (g_PicStdFact.isUnloadAsked())
		xbThrow xbEhotPlug(E_ABORT,strEnd+", plugin in destruction (png plugin)",xbEdebug("pngCompressor::"+strCtx+""));
	xbThrow gdiEcompressUser(E_ABORT,strEnd+", operation cancelled by application (png plugin)",xbEdebug("pngCompressor::"+strCtx+""));
}

pngCompressor::pngCompressor()
{
	m_png_ptr = NULL;
	m_info_ptr = NULL;
	m_compressLevel = pngZ_DEFAULT_COMPRESSION;
	m_rowCount = -1;
	m_rowNo = -1;
}

void pngCompressor::askClose(LPCSTR pReason)
{
	{
		xbSectLock lck(&m_sectPtr);
		if (pReason && m_closeReason.IsEmpty())
			m_closeReason = pReason;
	}
	Destroy();
}

xbString pngCompressor::getCloseReason() const
{
	xbSectLock lck(&m_sectPtr);
	xbString ret(m_closeReason);
	ret.OwnData();
	return ret;
}

void pngCompressor::onDestruction(bool /*bInDestroy*/)
{
	eProtect(pngClose());
	eProtect(m_prImg.Empty());
	eProtect(m_prBmp.Empty());
}

// IgdiCompressor methods
void pngCompressor::NewInput(IgdiImage *pImg)
{
	pngOpen();
	m_prImg = pImg;
	pImg->GetBitmap(-1,&m_prBmp);
	
	pngInit();
}

void pngCompressor::NewInput(IgdiBitmap *pBmp)
{
	pngOpen();
	m_prImg.Empty();
	m_prBmp = pBmp;
}

bool pngCompressor::GetInput(xbRefPtr<IgdiImage> *pprImg,xbOpMode reqMode)
{
	if (m_prImg.IsEmpty())
	{
		if (reqMode == xbOp_Required)
			xbThrow gdiEcompress("No current image affected to png compressor","pngCompressor::GetInput()");
		return false;
	}
	
	if (pprImg)
		*pprImg = m_prImg;
	return true;
}

bool pngCompressor::GetInput(xbRefPtr<IgdiBitmap> *pprBmp,xbOpMode reqMode)
{
	if (m_prBmp.IsEmpty())
	{
		if (reqMode == xbOp_Required)
			xbThrow gdiEcompress("No current bitmap affected to png compressor","pngCompressor::GetInput()");
		return false;
	}
	
	if (pprBmp)
		*pprBmp = m_prBmp;
	return true;
}

void pngCompressor::SetCompressLevel(int compressLevel)
{
	if (m_png_ptr)
		xbThrow gdiEcompress("png compression: can't change compression level after compression is started","pngCompressor::SetCompressLevel("+xbString(compressLevel)+")"); 
	switch (compressLevel)
	{
	case gdiCompressLow:
		compressLevel = Z_NO_COMPRESSION;
		break;
	case gdiCompressMid:
		compressLevel = Z_BEST_SPEED;
		break;
	case gdiCompressDefault:
		compressLevel = pngZ_DEFAULT_COMPRESSION; 
		break;
	case gdiCompressHigh:
		compressLevel = Z_BEST_COMPRESSION; //9
		break;
	}
	if (compressLevel < 0 || compressLevel > Z_BEST_COMPRESSION)
		xbThrow gdiEcompress("png compression: Bad compression level (must be between 0 and 9)","pngCompressor::SetCompressionLevel("+xbString(compressLevel)+")"); 
	m_compressLevel = compressLevel;
}

int pngCompressor::GetCompressLevel() const
{
	return m_compressLevel;
}


bool pngCompressor::EnumCompressSetting(int &no, gdiSettingInfo *pInfo) const
{
	return false;
}


bool pngCompressor::SetCompressSetting(LPCSTR pszName,int intVal,LPCSTR strVal,xbOpMode reqMode)
{
	if (reqMode == xbOp_Required)
		xbThrow gdiEcompressUser(xbString(pszName)+" setting not supported or unknown","pngCompressor::SetCompressSetting()");
	return false;
}

bool pngCompressor::GetCompressSetting(LPCSTR pszName,int *pIntVal,xbString *pStrVal) const
{
	return false;
}

void pngCompressor::ResetCompressSettings(int compressLevel)
{
	pngClose();
	SetCompressLevel(compressLevel);
}


//IxbInputStream methods
bool	pngCompressor::CanRead() const
{
	return m_png_ptr && m_prImg.IsValid();
}

ulong	pngCompressor::Read(void *pData,ulong DataLg,bool DataLg_Required)
{
	if (!m_png_ptr || m_prImg.IsEmpty())
		xbThrow gdiEcompress(STG_E_READFAULT,"Read failed, png compressor not started","pngCompressor::Read()");
	uchar *ptr = (uchar*)pData;
	ulong rd = 0;
	while(DataLg > 0)
	{
		if (!pngRead(DataLg))
		{
			if (DataLg_Required)
				xbThrow gdiEcompress(STG_E_READFAULT,"Read failed, png End of image reached","pngCompressor::Read()");
			break;
		}
		int cByte = m_resDta.Length;
		if (ulong(cByte) <= DataLg)
		{
			memcpy(ptr,m_resDta,cByte);
			m_resDta.Empty();
		}
		else
		{
			memcpy(ptr,m_resDta,DataLg);
			m_resDta.Delete(0,DataLg);
			cByte = int(DataLg);
		}
		ptr += cByte;
		rd += cByte;
		DataLg -= cByte;
	}
	return rd;
}


ulong	pngCompressor::ReadString(xbString *pStr,xbString *pStopStr,IxbStreamMode *pMode)
{
	xbThrow gdiEcompress(E_NOTIMPL,"pngCompressor::ReadString() : not implemented (png plugin)");
	return -1;
}

ulong	pngCompressor::ReadStringW(xbBSTR *pStr,xbBSTR *pStopStr,IxbStreamMode *pMode)
{
	xbThrow gdiEcompress(E_NOTIMPL,"pngCompressor::ReadStringW() : not implemented (png plugin)");
	return -1;
}

void cpng_user_error_fn(png_structp png_ptr,png_const_charp error_msg)
{
    pngCompressor *pComp = (pngCompressor*)png_get_error_ptr(png_ptr);

	xbThrow gdiEcompress(error_msg,"PNG Library Error");
}

void cpng_user_warning_fn(png_structp png_ptr,png_const_charp warning_msg)
{
    pngCompressor *pComp = (pngCompressor*)png_get_error_ptr(png_ptr);

	xbRelOutput(xbString("PNG Library Error: ")+warning_msg);
}

void cpng_user_write_data(png_structp png_ptr,png_bytep data, png_size_t length)
{
    pngCompressor *pComp = (pngCompressor*)png_get_io_ptr(png_ptr);

	pComp->m_resDta.Add(data,length);
}

void cpng_user_flush_data(png_structp png_ptr)
{
    pngCompressor *pComp = (pngCompressor*)png_get_io_ptr(png_ptr);
}

void pngCompressor::pngOpen()
{
	pngClose();
	
    m_png_ptr = png_create_write_struct
        (PNG_LIBPNG_VER_STRING, (png_voidp)this,
         cpng_user_error_fn, cpng_user_warning_fn);
    if (!m_png_ptr)
		xbEmem::Throw("not enough memory for png compressor");
    
	m_info_ptr = png_create_info_struct(m_png_ptr);
    if (!m_info_ptr)
		xbEmem::Throw("not enough memory for png compressor");

    png_set_write_fn(m_png_ptr,
        this, cpng_user_write_data,cpng_user_flush_data);
	m_rowCount = -1;
	m_rowNo = -1;
}

void pngCompressor::pngInit()
{
	m_transparentEntry = -1;
	m_resDta.Empty();

    /* set the zlib compression level */
    png_set_compression_level(m_png_ptr,
        m_compressLevel);

    /* set other zlib parameters 
    png_set_compression_mem_level(png_ptr, 8);
    png_set_compression_strategy(png_ptr,
        Z_DEFAULT_STRATEGY);
    png_set_compression_window_bits(png_ptr, 15);
    png_set_compression_method(png_ptr, 8);
    png_set_compression_buffer_size(png_ptr, 8192)
	*/

	gdiSizeLow			sz;
	gdiBmpColorType	bmpType;
	int				bmpBitsPerPixel;
	m_prBmp->GetBitmapInfo(&sz,&bmpType,&bmpBitsPerPixel);
	m_prBmp->msAlphaUndo();

	int bit_depth=0, color_type=-1, color_channel = 0, interlace_type=PNG_INTERLACE_NONE,
       compression_type=PNG_COMPRESSION_TYPE_DEFAULT, filter_method=PNG_FILTER_TYPE_DEFAULT;
	
	bool bConvert = false;
	switch(bmpType)
	{
	case gdiBmpColor_Gray:
		color_type=PNG_COLOR_TYPE_GRAY;
		bit_depth = 8;
		color_channel = 1;
		break;
	case gdiBmpColor_RGB:
		color_type=PNG_COLOR_TYPE_RGB;
		bit_depth = 8;
		color_channel = 3;
		break;
	case gdiBmpColor_RGBA:
		color_type=PNG_COLOR_TYPE_RGBA;
		bit_depth = 8;
		color_channel = 4;
		break;
	default:
		bConvert = true;
		color_type=PNG_COLOR_TYPE_RGB;
		bit_depth = 8;
		color_channel = 3;
		break;
	};

	if (bConvert || bmpBitsPerPixel != (color_channel * bit_depth))
	{
		xbRefPtr<IgdiBitmap> prNewBmp;
		m_prBmp->Convert(NULL,bmpType,(color_channel * bit_depth),&prNewBmp);
		m_prBmp = prNewBmp;
	}

	if (picBitmap_calcPalette(m_prBmp,m_palette,&m_transparentEntry,&m_transparentColor))
	{
		if (color_type==PNG_COLOR_TYPE_RGBA)
			color_type=PNG_COLOR_TYPE_RGB;
		color_type |= PNG_COLOR_MASK_PALETTE;
		bit_depth = 8;
		color_channel = 1;
	}
	else
		m_palette.Empty();
	
    png_set_IHDR(m_png_ptr, m_info_ptr, sz.cx, sz.cy,
       bit_depth, color_type, interlace_type,
       compression_type, filter_method);

	gdiSizeU	szU;
	if (m_prImg.IsValid())
		szU = m_prImg->GetMetricSize();
	
    if (szU.IsValid()
		&& szU.unitX != gdiUnit_void && szU.unitX != gdiUnit_percent
		&& szU.unitX == szU.unitY
		) 
	{
		/* Set JFIF density parameters from the BMP data */
		switch(szU.unitX)
		{
		case gdiUnit_inch:
			{
				double res_x = double(sz.cx) / (szU.cx*25.4) * 1000.0; /* dots/meter */
				double res_y = double(sz.cy) / (szU.cy*25.4) * 1000.0; /* dots/meter */

				png_set_pHYs(m_png_ptr, m_info_ptr, ulong(res_x), ulong(res_y),
					PNG_RESOLUTION_METER);
			}
			break;
		case gdiUnit_mm:
			{
				double res_x = double(sz.cx) / szU.cx * 1000.0; /* dots/meter */
				double res_y = double(sz.cy) / szU.cy * 1000.0; /* dots/meter */

				png_set_pHYs(m_png_ptr, m_info_ptr, ulong(res_x), ulong(res_y),
					PNG_RESOLUTION_METER);
			}
			break;
		case gdiUnit_point:
			{
				double res_x = (double(sz.cx) * gdiPOINT_PER_INCH) / (szU.cx*25.4) * 1000.0; /* dots/meter */
				double res_y = (double(sz.cy) * gdiPOINT_PER_INCH) / (szU.cy*25.4) * 1000.0; /* dots/meter */

				png_set_pHYs(m_png_ptr, m_info_ptr, ulong(res_x), ulong(res_y),
					PNG_RESOLUTION_METER);
			}
			break;
		case gdiUnit_pixel:
			png_set_pHYs(m_png_ptr, m_info_ptr, ulong(szU.cx), ulong(szU.cy),
				PNG_RESOLUTION_UNKNOWN);
			break;
		default:
			gdiEsys::Throw("Invalid x unit for picture size");
		}
    }
	else
	{
		png_set_pHYs(m_png_ptr, m_info_ptr, ulong(sz.cx), ulong(sz.cy),
			PNG_RESOLUTION_UNKNOWN);
	}

	if (m_palette.IsValid())
	{
		png_set_PLTE(m_png_ptr, m_info_ptr, m_palette.Begin(),m_palette.Length);
		if (m_transparentEntry>=0)
		{
			uchar	trans = uchar(m_transparentEntry);
			png_set_tRNS(m_png_ptr, m_info_ptr, &trans, 1, NULL);

			m_palette.Delete(0);
		}
    }
    else
		png_set_bgr(m_png_ptr);

    png_write_info(m_png_ptr, m_info_ptr);

//    png_set_packing(m_png_ptr);

	m_rowCount = sz.cy;
	m_rowNo = 0;
}

void pngCompressor::pngClose()
{
	if (m_png_ptr)
		png_destroy_write_struct(&m_png_ptr, /*(png_infopp)*/&m_info_ptr);

	m_info_ptr = NULL;
	
	m_resDta.Empty();
	m_rowCount = -1;
	m_rowNo = -1;
	m_palette.Empty();
}

bool pngCompressor::pngRead(int dataLg)
{
	if (dataLg <= m_resDta.Length)
		return true;

	if (m_rowNo >= m_rowCount) 
		return m_resDta.IsValid();
	
	int rowLength = png_get_rowbytes(m_png_ptr, m_info_ptr);
	uchar *row_pointer;
	xbLock	lck;
	while (m_rowNo < m_rowCount) 
	{
		if (m_palette.IsValid())
		{
			picBitmap_getRowWithPalette(m_prBmp, m_rowNo, m_palette, m_transparentEntry, m_palData);
			row_pointer = (uchar*)(LPCVOID)m_palData;
		}
		else	
		{
			m_prBmp->GetBitmapRow(&lck,m_rowNo, (uchar**) &row_pointer, 1, rowLength);
		}
		
		png_write_row(m_png_ptr, row_pointer);
		m_rowNo++;
		if (m_rowNo < m_rowCount && dataLg <= m_resDta.Length)
			return true;
	}
	
	/* Finish compression */
    png_write_end(m_png_ptr, m_info_ptr);
	
	return m_resDta.IsValid();
}

/**************************************************************************************
* pngDecompressor
**************************************************************************************/
void pngDecompressor::CreateObject(xbHotPlugFactory *pFactory,xbRefPtr<IgdiDecompressor> *ppObj)
{
	pngDecompressor *plg = xbDnew pngDecompressor();
	try
	{
		plg->setFactory(pFactory);
		plg->Create();
		
		(*ppObj) = plg;
		
		plg->Release();
	}
	catch(...)
	{
		plg->Release();
		throw;
	}
}
void pngDecompressor::throwEnd(const xbString &strEnd,const xbString &strCtx) const
{
	if (g_PicStdFact.isUnloadAsked())
		xbThrow xbEhotPlug(E_ABORT,strEnd+", plugin in destruction (png plugin)",xbEdebug("pngDecompressor::"+strCtx+""));
	xbThrow gdiEcompressUser(E_ABORT,strEnd+", operation cancelled by application (png plugin)",xbEdebug("pngDecompressor::"+strCtx+""));
}

pngDecompressor::pngDecompressor()
{
	m_png_ptr = NULL;
	m_info_ptr = NULL;
}

void pngDecompressor::askClose(LPCSTR pReason)
{
	{
		xbSectLock lck(&m_sectPtr);
		if (pReason && m_closeReason.IsEmpty())
			m_closeReason = pReason;
	}
	Destroy();
}
xbString pngDecompressor::getCloseReason() const
{
	xbSectLock lck(&m_sectPtr);
	xbString ret(m_closeReason);
	ret.OwnData();
	return ret;
}

void pngDecompressor::onDestruction(bool /*bInDestroy*/)
{
	eProtect(pngClose());
	eProtect(m_prImg.Empty());
	eProtect(m_prBmp.Empty());
}

void pngDecompressor::NewOutput(IgdiImage *pImg,IgdiDecompressorCallBack *pCb)
{
	pngOpen();
	if (pImg)
		m_prImg = pImg;
	else
		m_prImg.Empty();
	m_prBmp.Empty();
}

bool pngDecompressor::GetOutput(xbRefPtr<IgdiImage> *pprImg,xbOpMode reqMode)
{
	if (m_prImg.IsEmpty())
	{
		if (reqMode == xbOp_Required)
			xbThrow gdiEcompress("No current image affected to png decompressor","pngCompressor::GetOutput()");
		return false;
	}
	
	if (pprImg)
		*pprImg = m_prImg;
	return true;
}

bool pngDecompressor::EnumDecompressSetting(int &no, gdiSettingInfo *pInfo) const
{
	return false;
}


bool pngDecompressor::SetDecompressSetting(LPCSTR pszName,int intVal,LPCSTR strVal,xbOpMode reqMode)
{
	return false;
}

bool pngDecompressor::GetDecompressSetting(LPCSTR pszName,int *pIntVal,xbString *pStrVal) const
{
	return false;
}

void pngDecompressor::ResetDecompressSettings()
{
}


//IxbOutputStream methods
bool	pngDecompressor::CanWrite() const
{
	return m_png_ptr != NULL;
}

ulong	pngDecompressor::Write(const void *pData,ulong DataLg,bool DataLg_Required)
{
	if (!m_png_ptr)
		xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, png decompressor not started","pngCompressor::Write()");
	if (m_png_ptr->process_mode == PNG_READ_DONE_MODE)
	{
		if (DataLg_Required)
			xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, decompression end reached with staying data","pngCompressor::Write() : DataLg="+xbString(DataLg));
		return 0;
	}
//	int lgprev = m_png_ptr->save_buffer_size+DataLg;
	png_process_data(m_png_ptr,m_info_ptr,(uchar*)pData,DataLg);
//	DataLg -= lgprev - m_png_ptr->save_buffer_size;
	ulong cByteRd;
	if (m_png_ptr->process_mode == PNG_READ_DONE_MODE)
	{
//		if (m_png_ptr->current_buffer_size)
		{
			if (m_png_ptr->current_buffer_size >= DataLg)
				cByteRd = 0;
			else
				cByteRd = DataLg - m_png_ptr->current_buffer_size;
		}
		if (DataLg != cByteRd)
		{
			if (DataLg_Required)
				xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, decompression end reached with staying data","pngCompressor::Write() : Staying lg="+xbString(DataLg - cByteRd));
			return cByteRd;
		}
	}
	else
		cByteRd = DataLg;

	return cByteRd;
}

ulong	pngDecompressor::WriteStream(IxbInputStream *pstm)
{
	xbThrow gdiEcompress(E_NOTIMPL,"pngCompressor::WriteStream() : not implemented (png plugin)");
	return -1;
}

ulong	pngDecompressor::WriteStream(IxbInputStream *pstm,ulong DataLg,bool DataLg_Required)
{
	xbThrow gdiEcompress(E_NOTIMPL,"pngCompressor::WriteStream() : not implemented (png plugin)");
	return -1;
}

ulong	pngDecompressor::WriteString(const char *pStr,ulong lgStr,bool bAddLf,IxbStreamMode *pMode)
{
	xbThrow gdiEcompress(E_NOTIMPL,"pngCompressor::WriteString() : not implemented (png plugin)");
	return -1;
}

ulong	pngDecompressor::WriteStringW(const wchar *pStr,ulong lgStr,bool bAddLf,IxbStreamMode *pMode)
{
	xbThrow gdiEcompress(E_NOTIMPL,"pngCompressor::WriteStringW() : not implemented (png plugin)");
	return -1;
}

void pngDecompressor::pngClose()
{
	if (m_png_ptr)
		png_destroy_read_struct(&m_png_ptr, &m_info_ptr,
		   (png_infopp)NULL);

	m_info_ptr = NULL;
	m_prBmp.Empty();
}

void dpng_user_error_fn(png_structp png_ptr,png_const_charp error_msg)
{
    pngDecompressor *pDecomp = (pngDecompressor*)png_get_error_ptr(png_ptr);

	xbThrow gdiEcompress(error_msg,"PNG Library Error");
}

void dpng_user_warning_fn(png_structp png_ptr,png_const_charp warning_msg)
{
    pngDecompressor *pDecomp = (pngDecompressor*)png_get_error_ptr(png_ptr);

	xbRelOutput(xbString("PNG Library Error: ")+warning_msg);
}

/* This function is called (as set by
    png_set_progressive_read_fn() above) when enough data
    has been supplied so all of the header has been
    read.
 */
void dpng_info_callback(png_structp png_ptr, png_infop info_ptr)
{
/* Do any setup here, including setting any of
   the transformations mentioned in the Reading
   PNG files section.  For now, you _must_ call
   either png_start_read_image() or
   png_read_update_info() after all the
   transformations are set (even if you don't set
   any).  You may start getting rows before
   png_process_data() returns, so this is your
   last chance to prepare for that.
 */
	pngDecompressor *pDecomp = (pngDecompressor*)png_get_progressive_ptr(png_ptr);

	int bit_depth,color_type,interlace_type;
	gdiBmpInfo	info;
    png_get_IHDR(png_ptr, info_ptr, (ulong*)&info.bmpSz.cx,
      (ulong*)&info.bmpSz.cy, &bit_depth, &color_type, &interlace_type, NULL, NULL);

	if (pDecomp->m_prImg.IsEmpty())
	{
		gdiCreateImage(&pDecomp->m_prImg,"simple","png image");
	}
	pDecomp->m_prImg->SetBitmapCount(0);
	pDecomp->m_prBmp.Empty();

    if (color_type == PNG_COLOR_TYPE_PALETTE)
        png_set_expand(png_ptr);
    if (png_get_valid(png_ptr, info_ptr, PNG_INFO_tRNS))
        png_set_expand(png_ptr);
    if ((color_type == PNG_COLOR_TYPE_GRAY || color_type == PNG_COLOR_TYPE_GRAY_ALPHA) && bit_depth < 8)
        png_set_expand(png_ptr);

	int number_of_passes = 0;
    if (interlace_type == PNG_INTERLACE_ADAM7)
        number_of_passes
           = png_set_interlace_handling(png_ptr);

//	if (color_type == PNG_COLOR_TYPE_GRAY_ALPHA||color_type == PNG_COLOR_TYPE_RGBA) 
//		png_set_invert_alpha(png_ptr);

    if (bit_depth == 16)
      png_set_strip_16(png_ptr);
  
	if (color_type == PNG_COLOR_TYPE_GRAY ||
      color_type == PNG_COLOR_TYPE_GRAY_ALPHA)
        png_set_gray_to_rgb(png_ptr);

	png_set_bgr(png_ptr);

/*	png_color_16 my_background={ 0, 192, 192, 192, 0 };
	png_color_16 *image_background;

	if (info_ptr->pixel_depth != 32){
		//<yeonjun jeong> preserve original background info.
		if (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_SCREEN, 0, 1.0);
// <vho> - we call png_set_bgr() below
// <vho>	//<yeonjun jeong> safe check
// <vho>	if (info_ptr->pixel_depth > 16 ) info_ptr->color_type = COLORTYPE_COLOR;
	}

	//<DP> hack for images with alpha channel
	if (info_ptr->pixel_depth == 32){
//		info.nBkgndIndex = 0; //enable transparency
		if (png_get_bKGD(png_ptr, info_ptr, &image_background)){
			info.nBkgndColor.rgbRed   = (BYTE)image_background->red;
			info.nBkgndColor.rgbGreen = (BYTE)image_background->green;
			info.nBkgndColor.rgbBlue  = (BYTE)image_background->blue;
			info.nBkgndColor.rgbReserved = 0;	// <vho>
		}
	}
*/

	png_read_update_info(png_ptr, info_ptr);

	switch(png_get_color_type(png_ptr, info_ptr))
	{
/*	case PNG_COLOR_TYPE_GRAY:
		info.bmpType = gdiBmpColor_Gray;
		break;
	case PNG_COLOR_TYPE_GRAY_ALPHA:
		info.bmpType = gdiBmpColor_GrayAlpha;
		break;*/
	case PNG_COLOR_TYPE_RGB:
		info.bmpType = gdiBmpColor_RGB;
		break;
	case PNG_COLOR_TYPE_RGBA:
		info.bmpType = gdiBmpColor_RGBA;
		break;
	default:
		xbThrow gdiEcompress(E_NOTIMPL,"Unknown png format","pngCompressor::pngProcessWaitingData(type: "+xbString(png_get_color_type(png_ptr, info_ptr))+", num components: "+xbString( png_get_channels(png_ptr, info_ptr))+")");
	};

	info.bmpBitsPerPixel = png_get_channels(png_ptr, info_ptr) * png_get_bit_depth(png_ptr, info_ptr);

	pDecomp->m_prImg->CreateBitmap(-1,&info,&pDecomp->m_prBmp);

/*	if (info.bmpType == gdiBmpColor_RGBA)
		m_rgbaMStransfoByte = info.bmpSz.cy;
	else
		m_rgbaMStransfoByte = 0;*/
}

 /* This function is called when each row of image
    data is complete */
void dpng_row_callback(png_structp png_ptr, png_bytep new_row,
    png_uint_32 row_num, int pass)
{
/* If the image is interlaced, and you turned
   on the interlace handler, this function will
   be called for every row in every pass.  Some
   of these rows will not be changed from the
   previous pass.  When the row is not changed,
   the new_row variable will be NULL.  The rows
   and passes are called in order, so you don't
   really need the row_num and pass, but I'm
   supplying them because it may make your life
   easier.

   For the non-NULL rows of interlaced images,
   you must call png_progressive_combine_row()
   passing in the row and the old row.  You can
   call this function for NULL rows (it will just
   return) and for non-interlaced images (it just
   does the memcpy for you) if it will make the
   code easier.  Thus, you can just do this for
   all cases:
 */

    /* first check whether the row differs from the previous pass; if not,
     * nothing to combine or display */

	if (!new_row)
		return;

	pngDecompressor *pDecomp = (pngDecompressor*)png_get_progressive_ptr(png_ptr);

	uchar *row_pointer=NULL; 
	xbLock	lck;
	pDecomp->m_prBmp->GetBitmapRow(&lck,row_num, &row_pointer, 1, png_get_rowbytes(png_ptr, pDecomp->m_info_ptr));

    png_progressive_combine_row(png_ptr, row_pointer,
      new_row);

/* where old_row is what was displayed for
   previously for the row.  Note that the first
   pass (pass == 0, really) will completely cover
   the old row, so the rows do not have to be
   initialized.  After the first pass (and only
   for interlaced images), you will have to pass
   the current row, and the function will combine
   the old row and the new row.
*/

/*	if (m_rgbaMStransfoByte)
	{
		int cByte = m_rgbaMStransfoByte;
		while(cByte > 0)
		{
			row_pointer[0]=(ulong(row_pointer[0])*ulong(row_pointer[0])
			cByte -= 4;
		}
	}*/
}

void dpng_end_callback(png_structp png_ptr, png_infop info_ptr)
{
/* This function is called after the whole image
   has been read, including any chunks after the
   image (up to and including the IEND).  You
   will usually have the same info chunk as you
   had in the header, although some data may have
   been added to the comments and time fields.

   Most people won't do much here, perhaps setting
   a flag that marks the image as finished.
 */
}

void pngDecompressor::pngOpen()
{
	pngClose();
	
    m_png_ptr = png_create_read_struct
        (PNG_LIBPNG_VER_STRING, (png_voidp)this,
         dpng_user_error_fn, dpng_user_warning_fn);
    if (!m_png_ptr)
		xbEmem::Throw("not enough memory for png decompressor");
    m_info_ptr = png_create_info_struct(m_png_ptr);
    if (!m_info_ptr)
		xbEmem::Throw("not enough memory for png decompressor");

    /* This one's new.  You can provide functions
       to be called when the header info is valid,
       when each row is completed, and when the image
       is finished.  If you aren't using all functions,
       you can specify NULL parameters.  Even when all
       three functions are NULL, you need to call
       png_set_progressive_read_fn().  You can use
       any struct as the user_ptr (cast to a void pointer
       for the function call), and retrieve the pointer
       from inside the callbacks using the function

          png_get_progressive_ptr(png_ptr);

       which will return a void pointer, which you have
       to cast appropriately.
     */
    png_set_progressive_read_fn(m_png_ptr, (void *)this,
        dpng_info_callback, dpng_row_callback, dpng_end_callback);
}

/*
int pngDecompressor::pngEndStayingData(int dataLg)
{
	my_src_ptr src = (my_src_ptr) m_cinfo.src;
	if (src->cByteToSkip != 0)
		xbThrow gdiEcompress("Bytes to skip staying at decompression end, can't get processed data length (png plugin, bytes: "+xbString(src->cByteToSkip)+")");

	int dataStaying = src->pub.bytes_in_buffer;
	if (dataStaying > dataLg)
		xbThrow gdiEcompress("Too many data staying at decompression end, can't get processed data length (png plugin, bytes staying: "+xbString(dataStaying)+", current bytes written: "+xbString(dataLg)+")");

	return dataLg - dataStaying;
}
*/

int palRGB_find(const png_color &entry, const void *ptr)
{
	int ret = int(entry.red) - (int(*((uchar*)ptr+2)));
	if (ret != 0)
		return ret;
	ret = int(entry.green) - (int(*((uchar*)ptr+1)));
	if (ret != 0)
		return ret;
	return int(entry.blue) - (int(*((uchar*)ptr)));
}

/*int palRGBA_find(const png_sPLT_entry_struct &entry, const void *ptr)
{
	int ret = int(entry.red) - int(*(uchar*)ptr);
	if (ret != 0)
		return ret;
	ret = int(entry.green) - int(*((uchar*)ptr+1));
	if (ret != 0)
		return ret;
	ret = int(entry.blue) - int(*((uchar*)ptr+2));
	if (ret != 0)
		return ret;
	return int(entry.alpha) - int(*((uchar*)ptr+3));
}
*/

void picBitmap_getRowWithPalette(IgdiBitmap *pBmp, int row, const xbArray<png_color> &palette, int transparentEntry, xbData &bmpData)
{
	int bytesPerPixels = pBmp->GetBitsPerPixel();
	bytesPerPixels /= 8;

	gdiSizeLow sz(pBmp->GetPixelSize());

	xbLock	lck;
	uchar *row_pointer=NULL,*row_end,*row_dest = (uchar*)bmpData.GetWritePtr(sz.cx);
	int rowLength = bytesPerPixels * sz.cx,entryPos;
	uchar alpha;

//	const png_color *pEntry;
	pBmp->GetBitmapRow(&lck,row, &row_pointer, 1, rowLength);

	row_end = row_pointer + rowLength;
	while(row_pointer < row_end)
	{
		if (bytesPerPixels>3)
		{
			alpha = *(row_pointer+3);
			if (transparentEntry>=0 && alpha <= 0x0F)
			{
				*(row_dest++) = 0;

				row_pointer+=bytesPerPixels;
				continue;
			}
		}

		/*pEntry =*/ palette.SortedFind(row_pointer,palRGB_find,&entryPos);
		if (transparentEntry>=0)
			*(row_dest++) = entryPos+1;
		else
			*(row_dest++) = entryPos;
		row_pointer+=bytesPerPixels;
	}
};

bool picBitmap_calcPalette(IgdiBitmap *pBmp, xbArray<png_color> &palette,int *transparentEntry,png_color *pTransparentColor)
{
	palette.Length = 0;

	int bytesPerPixels = pBmp->GetBitsPerPixel();
//	if (bytesPerPixels <= 16 || bytesPerPixels > 32 || (bytesPerPixels%8) != 0)
//		return false;
	if (bytesPerPixels != 24 && (!transparentEntry || bytesPerPixels != 32))
		return false;
	if (transparentEntry)
		*transparentEntry = -1;

	bytesPerPixels /= 8;

//	int (*pFindCb)(const png_sPLT_entry_struct &entry, const void *ptr) = bytesPerPixels==3?palRGB_find:palRGBA_find;

	gdiSizeLow sz(pBmp->GetPixelSize());

	xbLock	lck;
	uchar *row_pointer=NULL,*row_end;
	int rowLength = bytesPerPixels * sz.cx;
	png_color *pEntry;
	int entryPos;
	bool	bAlpha = false;
	uchar alpha;
	png_color alphaEntry;

	for(int row = 0; row < sz.cy; row++)
	{
		pBmp->GetBitmapRow(&lck,row, &row_pointer, 1, rowLength);

		row_end = row_pointer + rowLength;
		while(row_pointer < row_end)
		{
			pEntry = palette.SortedFind(row_pointer,palRGB_find,&entryPos);
			if (!pEntry)
			{
				if (palette.Length >= 256)
					return false;
			
				png_color newEntry;
				newEntry.blue = *(row_pointer++);
				newEntry.green = *(row_pointer++);
				newEntry.red = *(row_pointer++);
				if (bytesPerPixels > 3)
				{
					alpha = *(row_pointer++);
					if (alpha >0xF && alpha < 0xF0)
						return false;
				}
				if (bytesPerPixels > 3 && alpha <= 0x0F)
				{
					if (bAlpha && memcmp(&alphaEntry,&newEntry,sizeof(newEntry)) != 0)
						return false;
					alphaEntry = newEntry;
					bAlpha = true;
					continue;
				}
	
				palette.Insert(entryPos,newEntry);
				
				continue;
			}

			row_pointer+=bytesPerPixels;
		}
	}

	if (!bAlpha)
		return true;
	if (palette.Length >= 256)
		return false;
	palette.Insert(0,alphaEntry);
	*transparentEntry = 0x0;
	if (pTransparentColor)
		*pTransparentColor = alphaEntry;
	return true;
};
