• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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/private/base/SkTPin.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 
build_lutskottie::internal::__anon9d91e0560111::ChannelMapper36     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:
EasyLevelsEffectAdapter(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const AnimationBuilder * abuilder)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:
onSync()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(SkColorFilters::TableARGB(
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:
ProLevelsEffectAdapter(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const AnimationBuilder * abuilder)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:
onSync()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 = SkColorFilters::TableARGB(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(SkColorFilters::TableARGB(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 }  // namespace
294 
attachEasyLevelsEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const295 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 
attachProLevelsEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const302 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