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