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