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->setProgress(0);
421
422 d->setImage(QImage());
423 if (sourceSize() != d->oldSourceSize) {
424 d->oldSourceSize = sourceSize();
425 emit sourceSizeChanged();
426 }
427
428 d->setStatus(Null);
429 if (isPlaying() != d->oldPlaying)
430 emit playingChanged();
431 } else {
432 const qreal targetDevicePixelRatio = d->effectiveDevicePixelRatio();
433 d->devicePixelRatio = 1.0;
434
435 const auto context = qmlContext(this);
436 QUrl loadUrl = context ? context->resolvedUrl(d->url) : d->url;
437 const QUrl resolvedUrl = loadUrl;
438 resolve2xLocalFile(resolvedUrl, targetDevicePixelRatio, &loadUrl, &d->devicePixelRatio);
439 QString lf = QQmlFile::urlToLocalFileOrQrc(loadUrl);
440
441 d->status = Null; // reset status, no emit
442
443 if (!lf.isEmpty()) {
444 d->setMovie(new QMovie(lf));
445 movieRequestFinished();
446 } else {
447#if QT_CONFIG(qml_network)
448 if (d->reply)
449 return;
450
451 d->setStatus(Loading);
452 d->setProgress(0);
453 QNetworkRequest req(d->url);
454 req.setAttribute(QNetworkRequest::HttpPipeliningAllowedAttribute, true);
455
456 d->reply = qmlEngine(this)->networkAccessManager()->get(req);
457 connect(d->reply, &QNetworkReply::finished, this, &QQuickAnimatedImage::movieRequestFinished);
458 connect(d->reply, SIGNAL(downloadProgress(qint64,qint64)), this, SLOT(requestProgress(qint64,qint64)));
459#endif
460 }
461 }
462}
463
464void QQuickAnimatedImage::movieRequestFinished()
465{
466 Q_D(QQuickAnimatedImage);
467
468#if QT_CONFIG(qml_network)
469 if (d->reply) {
470 auto movie = new QMovie(d->reply);
471 // From this point, we no longer need to handle the reply.
472 // I.e. it will be used only as a data source for QMovie,
473 // so it should live as long as the movie lives.
474 d->reply->disconnect(this);
475 d->reply->setParent(movie);
476 d->reply = nullptr;
477
478 d->setMovie(movie);
479 }
480#endif
481
482 if (!d->movie || !d->movie->isValid()) {
483 const QQmlContext *context = qmlContext(this);
484 qmlWarning(this) << "Error Reading Animated Image File "
485 << (context ? context->resolvedUrl(d->url) : d->url).toString();
486 d->setMovie(nullptr);
487
488 d->setImage(QImage());
489 if (sourceSize() != d->oldSourceSize) {
490 d->oldSourceSize = sourceSize();
491 emit sourceSizeChanged();
492 }
493
494 d->setProgress(0);
495 d->setStatus(Error);
496
497 if (isPlaying() != d->oldPlaying)
498 emit playingChanged();
499 return;
500 }
501
502 connect(d->movie, &QMovie::stateChanged, this, &QQuickAnimatedImage::playingStatusChanged);
503 connect(d->movie, &QMovie::frameChanged, this, &QQuickAnimatedImage::movieUpdate);
504 connect(d->movie, &QMovie::finished, this, &QQuickAnimatedImage::onMovieFinished);
505 if (d->cache)
506 d->movie->setCacheMode(QMovie::CacheAll);
507 d->movie->setSpeed(qRound(d->speed * 100.0));
508
509 d->setProgress(1);
510
511 bool pausedAtStart = d->paused;
512 if (d->movie && d->playing && d->loops != 0)
513 d->movie->start();
514 if (d->movie && pausedAtStart)
515 d->movie->setPaused(true);
516 if (d->movie && (d->paused || !d->playing || d->loops == 0)) {
517 d->movie->jumpToFrame(d->presetCurrentFrame);
518 d->presetCurrentFrame = 0;
519 }
520
521 QQuickPixmap *pixmap = d->infoForCurrentFrame(qmlEngine(this));
522 if (pixmap) {
523 d->setPixmap(*pixmap);
524 if (sourceSize() != d->oldSourceSize) {
525 d->oldSourceSize = sourceSize();
526 emit sourceSizeChanged();
527 }
528 }
529
530 d->setStatus(Ready);
531
532 if (isPlaying() != d->oldPlaying)
533 emit playingChanged();
534}
535
536void QQuickAnimatedImage::movieUpdate()
537{
538 Q_D(QQuickAnimatedImage);
539
540 if (!d->cache)
541 d->clearCache();
542
543 if (d->movie) {
544 int currentFrame = d->movie->currentFrameNumber();
545 // Detect wrap-around: current frame < previous frame
546 if (currentFrame < d->lastFrameNumber && d->lastFrameNumber > 0) {
547 d->currentLoop++;
548 if (d->loops != Infinite && d->currentLoop >= d->loops) {
549 d->handleLoopCompletion();
550 // handleLoopCompletion() stops the movie and may jump to a
551 // different frame. Re-read the frame number and fall
552 // through to setPixmap() so the display is updated.
553 currentFrame = d->movie->currentFrameNumber();
554 }
555 }
556 d->lastFrameNumber = currentFrame;
557 d->setPixmap(*d->infoForCurrentFrame(qmlEngine(this)));
558 emit QQuickImageBase::currentFrameChanged();
559 }
560}
561
562void QQuickAnimatedImage::playingStatusChanged()
563{
564 Q_D(QQuickAnimatedImage);
565
566 if ((d->movie->state() != QMovie::NotRunning) != d->playing) {
567 d->playing = (d->movie->state() != QMovie::NotRunning);
568 emit playingChanged();
569 }
570 if ((d->movie->state() == QMovie::Paused) != d->paused) {
571 d->paused = (d->movie->state() == QMovie::Paused);
572 emit pausedChanged();
573 }
574}
575
576/*!
577 \qmlsignal QtQuick::AnimatedImage::finished()
578 \since 6.12
579
580 This signal is emitted when the animation has finished playing
581 the number of times specified by \l loops.
582
583 It is not emitted when \l playing is set to \c false manually,
584 nor for animations whose \l loops property is set to
585 \c AnimatedImage.Infinite.
586*/
587
588void QQuickAnimatedImage::onMovieFinished()
589{
590 Q_D(QQuickAnimatedImage);
591 // Ignore single-frame images — restarting them would loop forever.
592 if (d->movie->frameCount() <= 1)
593 return;
594 d->currentLoop++;
595 if (d->loops == Infinite || d->currentLoop < d->loops) {
596 // Avoid false wrap-around detection on restart.
597 d->lastFrameNumber = 0;
598 d->movie->start();
599 } else {
600 d->handleLoopCompletion();
601 }
602}
603
604void QQuickAnimatedImage::onCacheChanged()
605{
606 Q_D(QQuickAnimatedImage);
607 if (!cache()) {
608 d->clearCache();
609 if (d->movie)
610 d->movie->setCacheMode(QMovie::CacheNone);
611 } else {
612 if (d->movie)
613 d->movie->setCacheMode(QMovie::CacheAll);
614 }
615}
616
617void QQuickAnimatedImage::componentComplete()
618{
619 QQuickItem::componentComplete(); // NOT QQuickImage
620 load();
621}
622
624{
625 if (movie == m)
626 return;
627
628 Q_Q(QQuickAnimatedImage);
629 const int oldFrameCount = q->frameCount();
630
631 if (movie) {
632 movie->disconnect();
633 movie->deleteLater();
634 }
635
636 movie = m;
638
639 if (movie)
640 movie->setScaledSize(sourcesize);
641
642 if (oldFrameCount != q->frameCount())
643 emit q->frameCountChanged();
644}
645
646QT_END_NAMESPACE
647
648#include "moc_qquickanimatedimage_p.cpp"
Combined button and popup list for selecting options.