1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4/*!
5 \class QMovie
6
7 \inmodule QtGui
8
9 \brief The QMovie class is a convenience class for playing movies
10 with QImageReader.
11
12 This class is used to show simple animations without sound.
13
14 First, create a QMovie object by passing either the name of a file or a
15 pointer to a QIODevice containing an animated image format to QMovie's
16 constructor. You can call isValid() to check if the image data is valid,
17 before starting the movie. To start the movie, call start(). QMovie will
18 enter \l Running state, and emit started() and stateChanged(). To get the
19 current state of the movie, call state().
20
21 To display the movie in your application, you can pass your QMovie object
22 to QLabel::setMovie(). Example:
23
24 \snippet code/src_gui_image_qmovie.cpp 0
25
26 Whenever a new frame is available in the movie, QMovie will emit
27 updated(). If the size of the frame changes, resized() is emitted. You can
28 call currentImage() or currentPixmap() to get a copy of the current
29 frame. When the movie is done, QMovie emits finished(). If any error
30 occurs during playback (i.e, the image file is corrupt), QMovie will emit
31 error().
32
33 You can control the speed of the movie playback by calling setSpeed(),
34 which takes the percentage of the original speed as an argument. Pause the
35 movie by calling setPaused(true). QMovie will then enter \l Paused state
36 and emit stateChanged(). If you call setPaused(false), QMovie will reenter
37 \l Running state and start the movie again. To stop the movie, call
38 stop().
39
40 Certain animation formats allow you to set the background color. You can
41 call setBackgroundColor() to set the color, or backgroundColor() to
42 retrieve the current background color.
43
44 currentFrameNumber() returns the sequence number of the current frame. The
45 first frame in the animation has the sequence number 0. frameCount()
46 returns the total number of frames in the animation, if the image format
47 supports this. You can call loopCount() to get the number of times the
48 movie should loop before finishing. nextFrameDelay() returns the number of
49 milliseconds the current frame should be displayed.
50
51 QMovie can be instructed to cache frames of an animation by calling
52 setCacheMode().
53
54 Call supportedFormats() for a list of formats that QMovie supports.
55
56 \sa QLabel, QImageReader
57*/
58
59/*! \enum QMovie::MovieState
60
61 This enum describes the different states of QMovie.
62
63 \value NotRunning The movie is not running. This is QMovie's initial
64 state, and the state it enters after stop() has been called or the movie
65 is finished.
66
67 \value Paused The movie is paused, and QMovie stops emitting updated() or
68 resized(). This state is entered after calling pause() or
69 setPaused(true). The current frame number it kept, and the movie will
70 continue with the next frame when unpause() or setPaused(false) is called.
71
72 \value Running The movie is running.
73*/
74
75/*! \enum QMovie::CacheMode
76
77 This enum describes the different cache modes of QMovie.
78
79 \value CacheNone No frames are cached (the default).
80
81 \value CacheAll All frames are cached.
82*/
83
84/*! \fn void QMovie::started()
85
86 This signal is emitted after QMovie::start() has been called, and QMovie
87 has entered QMovie::Running state.
88*/
89
90/*! \fn void QMovie::resized(const QSize &size)
91
92 This signal is emitted when the current frame has been resized to \a
93 size. This effect is sometimes used in animations as an alternative to
94 replacing the frame. You can call currentImage() or currentPixmap() to get a
95 copy of the updated frame.
96*/
97
98/*! \fn void QMovie::updated(const QRect &rect)
99
100 This signal is emitted when the rect \a rect in the current frame has been
101 updated. You can call currentImage() or currentPixmap() to get a copy of the
102 updated frame.
103*/
104
105/*! \fn void QMovie::frameChanged(int frameNumber)
106
107 This signal is emitted when the frame number has changed to
108 \a frameNumber. You can call currentImage() or currentPixmap() to get a
109 copy of the frame.
110*/
111
112/*!
113 \fn void QMovie::stateChanged(QMovie::MovieState state)
114
115 This signal is emitted every time the state of the movie changes. The new
116 state is specified by \a state.
117
118 \sa QMovie::state()
119*/
120
121/*! \fn void QMovie::error(QImageReader::ImageReaderError error)
122
123 This signal is emitted by QMovie when the error \a error occurred during
124 playback. QMovie will stop the movie, and enter QMovie::NotRunning state.
125
126 \sa lastError(), lastErrorString()
127*/
128
129/*! \fn void QMovie::finished()
130
131 This signal is emitted when the movie has finished.
132
133 \sa QMovie::stop()
134*/
135
136#include "qmovie.h"
137
138#include "qglobal.h"
139#include "qelapsedtimer.h"
140#include "qimage.h"
141#include "qimagereader.h"
142#include "qpixmap.h"
143#include "qrect.h"
144#include "qelapsedtimer.h"
145#include "qtimer.h"
146#include "qmap.h"
147#include "qlist.h"
148#include "qbuffer.h"
149#include "qdir.h"
150#include "qloggingcategory.h"
151#include "private/qobject_p.h"
152#include "private/qproperty_p.h"
153
154#define QMOVIE_INVALID_DELAY -1
155
156QT_BEGIN_NAMESPACE
157
158Q_DECLARE_LOGGING_CATEGORY(lcImageIo)
159
160class QFrameInfo
161{
162public:
163 QPixmap pixmap;
164 int delay;
165 bool endMark;
166 inline QFrameInfo(bool endMark)
167 : pixmap(QPixmap()), delay(QMOVIE_INVALID_DELAY), endMark(endMark)
168 { }
169
170 inline QFrameInfo()
171 : pixmap(QPixmap()), delay(QMOVIE_INVALID_DELAY), endMark(false)
172 { }
173
174 inline QFrameInfo(QPixmap &&pixmap, int delay)
175 : pixmap(std::move(pixmap)), delay(delay), endMark(false)
176 { }
177
178 inline bool isValid()
179 {
180 return endMark || !(pixmap.isNull() && (delay == QMOVIE_INVALID_DELAY));
181 }
182
183 inline bool isEndMarker()
184 { return endMark; }
185
186 static inline QFrameInfo endMarker()
187 { return QFrameInfo(true); }
188};
189Q_DECLARE_TYPEINFO(QFrameInfo, Q_RELOCATABLE_TYPE);
190
191class QMoviePrivate : public QObjectPrivate
192{
193 Q_DECLARE_PUBLIC(QMovie)
194
195public:
196 QMoviePrivate(QMovie *qq);
197 bool isDone();
198 bool next();
199 int speedAdjustedDelay(int delay) const;
200 bool isValid() const;
201 bool jumpToFrame(int frameNumber);
202 int frameCount() const;
203 bool jumpToNextFrame();
204 QFrameInfo infoForFrame(int frameNumber);
205 void reset();
206
207 inline void enterState(QMovie::MovieState newState) {
208 movieState = newState;
209 emit q_func()->stateChanged(state: newState);
210 }
211
212 // private slots
213 void _q_loadNextFrame();
214 void _q_loadNextFrame(bool starting);
215
216 QImageReader *reader = nullptr;
217
218 void setSpeed(int percentSpeed) { q_func()->setSpeed(percentSpeed); }
219 Q_OBJECT_COMPAT_PROPERTY_WITH_ARGS(QMoviePrivate, int, speed, &QMoviePrivate::setSpeed, 100)
220
221 QMovie::MovieState movieState = QMovie::NotRunning;
222 QRect frameRect;
223 QPixmap currentPixmap;
224 int currentFrameNumber = -1;
225 int nextFrameNumber = 0;
226 int greatestFrameNumber = -1;
227 int nextDelay = 0;
228 int playCounter = -1;
229 qint64 initialDevicePos = 0;
230 Q_OBJECT_BINDABLE_PROPERTY_WITH_ARGS(QMoviePrivate, QMovie::CacheMode, cacheMode,
231 QMovie::CacheNone)
232 bool haveReadAll = false;
233 bool isFirstIteration = true;
234 QMap<int, QFrameInfo> frameMap;
235 QString absoluteFilePath;
236
237 QTimer nextImageTimer;
238};
239
240/*! \internal
241 */
242QMoviePrivate::QMoviePrivate(QMovie *qq)
243{
244 q_ptr = qq;
245 nextImageTimer.setSingleShot(true);
246}
247
248/*! \internal
249 */
250void QMoviePrivate::reset()
251{
252 nextImageTimer.stop();
253 if (reader->device())
254 initialDevicePos = reader->device()->pos();
255 currentFrameNumber = -1;
256 nextFrameNumber = 0;
257 greatestFrameNumber = -1;
258 nextDelay = 0;
259 playCounter = -1;
260 haveReadAll = false;
261 isFirstIteration = true;
262 frameMap.clear();
263}
264
265/*! \internal
266 */
267bool QMoviePrivate::isDone()
268{
269 return (playCounter == 0);
270}
271
272/*!
273 \internal
274
275 Given the original \a delay, this function returns the
276 actual number of milliseconds to delay according to
277 the current speed. E.g. if the speed is 200%, the
278 result will be half of the original delay.
279*/
280int QMoviePrivate::speedAdjustedDelay(int delay) const
281{
282 return int( (qint64(delay) * qint64(100) ) / qint64(speed) );
283}
284
285/*!
286 \internal
287
288 Returns the QFrameInfo for the given \a frameNumber.
289
290 If the frame number is invalid, an invalid QFrameInfo is
291 returned.
292
293 If the end of the animation has been reached, a
294 special end marker QFrameInfo is returned.
295
296*/
297QFrameInfo QMoviePrivate::infoForFrame(int frameNumber)
298{
299 Q_Q(QMovie);
300
301 if (frameNumber < 0)
302 return QFrameInfo(); // Invalid
303
304 if (haveReadAll && (frameNumber > greatestFrameNumber)) {
305 if (frameNumber == greatestFrameNumber+1)
306 return QFrameInfo::endMarker();
307 return QFrameInfo(); // Invalid
308 }
309
310 // For an animated image format, the tradition is that QMovie calls read()
311 // until canRead() == false, because the number of frames may not be known
312 // in advance; but if we're abusing a multi-frame format as an animation,
313 // canRead() may remain true, and we need to stop after reading the maximum
314 // number of frames that the image provides.
315 const bool supportsAnimation = reader->supportsOption(option: QImageIOHandler::Animation);
316 const int stopAtFrame = supportsAnimation ? -1 : frameCount();
317
318 // For an animated image format, QImageIOHandler::nextImageDelay() should
319 // provide the time to wait until showing the next frame; but multi-frame
320 // formats are not expected to provide this value, so use 1000 ms by default.
321 const auto nextFrameDelay = [&]() { return supportsAnimation ? reader->nextImageDelay() : 1000; };
322
323 if (cacheMode == QMovie::CacheNone) {
324 if (frameNumber != currentFrameNumber+1) {
325 // Non-sequential frame access
326 if (!reader->jumpToImage(imageNumber: frameNumber)) {
327 if (frameNumber == 0) {
328 // Special case: Attempt to "rewind" so we can loop
329 // ### This could be implemented as QImageReader::rewind()
330 if (reader->device()->isSequential())
331 return QFrameInfo(); // Invalid
332 QString fileName = reader->fileName();
333 QByteArray format = reader->format();
334 QIODevice *device = reader->device();
335 QColor bgColor = reader->backgroundColor();
336 QSize scaledSize = reader->scaledSize();
337 delete reader;
338 if (fileName.isEmpty())
339 reader = new QImageReader(device, format);
340 else
341 reader = new QImageReader(absoluteFilePath, format);
342 if (!reader->canRead()) // Provoke a device->open() call
343 emit q->error(error: reader->error());
344 reader->device()->seek(pos: initialDevicePos);
345 reader->setBackgroundColor(bgColor);
346 reader->setScaledSize(scaledSize);
347 } else {
348 return QFrameInfo(); // Invalid
349 }
350 }
351 }
352 qCDebug(lcImageIo, "CacheNone: read frame %d of %d", frameNumber, stopAtFrame);
353 if (stopAtFrame > 0 ? (frameNumber < stopAtFrame) : reader->canRead()) {
354 // reader says we can read. Attempt to actually read image
355 // But if it's a non-animated multi-frame format and we know the frame count, stop there.
356 if (stopAtFrame > 0)
357 reader->jumpToImage(imageNumber: frameNumber);
358 QImage anImage = reader->read();
359 if (anImage.isNull()) {
360 // Reading image failed.
361 return QFrameInfo(); // Invalid
362 }
363 if (frameNumber > greatestFrameNumber)
364 greatestFrameNumber = frameNumber;
365 return QFrameInfo(QPixmap::fromImage(image: std::move(anImage)), nextFrameDelay());
366 } else if (frameNumber != 0) {
367 // We've read all frames now. Return an end marker
368 haveReadAll = true;
369 return QFrameInfo::endMarker();
370 } else {
371 // No readable frames
372 haveReadAll = true;
373 return QFrameInfo();
374 }
375 }
376
377 // CacheMode == CacheAll
378 if (frameNumber > greatestFrameNumber) {
379 // Frame hasn't been read from file yet. Try to do it
380 for (int i = greatestFrameNumber + 1; i <= frameNumber; ++i) {
381 qCDebug(lcImageIo, "CacheAll: read frame %d of %d", frameNumber, stopAtFrame);
382 if (stopAtFrame > 0 ? (frameNumber < stopAtFrame) : reader->canRead()) {
383 // reader says we can read. Attempt to actually read image
384 // But if it's a non-animated multi-frame format and we know the frame count, stop there.
385 if (stopAtFrame > 0)
386 reader->jumpToImage(imageNumber: frameNumber);
387 QImage anImage = reader->read();
388 if (anImage.isNull()) {
389 // Reading image failed.
390 return QFrameInfo(); // Invalid
391 }
392 greatestFrameNumber = i;
393 QFrameInfo info(QPixmap::fromImage(image: std::move(anImage)), nextFrameDelay());
394 // Cache it!
395 frameMap.insert(key: i, value: info);
396 if (i == frameNumber) {
397 return info;
398 }
399 } else {
400 // We've read all frames now. Return an end marker
401 haveReadAll = true;
402 return frameNumber == greatestFrameNumber + 1 ? QFrameInfo::endMarker() : QFrameInfo();
403 }
404 }
405 }
406 // Return info for requested (cached) frame
407 return frameMap.value(key: frameNumber);
408}
409
410/*!
411 \internal
412
413 Attempts to advance the animation to the next frame.
414 If successful, currentFrameNumber, currentPixmap and
415 nextDelay are updated accordingly, and true is returned.
416 Otherwise, false is returned.
417 When false is returned, isDone() can be called to
418 determine whether the animation ended gracefully or
419 an error occurred when reading the frame.
420*/
421bool QMoviePrivate::next()
422{
423 QElapsedTimer time;
424 time.start();
425 QFrameInfo info = infoForFrame(frameNumber: nextFrameNumber);
426 if (!info.isValid())
427 return false;
428 if (info.isEndMarker()) {
429 // We reached the end of the animation.
430 if (isFirstIteration) {
431 if (nextFrameNumber == 0) {
432 // No frames could be read at all (error).
433 return false;
434 }
435 // End of first iteration. Initialize play counter
436 playCounter = reader->loopCount();
437 isFirstIteration = false;
438 }
439 // Loop as appropriate
440 if (playCounter != 0) {
441 if (playCounter != -1) // Infinite?
442 playCounter--; // Nope
443 nextFrameNumber = 0;
444 return next();
445 }
446 // Loop no more. Done
447 return false;
448 }
449 // Image and delay OK, update internal state
450 currentFrameNumber = nextFrameNumber++;
451 currentPixmap = info.pixmap;
452
453 if (!speed)
454 return true;
455
456 nextDelay = speedAdjustedDelay(delay: info.delay);
457 // Adjust delay according to the time it took to read the frame
458 int processingTime = time.elapsed();
459 if (processingTime > nextDelay)
460 nextDelay = 0;
461 else
462 nextDelay = nextDelay - processingTime;
463 return true;
464}
465
466/*! \internal
467 */
468void QMoviePrivate::_q_loadNextFrame()
469{
470 _q_loadNextFrame(starting: false);
471}
472
473void QMoviePrivate::_q_loadNextFrame(bool starting)
474{
475 Q_Q(QMovie);
476 if (next()) {
477 if (starting && movieState == QMovie::NotRunning) {
478 enterState(newState: QMovie::Running);
479 emit q->started();
480 }
481
482 if (frameRect.size() != currentPixmap.rect().size()) {
483 frameRect = currentPixmap.rect();
484 emit q->resized(size: frameRect.size());
485 }
486
487 emit q->updated(rect: frameRect);
488 emit q->frameChanged(frameNumber: currentFrameNumber);
489
490 if (speed && movieState == QMovie::Running)
491 nextImageTimer.start(msec: nextDelay);
492 } else {
493 // Could not read another frame
494 if (!isDone()) {
495 emit q->error(error: reader->error());
496 }
497
498 // Graceful finish
499 if (movieState != QMovie::Paused) {
500 nextFrameNumber = 0;
501 isFirstIteration = true;
502 playCounter = -1;
503 enterState(newState: QMovie::NotRunning);
504 emit q->finished();
505 }
506 }
507}
508
509/*!
510 \internal
511*/
512bool QMoviePrivate::isValid() const
513{
514 Q_Q(const QMovie);
515
516 if (greatestFrameNumber >= 0)
517 return true; // have we seen valid data
518 bool canRead = reader->canRead();
519 if (!canRead) {
520 // let the consumer know it's broken
521 //
522 // ### the const_cast here is ugly, but 'const' of this method is
523 // technically wrong right now, since it may cause the underlying device
524 // to open.
525 emit const_cast<QMovie*>(q)->error(error: reader->error());
526 }
527 return canRead;
528}
529
530/*!
531 \internal
532*/
533bool QMoviePrivate::jumpToFrame(int frameNumber)
534{
535 if (frameNumber < 0)
536 return false;
537 if (currentFrameNumber == frameNumber)
538 return true;
539 nextFrameNumber = frameNumber;
540 if (movieState == QMovie::Running)
541 nextImageTimer.stop();
542 _q_loadNextFrame();
543 return (nextFrameNumber == currentFrameNumber+1);
544}
545
546/*!
547 \internal
548*/
549int QMoviePrivate::frameCount() const
550{
551 int result;
552 if ((result = reader->imageCount()) != 0)
553 return result;
554 if (haveReadAll)
555 return greatestFrameNumber+1;
556 return 0; // Don't know
557}
558
559/*!
560 \internal
561*/
562bool QMoviePrivate::jumpToNextFrame()
563{
564 return jumpToFrame(frameNumber: currentFrameNumber+1);
565}
566
567/*!
568 Constructs a QMovie object, passing the \a parent object to QObject's
569 constructor.
570
571 \sa setFileName(), setDevice(), setFormat()
572 */
573QMovie::QMovie(QObject *parent)
574 : QObject(*new QMoviePrivate(this), parent)
575{
576 Q_D(QMovie);
577 d->reader = new QImageReader;
578 connect(sender: &d->nextImageTimer, SIGNAL(timeout()), receiver: this, SLOT(_q_loadNextFrame()));
579}
580
581/*!
582 Constructs a QMovie object. QMovie will use read image data from \a
583 device, which it assumes is open and readable. If \a format is not empty,
584 QMovie will use the image format \a format for decoding the image
585 data. Otherwise, QMovie will attempt to guess the format.
586
587 The \a parent object is passed to QObject's constructor.
588 */
589QMovie::QMovie(QIODevice *device, const QByteArray &format, QObject *parent)
590 : QObject(*new QMoviePrivate(this), parent)
591{
592 Q_D(QMovie);
593 d->reader = new QImageReader(device, format);
594 d->initialDevicePos = device->pos();
595 connect(sender: &d->nextImageTimer, SIGNAL(timeout()), receiver: this, SLOT(_q_loadNextFrame()));
596}
597
598/*!
599 Constructs a QMovie object. QMovie will use read image data from \a
600 fileName. If \a format is not empty, QMovie will use the image format \a
601 format for decoding the image data. Otherwise, QMovie will attempt to
602 guess the format.
603
604 The \a parent object is passed to QObject's constructor.
605 */
606QMovie::QMovie(const QString &fileName, const QByteArray &format, QObject *parent)
607 : QObject(*new QMoviePrivate(this), parent)
608{
609 Q_D(QMovie);
610 d->absoluteFilePath = QDir(fileName).absolutePath();
611 d->reader = new QImageReader(fileName, format);
612 if (d->reader->device())
613 d->initialDevicePos = d->reader->device()->pos();
614 connect(sender: &d->nextImageTimer, SIGNAL(timeout()), receiver: this, SLOT(_q_loadNextFrame()));
615}
616
617/*!
618 Destructs the QMovie object.
619*/
620QMovie::~QMovie()
621{
622 Q_D(QMovie);
623 delete d->reader;
624}
625
626/*!
627 Sets the current device to \a device. QMovie will read image data from
628 this device when the movie is running.
629
630 \sa device(), setFormat()
631*/
632void QMovie::setDevice(QIODevice *device)
633{
634 Q_D(QMovie);
635 d->reader->setDevice(device);
636 d->reset();
637}
638
639/*!
640 Returns the device QMovie reads image data from. If no device has
641 currently been assigned, \nullptr is returned.
642
643 \sa setDevice(), fileName()
644*/
645QIODevice *QMovie::device() const
646{
647 Q_D(const QMovie);
648 return d->reader->device();
649}
650
651/*!
652 Sets the name of the file that QMovie reads image data from, to \a
653 fileName.
654
655 \sa fileName(), setDevice(), setFormat()
656*/
657void QMovie::setFileName(const QString &fileName)
658{
659 Q_D(QMovie);
660 d->absoluteFilePath = QDir(fileName).absolutePath();
661 d->reader->setFileName(fileName);
662 d->reset();
663}
664
665/*!
666 Returns the name of the file that QMovie reads image data from. If no file
667 name has been assigned, or if the assigned device is not a file, an empty
668 QString is returned.
669
670 \sa setFileName(), device()
671*/
672QString QMovie::fileName() const
673{
674 Q_D(const QMovie);
675 return d->reader->fileName();
676}
677
678/*!
679 Sets the format that QMovie will use when decoding image data, to \a
680 format. By default, QMovie will attempt to guess the format of the image
681 data.
682
683 You can call supportedFormats() for the full list of formats
684 QMovie supports.
685
686 \sa QImageReader::supportedImageFormats()
687*/
688void QMovie::setFormat(const QByteArray &format)
689{
690 Q_D(QMovie);
691 d->reader->setFormat(format);
692}
693
694/*!
695 Returns the format that QMovie uses when decoding image data. If no format
696 has been assigned, an empty QByteArray() is returned.
697
698 \sa setFormat()
699*/
700QByteArray QMovie::format() const
701{
702 Q_D(const QMovie);
703 return d->reader->format();
704}
705
706/*!
707 For image formats that support it, this function sets the background color
708 to \a color.
709
710 \sa backgroundColor()
711*/
712void QMovie::setBackgroundColor(const QColor &color)
713{
714 Q_D(QMovie);
715 d->reader->setBackgroundColor(color);
716}
717
718/*!
719 Returns the background color of the movie. If no background color has been
720 assigned, an invalid QColor is returned.
721
722 \sa setBackgroundColor()
723*/
724QColor QMovie::backgroundColor() const
725{
726 Q_D(const QMovie);
727 return d->reader->backgroundColor();
728}
729
730/*!
731 Returns the current state of QMovie.
732
733 \sa MovieState, stateChanged()
734*/
735QMovie::MovieState QMovie::state() const
736{
737 Q_D(const QMovie);
738 return d->movieState;
739}
740
741/*!
742 Returns the rect of the last frame. If no frame has yet been updated, an
743 invalid QRect is returned.
744
745 \sa currentImage(), currentPixmap()
746*/
747QRect QMovie::frameRect() const
748{
749 Q_D(const QMovie);
750 return d->frameRect;
751}
752
753/*!
754 Returns the current frame as a QPixmap.
755
756 \sa currentImage(), updated()
757*/
758QPixmap QMovie::currentPixmap() const
759{
760 Q_D(const QMovie);
761 return d->currentPixmap;
762}
763
764/*!
765 Returns the current frame as a QImage.
766
767 \sa currentPixmap(), updated()
768*/
769QImage QMovie::currentImage() const
770{
771 Q_D(const QMovie);
772 return d->currentPixmap.toImage();
773}
774
775/*!
776 Returns \c true if the movie is valid (e.g., the image data is readable and
777 the image format is supported); otherwise returns \c false.
778
779 For information about why the movie is not valid, see lastError().
780*/
781bool QMovie::isValid() const
782{
783 Q_D(const QMovie);
784 return d->isValid();
785}
786
787/*!
788 Returns the most recent error that occurred while attempting to read image data.
789
790 \sa lastErrorString()
791*/
792QImageReader::ImageReaderError QMovie::lastError() const
793{
794 Q_D(const QMovie);
795 return d->reader->error();
796}
797
798/*!
799 Returns a human-readable representation of the most recent error that occurred
800 while attempting to read image data.
801
802 \sa lastError()
803*/
804QString QMovie::lastErrorString() const
805{
806 Q_D(const QMovie);
807 return d->reader->errorString();
808}
809
810/*!
811 Returns the number of frames in the movie.
812
813 Certain animation formats do not support this feature, in which
814 case 0 is returned.
815*/
816int QMovie::frameCount() const
817{
818 Q_D(const QMovie);
819 return d->frameCount();
820}
821
822/*!
823 Returns the number of milliseconds QMovie will wait before updating the
824 next frame in the animation.
825*/
826int QMovie::nextFrameDelay() const
827{
828 Q_D(const QMovie);
829 return d->nextDelay;
830}
831
832/*!
833 Returns the sequence number of the current frame. The number of the first
834 frame in the movie is 0.
835*/
836int QMovie::currentFrameNumber() const
837{
838 Q_D(const QMovie);
839 return d->currentFrameNumber;
840}
841
842/*!
843 Jumps to the next frame. Returns \c true on success; otherwise returns \c false.
844*/
845bool QMovie::jumpToNextFrame()
846{
847 Q_D(QMovie);
848 return d->jumpToNextFrame();
849}
850
851/*!
852 Jumps to frame number \a frameNumber. Returns \c true on success; otherwise
853 returns \c false.
854*/
855bool QMovie::jumpToFrame(int frameNumber)
856{
857 Q_D(QMovie);
858 return d->jumpToFrame(frameNumber);
859}
860
861/*!
862 Returns the number of times the movie will loop before it finishes.
863 If the movie will only play once (no looping), loopCount returns 0.
864 If the movie loops forever, loopCount returns -1.
865
866 Note that, if the image data comes from a sequential device (e.g. a
867 socket), QMovie can only loop the movie if the cacheMode is set to
868 QMovie::CacheAll.
869*/
870int QMovie::loopCount() const
871{
872 Q_D(const QMovie);
873 return d->reader->loopCount();
874}
875
876/*!
877 If \a paused is true, QMovie will enter \l Paused state and emit
878 stateChanged(Paused); otherwise it will enter \l Running state and emit
879 stateChanged(Running).
880
881 \sa state()
882*/
883void QMovie::setPaused(bool paused)
884{
885 Q_D(QMovie);
886 if (paused) {
887 if (d->movieState == NotRunning)
888 return;
889 d->enterState(newState: Paused);
890 d->nextImageTimer.stop();
891 } else {
892 if (d->movieState == Running)
893 return;
894 d->enterState(newState: Running);
895 d->nextImageTimer.start(msec: nextFrameDelay());
896 }
897}
898
899/*!
900 \property QMovie::speed
901 \brief the movie's speed
902
903 The speed is measured in percentage of the original movie speed.
904 The default speed is 100%.
905 Example:
906
907 \snippet code/src_gui_image_qmovie.cpp 1
908*/
909void QMovie::setSpeed(int percentSpeed)
910{
911 Q_D(QMovie);
912 if (!d->speed && d->movieState == Running)
913 d->nextImageTimer.start(msec: nextFrameDelay());
914 if (percentSpeed != d->speed) {
915 d->speed = percentSpeed;
916 d->speed.notify();
917 } else {
918 d->speed.removeBindingUnlessInWrapper();
919 }
920}
921
922int QMovie::speed() const
923{
924 Q_D(const QMovie);
925 return d->speed;
926}
927
928QBindable<int> QMovie::bindableSpeed()
929{
930 Q_D(QMovie);
931 return &d->speed;
932}
933
934/*!
935 Starts the movie. QMovie will enter \l Running state, and start emitting
936 updated() and resized() as the movie progresses.
937
938 If QMovie is in the \l Paused state, this function is equivalent
939 to calling setPaused(false). If QMovie is already in the \l
940 Running state, this function does nothing.
941
942 \sa stop(), setPaused()
943*/
944void QMovie::start()
945{
946 Q_D(QMovie);
947 if (d->movieState == NotRunning) {
948 d->_q_loadNextFrame(starting: true);
949 } else if (d->movieState == Paused) {
950 setPaused(false);
951 }
952}
953
954/*!
955 Stops the movie. QMovie enters \l NotRunning state, and stops emitting
956 updated() and resized(). If start() is called again, the movie will
957 restart from the beginning.
958
959 If QMovie is already in the \l NotRunning state, this function
960 does nothing.
961
962 \sa start(), setPaused()
963*/
964void QMovie::stop()
965{
966 Q_D(QMovie);
967 if (d->movieState == NotRunning)
968 return;
969 d->enterState(newState: NotRunning);
970 d->nextImageTimer.stop();
971 d->nextFrameNumber = 0;
972}
973
974/*!
975 Returns the scaled size of frames.
976
977 \sa QImageReader::scaledSize()
978*/
979QSize QMovie::scaledSize()
980{
981 Q_D(QMovie);
982 return d->reader->scaledSize();
983}
984
985/*!
986 Sets the scaled frame size to \a size.
987
988 \sa QImageReader::setScaledSize()
989*/
990void QMovie::setScaledSize(const QSize &size)
991{
992 Q_D(QMovie);
993 d->reader->setScaledSize(size);
994}
995
996/*!
997 Returns the list of image formats supported by QMovie.
998
999 \sa QImageReader::supportedImageFormats()
1000*/
1001QList<QByteArray> QMovie::supportedFormats()
1002{
1003 QList<QByteArray> list = QImageReader::supportedImageFormats();
1004
1005 QBuffer buffer;
1006 buffer.open(openMode: QIODevice::ReadOnly);
1007
1008 const auto doesntSupportAnimation =
1009 [&buffer](const QByteArray &format) {
1010 return !QImageReader(&buffer, format).supportsOption(option: QImageIOHandler::Animation);
1011 };
1012
1013 list.removeIf(pred: doesntSupportAnimation);
1014 return list;
1015}
1016
1017/*!
1018 \property QMovie::cacheMode
1019 \brief the movie's cache mode
1020
1021 Caching frames can be useful when the underlying animation format handler
1022 that QMovie relies on to decode the animation data does not support
1023 jumping to particular frames in the animation, or even "rewinding" the
1024 animation to the beginning (for looping). Furthermore, if the image data
1025 comes from a sequential device, it is not possible for the underlying
1026 animation handler to seek back to frames whose data has already been read
1027 (making looping altogether impossible).
1028
1029 To aid in such situations, a QMovie object can be instructed to cache the
1030 frames, at the added memory cost of keeping the frames in memory for the
1031 lifetime of the object.
1032
1033 By default, this property is set to \l CacheNone.
1034
1035 \sa QMovie::CacheMode
1036*/
1037
1038QMovie::CacheMode QMovie::cacheMode() const
1039{
1040 Q_D(const QMovie);
1041 return d->cacheMode;
1042}
1043
1044void QMovie::setCacheMode(CacheMode cacheMode)
1045{
1046 Q_D(QMovie);
1047 d->cacheMode = cacheMode;
1048}
1049
1050QBindable<QMovie::CacheMode> QMovie::bindableCacheMode()
1051{
1052 Q_D(QMovie);
1053 return &d->cacheMode;
1054}
1055
1056QT_END_NAMESPACE
1057
1058#include "moc_qmovie.cpp"
1059

Provided by KDAB

Privacy Policy
Start learning QML with our Intro Training
Find out more

source code of qtbase/src/gui/image/qmovie.cpp