xref: /aosp_15_r20/external/skia/modules/skottie/src/effects/DisplacementMapEffect.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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