add mixer colorfilter

BUG=skia:

Change-Id: Icbee96056b17c5396a9d3783055ddd0b7d4fd3ff
Reviewed-on: https://skia-review.googlesource.com/c/9514
Commit-Queue: Florin Malita <fmalita@chromium.org>
Commit-Queue: Mike Reed <reed@google.com>
Reviewed-by: Florin Malita <fmalita@chromium.org>
diff --git a/gn/samples.gni b/gn/samples.gni
index d1b7904..1795584 100644
--- a/gn/samples.gni
+++ b/gn/samples.gni
@@ -62,6 +62,7 @@
   "$_samplecode/SampleManyRects.cpp",
   "$_samplecode/SampleMegaStroke.cpp",
   "$_samplecode/SampleNima.cpp",
+  "$_samplecode/SampleMixer.cpp",
   "$_samplecode/SamplePatch.cpp",
   "$_samplecode/SamplePath.cpp",
   "$_samplecode/SamplePathText.cpp",
diff --git a/include/core/SkColorFilter.h b/include/core/SkColorFilter.h
index 4fbacf3..ac167b3 100644
--- a/include/core/SkColorFilter.h
+++ b/include/core/SkColorFilter.h
@@ -120,6 +120,17 @@
      */
     static sk_sp<SkColorFilter> MakeSRGBToLinearGamma();
 
+    /**
+     *  Returns a new filter that returns the weighted average between the outputs of
+     *  two other filters. If either is null, then it is treated as an identity filter.
+     *
+     *  result = cf0(color) * (1 - weight) + cf1(color) * weight
+     *
+     *  If both filters are null, or if weight is NaN, then null is returned.
+     */
+    static sk_sp<SkColorFilter> MakeMixer(sk_sp<SkColorFilter> cf0, sk_sp<SkColorFilter> cf1,
+                                          float weight);
+
 #if SK_SUPPORT_GPU
     /**
      *  A subclass may implement this factory function to work with the GPU backend. It returns
diff --git a/samplecode/SampleMixer.cpp b/samplecode/SampleMixer.cpp
new file mode 100644
index 0000000..65dcddc
--- /dev/null
+++ b/samplecode/SampleMixer.cpp
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2017 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "Sample.h"
+#include "SkCanvas.h"
+#include "SkColorFilter.h"
+#include "SkGradientShader.h"
+#include "SkImage.h"
+#include "SkPath.h"
+#include "SkRegion.h"
+#include "SkShader.h"
+#include "SkUtils.h"
+#include "Resources.h"
+
+const SkScalar gMat[] = {
+    .3f, .6f, .1f, 0, 0,
+    .3f, .6f, .1f, 0, 0,
+    .3f, .6f, .1f, 0, 0,
+      0,   0,   0, 1, 0,
+};
+
+class MixerView : public Sample {
+    sk_sp<SkImage>          fImg;
+    sk_sp<SkColorFilter>    fCF0;
+    sk_sp<SkColorFilter>    fCF1;
+
+    float fWeight = 0;
+    float fDW = 0.02f;
+
+public:
+    MixerView() {}
+
+protected:
+    bool onQuery(Event* evt) override {
+        if (Sample::TitleQ(*evt)) {
+            Sample::TitleR(evt, "Mixer");
+            return true;
+        }
+        return this->INHERITED::onQuery(evt);
+    }
+
+    void dodraw(SkCanvas* canvas, sk_sp<SkColorFilter> cf0, sk_sp<SkColorFilter> cf1, float gap) {
+        SkPaint paint;
+        paint.setColorFilter(cf0);
+        canvas->drawImage(fImg, 0, 0, &paint);
+
+        paint.setColorFilter(SkColorFilter::MakeMixer(cf0, cf1, fWeight));
+        canvas->drawImage(fImg, fImg->width() + gap * fWeight, 0, &paint);
+
+        paint.setColorFilter(cf1);
+        canvas->drawImage(fImg, 2*fImg->width() + gap, 0, &paint);
+    }
+
+    void onDrawContent(SkCanvas* canvas) override {
+        if (!fImg) {
+            fImg = GetResourceAsImage("images/mandrill_256.png");
+            fCF0 = SkColorFilter::MakeMatrixFilterRowMajor255(gMat);
+            fCF1 = SkColorFilter::MakeModeFilter(0xFF44CC88, SkBlendMode::kScreen);
+        }
+
+        float gap = fImg->width() * 3;
+
+        canvas->translate(10, 10);
+        dodraw(canvas, nullptr, fCF1, gap);
+        canvas->translate(0, fImg->height() + 10);
+        dodraw(canvas, fCF0, nullptr, gap);
+        canvas->translate(0, fImg->height() + 10);
+        dodraw(canvas, fCF0, fCF1, gap);
+
+        fWeight += fDW;
+        if (fWeight > 1 || fWeight < 0) {
+            fDW = -fDW;
+        }
+    }
+
+    virtual Click* onFindClickHandler(SkScalar x, SkScalar y, unsigned) override {
+        return fRect.contains(SkScalarRoundToInt(x),
+                              SkScalarRoundToInt(y)) ? new Click(this) : nullptr;
+    }
+
+    bool onClick(Click* click) override {
+        fRect.offset(click->fICurr.fX - click->fIPrev.fX,
+                     click->fICurr.fY - click->fIPrev.fY);
+        return true;
+    }
+
+private:
+    SkIRect fRect;
+
+    typedef Sample INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+DEF_SAMPLE( return new MixerView; )
diff --git a/src/core/SkColorFilter.cpp b/src/core/SkColorFilter.cpp
index 2089c08..7b2cc22 100644
--- a/src/core/SkColorFilter.cpp
+++ b/src/core/SkColorFilter.cpp
@@ -279,10 +279,116 @@
 
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
+class SkMixerColorFilter : public SkColorFilter {
+public:
+    SkMixerColorFilter(sk_sp<SkColorFilter> cf0, sk_sp<SkColorFilter> cf1, float weight)
+        : fCF0(std::move(cf0)), fCF1(std::move(cf1)), fWeight(weight)
+    {
+        SkASSERT(fCF0 || fCF1);
+        SkASSERT(fWeight >= 0 && fWeight <= 1);
+    }
+
+    uint32_t getFlags() const override {
+        uint32_t f0 = fCF0 ? fCF0->getFlags() : ~0U;
+        uint32_t f1 = fCF1 ? fCF1->getFlags() : ~0U;
+        return f0 & f1;
+    }
+
+    void onAppendStages(SkRasterPipeline* p, SkColorSpace* dst, SkArenaAlloc* alloc,
+                        bool shaderIsOpaque) const override {
+        // want cf0 * (1 - w) + cf1 * w == lerp(w)
+        // which means
+        //      dr,dg,db,da <-- cf0
+        //      r,g,b,a     <-- cf1
+        struct State {
+            float     orig_rgba[4 * SkRasterPipeline_kMaxStride];
+            float filtered_rgba[4 * SkRasterPipeline_kMaxStride];
+        };
+        auto state = alloc->make<State>();
+
+        p->append(SkRasterPipeline::store_rgba, state->orig_rgba);
+        if (fCF0 && !fCF1) {
+            fCF0->appendStages(p, dst, alloc, shaderIsOpaque);
+            p->append(SkRasterPipeline::move_src_dst);
+            p->append(SkRasterPipeline::load_rgba, state->orig_rgba);
+        } else {
+            fCF1->appendStages(p, dst, alloc, shaderIsOpaque);
+            p->append(SkRasterPipeline::store_rgba, state->filtered_rgba);
+            p->append(SkRasterPipeline::load_rgba, state->orig_rgba);
+            if (fCF0) {
+                fCF0->appendStages(p, dst, alloc, shaderIsOpaque);
+            }
+            p->append(SkRasterPipeline::move_src_dst);
+            p->append(SkRasterPipeline::load_rgba, state->filtered_rgba);
+        }
+        float* storage = alloc->make<float>(fWeight);
+        p->append(SkRasterPipeline::lerp_1_float, storage);
+    }
+
+#if SK_SUPPORT_GPU
+    std::unique_ptr<GrFragmentProcessor> asFragmentProcessor(
+            GrContext* context, const GrColorSpaceInfo&) const override {
+        return nullptr;
+    }
+#endif
+
+protected:
+    void flatten(SkWriteBuffer& buffer) const override {
+        buffer.writeFlattenable(fCF0.get());
+        buffer.writeFlattenable(fCF1.get());
+        buffer.writeScalar(fWeight);
+    }
+
+private:
+    SK_FLATTENABLE_HOOKS(SkMixerColorFilter)
+
+    sk_sp<SkColorFilter> fCF0;
+    sk_sp<SkColorFilter> fCF1;
+    const float          fWeight;
+
+    friend class SkColorFilter;
+
+    typedef SkColorFilter INHERITED;
+};
+
+sk_sp<SkFlattenable> SkMixerColorFilter::CreateProc(SkReadBuffer& buffer) {
+    sk_sp<SkColorFilter> cf0(buffer.readColorFilter());
+    sk_sp<SkColorFilter> cf1(buffer.readColorFilter());
+    const float weight = buffer.readScalar();
+    return MakeMixer(std::move(cf0), std::move(cf1), weight);
+}
+
+sk_sp<SkColorFilter> SkColorFilter::MakeMixer(sk_sp<SkColorFilter> cf0,
+                                              sk_sp<SkColorFilter> cf1,
+                                              float weight) {
+    if (!cf0 && !cf1) {
+        return nullptr;
+    }
+    if (SkScalarIsNaN(weight)) {
+        return nullptr;
+    }
+
+    if (cf0 == cf1) {
+        return cf0; // or cf1
+    }
+
+    if (weight <= 0) {
+        return cf0;
+    }
+    if (weight >= 1) {
+        return cf1;
+    }
+
+    return sk_sp<SkColorFilter>(new SkMixerColorFilter(std::move(cf0), std::move(cf1), weight));
+}
+
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
 #include "SkModeColorFilter.h"
 
 void SkColorFilter::RegisterFlattenables() {
     SK_REGISTER_FLATTENABLE(SkComposeColorFilter);
     SK_REGISTER_FLATTENABLE(SkModeColorFilter);
     SK_REGISTER_FLATTENABLE(SkSRGBGammaColorFilter);
+    SK_REGISTER_FLATTENABLE(SkMixerColorFilter);
 }