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