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