Add animation support to SkWebpCodec

TBR=reed@google.com
(No change to the public API, but changed a header file)

SkWebpCodec:
- Implement onGetFrameCount, onGetFrameInfo, and onGetRepetitionCount
- Respect the alpha reported by libwebp. Although the spec states that
  it is only a hint, the libwebp encoder uses it properly. Respecting
  allows us to draw opaque images faster and decode them to 565. This
  also matches other SkCodecs (and Chromium).
- onGetPixels:
  - Decode the frame requested, recursively decoding required frame if
    necessary
  - When blending with a prior frame, use SkRasterPipeline

SkCodec:
- Move check for negative index to getFrameInfo
- Reset the colorXform if one is not needed

SkCodecAnimation:
- Add new blend enum, for WebP's (and APNG's) non-blending option

SkFrameHolder:
- New base classes for frames and the owner of the frames, allowing
  code sharing between SkWebpCodec and SkGifCodec (particularly for
  determining whether a frame has alpha and what frame it depends on)
- When moving items from SkGIFFrameContext, use Skia conventions (i.e.
  int instead of unsigned)
- Rename "delay time" to "duration", to match e.g. SkFrameInfo::
  fDuration

SkGifImageReader:
- Move pieces to SkFrameHolder, and adapt to changes made in the
  process
- Make setAlphaAndRequiredFrame (now on the base class SkFrameHolder)
  more general to support webp, and add support for frames that do not
  blend
- Change SkGIFFrameContext from a struct to a class, to match how we
  use the distinction elsewhere (i.e. struct is a small object with
  public fields)
- Rework hasTransparentPixel (now hasTransparency, since it returns true
  in some cases where there is not a transparent pixel) to better fit
  with the modified setAlphaAndRequiredFrame. Also be more consistent
  when there is no transparent pixel but no color map.
- Simplify an if condition that was previously simplified in 2d61e717
  but accidentally got reverted in a4db9be6

CodecAnimTest:
- Test new animated webp files
- Rearrange the test to more cleanly print alpha type mismatches for
  the first frame

resources:
- webp-animated.webp
  - animated webp from Chromium
- blendBG.webp
  - new webp file using bits of webp-animated-semitransparent4.webp
    from Chromium
  - tests required frame and alpha when using the non-blending mode
  - frames have the following properties:
    - Frame 0: no alpha, fills screen
    - Frame 1: alpha, fills screen
    - Frame 2: no alpha, fills screen
    - Frame 3: alpha, fills screen, blendBG
    - Frame 4: no alpha, fills screen, blendBG
    - Frame 5: alpha, blendBG
    - Frame 6: covers 4, has alpha, blendBG
  - also used to test decoding to 565 if the new frame data has alpha
    but blends onto an opaque frame

DM.cpp:
- Test animated images to non-native 8888 and unpremul

DMSrcSink.cpp:
- Do not test non-native 8888 decodes to f16 dst
- Test unpremul decodes to f16
- Copy a frame of an animated image prior to drawing, since in unpremul
  mode, the DM code will premultiply first.

Bug: skia: 3315
Change-Id: I4e55ae2ee5bc095b37a743bdcfac644be603b980
Reviewed-on: https://skia-review.googlesource.com/16707
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Mike Reed <reed@google.com>
Reviewed-by: Leon Scroggins <scroggo@google.com>
Reviewed-by: Matt Sarett <msarett@google.com>
diff --git a/src/codec/SkWebpCodec.cpp b/src/codec/SkWebpCodec.cpp
index ab0b91b..c99d8a7 100644
--- a/src/codec/SkWebpCodec.cpp
+++ b/src/codec/SkWebpCodec.cpp
@@ -5,8 +5,11 @@
  * found in the LICENSE file.
  */
 
+#include "SkBitmap.h"
+#include "SkCanvas.h"
 #include "SkCodecPriv.h"
 #include "SkColorSpaceXform.h"
+#include "SkRasterPipeline.h"
 #include "SkSampler.h"
 #include "SkStreamPriv.h"
 #include "SkTemplates.h"
@@ -32,10 +35,12 @@
     return bytesRead >= 14 && !memcmp(bytes, "RIFF", 4) && !memcmp(&bytes[8], "WEBPVP", 6);
 }
 
+static SkAlphaType alpha_type(bool hasAlpha) {
+    return hasAlpha ? kUnpremul_SkAlphaType : kOpaque_SkAlphaType;
+}
+
 // Parse headers of RIFF container, and check for valid Webp (VP8) content.
-// NOTE: This calls peek instead of read, since onGetPixels will need these
-// bytes again.
-// Returns an SkWebpCodec on success;
+// Returns an SkWebpCodec on success
 SkCodec* SkWebpCodec::NewFromStream(SkStream* stream) {
     std::unique_ptr<SkStream> streamDeleter(stream);
 
@@ -90,8 +95,6 @@
     }
 
     // Get the first frame and its "features" to determine the color and alpha types.
-    // Since we do not yet support animated webp, this is the only frame that we will
-    // decode.
     WebPIterator frame;
     SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame);
     if (!WebPDemuxGetFrame(demux, 1, &frame)) {
@@ -104,23 +107,32 @@
         return nullptr;
     }
 
+    const bool hasAlpha = SkToBool(frame.has_alpha)
+            || frame.width != width || frame.height != height;
     SkEncodedInfo::Color color;
     SkEncodedInfo::Alpha alpha;
     switch (features.format) {
         case 0:
             // This indicates a "mixed" format.  We could see this for
             // animated webps (multiple fragments).
-            // I believe that this is a rare case.
             // We could also guess kYUV here, but I think it makes more
             // sense to guess kBGRA which is likely closer to the final
             // output.  Otherwise, we might end up converting
             // BGRA->YUVA->BGRA.
-            color = SkEncodedInfo::kBGRA_Color;
-            alpha = SkEncodedInfo::kUnpremul_Alpha;
+            // Fallthrough:
+        case 2:
+            // This is the lossless format (BGRA).
+            if (hasAlpha) {
+                color = SkEncodedInfo::kBGRA_Color;
+                alpha = SkEncodedInfo::kUnpremul_Alpha;
+            } else {
+                color = SkEncodedInfo::kBGRX_Color;
+                alpha = SkEncodedInfo::kOpaque_Alpha;
+            }
             break;
         case 1:
             // This is the lossy format (YUV).
-            if (SkToBool(features.has_alpha) || frame.width != width || frame.height != height) {
+            if (hasAlpha) {
                 color = SkEncodedInfo::kYUVA_Color;
                 alpha = SkEncodedInfo::kUnpremul_Alpha;
             } else {
@@ -128,11 +140,6 @@
                 alpha = SkEncodedInfo::kOpaque_Alpha;
             }
             break;
-        case 2:
-            // This is the lossless format (BGRA).
-            color = SkEncodedInfo::kBGRA_Color;
-            alpha = SkEncodedInfo::kUnpremul_Alpha;
-            break;
         default:
             return nullptr;
     }
@@ -160,8 +167,9 @@
             && dim.height() >= 1 && dim.height() <= info.height();
 }
 
-static WEBP_CSP_MODE webp_decode_mode(SkColorType ct, bool premultiply) {
-    switch (ct) {
+static WEBP_CSP_MODE webp_decode_mode(const SkImageInfo& info) {
+    const bool premultiply = info.alphaType() == kPremul_SkAlphaType;
+    switch (info.colorType()) {
         case kBGRA_8888_SkColorType:
             return premultiply ? MODE_bgrA : MODE_BGRA;
         case kRGBA_8888_SkColorType:
@@ -173,6 +181,12 @@
     }
 }
 
+SkWebpCodec::Frame* SkWebpCodec::FrameHolder::appendNewFrame(bool hasAlpha) {
+    const int i = this->size();
+    fFrames.emplace_back(i, hasAlpha);
+    return &fFrames[i];
+}
+
 bool SkWebpCodec::onGetValidSubset(SkIRect* desiredSubset) const {
     if (!desiredSubset) {
         return false;
@@ -191,13 +205,210 @@
     return true;
 }
 
+int SkWebpCodec::onGetRepetitionCount() {
+    auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS);
+    if (!(flags & ANIMATION_FLAG)) {
+        return 0;
+    }
+
+    const int repCount = WebPDemuxGetI(fDemux.get(), WEBP_FF_LOOP_COUNT);
+    if (0 == repCount) {
+        return kRepetitionCountInfinite;
+    }
+
+    return repCount;
+}
+
+int SkWebpCodec::onGetFrameCount() {
+    auto flags = WebPDemuxGetI(fDemux.get(), WEBP_FF_FORMAT_FLAGS);
+    if (!(flags & ANIMATION_FLAG)) {
+        return 1;
+    }
+
+    const uint32_t oldFrameCount = fFrameHolder.size();
+    if (fFailed) {
+        return oldFrameCount;
+    }
+
+    const uint32_t frameCount = WebPDemuxGetI(fDemux, WEBP_FF_FRAME_COUNT);
+    if (oldFrameCount == frameCount) {
+        // We have already parsed this.
+        return frameCount;
+    }
+
+    fFrameHolder.reserve(frameCount);
+
+    for (uint32_t i = oldFrameCount; i < frameCount; i++) {
+        WebPIterator iter;
+        SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoIter(&iter);
+
+        if (!WebPDemuxGetFrame(fDemux.get(), i + 1, &iter)) {
+            fFailed = true;
+            break;
+        }
+
+        // libwebp only reports complete frames of an animated image.
+        SkASSERT(iter.complete);
+
+        Frame* frame = fFrameHolder.appendNewFrame(iter.has_alpha);
+        frame->setXYWH(iter.x_offset, iter.y_offset, iter.width, iter.height);
+        frame->setDisposalMethod(iter.dispose_method == WEBP_MUX_DISPOSE_BACKGROUND ?
+                SkCodecAnimation::RestoreBGColor_DisposalMethod :
+                SkCodecAnimation::Keep_DisposalMethod);
+        frame->setDuration(iter.duration);
+        if (WEBP_MUX_BLEND != iter.blend_method) {
+            frame->setBlend(SkCodecAnimation::Blend::kBG);
+        }
+        fFrameHolder.setAlphaAndRequiredFrame(frame);
+    }
+
+    return fFrameHolder.size();
+
+}
+
+const SkFrame* SkWebpCodec::FrameHolder::onGetFrame(int i) const {
+    return static_cast<const SkFrame*>(this->frame(i));
+}
+
+const SkWebpCodec::Frame* SkWebpCodec::FrameHolder::frame(int i) const {
+    SkASSERT(i >= 0 && i < this->size());
+    return &fFrames[i];
+}
+
+bool SkWebpCodec::onGetFrameInfo(int i, FrameInfo* frameInfo) const {
+    if (i >= fFrameHolder.size()) {
+        return false;
+    }
+
+    const Frame* frame = fFrameHolder.frame(i);
+    if (!frame) {
+        return false;
+    }
+
+    if (frameInfo) {
+        frameInfo->fRequiredFrame = frame->getRequiredFrame();
+        frameInfo->fDuration = frame->getDuration();
+        // libwebp only reports fully received frames for an
+        // animated image.
+        frameInfo->fFullyReceived = true;
+        frameInfo->fAlphaType = alpha_type(frame->hasAlpha());
+    }
+
+    return true;
+}
+
+static bool is_8888(SkColorType colorType) {
+    switch (colorType) {
+        case kRGBA_8888_SkColorType:
+        case kBGRA_8888_SkColorType:
+            return true;
+        default:
+            return false;
+    }
+}
+
+static void pick_memory_stages(SkColorType ct, SkRasterPipeline::StockStage* load,
+                                               SkRasterPipeline::StockStage* store) {
+    switch(ct) {
+        case kUnknown_SkColorType:
+        case kAlpha_8_SkColorType:
+        case kARGB_4444_SkColorType:
+        case kIndex_8_SkColorType:
+        case kGray_8_SkColorType:
+            SkASSERT(false);
+            break;
+        case kRGB_565_SkColorType:
+            if (load) *load = SkRasterPipeline::load_565;
+            if (store) *store = SkRasterPipeline::store_565;
+            break;
+        case kRGBA_8888_SkColorType:
+        case kBGRA_8888_SkColorType:
+            if (load) *load = SkRasterPipeline::load_8888;
+            if (store) *store = SkRasterPipeline::store_8888;
+            break;
+        case kRGBA_F16_SkColorType:
+            if (load) *load = SkRasterPipeline::load_f16;
+            if (store) *store = SkRasterPipeline::store_f16;
+            break;
+    }
+}
+
+static void blend_line(SkColorType dstCT, void* dst,
+                       SkColorType srcCT, void* src,
+                       bool needsSrgbToLinear, SkAlphaType at,
+                       int width) {
+    // Setup conversion from the source and dest, which will be the same.
+    SkRasterPipeline convert_to_linear_premul;
+    if (needsSrgbToLinear) {
+        convert_to_linear_premul.append_from_srgb(at);
+    }
+    if (kUnpremul_SkAlphaType == at) {
+        // srcover assumes premultiplied inputs.
+        convert_to_linear_premul.append(SkRasterPipeline::premul);
+    }
+
+    SkRasterPipeline p;
+    SkRasterPipeline::StockStage load_dst, store_dst;
+    pick_memory_stages(dstCT, &load_dst, &store_dst);
+
+    // Load the final dst.
+    p.append(load_dst, dst);
+    p.extend(convert_to_linear_premul);
+    p.append(SkRasterPipeline::move_src_dst);
+
+    // Load the src.
+    SkRasterPipeline::StockStage load_src;
+    pick_memory_stages(srcCT, &load_src, nullptr);
+    p.append(load_src, src);
+    if (dstCT != srcCT) {
+        SkASSERT(kBGRA_8888_SkColorType == srcCT);
+        p.append(SkRasterPipeline::swap_rb);
+    }
+    p.extend(convert_to_linear_premul);
+
+    p.append(SkRasterPipeline::srcover);
+
+    // Convert back to dst.
+    if (kUnpremul_SkAlphaType == at) {
+        p.append(SkRasterPipeline::unpremul);
+    }
+    if (needsSrgbToLinear) {
+        p.append(SkRasterPipeline::to_srgb);
+    }
+    p.append(store_dst, dst);
+
+    p.run(0, width);
+}
+
 SkCodec::Result SkWebpCodec::onGetPixels(const SkImageInfo& dstInfo, void* dst, size_t rowBytes,
                                          const Options& options, SkPMColor*, int*,
                                          int* rowsDecodedPtr) {
-    if (!conversion_possible(dstInfo, this->getInfo()) ||
-        !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+    // Ensure that we have parsed this far.
+    const int index = options.fFrameIndex;
+    if (index >= this->onGetFrameCount()) {
+        return kIncompleteInput;
+    }
+
+    const auto& srcInfo = this->getInfo();
     {
-        return kInvalidConversion;
+        auto info = srcInfo;
+        if (index > 0) {
+            auto alphaType = alpha_type(fFrameHolder.frame(index)->hasAlpha());
+            info = info.makeAlphaType(alphaType);
+        }
+        if (!conversion_possible(dstInfo, info) ||
+            !this->initializeColorXform(dstInfo, options.fPremulBehavior))
+        {
+            return kInvalidConversion;
+        }
+    }
+
+    if (index > 0 && (options.fSubset || dstInfo.dimensions() != srcInfo.dimensions())) {
+        // Subsetting and scaling are tricky when asking for frames beyond frame 0. In order to
+        // support it, we'll need to determine the proper rectangle for a
+        // WEBP_MUX_DISPOSE_BACKGROUND required frame before erasing it. (Currently the order
+        // is backwards.) Disable until it becomes clear that supporting it is important.
+        return kUnimplemented;
     }
 
     WebPDecoderConfig config;
@@ -212,16 +423,46 @@
 
     WebPIterator frame;
     SkAutoTCallVProc<WebPIterator, WebPDemuxReleaseIterator> autoFrame(&frame);
-    // If this succeeded in NewFromStream(), it should succeed again here.
-    SkAssertResult(WebPDemuxGetFrame(fDemux, 1, &frame));
+    // If this succeeded in onGetFrameCount(), it should succeed again here.
+    SkAssertResult(WebPDemuxGetFrame(fDemux, index + 1, &frame));
 
+    const int requiredFrame = index == 0 ? kNone : fFrameHolder.frame(index)->getRequiredFrame();
     // Get the frameRect.  libwebp will have already signaled an error if this is not fully
     // contained by the canvas.
     auto frameRect = SkIRect::MakeXYWH(frame.x_offset, frame.y_offset, frame.width, frame.height);
-    SkASSERT(this->getInfo().bounds().contains(frameRect));
-    bool frameIsSubset = frameRect.size() != this->getInfo().dimensions();
-    if (frameIsSubset) {
-        SkSampler::Fill(dstInfo, dst, rowBytes, 0, options.fZeroInitialized);
+    SkASSERT(srcInfo.bounds().contains(frameRect));
+    const bool frameIsSubset = frameRect != srcInfo.bounds();
+    if (kNone == requiredFrame) {
+        if (frameIsSubset) {
+            SkSampler::Fill(dstInfo, dst, rowBytes, 0, options.fZeroInitialized);
+        }
+    } else {
+        if (!options.fHasPriorFrame) {
+            Options prevFrameOpts(options);
+            prevFrameOpts.fFrameIndex = requiredFrame;
+            const auto result = this->getPixels(dstInfo, dst, rowBytes, &prevFrameOpts,
+                                                nullptr, nullptr);
+            switch (result) {
+                case kSuccess:
+                    break;
+                case kIncompleteInput:
+                    return kInvalidInput;
+                default:
+                    return result;
+            }
+        }
+
+        // Dispose bg color
+        const Frame* priorFrame = fFrameHolder.frame(requiredFrame);
+        if (priorFrame->getDisposalMethod() == SkCodecAnimation::RestoreBGColor_DisposalMethod) {
+            // FIXME: If we add support for scaling/subsets, this rectangle needs to be adjusted.
+            const auto priorRect = priorFrame->frameRect();
+            const auto info = dstInfo.makeWH(priorRect.width(), priorRect.height());
+            const size_t bpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
+            const size_t offset = priorRect.x() * bpp + priorRect.y() * rowBytes;
+            auto* eraseDst = SkTAddOffset<void>(dst, offset);
+            SkSampler::Fill(info, eraseDst, rowBytes, 0, kNo_ZeroInitialized);
+        }
     }
 
     int dstX = frameRect.x();
@@ -265,7 +506,7 @@
     // Ignore the frame size and offset when determining if scaling is necessary.
     int scaledWidth = subsetWidth;
     int scaledHeight = subsetHeight;
-    SkISize srcSize = options.fSubset ? options.fSubset->size() : this->getInfo().dimensions();
+    SkISize srcSize = options.fSubset ? options.fSubset->size() : srcInfo.dimensions();
     if (srcSize != dstInfo.dimensions()) {
         config.options.use_scaling = 1;
 
@@ -291,32 +532,55 @@
         config.options.scaled_height = scaledHeight;
     }
 
-    // Swizzling between RGBA and BGRA is zero cost in a color transform.  So when we have a
-    // color transform, we should decode to whatever is easiest for libwebp, and then let the
-    // color transform swizzle if necessary.
-    // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost).  Lossless webp is
-    // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA.
-    config.output.colorspace = this->colorXform() ? MODE_BGRA :
-            webp_decode_mode(dstInfo.colorType(), dstInfo.alphaType() == kPremul_SkAlphaType);
+    const bool blendWithPrevFrame = requiredFrame != kNone && frame.blend_method == WEBP_MUX_BLEND
+        && frame.has_alpha;
+    if (blendWithPrevFrame && options.fPremulBehavior == SkTransferFunctionBehavior::kRespect) {
+        // Blending is done with SkRasterPipeline, which requires a color space that is valid for
+        // rendering.
+        const auto* cs = dstInfo.colorSpace();
+        if (!cs || (!cs->gammaCloseToSRGB() && !cs->gammaIsLinear())) {
+            return kInvalidConversion;
+        }
+    }
+
+    SkBitmap webpDst;
+    if ((this->colorXform() && !is_8888(dstInfo.colorType())) || blendWithPrevFrame) {
+        // Swizzling between RGBA and BGRA is zero cost in a color transform.  So when we have a
+        // color transform, we should decode to whatever is easiest for libwebp, and then let the
+        // color transform swizzle if necessary.
+        // Lossy webp is encoded as YUV (so RGBA and BGRA are the same cost).  Lossless webp is
+        // encoded as BGRA. This means decoding to BGRA is either faster or the same cost as RGBA.
+        auto info = dstInfo.makeColorType(kBGRA_8888_SkColorType);
+        if (info.alphaType() == kPremul_SkAlphaType && this->colorXform()) {
+            info = info.makeAlphaType(kUnpremul_SkAlphaType);
+        }
+
+        // We will decode the entire image and then perform the color transform.  libwebp
+        // does not provide a row-by-row API.  This is a shame particularly when we do not want
+        // 8888, since we will need to create another image sized buffer.
+        webpDst.allocPixels(info);
+    } else {
+        // libwebp can decode directly into the output memory.
+        auto info = dstInfo;
+        if (this->colorXform()) {
+            SkASSERT(is_8888(dstInfo.colorType()));
+            // As above, BGRA is faster or the same cost as RGBA for libwebp,
+            // and we're going to transform, so tell libwebp to use BGRA.
+            info = info.makeColorType(kBGRA_8888_SkColorType);
+        }
+        webpDst.installPixels(info, dst, rowBytes);
+    }
+
+    if (!frame.has_alpha) {
+        webpDst.setAlphaType(kOpaque_SkAlphaType);
+    }
+
+    config.output.colorspace = webp_decode_mode(webpDst.info());
     config.output.is_external_memory = 1;
 
-    // We will decode the entire image and then perform the color transform.  libwebp
-    // does not provide a row-by-row API.  This is a shame particularly when we do not want
-    // 8888, since we will need to create another image sized buffer.
-    SkAutoTMalloc<uint32_t> pixels;
-    bool needsCopy = this->colorXform() && kRGBA_8888_SkColorType != dstInfo.colorType() &&
-                                           kBGRA_8888_SkColorType != dstInfo.colorType();
-    void* webpDst = needsCopy ? pixels.reset(dstInfo.width() * dstInfo.height()) : dst;
-    size_t webpRowBytes = needsCopy ? dstInfo.width() * sizeof(uint32_t) : rowBytes;
-    size_t totalBytes = needsCopy ? webpRowBytes * dstInfo.height()
-                                  : dstInfo.getSafeSize(webpRowBytes);
-    size_t dstBpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
-    size_t webpBpp = needsCopy ? sizeof(uint32_t) : dstBpp;
-
-    size_t offset = dstX * webpBpp + dstY * webpRowBytes;
-    config.output.u.RGBA.rgba = SkTAddOffset<uint8_t>(webpDst, offset);
-    config.output.u.RGBA.stride = (int) webpRowBytes;
-    config.output.u.RGBA.size = totalBytes - offset;
+    config.output.u.RGBA.rgba = reinterpret_cast<uint8_t*>(webpDst.getAddr(dstX, dstY));
+    config.output.u.RGBA.stride = static_cast<int>(webpDst.rowBytes());
+    config.output.u.RGBA.size = webpDst.getSafeSize();
 
     SkAutoTCallVProc<WebPIDecoder, WebPIDelete> idec(WebPIDecode(nullptr, 0, &config));
     if (!idec) {
@@ -339,21 +603,55 @@
             return kInvalidInput;
     }
 
+    // We're only transforming the new part of the frame, so no need to worry about the
+    // final composited alpha.
+    const auto srcAlpha = 0 == index ? srcInfo.alphaType() : alpha_type(frame.has_alpha);
+    const auto xformAlphaType = select_xform_alpha(dstInfo.alphaType(), srcAlpha);
+    const bool needsSrgbToLinear = dstInfo.gammaCloseToSRGB() &&
+            options.fPremulBehavior == SkTransferFunctionBehavior::kRespect;
+
+    const size_t dstBpp = SkColorTypeBytesPerPixel(dstInfo.colorType());
+    dst = SkTAddOffset<void>(dst, dstBpp * dstX + rowBytes * dstY);
+    const size_t srcRowBytes = config.output.u.RGBA.stride;
+
+    const auto dstCT = dstInfo.colorType();
     if (this->colorXform()) {
-        SkColorSpaceXform::ColorFormat dstColorFormat = select_xform_format(dstInfo.colorType());
-        SkAlphaType xformAlphaType = select_xform_alpha(dstInfo.alphaType(),
-                                                        this->getInfo().alphaType());
+        const auto dstColorFormat = select_xform_format(dstInfo.colorType());
+        const auto srcColorFormat = SkColorSpaceXform::kBGRA_8888_ColorFormat;
+        SkASSERT(select_xform_format(webpDst.colorType()) == srcColorFormat);
 
         uint32_t* xformSrc = (uint32_t*) config.output.u.RGBA.rgba;
-        void* xformDst = SkTAddOffset<void>(dst, dstBpp * dstX + rowBytes * dstY);
-        size_t srcRowBytes = config.output.u.RGBA.stride;
+        SkBitmap tmp;
+        void* xformDst;
+
+        if (blendWithPrevFrame) {
+            // Xform into temporary bitmap big enough for one row.
+            tmp.allocPixels(dstInfo.makeWH(scaledWidth, 1));
+            xformDst = tmp.getPixels();
+        } else {
+            xformDst = dst;
+        }
         for (int y = 0; y < rowsDecoded; y++) {
             SkAssertResult(this->colorXform()->apply(dstColorFormat, xformDst,
-                    SkColorSpaceXform::kBGRA_8888_ColorFormat, xformSrc, scaledWidth,
-                    xformAlphaType));
-            xformDst = SkTAddOffset<void>(xformDst, rowBytes);
+                    srcColorFormat, xformSrc, scaledWidth, xformAlphaType));
+            if (blendWithPrevFrame) {
+                blend_line(dstCT, &dst, dstCT, &xformDst, needsSrgbToLinear, xformAlphaType,
+                        scaledWidth);
+                dst = SkTAddOffset<void>(dst, rowBytes);
+            } else {
+                xformDst = SkTAddOffset<void>(xformDst, rowBytes);
+            }
             xformSrc = SkTAddOffset<uint32_t>(xformSrc, srcRowBytes);
         }
+    } else if (blendWithPrevFrame) {
+        const uint8_t* src = config.output.u.RGBA.rgba;
+
+        for (int y = 0; y < rowsDecoded; y++) {
+            blend_line(dstCT, &dst, webpDst.colorType(), &src, needsSrgbToLinear,
+                    xformAlphaType, scaledWidth);
+            src = SkTAddOffset<const uint8_t>(src, srcRowBytes);
+            dst = SkTAddOffset<void>(dst, rowBytes);
+        }
     }
 
     return result;
@@ -365,4 +663,7 @@
     : INHERITED(width, height, info, stream, std::move(colorSpace))
     , fDemux(demux)
     , fData(std::move(data))
-{}
+    , fFailed(false)
+{
+    fFrameHolder.setScreenSize(width, height);
+}