blob: 3f6d3c575f66c30e86c352169a919bf632a565d5 [file] [log] [blame]
/*
* 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 "SkColorPriv.h"
#include "SkFlattenableBuffers.h"
#if SK_SUPPORT_GPU
#include "GrContext.h"
#include "gl/GrGLEffect.h"
#include "gl/GrGLEffectMatrix.h"
#include "GrTBackendEffectFactory.h"
#include "SkImageFilterUtils.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::kModulate_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)
: INHERITED(background, foreground), fMode(mode)
{
}
SkBlendImageFilter::~SkBlendImageFilter() {
}
SkBlendImageFilter::SkBlendImageFilter(SkFlattenableReadBuffer& buffer)
: INHERITED(buffer)
{
fMode = (SkBlendImageFilter::Mode) buffer.readInt();
}
void SkBlendImageFilter::flatten(SkFlattenableWriteBuffer& buffer) const {
this->INHERITED::flatten(buffer);
buffer.writeInt((int) fMode);
}
bool SkBlendImageFilter::onFilterImage(Proxy* proxy,
const SkBitmap& src,
const SkMatrix& ctm,
SkBitmap* dst,
SkIPoint* offset) {
SkBitmap background, foreground = src;
SkImageFilter* backgroundInput = getBackgroundInput();
SkImageFilter* foregroundInput = getForegroundInput();
SkASSERT(NULL != backgroundInput);
if (!backgroundInput->filterImage(proxy, src, ctm, &background, offset)) {
return false;
}
if (foregroundInput && !foregroundInput->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 GrGLEffect {
public:
GrGLBlendEffect(const GrBackendEffectFactory& factory,
const GrEffectRef& effect);
virtual ~GrGLBlendEffect();
virtual void emitCode(GrGLShaderBuilder*,
const GrEffectStage&,
EffectKey,
const char* vertexCoords,
const char* outputColor,
const char* inputColor,
const TextureSamplerArray&) SK_OVERRIDE;
static inline EffectKey GenKey(const GrEffectStage&, const GrGLCaps&);
virtual void setData(const GrGLUniformManager&, const GrEffectStage&);
private:
SkBlendImageFilter::Mode fMode;
GrGLEffectMatrix fForegroundEffectMatrix;
GrGLEffectMatrix fBackgroundEffectMatrix;
typedef GrGLEffect INHERITED;
};
///////////////////////////////////////////////////////////////////////////////
class GrBlendEffect : public GrEffect {
public:
static GrEffectRef* Create(SkBlendImageFilter::Mode mode,
GrTexture* foreground,
GrTexture* background) {
AutoEffectUnref effect(SkNEW_ARGS(GrBlendEffect, (mode, foreground, background)));
return CreateEffectRef(effect);
}
virtual ~GrBlendEffect();
const GrBackendEffectFactory& getFactory() const;
SkBlendImageFilter::Mode mode() const { return fMode; }
typedef GrGLBlendEffect GLEffect;
static const char* Name() { return "Blend"; }
void getConstantColorComponents(GrColor* color, uint32_t* validFlags) const SK_OVERRIDE;
private:
virtual bool onIsEqual(const GrEffect&) const SK_OVERRIDE;
GrBlendEffect(SkBlendImageFilter::Mode mode, GrTexture* foreground, GrTexture* background);
GrTextureAccess fForegroundAccess;
GrTextureAccess fBackgroundAccess;
SkBlendImageFilter::Mode fMode;
typedef GrEffect INHERITED;
};
bool SkBlendImageFilter::filterImageGPU(Proxy* proxy, const SkBitmap& src, SkBitmap* result) {
SkBitmap backgroundBM;
if (!SkImageFilterUtils::GetInputResultGPU(getBackgroundInput(), proxy, src, &backgroundBM)) {
return false;
}
GrTexture* background = (GrTexture*) backgroundBM.getTexture();
SkBitmap foregroundBM;
if (!SkImageFilterUtils::GetInputResultGPU(getForegroundInput(), proxy, src, &foregroundBM)) {
return false;
}
GrTexture* foreground = (GrTexture*) foregroundBM.getTexture();
GrContext* context = foreground->getContext();
GrTextureDesc desc;
desc.fFlags = kRenderTarget_GrTextureFlagBit | kNoStencil_GrTextureFlagBit;
desc.fWidth = src.width();
desc.fHeight = src.height();
desc.fConfig = kSkia8888_GrPixelConfig;
GrAutoScratchTexture ast(context, desc);
SkAutoTUnref<GrTexture> dst(ast.detach());
GrContext::AutoRenderTarget art(context, dst->asRenderTarget());
GrPaint paint;
paint.colorStage(0)->setEffect(
GrBlendEffect::Create(fMode, foreground, background))->unref();
SkRect srcRect;
src.getBounds(&srcRect);
context->drawRect(paint, srcRect);
return SkImageFilterUtils::WrapTexture(dst, src.width(), src.height(), result);
}
///////////////////////////////////////////////////////////////////////////////
GrBlendEffect::GrBlendEffect(SkBlendImageFilter::Mode mode,
GrTexture* foreground,
GrTexture* background)
: fForegroundAccess(foreground)
, fBackgroundAccess(background)
, fMode(mode) {
this->addTextureAccess(&fForegroundAccess);
this->addTextureAccess(&fBackgroundAccess);
}
GrBlendEffect::~GrBlendEffect() {
}
bool GrBlendEffect::onIsEqual(const GrEffect& sBase) const {
const GrBlendEffect& s = CastEffect<GrBlendEffect>(sBase);
return fForegroundAccess.getTexture() == s.fForegroundAccess.getTexture() &&
fBackgroundAccess.getTexture() == s.fBackgroundAccess.getTexture() &&
fMode == s.fMode;
}
const GrBackendEffectFactory& GrBlendEffect::getFactory() const {
return GrTBackendEffectFactory<GrBlendEffect>::getInstance();
}
void GrBlendEffect::getConstantColorComponents(GrColor* color, uint32_t* validFlags) const {
// The output alpha is always 1 - (1 - FGa) * (1 - BGa). So if either FGa or BGa is known to
// be one then the output alpha is one. (This effect ignores its input. We should have a way to
// communicate this.)
if (GrPixelConfigIsOpaque(fForegroundAccess.getTexture()->config()) ||
GrPixelConfigIsOpaque(fBackgroundAccess.getTexture()->config())) {
*validFlags = kA_ValidComponentFlag;
*color = GrColorPackRGBA(0, 0, 0, 0xff);
} else {
*validFlags = 0;
}
}
///////////////////////////////////////////////////////////////////////////////
GrGLBlendEffect::GrGLBlendEffect(const GrBackendEffectFactory& factory, const GrEffectRef& effect)
: INHERITED(factory),
fMode(CastEffect<GrBlendEffect>(effect).mode()) {
}
GrGLBlendEffect::~GrGLBlendEffect() {
}
void GrGLBlendEffect::emitCode(GrGLShaderBuilder* builder,
const GrEffectStage&,
EffectKey key,
const char* vertexCoords,
const char* outputColor,
const char* inputColor,
const TextureSamplerArray& samplers) {
const char* fgCoords;
const char* bgCoords;
GrSLType fgCoordsType = fForegroundEffectMatrix.emitCode(
builder, key, vertexCoords, &fgCoords, NULL, "FG");
GrSLType bgCoordsType = fBackgroundEffectMatrix.emitCode(
builder, key, vertexCoords, &bgCoords, NULL, "BG");
SkString* code = &builder->fFSCode;
const char* bgColor = "bgColor";
const char* fgColor = "fgColor";
code->appendf("\t\tvec4 %s = ", fgColor);
builder->appendTextureLookup(code, samplers[0], fgCoords, fgCoordsType);
code->append(";\n");
code->appendf("\t\tvec4 %s = ", bgColor);
builder->appendTextureLookup(code, samplers[1], bgCoords, bgCoordsType);
code->append(";\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;
}
}
void GrGLBlendEffect::setData(const GrGLUniformManager& uman, const GrEffectStage& stage) {
const GrBlendEffect& blend = GetEffectFromStage<GrBlendEffect>(stage);
GrTexture* fgTex = blend.texture(0);
GrTexture* bgTex = blend.texture(1);
fForegroundEffectMatrix.setData(uman,
GrEffect::MakeDivByTextureWHMatrix(fgTex),
stage.getCoordChangeMatrix(),
fgTex);
fBackgroundEffectMatrix.setData(uman,
GrEffect::MakeDivByTextureWHMatrix(bgTex),
stage.getCoordChangeMatrix(),
bgTex);
}
GrGLEffect::EffectKey GrGLBlendEffect::GenKey(const GrEffectStage& stage, const GrGLCaps&) {
const GrBlendEffect& blend = GetEffectFromStage<GrBlendEffect>(stage);
GrTexture* fgTex = blend.texture(0);
GrTexture* bgTex = blend.texture(1);
EffectKey fgKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(fgTex),
stage.getCoordChangeMatrix(),
fgTex);
EffectKey bgKey = GrGLEffectMatrix::GenKey(GrEffect::MakeDivByTextureWHMatrix(bgTex),
stage.getCoordChangeMatrix(),
bgTex);
bgKey <<= GrGLEffectMatrix::kKeyBits;
EffectKey modeKey = blend.mode() << (2 * GrGLEffectMatrix::kKeyBits);
return modeKey | bgKey | fgKey;
}
#endif