summaryrefslogtreecommitdiffstats
path: root/WebCore/platform/image-decoders/gif
diff options
context:
space:
mode:
Diffstat (limited to 'WebCore/platform/image-decoders/gif')
-rw-r--r--WebCore/platform/image-decoders/gif/GIFImageDecoder.cpp416
-rw-r--r--WebCore/platform/image-decoders/gif/GIFImageDecoder.h88
-rw-r--r--WebCore/platform/image-decoders/gif/GIFImageReader.cpp943
-rw-r--r--WebCore/platform/image-decoders/gif/GIFImageReader.h214
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
+