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#include "qquickanimatedimage_p.h"
5#include "qquickanimatedimage_p_p.h"
6
7#include <QtGui/qguiapplication.h>
8#include <QtQml/qqmlinfo.h>
9#include <QtQml/qqmlfile.h>
10#include <QtQml/qqmlengine.h>
11#include <QtGui/qmovie.h>
12#if QT_CONFIG(qml_network)
13#include <QtNetwork/qnetworkrequest.h>
14#include <QtNetwork/qnetworkreply.h>
15#endif
16
17QT_BEGIN_NAMESPACE
18
19QQuickPixmap* QQuickAnimatedImagePrivate::infoForCurrentFrame(QQmlEngine *engine)
20{
21 if (!movie)
22 return nullptr;
23
24 int current = movie->currentFrameNumber();
25 if (!frameMap.contains(key: current)) {
26 QUrl requestedUrl;
27 QQuickPixmap *pixmap = nullptr;
28 if (engine && !movie->fileName().isEmpty()) {
29 requestedUrl.setUrl(url: QString::fromUtf8(utf8: "quickanimatedimage://%1#%2x%3#%4")
30 .arg(a: movie->fileName())
31 .arg(a: movie->scaledSize().width())
32 .arg(a: movie->scaledSize().height())
33 .arg(a: current));
34 }
35 if (!requestedUrl.isEmpty()) {
36 if (QQuickPixmap::isCached(url: requestedUrl, requestRegion: QRect(), requestSize: QSize(), frame: 0, options: QQuickImageProviderOptions()))
37 pixmap = new QQuickPixmap(engine, requestedUrl);
38 else
39 pixmap = new QQuickPixmap(requestedUrl, movie->currentImage());
40 } else {
41 pixmap = new QQuickPixmap;
42 pixmap->setImage(movie->currentImage());
43 }
44 frameMap.insert(key: current, value: pixmap);
45 }
46
47 return frameMap.value(key: current);
48}
49
50void QQuickAnimatedImagePrivate::clearCache()
51{
52 qDeleteAll(c: frameMap);
53 frameMap.clear();
54}
55
56/*!
57 \qmltype AnimatedImage
58 \instantiates QQuickAnimatedImage
59 \inqmlmodule QtQuick
60 \inherits Image
61 \brief Plays animations stored as a series of images.
62 \ingroup qtquick-visual
63
64 The AnimatedImage type extends the features of the \l Image type, providing
65 a way to play animations stored as images containing a series of frames,
66 such as those stored in GIF files.
67
68 Information about the current frame and total length of the animation can be
69 obtained using the \l currentFrame and \l frameCount properties. You can
70 start, pause and stop the animation by changing the values of the \l playing
71 and \l paused properties.
72
73 The full list of supported formats can be determined with QMovie::supportedFormats().
74
75 \section1 Example Usage
76
77 \beginfloatleft
78 \image animatedimageitem.gif
79 \endfloat
80
81 The following QML shows how to display an animated image and obtain information
82 about its state, such as the current frame and total number of frames.
83 The result is an animated image with a simple progress indicator underneath it.
84
85 \b Note: When animated images are cached, every frame of the animation will be cached.
86
87 Set cache to false if you are playing a long or large animation and you
88 want to conserve memory.
89
90 If the image data comes from a sequential device (e.g. a socket),
91 AnimatedImage can only loop if cache is set to true.
92
93 \clearfloat
94 \snippet qml/animatedimage.qml document
95
96 \sa BorderImage, Image
97*/
98
99/*!
100 \qmlproperty url QtQuick::AnimatedImage::source
101
102 This property holds the URL that refers to the source image.
103
104 AnimatedImage can handle any image format supported by Qt, loaded
105 from any URL scheme supported by Qt. It is however not compatible
106 with QQuickImageProvider.
107*/
108
109/*!
110 \qmlproperty size QtQuick::AnimatedImage::sourceSize
111
112 This property holds the scaled width and height of the full-frame image.
113
114 Unlike the \l {Item::}{width} and \l {Item::}{height} properties, which scale
115 the painting of the image, this property sets the maximum number of pixels
116 stored for cached frames so that large animations do not use more
117 memory than necessary.
118
119 If the original size is larger than \c sourceSize, the image is scaled down.
120
121 The natural size of the image can be restored by setting this property to
122 \c undefined.
123
124 \note \e {Changing this property dynamically causes the image source to be reloaded,
125 potentially even from the network, if it is not in the disk cache.}
126
127 \sa Image::sourceSize
128*/
129QQuickAnimatedImage::QQuickAnimatedImage(QQuickItem *parent)
130 : QQuickImage(*(new QQuickAnimatedImagePrivate), parent)
131{
132 connect(sender: this, signal: &QQuickImageBase::cacheChanged, context: this, slot: &QQuickAnimatedImage::onCacheChanged);
133 connect(sender: this, signal: &QQuickImageBase::currentFrameChanged, context: this, slot: &QQuickAnimatedImage::frameChanged);
134 connect(sender: this, signal: &QQuickImageBase::currentFrameChanged, context: this, slot: &QQuickAnimatedImage::currentFrameChanged);
135 connect(sender: this, signal: &QQuickImageBase::frameCountChanged, context: this, slot: &QQuickAnimatedImage::frameCountChanged);
136}
137
138QQuickAnimatedImage::~QQuickAnimatedImage()
139{
140 Q_D(QQuickAnimatedImage);
141#if QT_CONFIG(qml_network)
142 if (d->reply)
143 d->reply->deleteLater();
144#endif
145 delete d->movie;
146 d->clearCache();
147}
148
149/*!
150 \qmlproperty bool QtQuick::AnimatedImage::paused
151 This property holds whether the animated image is paused.
152
153 By default, this property is false. Set it to true when you want to pause
154 the animation.
155*/
156
157bool QQuickAnimatedImage::isPaused() const
158{
159 Q_D(const QQuickAnimatedImage);
160 if (!d->movie)
161 return d->paused;
162 return d->movie->state()==QMovie::Paused;
163}
164
165void QQuickAnimatedImage::setPaused(bool pause)
166{
167 Q_D(QQuickAnimatedImage);
168 if (pause == d->paused)
169 return;
170 if (!d->movie) {
171 d->paused = pause;
172 emit pausedChanged();
173 } else {
174 d->movie->setPaused(pause);
175 }
176}
177
178/*!
179 \qmlproperty bool QtQuick::AnimatedImage::playing
180 This property holds whether the animated image is playing.
181
182 By default, this property is true, meaning that the animation
183 will start playing immediately.
184
185 \b Note: this property is affected by changes to the actual playing
186 state of AnimatedImage. If non-animated images are used, \a playing
187 will need to be manually set to \a true in order to animate
188 following images.
189 \qml
190 AnimatedImage {
191 onStatusChanged: playing = (status == AnimatedImage.Ready)
192 }
193 \endqml
194*/
195
196bool QQuickAnimatedImage::isPlaying() const
197{
198 Q_D(const QQuickAnimatedImage);
199 if (!d->movie)
200 return d->playing;
201 return d->movie->state()!=QMovie::NotRunning;
202}
203
204void QQuickAnimatedImage::setPlaying(bool play)
205{
206 Q_D(QQuickAnimatedImage);
207 if (play == d->playing)
208 return;
209 if (!d->movie) {
210 d->playing = play;
211 emit playingChanged();
212 return;
213 }
214 if (play)
215 d->movie->start();
216 else
217 d->movie->stop();
218}
219
220/*!
221 \qmlproperty int QtQuick::AnimatedImage::currentFrame
222 \qmlproperty int QtQuick::AnimatedImage::frameCount
223
224 currentFrame is the frame that is currently visible. By monitoring this property
225 for changes, you can animate other items at the same time as the image.
226
227 frameCount is the number of frames in the animation. For some animation formats,
228 frameCount is unknown and has a value of zero.
229*/
230int QQuickAnimatedImage::currentFrame() const
231{
232 Q_D(const QQuickAnimatedImage);
233 if (!d->movie)
234 return d->presetCurrentFrame;
235 return d->movie->currentFrameNumber();
236}
237
238void QQuickAnimatedImage::setCurrentFrame(int frame)
239{
240 Q_D(QQuickAnimatedImage);
241 if (!d->movie) {
242 d->presetCurrentFrame = frame;
243 return;
244 }
245 d->movie->jumpToFrame(frameNumber: frame);
246}
247
248int QQuickAnimatedImage::frameCount() const
249{
250 Q_D(const QQuickAnimatedImage);
251 if (!d->movie)
252 return 0;
253 return d->movie->frameCount();
254}
255
256/*!
257 \qmlproperty real QtQuick::AnimatedImage::speed
258 \since QtQuick 2.11
259
260 This property holds the speed of the animation.
261
262 The speed is measured in percentage of the original animated image speed.
263 The default speed is 1.0 (original speed).
264*/
265qreal QQuickAnimatedImage::speed() const
266{
267 Q_D(const QQuickAnimatedImage);
268 return d->speed;
269}
270
271void QQuickAnimatedImage::setSpeed(qreal speed)
272{
273 Q_D(QQuickAnimatedImage);
274 if (d->speed != speed) {
275 d->speed = speed;
276 if (d->movie)
277 d->movie->setSpeed(qRound(d: speed * 100.0));
278 emit speedChanged();
279 }
280}
281
282void QQuickAnimatedImage::setSource(const QUrl &url)
283{
284 Q_D(QQuickAnimatedImage);
285 if (url == d->url)
286 return;
287
288#if QT_CONFIG(qml_network)
289 if (d->reply) {
290 d->reply->deleteLater();
291 d->reply = nullptr;
292 }
293#endif
294
295 d->setImage(QImage());
296 d->oldPlaying = isPlaying();
297 d->setMovie(nullptr);
298 d->url = url;
299 emit sourceChanged(d->url);
300
301 if (isComponentComplete())
302 load();
303}
304
305void QQuickAnimatedImage::load()
306{
307 Q_D(QQuickAnimatedImage);
308
309 if (d->url.isEmpty()) {
310 d->setProgress(0);
311
312 d->setImage(QImage());
313 if (sourceSize() != d->oldSourceSize) {
314 d->oldSourceSize = sourceSize();
315 emit sourceSizeChanged();
316 }
317
318 d->setStatus(Null);
319 if (isPlaying() != d->oldPlaying)
320 emit playingChanged();
321 } else {
322 const qreal targetDevicePixelRatio = (window() ? window()->effectiveDevicePixelRatio() : qApp->devicePixelRatio());
323 d->devicePixelRatio = 1.0;
324
325 const auto context = qmlContext(this);
326 QUrl loadUrl = context ? context->resolvedUrl(d->url) : d->url;
327 const QUrl resolvedUrl = loadUrl;
328 resolve2xLocalFile(url: resolvedUrl, targetDevicePixelRatio, sourceUrl: &loadUrl, sourceDevicePixelRatio: &d->devicePixelRatio);
329 QString lf = QQmlFile::urlToLocalFileOrQrc(loadUrl);
330
331 d->status = Null; // reset status, no emit
332
333 if (!lf.isEmpty()) {
334 d->setMovie(new QMovie(lf));
335 movieRequestFinished();
336 } else {
337#if QT_CONFIG(qml_network)
338 d->setStatus(Loading);
339 d->setProgress(0);
340 QNetworkRequest req(d->url);
341 req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true);
342
343 d->reply = qmlEngine(this)->networkAccessManager()->get(request: req);
344 connect(sender: d->reply, signal: &QNetworkReply::finished, context: this, slot: &QQuickAnimatedImage::movieRequestFinished);
345 connect(sender: d->reply, SIGNAL(downloadProgress(qint64,qint64)), receiver: this, SLOT(requestProgress(qint64,qint64)));
346#endif
347 }
348 }
349}
350
351#define ANIMATEDIMAGE_MAXIMUM_REDIRECT_RECURSION 16
352
353void QQuickAnimatedImage::movieRequestFinished()
354{
355 Q_D(QQuickAnimatedImage);
356
357#if QT_CONFIG(qml_network)
358 if (d->reply) {
359 d->redirectCount++;
360 if (d->redirectCount < ANIMATEDIMAGE_MAXIMUM_REDIRECT_RECURSION) {
361 QVariant redirect = d->reply->attribute(code: QNetworkRequest::RedirectionTargetAttribute);
362 if (redirect.isValid()) {
363 QUrl url = d->reply->url().resolved(relative: redirect.toUrl());
364 d->reply->deleteLater();
365 setSource(url);
366 return;
367 }
368 }
369
370 d->redirectCount=0;
371 d->setMovie(new QMovie(d->reply));
372 }
373#endif
374
375 if (!d->movie || !d->movie->isValid()) {
376 const QQmlContext *context = qmlContext(this);
377 qmlWarning(me: this) << "Error Reading Animated Image File "
378 << (context ? context->resolvedUrl(d->url) : d->url).toString();
379 d->setMovie(nullptr);
380
381 d->setImage(QImage());
382 if (sourceSize() != d->oldSourceSize) {
383 d->oldSourceSize = sourceSize();
384 emit sourceSizeChanged();
385 }
386
387 d->setProgress(0);
388 d->setStatus(Error);
389
390 if (isPlaying() != d->oldPlaying)
391 emit playingChanged();
392 return;
393 }
394
395 connect(sender: d->movie, signal: &QMovie::stateChanged, context: this, slot: &QQuickAnimatedImage::playingStatusChanged);
396 connect(sender: d->movie, signal: &QMovie::frameChanged, context: this, slot: &QQuickAnimatedImage::movieUpdate);
397 if (d->cache)
398 d->movie->setCacheMode(QMovie::CacheAll);
399 d->movie->setSpeed(qRound(d: d->speed * 100.0));
400
401 d->setProgress(1);
402
403 bool pausedAtStart = d->paused;
404 if (d->movie && d->playing)
405 d->movie->start();
406 if (d->movie && pausedAtStart)
407 d->movie->setPaused(true);
408 if (d->movie && (d->paused || !d->playing)) {
409 d->movie->jumpToFrame(frameNumber: d->presetCurrentFrame);
410 d->presetCurrentFrame = 0;
411 }
412
413 QQuickPixmap *pixmap = d->infoForCurrentFrame(engine: qmlEngine(this));
414 if (pixmap) {
415 d->setPixmap(*pixmap);
416 if (sourceSize() != d->oldSourceSize) {
417 d->oldSourceSize = sourceSize();
418 emit sourceSizeChanged();
419 }
420 }
421
422 d->setStatus(Ready);
423
424 if (isPlaying() != d->oldPlaying)
425 emit playingChanged();
426}
427
428void QQuickAnimatedImage::movieUpdate()
429{
430 Q_D(QQuickAnimatedImage);
431
432 if (!d->cache)
433 d->clearCache();
434
435 if (d->movie) {
436 d->setPixmap(*d->infoForCurrentFrame(engine: qmlEngine(this)));
437 emit QQuickImageBase::currentFrameChanged();
438 }
439}
440
441void QQuickAnimatedImage::playingStatusChanged()
442{
443 Q_D(QQuickAnimatedImage);
444
445 if ((d->movie->state() != QMovie::NotRunning) != d->playing) {
446 d->playing = (d->movie->state() != QMovie::NotRunning);
447 emit playingChanged();
448 }
449 if ((d->movie->state() == QMovie::Paused) != d->paused) {
450 d->paused = (d->movie->state() == QMovie::Paused);
451 emit pausedChanged();
452 }
453}
454
455void QQuickAnimatedImage::onCacheChanged()
456{
457 Q_D(QQuickAnimatedImage);
458 if (!cache()) {
459 d->clearCache();
460 if (d->movie)
461 d->movie->setCacheMode(QMovie::CacheNone);
462 } else {
463 if (d->movie)
464 d->movie->setCacheMode(QMovie::CacheAll);
465 }
466}
467
468void QQuickAnimatedImage::componentComplete()
469{
470 QQuickItem::componentComplete(); // NOT QQuickImage
471 load();
472}
473
474void QQuickAnimatedImagePrivate::setMovie(QMovie *m)
475{
476 if (movie == m)
477 return;
478
479 Q_Q(QQuickAnimatedImage);
480 const int oldFrameCount = q->frameCount();
481
482 if (movie) {
483 movie->disconnect();
484 movie->deleteLater();
485 }
486
487 movie = m;
488 clearCache();
489
490 if (movie)
491 movie->setScaledSize(sourcesize);
492
493 if (oldFrameCount != q->frameCount())
494 emit q->frameCountChanged();
495}
496
497QT_END_NAMESPACE
498
499#include "moc_qquickanimatedimage_p.cpp"
500

source code of qtdeclarative/src/quick/items/qquickanimatedimage.cpp