Flutter Engine
The Flutter Engine
LevelsEffect.cpp
Go to the documentation of this file.
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
20
21#include <algorithm>
22#include <array>
23#include <cmath>
24#include <cstddef>
25#include <cstdint>
26#include <utility>
27
28namespace skjson {
29class ArrayValue;
30}
31
32namespace skottie {
33namespace internal {
34
35namespace {
36
37struct ClipInfo {
38 ScalarValue fClipBlack = 1, // 1: clip, 2/3: don't clip
39 fClipWhite = 1; // ^
40};
41
42struct ChannelMapper {
47 fGamma = 1;
48
49 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,
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);
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);
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
124class EasyLevelsEffectAdapter final : public DiscardableAdapterBase<EasyLevelsEffectAdapter,
125 sksg::ExternalColorFilter> {
126public:
127 EasyLevelsEffectAdapter(const skjson::ArrayValue& jprops,
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
154private:
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
190class ProLevelsEffectAdapter final : public DiscardableAdapterBase<ProLevelsEffectAdapter,
191 sksg::ExternalColorFilter> {
192public:
193 ProLevelsEffectAdapter(const skjson::ArrayValue& jprops,
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
271private:
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)) {
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
308sk_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
315sk_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
static void round(SkPoint *p)
ScalarValue fOutWhite
ScalarValue fClipBlack
ScalarValue fInBlack
ScalarValue fGamma
ScalarValue fClipWhite
ScalarValue fOutBlack
ScalarValue fInWhite
#define SkASSERT(cond)
Definition: SkAssert.h:116
static constexpr bool SkIsNaN(T x)
static constexpr float sk_ieee_float_divide(float numer, float denom)
static constexpr double kEpsilon
static SkPath clip(const SkPath &path, const SkHalfPlane &plane)
Definition: SkPath.cpp:3892
#define INHERITED(method,...)
Definition: SkRecorder.cpp:128
static bool SkScalarNearlyZero(SkScalar x, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:101
#define SkScalarTruncToInt(x)
Definition: SkScalar.h:59
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition: SkScalar.h:107
#define SK_ScalarNearlyZero
Definition: SkScalar.h:99
static constexpr const T & SkTPin(const T &x, const T &lo, const T &hi)
Definition: SkTPin.h:19
static sk_sp< SkColorFilter > Compose(const sk_sp< SkColorFilter > &outer, sk_sp< SkColorFilter > inner)
Definition: SkColorFilter.h:92
static sk_sp< SkColorFilter > TableARGB(const uint8_t tableA[256], const uint8_t tableR[256], const uint8_t tableG[256], const uint8_t tableB[256])
void attachDiscardableAdapter(sk_sp< T > adapter) const
Definition: SkottiePriv.h:139
static float max(float r, float g, float b)
Definition: hsl.cpp:49
SK_API sk_sp< SkDocument > Make(SkWStream *dst, const SkSerialProcs *=nullptr, std::function< void(const SkPicture *)> onEndPage=nullptr)
SkScalar ScalarValue
Definition: SkottieValue.h:22
Definition: Skottie.h:32
Definition: ref_ptr.h:256