| /* |
| * Copyright (C) 2006 Friedemann Kleint <fkleint@trolltech.com> |
| * Copyright (C) 2006 Trolltech ASA |
| * |
| * 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 "ImageDecoderQt.h" |
| |
| #include <QtCore/QByteArray> |
| #include <QtCore/QBuffer> |
| |
| #include <QtGui/QImageReader> |
| #include <qdebug.h> |
| |
| namespace { |
| const QImage::Format DesiredFormat = QImage::Format_ARGB32; |
| const bool debugImageDecoderQt = false; |
| } |
| |
| namespace WebCore { |
| ImageDecoderQt::ImageData::ImageData(const QPixmap& image, QImage::Format format, |
| ImageState imageState, int duration) |
| : m_image(image), m_format(format), |
| m_imageState(imageState), m_duration(duration) |
| { |
| } |
| |
| // Context, maintains IODevice on a data buffer. |
| class ImageDecoderQt::ReadContext { |
| public: |
| |
| enum LoadMode { |
| // Load images incrementally. This is still experimental and |
| // will cause the image plugins to report errors. |
| // Also note that as of Qt 4.2.2, the JPEG loader does not return error codes |
| // on "preliminary end of data". |
| LoadIncrementally, |
| // Load images only if all data have been received |
| LoadComplete }; |
| |
| ReadContext(const IncomingData & data, LoadMode loadMode, ImageList &target); |
| |
| enum ReadResult { ReadEOF, ReadFailed, ReadPartial, ReadComplete }; |
| |
| // Append data and read out all images. Returns the result |
| // of the last read operation, so, even if ReadPartial is returned, |
| // a few images might have been read. |
| ReadResult read(bool allDataReceived); |
| |
| private: |
| enum IncrementalReadResult { IncrementalReadFailed, IncrementalReadPartial, IncrementalReadComplete }; |
| // Incrementally read an image |
| IncrementalReadResult readImageLines(ImageData &); |
| |
| const LoadMode m_loadMode; |
| |
| QByteArray m_data; |
| QBuffer m_buffer; |
| QImageReader m_reader; |
| |
| ImageList &m_target; |
| |
| // Detected data format of the stream |
| enum QImage::Format m_dataFormat; |
| QSize m_size; |
| |
| }; |
| |
| ImageDecoderQt::ReadContext::ReadContext(const IncomingData & data, LoadMode loadMode, ImageList &target) : |
| m_loadMode(loadMode), |
| m_data(data.data(), data.size()), |
| m_buffer(&m_data), |
| m_reader(&m_buffer), |
| m_target(target), |
| m_dataFormat(QImage::Format_Invalid) |
| { |
| m_buffer.open(QIODevice::ReadOnly); |
| } |
| |
| |
| ImageDecoderQt::ReadContext::ReadResult |
| ImageDecoderQt::ReadContext::read(bool allDataReceived) |
| { |
| // Complete mode: Read only all all data received |
| if (m_loadMode == LoadComplete && !allDataReceived) |
| return ReadPartial; |
| |
| // Attempt to read out all images |
| while (true) { |
| if (m_target.empty() || m_target.back().m_imageState == ImageComplete) { |
| // Start a new image. |
| if (!m_reader.canRead()) |
| return ReadEOF; |
| |
| // Attempt to construct an empty image of the matching size and format |
| // for efficient reading |
| QImage newImage = m_dataFormat != QImage::Format_Invalid ? |
| QImage(m_size,m_dataFormat) : QImage(); |
| m_target.push_back(ImageData(QPixmap::fromImage(newImage), m_dataFormat)); |
| } |
| |
| // read chunks |
| switch (readImageLines(m_target.back())) { |
| case IncrementalReadFailed: |
| m_target.pop_back(); |
| return ReadFailed; |
| case IncrementalReadPartial: |
| return ReadPartial; |
| case IncrementalReadComplete: |
| m_target.back().m_imageState = ImageComplete; |
| //store for next |
| m_dataFormat = m_target.back().m_format; |
| m_size = m_target.back().m_image.size(); |
| const bool supportsAnimation = m_reader.supportsAnimation(); |
| |
| if (debugImageDecoderQt) |
| qDebug() << "readImage(): #" << m_target.size() << " complete, " << m_size << " format " << m_dataFormat |
| << " supportsAnimation=" << supportsAnimation ; |
| // No point in readinfg further |
| if (!supportsAnimation) |
| return ReadComplete; |
| |
| break; |
| } |
| } |
| return ReadComplete; |
| } |
| |
| |
| |
| ImageDecoderQt::ReadContext::IncrementalReadResult |
| ImageDecoderQt::ReadContext::readImageLines(ImageData &imageData) |
| { |
| // TODO: Implement incremental reading here, |
| // set state to reflect complete header, etc. |
| // For now, we read the whole image. |
| |
| const qint64 startPos = m_buffer.pos (); |
| // Oops, failed. Rewind. |
| QImage image = imageData.m_image.toImage(); |
| if (!m_reader.read(&image)) { |
| m_buffer.seek(startPos); |
| const bool gotHeader = imageData.m_image.size().width(); |
| |
| if (debugImageDecoderQt) |
| qDebug() << "readImageLines(): read() failed: " << m_reader.errorString() |
| << " got header=" << gotHeader; |
| // [Experimental] Did we manage to read the header? |
| if (gotHeader) { |
| imageData.m_imageState = ImageHeaderValid; |
| return IncrementalReadPartial; |
| } |
| return IncrementalReadFailed; |
| } |
| imageData.m_image = QPixmap::fromImage(image); |
| imageData.m_duration = m_reader.nextImageDelay(); |
| return IncrementalReadComplete; |
| } |
| |
| |
| // ImageDecoderQt |
| ImageDecoderQt::ImageDecoderQt( ) |
| { |
| } |
| |
| ImageDecoderQt::~ImageDecoderQt() |
| { |
| } |
| |
| bool ImageDecoderQt::hasFirstImageHeader() const |
| { |
| return !m_imageList.empty() && m_imageList[0].m_imageState >= ImageHeaderValid; |
| } |
| |
| void ImageDecoderQt::reset() |
| { |
| m_failed = false; |
| m_imageList.clear(); |
| m_sizeAvailable = false; |
| m_size = IntSize(-1, -1); |
| } |
| |
| void ImageDecoderQt::setData(const IncomingData &data, bool allDataReceived) |
| { |
| reset(); |
| ReadContext readContext(data, ReadContext::LoadComplete, m_imageList); |
| |
| if (debugImageDecoderQt) |
| qDebug() << " setData " << data.size() << " image bytes, complete=" << allDataReceived; |
| |
| const ReadContext::ReadResult readResult = readContext.read(allDataReceived); |
| |
| if (debugImageDecoderQt) |
| qDebug() << " read returns " << readResult; |
| |
| switch ( readResult) { |
| case ReadContext::ReadFailed: |
| m_failed = true; |
| break; |
| case ReadContext::ReadEOF: |
| case ReadContext::ReadPartial: |
| case ReadContext::ReadComplete: |
| // Did we read anything - try to set the size. |
| if (hasFirstImageHeader()) { |
| m_sizeAvailable = true; |
| m_size = m_imageList[0].m_image.size(); |
| } |
| break; |
| } |
| } |
| |
| |
| bool ImageDecoderQt::isSizeAvailable() const |
| { |
| if (debugImageDecoderQt) |
| qDebug() << " ImageDecoderQt::isSizeAvailable() returns" << m_sizeAvailable; |
| return m_sizeAvailable; |
| } |
| |
| int ImageDecoderQt::frameCount() const |
| { |
| return m_imageList.size(); |
| } |
| |
| |
| int ImageDecoderQt::repetitionCount() const |
| { |
| // TODO: Am I Moses?! |
| return cAnimationNone; |
| } |
| |
| |
| bool ImageDecoderQt::ImageDecoderQt::supportsAlpha() const |
| { |
| return hasFirstImageHeader() && m_imageList[0].m_image.hasAlphaChannel(); |
| } |
| |
| int ImageDecoderQt::duration(size_t index) const |
| { |
| if (index >= m_imageList.size()) |
| return 0; |
| return m_imageList[index].m_duration; |
| } |
| |
| RGBA32Buffer* ImageDecoderQt::frameBufferAtIndex(size_t index) |
| { |
| Q_ASSERT("use imageAtIndex instead"); |
| return 0; |
| } |
| |
| const QPixmap* ImageDecoderQt::imageAtIndex(size_t index) const |
| { |
| if (debugImageDecoderQt) |
| qDebug() << "ImageDecoderQt::imageAtIndex(" << index << ')'; |
| |
| if (index >= m_imageList.size()) |
| return 0; |
| return &m_imageList[index].m_image; |
| } |
| |
| } |
| |
| // vim: ts=4 sw=4 et |