Implement cache property for QQuickAnimatedImage
authorDaiwei Li <daiweili@suitabletech.com>
Sat, 28 Feb 2015 07:56:12 +0000 (23:56 -0800)
committerDaiwei Li <daiweili@suitabletech.com>
Fri, 6 Mar 2015 18:07:01 +0000 (18:07 +0000)
Some longer and larger .gifs can consume a lot of memory
if every decoded frame is cached. Just as the backing QMovie
provides the ability to not cache frame, so should AnimatedImage.

This also allows a workaround for some animated image types
that can contain loops that aren't handled correctly (QTBUG-24869)
to not leak memory.

Change-Id: I0639461d75bb2c758917893e7a6ae5c215fffa9d
Task-number: QTBUG-44447
Task-number: QTBUG-24869
Task-number: QTBUG-28844
Reviewed-by: Alan Alpert <aalpert@blackberry.com>
src/quick/items/qquickanimatedimage.cpp
src/quick/items/qquickanimatedimage_p.h
tests/auto/quick/qquickanimatedimage/data/colors_nocache.qml [new file with mode: 0644]
tests/auto/quick/qquickanimatedimage/tst_qquickanimatedimage.cpp

index a989e81..49fef12 100644 (file)
@@ -104,7 +104,13 @@ QQuickPixmap* QQuickAnimatedImagePrivate::infoForCurrentFrame(QQmlEngine *engine
     about its state, such as the current frame and total number of frames.
     The result is an animated image with a simple progress indicator underneath it.
 
-    \b Note: Unlike images, animated images are not cached or shared internally.
+    \b Note: When animated images are cached, every frame of the animation will be cached.
+
+    Set cache to false if you are playing a long or large animation and you
+    want to conserve memory.
+
+    If the image data comes from a sequential device (e.g. a socket),
+    AnimatedImage can only loop if cache is set to true.
 
     \clearfloat
     \snippet qml/animatedimage.qml document
@@ -126,6 +132,7 @@ QQuickPixmap* QQuickAnimatedImagePrivate::infoForCurrentFrame(QQmlEngine *engine
 QQuickAnimatedImage::QQuickAnimatedImage(QQuickItem *parent)
     : QQuickImage(*(new QQuickAnimatedImagePrivate), parent)
 {
+    QObject::connect(this, &QQuickImageBase::cacheChanged, this, &QQuickAnimatedImage::onCacheChanged);
 }
 
 QQuickAnimatedImage::~QQuickAnimatedImage()
@@ -372,7 +379,8 @@ void QQuickAnimatedImage::movieRequestFinished()
             this, SLOT(playingStatusChanged()));
     connect(d->_movie, SIGNAL(frameChanged(int)),
             this, SLOT(movieUpdate()));
-    d->_movie->setCacheMode(QMovie::CacheAll);
+    if (d->cache)
+        d->_movie->setCacheMode(QMovie::CacheAll);
 
     d->status = Ready;
     emit statusChanged(d->status);
@@ -406,6 +414,11 @@ void QQuickAnimatedImage::movieUpdate()
 {
     Q_D(QQuickAnimatedImage);
 
+    if (!d->cache) {
+        qDeleteAll(d->frameMap);
+        d->frameMap.clear();
+    }
+
     if (d->_movie) {
         d->setPixmap(*d->infoForCurrentFrame(qmlEngine(this)));
         emit frameChanged();
@@ -426,6 +439,22 @@ void QQuickAnimatedImage::playingStatusChanged()
     }
 }
 
+void QQuickAnimatedImage::onCacheChanged()
+{
+    Q_D(QQuickAnimatedImage);
+    if (!cache()) {
+        qDeleteAll(d->frameMap);
+        d->frameMap.clear();
+        if (d->_movie) {
+            d->_movie->setCacheMode(QMovie::CacheNone);
+        }
+    } else {
+        if (d->_movie) {
+            d->_movie->setCacheMode(QMovie::CacheAll);
+        }
+    }
+}
+
 QSize QQuickAnimatedImage::sourceSize()
 {
     Q_D(QQuickAnimatedImage);
index de37cd8..4099338 100644 (file)
@@ -84,6 +84,7 @@ private Q_SLOTS:
     void movieUpdate();
     void movieRequestFinished();
     void playingStatusChanged();
+    void onCacheChanged();
 
 protected:
     void load() Q_DECL_OVERRIDE;
diff --git a/tests/auto/quick/qquickanimatedimage/data/colors_nocache.qml b/tests/auto/quick/qquickanimatedimage/data/colors_nocache.qml
new file mode 100644 (file)
index 0000000..24adca6
--- /dev/null
@@ -0,0 +1,6 @@
+import QtQuick 2.0
+
+AnimatedImage {
+    source: "colors.gif"
+    cache: false
+}
index ee38a0e..c42000d 100644 (file)
@@ -70,6 +70,7 @@ private slots:
     void qtbug_16520();
     void progressAndStatusChanges();
     void playingAndPausedChanges();
+    void noCaching();
 };
 
 void tst_qquickanimatedimage::cleanup()
@@ -528,6 +529,39 @@ void tst_qquickanimatedimage::playingAndPausedChanges()
 
     delete obj;
 }
+
+void tst_qquickanimatedimage::noCaching()
+{
+    QQuickView window, window_nocache;
+    window.setSource(testFileUrl("colors.qml"));
+    window_nocache.setSource(testFileUrl("colors_nocache.qml"));
+    window.show();
+    window_nocache.show();
+    QTest::qWaitForWindowExposed(&window);
+    QTest::qWaitForWindowExposed(&window_nocache);
+
+    QQuickAnimatedImage *anim = qobject_cast<QQuickAnimatedImage *>(window.rootObject());
+    QVERIFY(anim);
+
+    QQuickAnimatedImage *anim_nocache = qobject_cast<QQuickAnimatedImage *>(window_nocache.rootObject());
+    QVERIFY(anim_nocache);
+
+    QCOMPARE(anim->frameCount(), anim_nocache->frameCount());
+
+    // colors.gif only has 3 frames so this should be fast
+    for (int loops = 0; loops <= 2; ++loops) {
+        for (int frame = 0; frame < anim->frameCount(); ++frame) {
+            anim->setCurrentFrame(frame);
+            anim_nocache->setCurrentFrame(frame);
+
+            QImage image_cache = window.grabWindow();
+            QImage image_nocache = window_nocache.grabWindow();
+
+            QCOMPARE(image_cache, image_nocache);
+        }
+    }
+}
+
 QTEST_MAIN(tst_qquickanimatedimage)
 
 #include "tst_qquickanimatedimage.moc"