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