1 /*
2 * Copyright 2020 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 "include/private/SkTPin.h"
9 #include "modules/skottie/src/Adapter.h"
10 #include "modules/skottie/src/SkottieJson.h"
11 #include "modules/skottie/src/SkottiePriv.h"
12 #include "modules/skottie/src/SkottieValue.h"
13 #include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
14 #include "modules/sksg/include/SkSGGradient.h"
15 #include "modules/sksg/include/SkSGPaint.h"
16
17 namespace skottie {
18 namespace internal {
19
20 namespace {
21
22 class GradientAdapter final : public AnimatablePropertyContainer {
23 public:
Make(const skjson::ObjectValue & jgrad,const AnimationBuilder & abuilder)24 static sk_sp<GradientAdapter> Make(const skjson::ObjectValue& jgrad,
25 const AnimationBuilder& abuilder) {
26 const skjson::ObjectValue* jstops = jgrad["g"];
27 if (!jstops)
28 return nullptr;
29
30 const auto stopCount = ParseDefault<int>((*jstops)["p"], -1);
31 if (stopCount < 0)
32 return nullptr;
33
34 const auto type = (ParseDefault<int>(jgrad["t"], 1) == 1) ? Type::kLinear
35 : Type::kRadial;
36 auto gradient_node = (type == Type::kLinear)
37 ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
38 : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
39
40 return sk_sp<GradientAdapter>(new GradientAdapter(std::move(gradient_node),
41 type,
42 SkToSizeT(stopCount),
43 jgrad, *jstops, abuilder));
44 }
45
node() const46 const sk_sp<sksg::Gradient>& node() const { return fGradient; }
47
48 private:
49 enum class Type { kLinear, kRadial };
50
GradientAdapter(sk_sp<sksg::Gradient> gradient,Type type,size_t stop_count,const skjson::ObjectValue & jgrad,const skjson::ObjectValue & jstops,const AnimationBuilder & abuilder)51 GradientAdapter(sk_sp<sksg::Gradient> gradient,
52 Type type,
53 size_t stop_count,
54 const skjson::ObjectValue& jgrad,
55 const skjson::ObjectValue& jstops,
56 const AnimationBuilder& abuilder)
57 : fGradient(std::move(gradient))
58 , fType(type)
59 , fStopCount(stop_count) {
60 this->bind(abuilder, jgrad["s"], fStartPoint);
61 this->bind(abuilder, jgrad["e"], fEndPoint );
62 this->bind(abuilder, jstops["k"], fStops );
63 }
64
onSync()65 void onSync() override {
66 const auto s_point = SkPoint{fStartPoint.x, fStartPoint.y},
67 e_point = SkPoint{ fEndPoint.x, fEndPoint.y};
68
69 switch (fType) {
70 case Type::kLinear: {
71 auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
72 grad->setStartPoint(s_point);
73 grad->setEndPoint(e_point);
74
75 break;
76 }
77 case Type::kRadial: {
78 auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
79 grad->setStartCenter(s_point);
80 grad->setEndCenter(s_point);
81 grad->setStartRadius(0);
82 grad->setEndRadius(SkPoint::Distance(s_point, e_point));
83
84 break;
85 }
86 }
87
88 // Gradient color stops are specified as a consolidated float vector holding:
89 //
90 // a) an (optional) array of color/RGB stop records (t, r, g, b)
91 //
92 // followed by
93 //
94 // b) an (optional) array of opacity/alpha stop records (t, a)
95 //
96 struct ColorRec { float t, r, g, b; };
97 struct OpacityRec { float t, a; };
98
99 // The number of color records is explicit (fColorStopCount),
100 // while the number of opacity stops is implicit (based on the size of fStops).
101 //
102 // |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N
103 const auto c_count = fStopCount,
104 c_size = c_count * 4,
105 o_count = (fStops.size() - c_size) / 2;
106 if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) {
107 // apply() may get called before the stops are set, so only log when we have some stops.
108 if (!fStops.empty()) {
109 SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size());
110 }
111 return;
112 }
113
114 const auto* c_rec = c_count > 0
115 ? reinterpret_cast<const ColorRec*>(fStops.data())
116 : nullptr;
117 const auto* o_rec = o_count > 0
118 ? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size)
119 : nullptr;
120 const auto* c_end = c_rec + c_count;
121 const auto* o_end = o_rec + o_count;
122
123 sksg::Gradient::ColorStop current_stop = {
124 0.0f, {
125 c_rec ? c_rec->r : 0,
126 c_rec ? c_rec->g : 0,
127 c_rec ? c_rec->b : 0,
128 o_rec ? o_rec->a : 1,
129 }};
130
131 std::vector<sksg::Gradient::ColorStop> stops;
132 stops.reserve(c_count);
133
134 // Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed.
135 while (c_rec || o_rec) {
136 // After exhausting one of color recs / opacity recs, continue propagating the last
137 // computed values (as if they were specified at the current position).
138 const auto& cs = c_rec
139 ? *c_rec
140 : ColorRec{ o_rec->t,
141 current_stop.fColor.fR,
142 current_stop.fColor.fG,
143 current_stop.fColor.fB };
144 const auto& os = o_rec
145 ? *o_rec
146 : OpacityRec{ c_rec->t, current_stop.fColor.fA };
147
148 // Compute component lerp coefficients based on the relative position of the stops
149 // being considered. The idea is to select the smaller-pos stop, use its own properties
150 // as specified (lerp with t == 1), and lerp (with t < 1) the properties from the
151 // larger-pos stop against the previously computed gradient stop values.
152 const auto c_pos = std::max(cs.t, current_stop.fPosition),
153 o_pos = std::max(os.t, current_stop.fPosition),
154 c_pos_rel = c_pos - current_stop.fPosition,
155 o_pos_rel = o_pos - current_stop.fPosition,
156 t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f),
157 t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f);
158
159 auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
160
161 current_stop = {
162 std::min(c_pos, o_pos),
163 {
164 lerp(current_stop.fColor.fR, cs.r, t_c ),
165 lerp(current_stop.fColor.fG, cs.g, t_c ),
166 lerp(current_stop.fColor.fB, cs.b, t_c ),
167 lerp(current_stop.fColor.fA, os.a, t_o)
168 }
169 };
170 stops.push_back(current_stop);
171
172 // Consume one of, or both (for coincident positions) color/opacity stops.
173 if (c_pos <= o_pos) {
174 c_rec = next_rec<ColorRec>(c_rec, c_end);
175 }
176 if (o_pos <= c_pos) {
177 o_rec = next_rec<OpacityRec>(o_rec, o_end);
178 }
179 }
180
181 stops.shrink_to_fit();
182 fGradient->setColorStops(std::move(stops));
183 }
184
185 private:
186 template <typename T>
next_rec(const T * rec,const T * end_rec) const187 const T* next_rec(const T* rec, const T* end_rec) const {
188 if (!rec) return nullptr;
189
190 SkASSERT(rec < end_rec);
191 rec++;
192
193 return rec < end_rec ? rec : nullptr;
194 }
195
196 const sk_sp<sksg::Gradient> fGradient;
197 const Type fType;
198 const size_t fStopCount;
199
200 VectorValue fStops;
201 Vec2Value fStartPoint = {0,0},
202 fEndPoint = {0,0};
203 };
204
205 } // namespace
206
AttachGradientFill(const skjson::ObjectValue & jgrad,const AnimationBuilder * abuilder)207 sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientFill(const skjson::ObjectValue& jgrad,
208 const AnimationBuilder* abuilder) {
209 auto adapter = GradientAdapter::Make(jgrad, *abuilder);
210
211 return adapter
212 ? AttachFill(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
213 : nullptr;
214 }
215
AttachGradientStroke(const skjson::ObjectValue & jgrad,const AnimationBuilder * abuilder)216 sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientStroke(const skjson::ObjectValue& jgrad,
217 const AnimationBuilder* abuilder) {
218 auto adapter = GradientAdapter::Make(jgrad, *abuilder);
219
220 return adapter
221 ? AttachStroke(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
222 : nullptr;
223 }
224
225 } // namespace internal
226 } // namespace skottie
227