[skottie] Add tritone layer effect support
Generalize sksg::TintColorFilter into GradientColorfilter, to support
multiple color interpolation ranges.
Keep the two-color/single-matrix (tint) specialization, and use a table
color filter for the general case.
Change-Id: Idf42833462127c29fb69fe94337a1d1478b77dd9
Reviewed-on: https://skia-review.googlesource.com/c/194024
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Florin Malita <fmalita@chromium.org>
diff --git a/modules/skottie/src/SkottieLayerEffect.cpp b/modules/skottie/src/SkottieLayerEffect.cpp
index 61679ff..70e8314 100644
--- a/modules/skottie/src/SkottieLayerEffect.cpp
+++ b/modules/skottie/src/SkottieLayerEffect.cpp
@@ -45,9 +45,10 @@
return nullptr;
}
- auto tint_node = sksg::TintColorFilter::Make(std::move(layer),
- abuilder->attachColor(*color0_prop, ascope, "v"),
- abuilder->attachColor(*color1_prop, ascope, "v"));
+ auto tint_node =
+ sksg::GradientColorFilter::Make(std::move(layer),
+ abuilder->attachColor(*color0_prop, ascope, "v"),
+ abuilder->attachColor(*color1_prop, ascope, "v"));
if (!tint_node) {
return nullptr;
}
@@ -60,6 +61,49 @@
return std::move(tint_node);
}
+sk_sp<sksg::RenderNode> AttachTritoneLayerEffect(const skjson::ArrayValue& jprops,
+ const AnimationBuilder* abuilder,
+ AnimatorScope* ascope,
+ sk_sp<sksg::RenderNode> layer) {
+ enum : size_t {
+ kHiColor_Index = 0,
+ kMiColor_Index = 1,
+ kLoColor_Index = 2,
+ kBlendAmount_Index = 3,
+
+ kMax_Index = kBlendAmount_Index,
+ };
+
+ if (jprops.size() <= kMax_Index) {
+ return nullptr;
+ }
+
+ const skjson::ObjectValue* hicolor_prop = jprops[ kHiColor_Index];
+ const skjson::ObjectValue* micolor_prop = jprops[ kMiColor_Index];
+ const skjson::ObjectValue* locolor_prop = jprops[ kLoColor_Index];
+ const skjson::ObjectValue* blend_prop = jprops[kBlendAmount_Index];
+
+ if (!hicolor_prop || !micolor_prop || !locolor_prop || !blend_prop) {
+ return nullptr;
+ }
+
+ auto tritone_node =
+ sksg::GradientColorFilter::Make(std::move(layer), {
+ abuilder->attachColor(*locolor_prop, ascope, "v"),
+ abuilder->attachColor(*micolor_prop, ascope, "v"),
+ abuilder->attachColor(*hicolor_prop, ascope, "v") });
+ if (!tritone_node) {
+ return nullptr;
+ }
+
+ abuilder->bindProperty<ScalarValue>((*blend_prop)["v"], ascope,
+ [tritone_node](const ScalarValue& w) {
+ tritone_node->setWeight((100 - w) / 100); // 100-based, inverted (!?).
+ });
+
+ return std::move(tritone_node);
+}
+
sk_sp<sksg::RenderNode> AttachFillLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
@@ -181,6 +225,7 @@
enum : int32_t {
kTint_Effect = 20,
kFill_Effect = 21,
+ kTritone_Effect = 23,
kDropShadow_Effect = 25,
};
@@ -201,6 +246,9 @@
case kFill_Effect:
layer = AttachFillLayerEffect(*jprops, this, ascope, std::move(layer));
break;
+ case kTritone_Effect:
+ layer = AttachTritoneLayerEffect(*jprops, this, ascope, std::move(layer));
+ break;
case kDropShadow_Effect:
layer = AttachDropShadowLayerEffect(*jprops, this, ascope, std::move(layer));
break;
diff --git a/modules/sksg/include/SkSGColorFilter.h b/modules/sksg/include/SkSGColorFilter.h
index 4397cc4..0029b31 100644
--- a/modules/sksg/include/SkSGColorFilter.h
+++ b/modules/sksg/include/SkSGColorFilter.h
@@ -12,6 +12,8 @@
#include "SkBlendMode.h"
+#include <vector>
+
class SkColorFilter;
namespace sksg {
@@ -62,17 +64,18 @@
};
/**
- * 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.
+ * Tint/multi-tone color effect: maps RGB colors to the [C0,C1][C1,C2]..[Cn-1,Cn] gradient
+ * based on input luminance (where the colors are evenly distributed across the luminance domain),
+ * then mixes with the input based on weight. Leaves alpha unchanged.
*/
-
-class TintColorFilter final : public ColorFilter {
+class GradientColorFilter final : public ColorFilter {
public:
- ~TintColorFilter() override;
+ ~GradientColorFilter() override;
- static sk_sp<TintColorFilter> Make(sk_sp<RenderNode> child,
- sk_sp<Color> color0,
- sk_sp<Color> color1);
+ static sk_sp<GradientColorFilter> Make(sk_sp<RenderNode> child,
+ sk_sp<Color> c0, sk_sp<Color> c1);
+ static sk_sp<GradientColorFilter> Make(sk_sp<RenderNode> child,
+ std::vector<sk_sp<Color>>);
SG_ATTRIBUTE(Weight, float, fWeight)
@@ -80,14 +83,13 @@
sk_sp<SkColorFilter> onRevalidateFilter() override;
private:
- TintColorFilter(sk_sp<RenderNode>, sk_sp<Color>, sk_sp<Color>);
+ GradientColorFilter(sk_sp<RenderNode>, std::vector<sk_sp<Color>>);
- const sk_sp<Color> fColor0,
- fColor1;
+ const std::vector<sk_sp<Color>> fColors;
- float fWeight = 0;
+ float fWeight = 0;
- typedef ColorFilter INHERITED;
+ using INHERITED = ColorFilter;
};
} // namespace sksg
diff --git a/modules/sksg/src/SkSGColorFilter.cpp b/modules/sksg/src/SkSGColorFilter.cpp
index f10d83f..c34c729 100644
--- a/modules/sksg/src/SkSGColorFilter.cpp
+++ b/modules/sksg/src/SkSGColorFilter.cpp
@@ -9,6 +9,9 @@
#include "SkColorFilter.h"
#include "SkSGColor.h"
+#include "SkTableColorFilter.h"
+
+#include <cmath>
namespace sksg {
@@ -57,48 +60,49 @@
return 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;
+sk_sp<GradientColorFilter> GradientColorFilter::Make(sk_sp<RenderNode> child,
+ sk_sp<Color> c0, sk_sp<Color> c1) {
+ return Make(std::move(child), { std::move(c0), std::move(c1) });
}
-TintColorFilter::TintColorFilter(sk_sp<RenderNode> child, sk_sp<Color> c0, sk_sp<Color> c1)
+sk_sp<GradientColorFilter> GradientColorFilter::Make(sk_sp<RenderNode> child,
+ std::vector<sk_sp<Color>> colors) {
+ return (child && colors.size() > 1)
+ ? sk_sp<GradientColorFilter>(new GradientColorFilter(std::move(child), std::move(colors)))
+ : nullptr;
+}
+
+GradientColorFilter::GradientColorFilter(sk_sp<RenderNode> child, std::vector<sk_sp<Color>> colors)
: 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;
+ , fColors(std::move(colors)) {
+ for (const auto& color : fColors) {
+ this->observeInval(color);
}
+}
- const auto c0 = SkColor4f::FromColor(fColor0->getColor()),
- c1 = SkColor4f::FromColor(fColor1->getColor());
+GradientColorFilter::~GradientColorFilter() {
+ for (const auto& color : fColors) {
+ this->unobserveInval(color);
+ }
+}
- // luminance coefficients
- static constexpr float kR = 0.2126f,
- kG = 0.7152f,
- kB = 0.0722f;
+namespace {
+
+// luminance coefficients
+static constexpr float kR = 0.2126f,
+ kG = 0.7152f,
+ kB = 0.0722f;
+
+sk_sp<SkColorFilter> Make2ColorGradient(const sk_sp<Color>& color0, const sk_sp<Color>& color1) {
+ const auto c0 = SkColor4f::FromColor(color0->getColor()),
+ c1 = SkColor4f::FromColor(color1->getColor());
const auto dR = c1.fR - c0.fR,
dG = c1.fG - c0.fG,
dB = c1.fB - c0.fB;
- // First, we need a luminance:
+ // A 2-color gradient can be expressed as a color matrix (and combined with the luminance
+ // calculation). First, the luminance:
//
// L = [r,g,b] . [kR,kG,kB]
//
@@ -132,9 +136,77 @@
0, 0, 0, 1, 0,
};
- return SkColorFilter::MakeMixer(nullptr,
- SkColorFilter::MakeMatrixFilterRowMajor255(tint_matrix),
- fWeight);
+ return SkColorFilter::MakeMatrixFilterRowMajor255(tint_matrix);
+}
+
+sk_sp<SkColorFilter> MakeNColorGradient(const std::vector<sk_sp<Color>>& colors) {
+ // For N colors, we build a gradient color table.
+ uint8_t rTable[256], gTable[256], bTable[256];
+
+ SkASSERT(colors.size() > 2);
+ const auto span_count = colors.size() - 1;
+
+ size_t span_start = 0;
+ for (size_t i = 0; i < span_count; ++i) {
+ const auto span_stop = static_cast<size_t>(std::round((i + 1) * 255.0f / span_count)),
+ span_size = span_stop - span_start;
+ if (span_start > span_stop) {
+ // Degenerate case.
+ continue;
+ }
+ SkASSERT(span_stop <= 255);
+
+ // Fill the gradient in [span_start,span_stop] -> [c0,c1]
+ const SkColor c0 = colors[i ]->getColor(),
+ c1 = colors[i + 1]->getColor();
+ float r = SkColorGetR(c0),
+ g = SkColorGetG(c0),
+ b = SkColorGetB(c0);
+ const float dR = (SkColorGetR(c1) - r) / span_size,
+ dG = (SkColorGetG(c1) - g) / span_size,
+ dB = (SkColorGetB(c1) - b) / span_size;
+
+ for (size_t j = span_start; j <= span_stop; ++j) {
+ rTable[j] = static_cast<uint8_t>(std::round(r));
+ gTable[j] = static_cast<uint8_t>(std::round(g));
+ bTable[j] = static_cast<uint8_t>(std::round(b));
+ r += dR;
+ g += dG;
+ b += dB;
+ }
+
+ // Ensure we always advance.
+ span_start = span_stop + 1;
+ }
+ SkASSERT(span_start == 256);
+
+ const SkScalar luminance_matrix[] = {
+ kR, kG, kB, 0, 0, // r' = L
+ kR, kG, kB, 0, 0, // g' = L
+ kR, kG, kB, 0, 0, // b' = L
+ 0, 0, 0, 1, 0, // a' = a
+ };
+
+ return SkTableColorFilter::MakeARGB(nullptr, rTable, gTable, bTable)
+ ->makeComposed(SkColorFilter::MakeMatrixFilterRowMajor255(luminance_matrix));
+}
+
+} // namespace
+
+sk_sp<SkColorFilter> GradientColorFilter::onRevalidateFilter() {
+ for (const auto& color : fColors) {
+ color->revalidate(nullptr, SkMatrix::I());
+ }
+
+ if (fWeight <= 0) {
+ return nullptr;
+ }
+
+ SkASSERT(fColors.size() > 1);
+ auto gradientCF = (fColors.size() > 2) ? MakeNColorGradient(fColors)
+ : Make2ColorGradient(fColors[0], fColors[1]);
+
+ return SkColorFilter::MakeMixer(nullptr, std::move(gradientCF), fWeight);
}
} // namespace sksg