text: /*
      SDL_anigif:  An example animated GIF image loading library for use with SDL
      SDL_image Copyright (C) 1997-2006 Sam Lantinga
      Animated GIF "derived work" Copyright (C) 2006 Doug McFadyen
  
      This library is free software; you can redistribute it and/or
      modify it under the terms of the GNU Lesser General Public
      License as published by the Free Software Foundation; either
      version 2.1 of the License, or (at your option) any later version.
  
      This library is distributed in the hope that it will be useful,
      but WITHOUT ANY WARRANTY; without even the implied warranty of
      MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      Lesser General Public License for more details.
  
      You should have received a copy of the GNU Lesser General Public
      License along with this library; if not, write to the Free Software
      Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
  */
  
  #include <stdio.h>
  #include <string.h>
  #include "SDL_anigif.h"
  
  
  
  /* Code from here to end of file has been adapted from XPaint:           */
  /* +-------------------------------------------------------------------+ */
  /* | Copyright 1990, 1991, 1993 David Koblas.                          | */
  /* | Copyright 1996 Torsten Martinsen.                                 | */
  /* |   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.	       | */
  /* +-------------------------------------------------------------------+ */
  /* Adapted for use in SDL by Sam Lantinga -- 7/20/98 */
  /* Animated GIF support by Doug McFadyen -- 10/19/06 */
  
  #define	MAXCOLORMAPSIZE			256
  
  #define	TRUE					1
  #define	FALSE					0
  
  #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 LM_to_uint(a,b)			(((b)<<8)|(a))
  
  #define SDL_SetError(t)			((void)0)		/* We're not SDL so ignore error reporting */
  
  
  typedef struct
  {
  	unsigned int	Width;
  	unsigned int	Height;
  	unsigned char	ColorMap[3][MAXCOLORMAPSIZE];
  	unsigned int	BitPixel;
  	unsigned int	ColorResolution;
  	unsigned int	Background;
  	unsigned int	AspectRatio;
  } gifscreen;
  
  typedef struct
  {
  	int				transparent;
  	int				delayTime;
  	int				inputFlag;
  	int				disposal;
  } gif89;
  
  typedef struct
  {
  	/* global data */
  	SDL_RWops*		src;
  	gifscreen		gs;
  	gif89			g89;
  	int				zerodatablock;
  	/* AG_LoadGIF_RW data */
  	unsigned char	localColorMap[3][MAXCOLORMAPSIZE];
  	/* GetCode data */
  	unsigned char	buf[280];
  	int				curbit, lastbit, done, lastbyte;
  	/* LWZReadByte data */
  	int				fresh, code, incode;
  	int				codesize, setcodesize;
  	int				maxcode, maxcodesize;
  	int				firstcode, oldcode;
  	int				clearcode, endcode;
  	int				table[2][(1 << MAX_LWZ_BITS)];
  	int				stack[(1 << (MAX_LWZ_BITS))*2], *sp;
  } gifdata;
  
  
  
  static int			ReadColorMap( gifdata* gd, int number, unsigned char buffer[3][MAXCOLORMAPSIZE] );
  static int			DoExtension( gifdata* gd, int label );
  static int			GetDataBlock( gifdata* gd, unsigned char* buf );
  static int			GetCode( gifdata* gd, int code_size, int flag );
  static int			LWZReadByte( gifdata* gd, int flag, int input_code_size );
  static SDL_Surface*	ReadImage( gifdata* gd, int len, int height, int, unsigned char cmap[3][MAXCOLORMAPSIZE], int interlace, int ignore );
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  int AG_isGIF( SDL_RWops* src )
  {
  	int isGIF = FALSE;
  
  	if ( src )
  	{
  		int start = SDL_RWtell( src );
  		char magic[6];
  
  		if ( SDL_RWread(src,magic,sizeof(magic),1) )
  		{
  			if ( (strncmp(magic,"GIF",3) == 0) && ((memcmp(magic+3,"87a",3) == 0) || (memcmp(magic+3,"89a",3) == 0)) )
  			{
  				isGIF = TRUE;
  			}
  		}
  
  		SDL_RWseek( src, start, SEEK_SET );
  	}
  
  	return isGIF;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  int AG_LoadGIF( const char* file, AG_Frame* frames, int size )
  {
  	int n = 0;
  
  	SDL_RWops* src = SDL_RWFromFile( file, "rb" );
  
  	if ( src )
  	{
  		n = AG_LoadGIF_RW( src, frames, size );
  		SDL_RWclose( src );
  	}
  
  	return n;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  void AG_FreeSurfaces( AG_Frame* frames, int nFrames )
  {
  	int i;
  
  	if ( frames )
  	{
  		for ( i = 0; i < nFrames; i++ )
  		{
  			if ( frames[i].surface )
  			{
  				SDL_FreeSurface( frames[i].surface );
  				frames[i].surface = NULL;
  			}
  		}
  	}
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  int AG_ConvertSurfacesToDisplayFormat( AG_Frame* frames, int nFrames )
  {
  	int i;
  	int n = 0;
  
  	if ( frames )
  	{
  		for ( i = 0; i < nFrames; i++ )
  		{
  			if ( frames[i].surface )
  			{
  				SDL_Surface* surface = (frames[i].surface->flags & SDL_SRCCOLORKEY) ? SDL_DisplayFormatAlpha(frames[i].surface) : SDL_DisplayFormat(frames[i].surface);
  
  				if ( surface )
  				{
  					SDL_FreeSurface( frames[i].surface );
  					frames[i].surface = surface;
  					n++;
  				}
  			}
  		}
  	}
  
  	return n;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  int AG_NormalizeSurfacesToDisplayFormat( AG_Frame* frames, int nFrames )
  {
  	int n = 0;
  
  	if ( nFrames > 0 && frames && frames[0].surface )
  	{
  		SDL_Surface* mainSurface = (frames[0].surface->flags & SDL_SRCCOLORKEY) ? SDL_DisplayFormatAlpha(frames[0].surface) : SDL_DisplayFormat(frames[0].surface);
  		const int newDispose = (frames[0].surface->flags & SDL_SRCCOLORKEY) ? AG_DISPOSE_RESTORE_BACKGROUND : AG_DISPOSE_NONE;
  
  		if ( mainSurface )
  		{
  			int i;
  			int lastDispose = AG_DISPOSE_NA;
  			int iRestore = 0;
  			const Uint8 alpha = (frames[0].disposal == AG_DISPOSE_NONE) ? SDL_ALPHA_OPAQUE : SDL_ALPHA_TRANSPARENT;
  
  			SDL_FillRect( mainSurface, NULL, SDL_MapRGBA(mainSurface->format,0,0,0,alpha) );
  
  			for ( i = 0; i < nFrames; i++ )
  			{
  				if ( frames[i].surface )
  				{
  					SDL_Surface* surface = SDL_ConvertSurface( mainSurface, mainSurface->format, mainSurface->flags );
  
  					if ( surface )
  					{
  						SDL_Rect r;
  
  						if ( lastDispose == AG_DISPOSE_NONE )
  							SDL_BlitSurface( frames[i-1].surface, NULL, surface, NULL );
  
  						if ( lastDispose == AG_DISPOSE_RESTORE_PREVIOUS )
  							SDL_BlitSurface( frames[iRestore].surface, NULL, surface, NULL );
  						if ( frames[i].disposal != AG_DISPOSE_RESTORE_PREVIOUS )
  							iRestore = i;
  
  						r.x = (Sint16)frames[i].x;
  						r.y = (Sint16)frames[i].y;
  						SDL_BlitSurface( frames[i].surface, NULL, surface, &r );
  
  						SDL_FreeSurface( frames[i].surface );
  						frames[i].surface = surface;
  						frames[i].x = frames[i].y = 0;
  						lastDispose = frames[i].disposal;
  						frames[i].disposal = newDispose;
  						n++;
  					}
  				}
  			}
  
  			SDL_FreeSurface( mainSurface );
  		}
  	}
  
  	return n;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  int AG_LoadGIF_RW( SDL_RWops* src, AG_Frame* frames, int maxFrames )
  {
  	int start;
  	unsigned char buf[16];
  	unsigned char c;
  	int useGlobalColormap;
  	int bitPixel;
  	int iFrame = 0;
  	char version[4];
  	SDL_Surface* image = NULL;
  	gifdata* gd;
  
  	if ( src == NULL )
  		return 0;
  
  	gd = malloc( sizeof(*gd) );
  	memset( gd, 0, sizeof(*gd) );
  	gd->src = src;
  
  	start = SDL_RWtell( src );
  
  	if ( !SDL_RWread(src,buf,6,1) )
  	{
  		SDL_SetError( "error reading magic number" );
  		goto done;
  	}
  
  	if ( strncmp((char*)buf,"GIF",3) != 0 )
  	{
  		SDL_SetError( "not a GIF file" );
  		goto done;
  	}
  
  	strncpy( version, (char*)buf+3, 3 );
  	version[3] = '\0';
  
  	if ( (strcmp(version,"87a") != 0) && (strcmp(version,"89a") != 0) )
  	{
  		SDL_SetError( "bad version number, not '87a' or '89a'" );
  		goto done;
  	}
  
  	gd->g89.transparent	= -1;
  	gd->g89.delayTime	= -1;
  	gd->g89.inputFlag	= -1;
  	gd->g89.disposal	= AG_DISPOSE_NA;
  
  	if ( !SDL_RWread(src,buf,7,1) )
  	{
  		SDL_SetError( "failed to read screen descriptor" );
  		goto done;
  	}
  
  	gd->gs.Width			= LM_to_uint(buf[0],buf[1]);
  	gd->gs.Height			= LM_to_uint(buf[2],buf[3]);
  	gd->gs.BitPixel			= 2 << (buf[4] & 0x07);
  	gd->gs.ColorResolution	= (((buf[4] & 0x70) >> 3) + 1);
  	gd->gs.Background		= buf[5];
  	gd->gs.AspectRatio		= buf[6];
  
  	if ( BitSet(buf[4],LOCALCOLORMAP) )		/* Global Colormap */
  	{
  		if ( ReadColorMap(gd,gd->gs.BitPixel,gd->gs.ColorMap) )
  		{
  			SDL_SetError( "error reading global colormap" );
  			goto done;
  		}
  	}
  
  	do
  	{
  		if ( !SDL_RWread(src,&c,1,1) )
  		{
  			SDL_SetError( "EOF / read error on image data" );
  			goto done;
  		}
  
  		if ( c == ';' )		/* GIF terminator */
  			goto done;
  
  		if ( c == '!' )		/* Extension */
  		{
  			if ( !SDL_RWread(src,&c,1,1) )
  			{
  				SDL_SetError( "EOF / read error on extention function code" );
  				goto done;
  			}
  			DoExtension( gd, c );
  			continue;
  		}
  
  		if ( c != ',' )		/* Not a valid start character */
  			continue;
  
  		if ( !SDL_RWread(src,buf,9,1) )
  		{
  			SDL_SetError( "couldn't read left/top/width/height" );
  			goto done;
  		}
  
  		useGlobalColormap = !BitSet(buf[8],LOCALCOLORMAP);
  		bitPixel = 1 << ((buf[8] & 0x07) + 1);
  
  		if ( !useGlobalColormap )
  		{
  			if ( ReadColorMap(gd,bitPixel,gd->localColorMap) )
  			{
  				SDL_SetError( "error reading local colormap" );
  				goto done;
  			}
  			image = ReadImage( gd, LM_to_uint(buf[4],buf[5]), LM_to_uint(buf[6],buf[7]), bitPixel, gd->localColorMap, BitSet(buf[8],INTERLACE), (frames==NULL) );
  		}
  		else
  		{
  			image = ReadImage( gd, LM_to_uint(buf[4],buf[5]), LM_to_uint(buf[6],buf[7]), gd->gs.BitPixel, gd->gs.ColorMap, BitSet(buf[8],INTERLACE), (frames==NULL) );
  		}
  
  		if ( frames )
  		{
  			if ( image == NULL )
  				goto done;
  
  			if ( gd->g89.transparent >= 0 )
  				SDL_SetColorKey( image, SDL_SRCCOLORKEY, gd->g89.transparent );
  
  			frames[iFrame].surface	= image;
  			frames[iFrame].x		= LM_to_uint(buf[0], buf[1]);
  			frames[iFrame].y		= LM_to_uint(buf[2], buf[3]);
  			frames[iFrame].disposal	= gd->g89.disposal;
  			frames[iFrame].delay	= gd->g89.delayTime*10;
  /*			gd->g89.transparent		= -1;			** Hmmm, not sure if this should be reset for each frame? */
  		}
  
  		iFrame++;
  	} while ( iFrame < maxFrames || frames == NULL );
  
  done:
  	if ( image == NULL )
  		SDL_RWseek( src, start, SEEK_SET );
  
  	free( gd );
  
  	return iFrame;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  static int ReadColorMap( gifdata* gd, int number, unsigned char buffer[3][MAXCOLORMAPSIZE] )
  {
  	int i;
  	unsigned char rgb[3];
  	int flag;
  
  	flag = TRUE;
  
  	for ( i = 0; i < number; ++i )
  	{
  		if ( !SDL_RWread(gd->src,rgb,sizeof(rgb),1) )
  		{
  			SDL_SetError( "bad colormap" );
  			return 1;
  		}
  
  		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 FALSE;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  static int DoExtension( gifdata* gd, int label )
  {
  	unsigned char buf[256];
  
  	switch ( label )
  	{
  	  case 0x01:		/* Plain Text Extension */
  		break;
  
  	  case 0xff:		/* Application Extension */
  		break;
  
  	  case 0xfe:		/* Comment Extension */
  		while ( GetDataBlock(gd,buf) != 0 )
  			;
  		return FALSE;
  
  	  case 0xf9:		/* Graphic Control Extension */
  		(void)GetDataBlock( gd, buf );
  		gd->g89.disposal	= (buf[0] >> 2) & 0x7;
  		gd->g89.inputFlag	= (buf[0] >> 1) & 0x1;
  		gd->g89.delayTime	= LM_to_uint(buf[1],buf[2]);
  		if ( (buf[0] & 0x1) != 0 )
  			gd->g89.transparent = buf[3];
  
  		while ( GetDataBlock(gd,buf) != 0 )
  			;
  		return FALSE;
  	}
  
  	while ( GetDataBlock(gd,buf) != 0 )
  		;
  
  	return FALSE;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  static int GetDataBlock( gifdata* gd, unsigned char* buf )
  {
  	unsigned char count;
  
  	if ( !SDL_RWread(gd->src,&count,1,1) )
  	{
  		/* pm_message("error in getting DataBlock size" ); */
  		return -1;
  	}
  
  	gd->zerodatablock = count == 0;
  
  	if ( (count != 0) && !SDL_RWread(gd->src,buf,count,1) )
  	{
  		/* pm_message("error in reading DataBlock" ); */
  		return -1;
  	}
  
  	return count;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  static int GetCode( gifdata* gd, int code_size, int flag )
  {
  	int i, j, ret;
  	int count;
  
  	if ( flag )
  	{
  		gd->curbit = 0;
  		gd->lastbit = 0;
  		gd->done = FALSE;
  		return 0;
  	}
  
  	if ( (gd->curbit + code_size) >= gd->lastbit )
  	{
  		if ( gd->done )
  		{
  			if ( gd->curbit >= gd->lastbit )
  			SDL_SetError( "ran off the end of my bits" );
  			return -1;
  		}
  
  		gd->buf[0] = gd->buf[gd->lastbyte - 2];
  		gd->buf[1] = gd->buf[gd->lastbyte - 1];
  
  		if ( (count = GetDataBlock(gd, &gd->buf[2])) == 0 )
  			gd->done = TRUE;
  
  		gd->lastbyte = 2 + count;
  		gd->curbit = (gd->curbit - gd->lastbit) + 16;
  		gd->lastbit = (2 + count)*8;
  	}
  
  	ret = 0;
  	for ( i = gd->curbit, j = 0; j < code_size; ++i, ++j )
  		ret |= ((gd->buf[i / 8] & (1 << (i % 8))) != 0) << j;
  
  	gd->curbit += code_size;
  
  	return ret;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  static int LWZReadByte( gifdata* gd, int flag, int input_code_size )
  {
  	int i, code, incode;
  
  	if ( flag )
  	{
  		gd->setcodesize	= input_code_size;
  		gd->codesize	= gd->setcodesize + 1;
  		gd->clearcode	= 1 << gd->setcodesize;
  		gd->endcode		= gd->clearcode + 1;
  		gd->maxcodesize	= gd->clearcode*2;
  		gd->maxcode		= gd->clearcode + 2;
  
  		GetCode( gd, 0, TRUE );
  
  		gd->fresh = TRUE;
  
  		for ( i = 0; i < gd->clearcode; ++i )
  		{
  			gd->table[0][i] = 0;
  			gd->table[1][i] = i;
  		}
  
  		for ( ; i < (1 << MAX_LWZ_BITS); ++i )
  			gd->table[0][i] = gd->table[1][0] = 0;
  
  		gd->sp = gd->stack;
  		return 0;
  	}
  	else if ( gd->fresh )
  	{
  		gd->fresh = FALSE;
  		do
  		{
  			gd->firstcode = gd->oldcode = GetCode( gd, gd->codesize, FALSE );
  		} while ( gd->firstcode == gd->clearcode );
  		return gd->firstcode;
  	}
  
  	if ( gd->sp > gd->stack )
  		return *--gd->sp;
  
  	while ( (code = GetCode(gd,gd->codesize,FALSE)) >= 0 )
  	{
  		if ( code == gd->clearcode )
  		{
  			for ( i = 0; i < gd->clearcode; ++i )
  			{
  				gd->table[0][i] = 0;
  				gd->table[1][i] = i;
  			}
  
  			for ( ; i < (1 << MAX_LWZ_BITS); ++i )
  				gd->table[0][i] = gd->table[1][i] = 0;
  
  			gd->codesize	= gd->setcodesize + 1;
  			gd->maxcodesize	= gd->clearcode*2;
  			gd->maxcode		= gd->clearcode + 2;
  			gd->sp			= gd->stack;
  			gd->firstcode	= gd->oldcode = GetCode( gd, gd->codesize, FALSE );
  			return gd->firstcode;
  		}
  		else if ( code == gd->endcode )
  		{
  			int count;
  			unsigned char buf[260];
  
  			if ( gd->zerodatablock )
  				return -2;
  
  			while ( (count = GetDataBlock(gd,buf)) > 0 )
  				;
  
  			if ( count != 0 )
  			{
  				/* pm_message("missing EOD in data stream (common occurence)"); */
  			}
  			return -2;
  		}
  
  		incode = code;
  
  		if ( code >= gd->maxcode )
  		{
  			*gd->sp++ = gd->firstcode;
  			code = gd->oldcode;
  		}
  
  		while ( code >= gd->clearcode )
  		{
  			*gd->sp++ = gd->table[1][code];
  			if ( code == gd->table[0][code] )
  				SDL_SetError( "circular table entry BIG ERROR" );
  			code = gd->table[0][code];
  		}
  
  		*gd->sp++ = gd->firstcode = gd->table[1][code];
  
  		if ( (code = gd->maxcode) < (1 << MAX_LWZ_BITS) )
  		{
  			gd->table[0][code] = gd->oldcode;
  			gd->table[1][code] = gd->firstcode;
  			++gd->maxcode;
  			if ( (gd->maxcode >= gd->maxcodesize) && (gd->maxcodesize < (1 << MAX_LWZ_BITS)) )
  			{
  				gd->maxcodesize *= 2;
  				++gd->codesize;
  			}
  		}
  
  		gd->oldcode = incode;
  
  		if ( gd->sp > gd->stack )
  			return *--gd->sp;
  	}
  
  	return code;
  }
  
  
  
  /*--------------------------------------------------------------------------*/
  /*
  /*--------------------------------------------------------------------------*/
  static SDL_Surface* ReadImage( gifdata* gd, int len, int height, int cmapSize, unsigned char cmap[3][MAXCOLORMAPSIZE], int interlace, int ignore )
  {
  	SDL_Surface* image;
  	unsigned char c;
  	int i, v;
  	int xpos = 0, ypos = 0, pass = 0;
  
  	/* Initialize the compression routines */
  	if ( !SDL_RWread(gd->src,&c,1,1) )
  	{
  		SDL_SetError( "EOF / read error on image data" );
  		return NULL;
  	}
  
  	if ( LWZReadByte(gd,TRUE,c) < 0 )
  	{
  		SDL_SetError( "error reading image" );
  		return NULL;
  	}
  
  	/* If this is an "uninteresting picture" ignore it. */
  	if ( ignore )
  	{
  		while ( LWZReadByte(gd,FALSE,c) >= 0 )
  			;
  		return NULL;
  	}
  
  	image = SDL_AllocSurface( SDL_SWSURFACE, len, height, 8, 0, 0, 0, 0 );
  
  	for ( i = 0; i < cmapSize; i++ )
  	{
  		image->format->palette->colors[i].r = cmap[CM_RED][i];
  		image->format->palette->colors[i].g = cmap[CM_GREEN][i];
  		image->format->palette->colors[i].b = cmap[CM_BLUE][i];
  	}
  
  	while ( (v = LWZReadByte(gd,FALSE,c)) >= 0 )
  	{
  		((Uint8*)image->pixels)[xpos + ypos*image->pitch] = (Uint8)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 image;
  }
  
  