source: trunk/src/gui/image/qmovie.cpp@ 781

Last change on this file since 781 was 651, checked in by Dmitry A. Kuminov, 16 years ago

trunk: Merged in qt 4.6.2 sources.

File size: 28.8 KB
Line 
1/****************************************************************************
2**
3** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies).
4** All rights reserved.
5** Contact: Nokia Corporation ([email protected])
6**
7** This file is part of the QtGui module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial Usage
11** Licensees holding valid Qt Commercial licenses may use this file in
12** accordance with the Qt Commercial License Agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and Nokia.
15**
16** GNU Lesser General Public License Usage
17** Alternatively, this file may be used under the terms of the GNU Lesser
18** General Public License version 2.1 as published by the Free Software
19** Foundation and appearing in the file LICENSE.LGPL included in the
20** packaging of this file. Please review the following information to
21** ensure the GNU Lesser General Public License version 2.1 requirements
22** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
23**
24** In addition, as a special exception, Nokia gives you certain additional
25** rights. These rights are described in the Nokia Qt LGPL Exception
26** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
27**
28** GNU General Public License Usage
29** Alternatively, this file may be used under the terms of the GNU
30** General Public License version 3.0 as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL included in the
32** packaging of this file. Please review the following information to
33** ensure the GNU General Public License version 3.0 requirements will be
34** met: http://www.gnu.org/copyleft/gpl.html.
35**
36** If you have questions regarding the use of this file, please contact
37** Nokia at [email protected].
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42/*!
43 \class QMovie
44
45 \brief The QMovie class is a convenience class for playing movies
46 with QImageReader.
47
48 \ingroup painting
49
50 This class is used to show simple animations without sound. If you want
51 to display video and media content, use the \l{Phonon Module}{Phonon}
52 multimedia framework instead.
53
54 First, create a QMovie object by passing either the name of a file or a
55 pointer to a QIODevice containing an animated image format to QMovie's
56 constructor. You can call isValid() to check if the image data is valid,
57 before starting the movie. To start the movie, call start(). QMovie will
58 enter \l Running state, and emit started() and stateChanged(). To get the
59 current state of the movie, call state().
60
61 To display the movie in your application, you can pass your QMovie object
62 to QLabel::setMovie(). Example:
63
64 \snippet doc/src/snippets/code/src_gui_image_qmovie.cpp 0
65
66 Whenever a new frame is available in the movie, QMovie will emit
67 updated(). If the size of the frame changes, resized() is emitted. You can
68 call currentImage() or currentPixmap() to get a copy of the current
69 frame. When the movie is done, QMovie emits finished(). If any error
70 occurs during playback (i.e, the image file is corrupt), QMovie will emit
71 error().
72
73 You can control the speed of the movie playback by calling setSpeed(),
74 which takes the percentage of the original speed as an argument. Pause the
75 movie by calling setPaused(true). QMovie will then enter \l Paused state
76 and emit stateChanged(). If you call setPaused(false), QMovie will reenter
77 \l Running state and start the movie again. To stop the movie, call
78 stop().
79
80 Certain animation formats allow you to set the background color. You can
81 call setBackgroundColor() to set the color, or backgroundColor() to
82 retrieve the current background color.
83
84 currentFrameNumber() returns the sequence number of the current frame. The
85 first frame in the animation has the sequence number 0. frameCount()
86 returns the total number of frames in the animation, if the image format
87 supports this. You can call loopCount() to get the number of times the
88 movie should loop before finishing. nextFrameDelay() returns the number of
89 milliseconds the current frame should be displayed.
90
91 QMovie can be instructed to cache frames of an animation by calling
92 setCacheMode().
93
94 Call supportedFormats() for a list of formats that QMovie supports.
95
96 \sa QLabel, QImageReader, {Movie Example}
97*/
98
99/*! \enum QMovie::MovieState
100
101 This enum describes the different states of QMovie.
102
103 \value NotRunning The movie is not running. This is QMovie's initial
104 state, and the state it enters after stop() has been called or the movie
105 is finished.
106
107 \value Paused The movie is paused, and QMovie stops emitting updated() or
108 resized(). This state is entered after calling pause() or
109 setPaused(true). The current frame number it kept, and the movie will
110 continue with the next frame when unpause() or setPaused(false) is called.
111
112 \value Running The movie is running.
113*/
114
115/*! \enum QMovie::CacheMode
116
117 This enum describes the different cache modes of QMovie.
118
119 \value CacheNone No frames are cached (the default).
120
121 \value CacheAll All frames are cached.
122*/
123
124/*! \fn void QMovie::started()
125
126 This signal is emitted after QMovie::start() has been called, and QMovie
127 has entered QMovie::Running state.
128*/
129
130/*! \fn void QMovie::resized(const QSize &size)
131
132 This signal is emitted when the current frame has been resized to \a
133 size. This effect is sometimes used in animations as an alternative to
134 replacing the frame. You can call currentImage() or currentPixmap() to get a
135 copy of the updated frame.
136*/
137
138/*! \fn void QMovie::updated(const QRect &rect)
139
140 This signal is emitted when the rect \a rect in the current frame has been
141 updated. You can call currentImage() or currentPixmap() to get a copy of the
142 updated frame.
143*/
144
145/*! \fn void QMovie::frameChanged(int frameNumber)
146 \since 4.1
147
148 This signal is emitted when the frame number has changed to
149 \a frameNumber. You can call currentImage() or currentPixmap() to get a
150 copy of the frame.
151*/
152
153/*!
154 \fn void QMovie::stateChanged(QMovie::MovieState state)
155
156 This signal is emitted every time the state of the movie changes. The new
157 state is specified by \a state.
158
159 \sa QMovie::state()
160*/
161
162/*! \fn void QMovie::error(QImageReader::ImageReaderError error)
163
164 This signal is emitted by QMovie when the error \a error occurred during
165 playback. QMovie will stop the movie, and enter QMovie::NotRunning state.
166*/
167
168/*! \fn void QMovie::finished()
169
170 This signal is emitted when the movie has finished.
171
172 \sa QMovie::stop()
173*/
174
175#include "qglobal.h"
176
177#ifndef QT_NO_MOVIE
178
179#include "qmovie.h"
180#include "qimage.h"
181#include "qimagereader.h"
182#include "qpixmap.h"
183#include "qrect.h"
184#include "qdatetime.h"
185#include "qtimer.h"
186#include "qpair.h"
187#include "qmap.h"
188#include "qlist.h"
189#include "qbuffer.h"
190#include "qdir.h"
191#include "private/qobject_p.h"
192
193#define QMOVIE_INVALID_DELAY -1
194
195QT_BEGIN_NAMESPACE
196
197class QFrameInfo
198{
199public:
200 QPixmap pixmap;
201 int delay;
202 bool endMark;
203 inline QFrameInfo(bool endMark)
204 : pixmap(QPixmap()), delay(QMOVIE_INVALID_DELAY), endMark(endMark)
205 { }
206
207 inline QFrameInfo()
208 : pixmap(QPixmap()), delay(QMOVIE_INVALID_DELAY), endMark(false)
209 { }
210
211 inline QFrameInfo(const QPixmap &pixmap, int delay)
212 : pixmap(pixmap), delay(delay), endMark(false)
213 { }
214
215 inline bool isValid()
216 {
217 return endMark || !(pixmap.isNull() && (delay == QMOVIE_INVALID_DELAY));
218 }
219
220 inline bool isEndMarker()
221 { return endMark; }
222
223 static inline QFrameInfo endMarker()
224 { return QFrameInfo(true); }
225};
226
227class QMoviePrivate : public QObjectPrivate
228{
229 Q_DECLARE_PUBLIC(QMovie)
230
231public:
232 QMoviePrivate(QMovie *qq);
233 bool isDone();
234 bool next();
235 int speedAdjustedDelay(int delay) const;
236 bool isValid() const;
237 bool jumpToFrame(int frameNumber);
238 int frameCount() const;
239 bool jumpToNextFrame();
240 QFrameInfo infoForFrame(int frameNumber);
241 void reset();
242
243 inline void enterState(QMovie::MovieState newState) {
244 movieState = newState;
245 emit q_func()->stateChanged(newState);
246 }
247
248 // private slots
249 void _q_loadNextFrame();
250 void _q_loadNextFrame(bool starting);
251
252 QImageReader *reader;
253 int speed;
254 QMovie::MovieState movieState;
255 QRect frameRect;
256 QPixmap currentPixmap;
257 int currentFrameNumber;
258 int nextFrameNumber;
259 int greatestFrameNumber;
260 int nextDelay;
261 int playCounter;
262 qint64 initialDevicePos;
263 QMovie::CacheMode cacheMode;
264 bool haveReadAll;
265 bool isFirstIteration;
266 QMap<int, QFrameInfo> frameMap;
267 QString absoluteFilePath;
268
269 QTimer nextImageTimer;
270};
271
272/*! \internal
273 */
274QMoviePrivate::QMoviePrivate(QMovie *qq)
275 : reader(0), speed(100), movieState(QMovie::NotRunning),
276 currentFrameNumber(-1), nextFrameNumber(0), greatestFrameNumber(-1),
277 nextDelay(0), playCounter(-1),
278 cacheMode(QMovie::CacheNone), haveReadAll(false), isFirstIteration(true)
279{
280 q_ptr = qq;
281 nextImageTimer.setSingleShot(true);
282}
283
284/*! \internal
285 */
286void QMoviePrivate::reset()
287{
288 nextImageTimer.stop();
289 if (reader->device())
290 initialDevicePos = reader->device()->pos();
291 currentFrameNumber = -1;
292 nextFrameNumber = 0;
293 greatestFrameNumber = -1;
294 nextDelay = 0;
295 playCounter = -1;
296 haveReadAll = false;
297 isFirstIteration = true;
298 frameMap.clear();
299}
300
301/*! \internal
302 */
303bool QMoviePrivate::isDone()
304{
305 return (playCounter == 0);
306}
307
308/*!
309 \internal
310
311 Given the original \a delay, this function returns the
312 actual number of milliseconds to delay according to
313 the current speed. E.g. if the speed is 200%, the
314 result will be half of the original delay.
315*/
316int QMoviePrivate::speedAdjustedDelay(int delay) const
317{
318 return int( (qint64(delay) * qint64(100) ) / qint64(speed) );
319}
320
321/*!
322 \internal
323
324 Returns the QFrameInfo for the given \a frameNumber.
325
326 If the frame number is invalid, an invalid QFrameInfo is
327 returned.
328
329 If the end of the animation has been reached, a
330 special end marker QFrameInfo is returned.
331
332*/
333QFrameInfo QMoviePrivate::infoForFrame(int frameNumber)
334{
335 if (frameNumber < 0)
336 return QFrameInfo(); // Invalid
337
338 if (haveReadAll && (frameNumber > greatestFrameNumber)) {
339 if (frameNumber == greatestFrameNumber+1)
340 return QFrameInfo::endMarker();
341 return QFrameInfo(); // Invalid
342 }
343
344 if (cacheMode == QMovie::CacheNone) {
345 if (frameNumber != currentFrameNumber+1) {
346 // Non-sequential frame access
347 if (!reader->jumpToImage(frameNumber)) {
348 if (frameNumber == 0) {
349 // Special case: Attempt to "rewind" so we can loop
350 // ### This could be implemented as QImageReader::rewind()
351 if (reader->device()->isSequential())
352 return QFrameInfo(); // Invalid
353 QString fileName = reader->fileName();
354 QByteArray format = reader->format();
355 QIODevice *device = reader->device();
356 QColor bgColor = reader->backgroundColor();
357 QSize scaledSize = reader->scaledSize();
358 delete reader;
359 if (fileName.isEmpty())
360 reader = new QImageReader(device, format);
361 else
362 reader = new QImageReader(absoluteFilePath, format);
363 (void)reader->canRead(); // Provoke a device->open() call
364 reader->device()->seek(initialDevicePos);
365 reader->setBackgroundColor(bgColor);
366 reader->setScaledSize(scaledSize);
367 } else {
368 return QFrameInfo(); // Invalid
369 }
370 }
371 }
372 if (reader->canRead()) {
373 // reader says we can read. Attempt to actually read image
374 QImage anImage = reader->read();
375 if (anImage.isNull()) {
376 // Reading image failed.
377 return QFrameInfo(); // Invalid
378 }
379 if (frameNumber > greatestFrameNumber)
380 greatestFrameNumber = frameNumber;
381 QPixmap aPixmap = QPixmap::fromImage(anImage);
382 int aDelay = reader->nextImageDelay();
383 return QFrameInfo(aPixmap, aDelay);
384 } else {
385 // We've read all frames now. Return an end marker
386 haveReadAll = true;
387 return QFrameInfo::endMarker();
388 }
389 }
390
391 // CacheMode == CacheAll
392 if (frameNumber > greatestFrameNumber) {
393 // Frame hasn't been read from file yet. Try to do it
394 for (int i = greatestFrameNumber + 1; i <= frameNumber; ++i) {
395 if (reader->canRead()) {
396 // reader says we can read. Attempt to actually read image
397 QImage anImage = reader->read();
398 if (anImage.isNull()) {
399 // Reading image failed.
400 return QFrameInfo(); // Invalid
401 }
402 greatestFrameNumber = i;
403 QPixmap aPixmap = QPixmap::fromImage(anImage);
404 int aDelay = reader->nextImageDelay();
405 QFrameInfo info(aPixmap, aDelay);
406 // Cache it!
407 frameMap.insert(i, info);
408 if (i == frameNumber) {
409 return info;
410 }
411 } else {
412 // We've read all frames now. Return an end marker
413 haveReadAll = true;
414 return QFrameInfo::endMarker();
415 }
416 }
417 }
418 // Return info for requested (cached) frame
419 return frameMap.value(frameNumber);
420}
421
422/*!
423 \internal
424
425 Attempts to advance the animation to the next frame.
426 If successful, currentFrameNumber, currentPixmap and
427 nextDelay are updated accordingly, and true is returned.
428 Otherwise, false is returned.
429 When false is returned, isDone() can be called to
430 determine whether the animation ended gracefully or
431 an error occurred when reading the frame.
432*/
433bool QMoviePrivate::next()
434{
435 QTime time;
436 time.start();
437 QFrameInfo info = infoForFrame(nextFrameNumber);
438 if (!info.isValid())
439 return false;
440 if (info.isEndMarker()) {
441 // We reached the end of the animation.
442 if (isFirstIteration) {
443 if (nextFrameNumber == 0) {
444 // No frames could be read at all (error).
445 return false;
446 }
447 // End of first iteration. Initialize play counter
448 playCounter = reader->loopCount();
449 isFirstIteration = false;
450 }
451 // Loop as appropriate
452 if (playCounter != 0) {
453 if (playCounter != -1) // Infinite?
454 playCounter--; // Nope
455 nextFrameNumber = 0;
456 return next();
457 }
458 // Loop no more. Done
459 return false;
460 }
461 // Image and delay OK, update internal state
462 currentFrameNumber = nextFrameNumber++;
463 QSize scaledSize = reader->scaledSize();
464 if (scaledSize.isValid() && (scaledSize != info.pixmap.size()))
465 currentPixmap = QPixmap::fromImage( info.pixmap.toImage().scaled(scaledSize) );
466 else
467 currentPixmap = info.pixmap;
468 nextDelay = speedAdjustedDelay(info.delay);
469 // Adjust delay according to the time it took to read the frame
470 int processingTime = time.elapsed();
471 if (processingTime > nextDelay)
472 nextDelay = 0;
473 else
474 nextDelay = nextDelay - processingTime;
475 return true;
476}
477
478/*! \internal
479 */
480void QMoviePrivate::_q_loadNextFrame()
481{
482 _q_loadNextFrame(false);
483}
484
485void QMoviePrivate::_q_loadNextFrame(bool starting)
486{
487 Q_Q(QMovie);
488 if (next()) {