Skip to content

Commit

Permalink
fix pathops stitching bug
Browse files Browse the repository at this point in the history
Tight data generates intersections that are currently
unsortable. Edges that can be sorted are added; simple
connected edges need to be added as well.

Look for output edges with gaps and add simple edges that
continue at the ends to reduce the gap size.

Extended tests with region check (pathops_unittest -V -x)
pass in debug and release.

TBR=reed@google.com

Bug: skia:8223
Change-Id: Ib0f22061ae3676e1a3b94574516a61cbbea2948f
Reviewed-on: https://skia-review.googlesource.com/145644
Reviewed-by: Cary Clark <caryclark@skia.org>
Commit-Queue: Cary Clark <caryclark@skia.org>
  • Loading branch information
Cary Clark authored and Skia Commit-Bot committed Aug 6, 2018
1 parent 82c11e0 commit cadc506
Show file tree
Hide file tree
Showing 6 changed files with 318 additions and 531 deletions.
6 changes: 5 additions & 1 deletion src/pathops/SkOpSegment.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,9 @@ bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sum

bool SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end,
SkPathWriter* path) const {
FAIL_IF(start->starter(end)->alreadyAdded());
const SkOpSpan* spanStart = start->starter(end);
FAIL_IF(spanStart->alreadyAdded());
const_cast<SkOpSpan*>(spanStart)->markAdded();
SkDCurveSweep curvePart;
start->segment()->subDivide(start, end, &curvePart.fCurve);
curvePart.setCurveHullSweep(fVerb);
Expand Down Expand Up @@ -951,6 +953,7 @@ bool SkOpSegment::markAngle(int maxWinding, int sumWinding, const SkOpAngle* ang
return false;
}
#if DEBUG_WINDING
SkOpSpanBase* last = *result;
if (last) {
SkDebugf("%s last seg=%d span=%d", __FUNCTION__,
last->segment()->debugID(), last->debugID());
Expand Down Expand Up @@ -978,6 +981,7 @@ bool SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding,
return false;
}
#if DEBUG_WINDING
SkOpSpanBase* last = *result;
if (last) {
SkDebugf("%s last segment=%d span=%d", __FUNCTION__,
last->segment()->debugID(), last->debugID());
Expand Down
2 changes: 1 addition & 1 deletion src/pathops/SkOpSegment.h
Original file line number Diff line number Diff line change
Expand Up @@ -251,7 +251,7 @@ class SkOpSegment {
return fBounds.fTop == fBounds.fBottom;
}

SkOpSegment* isSimple(SkOpSpanBase** end, int* step) {
SkOpSegment* isSimple(SkOpSpanBase** end, int* step) const {
return nextChase(end, step, nullptr, nullptr);
}

Expand Down
7 changes: 5 additions & 2 deletions src/pathops/SkOpSpan.h
Original file line number Diff line number Diff line change
Expand Up @@ -421,7 +421,6 @@ class SkOpSpan : public SkOpSpanBase {
if (fAlreadyAdded) {
return true;
}
fAlreadyAdded = true;
return false;
}

Expand Down Expand Up @@ -489,6 +488,10 @@ class SkOpSpan : public SkOpSpanBase {
return fCoincident != this;
}

void markAdded() {
fAlreadyAdded = true;
}

SkOpSpanBase* next() const {
SkASSERT(!final());
return fNext;
Expand Down Expand Up @@ -569,7 +572,7 @@ class SkOpSpan : public SkOpSpanBase {
int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here
int fTopTTry; // specifies direction and t value to try next
bool fDone; // if set, this span to next higher T has been processed
mutable bool fAlreadyAdded;
bool fAlreadyAdded;
};

#endif
44 changes: 44 additions & 0 deletions src/pathops/SkPathWriter.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* Use of this source code is governed by a BSD-style license that can be
* found in the LICENSE file.
*/
#include "SkOpSegment.h"
#include "SkOpSpan.h"
#include "SkPathOpsPoint.h"
#include "SkPathWriter.h"
Expand Down Expand Up @@ -214,6 +215,49 @@ void SkPathWriter::assemble() {
eStart->fPt.fX, eStart->fPt.fY, eEnd->fPt.fX, eEnd->fPt.fY);
}
#endif
// lengthen any partial contour adjacent to a simple segment
for (int pIndex = 0; pIndex < endCount; pIndex++) {
SkOpPtT* opPtT = const_cast<SkOpPtT*>(runs[pIndex]);
SkPath dummy;
SkPathWriter partWriter(dummy);
do {
if (!zero_or_one(opPtT->fT)) {
break;
}
SkOpSpanBase* opSpanBase = opPtT->span();
SkOpSpanBase* start = opPtT->fT ? opSpanBase->prev() : opSpanBase->upCast()->next();
int step = opPtT->fT ? 1 : -1;
const SkOpSegment* opSegment = opSpanBase->segment();
const SkOpSegment* nextSegment = opSegment->isSimple(&start, &step);
if (!nextSegment) {
break;
}
SkOpSpanBase* opSpanEnd = start->t() ? start->prev() : start->upCast()->next();
if (start->starter(opSpanEnd)->alreadyAdded()) {
break;
}
nextSegment->addCurveTo(start, opSpanEnd, &partWriter);
opPtT = opSpanEnd->ptT();
SkOpPtT** runsPtr = const_cast<SkOpPtT**>(&runs[pIndex]);
*runsPtr = opPtT;
} while (true);
partWriter.finishContour();
const SkTArray<SkPath>& partPartials = partWriter.partials();
if (!partPartials.count()) {
continue;
}
// if pIndex is even, reverse and prepend to fPartials; otherwise, append
SkPath& partial = const_cast<SkPath&>(fPartials[pIndex >> 1]);
const SkPath& part = partPartials[0];
if (pIndex & 1) {
partial.addPath(part, SkPath::kExtend_AddPathMode);
} else {
SkPath reverse;
reverse.reverseAddPath(part);
reverse.addPath(partial, SkPath::kExtend_AddPathMode);
partial = reverse;
}
}
SkTDArray<int> sLink, eLink;
int linkCount = endCount / 2; // number of partial contours
sLink.append(linkCount);
Expand Down
95 changes: 94 additions & 1 deletion tests/PathOpsOpTest.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -8943,14 +8943,107 @@ path.close();
testPathOpFail(reporter, path, path1, kXOR_SkPathOp, filename);
}

static void op_1(skiatest::Reporter* reporter, const char* filename) {
SkPath path;
path.setFillType((SkPath::FillType) 0);
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(SkBits2Float(0x15e80300), SkBits2Float(0x400004dc)); // 9.37088e-26f, 2.0003f
path.quadTo(SkBits2Float(0xe56c206c), SkBits2Float(0x646c5f40), SkBits2Float(0x6c80885e), SkBits2Float(0xb4bc576c)); // -6.96923e+22f, 1.74412e+22f, 1.24309e+27f, -3.50813e-07f

SkPath path1(path);
path.reset();
path.setFillType((SkPath::FillType) 0);
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(SkBits2Float(0x1b000010), SkBits2Float(0x6e5a5a1b)); // 1.05879e-22f, 1.68942e+28f
path.quadTo(SkBits2Float(0xef646464), SkBits2Float(0xefefefef), SkBits2Float(0x000000ef), SkBits2Float(0x1bb4bc00)); // -7.06839e+28f, -1.48514e+29f, 3.3491e-43f, 2.99e-22f

SkPath path2(path);
testPathOpFuzz(reporter, path1, path2, (SkPathOp) 2, filename);
}


static void op_2(skiatest::Reporter* reporter, const char* filename) {
SkPath path;
path.setFillType((SkPath::FillType) 1);
path.setFillType(SkPath::kEvenOdd_FillType);
path.moveTo(SkBits2Float(0xeee3ef57), SkBits2Float(0xef6300f8)); // -3.52712e+28f, -7.02543e+28f
path.quadTo(SkBits2Float(0xeeee9c6e), SkBits2Float(0xef609993), SkBits2Float(0x00000000), SkBits2Float(0x6e5a5a1b)); // -3.69233e+28f, -6.95103e+28f, 0, 1.68942e+28f
path.lineTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
path.quadTo(SkBits2Float(0xe56c206c), SkBits2Float(0x646c5f40), SkBits2Float(0x6c80885e), SkBits2Float(0x00000000)); // -6.96923e+22f, 1.74412e+22f, 1.24309e+27f, 0
path.lineTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
path.quadTo(SkBits2Float(0xeeda2c5a), SkBits2Float(0xef6533a7), SkBits2Float(0xeee3ef57), SkBits2Float(0xef6300f8)); // -3.37607e+28f, -7.09345e+28f, -3.52712e+28f, -7.02543e+28f
path.close();

SkPath path1(path);
path.reset();
path.setFillType((SkPath::FillType) 0);
path.setFillType(SkPath::kWinding_FillType);
path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
path.lineTo(SkBits2Float(0x1b1b1b00), SkBits2Float(0x1b5a5a1b)); // 1.283e-22f, 1.80617e-22f

SkPath path2(path);
testPathOpFuzz(reporter, path1, path2, (SkPathOp) 0, filename);
}


static void op_3(skiatest::Reporter* reporter, const char* filename) {
SkPath path;
path.setFillType((SkPath::FillType) 1);
path.setFillType(SkPath::kEvenOdd_FillType);
path.moveTo(SkBits2Float(0x00000000), SkBits2Float(0x6e5a5a1b)); // 0, 1.68942e+28f
path.quadTo(SkBits2Float(0xeeee9c6e), SkBits2Float(0xef609993), SkBits2Float(0xeee3ef57), SkBits2Float(0xef6300f8)); // -3.69233e+28f, -6.95103e+28f, -3.52712e+28f, -7.02543e+28f
path.quadTo(SkBits2Float(0xeeda2c5a), SkBits2Float(0xef6533a7), SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // -3.37607e+28f, -7.09345e+28f, 0, 0
path.lineTo(SkBits2Float(0x00000000), SkBits2Float(0x6e5a5a1b)); // 0, 1.68942e+28f
path.close();
path.moveTo(SkBits2Float(0x6c80885e), SkBits2Float(0x00000000)); // 1.24309e+27f, 0
path.quadTo(SkBits2Float(0xe56c206c), SkBits2Float(0x646c5f40), SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // -6.96923e+22f, 1.74412e+22f, 0, 0
path.lineTo(SkBits2Float(0x00000000), SkBits2Float(0x00000000)); // 0, 0
path.lineTo(SkBits2Float(0x6c80885e), SkBits2Float(0x00000000)); // 1.24309e+27f, 0
path.close();

SkPath path1(path);
path.reset();
path.setFillType((SkPath::FillType) 0);
path.setFillType(SkPath::kWinding_FillType);

SkPath path2(path);
testPathOpFuzz(reporter, path1, path2, (SkPathOp) 0, filename);
}

static void op_4(skiatest::Reporter* reporter, const char* filename) {
SkPath patha, pathb;

patha.setFillType(SkPath::kEvenOdd_FillType);
patha.moveTo(SkBits2Float(0x40d7ea90), SkBits2Float(0x3fa58930)); // 6.74738f, 1.29325f
patha.lineTo(SkBits2Float(0x40ad3d93), SkBits2Float(0x3fa58930)); // 5.41377f, 1.29325f
patha.lineTo(SkBits2Float(0x40ad3d93), SkBits2Float(0x3edba819)); // 5.41377f, 0.429017f
patha.lineTo(SkBits2Float(0x40fc41e0), SkBits2Float(0x3edba819)); // 7.88304f, 0.429017f
patha.lineTo(SkBits2Float(0x40fc41e0), SkBits2Float(0x3f3b7c94)); // 7.88304f, 0.73237f
patha.lineTo(SkBits2Float(0x40d7ea90), SkBits2Float(0x3f3b7c94)); // 6.74738f, 0.73237f
patha.lineTo(SkBits2Float(0x40d7ea90), SkBits2Float(0x3fa58930)); // 6.74738f, 1.29325f
patha.close();

pathb.setFillType(SkPath::kEvenOdd_FillType);
pathb.moveTo(SkBits2Float(0x40d7ea89), SkBits2Float(0x409a721d)); // 6.74738f, 4.82643f
pathb.lineTo(SkBits2Float(0x411a9d73), SkBits2Float(0x409a721d)); // 9.66344f, 4.82643f
pathb.lineTo(SkBits2Float(0x411a9d73), SkBits2Float(0x3f3b7c9a)); // 9.66344f, 0.73237f
pathb.lineTo(SkBits2Float(0x40d7ea89), SkBits2Float(0x3f3b7c9a)); // 6.74738f, 0.73237f
pathb.lineTo(SkBits2Float(0x40d7ea89), SkBits2Float(0x409a721d)); // 6.74738f, 4.82643f
pathb.close();
testPathOp(reporter, patha, pathb, kDifference_SkPathOp, filename);
}

static void (*skipTest)(skiatest::Reporter* , const char* filename) = 0;
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(cubics41d),
TEST(op_4),
TEST(op_1),
TEST(op_2),
TEST(op_3),
TEST(grshapearcs1),
TEST(filinmangust14),
TEST(testRect1_u),
Expand Down
Loading

0 comments on commit cadc506

Please sign in to comment.