/*<<<***************************************************************************
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: xdcPicGifPlug
#Project: xdcPicStd
////////////////////////////////////////////////////////////////////////////////
#Author: Marc MIRTI MANCINELLI
#History:
<15/09/2013: creation.
12/02/2014: this notice inserted.
***************************************************************************>>>*/
#include "stdafx.h"
#include "xdcPicStd.h"
#include "xdcPicGifPlug.h"

//http://www.w3.org/Graphics/GIF/spec-gif89a.txt
//extension http://odur.let.rug.nl/~kleiweg/gif/netscape.html

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

gifPlug::gifPlug()
{
}

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

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

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

// IgdiCompressPlug methods
bool gifPlug::CanWorkWithType(LPCSTR pszType, IxbInputStream *pStm, xbRefPtr<IgdiDecompressor> *pprDecomp, IgdiImage *pImg) const
{
	if (pszType && stricmp(pszType,"gif") == 0)
	{
		if (pprDecomp || pImg)
		{
			((gifPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}
	//TODO
	return false;
}

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

	if (memcmp(pStmData,"GIF87",5) == 0 || memcmp(pStmData,"GIF89",5) == 0)
	{
		if (pprDecomp || pImg)
		{
			((gifPlug*)this)->Decompress(0,pprDecomp);
			(*pprDecomp)->NewOutput(pImg);
		}
		return true;
	}

	return false;
}

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

bool gifPlug::Compress(ulong caps, xbRefPtr<IgdiCompressor> *pprComp,xbOpMode reqMode)
{
//	xbAssert(caps == 0);
//	pngCompressor::CreateObject(m_pFact,pprComp);
	xbThrow gdiEcompress(E_NOTIMPL,"gifPlug::Compress() : not implemented (GIF plugin)");
	return false;
}

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

xbString gifPlug::GetCompressVersion(bool bShort,LCID lcid) const
{
	return "xdcPicture gif/1.0 Plugin";
}

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

xbString gifPlug::GetDefaultExtension() const
{
	return "gif";
}

bool gifPlug::GetCompressLevelInfo(gdiSettingInfo *pInfo) const
{
	return false;
}

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

gdiCompressCaps gifPlug::GetCaps() const
{
	return gdiCompressCaps(gdiCompressCaps_Read | gdiCompressCaps_CompressWithNoQualityLost | gdiCompressCaps_TransparentColor);
}

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

gifDecompressor::gifDecompressor()
{
	m_bGifInit = false;
	m_gifStep = gifStep_Unknown;
}

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

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

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

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

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

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


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

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

void gifDecompressor::ResetDecompressSettings()
{
}


//IxbOutputStream methods
bool	gifDecompressor::CanWrite() const
{
	return m_bGifInit;
}

ulong	gifDecompressor::Write(const void *pData,ulong DataLg,bool DataLg_Required)
{
	if (!m_bGifInit)
		xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, GIF decompressor not started","gifDecompressor::Write()");
	
	if (m_gifStep == gifStep_Unknown || m_gifStep == gifStep_GifEnd)
	{
		if (DataLg_Required && DataLg > 0)
			xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, decompression end reached with staying data","gifDecompressor::Write() : DataLg="+xbString(DataLg)+", Staying="+xbString(DataLg + waitingLength()));

		return DataLg;
	}

	m_waitingDta.Add(pData,DataLg);

	waitingStart();

	try
	{
		while(gifProcessWaitingData());
	}
	catch(...)
	{
		waitingEnd();
		m_gifStep = gifStep_Unknown;
		throw;
	}

	waitingEnd();

	if (m_gifStep == gifStep_Unknown || m_gifStep == gifStep_GifEnd)
	{
		int cDta = gifEndStayingData(DataLg);
		if (DataLg_Required && cDta != int(DataLg))
			xbThrow gdiEcompress(STG_E_WRITEFAULT,"Write failed, decompression end reached with staying data","gifDecompressor::Write() : DataLg="+xbString(DataLg)+", Staying="+xbString(cDta));
		return cDta;
	}
	return DataLg;
}

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

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

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

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


void gifDecompressor::gifClose()
{
	if (!m_bGifInit)
		return;
	
	m_bGifInit = false;
	m_img.Empty();
	m_hdr.Empty();
	m_lzw.Empty();


	m_prBmp.Empty();
	m_gifStep = gifStep_Unknown;
}


void gifDecompressor::gifOpen()
{
	gifClose();
	
	m_bGifInit = true;
	
	m_waitingDta.Empty();
	m_gifStep = gifStep_ReadHeader;
}

int gifDecompressor::gifEndStayingData(int dataLg)
{
	int dataStaying = waitingLength();
	if (dataStaying > dataLg)
		xbThrow gdiEcompress("Too many data staying at decompression end, can't get processed data length (GIF plugin, bytes staying: "+xbString(dataStaying)+", current bytes written: "+xbString(dataLg)+")");

	return dataLg - dataStaying;
}

bool gifDecompressor::gifProcessWaitingData()
{
	uchar byte,*pBuf;
	switch(m_gifStep)
	{
	case gifStep_ReadBodySkip:
		for(;;)
		{
			if (waitingLength() < 1)
				return false;
			byte = waitingReadUchar();
			if (byte == 0)
				break;
			if (waitingLength() < byte)
			{
				waitingBackward(1);
				return false;
			}
			waitingForward(byte);
		}
		
		m_gifStep = gifStep_ReadBody;
		return true;

	case gifStep_ReadImageColorMap:
		if (waitingLength() < 3*m_img.colors)
			return false;
		waitingRead(m_img.colormap.GetWritePtr(3*m_img.colors), 3*m_img.colors);
		m_gifStep = gifStep_ReadImagePixels;
		return true;

	case gifStep_ReadHeader:
		if (waitingLength() < gifHeader_size)
			return false;

		waitingRead(m_hdr.magic_cookie, 6);

		if (strncmp((char *)m_hdr.magic_cookie, "GIF87",5) == 0)
		  m_hdr.file_type = GIF87;
		else if (strncmp((char *)m_hdr.magic_cookie, "GIF89", 5) == 0)
		  m_hdr.file_type = GIF89;
		else
			xbThrow gdiEcompress("Invalid GIF file (invalid header, GIF plugin)");

		m_hdr.width = waitingReadUshort();
		m_hdr.height = waitingReadUshort();

		m_hdr.flag = waitingReadUchar();
		m_hdr.background = waitingReadUchar();
		m_hdr.pixel_aspect = waitingReadUchar();

		m_hdr.trans_index = -1;
		m_hdr.iteration = -1;
		m_hdr.frame_count = 0;
		m_hdr.disposeFlags = 0;
		m_hdr.delay = 0;

		if (m_prImg.IsEmpty())
		{
			gdiCreateImage(&m_prImg,"simple","GIF image");
		}
		m_prImg->SetBitmapCount(0);

		/* Check and then read in a global color map if there is one. */
		if ((m_hdr.flag & 0x80) != 0x80)
		{
		  m_hdr.global_colors=0;
		  m_gifStep = gifStep_ReadBody;
		  return true;
		}

		m_hdr.global_colors=1 << ((m_hdr.flag & 0x07)+1);
		m_hdr.global_colormap.Length = 3*m_hdr.global_colors;
		m_gifStep = gifStep_ReadGlobalColorMap;
		if (waitingLength() < gifHeader_size)
			return false;
	case gifStep_ReadGlobalColorMap:
		if (waitingLength() < 3*m_hdr.global_colors)
			return false;
		waitingRead(m_hdr.global_colormap.GetWritePtr(3*m_hdr.global_colors), 3*m_hdr.global_colors);
		m_gifStep = gifStep_ReadBody;
	case gifStep_ReadBody:
		for (;;)
		{
			if (waitingLength() < 1)
				return false;
			byte = waitingReadUchar();

			if (byte == ';')
			{
				m_gifStep = gifStep_GifEnd;
				return false;
			}
			if (byte == ',')
			{
				m_gifStep = gifStep_ReadImage;
				break; 
			}
			if (byte != 0x21)
				 continue;
			
			if (waitingLength() < 1)
			{
				waitingBackward(1);
				return false;
			}

			byte = waitingReadUchar();

			switch(byte)
			{
			case 0xf9:
				if (waitingLength() < 1)
				{
					waitingBackward(2);
					return false;
				}

				byte = waitingReadUchar();
				if (byte < 3)
					xbThrow gdiEcompress("Invalid GIF file (invalid body, GIF plugin)");

				if (waitingLength() < byte)
				{
					waitingBackward(3);
					return false;
				}

				waitingRead(pBuf = (uchar*)m_hdr.byte_buf.GetWritePtr(byte), (int)byte);

				m_hdr.disposeFlags = pBuf[0];// >> 2;
				m_hdr.delay = (pBuf[2] << 8) | pBuf[1]; //hundredths (1/100) of a second 
				if (m_hdr.disposeFlags & 0x1)
				{
					if (byte < 4)
						xbThrow gdiEcompress("Invalid GIF file (invalid body, GIF plugin)");
					m_hdr.trans_index = pBuf[3];
				}

				break;
			case 0xfe: //comment
//				m_gifStep = gifStep_ReadBodySkip;
				if (waitingLength() < 1)
				{
					waitingBackward(2);
					return false;
				}

				byte = waitingReadUchar();
				if (byte < 3)
					xbThrow gdiEcompress("Invalid GIF file (invalid body comment, GIF plugin)");

				if (waitingLength() < byte)
				{
					waitingBackward(3);
					return false;
				}
				waitingRead(pBuf = (uchar*)m_hdr.byte_buf.GetWritePtr(byte), (int)byte);
				if (m_hdr.comment.IsValid())
					m_hdr.comment += "\n";
				m_hdr.comment += xbString((LPCSTR)pBuf,byte);

				m_gifStep = gifStep_ReadBodySkip;

				return true;
			case 0xff: //app specific extensions
				if (waitingLength() < 1)
				{
					waitingBackward(2);
					return false;
				}

				byte = waitingReadUchar();
				if (byte < 3)
					xbThrow gdiEcompress("Invalid GIF file (invalid body extension, GIF plugin)");

				if (waitingLength() < byte)
				{
					waitingBackward(3);
					return false;
				}

				waitingRead(pBuf = (uchar*)m_hdr.byte_buf.GetWritePtr(byte), (int)byte);
				while (byte > 8)
				{
					if (byte == 11)
					{
						xbString appName((LPCSTR)pBuf,11);
						if (appName.Compare("NETSCAPE2.0")==0)
						{
							//extension http://odur.let.rug.nl/~kleiweg/gif/netscape.html
							byte = waitingReadUchar();

							if (waitingLength() < byte)
							{
								waitingBackward(3+11);
								return false;
							}

							waitingRead(pBuf = (uchar*)m_hdr.byte_buf.GetWritePtr(byte), (int)byte);

							m_hdr.iteration = (pBuf[2] << 8) | pBuf[1];				//pBuf = Appli name
							break;
						}
					}
					break;
				}

				m_gifStep = gifStep_ReadBodySkip;
				break;
			default:
				if (waitingLength() < 1)
				{
					waitingBackward(2);
					return false;
				}

				byte = waitingReadUchar();

				if (waitingLength() < byte)
				{
					waitingBackward(3);
					return false;
				}
				if (byte > 0)
					waitingForward(byte);
				break;
			}
		}
	case gifStep_ReadImage:
		if (waitingLength() < gifImageHeader_size)
			return false;

		m_hdr.frame_count++;

		m_img.posX	= waitingReadUshort();
		m_img.posY = waitingReadUshort();
		m_img.width	= waitingReadUshort();
		m_img.height = waitingReadUshort();
		m_img.flag = waitingReadUchar();

		{
			m_prBmp.Empty();

			gdiBmpInfo	info;
			info.imgSz.cx = m_hdr.width;
			info.imgSz.cy = m_hdr.height;
			info.bmpSz.cx = m_img.width;
			info.bmpSz.cy = m_img.height;
			info.bmpOffset.x = m_img.posX;
			info.bmpOffset.y = m_img.posY;
			info.bmpType = gdiBmpColor_RGBA;
			info.bmpLoopTime = m_hdr.delay*10;
			info.bmpLoopNext=-1;
			{
				int dispose = ((m_hdr.disposeFlags>>2) & 0x7);
				switch(dispose)
				{
				case 0x1: //leave image for overwrite
					info.bmpLoopFlags|=gdiLoopFlag_LeaveImage;
					break;
				case 0x3: //restore previous
					info.bmpLoopFlags|=gdiLoopFlag_RestorePrevious;
					break;
				default:
				case 0x2: //restore background
					info.bmpLoopFlags|=gdiLoopFlag_RestoreBackground;
					break;
				}
			}
			if (m_hdr.disposeFlags & 0x2)
				info.bmpLoopFlags|=gdiLoopFlag_WaitUserInput;
			info.bmpLoopCount=m_hdr.iteration;
/*			if (m_hdr.disposeFlags & 0x1)
			{
				info.bmpLoopFlags|=gdiLoopFlag_ForceBackground;
				info.bmpBgColor=
				const uchar *colormap;
				if (pDecomp->m_img.colors)
					colormap = (const uchar*)(LPCVOID)pDecomp->m_img.colormap;
				else
					colormap = (const uchar*)(LPCVOID)pDecomp->m_hdr.global_colormap;
			}
*/
			info.bmpBitsPerPixel = 4 * 8;
			info.comment = m_hdr.comment;
			m_hdr.comment.Empty();

			m_prImg->CreateBitmap(m_hdr.frame_count,&info,&m_prBmp);
		}

		if (m_img.flag & 0x80)
		{
			m_img.colors = 1 << (( m_img.flag & 0x07)+1);
			m_gifStep = gifStep_ReadImageColorMap;
			return true;
		}

		m_img.colors = 0;
		m_gifStep = gifStep_ReadImagePixels;
	case gifStep_ReadImagePixels:
		if (waitingLength() < 1)
			return false;
		m_lzw.Init(m_img.width	* m_img.height,waitingReadUchar());
		m_gifStep = gifStep_ReadLZWchunk;
	case gifStep_ReadLZWchunk:
		if (!m_lzw.Decode(this))
			return false;

		if ((m_img.flag & 0x40) == 0x40)
		{
			xbData tmpPlaneDta;
			
			const ushort *map1 = m_lzw.bitplane;
			ushort *tempplane = (ushort*)tmpPlaneDta.GetWritePtr(m_img.width*m_img.height*sizeof(unsigned short));
			ushort *map2 = tempplane;
			
			static int
				  interlace_rate[4] = { 8, 8, 4, 2 },
				  interlace_start[4] = { 0, 4, 2, 1 };

			int y;
			for (int pass=0; pass < 4; pass++)
			{
				y = interlace_start[pass];
				while (y < m_img.height)
				{
					map2 = tempplane+(y*m_img.width);
					for (int x=0; x < m_img.width; x++)
					{
						*map2=(*map1);
						map1++;
						map2++;
					}
					y+=interlace_rate[pass];
				}
			}
			m_lzw.pixelsDta.Attach(&tmpPlaneDta);
			m_lzw.bitplane = (ushort*)(LPCVOID)m_lzw.pixelsDta;
			m_lzw.bitplaneEnd = m_lzw.bitplane + m_lzw.size;
		}

		m_lzw.ToRGBA(this,m_prBmp);
		
		m_gifStep = gifStep_ReadBody;
		return true;
	case gifStep_GifEnd:
		return false;
	}

	xbThrow gdiEcompress(E_INVALIDARG,"gifCompressor::gifProcess() : unknown step("+xbString(int(m_gifStep))+") (GIF plugin)");
}

#define MaxStackSize  4096
#define NullCode  (-1)
#define MaxPacketSize  256
void gifLZW::Init(int cPixel,int cBits)
{
	size = cPixel;
	bitplane = (ushort*)pixelsDta.GetWritePtr(size * 2);
	data_size = cBits;

/*
Allocate decoder tables.
	*/
	packet=(uchar*)packetDta.GetWritePtr(MaxPacketSize*sizeof(byte));
	
	
	prefix=(short *)prefixDta.GetWritePtr(MaxStackSize*sizeof(short));
	suffix=(uchar *)suffixDta.GetWritePtr(MaxStackSize*sizeof(uchar));
	pixel_stack=(uchar *)pixel_stackDta.GetWritePtr((MaxStackSize+1)*sizeof(uchar));
	
	/*
    Initialize GIF data stream decoder.
	*/
	clear=1 << data_size;
	end_of_information=clear+1;
	available=clear+2;
	old_code=NullCode;
	code_size=data_size+1;
	code_mask=(1 << code_size)-1;

	if (available > MaxStackSize)
		xbThrow gdiEcompress("Invalid GIF file (invalid lzw data, cbits="+xbString(data_size)+", GIF plugin)");

	for (code=0; code < clear; code++)
	{
		prefix[code]=0;
		suffix[code]=code;
	}

	datum=0;
	bits=0;
	c=NULL;
	count=0;
	first=0;
	top_stack=pixel_stack;
	
	p = bitplane;

	bitplaneEnd = bitplane + size;
}

bool gifLZW::Decode(gifDecompressor *pDecomp)
{
	uchar data;
	while(p < bitplaneEnd)
	{
		if (top_stack > pixel_stack)
		{
			/*
			Pop a pixel off the pixel stack.
			*/
			top_stack--;
			*p = (unsigned short) *top_stack;
			p++;
			continue;
		}
		if (bits < code_size)
		{
			/*
			Load bytes until there is enough bits for a code.
			*/
			if (count == 0)
			{
				/*
				Read a new data block.
				*/
				/* count=ReadDataBlock((char *) packet,image->file); */
				if (pDecomp->waitingLength()<1)
					return false;
				data = pDecomp->waitingReadUchar();
				if (data == 0)
					break;
//Pauv tache				if (data > MaxStackSize)
//					xbThrow gdiEcompress("Invalid GIF file (lzw packet size too big, GIF plugin)");

				if (pDecomp->waitingLength()<data)
				{
					pDecomp->waitingBackward(1);
					return false;
				}

				count = data;
				pDecomp->waitingRead(packet, count);
				c=packet;
			}

			datum+=(*c) << bits;
			bits+=8;
			c++;
			count--;
			continue;
		}
		/*
		Get the next code.
		*/
		code=datum & code_mask;
		datum>>=code_size;
		bits-=code_size;
		/*
		Interpret the code
		*/
		
		if ((code > available) || (code == end_of_information))
			break;
		
		if (code == clear)
		{
			/*
			Reset decoder.
			*/
			code_size=data_size+1;
			code_mask=(1 << code_size)-1;
			available=clear+2;
			old_code=NullCode;
			continue;
		}
		if (old_code == NullCode)
		{
			*top_stack++=suffix[code];
			old_code=code;
			first=code;
			continue;
		}
		int in_code=code;
		if (code == available)
		{
			*top_stack++=first;
			code=old_code;
		}
		while (code > clear)
		{
			*top_stack++=suffix[code];
			code=prefix[code];
		}
		first=suffix[code];
		/*
		Add a new string to the string table,
		*/
		if (available >= MaxStackSize)
			xbThrow gdiEcompress("Invalid GIF file (invalid lzw data, GIF plugin)");

		*top_stack++=first;
		
		prefix[available]=old_code;
		suffix[available]=first;
		available++;
		if (((available & code_mask) == 0) && (available < MaxStackSize))
		{
			code_size++;
			code_mask+=available;
		}
		old_code=in_code;
	}
	while(p < bitplaneEnd)
		*(p++) = 0;
	return true;
}

void gifLZW::ToRGBA(gifDecompressor *pDecomp,IgdiBitmap *pBmp)
{
	const uchar *colormap;
	if (pDecomp->m_img.colors)
		colormap = (const uchar*)(LPCVOID)pDecomp->m_img.colormap;
	else
		colormap = (const uchar*)(LPCVOID)pDecomp->m_hdr.global_colormap;

	uchar *pDest = NULL;

	xbLock lck;
	pBmp->GetBitmapRow(&lck,0, (uchar**) &pDest, 1, 0);

	int transparentColorIndex = pDecomp->m_hdr.trans_index;
	int index;
	p = bitplane;
	while(p < bitplaneEnd)
	{
		index = (*p)*3;

		if ((*p) == transparentColorIndex)
		{
			//WARNING:
			//Pour win!: fct alphablend gérant à priori mal la couche alpha: pas de coeff alpha sur la src uniquement
			// sur la dest: mode de calc des couches RGB avec alphablend: cDest = cSrc + cDest * (255 - alphaSrc) / 255
			// alors que ça devrait être cDest = (cSrc*alphaSrc)/255 + (cDest * (255 - alphaSrc) / 255)
			// il ne faut donc pas utiliser la palette pour la couleur transparente, 
			// et mettre les composantes du pixel transparent à 0
			*(pDest++) = 0x00;
			*(pDest++) = 0x00;	
			*(pDest++) = 0x00;
			*(pDest++) = 0x00;
			p++;
			continue;
		}
		*(pDest++) = colormap[index+2];
		*(pDest++) = colormap[index+1];
		*(pDest++) = colormap[index];
		*(pDest++) = 0xFF;

		p++;
	}
}

int gifDecompressor::waitingLength()
{
	if (m_pWaitingDta)
		return xbNptr(m_pWaitingDtaEnd) - xbNptr(m_pWaitingDta);
	return m_waitingDta.Length;
}

void gifDecompressor::waitingRead(void *pBuf, int cByte)
{
	if (cByte + m_pWaitingDta > m_pWaitingDtaEnd)
		xbThrow gdiEcompress("gifDecompressor::waitingRead() : not enough waiting data");
	memcpy(pBuf,m_pWaitingDta,cByte);
	m_pWaitingDta+=cByte;
}

uchar gifDecompressor::waitingReadUchar()
{
	if (m_pWaitingDta >= m_pWaitingDtaEnd)
		xbThrow gdiEcompress("gifDecompressor::waitingReadUchar() : not enough waiting data");
	return *(m_pWaitingDta++);
}

ushort gifDecompressor::waitingReadUshort()
{
	if (m_pWaitingDta + 2 > m_pWaitingDtaEnd)
		xbThrow gdiEcompress("gifDecompressor::waitingReadUshort() : not enough waiting data");
   ushort val = ((m_pWaitingDta[1] << 8) | m_pWaitingDta[0]);
   m_pWaitingDta+=2;
   return val;
}

void gifDecompressor::waitingForward(int cByte)
{
	if (cByte < 0 || cByte + m_pWaitingDta > m_pWaitingDtaEnd)
		xbThrow gdiEcompress("gifDecompressor::waitingForward() : not enough waiting data");
	m_pWaitingDta+=cByte;
}

void gifDecompressor::waitingBackward(int cByte)
{
	if (cByte < 0 || m_pWaitingDta-cByte < m_pWaitingDtaStart)
		xbThrow gdiEcompress("gifDecompressor::waitingBackward() : not enough waiting data");
	m_pWaitingDta-=cByte;
}

void gifDecompressor::waitingStart()
{
	m_pWaitingDtaStart = (const uchar *)(LPCVOID)m_waitingDta;
	m_pWaitingDtaEnd = m_pWaitingDtaStart + m_waitingDta.Length;
	m_pWaitingDta = m_pWaitingDtaStart;
}
void gifDecompressor::waitingEnd()
{
	int cByte = xbNptr(m_pWaitingDta) - xbNptr(m_pWaitingDtaStart);
	m_waitingDta.Delete(0,cByte);
	m_pWaitingDta=NULL;
}

