SkWuffsCodec: fix dependent frames

SkWuffsCodec tells wuffs to decode only the current frame's
contribution, and then SkWuffsCodec blends it onto the prior frame (if
any). If there is a prior frame, do not call SkSampler::Fill, and blend
the new frame onto the old, rather than replacing it.

In addition, set rowsDecoded to scaledHeight to work properly for
scaled decodes.

TBR=reed@google.com No change to public API

Bug: skia:8235
Change-Id: I08e89f1083da3f9b98f93d8d2375ce78f0c378f8
Reviewed-on: https://skia-review.googlesource.com/c/171645
Commit-Queue: Leon Scroggins <scroggo@google.com>
Reviewed-by: Nigel Tao <nigeltao@google.com>
diff --git a/include/codec/SkCodec.h b/include/codec/SkCodec.h
index 623a35f..201cd74 100644
--- a/include/codec/SkCodec.h
+++ b/include/codec/SkCodec.h
@@ -785,6 +785,14 @@
 
     virtual int onOutputScanline(int inputScanline) const;
 
+    /**
+     *  Return whether we can convert to dst.
+     *
+     *  Will be called for the appropriate frame, prior to initializing the colorXform.
+     */
+    virtual bool conversionSupported(const SkImageInfo& dst, bool srcIsOpaque,
+                                     bool needsColorXform);
+
     // Some classes never need a colorXform e.g.
     // - ICO uses its embedded codec's colorXform
     // - WBMP is just Black/White
@@ -831,14 +839,6 @@
 
     bool                               fStartedIncrementalDecode;
 
-    /**
-     *  Return whether we can convert to dst.
-     *
-     *  Will be called for the appropriate frame, prior to initializing the colorXform.
-     */
-    virtual bool conversionSupported(const SkImageInfo& dst, bool srcIsOpaque,
-                                     bool needsColorXform);
-
     bool initializeColorXform(const SkImageInfo& dstInfo, SkEncodedInfo::Alpha, bool srcIsOpaque);
 
     /**
diff --git a/src/codec/SkWuffsCodec.cpp b/src/codec/SkWuffsCodec.cpp
index 4d2bbe7..090c234 100644
--- a/src/codec/SkWuffsCodec.cpp
+++ b/src/codec/SkWuffsCodec.cpp
@@ -173,6 +173,7 @@
     bool                 onGetFrameInfo(int, FrameInfo*) const override;
     int                  onGetRepetitionCount() override;
     SkSampler*           getSampler(bool createIfNecessary) override;
+    bool                 conversionSupported(const SkImageInfo& dst, bool, bool) override;
 
     void   readFrames();
     Result seekFrame(int frameIndex);
@@ -389,6 +390,26 @@
     return SkCodec::kSuccess;
 }
 
+static bool independent_frame(SkCodec* codec, int frameIndex) {
+    if (frameIndex == 0) {
+        return true;
+    }
+
+    SkCodec::FrameInfo frameInfo;
+    SkAssertResult(codec->getFrameInfo(frameIndex, &frameInfo));
+    return frameInfo.fRequiredFrame == SkCodec::kNoFrame;
+}
+
+static void blend(uint32_t* dst, const uint32_t* src, int width) {
+    while (width --> 0) {
+        if (*src != 0) {
+            *dst = *src;
+        }
+        src++;
+        dst++;
+    }
+}
+
 SkCodec::Result SkWuffsCodec::onIncrementalDecode(int* rowsDecoded) {
     if (!fIncrDecDst) {
         return SkCodec::kInternalError;
@@ -418,6 +439,7 @@
         return SkCodec::kErrorInInput;
     }
 
+    const bool independent = independent_frame(this, options().fFrameIndex);
     wuffs_base__rect_ie_u32 r = fFrameConfig.bounds();
     if (!fSwizzler) {
         SkIRect swizzleRect = SkIRect::MakeLTRB(r.min_incl_x, 0, r.max_excl_x, 1);
@@ -426,9 +448,12 @@
         fSwizzler->setSampleX(fSpySampler.sampleX());
         fSwizzler->setSampleY(fSpySampler.sampleY());
 
-        auto fillInfo = dstInfo().makeWH(
-            fSwizzler->fillWidth(), get_scaled_dimension(dstInfo().height(), fSwizzler->sampleY()));
-        SkSampler::Fill(fillInfo, fIncrDecDst, fIncrDecRowBytes, options().fZeroInitialized);
+        if (independent) {
+            auto fillInfo = dstInfo().makeWH(fSwizzler->fillWidth(),
+                                             get_scaled_dimension(this->dstInfo().height(),
+                                                                  fSwizzler->sampleY()));
+            SkSampler::Fill(fillInfo, fIncrDecDst, fIncrDecRowBytes, options().fZeroInitialized);
+        }
     }
 
     wuffs_base__slice_u8 palette = fPixelBuffer.palette();
@@ -439,6 +464,10 @@
         fColorTable[i] = proc(p[3], p[2], p[1], p[0]);
     }
 
+    std::unique_ptr<uint8_t[]> tmpBuffer;
+    if (!independent) {
+        tmpBuffer.reset(new uint8_t[dstInfo().minRowBytes()]);
+    }
     wuffs_base__table_u8 pixels = fPixelBuffer.plane(0);
     const int            sampleY = fSwizzler->sampleY();
     const int            scaledHeight = get_scaled_dimension(dstInfo().height(), sampleY);
@@ -468,7 +497,16 @@
         // To get from the start (in the X-direction) of the image to the start
         // of the frame, we adjust s by (r.min_incl_x * src_bpp).
         uint8_t* s = pixels.ptr + (y * pixels.stride) + (r.min_incl_x * src_bpp);
-        fSwizzler->swizzle(d, s);
+        if (independent) {
+            fSwizzler->swizzle(d, s);
+        } else {
+            SkASSERT(tmpBuffer.get());
+            fSwizzler->swizzle(tmpBuffer.get(), s);
+            d = SkTAddOffset<uint8_t>(d, fSwizzler->swizzleOffsetBytes());
+            const auto* swizzled = SkTAddOffset<uint32_t>(tmpBuffer.get(),
+                                                          fSwizzler->swizzleOffsetBytes());
+            blend(reinterpret_cast<uint32_t*>(d), swizzled, fSwizzler->swizzleWidth());
+        }
     }
 
     // The semantics of *rowsDecoded is: say you have a 10 pixel high image
@@ -484,8 +522,12 @@
     // interlaced), we just zero-initialize all 10 rows ahead of time and
     // return the height of the image, so the caller knows it doesn't need to
     // do anything.
+    //
+    // Similarly, if the output is scaled, we zero-initialized all
+    // |scaledHeight| rows (the scaled image height), so we inform the caller
+    // that it doesn't need to do anything.
     if (rowsDecoded) {
-        *rowsDecoded = static_cast<int>(fPixelBuffer.pixcfg.height());
+        *rowsDecoded = scaledHeight;
     }
 
     if (result == SkCodec::kSuccess) {
@@ -543,6 +585,22 @@
     return nullptr;
 }
 
+bool SkWuffsCodec::conversionSupported(const SkImageInfo& dst, bool srcIsOpaque, bool needsColorXform) {
+    if (!this->INHERITED::conversionSupported(dst, srcIsOpaque, needsColorXform)) {
+        return false;
+    }
+
+    switch (dst.colorType()) {
+        case kRGBA_8888_SkColorType:
+        case kBGRA_8888_SkColorType:
+            return true;
+        default:
+            // FIXME: Add skcms to support F16
+            // FIXME: Add support for 565 on the first frame
+            return false;
+    }
+}
+
 void SkWuffsCodec::readFrames() {
     size_t n = fFrames.size();
     int    i = n ? n - 1 : 0;