1 /*
2 * Copyright 2021 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/SkM44.h"
11 #include "include/core/SkMatrix.h"
12 #include "include/core/SkPaint.h"
13 #include "include/core/SkRect.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkScalar.h"
16 #include "include/core/SkShader.h"
17 #include "include/core/SkString.h"
18 #include "include/effects/SkRuntimeEffect.h"
19 #include "include/private/base/SkAssert.h"
20 #include "include/private/base/SkFloatingPoint.h"
21 #include "include/private/base/SkTPin.h"
22 #include "modules/skottie/src/Adapter.h"
23 #include "modules/skottie/src/SkottiePriv.h"
24 #include "modules/skottie/src/SkottieValue.h"
25 #include "modules/skottie/src/effects/Effects.h"
26 #include "modules/sksg/include/SkSGNode.h"
27 #include "modules/sksg/include/SkSGRenderNode.h"
28 #include "src/base/SkRandom.h"
29
30 #include <algorithm>
31 #include <array>
32 #include <cmath>
33 #include <cstddef>
34 #include <cstdint>
35 #include <tuple>
36 #include <utility>
37 #include <vector>
38
39 struct SkPoint;
40
41 namespace skjson {
42 class ArrayValue;
43 }
44 namespace sksg {
45 class InvalidationController;
46 }
47
48 namespace skottie::internal {
49 namespace {
50
51 // An implementation of the ADBE Fractal Noise effect:
52 //
53 // - multiple noise sublayers (octaves) are combined using a weighted average
54 // - each layer is subject to a (cumulative) transform, filter and post-sampling options
55 //
56 // Parameters:
57 //
58 // * Noise Type -- controls noise layer post-sampling filtering
59 // (Block, Linear, Soft Linear, Spline)
60 // * Fractal Type -- determines a noise layer post-filtering transformation
61 // (Basic, Turbulent Smooth, Turbulent Basic, etc)
62 // * Transform -- offset/scale/rotate the noise effect (local matrix)
63 //
64 // * Complexity -- number of sublayers;
65 // can be fractional, where the fractional part modulates the last layer
66 // * Evolution -- controls noise topology in a gradual manner (can be animated for smooth
67 // noise transitions)
68 // * Sub Influence -- relative amplitude weight for sublayers (cumulative)
69 //
70 // * Sub Scaling/Rotation/Offset -- relative scale for sublayers (cumulative)
71 //
72 // * Invert -- invert noise values
73 //
74 // * Contrast -- apply a contrast to the noise result
75 //
76 // * Brightness -- apply a brightness effect to the noise result
77 //
78 //
79 // TODO:
80 // - Invert
81 // - Contrast/Brightness
82
83 static constexpr char gNoiseEffectSkSL[] =
84 "uniform float3x3 u_submatrix;" // sublayer transform
85
86 "uniform float2 u_noise_planes;" // noise planes computed based on evolution params
87 "uniform float u_noise_weight," // noise planes lerp weight
88 "u_octaves," // number of octaves (can be fractional)
89 "u_persistence;" // relative octave weight
90
91 // Hash based on hash13 (https://www.shadertoy.com/view/4djSRW).
92 "float hash(float3 v) {"
93 "v = fract(v*0.1031);"
94 "v += dot(v, v.zxy + 31.32);"
95 "return fract((v.x + v.y)*v.z);"
96 "}"
97
98 // The general idea is to compute a coherent hash for two planes in discretized (x,y,e) space,
99 // and interpolate between them. This yields gradual changes when animating |e| - which is the
100 // desired outcome.
101 "float sample_noise(float2 xy) {"
102 "xy = floor(xy);"
103
104 "float n0 = hash(float3(xy, u_noise_planes.x)),"
105 "n1 = hash(float3(xy, u_noise_planes.y));"
106
107 // Note: Ideally we would use 4 samples (-1, 0, 1, 2) and cubic interpolation for
108 // better results -- but that's significantly more expensive than lerp.
109
110 "return mix(n0, n1, u_noise_weight);"
111 "}"
112
113 // filter() placeholder
114 "%s"
115
116 // fractal() placeholder
117 "%s"
118
119 // Generate ceil(u_octaves) noise layers and combine based on persistentce and sublayer xform.
120 "float4 main(vec2 xy) {"
121 "float oct = u_octaves," // initial octave count (this is the effective loop counter)
122 "amp = 1," // initial layer amplitude
123 "wacc = 0," // weight accumulator
124 "n = 0;" // noise accumulator
125
126 // Constant loop counter chosen to be >= ceil(u_octaves).
127 // The logical counter is actually 'oct'.
128 "for (float i = 0; i < %u; ++i) {"
129 // effective layer weight
130 // -- for full octaves: layer amplitude
131 // -- for fractional octave: layer amplitude modulated by fractional part
132 "float w = amp*min(oct,1.0);"
133
134 "n += w*fractal(filter(xy));"
135 "wacc += w;"
136
137 "if (oct <= 1.0) { break; }"
138
139 "oct -= 1.0;"
140 "amp *= u_persistence;"
141 "xy = (u_submatrix*float3(xy,1)).xy;"
142 "}"
143
144 "n /= wacc;"
145
146 // TODO: fractal functions
147
148 "return float4(n,n,n,1);"
149 "}";
150
151 static constexpr char gFilterNearestSkSL[] =
152 "float filter(float2 xy) {"
153 "return sample_noise(xy);"
154 "}";
155
156 static constexpr char gFilterLinearSkSL[] =
157 "float filter(float2 xy) {"
158 "xy -= 0.5;"
159
160 "float n00 = sample_noise(xy + float2(0,0)),"
161 "n10 = sample_noise(xy + float2(1,0)),"
162 "n01 = sample_noise(xy + float2(0,1)),"
163 "n11 = sample_noise(xy + float2(1,1));"
164
165 "float2 t = fract(xy);"
166
167 "return mix(mix(n00, n10, t.x), mix(n01, n11, t.x), t.y);"
168 "}";
169
170 static constexpr char gFilterSoftLinearSkSL[] =
171 "float filter(float2 xy) {"
172 "xy -= 0.5;"
173
174 "float n00 = sample_noise(xy + float2(0,0)),"
175 "n10 = sample_noise(xy + float2(1,0)),"
176 "n01 = sample_noise(xy + float2(0,1)),"
177 "n11 = sample_noise(xy + float2(1,1));"
178
179 "float2 t = smoothstep(0, 1, fract(xy));"
180
181 "return mix(mix(n00, n10, t.x), mix(n01, n11, t.x), t.y);"
182 "}";
183
184 static constexpr char gFractalBasicSkSL[] =
185 "float fractal(float n) {"
186 "return n;"
187 "}";
188
189 static constexpr char gFractalTurbulentBasicSkSL[] =
190 "float fractal(float n) {"
191 "return 2*abs(0.5 - n);"
192 "}";
193
194 static constexpr char gFractalTurbulentSmoothSkSL[] =
195 "float fractal(float n) {"
196 "n = 2*abs(0.5 - n);"
197 "return n*n;"
198 "}";
199
200 static constexpr char gFractalTurbulentSharpSkSL[] =
201 "float fractal(float n) {"
202 "return sqrt(2*abs(0.5 - n));"
203 "}";
204
205 enum class NoiseFilter {
206 kNearest,
207 kLinear,
208 kSoftLinear,
209 // TODO: kSpline?
210 };
211
212 enum class NoiseFractal {
213 kBasic,
214 kTurbulentBasic,
215 kTurbulentSmooth,
216 kTurbulentSharp,
217 };
218
make_noise_effect(unsigned loops,const char * filter,const char * fractal)219 sk_sp<SkRuntimeEffect> make_noise_effect(unsigned loops, const char* filter, const char* fractal) {
220 auto result = SkRuntimeEffect::MakeForShader(
221 SkStringPrintf(gNoiseEffectSkSL, filter, fractal, loops), {});
222
223 return std::move(result.effect);
224 }
225
noise_effect(float octaves,NoiseFilter filter,NoiseFractal fractal)226 sk_sp<SkRuntimeEffect> noise_effect(float octaves, NoiseFilter filter, NoiseFractal fractal) {
227 static constexpr char const* gFilters[] = {
228 gFilterNearestSkSL,
229 gFilterLinearSkSL,
230 gFilterSoftLinearSkSL
231 };
232
233 static constexpr char const* gFractals[] = {
234 gFractalBasicSkSL,
235 gFractalTurbulentBasicSkSL,
236 gFractalTurbulentSmoothSkSL,
237 gFractalTurbulentSharpSkSL
238 };
239
240 SkASSERT(static_cast<size_t>(filter) < std::size(gFilters));
241 SkASSERT(static_cast<size_t>(fractal) < std::size(gFractals));
242
243 // Bin the loop counter based on the number of octaves (range: [1..20]).
244 // Low complexities are common, so we maximize resolution for the low end.
245 struct BinInfo {
246 float threshold;
247 unsigned loops;
248 };
249 static constexpr BinInfo kLoopBins[] = {
250 { 8, 20 },
251 { 4, 8 },
252 { 3, 4 },
253 { 2, 3 },
254 { 1, 2 },
255 { 0, 1 }
256 };
257
258 auto bin_index = [](float octaves) {
259 SkASSERT(octaves > kLoopBins[std::size(kLoopBins) - 1].threshold);
260
261 for (size_t i = 0; i < std::size(kLoopBins); ++i) {
262 if (octaves > kLoopBins[i].threshold) {
263 return i;
264 }
265 }
266 SkUNREACHABLE;
267 };
268
269 static SkRuntimeEffect* kEffectCache[std::size(kLoopBins)]
270 [std::size(gFilters)]
271 [std::size(gFractals)];
272
273 const size_t bin = bin_index(octaves);
274
275 auto& effect = kEffectCache[bin]
276 [static_cast<size_t>(filter)]
277 [static_cast<size_t>(fractal)];
278 if (!effect) {
279 effect = make_noise_effect(kLoopBins[bin].loops,
280 gFilters[static_cast<size_t>(filter)],
281 gFractals[static_cast<size_t>(fractal)])
282 .release();
283 }
284
285 SkASSERT(effect);
286 return sk_ref_sp(effect);
287 }
288
289 class FractalNoiseNode final : public sksg::CustomRenderNode {
290 public:
FractalNoiseNode(sk_sp<RenderNode> child)291 explicit FractalNoiseNode(sk_sp<RenderNode> child) : INHERITED({std::move(child)}) {}
292
293 SG_ATTRIBUTE(Matrix , SkMatrix , fMatrix )
294 SG_ATTRIBUTE(SubMatrix , SkMatrix , fSubMatrix )
295
296 SG_ATTRIBUTE(NoiseFilter , NoiseFilter , fFilter )
297 SG_ATTRIBUTE(NoiseFractal , NoiseFractal, fFractal )
298 SG_ATTRIBUTE(NoisePlanes , SkV2 , fNoisePlanes )
299 SG_ATTRIBUTE(NoiseWeight , float , fNoiseWeight )
300 SG_ATTRIBUTE(Octaves , float , fOctaves )
301 SG_ATTRIBUTE(Persistence , float , fPersistence )
302
303 private:
getEffect(NoiseFilter filter) const304 sk_sp<SkRuntimeEffect> getEffect(NoiseFilter filter) const {
305 switch (fFractal) {
306 case NoiseFractal::kBasic:
307 return noise_effect(fOctaves, filter, NoiseFractal::kBasic);
308 case NoiseFractal::kTurbulentBasic:
309 return noise_effect(fOctaves, filter, NoiseFractal::kTurbulentBasic);
310 case NoiseFractal::kTurbulentSmooth:
311 return noise_effect(fOctaves, filter, NoiseFractal::kTurbulentSmooth);
312 case NoiseFractal::kTurbulentSharp:
313 return noise_effect(fOctaves, filter, NoiseFractal::kTurbulentSharp);
314 }
315 SkUNREACHABLE;
316 }
317
getEffect() const318 sk_sp<SkRuntimeEffect> getEffect() const {
319 switch (fFilter) {
320 case NoiseFilter::kNearest : return this->getEffect(NoiseFilter::kNearest);
321 case NoiseFilter::kLinear : return this->getEffect(NoiseFilter::kLinear);
322 case NoiseFilter::kSoftLinear: return this->getEffect(NoiseFilter::kSoftLinear);
323 }
324 SkUNREACHABLE;
325 }
326
buildEffectShader() const327 sk_sp<SkShader> buildEffectShader() const {
328 SkRuntimeShaderBuilder builder(this->getEffect());
329
330 builder.uniform("u_noise_planes") = fNoisePlanes;
331 builder.uniform("u_noise_weight") = fNoiseWeight;
332 builder.uniform("u_octaves" ) = fOctaves;
333 builder.uniform("u_persistence" ) = fPersistence;
334 builder.uniform("u_submatrix" ) = std::array<float,9>{
335 fSubMatrix.rc(0,0), fSubMatrix.rc(1,0), fSubMatrix.rc(2,0),
336 fSubMatrix.rc(0,1), fSubMatrix.rc(1,1), fSubMatrix.rc(2,1),
337 fSubMatrix.rc(0,2), fSubMatrix.rc(1,2), fSubMatrix.rc(2,2),
338 };
339
340 return builder.makeShader(&fMatrix);
341 }
342
onRevalidate(sksg::InvalidationController * ic,const SkMatrix & ctm)343 SkRect onRevalidate(sksg::InvalidationController* ic, const SkMatrix& ctm) override {
344 const auto& child = this->children()[0];
345 const auto bounds = child->revalidate(ic, ctm);
346
347 fEffectShader = this->buildEffectShader();
348
349 return bounds;
350 }
351
onRender(SkCanvas * canvas,const RenderContext * ctx) const352 void onRender(SkCanvas* canvas, const RenderContext* ctx) const override {
353 const auto& bounds = this->bounds();
354 const auto local_ctx = ScopedRenderContext(canvas, ctx)
355 .setIsolation(bounds, canvas->getTotalMatrix(), true);
356
357 canvas->saveLayer(&bounds, nullptr);
358 this->children()[0]->render(canvas, local_ctx);
359
360 SkPaint effect_paint;
361 effect_paint.setShader(fEffectShader);
362 effect_paint.setBlendMode(SkBlendMode::kSrcIn);
363
364 canvas->drawPaint(effect_paint);
365 }
366
onNodeAt(const SkPoint &) const367 const RenderNode* onNodeAt(const SkPoint&) const override { return nullptr; } // no hit-testing
368
369 sk_sp<SkShader> fEffectShader;
370
371 SkMatrix fMatrix,
372 fSubMatrix;
373 NoiseFilter fFilter = NoiseFilter::kNearest;
374 NoiseFractal fFractal = NoiseFractal::kBasic;
375 SkV2 fNoisePlanes = {0,0};
376 float fNoiseWeight = 0,
377 fOctaves = 1,
378 fPersistence = 1;
379
380 using INHERITED = sksg::CustomRenderNode;
381 };
382
383 class FractalNoiseAdapter final : public DiscardableAdapterBase<FractalNoiseAdapter,
384 FractalNoiseNode> {
385 public:
FractalNoiseAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder * abuilder,sk_sp<FractalNoiseNode> node)386 FractalNoiseAdapter(const skjson::ArrayValue& jprops,
387 const AnimationBuilder* abuilder,
388 sk_sp<FractalNoiseNode> node)
389 : INHERITED(std::move(node))
390 {
391 EffectBinder(jprops, *abuilder, this)
392 .bind( 0, fFractalType )
393 .bind( 1, fNoiseType )
394 .bind( 2, fInvert )
395 .bind( 3, fContrast )
396 .bind( 4, fBrightness )
397 // 5 -- overflow
398 // 6 -- transform begin-group
399 .bind( 7, fRotation )
400 .bind( 8, fUniformScaling )
401 .bind( 9, fScale )
402 .bind(10, fScaleWidth )
403 .bind(11, fScaleHeight )
404 .bind(12, fOffset )
405 // 13 -- TODO: perspective offset
406 // 14 -- transform end-group
407 .bind(15, fComplexity )
408 // 16 -- sub settings begin-group
409 .bind(17, fSubInfluence )
410 .bind(18, fSubScale )
411 .bind(19, fSubRotation )
412 .bind(20, fSubOffset )
413 // 21 -- center subscale
414 // 22 -- sub settings end-group
415 .bind(23, fEvolution )
416 // 24 -- evolution options begin-group
417 .bind(25, fCycleEvolution )
418 .bind(26, fCycleRevolutions)
419 .bind(27, fRandomSeed )
420 // 28 -- evolution options end-group
421 .bind(29, fOpacity );
422 // 30 -- TODO: blending mode
423 }
424
425 private:
noise() const426 std::tuple<SkV2, float> noise() const {
427 // Constant chosen to visually match AE's evolution rate.
428 static constexpr auto kEvolutionScale = 0.25f;
429
430 // Evolution inputs:
431 //
432 // * evolution - main evolution control (degrees)
433 // * cycle evolution - flag controlling whether evolution cycles
434 // * cycle revolutions - number of revolutions after which evolution cycles (period)
435 // * random seed - determines an arbitrary starting plane (evolution offset)
436 //
437 // The shader uses evolution floor/ceil to select two noise planes, and the fractional part
438 // to interpolate between the two -> in order to wrap around smoothly, the cycle/period
439 // must be integral.
440 const float
441 evo_rad = SkDegreesToRadians(fEvolution),
442 rev_rad = std::max(fCycleRevolutions, 1.0f)*SK_FloatPI*2,
443 cycle = fCycleEvolution
444 ? SkScalarRoundToScalar(rev_rad*kEvolutionScale)
445 : SK_ScalarMax,
446 // Adjust scale when cycling to ensure an integral period (post scaling).
447 scale = fCycleEvolution
448 ? cycle/rev_rad
449 : kEvolutionScale,
450 offset = SkRandom(static_cast<uint32_t>(fRandomSeed)).nextRangeU(0, 100),
451 evo = evo_rad*scale,
452 evo_ = std::floor(evo),
453 weight = evo - evo_;
454
455 // We want the GLSL mod() flavor.
456 auto glsl_mod = [](float x, float y) {
457 return x - y*std::floor(x/y);
458 };
459
460 const SkV2 noise_planes = {
461 glsl_mod(evo_ + 0, cycle) + offset,
462 glsl_mod(evo_ + 1, cycle) + offset,
463 };
464
465 return std::make_tuple(noise_planes, weight);
466 }
467
shaderMatrix() const468 SkMatrix shaderMatrix() const {
469 static constexpr float kGridSize = 64;
470
471 const auto scale = (SkScalarRoundToInt(fUniformScaling) == 1)
472 ? SkV2{fScale, fScale}
473 : SkV2{fScaleWidth, fScaleHeight};
474
475 return SkMatrix::Translate(fOffset.x, fOffset.y)
476 * SkMatrix::Scale(SkTPin(scale.x, 1.0f, 10000.0f) * 0.01f,
477 SkTPin(scale.y, 1.0f, 10000.0f) * 0.01f)
478 * SkMatrix::RotateDeg(fRotation)
479 * SkMatrix::Scale(kGridSize, kGridSize);
480 }
481
subMatrix() const482 SkMatrix subMatrix() const {
483 const auto scale = 100 / SkTPin(fSubScale, 10.0f, 10000.0f);
484
485 return SkMatrix::Translate(-fSubOffset.x * 0.01f, -fSubOffset.y * 0.01f)
486 * SkMatrix::RotateDeg(-fSubRotation)
487 * SkMatrix::Scale(scale, scale);
488 }
489
noiseFilter() const490 NoiseFilter noiseFilter() const {
491 switch (SkScalarRoundToInt(fNoiseType)) {
492 case 1: return NoiseFilter::kNearest;
493 case 2: return NoiseFilter::kLinear;
494 default: return NoiseFilter::kSoftLinear;
495 }
496 SkUNREACHABLE;
497 }
498
noiseFractal() const499 NoiseFractal noiseFractal() const {
500 switch (SkScalarRoundToInt(fFractalType)) {
501 case 1: return NoiseFractal::kBasic;
502 case 3: return NoiseFractal::kTurbulentSmooth;
503 case 4: return NoiseFractal::kTurbulentBasic;
504 default: return NoiseFractal::kTurbulentSharp;
505 }
506 SkUNREACHABLE;
507 }
508
onSync()509 void onSync() override {
510 const auto& n = this->node();
511
512 const auto [noise_planes, noise_weight] = this->noise();
513
514 n->setOctaves(SkTPin(fComplexity, 1.0f, 20.0f));
515 n->setPersistence(SkTPin(fSubInfluence * 0.01f, 0.0f, 100.0f));
516 n->setNoisePlanes(noise_planes);
517 n->setNoiseWeight(noise_weight);
518 n->setNoiseFilter(this->noiseFilter());
519 n->setNoiseFractal(this->noiseFractal());
520 n->setMatrix(this->shaderMatrix());
521 n->setSubMatrix(this->subMatrix());
522 }
523
524 Vec2Value fOffset = {0,0},
525 fSubOffset = {0,0};
526
527 ScalarValue fFractalType = 0,
528 fNoiseType = 0,
529
530 fRotation = 0,
531 fUniformScaling = 0,
532 fScale = 100, // used when uniform scaling is selected
533 fScaleWidth = 100, // used when uniform scaling is not selected
534 fScaleHeight = 100, // ^
535
536 fComplexity = 1,
537 fSubInfluence = 100,
538 fSubScale = 50,
539 fSubRotation = 0,
540
541 fEvolution = 0,
542 fCycleEvolution = 0,
543 fCycleRevolutions = 0,
544 fRandomSeed = 0,
545
546 fOpacity = 100, // TODO
547 fInvert = 0, // TODO
548 fContrast = 100, // TODO
549 fBrightness = 0; // TODO
550
551 using INHERITED = DiscardableAdapterBase<FractalNoiseAdapter, FractalNoiseNode>;
552 };
553
554 } // namespace
555
attachFractalNoiseEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const556 sk_sp<sksg::RenderNode> EffectBuilder::attachFractalNoiseEffect(
557 const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
558 auto fractal_noise = sk_make_sp<FractalNoiseNode>(std::move(layer));
559
560 return fBuilder->attachDiscardableAdapter<FractalNoiseAdapter>(jprops, fBuilder,
561 std::move(fractal_noise));
562 }
563
564 } // namespace skottie::internal
565