pathops remove degenerate line on close
If first and last point are very nearly the same,
pathops considers the contour closed, but SkPath
adds a degenerate line to connect the two.
Change pathops to emit the first point rather than
a point very nearly the same as the last point
in a contour to avoid the degenerate line.
TBR=reed@google.com
Bug: skia:8249
Change-Id: Ibcc18643c78db4955c9eda9ca90219aad81d56d5
Reviewed-on: https://skia-review.googlesource.com/147720
Reviewed-by: Cary Clark <caryclark@skia.org>
Commit-Queue: Cary Clark <caryclark@skia.org>
Auto-Submit: Cary Clark <caryclark@skia.org>
diff --git a/src/pathops/SkPathWriter.cpp b/src/pathops/SkPathWriter.cpp
index 797f42f..5729827 100644
--- a/src/pathops/SkPathWriter.cpp
+++ b/src/pathops/SkPathWriter.cpp
@@ -32,21 +32,21 @@
}
void SkPathWriter::conicTo(const SkPoint& pt1, const SkOpPtT* pt2, SkScalar weight) {
- this->update(pt2);
+ SkPoint pt2pt = this->update(pt2);
#if DEBUG_PATH_CONSTRUCTION
SkDebugf("path.conicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g);\n",
- pt1.fX, pt1.fY, pt2->fPt.fX, pt2->fPt.fY, weight);
+ pt1.fX, pt1.fY, pt2pt.fX, pt2pt.fY, weight);
#endif
- fCurrent.conicTo(pt1, pt2->fPt, weight);
+ fCurrent.conicTo(pt1, pt2pt, weight);
}
void SkPathWriter::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkOpPtT* pt3) {
- this->update(pt3);
+ SkPoint pt3pt = this->update(pt3);
#if DEBUG_PATH_CONSTRUCTION
SkDebugf("path.cubicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g,%1.9g);\n",
- pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3->fPt.fX, pt3->fPt.fY);
+ pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3pt.fX, pt3pt.fY);
#endif
- fCurrent.cubicTo(pt1, pt2, pt3->fPt);
+ fCurrent.cubicTo(pt1, pt2, pt3pt);
}
bool SkPathWriter::deferredLine(const SkOpPtT* pt) {
@@ -144,21 +144,28 @@
}
void SkPathWriter::quadTo(const SkPoint& pt1, const SkOpPtT* pt2) {
- this->update(pt2);
+ SkPoint pt2pt = this->update(pt2);
#if DEBUG_PATH_CONSTRUCTION
SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n",
- pt1.fX, pt1.fY, pt2->fPt.fX, pt2->fPt.fY);
+ pt1.fX, pt1.fY, pt2pt.fX, pt2pt.fY);
#endif
- fCurrent.quadTo(pt1, pt2->fPt);
+ fCurrent.quadTo(pt1, pt2pt);
}
-void SkPathWriter::update(const SkOpPtT* pt) {
+// if last point to be written matches the current path's first point, alter the
+// last to avoid writing a degenerate lineTo when the path is closed
+SkPoint SkPathWriter::update(const SkOpPtT* pt) {
if (!fDefer[1]) {
this->moveTo();
} else if (!this->matchedLast(fDefer[0])) {
this->lineTo();
}
+ SkPoint result = pt->fPt;
+ if (fFirstPtT && result != fFirstPtT->fPt && fFirstPtT->contains(pt)) {
+ result = fFirstPtT->fPt;
+ }
fDefer[0] = fDefer[1] = pt; // set both to know that there is not a pending deferred line
+ return result;
}
bool SkPathWriter::someAssemblyRequired() {
diff --git a/src/pathops/SkPathWriter.h b/src/pathops/SkPathWriter.h
index 6534b84..b3a26b4 100644
--- a/src/pathops/SkPathWriter.h
+++ b/src/pathops/SkPathWriter.h
@@ -41,7 +41,7 @@
void moveTo();
const SkTArray<SkPath>& partials() const { return fPartials; }
bool someAssemblyRequired();
- void update(const SkOpPtT* pt);
+ SkPoint update(const SkOpPtT* pt);
SkPath fCurrent; // contour under construction
SkTArray<SkPath> fPartials; // contours with mismatched starts and ends
diff --git a/tests/PathOpsSimplifyTest.cpp b/tests/PathOpsSimplifyTest.cpp
index 8a5c937..e758cfb 100644
--- a/tests/PathOpsSimplifyTest.cpp
+++ b/tests/PathOpsSimplifyTest.cpp
@@ -9267,11 +9267,39 @@
testSimplifyFail(reporter, path, filename);
}
+static void bug8249(skiatest::Reporter* reporter, const char* filename) {
+SkPath path;
+path.setFillType(SkPath::kWinding_FillType);
+path.moveTo(SkBits2Float(0x43310000), SkBits2Float(0x43810000)); // 177, 258
+path.lineTo(SkBits2Float(0x43480000), SkBits2Float(0x43868000)); // 200, 269
+path.cubicTo(SkBits2Float(0x43480000), SkBits2Float(0x43b20000), SkBits2Float(0x437a0000), SkBits2Float(0x43cd0000), SkBits2Float(0x43c80000), SkBits2Float(0x43cd0000)); // 200, 356, 250, 410, 400, 410
+path.cubicTo(SkBits2Float(0x44098000), SkBits2Float(0x43cd0000), SkBits2Float(0x44160000), SkBits2Float(0x43b20000), SkBits2Float(0x44160000), SkBits2Float(0x43868000)); // 550, 410, 600, 356, 600, 269
+path.lineTo(SkBits2Float(0x44160000), SkBits2Float(0x43808000)); // 600, 257
+path.cubicTo(SkBits2Float(0x44160000), SkBits2Float(0x43330000), SkBits2Float(0x44110000), SkBits2Float(0x429c0000), SkBits2Float(0x43cd0000), SkBits2Float(0x429c0000)); // 600, 179, 580, 78, 410, 78
+path.cubicTo(SkBits2Float(0x43700000), SkBits2Float(0x429c0000), SkBits2Float(0x43480000), SkBits2Float(0x431f0000), SkBits2Float(0x43480000), SkBits2Float(0x438a8000)); // 240, 78, 200, 159, 200, 277
+path.lineTo(SkBits2Float(0x43480000), SkBits2Float(0x4401c000)); // 200, 519
+path.cubicTo(SkBits2Float(0x43480000), SkBits2Float(0x441f0000), SkBits2Float(0x43660000), SkBits2Float(0x44340000), SkBits2Float(0x43c80000), SkBits2Float(0x44340000)); // 200, 636, 230, 720, 400, 720
+path.cubicTo(SkBits2Float(0x4404c000), SkBits2Float(0x44340000), SkBits2Float(0x440d0000), SkBits2Float(0x442b8000), SkBits2Float(0x44118000), SkBits2Float(0x4416c000)); // 531, 720, 564, 686, 582, 603
+path.lineTo(SkBits2Float(0x442cc000), SkBits2Float(0x441c8000)); // 691, 626
+path.cubicTo(SkBits2Float(0x44260000), SkBits2Float(0x443d4000), SkBits2Float(0x44114000), SkBits2Float(0x444a8000), SkBits2Float(0x43c88000), SkBits2Float(0x444a8000)); // 664, 757, 581, 810, 401, 810
+path.cubicTo(SkBits2Float(0x43350000), SkBits2Float(0x444a8000), SkBits2Float(0x42c80000), SkBits2Float(0x442e0000), SkBits2Float(0x42c80000), SkBits2Float(0x4401c000)); // 181, 810, 100, 696, 100, 519
+path.lineTo(SkBits2Float(0x42c80000), SkBits2Float(0x438a8000)); // 100, 277
+path.cubicTo(SkBits2Float(0x42c80000), SkBits2Float(0x42cc0000), SkBits2Float(0x433e0000), SkBits2Float(0xc1200000), SkBits2Float(0x43cd0000), SkBits2Float(0xc1200000)); // 100, 102, 190, -10, 410, -10
+path.cubicTo(SkBits2Float(0x441d8000), SkBits2Float(0xc1200000), SkBits2Float(0x442f0000), SkBits2Float(0x42e60000), SkBits2Float(0x442f0000), SkBits2Float(0x437a0000)); // 630, -10, 700, 115, 700, 250
+path.lineTo(SkBits2Float(0x442f0000), SkBits2Float(0x43880000)); // 700, 272
+path.cubicTo(SkBits2Float(0x442f0000), SkBits2Float(0x43d18000), SkBits2Float(0x44164000), SkBits2Float(0x43fa0000), SkBits2Float(0x43c88000), SkBits2Float(0x43fa0000)); // 700, 419, 601, 500, 401, 500
+path.cubicTo(SkBits2Float(0x43490000), SkBits2Float(0x43fa0000), SkBits2Float(0x43160000), SkBits2Float(0x43d00000), SkBits2Float(0x43160000), SkBits2Float(0x43868000)); // 201, 500, 150, 416, 150, 269
+path.lineTo(SkBits2Float(0x43310000), SkBits2Float(0x43810000)); // 177, 258
+path.close();
+testSimplify(reporter, path, filename);
+}
+
static void (*skipTest)(skiatest::Reporter* , const char* filename) = nullptr;
static void (*firstTest)(skiatest::Reporter* , const char* filename) = nullptr;
static void (*stopTest)(skiatest::Reporter* , const char* filename) = nullptr;
static TestDesc tests[] = {
+ TEST(bug8249),
TEST(grshapearc),
TEST(coincubics),
TEST(joel_16x),