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