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>
This commit is contained in:
Mike Reed 2020-08-03 11:02:20 -04:00 committed by Skia Commit-Bot
parent a52442ab76
commit cfb130c662
12 changed files with 503 additions and 77 deletions

View File

@ -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 @@ static void test4(SkCanvas* canvas) {
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 @@ static void test4(SkCanvas* canvas) {
}
SkRect clip = {0, 130, 772, 531};
canvas->clipRect(clip);
canvas->drawPath(path, paint);
canvas->drawPath(path.detach(), paint);
}
constexpr SkBlendMode gModes[] = {

View File

@ -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 @@ DEF_GM( return new FillCircleGM; )
//////////////////////
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 @@ DEF_SIMPLE_GM(manyarcs, canvas, 620, 330) {
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 @@ DEF_SIMPLE_GM(tinyanglearcs, canvas, 620, 330) {
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 @@ DEF_SIMPLE_GM(tinyanglearcs, canvas, 620, 330) {
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);
}
}

View File

@ -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 @@ DEF_SIMPLE_GM(arcto, canvas, 500, 600) {
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 @@ DEF_SIMPLE_GM(arcto, canvas, 500, 600) {
};
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(parsedpaths, canvas, kParsePathTestDimension, kParsePathTestDimens
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 @@ DEF_SIMPLE_GM(bug593049, canvas, 300, 300) {
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) {

View File

@ -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 @@ protected:
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;

View File

@ -156,9 +156,9 @@ DEF_SIMPLE_GM(arccirclegap, canvas, 250, 250) {
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);
}

View File

@ -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 @@ DEF_SIMPLE_GM_BG(crbug_996140, canvas, 300, 300, SK_ColorWHITE) {
canvas->translate(-800, -200);
// 3: ctx.beginPath();
SkPath path;
// 4: ctx.scale(203.20, 203.20);
canvas->scale(s, s);
@ -66,8 +65,10 @@ DEF_SIMPLE_GM_BG(crbug_996140, canvas, 300, 300, SK_ColorWHITE) {
// 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();

View File

@ -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 cubic_path() {
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) {

View File

@ -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"

View File

@ -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 @@ DEF_SIMPLE_GM(path_arcto_skbug_9077, canvas, 200, 200) {
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 @@ DEF_SIMPLE_GM(path_arcto_skbug_9077, canvas, 200, 200) {
path.lineTo(pts[2]);
path.close();
path.arcTo(pts[3], pts[4], radius);
canvas->drawPath(path, p);
canvas->drawPath(path.detach(), p);
}

View File

@ -73,7 +73,95 @@ public:
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);

View File

@ -125,6 +125,8 @@ public:
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) {

View File

@ -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::close() {
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 @@ SkPath SkPathBuilder::detach() {
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 {