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