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