Пример #1
0
// 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;
}
Пример #2
0
// 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));
}
Пример #3
0
// 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));
}
Пример #4
0
// 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;
}
Пример #5
0
// 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;
}