+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is mozilla.org code.
+ *
+ * The Initial Developer of the Original Code is
+ * Netscape Communications Corporation.
+ * Portions created by the Initial Developer are Copyright (C) 1998
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ * Chris Saari <saari@netscape.com>
+ * Apple Computer
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+/*
+The Graphics Interchange Format(c) is the copyright property of CompuServe
+Incorporated. Only CompuServe Incorporated is authorized to define, redefine,
+enhance, alter, modify or change in any way the definition of the format.
+
+CompuServe Incorporated hereby grants a limited, non-exclusive, royalty-free
+license for the use of the Graphics Interchange Format(sm) in computer
+software; computer software utilizing GIF(sm) must acknowledge ownership of the
+Graphics Interchange Format and its Service Mark by CompuServe Incorporated, in
+User and Technical Documentation. Computer software utilizing GIF, which is
+distributed or may be distributed without User or Technical Documentation must
+display to the screen or printer a message acknowledging ownership of the
+Graphics Interchange Format and the Service Mark by CompuServe Incorporated; in
+this case, the acknowledgement may be displayed in an opening screen or leading
+banner, or a closing screen or trailing banner. A message such as the following
+may be used:
+
+ "The Graphics Interchange Format(c) is the Copyright property of
+ CompuServe Incorporated. GIF(sm) is a Service Mark property of
+ CompuServe Incorporated."
+
+For further information, please contact :
+
+ CompuServe Incorporated
+ Graphics Technology Department
+ 5000 Arlington Center Boulevard
+ Columbus, Ohio 43220
+ U. S. A.
+
+CompuServe Incorporated maintains a mailing list with all those individuals and
+organizations who wish to receive copies of this document when it is corrected
+or revised. This service is offered free of charge; please provide us with your
+mailing address.
+*/
+
+#include "config.h"
+#include "GIFImageReader.h"
+
+#include <string.h>
+#include "GIFImageDecoder.h"
+
+#if PLATFORM(CAIRO) || PLATFORM(QT) || PLATFORM(WX)
+
+using WebCore::GIFImageDecoder;
+
+// Define the Mozilla macro setup so that we can leave the macros alone.
+#define PR_BEGIN_MACRO do {
+#define PR_END_MACRO } while (0)
+
+/*
+ * GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's'
+ *
+ * Note, the hold will never need to be bigger than 256 bytes to gather up in the hold,
+ * as each GIF block (except colormaps) can never be bigger than 256 bytes.
+ * Colormaps are directly copied in the resp. global_colormap or dynamically allocated local_colormap.
+ * So a fixed buffer in GIFImageReader is good enough.
+ * This buffer is only needed to copy left-over data from one GifWrite call to the next
+ */
+#define GETN(n,s) \
+ PR_BEGIN_MACRO \
+ bytes_to_consume = (n); \
+ state = (s); \
+ PR_END_MACRO
+
+/* Get a 16-bit value stored in little-endian format */
+#define GETINT16(p) ((p)[1]<<8|(p)[0])
+
+//******************************************************************************
+// Send the data to the display front-end.
+void GIFImageReader::output_row()
+{
+ GIFFrameReader* gs = frame_reader;
+
+ int drow_start, drow_end;
+
+ drow_start = drow_end = gs->irow;
+
+ /*
+ * Haeberli-inspired hack for interlaced GIFs: Replicate lines while
+ * displaying to diminish the "venetian-blind" effect as the image is
+ * loaded. Adjust pixel vertical positions to avoid the appearance of the
+ * image crawling up the screen as successive passes are drawn.
+ */
+ if (gs->progressive_display && gs->interlaced && gs->ipass < 4) {
+ unsigned row_dup = 0, row_shift = 0;
+
+ switch (gs->ipass) {
+ case 1:
+ row_dup = 7;
+ row_shift = 3;
+ break;
+ case 2:
+ row_dup = 3;
+ row_shift = 1;
+ break;
+ case 3:
+ row_dup = 1;
+ row_shift = 0;
+ break;
+ default:
+ break;
+ }
+
+ drow_start -= row_shift;
+ drow_end = drow_start + row_dup;
+
+ /* Extend if bottom edge isn't covered because of the shift upward. */
+ if (((gs->height - 1) - drow_end) <= row_shift)
+ drow_end = gs->height - 1;
+
+ /* Clamp first and last rows to upper and lower edge of image. */
+ if (drow_start < 0)
+ drow_start = 0;
+ if ((unsigned)drow_end >= gs->height)
+ drow_end = gs->height - 1;
+ }
+
+ /* Protect against too much image data */
+ if ((unsigned)drow_start >= gs->height)
+ return;
+
+ // CALLBACK: Let the client know we have decoded a row.
+ if (clientptr && frame_reader)
+ clientptr->haveDecodedRow(images_count - 1, frame_reader->rowbuf, frame_reader->rowend,
+ drow_start, drow_end - drow_start + 1,
+ gs->progressive_display && gs->interlaced && gs->ipass > 1);
+
+ gs->rowp = gs->rowbuf;
+
+ if (!gs->interlaced)
+ gs->irow++;
+ else {
+ do {
+ switch (gs->ipass)
+ {
+ case 1:
+ gs->irow += 8;
+ if (gs->irow >= gs->height) {
+ gs->ipass++;
+ gs->irow = 4;
+ }
+ break;
+
+ case 2:
+ gs->irow += 8;
+ if (gs->irow >= gs->height) {
+ gs->ipass++;
+ gs->irow = 2;
+ }
+ break;
+
+ case 3:
+ gs->irow += 4;
+ if (gs->irow >= gs->height) {
+ gs->ipass++;
+ gs->irow = 1;
+ }
+ break;
+
+ case 4:
+ gs->irow += 2;
+ if (gs->irow >= gs->height){
+ gs->ipass++;
+ gs->irow = 0;
+ }
+ break;
+
+ default:
+ break;
+ }
+ } while (gs->irow > (gs->height - 1));
+ }
+}
+
+//******************************************************************************
+/* Perform Lempel-Ziv-Welch decoding */
+int GIFImageReader::do_lzw(const unsigned char *q)
+{
+ GIFFrameReader* gs = frame_reader;
+ if (!gs)
+ return 0;
+
+ int code;
+ int incode;
+ const unsigned char *ch;
+
+ /* Copy all the decoder state variables into locals so the compiler
+ * won't worry about them being aliased. The locals will be homed
+ * back into the GIF decoder structure when we exit.
+ */
+ int avail = gs->avail;
+ int bits = gs->bits;
+ int cnt = count;
+ int codesize = gs->codesize;
+ int codemask = gs->codemask;
+ int oldcode = gs->oldcode;
+ int clear_code = gs->clear_code;
+ unsigned char firstchar = gs->firstchar;
+ int datum = gs->datum;
+
+ if (!gs->prefix) {
+ gs->prefix = new unsigned short[MAX_BITS];
+ memset(gs->prefix, 0, MAX_BITS * sizeof(short));
+ }
+
+ unsigned short *prefix = gs->prefix;
+ unsigned char *stackp = gs->stackp;
+ unsigned char *suffix = gs->suffix;
+ unsigned char *stack = gs->stack;
+ unsigned char *rowp = gs->rowp;
+ unsigned char *rowend = gs->rowend;
+ unsigned rows_remaining = gs->rows_remaining;
+
+ if (rowp == rowend)
+ return 0;
+
+#define OUTPUT_ROW \
+ PR_BEGIN_MACRO \
+ output_row(); \
+ rows_remaining--; \
+ rowp = frame_reader->rowp; \
+ if (!rows_remaining) \
+ goto END; \
+ PR_END_MACRO
+
+ for (ch = q; cnt-- > 0; ch++)
+ {
+ /* Feed the next byte into the decoder's 32-bit input buffer. */
+ datum += ((int) *ch) << bits;
+ bits += 8;
+
+ /* Check for underflow of decoder's 32-bit input buffer. */
+ while (bits >= codesize)
+ {
+ /* Get the leading variable-length symbol from the data stream */
+ code = datum & codemask;
+ datum >>= codesize;
+ bits -= codesize;
+
+ /* Reset the dictionary to its original state, if requested */
+ if (code == clear_code) {
+ codesize = gs->datasize + 1;
+ codemask = (1 << codesize) - 1;
+ avail = clear_code + 2;
+ oldcode = -1;
+ continue;
+ }
+
+ /* Check for explicit end-of-stream code */
+ if (code == (clear_code + 1)) {
+ /* end-of-stream should only appear after all image data */
+ if (rows_remaining != 0)
+ return -1;
+ return 0;
+ }
+
+ if (oldcode == -1) {
+ *rowp++ = suffix[code];
+ if (rowp == rowend)
+ OUTPUT_ROW;
+
+ firstchar = oldcode = code;
+ continue;
+ }
+
+ incode = code;
+ if (code >= avail) {
+ *stackp++ = firstchar;
+ code = oldcode;
+
+ if (stackp == stack + MAX_BITS)
+ return -1;
+ }
+
+ while (code >= clear_code)
+ {
+ if (code == prefix[code])
+ return -1;
+
+ *stackp++ = suffix[code];
+ code = prefix[code];
+
+ if (stackp == stack + MAX_BITS)
+ return -1;
+ }
+
+ *stackp++ = firstchar = suffix[code];
+
+ /* Define a new codeword in the dictionary. */
+ if (avail < 4096) {
+ prefix[avail] = oldcode;
+ suffix[avail] = firstchar;
+ avail++;
+
+ /* If we've used up all the codewords of a given length
+ * increase the length of codewords by one bit, but don't
+ * exceed the specified maximum codeword size of 12 bits.
+ */
+ if (((avail & codemask) == 0) && (avail < 4096)) {
+ codesize++;
+ codemask += avail;
+ }
+ }
+ oldcode = incode;
+
+ /* Copy the decoded data out to the scanline buffer. */
+ do {
+ *rowp++ = *--stackp;
+ if (rowp == rowend) {
+ OUTPUT_ROW;
+ }
+ } while (stackp > stack);
+ }
+ }
+
+ END:
+
+ /* Home the local copies of the GIF decoder state variables */
+ gs->avail = avail;
+ gs->bits = bits;
+ gs->codesize = codesize;
+ gs->codemask = codemask;
+ count = cnt;
+ gs->oldcode = oldcode;
+ gs->firstchar = firstchar;
+ gs->datum = datum;
+ gs->stackp = stackp;
+ gs->rowp = rowp;
+ gs->rows_remaining = rows_remaining;
+
+ return 0;
+}
+
+
+/******************************************************************************/
+/*
+ * process data arriving from the stream for the gif decoder
+ */
+
+bool GIFImageReader::read(const unsigned char *buf, unsigned len,
+ GIFImageDecoder::GIFQuery query, unsigned haltAtFrame)
+{
+ if (!len) {
+ // No new data has come in since the last call, just ignore this call.
+ return true;
+ }
+
+ const unsigned char *q = buf;
+
+ // Add what we have so far to the block
+ // If previous call to me left something in the hold first complete current block
+ // Or if we are filling the colormaps, first complete the colormap
+ unsigned char* p = 0;
+ if (state == gif_global_colormap)
+ p = global_colormap;
+ else if (state == gif_image_colormap)
+ p = frame_reader ? frame_reader->local_colormap : 0;
+ else if (bytes_in_hold)
+ p = hold;
+ else
+ p = 0;
+
+ if (p || (state == gif_global_colormap) || (state == gif_image_colormap)) {
+ // Add what we have sofar to the block
+ unsigned l = len < bytes_to_consume ? len : bytes_to_consume;
+ if (p)
+ memcpy(p + bytes_in_hold, buf, l);
+
+ if (l < bytes_to_consume) {
+ // Not enough in 'buf' to complete current block, get more
+ bytes_in_hold += l;
+ bytes_to_consume -= l;
+ if (clientptr)
+ clientptr->decodingHalted(0);
+ return true;
+ }
+ // Reset hold buffer count
+ bytes_in_hold = 0;
+ // Point 'q' to complete block in hold (or in colormap)
+ q = p;
+ }
+
+ // Invariant:
+ // 'q' is start of current to be processed block (hold, colormap or buf)
+ // 'bytes_to_consume' is number of bytes to consume from 'buf'
+ // 'buf' points to the bytes to be consumed from the input buffer
+ // 'len' is number of bytes left in input buffer from position 'buf'.
+ // At entrance of the for loop will 'buf' will be moved 'bytes_to_consume'
+ // to point to next buffer, 'len' is adjusted accordingly.
+ // So that next round in for loop, q gets pointed to the next buffer.
+
+ for (;len >= bytes_to_consume; q=buf) {
+ // Eat the current block from the buffer, q keeps pointed at current block
+ buf += bytes_to_consume;
+ len -= bytes_to_consume;
+
+ switch (state)
+ {
+ case gif_lzw:
+ if (do_lzw(q) < 0) {
+ state = gif_error;
+ break;
+ }
+ GETN(1, gif_sub_block);
+ break;
+
+ case gif_lzw_start:
+ {
+ /* Initialize LZW parser/decoder */
+ int datasize = *q;
+ if (datasize > MAX_LZW_BITS) {
+ state = gif_error;
+ break;
+ }
+ int clear_code = 1 << datasize;
+ if (clear_code >= MAX_BITS) {
+ state = gif_error;
+ break;
+ }
+
+ if (frame_reader) {
+ frame_reader->datasize = datasize;
+ frame_reader->clear_code = clear_code;
+ frame_reader->avail = frame_reader->clear_code + 2;
+ frame_reader->oldcode = -1;
+ frame_reader->codesize = frame_reader->datasize + 1;
+ frame_reader->codemask = (1 << frame_reader->codesize) - 1;
+
+ frame_reader->datum = frame_reader->bits = 0;
+
+ /* init the tables */
+ if (!frame_reader->suffix)
+ frame_reader->suffix = new unsigned char[MAX_BITS];
+ for (int i = 0; i < frame_reader->clear_code; i++)
+ frame_reader->suffix[i] = i;
+
+ if (!frame_reader->stack)
+ frame_reader->stack = new unsigned char[MAX_BITS];
+ frame_reader->stackp = frame_reader->stack;
+ }
+
+ GETN(1, gif_sub_block);
+ }
+ break;
+
+ /* All GIF files begin with "GIF87a" or "GIF89a" */
+ case gif_type:
+ {
+ if (!strncmp((char*)q, "GIF89a", 6)) {
+ version = 89;
+ } else if (!strncmp((char*)q, "GIF87a", 6)) {
+ version = 87;
+ } else {
+ state = gif_error;
+ break;
+ }
+ GETN(7, gif_global_header);
+ }
+ break;
+
+ case gif_global_header:
+ {
+ /* This is the height and width of the "screen" or
+ * frame into which images are rendered. The
+ * individual images can be smaller than the
+ * screen size and located with an origin anywhere
+ * within the screen.
+ */
+
+ screen_width = GETINT16(q);
+ screen_height = GETINT16(q + 2);
+
+ // CALLBACK: Inform the decoderplugin of our size.
+ if (clientptr)
+ clientptr->sizeNowAvailable(screen_width, screen_height);
+
+ screen_bgcolor = q[5];
+ global_colormap_size = 2<<(q[4]&0x07);
+
+ if ((q[4] & 0x80) && global_colormap_size > 0) { /* global map */
+ // Get the global colormap
+ const unsigned size = 3*global_colormap_size;
+
+ // Malloc the color map, but only if we're not just counting frames.
+ if (query != GIFImageDecoder::GIFFrameCountQuery)
+ global_colormap = new unsigned char[size];
+
+ if (len < size) {
+ // Use 'hold' pattern to get the global colormap
+ GETN(size, gif_global_colormap);
+ break;
+ }
+
+ // Copy everything and go directly to gif_image_start.
+ if (global_colormap)
+ memcpy(global_colormap, buf, size);
+ buf += size;
+ len -= size;
+ }
+
+ GETN(1, gif_image_start);
+
+ // q[6] = Pixel Aspect Ratio
+ // Not used
+ // float aspect = (float)((q[6] + 15) / 64.0);
+ }
+ break;
+
+ case gif_global_colormap:
+ // Everything is already copied into global_colormap
+ GETN(1, gif_image_start);
+ break;
+
+ case gif_image_start:
+ {
+ if (*q == ';') { /* terminator */
+ state = gif_done;
+ break;
+ }
+
+ if (*q == '!') { /* extension */
+ GETN(2, gif_extension);
+ break;
+ }
+
+ /* If we get anything other than ',' (image separator), '!'
+ * (extension), or ';' (trailer), there is extraneous data
+ * between blocks. The GIF87a spec tells us to keep reading
+ * until we find an image separator, but GIF89a says such
+ * a file is corrupt. We follow GIF89a and bail out. */
+ if (*q != ',') {
+ if (images_decoded > 0) {
+ /* The file is corrupt, but one or more images have
+ * been decoded correctly. In this case, we proceed
+ * as if the file were correctly terminated and set
+ * the state to gif_done, so the GIF will display.
+ */
+ state = gif_done;
+ } else {
+ /* No images decoded, there is nothing to display. */
+ state = gif_error;
+ }
+ break;
+ } else
+ GETN(9, gif_image_header);
+ }
+ break;
+
+ case gif_extension:
+ {
+ int len = count = q[1];
+ gstate es = gif_skip_block;
+
+ switch (*q)
+ {
+ case 0xf9:
+ es = gif_control_extension;
+ break;
+
+ case 0x01:
+ // ignoring plain text extension
+ break;
+
+ case 0xff:
+ es = gif_application_extension;
+ break;
+
+ case 0xfe:
+ es = gif_consume_comment;
+ break;
+ }
+
+ if (len)
+ GETN(len, es);
+ else
+ GETN(1, gif_image_start);
+ }
+ break;
+
+ case gif_consume_block:
+ if (!*q)
+ GETN(1, gif_image_start);
+ else
+ GETN(*q, gif_skip_block);
+ break;
+
+ case gif_skip_block:
+ GETN(1, gif_consume_block);
+ break;
+
+ case gif_control_extension:
+ {
+ if (query != GIFImageDecoder::GIFFrameCountQuery) {
+ if (!frame_reader)
+ frame_reader = new GIFFrameReader();
+ }
+
+ if (frame_reader) {
+ if (*q & 0x1) {
+ frame_reader->tpixel = q[3];
+ frame_reader->is_transparent = true;
+ } else {
+ frame_reader->is_transparent = false;
+ // ignoring gfx control extension
+ }
+ // NOTE: This relies on the values in the FrameDisposalMethod enum
+ // matching those in the GIF spec!
+ frame_reader->disposal_method = (WebCore::RGBA32Buffer::FrameDisposalMethod)(((*q) >> 2) & 0x7);
+ // Some specs say 3rd bit (value 4), other specs say value 3
+ // Let's choose 3 (the more popular)
+ if (frame_reader->disposal_method == 4)
+ frame_reader->disposal_method = WebCore::RGBA32Buffer::DisposeOverwritePrevious;
+ frame_reader->delay_time = GETINT16(q + 1) * 10;
+ }
+ GETN(1, gif_consume_block);
+ }
+ break;
+
+ case gif_comment_extension:
+ {
+ if (*q)
+ GETN(*q, gif_consume_comment);
+ else
+ GETN(1, gif_image_start);
+ }
+ break;
+
+ case gif_consume_comment:
+ GETN(1, gif_comment_extension);
+ break;
+
+ case gif_application_extension:
+ /* Check for netscape application extension */
+ if (!strncmp((char*)q, "NETSCAPE2.0", 11) ||
+ !strncmp((char*)q, "ANIMEXTS1.0", 11))
+ GETN(1, gif_netscape_extension_block);
+ else
+ GETN(1, gif_consume_block);
+ break;
+
+ /* Netscape-specific GIF extension: animation looping */
+ case gif_netscape_extension_block:
+ if (*q)
+ GETN(*q, gif_consume_netscape_extension);
+ else
+ GETN(1, gif_image_start);
+ break;
+
+ /* Parse netscape-specific application extensions */
+ case gif_consume_netscape_extension:
+ {
+ int netscape_extension = q[0] & 7;
+
+ /* Loop entire animation specified # of times. Only read the
+ loop count during the first iteration. */
+ if (netscape_extension == 1) {
+ loop_count = GETINT16(q + 1);
+
+ GETN(1, gif_netscape_extension_block);
+ }
+ /* Wait for specified # of bytes to enter buffer */
+ else if (netscape_extension == 2) {
+ // Don't do this, this extension doesn't exist (isn't used at all)
+ // and doesn't do anything, as our streaming/buffering takes care of it all...
+ // See: http://semmix.pl/color/exgraf/eeg24.htm
+ GETN(1, gif_netscape_extension_block);
+ } else
+ state = gif_error; // 0,3-7 are yet to be defined netscape
+ // extension codes
+
+ break;
+ }
+
+ case gif_image_header:
+ {
+ unsigned height, width, x_offset, y_offset;
+
+ /* Get image offsets, with respect to the screen origin */
+ x_offset = GETINT16(q);
+ y_offset = GETINT16(q + 2);
+
+ /* Get image width and height. */
+ width = GETINT16(q + 4);
+ height = GETINT16(q + 6);
+
+ /* Work around broken GIF files where the logical screen
+ * size has weird width or height. We assume that GIF87a
+ * files don't contain animations.
+ */
+ if ((images_decoded == 0) &&
+ ((screen_height < height) || (screen_width < width) ||
+ (version == 87)))
+ {
+ screen_height = height;
+ screen_width = width;
+ x_offset = 0;
+ y_offset = 0;
+
+ // CALLBACK: Inform the decoderplugin of our size.
+ if (clientptr)
+ clientptr->sizeNowAvailable(screen_width, screen_height);
+ }
+
+ /* Work around more broken GIF files that have zero image
+ width or height */
+ if (!height || !width) {
+ height = screen_height;
+ width = screen_width;
+ if (!height || !width) {
+ state = gif_error;
+ break;
+ }
+ }
+
+ if (query == GIFImageDecoder::GIFSizeQuery || haltAtFrame == images_decoded) {
+ // The decoder needs to stop. Hand back the number of bytes we consumed from
+ // buffer minus 9 (the amount we consumed to read the header).
+ if (clientptr)
+ clientptr->decodingHalted(len + 9);
+ GETN(9, gif_image_header);
+ return true;
+ }
+
+ images_count = images_decoded + 1;
+
+ if (query == GIFImageDecoder::GIFFullQuery && !frame_reader)
+ frame_reader = new GIFFrameReader();
+
+ if (frame_reader) {
+ frame_reader->x_offset = x_offset;
+ frame_reader->y_offset = y_offset;
+ frame_reader->height = height;
+ frame_reader->width = width;
+
+ /* This case will never be taken if this is the first image */
+ /* being decoded. If any of the later images are larger */
+ /* than the screen size, we need to reallocate buffers. */
+ if (screen_width < width) {
+ /* XXX Deviant! */
+
+ delete []frame_reader->rowbuf;
+ screen_width = width;
+ frame_reader->rowbuf = new unsigned char[screen_width];
+ } else if (!frame_reader->rowbuf) {
+ frame_reader->rowbuf = new unsigned char[screen_width];
+ }
+
+ if (!frame_reader->rowbuf) {
+ state = gif_oom;
+ break;
+ }
+ if (screen_height < height)
+ screen_height = height;
+
+ if (q[8] & 0x40) {
+ frame_reader->interlaced = true;
+ frame_reader->ipass = 1;
+ } else {
+ frame_reader->interlaced = false;
+ frame_reader->ipass = 0;
+ }
+
+ if (images_decoded == 0) {
+ frame_reader->progressive_display = true;
+ } else {
+ /* Overlaying interlaced, transparent GIFs over
+ existing image data using the Haeberli display hack
+ requires saving the underlying image in order to
+ avoid jaggies at the transparency edges. We are
+ unprepared to deal with that, so don't display such
+ images progressively */
+ frame_reader->progressive_display = false;
+ }
+
+ /* Clear state from last image */
+ frame_reader->irow = 0;
+ frame_reader->rows_remaining = frame_reader->height;
+ frame_reader->rowend = frame_reader->rowbuf + frame_reader->width;
+ frame_reader->rowp = frame_reader->rowbuf;
+
+ /* bits per pixel is q[8]&0x07 */
+ }
+
+ if (q[8] & 0x80) /* has a local colormap? */
+ {
+ int num_colors = 2 << (q[8] & 0x7);
+ const unsigned size = 3*num_colors;
+ unsigned char *map = frame_reader ? frame_reader->local_colormap : 0;
+ if (frame_reader && (!map || (num_colors > frame_reader->local_colormap_size))) {
+ delete []map;
+ map = new unsigned char[size];
+ if (!map) {
+ state = gif_oom;
+ break;
+ }
+ }
+
+ /* Switch to the new local palette after it loads */
+ if (frame_reader) {
+ frame_reader->local_colormap = map;
+ frame_reader->local_colormap_size = num_colors;
+ frame_reader->is_local_colormap_defined = true;
+ }
+
+ if (len < size) {
+ // Use 'hold' pattern to get the image colormap
+ GETN(size, gif_image_colormap);
+ break;
+ }
+ // Copy everything and directly go to gif_lzw_start
+ if (frame_reader)
+ memcpy(frame_reader->local_colormap, buf, size);
+ buf += size;
+ len -= size;
+ } else if (frame_reader) {
+ /* Switch back to the global palette */
+ frame_reader->is_local_colormap_defined = false;
+ }
+ GETN(1, gif_lzw_start);
+ }
+ break;
+
+ case gif_image_colormap:
+ // Everything is already copied into local_colormap
+ GETN(1, gif_lzw_start);
+ break;
+
+ case gif_sub_block:
+ {
+ if ((count = *q) != 0)
+ /* Still working on the same image: Process next LZW data block */
+ {
+ /* Make sure there are still rows left. If the GIF data */
+ /* is corrupt, we may not get an explicit terminator. */
+ if (frame_reader && frame_reader->rows_remaining == 0) {
+ /* This is an illegal GIF, but we remain tolerant. */
+ GETN(1, gif_sub_block);
+ }
+ GETN(count, gif_lzw);
+ }
+ else
+ /* See if there are any more images in this sequence. */
+ {
+ images_decoded++;
+
+ // CALLBACK: The frame is now complete.
+ if (clientptr && frame_reader)
+ clientptr->frameComplete(images_decoded - 1, frame_reader->delay_time,
+ frame_reader->disposal_method);
+
+ /* Clear state from this image */
+ if (frame_reader) {
+ frame_reader->is_local_colormap_defined = false;
+ frame_reader->is_transparent = false;
+ }
+
+ GETN(1, gif_image_start);
+ }
+ }
+ break;
+
+ case gif_done:
+ // When the GIF is done, we can stop.
+ if (clientptr)
+ clientptr->gifComplete();
+ return true;
+
+ // Handle out of memory errors
+ case gif_oom:
+ return false;
+
+ // Handle general errors
+ case gif_error:
+ // nsGIFDecoder2::EndGIF(gs->clientptr, gs->loop_count);
+ return false;
+
+ // We shouldn't ever get here.
+ default:
+ break;
+ }
+ }
+
+ // Copy the leftover into gs->hold
+ bytes_in_hold = len;
+ if (len) {
+ // Add what we have sofar to the block
+ unsigned char* p;
+ if (state == gif_global_colormap)
+ p = global_colormap;
+ else if (state == gif_image_colormap)
+ p = frame_reader ? frame_reader->local_colormap : 0;
+ else
+ p = hold;
+ if (p)
+ memcpy(p, buf, len);
+ bytes_to_consume -= len;
+ }
+
+ if (clientptr)
+ clientptr->decodingHalted(0);
+ return true;
+}
+
+#endif // PLATFORM(CAIRO)