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/SkTableColorFilter.h" |
11 | #include "modules/skottie/src/Adapter.h" |
12 | #include "modules/skottie/src/SkottieValue.h" |
13 | #include "modules/sksg/include/SkSGColorFilter.h" |
14 | #include "src/utils/SkJSON.h" |
15 | |
16 | #include <array> |
17 | #include <cmath> |
18 | |
19 | namespace skottie { |
20 | namespace internal { |
21 | |
22 | namespace { |
23 | |
24 | struct ClipInfo { |
25 | ScalarValue fClipBlack = 1, // 1: clip, 2/3: don't clip |
26 | fClipWhite = 1; // ^ |
27 | }; |
28 | |
29 | struct ChannelMapper { |
30 | ScalarValue fInBlack = 0, |
31 | fInWhite = 1, |
32 | fOutBlack = 0, |
33 | fOutWhite = 1, |
34 | fGamma = 1; |
35 | |
36 | const uint8_t* build_lut(std::array<uint8_t, 256>& lut_storage, |
37 | const ClipInfo& clip_info) const { |
38 | auto in_0 = fInBlack, |
39 | in_1 = fInWhite, |
40 | out_0 = fOutBlack, |
41 | out_1 = fOutWhite, |
42 | g = sk_ieee_float_divide(1, std::max(fGamma, 0.0f)); |
43 | |
44 | float clip[] = {0, 1}; |
45 | const auto kLottieDoClip = 1; |
46 | if (SkScalarTruncToInt(clip_info.fClipBlack) == kLottieDoClip) { |
47 | const auto idx = fOutBlack <= fOutWhite ? 0 : 1; |
48 | clip[idx] = SkTPin(out_0, 0.0f, 1.0f); |
49 | } |
50 | if (SkScalarTruncToInt(clip_info.fClipWhite) == kLottieDoClip) { |
51 | const auto idx = fOutBlack <= fOutWhite ? 1 : 0; |
52 | clip[idx] = SkTPin(out_1, 0.0f, 1.0f); |
53 | } |
54 | SkASSERT(clip[0] <= clip[1]); |
55 | |
56 | if (SkScalarNearlyEqual(in_0, out_0) && |
57 | SkScalarNearlyEqual(in_1, out_1) && |
58 | SkScalarNearlyEqual(g, 1)) { |
59 | // no-op |
60 | return nullptr; |
61 | } |
62 | |
63 | auto dIn = in_1 - in_0, |
64 | dOut = out_1 - out_0; |
65 | |
66 | if (SkScalarNearlyZero(dIn)) { |
67 | // Degenerate dIn == 0 makes the arithmetic below explode. |
68 | // |
69 | // We could specialize the builder to deal with that case, or we could just |
70 | // nudge by epsilon to make it all work. The latter approach is simpler |
71 | // and doesn't have any noticeable downsides. |
72 | // |
73 | // Also nudge in_0 towards 0.5, in case it was sqashed against an extremity. |
74 | // This allows for some abrupt transition when the output interval is not |
75 | // collapsed, and produces results closer to AE. |
76 | static constexpr auto kEpsilon = 2 * SK_ScalarNearlyZero; |
77 | dIn += std::copysign(kEpsilon, dIn); |
78 | in_0 += std::copysign(kEpsilon, .5f - in_0); |
79 | SkASSERT(!SkScalarNearlyZero(dIn)); |
80 | } |
81 | |
82 | auto t = -in_0 / dIn, |
83 | dT = 1 / 255.0f / dIn; |
84 | |
85 | for (size_t i = 0; i < 256; ++i) { |
86 | const auto out = out_0 + dOut * std::pow(std::max(t, 0.0f), g); |
87 | SkASSERT(!SkScalarIsNaN(out)); |
88 | |
89 | lut_storage[i] = static_cast<uint8_t>(std::round(SkTPin(out, clip[0], clip[1]) * 255)); |
90 | |
91 | t += dT; |
92 | } |
93 | |
94 | return lut_storage.data(); |
95 | } |
96 | }; |
97 | |
98 | // ADBE Easy Levels2 color correction effect. |
99 | // |
100 | // Maps the selected channel(s) from [inBlack...inWhite] to [outBlack, outWhite], |
101 | // based on a gamma exponent. |
102 | // |
103 | // For [i0..i1] -> [o0..o1]: |
104 | // |
105 | // c' = o0 + (o1 - o0) * ((c - i0) / (i1 - i0)) ^ G |
106 | // |
107 | // The output is optionally clipped to the output range. |
108 | // |
109 | // In/out intervals are clampped to [0..1]. Inversion is allowed. |
110 | |
111 | class EasyLevelsEffectAdapter final : public DiscardableAdapterBase<EasyLevelsEffectAdapter, |
112 | sksg::ExternalColorFilter> { |
113 | public: |
114 | EasyLevelsEffectAdapter(const skjson::ArrayValue& jprops, |
115 | sk_sp<sksg::RenderNode> layer, |
116 | const AnimationBuilder* abuilder) |
117 | : INHERITED(sksg::ExternalColorFilter::Make(std::move(layer))) { |
118 | enum : size_t { |
119 | kChannel_Index = 0, |
120 | // kHist_Index = 1, |
121 | kInBlack_Index = 2, |
122 | kInWhite_Index = 3, |
123 | kGamma_Index = 4, |
124 | kOutBlack_Index = 5, |
125 | kOutWhite_Index = 6, |
126 | kClipToOutBlack_Index = 7, |
127 | kClipToOutWhite_Index = 8, |
128 | }; |
129 | |
130 | EffectBinder(jprops, *abuilder, this) |
131 | .bind( kChannel_Index, fChannel ) |
132 | .bind( kInBlack_Index, fMapper.fInBlack ) |
133 | .bind( kInWhite_Index, fMapper.fInWhite ) |
134 | .bind( kGamma_Index, fMapper.fGamma ) |
135 | .bind( kOutBlack_Index, fMapper.fOutBlack) |
136 | .bind( kOutWhite_Index, fMapper.fOutWhite) |
137 | .bind(kClipToOutBlack_Index, fClip.fClipBlack ) |
138 | .bind(kClipToOutWhite_Index, fClip.fClipWhite ); |
139 | } |
140 | |
141 | private: |
142 | void onSync() override { |
143 | enum LottieChannel { |
144 | kRGB_Channel = 1, |
145 | kR_Channel = 2, |
146 | kG_Channel = 3, |
147 | kB_Channel = 4, |
148 | kA_Channel = 5, |
149 | }; |
150 | |
151 | const auto channel = SkScalarTruncToInt(fChannel); |
152 | std::array<uint8_t, 256> lut; |
153 | if (channel < kRGB_Channel || channel > kA_Channel || !fMapper.build_lut(lut, fClip)) { |
154 | this->node()->setColorFilter(nullptr); |
155 | return; |
156 | } |
157 | |
158 | this->node()->setColorFilter(SkTableColorFilter::MakeARGB( |
159 | channel == kA_Channel ? lut.data() : nullptr, |
160 | channel == kR_Channel || channel == kRGB_Channel ? lut.data() : nullptr, |
161 | channel == kG_Channel || channel == kRGB_Channel ? lut.data() : nullptr, |
162 | channel == kB_Channel || channel == kRGB_Channel ? lut.data() : nullptr |
163 | )); |
164 | } |
165 | |
166 | ChannelMapper fMapper; |
167 | ClipInfo fClip; |
168 | ScalarValue fChannel = 1; // 1: RGB, 2: R, 3: G, 4: B, 5: A |
169 | |
170 | using INHERITED = DiscardableAdapterBase<EasyLevelsEffectAdapter, sksg::ExternalColorFilter>; |
171 | }; |
172 | |
173 | // ADBE Pro Levels2 color correction effect. |
174 | // |
175 | // Similar to ADBE Easy Levels2, but offers separate controls for each channel. |
176 | |
177 | class ProLevelsEffectAdapter final : public DiscardableAdapterBase<ProLevelsEffectAdapter, |
178 | sksg::ExternalColorFilter> { |
179 | public: |
180 | ProLevelsEffectAdapter(const skjson::ArrayValue& jprops, |
181 | sk_sp<sksg::RenderNode> layer, |
182 | const AnimationBuilder* abuilder) |
183 | : INHERITED(sksg::ExternalColorFilter::Make(std::move(layer))) { |
184 | enum : size_t { |
185 | // kHistChan_Index = 0, |
186 | // kHist_Index = 1, |
187 | // kRGBBegin_Index = 2, |
188 | kRGBInBlack_Index = 3, |
189 | kRGBInWhite_Index = 4, |
190 | kRGBGamma_Index = 5, |
191 | kRGBOutBlack_Index = 6, |
192 | kRGBOutWhite_Index = 7, |
193 | // kRGBEnd_Index = 8, |
194 | // kRBegin_Index = 9, |
195 | kRInBlack_Index = 10, |
196 | kRInWhite_Index = 11, |
197 | kRGamma_Index = 12, |
198 | kROutBlack_Index = 13, |
199 | kROutWhite_Index = 14, |
200 | // kREnd_Index = 15, |
201 | // kGBegin_Index = 16, |
202 | kGInBlack_Index = 17, |
203 | kGInWhite_Index = 18, |
204 | kGGamma_Index = 19, |
205 | kGOutBlack_Index = 20, |
206 | kGOutWhite_Index = 21, |
207 | // kGEnd_Index = 22, |
208 | // kBBegin_Index = 23, |
209 | kBInBlack_Index = 24, |
210 | kBInWhite_Index = 25, |
211 | kBGamma_Index = 26, |
212 | kBOutBlack_Index = 27, |
213 | kBOutWhite_Index = 28, |
214 | // kBEnd_Index = 29, |
215 | // kABegin_Index = 30, |
216 | kAInBlack_Index = 31, |
217 | kAInWhite_Index = 32, |
218 | kAGamma_Index = 33, |
219 | kAOutBlack_Index = 34, |
220 | kAOutWhite_Index = 35, |
221 | // kAEnd_Index = 36, |
222 | kClipToOutBlack_Index = 37, |
223 | kClipToOutWhite_Index = 38, |
224 | }; |
225 | |
226 | EffectBinder(jprops, *abuilder, this) |
227 | .bind( kRGBInBlack_Index, fRGBMapper.fInBlack ) |
228 | .bind( kRGBInWhite_Index, fRGBMapper.fInWhite ) |
229 | .bind( kRGBGamma_Index, fRGBMapper.fGamma ) |
230 | .bind(kRGBOutBlack_Index, fRGBMapper.fOutBlack) |
231 | .bind(kRGBOutWhite_Index, fRGBMapper.fOutWhite) |
232 | |
233 | .bind( kRInBlack_Index, fRMapper.fInBlack ) |
234 | .bind( kRInWhite_Index, fRMapper.fInWhite ) |
235 | .bind( kRGamma_Index, fRMapper.fGamma ) |
236 | .bind(kROutBlack_Index, fRMapper.fOutBlack) |
237 | .bind(kROutWhite_Index, fRMapper.fOutWhite) |
238 | |
239 | .bind( kGInBlack_Index, fGMapper.fInBlack ) |
240 | .bind( kGInWhite_Index, fGMapper.fInWhite ) |
241 | .bind( kGGamma_Index, fGMapper.fGamma ) |
242 | .bind(kGOutBlack_Index, fGMapper.fOutBlack) |
243 | .bind(kGOutWhite_Index, fGMapper.fOutWhite) |
244 | |
245 | .bind( kBInBlack_Index, fBMapper.fInBlack ) |
246 | .bind( kBInWhite_Index, fBMapper.fInWhite ) |
247 | .bind( kBGamma_Index, fBMapper.fGamma ) |
248 | .bind(kBOutBlack_Index, fBMapper.fOutBlack) |
249 | .bind(kBOutWhite_Index, fBMapper.fOutWhite) |
250 | |
251 | .bind( kAInBlack_Index, fAMapper.fInBlack ) |
252 | .bind( kAInWhite_Index, fAMapper.fInWhite ) |
253 | .bind( kAGamma_Index, fAMapper.fGamma ) |
254 | .bind(kAOutBlack_Index, fAMapper.fOutBlack) |
255 | .bind(kAOutWhite_Index, fAMapper.fOutWhite); |
256 | } |
257 | |
258 | private: |
259 | void onSync() override { |
260 | std::array<uint8_t, 256> a_lut_storage, |
261 | r_lut_storage, |
262 | g_lut_storage, |
263 | b_lut_storage; |
264 | |
265 | auto cf = SkTableColorFilter::MakeARGB(fAMapper.build_lut(a_lut_storage, fClip), |
266 | fRMapper.build_lut(r_lut_storage, fClip), |
267 | fGMapper.build_lut(g_lut_storage, fClip), |
268 | fBMapper.build_lut(b_lut_storage, fClip)); |
269 | |
270 | // The RGB mapper composes outside individual channel mappers. |
271 | if (const auto* rgb_lut = fRGBMapper.build_lut(a_lut_storage, fClip)) { |
272 | cf = SkColorFilters::Compose(SkTableColorFilter::MakeARGB(nullptr, |
273 | rgb_lut, |
274 | rgb_lut, |
275 | rgb_lut), |
276 | std::move(cf)); |
277 | } |
278 | |
279 | this->node()->setColorFilter(std::move(cf)); |
280 | } |
281 | |
282 | ChannelMapper fRGBMapper, |
283 | fRMapper, |
284 | fGMapper, |
285 | fBMapper, |
286 | fAMapper; |
287 | |
288 | ClipInfo fClip; |
289 | |
290 | using INHERITED = DiscardableAdapterBase<ProLevelsEffectAdapter, sksg::ExternalColorFilter>; |
291 | }; |
292 | |
293 | } // anonymous ns |
294 | |
295 | sk_sp<sksg::RenderNode> EffectBuilder::attachEasyLevelsEffect(const skjson::ArrayValue& jprops, |
296 | sk_sp<sksg::RenderNode> layer) const { |
297 | return fBuilder->attachDiscardableAdapter<EasyLevelsEffectAdapter>(jprops, |
298 | std::move(layer), |
299 | fBuilder); |
300 | } |
301 | |
302 | sk_sp<sksg::RenderNode> EffectBuilder::attachProLevelsEffect(const skjson::ArrayValue& jprops, |
303 | sk_sp<sksg::RenderNode> layer) const { |
304 | return fBuilder->attachDiscardableAdapter<ProLevelsEffectAdapter>(jprops, |
305 | std::move(layer), |
306 | fBuilder); |
307 | } |
308 | |
309 | } // namespace internal |
310 | } // namespace skottie |
311 | |