| /* -*- 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) |
| |
| 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 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) { |
| 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; |
| |
| /* Check for scanline below edge of logical screen */ |
| if ((gs->y_offset + gs->irow) < screen_height) { |
| /* Clip if right edge of image exceeds limits */ |
| if ((gs->x_offset + gs->width) > screen_width) |
| width = screen_width - gs->x_offset; |
| else |
| width = gs->width; |
| |
| // CALLBACK: Let the client know we have decoded a row. |
| if (width > 0 && clientptr && frame_reader) |
| clientptr->haveDecodedRow(images_count - 1, frame_reader->rowbuf, frame_reader->rowend, |
| drow_start, drow_end - drow_start + 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) |
| return false; |
| |
| 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; |
| 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 |
| } |
| frame_reader->disposal_method = (gdispose)(((*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 = (gdispose)3; |
| unsigned short n = GETINT16(q + 1); |
| // Many annoying ads specify a 0 duration to make an image flash as quickly as possible. |
| // We follow Firefox's behavior and use a duration of 100 ms for any frames that specify |
| // a duration of <= 10 ms. See gfxImageFrame::GetTimeout in Gecko or Radar 4051389 for more. |
| frame_reader->delay_time = n <= 1 ? 100 : n * 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; |
| frame_reader->rowbuf = new unsigned char[width]; |
| |
| if (!frame_reader->rowbuf) { |
| state = gif_oom; |
| break; |
| } |
| |
| screen_width = width; |
| if (screen_height < frame_reader->height) |
| screen_height = frame_reader->height; |
| } |
| else { |
| if (!frame_reader->rowbuf) |
| frame_reader->rowbuf = new unsigned char[screen_width]; |
| } |
| |
| if (!frame_reader->rowbuf) { |
| state = gif_oom; |
| break; |
| } |
| |
| 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 == DISPOSE_KEEP); |
| |
| /* 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 true; |
| |
| // 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; |
| } |
| |
| return true; |
| } |
| |
| #endif // PLATFORM(CAIRO) |