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>
diff --git a/gm/colorfilterimagefilter.cpp b/gm/colorfilterimagefilter.cpp
index 5ef6379..73b9f20 100644
--- a/gm/colorfilterimagefilter.cpp
+++ b/gm/colorfilterimagefilter.cpp
@@ -223,3 +223,33 @@
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);
+ }
+}
diff --git a/include/core/SkPicture.h b/include/core/SkPicture.h
index 9f14f80..f90e17a 100644
--- a/include/core/SkPicture.h
+++ b/include/core/SkPicture.h
@@ -201,10 +201,11 @@
// 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&,
diff --git a/include/core/SkShader.h b/include/core/SkShader.h
index 7f78d4b..daed2ba 100644
--- a/include/core/SkShader.h
+++ b/include/core/SkShader.h
@@ -210,7 +210,38 @@
*/
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.
*
diff --git a/src/core/SkDraw_vertices.cpp b/src/core/SkDraw_vertices.cpp
index d0ef2d7..4f7fc13 100644
--- a/src/core/SkDraw_vertices.cpp
+++ b/src/core/SkDraw_vertices.cpp
@@ -247,7 +247,7 @@
matrix43 = triShader->getMatrix43();
if (shader) {
shader = outerAlloc.make<SkComposeShader>(sk_ref_sp(triShader), sk_ref_sp(shader),
- bmode);
+ bmode, 1);
} else {
shader = triShader;
}
diff --git a/src/core/SkReadBuffer.h b/src/core/SkReadBuffer.h
index 4e61050..48cb225 100644
--- a/src/core/SkReadBuffer.h
+++ b/src/core/SkReadBuffer.h
@@ -72,6 +72,7 @@
kXfermodeToBlendMode2_Version = 51,
*/
kTextBlobImplicitRunCount_Version = 52,
+ kComposeShaderCanLerp_Version = 54,
};
/**
diff --git a/src/shaders/SkComposeShader.cpp b/src/shaders/SkComposeShader.cpp
index 75f9cd2..3155784 100644
--- a/src/shaders/SkComposeShader.cpp
+++ b/src/shaders/SkComposeShader.cpp
@@ -17,57 +17,71 @@
#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) {
- return src;
- }
- if (SkBlendMode::kDst == mode) {
+ lerpT = SkScalarPin(lerpT, 0, 1);
+
+ if (lerpT == 0) {
return dst;
+ } else if (lerpT == 1) {
+ if (mode == SkBlendMode::kSrc) {
+ return src;
+ }
+ 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 (!this->isJustMode()) {
+ return false;
+ }
+
if (rec) {
- rec->fShaderA = fShaderA.get();
- rec->fShaderB = fShaderB.get();
+ rec->fShaderA = fDst.get();
+ rec->fShaderB = fSrc.get();
rec->fBlendMode = fMode;
}
return true;
}
-bool SkComposeShader::isRasterPipelineOnly() const {
- return true;
-}
-
bool SkComposeShader::onAppendStages(SkRasterPipeline* pipeline, SkColorSpace* dstCS,
SkArenaAlloc* alloc, const SkMatrix& ctm,
const SkPaint& paint, const SkMatrix* localM) const {
@@ -77,27 +91,32 @@
};
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);
- // 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->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,29 +129,25 @@
/////////////////////////////////////////////////////////////////////
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));
- if (!fpA) {
- return nullptr;
- }
- sk_sp<GrFragmentProcessor> fpB(as_SB(fShaderB)->asFragmentProcessor(args));
- if (!fpB) {
- return nullptr;
- }
- return GrXfermodeFragmentProcessor::MakeFromTwoProcessors(std::move(fpB),
- std::move(fpA), fMode);
+ }
}
+
+ sk_sp<GrFragmentProcessor> fpA(as_SB(fDst)->asFragmentProcessor(args));
+ if (!fpA) {
+ return nullptr;
+ }
+ 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
@@ -140,13 +155,12 @@
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);
diff --git a/src/shaders/SkComposeShader.h b/src/shaders/SkComposeShader.h
index 39c43d6..0386100 100644
--- a/src/shaders/SkComposeShader.h
+++ b/src/shaders/SkComposeShader.h
@@ -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 @@
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;
};