Extend composeshader to support a lerp parameter

Bug: skia:
Change-Id: I3bbb2cb8d0a84fca0309654498548ebc94d8938f
Reviewed-on: https://skia-review.googlesource.com/18460
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/gm/colorfilterimagefilter.cpp b/gm/colorfilterimagefilter.cpp
index 5ef6379..73b9f20 100644
--- a/gm/colorfilterimagefilter.cpp
+++ b/gm/colorfilterimagefilter.cpp
@@ -223,3 +223,33 @@
         canvas->translate(0, 150);
     }
 }
+
+DEF_SIMPLE_GM(mixershader, canvas, 800, 700) {
+    auto shaderA = GetResourceAsImage("mandrill_128.png")->makeShader(SkShader::kClamp_TileMode,
+                                                                      SkShader::kClamp_TileMode);
+    const SkColor colors[] = { SK_ColorGREEN, 0 };
+    auto shaderB = SkGradientShader::MakeRadial({60, 60}, 55, colors, nullptr, 2,
+                                                SkShader::kClamp_TileMode,
+                                                SkGradientShader::kInterpolateColorsInPremul_Flag,
+                                                nullptr);
+    const SkBlendMode modes[] = {
+        SkBlendMode::kSrc, SkBlendMode::kModulate, SkBlendMode::kColorBurn, SkBlendMode::kPlus,
+        SkBlendMode::kDstATop,
+    };
+    SkPaint paint;
+    SkRect r = SkRect::MakeWH(120, 120);
+
+    canvas->translate(10, 10);
+    for (auto mode : modes) {
+        canvas->save();
+        const int count = 6;
+        for (int x = 0; x < count; ++x) {
+            const float t = x * 1.0f / (count - 1);
+            paint.setShader(SkShader::MakeCompose(shaderA, shaderB, mode, t));
+            canvas->drawRect(r, paint);
+            canvas->translate(r.width() + 10, 0);
+        }
+        canvas->restore();
+        canvas->translate(0, r.height() + 20);
+    }
+}
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 9f14f80..f90e17a 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -201,10 +201,11 @@
     // V51: more SkXfermode -> SkBlendMode
     // V52: Remove SkTextBlob::fRunCount
     // V53: SaveLayerRec clip mask
+    // V54: ComposeShader can use a Mode or a Lerp
 
     // Only SKPs within the min/current picture version range (inclusive) can be read.
     static const uint32_t     MIN_PICTURE_VERSION = 51;     // Produced by Chrome ~M56.
-    static const uint32_t CURRENT_PICTURE_VERSION = 53;
+    static const uint32_t CURRENT_PICTURE_VERSION = 54;
 
     static bool IsValidPictInfo(const SkPictInfo& info);
     static sk_sp<SkPicture> Forwardport(const SkPictInfo&,
diff --git a/include/core/SkShader.h b/include/core/SkShader.h
index 7f78d4b..daed2ba 100644
--- a/include/core/SkShader.h
+++ b/include/core/SkShader.h
@@ -210,7 +210,38 @@
      */
     static sk_sp<SkShader> MakeColorShader(const SkColor4f&, sk_sp<SkColorSpace>);
 
-    static sk_sp<SkShader> MakeComposeShader(sk_sp<SkShader> dst, sk_sp<SkShader> src, SkBlendMode);
+    /**
+     *  Compose two shaders together, using two operators: mode and lerp. The resulting colors
+     *  are computed by first combining the src and dst shaders using mode, and then linearly
+     *  interpolating between the dst and result colors using lerp.
+     *
+     *      result = dst * (1 - lerp) + (src (mode) dst) * lerp
+     *
+     *  If either shader is nullptr, then this returns nullptr.
+     *  If lerp is NaN then this returns nullptr, otherwise lerp is clamped to [0..1].
+     */
+    static sk_sp<SkShader> MakeCompose(sk_sp<SkShader> dst, sk_sp<SkShader> src,
+                                       SkBlendMode mode, float lerp = 1);
+
+    /*
+     *  DEPRECATED: call MakeCompose.
+     */
+    static sk_sp<SkShader> MakeComposeShader(sk_sp<SkShader> dst, sk_sp<SkShader> src,
+                                             SkBlendMode mode) {
+        return MakeCompose(std::move(dst), std::move(src), mode, 1);
+    }
+
+    /**
+     *  Compose two shaders together using a weighted average.
+     *
+     *  result = dst * (1 - lerp) + src * lerp
+     *
+     *  If either shader is nullptr, then this returns nullptr.
+     *  If lerp is NaN then this returns nullptr, otherwise lerp is clamped to [0..1].
+     */
+    static sk_sp<SkShader> MakeMixer(sk_sp<SkShader> dst, sk_sp<SkShader> src, float lerp) {
+        return MakeCompose(std::move(dst), std::move(src), SkBlendMode::kSrc, lerp);
+    }
 
     /** Call this to create a new shader that will draw with the specified bitmap.
      *
diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp
index d0ef2d7..4f7fc13 100644
--- a/src/core/SkDraw_vertices.cpp
+++ b/src/core/SkDraw_vertices.cpp
@@ -247,7 +247,7 @@
             matrix43 = triShader->getMatrix43();
             if (shader) {
                 shader = outerAlloc.make<SkComposeShader>(sk_ref_sp(triShader), sk_ref_sp(shader),
-                                                          bmode);
+                                                          bmode, 1);
             } else {
                 shader = triShader;
             }
diff --git a/src/core/SkReadBuffer.h b/src/core/SkReadBuffer.h
index 4e61050..48cb225 100644
--- a/src/core/SkReadBuffer.h
+++ b/src/core/SkReadBuffer.h
@@ -72,6 +72,7 @@
         kXfermodeToBlendMode2_Version      = 51,
          */
         kTextBlobImplicitRunCount_Version  = 52,
+        kComposeShaderCanLerp_Version      = 54,
     };
 
     /**
diff --git a/src/shaders/SkComposeShader.cpp b/src/shaders/SkComposeShader.cpp
index 75f9cd2..3155784 100644
--- a/src/shaders/SkComposeShader.cpp
+++ b/src/shaders/SkComposeShader.cpp
@@ -17,57 +17,71 @@
 #include "SkString.h"
 #include "../jumper/SkJumper.h"
 
-sk_sp<SkShader> SkShader::MakeComposeShader(sk_sp<SkShader> dst, sk_sp<SkShader> src,
-                                            SkBlendMode mode) {
-    if (!src || !dst) {
+sk_sp<SkShader> SkShader::MakeCompose(sk_sp<SkShader> dst, sk_sp<SkShader> src, SkBlendMode mode,
+                                      float lerpT) {
+    if (!src || !dst || SkScalarIsNaN(lerpT)) {
         return nullptr;
     }
-    if (SkBlendMode::kSrc == mode) {
-        return src;
-    }
-    if (SkBlendMode::kDst == mode) {
+    lerpT = SkScalarPin(lerpT, 0, 1);
+
+    if (lerpT == 0) {
         return dst;
+    } else if (lerpT == 1) {
+        if (mode == SkBlendMode::kSrc) {
+            return src;
+        }
+        if (mode == SkBlendMode::kDst) {
+            return dst;
+        }
     }
-    return sk_sp<SkShader>(new SkComposeShader(std::move(dst), std::move(src), mode));
+    return sk_sp<SkShader>(new SkComposeShader(std::move(dst), std::move(src), mode, lerpT));
 }
 
 ///////////////////////////////////////////////////////////////////////////////
 
 sk_sp<SkFlattenable> SkComposeShader::CreateProc(SkReadBuffer& buffer) {
-    sk_sp<SkShader> shaderA(buffer.readShader());
-    sk_sp<SkShader> shaderB(buffer.readShader());
-    SkBlendMode mode = (SkBlendMode)buffer.read32();
+    sk_sp<SkShader> dst(buffer.readShader());
+    sk_sp<SkShader> src(buffer.readShader());
+    unsigned        mode = buffer.read32();
 
-    if (!shaderA || !shaderB) {
+    float lerp = 1;
+    if (!buffer.isVersionLT(SkReadBuffer::kComposeShaderCanLerp_Version)) {
+        lerp = buffer.readScalar();
+    }
+
+    // check for valid mode before we cast to the enum type
+    if (mode > (unsigned)SkBlendMode::kLastMode) {
         return nullptr;
     }
-    return sk_make_sp<SkComposeShader>(std::move(shaderA), std::move(shaderB), mode);
+
+    return MakeCompose(std::move(dst), std::move(src), static_cast<SkBlendMode>(mode), lerp);
 }
 
 void SkComposeShader::flatten(SkWriteBuffer& buffer) const {
-    buffer.writeFlattenable(fShaderA.get());
-    buffer.writeFlattenable(fShaderB.get());
+    buffer.writeFlattenable(fDst.get());
+    buffer.writeFlattenable(fSrc.get());
     buffer.write32((int)fMode);
+    buffer.writeScalar(fLerpT);
 }
 
 sk_sp<SkShader> SkComposeShader::onMakeColorSpace(SkColorSpaceXformer* xformer) const {
-    return SkShader::MakeComposeShader(xformer->apply(fShaderA.get()),
-                                       xformer->apply(fShaderB.get()), fMode);
+    return MakeCompose(xformer->apply(fDst.get()), xformer->apply(fSrc.get()),
+                       fMode, fLerpT);
 }
 
 bool SkComposeShader::asACompose(ComposeRec* rec) const {
+    if (!this->isJustMode()) {
+        return false;
+    }
+
     if (rec) {
-        rec->fShaderA   = fShaderA.get();
-        rec->fShaderB   = fShaderB.get();
+        rec->fShaderA   = fDst.get();
+        rec->fShaderB   = fSrc.get();
         rec->fBlendMode = fMode;
     }
     return true;
 }
 
-bool SkComposeShader::isRasterPipelineOnly() const {
-    return true;
-}
-
 bool SkComposeShader::onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* dstCS,
                                      SkArenaAlloc* alloc, const SkMatrix& ctm,
                                      const SkPaint& paint, const SkMatrix* localM) const {
@@ -77,27 +91,32 @@
     };
     auto storage = alloc->make<Storage>();
 
-    if (!as_SB(fShaderB)->appendStages(pipeline, dstCS, alloc, ctm, paint, localM)) { // SRC
+    if (!as_SB(fSrc)->appendStages(pipeline, dstCS, alloc, ctm, paint, localM)) {
         return false;
     }
     // This outputs r,g,b,a, which we'll need later when we apply the mode, but we save it off now
     // since fShaderB will overwrite them.
     pipeline->append(SkRasterPipeline::store_rgba, storage->fRGBA);
 
-    if (!as_SB(fShaderA)->appendStages(pipeline, dstCS, alloc, ctm, paint, localM)) {  // DST
+    if (!as_SB(fDst)->appendStages(pipeline, dstCS, alloc, ctm, paint, localM)) {
         return false;
     }
-    // We now have our logical 'dst' in r,g,b,a, but we need it in dr,dg,db,da for the mode
+    // We now have our logical 'dst' in r,g,b,a, but we need it in dr,dg,db,da for the mode/lerp
     // so we have to shuttle them. If we had a stage the would load_into_dst, then we could
     // reverse the two shader invocations, and avoid this move...
     pipeline->append(SkRasterPipeline::move_src_dst);
     pipeline->append(SkRasterPipeline::load_rgba, storage->fRGBA);
 
-    // Idea: should time this, and see if it helps to have custom versions of the overflow modes
-    //       that do their own clamping, avoiding the overhead of an extra stage.
-    SkBlendMode_AppendStages(fMode, pipeline);
-    if (SkBlendMode_CanOverflow(fMode)) {
-        pipeline->append(SkRasterPipeline::clamp_a);
+    if (!this->isJustLerp()) {
+        // Idea: should time this, and see if it helps to have custom versions of the overflow modes
+        //       that do their own clamping, avoiding the overhead of an extra stage.
+        SkBlendMode_AppendStages(fMode, pipeline);
+        if (SkBlendMode_CanOverflow(fMode)) {
+            pipeline->append(SkRasterPipeline::clamp_a);
+        }
+    }
+    if (!this->isJustMode()) {
+        pipeline->append(SkRasterPipeline::lerp_1_float, &fLerpT);
     }
     return true;
 }
@@ -110,29 +129,25 @@
 /////////////////////////////////////////////////////////////////////
 
 sk_sp<GrFragmentProcessor> SkComposeShader::asFragmentProcessor(const AsFPArgs& args) const {
-    switch (fMode) {
-        case SkBlendMode::kClear:
+    if (this->isJustMode()) {
+        SkASSERT(fMode != SkBlendMode::kSrc && fMode != SkBlendMode::kDst); // caught in factory
+        if (fMode == SkBlendMode::kClear) {
             return GrConstColorProcessor::Make(GrColor4f::TransparentBlack(),
                                                GrConstColorProcessor::kIgnore_InputMode);
-            break;
-        case SkBlendMode::kSrc:
-            return as_SB(fShaderB)->asFragmentProcessor(args);
-            break;
-        case SkBlendMode::kDst:
-            return as_SB(fShaderA)->asFragmentProcessor(args);
-            break;
-        default:
-            sk_sp<GrFragmentProcessor> fpA(as_SB(fShaderA)->asFragmentProcessor(args));
-            if (!fpA) {
-                return nullptr;
-            }
-            sk_sp<GrFragmentProcessor> fpB(as_SB(fShaderB)->asFragmentProcessor(args));
-            if (!fpB) {
-                return nullptr;
-            }
-            return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(fpB),
-                                                                      std::move(fpA), fMode);
+        }
     }
+
+    sk_sp<GrFragmentProcessor> fpA(as_SB(fDst)->asFragmentProcessor(args));
+    if (!fpA) {
+        return nullptr;
+    }
+    sk_sp<GrFragmentProcessor> fpB(as_SB(fSrc)->asFragmentProcessor(args));
+    if (!fpB) {
+        return nullptr;
+    }
+    // TODO: account for fLerpT when it is < 1
+    return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(fpB),
+                                                              std::move(fpA), fMode);
 }
 #endif
 
@@ -140,13 +155,12 @@
 void SkComposeShader::toString(SkString* str) const {
     str->append("SkComposeShader: (");
 
-    str->append("ShaderA: ");
-    as_SB(fShaderA)->toString(str);
-    str->append(" ShaderB: ");
-    as_SB(fShaderB)->toString(str);
-    if (SkBlendMode::kSrcOver != fMode) {
-        str->appendf(" Xfermode: %s", SkBlendMode_Name(fMode));
-    }
+    str->append("dst: ");
+    as_SB(fDst)->toString(str);
+    str->append(" src: ");
+    as_SB(fSrc)->toString(str);
+    str->appendf(" mode: %s", SkBlendMode_Name(fMode));
+    str->appendf(" lerpT: %g", fLerpT);
 
     this->INHERITED::toString(str);
 
diff --git a/src/shaders/SkComposeShader.h b/src/shaders/SkComposeShader.h
index 39c43d6..0386100 100644
--- a/src/shaders/SkComposeShader.h
+++ b/src/shaders/SkComposeShader.h
@@ -11,36 +11,22 @@
 #include "SkShaderBase.h"
 #include "SkBlendMode.h"
 
-class SkColorSpacXformer;
-
-///////////////////////////////////////////////////////////////////////////////////////////
-
-/** \class SkComposeShader
-    This subclass of shader returns the composition of two other shaders, combined by
-    a xfermode.
-*/
 class SkComposeShader : public SkShaderBase {
 public:
-    /** Create a new compose shader, given shaders A, B, and a combining xfermode mode.
-        When the xfermode is called, it will be given the result from shader A as its
-        "dst", and the result from shader B as its "src".
-        mode->xfer32(sA_result, sB_result, ...)
-        @param shaderA  The colors from this shader are seen as the "dst" by the xfermode
-        @param shaderB  The colors from this shader are seen as the "src" by the xfermode
-        @param mode     The xfermode that combines the colors from the two shaders. If mode
-                        is null, then SRC_OVER is assumed.
-    */
-    SkComposeShader(sk_sp<SkShader> sA, sk_sp<SkShader> sB, SkBlendMode mode)
-        : fShaderA(std::move(sA))
-        , fShaderB(std::move(sB))
+    SkComposeShader(sk_sp<SkShader> dst, sk_sp<SkShader> src, SkBlendMode mode, float lerpT)
+        : fDst(std::move(dst))
+        , fSrc(std::move(src))
+        , fLerpT(lerpT)
         , fMode(mode)
-    {}
+    {
+        SkASSERT(lerpT >= 0 && lerpT <= 1);
+    }
 
 #if SK_SUPPORT_GPU
     sk_sp<GrFragmentProcessor> asFragmentProcessor(const AsFPArgs&) const override;
 #endif
 
-#ifdef SK_DEBUG
+#ifdef SK_DEBUGx
     SkShader* getShaderA() { return fShaderA.get(); }
     SkShader* getShaderB() { return fShaderB.get(); }
 #endif
@@ -57,12 +43,16 @@
     bool onAppendStages(SkRasterPipeline*, SkColorSpace* dstCS, SkArenaAlloc*,
                         const SkMatrix&, const SkPaint&, const SkMatrix* localM) const override;
 
-    bool isRasterPipelineOnly() const final;
+    bool isRasterPipelineOnly() const final { return true; }
 
 private:
-    sk_sp<SkShader>     fShaderA;
-    sk_sp<SkShader>     fShaderB;
-    SkBlendMode         fMode;
+    sk_sp<SkShader>     fDst;
+    sk_sp<SkShader>     fSrc;
+    const float         fLerpT;
+    const SkBlendMode   fMode;
+
+    bool isJustMode() const { return fLerpT == 1; }
+    bool isJustLerp() const { return fMode == SkBlendMode::kSrc; }
 
     typedef SkShaderBase INHERITED;
 };