Implements the non-Porter-Duff compositing modes required for SVG's feBlend
element.  This filter has two inputs, since normal blending can't be used.  The
GPU side uses two filter stages to accomplish this:  one to sample the
background, and one to sample the foreground and blend it.

Review URL:  https://codereview.appspot.com/6463081/



git-svn-id: http://skia.googlecode.com/svn/trunk@5231 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/gm/blend.cpp b/gm/blend.cpp
new file mode 100644
index 0000000..d766a18
--- /dev/null
+++ b/gm/blend.cpp
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2012 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "gm.h"
+#include "SkBlendImageFilter.h"
+#include "SkBitmapSource.h"
+
+namespace skiagm {
+
+class ImageBlendGM : public GM {
+public:
+    ImageBlendGM() : fInitialized(false) {
+        this->setBGColor(0xFF000000);
+    }
+    
+protected:
+    virtual SkString onShortName() {
+        return SkString("blend");
+    }
+
+    void make_bitmap() {
+        fBitmap.setConfig(SkBitmap::kARGB_8888_Config, 80, 80);
+        fBitmap.allocPixels();
+        SkDevice device(fBitmap);
+        SkCanvas canvas(&device);
+        canvas.clear(0x00000000);
+        SkPaint paint;
+        paint.setAntiAlias(true);
+        paint.setColor(0xD000D000);
+        paint.setTextSize(SkIntToScalar(96));
+        const char* str = "e";
+        canvas.drawText(str, strlen(str), SkIntToScalar(15), SkIntToScalar(65), paint);
+    }
+
+    void make_checkerboard() {
+        fCheckerboard.setConfig(SkBitmap::kARGB_8888_Config, 80, 80);
+        fCheckerboard.allocPixels();
+        SkDevice device(fCheckerboard);
+        SkCanvas canvas(&device);
+        canvas.clear(0x00000000);
+        SkPaint darkPaint;
+        darkPaint.setColor(0xFF404040);
+        SkPaint lightPaint;
+        lightPaint.setColor(0xFFA0A0A0);
+        for (int y = 0; y < 80; y += 16) {
+          for (int x = 0; x < 80; x += 16) {
+            canvas.save();
+            canvas.translate(x, y);
+            canvas.drawRect(SkRect::MakeXYWH(0, 0, 8, 8), darkPaint);
+            canvas.drawRect(SkRect::MakeXYWH(8, 0, 8, 8), lightPaint);
+            canvas.drawRect(SkRect::MakeXYWH(0, 8, 8, 8), lightPaint);
+            canvas.drawRect(SkRect::MakeXYWH(8, 8, 8, 8), darkPaint);
+            canvas.restore();
+          }
+        }
+    }
+
+    virtual SkISize onISize() {
+        return make_isize(500, 100);
+    }
+
+    virtual void onDraw(SkCanvas* canvas) {
+        if (!fInitialized) {
+            make_bitmap();
+            make_checkerboard();
+            fInitialized = true;
+        }
+        canvas->clear(0x00000000);
+        SkPaint paint;
+        SkAutoTUnref<SkImageFilter> background(SkNEW_ARGS(SkBitmapSource, (fCheckerboard)));
+        paint.setImageFilter(SkNEW_ARGS(SkBlendImageFilter, (SkBlendImageFilter::kNormal_Mode, background)))->unref();
+        canvas->drawSprite(fBitmap, 0, 0, &paint);
+        paint.setImageFilter(SkNEW_ARGS(SkBlendImageFilter, (SkBlendImageFilter::kMultiply_Mode, background)))->unref();
+        canvas->drawSprite(fBitmap, 100, 0, &paint);
+        paint.setImageFilter(SkNEW_ARGS(SkBlendImageFilter, (SkBlendImageFilter::kScreen_Mode, background)))->unref();
+        canvas->drawSprite(fBitmap, 200, 0, &paint);
+        paint.setImageFilter(SkNEW_ARGS(SkBlendImageFilter, (SkBlendImageFilter::kDarken_Mode, background)))->unref();
+        canvas->drawSprite(fBitmap, 300, 0, &paint);
+        paint.setImageFilter(SkNEW_ARGS(SkBlendImageFilter, (SkBlendImageFilter::kLighten_Mode, background)))->unref();
+        canvas->drawSprite(fBitmap, 400, 0, &paint);
+    }
+    
+private:
+    typedef GM INHERITED;
+    SkBitmap fBitmap, fCheckerboard;
+    bool fInitialized;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new ImageBlendGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gyp/effects.gypi b/gyp/effects.gypi
index acae42a..164fc82 100644
--- a/gyp/effects.gypi
+++ b/gyp/effects.gypi
@@ -12,6 +12,7 @@
     '<(skia_src_path)/effects/SkAvoidXfermode.cpp',
     '<(skia_src_path)/effects/SkArithmeticMode.cpp',
     '<(skia_src_path)/effects/SkBitmapSource.cpp',
+    '<(skia_src_path)/effects/SkBlendImageFilter.cpp',
     '<(skia_src_path)/effects/SkBlurDrawLooper.cpp',
     '<(skia_src_path)/effects/SkBlurMask.cpp',
     '<(skia_src_path)/effects/SkBlurMask.h',
@@ -67,6 +68,7 @@
     '<(skia_include_path)/effects/SkAvoidXfermode.h',
     '<(skia_include_path)/effects/SkArithmeticMode.h',
     '<(skia_include_path)/effects/SkBitmapSource.h',
+    '<(skia_include_path)/effects/SkBlendImageFilter.h',
     '<(skia_include_path)/effects/SkBlurDrawLooper.h',
     '<(skia_include_path)/effects/SkBlurImageFilter.h',
     '<(skia_include_path)/effects/SkBlurMaskFilter.h',
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index 324d143..e36abf5 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -9,6 +9,7 @@
     '../gm/bitmapmatrix.cpp',
     '../gm/bitmapfilters.cpp',
     '../gm/bitmapscroll.cpp',
+    '../gm/blend.cpp',
     '../gm/blurs.cpp',
     '../gm/circles.cpp',
     '../gm/colormatrix.cpp',
diff --git a/include/effects/SkBlendImageFilter.h b/include/effects/SkBlendImageFilter.h
new file mode 100644
index 0000000..6818c9f
--- /dev/null
+++ b/include/effects/SkBlendImageFilter.h
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+
+#ifndef SkBlendImageFilter_DEFINED
+#define SkBlendImageFilter_DEFINED
+
+#include "SkImageFilter.h"
+#include "SkBitmap.h"
+
+class SK_API SkBlendImageFilter : public SkImageFilter {
+public:
+    enum Mode {
+      kNormal_Mode,
+      kMultiply_Mode,
+      kScreen_Mode,
+      kDarken_Mode,
+      kLighten_Mode,
+    };
+    SkBlendImageFilter(Mode mode, SkImageFilter* background, SkImageFilter* foreground = NULL);
+
+    ~SkBlendImageFilter();
+
+    SK_DECLARE_PUBLIC_FLATTENABLE_DESERIALIZATION_PROCS(SkBlendImageFilter)
+
+    virtual bool onFilterImage(Proxy* proxy,
+                               const SkBitmap& src,
+                               const SkMatrix& ctm,
+                               SkBitmap* dst,
+                               SkIPoint* offset) SK_OVERRIDE;
+#if SK_SUPPORT_GPU
+    virtual bool canFilterImageGPU() const SK_OVERRIDE { return true; }
+    virtual GrTexture* onFilterImageGPU(GrTexture* src, const SkRect& rect) SK_OVERRIDE;
+#endif
+
+protected:
+    explicit SkBlendImageFilter(SkFlattenableReadBuffer& buffer);
+    virtual void flatten(SkFlattenableWriteBuffer&) const SK_OVERRIDE;
+
+private:
+    SkImageFilter* fForeground;
+    SkImageFilter* fBackground;
+    Mode fMode;
+    typedef SkImageFilter INHERITED;
+};
+
+#endif
+
diff --git a/src/effects/SkBlendImageFilter.cpp b/src/effects/SkBlendImageFilter.cpp
new file mode 100644
index 0000000..6822624
--- /dev/null
+++ b/src/effects/SkBlendImageFilter.cpp
@@ -0,0 +1,294 @@
+/*
+ * Copyright 2012 The Android Open Source Project
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+
+#include "SkBlendImageFilter.h"
+#include "SkCanvas.h"
+#include "SkFlattenableBuffers.h"
+#if SK_SUPPORT_GPU
+#include "SkGr.h"
+#include "SkGrPixelRef.h"
+#include "gl/GrGLProgramStage.h"
+#endif
+
+namespace {
+
+SkXfermode::Mode modeToXfermode(SkBlendImageFilter::Mode mode)
+{
+    switch (mode) {
+      case SkBlendImageFilter::kNormal_Mode:
+        return SkXfermode::kSrcOver_Mode;
+      case SkBlendImageFilter::kMultiply_Mode:
+        return SkXfermode::kMultiply_Mode;
+      case SkBlendImageFilter::kScreen_Mode:
+        return SkXfermode::kScreen_Mode;
+      case SkBlendImageFilter::kDarken_Mode:
+        return SkXfermode::kDarken_Mode;
+      case SkBlendImageFilter::kLighten_Mode:
+        return SkXfermode::kLighten_Mode;
+    }
+    SkASSERT(0);
+    return SkXfermode::kSrcOver_Mode;
+}
+
+SkPMColor multiply_proc(SkPMColor src, SkPMColor dst) {
+    int omsa = 255 - SkGetPackedA32(src);
+    int sr = SkGetPackedR32(src), sg = SkGetPackedG32(src), sb = SkGetPackedB32(src);
+    int omda = 255 - SkGetPackedA32(dst);
+    int dr = SkGetPackedR32(dst), dg = SkGetPackedG32(dst), db = SkGetPackedB32(dst);
+    int a = 255 - SkMulDiv255Round(omsa, omda);
+    int r = SkMulDiv255Round(omsa, dr) + SkMulDiv255Round(omda, sr) + SkMulDiv255Round(sr, dr);
+    int g = SkMulDiv255Round(omsa, dg) + SkMulDiv255Round(omda, sg) + SkMulDiv255Round(sg, dg);
+    int b = SkMulDiv255Round(omsa, db) + SkMulDiv255Round(omda, sb) + SkMulDiv255Round(sb, db);
+    return SkPackARGB32(a, r, g, b);
+}
+
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+SkBlendImageFilter::SkBlendImageFilter(SkBlendImageFilter::Mode mode, SkImageFilter* background, SkImageFilter* foreground)
+  : fMode(mode)
+{
+    fBackground = background;
+    SkASSERT(background != NULL);
+    SkSafeRef(fBackground);
+    fForeground = foreground;
+    SkSafeRef(fForeground);
+}
+
+SkBlendImageFilter::~SkBlendImageFilter() {
+    SkSafeUnref(fBackground);
+    SkSafeUnref(fForeground);
+}
+
+SkBlendImageFilter::SkBlendImageFilter(SkFlattenableReadBuffer& buffer)
+  : INHERITED(buffer)
+{
+    fMode = (SkBlendImageFilter::Mode) buffer.readInt();
+    fBackground = buffer.readFlattenableT<SkImageFilter>();
+    if (buffer.readBool()) {
+        fForeground = buffer.readFlattenableT<SkImageFilter>();
+    } else {
+        fForeground = NULL;
+    }
+}
+
+void SkBlendImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
+    this->INHERITED::flatten(buffer);
+    buffer.writeInt((int) fMode);
+    buffer.writeFlattenable(fBackground);
+    buffer.writeBool(NULL != fForeground);
+    if (NULL != fForeground) {
+        buffer.writeFlattenable(fForeground);
+    }
+}
+
+bool SkBlendImageFilter::onFilterImage(Proxy* proxy,
+                                       const SkBitmap& src,
+                                       const SkMatrix& ctm,
+                                       SkBitmap* dst,
+                                       SkIPoint* offset) {
+    SkBitmap background, foreground = src;
+    SkASSERT(fBackground);
+    if (!fBackground->filterImage(proxy, src, ctm, &background, offset)) {
+        return false;
+    }
+    if (fForeground && !fForeground->filterImage(proxy, src, ctm, &foreground, offset)) {
+        return false;
+    }
+    SkAutoLockPixels alp_foreground(foreground), alp_background(background);
+    if (!foreground.getPixels() || !background.getPixels()) {
+        return false;
+    }
+    dst->setConfig(background.config(), background.width(), background.height());
+    dst->allocPixels();
+    SkCanvas canvas(*dst);
+    SkPaint paint;
+    paint.setXfermodeMode(SkXfermode::kSrc_Mode);
+    canvas.drawBitmap(background, 0, 0, &paint);
+    // FEBlend's multiply mode is (1 - Sa) * Da + (1 - Da) * Sc + Sc * Dc
+    // Skia's is just Sc * Dc.  So we use a custom proc to implement FEBlend's
+    // version.
+    if (fMode == SkBlendImageFilter::kMultiply_Mode) {
+        paint.setXfermode(new SkProcXfermode(multiply_proc))->unref();
+    } else {
+        paint.setXfermodeMode(modeToXfermode(fMode));
+    }
+    canvas.drawBitmap(foreground, 0, 0, &paint);
+    return true;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+#if SK_SUPPORT_GPU
+class GrGLBlendEffect  : public GrGLProgramStage {
+public:
+    GrGLBlendEffect(const GrProgramStageFactory& factory,
+                    const GrCustomStage& stage);
+    virtual ~GrGLBlendEffect();
+
+    virtual void emitFS(GrGLShaderBuilder* builder,
+                        const char* outputColor,
+                        const char* inputColor,
+                        const char* samplerName) SK_OVERRIDE;
+
+    virtual void emitVS(GrGLShaderBuilder* builder,
+                        const char* vertexCoords) SK_OVERRIDE {}
+
+    static inline StageKey GenKey(const GrCustomStage& s, const GrGLCaps&);
+
+private:
+    typedef GrGLProgramStage INHERITED;
+    SkBlendImageFilter::Mode fMode;
+};
+
+///////////////////////////////////////////////////////////////////////////////
+
+class GrBlendEffect : public GrSingleTextureEffect {
+public:
+    GrBlendEffect(SkBlendImageFilter::Mode mode, GrTexture* foreground);
+    virtual ~GrBlendEffect();
+
+    virtual bool isEqual(const GrCustomStage&) const SK_OVERRIDE;
+    const GrProgramStageFactory& getFactory() const;
+    SkBlendImageFilter::Mode mode() const { return fMode; }
+
+    typedef GrGLBlendEffect GLProgramStage;
+    static const char* Name() { return "Blend"; }
+
+private:
+    typedef GrSingleTextureEffect INHERITED;
+    SkBlendImageFilter::Mode fMode;
+};
+
+// FIXME:  This should be refactored with SkSingleInputImageFilter's version.
+static GrTexture* getInputResultAsTexture(SkImageFilter* input,
+                                          GrTexture* src,
+                                          const SkRect& rect) {
+    GrTexture* resultTex;
+    if (!input) {
+        resultTex = src;
+    } else if (input->canFilterImageGPU()) {
+        // onFilterImageGPU() already refs the result, so just return it here.
+        return input->onFilterImageGPU(src, rect);
+    } else {
+        SkBitmap srcBitmap, result;
+        srcBitmap.setConfig(SkBitmap::kARGB_8888_Config, src->width(), src->height());
+        srcBitmap.setPixelRef(new SkGrPixelRef(src))->unref();
+        SkIPoint offset;
+        if (input->filterImage(NULL, srcBitmap, SkMatrix(), &result, &offset)) {
+            if (result.getTexture()) {
+                resultTex = (GrTexture*) result.getTexture();
+            } else {
+                resultTex = GrLockCachedBitmapTexture(src->getContext(), result, NULL);
+                SkSafeRef(resultTex);
+                GrUnlockCachedBitmapTexture(resultTex);
+                return resultTex;
+            }
+        } else {
+            resultTex = src;
+        }
+    }
+    SkSafeRef(resultTex);
+    return resultTex;
+}
+
+GrTexture* SkBlendImageFilter::onFilterImageGPU(GrTexture* src, const SkRect& rect) {
+    SkAutoTUnref<GrTexture> background(getInputResultAsTexture(fBackground, src, rect));
+    SkAutoTUnref<GrTexture> foreground(getInputResultAsTexture(fForeground, src, rect));
+    GrContext* context = src->getContext();
+
+    GrTextureDesc desc;
+    desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
+    desc.fWidth = SkScalarCeilToInt(rect.width());
+    desc.fHeight = SkScalarCeilToInt(rect.height());
+    desc.fConfig = kRGBA_8888_GrPixelConfig;
+
+    GrAutoScratchTexture ast(context, desc);
+    GrTexture* dst = ast.detach();
+    GrContext::AutoMatrix avm(context, GrMatrix::I());
+    GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
+    GrContext::AutoClip ac(context, rect);
+
+    GrMatrix sampleM;
+    sampleM.setIDiv(background->width(), background->height());
+    GrPaint paint;
+    paint.reset();
+    paint.textureSampler(0)->reset(sampleM);
+    paint.textureSampler(0)->setCustomStage(SkNEW_ARGS(GrSingleTextureEffect, (background.get())))->unref();
+    paint.textureSampler(1)->reset(sampleM);
+    paint.textureSampler(1)->setCustomStage(SkNEW_ARGS(GrBlendEffect, (fMode, foreground.get())))->unref();
+    context->drawRect(paint, rect);
+    return dst;
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrBlendEffect::GrBlendEffect(SkBlendImageFilter::Mode mode, GrTexture* foreground)
+    : INHERITED(foreground), fMode(mode) {
+}
+
+GrBlendEffect::~GrBlendEffect() {
+}
+
+bool GrBlendEffect::isEqual(const GrCustomStage& sBase) const {
+    const GrBlendEffect& s = static_cast<const GrBlendEffect&>(sBase);
+    return INHERITED::isEqual(sBase) &&
+           fMode == s.fMode;
+}
+
+const GrProgramStageFactory& GrBlendEffect::getFactory() const {
+    return GrTProgramStageFactory<GrBlendEffect>::getInstance();
+}
+
+///////////////////////////////////////////////////////////////////////////////
+
+GrGLBlendEffect::GrGLBlendEffect(const GrProgramStageFactory& factory,
+                                 const GrCustomStage& stage)
+    : GrGLProgramStage(factory),
+      fMode(static_cast<const GrBlendEffect&>(stage).mode()) {
+}
+
+GrGLBlendEffect::~GrGLBlendEffect() {
+}
+
+void GrGLBlendEffect::emitFS(GrGLShaderBuilder* builder,
+                             const char* outputColor,
+                             const char* inputColor,
+                             const char* samplerName) {
+    SkString* code = &builder->fFSCode;
+    const char* bgColor = inputColor;
+    const char* fgColor = "fgColor";
+    code->appendf("\t\tvec4 %s = ", fgColor);
+    builder->emitTextureLookup(samplerName);
+    code->appendf(";\n");
+    code->appendf("\t\t%s.a = 1.0 - (1.0 - %s.a) * (1.0 - %s.b);\n", outputColor, bgColor, fgColor);
+    switch (fMode) {
+      case SkBlendImageFilter::kNormal_Mode:
+        code->appendf("\t\t%s.rgb = (1.0 - %s.a) * %s.rgb + %s.rgb;\n", outputColor, fgColor, bgColor, fgColor);
+        break;
+      case SkBlendImageFilter::kMultiply_Mode:
+        code->appendf("\t\t%s.rgb = (1.0 - %s.a) * %s.rgb + (1.0 - %s.a) * %s.rgb + %s.rgb * %s.rgb;\n", outputColor, fgColor, bgColor, bgColor, fgColor, fgColor, bgColor);
+        break;
+      case SkBlendImageFilter::kScreen_Mode:
+        code->appendf("\t\t%s.rgb = %s.rgb + %s.rgb - %s.rgb * %s.rgb;\n", outputColor, bgColor, fgColor, fgColor, bgColor);
+        break;
+      case SkBlendImageFilter::kDarken_Mode:
+        code->appendf("\t\t%s.rgb = min((1.0 - %s.a) * %s.rgb + %s.rgb, (1.0 - %s.a) * %s.rgb + %s.rgb);\n", outputColor, fgColor, bgColor, fgColor, bgColor, fgColor, bgColor);
+        break;
+      case SkBlendImageFilter::kLighten_Mode:
+        code->appendf("\t\t%s.rgb = max((1.0 - %s.a) * %s.rgb + %s.rgb, (1.0 - %s.a) * %s.rgb + %s.rgb);\n", outputColor, fgColor, bgColor, fgColor, bgColor, fgColor, bgColor);
+        break;
+    }
+}
+
+GrGLProgramStage::StageKey GrGLBlendEffect::GenKey(const GrCustomStage& s, const GrGLCaps&) {
+    return static_cast<const GrBlendEffect&>(s).mode();
+}
+#endif
+
+SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlendImageFilter)
diff --git a/src/ports/SkGlobalInitialization_default.cpp b/src/ports/SkGlobalInitialization_default.cpp
index b28fbcd..e862ea6 100644
--- a/src/ports/SkGlobalInitialization_default.cpp
+++ b/src/ports/SkGlobalInitialization_default.cpp
@@ -52,6 +52,7 @@
 
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkAvoidXfermode)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBitmapProcShader)
+    SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlendImageFilter)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurDrawLooper)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkBlurImageFilter)
     SK_DEFINE_FLATTENABLE_REGISTRAR_ENTRY(SkColorMatrixFilter)