| 1 | /* |
| 2 | * Copyright 2019 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 "modules/skottie/src/SkottieValue.h" |
| 11 | #include "modules/sksg/include/SkSGColorFilter.h" |
| 12 | #include "src/utils/SkJSON.h" |
| 13 | |
| 14 | namespace skottie { |
| 15 | namespace internal { |
| 16 | |
| 17 | namespace { |
| 18 | |
| 19 | class InvertEffectAdapter final : public AnimatablePropertyContainer { |
| 20 | public: |
| 21 | static sk_sp<InvertEffectAdapter> Make(const skjson::ArrayValue& jprops, |
| 22 | sk_sp<sksg::RenderNode> layer, |
| 23 | const AnimationBuilder* abuilder) { |
| 24 | return sk_sp<InvertEffectAdapter>( |
| 25 | new InvertEffectAdapter(jprops, std::move(layer), abuilder)); |
| 26 | } |
| 27 | |
| 28 | const sk_sp<sksg::ExternalColorFilter>& node() const { return fColorFilter; } |
| 29 | |
| 30 | private: |
| 31 | InvertEffectAdapter(const skjson::ArrayValue& jprops, |
| 32 | sk_sp<sksg::RenderNode> layer, |
| 33 | const AnimationBuilder* abuilder) |
| 34 | : fColorFilter(sksg::ExternalColorFilter::Make(std::move(layer))) { |
| 35 | enum : size_t { |
| 36 | kChannel_Index = 0, |
| 37 | }; |
| 38 | |
| 39 | EffectBinder(jprops, *abuilder, this).bind(kChannel_Index, fChannel); |
| 40 | } |
| 41 | |
| 42 | void onSync() override { |
| 43 | struct STColorMatrix { |
| 44 | std::array<float,4> scale, |
| 45 | trans; |
| 46 | bool hsla; |
| 47 | }; |
| 48 | |
| 49 | const auto stcm = [this]() -> STColorMatrix { |
| 50 | // https://helpx.adobe.com/after-effects/using/channel-effects.html#invert_effect |
| 51 | enum : uint8_t { |
| 52 | kRGB_Channel = 1, |
| 53 | kR_Channel = 2, |
| 54 | kG_Channel = 3, |
| 55 | kB_Channel = 4, |
| 56 | |
| 57 | // NB: HLS vs. HSL |
| 58 | kHLS_Channel = 6, |
| 59 | kH_Channel = 7, |
| 60 | kL_Channel = 8, |
| 61 | kS_Channel = 9, |
| 62 | |
| 63 | // kYIQ_Channel = ?, |
| 64 | // kLum_Channel = ?, |
| 65 | // kIPC_Channel = ?, |
| 66 | // kQAC_Channel = ?, |
| 67 | |
| 68 | kA_Channel = 16, |
| 69 | }; |
| 70 | |
| 71 | switch (static_cast<uint8_t>(fChannel)) { |
| 72 | case kR_Channel: return { {-1, 1, 1, 1}, { 1,0,0,0}, false}; // r' = 1 - r |
| 73 | case kG_Channel: return { { 1,-1, 1, 1}, { 0,1,0,0}, false}; // g' = 1 - g |
| 74 | case kB_Channel: return { { 1, 1,-1, 1}, { 0,0,1,0}, false}; // b' = 1 - b |
| 75 | case kA_Channel: return { { 1, 1, 1,-1}, { 0,0,0,1}, false}; // a' = 1 - a |
| 76 | case kRGB_Channel: return { {-1,-1,-1, 1}, { 1,1,1,0}, false}; |
| 77 | |
| 78 | case kH_Channel: return { {-1, 1, 1, 1}, {.5f,0,0,0}, true}; // h' = .5 - h |
| 79 | case kS_Channel: return { { 1,-1, 1, 1}, { 0,1,0,0}, true}; // s' = 1 - s |
| 80 | case kL_Channel: return { { 1, 1,-1, 1}, { 0,0,1,0}, true}; // l' = 1 - l |
| 81 | case kHLS_Channel: return { {-1,-1,-1, 1}, {.5f,1,1,0}, true}; |
| 82 | |
| 83 | default: return { { 1, 1, 1, 1}, { 0,0,0,0}, false}; |
| 84 | } |
| 85 | |
| 86 | SkUNREACHABLE; |
| 87 | }(); |
| 88 | |
| 89 | const float m[] = { |
| 90 | stcm.scale[0], 0, 0, 0, stcm.trans[0], |
| 91 | 0, stcm.scale[1], 0, 0, stcm.trans[1], |
| 92 | 0, 0, stcm.scale[2], 0, stcm.trans[2], |
| 93 | 0, 0, 0, stcm.scale[3], stcm.trans[3], |
| 94 | |
| 95 | }; |
| 96 | |
| 97 | fColorFilter->setColorFilter(stcm.hsla ? SkColorFilters::HSLAMatrix(m) |
| 98 | : SkColorFilters::Matrix(m)); |
| 99 | } |
| 100 | |
| 101 | const sk_sp<sksg::ExternalColorFilter> fColorFilter; |
| 102 | |
| 103 | float fChannel = 0; |
| 104 | }; |
| 105 | |
| 106 | } // namespace |
| 107 | |
| 108 | sk_sp<sksg::RenderNode> EffectBuilder::attachInvertEffect(const skjson::ArrayValue& jprops, |
| 109 | sk_sp<sksg::RenderNode> layer) const { |
| 110 | return fBuilder->attachDiscardableAdapter<InvertEffectAdapter>(jprops, |
| 111 | std::move(layer), |
| 112 | fBuilder); |
| 113 | } |
| 114 | |
| 115 | } // namespace internal |
| 116 | } // namespace skottie |
| 117 | |