Parametric SkPath oval/rect/rrect starting point
Extend the SkPath API to allow specifying the contour starting point.
This will allow removing the explicit cubic code from Blink/Path.
BUG=chromium:315277
R=reed@google.com,caryclark@google.com
Review URL: https://codereview.chromium.org/1452203002
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 1326362..a3f5e13 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -840,36 +840,118 @@
///////////////////////////////////////////////////////////////////////////////
+namespace {
+
+template <unsigned N>
+class PointIterator {
+public:
+ PointIterator(SkPath::Direction dir, unsigned startIndex)
+ : fCurrent(startIndex % N)
+ , fAdvance(dir == SkPath::kCW_Direction ? 1 : N - 1) { }
+
+ const SkPoint& current() const {
+ SkASSERT(fCurrent < N);
+ return fPts[fCurrent];
+ }
+
+ const SkPoint& next() {
+ fCurrent = (fCurrent + fAdvance) % N;
+ return this->current();
+ }
+
+protected:
+ SkPoint fPts[N];
+
+private:
+ unsigned fCurrent;
+ unsigned fAdvance;
+};
+
+class RectPointIterator : public PointIterator<4> {
+public:
+ RectPointIterator(const SkRect& rect, SkPath::Direction dir, unsigned startIndex)
+ : PointIterator(dir, startIndex) {
+
+ fPts[0] = SkPoint::Make(rect.fLeft, rect.fTop);
+ fPts[1] = SkPoint::Make(rect.fRight, rect.fTop);
+ fPts[2] = SkPoint::Make(rect.fRight, rect.fBottom);
+ fPts[3] = SkPoint::Make(rect.fLeft, rect.fBottom);
+ }
+};
+
+class OvalPointIterator : public PointIterator<4> {
+public:
+ OvalPointIterator(const SkRect& oval, SkPath::Direction dir, unsigned startIndex)
+ : PointIterator(dir, startIndex) {
+
+ const SkScalar cx = oval.centerX();
+ const SkScalar cy = oval.centerY();
+
+ fPts[0] = SkPoint::Make(cx, oval.fTop);
+ fPts[1] = SkPoint::Make(oval.fRight, cy);
+ fPts[2] = SkPoint::Make(cx, oval.fBottom);
+ fPts[3] = SkPoint::Make(oval.fLeft, cy);
+ }
+};
+
+class RRectPointIterator : public PointIterator<8> {
+public:
+ RRectPointIterator(const SkRRect& rrect, SkPath::Direction dir, unsigned startIndex)
+ : PointIterator(dir, startIndex) {
+
+ const SkRect& bounds = rrect.getBounds();
+ const SkScalar L = bounds.fLeft;
+ const SkScalar T = bounds.fTop;
+ const SkScalar R = bounds.fRight;
+ const SkScalar B = bounds.fBottom;
+
+ fPts[0] = SkPoint::Make(L + rrect.radii(SkRRect::kUpperLeft_Corner).fX, T);
+ fPts[1] = SkPoint::Make(R - rrect.radii(SkRRect::kUpperRight_Corner).fX, T);
+ fPts[2] = SkPoint::Make(R, T + rrect.radii(SkRRect::kUpperRight_Corner).fY);
+ fPts[3] = SkPoint::Make(R, B - rrect.radii(SkRRect::kLowerRight_Corner).fY);
+ fPts[4] = SkPoint::Make(R - rrect.radii(SkRRect::kLowerRight_Corner).fX, B);
+ fPts[5] = SkPoint::Make(L + rrect.radii(SkRRect::kLowerLeft_Corner).fX, B);
+ fPts[6] = SkPoint::Make(L, B - rrect.radii(SkRRect::kLowerLeft_Corner).fY);
+ fPts[7] = SkPoint::Make(L, T + rrect.radii(SkRRect::kUpperLeft_Corner).fY);
+ }
+};
+
+} // anonymous namespace
+
static void assert_known_direction(int dir) {
SkASSERT(SkPath::kCW_Direction == dir || SkPath::kCCW_Direction == dir);
}
void SkPath::addRect(const SkRect& rect, Direction dir) {
- this->addRect(rect.fLeft, rect.fTop, rect.fRight, rect.fBottom, dir);
+ this->addRect(rect, dir, 0);
}
void SkPath::addRect(SkScalar left, SkScalar top, SkScalar right,
SkScalar bottom, Direction dir) {
+ this->addRect(SkRect::MakeLTRB(left, top, right, bottom), dir, 0);
+}
+
+void SkPath::addRect(const SkRect &rect, Direction dir, unsigned startIndex) {
assert_known_direction(dir);
fFirstDirection = this->hasOnlyMoveTos() ?
- (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
+ (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
SkAutoDisableDirectionCheck addc(this);
+ SkAutoPathBoundsUpdate apbu(this, rect);
- SkAutoPathBoundsUpdate apbu(this, left, top, right, bottom);
+ SkDEBUGCODE(int initialVerbCount = this->countVerbs());
- this->incReserve(5);
+ const int kVerbs = 5; // moveTo + 3x lineTo + close
+ this->incReserve(kVerbs);
- this->moveTo(left, top);
- if (dir == kCCW_Direction) {
- this->lineTo(left, bottom);
- this->lineTo(right, bottom);
- this->lineTo(right, top);
- } else {
- this->lineTo(right, top);
- this->lineTo(right, bottom);
- this->lineTo(left, bottom);
- }
+ RectPointIterator iter(rect, dir, startIndex);
+
+ this->moveTo(iter.current());
+ this->lineTo(iter.next());
+ this->lineTo(iter.next());
+ this->lineTo(iter.next());
this->close();
+
+ SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
}
void SkPath::addPoly(const SkPoint pts[], int count, bool close) {
@@ -979,64 +1061,68 @@
}
void SkPath::addRRect(const SkRRect& rrect, Direction dir) {
- assert_known_direction(dir);
+ // legacy start indices: 6 (CW) and 7(CCW)
+ this->addRRect(rrect, dir, dir == kCW_Direction ? 6 : 7);
+}
- if (rrect.isEmpty()) {
- return;
- }
+void SkPath::addRRect(const SkRRect &rrect, Direction dir, unsigned startIndex) {
+ assert_known_direction(dir);
- const SkRect& bounds = rrect.getBounds();
-
- if (rrect.isRect()) {
- this->addRect(bounds, dir);
- } else if (rrect.isOval()) {
- this->addOval(bounds, dir);
- } else {
- fFirstDirection = this->hasOnlyMoveTos() ?
- (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
-
- SkAutoPathBoundsUpdate apbu(this, bounds);
- SkAutoDisableDirectionCheck addc(this);
-
- const SkScalar L = bounds.fLeft;
- const SkScalar T = bounds.fTop;
- const SkScalar R = bounds.fRight;
- const SkScalar B = bounds.fBottom;
- const SkScalar W = SK_ScalarRoot2Over2;
-
- this->incReserve(13);
- if (kCW_Direction == dir) {
- this->moveTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
-
- this->lineTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
- this->conicTo(L, T, L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T, W);
-
- this->lineTo(R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T);
- this->conicTo(R, T, R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY, W);
-
- this->lineTo(R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY);
- this->conicTo(R, B, R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B, W);
-
- this->lineTo(L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B);
- this->conicTo(L, B, L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY, W);
- } else {
- this->moveTo(L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY);
-
- this->lineTo(L, B - rrect.fRadii[SkRRect::kLowerLeft_Corner].fY);
- this->conicTo(L, B, L + rrect.fRadii[SkRRect::kLowerLeft_Corner].fX, B, W);
-
- this->lineTo(R - rrect.fRadii[SkRRect::kLowerRight_Corner].fX, B);
- this->conicTo(R, B, R, B - rrect.fRadii[SkRRect::kLowerRight_Corner].fY, W);
-
- this->lineTo(R, T + rrect.fRadii[SkRRect::kUpperRight_Corner].fY);
- this->conicTo(R, T, R - rrect.fRadii[SkRRect::kUpperRight_Corner].fX, T, W);
-
- this->lineTo(L + rrect.fRadii[SkRRect::kUpperLeft_Corner].fX, T);
- this->conicTo(L, T, L, T + rrect.fRadii[SkRRect::kUpperLeft_Corner].fY, W);
+ if (rrect.isEmpty()) {
+ return;
}
- this->close();
- }
- SkDEBUGCODE(fPathRef->validate();)
+
+ const SkRect& bounds = rrect.getBounds();
+
+ if (rrect.isRect()) {
+ // degenerate(rect) => radii points are collapsing
+ this->addRect(bounds, dir, (startIndex + 1) / 2);
+ } else if (rrect.isOval()) {
+ // degenerate(oval) => line points are collapsing
+ this->addOval(bounds, dir, startIndex / 2);
+ } else {
+ fFirstDirection = this->hasOnlyMoveTos() ?
+ (SkPathPriv::FirstDirection)dir : SkPathPriv::kUnknown_FirstDirection;
+
+ SkAutoPathBoundsUpdate apbu(this, bounds);
+ SkAutoDisableDirectionCheck addc(this);
+
+ // we start with a conic on odd indices when moving CW vs. even indices when moving CCW
+ const bool startsWithConic = ((startIndex & 1) == (dir == kCW_Direction));
+ const SkScalar weight = SK_ScalarRoot2Over2;
+
+ SkDEBUGCODE(int initialVerbCount = this->countVerbs());
+ const int kVerbs = startsWithConic
+ ? 9 // moveTo + 4x conicTo + 3x lineTo + close
+ : 10; // moveTo + 4x lineTo + 4x conicTo + close
+ this->incReserve(kVerbs);
+
+ RRectPointIterator rrectIter(rrect, dir, startIndex);
+ // Corner iterator indices follow the collapsed radii model,
+ // adjusted such that the start pt is "behind" the radii start pt.
+ const unsigned rectStartIndex = startIndex / 2 + (dir == kCW_Direction ? 0 : 1);
+ RectPointIterator rectIter(bounds, dir, rectStartIndex);
+
+ this->moveTo(rrectIter.current());
+ if (startsWithConic) {
+ for (unsigned i = 0; i < 3; ++i) {
+ this->conicTo(rectIter.next(), rrectIter.next(), weight);
+ this->lineTo(rrectIter.next());
+ }
+ this->conicTo(rectIter.next(), rrectIter.next(), weight);
+ // final lineTo handled by close().
+ } else {
+ for (unsigned i = 0; i < 4; ++i) {
+ this->lineTo(rrectIter.next());
+ this->conicTo(rectIter.next(), rrectIter.next(), weight);
+ }
+ }
+ this->close();
+
+ SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
+ }
+
+ SkDEBUGCODE(fPathRef->validate();)
}
bool SkPath::hasOnlyMoveTos() const {
@@ -1072,6 +1158,11 @@
}
void SkPath::addOval(const SkRect& oval, Direction dir) {
+ // legacy start index: 1
+ this->addOval(oval, dir, 1);
+}
+
+void SkPath::addOval(const SkRect &oval, Direction dir, unsigned startPointIndex) {
assert_known_direction(dir);
/* If addOval() is called after previous moveTo(),
@@ -1088,32 +1179,25 @@
}
SkAutoDisableDirectionCheck addc(this);
-
SkAutoPathBoundsUpdate apbu(this, oval);
- const SkScalar L = oval.fLeft;
- const SkScalar T = oval.fTop;
- const SkScalar R = oval.fRight;
- const SkScalar B = oval.fBottom;
- const SkScalar cx = oval.centerX();
- const SkScalar cy = oval.centerY();
+ SkDEBUGCODE(int initialVerbCount = this->countVerbs());
+ const int kVerbs = 6; // moveTo + 4x conicTo + close
+ this->incReserve(kVerbs);
+
+ OvalPointIterator ovalIter(oval, dir, startPointIndex);
+ // The corner iterator pts are tracking "behind" the oval/radii pts.
+ RectPointIterator rectIter(oval, dir, startPointIndex + (dir == kCW_Direction ? 0 : 1));
const SkScalar weight = SK_ScalarRoot2Over2;
- this->incReserve(9); // move + 4 conics
- this->moveTo(R, cy);
- if (dir == kCCW_Direction) {
- this->conicTo(R, T, cx, T, weight);
- this->conicTo(L, T, L, cy, weight);
- this->conicTo(L, B, cx, B, weight);
- this->conicTo(R, B, R, cy, weight);
- } else {
- this->conicTo(R, B, cx, B, weight);
- this->conicTo(L, B, L, cy, weight);
- this->conicTo(L, T, cx, T, weight);
- this->conicTo(R, T, R, cy, weight);
+ this->moveTo(ovalIter.current());
+ for (unsigned i = 0; i < 4; ++i) {
+ this->conicTo(rectIter.next(), ovalIter.next(), weight);
}
this->close();
+ SkASSERT(this->countVerbs() == initialVerbCount + kVerbs);
+
SkPathRef::Editor ed(&fPathRef);
ed.setIsOval(isOval);
@@ -1121,9 +1205,7 @@
void SkPath::addCircle(SkScalar x, SkScalar y, SkScalar r, Direction dir) {
if (r > 0) {
- SkRect rect;
- rect.set(x - r, y - r, x + r, y + r);
- this->addOval(rect, dir);
+ this->addOval(SkRect::MakeLTRB(x - r, y - r, x + r, y + r), dir);
}
}