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