| /* |
| * Copyright 2020 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| |
| #include "modules/skottie/src/effects/Effects.h" |
| |
| #include "include/effects/SkRuntimeEffect.h" |
| #include "include/private/SkTPin.h" |
| #include "modules/skottie/src/Adapter.h" |
| #include "modules/skottie/src/SkottieJson.h" |
| #include "modules/skottie/src/SkottieValue.h" |
| #include "modules/sksg/include/SkSGColorFilter.h" |
| |
| namespace skottie::internal { |
| |
| namespace { |
| |
| // The B&W effect allows controlling individual luminance contribution of |
| // primary and secondary colors. |
| // |
| // The implementation relies on computing primary/secondary relative weights |
| // for the input color on the hue hexagon, and modulating based on weight |
| // coefficients. |
| // |
| // Note: |
| // - at least one of (dr,dg,db) is 0 |
| // - at least two of (wr,wg,wb) and two of (wy,wc,wm) are 0 |
| // => we are effectively selecting the color hue sextant without explicit branching |
| // |
| // (inspired by https://github.com/RoyiAvital/StackExchangeCodes/blob/master/SignalProcessing/Q688/ApplyBlackWhiteFilter.m) |
| |
| static sk_sp<SkRuntimeEffect> make_effect() { |
| static constexpr char BLACK_AND_WHITE_EFFECT[] = R"( |
| uniform shader input; |
| uniform half kR, kY, kG, kC, kB, kM; |
| |
| half4 main() { |
| half4 c = sample(input); |
| |
| half m = min(min(c.r, c.g), c.b), |
| |
| dr = c.r - m, |
| dg = c.g - m, |
| db = c.b - m, |
| |
| // secondaries weights |
| wy = min(dr,dg), |
| wc = min(dg,db), |
| wm = min(db,dr), |
| |
| // primaries weights |
| wr = dr - wy - wm, |
| wg = dg - wy - wc, |
| wb = db - wc - wm, |
| |
| // final luminance |
| l = m + kR*wr + kY*wy + kG*wg + kC*wc + kB*wb + kM*wm; |
| |
| return half4(l, l, l, c.a); |
| } |
| )"; |
| |
| static const SkRuntimeEffect* effect = |
| std::get<0>(SkRuntimeEffect::Make(SkString(BLACK_AND_WHITE_EFFECT))).release(); |
| SkASSERT(effect); |
| |
| return sk_ref_sp(effect); |
| } |
| |
| class BlackAndWhiteAdapter final : public DiscardableAdapterBase<BlackAndWhiteAdapter, |
| sksg::ExternalColorFilter> { |
| public: |
| BlackAndWhiteAdapter(const skjson::ArrayValue& jprops, |
| const AnimationBuilder& abuilder, |
| sk_sp<sksg::RenderNode> layer) |
| : INHERITED(sksg::ExternalColorFilter::Make(std::move(layer))) |
| , fEffect(make_effect()) |
| { |
| SkASSERT(fEffect); |
| |
| enum : size_t { |
| kReds_Index = 0, |
| kYellows_Index = 1, |
| kGreens_Index = 2, |
| kCyans_Index = 3, |
| kBlues_Index = 4, |
| kMagentas_Index = 5, |
| // TODO |
| // kTint_Index = 6, |
| // kTintColorIndex = 7, |
| }; |
| |
| EffectBinder(jprops, abuilder, this) |
| .bind( kReds_Index, fCoeffs[0]) |
| .bind( kYellows_Index, fCoeffs[1]) |
| .bind( kGreens_Index, fCoeffs[2]) |
| .bind( kCyans_Index, fCoeffs[3]) |
| .bind( kBlues_Index, fCoeffs[4]) |
| .bind(kMagentas_Index, fCoeffs[5]); |
| } |
| |
| private: |
| void onSync() override { |
| struct { |
| float normalized_coeffs[6]; |
| } coeffs = { |
| (fCoeffs[0] ) / 100, |
| (fCoeffs[1] ) / 100, |
| (fCoeffs[2] ) / 100, |
| (fCoeffs[3] ) / 100, |
| (fCoeffs[4] ) / 100, |
| (fCoeffs[5] ) / 100, |
| }; |
| |
| sk_sp<SkColorFilter> input; |
| |
| this->node()->setColorFilter( |
| fEffect->makeColorFilter(SkData::MakeWithCopy(&coeffs, sizeof(coeffs)), &input, 1)); |
| } |
| |
| const sk_sp<SkRuntimeEffect> fEffect; |
| |
| ScalarValue fCoeffs[6]; |
| |
| using INHERITED = DiscardableAdapterBase<BlackAndWhiteAdapter, sksg::ExternalColorFilter>; |
| }; |
| |
| } // namespace |
| |
| |
| sk_sp<sksg::RenderNode> EffectBuilder::attachBlackAndWhiteEffect( |
| const skjson::ArrayValue& jprops, sk_sp<sksg::RenderNode> layer) const { |
| return fBuilder->attachDiscardableAdapter<BlackAndWhiteAdapter>(jprops, |
| *fBuilder, |
| std::move(layer)); |
| } |
| |
| } // namespace skottie::internal |