1 /*
2 * Copyright 2018 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 "SkottieAdapter.h"
9
10 #include "SkFont.h"
11 #include "SkMatrix.h"
12 #include "SkMatrix44.h"
13 #include "SkPath.h"
14 #include "SkRRect.h"
15 #include "SkSGColor.h"
16 #include "SkSGDraw.h"
17 #include "SkSGGradient.h"
18 #include "SkSGGroup.h"
19 #include "SkSGPath.h"
20 #include "SkSGRect.h"
21 #include "SkSGText.h"
22 #include "SkSGTransform.h"
23 #include "SkSGTrimEffect.h"
24 #include "SkTextBlob.h"
25 #include "SkTextUtils.h"
26 #include "SkTo.h"
27 #include "SkUTF.h"
28 #include "SkottieValue.h"
29
30 #include <cmath>
31 #include <utility>
32
33 namespace skottie {
34
RRectAdapter(sk_sp<sksg::RRect> wrapped_node)35 RRectAdapter::RRectAdapter(sk_sp<sksg::RRect> wrapped_node)
36 : fRRectNode(std::move(wrapped_node)) {}
37
38 RRectAdapter::~RRectAdapter() = default;
39
apply()40 void RRectAdapter::apply() {
41 // BM "position" == "center position"
42 auto rr = SkRRect::MakeRectXY(SkRect::MakeXYWH(fPosition.x() - fSize.width() / 2,
43 fPosition.y() - fSize.height() / 2,
44 fSize.width(), fSize.height()),
45 fRadius.width(),
46 fRadius.height());
47 fRRectNode->setRRect(rr);
48 }
49
TransformAdapter2D(sk_sp<sksg::Matrix<SkMatrix>> matrix)50 TransformAdapter2D::TransformAdapter2D(sk_sp<sksg::Matrix<SkMatrix>> matrix)
51 : fMatrixNode(std::move(matrix)) {}
52
53 TransformAdapter2D::~TransformAdapter2D() = default;
54
totalMatrix() const55 SkMatrix TransformAdapter2D::totalMatrix() const {
56 SkMatrix t = SkMatrix::MakeTrans(-fAnchorPoint.x(), -fAnchorPoint.y());
57
58 t.postScale(fScale.x() / 100, fScale.y() / 100); // 100% based
59 t.postRotate(fRotation);
60 t.postTranslate(fPosition.x(), fPosition.y());
61 // TODO: skew
62
63 return t;
64 }
65
apply()66 void TransformAdapter2D::apply() {
67 fMatrixNode->setMatrix(this->totalMatrix());
68 }
69
Vec3(const VectorValue & v)70 TransformAdapter3D::Vec3::Vec3(const VectorValue& v) {
71 fX = v.size() > 0 ? v[0] : 0;
72 fY = v.size() > 1 ? v[1] : 0;
73 fZ = v.size() > 2 ? v[2] : 0;
74 }
75
TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>> matrix)76 TransformAdapter3D::TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>> matrix)
77 : fMatrixNode(std::move(matrix)) {}
78
79 TransformAdapter3D::~TransformAdapter3D() = default;
80
totalMatrix() const81 SkMatrix44 TransformAdapter3D::totalMatrix() const {
82 SkMatrix44 t;
83
84 t.setTranslate(-fAnchorPoint.fX, -fAnchorPoint.fY, -fAnchorPoint.fZ);
85 t.postScale(fScale.fX / 100, fScale.fY / 100, fScale.fZ / 100);
86
87 // TODO: SkMatrix44:postRotate()?
88 SkMatrix44 r;
89 r.setRotateDegreesAbout(1, 0, 0, fRotation.fX);
90 t.postConcat(r);
91 r.setRotateDegreesAbout(0, 1, 0, fRotation.fY);
92 t.postConcat(r);
93 r.setRotateDegreesAbout(0, 0, 1, fRotation.fZ);
94 t.postConcat(r);
95
96 t.postTranslate(fPosition.fX, fPosition.fY, fPosition.fZ);
97
98 return t;
99 }
100
apply()101 void TransformAdapter3D::apply() {
102 fMatrixNode->setMatrix(this->totalMatrix());
103 }
104
RepeaterAdapter(sk_sp<sksg::RenderNode> repeater_node,Composite composite)105 RepeaterAdapter::RepeaterAdapter(sk_sp<sksg::RenderNode> repeater_node, Composite composite)
106 : fRepeaterNode(repeater_node)
107 , fComposite(composite)
108 , fRoot(sksg::Group::Make()) {}
109
110 RepeaterAdapter::~RepeaterAdapter() = default;
111
apply()112 void RepeaterAdapter::apply() {
113 static constexpr SkScalar kMaxCount = 512;
114 const auto count = static_cast<size_t>(SkTPin(fCount, 0.0f, kMaxCount) + 0.5f);
115
116 const auto& compute_transform = [this] (size_t index) {
117 const auto t = fOffset + index;
118
119 // Position, scale & rotation are "scaled" by index/offset.
120 SkMatrix m = SkMatrix::MakeTrans(-fAnchorPoint.x(),
121 -fAnchorPoint.y());
122 m.postScale(std::pow(fScale.x() * .01f, fOffset),
123 std::pow(fScale.y() * .01f, fOffset));
124 m.postRotate(t * fRotation);
125 m.postTranslate(t * fPosition.x() + fAnchorPoint.x(),
126 t * fPosition.y() + fAnchorPoint.y());
127
128 return m;
129 };
130
131 // TODO: start/end opacity support.
132
133 // TODO: we can avoid rebuilding all the fragments in most cases.
134 fRoot->clear();
135 for (size_t i = 0; i < count; ++i) {
136 const auto insert_index = (fComposite == Composite::kAbove) ? i : count - i - 1;
137 fRoot->addChild(sksg::TransformEffect::Make(fRepeaterNode,
138 compute_transform(insert_index)));
139 }
140 }
141
PolyStarAdapter(sk_sp<sksg::Path> wrapped_node,Type t)142 PolyStarAdapter::PolyStarAdapter(sk_sp<sksg::Path> wrapped_node, Type t)
143 : fPathNode(std::move(wrapped_node))
144 , fType(t) {}
145
146 PolyStarAdapter::~PolyStarAdapter() = default;
147
apply()148 void PolyStarAdapter::apply() {
149 static constexpr int kMaxPointCount = 100000;
150 const auto count = SkToUInt(SkTPin(SkScalarRoundToInt(fPointCount), 0, kMaxPointCount));
151 const auto arc = sk_ieee_float_divide(SK_ScalarPI * 2, count);
152
153 const auto pt_on_circle = [](const SkPoint& c, SkScalar r, SkScalar a) {
154 return SkPoint::Make(c.x() + r * std::cos(a),
155 c.y() + r * std::sin(a));
156 };
157
158 // TODO: inner/outer "roundness"?
159
160 SkPath poly;
161
162 auto angle = SkDegreesToRadians(fRotation - 90);
163 poly.moveTo(pt_on_circle(fPosition, fOuterRadius, angle));
164 poly.incReserve(fType == Type::kStar ? count * 2 : count);
165
166 for (unsigned i = 0; i < count; ++i) {
167 if (fType == Type::kStar) {
168 poly.lineTo(pt_on_circle(fPosition, fInnerRadius, angle + arc * 0.5f));
169 }
170 angle += arc;
171 poly.lineTo(pt_on_circle(fPosition, fOuterRadius, angle));
172 }
173
174 poly.close();
175 fPathNode->setPath(poly);
176 }
177
GradientAdapter(sk_sp<sksg::Gradient> grad,size_t stopCount)178 GradientAdapter::GradientAdapter(sk_sp<sksg::Gradient> grad, size_t stopCount)
179 : fGradient(std::move(grad))
180 , fStopCount(stopCount) {}
181
apply()182 void GradientAdapter::apply() {
183 this->onApply();
184
185 // |fColorStops| holds |fStopCount| x [ pos, r, g, g ] + ? x [ pos, alpha ]
186
187 if (fColorStops.size() < fStopCount * 4 || ((fColorStops.size() - fStopCount * 4) % 2)) {
188 // apply() may get called before the stops are set, so only log when we have some stops.
189 if (!fColorStops.empty()) {
190 SkDebugf("!! Invalid gradient stop array size: %zu\n", fColorStops.size());
191 }
192 return;
193 }
194
195 std::vector<sksg::Gradient::ColorStop> stops;
196
197 // TODO: merge/lerp opacity stops
198 const auto csEnd = fColorStops.cbegin() + fStopCount * 4;
199 for (auto cs = fColorStops.cbegin(); cs != csEnd; cs += 4) {
200 const auto pos = cs[0];
201 const VectorValue rgb({ cs[1], cs[2], cs[3] });
202
203 stops.push_back({ pos, ValueTraits<VectorValue>::As<SkColor>(rgb) });
204 }
205
206 fGradient->setColorStops(std::move(stops));
207 }
208
LinearGradientAdapter(sk_sp<sksg::LinearGradient> grad,size_t stopCount)209 LinearGradientAdapter::LinearGradientAdapter(sk_sp<sksg::LinearGradient> grad, size_t stopCount)
210 : INHERITED(std::move(grad), stopCount) {}
211
onApply()212 void LinearGradientAdapter::onApply() {
213 auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
214 grad->setStartPoint(this->startPoint());
215 grad->setEndPoint(this->endPoint());
216 }
217
RadialGradientAdapter(sk_sp<sksg::RadialGradient> grad,size_t stopCount)218 RadialGradientAdapter::RadialGradientAdapter(sk_sp<sksg::RadialGradient> grad, size_t stopCount)
219 : INHERITED(std::move(grad), stopCount) {}
220
onApply()221 void RadialGradientAdapter::onApply() {
222 auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
223 grad->setStartCenter(this->startPoint());
224 grad->setEndCenter(this->startPoint());
225 grad->setStartRadius(0);
226 grad->setEndRadius(SkPoint::Distance(this->startPoint(), this->endPoint()));
227 }
228
TrimEffectAdapter(sk_sp<sksg::TrimEffect> trimEffect)229 TrimEffectAdapter::TrimEffectAdapter(sk_sp<sksg::TrimEffect> trimEffect)
230 : fTrimEffect(std::move(trimEffect)) {
231 SkASSERT(fTrimEffect);
232 }
233
234 TrimEffectAdapter::~TrimEffectAdapter() = default;
235
apply()236 void TrimEffectAdapter::apply() {
237 // BM semantics: start/end are percentages, offset is "degrees" (?!).
238 const auto start = fStart / 100,
239 end = fEnd / 100,
240 offset = fOffset / 360;
241
242 auto startT = SkTMin(start, end) + offset,
243 stopT = SkTMax(start, end) + offset;
244 auto mode = SkTrimPathEffect::Mode::kNormal;
245
246 if (stopT - startT < 1) {
247 startT -= SkScalarFloorToScalar(startT);
248 stopT -= SkScalarFloorToScalar(stopT);
249
250 if (startT > stopT) {
251 using std::swap;
252 swap(startT, stopT);
253 mode = SkTrimPathEffect::Mode::kInverted;
254 }
255 } else {
256 startT = 0;
257 stopT = 1;
258 }
259
260 fTrimEffect->setStart(startT);
261 fTrimEffect->setStop(stopT);
262 fTrimEffect->setMode(mode);
263 }
264
TextAdapter(sk_sp<sksg::Group> root)265 TextAdapter::TextAdapter(sk_sp<sksg::Group> root)
266 : fRoot(std::move(root))
267 , fTextNode(sksg::TextBlob::Make())
268 , fFillColor(sksg::Color::Make(SK_ColorTRANSPARENT))
269 , fStrokeColor(sksg::Color::Make(SK_ColorTRANSPARENT))
270 , fFillNode(sksg::Draw::Make(fTextNode, fFillColor))
271 , fStrokeNode(sksg::Draw::Make(fTextNode, fStrokeColor))
272 , fHadFill(false)
273 , fHadStroke(false) {
274 // Build a SG fragment with the following general format:
275 //
276 // [Group]
277 // [Draw]
278 // [FillPaint]
279 // [Text]*
280 // [Draw]
281 // [StrokePaint]
282 // [Text]*
283 //
284 // * where the text node is shared
285
286 fFillColor->setAntiAlias(true);
287 fStrokeColor->setAntiAlias(true);
288 fStrokeColor->setStyle(SkPaint::kStroke_Style);
289 }
290
291 TextAdapter::~TextAdapter() = default;
292
makeBlob() const293 sk_sp<SkTextBlob> TextAdapter::makeBlob() const {
294 SkFont font(fText.fTypeface, fText.fTextSize);
295 font.setHinting(kNo_SkFontHinting);
296 font.setSubpixel(true);
297 font.setEdging(SkFont::Edging::kAntiAlias);
298
299 const auto align_fract = [](SkTextUtils::Align align) {
300 switch (align) {
301 case SkTextUtils::kLeft_Align: return 0.0f;
302 case SkTextUtils::kCenter_Align: return -0.5f;
303 case SkTextUtils::kRight_Align: return -1.0f;
304 }
305 return 0.0f; // go home, msvc...
306 }(fText.fAlign);
307
308 const auto line_spacing = font.getSpacing();
309 float y_off = 0;
310 SkSTArray<256, SkGlyphID, true> line_glyph_buffer;
311 SkTextBlobBuilder builder;
312
313 const auto& push_line = [&](const char* start, const char* end) {
314 if (end > start) {
315 const auto len = SkToSizeT(end - start);
316 line_glyph_buffer.reset(font.countText(start, len, kUTF8_SkTextEncoding));
317 SkAssertResult(font.textToGlyphs(start, len, kUTF8_SkTextEncoding, line_glyph_buffer.data(),
318 line_glyph_buffer.count())
319 == line_glyph_buffer.count());
320
321 const auto x_off = align_fract != 0
322 ? align_fract * font.measureText(start, len, kUTF8_SkTextEncoding)
323 : 0;
324 const auto& buf = builder.allocRun(font, line_glyph_buffer.count(), x_off, y_off);
325 if (!buf.glyphs) {
326 return;
327 }
328
329 memcpy(buf.glyphs, line_glyph_buffer.data(),
330 SkToSizeT(line_glyph_buffer.count()) * sizeof(SkGlyphID));
331
332 y_off += line_spacing;
333 }
334 };
335
336 const auto& is_line_break = [](SkUnichar uch) {
337 // TODO: other explicit breaks?
338 return uch == '\r';
339 };
340
341 const char* ptr = fText.fText.c_str();
342 const char* line_start = ptr;
343 const char* end = ptr + fText.fText.size();
344
345 while (ptr < end) {
346 if (is_line_break(SkUTF::NextUTF8(&ptr, end))) {
347 push_line(line_start, ptr - 1);
348 line_start = ptr;
349 }
350 }
351 push_line(line_start, ptr);
352
353 return builder.make();
354 }
355
apply()356 void TextAdapter::apply() {
357 fTextNode->setBlob(this->makeBlob());
358 fFillColor->setColor(fText.fFillColor);
359 fStrokeColor->setColor(fText.fStrokeColor);
360 fStrokeColor->setStrokeWidth(fText.fStrokeWidth);
361
362 // Turn the state transition into a tri-state value:
363 // -1: detach node
364 // 0: no change
365 // 1: attach node
366 const auto fill_change = SkToInt(fText.fHasFill) - SkToInt(fHadFill);
367 const auto stroke_change = SkToInt(fText.fHasStroke) - SkToInt(fHadStroke);
368
369 // Sync SG topology.
370 if (fill_change || stroke_change) {
371 // This is trickier than it should be because sksg::Group only allows adding children
372 // in paint-order.
373 if (stroke_change < 0 || (fHadStroke && fill_change > 0)) {
374 fRoot->removeChild(fStrokeNode);
375 }
376
377 if (fill_change < 0) {
378 fRoot->removeChild(fFillNode);
379 } else if (fill_change > 0) {
380 fRoot->addChild(fFillNode);
381 }
382
383 if (stroke_change > 0 || (fHadStroke && fill_change > 0)) {
384 fRoot->addChild(fStrokeNode);
385 }
386 }
387
388 // Track current state.
389 fHadFill = fText.fHasFill;
390 fHadStroke = fText.fHasStroke;
391 }
392
393 } // namespace skottie
394