diff options
-rw-r--r-- | gm/cubicpaths.cpp | 189 | ||||
-rw-r--r-- | gm/degeneratesegments.cpp | 398 | ||||
-rw-r--r-- | gm/linepaths.cpp | 181 | ||||
-rw-r--r-- | gm/movepaths.cpp | 180 | ||||
-rw-r--r-- | gm/quadpaths.cpp | 185 | ||||
-rw-r--r-- | gyp/gmslides.gypi | 5 | ||||
-rw-r--r-- | include/core/SkPath.h | 32 | ||||
-rw-r--r-- | include/core/SkPoint.h | 7 | ||||
-rw-r--r-- | src/core/SkPath.cpp | 155 | ||||
-rw-r--r-- | src/core/SkStroke.cpp | 18 | ||||
-rw-r--r-- | tests/PathTest.cpp | 145 |
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); |