| /* -*- 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 Inc. |
| * |
| * 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" |
| |
| using WebCore::GIFImageDecoder; |
| |
| // 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) \ |
| do { \ |
| m_bytesToConsume = (n); \ |
| m_state = (s); \ |
| } while (0) |
| |
| // 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. |
| bool GIFLZWContext::outputRow() |
| { |
| int drowStart = irow; |
| int drowEnd = 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 (m_frameContext->progressiveDisplay && m_frameContext->interlaced && ipass < 4) { |
| unsigned rowDup = 0; |
| unsigned rowShift = 0; |
| |
| switch (ipass) { |
| case 1: |
| rowDup = 7; |
| rowShift = 3; |
| break; |
| case 2: |
| rowDup = 3; |
| rowShift = 1; |
| break; |
| case 3: |
| rowDup = 1; |
| rowShift = 0; |
| break; |
| default: |
| break; |
| } |
| |
| drowStart -= rowShift; |
| drowEnd = drowStart + rowDup; |
| |
| // Extend if bottom edge isn't covered because of the shift upward. |
| if (((m_frameContext->height - 1) - drowEnd) <= rowShift) |
| drowEnd = m_frameContext->height - 1; |
| |
| // Clamp first and last rows to upper and lower edge of image. |
| if (drowStart < 0) |
| drowStart = 0; |
| |
| if ((unsigned)drowEnd >= m_frameContext->height) |
| drowEnd = m_frameContext->height - 1; |
| } |
| |
| // Protect against too much image data. |
| if ((unsigned)drowStart >= m_frameContext->height) |
| return true; |
| |
| // CALLBACK: Let the client know we have decoded a row. |
| if (!m_client->haveDecodedRow(m_frameContext->frameId, rowBuffer, m_frameContext->width, |
| drowStart, drowEnd - drowStart + 1, m_frameContext->progressiveDisplay && m_frameContext->interlaced && ipass > 1)) |
| return false; |
| |
| if (!m_frameContext->interlaced) |
| irow++; |
| else { |
| do { |
| switch (ipass) { |
| case 1: |
| irow += 8; |
| if (irow >= m_frameContext->height) { |
| ipass++; |
| irow = 4; |
| } |
| break; |
| |
| case 2: |
| irow += 8; |
| if (irow >= m_frameContext->height) { |
| ipass++; |
| irow = 2; |
| } |
| break; |
| |
| case 3: |
| irow += 4; |
| if (irow >= m_frameContext->height) { |
| ipass++; |
| irow = 1; |
| } |
| break; |
| |
| case 4: |
| irow += 2; |
| if (irow >= m_frameContext->height) { |
| ipass++; |
| irow = 0; |
| } |
| break; |
| |
| default: |
| break; |
| } |
| } while (irow > (m_frameContext->height - 1)); |
| } |
| return true; |
| } |
| |
| // Perform Lempel-Ziv-Welch decoding. |
| // Returns true if decoding was successful. In this case the block will have been completely consumed and/or rowsRemaining will be 0. |
| // Otherwise, decoding failed; returns false in this case, which will always cause the GIFImageReader to set the "decode failed" flag. |
| bool GIFLZWContext::doLZW(const unsigned char* block, size_t bytesInBlock) |
| { |
| int code; |
| int incode; |
| const unsigned char *ch; |
| |
| if (rowPosition == rowBuffer.size()) |
| return true; |
| |
| #define OUTPUT_ROW \ |
| do { \ |
| if (!outputRow()) \ |
| return false; \ |
| rowsRemaining--; \ |
| rowPosition = 0; \ |
| if (!rowsRemaining) \ |
| return true; \ |
| } while (0) |
| |
| for (ch = block; bytesInBlock-- > 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 == clearCode) { |
| codesize = m_frameContext->datasize + 1; |
| codemask = (1 << codesize) - 1; |
| avail = clearCode + 2; |
| oldcode = -1; |
| continue; |
| } |
| |
| // Check for explicit end-of-stream code. |
| if (code == (clearCode + 1)) { |
| // end-of-stream should only appear after all image data. |
| if (!rowsRemaining) |
| return true; |
| return false; |
| } |
| |
| if (oldcode == -1) { |
| rowBuffer[rowPosition++] = suffix[code]; |
| if (rowPosition == rowBuffer.size()) |
| OUTPUT_ROW; |
| |
| firstchar = oldcode = code; |
| continue; |
| } |
| |
| incode = code; |
| if (code >= avail) { |
| stack[stackp++] = firstchar; |
| code = oldcode; |
| |
| if (stackp == MAX_BYTES) |
| return false; |
| } |
| |
| while (code >= clearCode) { |
| if (code >= MAX_BYTES || code == prefix[code]) |
| return false; |
| |
| // Even though suffix[] only holds characters through suffix[avail - 1], |
| // allowing code >= avail here lets us be more tolerant of malformed |
| // data. As long as code < MAX_BYTES, the only risk is a garbled image, |
| // which is no worse than refusing to display it. |
| stack[stackp++] = suffix[code]; |
| code = prefix[code]; |
| |
| if (stackp == MAX_BYTES) |
| return false; |
| } |
| |
| stack[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)) && (avail < 4096)) { |
| codesize++; |
| codemask += avail; |
| } |
| } |
| oldcode = incode; |
| |
| // Copy the decoded data out to the scanline buffer. |
| do { |
| rowBuffer[rowPosition++] = stack[--stackp]; |
| if (rowPosition == rowBuffer.size()) |
| OUTPUT_ROW; |
| } while (stackp > 0); |
| } |
| } |
| |
| return true; |
| } |
| |
| // Perform decoding for this frame. frameDecoded will be true if the entire frame is decoded. |
| // Returns false if a decoding error occurred. This is a fatal error and causes the GIFImageReader to set the "decode failed" flag. |
| // Otherwise, either not enough data is available to decode further than before, or the new data has been decoded successfully; returns true in this case. |
| bool GIFFrameContext::decode(const unsigned char* data, size_t length, WebCore::GIFImageDecoder* client, bool* frameDecoded) |
| { |
| *frameDecoded = false; |
| if (!m_lzwContext) { |
| // Wait for more data to properly initialize GIFLZWContext. |
| if (!isDataSizeDefined() || !isHeaderDefined()) |
| return true; |
| |
| m_lzwContext = makeUnique<GIFLZWContext>(client, this); |
| if (!m_lzwContext->prepareToDecode()) { |
| m_lzwContext = nullptr; |
| return false; |
| } |
| |
| m_currentLzwBlock = 0; |
| } |
| |
| // Some bad GIFs have extra blocks beyond the last row, which we don't want to decode. |
| while (m_currentLzwBlock < m_lzwBlocks.size() && m_lzwContext->hasRemainingRows()) { |
| size_t blockPosition = m_lzwBlocks[m_currentLzwBlock].blockPosition; |
| size_t blockSize = m_lzwBlocks[m_currentLzwBlock].blockSize; |
| if (blockPosition + blockSize > length) |
| return false; |
| if (!m_lzwContext->doLZW(data + blockPosition, blockSize)) |
| return false; |
| ++m_currentLzwBlock; |
| } |
| |
| // If this frame is data complete then the previous loop must have completely decoded all LZW blocks. |
| // There will be no more decoding for this frame so it's time to cleanup. |
| if (isComplete()) { |
| *frameDecoded = true; |
| m_lzwContext = nullptr; |
| } |
| return true; |
| } |
| |
| // Decode all frames before haltAtFrame. |
| // This method uses GIFFrameContext:decode() to decode each frame; decoding error is reported to client as a critical failure. |
| // Return true if decoding has progressed. Return false if an error has occurred. |
| bool GIFImageReader::decode(GIFImageDecoder::GIFQuery query, unsigned haltAtFrame) |
| { |
| ASSERT(m_bytesRead <= m_data->size()); |
| |
| if (!parse(m_bytesRead, m_data->size() - m_bytesRead, query == GIFImageDecoder::GIFSizeQuery)) |
| return false; |
| |
| if (query != GIFImageDecoder::GIFFullQuery) |
| return true; |
| |
| // Already decoded frames can be deleted from the cache and then they require to be decoded again, so |
| // the haltAtFrame value we receive here may be lower than m_currentDecodingFrame. In this case |
| // we position m_currentDecodingFrame to haltAtFrame - 1 and decode from there. |
| // See bug https://bugs.webkit.org/show_bug.cgi?id=176089. |
| m_currentDecodingFrame = std::min(m_currentDecodingFrame, static_cast<size_t>(haltAtFrame) - 1); |
| |
| while (m_currentDecodingFrame < std::min(m_frames.size(), static_cast<size_t>(haltAtFrame))) { |
| bool frameDecoded = false; |
| GIFFrameContext* currentFrame = m_frames[m_currentDecodingFrame].get(); |
| |
| if (!currentFrame->decode(data(0), m_data->size(), m_client, &frameDecoded)) |
| return false; |
| |
| // We need more data to continue decoding. |
| if (!frameDecoded) |
| break; |
| |
| if (!m_client->frameComplete(m_currentDecodingFrame, currentFrame->delayTime, currentFrame->disposalMethod)) |
| return false; |
| ++m_currentDecodingFrame; |
| } |
| |
| // All frames decoded. |
| if (m_currentDecodingFrame == m_frames.size() && m_parseCompleted) |
| m_client->gifComplete(); |
| return true; |
| } |
| |
| // Parse incoming GIF data stream into internal data structures. |
| // Return true if parsing has progressed or there is not enough data. |
| // Return false if a fatal error is encountered. |
| bool GIFImageReader::parse(size_t dataPosition, size_t len, bool parseSizeOnly) |
| { |
| if (!len) { |
| // No new data has come in since the last call, just ignore this call. |
| return true; |
| } |
| |
| if (len < m_bytesToConsume) |
| return true; |
| |
| // This loop reads as many components from |m_data| as possible. |
| // At the beginning of each iteration, dataPosition will be advanced by m_bytesToConsume to |
| // point to the next component. len will be decremented accordingly. |
| while (len >= m_bytesToConsume) { |
| const size_t currentComponentPosition = dataPosition; |
| const unsigned char* currentComponent = data(dataPosition); |
| |
| // Mark the current component as consumed. Note that currentComponent will remain pointed at this |
| // component until the next loop iteration. |
| dataPosition += m_bytesToConsume; |
| len -= m_bytesToConsume; |
| |
| switch (m_state) { |
| case GIFLZW: |
| ASSERT(!m_frames.isEmpty()); |
| // m_bytesToConsume is the current component size because it hasn't been updated. |
| m_frames.last()->addLzwBlock(currentComponentPosition, m_bytesToConsume); |
| GETN(1, GIFSubBlock); |
| break; |
| |
| case GIFLZWStart: { |
| ASSERT(!m_frames.isEmpty()); |
| m_frames.last()->setDataSize(*currentComponent); |
| GETN(1, GIFSubBlock); |
| break; |
| } |
| |
| case GIFType: { |
| // All GIF files begin with "GIF87a" or "GIF89a". |
| if (!strncmp((char*)currentComponent, "GIF89a", 6)) |
| m_version = 89; |
| else if (!strncmp((char*)currentComponent, "GIF87a", 6)) |
| m_version = 87; |
| else |
| return false; |
| GETN(7, GIFGlobalHeader); |
| break; |
| } |
| |
| case GIFGlobalHeader: { |
| // 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. |
| m_screenWidth = GETINT16(currentComponent); |
| m_screenHeight = GETINT16(currentComponent + 2); |
| |
| // CALLBACK: Inform the decoderplugin of our size. |
| // Note: A subsequent frame might have dimensions larger than the "screen" dimensions. |
| if (m_client && !m_client->setSize(WebCore::IntSize(m_screenWidth, m_screenHeight))) |
| return false; |
| |
| m_screenBgcolor = currentComponent[5]; |
| m_globalColormapSize = 2 << (currentComponent[4] & 0x07); |
| |
| if ((currentComponent[4] & 0x80) && m_globalColormapSize > 0) { /* global map */ |
| // Get the global colormap |
| const size_t globalColormapBytes = 3 * m_globalColormapSize; |
| m_globalColormapPosition = dataPosition; |
| |
| if (len < globalColormapBytes) { |
| // Wait until we have enough bytes to consume the entire colormap at once. |
| GETN(globalColormapBytes, GIFGlobalColormap); |
| break; |
| } |
| |
| m_isGlobalColormapDefined = true; |
| dataPosition += globalColormapBytes; |
| len -= globalColormapBytes; |
| } |
| |
| GETN(1, GIFImageStart); |
| |
| // currentComponent[6] = Pixel Aspect Ratio |
| // Not used |
| // float aspect = (float)((currentComponent[6] + 15) / 64.0); |
| break; |
| } |
| |
| case GIFGlobalColormap: { |
| m_isGlobalColormapDefined = true; |
| GETN(1, GIFImageStart); |
| break; |
| } |
| |
| case GIFImageStart: { |
| if (*currentComponent == ';') { // terminator. |
| GETN(0, GIFDone); |
| break; |
| } |
| |
| if (*currentComponent == '!') { // extension. |
| GETN(2, GIFExtension); |
| 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 (*currentComponent != ',') |
| return false; |
| |
| GETN(9, GIFImageHeader); |
| break; |
| } |
| |
| case GIFExtension: { |
| size_t bytesInBlock = currentComponent[1]; |
| GIFState es = GIFSkipBlock; |
| |
| switch (*currentComponent) { |
| case 0xf9: |
| es = GIFControlExtension; |
| // The GIF spec mandates that the GIFControlExtension header block length is 4 bytes, |
| // and the parser for this block reads 4 bytes, so we must enforce that the buffer |
| // contains at least this many bytes. If the GIF specifies a different length, we |
| // allow that, so long as it's larger; the additional data will simply be ignored. |
| bytesInBlock = std::max(bytesInBlock, static_cast<size_t>(4)); |
| break; |
| |
| // The GIF spec also specifies the lengths of the following two extensions' headers |
| // (as 12 and 11 bytes, respectively). Because we ignore the plain text extension entirely |
| // and sanity-check the actual length of the application extension header before reading it, |
| // we allow GIFs to deviate from these values in either direction. This is important for |
| // real-world compatibility, as GIFs in the wild exist with application extension headers |
| // that are both shorter and longer than 11 bytes. |
| case 0x01: |
| // ignoring plain text extension |
| break; |
| |
| case 0xff: |
| es = GIFApplicationExtension; |
| break; |
| |
| case 0xfe: |
| es = GIFConsumeComment; |
| break; |
| } |
| |
| if (bytesInBlock) |
| GETN(bytesInBlock, es); |
| else |
| GETN(1, GIFImageStart); |
| break; |
| } |
| |
| case GIFConsumeBlock: { |
| if (!*currentComponent) |
| GETN(1, GIFImageStart); |
| else |
| GETN(*currentComponent, GIFSkipBlock); |
| break; |
| } |
| |
| case GIFSkipBlock: { |
| GETN(1, GIFConsumeBlock); |
| break; |
| } |
| |
| case GIFControlExtension: { |
| addFrameIfNecessary(); |
| GIFFrameContext* currentFrame = m_frames.last().get(); |
| currentFrame->isTransparent = *currentComponent & 0x1; |
| if (currentFrame->isTransparent) |
| currentFrame->tpixel = currentComponent[3]; |
| |
| // We ignore the "user input" bit. |
| |
| // NOTE: This relies on the values in the DisposalMethod enum |
| // matching those in the GIF spec! |
| int disposalMethod = ((*currentComponent) >> 2) & 0x7; |
| currentFrame->disposalMethod = static_cast<WebCore::ScalableImageDecoderFrame::DisposalMethod>(disposalMethod); |
| // Some specs say that disposal method 3 is "overwrite previous", others that setting |
| // the third bit of the field (i.e. method 4) is. We map both to the same value. |
| if (disposalMethod == 4) |
| currentFrame->disposalMethod = WebCore::ScalableImageDecoderFrame::DisposalMethod::RestoreToPrevious; |
| currentFrame->delayTime = GETINT16(currentComponent + 1) * 10; |
| GETN(1, GIFConsumeBlock); |
| break; |
| } |
| |
| case GIFCommentExtension: { |
| if (*currentComponent) |
| GETN(*currentComponent, GIFConsumeComment); |
| else |
| GETN(1, GIFImageStart); |
| break; |
| } |
| |
| case GIFConsumeComment: { |
| GETN(1, GIFCommentExtension); |
| break; |
| } |
| |
| case GIFApplicationExtension: { |
| // Check for netscape application extension. |
| if (m_bytesToConsume == 11 |
| && (!strncmp((char*)currentComponent, "NETSCAPE2.0", 11) || !strncmp((char*)currentComponent, "ANIMEXTS1.0", 11))) |
| GETN(1, GIFNetscapeExtensionBlock); |
| else |
| GETN(1, GIFConsumeBlock); |
| break; |
| } |
| |
| // Netscape-specific GIF extension: animation looping. |
| case GIFNetscapeExtensionBlock: { |
| // GIFConsumeNetscapeExtension always reads 3 bytes from the stream; we should at least wait for this amount. |
| if (*currentComponent) |
| GETN(std::max(3, static_cast<int>(*currentComponent)), GIFConsumeNetscapeExtension); |
| else |
| GETN(1, GIFImageStart); |
| break; |
| } |
| |
| // Parse netscape-specific application extensions |
| case GIFConsumeNetscapeExtension: { |
| int netscapeExtension = currentComponent[0] & 7; |
| |
| // Loop entire animation specified # of times. Only read the loop count during the first iteration. |
| if (netscapeExtension == 1) { |
| m_loopCount = GETINT16(currentComponent + 1); |
| |
| // Zero loop count is infinite animation loop request. |
| if (!m_loopCount) |
| m_loopCount = WebCore::RepetitionCountInfinite; |
| |
| GETN(1, GIFNetscapeExtensionBlock); |
| } else if (netscapeExtension == 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, GIFNetscapeExtensionBlock); |
| } else { |
| // 0,3-7 are yet to be defined netscape extension codes |
| return false; |
| } |
| break; |
| } |
| |
| case GIFImageHeader: { |
| unsigned height, width, xOffset, yOffset; |
| |
| /* Get image offsets, with respect to the screen origin */ |
| xOffset = GETINT16(currentComponent); |
| yOffset = GETINT16(currentComponent + 2); |
| |
| /* Get image width and height. */ |
| width = GETINT16(currentComponent + 4); |
| height = GETINT16(currentComponent + 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 (currentFrameIsFirstFrame() |
| && ((m_screenHeight < height) || (m_screenWidth < width) || (m_version == 87))) { |
| m_screenHeight = height; |
| m_screenWidth = width; |
| xOffset = 0; |
| yOffset = 0; |
| |
| // CALLBACK: Inform the decoderplugin of our size. |
| if (m_client && !m_client->setSize(WebCore::IntSize(m_screenWidth, m_screenHeight))) |
| return false; |
| } |
| |
| // Work around more broken GIF files that have zero image width or height |
| if (!height || !width) { |
| height = m_screenHeight; |
| width = m_screenWidth; |
| if (!height || !width) |
| return false; |
| } |
| |
| if (parseSizeOnly) { |
| // 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). |
| setRemainingBytes(len + 9); |
| GETN(9, GIFImageHeader); |
| return true; |
| } |
| |
| addFrameIfNecessary(); |
| GIFFrameContext* currentFrame = m_frames.last().get(); |
| |
| currentFrame->setHeaderDefined(); |
| currentFrame->xOffset = xOffset; |
| currentFrame->yOffset = yOffset; |
| currentFrame->height = height; |
| currentFrame->width = width; |
| m_screenWidth = std::max(m_screenWidth, width); |
| m_screenHeight = std::max(m_screenHeight, height); |
| currentFrame->interlaced = currentComponent[8] & 0x40; |
| |
| // 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. Which means only the first |
| // frame can be progressively displayed. |
| // FIXME: It is possible that a non-transparent frame |
| // can be interlaced and progressively displayed. |
| currentFrame->progressiveDisplay = currentFrameIsFirstFrame(); |
| |
| const bool isLocalColormapDefined = currentComponent[8] & 0x80; |
| if (isLocalColormapDefined) { |
| // The three low-order bits of currentComponent[8] specify the bits per pixel. |
| int numColors = 2 << (currentComponent[8] & 0x7); |
| const size_t localColormapBytes = 3 * numColors; |
| |
| // Switch to the new local palette after it loads |
| currentFrame->localColormapPosition = dataPosition; |
| currentFrame->localColormapSize = numColors; |
| |
| if (len < localColormapBytes) { |
| // Wait until we have enough bytes to consume the entire colormap at once. |
| GETN(localColormapBytes, GIFImageColormap); |
| break; |
| } |
| |
| currentFrame->isLocalColormapDefined = true; |
| dataPosition += localColormapBytes; |
| len -= localColormapBytes; |
| } else { |
| // Switch back to the global palette |
| currentFrame->isLocalColormapDefined = false; |
| } |
| GETN(1, GIFLZWStart); |
| break; |
| } |
| |
| case GIFImageColormap: { |
| ASSERT(!m_frames.isEmpty()); |
| m_frames.last()->isLocalColormapDefined = true; |
| GETN(1, GIFLZWStart); |
| break; |
| } |
| |
| case GIFSubBlock: { |
| const size_t bytesInBlock = *currentComponent; |
| if (bytesInBlock) |
| GETN(bytesInBlock, GIFLZW); |
| else { |
| // Finished parsing one frame; Process next frame. |
| ASSERT(!m_frames.isEmpty()); |
| // Note that some broken GIF files do not have enough LZW blocks to fully |
| // decode all rows but we treat it as frame complete. |
| m_frames.last()->setComplete(); |
| GETN(1, GIFImageStart); |
| } |
| break; |
| } |
| |
| case GIFDone: { |
| m_parseCompleted = true; |
| return true; |
| } |
| |
| default: |
| // We shouldn't ever get here. |
| return false; |
| break; |
| } |
| } |
| |
| setRemainingBytes(len); |
| return true; |
| } |
| |
| void GIFImageReader::setRemainingBytes(size_t remainingBytes) |
| { |
| ASSERT(remainingBytes <= m_data->size()); |
| m_bytesRead = m_data->size() - remainingBytes; |
| } |
| |
| void GIFImageReader::addFrameIfNecessary() |
| { |
| if (m_frames.isEmpty() || m_frames.last()->isComplete()) |
| m_frames.append(makeUnique<GIFFrameContext>(m_frames.size())); |
| } |
| |
| // FIXME: Move this method to close to doLZW(). |
| bool GIFLZWContext::prepareToDecode() |
| { |
| ASSERT(m_frameContext->isDataSizeDefined() && m_frameContext->isHeaderDefined()); |
| |
| // Since we use a codesize of 1 more than the datasize, we need to ensure |
| // that our datasize is strictly less than the MAX_LZW_BITS value (12). |
| // This sets the largest possible codemask correctly at 4095. |
| if (m_frameContext->datasize >= MAX_LZW_BITS) |
| return false; |
| clearCode = 1 << m_frameContext->datasize; |
| if (clearCode >= MAX_BYTES) |
| return false; |
| |
| avail = clearCode + 2; |
| oldcode = -1; |
| codesize = m_frameContext->datasize + 1; |
| codemask = (1 << codesize) - 1; |
| datum = bits = 0; |
| ipass = m_frameContext->interlaced ? 1 : 0; |
| irow = 0; |
| |
| // Initialize the tables lazily, this allows frame count query to use less memory. |
| suffix.resize(MAX_BYTES); |
| stack.resize(MAX_BYTES); |
| prefix.resize(MAX_BYTES); |
| |
| // Initialize output row buffer. |
| rowBuffer.resize(m_frameContext->width); |
| rowPosition = 0; |
| rowsRemaining = m_frameContext->height; |
| |
| // Clearing the whole suffix table lets us be more tolerant of bad data. |
| suffix.fill(0); |
| for (int i = 0; i < clearCode; i++) |
| suffix[i] = i; |
| stackp = 0; |
| return true; |
| } |