// Tabs:3 Indent:3

/*
 * GifToTiff.m, GIF to TIFF converter.
 * Author: Michael McCulloch
 * Written for 3.1, June 14, 1993.
 *
 * You may freely copy, distribute and reuse the code in this example.
 * Michael McCulloch disclaims any warranty of any kind, expressed or 
 * implied, as to its fitness for any particular use.
 */

#import <appkit/appkit.h>
#import <libc.h>
#import <string.h>
#import <stdio.h>

long int	imagePos;			// image position in stream
long int	imageNextPos;		// position of end of image
int	loadError;				// loading error flag

u_int	pixWide;					// pixel width
u_int	pixHigh;					// pixel height
u_long	rasterSize;			// width * height
u_int	rasterTop;				// top raster row
float	aspectRatio;			// aspect ratio

BOOL	isGlobalMap;			// Is there a global color map?
int	colorMapCount;			// Number of global colors
u_char	colorMap[256][3];	// RGB values for global color map
u_char	gamtab[256];		// gamma correction table

u_char	*imageRaster;		// raster buffer

#define	IMAX		((1<<8)-1)	/* max intensity value */

// error codes & descriptions
#define	errNONE			0
#define	errTABLE_OVERFLOW	1
#define	errRASTER_SIZE		2
#define	errNO_COLORMAP		3
#define	errINVALID_FILE		4
#define	errBAD_BLOCK		5
#define	errCANT_LOAD		6
#define	errEND_OF_FILE		7
#define	errNO_IMAGE		8

static char *errDesc[] = {
	"none",
	"code table overflow",
	"raster has the wrong size",
	"no colormap present for image",
	"invalid GIF file",
	"illegal GIF block type",
	"unable to load file (unknown reason)",
	"reached premature end-of-file"
	"no image found in file",
	0
};

const char *errorDesc(int x) 
{ 
	return errDesc[x];
}

static unsigned char getByte (NXStream *stream)
{
	return (unsigned char)NXGetc(stream);    
}

static void getBytes (NXStream *stream, u_char *buf, int count)
{
	NXRead(stream, buf, count);    
}


/* check file type */
static BOOL validImageType(NXStream *stream)
{
	char buf[16];
	getBytes(stream, buf, 6);
	return strncasecmp(buf,"GIF87a", 6) && strncasecmp(buf,"GIF89a", 6)? NO : YES;
}


static int readRaster(NXStream *imageStream)
{
	u_char *ch, buf[260], *fill;
	u_int	count, datm;
	int i, err, code, bits, thisCode;
	int codeTable[4096][2];	// LZW compression code data
	int stack[4096 * 2], *sp;
	int _datasize, _codesize, _maxsize;
	int _clear, _avail, _oldcode, _firstcode;

	/* initialize */
	err = errNONE;
	fill = imageRaster;
	_datasize = getByte(imageStream);
	_clear = 1 << _datasize;
	_codesize = _datasize + 1;
	_avail = _clear + 2;
	_maxsize = _clear * 2;
	_oldcode = -1;
	_firstcode = -1;
	bits = 0;
	datm = 0;
	sp = stack;

	/* prefill table */
	for(i = 0; i < 4096; i++){
		codeTable[i][0] = 0;
		codeTable[i][1] = (i < _clear)? i : 0;
	}

	/* decode raster */
	for(count = getByte(imageStream); count > 0; count = getByte(imageStream)){
  
		/* read block */
		getBytes(imageStream, buf, count);
  	
		/* process block */
		for(ch = buf; count-- > 0; ch++){
			datm += (u_int)(*ch) << bits;
			bits += 8;
			while(bits >= _codesize){
	  
				/* get code */
				code = datm & ((1 << _codesize) - 1);
				datm >>= _codesize;
				bits -= _codesize;

				/* clear table */
				if(code == _clear){
					_codesize = _datasize + 1;
					_maxsize = _clear * 2;
					_avail = _clear + 2;
					_oldcode = -1;
					_firstcode = -1;
					sp = stack;
					for(i = 0; i < 4096; i++){
						codeTable[i][0] = 0;
						codeTable[i][1] = (i < _clear)? i : 0;
					}
					continue;
				}	/* end of decompression */
	
				if(code == _clear + 1) goto EXITLOOP;  /* for non-standard GIF files */

				/* save first code */
				if(_firstcode == -1){
					_firstcode = _oldcode = code;
					*fill++ = code;
					continue;
				}

				/* save this code */
				thisCode = code;

				/* code check */
				if(code >= _avail){
					*sp++ = _firstcode;
					code = _oldcode;
				}

				/* place codes on stack */
				while(code >= _clear){
					*sp++ = codeTable[code][1];
					if(code == codeTable[code][0]){
						err = errTABLE_OVERFLOW;
						goto EXITLOOP;
					}
					code = codeTable[code][0];
				}
				*sp++ = _firstcode = codeTable[code][1];

				/* save in table */
				code = _avail;
				if(code < 4096){
					codeTable[code][0] = _oldcode;
					codeTable[code][1] = _firstcode;
					_avail++;
					if((_avail >= _maxsize) && (_maxsize < 4096)){
						_maxsize *= 2;
						_codesize++;
					}
				}

				/* save old code */
				_oldcode = thisCode;
		
				/* flush stack */
				while(sp > stack) *fill++ = *--sp;	
			}
		}
	}
  
	EXITLOOP:
	if(err == errEND_OF_FILE){
		err = errNONE;
		NXLogError("GIF premature end of file");
	}

	if(!err && (fill != imageRaster + rasterSize)) err = errRASTER_SIZE;
	return err;
}


static int loadImage(NXStream *imageStream)
{
	u_char buf[10];
	BOOL local, interleaved;
	int err, left;

	/* set file position */
	if(imagePos) 
		NXSeek(imageStream, imagePos, NX_FROMSTART);
	else 
		imagePos = NXTell(imageStream);
  
	/* read image information */
	getBytes(imageStream, buf, 9);
	left = buf[0] + (buf[1] << 8);
	rasterTop = buf[2] + (buf[3] << 8);
	pixWide = buf[4] + (buf[5] << 8);
	pixHigh = buf[6] + (buf[7] << 8);
	local = (buf[8] & 0x80)? YES : NO;
	interleaved = (buf[8] & 0x40)? YES : NO;
	rasterSize = (u_long)pixWide * (u_long)pixHigh;

	/* convert color map */
	if(local){
		colorMapCount = 1 << ((buf[8] & 0x7) + 1);
		getBytes(imageStream, &colorMap[0][0], 3*colorMapCount);
	}
	else{
		if(!isGlobalMap) return errNO_COLORMAP;
	}

  /* read image raster */
	if(!imageRaster) 
		memset((imageRaster=(u_char*)malloc(rasterSize)), 0, rasterSize);
  
	if((err = readRaster(imageStream)) && (err != errRASTER_SIZE)) 
		return err;

	/* remove any interleave */
	if(interleaved){
		int	i, r=0;
		u_char *temp = (u_char*)malloc(rasterSize);
		u_short *tbl = (u_short*)malloc(pixHigh * sizeof(u_short));
		for(i = rasterTop; i < (int)(rasterTop + pixHigh); i+=8) 
			tbl[i] = r++;
		for(i = rasterTop + 4; i < (int)(rasterTop + pixHigh); i+=8) 
			tbl[i] = r++;
		for(i = rasterTop + 2; i < (int)(rasterTop + pixHigh); i+=4)
			tbl[i] = r++;
		for(i = rasterTop + 1; i < (int)(rasterTop + pixHigh); i += 2) 
			tbl[i] = r++;
		for(i = 0, r = rasterTop; r < (int)(rasterTop + pixHigh); i++, r++)
			memcpy(&temp[i * pixWide], &imageRaster[tbl[r] * pixWide], pixWide);
		free(tbl);
		free(imageRaster);
		imageRaster = temp;
		rasterTop = 0;
	}

	/* return successful */
	return errNONE;
}


/* read GIF header info */
static int readGIFHeader(NXStream *imageStream, BOOL showMessage, const char *imageFile)
{
	u_int screenWidth,screenHeight;	// screen dimensions
	u_int background;
	u_char buf[256];

	/* rewind file pointer */
	NXSeek(imageStream, 0, NX_FROMSTART);
 
	/* check gif signiture type */
	if(!validImageType(imageStream))
		return errINVALID_FILE;

	/* read global information */
	getBytes(imageStream, buf, 7);
	screenWidth = buf[0] + (buf[1] << 8);
	screenHeight = buf[2] + (buf[3] << 8);
	isGlobalMap = (buf[4] & 0x80)? YES : NO;	// global color map?
	background = buf[5];			// not used
	aspectRatio = (buf[6])? ((float)buf[6] + 15.0) / 64.0 : 1.0;	// aspect ratio

	if(showMessage)
		fprintf(stderr, "McFilter:GIFFilter converting %s: %d x %d\n", (strrchr(imageFile,'/')+1), screenWidth, screenHeight);

	/* load global colormap */
	if(isGlobalMap){
		colorMapCount = 1 << ((buf[4] & 0x07) + 1);
		getBytes(imageStream, &colorMap[0][0], 3*colorMapCount);
	}

	return errNONE;
}


/* initialize from stream */
static int readImage(NXStream *imageStream, BOOL showMessage, const char *imageFile)
{
	int err;

	/* read gif header info */
	if(err = readGIFHeader(imageStream, showMessage, imageFile))
		return err;

//	/* set file position override */
//	if(overridePos) 
//	NXSeek(imageStream, 0, NX_FROMSTART);
  
	/* loop through file */
	for(;;){
		int err;
		u_char buf[256];
		u_char ch = getByte(imageStream);
		switch(ch){
			case ',' :
				if (err = loadImage(imageStream)) 
				return err;
				imageNextPos = NXTell(imageStream);
				return errNONE;
			case '!' :	// skip extension
				for(ch=getByte(imageStream); ch=getByte(imageStream);)
				getBytes(imageStream, buf, ch);
				break;
			case '\0':
				break;	/* non-standard files */
			case ';' :
				return imageNextPos? errNONE: errNO_IMAGE;
			default  :
				return errBAD_BLOCK;
		}
	}
  
	/* return any error */
	return errNONE;
}


/* init from file stream */
NXBitmapImageRep *convertGIFToTIFF(NXStream *imageStream,float gammaFactor, BOOL showMessage, const char *imageFile)
{
	NXSize pSize;
	NXBitmapImageRep *tiff = nil;

	/* image data storage */
	int i, j, r, c, bpr;
	u_char *p;
	u_char *bitmap[5] = { 0, 0, 0, 0, 0 };

	/* local var init */
	imageRaster = (u_char*)nil;
	pixWide = 0;
	pixHigh = 0;
	rasterSize = 0L;
	isGlobalMap = NO;
	colorMapCount	= 0;
	imagePos = 0L;
	imageNextPos = 0L;
//	overridePos = 0L;

	/* initialize */
	loadError = readImage(imageStream,showMessage,imageFile);
	if (loadError == errNO_IMAGE)
		return (id)nil;
	if(!loadError && (!pixWide || !pixHigh)) 
		loadError = errCANT_LOAD;
	if(loadError){
		NXLogError("GIF stream error: %s", errDesc[loadError]);
		return (id)nil;
	}

	/* init size (check aspect ratio) */
	pSize.width  = (float)pixWide;
	pSize.height = (float)pixHigh;
	if (aspectRatio < 1.0) pSize.width  *= 1.0 / aspectRatio; else
	if (aspectRatio > 1.0) pSize.height *= aspectRatio;

	/* create gamma table */
	for(i=0; i<256; i++)
		gamtab[i] = (IMAX*pow(i/255.0,gammaFactor)+0.5);
    
	/* modify colorMap */
	for(i=0; i<256; i++)
		for(j=0; j<3; j++)
			colorMap[i][j] = gamtab[(int)colorMap[i][j]];

	/* build image bitmap */
	bitmap[0] = p = (u_char*)malloc(3L * rasterSize);
	for(r = rasterTop; r < (int)(rasterTop + pixHigh); r++){
		u_char *s = &imageRaster[r * pixWide];
		for (c = 0; c < (int)pixWide; c++, p+=3)
			memcpy(p, colorMap[s[c]], 3);
	}

  /* render bitmap */
	bpr = (7 + pixWide * 8 * 3) / 8;
	tiff = [[NXBitmapImageRep alloc] initDataPlanes:bitmap
			pixelsWide:pixWide
			pixelsHigh:pixHigh
			bitsPerSample:8
			samplesPerPixel:3
			hasAlpha:NO
			isPlanar:NO
			colorSpace:NX_RGBColorSpace
			bytesPerRow:bpr
			bitsPerPixel:(8*3)];

	/* init bitmap image rep */
	[tiff setSize:&pSize];
	free(imageRaster);

	return tiff;
}
