2 * Copyright (C) 2006 Friedemann Kleint <fkleint@trolltech.com>
3 * Copyright (C) 2006 Trolltech ASA
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
10 * 1. Redistributions of source code must retain the above copyright
11 * notice, this list of conditions and the following disclaimer.
12 * 2. Redistributions in binary form must reproduce the above copyright
13 * notice, this list of conditions and the following disclaimer in the
14 * documentation and/or other materials provided with the distribution.
16 * THIS SOFTWARE IS PROVIDED BY APPLE COMPUTER, INC. ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
18 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE COMPUTER, INC. OR
20 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
24 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
26 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
30 #include "ImageDecoderQt.h"
32 #include <QtCore/QByteArray>
33 #include <QtCore/QBuffer>
35 #include <QtGui/QImageReader>
39 const QImage::Format DesiredFormat = QImage::Format_ARGB32;
40 const bool debugImageDecoderQt = false;
44 ImageDecoderQt::ImageData::ImageData(const QImage& image, ImageState imageState, int duration) :
45 m_image(image), m_imageState(imageState), m_duration(duration)
49 // Context, maintains IODevice on a data buffer.
50 class ImageDecoderQt::ReadContext {
54 // Load images incrementally. This is still experimental and
55 // will cause the image plugins to report errors.
56 // Also note that as of Qt 4.2.2, the JPEG loader does not return error codes
57 // on "preliminary end of data".
59 // Load images only if all data have been received
62 ReadContext(const IncomingData & data, LoadMode loadMode, ImageList &target);
64 enum ReadResult { ReadEOF, ReadFailed, ReadPartial, ReadComplete };
66 // Append data and read out all images. Returns the result
67 // of the last read operation, so, even if ReadPartial is returned,
68 // a few images might have been read.
69 ReadResult read(bool allDataReceived);
71 QImageReader *reader() { return &m_reader; }
74 enum IncrementalReadResult { IncrementalReadFailed, IncrementalReadPartial, IncrementalReadComplete };
75 // Incrementally read an image
76 IncrementalReadResult readImageLines(ImageData &);
78 const LoadMode m_loadMode;
82 QImageReader m_reader;
86 // Detected data format of the stream
87 enum QImage::Format m_dataFormat;
92 ImageDecoderQt::ReadContext::ReadContext(const IncomingData & data, LoadMode loadMode, ImageList &target) :
94 m_data(data.data(), data.size()),
98 m_dataFormat(QImage::Format_Invalid)
100 m_buffer.open(QIODevice::ReadOnly);
104 ImageDecoderQt::ReadContext::ReadResult
105 ImageDecoderQt::ReadContext::read(bool allDataReceived)
107 // Complete mode: Read only all all data received
108 if (m_loadMode == LoadComplete && !allDataReceived)
111 // Attempt to read out all images
113 if (m_target.empty() || m_target.back().m_imageState == ImageComplete) {
114 // Start a new image.
115 if (!m_reader.canRead())
118 // Attempt to construct an empty image of the matching size and format
119 // for efficient reading
120 QImage newImage = m_dataFormat != QImage::Format_Invalid ?
121 QImage(m_size,m_dataFormat) : QImage();
122 m_target.push_back(ImageData(newImage));
126 switch (readImageLines(m_target.back())) {
127 case IncrementalReadFailed:
130 case IncrementalReadPartial:
132 case IncrementalReadComplete:
133 m_target.back().m_imageState = ImageComplete;
135 m_dataFormat = m_target.back().m_image.format();
136 m_size = m_target.back().m_image.size();
137 const bool supportsAnimation = m_reader.supportsAnimation();
139 if (debugImageDecoderQt)
140 qDebug() << "readImage(): #" << m_target.size() << " complete, " << m_size << " format " << m_dataFormat
141 << " supportsAnimation=" << supportsAnimation ;
142 // No point in readinfg further
143 if (!supportsAnimation)
154 ImageDecoderQt::ReadContext::IncrementalReadResult
155 ImageDecoderQt::ReadContext::readImageLines(ImageData &imageData)
157 // TODO: Implement incremental reading here,
158 // set state to reflect complete header, etc.
159 // For now, we read the whole image.
161 const qint64 startPos = m_buffer.pos ();
162 // Oops, failed. Rewind.
163 if (!m_reader.read(&imageData.m_image)) {
164 m_buffer.seek(startPos);
165 const bool gotHeader = imageData.m_image.size().width();
167 if (debugImageDecoderQt)
168 qDebug() << "readImageLines(): read() failed: " << m_reader.errorString()
169 << " got header=" << gotHeader;
170 // [Experimental] Did we manage to read the header?
172 imageData.m_imageState = ImageHeaderValid;
173 return IncrementalReadPartial;
175 return IncrementalReadFailed;
177 imageData.m_duration = m_reader.nextImageDelay();
178 return IncrementalReadComplete;
183 ImageDecoderQt::ImageDecoderQt( )
187 ImageDecoderQt::~ImageDecoderQt()
191 bool ImageDecoderQt::hasFirstImageHeader() const
193 return !m_imageList.empty() && m_imageList[0].m_imageState >= ImageHeaderValid;
196 void ImageDecoderQt::reset()
200 m_pixmapCache.clear();
201 m_sizeAvailable = false;
202 m_loopCount = cAnimationNone;
203 m_size = IntSize(-1, -1);
206 void ImageDecoderQt::setData(const IncomingData &data, bool allDataReceived)
209 ReadContext readContext(data, ReadContext::LoadComplete, m_imageList);
211 if (debugImageDecoderQt)
212 qDebug() << " setData " << data.size() << " image bytes, complete=" << allDataReceived;
214 const ReadContext::ReadResult readResult = readContext.read(allDataReceived);
216 if (debugImageDecoderQt)
217 qDebug() << " read returns " << readResult;
219 switch ( readResult) {
220 case ReadContext::ReadFailed:
223 case ReadContext::ReadEOF:
224 case ReadContext::ReadPartial:
225 case ReadContext::ReadComplete:
226 // Did we read anything - try to set the size.
227 if (hasFirstImageHeader()) {
228 m_sizeAvailable = true;
229 m_size = m_imageList[0].m_image.size();
231 if (readContext.reader()->supportsAnimation()) {
232 if (readContext.reader()->loopCount() != -1)
233 m_loopCount = readContext.reader()->loopCount();
235 m_loopCount = 0; //loop forever
243 bool ImageDecoderQt::isSizeAvailable() const
245 if (debugImageDecoderQt)
246 qDebug() << " ImageDecoderQt::isSizeAvailable() returns" << m_sizeAvailable;
247 return m_sizeAvailable;
250 int ImageDecoderQt::frameCount() const
252 if (debugImageDecoderQt)
253 qDebug() << " ImageDecoderQt::frameCount() returns" << m_imageList.size();
254 return m_imageList.size();
258 int ImageDecoderQt::repetitionCount() const
260 if (debugImageDecoderQt)
261 qDebug() << " ImageDecoderQt::repetitionCount() returns" << m_loopCount;
266 bool ImageDecoderQt::supportsAlpha() const
268 return hasFirstImageHeader() && m_imageList[0].m_image.hasAlphaChannel();
271 int ImageDecoderQt::duration(size_t index) const
273 if (index >= m_imageList.size())
275 return m_imageList[index].m_duration;
278 RGBA32Buffer* ImageDecoderQt::frameBufferAtIndex(size_t index)
280 Q_ASSERT("use imageAtIndex instead");
284 QPixmap* ImageDecoderQt::imageAtIndex(size_t index) const
286 if (debugImageDecoderQt)
287 qDebug() << "ImageDecoderQt::imageAtIndex(" << index << ')';
289 if (index >= m_imageList.size())
292 if (!m_pixmapCache.contains(index)) {
293 m_pixmapCache.insert(index,
294 QPixmap::fromImage(m_imageList[index].m_image));
296 return &m_pixmapCache[index];
299 void ImageDecoderQt::clearFrame(size_t index)
301 if (m_imageList.size() < (int)index)
302 m_imageList[index].m_image = QImage();
303 m_pixmapCache.take(index);