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 | return { nullptr, false }; |
56 | } |
57 | |
58 | if (fCompletion <= 0) { |
59 | // The layer is fully visible (no mask). |
60 | return { nullptr, true }; |
61 | } |
62 | |
63 | static constexpr float kFeatherSigmaFactor = 3.0f, |
64 | kMinFeather = 0.5f; // for soft gradient edges |
65 | |
66 | const auto t = fCompletion * 0.01f, |
67 | size = std::max(1.0f, fWidth), |
68 | angle = SkDegreesToRadians(-fDirection), |
69 | feather = std::max(fFeather * kFeatherSigmaFactor, kMinFeather), |
70 | df = feather / size, // feather distance in normalized stop space |
71 | df0 = 0.5f * std::min(df, t), |
72 | df1 = 0.5f * std::min(df, 1 - t); |
73 | |
74 | // In its simplest form, the Venetian Blinds effect is a single-step gradient |
75 | // repeating along the direction vector. |
76 | // |
77 | // To avoid an expensive blur pass, we emulate the feather property by softening |
78 | // the gradient edges: |
79 | // |
80 | // 1.0 [ | ------- ] |
81 | // [ | / \ ] |
82 | // [ | / \ ] |
83 | // [ | / \ ] |
84 | // [ | / \ ] |
85 | // [ | / \ ] |
86 | // [ | / \ ] |
87 | // [ |/ \] |
88 | // 0.5 [ | ] |
89 | // [\ /| ] |
90 | // [ \ / | ] |
91 | // [ \ / | ] |
92 | // [ \ / | ] |
93 | // [ \ / | ] |
94 | // [ \ / | ] |
95 | // [ \ / | ] |
96 | // 0.0 [ ------------------- | ] |
97 | // |
98 | // ^ ^ ^ ^ ^ ^ ^ |
99 | // 0 fp0 fp1 T fp2 fp3 1 |
100 | // |
101 | // | | | | | | | |
102 | // |< df0 >| |< df0 >|< df1 >| |< df1 >| |
103 | // |
104 | // ... df >| |< df >| |< df ... |
105 | // |
106 | // Note 1: fp0-fp1 and/or fp2-fp3 can collapse when df is large enough. |
107 | // |
108 | // Note 2: G(fp0) == G(fp1) and G(fp2) == G(fp3), whether collapsed or not. |
109 | // |
110 | // Note 3: to minimize the number of gradient stops, we can shift the gradient by -df0 |
111 | // (such that fp0 aligns with 0/pts[0]). |
112 | |
113 | // Gradient value at fp0/fp1, fp2/fp3. |
114 | // Note: g01 > 0 iff fp0-fp1 is collapsed and g23 < 1 iff fp2-fp3 is collapsed |
115 | const auto g01 = std::max(0.0f, 0.5f * (1 + (0 - t) / df)), |
116 | g23 = std::min(1.0f, 0.5f * (1 + (1 - t) / df)); |
117 | |
118 | const SkColor c01 = SkColorSetA(SK_ColorWHITE, SkScalarRoundToInt(g01 * 0xff)), |
119 | c23 = SkColorSetA(SK_ColorWHITE, SkScalarRoundToInt(g23 * 0xff)), |
120 | colors[] = { c01, c23, c23, c01 }; |
121 | |
122 | const SkScalar pos[] = { |
123 | // 0, // fp0 |
124 | t - df0 - df0, // fp1 |
125 | t + df1 - df0, // fp2 |
126 | 1 - df1 - df0, // fp3 |
127 | 1, |
128 | }; |
129 | static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(pos), "" ); |
130 | |
131 | const auto center = SkPoint::Make(0.5f * this->layerSize().width(), |
132 | 0.5f * this->layerSize().height()), |
133 | grad_vec = SkVector::Make( size * std::cos(angle), |
134 | -size * std::sin(angle)); |
135 | |
136 | const SkPoint pts[] = { |
137 | center + grad_vec * (df0 + 0), |
138 | center + grad_vec * (df0 + 1), |
139 | }; |
140 | |
141 | return { |
142 | SkGradientShader::MakeLinear(pts, colors, pos, SK_ARRAY_COUNT(colors), |
143 | SkTileMode::kRepeat), |
144 | true |
145 | }; |
146 | } |
147 | |
148 | ScalarValue fCompletion = 0, |
149 | fDirection = 0, |
150 | fWidth = 0, |
151 | fFeather = 0; |
152 | |
153 | using INHERITED = MaskShaderEffectBase; |
154 | }; |
155 | |
156 | } // namespace |
157 | |
158 | sk_sp<sksg::RenderNode> EffectBuilder::attachVenetianBlindsEffect( |
159 | const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const { |
160 | return fBuilder->attachDiscardableAdapter<VenetianBlindsAdapter>(jprops, |
161 | std::move(layer), |
162 | fLayerSize, |
163 | fBuilder); |
164 | } |
165 | |
166 | } // namespace internal |
167 | } // namespace skottie |
168 | |