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