/*****
* gif2gzf.c : gif to gzf convertor. Converts gif images *without* using a
*				built-in LZW decoder but uses the "compress" utility instead.
*
* This file Version	$Revision: 1.2 $
*
* Creation date:		Tue May  6 06:13:14 GMT+0100 1997
* Last modification: 	$Date: 1997/10/23 00:30:37 $
* By:					$Author: newt $
* Current State:		$State: Exp $
*
* Author:				Koen D'Hondt <ripley@xs4all.nl>
*
* Copyright (C) 1994-1997 by Ripley Software Development 
* All Rights Reserved
*
* 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.
*
*****/
/*****
* ChangeLog 
* $Log: gif2gzf.c,v $
* Revision 1.2  1997/10/23 00:30:37  newt
* XmHTML Beta 1.1.0 release
*
* Revision 1.1  1997/05/28 13:14:17  newt
* Initial Revision
*
*****/ 
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#ifdef HAVE_LIBZ

#include <zlib.h>

#ifdef _ZCONF_H
#define BYTE_ALREADY_TYPEDEFED	/* zconf.h typedefs Byte */
#endif

#include "ImBuffer.h"
#include <XmHTML/LZWStream.h>

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

/*** Public Variable Declarations ***/
#ifdef DEBUG
extern int lzw_debug;	/* in LZWStream.c */
#endif

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

#define False 0
#define True  1

#define LOCALCOLORMAP	0x80

/*** Private Function Prototype Declarations ****/
#define WriteOK(FILE,BUF,LEN)	fwrite(BUF,LEN,1,FILE)

#define BitSet(byte, bit)       (((byte) & (bit)) == (bit))
#define LM_to_uint(a,b)         ((((b)&0xff) << 8) | ((a)&0xff))

/*** Private Variable Declarations ***/
static int verify, progress;

static void
doColormap(ImageBuffer *ib, FILE *fp, int nentries)
{
	Byte rgb[3];
	int i = 0;
	for(; i < nentries; i++)
	{
		ReadOK(ib, (Byte*)rgb, sizeof(rgb));
		WriteOK(fp, rgb, sizeof(rgb));
	}
}

/*****
* Name: 		convertLZW
* Return Type: 	void
* Description: 	converts Gif raster data in LZW format to Gzf raster data in
*				LZ77 data.
* In: 
*	ib:			input buffer
*	nread:		size of returned buffer.
* Returns:
*	an allocated buffer with uncompressed image data.
*****/
static Byte*
convertLZW(ImageBuffer *ib, FILE *ob, int *nread)
{
	LZWStream *lzw;
	int len;
	static Byte *data;

	/* create a new stream object */
	lzw = LZWStreamCreate(ib, NULL);

	/* set read functions */
	lzw->readOK  = ReadOK;
	lzw->getData = GifGetDataBlock;

	/* initialize uncompression */
	if((LZWStreamInit(lzw)) <= 0)
	{
		fprintf(stderr, lzw->err_msg);
		LZWStreamDestroy(lzw);
		return(NULL);
	}

	/* convert data */
	LZWStreamConvert(lzw);

	/* get uncompressed data */
	if((data = LZWStreamUncompress(lzw, nread)) == NULL)
	{
		fprintf(stderr, lzw->err_msg);
		/* destroy stream */
		LZWStreamDestroy(lzw);
		return(NULL);
	}

	/* and write codeSize before destroying the stream converter */
	len = LZWStreamGetCodeSize(lzw);

	WriteOK(ob, &len, 1);

	/* destroy stream */
	LZWStreamDestroy(lzw);

	return(data);
}

static void
writeImage(Byte *image, FILE *fp, int size)
{
	Byte buf[256], *compressed, *uncompr;
	Byte *inPtr;
	int i = 0, j = 0;
	unsigned long csize, len;

	/* compress image data in one go */
	
	/* first allocate destination buffer */
	csize = size + (int)(0.15*size) + 12;
	compressed = (Byte*)malloc(csize*sizeof(Byte));

	if((compress(compressed, &csize, image, size)) != Z_OK)
	{
		fprintf(stderr, "Error: compress failed!\n");
		free(compressed);
		/* put block terminator */
		j = 0;
		WriteOK(fp, &j, 1);
		return;
	}
	inPtr = compressed;

	/* save image data in chunks of 255 bytes max. */
	for(i = 0; i < (int)csize ; i++)
	{
		buf[j++] = *inPtr++;
		if(j == 254)
		{
			fputc(j&0xff, fp);
			WriteOK(fp, buf, 254);
			j = 0;
		}
	}
	/* flush out remaining data */
	if(j)
	{
		WriteOK(fp, &j, 1);
		WriteOK(fp, buf, j);
	}
#ifdef DEBUG
	printf("Compressed %i bytes into %i bytes\n", size, (int)csize);
#endif

	/* and write the block terminator */
	j = 0;
	WriteOK(fp, &j, 1);

	/* verify uncompression of this data */
	if(verify)
	{
		uncompr = (Byte*)malloc((size+1)*sizeof(Byte));

		if((uncompress(uncompr, &len, compressed, csize)) != Z_OK)
		{
			fprintf(stderr, "Bad compression: uncompress failed\n");
		}
		else if((int)len != size)
			fprintf(stderr, "Bad compression: uncompress returned %i bytes "
				"instead of %i\n", (int)len, size);
		else
		{
			printf(" Good compression: uncompress succeeded\n");
			fflush(stdout);
		}
		free(uncompr);
	}
	free(compressed);
}

/*****
* Name:			GifToGzf
* Return Type:	Boolean
* Description:	converts a Gif87a, Gif89a, to Gzf87a, Gzf89a.
* In:
*	ib:			image memory buffer
*	file:		output filename
* Returns:
*	True when succesfull conversion, False otherwise.
*****/
Boolean
GifToGzf(ImageBuffer *ib, String file)
{
	FILE *fp;
	Byte buf[256], c;	/* blocks in a Gif image contain max. 256 bytes */
	int i, done = 0, w, h, imageCount = 0;
	Byte *image;

	if((fp = fopen(file, "w")) == NULL)
	{
		perror(file);
		return(False);
	}

	/* gif magic */
	ReadOK(ib, buf, 6);
	if(!(strncmp((char*)buf, "GIF87a", 6)))
	{
		strcpy((char*)buf, "GZF87a");
		buf[6] = '\0';
		WriteOK(fp, buf, 6);
	}
	else if(!(strncmp((char*)buf, "GIF89a", 6)))
	{
		strcpy((char*)buf, "GZF89a");
		buf[6] = '\0';
		WriteOK(fp, buf, 6);
	}
	else
	{
		fprintf(stderr, "Error: %s is not GIF87a or GIF89a\n", ib->file);
		fclose(fp);
		unlink(file);
		return(False);
	}

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

	if(BitSet(buf[4], LOCALCOLORMAP))
	{
		/* colormap has this many entries of 3 bytes */
		doColormap(ib, fp, 2<<(buf[4]&0x07));
	}

	while(True && !done)
	{
		/* block identifier */
		if(!ReadOK(ib,&c,1))
		{
			fprintf(stderr, "Failed to read block identifier.\n");
			done = -1;
			break;
		}
		/* save it */
		WriteOK(fp, &c, 1);

		/* GIF terminator */
		if(c == ';')
		{
#ifdef DEBUG
			fprintf(stderr, "Got GIF terminator.\n");
#endif
			done = 1;
			break;
		}

		/* Extension */
		if(c == '!')
		{
			/* error */
			if(!ReadOK(ib,&c,1))
			{
				done = -1;
				fprintf(stderr, "Failed to read extension type.\n");
				break;
			}
			WriteOK(fp, &c, 1);

			while((i = GifGetDataBlock(ib, (Byte*) buf)) > 0)
			{
				WriteOK(fp, &i, 1);
				WriteOK(fp, buf, i);
			}
			/* and write zero block terminator */
			c = 0;
			WriteOK(fp, &c, 1);
			continue;
		}

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

		/* image descriptor */
		if(!ReadOK(ib,buf,9))
		{
			/* error */
			done = -1;
			fprintf(stderr, "Failed to read image descriptor\n");
			break;
		}
		WriteOK(fp, buf, 9);

		/* we have a local colormap */
		if(BitSet(buf[8], LOCALCOLORMAP))
			doColormap(ib, fp, 1<<((buf[8]&0x07)+1));

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

		/* and convert the image data */
		if((image = convertLZW(ib, fp, &i)) != NULL)
		{
#ifdef DEBUG
			fprintf(stderr, "convertLZW: bytes expected: %i, retrieved: %i\n",
					w*h, i);
#else
			if(w*h != i)
				fprintf(stderr, "Warning: LZWUncompress returned %i bytes "
					"instead of %i.\n", i, w*h);
#endif
			writeImage(image, fp, i);
			/* and free image data */
			free(image);
			if(progress)
			{
				if(imageCount)	/* move cursor back by 8 positions */
					printf("\b\b\b\b\b\b\b\b");
				printf("%8i", imageCount++);
				fflush(stdout);
			}
		}
		else
			done = -1;
	}
	/* close output file */
	fclose(fp);

	/* and remove if we had an error */
	if(done == -1)
	{
		fprintf(stderr, "Error: %s is a corrupt GIF file. Cannot convert.\n",
			ib->file);
		unlink(file);
		return(False);
	}
	return(True);
}

int
main(int argc, char **argv)
{
	ImageBuffer *ib;
	FILE *f;
	int i;

	verify = progress = False;

	if(argc < 3)
	{
#ifdef DEBUG
		fprintf(stderr, "Usage: gif2gzf <infile> <outfile> [-v -p] [-d 1,2]\n");
#else
		fprintf(stderr, "%s: GIF to GZF converter\n"
			"Usage: gif2gzf <infile> <outfile> [-v -p]\n\n"
			"\tinfile  : GIF input file, required\n"
			"\toutfile : GZF output file, required\n"
			"\t-v      : verify zlib compression\n"
			"\t-p      : progress meter, handy for converting animations\n\n"
			"gif2gf is (C)Copyright 1997 by Ripley Software Development\n",
			argv[0]);
#endif
		exit(EXIT_FAILURE);
	}

	/* check args */
	for(i = 3; i < argc; i++)
	{
		if(argv[i][0] == '-')
			switch(argv[i][1])
			{
				case 'v':
					verify = True;
					break;
				case 'p':
					progress = True;
					break;
#ifdef DEBUG
				case 'd':
					i++;
					if(i >= argc)
					{
						fprintf(stderr, "Error: -d requires an argument\n");
						exit(EXIT_FAILURE);
					}
					lzw_debug = atoi(argv[i]);
					break;
#endif
				default:
					break;
			}
	}

	if((ib = ImageFileToBuffer(argv[1])) == NULL)
	{
		exit(EXIT_FAILURE);
	}

	printf("%s -> %s\n", argv[1], argv[2]);
	fflush(stdout);

	if(progress)
	{
		printf("Converted frame: ");
		fflush(stdout);
	}

	/* and convert it */
	if(GifToGzf(ib, argv[2]))
	{
		int osize = 0;

		if(progress) putchar('\n');
		/* get size increase/reduction */
		f = fopen(argv[2], "r");
		fseek(f, 0L, SEEK_END);
		osize = ftell(f);
		fclose(f);
		printf("Size reduction: %5.2f%%\n",
			(1 - (float)(osize/(float)ib->size)) * 100);
	}
	/* release buffer */
	FreeImageBuffer(ib);

	return(EXIT_SUCCESS);
}
#else

int
main(int argc, char **argv)
{
	printf("gif2gzf: cannot convert image %s to type GZF\n", argv[1]);
	printf("         libz not enabled when this program was build.\n");
	return(EXIT_FAILURE);
}

#endif
