1 /*
2  * Copyright 2020 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/base/SkTPin.h"
12 #include "modules/skottie/src/Adapter.h"
13 #include "modules/skottie/src/SkottieJson.h"
14 #include "modules/skottie/src/SkottieValue.h"
15 #include "modules/sksg/include/SkSGColorFilter.h"
16 
17 namespace skottie::internal {
18 
19 #ifdef SK_ENABLE_SKSL
20 
21 namespace  {
22 
23 // The B&W effect allows controlling individual luminance contribution of
24 // primary and secondary colors.
25 //
26 // The implementation relies on computing primary/secondary relative weights
27 // for the input color on the hue hexagon, and modulating based on weight
28 // coefficients.
29 //
30 // Note:
31 //   - at least one of (dr,dg,db) is 0
32 //   - at least two of (wr,wg,wb) and two of (wy,wc,wm) are 0
33 //  => we are effectively selecting the color hue sextant without explicit branching
34 //
35 // (inspired by https://github.com/RoyiAvital/StackExchangeCodes/blob/master/SignalProcessing/Q688/ApplyBlackWhiteFilter.m)
36 
make_effect()37 static sk_sp<SkRuntimeEffect> make_effect() {
38     static constexpr char BLACK_AND_WHITE_EFFECT[] =
39         "uniform half kR, kY, kG, kC, kB, kM;"
40 
41         "half4 main(half4 c) {"
42             "half m = min(min(c.r, c.g), c.b),"
43 
44                 "dr = c.r - m,"
45                 "dg = c.g - m,"
46                 "db = c.b - m,"
47 
48                 // secondaries weights
49                 "wy = min(dr,dg),"
50                 "wc = min(dg,db),"
51                 "wm = min(db,dr),"
52 
53                 // primaries weights
54                 "wr = dr - wy - wm,"
55                 "wg = dg - wy - wc,"
56                 "wb = db - wc - wm,"
57 
58                 // final luminance
59                 "l = m + kR*wr + kY*wy + kG*wg + kC*wc + kB*wb + kM*wm;"
60 
61             "return half4(l, l, l, c.a);"
62         "}"
63     ;
64 
65     static const SkRuntimeEffect* effect =
66             SkRuntimeEffect::MakeForColorFilter(SkString(BLACK_AND_WHITE_EFFECT)).effect.release();
67     SkASSERT(effect);
68 
69     return sk_ref_sp(effect);
70 }
71 
72 class BlackAndWhiteAdapter final : public DiscardableAdapterBase<BlackAndWhiteAdapter,
73                                                                  sksg::ExternalColorFilter> {
74 public:
BlackAndWhiteAdapter(const skjson::ArrayValue & jprops,const AnimationBuilder & abuilder,sk_sp<sksg::RenderNode> layer)75     BlackAndWhiteAdapter(const skjson::ArrayValue& jprops,
76                          const AnimationBuilder& abuilder,
77                          sk_sp<sksg::RenderNode> layer)
78         : INHERITED(sksg::ExternalColorFilter::Make(std::move(layer)))
79         , fEffect(make_effect())
80     {
81         SkASSERT(fEffect);
82 
83         enum : size_t {
84                 kReds_Index = 0,
85              kYellows_Index = 1,
86               kGreens_Index = 2,
87                kCyans_Index = 3,
88                kBlues_Index = 4,
89             kMagentas_Index = 5,
90             // TODO
91             //    kTint_Index = 6,
92             // kTintColorIndex = 7,
93         };
94 
95         EffectBinder(jprops, abuilder, this)
96             .bind(    kReds_Index, fCoeffs[0])
97             .bind( kYellows_Index, fCoeffs[1])
98             .bind(  kGreens_Index, fCoeffs[2])
99             .bind(   kCyans_Index, fCoeffs[3])
100             .bind(   kBlues_Index, fCoeffs[4])
101             .bind(kMagentas_Index, fCoeffs[5]);
102     }
103 
104 private:
onSync()105     void onSync() override {
106         struct {
107             float normalized_coeffs[6];
108         } coeffs = {
109             (fCoeffs[0] ) / 100,
110             (fCoeffs[1] ) / 100,
111             (fCoeffs[2] ) / 100,
112             (fCoeffs[3] ) / 100,
113             (fCoeffs[4] ) / 100,
114             (fCoeffs[5] ) / 100,
115         };
116 
117         this->node()->setColorFilter(
118                 fEffect->makeColorFilter(SkData::MakeWithCopy(&coeffs, sizeof(coeffs))));
119     }
120 
121     const sk_sp<SkRuntimeEffect> fEffect;
122 
123     ScalarValue                  fCoeffs[6];
124 
125     using INHERITED = DiscardableAdapterBase<BlackAndWhiteAdapter, sksg::ExternalColorFilter>;
126 };
127 
128 } // namespace
129 
130 #endif  // SK_ENABLE_SKSL
131 
attachBlackAndWhiteEffect(const skjson::ArrayValue & jprops,sk_sp<sksg::RenderNode> layer) const132 sk_sp<sksg::RenderNode> EffectBuilder::attachBlackAndWhiteEffect(
133         const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const {
134 #ifdef SK_ENABLE_SKSL
135     return fBuilder->attachDiscardableAdapter<BlackAndWhiteAdapter>(jprops,
136                                                                     *fBuilder,
137                                                                     std::move(layer));
138 #else
139     // TODO(skia:12197)
140     return layer;
141 #endif
142 }
143 
144 } // namespace skottie::internal
145