WEBPImageDecoder.cpp [plain text]
#include "config.h"
#include "WEBPImageDecoder.h"
#include <wtf/UniqueArray.h>
#if USE(WEBP)
namespace WebCore {
bool webpFrameAtIndex(WebPDemuxer* demuxer, size_t index, WebPIterator* webpFrame)
{
return WebPDemuxGetFrame(demuxer, index + 1, webpFrame);
}
WEBPImageDecoder::WEBPImageDecoder(AlphaOption alphaOption, GammaAndColorProfileOption gammaAndColorProfileOption)
: ScalableImageDecoder(alphaOption, gammaAndColorProfileOption)
{
}
WEBPImageDecoder::~WEBPImageDecoder() = default;
void WEBPImageDecoder::setData(SharedBuffer& data, bool allDataReceived)
{
if (failed())
return;
m_headerParsed = false;
ScalableImageDecoder::setData(data, allDataReceived);
parseHeader();
}
RepetitionCount WEBPImageDecoder::repetitionCount() const
{
if (failed())
return RepetitionCountOnce;
return m_repetitionCount ? m_repetitionCount : RepetitionCountInfinite;
}
ScalableImageDecoderFrame* WEBPImageDecoder::frameBufferAtIndex(size_t index)
{
if (index >= frameCount())
return 0;
if ((m_frameBufferCache.size() > index) && m_frameBufferCache[index].isComplete())
return &m_frameBufferCache[index];
decode(index, isAllDataReceived());
return &m_frameBufferCache[index];
}
size_t WEBPImageDecoder::findFirstRequiredFrameToDecode(size_t frameIndex, WebPDemuxer* demuxer)
{
if (!frameIndex)
return 0;
size_t firstIncompleteFrame = frameIndex;
for (; firstIncompleteFrame; --firstIncompleteFrame) {
if (m_frameBufferCache[firstIncompleteFrame - 1].isComplete())
break;
}
for (size_t firstIndependentFrame = frameIndex; firstIndependentFrame > firstIncompleteFrame ; --firstIndependentFrame) {
WebPIterator webpFrame;
if (!webpFrameAtIndex(demuxer, firstIndependentFrame, &webpFrame))
continue;
IntRect frameRect(webpFrame.x_offset, webpFrame.y_offset, webpFrame.width, webpFrame.height);
if (!frameRect.contains({ { }, size() }))
continue;
if (!webpFrame.has_alpha)
return firstIndependentFrame;
if (firstIndependentFrame < frameIndex && m_frameBufferCache[firstIndependentFrame].disposalMethod() == ScalableImageDecoderFrame::DisposalMethod::RestoreToBackground)
return firstIndependentFrame + 1;
}
return firstIncompleteFrame;
}
void WEBPImageDecoder::decode(size_t frameIndex, bool allDataReceived)
{
if (failed())
return;
RefPtr<SharedBuffer::DataSegment> protectedData(m_data);
WebPData inputData = { reinterpret_cast<const uint8_t*>(protectedData->data()), protectedData->size() };
WebPDemuxState demuxerState;
WebPDemuxer* demuxer = WebPDemuxPartial(&inputData, &demuxerState);
if (!demuxer) {
setFailed();
return;
}
m_frameBufferCache.resize(m_frameCount);
if (frameIndex >= m_frameBufferCache.size() - 1 && allDataReceived && demuxer && demuxerState != WEBP_DEMUX_DONE) {
WebPDemuxDelete(demuxer);
setFailed();
return;
}
for (size_t i = findFirstRequiredFrameToDecode(frameIndex, demuxer); i <= frameIndex; i++)
decodeFrame(i, demuxer);
WebPDemuxDelete(demuxer);
}
void WEBPImageDecoder::decodeFrame(size_t frameIndex, WebPDemuxer* demuxer)
{
if (failed())
return;
WebPIterator webpFrame;
if (!webpFrameAtIndex(demuxer, frameIndex, &webpFrame))
return;
const uint8_t* dataBytes = reinterpret_cast<const uint8_t*>(webpFrame.fragment.bytes);
size_t dataSize = webpFrame.fragment.size;
bool blend = webpFrame.blend_method == WEBP_MUX_BLEND ? true : false;
ASSERT(m_frameBufferCache.size() > frameIndex);
auto& buffer = m_frameBufferCache[frameIndex];
buffer.setDuration(Seconds::fromMilliseconds(webpFrame.duration));
buffer.setDisposalMethod(webpFrame.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ? ScalableImageDecoderFrame::DisposalMethod::RestoreToBackground : ScalableImageDecoderFrame::DisposalMethod::DoNotDispose);
ASSERT(!buffer.isComplete());
if (buffer.isInvalid() && !initFrameBuffer(frameIndex, &webpFrame)) {
setFailed();
return;
}
WebPDecBuffer decoderBuffer;
WebPInitDecBuffer(&decoderBuffer);
decoderBuffer.colorspace = MODE_RGBA;
decoderBuffer.u.RGBA.stride = webpFrame.width * sizeof(uint32_t);
decoderBuffer.u.RGBA.size = decoderBuffer.u.RGBA.stride * webpFrame.height;
decoderBuffer.is_external_memory = 1;
auto p = makeUniqueArray<uint8_t>(decoderBuffer.u.RGBA.size);
decoderBuffer.u.RGBA.rgba = p.get();
if (!decoderBuffer.u.RGBA.rgba) {
setFailed();
return;
}
WebPIDecoder* decoder = WebPINewDecoder(&decoderBuffer);
if (!decoder) {
setFailed();
return;
}
switch (WebPIUpdate(decoder, dataBytes, dataSize)) {
case VP8_STATUS_OK:
applyPostProcessing(frameIndex, decoder, decoderBuffer, blend);
buffer.setDecodingStatus(DecodingStatus::Complete);
break;
case VP8_STATUS_SUSPENDED:
if (!isAllDataReceived()) {
applyPostProcessing(frameIndex, decoder, decoderBuffer, blend);
buffer.setDecodingStatus(DecodingStatus::Partial);
break;
}
default:
setFailed();
}
WebPIDelete(decoder);
}
bool WEBPImageDecoder::initFrameBuffer(size_t frameIndex, const WebPIterator* webpFrame)
{
if (frameIndex >= frameCount())
return false;
auto& buffer = m_frameBufferCache[frameIndex];
IntRect frameRect(webpFrame->x_offset, webpFrame->y_offset, webpFrame->width, webpFrame->height);
frameRect.intersect({ { }, size() });
if (!frameIndex || !m_frameBufferCache[frameIndex - 1].backingStore()) {
if (!buffer.initialize(size(), m_premultiplyAlpha))
return false;
} else {
const auto& prevBuffer = m_frameBufferCache[frameIndex - 1];
ASSERT(prevBuffer.isComplete());
if (!prevBuffer.backingStore() || !buffer.initialize(*prevBuffer.backingStore()))
return false;
if (prevBuffer.disposalMethod() == ScalableImageDecoderFrame::DisposalMethod::RestoreToBackground) {
const IntRect& prevRect = prevBuffer.backingStore()->frameRect();
buffer.backingStore()->clearRect(prevRect);
}
}
buffer.setHasAlpha(webpFrame->has_alpha);
buffer.backingStore()->setFrameRect(frameRect);
return true;
}
void WEBPImageDecoder::applyPostProcessing(size_t frameIndex, WebPIDecoder* decoder, WebPDecBuffer& decoderBuffer, bool blend)
{
auto& buffer = m_frameBufferCache[frameIndex];
int decodedWidth = 0;
int decodedHeight = 0;
if (!WebPIDecGetRGB(decoder, &decodedHeight, &decodedWidth, 0, 0))
return; if (decodedHeight <= 0)
return;
const IntRect& frameRect = buffer.backingStore()->frameRect();
ASSERT_WITH_SECURITY_IMPLICATION(decodedWidth == frameRect.width());
ASSERT_WITH_SECURITY_IMPLICATION(decodedHeight <= frameRect.height());
const int left = frameRect.x();
const int top = frameRect.y();
for (int y = 0; y < decodedHeight; y++) {
const int canvasY = top + y;
for (int x = 0; x < decodedWidth; x++) {
const int canvasX = left + x;
auto* address = buffer.backingStore()->pixelAt(canvasX, canvasY);
uint8_t* pixel = decoderBuffer.u.RGBA.rgba + (y * frameRect.width() + x) * sizeof(uint32_t);
if (blend && (pixel[3] < 255))
buffer.backingStore()->blendPixel(address, pixel[0], pixel[1], pixel[2], pixel[3]);
else
buffer.backingStore()->setPixel(address, pixel[0], pixel[1], pixel[2], pixel[3]);
}
}
}
void WEBPImageDecoder::parseHeader()
{
if (m_headerParsed)
return;
m_headerParsed = true;
const unsigned webpHeaderSize = 30; if (m_data->size() < webpHeaderSize)
return;
WebPData inputData = { reinterpret_cast<const uint8_t*>(m_data->data()), m_data->size() };
WebPDemuxState demuxerState;
WebPDemuxer* demuxer = WebPDemuxPartial(&inputData, &demuxerState);
if (!demuxer) {
setFailed();
return;
}
m_frameCount = WebPDemuxGetI(demuxer, WEBP_FF_FRAME_COUNT);
if (!m_frameCount) {
WebPDemuxDelete(demuxer);
return; }
int width = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_WIDTH);
int height = WebPDemuxGetI(demuxer, WEBP_FF_CANVAS_HEIGHT);
if (!isSizeAvailable() && !setSize(IntSize(width, height))) {
WebPDemuxDelete(demuxer);
return;
}
m_formatFlags = WebPDemuxGetI(demuxer, WEBP_FF_FORMAT_FLAGS);
if (!(m_formatFlags & ANIMATION_FLAG))
m_repetitionCount = WebCore::RepetitionCountNone;
else {
m_repetitionCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
ASSERT(m_repetitionCount == (m_repetitionCount & 0xffff)); if (!m_repetitionCount)
m_repetitionCount = WebCore::RepetitionCountInfinite;
}
WebPDemuxDelete(demuxer);
}
void WEBPImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame)
{
if (m_frameBufferCache.isEmpty())
return;
clearBeforeFrame = std::min(clearBeforeFrame, m_frameBufferCache.size() - 1);
for (int i = clearBeforeFrame - 1; i >= 0; i--) {
auto& buffer = m_frameBufferCache[i];
if (!buffer.isInvalid())
buffer.clear();
}
}
}
#endif