From b2ee33c535720d066dd0d51f38686517efa34f2a Mon Sep 17 00:00:00 2001 From: "senorblanco@chromium.org" Date: Wed, 22 Aug 2012 16:24:44 +0000 Subject: [PATCH] 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 --- gm/blend.cpp | 98 +++++++++ gyp/effects.gypi | 2 + gyp/gmslides.gypi | 1 + include/effects/SkBlendImageFilter.h | 52 +++++ src/effects/SkBlendImageFilter.cpp | 294 +++++++++++++++++++++++++++ src/ports/SkGlobalInitialization_default.cpp | 1 + 6 files changed, 448 insertions(+) create mode 100644 gm/blend.cpp create mode 100644 include/effects/SkBlendImageFilter.h create mode 100644 src/effects/SkBlendImageFilter.cpp 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 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(); + if (buffer.readBool()) { + fForeground = buffer.readFlattenableT(); + } 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 background(getInputResultAsTexture(fBackground, src, rect)); + SkAutoTUnref 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(sBase); + return INHERITED::isEqual(sBase) && + fMode == s.fMode; +} + +const GrProgramStageFactory& GrBlendEffect::getFactory() const { + return GrTProgramStageFactory::getInstance(); +} + +/////////////////////////////////////////////////////////////////////////////// + +GrGLBlendEffect::GrGLBlendEffect(const GrProgramStageFactory& factory, + const GrCustomStage& stage) + : GrGLProgramStage(factory), + fMode(static_cast(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(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 @@ void SkFlattenable::InitializeFlattenables() { 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) -- 2.7.4