2 * Copyright 2021 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
8 #include "modules/skottie/src/effects/Effects.h"
10 #include "include/core/SkCanvas.h"
11 #include "include/core/SkPictureRecorder.h"
12 #include "include/effects/SkRuntimeEffect.h"
13 #include "modules/skottie/src/Adapter.h"
14 #include "modules/skottie/src/SkottieValue.h"
15 #include "modules/sksg/include/SkSGPaint.h"
16 #include "modules/sksg/include/SkSGRenderNode.h"
17 #include "src/utils/SkJSON.h"
19 namespace skottie::internal {
25 static constexpr char gBulgeDisplacementSkSL[] =
26 "uniform shader u_layer;"
28 "uniform float2 u_center;"
29 "uniform float2 u_radius;"
32 "uniform float u_asinInverseR;"
33 "uniform float u_rcpR;"
34 "uniform float u_rcpAsinInvR;"
35 "uniform float u_selector;"
37 // AE's bulge effect appears to be a combination of spherical displacement and
38 // exponential displacement along the radius.
39 // To simplify the math, we pre scale/translate such that the ellipse becomes a
40 // circle with radius == 1, centered on origin.
41 "float2 displace_sph(float2 v) {"
42 "float arc_ratio = asin(length(v)*u_rcpR)*u_rcpAsinInvR;"
43 "return normalize(v)*arc_ratio - v;"
46 "float2 displace_exp(float2 v) {"
47 "return v*pow(dot(v,v),u_h) - v;"
50 "half2 displace(float2 v) {"
51 "float t = dot(v, v);"
55 "float2 d = displace_sph(v) + displace_exp(v);"
56 "return v + (d * u_selector);"
59 "half4 main(float2 xy) {"
61 "xy = xy*u_radius + u_center;"
62 "return u_layer.eval(xy);"
65 static sk_sp<SkRuntimeEffect> bulge_effect() {
66 static const SkRuntimeEffect* effect =
67 SkRuntimeEffect::MakeForShader(SkString(gBulgeDisplacementSkSL), {}).effect.release();
70 return sk_ref_sp(effect);
73 class BulgeNode final : public sksg::CustomRenderNode {
75 explicit BulgeNode(sk_sp<RenderNode> child, const SkSize& child_size)
76 : INHERITED({std::move(child)})
77 , fChildSize(child_size) {}
79 SG_ATTRIBUTE(Center , SkPoint , fCenter)
80 SG_ATTRIBUTE(Radius , SkVector , fRadius)
81 SG_ATTRIBUTE(Height , float , fHeight)
84 sk_sp<SkShader> contentShader() {
85 if (!fContentShader || this->hasChildrenInval()) {
86 const auto& child = this->children()[0];
87 child->revalidate(nullptr, SkMatrix::I());
89 SkPictureRecorder recorder;
90 child->render(recorder.beginRecording(SkRect::MakeSize(fChildSize)));
92 fContentShader = recorder.finishRecordingAsPicture()
93 ->makeShader(SkTileMode::kRepeat, SkTileMode::kRepeat, SkFilterMode::kLinear,
97 return fContentShader;
100 sk_sp<SkShader> buildEffectShader() {
105 SkRuntimeShaderBuilder builder(bulge_effect());
106 float adjHeight = std::abs(fHeight)/4;
107 float r = (1 + adjHeight)/2/sqrt(adjHeight);
108 float h = std::pow(adjHeight, 3)*1.3;
109 builder.uniform("u_center") = fCenter;
110 builder.uniform("u_radius") = fRadius;
111 builder.uniform("u_h") = h;
112 builder.uniform("u_r") = r;
113 builder.uniform("u_asinInverseR") = std::asin(1/r);
114 builder.uniform("u_rcpR") = 1.0f/r;
115 builder.uniform("u_rcpAsinInvR") = 1.0f/std::asin(1/r);
116 builder.uniform("u_selector") = (fHeight > 0 ? 1.0f : -1.0f);
118 builder.child("u_layer") = this->contentShader();
120 const auto lm = SkMatrix::Translate(fCenter.x(), fCenter.y())
121 * SkMatrix::Scale(fRadius.x(), fRadius.y());
122 return builder.makeShader(&lm);
125 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
126 const auto& child = this->children()[0];
127 fEffectShader = buildEffectShader();
128 return child->revalidate(ic, ctm);
131 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
133 this->children()[0]->render(canvas, ctx);
136 const auto& bounds = this->bounds();
137 const auto local_ctx = ScopedRenderContext(canvas, ctx)
138 .setIsolation(bounds, canvas->getTotalMatrix(), true);
140 canvas->saveLayer(&bounds, nullptr);
142 SkPaint effect_paint;
143 effect_paint.setShader(fEffectShader);
144 effect_paint.setBlendMode(SkBlendMode::kSrcOver);
146 canvas->drawPaint(effect_paint);
149 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
151 sk_sp<SkShader> fEffectShader;
152 sk_sp<SkShader> fContentShader;
153 const SkSize fChildSize;
155 SkPoint fCenter = {0,0};
156 SkVector fRadius = {0,0};
159 using INHERITED = sksg::CustomRenderNode;
162 class BulgeEffectAdapter final : public DiscardableAdapterBase<BulgeEffectAdapter,
165 BulgeEffectAdapter(const skjson::ArrayValue& jprops,
166 const AnimationBuilder& abuilder,
167 sk_sp<BulgeNode> node)
168 : INHERITED(std::move(node))
171 kHorizontalRadius_Index = 0,
172 kVerticalRadius_Index = 1,
173 kBulgeCenter_Index = 2,
174 kBulgeHeight_Index = 3,
177 // kPinning_Index = 6,
179 EffectBinder(jprops, abuilder, this).bind(kHorizontalRadius_Index, fHorizontalRadius)
180 .bind(kVerticalRadius_Index, fVerticalRadius)
181 .bind(kBulgeCenter_Index, fCenter)
182 .bind(kBulgeHeight_Index, fBulgeHeight);
186 void onSync() override {
188 auto n = this->node();
189 n->setCenter({fCenter.x, fCenter.y});
190 n->setRadius({fHorizontalRadius, fVerticalRadius});
191 n->setHeight(fBulgeHeight);
195 ScalarValue fHorizontalRadius,
198 using INHERITED = DiscardableAdapterBase<BulgeEffectAdapter, BulgeNode>;
203 #endif // SK_ENABLE_SKSL
205 sk_sp<sksg::RenderNode> EffectBuilder::attachBulgeEffect(const skjson::ArrayValue& jprops,
206 sk_sp<sksg::RenderNode> layer) const {
207 #ifdef SK_ENABLE_SKSL
208 auto shaderNode = sk_make_sp<BulgeNode>(std::move(layer), fLayerSize);
209 return fBuilder->attachDiscardableAdapter<BulgeEffectAdapter>(jprops, *fBuilder, std::move(shaderNode));
215 } // namespace skottie::internal