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