From d94f5f5d8a614302583898100f9c57989d52ce9c Mon Sep 17 00:00:00 2001 From: Ivailo Monev Date: Wed, 10 Apr 2024 08:29:17 +0300 Subject: [PATCH] kimgio: handle all quircks of webp and QImageIOHandler now animations are read correctly, slow as molasses tho Signed-off-by: Ivailo Monev --- kimgio/webp.cpp | 131 ++++++++++++++++++++++++++++++------------------ kimgio/webp.h | 1 - 2 files changed, 83 insertions(+), 49 deletions(-) diff --git a/kimgio/webp.cpp b/kimgio/webp.cpp index ad7ec854..a2feda37 100644 --- a/kimgio/webp.cpp +++ b/kimgio/webp.cpp @@ -83,65 +83,100 @@ bool WebPHandler::read(QImage *image) m_loopcount = webpaniminfo.loop_count; m_imagecount = webpaniminfo.frame_count; - const WebPDemuxer* webpdemuxer = WebPAnimDecoderGetDemuxer(webpanimdec); + QRectF previousrect; + QImage buffer(webpaniminfo.canvas_width, webpaniminfo.canvas_height, QImage::Format_ARGB32); + if (Q_UNLIKELY(buffer.isNull())) { + kWarning() << "Could not create buffer image"; + return false; + } + const QColor background = QColor(QRgb(webpaniminfo.bgcolor)); + + int framecounter = 0; WebPIterator webpiter; - // NOTE: 0 will return the last frame, first frame is 1 but for QImageIOHandler first frame is 0 - webpstatus = WebPDemuxGetFrame(webpdemuxer, m_currentimage + 1, &webpiter); - if (Q_UNLIKELY(webpstatus == 0)) { - kWarning() << "Could not get frame"; - WebPAnimDecoderDelete(webpanimdec); - return false; - } + const WebPDemuxer* webpdemuxer = WebPAnimDecoderGetDemuxer(webpanimdec); + // NOTE: painting of all images up to the requested frame is done because QImageIOHandler API + // allows to jump to any frame via jumpToImage(), if not done like this skipping one frame will + // produce busted result + while (framecounter < m_imagecount) { + // NOTE: 0 will return the last frame + webpstatus = WebPDemuxGetFrame(webpdemuxer, framecounter + 1, &webpiter); + if (Q_UNLIKELY(webpstatus == 0)) { + kWarning() << "Could not get frame"; + WebPDemuxReleaseIterator(&webpiter); + WebPAnimDecoderDelete(webpanimdec); + return false; + } - // bound to reasonable limits - m_imagedelay = qBound(10, webpiter.duration, 10000); - - *image = QImage(webpiter.width, webpiter.height, QImage::Format_ARGB32); - if (Q_UNLIKELY(image->isNull())) { - kWarning() << "Could not create image"; - WebPDemuxReleaseIterator(&webpiter); - WebPAnimDecoderDelete(webpanimdec); - return false; - } + QImage frame(webpiter.width, webpiter.height, QImage::Format_ARGB32); + if (Q_UNLIKELY(frame.isNull())) { + kWarning() << "Could not create image"; + WebPDemuxReleaseIterator(&webpiter); + WebPAnimDecoderDelete(webpanimdec); + return false; + } #if Q_BYTE_ORDER == Q_BIG_ENDIAN - const uint8_t* webpoutput = WebPDecodeARGBInto( + const uint8_t* webpoutput = WebPDecodeARGBInto( #else - const uint8_t* webpoutput = WebPDecodeBGRAInto( + const uint8_t* webpoutput = WebPDecodeBGRAInto( #endif - webpiter.fragment.bytes, webpiter.fragment.size, - reinterpret_cast(image->bits()), image->byteCount(), - image->bytesPerLine() - ); - if (Q_UNLIKELY(!webpoutput)) { - kWarning() << "Could not decode image"; - *image = QImage(); - WebPDemuxReleaseIterator(&webpiter); - WebPAnimDecoderDelete(webpanimdec); - return false; - } + webpiter.fragment.bytes, webpiter.fragment.size, + reinterpret_cast(frame.bits()), frame.byteCount(), + frame.bytesPerLine() + ); + if (Q_UNLIKELY(!webpoutput)) { + kWarning() << "Could not decode image"; + WebPDemuxReleaseIterator(&webpiter); + WebPAnimDecoderDelete(webpanimdec); + return false; + } - switch (webpiter.blend_method) { - case WEBP_MUX_BLEND: { - // NOTE: there are bogus images that want to blend all frames, including the first one - if (!m_lastframe.isNull()) { - QPainter p(image); - // TODO: offsets (webpiter.x_offset and webpiter.y_offset) - p.setCompositionMode(QPainter::CompositionMode_DestinationOver); - p.drawImage(0, 0, m_lastframe); - p.end(); + QPainter painter(&buffer); + switch (webpiter.dispose_method) { + case WEBP_MUX_DISPOSE_BACKGROUND: { + // yep, there are images attempting to dispose the background without previous frame + if (!previousrect.isNull()) { + painter.setCompositionMode(QPainter::CompositionMode_Clear); + painter.fillRect(previousrect, background); + } + break; } + case WEBP_MUX_DISPOSE_NONE: { + break; + } + default: { + kWarning() << "Unknown dispose method" << webpiter.dispose_method; + break; + } + } + + switch (webpiter.blend_method) { + case WEBP_MUX_BLEND: { + painter.setCompositionMode(QPainter::CompositionMode_SourceOver); + break; + } + case WEBP_MUX_NO_BLEND: { + painter.setCompositionMode(QPainter::CompositionMode_Source); + break; + } + default: { + kWarning() << "Unknown blend method" << webpiter.blend_method; + painter.setCompositionMode(QPainter::CompositionMode_Source); + break; + } + } + previousrect = QRectF(webpiter.x_offset, webpiter.y_offset, webpiter.width, webpiter.height); + painter.drawImage(previousrect, frame); + painter.end(); + + if (framecounter == m_currentimage) { + // bound to reasonable limits + m_imagedelay = qBound(10, webpiter.duration, 10000); break; } - case WEBP_MUX_NO_BLEND: { - m_lastframe = *image; - break; - } - default: { - kWarning() << "Unknown blend method" << webpiter.blend_method; - break; - } + framecounter++; } + *image = buffer; m_currentimage++; if (m_currentimage >= m_imagecount) { diff --git a/kimgio/webp.h b/kimgio/webp.h index 3f1353f8..4c7b498c 100644 --- a/kimgio/webp.h +++ b/kimgio/webp.h @@ -53,7 +53,6 @@ private: int m_imagecount; int m_imagedelay; int m_currentimage; - QImage m_lastframe; };