1 /*
2 * Copyright 2020 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/core/SkPictureRecorder.h"
12 #include "include/effects/SkColorMatrix.h"
13 #include "include/effects/SkImageFilters.h"
14 #include "include/effects/SkRuntimeEffect.h"
15 #include "include/private/SkColorData.h"
16 #include "modules/skottie/src/Adapter.h"
17 #include "modules/skottie/src/SkottieJson.h"
18 #include "modules/skottie/src/SkottieValue.h"
19 #include "modules/sksg/include/SkSGRenderEffect.h"
20 #include "modules/sksg/include/SkSGRenderNode.h"
21
22 #include <cmath>
23 #include <tuple>
24
25 namespace skottie::internal {
26
27 #ifdef SK_ENABLE_SKSL
28
29 namespace {
30
31 // AE's displacement map effect [1] is somewhat similar to SVG's feDisplacementMap [2]. Main
32 // differences:
33 //
34 // - more selector options: full/half/off, luminance, hue/saturation/lightness
35 // - the scale factor is anisotropic (independent x/y values)
36 // - displacement coverage is restricted to non-transparent source for some selectors
37 // (specifically: r, g, b, h, s, l).
38 //
39 // [1] https://helpx.adobe.com/after-effects/using/distort-effects.html#displacement_map_effect
40 // [2] https://www.w3.org/TR/SVG11/filters.html#feDisplacementMapElement
41
42 // |selector_matrix| and |selector_offset| are set up to select and scale the x/y displacement
43 // in R/G, and the x/y coverage modulation in B/A.
44 static constexpr char gDisplacementSkSL[] =
45 "uniform shader child;"
46 "uniform shader displ;"
47
48 "uniform half4x4 selector_matrix;"
49 "uniform half4 selector_offset;"
50
51 "half4 main(float2 xy) {"
52 "half4 d = displ.eval(xy);"
53
54 "d = selector_matrix*unpremul(d) + selector_offset;"
55
56 "return child.eval(xy + d.xy*d.zw);"
57 "}"
58 ;
59
displacement_effect_singleton()60 static sk_sp<SkRuntimeEffect> displacement_effect_singleton() {
61 static const SkRuntimeEffect* effect =
62 SkRuntimeEffect::MakeForShader(SkString(gDisplacementSkSL)).effect.release();
63 if (0 && !effect) {
64 auto err = SkRuntimeEffect::MakeForShader(SkString(gDisplacementSkSL)).errorText;
65 printf("!!! %s\n", err.c_str());
66 }
67 SkASSERT(effect);
68
69 return sk_ref_sp(effect);
70 }
71
72 class DisplacementNode final : public sksg::CustomRenderNode {
73 public:
~DisplacementNode()74 ~DisplacementNode() override {
75 this->unobserveInval(fDisplSource);
76 }
77
Make(sk_sp<RenderNode> child,const SkSize & child_size,sk_sp<RenderNode> displ,const SkSize & displ_size)78 static sk_sp<DisplacementNode> Make(sk_sp<RenderNode> child,
79 const SkSize& child_size,
80 sk_sp<RenderNode> displ,
81 const SkSize& displ_size) {
82 if (!child || !displ) {
83 return nullptr;
84 }
85
86 return sk_sp<DisplacementNode>(new DisplacementNode(std::move(child), child_size,
87 std::move(displ), displ_size));
88 }
89
90 enum class Pos : unsigned {
91 kCenter,
92 kStretch,
93 kTile,
94
95 kLast = kTile,
96 };
97
98 enum class Selector : unsigned {
99 kR,
100 kG,
101 kB,
102 kA,
103 kLuminance,
104 kHue,
105 kLightness,
106 kSaturation,
107 kFull,
108 kHalf,
109 kOff,
110
111 kLast = kOff,
112 };
113
114 SG_ATTRIBUTE(Scale , SkV2 , fScale )
115 SG_ATTRIBUTE(ChildTileMode, SkTileMode, fChildTileMode )
116 SG_ATTRIBUTE(Pos , Pos , fPos )
117 SG_ATTRIBUTE(XSelector , Selector , fXSelector )
118 SG_ATTRIBUTE(YSelector , Selector , fYSelector )
119 SG_ATTRIBUTE(ExpandBounds , bool , fExpandBounds )
120
121 private:
DisplacementNode(sk_sp<RenderNode> child,const SkSize & child_size,sk_sp<RenderNode> displ,const SkSize & displ_size)122 DisplacementNode(sk_sp<RenderNode> child, const SkSize& child_size,
123 sk_sp<RenderNode> displ, const SkSize& displ_size)
124 : INHERITED({std::move(child)})
125 , fDisplSource(std::move(displ))
126 , fDisplSize(displ_size)
127 , fChildSize(child_size)
128 {
129 this->observeInval(fDisplSource);
130 }
131
132 struct SelectorCoeffs {
133 float dr, dg, db, da, d_offset, // displacement contribution
134 c_scale, c_offset; // coverage as a function of alpha
135 };
136
Coeffs(Selector sel)137 static SelectorCoeffs Coeffs(Selector sel) {
138 // D = displacement input
139 // C = displacement coverage
140 static constexpr SelectorCoeffs gCoeffs[] = {
141 { 1,0,0,0,0, 1,0 }, // kR: D = r, C = a
142 { 0,1,0,0,0, 1,0 }, // kG: D = g, C = a
143 { 0,0,1,0,0, 1,0 }, // kB: D = b, C = a
144 { 0,0,0,1,0, 0,1 }, // kA: D = a, C = 1.0
145 { SK_LUM_COEFF_R,SK_LUM_COEFF_G, SK_LUM_COEFF_B,0,0, 1,0},
146 // kLuminance: D = lum(rgb), C = a
147 { 1,0,0,0,0, 0,1 }, // kH: D = h, C = 1.0 (HSLA)
148 { 0,1,0,0,0, 0,1 }, // kL: D = l, C = 1.0 (HSLA)
149 { 0,0,1,0,0, 0,1 }, // kS: D = s, C = 1.0 (HSLA)
150 { 0,0,0,0,1, 0,1 }, // kFull: D = 1.0, C = 1.0
151 { 0,0,0,0,.5f, 0,1 }, // kHalf: D = 0.5, C = 1.0
152 { 0,0,0,0,0, 0,1 }, // kOff: D = 0.0, C = 1.0
153 };
154
155 const auto i = static_cast<size_t>(sel);
156 SkASSERT(i < std::size(gCoeffs));
157
158 return gCoeffs[i];
159 }
160
IsConst(Selector s)161 static bool IsConst(Selector s) {
162 return s == Selector::kFull
163 || s == Selector::kHalf
164 || s == Selector::kOff;
165 }
166
buildEffectShader(sksg::InvalidationController * ic,const SkMatrix & ctm)167 sk_sp<SkShader> buildEffectShader(sksg::InvalidationController* ic, const SkMatrix& ctm) {
168 // AE quirk: combining two const/generated modes does not displace - we need at
169 // least one non-const selector to trigger the effect. *shrug*
170 if ((IsConst(fXSelector) && IsConst(fYSelector)) ||
171 (SkScalarNearlyZero(fScale.x) && SkScalarNearlyZero(fScale.y))) {
172 return nullptr;
173 }
174
175 auto get_content_picture = [](const sk_sp<sksg::RenderNode>& node,
176 sksg::InvalidationController* ic, const SkMatrix& ctm) {
177 if (!node) {
178 return sk_sp<SkPicture>(nullptr);
179 }
180
181 const auto bounds = node->revalidate(ic, ctm);
182
183 SkPictureRecorder recorder;
184 node->render(recorder.beginRecording(bounds));
185 return recorder.finishRecordingAsPicture();
186 };
187
188 const auto child_content = get_content_picture(this->children()[0], ic, ctm),
189 displ_content = get_content_picture(fDisplSource, ic, ctm);
190 if (!child_content || !displ_content) {
191 return nullptr;
192 }
193
194 const auto child_tile = SkRect::MakeSize(fChildSize);
195 auto child_shader = child_content->makeShader(fChildTileMode,
196 fChildTileMode,
197 SkFilterMode::kLinear,
198 nullptr,
199 &child_tile);
200
201 const auto displ_tile = SkRect::MakeSize(fDisplSize);
202 const auto displ_mode = this->displacementTileMode();
203 const auto displ_matrix = this->displacementMatrix();
204 auto displ_shader = displ_content->makeShader(displ_mode,
205 displ_mode,
206 SkFilterMode::kLinear,
207 &displ_matrix,
208 &displ_tile);
209
210 SkRuntimeShaderBuilder builder(displacement_effect_singleton());
211 builder.child("child") = std::move(child_shader);
212 builder.child("displ") = std::move(displ_shader);
213
214 const auto xc = Coeffs(fXSelector),
215 yc = Coeffs(fYSelector);
216
217 const auto s = fScale * 2;
218
219 const float selector_m[] = {
220 xc.dr*s.x, yc.dr*s.y, 0, 0,
221 xc.dg*s.x, yc.dg*s.y, 0, 0,
222 xc.db*s.x, yc.db*s.y, 0, 0,
223 xc.da*s.x, yc.da*s.y, xc.c_scale, yc.c_scale,
224
225 // │ │ │ └──── A -> vertical modulation
226 // │ │ └──────────────── B -> horizontal modulation
227 // │ └──────────────────────────────── G -> vertical displacement
228 // └─────────────────────────────────────────── R -> horizontal displacement
229 };
230 const float selector_o[] = {
231 (xc.d_offset - .5f) * s.x,
232 (yc.d_offset - .5f) * s.y,
233 xc.c_offset,
234 yc.c_offset,
235 };
236
237 builder.uniform("selector_matrix") = selector_m;
238 builder.uniform("selector_offset") = selector_o;
239
240 // TODO: RGB->HSL stage
241 return builder.makeShader();
242 }
243
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)244 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
245 fEffectShader = this->buildEffectShader(ic, ctm);
246
247 auto bounds = this->children()[0]->revalidate(ic, ctm);
248 if (fExpandBounds) {
249 // Expand the bounds to accommodate max displacement (which is |fScale|).
250 bounds.outset(std::abs(fScale.x), std::abs(fScale.y));
251 }
252
253 return bounds;
254 }
255
onRender(SkCanvas * canvas,const RenderContext * ctx) const256 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
257 if (!fEffectShader) {
258 // no displacement effect - just render the content
259 this->children()[0]->render(canvas, ctx);
260 return;
261 }
262
263 auto local_ctx = ScopedRenderContext(canvas, ctx).setIsolation(this->bounds(),
264 canvas->getTotalMatrix(),
265 true);
266 SkPaint shader_paint;
267 shader_paint.setShader(fEffectShader);
268
269 canvas->drawRect(this->bounds(), shader_paint);
270 }
271
displacementTileMode() const272 SkTileMode displacementTileMode() const {
273 return fPos == Pos::kTile
274 ? SkTileMode::kRepeat
275 : SkTileMode::kClamp;
276 }
277
displacementMatrix() const278 SkMatrix displacementMatrix() const {
279 switch (fPos) {
280 case Pos::kCenter: return SkMatrix::Translate(
281 (fChildSize.fWidth - fDisplSize.fWidth ) / 2,
282 (fChildSize.fHeight - fDisplSize.fHeight) / 2);
283 case Pos::kStretch: return SkMatrix::Scale(
284 fChildSize.fWidth / fDisplSize.fWidth,
285 fChildSize.fHeight / fDisplSize.fHeight);
286 case Pos::kTile: return SkMatrix::I();
287 }
288 SkUNREACHABLE;
289 }
290
onNodeAt(const SkPoint &) const291 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
292
293 const sk_sp<sksg::RenderNode> fDisplSource;
294 const SkSize fDisplSize,
295 fChildSize;
296
297 // Cached top-level shader
298 sk_sp<SkShader> fEffectShader;
299
300 SkV2 fScale = { 0, 0 };
301 SkTileMode fChildTileMode = SkTileMode::kDecal;
302 Pos fPos = Pos::kCenter;
303 Selector fXSelector = Selector::kR,
304 fYSelector = Selector::kR;
305 bool fExpandBounds = false;
306
307 using INHERITED = sksg::CustomRenderNode;
308 };
309
310 class DisplacementMapAdapter final : public DiscardableAdapterBase<DisplacementMapAdapter,
311 DisplacementNode> {
312 public:
DisplacementMapAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,sk_sp<DisplacementNode> node)313 DisplacementMapAdapter(const skjson::ArrayValue& jprops,
314 const AnimationBuilder* abuilder,
315 sk_sp<DisplacementNode> node)
316 : INHERITED(std::move(node)) {
317 EffectBinder(jprops, *abuilder, this)
318 .bind(kUseForHorizontal_Index, fHorizontalSelector)
319 .bind(kMaxHorizontal_Index , fMaxHorizontal )
320 .bind(kUseForVertical_Index , fVerticalSelector )
321 .bind(kMaxVertical_Index , fMaxVertical )
322 .bind(kMapBehavior_Index , fMapBehavior )
323 .bind(kEdgeBehavior_Index , fEdgeBehavior )
324 .bind(kExpandOutput_Index , fExpandOutput );
325 }
326
GetDisplacementSource(const skjson::ArrayValue & jprops,const EffectBuilder * ebuilder)327 static std::tuple<sk_sp<sksg::RenderNode>, SkSize> GetDisplacementSource(
328 const skjson::ArrayValue& jprops,
329 const EffectBuilder* ebuilder) {
330
331 if (const skjson::ObjectValue* jv = EffectBuilder::GetPropValue(jprops, kMapLayer_Index)) {
332 auto* map_builder = ebuilder->getLayerBuilder(ParseDefault((*jv)["k"], -1));
333 if (map_builder) {
334 return std::make_tuple(map_builder->contentTree(), map_builder->size());
335 }
336 }
337
338 return std::make_tuple<sk_sp<sksg::RenderNode>, SkSize>(nullptr, {0,0});
339 }
340
341 private:
342 enum : size_t {
343 kMapLayer_Index = 0,
344 kUseForHorizontal_Index = 1,
345 kMaxHorizontal_Index = 2,
346 kUseForVertical_Index = 3,
347 kMaxVertical_Index = 4,
348 kMapBehavior_Index = 5,
349 kEdgeBehavior_Index = 6,
350 kExpandOutput_Index = 7,
351 };
352
353 template <typename E>
ToEnum(float v)354 E ToEnum(float v) {
355 // map one-based float "enums" to real enum types
356 const auto uv = std::min(static_cast<unsigned>(v) - 1,
357 static_cast<unsigned>(E::kLast));
358
359 return static_cast<E>(uv);
360 }
361
onSync()362 void onSync() override {
363 if (!this->node()) {
364 return;
365 }
366
367 this->node()->setScale({fMaxHorizontal, fMaxVertical});
368 this->node()->setChildTileMode(fEdgeBehavior != 0 ? SkTileMode::kRepeat
369 : SkTileMode::kDecal);
370
371 this->node()->setPos(ToEnum<DisplacementNode::Pos>(fMapBehavior));
372 this->node()->setXSelector(ToEnum<DisplacementNode::Selector>(fHorizontalSelector));
373 this->node()->setYSelector(ToEnum<DisplacementNode::Selector>(fVerticalSelector));
374 this->node()->setExpandBounds(fExpandOutput != 0);
375 }
376
377 ScalarValue fHorizontalSelector = 0,
378 fVerticalSelector = 0,
379 fMaxHorizontal = 0,
380 fMaxVertical = 0,
381 fMapBehavior = 0,
382 fEdgeBehavior = 0,
383 fExpandOutput = 0;
384
385 using INHERITED = DiscardableAdapterBase<DisplacementMapAdapter, DisplacementNode>;
386 };
387
388 } // namespace
389
390 #endif // SK_ENABLE_SKSL
391
attachDisplacementMapEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const392 sk_sp<sksg::RenderNode> EffectBuilder::attachDisplacementMapEffect(
393 const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
394 #ifdef SK_ENABLE_SKSL
395 auto [ displ, displ_size ] = DisplacementMapAdapter::GetDisplacementSource(jprops, this);
396
397 auto displ_node = DisplacementNode::Make(layer, fLayerSize, std::move(displ), displ_size);
398
399 if (!displ_node) {
400 return layer;
401 }
402
403 return fBuilder->attachDiscardableAdapter<DisplacementMapAdapter>(jprops,
404 fBuilder,
405 std::move(displ_node));
406 #else
407 // TODO(skia:12197)
408 return layer;
409 #endif
410 }
411
412 } // namespace skottie::internal
413