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