1*c8dee2aaSAndroid Build Coastguard Worker /*
2*c8dee2aaSAndroid Build Coastguard Worker * Copyright 2019 Google Inc.
3*c8dee2aaSAndroid Build Coastguard Worker *
4*c8dee2aaSAndroid Build Coastguard Worker * Use of this source code is governed by a BSD-style license that can be
5*c8dee2aaSAndroid Build Coastguard Worker * found in the LICENSE file.
6*c8dee2aaSAndroid Build Coastguard Worker */
7*c8dee2aaSAndroid Build Coastguard Worker
8*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkColor.h"
9*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkPoint.h"
10*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkRefCnt.h"
11*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkScalar.h"
12*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkShader.h"
13*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkSize.h"
14*c8dee2aaSAndroid Build Coastguard Worker #include "include/core/SkTileMode.h"
15*c8dee2aaSAndroid Build Coastguard Worker #include "include/effects/SkGradientShader.h"
16*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkAssert.h"
17*c8dee2aaSAndroid Build Coastguard Worker #include "include/private/base/SkFloatingPoint.h"
18*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottiePriv.h"
19*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/SkottieValue.h"
20*c8dee2aaSAndroid Build Coastguard Worker #include "modules/skottie/src/effects/Effects.h"
21*c8dee2aaSAndroid Build Coastguard Worker #include "modules/sksg/include/SkSGRenderNode.h"
22*c8dee2aaSAndroid Build Coastguard Worker
23*c8dee2aaSAndroid Build Coastguard Worker #include <algorithm>
24*c8dee2aaSAndroid Build Coastguard Worker #include <array>
25*c8dee2aaSAndroid Build Coastguard Worker #include <cmath>
26*c8dee2aaSAndroid Build Coastguard Worker #include <cstddef>
27*c8dee2aaSAndroid Build Coastguard Worker #include <utility>
28*c8dee2aaSAndroid Build Coastguard Worker
29*c8dee2aaSAndroid Build Coastguard Worker namespace skjson {
30*c8dee2aaSAndroid Build Coastguard Worker class ArrayValue;
31*c8dee2aaSAndroid Build Coastguard Worker }
32*c8dee2aaSAndroid Build Coastguard Worker
33*c8dee2aaSAndroid Build Coastguard Worker namespace skottie {
34*c8dee2aaSAndroid Build Coastguard Worker namespace internal {
35*c8dee2aaSAndroid Build Coastguard Worker
36*c8dee2aaSAndroid Build Coastguard Worker namespace {
37*c8dee2aaSAndroid Build Coastguard Worker
38*c8dee2aaSAndroid Build Coastguard Worker class VenetianBlindsAdapter final : public MaskShaderEffectBase {
39*c8dee2aaSAndroid Build Coastguard Worker public:
Make(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const SkSize & layer_size,const AnimationBuilder * abuilder)40*c8dee2aaSAndroid Build Coastguard Worker static sk_sp<VenetianBlindsAdapter> Make(const skjson::ArrayValue& jprops,
41*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::RenderNode> layer,
42*c8dee2aaSAndroid Build Coastguard Worker const SkSize& layer_size,
43*c8dee2aaSAndroid Build Coastguard Worker const AnimationBuilder* abuilder) {
44*c8dee2aaSAndroid Build Coastguard Worker return sk_sp<VenetianBlindsAdapter>(
45*c8dee2aaSAndroid Build Coastguard Worker new VenetianBlindsAdapter(jprops, std::move(layer), layer_size, abuilder));
46*c8dee2aaSAndroid Build Coastguard Worker }
47*c8dee2aaSAndroid Build Coastguard Worker
48*c8dee2aaSAndroid Build Coastguard Worker private:
VenetianBlindsAdapter(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const SkSize & ls,const AnimationBuilder * abuilder)49*c8dee2aaSAndroid Build Coastguard Worker VenetianBlindsAdapter(const skjson::ArrayValue& jprops,
50*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::RenderNode> layer, const SkSize& ls,
51*c8dee2aaSAndroid Build Coastguard Worker const AnimationBuilder* abuilder)
52*c8dee2aaSAndroid Build Coastguard Worker : INHERITED(std::move(layer), ls) {
53*c8dee2aaSAndroid Build Coastguard Worker enum : size_t {
54*c8dee2aaSAndroid Build Coastguard Worker kCompletion_Index = 0,
55*c8dee2aaSAndroid Build Coastguard Worker kDirection_Index = 1,
56*c8dee2aaSAndroid Build Coastguard Worker kWidth_Index = 2,
57*c8dee2aaSAndroid Build Coastguard Worker kFeather_Index = 3,
58*c8dee2aaSAndroid Build Coastguard Worker };
59*c8dee2aaSAndroid Build Coastguard Worker
60*c8dee2aaSAndroid Build Coastguard Worker EffectBinder(jprops, *abuilder, this)
61*c8dee2aaSAndroid Build Coastguard Worker .bind(kCompletion_Index, fCompletion)
62*c8dee2aaSAndroid Build Coastguard Worker .bind( kDirection_Index, fDirection )
63*c8dee2aaSAndroid Build Coastguard Worker .bind( kWidth_Index, fWidth )
64*c8dee2aaSAndroid Build Coastguard Worker .bind( kFeather_Index, fFeather );
65*c8dee2aaSAndroid Build Coastguard Worker }
66*c8dee2aaSAndroid Build Coastguard Worker
onMakeMask() const67*c8dee2aaSAndroid Build Coastguard Worker MaskInfo onMakeMask() const override {
68*c8dee2aaSAndroid Build Coastguard Worker if (fCompletion >= 100) {
69*c8dee2aaSAndroid Build Coastguard Worker // The layer is fully disabled.
70*c8dee2aaSAndroid Build Coastguard Worker // TODO: fix layer controller visibility clash and pass a null shader instead.
71*c8dee2aaSAndroid Build Coastguard Worker return { SkShaders::Color(SK_ColorTRANSPARENT), false };
72*c8dee2aaSAndroid Build Coastguard Worker }
73*c8dee2aaSAndroid Build Coastguard Worker
74*c8dee2aaSAndroid Build Coastguard Worker if (fCompletion <= 0) {
75*c8dee2aaSAndroid Build Coastguard Worker // The layer is fully visible (no mask).
76*c8dee2aaSAndroid Build Coastguard Worker return { nullptr, true };
77*c8dee2aaSAndroid Build Coastguard Worker }
78*c8dee2aaSAndroid Build Coastguard Worker
79*c8dee2aaSAndroid Build Coastguard Worker static constexpr float kFeatherSigmaFactor = 3.0f,
80*c8dee2aaSAndroid Build Coastguard Worker kMinFeather = 0.5f; // for soft gradient edges
81*c8dee2aaSAndroid Build Coastguard Worker
82*c8dee2aaSAndroid Build Coastguard Worker const auto t = fCompletion * 0.01f,
83*c8dee2aaSAndroid Build Coastguard Worker size = std::max(1.0f, fWidth),
84*c8dee2aaSAndroid Build Coastguard Worker angle = SkDegreesToRadians(-fDirection),
85*c8dee2aaSAndroid Build Coastguard Worker feather = std::max(fFeather * kFeatherSigmaFactor, kMinFeather),
86*c8dee2aaSAndroid Build Coastguard Worker df = feather / size, // feather distance in normalized stop space
87*c8dee2aaSAndroid Build Coastguard Worker df0 = 0.5f * std::min(df, t),
88*c8dee2aaSAndroid Build Coastguard Worker df1 = 0.5f * std::min(df, 1 - t);
89*c8dee2aaSAndroid Build Coastguard Worker
90*c8dee2aaSAndroid Build Coastguard Worker // In its simplest form, the Venetian Blinds effect is a single-step gradient
91*c8dee2aaSAndroid Build Coastguard Worker // repeating along the direction vector.
92*c8dee2aaSAndroid Build Coastguard Worker //
93*c8dee2aaSAndroid Build Coastguard Worker // To avoid an expensive blur pass, we emulate the feather property by softening
94*c8dee2aaSAndroid Build Coastguard Worker // the gradient edges:
95*c8dee2aaSAndroid Build Coastguard Worker //
96*c8dee2aaSAndroid Build Coastguard Worker // 1.0 [ | ------- ]
97*c8dee2aaSAndroid Build Coastguard Worker // [ | / \ ]
98*c8dee2aaSAndroid Build Coastguard Worker // [ | / \ ]
99*c8dee2aaSAndroid Build Coastguard Worker // [ | / \ ]
100*c8dee2aaSAndroid Build Coastguard Worker // [ | / \ ]
101*c8dee2aaSAndroid Build Coastguard Worker // [ | / \ ]
102*c8dee2aaSAndroid Build Coastguard Worker // [ | / \ ]
103*c8dee2aaSAndroid Build Coastguard Worker // [ |/ \]
104*c8dee2aaSAndroid Build Coastguard Worker // 0.5 [ | ]
105*c8dee2aaSAndroid Build Coastguard Worker // [\ /| ]
106*c8dee2aaSAndroid Build Coastguard Worker // [ \ / | ]
107*c8dee2aaSAndroid Build Coastguard Worker // [ \ / | ]
108*c8dee2aaSAndroid Build Coastguard Worker // [ \ / | ]
109*c8dee2aaSAndroid Build Coastguard Worker // [ \ / | ]
110*c8dee2aaSAndroid Build Coastguard Worker // [ \ / | ]
111*c8dee2aaSAndroid Build Coastguard Worker // [ \ / | ]
112*c8dee2aaSAndroid Build Coastguard Worker // 0.0 [ ------------------- | ]
113*c8dee2aaSAndroid Build Coastguard Worker //
114*c8dee2aaSAndroid Build Coastguard Worker // ^ ^ ^ ^ ^ ^ ^
115*c8dee2aaSAndroid Build Coastguard Worker // 0 fp0 fp1 T fp2 fp3 1
116*c8dee2aaSAndroid Build Coastguard Worker //
117*c8dee2aaSAndroid Build Coastguard Worker // | | | | | | |
118*c8dee2aaSAndroid Build Coastguard Worker // |< df0 >| |< df0 >|< df1 >| |< df1 >|
119*c8dee2aaSAndroid Build Coastguard Worker //
120*c8dee2aaSAndroid Build Coastguard Worker // ... df >| |< df >| |< df ...
121*c8dee2aaSAndroid Build Coastguard Worker //
122*c8dee2aaSAndroid Build Coastguard Worker // Note 1: fp0-fp1 and/or fp2-fp3 can collapse when df is large enough.
123*c8dee2aaSAndroid Build Coastguard Worker //
124*c8dee2aaSAndroid Build Coastguard Worker // Note 2: G(fp0) == G(fp1) and G(fp2) == G(fp3), whether collapsed or not.
125*c8dee2aaSAndroid Build Coastguard Worker //
126*c8dee2aaSAndroid Build Coastguard Worker // Note 3: to minimize the number of gradient stops, we can shift the gradient by -df0
127*c8dee2aaSAndroid Build Coastguard Worker // (such that fp0 aligns with 0/pts[0]).
128*c8dee2aaSAndroid Build Coastguard Worker
129*c8dee2aaSAndroid Build Coastguard Worker // Gradient value at fp0/fp1, fp2/fp3.
130*c8dee2aaSAndroid Build Coastguard Worker // Note: g01 > 0 iff fp0-fp1 is collapsed and g23 < 1 iff fp2-fp3 is collapsed
131*c8dee2aaSAndroid Build Coastguard Worker const auto g01 = std::max(0.0f, 0.5f * (1 + sk_ieee_float_divide(0 - t, df))),
132*c8dee2aaSAndroid Build Coastguard Worker g23 = std::min(1.0f, 0.5f * (1 + sk_ieee_float_divide(1 - t, df)));
133*c8dee2aaSAndroid Build Coastguard Worker SkASSERT(0 <= g01 && g01 <= g23 && g23 <= 1);
134*c8dee2aaSAndroid Build Coastguard Worker
135*c8dee2aaSAndroid Build Coastguard Worker const SkColor c01 = SkColorSetA(SK_ColorWHITE, SkScalarRoundToInt(g01 * 0xff)),
136*c8dee2aaSAndroid Build Coastguard Worker c23 = SkColorSetA(SK_ColorWHITE, SkScalarRoundToInt(g23 * 0xff)),
137*c8dee2aaSAndroid Build Coastguard Worker colors[] = { c01, c23, c23, c01 };
138*c8dee2aaSAndroid Build Coastguard Worker
139*c8dee2aaSAndroid Build Coastguard Worker const SkScalar pos[] = {
140*c8dee2aaSAndroid Build Coastguard Worker // 0, // fp0
141*c8dee2aaSAndroid Build Coastguard Worker t - df0 - df0, // fp1
142*c8dee2aaSAndroid Build Coastguard Worker t + df1 - df0, // fp2
143*c8dee2aaSAndroid Build Coastguard Worker 1 - df1 - df0, // fp3
144*c8dee2aaSAndroid Build Coastguard Worker 1,
145*c8dee2aaSAndroid Build Coastguard Worker };
146*c8dee2aaSAndroid Build Coastguard Worker static_assert(std::size(colors) == std::size(pos), "");
147*c8dee2aaSAndroid Build Coastguard Worker
148*c8dee2aaSAndroid Build Coastguard Worker const auto center = SkPoint::Make(0.5f * this->layerSize().width(),
149*c8dee2aaSAndroid Build Coastguard Worker 0.5f * this->layerSize().height()),
150*c8dee2aaSAndroid Build Coastguard Worker grad_vec = SkVector::Make( size * std::cos(angle),
151*c8dee2aaSAndroid Build Coastguard Worker -size * std::sin(angle));
152*c8dee2aaSAndroid Build Coastguard Worker
153*c8dee2aaSAndroid Build Coastguard Worker const SkPoint pts[] = {
154*c8dee2aaSAndroid Build Coastguard Worker center + grad_vec * (df0 + 0),
155*c8dee2aaSAndroid Build Coastguard Worker center + grad_vec * (df0 + 1),
156*c8dee2aaSAndroid Build Coastguard Worker };
157*c8dee2aaSAndroid Build Coastguard Worker
158*c8dee2aaSAndroid Build Coastguard Worker return {
159*c8dee2aaSAndroid Build Coastguard Worker SkGradientShader::MakeLinear(pts, colors, pos, std::size(colors),
160*c8dee2aaSAndroid Build Coastguard Worker SkTileMode::kRepeat),
161*c8dee2aaSAndroid Build Coastguard Worker true
162*c8dee2aaSAndroid Build Coastguard Worker };
163*c8dee2aaSAndroid Build Coastguard Worker }
164*c8dee2aaSAndroid Build Coastguard Worker
165*c8dee2aaSAndroid Build Coastguard Worker ScalarValue fCompletion = 0,
166*c8dee2aaSAndroid Build Coastguard Worker fDirection = 0,
167*c8dee2aaSAndroid Build Coastguard Worker fWidth = 0,
168*c8dee2aaSAndroid Build Coastguard Worker fFeather = 0;
169*c8dee2aaSAndroid Build Coastguard Worker
170*c8dee2aaSAndroid Build Coastguard Worker using INHERITED = MaskShaderEffectBase;
171*c8dee2aaSAndroid Build Coastguard Worker };
172*c8dee2aaSAndroid Build Coastguard Worker
173*c8dee2aaSAndroid Build Coastguard Worker } // namespace
174*c8dee2aaSAndroid Build Coastguard Worker
attachVenetianBlindsEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const175*c8dee2aaSAndroid Build Coastguard Worker sk_sp<sksg::RenderNode> EffectBuilder::attachVenetianBlindsEffect(
176*c8dee2aaSAndroid Build Coastguard Worker const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
177*c8dee2aaSAndroid Build Coastguard Worker return fBuilder->attachDiscardableAdapter<VenetianBlindsAdapter>(jprops,
178*c8dee2aaSAndroid Build Coastguard Worker std::move(layer),
179*c8dee2aaSAndroid Build Coastguard Worker fLayerSize,
180*c8dee2aaSAndroid Build Coastguard Worker fBuilder);
181*c8dee2aaSAndroid Build Coastguard Worker }
182*c8dee2aaSAndroid Build Coastguard Worker
183*c8dee2aaSAndroid Build Coastguard Worker } // namespace internal
184*c8dee2aaSAndroid Build Coastguard Worker } // namespace skottie
185