Solid Fluid System Solutions  
Home Software About Hardware Firmware
Document Icon LibGIF
Document Icon LibGif Header
Current Document Icon LibGif Source
Document Icon ToGIF / FromGIF Common
Document Icon ToGIF
Document Icon FromGIF

The GIF.c source file

/*
 *  Cross platform GIF source code.
 *
 *  Platform: Neutral
 *
 *  Version: 2.30  1997/07/07  Original version by Lachlan Patrick.
 *  Version: 2.35  1998/09/09  Minor upgrade to list functions.
 *  Version: 2.50  2000/01/01  Added the ability to load an animated gif.
 *  Version: 3.00  2001/03/03  Fixed a few bugs and updated the interface.
 *  Version: 3.34  2002/12/18  Debugging code is now better encapsulated.
 *  Version: 3.56  2005/08/09  Silenced a size_t conversion warning.
 *  Version: 3.60  2007/06/06  Fixed a memory leak in del_gif.
 */

/* Copyright (c) L. Patrick

   This file is part of the App cross-platform programming package.
   You may redistribute it and/or modify it under the terms of the
   App Software License. See the file LICENSE.TXT for details.
*/

/*
 *  Gif.c - Cross-platform code for loading and saving GIFs
 *
 *  The LZW encoder and decoder used in this file were
 *  written by Gershon Elber and Eric S. Raymond as part of
 *  the GifLib package.
 *
 *  The remainder of the code was written by Lachlan Patrick
 *  as part of the GraphApp cross-platform graphics library.
 *
 *  GIF(sm) is a service mark property of CompuServe Inc.
 *  For better compression and more features than GIF,
 *  use PNG: the Portable Network Graphics format.
 */

/*
 *  Copyright and patent information:
 *
 *  Because the LZW algorithm has been patented by
 *  CompuServe Inc, you probably can't use this file
 *  in a commercial application without first paying
 *  CompuServe the appropriate licensing fee.
 *  Contact CompuServe for more information about that.
 */

/*
 *  Known problems with this code:
 *
 *  There is really only one thing to watch out for:
 *  on a PC running a 16-bit operating system, such
 *  as Windows 95 or Windows 3.1, there is a 64K limit
 *  to the size of memory blocks. This may limit the
 *  size of GIF files you can load, perhaps to less
 *  than 256 pixels x 256 pixels. The new row pointer
 *  technique used in this version of this file should
 *  remove that limitation, but you should test this
 *  on your system before believing me.
 */

#include <stdio.h>
#include <string.h>
#include <ctype.h>

#include "app.h"
#include "gif.h"

/*
 *  GIF memory allocation helper functions.
 */

void * gif_alloc(long bytes)
{
	return app_zero_alloc(bytes);
}

/*
 *  GIF file input/output functions.
 */

static unsigned char read_byte(FILE *file)
{
	int ch = getc(file);
	if (ch == EOF)
		ch = 0;
	return ch;
}

static int write_byte(FILE *file, int ch)
{
	return putc(ch, file);
}

static int read_stream(FILE *file, unsigned char buffer[], int length)
{
	int count = (int) fread(buffer, 1, length, file);
	int i = count;
	while (i < length)
		buffer[i++] = '\0';
	return count;
}

static int write_stream(FILE *file, unsigned char buffer[], int length)
{
	return (int) fwrite(buffer, 1, length, file);
}

int read_gif_int(FILE *file)
{
	int output;
	unsigned char buf[2];

	if (fread(buf, 1, 2, file) != 2)
		return 0;
	output = (((unsigned int) buf[1]) << 8) | buf[0];
	return output;
}

void write_gif_int(FILE *file, int output)
{
	putc ( (output & 0xff), file);
	putc ( (((unsigned int) output) >> 8) & 0xff, file);
}

/*
 *  Gif data blocks:
 */

GifData * new_gif_data(int size)
{
	GifData *data = gif_alloc(sizeof(GifData));
	if (data) {
		data->byte_count = size;
		data->bytes = app_zero_alloc(size * sizeof(unsigned char));
	}
	return data;
}

void del_gif_data(GifData *data)
{
	app_free(data->bytes);
	app_free(data);
}

/*
 *  Read one code block from the Gif file.
 *  This routine should be called until NULL is returned.
 *  Use app_free() to free the returned array of bytes.
 */
GifData * read_gif_data(FILE *file)
{
	GifData *data;
	int size;

	size = read_byte(file);

	if (size > 0) {
		data = new_gif_data(size);
		read_stream(file, data->bytes, size);
	}
	else {
		data = NULL;
	}
	return data;
}

/*
 *  Write a Gif data block to a file.
 *  A Gif data block is a size-byte followed by that many
 *  bytes of data (0 to 255 of them).
 */
void write_gif_data(FILE *file, GifData *data)
{
	if (data) {
		write_byte(file, data->byte_count);
		write_stream(file, data->bytes, data->byte_count);
	}
	else
		write_byte(file, 0);
}

#ifdef GIF_DEBUG
void print_gif_data(FILE *file, GifData *data)
{
	int i, ch, prev;
	int ch_printable, prev_printable;

	if (data) {
		fprintf(file, "(length=%d) [", data->byte_count);
		prev_printable = 1;
		for (i=0; i < data->byte_count; i++) {
			ch = data->bytes[i];
			ch_printable = isprint(ch) ? 1 : 0;

			if (ch_printable != prev_printable)
				fprintf(file, " ");

			if (ch_printable)
				fprintf(file, "%c", (char)ch);
			else
				fprintf(file, "%02X,", ch);

			prev = ch;
			prev_printable = isprint(prev) ? 1 : 0;
		}
		fprintf(file, "]\n");
	}
	else {
		fprintf(file, "[]\n");
	}
}
#endif

/*
 *  Read the next byte from a Gif file.
 *
 *  This function is aware of the block-nature of Gif files,
 *  and will automatically skip to the next block to find
 *  a new byte to read, or return 0 if there is no next block.
 */
static unsigned char read_gif_byte(FILE *file, GifDecoder *decoder)
{
	unsigned char *buf = decoder->buf;
	unsigned char next;

	if (decoder->file_state == IMAGE_COMPLETE)
		return '\0';

	if (decoder->position == decoder->bufsize)
	{	/* internal buffer now empty! */
		/* read the block size */
		decoder->bufsize = read_byte(file);
		if (decoder->bufsize == 0) {
			decoder->file_state = IMAGE_COMPLETE;
			return '\0';
		}
		read_stream(file, buf, decoder->bufsize);
		next = buf[0];
		decoder->position = 1;	/* where to get chars */
	}
	else {
		next = buf[decoder->position++];
	}

	return next;
}

/*
 *  Read to end of an image, including the zero block.
 */
static void finish_gif_picture(FILE *file, GifDecoder *decoder)
{
	unsigned char *buf = decoder->buf;

	while (decoder->bufsize != 0) {
		decoder->bufsize = read_byte(file);
		if (decoder->bufsize == 0) {
			decoder->file_state = IMAGE_COMPLETE;
			break;
		}
		read_stream(file, buf, decoder->bufsize);
	}
}

/*
 *  Write a byte to a Gif file.
 *
 *  This function is aware of Gif block structure and buffers
 *  chars until 255 can be written, writing the size byte first.
 *  If FLUSH_OUTPUT is the char to be written, the buffer is
 *  written and an empty block appended.
 */
static void write_gif_byte(FILE *file, GifEncoder *encoder, int ch)
{
	unsigned char *buf = encoder->buf;

	if (encoder->file_state == IMAGE_COMPLETE)
		return;

	if (ch == FLUSH_OUTPUT)
	{
		if (encoder->bufsize) {
			write_byte(file, encoder->bufsize);
			write_stream(file, buf, encoder->bufsize);
			encoder->bufsize = 0;
		}
		/* write an empty block to mark end of data */
		write_byte(file, 0);
		encoder->file_state = IMAGE_COMPLETE;
	}
	else {
		if (encoder->bufsize == 255) {
			/* write this buffer to the file */
			write_byte(file, encoder->bufsize);
			write_stream(file, buf, encoder->bufsize);
			encoder->bufsize = 0;
		}
		buf[encoder->bufsize++] = ch;
	}
}

/*
 *  Colour maps:
 */

GifPalette * new_gif_palette(void)
{
	return gif_alloc(sizeof(GifPalette));
}

void del_gif_palette(GifPalette *cmap)
{
	app_free(cmap->colours);
	app_free(cmap);
}

void read_gif_palette(FILE *file, GifPalette *cmap)
{
	int i;
	unsigned char r, g, b;

	cmap->colours = app_alloc(cmap->length * sizeof(Colour));

	for (i=0; i<cmap->length; i++) {
		r = read_byte(file);
		g = read_byte(file);
		b = read_byte(file);
		cmap->colours[i] = rgb(r,g,b);
	}
}

void write_gif_palette(FILE *file, GifPalette *cmap)
{
	int i;
	Colour c;

	for (i=0; i<cmap->length; i++) {
		c = cmap->colours[i];
		write_byte(file, c.red);
		write_byte(file, c.green);
		write_byte(file, c.blue);
	}
}

#ifdef GIF_DEBUG
void print_gif_palette(FILE *file, GifPalette *cmap)
{
	int i;

	fprintf(file, "  GifPalette (length=%d):\n", cmap->length);
	for (i=0; i<cmap->length; i++) {
		fprintf(file, "   %02X = 0x", i);
		fprintf(file, "%02X", cmap->colours[i].red);
		fprintf(file, "%02X", cmap->colours[i].green);
		fprintf(file, "%02X\n", cmap->colours[i].blue);
	}
}
#endif

/*
 *  GifScreen:
 */

GifScreen * new_gif_screen(void)
{
	GifScreen *screen = gif_alloc(sizeof(GifScreen));
	if (screen)
		screen->cmap = new_gif_palette();
	return screen;
}

void del_gif_screen(GifScreen *screen)
{
	del_gif_palette(screen->cmap);
	app_free(screen);
}

void read_gif_screen(FILE *file, GifScreen *screen)
{
	unsigned char info;

	screen->width       = read_gif_int(file);
	screen->height      = read_gif_int(file);

	info                = read_byte(file);
	screen->has_cmap    =  (info & 0x80) >> 7;
	screen->color_res   = ((info & 0x70) >> 4) + 1;
	screen->sorted      =  (info & 0x08) >> 3;
	screen->cmap_depth  =  (info & 0x07)       + 1;

	screen->bgcolour    = read_byte(file);
	screen->aspect      = read_byte(file);

	if (screen->has_cmap) {
		screen->cmap->length = 1 << screen->cmap_depth;
		read_gif_palette(file, screen->cmap);
	}
}

void write_gif_screen(FILE *file, GifScreen *screen)
{
	unsigned char info;

	write_gif_int(file, screen->width);
	write_gif_int(file, screen->height);

	info = 0;
	info = info | (screen->has_cmap ? 0x80 : 0x00);
	info = info | ((screen->color_res - 1) << 4);
	info = info | (screen->sorted ? 0x08 : 0x00);
	if (screen->cmap_depth > 0)
		info = info | ((screen->cmap_depth) - 1);
	write_byte(file, info);

	write_byte(file, screen->bgcolour);
	write_byte(file, screen->aspect);

	if (screen->has_cmap) {
		write_gif_palette(file, screen->cmap);
	}
}

#ifdef GIF_DEBUG
void print_gif_screen(FILE *file, GifScreen *screen)
{
	fprintf(file, " GifScreen:\n");
	fprintf(file, "  width      = %d\n", screen->width);
	fprintf(file, "  height     = %d\n", screen->height);

	fprintf(file, "  has_cmap   = %d\n", screen->has_cmap ? 1:0);
	fprintf(file, "  color_res  = %d\n", screen->color_res);
	fprintf(file, "  sorted     = %d\n", screen->sorted ? 1:0);
	fprintf(file, "  cmap_depth = %d\n", screen->cmap_depth);

	fprintf(file, "  bgcolour   = %02X\n", screen->bgcolour);
	fprintf(file, "  aspect     = %d\n", screen->aspect);

	if (screen->has_cmap) {
		print_gif_palette(file, screen->cmap);
	}
}
#endif

/*
 *  GifExtension:
 */

GifExtension *new_gif_extension(void)
{
	return gif_alloc(sizeof(GifExtension));
}

void del_gif_extension(GifExtension *ext)
{
	int i;

	for (i=0; i < ext->data_count; i++)
		del_gif_data(ext->data[i]);
	app_free(ext->data);
	app_free(ext);
}

void read_gif_extension(FILE *file, GifExtension *ext)
{
	GifData *data;
	int i;

	ext->marker = read_byte(file);

	data = read_gif_data(file);
	while (data) {
		/* Append the data object: */
		i = ++ext->data_count;
		ext->data = app_realloc(ext->data, i * sizeof(GifData *));
		ext->data[i-1] = data;
		data = read_gif_data(file);
	}
}

void write_gif_extension(FILE *file, GifExtension *ext)
{
	int i;

	write_byte(file, ext->marker);

	for (i=0; i < ext->data_count; i++)
		write_gif_data(file, ext->data[i]);
	write_gif_data(file, NULL);
}

#ifdef GIF_DEBUG
void print_gif_extension(FILE *file, GifExtension *ext)
{
	int i;

	fprintf(file, " GifExtension:\n");
	fprintf(file, "  marker = 0x%02X\n", ext->marker);
	for (i=0; i < ext->data_count; i++) {
		fprintf(file, "  data = ");
		print_gif_data(file, ext->data[i]);
	}
}
#endif

/*
 *  GifDecoder:
 */

GifDecoder * new_gif_decoder(void)
{
	return gif_alloc(sizeof(GifDecoder));
}

void del_gif_decoder(GifDecoder *decoder)
{
	app_free(decoder);
}

void init_gif_decoder(FILE *file, GifDecoder *decoder)
{
	int i, depth;
	int lzw_min;
	unsigned int *prefix;

	lzw_min = read_byte(file);
	depth = lzw_min;

	decoder->file_state   = IMAGE_LOADING;
	decoder->position     = 0;
	decoder->bufsize      = 0;
	decoder->buf[0]       = 0;
	decoder->depth        = depth;
	decoder->clear_code   = (1 << depth);
	decoder->eof_code     = decoder->clear_code + 1;
	decoder->running_code = decoder->eof_code + 1;
	decoder->running_bits = depth + 1;
	decoder->max_code_plus_one = 1 << decoder->running_bits;
	decoder->stack_ptr    = 0;
	decoder->prev_code    = NO_SUCH_CODE;
	decoder->shift_state  = 0;
	decoder->shift_data   = 0;

	prefix = decoder->prefix;
	for (i = 0; i <= LZ_MAX_CODE; i++)
		prefix[i] = NO_SUCH_CODE;
}

/*
 *  Read the next Gif code word from the file.
 *
 *  This function looks in the decoder to find out how many
 *  bits to read, and uses a buffer in the decoder to remember
 *  bits from the last byte input.
 */
int read_gif_code(FILE *file, GifDecoder *decoder)
{
	int code;
	unsigned char next_byte;
	static int code_masks[] = {
		0x0000, 0x0001, 0x0003, 0x0007,
		0x000f, 0x001f, 0x003f, 0x007f,
		0x00ff, 0x01ff, 0x03ff, 0x07ff,
		0x0fff
	};

	while (decoder->shift_state < decoder->running_bits)
	{
		/* Need more bytes from input file for next code: */
		next_byte = read_gif_byte(file, decoder);
		decoder->shift_data |=
		  ((unsigned long) next_byte) << decoder->shift_state;
		decoder->shift_state += 8;
	}

	code = decoder->shift_data & code_masks[decoder->running_bits];

	decoder->shift_data >>= decoder->running_bits;
	decoder->shift_state -= decoder->running_bits;

	/* If code cannot fit into running_bits bits,
	 * we must raise its size.
	 * Note: codes above 4095 are used for signalling. */
	if (++decoder->running_code > decoder->max_code_plus_one
		&& decoder->running_bits < LZ_BITS)
	{
		decoder->max_code_plus_one <<= 1;
		decoder->running_bits++;
	}
	return code;
}

/*
 *  Routine to trace the prefix-linked-list until we get
 *  a prefix which is a pixel value (less than clear_code).
 *  Returns that pixel value.
 *
 *  If the picture is defective, we might loop here forever,
 *  so we limit the loops to the maximum possible if the
 *  picture is okay, i.e. LZ_MAX_CODE times.
 */
static int trace_prefix(unsigned int *prefix, int code, int clear_code)
{
	int i = 0;

	while (code > clear_code && i++ <= LZ_MAX_CODE)
		code = prefix[code];
	return code;
}

/*
 *  The LZ decompression routine:
 *  Call this function once per scanline to fill in a picture.
 */
void read_gif_line(FILE *file, GifDecoder *decoder,
			unsigned char *line, int length)
{
    int i = 0, j;
    int current_code, eof_code, clear_code;
    int current_prefix, prev_code, stack_ptr;
    unsigned char *stack, *suffix;
    unsigned int *prefix;

    prefix	= decoder->prefix;
    suffix	= decoder->suffix;
    stack	= decoder->stack;
    stack_ptr	= decoder->stack_ptr;
    eof_code	= decoder->eof_code;
    clear_code	= decoder->clear_code;
    prev_code	= decoder->prev_code;

    if (stack_ptr != 0) {
	/* Pop the stack */
	while (stack_ptr != 0 && i < length)
		line[i++] = stack[--stack_ptr];
    }

    while (i < length)
    {
	current_code = read_gif_code(file, decoder);

	if (current_code == eof_code)
	{
	   /* unexpected EOF */
	   if (i != length - 1 || decoder->pixel_count != 0)
		return;
	   i++;
	}
	else if (current_code == clear_code)
	{
	    /* reset prefix table etc */
	    for (j = 0; j <= LZ_MAX_CODE; j++)
		prefix[j] = NO_SUCH_CODE;
	    decoder->running_code = decoder->eof_code + 1;
	    decoder->running_bits = decoder->depth + 1;
	    decoder->max_code_plus_one = 1 << decoder->running_bits;
	    prev_code = decoder->prev_code = NO_SUCH_CODE;
	}
	else {
	    /* Regular code - if in pixel range
	     * simply add it to output pixel stream,
	     * otherwise trace code-linked-list until
	     * the prefix is in pixel range. */
	    if (current_code < clear_code) {
		/* Simple case. */
		line[i++] = current_code;
	    }
	    else {
		/* This code needs to be traced:
		 * trace the linked list until the prefix is a
		 * pixel, while pushing the suffix pixels on
		 * to the stack. If finished, pop the stack
		 * to output the pixel values. */
		if ((current_code < 0) || (current_code > LZ_MAX_CODE))
			return; /* image defect */
		if (prefix[current_code] == NO_SUCH_CODE) {
		    /* Only allowed if current_code is exactly
		     * the running code:
		     * In that case current_code = XXXCode,
		     * current_code or the prefix code is the
		     * last code and the suffix char is
		     * exactly the prefix of last code! */
		    if (current_code == decoder->running_code - 2) {
			current_prefix = prev_code;
			suffix[decoder->running_code - 2]
			    = stack[stack_ptr++]
			    = trace_prefix(prefix, prev_code, clear_code);
		    }
		    else {
			return; /* image defect */
		    }
		}
		else
		    current_prefix = current_code;

		/* Now (if picture is okay) we should get
		 * no NO_SUCH_CODE during the trace.
		 * As we might loop forever (if picture defect)
		 * we count the number of loops we trace and
		 * stop if we get LZ_MAX_CODE.
		 * Obviously we cannot loop more than that. */
		j = 0;
		while (j++ <= LZ_MAX_CODE
			&& current_prefix > clear_code
			&& current_prefix <= LZ_MAX_CODE)
		{
		    stack[stack_ptr++] = suffix[current_prefix];
		    current_prefix = prefix[current_prefix];
		}
		if (j >= LZ_MAX_CODE || current_prefix > LZ_MAX_CODE)
		    return; /* image defect */

		/* Push the last character on stack: */
		stack[stack_ptr++] = current_prefix;

		/* Now pop the entire stack into output: */
		while (stack_ptr != 0 && i < length)
		    line[i++] = stack[--stack_ptr];
	    }
	    if (prev_code != NO_SUCH_CODE) {
		if ((decoder->running_code < 2) ||
		  (decoder->running_code > LZ_MAX_CODE+2))
			return; /* image defect */
		prefix[decoder->running_code - 2] = prev_code;

		if (current_code == decoder->running_code - 2) {
		    /* Only allowed if current_code is exactly
		     * the running code:
		     * In that case current_code = XXXCode,
		     * current_code or the prefix code is the
		     * last code and the suffix char is
		     * exactly the prefix of the last code! */
		    suffix[decoder->running_code - 2]
			= trace_prefix(prefix, prev_code, clear_code);
		}
		else {
		    suffix[decoder->running_code - 2]
			= trace_prefix(prefix, current_code, clear_code);
		}
	    }
	    prev_code = current_code;
	}
    }

    decoder->prev_code = prev_code;
    decoder->stack_ptr = stack_ptr;
}

/*
 *  Hash table:
 */

/*
 *  The 32 bits contain two parts: the key & code:
 *  The code is 12 bits since the algorithm is limited to 12 bits
 *  The key is a 12 bit prefix code + 8 bit new char = 20 bits.
 */
#define HT_GET_KEY(x)	((x) >> 12)
#define HT_GET_CODE(x)	((x) & 0x0FFF)
#define HT_PUT_KEY(x)	((x) << 12)
#define HT_PUT_CODE(x)	((x) & 0x0FFF)

/*
 *  Generate a hash key from the given unique key.
 *  The given key is assumed to be 20 bits as follows:
 *    lower 8 bits are the new postfix character,
 *    the upper 12 bits are the prefix code.
 */
static int gif_hash_key(unsigned long key)
{
	return ((key >> 12) ^ key) & HT_KEY_MASK;
}

/*
 *  Clear the hash_table to an empty state.
 */
static void clear_gif_hash_table(unsigned long *hash_table)
{
	int i;
	for (i=0; i<HT_SIZE; i++)
		hash_table[i] = 0xFFFFFFFFL;
}

/*
 *  Insert a new item into the hash_table.
 *  The data is assumed to be new.
 */
static void add_gif_hash_entry(unsigned long *hash_table, unsigned long key, int code)
{
	int hkey = gif_hash_key(key);

	while (HT_GET_KEY(hash_table[hkey]) != 0xFFFFFL) {
		hkey = (hkey + 1) & HT_KEY_MASK;
	}
	hash_table[hkey] = HT_PUT_KEY(key) | HT_PUT_CODE(code);
}

/*
 *  Determine if given key exists in hash_table and if so
 *  returns its code, otherwise returns -1.
 */
static int lookup_gif_hash(unsigned long *hash_table, unsigned long key)
{
	int hkey = gif_hash_key(key);
	unsigned long htkey;

	while ((htkey = HT_GET_KEY(hash_table[hkey])) != 0xFFFFFL) {
		if (key == htkey)
			return HT_GET_CODE(hash_table[hkey]);
		hkey = (hkey + 1) & HT_KEY_MASK;
	}
	return -1;
}

/*
 *  GifEncoder:
 */

GifEncoder *new_gif_encoder(void)
{
	return gif_alloc(sizeof(GifEncoder));
}

void del_gif_encoder(GifEncoder *encoder)
{
	app_free(encoder);
}

/*
 *  Write a Gif code word to the output file.
 *
 *  This function packages code words up into whole bytes
 *  before writing them. It uses the encoder to store
 *  codes until enough can be packaged into a whole byte.
 */
void write_gif_code(FILE *file, GifEncoder *encoder, int code)
{
	if (code == FLUSH_OUTPUT) {
		/* write all remaining data */
		while (encoder->shift_state > 0)
		{
			write_gif_byte(file, encoder,
				encoder->shift_data & 0xff);
			encoder->shift_data >>= 8;
			encoder->shift_state -= 8;
		}
		encoder->shift_state = 0;
		write_gif_byte(file, encoder, FLUSH_OUTPUT);
	}
	else {
		encoder->shift_data |=
			((long) code) << encoder->shift_state;
		encoder->shift_state += encoder->running_bits;

		while (encoder->shift_state >= 8)
		{
			/* write full bytes */
			write_gif_byte(file, encoder,
				encoder->shift_data & 0xff);
			encoder->shift_data >>= 8;
			encoder->shift_state -= 8;
		}
	}

	/* If code can't fit into running_bits bits, raise its size.
	 * Note that codes above 4095 are for signalling. */
	if (encoder->running_code >= encoder->max_code_plus_one
		&& code <= 4095)
	{
    		encoder->max_code_plus_one = 1 << ++encoder->running_bits;
	}
}

/*
 *   Initialise the encoder, given a GifPalette depth.
 */
void init_gif_encoder(FILE *file, GifEncoder *encoder, int depth)
{
	int lzw_min = depth = (depth < 2 ? 2 : depth);

	encoder->file_state   = IMAGE_SAVING;
	encoder->position     = 0;
	encoder->bufsize      = 0;
	encoder->buf[0]       = 0;
	encoder->depth        = depth;
	encoder->clear_code   = (1 << depth);
	encoder->eof_code     = encoder->clear_code + 1;
	encoder->running_code = encoder->eof_code + 1;
	encoder->running_bits = depth + 1;
	encoder->max_code_plus_one = 1 << encoder->running_bits;
	encoder->current_code = FIRST_CODE;
	encoder->shift_state  = 0;
	encoder->shift_data   = 0;

	/* Write the LZW minimum code size: */
	write_byte(file, lzw_min);

	/* Clear hash table, output Clear code: */
	clear_gif_hash_table(encoder->hash_table);
	write_gif_code(file, encoder, encoder->clear_code);
}

/*
 *  Write one scanline of pixels out to the Gif file,
 *  compressing that line using LZW into a series of codes.
 */
void write_gif_line(FILE *file, GifEncoder *encoder, unsigned char *line, int length)
{
    int i = 0, current_code, new_code;
    unsigned long new_key;
    unsigned char pixval;
    unsigned long *hash_table;

    hash_table = encoder->hash_table;

    if (encoder->current_code == FIRST_CODE)
	current_code = line[i++];
    else
	current_code = encoder->current_code;

    while (i < length)
    {
	pixval = line[i++]; /* Fetch next pixel from stream */

	/* Form a new unique key to search hash table for the code
	 * Combines current_code as prefix string with pixval as
	 * postfix char */
	new_key = (((unsigned long) current_code) << 8) + pixval;
	if ((new_code = lookup_gif_hash(hash_table, new_key)) >= 0) {
	    /* This key is already there, or the string is old,
	     * so simply take new code as current_code */
	    current_code = new_code;
	}
	else {
	    /* Put it in hash table, output the prefix code,
	     * and make current_code equal to pixval */
	    write_gif_code(file, encoder, current_code);
	    current_code = pixval;

	    /* If the hash_table if full, send a clear first
	     * then clear the hash table: */
	    if (encoder->running_code >= LZ_MAX_CODE) {
		write_gif_code(file, encoder, encoder->clear_code);
		encoder->running_code = encoder->eof_code + 1;
		encoder->running_bits = encoder->depth + 1;
		encoder->max_code_plus_one = 1 << encoder->running_bits;
		clear_gif_hash_table(hash_table);
	    }
	    else {
		/* Put this unique key with its relative code in hash table */
		add_gif_hash_entry(hash_table, new_key, encoder->running_code++);
	    }
	}
    }

    /* Preserve the current state of the compression algorithm: */
    encoder->current_code = current_code;
}

void flush_gif_encoder(FILE *file, GifEncoder *encoder)
{
	write_gif_code(file, encoder, encoder->current_code);
	write_gif_code(file, encoder, encoder->eof_code);
	write_gif_code(file, encoder, FLUSH_OUTPUT);
}

/*
 *  GifPicture:
 */

GifPicture * new_gif_picture(void)
{
	GifPicture *pic = gif_alloc(sizeof(GifPicture));
	if (pic) {
		pic->cmap = new_gif_palette();
		pic->data = NULL;
	}
	return pic;
}

void del_gif_picture(GifPicture *pic)
{
	int row;

	del_gif_palette(pic->cmap);
	if (pic->data) {
		for (row=0; row < pic->height; row++)
			app_free(pic->data[row]);
		app_free(pic->data);
	}
	app_free(pic);
}

static void read_gif_picture_data(FILE *file, GifPicture *pic)
{
	GifDecoder *decoder;
	long w, h;
	int interlace_start[] = {0, 4, 2, 1};
	int interlace_step[]  = {8, 8, 4, 2};
	int scan_pass, row;

	w = pic->width;
	h = pic->height;
	pic->data = app_alloc(h * sizeof(unsigned char *));
	if (pic->data == NULL)
		return;
	for (row=0; row < h; row++)
		pic->data[row] = app_zero_alloc(w * sizeof(unsigned char));

	decoder = new_gif_decoder();
	init_gif_decoder(file, decoder);

	if (pic->interlace) {
	  for (scan_pass = 0; scan_pass < 4; scan_pass++) {
	    row = interlace_start[scan_pass];
	    while (row < h) {
	      read_gif_line(file, decoder, pic->data[row], w);
	      row += interlace_step[scan_pass];
	    }
	  }
	}
	else {
	  row = 0;
	  while (row < h) {
	    read_gif_line(file, decoder, pic->data[row], w);
	    row += 1;
	  }
	}
	finish_gif_picture(file, decoder);

	del_gif_decoder(decoder);
}

void read_gif_picture(FILE *file, GifPicture *pic)
{
	unsigned char info;

	pic->left   = read_gif_int(file);
	pic->top    = read_gif_int(file);
	pic->width  = read_gif_int(file);
	pic->height = read_gif_int(file);

	info = read_byte(file);
	pic->has_cmap    = (info & 0x80) >> 7;
	pic->interlace   = (info & 0x40) >> 6;
	pic->sorted      = (info & 0x20) >> 5;
	pic->reserved    = (info & 0x18) >> 3;

	if (pic->has_cmap) {
		pic->cmap_depth  = (info & 0x07) + 1;
		pic->cmap->length = 1 << pic->cmap_depth;
		read_gif_palette(file, pic->cmap);
	}

	read_gif_picture_data(file, pic);
}

static void write_gif_picture_data(FILE *file, GifPicture *pic)
{
	GifEncoder *encoder;
	long w, h;
	int interlace_start[] = {0, 4, 2, 1};
	int interlace_step[]  = {8, 8, 4, 2};
	int scan_pass, row;

	w = pic->width;
	h = pic->height;

	encoder = new_gif_encoder();
	init_gif_encoder(file, encoder, pic->cmap_depth);

	if (pic->interlace) {
	  for (scan_pass = 0; scan_pass < 4; scan_pass++) {
	    row = interlace_start[scan_pass];
	    while (row < h) {
	      write_gif_line(file, encoder, pic->data[row], w);
	      row += interlace_step[scan_pass];
	    }
	  }
	}
	else {
	  row = 0;
	  while (row < h) {
	    write_gif_line(file, encoder, pic->data[row], w);
	    row += 1;
	  }
	}

	flush_gif_encoder(file, encoder);
	del_gif_encoder(encoder);
}

void write_gif_picture(FILE *file, GifPicture *pic)
{
	unsigned char info;

	write_gif_int(file, pic->left);
	write_gif_int(file, pic->top);
	write_gif_int(file, pic->width);
	write_gif_int(file, pic->height);

	info = 0;
	info = info | (pic->has_cmap    ? 0x80 : 0x00);
	info = info | (pic->interlace   ? 0x40 : 0x00);
	info = info | (pic->sorted      ? 0x20 : 0x00);
	info = info | ((pic->reserved << 3) & 0x18);
	if (pic->has_cmap)
		info = info | (pic->cmap_depth - 1);

	write_byte(file, info);

	if (pic->has_cmap)
		write_gif_palette(file, pic->cmap);

	write_gif_picture_data(file, pic);
}

#ifdef GIF_DEBUG
static void print_gif_picture_data(FILE *file, GifPicture *pic)
{
	int pixval, row, col;

	for (row = 0; row < pic->height; row++) {
	  fprintf(file, "   [");
	  for (col = 0; col < pic->width; col++) {
	    pixval = pic->data[row][col];
	    fprintf(file, "%02X", pixval);
	  }
	  fprintf(file, "]\n");
	}
}

static void print_gif_picture_header(FILE *file, GifPicture *pic)
{
	fprintf(file, " GifPicture:\n");
	fprintf(file, "  left       = %d\n", pic->left);
	fprintf(file, "  top        = %d\n", pic->top);
	fprintf(file, "  width      = %d\n", pic->width);
	fprintf(file, "  height     = %d\n", pic->height);

	fprintf(file, "  has_cmap   = %d\n", pic->has_cmap);
	fprintf(file, "  interlace  = %d\n", pic->interlace);
	fprintf(file, "  sorted     = %d\n", pic->sorted);
	fprintf(file, "  reserved   = %d\n", pic->reserved);
	fprintf(file, "  cmap_depth = %d\n", pic->cmap_depth);
}

void print_gif_picture(FILE *file, GifPicture *pic)
{
	print_gif_picture_header(file, pic);

	if (pic->has_cmap)
		print_gif_palette(file, pic->cmap);

	print_gif_picture_data(file, pic);
}
#endif

/*
 *  GifBlock:
 */

GifBlock *new_gif_block(void)
{
	return gif_alloc(sizeof(GifBlock));
}

void del_gif_block(GifBlock *block)
{
	if (block->pic)
		del_gif_picture(block->pic);
	if (block->ext)
		del_gif_extension(block->ext);
	app_free(block);
}

void read_gif_block(FILE *file, GifBlock *block)
{
	block->intro = read_byte(file);
	if (block->intro == 0x2C) {
		block->pic = new_gif_picture();
		read_gif_picture(file, block->pic);
	}
	else if (block->intro == 0x21) {
		block->ext = new_gif_extension();
		read_gif_extension(file, block->ext);
	}
}

void write_gif_block(FILE *file, GifBlock *block)
{
	write_byte(file, block->intro);
	if (block->pic)
		write_gif_picture(file, block->pic);
	if (block->ext)
		write_gif_extension(file, block->ext);
}

#ifdef GIF_DEBUG
void print_gif_block(FILE *file, GifBlock *block)
{
	fprintf(file, " GifBlock (intro=0x%02X):\n", block->intro);
	if (block->pic)
		print_gif_picture(file, block->pic);
	if (block->ext)
		print_gif_extension(file, block->ext);
}
#endif

/*
 *  Gif:
 */

Gif * new_gif(void)
{
	Gif *gif = gif_alloc(sizeof(Gif));
	if (gif) {
		strcpy(gif->header, "GIF87a");
		gif->screen = new_gif_screen();
		gif->blocks = NULL;
	}
	return gif;
}

void del_gif(Gif *gif)
{
	int i;

	del_gif_screen(gif->screen);
	for (i=0; i < gif->block_count; i++)
		del_gif_block(gif->blocks[i]);
	app_free(gif->blocks);
	app_free(gif);
}

void read_gif(FILE *file, Gif *gif)
{
	int i;
	GifBlock *block;

	for (i=0; i<6; i++)
		gif->header[i] = read_byte(file);
	if (strncmp(gif->header, "GIF", 3) != 0)
		return; /* error */

	read_gif_screen(file, gif->screen);

	while (1) {
		block = new_gif_block();
		read_gif_block(file, block);

		if (block->intro == 0x3B) {	/* terminator */
			del_gif_block(block);
			break;
		}
		else  if (block->intro == 0x2C) {	/* image */
			/* Append the block: */
			i = ++gif->block_count;
			gif->blocks = app_realloc(gif->blocks, i * sizeof(GifBlock *));
			gif->blocks[i-1] = block;
		}
		else  if (block->intro == 0x21) {	/* extension */
			/* Append the block: */
			i = ++gif->block_count;
			gif->blocks = app_realloc(gif->blocks, i * sizeof(GifBlock *));
			gif->blocks[i-1] = block;
		}
		else {	/* error */
			del_gif_block(block);
			break;
		}
	}
}

void read_one_gif_picture(FILE *file, Gif *gif)
{
	int i;
	GifBlock *block;

	for (i=0; i<6; i++)
		gif->header[i] = read_byte(file);
	if (strncmp(gif->header, "GIF", 3) != 0)
		return; /* error */

	read_gif_screen(file, gif->screen);

	while (1) {
		block = new_gif_block();
		read_gif_block(file, block);

		if (block->intro == 0x3B) {	/* terminator */
			del_gif_block(block);
			break;
		}
		else if (block->intro == 0x2C) { /* image */
			/* Append the block: */
			i = ++gif->block_count;
			gif->blocks = app_realloc(gif->blocks, i * sizeof(GifBlock *));
			gif->blocks[i-1] = block;
			break;
		}
		else if (block->intro == 0x21) { /* extension */
			/* Append the block: */
			i = ++gif->block_count;
			gif->blocks = app_realloc(gif->blocks, i * sizeof(GifBlock *));
			gif->blocks[i-1] = block;
			continue;
		}
		else {	/* error! */
			del_gif_block(block);
			break;
		}
	}
}

void write_gif(FILE *file, Gif *gif)
{
	int i;

	fprintf(file, "%s", gif->header);
	write_gif_screen(file, gif->screen);
	for (i=0; i < gif->block_count; i++)
		write_gif_block(file, gif->blocks[i]);
	write_byte(file, 0x3B);
}

#ifdef GIF_DEBUG
void print_gif(FILE *file, Gif *gif)
{
	int i;

	fprintf(file, "Gif header=%s\n", gif->header);
	print_gif_screen(file, gif->screen);
	for (i=0; i < gif->block_count; i++)
		print_gif_block(file, gif->blocks[i]);
	fprintf(file, "End of gif.\n\n");
}
#endif

/*
 *  Reading and Writing Gif files:
 */

Gif * read_gif_file(const char *filename)
{
	Gif *gif;
	FILE *file;

	file = app_open_file(filename, "rb");
	if (file == NULL)
		return NULL;
	gif = new_gif();
	if (gif == NULL) {
		app_close_file(file);
		return NULL;
	}
	read_gif(file, gif);
	app_close_file(file);
	if (strncmp(gif->header, "GIF", 3) != 0) {
		del_gif(gif);
		gif = NULL;
	}
	return gif;
}

void write_gif_file(const char *filename, Gif *gif)
{
	FILE *file;

	file = app_open_file(filename, "wb");
	if (file == NULL)
		return;
	if (gif == NULL) {
		app_close_file(file);
		return;
	}
	write_gif(file, gif);
	app_close_file(file);
}
  
Copyright © Solid Fluid 2007-2022
Last modified: SolFlu  Sun, 14 Jun 2009 09:22:03 GMT