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/SkSGGradient.h"
12#include "modules/sksg/include/SkSGRenderEffect.h"
13#include "src/utils/SkJSON.h"
14
15namespace skottie {
16namespace internal {
17
18namespace  {
19
20class GradientRampEffectAdapter final : public AnimatablePropertyContainer {
21public:
22    static sk_sp<GradientRampEffectAdapter> Make(const skjson::ArrayValue& jprops,
23                                                 sk_sp<sksg::RenderNode> layer,
24                                                 const AnimationBuilder* abuilder) {
25        return sk_sp<GradientRampEffectAdapter>(new GradientRampEffectAdapter(jprops,
26                                                                              std::move(layer),
27                                                                              abuilder));
28    }
29
30    sk_sp<sksg::RenderNode> node() const { return fShaderEffect; }
31
32private:
33    GradientRampEffectAdapter(const skjson::ArrayValue& jprops,
34                              sk_sp<sksg::RenderNode> layer,
35                              const AnimationBuilder* abuilder)
36        : fShaderEffect(sksg::ShaderEffect::Make(std::move(layer))) {
37        enum : size_t {
38             kStartPoint_Index = 0,
39             kStartColor_Index = 1,
40               kEndPoint_Index = 2,
41               kEndColor_Index = 3,
42              kRampShape_Index = 4,
43            kRampScatter_Index = 5,
44             kBlendRatio_Index = 6,
45        };
46
47        EffectBinder(jprops, *abuilder, this)
48                .bind( kStartPoint_Index, fStartPoint)
49                .bind( kStartColor_Index, fStartColor)
50                .bind(   kEndPoint_Index, fEndPoint  )
51                .bind(   kEndColor_Index, fEndColor  )
52                .bind(  kRampShape_Index, fShape     )
53                .bind(kRampScatter_Index, fScatter   )
54                .bind( kBlendRatio_Index, fBlend     );
55    }
56
57    enum class InstanceType {
58        kNone,
59        kLinear,
60        kRadial,
61    };
62
63    void onSync() override {
64        // This adapter manages a SG fragment with the following structure:
65        //
66        // - ShaderEffect [fRoot]
67        //     \  GradientShader [fGradient]
68        //     \  child/wrapped fragment
69        //
70        // The gradient shader is updated based on the (animatable) instance type (linear/radial).
71
72        auto update_gradient = [this] (InstanceType new_type) {
73            if (new_type != fInstanceType) {
74                fGradient = new_type == InstanceType::kLinear
75                        ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
76                        : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
77
78                fShaderEffect->setShader(fGradient);
79                fInstanceType = new_type;
80            }
81
82            fGradient->setColorStops({{0, fStartColor},
83                                      {1,   fEndColor}});
84        };
85
86        static constexpr int kLinearShapeValue = 1;
87        const auto instance_type = (SkScalarRoundToInt(fShape) == kLinearShapeValue)
88                ? InstanceType::kLinear
89                : InstanceType::kRadial;
90
91        // Sync the gradient shader instance if needed.
92        update_gradient(instance_type);
93
94        // Sync instance-dependent gradient params.
95        const auto start_point = SkPoint{fStartPoint.x, fStartPoint.y},
96                     end_point = SkPoint{  fEndPoint.x,   fEndPoint.y};
97        if (instance_type == InstanceType::kLinear) {
98            auto* lg = static_cast<sksg::LinearGradient*>(fGradient.get());
99            lg->setStartPoint(start_point);
100            lg->setEndPoint(end_point);
101        } else {
102            SkASSERT(instance_type == InstanceType::kRadial);
103
104            auto* rg = static_cast<sksg::RadialGradient*>(fGradient.get());
105            rg->setStartCenter(start_point);
106            rg->setEndCenter(start_point);
107            rg->setEndRadius(SkPoint::Distance(start_point, end_point));
108        }
109
110        // TODO: blend, scatter
111    }
112
113    const sk_sp<sksg::ShaderEffect> fShaderEffect;
114    sk_sp<sksg::Gradient>           fGradient;
115
116    InstanceType              fInstanceType = InstanceType::kNone;
117
118    VectorValue fStartColor,
119                  fEndColor;
120    Vec2Value   fStartPoint = {0,0},
121                fEndPoint   = {0,0};
122    ScalarValue fBlend   = 0,
123                fScatter = 0,
124                fShape   = 0; // 1 -> linear, 7 -> radial (?!)
125};
126
127}  // namespace
128
129sk_sp<sksg::RenderNode> EffectBuilder::attachGradientEffect(const skjson::ArrayValue& jprops,
130                                                            sk_sp<sksg::RenderNode> layer) const {
131    return fBuilder->attachDiscardableAdapter<GradientRampEffectAdapter>(jprops,
132                                                                         std::move(layer),
133                                                                         fBuilder);
134}
135
136} // namespace internal
137} // namespace skottie
138