• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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