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