From 1d4f24820c0fff474d524e006d715e13e409a4b8 Mon Sep 17 00:00:00 2001 From: Dayang Shen 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 Reviewed-by: Liang Qi --- src/plugins/imageformats/webp/qwebphandler.cpp | 152 +++++++++++++++++++++++-- src/plugins/imageformats/webp/qwebphandler_p.h | 20 +++- 2 files changed, 159 insertions(+), 13 deletions(-) (limited to 'src') 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 #include #include +#include #include 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(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(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(data.constData()), data.size(), output, output_size, result.bytesPerLine())) + if (!WebPDecodeBGRAInto( + reinterpret_cast(m_iter.fragment.bytes), m_iter.fragment.size, + output, output_size, frame.bytesPerLine())) #else - if (!WebPDecodeARGBInto(reinterpret_cast(data.constData()), data.size(), output, output_size, result.bytesPerLine())) + if (!WebPDecodeARGBInto( + reinterpret_cast(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 +#include #include #include #include #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 -- cgit v1.2.3