From 9f8dd008498cf8b3a8ea100e1dffdbaa5c408b9e Mon Sep 17 00:00:00 2001 From: "tor@cs.brown.edu" Date: Mon, 25 Jun 2007 09:41:21 -0700 Subject: [PATCH] Bug 196295 - move/merge GIF2.cpp into nsGIFDecoder2. Patch by alfredkayser@nl.ibm.com, r=biesi, sr=tor --- modules/libpr0n/build/nsImageModule.cpp | 3 - modules/libpr0n/decoders/gif/GIF2.cpp | 947 ---------------- modules/libpr0n/decoders/gif/GIF2.h | 14 - modules/libpr0n/decoders/gif/Makefile.in | 2 +- .../libpr0n/decoders/gif/nsGIFDecoder2.cpp | 1009 +++++++++++++---- modules/libpr0n/decoders/gif/nsGIFDecoder2.h | 50 +- 6 files changed, 835 insertions(+), 1190 deletions(-) delete mode 100644 modules/libpr0n/decoders/gif/GIF2.cpp diff --git a/modules/libpr0n/build/nsImageModule.cpp b/modules/libpr0n/build/nsImageModule.cpp index 1ca374c1f1d1..0ed1e6b3cc1d 100644 --- a/modules/libpr0n/build/nsImageModule.cpp +++ b/modules/libpr0n/build/nsImageModule.cpp @@ -312,9 +312,6 @@ PR_STATIC_CALLBACK(void) imglib_Shutdown(nsIModule* aSelf) { imgCache::Shutdown(); -#ifdef IMG_BUILD_DECODER_gif - nsGifShutdown(); -#endif } NS_IMPL_NSGETMODULE_WITH_CTOR_DTOR(nsImageLib2Module, components, diff --git a/modules/libpr0n/decoders/gif/GIF2.cpp b/modules/libpr0n/decoders/gif/GIF2.cpp deleted file mode 100644 index 0b1178e3a07a..000000000000 --- a/modules/libpr0n/decoders/gif/GIF2.cpp +++ /dev/null @@ -1,947 +0,0 @@ -/* -*- 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 - * - * 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 -#include "prtypes.h" -#include "prmem.h" -#include "prlog.h" -#include "GIF2.h" - -#include "nsGIFDecoder2.h" - -/* - * 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 gif_struct 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 \ - gs->bytes_to_consume = (n); \ - gs->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. -static void output_row(gif_struct *gs) -{ - int width, 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)) { - PRUintn 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 ((PRUintn)drow_end >= gs->height) - drow_end = gs->height - 1; - } - - /* Protect against too much image data */ - if ((PRUintn)drow_start >= gs->height) { - NS_WARNING("GIF2.cpp::output_row - too much image data"); - return; - } - - /* Check for scanline below edge of logical screen */ - if ((gs->y_offset + gs->irow) < gs->screen_height) { - /* Clip if right edge of image exceeds limits */ - if ((gs->x_offset + gs->width) > gs->screen_width) - width = gs->screen_width - gs->x_offset; - else - width = gs->width; - - if (width > 0) - /* Decoded data available callback */ - nsGIFDecoder2::HaveDecodedRow( - gs->clientptr, - gs->rowbuf, // Pointer to single scanline temporary buffer - drow_start, // Row number - drow_end - drow_start + 1, // Number of times to duplicate the row? - gs->ipass); // interlace pass (1-4) - } - - 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 */ -static int do_lzw(gif_struct *gs, const PRUint8 *q) -{ - int code; - int incode; - const PRUint8 *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 codesize = gs->codesize; - int codemask = gs->codemask; - int count = gs->count; - int oldcode = gs->oldcode; - int clear_code = gs->clear_code; - PRUint8 firstchar = gs->firstchar; - PRInt32 datum = gs->datum; - PRUint16 *prefix = gs->prefix; - PRUint8 *stackp = gs->stackp; - PRUint8 *suffix = gs->suffix; - PRUint8 *stack = gs->stack; - PRUint8 *rowp = gs->rowp; - PRUint8 *rowend = gs->rowend; - PRUintn rows_remaining = gs->rows_remaining; - - if (rowp == rowend) - return 0; - -#define OUTPUT_ROW(gs) \ - PR_BEGIN_MACRO \ - output_row(gs); \ - rows_remaining--; \ - rowp = gs->rowp; \ - if (!rows_remaining) \ - goto END; \ - PR_END_MACRO - - for (ch = q; count-- > 0; ch++) - { - /* Feed the next byte into the decoder's 32-bit input buffer. */ - datum += ((int32) *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(gs); - - 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 || code >= 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(gs); - } - } 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; - gs->count = count; - gs->oldcode = oldcode; - gs->firstchar = firstchar; - gs->datum = datum; - gs->stackp = stackp; - gs->rowp = rowp; - gs->rows_remaining = rows_remaining; - - return 0; -} - -/******************************************************************************* - * setup for gif_struct decoding - */ -PRBool GIFInit(gif_struct* gs, void* aClientData) -{ - NS_ASSERTION(gs, "Got null argument"); - if (!gs) - return PR_FALSE; - - // Clear out the structure, excluding the arrays - memset(gs, 0, sizeof(gif_struct)); - gs->clientptr = aClientData; - - // Start with the version (GIF89a|GIF87a) - gs->state = gif_type; - gs->bytes_to_consume = 6; - - return PR_TRUE; -} - -/******************************************************************************/ -/* - * process data arriving from the stream for the gif decoder - */ - -PRStatus gif_write(gif_struct *gs, const PRUint8 *buf, PRUint32 len) -{ - if (!gs || !len) - return PR_FAILURE; - - const PRUint8 *q = buf; - - // Add what we have sofar 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 - PRUint8* p; - if (gs->state == gif_global_colormap) - p = gs->global_colormap; - else if (gs->state == gif_image_colormap) - p = gs->local_colormap; - else if (gs->bytes_in_hold) - p = gs->hold; - else - p = nsnull; - - if (p) { - // Add what we have sofar to the block - PRUint32 l = PR_MIN(len, gs->bytes_to_consume); - memcpy(p+gs->bytes_in_hold, buf, l); - - if (l < gs->bytes_to_consume) { - // Not enough in 'buf' to complete current block, get more - gs->bytes_in_hold += l; - gs->bytes_to_consume -= l; - return PR_SUCCESS; - } - // Reset hold buffer count - gs->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 >= gs->bytes_to_consume; q=buf) { - // Eat the current block from the buffer, q keeps pointed at current block - buf += gs->bytes_to_consume; - len -= gs->bytes_to_consume; - - switch (gs->state) - { - case gif_lzw: - if (do_lzw(gs, q) < 0) { - gs->state = gif_error; - break; - } - GETN(1, gif_sub_block); - break; - - case gif_lzw_start: - { - /* Initialize LZW parser/decoder */ - gs->datasize = *q; - if (gs->datasize >= MAX_LZW_BITS) { - gs->state = gif_error; - break; - } - - gs->clear_code = 1 << gs->datasize; - gs->avail = gs->clear_code + 2; - gs->oldcode = -1; - gs->codesize = gs->datasize + 1; - gs->codemask = (1 << gs->codesize) - 1; - - gs->datum = gs->bits = 0; - - if (gs->clear_code >= MAX_BITS) { - gs->state = gif_error; - break; - } - - /* init the tables */ - for (int i = 0; i < gs->clear_code; i++) - gs->suffix[i] = i; - - gs->stackp = gs->stack; - - GETN(1, gif_sub_block); - } - break; - - /* All GIF files begin with "GIF87a" or "GIF89a" */ - case gif_type: - { - if (!strncmp((char*)q, "GIF89a", 6)) { - gs->version = 89; - } else if (!strncmp((char*)q, "GIF87a", 6)) { - gs->version = 87; - } else { - gs->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. - */ - - gs->screen_width = GETINT16(q); - gs->screen_height = GETINT16(q + 2); - - gs->screen_bgcolor = q[5]; - - gs->global_colormap_size = 2<<(q[4]&0x07); - - // XXX make callback - nsGIFDecoder2::BeginGIF( - gs->clientptr, - gs->screen_width, - gs->screen_height, - gs->screen_bgcolor); - - if (q[4] & 0x80) { /* global map */ - // Get the global colormap - const PRUint32 size = 3*gs->global_colormap_size; - if (len < size) { - // Use 'hold' pattern to get the global colormap - GETN(size, gif_global_colormap); - break; - } - // Copy everything and directly go to gif_lzw_start - memcpy(gs->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 */ - gs->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 (gs->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. - */ - gs->state = gif_done; - } else { - /* No images decoded, there is nothing to display. */ - gs->state = gif_error; - } - break; - } else - GETN(9, gif_image_header); - } - break; - - case gif_extension: - { - int len = gs->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 (*q & 0x1) { - gs->tpixel = q[3]; - gs->is_transparent = PR_TRUE; - } else { - gs->is_transparent = PR_FALSE; - // ignoring gfx control extension - } - gs->disposal_method = ((*q) >> 2) & 0x7; - // Some specs say 3rd bit (value 4), other specs say value 3 - // Let's choose 3 (the more popular) - if (gs->disposal_method == 4) - gs->disposal_method = 3; - gs->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) { - gs->loop_count = GETINT16(q + 1); - - /* Zero loop count is infinite animation loop request */ - if (gs->loop_count == 0) - gs->loop_count = -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 - gs->state = gif_error; // 0,3-7 are yet to be defined netscape - // extension codes - - break; - } - - case gif_image_header: - { - PRUintn height, width; - - /* Get image offsets, with respect to the screen origin */ - gs->x_offset = GETINT16(q); - gs->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 ((gs->images_decoded == 0) && - ((gs->screen_height < height) || (gs->screen_width < width) || - (gs->version == 87))) - { - gs->screen_height = height; - gs->screen_width = width; - gs->x_offset = 0; - gs->y_offset = 0; - - nsGIFDecoder2::BeginGIF(gs->clientptr, - gs->screen_width, - gs->screen_height, - gs->screen_bgcolor); - } - - /* Work around more broken GIF files that have zero image - width or height */ - if (!height || !width) { - height = gs->screen_height; - width = gs->screen_width; - if (!height || !width) { - gs->state = gif_error; - break; - } - } - - gs->height = height; - gs->width = width; - - nsGIFDecoder2::BeginImageFrame(gs->clientptr, - gs->images_decoded + 1, /* Frame number, 1-n */ - gs->x_offset, /* X offset in logical screen */ - gs->y_offset, /* Y offset in logical screen */ - width, - height); - - /* 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 (gs->screen_width < width) { - /* XXX Deviant! */ - - gs->rowbuf = (PRUint8*)PR_REALLOC(gs->rowbuf, width); - - if (!gs->rowbuf) { - gs->state = gif_oom; - break; - } - - gs->screen_width = width; - if (gs->screen_height < gs->height) - gs->screen_height = gs->height; - - } - else { - if (!gs->rowbuf) - gs->rowbuf = (PRUint8*)PR_MALLOC(gs->screen_width); - } - - if (!gs->rowbuf) { - gs->state = gif_oom; - break; - } - - if (q[8] & 0x40) { - gs->interlaced = PR_TRUE; - gs->ipass = 1; - } else { - gs->interlaced = PR_FALSE; - gs->ipass = 0; - } - - if (gs->images_decoded == 0) { - gs->progressive_display = PR_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 */ - gs->progressive_display = PR_FALSE; - } - - /* Clear state from last image */ - gs->irow = 0; - gs->rows_remaining = gs->height; - gs->rowend = gs->rowbuf + gs->width; - gs->rowp = gs->rowbuf; - - /* bits per pixel is q[8]&0x07 */ - - if (q[8] & 0x80) /* has a local colormap? */ - { - int num_colors = 2 << (q[8] & 0x7); - const PRUint32 size = 3*num_colors; - PRUint8 *map = gs->local_colormap; - if (!map || (num_colors > gs->local_colormap_size)) { - map = (PRUint8*)PR_REALLOC(map, size); - if (!map) { - gs->state = gif_oom; - break; - } - } - - /* Switch to the new local palette after it loads */ - gs->local_colormap = map; - gs->local_colormap_size = num_colors; - gs->is_local_colormap_defined = PR_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 - memcpy(gs->local_colormap, buf, size); - buf += size; - len -= size; - } else { - /* Switch back to the global palette */ - gs->is_local_colormap_defined = PR_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 ((gs->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 (gs->rows_remaining == 0) { - /* This is an illegal GIF, but we remain tolerant. */ -#ifdef DONT_TOLERATE_BROKEN_GIFS - gs->state = gif_error; - break; -#else - GETN(1, gif_sub_block); -#endif - } - GETN(gs->count, gif_lzw); - } - else - /* See if there are any more images in this sequence. */ - { - gs->images_decoded++; - - nsGIFDecoder2::EndImageFrame(gs->clientptr, - gs->images_decoded, - gs->delay_time); - - /* Clear state from this image */ - gs->is_local_colormap_defined = PR_FALSE; - gs->is_transparent = PR_FALSE; - - /* An image can specify a delay time before which to display - subsequent images. */ - if (gs->delay_time < MINIMUM_DELAY_TIME) - gs->delay_time = MINIMUM_DELAY_TIME; - - GETN(1, gif_image_start); - } - } - break; - - case gif_done: - nsGIFDecoder2::EndGIF(gs->clientptr, gs->loop_count); - return PR_SUCCESS; - break; - - // Handle out of memory errors - case gif_oom: - return PR_FAILURE; - - // Handle general errors - case gif_error: - nsGIFDecoder2::EndGIF(gs->clientptr, gs->loop_count); - return PR_SUCCESS; - - // We shouldn't ever get here. - default: - break; - } - } - - // Copy the leftover into gs->hold - gs->bytes_in_hold = len; - if (len) { - // Add what we have sofar to the block - PRUint8* p; - if (gs->state == gif_global_colormap) - p = gs->global_colormap; - else if (gs->state == gif_image_colormap) - p = gs->local_colormap; - else - p = gs->hold; - memcpy(p, buf, len); - gs->bytes_to_consume -= len; - } - - return PR_SUCCESS; -} - -//****************************************************************************** -/* Free up all the data structures associated with decoding a GIF */ -void gif_destroy(gif_struct *gs) -{ - if (!gs) - return; - - /* Clear any pending timeouts */ - if (gs->delay_time) - gs->delay_time = 0; - - PR_FREEIF(gs->rowbuf); - PR_FREEIF(gs->local_colormap); -} - diff --git a/modules/libpr0n/decoders/gif/GIF2.h b/modules/libpr0n/decoders/gif/GIF2.h index 62bf717541bf..29f3ed3c3d22 100644 --- a/modules/libpr0n/decoders/gif/GIF2.h +++ b/modules/libpr0n/decoders/gif/GIF2.h @@ -74,7 +74,6 @@ typedef enum { /* A GIF decoder's state */ typedef struct gif_struct { - void* clientptr; /* Parsing state machine */ gstate state; /* Curent decoder master state */ PRUint32 bytes_to_consume; /* Number of bytes to accumulate */ @@ -85,7 +84,6 @@ typedef struct gif_struct { int datasize; int codesize; int codemask; - int clear_code; /* Codeword used to trigger dictionary reset */ int avail; /* Index of next available slot in dictionary */ int oldcode; PRUint8 firstchar; @@ -112,7 +110,6 @@ typedef struct gif_struct { for this image in a multi-image GIF */ /* Global (multi-image) state */ - int screen_bgcolor; /* Logical screen background color */ int version; /* Either 89 for GIF89 or 87 for GIF87 */ PRUintn screen_width; /* Logical screen width & height */ PRUintn screen_height; @@ -134,16 +131,5 @@ typedef struct gif_struct { } gif_struct; - -/* These are the APIs that the client calls to intialize, -push data to, and shut down the GIF decoder. */ -PRBool GIFInit(gif_struct* gs, void* aClientData); - -void gif_destroy(gif_struct* aGIFStruct); - -PRStatus gif_write(gif_struct* aGIFStruct, const PRUint8 * buf, PRUint32 numbytes); - -PRBool gif_write_ready(const gif_struct* aGIFStruct); - #endif diff --git a/modules/libpr0n/decoders/gif/Makefile.in b/modules/libpr0n/decoders/gif/Makefile.in index 6e350fa8235f..4b0947ff3558 100644 --- a/modules/libpr0n/decoders/gif/Makefile.in +++ b/modules/libpr0n/decoders/gif/Makefile.in @@ -56,7 +56,7 @@ REQUIRES = xpcom \ imglib2 \ $(NULL) -CPPSRCS = GIF2.cpp nsGIFDecoder2.cpp +CPPSRCS = nsGIFDecoder2.cpp # nsGIFDecoder2.cpp includes imgContainer.h LOCAL_INCLUDES += -I$(topsrcdir)/modules/libpr0n/src diff --git a/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp b/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp index df774658d7ca..bcbced61faca 100644 --- a/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp +++ b/modules/libpr0n/decoders/gif/nsGIFDecoder2.cpp @@ -36,37 +36,73 @@ * 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 +#include "prtypes.h" #include "prmem.h" +#include "prlog.h" +#include "GIF2.h" #include "nsGIFDecoder2.h" #include "nsIInputStream.h" #include "nsIComponentManager.h" -#include "nsRecyclingAllocator.h" - #include "imgIContainerObserver.h" #include "imgILoad.h" #include "imgContainer.h" -/******************************************************************************* - * Gif decoder allocator +/* + * GETN(n, s) requests at least 'n' bytes available from 'q', at start of state 's' * - * For every image that gets loaded, we allocate a 'gif_struct' - * This allocator tries to keep one set of these around - * and reuses them; automatically fails over to use calloc/free when all - * buckets are full. + * 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 gif_struct is good enough. + * This buffer is only needed to copy left-over data from one GifWrite call to the next */ -const int kGifAllocatorNBucket = 3; -static nsRecyclingAllocator *gGifAllocator = nsnull; +#define GETN(n,s) \ + PR_BEGIN_MACRO \ + mGIFStruct.bytes_to_consume = (n); \ + mGIFStruct.state = (s); \ + PR_END_MACRO -void nsGifShutdown() -{ - // Release cached buffers from zlib allocator - delete gGifAllocator; - gGifAllocator = nsnull; -} +/* Get a 16-bit value stored in little-endian format */ +#define GETINT16(p) ((p)[1]<<8|(p)[0]) ////////////////////////////////////////////////////////////////////// @@ -78,17 +114,17 @@ NS_IMPL_ISUPPORTS1(nsGIFDecoder2, imgIDecoder) nsGIFDecoder2::nsGIFDecoder2() : mCurrentRow(-1) , mLastFlushedRow(-1) - , mGIFStruct(nsnull) , mRGBLine(nsnull) , mRGBLineMaxSize(0) - , mBackgroundRGBIndex(0) , mCurrentPass(0) , mLastFlushedPass(0) , mGIFOpen(PR_FALSE) { + // Clear out the structure, excluding the arrays + memset(&mGIFStruct, 0, sizeof(mGIFStruct)); } -nsGIFDecoder2::~nsGIFDecoder2(void) +nsGIFDecoder2::~nsGIFDecoder2() { Close(); } @@ -106,19 +142,9 @@ NS_IMETHODIMP nsGIFDecoder2::Init(imgILoad *aLoad) mImageContainer = do_CreateInstance("@mozilla.org/image/container;1"); aLoad->SetImage(mImageContainer); - if (!gGifAllocator) { - gGifAllocator = new nsRecyclingAllocator(kGifAllocatorNBucket, - NS_DEFAULT_RECYCLE_TIMEOUT, "gif"); - if (!gGifAllocator) - return NS_ERROR_FAILURE; - } - mGIFStruct = (gif_struct *)gGifAllocator->Malloc(sizeof(gif_struct)); - NS_ASSERTION(mGIFStruct, "gif_create failed"); - if (!mGIFStruct) - return NS_ERROR_FAILURE; - - // Call GIF decoder init routine - GIFInit(mGIFStruct, this); + // Start with the version (GIF89a|GIF87a) + mGIFStruct.state = gif_type; + mGIFStruct.bytes_to_consume = 6; return NS_OK; } @@ -132,19 +158,12 @@ NS_IMETHODIMP nsGIFDecoder2::Init(imgILoad *aLoad) /* void close (); */ NS_IMETHODIMP nsGIFDecoder2::Close() { - if (mGIFStruct) { - nsGIFDecoder2 *decoder = NS_STATIC_CAST(nsGIFDecoder2*, mGIFStruct->clientptr); - if (decoder->mImageFrame) - EndImageFrame(mGIFStruct->clientptr, mGIFStruct->images_decoded + 1, - mGIFStruct->delay_time); - if (decoder->mGIFOpen) - EndGIF(mGIFStruct->clientptr, mGIFStruct->loop_count); + if (mImageFrame) + EndImageFrame(); + EndGIF(); - gif_destroy(mGIFStruct); - if (gGifAllocator) - gGifAllocator->Free(mGIFStruct); - mGIFStruct = nsnull; - } + PR_FREEIF(mGIFStruct.rowbuf); + PR_FREEIF(mGIFStruct.local_colormap); PR_FREEIF(mRGBLine); return NS_OK; @@ -179,7 +198,7 @@ static NS_METHOD ReadDataOut(nsIInputStream* in, // Push any new rows according to mCurrentPass/mLastFlushedPass and // mCurrentRow/mLastFlushedRow. Note: caller is responsible for // updating mlastFlushed{Row,Pass}. -NS_METHOD +void nsGIFDecoder2::FlushImageData() { PRInt32 imgWidth; @@ -212,8 +231,6 @@ nsGIFDecoder2::FlushImageData() mObserver->OnDataAvailable(nsnull, mImageFrame, &r); } } - - return NS_OK; } //****************************************************************************** @@ -221,9 +238,8 @@ nsresult nsGIFDecoder2::ProcessData(unsigned char *data, PRUint32 count, PRUint3 { // Push the data to the GIF decoder - PRStatus result = gif_write(mGIFStruct, data, count); - if (result != PR_SUCCESS) - return NS_ERROR_FAILURE; + nsresult rv = GifWrite(data, count); + NS_ENSURE_SUCCESS(rv, rv); if (mImageFrame && mObserver) { FlushImageData(); @@ -245,7 +261,7 @@ NS_IMETHODIMP nsGIFDecoder2::WriteFrom(nsIInputStream *inStr, PRUint32 count, PR /* necko doesn't propagate the errors from ReadDataOut - take matters into our own hands. if we have at least one frame of an animated gif, then return success so we keep displaying as much as possible. */ - if (NS_SUCCEEDED(rv) && mGIFStruct && mGIFStruct->state == gif_error) { + if (NS_SUCCEEDED(rv) && mGIFStruct.state == gif_error) { PRUint32 numFrames = 0; if (mImageContainer) mImageContainer->GetNumFrames(&numFrames); @@ -262,229 +278,177 @@ NS_IMETHODIMP nsGIFDecoder2::WriteFrom(nsIInputStream *inStr, PRUint32 count, PR //****************************************************************************** //****************************************************************************** -int nsGIFDecoder2::BeginGIF( - void* aClientData, - PRUint32 aLogicalScreenWidth, - PRUint32 aLogicalScreenHeight, - PRUint8 aBackgroundRGBIndex) +void nsGIFDecoder2::BeginGIF() { // If we have passed an illogical screen size, bail and hope that we'll get // set later by the first frame's local image header. - if(aLogicalScreenWidth == 0 || aLogicalScreenHeight == 0) - return 0; + if (mGIFStruct.screen_width == 0 || mGIFStruct.screen_height == 0) + return; - // copy GIF info into imagelib structs - nsGIFDecoder2 *decoder = NS_STATIC_CAST(nsGIFDecoder2*, aClientData); + if (mObserver) + mObserver->OnStartDecode(nsnull); - decoder->mBackgroundRGBIndex = aBackgroundRGBIndex; + mImageContainer->Init(mGIFStruct.screen_width, mGIFStruct.screen_height, mObserver); - if (decoder->mObserver) - decoder->mObserver->OnStartDecode(nsnull); + if (mObserver) + mObserver->OnStartContainer(nsnull, mImageContainer); - decoder->mImageContainer->Init(aLogicalScreenWidth, aLogicalScreenHeight, decoder->mObserver); - - if (decoder->mObserver) - decoder->mObserver->OnStartContainer(nsnull, decoder->mImageContainer); - - decoder->mGIFOpen = PR_TRUE; - return 0; + mGIFOpen = PR_TRUE; } //****************************************************************************** -int nsGIFDecoder2::EndGIF( - void* aClientData, - int aAnimationLoopCount) +void nsGIFDecoder2::EndGIF() { - nsGIFDecoder2 *decoder = NS_STATIC_CAST(nsGIFDecoder2*, aClientData); + if (!mGIFOpen) + return; - if (!decoder->mGIFOpen) - return 0; - - if (decoder->mObserver) { - decoder->mObserver->OnStopContainer(nsnull, decoder->mImageContainer); - decoder->mObserver->OnStopDecode(nsnull, NS_OK, nsnull); + if (mObserver) { + mObserver->OnStopContainer(nsnull, mImageContainer); + mObserver->OnStopDecode(nsnull, NS_OK, nsnull); } - decoder->mImageContainer->SetLoopCount(aAnimationLoopCount); - decoder->mImageContainer->DecodingComplete(); + mImageContainer->SetLoopCount(mGIFStruct.loop_count); + mImageContainer->DecodingComplete(); - decoder->mGIFOpen = PR_FALSE; - return 0; + mGIFOpen = PR_FALSE; } //****************************************************************************** -int nsGIFDecoder2::BeginImageFrame( - void* aClientData, - PRUint32 aFrameNumber, /* Frame number, 1-n */ - PRUint32 aFrameXOffset, /* X offset in logical screen */ - PRUint32 aFrameYOffset, /* Y offset in logical screen */ - PRUint32 aFrameWidth, - PRUint32 aFrameHeight) +void nsGIFDecoder2::BeginImageFrame() { - nsGIFDecoder2* decoder = NS_STATIC_CAST(nsGIFDecoder2*, aClientData); - - decoder->mImageFrame = nsnull; // clear out our current frame reference - decoder->mGIFStruct->x_offset = aFrameXOffset; - decoder->mGIFStruct->y_offset = aFrameYOffset; - decoder->mGIFStruct->width = aFrameWidth; - decoder->mGIFStruct->height = aFrameHeight; + mImageFrame = nsnull; // clear out our current frame reference - if (aFrameNumber == 1) { + if (!mGIFStruct.images_decoded) { // Send a onetime OnDataAvailable (Display Refresh) for the first frame // if it has a y-axis offset. Otherwise, the area may never be refreshed // and the placeholder will remain on the screen. (Bug 37589) - PRInt32 imgWidth; - decoder->mImageContainer->GetWidth(&imgWidth); - if (aFrameYOffset > 0) { - nsIntRect r(0, 0, imgWidth, aFrameYOffset); - decoder->mObserver->OnDataAvailable(nsnull, decoder->mImageFrame, &r); + if (mGIFStruct.y_offset > 0) { + PRInt32 imgWidth; + mImageContainer->GetWidth(&imgWidth); + nsIntRect r(0, 0, imgWidth, mGIFStruct.y_offset); + mObserver->OnDataAvailable(nsnull, mImageFrame, &r); } } - - return 0; } //****************************************************************************** -int nsGIFDecoder2::EndImageFrame( - void* aClientData, - PRUint32 aFrameNumber, - PRUint32 aDelayTimeout) /* Time this frame should be displayed before the next frame - we can't have this in the image frame init because it doesn't - show up in the GIF frame header, it shows up in a sub control - block.*/ +void nsGIFDecoder2::EndImageFrame() { - nsGIFDecoder2* decoder = NS_STATIC_CAST(nsGIFDecoder2*, aClientData); - + // An image can specify a delay time before which to display + // subsequent images. + if (mGIFStruct.delay_time < MINIMUM_DELAY_TIME) + mGIFStruct.delay_time = MINIMUM_DELAY_TIME; + + mGIFStruct.images_decoded++; + // If mImageFrame hasn't been initialized, call HaveDecodedRow to init it // One reason why it may not be initialized is because the frame // is out of the bounds of the image. - if (!decoder->mImageFrame) { - HaveDecodedRow(aClientData,nsnull,0,0,0); + if (!mImageFrame) { + HaveDecodedRow(nsnull,0,0,0); } else { // We actually have the timeout information before we get the lzw encoded // image data, at least according to the spec, but we delay in setting the // timeout for the image until here to help ensure that we have the whole // image frame decoded before we go off and try to display another frame. - decoder->mImageFrame->SetTimeout(aDelayTimeout); + mImageFrame->SetTimeout(mGIFStruct.delay_time); } - decoder->mImageContainer->EndFrameDecode(aFrameNumber, aDelayTimeout); + mImageContainer->EndFrameDecode(mGIFStruct.images_decoded, mGIFStruct.delay_time); - if (decoder->mObserver && decoder->mImageFrame) { - decoder->FlushImageData(); + if (mObserver && mImageFrame) { + FlushImageData(); - if (aFrameNumber == 1) { + if (mGIFStruct.images_decoded == 1) { // If the first frame is smaller in height than the entire image, send a // OnDataAvailable (Display Refresh) for the area it does not have data for. // This will clear the remaining bits of the placeholder. (Bug 37589) PRInt32 imgHeight; - PRInt32 realFrameHeight = decoder->mGIFStruct->height + decoder->mGIFStruct->y_offset; + PRInt32 realFrameHeight = mGIFStruct.height + mGIFStruct.y_offset; - decoder->mImageContainer->GetHeight(&imgHeight); + mImageContainer->GetHeight(&imgHeight); if (imgHeight > realFrameHeight) { PRInt32 imgWidth; - decoder->mImageContainer->GetWidth(&imgWidth); + mImageContainer->GetWidth(&imgWidth); nsIntRect r(0, realFrameHeight, imgWidth, imgHeight - realFrameHeight); - decoder->mObserver->OnDataAvailable(nsnull, decoder->mImageFrame, &r); + mObserver->OnDataAvailable(nsnull, mImageFrame, &r); } } - decoder->mCurrentRow = decoder->mLastFlushedRow = -1; - decoder->mCurrentPass = decoder->mLastFlushedPass = 0; + mCurrentRow = mLastFlushedRow = -1; + mCurrentPass = mLastFlushedPass = 0; - decoder->mObserver->OnStopFrame(nsnull, decoder->mImageFrame); + mObserver->OnStopFrame(nsnull, mImageFrame); } - decoder->mImageFrame = nsnull; - decoder->mGIFStruct->is_transparent = PR_FALSE; - return 0; + // Clear state from this image + mImageFrame = nsnull; + mGIFStruct.is_local_colormap_defined = PR_FALSE; + mGIFStruct.is_transparent = PR_FALSE; } //****************************************************************************** // GIF decoder callback notification that it has decoded a row -int nsGIFDecoder2::HaveDecodedRow( - void* aClientData, +void nsGIFDecoder2::HaveDecodedRow( PRUint8* aRowBufPtr, // Pointer to single scanline temporary buffer int aRowNumber, // Row number? int aDuplicateCount, // Number of times to duplicate the row? int aInterlacePass) // interlace pass (1-4) { - nsGIFDecoder2* decoder = NS_STATIC_CAST(nsGIFDecoder2*, aClientData); - PRUint32 bpr; + const PRUint32 bpr = mGIFStruct.width * sizeof(PRUint32); + // We have to delay allocation of the image frame until now because - // we won't have control block info (transparency) until now. The conrol + // we won't have control block info (transparency) until now. The control // block of a GIF stream shows up after the image header since transparency // is added in GIF89a and control blocks are how the extensions are done. // How annoying. - if(! decoder->mImageFrame) { + if (!mImageFrame) { gfx_format format = gfxIFormats::RGB; - if (decoder->mGIFStruct->is_transparent) { + if (mGIFStruct.is_transparent) { format = gfxIFormats::RGB_A1; // XXX not really } // initialize the frame and append it to the container - decoder->mImageFrame = do_CreateInstance("@mozilla.org/gfx/image/frame;2"); - if (!decoder->mImageFrame || NS_FAILED(decoder->mImageFrame->Init( - decoder->mGIFStruct->x_offset, decoder->mGIFStruct->y_offset, - decoder->mGIFStruct->width, decoder->mGIFStruct->height, format, 24))) { - decoder->mImageFrame = 0; - return 0; + mImageFrame = do_CreateInstance("@mozilla.org/gfx/image/frame;2"); + if (!mImageFrame || NS_FAILED(mImageFrame->Init( + mGIFStruct.x_offset, mGIFStruct.y_offset, + mGIFStruct.width, mGIFStruct.height, format, 24))) { + mImageFrame = 0; + return; } - decoder->mImageFrame->SetFrameDisposalMethod(decoder->mGIFStruct->disposal_method); - decoder->mImageContainer->AppendFrame(decoder->mImageFrame); + mImageFrame->SetFrameDisposalMethod(mGIFStruct.disposal_method); + mImageContainer->AppendFrame(mImageFrame); - if (decoder->mObserver) - decoder->mObserver->OnStartFrame(nsnull, decoder->mImageFrame); + if (mObserver) + mObserver->OnStartFrame(nsnull, mImageFrame); - decoder->mImageFrame->GetImageBytesPerRow(&bpr); - - if (bpr > decoder->mRGBLineMaxSize) { - decoder->mRGBLine = (PRUint8 *)PR_REALLOC(decoder->mRGBLine, bpr); - decoder->mRGBLineMaxSize = bpr; + if (bpr > mRGBLineMaxSize) { + mRGBLine = (PRUint8 *)PR_REALLOC(mRGBLine, bpr); + mRGBLineMaxSize = bpr; } - } else { - decoder->mImageFrame->GetImageBytesPerRow(&bpr); } if (aRowBufPtr) { - PRInt32 width; - decoder->mImageFrame->GetWidth(&width); - - gfx_format format; - decoder->mImageFrame->GetFormat(&format); - - // XXX map the data into colors - int cmapsize; - PRUint8* cmap; - cmapsize = decoder->mGIFStruct->global_colormap_size; - cmap = decoder->mGIFStruct->global_colormap; - - if(decoder->mGIFStruct->global_colormap && - decoder->mGIFStruct->screen_bgcolor < cmapsize) { - gfx_color bgColor = 0; - PRUint32 bgIndex = decoder->mGIFStruct->screen_bgcolor * 3; - bgColor |= cmap[bgIndex]; - bgColor |= cmap[bgIndex + 1] << 8; - bgColor |= cmap[bgIndex + 2] << 16; - decoder->mImageFrame->SetBackgroundColor(bgColor); - } - if (decoder->mGIFStruct->is_local_colormap_defined) { - cmapsize = decoder->mGIFStruct->local_colormap_size; - cmap = decoder->mGIFStruct->local_colormap; + // Map the data into colors + int cmapsize = mGIFStruct.global_colormap_size; + PRUint8* cmap = mGIFStruct.global_colormap; + if (mGIFStruct.is_local_colormap_defined) { + cmapsize = mGIFStruct.local_colormap_size; + cmap = mGIFStruct.local_colormap; } if (!cmap) { // cmap could have null value if the global color table flag is 0 - nsIntRect r(0, aRowNumber, width, aDuplicateCount); - imgContainer::ClearFrame(decoder->mImageFrame, r); + nsIntRect r(0, aRowNumber, mGIFStruct.width, aDuplicateCount); + imgContainer::ClearFrame(mImageFrame, r); } else { PRUint8* rowBufIndex = aRowBufPtr; - PRUint32 *rgbRowIndex = (PRUint32*)decoder->mRGBLine; + PRUint32* rgbRowIndex = (PRUint32*)mRGBLine; - PRInt32 tpixel = - decoder->mGIFStruct->is_transparent ? decoder->mGIFStruct->tpixel : -1; + const PRInt32 tpixel = + mGIFStruct.is_transparent ? mGIFStruct.tpixel : -1; - while (rowBufIndex != decoder->mGIFStruct->rowend) { + while (rowBufIndex != mGIFStruct.rowend) { if (*rowBufIndex >= cmapsize || *rowBufIndex == tpixel) { *rgbRowIndex++ = 0x00000000; ++rowBufIndex; @@ -499,14 +463,677 @@ int nsGIFDecoder2::HaveDecodedRow( ++rowBufIndex; } for (int i=0; imImageFrame->SetImageData(decoder->mRGBLine, bpr, (aRowNumber+i)*bpr); + mImageFrame->SetImageData(mRGBLine, bpr, (aRowNumber+i)*bpr); } - decoder->mCurrentRow = aRowNumber + aDuplicateCount - 1; - decoder->mCurrentPass = aInterlacePass; + mCurrentRow = aRowNumber + aDuplicateCount - 1; + mCurrentPass = aInterlacePass; if (aInterlacePass == 1) - decoder->mLastFlushedPass = aInterlacePass; // interlaced starts at 1 + mLastFlushedPass = aInterlacePass; // interlaced starts at 1 + } +} + + +//****************************************************************************** +// Send the data to the display front-end. +PRUint32 nsGIFDecoder2::OutputRow() +{ + int width, drow_start, drow_end; + + drow_start = drow_end = mGIFStruct.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 (mGIFStruct.progressive_display && mGIFStruct.interlaced && (mGIFStruct.ipass < 4)) { + /* ipass = 1,2,3 results in resp. row_dup = 7,3,1 and row_shift = 3,1,0 */ + const PRUint32 row_dup = 15 >> mGIFStruct.ipass; + const PRUint32 row_shift = row_dup >> 1; + + drow_start -= row_shift; + drow_end = drow_start + row_dup; + + /* Extend if bottom edge isn't covered because of the shift upward. */ + if (((mGIFStruct.height - 1) - drow_end) <= row_shift) + drow_end = mGIFStruct.height - 1; + + /* Clamp first and last rows to upper and lower edge of image. */ + if (drow_start < 0) + drow_start = 0; + if ((PRUintn)drow_end >= mGIFStruct.height) + drow_end = mGIFStruct.height - 1; } - return 0; + /* Protect against too much image data */ + if ((PRUintn)drow_start >= mGIFStruct.height) { + NS_WARNING("GIF2.cpp::OutputRow - too much image data"); + return 0; + } + + /* Check for scanline below edge of logical screen */ + if ((mGIFStruct.y_offset + mGIFStruct.irow) < mGIFStruct.screen_height) { + /* Clip if right edge of image exceeds limits */ + if ((mGIFStruct.x_offset + mGIFStruct.width) > mGIFStruct.screen_width) + width = mGIFStruct.screen_width - mGIFStruct.x_offset; + else + width = mGIFStruct.width; + + if (width > 0) + /* Decoded data available callback */ + HaveDecodedRow( + mGIFStruct.rowbuf, // Pointer to single scanline temporary buffer + drow_start, // Row number + drow_end - drow_start + 1, // Number of times to duplicate the row? + mGIFStruct.ipass); // interlace pass (1-4) + } + + mGIFStruct.rowp = mGIFStruct.rowbuf; + + if (!mGIFStruct.interlaced) { + mGIFStruct.irow++; + } else { + static const PRUint8 kjump[5] = { 1, 8, 8, 4, 2 }; + do { + // Row increments resp. per 8,8,4,2 rows + mGIFStruct.irow += kjump[mGIFStruct.ipass]; + if (mGIFStruct.irow >= mGIFStruct.height) { + // Next pass starts resp. at row 4,2,1,0 + mGIFStruct.irow = 8 >> mGIFStruct.ipass; + mGIFStruct.ipass++; + } + } while (mGIFStruct.irow >= mGIFStruct.height); + } + + return --mGIFStruct.rows_remaining; +} + +//****************************************************************************** +/* Perform Lempel-Ziv-Welch decoding */ +PRBool +nsGIFDecoder2::DoLzw(const PRUint8 *q) +{ + /* 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 = mGIFStruct.avail; + int bits = mGIFStruct.bits; + int codesize = mGIFStruct.codesize; + int codemask = mGIFStruct.codemask; + int count = mGIFStruct.count; + int oldcode = mGIFStruct.oldcode; + const int clear_code = ClearCode(); + PRUint8 firstchar = mGIFStruct.firstchar; + PRInt32 datum = mGIFStruct.datum; + PRUint16 *prefix = mGIFStruct.prefix; + PRUint8 *stackp = mGIFStruct.stackp; + PRUint8 *suffix = mGIFStruct.suffix; + PRUint8 *stack = mGIFStruct.stack; + PRUint8 *rowp = mGIFStruct.rowp; + PRUint8 *rowend = mGIFStruct.rowend; + + if (rowp == rowend) + return PR_TRUE; + +#define OUTPUT_ROW() \ + PR_BEGIN_MACRO \ + if (!OutputRow()) \ + goto END; \ + rowp = mGIFStruct.rowp; \ + PR_END_MACRO + + for (const PRUint8* ch = q; count-- > 0; ch++) + { + /* Feed the next byte into the decoder's 32-bit input buffer. */ + datum += ((int32) *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 */ + int code = datum & codemask; + datum >>= codesize; + bits -= codesize; + + /* Reset the dictionary to its original state, if requested */ + if (code == clear_code) { + codesize = mGIFStruct.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 */ + return (mGIFStruct.rows_remaining == 0); + } + + if (oldcode == -1) { + *rowp++ = suffix[code]; + if (rowp == rowend) + OUTPUT_ROW(); + + firstchar = oldcode = code; + continue; + } + + int incode = code; + if (code >= avail) { + *stackp++ = firstchar; + code = oldcode; + + if (stackp == stack + MAX_BITS) + return PR_FALSE; + } + + while (code >= clear_code) + { + if (code == prefix[code]) + return PR_FALSE; + + *stackp++ = suffix[code]; + code = prefix[code]; + + if (stackp == stack + MAX_BITS) + return PR_FALSE; + } + + *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 */ + mGIFStruct.avail = avail; + mGIFStruct.bits = bits; + mGIFStruct.codesize = codesize; + mGIFStruct.codemask = codemask; + mGIFStruct.count = count; + mGIFStruct.oldcode = oldcode; + mGIFStruct.firstchar = firstchar; + mGIFStruct.datum = datum; + mGIFStruct.stackp = stackp; + mGIFStruct.rowp = rowp; + + return PR_TRUE; +} + +/******************************************************************************/ +/* + * process data arriving from the stream for the gif decoder + */ + +nsresult nsGIFDecoder2::GifWrite(const PRUint8 *buf, PRUint32 len) +{ + if (!buf || !len) + return NS_ERROR_FAILURE; + + const PRUint8 *q = buf; + + // Add what we have sofar 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 + PRUint8* p = (mGIFStruct.state == gif_global_colormap) ? mGIFStruct.global_colormap : + (mGIFStruct.state == gif_image_colormap) ? mGIFStruct.local_colormap : + (mGIFStruct.bytes_in_hold) ? mGIFStruct.hold : nsnull; + if (p) { + // Add what we have sofar to the block + PRUint32 l = PR_MIN(len, mGIFStruct.bytes_to_consume); + memcpy(p+mGIFStruct.bytes_in_hold, buf, l); + + if (l < mGIFStruct.bytes_to_consume) { + // Not enough in 'buf' to complete current block, get more + mGIFStruct.bytes_in_hold += l; + mGIFStruct.bytes_to_consume -= l; + return NS_OK; + } + // Reset hold buffer count + mGIFStruct.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 >= mGIFStruct.bytes_to_consume; q=buf) { + // Eat the current block from the buffer, q keeps pointed at current block + buf += mGIFStruct.bytes_to_consume; + len -= mGIFStruct.bytes_to_consume; + + switch (mGIFStruct.state) + { + case gif_lzw: + if (!DoLzw(q)) { + mGIFStruct.state = gif_error; + break; + } + GETN(1, gif_sub_block); + break; + + case gif_lzw_start: + { + /* Initialize LZW parser/decoder */ + mGIFStruct.datasize = *q; + const int clear_code = ClearCode(); + if (mGIFStruct.datasize > MAX_LZW_BITS || + clear_code >= MAX_BITS) { + mGIFStruct.state = gif_error; + break; + } + + mGIFStruct.avail = clear_code + 2; + mGIFStruct.oldcode = -1; + mGIFStruct.codesize = mGIFStruct.datasize + 1; + mGIFStruct.codemask = (1 << mGIFStruct.codesize) - 1; + mGIFStruct.datum = mGIFStruct.bits = 0; + + /* init the tables */ + for (int i = 0; i < clear_code; i++) + mGIFStruct.suffix[i] = i; + + mGIFStruct.stackp = mGIFStruct.stack; + + GETN(1, gif_sub_block); + } + break; + + /* All GIF files begin with "GIF87a" or "GIF89a" */ + case gif_type: + if (!strncmp((char*)q, "GIF89a", 6)) { + mGIFStruct.version = 89; + } else if (!strncmp((char*)q, "GIF87a", 6)) { + mGIFStruct.version = 87; + } else { + mGIFStruct.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. + */ + + mGIFStruct.screen_width = GETINT16(q); + mGIFStruct.screen_height = GETINT16(q + 2); + mGIFStruct.global_colormap_size = 2<<(q[4]&0x07); + + // screen_bgcolor is not used + //mGIFStruct.screen_bgcolor = q[5]; + // q[6] = Pixel Aspect Ratio + // Not used + // float aspect = (float)((q[6] + 15) / 64.0); + + // Start the GIF container + BeginGIF(); + + if (q[4] & 0x80) { /* global map */ + // Get the global colormap + const PRUint32 size = 3*mGIFStruct.global_colormap_size; + if (len < size) { + // Use 'hold' pattern to get the global colormap + GETN(size, gif_global_colormap); + break; + } + // Copy everything and directly go to gif_lzw_start + memcpy(mGIFStruct.global_colormap, buf, size); + buf += size; + len -= size; + } + + GETN(1, gif_image_start); + break; + + case gif_global_colormap: + // Everything is already copied into global_colormap + GETN(1, gif_image_start); + break; + + case gif_image_start: + switch (*q) { + case ';': /* terminator */ + mGIFStruct.state = gif_done; + break; + + case '!': /* extension */ + GETN(2, gif_extension); + break; + + case ',': + GETN(9, gif_image_header); + break; + + default: + /* 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 (mGIFStruct.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. + */ + mGIFStruct.state = gif_done; + } else { + /* No images decoded, there is nothing to display. */ + mGIFStruct.state = gif_error; + } + } + break; + + case gif_extension: + mGIFStruct.bytes_to_consume = q[1]; + if (mGIFStruct.bytes_to_consume) { + switch (*q) { + case 0xf9: + mGIFStruct.state = gif_control_extension; + break; + + case 0xff: + mGIFStruct.state = gif_application_extension; + break; + + case 0xfe: + mGIFStruct.state = gif_consume_comment; + break; + + default: + mGIFStruct.state = gif_skip_block; + } + } 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: + mGIFStruct.is_transparent = *q & 0x1; + mGIFStruct.tpixel = q[3]; + mGIFStruct.disposal_method = ((*q) >> 2) & 0x7; + // Some specs say 3rd bit (value 4), other specs say value 3 + // Let's choose 3 (the more popular) + if (mGIFStruct.disposal_method == 4) + mGIFStruct.disposal_method = 3; + mGIFStruct.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: + switch (q[0] & 7) { + case 1: + /* Loop entire animation specified # of times. Only read the + loop count during the first iteration. */ + mGIFStruct.loop_count = GETINT16(q + 1); + + /* Zero loop count is infinite animation loop request */ + if (mGIFStruct.loop_count == 0) + mGIFStruct.loop_count = -1; + + GETN(1, gif_netscape_extension_block); + break; + + case 2: + /* Wait for specified # of bytes to enter buffer */ + // 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); + break; + + default: + // 0,3-7 are yet to be defined netscape extension codes + mGIFStruct.state = gif_error; + } + break; + + case gif_image_header: + /* Get image offsets, with respect to the screen origin */ + mGIFStruct.x_offset = GETINT16(q); + mGIFStruct.y_offset = GETINT16(q + 2); + + /* Get image width and height. */ + mGIFStruct.width = GETINT16(q + 4); + mGIFStruct.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 (!mGIFStruct.images_decoded && + ((mGIFStruct.screen_height < mGIFStruct.height) || + (mGIFStruct.screen_width < mGIFStruct.width) || + (mGIFStruct.version == 87))) + { + mGIFStruct.screen_height = mGIFStruct.height; + mGIFStruct.screen_width = mGIFStruct.width; + mGIFStruct.x_offset = 0; + mGIFStruct.y_offset = 0; + + BeginGIF(); + } + + /* Work around more broken GIF files that have zero image + width or height */ + if (!mGIFStruct.height || !mGIFStruct.width) { + mGIFStruct.height = mGIFStruct.screen_height; + mGIFStruct.width = mGIFStruct.screen_width; + if (!mGIFStruct.height || !mGIFStruct.width) { + mGIFStruct.state = gif_error; + break; + } + } + + BeginImageFrame(); + + /* 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 (mGIFStruct.screen_width < mGIFStruct.width) { + /* XXX Deviant! */ + + mGIFStruct.rowbuf = (PRUint8*)PR_REALLOC(mGIFStruct.rowbuf, mGIFStruct.width); + mGIFStruct.screen_width = mGIFStruct.width; + } else if (!mGIFStruct.rowbuf) { + mGIFStruct.rowbuf = (PRUint8*)PR_MALLOC(mGIFStruct.screen_width); + } + + if (!mGIFStruct.rowbuf) { + mGIFStruct.state = gif_oom; + break; + } + if (mGIFStruct.screen_height < mGIFStruct.height) + mGIFStruct.screen_height = mGIFStruct.height; + + if (q[8] & 0x40) { + mGIFStruct.interlaced = PR_TRUE; + mGIFStruct.ipass = 1; + } else { + mGIFStruct.interlaced = PR_FALSE; + mGIFStruct.ipass = 0; + } + + /* Only apply the Haeberli display hack on the first frame */ + mGIFStruct.progressive_display = (mGIFStruct.images_decoded == 0); + + /* Clear state from last image */ + mGIFStruct.irow = 0; + mGIFStruct.rows_remaining = mGIFStruct.height; + mGIFStruct.rowend = mGIFStruct.rowbuf + mGIFStruct.width; + mGIFStruct.rowp = mGIFStruct.rowbuf; + + /* bits per pixel is q[8]&0x07 */ + + if (q[8] & 0x80) /* has a local colormap? */ + { + const int num_colors = 2 << (q[8] & 0x7); + const PRUint32 size = 3*num_colors; + PRUint8 *map = mGIFStruct.local_colormap; + if (!map || (num_colors > mGIFStruct.local_colormap_size)) { + map = (PRUint8*)PR_REALLOC(map, size); + if (!map) { + mGIFStruct.state = gif_oom; + break; + } + mGIFStruct.local_colormap = map; + } + + /* Switch to the new local palette after it loads */ + mGIFStruct.local_colormap_size = num_colors; + mGIFStruct.is_local_colormap_defined = PR_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 + memcpy(mGIFStruct.local_colormap, buf, size); + buf += size; + len -= size; + } else { + /* Switch back to the global palette */ + mGIFStruct.is_local_colormap_defined = PR_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: + mGIFStruct.count = *q; + if (mGIFStruct.count) { + /* 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 (!mGIFStruct.rows_remaining) { +#ifdef DONT_TOLERATE_BROKEN_GIFS + mGIFStruct.state = gif_error; +#else + /* This is an illegal GIF, but we remain tolerant. */ + GETN(1, gif_sub_block); +#endif + break; + } + GETN(mGIFStruct.count, gif_lzw); + } else { + /* See if there are any more images in this sequence. */ + EndImageFrame(); + GETN(1, gif_image_start); + } + break; + + case gif_done: + case gif_error: + EndGIF(); + return NS_OK; + break; + + // Handle out of memory errors + case gif_oom: + return NS_ERROR_OUT_OF_MEMORY; + + // We shouldn't ever get here. + default: + break; + } + } + + // Copy the leftover into mGIFStruct.hold + mGIFStruct.bytes_in_hold = len; + if (len) { + // Add what we have sofar to the block + PRUint8* p = (mGIFStruct.state == gif_global_colormap) ? mGIFStruct.global_colormap : + (mGIFStruct.state == gif_image_colormap) ? mGIFStruct.local_colormap : + mGIFStruct.hold; + memcpy(p, buf, len); + mGIFStruct.bytes_to_consume -= len; + } + + return NS_OK; } diff --git a/modules/libpr0n/decoders/gif/nsGIFDecoder2.h b/modules/libpr0n/decoders/gif/nsGIFDecoder2.h index 7fd1ab3219b2..020c5e974d50 100644 --- a/modules/libpr0n/decoders/gif/nsGIFDecoder2.h +++ b/modules/libpr0n/decoders/gif/nsGIFDecoder2.h @@ -70,58 +70,40 @@ public: nsresult ProcessData(unsigned char *data, PRUint32 count, PRUint32 *_retval); - NS_METHOD FlushImageData(); +private: + /* These functions will be called when the decoder has a decoded row, + * frame size information, etc. */ - /* These functions will be called when the decoder has a decoded - * rows, frame size information, etc. */ + void BeginGIF(); + void EndGIF(); + void BeginImageFrame(); + void EndImageFrame(); + void FlushImageData(); - static int BeginGIF( - void* aClientData, - PRUint32 aLogicalScreenWidth, - PRUint32 aLogicalScreenHeight, - PRUint8 aBackgroundRGBIndex); - - static int EndGIF( - void* aClientData, - int aAnimationLoopCount); - - static int BeginImageFrame( - void* aClientData, - PRUint32 aFrameNumber, /* Frame number, 1-n */ - PRUint32 aFrameXOffset, /* X offset in logical screen */ - PRUint32 aFrameYOffset, /* Y offset in logical screen */ - PRUint32 aFrameWidth, - PRUint32 aFrameHeight); - - static int EndImageFrame( - void* aClientData, - PRUint32 aFrameNumber, - PRUint32 aDelayTimeout); - - static int HaveDecodedRow( - void* aClientData, + nsresult GifWrite(const PRUint8 * buf, PRUint32 numbytes); + PRUint32 OutputRow(); + PRBool DoLzw(const PRUint8 *q); + void HaveDecodedRow( PRUint8* aRowBufPtr, /* Pointer to single scanline temporary buffer */ int aRow, /* Row number? */ int aDuplicateCount, /* Number of times to duplicate the row? */ int aInterlacePass); -private: + inline int ClearCode() const { return 1 << mGIFStruct.datasize; } + nsCOMPtr mImageContainer; nsCOMPtr mImageFrame; nsCOMPtr mObserver; // this is just qi'd from mRequest for speed PRInt32 mCurrentRow; PRInt32 mLastFlushedRow; - gif_struct *mGIFStruct; - PRUint8 *mRGBLine; PRUint32 mRGBLineMaxSize; - PRUint8 mBackgroundRGBIndex; PRUint8 mCurrentPass; PRUint8 mLastFlushedPass; PRPackedBool mGIFOpen; + + gif_struct mGIFStruct; }; -void nsGifShutdown(); - #endif