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
16namespace skottie {
17namespace internal {
18
19namespace {
20
21class GradientAdapter final : public AnimatablePropertyContainer {
22public:
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
47private:
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
184private:
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
206sk_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
215sk_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