[skottie] Tint effect support

Introduce a TintColorFilter scene graph node, and hook into Skottie.

Change-Id: I350dec9ebcdc0bfc68d2874703112f1b20b51d75
Reviewed-on: https://skia-review.googlesource.com/c/193662
Commit-Queue: Florin Malita <fmalita@chromium.org>
Reviewed-by: Mike Reed <reed@google.com>
This commit is contained in:
Florin Malita 2019-02-20 17:17:14 -05:00 committed by Skia Commit-Bot
parent ddeed37bf8
commit 48cc589886
3 changed files with 186 additions and 25 deletions

View File

@ -20,6 +20,46 @@ namespace internal {
namespace {
sk_sp<sksg::RenderNode> AttachTintLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kMapBlackTo_Index = 0,
kMapWhiteTo_Index = 1,
kAmount_Index = 2,
// kOpacity_Index = 3, // currently unused (not exported)
kMax_Index = kAmount_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* color0_prop = jprops[kMapBlackTo_Index];
const skjson::ObjectValue* color1_prop = jprops[kMapWhiteTo_Index];
const skjson::ObjectValue* amount_prop = jprops[ kAmount_Index];
if (!color0_prop || !color1_prop || !amount_prop) {
return nullptr;
}
auto tint_node = sksg::TintColorFilter::Make(std::move(layer),
abuilder->attachColor(*color0_prop, ascope, "v"),
abuilder->attachColor(*color1_prop, ascope, "v"));
if (!tint_node) {
return nullptr;
}
abuilder->bindProperty<ScalarValue>((*amount_prop)["v"], ascope,
[tint_node](const ScalarValue& w) {
tint_node->setWeight(w / 100); // 100-based
});
return std::move(tint_node);
}
sk_sp<sksg::RenderNode> AttachFillLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
@ -58,7 +98,7 @@ sk_sp<sksg::RenderNode> AttachFillLayerEffect(const skjson::ArrayValue& jprops,
color_node->setColor(SkColorSetA(c, a));
});
return sksg::ColorModeFilter::Make(std::move(layer),
return sksg::ModeColorFilter::Make(std::move(layer),
std::move(color_node),
SkBlendMode::kSrcIn);
}
@ -139,6 +179,7 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachLayerEffects(const skjson::Array
}
enum : int32_t {
kTint_Effect = 20,
kFill_Effect = 21,
kDropShadow_Effect = 25,
};
@ -154,6 +195,9 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachLayerEffects(const skjson::Array
}
switch (const auto ty = ParseDefault<int>((*jeffect)["ty"], -1)) {
case kTint_Effect:
layer = AttachTintLayerEffect(*jprops, this, ascope, std::move(layer));
break;
case kFill_Effect:
layer = AttachFillLayerEffect(*jprops, this, ascope, std::move(layer));
break;

View File

@ -20,7 +20,6 @@ class Color;
/**
* Base class for nodes which apply a color filter when rendering their descendants.
*
*/
class ColorFilter : public EffectNode {
protected:
@ -29,37 +28,64 @@ protected:
void onRender(SkCanvas*, const RenderContext*) const final;
const RenderNode* onNodeAt(const SkPoint&) const final;
sk_sp<SkColorFilter> fColorFilter;
SkRect onRevalidate(InvalidationController*, const SkMatrix&) final;
virtual sk_sp<SkColorFilter> onRevalidateFilter() = 0;
private:
sk_sp<SkColorFilter> fColorFilter;
typedef EffectNode INHERITED;
};
/**
* Concrete SkModeColorFilter Effect node.
*
*/
class ColorModeFilter final : public ColorFilter {
class ModeColorFilter final : public ColorFilter {
public:
~ColorModeFilter() override;
~ModeColorFilter() override;
static sk_sp<ColorModeFilter> Make(sk_sp<RenderNode> child, sk_sp<Color> color,
SkBlendMode mode) {
return (child && color)
? sk_sp<ColorModeFilter>(new ColorModeFilter(std::move(child), std::move(color), mode))
: nullptr;
}
SG_ATTRIBUTE(Mode , SkBlendMode, fMode )
static sk_sp<ModeColorFilter> Make(sk_sp<RenderNode> child,
sk_sp<Color> color,
SkBlendMode mode);
protected:
SkRect onRevalidate(InvalidationController*, const SkMatrix&) override;
sk_sp<SkColorFilter> onRevalidateFilter() override;
private:
ColorModeFilter(sk_sp<RenderNode>, sk_sp<Color>, SkBlendMode);
ModeColorFilter(sk_sp<RenderNode>, sk_sp<Color>, SkBlendMode);
sk_sp<Color> fColor;
SkBlendMode fMode;
const sk_sp<Color> fColor;
const SkBlendMode fMode;
typedef ColorFilter INHERITED;
};
/**
* Tint color effect: maps RGB colors to the [c0,c1] gradient based on input luminance
* (while leaving the alpha channel unchanged), then mixes with the input based on weight.
*/
class TintColorFilter final : public ColorFilter {
public:
~TintColorFilter() override;
static sk_sp<TintColorFilter> Make(sk_sp<RenderNode> child,
sk_sp<Color> color0,
sk_sp<Color> color1);
SG_ATTRIBUTE(Weight, float, fWeight)
protected:
sk_sp<SkColorFilter> onRevalidateFilter() override;
private:
TintColorFilter(sk_sp<RenderNode>, sk_sp<Color>, sk_sp<Color>);
const sk_sp<Color> fColor0,
fColor1;
float fWeight = 0;
typedef ColorFilter INHERITED;
};

View File

@ -29,24 +29,115 @@ const RenderNode* ColorFilter::onNodeAt(const SkPoint& p) const {
return this->INHERITED::onNodeAt(p);
}
ColorModeFilter::ColorModeFilter(sk_sp<RenderNode> child, sk_sp<Color> color, SkBlendMode mode)
SkRect ColorFilter::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
fColorFilter = this->onRevalidateFilter();
return this->INHERITED::onRevalidate(ic, ctm);
}
sk_sp<ModeColorFilter> ModeColorFilter::Make(sk_sp<RenderNode> child, sk_sp<Color> color,
SkBlendMode mode) {
return (child && color) ? sk_sp<ModeColorFilter>(new ModeColorFilter(std::move(child),
std::move(color), mode))
: nullptr;
}
ModeColorFilter::ModeColorFilter(sk_sp<RenderNode> child, sk_sp<Color> color, SkBlendMode mode)
: INHERITED(std::move(child))
, fColor(std::move(color))
, fMode(mode) {
this->observeInval(fColor);
}
ColorModeFilter::~ColorModeFilter() {
ModeColorFilter::~ModeColorFilter() {
this->unobserveInval(fColor);
}
SkRect ColorModeFilter::onRevalidate(InvalidationController* ic, const SkMatrix& ctm) {
SkASSERT(this->hasInval());
sk_sp<SkColorFilter> ModeColorFilter::onRevalidateFilter() {
fColor->revalidate(nullptr, SkMatrix::I());
return SkColorFilter::MakeModeFilter(fColor->getColor(), fMode);
}
fColor->revalidate(ic, ctm);
fColorFilter = SkColorFilter::MakeModeFilter(fColor->getColor(), fMode);
sk_sp<TintColorFilter> TintColorFilter::Make(sk_sp<RenderNode> child,
sk_sp<Color> c0, sk_sp<Color> c1) {
return (child && c0 && c1) ? sk_sp<TintColorFilter>(new TintColorFilter(std::move(child),
std::move(c0),
std::move(c1)))
: nullptr;
}
return this->INHERITED::onRevalidate(ic, ctm);
TintColorFilter::TintColorFilter(sk_sp<RenderNode> child, sk_sp<Color> c0, sk_sp<Color> c1)
: INHERITED(std::move(child))
, fColor0(std::move(c0))
, fColor1(std::move(c1)) {
this->observeInval(fColor0);
this->observeInval(fColor1);
}
TintColorFilter::~TintColorFilter() {
this->unobserveInval(fColor0);
this->unobserveInval(fColor1);
}
sk_sp<SkColorFilter> TintColorFilter::onRevalidateFilter() {
fColor0->revalidate(nullptr, SkMatrix::I());
fColor1->revalidate(nullptr, SkMatrix::I());
if (fWeight <= 0) {
return nullptr;
}
const auto c0 = SkColor4f::FromColor(fColor0->getColor()),
c1 = SkColor4f::FromColor(fColor1->getColor());
// luminance coefficients
static constexpr float kR = 0.2126f,
kG = 0.7152f,
kB = 0.0722f;
const auto dR = c1.fR - c0.fR,
dG = c1.fG - c0.fG,
dB = c1.fB - c0.fB;
// First, we need a luminance:
//
// L = [r,g,b] . [kR,kG,kB]
//
// We can compute it using a color matrix (result stored in R):
//
// | kR, kG, kB, 0, 0 | r' = L
// | 0, 0, 0, 0, 0 | g' = 0
// | 0, 0, 0, 0, 0 | b' = 0
// | 0, 0, 0, 1, 0 | a' = a
//
// Then we want to interpolate component-wise, based on L:
//
// r' = c0.r + (c1.r - c0.r) * L = c0.r + dR*L
// g' = c0.g + (c1.g - c0.g) * L = c0.g + dG*L
// b' = c0.b + (c1.b - c0.b) * L = c0.b + dB*L
// a' = a
//
// This can be expressed as another color matrix (when L is stored in R):
//
// | dR, 0, 0, 0, c0.r |
// | dG, 0, 0, 0, c0.g |
// | dB, 0, 0, 0, c0.b |
// | 0, 0, 0, 1, 0 |
//
// Composing these two, we get the total tint matrix:
const SkScalar tint_matrix[] = {
dR*kR, dR*kG, dR*kB, 0, c0.fR * 255,
dG*kR, dG*kG, dG*kB, 0, c0.fG * 255,
dB*kR, dB*kG, dB*kB, 0, c0.fB * 255,
0, 0, 0, 1, 0,
};
return SkColorFilter::MakeMixer(nullptr,
SkColorFilter::MakeMatrixFilterRowMajor255(tint_matrix),
fWeight);
}
} // namespace sksg