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