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 \nativetype 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 if (d->reply)
339 return;
340
341 d->setStatus(Loading);
342 d->setProgress(0);
343 QNetworkRequest req(d->url);
344 req.setAttribute(code: QNetworkRequest::HttpPipeliningAllowedAttribute, value: true);
345
346 d->reply = qmlEngine(this)->networkAccessManager()->get(request: req);
347 connect(sender: d->reply, signal: &QNetworkReply::finished, context: this, slot: &QQuickAnimatedImage::movieRequestFinished);
348 connect(sender: d->reply, SIGNAL(downloadProgress(qint64,qint64)), receiver: this, SLOT(requestProgress(qint64,qint64)));
349#endif
350 }
351 }
352}
353
354void QQuickAnimatedImage::movieRequestFinished()
355{
356 Q_D(QQuickAnimatedImage);
357
358#if QT_CONFIG(qml_network)
359 if (d->reply) {
360 auto movie = new QMovie(d->reply);
361 // From this point, we no longer need to handle the reply.
362 // I.e. it will be used only as a data source for QMovie,
363 // so it should live as long as the movie lives.
364 d->reply->disconnect(receiver: this);
365 d->reply->setParent(movie);
366 d->reply = nullptr;
367
368 d->setMovie(movie);
369 }
370#endif
371
372 if (!d->movie || !d->movie->isValid()) {
373 const QQmlContext *context = qmlContext(this);
374 qmlWarning(me: this) << "Error Reading Animated Image File "
375 << (context ? context->resolvedUrl(d->url) : d->url).toString();
376 d->setMovie(nullptr);
377
378 d->setImage(QImage());
379 if (sourceSize() != d->oldSourceSize) {
380 d->oldSourceSize = sourceSize();
381 emit sourceSizeChanged();
382 }
383
384 d->setProgress(0);
385 d->setStatus(Error);
386
387 if (isPlaying() != d->oldPlaying)
388 emit playingChanged();
389 return;
390 }
391
392 connect(sender: d->movie, signal: &QMovie::stateChanged, context: this, slot: &QQuickAnimatedImage::playingStatusChanged);
393 connect(sender: d->movie, signal: &QMovie::frameChanged, context: this, slot: &QQuickAnimatedImage::movieUpdate);
394 if (d->cache)
395 d->movie->setCacheMode(QMovie::CacheAll);
396 d->movie->setSpeed(qRound(d: d->speed * 100.0));
397
398 d->setProgress(1);
399
400 bool pausedAtStart = d->paused;
401 if (d->movie && d->playing)
402 d->movie->start();
403 if (d->movie && pausedAtStart)
404 d->movie->setPaused(true);
405 if (d->movie && (d->paused || !d->playing)) {
406 d->movie->jumpToFrame(frameNumber: d->presetCurrentFrame);
407 d->presetCurrentFrame = 0;
408 }
409
410 QQuickPixmap *pixmap = d->infoForCurrentFrame(engine: qmlEngine(this));
411 if (pixmap) {
412 d->setPixmap(*pixmap);
413 if (sourceSize() != d->oldSourceSize) {
414 d->oldSourceSize = sourceSize();
415 emit sourceSizeChanged();
416 }
417 }
418
419 d->setStatus(Ready);
420
421 if (isPlaying() != d->oldPlaying)
422 emit playingChanged();
423}
424
425void QQuickAnimatedImage::movieUpdate()
426{
427 Q_D(QQuickAnimatedImage);
428
429 if (!d->cache)
430 d->clearCache();
431
432 if (d->movie) {
433 d->setPixmap(*d->infoForCurrentFrame(engine: qmlEngine(this)));
434 emit QQuickImageBase::currentFrameChanged();
435 }
436}
437
438void QQuickAnimatedImage::playingStatusChanged()
439{
440 Q_D(QQuickAnimatedImage);
441
442 if ((d->movie->state() != QMovie::NotRunning) != d->playing) {
443 d->playing = (d->movie->state() != QMovie::NotRunning);
444 emit playingChanged();
445 }
446 if ((d->movie->state() == QMovie::Paused) != d->paused) {
447 d->paused = (d->movie->state() == QMovie::Paused);
448 emit pausedChanged();
449 }
450}
451
452void QQuickAnimatedImage::onCacheChanged()
453{
454 Q_D(QQuickAnimatedImage);
455 if (!cache()) {
456 d->clearCache();
457 if (d->movie)
458 d->movie->setCacheMode(QMovie::CacheNone);
459 } else {
460 if (d->movie)
461 d->movie->setCacheMode(QMovie::CacheAll);
462 }
463}
464
465void QQuickAnimatedImage::componentComplete()
466{
467 QQuickItem::componentComplete(); // NOT QQuickImage
468 load();
469}
470
471void QQuickAnimatedImagePrivate::setMovie(QMovie *m)
472{
473 if (movie == m)
474 return;
475
476 Q_Q(QQuickAnimatedImage);
477 const int oldFrameCount = q->frameCount();
478
479 if (movie) {
480 movie->disconnect();
481 movie->deleteLater();
482 }
483
484 movie = m;
485 clearCache();
486
487 if (movie)
488 movie->setScaledSize(sourcesize);
489
490 if (oldFrameCount != q->frameCount())
491 emit q->frameCountChanged();
492}
493
494QT_END_NAMESPACE
495
496#include "moc_qquickanimatedimage_p.cpp"
497

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

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