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