WEBPImageDecoder.cpp   [plain text]


/*
 * Copyright (C) 2010 Google Inc. All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1.  Redistributions of source code must retain the above copyright
 *     notice, this list of conditions and the following disclaimer.
 * 2.  Redistributions in binary form must reproduce the above copyright
 *     notice, this list of conditions and the following disclaimer in the
 *     documentation and/or other materials provided with the distribution.
 * 3.  Neither the name of Apple Inc. ("Apple") nor the names of
 *     its contributors may be used to endorse or promote products derived
 *     from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY APPLE AND ITS CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL APPLE OR ITS CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include "config.h"
#include "WEBPImageDecoder.h"
#include <wtf/UniqueArray.h>

#if USE(WEBP)

namespace WebCore {

// Convenience function to improve code readability, as WebPDemuxGetFrame is +1 based.
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;

    // We need to ensure that the header is parsed everytime new data arrives, as the number
    // of frames may change until we have the complete data. If the size has not been obtained
    // yet, the call to ScalableImageDecoder::setData() will call parseHeader() and set
    // m_headerParsed to true, so the call to parseHeader() at the end won't do anything. If the size
    // is available, then parseHeader() is only called once.
    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;

    // The size of m_frameBufferCache may be smaller than the index requested. This can happen
    // because new data may have arrived with a bigger frameCount, but decode() hasn't been called
    // yet, which is the one that resizes the cache.
    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)
{
    // The first frame doesn't depend on any other.
    if (!frameIndex)
        return 0;

    // Go backwards and find the first complete frame.
    size_t firstIncompleteFrame = frameIndex;
    for (; firstIncompleteFrame; --firstIncompleteFrame) {
        if (m_frameBufferCache[firstIncompleteFrame - 1].isComplete())
            break;
    }

    // Check if there are any independent frames between firstIncompleteFrame and frameIndex.
    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;

        // This frame covers the whole area and doesn't have alpha, so it can be rendered without
        // dependencies.
        if (!webpFrame.has_alpha)
            return firstIndependentFrame;

        // This frame covers the whole area and its disposalMethod is RestoreToBackground, which means
        // that the next frame will be rendered on top of a transparent background, and can be decoded
        // without dependencies. This can only be checked for frames prior to frameIndex.
        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;

    // This can be executed both in the main thread (when not using async decoding) or in the decoding thread.
    // When executed in the decoding thread, a call to setData() from the main thread may change the data
    // the WebPDemuxer is using, leaving it in an inconsistent state, so we need to protect the data.
    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);

    // It is a fatal error if all data is received and we have decoded all frames available but the file is truncated.
    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;
        }
        // Fallthrough.
    default:
        setFailed();
    }

    WebPIDelete(decoder);
}

bool WEBPImageDecoder::initFrameBuffer(size_t frameIndex, const WebPIterator* webpFrame)
{
    if (frameIndex >= frameCount())
        return false;

    auto& buffer = m_frameBufferCache[frameIndex];

    // Initialize the frame rect in our buffer.
    IntRect frameRect(webpFrame->x_offset, webpFrame->y_offset, webpFrame->width, webpFrame->height);

    // Make sure the frameRect doesn't extend outside the buffer.
    frameRect.intersect({ { }, size() });

    if (!frameIndex || !m_frameBufferCache[frameIndex - 1].backingStore()) {
        // This frame doesn't rely on any previous data.
        if (!buffer.initialize(size(), m_premultiplyAlpha))
            return false;
    } else {
        const auto& prevBuffer = m_frameBufferCache[frameIndex - 1];
        ASSERT(prevBuffer.isComplete());

        // Preserve the last frame as the starting state for this frame.
        if (!prevBuffer.backingStore() || !buffer.initialize(*prevBuffer.backingStore()))
            return false;

        if (prevBuffer.disposalMethod() == ScalableImageDecoderFrame::DisposalMethod::RestoreToBackground) {
            // We want to clear the previous frame to transparent, without
            // affecting pixels in the image outside of the frame.
            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; // See also https://bugs.webkit.org/show_bug.cgi?id=74062
    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; // RIFF_HEADER_SIZE + CHUNK_HEADER_SIZE + VP8_FRAME_HEADER_SIZE
    if (m_data->size() < webpHeaderSize)
        return; // Await VP8X header so WebPDemuxPartial succeeds.

    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; // Wait until the encoded image frame data arrives.
    }

    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 {
        // Since we have parsed at least one frame, even if partially,
        // the global animation (ANIM) properties have been read since
        // an ANIM chunk must precede the ANMF frame chunks.
        m_repetitionCount = WebPDemuxGetI(demuxer, WEBP_FF_LOOP_COUNT);
        ASSERT(m_repetitionCount == (m_repetitionCount & 0xffff)); // Loop count is always <= 16 bits.
        if (!m_repetitionCount)
            m_repetitionCount = WebCore::RepetitionCountInfinite;
    }

    WebPDemuxDelete(demuxer);
}

void WEBPImageDecoder::clearFrameBufferCache(size_t clearBeforeFrame)
{
    if (m_frameBufferCache.isEmpty())
        return;

    // We don't want to delete the last frame in the cache, as is may be needed for
    // decoding when new data arrives. See GIFImageDecoder for the full explanation.
    clearBeforeFrame = std::min(clearBeforeFrame, m_frameBufferCache.size() - 1);

    // Also from GIFImageDecoder: We need to preserve frames such that:
    //   * We don't clear |clearBeforeFrame|.
    //   * We don't clear the frame we're currently decoding.
    //   * We don't clear any frame from which a future initFrameBuffer() call will copy bitmap data.
    //
    // In WEBP every frame depends on the previous one or none. That means that frames after clearBeforeFrame
    // won't need any frame before them to render, so we can clear them all.
    for (int i = clearBeforeFrame - 1; i >= 0; i--) {
        auto& buffer = m_frameBufferCache[i];
        if (!buffer.isInvalid())
            buffer.clear();
    }
}

} // namespace WebCore

#endif