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