Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickanimatedimage.cpp
Go to the documentation of this file.
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// Qt-Security score:significant reason:default
4
7
8#include <QtGui/qguiapplication.h>
9#include <QtQml/qqmlinfo.h>
10#include <QtQml/qqmlfile.h>
11#include <QtQml/qqmlengine.h>
12#include <QtGui/qmovie.h>
13#if QT_CONFIG(qml_network)
14#include <QtNetwork/qnetworkrequest.h>
15#include <QtNetwork/qnetworkreply.h>
16#endif
17
19
20QQuickPixmap* QQuickAnimatedImagePrivate::infoForCurrentFrame(QQmlEngine *engine)
21{
22 if (!movie)
23 return nullptr;
24
25 int current = movie->currentFrameNumber();
26 if (!frameMap.contains(current)) {
27 QUrl requestedUrl;
28 QQuickPixmap *pixmap = nullptr;
29 if (engine && !movie->fileName().isEmpty()) {
30 requestedUrl.setUrl(QString::fromUtf8("quickanimatedimage://%1#%2x%3#%4")
31 .arg(movie->fileName())
32 .arg(movie->scaledSize().width())
33 .arg(movie->scaledSize().height())
34 .arg(current));
35 }
36 if (!requestedUrl.isEmpty()) {
37 if (QQuickPixmap::isCached(requestedUrl, QRect(), QSize(), 0, QQuickImageProviderOptions()))
38 pixmap = new QQuickPixmap(engine, requestedUrl);
39 else
40 pixmap = new QQuickPixmap(requestedUrl, movie->currentImage());
41 } else {
42 pixmap = new QQuickPixmap;
43 pixmap->setImage(movie->currentImage());
44 }
45 frameMap.insert(current, pixmap);
46 }
47
48 return frameMap.value(current);
49}
50
52{
53 qDeleteAll(frameMap);
54 frameMap.clear();
55}
56
58{
59 Q_Q(QQuickAnimatedImage);
60 const int targetFrame = (finishBehavior == QQuickAnimatedImage::FinishAtInitialFrame)
61 ? 0
62 : qMax(movie->frameCount() - 1, 0);
63 lastFrameNumber = targetFrame;
64 movie->stop();
65 movie->jumpToFrame(targetFrame);
66 // playing = false will be set via playingStatusChanged()
67 emit q->finished();
68}
69
70/*!
71 \qmltype AnimatedImage
72 \nativetype QQuickAnimatedImage
73 \inqmlmodule QtQuick
74 \inherits Image
75 \brief Plays animations stored as a series of images.
76 \ingroup qtquick-visual
77
78 The AnimatedImage type extends the features of the \l Image type, providing
79 a way to play animations stored as images containing a series of frames,
80 such as those stored in GIF files.
81
82 Information about the current frame and total length of the animation can be
83 obtained using the \l currentFrame and \l frameCount properties. You can
84 start, pause and stop the animation by changing the values of the \l playing
85 and \l paused properties.
86
87 The full list of supported formats can be determined with QMovie::supportedFormats().
88
89 The number of times the animation plays can be controlled using the
90 \l loops property. By default, the animation loops indefinitely.
91 The \l finished signal is emitted when the animation finishes playing
92 the specified number of loops.
93
94 \section1 Example Usage
95
96 \beginfloatleft
97 \image animatedimageitem.gif {Game controller bouncing animation
98 with playback frame indicator underneath}
99 \endfloat
100
101 The following QML shows how to display an animated image and obtain information
102 about its state, such as the current frame and total number of frames.
103 The result is an animated image with a simple progress indicator underneath it.
104
105 \b Note: When animated images are cached, every frame of the animation will be cached.
106
107 Set cache to false if you are playing a long or large animation and you
108 want to conserve memory.
109
110 If the image data comes from a sequential device (e.g. a socket),
111 AnimatedImage can only loop if cache is set to true.
112
113 \clearfloat
114 \snippet qml/animatedimage.qml document
115
116 \sa BorderImage, Image
117*/
118
119/*!
120 \qmlproperty url QtQuick::AnimatedImage::source
121
122 This property holds the URL that refers to the source image.
123
124 AnimatedImage can handle any image format supported by Qt, loaded
125 from any URL scheme supported by Qt. It is however not compatible
126 with QQuickImageProvider.
127*/
128
129/*!
130 \qmlproperty size QtQuick::AnimatedImage::sourceSize
131
132 This property holds the scaled width and height of the full-frame image.
133
134 Unlike the \l {Item::}{width} and \l {Item::}{height} properties, which scale
135 the painting of the image, this property sets the maximum number of pixels
136 stored for cached frames so that large animations do not use more
137 memory than necessary.
138
139 If the original size is larger than \c sourceSize, the image is scaled down.
140
141 The natural size of the image can be restored by setting this property to
142 \c undefined.
143
144 \note \e {Changing this property dynamically causes the image source to be reloaded,
145 potentially even from the network, if it is not in the disk cache.}
146
147 \sa Image::sourceSize
148*/
149QQuickAnimatedImage::QQuickAnimatedImage(QQuickItem *parent)
150 : QQuickImage(*(new QQuickAnimatedImagePrivate), parent)
151{
152 connect(this, &QQuickImageBase::cacheChanged, this, &QQuickAnimatedImage::onCacheChanged);
153 connect(this, &QQuickImageBase::currentFrameChanged, this, &QQuickAnimatedImage::frameChanged);
154 connect(this, &QQuickImageBase::currentFrameChanged, this, &QQuickAnimatedImage::currentFrameChanged);
155 connect(this, &QQuickImageBase::frameCountChanged, this, &QQuickAnimatedImage::frameCountChanged);
156}
157
158QQuickAnimatedImage::~QQuickAnimatedImage()
159{
160 Q_D(QQuickAnimatedImage);
161#if QT_CONFIG(qml_network)
162 if (d->reply)
163 d->reply->deleteLater();
164#endif
165 delete d->movie;
166 d->clearCache();
167}
168
169/*!
170 \qmlproperty bool QtQuick::AnimatedImage::paused
171 This property holds whether the animated image is paused.
172
173 By default, this property is false. Set it to true when you want to pause
174 the animation.
175*/
176
177bool QQuickAnimatedImage::isPaused() const
178{
179 Q_D(const QQuickAnimatedImage);
180 if (!d->movie)
181 return d->paused;
182 return d->movie->state()==QMovie::Paused;
183}
184
185void QQuickAnimatedImage::setPaused(bool pause)
186{
187 Q_D(QQuickAnimatedImage);
188 if (pause == d->paused)
189 return;
190 if (!d->movie) {
191 d->paused = pause;
192 emit pausedChanged();
193 } else {
194 d->movie->setPaused(pause);
195 }
196}
197
198/*!
199 \qmlproperty bool QtQuick::AnimatedImage::playing
200 This property holds whether the animated image is playing.
201
202 By default, this property is true, meaning that the animation
203 will start playing immediately.
204
205 \b Note: this property is affected by changes to the actual playing
206 state of AnimatedImage. If non-animated images are used, \a playing
207 will need to be manually set to \a true in order to animate
208 following images.
209 \qml
210 AnimatedImage {
211 onStatusChanged: playing = (status == AnimatedImage.Ready)
212 }
213 \endqml
214*/
215
216bool QQuickAnimatedImage::isPlaying() const
217{
218 Q_D(const QQuickAnimatedImage);
219 if (!d->movie)
220 return d->playing;
221 return d->movie->state()!=QMovie::NotRunning;
222}
223
224void QQuickAnimatedImage::setPlaying(bool play)
225{
226 Q_D(QQuickAnimatedImage);
227 if (play == d->playing)
228 return;
229 if (!d->movie) {
230 d->playing = play;
231 emit playingChanged();
232 return;
233 }
234 if (play) {
235 if (d->loops == 0)
236 return;
237 d->currentLoop = 0;
238 d->lastFrameNumber = 0;
239 d->movie->start();
240 } else {
241 d->movie->stop();
242 }
243}
244
245/*!
246 \qmlproperty int QtQuick::AnimatedImage::currentFrame
247 \qmlproperty int QtQuick::AnimatedImage::frameCount
248
249 currentFrame is the frame that is currently visible. By monitoring this property
250 for changes, you can animate other items at the same time as the image.
251
252 frameCount is the number of frames in the animation. For some animation formats,
253 frameCount is unknown and has a value of zero.
254*/
255int QQuickAnimatedImage::currentFrame() const
256{
257 Q_D(const QQuickAnimatedImage);
258 if (!d->movie)
259 return d->presetCurrentFrame;
260 return d->movie->currentFrameNumber();
261}
262
263void QQuickAnimatedImage::setCurrentFrame(int frame)
264{
265 Q_D(QQuickAnimatedImage);
266 if (!d->movie) {
267 d->presetCurrentFrame = frame;
268 return;
269 }
270 // Update lastFrameNumber before jumpToFrame so that the synchronous
271 // frameChanged signal from QMovie does not trigger false wrap-around
272 // detection in movieUpdate()
273 d->lastFrameNumber = frame;
274 d->movie->jumpToFrame(frame);
275}
276
277int QQuickAnimatedImage::frameCount() const
278{
279 Q_D(const QQuickAnimatedImage);
280 if (!d->movie)
281 return 0;
282 return d->movie->frameCount();
283}
284
285/*!
286 \qmlproperty real QtQuick::AnimatedImage::speed
287 \since QtQuick 2.11
288
289 This property holds the speed of the animation.
290
291 The speed is measured in percentage of the original animated image speed.
292 The default speed is 1.0 (original speed).
293*/
294qreal QQuickAnimatedImage::speed() const
295{
296 Q_D(const QQuickAnimatedImage);
297 return d->speed;
298}
299
300void QQuickAnimatedImage::setSpeed(qreal speed)
301{
302 Q_D(QQuickAnimatedImage);
303 if (d->speed != speed) {
304 d->speed = speed;
305 if (d->movie)
306 d->movie->setSpeed(qRound(speed * 100.0));
307 emit speedChanged();
308 }
309}
310
311/*!
312 \qmlproperty int QtQuick::AnimatedImage::loops
313 \since 6.12
314
315 This property holds the number of times the animation will play.
316
317 After playing the animation this many times, the animation will
318 automatically stop and the \l finished signal will be emitted.
319
320 If this is set to \c AnimatedImage.Infinite (the default), the
321 animation will not stop playing on its own.
322
323 Setting \c loops to \c 0 means the animation will not play.
324
325 Negative values are invalid.
326*/
327int QQuickAnimatedImage::loops() const
328{
329 Q_D(const QQuickAnimatedImage);
330 return d->loops;
331}
332
333void QQuickAnimatedImage::setLoops(int loops)
334{
335 Q_D(QQuickAnimatedImage);
336 if (loops < 0 && loops != Infinite) {
337 qmlWarning(this) << "Loops must be AnimatedImage.Infinite, 0, or a positive integer, got"
338 << loops;
339 loops = Infinite;
340 }
341 if (d->loops == loops)
342 return;
343 d->loops = loops;
344 emit loopsChanged();
345 if (loops == 0 && d->movie && d->movie->state() != QMovie::NotRunning)
346 d->movie->stop();
347}
348
349/*!
350 \qmlenum QtQuick::AnimatedImage::FinishBehavior
351 \since 6.12
352
353 This enum describes the behavior when the animation finishes
354 on its own.
355
356 \value FinishAtInitialFrame
357 When the animation finishes it returns to the initial frame.
358 This is the default behavior.
359
360 \value FinishAtFinalFrame
361 When the animation finishes it stays on the final frame.
362*/
363
364/*!
365 \qmlproperty enumeration QtQuick::AnimatedImage::finishBehavior
366 \since 6.12
367
368 This property holds the behavior when the animation finishes
369 on its own. The default value is \l {FinishBehavior}.{FinishAtInitialFrame}.
370
371 \sa {FinishBehavior}
372*/
373QQuickAnimatedImage::FinishBehavior QQuickAnimatedImage::finishBehavior() const
374{
375 Q_D(const QQuickAnimatedImage);
376 return d->finishBehavior;
377}
378
379void QQuickAnimatedImage::setFinishBehavior(FinishBehavior behavior)
380{
381 Q_D(QQuickAnimatedImage);
382 if (d->finishBehavior == behavior)
383 return;
384 d->finishBehavior = behavior;
385 emit finishBehaviorChanged();
386}
387
388void QQuickAnimatedImage::setSource(const QUrl &url)
389{
390 Q_D(QQuickAnimatedImage);
391 if (url == d->url)
392 return;
393
394 d->currentLoop = 0;
395 d->lastFrameNumber = 0;
396
397#if QT_CONFIG(qml_network)
398 if (d->reply) {
399 d->reply->deleteLater();
400 d->reply = nullptr;
401 }
402#endif
403
404 d->setImage(QImage());
405 d->oldPlaying = isPlaying();
406 d->setMovie(nullptr);
407 d->url = url;
408 emit sourceChanged(d->url);
409
410 if (isComponentComplete())
411 load();
412}
413
414void QQuickAnimatedImage::load()
415{
416 Q_D(QQuickAnimatedImage);
417
418 if (d->url.isEmpty()) {
419 d->setProgress(0);
420
421 d->setImage(QImage());
422 if (sourceSize() != d->oldSourceSize) {
423 d->oldSourceSize = sourceSize();
424 emit sourceSizeChanged();
425 }
426
427 d->setStatus(Null);
428 if (isPlaying() != d->oldPlaying)
429 emit playingChanged();
430 } else {
431 const qreal targetDevicePixelRatio = d->effectiveDevicePixelRatio();
432 d->devicePixelRatio = 1.0;
433
434 const auto context = qmlContext(this);
435 QUrl loadUrl = context ? context->resolvedUrl(d->url) : d->url;
436 const QUrl resolvedUrl = loadUrl;
437 resolve2xLocalFile(resolvedUrl, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio);
438 QString lf = QQmlFile::urlToLocalFileOrQrc(loadUrl);
439
440 d->status = Null; // reset status, no emit
441
442 if (!lf.isEmpty()) {
443 d->setMovie(new QMovie(lf));
444 movieRequestFinished();
445 } else {
446#if QT_CONFIG(qml_network)
447 if (d->reply)
448 return;
449
450 d->setStatus(Loading);
451 d->setProgress(0);
452 QNetworkRequest req(d->url);
453 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
454
455 d->reply = qmlEngine(this)->networkAccessManager()->get(req);
456 connect(d->reply, &QNetworkReply::finished, this, &QQuickAnimatedImage::movieRequestFinished);
457 connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(requestProgress(qint64,qint64)));
458#endif
459 }
460 }
461}
462
463void QQuickAnimatedImage::movieRequestFinished()
464{
465 Q_D(QQuickAnimatedImage);
466
467#if QT_CONFIG(qml_network)
468 if (d->reply) {
469 auto movie = new QMovie(d->reply);
470 // From this point, we no longer need to handle the reply.
471 // I.e. it will be used only as a data source for QMovie,
472 // so it should live as long as the movie lives.
473 d->reply->disconnect(this);
474 d->reply->setParent(movie);
475 d->reply = nullptr;
476
477 d->setMovie(movie);
478 }
479#endif
480
481 if (!d->movie || !d->movie->isValid()) {
482 const QQmlContext *context = qmlContext(this);
483 qmlWarning(this) << "Error Reading Animated Image File "
484 << (context ? context->resolvedUrl(d->url) : d->url).toString();
485 d->setMovie(nullptr);
486
487 d->setImage(QImage());
488 if (sourceSize() != d->oldSourceSize) {
489 d->oldSourceSize = sourceSize();
490 emit sourceSizeChanged();
491 }
492
493 d->setProgress(0);
494 d->setStatus(Error);
495
496 if (isPlaying() != d->oldPlaying)
497 emit playingChanged();
498 return;
499 }
500
501 connect(d->movie, &QMovie::stateChanged, this, &QQuickAnimatedImage::playingStatusChanged);
502 connect(d->movie, &QMovie::frameChanged, this, &QQuickAnimatedImage::movieUpdate);
503 connect(d->movie, &QMovie::finished, this, &QQuickAnimatedImage::onMovieFinished);
504 if (d->cache)
505 d->movie->setCacheMode(QMovie::CacheAll);
506 d->movie->setSpeed(qRound(d->speed * 100.0));
507
508 d->setProgress(1);
509
510 bool pausedAtStart = d->paused;
511 if (d->movie && d->playing && d->loops != 0)
512 d->movie->start();
513 if (d->movie && pausedAtStart)
514 d->movie->setPaused(true);
515 if (d->movie && (d->paused || !d->playing || d->loops == 0)) {
516 d->movie->jumpToFrame(d->presetCurrentFrame);
517 d->presetCurrentFrame = 0;
518 }
519
520 QQuickPixmap *pixmap = d->infoForCurrentFrame(qmlEngine(this));
521 if (pixmap) {
522 d->setPixmap(*pixmap);
523 if (sourceSize() != d->oldSourceSize) {
524 d->oldSourceSize = sourceSize();
525 emit sourceSizeChanged();
526 }
527 }
528
529 d->setStatus(Ready);
530
531 if (isPlaying() != d->oldPlaying)
532 emit playingChanged();
533}
534
535void QQuickAnimatedImage::movieUpdate()
536{
537 Q_D(QQuickAnimatedImage);
538
539 if (!d->cache)
540 d->clearCache();
541
542 if (d->movie) {
543 int currentFrame = d->movie->currentFrameNumber();
544 // Detect wrap-around: current frame < previous frame
545 if (currentFrame < d->lastFrameNumber && d->lastFrameNumber > 0) {
546 d->currentLoop++;
547 if (d->loops != Infinite && d->currentLoop >= d->loops) {
548 d->handleLoopCompletion();
549 // handleLoopCompletion() stops the movie and may jump to a
550 // different frame. Re-read the frame number and fall
551 // through to setPixmap() so the display is updated.
552 currentFrame = d->movie->currentFrameNumber();
553 }
554 }
555 d->lastFrameNumber = currentFrame;
556 d->setPixmap(*d->infoForCurrentFrame(qmlEngine(this)));
557 emit QQuickImageBase::currentFrameChanged();
558 }
559}
560
561void QQuickAnimatedImage::playingStatusChanged()
562{
563 Q_D(QQuickAnimatedImage);
564
565 if ((d->movie->state() != QMovie::NotRunning) != d->playing) {
566 d->playing = (d->movie->state() != QMovie::NotRunning);
567 emit playingChanged();
568 }
569 if ((d->movie->state() == QMovie::Paused) != d->paused) {
570 d->paused = (d->movie->state() == QMovie::Paused);
571 emit pausedChanged();
572 }
573}
574
575/*!
576 \qmlsignal QtQuick::AnimatedImage::finished()
577 \since 6.12
578
579 This signal is emitted when the animation has finished playing
580 the number of times specified by \l loops.
581
582 It is not emitted when \l playing is set to \c false manually,
583 nor for animations whose \l loops property is set to
584 \c AnimatedImage.Infinite.
585*/
586
587void QQuickAnimatedImage::onMovieFinished()
588{
589 Q_D(QQuickAnimatedImage);
590 // Ignore single-frame images — restarting them would loop forever.
591 if (d->movie->frameCount() <= 1)
592 return;
593 if (d->loops == Infinite || d->currentLoop < d->loops) {
594 // Avoid false wrap-around detection on restart.
595 d->lastFrameNumber = 0;
596 d->movie->start();
597 } else {
598 d->handleLoopCompletion();
599 }
600}
601
602void QQuickAnimatedImage::onCacheChanged()
603{
604 Q_D(QQuickAnimatedImage);
605 if (!cache()) {
606 d->clearCache();
607 if (d->movie)
608 d->movie->setCacheMode(QMovie::CacheNone);
609 } else {
610 if (d->movie)
611 d->movie->setCacheMode(QMovie::CacheAll);
612 }
613}
614
615void QQuickAnimatedImage::componentComplete()
616{
617 QQuickItem::componentComplete(); // NOT QQuickImage
618 load();
619}
620
622{
623 if (movie == m)
624 return;
625
626 Q_Q(QQuickAnimatedImage);
627 const int oldFrameCount = q->frameCount();
628
629 if (movie) {
630 movie->disconnect();
631 movie->deleteLater();
632 }
633
634 movie = m;
636
637 if (movie)
638 movie->setScaledSize(sourcesize);
639
640 if (oldFrameCount != q->frameCount())
641 emit q->frameCountChanged();
642}
643
644QT_END_NAMESPACE
645
646#include "moc_qquickanimatedimage_p.cpp"
Combined button and popup list for selecting options.