blob: 71be1636beb7845ea801da0928c044783df6c30b [file] [log] [blame]
Florin Malitab97824d2019-06-17 11:37:02 -04001/*
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"
Florin Malita60e60df2019-06-17 14:18:11 -040012#include "include/core/SkShader.h"
13#include "include/effects/SkGradientShader.h"
Florin Malitacc982ec2020-01-30 10:05:02 -050014#include "modules/skottie/src/Adapter.h"
Florin Malitab97824d2019-06-17 11:37:02 -040015#include "modules/skottie/src/SkottieValue.h"
16#include "modules/sksg/include/SkSGRenderNode.h"
17#include "src/utils/SkJSON.h"
18
Florin Malita60e60df2019-06-17 14:18:11 -040019#include <cmath>
20
Florin Malitab97824d2019-06-17 11:37:02 -040021namespace skottie {
22namespace internal {
23
24namespace {
25
26// AE motion tile effect semantics
27// (https://helpx.adobe.com/after-effects/using/stylize-effects.html#motion_tile_effect):
28//
29// - the full content of the layer is mapped to a tile: tile_center, tile_width, tile_height
30//
31// - tiles are repeated in both dimensions to fill the output area: output_width, output_height
32//
33// - tiling mode is either kRepeat (default) or kMirror (when mirror_edges == true)
34//
35// - for a non-zero phase, alternating vertical columns (every other column) are offset by
36// the specified amount
37//
38// - when horizontal_phase is true, the phase is applied to horizontal rows instead of columns
39//
40class TileRenderNode final : public sksg::CustomRenderNode {
41public:
42 TileRenderNode(const SkSize& size, sk_sp<sksg::RenderNode> layer)
43 : INHERITED({std::move(layer)})
44 , fLayerSize(size) {}
45
46 SG_ATTRIBUTE(TileCenter , SkPoint , fTileCenter )
47 SG_ATTRIBUTE(TileWidth , SkScalar, fTileW )
48 SG_ATTRIBUTE(TileHeight , SkScalar, fTileH )
49 SG_ATTRIBUTE(OutputWidth , SkScalar, fOutputW )
50 SG_ATTRIBUTE(OutputHeight , SkScalar, fOutputH )
51 SG_ATTRIBUTE(Phase , SkScalar, fPhase )
52 SG_ATTRIBUTE(MirrorEdges , bool , fMirrorEdges )
53 SG_ATTRIBUTE(HorizontalPhase, bool , fHorizontalPhase)
54
55protected:
56 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
57
58 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
Florin Malitaccacfa02019-07-10 13:38:48 -040059 // Re-record the layer picture if needed.
60 if (!fLayerPicture || this->hasChildrenInval()) {
61 SkASSERT(this->children().size() == 1ul);
62 const auto& layer = this->children()[0];
63
64 layer->revalidate(ic, ctm);
65
66 SkPictureRecorder recorder;
67 layer->render(recorder.beginRecording(fLayerSize.width(), fLayerSize.height()));
68 fLayerPicture = recorder.finishRecordingAsPicture();
69 }
Florin Malitab97824d2019-06-17 11:37:02 -040070
Florin Malita60e60df2019-06-17 14:18:11 -040071 // tileW and tileH use layer size percentage units.
72 const auto tileW = SkTPin(fTileW, 0.0f, 100.0f) * 0.01f * fLayerSize.width(),
73 tileH = SkTPin(fTileH, 0.0f, 100.0f) * 0.01f * fLayerSize.height();
74 const auto tile_size = SkSize::Make(std::max(tileW, 1.0f),
75 std::max(tileH, 1.0f));
76 const auto tile = SkRect::MakeXYWH(fTileCenter.fX - 0.5f * tile_size.width(),
77 fTileCenter.fY - 0.5f * tile_size.height(),
78 tile_size.width(),
79 tile_size.height());
80
Florin Malitaccacfa02019-07-10 13:38:48 -040081 const auto layerShaderMatrix = SkMatrix::MakeRectToRect(
82 SkRect::MakeWH(fLayerSize.width(), fLayerSize.height()),
83 tile, SkMatrix::kFill_ScaleToFit);
84
85 const auto tm = fMirrorEdges ? SkTileMode::kMirror : SkTileMode::kRepeat;
86 auto layer_shader = fLayerPicture->makeShader(tm, tm, &layerShaderMatrix);
Florin Malita60e60df2019-06-17 14:18:11 -040087
88 if (fPhase) {
89 // To implement AE phase semantics, we construct a mask shader for the pass-through
90 // rows/columns. We then draw the layer content through this mask, and then again
91 // through the inverse mask with a phase shift.
92 const auto phase_vec = fHorizontalPhase
93 ? SkVector::Make(tile.width(), 0)
94 : SkVector::Make(0, tile.height());
Florin Malitaccacfa02019-07-10 13:38:48 -040095 const auto phase_shift = SkVector::Make(phase_vec.fX / layerShaderMatrix.getScaleX(),
96 phase_vec.fY / layerShaderMatrix.getScaleY())
Florin Malita60e60df2019-06-17 14:18:11 -040097 * std::fmod(fPhase * (1/360.0f), 1);
Mike Reed1f607332020-05-21 12:11:27 -040098 const auto phase_shader_matrix = SkMatrix::Translate(phase_shift.x(), phase_shift.y());
Florin Malita60e60df2019-06-17 14:18:11 -040099
100 // The mask is generated using a step gradient shader, spanning 2 x tile width/height,
101 // and perpendicular to the phase vector.
102 static constexpr SkColor colors[] = { 0xffffffff, 0x00000000 };
103 static constexpr SkScalar pos[] = { 0.5f, 0.5f };
104
105 const SkPoint pts[] = {{ tile.x(), tile.y() },
106 { tile.x() + 2 * (tile.width() - phase_vec.fX),
107 tile.y() + 2 * (tile.height() - phase_vec.fY) }};
108
Florin Malitaccacfa02019-07-10 13:38:48 -0400109 auto mask_shader = SkGradientShader::MakeLinear(pts, colors, pos,
110 SK_ARRAY_COUNT(colors),
111 SkTileMode::kRepeat);
112
113 // First drawing pass: in-place masked layer content.
114 fMainPassShader = SkShaders::Blend(SkBlendMode::kSrcIn , mask_shader, layer_shader);
115 // Second pass: phased-shifted layer content, with an inverse mask.
Kevin Lubick988ce042020-03-25 13:13:20 -0400116 fPhasePassShader = SkShaders::Blend(SkBlendMode::kSrcOut, mask_shader, layer_shader)
117 ->makeWithLocalMatrix(phase_shader_matrix);
Florin Malita60e60df2019-06-17 14:18:11 -0400118 } else {
Florin Malitaccacfa02019-07-10 13:38:48 -0400119 fMainPassShader = std::move(layer_shader);
120 fPhasePassShader = nullptr;
Florin Malita60e60df2019-06-17 14:18:11 -0400121 }
122
123 // outputW and outputH also use layer size percentage units.
Florin Malitab97824d2019-06-17 11:37:02 -0400124 const auto outputW = fOutputW * 0.01f * fLayerSize.width(),
125 outputH = fOutputH * 0.01f * fLayerSize.height();
126
127 return SkRect::MakeXYWH((fLayerSize.width() - outputW) * 0.5f,
128 (fLayerSize.height() - outputH) * 0.5f,
129 outputW, outputH);
130 }
131
132 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
Florin Malitab97824d2019-06-17 11:37:02 -0400133 // AE allow one of the tile dimensions to collapse, but not both.
Florin Malita60e60df2019-06-17 14:18:11 -0400134 if (this->bounds().isEmpty() || (fTileW <= 0 && fTileH <= 0)) {
Florin Malitab97824d2019-06-17 11:37:02 -0400135 return;
136 }
137
Florin Malitab97824d2019-06-17 11:37:02 -0400138 SkPaint paint;
139 paint.setAntiAlias(true);
Florin Malitab97824d2019-06-17 11:37:02 -0400140
Florin Malitaccacfa02019-07-10 13:38:48 -0400141 paint.setShader(fMainPassShader);
Florin Malitab97824d2019-06-17 11:37:02 -0400142 canvas->drawRect(this->bounds(), paint);
Florin Malita60e60df2019-06-17 14:18:11 -0400143
Florin Malitaccacfa02019-07-10 13:38:48 -0400144 if (fPhasePassShader) {
145 paint.setShader(fPhasePassShader);
Florin Malita60e60df2019-06-17 14:18:11 -0400146 canvas->drawRect(this->bounds(), paint);
147 }
Florin Malitab97824d2019-06-17 11:37:02 -0400148 }
149
150private:
151 const SkSize fLayerSize;
152
153 SkPoint fTileCenter = { 0, 0 };
154 SkScalar fTileW = 1,
155 fTileH = 1,
156 fOutputW = 1,
157 fOutputH = 1,
158 fPhase = 0;
159 bool fMirrorEdges = false;
160 bool fHorizontalPhase = false;
161
Florin Malita60e60df2019-06-17 14:18:11 -0400162 // These are computed/cached on revalidation.
Florin Malitaccacfa02019-07-10 13:38:48 -0400163 sk_sp<SkPicture> fLayerPicture; // cached picture for layer content
164 sk_sp<SkShader> fMainPassShader, // shader for the main tile(s)
165 fPhasePassShader; // shader for the phased tile(s)
Florin Malita60e60df2019-06-17 14:18:11 -0400166
Florin Malitab97824d2019-06-17 11:37:02 -0400167 using INHERITED = sksg::CustomRenderNode;
168};
169
Florin Malitacc982ec2020-01-30 10:05:02 -0500170class MotionTileAdapter final : public DiscardableAdapterBase<MotionTileAdapter, TileRenderNode> {
171public:
172 MotionTileAdapter(const skjson::ArrayValue& jprops,
173 sk_sp<sksg::RenderNode> layer,
174 const AnimationBuilder& abuilder,
175 const SkSize& layer_size)
176 : INHERITED(sk_make_sp<TileRenderNode>(layer_size, std::move(layer))) {
177
178 enum : size_t {
179 kTileCenter_Index = 0,
180 kTileWidth_Index = 1,
181 kTileHeight_Index = 2,
182 kOutputWidth_Index = 3,
183 kOutputHeight_Index = 4,
184 kMirrorEdges_Index = 5,
185 kPhase_Index = 6,
186 kHorizontalPhaseShift_Index = 7,
187 };
188
189 EffectBinder(jprops, abuilder, this)
190 .bind( kTileCenter_Index, fTileCenter )
191 .bind( kTileWidth_Index, fTileW )
192 .bind( kTileHeight_Index, fTileH )
193 .bind( kOutputWidth_Index, fOutputW )
194 .bind( kOutputHeight_Index, fOutputH )
195 .bind( kMirrorEdges_Index, fMirrorEdges )
196 .bind( kPhase_Index, fPhase )
197 .bind(kHorizontalPhaseShift_Index, fHorizontalPhase);
198 }
199
200private:
201 void onSync() override {
202 const auto& tiler = this->node();
203
Florin Malitac02e77c2020-03-12 09:34:41 -0400204 tiler->setTileCenter({fTileCenter.x, fTileCenter.y});
Florin Malitacc982ec2020-01-30 10:05:02 -0500205 tiler->setTileWidth (fTileW);
206 tiler->setTileHeight(fTileH);
207 tiler->setOutputWidth (fOutputW);
208 tiler->setOutputHeight(fOutputH);
209 tiler->setPhase(fPhase);
210 tiler->setMirrorEdges(SkToBool(fMirrorEdges));
211 tiler->setHorizontalPhase(SkToBool(fHorizontalPhase));
212 }
213
Florin Malitac02e77c2020-03-12 09:34:41 -0400214 Vec2Value fTileCenter = {0,0};
Florin Malitacc982ec2020-01-30 10:05:02 -0500215 ScalarValue fTileW = 1,
216 fTileH = 1,
217 fOutputW = 1,
218 fOutputH = 1,
219 fMirrorEdges = 0,
220 fPhase = 0,
221 fHorizontalPhase = 0;
222
223 using INHERITED = DiscardableAdapterBase<MotionTileAdapter, TileRenderNode>;
224};
225
Florin Malitab97824d2019-06-17 11:37:02 -0400226} // anonymous ns
227
228sk_sp<sksg::RenderNode> EffectBuilder::attachMotionTileEffect(const skjson::ArrayValue& jprops,
229 sk_sp<sksg::RenderNode> layer) const {
Florin Malitacc982ec2020-01-30 10:05:02 -0500230 return fBuilder->attachDiscardableAdapter<MotionTileAdapter>(jprops,
231 std::move(layer),
232 *fBuilder,
233 fLayerSize);
Florin Malitab97824d2019-06-17 11:37:02 -0400234}
235
236} // namespace internal
237} // namespace skottie