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:
parent
a52442ab76
commit
cfb130c662
@ -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[] = {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
46
gm/arcto.cpp
46
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 @@ 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) {
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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) {
|
||||
|
@ -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"
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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) {
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user