#include "wvimage.h"

#include <stdlib.h>  
#include <tchar.h>


#include <windowsx.h>
#include <windows.h>

static BOOL bWarned = FALSE;

typedef struct tag_PNGHEADER
{
	BYTE sig[8];
	BYTE bLen[4];
	char Type[4];
	BYTE bWidth[4];
	BYTE bHeight[4];
	BYTE  bpp;
	BYTE  color_type;
	BYTE  compression;
	BYTE  filter;
	BYTE  interlace;

} PNGHEADER, * LPPNGHEADER;

HINSTANCE GDIPlusImage::m_hLibGDIPlus = NULL;

DWORD ReadDWord( LPBYTE pBuf)
{
	return (pBuf[3] + (pBuf[2] << 8) + (pBuf[1] << 16) + (pBuf[0] << 24) );
}


GDIPlusImage::GDIPlusImage()
{
	m_pImage = NULL;      
	m_gdiplusToken = NULL;
	UnInit();

}

GDIPlusImage::~GDIPlusImage()
{
	UnInit();
}

BOOL GDIPlusImage::Init()
{
	if (!m_hLibGDIPlus)
	{
		TCHAR szAppPath[_MAX_PATH];
		GetModuleFileName(0, (LPTSTR) &szAppPath[0], _MAX_PATH);
		TCHAR *p = _tcsrchr( (LPTSTR) &szAppPath[0], _T('\\'));
		
		if (p)
			p++;
		else
			p = szAppPath;

		_tcscpy(p, _T("GDIPLUS.DLL"));
		m_hLibGDIPlus = LoadLibrary(szAppPath);
	}

	if (!m_hLibGDIPlus)
		m_hLibGDIPlus = LoadLibrary(_T("GDIPLUS.DLL"));


	if (!pGdiplusStartup && m_hLibGDIPlus)										   
	{
		//not supported on old GDIplus
		pGdiBitmapConvertFormat = (PGDIBITMAPCONVERTFORMAT) GetProcAddress( m_hLibGDIPlus, _T("GdipBitmapConvertFormat"));
	
		pGdipGetImageWidth = (PGDIPGETIMAGEWIDTH) GetProcAddress( m_hLibGDIPlus, _T("GdipGetImageWidth"));
		pGdipGetImageHeight = (PGDIPGETIMAGEHEIGHT)	GetProcAddress( m_hLibGDIPlus, _T("GdipGetImageHeight"));
		pGdipLoadImageFromFile = (PGDIPLOADIMAGEFROMFILE) GetProcAddress( m_hLibGDIPlus, _T("GdipLoadImageFromFile"));
		pGdipLoadImageFromStream = (PGDIPLOADIMAGEFROMSTREAM) GetProcAddress( m_hLibGDIPlus, _T("GdipLoadImageFromStream"));

		pGdipDrawImageRectRectI = (PDRAWIMAGERECTRECTI) GetProcAddress(m_hLibGDIPlus, _T("GdipDrawImageRectRectI"));
		pGdipCreateFromHDC = (PGDIPCREATEFROMHDC) GetProcAddress(m_hLibGDIPlus, _T("GdipCreateFromHDC"));
		pGdipSetInterpolationMode = (PGDIPSETINTERPOLATIONMODE) GetProcAddress(m_hLibGDIPlus, _T("GdipSetInterpolationMode"));
		pGdipDeleteGraphics = (PGDIPDELETEGRAPHICS) GetProcAddress(m_hLibGDIPlus, _T("GdipDeleteGraphics"));

		pGdipDisposeImage = (PGDIPDISPOSEIMAGE ) GetProcAddress(m_hLibGDIPlus, _T("GdipDisposeImage"));

		pGdiplusStartup = (PGDIPLUSSTARTUP)	GetProcAddress(m_hLibGDIPlus, _T("GdiplusStartup"));
		pGdiplusShutdown = (PGDIPLUSSHUTDOWN) GetProcAddress(m_hLibGDIPlus, _T("GdiplusShutdown"));
		pGdipCreateBitmapFromFile = (PGDIPCREATEBITMAPFROMFILE)	GetProcAddress(m_hLibGDIPlus, _T("GdipCreateBitmapFromFile"));
		pGdipCreateBitmapFromStream = (PGDIPCREATEBITMAPFROMSTREAM) GetProcAddress(m_hLibGDIPlus, _T("GdipCreateBitmapFromStream"));
		pGdipBitmapLockBits =(PGDIPBITMAPLOCKBITS )	GetProcAddress(m_hLibGDIPlus, _T("GdipBitmapLockBits"));
		pGdipBitmapUnlockBits = (PGDIPBITMAPUNLOCKBITS) GetProcAddress(m_hLibGDIPlus, _T("GdipBitmapUnlockBits"));
		pGdipCreateBitmapFromGraphics = (PGDICREATEBITMAPFROMGRAPHICS) GetProcAddress(m_hLibGDIPlus, _T("GdipCreateBitmapFromGraphics"));

	
		if ( /*!pGdiBitmapConvertFormat || */!pGdipGetImageWidth || !pGdipGetImageHeight ||
			!pGdipLoadImageFromFile || !pGdipDrawImageRectRectI || !pGdipCreateFromHDC ||
			!pGdipSetInterpolationMode || !pGdipDeleteGraphics || !pGdipDisposeImage ||
			!pGdiplusStartup || !pGdiplusShutdown || !pGdipCreateBitmapFromFile ||
			!pGdipBitmapLockBits || !pGdipBitmapUnlockBits || !pGdipCreateBitmapFromGraphics ||
			!pGdipLoadImageFromStream || !pGdipCreateBitmapFromStream)
		{
			UnInit();

			return -2;
		}

		//if (!pGdiBitmapConvertFormat)
		//	::MessageBox(0, "GdiBitmapConvertFormat not supported in GDIPLUS.DLL, get new version", "Warning", MB_OK);
	
	}
	if (!m_hLibGDIPlus)
	{
		
		UnInit();
	
		return -1;
	}

	{

		struct GdiplusStartupInput input;
		m_bInit = (pGdiplusStartup(&m_gdiplusToken, &input, NULL) == 0);
		return m_bInit;
	}

}


void GDIPlusImage::UnInit()
{

	if (m_pImage && pGdipDisposeImage)
	{
		pGdipDisposeImage( m_pImage);
		m_pImage = NULL;
		
	}	
	
	if (m_gdiplusToken && pGdiplusShutdown)
		pGdiplusShutdown(m_gdiplusToken);
	m_gdiplusToken = NULL;

	
	pGdiBitmapConvertFormat = NULL;       
	pGdipGetImageWidth = NULL;            
	pGdipGetImageHeight = NULL;           
	pGdipLoadImageFromFile = NULL;
	pGdipLoadImageFromStream = NULL;       
	pGdipDrawImageRectRectI = NULL;       
	pGdipCreateFromHDC = NULL;            
	pGdipSetInterpolationMode = NULL;     
	pGdipDeleteGraphics = NULL;           
	pGdipDisposeImage = NULL;             
	pGdiplusStartup = NULL;               
	pGdiplusShutdown = NULL;              
	pGdipCreateBitmapFromFile = NULL;
	pGdipCreateBitmapFromStream = NULL;
	pGdipBitmapLockBits	= NULL;           
	pGdipBitmapUnlockBits = NULL;         
	pGdipCreateBitmapFromGraphics = NULL;
	m_bInit = FALSE;


}

BOOL GDIPlusImage::LoadImageFile(LPCTSTR szPath)
{
	LPWSTR pathW = NULL;	
	if (!pGdipCreateBitmapFromFile)
		return FALSE;

	if (m_pImage)
	{
		pGdipDisposeImage( m_pImage);
		m_pImage = NULL;
	}
	try
	{

		// we want to convert an MBCS string in lpszA
		int nLen = MultiByteToWideChar(CP_ACP, 0,szPath, -1, NULL, NULL);
		LPWSTR pathW = new WCHAR[nLen];
		MultiByteToWideChar(CP_ACP, 0, szPath, -1, pathW, nLen);

		GpStatus status =  pGdipCreateBitmapFromFile(pathW, &m_pImage);

		if (status == GdiplusNotInitialized)
		{
			m_pImage = NULL;
		}

		// free the string
		delete[] pathW;
		pathW = NULL;
		
		if (m_pImage)
		{

			UINT width = 0;
			UINT height = 0;

			pGdipGetImageWidth(m_pImage, &width);
			pGdipGetImageHeight(m_pImage, &height);

			if (width == 0 || height == 0)
			{
				pGdipDisposeImage( m_pImage);
				m_pImage = NULL;
			}

		}
	}
	catch (...)
	{
		if (pathW)
			delete[] pathW;
	}


	return (m_pImage != NULL);
}

HGLOBAL GDIPlusImage::GetDIB(HDC hdc)
{

	GpGraphics *graphics = NULL;
	HDC hDC = NULL;
	HBITMAP hbm = NULL, bmold = NULL;
	HGLOBAL hMem = NULL;
	UINT width, height;

	if (!pGdipGetImageWidth)
		return NULL;
   try
   {
		pGdipGetImageWidth(m_pImage, &width);
		pGdipGetImageHeight(m_pImage, &height);


		BITMAPINFO bmi;
		LPBITMAPINFOHEADER lpbmih = (LPBITMAPINFOHEADER) &bmi;
		LPBYTE pBits = NULL;
		
		lpbmih->biSize = sizeof(BITMAPINFOHEADER);
		lpbmih->biWidth = width; 
		lpbmih->biHeight = height; 
		lpbmih->biPlanes = 1; 
		lpbmih->biBitCount = 24; 
		lpbmih->biCompression = BI_RGB; 
		lpbmih->biSizeImage = 0; 
		lpbmih->biXPelsPerMeter = 0; 
		lpbmih->biYPelsPerMeter = 0; 
		lpbmih->biClrUsed = 0; 
		lpbmih->biClrImportant= 0; 

		int nBpp = lpbmih->biPlanes * lpbmih->biBitCount;

		hDC = CreateCompatibleDC(hdc);
		if (!hDC)
			return NULL;

		//always create 24bpp image to store image
		hbm = CreateDIBSection( hDC, &bmi,DIB_RGB_COLORS, (VOID**)&pBits, NULL, 0);  
		if (!hbm)
		{
			DeleteDC(hDC);
			return NULL;	
		}
		
		bmold = (HBITMAP) SelectObject(hDC, (HGDIOBJ)hbm);
		
		pGdipCreateFromHDC(hDC, &graphics);

		if (graphics)
		{
			pGdipSetInterpolationMode(graphics, InterpolationModeHighQualityBicubic);
			
			
			if ( nBpp == 16)
			{
				
				ColorPalette cpal;
				ZeroMemory(&cpal, sizeof(cpal));

				if (pGdiBitmapConvertFormat)
				{
				
					if (1)// ddsd.ddpfPixelFormat.dwGBitMask == 0x07e0)
						pGdiBitmapConvertFormat( m_pImage /*(GpBitmap*) ((LPBYTE)gp_Image)+0x28*/,  PixelFormat16bppRGB565, 
										DitherTypeErrorDiffusion, 
										PaletteTypeFixedHalftone256,
										&cpal, 0.0);
					else
						pGdiBitmapConvertFormat( m_pImage,PixelFormat16bppRGB555, 
										DitherTypeErrorDiffusion, 
										PaletteTypeFixedHalftone256,
										&cpal, 0.0);
				
				}
			}
			
			pGdipDrawImageRectRectI( graphics ,m_pImage, 0, 0, width, height,
								   0, 0, width, height, UnitPixel, NULL, 
								   /*DrawImageAbort callback*/NULL, /*callbackData*/NULL); 

			//done with graphics
			pGdipDeleteGraphics(graphics);

			//SaveHBitmap(hDC, hbm, "c:\\test.bmp");

			//Create packed DIB
			{
				
				long lBytesPerRow = ((((lpbmih->biWidth * nBpp ) + 31) & ~31) >> 3);
				int nPaletteEntries = nBpp < 9 ? (1 << nBpp) : 0;
				DWORD dwSize = abs(lBytesPerRow * lpbmih->biHeight);
				dwSize += sizeof(BITMAPINFOHEADER) + (nPaletteEntries * sizeof(RGBQUAD));
				
				
				hMem = GlobalAlloc(GHND, dwSize);
				 
				BITMAPINFOHEADER* pInfo = (BITMAPINFOHEADER*) GlobalLock(hMem);
				memcpy(pInfo, lpbmih, sizeof(BITMAPINFOHEADER));
				pInfo->biSizeImage = abs(lBytesPerRow * lpbmih->biHeight);

				if (nPaletteEntries)
				{
					RGBQUAD * pColor = (RGBQUAD*) ((LPBYTE)pInfo + pInfo->biSize);
					GetDIBColorTable( hDC, 0, nPaletteEntries, pColor); 

 
				}

				LPBYTE pDestBits = (LPBYTE)pInfo + sizeof(BITMAPINFOHEADER) + (nPaletteEntries * sizeof(RGBQUAD));
				memcpy(pDestBits, pBits, pInfo->biSizeImage );

				GlobalUnlock(hMem);
			}

		}

		if (bmold)
			SelectObject(hDC, (HGDIOBJ)bmold);
	}
	catch(...)
	{
		if (hMem)
		{
			GlobalFree(hMem);
			hMem = NULL;
		}
	
	}
	
	if (hbm)
	{
		DeleteObject(hbm);
		hbm = NULL;
	}
	
	if (hDC)
	{
		DeleteDC(hDC);
		hDC = NULL;
	}



	return hMem;
}

HGLOBAL GDIPlusImage::LoadImageMemory(HANDLE hMem)
{

	IStream* pStream = NULL;
	HGLOBAL hDib = NULL;

	if (!pGdipDisposeImage)
		return NULL;
	
	try
	{
		if (m_pImage)
		{
			pGdipDisposeImage( m_pImage);
			m_pImage = NULL;
		}


		if(CreateStreamOnHGlobal(hMem, TRUE, &pStream) == S_OK)
		{
			
			GpStatus status =  pGdipCreateBitmapFromStream(pStream, &m_pImage);

			if (status != GdiplusNotInitialized)
			{

			
				if (m_pImage)
				{

					//now do something with it
					HDC hDC = GetDC(NULL);
					if (hDC)
					{
						hDib = GetDIB(hDC);
						ReleaseDC(NULL, hDC);
					}
					
					//done with image, free bitmap
					pGdipDisposeImage( m_pImage);
					m_pImage = NULL;
				}
			}

			pStream->Release();
			pStream = NULL;

		}
	}
	catch(...)
	{

	
	}

   return hDib;

}

#define XX 127
/* Table for decoding base64 */
static unsigned char index_64[256] = {
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, //00 -0F
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, //10- 1F
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,62, XX,XX,XX,63, //20- 2F
    52,53,54,55, 56,57,58,59, 60,61,XX,XX, XX,XX,XX,XX, //30- 3F
    XX, 0, 1, 2,  3, 4, 5, 6,  7, 8, 9,10, 11,12,13,14, //40- 4F
    15,16,17,18, 19,20,21,22, 23,24,25,XX, XX,XX,XX,XX, //50- 5F
    XX,26,27,28, 29,30,31,32, 33,34,35,36, 37,38,39,40,
    41,42,43,44, 45,46,47,48, 49,50,51,XX, XX,XX,XX,XX,

    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
    XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX, XX,XX,XX,XX,
};
#define CHAR64(c)  (index_64[(unsigned char)(c)])

static char basis_64[] =
    "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
static const char Pad64 = '=';


//decode a line of Base64 to out. (Out) must be at least as large as (line)
//returns the number of bytes written or error < 0;
int KWIKdecode64(const char *line, BYTE *out, int len /*=0*/, int nMax /*=-1*/)
{
    int pos = 0;
    unsigned char c1, c2, c3 = 0, c4;
    int count = 0;
    BOOL bSkipSpace = FALSE;
	    
    if (!line || !out)
        return -1;
    
    if (len <= 0)
        len = strlen(line);
    
	int nibble = 0;
	BOOL bDone = FALSE;
    
    //4 ascii -> 3 binary, = is pad
    while (pos < len && !bDone)
    {

		if (line[pos] == 0x0D || line[pos] == 0x0A)
		{
			bSkipSpace = TRUE;
			pos++;
			continue;
		}
		if (bSkipSpace)
		{
			if (line[pos] == 0x20)
			{
				pos++;
				continue;
			}
			bSkipSpace = FALSE;
		}
		if (CHAR64(line[pos]) == XX)
		{
			pos++;
			continue;
		}
        
		switch (nibble)
		{
			case 0:
				c1 = CHAR64(line[pos++]);
				break;
			case 1:
		        c2 = CHAR64(line[pos++]);
				
				if (c1 == XX || c2 == XX)
					bDone = TRUE;
				else
					out[count++] = (unsigned char) ((c1<<2) | ((c2&0x30)>>4));
        
				if (nMax > 0 && count >= nMax)
					bDone = TRUE;
				break;
			case 2:
				c3 = CHAR64(line[pos++]);
				if (c3 == XX)
					bDone = TRUE;
				else
					out[count++] = (unsigned char)(((c2&0x0F) << 4) | ((c3&0x3C) >> 2));
 
				if (nMax > 0 && count >= nMax)
					bDone = TRUE;
				break;
			case 3:
				c4 = CHAR64(line[pos++]);
				if (c4 == XX)
					bDone = TRUE;
				else
					out[count++] = (unsigned char)(((c3&0x03) << 6) | c4);

				if (nMax > 0 && count >= nMax)
					bDone = TRUE;
				break;   
		}
		nibble++;
		nibble %= 4;


    }

    return count;
}

//From TIN
int Encode64(const BYTE *src, int srclength, char *target, int targsize)
{
    int          datalength = 0;
    unsigned char   input[3];
    unsigned char   output[4];
    int            i;

    while (2 < srclength) 
	{
		input[0] = *src++;
		input[1] = *src++;
		input[2] = *src++;
		srclength -= 3;

		output[0] = input[0] >> 2;
		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);
		output[3] = input[2] & 0x3f;


		if (datalength + 4 > targsize)
			return (-1);
		target[datalength++] = basis_64[output[0]];
		target[datalength++] = basis_64[output[1]];
		target[datalength++] = basis_64[output[2]];
		target[datalength++] = basis_64[output[3]];
    }

    /* Now we worry about padding. */
    if (0 != srclength) 
	{
		/* Get what's left. */
		input[0] = input[1] = input[2] = '\0';
		for (i = 0; i < srclength; i++)
			input[i] = *src++;

		output[0] = input[0] >> 2;
		output[1] = ((input[0] & 0x03) << 4) + (input[1] >> 4);
		output[2] = ((input[1] & 0x0f) << 2) + (input[2] >> 6);


		if (datalength + 4 > targsize)
			return (-1);
		target[datalength++] = basis_64[output[0]];
		target[datalength++] = basis_64[output[1]];
		if (srclength == 1)
			target[datalength++] = Pad64;
		else
			target[datalength++] = basis_64[output[2]];
		target[datalength++] = Pad64;
    }
    if (datalength >= targsize)
		return (-1);
    target[datalength] = '\0';	/* Returned value doesn't count \0. */
    return (datalength);
}



HGLOBAL GDIPlusImage::DIBFromFace(LPCSTR szFace)
{
	if (!szFace || !*szFace || !pGdipDisposeImage)
		return NULL;
	int nLen = strlen(szFace);
	int nData = 0;
	
	LPBYTE pData = NULL;
	
	//should only need 968 bytes, but just to be safe...
	HANDLE hMem = GlobalAlloc( GHND, strlen(szFace) );
	
	HGLOBAL hDIB = NULL;

	if (hMem)
		pData = (LPBYTE) GlobalLock(hMem);

	if (pData)
	{
		int nWidth = 0, nHeight = 0, nBpp = 0;

		nData = KWIKdecode64(szFace, pData, nLen, nLen-1);

		LPPNGHEADER pPng = (LPPNGHEADER) pData;
		if ( !strncmp(pPng->Type, "IHDR", 4) )
		{
		
			nWidth = ReadDWord(pPng->bWidth);
			nHeight = ReadDWord(pPng->bHeight);
			nBpp =  pPng->bpp;
			switch(pPng->color_type)
			{
				case 6:
				case 2:
					nBpp *= 3;
					break;
				case 0:
				case 3:
				case 4:
					break;
			}
		}

		GlobalUnlock(hMem);
		//should be 48x48, but not all are ...
		if ( nWidth > 48 || nWidth < 16 || nHeight > 48 || nHeight < 16)
		{
			//Not a PNG or size wrong
			GlobalFree(hMem);
			return NULL;
		}


	}

	if (nData)
		hDIB =  LoadImageMemory(hMem);

	
	if (hMem)
	{
		GlobalFree(hMem);
		hMem = NULL;
	}

	return hDIB;

}