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/core/SkColor.h"
9 #include "include/core/SkMatrix.h"
10 #include "include/core/SkPoint.h"
11 #include "include/core/SkRefCnt.h"
12 #include "include/core/SkScalar.h"
13 #include "include/private/base/SkAssert.h"
14 #include "include/private/base/SkDebug.h"
15 #include "include/private/base/SkFloatingPoint.h"
16 #include "include/private/base/SkTPin.h"
17 #include "include/private/base/SkTo.h"
18 #include "modules/skottie/src/SkottieJson.h"
19 #include "modules/skottie/src/SkottieValue.h"
20 #include "modules/skottie/src/animator/Animator.h"
21 #include "modules/skottie/src/layers/shapelayer/ShapeLayer.h"
22 #include "modules/sksg/include/SkSGGradient.h"
23 #include "modules/sksg/include/SkSGPaint.h"
24 #include "modules/sksg/include/SkSGRenderEffect.h"
25 #include "src/utils/SkJSON.h"
26
27 #include <algorithm>
28 #include <cstddef>
29 #include <utility>
30 #include <vector>
31
32 namespace skottie {
33 namespace internal {
34 class AnimationBuilder;
35
36 namespace {
37
38 class GradientAdapter final : public AnimatablePropertyContainer {
39 public:
Make(const skjson::ObjectValue & jgrad,const AnimationBuilder & abuilder)40 static sk_sp<GradientAdapter> Make(const skjson::ObjectValue& jgrad,
41 const AnimationBuilder& abuilder) {
42 const skjson::ObjectValue* jstops = jgrad["g"];
43 if (!jstops)
44 return nullptr;
45
46 const auto stopCount = ParseDefault<int>((*jstops)["p"], -1);
47 if (stopCount < 0)
48 return nullptr;
49
50 const auto type = (ParseDefault<int>(jgrad["t"], 1) == 1) ? Type::kLinear
51 : Type::kRadial;
52 auto gradient_node = (type == Type::kLinear)
53 ? sk_sp<sksg::Gradient>(sksg::LinearGradient::Make())
54 : sk_sp<sksg::Gradient>(sksg::RadialGradient::Make());
55
56 return sk_sp<GradientAdapter>(new GradientAdapter(std::move(gradient_node),
57 type,
58 SkToSizeT(stopCount),
59 jgrad, *jstops, abuilder));
60 }
61
node() const62 const sk_sp<sksg::Gradient>& node() const { return fGradient; }
63
64 private:
65 enum class Type { kLinear, kRadial };
66
GradientAdapter(sk_sp<sksg::Gradient> gradient,Type type,size_t stop_count,const skjson::ObjectValue & jgrad,const skjson::ObjectValue & jstops,const AnimationBuilder & abuilder)67 GradientAdapter(sk_sp<sksg::Gradient> gradient,
68 Type type,
69 size_t stop_count,
70 const skjson::ObjectValue& jgrad,
71 const skjson::ObjectValue& jstops,
72 const AnimationBuilder& abuilder)
73 : fGradient(std::move(gradient))
74 , fType(type)
75 , fStopCount(stop_count) {
76 this->bind(abuilder, jgrad["s"], fStartPoint );
77 this->bind(abuilder, jgrad["e"], fEndPoint );
78 this->bind(abuilder, jgrad["h"], fHighlightLength);
79 this->bind(abuilder, jgrad["a"], fHighlightAngle );
80 this->bind(abuilder, jstops["k"], fStops );
81 }
82
onSync()83 void onSync() override {
84 const auto s_point = SkPoint{fStartPoint.x, fStartPoint.y},
85 e_point = SkPoint{ fEndPoint.x, fEndPoint.y};
86
87 switch (fType) {
88 case Type::kLinear: {
89 auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
90 grad->setStartPoint(s_point);
91 grad->setEndPoint(e_point);
92
93 break;
94 }
95 case Type::kRadial: {
96 // The highlight parameters control the location of the actual gradient start point
97 // (equivalent to SVG's radial gradient focal point
98 // https://www.w3.org/TR/SVG11/pservers.html#RadialGradients)
99 //
100 // - highlight length determines the position along the |s_point -> e_point| vector,
101 // where 0% corresponds to s_point and 100% corresponds to e_point.
102 // - highlight angle rotates the point around s_point
103 //
104 const SkPoint rotated_e_point =
105 SkMatrix::RotateDeg(fHighlightAngle, s_point).mapPoint(e_point);
106
107 // The valid range for length is [-100% .. 100%], where negative values mirror the
108 // positive interval relative to s_point.
109 //
110 // Edge case: for exactly -100% and 100%, the focal point lies on the end circle and
111 // this triggers specific SVG behavior (see
112 // SkConicalGrdient::FocalData::isFocalOnCircle and friends), which does not match
113 // AE's semantics. To avoid that, we clamp by an epsilon value.
114 const float eps = SK_ScalarNearlyZero * 2,
115 h_len = SkTPin(fHighlightLength * 0.01f, -1 + eps, 1 - eps);
116
117 const SkPoint focal_point = s_point + (rotated_e_point - s_point) * h_len;
118
119 auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
120 grad->setStartCenter(focal_point);
121 grad->setEndCenter(s_point);
122 grad->setStartRadius(0);
123 grad->setEndRadius(SkPoint::Distance(s_point, rotated_e_point));
124
125 break;
126 }
127 }
128
129 // Gradient color stops are specified as a consolidated float vector holding:
130 //
131 // a) an (optional) array of color/RGB stop records (t, r, g, b)
132 //
133 // followed by
134 //
135 // b) an (optional) array of opacity/alpha stop records (t, a)
136 //
137 struct ColorRec { float t, r, g, b; };
138 struct OpacityRec { float t, a; };
139
140 // The number of color records is explicit (fColorStopCount),
141 // while the number of opacity stops is implicit (based on the size of fStops).
142 //
143 // |fStops| holds ColorRec x |fColorStopCount| + OpacityRec x N
144 const auto c_count = fStopCount,
145 c_size = c_count * 4,
146 o_count = (fStops.size() - c_size) / 2;
147 if (fStops.size() < c_size || fStops.size() != (c_count * 4 + o_count * 2)) {
148 // apply() may get called before the stops are set, so only log when we have some stops.
149 if (!fStops.empty()) {
150 SkDebugf("!! Invalid gradient stop array size: %zu\n", fStops.size());
151 }
152 return;
153 }
154
155 const auto* c_rec = c_count > 0
156 ? reinterpret_cast<const ColorRec*>(fStops.data())
157 : nullptr;
158 const auto* o_rec = o_count > 0
159 ? reinterpret_cast<const OpacityRec*>(fStops.data() + c_size)
160 : nullptr;
161 const auto* c_end = c_rec + c_count;
162 const auto* o_end = o_rec + o_count;
163
164 sksg::Gradient::ColorStop current_stop = {
165 0.0f, {
166 c_rec ? c_rec->r : 0,
167 c_rec ? c_rec->g : 0,
168 c_rec ? c_rec->b : 0,
169 o_rec ? o_rec->a : 1,
170 }};
171
172 std::vector<sksg::Gradient::ColorStop> stops;
173 stops.reserve(c_count);
174
175 // Merge-sort the color and opacity stops, LERP-ing intermediate channel values as needed.
176 while (c_rec || o_rec) {
177 // After exhausting one of color recs / opacity recs, continue propagating the last
178 // computed values (as if they were specified at the current position).
179 const auto& cs = c_rec
180 ? *c_rec
181 : ColorRec{ o_rec->t,
182 current_stop.fColor.fR,
183 current_stop.fColor.fG,
184 current_stop.fColor.fB };
185 const auto& os = o_rec
186 ? *o_rec
187 : OpacityRec{ c_rec->t, current_stop.fColor.fA };
188
189 // Compute component lerp coefficients based on the relative position of the stops
190 // being considered. The idea is to select the smaller-pos stop, use its own properties
191 // as specified (lerp with t == 1), and lerp (with t < 1) the properties from the
192 // larger-pos stop against the previously computed gradient stop values.
193 const auto c_pos = std::max(cs.t, current_stop.fPosition),
194 o_pos = std::max(os.t, current_stop.fPosition),
195 c_pos_rel = c_pos - current_stop.fPosition,
196 o_pos_rel = o_pos - current_stop.fPosition,
197 t_c = SkTPin(sk_ieee_float_divide(o_pos_rel, c_pos_rel), 0.0f, 1.0f),
198 t_o = SkTPin(sk_ieee_float_divide(c_pos_rel, o_pos_rel), 0.0f, 1.0f);
199
200 auto lerp = [](float a, float b, float t) { return a + t * (b - a); };
201
202 current_stop = {
203 std::min(c_pos, o_pos),
204 {
205 lerp(current_stop.fColor.fR, cs.r, t_c ),
206 lerp(current_stop.fColor.fG, cs.g, t_c ),
207 lerp(current_stop.fColor.fB, cs.b, t_c ),
208 lerp(current_stop.fColor.fA, os.a, t_o)
209 }
210 };
211 stops.push_back(current_stop);
212
213 // Consume one of, or both (for coincident positions) color/opacity stops.
214 if (c_pos <= o_pos) {
215 c_rec = next_rec<ColorRec>(c_rec, c_end);
216 }
217 if (o_pos <= c_pos) {
218 o_rec = next_rec<OpacityRec>(o_rec, o_end);
219 }
220 }
221
222 stops.shrink_to_fit();
223 fGradient->setColorStops(std::move(stops));
224 }
225
226 private:
227 template <typename T>
next_rec(const T * rec,const T * end_rec) const228 const T* next_rec(const T* rec, const T* end_rec) const {
229 if (!rec) return nullptr;
230
231 SkASSERT(rec < end_rec);
232 rec++;
233
234 return rec < end_rec ? rec : nullptr;
235 }
236
237 const sk_sp<sksg::Gradient> fGradient;
238 const Type fType;
239 const size_t fStopCount;
240
241 VectorValue fStops;
242 Vec2Value fStartPoint = {0,0},
243 fEndPoint = {0,0};
244 float fHighlightLength = 0,
245 fHighlightAngle = 0;
246 };
247
248 } // namespace
249
AttachGradientFill(const skjson::ObjectValue & jgrad,const AnimationBuilder * abuilder)250 sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientFill(const skjson::ObjectValue& jgrad,
251 const AnimationBuilder* abuilder) {
252 auto adapter = GradientAdapter::Make(jgrad, *abuilder);
253
254 return adapter
255 ? AttachFill(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
256 : nullptr;
257 }
258
AttachGradientStroke(const skjson::ObjectValue & jgrad,const AnimationBuilder * abuilder)259 sk_sp<sksg::PaintNode> ShapeBuilder::AttachGradientStroke(const skjson::ObjectValue& jgrad,
260 const AnimationBuilder* abuilder) {
261 auto adapter = GradientAdapter::Make(jgrad, *abuilder);
262
263 return adapter
264 ? AttachStroke(jgrad, abuilder, sksg::ShaderPaint::Make(adapter->node()), adapter)
265 : nullptr;
266 }
267
268 } // namespace internal
269 } // namespace skottie
270