• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2019 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/SkCanvas.h"
11 #include "include/core/SkPictureRecorder.h"
12 #include "include/core/SkShader.h"
13 #include "include/effects/SkGradientShader.h"
14 #include "modules/skottie/src/SkottieValue.h"
15 #include "modules/sksg/include/SkSGRenderNode.h"
16 #include "src/utils/SkJSON.h"
17 
18 #include <cmath>
19 
20 namespace skottie {
21 namespace internal {
22 
23 namespace  {
24 
25 // AE motion tile effect semantics
26 // (https://helpx.adobe.com/after-effects/using/stylize-effects.html#motion_tile_effect):
27 //
28 //   - the full content of the layer is mapped to a tile: tile_center, tile_width, tile_height
29 //
30 //   - tiles are repeated in both dimensions to fill the output area: output_width, output_height
31 //
32 //   - tiling mode is either kRepeat (default) or kMirror (when mirror_edges == true)
33 //
34 //   - for a non-zero phase, alternating vertical columns (every other column) are offset by
35 //     the specified amount
36 //
37 //   - when horizontal_phase is true, the phase is applied to horizontal rows instead of columns
38 //
39 class TileRenderNode final : public sksg::CustomRenderNode {
40 public:
TileRenderNode(const SkSize & size,sk_sp<sksg::RenderNode> layer)41     TileRenderNode(const SkSize& size, sk_sp<sksg::RenderNode> layer)
42         : INHERITED({std::move(layer)})
43         , fLayerSize(size) {}
44 
45     SG_ATTRIBUTE(TileCenter     , SkPoint , fTileCenter     )
46     SG_ATTRIBUTE(TileWidth      , SkScalar, fTileW          )
47     SG_ATTRIBUTE(TileHeight     , SkScalar, fTileH          )
48     SG_ATTRIBUTE(OutputWidth    , SkScalar, fOutputW        )
49     SG_ATTRIBUTE(OutputHeight   , SkScalar, fOutputH        )
50     SG_ATTRIBUTE(Phase          , SkScalar, fPhase          )
51     SG_ATTRIBUTE(MirrorEdges    , bool    , fMirrorEdges    )
52     SG_ATTRIBUTE(HorizontalPhase, bool    , fHorizontalPhase)
53 
54 protected:
onNodeAt(const SkPoint &) const55     const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
56 
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)57     SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
58         // Re-record the layer picture if needed.
59         if (!fLayerPicture || this->hasChildrenInval()) {
60             SkASSERT(this->children().size() == 1ul);
61             const auto& layer = this->children()[0];
62 
63             layer->revalidate(ic, ctm);
64 
65             SkPictureRecorder recorder;
66             layer->render(recorder.beginRecording(fLayerSize.width(), fLayerSize.height()));
67             fLayerPicture = recorder.finishRecordingAsPicture();
68         }
69 
70         // tileW and tileH use layer size percentage units.
71         const auto tileW = SkTPin(fTileW, 0.0f, 100.0f) * 0.01f * fLayerSize.width(),
72                    tileH = SkTPin(fTileH, 0.0f, 100.0f) * 0.01f * fLayerSize.height();
73         const auto tile_size = SkSize::Make(std::max(tileW, 1.0f),
74                                             std::max(tileH, 1.0f));
75         const auto tile  = SkRect::MakeXYWH(fTileCenter.fX - 0.5f * tile_size.width(),
76                                             fTileCenter.fY - 0.5f * tile_size.height(),
77                                             tile_size.width(),
78                                             tile_size.height());
79 
80         const auto layerShaderMatrix = SkMatrix::MakeRectToRect(
81                     SkRect::MakeWH(fLayerSize.width(), fLayerSize.height()),
82                     tile, SkMatrix::kFill_ScaleToFit);
83 
84         const auto tm = fMirrorEdges ? SkTileMode::kMirror : SkTileMode::kRepeat;
85         auto layer_shader = fLayerPicture->makeShader(tm, tm, &layerShaderMatrix);
86 
87         if (fPhase) {
88             // To implement AE phase semantics, we construct a mask shader for the pass-through
89             // rows/columns.  We then draw the layer content through this mask, and then again
90             // through the inverse mask with a phase shift.
91             const auto phase_vec = fHorizontalPhase
92                     ? SkVector::Make(tile.width(), 0)
93                     : SkVector::Make(0, tile.height());
94             const auto phase_shift = SkVector::Make(phase_vec.fX / layerShaderMatrix.getScaleX(),
95                                                     phase_vec.fY / layerShaderMatrix.getScaleY())
96                                      * std::fmod(fPhase * (1/360.0f), 1);
97             const auto phase_shader_matrix = SkMatrix::MakeTrans(phase_shift.x(), phase_shift.y());
98 
99             // The mask is generated using a step gradient shader, spanning 2 x tile width/height,
100             // and perpendicular to the phase vector.
101             static constexpr SkColor colors[] = { 0xffffffff, 0x00000000 };
102             static constexpr SkScalar   pos[] = {       0.5f,       0.5f };
103 
104             const SkPoint pts[] = {{ tile.x(), tile.y() },
105                                    { tile.x() + 2 * (tile.width()  - phase_vec.fX),
106                                      tile.y() + 2 * (tile.height() - phase_vec.fY) }};
107 
108             auto mask_shader = SkGradientShader::MakeLinear(pts, colors, pos,
109                                                             SK_ARRAY_COUNT(colors),
110                                                             SkTileMode::kRepeat);
111 
112             // First drawing pass: in-place masked layer content.
113             fMainPassShader  = SkShaders::Blend(SkBlendMode::kSrcIn , mask_shader, layer_shader);
114             // Second pass: phased-shifted layer content, with an inverse mask.
115             fPhasePassShader = SkShaders::Blend(SkBlendMode::kSrcOut, mask_shader, layer_shader,
116                                                 &phase_shader_matrix);
117         } else {
118             fMainPassShader  = std::move(layer_shader);
119             fPhasePassShader = nullptr;
120         }
121 
122         // outputW and outputH also use layer size percentage units.
123         const auto outputW = fOutputW * 0.01f * fLayerSize.width(),
124                    outputH = fOutputH * 0.01f * fLayerSize.height();
125 
126         return SkRect::MakeXYWH((fLayerSize.width()  - outputW) * 0.5f,
127                                 (fLayerSize.height() - outputH) * 0.5f,
128                                 outputW, outputH);
129     }
130 
onRender(SkCanvas * canvas,const RenderContext * ctx) const131     void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
132         // AE allow one of the tile dimensions to collapse, but not both.
133         if (this->bounds().isEmpty() || (fTileW <= 0 && fTileH <= 0)) {
134             return;
135         }
136 
137         SkPaint paint;
138         paint.setAntiAlias(true);
139 
140         paint.setShader(fMainPassShader);
141         canvas->drawRect(this->bounds(), paint);
142 
143         if (fPhasePassShader) {
144             paint.setShader(fPhasePassShader);
145             canvas->drawRect(this->bounds(), paint);
146         }
147     }
148 
149 private:
150     const SkSize fLayerSize;
151 
152     SkPoint  fTileCenter      = { 0, 0 };
153     SkScalar fTileW           = 1,
154              fTileH           = 1,
155              fOutputW         = 1,
156              fOutputH         = 1,
157              fPhase           = 0;
158     bool     fMirrorEdges     = false;
159     bool     fHorizontalPhase = false;
160 
161     // These are computed/cached on revalidation.
162     sk_sp<SkPicture> fLayerPicture;      // cached picture for layer content
163     sk_sp<SkShader>  fMainPassShader,    // shader for the main tile(s)
164                      fPhasePassShader;   // shader for the phased tile(s)
165 
166     using INHERITED = sksg::CustomRenderNode;
167 };
168 
169 } // anonymous ns
170 
attachMotionTileEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const171 sk_sp<sksg::RenderNode> EffectBuilder::attachMotionTileEffect(const skjson::ArrayValue& jprops,
172                                                               sk_sp<sksg::RenderNode> layer) const {
173     enum : size_t {
174         kTileCenter_Index           = 0,
175         kTileWidth_Index            = 1,
176         kTileHeight_Index           = 2,
177         kOutputWidth_Index          = 3,
178         kOutputHeight_Index         = 4,
179         kMirrorEdges_Index          = 5,
180         kPhase_Index                = 6,
181         kHorizontalPhaseShift_Index = 7,
182     };
183 
184     auto tiler = sk_make_sp<TileRenderNode>(fLayerSize, std::move(layer));
185 
186     fBuilder->bindProperty<VectorValue>(GetPropValue(jprops, kTileCenter_Index),
187         [tiler](const VectorValue& tc) {
188             tiler->setTileCenter(ValueTraits<VectorValue>::As<SkPoint>(tc));
189         });
190     fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kTileWidth_Index),
191         [tiler](const ScalarValue& tw) {
192             tiler->setTileWidth(tw);
193         });
194     fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kTileHeight_Index),
195         [tiler](const ScalarValue& th) {
196             tiler->setTileHeight(th);
197         });
198     fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputWidth_Index),
199         [tiler](const ScalarValue& ow) {
200             tiler->setOutputWidth(ow);
201         });
202     fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kOutputHeight_Index),
203         [tiler](const ScalarValue& oh) {
204             tiler->setOutputHeight(oh);
205         });
206     fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kMirrorEdges_Index),
207         [tiler](const ScalarValue& me) {
208             tiler->setMirrorEdges(SkScalarRoundToInt(me));
209         });
210     fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kPhase_Index),
211         [tiler](const ScalarValue& ph) {
212             tiler->setPhase(ph);
213         });
214     fBuilder->bindProperty<ScalarValue>(GetPropValue(jprops, kHorizontalPhaseShift_Index),
215         [tiler](const ScalarValue& hp) {
216             tiler->setHorizontalPhase(SkScalarRoundToInt(hp));
217         });
218 
219     return tiler;
220 }
221 
222 } // namespace internal
223 } // namespace skottie
224