/*****
* gifinfo.c : gif info program. Based on readGIF.c
*
* This file Version	$Revision: 1.4 $
*
* Creation date:		Wed Feb 26 01:57:10 GMT+0100 1997
* Last modification: 	$Date: 1997/10/23 00:30:38 $
* By:					$Author: newt $
* Current State:		$State: Exp $
*
* Author:				newt
*
* Copyright (C) 1994-1997 by Ripley Software Development 
* All Rights Reserved
*
* +-------------------------------------------------------------------+
* | Copyright 1990 - 1994, 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.	          |
* +-------------------------------------------------------------------+
*
* This file is part of the XmHTML Widget Library.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 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
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public
* License along with this library; if not, write to the Free
* Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*****/
/*****
* ChangeLog 
* $Log: gifinfo.c,v $
* Revision 1.4  1997/10/23 00:30:38  newt
* XmHTML Beta 1.1.0 release
*
* Revision 1.3  1997/08/30 02:03:02  newt
* Removed all ImageBuffer routines, they are now in ImBuffer.c.
* Changed gif recognition login due to these ImageBuffer changes.
*
* Revision 1.2  1997/03/11 19:58:30  newt
* ImageBuffer changes. Added support for reading animated gifs
*
* Revision 1.1  1997/03/02 23:02:49  newt
* Initial Revision
*
*****/ 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>

#include "ImBuffer.h"

/*** External Function Prototype Declarations ***/

/*** Public Variable Declarations ***/

/*** Private Datatype Declarations ****/
typedef unsigned char Boolean;
typedef char* String;

FILE *output;

#ifndef True
#  define True	1
#  define False	0
#endif

#define Abs(num) ((num) < 0 ? -1*(num) : (num))

#define IMAGE_UNKNOWN	0
#define IMAGE_GIF		1
#define IMAGE_GIFANIM	2

#define XmIMAGE_DISPOSE_NONE			1
#define XmIMAGE_DISPOSE_BY_BACKGROUND	2
#define XmIMAGE_DISPOSE_BY_PREVIOUS		3

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

#define	MAX_LWZ_BITS		12
#define INTERLACE		0x40
#define LOCALCOLORMAP	0x80

/*** Private Function Prototype Declarations ****/
#define BitSet(byte, bit)		(((byte) & (bit)) == (bit))
#define LM_to_uint(a,b)			((((b)&0xFF) << 8) | ((a)&0xFF))

static int ReadColorMap(ImageBuffer *, int, Byte [3][MAX_IMAGE_COLORS], int *);
static int DoExtension(ImageBuffer *ib, int label);
static void SkipImage(ImageBuffer *, int show_bits);

/*** Private Variable Declarations ***/
static struct {
	unsigned int	Width;
	unsigned int	Height;
	Byte			ColorMap[3][MAX_IMAGE_COLORS];
	unsigned int	BitPixel;
	unsigned int	ColorResolution;
	unsigned int	Background;
	unsigned int	AspectRatio;
	int	      xGrayScale;
}GifAnimScreen;

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

/*****
* Name: 		isGifAnimated
* Return Type: 	int
* Description: 	checks whether a file is a GIF or animated GIF image
* In: 
*	ib:			file descriptor of image to check
* Returns:
*	IMAGE_UNKNOWN, IMAGE_GIF or IMAGE_GIFANIM
*****/
int
isGifAnimated(ImageBuffer *ib)
{
	Byte buf[16], c;
	int imageCount = 0;

	/*
	* Initialize GIF89 extensions
	*/
	Gif89.transparent = -1;
	Gif89.delayTime = -1;
	Gif89.inputFlag = -1;
	Gif89.disposal = 0;
	Gif89.loopCount = 0;	/* infinite looping */

	/*
	* When we get here, we have already know this is a gif image
	* so don't test again.
	*/
	/* gif magic */
	(void)ReadOK(ib,buf,6);

	/* logical screen descriptor */
	(void)ReadOK(ib,buf,7);

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

	/* skip global colormap */
	if(BitSet(buf[4], LOCALCOLORMAP))
	{
		if(ReadColorMap(ib,GifAnimScreen.BitPixel,GifAnimScreen.ColorMap,
				&GifAnimScreen.xGrayScale))
		{
			fprintf(stderr, "Global colormap read error!\n");
			return(IMAGE_UNKNOWN);	/* can't read colormap */
		}
	}

	/*
	* We know a gif image is an animation if either the Netscape2.0 loop
	* extension is present or if we have at least two images.
	* The first case is pretty easy to detect, the second one requires us
	* to process the giffile up to the second image. Although we do not
	* actually decode the image itself, there is a performance loss here.
	*/
	while(imageCount < 2)
	{
		/* read block identifier */
		if(!ReadOK(ib, &c, 1))
			break;

		if(c == ';')
			break;	/* gif terminator */

		if(c == '!')
		{
			if(!ReadOK(ib, &c, 1))
				return(IMAGE_UNKNOWN);
			if((DoExtension(ib, c)) == IMAGE_GIFANIM)
				return(IMAGE_GIFANIM);
			continue;
		}
		if(c != ',')
			continue;	/* invalid start character */

		/* get next block */
		if(!ReadOK(ib, buf, 9))
			break;

		if(BitSet(buf[8], LOCALCOLORMAP))
		{
			if(ReadColorMap(ib, GifAnimScreen.BitPixel, GifAnimScreen.ColorMap,
				&GifAnimScreen.xGrayScale))
			return(IMAGE_UNKNOWN);	/* can't read colormap */
		}
		/* skip this image */
		SkipImage(ib, False);

		imageCount++;
	}
	return(imageCount >= 2 ? IMAGE_GIFANIM : IMAGE_GIF);
}

void
gifAnimTerminate(ImageBuffer *ib)
{
	/* rewind image buffer */
	RewindImageBuffer(ib);
}

Boolean
gifInit(ImageBuffer *ib)
{
	Byte buf[16], c;
	Boolean netscape = False;
	size_t curr_pos = 0;

	/* rewind imagebuffer */
	RewindImageBuffer(ib);

	/*
	* Initialize GIF89 extensions
	*/
	Gif89.transparent = -1;
	Gif89.delayTime = -1;
	Gif89.inputFlag = -1;
	Gif89.disposal = 0;
	Gif89.loopCount = 0;	/* inifinite by default */

	/* at this point, we *know* this is a valid gif image */
	/* skip GIF magic */
	(void)ReadOK(ib,buf,6);

	/* read logical screen descriptor */
	(void)ReadOK(ib,buf,7);

	/* get width and height */
	GifAnimScreen.Width	  = LM_to_uint(buf[0],buf[1]);
	GifAnimScreen.Height     = LM_to_uint(buf[2],buf[3]);
	GifAnimScreen.BitPixel        = 2<<(buf[4]&0x07);
	GifAnimScreen.ColorResolution = (((buf[4]&0x70)>>3)+1);
	GifAnimScreen.Background      = buf[5];
	GifAnimScreen.AspectRatio     = buf[6];

	fprintf(output, "\n%s:\n", ib->file);

	fprintf(output, "\tLogical Screen dimensions: %ix%i\n", 
		GifAnimScreen.Width, GifAnimScreen.Height);
	fprintf(output, "\tBackground index         : %i\n",
		GifAnimScreen.Background);
	fprintf(output, "\tAspect Ratio             : %i\n",
		GifAnimScreen.AspectRatio);
	fprintf(output, "\tGlobal colormap          : %s\n",
		(BitSet(buf[4], LOCALCOLORMAP) ? "present" : "not present"));
	fprintf(output, "\tGlobal Colormap size     : %i %s\n\n",
		GifAnimScreen.BitPixel,
		BitSet(buf[4], LOCALCOLORMAP) ? "" : "[ignored]");

	/* A global colormap */
	if(BitSet(buf[4], LOCALCOLORMAP))
	{
		if (ReadColorMap(ib, GifAnimScreen.BitPixel, 
				GifAnimScreen.ColorMap, &GifAnimScreen.xGrayScale))
		{
			fprintf(stderr, "Global colormap read error!\n");
			return(False);
		}
	}

	/* save current position in block */
	curr_pos = ib->next;

	/* move past all initial headers */

	/* block descriptor */
	if(!ReadOK(ib,&c,1))
	{
		fprintf(stderr, "Error: failed to read block descriptor.\n");
		return(False);
	}

	/* loop thru all extensions and global colormap */
	while(c == '!')
	{
		/* read extension type */
		if(!ReadOK(ib, &c, 1))
		{
			fprintf(stderr, "Error: failed to read extension type.\n"); 
			return(False);
		}

		/*
		* Problem: we know this gif is an animation, but it
		* may be a NETSCAPE2.0 loop extension or a series of
		* images. The first has a loop count in it, but the
		* second one doesn't. DoExtension returns
		* IMAGE_GIFANIM if this is a NETSCAPE2.0 or IMAGE_GIF
		* if it's a series of images. 
		* So we need to see what we get returned: if it's 
		* GIFANIM we get the loop_count out of the Gif89 struct.
		* if it's just GIF we set it to 1.
		*/
		if((DoExtension(ib, c)) == IMAGE_GIFANIM)
			netscape = True;

		/* get next block */
		if(!ReadOK(ib, &c, 1))
		{
			fprintf(stderr, "Error: failed to read block descriptor.\n");
			return(False);
		}
	}

	if(netscape)
	{
		fprintf(output, "Image contains NETSCAPE2.0 Loop Extension\n");
		fprintf(output, "Animation loop count: %i\n\n", Gif89.loopCount);
	}

	/* reset file pointer */
	ib->next = curr_pos;
	return(True);
}

int
gifAnimNextFrame(ImageBuffer *ib)
{
	Byte buf[16], c, localColorMap[3][MAX_IMAGE_COLORS];
	int useGlobalColormap, bitPixel, grayScale, w, h, x, y;
	static int current_frame;

	/*
	* Initialize GIF89 extensions
	*/
	Gif89.transparent = -1;
	Gif89.delayTime = -1;
	Gif89.inputFlag = -1;
	Gif89.disposal = 0;
	Gif89.loopCount = 0;

	/* block type */
	if(!ReadOK(ib, &c, 1))
	{
		fprintf(stderr, "Failed to read block descriptor\n");
		return(-1);
	}

	/* run until we get to the image data start character */
	while(c != ',')
	{
		/* GIF terminator, processing ends */
		if(c == ';')
			return(0);

		/* loop thru all extensions */
		if(c == '!')
		{
			/* read extension type */
			if(!ReadOK(ib, &c, 1))
			{
				fprintf(stderr, "Error on frame %i: failed to read "
					"extension type.\n", current_frame);
				return(-1);
			}

			DoExtension(ib, c);
		}
		/* block type */
		if(!ReadOK(ib, &c, 1))
		{
			fprintf(stderr, "Failed to read block descriptor\n");
			return(-1);
		}
	}

	/* image descriptor */
	if(!ReadOK(ib,buf,9))
	{
		fprintf(stderr, "Error on frame %i: failed to read "
			"image descriptor\n", current_frame);
		return(-1);
	}

	/* offsets in the logical screen */
	x = LM_to_uint(buf[0], buf[1]);
	y = LM_to_uint(buf[2], buf[3]);

	/* width and height for this particular frame */
	w = LM_to_uint(buf[4],buf[5]);
	h = LM_to_uint(buf[6],buf[7]);

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

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

	fprintf(output, "frame %i:\n", current_frame);
	fprintf(output, "\tInterlaced          : %s\n",
			BitSet(buf[8], INTERLACE) ? "Yes" : "No");
	fprintf(output, "\tDimensions          : %ix%i\n", w, h);
	fprintf(output, "\tLogical Offset      : %ix%i\n", x, y);
	fprintf(output, "\tTimeout             : %i\n", Abs(Gif89.delayTime*10));
	fprintf(output, "\tDisposal Method     : %s\n",
		(Gif89.disposal == 0 ? (current_frame == 0 ? "none" : "inherited") :
		(Gif89.disposal == 1 ? "none" : 
		(Gif89.disposal == 2 ? "by background" : "by previous"))));
	fprintf(output, "\tTransparency index  : %i\n", Gif89.transparent);
	fprintf(output, "\tLocal Colormap      : %s\n",
		useGlobalColormap ? "not present" : "present");
	fprintf(output, "\tLocal Colormap size : %i %s\n", bitPixel,
		useGlobalColormap ? "[ignored]" : "");

	if(!useGlobalColormap)
	{
		if(ReadColorMap(ib, bitPixel, localColorMap, &grayScale))
		{
			fprintf(stderr, "Error on frame %i: failed to read local "
				"colormap\n", current_frame);
			return(-1);
		}
		/* compare with global colormap and check if it differs */
		if(bitPixel == GifAnimScreen.BitPixel)
		{
			int i;
			for(i = 0; i < GifAnimScreen.BitPixel; i++)
			{
				/* break out as soon as it differs */
				if(localColorMap[CM_RED][i] !=
						GifAnimScreen.ColorMap[CM_RED][i] ||
					localColorMap[CM_GREEN][i] !=
						GifAnimScreen.ColorMap[CM_GREEN][i] ||
					localColorMap[CM_BLUE][i] !=
						GifAnimScreen.ColorMap[CM_BLUE][i])
					break;
			}
			if(i != GifAnimScreen.BitPixel)
				fprintf(output, "\tLocal Colormap differs from global "
					"colormap\n");
			else
				fprintf(output, "\tLocal Colormap equal to global colormap\n");
		}
	}
	SkipImage(ib, True);

	current_frame++;
	return(1);
}

static int
ReadColorMap(ImageBuffer *ib, int number, Byte buffer[3][MAX_IMAGE_COLORS], 
	int *gray)
{
	int flag;
	register int i;
	Byte rgb[3];

	flag = True;

	for(i = 0; i < number; ++i)
	{
		if(!ReadOK(ib, rgb, sizeof(rgb)))
			return(True);

		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]);
	}
	*gray = flag;

	return False;
}

static int
DoExtension(ImageBuffer *ib, int label)
{
	static char	buf[256];
	int ret_val = IMAGE_GIF;

	switch(label)
	{
		case 0x01:		/* Plain Text Extension */
			break;
		case 0xff:		/* Application Extension */
			/*
			* Netscape Looping extension
			* Get first block
			*/
			(void)GifGetDataBlock(ib, (Byte*)buf);
			if(!(strncmp((char*)buf, "NETSCAPE2.0", 11)))
			{
				ret_val = IMAGE_GIFANIM;
				if((GifGetDataBlock(ib, (Byte*)buf)) <= 0)
					ret_val = IMAGE_UNKNOWN;	/* corrupt animation */
				else
					Gif89.loopCount = LM_to_uint(buf[1], buf[2]);
			}
			break;
		case 0xfe:		/* Comment Extension */
			while(GifGetDataBlock(ib, (Byte*) buf) > 0);
			return(ret_val);
		case 0xf9:		/* Graphic Control Extension */
			(void)GifGetDataBlock(ib, (Byte*) 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 = (int)((Byte)buf[3]);

			while(GifGetDataBlock(ib, (Byte*) buf) > 0);
			return(ret_val);
		default:
			break;
	}
	while(GifGetDataBlock(ib, (Byte*) buf) > 0);

	return(ret_val);
}

static void
SkipImage(ImageBuffer *ib, int show_bits)
{
	Byte c;	
	static char	buf[256];

	/* Initialize the Compression routines */
	if(!ReadOK(ib,&c,1))
		return;

	if(show_bits)
		fprintf(output, "\tBits per pixel      : %i\n\n", (int)c);

	/* skip image */
	while((GifGetDataBlock(ib, (Byte*)buf)) > 0);
}

int
main(int argc, char **argv)
{
	ImageBuffer *ib;
	int size, img_type;
	Byte magic[30];

	if(argc == 1)
	{
		fprintf(stderr, "Usage: gifinfo <file> [output file]\n");
		exit(EXIT_SUCCESS);
	}

	ib = NULL;

	if(access(argv[1], R_OK))
	{
		perror(argv[1]);
		exit(EXIT_FAILURE);
	}
	output = stdout;
	if(argc == 3)
	{
		if((output = fopen(argv[2], "w")) == NULL)
		{
			perror(argv[2]);
			output = stdout;
		}
	}

	if((ib = ImageFileToBuffer(argv[1])) == NULL)
	{
		if(output != stdout)
		{
			fclose(output);
			unlink(argv[2]);
		}
		exit(EXIT_FAILURE);
	}

	memcpy(magic, ib->buffer, 30);
	img_type = IMAGE_UNKNOWN;

	if(!(strncmp((char*)magic, "GIF87a", 6)))
	{
		fprintf(output, "%s is a plain GIF87a\n", ib->file);
		img_type = IMAGE_GIF;
	}
	else
	{
		if(!(strncmp((char*)magic, "GIF89a", 6)))
		{
			img_type = IMAGE_GIF;
			if((img_type = isGifAnimated(ib)) == IMAGE_UNKNOWN)
				fprintf(stderr, "%s: corrupted GIF89a!\n", ib->file);
			else if(img_type == IMAGE_GIF)
				fprintf(output, "%s is a plain GIF89a\n", ib->file);
			else
				fprintf(output, "%s is an animated GIF89a\n", ib->file);
		}
		else
		{
			/* check gzf format */
			if(!(strncmp((char*)magic, "GZF87a", 6)))
			{
				fprintf(output, "%s is a plain GZF87a\n", ib->file);
				img_type = IMAGE_GIF;
			}
			else
			{
				if(!(strncmp((char*)magic, "GZF89a", 6)))
				{
					img_type = IMAGE_GIF;
					if((img_type = isGifAnimated(ib)) == IMAGE_UNKNOWN)
						fprintf(stderr, "%s: corrupted GZF89a!\n", ib->file);
					else if(img_type == IMAGE_GIF)
						fprintf(output, "%s is a plain GZF89a\n", ib->file);
					else
						fprintf(output, "%s is an animated GZF89a\n", ib->file);
				}
				else
				{
					fprintf(stderr, "%s: neither GIF nor GZF\n", ib->file);
					img_type = IMAGE_UNKNOWN;
				}
			}
		}
	}

	if(img_type == IMAGE_UNKNOWN || !(gifInit(ib)))
	{
		if(img_type != IMAGE_UNKNOWN)
			fprintf(stderr, "GIF initialization failed\n");
		FreeImageBuffer(ib);
		if(output != stdout)
		{
			fclose(output);
			unlink(argv[2]);
		}
		exit(EXIT_FAILURE);
	}

	/* plain gif image, read and exit */
	if(img_type == IMAGE_GIF)
	{
		if((size = gifAnimNextFrame(ib)) == -1)
			fprintf(stderr, "%s: image corrupted\n", ib->file);
		FreeImageBuffer(ib);
		exit(size == -1 ? EXIT_FAILURE : EXIT_SUCCESS);
	}

	/* animated gif, read all frames */
	while((gifAnimNextFrame(ib)) > 0);

	FreeImageBuffer(ib);
	exit(EXIT_SUCCESS);
}
