
/*
	
		Convert in-memory face GIF file to BMP format.
		
		based on the NETPBM utility GIFTOPNM:

*/

/* +-------------------------------------------------------------------+ */
/* | Copyright 1990, 1991, 1993, David Koblas.	(koblas@netcom.com)    | */
/* |   Permission to use, copy, modify, and distribute this software   | */
/* |   and its documentation for any purpose and without fee is hereby | */
/* |   granted, provided that the above copyright notice appear in all | */
/* |   copies and that both that copyright notice and this permission  | */
/* |   notice appear in supporting documentation.  This software is    | */
/* |   provided "as is" without express or implied warranty.           | */
/* +-------------------------------------------------------------------+ */


#include "netfone.h"

#define MAXCOLORMAPSIZE		256

#define CM_RED				0
#define CM_GREEN			1
#define CM_BLUE				2

#define MAX_LWZ_BITS		12

#define INTERLACE			0x40
#define LOCALCOLORMAP		0x80
#define BitSet(byte, bit)	(((byte) & (bit)) == (bit))

#define PBM_TYPE			1
#define PGM_TYPE			2
#define PPM_TYPE			3

static LPBYTE gifFile;				// In-memory GIF file pointer
static long gifAddr;				// "Seek address" in GIF file
static LPBYTE bmpFile = NULL;		// In-memory BMP file being constructed

#define ReadOK(file, buffer, len) ((memcpy(buffer, gifFile + gifAddr, len), gifAddr += len), TRUE)

#define LM_to_uint(a, b)	(((b) << 8) | (a))

struct GifScreenStruct {
	   unsigned int Width;
	   unsigned int Height;
	   unsigned char ColorMap[3][MAXCOLORMAPSIZE];
	   unsigned int BitPixel;
	   unsigned int ColorResolution;
	   unsigned int Background;
	   unsigned int AspectRatio;
	   int GrayScale;
};

static struct {
	   int	   transparent;
	   int	   delayTime;
	   int	   inputFlag;
	   int	   disposal;
} Gif89 = { -1, -1, -1, 0 };

/*	GIFtoBMP uses a lot of storage for LZW decoding, colour map
	tables etc.  To avoid busting DGROUP in 16 bit medium memory
	model or going to large model (which would block multiple
	instances running at once), we define these buffers as members
	of a "context" structure which is allocated from the global heap
	for the duration of our processing.  */

static struct context {
	struct GifScreenStruct c_GifScreen;
	unsigned char c_GetCodebuf[280];
	int c_LZWtable[2][(1 << MAX_LWZ_BITS)];
	int c_LZWstack[(1 << (MAX_LWZ_BITS)) * 2];
	unsigned char c_localColorMap[3][MAXCOLORMAPSIZE];
} *GIFcontext = NULL;

//	Now define shortcuts to access items in the context

#define	GifScreen		GIFcontext->c_GifScreen
#define GetCodebuf		GIFcontext->c_GetCodebuf
#define LZWtable		GIFcontext->c_LZWtable
#define LZWstack		GIFcontext->c_LZWstack
#define localColorMap	GIFcontext->c_localColorMap

#define pm_error(x) { fatal = TRUE;		\
					  if (GIFcontext != NULL) { GlobalFreePtr(GIFcontext); GIFcontext = NULL; } \
					  if (bmpFile != NULL) { GlobalFreePtr(bmpFile); bmpFile = NULL; } \
					  return FALSE; }

static int fatal;
static int ZeroDataBlock = FALSE;

//	READCOLORMAP  --  Read colour palette table

static int ReadColorMap(int number, unsigned char buffer[3][MAXCOLORMAPSIZE])
{
	   int i;
	   unsigned char rgb[3];
	   int flag;

	   flag = FALSE;

	   for (i = 0; i < number; ++i) {
			   ReadOK(fd, rgb, sizeof(rgb));

			   buffer[CM_RED][i] = rgb[0];
			   buffer[CM_GREEN][i] = rgb[1];
			   buffer[CM_BLUE][i] = rgb[2];

			   flag &= (rgb[0] == rgb[1] && rgb[1] == rgb[2]);
	   }
	   return TRUE;
}

//	GETDATABLOCK  --  Read a variable-sized data block

static int GetDataBlock(LPBYTE buf)
{
	   unsigned char count;

	   ReadOK(fd, &count, 1);

	   ZeroDataBlock = count == 0;

	   if (count == 0) {
			   return -1;
	   }
	   ReadOK(fd, buf, count);

	   return count;
}

//	GETCODE  --  Read LZW code

static int GetCode(int code_size, int flag)
{
	   static int curbit, lastbit, done, last_byte;
	   int i, j, ret;
	   unsigned char count;

	   if (flag) {
			   curbit = 0;
			   lastbit = 0;
			   done = FALSE;
			   return 0;
	   }

	   if ((curbit + code_size) >= lastbit) {
			   if (done) {
					   return -1;
			   }
			   GetCodebuf[0] = GetCodebuf[last_byte - 2];
			   GetCodebuf[1] = GetCodebuf[last_byte - 1];

			   if ((count = GetDataBlock(&GetCodebuf[2])) == 0)
					   done = TRUE;

			   last_byte = 2 + count;
			   curbit = (curbit - lastbit) + 16;
			   lastbit = (2 + count) * 8 ;
	   }

	   ret = 0;
	   for (i = curbit, j = 0; j < code_size; ++i, ++j)
			   ret |= ((GetCodebuf[ i / 8 ] & (1 << (i % 8))) != 0) << j;

	   curbit += code_size;

	   return ret;
}

//	LWZREADBYTE  --  Read next uncompressed byte from the compressed stream

static int LWZReadByte(int flag, int input_code_size)
{
	   static int fresh = FALSE;
	   int code, incode;
	   static int code_size, set_code_size;
	   static int max_code, max_code_size;
	   static int firstcode, oldcode;
	   static int clear_code, end_code;
	   static int *sp;
	   register int i;

	   if (flag) {
			   set_code_size = input_code_size;
			   code_size = set_code_size + 1;
			   clear_code = 1 << set_code_size ;
			   end_code = clear_code + 1;
			   max_code_size = 2 * clear_code;
			   max_code = clear_code+2;

			   GetCode(0, TRUE);
			   
			   fresh = TRUE;

			   for (i = 0; i < clear_code; ++i) {
					   LZWtable[0][i] = 0;
					   LZWtable[1][i] = i;
			   }
			   for (; i < (1 << MAX_LWZ_BITS); ++i)
					   LZWtable[0][i] = LZWtable[1][0] = 0;

			   sp = LZWstack;

			   return 0;
	   } else if (fresh) {
			   fresh = FALSE;
			   do {
					   firstcode = oldcode =
							   GetCode(code_size, FALSE);
			   } while (firstcode == clear_code);
			   return firstcode;
	   }

	   if (sp > LZWstack)
			   return *--sp;

	   while ((code = GetCode(code_size, FALSE)) >= 0) {
			   if (code == clear_code) {
					   for (i = 0; i < clear_code; ++i) {
							   LZWtable[0][i] = 0;
							   LZWtable[1][i] = i;
					   }
					   for (; i < (1 << MAX_LWZ_BITS); ++i)
							   LZWtable[0][i] = LZWtable[1][i] = 0;
					   code_size = set_code_size + 1;
					   max_code_size = 2 * clear_code;
					   max_code = clear_code + 2;
					   sp = LZWstack;
					   firstcode = oldcode =
									   GetCode(code_size, FALSE);
					   return firstcode;
			   } else if (code == end_code) {
					   int count;
					   unsigned char buf[260];

					   if (ZeroDataBlock)
							   return -2;

					   while ((count = GetDataBlock(buf)) > 0) ;
					   return -2;
			   }

			   incode = code;

			   if (code >= max_code) {
					   *sp++ = firstcode;
					   code = oldcode;
			   }

			   while (code >= clear_code) {
					   *sp++ = LZWtable[1][code];
					   if (code == LZWtable[0][code]) {
                       	   fatal = TRUE;
                       	   return -1;
                       }
					   code = LZWtable[0][code];
			   }

			   *sp++ = firstcode = LZWtable[1][code];

			   if ((code = max_code) <(1 << MAX_LWZ_BITS)) {
					   LZWtable[0][code] = oldcode;
					   LZWtable[1][code] = firstcode;
					   ++max_code;
					   if ((max_code >= max_code_size) &&
							   (max_code_size < (1 << MAX_LWZ_BITS))) {
							   max_code_size *= 2;
							   ++code_size;
					   }
			   }

			   oldcode = incode;

			   if (sp > LZWstack)
					   return *--sp;
	   }
	   return code;
}

//	DOEXTENSION  --  Process extension items

static int DoExtension(int label)
{
	   char buf[256];
#ifdef GIF_DEBUG	   
	   char *str;
#endif	   

	   switch (label) {
	   case 0x01:			   /* Plain Text Extension */
#ifdef GIF_DEBUG	   
               str = "Plain Text Extension";
#endif	   
			   break; 
			   
	   case 0xff:			   /* Application Extension */
#ifdef GIF_DEBUG	   
               str = "Application Extension";
#endif	   
			   break;
			   
	   case 0xfe:			   /* Comment Extension */
#ifdef GIF_DEBUG	   
               str = "Comment Extension";
#endif
         // This line should fix many crashes because John Walker was
         // originally checking for a zero return, which would never
         // occur because if the block is zero length, GetDataBlock()
         // returns -1, never 0.
			   while (GetDataBlock((unsigned char *) buf) != -1) {
			   }
			   return FALSE;
			   
	   case 0xf9:			   /* Graphic Control Extension */
#ifdef GIF_DEBUG	   
               str = "Graphic Control Extension";
#endif	   
			   (void) GetDataBlock((unsigned char *) buf);
			   Gif89.disposal = (buf[0] >> 2) & 0x7;
			   Gif89.inputFlag = (buf[0] >> 1) & 0x1;
			   Gif89.delayTime = LM_to_uint(buf[1],buf[2]);
			   if ((buf[0] & 0x1) != 0)
					   Gif89.transparent = buf[3];

         // This line should fix many crashes because John Walker was
         // originally checking for a zero return, which would never
         // occur because if the block is zero length, GetDataBlock()
         // returns -1, never 0.
			   while (GetDataBlock((unsigned char *) buf) != -1) ;
			   return FALSE;
			   
	   default:
			   break;
	   }

     // This line should fix many crashes because John Walker was
     // originally checking for a zero return, which would never
     // occur because if the block is zero length, GetDataBlock()
     // returns -1, never 0.
	   while (GetDataBlock((unsigned char *) buf) != -1) ;

	   return FALSE;
}

/*	READIMAGE  --  Read all components of image and, if successful,
				   create the in-memory .BMP file.  */

static int ReadImage(int len, int height,
					 int ncolours, unsigned char cmap[3][MAXCOLORMAPSIZE],
					 int interlace, int ignore)
{
	   unsigned char c;	   
	   int v, linelen;
	   int xpos = 0, ypos = 0, pass = 0;
	   DWORD bfsize;
	   BYTE _huge *pixels;
	   LPBITMAPFILEHEADER bfh;
	   LPBITMAPINFO bi;
	   LPBITMAPINFOHEADER bh;

	   //  Initialize the Compression routines
	   
	   ReadOK(fd, &c, 1);

	   if (LWZReadByte(TRUE, c) < 0)
               return FALSE;

       //  If this is an "uninteresting picture" ignore it.

	   if (ignore) {
			   while (LWZReadByte(FALSE, c) >= 0) ;
			   return FALSE;
	   }
	   
	   //  Allocate the in-memory bitmap file.
	   
	   linelen = (len + 3) & (~3);
	   bmpFile = (LPBYTE) GlobalAllocPtr(GPTR, bfsize =
	   				sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
	   				256 * sizeof(RGBQUAD) + (linelen * ((DWORD) height)));
	   bfh = (LPBITMAPFILEHEADER) bmpFile;
	   bi = (LPBITMAPINFO) (bmpFile + sizeof(BITMAPFILEHEADER));
	   bh = &(bi->bmiHeader); 
	   pixels = bmpFile + sizeof(BITMAPFILEHEADER) + sizeof(BITMAPINFOHEADER) +
	   				256 * sizeof(RGBQUAD);
	   				
	   //	Build file header
	   
	   memcpy(&bfh->bfType, "BM", 2);
	   bfh->bfSize = bfsize;
	   bfh->bfOffBits = ((LPBYTE) pixels) - bmpFile;
	   
	   //	Build BITMAPINFOHEADER
	   
	   bh->biSize = sizeof(BITMAPINFOHEADER);
	   bh->biWidth = len;
	   bh->biHeight = height;
	   bh->biPlanes = 1;
	   bh->biBitCount = 8;
	   bh->biCompression = BI_RGB;
	   bh->biSizeImage = 0;
	   bh->biClrUsed = bh->biClrImportant = ncolours;
	   
	   //	Transcribe colour table to the bmiColors list
	   
	   for (v = 0; v < ncolours; v++) {
	       bi->bmiColors[v].rgbRed = cmap[CM_RED][v];
	       bi->bmiColors[v].rgbGreen = cmap[CM_GREEN][v];
	       bi->bmiColors[v].rgbBlue = cmap[CM_BLUE][v];
	   }  

	   while ((v = LWZReadByte(FALSE, c)) >= 0 ) {
	   		   if (ypos > 0 && ypos < height) {
	               pixels[(linelen * ((DWORD) ((height - 1) - ypos))) + xpos] = v;
	           }

			   ++xpos;
			   if (xpos == len) {
					   xpos = 0;
					   if (interlace) {
							   switch (pass) {
							   case 0:
							   case 1:
									   ypos += 8; break;
							   case 2:
									   ypos += 4; break;
							   case 3:
									   ypos += 2; break;
							   }

							   if (ypos >= height) {
									   ++pass;
									   switch (pass) {
									   case 1:
											   ypos = 4; break;
									   case 2:
											   ypos = 2; break;
									   case 3:
											   ypos = 1; break;
									   default:
											   goto fini;
									   }
							   }
					   } else {
							   ++ypos;
					   }
			   }
			   if (ypos >= height)
					   break;
	   }

fini:
		return TRUE;
}

/*	GIFTOBMP  --  Convert an in-memory GIF file to an in-memory
				  .BMP file.  Returns NULL if anything went wrong
				  in the conversion process.  */

LPBYTE GIFtoBMP(LPBYTE gif, int imageNumber)
{
	   unsigned char buf[16];
	   unsigned char c;
	   int useGlobalColormap;
	   int bitPixel;
	   int imageCount = 0;
	   char version[4];
       
       gifFile = gif;
       gifAddr = 0;
       fatal = FALSE;
	   ReadOK(fd, buf, 6);

       if (strncmp((char *) buf,"GIF",3) != 0)
               pm_error("not a GIF file");

	   strncpy(version, (char *) buf + 3, 3);
       version[3] = '\0';

       if ((strcmp(version, "87a") != 0) && (strcmp(version, "89a") != 0))
               pm_error("bad version number, not '87a' or '89a'");
               
       GIFcontext = (struct context *) GlobalAllocPtr(GPTR, sizeof(struct context));
       if (GIFcontext == NULL) {
           return NULL;
       }

	   ReadOK(fd,buf,7);

	   GifScreen.Width = LM_to_uint(buf[0], buf[1]);
	   GifScreen.Height = LM_to_uint(buf[2], buf[3]);
	   GifScreen.BitPixel = 2 << (buf[4] & 0x07);
	   GifScreen.ColorResolution = (((buf[4] & 0x70) >> 3) + 1);
	   GifScreen.Background = buf[5];
	   GifScreen.AspectRatio = buf[6];

	   if (BitSet(buf[4], LOCALCOLORMAP)) {    /* Global Colormap */
			   if (!ReadColorMap(GifScreen.BitPixel, GifScreen.ColorMap))
                       pm_error("error reading global colormap");
	   }

	   for (;;) {
			   ReadOK(fd, &c, 1);

               if (c == ';') {         /* GIF terminator */
					   if (imageCount < imageNumber)
                               pm_error("Too few images found in file");
                       if (GIFcontext != NULL) {
                       	   GlobalFreePtr(GIFcontext);
                       	   GIFcontext = NULL;
                       }
					   return bmpFile;
			   }

               if (c == '!') {         /* Extension */
					   ReadOK(fd, &c, 1);
					   DoExtension(c);
					   continue;
			   }

               if (c != ',') {         /* Not a valid start character */
					   continue;
			   }

			   ++imageCount;

			   ReadOK(fd, buf, 9);

			   useGlobalColormap = ! BitSet(buf[8], LOCALCOLORMAP);

			   bitPixel = 1 << ((buf[8] & 0x07) + 1);

			   if (!useGlobalColormap) {
					   if (!ReadColorMap(bitPixel, localColorMap))
                               pm_error("error reading local colormap");
					   ReadImage(LM_to_uint(buf[4], buf[5]),
								 LM_to_uint(buf[6], buf[7]), 
								 bitPixel, localColorMap,
								 BitSet(buf[8], INTERLACE), imageCount != imageNumber);
			   } else {
					   ReadImage(LM_to_uint(buf[4], buf[5]),
								 LM_to_uint(buf[6], buf[7]), 
								 GifScreen.BitPixel, GifScreen.ColorMap,
								 BitSet(buf[8], INTERLACE), imageCount != imageNumber);
			   }

	   }
}
