/*  
** Copyright (c) 1995 Netsurfer Inc.  All Rights Reserved.
**
** Author: <bbum@friday.com>
*/

/*  This object is included in the MiscKit by permission from the author
**  and its use is governed by the MiscKit license, found in the file
**  "LICENSE.rtf" in the MiscKit distribution.  Please refer to that file
**  for a list of all applicable permissions and restrictions.
*/

#import <appkit/appkit.h>

#import "SurfGIFSupport.h"
#import "SurfGIFDecoder.Internal.h"

#ifdef VERBOSE
#undef VERBOSE
#undef METHOD
#undef METHODnl
#endif

#define VERBOSE 		if(_SDFlags.verboseMode)
#define METHOD  		fprintf(stderr, "[%s %s%s] ", \
                                object_getClassName(self), \
                 		        (self == [self class]) ? "+" : "-", \
				                SELNAME(_cmd))
#define METHODnl  		fprintf(stderr, "[%s %s%s]\n", \
                                object_getClassName(self), \
                 		        (self == [self class]) ? "+" : "-", \
				                SELNAME(_cmd))

extern unsigned short read_short(unsigned char *buf)
    /* This radically simple function was written instead of a
       #define mostly for debugging purposes.  It will be optmized
       away....
     */
{
	unsigned short upper = buf[1] * 256;
	unsigned short lower = buf[0];

	return (upper + lower);
}

@implementation SurfGIFDecoder (Internal)
/*"
 * Implements the custom GIF decoding engine.  Upon entry into
 * #{-_decodeFromStream:}, the decoder progressively decodes each part of
 * the image stream by calling the various decoding methods implemented by
 * this category.
 * 
 * The #{UnsupportedBlocks} category defines stub methods that simply skip
 * unsupported GIF blocks without producing an error message.
 * 
 * The code is heavily documented.
 * 
 * The following is the copyright message associated with the LWZ decoder
 * engine that was recycled out of the netpbm package of tools.
 * 
 * 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.
"*/

- _decodeFromStream:(NXStream *) stream
{
// <GIF Data Stream> ::=     Header <Logical Screen> <Data>* Trailer
// <Logical Screen>  ::=     Logical Screen Descriptor [Global Color Table]

//	Header
	NXSeek(stream, 3, NX_FROMSTART);  // dump 'GIF'

	// read version information.
	if(NXRead(stream, version, 3) != 3)  {
		VERBOSE {
			METHOD;
			fprintf(stderr, "Failed to read version information\n");
		}
		goto CleanupAndReturnNIL;
	} else VERBOSE {
		METHOD;
		fprintf(stderr, "Loading GIF version '%s'\n", version);
	}

// <Logical Screen Descriptor>
    if(![self _decodeLogicalScreenDescriptor: stream])
		goto CleanupAndReturnNIL;	

// [Global Color Table] -- optional
	if(_SGIFDFlags.gctPresent && ![self _decodeGCT:stream])
		goto CleanupAndReturnNIL;

// <Data>
	if(! [self _decodeData:stream])
		goto CleanupAndReturnNIL;

    /* The decoder is reset at the end because it could potentially
       free up memory [and the rest of the reset is simply scalar
       assignments, so the performance hit is minimal].
     */
	[self _resetDecoder];

    /* since resetDecoder assigns nil to returnImage, return
       imageCache-- which was set = to returnImage at the beginning
       of this method.
     */
	return returnImage;
	
CleanupAndReturnNIL:
	[self _resetDecoder];

	return nil;
}

- _decodeLogicalScreenDescriptor:(NXStream *) stream
/*    18. Logical Screen Descriptor.
 *
 *      a. Description.  The Logical Screen Descriptor contains the parameters
 *      necessary to define the area of the display device within which the
 *      images will be rendered.  The coordinates in this block are given with
 *      respect to the top-left corner of the virtual screen; they do not
 *      necessarily refer to absolute coordinates on the display device.  This
 *      implies that they could refer to window coordinates in a window-based
 *      environment or printer coordinates when a printer is used.
 *
 *      This block is REQUIRED; exactly one Logical Screen Descriptor must be
 *      present per Data Stream.
 *
 *      b. Required Version.  Not applicable. This block is not subject to a
 *      version number. This block must appear immediately after the Header.
 *
 *      c. Syntax.
 *
 *      7 6 5 4 3 2 1 0        Field Name                    Type
 *     +---------------+
 *  0  |               |       Logical Screen Width          Unsigned
 *     +-             -+
 *  1  |               |
 *     +---------------+
 *  2  |               |       Logical Screen Height         Unsigned
 *     +-             -+
 *  3  |               |
 *     +---------------+
 *  4  | |     | |     |       <Packed Fields>               See below
 *     +---------------+
 *  5  |               |       Background Color Index        Byte
 *     +---------------+
 *  6  |               |       Pixel Aspect Ratio            Byte
 *     +---------------+
 *
 *     <Packed Fields>  =      Global Color Table Flag       1 Bit
 *                             Color Resolution              3 Bits
 *                             Sort Flag                     1 Bit
 *                             Size of Global Color Table    3 Bits
 */
{
	unsigned char lsdBuf[7];

	VERBOSE {
		METHOD;
		fprintf(stderr, "Decoding <Logical Screen Descriptor>\n");
	}
	
	if(NXRead(stream, lsdBuf, 7) != 7) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Failed to read 7 byte <Logical Screen Descriptor>.\n");
		}
		return nil;
	}

    /* grab width
     */
	lsdWidth  = read_short(lsdBuf);

    /* grab height
     */
	lsdHeight = read_short(lsdBuf + 2);

    /* top bit indicates presence of global color table -- the ?:
       logic should not be necessary, but apparently is-- at least,
       it is in debug mode.
     */
	_SGIFDFlags.gctPresent = (lsdBuf[4] & 0x80) ? YES : NO;

    /* Next three bits contain the number of bits per primary color
       available to the original image, minus 1.  
     */
	lsdColorResolution = ((lsdBuf[4] & 0x70) >> 4) + 1;

	if(_SGIFDFlags.gctPresent) {
        /* Sort order only matters if global color table is present
         */
		_SGIFDFlags.gctSorted = lsdBuf[4] & 0x8;
		lsdBackgroundIndex	  = lsdBuf[5];
	}

    /* Regardless of whether or not there is a global color table,
       this should be set anyway.
     */
	lsdGlobalColorTableSize = 2 << (lsdBuf[4] & 0x7);

    /* Aspect ratio is ignored.
     */

	VERBOSE {
		METHOD;
		fprintf(stderr, "Image size- %d x %d\n", lsdWidth, lsdHeight);
		METHOD;
		fprintf(stderr, "Has GCT: %s; Size: %d entries\n",
				BOOL2STR(_SGIFDFlags.gctPresent),
				lsdGlobalColorTableSize);
		METHOD;
		fprintf(stderr, "Entry resolution: %d bits",
				lsdColorResolution);
		if(_SGIFDFlags.gctPresent){
			fprintf(stderr,
					"; Sorted:%s\n",
					BOOL2STR(_SGIFDFlags.gctSorted));
			METHOD;
			fprintf(stderr,
					"Background Color Index: %d\n",
					lsdBackgroundIndex);
		} else
			fprintf(stderr, "\n");
	}
	
	return self;
}

- (unsigned char *) _decodeColorTable:(NXStream *)stream
                           entryCount:(unsigned) tableSize
                           intoBuffer:(unsigned char *)buf
{
	unsigned index;
	unsigned tableBytes;
	BOOL localMallocFlag = NO;

	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Reading Color Table %d entries\n",
				tableSize);
	}

	if(imageDepth == NX_TwelveBitRGBDepth) {
		SurfColorEntry16 *colorTable;
		tableBytes = tableSize * sizeof(SurfColorEntry16);
		if(!buf) {
			localMallocFlag = YES;
			buf = (unsigned char *) NXZoneMalloc([self zone], tableBytes);
		}
		colorTable = (SurfColorEntry16 *) buf;
		
		for(index = 0; (index < tableSize) && !NXAtEOS(stream); index++) {
            /* take 4 most significant bits from each entry.
             */
			colorTable[index].red	   = NXGetc(stream) >> 4;
			colorTable[index].green	   = NXGetc(stream) >> 4;
			colorTable[index].blue	   = NXGetc(stream) >> 4;
			colorTable[index].alpha	   = 0xf;

		}
	} else {
		SurfColorEntry32 *colorTable;
		tableBytes = tableSize * sizeof(SurfColorEntry32);
		if(!buf) {
			localMallocFlag = YES;
			buf = (unsigned char *) NXZoneMalloc([self zone], tableBytes);
		}
		colorTable = (SurfColorEntry32 *) buf;
		
		for(index = 0; (index < tableSize) && !NXAtEOS(stream); index++) {
			colorTable[index].red	   = NXGetc(stream);
			colorTable[index].green	   = NXGetc(stream);
			colorTable[index].blue	   = NXGetc(stream);
			colorTable[index].alpha	   = 255;
		}
	}

		
    if(NXAtEOS(stream)) {
        VERBOSE {
            METHOD;
            fprintf(stderr, "Encountered EOS in middle of color table\n");
        }
        if(localMallocFlag)
            NX_FREE(buf);
        return NULL;
    }

	if(_SGIFDFlags.showColorTable) {
		METHOD;
		fprintf(stderr, "Color Table with %d Entries:\n", tableSize);
		if(imageDepth == NX_TwelveBitRGBDepth) {
			SurfColorEntry16 *colorTable = (SurfColorEntry16 *) buf;
			for(index=0; (index < tableSize); index++) {
				METHOD;
				fprintf(stderr,
						"16bpp: r:%02x g:%02x b:%02x a:%02x\n",
						colorTable[index].red,
						colorTable[index].green,
						colorTable[index].blue,
						colorTable[index].alpha);
			}
		} else {
			SurfColorEntry32 *colorTable = (SurfColorEntry32 *) buf;
			for(index=0; (index < tableSize); index++) {
				METHOD;
				fprintf(stderr,
						"32bpp: r:%02x g:%02x b:%02x a:%02x\n",
						colorTable[index].red,
						colorTable[index].green,
						colorTable[index].blue,
						colorTable[index].alpha);
			}
		}
	}

	return buf;
}

- _decodeGCT:(NXStream *) stream
/*19. Global Color Table.
 *
 *    a. Description. This block contains a color table, which is a sequence of
 *     bytes representing red-green-blue color triplets. The Global Color Table
 *     is used by images without a Local Color Table and by Plain Text
 *     Extensions. Its presence is marked by the Global Color Table Flag being
 *     set to 1 in the Logical Screen Descriptor; if present, it immediately
 *     follows the Logical Screen Descriptor and contains a number of bytes
 *     equal to
 *                   3 x 2^(Size of Global Color Table+1).
 *
 *     This block is OPTIONAL; at most one Global Color Table may be present
 *     per Data Stream.
 *
 *     b. Required Version.  87a
 *
 *     c. Syntax.
 *
 *      7 6 5 4 3 2 1 0        Field Name                    Type
 *     +===============+
 *  0  |               |       Red 0                         Byte
 *     +-             -+
 *  1  |               |       Green 0                       Byte
 *     +-             -+
 *  2  |               |       Blue 0                        Byte
 *     +-             -+
 *  3  |               |       Red 1                         Byte
 *     +-             -+
 *     |               |       Green 1                       Byte
 *     +-             -+
 * up  |               |
 *     +-   . . . .   -+       ...
 * to  |               |
 *     +-             -+
 *     |               |       Green 255                     Byte
 *     +-             -+
 *767  |               |       Blue 255                      Byte
 *     +===============+
 */
{
	unsigned gctByteSize = lsdGlobalColorTableSize * 4;

	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Reading Global Color Table with %d entries\n",
				lsdGlobalColorTableSize);
	}

	if(![self _decodeColorTable:stream
			  entryCount:lsdGlobalColorTableSize
			  intoBuffer:globalColorTable])
		return nil;

    /* if the global color table doesn't fill up all of globalColorTable
       instance variable [a large unsigned char array] and zero
       unused colors is turned on, then zero from end of newly loaded
       gct to end of globalColorTable array.
     */
	if(_SGIFDFlags.gctZeroUnused && (gctByteSize < GCT_MAX_BYTES))
		bzero(&(globalColorTable[gctByteSize]), GCT_MAX_BYTES - gctByteSize);

	return self;
}

- _decodeGraphicControlExtension:(NXStream *) stream
/*23. Graphic Control Extension.
 *
 *    a. Description. The Graphic Control Extension contains parameters used
 *    when processing a graphic rendering block. The scope of this extension is
 *    the first graphic rendering block to follow. The extension contains only
 *    one data sub-block.
 *
 *    This block is OPTIONAL; at most one Graphic Control Extension may precede
 *    a graphic rendering block. This is the only limit to the number of
 *    Graphic Control Extensions that may be contained in a Data Stream.
 *
 *     b. Required Version.  89a.
 *
 *     c. Syntax.
 *
 *      7 6 5 4 3 2 1 0        Field Name                    Type
 *     +---------------+
 *  0  |               |       Extension Introducer          Byte
 *     +---------------+
 *  1  |               |       Graphic Control Label         Byte
 *     +---------------+
 *
 *     +---------------+
 *  0  |               |       Block Size                    Byte
 *     +---------------+
 *  1  |     |     | | |       <Packed Fields>               See below
 *     +---------------+
 *  2  |               |       Delay Time                    Unsigned
 *     +-             -+
 *  3  |               |
 *     +---------------+
 *  4  |               |       Transparent Color Index       Byte
 *     +---------------+
 *
 *     +---------------+
 *  0  |               |       Block Terminator              Byte
 *     +---------------+
 *
 *
 *      <Packed Fields>  =     Reserved                      3 Bits
 *                             Disposal Method               3 Bits
 *                             User Input Flag               1 Bit
 *                             Transparent Color Flag        1 Bit
 */

    /* Upon entry, the Extension Introducer and Graphic Control
       Label have already been eaten by the nested switches in
       -_decodeData:

	   The Block Size field is, by definition, a fixed constant of 4
	   bytes.  That is, 4 bytes AFTER the Block Size field BUT NOT
	   INCLUDING the block terminator.  Because of this, dataBuf is
	   set to 6 bytes and 6 bytes are read from the stream.  The Block
	   Size field is ignored, but the Block Terminator is not-- if the
	   Block Terminator does not contain 0x00, then something is
	   drastically wrong.

	   The User Input Flag, Disposal Method, and Delay Time attributes
	   are all ignored.  Eventually, a <graphic control extension>
	   should appropriately configure the SurfGIFImageRep that will
	   encapsulate the immediately following Image Descriptor.
     */
{
	
	unsigned char dataBuf[6];

	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Decoding <Graphic Control Extension>\n");
	}

	if(NXRead(stream, dataBuf, 6) != 6) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Could not read 6 bytes of data from stream\n");
		}
		return nil;
	}

	if (dataBuf[5] != 0x00) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Expected Block Terminator (0x00), but found %02x\n",
					dataBuf[5]);
		}
		return nil;
	}
	
    /* configure according to values found
     */
	_SGIFDFlags.gcePresent		   = YES;
	_SGIFDFlags.gceUseTransparency = (dataBuf[1] & 0x01) ? YES : NO;
	gceTransparentColorIndex	   = dataBuf[4];

	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Transparency enabled: %s",
				BOOL2STR(_SGIFDFlags.gceUseTransparency));
		if(_SGIFDFlags.gceUseTransparency)
			fprintf(stderr, "; Index: %d\n",
					gceTransparentColorIndex);
		else
			fprintf(stderr, "\n");
	}
	
	return self;
}

- _decodeImageData:(NXStream *)stream
/*22. Table Based Image Data.
 *
 *   a. Description. The image data for a table based image consists of a
 *   sequence of sub-blocks, of size at most 255 bytes each, containing an
 *   index into the active color table, for each pixel in the image.  Pixel
 *   indices are in order of left to right and from top to bottom.  Each index
 *   must be within the range of the size of the active color table, starting
 *   at 0. The sequence of indices is encoded using the LWZ Algorithm with
 *   variable-length code, as described in Appendix F
 *
 *   b. Required Version.  87a.
 *
 *   c. Syntax. The image data format is as follows:
 *
 *    7 6 5 4 3 2 1 0        Field Name                    Type
 *   +---------------+
 *   |               |       LWZ Minimum Code Size         Byte
 *   +---------------+
 *
 *   +===============+
 *   |               |
 *   /               /       Image Data                    Data Sub-blocks
 *   |               |
 *   +===============+
 *
 *          i) LWZ Minimum Code Size.  This byte determines the initial number
 *          of bits used for LWZ codes in the image data, as described in
 *          Appendix F.
 *
 *     d. Extensions and Scope. This block has no scope, it contains raster
 *     data. Extensions intended to modify a Table-based image must appear
 *     before the corresponding Image Descriptor.
 *
 *     e. Recommendations. None.
*/
{
	unsigned char lwzCodeSize;

	lwzCodeSize = NXGetc(stream);

	if(!lwzCodeSize) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Decoding <Image Data> FAILURE-- minimum code size 0\n");
		}
		return nil;
	} else VERBOSE {
		METHOD;
		fprintf(stderr,
				"Decoding <Image Data> [LWZ Minimum Code Size: %d]\n",
				lwzCodeSize);
	}

    /* initialize decompressor
     */
	bzero(decompressionState, sizeof(*decompressionState));
	LWZInitState(self, decompressionState, lwzCodeSize);

	if(imageDepth == NX_TwelveBitRGBDepth) {
		// decode into 16 bit image buffer
		return [self _decompressImage16:stream];
	} else {
		// decode into 32 bit image buffer
		return [self _decompressImage32:stream];
	}
}

- _decompressImage32:(NXStream *) stream
{
	NXBitmapImageRep *imageRep;
	int pixelValue;
	unsigned int xpos, ypos, pass;
	unsigned int pixelIndex;
	SurfColorEntry32 *colorData;
	SurfColorEntry32 *colorTable;

	VERBOSE {
		METHODnl;
	}

    /* allocate space for image-- the following is for a 24 bit
       image w/four channels;  red, green, blue, and alpha-- as a
       possible optimization, it may be wise to allocate the image
       buffer from a newly created zone, then destroy that zone
       afterwords.  Considering the current implementation of NXZone,
       it probably won't buy us a thing.
     */
	imageRep = [[NXBitmapImageRep allocFromZone:[self zone]]
					initDataPlanes:NULL // instance will allocate
					pixelsWide:idWidth
					pixelsHigh:idHeight
					bitsPerSample:8 
					samplesPerPixel:4
					hasAlpha:YES
					isPlanar:NO
					colorSpace:NX_RGBColorSpace
					bytesPerRow:0 // instance will figure it out
					bitsPerPixel:0];

	if(!imageRep) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Failed to allocate imageRep.\n");
		}
		return nil;
	}

	colorData  = (SurfColorEntry32 *) [imageRep data];
	colorTable = (SurfColorEntry32 *) localColorTable;

    /* set up transparency
     */
	if(_SGIFDFlags.gceUseTransparency)
		colorTable[gceTransparentColorIndex].alpha = 0x00;
		
	xpos = 0;
	ypos = 0;
	pass = 0;
	while( (pixelValue = LWZReadByte(self,
									 decompressionState,
									 stream)) >= 0 ) {

		pixelIndex = xpos + (ypos * idWidth);
		
		if(_SGIFDFlags.produceSilhouette) {
			colorData[pixelIndex].red	= 
			colorData[pixelIndex].green	= 
			colorData[pixelIndex].blue	= 255;

			colorData[pixelIndex].alpha	= 
			(_SGIFDFlags.gceUseTransparency &&
			 (pixelValue == gceTransparentColorIndex)) ? 0 : 255;
		} else {
			colorData[pixelIndex] = colorTable[pixelValue];
		}
		
		++xpos;
		if (xpos == idWidth) {
			xpos = 0;
			if (_SGIFDFlags.idInterlaced) {
				switch (pass) {
				case 0:
				case 1:
					ypos += 8; break;
				case 2:
					ypos += 4; break;
				case 3:
					ypos += 2; break;
				}

				if (ypos >= idHeight) {
					++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 >= idHeight)
			break;
	}

	if(pixelValue < LWZ_DONE) {
		VERBOSE {
			METHOD;
			fprintf(stderr, "LWZReadByte() failed with %d\n", pixelValue);
		}
		_SDFlags.lastCorrupt = YES;
	}
	
fini:
    /* If everything went correctly above, the next call to LWZReadByte
       should return LWZ_DONE.

	   pixelValue is being recycled....
     */
    if ( (pixelValue = LWZReadByte(self, decompressionState, stream)) != 0) {
		VERBOSE{
            METHOD;
            fprintf(stderr, "Image may be corrupt (last return: %02d\n",
					pixelValue);
		}
		_SDFlags.lastCorrupt = YES;		
    }

    /* undo transparency
     */
	if(_SGIFDFlags.gceUseTransparency)
		colorTable[gceTransparentColorIndex].alpha = 0xff;

	[returnImage useRepresentation:imageRep];

	return self;
}

- _decompressImage16:(NXStream *) stream
{
	NXBitmapImageRep *imageRep;
	int pixelValue;
	unsigned int xpos, ypos, pass;
	unsigned int pixelIndex;
	SurfColorEntry16 *colorData;
	SurfColorEntry16 *colorTable;

	VERBOSE {
		METHODnl;
	}
	imageRep = [[NXBitmapImageRep allocFromZone:[self zone]]
					initDataPlanes:NULL // instance will allocate
					pixelsWide:idWidth
					pixelsHigh:idHeight
					bitsPerSample:4 
					samplesPerPixel:4
					hasAlpha:YES
					isPlanar:NO
					colorSpace:NX_RGBColorSpace
					bytesPerRow:0 // instance will figure it out
					bitsPerPixel:0];

	if(!imageRep) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Failed to allocate imageRep.\n");
		}
		return nil;
	}

	colorData  = (SurfColorEntry16 *) [imageRep data];
	colorTable = (SurfColorEntry16 *) localColorTable;

    /* set up transparency
     */
	if(_SGIFDFlags.gceUseTransparency)
		colorTable[gceTransparentColorIndex].alpha = 0x0;
	
	xpos = 0;
	ypos = 0;
	pass = 0;
	while( (pixelValue = LWZReadByte(self,
									 decompressionState,
									 stream)) >= 0 ) {

		pixelIndex = xpos + (ypos * idWidth);
		
		if(_SGIFDFlags.produceSilhouette) {
			colorData[pixelIndex].red	= 
			colorData[pixelIndex].green	= 
			colorData[pixelIndex].blue	= 0xf;

			colorData[pixelIndex].alpha	= 
			(_SGIFDFlags.gceUseTransparency &&
			 (pixelValue == gceTransparentColorIndex)) ? 0 : 0xf;
		} else {
#ifdef __BIG_ENDIAN__
			colorData[pixelIndex] = colorTable[pixelValue];
#else
			colorData[pixelIndex].red	= colorTable[pixelValue].green;
			colorData[pixelIndex].green = colorTable[pixelValue].red;
			colorData[pixelIndex].blue	= colorTable[pixelValue].alpha;
			colorData[pixelIndex].alpha	= colorTable[pixelValue].blue;
#endif
		}
		
		++xpos;
		if (xpos == idWidth) {
			xpos = 0;
			if (_SGIFDFlags.idInterlaced) {
				switch (pass) {
				case 0:
				case 1:
					ypos += 8; break;
				case 2:
					ypos += 4; break;
				case 3:
					ypos += 2; break;
				}

				if (ypos >= idHeight) {
					++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 >= idHeight)
			break;
	}

	if(pixelValue < LWZ_DONE) {
		VERBOSE {
			METHOD;
			fprintf(stderr, "LWZReadByte() failed with %d\n", pixelValue);
		}
		_SDFlags.lastCorrupt = YES;
	}
	
fini:
    /* If everything went correctly above, the next call to LWZReadByte
       should return LWZ_DONE.

	   pixelValue is being recycled....
     */
    if ( (pixelValue = LWZReadByte(self, decompressionState, stream)) != 0) {
		VERBOSE{
            METHOD;
            fprintf(stderr, "Image may be corrupt (last return: %02d\n",
					pixelValue);
		}
		_SDFlags.lastCorrupt = YES;
    }

    /* undo transparency
     */
	if(_SGIFDFlags.gceUseTransparency)
		colorTable[gceTransparentColorIndex].alpha = 0xf;

	[returnImage useRepresentation:imageRep];

	return self;
}

- _decodeImageDescriptor:(NXStream *)stream
/*20. Image Descriptor.
 *
 *    a. Description. Each image in the Data Stream is composed of an Image
 *    Descriptor, an optional Local Color Table, and the image data.  Each
 *    image must fit within the boundaries of the Logical Screen, as defined
 *    in the Logical Screen Descriptor.
 *
 *    The Image Descriptor contains the parameters necessary to process a table
 *    based image. The coordinates given in this block refer to coordinates
 *    within the Logical Screen, and are given in pixels. This block is a
 *    Graphic-Rendering Block, optionally preceded by one or more Control
 *    blocks such as the Graphic Control Extension, and may be optionally
 *    followed by a Local Color Table; the Image Descriptor is always followed
 *    by the image data.
 *
 *    This block is REQUIRED for an image.  Exactly one Image Descriptor must
 *    be present per image in the Data Stream.  An unlimited number of images
 *    may be present per Data Stream.
 *
 *    b. Required Version.  87a.
 *
 *    c. Syntax.
 *
 *      7 6 5 4 3 2 1 0        Field Name                    Type
 *     +---------------+
 *  0  |               |       Image Separator               Byte
 *     +---------------+
 *  1  |               |       Image Left Position           Unsigned
 *     +-             -+
 *  2  |               |
 *     +---------------+
 *  3  |               |       Image Top Position            Unsigned
 *     +-             -+
 *  4  |               |
 *     +---------------+
 *  5  |               |       Image Width                   Unsigned
 *     +-             -+
 *  6  |               |
 *     +---------------+
 *  7  |               |       Image Height                  Unsigned
 *     +-             -+
 *  8  |               |
 *     +---------------+
 *  9  | | | |   |     |       <Packed Fields>               See below
 *     +---------------+
 *
 *     <Packed Fields>  =      Local Color Table Flag        1 Bit
 *                             Interlace Flag                1 Bit
 *                             Sort Flag                     1 Bit
 *                             Reserved                      2 Bits
 *                             Size of Local Color Table     3 Bits
 */

/* Image Seperator has already been eaten by -_decodeData:
 */
{
	unsigned char dataBuf[9];
	BOOL success;

	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Decoding <Image Descriptor>\n");
	}

	if(NXRead(stream, dataBuf, 9) != 9) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Could not read 9 bytes of data from stream\n");
		}
		return nil;
	}

	idX		 = read_short(dataBuf);
	idY		 = read_short(dataBuf + 2);
	idWidth	 = read_short(dataBuf + 4);
	idHeight = read_short(dataBuf + 6);

	if( ( (idX + idWidth) > lsdWidth ) || ((idY + idHeight) > lsdHeight ) ) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Image Descriptor size or location invalid.\n");
			METHOD;
			fprintf(stderr,
					"Logical Screen Size: %d x %d\n",
					lsdWidth, lsdHeight);
			METHOD;		
			fprintf(stderr,
					"ID attributes: at %d x %d, size %d x %d\n",
					idX, idY, idWidth, idHeight);
		}
		return nil;
	}

	_SGIFDFlags.lctPresent	 = (dataBuf[8] & 0x80) ? YES : NO;
	_SGIFDFlags.idInterlaced = (dataBuf[8] & 0x40) ? YES : NO;
	_SGIFDFlags.lctSorted	 = (dataBuf[8] & 0x20) ? YES : NO;

	if (_SGIFDFlags.lctPresent)
		lctSize = 2 << (dataBuf[8] & 0x7);

	VERBOSE {
		METHOD;
		fprintf(stderr,
				"ID attributes: at %d x %d, size %d x %d\n",
				idX, idY, idWidth, idHeight);
		METHOD;
		fprintf(stderr,
				"Local Color Table: %s; Interlaced: %s\n",
				BOOL2STR(_SGIFDFlags.lctPresent),
				BOOL2STR(_SGIFDFlags.idInterlaced));
		if(_SGIFDFlags.lctPresent) {
			METHOD;
			fprintf(stderr,
					"LCT Sorted: %s; %d entries\n",
					BOOL2STR(_SGIFDFlags.lctSorted),
					lctSize);
		}
	}

    /* +++++++++++++++ READ LOCAL COLOR TABLE [or not]
     */
	if(_SGIFDFlags.lctPresent) {
		localColorTable = [self _decodeColorTable:stream
								entryCount:lctSize
								intoBuffer:NULL];
		if(!localColorTable)			
			return nil;
	} else
        /* No local color table present-- use global color table.
         */
		localColorTable = globalColorTable;

	success = [self _decodeImageData:stream] ? YES : NO;
	
	[self _resetImageDescriptor];
	
	return success ? self : nil;
}

- _decodeData:(NXStream *) stream
{
/*<Data> ::=                <Graphic Block>  |
 *                          <Special-Purpose Block>
 *
 *<Graphic Block> ::=     [Graphic Control Extension] <Graphic-Rendering Block>
 *
 *<Graphic-Rendering Block> ::=  <Table-Based Image>  |
 *                               Plain Text Extension
 *
 *<Table-Based Image> ::=   Image Descriptor [Local Color Table] Image Data
 *
 *<Special-Purpose Block> ::=    Application Extension  |
 *                               Comment Extension
 */
/*A. Quick Reference Table.
 *
 *Block Name                  Required   Label       Ext.   Vers.
 *Application Extension       Opt. (*)   0xFF (255)  yes    89a
 *Comment Extension           Opt. (*)   0xFE (254)  yes    89a
 *Global Color Table          Opt. (1)   none        no     87a
 *Graphic Control Extension   Opt. (*)   0xF9 (249)  yes    89a
 *Header                      Req. (1)   none        no     N/A
 *Image Descriptor            Opt. (*)   0x2C (044)  no     87a (89a)
 *Local Color Table           Opt. (*)   none        no     87a
 *Logical Screen Descriptor   Req. (1)   none        no     87a (89a)
 *Plain Text Extension        Opt. (*)   0x01 (001)  yes    89a
 *Trailer                     Req. (1)   0x3B (059)  no     87a
 *
 *Unlabeled Blocks
 *Header                      Req. (1)   none        no     N/A
 *Logical Screen Descriptor   Req. (1)   none        no     87a (89a)
 *Global Color Table          Opt. (1)   none        no     87a
 *Local Color Table           Opt. (*)   none        no     87a
 *
 *Graphic-Rendering Blocks
 *Plain Text Extension        Opt. (*)   0x01 (001)  yes    89a
 *Image Descriptor            Opt. (*)   0x2C (044)  no     87a (89a)
 *
 *Control Blocks
 *Graphic Control Extension   Opt. (*)   0xF9 (249)  yes    89a
 *
 *Special Purpose Blocks
 *Trailer                     Req. (1)   0x3B (059)  no     87a
 *Comment Extension           Opt. (*)   0xFE (254)  yes    89a
 *Application Extension       Opt. (*)   0xFF (255)  yes    89a
 *
 *legend:           (1)   if present, at most one occurrence
 *                  (*)   zero or more occurrences
 *                  (+)   one or more occurrences
 */
	unsigned char identifier, extensionIdentifier;

	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Decoding <data> into instance of NXImage\n");
	}
	
    /* The following do {...} while loop iterates across all of the
       various blocks within the GIF stream and decodes those that
       affect the resulting image.  All other blocks are ignored.
       
       This loop is terminated by one of three things;  encountering
       the end of the stream, encountering a trailer block [id:
       0x3b], or the returnImage being freed and set to nil.
       
       If it is terminated by an EOS and the final identifier was
       not an End-Of-Stream, the returnImage is returned if it
       contains any* image reps -- otherwise, it is freed and nil
       is returned.
       
       The returnImage being freed and then being set to nil is
       indicative that some particualr internal decoding algorithm
       did not find valid data.
	   */
	do {
        /* read identifier
         */
		identifier = NXGetc(stream);
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Encountered Identifier: 0x%02x\n",
					identifier);
		}
        /* determine block type from identifier
         */
		switch(identifier) {
		case 0x21:  {
            /* Must be an extension-- read extension ID and decode
               appropriately.
             */
			extensionIdentifier = NXGetc(stream);
			VERBOSE {
				METHOD;
				fprintf(stderr,
						"Encountered Extension Identifier: 0x%02x\n",
						extensionIdentifier);
			}
			switch(extensionIdentifier) {
			case 0x01:
				/* plain text extension */
				if(![self _decodePlainTextExtension:stream])
					goto MarkCorruptedAndReturn;
				break;
			case 0xf9:
				/* graphic control extension */
				if(![self _decodeGraphicControlExtension:stream])
					goto MarkCorruptedAndReturn;
				_SGIFDFlags.gcePresent = YES;
				break;
			case 0xfe:
				/* comment extension */
				if(![self _decodeCommentExtension:stream])
					goto MarkCorruptedAndReturn;
				break;
			case 0xff:
				/* application extension */
				if(![self _decodeApplicationExtension:stream])
					goto MarkCorruptedAndReturn;
				break;
			case 0x00: // if NXGetc fails...				
			default:
                /* unknown extension
                 */
				VERBOSE {
					METHOD;
					fprintf(stderr,
							"Encountered unknown extension 0x%02x\n",
							extensionIdentifier);
				}
				goto MarkCorruptedAndReturn;
			}
		}
			break;
		case 0x2c:
			if(![self _decodeImageDescriptor:stream])
				goto MarkCorruptedAndReturn;
            /* Once an <Image Descriptor> has been decoded, gcePresent
               should always be reset to NO.
			   */
			if(_SGIFDFlags.gcePresent)
				[self _resetGraphicControlExtension];
			break;
		case 0x3b:
			VERBOSE {
				METHOD;
				fprintf(stderr,
						"Encountered Trailer (0x3b) Block\n");
			}
			break;
		case 0x00: // if NXGetc fails...
		default:
			VERBOSE{
				METHOD;
				fprintf(stderr,
						"Encounterd unknown identifier 0x%02x\n",
						identifier);
			}
			goto MarkCorruptedAndReturn;
		}
	} while ( returnImage &&
			  (identifier != 0x3B) &&
			  !NXAtEOS(stream) );

	if(![[returnImage representationList] count]) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Stream decoded, but contained no images.\n");
		}	
		goto MarkCorruptedAndReturnNIL;
	}
	
	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Stream decoded; contained %d images.\n",
				[[returnImage representationList] count]);
	}		
	
	return returnImage;

MarkCorruptedAndReturn:
//	Mark as corrupted, but return image [if it contains representations]
	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Stream corrupted, %d image descriptors decoded\n",
				[[returnImage representationList] count]);
	}
	_SDFlags.lastCorrupt = YES;
	return [[returnImage representationList] count] ? returnImage : nil;

MarkCorruptedAndReturnNIL:
	// Mark as corrupted and return nil.
	_SDFlags.lastCorrupt = YES;
	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Stream corrupted, returning nil\n");
	}

	return nil;		
}

- _resetGraphicControlExtension
{
	VERBOSE {
		METHODnl;
	}

    /* reset flags
     */
	_SGIFDFlags.gcePresent		   = NO;
	_SGIFDFlags.gceUseTransparency = NO;

	gceTransparentColorIndex	   = 0;

	return self;
}

- _resetImageDescriptor
{
	lctSize						   = 0;

	if(localColorTable && (localColorTable != globalColorTable))
		NX_FREE(localColorTable);
	localColorTable				   = NULL;

    /* reset flags
     */
	_SDFlags.lastCorrupt		   = NO;
	_SGIFDFlags.idInterlaced	   = NO;

	_SGIFDFlags.lctPresent		   = NO;
	_SGIFDFlags.lctSorted		   = NO;

	idX = idY = idWidth = idHeight = 0;
	
	return self;
}

- _resetDecoder
/*  Resets state and frees memory associated with a Graphic Control
    Extension
 */
{
	VERBOSE {
		METHODnl;
	}

	[self _resetGraphicControlExtension];
	[self _resetImageDescriptor];

	bzero(version, sizeof(version));

    /* LWZ decompression reset much later-- it relies upon information
       decoded from the GIF stream for *each* Image Descriptor.
     */
		
	return self;
}
@end

@implementation SurfGIFDecoder (UnsupportedBlocks)
- _skipDataBlocks:(NXStream *) stream
{
	unsigned char blockSize;

	NX_DURING
	while(!NXAtEOS(stream) && (blockSize = NXGetc(stream)) )
		NXSeek(stream, blockSize, NX_FROMCURRENT);
	NX_HANDLER
	VERBOSE {
		METHOD;
		fprintf(stderr,
				"failed to seek through data block\n");
	}
	NX_VALRETURN(nil);
	NX_ENDHANDLER

	if(blockSize) {
		VERBOSE {
			METHOD;
			fprintf(stderr,
					"Encountered EOS before terminator data block."
					"<Application Extension>\n");
		}
		return nil;
	}

	return self;

}

- _decodeApplicationExtension:(NXStream *) stream
{
	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Skipping <Application Extension>\n");
	}

	return [self _skipDataBlocks:stream];
}

- _decodeCommentExtension:(NXStream *) stream
{
	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Skipping <Comment Extension>\n");
	}

	return [self _skipDataBlocks:stream];
}

- _decodePlainTextExtension:(NXStream *) stream
{
	VERBOSE {
		METHOD;
		fprintf(stderr,
				"Skipping <Plain Text Extension>\n");
	}

	return [self _skipDataBlocks:stream];
}
@end
