'../tests/PathOpsQuadParameterizationTest.cpp',
'../tests/PathOpsQuadReduceOrderTest.cpp',
'../tests/PathOpsSimplifyDegenerateThreadedTest.cpp',
+ '../tests/PathOpsSimplifyFailTest.cpp',
'../tests/PathOpsSimplifyQuadralateralsThreadedTest.cpp',
'../tests/PathOpsSimplifyQuadThreadedTest.cpp',
'../tests/PathOpsSimplifyRectThreadedTest.cpp',
kReverseDifference_PathOp, //!< subtract the first path from the op path
};
-/**
- * Set this path to the result of applying the Op to this path and the
- * specified path: this = (this op operand). The resulting path will be constructed
- * from non-overlapping contours. The curve order is reduced where possible so that cubics may
- * be turned into quadratics, and quadratics maybe turned into lines.
+/** Set this path to the result of applying the Op to this path and the
+ specified path: this = (this op operand).
+ The resulting path will be constructed from non-overlapping contours.
+ The curve order is reduced where possible so that cubics may be turned
+ into quadratics, and quadratics maybe turned into lines.
+
+ Returns true if operation was able to produce a result;
+ otherwise, result is unmodified.
+
+ @param one The first operand (for difference, the minuend)
+ @param two The second operand (for difference, the subtrahend)
+ @param result The product of the operands. The result may be one of the
+ inputs.
+ @return True if operation succeeded.
*/
-void Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result);
+bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result);
-/**
- * Set this path to a set of non-overlapping contours that describe the same
- * area as the original path. The curve order is reduced where possible so that cubics may
- * be turned into quadratics, and quadratics maybe turned into lines.
+/** Set this path to a set of non-overlapping contours that describe the
+ same area as the original path.
+ The curve order is reduced where possible so that cubics may
+ be turned into quadratics, and quadratics maybe turned into lines.
+
+ Returns true if operation was able to produce a result;
+ otherwise, result is unmodified.
+
+ @param path The path to simplify.
+ @param result The simplified path. The result may be the input.
+ @return True if simplification succeeded.
*/
-void Simplify(const SkPath& path, SkPath* result);
+bool Simplify(const SkPath& path, SkPath* result);
#endif
gContourID = 0;
gSegmentID = 0;
#endif
+ fUnparseable = false;
fSecondHalf = preFetch();
}
preFetch();
}
-void SkOpEdgeBuilder::finish() {
- walk();
+bool SkOpEdgeBuilder::finish() {
+ if (fUnparseable || !walk()) {
+ return false;
+ }
complete();
if (fCurrentContour && !fCurrentContour->segments().count()) {
fContours.pop_back();
&fReducePts[rIndex]);
}
fExtra.reset(); // we're done with this
+ return true;
}
// Note that copying the points here avoids copying the resulting path later.
// OPTIMIZATION: This copies both sets of input points every time. If the input data was read
// directly, the output path would only need to be copied if it was also one of the input paths.
int SkOpEdgeBuilder::preFetch() {
+ if (!fPath->isFinite()) {
+ fUnparseable = true;
+ return 0;
+ }
SkPath::RawIter iter(*fPath);
SkPoint pts[4];
SkPath::Verb verb;
return fPathVerbs.count() - 1;
}
-void SkOpEdgeBuilder::walk() {
+bool SkOpEdgeBuilder::close() {
+ if (fFinalCurveStart && fFinalCurveEnd && *fFinalCurveStart != *fFinalCurveEnd) {
+ *fReducePts.append() = *fFinalCurveStart;
+ *fReducePts.append() = *fFinalCurveEnd;
+ const SkPoint* lineStart = fReducePts.end() - 2;
+ *fExtra.append() = fCurrentContour->addLine(lineStart);
+ }
+ complete();
+ return true;
+}
+
+bool SkOpEdgeBuilder::walk() {
SkPath::Verb reducedVerb;
uint8_t* verbPtr = fPathVerbs.begin();
uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf];
const SkPoint* pointsPtr = fPathPts.begin();
- const SkPoint* finalCurveStart = NULL;
- const SkPoint* finalCurveEnd = NULL;
SkPath::Verb verb;
+ fFinalCurveStart = NULL;
+ fFinalCurveEnd = NULL;
while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) {
if (verbPtr == endOfFirstHalf) {
fOperand = true;
verbPtr++;
switch (verb) {
case SkPath::kMove_Verb:
- complete();
+ if (fCurrentContour) {
+ if (fAllowOpenContours) {
+ complete();
+ } else if (!close()) {
+ return false;
+ }
+ }
if (!fCurrentContour) {
fCurrentContour = fContours.push_back_n(1);
fCurrentContour->setOperand(fOperand);
fCurrentContour->setXor(fXorMask[fOperand] == kEvenOdd_PathOpsMask);
*fExtra.append() = -1; // start new contour
}
- finalCurveEnd = pointsPtr++;
+ fFinalCurveEnd = pointsPtr++;
continue;
- case SkPath::kLine_Verb:
+ case SkPath::kLine_Verb: {
+ const SkPoint& lineEnd = pointsPtr[0];
+ const SkPoint& lineStart = pointsPtr[-1];
// skip degenerate points
- if (pointsPtr[-1].fX != pointsPtr[0].fX || pointsPtr[-1].fY != pointsPtr[0].fY) {
- fCurrentContour->addLine(&pointsPtr[-1]);
+ if (lineStart.fX != lineEnd.fX || lineStart.fY != lineEnd.fY) {
+ fCurrentContour->addLine(&lineStart);
}
- break;
- case SkPath::kQuad_Verb:
- reducedVerb = SkReduceOrder::Quad(&pointsPtr[-1], &fReducePts);
+ } break;
+ case SkPath::kQuad_Verb: {
+ const SkPoint* quadStart = &pointsPtr[-1];
+ reducedVerb = SkReduceOrder::Quad(quadStart, &fReducePts);
if (reducedVerb == 0) {
break; // skip degenerate points
}
if (reducedVerb == 1) {
- *fExtra.append() =
- fCurrentContour->addLine(fReducePts.end() - 2);
+ const SkPoint* lineStart = fReducePts.end() - 2;
+ *fExtra.append() = fCurrentContour->addLine(lineStart);
break;
}
- fCurrentContour->addQuad(&pointsPtr[-1]);
- break;
- case SkPath::kCubic_Verb:
- reducedVerb = SkReduceOrder::Cubic(&pointsPtr[-1], &fReducePts);
+ fCurrentContour->addQuad(quadStart);
+ } break;
+ case SkPath::kCubic_Verb: {
+ const SkPoint* cubicStart = &pointsPtr[-1];
+ reducedVerb = SkReduceOrder::Cubic(cubicStart, &fReducePts);
if (reducedVerb == 0) {
break; // skip degenerate points
}
if (reducedVerb == 1) {
- *fExtra.append() = fCurrentContour->addLine(fReducePts.end() - 2);
+ const SkPoint* lineStart = fReducePts.end() - 2;
+ *fExtra.append() = fCurrentContour->addLine(lineStart);
break;
}
if (reducedVerb == 2) {
- *fExtra.append() = fCurrentContour->addQuad(fReducePts.end() - 3);
+ const SkPoint* quadStart = fReducePts.end() - 3;
+ *fExtra.append() = fCurrentContour->addQuad(quadStart);
break;
}
- fCurrentContour->addCubic(&pointsPtr[-1]);
- break;
+ fCurrentContour->addCubic(cubicStart);
+ } break;
case SkPath::kClose_Verb:
SkASSERT(fCurrentContour);
- if (finalCurveStart && finalCurveEnd
- && *finalCurveStart != *finalCurveEnd) {
- *fReducePts.append() = *finalCurveStart;
- *fReducePts.append() = *finalCurveEnd;
- *fExtra.append() = fCurrentContour->addLine(fReducePts.end() - 2);
+ if (!close()) {
+ return false;
}
- complete();
continue;
default:
SkDEBUGFAIL("bad verb");
- return;
+ return false;
}
- finalCurveStart = &pointsPtr[verb - 1];
+ fFinalCurveStart = &pointsPtr[verb - 1];
pointsPtr += verb;
SkASSERT(fCurrentContour);
}
+ if (fCurrentContour && !fAllowOpenContours && !close()) {
+ return false;
+ }
+ return true;
}
public:
SkOpEdgeBuilder(const SkPathWriter& path, SkTArray<SkOpContour>& contours)
: fPath(path.nativePath())
- , fContours(contours) {
+ , fContours(contours)
+ , fAllowOpenContours(true) {
init();
}
SkOpEdgeBuilder(const SkPath& path, SkTArray<SkOpContour>& contours)
: fPath(&path)
- , fContours(contours) {
+ , fContours(contours)
+ , fAllowOpenContours(false) {
init();
}
}
void addOperand(const SkPath& path);
- void finish();
+ bool finish();
void init();
private:
+ bool close();
int preFetch();
- void walk();
+ bool walk();
const SkPath* fPath;
- SkTDArray<SkPoint> fPathPts; // FIXME: point directly to path pts instead
- SkTDArray<uint8_t> fPathVerbs; // FIXME: remove
+ SkTDArray<SkPoint> fPathPts;
+ SkTDArray<uint8_t> fPathVerbs;
SkOpContour* fCurrentContour;
SkTArray<SkOpContour>& fContours;
SkTDArray<SkPoint> fReducePts; // segments created on the fly
SkTDArray<int> fExtra; // -1 marks new contour, > 0 offsets into contour
SkPathOpsMask fXorMask[2];
+ const SkPoint* fFinalCurveStart;
+ const SkPoint* fFinalCurveEnd;
int fSecondHalf;
bool fOperand;
+ bool fAllowOpenContours;
+ bool fUnparseable;
};
#endif
{{ false, true }, { false, false }}, // rev diff
};
-void Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
+bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()];
SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()]
? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType;
SkOpEdgeBuilder builder(*minuend, contours);
const int xorMask = builder.xorMask();
builder.addOperand(*subtrahend);
- builder.finish();
+ if (!builder.finish()) {
+ return false;
+ }
result->reset();
result->setFillType(fillType);
const int xorOpMask = builder.xorMask();
xorOpMask == kEvenOdd_PathOpsMask);
SkOpContour** currentPtr = contourList.begin();
if (!currentPtr) {
- return;
+ return true;
}
SkOpContour** listEnd = contourList.end();
// find all intersections between segments
SkPathWriter assembled(temp);
Assemble(wrapper, &assembled);
*result = *assembled.nativePath();
+ result->setFillType(fillType);
}
+ return true;
}
}
// FIXME : add this as a member of SkPath
-void Simplify(const SkPath& path, SkPath* result) {
+bool Simplify(const SkPath& path, SkPath* result) {
#if DEBUG_SORT || DEBUG_SWAP_TOP
gDebugSortCount = gDebugSortCountDefault;
#endif
// returns 1 for evenodd, -1 for winding, regardless of inverse-ness
- result->reset();
SkPath::FillType fillType = path.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType
: SkPath::kEvenOdd_FillType;
- result->setFillType(fillType);
- SkPathWriter simple(*result);
// turn path into list of segments
SkTArray<SkOpContour> contours;
SkOpEdgeBuilder builder(path, contours);
- builder.finish();
+ if (!builder.finish()) {
+ return false;
+ }
SkTDArray<SkOpContour*> contourList;
MakeContourList(contours, contourList, false, false);
SkOpContour** currentPtr = contourList.begin();
+ result->setFillType(fillType);
+ result->reset();
if (!currentPtr) {
- return;
+ return true;
}
SkOpContour** listEnd = contourList.end();
// find all intersections between segments
DebugShowActiveSpans(contourList);
#endif
// construct closed contours
+ SkPathWriter simple(*result);
if (builder.xorMask() == kWinding_PathOpsMask ? bridgeWinding(contourList, &simple)
: !bridgeXor(contourList, &simple))
{ // if some edges could not be resolved, assemble remaining fragments
SkPathWriter assembled(temp);
Assemble(simple, &assembled);
*result = *assembled.nativePath();
+ result->setFillType(fillType);
}
+ return true;
}
if (gShowPath) {
showPath(path);
}
- Simplify(path, &out);
+ if (!Simplify(path, &out)) {
+ SkDebugf("%s did not expect failure\n", __FUNCTION__);
+ REPORTER_ASSERT(state.fReporter, 0);
+ return false;
+ }
if (!gComparePaths) {
return true;
}
showPathData(path);
#endif
SkPath out;
- Simplify(path, &out);
+ if (!Simplify(path, &out)) {
+ SkDebugf("%s did not expect failure\n", __FUNCTION__);
+ REPORTER_ASSERT(reporter, 0);
+ return false;
+ }
SkBitmap bitmap;
int result = comparePaths(reporter, path, out, bitmap);
if (result && gPathStrAssert) {
showPathData(b);
#endif
SkPath out;
- Op(a, b, shapeOp, &out);
+ if (!Op(a, b, shapeOp, &out) ) {
+ SkDebugf("%s did not expect failure\n", __FUNCTION__);
+ REPORTER_ASSERT(reporter, 0);
+ return false;
+ }
SkPath pathOut, scaledPathOut;
SkRegion rgnA, rgnB, openClip, rgnOut;
openClip.setRect(-16000, -16000, 16000, 16000);
--- /dev/null
+/*
+ * Copyright 2013 Google Inc.
+ *
+ * Use of this source code is governed by a BSD-style license that can be
+ * found in the LICENSE file.
+ */
+#include "SkPathOps.h"
+#include "SkPath.h"
+#include "SkPoint.h"
+#include "Test.h"
+
+static const SkPoint nonFinitePts[] = {
+ { SK_ScalarInfinity, 0 },
+ { 0, SK_ScalarInfinity },
+ { SK_ScalarInfinity, SK_ScalarInfinity },
+ { SK_ScalarNegativeInfinity, 0},
+ { 0, SK_ScalarNegativeInfinity },
+ { SK_ScalarNegativeInfinity, SK_ScalarNegativeInfinity },
+ { SK_ScalarNegativeInfinity, SK_ScalarInfinity },
+ { SK_ScalarInfinity, SK_ScalarNegativeInfinity },
+ { SK_ScalarNaN, 0 },
+ { 0, SK_ScalarNaN },
+ { SK_ScalarNaN, SK_ScalarNaN },
+};
+
+const size_t nonFinitePtsCount = sizeof(nonFinitePts) / sizeof(nonFinitePts[0]);
+
+static const SkPoint finitePts[] = {
+ { 0, 0 },
+ { SK_ScalarMax, 0 },
+ { 0, SK_ScalarMax },
+ { SK_ScalarMax, SK_ScalarMax },
+ { SK_ScalarMin, 0 },
+ { 0, SK_ScalarMin },
+ { SK_ScalarMin, SK_ScalarMin },
+};
+
+const size_t finitePtsCount = sizeof(finitePts) / sizeof(finitePts[0]);
+
+static void PathOpsSimplifyFailTest(skiatest::Reporter* reporter) {
+ for (int index = 0; index < (int) (13 * nonFinitePtsCount * finitePtsCount); ++index) {
+ SkPath path;
+ int i = (int) (index % nonFinitePtsCount);
+ int f = (int) (index % finitePtsCount);
+ int g = (int) ((f + 1) % finitePtsCount);
+ switch (index % 13) {
+ case 0: path.lineTo(nonFinitePts[i]); break;
+ case 1: path.quadTo(nonFinitePts[i], nonFinitePts[i]); break;
+ case 2: path.quadTo(nonFinitePts[i], finitePts[f]); break;
+ case 3: path.quadTo(finitePts[f], nonFinitePts[i]); break;
+ case 4: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[f]); break;
+ case 5: path.cubicTo(finitePts[f], nonFinitePts[i], finitePts[f]); break;
+ case 6: path.cubicTo(finitePts[f], finitePts[f], nonFinitePts[i]); break;
+ case 7: path.cubicTo(nonFinitePts[i], nonFinitePts[i], finitePts[f]); break;
+ case 8: path.cubicTo(nonFinitePts[i], finitePts[f], nonFinitePts[i]); break;
+ case 9: path.cubicTo(finitePts[f], nonFinitePts[i], nonFinitePts[i]); break;
+ case 10: path.cubicTo(nonFinitePts[i], nonFinitePts[i], nonFinitePts[i]); break;
+ case 11: path.cubicTo(nonFinitePts[i], finitePts[f], finitePts[g]); break;
+ case 12: path.moveTo(nonFinitePts[i]); break;
+ }
+ SkPath result;
+ result.setFillType(SkPath::kWinding_FillType);
+ bool success = Simplify(path, &result);
+ REPORTER_ASSERT(reporter, !success);
+ REPORTER_ASSERT(reporter, result.isEmpty());
+ REPORTER_ASSERT(reporter, result.getFillType() == SkPath::kWinding_FillType);
+ reporter->bumpTestCount();
+ }
+ for (int index = 0; index < (int) (11 * finitePtsCount); ++index) {
+ SkPath path;
+ int f = (int) (index % finitePtsCount);
+ int g = (int) ((f + 1) % finitePtsCount);
+ switch (index % 11) {
+ case 0: path.lineTo(finitePts[f]); break;
+ case 1: path.quadTo(finitePts[f], finitePts[f]); break;
+ case 2: path.quadTo(finitePts[f], finitePts[g]); break;
+ case 3: path.quadTo(finitePts[g], finitePts[f]); break;
+ case 4: path.cubicTo(finitePts[f], finitePts[f], finitePts[f]); break;
+ case 5: path.cubicTo(finitePts[f], finitePts[f], finitePts[g]); break;
+ case 6: path.cubicTo(finitePts[f], finitePts[g], finitePts[f]); break;
+ case 7: path.cubicTo(finitePts[f], finitePts[g], finitePts[g]); break;
+ case 8: path.cubicTo(finitePts[g], finitePts[f], finitePts[f]); break;
+ case 9: path.cubicTo(finitePts[g], finitePts[f], finitePts[g]); break;
+ case 10: path.moveTo(finitePts[f]); break;
+ }
+ SkPath result;
+ result.setFillType(SkPath::kWinding_FillType);
+ bool success = Simplify(path, &result);
+ REPORTER_ASSERT(reporter, success);
+ REPORTER_ASSERT(reporter, result.getFillType() != SkPath::kWinding_FillType);
+ reporter->bumpTestCount();
+ }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS_SHORT(PathOpsSimplifyFailTest)
testSimplify(reporter, path);
}
+// A test this for this case:
+// contourA has two segments that are coincident
+// contourB has two segments that are coincident in the same place
+// each ends up with +2/0 pairs for winding count
+// since logic in OpSegment::addTCoincident doesn't transfer count (only increments/decrements)
+// can this be resolved to +4/0 ?
+static void testAddTCoincident1(skiatest::Reporter* reporter) {
+ SkPath path;
+ path.moveTo(2, 0);
+ path.lineTo(2, 2);
+ path.lineTo(1, 1);
+ path.lineTo(2, 0);
+ path.lineTo(2, 2);
+ path.lineTo(1, 1);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(2, 2);
+ path.lineTo(3, 1);
+ path.lineTo(2, 0);
+ path.lineTo(2, 2);
+ path.lineTo(3, 1);
+ path.close();
+ testSimplify(reporter, path);
+}
+
+// test with implicit close
+static void testAddTCoincident2(skiatest::Reporter* reporter) {
+ SkPath path;
+ path.moveTo(2, 0);
+ path.lineTo(2, 2);
+ path.lineTo(1, 1);
+ path.lineTo(2, 0);
+ path.lineTo(2, 2);
+ path.lineTo(1, 1);
+ path.moveTo(2, 0);
+ path.lineTo(2, 2);
+ path.lineTo(3, 1);
+ path.lineTo(2, 0);
+ path.lineTo(2, 2);
+ path.lineTo(3, 1);
+ testSimplify(reporter, path);
+}
+
static void (*firstTest)(skiatest::Reporter* ) = 0;
static TestDesc tests[] = {
+ TEST(testAddTCoincident2),
+ TEST(testAddTCoincident1),
TEST(testTriangles2),
TEST(testTriangles1),
TEST(testQuadratic97),