// 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()); // Try to be tolerant and don't report parsing errors but we will not parse again. // TODO: Report parsing errors to client. if (!m_parseFailed && !parse(m_bytesRead, m_data->size() - m_bytesRead, query == GIFImageDecoder::GIFSizeQuery)) m_parseFailed = true; if (query != GIFImageDecoder::GIFFullQuery) return true; 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; }
// Decode a frame. // This method uses GIFFrameContext:decode() to decode the 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(size_t frameIndex) { m_globalColorMap.buildTable(data(0), m_data->size()); bool frameDecoded = false; GIFFrameContext* currentFrame = m_frames[frameIndex].get(); return currentFrame->decode(data(0), m_data->size(), m_client, &frameDecoded) && (!frameDecoded || m_client->frameComplete(frameIndex)); }
// Decode a frame. // This method uses GIFFrameContext:decode() to decode the 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(size_t frameIndex) { blink::FastSharedBufferReader reader(m_data); m_globalColorMap.buildTable(&reader); bool frameDecoded = false; GIFFrameContext* currentFrame = m_frames[frameIndex].get(); return currentFrame->decode(&reader, m_client, &frameDecoded) && (!frameDecoded || m_client->frameComplete(frameIndex)); }
// 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::parseData(size_t dataPosition, size_t len, GIFImageDecoder::GIFParseQuery query) { if (!len) { // No new data has come in since the last call, just ignore this call. return true; } if (len < m_bytesToConsume) return true; blink::FastSharedBufferReader reader(m_data); // A read buffer of 16 bytes is enough to accomodate all possible reads for parsing. char readBuffer[16]; // 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; // 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(static_cast<unsigned char>(reader.getOneByte(currentComponentPosition))); GETN(1, GIFSubBlock); break; } case GIFType: { const char* currentComponent = reader.getConsecutiveData(currentComponentPosition, 6, readBuffer); // All GIF files begin with "GIF87a" or "GIF89a". if (!memcmp(currentComponent, "GIF89a", 6)) m_version = 89; else if (!memcmp(currentComponent, "GIF87a", 6)) m_version = 87; else return false; GETN(7, GIFGlobalHeader); break; } case GIFGlobalHeader: { const unsigned char* currentComponent = reinterpret_cast<const unsigned char*>( reader.getConsecutiveData(currentComponentPosition, 5, readBuffer)); // 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. // Note that we don't inform the client of the size yet, as it might // change after we read the first frame's image header. m_screenWidth = GETINT16(currentComponent); m_screenHeight = GETINT16(currentComponent + 2); const size_t globalColorMapColors = 2 << (currentComponent[4] & 0x07); if ((currentComponent[4] & 0x80) && globalColorMapColors > 0) { /* global map */ m_globalColorMap.setTablePositionAndSize(dataPosition, globalColorMapColors); GETN(BYTES_PER_COLORMAP_ENTRY * globalColorMapColors, GIFGlobalColormap); break; } GETN(1, GIFImageStart); break; } case GIFGlobalColormap: { m_globalColorMap.setDefined(); GETN(1, GIFImageStart); break; } case GIFImageStart: { const char currentComponent = reader.getOneByte(currentComponentPosition); if (currentComponent == '!') { // extension. GETN(2, GIFExtension); break; } if (currentComponent == ',') { // image separator. GETN(9, GIFImageHeader); 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 Mozilla's implementation and // proceed as if the file were correctly terminated, so the // GIF will display. GETN(0, GIFDone); break; } case GIFExtension: { const unsigned char* currentComponent = reinterpret_cast<const unsigned char*>( reader.getConsecutiveData(currentComponentPosition, 2, readBuffer)); size_t bytesInBlock = currentComponent[1]; GIFState exceptionState = GIFSkipBlock; switch (*currentComponent) { case 0xf9: exceptionState = 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: exceptionState = GIFApplicationExtension; break; case 0xfe: exceptionState = GIFConsumeComment; break; } if (bytesInBlock) GETN(bytesInBlock, exceptionState); else GETN(1, GIFImageStart); break; } case GIFConsumeBlock: { const unsigned char currentComponent = static_cast<unsigned char>(reader.getOneByte(currentComponentPosition)); if (!currentComponent) GETN(1, GIFImageStart); else GETN(currentComponent, GIFSkipBlock); break; } case GIFSkipBlock: { GETN(1, GIFConsumeBlock); break; } case GIFControlExtension: { const unsigned char* currentComponent = reinterpret_cast<const unsigned char*>( reader.getConsecutiveData(currentComponentPosition, 4, readBuffer)); addFrameIfNecessary(); GIFFrameContext* currentFrame = m_frames.last().get(); if (*currentComponent & 0x1) currentFrame->setTransparentPixel(currentComponent[3]); // We ignore the "user input" bit. // NOTE: This relies on the values in the FrameDisposalMethod enum // matching those in the GIF spec! int disposalMethod = ((*currentComponent) >> 2) & 0x7; if (disposalMethod < 4) { currentFrame->setDisposalMethod(static_cast<blink::ImageFrame::DisposalMethod>(disposalMethod)); } else if (disposalMethod == 4) { // 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. currentFrame->setDisposalMethod(blink::ImageFrame::DisposeOverwritePrevious); } currentFrame->setDelayTime(GETINT16(currentComponent + 1) * 10); GETN(1, GIFConsumeBlock); break; } case GIFCommentExtension: { const unsigned char currentComponent = static_cast<unsigned char>(reader.getOneByte(currentComponentPosition)); 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) { const unsigned char* currentComponent = reinterpret_cast<const unsigned char*>( reader.getConsecutiveData(currentComponentPosition, 11, readBuffer)); if (!memcmp(currentComponent, "NETSCAPE2.0", 11) || !memcmp(currentComponent, "ANIMEXTS1.0", 11)) GETN(1, GIFNetscapeExtensionBlock); } if (m_state != GIFNetscapeExtensionBlock) GETN(1, GIFConsumeBlock); break; } // Netscape-specific GIF extension: animation looping. case GIFNetscapeExtensionBlock: { const int currentComponent = static_cast<unsigned char>(reader.getOneByte(currentComponentPosition)); // GIFConsumeNetscapeExtension always reads 3 bytes from the stream; we should at least wait for this amount. if (currentComponent) GETN(std::max(3, currentComponent), GIFConsumeNetscapeExtension); else GETN(1, GIFImageStart); break; } // Parse netscape-specific application extensions case GIFConsumeNetscapeExtension: { const unsigned char* currentComponent = reinterpret_cast<const unsigned char*>( reader.getConsecutiveData(currentComponentPosition, 3, readBuffer)); 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 = blink::cAnimationLoopInfinite; 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; const unsigned char* currentComponent = reinterpret_cast<const unsigned char*>( reader.getConsecutiveData(currentComponentPosition, 9, readBuffer)); /* 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); // Some GIF files have frames that don't fit in the specified // overall image size. For the first frame, we can simply enlarge // the image size to allow the frame to be visible. We can't do // this on subsequent frames because the rest of the decoding // infrastructure assumes the image size won't change as we // continue decoding, so any subsequent frames that are even // larger will be cropped. // Luckily, handling just the first frame is sufficient to deal // with most cases, e.g. ones where the image size is erroneously // set to zero, since usually the first frame completely fills // the image. if (currentFrameIsFirstFrame()) { m_screenHeight = std::max(m_screenHeight, yOffset + height); m_screenWidth = std::max(m_screenWidth, xOffset + width); } // Inform the client of the final size. if (!m_sentSizeToClient && m_client && !m_client->setSize(m_screenWidth, m_screenHeight)) return false; m_sentSizeToClient = true; if (query == GIFImageDecoder::GIFSizeQuery) { // 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(); // 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; } currentFrame->setRect(xOffset, yOffset, width, height); currentFrame->setInterlaced(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->setProgressiveDisplay(currentFrameIsFirstFrame()); const bool isLocalColormapDefined = currentComponent[8] & 0x80; if (isLocalColormapDefined) { // The three low-order bits of currentComponent[8] specify the bits per pixel. const size_t numColors = 2 << (currentComponent[8] & 0x7); currentFrame->localColorMap().setTablePositionAndSize(dataPosition, numColors); GETN(BYTES_PER_COLORMAP_ENTRY * numColors, GIFImageColormap); break; } GETN(1, GIFLZWStart); break; } case GIFImageColormap: { ASSERT(!m_frames.isEmpty()); m_frames.last()->localColorMap().setDefined(); GETN(1, GIFLZWStart); break; } case GIFSubBlock: { const size_t bytesInBlock = static_cast<unsigned char>(reader.getOneByte(currentComponentPosition)); 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; }
// 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(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 FrameDisposalMethod enum // matching those in the GIF spec! int disposalMethod = ((*currentComponent) >> 2) & 0x7; currentFrame->disposalMethod = static_cast<WebCore::ImageFrame::FrameDisposalMethod>(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::ImageFrame::DisposeOverwritePrevious; 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::cAnimationLoopInfinite; 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(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; }