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 "include/effects/SkShaderMaskFilter.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 fMaskFilter = nullptr;
49 } else {
50 static constexpr float kFeatherToSigma = 0.3f; // close enough to AE
51 fMaskSigma = std::max(fFeather, 0.0f) * kFeatherToSigma;
52
53 // The gradient is inverted between non-blurred and blurred (latter requires dstOut).
54 const SkColor c0 = fMaskSigma > 0 ? 0xffffffff : 0x00000000,
55 c1 = 0xffffffff - c0;
56 auto t = fCompletion * 0.01f;
57
58 const SkColor grad_colors[] = { c0, c1 };
59 const SkScalar grad_pos[] = { t, t };
60
61 SkMatrix lm;
62 lm.setRotate(fStartAngle - 90 + t * this->wipeAlignment(),
63 fWipeCenter.x(), fWipeCenter.y());
64
65 fMaskFilter = SkShaderMaskFilter::Make(
66 SkGradientShader::MakeSweep(fWipeCenter.x(), fWipeCenter.y(),
67 grad_colors, grad_pos,
68 SK_ARRAY_COUNT(grad_colors), 0, &lm));
69
70 // Edge feather requires a real blur.
71 if (fMaskSigma > 0) {
72 fMaskFilter = SkMaskFilter::MakeCompose(SkMaskFilter::MakeBlur(kNormal_SkBlurStyle,
73 fMaskSigma),
74 std::move(fMaskFilter));
75 }
76 }
77
78 return content_bounds;
79 }
80
onRender(SkCanvas * canvas,const RenderContext * ctx) const81 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
82 if (fCompletion >= 100) {
83 // Fully masked out.
84 return;
85 }
86
87 if (!fMaskSigma) {
88 // No mask filter, or a shader-only mask filter: we can draw the content directly.
89 const auto local_ctx = ScopedRenderContext(canvas, ctx)
90 .modulateMaskFilter(fMaskFilter, canvas->getTotalMatrix());
91 this->children()[0]->render(canvas, local_ctx);
92 return;
93 }
94
95 // Blurred mask filters require a separate layer.
96 SkAutoCanvasRestore acr(canvas, false);
97 canvas->saveLayer(this->bounds(), nullptr);
98
99 this->children()[0]->render(canvas, ctx);
100
101 // Outset the mask to clip-out any edge blur.
102 const auto mask_bounds = this->bounds().makeOutset(fMaskSigma * 3, fMaskSigma * 3);
103
104 SkPaint mask_paint;
105 mask_paint.setBlendMode(SkBlendMode::kDstOut);
106 mask_paint.setMaskFilter(fMaskFilter);
107 canvas->drawRect(mask_bounds, mask_paint);
108 }
109
110 private:
wipeAlignment() const111 float wipeAlignment() const {
112 switch (SkScalarRoundToInt(fWipe)) {
113 case 1: return 0.0f; // Clockwise
114 case 2: return -360.0f; // Counterclockwise
115 case 3: return -180.0f; // Both/center
116 default: break;
117 }
118 return 0.0f;
119 }
120
121 SkPoint fWipeCenter = { 0, 0 };
122 float fCompletion = 0,
123 fStartAngle = 0,
124 fWipe = 0,
125 fFeather = 0;
126
127 // Cached during revalidation.
128 sk_sp<SkMaskFilter> fMaskFilter;
129 float fMaskSigma; // edge feather/blur
130
131 using INHERITED = sksg::CustomRenderNode;
132 };
133
134 } // namespace
135
attachRadialWipeEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const136 sk_sp<sksg::RenderNode> EffectBuilder::attachRadialWipeEffect(const skjson::ArrayValue& jprops,
137 sk_sp<sksg::RenderNode> layer) const {
138 enum : size_t {
139 kCompletion_Index = 0,
140 kStartAngle_Index = 1,
141 kWipeCenter_Index = 2,
142 kWipe_Index = 3,
143 kFeather_Index = 4,
144 };
145
146 auto wiper = sk_make_sp<RWipeRenderNode>(std::move(layer));
147
148 fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kCompletion_Index),
149 [wiper](const ScalarValue& c) {
150 wiper->setCompletion(c);
151 });
152 fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kStartAngle_Index),
153 [wiper](const ScalarValue& sa) {
154 wiper->setStartAngle(sa);
155 });
156 fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kWipeCenter_Index),
157 [wiper](const VectorValue& c) {
158 wiper->setWipeCenter(ValueTraits<VectorValue>::As<SkPoint>(c));
159 });
160 fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kWipe_Index),
161 [wiper](const ScalarValue& w) {
162 wiper->setWipe(w);
163 });
164 fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index),
165 [wiper](const ScalarValue& f) {
166 wiper->setFeather(f);
167 });
168
169 return wiper;
170 }
171
172 } // namespace internal
173 } // namespace skottie
174