| /* |
| * Copyright 2012 Google Inc. |
| * |
| * Use of this source code is governed by a BSD-style license that can be |
| * found in the LICENSE file. |
| */ |
| #include "SkOpCoincidence.h" |
| #include "SkOpContour.h" |
| #include "SkOpSegment.h" |
| #include "SkPathWriter.h" |
| |
| /* |
| After computing raw intersections, post process all segments to: |
| - find small collections of points that can be collapsed to a single point |
| - find missing intersections to resolve differences caused by different algorithms |
| |
| Consider segments containing tiny or small intervals. Consider coincident segments |
| because coincidence finds intersections through distance measurement that non-coincident |
| intersection tests cannot. |
| */ |
| |
| #define F (false) // discard the edge |
| #define T (true) // keep the edge |
| |
| static const bool gUnaryActiveEdge[2][2] = { |
| // from=0 from=1 |
| // to=0,1 to=0,1 |
| {F, T}, {T, F}, |
| }; |
| |
| static const bool gActiveEdge[kXOR_SkPathOp + 1][2][2][2][2] = { |
| // miFrom=0 miFrom=1 |
| // miTo=0 miTo=1 miTo=0 miTo=1 |
| // suFrom=0 1 suFrom=0 1 suFrom=0 1 suFrom=0 1 |
| // suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 |
| {{{{F, F}, {F, F}}, {{T, F}, {T, F}}}, {{{T, T}, {F, F}}, {{F, T}, {T, F}}}}, // mi - su |
| {{{{F, F}, {F, F}}, {{F, T}, {F, T}}}, {{{F, F}, {T, T}}, {{F, T}, {T, F}}}}, // mi & su |
| {{{{F, T}, {T, F}}, {{T, T}, {F, F}}}, {{{T, F}, {T, F}}, {{F, F}, {F, F}}}}, // mi | su |
| {{{{F, T}, {T, F}}, {{T, F}, {F, T}}}, {{{T, F}, {F, T}}, {{F, T}, {T, F}}}}, // mi ^ su |
| }; |
| |
| #undef F |
| #undef T |
| |
| SkOpAngle* SkOpSegment::activeAngle(SkOpSpanBase* start, SkOpSpanBase** startPtr, |
| SkOpSpanBase** endPtr, bool* done, bool* sortable) { |
| if (SkOpAngle* result = activeAngleInner(start, startPtr, endPtr, done, sortable)) { |
| return result; |
| } |
| if (SkOpAngle* result = activeAngleOther(start, startPtr, endPtr, done, sortable)) { |
| return result; |
| } |
| return NULL; |
| } |
| |
| SkOpAngle* SkOpSegment::activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, |
| SkOpSpanBase** endPtr, bool* done, bool* sortable) { |
| SkOpSpan* upSpan = start->upCastable(); |
| if (upSpan) { |
| if (upSpan->windValue() || upSpan->oppValue()) { |
| SkOpSpanBase* next = upSpan->next(); |
| if (!*endPtr) { |
| *startPtr = start; |
| *endPtr = next; |
| } |
| if (!upSpan->done()) { |
| if (upSpan->windSum() != SK_MinS32) { |
| return spanToAngle(start, next); |
| } |
| *done = false; |
| } |
| } else { |
| SkASSERT(upSpan->done()); |
| } |
| } |
| SkOpSpan* downSpan = start->prev(); |
| // edge leading into junction |
| if (downSpan) { |
| if (downSpan->windValue() || downSpan->oppValue()) { |
| if (!*endPtr) { |
| *startPtr = start; |
| *endPtr = downSpan; |
| } |
| if (!downSpan->done()) { |
| if (downSpan->windSum() != SK_MinS32) { |
| return spanToAngle(start, downSpan); |
| } |
| *done = false; |
| } |
| } else { |
| SkASSERT(downSpan->done()); |
| } |
| } |
| return NULL; |
| } |
| |
| SkOpAngle* SkOpSegment::activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, |
| SkOpSpanBase** endPtr, bool* done, bool* sortable) { |
| SkOpPtT* oPtT = start->ptT()->next(); |
| SkOpSegment* other = oPtT->segment(); |
| SkOpSpanBase* oSpan = oPtT->span(); |
| return other->activeAngleInner(oSpan, startPtr, endPtr, done, sortable); |
| } |
| |
| SkPoint SkOpSegment::activeLeftTop(SkOpSpanBase** firstSpan) { |
| SkASSERT(!done()); |
| SkPoint topPt = {SK_ScalarMax, SK_ScalarMax}; |
| // see if either end is not done since we want smaller Y of the pair |
| bool lastDone = true; |
| double lastT = -1; |
| SkOpSpanBase* span = &fHead; |
| do { |
| if (lastDone && (span->final() || span->upCast()->done())) { |
| goto next; |
| } |
| { |
| const SkPoint& xy = span->pt(); |
| if (topPt.fY > xy.fY || (topPt.fY == xy.fY && topPt.fX > xy.fX)) { |
| topPt = xy; |
| if (firstSpan) { |
| *firstSpan = span; |
| } |
| } |
| if (fVerb != SkPath::kLine_Verb && !lastDone) { |
| SkPoint curveTop = (*CurveTop[SkPathOpsVerbToPoints(fVerb)])(fPts, lastT, |
| span->t()); |
| if (topPt.fY > curveTop.fY || (topPt.fY == curveTop.fY |
| && topPt.fX > curveTop.fX)) { |
| topPt = curveTop; |
| if (firstSpan) { |
| *firstSpan = span; |
| } |
| } |
| } |
| lastT = span->t(); |
| } |
| next: |
| if (span->final()) { |
| break; |
| } |
| lastDone = span->upCast()->done(); |
| } while ((span = span->upCast()->next())); |
| return topPt; |
| } |
| |
| bool SkOpSegment::activeOp(SkOpSpanBase* start, SkOpSpanBase* end, int xorMiMask, int xorSuMask, |
| SkPathOp op) { |
| int sumMiWinding = this->updateWinding(end, start); |
| int sumSuWinding = this->updateOppWinding(end, start); |
| #if DEBUG_LIMIT_WIND_SUM |
| SkASSERT(abs(sumMiWinding) <= DEBUG_LIMIT_WIND_SUM); |
| SkASSERT(abs(sumSuWinding) <= DEBUG_LIMIT_WIND_SUM); |
| #endif |
| if (this->operand()) { |
| SkTSwap<int>(sumMiWinding, sumSuWinding); |
| } |
| return this->activeOp(xorMiMask, xorSuMask, start, end, op, &sumMiWinding, &sumSuWinding); |
| } |
| |
| bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, SkOpSpanBase* end, |
| SkPathOp op, int* sumMiWinding, int* sumSuWinding) { |
| int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; |
| this->setUpWindings(start, end, sumMiWinding, sumSuWinding, |
| &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); |
| bool miFrom; |
| bool miTo; |
| bool suFrom; |
| bool suTo; |
| if (operand()) { |
| miFrom = (oppMaxWinding & xorMiMask) != 0; |
| miTo = (oppSumWinding & xorMiMask) != 0; |
| suFrom = (maxWinding & xorSuMask) != 0; |
| suTo = (sumWinding & xorSuMask) != 0; |
| } else { |
| miFrom = (maxWinding & xorMiMask) != 0; |
| miTo = (sumWinding & xorMiMask) != 0; |
| suFrom = (oppMaxWinding & xorSuMask) != 0; |
| suTo = (oppSumWinding & xorSuMask) != 0; |
| } |
| bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo]; |
| #if DEBUG_ACTIVE_OP |
| SkDebugf("%s id=%d t=%1.9g tEnd=%1.9g op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", |
| __FUNCTION__, debugID(), start->t(), end->t(), |
| SkPathOpsDebug::kPathOpStr[op], miFrom, miTo, suFrom, suTo, result); |
| #endif |
| return result; |
| } |
| |
| bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end) { |
| int sumWinding = updateWinding(end, start); |
| return activeWinding(start, end, &sumWinding); |
| } |
| |
| bool SkOpSegment::activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding) { |
| int maxWinding; |
| setUpWinding(start, end, &maxWinding, sumWinding); |
| bool from = maxWinding != 0; |
| bool to = *sumWinding != 0; |
| bool result = gUnaryActiveEdge[from][to]; |
| return result; |
| } |
| |
| void SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, |
| SkPathWriter* path, bool active) const { |
| SkPoint edge[4]; |
| const SkPoint* ePtr; |
| if ((start == &fHead && end == &fTail) || (start == &fTail && end == &fHead)) { |
| ePtr = fPts; |
| } else { |
| // OPTIMIZE? if not active, skip remainder and return xyAtT(end) |
| subDivide(start, end, edge); |
| ePtr = edge; |
| } |
| if (active) { |
| bool reverse = ePtr == fPts && start != &fHead; |
| if (reverse) { |
| path->deferredMoveLine(ePtr[SkPathOpsVerbToPoints(fVerb)]); |
| switch (fVerb) { |
| case SkPath::kLine_Verb: |
| path->deferredLine(ePtr[0]); |
| break; |
| case SkPath::kQuad_Verb: |
| path->quadTo(ePtr[1], ePtr[0]); |
| break; |
| case SkPath::kCubic_Verb: |
| path->cubicTo(ePtr[2], ePtr[1], ePtr[0]); |
| break; |
| default: |
| SkASSERT(0); |
| } |
| } else { |
| path->deferredMoveLine(ePtr[0]); |
| switch (fVerb) { |
| case SkPath::kLine_Verb: |
| path->deferredLine(ePtr[1]); |
| break; |
| case SkPath::kQuad_Verb: |
| path->quadTo(ePtr[1], ePtr[2]); |
| break; |
| case SkPath::kCubic_Verb: |
| path->cubicTo(ePtr[1], ePtr[2], ePtr[3]); |
| break; |
| default: |
| SkASSERT(0); |
| } |
| } |
| } |
| } |
| |
| SkOpPtT* SkOpSegment::addMissing(double t, SkOpSegment* opp, SkChunkAlloc* allocator) { |
| SkOpSpanBase* existing = NULL; |
| SkOpSpanBase* test = &fHead; |
| double testT; |
| do { |
| if ((testT = test->ptT()->fT) >= t) { |
| if (testT == t) { |
| existing = test; |
| } |
| break; |
| } |
| } while ((test = test->upCast()->next())); |
| SkOpPtT* result; |
| if (existing && existing->contains(opp)) { |
| result = existing->ptT(); |
| } else { |
| result = this->addT(t, SkOpSegment::kNoAlias, allocator); |
| } |
| SkASSERT(result); |
| return result; |
| } |
| |
| SkOpAngle* SkOpSegment::addSingletonAngleDown(SkOpSegment** otherPtr, SkOpAngle** anglePtr, |
| SkChunkAlloc* allocator) { |
| SkOpSpan* startSpan = fTail.prev(); |
| SkASSERT(startSpan); |
| SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); |
| *anglePtr = angle; |
| angle->set(&fTail, startSpan); |
| fTail.setFromAngle(angle); |
| SkOpSegment* other = NULL; // these initializations silence a release build warning |
| SkOpSpan* oStartSpan = NULL; |
| SkOpSpanBase* oEndSpan = NULL; |
| SkOpPtT* ptT = fTail.ptT(), * startPtT = ptT; |
| while ((ptT = ptT->next()) != startPtT) { |
| other = ptT->segment(); |
| oStartSpan = ptT->span()->upCastable(); |
| if (oStartSpan && oStartSpan->windValue()) { |
| oEndSpan = oStartSpan->next(); |
| break; |
| } |
| oEndSpan = ptT->span(); |
| oStartSpan = oEndSpan->prev(); |
| if (oStartSpan && oStartSpan->windValue()) { |
| break; |
| } |
| } |
| SkOpAngle* oAngle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); |
| oAngle->set(oStartSpan, oEndSpan); |
| oStartSpan->setToAngle(oAngle); |
| *otherPtr = other; |
| return oAngle; |
| } |
| |
| SkOpAngle* SkOpSegment::addSingletonAngles(int step, SkChunkAlloc* allocator) { |
| SkOpSegment* other; |
| SkOpAngle* angle, * otherAngle; |
| if (step > 0) { |
| otherAngle = addSingletonAngleUp(&other, &angle, allocator); |
| } else { |
| otherAngle = addSingletonAngleDown(&other, &angle, allocator); |
| } |
| angle->insert(otherAngle); |
| return angle; |
| } |
| |
| SkOpAngle* SkOpSegment::addSingletonAngleUp(SkOpSegment** otherPtr, SkOpAngle** anglePtr, |
| SkChunkAlloc* allocator) { |
| SkOpSpanBase* endSpan = fHead.next(); |
| SkASSERT(endSpan); |
| SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); |
| *anglePtr = angle; |
| angle->set(&fHead, endSpan); |
| fHead.setToAngle(angle); |
| SkOpSegment* other = NULL; // these initializations silence a release build warning |
| SkOpSpan* oStartSpan = NULL; |
| SkOpSpanBase* oEndSpan = NULL; |
| SkOpPtT* ptT = fHead.ptT(), * startPtT = ptT; |
| while ((ptT = ptT->next()) != startPtT) { |
| other = ptT->segment(); |
| oEndSpan = ptT->span(); |
| oStartSpan = oEndSpan->prev(); |
| if (oStartSpan && oStartSpan->windValue()) { |
| break; |
| } |
| oStartSpan = oEndSpan->upCastable(); |
| if (oStartSpan && oStartSpan->windValue()) { |
| oEndSpan = oStartSpan->next(); |
| break; |
| } |
| } |
| SkOpAngle* oAngle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); |
| oAngle->set(oEndSpan, oStartSpan); |
| oEndSpan->setFromAngle(oAngle); |
| *otherPtr = other; |
| return oAngle; |
| } |
| |
| SkOpPtT* SkOpSegment::addT(double t, AllowAlias allowAlias, SkChunkAlloc* allocator) { |
| debugValidate(); |
| SkPoint pt = this->ptAtT(t); |
| SkOpSpanBase* span = &fHead; |
| do { |
| SkOpPtT* result = span->ptT(); |
| if (t == result->fT) { |
| return result; |
| } |
| if (this->match(result, this, t, pt)) { |
| // see if any existing alias matches segment, pt, and t |
| SkOpPtT* loop = result->next(); |
| bool duplicatePt = false; |
| while (loop != result) { |
| bool ptMatch = loop->fPt == pt; |
| if (loop->segment() == this && loop->fT == t && ptMatch) { |
| return result; |
| } |
| duplicatePt |= ptMatch; |
| loop = loop->next(); |
| } |
| if (kNoAlias == allowAlias) { |
| return result; |
| } |
| SkOpPtT* alias = SkOpTAllocator<SkOpPtT>::Allocate(allocator); |
| alias->init(result->span(), t, pt, duplicatePt); |
| result->insert(alias); |
| result->span()->unaligned(); |
| this->debugValidate(); |
| #if DEBUG_ADD_T |
| SkDebugf("%s alias t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, |
| alias->segment()->debugID(), alias->span()->debugID()); |
| #endif |
| return alias; |
| } |
| if (t < result->fT) { |
| SkOpSpan* prev = result->span()->prev(); |
| SkOpSpan* span = insert(prev, allocator); |
| span->init(this, prev, t, pt); |
| this->debugValidate(); |
| #if DEBUG_ADD_T |
| SkDebugf("%s insert t=%1.9g segID=%d spanID=%d\n", __FUNCTION__, t, |
| span->segment()->debugID(), span->debugID()); |
| #endif |
| return span->ptT(); |
| } |
| SkASSERT(span != &fTail); |
| } while ((span = span->upCast()->next())); |
| SkASSERT(0); |
| return NULL; |
| } |
| |
| // choose a solitary t and pt value; remove aliases; align the opposite ends |
| void SkOpSegment::align() { |
| debugValidate(); |
| SkOpSpanBase* span = &fHead; |
| if (!span->aligned()) { |
| span->alignEnd(0, fPts[0]); |
| } |
| while ((span = span->upCast()->next())) { |
| if (span == &fTail) { |
| break; |
| } |
| span->align(); |
| } |
| if (!span->aligned()) { |
| span->alignEnd(1, fPts[SkPathOpsVerbToPoints(fVerb)]); |
| } |
| debugValidate(); |
| } |
| |
| bool SkOpSegment::BetweenTs(const SkOpSpanBase* lesser, double testT, |
| const SkOpSpanBase* greater) { |
| if (lesser->t() > greater->t()) { |
| SkTSwap<const SkOpSpanBase*>(lesser, greater); |
| } |
| return approximately_between(lesser->t(), testT, greater->t()); |
| } |
| |
| void SkOpSegment::calcAngles(SkChunkAlloc* allocator) { |
| bool activePrior = !fHead.isCanceled(); |
| if (activePrior && !fHead.simple()) { |
| addStartSpan(allocator); |
| } |
| SkOpSpan* prior = &fHead; |
| SkOpSpanBase* spanBase = fHead.next(); |
| while (spanBase != &fTail) { |
| if (activePrior) { |
| SkOpAngle* priorAngle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); |
| priorAngle->set(spanBase, prior); |
| spanBase->setFromAngle(priorAngle); |
| } |
| SkOpSpan* span = spanBase->upCast(); |
| bool active = !span->isCanceled(); |
| SkOpSpanBase* next = span->next(); |
| if (active) { |
| SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(allocator); |
| angle->set(span, next); |
| span->setToAngle(angle); |
| } |
| activePrior = active; |
| prior = span; |
| spanBase = next; |
| } |
| if (activePrior && !fTail.simple()) { |
| addEndSpan(allocator); |
| } |
| } |
| |
| void SkOpSegment::checkAngleCoin(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) { |
| SkOpSpanBase* base = &fHead; |
| SkOpSpan* span; |
| do { |
| SkOpAngle* angle = base->fromAngle(); |
| if (angle && angle->fCheckCoincidence) { |
| angle->checkNearCoincidence(); |
| } |
| if (base->final()) { |
| break; |
| } |
| span = base->upCast(); |
| angle = span->toAngle(); |
| if (angle && angle->fCheckCoincidence) { |
| angle->checkNearCoincidence(); |
| } |
| } while ((base = span->next())); |
| } |
| |
| // from http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order |
| bool SkOpSegment::clockwise(const SkOpSpanBase* start, const SkOpSpanBase* end, bool* swap) const { |
| SkASSERT(fVerb != SkPath::kLine_Verb); |
| SkPoint edge[4]; |
| if (fVerb == SkPath::kCubic_Verb) { |
| double startT = start->t(); |
| double endT = end->t(); |
| bool flip = startT > endT; |
| SkDCubic cubic; |
| cubic.set(fPts); |
| double inflectionTs[2]; |
| int inflections = cubic.findInflections(inflectionTs); |
| for (int index = 0; index < inflections; ++index) { |
| double inflectionT = inflectionTs[index]; |
| if (between(startT, inflectionT, endT)) { |
| if (flip) { |
| if (inflectionT != endT) { |
| startT = inflectionT; |
| } |
| } else { |
| if (inflectionT != startT) { |
| endT = inflectionT; |
| } |
| } |
| } |
| } |
| SkDCubic part = cubic.subDivide(startT, endT); |
| for (int index = 0; index < 4; ++index) { |
| edge[index] = part[index].asSkPoint(); |
| } |
| } else { |
| subDivide(start, end, edge); |
| } |
| bool sumSet = false; |
| int points = SkPathOpsVerbToPoints(fVerb); |
| double sum = (edge[0].fX - edge[points].fX) * (edge[0].fY + edge[points].fY); |
| if (!sumSet) { |
| for (int idx = 0; idx < points; ++idx){ |
| sum += (edge[idx + 1].fX - edge[idx].fX) * (edge[idx + 1].fY + edge[idx].fY); |
| } |
| } |
| if (fVerb == SkPath::kCubic_Verb) { |
| SkDCubic cubic; |
| cubic.set(edge); |
| *swap = sum > 0 && !cubic.monotonicInY(); |
| } else { |
| SkDQuad quad; |
| quad.set(edge); |
| *swap = sum > 0 && !quad.monotonicInY(); |
| } |
| return sum <= 0; |
| } |
| |
| 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; |
| SkOpSpanBase* 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, |
| nextAngle); |
| } else { |
| nextSegment->setUpWindings(nextAngle->start(), nextAngle->end(), &sumMiWinding, |
| &maxWinding, &sumWinding); |
| last = nextSegment->markAngle(maxWinding, sumWinding, 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; |
| SkOpSpanBase* 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, |
| nextAngle); |
| } else { |
| nextSegment->setUpWindings(nextAngle->end(), nextAngle->start(), &sumMiWinding, |
| &maxWinding, &sumWinding); |
| last = nextSegment->markAngle(maxWinding, sumWinding, nextAngle); |
| } |
| nextAngle->setLastMarked(last); |
| } |
| |
| // at this point, the span is already ordered, or unorderable |
| int SkOpSegment::computeSum(SkOpSpanBase* start, SkOpSpanBase* end, |
| SkOpAngle::IncludeType includeType) { |
| SkASSERT(includeType != SkOpAngle::kUnaryXor); |
| SkOpAngle* firstAngle = this->spanToAngle(end, start); |
| if (NULL == firstAngle || NULL == firstAngle->next()) { |
| return SK_NaN32; |
| } |
| // 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 both are next to orderable angles, |
| // and one has winding computed, transfer to the other |
| SkOpAngle* baseAngle = NULL; |
| bool tryReverse = false; |
| // look for counterclockwise transfers |
| SkOpAngle* angle = firstAngle->previous(); |
| SkOpAngle* next = angle->next(); |
| firstAngle = next; |
| do { |
| SkOpAngle* prior = angle; |
| angle = next; |
| next = angle->next(); |
| SkASSERT(prior->next() == angle); |
| SkASSERT(angle->next() == next); |
| if (prior->unorderable() || angle->unorderable() || next->unorderable()) { |
| baseAngle = NULL; |
| continue; |
| } |
| int testWinding = angle->starter()->windSum(); |
| if (SK_MinS32 != testWinding) { |
| baseAngle = angle; |
| tryReverse = true; |
| continue; |
| } |
| if (baseAngle) { |
| ComputeOneSum(baseAngle, angle, includeType); |
| baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : NULL; |
| } |
| } while (next != firstAngle); |
| if (baseAngle && SK_MinS32 == firstAngle->starter()->windSum()) { |
| firstAngle = baseAngle; |
| tryReverse = true; |
| } |
| if (tryReverse) { |
| baseAngle = NULL; |
| SkOpAngle* prior = firstAngle; |
| do { |
| angle = prior; |
| prior = angle->previous(); |
| SkASSERT(prior->next() == angle); |
| next = angle->next(); |
| if (prior->unorderable() || angle->unorderable() || next->unorderable()) { |
| baseAngle = NULL; |
| continue; |
| } |
| int testWinding = angle->starter()->windSum(); |
| if (SK_MinS32 != testWinding) { |
| baseAngle = angle; |
| continue; |
| } |
| if (baseAngle) { |
| ComputeOneSumReverse(baseAngle, angle, includeType); |
| baseAngle = SK_MinS32 != angle->starter()->windSum() ? angle : NULL; |
| } |
| } while (prior != firstAngle); |
| } |
| return start->starter(end)->windSum(); |
| } |
| |
| SkOpSpan* SkOpSegment::crossedSpanY(const SkPoint& basePt, double mid, bool opp, bool current, |
| SkScalar* bestY, double* hitT, bool* hitSomething, bool* vertical) { |
| SkScalar bottom = fBounds.fBottom; |
| *vertical = false; |
| if (bottom <= *bestY) { |
| return NULL; |
| } |
| SkScalar top = fBounds.fTop; |
| if (top >= basePt.fY) { |
| return NULL; |
| } |
| if (fBounds.fLeft > basePt.fX) { |
| return NULL; |
| } |
| if (fBounds.fRight < basePt.fX) { |
| return NULL; |
| } |
| if (fBounds.fLeft == fBounds.fRight) { |
| // if vertical, and directly above test point, wait for another one |
| *vertical = AlmostEqualUlps(basePt.fX, fBounds.fLeft); |
| return NULL; |
| } |
| // intersect ray starting at basePt with edge |
| SkIntersections intersections; |
| // OPTIMIZE: use specialty function that intersects ray with curve, |
| // returning t values only for curve (we don't care about t on ray) |
| intersections.allowNear(false); |
| int pts = (intersections.*CurveVertical[SkPathOpsVerbToPoints(fVerb)]) |
| (fPts, top, bottom, basePt.fX, false); |
| if (pts == 0 || (current && pts == 1)) { |
| return NULL; |
| } |
| if (current) { |
| SkASSERT(pts > 1); |
| int closestIdx = 0; |
| double closest = fabs(intersections[0][0] - mid); |
| for (int idx = 1; idx < pts; ++idx) { |
| double test = fabs(intersections[0][idx] - mid); |
| if (closest > test) { |
| closestIdx = idx; |
| closest = test; |
| } |
| } |
| intersections.quickRemoveOne(closestIdx, --pts); |
| } |
| double bestT = -1; |
| for (int index = 0; index < pts; ++index) { |
| double foundT = intersections[0][index]; |
| if (approximately_less_than_zero(foundT) |
| || approximately_greater_than_one(foundT)) { |
| continue; |
| } |
| SkScalar testY = (*CurvePointAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, foundT).fY; |
| if (approximately_negative(testY - *bestY) |
| || approximately_negative(basePt.fY - testY)) { |
| continue; |
| } |
| if (pts > 1 && fVerb == SkPath::kLine_Verb) { |
| *vertical = true; |
| return NULL; // if the intersection is edge on, wait for another one |
| } |
| if (fVerb > SkPath::kLine_Verb) { |
| SkScalar dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, foundT).fX; |
| if (approximately_zero(dx)) { |
| *vertical = true; |
| return NULL; // hit vertical, wait for another one |
| } |
| } |
| *bestY = testY; |
| bestT = foundT; |
| } |
| if (bestT < 0) { |
| return NULL; |
| } |
| SkASSERT(bestT >= 0); |
| SkASSERT(bestT < 1); |
| SkOpSpanBase* testTSpanBase = &this->fHead; |
| SkOpSpanBase* nextTSpan; |
| double endT = 0; |
| do { |
| nextTSpan = testTSpanBase->upCast()->next(); |
| endT = nextTSpan->t(); |
| if (endT >= bestT) { |
| break; |
| } |
| testTSpanBase = nextTSpan; |
| } while (testTSpanBase); |
| SkOpSpan* bestTSpan = NULL; |
| SkOpSpan* testTSpan = testTSpanBase->upCast(); |
| if (!testTSpan->isCanceled()) { |
| *hitT = bestT; |
| bestTSpan = testTSpan; |
| *hitSomething = true; |
| } |
| return bestTSpan; |
| } |
| |
| void SkOpSegment::detach(const SkOpSpan* span) { |
| if (span->done()) { |
| --this->fDoneCount; |
| } |
| --this->fCount; |
| } |
| |
| double SkOpSegment::distSq(double t, SkOpAngle* oppAngle) { |
| SkDPoint testPt = this->dPtAtT(t); |
| SkDLine testPerp = {{ testPt, testPt }}; |
| SkDVector slope = this->dSlopeAtT(t); |
| testPerp[1].fX += slope.fY; |
| testPerp[1].fY -= slope.fX; |
| SkIntersections i; |
| SkOpSegment* oppSegment = oppAngle->segment(); |
| int oppPtCount = SkPathOpsVerbToPoints(oppSegment->verb()); |
| (*CurveIntersectRay[oppPtCount])(oppSegment->pts(), testPerp, &i); |
| double closestDistSq = SK_ScalarInfinity; |
| for (int index = 0; index < i.used(); ++index) { |
| if (!between(oppAngle->start()->t(), i[0][index], oppAngle->end()->t())) { |
| continue; |
| } |
| double testDistSq = testPt.distanceSquared(i.pt(index)); |
| if (closestDistSq > testDistSq) { |
| closestDistSq = testDistSq; |
| } |
| } |
| return closestDistSq; |
| } |
| |
| /* |
| The M and S variable name parts stand for the operators. |
| Mi stands for Minuend (see wiki subtraction, analogous to difference) |
| Su stands for Subtrahend |
| The Opp variable name part designates that the value is for the Opposite operator. |
| Opposite values result from combining coincident spans. |
| */ |
| SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** nextStart, |
| SkOpSpanBase** nextEnd, bool* unsortable, SkPathOp op, int xorMiMask, int xorSuMask) { |
| SkOpSpanBase* start = *nextStart; |
| SkOpSpanBase* end = *nextEnd; |
| SkASSERT(start != end); |
| int step = start->step(end); |
| SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart |
| if (other) { |
| // mark the smaller of startIndex, endIndex done, and all adjacent |
| // spans with the same T value (but not 'other' spans) |
| #if DEBUG_WINDING |
| SkDebugf("%s simple\n", __FUNCTION__); |
| #endif |
| SkOpSpan* startSpan = start->starter(end); |
| if (startSpan->done()) { |
| return NULL; |
| } |
| markDone(startSpan); |
| *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); |
| return other; |
| } |
| SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); |
| SkASSERT(endNear == end); // is this ever not end? |
| SkASSERT(endNear); |
| SkASSERT(start != endNear); |
| SkASSERT((start->t() < endNear->t()) ^ (step < 0)); |
| // more than one viable candidate -- measure angles to find best |
| int calcWinding = computeSum(start, endNear, SkOpAngle::kBinaryOpp); |
| bool sortable = calcWinding != SK_NaN32; |
| if (!sortable) { |
| *unsortable = true; |
| markDone(start->starter(end)); |
| return NULL; |
| } |
| SkOpAngle* angle = this->spanToAngle(end, start); |
| if (angle->unorderable()) { |
| *unsortable = true; |
| markDone(start->starter(end)); |
| return NULL; |
| } |
| #if DEBUG_SORT |
| SkDebugf("%s\n", __FUNCTION__); |
| angle->debugLoop(); |
| #endif |
| int sumMiWinding = updateWinding(end, start); |
| if (sumMiWinding == SK_MinS32) { |
| *unsortable = true; |
| markDone(start->starter(end)); |
| return NULL; |
| } |
| int sumSuWinding = updateOppWinding(end, start); |
| if (operand()) { |
| SkTSwap<int>(sumMiWinding, sumSuWinding); |
| } |
| SkOpAngle* nextAngle = angle->next(); |
| const SkOpAngle* foundAngle = NULL; |
| bool foundDone = false; |
| // iterate through the angle, and compute everyone's winding |
| SkOpSegment* nextSegment; |
| int activeCount = 0; |
| do { |
| nextSegment = nextAngle->segment(); |
| bool activeAngle = nextSegment->activeOp(xorMiMask, xorSuMask, nextAngle->start(), |
| nextAngle->end(), op, &sumMiWinding, &sumSuWinding); |
| if (activeAngle) { |
| ++activeCount; |
| if (!foundAngle || (foundDone && activeCount & 1)) { |
| foundAngle = nextAngle; |
| foundDone = nextSegment->done(nextAngle); |
| } |
| } |
| if (nextSegment->done()) { |
| continue; |
| } |
| if (!activeAngle) { |
| (void) nextSegment->markAndChaseDone(nextAngle->start(), nextAngle->end()); |
| } |
| SkOpSpanBase* last = nextAngle->lastMarked(); |
| if (last) { |
| SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last)); |
| *chase->append() = last; |
| #if DEBUG_WINDING |
| SkDebugf("%s chase.append segment=%d span=%d", __FUNCTION__, |
| last->segment()->debugID(), last->debugID()); |
| if (!last->final()) { |
| SkDebugf(" windSum=%d", last->upCast()->windSum()); |
| } |
| SkDebugf("\n"); |
| #endif |
| } |
| } while ((nextAngle = nextAngle->next()) != angle); |
| start->segment()->markDone(start->starter(end)); |
| if (!foundAngle) { |
| return NULL; |
| } |
| *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); |
| #endif |
| return nextSegment; |
| } |
| |
| SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpanBase*>* chase, |
| SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable) { |
| SkOpSpanBase* start = *nextStart; |
| SkOpSpanBase* end = *nextEnd; |
| SkASSERT(start != end); |
| int step = start->step(end); |
| SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart |
| if (other) { |
| // mark the smaller of startIndex, endIndex done, and all adjacent |
| // spans with the same T value (but not 'other' spans) |
| #if DEBUG_WINDING |
| SkDebugf("%s simple\n", __FUNCTION__); |
| #endif |
| SkOpSpan* startSpan = start->starter(end); |
| if (startSpan->done()) { |
| return NULL; |
| } |
| markDone(startSpan); |
| *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); |
| return other; |
| } |
| SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); |
| SkASSERT(endNear == end); // is this ever not end? |
| SkASSERT(endNear); |
| SkASSERT(start != endNear); |
| SkASSERT((start->t() < endNear->t()) ^ (step < 0)); |
| // more than one viable candidate -- measure angles to find best |
| int calcWinding = computeSum(start, endNear, SkOpAngle::kUnaryWinding); |
| bool sortable = calcWinding != SK_NaN32; |
| if (!sortable) { |
| *unsortable = true; |
| markDone(start->starter(end)); |
| return NULL; |
| } |
| SkOpAngle* angle = this->spanToAngle(end, start); |
| if (angle->unorderable()) { |
| *unsortable = true; |
| markDone(start->starter(end)); |
| return NULL; |
| } |
| #if DEBUG_SORT |
| SkDebugf("%s\n", __FUNCTION__); |
| angle->debugLoop(); |
| #endif |
| int sumWinding = updateWinding(end, start); |
| SkOpAngle* nextAngle = angle->next(); |
| const SkOpAngle* foundAngle = NULL; |
| bool foundDone = false; |
| // iterate through the angle, and compute everyone's winding |
| SkOpSegment* nextSegment; |
| int activeCount = 0; |
| do { |
| nextSegment = nextAngle->segment(); |
| bool activeAngle = nextSegment->activeWinding(nextAngle->start(), nextAngle->end(), |
| &sumWinding); |
| if (activeAngle) { |
| ++activeCount; |
| if (!foundAngle || (foundDone && activeCount & 1)) { |
| foundAngle = nextAngle; |
| foundDone = nextSegment->done(nextAngle); |
| } |
| } |
| if (nextSegment->done()) { |
| continue; |
| } |
| if (!activeAngle) { |
| (void) nextSegment->markAndChaseDone(nextAngle->start(), nextAngle->end()); |
| } |
| SkOpSpanBase* last = nextAngle->lastMarked(); |
| if (last) { |
| SkASSERT(!SkPathOpsDebug::ChaseContains(*chase, last)); |
| *chase->append() = last; |
| #if DEBUG_WINDING |
| SkDebugf("%s chase.append segment=%d span=%d", __FUNCTION__, |
| last->segment()->debugID(), last->debugID()); |
| if (!last->final()) { |
| SkDebugf(" windSum=%d", last->upCast()->windSum()); |
| } |
| SkDebugf("\n"); |
| #endif |
| } |
| } while ((nextAngle = nextAngle->next()) != angle); |
| start->segment()->markDone(start->starter(end)); |
| if (!foundAngle) { |
| return NULL; |
| } |
| *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); |
| #endif |
| return nextSegment; |
| } |
| |
| SkOpSegment* SkOpSegment::findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, |
| bool* unsortable) { |
| SkOpSpanBase* start = *nextStart; |
| SkOpSpanBase* end = *nextEnd; |
| SkASSERT(start != end); |
| int step = start->step(end); |
| SkOpSegment* other = this->isSimple(nextStart, &step); // advances nextStart |
| if (other) { |
| // mark the smaller of startIndex, endIndex done, and all adjacent |
| // spans with the same T value (but not 'other' spans) |
| #if DEBUG_WINDING |
| SkDebugf("%s simple\n", __FUNCTION__); |
| #endif |
| SkOpSpan* startSpan = start->starter(end); |
| if (startSpan->done()) { |
| return NULL; |
| } |
| markDone(startSpan); |
| *nextEnd = step > 0 ? (*nextStart)->upCast()->next() : (*nextStart)->prev(); |
| return other; |
| } |
| SkDEBUGCODE(SkOpSpanBase* endNear = step > 0 ? (*nextStart)->upCast()->next() \ |
| : (*nextStart)->prev()); |
| SkASSERT(endNear == end); // is this ever not end? |
| SkASSERT(endNear); |
| SkASSERT(start != endNear); |
| SkASSERT((start->t() < endNear->t()) ^ (step < 0)); |
| SkOpAngle* angle = this->spanToAngle(end, start); |
| if (angle->unorderable()) { |
| *unsortable = true; |
| markDone(start->starter(end)); |
| return NULL; |
| } |
| #if DEBUG_SORT |
| SkDebugf("%s\n", __FUNCTION__); |
| angle->debugLoop(); |
| #endif |
| SkOpAngle* nextAngle = angle->next(); |
| const SkOpAngle* foundAngle = NULL; |
| bool foundDone = false; |
| // iterate through the angle, and compute everyone's winding |
| SkOpSegment* nextSegment; |
| int activeCount = 0; |
| do { |
| nextSegment = nextAngle->segment(); |
| ++activeCount; |
| if (!foundAngle || (foundDone && activeCount & 1)) { |
| foundAngle = nextAngle; |
| if (!(foundDone = nextSegment->done(nextAngle))) { |
| break; |
| } |
| } |
| nextAngle = nextAngle->next(); |
| } while (nextAngle != angle); |
| start->segment()->markDone(start->starter(end)); |
| if (!foundAngle) { |
| return NULL; |
| } |
| *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); |
| #endif |
| return nextSegment; |
| } |
| |
| SkOpSegment* SkOpSegment::findTop(bool firstPass, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, |
| bool* unsortable, SkChunkAlloc* allocator) { |
| // iterate through T intersections and return topmost |
| // topmost tangent from y-min to first pt is closer to horizontal |
| SkASSERT(!done()); |
| SkOpSpanBase* firstT = NULL; |
| (void) this->activeLeftTop(&firstT); |
| if (!firstT) { |
| *unsortable = !firstPass; |
| firstT = &fHead; |
| while (firstT->upCast()->done()) { |
| firstT = firstT->upCast()->next(); |
| } |
| *startPtr = firstT; |
| *endPtr = firstT->upCast()->next(); |
| return this; |
| } |
| // sort the edges to find the leftmost |
| int step = 1; |
| SkOpSpanBase* end; |
| if (firstT->final() || firstT->upCast()->done()) { |
| step = -1; |
| end = firstT->prev(); |
| SkASSERT(end); |
| } else { |
| end = firstT->upCast()->next(); |
| } |
| // if the topmost T is not on end, or is three-way or more, find left |
| // look for left-ness from tLeft to firstT (matching y of other) |
| SkASSERT(firstT != end); |
| SkOpAngle* markAngle = spanToAngle(firstT, end); |
| if (!markAngle) { |
| markAngle = addSingletonAngles(step, allocator); |
| } |
| markAngle->markStops(); |
| const SkOpAngle* baseAngle = markAngle->next() == markAngle && !isVertical() ? markAngle |
| : markAngle->findFirst(); |
| if (!baseAngle) { |
| return NULL; // nothing to do |
| } |
| SkScalar top = SK_ScalarMax; |
| const SkOpAngle* firstAngle = NULL; |
| const SkOpAngle* angle = baseAngle; |
| do { |
| if (!angle->unorderable()) { |
| const SkOpSegment* next = angle->segment(); |
| SkPathOpsBounds bounds; |
| next->subDivideBounds(angle->end(), angle->start(), &bounds); |
| bool nearSame = AlmostEqualUlps(top, bounds.top()); |
| bool lowerSector = !firstAngle || angle->sectorEnd() < firstAngle->sectorStart(); |
| bool lesserSector = top > bounds.fTop; |
| if (lesserSector && (!nearSame || lowerSector)) { |
| top = bounds.fTop; |
| firstAngle = angle; |
| } |
| } |
| angle = angle->next(); |
| } while (angle != baseAngle); |
| if (!firstAngle) { |
| return NULL; // if all are unorderable, give up |
| } |
| #if DEBUG_SORT |
| SkDebugf("%s\n", __FUNCTION__); |
| firstAngle->debugLoop(); |
| #endif |
| // skip edges that have already been processed |
| angle = firstAngle; |
| SkOpSegment* leftSegment = NULL; |
| bool looped = false; |
| do { |
| *unsortable = angle->unorderable(); |
| if (firstPass || !*unsortable) { |
| leftSegment = angle->segment(); |
| *startPtr = angle->end(); |
| *endPtr = angle->start(); |
| const SkOpSpan* firstSpan = (*startPtr)->starter(*endPtr); |
| if (!firstSpan->done()) { |
| break; |
| } |
| } |
| angle = angle->next(); |
| looped = true; |
| } while (angle != firstAngle); |
| if (angle == firstAngle && looped) { |
| return NULL; |
| } |
| if (leftSegment->verb() >= SkPath::kQuad_Verb) { |
| SkOpSpanBase* start = *startPtr; |
| SkOpSpanBase* end = *endPtr; |
| bool swap; |
| if (!leftSegment->clockwise(start, end, &swap)) { |
| #if DEBUG_SWAP_TOP |
| SkDebugf("%s swap=%d inflections=%d monotonic=%d\n", |
| __FUNCTION__, |
| swap, leftSegment->debugInflections(start, end), |
| leftSegment->monotonicInY(start, end)); |
| #endif |
| if (swap) { |
| // FIXME: I doubt it makes sense to (necessarily) swap if the edge was not the first |
| // sorted but merely the first not already processed (i.e., not done) |
| SkTSwap(*startPtr, *endPtr); |
| } |
| } |
| } |
| return leftSegment; |
| } |
| |
| SkOpGlobalState* SkOpSegment::globalState() const { |
| return contour()->globalState(); |
| } |
| |
| void SkOpSegment::init(SkPoint pts[], SkOpContour* contour, SkPath::Verb verb) { |
| fContour = contour; |
| fNext = NULL; |
| fPts = pts; |
| fVerb = verb; |
| fCount = 0; |
| fDoneCount = 0; |
| fVisited = false; |
| SkOpSpan* zeroSpan = &fHead; |
| zeroSpan->init(this, NULL, 0, fPts[0]); |
| SkOpSpanBase* oneSpan = &fTail; |
| zeroSpan->setNext(oneSpan); |
| oneSpan->initBase(this, zeroSpan, 1, fPts[SkPathOpsVerbToPoints(fVerb)]); |
| PATH_OPS_DEBUG_CODE(fID = globalState()->nextSegmentID()); |
| } |
| |
| void SkOpSegment::initWinding(SkOpSpanBase* start, SkOpSpanBase* end, |
| SkOpAngle::IncludeType angleIncludeType) { |
| int local = SkOpSegment::SpanSign(start, end); |
| SkDEBUGCODE(bool success); |
| if (angleIncludeType == SkOpAngle::kBinarySingle) { |
| int oppLocal = SkOpSegment::OppSign(start, end); |
| SkDEBUGCODE(success =) markAndChaseWinding(start, end, local, oppLocal, NULL); |
| // OPTIMIZATION: the reverse mark and chase could skip the first marking |
| SkDEBUGCODE(success |=) markAndChaseWinding(end, start, local, oppLocal, NULL); |
| } else { |
| SkDEBUGCODE(success =) markAndChaseWinding(start, end, local, NULL); |
| // OPTIMIZATION: the reverse mark and chase could skip the first marking |
| SkDEBUGCODE(success |=) markAndChaseWinding(end, start, local, NULL); |
| } |
| SkASSERT(success); |
| } |
| |
| /* |
| when we start with a vertical intersect, we try to use the dx to determine if the edge is to |
| the left or the right of vertical. This determines if we need to add the span's |
| sign or not. However, this isn't enough. |
| If the supplied sign (winding) is zero, then we didn't hit another vertical span, so dx is needed. |
| If there was a winding, then it may or may not need adjusting. If the span the winding was borrowed |
| from has the same x direction as this span, the winding should change. If the dx is opposite, then |
| the same winding is shared by both. |
| */ |
| bool SkOpSegment::initWinding(SkOpSpanBase* start, SkOpSpanBase* end, double tHit, |
| int winding, SkScalar hitDx, int oppWind, SkScalar hitOppDx) { |
| SkASSERT(this == start->segment()); |
| SkASSERT(hitDx || !winding); |
| SkScalar dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX; |
| // SkASSERT(dx); |
| int windVal = start->starter(end)->windValue(); |
| #if DEBUG_WINDING_AT_T |
| SkDebugf("%s id=%d oldWinding=%d hitDx=%c dx=%c windVal=%d", __FUNCTION__, debugID(), winding, |
| hitDx ? hitDx > 0 ? '+' : '-' : '0', dx > 0 ? '+' : '-', windVal); |
| #endif |
| int sideWind = winding + (dx < 0 ? windVal : -windVal); |
| if (abs(winding) < abs(sideWind)) { |
| winding = sideWind; |
| } |
| SkDEBUGCODE(int oppLocal = SkOpSegment::OppSign(start, end)); |
| SkASSERT(hitOppDx || !oppWind || !oppLocal); |
| int oppWindVal = start->starter(end)->oppValue(); |
| if (!oppWind) { |
| oppWind = dx < 0 ? oppWindVal : -oppWindVal; |
| } else if (hitOppDx * dx >= 0) { |
| int oppSideWind = oppWind + (dx < 0 ? oppWindVal : -oppWindVal); |
| if (abs(oppWind) < abs(oppSideWind)) { |
| oppWind = oppSideWind; |
| } |
| } |
| #if DEBUG_WINDING_AT_T |
| SkDebugf(" winding=%d oppWind=%d\n", winding, oppWind); |
| #endif |
| // if this fails to mark (because the edges are too small) inform caller to try again |
| bool success = markAndChaseWinding(start, end, winding, oppWind, NULL); |
| // OPTIMIZATION: the reverse mark and chase could skip the first marking |
| success |= markAndChaseWinding(end, start, winding, oppWind, NULL); |
| return success; |
| } |
| |
| bool SkOpSegment::isClose(double t, const SkOpSegment* opp) const { |
| SkDPoint cPt = this->dPtAtT(t); |
| int pts = SkPathOpsVerbToPoints(this->verb()); |
| SkDVector dxdy = (*CurveDSlopeAtT[pts])(this->pts(), t); |
| SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; |
| SkIntersections i; |
| int oppPts = SkPathOpsVerbToPoints(opp->verb()); |
| (*CurveIntersectRay[oppPts])(opp->pts(), perp, &i); |
| int used = i.used(); |
| for (int index = 0; index < used; ++index) { |
| if (cPt.roughlyEqual(i.pt(index))) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| bool SkOpSegment::isXor() const { |
| return fContour->isXor(); |
| } |
| |
| SkOpSpanBase* SkOpSegment::markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end) { |
| int step = start->step(end); |
| SkOpSpan* minSpan = start->starter(end); |
| markDone(minSpan); |
| SkOpSpanBase* last = NULL; |
| SkOpSegment* other = this; |
| while ((other = other->nextChase(&start, &step, &minSpan, &last))) { |
| if (other->done()) { |
| SkASSERT(!last); |
| break; |
| } |
| other->markDone(minSpan); |
| } |
| return last; |
| } |
| |
| bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, |
| SkOpSpanBase** lastPtr) { |
| SkOpSpan* spanStart = start->starter(end); |
| int step = start->step(end); |
| bool success = markWinding(spanStart, winding); |
| SkOpSpanBase* last = NULL; |
| SkOpSegment* other = this; |
| while ((other = other->nextChase(&start, &step, &spanStart, &last))) { |
| if (spanStart->windSum() != SK_MinS32) { |
| SkASSERT(spanStart->windSum() == winding); |
| SkASSERT(!last); |
| break; |
| } |
| (void) other->markWinding(spanStart, winding); |
| } |
| if (lastPtr) { |
| *lastPtr = last; |
| } |
| return success; |
| } |
| |
| bool SkOpSegment::markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, |
| int winding, int oppWinding, SkOpSpanBase** lastPtr) { |
| SkOpSpan* spanStart = start->starter(end); |
| int step = start->step(end); |
| bool success = markWinding(spanStart, winding, oppWinding); |
| SkOpSpanBase* last = NULL; |
| SkOpSegment* other = this; |
| while ((other = other->nextChase(&start, &step, &spanStart, &last))) { |
| if (spanStart->windSum() != SK_MinS32) { |
| if (this->operand() == other->operand()) { |
| SkASSERT(spanStart->windSum() == winding); |
| if (spanStart->oppSum() != oppWinding) { |
| this->globalState()->setWindingFailed(); |
| return false; |
| } |
| } else { |
| SkASSERT(spanStart->windSum() == oppWinding); |
| SkASSERT(spanStart->oppSum() == winding); |
| } |
| SkASSERT(!last); |
| break; |
| } |
| if (this->operand() == other->operand()) { |
| (void) other->markWinding(spanStart, winding, oppWinding); |
| } else { |
| (void) other->markWinding(spanStart, oppWinding, winding); |
| } |
| } |
| if (lastPtr) { |
| *lastPtr = last; |
| } |
| return success; |
| } |
| |
| SkOpSpanBase* SkOpSegment::markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle) { |
| SkASSERT(angle->segment() == this); |
| if (UseInnerWinding(maxWinding, sumWinding)) { |
| maxWinding = sumWinding; |
| } |
| SkOpSpanBase* last; |
| (void) markAndChaseWinding(angle->start(), angle->end(), maxWinding, &last); |
| #if DEBUG_WINDING |
| if (last) { |
| SkDebugf("%s last seg=%d span=%d", __FUNCTION__, |
| last->segment()->debugID(), last->debugID()); |
| if (!last->final()) { |
| SkDebugf(" windSum="); |
| SkPathOpsDebug::WindingPrintf(last->upCast()->windSum()); |
| } |
| SkDebugf("\n"); |
| } |
| #endif |
| return last; |
| } |
| |
| SkOpSpanBase* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding, |
| int oppSumWinding, const SkOpAngle* angle) { |
| SkASSERT(angle->segment() == this); |
| if (UseInnerWinding(maxWinding, sumWinding)) { |
| maxWinding = sumWinding; |
| } |
| if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) { |
| oppMaxWinding = oppSumWinding; |
| } |
| SkOpSpanBase* last = NULL; |
| // caller doesn't require that this marks anything |
| (void) markAndChaseWinding(angle->start(), angle->end(), maxWinding, oppMaxWinding, &last); |
| #if DEBUG_WINDING |
| if (last) { |
| SkDebugf("%s last segment=%d span=%d", __FUNCTION__, |
| last->segment()->debugID(), last->debugID()); |
| if (!last->final()) { |
| SkDebugf(" windSum="); |
| SkPathOpsDebug::WindingPrintf(last->upCast()->windSum()); |
| } |
| SkDebugf(" \n"); |
| } |
| #endif |
| return last; |
| } |
| |
| void SkOpSegment::markDone(SkOpSpan* span) { |
| SkASSERT(this == span->segment()); |
| if (span->done()) { |
| return; |
| } |
| #if DEBUG_MARK_DONE |
| debugShowNewWinding(__FUNCTION__, span, span->windSum(), span->oppSum()); |
| #endif |
| span->setDone(true); |
| ++fDoneCount; |
| debugValidate(); |
| } |
| |
| bool SkOpSegment::markWinding(SkOpSpan* span, int winding) { |
| SkASSERT(this == span->segment()); |
| SkASSERT(winding); |
| if (span->done()) { |
| return false; |
| } |
| #if DEBUG_MARK_DONE |
| debugShowNewWinding(__FUNCTION__, span, winding); |
| #endif |
| span->setWindSum(winding); |
| debugValidate(); |
| return true; |
| } |
| |
| bool SkOpSegment::markWinding(SkOpSpan* span, int winding, int oppWinding) { |
| SkASSERT(this == span->segment()); |
| SkASSERT(winding || oppWinding); |
| if (span->done()) { |
| return false; |
| } |
| #if DEBUG_MARK_DONE |
| debugShowNewWinding(__FUNCTION__, span, winding, oppWinding); |
| #endif |
| span->setWindSum(winding); |
| span->setOppSum(oppWinding); |
| debugValidate(); |
| return true; |
| } |
| |
| bool SkOpSegment::match(const SkOpPtT* base, const SkOpSegment* testParent, double testT, |
| const SkPoint& testPt) const { |
| const SkOpSegment* baseParent = base->segment(); |
| if (this == baseParent && this == testParent && precisely_equal(base->fT, testT)) { |
| return true; |
| } |
| if (!SkDPoint::ApproximatelyEqual(testPt, base->fPt)) { |
| return false; |
| } |
| return !ptsDisjoint(base->fT, base->fPt, testT, testPt); |
| } |
| |
| static SkOpSegment* set_last(SkOpSpanBase** last, SkOpSpanBase* endSpan) { |
| if (last) { |
| *last = endSpan; |
| } |
| return NULL; |
| } |
| |
| bool SkOpSegment::monotonicInY(const SkOpSpanBase* start, const SkOpSpanBase* end) const { |
| SkASSERT(fVerb != SkPath::kLine_Verb); |
| if (fVerb == SkPath::kQuad_Verb) { |
| SkDQuad dst = SkDQuad::SubDivide(fPts, start->t(), end->t()); |
| return dst.monotonicInY(); |
| } |
| SkASSERT(fVerb == SkPath::kCubic_Verb); |
| SkDCubic dst = SkDCubic::SubDivide(fPts, start->t(), end->t()); |
| return dst.monotonicInY(); |
| } |
| |
| bool SkOpSegment::NextCandidate(SkOpSpanBase* span, SkOpSpanBase** start, |
| SkOpSpanBase** end) { |
| while (span->final() || span->upCast()->done()) { |
| if (span->final()) { |
| return false; |
| } |
| span = span->upCast()->next(); |
| } |
| *start = span; |
| *end = span->upCast()->next(); |
| return true; |
| } |
| |
| SkOpSegment* SkOpSegment::nextChase(SkOpSpanBase** startPtr, int* stepPtr, SkOpSpan** minPtr, |
| SkOpSpanBase** last) const { |
| SkOpSpanBase* origStart = *startPtr; |
| int step = *stepPtr; |
| SkOpSpanBase* endSpan = step > 0 ? origStart->upCast()->next() : origStart->prev(); |
| SkASSERT(endSpan); |
| SkOpAngle* angle = step > 0 ? endSpan->fromAngle() : endSpan->upCast()->toAngle(); |
| SkOpSpanBase* foundSpan; |
| SkOpSpanBase* otherEnd; |
| SkOpSegment* other; |
| if (angle == NULL) { |
| if (endSpan->t() != 0 && endSpan->t() != 1) { |
| return NULL; |
| } |
| SkOpPtT* otherPtT = endSpan->ptT()->next(); |
| other = otherPtT->segment(); |
| foundSpan = otherPtT->span(); |
| otherEnd = step > 0 ? foundSpan->upCast()->next() : foundSpan->prev(); |
| } else { |
| int loopCount = angle->loopCount(); |
| if (loopCount > 2) { |
| return set_last(last, endSpan); |
| } |
| const SkOpAngle* next = angle->next(); |
| if (NULL == next) { |
| return NULL; |
| } |
| #if DEBUG_WINDING |
| if (angle->sign() != next->sign() && !angle->segment()->contour()->isXor() |
| && !next->segment()->contour()->isXor()) { |
| SkDebugf("%s mismatched signs\n", __FUNCTION__); |
| } |
| #endif |
| other = next->segment(); |
| foundSpan = endSpan = next->start(); |
| otherEnd = next->end(); |
| } |
| int foundStep = foundSpan->step(otherEnd); |
| if (*stepPtr != foundStep) { |
| return set_last(last, endSpan); |
| } |
| SkASSERT(*startPtr); |
| if (!otherEnd) { |
| return NULL; |
| } |
| // SkASSERT(otherEnd >= 0); |
| SkOpSpan* origMin = step < 0 ? origStart->prev() : origStart->upCast(); |
| SkOpSpan* foundMin = foundSpan->starter(otherEnd); |
| if (foundMin->windValue() != origMin->windValue() |
| || foundMin->oppValue() != origMin->oppValue()) { |
| return set_last(last, endSpan); |
| } |
| *startPtr = foundSpan; |
| *stepPtr = foundStep; |
| if (minPtr) { |
| *minPtr = foundMin; |
| } |
| return other; |
| } |
| |
| static void clear_visited(SkOpSpan* span) { |
| // reset visited flag back to false |
| do { |
| SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; |
| while ((ptT = ptT->next()) != stopPtT) { |
| SkOpSegment* opp = ptT->segment(); |
| opp->resetVisited(); |
| } |
| } while ((span = span->next()->upCastable())); |
| } |
| |
| // look for pairs of undetected coincident curves |
| // assumes that segments going in have visited flag clear |
| // curve/curve intersection should now do a pretty good job of finding coincident runs so |
| // this may be only be necessary for line/curve pairs -- so skip unless this is a line and the |
| // the opp is not a line |
| void SkOpSegment::missingCoincidence(SkOpCoincidence* coincidences, SkChunkAlloc* allocator) { |
| if (this->verb() != SkPath::kLine_Verb) { |
| return; |
| } |
| SkOpSpan* prior = NULL; |
| SkOpSpan* span = &fHead; |
| do { |
| SkOpPtT* ptT = span->ptT(), * spanStopPtT = ptT; |
| SkASSERT(ptT->span() == span); |
| while ((ptT = ptT->next()) != spanStopPtT) { |
| SkOpSegment* opp = ptT->span()->segment(); |
| if (opp->setVisited()) { |
| continue; |
| } |
| if (opp->verb() == SkPath::kLine_Verb) { |
| continue; |
| } |
| if (span->containsCoincidence(opp)) { // FIXME: this assumes that if the opposite |
| // segment is coincident then no more coincidence |
| // needs to be detected. This may not be true. |
| continue; |
| } |
| if (span->containsCoinEnd(opp)) { |
| continue; |
| } |
| // if already visited and visited again, check for coin |
| if (span == &fHead) { |
| continue; |
| } |
| SkOpPtT* priorPtT = NULL, * priorStopPtT; |
| // find prior span containing opp segment |
| SkOpSegment* priorOpp = NULL; |
| prior = span; |
| while (!priorOpp && (prior = prior->prev())) { |
| priorStopPtT = priorPtT = prior->ptT(); |
| while ((priorPtT = priorPtT->next()) != priorStopPtT) { |
| SkOpSegment* segment = priorPtT->span()->segment(); |
| if (segment == opp) { |
| priorOpp = opp; |
| break; |
| } |
| } |
| } |
| if (!priorOpp) { |
| continue; |
| } |
| SkOpPtT* oppStart = prior->ptT(); |
| SkOpPtT* oppEnd = span->ptT(); |
| bool swapped = priorPtT->fT > ptT->fT; |
| if (swapped) { |
| SkTSwap(priorPtT, ptT); |
| SkTSwap(oppStart, oppEnd); |
| } |
| bool flipped = oppStart->fT > oppEnd->fT; |
| bool coincident; |
| if (coincidences->contains(priorPtT, ptT, oppStart, oppEnd, flipped)) { |
| goto swapBack; |
| } |
| { |
| // average t, find mid pt |
| double midT = (prior->t() + span->t()) / 2; |
| SkPoint midPt = this->ptAtT(midT); |
| coincident = true; |
| // if the mid pt is not near either end pt, project perpendicular through opp seg |
| if (!SkDPoint::ApproximatelyEqual(priorPtT->fPt, midPt) |
| && !SkDPoint::ApproximatelyEqual(ptT->fPt, midPt)) { |
| coincident = false; |
| SkIntersections i; |
| int ptCount = SkPathOpsVerbToPoints(this->verb()); |
| SkVector dxdy = (*CurveSlopeAtT[ptCount])(pts(), midT); |
| SkDLine ray = {{{midPt.fX, midPt.fY}, |
| {midPt.fX + dxdy.fY, midPt.fY - dxdy.fX}}}; |
| int oppPtCount = SkPathOpsVerbToPoints(opp->verb()); |
| (*CurveIntersectRay[oppPtCount])(opp->pts(), ray, &i); |
| // measure distance and see if it's small enough to denote coincidence |
| for (int index = 0; index < i.used(); ++index) { |
| SkDPoint oppPt = i.pt(index); |
| if (oppPt.approximatelyEqual(midPt)) { |
| SkVector oppDxdy = (*CurveSlopeAtT[oppPtCount])(opp->pts(), |
| i[index][0]); |
| oppDxdy.normalize(); |
| dxdy.normalize(); |
| SkScalar flatness = SkScalarAbs(dxdy.cross(oppDxdy) / FLT_EPSILON); |
| coincident |= flatness < 5000; // FIXME: replace with tuned value |
| } |
| } |
| } |
| } |
| if (coincident) { |
| // mark coincidence |
| coincidences->add(priorPtT, ptT, oppStart, oppEnd, allocator); |
| clear_visited(&fHead); |
| missingCoincidence(coincidences, allocator); |
| return; |
| } |
| swapBack: |
| if (swapped) { |
| SkTSwap(priorPtT, ptT); |
| } |
| } |
| } while ((span = span->next()->upCastable())); |
| clear_visited(&fHead); |
| } |
| |
| // Move nearby t values and pts so they all hang off the same span. Alignment happens later. |
| bool SkOpSegment::moveNearby() { |
| debugValidate(); |
| SkOpSpanBase* spanS = &fHead; |
| do { |
| SkOpSpanBase* test = spanS->upCast()->next(); |
| SkOpSpanBase* next; |
| if (spanS->contains(test)) { |
| if (!test->final()) { |
| test->upCast()->detach(spanS->ptT()); |
| continue; |
| } else if (spanS != &fHead) { |
| spanS->upCast()->detach(test->ptT()); |
| spanS = test; |
| continue; |
| } |
| } |
| do { // iterate through all spans associated with start |
| SkOpPtT* startBase = spanS->ptT(); |
| next = test->final() ? NULL : test->upCast()->next(); |
| do { |
| SkOpPtT* testBase = test->ptT(); |
| do { |
| if (startBase == testBase) { |
| goto checkNextSpan; |
| } |
| if (testBase->duplicate()) { |
| continue; |
| } |
| if (this->match(startBase, testBase->segment(), testBase->fT, testBase->fPt)) { |
| if (test == &this->fTail) { |
| if (spanS == &fHead) { |
| debugValidate(); |
| return true; // if this span has collapsed, remove it from parent |
| } |
| this->fTail.merge(spanS->upCast()); |
| debugValidate(); |
| return true; |
| } |
| spanS->merge(test->upCast()); |
| spanS->upCast()->setNext(next); |
| goto checkNextSpan; |
| } |
| } while ((testBase = testBase->next()) != test->ptT()); |
| } while ((startBase = startBase->next()) != spanS->ptT()); |
| checkNextSpan: |
| ; |
| } while ((test = next)); |
| spanS = spanS->upCast()->next(); |
| } while (!spanS->final()); |
| debugValidate(); |
| return true; |
| } |
| |
| bool SkOpSegment::operand() const { |
| return fContour->operand(); |
| } |
| |
| bool SkOpSegment::oppXor() const { |
| return fContour->oppXor(); |
| } |
| |
| bool SkOpSegment::ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const { |
| if (fVerb == SkPath::kLine_Verb) { |
| return false; |
| } |
| // quads (and cubics) can loop back to nearly a line so that an opposite curve |
| // hits in two places with very different t values. |
| // OPTIMIZATION: curves could be preflighted so that, for example, something like |
| // 'controls contained by ends' could avoid this check for common curves |
| // 'ends are extremes in x or y' is cheaper to compute and real-world common |
| // on the other hand, the below check is relatively inexpensive |
| double midT = (t1 + t2) / 2; |
| SkPoint midPt = this->ptAtT(midT); |
| double seDistSq = SkTMax(pt1.distanceToSqd(pt2) * 2, FLT_EPSILON * 2); |
| return midPt.distanceToSqd(pt1) > seDistSq || midPt.distanceToSqd(pt2) > seDistSq; |
| } |
| |
| void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, |
| int* maxWinding, int* sumWinding) { |
| int deltaSum = SpanSign(start, end); |
| *maxWinding = *sumMiWinding; |
| *sumWinding = *sumMiWinding -= deltaSum; |
| SkASSERT(!DEBUG_LIMIT_WIND_SUM || abs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); |
| } |
| |
| void SkOpSegment::setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, |
| int* sumSuWinding, int* maxWinding, int* sumWinding, int* oppMaxWinding, |
| int* oppSumWinding) { |
| int deltaSum = SpanSign(start, end); |
| int oppDeltaSum = OppSign(start, end); |
| if (operand()) { |
| *maxWinding = *sumSuWinding; |
| *sumWinding = *sumSuWinding -= deltaSum; |
| *oppMaxWinding = *sumMiWinding; |
| *oppSumWinding = *sumMiWinding -= oppDeltaSum; |
| } else { |
| *maxWinding = *sumMiWinding; |
| *sumWinding = *sumMiWinding -= deltaSum; |
| *oppMaxWinding = *sumSuWinding; |
| *oppSumWinding = *sumSuWinding -= oppDeltaSum; |
| } |
| SkASSERT(!DEBUG_LIMIT_WIND_SUM || abs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); |
| SkASSERT(!DEBUG_LIMIT_WIND_SUM || abs(*oppSumWinding) <= DEBUG_LIMIT_WIND_SUM); |
| } |
| |
| void SkOpSegment::sortAngles() { |
| SkOpSpanBase* span = &this->fHead; |
| do { |
| SkOpAngle* fromAngle = span->fromAngle(); |
| SkOpAngle* toAngle = span->final() ? NULL : span->upCast()->toAngle(); |
| if (!fromAngle && !toAngle) { |
| continue; |
| } |
| #if DEBUG_ANGLE |
| bool wroteAfterHeader = false; |
| #endif |
| SkOpAngle* baseAngle = fromAngle; |
| if (fromAngle && toAngle) { |
| #if DEBUG_ANGLE |
| SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), span->t(), |
| span->debugID()); |
| wroteAfterHeader = true; |
| #endif |
| fromAngle->insert(toAngle); |
| } else if (!fromAngle) { |
| baseAngle = toAngle; |
| } |
| SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; |
| do { |
| SkOpSpanBase* oSpan = ptT->span(); |
| if (oSpan == span) { |
| continue; |
| } |
| SkOpAngle* oAngle = oSpan->fromAngle(); |
| if (oAngle) { |
| #if DEBUG_ANGLE |
| if (!wroteAfterHeader) { |
| SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), |
| span->t(), span->debugID()); |
| wroteAfterHeader = true; |
| } |
| #endif |
| if (!oAngle->loopContains(baseAngle)) { |
| baseAngle->insert(oAngle); |
| } |
| } |
| if (!oSpan->final()) { |
| oAngle = oSpan->upCast()->toAngle(); |
| if (oAngle) { |
| #if DEBUG_ANGLE |
| if (!wroteAfterHeader) { |
| SkDebugf("%s [%d] tStart=%1.9g [%d]\n", __FUNCTION__, debugID(), |
| span->t(), span->debugID()); |
| wroteAfterHeader = true; |
| } |
| #endif |
| if (!oAngle->loopContains(baseAngle)) { |
| baseAngle->insert(oAngle); |
| } |
| } |
| } |
| } while ((ptT = ptT->next()) != stopPtT); |
| if (baseAngle->loopCount() == 1) { |
| span->setFromAngle(NULL); |
| if (toAngle) { |
| span->upCast()->setToAngle(NULL); |
| } |
| baseAngle = NULL; |
| } |
| #if DEBUG_SORT |
| SkASSERT(!baseAngle || baseAngle->loopCount() > 1); |
| #endif |
| } while (!span->final() && (span = span->upCast()->next())); |
| } |
| |
| // return true if midpoints were computed |
| bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, |
| SkPoint edge[4]) const { |
| SkASSERT(start != end); |
| const SkOpPtT& startPtT = *start->ptT(); |
| const SkOpPtT& endPtT = *end->ptT(); |
| edge[0] = startPtT.fPt; |
| int points = SkPathOpsVerbToPoints(fVerb); |
| edge[points] = endPtT.fPt; |
| if (fVerb == SkPath::kLine_Verb) { |
| return false; |
| } |
| double startT = startPtT.fT; |
| double endT = endPtT.fT; |
| if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) { |
| // don't compute midpoints if we already have them |
| if (fVerb == SkPath::kQuad_Verb) { |
| edge[1] = fPts[1]; |
| return false; |
| } |
| SkASSERT(fVerb == SkPath::kCubic_Verb); |
| if (start < end) { |
| edge[1] = fPts[1]; |
| edge[2] = fPts[2]; |
| return false; |
| } |
| edge[1] = fPts[2]; |
| edge[2] = fPts[1]; |
| return false; |
| } |
| const SkDPoint sub[2] = {{ edge[0].fX, edge[0].fY}, {edge[points].fX, edge[points].fY }}; |
| if (fVerb == SkPath::kQuad_Verb) { |
| edge[1] = SkDQuad::SubDivide(fPts, sub[0], sub[1], startT, endT).asSkPoint(); |
| } else { |
| SkASSERT(fVerb == SkPath::kCubic_Verb); |
| SkDPoint ctrl[2]; |
| SkDCubic::SubDivide(fPts, sub[0], sub[1], startT, endT, ctrl); |
| edge[1] = ctrl[0].asSkPoint(); |
| edge[2] = ctrl[1].asSkPoint(); |
| } |
| return true; |
| } |
| |
| bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, |
| SkDCubic* result) const { |
| SkASSERT(start != end); |
| const SkOpPtT& startPtT = *start->ptT(); |
| const SkOpPtT& endPtT = *end->ptT(); |
| (*result)[0].set(startPtT.fPt); |
| int points = SkPathOpsVerbToPoints(fVerb); |
| (*result)[points].set(endPtT.fPt); |
| if (fVerb == SkPath::kLine_Verb) { |
| return false; |
| } |
| double startT = startPtT.fT; |
| double endT = endPtT.fT; |
| if ((startT == 0 || endT == 0) && (startT == 1 || endT == 1)) { |
| // don't compute midpoints if we already have them |
| if (fVerb == SkPath::kQuad_Verb) { |
| (*result)[1].set(fPts[1]); |
| return false; |
| } |
| SkASSERT(fVerb == SkPath::kCubic_Verb); |
| if (startT == 0) { |
| (*result)[1].set(fPts[1]); |
| (*result)[2].set(fPts[2]); |
| return false; |
| } |
| (*result)[1].set(fPts[2]); |
| (*result)[2].set(fPts[1]); |
| return false; |
| } |
| if (fVerb == SkPath::kQuad_Verb) { |
| (*result)[1] = SkDQuad::SubDivide(fPts, (*result)[0], (*result)[2], startT, endT); |
| } else { |
| SkASSERT(fVerb == SkPath::kCubic_Verb); |
| SkDCubic::SubDivide(fPts, (*result)[0], (*result)[3], startT, endT, &(*result)[1]); |
| } |
| return true; |
| } |
| |
| void SkOpSegment::subDivideBounds(const SkOpSpanBase* start, const SkOpSpanBase* end, |
| SkPathOpsBounds* bounds) const { |
| SkPoint edge[4]; |
| subDivide(start, end, edge); |
| (bounds->*SetCurveBounds[SkPathOpsVerbToPoints(fVerb)])(edge); |
| } |
| |
| void SkOpSegment::undoneSpan(SkOpSpanBase** start, SkOpSpanBase** end) { |
| SkOpSpan* span = this->head(); |
| do { |
| if (!span->done()) { |
| break; |
| } |
| } while ((span = span->next()->upCastable())); |
| SkASSERT(span); |
| *start = span; |
| *end = span->next(); |
| } |
| |
| int SkOpSegment::updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const { |
| const SkOpSpan* lesser = start->starter(end); |
| int oppWinding = lesser->oppSum(); |
| int oppSpanWinding = SkOpSegment::OppSign(start, end); |
| if (oppSpanWinding && UseInnerWinding(oppWinding - oppSpanWinding, oppWinding) |
| && oppWinding != SK_MaxS32) { |
| oppWinding -= oppSpanWinding; |
| } |
| return oppWinding; |
| } |
| |
| int SkOpSegment::updateOppWinding(const SkOpAngle* angle) const { |
| const SkOpSpanBase* startSpan = angle->start(); |
| const SkOpSpanBase* endSpan = angle->end(); |
| return updateOppWinding(endSpan, startSpan); |
| } |
| |
| int SkOpSegment::updateOppWindingReverse(const SkOpAngle* angle) const { |
| const SkOpSpanBase* startSpan = angle->start(); |
| const SkOpSpanBase* endSpan = angle->end(); |
| return updateOppWinding(startSpan, endSpan); |
| } |
| |
| int SkOpSegment::updateWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const { |
| const SkOpSpan* lesser = start->starter(end); |
| int winding = lesser->windSum(); |
| if (winding == SK_MinS32) { |
| return winding; |
| } |
| int spanWinding = SkOpSegment::SpanSign(start, end); |
| if (winding && UseInnerWinding(winding - spanWinding, winding) |
| && winding != SK_MaxS32) { |
| winding -= spanWinding; |
| } |
| return winding; |
| } |
| |
| int SkOpSegment::updateWinding(const SkOpAngle* angle) const { |
| const SkOpSpanBase* startSpan = angle->start(); |
| const SkOpSpanBase* endSpan = angle->end(); |
| return updateWinding(endSpan, startSpan); |
| } |
| |
| int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) const { |
| const SkOpSpanBase* startSpan = angle->start(); |
| const SkOpSpanBase* endSpan = angle->end(); |
| return updateWinding(startSpan, endSpan); |
| } |
| |
| // 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; |
| } |
| |
| int SkOpSegment::windingAtT(double tHit, const SkOpSpan* span, bool crossOpp, |
| SkScalar* dx) const { |
| if (approximately_zero(tHit - span->t())) { // if we hit the end of a span, disregard |
| return SK_MinS32; |
| } |
| int winding = crossOpp ? span->oppSum() : span->windSum(); |
| SkASSERT(winding != SK_MinS32); |
| int windVal = crossOpp ? span->oppValue() : span->windValue(); |
| #if DEBUG_WINDING_AT_T |
| SkDebugf("%s id=%d opp=%d tHit=%1.9g t=%1.9g oldWinding=%d windValue=%d", __FUNCTION__, |
| debugID(), crossOpp, tHit, span->t(), winding, windVal); |
| #endif |
| // see if a + change in T results in a +/- change in X (compute x'(T)) |
| *dx = (*CurveSlopeAtT[SkPathOpsVerbToPoints(fVerb)])(fPts, tHit).fX; |
| if (fVerb > SkPath::kLine_Verb && approximately_zero(*dx)) { |
| *dx = fPts[2].fX - fPts[1].fX - *dx; |
| } |
| if (*dx == 0) { |
| #if DEBUG_WINDING_AT_T |
| SkDebugf(" dx=0 winding=SK_MinS32\n"); |
| #endif |
| return SK_MinS32; |
| } |
| if (windVal < 0) { // reverse sign if opp contour traveled in reverse |
| *dx = -*dx; |
| } |
| if (winding * *dx > 0) { // if same signs, result is negative |
| winding += *dx > 0 ? -windVal : windVal; |
| } |
| #if DEBUG_WINDING_AT_T |
| SkDebugf(" dx=%c winding=%d\n", *dx > 0 ? '+' : '-', winding); |
| #endif |
| return winding; |
| } |
| |
| int SkOpSegment::windSum(const SkOpAngle* angle) const { |
| const SkOpSpan* minSpan = angle->start()->starter(angle->end()); |
| return minSpan->windSum(); |
| } |