Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit d7b321a

Browse files
fmalitaSkia Commit-Bot
authored andcommitted
[skottie] Radial swipe effect
Implement radial wipe with a sweep gradient shader mask filter. The implementation is slightly convoluted because edge feathering requires a real blur, which in turn requires content layer isolation. So there are two distinct operation modes: - no feather -> draw the content directly into the dest buffer, with the mask filter deferred in SG context - feather -> draw the content into a separate layer, then blend (dstOut) the composed blur+shader mask on top Change-Id: I253701aff42db8010ce463762252c262e2c5d92b Reviewed-on: https://skia-review.googlesource.com/c/skia/+/222596 Reviewed-by: Mike Reed <reed@google.com> Commit-Queue: Florin Malita <fmalita@chromium.org>
1 parent d3494ed commit d7b321a

File tree

5 files changed

+180
-0
lines changed

5 files changed

+180
-0
lines changed

modules/skottie/skottie.gni

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ skia_skottie_sources = [
3636
"$_src/effects/LevelsEffect.cpp",
3737
"$_src/effects/LinearWipeEffect.cpp",
3838
"$_src/effects/MotionTileEffect.cpp",
39+
"$_src/effects/RadialWipeEffect.cpp",
3940
"$_src/effects/TintEffect.cpp",
4041
"$_src/effects/TransformEffect.cpp",
4142
"$_src/effects/TritoneEffect.cpp",

modules/skottie/src/effects/Effects.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ EffectBuilder::EffectBuilderT EffectBuilder::findBuilder(const skjson::ObjectVal
2727
kFill_Effect = 21,
2828
kTritone_Effect = 23,
2929
kDropShadow_Effect = 25,
30+
kRadialWipe_Effect = 26,
3031
kGaussianBlur_Effect = 29,
3132
};
3233

@@ -41,6 +42,8 @@ EffectBuilder::EffectBuilderT EffectBuilder::findBuilder(const skjson::ObjectVal
4142
return &EffectBuilder::attachTritoneEffect;
4243
case kDropShadow_Effect:
4344
return &EffectBuilder::attachDropShadowEffect;
45+
case kRadialWipe_Effect:
46+
return &EffectBuilder::attachRadialWipeEffect;
4447
case kGaussianBlur_Effect:
4548
return &EffectBuilder::attachGaussianBlurEffect;
4649
default:

modules/skottie/src/effects/Effects.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ class EffectBuilder final : public SkNoncopyable {
3838
sk_sp<sksg::RenderNode>) const;
3939
sk_sp<sksg::RenderNode> attachMotionTileEffect (const skjson::ArrayValue&,
4040
sk_sp<sksg::RenderNode>) const;
41+
sk_sp<sksg::RenderNode> attachRadialWipeEffect (const skjson::ArrayValue&,
42+
sk_sp<sksg::RenderNode>) const;
4143
sk_sp<sksg::RenderNode> attachTintEffect (const skjson::ArrayValue&,
4244
sk_sp<sksg::RenderNode>) const;
4345
sk_sp<sksg::RenderNode> attachTransformEffect (const skjson::ArrayValue&,
Lines changed: 173 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,173 @@
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:
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:
36+
const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
37+
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+
81+
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:
111+
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+
136+
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), fScope,
149+
[wiper](const ScalarValue& c) {
150+
wiper->setCompletion(c);
151+
});
152+
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kStartAngle_Index), fScope,
153+
[wiper](const ScalarValue& sa) {
154+
wiper->setStartAngle(sa);
155+
});
156+
fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kWipeCenter_Index), fScope,
157+
[wiper](const VectorValue& c) {
158+
wiper->setWipeCenter(ValueTraits<VectorValue>::As<SkPoint>(c));
159+
});
160+
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kWipe_Index), fScope,
161+
[wiper](const ScalarValue& w) {
162+
wiper->setWipe(w);
163+
});
164+
fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kFeather_Index), fScope,
165+
[wiper](const ScalarValue& f) {
166+
wiper->setFeather(f);
167+
});
168+
169+
return std::move(wiper);
170+
}
171+
172+
} // namespace internal
173+
} // namespace skottie
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{"v":"5.5.2","fr":60,"ip":0,"op":300,"w":500,"h":500,"nm":"radial wipe","ddd":0,"assets":[],"layers":[{"ddd":0,"ind":1,"ty":1,"nm":"Green Solid 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[130,130,0],"ix":2},"a":{"a":0,"k":[125,125,0],"ix":1},"s":{"a":0,"k":[80,80,100],"ix":6}},"ao":0,"ef":[{"ty":26,"nm":"Radial Wipe","np":7,"mn":"ADBE Radial Wipe","ix":1,"en":1,"ef":[{"ty":0,"nm":"Transition Completion","mn":"ADBE Radial Wipe-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":299,"s":[100]}],"ix":1}},{"ty":0,"nm":"Start Angle","mn":"ADBE Radial Wipe-0002","ix":2,"v":{"a":0,"k":0,"ix":2}},{"ty":3,"nm":"Wipe Center","mn":"ADBE Radial Wipe-0003","ix":3,"v":{"a":0,"k":[125,125],"ix":3}},{"ty":7,"nm":"Wipe","mn":"ADBE Radial Wipe-0004","ix":4,"v":{"a":0,"k":1,"ix":4}},{"ty":0,"nm":"Feather","mn":"ADBE Radial Wipe-0005","ix":5,"v":{"a":0,"k":10,"ix":5}}]}],"sw":250,"sh":250,"sc":"#00ff00","ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":2,"ty":1,"nm":"Green Solid 4","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[130,370,0],"ix":2},"a":{"a":0,"k":[125,125,0],"ix":1},"s":{"a":0,"k":[80,80,100],"ix":6}},"ao":0,"ef":[{"ty":26,"nm":"Radial Wipe","np":7,"mn":"ADBE Radial Wipe","ix":1,"en":1,"ef":[{"ty":0,"nm":"Transition Completion","mn":"ADBE Radial Wipe-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":299,"s":[100]}],"ix":1}},{"ty":0,"nm":"Start Angle","mn":"ADBE Radial Wipe-0002","ix":2,"v":{"a":0,"k":180,"ix":2}},{"ty":3,"nm":"Wipe Center","mn":"ADBE Radial Wipe-0003","ix":3,"v":{"a":0,"k":[125,125],"ix":3}},{"ty":7,"nm":"Wipe","mn":"ADBE Radial Wipe-0004","ix":4,"v":{"a":0,"k":3,"ix":4}},{"ty":0,"nm":"Feather","mn":"ADBE Radial Wipe-0005","ix":5,"v":{"a":0,"k":10,"ix":5}}]}],"sw":250,"sh":250,"sc":"#00ff00","ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":3,"ty":4,"nm":"Shape Layer 1","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[370,130,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[80,80,100],"ix":6}},"ao":0,"ef":[{"ty":26,"nm":"Radial Wipe","np":7,"mn":"ADBE Radial Wipe","ix":1,"en":1,"ef":[{"ty":0,"nm":"Transition Completion","mn":"ADBE Radial Wipe-0001","ix":1,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"t":299,"s":[100]}],"ix":1}},{"ty":0,"nm":"Start Angle","mn":"ADBE Radial Wipe-0002","ix":2,"v":{"a":0,"k":0,"ix":2}},{"ty":3,"nm":"Wipe Center","mn":"ADBE Radial Wipe-0003","ix":3,"v":{"a":0,"k":[370,130],"ix":3}},{"ty":7,"nm":"Wipe","mn":"ADBE Radial Wipe-0004","ix":4,"v":{"a":0,"k":2,"ix":4}},{"ty":0,"nm":"Feather","mn":"ADBE Radial Wipe-0005","ix":5,"v":{"a":0,"k":10,"ix":5}}]}],"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"a":0,"k":5,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"ir":{"a":0,"k":33,"ix":6},"is":{"a":0,"k":0,"ix":8},"or":{"a":0,"k":100,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"fl","c":{"a":0,"k":[0.314644604921,0.276639103889,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[250,250],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,1,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":4,"ty":4,"nm":"Shape Layer 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[370,370,0],"ix":2},"a":{"a":0,"k":[0,0,0],"ix":1},"s":{"a":0,"k":[80,80,100],"ix":6}},"ao":0,"ef":[{"ty":26,"nm":"Radial Wipe","np":7,"mn":"ADBE Radial Wipe","ix":1,"en":1,"ef":[{"ty":0,"nm":"Transition Completion","mn":"ADBE Radial Wipe-0001","ix":1,"v":{"a":0,"k":20,"ix":1}},{"ty":0,"nm":"Start Angle","mn":"ADBE Radial Wipe-0002","ix":2,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[-450]},{"t":299,"s":[630]}],"ix":2}},{"ty":3,"nm":"Wipe Center","mn":"ADBE Radial Wipe-0003","ix":3,"v":{"a":0,"k":[370,370],"ix":3}},{"ty":7,"nm":"Wipe","mn":"ADBE Radial Wipe-0004","ix":4,"v":{"a":0,"k":3,"ix":4}},{"ty":0,"nm":"Feather","mn":"ADBE Radial Wipe-0005","ix":5,"v":{"a":1,"k":[{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":0,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":60,"s":[0]},{"i":{"x":[0.833],"y":[0.833]},"o":{"x":[0.167],"y":[0.167]},"t":150,"s":[70.044]},{"t":240,"s":[0]}],"ix":5}}]}],"shapes":[{"ty":"gr","it":[{"ty":"sr","sy":1,"d":1,"pt":{"a":0,"k":5,"ix":3},"p":{"a":0,"k":[0,0],"ix":4},"r":{"a":0,"k":0,"ix":5},"ir":{"a":0,"k":33,"ix":6},"is":{"a":0,"k":0,"ix":8},"or":{"a":0,"k":100,"ix":7},"os":{"a":0,"k":0,"ix":9},"ix":1,"nm":"Polystar Path 1","mn":"ADBE Vector Shape - Star","hd":false},{"ty":"fl","c":{"a":0,"k":[0.314644604921,0.276639103889,1,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 2","np":2,"cix":2,"bm":0,"ix":1,"mn":"ADBE Vector Group","hd":false},{"ty":"gr","it":[{"ty":"rc","d":1,"s":{"a":0,"k":[250,250],"ix":2},"p":{"a":0,"k":[0,0],"ix":3},"r":{"a":0,"k":0,"ix":4},"nm":"Rectangle Path 1","mn":"ADBE Vector Shape - Rect","hd":false},{"ty":"fl","c":{"a":0,"k":[0,1,0,1],"ix":4},"o":{"a":0,"k":100,"ix":5},"r":1,"bm":0,"nm":"Fill 1","mn":"ADBE Vector Graphic - Fill","hd":false},{"ty":"tr","p":{"a":0,"k":[0,0],"ix":2},"a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"r":{"a":0,"k":0,"ix":6},"o":{"a":0,"k":100,"ix":7},"sk":{"a":0,"k":0,"ix":4},"sa":{"a":0,"k":0,"ix":5},"nm":"Transform"}],"nm":"Group 1","np":2,"cix":2,"bm":0,"ix":2,"mn":"ADBE Vector Group","hd":false}],"ip":0,"op":300,"st":0,"bm":0},{"ddd":0,"ind":5,"ty":1,"nm":"Black Solid 2","sr":1,"ks":{"o":{"a":0,"k":100,"ix":11},"r":{"a":0,"k":0,"ix":10},"p":{"a":0,"k":[250,250,0],"ix":2},"a":{"a":0,"k":[250,250,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6}},"ao":0,"sw":500,"sh":500,"sc":"#000000","ip":0,"op":300,"st":0,"bm":0}],"markers":[]}

0 commit comments

Comments
 (0)