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} // anonymous ns
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