1 /*
2 * Copyright 2019 Google Inc.
3 *
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
6 */
7
8 #include "modules/skottie/src/effects/Effects.h"
9
10 #include "include/core/SkCanvas.h"
11 #include "include/effects/SkGradientShader.h"
12 #include "modules/skottie/src/Adapter.h"
13 #include "modules/skottie/src/SkottieValue.h"
14 #include "modules/sksg/include/SkSGRenderNode.h"
15 #include "src/utils/SkJSON.h"
16
17 #include <cmath>
18
19 namespace skottie {
20 namespace internal {
21
22 namespace {
23
24 class RWipeRenderNode final : public sksg::CustomRenderNode {
25 public:
RWipeRenderNode(sk_sp<sksg::RenderNode> layer)26 explicit RWipeRenderNode(sk_sp<sksg::RenderNode> layer)
27 : INHERITED({std::move(layer)}) {}
28
29 SG_ATTRIBUTE(Completion, float , fCompletion)
30 SG_ATTRIBUTE(StartAngle, float , fStartAngle)
31 SG_ATTRIBUTE(WipeCenter, SkPoint, fWipeCenter)
32 SG_ATTRIBUTE(Wipe , float , fWipe )
33 SG_ATTRIBUTE(Feather , float , fFeather )
34
35 protected:
onNodeAt(const SkPoint &) const36 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
37
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)38 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
39 SkASSERT(this->children().size() == 1ul);
40 const auto content_bounds = this->children()[0]->revalidate(ic, ctm);
41
42 if (fCompletion >= 100) {
43 return SkRect::MakeEmpty();
44 }
45
46 if (fCompletion <= 0) {
47 fMaskSigma = 0;
48 fMaskShader = nullptr;
49 } else {
50 fMaskSigma = std::max(fFeather, 0.0f) * kBlurSizeToSigma;
51
52 const auto t = fCompletion * 0.01f;
53
54 // Note: this could be simplified as a one-hard-stop gradient + local matrix
55 // (to apply rotation). Alas, local matrices are no longer supported in SkSG.
56 SkColor c0 = 0x00000000,
57 c1 = 0xffffffff;
58 auto sanitize_angle = [](float a) {
59 a = std::fmod(a, 360);
60 if (a < 0) {
61 a += 360;
62 }
63 return a;
64 };
65
66 auto a0 = sanitize_angle(fStartAngle - 90 + t * this->wipeAlignment()),
67 a1 = sanitize_angle(a0 + t * 360);
68 if (a0 > a1) {
69 std::swap(a0, a1);
70 std::swap(c0, c1);
71 }
72
73 const SkColor grad_colors[] = { c1, c0, c0, c1 };
74 const SkScalar grad_pos[] = { 0, 0, 1, 1 };
75
76 fMaskShader = SkGradientShader::MakeSweep(fWipeCenter.x(), fWipeCenter.y(),
77 grad_colors, grad_pos,
78 SK_ARRAY_COUNT(grad_colors),
79 SkTileMode::kClamp,
80 a0, a1, 0, nullptr);
81
82 // Edge feather requires a real blur.
83 if (fMaskSigma > 0) {
84 // TODO: this feature is disabled ATM.
85 }
86 }
87
88 return content_bounds;
89 }
90
onRender(SkCanvas * canvas,const RenderContext * ctx) const91 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
92 if (fCompletion >= 100) {
93 // Fully masked out.
94 return;
95 }
96
97 const auto local_ctx = ScopedRenderContext(canvas, ctx)
98 .modulateMaskShader(fMaskShader, canvas->getTotalMatrix());
99 this->children()[0]->render(canvas, local_ctx);
100 }
101
102 private:
wipeAlignment() const103 float wipeAlignment() const {
104 switch (SkScalarRoundToInt(fWipe)) {
105 case 1: return 0.0f; // Clockwise
106 case 2: return -360.0f; // Counterclockwise
107 case 3: return -180.0f; // Both/center
108 default: break;
109 }
110 return 0.0f;
111 }
112
113 SkPoint fWipeCenter = { 0, 0 };
114 float fCompletion = 0,
115 fStartAngle = 0,
116 fWipe = 0,
117 fFeather = 0;
118
119 // Cached during revalidation.
120 sk_sp<SkShader> fMaskShader;
121 float fMaskSigma; // edge feather/blur
122
123 using INHERITED = sksg::CustomRenderNode;
124 };
125
126 class RadialWipeAdapter final : public DiscardableAdapterBase<RadialWipeAdapter, RWipeRenderNode> {
127 public:
RadialWipeAdapter(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const AnimationBuilder & abuilder)128 RadialWipeAdapter(const skjson::ArrayValue& jprops,
129 sk_sp<sksg::RenderNode> layer,
130 const AnimationBuilder& abuilder)
131 : INHERITED(sk_make_sp<RWipeRenderNode>(std::move(layer))) {
132
133 enum : size_t {
134 kCompletion_Index = 0,
135 kStartAngle_Index = 1,
136 kWipeCenter_Index = 2,
137 kWipe_Index = 3,
138 kFeather_Index = 4,
139 };
140
141 EffectBinder(jprops, abuilder, this)
142 .bind(kCompletion_Index, fCompletion)
143 .bind(kStartAngle_Index, fStartAngle)
144 .bind(kWipeCenter_Index, fWipeCenter)
145 .bind( kWipe_Index, fWipe )
146 .bind( kFeather_Index, fFeather );
147 }
148
149 private:
onSync()150 void onSync() override {
151 const auto& wiper = this->node();
152
153 wiper->setCompletion(fCompletion);
154 wiper->setStartAngle(fStartAngle);
155 wiper->setWipeCenter({fWipeCenter.x, fWipeCenter.y});
156 wiper->setWipe(fWipe);
157 wiper->setFeather(fFeather);
158 }
159
160 Vec2Value fWipeCenter = {0,0};
161 ScalarValue fCompletion = 0,
162 fStartAngle = 0,
163 fWipe = 0,
164 fFeather = 0;
165
166 using INHERITED = DiscardableAdapterBase<RadialWipeAdapter, RWipeRenderNode>;
167 };
168
169 } // namespace
170
attachRadialWipeEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const171 sk_sp<sksg::RenderNode> EffectBuilder::attachRadialWipeEffect(const skjson::ArrayValue& jprops,
172 sk_sp<sksg::RenderNode> layer) const {
173 return fBuilder->attachDiscardableAdapter<RadialWipeAdapter>(jprops,
174 std::move(layer),
175 *fBuilder);
176 }
177
178 } // namespace internal
179 } // namespace skottie
180