Blame | Last modification | View Log | Download | RSS feed
/** GraphApp - Cross-Platform Graphics Programming Library.** File: gif.c -- cross platform GIF source code.* Platform: Neutral Version: 2.35 Date: 1998/03/03** Version: 2.30 Changes: Original version by Lachlan Patrick.*//* Copyright (C) 1993-1998 Lachlan PatrickThis file is part of GraphApp, a cross-platform C graphics library.GraphApp is free software; you can redistribute it and/or modify itunder the terms of the GNU Library General Public License.GraphApp is distributed in the hope that it will be useful, butWITHOUT ANY WARRANTY.See the file COPYLIB.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. In practice this* limits the size of GIF files you can load to less* than 256 pixels x 256 pixels.*/#include <stdio.h>#include <string.h>#include "internal.h"/** Unsigned types defined for convenience.*/#ifndef _GRAPHAPP_Htypedef unsigned char byte;#endif/** Colours.*/#ifndef rgbtypedef unsigned long rgb;#define rgb(r,g,b) ( (((rgb)r)<<16)|(((rgb)g)<<8)|((rgb)b) )#define getalpha(col) (((col) & 0xFF000000UL) >> 24)#define getred(col) (((col) & 0x00FF0000UL) >> 16)#define getgreen(col) (((col) & 0x0000FF00UL) >> 8)#define getblue(col) (((col) & 0x000000FFUL))#endif /* rgb definition *//** Platform independent image data:*/#ifndef _GRAPHAPP_Htypedef struct imagedata {int depth;int width;int height;int cmapsize;rgb * cmap;byte * pixels;} * image;extern image convert32to8(image img);extern void delimage(image img);#endif#ifndef array#define create(type) ( (type*) memalloc(sizeof(type)) )#define array(n,type) ( (type*) memalloc(n*sizeof(type)) )#define len(a) ( memlength((char*)(a))/sizeof((a)[0]) )#define element(a,i) ( (((i)<len(a)) && ((i)>=0)) ? (a)[i] : 0 )#define append(a,e) ( *(char**)&(a)=memexpand((char*)(a),sizeof((a)[0])),\(a)[len(a)-1]=(e) )#define join(a,b) ( *(char**)&(a)=memjoin((char*)(a),(char*)(b)) )#define discard(a) ( memfree((char*)(a)), (a)=0 )char * memalloc(long size);void memfree(char *a);long memlength(char *a);char * memexpand(char *a, long extra);char * memjoin(char *a, char *b);#endif/** Gif structures:*/typedef struct {int length;rgb * data;} rgbmap;typedef struct {int width, height;int has_cmap, color_res, sorted, cmap_depth;int bgcolor, aspect;rgbmap * cmap;} GifScreen;typedef struct {int marker;byte ** data;} GifExtension;typedef struct {int left, top, width, height;int has_cmap, interlace, sorted, reserved, cmap_depth;rgbmap * cmap;byte * data;} GifPicture;typedef struct {int intro;GifPicture * pic;GifExtension * ext;} GifBlock;typedef struct {char header[8];GifScreen * screen;GifBlock ** blocks;} Gif;/** Gif internal definitions:*/#define LZ_MAX_CODE 4095 /* Largest 12 bit code */#define LZ_BITS 12#define FLUSH_OUTPUT 4096 /* Impossible code = flush */#define FIRST_CODE 4097 /* Impossible code = first */#define NO_SUCH_CODE 4098 /* Impossible code = empty */#define HT_SIZE 8192 /* 13 bit hash table size */#define HT_KEY_MASK 0x1FFF /* 13 bit key mask */#define IMAGE_LOADING 0 /* file_state = processing */#define IMAGE_SAVING 0 /* file_state = processing */#define IMAGE_COMPLETE 1 /* finished reading or writing */typedef struct {FILE *file;int depth,clear_code, eof_code,running_code, running_bits,max_code_plus_one,prev_code, current_code,stack_ptr,shift_state;unsigned long shift_data;unsigned long pixel_count;int file_state, position, bufsize;byte buf[256];unsigned long hash_table[HT_SIZE];} GifEncoder;typedef struct {FILE *file;int depth,clear_code, eof_code,running_code, running_bits,max_code_plus_one,prev_code, current_code,stack_ptr,shift_state;unsigned long shift_data;unsigned long pixel_count;int file_state, position, bufsize;byte buf[256];byte stack[LZ_MAX_CODE+1];byte suffix[LZ_MAX_CODE+1];unsigned int prefix[LZ_MAX_CODE+1];} GifDecoder;/** GIF file input/output functions.*/static unsigned char read_byte(FILE *file){int ch = getc(file);if (ch == EOF)ch = 0;return ch;}#define write_byte(file, ch) putc(ch, file)static void read_stream(FILE *file, byte buffer[], int length){int count = fread(buffer, 1, length, file);while (count < length)buffer[count++] = '\0';}#define write_stream(file, buf, len) fwrite((buf),1,(int)(len),(file))static 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;}static void write_gif_int(FILE *file, int output){putc ( (output & 0xff), file);putc ( (((unsigned int) output) >> 8) & 0xff, file);}/** Gif data blocks:*/#define new_gif_data(size) array((size), byte)#define del_gif_data(data) discard(data)/** Read one code block from the Gif file.* This routine should be called until NULL is returned.* Use discard() to free the returned array of bytes.*/static byte * read_gif_data(FILE *file){byte *data;int size;size = read_byte(file);if (size > 0) {data = new_gif_data(size);read_stream(file, data, 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).*/static void write_gif_data(FILE *file, byte *data){if (data) {write_byte(file, len(data));write_stream(file, data, len(data));}elsewrite_byte(file, 0);}#if DEBUGstatic void print_gif_data(FILE *file, byte *data){int i, ch, prev;int ch_printable, prev_printable;if (data) {fprintf(file, "(length=%d) [", len(data));prev_printable = 1;for (i=0; i < len(data); i++) {ch = data[i];ch_printable = isprint(ch) ? 1 : 0;if (ch_printable != prev_printable)fprintf(file, " ");if (ch_printable)fprintf(file, "%c", (char)ch);elsefprintf(file, "%-02.2X,", 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 byte read_gif_byte(FILE *file, GifDecoder *decoder){byte *buf = decoder->buf;byte 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){byte *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){byte *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:*/#define new_rgbmap() create(rgbmap)static void del_rgbmap(rgbmap *cmap){discard(cmap->data);discard(cmap);}static void read_rgbmap(FILE *file, rgbmap *cmap){int i;byte r, g, b;for (i=0; i<cmap->length; i++) {r = read_byte(file);g = read_byte(file);b = read_byte(file);append(cmap->data, rgb(r,g,b));}}static void write_rgbmap(FILE *file, rgbmap *cmap){int i;rgb c;for (i=0; i<cmap->length; i++) {c = cmap->data[i];write_byte(file, getred(c));write_byte(file, getgreen(c));write_byte(file, getblue(c));}}#if DEBUGstatic void print_rgbmap(FILE *file, rgbmap *cmap){int i;fprintf(file, " rgbmap (length=%d):\n", cmap->length);for (i=0; i<cmap->length; i++) {fprintf(file, " %-02.2X = ", i);fprintf(file, "0x%-08.8lX\n", cmap->data[i]);}}#endif/** GifScreen:*/static GifScreen * new_gif_screen(void){GifScreen *screen = create(GifScreen);if (screen)screen->cmap = new_rgbmap();return screen;}static void del_gif_screen(GifScreen *screen){del_rgbmap(screen->cmap);discard(screen);}static void read_gif_screen(FILE *file, GifScreen *screen){byte 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->bgcolor = read_byte(file);screen->aspect = read_byte(file);if (screen->has_cmap) {screen->cmap->length = 1 << screen->cmap_depth;read_rgbmap(file, screen->cmap);}}static void write_gif_screen(FILE *file, GifScreen *screen){byte 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);info = info | ((screen->cmap_depth) - 1);write_byte(file, info);write_byte(file, screen->bgcolor);write_byte(file, screen->aspect);if (screen->has_cmap) {write_rgbmap(file, screen->cmap);}}#if DEBUGstatic 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, " bgcolor = %-02.2X\n", screen->bgcolor);fprintf(file, " aspect = %d\n", screen->aspect);if (screen->has_cmap) {print_rgbmap(file, screen->cmap);}}#endif/** GifExtension:*/#define new_gif_extension() create(GifExtension)static void del_gif_extension(GifExtension *ext){int i;for (i=0; i < len(ext->data); i++)del_gif_data(ext->data[i]);discard(ext->data);discard(ext);}static void read_gif_extension(FILE *file, GifExtension *ext){byte *data;ext->marker = read_byte(file);data = read_gif_data(file);while (data) {append(ext->data, data);data = read_gif_data(file);}}static void write_gif_extension(FILE *file, GifExtension *ext){int i;write_byte(file, ext->marker);for (i=0; i < len(ext->data); i++)write_gif_data(file, ext->data[i]);write_gif_data(file, NULL);}#if DEBUGstatic void print_gif_extension(FILE *file, GifExtension *ext){int i;fprintf(file, " GifExtension:\n");fprintf(file, " marker = 0x%-02.2X\n", ext->marker);for (i=0; i < len(ext->data); i++) {fprintf(file, " data = ");print_gif_data(file, ext->data[i]);}}#endif/** GifDecoder:*/#define new_gif_decoder() create(GifDecoder)#define del_gif_decoder(decoder) discard(decoder)static 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.*/static int read_gif_code(FILE *file, GifDecoder *decoder){int code;byte 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 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.*/static void read_gif_line(FILE *file, GifDecoder *decoder, byte *line, int length){int i = 0, j;int current_code, eof_code, clear_code;int current_prefix, prev_code, stack_ptr;byte *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 (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 */}}elsecurrent_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) {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 12bits* 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:*/#define new_gif_encoder() create(GifEncoder)#define del_gif_encoder(encoder) discard(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.*/static voidwrite_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 rgbmap depth.*/static voidinit_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.*/static voidwrite_gif_line(FILE *file, GifEncoder *encoder, byte *line, int length){int i = 0, current_code, new_code;unsigned long new_key;byte pixval;unsigned long *hash_table;hash_table = encoder->hash_table;if (encoder->current_code == FIRST_CODE)current_code = line[i++];elsecurrent_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;}static 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:*/static GifPicture * new_gif_picture(void){GifPicture *pic = create(GifPicture);if (pic) {pic->cmap = new_rgbmap();pic->data = NULL;}return pic;}static void del_gif_picture(GifPicture *pic){del_rgbmap(pic->cmap);discard(pic->data);discard(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 = array(w * h, byte);if (pic->data == NULL)return;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 + w*row, w);row += interlace_step[scan_pass];}}}else {row = 0;while (row < h) {read_gif_line(file, decoder, pic->data + w*row, w);row += 1;}}finish_gif_picture(file, decoder);del_gif_decoder(decoder);}static void read_gif_picture(FILE *file, GifPicture *pic){byte 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) >> 4;pic->cmap_depth = (info & 0x07) + 1;if (pic->has_cmap) {pic->cmap->length = 1 << pic->cmap_depth;read_rgbmap(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 + w*row, w);row += interlace_step[scan_pass];}}}else {row = 0;while (row < h) {write_gif_line(file, encoder, pic->data + w*row, w);row += 1;}}flush_gif_encoder(file, encoder);del_gif_encoder(encoder);}static void write_gif_picture(FILE *file, GifPicture *pic){byte 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 << 4);info = info | (pic->cmap_depth - 1);write_byte(file, info);if (pic->has_cmap)write_rgbmap(file, pic->cmap);write_gif_picture_data(file, pic);}#if DEBUGstatic 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*pic->width+col];fprintf(file, "%-02.2X", pixval);}fprintf(file, "]\n");}}static void print_gif_picture(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);if (pic->has_cmap)print_rgbmap(file, pic->cmap);print_gif_picture_data(file, pic);}#endif/** GifBlock:*/#define new_gif_block() create(GifBlock)static void del_gif_block(GifBlock *block){if (block->pic)del_gif_picture(block->pic);if (block->ext)del_gif_extension(block->ext);discard(block);}static 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);}}static 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);}#if DEBUGstatic void print_gif_block(FILE *file, GifBlock *block){fprintf(file, " GifBlock (intro=0x%-02.2X):\n", block->intro);if (block->pic)print_gif_picture(file, block->pic);if (block->ext)print_gif_extension(file, block->ext);}#endif/** Gif:*/static Gif * new_gif(void){Gif *gif = create(Gif);if (gif) {strcpy(gif->header, "GIF87a");gif->screen = new_gif_screen();gif->blocks = NULL;}return gif;}static void del_gif(Gif *gif){int i;del_gif_screen(gif->screen);for (i=0; i < len(gif->blocks); i++)del_gif_block(gif->blocks[i]);discard(gif);}#if 0 /* currently unused but completely functional */static 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(gif->blocks, block);else if (block->intro == 0x21) /* extension */append(gif->blocks, block);else { /* error */del_gif_block(block);break;}}}#endifstatic 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(gif->blocks, block);break;}else if (block->intro == 0x21) { /* extension */append(gif->blocks, block);continue;}else { /* error! */del_gif_block(block);break;}}}static void write_gif(FILE *file, Gif *gif){int i;fprintf(file, "%s", gif->header);write_gif_screen(file, gif->screen);for (i=0; i < len(gif->blocks); i++)write_gif_block(file, gif->blocks[i]);write_byte(file, 0x3B);}#if DEBUGstatic 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 < len(gif->blocks); i++)print_gif_block(file, gif->blocks[i]);fprintf(file, "End of gif.\n\n");}#endif/** Reading and Writing Gif files:*/static Gif * read_gif_file(char *filename){Gif *gif;FILE *file;file = fopen(filename, "rb");if (file == NULL)return NULL;gif = new_gif();if (gif == NULL) {fclose(file);return NULL;}read_one_gif_picture(file, gif);fclose(file);if (strncmp(gif->header, "GIF", 3) != 0) {del_gif(gif);gif = NULL;}return gif;}static void write_gif_file(char *filename, Gif *gif){FILE *file;file = fopen(filename, "wb");if (file == NULL)return;if (gif == NULL) {fclose(file);return;}write_gif(file, gif);fclose(file);}/** Loading and Saving images:*/image load_gif(char *filename){image img;Gif *gif;GifPicture *pic = NULL;GifExtension *ext = NULL;rgbmap *cmap;int i, trans_value;/* Read the Gif file into memory: */gif = read_gif_file(filename);if (gif == NULL)return NULL;/* Find an image block and the extension just before it: */for (i=0; i < len(gif->blocks); i++) {if (gif->blocks[i]->ext) {ext = gif->blocks[i]->ext;}if (gif->blocks[i]->pic) {pic = gif->blocks[i]->pic;break;}}if (pic == NULL) {del_gif(gif);return NULL;}/* Find a colour map: */cmap = pic->cmap;if ((cmap == NULL) || (cmap->length == 0))cmap = gif->screen->cmap;if (cmap == NULL) {del_gif(gif);return NULL;}/* Check for a transparent colour: */if ((ext) && (ext->marker == 0xF9)) /* graphic control block */{if ((ext->data) && (len(ext->data[0]) == 4)&& (ext->data[0][0] & 0x01)) /* transparent flag */{trans_value = ext->data[0][3];if (cmap->length > trans_value)cmap->data[trans_value] |= 0xFF000000UL;}}/* Create a image: */img = create(struct imagedata);if (img == NULL) {del_gif(gif);return NULL;}/* Fill the image with information: */img->depth = 8;img->width = pic->width;img->height = pic->height;img->cmapsize = cmap->length;img->cmap = cmap->data;img->pixels = pic->data;/* Unlink the pointers from their original locations: */cmap->data = NULL;pic->data = NULL;/* Clean up and return the image: */del_gif(gif);return img;}static int bitsize(int cmap_length){int depth;for (depth = 1; depth <= 8; depth++)if ((1 << depth) >= cmap_length)break;return depth;}void save_gif(image img, char *filename){Gif *gif;rgbmap *cmap;GifPicture *pic;GifExtension *ext = NULL;byte *data;GifBlock *block;int i, depth;image img8 = NULL;/* Create a blank Gif: */gif = new_gif();if (gif == NULL)return;if (img->depth > 8)img = img8 = convert32to8 (img);/* Set the screen information: */depth = bitsize(img->cmapsize);gif->screen->width = img->width;gif->screen->height = img->height;gif->screen->has_cmap = 1;gif->screen->color_res = 8;gif->screen->cmap_depth = depth;/* Fill the colour map: */cmap = gif->screen->cmap;cmap->length = 1 << depth;for (i=0; i < img->cmapsize; i++) {append(cmap->data, img->cmap[i]);if (getalpha(img->cmap[i]) != 0xFF)continue; /* not transparent *//* Create transparent colour block: */if (ext)continue; /* already made one */ext = new_gif_extension();ext->marker = 0xF9;data = NULL;append(data, 0x09);append(data, 0x00);append(data, 0x00);append(data, i);append(ext->data, data);/* Link the transparency block to the Gif: */block = new_gif_block();block->intro = 0x21;block->ext = ext;append(gif->blocks, block);}/* Fill rest of colour map with zeros: */for ( ; i < cmap->length; i++)append(cmap->data, 0x00000000UL);/* Create a new GifPicture: */pic = new_gif_picture();pic->width = img->width;pic->height = img->height;pic->has_cmap = 0;pic->cmap_depth = depth;/* Link GifPicture data to supplied pixels: */pic->data = img->pixels;/* Link the GifPicture to the Gif: */block = new_gif_block();block->intro = 0x2C;block->pic = pic;append(gif->blocks, block);/* Write the Gif file: */write_gif_file(filename, gif);/* Unlink the pixel data and clean up: */pic->data = NULL;del_gif(gif);delimage(img8);}