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