1 /*
2 * Copyright 2015 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/SkPathBuilder.h"
9
10 #include "include/core/SkMatrix.h"
11 #include "include/core/SkRRect.h"
12 #include "include/private/SkPathRef.h"
13 #include "include/private/base/SkPathEnums.h"
14 #include "include/private/base/SkSafe32.h"
15 #include "src/base/SkVx.h"
16 #include "src/core/SkGeometry.h"
17 #include "src/core/SkPathPriv.h"
18
19 #include <algorithm>
20 #include <cmath>
21 #include <cstdint>
22 #include <cstring>
23 #include <iterator>
24 #include <utility>
25
SkPathBuilder()26 SkPathBuilder::SkPathBuilder() {
27 this->reset();
28 }
29
SkPathBuilder(SkPathFillType ft)30 SkPathBuilder::SkPathBuilder(SkPathFillType ft) {
31 this->reset();
32 fFillType = ft;
33 }
34
SkPathBuilder(const SkPath & src)35 SkPathBuilder::SkPathBuilder(const SkPath& src) {
36 *this = src;
37 }
38
~SkPathBuilder()39 SkPathBuilder::~SkPathBuilder() {
40 }
41
reset()42 SkPathBuilder& SkPathBuilder::reset() {
43 fPts.clear();
44 fVerbs.clear();
45 fConicWeights.clear();
46 fFillType = SkPathFillType::kWinding;
47 fIsVolatile = false;
48
49 // these are internal state
50
51 fSegmentMask = 0;
52 fLastMovePoint = {0, 0};
53 fLastMoveIndex = -1; // illegal
54 fNeedsMoveVerb = true;
55
56 return *this;
57 }
58
operator =(const SkPath & src)59 SkPathBuilder& SkPathBuilder::operator=(const SkPath& src) {
60 this->reset().setFillType(src.getFillType());
61
62 for (auto [verb, pts, w] : SkPathPriv::Iterate(src)) {
63 switch (verb) {
64 case SkPathVerb::kMove: this->moveTo(pts[0]); break;
65 case SkPathVerb::kLine: this->lineTo(pts[1]); break;
66 case SkPathVerb::kQuad: this->quadTo(pts[1], pts[2]); break;
67 case SkPathVerb::kConic: this->conicTo(pts[1], pts[2], w[0]); break;
68 case SkPathVerb::kCubic: this->cubicTo(pts[1], pts[2], pts[3]); break;
69 case SkPathVerb::kClose: this->close(); break;
70 }
71 }
72 return *this;
73 }
74
incReserve(int extraPtCount,int extraVbCount)75 void SkPathBuilder::incReserve(int extraPtCount, int extraVbCount) {
76 fPts.reserve_back( Sk32_sat_add(fPts.size(), extraPtCount));
77 fVerbs.reserve_back(Sk32_sat_add(fVerbs.size(), extraVbCount));
78 }
79
computeBounds() const80 SkRect SkPathBuilder::computeBounds() const {
81 SkRect bounds;
82 bounds.setBounds(fPts.begin(), fPts.size());
83 return bounds;
84 }
85
86 /*
87 * Some old behavior in SkPath -- should we keep it?
88 *
89 * After each edit (i.e. adding a verb)
90 this->setConvexityType(SkPathConvexity::kUnknown);
91 this->setFirstDirection(SkPathPriv::kUnknown_FirstDirection);
92 */
93
moveTo(SkPoint pt)94 SkPathBuilder& SkPathBuilder::moveTo(SkPoint pt) {
95 // only needed while SkPath is mutable
96 fLastMoveIndex = SkToInt(fPts.size());
97
98 fPts.push_back(pt);
99 fVerbs.push_back((uint8_t)SkPathVerb::kMove);
100
101 fLastMovePoint = pt;
102 fNeedsMoveVerb = false;
103 return *this;
104 }
105
lineTo(SkPoint pt)106 SkPathBuilder& SkPathBuilder::lineTo(SkPoint pt) {
107 this->ensureMove();
108
109 fPts.push_back(pt);
110 fVerbs.push_back((uint8_t)SkPathVerb::kLine);
111
112 fSegmentMask |= kLine_SkPathSegmentMask;
113 return *this;
114 }
115
quadTo(SkPoint pt1,SkPoint pt2)116 SkPathBuilder& SkPathBuilder::quadTo(SkPoint pt1, SkPoint pt2) {
117 this->ensureMove();
118
119 SkPoint* p = fPts.push_back_n(2);
120 p[0] = pt1;
121 p[1] = pt2;
122 fVerbs.push_back((uint8_t)SkPathVerb::kQuad);
123
124 fSegmentMask |= kQuad_SkPathSegmentMask;
125 return *this;
126 }
127
conicTo(SkPoint pt1,SkPoint pt2,SkScalar w)128 SkPathBuilder& SkPathBuilder::conicTo(SkPoint pt1, SkPoint pt2, SkScalar w) {
129 this->ensureMove();
130
131 SkPoint* p = fPts.push_back_n(2);
132 p[0] = pt1;
133 p[1] = pt2;
134 fVerbs.push_back((uint8_t)SkPathVerb::kConic);
135 fConicWeights.push_back(w);
136
137 fSegmentMask |= kConic_SkPathSegmentMask;
138 return *this;
139 }
140
cubicTo(SkPoint pt1,SkPoint pt2,SkPoint pt3)141 SkPathBuilder& SkPathBuilder::cubicTo(SkPoint pt1, SkPoint pt2, SkPoint pt3) {
142 this->ensureMove();
143
144 SkPoint* p = fPts.push_back_n(3);
145 p[0] = pt1;
146 p[1] = pt2;
147 p[2] = pt3;
148 fVerbs.push_back((uint8_t)SkPathVerb::kCubic);
149
150 fSegmentMask |= kCubic_SkPathSegmentMask;
151 return *this;
152 }
153
close()154 SkPathBuilder& SkPathBuilder::close() {
155 if (!fVerbs.empty()) {
156 this->ensureMove();
157
158 fVerbs.push_back((uint8_t)SkPathVerb::kClose);
159
160 // fLastMovePoint stays where it is -- the previous moveTo
161 fNeedsMoveVerb = true;
162 }
163 return *this;
164 }
165
166 ///////////////////////////////////////////////////////////////////////////////////////////
167
rLineTo(SkPoint p1)168 SkPathBuilder& SkPathBuilder::rLineTo(SkPoint p1) {
169 this->ensureMove();
170 return this->lineTo(fPts.back() + p1);
171 }
172
rQuadTo(SkPoint p1,SkPoint p2)173 SkPathBuilder& SkPathBuilder::rQuadTo(SkPoint p1, SkPoint p2) {
174 this->ensureMove();
175 SkPoint base = fPts.back();
176 return this->quadTo(base + p1, base + p2);
177 }
178
rConicTo(SkPoint p1,SkPoint p2,SkScalar w)179 SkPathBuilder& SkPathBuilder::rConicTo(SkPoint p1, SkPoint p2, SkScalar w) {
180 this->ensureMove();
181 SkPoint base = fPts.back();
182 return this->conicTo(base + p1, base + p2, w);
183 }
184
rCubicTo(SkPoint p1,SkPoint p2,SkPoint p3)185 SkPathBuilder& SkPathBuilder::rCubicTo(SkPoint p1, SkPoint p2, SkPoint p3) {
186 this->ensureMove();
187 SkPoint base = fPts.back();
188 return this->cubicTo(base + p1, base + p2, base + p3);
189 }
190
191 ///////////////////////////////////////////////////////////////////////////////////////////
192
make(sk_sp<SkPathRef> pr) const193 SkPath SkPathBuilder::make(sk_sp<SkPathRef> pr) const {
194 auto convexity = SkPathConvexity::kUnknown;
195 SkPathFirstDirection dir = SkPathFirstDirection::kUnknown;
196
197 switch (fIsA) {
198 case kIsA_Oval:
199 pr->setIsOval( true, fIsACCW, fIsAStart);
200 convexity = SkPathConvexity::kConvex;
201 dir = fIsACCW ? SkPathFirstDirection::kCCW : SkPathFirstDirection::kCW;
202 break;
203 case kIsA_RRect:
204 pr->setIsRRect(true, fIsACCW, fIsAStart);
205 convexity = SkPathConvexity::kConvex;
206 dir = fIsACCW ? SkPathFirstDirection::kCCW : SkPathFirstDirection::kCW;
207 break;
208 default: break;
209 }
210
211 // Wonder if we can combine convexity and dir internally...
212 // unknown, convex_cw, convex_ccw, concave
213 // Do we ever have direction w/o convexity, or viceversa (inside path)?
214 //
215 auto path = SkPath(std::move(pr), fFillType, fIsVolatile, convexity, dir);
216
217 // This hopefully can go away in the future when Paths are immutable,
218 // but if while they are still editable, we need to correctly set this.
219 const uint8_t* start = path.fPathRef->verbsBegin();
220 const uint8_t* stop = path.fPathRef->verbsEnd();
221 if (start < stop) {
222 SkASSERT(fLastMoveIndex >= 0);
223 // peek at the last verb, to know if our last contour is closed
224 const bool isClosed = (stop[-1] == (uint8_t)SkPathVerb::kClose);
225 path.fLastMoveToIndex = isClosed ? ~fLastMoveIndex : fLastMoveIndex;
226 }
227
228 return path;
229 }
230
snapshot() const231 SkPath SkPathBuilder::snapshot() const {
232 return this->make(sk_sp<SkPathRef>(new SkPathRef(fPts,
233 fVerbs,
234 fConicWeights,
235 fSegmentMask)));
236 }
237
detach()238 SkPath SkPathBuilder::detach() {
239 auto path = this->make(sk_sp<SkPathRef>(new SkPathRef(std::move(fPts),
240 std::move(fVerbs),
241 std::move(fConicWeights),
242 fSegmentMask)));
243 this->reset();
244 return path;
245 }
246
247 ///////////////////////////////////////////////////////////////////////////////////////////////////
248
arc_is_lone_point(const SkRect & oval,SkScalar startAngle,SkScalar sweepAngle,SkPoint * pt)249 static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
250 SkPoint* pt) {
251 if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
252 // Chrome uses this path to move into and out of ovals. If not
253 // treated as a special case the moves can distort the oval's
254 // bounding box (and break the circle special case).
255 pt->set(oval.fRight, oval.centerY());
256 return true;
257 } else if (0 == oval.width() && 0 == oval.height()) {
258 // Chrome will sometimes create 0 radius round rects. Having degenerate
259 // quad segments in the path prevents the path from being recognized as
260 // a rect.
261 // TODO: optimizing the case where only one of width or height is zero
262 // should also be considered. This case, however, doesn't seem to be
263 // as common as the single point case.
264 pt->set(oval.fRight, oval.fTop);
265 return true;
266 }
267 return false;
268 }
269
270 // Return the unit vectors pointing at the start/stop points for the given start/sweep angles
271 //
angles_to_unit_vectors(SkScalar startAngle,SkScalar sweepAngle,SkVector * startV,SkVector * stopV,SkRotationDirection * dir)272 static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
273 SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
274 SkScalar startRad = SkDegreesToRadians(startAngle),
275 stopRad = SkDegreesToRadians(startAngle + sweepAngle);
276
277 startV->fY = SkScalarSinSnapToZero(startRad);
278 startV->fX = SkScalarCosSnapToZero(startRad);
279 stopV->fY = SkScalarSinSnapToZero(stopRad);
280 stopV->fX = SkScalarCosSnapToZero(stopRad);
281
282 /* If the sweep angle is nearly (but less than) 360, then due to precision
283 loss in radians-conversion and/or sin/cos, we may end up with coincident
284 vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
285 of drawing a nearly complete circle (good).
286 e.g. canvas.drawArc(0, 359.99, ...)
287 -vs- canvas.drawArc(0, 359.9, ...)
288 We try to detect this edge case, and tweak the stop vector
289 */
290 if (*startV == *stopV) {
291 SkScalar sw = SkScalarAbs(sweepAngle);
292 if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
293 // make a guess at a tiny angle (in radians) to tweak by
294 SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
295 // not sure how much will be enough, so we use a loop
296 do {
297 stopRad -= deltaRad;
298 stopV->fY = SkScalarSinSnapToZero(stopRad);
299 stopV->fX = SkScalarCosSnapToZero(stopRad);
300 } while (*startV == *stopV);
301 }
302 }
303 *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
304 }
305
306 /**
307 * If this returns 0, then the caller should just line-to the singlePt, else it should
308 * ignore singlePt and append the specified number of conics.
309 */
build_arc_conics(const SkRect & oval,const SkVector & start,const SkVector & stop,SkRotationDirection dir,SkConic conics[SkConic::kMaxConicsForArc],SkPoint * singlePt)310 static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
311 SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
312 SkPoint* singlePt) {
313 SkMatrix matrix;
314
315 matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
316 matrix.postTranslate(oval.centerX(), oval.centerY());
317
318 int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
319 if (0 == count) {
320 matrix.mapXY(stop.x(), stop.y(), singlePt);
321 }
322 return count;
323 }
324
nearly_equal(const SkPoint & a,const SkPoint & b)325 static bool nearly_equal(const SkPoint& a, const SkPoint& b) {
326 return SkScalarNearlyEqual(a.fX, b.fX)
327 && SkScalarNearlyEqual(a.fY, b.fY);
328 }
329
arcTo(const SkRect & oval,SkScalar startAngle,SkScalar sweepAngle,bool forceMoveTo)330 SkPathBuilder& SkPathBuilder::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
331 bool forceMoveTo) {
332 if (oval.width() < 0 || oval.height() < 0) {
333 return *this;
334 }
335
336 if (fVerbs.empty()) {
337 forceMoveTo = true;
338 }
339
340 SkPoint lonePt;
341 if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
342 return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
343 }
344
345 SkVector startV, stopV;
346 SkRotationDirection dir;
347 angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
348
349 SkPoint singlePt;
350
351 // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
352 // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
353 // arcs from the same oval.
354 auto addPt = [forceMoveTo, this](const SkPoint& pt) {
355 if (forceMoveTo) {
356 this->moveTo(pt);
357 } else if (!nearly_equal(fPts.back(), pt)) {
358 this->lineTo(pt);
359 }
360 };
361
362 // At this point, we know that the arc is not a lone point, but startV == stopV
363 // indicates that the sweepAngle is too small such that angles_to_unit_vectors
364 // cannot handle it.
365 if (startV == stopV) {
366 SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
367 SkScalar radiusX = oval.width() / 2;
368 SkScalar radiusY = oval.height() / 2;
369 // We do not use SkScalar[Sin|Cos]SnapToZero here. When sin(startAngle) is 0 and sweepAngle
370 // is very small and radius is huge, the expected behavior here is to draw a line. But
371 // calling SkScalarSinSnapToZero will make sin(endAngle) be 0 which will then draw a dot.
372 singlePt.set(oval.centerX() + radiusX * SkScalarCos(endAngle),
373 oval.centerY() + radiusY * SkScalarSin(endAngle));
374 addPt(singlePt);
375 return *this;
376 }
377
378 SkConic conics[SkConic::kMaxConicsForArc];
379 int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
380 if (count) {
381 this->incReserve(count * 2 + 1);
382 const SkPoint& pt = conics[0].fPts[0];
383 addPt(pt);
384 for (int i = 0; i < count; ++i) {
385 this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
386 }
387 } else {
388 addPt(singlePt);
389 }
390 return *this;
391 }
392
addArc(const SkRect & oval,SkScalar startAngle,SkScalar sweepAngle)393 SkPathBuilder& SkPathBuilder::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
394 if (oval.isEmpty() || 0 == sweepAngle) {
395 return *this;
396 }
397
398 const SkScalar kFullCircleAngle = SkIntToScalar(360);
399
400 if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
401 // We can treat the arc as an oval if it begins at one of our legal starting positions.
402 // See SkPath::addOval() docs.
403 SkScalar startOver90 = startAngle / 90.f;
404 SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
405 SkScalar error = startOver90 - startOver90I;
406 if (SkScalarNearlyEqual(error, 0)) {
407 // Index 1 is at startAngle == 0.
408 SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
409 startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
410 return this->addOval(oval, sweepAngle > 0 ? SkPathDirection::kCW : SkPathDirection::kCCW,
411 (unsigned) startIndex);
412 }
413 }
414 return this->arcTo(oval, startAngle, sweepAngle, true);
415 }
416
arcTo(SkPoint p1,SkPoint p2,SkScalar radius)417 SkPathBuilder& SkPathBuilder::arcTo(SkPoint p1, SkPoint p2, SkScalar radius) {
418 this->ensureMove();
419
420 if (radius == 0) {
421 return this->lineTo(p1);
422 }
423
424 // need to know our prev pt so we can construct tangent vectors
425 SkPoint start = fPts.back();
426
427 // need double precision for these calcs.
428 skvx::double2 befored = normalize(skvx::double2{p1.fX - start.fX, p1.fY - start.fY});
429 skvx::double2 afterd = normalize(skvx::double2{p2.fX - p1.fX, p2.fY - p1.fY});
430 double cosh = dot(befored, afterd);
431 double sinh = cross(befored, afterd);
432
433 // If the previous point equals the first point, befored will be denormalized.
434 // If the two points equal, afterd will be denormalized.
435 // If the second point equals the first point, sinh will be zero.
436 // In all these cases, we cannot construct an arc, so we construct a line to the first point.
437 if (!isfinite(befored) || !isfinite(afterd) || SkScalarNearlyZero(SkDoubleToScalar(sinh))) {
438 return this->lineTo(p1);
439 }
440
441 // safe to convert back to floats now
442 SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh));
443 SkScalar xx = p1.fX - dist * befored[0];
444 SkScalar yy = p1.fY - dist * befored[1];
445
446 SkVector after = SkVector::Make(afterd[0], afterd[1]);
447 after.setLength(dist);
448 this->lineTo(xx, yy);
449 SkScalar weight = SkScalarSqrt(SkDoubleToScalar(SK_ScalarHalf + cosh * 0.5));
450 return this->conicTo(p1, p1 + after, weight);
451 }
452
453 // This converts the SVG arc to conics.
454 // Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
455 // Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
456 // See also SVG implementation notes:
457 // http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
458 // Note that arcSweep bool value is flipped from the original implementation.
arcTo(SkPoint rad,SkScalar angle,SkPathBuilder::ArcSize arcLarge,SkPathDirection arcSweep,SkPoint endPt)459 SkPathBuilder& SkPathBuilder::arcTo(SkPoint rad, SkScalar angle, SkPathBuilder::ArcSize arcLarge,
460 SkPathDirection arcSweep, SkPoint endPt) {
461 this->ensureMove();
462
463 SkPoint srcPts[2] = { fPts.back(), endPt };
464
465 // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
466 // joining the endpoints.
467 // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
468 if (!rad.fX || !rad.fY) {
469 return this->lineTo(endPt);
470 }
471 // If the current point and target point for the arc are identical, it should be treated as a
472 // zero length path. This ensures continuity in animations.
473 if (srcPts[0] == srcPts[1]) {
474 return this->lineTo(endPt);
475 }
476 SkScalar rx = SkScalarAbs(rad.fX);
477 SkScalar ry = SkScalarAbs(rad.fY);
478 SkVector midPointDistance = srcPts[0] - srcPts[1];
479 midPointDistance *= 0.5f;
480
481 SkMatrix pointTransform;
482 pointTransform.setRotate(-angle);
483
484 SkPoint transformedMidPoint;
485 pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
486 SkScalar squareRx = rx * rx;
487 SkScalar squareRy = ry * ry;
488 SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
489 SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
490
491 // Check if the radii are big enough to draw the arc, scale radii if not.
492 // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
493 SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
494 if (radiiScale > 1) {
495 radiiScale = SkScalarSqrt(radiiScale);
496 rx *= radiiScale;
497 ry *= radiiScale;
498 }
499
500 pointTransform.setScale(1 / rx, 1 / ry);
501 pointTransform.preRotate(-angle);
502
503 SkPoint unitPts[2];
504 pointTransform.mapPoints(unitPts, srcPts, (int) std::size(unitPts));
505 SkVector delta = unitPts[1] - unitPts[0];
506
507 SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
508 SkScalar scaleFactorSquared = std::max(1 / d - 0.25f, 0.f);
509
510 SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
511 if ((arcSweep == SkPathDirection::kCCW) != SkToBool(arcLarge)) { // flipped from the original implementation
512 scaleFactor = -scaleFactor;
513 }
514 delta.scale(scaleFactor);
515 SkPoint centerPoint = unitPts[0] + unitPts[1];
516 centerPoint *= 0.5f;
517 centerPoint.offset(-delta.fY, delta.fX);
518 unitPts[0] -= centerPoint;
519 unitPts[1] -= centerPoint;
520 SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
521 SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
522 SkScalar thetaArc = theta2 - theta1;
523 if (thetaArc < 0 && (arcSweep == SkPathDirection::kCW)) { // arcSweep flipped from the original implementation
524 thetaArc += SK_ScalarPI * 2;
525 } else if (thetaArc > 0 && (arcSweep != SkPathDirection::kCW)) { // arcSweep flipped from the original implementation
526 thetaArc -= SK_ScalarPI * 2;
527 }
528
529 // Very tiny angles cause our subsequent math to go wonky (skbug.com/9272)
530 // so we do a quick check here. The precise tolerance amount is just made up.
531 // PI/million happens to fix the bug in 9272, but a larger value is probably
532 // ok too.
533 if (SkScalarAbs(thetaArc) < (SK_ScalarPI / (1000 * 1000))) {
534 return this->lineTo(endPt);
535 }
536
537 pointTransform.setRotate(angle);
538 pointTransform.preScale(rx, ry);
539
540 // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
541 int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
542 SkScalar thetaWidth = thetaArc / segments;
543 SkScalar t = SkScalarTan(0.5f * thetaWidth);
544 if (!SkScalarIsFinite(t)) {
545 return *this;
546 }
547 SkScalar startTheta = theta1;
548 SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
549 auto scalar_is_integer = [](SkScalar scalar) -> bool {
550 return scalar == SkScalarFloorToScalar(scalar);
551 };
552 bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
553 scalar_is_integer(rx) && scalar_is_integer(ry) &&
554 scalar_is_integer(endPt.fX) && scalar_is_integer(endPt.fY);
555
556 for (int i = 0; i < segments; ++i) {
557 SkScalar endTheta = startTheta + thetaWidth,
558 sinEndTheta = SkScalarSinSnapToZero(endTheta),
559 cosEndTheta = SkScalarCosSnapToZero(endTheta);
560
561 unitPts[1].set(cosEndTheta, sinEndTheta);
562 unitPts[1] += centerPoint;
563 unitPts[0] = unitPts[1];
564 unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
565 SkPoint mapped[2];
566 pointTransform.mapPoints(mapped, unitPts, (int) std::size(unitPts));
567 /*
568 Computing the arc width introduces rounding errors that cause arcs to start
569 outside their marks. A round rect may lose convexity as a result. If the input
570 values are on integers, place the conic on integers as well.
571 */
572 if (expectIntegers) {
573 for (SkPoint& point : mapped) {
574 point.fX = SkScalarRoundToScalar(point.fX);
575 point.fY = SkScalarRoundToScalar(point.fY);
576 }
577 }
578 this->conicTo(mapped[0], mapped[1], w);
579 startTheta = endTheta;
580 }
581
582 // The final point should match the input point (by definition); replace it to
583 // ensure that rounding errors in the above math don't cause any problems.
584 fPts.back() = endPt;
585 return *this;
586 }
587
588 ///////////////////////////////////////////////////////////////////////////////////////////
589
590 namespace {
591 template <unsigned N> class PointIterator {
592 public:
PointIterator(SkPathDirection dir,unsigned startIndex)593 PointIterator(SkPathDirection dir, unsigned startIndex)
594 : fCurrent(startIndex % N)
595 , fAdvance(dir == SkPathDirection::kCW ? 1 : N - 1)
596 {}
597
current() const598 const SkPoint& current() const {
599 SkASSERT(fCurrent < N);
600 return fPts[fCurrent];
601 }
602
next()603 const SkPoint& next() {
604 fCurrent = (fCurrent + fAdvance) % N;
605 return this->current();
606 }
607
608 protected:
609 SkPoint fPts[N];
610
611 private:
612 unsigned fCurrent;
613 unsigned fAdvance;
614 };
615
616 class RectPointIterator : public PointIterator<4> {
617 public:
RectPointIterator(const SkRect & rect,SkPathDirection dir,unsigned startIndex)618 RectPointIterator(const SkRect& rect, SkPathDirection dir, unsigned startIndex)
619 : PointIterator(dir, startIndex) {
620
621 fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
622 fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
623 fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
624 fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
625 }
626 };
627
628 class OvalPointIterator : public PointIterator<4> {
629 public:
OvalPointIterator(const SkRect & oval,SkPathDirection dir,unsigned startIndex)630 OvalPointIterator(const SkRect& oval, SkPathDirection dir, unsigned startIndex)
631 : PointIterator(dir, startIndex) {
632
633 const SkScalar cx = oval.centerX();
634 const SkScalar cy = oval.centerY();
635
636 fPts[0] = SkPoint::Make(cx, oval.fTop);
637 fPts[1] = SkPoint::Make(oval.fRight, cy);
638 fPts[2] = SkPoint::Make(cx, oval.fBottom);
639 fPts[3] = SkPoint::Make(oval.fLeft, cy);
640 }
641 };
642
643 class RRectPointIterator : public PointIterator<8> {
644 public:
RRectPointIterator(const SkRRect & rrect,SkPathDirection dir,unsigned startIndex)645 RRectPointIterator(const SkRRect& rrect, SkPathDirection dir, unsigned startIndex)
646 : PointIterator(dir, startIndex)
647 {
648 const SkRect& bounds = rrect.getBounds();
649 const SkScalar L = bounds.fLeft;
650 const SkScalar T = bounds.fTop;
651 const SkScalar R = bounds.fRight;
652 const SkScalar B = bounds.fBottom;
653
654 fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
655 fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
656 fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
657 fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
658 fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
659 fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
660 fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
661 fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
662 }
663 };
664 } // anonymous namespace
665
666
addRect(const SkRect & rect,SkPathDirection dir,unsigned index)667 SkPathBuilder& SkPathBuilder::addRect(const SkRect& rect, SkPathDirection dir, unsigned index) {
668 const int kPts = 4; // moveTo + 3 lines
669 const int kVerbs = 5; // moveTo + 3 lines + close
670 this->incReserve(kPts, kVerbs);
671
672 RectPointIterator iter(rect, dir, index);
673
674 this->moveTo(iter.current());
675 this->lineTo(iter.next());
676 this->lineTo(iter.next());
677 this->lineTo(iter.next());
678 return this->close();
679 }
680
addOval(const SkRect & oval,SkPathDirection dir,unsigned index)681 SkPathBuilder& SkPathBuilder::addOval(const SkRect& oval, SkPathDirection dir, unsigned index) {
682 const IsA prevIsA = fIsA;
683
684 const int kPts = 9; // moveTo + 4 conics(2 pts each)
685 const int kVerbs = 6; // moveTo + 4 conics + close
686 this->incReserve(kPts, kVerbs);
687
688 OvalPointIterator ovalIter(oval, dir, index);
689 RectPointIterator rectIter(oval, dir, index + (dir == SkPathDirection::kCW ? 0 : 1));
690
691 // The corner iterator pts are tracking "behind" the oval/radii pts.
692
693 this->moveTo(ovalIter.current());
694 for (unsigned i = 0; i < 4; ++i) {
695 this->conicTo(rectIter.next(), ovalIter.next(), SK_ScalarRoot2Over2);
696 }
697 this->close();
698
699 if (prevIsA == kIsA_JustMoves) {
700 fIsA = kIsA_Oval;
701 fIsACCW = (dir == SkPathDirection::kCCW);
702 fIsAStart = index % 4;
703 }
704 return *this;
705 }
706
addRRect(const SkRRect & rrect,SkPathDirection dir,unsigned index)707 SkPathBuilder& SkPathBuilder::addRRect(const SkRRect& rrect, SkPathDirection dir, unsigned index) {
708 const IsA prevIsA = fIsA;
709 const SkRect& bounds = rrect.getBounds();
710
711 if (rrect.isRect() || rrect.isEmpty()) {
712 // degenerate(rect) => radii points are collapsing
713 this->addRect(bounds, dir, (index + 1) / 2);
714 } else if (rrect.isOval()) {
715 // degenerate(oval) => line points are collapsing
716 this->addOval(bounds, dir, index / 2);
717 } else {
718 // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
719 const bool startsWithConic = ((index & 1) == (dir == SkPathDirection::kCW));
720 const SkScalar weight = SK_ScalarRoot2Over2;
721
722 const int kVerbs = startsWithConic
723 ? 9 // moveTo + 4x conicTo + 3x lineTo + close
724 : 10; // moveTo + 4x lineTo + 4x conicTo + close
725 this->incReserve(kVerbs);
726
727 RRectPointIterator rrectIter(rrect, dir, index);
728 // Corner iterator indices follow the collapsed radii model,
729 // adjusted such that the start pt is "behind" the radii start pt.
730 const unsigned rectStartIndex = index / 2 + (dir == SkPathDirection::kCW ? 0 : 1);
731 RectPointIterator rectIter(bounds, dir, rectStartIndex);
732
733 this->moveTo(rrectIter.current());
734 if (startsWithConic) {
735 for (unsigned i = 0; i < 3; ++i) {
736 this->conicTo(rectIter.next(), rrectIter.next(), weight);
737 this->lineTo(rrectIter.next());
738 }
739 this->conicTo(rectIter.next(), rrectIter.next(), weight);
740 // final lineTo handled by close().
741 } else {
742 for (unsigned i = 0; i < 4; ++i) {
743 this->lineTo(rrectIter.next());
744 this->conicTo(rectIter.next(), rrectIter.next(), weight);
745 }
746 }
747 this->close();
748 }
749
750 if (prevIsA == kIsA_JustMoves) {
751 fIsA = kIsA_RRect;
752 fIsACCW = (dir == SkPathDirection::kCCW);
753 fIsAStart = index % 8;
754 }
755 return *this;
756 }
757
addCircle(SkScalar x,SkScalar y,SkScalar r,SkPathDirection dir)758 SkPathBuilder& SkPathBuilder::addCircle(SkScalar x, SkScalar y, SkScalar r, SkPathDirection dir) {
759 if (r >= 0) {
760 this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
761 }
762 return *this;
763 }
764
addPolygon(const SkPoint pts[],int count,bool isClosed)765 SkPathBuilder& SkPathBuilder::addPolygon(const SkPoint pts[], int count, bool isClosed) {
766 if (count <= 0) {
767 return *this;
768 }
769
770 this->moveTo(pts[0]);
771 this->polylineTo(&pts[1], count - 1);
772 if (isClosed) {
773 this->close();
774 }
775 return *this;
776 }
777
polylineTo(const SkPoint pts[],int count)778 SkPathBuilder& SkPathBuilder::polylineTo(const SkPoint pts[], int count) {
779 if (count > 0) {
780 this->ensureMove();
781
782 this->incReserve(count, count);
783 memcpy(fPts.push_back_n(count), pts, count * sizeof(SkPoint));
784 memset(fVerbs.push_back_n(count), (uint8_t)SkPathVerb::kLine, count);
785 fSegmentMask |= kLine_SkPathSegmentMask;
786 }
787 return *this;
788 }
789
790 //////////////////////////////////////////////////////////////////////////////////////////////////
791
offset(SkScalar dx,SkScalar dy)792 SkPathBuilder& SkPathBuilder::offset(SkScalar dx, SkScalar dy) {
793 for (auto& p : fPts) {
794 p += {dx, dy};
795 }
796 return *this;
797 }
798
addPath(const SkPath & src)799 SkPathBuilder& SkPathBuilder::addPath(const SkPath& src) {
800 SkPath::RawIter iter(src);
801 SkPoint pts[4];
802 SkPath::Verb verb;
803
804 while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
805 switch (verb) {
806 case SkPath::kMove_Verb: this->moveTo (pts[0]); break;
807 case SkPath::kLine_Verb: this->lineTo (pts[1]); break;
808 case SkPath::kQuad_Verb: this->quadTo (pts[1], pts[2]); break;
809 case SkPath::kCubic_Verb: this->cubicTo(pts[1], pts[2], pts[3]); break;
810 case SkPath::kConic_Verb: this->conicTo(pts[1], pts[2], iter.conicWeight()); break;
811 case SkPath::kClose_Verb: this->close(); break;
812 case SkPath::kDone_Verb: SkUNREACHABLE;
813 }
814 }
815
816 return *this;
817 }
818
privateReverseAddPath(const SkPath & src)819 SkPathBuilder& SkPathBuilder::privateReverseAddPath(const SkPath& src) {
820
821 const uint8_t* verbsBegin = src.fPathRef->verbsBegin();
822 const uint8_t* verbs = src.fPathRef->verbsEnd();
823 const SkPoint* pts = src.fPathRef->pointsEnd();
824 const SkScalar* conicWeights = src.fPathRef->conicWeightsEnd();
825
826 bool needMove = true;
827 bool needClose = false;
828 while (verbs > verbsBegin) {
829 uint8_t v = *--verbs;
830 int n = SkPathPriv::PtsInVerb(v);
831
832 if (needMove) {
833 --pts;
834 this->moveTo(pts->fX, pts->fY);
835 needMove = false;
836 }
837 pts -= n;
838 switch ((SkPathVerb)v) {
839 case SkPathVerb::kMove:
840 if (needClose) {
841 this->close();
842 needClose = false;
843 }
844 needMove = true;
845 pts += 1; // so we see the point in "if (needMove)" above
846 break;
847 case SkPathVerb::kLine:
848 this->lineTo(pts[0]);
849 break;
850 case SkPathVerb::kQuad:
851 this->quadTo(pts[1], pts[0]);
852 break;
853 case SkPathVerb::kConic:
854 this->conicTo(pts[1], pts[0], *--conicWeights);
855 break;
856 case SkPathVerb::kCubic:
857 this->cubicTo(pts[2], pts[1], pts[0]);
858 break;
859 case SkPathVerb::kClose:
860 needClose = true;
861 break;
862 default:
863 SkDEBUGFAIL("unexpected verb");
864 }
865 }
866 return *this;
867 }
868