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->jumpToFrame(0);
240 d->movie->start();
241 } else {
242 d->movie->stop();
243 }
244}
245
246/*!
247 \qmlproperty int QtQuick::AnimatedImage::currentFrame
248 \qmlproperty int QtQuick::AnimatedImage::frameCount
249
250 currentFrame is the frame that is currently visible. By monitoring this property
251 for changes, you can animate other items at the same time as the image.
252
253 frameCount is the number of frames in the animation. For some animation formats,
254 frameCount is unknown and has a value of zero.
255*/
256int QQuickAnimatedImage::currentFrame() const
257{
258 Q_D(const QQuickAnimatedImage);
259 if (!d->movie)
260 return d->presetCurrentFrame;
261 return d->movie->currentFrameNumber();
262}
263
264void QQuickAnimatedImage::setCurrentFrame(int frame)
265{
266 Q_D(QQuickAnimatedImage);
267 if (!d->movie) {
268 d->presetCurrentFrame = frame;
269 return;
270 }
271 // Update lastFrameNumber before jumpToFrame so that the synchronous
272 // frameChanged signal from QMovie does not trigger false wrap-around
273 // detection in movieUpdate()
274 d->lastFrameNumber = frame;
275 d->movie->jumpToFrame(frame);
276}
277
278int QQuickAnimatedImage::frameCount() const
279{
280 Q_D(const QQuickAnimatedImage);
281 if (!d->movie)
282 return 0;
283 return d->movie->frameCount();
284}
285
286/*!
287 \qmlproperty real QtQuick::AnimatedImage::speed
288 \since QtQuick 2.11
289
290 This property holds the speed of the animation.
291
292 The speed is measured in percentage of the original animated image speed.
293 The default speed is 1.0 (original speed).
294*/
295qreal QQuickAnimatedImage::speed() const
296{
297 Q_D(const QQuickAnimatedImage);
298 return d->speed;
299}
300
301void QQuickAnimatedImage::setSpeed(qreal speed)
302{
303 Q_D(QQuickAnimatedImage);
304 if (d->speed != speed) {
305 d->speed = speed;
306 if (d->movie)
307 d->movie->setSpeed(qRound(speed * 100.0));
308 emit speedChanged();
309 }
310}
311
312/*!
313 \qmlproperty int QtQuick::AnimatedImage::loops
314 \since 6.12
315
316 This property holds the number of times the animation will play.
317
318 After playing the animation this many times, the animation will
319 automatically stop and the \l finished signal will be emitted.
320
321 If this is set to \c AnimatedImage.Infinite (the default), the
322 animation will not stop playing on its own.
323
324 Setting \c loops to \c 0 means the animation will not play.
325
326 Negative values are invalid.
327*/
328int QQuickAnimatedImage::loops() const
329{
330 Q_D(const QQuickAnimatedImage);
331 return d->loops;
332}
333
334void QQuickAnimatedImage::setLoops(int loops)
335{
336 Q_D(QQuickAnimatedImage);
337 if (loops < 0 && loops != Infinite) {
338 qmlWarning(this) << "Loops must be AnimatedImage.Infinite, 0, or a positive integer, got"
339 << loops;
340 loops = Infinite;
341 }
342 if (d->loops == loops)
343 return;
344 d->loops = loops;
345 emit loopsChanged();
346 if (loops == 0 && d->movie && d->movie->state() != QMovie::NotRunning)
347 d->movie->stop();
348}
349
350/*!
351 \qmlenum QtQuick::AnimatedImage::FinishBehavior
352 \since 6.12
353
354 This enum describes the behavior when the animation finishes
355 on its own.
356
357 \value FinishAtInitialFrame
358 When the animation finishes it returns to the initial frame.
359 This is the default behavior.
360
361 \value FinishAtFinalFrame
362 When the animation finishes it stays on the final frame.
363*/
364
365/*!
366 \qmlproperty enumeration QtQuick::AnimatedImage::finishBehavior
367 \since 6.12
368
369 This property holds the behavior when the animation finishes
370 on its own. The default value is \l {FinishBehavior}.{FinishAtInitialFrame}.
371
372 \sa {FinishBehavior}
373*/
374QQuickAnimatedImage::FinishBehavior QQuickAnimatedImage::finishBehavior() const
375{
376 Q_D(const QQuickAnimatedImage);
377 return d->finishBehavior;
378}
379
380void QQuickAnimatedImage::setFinishBehavior(FinishBehavior behavior)
381{
382 Q_D(QQuickAnimatedImage);
383 if (d->finishBehavior == behavior)
384 return;
385 d->finishBehavior = behavior;
386 emit finishBehaviorChanged();
387}
388
389void QQuickAnimatedImage::setSource(const QUrl &url)
390{
391 Q_D(QQuickAnimatedImage);
392 if (url == d->url)
393 return;
394
395 d->currentLoop = 0;
396 d->lastFrameNumber = 0;
397
398#if QT_CONFIG(qml_network)
399 if (d->reply) {
400 d->reply->deleteLater();
401 d->reply = nullptr;
402 }
403#endif
404
405 d->setImage(QImage());
406 d->oldPlaying = isPlaying();
407 d->setMovie(nullptr);
408 d->url = url;
409 emit sourceChanged(d->url);
410
411 if (isComponentComplete())
412 load();
413}
414
415void QQuickAnimatedImage::load()
416{
417 Q_D(QQuickAnimatedImage);
418
419 if (d->url.isEmpty()) {
420 d->loadedForDevicePixelRatio = 0;
421 d->setProgress(0);
422
423 d->setImage(QImage());
424 if (sourceSize() != d->oldSourceSize) {
425 d->oldSourceSize = sourceSize();
426 emit sourceSizeChanged();
427 }
428
429 d->setStatus(Null);
430 if (isPlaying() != d->oldPlaying)
431 emit playingChanged();
432 } else {
433 const qreal targetDevicePixelRatio = d->devicePixelRatioForLoading();
434 d->devicePixelRatio = 1.0;
435
436 const auto context = qmlContext(this);
437 QUrl loadUrl = context ? context->resolvedUrl(d->url) : d->url;
438 const QUrl resolvedUrl = loadUrl;
439 resolve2xLocalFile(resolvedUrl, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio);
440 QString lf = QQmlFile::urlToLocalFileOrQrc(loadUrl);
441
442 d->status = Null; // reset status, no emit
443
444 if (!lf.isEmpty()) {
445 d->setMovie(new QMovie(lf));
446 movieRequestFinished();
447 } else {
448#if QT_CONFIG(qml_network)
449 if (d->reply)
450 return;
451
452 d->setStatus(Loading);
453 d->setProgress(0);
454 QNetworkRequest req(d->url);
455 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
456
457 d->reply = qmlEngine(this)->networkAccessManager()->get(req);
458 connect(d->reply, &QNetworkReply::finished, this, &QQuickAnimatedImage::movieRequestFinished);
459 connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(requestProgress(qint64,qint64)));
460#endif
461 }
462 }
463}
464
465void QQuickAnimatedImage::movieRequestFinished()
466{
467 Q_D(QQuickAnimatedImage);
468
469#if QT_CONFIG(qml_network)
470 if (d->reply) {
471 auto movie = new QMovie(d->reply);
472 // From this point, we no longer need to handle the reply.
473 // I.e. it will be used only as a data source for QMovie,
474 // so it should live as long as the movie lives.
475 d->reply->disconnect(this);
476 d->reply->setParent(movie);
477 d->reply = nullptr;
478
479 d->setMovie(movie);
480 }
481#endif
482
483 if (!d->movie || !d->movie->isValid()) {
484 const QQmlContext *context = qmlContext(this);
485 qmlWarning(this) << "Error Reading Animated Image File "
486 << (context ? context->resolvedUrl(d->url) : d->url).toString();
487 d->setMovie(nullptr);
488
489 d->setImage(QImage());
490 if (sourceSize() != d->oldSourceSize) {
491 d->oldSourceSize = sourceSize();
492 emit sourceSizeChanged();
493 }
494
495 d->setProgress(0);
496 d->setStatus(Error);
497
498 if (isPlaying() != d->oldPlaying)
499 emit playingChanged();
500 return;
501 }
502
503 connect(d->movie, &QMovie::stateChanged, this, &QQuickAnimatedImage::playingStatusChanged);
504 connect(d->movie, &QMovie::frameChanged, this, &QQuickAnimatedImage::movieUpdate);
505 connect(d->movie, &QMovie::finished, this, &QQuickAnimatedImage::onMovieFinished);
506 if (d->cache)
507 d->movie->setCacheMode(QMovie::CacheAll);
508 d->movie->setSpeed(qRound(d->speed * 100.0));
509
510 d->setProgress(1);
511
512 bool pausedAtStart = d->paused;
513 if (d->movie && d->playing && d->loops != 0)
514 d->movie->start();
515 if (d->movie && pausedAtStart)
516 d->movie->setPaused(true);
517 if (d->movie && (d->paused || !d->playing || d->loops == 0)) {
518 d->movie->jumpToFrame(d->presetCurrentFrame);
519 d->presetCurrentFrame = 0;
520 }
521
522 QQuickPixmap *pixmap = d->infoForCurrentFrame(qmlEngine(this));
523 if (pixmap) {
524 d->setPixmap(*pixmap);
525 if (sourceSize() != d->oldSourceSize) {
526 d->oldSourceSize = sourceSize();
527 emit sourceSizeChanged();
528 }
529 }
530
531 d->setStatus(Ready);
532
533 if (isPlaying() != d->oldPlaying)
534 emit playingChanged();
535}
536
537void QQuickAnimatedImage::movieUpdate()
538{
539 Q_D(QQuickAnimatedImage);
540
541 if (!d->cache)
542 d->clearCache();
543
544 if (d->movie) {
545 int currentFrame = d->movie->currentFrameNumber();
546 // Detect wrap-around: current frame < previous frame
547 if (currentFrame < d->lastFrameNumber && d->lastFrameNumber > 0) {
548 d->currentLoop++;
549 if (d->loops != Infinite && d->currentLoop >= d->loops) {
550 d->handleLoopCompletion();
551 // handleLoopCompletion() stops the movie and may jump to a
552 // different frame. Re-read the frame number and fall
553 // through to setPixmap() so the display is updated.
554 currentFrame = d->movie->currentFrameNumber();
555 }
556 }
557 d->lastFrameNumber = currentFrame;
558 d->setPixmap(*d->infoForCurrentFrame(qmlEngine(this)));
559 emit QQuickImageBase::currentFrameChanged();
560 }
561}
562
563void QQuickAnimatedImage::playingStatusChanged()
564{
565 Q_D(QQuickAnimatedImage);
566
567 if ((d->movie->state() != QMovie::NotRunning) != d->playing) {
568 d->playing = (d->movie->state() != QMovie::NotRunning);
569 emit playingChanged();
570 }
571 if ((d->movie->state() == QMovie::Paused) != d->paused) {
572 d->paused = (d->movie->state() == QMovie::Paused);
573 emit pausedChanged();
574 }
575}
576
577/*!
578 \qmlsignal QtQuick::AnimatedImage::finished()
579 \since 6.12
580
581 This signal is emitted when the animation has finished playing
582 the number of times specified by \l loops.
583
584 It is not emitted when \l playing is set to \c false manually,
585 nor for animations whose \l loops property is set to
586 \c AnimatedImage.Infinite.
587*/
588
589void QQuickAnimatedImage::onMovieFinished()
590{
591 Q_D(QQuickAnimatedImage);
592 // Ignore single-frame images — restarting them would loop forever.
593 if (d->movie->frameCount() <= 1)
594 return;
595 d->currentLoop++;
596 if (d->loops == Infinite || d->currentLoop < d->loops) {
597 // Avoid false wrap-around detection on restart.
598 d->lastFrameNumber = 0;
599 d->movie->start();
600 } else {
601 d->handleLoopCompletion();
602 }
603}
604
605void QQuickAnimatedImage::onCacheChanged()
606{
607 Q_D(QQuickAnimatedImage);
608 if (!cache()) {
609 d->clearCache();
610 if (d->movie)
611 d->movie->setCacheMode(QMovie::CacheNone);
612 } else {
613 if (d->movie)
614 d->movie->setCacheMode(QMovie::CacheAll);
615 }
616}
617
618void QQuickAnimatedImage::componentComplete()
619{
620 QQuickItem::componentComplete(); // NOT QQuickImage
621 load();
622}
623
625{
626 if (movie == m)
627 return;
628
629 Q_Q(QQuickAnimatedImage);
630 const int oldFrameCount = q->frameCount();
631
632 if (movie) {
633 movie->disconnect();
634 movie->deleteLater();
635 }
636
637 movie = m;
638 currentLoop = 0;
639 lastFrameNumber = 0;
641
642 if (movie)
643 movie->setScaledSize(sourcesize);
644
645 if (oldFrameCount != q->frameCount())
646 emit q->frameCountChanged();
647}
648
649QT_END_NAMESPACE
650
651#include "moc_qquickanimatedimage_p.cpp"
Combined button and popup list for selecting options.