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

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