[skottie] Effects cleanup pass

Split-off effects into separate CUs.

TBR=
Change-Id: Ic214027d27e1c341085d9a8c74f605caba260630
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/221118
Reviewed-by: Florin Malita <fmalita@chromium.org>
Commit-Queue: Florin Malita <fmalita@chromium.org>
This commit is contained in:
Florin Malita 2019-06-14 16:05:22 -04:00 committed by Skia Commit-Bot
parent d30e039d07
commit 941b9ff09b
16 changed files with 861 additions and 833 deletions

View File

@ -20,7 +20,6 @@ skia_skottie_sources = [
"$_src/SkottieJson.cpp",
"$_src/SkottieJson.h",
"$_src/SkottieLayer.cpp",
"$_src/SkottieLayerEffect.cpp",
"$_src/SkottiePriv.h",
"$_src/SkottiePrecompLayer.cpp",
"$_src/SkottieProperty.cpp",
@ -28,9 +27,16 @@ skia_skottie_sources = [
"$_src/SkottieValue.cpp",
"$_src/SkottieValue.h",
"$_src/effects/DropShadowEffect.cpp",
"$_src/effects/Effects.cpp",
"$_src/effects/Effects.h",
"$_src/effects/FillEffect.cpp",
"$_src/effects/GaussianBlurEffect.cpp",
"$_src/effects/GradientEffect.cpp",
"$_src/effects/LevelsEffect.cpp",
"$_src/effects/TintEffect.cpp",
"$_src/effects/TransformEffect.cpp",
"$_src/effects/TritoneEffect.cpp",
"$_src/text/RangeSelector.cpp",
"$_src/text/RangeSelector.h",

View File

@ -12,18 +12,15 @@
#include "include/core/SkMatrix44.h"
#include "include/core/SkPath.h"
#include "include/core/SkRRect.h"
#include "include/effects/SkTableColorFilter.h"
#include "include/private/SkTo.h"
#include "include/utils/Sk3D.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGColorFilter.h"
#include "modules/sksg/include/SkSGDraw.h"
#include "modules/sksg/include/SkSGGradient.h"
#include "modules/sksg/include/SkSGGroup.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "modules/sksg/include/SkSGPath.h"
#include "modules/sksg/include/SkSGRect.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "modules/sksg/include/SkSGTransform.h"
#include "modules/sksg/include/SkSGTrimEffect.h"
@ -288,58 +285,6 @@ void RadialGradientAdapter::onApply() {
grad->setEndRadius(SkPoint::Distance(this->startPoint(), this->endPoint()));
}
GradientRampEffectAdapter::GradientRampEffectAdapter(sk_sp<sksg::RenderNode> child)
: fRoot(sksg::ShaderEffect::Make(std::move(child))) {}
GradientRampEffectAdapter::~GradientRampEffectAdapter() = default;
void GradientRampEffectAdapter::apply() {
// This adapter manages a SG fragment with the following structure:
//
// - ShaderEffect [fRoot]
// \ GradientShader [fGradient]
// \ child/wrapped fragment
//
// The gradient shader is updated based on the (animatable) intance type (linear/radial).
auto update_gradient = [this] (InstanceType new_type) {
if (new_type != fInstanceType) {
fGradient = new_type == InstanceType::kLinear
? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
: sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
fRoot->setShader(fGradient);
fInstanceType = new_type;
}
fGradient->setColorStops({ {0, fStartColor}, {1, fEndColor} });
};
static constexpr int kLinearShapeValue = 1;
const auto instance_type = (SkScalarRoundToInt(fShape) == kLinearShapeValue)
? InstanceType::kLinear
: InstanceType::kRadial;
// Sync the gradient shader instance if needed.
update_gradient(instance_type);
// Sync instance-dependent gradient params.
if (instance_type == InstanceType::kLinear) {
auto* lg = static_cast<sksg::LinearGradient*>(fGradient.get());
lg->setStartPoint(fStartPoint);
lg->setEndPoint(fEndPoint);
} else {
SkASSERT(instance_type == InstanceType::kRadial);
auto* rg = static_cast<sksg::RadialGradient*>(fGradient.get());
rg->setStartCenter(fStartPoint);
rg->setEndCenter(fStartPoint);
rg->setEndRadius(SkPoint::Distance(fStartPoint, fEndPoint));
}
// TODO: blend, scatter
}
TrimEffectAdapter::TrimEffectAdapter(sk_sp<sksg::TrimEffect> trimEffect)
: fTrimEffect(std::move(trimEffect)) {
SkASSERT(fTrimEffect);
@ -376,158 +321,4 @@ void TrimEffectAdapter::apply() {
fTrimEffect->setMode(mode);
}
DropShadowEffectAdapter::DropShadowEffectAdapter(sk_sp<sksg::DropShadowImageFilter> dropShadow)
: fDropShadow(std::move(dropShadow)) {
SkASSERT(fDropShadow);
}
DropShadowEffectAdapter::~DropShadowEffectAdapter() = default;
void DropShadowEffectAdapter::apply() {
// fColor -> RGB, fOpacity -> A
fDropShadow->setColor(SkColorSetA(fColor, SkTPin(SkScalarRoundToInt(fOpacity), 0, 255)));
// The offset is specified in terms of a bearing angle + distance.
SkScalar rad = SkDegreesToRadians(90 - fDirection);
fDropShadow->setOffset(SkVector::Make( fDistance * SkScalarCos(rad),
-fDistance * SkScalarSin(rad)));
// Close enough to AE.
static constexpr SkScalar kSoftnessToSigmaFactor = 0.3f;
const auto sigma = fSoftness * kSoftnessToSigmaFactor;
fDropShadow->setSigma(SkVector::Make(sigma, sigma));
fDropShadow->setMode(fShadowOnly ? sksg::DropShadowImageFilter::Mode::kShadowOnly
: sksg::DropShadowImageFilter::Mode::kShadowAndForeground);
}
GaussianBlurEffectAdapter::GaussianBlurEffectAdapter(sk_sp<sksg::BlurImageFilter> blur)
: fBlur(std::move(blur)) {
SkASSERT(fBlur);
}
GaussianBlurEffectAdapter::~GaussianBlurEffectAdapter() = default;
void GaussianBlurEffectAdapter::apply() {
static constexpr SkVector kDimensionsMap[] = {
{ 1, 1 }, // 1 -> horizontal and vertical
{ 1, 0 }, // 2 -> horizontal
{ 0, 1 }, // 3 -> vertical
};
const auto dim_index = SkTPin<size_t>(static_cast<size_t>(fDimensions),
1, SK_ARRAY_COUNT(kDimensionsMap)) - 1;
// Close enough to AE.
static constexpr SkScalar kBlurrinessToSigmaFactor = 0.3f;
const auto sigma = fBlurriness * kBlurrinessToSigmaFactor;
fBlur->setSigma({ sigma * kDimensionsMap[dim_index].x(),
sigma * kDimensionsMap[dim_index].y() });
static constexpr SkBlurImageFilter::TileMode kRepeatEdgeMap[] = {
SkBlurImageFilter::kClampToBlack_TileMode, // 0 -> repeat edge pixels: off
SkBlurImageFilter:: kClamp_TileMode, // 1 -> repeat edge pixels: on
};
const auto repeat_index = SkTPin<size_t>(static_cast<size_t>(fRepeatEdge),
0, SK_ARRAY_COUNT(kRepeatEdgeMap) - 1);
fBlur->setTileMode(kRepeatEdgeMap[repeat_index]);
}
// Levels color correction effect.
//
// Maps the selected channels from [inBlack...inWhite] to [outBlack, outWhite],
// based on a gamma exponent.
//
// For [i0..i1] -> [o0..o1]:
//
// c' = o0 + (o1 - o0) * ((c - i0) / (i1 - i0)) ^ G
//
// The output is optionally clipped to the output range.
//
// In/out intervals are clampped to [0..1]. Inversion is allowed.
LevelsEffectAdapter::LevelsEffectAdapter(sk_sp<sksg::RenderNode> child)
: fEffect(sksg::ExternalColorFilter::Make(std::move(child))) {
SkASSERT(fEffect);
}
LevelsEffectAdapter::~LevelsEffectAdapter() = default;
void LevelsEffectAdapter::apply() {
enum LottieChannel {
kRGB_Channel = 1,
kR_Channel = 2,
kG_Channel = 3,
kB_Channel = 4,
kA_Channel = 5,
};
const auto channel = SkScalarTruncToInt(fChannel);
if (channel < kRGB_Channel || channel > kA_Channel) {
fEffect->setColorFilter(nullptr);
return;
}
auto in_0 = SkTPin(fInBlack, 0.0f, 1.0f),
in_1 = SkTPin(fInWhite, 0.0f, 1.0f),
out_0 = SkTPin(fOutBlack, 0.0f, 1.0f),
out_1 = SkTPin(fOutWhite, 0.0f, 1.0f),
g = 1 / SkTMax(fGamma, 0.0f);
float clip[] = {0, 1};
const auto kLottieDoClip = 1;
if (SkScalarTruncToInt(fClipBlack) == kLottieDoClip) {
const auto idx = fOutBlack <= fOutWhite ? 0 : 1;
clip[idx] = out_0;
}
if (SkScalarTruncToInt(fClipWhite) == kLottieDoClip) {
const auto idx = fOutBlack <= fOutWhite ? 1 : 0;
clip[idx] = out_1;
}
SkASSERT(clip[0] <= clip[1]);
auto dIn = in_1 - in_0,
dOut = out_1 - out_0;
if (SkScalarNearlyZero(dIn)) {
// Degenerate dIn == 0 makes the arithmetic below explode.
//
// We could specialize the builder to deal with that case, or we could just
// nudge by epsilon to make it all work. The latter approach is simpler
// and doesn't have any noticeable downsides.
//
// Also nudge in_0 towards 0.5, in case it was sqashed against an extremity.
// This allows for some abrupt transition when the output interval is not
// collapsed, and produces results closer to AE.
static constexpr auto kEpsilon = 2 * SK_ScalarNearlyZero;
dIn += std::copysign(kEpsilon, dIn);
in_0 += std::copysign(kEpsilon, .5f - in_0);
SkASSERT(!SkScalarNearlyZero(dIn));
}
uint8_t lut[256];
auto t = -in_0 / dIn,
dT = 1 / 255.0f / dIn;
// TODO: is linear gamma common-enough to warrant a fast path?
for (size_t i = 0; i < 256; ++i) {
const auto out = out_0 + dOut * std::pow(std::max(t, 0.0f), g);
SkASSERT(!SkScalarIsNaN(out));
lut[i] = static_cast<uint8_t>(std::round(SkTPin(out, clip[0], clip[1]) * 255));
t += dT;
}
fEffect->setColorFilter(SkTableColorFilter::MakeARGB(
channel == kA_Channel ? lut : nullptr,
channel == kR_Channel || channel == kRGB_Channel ? lut : nullptr,
channel == kG_Channel || channel == kRGB_Channel ? lut : nullptr,
channel == kB_Channel || channel == kRGB_Channel ? lut : nullptr
));
}
} // namespace skottie

View File

@ -234,37 +234,6 @@ private:
using INHERITED = GradientAdapter;
};
class GradientRampEffectAdapter final : public SkNVRefCnt<GradientRampEffectAdapter> {
public:
explicit GradientRampEffectAdapter(sk_sp<sksg::RenderNode> child);
~GradientRampEffectAdapter();
ADAPTER_PROPERTY(StartPoint, SkPoint , SkPoint::Make(0, 0))
ADAPTER_PROPERTY(EndPoint , SkPoint , SkPoint::Make(0, 0))
ADAPTER_PROPERTY(StartColor, SkColor , SK_ColorBLACK)
ADAPTER_PROPERTY(EndColor , SkColor , SK_ColorBLACK)
ADAPTER_PROPERTY(Blend , SkScalar, 0)
ADAPTER_PROPERTY(Scatter , SkScalar, 0)
// Really an enum: 1 -> linear, 7 -> radial (?!)
ADAPTER_PROPERTY(Shape , SkScalar, 0)
const sk_sp<sksg::ShaderEffect>& root() const { return fRoot; }
private:
enum class InstanceType {
kNone,
kLinear,
kRadial,
};
void apply();
sk_sp<sksg::ShaderEffect> fRoot;
sk_sp<sksg::Gradient> fGradient;
InstanceType fInstanceType = InstanceType::kNone;
};
class TrimEffectAdapter final : public SkNVRefCnt<TrimEffectAdapter> {
public:
explicit TrimEffectAdapter(sk_sp<sksg::TrimEffect>);
@ -280,79 +249,6 @@ private:
sk_sp<sksg::TrimEffect> fTrimEffect;
};
class DropShadowEffectAdapter final : public SkNVRefCnt<DropShadowEffectAdapter> {
public:
explicit DropShadowEffectAdapter(sk_sp<sksg::DropShadowImageFilter>);
~DropShadowEffectAdapter();
ADAPTER_PROPERTY(Color , SkColor , SK_ColorBLACK)
ADAPTER_PROPERTY(Opacity , SkScalar, 255)
ADAPTER_PROPERTY(Direction , SkScalar, 0)
ADAPTER_PROPERTY(Distance , SkScalar, 0)
ADAPTER_PROPERTY(Softness , SkScalar, 0)
ADAPTER_PROPERTY(ShadowOnly, bool , false)
private:
void apply();
const sk_sp<sksg::DropShadowImageFilter> fDropShadow;
};
class GaussianBlurEffectAdapter final : public SkNVRefCnt<GaussianBlurEffectAdapter> {
public:
explicit GaussianBlurEffectAdapter(sk_sp<sksg::BlurImageFilter>);
~GaussianBlurEffectAdapter();
// AE/BM model properties. These are all animatable/interpolatable.
// Controls the blur sigma.
ADAPTER_PROPERTY(Blurriness, SkScalar, 0)
// Enum selecting the blur dimensionality:
//
// 1 -> horizontal & vertical
// 2 -> horizontal
// 3 -> vertical
//
ADAPTER_PROPERTY(Dimensions, SkScalar, 1)
// Enum selecting edge behavior:
//
// 0 -> clamp
// 1 -> repeat
//
ADAPTER_PROPERTY(RepeatEdge, SkScalar, 0)
private:
void apply();
const sk_sp<sksg::BlurImageFilter> fBlur;
};
class LevelsEffectAdapter final : public SkNVRefCnt<LevelsEffectAdapter> {
public:
explicit LevelsEffectAdapter(sk_sp<sksg::RenderNode> child);
~LevelsEffectAdapter();
// 1: RGB, 2: R, 3: G, 4: B, 5: A
ADAPTER_PROPERTY( Channel, SkScalar, 1)
ADAPTER_PROPERTY( InBlack, SkScalar, 0)
ADAPTER_PROPERTY( InWhite, SkScalar, 1)
ADAPTER_PROPERTY( OutBlack, SkScalar, 0)
ADAPTER_PROPERTY( OutWhite, SkScalar, 1)
ADAPTER_PROPERTY( Gamma, SkScalar, 1)
// 1: clip, 2,3: don't clip
ADAPTER_PROPERTY(ClipBlack, SkScalar, 1)
ADAPTER_PROPERTY(ClipWhite, SkScalar, 1)
const sk_sp<sksg::ExternalColorFilter>& root() const { return fEffect; }
private:
void apply();
sk_sp<sksg::ExternalColorFilter> fEffect;
};
} // namespace skottie
#endif // SkottieAdapter_DEFINED

View File

@ -15,6 +15,7 @@
#include "modules/skottie/src/SkottieAdapter.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/skottie/src/effects/Effects.h"
#include "modules/sksg/include/SkSGClipEffect.h"
#include "modules/sksg/include/SkSGDraw.h"
#include "modules/sksg/include/SkSGGroup.h"
@ -625,7 +626,7 @@ sk_sp<sksg::RenderNode> AnimationBuilder::attachLayer(const skjson::ObjectValue*
// Optional layer effects.
if (const skjson::ArrayValue* jeffects = (*jlayer)["ef"]) {
layer = this->attachLayerEffects(*jeffects, &layer_animators, std::move(layer));
layer = EffectBuilder(this, &layer_animators).attachEffects(*jeffects, std::move(layer));
}
// Attach the transform after effects, when needed.

View File

@ -1,485 +0,0 @@
/*
* Copyright 2018 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/Effects.h"
#include "modules/skottie/src/SkottieAdapter.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGColorFilter.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "src/utils/SkJSON.h"
namespace skottie {
namespace internal {
namespace {
sk_sp<sksg::RenderNode> AttachGradientLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kStartPoint_Index = 0,
kStartColor_Index = 1,
kEndPoint_Index = 2,
kEndColor_Index = 3,
kRampShape_Index = 4,
kRampScatter_Index = 5,
kBlendRatio_Index = 6,
kMax_Index = kBlendRatio_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* p0 = jprops[ kStartPoint_Index];
const skjson::ObjectValue* p1 = jprops[ kEndPoint_Index];
const skjson::ObjectValue* c0 = jprops[ kStartColor_Index];
const skjson::ObjectValue* c1 = jprops[ kEndColor_Index];
const skjson::ObjectValue* sh = jprops[ kRampShape_Index];
const skjson::ObjectValue* bl = jprops[ kBlendRatio_Index];
const skjson::ObjectValue* sc = jprops[kRampScatter_Index];
if (!p0 || !p1 || !c0 || !c1 || !sh || !bl || !sc) {
return nullptr;
}
auto adapter = sk_make_sp<GradientRampEffectAdapter>(std::move(layer));
abuilder->bindProperty<VectorValue>((*p0)["v"], ascope,
[adapter](const VectorValue& p0) {
adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(p0));
});
abuilder->bindProperty<VectorValue>((*p1)["v"], ascope,
[adapter](const VectorValue& p1) {
adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(p1));
});
abuilder->bindProperty<VectorValue>((*c0)["v"], ascope,
[adapter](const VectorValue& c0) {
adapter->setStartColor(ValueTraits<VectorValue>::As<SkColor>(c0));
});
abuilder->bindProperty<VectorValue>((*c1)["v"], ascope,
[adapter](const VectorValue& c1) {
adapter->setEndColor(ValueTraits<VectorValue>::As<SkColor>(c1));
});
abuilder->bindProperty<ScalarValue>((*sh)["v"], ascope,
[adapter](const ScalarValue& shape) {
adapter->setShape(shape);
});
abuilder->bindProperty<ScalarValue>((*sh)["v"], ascope,
[adapter](const ScalarValue& blend) {
adapter->setBlend(blend);
});
abuilder->bindProperty<ScalarValue>((*sc)["v"], ascope,
[adapter](const ScalarValue& scatter) {
adapter->setScatter(scatter);
});
return adapter->root();
}
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::GradientColorFilter::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> 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,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kFillMask_Index = 0,
kAllMasks_Index = 1,
kColor_Index = 2,
kInvert_Index = 3,
kHFeather_Index = 4,
kVFeather_Index = 5,
kOpacity_Index = 6,
kMax_Index = kOpacity_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* color_prop = jprops[ kColor_Index];
const skjson::ObjectValue* opacity_prop = jprops[kOpacity_Index];
if (!color_prop || !opacity_prop) {
return nullptr;
}
sk_sp<sksg::Color> color_node = abuilder->attachColor(*color_prop, ascope, "v");
if (!color_node) {
return nullptr;
}
abuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], ascope,
[color_node](const ScalarValue& o) {
const auto c = color_node->getColor();
const auto a = sk_float_round2int_no_saturate(SkTPin(o, 0.0f, 1.0f) * 255);
color_node->setColor(SkColorSetA(c, a));
});
return sksg::ModeColorFilter::Make(std::move(layer),
std::move(color_node),
SkBlendMode::kSrcIn);
}
sk_sp<sksg::RenderNode> AttachDropShadowLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kShadowColor_Index = 0,
kOpacity_Index = 1,
kDirection_Index = 2,
kDistance_Index = 3,
kSoftness_Index = 4,
kShadowOnly_Index = 5,
kMax_Index = kShadowOnly_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* color_prop = jprops[kShadowColor_Index];
const skjson::ObjectValue* opacity_prop = jprops[ kOpacity_Index];
const skjson::ObjectValue* direction_prop = jprops[ kDirection_Index];
const skjson::ObjectValue* distance_prop = jprops[ kDistance_Index];
const skjson::ObjectValue* softness_prop = jprops[ kSoftness_Index];
const skjson::ObjectValue* shadow_only_prop = jprops[ kShadowOnly_Index];
if (!color_prop ||
!opacity_prop ||
!direction_prop ||
!distance_prop ||
!softness_prop ||
!shadow_only_prop) {
return nullptr;
}
auto shadow_effect = sksg::DropShadowImageFilter::Make();
auto shadow_adapter = sk_make_sp<DropShadowEffectAdapter>(shadow_effect);
abuilder->bindProperty<VectorValue>((*color_prop)["v"], ascope,
[shadow_adapter](const VectorValue& c) {
shadow_adapter->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
});
abuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& o) {
shadow_adapter->setOpacity(o);
});
abuilder->bindProperty<ScalarValue>((*direction_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& d) {
shadow_adapter->setDirection(d);
});
abuilder->bindProperty<ScalarValue>((*distance_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& d) {
shadow_adapter->setDistance(d);
});
abuilder->bindProperty<ScalarValue>((*softness_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& s) {
shadow_adapter->setSoftness(s);
});
abuilder->bindProperty<ScalarValue>((*shadow_only_prop)["v"], ascope,
[shadow_adapter](const ScalarValue& s) {
shadow_adapter->setShadowOnly(SkToBool(s));
});
return sksg::ImageFilterEffect::Make(std::move(layer), std::move(shadow_effect));
}
sk_sp<sksg::RenderNode> AttachGaussianBlurLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kBlurriness_Index = 0,
kDimensions_Index = 1,
kRepeatEdge_Index = 2,
kMax_Index = kRepeatEdge_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* blurriness_prop = jprops[kBlurriness_Index];
const skjson::ObjectValue* dimensions_prop = jprops[kDimensions_Index];
const skjson::ObjectValue* repeatedge_prop = jprops[kRepeatEdge_Index];
if (!blurriness_prop || !dimensions_prop || !repeatedge_prop) {
return nullptr;
}
auto blur_effect = sksg::BlurImageFilter::Make();
auto blur_addapter = sk_make_sp<GaussianBlurEffectAdapter>(blur_effect);
abuilder->bindProperty<ScalarValue>((*blurriness_prop)["v"], ascope,
[blur_addapter](const ScalarValue& b) {
blur_addapter->setBlurriness(b);
});
abuilder->bindProperty<ScalarValue>((*dimensions_prop)["v"], ascope,
[blur_addapter](const ScalarValue& d) {
blur_addapter->setDimensions(d);
});
abuilder->bindProperty<ScalarValue>((*repeatedge_prop)["v"], ascope,
[blur_addapter](const ScalarValue& r) {
blur_addapter->setRepeatEdge(r);
});
return sksg::ImageFilterEffect::Make(std::move(layer), std::move(blur_effect));
}
sk_sp<sksg::RenderNode> AttachLevelsLayerEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
enum : size_t {
kChannel_Index = 0,
// ??? = 1,
kInputBlack_Index = 2,
kInputWhite_Index = 3,
kGamma_Index = 4,
kOutputBlack_Index = 5,
kOutputWhite_Index = 6,
kClipToOutBlack_Index = 7,
kClipToOutWhite_Index = 8,
kMax_Index = kClipToOutWhite_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* channel_prop = jprops[ kChannel_Index];
const skjson::ObjectValue* iblack_prop = jprops[ kInputBlack_Index];
const skjson::ObjectValue* iwhite_prop = jprops[ kInputWhite_Index];
const skjson::ObjectValue* gamma_prop = jprops[ kGamma_Index];
const skjson::ObjectValue* oblack_prop = jprops[ kOutputBlack_Index];
const skjson::ObjectValue* owhite_prop = jprops[ kOutputWhite_Index];
const skjson::ObjectValue* clip_black_prop = jprops[kClipToOutBlack_Index];
const skjson::ObjectValue* clip_white_prop = jprops[kClipToOutWhite_Index];
if (!channel_prop || !iblack_prop || !iwhite_prop || !gamma_prop ||
!oblack_prop || !owhite_prop || !clip_black_prop || !clip_white_prop) {
return nullptr;
}
auto adapter = sk_make_sp<LevelsEffectAdapter>(std::move(layer));
abuilder->bindProperty<ScalarValue>((*channel_prop)["v"], ascope,
[adapter](const ScalarValue& channel) {
adapter->setChannel(channel);
});
abuilder->bindProperty<ScalarValue>((*iblack_prop)["v"], ascope,
[adapter](const ScalarValue& ib) {
adapter->setInBlack(ib);
});
abuilder->bindProperty<ScalarValue>((*iwhite_prop)["v"], ascope,
[adapter](const ScalarValue& iw) {
adapter->setInWhite(iw);
});
abuilder->bindProperty<ScalarValue>((*oblack_prop)["v"], ascope,
[adapter](const ScalarValue& ob) {
adapter->setOutBlack(ob);
});
abuilder->bindProperty<ScalarValue>((*owhite_prop)["v"], ascope,
[adapter](const ScalarValue& ow) {
adapter->setOutWhite(ow);
});
abuilder->bindProperty<ScalarValue>((*gamma_prop)["v"], ascope,
[adapter](const ScalarValue& g) {
adapter->setGamma(g);
});
abuilder->bindProperty<ScalarValue>((*clip_black_prop)["v"], ascope,
[adapter](const ScalarValue& cb) {
adapter->setClipBlack(cb);
});
abuilder->bindProperty<ScalarValue>((*clip_white_prop)["v"], ascope,
[adapter](const ScalarValue& cw) {
adapter->setClipWhite(cw);
});
return adapter->root();
}
using EffectBuilderT = sk_sp<sksg::RenderNode> (*)(const skjson::ArrayValue&,
const AnimationBuilder*,
AnimatorScope*,
sk_sp<sksg::RenderNode>);
EffectBuilderT FindEffectBuilder(const AnimationBuilder* abuilder,
const skjson::ObjectValue& jeffect) {
// First, try assigned types.
enum : int32_t {
kTint_Effect = 20,
kFill_Effect = 21,
kTritone_Effect = 23,
kDropShadow_Effect = 25,
kGaussianBlur_Effect = 29,
};
const auto ty = ParseDefault<int>(jeffect["ty"], -1);
switch (ty) {
case kTint_Effect:
return AttachTintLayerEffect;
case kFill_Effect:
return AttachFillLayerEffect;
case kTritone_Effect:
return AttachTritoneLayerEffect;
case kDropShadow_Effect:
return AttachDropShadowLayerEffect;
case kGaussianBlur_Effect:
return AttachGaussianBlurLayerEffect;
default:
break;
}
// Some effects don't have an assigned type, but the data is still present.
// Try a name-based lookup.
if (const skjson::StringValue* mn = jeffect["mn"]) {
if (!strcmp(mn->begin(), "ADBE Ramp")) {
return AttachGradientLayerEffect;
}
if (!strcmp(mn->begin(), "ADBE Easy Levels2")) {
return AttachLevelsLayerEffect;
}
if (!strcmp(mn->begin(), "ADBE Geometry2")) {
return AttachTransformEffect;
}
}
abuilder->log(Logger::Level::kWarning, nullptr, "Unsupported layer effect type: %d.", ty);
return nullptr;
}
} // namespace
sk_sp<sksg::RenderNode> AnimationBuilder::attachLayerEffects(const skjson::ArrayValue& jeffects,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) const {
if (!layer) {
return nullptr;
}
for (const skjson::ObjectValue* jeffect : jeffects) {
if (!jeffect) {
continue;
}
const auto builder = FindEffectBuilder(this, *jeffect);
const skjson::ArrayValue* jprops = (*jeffect)["ef"];
if (!builder || !jprops) {
continue;
}
layer = builder(*jprops, this, ascope, std::move(layer));
if (!layer) {
this->log(Logger::Level::kError, jeffect, "Invalid layer effect.");
return nullptr;
}
}
return layer;
}
} // namespace internal
} // namespace skottie

View File

@ -101,8 +101,6 @@ private:
sk_sp<sksg::RenderNode> attachComposition(const skjson::ObjectValue&, AnimatorScope*) const;
sk_sp<sksg::RenderNode> attachLayer(const skjson::ObjectValue*, AttachLayerContext*) const;
sk_sp<sksg::RenderNode> attachLayerEffects(const skjson::ArrayValue& jeffects, AnimatorScope*,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachBlendMode(const skjson::ObjectValue&,
sk_sp<sksg::RenderNode>) const;

View File

@ -0,0 +1,101 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/Effects.h"
#include "modules/skottie/src/SkottieAdapter.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "src/utils/SkJSON.h"
namespace skottie {
namespace internal {
namespace {
class DropShadowAdapter final : public SkNVRefCnt<DropShadowAdapter> {
public:
explicit DropShadowAdapter(sk_sp<sksg::DropShadowImageFilter> dropShadow)
: fDropShadow(std::move(dropShadow)) {
SkASSERT(fDropShadow);
}
ADAPTER_PROPERTY(Color , SkColor , SK_ColorBLACK)
ADAPTER_PROPERTY(Opacity , SkScalar, 255)
ADAPTER_PROPERTY(Direction , SkScalar, 0)
ADAPTER_PROPERTY(Distance , SkScalar, 0)
ADAPTER_PROPERTY(Softness , SkScalar, 0)
ADAPTER_PROPERTY(ShadowOnly, bool , false)
private:
void apply() {
// fColor -> RGB, fOpacity -> A
fDropShadow->setColor(SkColorSetA(fColor, SkTPin(SkScalarRoundToInt(fOpacity), 0, 255)));
// The offset is specified in terms of a bearing angle + distance.
SkScalar rad = SkDegreesToRadians(90 - fDirection);
fDropShadow->setOffset(SkVector::Make( fDistance * SkScalarCos(rad),
-fDistance * SkScalarSin(rad)));
// Close enough to AE.
static constexpr SkScalar kSoftnessToSigmaFactor = 0.3f;
const auto sigma = fSoftness * kSoftnessToSigmaFactor;
fDropShadow->setSigma(SkVector::Make(sigma, sigma));
fDropShadow->setMode(fShadowOnly ? sksg::DropShadowImageFilter::Mode::kShadowOnly
: sksg::DropShadowImageFilter::Mode::kShadowAndForeground);
}
const sk_sp<sksg::DropShadowImageFilter> fDropShadow;
};
} // anonymous ns
sk_sp<sksg::RenderNode> EffectBuilder::attachDropShadowEffect(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
enum : size_t {
kShadowColor_Index = 0,
kOpacity_Index = 1,
kDirection_Index = 2,
kDistance_Index = 3,
kSoftness_Index = 4,
kShadowOnly_Index = 5,
};
auto shadow_effect = sksg::DropShadowImageFilter::Make();
auto shadow_adapter = sk_make_sp<DropShadowAdapter>(shadow_effect);
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kShadowColor_Index), fScope,
[shadow_adapter](const VectorValue& c) {
shadow_adapter->setColor(ValueTraits<VectorValue>::As<SkColor>(c));
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOpacity_Index), fScope,
[shadow_adapter](const ScalarValue& o) {
shadow_adapter->setOpacity(o);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDirection_Index), fScope,
[shadow_adapter](const ScalarValue& d) {
shadow_adapter->setDirection(d);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDistance_Index), fScope,
[shadow_adapter](const ScalarValue& d) {
shadow_adapter->setDistance(d);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSoftness_Index), fScope,
[shadow_adapter](const ScalarValue& s) {
shadow_adapter->setSoftness(s);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kShadowOnly_Index), fScope,
[shadow_adapter](const ScalarValue& s) {
shadow_adapter->setShadowOnly(SkToBool(s));
});
return sksg::ImageFilterEffect::Make(std::move(layer), std::move(shadow_effect));
}
} // namespace internal
} // namespace skottie

View File

@ -7,11 +7,96 @@
#include "modules/skottie/src/effects/Effects.h"
#include "modules/skottie/src/SkottieJson.h"
#include "modules/sksg/include/SkSGRenderNode.h"
#include "src/utils/SkJSON.h"
namespace skottie {
namespace internal {
EffectBuilder::EffectBuilder(const AnimationBuilder* abuilder, AnimatorScope* ascope)
: fBuilder(abuilder)
, fScope(ascope) {}
EffectBuilder::EffectBuilderT EffectBuilder::findBuilder(const skjson::ObjectValue& jeffect) const {
// First, try assigned types.
enum : int32_t {
kTint_Effect = 20,
kFill_Effect = 21,
kTritone_Effect = 23,
kDropShadow_Effect = 25,
kGaussianBlur_Effect = 29,
};
const auto ty = ParseDefault<int>(jeffect["ty"], -1);
switch (ty) {
case kTint_Effect:
return &EffectBuilder::attachTintEffect;
case kFill_Effect:
return &EffectBuilder::attachFillEffect;
case kTritone_Effect:
return &EffectBuilder::attachTritoneEffect;
case kDropShadow_Effect:
return &EffectBuilder::attachDropShadowEffect;
case kGaussianBlur_Effect:
return &EffectBuilder::attachGaussianBlurEffect;
default:
break;
}
// Some effects don't have an assigned type, but the data is still present.
// Try a name-based lookup.
static constexpr char kGradientEffectMN[] = "ADBE Ramp",
kLevelsEffectMN[] = "ADBE Easy Levels2",
kTransformEffectMN[] = "ADBE Geometry2";
if (const skjson::StringValue* mn = jeffect["mn"]) {
if (!strcmp(mn->begin(), kGradientEffectMN)) {
return &EffectBuilder::attachGradientEffect;
}
if (!strcmp(mn->begin(), kLevelsEffectMN)) {
return &EffectBuilder::attachLevelsEffect;
}
if (!strcmp(mn->begin(), kTransformEffectMN)) {
return &EffectBuilder::attachTransformEffect;
}
}
fBuilder->log(Logger::Level::kWarning, nullptr, "Unsupported layer effect type: %d.", ty);
return nullptr;
}
sk_sp<sksg::RenderNode> EffectBuilder::attachEffects(const skjson::ArrayValue& jeffects,
sk_sp<sksg::RenderNode> layer) const {
if (!layer) {
return nullptr;
}
for (const skjson::ObjectValue* jeffect : jeffects) {
if (!jeffect) {
continue;
}
const auto builder = this->findBuilder(*jeffect);
const skjson::ArrayValue* jprops = (*jeffect)["ef"];
if (!builder || !jprops) {
continue;
}
layer = (this->*builder)(*jprops, std::move(layer));
if (!layer) {
fBuilder->log(Logger::Level::kError, jeffect, "Invalid layer effect.");
return nullptr;
}
}
return layer;
}
const skjson::Value& EffectBuilder::GetPropValue(const skjson::ArrayValue& jprops,
size_t prop_index) {
static skjson::NullValue kNull;

View File

@ -13,20 +13,42 @@
namespace skottie {
namespace internal {
class AnimationBuilder;
// TODO: relocate SkottieLayerEffect builder logic here.
class EffectBuilder final : public SkNoncopyable {
public:
EffectBuilder(const AnimationBuilder*, AnimatorScope*);
sk_sp<sksg::RenderNode> attachEffects(const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
private:
using EffectBuilderT = sk_sp<sksg::RenderNode>(EffectBuilder::*)(const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachTintEffect (const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachFillEffect (const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachTritoneEffect (const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachDropShadowEffect (const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachGaussianBlurEffect(const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachGradientEffect (const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachLevelsEffect (const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
sk_sp<sksg::RenderNode> attachTransformEffect (const skjson::ArrayValue&,
sk_sp<sksg::RenderNode>) const;
EffectBuilderT findBuilder(const skjson::ObjectValue&) const;
static const skjson::Value& GetPropValue(const skjson::ArrayValue& jprops, size_t prop_index);
const AnimationBuilder* fBuilder;
AnimatorScope* fScope;
};
sk_sp<sksg::RenderNode> AttachTransformEffect(const skjson::ArrayValue&,
const AnimationBuilder*,
AnimatorScope*,
sk_sp<sksg::RenderNode>);
} // namespace internal
} // namespace skottie

View File

@ -0,0 +1,59 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/Effects.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGColorFilter.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "src/utils/SkJSON.h"
namespace skottie {
namespace internal {
sk_sp<sksg::RenderNode> EffectBuilder::attachFillEffect(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
enum : size_t {
kFillMask_Index = 0,
kAllMasks_Index = 1,
kColor_Index = 2,
kInvert_Index = 3,
kHFeather_Index = 4,
kVFeather_Index = 5,
kOpacity_Index = 6,
kMax_Index = kOpacity_Index,
};
if (jprops.size() <= kMax_Index) {
return nullptr;
}
const skjson::ObjectValue* color_prop = jprops[ kColor_Index];
const skjson::ObjectValue* opacity_prop = jprops[kOpacity_Index];
if (!color_prop || !opacity_prop) {
return nullptr;
}
sk_sp<sksg::Color> color_node = fBuilder->attachColor(*color_prop, fScope, "v");
if (!color_node) {
return nullptr;
}
fBuilder->bindProperty<ScalarValue>((*opacity_prop)["v"], fScope,
[color_node](const ScalarValue& o) {
const auto c = color_node->getColor();
const auto a = sk_float_round2int_no_saturate(SkTPin(o, 0.0f, 1.0f) * 255);
color_node->setColor(SkColorSetA(c, a));
});
return sksg::ModeColorFilter::Make(std::move(layer),
std::move(color_node),
SkBlendMode::kSrcIn);
}
} // namespace internal
} // namespace skottie

View File

@ -0,0 +1,110 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/Effects.h"
#include "include/effects/SkBlurImageFilter.h"
#include "modules/skottie/src/SkottieAdapter.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "src/utils/SkJSON.h"
namespace skottie {
namespace internal {
namespace {
class GaussianBlurEffectAdapter final : public SkNVRefCnt<GaussianBlurEffectAdapter> {
public:
explicit GaussianBlurEffectAdapter(sk_sp<sksg::BlurImageFilter> blur)
: fBlur(std::move(blur)) {
SkASSERT(fBlur);
}
// AE/BM model properties. These are all animatable/interpolatable.
// Controls the blur sigma.
ADAPTER_PROPERTY(Blurriness, SkScalar, 0)
// Enum selecting the blur dimensionality:
//
// 1 -> horizontal & vertical
// 2 -> horizontal
// 3 -> vertical
//
ADAPTER_PROPERTY(Dimensions, SkScalar, 1)
// Enum selecting edge behavior:
//
// 0 -> clamp
// 1 -> repeat
//
ADAPTER_PROPERTY(RepeatEdge, SkScalar, 0)
private:
void apply() {
static constexpr SkVector kDimensionsMap[] = {
{ 1, 1 }, // 1 -> horizontal and vertical
{ 1, 0 }, // 2 -> horizontal
{ 0, 1 }, // 3 -> vertical
};
const auto dim_index = SkTPin<size_t>(static_cast<size_t>(fDimensions),
1, SK_ARRAY_COUNT(kDimensionsMap)) - 1;
// Close enough to AE.
static constexpr SkScalar kBlurrinessToSigmaFactor = 0.3f;
const auto sigma = fBlurriness * kBlurrinessToSigmaFactor;
fBlur->setSigma({ sigma * kDimensionsMap[dim_index].x(),
sigma * kDimensionsMap[dim_index].y() });
static constexpr SkBlurImageFilter::TileMode kRepeatEdgeMap[] = {
SkBlurImageFilter::kClampToBlack_TileMode, // 0 -> repeat edge pixels: off
SkBlurImageFilter:: kClamp_TileMode, // 1 -> repeat edge pixels: on
};
const auto repeat_index = SkTPin<size_t>(static_cast<size_t>(fRepeatEdge),
0, SK_ARRAY_COUNT(kRepeatEdgeMap) - 1);
fBlur->setTileMode(kRepeatEdgeMap[repeat_index]);
}
const sk_sp<sksg::BlurImageFilter> fBlur;
};
} // anonymous ns
sk_sp<sksg::RenderNode> EffectBuilder::attachGaussianBlurEffect(
const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
enum : size_t {
kBlurriness_Index = 0,
kDimensions_Index = 1,
kRepeatEdge_Index = 2,
};
auto blur_effect = sksg::BlurImageFilter::Make();
auto blur_addapter = sk_make_sp<GaussianBlurEffectAdapter>(blur_effect);
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kBlurriness_Index), fScope,
[blur_addapter](const ScalarValue& b) {
blur_addapter->setBlurriness(b);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kDimensions_Index), fScope,
[blur_addapter](const ScalarValue& d) {
blur_addapter->setDimensions(d);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRepeatEdge_Index), fScope,
[blur_addapter](const ScalarValue& r) {
blur_addapter->setRepeatEdge(r);
});
return sksg::ImageFilterEffect::Make(std::move(layer), std::move(blur_effect));
}
} // namespace internal
} // namespace skottie

View File

@ -0,0 +1,146 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/Effects.h"
#include "modules/skottie/src/SkottieAdapter.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGGradient.h"
#include "modules/sksg/include/SkSGRenderEffect.h"
#include "src/utils/SkJSON.h"
namespace skottie {
namespace internal {
namespace {
class GradientRampEffectAdapter final : public SkNVRefCnt<GradientRampEffectAdapter> {
public:
explicit GradientRampEffectAdapter(sk_sp<sksg::RenderNode> child)
: fRoot(sksg::ShaderEffect::Make(std::move(child))) {}
ADAPTER_PROPERTY(StartPoint, SkPoint , SkPoint::Make(0, 0))
ADAPTER_PROPERTY(EndPoint , SkPoint , SkPoint::Make(0, 0))
ADAPTER_PROPERTY(StartColor, SkColor , SK_ColorBLACK)
ADAPTER_PROPERTY(EndColor , SkColor , SK_ColorBLACK)
ADAPTER_PROPERTY(Blend , SkScalar, 0)
ADAPTER_PROPERTY(Scatter , SkScalar, 0)
// Really an enum: 1 -> linear, 7 -> radial (?!)
ADAPTER_PROPERTY(Shape , SkScalar, 0)
const sk_sp<sksg::ShaderEffect>& root() const { return fRoot; }
private:
enum class InstanceType {
kNone,
kLinear,
kRadial,
};
void apply() {
// This adapter manages a SG fragment with the following structure:
//
// - ShaderEffect [fRoot]
// \ GradientShader [fGradient]
// \ child/wrapped fragment
//
// The gradient shader is updated based on the (animatable) instance type (linear/radial).
auto update_gradient = [this] (InstanceType new_type) {
if (new_type != fInstanceType) {
fGradient = new_type == InstanceType::kLinear
? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
: sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
fRoot->setShader(fGradient);
fInstanceType = new_type;
}
fGradient->setColorStops({ {0, fStartColor}, {1, fEndColor} });
};
static constexpr int kLinearShapeValue = 1;
const auto instance_type = (SkScalarRoundToInt(fShape) == kLinearShapeValue)
? InstanceType::kLinear
: InstanceType::kRadial;
// Sync the gradient shader instance if needed.
update_gradient(instance_type);
// Sync instance-dependent gradient params.
if (instance_type == InstanceType::kLinear) {
auto* lg = static_cast<sksg::LinearGradient*>(fGradient.get());
lg->setStartPoint(fStartPoint);
lg->setEndPoint(fEndPoint);
} else {
SkASSERT(instance_type == InstanceType::kRadial);
auto* rg = static_cast<sksg::RadialGradient*>(fGradient.get());
rg->setStartCenter(fStartPoint);
rg->setEndCenter(fStartPoint);
rg->setEndRadius(SkPoint::Distance(fStartPoint, fEndPoint));
}
// TODO: blend, scatter
}
sk_sp<sksg::ShaderEffect> fRoot;
sk_sp<sksg::Gradient> fGradient;
InstanceType fInstanceType = InstanceType::kNone;
};
} // anonymous ns
sk_sp<sksg::RenderNode> EffectBuilder::attachGradientEffect(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
enum : size_t {
kStartPoint_Index = 0,
kStartColor_Index = 1,
kEndPoint_Index = 2,
kEndColor_Index = 3,
kRampShape_Index = 4,
kRampScatter_Index = 5,
kBlendRatio_Index = 6,
};
auto adapter = sk_make_sp<GradientRampEffectAdapter>(std::move(layer));
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kStartPoint_Index), fScope,
[adapter](const VectorValue& p0) {
adapter->setStartPoint(ValueTraits<VectorValue>::As<SkPoint>(p0));
});
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kEndPoint_Index), fScope,
[adapter](const VectorValue& p1) {
adapter->setEndPoint(ValueTraits<VectorValue>::As<SkPoint>(p1));
});
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kStartColor_Index), fScope,
[adapter](const VectorValue& c0) {
adapter->setStartColor(ValueTraits<VectorValue>::As<SkColor>(c0));
});
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kEndColor_Index), fScope,
[adapter](const VectorValue& c1) {
adapter->setEndColor(ValueTraits<VectorValue>::As<SkColor>(c1));
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRampShape_Index), fScope,
[adapter](const ScalarValue& shape) {
adapter->setShape(shape);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kBlendRatio_Index), fScope,
[adapter](const ScalarValue& blend) {
adapter->setBlend(blend);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRampScatter_Index), fScope,
[adapter](const ScalarValue& scatter) {
adapter->setScatter(scatter);
});
return adapter->root();
}
} // namespace internal
} // namespace skottie

View File

@ -0,0 +1,191 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/Effects.h"
#include "include/effects/SkTableColorFilter.h"
#include "modules/skottie/src/SkottieAdapter.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGColorFilter.h"
#include "src/utils/SkJSON.h"
#include <cmath>
namespace skottie {
namespace internal {
// Levels color correction effect.
//
// Maps the selected channels from [inBlack...inWhite] to [outBlack, outWhite],
// based on a gamma exponent.
//
// For [i0..i1] -> [o0..o1]:
//
// c' = o0 + (o1 - o0) * ((c - i0) / (i1 - i0)) ^ G
//
// The output is optionally clipped to the output range.
//
// In/out intervals are clampped to [0..1]. Inversion is allowed.
namespace {
class LevelsEffectAdapter final : public SkNVRefCnt<LevelsEffectAdapter> {
public:
explicit LevelsEffectAdapter(sk_sp<sksg::RenderNode> child)
: fEffect(sksg::ExternalColorFilter::Make(std::move(child))) {
SkASSERT(fEffect);
}
// 1: RGB, 2: R, 3: G, 4: B, 5: A
ADAPTER_PROPERTY( Channel, SkScalar, 1)
ADAPTER_PROPERTY( InBlack, SkScalar, 0)
ADAPTER_PROPERTY( InWhite, SkScalar, 1)
ADAPTER_PROPERTY( OutBlack, SkScalar, 0)
ADAPTER_PROPERTY( OutWhite, SkScalar, 1)
ADAPTER_PROPERTY( Gamma, SkScalar, 1)
// 1: clip, 2,3: don't clip
ADAPTER_PROPERTY(ClipBlack, SkScalar, 1)
ADAPTER_PROPERTY(ClipWhite, SkScalar, 1)
const sk_sp<sksg::ExternalColorFilter>& root() const { return fEffect; }
private:
void apply() {
enum LottieChannel {
kRGB_Channel = 1,
kR_Channel = 2,
kG_Channel = 3,
kB_Channel = 4,
kA_Channel = 5,
};
const auto channel = SkScalarTruncToInt(fChannel);
if (channel < kRGB_Channel || channel > kA_Channel) {
fEffect->setColorFilter(nullptr);
return;
}
auto in_0 = SkTPin(fInBlack, 0.0f, 1.0f),
in_1 = SkTPin(fInWhite, 0.0f, 1.0f),
out_0 = SkTPin(fOutBlack, 0.0f, 1.0f),
out_1 = SkTPin(fOutWhite, 0.0f, 1.0f),
g = 1 / SkTMax(fGamma, 0.0f);
float clip[] = {0, 1};
const auto kLottieDoClip = 1;
if (SkScalarTruncToInt(fClipBlack) == kLottieDoClip) {
const auto idx = fOutBlack <= fOutWhite ? 0 : 1;
clip[idx] = out_0;
}
if (SkScalarTruncToInt(fClipWhite) == kLottieDoClip) {
const auto idx = fOutBlack <= fOutWhite ? 1 : 0;
clip[idx] = out_1;
}
SkASSERT(clip[0] <= clip[1]);
auto dIn = in_1 - in_0,
dOut = out_1 - out_0;
if (SkScalarNearlyZero(dIn)) {
// Degenerate dIn == 0 makes the arithmetic below explode.
//
// We could specialize the builder to deal with that case, or we could just
// nudge by epsilon to make it all work. The latter approach is simpler
// and doesn't have any noticeable downsides.
//
// Also nudge in_0 towards 0.5, in case it was sqashed against an extremity.
// This allows for some abrupt transition when the output interval is not
// collapsed, and produces results closer to AE.
static constexpr auto kEpsilon = 2 * SK_ScalarNearlyZero;
dIn += std::copysign(kEpsilon, dIn);
in_0 += std::copysign(kEpsilon, .5f - in_0);
SkASSERT(!SkScalarNearlyZero(dIn));
}
uint8_t lut[256];
auto t = -in_0 / dIn,
dT = 1 / 255.0f / dIn;
// TODO: is linear gamma common-enough to warrant a fast path?
for (size_t i = 0; i < 256; ++i) {
const auto out = out_0 + dOut * std::pow(std::max(t, 0.0f), g);
SkASSERT(!SkScalarIsNaN(out));
lut[i] = static_cast<uint8_t>(std::round(SkTPin(out, clip[0], clip[1]) * 255));
t += dT;
}
fEffect->setColorFilter(SkTableColorFilter::MakeARGB(
channel == kA_Channel ? lut : nullptr,
channel == kR_Channel || channel == kRGB_Channel ? lut : nullptr,
channel == kG_Channel || channel == kRGB_Channel ? lut : nullptr,
channel == kB_Channel || channel == kRGB_Channel ? lut : nullptr
));
}
sk_sp<sksg::ExternalColorFilter> fEffect;
};
} // anonymous ns
sk_sp<sksg::RenderNode> EffectBuilder::attachLevelsEffect(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
enum : size_t {
kChannel_Index = 0,
// ??? = 1,
kInputBlack_Index = 2,
kInputWhite_Index = 3,
kGamma_Index = 4,
kOutputBlack_Index = 5,
kOutputWhite_Index = 6,
kClipToOutBlack_Index = 7,
kClipToOutWhite_Index = 8,
};
auto adapter = sk_make_sp<LevelsEffectAdapter>(std::move(layer));
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kChannel_Index), fScope,
[adapter](const ScalarValue& channel) {
adapter->setChannel(channel);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kInputBlack_Index), fScope,
[adapter](const ScalarValue& ib) {
adapter->setInBlack(ib);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kInputWhite_Index), fScope,
[adapter](const ScalarValue& iw) {
adapter->setInWhite(iw);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputBlack_Index), fScope,
[adapter](const ScalarValue& ob) {
adapter->setOutBlack(ob);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputWhite_Index), fScope,
[adapter](const ScalarValue& ow) {
adapter->setOutWhite(ow);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kGamma_Index), fScope,
[adapter](const ScalarValue& g) {
adapter->setGamma(g);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kClipToOutBlack_Index), fScope,
[adapter](const ScalarValue& cb) {
adapter->setClipBlack(cb);
});
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kClipToOutWhite_Index), fScope,
[adapter](const ScalarValue& cw) {
adapter->setClipWhite(cw);
});
return adapter->root();
}
} // namespace internal
} // namespace skottie

View File

@ -0,0 +1,58 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/Effects.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "modules/sksg/include/SkSGColorFilter.h"
#include "src/utils/SkJSON.h"
namespace skottie {
namespace internal {
sk_sp<sksg::RenderNode> EffectBuilder::attachTintEffect(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
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::GradientColorFilter::Make(std::move(layer),
fBuilder->attachColor(*color0_prop, fScope, "v"),
fBuilder->attachColor(*color1_prop, fScope, "v"));
if (!tint_node) {
return nullptr;
}
fBuilder->bindProperty<ScalarValue>((*amount_prop)["v"], fScope,
[tint_node](const ScalarValue& w) {
tint_node->setWeight(w / 100); // 100-based
});
return std::move(tint_node);
}
} // namespace internal
} // namespace skottie

View File

@ -41,10 +41,8 @@ private:
} // anonymous ns
sk_sp<sksg::RenderNode> AttachTransformEffect(const skjson::ArrayValue& jprops,
const AnimationBuilder* abuilder,
AnimatorScope* ascope,
sk_sp<sksg::RenderNode> layer) {
sk_sp<sksg::RenderNode> EffectBuilder::attachTransformEffect(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
enum : size_t {
kAnchorPoint_Index = 0,
kPosition_Index = 1,
@ -64,44 +62,36 @@ sk_sp<sksg::RenderNode> AttachTransformEffect(const skjson::ArrayValue& jprops,
auto t_adapter = sk_make_sp<TransformAdapter2D>(matrix);
auto s_adapter = sk_make_sp<ScaleAdapter>(t_adapter);
abuilder->bindProperty<VectorValue>(EffectBuilder::GetPropValue(jprops, kAnchorPoint_Index),
ascope,
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kAnchorPoint_Index), fScope,
[t_adapter](const VectorValue& ap) {
t_adapter->setAnchorPoint(ValueTraits<VectorValue>::As<SkPoint>(ap));
});
abuilder->bindProperty<VectorValue>(EffectBuilder::GetPropValue(jprops, kPosition_Index),
ascope,
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kPosition_Index), fScope,
[t_adapter](const VectorValue& p) {
t_adapter->setPosition(ValueTraits<VectorValue>::As<SkPoint>(p));
});
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops, kRotation_Index),
ascope,
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kRotation_Index), fScope,
[t_adapter](const ScalarValue& r) {
t_adapter->setRotation(r);
});
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops, kSkew_Index),
ascope,
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSkew_Index), fScope,
[t_adapter](const ScalarValue& s) {
t_adapter->setSkew(s);
});
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops, kSkewAxis_Index),
ascope,
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kSkewAxis_Index), fScope,
[t_adapter](const ScalarValue& sa) {
t_adapter->setSkewAxis(sa);
});
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops, kUniformScale_Index),
ascope,
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kUniformScale_Index), fScope,
[s_adapter](const ScalarValue& u) {
s_adapter->setIsUniform(SkScalarRoundToInt(u));
});
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops, kScaleHeight_Index),
ascope,
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kScaleHeight_Index), fScope,
[s_adapter](const ScalarValue& sh) {
s_adapter->setScaleHeight(sh);
});
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops, kScaleWidth_Index),
ascope,
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kScaleWidth_Index), fScope,
[s_adapter](const ScalarValue& sw) {
s_adapter->setScaleWidth(sw);
});
@ -109,8 +99,7 @@ sk_sp<sksg::RenderNode> AttachTransformEffect(const skjson::ArrayValue& jprops,
auto opacity_node = sksg::OpacityEffect::Make(sksg::TransformEffect::Make(std::move(layer),
std::move(matrix)));
abuilder->bindProperty<ScalarValue>(EffectBuilder::GetPropValue(jprops, kOpacity_Index),
ascope,
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOpacity_Index), fScope,
[opacity_node](const ScalarValue& o) {
opacity_node->setOpacity(o * 0.01f);
});

View File

@ -0,0 +1,60 @@
/*
* Copyright 2019 Google Inc.
*
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "modules/skottie/src/effects/Effects.h"
#include "modules/skottie/src/SkottieValue.h"
#include "modules/sksg/include/SkSGColorFilter.h"
#include "modules/sksg/include/SkSGPaint.h"
#include "src/utils/SkJSON.h"
namespace skottie {
namespace internal {
sk_sp<sksg::RenderNode> EffectBuilder::attachTritoneEffect(const skjson::ArrayValue& jprops,
sk_sp<sksg::RenderNode> layer) const {
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), {
fBuilder->attachColor(*locolor_prop, fScope, "v"),
fBuilder->attachColor(*micolor_prop, fScope, "v"),
fBuilder->attachColor(*hicolor_prop, fScope, "v") });
if (!tritone_node) {
return nullptr;
}
fBuilder->bindProperty<ScalarValue>((*blend_prop)["v"], fScope,
[tritone_node](const ScalarValue& w) {
tritone_node->setWeight((100 - w) / 100); // 100-based, inverted (!?).
});
return std::move(tritone_node);
}
} // namespace internal
} // namespace skottie