fix pathops fuzz failures
If a curve has the identical start and control points, the
initial or final tangent can't be trivally determined. The
perpendicular to the tangent is used to measure coincidence.
Add logic for cubics, quadratics, and conics, to use the
secondary control points or the end points if the initial
control point alone can't determine the tangent.
Add debugging (currently untriggered by exhaustive testing)
to detect zero-length tangents which are not at the curve
endpoints.
Increase the number of temporary intersecions gathered from
10 to 12 but reduce the max passed in by cubic intersection from
27 to 12. Also, add checks if the max passed exceeds the
storage allocated.
When cleaning up parallel lines, choose the intersection which
is on the end of both segments over the intersection which
is on the end of a single segment.
TBR=reed@google.com
BUG=425140,516266
Review URL: https://codereview.chromium.org/1288863004
diff --git a/src/pathops/SkDLineIntersection.cpp b/src/pathops/SkDLineIntersection.cpp
index 5fd8e7f..bfbbc7e 100644
--- a/src/pathops/SkDLineIntersection.cpp
+++ b/src/pathops/SkDLineIntersection.cpp
@@ -12,11 +12,16 @@
removeOne(1);
}
if (fUsed == 2 && !parallel) {
- bool startMatch = fT[0][0] == 0 || fT[1][0] == 0 || fT[1][0] == 1;
- bool endMatch = fT[0][1] == 1 || fT[1][1] == 0 || fT[1][1] == 1;
+ bool startMatch = fT[0][0] == 0 || zero_or_one(fT[1][0]);
+ bool endMatch = fT[0][1] == 1 || zero_or_one(fT[1][1]);
if ((!startMatch && !endMatch) || approximately_equal(fT[0][0], fT[0][1])) {
SkASSERT(startMatch || endMatch);
- removeOne(endMatch);
+ if (startMatch && endMatch && (fT[0][0] != 0 || !zero_or_one(fT[1][0]))
+ && fT[0][1] == 1 && zero_or_one(fT[1][1])) {
+ removeOne(0);
+ } else {
+ removeOne(endMatch);
+ }
}
}
if (fUsed == 2) {
diff --git a/src/pathops/SkIntersections.cpp b/src/pathops/SkIntersections.cpp
index 7caf04b..9683796 100644
--- a/src/pathops/SkIntersections.cpp
+++ b/src/pathops/SkIntersections.cpp
@@ -87,6 +87,7 @@
fT[0][index] = one;
fT[1][index] = two;
++fUsed;
+ SkASSERT(fUsed <= SK_ARRAY_COUNT(fPt));
return index;
}
diff --git a/src/pathops/SkIntersections.h b/src/pathops/SkIntersections.h
index c12db38..ac9276b 100644
--- a/src/pathops/SkIntersections.h
+++ b/src/pathops/SkIntersections.h
@@ -188,6 +188,7 @@
}
void setMax(int max) {
+ SkASSERT(max <= (int) SK_ARRAY_COUNT(fPt));
fMax = max;
}
@@ -286,9 +287,9 @@
void cleanUpParallelLines(bool parallel);
void computePoints(const SkDLine& line, int used);
- SkDPoint fPt[10]; // FIXME: since scans store points as SkPoint, this should also
+ SkDPoint fPt[12]; // FIXME: since scans store points as SkPoint, this should also
SkDPoint fPt2[2]; // used by nearly same to store alternate intersection point
- double fT[2][10];
+ double fT[2][12];
uint16_t fIsCoincident[2]; // bit set for each curve's coincident T
bool fNearlySame[2]; // true if end points nearly match
unsigned char fUsed;
diff --git a/src/pathops/SkPathOpsConic.cpp b/src/pathops/SkPathOpsConic.cpp
index 82353d6..013136b 100644
--- a/src/pathops/SkPathOpsConic.cpp
+++ b/src/pathops/SkPathOpsConic.cpp
@@ -48,6 +48,14 @@
conic_eval_tan(&fPts[0].fX, fWeight, t),
conic_eval_tan(&fPts[0].fY, fWeight, t)
};
+ if (result.fX == 0 && result.fY == 0) {
+ if (zero_or_one(t)) {
+ result = fPts[2] - fPts[0];
+ } else {
+ // incomplete
+ SkDebugf("!k");
+ }
+ }
return result;
}
diff --git a/src/pathops/SkPathOpsCubic.cpp b/src/pathops/SkPathOpsCubic.cpp
index 972c510..f82bc35 100644
--- a/src/pathops/SkPathOpsCubic.cpp
+++ b/src/pathops/SkPathOpsCubic.cpp
@@ -474,6 +474,19 @@
// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t?
SkDVector SkDCubic::dxdyAtT(double t) const {
SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) };
+ if (result.fX == 0 && result.fY == 0) {
+ if (t == 0) {
+ result = fPts[2] - fPts[0];
+ } else if (t == 1) {
+ result = fPts[3] - fPts[1];
+ } else {
+ // incomplete
+ SkDebugf("!c");
+ }
+ if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) {
+ result = fPts[3] - fPts[0];
+ }
+ }
return result;
}
diff --git a/src/pathops/SkPathOpsQuad.cpp b/src/pathops/SkPathOpsQuad.cpp
index 717d8bc..12b9658 100644
--- a/src/pathops/SkPathOpsQuad.cpp
+++ b/src/pathops/SkPathOpsQuad.cpp
@@ -161,6 +161,14 @@
double c = t;
SkDVector result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX,
a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY };
+ if (result.fX == 0 && result.fY == 0) {
+ if (zero_or_one(t)) {
+ result = fPts[2] - fPts[0];
+ } else {
+ // incomplete
+ SkDebugf("!q");
+ }
+ }
return result;
}
diff --git a/src/pathops/SkPathOpsTSect.h b/src/pathops/SkPathOpsTSect.h
index 4cf5978..50e1279 100644
--- a/src/pathops/SkPathOpsTSect.h
+++ b/src/pathops/SkPathOpsTSect.h
@@ -1907,7 +1907,7 @@
SkDEBUGCODE(sect1->fOppSect = sect2);
SkDEBUGCODE(sect2->fOppSect = sect1);
intersections->reset();
- intersections->setMax(TCurve::kMaxIntersections * 3); // give extra for slop
+ intersections->setMax(TCurve::kMaxIntersections + 3); // give extra for slop
SkTSpan<TCurve, OppCurve>* span1 = sect1->fHead;
SkTSpan<OppCurve, TCurve>* span2 = sect2->fHead;
int oppSect, sect = sect1->intersects(span1, sect2, span2, &oppSect);
diff --git a/tests/PathOpsCubicIntersectionTest.cpp b/tests/PathOpsCubicIntersectionTest.cpp
index 138e6f4..75a5cc3 100644
--- a/tests/PathOpsCubicIntersectionTest.cpp
+++ b/tests/PathOpsCubicIntersectionTest.cpp
@@ -653,6 +653,11 @@
}
static const SkDCubic coinSet[] = {
+ {{{297.04998779296875, 43.928997039794922}, {297.04998779296875, 43.928997039794922},
+ {300.69699096679688, 45.391998291015625}, {306.92498779296875, 43.08599853515625}}},
+ {{{297.04998779296875, 43.928997039794922}, {297.04998779296875, 43.928997039794922},
+ {300.69699096679688, 45.391998291015625}, {306.92498779296875, 43.08599853515625}}},
+
{{{2, 3}, {0, 4}, {3, 2}, {5, 3}}},
{{{2, 3}, {0, 4}, {3, 2}, {5, 3}}},
diff --git a/tests/PathOpsOpTest.cpp b/tests/PathOpsOpTest.cpp
index 637bd04..fbe0116 100644
--- a/tests/PathOpsOpTest.cpp
+++ b/tests/PathOpsOpTest.cpp
@@ -5153,13 +5153,25 @@
testPathOp(reporter, path, pathB, kUnion_SkPathOp, filename);
}
+static void fuzz38(skiatest::Reporter* reporter, const char* filename) {
+ SkPath path, pathB;
+ path.moveTo(100.34f, 303.312f);
+ path.lineTo(-1e+08, 303.312f);
+ path.lineTo(102, 310.156f);
+ path.lineTo(100.34f, 310.156f);
+ path.lineTo(100.34f, 303.312f);
+ path.close();
+ testPathOpCheck(reporter, path, pathB, kUnion_SkPathOp, filename, FLAGS_runFail);
+}
+
static void (*skipTest)(skiatest::Reporter* , const char* filename) = 0;
-static void (*firstTest)(skiatest::Reporter* , const char* filename) = loops63i;
+static void (*firstTest)(skiatest::Reporter* , const char* filename) = 0;
static void (*stopTest)(skiatest::Reporter* , const char* filename) = 0;
#define TEST(name) { name, #name }
static struct TestDesc tests[] = {
+ TEST(fuzz38),
TEST(cubics44d),
TEST(cubics45u),
TEST(loops61i),