summaryrefslogtreecommitdiff
path: root/src/gif.c
diff options
context:
space:
mode:
authorjcid <devnull@localhost>2007-10-07 00:36:34 +0200
committerjcid <devnull@localhost>2007-10-07 00:36:34 +0200
commit93715c46a99c96d6c866968312691ec9ab0f6a03 (patch)
tree573f19ec6aa740844f53a7c0eb7114f04096bf64 /src/gif.c
Initial revision
Diffstat (limited to 'src/gif.c')
-rw-r--r--src/gif.c1054
1 files changed, 1054 insertions, 0 deletions
diff --git a/src/gif.c b/src/gif.c
new file mode 100644
index 00000000..9806cbb1
--- /dev/null
+++ b/src/gif.c
@@ -0,0 +1,1054 @@
+/*
+ * File: gif.c
+ *
+ * Copyright (C) 1997 Raph Levien <raph@acm.org>
+ * Copyright (C) 2000-2002 Jorge Arellano Cid <jcid@dillo.org>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 3 of the License, or
+ * (at your option) any later version.
+ */
+
+/*
+ * The GIF decoder for dillo. It is responsible for decoding GIF data
+ * and transferring it to the dicache.
+ */
+
+
+/* Notes 13 Oct 1997 --RLL
+ *
+ * Today, just for the hell of it, I implemented a new decoder from
+ * scratch. It's oriented around pushing bytes, while the old decoder
+ * was based around reads which may suspend. There were basically
+ * three motivations.
+ *
+ * 1. To increase the speed.
+ *
+ * 2. To fix some bugs I had seen, most likely due to suspension.
+ *
+ * 3. To make sure that the code had no buffer overruns or the like.
+ *
+ * 4. So that the code could be released under a freer license.
+ *
+ * Let's see how we did on speed. I used a large image for testing
+ * (fvwm95-2.gif).
+ *
+ * The old decoder spent a total of about 1.04 seconds decoding the
+ * image. Another .58 seconds went into Image_line, almost
+ * entirely conversion from colormap to RGB.
+ *
+ * The new decoder spent a total of 0.46 seconds decoding the image.
+ * However, the time for Image_line went up to 1.01 seconds.
+ * Thus, even though the decoder seems to be about twice as fast,
+ * the net gain is pretty minimal. Could this be because of cache
+ * effects?
+ *
+ * Lessons learned: The first, which I keep learning over and over, is
+ * not to try to optimize too much. It doesn't work. Just keep things
+ * simple.
+ *
+ * Second, it seems that the colormap to RGB conversion is really a
+ * significant part of the overall time. It's possible that going
+ * directly to 16 bits would help, but that's optimization again :)
+ */
+
+
+/* todo:
+ * + Make sure to handle error cases gracefully (including aborting the
+ * connection, if necessary).
+ */
+
+#include <config.h>
+#ifdef ENABLE_GIF
+
+#include <stdio.h> /* for sprintf */
+#include <string.h> /* for memcpy and memmove */
+
+#include "msg.h"
+#include "image.hh"
+#include "web.hh"
+#include "cache.h"
+#include "dicache.h"
+#include "prefs.h"
+
+#define DEBUG_LEVEL 6
+#include "debug.h"
+
+#define INTERLACE 0x40
+#define LOCALCOLORMAP 0x80
+
+#define LM_to_uint(a,b) ((((uchar_t)b)<<8)|((uchar_t)a))
+
+#define MAXCOLORMAPSIZE 256
+#define MAX_LWZ_BITS 12
+
+
+typedef struct _DilloGif {
+ DilloImage *Image;
+ DilloUrl *url;
+ int version;
+
+ int state;
+ size_t Start_Ofs;
+ uint_t Flags;
+
+ uchar_t input_code_size;
+ uchar_t *linebuf;
+ int pass;
+
+ uint_t y;
+
+ /* state for lwz_read_byte */
+ int code_size;
+
+ /* The original GifScreen from giftopnm */
+ uint_t Width;
+ uint_t Height;
+ size_t ColorMap_ofs;
+ uint_t ColorResolution;
+ uint_t NumColors;
+ int Background;
+ uint_t spill_line_index;
+#if 0
+ uint_t AspectRatio; /* AspectRatio (not used) */
+#endif
+
+ /* Gif89 extensions */
+ int transparent;
+#if 0
+ /* None are used: */
+ int delayTime;
+ int inputFlag;
+ int disposal;
+#endif
+
+ /* state for the new push-oriented decoder */
+ int packet_size; /* The amount of the data block left to process */
+ uint_t window;
+ int bits_in_window;
+ uint_t last_code; /* Last "compressed" code in the look up table */
+ uint_t line_index;
+ uchar_t **spill_lines;
+ int num_spill_lines_max;
+ int length[(1 << MAX_LWZ_BITS) + 1];
+ int code_and_byte[(1 << MAX_LWZ_BITS) + 1];
+} DilloGif;
+
+/* Some invariants:
+ *
+ * last_code <= code_mask
+ *
+ * code_and_byte is stored packed: (code << 8) | byte
+ */
+
+
+/*
+ * Forward declarations
+ */
+static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize);
+static void Gif_close(DilloGif *gif, CacheClient_t *Client);
+static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *buf,
+ int bufsize, void *Buf);
+static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version);
+static void Gif_callback(int Op, CacheClient_t *Client);
+
+/* exported function */
+void *a_Gif_image(const char *Type, void *Ptr, CA_Callback_t *Call,
+ void **Data);
+
+
+/*
+ * MIME handler for "image/gif" type
+ * (Sets Gif_callback as cache-client)
+ */
+void *a_Gif_image(const char *Type, void *Ptr, CA_Callback_t *Call,
+ void **Data)
+{
+ DilloWeb *web = Ptr;
+ DICacheEntry *DicEntry;
+
+ if (!web->Image)
+ web->Image = a_Image_new(0, 0, NULL, prefs.bg_color);
+ /* todo: get the backgound color from the parent widget -- Livio. */
+
+ /* Add an extra reference to the Image (for dicache usage) */
+ a_Image_ref(web->Image);
+
+ DicEntry = a_Dicache_get_entry(web->url);
+ if (!DicEntry) {
+ /* Let's create an entry for this image... */
+ DicEntry = a_Dicache_add_entry(web->url);
+
+ /* ... and let the decoder feed it! */
+ *Data = Gif_new(web->Image, DicEntry->url, DicEntry->version);
+ *Call = (CA_Callback_t) Gif_callback;
+ } else {
+ /* Let's feed our client from the dicache */
+ a_Dicache_ref(DicEntry->url, DicEntry->version);
+ *Data = web->Image;
+ *Call = (CA_Callback_t) a_Dicache_callback;
+ }
+ return (web->Image->dw);
+}
+
+/*
+ * Create a new gif structure for decoding a gif into a RGB buffer
+ */
+static DilloGif *Gif_new(DilloImage *Image, DilloUrl *url, int version)
+{
+ DilloGif *gif = dMalloc(sizeof(DilloGif));
+
+ gif->Image = Image;
+ gif->url = url;
+ gif->version = version;
+
+ gif->Flags = 0;
+ gif->state = 0;
+ gif->Start_Ofs = 0;
+ gif->linebuf = NULL;
+ gif->Background = -1;
+ gif->transparent = -1;
+ gif->num_spill_lines_max = 0;
+ gif->spill_lines = NULL;
+ gif->window = 0;
+ gif->packet_size = 0;
+ gif->ColorMap_ofs = 0;
+
+ return gif;
+}
+
+/*
+ * This function is a cache client, it receives data from the cache
+ * and dispatches it to the appropriate gif-processing functions
+ */
+static void Gif_callback(int Op, CacheClient_t *Client)
+{
+ if (Op)
+ Gif_close(Client->CbData, Client);
+ else
+ Gif_write(Client->CbData, Client->Buf, Client->BufSize);
+}
+
+/*
+ * Receive and process new chunks of GIF image data
+ */
+static void Gif_write(DilloGif *gif, void *Buf, uint_t BufSize)
+{
+ uchar_t *buf;
+ int bufsize, bytes_consumed;
+
+ /* Sanity checks */
+ if (!Buf || !gif->Image || BufSize == 0)
+ return;
+
+ buf = ((uchar_t *) Buf) + gif->Start_Ofs;
+ bufsize = BufSize - gif->Start_Ofs;
+
+ DEBUG_MSG(5, "Gif_write: %u bytes\n", BufSize);
+
+ /* Process the bytes in the input buffer. */
+ bytes_consumed = Gif_process_bytes(gif, buf, bufsize, Buf);
+
+ if (bytes_consumed < 1)
+ return;
+ gif->Start_Ofs += bytes_consumed;
+
+ DEBUG_MSG(5, "exit Gif_write, bufsize=%ld\n", (long)bufsize);
+}
+
+/*
+ * Finish the decoding process (and free the memory)
+ */
+static void Gif_close(DilloGif *gif, CacheClient_t *Client)
+{
+ int i;
+
+ DEBUG_MSG(5, "destroy gif %p\n", gif);
+
+ a_Dicache_close(gif->url, gif->version, Client);
+
+ dFree(gif->linebuf);
+
+ if (gif->spill_lines != NULL) {
+ for (i = 0; i < gif->num_spill_lines_max; i++)
+ dFree(gif->spill_lines[i]);
+ dFree(gif->spill_lines);
+ }
+ dFree(gif);
+}
+
+
+/* --- GIF Extensions ----------------------------------------------------- */
+
+/*
+ * This reads a sequence of GIF data blocks.. and ignores them!
+ * Buf points to the first data block.
+ *
+ * Return Value
+ * 0 = There wasn't enough bytes read yet to read the whole datablock
+ * otherwise the size of the data blocks
+ */
+static inline size_t Gif_data_blocks(const uchar_t *Buf, size_t BSize)
+{
+ size_t Size = 0;
+
+ if (BSize < 1)
+ return 0;
+ while (Buf[0]) {
+ if (BSize <= (size_t)(Buf[0] + 1))
+ return 0;
+ Size += Buf[0] + 1;
+ BSize -= Buf[0] + 1;
+ Buf += Buf[0] + 1;
+ }
+ return Size + 1;
+}
+
+/*
+ * This is a GIF extension. We ignore it with this routine.
+ * Buffer points to just after the extension label.
+ *
+ * Return Value
+ * 0 -- block not processed
+ * otherwise the size of the extension label.
+ */
+static inline size_t Gif_do_generic_ext(const uchar_t *Buf, size_t BSize)
+{
+ size_t Size = Buf[0] + 1, DSize;
+
+ /* The Block size (the first byte) is supposed to be a specific size
+ * for each extension... we don't check.
+ */
+
+ if (Buf[0] > BSize)
+ return 0;
+ DSize = Gif_data_blocks(Buf + Size, BSize - Size);
+ if (!DSize)
+ return 0;
+ Size += DSize;
+ return Size <= BSize ? Size : 0;
+}
+
+/*
+ * ?
+ */
+static inline size_t
+ Gif_do_gc_ext(DilloGif *gif, const uchar_t *Buf, size_t BSize)
+{
+ /* Graphic Control Extension */
+ size_t Size = Buf[0] + 2;
+ uint_t Flags;
+
+ if (Size > BSize)
+ return 0;
+ Buf++;
+ Flags = Buf[0];
+
+ /* The packed fields */
+#if 0
+ gif->disposal = (Buf[0] >> 2) & 0x7;
+ gif->inputFlag = (Buf[0] >> 1) & 0x1;
+
+ /* Delay time */
+ gif->delayTime = LM_to_uint(Buf[1], Buf[2]);
+#endif
+
+ /* Transparent color index, may not be valid (unless flag is set) */
+ if ((Flags & 0x1)) {
+ gif->transparent = Buf[3];
+ }
+ return Size;
+}
+
+#define App_Ext (0xff)
+#define Cmt_Ext (0xfe)
+#define GC_Ext (0xf9)
+#define Txt_Ext (0x01)
+
+/*
+ * ?
+ * Return value:
+ * TRUE when the extension is over
+ */
+static size_t Gif_do_extension(DilloGif *gif, uint_t Label,
+ const uchar_t *buf,
+ size_t BSize)
+{
+ switch (Label) {
+ case GC_Ext: /* Graphics extension */
+ return Gif_do_gc_ext(gif, buf, BSize);
+
+ case Cmt_Ext: /* Comment extension */
+ return Gif_data_blocks(buf, BSize);
+
+ case Txt_Ext: /* Plain text Extension */
+ /* This extension allows (rcm thinks) the image to be rendered as text.
+ */
+ case App_Ext: /* Application Extension */
+ default:
+ return Gif_do_generic_ext(buf, BSize); /*Ignore Extension */
+ }
+}
+
+/* --- General Image Decoder ----------------------------------------------- */
+/* Here begins the new push-oriented decoder. */
+
+/*
+ * ?
+ */
+static void Gif_lwz_init(DilloGif *gif)
+{
+ gif->num_spill_lines_max = 1;
+ gif->spill_lines = dMalloc(sizeof(uchar_t *) * gif->num_spill_lines_max);
+
+ gif->spill_lines[0] = dMalloc(gif->Width);
+ gif->bits_in_window = 0;
+
+ /* First code in table = clear_code +1
+ * Last code in table = first code in table
+ * clear_code = (1<< input code size)
+ */
+ gif->last_code = (1 << gif->input_code_size) + 1;
+ memset(gif->code_and_byte, 0,
+ (1 + gif->last_code) * sizeof(gif->code_and_byte[0]));
+ gif->code_size = gif->input_code_size + 1;
+ gif->line_index = 0;
+}
+
+/*
+ * Send the image line to the dicache, also handling the interlacing.
+ */
+static void Gif_emit_line(DilloGif *gif, const uchar_t *linebuf)
+{
+ a_Dicache_write(gif->Image, gif->url, gif->version, linebuf, 0, gif->y);
+ if (gif->Flags & INTERLACE) {
+ switch (gif->pass) {
+ case 0:
+ case 1:
+ gif->y += 8;
+ break;
+ case 2:
+ gif->y += 4;
+ break;
+ case 3:
+ gif->y += 2;
+ break;
+ }
+ if (gif->y >= gif->Height) {
+ gif->pass++;
+ switch (gif->pass) {
+ case 1:
+ gif->y = 4;
+ break;
+ case 2:
+ gif->y = 2;
+ break;
+ case 3:
+ gif->y = 1;
+ break;
+ default:
+ /* arriving here is an error in the input image. */
+ gif->y = 0;
+ break;
+ }
+ }
+ } else {
+ if (gif->y < gif->Height)
+ gif->y++;
+ }
+}
+
+/*
+ * I apologize for the large size of this routine and the goto error
+ * construct - I almost _never_ do that. I offer the excuse of
+ * optimizing for speed.
+ *
+ * RCM -- busted these down into smaller subroutines... still very hard to
+ * read.
+ */
+
+
+/*
+ * Decode the packetized lwz bytes
+ */
+static void Gif_literal(DilloGif *gif, uint_t code)
+{
+ gif->linebuf[gif->line_index++] = code;
+ if (gif->line_index >= gif->Width) {
+ Gif_emit_line(gif, gif->linebuf);
+ gif->line_index = 0;
+ }
+ gif->length[gif->last_code + 1] = 2;
+ gif->code_and_byte[gif->last_code + 1] = (code << 8);
+ gif->code_and_byte[gif->last_code] |= code;
+}
+
+/*
+ * ?
+ */
+/* Profiling reveals over half the GIF time is spent here: */
+static void Gif_sequence(DilloGif *gif, uint_t code)
+{
+ uint_t o_index, o_size, orig_code;
+ uint_t sequence_length = gif->length[code];
+ uint_t line_index = gif->line_index;
+ int num_spill_lines;
+ int spill_line_index = gif->spill_line_index;
+ uchar_t *last_byte_ptr, *obuf;
+
+ gif->length[gif->last_code + 1] = sequence_length + 1;
+ gif->code_and_byte[gif->last_code + 1] = (code << 8);
+
+ /* We're going to traverse the sequence backwards. Thus,
+ * we need to allocate spill lines if the sequence won't
+ * fit entirely within the present scan line. */
+ if (line_index + sequence_length <= gif->Width) {
+ num_spill_lines = 0;
+ obuf = gif->linebuf;
+ o_index = line_index + sequence_length;
+ o_size = sequence_length - 1;
+ } else {
+ num_spill_lines = (line_index + sequence_length - 1) /
+ gif->Width;
+ o_index = (line_index + sequence_length - 1) % gif->Width + 1;
+ if (num_spill_lines > gif->num_spill_lines_max) {
+ /* Allocate more spill lines. */
+ spill_line_index = gif->num_spill_lines_max;
+ gif->num_spill_lines_max = num_spill_lines << 1;
+ gif->spill_lines = dRealloc(gif->spill_lines,
+ gif->num_spill_lines_max *
+ sizeof(uchar_t *));
+
+ for (; spill_line_index < gif->num_spill_lines_max;
+ spill_line_index++)
+ gif->spill_lines[spill_line_index] =
+ dMalloc(gif->Width);
+ }
+ spill_line_index = num_spill_lines - 1;
+ obuf = gif->spill_lines[spill_line_index];
+ o_size = o_index;
+ }
+ gif->line_index = o_index; /* for afterwards */
+
+ /* for fixing up later if last_code == code */
+ orig_code = code;
+ last_byte_ptr = obuf + o_index - 1;
+
+ /* spill lines are allocated, and we are clear to
+ * write. This loop does not write the first byte of
+ * the sequence, however (last byte traversed). */
+ while (sequence_length > 1) {
+ sequence_length -= o_size;
+ /* Write o_size bytes to
+ * obuf[o_index - o_size..o_index). */
+ for (; o_size > 0 && o_index > 0; o_size--) {
+ uint_t code_and_byte = gif->code_and_byte[code];
+
+ DEBUG_MSG(5, "%d ", gif->code_and_byte[code] & 255);
+
+ obuf[--o_index] = code_and_byte & 255;
+ code = code_and_byte >> 8;
+ }
+ /* Prepare for writing to next line. */
+ if (o_index == 0) {
+ if (spill_line_index > 0) {
+ spill_line_index--;
+ obuf = gif->spill_lines[spill_line_index];
+ o_size = gif->Width;
+ } else {
+ obuf = gif->linebuf;
+ o_size = sequence_length - 1;
+ }
+ o_index = gif->Width;
+ }
+ }
+ /* Ok, now we write the first byte of the sequence. */
+ /* We are sure that the code is literal. */
+ DEBUG_MSG(5, "%d", code);
+ obuf[--o_index] = code;
+ gif->code_and_byte[gif->last_code] |= code;
+
+ /* Fix up the output if the original code was last_code. */
+ if (orig_code == gif->last_code) {
+ *last_byte_ptr = code;
+ DEBUG_MSG(5, " fixed (%d)!", code);
+ }
+ DEBUG_MSG(5, "\n");
+
+ /* Output any full lines. */
+ if (gif->line_index >= gif->Width) {
+ Gif_emit_line(gif, gif->linebuf);
+ gif->line_index = 0;
+ }
+ if (num_spill_lines) {
+ if (gif->line_index)
+ Gif_emit_line(gif, gif->linebuf);
+ for (spill_line_index = 0;
+ spill_line_index < num_spill_lines - (gif->line_index ? 1 : 0);
+ spill_line_index++)
+ Gif_emit_line(gif, gif->spill_lines[spill_line_index]);
+ }
+ if (num_spill_lines) {
+ /* Swap the last spill line with the gif line, using
+ * linebuf as the swap temporary. */
+ uchar_t *linebuf = gif->spill_lines[num_spill_lines - 1];
+
+ gif->spill_lines[num_spill_lines - 1] = gif->linebuf;
+ gif->linebuf = linebuf;
+ }
+ gif->spill_line_index = spill_line_index;
+}
+
+/*
+ * ?
+ *
+ * Return Value:
+ * 2 -- quit
+ * 1 -- new last code needs to be done
+ * 0 -- okay, but reset the code table
+ * < 0 on error
+ * -1 if the decompression code was not in the lookup table
+ */
+static int Gif_process_code(DilloGif *gif, uint_t code, uint_t clear_code)
+{
+
+ /* A short table describing what to do with the code:
+ * code < clear_code : This is uncompressed, raw data
+ * code== clear_code : Reset the decompression table
+ * code== clear_code+1: End of data stream
+ * code > clear_code+1: Compressed code; look up in table
+ */
+ if (code < clear_code) {
+ /* a literal code. */
+ DEBUG_MSG(5, "literal\n");
+ Gif_literal(gif, code);
+ return 1;
+ } else if (code >= clear_code + 2) {
+ /* a sequence code. */
+ if (code > gif->last_code)
+ return -1;
+ Gif_sequence(gif, code);
+ return 1;
+ } else if (code == clear_code) {
+ /* clear code. Resets the whole table */
+ DEBUG_MSG(5, "clear\n");
+ return 0;
+ } else {
+ /* end code. */
+ DEBUG_MSG(5, "end\n");
+ return 2;
+ }
+}
+
+/*
+ * ?
+ */
+static int Gif_decode(DilloGif *gif, const uchar_t *buf, size_t bsize)
+{
+ /*
+ * Data block processing. The image stuff is a series of data blocks.
+ * Each data block is 1 to 256 bytes long. The first byte is the length
+ * of the data block. 0 == the last data block.
+ */
+ size_t bufsize, packet_size;
+ uint_t clear_code;
+ uint_t window;
+ int bits_in_window;
+ uint_t code;
+ int code_size;
+ uint_t code_mask;
+
+ bufsize = bsize;
+
+ /* Want to get all inner loop state into local variables. */
+ packet_size = gif->packet_size;
+ window = gif->window;
+ bits_in_window = gif->bits_in_window;
+ code_size = gif->code_size;
+ code_mask = (1 << code_size) - 1;
+ clear_code = 1 << gif->input_code_size;
+
+ /* If packet size == 0, we are at the start of a data block.
+ * The first byte of the data block indicates how big it is (0 == last
+ * datablock)
+ * packet size is set to this size; it indicates how much of the data block
+ * we have left to process.
+ */
+ while (bufsize > 0) {
+ /* lwz_bytes is the number of remaining lwz bytes in the packet. */
+ int lwz_bytes = MIN(packet_size, bufsize);
+
+ bufsize -= lwz_bytes;
+ packet_size -= lwz_bytes;
+ for (; lwz_bytes > 0; lwz_bytes--) {
+ /* printf ("%d ", *buf) would print the depacketized lwz stream. */
+
+ /* Push the byte onto the "end" of the window (MSB). The low order
+ * bits always come first in the LZW stream. */
+ window = (window >> 8) | (*buf++ << 24);
+ bits_in_window += 8;
+
+ while (bits_in_window >= code_size) {
+ /* Extract the code. The code is code_size (3 to 12) bits long,
+ * at the start of the window */
+ code = (window >> (32 - bits_in_window)) & code_mask;
+
+ DEBUG_MSG(5, "code = %d, ", code);
+
+ bits_in_window -= code_size;
+ switch (Gif_process_code(gif, code, clear_code)) {
+ case 1: /* Increment last code */
+ gif->last_code++;
+ /*gif->code_and_byte[gif->last_code+1]=0; */
+
+ if ((gif->last_code & code_mask) == 0) {
+ if (gif->last_code == (1 << MAX_LWZ_BITS))
+ gif->last_code--;
+ else {
+ code_size++;
+ code_mask = (1 << code_size) - 1;
+ }
+ }
+ break;
+
+ case 0: /* Reset codes size and mask */
+ gif->last_code = clear_code + 1;
+ code_size = gif->input_code_size + 1;
+ code_mask = (1 << code_size) - 1;
+ break;
+
+ case 2: /* End code... consume remaining data chunks..? */
+ goto error; /* Could clean up better? */
+ default:
+ printf("dillo_gif_decode: error!\n");
+ goto error;
+ }
+ }
+ }
+
+ /* We reach here if
+ * a) We have reached the end of the data block;
+ * b) we ran out of data before reaching the end of the data block
+ */
+ if (bufsize <= 0)
+ break; /* We are out of data; */
+
+ /* Start of new data block */
+ bufsize--;
+ if (!(packet_size = *buf++)) {
+ /* This is the "block terminator" -- the last data block */
+ gif->state = 999; /* BUG: should Go back to getting GIF blocks. */
+ break;
+ }
+ }
+
+ gif->packet_size = packet_size;
+ gif->window = window;
+ gif->bits_in_window = bits_in_window;
+ gif->code_size = code_size;
+ return bsize - bufsize;
+
+ error:
+ gif->state = 999;
+ return bsize - bufsize;
+}
+
+/*
+ * ?
+ */
+static int Gif_check_sig(DilloGif *gif, const uchar_t *ibuf, int ibsize)
+{
+ /* at beginning of file - read magic number */
+ if (ibsize < 6)
+ return 0;
+ if (memcmp(ibuf, "GIF", 3) != 0) {
+ gif->state = 999;
+ return 6;
+ }
+ if (memcmp(ibuf + 3, "87a", 3) != 0 &&
+ memcmp(ibuf + 3, "89a", 3) != 0) {
+ gif->state = 999;
+ return 6;
+ }
+ gif->state = 1;
+ return 6;
+}
+
+/* Read the color map
+ *
+ * Implements, from the spec:
+ * Global Color Table
+ * Local Color Table
+ */
+static inline size_t
+ Gif_do_color_table(DilloGif *gif, void *Buf,
+ const uchar_t *buf, size_t bsize, size_t CT_Size)
+{
+ size_t Size = 3 * (1 << (1 + CT_Size));
+
+ if (Size > bsize)
+ return 0;
+
+ gif->ColorMap_ofs = (ulong_t) buf - (ulong_t) Buf;
+ gif->NumColors = (1 << (1 + CT_Size));
+ return Size;
+}
+
+/*
+ * This implements, from the spec:
+ * <Logical Screen> ::= Logical Screen Descriptor [Global Color Table]
+ */
+static size_t Gif_get_descriptor(DilloGif *gif, void *Buf,
+ const uchar_t *buf, int bsize)
+{
+
+ /* screen descriptor */
+ size_t Size = 7, /* Size of descriptor */
+ mysize; /* Size of color table */
+ uchar_t Flags;
+
+ if (bsize < 7)
+ return 0;
+ Flags = buf[4];
+
+ if (Flags & LOCALCOLORMAP) {
+ mysize = Gif_do_color_table(
+ gif, Buf, buf + 7, (size_t)bsize - 7, Flags & (size_t)0x7);
+ if (!mysize)
+ return 0;
+ Size += mysize; /* Size of the color table that follows */
+ gif->Background = buf[5];
+ }
+ /* gif->Width = LM_to_uint(buf[0], buf[1]);
+ gif->Height = LM_to_uint(buf[2], buf[3]); */
+ gif->ColorResolution = (((buf[4] & 0x70) >> 3) + 1);
+ /* gif->AspectRatio = buf[6]; */
+
+ return Size;
+}
+
+/*
+ * This implements, from the spec:
+ * <Table-Based Image> ::= Image Descriptor [Local Color Table] Image Data
+ *
+ * ('Buf' points to just after the Image separator)
+ * we should probably just check that the local stuff is consistent
+ * with the stuff at the header. For now, we punt...
+ */
+static size_t Gif_do_img_desc(DilloGif *gif, void *Buf,
+ const uchar_t *buf, size_t bsize)
+{
+ uchar_t Flags;
+ size_t Size = 9 + 1; /* image descriptor size + first byte of image data */
+
+ if (bsize < 10)
+ return 0;
+
+ gif->Width = LM_to_uint(buf[4], buf[5]);
+ gif->Height = LM_to_uint(buf[6], buf[7]);
+ gif->linebuf = dMalloc(gif->Width);
+
+ a_Dicache_set_parms(gif->url, gif->version, gif->Image,
+ gif->Width, gif->Height, DILLO_IMG_TYPE_INDEXED);
+
+ Flags = buf[8];
+
+ gif->Flags |= Flags & INTERLACE;
+ gif->pass = 0;
+ bsize -= 9;
+ buf += 9;
+ if (Flags & LOCALCOLORMAP) {
+ size_t LSize = Gif_do_color_table(
+ gif, Buf, buf, bsize, Flags & (size_t)0x7);
+
+ if (!LSize)
+ return 0;
+ Size += LSize;
+ buf += LSize;
+ bsize -= LSize;
+ }
+ /* Finally, get the first byte of the LZW image data */
+ if (bsize < 1)
+ return 0;
+ gif->input_code_size = *buf++;
+ if (gif->input_code_size > 8) {
+ gif->state = 999;
+ return Size;
+ }
+ gif->y = 0;
+ Gif_lwz_init(gif);
+ gif->spill_line_index = 0;
+ gif->state = 3; /*Process the lzw data next */
+ if (gif->Image && gif->ColorMap_ofs) {
+ a_Dicache_set_cmap(gif->url, gif->version, gif->Image,
+ (uchar_t *) Buf + gif->ColorMap_ofs,
+ gif->NumColors, 256, gif->transparent);
+ }
+ return Size;
+}
+
+/* --- Top level data block processors ------------------------------------ */
+#define Img_Desc (0x2c)
+#define Trailer (0x3B)
+#define Ext_Id (0x21)
+
+/*
+ * This identifies which kind of GIF blocks are next, and processes them.
+ * It returns if there isn't enough data to process the next blocks, or if
+ * the next block is the lzw data (which is streamed differently)
+ *
+ * This implements, from the spec, <Data>* Trailer
+ * <Data> ::= <Graphic Block> | <Special-Purpose Block>
+ * <Special-Purpose Block> ::= Application Extension | Comment Extension
+ * <Graphic Block> ::= [Graphic Control Extension] <Graphic-Rendering Block>
+ * <Graphic-Rendering Block> ::= <Table-Based Image> | Plain Text Extension
+ *
+ * <Data>* --> GIF_Block
+ * <Data> --> while (...)
+ * <Special-Purpose Block> --> Gif_do_extension
+ * Graphic Control Extension --> Gif_do_extension
+ * Plain Text Extension --> Gif_do_extension
+ * <Table-Based Image> --> Gif_do_img_desc
+ *
+ * Return Value
+ * 0 if not enough data is present, otherwise the number of bytes
+ * "consumed"
+ */
+static size_t GIF_Block(DilloGif * gif, void *Buf,
+ const uchar_t *buf, size_t bsize)
+{
+ size_t Size = 0, mysize;
+ uchar_t C;
+
+ if (bsize < 1)
+ return 0;
+ while (gif->state == 2) {
+ if (bsize < 1)
+ return Size;
+ bsize--;
+ switch (*buf++) {
+ case Ext_Id:
+ /* get the extension type */
+ if (bsize < 2)
+ return Size;
+
+ /* Have the extension block intepreted. */
+ C = *buf++;
+ bsize--;
+ mysize = Gif_do_extension(gif, C, buf, bsize);
+
+ if (!mysize)
+ /* Not all of the extension is there.. quit until more data
+ * arrives */
+ return Size;
+
+ bsize -= mysize;
+ buf += mysize;
+
+ /* Increment the amount consumed by the extension introducer
+ * and id, and extension block size */
+ Size += mysize + 2;
+ /* Do more GIF Blocks */
+ continue;
+
+ case Img_Desc: /* Image descriptor */
+ mysize = Gif_do_img_desc(gif, Buf, buf, bsize);
+ if (!mysize)
+ return Size;
+
+ /* Increment the amount consumed by the Image Separator and the
+ * Resultant blocks */
+ Size += 1 + mysize;
+ return Size;
+
+ case Trailer:
+ gif->state = 999; /* BUG: should close the rest of the file */
+ return Size + 1;
+ break; /* GIF terminator */
+
+ default: /* Unknown */
+ /* gripe and complain */
+ MSG ("gif.c::GIF_Block: Error, 0x%x found\n", *(buf-1));
+ gif->state = 999;
+ return Size + 1;
+ }
+ }
+ return Size;
+}
+
+
+/*
+ * Process some bytes from the input gif stream. It's a state machine.
+ *
+ * From the GIF spec:
+ * <GIF Data Stream> ::= Header <Logical Screen> <Data>* Trailer
+ *
+ * <GIF Data Stream> --> Gif_process_bytes
+ * Header --> State 0
+ * <Logical Screen> --> State 1
+ * <Data>* --> State 2
+ * Trailer --> State > 3
+ *
+ * State == 3 is special... this is inside of <Data> but all of the stuff in
+ * there has been gotten and set up. So we stream it outside.
+ */
+static size_t Gif_process_bytes(DilloGif *gif, const uchar_t *ibuf,
+ int bufsize, void *Buf)
+{
+ int tmp_bufsize = bufsize;
+ size_t mysize;
+
+ switch (gif->state) {
+ case 0:
+ mysize = Gif_check_sig(gif, ibuf, tmp_bufsize);
+ if (!mysize)
+ break;
+ tmp_bufsize -= mysize;
+ ibuf += mysize;
+ if (gif->state != 1)
+ break;
+
+ case 1:
+ mysize = Gif_get_descriptor(gif, Buf, ibuf, tmp_bufsize);
+ if (!mysize)
+ break;
+ tmp_bufsize -= mysize;
+ ibuf += mysize;
+ gif->state = 2;
+
+ case 2:
+ /* Ok, this loop construction looks weird. It implements the <Data>* of
+ * the GIF grammar. All sorts of stuff is allocated to set up for the
+ * decode part (state ==2) and then there is the actual decode part (3)
+ */
+ mysize = GIF_Block(gif, Buf, ibuf, (size_t)tmp_bufsize);
+ if (!mysize)
+ break;
+ tmp_bufsize -= mysize;
+ ibuf += mysize;
+ if (gif->state != 3)
+ break;
+
+ case 3:
+ /* get an image byte */
+ /* The users sees all of this stuff */
+ mysize = Gif_decode(gif, ibuf, (size_t)tmp_bufsize);
+ if (mysize == 0)
+ break;
+ ibuf += mysize;
+ tmp_bufsize -= mysize;
+
+ default:
+ /* error - just consume all input */
+ tmp_bufsize = 0;
+ break;
+ }
+
+ DEBUG_MSG(5, "Gif_process_bytes: final state %d, %ld bytes consumed\n",
+ gif->state, (long)(bufsize - tmp_bufsize));
+
+ return bufsize - tmp_bufsize;
+}
+
+#endif /* ENABLE_GIF */