[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>
This commit is contained in:
parent
30c9eda01b
commit
71c70ea13e
@ -45,7 +45,8 @@ sk_sp<sksg::RenderNode> AttachTintLayerEffect(const skjson::ArrayValue& jprops,
|
|||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto tint_node = sksg::TintColorFilter::Make(std::move(layer),
|
auto tint_node =
|
||||||
|
sksg::GradientColorFilter::Make(std::move(layer),
|
||||||
abuilder->attachColor(*color0_prop, ascope, "v"),
|
abuilder->attachColor(*color0_prop, ascope, "v"),
|
||||||
abuilder->attachColor(*color1_prop, ascope, "v"));
|
abuilder->attachColor(*color1_prop, ascope, "v"));
|
||||||
if (!tint_node) {
|
if (!tint_node) {
|
||||||
@ -60,6 +61,49 @@ sk_sp<sksg::RenderNode> AttachTintLayerEffect(const skjson::ArrayValue& jprops,
|
|||||||
return std::move(tint_node);
|
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,
|
sk_sp<sksg::RenderNode> AttachFillLayerEffect(const skjson::ArrayValue& jprops,
|
||||||
const AnimationBuilder* abuilder,
|
const AnimationBuilder* abuilder,
|
||||||
AnimatorScope* ascope,
|
AnimatorScope* ascope,
|
||||||
@ -181,6 +225,7 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachLayerEffects(const skjson::Array
|
|||||||
enum : int32_t {
|
enum : int32_t {
|
||||||
kTint_Effect = 20,
|
kTint_Effect = 20,
|
||||||
kFill_Effect = 21,
|
kFill_Effect = 21,
|
||||||
|
kTritone_Effect = 23,
|
||||||
kDropShadow_Effect = 25,
|
kDropShadow_Effect = 25,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -201,6 +246,9 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachLayerEffects(const skjson::Array
|
|||||||
case kFill_Effect:
|
case kFill_Effect:
|
||||||
layer = AttachFillLayerEffect(*jprops, this, ascope, std::move(layer));
|
layer = AttachFillLayerEffect(*jprops, this, ascope, std::move(layer));
|
||||||
break;
|
break;
|
||||||
|
case kTritone_Effect:
|
||||||
|
layer = AttachTritoneLayerEffect(*jprops, this, ascope, std::move(layer));
|
||||||
|
break;
|
||||||
case kDropShadow_Effect:
|
case kDropShadow_Effect:
|
||||||
layer = AttachDropShadowLayerEffect(*jprops, this, ascope, std::move(layer));
|
layer = AttachDropShadowLayerEffect(*jprops, this, ascope, std::move(layer));
|
||||||
break;
|
break;
|
||||||
|
@ -12,6 +12,8 @@
|
|||||||
|
|
||||||
#include "SkBlendMode.h"
|
#include "SkBlendMode.h"
|
||||||
|
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
class SkColorFilter;
|
class SkColorFilter;
|
||||||
|
|
||||||
namespace sksg {
|
namespace sksg {
|
||||||
@ -62,17 +64,18 @@ private:
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tint color effect: maps RGB colors to the [c0,c1] gradient based on input luminance
|
* Tint/multi-tone color effect: maps RGB colors to the [C0,C1][C1,C2]..[Cn-1,Cn] gradient
|
||||||
* (while leaving the alpha channel unchanged), then mixes with the input based on weight.
|
* 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 GradientColorFilter final : public ColorFilter {
|
||||||
class TintColorFilter final : public ColorFilter {
|
|
||||||
public:
|
public:
|
||||||
~TintColorFilter() override;
|
~GradientColorFilter() override;
|
||||||
|
|
||||||
static sk_sp<TintColorFilter> Make(sk_sp<RenderNode> child,
|
static sk_sp<GradientColorFilter> Make(sk_sp<RenderNode> child,
|
||||||
sk_sp<Color> color0,
|
sk_sp<Color> c0, sk_sp<Color> c1);
|
||||||
sk_sp<Color> color1);
|
static sk_sp<GradientColorFilter> Make(sk_sp<RenderNode> child,
|
||||||
|
std::vector<sk_sp<Color>>);
|
||||||
|
|
||||||
SG_ATTRIBUTE(Weight, float, fWeight)
|
SG_ATTRIBUTE(Weight, float, fWeight)
|
||||||
|
|
||||||
@ -80,14 +83,13 @@ protected:
|
|||||||
sk_sp<SkColorFilter> onRevalidateFilter() override;
|
sk_sp<SkColorFilter> onRevalidateFilter() override;
|
||||||
|
|
||||||
private:
|
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,
|
const std::vector<sk_sp<Color>> fColors;
|
||||||
fColor1;
|
|
||||||
|
|
||||||
float fWeight = 0;
|
float fWeight = 0;
|
||||||
|
|
||||||
typedef ColorFilter INHERITED;
|
using INHERITED = ColorFilter;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace sksg
|
} // namespace sksg
|
||||||
|
@ -9,6 +9,9 @@
|
|||||||
|
|
||||||
#include "SkColorFilter.h"
|
#include "SkColorFilter.h"
|
||||||
#include "SkSGColor.h"
|
#include "SkSGColor.h"
|
||||||
|
#include "SkTableColorFilter.h"
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
namespace sksg {
|
namespace sksg {
|
||||||
|
|
||||||
@ -57,48 +60,49 @@ sk_sp<SkColorFilter> ModeColorFilter::onRevalidateFilter() {
|
|||||||
return SkColorFilter::MakeModeFilter(fColor->getColor(), fMode);
|
return SkColorFilter::MakeModeFilter(fColor->getColor(), fMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
sk_sp<TintColorFilter> TintColorFilter::Make(sk_sp<RenderNode> child,
|
sk_sp<GradientColorFilter> GradientColorFilter::Make(sk_sp<RenderNode> child,
|
||||||
sk_sp<Color> c0, sk_sp<Color> c1) {
|
sk_sp<Color> c0, sk_sp<Color> c1) {
|
||||||
return (child && c0 && c1) ? sk_sp<TintColorFilter>(new TintColorFilter(std::move(child),
|
return Make(std::move(child), { std::move(c0), std::move(c1) });
|
||||||
std::move(c0),
|
}
|
||||||
std::move(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;
|
: nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
TintColorFilter::TintColorFilter(sk_sp<RenderNode> child, sk_sp<Color> c0, sk_sp<Color> c1)
|
GradientColorFilter::GradientColorFilter(sk_sp<RenderNode> child, std::vector<sk_sp<Color>> colors)
|
||||||
: INHERITED(std::move(child))
|
: INHERITED(std::move(child))
|
||||||
, fColor0(std::move(c0))
|
, fColors(std::move(colors)) {
|
||||||
, fColor1(std::move(c1)) {
|
for (const auto& color : fColors) {
|
||||||
this->observeInval(fColor0);
|
this->observeInval(color);
|
||||||
this->observeInval(fColor1);
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TintColorFilter::~TintColorFilter() {
|
GradientColorFilter::~GradientColorFilter() {
|
||||||
this->unobserveInval(fColor0);
|
for (const auto& color : fColors) {
|
||||||
this->unobserveInval(fColor1);
|
this->unobserveInval(color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
sk_sp<SkColorFilter> TintColorFilter::onRevalidateFilter() {
|
namespace {
|
||||||
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
|
// luminance coefficients
|
||||||
static constexpr float kR = 0.2126f,
|
static constexpr float kR = 0.2126f,
|
||||||
kG = 0.7152f,
|
kG = 0.7152f,
|
||||||
kB = 0.0722f;
|
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,
|
const auto dR = c1.fR - c0.fR,
|
||||||
dG = c1.fG - c0.fG,
|
dG = c1.fG - c0.fG,
|
||||||
dB = c1.fB - c0.fB;
|
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]
|
// L = [r,g,b] . [kR,kG,kB]
|
||||||
//
|
//
|
||||||
@ -132,9 +136,77 @@ sk_sp<SkColorFilter> TintColorFilter::onRevalidateFilter() {
|
|||||||
0, 0, 0, 1, 0,
|
0, 0, 0, 1, 0,
|
||||||
};
|
};
|
||||||
|
|
||||||
return SkColorFilter::MakeMixer(nullptr,
|
return SkColorFilter::MakeMatrixFilterRowMajor255(tint_matrix);
|
||||||
SkColorFilter::MakeMatrixFilterRowMajor255(tint_matrix),
|
}
|
||||||
fWeight);
|
|
||||||
|
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
|
} // namespace sksg
|
||||||
|
Loading…
Reference in New Issue
Block a user