From: darin@chromium.org Date: Fri, 23 Jan 2009 18:53:20 +0000 (+0000) Subject: 2009-01-23 Darin Fisher X-Git-Url: http://git.webkit.org/?p=WebKit-https.git;a=commitdiff_plain;h=1dd6c9f81759d7a2d65630a6ee5d375330fcc225 2009-01-23 Darin Fisher Reviewed by Eric Seidel. https://bugs.webkit.org/show_bug.cgi?id=23506 Copy existing image-decoders in preparation for landing Skia changes on top. * platform/image-decoders/skia: Added. * platform/image-decoders/skia/GIFImageDecoder.cpp: Copied from platform/image-decoders/gif/GIFImageDecoder.cpp. * platform/image-decoders/skia/GIFImageDecoder.h: Copied from platform/image-decoders/gif/GIFImageDecoder.h. * platform/image-decoders/skia/GIFImageReader.cpp: Copied from platform/image-decoders/gif/GIFImageReader.cpp. * platform/image-decoders/skia/GIFImageReader.h: Copied from platform/image-decoders/gif/GIFImageReader.h. * platform/image-decoders/skia/JPEGImageDecoder.cpp: Copied from platform/image-decoders/jpeg/JPEGImageDecoder.cpp. * platform/image-decoders/skia/JPEGImageDecoder.h: Copied from platform/image-decoders/jpeg/JPEGImageDecoder.h. * platform/image-decoders/skia/PNGImageDecoder.cpp: Copied from platform/image-decoders/png/PNGImageDecoder.cpp. * platform/image-decoders/skia/PNGImageDecoder.h: Copied from platform/image-decoders/png/PNGImageDecoder.h. git-svn-id: https://svn.webkit.org/repository/webkit/trunk@40165 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog index cdb1597..e018eab 100644 --- a/WebCore/ChangeLog +++ b/WebCore/ChangeLog @@ -1,3 +1,20 @@ +2009-01-23 Darin Fisher + + Reviewed by Eric Seidel. + + https://bugs.webkit.org/show_bug.cgi?id=23506 + Copy existing image-decoders in preparation for landing Skia changes on top. + + * platform/image-decoders/skia: Added. + * platform/image-decoders/skia/GIFImageDecoder.cpp: Copied from platform/image-decoders/gif/GIFImageDecoder.cpp. + * platform/image-decoders/skia/GIFImageDecoder.h: Copied from platform/image-decoders/gif/GIFImageDecoder.h. + * platform/image-decoders/skia/GIFImageReader.cpp: Copied from platform/image-decoders/gif/GIFImageReader.cpp. + * platform/image-decoders/skia/GIFImageReader.h: Copied from platform/image-decoders/gif/GIFImageReader.h. + * platform/image-decoders/skia/JPEGImageDecoder.cpp: Copied from platform/image-decoders/jpeg/JPEGImageDecoder.cpp. + * platform/image-decoders/skia/JPEGImageDecoder.h: Copied from platform/image-decoders/jpeg/JPEGImageDecoder.h. + * platform/image-decoders/skia/PNGImageDecoder.cpp: Copied from platform/image-decoders/png/PNGImageDecoder.cpp. + * platform/image-decoders/skia/PNGImageDecoder.h: Copied from platform/image-decoders/png/PNGImageDecoder.h. + 2009-01-23 Holger Freyther Unreviewed build fix. diff --git a/WebCore/platform/image-decoders/skia/GIFImageDecoder.cpp b/WebCore/platform/image-decoders/skia/GIFImageDecoder.cpp new file mode 100644 index 0000000..843e65a --- /dev/null +++ b/WebCore/platform/image-decoders/skia/GIFImageDecoder.cpp @@ -0,0 +1,465 @@ +/* + * 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& 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". + if (m_reader) { + // Added wrinkle: ImageSource::clear() may destroy the reader, making + // the result from the reader _less_ authoritative on future calls. To + // detect this, the reader returns cLoopCountNotSeen (-2) instead of + // cAnimationLoopOnce (-1) when its current incarnation hasn't actually + // seen a loop count yet; in this case we return our previously-cached + // value. + const int repetitionCount = m_reader->repetitionCount(); + if (repetitionCount != cLoopCountNotSeen) + m_repetitionCount = 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; +} + +void GIFImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame) +{ + // In some cases, like if the decoder was destroyed while animating, we + // can be asked to clear more frames than we currently have. + if (m_frameBufferCache.isEmpty()) + return; // Nothing to do. + // The "-1" here is tricky. It does not mean that |clearBeforeFrame| is the + // last frame we wish to preserve, but rather that we never want to clear + // the very last frame in the cache: it's empty (so clearing it is + // pointless), it's partial (so we don't want to clear it anyway), or the + // cache could be enlarged with a future setData() call and it could be + // needed to construct the next frame (see comments below). Callers can + // always use ImageSource::clear(true, ...) to completely free the memory in + // this case. + clearBeforeFrame = std::min(clearBeforeFrame, m_frameBufferCache.size() - 1); + const Vector::iterator end(m_frameBufferCache.begin() + clearBeforeFrame); + for (Vector::iterator i(m_frameBufferCache.begin()); i != end; ++i) { + if (i->status() == RGBA32Buffer::FrameEmpty) + continue; // Nothing to do. + + // The layout of frames is: + // [empty frames][complete frames][partial frame][empty frames] + // ...where each of these groups may be empty. We should not clear a + // partial frame since that's what's being decoded right now, and we + // also should not clear the last complete frame, since it may be needed + // when constructing the next frame. Note that "i + 1" is safe since + // i < end < m_frameBufferCache.end(). + if ((i->status() == RGBA32Buffer::FramePartial) || ((i + 1)->status() != RGBA32Buffer::FrameComplete)) + break; + + i->clear(); + } +} + +// 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]; + ASSERT(prevBuffer->status() == RGBA32Buffer::FrameComplete); + 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(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) +{ + // Initialize the frame if necessary. Some GIFs insert do-nothing frames, + // in which case we never reach haveDecodedRow() before getting here. + RGBA32Buffer& buffer = m_frameBufferCache[frameIndex]; + if (buffer.status() == RGBA32Buffer::FrameEmpty) + initFrameBuffer(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/skia/GIFImageDecoder.h b/WebCore/platform/image-decoders/skia/GIFImageDecoder.h new file mode 100644 index 0000000..02b43a2 --- /dev/null +++ b/WebCore/platform/image-decoders/skia/GIFImageDecoder.h @@ -0,0 +1,92 @@ +/* + * 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(); + + virtual String filenameExtension() const { return "gif"; } + + // 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 void clearFrameBufferCache(size_t clearBeforeFrame); + + 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/skia/GIFImageReader.cpp b/WebCore/platform/image-decoders/skia/GIFImageReader.cpp new file mode 100644 index 0000000..04347af --- /dev/null +++ b/WebCore/platform/image-decoders/skia/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 + * 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 +#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/skia/GIFImageReader.h b/WebCore/platform/image-decoders/skia/GIFImageReader.h new file mode 100644 index 0000000..855e6be --- /dev/null +++ b/WebCore/platform/image-decoders/skia/GIFImageReader.h @@ -0,0 +1,216 @@ +/* -*- 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 + +const int cLoopCountNotSeen = -2; + +/* 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 = cLoopCountNotSeen; + 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 + diff --git a/WebCore/platform/image-decoders/skia/JPEGImageDecoder.cpp b/WebCore/platform/image-decoders/skia/JPEGImageDecoder.cpp new file mode 100644 index 0000000..44e0e4c --- /dev/null +++ b/WebCore/platform/image-decoders/skia/JPEGImageDecoder.cpp @@ -0,0 +1,531 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * Portions are Copyright (C) 2001-6 mozilla.org + * + * Other contributors: + * Stuart Parmenter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#include "config.h" +#include "JPEGImageDecoder.h" +#include +#include + +#if PLATFORM(CAIRO) || PLATFORM(QT) || PLATFORM(WX) + +#if COMPILER(MSVC) +// Remove warnings from warning level 4. +#pragma warning(disable : 4611) // warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable + +// if ADDRESS_TAG_BIT is dfined, INT32 has been declared as a typedef in the PlatformSDK (BaseTsd.h), +// so we need to stop jpeglib.h from trying to #define it +// see here for more info: http://www.cygwin.com/ml/cygwin/2004-07/msg01051.html +# if defined(ADDRESS_TAG_BIT) && !defined(XMD_H) +# define XMD_H +# define VTK_JPEG_XMD_H +# endif +#endif // COMPILER(MSVC) + +extern "C" { +#include "jpeglib.h" +} + +#if COMPILER(MSVC) +# if defined(VTK_JPEG_XMD_H) +# undef VTK_JPEG_XMD_H +# undef XMD_H +# endif +#endif // COMPILER(MSVC) + +#include + +namespace WebCore { + +struct decoder_error_mgr { + struct jpeg_error_mgr pub; /* "public" fields for IJG library*/ + jmp_buf setjmp_buffer; /* For handling catastropic errors */ +}; + +enum jstate { + JPEG_HEADER, /* Reading JFIF headers */ + JPEG_START_DECOMPRESS, + JPEG_DECOMPRESS_PROGRESSIVE, /* Output progressive pixels */ + JPEG_DECOMPRESS_SEQUENTIAL, /* Output sequential pixels */ + JPEG_DONE, + JPEG_SINK_NON_JPEG_TRAILER, /* Some image files have a */ + /* non-JPEG trailer */ + JPEG_ERROR +}; + +void init_source(j_decompress_ptr jd); +boolean fill_input_buffer(j_decompress_ptr jd); +void skip_input_data(j_decompress_ptr jd, long num_bytes); +void term_source(j_decompress_ptr jd); +void error_exit(j_common_ptr cinfo); + +/* + * Implementation of a JPEG src object that understands our state machine + */ +struct decoder_source_mgr { + /* public fields; must be first in this struct! */ + struct jpeg_source_mgr pub; + + JPEGImageReader *decoder; +}; + +class JPEGImageReader +{ +public: + JPEGImageReader(JPEGImageDecoder* decoder) + : m_decoder(decoder) + , m_bufferLength(0) + , m_bytesToSkip(0) + , m_state(JPEG_HEADER) + , m_samples(0) + { + memset(&m_info, 0, sizeof(jpeg_decompress_struct)); + + /* We set up the normal JPEG error routines, then override error_exit. */ + m_info.err = jpeg_std_error(&m_err.pub); + m_err.pub.error_exit = error_exit; + + /* Allocate and initialize JPEG decompression object */ + jpeg_create_decompress(&m_info); + + decoder_source_mgr* src = 0; + if (!m_info.src) { + src = (decoder_source_mgr*)fastCalloc(sizeof(decoder_source_mgr), 1); + if (!src) { + m_state = JPEG_ERROR; + return; + } + } + + m_info.src = (jpeg_source_mgr*)src; + + /* Set up callback functions. */ + src->pub.init_source = init_source; + src->pub.fill_input_buffer = fill_input_buffer; + src->pub.skip_input_data = skip_input_data; + src->pub.resync_to_restart = jpeg_resync_to_restart; + src->pub.term_source = term_source; + src->decoder = this; + } + + ~JPEGImageReader() + { + close(); + } + + void close() { + decoder_source_mgr* src = (decoder_source_mgr*)m_info.src; + if (src) + fastFree(src); + m_info.src = 0; + + jpeg_destroy_decompress(&m_info); + } + + void skipBytes(long num_bytes) { + decoder_source_mgr* src = (decoder_source_mgr*)m_info.src; + long bytesToSkip = std::min(num_bytes, (long)src->pub.bytes_in_buffer); + src->pub.bytes_in_buffer -= (size_t)bytesToSkip; + src->pub.next_input_byte += bytesToSkip; + + if (num_bytes > bytesToSkip) + m_bytesToSkip = (size_t)(num_bytes - bytesToSkip); + else + m_bytesToSkip = 0; + } + + bool decode(const Vector& data, bool sizeOnly) { + m_decodingSizeOnly = sizeOnly; + + unsigned newByteCount = data.size() - m_bufferLength; + unsigned readOffset = m_bufferLength - m_info.src->bytes_in_buffer; + + m_info.src->bytes_in_buffer += newByteCount; + m_info.src->next_input_byte = (JOCTET*)(data.data()) + readOffset; + + // If we still have bytes to skip, try to skip those now. + if (m_bytesToSkip) + skipBytes(m_bytesToSkip); + + m_bufferLength = data.size(); + + // We need to do the setjmp here. Otherwise bad things will happen + if (setjmp(m_err.setjmp_buffer)) { + m_state = JPEG_SINK_NON_JPEG_TRAILER; + close(); + return false; + } + + switch (m_state) { + case JPEG_HEADER: + { + /* Read file parameters with jpeg_read_header() */ + if (jpeg_read_header(&m_info, true) == JPEG_SUSPENDED) + return true; /* I/O suspension */ + + /* let libjpeg take care of gray->RGB and YCbCr->RGB conversions */ + switch (m_info.jpeg_color_space) { + case JCS_GRAYSCALE: + case JCS_RGB: + case JCS_YCbCr: + m_info.out_color_space = JCS_RGB; + break; + case JCS_CMYK: + case JCS_YCCK: + default: + m_state = JPEG_ERROR; + return false; + } + + /* + * Don't allocate a giant and superfluous memory buffer + * when the image is a sequential JPEG. + */ + m_info.buffered_image = jpeg_has_multiple_scans(&m_info); + + /* Used to set up image size so arrays can be allocated */ + jpeg_calc_output_dimensions(&m_info); + + /* + * Make a one-row-high sample array that will go away + * when done with image. Always make it big enough to + * hold an RGB row. Since this uses the IJG memory + * manager, it must be allocated before the call to + * jpeg_start_compress(). + */ + int row_stride = m_info.output_width * 4; // RGBA buffer + + + m_samples = (*m_info.mem->alloc_sarray)((j_common_ptr) &m_info, + JPOOL_IMAGE, + row_stride, 1); + + m_state = JPEG_START_DECOMPRESS; + + // We can fill in the size now that the header is available. + m_decoder->setSize(m_info.image_width, m_info.image_height); + + if (m_decodingSizeOnly) { + // We can stop here. + // Reduce our buffer length and available data. + m_bufferLength -= m_info.src->bytes_in_buffer; + m_info.src->bytes_in_buffer = 0; + return true; + } + } + + case JPEG_START_DECOMPRESS: + { + /* Set parameters for decompression */ + /* FIXME -- Should reset dct_method and dither mode + * for final pass of progressive JPEG + */ + m_info.dct_method = JDCT_ISLOW; + m_info.dither_mode = JDITHER_FS; + m_info.do_fancy_upsampling = true; + m_info.enable_2pass_quant = false; + m_info.do_block_smoothing = true; + + /* Start decompressor */ + if (!jpeg_start_decompress(&m_info)) + return true; /* I/O suspension */ + + /* If this is a progressive JPEG ... */ + m_state = (m_info.buffered_image) ? JPEG_DECOMPRESS_PROGRESSIVE : JPEG_DECOMPRESS_SEQUENTIAL; + } + + case JPEG_DECOMPRESS_SEQUENTIAL: + { + if (m_state == JPEG_DECOMPRESS_SEQUENTIAL) { + + if (!m_decoder->outputScanlines()) + return true; /* I/O suspension */ + + /* If we've completed image output ... */ + assert(m_info.output_scanline == m_info.output_height); + m_state = JPEG_DONE; + } + } + + case JPEG_DECOMPRESS_PROGRESSIVE: + { + if (m_state == JPEG_DECOMPRESS_PROGRESSIVE) { + int status; + do { + status = jpeg_consume_input(&m_info); + } while ((status != JPEG_SUSPENDED) && + (status != JPEG_REACHED_EOI)); + + for (;;) { + if (m_info.output_scanline == 0) { + int scan = m_info.input_scan_number; + + /* if we haven't displayed anything yet (output_scan_number==0) + and we have enough data for a complete scan, force output + of the last full scan */ + if ((m_info.output_scan_number == 0) && + (scan > 1) && + (status != JPEG_REACHED_EOI)) + scan--; + + if (!jpeg_start_output(&m_info, scan)) + return true; /* I/O suspension */ + } + + if (m_info.output_scanline == 0xffffff) + m_info.output_scanline = 0; + + if (!m_decoder->outputScanlines()) { + if (m_info.output_scanline == 0) + /* didn't manage to read any lines - flag so we don't call + jpeg_start_output() multiple times for the same scan */ + m_info.output_scanline = 0xffffff; + return true; /* I/O suspension */ + } + + if (m_info.output_scanline == m_info.output_height) { + if (!jpeg_finish_output(&m_info)) + return true; /* I/O suspension */ + + if (jpeg_input_complete(&m_info) && + (m_info.input_scan_number == m_info.output_scan_number)) + break; + + m_info.output_scanline = 0; + } + } + + m_state = JPEG_DONE; + } + } + + case JPEG_DONE: + { + /* Finish decompression */ + if (!jpeg_finish_decompress(&m_info)) + return true; /* I/O suspension */ + + m_state = JPEG_SINK_NON_JPEG_TRAILER; + + /* we're done */ + break; + } + + case JPEG_SINK_NON_JPEG_TRAILER: + break; + + case JPEG_ERROR: + break; + } + + return true; + } + + jpeg_decompress_struct* info() { return &m_info; } + JSAMPARRAY samples() const { return m_samples; } + JPEGImageDecoder* decoder() { return m_decoder; } + +private: + JPEGImageDecoder* m_decoder; + unsigned m_bufferLength; + int m_bytesToSkip; + bool m_decodingSizeOnly; + bool m_initialized; + + jpeg_decompress_struct m_info; + decoder_error_mgr m_err; + jstate m_state; + + JSAMPARRAY m_samples; +}; + +/* Override the standard error method in the IJG JPEG decoder code. */ +void error_exit(j_common_ptr cinfo) +{ + /* Return control to the setjmp point. */ + decoder_error_mgr *err = (decoder_error_mgr *) cinfo->err; + longjmp(err->setjmp_buffer, -1); +} + +void init_source(j_decompress_ptr jd) +{ +} + +void skip_input_data(j_decompress_ptr jd, long num_bytes) +{ + decoder_source_mgr *src = (decoder_source_mgr *)jd->src; + src->decoder->skipBytes(num_bytes); +} + +boolean fill_input_buffer(j_decompress_ptr jd) +{ + // Our decode step always sets things up properly, so if this method is ever + // called, then we have hit the end of the buffer. A return value of FALSE indicates + // that we have no data to supply yet. + return false; +} + +void term_source (j_decompress_ptr jd) +{ + decoder_source_mgr *src = (decoder_source_mgr *)jd->src; + src->decoder->decoder()->jpegComplete(); +} + +JPEGImageDecoder::JPEGImageDecoder() +: m_reader(0) +{} + +JPEGImageDecoder::~JPEGImageDecoder() +{ + delete m_reader; +} + +// Take the data and store it. +void JPEGImageDecoder::setData(SharedBuffer* data, bool allDataReceived) +{ + if (m_failed) + return; + + // Cache our new data. + ImageDecoder::setData(data, allDataReceived); + + // Create the JPEG reader. + if (!m_reader && !m_failed) + m_reader = new JPEGImageReader(this); +} + +// Whether or not the size information has been decoded yet. +bool JPEGImageDecoder::isSizeAvailable() const +{ + // If we have pending data to decode, send it to the JPEG reader now. + if (!m_sizeAvailable && m_reader) { + if (m_failed) + return false; + + // The decoder will go ahead and aggressively consume everything up until the + // size is encountered. + decode(true); + } + + return m_sizeAvailable; +} + +RGBA32Buffer* JPEGImageDecoder::frameBufferAtIndex(size_t index) +{ + if (index) + return 0; + + if (m_frameBufferCache.isEmpty()) + m_frameBufferCache.resize(1); + + RGBA32Buffer& frame = m_frameBufferCache[0]; + if (frame.status() != RGBA32Buffer::FrameComplete && m_reader) + // Decode this frame. + decode(); + return &frame; +} + +// Feed data to the JPEG reader. +void JPEGImageDecoder::decode(bool sizeOnly) const +{ + if (m_failed) + return; + + m_failed = !m_reader->decode(m_data->buffer(), sizeOnly); + + if (m_failed || (!m_frameBufferCache.isEmpty() && m_frameBufferCache[0].status() == RGBA32Buffer::FrameComplete)) { + delete m_reader; + m_reader = 0; + } +} + +bool JPEGImageDecoder::outputScanlines() +{ + if (m_frameBufferCache.isEmpty()) + return false; + + // Resize to the width and height of the image. + RGBA32Buffer& buffer = m_frameBufferCache[0]; + if (buffer.status() == RGBA32Buffer::FrameEmpty) { + // Let's resize our buffer now to the correct width/height. + RGBA32Array& bytes = buffer.bytes(); + bytes.resize(m_size.width() * m_size.height()); + + // Update our status to be partially complete. + buffer.setStatus(RGBA32Buffer::FramePartial); + + // For JPEGs, the frame always fills the entire image. + buffer.setRect(IntRect(0, 0, m_size.width(), m_size.height())); + + // We don't have alpha (this is the default when the buffer is constructed). + } + + jpeg_decompress_struct* info = m_reader->info(); + JSAMPARRAY samples = m_reader->samples(); + + unsigned* dst = buffer.bytes().data() + info->output_scanline * m_size.width(); + + while (info->output_scanline < info->output_height) { + /* Request one scanline. Returns 0 or 1 scanlines. */ + if (jpeg_read_scanlines(info, samples, 1) != 1) + return false; + JSAMPLE *j1 = samples[0]; + for (unsigned i = 0; i < info->output_width; ++i) { + unsigned r = *j1++; + unsigned g = *j1++; + unsigned b = *j1++; + RGBA32Buffer::setRGBA(*dst++, r, g, b, 0xFF); + } + + buffer.ensureHeight(info->output_scanline); + } + + return true; +} + +void JPEGImageDecoder::jpegComplete() +{ + if (m_frameBufferCache.isEmpty()) + return; + + // Hand back an appropriately sized buffer, even if the image ended up being empty. + RGBA32Buffer& buffer = m_frameBufferCache[0]; + buffer.setStatus(RGBA32Buffer::FrameComplete); +} + +} + +#endif // PLATFORM(CAIRO) diff --git a/WebCore/platform/image-decoders/skia/JPEGImageDecoder.h b/WebCore/platform/image-decoders/skia/JPEGImageDecoder.h new file mode 100644 index 0000000..b4d7b2a --- /dev/null +++ b/WebCore/platform/image-decoders/skia/JPEGImageDecoder.h @@ -0,0 +1,74 @@ +/* + * 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 JPEG_DECODER_H_ +#define JPEG_DECODER_H_ + +#include "ImageDecoder.h" + +namespace WebCore { + +class JPEGImageReader; + +// This class decodes the JPEG image format. +class JPEGImageDecoder : public ImageDecoder +{ +public: + JPEGImageDecoder(); + ~JPEGImageDecoder(); + + virtual String filenameExtension() const { return "jpg"; } + + // 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; + + virtual RGBA32Buffer* frameBufferAtIndex(size_t index); + + virtual bool supportsAlpha() const { return false; } + + void decode(bool sizeOnly = false) const; + + JPEGImageReader* reader() { return m_reader; } + + void setSize(int width, int height) { + if (!m_sizeAvailable) { + m_sizeAvailable = true; + m_size = IntSize(width, height); + } + } + + bool outputScanlines(); + void jpegComplete(); + +private: + mutable JPEGImageReader* m_reader; +}; + +} + +#endif diff --git a/WebCore/platform/image-decoders/skia/PNGImageDecoder.cpp b/WebCore/platform/image-decoders/skia/PNGImageDecoder.cpp new file mode 100644 index 0000000..17143b1 --- /dev/null +++ b/WebCore/platform/image-decoders/skia/PNGImageDecoder.cpp @@ -0,0 +1,403 @@ +/* + * Copyright (C) 2006 Apple Computer, Inc. + * + * Portions are Copyright (C) 2001 mozilla.org + * + * Other contributors: + * Stuart Parmenter + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Alternatively, the contents of this file may be used under the terms + * of either the Mozilla Public License Version 1.1, found at + * http://www.mozilla.org/MPL/ (the "MPL") or the GNU General Public + * License Version 2.0, found at http://www.fsf.org/copyleft/gpl.html + * (the "GPL"), in which case the provisions of the MPL or the GPL are + * applicable instead of those above. If you wish to allow use of your + * version of this file only under the terms of one of those two + * licenses (the MPL or the GPL) and not to allow others to use your + * version of this file under the LGPL, indicate your decision by + * deletingthe provisions above and replace them with the notice and + * other provisions required by the MPL or the GPL, as the case may be. + * If you do not delete the provisions above, a recipient may use your + * version of this file under any of the LGPL, the MPL or the GPL. + */ + +#include "config.h" +#include "PNGImageDecoder.h" +#include "png.h" +#include "assert.h" + +#if PLATFORM(CAIRO) || PLATFORM(QT) || PLATFORM(WX) + +#if COMPILER(MSVC) +// Remove warnings from warning level 4. +#pragma warning(disable : 4611) // warning C4611: interaction between '_setjmp' and C++ object destruction is non-portable +#endif + +namespace WebCore { + +// Gamma constants. +const double cMaxGamma = 21474.83; +const double cDefaultGamma = 2.2; +const double cInverseGamma = 0.45455; + +// Protect against large PNGs. See Mozilla's bug #251381 for more info. +const long cMaxPNGSize = 1000000L; + +// Called if the decoding of the image fails. +static void PNGAPI decodingFailed(png_structp png_ptr, png_const_charp error_msg); + +// Callbacks given to the read struct. The first is for warnings (we want to treat a particular warning +// as an error, which is why we have to register this callback. +static void PNGAPI decodingWarning(png_structp png_ptr, png_const_charp warning_msg); + +// Called when we have obtained the header information (including the size). +static void PNGAPI headerAvailable(png_structp png_ptr, png_infop info_ptr); + +// Called when a row is ready. +static void PNGAPI rowAvailable(png_structp png_ptr, png_bytep new_row, + png_uint_32 row_num, int pass); + +// Called when we have completely finished decoding the image. +static void PNGAPI pngComplete(png_structp png_ptr, png_infop info_ptr); + +class PNGImageReader +{ +public: + PNGImageReader(PNGImageDecoder* decoder) + : m_readOffset(0), m_decodingSizeOnly(false), m_interlaceBuffer(0), m_hasAlpha(0) + { + m_png = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, decodingFailed, decodingWarning); + m_info = png_create_info_struct(m_png); + png_set_progressive_read_fn(m_png, decoder, headerAvailable, rowAvailable, pngComplete); + } + + ~PNGImageReader() + { + close(); + } + + void close() { + if (m_png && m_info) + png_destroy_read_struct(&m_png, &m_info, 0); + delete []m_interlaceBuffer; + m_readOffset = 0; + } + + void decode(const Vector& data, bool sizeOnly) + { + m_decodingSizeOnly = sizeOnly; + + // We need to do the setjmp here. Otherwise bad things will happen + if (setjmp(m_png->jmpbuf)) { + close(); + return; + } + + // Go ahead and assume we consumed all the data. If we consume less, the + // callback will adjust our read offset accordingly. Do not attempt to adjust the + // offset after png_process_data returns. + unsigned offset = m_readOffset; + unsigned remaining = data.size() - m_readOffset; + m_readOffset = data.size(); + png_process_data(m_png, m_info, (png_bytep)(data.data()) + offset, remaining); + } + + bool decodingSizeOnly() const { return m_decodingSizeOnly; } + png_structp pngPtr() const { return m_png; } + png_infop infoPtr() const { return m_info; } + png_bytep interlaceBuffer() const { return m_interlaceBuffer; } + bool hasAlpha() const { return m_hasAlpha; } + + void setReadOffset(unsigned offset) { m_readOffset = offset; } + void setHasAlpha(bool b) { m_hasAlpha = b; } + + void createInterlaceBuffer(int size) { + m_interlaceBuffer = new png_byte[size]; + } + +private: + unsigned m_readOffset; + bool m_decodingSizeOnly; + png_structp m_png; + png_infop m_info; + png_bytep m_interlaceBuffer; + bool m_hasAlpha; +}; + +PNGImageDecoder::PNGImageDecoder() +: m_reader(0) +{ + m_frameBufferCache.resize(1); +} + +PNGImageDecoder::~PNGImageDecoder() +{ + delete m_reader; +} + +// Take the data and store it. +void PNGImageDecoder::setData(SharedBuffer* data, bool allDataReceived) +{ + if (m_failed) + return; + + // Cache our new data. + ImageDecoder::setData(data, allDataReceived); + + // Create the PNG reader. + if (!m_reader && !m_failed) + m_reader = new PNGImageReader(this); +} + +// Whether or not the size information has been decoded yet. +bool PNGImageDecoder::isSizeAvailable() const +{ + // If we have pending data to decode, send it to the PNG reader now. + if (!m_sizeAvailable && m_reader) { + if (m_failed) + return false; + + // The decoder will go ahead and aggressively consume everything up until the + // size is encountered. + decode(true); + } + + return m_sizeAvailable; +} + +RGBA32Buffer* PNGImageDecoder::frameBufferAtIndex(size_t index) +{ + if (index) + return 0; + + RGBA32Buffer& frame = m_frameBufferCache[0]; + if (frame.status() != RGBA32Buffer::FrameComplete && m_reader) + // Decode this frame. + decode(); + return &frame; +} + +// Feed data to the PNG reader. +void PNGImageDecoder::decode(bool sizeOnly) const +{ + if (m_failed) + return; + + m_reader->decode(m_data->buffer(), sizeOnly); + + if (m_failed || (m_frameBufferCache[0].status() == RGBA32Buffer::FrameComplete)) { + delete m_reader; + m_reader = 0; + } +} + +void decodingFailed(png_structp png, png_const_charp errorMsg) +{ + static_cast(png_get_progressive_ptr(png))->decodingFailed(); + longjmp(png->jmpbuf, 1); +} + +void decodingWarning(png_structp png, png_const_charp warningMsg) +{ + // Mozilla did this, so we will too. + // Convert a tRNS warning to be an error (documented in bugzilla.mozilla.org bug #251381) + if (!strncmp(warningMsg, "Missing PLTE before tRNS", 24)) + png_error(png, warningMsg); +} + +void headerAvailable(png_structp png, png_infop info) +{ + static_cast(png_get_progressive_ptr(png))->headerAvailable(); +} + +void PNGImageDecoder::headerAvailable() +{ + png_structp png = reader()->pngPtr(); + png_infop info = reader()->infoPtr(); + png_uint_32 width = png->width; + png_uint_32 height = png->height; + + // Protect against large images. + if (png->width > cMaxPNGSize || png->height > cMaxPNGSize) { + m_failed = true; + longjmp(png->jmpbuf, 1); + return; + } + + // We can fill in the size now that the header is available. + if (!m_sizeAvailable) { + m_sizeAvailable = true; + m_size = IntSize(width, height); + } + + int bitDepth, colorType, interlaceType, compressionType, filterType, channels; + png_get_IHDR(png, info, &width, &height, &bitDepth, &colorType, + &interlaceType, &compressionType, &filterType); + + // The options we set here match what Mozilla does. + + // Expand to ensure we use 24-bit for RGB and 32-bit for RGBA. + if (colorType == PNG_COLOR_TYPE_PALETTE || + (colorType == PNG_COLOR_TYPE_GRAY && bitDepth < 8)) + png_set_expand(png); + + png_bytep trns = 0; + int trnsCount = 0; + if (png_get_valid(png, info, PNG_INFO_tRNS)) { + png_get_tRNS(png, info, &trns, &trnsCount, 0); + png_set_expand(png); + } + + if (bitDepth == 16) + png_set_strip_16(png); + + if (colorType == PNG_COLOR_TYPE_GRAY || + colorType == PNG_COLOR_TYPE_GRAY_ALPHA) + png_set_gray_to_rgb(png); + + // Deal with gamma and keep it under our control. + double gamma; + if (png_get_gAMA(png, info, &gamma)) { + if ((gamma <= 0.0) || (gamma > cMaxGamma)) { + gamma = cInverseGamma; + png_set_gAMA(png, info, gamma); + } + png_set_gamma(png, cDefaultGamma, gamma); + } + else + png_set_gamma(png, cDefaultGamma, cInverseGamma); + + // Tell libpng to send us rows for interlaced pngs. + if (interlaceType == PNG_INTERLACE_ADAM7) + png_set_interlace_handling(png); + + // Update our info now + png_read_update_info(png, info); + channels = png_get_channels(png, info); + assert(channels == 3 || channels == 4); + + reader()->setHasAlpha(channels == 4); + + if (reader()->decodingSizeOnly()) { + // If we only needed the size, halt the reader. + reader()->setReadOffset(m_data->size() - png->buffer_size); + png->buffer_size = 0; + } +} + +void rowAvailable(png_structp png, png_bytep rowBuffer, + png_uint_32 rowIndex, int interlacePass) +{ + static_cast(png_get_progressive_ptr(png))->rowAvailable(rowBuffer, rowIndex, interlacePass); +} + +void PNGImageDecoder::rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, int interlacePass) +{ + // Resize to the width and height of the image. + RGBA32Buffer& buffer = m_frameBufferCache[0]; + if (buffer.status() == RGBA32Buffer::FrameEmpty) { + // Let's resize our buffer now to the correct width/height. + RGBA32Array& bytes = buffer.bytes(); + bytes.resize(m_size.width() * m_size.height()); + + // Update our status to be partially complete. + buffer.setStatus(RGBA32Buffer::FramePartial); + + // For PNGs, the frame always fills the entire image. + buffer.setRect(IntRect(0, 0, m_size.width(), m_size.height())); + + if (reader()->pngPtr()->interlaced) + reader()->createInterlaceBuffer((reader()->hasAlpha() ? 4 : 3) * m_size.width() * m_size.height()); + } + + if (rowBuffer == 0) + return; + + /* libpng comments (pasted in here to explain what follows) + * + * this function is called for every row in the image. If the + * image is interlacing, and you turned on the interlace handler, + * this function will be called for every row in every pass. + * Some of these rows will not be changed from the previous pass. + * When the row is not changed, the new_row variable will be NULL. + * The rows and passes are called in order, so you don't really + * need the row_num and pass, but I'm supplying them because it + * may make your life easier. + * + * For the non-NULL rows of interlaced images, you must call + * png_progressive_combine_row() passing in the row and the + * old row. You can call this function for NULL rows (it will + * just return) and for non-interlaced images (it just does the + * memcpy for you) if it will make the code easier. Thus, you + * can just do this for all cases: + * + * png_progressive_combine_row(png_ptr, old_row, new_row); + * + * where old_row is what was displayed for previous rows. Note + * that the first pass (pass == 0 really) will completely cover + * the old row, so the rows do not have to be initialized. After + * the first pass (and only for interlaced images), you will have + * to pass the current row, and the function will combine the + * old row and the new row. + */ + + png_structp png = reader()->pngPtr(); + bool hasAlpha = reader()->hasAlpha(); + unsigned colorChannels = hasAlpha ? 4 : 3; + png_bytep row; + png_bytep interlaceBuffer = reader()->interlaceBuffer(); + if (interlaceBuffer) { + row = interlaceBuffer + (rowIndex * colorChannels * m_size.width()); + png_progressive_combine_row(png, row, rowBuffer); + } + else + row = rowBuffer; + + // Copy the data into our buffer. + int width = m_size.width(); + unsigned* dst = buffer.bytes().data() + rowIndex * width; + bool sawAlpha = false; + for (int i = 0; i < width; i++) { + unsigned red = *row++; + unsigned green = *row++; + unsigned blue = *row++; + unsigned alpha = (hasAlpha ? *row++ : 255); + RGBA32Buffer::setRGBA(*dst++, red, green, blue, alpha); + if (!sawAlpha && alpha < 255) { + sawAlpha = true; + buffer.setHasAlpha(true); + } + } + + buffer.ensureHeight(rowIndex + 1); +} + +void pngComplete(png_structp png, png_infop info) +{ + static_cast(png_get_progressive_ptr(png))->pngComplete(); +} + +void PNGImageDecoder::pngComplete() +{ + // Hand back an appropriately sized buffer, even if the image ended up being empty. + RGBA32Buffer& buffer = m_frameBufferCache[0]; + buffer.setStatus(RGBA32Buffer::FrameComplete); +} + +} + +#endif // PLATFORM(CAIRO) diff --git a/WebCore/platform/image-decoders/skia/PNGImageDecoder.h b/WebCore/platform/image-decoders/skia/PNGImageDecoder.h new file mode 100644 index 0000000..8c73785 --- /dev/null +++ b/WebCore/platform/image-decoders/skia/PNGImageDecoder.h @@ -0,0 +1,68 @@ +/* + * 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 PNG_DECODER_H_ +#define PNG_DECODER_H_ + +#include "ImageDecoder.h" + +namespace WebCore { + +class PNGImageReader; + +// This class decodes the PNG image format. +class PNGImageDecoder : public ImageDecoder +{ +public: + PNGImageDecoder(); + ~PNGImageDecoder(); + + virtual String filenameExtension() const { return "png"; } + + // 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; + + virtual RGBA32Buffer* frameBufferAtIndex(size_t index); + + void decode(bool sizeOnly = false) const; + + PNGImageReader* reader() { return m_reader; } + + // Callbacks from libpng + void decodingFailed() { m_failed = true; } + void headerAvailable(); + void rowAvailable(unsigned char* rowBuffer, unsigned rowIndex, int interlacePass); + void pngComplete(); + +private: + mutable PNGImageReader* m_reader; +}; + +} + +#endif