diff options
Diffstat (limited to 'WebCore/platform/image-decoders/gif')
-rw-r--r-- | WebCore/platform/image-decoders/gif/GIFImageDecoder.cpp | 416 | ||||
-rw-r--r-- | WebCore/platform/image-decoders/gif/GIFImageDecoder.h | 88 | ||||
-rw-r--r-- | WebCore/platform/image-decoders/gif/GIFImageReader.cpp | 943 | ||||
-rw-r--r-- | WebCore/platform/image-decoders/gif/GIFImageReader.h | 214 |
4 files changed, 1661 insertions, 0 deletions
diff --git a/WebCore/platform/image-decoders/gif/GIFImageDecoder.cpp b/WebCore/platform/image-decoders/gif/GIFImageDecoder.cpp new file mode 100644 index 0000000..22a6f09 --- /dev/null +++ b/WebCore/platform/image-decoders/gif/GIFImageDecoder.cpp @@ -0,0 +1,416 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "GIFImageDecoder.h" +#include "GIFImageReader.h" + +#if PLATFORM(CAIRO) || PLATFORM(QT) || PLATFORM(WX) + +namespace WebCore { + +class GIFImageDecoderPrivate +{ +public: + GIFImageDecoderPrivate(GIFImageDecoder* decoder = 0) + : m_reader(decoder) + { + m_readOffset = 0; + } + + ~GIFImageDecoderPrivate() + { + m_reader.close(); + } + + bool decode(const Vector<char>& data, + GIFImageDecoder::GIFQuery query = GIFImageDecoder::GIFFullQuery, + unsigned int haltFrame = -1) + { + return m_reader.read((const unsigned char*)data.data() + m_readOffset, data.size() - m_readOffset, + query, + haltFrame); + } + + unsigned frameCount() const { return m_reader.images_count; } + int repetitionCount() const { return m_reader.loop_count; } + + void setReadOffset(unsigned o) { m_readOffset = o; } + + bool isTransparent() const { return m_reader.frame_reader->is_transparent; } + + void getColorMap(unsigned char*& map, unsigned& size) const { + if (m_reader.frame_reader->is_local_colormap_defined) { + map = m_reader.frame_reader->local_colormap; + size = (unsigned)m_reader.frame_reader->local_colormap_size; + } else { + map = m_reader.global_colormap; + size = m_reader.global_colormap_size; + } + } + + unsigned frameXOffset() const { return m_reader.frame_reader->x_offset; } + unsigned frameYOffset() const { return m_reader.frame_reader->y_offset; } + unsigned frameWidth() const { return m_reader.frame_reader->width; } + unsigned frameHeight() const { return m_reader.frame_reader->height; } + + int transparentPixel() const { return m_reader.frame_reader->tpixel; } + + unsigned duration() const { return m_reader.frame_reader->delay_time; } + +private: + GIFImageReader m_reader; + unsigned m_readOffset; +}; + +GIFImageDecoder::GIFImageDecoder() +: m_frameCountValid(true), m_repetitionCount(cAnimationLoopOnce), m_reader(0) +{} + +GIFImageDecoder::~GIFImageDecoder() +{ + delete m_reader; +} + +// Take the data and store it. +void GIFImageDecoder::setData(SharedBuffer* data, bool allDataReceived) +{ + if (m_failed) + return; + + // Cache our new data. + ImageDecoder::setData(data, allDataReceived); + + // Our frame count is now unknown. + m_frameCountValid = false; + + // Create the GIF reader. + if (!m_reader && !m_failed) + m_reader = new GIFImageDecoderPrivate(this); +} + +// Whether or not the size information has been decoded yet. +bool GIFImageDecoder::isSizeAvailable() const +{ + // If we have pending data to decode, send it to the GIF reader now. + if (!m_sizeAvailable && m_reader) { + if (m_failed) + return false; + + // The decoder will go ahead and aggressively consume everything up until the first + // size is encountered. + decode(GIFSizeQuery, 0); + } + + return m_sizeAvailable; +} + +// The total number of frames for the image. Will scan the image data for the answer +// (without necessarily decoding all of the individual frames). +int GIFImageDecoder::frameCount() +{ + // If the decoder had an earlier error, we will just return what we had decoded + // so far. + if (!m_frameCountValid) { + // FIXME: Scanning all the data has O(n^2) behavior if the data were to come in really + // slowly. Might be interesting to try to clone our existing read session to preserve + // state, but for now we just crawl all the data. Note that this is no worse than what + // ImageIO does on Mac right now (it also crawls all the data again). + GIFImageDecoderPrivate reader; + reader.decode(m_data->buffer(), GIFFrameCountQuery); + m_frameCountValid = true; + m_frameBufferCache.resize(reader.frameCount()); + } + + return m_frameBufferCache.size(); +} + +// The number of repetitions to perform for an animation loop. +int GIFImageDecoder::repetitionCount() const +{ + // This value can arrive at any point in the image data stream. Most GIFs + // in the wild declare it near the beginning of the file, so it usually is + // set by the time we've decoded the size, but (depending on the GIF and the + // packets sent back by the webserver) not always. Our caller is + // responsible for waiting until image decoding has finished to ask this if + // it needs an authoritative answer. In the meantime, we should default to + // "loop once", both in the reader and here. + if (m_reader) + m_repetitionCount = m_reader->repetitionCount(); + return m_repetitionCount; +} + +RGBA32Buffer* GIFImageDecoder::frameBufferAtIndex(size_t index) +{ + if (index >= frameCount()) + return 0; + + RGBA32Buffer& frame = m_frameBufferCache[index]; + if (frame.status() != RGBA32Buffer::FrameComplete && m_reader) + // Decode this frame. + decode(GIFFullQuery, index+1); + return &frame; +} + +// Feed data to the GIF reader. +void GIFImageDecoder::decode(GIFQuery query, unsigned haltAtFrame) const +{ + if (m_failed) + return; + + m_failed = !m_reader->decode(m_data->buffer(), query, haltAtFrame); + + if (m_failed) { + delete m_reader; + m_reader = 0; + } +} + +// Callbacks from the GIF reader. +void GIFImageDecoder::sizeNowAvailable(unsigned width, unsigned height) +{ + m_size = IntSize(width, height); + m_sizeAvailable = true; +} + +void GIFImageDecoder::decodingHalted(unsigned bytesLeft) +{ + m_reader->setReadOffset(m_data->size() - bytesLeft); +} + +void GIFImageDecoder::initFrameBuffer(unsigned frameIndex) +{ + // Initialize the frame rect in our buffer. + IntRect frameRect(m_reader->frameXOffset(), m_reader->frameYOffset(), + m_reader->frameWidth(), m_reader->frameHeight()); + + // Make sure the frameRect doesn't extend past the bottom-right of the buffer. + if (frameRect.right() > m_size.width()) + frameRect.setWidth(m_size.width() - m_reader->frameXOffset()); + if (frameRect.bottom() > m_size.height()) + frameRect.setHeight(m_size.height() - m_reader->frameYOffset()); + + RGBA32Buffer* const buffer = &m_frameBufferCache[frameIndex]; + buffer->setRect(frameRect); + + if (frameIndex == 0) { + // This is the first frame, so we're not relying on any previous data. + prepEmptyFrameBuffer(buffer); + } else { + // The starting state for this frame depends on the previous frame's + // disposal method. + // + // Frames that use the DisposeOverwritePrevious method are effectively + // no-ops in terms of changing the starting state of a frame compared to + // the starting state of the previous frame, so skip over them. (If the + // first frame specifies this method, it will get treated like + // DisposeOverwriteBgcolor below and reset to a completely empty image.) + const RGBA32Buffer* prevBuffer = &m_frameBufferCache[--frameIndex]; + RGBA32Buffer::FrameDisposalMethod prevMethod = + prevBuffer->disposalMethod(); + while ((frameIndex > 0) && + (prevMethod == RGBA32Buffer::DisposeOverwritePrevious)) { + prevBuffer = &m_frameBufferCache[--frameIndex]; + prevMethod = prevBuffer->disposalMethod(); + } + + if ((prevMethod == RGBA32Buffer::DisposeNotSpecified) || + (prevMethod == RGBA32Buffer::DisposeKeep)) { + // Preserve the last frame as the starting state for this frame. + buffer->bytes() = prevBuffer->bytes(); + buffer->setHasAlpha(prevBuffer->hasAlpha()); + } else { + // We want to clear the previous frame to transparent, without + // affecting pixels in the image outside of the frame. + const IntRect& prevRect = prevBuffer->rect(); + if ((frameIndex == 0) || + prevRect.contains(IntRect(IntPoint(0, 0), m_size))) { + // Clearing the first frame, or a frame the size of the whole + // image, results in a completely empty image. + prepEmptyFrameBuffer(buffer); + } else { + // Copy the whole previous buffer, then clear just its frame. + buffer->bytes() = prevBuffer->bytes(); + buffer->setHasAlpha(prevBuffer->hasAlpha()); + for (int y = prevRect.y(); y < prevRect.bottom(); ++y) { + unsigned* const currentRow = + buffer->bytes().data() + (y * m_size.width()); + for (int x = prevRect.x(); x < prevRect.right(); ++x) + buffer->setRGBA(*(currentRow + x), 0, 0, 0, 0); + } + if ((prevRect.width() > 0) && (prevRect.height() > 0)) + buffer->setHasAlpha(true); + } + } + } + + // Update our status to be partially complete. + buffer->setStatus(RGBA32Buffer::FramePartial); + + // Reset the alpha pixel tracker for this frame. + m_currentBufferSawAlpha = false; +} + +void GIFImageDecoder::prepEmptyFrameBuffer(RGBA32Buffer* buffer) const +{ + buffer->bytes().resize(m_size.width() * m_size.height()); + buffer->bytes().fill(0); + buffer->setHasAlpha(true); +} + +void GIFImageDecoder::haveDecodedRow(unsigned frameIndex, + unsigned char* rowBuffer, // Pointer to single scanline temporary buffer + unsigned char* rowEnd, + unsigned rowNumber, // The row index + unsigned repeatCount, // How many times to repeat the row + bool writeTransparentPixels) +{ + // Initialize the frame if necessary. + RGBA32Buffer& buffer = m_frameBufferCache[frameIndex]; + if (buffer.status() == RGBA32Buffer::FrameEmpty) + initFrameBuffer(frameIndex); + + // Do nothing for bogus data. + if (rowBuffer == 0 || static_cast<int>(m_reader->frameYOffset() + rowNumber) >= m_size.height()) + return; + + unsigned colorMapSize; + unsigned char* colorMap; + m_reader->getColorMap(colorMap, colorMapSize); + if (!colorMap) + return; + + // The buffers that we draw are the entire image's width and height, so a final output frame is + // width * height RGBA32 values in size. + // + // A single GIF frame, however, can be smaller than the entire image, i.e., it can represent some sub-rectangle + // within the overall image. The rows we are decoding are within this + // sub-rectangle. This means that if the GIF frame's sub-rectangle is (x,y,w,h) then row 0 is really row + // y, and each row goes from x to x+w. + unsigned dstPos = (m_reader->frameYOffset() + rowNumber) * m_size.width() + m_reader->frameXOffset(); + unsigned* dst = buffer.bytes().data() + dstPos; + unsigned* dstEnd = dst + m_size.width() - m_reader->frameXOffset(); + unsigned* currDst = dst; + unsigned char* currentRowByte = rowBuffer; + + while (currentRowByte != rowEnd && currDst < dstEnd) { + if ((!m_reader->isTransparent() || *currentRowByte != m_reader->transparentPixel()) && *currentRowByte < colorMapSize) { + unsigned colorIndex = *currentRowByte * 3; + unsigned red = colorMap[colorIndex]; + unsigned green = colorMap[colorIndex + 1]; + unsigned blue = colorMap[colorIndex + 2]; + RGBA32Buffer::setRGBA(*currDst, red, green, blue, 255); + } else { + m_currentBufferSawAlpha = true; + // We may or may not need to write transparent pixels to the buffer. + // If we're compositing against a previous image, it's wrong, and if + // we're writing atop a cleared, fully transparent buffer, it's + // unnecessary; but if we're decoding an interlaced gif and + // displaying it "Haeberli"-style, we must write these for passes + // beyond the first, or the initial passes will "show through" the + // later ones. + if (writeTransparentPixels) + RGBA32Buffer::setRGBA(*currDst, 0, 0, 0, 0); + } + currDst++; + currentRowByte++; + } + + if (repeatCount > 1) { + // Copy the row |repeatCount|-1 times. + unsigned num = currDst - dst; + unsigned size = num * sizeof(unsigned); + unsigned width = m_size.width(); + unsigned* end = buffer.bytes().data() + width * m_size.height(); + currDst = dst + width; + for (unsigned i = 1; i < repeatCount; i++) { + if (currDst + num > end) // Protect against a buffer overrun from a bogus repeatCount. + break; + memcpy(currDst, dst, size); + currDst += width; + } + } + + // Our partial height is rowNumber + 1, e.g., row 2 is the 3rd row, so that's a height of 3. + // Adding in repeatCount - 1 to rowNumber + 1 works out to just be rowNumber + repeatCount. + buffer.ensureHeight(rowNumber + repeatCount); +} + +void GIFImageDecoder::frameComplete(unsigned frameIndex, unsigned frameDuration, RGBA32Buffer::FrameDisposalMethod disposalMethod) +{ + RGBA32Buffer& buffer = m_frameBufferCache[frameIndex]; + buffer.ensureHeight(m_size.height()); + buffer.setStatus(RGBA32Buffer::FrameComplete); + buffer.setDuration(frameDuration); + buffer.setDisposalMethod(disposalMethod); + + if (!m_currentBufferSawAlpha) { + // The whole frame was non-transparent, so it's possible that the entire + // resulting buffer was non-transparent, and we can setHasAlpha(false). + if (buffer.rect().contains(IntRect(IntPoint(0, 0), m_size))) { + buffer.setHasAlpha(false); + } else if (frameIndex > 0) { + // Tricky case. This frame does not have alpha only if everywhere + // outside its rect doesn't have alpha. To know whether this is + // true, we check the start state of the frame -- if it doesn't have + // alpha, we're safe. + // + // First skip over prior DisposeOverwritePrevious frames (since they + // don't affect the start state of this frame) the same way we do in + // initFrameBuffer(). + const RGBA32Buffer* prevBuffer = &m_frameBufferCache[--frameIndex]; + while ((frameIndex > 0) && + (prevBuffer->disposalMethod() == + RGBA32Buffer::DisposeOverwritePrevious)) + prevBuffer = &m_frameBufferCache[--frameIndex]; + + // Now, if we're at a DisposeNotSpecified or DisposeKeep frame, then + // we can say we have no alpha if that frame had no alpha. But + // since in initFrameBuffer() we already copied that frame's alpha + // state into the current frame's, we need do nothing at all here. + // + // The only remaining case is a DisposeOverwriteBgcolor frame. If + // it had no alpha, and its rect is contained in the current frame's + // rect, we know the current frame has no alpha. + if ((prevBuffer->disposalMethod() == + RGBA32Buffer::DisposeOverwriteBgcolor) && + !prevBuffer->hasAlpha() && + buffer.rect().contains(prevBuffer->rect())) + buffer.setHasAlpha(false); + } + } +} + +void GIFImageDecoder::gifComplete() +{ + if (m_reader) + m_repetitionCount = m_reader->repetitionCount(); + delete m_reader; + m_reader = 0; +} + +} + +#endif // PLATFORM(CAIRO) diff --git a/WebCore/platform/image-decoders/gif/GIFImageDecoder.h b/WebCore/platform/image-decoders/gif/GIFImageDecoder.h new file mode 100644 index 0000000..b407b8d --- /dev/null +++ b/WebCore/platform/image-decoders/gif/GIFImageDecoder.h @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY + * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef GIF_DECODER_H_ +#define GIF_DECODER_H_ + +#include "ImageDecoder.h" + +namespace WebCore { + +class GIFImageDecoderPrivate; + +// This class decodes the GIF image format. +class GIFImageDecoder : public ImageDecoder +{ +public: + GIFImageDecoder(); + ~GIFImageDecoder(); + + // Take the data and store it. + virtual void setData(SharedBuffer* data, bool allDataReceived); + + // Whether or not the size information has been decoded yet. + virtual bool isSizeAvailable() const; + + // The total number of frames for the image. Will scan the image data for the answer + // (without necessarily decoding all of the individual frames). + virtual int frameCount(); + + // The number of repetitions to perform for an animation loop. + virtual int repetitionCount() const; + + virtual RGBA32Buffer* frameBufferAtIndex(size_t index); + + virtual unsigned frameDurationAtIndex(size_t index) { return 0; } + + enum GIFQuery { GIFFullQuery, GIFSizeQuery, GIFFrameCountQuery }; + + void decode(GIFQuery query, unsigned haltAtFrame) const; + + // Callbacks from the GIF reader. + void sizeNowAvailable(unsigned width, unsigned height); + void decodingHalted(unsigned bytesLeft); + void haveDecodedRow(unsigned frameIndex, unsigned char* rowBuffer, unsigned char* rowEnd, unsigned rowNumber, + unsigned repeatCount, bool writeTransparentPixels); + void frameComplete(unsigned frameIndex, unsigned frameDuration, RGBA32Buffer::FrameDisposalMethod disposalMethod); + void gifComplete(); + +private: + // Called to initialize the frame buffer with the given index, based on the + // previous frame's disposal method. + void initFrameBuffer(unsigned frameIndex); + + // A helper for initFrameBuffer(), this sets the size of the buffer, and + // fills it with transparent pixels. + void prepEmptyFrameBuffer(RGBA32Buffer* buffer) const; + + bool m_frameCountValid; + bool m_currentBufferSawAlpha; + mutable int m_repetitionCount; + mutable GIFImageDecoderPrivate* m_reader; +}; + +} + +#endif diff --git a/WebCore/platform/image-decoders/gif/GIFImageReader.cpp b/WebCore/platform/image-decoders/gif/GIFImageReader.cpp new file mode 100644 index 0000000..04347af --- /dev/null +++ b/WebCore/platform/image-decoders/gif/GIFImageReader.cpp @@ -0,0 +1,943 @@ +/* -*- 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) || PLATFORM(WX) + +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 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; + + // CALLBACK: Let the client know we have decoded a row. + if (clientptr && frame_reader) + clientptr->haveDecodedRow(images_count - 1, frame_reader->rowbuf, frame_reader->rowend, + drow_start, drow_end - drow_start + 1, + gs->progressive_display && gs->interlaced && gs->ipass > 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) { + // No new data has come in since the last call, just ignore this call. + return true; + } + + 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; + if (clientptr) + clientptr->decodingHalted(0); + 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 + } + // NOTE: This relies on the values in the FrameDisposalMethod enum + // matching those in the GIF spec! + frame_reader->disposal_method = (WebCore::RGBA32Buffer::FrameDisposalMethod)(((*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 = WebCore::RGBA32Buffer::DisposeOverwritePrevious; + frame_reader->delay_time = GETINT16(q + 1) * 10; + } + GETN(1, gif_consume_block); + } + break; + + case gif_comment_extension: + { + if (*q) + GETN(*q, gif_consume_comment); + else + GETN(1, gif_image_start); + } + break; + + case gif_consume_comment: + GETN(1, gif_comment_extension); + break; + + case gif_application_extension: + /* Check for netscape application extension */ + if (!strncmp((char*)q, "NETSCAPE2.0", 11) || + !strncmp((char*)q, "ANIMEXTS1.0", 11)) + GETN(1, gif_netscape_extension_block); + else + GETN(1, gif_consume_block); + break; + + /* Netscape-specific GIF extension: animation looping */ + case gif_netscape_extension_block: + if (*q) + GETN(*q, gif_consume_netscape_extension); + else + GETN(1, gif_image_start); + break; + + /* Parse netscape-specific application extensions */ + case gif_consume_netscape_extension: + { + int netscape_extension = q[0] & 7; + + /* Loop entire animation specified # of times. Only read the + loop count during the first iteration. */ + if (netscape_extension == 1) { + 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; + screen_width = width; + frame_reader->rowbuf = new unsigned char[screen_width]; + } else if (!frame_reader->rowbuf) { + frame_reader->rowbuf = new unsigned char[screen_width]; + } + + if (!frame_reader->rowbuf) { + state = gif_oom; + break; + } + if (screen_height < height) + screen_height = height; + + 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); + + /* 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 false; + + // 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; + } + + if (clientptr) + clientptr->decodingHalted(0); + return true; +} + +#endif // PLATFORM(CAIRO) diff --git a/WebCore/platform/image-decoders/gif/GIFImageReader.h b/WebCore/platform/image-decoders/gif/GIFImageReader.h new file mode 100644 index 0000000..faa08d2 --- /dev/null +++ b/WebCore/platform/image-decoders/gif/GIFImageReader.h @@ -0,0 +1,214 @@ +/* -*- Mode: C; tab-width: 4; 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 Communicator client 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): + * + * 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 ***** */ + +#ifndef _GIF_H_ +#define _GIF_H_ + +// Define ourselves as the clientPtr. Mozilla just hacked their C++ callback class into this old C decoder, +// so we will too. +#include "GIFImageDecoder.h" + +#define MAX_LZW_BITS 12 +#define MAX_BITS 4097 /* 2^MAX_LZW_BITS+1 */ +#define MAX_COLORS 256 +#define MAX_HOLD_SIZE 256 + +/* gif2.h + The interface for the GIF87/89a decoder. +*/ +// List of possible parsing states +typedef enum { + gif_type, + gif_global_header, + gif_global_colormap, + gif_image_start, + gif_image_header, + gif_image_colormap, + gif_image_body, + gif_lzw_start, + gif_lzw, + gif_sub_block, + gif_extension, + gif_control_extension, + gif_consume_block, + gif_skip_block, + gif_done, + gif_oom, + gif_error, + gif_comment_extension, + gif_application_extension, + gif_netscape_extension_block, + gif_consume_netscape_extension, + gif_consume_comment +} gstate; + +struct GIFFrameReader { + /* LZW decoder state machine */ + unsigned char *stackp; /* Current stack pointer */ + int datasize; + int codesize; + int codemask; + int clear_code; /* Codeword used to trigger dictionary reset */ + int avail; /* Index of next available slot in dictionary */ + int oldcode; + unsigned char firstchar; + int bits; /* Number of unread bits in "datum" */ + int datum; /* 32-bit input buffer */ + + /* Output state machine */ + int ipass; /* Interlace pass; Ranges 1-4 if interlaced. */ + unsigned int rows_remaining; /* Rows remaining to be output */ + unsigned int irow; /* Current output row, starting at zero */ + unsigned char *rowbuf; /* Single scanline temporary buffer */ + unsigned char *rowend; /* Pointer to end of rowbuf */ + unsigned char *rowp; /* Current output pointer */ + + /* Parameters for image frame currently being decoded */ + unsigned int x_offset, y_offset; /* With respect to "screen" origin */ + unsigned int height, width; + int tpixel; /* Index of transparent pixel */ + WebCore::RGBA32Buffer::FrameDisposalMethod disposal_method; /* Restore to background, leave in place, etc.*/ + unsigned char *local_colormap; /* Per-image colormap */ + int local_colormap_size; /* Size of local colormap array. */ + + bool is_local_colormap_defined : 1; + bool progressive_display : 1; /* If TRUE, do Haeberli interlace hack */ + bool interlaced : 1; /* TRUE, if scanlines arrive interlaced order */ + bool is_transparent : 1; /* TRUE, if tpixel is valid */ + + unsigned delay_time; /* Display time, in milliseconds, + for this image in a multi-image GIF */ + + + unsigned short* prefix; /* LZW decoding tables */ + unsigned char* suffix; /* LZW decoding tables */ + unsigned char* stack; /* Base of LZW decoder stack */ + + + GIFFrameReader() { + stackp = 0; + datasize = codesize = codemask = clear_code = avail = oldcode = 0; + firstchar = 0; + bits = datum = 0; + ipass = 0; + rows_remaining = irow = 0; + rowbuf = rowend = rowp = 0; + + x_offset = y_offset = width = height = 0; + tpixel = 0; + disposal_method = WebCore::RGBA32Buffer::DisposeNotSpecified; + + local_colormap = 0; + local_colormap_size = 0; + is_local_colormap_defined = progressive_display = is_transparent = interlaced = false; + + delay_time = 0; + + prefix = 0; + suffix = stack = 0; + } + + ~GIFFrameReader() { + delete []rowbuf; + delete []local_colormap; + delete []prefix; + delete []suffix; + delete []stack; + } +}; + +struct GIFImageReader { + WebCore::GIFImageDecoder* clientptr; + /* Parsing state machine */ + gstate state; /* Current decoder master state */ + unsigned bytes_to_consume; /* Number of bytes to accumulate */ + unsigned bytes_in_hold; /* bytes accumulated so far*/ + unsigned char hold[MAX_HOLD_SIZE]; /* Accumulation buffer */ + unsigned char* global_colormap; /* (3* MAX_COLORS in size) Default colormap if local not supplied, 3 bytes for each color */ + + /* Global (multi-image) state */ + int screen_bgcolor; /* Logical screen background color */ + int version; /* Either 89 for GIF89 or 87 for GIF87 */ + unsigned screen_width; /* Logical screen width & height */ + unsigned screen_height; + int global_colormap_size; /* Size of global colormap array. */ + int images_decoded; /* Counts completed frames for animated GIFs */ + int images_count; /* Counted all frames seen so far (including incomplete frames) */ + int loop_count; /* Netscape specific extension block to control + the number of animation loops a GIF renders. */ + + // Not really global, but convenient to locate here. + int count; /* Remaining # bytes in sub-block */ + + GIFFrameReader* frame_reader; + + GIFImageReader(WebCore::GIFImageDecoder* client = 0) { + clientptr = client; + state = gif_type; + bytes_to_consume = 6; + bytes_in_hold = 0; + frame_reader = 0; + global_colormap = 0; + + screen_bgcolor = version = 0; + screen_width = screen_height = 0; + global_colormap_size = images_decoded = images_count = 0; + loop_count = -1; + count = 0; + } + + ~GIFImageReader() { + close(); + } + + void close() { + delete []global_colormap; + global_colormap = 0; + delete frame_reader; + frame_reader = 0; + } + + bool read(const unsigned char * buf, unsigned int numbytes, + WebCore::GIFImageDecoder::GIFQuery query = WebCore::GIFImageDecoder::GIFFullQuery, unsigned haltAtFrame = -1); + +private: + void output_row(); + int do_lzw(const unsigned char *q); +}; + +#endif + |