/*----------------------------------------------------------------------
Copyright (c) 1998,1999 Gipsysoft. All Rights Reserved.
Please see the file "licence.txt" for licencing details.
File:	DecodePCX.cpp
Owner:	rich@woodbridgeinternalmed.com
Purpose:	Decode an PCX file into a frame array.
			The only exported function is:
			bool DecodePCX( CDataSourceABC &ds, CFrameArray &arrFrames, int &nWidth, int &nHeight )
----------------------------------------------------------------------*/
#include "stdafx.h"
#include "DataSourceABC.h"
#include <ImgLib.h>
#include "ImgLibInt.h"
#include "Config.h"

#ifdef TRACING
	#define ITRACE TRACE
#else	//	TRACING
	#define ITRACE 
#endif	//	TRACING

#ifdef IMGLIB_PCX

// PCX Data structure
#pragma pack(push, 1)

struct PCXHEAD
{
	char manufacturer;
	char version;
	char encoding;
	char bits;
	short xmin, ymin;
	short xmax, ymax;
	short hres;
	short vres;
	char palette[48];
	char reserved;
	char color_planes;
	short bytes_per_line;
	short palette_type;
	char filler[58];
};

#pragma pack(pop)

// Here is a local class that handles some of the details, such as buffering
// the input.

class PCXFileReader
{
public:
	PCXFileReader();

	bool Decode(CDataSourceABC &ds, CFrameArray &arrFrames, SIZE &size );

protected:
	bool ReadLine(BYTE *p, int bytes);
	int NextChar();

	CDataSourceABC* m_ds;

	WORD m_width, m_depth, m_bits;
	BYTE m_palette[768];

	BYTE* m_readbuffer;
	BYTE* m_nextbyte;
	DWORD m_bytes_in_buffer;
	DWORD m_sourceSize;

};

#define RGB_RED		0
#define RGB_GREEN		1
#define RGB_BLUE     2
#define WRGB_RED     2
#define WRGB_GREEN   1
#define WRGB_BLUE     0
#define RGB_SIZE 		3



static const int masktable[8] =
   {0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

static const int bittable[8] =
   {0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80};


#define READ_BUFFER_SIZE  16384

PCXFileReader::PCXFileReader()
: m_readbuffer(0)
{
}

int PCXFileReader::NextChar()
{
	if (!m_bytes_in_buffer)
	{
		// Get bytes to read...
		m_bytes_in_buffer = min(READ_BUFFER_SIZE, m_sourceSize - m_ds->GetCurrentPos());
		// load more data...
		if (!m_ds->ReadBytes(m_readbuffer, m_bytes_in_buffer))
		{
			m_bytes_in_buffer = 0;
		}
		m_nextbyte = m_readbuffer;
   }
   if (!m_bytes_in_buffer)
      return -1;
   --m_bytes_in_buffer;
   return *m_nextbyte++;
}


bool PCXFileReader::ReadLine(BYTE* p, int bytes)
{
	short n = 0, i;
	int c;

	do
	{
		c = NextChar();
		if (c == -1) { return false; }
		if ((c & 0xc0) == 0xc0)
		{
			i = WORD(c & 0x3f);
			c = NextChar();
			if (c == -1) { return false; }
			while (i--) p[n++] = (BYTE)c;
		} else p[n++] = (BYTE)c;
	} while (n < bytes);
	if (n != bytes)
      return false;
   return true;
}

inline
DWORD LineBytes(DWORD width, WORD bpp)
{
  return (DWORD)((width*(DWORD)bpp+31)&(~31))/8L;
}

bool PCXFileReader::Decode(CDataSourceABC &ds, CFrameArray &arrFrames, SIZE &size )
{
	PCXHEAD pcx;
	BYTE *ps, *pd, *linebuffer = 0, *extrabuffer = 0;
	int a, i, j, k, n, x, bytes;
	bool readLargePal = false;
	CDib * pDib = NULL;

	static const BYTE pcxpalette[48] = { 0x00, 0x00, 0x0e, 0x00, 0x52, 0x07, 0x2c, 0x00,
									  0x0e, 0x00, 0x00, 0x00, 0xf8, 0x01, 0x2c, 0x00,
									  0x85, 0x0f, 0x42, 0x00, 0x21, 0x00, 0x00, 0x00,
									  0x00, 0x00, 0x6a, 0x24, 0x9b, 0x49, 0xa1, 0x5e,
									  0x90, 0x5e, 0x18, 0x5e, 0x84, 0x14, 0xd9, 0x95,
									  0xa0, 0x14, 0x12, 0x00, 0x06, 0x00, 0x68, 0x1f
	};

	try {
		m_ds = &ds;
		m_sourceSize = m_ds->GetSize();
		
		if (!m_ds->ReadBytes((BYTE*)&pcx, sizeof(PCXHEAD)))
			return false;

		// Make sure it's a pcx file
		if (pcx.manufacturer != 0x0a)
	      return false;
		m_width = (WORD)(pcx.xmax - pcx.xmin + 1);
		m_depth = (WORD)(pcx.ymax - pcx.ymin + 1);

		if (pcx.bits == 8 && pcx.color_planes == 3) 
			m_bits = 24;
		else if (pcx.bits == 1) 
			m_bits = pcx.color_planes;
		else 
			m_bits = pcx.bits;

		if (m_bits == 1)
			memcpy(m_palette, "\000\000\000\377\377\377", 6);
		else
		{
			if (m_bits == 8 && pcx.version >= 5)
			{
				readLargePal = true;
			} 
			else if (pcx.version == 3) 
				memcpy(m_palette, pcxpalette, 48);
			else 
				memcpy(m_palette, pcx.palette, 48);
		}

		if (m_bits > 1 && m_bits <= 4)
		{
			if (pcx.bits == 4 && pcx.color_planes == 1) 
				bytes = pcx.bytes_per_line;
			else 
				bytes = (short)(pcx.bytes_per_line * m_bits);
		} 
		else if (m_bits == 24) 
			bytes = (short)(pcx.bytes_per_line * RGB_SIZE);
		else
			bytes = pcx.bytes_per_line;

		size.cx = m_width;
		size.cy = m_depth;

		pDib = new CDib( size.cx, size.cy, m_bits );

		// Allocate work data
		long linewidth = LineBytes(m_width, m_bits);
		long l = m_width > linewidth ? m_width : linewidth;
		// Need to be able to read at least 'bytes' into memory 
		long l1 = l > (long)bytes ? l : bytes;
		linebuffer = new BYTE[l1];
		extrabuffer= new BYTE[l1];
		n = linewidth;

		// Setup a read buffer
		m_bytes_in_buffer = 0;
		m_readbuffer = new BYTE[READ_BUFFER_SIZE];   // big enough?

		// Get line array from CDib
		Container::CArray< BYTE * > arrLines;

		pDib->GetLineArray( arrLines );
	
		for (i = 0; i < m_depth; ++i)
		{
			if (!ReadLine(linebuffer, bytes))
				throw 1;
			if (m_bits > 1 && m_bits <= 4)
			{
				if (pcx.bits != 4 || pcx.color_planes != 1)
				{
					memset(extrabuffer, 0, linewidth);

					pd = extrabuffer;
					ps = linebuffer;
					for (j = 0; j < m_bits; ++j)
					{
						memcpy(pd, ps, pcx.bytes_per_line);
						ps += pcx.bytes_per_line;
						pd += n;
					}
					memset(linebuffer, 0, linewidth);
					for (j = x = 0; j < m_width;)
					{
						a = 0;
						ps = extrabuffer;
						for (k = 0; k < m_bits; ++k)
						{
							if (ps[j >> 3] & masktable[j & 0x0007])
							a |= bittable[k];
							ps += n;
						}
						linebuffer[x] = (BYTE)((a & 0x0f) << 4);

						++j;

						if (j < m_width)
						{
							a = 0;
							ps = extrabuffer;
							for (k = 0; k < m_bits; ++k)
							{
								if (ps[j >> 3] & masktable[j & 0x0007])
								a |= bittable[k];
								ps += n;
							}
							linebuffer[x] |= (BYTE)(a & 0x0f);
						}
						++j;
						++x;
					}
				}
			}
			else if (m_bits == 24)
			{
				memcpy(extrabuffer, linebuffer, linewidth);
				pd = linebuffer;
				ps = extrabuffer;
				for (j = 0; j < m_width; ++j)
				{
					pd[j*RGB_SIZE + WRGB_RED] = ps[j];
					pd[j*RGB_SIZE + WRGB_GREEN] = ps[RGB_GREEN * pcx.bytes_per_line + j];
					pd[j*RGB_SIZE + WRGB_BLUE] = ps[RGB_BLUE * pcx.bytes_per_line + j];
				}
			}
			memcpy(arrLines[i], linebuffer, linewidth);
		}

		// Now, create the palette
		RGBQUAD *pct = pDib->GetColorTable();

		if (pct)
		{
			// Now, a 256 color palette may need to be read...
			if (readLargePal)
			{
				if (NextChar() == 12)
				{
					for (int y = 0; y < 768; ++y)
					m_palette[y] = (BYTE)NextChar();
				}
				else 
					memcpy(m_palette, pcx.palette, 48);
			}
		     // and set the palette
			int num_colors = 1 << m_bits;
			for (short i = 0; i < num_colors; i++)
			{
					pct->rgbRed   = m_palette[(i*3)];
					pct->rgbGreen = m_palette[(i*3)+1];
					pct->rgbBlue  = m_palette[(i*3)+2];
					pct->rgbReserved = 0xFF;
					pct++;
			}
		}
		if (pDib)
		{
			CFrame *pFrame = new CFrame( pDib, 0 );
			arrFrames.Add( pFrame );
		}
		delete [] m_readbuffer;
		delete [] extrabuffer;
		delete [] linebuffer;
	} 
	catch (...) 
	{
		if( pDib )
		{
			delete pDib;
			pDib = NULL;
		}
		delete [] m_readbuffer;
		delete [] extrabuffer;
		delete [] linebuffer;
	}

	if( pDib )
		return true;
	return false;
}

bool DecodePCX(CDataSourceABC &ds, CFrameArray &arrFrames, SIZE &size )
{
	PCXFileReader fr;
	return fr.Decode(ds, arrFrames, size);
}

#endif	//	IMGLIB_PCX
