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>
This commit is contained in:
Mike Reed 2017-06-09 10:51:52 -04:00 committed by Skia Commit-Bot
parent 6a0feba05b
commit 01b2b83aba
7 changed files with 154 additions and 87 deletions

View File

@ -223,3 +223,33 @@ DEF_SIMPLE_GM(colorfiltershader, canvas, 610, 610) {
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);
}
}

View File

@ -201,10 +201,11 @@ private:
// 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&,

View File

@ -210,7 +210,38 @@ public:
*/
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.
*

View File

@ -247,7 +247,7 @@ void SkDraw::drawVertices(SkVertices::VertexMode vmode, int count,
matrix43 = triShader->getMatrix43();
if (shader) {
shader = outerAlloc.make<SkComposeShader>(sk_ref_sp(triShader), sk_ref_sp(shader),
bmode);
bmode, 1);
} else {
shader = triShader;
}

View File

@ -72,6 +72,7 @@ public:
kXfermodeToBlendMode2_Version = 51,
*/
kTextBlobImplicitRunCount_Version = 52,
kComposeShaderCanLerp_Version = 54,
};
/**

View File

@ -17,54 +17,68 @@
#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) {
lerpT = SkScalarPin(lerpT, 0, 1);
if (lerpT == 0) {
return dst;
} else if (lerpT == 1) {
if (mode == SkBlendMode::kSrc) {
return src;
}
if (SkBlendMode::kDst == mode) {
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 (rec) {
rec->fShaderA = fShaderA.get();
rec->fShaderB = fShaderB.get();
rec->fBlendMode = fMode;
}
return true;
if (!this->isJustMode()) {
return false;
}
bool SkComposeShader::isRasterPipelineOnly() const {
if (rec) {
rec->fShaderA = fDst.get();
rec->fShaderB = fSrc.get();
rec->fBlendMode = fMode;
}
return true;
}
@ -77,28 +91,33 @@ bool SkComposeShader::onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* d
};
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);
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,43 +129,38 @@ bool SkComposeShader::onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* d
/////////////////////////////////////////////////////////////////////
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));
}
}
sk_sp<GrFragmentProcessor> fpA(as_SB(fDst)->asFragmentProcessor(args));
if (!fpA) {
return nullptr;
}
sk_sp<GrFragmentProcessor> fpB(as_SB(fShaderB)->asFragmentProcessor(args));
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
#ifndef SK_IGNORE_TO_STRING
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);

View File

@ -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 @@ protected:
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;
};