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