• 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/SkRuntimeEffect.h"
11 #include "include/private/SkTPin.h"
12 #include "modules/skottie/src/SkottieJson.h"
13 #include "modules/skottie/src/SkottieValue.h"
14 #include "modules/sksg/include/SkSGColorFilter.h"
15 #include "src/utils/SkJSON.h"
16 
17 namespace skottie::internal {
18 
19 namespace  {
20 
21 #ifdef SK_ENABLE_SKSL
22 
23 // AE Saturation semantics:
24 //
25 //   - saturation is applied as a component-wise scale (interpolation/extrapolation)
26 //     relative to chroma mid point
27 //   - the scale factor is clamped such that none of the components over/under saturates
28 //     (e.g. below G/R and B are constrained to low_range and high_range, respectively)
29 //   - the scale is also clammped to a maximum value of 126, empirically
30 //   - the control is mapped linearly when desaturating, and non-linearly (1/1-S) when saturating
31 //
32 // 0               G    R                  B                                   1
33 // |---------------+----+------------------+-----------------------------------|
34 //                 |           |           |
35 //                min         mid         max
36 //                  <------- chroma ------>
37 //  <------- low_range -------> <---------------- high_range ----------------->
38 //
39 // With some care, we can stay in premul for these calculations.
40 static constexpr char gSaturateSkSL[] =
41 "uniform half u_scale;"
42 
43 "half4 main(half4 c) {"
44     // component min/max
45     "half2 rg_srt = (c.r < c.g) ? c.rg : c.gr;"
46     "half c_min = min(rg_srt.x, c.b),"
47          "c_max = max(rg_srt.y, c.b),"
48 
49     // chroma and mid-chroma (epsilon to avoid blowing up in the division below)
50     "ch     = max(c_max - c_min, 0.0001),"
51     "ch_mid = (c_min + c_max)*0.5,"
52 
53     // clamp scale to the maximum value which doesn't over/under saturate individual components
54     "scale_max = min(ch_mid, c.a - ch_mid)/ch*2,"
55     "scale = min(u_scale, scale_max);"
56 
57     // lerp
58     "c.rgb = ch_mid + (c.rgb - ch_mid)*scale;"
59 
60     "return c;"
61 "}";
62 
make_saturate(float chroma_scale)63 static sk_sp<SkColorFilter> make_saturate(float chroma_scale) {
64     static const auto* effect =
65             SkRuntimeEffect::MakeForColorFilter(SkString(gSaturateSkSL), {}).effect.release();
66     SkASSERT(effect);
67 
68     return effect->makeColorFilter(SkData::MakeWithCopy(&chroma_scale, sizeof(chroma_scale)));
69 }
70 
71 #else
72 static sk_sp<SkColorFilter> make_saturate(float) { return nullptr; }
73 #endif  // SK_ENABLE_SKSL
74 
75 class HueSaturationEffectAdapter final : public AnimatablePropertyContainer {
76 public:
Make(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const AnimationBuilder * abuilder)77     static sk_sp<HueSaturationEffectAdapter> Make(const skjson::ArrayValue& jprops,
78                                                   sk_sp<sksg::RenderNode> layer,
79                                                   const AnimationBuilder* abuilder) {
80 
81         return sk_sp<HueSaturationEffectAdapter>(
82                     new HueSaturationEffectAdapter(jprops, std::move(layer), abuilder));
83     }
84 
node() const85     const sk_sp<sksg::ExternalColorFilter>& node() const { return fColorFilter; }
86 
87 private:
HueSaturationEffectAdapter(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer,const AnimationBuilder * abuilder)88     HueSaturationEffectAdapter(const skjson::ArrayValue& jprops,
89                                sk_sp<sksg::RenderNode> layer,
90                                const AnimationBuilder* abuilder)
91         : fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer))) {
92         enum : size_t {
93                kChannelControl_Index = 0,
94                  kChannelRange_Index = 1,
95                     kMasterHue_Index = 2,
96                     kMasterSat_Index = 3,
97               kMasterLightness_Index = 4,
98                      kColorize_Index = 5,
99                   kColorizeHue_Index = 6,
100                   kColorizeSat_Index = 7,
101             kColorizeLightness_Index = 8,
102         };
103 
104         EffectBinder(jprops, *abuilder, this)
105                 .bind( kChannelControl_Index, fChanCtrl   )
106                 .bind(      kMasterHue_Index, fMasterHue  )
107                 .bind(      kMasterSat_Index, fMasterSat  )
108                 .bind(kMasterLightness_Index, fMasterLight);
109 
110         // TODO: colorize support?
111     }
112 
onSync()113     void onSync() override {
114         fColorFilter->setColorFilter(this->makeColorFilter());
115     }
116 
makeColorFilter() const117     sk_sp<SkColorFilter> makeColorFilter() const {
118         enum : uint8_t {
119             kMaster_Chan   = 0x01,
120             kReds_Chan     = 0x02,
121             kYellows_Chan  = 0x03,
122             kGreens_Chan   = 0x04,
123             kCyans_Chan    = 0x05,
124             kBlues_Chan    = 0x06,
125             kMagentas_Chan = 0x07,
126         };
127 
128         // We only support master channel controls at this point.
129         if (static_cast<int>(fChanCtrl) != kMaster_Chan) {
130             return nullptr;
131         }
132 
133         sk_sp<SkColorFilter> cf;
134 
135         if (!SkScalarNearlyZero(fMasterHue)) {
136             // Linear control mapping hue(degrees) -> hue offset]
137             const auto h = fMasterHue/360;
138 
139             const float cm[20] = {
140                 1, 0, 0, 0, h,
141                 0, 1, 0, 0, 0,
142                 0, 0, 1, 0, 0,
143                 0, 0, 0, 1, 0,
144             };
145 
146             cf = SkColorFilters::HSLAMatrix(cm);
147         }
148 
149         if (!SkScalarNearlyZero(fMasterSat)) {
150             // AE clamps the max chroma scale to this value.
151             static constexpr auto kMaxScale = 126.0f;
152 
153             // Control mapping:
154             //   * sat [-100 .. 0) -> scale [0 .. 1)   , linear
155             //   * sat  [0 .. 100] -> scale [1 .. max] , nonlinear: 100/(100 - sat)
156             const auto s            = SkTPin(fMasterSat/100, -1.0f, 1.0f),
157                        chroma_scale = s < 0 ? s + 1 : std::min(1/(1 - s), kMaxScale);
158 
159             cf = SkColorFilters::Compose(std::move(cf), make_saturate(chroma_scale));
160         }
161 
162         if (!SkScalarNearlyZero(fMasterLight)) {
163             // AE implements Lightness as a component-wise interpolation to 0 (for L < 0),
164             // or 1 (for L > 0).
165             //
166             // Control mapping:
167             //   * lightness [-100 .. 0) -> lerp[0 .. 1) from 0, linear
168             //   * lightness  [0 .. 100] -> lerp[1 .. 0] from 1, linear
169             const auto l  = SkTPin(fMasterLight/100, -1.0f, 1.0f),
170                        ls = 1 - std::abs(l),    // scale
171                        lo = l < 0 ? 0 : 1 - ls; // offset
172 
173             const float cm[20] = {
174                 ls,  0,  0, 0, lo,
175                  0, ls,  0, 0, lo,
176                  0,  0, ls, 0, lo,
177                  0,  0,  0, 1,  0,
178             };
179 
180             cf = SkColorFilters::Compose(std::move(cf), SkColorFilters::Matrix(cm));
181         }
182 
183         return cf;
184     }
185 
186     const sk_sp<sksg::ExternalColorFilter> fColorFilter;
187 
188     float fChanCtrl    = 0.0f,
189           fMasterHue   = 0.0f,
190           fMasterSat   = 0.0f,
191           fMasterLight = 0.0f;
192 };
193 
194 } // namespace
195 
attachHueSaturationEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const196 sk_sp<sksg::RenderNode> EffectBuilder::attachHueSaturationEffect(
197         const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
198     return fBuilder->attachDiscardableAdapter<HueSaturationEffectAdapter>(jprops,
199                                                                           std::move(layer),
200                                                                           fBuilder);
201 }
202 
203 } // namespace skottie::internal
204