aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--gm/cubicpaths.cpp189
-rw-r--r--gm/degeneratesegments.cpp398
-rw-r--r--gm/linepaths.cpp181
-rw-r--r--gm/movepaths.cpp180
-rw-r--r--gm/quadpaths.cpp185
-rw-r--r--gyp/gmslides.gypi5
-rw-r--r--include/core/SkPath.h32
-rw-r--r--include/core/SkPoint.h7
-rw-r--r--src/core/SkPath.cpp155
-rw-r--r--src/core/SkStroke.cpp18
-rw-r--r--tests/PathTest.cpp145
11 files changed, 1448 insertions, 47 deletions
diff --git a/gm/cubicpaths.cpp b/gm/cubicpaths.cpp
new file mode 100644
index 0000000000..80e38ca6ab
--- /dev/null
+++ b/gm/cubicpaths.cpp
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+
+namespace skiagm {
+
+class CubicPathsGM : public GM {
+public:
+ CubicPathsGM() {}
+
+protected:
+ SkString onShortName() {
+ return SkString("cubicpaths");
+ }
+
+ SkISize onISize() { return make_isize(1800, 1110); }
+
+ void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
+ const SkRect& clip,SkPaint::Cap cap,
+ SkPaint::Style style, SkPath::FillType fill,
+ SkScalar strokeWidth) {
+ path.setFillType(fill);
+ SkPaint paint;
+ paint.setStrokeCap(cap);
+ paint.setStrokeWidth(strokeWidth);
+ paint.setColor(color);
+ paint.setStyle(style);
+ canvas->save();
+ canvas->clipRect(clip);
+ canvas->drawPath(path, paint);
+ canvas->restore();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ struct FillAndName {
+ SkPath::FillType fFill;
+ const char* fName;
+ };
+ static const FillAndName gFills[] = {
+ {SkPath::kWinding_FillType, "Winding"},
+ {SkPath::kEvenOdd_FillType, "Even / Odd"},
+ {SkPath::kInverseWinding_FillType, "Inverse Winding"},
+ {SkPath::kInverseEvenOdd_FillType, "Inverse Even / Odd"},
+ };
+ struct StyleAndName {
+ SkPaint::Style fStyle;
+ const char* fName;
+ };
+ static const StyleAndName gStyles[] = {
+ {SkPaint::kFill_Style, "Fill"},
+ {SkPaint::kStroke_Style, "Stroke"},
+ {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
+ };
+ struct CapAndName {
+ SkPaint::Cap fCap;
+ const char* fName;
+ };
+ static const CapAndName gCaps[] = {
+ {SkPaint::kButt_Cap, "Butt"},
+ {SkPaint::kRound_Cap, "Round"},
+ {SkPaint::kSquare_Cap, "Square"},
+ };
+ struct PathAndName {
+ SkPath fPath;
+ const char* fName;
+ };
+ PathAndName gPaths[4];
+ gPaths[0].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[0].fPath.cubicTo(50*SK_Scalar1, 15*SK_Scalar1,
+ 50*SK_Scalar1, 15*SK_Scalar1,
+ 50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[0].fName = "moveTo-zerocubic";
+ gPaths[1].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[1].fPath.cubicTo(50*SK_Scalar1, 15*SK_Scalar1,
+ 50*SK_Scalar1, 15*SK_Scalar1,
+ 50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[1].fPath.close();
+ gPaths[1].fName = "moveTo-zerocubic-close";
+ gPaths[2].fPath.moveTo(30*SK_Scalar1, 10*SK_Scalar1);
+ gPaths[2].fPath.cubicTo(40*SK_Scalar1, 20*SK_Scalar1,
+ 60*SK_Scalar1, 20*SK_Scalar1,
+ 70*SK_Scalar1, 10*SK_Scalar1);
+ gPaths[2].fName = "moveTo-cubic";
+ gPaths[3].fPath.moveTo(30*SK_Scalar1, 10*SK_Scalar1);
+ gPaths[3].fPath.cubicTo(40*SK_Scalar1, 20*SK_Scalar1,
+ 60*SK_Scalar1, 20*SK_Scalar1,
+ 70*SK_Scalar1, 10*SK_Scalar1);
+ gPaths[3].fPath.close();
+ gPaths[3].fName = "moveTo-cubic-close";
+
+ SkPaint titlePaint;
+ titlePaint.setColor(SK_ColorBLACK);
+ titlePaint.setAntiAlias(true);
+ titlePaint.setLCDRenderText(true);
+ titlePaint.setTextSize(15 * SK_Scalar1);
+ const char title[] = "Cubic Paths Drawn Into Rectangle Clips With "
+ "Indicated Style, Fill and Linecaps, "
+ "with random stroke widths";
+ canvas->drawText(title, strlen(title),
+ 20 * SK_Scalar1,
+ 20 * SK_Scalar1,
+ titlePaint);
+
+ SkRandom rand;
+ SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
+ canvas->save();
+ canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
+ canvas->save();
+ for (size_t path = 0; path < SK_ARRAY_COUNT(gPaths); ++path) {
+ if (0 < path) {
+ canvas->translate(0, (rect.height() + 60 * SK_Scalar1) * 3);
+ }
+ canvas->save();
+ for (size_t cap = 0; cap < SK_ARRAY_COUNT(gCaps); ++cap) {
+ if (0 < cap) {
+ canvas->translate((rect.width() + 40 * SK_Scalar1) * 4, 0);
+ }
+ canvas->save();
+ for (size_t style = 0; style < SK_ARRAY_COUNT(gStyles); ++style) {
+ if (0 < style) {
+ canvas->translate(0, rect.height() + 60 * SK_Scalar1);
+ }
+ canvas->save();
+ for (size_t fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) {
+ if (0 < fill) {
+ canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
+ }
+
+ SkColor color = 0xff007000;
+ this->drawPath(gPaths[path].fPath, canvas, color, rect,
+ gCaps[cap].fCap, gStyles[style].fStyle,
+ gFills[fill].fFill, SK_Scalar1*10);
+
+ SkPaint rectPaint;
+ rectPaint.setColor(SK_ColorBLACK);
+ rectPaint.setStyle(SkPaint::kStroke_Style);
+ rectPaint.setStrokeWidth(-1);
+ rectPaint.setAntiAlias(true);
+ canvas->drawRect(rect, rectPaint);
+
+ SkPaint labelPaint;
+ labelPaint.setColor(color);
+ labelPaint.setAntiAlias(true);
+ labelPaint.setLCDRenderText(true);
+ labelPaint.setTextSize(10 * SK_Scalar1);
+ canvas->drawText(gStyles[style].fName,
+ strlen(gStyles[style].fName),
+ 0, rect.height() + 12 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gFills[fill].fName,
+ strlen(gFills[fill].fName),
+ 0, rect.height() + 24 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gCaps[cap].fName,
+ strlen(gCaps[cap].fName),
+ 0, rect.height() + 36 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gPaths[path].fName,
+ strlen(gPaths[path].fName),
+ 0, rect.height() + 48 * SK_Scalar1,
+ labelPaint);
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ canvas->restore();
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new CubicPathsGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gm/degeneratesegments.cpp b/gm/degeneratesegments.cpp
new file mode 100644
index 0000000000..7288d953bf
--- /dev/null
+++ b/gm/degeneratesegments.cpp
@@ -0,0 +1,398 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+
+namespace skiagm {
+
+class DegenerateSegmentsGM : public GM {
+public:
+ DegenerateSegmentsGM() {}
+
+protected:
+ struct PathAndName {
+ SkPath fPath;
+ const char* fName1;
+ const char* fName2;
+ };
+
+ SkString onShortName() {
+ return SkString("degeneratesegments");
+ }
+
+ SkISize onISize() { return make_isize(1368, 1230); }
+
+ typedef SkPoint (*AddSegmentFunc)(SkPath&, SkPoint&);
+
+ // We need to use explicit commands here, instead of addPath, because we
+ // do not want the SkPath::Iter used in addPath to remove the degenerate
+ // segments before we send th epath off for drawing.
+ static SkPoint AddMove(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ path.moveTo(moveToPt);
+ return moveToPt;
+ }
+
+ static SkPoint AddMoveClose(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ path.moveTo(moveToPt);
+ path.close();
+ return moveToPt;
+ }
+
+ static SkPoint AddDegenLine(SkPath& path, SkPoint& startPt) {
+ path.lineTo(startPt);
+ return startPt;
+ }
+
+ static SkPoint AddMoveDegenLine(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ path.moveTo(moveToPt);
+ path.lineTo(moveToPt);
+ return moveToPt;
+ }
+
+ static SkPoint AddMoveDegenLineClose(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ path.moveTo(moveToPt);
+ path.lineTo(moveToPt);
+ path.close();
+ return moveToPt;
+ }
+
+ static SkPoint AddDegenQuad(SkPath& path, SkPoint& startPt) {
+ path.quadTo(startPt, startPt);
+ return startPt;
+ }
+
+ static SkPoint AddMoveDegenQuad(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ path.moveTo(moveToPt);
+ path.quadTo(moveToPt, moveToPt);
+ return moveToPt;
+ }
+
+ static SkPoint AddMoveDegenQuadClose(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ path.moveTo(moveToPt);
+ path.quadTo(moveToPt, moveToPt);
+ path.close();
+ return moveToPt;
+ }
+
+ static SkPoint AddDegenCubic(SkPath& path, SkPoint& startPt) {
+ path.cubicTo(startPt, startPt, startPt);
+ return startPt;
+ }
+
+ static SkPoint AddMoveDegenCubic(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ path.moveTo(moveToPt);
+ path.cubicTo(moveToPt, moveToPt, moveToPt);
+ return moveToPt;
+ }
+
+ static SkPoint AddMoveDegenCubicClose(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ path.moveTo(moveToPt);
+ path.cubicTo(moveToPt, moveToPt, moveToPt);
+ path.close();
+ return moveToPt;
+ }
+
+ static SkPoint AddClose(SkPath& path, SkPoint& startPt) {
+ path.close();
+ return startPt;
+ }
+
+ static SkPoint AddLine(SkPath& path, SkPoint& startPt) {
+ SkPoint endPt = startPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.lineTo(endPt);
+ return endPt;
+ }
+
+ static SkPoint AddMoveLine(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ SkPoint endPt = moveToPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.moveTo(moveToPt);
+ path.lineTo(endPt);
+ return endPt;
+ }
+
+ static SkPoint AddMoveLineClose(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ SkPoint endPt = moveToPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.moveTo(moveToPt);
+ path.lineTo(endPt);
+ path.close();
+ return endPt;
+ }
+
+ static SkPoint AddQuad(SkPath& path, SkPoint& startPt) {
+ SkPoint midPt = startPt + SkPoint::Make(20*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint endPt = startPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.quadTo(midPt, endPt);
+ return endPt;
+ }
+
+ static SkPoint AddMoveQuad(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ SkPoint midPt = moveToPt + SkPoint::Make(20*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint endPt = moveToPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.moveTo(moveToPt);
+ path.quadTo(midPt, endPt);
+ return endPt;
+ }
+
+ static SkPoint AddMoveQuadClose(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ SkPoint midPt = moveToPt + SkPoint::Make(20*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint endPt = moveToPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.moveTo(moveToPt);
+ path.quadTo(midPt, endPt);
+ path.close();
+ return endPt;
+ }
+
+ static SkPoint AddCubic(SkPath& path, SkPoint& startPt) {
+ SkPoint t1Pt = startPt + SkPoint::Make(15*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint t2Pt = startPt + SkPoint::Make(25*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint endPt = startPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.cubicTo(t1Pt, t2Pt, endPt);
+ return endPt;
+ }
+
+ static SkPoint AddMoveCubic(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ SkPoint t1Pt = moveToPt + SkPoint::Make(15*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint t2Pt = moveToPt + SkPoint::Make(25*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint endPt = moveToPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.moveTo(moveToPt);
+ path.cubicTo(t1Pt, t2Pt, endPt);
+ return endPt;
+ }
+
+ static SkPoint AddMoveCubicClose(SkPath& path, SkPoint& startPt) {
+ SkPoint moveToPt = startPt + SkPoint::Make(0, 10*SK_Scalar1);
+ SkPoint t1Pt = moveToPt + SkPoint::Make(15*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint t2Pt = moveToPt + SkPoint::Make(25*SK_Scalar1, 5*SK_Scalar1);
+ SkPoint endPt = moveToPt + SkPoint::Make(40*SK_Scalar1, 0);
+ path.moveTo(moveToPt);
+ path.cubicTo(t1Pt, t2Pt, endPt);
+ path.close();
+ return endPt;
+ }
+
+ void drawPath(SkPath& path, SkCanvas* canvas, SkColor color,
+ const SkRect& clip, SkPaint::Cap cap,
+ SkPaint::Style style, SkPath::FillType fill,
+ SkScalar strokeWidth) {
+ path.setFillType(fill);
+ SkPaint paint;
+ paint.setStrokeCap(cap);
+ paint.setStrokeWidth(strokeWidth);
+ paint.setColor(color);
+ paint.setStyle(style);
+ canvas->save();
+ canvas->clipRect(clip);
+ canvas->drawPath(path, paint);
+ canvas->restore();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ static const AddSegmentFunc gSegmentFunctions[] = {
+ AddMove,
+ AddMoveClose,
+ AddDegenLine,
+ AddMoveDegenLine,
+ AddMoveDegenLineClose,
+ AddDegenQuad,
+ AddMoveDegenQuad,
+ AddMoveDegenQuadClose,
+ AddDegenCubic,
+ AddMoveDegenCubic,
+ AddMoveDegenCubicClose,
+ AddClose,
+ AddLine,
+ AddMoveLine,
+ AddMoveLineClose,
+ AddQuad,
+ AddMoveQuad,
+ AddMoveQuadClose,
+ AddCubic,
+ AddMoveCubic,
+ AddMoveCubicClose
+ };
+ static const char* gSegmentNames[] = {
+ "Move",
+ "MoveClose",
+ "DegenLine",
+ "MoveDegenLine",
+ "MoveDegenLineClose",
+ "DegenQuad",
+ "MoveDegenQuad",
+ "MoveDegenQuadClose",
+ "DegenCubic",
+ "MoveDegenCubic",
+ "MoveDegenCubicClose",
+ "Close",
+ "Line",
+ "MoveLine",
+ "MoveLineClose",
+ "Quad",
+ "MoveQuad",
+ "MoveQuadClose",
+ "Cubic",
+ "MoveCubic",
+ "MoveCubicClose"
+ };
+
+ struct FillAndName {
+ SkPath::FillType fFill;
+ const char* fName;
+ };
+ static const FillAndName gFills[] = {
+ {SkPath::kWinding_FillType, "Winding"},
+ {SkPath::kEvenOdd_FillType, "Even / Odd"},
+ {SkPath::kInverseWinding_FillType, "Inverse Winding"},
+ {SkPath::kInverseEvenOdd_FillType, "Inverse Even / Odd"}
+ };
+ struct StyleAndName {
+ SkPaint::Style fStyle;
+ const char* fName;
+ };
+ static const StyleAndName gStyles[] = {
+ {SkPaint::kFill_Style, "Fill"},
+ {SkPaint::kStroke_Style, "Stroke 10"},
+ {SkPaint::kStrokeAndFill_Style, "Stroke 10 And Fill"}
+ };
+ struct CapAndName {
+ SkPaint::Cap fCap;
+ const char* fName;
+ };
+ static const CapAndName gCaps[] = {
+ {SkPaint::kButt_Cap, "Butt"},
+ {SkPaint::kRound_Cap, "Round"},
+ {SkPaint::kSquare_Cap, "Square"}
+ };
+
+ SkPaint titlePaint;
+ titlePaint.setColor(SK_ColorBLACK);
+ titlePaint.setAntiAlias(true);
+ titlePaint.setLCDRenderText(true);
+ titlePaint.setTextSize(15 * SK_Scalar1);
+ const char title[] = "Random Paths Drawn Into Rectangle Clips With "
+ "Indicated Style, Fill and Linecaps, "
+ "with Stroke width 6";
+ canvas->drawText(title, strlen(title),
+ 20 * SK_Scalar1,
+ 20 * SK_Scalar1,
+ titlePaint);
+
+ SkRandom rand;
+ SkRect rect = SkRect::MakeWH(220*SK_Scalar1, 50*SK_Scalar1);
+ canvas->save();
+ canvas->translate(2*SK_Scalar1, 30 * SK_Scalar1); // The title
+ canvas->save();
+ unsigned numSegments = SK_ARRAY_COUNT(gSegmentFunctions);
+ unsigned numCaps = SK_ARRAY_COUNT(gCaps);
+ unsigned numStyles = SK_ARRAY_COUNT(gStyles);
+ unsigned numFills = SK_ARRAY_COUNT(gFills);
+ for (size_t row = 0; row < 8; ++row) {
+ if (0 < row) {
+ canvas->translate(0, rect.height() + 100*SK_Scalar1);
+ }
+ canvas->save();
+ for (size_t column = 0; column < 6; ++column) {
+ if (0 < column) {
+ canvas->translate(rect.width() + 4*SK_Scalar1, 0);
+ }
+
+ SkColor color = 0xff007000;
+ StyleAndName style = gStyles[(rand.nextU() >> 16) % numStyles];
+ CapAndName cap = gCaps[(rand.nextU() >> 16) % numCaps];
+ FillAndName fill = gFills[(rand.nextU() >> 16) % numFills];
+ SkPath path;
+ unsigned s1 = (rand.nextU() >> 16) % numSegments;
+ unsigned s2 = (rand.nextU() >> 16) % numSegments;
+ unsigned s3 = (rand.nextU() >> 16) % numSegments;
+ unsigned s4 = (rand.nextU() >> 16) % numSegments;
+ unsigned s5 = (rand.nextU() >> 16) % numSegments;
+ SkPoint pt = SkPoint::Make(10*SK_Scalar1, 0);
+ pt = gSegmentFunctions[s1](path, pt);
+ pt = gSegmentFunctions[s2](path, pt);
+ pt = gSegmentFunctions[s3](path, pt);
+ pt = gSegmentFunctions[s4](path, pt);
+ pt = gSegmentFunctions[s5](path, pt);
+
+ this->drawPath(path, canvas, color, rect,
+ cap.fCap, style.fStyle,
+ fill.fFill, SK_Scalar1*6);
+
+ SkPaint rectPaint;
+ rectPaint.setColor(SK_ColorBLACK);
+ rectPaint.setStyle(SkPaint::kStroke_Style);
+ rectPaint.setStrokeWidth(-1);
+ rectPaint.setAntiAlias(true);
+ canvas->drawRect(rect, rectPaint);
+
+ SkPaint labelPaint;
+ labelPaint.setColor(color);
+ labelPaint.setAntiAlias(true);
+ labelPaint.setLCDRenderText(true);
+ labelPaint.setTextSize(10 * SK_Scalar1);
+ canvas->drawText(style.fName,
+ strlen(style.fName),
+ 0, rect.height() + 12 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(fill.fName,
+ strlen(fill.fName),
+ 0, rect.height() + 24 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(cap.fName,
+ strlen(cap.fName),
+ 0, rect.height() + 36 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gSegmentNames[s1],
+ strlen(gSegmentNames[s1]),
+ 0, rect.height() + 48 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gSegmentNames[s2],
+ strlen(gSegmentNames[s2]),
+ 0, rect.height() + 60 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gSegmentNames[s3],
+ strlen(gSegmentNames[s3]),
+ 0, rect.height() + 72 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gSegmentNames[s4],
+ strlen(gSegmentNames[s4]),
+ 0, rect.height() + 84 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gSegmentNames[s5],
+ strlen(gSegmentNames[s5]),
+ 0, rect.height() + 96 * SK_Scalar1,
+ labelPaint);
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ canvas->restore();
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new DegenerateSegmentsGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gm/linepaths.cpp b/gm/linepaths.cpp
new file mode 100644
index 0000000000..2a33920128
--- /dev/null
+++ b/gm/linepaths.cpp
@@ -0,0 +1,181 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+
+namespace skiagm {
+
+class LinePathsGM : public GM {
+public:
+ LinePathsGM() {}
+
+protected:
+ SkString onShortName() {
+ return SkString("linepaths");
+ }
+
+ SkISize onISize() { return make_isize(1800, 1110); }
+
+ void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
+ const SkRect& clip,SkPaint::Cap cap,
+ SkPaint::Style style, SkPath::FillType fill,
+ SkScalar strokeWidth) {
+ path.setFillType(fill);
+ SkPaint paint;
+ paint.setStrokeCap(cap);
+ paint.setStrokeWidth(strokeWidth);
+ paint.setColor(color);
+ paint.setStyle(style);
+ canvas->save();
+ canvas->clipRect(clip);
+ canvas->drawPath(path, paint);
+ canvas->restore();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ struct FillAndName {
+ SkPath::FillType fFill;
+ const char* fName;
+ };
+ static const FillAndName gFills[] = {
+ {SkPath::kWinding_FillType, "Winding"},
+ {SkPath::kEvenOdd_FillType, "Even / Odd"},
+ {SkPath::kInverseWinding_FillType, "Inverse Winding"},
+ {SkPath::kInverseEvenOdd_FillType, "Inverse Even / Odd"},
+ };
+ struct StyleAndName {
+ SkPaint::Style fStyle;
+ const char* fName;
+ };
+ static const StyleAndName gStyles[] = {
+ {SkPaint::kFill_Style, "Fill"},
+ {SkPaint::kStroke_Style, "Stroke"},
+ {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
+ };
+ struct CapAndName {
+ SkPaint::Cap fCap;
+ const char* fName;
+ };
+ static const CapAndName gCaps[] = {
+ {SkPaint::kButt_Cap, "Butt"},
+ {SkPaint::kRound_Cap, "Round"},
+ {SkPaint::kSquare_Cap, "Square"},
+ };
+ struct PathAndName {
+ SkPath fPath;
+ const char* fName;
+ };
+ PathAndName gPaths[4];
+ gPaths[0].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[0].fPath.lineTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[0].fName = "moveTo-zeroline";
+ gPaths[1].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[1].fPath.lineTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[1].fPath.close();
+ gPaths[1].fName = "moveTo-zeroline-close";
+ gPaths[2].fPath.moveTo(30*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[2].fPath.lineTo(70*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[2].fName = "moveTo-line";
+ gPaths[3].fPath.moveTo(30*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[3].fPath.lineTo(70*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[3].fPath.close();
+ gPaths[3].fName = "moveTo-line-close";
+
+ SkPaint titlePaint;
+ titlePaint.setColor(SK_ColorBLACK);
+ titlePaint.setAntiAlias(true);
+ titlePaint.setLCDRenderText(true);
+ titlePaint.setTextSize(15 * SK_Scalar1);
+ const char title[] = "Line Paths Drawn Into Rectangle Clips With "
+ "Indicated Style, Fill and Linecaps, "
+ "with random stroke widths";
+ canvas->drawText(title, strlen(title),
+ 20 * SK_Scalar1,
+ 20 * SK_Scalar1,
+ titlePaint);
+
+ SkRandom rand;
+ SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
+ canvas->save();
+ canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
+ canvas->save();
+ for (size_t path = 0; path < SK_ARRAY_COUNT(gPaths); ++path) {
+ if (0 < path) {
+ canvas->translate(0, (rect.height() + 60 * SK_Scalar1) * 3);
+ }
+ canvas->save();
+ for (size_t cap = 0; cap < SK_ARRAY_COUNT(gCaps); ++cap) {
+ if (0 < cap) {
+ canvas->translate((rect.width() + 40 * SK_Scalar1) * 4, 0);
+ }
+ canvas->save();
+ for (size_t style = 0; style < SK_ARRAY_COUNT(gStyles); ++style) {
+ if (0 < style) {
+ canvas->translate(0, rect.height() + 60 * SK_Scalar1);
+ }
+ canvas->save();
+ for (size_t fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) {
+ if (0 < fill) {
+ canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
+ }
+
+ SkColor color = 0xff007000;
+ this->drawPath(gPaths[path].fPath, canvas, color, rect,
+ gCaps[cap].fCap, gStyles[style].fStyle,
+ gFills[fill].fFill, SK_Scalar1*10);
+
+ SkPaint rectPaint;
+ rectPaint.setColor(SK_ColorBLACK);
+ rectPaint.setStyle(SkPaint::kStroke_Style);
+ rectPaint.setStrokeWidth(-1);
+ rectPaint.setAntiAlias(true);
+ canvas->drawRect(rect, rectPaint);
+
+ SkPaint labelPaint;
+ labelPaint.setColor(color);
+ labelPaint.setAntiAlias(true);
+ labelPaint.setLCDRenderText(true);
+ labelPaint.setTextSize(10 * SK_Scalar1);
+ canvas->drawText(gStyles[style].fName,
+ strlen(gStyles[style].fName),
+ 0, rect.height() + 12 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gFills[fill].fName,
+ strlen(gFills[fill].fName),
+ 0, rect.height() + 24 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gCaps[cap].fName,
+ strlen(gCaps[cap].fName),
+ 0, rect.height() + 36 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gPaths[path].fName,
+ strlen(gPaths[path].fName),
+ 0, rect.height() + 48 * SK_Scalar1,
+ labelPaint);
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ canvas->restore();
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new LinePathsGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gm/movepaths.cpp b/gm/movepaths.cpp
new file mode 100644
index 0000000000..574058e4ba
--- /dev/null
+++ b/gm/movepaths.cpp
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+
+namespace skiagm {
+
+class MovePathsGM : public GM {
+public:
+ MovePathsGM() {}
+
+protected:
+ SkString onShortName() {
+ return SkString("movepaths");
+ }
+
+ SkISize onISize() { return make_isize(1800, 1110); }
+
+ void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
+ const SkRect& clip,SkPaint::Cap cap,
+ SkPaint::Style style, SkPath::FillType fill,
+ SkScalar strokeWidth) {
+ path.setFillType(fill);
+ SkPaint paint;
+ paint.setStrokeCap(cap);
+ paint.setStrokeWidth(strokeWidth);
+ paint.setColor(color);
+ paint.setStyle(style);
+ canvas->save();
+ canvas->clipRect(clip);
+ canvas->drawPath(path, paint);
+ canvas->restore();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ struct FillAndName {
+ SkPath::FillType fFill;
+ const char* fName;
+ };
+ static const FillAndName gFills[] = {
+ {SkPath::kWinding_FillType, "Winding"},
+ {SkPath::kEvenOdd_FillType, "Even / Odd"},
+ {SkPath::kInverseWinding_FillType, "Inverse Winding"},
+ {SkPath::kInverseEvenOdd_FillType, "Inverse Even / Odd"},
+ };
+ struct StyleAndName {
+ SkPaint::Style fStyle;
+ const char* fName;
+ };
+ static const StyleAndName gStyles[] = {
+ {SkPaint::kFill_Style, "Fill"},
+ {SkPaint::kStroke_Style, "Stroke"},
+ {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
+ };
+ struct CapAndName {
+ SkPaint::Cap fCap;
+ const char* fName;
+ };
+ static const CapAndName gCaps[] = {
+ {SkPaint::kButt_Cap, "Butt"},
+ {SkPaint::kRound_Cap, "Round"},
+ {SkPaint::kSquare_Cap, "Square"},
+ };
+ struct PathAndName {
+ SkPath fPath;
+ const char* fName;
+ };
+ PathAndName gPaths[4];
+ gPaths[0].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[0].fName = "moveTo";
+ gPaths[1].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[1].fPath.close();
+ gPaths[1].fName = "moveTo-close";
+ gPaths[2].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[2].fPath.moveTo(75*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[2].fName = "moveTo-moveTo";
+ gPaths[3].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[3].fPath.close();
+ gPaths[3].fPath.moveTo(75*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[3].fPath.close();
+ gPaths[3].fName = "moveTo-close-moveTo-close";
+
+ SkPaint titlePaint;
+ titlePaint.setColor(SK_ColorBLACK);
+ titlePaint.setAntiAlias(true);
+ titlePaint.setLCDRenderText(true);
+ titlePaint.setTextSize(15 * SK_Scalar1);
+ const char title[] = "MoveTo Paths Drawn Into Rectangle Clips With "
+ "Indicated Style, Fill and Linecaps, "
+ "with random stroke widths";
+ canvas->drawText(title, strlen(title),
+ 20 * SK_Scalar1,
+ 20 * SK_Scalar1,
+ titlePaint);
+
+ SkRandom rand;
+ SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
+ canvas->save();
+ canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
+ canvas->save();
+ for (size_t path = 0; path < SK_ARRAY_COUNT(gPaths); ++path) {
+ if (0 < path) {
+ canvas->translate(0, (rect.height() + 60 * SK_Scalar1) * 3);
+ }
+ canvas->save();
+ for (size_t cap = 0; cap < SK_ARRAY_COUNT(gCaps); ++cap) {
+ if (0 < cap) {
+ canvas->translate((rect.width() + 40 * SK_Scalar1) * 4, 0);
+ }
+ canvas->save();
+ for (size_t style = 0; style < SK_ARRAY_COUNT(gStyles); ++style) {
+ if (0 < style) {
+ canvas->translate(0, rect.height() + 60 * SK_Scalar1);
+ }
+ canvas->save();
+ for (size_t fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) {
+ if (0 < fill) {
+ canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
+ }
+
+ SkColor color = 0xff007000;
+ this->drawPath(gPaths[path].fPath, canvas, color, rect,
+ gCaps[cap].fCap, gStyles[style].fStyle,
+ gFills[fill].fFill, SK_Scalar1*10);
+
+ SkPaint rectPaint;
+ rectPaint.setColor(SK_ColorBLACK);
+ rectPaint.setStyle(SkPaint::kStroke_Style);
+ rectPaint.setStrokeWidth(-1);
+ rectPaint.setAntiAlias(true);
+ canvas->drawRect(rect, rectPaint);
+
+ SkPaint labelPaint;
+ labelPaint.setColor(color);
+ labelPaint.setAntiAlias(true);
+ labelPaint.setLCDRenderText(true);
+ labelPaint.setTextSize(10 * SK_Scalar1);
+ canvas->drawText(gStyles[style].fName,
+ strlen(gStyles[style].fName),
+ 0, rect.height() + 12 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gFills[fill].fName,
+ strlen(gFills[fill].fName),
+ 0, rect.height() + 24 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gCaps[cap].fName,
+ strlen(gCaps[cap].fName),
+ 0, rect.height() + 36 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gPaths[path].fName,
+ strlen(gPaths[path].fName),
+ 0, rect.height() + 48 * SK_Scalar1,
+ labelPaint);
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ canvas->restore();
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new MovePathsGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gm/quadpaths.cpp b/gm/quadpaths.cpp
new file mode 100644
index 0000000000..32de2f4317
--- /dev/null
+++ b/gm/quadpaths.cpp
@@ -0,0 +1,185 @@
+/*
+ * Copyright 2011 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "gm.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+#include "SkRandom.h"
+
+namespace skiagm {
+
+class QuadPathsGM : public GM {
+public:
+ QuadPathsGM() {}
+
+protected:
+ SkString onShortName() {
+ return SkString("quadpaths");
+ }
+
+ SkISize onISize() { return make_isize(1800, 1110); }
+
+ void drawPath(SkPath& path,SkCanvas* canvas,SkColor color,
+ const SkRect& clip,SkPaint::Cap cap,
+ SkPaint::Style style, SkPath::FillType fill,
+ SkScalar strokeWidth) {
+ path.setFillType(fill);
+ SkPaint paint;
+ paint.setStrokeCap(cap);
+ paint.setStrokeWidth(strokeWidth);
+ paint.setColor(color);
+ paint.setStyle(style);
+ canvas->save();
+ canvas->clipRect(clip);
+ canvas->drawPath(path, paint);
+ canvas->restore();
+ }
+
+ virtual void onDraw(SkCanvas* canvas) {
+ struct FillAndName {
+ SkPath::FillType fFill;
+ const char* fName;
+ };
+ static const FillAndName gFills[] = {
+ {SkPath::kWinding_FillType, "Winding"},
+ {SkPath::kEvenOdd_FillType, "Even / Odd"},
+ {SkPath::kInverseWinding_FillType, "Inverse Winding"},
+ {SkPath::kInverseEvenOdd_FillType, "Inverse Even / Odd"},
+ };
+ struct StyleAndName {
+ SkPaint::Style fStyle;
+ const char* fName;
+ };
+ static const StyleAndName gStyles[] = {
+ {SkPaint::kFill_Style, "Fill"},
+ {SkPaint::kStroke_Style, "Stroke"},
+ {SkPaint::kStrokeAndFill_Style, "Stroke And Fill"},
+ };
+ struct CapAndName {
+ SkPaint::Cap fCap;
+ const char* fName;
+ };
+ static const CapAndName gCaps[] = {
+ {SkPaint::kButt_Cap, "Butt"},
+ {SkPaint::kRound_Cap, "Round"},
+ {SkPaint::kSquare_Cap, "Square"},
+ };
+ struct PathAndName {
+ SkPath fPath;
+ const char* fName;
+ };
+ PathAndName gPaths[4];
+ gPaths[0].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[0].fPath.quadTo(50*SK_Scalar1, 15*SK_Scalar1,
+ 50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[0].fName = "moveTo-zeroquad";
+ gPaths[1].fPath.moveTo(50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[1].fPath.quadTo(50*SK_Scalar1, 15*SK_Scalar1,
+ 50*SK_Scalar1, 15*SK_Scalar1);
+ gPaths[1].fPath.close();
+ gPaths[1].fName = "moveTo-zeroquad-close";
+ gPaths[2].fPath.moveTo(30*SK_Scalar1, 10*SK_Scalar1);
+ gPaths[2].fPath.quadTo(50*SK_Scalar1, 20*SK_Scalar1,
+ 70*SK_Scalar1, 10*SK_Scalar1);
+ gPaths[2].fName = "moveTo-quad";
+ gPaths[3].fPath.moveTo(30*SK_Scalar1, 10*SK_Scalar1);
+ gPaths[3].fPath.quadTo(50*SK_Scalar1, 20*SK_Scalar1,
+ 70*SK_Scalar1, 10*SK_Scalar1);
+ gPaths[3].fPath.close();
+ gPaths[3].fName = "moveTo-quad-close";
+
+ SkPaint titlePaint;
+ titlePaint.setColor(SK_ColorBLACK);
+ titlePaint.setAntiAlias(true);
+ titlePaint.setLCDRenderText(true);
+ titlePaint.setTextSize(15 * SK_Scalar1);
+ const char title[] = "Zero Paths Drawn Into Rectangle Clips With "
+ "Indicated Style, Fill and Linecaps, "
+ "with random stroke widths";
+ canvas->drawText(title, strlen(title),
+ 20 * SK_Scalar1,
+ 20 * SK_Scalar1,
+ titlePaint);
+
+ SkRandom rand;
+ SkRect rect = SkRect::MakeWH(100*SK_Scalar1, 30*SK_Scalar1);
+ canvas->save();
+ canvas->translate(10 * SK_Scalar1, 30 * SK_Scalar1);
+ canvas->save();
+ for (size_t path = 0; path < SK_ARRAY_COUNT(gPaths); ++path) {
+ if (0 < path) {
+ canvas->translate(0, (rect.height() + 60 * SK_Scalar1) * 3);
+ }
+ canvas->save();
+ for (size_t cap = 0; cap < SK_ARRAY_COUNT(gCaps); ++cap) {
+ if (0 < cap) {
+ canvas->translate((rect.width() + 40 * SK_Scalar1) * 4, 0);
+ }
+ canvas->save();
+ for (size_t style = 0; style < SK_ARRAY_COUNT(gStyles); ++style) {
+ if (0 < style) {
+ canvas->translate(0, rect.height() + 60 * SK_Scalar1);
+ }
+ canvas->save();
+ for (size_t fill = 0; fill < SK_ARRAY_COUNT(gFills); ++fill) {
+ if (0 < fill) {
+ canvas->translate(rect.width() + 40 * SK_Scalar1, 0);
+ }
+
+ SkColor color = 0xff007000;
+ this->drawPath(gPaths[path].fPath, canvas, color, rect,
+ gCaps[cap].fCap, gStyles[style].fStyle,
+ gFills[fill].fFill, SK_Scalar1*10);
+
+ SkPaint rectPaint;
+ rectPaint.setColor(SK_ColorBLACK);
+ rectPaint.setStyle(SkPaint::kStroke_Style);
+ rectPaint.setStrokeWidth(-1);
+ rectPaint.setAntiAlias(true);
+ canvas->drawRect(rect, rectPaint);
+
+ SkPaint labelPaint;
+ labelPaint.setColor(color);
+ labelPaint.setAntiAlias(true);
+ labelPaint.setLCDRenderText(true);
+ labelPaint.setTextSize(10 * SK_Scalar1);
+ canvas->drawText(gStyles[style].fName,
+ strlen(gStyles[style].fName),
+ 0, rect.height() + 12 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gFills[fill].fName,
+ strlen(gFills[fill].fName),
+ 0, rect.height() + 24 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gCaps[cap].fName,
+ strlen(gCaps[cap].fName),
+ 0, rect.height() + 36 * SK_Scalar1,
+ labelPaint);
+ canvas->drawText(gPaths[path].fName,
+ strlen(gPaths[path].fName),
+ 0, rect.height() + 48 * SK_Scalar1,
+ labelPaint);
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ }
+ canvas->restore();
+ canvas->restore();
+ }
+
+private:
+ typedef GM INHERITED;
+};
+
+//////////////////////////////////////////////////////////////////////////////
+
+static GM* MyFactory(void*) { return new QuadPathsGM; }
+static GMRegistry reg(MyFactory);
+
+}
diff --git a/gyp/gmslides.gypi b/gyp/gmslides.gypi
index 17873d6ab7..d20cf731d4 100644
--- a/gyp/gmslides.gypi
+++ b/gyp/gmslides.gypi
@@ -9,6 +9,8 @@
'../gm/blurs.cpp',
'../gm/complexclip.cpp',
'../gm/complexclip2.cpp',
+ '../gm/cubicpaths.cpp',
+ '../gm/degeneratesegments.cpp',
'../gm/drawbitmaprect.cpp',
'../gm/emptypath.cpp',
'../gm/filltypes.cpp',
@@ -21,11 +23,14 @@
# See http://code.google.com/p/skia/issues/detail?id=391
# '../gm/imageblur.cpp',
'../gm/lcdtext.cpp',
+ '../gm/linepaths.cpp',
+ '../gm/movepaths.cpp',
'../gm/ninepatchstretch.cpp',
'../gm/nocolorbleed.cpp',
'../gm/pathfill.cpp',
'../gm/points.cpp',
'../gm/poly2poly.cpp',
+ '../gm/quadpaths.cpp',
'../gm/shadertext.cpp',
'../gm/shadows.cpp',
'../gm/shapes.cpp',
diff --git a/include/core/SkPath.h b/include/core/SkPath.h
index 8eedb4655c..f0fd5e93cd 100644
--- a/include/core/SkPath.h
+++ b/include/core/SkPath.h
@@ -180,6 +180,35 @@ public:
*/
bool isEmpty() const;
+ /** Test a line for zero length
+
+ @return true if the line is of zero length; otherwise false.
+ */
+ static bool IsLineDegenerate(const SkPoint& p1, const SkPoint& p2) {
+ return p1.equalsWithinTolerance(p2, SK_ScalarNearlyZero);
+ }
+
+ /** Test a quad for zero length
+
+ @return true if the quad is of zero length; otherwise false.
+ */
+ static bool IsQuadDegenerate(const SkPoint& p1, const SkPoint& p2,
+ const SkPoint& p3) {
+ return p1.equalsWithinTolerance(p2, SK_ScalarNearlyZero) &&
+ p2.equalsWithinTolerance(p3, SK_ScalarNearlyZero);
+ }
+
+ /** Test a cubic curve for zero length
+
+ @return true if the cubic is of zero length; otherwise false.
+ */
+ static bool IsCubicDegenerate(const SkPoint& p1, const SkPoint& p2,
+ const SkPoint& p3, const SkPoint& p4) {
+ return p1.equalsWithinTolerance(p2, SK_ScalarNearlyZero) &&
+ p2.equalsWithinTolerance(p3, SK_ScalarNearlyZero) &&
+ p3.equalsWithinTolerance(p4, SK_ScalarNearlyZero);
+ }
+
/** Returns true if the path specifies a rectangle. If so, and if rect is
not null, set rect to the bounds of the path. If the path does not
specify a rectangle, return false and ignore rect.
@@ -633,11 +662,12 @@ public:
SkPoint fLastPt;
SkBool8 fForceClose;
SkBool8 fNeedClose;
- SkBool8 fNeedMoveTo;
SkBool8 fCloseLine;
+ uint8_t fSegmentState;
bool cons_moveTo(SkPoint pts[1]);
Verb autoClose(SkPoint pts[2]);
+ void consumeDegenerateSegments();
};
void dump(bool forceClose, const char title[] = NULL) const;
diff --git a/include/core/SkPoint.h b/include/core/SkPoint.h
index fedc404a33..de7c0ef47e 100644
--- a/include/core/SkPoint.h
+++ b/include/core/SkPoint.h
@@ -315,6 +315,13 @@ struct SK_API SkPoint {
return a.fX != b.fX || a.fY != b.fY;
}
+ /** Return true if this and the given point are componentwise within tol.
+ */
+ bool equalsWithinTolerance(const SkPoint& v, SkScalar tol) const {
+ return SkScalarNearlyZero(fX - v.fX, tol)
+ && SkScalarNearlyZero(fY - v.fY, tol);
+ }
+
/** Returns a new point whose coordinates are the difference between
a's and b's (a - b)
*/
diff --git a/src/core/SkPath.cpp b/src/core/SkPath.cpp
index 7256021811..07e54762da 100644
--- a/src/core/SkPath.cpp
+++ b/src/core/SkPath.cpp
@@ -89,13 +89,14 @@ static void compute_pt_bounds(SkRect* bounds, const SkTDArray<SkPoint>& pts) {
/*
Stores the verbs and points as they are given to us, with exceptions:
- - we only record "Close" if it was immediately preceeded by Line | Quad | Cubic
+ - we only record "Close" if it was immediately preceeded by Move | Line | Quad | Cubic
- we insert a Move(0,0) if Line | Quad | Cubic is our first command
The iterator does more cleanup, especially if forceClose == true
- 1. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
- 2. if we encounter Move without a preceeding Close, and forceClose is true, goto #1
- 3. if we encounter Line | Quad | Cubic after Close, cons up a Move
+ 1. If we encounter degenerate segments, remove them
+ 2. if we encounter Close, return a cons'd up Line() first (if the curr-pt != start-pt)
+ 3. if we encounter Move without a preceeding Close, and forceClose is true, goto #2
+ 4. if we encounter Line | Quad | Cubic after Close, cons up a Move
*/
////////////////////////////////////////////////////////////////////////////
@@ -191,16 +192,15 @@ void SkPath::rewind() {
fPts.rewind();
fVerbs.rewind();
GEN_ID_INC;
- fBoundsIsDirty = true;
fConvexity = kUnknown_Convexity;
+ fBoundsIsDirty = true;
fSegmentMask = 0;
}
bool SkPath::isEmpty() const {
SkDEBUGCODE(this->validate();)
- int count = fVerbs.count();
- return count == 0 || (count == 1 && fVerbs[0] == kMove_Verb);
+ return 0 == fVerbs.count();
}
/*
@@ -385,10 +385,10 @@ void SkPath::setConvexity(Convexity c) {
//////////////////////////////////////////////////////////////////////////////
// Construction methods
-#define DIRTY_AFTER_EDIT \
- do { \
- fBoundsIsDirty = true; \
- fConvexity = kUnknown_Convexity;\
+#define DIRTY_AFTER_EDIT \
+ do { \
+ fBoundsIsDirty = true; \
+ fConvexity = kUnknown_Convexity; \
} while (0)
void SkPath::incReserve(U16CPU inc) {
@@ -406,12 +406,8 @@ void SkPath::moveTo(SkScalar x, SkScalar y) {
int vc = fVerbs.count();
SkPoint* pt;
- if (vc > 0 && fVerbs[vc - 1] == kMove_Verb) {
- pt = &fPts[fPts.count() - 1];
- } else {
- pt = fPts.append();
- *fVerbs.append() = kMove_Verb;
- }
+ pt = fPts.append();
+ *fVerbs.append() = kMove_Verb;
pt->set(x, y);
GEN_ID_INC;
@@ -505,11 +501,12 @@ void SkPath::close() {
case kLine_Verb:
case kQuad_Verb:
case kCubic_Verb:
+ case kMove_Verb:
*fVerbs.append() = kClose_Verb;
GEN_ID_INC;
break;
default:
- // don't add a close if the prev wasn't a primitive
+ // don't add a close if it's the first verb or a repeat
break;
}
}
@@ -1143,17 +1140,20 @@ void SkPath::transform(const SkMatrix& matrix, SkPath* dst) const {
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
-enum NeedMoveToState {
- kAfterClose_NeedMoveToState,
- kAfterCons_NeedMoveToState,
- kAfterPrefix_NeedMoveToState
+enum SegmentState {
+ kAfterClose_SegmentState, // We will need a move next, but we have a
+ // previous close pt to use for the new move.
+ kAfterMove_SegmentState, // We have seen a move, but nothing else.
+ kAfterPrimitive_SegmentState // We have seen a primitive but not yet
+ // closed the path. Also the initial state.
};
SkPath::Iter::Iter() {
#ifdef SK_DEBUG
fPts = NULL;
fMoveTo.fX = fMoveTo.fY = fLastPt.fX = fLastPt.fY = 0;
- fForceClose = fNeedMoveTo = fCloseLine = false;
+ fForceClose = fCloseLine = false;
+ fSegmentState = kAfterPrimitive_SegmentState;
#endif
// need to init enough to make next() harmlessly return kDone_Verb
fVerbs = NULL;
@@ -1169,9 +1169,10 @@ void SkPath::Iter::setPath(const SkPath& path, bool forceClose) {
fPts = path.fPts.begin();
fVerbs = path.fVerbs.begin();
fVerbStop = path.fVerbs.end();
+ fLastPt.fX = fLastPt.fY = 0;
fForceClose = SkToU8(forceClose);
fNeedClose = false;
- fNeedMoveTo = kAfterPrefix_NeedMoveToState;
+ fSegmentState = kAfterPrimitive_SegmentState;
}
bool SkPath::Iter::isClosedContour() const {
@@ -1225,23 +1226,27 @@ SkPath::Verb SkPath::Iter::autoClose(SkPoint pts[2]) {
}
bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
- if (fNeedMoveTo == kAfterClose_NeedMoveToState) {
+ if (fSegmentState == kAfterClose_SegmentState) {
+ // We have closed a curve and have a primitive, so we need a move.
+ // Set the first return pt to the most recent move pt
if (pts) {
*pts = fMoveTo;
}
fNeedClose = fForceClose;
- fNeedMoveTo = kAfterCons_NeedMoveToState;
- fVerbs -= 1;
+ fSegmentState = kAfterMove_SegmentState;
+ fVerbs -= 1; // Step back to see the primitive again
return true;
}
- if (fNeedMoveTo == kAfterCons_NeedMoveToState) {
+ if (fSegmentState == kAfterMove_SegmentState) {
+ // Set the first return pt to the move pt
if (pts) {
*pts = fMoveTo;
}
- fNeedMoveTo = kAfterPrefix_NeedMoveToState;
+ fSegmentState = kAfterPrimitive_SegmentState;
} else {
- SkASSERT(fNeedMoveTo == kAfterPrefix_NeedMoveToState);
+ SkASSERT(fSegmentState == kAfterPrimitive_SegmentState);
+ // Set the first return pt to the last pt of the previous primitive.
if (pts) {
*pts = fPts[-1];
}
@@ -1249,9 +1254,91 @@ bool SkPath::Iter::cons_moveTo(SkPoint pts[1]) {
return false;
}
+void SkPath::Iter::consumeDegenerateSegments() {
+ // We need to step over anything that will not move the current draw point
+ // forward before the next move is seen
+ const uint8_t* lastMoveVerb = 0;
+ const SkPoint* lastMovePt = 0;
+ SkPoint lastPt = fLastPt;
+ while (fVerbs != fVerbStop) {
+ unsigned verb = *fVerbs;
+ switch (verb) {
+ case kMove_Verb:
+ // Set state for the next method.
+ fSegmentState = kAfterMove_SegmentState;
+ fMoveTo = fPts[0];
+
+ // Keep a record of this most recent move
+ lastMoveVerb = fVerbs;
+ lastMovePt = fPts;
+ lastPt = fPts[0];
+ fVerbs++;
+ fPts++;
+ break;
+
+ case kClose_Verb:
+ // A close when we are in a segment is always valid
+ if (fSegmentState == kAfterPrimitive_SegmentState) {
+ return;
+ }
+ // A close at any other time must be ignored
+ fVerbs++;
+ break;
+
+ case kLine_Verb:
+ if (!IsLineDegenerate(lastPt, fPts[0])) {
+ if (lastMoveVerb) {
+ fVerbs = lastMoveVerb;
+ fPts = lastMovePt;
+ return;
+ }
+ return;
+ }
+ // Ignore this line and continue
+ fVerbs++;
+ fPts++;
+ break;
+
+ case kQuad_Verb:
+ if (!IsQuadDegenerate(lastPt, fPts[0], fPts[1])) {
+ if (lastMoveVerb) {
+ fVerbs = lastMoveVerb;
+ fPts = lastMovePt;
+ return;
+ }
+ return;
+ }
+ // Ignore this line and continue
+ fVerbs++;
+ fPts += 2;
+ break;
+
+ case kCubic_Verb:
+ if (!IsCubicDegenerate(lastPt, fPts[0], fPts[1], fPts[2])) {
+ if (lastMoveVerb) {
+ fVerbs = lastMoveVerb;
+ fPts = lastMovePt;
+ return;
+ }
+ return;
+ }
+ // Ignore this line and continue
+ fVerbs++;
+ fPts += 3;
+ break;
+
+ default:
+ SkASSERT(false && "Should never see kDone_Verb");
+ }
+ }
+}
+
SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
+ this->consumeDegenerateSegments();
+
if (fVerbs == fVerbStop) {
- if (fNeedClose) {
+ // Close the curve if requested and if there is some curve to close
+ if (fNeedClose && fSegmentState == kAfterPrimitive_SegmentState) {
if (kLine_Verb == this->autoClose(pts)) {
return kLine_Verb;
}
@@ -1277,12 +1364,11 @@ SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
if (fVerbs == fVerbStop) { // might be a trailing moveto
return kDone_Verb;
}
- fMoveTo = *srcPts;
if (pts) {
pts[0] = *srcPts;
}
srcPts += 1;
- fNeedMoveTo = kAfterCons_NeedMoveToState;
+ fLastPt = fMoveTo;
fNeedClose = fForceClose;
break;
case kLine_Verb:
@@ -1322,8 +1408,9 @@ SkPath::Verb SkPath::Iter::next(SkPoint pts[4]) {
fVerbs -= 1;
} else {
fNeedClose = false;
+ fSegmentState = kAfterClose_SegmentState;
}
- fNeedMoveTo = kAfterClose_NeedMoveToState;
+ fLastPt = fMoveTo;
break;
}
fPts = srcPts;
diff --git a/src/core/SkStroke.cpp b/src/core/SkStroke.cpp
index 0051b196d7..fa89ab1a70 100644
--- a/src/core/SkStroke.cpp
+++ b/src/core/SkStroke.cpp
@@ -18,12 +18,6 @@ static inline bool degenerate_vector(const SkVector& v) {
return !SkPoint::CanNormalize(v.fX, v.fY);
}
-static inline bool degenerate_line(const SkPoint& a, const SkPoint& b,
- SkScalar tolerance = SK_ScalarNearlyZero) {
- return SkScalarNearlyZero(a.fX - b.fX, tolerance) &&
- SkScalarNearlyZero(a.fY - b.fY, tolerance);
-}
-
static inline bool normals_too_curvy(const SkVector& norm0, SkVector& norm1) {
/* root2/2 is a 45-degree angle
make this constant bigger for more subdivisions (but not >= 1)
@@ -219,7 +213,7 @@ void SkPathStroker::line_to(const SkPoint& currPt, const SkVector& normal) {
}
void SkPathStroker::lineTo(const SkPoint& currPt) {
- if (degenerate_line(fPrevPt, currPt)) {
+ if (SkPath::IsLineDegenerate(fPrevPt, currPt)) {
return;
}
SkVector normal, unitNormal;
@@ -350,8 +344,8 @@ DRAW_LINE:
}
void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
- bool degenerateAB = degenerate_line(fPrevPt, pt1);
- bool degenerateBC = degenerate_line(pt1, pt2);
+ bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
+ bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
if (degenerateAB | degenerateBC) {
if (degenerateAB ^ degenerateBC) {
@@ -406,9 +400,9 @@ void SkPathStroker::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
void SkPathStroker::cubicTo(const SkPoint& pt1, const SkPoint& pt2,
const SkPoint& pt3) {
- bool degenerateAB = degenerate_line(fPrevPt, pt1);
- bool degenerateBC = degenerate_line(pt1, pt2);
- bool degenerateCD = degenerate_line(pt2, pt3);
+ bool degenerateAB = SkPath::IsLineDegenerate(fPrevPt, pt1);
+ bool degenerateBC = SkPath::IsLineDegenerate(pt1, pt2);
+ bool degenerateCD = SkPath::IsLineDegenerate(pt2, pt3);
if (degenerateAB + degenerateBC + degenerateCD >= 2) {
this->lineTo(pt3);
diff --git a/tests/PathTest.cpp b/tests/PathTest.cpp
index 5e9a520f63..53752e1908 100644
--- a/tests/PathTest.cpp
+++ b/tests/PathTest.cpp
@@ -522,6 +522,141 @@ static void test_transform(skiatest::Reporter* reporter) {
}
}
+static void test_zero_length_paths(skiatest::Reporter* reporter) {
+ SkPath p;
+ SkRect bounds;
+
+ // Lone moveTo case
+ p.moveTo(SK_Scalar1, SK_Scalar1);
+ bounds.set(0, 0, 0, 0);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 1 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // MoveTo-MoveTo case
+ p.moveTo(SK_Scalar1*2, SK_Scalar1);
+ bounds.set(1, 1, 2, 1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 2 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-close case
+ p.reset();
+ p.moveTo(SK_Scalar1, SK_Scalar1);
+ p.close();
+ bounds.set(0, 0, 0, 0);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 1 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-close-moveTo-close case
+ p.moveTo(SK_Scalar1*2, SK_Scalar1);
+ p.close();
+ bounds.set(1, 1, 2, 1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 2 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-line case
+ p.reset();
+ p.moveTo(SK_Scalar1, SK_Scalar1);
+ p.lineTo(SK_Scalar1, SK_Scalar1);
+ bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 2 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-lineTo-moveTo-lineTo case
+ p.moveTo(SK_Scalar1*2, SK_Scalar1);
+ p.lineTo(SK_Scalar1*2, SK_Scalar1);
+ bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 4 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-line-close case
+ p.reset();
+ p.moveTo(SK_Scalar1, SK_Scalar1);
+ p.lineTo(SK_Scalar1, SK_Scalar1);
+ p.close();
+ bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 2 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-line-close-moveTo-line-close case
+ p.moveTo(SK_Scalar1*2, SK_Scalar1);
+ p.lineTo(SK_Scalar1*2, SK_Scalar1);
+ p.close();
+ bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 4 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-quadTo case
+ p.reset();
+ p.moveTo(SK_Scalar1, SK_Scalar1);
+ p.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
+ bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 3 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-quadTo-close case
+ p.close();
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 3 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-quadTo-moveTo-quadTo case
+ p.reset();
+ p.moveTo(SK_Scalar1, SK_Scalar1);
+ p.quadTo(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
+ p.moveTo(SK_Scalar1*2, SK_Scalar1);
+ p.quadTo(SK_Scalar1*2, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
+ bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 6 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-cubicTo case
+ p.reset();
+ p.moveTo(SK_Scalar1, SK_Scalar1);
+ p.cubicTo(SK_Scalar1, SK_Scalar1,
+ SK_Scalar1, SK_Scalar1,
+ SK_Scalar1, SK_Scalar1);
+ bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1, SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 4 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-quadTo-close case
+ p.close();
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 4 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+
+ // moveTo-quadTo-moveTo-quadTo case
+ p.reset();
+ p.moveTo(SK_Scalar1, SK_Scalar1);
+ p.cubicTo(SK_Scalar1, SK_Scalar1,
+ SK_Scalar1, SK_Scalar1,
+ SK_Scalar1, SK_Scalar1);
+ p.moveTo(SK_Scalar1*2, SK_Scalar1);
+ p.cubicTo(SK_Scalar1*2, SK_Scalar1,
+ SK_Scalar1*2, SK_Scalar1,
+ SK_Scalar1*2, SK_Scalar1);
+ bounds.set(SK_Scalar1, SK_Scalar1, SK_Scalar1*2, SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ REPORTER_ASSERT(reporter, 8 == p.countPoints());
+ REPORTER_ASSERT(reporter, bounds == p.getBounds());
+}
+
+struct SegmentInfo {
+ SkPath fPath;
+ int fPointCount;
+};
+
#define kCurveSegmentMask (SkPath::kQuad_SegmentMask | SkPath::kCubic_SegmentMask)
void TestPath(skiatest::Reporter* reporter);
@@ -540,6 +675,7 @@ void TestPath(skiatest::Reporter* reporter) {
SkRect bounds, bounds2;
REPORTER_ASSERT(reporter, p.isEmpty());
+ REPORTER_ASSERT(reporter, 0 == p.countPoints());
REPORTER_ASSERT(reporter, 0 == p.getSegmentMasks());
REPORTER_ASSERT(reporter, p.isConvex());
REPORTER_ASSERT(reporter, p.getFillType() == SkPath::kWinding_FillType);
@@ -555,18 +691,22 @@ void TestPath(skiatest::Reporter* reporter) {
check_convex_bounds(reporter, p, bounds);
// we have quads or cubics
REPORTER_ASSERT(reporter, p.getSegmentMasks() & kCurveSegmentMask);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
p.reset();
REPORTER_ASSERT(reporter, 0 == p.getSegmentMasks());
+ REPORTER_ASSERT(reporter, p.isEmpty());
p.addOval(bounds);
check_convex_bounds(reporter, p, bounds);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
p.reset();
p.addRect(bounds);
check_convex_bounds(reporter, p, bounds);
// we have only lines
REPORTER_ASSERT(reporter, SkPath::kLine_SegmentMask == p.getSegmentMasks());
+ REPORTER_ASSERT(reporter, !p.isEmpty());
REPORTER_ASSERT(reporter, p != p2);
REPORTER_ASSERT(reporter, !(p == p2));
@@ -599,7 +739,9 @@ void TestPath(skiatest::Reporter* reporter) {
p.moveTo(SK_Scalar1, 0);
p.getLastPt(&pt);
REPORTER_ASSERT(reporter, pt.fX == SK_Scalar1);
+ REPORTER_ASSERT(reporter, !p.isEmpty());
+ test_zero_length_paths(reporter);
test_convexity(reporter);
test_convexity2(reporter);
test_close(reporter);
@@ -608,12 +750,15 @@ void TestPath(skiatest::Reporter* reporter) {
p.moveTo(0, 0);
p.quadTo(100, 100, 200, 200);
REPORTER_ASSERT(reporter, SkPath::kQuad_SegmentMask == p.getSegmentMasks());
+ REPORTER_ASSERT(reporter, !p.isEmpty());
p.cubicTo(100, 100, 200, 200, 300, 300);
REPORTER_ASSERT(reporter, kCurveSegmentMask == p.getSegmentMasks());
+ REPORTER_ASSERT(reporter, !p.isEmpty());
p.reset();
p.moveTo(0, 0);
p.cubicTo(100, 100, 200, 200, 300, 300);
REPORTER_ASSERT(reporter, SkPath::kCubic_SegmentMask == p.getSegmentMasks());
+ REPORTER_ASSERT(reporter, !p.isEmpty());
test_flattening(reporter);
test_transform(reporter);