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 "SkSGRenderEffect.h"
22 #include "SkSGText.h"
23 #include "SkSGTransform.h"
24 #include "SkSGTrimEffect.h"
25 #include "SkShaper.h"
26 #include "SkTextBlob.h"
27 #include "SkTextUtils.h"
28 #include "SkTo.h"
29 #include "SkUTF.h"
30 #include "SkottieValue.h"
31
32 #include <cmath>
33 #include <utility>
34
35 namespace skottie {
36
RRectAdapter(sk_sp<sksg::RRect> wrapped_node)37 RRectAdapter::RRectAdapter(sk_sp<sksg::RRect> wrapped_node)
38 : fRRectNode(std::move(wrapped_node)) {}
39
40 RRectAdapter::~RRectAdapter() = default;
41
apply()42 void RRectAdapter::apply() {
43 // BM "position" == "center position"
44 auto rr = SkRRect::MakeRectXY(SkRect::MakeXYWH(fPosition.x() - fSize.width() / 2,
45 fPosition.y() - fSize.height() / 2,
46 fSize.width(), fSize.height()),
47 fRadius.width(),
48 fRadius.height());
49 fRRectNode->setRRect(rr);
50 }
51
TransformAdapter2D(sk_sp<sksg::Matrix<SkMatrix>> matrix)52 TransformAdapter2D::TransformAdapter2D(sk_sp<sksg::Matrix<SkMatrix>> matrix)
53 : fMatrixNode(std::move(matrix)) {}
54
55 TransformAdapter2D::~TransformAdapter2D() = default;
56
totalMatrix() const57 SkMatrix TransformAdapter2D::totalMatrix() const {
58 SkMatrix t = SkMatrix::MakeTrans(-fAnchorPoint.x(), -fAnchorPoint.y());
59
60 t.postScale(fScale.x() / 100, fScale.y() / 100); // 100% based
61 t.postRotate(fRotation);
62 t.postTranslate(fPosition.x(), fPosition.y());
63 // TODO: skew
64
65 return t;
66 }
67
apply()68 void TransformAdapter2D::apply() {
69 fMatrixNode->setMatrix(this->totalMatrix());
70 }
71
Vec3(const VectorValue & v)72 TransformAdapter3D::Vec3::Vec3(const VectorValue& v) {
73 fX = v.size() > 0 ? v[0] : 0;
74 fY = v.size() > 1 ? v[1] : 0;
75 fZ = v.size() > 2 ? v[2] : 0;
76 }
77
TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>> matrix)78 TransformAdapter3D::TransformAdapter3D(sk_sp<sksg::Matrix<SkMatrix44>> matrix)
79 : fMatrixNode(std::move(matrix)) {}
80
81 TransformAdapter3D::~TransformAdapter3D() = default;
82
totalMatrix() const83 SkMatrix44 TransformAdapter3D::totalMatrix() const {
84 SkMatrix44 t;
85
86 t.setTranslate(-fAnchorPoint.fX, -fAnchorPoint.fY, -fAnchorPoint.fZ);
87 t.postScale(fScale.fX / 100, fScale.fY / 100, fScale.fZ / 100);
88
89 // TODO: SkMatrix44:postRotate()?
90 SkMatrix44 r;
91 r.setRotateDegreesAbout(1, 0, 0, fRotation.fX);
92 t.postConcat(r);
93 r.setRotateDegreesAbout(0, 1, 0, fRotation.fY);
94 t.postConcat(r);
95 r.setRotateDegreesAbout(0, 0, 1, fRotation.fZ);
96 t.postConcat(r);
97
98 t.postTranslate(fPosition.fX, fPosition.fY, fPosition.fZ);
99
100 return t;
101 }
102
apply()103 void TransformAdapter3D::apply() {
104 fMatrixNode->setMatrix(this->totalMatrix());
105 }
106
RepeaterAdapter(sk_sp<sksg::RenderNode> repeater_node,Composite composite)107 RepeaterAdapter::RepeaterAdapter(sk_sp<sksg::RenderNode> repeater_node, Composite composite)
108 : fRepeaterNode(repeater_node)
109 , fComposite(composite)
110 , fRoot(sksg::Group::Make()) {}
111
112 RepeaterAdapter::~RepeaterAdapter() = default;
113
apply()114 void RepeaterAdapter::apply() {
115 static constexpr SkScalar kMaxCount = 512;
116 const auto count = static_cast<size_t>(SkTPin(fCount, 0.0f, kMaxCount) + 0.5f);
117
118 const auto& compute_transform = [this] (size_t index) {
119 const auto t = fOffset + index;
120
121 // Position, scale & rotation are "scaled" by index/offset.
122 SkMatrix m = SkMatrix::MakeTrans(-fAnchorPoint.x(),
123 -fAnchorPoint.y());
124 m.postScale(std::pow(fScale.x() * .01f, fOffset),
125 std::pow(fScale.y() * .01f, fOffset));
126 m.postRotate(t * fRotation);
127 m.postTranslate(t * fPosition.x() + fAnchorPoint.x(),
128 t * fPosition.y() + fAnchorPoint.y());
129
130 return m;
131 };
132
133 // TODO: start/end opacity support.
134
135 // TODO: we can avoid rebuilding all the fragments in most cases.
136 fRoot->clear();
137 for (size_t i = 0; i < count; ++i) {
138 const auto insert_index = (fComposite == Composite::kAbove) ? i : count - i - 1;
139 fRoot->addChild(sksg::TransformEffect::Make(fRepeaterNode,
140 compute_transform(insert_index)));
141 }
142 }
143
PolyStarAdapter(sk_sp<sksg::Path> wrapped_node,Type t)144 PolyStarAdapter::PolyStarAdapter(sk_sp<sksg::Path> wrapped_node, Type t)
145 : fPathNode(std::move(wrapped_node))
146 , fType(t) {}
147
148 PolyStarAdapter::~PolyStarAdapter() = default;
149
apply()150 void PolyStarAdapter::apply() {
151 static constexpr int kMaxPointCount = 100000;
152 const auto count = SkToUInt(SkTPin(SkScalarRoundToInt(fPointCount), 0, kMaxPointCount));
153 const auto arc = sk_ieee_float_divide(SK_ScalarPI * 2, count);
154
155 const auto pt_on_circle = [](const SkPoint& c, SkScalar r, SkScalar a) {
156 return SkPoint::Make(c.x() + r * std::cos(a),
157 c.y() + r * std::sin(a));
158 };
159
160 // TODO: inner/outer "roundness"?
161
162 SkPath poly;
163
164 auto angle = SkDegreesToRadians(fRotation - 90);
165 poly.moveTo(pt_on_circle(fPosition, fOuterRadius, angle));
166 poly.incReserve(fType == Type::kStar ? count * 2 : count);
167
168 for (unsigned i = 0; i < count; ++i) {
169 if (fType == Type::kStar) {
170 poly.lineTo(pt_on_circle(fPosition, fInnerRadius, angle + arc * 0.5f));
171 }
172 angle += arc;
173 poly.lineTo(pt_on_circle(fPosition, fOuterRadius, angle));
174 }
175
176 poly.close();
177 fPathNode->setPath(poly);
178 }
179
GradientAdapter(sk_sp<sksg::Gradient> grad,size_t stopCount)180 GradientAdapter::GradientAdapter(sk_sp<sksg::Gradient> grad, size_t stopCount)
181 : fGradient(std::move(grad))
182 , fStopCount(stopCount) {}
183
apply()184 void GradientAdapter::apply() {
185 this->onApply();
186
187 // |fColorStops| holds |fStopCount| x [ pos, r, g, g ] + ? x [ pos, alpha ]
188
189 if (fColorStops.size() < fStopCount * 4 || ((fColorStops.size() - fStopCount * 4) % 2)) {
190 // apply() may get called before the stops are set, so only log when we have some stops.
191 if (!fColorStops.empty()) {
192 SkDebugf("!! Invalid gradient stop array size: %zu\n", fColorStops.size());
193 }
194 return;
195 }
196
197 std::vector<sksg::Gradient::ColorStop> stops;
198
199 // TODO: merge/lerp opacity stops
200 const auto csEnd = fColorStops.cbegin() + fStopCount * 4;
201 for (auto cs = fColorStops.cbegin(); cs != csEnd; cs += 4) {
202 const auto pos = cs[0];
203 const VectorValue rgb({ cs[1], cs[2], cs[3] });
204
205 stops.push_back({ pos, ValueTraits<VectorValue>::As<SkColor>(rgb) });
206 }
207
208 fGradient->setColorStops(std::move(stops));
209 }
210
LinearGradientAdapter(sk_sp<sksg::LinearGradient> grad,size_t stopCount)211 LinearGradientAdapter::LinearGradientAdapter(sk_sp<sksg::LinearGradient> grad, size_t stopCount)
212 : INHERITED(std::move(grad), stopCount) {}
213
onApply()214 void LinearGradientAdapter::onApply() {
215 auto* grad = static_cast<sksg::LinearGradient*>(fGradient.get());
216 grad->setStartPoint(this->startPoint());
217 grad->setEndPoint(this->endPoint());
218 }
219
RadialGradientAdapter(sk_sp<sksg::RadialGradient> grad,size_t stopCount)220 RadialGradientAdapter::RadialGradientAdapter(sk_sp<sksg::RadialGradient> grad, size_t stopCount)
221 : INHERITED(std::move(grad), stopCount) {}
222
onApply()223 void RadialGradientAdapter::onApply() {
224 auto* grad = static_cast<sksg::RadialGradient*>(fGradient.get());
225 grad->setStartCenter(this->startPoint());
226 grad->setEndCenter(this->startPoint());
227 grad->setStartRadius(0);
228 grad->setEndRadius(SkPoint::Distance(this->startPoint(), this->endPoint()));
229 }
230
TrimEffectAdapter(sk_sp<sksg::TrimEffect> trimEffect)231 TrimEffectAdapter::TrimEffectAdapter(sk_sp<sksg::TrimEffect> trimEffect)
232 : fTrimEffect(std::move(trimEffect)) {
233 SkASSERT(fTrimEffect);
234 }
235
236 TrimEffectAdapter::~TrimEffectAdapter() = default;
237
apply()238 void TrimEffectAdapter::apply() {
239 // BM semantics: start/end are percentages, offset is "degrees" (?!).
240 const auto start = fStart / 100,
241 end = fEnd / 100,
242 offset = fOffset / 360;
243
244 auto startT = SkTMin(start, end) + offset,
245 stopT = SkTMax(start, end) + offset;
246 auto mode = SkTrimPathEffect::Mode::kNormal;
247
248 if (stopT - startT < 1) {
249 startT -= SkScalarFloorToScalar(startT);
250 stopT -= SkScalarFloorToScalar(stopT);
251
252 if (startT > stopT) {
253 using std::swap;
254 swap(startT, stopT);
255 mode = SkTrimPathEffect::Mode::kInverted;
256 }
257 } else {
258 startT = 0;
259 stopT = 1;
260 }
261
262 fTrimEffect->setStart(startT);
263 fTrimEffect->setStop(stopT);
264 fTrimEffect->setMode(mode);
265 }
266
DropShadowEffectAdapter(sk_sp<sksg::DropShadowImageFilter> dropShadow)267 DropShadowEffectAdapter::DropShadowEffectAdapter(sk_sp<sksg::DropShadowImageFilter> dropShadow)
268 : fDropShadow(std::move(dropShadow)) {
269 SkASSERT(fDropShadow);
270 }
271
272 DropShadowEffectAdapter::~DropShadowEffectAdapter() = default;
273
apply()274 void DropShadowEffectAdapter::apply() {
275 // fColor -> RGB, fOpacity -> A
276 fDropShadow->setColor(SkColorSetA(fColor, SkTPin(SkScalarRoundToInt(fOpacity), 0, 255)));
277
278 // The offset is specified in terms of a bearing angle + distance.
279 SkScalar sinV, cosV;
280 sinV = SkScalarSinCos(SkDegreesToRadians(90 - fDirection), &cosV);
281 fDropShadow->setOffset(SkVector::Make(fDistance * cosV, -fDistance * sinV));
282
283 // Close enough to AE.
284 static constexpr SkScalar kSoftnessToSigmaFactor = 0.3f;
285 const auto sigma = fSoftness * kSoftnessToSigmaFactor;
286 fDropShadow->setSigma(SkVector::Make(sigma, sigma));
287
288 fDropShadow->setMode(fShadowOnly ? sksg::DropShadowImageFilter::Mode::kShadowOnly
289 : sksg::DropShadowImageFilter::Mode::kShadowAndForeground);
290 }
291
GaussianBlurEffectAdapter(sk_sp<sksg::BlurImageFilter> blur)292 GaussianBlurEffectAdapter::GaussianBlurEffectAdapter(sk_sp<sksg::BlurImageFilter> blur)
293 : fBlur(std::move(blur)) {
294 SkASSERT(fBlur);
295 }
296
297 GaussianBlurEffectAdapter::~GaussianBlurEffectAdapter() = default;
298
apply()299 void GaussianBlurEffectAdapter::apply() {
300 static constexpr SkVector kDimensionsMap[] = {
301 { 1, 1 }, // 1 -> horizontal and vertical
302 { 1, 0 }, // 2 -> horizontal
303 { 0, 1 }, // 3 -> vertical
304 };
305
306 const auto dim_index = SkTPin<size_t>(static_cast<size_t>(fDimensions),
307 1, SK_ARRAY_COUNT(kDimensionsMap)) - 1;
308
309 // Close enough to AE.
310 static constexpr SkScalar kBlurrinessToSigmaFactor = 0.3f;
311 const auto sigma = fBlurriness * kBlurrinessToSigmaFactor;
312
313 fBlur->setSigma({ sigma * kDimensionsMap[dim_index].x(),
314 sigma * kDimensionsMap[dim_index].y() });
315
316 static constexpr SkBlurImageFilter::TileMode kRepeatEdgeMap[] = {
317 SkBlurImageFilter::kClampToBlack_TileMode, // 0 -> repeat edge pixels: off
318 SkBlurImageFilter:: kClamp_TileMode, // 1 -> repeat edge pixels: on
319 };
320
321 const auto repeat_index = SkTPin<size_t>(static_cast<size_t>(fRepeatEdge),
322 0, SK_ARRAY_COUNT(kRepeatEdgeMap) - 1);
323 fBlur->setTileMode(kRepeatEdgeMap[repeat_index]);
324 }
325
TextAdapter(sk_sp<sksg::Group> root)326 TextAdapter::TextAdapter(sk_sp<sksg::Group> root)
327 : fRoot(std::move(root))
328 , fTextNode(sksg::TextBlob::Make())
329 , fFillColor(sksg::Color::Make(SK_ColorTRANSPARENT))
330 , fStrokeColor(sksg::Color::Make(SK_ColorTRANSPARENT))
331 , fFillNode(sksg::Draw::Make(fTextNode, fFillColor))
332 , fStrokeNode(sksg::Draw::Make(fTextNode, fStrokeColor))
333 , fHadFill(false)
334 , fHadStroke(false) {
335 // Build a SG fragment with the following general format:
336 //
337 // [Group]
338 // [Draw]
339 // [FillPaint]
340 // [Text]*
341 // [Draw]
342 // [StrokePaint]
343 // [Text]*
344 //
345 // * where the text node is shared
346
347 fFillColor->setAntiAlias(true);
348 fStrokeColor->setAntiAlias(true);
349 fStrokeColor->setStyle(SkPaint::kStroke_Style);
350 }
351
352 TextAdapter::~TextAdapter() = default;
353
makeBlob() const354 sk_sp<SkTextBlob> TextAdapter::makeBlob() const {
355 SkFont font(fText.fTypeface, fText.fTextSize);
356 font.setHinting(kNo_SkFontHinting);
357 font.setSubpixel(true);
358 font.setEdging(SkFont::Edging::kAntiAlias);
359
360 // Helper for interfacing with SkShaper: buffers shaper-fed runs and performs
361 // per-line position adjustments (for external line breaking, horizontal alignment, etc).
362 class BlobMaker final : public SkShaper::RunHandler {
363 public:
364 BlobMaker(SkTextUtils::Align align)
365 : fAlignFactor(AlignFactor(align)) {}
366
367 Buffer newRunBuffer(const RunInfo& info, const SkFont& font, int glyphCount,
368 SkSpan<const char> utf8) override {
369 fPendingLineAdvance += info.fAdvance;
370
371 auto& run = fPendingLineRuns.emplace_back(font, info, glyphCount);
372
373 return {
374 run.fGlyphs .data(),
375 run.fPositions.data(),
376 nullptr,
377 };
378 }
379
380 void commitRun() override { }
381
382 void commitLine() override {
383 SkScalar line_spacing = 0;
384
385 for (const auto& run : fPendingLineRuns) {
386 const auto runSize = run.size();
387 const auto& blobBuffer = fBuilder.allocRunPos(run.fFont, SkToInt(runSize));
388
389 sk_careful_memcpy(blobBuffer.glyphs,
390 run.fGlyphs.data(),
391 runSize * sizeof(SkGlyphID));
392
393 // For each buffered line, perform the following position adjustments:
394 // 1) horizontal alignment
395 // 2) vertical advance (based on line number/offset)
396 // 3) baseline/ascent adjustment
397 const auto offset = SkVector::Make(fAlignFactor * fPendingLineAdvance.x(),
398 fPendingLineVOffset + run.fInfo.fAscent);
399 for (size_t i = 0; i < runSize; ++i) {
400 blobBuffer.points()[i] = run.fPositions[SkToInt(i)] + offset;
401 }
402
403 line_spacing = SkTMax(line_spacing,
404 run.fInfo.fDescent - run.fInfo.fAscent + run.fInfo.fLeading);
405 }
406
407 fPendingLineRuns.reset();
408 fPendingLineVOffset += line_spacing;
409 fPendingLineAdvance = { 0, 0 };
410 }
411
412 sk_sp<SkTextBlob> makeBlob() {
413 return fBuilder.make();
414 }
415
416 private:
417 static float AlignFactor(SkTextUtils::Align align) {
418 switch (align) {
419 case SkTextUtils::kLeft_Align: return 0.0f;
420 case SkTextUtils::kCenter_Align: return -0.5f;
421 case SkTextUtils::kRight_Align: return -1.0f;
422 }
423 return 0.0f; // go home, msvc...
424 }
425
426 struct Run {
427 SkFont fFont;
428 SkShaper::RunHandler::RunInfo fInfo;
429 SkSTArray<128, SkGlyphID, true> fGlyphs;
430 SkSTArray<128, SkPoint , true> fPositions;
431
432 Run(const SkFont& font, const SkShaper::RunHandler::RunInfo& info, int count)
433 : fFont(font)
434 , fInfo(info)
435 , fGlyphs (count)
436 , fPositions(count) {
437 fGlyphs .push_back_n(count);
438 fPositions.push_back_n(count);
439 }
440
441 size_t size() const {
442 SkASSERT(fGlyphs.size() == fPositions.size());
443 return fGlyphs.size();
444 }
445 };
446
447 const float fAlignFactor;
448
449 SkTextBlobBuilder fBuilder;
450 SkSTArray<2, Run, false> fPendingLineRuns;
451 SkScalar fPendingLineVOffset = 0;
452 SkVector fPendingLineAdvance = { 0, 0 };
453 };
454
455 BlobMaker blobMaker(fText.fAlign);
456
457 const auto& push_line = [&](const char* start, const char* end) {
458 std::unique_ptr<SkShaper> shaper = SkShaper::Make();
459 if (!shaper) {
460 return;
461 }
462
463 shaper->shape(&blobMaker, font, start, SkToSizeT(end - start), true, { 0, 0 }, SK_ScalarMax);
464 };
465
466 const auto& is_line_break = [](SkUnichar uch) {
467 // TODO: other explicit breaks?
468 return uch == '\r';
469 };
470
471 const char* ptr = fText.fText.c_str();
472 const char* line_start = ptr;
473 const char* end = ptr + fText.fText.size();
474
475 while (ptr < end) {
476 if (is_line_break(SkUTF::NextUTF8(&ptr, end))) {
477 push_line(line_start, ptr - 1);
478 line_start = ptr;
479 }
480 }
481 push_line(line_start, ptr);
482
483 return blobMaker.makeBlob();
484 }
485
apply()486 void TextAdapter::apply() {
487 fTextNode->setBlob(this->makeBlob());
488 fFillColor->setColor(fText.fFillColor);
489 fStrokeColor->setColor(fText.fStrokeColor);
490 fStrokeColor->setStrokeWidth(fText.fStrokeWidth);
491
492 // Turn the state transition into a tri-state value:
493 // -1: detach node
494 // 0: no change
495 // 1: attach node
496 const auto fill_change = SkToInt(fText.fHasFill) - SkToInt(fHadFill);
497 const auto stroke_change = SkToInt(fText.fHasStroke) - SkToInt(fHadStroke);
498
499 // Sync SG topology.
500 if (fill_change || stroke_change) {
501 // This is trickier than it should be because sksg::Group only allows adding children
502 // in paint-order.
503 if (stroke_change < 0 || (fHadStroke && fill_change > 0)) {
504 fRoot->removeChild(fStrokeNode);
505 }
506
507 if (fill_change < 0) {
508 fRoot->removeChild(fFillNode);
509 } else if (fill_change > 0) {
510 fRoot->addChild(fFillNode);
511 }
512
513 if (stroke_change > 0 || (fHadStroke && fill_change > 0)) {
514 fRoot->addChild(fStrokeNode);
515 }
516 }
517
518 // Track current state.
519 fHadFill = fText.fHasFill;
520 fHadStroke = fText.fHasStroke;
521 }
522
523 } // namespace skottie
524