summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorDayang Shen <Archangel.SDY@gmail.com>2016-03-05 15:40:30 +0800
committerLiang Qi <liang.qi@theqtcompany.com>2016-03-10 14:38:14 +0000
commit1d4f24820c0fff474d524e006d715e13e409a4b8 (patch)
tree903ca840e78e32504699ddb24d535ed25d748796
parent35b4108a4e2090a856226ac15b796e118655fa8b (diff)
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>
-rw-r--r--config.tests/libwebp/libwebp.cpp5
-rw-r--r--config.tests/libwebp/libwebp.pro4
-rw-r--r--src/plugins/imageformats/webp/qwebphandler.cpp152
-rw-r--r--src/plugins/imageformats/webp/qwebphandler_p.h20
-rw-r--r--tests/auto/webp/images/kollada_animation.webpbin0 -> 24726 bytes
-rw-r--r--tests/auto/webp/tst_qwebp.cpp60
-rw-r--r--tests/auto/webp/webp.qrc1
7 files changed, 227 insertions, 15 deletions
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
--- /dev/null
+++ b/tests/auto/webp/images/kollada_animation.webp
Binary files 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>