Add arc methods to path builder
Bug: skia:9000
Change-Id: I0a25c6f792f59230762651386da74e547b073930
Reviewed-on: https://skia-review.googlesource.com/c/skia/+/307558
Reviewed-by: Mike Reed <reed@google.com>
Commit-Queue: Mike Reed <reed@google.com>
diff --git a/gm/aarectmodes.cpp b/gm/aarectmodes.cpp
index cd14096..31e6ef6 100644
--- a/gm/aarectmodes.cpp
+++ b/gm/aarectmodes.cpp
@@ -14,6 +14,7 @@
#include "include/core/SkMatrix.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
#include "include/core/SkRefCnt.h"
@@ -44,7 +45,7 @@
0, 1, 1, 1, 4,
0, 1, 1, 1, 4
};
- SkPath path;
+ SkPathBuilder path;
SkPoint* ptPtr = pts;
for (size_t i = 0; i < sizeof(verbs); ++i) {
switch ((SkPath::Verb) verbs[i]) {
@@ -66,7 +67,7 @@
}
SkRect clip = {0, 130, 772, 531};
canvas->clipRect(clip);
- canvas->drawPath(path, paint);
+ canvas->drawPath(path.detach(), paint);
}
constexpr SkBlendMode gModes[] = {
diff --git a/gm/addarc.cpp b/gm/addarc.cpp
index eeed7d6..3f4d578 100644
--- a/gm/addarc.cpp
+++ b/gm/addarc.cpp
@@ -10,6 +10,7 @@
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathMeasure.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
@@ -215,7 +216,7 @@
//////////////////////
-static void html_canvas_arc(SkPath* path, SkScalar x, SkScalar y, SkScalar r, SkScalar start,
+static void html_canvas_arc(SkPathBuilder* path, SkScalar x, SkScalar y, SkScalar r, SkScalar start,
SkScalar end, bool ccw, bool callArcTo) {
SkRect bounds = { x - r, y - r, x + r, y + r };
SkScalar sweep = ccw ? end - start : start - end;
@@ -257,12 +258,12 @@
SkScalar startAngle = startAngles[i % SK_ARRAY_COUNT(startAngles)] * sign;
canvas->save();
for (size_t j = 0; j < SK_ARRAY_COUNT(sweepAngles); ++j) {
- SkPath path;
+ SkPathBuilder path;
path.moveTo(0, 2);
html_canvas_arc(&path, 18, 15, 10, startAngle, startAngle + (sweepAngles[j] * sign),
anticlockwise, true);
path.lineTo(0, 28);
- canvas->drawPath(path, paint);
+ canvas->drawPath(path.detach(), paint);
canvas->translate(30, 0);
}
canvas->restore();
@@ -286,7 +287,7 @@
SkScalar sweepAngle = 10.0f / outerRadius;
for (size_t i = 0; i < SK_ARRAY_COUNT(startAngles); ++i) {
- SkPath path;
+ SkPathBuilder path;
SkScalar endAngle = startAngles[i] + sweepAngle;
path.moveTo(centerX + innerRadius * sk_float_cos(startAngles[i]),
centerY + innerRadius * sk_float_sin(startAngles[i]));
@@ -301,7 +302,7 @@
html_canvas_arc(&path, centerX, outerRadius, innerRadius,
endAngle * 180 / SK_ScalarPI, startAngles[i] * 180 / SK_ScalarPI,
true, false);
- canvas->drawPath(path, paint);
+ canvas->drawPath(path.detach(), paint);
canvas->translate(20, 0);
}
}
diff --git a/gm/arcto.cpp b/gm/arcto.cpp
index 03942fe..20528c8 100644
--- a/gm/arcto.cpp
+++ b/gm/arcto.cpp
@@ -9,7 +9,7 @@
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathEffect.h"
#include "include/core/SkPathMeasure.h"
#include "include/core/SkRect.h"
@@ -72,23 +72,21 @@
paint.setColor(0xFF660000);
// canvas->scale(2, 2); // for testing on retina
SkRect oval = SkRect::MakeXYWH(100, 100, 100, 100);
- SkPath svgArc;
- for (int angle = 0; angle <= 45; angle += 45) {
- for (int oHeight = 2; oHeight >= 1; --oHeight) {
+ for (SkScalar angle = 0; angle <= 45; angle += 45) {
+ for (int oHeight = 2; oHeight >= 1; --oHeight) {
+ SkPathBuilder svgArc;
SkScalar ovalHeight = oval.height() / oHeight;
svgArc.moveTo(oval.fLeft, oval.fTop);
- svgArc.arcTo(oval.width() / 2, ovalHeight, SkIntToScalar(angle), SkPath::kSmall_ArcSize,
- SkPathDirection::kCW, oval.right(), oval.bottom());
- canvas->drawPath(svgArc, paint);
- svgArc.reset();
+ svgArc.arcTo({oval.width() / 2, ovalHeight}, angle, SkPathBuilder::kSmall_ArcSize,
+ SkPathDirection::kCW, {oval.right(), oval.bottom()});
+ canvas->drawPath(svgArc.detach(), paint);
svgArc.moveTo(oval.fLeft + 100, oval.fTop + 100);
- svgArc.arcTo(oval.width() / 2, ovalHeight, SkIntToScalar(angle), SkPath::kLarge_ArcSize,
- SkPathDirection::kCCW, oval.right(), oval.bottom() + 100);
- canvas->drawPath(svgArc, paint);
+ svgArc.arcTo({oval.width() / 2, ovalHeight}, angle, SkPathBuilder::kLarge_ArcSize,
+ SkPathDirection::kCCW, {oval.right(), oval.bottom() + 100});
+ canvas->drawPath(svgArc.detach(), paint);
oval.offset(50, 0);
- svgArc.reset();
}
}
@@ -105,22 +103,22 @@
};
int cIndex = 0;
for (const char* arcstr : arcstrs) {
- SkParsePath::FromSVGString(arcstr, &svgArc);
+ SkPath path;
+ SkParsePath::FromSVGString(arcstr, &path);
paint.setColor(colors[cIndex++]);
- canvas->drawPath(svgArc, paint);
+ canvas->drawPath(path, paint);
}
// test that zero length arcs still draw round cap
paint.setStrokeCap(SkPaint::kRound_Cap);
- SkPath path;
- path.moveTo(100, 100);
- path.arcTo(0, 0, 0, SkPath::kLarge_ArcSize, SkPathDirection::kCW, 200, 200);
- canvas->drawPath(path, paint);
+ SkPathBuilder path;
+ path.moveTo(100, 100)
+ .arcTo({0, 0}, 0, SkPathBuilder::kLarge_ArcSize, SkPathDirection::kCW, {200, 200});
+ canvas->drawPath(path.detach(), paint);
- path.reset();
- path.moveTo(200, 100);
- path.arcTo(80, 80, 0, SkPath::kLarge_ArcSize, SkPathDirection::kCW, 200, 100);
- canvas->drawPath(path, paint);
+ path.moveTo(200, 100)
+ .arcTo({80, 80}, 0, SkPathBuilder::kLarge_ArcSize, SkPathDirection::kCW, {200, 100});
+ canvas->drawPath(path.detach(), paint);
}
enum {
@@ -200,7 +198,7 @@
DEF_SIMPLE_GM(bug593049, canvas, 300, 300) {
canvas->translate(111, 0);
- SkPath p;
+ SkPathBuilder p;
p.moveTo(-43.44464063610148f, 79.43535936389853f);
const SkScalar yOffset = 122.88f;
const SkScalar radius = 61.44f;
@@ -212,7 +210,7 @@
paint.setStrokeCap(SkPaint::kRound_Cap);
paint.setStrokeWidth(15.36f);
- canvas->drawPath(p, paint);
+ canvas->drawPath(p.detach(), paint);
}
DEF_SIMPLE_GM(bug583299, canvas, 300, 300) {
diff --git a/gm/complexclip3.cpp b/gm/complexclip3.cpp
index b6fea1f..3c160e0 100644
--- a/gm/complexclip3.cpp
+++ b/gm/complexclip3.cpp
@@ -11,7 +11,7 @@
#include "include/core/SkColor.h"
#include "include/core/SkFont.h"
#include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
@@ -45,21 +45,18 @@
SkISize onISize() override { return SkISize::Make(400, 950); }
void onDraw(SkCanvas* canvas) override {
- SkPath clipSimple;
- clipSimple.addCircle(SkIntToScalar(70), SkIntToScalar(50), SkIntToScalar(20));
+ SkPath clipSimple = SkPath::Circle(70, 50, 20);
SkRect r1 = { 10, 20, 70, 80 };
- SkPath clipComplex;
- clipComplex.moveTo(SkIntToScalar(40), SkIntToScalar(50));
- clipComplex.arcTo(r1, SkIntToScalar(30), SkIntToScalar(300), false);
- clipComplex.close();
+ SkPath clipComplex = SkPathBuilder().moveTo(40, 50)
+ .arcTo(r1, 30, 300, false)
+ .close()
+ .detach();
SkPath* firstClip = &clipSimple;
SkPath* secondClip = &clipComplex;
-
if (!fDoSimpleClipFirst) {
- using std::swap;
- swap(firstClip, secondClip);
+ std::swap(firstClip, secondClip);
}
SkPaint paint;
diff --git a/gm/conicpaths.cpp b/gm/conicpaths.cpp
index f3ceaea..f68edbd 100644
--- a/gm/conicpaths.cpp
+++ b/gm/conicpaths.cpp
@@ -156,9 +156,9 @@
paint.setAntiAlias(true);
paint.setStroke(true);
canvas->drawCircle(c, radius, paint);
- SkPath path;
- path.moveTo(288.88884710654133f, -280.26680862609f);
- path.arcTo(0, 0, -39.00216443306411f, 400.6058925796476f, radius);
+ SkPath path = SkPathBuilder().moveTo(288.88884710654133f, -280.26680862609f)
+ .arcTo({0, 0}, {-39.00216443306411f, 400.6058925796476f}, radius)
+ .detach();
paint.setColor(0xff007f00);
canvas->drawPath(path, paint);
}
diff --git a/gm/crbug_996140.cpp b/gm/crbug_996140.cpp
index d2d2b6d..1cc6097 100644
--- a/gm/crbug_996140.cpp
+++ b/gm/crbug_996140.cpp
@@ -9,7 +9,7 @@
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
/*
* Canvas example. Expected large blue stroked circle, white middle, small red circle.
@@ -43,7 +43,6 @@
canvas->translate(-800, -200);
// 3: ctx.beginPath();
- SkPath path;
// 4: ctx.scale(203.20, 203.20);
canvas->scale(s, s);
@@ -66,8 +65,10 @@
// 9: ctx.arc(19.221, 720-6.76,0.0295275590551181,0,2*Math.PI);
// This matches how Canvas prepares an arc(x, y, radius, 0, 2pi) call
SkRect boundingBox = SkRect::MakeLTRB(cx - radius, cy - radius, cx + radius, cy + radius);
- path.arcTo(boundingBox, 0, 180.f, false);
- path.arcTo(boundingBox, 180.f, 180.f, false);
+
+ auto path = SkPathBuilder().arcTo(boundingBox, 0, 180.f, false)
+ .arcTo(boundingBox, 180.f, 180.f, false)
+ .detach();
// 12: ctx.closePath();
// path.close();
diff --git a/gm/overstroke.cpp b/gm/overstroke.cpp
index 6352a94..ecac1bb 100644
--- a/gm/overstroke.cpp
+++ b/gm/overstroke.cpp
@@ -26,7 +26,7 @@
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
#include "include/core/SkPathMeasure.h"
#include "include/core/SkPoint.h"
#include "include/core/SkRect.h"
@@ -83,11 +83,7 @@
SkPath oval_path() {
SkRect oval = SkRect::MakeXYWH(0, -25, 100, 50);
- SkPath path;
- path.arcTo(oval, 0, 359, true);
- path.close();
-
- return path;
+ return SkPathBuilder().arcTo(oval, 0, 359, true).close().detach();
}
SkPath ribs_path(SkPath path, SkScalar radius) {
diff --git a/gm/patharcto.cpp b/gm/patharcto.cpp
index 1350033..d67033d 100644
--- a/gm/patharcto.cpp
+++ b/gm/patharcto.cpp
@@ -8,7 +8,7 @@
#include "gm/gm.h"
#include "include/core/SkCanvas.h"
#include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
// crbug.com/982968
// Intended to draw a curvy triangle.
@@ -18,25 +18,25 @@
// The fix was to use doubles for this part of the calc in SkPath::arcTo().
//
DEF_SIMPLE_GM(shallow_angle_path_arcto, canvas, 300, 300) {
- SkPath path;
+ SkPathBuilder path;
SkPaint paint;
paint.setStyle(SkPaint::kStroke_Style);
- path.moveTo(313.44189096331155f, 106.6009423589212f);
- path.arcTo(284.3113082008462f, 207.1407719157063f,
- 255.15053777129728f, 307.6718505416374f,
- 697212.0011054524f);
- path.lineTo(255.15053777129728f, 307.6718505416374f);
- path.arcTo(340.4737465981018f, 252.6907319346971f,
- 433.54333477716153f, 212.18116363345337f,
- 1251.2484277907251f);
- path.lineTo(433.54333477716153f, 212.18116363345337f);
- path.arcTo(350.19513833839466f, 185.89280014838369f,
- 313.44189096331155f, 106.6009423589212f,
+ path.moveTo(313.44189096331155f, 106.6009423589212f)
+ .arcTo({284.3113082008462f, 207.1407719157063f},
+ {255.15053777129728f, 307.6718505416374f},
+ 697212.0011054524f)
+ .lineTo(255.15053777129728f, 307.6718505416374f)
+ .arcTo({340.4737465981018f, 252.6907319346971f},
+ {433.54333477716153f, 212.18116363345337f},
+ 1251.2484277907251f)
+ .lineTo(433.54333477716153f, 212.18116363345337f)
+ .arcTo({350.19513833839466f, 185.89280014838369f},
+ {313.44189096331155f, 106.6009423589212f},
198.03116885327813f);
canvas->translate(-200, -50);
- canvas->drawPath(path, paint);
+ canvas->drawPath(path.detach(), paint);
};
#include "include/utils/SkParsePath.h"
diff --git a/gm/pathfill.cpp b/gm/pathfill.cpp
index 9d908cb..17244c5 100644
--- a/gm/pathfill.cpp
+++ b/gm/pathfill.cpp
@@ -9,7 +9,7 @@
#include "include/core/SkCanvas.h"
#include "include/core/SkColor.h"
#include "include/core/SkPaint.h"
-#include "include/core/SkPath.h"
+#include "include/core/SkPathBuilder.h"
#include "include/core/SkRect.h"
#include "include/core/SkScalar.h"
#include "include/core/SkSize.h"
@@ -668,7 +668,7 @@
p.setStyle(SkPaint::kStroke_Style);
p.setStrokeWidth(2);
- SkPath path;
+ SkPathBuilder path;
SkPoint pts[] = { {20, 20}, {100, 20}, {100, 60}, {130, 150}, {180, 160} };
SkScalar radius = 60;
path.moveTo(pts[0]);
@@ -676,5 +676,5 @@
path.lineTo(pts[2]);
path.close();
path.arcTo(pts[3], pts[4], radius);
- canvas->drawPath(path, p);
+ canvas->drawPath(path.detach(), p);
}
diff --git a/include/core/SkPathBuilder.h b/include/core/SkPathBuilder.h
index 86e48f9..029f851 100644
--- a/include/core/SkPathBuilder.h
+++ b/include/core/SkPathBuilder.h
@@ -73,7 +73,95 @@
return this->rCubicTo({x1, y1}, {x2, y2}, {x3, y3});
}
- // Add a closed contour
+ // Arcs
+
+ /** Appends arc to the builder. Arc added is part of ellipse
+ bounded by oval, from startAngle through sweepAngle. Both startAngle and
+ sweepAngle are measured in degrees, where zero degrees is aligned with the
+ positive x-axis, and positive sweeps extends arc clockwise.
+
+ arcTo() adds line connecting the builder's last point to initial arc point if forceMoveTo
+ is false and the builder is not empty. Otherwise, added contour begins with first point
+ of arc. Angles greater than -360 and less than 360 are treated modulo 360.
+
+ @param oval bounds of ellipse containing arc
+ @param startAngleDeg starting angle of arc in degrees
+ @param sweepAngleDeg sweep, in degrees. Positive is clockwise; treated modulo 360
+ @param forceMoveTo true to start a new contour with arc
+ @return reference to the builder
+ */
+ SkPathBuilder& arcTo(const SkRect& oval, SkScalar startAngleDeg, SkScalar sweepAngleDeg,
+ bool forceMoveTo);
+
+ /** Appends arc to SkPath, after appending line if needed. Arc is implemented by conic
+ weighted to describe part of circle. Arc is contained by tangent from
+ last SkPath point to p1, and tangent from p1 to p2. Arc
+ is part of circle sized to radius, positioned so it touches both tangent lines.
+
+ If last SkPath SkPoint does not start arc, arcTo() appends connecting line to SkPath.
+ The length of vector from p1 to p2 does not affect arc.
+
+ Arc sweep is always less than 180 degrees. If radius is zero, or if
+ tangents are nearly parallel, arcTo() appends line from last SkPath SkPoint to p1.
+
+ arcTo() appends at most one line and one conic.
+ arcTo() implements the functionality of PostScript arct and HTML Canvas arcTo.
+
+ @param p1 SkPoint common to pair of tangents
+ @param p2 end of second tangent
+ @param radius distance from arc to circle center
+ @return reference to SkPath
+ */
+ SkPathBuilder& arcTo(SkPoint p1, SkPoint p2, SkScalar radius);
+
+ enum ArcSize {
+ kSmall_ArcSize, //!< smaller of arc pair
+ kLarge_ArcSize, //!< larger of arc pair
+ };
+
+ /** Appends arc to SkPath. Arc is implemented by one or more conic weighted to describe
+ part of oval with radii (r.fX, r.fY) rotated by xAxisRotate degrees. Arc curves
+ from last SkPath SkPoint to (xy.fX, xy.fY), choosing one of four possible routes:
+ clockwise or counterclockwise,
+ and smaller or larger.
+
+ Arc sweep is always less than 360 degrees. arcTo() appends line to xy if either
+ radii are zero, or if last SkPath SkPoint equals (xy.fX, xy.fY). arcTo() scales radii r to
+ fit last SkPath SkPoint and xy if both are greater than zero but too small to describe
+ an arc.
+
+ arcTo() appends up to four conic curves.
+ arcTo() implements the functionality of SVG arc, although SVG sweep-flag value is
+ opposite the integer value of sweep; SVG sweep-flag uses 1 for clockwise, while
+ kCW_Direction cast to int is zero.
+
+ @param r radii on axes before x-axis rotation
+ @param xAxisRotate x-axis rotation in degrees; positive values are clockwise
+ @param largeArc chooses smaller or larger arc
+ @param sweep chooses clockwise or counterclockwise arc
+ @param xy end of arc
+ @return reference to SkPath
+ */
+ SkPathBuilder& arcTo(SkPoint r, SkScalar xAxisRotate, ArcSize largeArc, SkPathDirection sweep,
+ SkPoint xy);
+
+ /** Appends arc to the builder, as the start of new contour. Arc added is part of ellipse
+ bounded by oval, from startAngle through sweepAngle. Both startAngle and
+ sweepAngle are measured in degrees, where zero degrees is aligned with the
+ positive x-axis, and positive sweeps extends arc clockwise.
+
+ If sweepAngle <= -360, or sweepAngle >= 360; and startAngle modulo 90 is nearly
+ zero, append oval instead of arc. Otherwise, sweepAngle values are treated
+ modulo 360, and arc may or may not draw depending on numeric rounding.
+
+ @param oval bounds of ellipse containing arc
+ @param startAngleDeg starting angle of arc in degrees
+ @param sweepAngleDeg sweep, in degrees. Positive is clockwise; treated modulo 360
+ @return reference to this builder
+ */
+ SkPathBuilder& addArc(const SkRect& oval, SkScalar startAngleDeg, SkScalar sweepAngleDeg);
+
+ // Add a new contour
SkPathBuilder& addRect(const SkRect&, SkPathDirection, unsigned startIndex);
SkPathBuilder& addOval(const SkRect&, SkPathDirection, unsigned startIndex);
diff --git a/include/private/SkTDArray.h b/include/private/SkTDArray.h
index 9d475f6..6ce7170 100644
--- a/include/private/SkTDArray.h
+++ b/include/private/SkTDArray.h
@@ -125,6 +125,8 @@
return (*this)[index];
}
+ const T& back() const { SkASSERT(fCount > 0); return fArray[fCount-1]; }
+ T& back() { SkASSERT(fCount > 0); return fArray[fCount-1]; }
void reset() {
if (fArray) {
diff --git a/src/core/SkPathBuilder.cpp b/src/core/SkPathBuilder.cpp
index 134f356..cc2208b 100644
--- a/src/core/SkPathBuilder.cpp
+++ b/src/core/SkPathBuilder.cpp
@@ -9,6 +9,9 @@
#include "include/core/SkRRect.h"
#include "include/private/SkPathRef.h"
#include "include/private/SkSafe32.h"
+#include "src/core/SkGeometry.h"
+// need SkDVector
+#include "src/pathops/SkPathOpsPoint.h"
SkPathBuilder::SkPathBuilder() {
this->reset();
@@ -117,25 +120,24 @@
SkPathBuilder& SkPathBuilder::rLineTo(SkPoint p1) {
this->ensureMove();
- SkPoint base = fPts[fPts.count() - 1];
- return this->lineTo(base + p1);
+ return this->lineTo(fPts.back() + p1);
}
SkPathBuilder& SkPathBuilder::rQuadTo(SkPoint p1, SkPoint p2) {
this->ensureMove();
- SkPoint base = fPts[fPts.count() - 1];
+ SkPoint base = fPts.back();
return this->quadTo(base + p1, base + p2);
}
SkPathBuilder& SkPathBuilder::rConicTo(SkPoint p1, SkPoint p2, SkScalar w) {
this->ensureMove();
- SkPoint base = fPts[fPts.count() - 1];
+ SkPoint base = fPts.back();
return this->conicTo(base + p1, base + p2, w);
}
SkPathBuilder& SkPathBuilder::rCubicTo(SkPoint p1, SkPoint p2, SkPoint p3) {
this->ensureMove();
- SkPoint base = fPts[fPts.count() - 1];
+ SkPoint base = fPts.back();
return this->cubicTo(base + p1, base + p2, base + p3);
}
@@ -166,6 +168,346 @@
return path;
}
+///////////////////////////////////////////////////////////////////////////////////////////////////
+
+static bool arc_is_lone_point(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
+ SkPoint* pt) {
+ if (0 == sweepAngle && (0 == startAngle || SkIntToScalar(360) == startAngle)) {
+ // Chrome uses this path to move into and out of ovals. If not
+ // treated as a special case the moves can distort the oval's
+ // bounding box (and break the circle special case).
+ pt->set(oval.fRight, oval.centerY());
+ return true;
+ } else if (0 == oval.width() && 0 == oval.height()) {
+ // Chrome will sometimes create 0 radius round rects. Having degenerate
+ // quad segments in the path prevents the path from being recognized as
+ // a rect.
+ // TODO: optimizing the case where only one of width or height is zero
+ // should also be considered. This case, however, doesn't seem to be
+ // as common as the single point case.
+ pt->set(oval.fRight, oval.fTop);
+ return true;
+ }
+ return false;
+}
+
+// Return the unit vectors pointing at the start/stop points for the given start/sweep angles
+//
+static void angles_to_unit_vectors(SkScalar startAngle, SkScalar sweepAngle,
+ SkVector* startV, SkVector* stopV, SkRotationDirection* dir) {
+ SkScalar startRad = SkDegreesToRadians(startAngle),
+ stopRad = SkDegreesToRadians(startAngle + sweepAngle);
+
+ startV->fY = SkScalarSinSnapToZero(startRad);
+ startV->fX = SkScalarCosSnapToZero(startRad);
+ stopV->fY = SkScalarSinSnapToZero(stopRad);
+ stopV->fX = SkScalarCosSnapToZero(stopRad);
+
+ /* If the sweep angle is nearly (but less than) 360, then due to precision
+ loss in radians-conversion and/or sin/cos, we may end up with coincident
+ vectors, which will fool SkBuildQuadArc into doing nothing (bad) instead
+ of drawing a nearly complete circle (good).
+ e.g. canvas.drawArc(0, 359.99, ...)
+ -vs- canvas.drawArc(0, 359.9, ...)
+ We try to detect this edge case, and tweak the stop vector
+ */
+ if (*startV == *stopV) {
+ SkScalar sw = SkScalarAbs(sweepAngle);
+ if (sw < SkIntToScalar(360) && sw > SkIntToScalar(359)) {
+ // make a guess at a tiny angle (in radians) to tweak by
+ SkScalar deltaRad = SkScalarCopySign(SK_Scalar1/512, sweepAngle);
+ // not sure how much will be enough, so we use a loop
+ do {
+ stopRad -= deltaRad;
+ stopV->fY = SkScalarSinSnapToZero(stopRad);
+ stopV->fX = SkScalarCosSnapToZero(stopRad);
+ } while (*startV == *stopV);
+ }
+ }
+ *dir = sweepAngle > 0 ? kCW_SkRotationDirection : kCCW_SkRotationDirection;
+}
+
+/**
+ * If this returns 0, then the caller should just line-to the singlePt, else it should
+ * ignore singlePt and append the specified number of conics.
+ */
+static int build_arc_conics(const SkRect& oval, const SkVector& start, const SkVector& stop,
+ SkRotationDirection dir, SkConic conics[SkConic::kMaxConicsForArc],
+ SkPoint* singlePt) {
+ SkMatrix matrix;
+
+ matrix.setScale(SkScalarHalf(oval.width()), SkScalarHalf(oval.height()));
+ matrix.postTranslate(oval.centerX(), oval.centerY());
+
+ int count = SkConic::BuildUnitArc(start, stop, dir, &matrix, conics);
+ if (0 == count) {
+ matrix.mapXY(stop.x(), stop.y(), singlePt);
+ }
+ return count;
+}
+
+static bool nearly_equal(const SkPoint& a, const SkPoint& b) {
+ return SkScalarNearlyEqual(a.fX, b.fX)
+ && SkScalarNearlyEqual(a.fY, b.fY);
+}
+
+SkPathBuilder& SkPathBuilder::arcTo(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle,
+ bool forceMoveTo) {
+ if (oval.width() < 0 || oval.height() < 0) {
+ return *this;
+ }
+
+ if (fVerbs.count() == 0) {
+ forceMoveTo = true;
+ }
+
+ SkPoint lonePt;
+ if (arc_is_lone_point(oval, startAngle, sweepAngle, &lonePt)) {
+ return forceMoveTo ? this->moveTo(lonePt) : this->lineTo(lonePt);
+ }
+
+ SkVector startV, stopV;
+ SkRotationDirection dir;
+ angles_to_unit_vectors(startAngle, sweepAngle, &startV, &stopV, &dir);
+
+ SkPoint singlePt;
+
+ // Adds a move-to to 'pt' if forceMoveTo is true. Otherwise a lineTo unless we're sufficiently
+ // close to 'pt' currently. This prevents spurious lineTos when adding a series of contiguous
+ // arcs from the same oval.
+ auto addPt = [forceMoveTo, this](const SkPoint& pt) {
+ if (forceMoveTo) {
+ this->moveTo(pt);
+ } else if (!nearly_equal(fPts.back(), pt)) {
+ this->lineTo(pt);
+ }
+ };
+
+ // At this point, we know that the arc is not a lone point, but startV == stopV
+ // indicates that the sweepAngle is too small such that angles_to_unit_vectors
+ // cannot handle it.
+ if (startV == stopV) {
+ SkScalar endAngle = SkDegreesToRadians(startAngle + sweepAngle);
+ SkScalar radiusX = oval.width() / 2;
+ SkScalar radiusY = oval.height() / 2;
+ // We do not use SkScalar[Sin|Cos]SnapToZero here. When sin(startAngle) is 0 and sweepAngle
+ // is very small and radius is huge, the expected behavior here is to draw a line. But
+ // calling SkScalarSinSnapToZero will make sin(endAngle) be 0 which will then draw a dot.
+ singlePt.set(oval.centerX() + radiusX * SkScalarCos(endAngle),
+ oval.centerY() + radiusY * SkScalarSin(endAngle));
+ addPt(singlePt);
+ return *this;
+ }
+
+ SkConic conics[SkConic::kMaxConicsForArc];
+ int count = build_arc_conics(oval, startV, stopV, dir, conics, &singlePt);
+ if (count) {
+ this->incReserve(count * 2 + 1);
+ const SkPoint& pt = conics[0].fPts[0];
+ addPt(pt);
+ for (int i = 0; i < count; ++i) {
+ this->conicTo(conics[i].fPts[1], conics[i].fPts[2], conics[i].fW);
+ }
+ } else {
+ addPt(singlePt);
+ }
+ return *this;
+}
+
+SkPathBuilder& SkPathBuilder::addArc(const SkRect& oval, SkScalar startAngle, SkScalar sweepAngle) {
+ if (oval.isEmpty() || 0 == sweepAngle) {
+ return *this;
+ }
+
+ const SkScalar kFullCircleAngle = SkIntToScalar(360);
+
+ if (sweepAngle >= kFullCircleAngle || sweepAngle <= -kFullCircleAngle) {
+ // We can treat the arc as an oval if it begins at one of our legal starting positions.
+ // See SkPath::addOval() docs.
+ SkScalar startOver90 = startAngle / 90.f;
+ SkScalar startOver90I = SkScalarRoundToScalar(startOver90);
+ SkScalar error = startOver90 - startOver90I;
+ if (SkScalarNearlyEqual(error, 0)) {
+ // Index 1 is at startAngle == 0.
+ SkScalar startIndex = std::fmod(startOver90I + 1.f, 4.f);
+ startIndex = startIndex < 0 ? startIndex + 4.f : startIndex;
+ return this->addOval(oval, sweepAngle > 0 ? SkPathDirection::kCW : SkPathDirection::kCCW,
+ (unsigned) startIndex);
+ }
+ }
+ return this->arcTo(oval, startAngle, sweepAngle, true);
+}
+
+SkPathBuilder& SkPathBuilder::arcTo(SkPoint p1, SkPoint p2, SkScalar radius) {
+ this->ensureMove();
+
+ if (radius == 0) {
+ return this->lineTo(p1);
+ }
+
+ // need to know our prev pt so we can construct tangent vectors
+ SkPoint start = fPts.back();
+
+ // need double precision for these calcs.
+ SkDVector befored, afterd;
+ befored.set({p1.fX - start.fX, p1.fY - start.fY}).normalize();
+ afterd.set({p2.fX - p1.fX, p2.fY - p1.fY}).normalize();
+ double cosh = befored.dot(afterd);
+ double sinh = befored.cross(afterd);
+
+ if (!befored.isFinite() || !afterd.isFinite() || SkScalarNearlyZero(SkDoubleToScalar(sinh))) {
+ return this->lineTo(p1);
+ }
+
+ // safe to convert back to floats now
+ SkVector before = befored.asSkVector();
+ SkVector after = afterd.asSkVector();
+ SkScalar dist = SkScalarAbs(SkDoubleToScalar(radius * (1 - cosh) / sinh));
+ SkScalar xx = p1.fX - dist * before.fX;
+ SkScalar yy = p1.fY - dist * before.fY;
+ after.setLength(dist);
+ this->lineTo(xx, yy);
+ SkScalar weight = SkScalarSqrt(SkDoubleToScalar(SK_ScalarHalf + cosh * 0.5));
+ return this->conicTo(p1, p1 + after, weight);
+}
+
+// This converts the SVG arc to conics.
+// Partly adapted from Niko's code in kdelibs/kdecore/svgicons.
+// Then transcribed from webkit/chrome's SVGPathNormalizer::decomposeArcToCubic()
+// See also SVG implementation notes:
+// http://www.w3.org/TR/SVG/implnote.html#ArcConversionEndpointToCenter
+// Note that arcSweep bool value is flipped from the original implementation.
+SkPathBuilder& SkPathBuilder::arcTo(SkPoint rad, SkScalar angle, SkPathBuilder::ArcSize arcLarge,
+ SkPathDirection arcSweep, SkPoint endPt) {
+ this->ensureMove();
+
+ SkPoint srcPts[2] = { fPts.back(), endPt };
+
+ // If rx = 0 or ry = 0 then this arc is treated as a straight line segment (a "lineto")
+ // joining the endpoints.
+ // http://www.w3.org/TR/SVG/implnote.html#ArcOutOfRangeParameters
+ if (!rad.fX || !rad.fY) {
+ return this->lineTo(endPt);
+ }
+ // If the current point and target point for the arc are identical, it should be treated as a
+ // zero length path. This ensures continuity in animations.
+ if (srcPts[0] == srcPts[1]) {
+ return this->lineTo(endPt);
+ }
+ SkScalar rx = SkScalarAbs(rad.fX);
+ SkScalar ry = SkScalarAbs(rad.fY);
+ SkVector midPointDistance = srcPts[0] - srcPts[1];
+ midPointDistance *= 0.5f;
+
+ SkMatrix pointTransform;
+ pointTransform.setRotate(-angle);
+
+ SkPoint transformedMidPoint;
+ pointTransform.mapPoints(&transformedMidPoint, &midPointDistance, 1);
+ SkScalar squareRx = rx * rx;
+ SkScalar squareRy = ry * ry;
+ SkScalar squareX = transformedMidPoint.fX * transformedMidPoint.fX;
+ SkScalar squareY = transformedMidPoint.fY * transformedMidPoint.fY;
+
+ // Check if the radii are big enough to draw the arc, scale radii if not.
+ // http://www.w3.org/TR/SVG/implnote.html#ArcCorrectionOutOfRangeRadii
+ SkScalar radiiScale = squareX / squareRx + squareY / squareRy;
+ if (radiiScale > 1) {
+ radiiScale = SkScalarSqrt(radiiScale);
+ rx *= radiiScale;
+ ry *= radiiScale;
+ }
+
+ pointTransform.setScale(1 / rx, 1 / ry);
+ pointTransform.preRotate(-angle);
+
+ SkPoint unitPts[2];
+ pointTransform.mapPoints(unitPts, srcPts, (int) SK_ARRAY_COUNT(unitPts));
+ SkVector delta = unitPts[1] - unitPts[0];
+
+ SkScalar d = delta.fX * delta.fX + delta.fY * delta.fY;
+ SkScalar scaleFactorSquared = std::max(1 / d - 0.25f, 0.f);
+
+ SkScalar scaleFactor = SkScalarSqrt(scaleFactorSquared);
+ if ((arcSweep == SkPathDirection::kCCW) != SkToBool(arcLarge)) { // flipped from the original implementation
+ scaleFactor = -scaleFactor;
+ }
+ delta.scale(scaleFactor);
+ SkPoint centerPoint = unitPts[0] + unitPts[1];
+ centerPoint *= 0.5f;
+ centerPoint.offset(-delta.fY, delta.fX);
+ unitPts[0] -= centerPoint;
+ unitPts[1] -= centerPoint;
+ SkScalar theta1 = SkScalarATan2(unitPts[0].fY, unitPts[0].fX);
+ SkScalar theta2 = SkScalarATan2(unitPts[1].fY, unitPts[1].fX);
+ SkScalar thetaArc = theta2 - theta1;
+ if (thetaArc < 0 && (arcSweep == SkPathDirection::kCW)) { // arcSweep flipped from the original implementation
+ thetaArc += SK_ScalarPI * 2;
+ } else if (thetaArc > 0 && (arcSweep != SkPathDirection::kCW)) { // arcSweep flipped from the original implementation
+ thetaArc -= SK_ScalarPI * 2;
+ }
+
+ // Very tiny angles cause our subsequent math to go wonky (skbug.com/9272)
+ // so we do a quick check here. The precise tolerance amount is just made up.
+ // PI/million happens to fix the bug in 9272, but a larger value is probably
+ // ok too.
+ if (SkScalarAbs(thetaArc) < (SK_ScalarPI / (1000 * 1000))) {
+ return this->lineTo(endPt);
+ }
+
+ pointTransform.setRotate(angle);
+ pointTransform.preScale(rx, ry);
+
+ // the arc may be slightly bigger than 1/4 circle, so allow up to 1/3rd
+ int segments = SkScalarCeilToInt(SkScalarAbs(thetaArc / (2 * SK_ScalarPI / 3)));
+ SkScalar thetaWidth = thetaArc / segments;
+ SkScalar t = SkScalarTan(0.5f * thetaWidth);
+ if (!SkScalarIsFinite(t)) {
+ return *this;
+ }
+ SkScalar startTheta = theta1;
+ SkScalar w = SkScalarSqrt(SK_ScalarHalf + SkScalarCos(thetaWidth) * SK_ScalarHalf);
+ auto scalar_is_integer = [](SkScalar scalar) -> bool {
+ return scalar == SkScalarFloorToScalar(scalar);
+ };
+ bool expectIntegers = SkScalarNearlyZero(SK_ScalarPI/2 - SkScalarAbs(thetaWidth)) &&
+ scalar_is_integer(rx) && scalar_is_integer(ry) &&
+ scalar_is_integer(endPt.fX) && scalar_is_integer(endPt.fY);
+
+ for (int i = 0; i < segments; ++i) {
+ SkScalar endTheta = startTheta + thetaWidth,
+ sinEndTheta = SkScalarSinSnapToZero(endTheta),
+ cosEndTheta = SkScalarCosSnapToZero(endTheta);
+
+ unitPts[1].set(cosEndTheta, sinEndTheta);
+ unitPts[1] += centerPoint;
+ unitPts[0] = unitPts[1];
+ unitPts[0].offset(t * sinEndTheta, -t * cosEndTheta);
+ SkPoint mapped[2];
+ pointTransform.mapPoints(mapped, unitPts, (int) SK_ARRAY_COUNT(unitPts));
+ /*
+ Computing the arc width introduces rounding errors that cause arcs to start
+ outside their marks. A round rect may lose convexity as a result. If the input
+ values are on integers, place the conic on integers as well.
+ */
+ if (expectIntegers) {
+ for (SkPoint& point : mapped) {
+ point.fX = SkScalarRoundToScalar(point.fX);
+ point.fY = SkScalarRoundToScalar(point.fY);
+ }
+ }
+ this->conicTo(mapped[0], mapped[1], w);
+ startTheta = endTheta;
+ }
+
+#ifndef SK_LEGACY_PATH_ARCTO_ENDPOINT
+ // The final point should match the input point (by definition); replace it to
+ // ensure that rounding errors in the above math don't cause any problems.
+ fPts.back() = endPt;
+#endif
+ return *this;
+}
+
///////////////////////////////////////////////////////////////////////////////////////////
namespace {