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/Composition.h"
11#include "modules/skottie/src/Layer.h"
12#include "modules/skottie/src/SkottieJson.h"
13#include "modules/sksg/include/SkSGRenderEffect.h"
14#include "src/utils/SkJSON.h"
15
16#include <algorithm>
17#include <iterator>
18
19namespace skottie {
20namespace internal {
21
22EffectBuilder::EffectBuilder(const AnimationBuilder* abuilder,
23                             const SkSize& layer_size,
24                             CompositionBuilder* cbuilder)
25    : fBuilder(abuilder)
26    , fCompBuilder(cbuilder)
27    , fLayerSize(layer_size) {}
28
29EffectBuilder::EffectBuilderT EffectBuilder::findBuilder(const skjson::ObjectValue& jeffect) const {
30    static constexpr struct BuilderInfo {
31        const char*    fName;
32        EffectBuilderT fBuilder;
33    } gBuilderInfo[] = {
34        // alphabetized for binary search lookup
35        { "ADBE Black&White"            , &EffectBuilder::attachBlackAndWhiteEffect      },
36        { "ADBE Brightness & Contrast 2", &EffectBuilder::attachBrightnessContrastEffect },
37        { "ADBE Bulge"                  , &EffectBuilder::attachBulgeEffect              },
38        { "ADBE Corner Pin"             , &EffectBuilder::attachCornerPinEffect          },
39        { "ADBE Displacement Map"       , &EffectBuilder::attachDisplacementMapEffect    },
40        { "ADBE Drop Shadow"            , &EffectBuilder::attachDropShadowEffect         },
41        { "ADBE Easy Levels2"           , &EffectBuilder::attachEasyLevelsEffect         },
42        { "ADBE Fill"                   , &EffectBuilder::attachFillEffect               },
43        { "ADBE Fractal Noise"          , &EffectBuilder::attachFractalNoiseEffect       },
44        { "ADBE Gaussian Blur 2"        , &EffectBuilder::attachGaussianBlurEffect       },
45        { "ADBE Geometry2"              , &EffectBuilder::attachTransformEffect          },
46        { "ADBE HUE SATURATION"         , &EffectBuilder::attachHueSaturationEffect      },
47        { "ADBE Invert"                 , &EffectBuilder::attachInvertEffect             },
48        { "ADBE Linear Wipe"            , &EffectBuilder::attachLinearWipeEffect         },
49        { "ADBE Motion Blur"            , &EffectBuilder::attachDirectionalBlurEffect    },
50        { "ADBE Pro Levels2"            , &EffectBuilder::attachProLevelsEffect          },
51        { "ADBE Radial Wipe"            , &EffectBuilder::attachRadialWipeEffect         },
52        { "ADBE Ramp"                   , &EffectBuilder::attachGradientEffect           },
53        { "ADBE Shift Channels"         , &EffectBuilder::attachShiftChannelsEffect      },
54        { "ADBE Threshold2"             , &EffectBuilder::attachThresholdEffect          },
55        { "ADBE Tile"                   , &EffectBuilder::attachMotionTileEffect         },
56        { "ADBE Tint"                   , &EffectBuilder::attachTintEffect               },
57        { "ADBE Tritone"                , &EffectBuilder::attachTritoneEffect            },
58        { "ADBE Venetian Blinds"        , &EffectBuilder::attachVenetianBlindsEffect     },
59        { "CC Sphere"                   , &EffectBuilder::attachSphereEffect             },
60        { "CC Toner"                    , &EffectBuilder::attachCCTonerEffect            },
61        { "SkSL Shader"                 , &EffectBuilder::attachSkSLEffect               },
62    };
63
64    const skjson::StringValue* mn = jeffect["mn"];
65    if (mn) {
66        const BuilderInfo key { mn->begin(), nullptr };
67        const auto* binfo = std::lower_bound(std::begin(gBuilderInfo),
68                                             std::end  (gBuilderInfo),
69                                             key,
70                                             [](const BuilderInfo& a, const BuilderInfo& b) {
71                                                 return strcmp(a.fName, b.fName) < 0;
72                                             });
73        if (binfo != std::end(gBuilderInfo) && !strcmp(binfo->fName, key.fName)) {
74            return binfo->fBuilder;
75        }
76    }
77
78    // Some legacy clients rely solely on the 'ty' field and generate (non-BM) JSON
79    // without a valid 'mn' string.  TODO: we should update them and remove this fallback.
80    enum : int32_t {
81        kTint_Effect         = 20,
82        kFill_Effect         = 21,
83        kTritone_Effect      = 23,
84        kDropShadow_Effect   = 25,
85        kRadialWipe_Effect   = 26,
86        kGaussianBlur_Effect = 29,
87    };
88
89    switch (ParseDefault<int>(jeffect["ty"], -1)) {
90        case         kTint_Effect: return &EffectBuilder::attachTintEffect;
91        case         kFill_Effect: return &EffectBuilder::attachFillEffect;
92        case      kTritone_Effect: return &EffectBuilder::attachTritoneEffect;
93        case   kDropShadow_Effect: return &EffectBuilder::attachDropShadowEffect;
94        case   kRadialWipe_Effect: return &EffectBuilder::attachRadialWipeEffect;
95        case kGaussianBlur_Effect: return &EffectBuilder::attachGaussianBlurEffect;
96        default: break;
97    }
98
99    fBuilder->log(Logger::Level::kWarning, &jeffect,
100                  "Unsupported layer effect: %s", mn ? mn->begin() : "(unknown)");
101
102    return nullptr;
103}
104
105sk_sp<sksg::RenderNode> EffectBuilder::attachEffects(const skjson::ArrayValue& jeffects,
106                                                     sk_sp<sksg::RenderNode> layer) const {
107    if (!layer) {
108        return nullptr;
109    }
110
111    for (const skjson::ObjectValue* jeffect : jeffects) {
112        if (!jeffect) {
113            continue;
114        }
115
116        const auto builder = this->findBuilder(*jeffect);
117        const skjson::ArrayValue* jprops = (*jeffect)["ef"];
118        if (!builder || !jprops) {
119            continue;
120        }
121
122        const AnimationBuilder::AutoPropertyTracker apt(fBuilder, *jeffect, PropertyObserver::NodeType::EFFECT);
123        layer = (this->*builder)(*jprops, std::move(layer));
124
125        if (!layer) {
126            fBuilder->log(Logger::Level::kError, jeffect, "Invalid layer effect.");
127            return nullptr;
128        }
129    }
130
131    return layer;
132}
133
134sk_sp<sksg::RenderNode> EffectBuilder::attachStyles(const skjson::ArrayValue& jstyles,
135                                                     sk_sp<sksg::RenderNode> layer) const {
136#if !defined(SKOTTIE_DISABLE_STYLES)
137    if (!layer) {
138        return nullptr;
139    }
140
141    using StyleBuilder =
142        sk_sp<sksg::RenderNode> (EffectBuilder::*)(const skjson::ObjectValue&,
143                                                   sk_sp<sksg::RenderNode>) const;
144    static constexpr StyleBuilder gStyleBuilders[] = {
145        nullptr,                                 // 'ty': 0 -> stroke
146        &EffectBuilder::attachDropShadowStyle,   // 'ty': 1 -> drop shadow
147        &EffectBuilder::attachInnerShadowStyle,  // 'ty': 2 -> inner shadow
148        &EffectBuilder::attachOuterGlowStyle,    // 'ty': 3 -> outer glow
149        &EffectBuilder::attachInnerGlowStyle,    // 'ty': 4 -> inner glow
150    };
151
152    for (const skjson::ObjectValue* jstyle : jstyles) {
153        if (!jstyle) {
154            continue;
155        }
156
157        const auto style_type =
158                ParseDefault<size_t>((*jstyle)["ty"], std::numeric_limits<size_t>::max());
159        auto builder = style_type < SK_ARRAY_COUNT(gStyleBuilders) ? gStyleBuilders[style_type]
160                                                                   : nullptr;
161
162        if (!builder) {
163            fBuilder->log(Logger::Level::kWarning, jstyle, "Unsupported layer style.");
164            continue;
165        }
166
167        layer = (this->*builder)(*jstyle, std::move(layer));
168    }
169#endif // !defined(SKOTTIE_DISABLE_STYLES)
170
171    return layer;
172}
173
174const skjson::Value& EffectBuilder::GetPropValue(const skjson::ArrayValue& jprops,
175                                                 size_t prop_index) {
176    static skjson::NullValue kNull;
177
178    if (prop_index >= jprops.size()) {
179        return kNull;
180    }
181
182    const skjson::ObjectValue* jprop = jprops[prop_index];
183
184    return jprop ? (*jprop)["v"] : kNull;
185}
186
187MaskShaderEffectBase::MaskShaderEffectBase(sk_sp<sksg::RenderNode> child, const SkSize& ls)
188    : fMaskEffectNode(sksg::MaskShaderEffect::Make(std::move(child)))
189    , fLayerSize(ls) {}
190
191void MaskShaderEffectBase::onSync() {
192    const auto minfo = this->onMakeMask();
193
194    fMaskEffectNode->setVisible(minfo.fVisible);
195    fMaskEffectNode->setShader(std::move(minfo.fMaskShader));
196}
197
198} // namespace internal
199} // namespace skottie
200