Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(805)

Unified Diff: src/pathops/SkOpSegment.cpp

Issue 21359002: path ops work in progress (Closed) Base URL: https://skia.googlecode.com/svn/trunk
Patch Set: remove space Created 7 years, 3 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « src/pathops/SkOpSegment.h ('k') | src/pathops/SkOpSpan.h » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: src/pathops/SkOpSegment.cpp
diff --git a/src/pathops/SkOpSegment.cpp b/src/pathops/SkOpSegment.cpp
index 7e69bb835b478d27e630a738811f84d840760428..c0ef1f8e1170ea8989beccadf6507c267ce7d579 100644
--- a/src/pathops/SkOpSegment.cpp
+++ b/src/pathops/SkOpSegment.cpp
@@ -32,36 +32,36 @@ static const bool gActiveEdge[kXOR_PathOp + 1][2][2][2][2] = {
#undef F
#undef T
-enum { kOutsideTrackedTCount = 16 }; // FIXME: determine what this should be
-
-// OPTIMIZATION: does the following also work, and is it any faster?
-// return outerWinding * innerWinding > 0
-// || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0)))
-bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) {
- SkASSERT(outerWinding != SK_MaxS32);
- SkASSERT(innerWinding != SK_MaxS32);
- int absOut = abs(outerWinding);
- int absIn = abs(innerWinding);
- bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn;
- return result;
-}
+enum {
+ kOutsideTrackedTCount = 16, // FIXME: determine what this should be
+ kMissingSpanCount = 4, // FIXME: determine what this should be
+};
+// note that this follows the same logic flow as build angles
bool SkOpSegment::activeAngle(int index, int* done, SkTArray<SkOpAngle, true>* angles) {
if (activeAngleInner(index, done, angles)) {
return true;
}
+ double referenceT = fTs[index].fT;
int lesser = index;
- while (--lesser >= 0 && equalPoints(index, lesser)) {
+ while (--lesser >= 0
+ && (precisely_negative(referenceT - fTs[lesser].fT) || fTs[lesser].fTiny)) {
if (activeAngleOther(lesser, done, angles)) {
return true;
}
}
- lesser = index;
do {
if (activeAngleOther(index, done, angles)) {
return true;
}
- } while (++index < fTs.count() && equalPoints(index, lesser));
+ if (++index == fTs.count()) {
+ break;
+ }
+ if (fTs[index - 1].fTiny) {
+ referenceT = fTs[index].fT;
+ continue;
+ }
+ } while (precisely_negative(fTs[index].fT - referenceT));
return false;
}
@@ -187,7 +187,7 @@ bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, int index, int endIndex
bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo];
#if DEBUG_ACTIVE_OP
SkDebugf("%s op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", __FUNCTION__,
- kPathOpStr[op], miFrom, miTo, suFrom, suTo, result);
+ SkPathOpsDebug::kPathOpStr[op], miFrom, miTo, suFrom, suTo, result);
#endif
return result;
}
@@ -209,47 +209,35 @@ bool SkOpSegment::activeWinding(int index, int endIndex, int* maxWinding, int* s
void SkOpSegment::addAngle(SkTArray<SkOpAngle, true>* anglesPtr, int start, int end) const {
SkASSERT(start != end);
SkOpAngle& angle = anglesPtr->push_back();
-#if 0 && DEBUG_ANGLE // computed pt and actual pt may differ by more than approx eq
- SkTArray<SkOpAngle, true>& angles = *anglesPtr;
- if (angles.count() > 1) {
- const SkOpSegment* aSeg = angles[0].segment();
- int aStart = angles[0].start();
- if (!aSeg->fTs[aStart].fTiny) {
- const SkPoint& angle0Pt = aSeg->xyAtT(aStart);
- SkDPoint newPt = (*CurveDPointAtT[SkPathOpsVerbToPoints(aSeg->fVerb)])(aSeg->fPts,
- aSeg->fTs[aStart].fT);
-#if ONE_OFF_DEBUG
- SkDebugf("%s t1=%1.9g (%1.9g, %1.9g) (%1.9g, %1.9g)\n", __FUNCTION__,
- aSeg->fTs[aStart].fT, newPt.fX, newPt.fY, angle0Pt.fX, angle0Pt.fY);
-#endif
- SkASSERT(newPt.approximatelyEqual(angle0Pt));
- }
- }
-#endif
angle.set(this, start, end);
}
-void SkOpSegment::addCancelOutsides(double tStart, double oStart, SkOpSegment* other, double oEnd) {
+void SkOpSegment::addCancelOutsides(const SkPoint& startPt, const SkPoint& endPt,
+ SkOpSegment* other) {
int tIndex = -1;
int tCount = fTs.count();
int oIndex = -1;
int oCount = other->fTs.count();
do {
++tIndex;
- } while (!approximately_negative(tStart - fTs[tIndex].fT) && tIndex < tCount);
+ } while (startPt != fTs[tIndex].fPt && tIndex < tCount);
int tIndexStart = tIndex;
do {
++oIndex;
- } while (!approximately_negative(oStart - other->fTs[oIndex].fT) && oIndex < oCount);
+ } while (endPt != other->fTs[oIndex].fPt && oIndex < oCount);
int oIndexStart = oIndex;
- double nextT;
+ const SkPoint* nextPt;
do {
- nextT = fTs[++tIndex].fT;
- } while (nextT < 1 && approximately_negative(nextT - tStart));
- double oNextT;
+ nextPt = &fTs[++tIndex].fPt;
+ SkASSERT(fTs[tIndex].fT < 1 || startPt != *nextPt);
+ } while (startPt == *nextPt);
+ double nextT = fTs[tIndex].fT;
+ const SkPoint* oNextPt;
do {
- oNextT = other->fTs[++oIndex].fT;
- } while (oNextT < 1 && approximately_negative(oNextT - oStart));
+ oNextPt = &other->fTs[++oIndex].fPt;
+ SkASSERT(other->fTs[oIndex].fT < 1 || endPt != *oNextPt);
+ } while (endPt == *oNextPt);
+ double oNextT = other->fTs[oIndex].fT;
// at this point, spans before and after are at:
// fTs[tIndexStart - 1], fTs[tIndexStart], fTs[tIndex]
// if tIndexStart == 0, no prior span
@@ -301,43 +289,39 @@ void SkOpSegment::addCancelOutsides(double tStart, double oStart, SkOpSegment* o
}
}
-void SkOpSegment::addCoinOutsides(const SkTArray<double, true>& outsideTs, SkOpSegment* other,
- double oEnd) {
- // walk this to outsideTs[0]
- // walk other to outsideTs[1]
+void SkOpSegment::addCoinOutsides(const SkPoint& startPt, const SkPoint& endPt,
+ SkOpSegment* other) {
+ // walk this to startPt
+ // walk other to startPt
// if either is > 0, add a pointer to the other, copying adjacent winding
int tIndex = -1;
int oIndex = -1;
- double tStart = outsideTs[0];
- double oStart = outsideTs[1];
do {
++tIndex;
- } while (!approximately_negative(tStart - fTs[tIndex].fT));
- SkPoint ptStart = fTs[tIndex].fPt;
+ } while (startPt != fTs[tIndex].fPt);
do {
++oIndex;
- } while (!approximately_negative(oStart - other->fTs[oIndex].fT));
+ } while (startPt != other->fTs[oIndex].fPt);
if (tIndex > 0 || oIndex > 0 || fOperand != other->fOperand) {
- addTPair(tStart, other, oStart, false, ptStart);
+ addTPair(fTs[tIndex].fT, other, other->fTs[oIndex].fT, false, startPt);
}
- tStart = fTs[tIndex].fT;
- oStart = other->fTs[oIndex].fT;
+ SkPoint nextPt = startPt;
do {
- double nextT;
+ const SkPoint* workPt;
do {
- nextT = fTs[++tIndex].fT;
- } while (approximately_negative(nextT - tStart));
- tStart = nextT;
- ptStart = fTs[tIndex].fPt;
+ workPt = &fTs[++tIndex].fPt;
+ } while (nextPt == *workPt);
do {
- nextT = other->fTs[++oIndex].fT;
- } while (approximately_negative(nextT - oStart));
- oStart = nextT;
+ workPt = &other->fTs[++oIndex].fPt;
+ } while (nextPt == *workPt);
+ nextPt = *workPt;
+ double tStart = fTs[tIndex].fT;
+ double oStart = other->fTs[oIndex].fT;
if (tStart == 1 && oStart == 1 && fOperand == other->fOperand) {
break;
}
- addTPair(tStart, other, oStart, false, ptStart);
- } while (tStart < 1 && oStart < 1 && !approximately_negative(oEnd - oStart));
+ addTPair(tStart, other, oStart, false, nextPt);
+ } while (endPt != nextPt);
}
void SkOpSegment::addCubic(const SkPoint pts[4], bool operand, bool evenOdd) {
@@ -423,7 +407,7 @@ void SkOpSegment::addQuad(const SkPoint pts[3], bool operand, bool evenOdd) {
// resolve overlapping ts when considering coincidence later
// add non-coincident intersection. Resulting edges are sorted in T.
-int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
+int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT, bool isNear) {
if (precisely_zero(newT)) {
newT = 0;
} else if (precisely_equal(newT, 1)) {
@@ -455,6 +439,7 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
span->fT = newT;
span->fOther = other;
span->fPt = pt;
+ span->fNear = isNear;
#if 0
// cubics, for instance, may not be exact enough to satisfy this check (e.g., cubicOp69d)
SkASSERT(approximately_equal(xyAtT(newT).fX, pt.fX)
@@ -464,6 +449,7 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
span->fOppSum = SK_MinS32;
span->fWindValue = 1;
span->fOppValue = 0;
+ span->fSmall = false;
span->fTiny = false;
span->fLoop = false;
if ((span->fDone = newT == 1)) {
@@ -472,7 +458,7 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
span->fUnsortableStart = false;
span->fUnsortableEnd = false;
int less = -1;
- while (&span[less + 1] - fTs.begin() > 0 && xyAtT(&span[less]) == xyAtT(span)) {
+ while (&span[less + 1] - fTs.begin() > 0 && AlmostEqualUlps(span[less].fPt, span->fPt)) {
if (span[less].fDone) {
break;
}
@@ -487,9 +473,11 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
break;
}
}
- span[less].fTiny = true;
+ span[less].fSmall = true;
+ bool tiny = span[less].fPt == span->fPt;
+ span[less].fTiny = tiny;
span[less].fDone = true;
- if (approximately_negative(newT - span[less].fT)) {
+ if (approximately_negative(newT - span[less].fT) && tiny) {
if (approximately_greater_than_one(newT)) {
SkASSERT(&span[less] - fTs.begin() < fTs.count());
span[less].fUnsortableStart = true;
@@ -508,7 +496,7 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
--less;
}
int more = 1;
- while (fTs.end() - &span[more - 1] > 1 && xyAtT(&span[more]) == xyAtT(span)) {
+ while (fTs.end() - &span[more - 1] > 1 && AlmostEqualUlps(span[more].fPt, span->fPt)) {
if (span[more - 1].fDone) {
break;
}
@@ -523,9 +511,11 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
break;
}
}
- span[more - 1].fTiny = true;
+ span[more - 1].fSmall = true;
+ bool tiny = span[more].fPt == span->fPt;
+ span[more - 1].fTiny = tiny;
span[more - 1].fDone = true;
- if (approximately_negative(span[more].fT - newT)) {
+ if (approximately_negative(span[more].fT - newT) && tiny) {
if (approximately_greater_than_one(span[more].fT)) {
span[more + 1].fUnsortableStart = true;
span[more].fUnsortableEnd = true;
@@ -553,148 +543,156 @@ int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
// FIXME? It seems that decrementing by one will fail for complex paths that
// have three or more coincident edges. Shouldn't this subtract the difference
// between the winding values?
-void SkOpSegment::addTCancel(double startT, double endT, SkOpSegment* other,
- double oStartT, double oEndT) {
- SkASSERT(!approximately_negative(endT - startT));
- SkASSERT(!approximately_negative(oEndT - oStartT));
+/* |--> |-->
+this 0>>>>1>>>>2>>>>3>>>4 0>>>>1>>>>2>>>>3>>>4 0>>>>1>>>>2>>>>3>>>4
+other 2<<<<1<<<<0 2<<<<1<<<<0 2<<<<1<<<<0
+ ^ ^ <--| <--|
+ startPt endPt test/oTest first pos test/oTest final pos
+*/
+void SkOpSegment::addTCancel(const SkPoint& startPt, const SkPoint& endPt, SkOpSegment* other) {
bool binary = fOperand != other->fOperand;
int index = 0;
- while (!approximately_negative(startT - fTs[index].fT)) {
+ while (startPt != fTs[index].fPt) {
+ SkASSERT(index < fTs.count());
++index;
}
int oIndex = other->fTs.count();
- while (approximately_positive(other->fTs[--oIndex].fT - oEndT))
- ;
- double tRatio = (oEndT - oStartT) / (endT - startT);
+ while (startPt != other->fTs[--oIndex].fPt) { // look for startPt match
+ SkASSERT(oIndex > 0);
+ }
+ while (startPt == other->fTs[--oIndex].fPt) { // look for first point beyond match
+ SkASSERT(oIndex > 0);
+ }
SkOpSpan* test = &fTs[index];
SkOpSpan* oTest = &other->fTs[oIndex];
- SkSTArray<kOutsideTrackedTCount, double, true> outsideTs;
- SkSTArray<kOutsideTrackedTCount, double, true> oOutsideTs;
+ SkSTArray<kOutsideTrackedTCount, SkPoint, true> outsidePts;
+ SkSTArray<kOutsideTrackedTCount, SkPoint, true> oOutsidePts;
do {
+ SkASSERT(test->fT < 1);
+ SkASSERT(oTest->fT < 1);
bool decrement = test->fWindValue && oTest->fWindValue;
bool track = test->fWindValue || oTest->fWindValue;
bool bigger = test->fWindValue >= oTest->fWindValue;
- double testT = test->fT;
- double oTestT = oTest->fT;
- SkOpSpan* span = test;
+ const SkPoint& testPt = test->fPt;
+ const SkPoint& oTestPt = oTest->fPt;
do {
if (decrement) {
if (binary && bigger) {
- span->fOppValue--;
+ test->fOppValue--;
} else {
- decrementSpan(span);
+ decrementSpan(test);
}
- } else if (track && span->fT < 1 && oTestT < 1) {
- TrackOutside(&outsideTs, span->fT, oTestT);
+ } else if (track) {
+ TrackOutsidePair(&outsidePts, testPt, oTestPt);
}
- span = &fTs[++index];
- } while (approximately_negative(span->fT - testT));
- SkOpSpan* oSpan = oTest;
- double otherTMatchStart = oEndT - (span->fT - startT) * tRatio;
- double otherTMatchEnd = oEndT - (test->fT - startT) * tRatio;
- SkDEBUGCODE(int originalWindValue = oSpan->fWindValue);
- while (approximately_negative(otherTMatchStart - oSpan->fT)
- && !approximately_negative(otherTMatchEnd - oSpan->fT)) {
- #ifdef SK_DEBUG
- SkASSERT(originalWindValue == oSpan->fWindValue);
- #endif
+ SkASSERT(index < fTs.count() - 1);
+ test = &fTs[++index];
+ } while (testPt == test->fPt);
+ SkDEBUGCODE(int originalWindValue = oTest->fWindValue);
+ do {
+ SkASSERT(oTest->fT < 1);
+ SkASSERT(originalWindValue == oTest->fWindValue);
if (decrement) {
if (binary && !bigger) {
- oSpan->fOppValue--;
+ oTest->fOppValue--;
} else {
- other->decrementSpan(oSpan);
+ other->decrementSpan(oTest);
}
- } else if (track && oSpan->fT < 1 && testT < 1) {
- TrackOutside(&oOutsideTs, oSpan->fT, testT);
+ } else if (track) {
+ TrackOutsidePair(&oOutsidePts, oTestPt, testPt);
}
if (!oIndex) {
break;
}
- oSpan = &other->fTs[--oIndex];
- }
- test = span;
- oTest = oSpan;
- } while (!approximately_negative(endT - test->fT));
- SkASSERT(!oIndex || approximately_negative(oTest->fT - oStartT));
+ oTest = &other->fTs[--oIndex];
+ } while (oTestPt == oTest->fPt);
+ SkASSERT(endPt != test->fPt || oTestPt == endPt);
+ } while (endPt != test->fPt);
// FIXME: determine if canceled edges need outside ts added
- if (!done() && outsideTs.count()) {
- double tStart = outsideTs[0];
- double oStart = outsideTs[1];
- addCancelOutsides(tStart, oStart, other, oEndT);
- int count = outsideTs.count();
- if (count > 2) {
- double tStart = outsideTs[count - 2];
- double oStart = outsideTs[count - 1];
- addCancelOutsides(tStart, oStart, other, oEndT);
+ int outCount = outsidePts.count();
+ if (!done() && outCount) {
+ addCancelOutsides(outsidePts[0], outsidePts[1], other);
+ if (outCount > 2) {
+ addCancelOutsides(outsidePts[outCount - 2], outsidePts[outCount - 1], other);
}
}
- if (!other->done() && oOutsideTs.count()) {
- double tStart = oOutsideTs[0];
- double oStart = oOutsideTs[1];
- other->addCancelOutsides(tStart, oStart, this, endT);
+ if (!other->done() && oOutsidePts.count()) {
+ other->addCancelOutsides(oOutsidePts[0], oOutsidePts[1], this);
}
}
int SkOpSegment::addSelfT(SkOpSegment* other, const SkPoint& pt, double newT) {
- int result = addT(other, pt, newT);
+ // if the tail nearly intersects itself but not quite, the caller records this separately
+ int result = addT(other, pt, newT, SkOpSpan::kPointIsExact);
SkOpSpan* span = &fTs[result];
span->fLoop = true;
return result;
}
-int SkOpSegment::addUnsortableT(SkOpSegment* other, bool start, const SkPoint& pt, double newT) {
- int result = addT(other, pt, newT);
- SkOpSpan* span = &fTs[result];
- if (start) {
- if (result > 0) {
- span[result - 1].fUnsortableEnd = true;
- }
- span[result].fUnsortableStart = true;
- } else {
- span[result].fUnsortableEnd = true;
- if (result + 1 < fTs.count()) {
- span[result + 1].fUnsortableStart = true;
- }
- }
- return result;
-}
-
-int SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool opp, int index,
- SkTArray<double, true>* outsideTs) {
+void SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool binary, int* indexPtr,
+ SkTArray<SkPoint, true>* outsideTs) {
+ int index = *indexPtr;
int oWindValue = oTest.fWindValue;
int oOppValue = oTest.fOppValue;
- if (opp) {
+ if (binary) {
SkTSwap<int>(oWindValue, oOppValue);
}
SkOpSpan* const test = &fTs[index];
SkOpSpan* end = test;
- const double oStartT = oTest.fT;
+ const SkPoint& oStartPt = oTest.fPt;
do {
if (bumpSpan(end, oWindValue, oOppValue)) {
- TrackOutside(outsideTs, end->fT, oStartT);
+ TrackOutside(outsideTs, oStartPt);
}
end = &fTs[++index];
- } while (approximately_negative(end->fT - test->fT));
- return index;
+ } while (end->fPt == test->fPt);
+ *indexPtr = index;
+}
+
+bool SkOpSegment::bumpCoincident(SkOpSpan* test, bool bigger, bool binary) {
+ if (bigger) {
+ if (binary) {
+ if (fOppXor) {
+ test->fOppValue ^= 1;
+ } else {
+ test->fOppValue++;
+ }
+ } else {
+ if (fXor) {
+ test->fWindValue ^= 1;
+ } else {
+ test->fWindValue++;
+ }
+ }
+ if (!test->fWindValue && !test->fOppValue) {
+ test->fDone = true;
+ ++fDoneSpans;
+ return true;
+ }
+ return false;
+ }
+ return decrementSpan(test);
}
// because of the order in which coincidences are resolved, this and other
// may not have the same intermediate points. Compute the corresponding
// intermediate T values (using this as the master, other as the follower)
// and walk other conditionally -- hoping that it catches up in the end
-int SkOpSegment::bumpCoincidentOther(const SkOpSpan& test, double oEndT, int& oIndex,
- SkTArray<double, true>* oOutsideTs) {
+void SkOpSegment::bumpCoincidentOther(const SkOpSpan& test, int* oIndexPtr,
+ SkTArray<SkPoint, true>* oOutsidePts) {
+ int oIndex = *oIndexPtr;
SkOpSpan* const oTest = &fTs[oIndex];
SkOpSpan* oEnd = oTest;
- const double startT = test.fT;
- const double oStartT = oTest->fT;
- while (!approximately_negative(oEndT - oEnd->fT)
- && approximately_negative(oEnd->fT - oStartT)) {
+ const SkPoint& startPt = test.fPt;
+ const SkPoint& oStartPt = oTest->fPt;
+ if (oStartPt == oEnd->fPt) {
+ TrackOutside(oOutsidePts, startPt);
+ }
+ while (oStartPt == oEnd->fPt) {
zeroSpan(oEnd);
- TrackOutside(oOutsideTs, oEnd->fT, startT);
oEnd = &fTs[++oIndex];
}
- return oIndex;
+ *oIndexPtr = oIndex;
}
// FIXME: need to test this case:
@@ -706,43 +704,75 @@ int SkOpSegment::bumpCoincidentOther(const SkOpSpan& test, double oEndT, int& oI
// set spans from start to end to increment the greater by one and decrement
// the lesser
-void SkOpSegment::addTCoincident(double startT, double endT, SkOpSegment* other, double oStartT,
- double oEndT) {
- SkASSERT(!approximately_negative(endT - startT));
- SkASSERT(!approximately_negative(oEndT - oStartT));
- bool opp = fOperand ^ other->fOperand;
+void SkOpSegment::addTCoincident(const SkPoint& startPt, const SkPoint& endPt,
+ SkOpSegment* other) {
+ bool binary = fOperand != other->fOperand;
int index = 0;
- while (!approximately_negative(startT - fTs[index].fT)) {
+ while (startPt != fTs[index].fPt) {
+ SkASSERT(index < fTs.count());
++index;
}
int oIndex = 0;
- while (!approximately_negative(oStartT - other->fTs[oIndex].fT)) {
+ while (startPt != other->fTs[oIndex].fPt) {
+ SkASSERT(oIndex < other->fTs.count());
++oIndex;
}
+ SkSTArray<kOutsideTrackedTCount, SkPoint, true> outsidePts;
+ SkSTArray<kOutsideTrackedTCount, SkPoint, true> oOutsidePts;
SkOpSpan* test = &fTs[index];
+ const SkPoint* testPt = &test->fPt;
SkOpSpan* oTest = &other->fTs[oIndex];
- SkSTArray<kOutsideTrackedTCount, double, true> outsideTs;
- SkSTArray<kOutsideTrackedTCount, double, true> oOutsideTs;
+ const SkPoint* oTestPt = &oTest->fPt;
+ SkASSERT(AlmostEqualUlps(*testPt, *oTestPt));
do {
- // if either span has an opposite value and the operands don't match, resolve first
- // SkASSERT(!test->fDone || !oTest->fDone);
+ SkASSERT(test->fT < 1);
+ SkASSERT(oTest->fT < 1);
+#if 0
if (test->fDone || oTest->fDone) {
- index = advanceCoincidentThis(oTest, opp, index);
- oIndex = other->advanceCoincidentOther(test, oEndT, oIndex);
+#else // consolidate the winding count even if done
+ if ((test->fWindValue == 0 && test->fOppValue == 0)
+ || (oTest->fWindValue == 0 && oTest->fOppValue == 0)) {
+#endif
+ SkDEBUGCODE(int firstWind = test->fWindValue);
+ SkDEBUGCODE(int firstOpp = test->fOppValue);
+ do {
+ SkASSERT(firstWind == fTs[index].fWindValue);
+ SkASSERT(firstOpp == fTs[index].fOppValue);
+ ++index;
+ SkASSERT(index < fTs.count());
+ } while (*testPt == fTs[index].fPt);
+ SkDEBUGCODE(firstWind = oTest->fWindValue);
+ SkDEBUGCODE(firstOpp = oTest->fOppValue);
+ do {
+ SkASSERT(firstWind == other->fTs[oIndex].fWindValue);
+ SkASSERT(firstOpp == other->fTs[oIndex].fOppValue);
+ ++oIndex;
+ SkASSERT(oIndex < other->fTs.count());
+ } while (*oTestPt == other->fTs[oIndex].fPt);
} else {
- index = bumpCoincidentThis(*oTest, opp, index, &outsideTs);
- oIndex = other->bumpCoincidentOther(*test, oEndT, oIndex, &oOutsideTs);
+ if (!binary || test->fWindValue + oTest->fOppValue >= 0) {
+ bumpCoincidentThis(*oTest, binary, &index, &outsidePts);
+ other->bumpCoincidentOther(*test, &oIndex, &oOutsidePts);
+ } else {
+ other->bumpCoincidentThis(*test, binary, &oIndex, &oOutsidePts);
+ bumpCoincidentOther(*oTest, &index, &outsidePts);
+ }
}
test = &fTs[index];
+ testPt = &test->fPt;
+ if (endPt == *testPt) {
+ break;
+ }
oTest = &other->fTs[oIndex];
- } while (!approximately_negative(endT - test->fT));
- SkASSERT(approximately_negative(oTest->fT - oEndT));
- SkASSERT(approximately_negative(oEndT - oTest->fT));
- if (!done() && outsideTs.count()) {
- addCoinOutsides(outsideTs, other, oEndT);
+ oTestPt = &oTest->fPt;
+ SkASSERT(AlmostEqualUlps(*testPt, *oTestPt));
+ } while (endPt != *oTestPt);
+ int outCount = outsidePts.count();
+ if (!done() && outCount) {
+ addCoinOutsides(outsidePts[0], endPt, other);
}
- if (!other->done() && oOutsideTs.count()) {
- other->addCoinOutsides(oOutsideTs, this, endT);
+ if (!other->done() && oOutsidePts.count()) {
+ other->addCoinOutsides(oOutsidePts[0], endPt, this);
}
}
@@ -769,8 +799,8 @@ void SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool bor
SkDebugf("%s addTPair this=%d %1.9g other=%d %1.9g\n",
__FUNCTION__, fID, t, other->fID, otherT);
#endif
- int insertedAt = addT(other, pt, t);
- int otherInsertedAt = other->addT(this, pt, otherT);
+ int insertedAt = addT(other, pt, t, SkOpSpan::kPointIsExact);
+ int otherInsertedAt = other->addT(this, pt, otherT, SkOpSpan::kPointIsExact);
addOtherT(insertedAt, otherT, otherInsertedAt);
other->addOtherT(otherInsertedAt, t, insertedAt);
matchWindingValue(insertedAt, t, borrowWind);
@@ -792,7 +822,151 @@ void SkOpSegment::addTwoAngles(int start, int end, SkTArray<SkOpAngle, true>* an
}
}
-int SkOpSegment::advanceCoincidentThis(const SkOpSpan* oTest, bool opp, int index) {
+SkOpSegment::MissingSpan::Command SkOpSegment::adjustThisNear(double startT, const SkPoint& startPt,
+ const SkPoint& endPt, SkTArray<MissingSpan, true>* missingSpans) {
+ // see if endPt exists on this curve, and if it has the same t or a different T than the startT
+ int count = this->count();
+ SkASSERT(count > 0);
+ int startIndex, endIndex, step;
+ if (startT == 0) {
+ startIndex = 0;
+ endIndex = count;
+ step = 1;
+ } else {
+ SkASSERT(startT == 1);
+ startIndex = count - 1;
+ endIndex = -1;
+ step = -1;
+ }
+ int index = startIndex;
+ do {
+ const SkOpSpan& span = fTs[index];
+ if (span.fPt != endPt) {
+ continue;
+ }
+ if (span.fT == startT) {
+ // check to see if otherT matches some other mid curve intersection
+ int inner = startIndex;
+ do {
+ if (inner == index) {
+ continue;
+ }
+ const SkOpSpan& matchSpan = fTs[inner];
+ double matchT = span.fOther->missingNear(span.fOtherT, matchSpan.fOther, startPt,
+ endPt);
+ if (matchT >= 0) {
+ SkASSERT(missingSpans);
+ MissingSpan& missingSpan = missingSpans->push_back();
+ SkDEBUGCODE(sk_bzero(&missingSpan, sizeof(missingSpan)));
+ missingSpan.fCommand = MissingSpan::kRemoveNear;
+ missingSpan.fT = startT;
+ missingSpan.fSegment = this;
+ missingSpan.fOther = span.fOther;
+ missingSpan.fOtherT = matchT;
+ return missingSpan.fCommand;
+ }
+ } while ((inner += step) != endIndex);
+ break;
+ }
+ double midT = (startT + span.fT) / 2;
+ if (betweenPoints(midT, startPt, endPt)) {
+ if (!missingSpans) {
+ return MissingSpan::kZeroSpan;
+ }
+ MissingSpan& missingSpan = missingSpans->push_back();
+ SkDEBUGCODE(sk_bzero(&missingSpan, sizeof(missingSpan)));
+ missingSpan.fCommand = MissingSpan::kZeroSpan;
+ missingSpan.fT = SkTMin(startT, span.fT);
+ missingSpan.fEndT = SkTMax(startT, span.fT);
+ missingSpan.fSegment = this;
+ return missingSpan.fCommand;
+ }
+ } while ((index += step) != endIndex);
+ return MissingSpan::kNoAction;
+}
+
+void SkOpSegment::adjustOtherNear(double startT, const SkPoint& startPt, const SkPoint& endPt,
+ SkTArray<MissingSpan, true>* missingSpans) {
+ int count = this->count();
+ SkASSERT(count > 0);
+ int startIndex, endIndex, step;
+ if (startT == 0) {
+ startIndex = 0;
+ endIndex = count;
+ step = 1;
+ } else {
+ SkASSERT(startT == 1);
+ startIndex = count - 1;
+ endIndex = -1;
+ step = -1;
+ }
+ int index = startIndex;
+ do {
+ const SkOpSpan& span = fTs[index];
+ if (span.fT != startT) {
+ return;
+ }
+ SkOpSegment* other = span.fOther;
+ if (other->fPts[0] == endPt) {
+ other->adjustThisNear(0, endPt, startPt, missingSpans);
+ } else if (other->fPts[0] == startPt) {
+ other->adjustThisNear(0, startPt, endPt, missingSpans);
+ }
+ if (other->ptAtT(1) == endPt) {
+ other->adjustThisNear(1, endPt, startPt, missingSpans);
+ } else if (other->ptAtT(1) == startPt) {
+ other->adjustThisNear(1, startPt, endPt, missingSpans);
+ }
+ } while ((index += step) != endIndex);
+}
+
+void SkOpSegment::adjustMissingNear(const SkPoint& startPt, const SkPoint& endPt,
+ SkTArray<MissingSpan, true>* missingSpans) {
+ int count = missingSpans->count();
+ for (int index = 0; index < count; ) {
+ MissingSpan& missing = (*missingSpans)[index];
+ SkOpSegment* other = missing.fOther;
+ MissingSpan::Command command = MissingSpan::kNoAction;
+ if (missing.fPt == startPt) {
+ if (missingNear(missing.fT, other, startPt, endPt) >= 0) {
+ command = MissingSpan::kZeroSpan;
+ } else if (other->ptAtT(0) == endPt) {
+ command = other->adjustThisNear(0, endPt, startPt, NULL);
+ } else if (other->ptAtT(1) == endPt) {
+ command = other->adjustThisNear(1, endPt, startPt, NULL);
+ }
+ } else if (missing.fPt == endPt) {
+ if (missingNear(missing.fT, other, endPt, startPt) >= 0) {
+ command = MissingSpan::kZeroSpan;
+ } else if (other->ptAtT(0) == startPt) {
+ command = other->adjustThisNear(0, startPt, endPt, NULL);
+ } else if (other->ptAtT(1) == startPt) {
+ command = other->adjustThisNear(1, startPt, endPt, NULL);
+ }
+ }
+ if (command == MissingSpan::kZeroSpan) {
+#if 1
+ missing = missingSpans->back();
+ missingSpans->pop_back();
+#else // if this is supported in the future ...
+ missingSpans->removeShuffle(index);
+#endif
+ --count;
+ continue;
+ }
+ ++index;
+ }
+}
+
+void SkOpSegment::adjustNear(double startT, const SkPoint& endPt,
+ SkTArray<MissingSpan, true>* missingSpans) {
+ const SkPoint startPt = ptAtT(startT);
+ adjustMissingNear(startPt, endPt, missingSpans);
+ adjustThisNear(startT, startPt, endPt, missingSpans);
+ adjustOtherNear(startT, startPt, endPt, missingSpans);
+}
+
+int SkOpSegment::advanceCoincidentThis(int index) {
SkOpSpan* const test = &fTs[index];
SkOpSpan* end;
do {
@@ -801,7 +975,7 @@ int SkOpSegment::advanceCoincidentThis(const SkOpSpan* oTest, bool opp, int inde
return index;
}
-int SkOpSegment::advanceCoincidentOther(const SkOpSpan* test, double oEndT, int oIndex) {
+int SkOpSegment::advanceCoincidentOther(double oEndT, int oIndex) {
SkOpSpan* const oTest = &fTs[oIndex];
SkOpSpan* oEnd = oTest;
const double oStartT = oTest->fT;
@@ -812,6 +986,14 @@ int SkOpSegment::advanceCoincidentOther(const SkOpSpan* test, double oEndT, int
return oIndex;
}
+bool SkOpSegment::betweenPoints(double midT, const SkPoint& pt1, const SkPoint& pt2) const {
+ const SkPoint midPt = ptAtT(midT);
+ SkPathOpsBounds bounds;
+ bounds.set(pt1.fX, pt1.fY, pt2.fX, pt2.fY);
+ bounds.sort();
+ return bounds.almostContains(midPt);
+}
+
bool SkOpSegment::betweenTs(int lesser, double testT, int greater) const {
if (lesser > greater) {
SkTSwap<int>(lesser, greater);
@@ -819,17 +1001,37 @@ bool SkOpSegment::betweenTs(int lesser, double testT, int greater) const {
return approximately_between(fTs[lesser].fT, testT, fTs[greater].fT);
}
-void SkOpSegment::buildAngles(int index, SkTArray<SkOpAngle, true>* angles, bool includeOpp) const {
+// note that this follows the same logic flow as active angle
+bool SkOpSegment::buildAngles(int index, SkTArray<SkOpAngle, true>* angles, bool allowOpp) const {
double referenceT = fTs[index].fT;
+ const SkPoint& referencePt = fTs[index].fPt;
int lesser = index;
- while (--lesser >= 0 && (includeOpp || fTs[lesser].fOther->fOperand == fOperand)
- && precisely_negative(referenceT - fTs[lesser].fT)) {
+ while (--lesser >= 0 && (allowOpp || fTs[lesser].fOther->fOperand == fOperand)
+ && (precisely_negative(referenceT - fTs[lesser].fT) || fTs[lesser].fTiny)) {
buildAnglesInner(lesser, angles);
}
do {
buildAnglesInner(index, angles);
- } while (++index < fTs.count() && (includeOpp || fTs[index].fOther->fOperand == fOperand)
- && precisely_negative(fTs[index].fT - referenceT));
+ if (++index == fTs.count()) {
+ break;
+ }
+ if (!allowOpp && fTs[index].fOther->fOperand != fOperand) {
+ break;
+ }
+ if (fTs[index - 1].fTiny) {
+ referenceT = fTs[index].fT;
+ continue;
+ }
+ if (!precisely_negative(fTs[index].fT - referenceT) && fTs[index].fPt == referencePt) {
+ // FIXME
+ // testQuad8 generates the wrong output unless false is returned here. Other tests will
+ // take this path although they aren't required to. This means that many go much slower
+ // because of this sort fail.
+ // SkDebugf("!!!\n");
+ return false;
+ }
+ } while (precisely_negative(fTs[index].fT - referenceT));
+ return true;
}
void SkOpSegment::buildAnglesInner(int index, SkTArray<SkOpAngle, true>* angles) const {
@@ -851,115 +1053,175 @@ void SkOpSegment::buildAnglesInner(int index, SkTArray<SkOpAngle, true>* angles)
other->addTwoAngles(next, oIndex, angles);
}
-int SkOpSegment::computeSum(int startIndex, int endIndex, bool binary) {
- SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
- addTwoAngles(startIndex, endIndex, &angles);
- buildAngles(endIndex, &angles, false);
- // OPTIMIZATION: check all angles to see if any have computed wind sum
- // before sorting (early exit if none)
- SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
- // FIXME?: Not sure if this sort must be ordered or if the relaxed ordering is OK ...
- bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMustBeOrdered_SortAngleKind);
-#if DEBUG_SORT
- sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0, sortable);
-#endif
- if (!sortable) {
- return SK_MinS32;
+int SkOpSegment::computeSum(int startIndex, int endIndex, SkOpAngle::IncludeType includeType,
+ SkTArray<SkOpAngle, true>* angles, SkTArray<SkOpAngle*, true>* sorted) {
+ addTwoAngles(startIndex, endIndex, angles);
+ if (!buildAngles(endIndex, angles, includeType == SkOpAngle::kBinaryOpp)) {
+ return SK_NaN32;
}
- int angleCount = angles.count();
- const SkOpAngle* angle;
- const SkOpSegment* base;
- int winding;
- int oWinding;
- int firstIndex = 0;
- do {
- angle = sorted[firstIndex];
- base = angle->segment();
- winding = base->windSum(angle);
- if (winding != SK_MinS32) {
- oWinding = base->oppSum(angle);
- break;
+ int angleCount = angles->count();
+ // abort early before sorting if an unsortable (not an unorderable) angle is found
+ if (includeType != SkOpAngle::kUnaryXor) {
+ int firstIndex = -1;
+ while (++firstIndex < angleCount) {
+ const SkOpAngle& angle = (*angles)[firstIndex];
+ if (angle.segment()->windSum(&angle) != SK_MinS32) {
+ break;
+ }
}
- if (++firstIndex == angleCount) {
+ if (firstIndex == angleCount) {
return SK_MinS32;
}
- } while (true);
- // turn winding into contourWinding
- int spanWinding = base->spanSign(angle);
- bool inner = UseInnerWinding(winding + spanWinding, winding);
-#if DEBUG_WINDING
- SkDebugf("%s spanWinding=%d winding=%d sign=%d inner=%d result=%d\n", __FUNCTION__,
- spanWinding, winding, angle->sign(), inner,
- inner ? winding + spanWinding : winding);
-#endif
- if (inner) {
- winding += spanWinding;
}
-#if DEBUG_SORT
- base->debugShowSort(__FUNCTION__, sorted, firstIndex, winding, oWinding, sortable);
+ bool sortable = SortAngles2(*angles, sorted);
+#if DEBUG_SORT_RAW
+ if (sorted->count() > 0) {
+ (*sorted)[0]->segment()->debugShowSort(__FUNCTION__, *sorted, 0, 0, 0, sortable);
+ }
#endif
- int nextIndex = firstIndex + 1;
- int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
- winding -= base->spanSign(angle);
- oWinding -= base->oppSign(angle);
+ if (!sortable) {
+ return SK_NaN32;
+ }
+ if (includeType == SkOpAngle::kUnaryXor) {
+ return SK_MinS32;
+ }
+ // if all angles have a computed winding,
+ // or if no adjacent angles are orderable,
+ // or if adjacent orderable angles have no computed winding,
+ // there's nothing to do
+ // if two orderable angles are adjacent, and one has winding computed, transfer to the other
+ const SkOpAngle* baseAngle = NULL;
+ int last = angleCount;
+ int finalInitialOrderable = -1;
+ bool tryReverse = false;
+ // look for counterclockwise transfers
do {
- if (nextIndex == angleCount) {
- nextIndex = 0;
- }
- angle = sorted[nextIndex];
- SkOpSegment* segment = angle->segment();
- bool opp = base->fOperand ^ segment->fOperand;
- int maxWinding, oMaxWinding;
- int spanSign = segment->spanSign(angle);
- int oppoSign = segment->oppSign(angle);
- if (opp) {
- oMaxWinding = oWinding;
- oWinding -= spanSign;
- maxWinding = winding;
- if (oppoSign) {
- winding -= oppoSign;
+ int index = 0;
+ do {
+ SkOpAngle* testAngle = (*sorted)[index];
+ int testWinding = testAngle->segment()->windSum(testAngle);
+ if (SK_MinS32 != testWinding && !testAngle->unorderable()) {
+ baseAngle = testAngle;
+ continue;
}
- } else {
- maxWinding = winding;
- winding -= spanSign;
- oMaxWinding = oWinding;
- if (oppoSign) {
- oWinding -= oppoSign;
+ if (testAngle->unorderable()) {
+ baseAngle = NULL;
+ tryReverse = true;
+ continue;
}
+ if (baseAngle) {
+ ComputeOneSum(baseAngle, testAngle, includeType);
+ baseAngle = SK_MinS32 != testAngle->segment()->windSum(testAngle) ? testAngle
+ : NULL;
+ tryReverse |= !baseAngle;
+ continue;
+ }
+ if (finalInitialOrderable + 1 == index) {
+ finalInitialOrderable = index;
+ }
+ } while (++index != last);
+ if (finalInitialOrderable < 0) {
+ break;
}
- if (segment->windSum(angle) == SK_MinS32) {
- if (opp) {
- if (UseInnerWinding(oMaxWinding, oWinding)) {
- oMaxWinding = oWinding;
+ last = finalInitialOrderable + 1;
+ finalInitialOrderable = -2; // make this always negative the second time through
+ } while (baseAngle);
+ if (tryReverse) {
+ baseAngle = NULL;
+ last = 0;
+ finalInitialOrderable = angleCount;
+ do {
+ int index = angleCount;
+ while (--index >= last) {
+ SkOpAngle* testAngle = (*sorted)[index];
+ int testWinding = testAngle->segment()->windSum(testAngle);
+ if (SK_MinS32 != testWinding) {
+ baseAngle = testAngle;
+ continue;
}
- if (oppoSign && UseInnerWinding(maxWinding, winding)) {
- maxWinding = winding;
+ if (testAngle->unorderable()) {
+ baseAngle = NULL;
+ continue;
}
-#ifdef SK_DEBUG
- SkASSERT(abs(maxWinding) <= gDebugMaxWindSum);
- SkASSERT(abs(oMaxWinding) <= gDebugMaxWindSum);
-#endif
- (void) segment->markAndChaseWinding(angle, oMaxWinding, maxWinding);
- } else {
- if (UseInnerWinding(maxWinding, winding)) {
- maxWinding = winding;
+ if (baseAngle) {
+ ComputeOneSumReverse(baseAngle, testAngle, includeType);
+ baseAngle = SK_MinS32 != testAngle->segment()->windSum(testAngle) ? testAngle
+ : NULL;
+ continue;
}
- if (oppoSign && UseInnerWinding(oMaxWinding, oWinding)) {
- oMaxWinding = oWinding;
+ if (finalInitialOrderable - 1 == index) {
+ finalInitialOrderable = index;
}
-#ifdef SK_DEBUG
- SkASSERT(abs(maxWinding) <= gDebugMaxWindSum);
- SkASSERT(abs(binary ? oMaxWinding : 0) <= gDebugMaxWindSum);
-#endif
- (void) segment->markAndChaseWinding(angle, maxWinding,
- binary ? oMaxWinding : 0);
}
- }
- } while (++nextIndex != lastIndex);
+ if (finalInitialOrderable >= angleCount) {
+ break;
+ }
+ last = finalInitialOrderable;
+ finalInitialOrderable = angleCount + 1; // make this inactive 2nd time through
+ } while (baseAngle);
+ }
int minIndex = SkMin32(startIndex, endIndex);
return windSum(minIndex);
}
+void SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle,
+ SkOpAngle::IncludeType includeType) {
+ const SkOpSegment* baseSegment = baseAngle->segment();
+ int sumMiWinding = baseSegment->updateWindingReverse(baseAngle);
+ int sumSuWinding;
+ bool binary = includeType >= SkOpAngle::kBinarySingle;
+ if (binary) {
+ sumSuWinding = baseSegment->updateOppWindingReverse(baseAngle);
+ if (baseSegment->operand()) {
+ SkTSwap<int>(sumMiWinding, sumSuWinding);
+ }
+ }
+ SkOpSegment* nextSegment = nextAngle->segment();
+ int maxWinding, sumWinding;
+ SkOpSpan* last;
+ if (binary) {
+ int oppMaxWinding, oppSumWinding;
+ nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding,
+ &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
+ last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding,
+ true, nextAngle);
+ } else {
+ nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding,
+ &maxWinding, &sumWinding);
+ last = nextSegment->markAngle(maxWinding, sumWinding, true, nextAngle);
+ }
+ nextAngle->setLastMarked(last);
+}
+
+void SkOpSegment::ComputeOneSumReverse(const SkOpAngle* baseAngle, SkOpAngle* nextAngle,
+ SkOpAngle::IncludeType includeType) {
+ const SkOpSegment* baseSegment = baseAngle->segment();
+ int sumMiWinding = baseSegment->updateWinding(baseAngle);
+ int sumSuWinding;
+ bool binary = includeType >= SkOpAngle::kBinarySingle;
+ if (binary) {
+ sumSuWinding = baseSegment->updateOppWinding(baseAngle);
+ if (baseSegment->operand()) {
+ SkTSwap<int>(sumMiWinding, sumSuWinding);
+ }
+ }
+ SkOpSegment* nextSegment = nextAngle->segment();
+ int maxWinding, sumWinding;
+ SkOpSpan* last;
+ if (binary) {
+ int oppMaxWinding, oppSumWinding;
+ nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding,
+ &sumSuWinding, &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
+ last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, oppSumWinding,
+ true, nextAngle);
+ } else {
+ nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding,
+ &maxWinding, &sumWinding);
+ last = nextSegment->markAngle(maxWinding, sumWinding, true, nextAngle);
+ }
+ nextAngle->setLastMarked(last);
+}
+
int SkOpSegment::crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT,
bool* hitSomething, double mid, bool opp, bool current) const {
SkScalar bottom = fBounds.fBottom;
@@ -1050,18 +1312,20 @@ int SkOpSegment::crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hi
return bestTIndex;
}
-void SkOpSegment::decrementSpan(SkOpSpan* span) {
+bool SkOpSegment::decrementSpan(SkOpSpan* span) {
SkASSERT(span->fWindValue > 0);
if (--(span->fWindValue) == 0) {
if (!span->fOppValue && !span->fDone) {
span->fDone = true;
++fDoneSpans;
+ return true;
}
}
+ return false;
}
bool SkOpSegment::bumpSpan(SkOpSpan* span, int windDelta, int oppDelta) {
- SkASSERT(!span->fDone);
+ SkASSERT(!span->fDone || span->fTiny);
span->fWindValue += windDelta;
SkASSERT(span->fWindValue >= 0);
span->fOppValue += oppDelta;
@@ -1083,98 +1347,295 @@ bool SkOpSegment::bumpSpan(SkOpSpan* span, int windDelta, int oppDelta) {
// look to see if the curve end intersects an intermediary that intersects the other
void SkOpSegment::checkEnds() {
debugValidate();
- SkTDArray<SkOpSpan> missingSpans;
+ SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans;
int count = fTs.count();
for (int index = 0; index < count; ++index) {
const SkOpSpan& span = fTs[index];
- const SkOpSegment* other = span.fOther;
- const SkOpSpan* otherSpan = &other->fTs[span.fOtherIndex];
- double otherT = otherSpan->fT;
- if (otherT != 0 && otherT != 1) {
+ double otherT = span.fOtherT;
+ if (otherT != 0 && otherT != 1) { // only check ends
continue;
}
+ const SkOpSegment* other = span.fOther;
+ // peek start/last describe the range of spans that match the other t of this span
int peekStart = span.fOtherIndex;
- while (peekStart > 0) {
- const SkOpSpan* peeker = &other->fTs[peekStart - 1];
- if (peeker->fT != otherT) {
- break;
- }
- --peekStart;
- }
- int otherLast = other->fTs.count() - 1;
+ while (--peekStart >= 0 && other->fTs[peekStart].fT == otherT)
+ ;
+ int otherCount = other->fTs.count();
int peekLast = span.fOtherIndex;
- while (peekLast < otherLast) {
- const SkOpSpan* peeker = &other->fTs[peekLast + 1];
- if (peeker->fT != otherT) {
- break;
- }
- ++peekLast;
- }
- if (peekStart == peekLast) {
+ while (++peekLast < otherCount && other->fTs[peekLast].fT == otherT)
+ ;
+ if (++peekStart == --peekLast) { // if there isn't a range, there's nothing to do
continue;
}
+ // t start/last describe the range of spans that match the t of this span
double t = span.fT;
int tStart = index;
- while (tStart > 0 && t == fTs[tStart - 1].fT) {
- --tStart;
- }
+ while (--tStart >= 0 && (t == fTs[tStart].fT || fTs[tStart].fTiny))
+ ;
int tLast = index;
- int last = count - 1;
- while (tLast < last && t == fTs[tLast + 1].fT) {
+ while (fTs[tLast].fTiny) {
++tLast;
}
+ while (++tLast < count && t == fTs[tLast].fT)
+ ;
for (int peekIndex = peekStart; peekIndex <= peekLast; ++peekIndex) {
- if (peekIndex == span.fOtherIndex) {
+ if (peekIndex == span.fOtherIndex) { // skip the other span pointed to by this span
continue;
}
const SkOpSpan& peekSpan = other->fTs[peekIndex];
SkOpSegment* match = peekSpan.fOther;
const double matchT = peekSpan.fOtherT;
- for (int tIndex = tStart; tIndex <= tLast; ++tIndex) {
+ // see if any of the spans match the other spans
+ for (int tIndex = tStart + 1; tIndex < tLast; ++tIndex) {
const SkOpSpan& tSpan = fTs[tIndex];
- if (tSpan.fOther == match && tSpan.fOtherT == matchT) {
- goto nextPeeker;
+ if (tSpan.fOther == match) {
+ if (tSpan.fOtherT == matchT) {
+ goto nextPeeker;
+ }
+ double midT = (tSpan.fOtherT + matchT) / 2;
+ if (match->betweenPoints(midT, tSpan.fPt, peekSpan.fPt)) {
+ goto nextPeeker;
+ }
}
}
+ if (missingSpans.count() > 0) {
+ const MissingSpan& lastMissing = missingSpans.back();
+ if (lastMissing.fCommand == MissingSpan::kAddMissing
+ && lastMissing.fT == t
+ && lastMissing.fOther == match
+ && lastMissing.fOtherT == matchT) {
+ SkASSERT(lastMissing.fPt == peekSpan.fPt);
+ continue;
+ }
+ }
+#if DEBUG_CHECK_ENDS
+ SkDebugf("%s id=%d missing t=%1.9g other=%d otherT=%1.9g pt=(%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, t, match->fID, matchT, peekSpan.fPt.fX, peekSpan.fPt.fY);
+#endif
// this segment is missing a entry that the other contains
// remember so we can add the missing one and recompute the indices
- SkOpSpan* missing = missingSpans.append();
- missing->fT = t;
- missing->fOther = match;
- missing->fOtherT = matchT;
- missing->fPt = peekSpan.fPt;
+ MissingSpan& missing = missingSpans.push_back();
+ SkDEBUGCODE(sk_bzero(&missing, sizeof(missing)));
+ missing.fCommand = MissingSpan::kAddMissing;
+ missing.fT = t;
+ missing.fOther = match;
+ missing.fOtherT = matchT;
+ missing.fPt = peekSpan.fPt;
}
nextPeeker:
;
}
- int missingCount = missingSpans.count();
- if (missingCount == 0) {
+ if (missingSpans.count() == 0) {
return;
}
+ // if one end is near the other point, look for a coincident span
+ for (int index = 0; index < count; ++index) {
+ const SkOpSpan& span = fTs[index];
+ if (span.fT > 0) {
+ break;
+ }
+ const SkOpSpan& otherSpan = span.fOther->span(span.fOtherIndex);
+ if (span.fNear) {
+ SkASSERT(otherSpan.fPt == fPts[0]);
+ adjustNear(0, span.fPt, &missingSpans);
+ continue;
+ }
+ if (otherSpan.fNear) {
+ SkASSERT(span.fPt == fPts[0]);
+ adjustNear(0, otherSpan.fPt, &missingSpans);
+ }
+ }
+ for (int index = count; --index >= 0; ) {
+ const SkOpSpan& span = fTs[index];
+ if (span.fT < 1) {
+ break;
+ }
+ const SkOpSegment* other = span.fOther;
+ if (span.fNear) {
+ SkASSERT(other->ptAtT(span.fOtherT) == ptAtT(1));
+ const SkPoint& otherPt = other->xyAtT(span.fOtherIndex);
+ SkASSERT(otherPt != ptAtT(1));
+ adjustNear(1, otherPt, &missingSpans);
+ continue;
+ }
+ const SkOpSpan& otherSpan = other->span(span.fOtherIndex);
+ if (otherSpan.fNear) {
+ SkASSERT(otherSpan.fPt == ptAtT(1));
+ SkPoint otherPt = other->ptAtT(span.fOtherT);
+ SkASSERT(otherPt != ptAtT(1));
+ adjustNear(1, otherPt, &missingSpans);
+ }
+ }
debugValidate();
+ int missingCount = missingSpans.count();
for (int index = 0; index < missingCount; ++index) {
- const SkOpSpan& missing = missingSpans[index];
- addTPair(missing.fT, missing.fOther, missing.fOtherT, false, missing.fPt);
+ MissingSpan& missing = missingSpans[index];
+ switch (missing.fCommand) {
+ case MissingSpan::kNoAction:
+ break;
+ case MissingSpan::kAddMissing:
+ addTPair(missing.fT, missing.fOther, missing.fOtherT, false, missing.fPt);
+ break;
+ case MissingSpan::kRemoveNear: {
+ SkOpSegment* segment = missing.fSegment;
+ int count = segment->count();
+ for (int inner = 0; inner < count; ++inner) {
+ const SkOpSpan& span = segment->span(inner);
+ if (span.fT != missing.fT && span.fOther != missing.fOther) {
+ continue;
+ }
+ SkASSERT(span.fNear);
+ SkOpSegment* other = span.fOther;
+ int otherCount = other->count();
+ for (int otherIndex = 0; otherIndex < otherCount; ++otherIndex) {
+ const SkOpSpan& otherSpan = other->span(otherIndex);
+ if (otherSpan.fT == span.fOtherT && otherSpan.fOther == segment
+ && otherSpan.fOtherT == span.fT) {
+ if (otherSpan.fDone) {
+ other->fDoneSpans--;
+ }
+ other->fTs.remove(otherIndex);
+ // FIXME: remove may leave a tiny dangling -- recompute tiny w/index
+ break;
+ }
+ }
+ if (span.fDone) {
+ segment->fDoneSpans--;
+ }
+ segment->fTs.remove(inner);
+ // FIXME: remove may leave a tiny dangling -- recompute tiny w/index
+ break;
+ }
+ break;
+ }
+ case MissingSpan::kZeroSpan: {
+ SkOpSegment* segment = missing.fSegment;
+ int count = segment->count();
+ for (int inner = 0; inner < count; ++inner) {
+ SkOpSpan& span = segment->fTs[inner];
+ if (span.fT < missing.fT) {
+ continue;
+ }
+ if (span.fT >= missing.fEndT) {
+ break;
+ }
+ span.fWindValue = span.fOppValue = 0;
+ if (!span.fDone) {
+ span.fDone = true;
+ ++segment->fDoneSpans;
+ }
+ }
+ break;
+ }
+ }
}
fixOtherTIndex();
+ // OPTIMIZATION: this may fix indices more than once. Build an array of unique segments to
+ // avoid this
for (int index = 0; index < missingCount; ++index) {
- const SkOpSpan& missing = missingSpans[index];
- missing.fOther->fixOtherTIndex();
+ const MissingSpan& missing = missingSpans[index];
+ switch (missing.fCommand) {
+ case MissingSpan::kNoAction:
+ break;
+ case MissingSpan::kAddMissing:
+ missing.fOther->fixOtherTIndex();
+ break;
+ case MissingSpan::kRemoveNear:
+ missing.fSegment->fixOtherTIndex();
+ missing.fOther->fixOtherTIndex();
+ break;
+ case MissingSpan::kZeroSpan:
+ break;
+ }
}
debugValidate();
}
-bool SkOpSegment::equalPoints(int greaterTIndex, int lesserTIndex) {
- SkASSERT(greaterTIndex >= lesserTIndex);
- double greaterT = fTs[greaterTIndex].fT;
- double lesserT = fTs[lesserTIndex].fT;
- if (greaterT == lesserT) {
+bool SkOpSegment::checkSmall(int index) const {
+ if (fTs[index].fSmall) {
return true;
}
- if (!approximately_negative(greaterT - lesserT)) {
- return false;
+ double tBase = fTs[index].fT;
+ while (index > 0 && precisely_negative(tBase - fTs[--index].fT))
+ ;
+ return fTs[index].fSmall;
+}
+
+// if pair of spans on either side of tiny have the same end point and mid point, mark
+// them as parallel
+// OPTIMIZATION : mark the segment to note that some span is tiny
+void SkOpSegment::checkTiny() {
+ SkSTArray<kMissingSpanCount, MissingSpan, true> missingSpans;
+ SkOpSpan* thisSpan = fTs.begin() - 1;
+ const SkOpSpan* endSpan = fTs.end() - 1; // last can't be tiny
+ while (++thisSpan < endSpan) {
+ if (!thisSpan->fTiny) {
+ continue;
+ }
+ SkOpSpan* nextSpan = thisSpan + 1;
+ double thisT = thisSpan->fT;
+ double nextT = nextSpan->fT;
+ if (thisT == nextT) {
+ continue;
+ }
+ SkASSERT(thisT < nextT);
+ SkASSERT(thisSpan->fPt == nextSpan->fPt);
+ SkOpSegment* thisOther = thisSpan->fOther;
+ SkOpSegment* nextOther = nextSpan->fOther;
+ int oIndex = thisSpan->fOtherIndex;
+ for (int oStep = -1; oStep <= 1; oStep += 2) {
+ int oEnd = thisOther->nextExactSpan(oIndex, oStep);
+ if (oEnd < 0) {
+ continue;
+ }
+ const SkOpSpan& oSpan = thisOther->span(oEnd);
+ int nIndex = nextSpan->fOtherIndex;
+ for (int nStep = -1; nStep <= 1; nStep += 2) {
+ int nEnd = nextOther->nextExactSpan(nIndex, nStep);
+ if (nEnd < 0) {
+ continue;
+ }
+ const SkOpSpan& nSpan = nextOther->span(nEnd);
+ if (oSpan.fPt != nSpan.fPt) {
+ continue;
+ }
+ double oMidT = (thisSpan->fOtherT + oSpan.fT) / 2;
+ const SkPoint& oPt = thisOther->ptAtT(oMidT);
+ double nMidT = (nextSpan->fOtherT + nSpan.fT) / 2;
+ const SkPoint& nPt = nextOther->ptAtT(nMidT);
+ if (!AlmostEqualUlps(oPt, nPt)) {
+ continue;
+ }
+#if DEBUG_CHECK_TINY
+ SkDebugf("%s [%d] add coincidence [%d] [%d]\n", __FUNCTION__, fID,
+ thisOther->fID, nextOther->fID);
+#endif
+ // this segment is missing a entry that the other contains
+ // remember so we can add the missing one and recompute the indices
+ MissingSpan& missing = missingSpans.push_back();
+ SkDEBUGCODE(sk_bzero(&missing, sizeof(missing)));
+ missing.fCommand = MissingSpan::kAddMissing;
+ missing.fSegment = thisOther;
+ missing.fT = thisSpan->fOtherT;
+ missing.fOther = nextOther;
+ missing.fOtherT = nextSpan->fOtherT;
+ missing.fPt = thisSpan->fPt;
+ }
+ }
+ }
+ int missingCount = missingSpans.count();
+ if (!missingCount) {
+ return;
+ }
+ for (int index = 0; index < missingCount; ++index) {
+ MissingSpan& missing = missingSpans[index];
+ missing.fSegment->addTPair(missing.fT, missing.fOther, missing.fOtherT, false, missing.fPt);
+ }
+ for (int index = 0; index < missingCount; ++index) {
+ MissingSpan& missing = missingSpans[index];
+ missing.fSegment->fixOtherTIndex();
+ missing.fOther->fixOtherTIndex();
}
- return xyAtT(greaterTIndex) == xyAtT(lesserTIndex);
}
/*
@@ -1214,8 +1675,7 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart
*nextEnd = *nextStart;
do {
*nextEnd += step;
- }
- while (precisely_zero(startT - other->fTs[*nextEnd].fT));
+ } while (precisely_zero(startT - other->fTs[*nextEnd].fT));
SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count());
if (other->isTiny(SkMin32(*nextStart, *nextEnd))) {
*unsortable = true;
@@ -1227,13 +1687,12 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart
SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
SkASSERT(startIndex - endIndex != 0);
SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
- addTwoAngles(startIndex, end, &angles);
- buildAngles(end, &angles, true);
SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
- bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMustBeOrdered_SortAngleKind);
+ int calcWinding = computeSum(startIndex, end, SkOpAngle::kBinaryOpp, &angles, &sorted);
+ bool sortable = calcWinding != SK_NaN32;
int angleCount = angles.count();
int firstIndex = findStartingEdge(sorted, startIndex, end);
- SkASSERT(firstIndex >= 0);
+ SkASSERT(!sortable || firstIndex >= 0);
#if DEBUG_SORT
debugShowSort(__FUNCTION__, sorted, firstIndex, sortable);
#endif
@@ -1277,22 +1736,25 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart
return NULL;
}
foundAngle = nextAngle;
- foundDone = nextSegment->done(nextAngle) && !nextSegment->isTiny(nextAngle);
+ foundDone = nextSegment->done(nextAngle);
}
}
if (nextSegment->done()) {
continue;
}
- if (nextSegment->windSum(nextAngle) != SK_MinS32) {
+ if (nextSegment->isTiny(nextAngle)) {
continue;
}
- SkOpSpan* last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding,
- oppSumWinding, activeAngle, nextAngle);
+ if (!activeAngle) {
+ nextSegment->markAndChaseDoneBinary(nextAngle->start(), nextAngle->end());
+ }
+ SkOpSpan* last = nextAngle->lastMarked();
if (last) {
*chase->append() = last;
#if DEBUG_WINDING
- SkDebugf("%s chase.append id=%d\n", __FUNCTION__,
- last->fOther->fTs[last->fOtherIndex].fOther->debugID());
+ SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID(), last->fWindSum,
+ last->fSmall);
#endif
}
} while (++nextIndex != lastIndex);
@@ -1303,7 +1765,6 @@ SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart
*nextStart = foundAngle->start();
*nextEnd = foundAngle->end();
nextSegment = foundAngle->segment();
-
#if DEBUG_WINDING
SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
__FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd);
@@ -1340,22 +1801,24 @@ SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* next
*nextEnd = *nextStart;
do {
*nextEnd += step;
- }
- while (precisely_zero(startT - other->fTs[*nextEnd].fT));
+ } while (precisely_zero(startT - other->fTs[*nextEnd].fT));
SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count());
+ if (other->isTiny(SkMin32(*nextStart, *nextEnd))) {
+ *unsortable = true;
+ return NULL;
+ }
return other;
}
// more than one viable candidate -- measure angles to find best
SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
SkASSERT(startIndex - endIndex != 0);
SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
- addTwoAngles(startIndex, end, &angles);
- buildAngles(end, &angles, true);
SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
- bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMustBeOrdered_SortAngleKind);
+ int calcWinding = computeSum(startIndex, end, SkOpAngle::kUnaryWinding, &angles, &sorted);
+ bool sortable = calcWinding != SK_NaN32;
int angleCount = angles.count();
int firstIndex = findStartingEdge(sorted, startIndex, end);
- SkASSERT(firstIndex >= 0);
+ SkASSERT(!sortable || firstIndex >= 0);
#if DEBUG_SORT
debugShowSort(__FUNCTION__, sorted, firstIndex, sortable);
#endif
@@ -1400,15 +1863,19 @@ SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* next
if (nextSegment->done()) {
continue;
}
- if (nextSegment->windSum(nextAngle) != SK_MinS32) {
+ if (nextSegment->isTiny(nextAngle)) {
continue;
}
- SkOpSpan* last = nextSegment->markAngle(maxWinding, sumWinding, activeAngle, nextAngle);
+ if (!activeAngle) {
+ nextSegment->markAndChaseDoneUnary(nextAngle->start(), nextAngle->end());
+ }
+ SkOpSpan* last = nextAngle->lastMarked();
if (last) {
*chase->append() = last;
#if DEBUG_WINDING
- SkDebugf("%s chase.append id=%d\n", __FUNCTION__,
- last->fOther->fTs[last->fOtherIndex].fOther->debugID());
+ SkDebugf("%s chase.append id=%d windSum=%d small=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID(), last->fWindSum,
+ last->fSmall);
#endif
}
} while (++nextIndex != lastIndex);
@@ -1431,8 +1898,7 @@ SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsort
const int endIndex = *nextEnd;
SkASSERT(startIndex != endIndex);
SkDEBUGCODE(int count = fTs.count());
- SkASSERT(startIndex < endIndex ? startIndex < count - 1
- : startIndex > 0);
+ SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0);
int step = SkSign32(endIndex - startIndex);
int end = nextExactSpan(startIndex, step);
SkASSERT(end >= 0);
@@ -1461,14 +1927,11 @@ SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsort
*nextEnd = *nextStart;
do {
*nextEnd += step;
- }
- while (precisely_zero(startT - other->fTs[*nextEnd].fT));
+ } while (precisely_zero(startT - other->fTs[*nextEnd].fT));
if (other->fTs[SkMin32(*nextStart, *nextEnd)].fWindValue) {
break;
}
-#ifdef SK_DEBUG
SkASSERT(firstLoop);
-#endif
SkDEBUGCODE(firstLoop = false;)
step = -step;
} while (true);
@@ -1478,25 +1941,24 @@ SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsort
SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
SkASSERT(startIndex - endIndex != 0);
SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
- addTwoAngles(startIndex, end, &angles);
- buildAngles(end, &angles, false);
SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
- bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMustBeOrdered_SortAngleKind);
- if (!sortable) {
- *unsortable = true;
-#if DEBUG_SORT
- debugShowSort(__FUNCTION__, sorted, findStartingEdge(sorted, startIndex, end), 0, 0,
- sortable);
-#endif
- return NULL;
- }
+ int calcWinding = computeSum(startIndex, end, SkOpAngle::kUnaryXor, &angles, &sorted);
+ bool sortable = calcWinding != SK_NaN32;
int angleCount = angles.count();
int firstIndex = findStartingEdge(sorted, startIndex, end);
- SkASSERT(firstIndex >= 0);
+ SkASSERT(!sortable || firstIndex >= 0);
#if DEBUG_SORT
debugShowSort(__FUNCTION__, sorted, firstIndex, 0, 0, sortable);
#endif
+ if (!sortable) {
+ *unsortable = true;
+ return NULL;
+ }
SkASSERT(sorted[firstIndex]->segment() == this);
+#if DEBUG_WINDING
+ SkDebugf("%s firstIndex=[%d] sign=%d\n", __FUNCTION__, firstIndex,
+ sorted[firstIndex]->sign());
+#endif
int nextIndex = firstIndex + 1;
int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
const SkOpAngle* foundAngle = NULL;
@@ -1551,138 +2013,6 @@ int SkOpSegment::findStartingEdge(const SkTArray<SkOpAngle*, true>& sorted, int
return firstIndex;
}
-// FIXME: this is tricky code; needs its own unit test
-// note that fOtherIndex isn't computed yet, so it can't be used here
-void SkOpSegment::findTooCloseToCall() {
- int count = fTs.count();
- if (count < 3) { // require t=0, x, 1 at minimum
- return;
- }
- int matchIndex = 0;
- int moCount;
- SkOpSpan* match;
- SkOpSegment* mOther;
- do {
- match = &fTs[matchIndex];
- mOther = match->fOther;
- // FIXME: allow quads, cubics to be near coincident?
- if (mOther->fVerb == SkPath::kLine_Verb) {
- moCount = mOther->fTs.count();
- if (moCount >= 3) {
- break;
- }
- }
- if (++matchIndex >= count) {
- return;
- }
- } while (true); // require t=0, x, 1 at minimum
- // OPTIMIZATION: defer matchPt until qualifying toCount is found?
- const SkPoint* matchPt = &xyAtT(match);
- // look for a pair of nearby T values that map to the same (x,y) value
- // if found, see if the pair of other segments share a common point. If
- // so, the span from here to there is coincident.
- for (int index = matchIndex + 1; index < count; ++index) {
- SkOpSpan* test = &fTs[index];
- if (test->fDone) {
- continue;
- }
- SkOpSegment* tOther = test->fOther;
- if (tOther->fVerb != SkPath::kLine_Verb) {
- continue; // FIXME: allow quads, cubics to be near coincident?
- }
- int toCount = tOther->fTs.count();
- if (toCount < 3) { // require t=0, x, 1 at minimum
- continue;
- }
- const SkPoint* testPt = &xyAtT(test);
- if (*matchPt != *testPt) {
- matchIndex = index;
- moCount = toCount;
- match = test;
- mOther = tOther;
- matchPt = testPt;
- continue;
- }
- int moStart = -1;
- int moEnd = -1;
- double moStartT = 0;
- double moEndT = 0;
- for (int moIndex = 0; moIndex < moCount; ++moIndex) {
- SkOpSpan& moSpan = mOther->fTs[moIndex];
- if (moSpan.fDone) {
- continue;
- }
- if (moSpan.fOther == this) {
- if (moSpan.fOtherT == match->fT) {
- moStart = moIndex;
- moStartT = moSpan.fT;
- }
- continue;
- }
- if (moSpan.fOther == tOther) {
- if (tOther->windValueAt(moSpan.fOtherT) == 0) {
- moStart = -1;
- break;
- }
- SkASSERT(moEnd == -1);
- moEnd = moIndex;
- moEndT = moSpan.fT;
- }
- }
- if (moStart < 0 || moEnd < 0) {
- continue;
- }
- // FIXME: if moStartT, moEndT are initialized to NaN, can skip this test
- if (approximately_equal(moStartT, moEndT)) {
- continue;
- }
- int toStart = -1;
- int toEnd = -1;
- double toStartT = 0;
- double toEndT = 0;
- for (int toIndex = 0; toIndex < toCount; ++toIndex) {
- SkOpSpan& toSpan = tOther->fTs[toIndex];
- if (toSpan.fDone) {
- continue;
- }
- if (toSpan.fOther == this) {
- if (toSpan.fOtherT == test->fT) {
- toStart = toIndex;
- toStartT = toSpan.fT;
- }
- continue;
- }
- if (toSpan.fOther == mOther && toSpan.fOtherT == moEndT) {
- if (mOther->windValueAt(toSpan.fOtherT) == 0) {
- moStart = -1;
- break;
- }
- SkASSERT(toEnd == -1);
- toEnd = toIndex;
- toEndT = toSpan.fT;
- }
- }
- // FIXME: if toStartT, toEndT are initialized to NaN, can skip this test
- if (toStart <= 0 || toEnd <= 0) {
- continue;
- }
- if (approximately_equal(toStartT, toEndT)) {
- continue;
- }
- // test to see if the segment between there and here is linear
- if (!mOther->isLinear(moStart, moEnd)
- || !tOther->isLinear(toStart, toEnd)) {
- continue;
- }
- bool flipped = (moStart - moEnd) * (toStart - toEnd) < 1;
- if (flipped) {
- mOther->addTCancel(moStartT, moEndT, tOther, toEndT, toStartT);
- } else {
- mOther->addTCoincident(moStartT, moEndT, tOther, toStartT, toEndT);
- }
- }
-}
-
// FIXME: either:
// a) mark spans with either end unsortable as done, or
// b) rewrite findTop / findTopSegment / findTopContour to iterate further
@@ -1722,14 +2052,24 @@ SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsort
SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle, true> angles;
SkASSERT(firstT - end != 0);
addTwoAngles(end, firstT, &angles);
- buildAngles(firstT, &angles, true);
+ if (!buildAngles(firstT, &angles, true) && onlySortable) {
+// *unsortable = true;
+// return NULL;
+ }
SkSTArray<SkOpAngle::kStackBasedCount, SkOpAngle*, true> sorted;
bool sortable = SortAngles(angles, &sorted, SkOpSegment::kMayBeUnordered_SortAngleKind);
+ if (onlySortable && !sortable) {
+ *unsortable = true;
+ return NULL;
+ }
int first = SK_MaxS32;
SkScalar top = SK_ScalarMax;
int count = sorted.count();
for (int index = 0; index < count; ++index) {
const SkOpAngle* angle = sorted[index];
+ if (onlySortable && angle->unorderable()) {
+ continue;
+ }
SkOpSegment* next = angle->segment();
SkPathOpsBounds bounds;
next->subDivideBounds(angle->end(), angle->start(), &bounds);
@@ -1742,10 +2082,6 @@ SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsort
#if DEBUG_SORT // || DEBUG_SWAP_TOP
sorted[first]->segment()->debugShowSort(__FUNCTION__, sorted, first, 0, 0, sortable);
#endif
- if (onlySortable && !sortable) {
- *unsortable = true;
- return NULL;
- }
// skip edges that have already been processed
firstT = first - 1;
SkOpSegment* leftSegment;
@@ -1789,6 +2125,7 @@ SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsort
// while the following fixes the indices up again, it isn't smart about
// skipping segments whose indices are already correct
// assuming we leave the code that wrote the index in the first place
+// FIXME: if called after remove, this needs to correct tiny
void SkOpSegment::fixOtherTIndex() {
int iCount = fTs.count();
for (int i = 0; i < iCount; ++i) {
@@ -1869,20 +2206,6 @@ void SkOpSegment::initWinding(int start, int end, double tHit, int winding, SkSc
(void) markAndChaseWinding(start, end, winding, oppWind);
}
-bool SkOpSegment::isLinear(int start, int end) const {
- if (fVerb == SkPath::kLine_Verb) {
- return true;
- }
- if (fVerb == SkPath::kQuad_Verb) {
- SkDQuad qPart = SkDQuad::SubDivide(fPts, fTs[start].fT, fTs[end].fT);
- return qPart.isLinear(0, 2);
- } else {
- SkASSERT(fVerb == SkPath::kCubic_Verb);
- SkDCubic cPart = SkDCubic::SubDivide(fPts, fTs[start].fT, fTs[end].fT);
- return cPart.isLinear(0, 3);
- }
-}
-
// OPTIMIZE: successive calls could start were the last leaves off
// or calls could specialize to walk forwards or backwards
bool SkOpSegment::isMissing(double startT) const {
@@ -2027,6 +2350,13 @@ SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, bool activeAngl
} else {
last = markAndChaseDoneUnary(angle, maxWinding);
}
+#if DEBUG_WINDING
+ if (last) {
+ SkDebugf("%s last id=%d windSum=%d small=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID(), last->fWindSum,
+ last->fSmall);
+ }
+#endif
return last;
}
@@ -2045,6 +2375,13 @@ SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWindi
} else {
last = markAndChaseDoneBinary(angle, maxWinding, oppMaxWinding);
}
+#if DEBUG_WINDING
+ if (last) {
+ SkDebugf("%s last id=%d windSum=%d small=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID(), last->fWindSum,
+ last->fSmall);
+ }
+#endif
return last;
}
@@ -2146,9 +2483,7 @@ SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int windi
debugShowNewWinding(funName, span, winding);
#endif
SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding);
-#ifdef SK_DEBUG
- SkASSERT(abs(winding) <= gDebugMaxWindSum);
-#endif
+ SkASSERT(abs(winding) <= SkPathOpsDebug::gMaxWindSum);
span.fWindSum = winding;
return &span;
}
@@ -2163,14 +2498,10 @@ SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int windi
debugShowNewWinding(funName, span, winding, oppWinding);
#endif
SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding);
-#ifdef SK_DEBUG
- SkASSERT(abs(winding) <= gDebugMaxWindSum);
-#endif
+ SkASSERT(abs(winding) <= SkPathOpsDebug::gMaxWindSum);
span.fWindSum = winding;
SkASSERT(span.fOppSum == SK_MinS32 || span.fOppSum == oppWinding);
-#ifdef SK_DEBUG
- SkASSERT(abs(oppWinding) <= gDebugMaxWindSum);
-#endif
+ SkASSERT(abs(oppWinding) <= SkPathOpsDebug::gMaxWindSum);
span.fOppSum = oppWinding;
return &span;
}
@@ -2331,6 +2662,21 @@ void SkOpSegment::matchWindingValue(int tIndex, double t, bool borrowWind) {
}
}
+double SkOpSegment::missingNear(double t, const SkOpSegment* other, const SkPoint& startPt,
+ const SkPoint& endPt) const {
+ int count = this->count();
+ for (int index = 0; index < count; ++index) {
+ const SkOpSpan& span = this->span(index);
+ if (span.fOther == other && span.fPt == startPt) {
+ double midT = (t + span.fT) / 2;
+ if (betweenPoints(midT, startPt, endPt)) {
+ return span.fT;
+ }
+ }
+ }
+ return -1;
+}
+
// return span if when chasing, two or more radiating spans are not done
// OPTIMIZATION: ? multiple spans is detected when there is only one valid
// candidate and the remaining spans have windValue == 0 (canceled by
@@ -2355,6 +2701,10 @@ bool SkOpSegment::nextCandidate(int* start, int* end) const {
SkOpSegment* SkOpSegment::nextChase(int* index, const int step, int* min, SkOpSpan** last) {
int end = nextExactSpan(*index, step);
SkASSERT(end >= 0);
+ if (fTs[end].fSmall) {
+ *last = NULL;
+ return NULL;
+ }
if (multipleSpans(end)) {
*last = &fTs[end];
return NULL;
@@ -2366,7 +2716,7 @@ SkOpSegment* SkOpSegment::nextChase(int* index, const int step, int* min, SkOpSp
int otherEnd = other->nextExactSpan(*index, step);
SkASSERT(otherEnd >= 0);
*min = SkMin32(*index, otherEnd);
- if (other->fTs[*min].fTiny) {
+ if (other->fTs[*min].fSmall) {
*last = NULL;
return NULL;
}
@@ -2397,18 +2747,30 @@ int SkOpSegment::nextSpan(int from, int step) const {
// FIXME
// this returns at any difference in T, vs. a preset minimum. It may be
// that all callers to nextSpan should use this instead.
-// OPTIMIZATION splitting this into separate loops for up/down steps
-// would allow using precisely_negative instead of precisely_zero
int SkOpSegment::nextExactSpan(int from, int step) const {
- const SkOpSpan& fromSpan = fTs[from];
- int count = fTs.count();
int to = from;
- while (step > 0 ? ++to < count : --to >= 0) {
- const SkOpSpan& span = fTs[to];
- if (precisely_zero(span.fT - fromSpan.fT)) {
- continue;
+ if (step < 0) {
+ const SkOpSpan& fromSpan = fTs[from];
+ while (--to >= 0) {
+ const SkOpSpan& span = fTs[to];
+ if (precisely_negative(fromSpan.fT - span.fT) || span.fTiny) {
+ continue;
+ }
+ return to;
+ }
+ } else {
+ while (fTs[from].fTiny) {
+ from++;
+ }
+ const SkOpSpan& fromSpan = fTs[from];
+ int count = fTs.count();
+ while (++to < count) {
+ const SkOpSpan& span = fTs[to];
+ if (precisely_negative(span.fT - fromSpan.fT)) {
+ continue;
+ }
+ return to;
}
- return to;
}
return -1;
}
@@ -2428,6 +2790,16 @@ void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding, int*
*oppMaxWinding = *sumSuWinding;
*oppSumWinding = *sumSuWinding -= oppDeltaSum;
}
+ SkASSERT(abs(*sumWinding) <= SkPathOpsDebug::gMaxWindSum);
+ SkASSERT(abs(*oppSumWinding) <= SkPathOpsDebug::gMaxWindSum);
+}
+
+void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding,
+ int* maxWinding, int* sumWinding) {
+ int deltaSum = spanSign(index, endIndex);
+ *maxWinding = *sumMiWinding;
+ *sumWinding = *sumMiWinding -= deltaSum;
+ SkASSERT(abs(*sumWinding) <= SkPathOpsDebug::gMaxWindSum);
}
// This marks all spans unsortable so that this info is available for early
@@ -2440,8 +2812,6 @@ bool SkOpSegment::SortAngles(const SkTArray<SkOpAngle, true>& angles,
bool sortable = true;
int angleCount = angles.count();
int angleIndex;
-// FIXME: caller needs to use SkTArray constructor with reserve count
-// angleList->setReserve(angleCount);
for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
const SkOpAngle& angle = angles[angleIndex];
angleList->push_back(const_cast<SkOpAngle*>(&angle));
@@ -2470,6 +2840,34 @@ bool SkOpSegment::SortAngles(const SkTArray<SkOpAngle, true>& angles,
return sortable;
}
+// set segments to unsortable if angle is unsortable, but do not set all angles
+// note that for a simple 4 way crossing, two of the edges may be orderable even though
+// two edges are too short to be orderable.
+// perhaps some classes of unsortable angles should make all shared angles unsortable, but
+// simple lines that have tiny crossings are always sortable on the large ends
+// OPTIMIZATION: check earlier when angles are added to input if any are unsortable
+// may make sense then to mark all segments in angle sweep as unsortableStart/unsortableEnd
+// solely for the purpose of short-circuiting future angle building around this center
+bool SkOpSegment::SortAngles2(const SkTArray<SkOpAngle, true>& angles,
+ SkTArray<SkOpAngle*, true>* angleList) {
+ int angleCount = angles.count();
+ int angleIndex;
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ const SkOpAngle& angle = angles[angleIndex];
+ if (angle.unsortable()) {
+ return false;
+ }
+ angleList->push_back(const_cast<SkOpAngle*>(&angle));
+#if DEBUG_ANGLE
+ (*(angleList->end() - 1))->setID(angleIndex);
+#endif
+ }
+ SkTQSort<SkOpAngle>(angleList->begin(), angleList->end() - 1);
+ // at this point angles are sorted but individually may not be orderable
+ // this means that only adjcent orderable segments may transfer winding
+ return true;
+}
+
// return true if midpoints were computed
bool SkOpSegment::subDivide(int start, int end, SkPoint edge[4]) const {
SkASSERT(start != end);
@@ -2563,11 +2961,19 @@ bool SkOpSegment::isTiny(int index) const {
return fTs[index].fTiny;
}
-void SkOpSegment::TrackOutside(SkTArray<double, true>* outsideTs, double end, double start) {
- int outCount = outsideTs->count();
- if (outCount == 0 || !approximately_negative(end - (*outsideTs)[outCount - 2])) {
- outsideTs->push_back(end);
- outsideTs->push_back(start);
+void SkOpSegment::TrackOutsidePair(SkTArray<SkPoint, true>* outsidePts, const SkPoint& endPt,
+ const SkPoint& startPt) {
+ int outCount = outsidePts->count();
+ if (outCount == 0 || endPt != (*outsidePts)[outCount - 2]) {
+ outsidePts->push_back(endPt);
+ outsidePts->push_back(startPt);
+ }
+}
+
+void SkOpSegment::TrackOutside(SkTArray<SkPoint, true>* outsidePts, const SkPoint& startPt) {
+ int outCount = outsidePts->count();
+ if (outCount == 0 || startPt != (*outsidePts)[outCount - 1]) {
+ outsidePts->push_back(startPt);
}
}
@@ -2615,7 +3021,8 @@ int SkOpSegment::updateWinding(int index, int endIndex) const {
int lesser = SkMin32(index, endIndex);
int winding = windSum(lesser);
int spanWinding = spanSign(index, endIndex);
- if (winding && UseInnerWinding(winding - spanWinding, winding) && winding != SK_MaxS32) {
+ if (winding && UseInnerWinding(winding - spanWinding, winding)
+ && winding != SK_MaxS32) {
winding -= spanWinding;
}
return winding;
@@ -2627,10 +3034,42 @@ int SkOpSegment::updateWinding(const SkOpAngle* angle) const {
return updateWinding(endIndex, startIndex);
}
+int SkOpSegment::updateWindingReverse(int index, int endIndex) const {
+ int lesser = SkMin32(index, endIndex);
+ int winding = windSum(lesser);
+ int spanWinding = spanSign(endIndex, index);
+ if (winding && UseInnerWindingReverse(winding - spanWinding, winding)
+ && winding != SK_MaxS32) {
+ winding -= spanWinding;
+ }
+ return winding;
+}
+
int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) const {
int startIndex = angle->start();
int endIndex = angle->end();
- return updateWinding(startIndex, endIndex);
+ return updateWindingReverse(endIndex, startIndex);
+}
+
+// OPTIMIZATION: does the following also work, and is it any faster?
+// return outerWinding * innerWinding > 0
+// || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0)))
+bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) {
+ SkASSERT(outerWinding != SK_MaxS32);
+ SkASSERT(innerWinding != SK_MaxS32);
+ int absOut = abs(outerWinding);
+ int absIn = abs(innerWinding);
+ bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn;
+ return result;
+}
+
+bool SkOpSegment::UseInnerWindingReverse(int outerWinding, int innerWinding) {
+ SkASSERT(outerWinding != SK_MaxS32);
+ SkASSERT(innerWinding != SK_MaxS32);
+ int absOut = abs(outerWinding);
+ int absIn = abs(innerWinding);
+ bool result = absOut == absIn ? true : absOut < absIn;
+ return result;
}
int SkOpSegment::windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const {
@@ -2861,9 +3300,17 @@ void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int
void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true>& angles,
int first, const int contourWinding,
const int oppContourWinding, bool sortable) const {
- if (--gDebugSortCount < 0) {
+ if (--SkPathOpsDebug::gSortCount < 0) {
return;
}
+ if (!sortable) {
+ if (angles.count() == 0) {
+ return;
+ }
+ if (first < 0) {
+ first = 0;
+ }
+ }
SkASSERT(angles[first]->segment() == this);
SkASSERT(!sortable || angles.count() > 1);
int lastSum = contourWinding;
@@ -2872,8 +3319,9 @@ void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true
int windSum = lastSum - spanSign(firstAngle);
int oppoSign = oppSign(firstAngle);
int oppWindSum = oppLastSum - oppoSign;
- #define WIND_AS_STRING(x) char x##Str[12]; if (!valid_wind(x)) strcpy(x##Str, "?"); \
- else SK_SNPRINTF(x##Str, sizeof(x##Str), "%d", x)
+ #define WIND_AS_STRING(x) char x##Str[12]; \
+ if (!SkPathOpsDebug::ValidWind(x)) strcpy(x##Str, "?"); \
+ else SK_SNPRINTF(x##Str, sizeof(x##Str), "%d", x)
WIND_AS_STRING(contourWinding);
WIND_AS_STRING(oppContourWinding);
SkDebugf("%s %s contourWinding=%s oppContourWinding=%s sign=%d\n", fun, __FUNCTION__,
@@ -2931,7 +3379,7 @@ void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true
SkDebugf(" tStart=%1.9g tEnd=%1.9g", sSpan.fT, eSpan.fT);
#endif
SkDebugf(" sign=%d windValue=%d windSum=", angle.sign(), mSpan.fWindValue);
- winding_printf(mSpan.fWindSum);
+ SkPathOpsDebug::WindingPrintf(mSpan.fWindSum);
int last, wind;
if (opp) {
last = oppLastSum;
@@ -2940,7 +3388,8 @@ void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true
last = lastSum;
wind = windSum;
}
- bool useInner = valid_wind(last) && valid_wind(wind) && UseInnerWinding(last, wind);
+ bool useInner = SkPathOpsDebug::ValidWind(last) && SkPathOpsDebug::ValidWind(wind)
+ && UseInnerWinding(last, wind);
WIND_AS_STRING(last);
WIND_AS_STRING(wind);
WIND_AS_STRING(lastSum);
@@ -2954,10 +3403,8 @@ void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true
SkDebugf(" %s->%s (%s->%s)", lastStr, windStr, opp ? lastSumStr : oppLastSumStr,
opp ? windSumStr : oppWindSumStr);
}
- SkDebugf(" done=%d tiny=%d opp=%d\n", mSpan.fDone, mSpan.fTiny, opp);
-#if 0 && DEBUG_ANGLE
- angle.debugShow(segment.xyAtT(&sSpan));
-#endif
+ SkDebugf(" done=%d unord=%d small=%d tiny=%d opp=%d\n",
+ mSpan.fDone, angle.unorderable(), mSpan.fSmall, mSpan.fTiny, opp);
++index;
if (index == angles.count()) {
index = 0;
@@ -2970,6 +3417,14 @@ void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true
void SkOpSegment::debugShowSort(const char* fun, const SkTArray<SkOpAngle*, true>& angles,
int first, bool sortable) {
+ if (!sortable) {
+ if (angles.count() == 0) {
+ return;
+ }
+ if (first < 0) {
+ first = 0;
+ }
+ }
const SkOpAngle* firstAngle = angles[first];
const SkOpSegment* segment = firstAngle->segment();
int winding = segment->updateWinding(firstAngle);
@@ -3025,3 +3480,40 @@ void SkOpSegment::debugValidate() const {
SkASSERT(done == fDoneSpans);
#endif
}
+
+#ifdef SK_DEBUG
+void SkOpSegment::dumpPts() const {
+ int last = SkPathOpsVerbToPoints(fVerb);
+ SkDebugf("{{");
+ int index = 0;
+ do {
+ SkDPoint::DumpSkPoint(fPts[index]);
+ SkDebugf(", ");
+ } while (++index < last);
+ SkDPoint::DumpSkPoint(fPts[index]);
+ SkDebugf("}}\n");
+}
+
+void SkOpSegment::dumpDPts() const {
+ int count = SkPathOpsVerbToPoints(fVerb);
+ SkDebugf("{{");
+ int index = 0;
+ do {
+ SkDPoint dPt = {fPts[index].fX, fPts[index].fY};
+ dPt.dump();
+ if (index != count) {
+ SkDebugf(", ");
+ }
+ } while (++index <= count);
+ SkDebugf("}}\n");
+}
+
+void SkOpSegment::dumpSpans() const {
+ int count = this->count();
+ for (int index = 0; index < count; ++index) {
+ const SkOpSpan& span = this->span(index);
+ SkDebugf("[%d] ", index);
+ span.dump();
+ }
+}
+#endif
« no previous file with comments | « src/pathops/SkOpSegment.h ('k') | src/pathops/SkOpSpan.h » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698