From 1d4f24820c0fff474d524e006d715e13e409a4b8 Mon Sep 17 00:00:00 2001
From: Dayang Shen <Archangel.SDY@gmail.com>
Date: Sat, 5 Mar 2016 15:40:30 +0800
Subject: Add animation support to WebP plugin

We now use WebP Demux API to decode both single image format and muxed animation format.

Change-Id: Ia2922892a3a626e9921c3910801d7c975d9fc6a2
Reviewed-by: aavit <eirik.aavitsland@theqtcompany.com>
Reviewed-by: Liang Qi <liang.qi@theqtcompany.com>
---
 config.tests/libwebp/libwebp.cpp               |   5 +
 config.tests/libwebp/libwebp.pro               |   4 +-
 src/plugins/imageformats/webp/qwebphandler.cpp | 152 +++++++++++++++++++++++--
 src/plugins/imageformats/webp/qwebphandler_p.h |  20 +++-
 tests/auto/webp/images/kollada_animation.webp  | Bin 0 -> 24726 bytes
 tests/auto/webp/tst_qwebp.cpp                  |  60 ++++++++++
 tests/auto/webp/webp.qrc                       |   1 +
 7 files changed, 227 insertions(+), 15 deletions(-)
 create mode 100644 tests/auto/webp/images/kollada_animation.webp

diff --git a/config.tests/libwebp/libwebp.cpp b/config.tests/libwebp/libwebp.cpp
index f021a17..720b72b 100644
--- a/config.tests/libwebp/libwebp.cpp
+++ b/config.tests/libwebp/libwebp.cpp
@@ -28,6 +28,7 @@
 
 #include <webp/decode.h>
 #include <webp/encode.h>
+#include <webp/demux.h>
 
 #if WEBP_ABI_IS_INCOMPATIBLE(WEBP_DECODER_ABI_VERSION, 0x0203) || WEBP_ABI_IS_INCOMPATIBLE(WEBP_ENCODER_ABI_VERSION, 0x0202)
 #error "Incompatible libwebp version"
@@ -42,6 +43,10 @@ int main(int, char **)
     picture.use_argb = 0;
     WebPConfig config2;
     config2.lossless = 0;
+    WebPData data = {};
+    WebPDemuxer *demuxer = WebPDemux(&data);
+    WebPIterator iter;
+    iter.frame_num = 0;
 
     return 0;
 }
diff --git a/config.tests/libwebp/libwebp.pro b/config.tests/libwebp/libwebp.pro
index d69b9be..bcbedf8 100644
--- a/config.tests/libwebp/libwebp.pro
+++ b/config.tests/libwebp/libwebp.pro
@@ -2,5 +2,5 @@ SOURCES = libwebp.cpp
 CONFIG -= qt dylib
 mac:CONFIG -= app_bundle
 win32:CONFIG += console
-unix|mingw: LIBS += -lwebp
-else:win32: LIBS += libwebp.lib
+unix|mingw: LIBS += -lwebp -lwebpdemux
+else:win32: LIBS += libwebp.lib libwebpdemux.lib
diff --git a/src/plugins/imageformats/webp/qwebphandler.cpp b/src/plugins/imageformats/webp/qwebphandler.cpp
index 636c4d8..a59e6bd 100644
--- a/src/plugins/imageformats/webp/qwebphandler.cpp
+++ b/src/plugins/imageformats/webp/qwebphandler.cpp
@@ -39,8 +39,10 @@
 
 #include "qwebphandler_p.h"
 #include "webp/encode.h"
+#include <qcolor.h>
 #include <qimage.h>
 #include <qdebug.h>
+#include <qpainter.h>
 #include <qvariant.h>
 
 static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_constants.h
@@ -48,8 +50,20 @@ static const int riffHeaderSize = 12; // RIFF_HEADER_SIZE from webp/format_const
 QWebpHandler::QWebpHandler() :
     m_lossless(false),
     m_quality(75),
-    m_scanState(ScanNotScanned)
+    m_scanState(ScanNotScanned),
+    m_loop(0),
+    m_frameCount(0),
+    m_demuxer(NULL),
+    m_composited(NULL)
 {
+    memset(&m_iter, 0, sizeof(m_iter));
+}
+
+QWebpHandler::~QWebpHandler()
+{
+    WebPDemuxReleaseIterator(&m_iter);
+    WebPDemuxDelete(m_demuxer);
+    delete m_composited;
 }
 
 bool QWebpHandler::canRead() const
@@ -92,31 +106,93 @@ bool QWebpHandler::ensureScanned() const
 
     QWebpHandler *that = const_cast<QWebpHandler *>(this);
     QByteArray header = device()->peek(sizeof(WebPBitstreamFeatures));
-    if (WebPGetFeatures((const uint8_t*)header.constData(), header.size(), &(that->m_features)) == VP8_STATUS_OK)
-        m_scanState = ScanSuccess;
+    if (WebPGetFeatures((const uint8_t*)header.constData(), header.size(), &(that->m_features)) == VP8_STATUS_OK) {
+        if (m_features.has_animation) {
+            // For animation, we have to read and scan whole file to determine loop count and images count
+            device()->seek(oldPos);
+
+            if (that->ensureDemuxer()) {
+                that->m_loop = WebPDemuxGetI(m_demuxer, WEBP_FF_LOOP_COUNT);
+                that->m_frameCount = WebPDemuxGetI(m_demuxer, WEBP_FF_FRAME_COUNT);
+                that->m_bgColor = QColor::fromRgba(QRgb(WebPDemuxGetI(m_demuxer, WEBP_FF_BACKGROUND_COLOR)));
+
+                that->m_composited = new QImage(that->m_features.width, that->m_features.height, QImage::Format_ARGB32);
+
+                // We do not reset device position since we have read in all data
+                m_scanState = ScanSuccess;
+                return true;
+            }
+        } else {
+            m_scanState = ScanSuccess;
+        }
+    }
 
     device()->seek(oldPos);
 
     return m_scanState == ScanSuccess;
 }
 
+bool QWebpHandler::ensureDemuxer()
+{
+    if (m_demuxer)
+        return true;
+
+    m_rawData = device()->readAll();
+    m_webpData.bytes = reinterpret_cast<const uint8_t *>(m_rawData.constData());
+    m_webpData.size = m_rawData.size();
+
+    m_demuxer = WebPDemux(&m_webpData);
+    if (m_demuxer == NULL)
+        return false;
+
+    return true;
+}
+
 bool QWebpHandler::read(QImage *image)
 {
-    if (!ensureScanned() || device()->isSequential())
+    if (!ensureScanned() || device()->isSequential() || !ensureDemuxer())
+        return false;
+
+    if (m_iter.frame_num == 0) {
+        // Go to first frame
+        if (!WebPDemuxGetFrame(m_demuxer, 1, &m_iter))
+            return false;
+    } else {
+        // Go to next frame
+        if (!WebPDemuxNextFrame(&m_iter))
+            return false;
+    }
+
+    WebPBitstreamFeatures features;
+    VP8StatusCode status = WebPGetFeatures(m_iter.fragment.bytes, m_iter.fragment.size, &features);
+    if (status != VP8_STATUS_OK)
         return false;
 
-    QByteArray data = device()->readAll();
-    QImage result(m_features.width, m_features.height, QImage::Format_ARGB32);
-    uint8_t *output = result.bits();
-    size_t output_size = result.byteCount();
+    QImage frame(m_iter.width, m_iter.height, QImage::Format_ARGB32);
+    uint8_t *output = frame.bits();
+    size_t output_size = frame.byteCount();
 #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
-    if (!WebPDecodeBGRAInto(reinterpret_cast<const uint8_t*>(data.constData()), data.size(), output, output_size, result.bytesPerLine()))
+    if (!WebPDecodeBGRAInto(
+        reinterpret_cast<const uint8_t*>(m_iter.fragment.bytes), m_iter.fragment.size,
+        output, output_size, frame.bytesPerLine()))
 #else
-    if (!WebPDecodeARGBInto(reinterpret_cast<const uint8_t*>(data.constData()), data.size(), output, output_size, result.bytesPerLine()))
+    if (!WebPDecodeARGBInto(
+        reinterpret_cast<const uint8_t*>(m_iter.fragment.bytes), m_iter.fragment.size,
+        output, output_size, frame.bytesPerLine()))
 #endif
         return false;
 
-    *image = result;
+    if (!m_features.has_animation) {
+        // Single image
+        *image = frame;
+    } else {
+        // Animation
+        QPainter painter(m_composited);
+        painter.drawImage(currentImageRect(), frame);
+
+        *image = *m_composited;
+    }
+
     return true;
 }
 
@@ -191,6 +267,10 @@ QVariant QWebpHandler::option(ImageOption option) const
         return m_quality;
     case Size:
         return QSize(m_features.width, m_features.height);
+    case Animation:
+        return m_features.has_animation;
+    case BackgroundColor:
+        return m_bgColor;
     default:
         return QVariant();
     }
@@ -211,10 +291,58 @@ void QWebpHandler::setOption(ImageOption option, const QVariant &value)
 
 bool QWebpHandler::supportsOption(ImageOption option) const
 {
-    return option == Quality || option == Size;
+    return option == Quality
+        || option == Size
+        || option == Animation
+        || option == BackgroundColor;
 }
 
 QByteArray QWebpHandler::name() const
 {
     return QByteArrayLiteral("webp");
 }
+
+int QWebpHandler::imageCount() const
+{
+    if (!ensureScanned())
+        return 0;
+
+    if (!m_features.has_animation)
+        return 1;
+
+    return m_frameCount;
+}
+
+int QWebpHandler::currentImageNumber() const
+{
+    if (!ensureScanned() || !m_features.has_animation)
+        return 0;
+
+    // Frame number in WebP starts from 1
+    return m_iter.frame_num - 1;
+}
+
+QRect QWebpHandler::currentImageRect() const
+{
+    if (!ensureScanned())
+        return QRect();
+
+    return QRect(m_iter.x_offset, m_iter.y_offset, m_iter.width, m_iter.height);
+}
+
+int QWebpHandler::loopCount() const
+{
+    if (!ensureScanned() || !m_features.has_animation)
+        return 0;
+
+    // Loop count in WebP starts from 0
+    return m_loop - 1;
+}
+
+int QWebpHandler::nextImageDelay() const
+{
+    if (!ensureScanned() || !m_features.has_animation)
+        return 0;
+
+    return m_iter.duration;
+}
diff --git a/src/plugins/imageformats/webp/qwebphandler_p.h b/src/plugins/imageformats/webp/qwebphandler_p.h
index 05e614a..36dfed7 100644
--- a/src/plugins/imageformats/webp/qwebphandler_p.h
+++ b/src/plugins/imageformats/webp/qwebphandler_p.h
@@ -40,17 +40,20 @@
 #ifndef QWEBPHANDLER_P_H
 #define QWEBPHANDLER_P_H
 
+#include <QtGui/qcolor.h>
+#include <QtGui/qimage.h>
 #include <QtGui/qimageiohandler.h>
 #include <QtCore/qbytearray.h>
 #include <QtCore/qsize.h>
 
 #include "webp/decode.h"
+#include "webp/demux.h"
 
 class QWebpHandler : public QImageIOHandler
 {
 public:
     QWebpHandler();
-    ~QWebpHandler() {}
+    ~QWebpHandler();
 
 public:
     QByteArray name() const;
@@ -65,8 +68,15 @@ public:
     void setOption(ImageOption option, const QVariant &value);
     bool supportsOption(ImageOption option) const;
 
+    int imageCount() const;
+    int currentImageNumber() const;
+    QRect currentImageRect() const;
+    int loopCount() const;
+    int nextImageDelay() const;
+
 private:
     bool ensureScanned() const;
+    bool ensureDemuxer();
 
 private:
     enum ScanState {
@@ -79,6 +89,14 @@ private:
     int m_quality;
     mutable ScanState m_scanState;
     WebPBitstreamFeatures m_features;
+    int m_loop;
+    int m_frameCount;
+    QColor m_bgColor;
+    QByteArray m_rawData;
+    WebPData m_webpData;
+    WebPDemuxer *m_demuxer;
+    WebPIterator m_iter;
+    QImage *m_composited;   // For animation frames composition
 };
 
 #endif // WEBPHANDLER_H
diff --git a/tests/auto/webp/images/kollada_animation.webp b/tests/auto/webp/images/kollada_animation.webp
new file mode 100644
index 0000000..c38751a
Binary files /dev/null and b/tests/auto/webp/images/kollada_animation.webp differ
diff --git a/tests/auto/webp/tst_qwebp.cpp b/tests/auto/webp/tst_qwebp.cpp
index 4126076..d1d30db 100644
--- a/tests/auto/webp/tst_qwebp.cpp
+++ b/tests/auto/webp/tst_qwebp.cpp
@@ -37,6 +37,8 @@ private slots:
     void initTestCase();
     void readImage_data();
     void readImage();
+    void readAnimation_data();
+    void readAnimation();
     void writeImage_data();
     void writeImage();
 };
@@ -69,6 +71,64 @@ void tst_qwebp::readImage()
     QCOMPARE(image.size(), size);
 }
 
+void tst_qwebp::readAnimation_data()
+{
+    QTest::addColumn<QString>("fileName");
+    QTest::addColumn<QSize>("size");
+    QTest::addColumn<int>("imageCount");
+    QTest::addColumn<int>("loopCount");
+    QTest::addColumn<QColor>("bgColor");
+    QTest::addColumn<QList<QRect> >("imageRects");
+    QTest::addColumn<QList<int> >("imageDelays");
+
+    QTest::newRow("kollada")
+        << QString("kollada")
+        << QSize(436, 160)
+        << 1
+        << 0
+        << QColor()
+        << (QList<QRect>() << QRect(0, 0, 436, 160))
+        << (QList<int>() << 0);
+    QTest::newRow("kollada_animation")
+        << QString("kollada_animation")
+        << QSize(536, 260)
+        << 2
+        << 2
+        << QColor(128, 128, 128, 128)
+        << (QList<QRect>() << QRect(0, 0, 436, 160) << QRect(100, 100, 436, 160))
+        << (QList<int>() << 1000 << 1200);
+}
+
+void tst_qwebp::readAnimation()
+{
+    QFETCH(QString, fileName);
+    QFETCH(QSize, size);
+    QFETCH(int, imageCount);
+    QFETCH(int, loopCount);
+    QFETCH(QColor, bgColor);
+    QFETCH(QList<QRect>, imageRects);
+    QFETCH(QList<int>, imageDelays);
+
+    const QString path = QStringLiteral(":/images/") + fileName + QStringLiteral(".webp");
+    QImageReader reader(path);
+    QVERIFY(reader.canRead());
+    QCOMPARE(reader.size(), size);
+    QCOMPARE(reader.imageCount(), imageCount);
+    QCOMPARE(reader.loopCount(), loopCount);
+    QCOMPARE(reader.backgroundColor(), bgColor);
+
+    for (int i = 0; i < reader.imageCount(); ++i) {
+        QImage image = reader.read();
+        QVERIFY2(!image.isNull(), qPrintable(reader.errorString()));
+        QCOMPARE(image.size(), size);
+        QCOMPARE(reader.currentImageNumber(), i);
+        QCOMPARE(reader.currentImageRect(), imageRects[i]);
+        QCOMPARE(reader.nextImageDelay(), imageDelays[i]);
+    }
+
+    QVERIFY(reader.read().isNull());
+}
+
 void tst_qwebp::writeImage_data()
 {
     QTest::addColumn<QString>("fileName");
diff --git a/tests/auto/webp/webp.qrc b/tests/auto/webp/webp.qrc
index ab0b1b2..6519e58 100644
--- a/tests/auto/webp/webp.qrc
+++ b/tests/auto/webp/webp.qrc
@@ -3,5 +3,6 @@
         <file>images/kollada.png</file>
         <file>images/kollada.webp</file>
         <file>images/kollada_lossless.webp</file>
+        <file>images/kollada_animation.webp</file>
     </qresource>
 </RCC>
-- 
cgit v1.0-4-g1e03
