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