diff options
author | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
---|---|---|
committer | Matt A. Tobin <mattatobin@localhost.localdomain> | 2018-02-02 04:16:08 -0500 |
commit | 5f8de423f190bbb79a62f804151bc24824fa32d8 (patch) | |
tree | 10027f336435511475e392454359edea8e25895d /gfx/skia/skia/src/pathops | |
parent | 49ee0794b5d912db1f95dce6eb52d781dc210db5 (diff) | |
download | UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.gz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.lz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.tar.xz UXP-5f8de423f190bbb79a62f804151bc24824fa32d8.zip |
Add m-esr52 at 52.6.0
Diffstat (limited to 'gfx/skia/skia/src/pathops')
57 files changed, 22055 insertions, 0 deletions
diff --git a/gfx/skia/skia/src/pathops/SkAddIntersections.cpp b/gfx/skia/skia/src/pathops/SkAddIntersections.cpp new file mode 100644 index 000000000..b3a82cdec --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkAddIntersections.cpp @@ -0,0 +1,564 @@ +/* + * 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 "SkAddIntersections.h" +#include "SkOpCoincidence.h" +#include "SkPathOpsBounds.h" + +#if DEBUG_ADD_INTERSECTING_TS + +static void debugShowLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " LINE_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, LINE_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " LINE_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], LINE_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + if (pts == 2) { + SkDebugf(" " T_DEBUG_STR(wtTs, 1) " " PT_DEBUG_STR, i[0][1], PT_DEBUG_DATA(i, 1)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + if (pts == 2) { + SkDebugf(" " T_DEBUG_STR(wnTs, 1), i[1][1]); + } + SkDebugf("\n"); +} + +static void debugShowQuadLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, + const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " QUAD_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowQuadIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " QUAD_DEBUG_STR " " QUAD_DEBUG_STR "\n", + __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowConicLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CONIC_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowConicQuadIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CONIC_DEBUG_STR " " QUAD_DEBUG_STR "\n", + __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), QUAD_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowConicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CONIC_DEBUG_STR " " CONIC_DEBUG_STR "\n", + __FUNCTION__, CONIC_DEBUG_DATA(wt.pts(), wt.weight()), + CONIC_DEBUG_DATA(wn.pts(), wn.weight())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CONIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CONIC_DEBUG_DATA(wt.pts(), wt.weight()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " CONIC_DEBUG_STR, i[1][0], CONIC_DEBUG_DATA(wn.pts(), wn.weight())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicQuadIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " QUAD_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicConicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " CONIC_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), CONIC_DEBUG_DATA(wn.pts(), wn.weight())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " CONIC_DEBUG_STR, i[1][0], CONIC_DEBUG_DATA(wn.pts(), wn.weight())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " CUBIC_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), CUBIC_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " CUBIC_DEBUG_STR, i[1][0], CUBIC_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +#else +static void debugShowLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowQuadLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowQuadIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowConicLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowConicQuadIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowConicIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicQuadIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicConicIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} +#endif + +bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence) { + if (test != next) { + if (AlmostLessUlps(test->bounds().fBottom, next->bounds().fTop)) { + return false; + } + // OPTIMIZATION: outset contour bounds a smidgen instead? + if (!SkPathOpsBounds::Intersects(test->bounds(), next->bounds())) { + return true; + } + } + SkIntersectionHelper wt; + wt.init(test); + do { + SkIntersectionHelper wn; + wn.init(next); + test->debugValidate(); + next->debugValidate(); + if (test == next && !wn.startAfter(wt)) { + continue; + } + do { + if (!SkPathOpsBounds::Intersects(wt.bounds(), wn.bounds())) { + continue; + } + int pts = 0; + SkIntersections ts { SkDEBUGCODE(test->globalState()) }; + bool swap = false; + SkDQuad quad1, quad2; + SkDConic conic1, conic2; + SkDCubic cubic1, cubic2; + switch (wt.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + swap = true; + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + case SkIntersectionHelper::kVerticalLine_Segment: + case SkIntersectionHelper::kLine_Segment: + pts = ts.lineHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kQuad_Segment: + pts = ts.quadHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kConic_Segment: + pts = ts.conicHorizontal(wn.pts(), wn.weight(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowConicLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kCubic_Segment: + pts = ts.cubicHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kVerticalLine_Segment: + swap = true; + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + case SkIntersectionHelper::kVerticalLine_Segment: + case SkIntersectionHelper::kLine_Segment: { + pts = ts.lineVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.quadVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kConic_Segment: { + pts = ts.conicVertical(wn.pts(), wn.weight(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowConicLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + pts = ts.cubicVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + } + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kLine_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.lineHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.lineVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: + pts = ts.lineLine(wt.pts(), wn.pts()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kQuad_Segment: + swap = true; + pts = ts.quadLine(wn.pts(), wt.pts()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kConic_Segment: + swap = true; + pts = ts.conicLine(wn.pts(), wn.weight(), wt.pts()); + debugShowConicLineIntersection(pts, wn, wt, ts); + break; + case SkIntersectionHelper::kCubic_Segment: + swap = true; + pts = ts.cubicLine(wn.pts(), wt.pts()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kQuad_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.quadHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.quadVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: + pts = ts.quadLine(wt.pts(), wn.pts()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.intersect(quad1.set(wt.pts()), quad2.set(wn.pts())); + debugShowQuadIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kConic_Segment: { + swap = true; + pts = ts.intersect(conic2.set(wn.pts(), wn.weight()), + quad1.set(wt.pts())); + debugShowConicQuadIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + swap = true; + pts = ts.intersect(cubic2.set(wn.pts()), quad1.set(wt.pts())); + debugShowCubicQuadIntersection(pts, wn, wt, ts); + break; + } + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kConic_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.conicHorizontal(wt.pts(), wt.weight(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowConicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.conicVertical(wt.pts(), wt.weight(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowConicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: + pts = ts.conicLine(wt.pts(), wt.weight(), wn.pts()); + debugShowConicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.intersect(conic1.set(wt.pts(), wt.weight()), + quad2.set(wn.pts())); + debugShowConicQuadIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kConic_Segment: { + pts = ts.intersect(conic1.set(wt.pts(), wt.weight()), + conic2.set(wn.pts(), wn.weight())); + debugShowConicIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + swap = true; + pts = ts.intersect(cubic2.set(wn.pts()), + conic1.set(wt.pts(), wt.weight())); + debugShowCubicConicIntersection(pts, wn, wt, ts); + break; + } + } + break; + case SkIntersectionHelper::kCubic_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.cubicHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.cubicVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: + pts = ts.cubicLine(wt.pts(), wn.pts()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.intersect(cubic1.set(wt.pts()), quad2.set(wn.pts())); + debugShowCubicQuadIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kConic_Segment: { + pts = ts.intersect(cubic1.set(wt.pts()), + conic2.set(wn.pts(), wn.weight())); + debugShowCubicConicIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + pts = ts.intersect(cubic1.set(wt.pts()), cubic2.set(wn.pts())); + debugShowCubicIntersection(pts, wt, wn, ts); + break; + } + default: + SkASSERT(0); + } + break; + default: + SkASSERT(0); + } +#if DEBUG_T_SECT_LOOP_COUNT + test->globalState()->debugAddLoopCount(&ts, wt, wn); +#endif + int coinIndex = -1; + SkOpPtT* coinPtT[2]; + for (int pt = 0; pt < pts; ++pt) { + SkASSERT(ts[0][pt] >= 0 && ts[0][pt] <= 1); + SkASSERT(ts[1][pt] >= 0 && ts[1][pt] <= 1); + wt.segment()->debugValidate(); + SkOpPtT* testTAt = wt.segment()->addT(ts[swap][pt]); + wn.segment()->debugValidate(); + SkOpPtT* nextTAt = wn.segment()->addT(ts[!swap][pt]); + if (!testTAt->contains(nextTAt)) { + SkOpPtT* oppPrev = testTAt->oppPrev(nextTAt); // Returns nullptr if pair + if (oppPrev) { // already share a pt-t loop. + testTAt->span()->mergeMatches(nextTAt->span()); + testTAt->addOpp(nextTAt, oppPrev); + } + if (testTAt->fPt != nextTAt->fPt) { + testTAt->span()->unaligned(); + nextTAt->span()->unaligned(); + } + wt.segment()->debugValidate(); + wn.segment()->debugValidate(); + } + if (!ts.isCoincident(pt)) { + continue; + } + if (coinIndex < 0) { + coinPtT[0] = testTAt; + coinPtT[1] = nextTAt; + coinIndex = pt; + continue; + } + if (coinPtT[0]->span() == testTAt->span()) { + coinIndex = -1; + continue; + } + if (coinPtT[1]->span() == nextTAt->span()) { + coinIndex = -1; // coincidence span collapsed + continue; + } + if (swap) { + SkTSwap(coinPtT[0], coinPtT[1]); + SkTSwap(testTAt, nextTAt); + } + SkASSERT(coincidence->globalState()->debugSkipAssert() + || coinPtT[0]->span()->t() < testTAt->span()->t()); + if (coinPtT[0]->span()->deleted()) { + coinIndex = -1; + continue; + } + if (testTAt->span()->deleted()) { + coinIndex = -1; + continue; + } + coincidence->add(coinPtT[0], testTAt, coinPtT[1], nextTAt); + wt.segment()->debugValidate(); + wn.segment()->debugValidate(); + coinIndex = -1; + } + SkASSERT(coinIndex < 0); // expect coincidence to be paired + } while (wn.advance()); + } while (wt.advance()); + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkAddIntersections.h b/gfx/skia/skia/src/pathops/SkAddIntersections.h new file mode 100644 index 000000000..ca409800c --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkAddIntersections.h @@ -0,0 +1,17 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkAddIntersections_DEFINED +#define SkAddIntersections_DEFINED + +#include "SkIntersectionHelper.h" +#include "SkIntersections.h" + +class SkOpCoincidence; + +bool AddIntersectTs(SkOpContour* test, SkOpContour* next, SkOpCoincidence* coincidence); + +#endif diff --git a/gfx/skia/skia/src/pathops/SkDConicLineIntersection.cpp b/gfx/skia/skia/src/pathops/SkDConicLineIntersection.cpp new file mode 100644 index 000000000..eb32068d0 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDConicLineIntersection.cpp @@ -0,0 +1,384 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkIntersections.h" +#include "SkPathOpsConic.h" +#include "SkPathOpsCurve.h" +#include "SkPathOpsLine.h" + +class LineConicIntersections { +public: + enum PinTPoint { + kPointUninitialized, + kPointInitialized + }; + + LineConicIntersections(const SkDConic& c, const SkDLine& l, SkIntersections* i) + : fConic(c) + , fLine(&l) + , fIntersections(i) + , fAllowNear(true) { + i->setMax(4); // allow short partial coincidence plus discrete intersection + } + + LineConicIntersections(const SkDConic& c) + : fConic(c) + SkDEBUGPARAMS(fLine(nullptr)) + SkDEBUGPARAMS(fIntersections(nullptr)) + SkDEBUGPARAMS(fAllowNear(false)) { + } + + void allowNear(bool allow) { + fAllowNear = allow; + } + + void checkCoincident() { + int last = fIntersections->used() - 1; + for (int index = 0; index < last; ) { + double conicMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; + SkDPoint conicMidPt = fConic.ptAtT(conicMidT); + double t = fLine->nearPoint(conicMidPt, nullptr); + if (t < 0) { + ++index; + continue; + } + if (fIntersections->isCoincident(index)) { + fIntersections->removeOne(index); + --last; + } else if (fIntersections->isCoincident(index + 1)) { + fIntersections->removeOne(index + 1); + --last; + } else { + fIntersections->setCoincident(index++); + } + fIntersections->setCoincident(index); + } + } + +#ifdef SK_DEBUG + static bool close_to(double a, double b, const double c[3]) { + double max = SkTMax(-SkTMin(SkTMin(c[0], c[1]), c[2]), SkTMax(SkTMax(c[0], c[1]), c[2])); + return approximately_zero_when_compared_to(a - b, max); + } +#endif + int horizontalIntersect(double axisIntercept, double roots[2]) { + double conicVals[] = { fConic[0].fY, fConic[1].fY, fConic[2].fY }; + return this->validT(conicVals, axisIntercept, roots); + } + + int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { + this->addExactHorizontalEndPoints(left, right, axisIntercept); + if (fAllowNear) { + this->addNearHorizontalEndPoints(left, right, axisIntercept); + } + double roots[2]; + int count = this->horizontalIntersect(axisIntercept, roots); + for (int index = 0; index < count; ++index) { + double conicT = roots[index]; + SkDPoint pt = fConic.ptAtT(conicT); + SkDEBUGCODE(double conicVals[] = { fConic[0].fY, fConic[1].fY, fConic[2].fY }); + SkOPOBJASSERT(fIntersections, close_to(pt.fY, axisIntercept, conicVals)); + double lineT = (pt.fX - left) / (right - left); + if (this->pinTs(&conicT, &lineT, &pt, kPointInitialized) + && this->uniqueAnswer(conicT, pt)) { + fIntersections->insert(conicT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + this->checkCoincident(); + return fIntersections->used(); + } + + int intersect() { + this->addExactEndPoints(); + if (fAllowNear) { + this->addNearEndPoints(); + } + double rootVals[2]; + int roots = this->intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double conicT = rootVals[index]; + double lineT = this->findLineT(conicT); +#ifdef SK_DEBUG + if (!fIntersections->debugGlobalState() + || !fIntersections->debugGlobalState()->debugSkipAssert()) { + SkDEBUGCODE(SkDPoint conicPt = fConic.ptAtT(conicT)); + SkDEBUGCODE(SkDPoint linePt = fLine->ptAtT(lineT)); + SkASSERT(conicPt.approximatelyDEqual(linePt)); + } +#endif + SkDPoint pt; + if (this->pinTs(&conicT, &lineT, &pt, kPointUninitialized) + && this->uniqueAnswer(conicT, pt)) { + fIntersections->insert(conicT, lineT, pt); + } + } + this->checkCoincident(); + return fIntersections->used(); + } + + int intersectRay(double roots[2]) { + double adj = (*fLine)[1].fX - (*fLine)[0].fX; + double opp = (*fLine)[1].fY - (*fLine)[0].fY; + double r[3]; + for (int n = 0; n < 3; ++n) { + r[n] = (fConic[n].fY - (*fLine)[0].fY) * adj - (fConic[n].fX - (*fLine)[0].fX) * opp; + } + return this->validT(r, 0, roots); + } + + int validT(double r[3], double axisIntercept, double roots[2]) { + double A = r[2]; + double B = r[1] * fConic.fWeight - axisIntercept * fConic.fWeight + axisIntercept; + double C = r[0]; + A += C - 2 * B; // A = a + c - 2*(b*w - xCept*w + xCept) + B -= C; // B = b*w - w * xCept + xCept - a + C -= axisIntercept; + return SkDQuad::RootsValidT(A, 2 * B, C, roots); + } + + int verticalIntersect(double axisIntercept, double roots[2]) { + double conicVals[] = { fConic[0].fX, fConic[1].fX, fConic[2].fX }; + return this->validT(conicVals, axisIntercept, roots); + } + + int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { + this->addExactVerticalEndPoints(top, bottom, axisIntercept); + if (fAllowNear) { + this->addNearVerticalEndPoints(top, bottom, axisIntercept); + } + double roots[2]; + int count = this->verticalIntersect(axisIntercept, roots); + for (int index = 0; index < count; ++index) { + double conicT = roots[index]; + SkDPoint pt = fConic.ptAtT(conicT); + SkDEBUGCODE(double conicVals[] = { fConic[0].fX, fConic[1].fX, fConic[2].fX }); + SkOPOBJASSERT(fIntersections, close_to(pt.fX, axisIntercept, conicVals)); + double lineT = (pt.fY - top) / (bottom - top); + if (this->pinTs(&conicT, &lineT, &pt, kPointInitialized) + && this->uniqueAnswer(conicT, pt)) { + fIntersections->insert(conicT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + this->checkCoincident(); + return fIntersections->used(); + } + +protected: +// OPTIMIZE: Functions of the form add .. points are indentical to the conic routines. + // add endpoints first to get zero and one t values exactly + void addExactEndPoints() { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double lineT = fLine->exactPoint(fConic[cIndex]); + if (lineT < 0) { + continue; + } + double conicT = (double) (cIndex >> 1); + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + } + + void addNearEndPoints() { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double conicT = (double) (cIndex >> 1); + if (fIntersections->hasT(conicT)) { + continue; + } + double lineT = fLine->nearPoint(fConic[cIndex], nullptr); + if (lineT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double conicT = ((SkDCurve*) &fConic)->nearPoint(SkPath::kConic_Verb, + (*fLine)[lIndex], (*fLine)[!lIndex]); + if (conicT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, (*fLine)[lIndex]); + } + } + + void addExactHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double lineT = SkDLine::ExactPointH(fConic[cIndex], left, right, y); + if (lineT < 0) { + continue; + } + double conicT = (double) (cIndex >> 1); + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + } + + void addNearHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double conicT = (double) (cIndex >> 1); + if (fIntersections->hasT(conicT)) { + continue; + } + double lineT = SkDLine::NearPointH(fConic[cIndex], left, right, y); + if (lineT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + this->addLineNearEndPoints(); + } + + void addExactVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double lineT = SkDLine::ExactPointV(fConic[cIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + double conicT = (double) (cIndex >> 1); + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + } + + void addNearVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < SkDConic::kPointCount; cIndex += SkDConic::kPointLast) { + double conicT = (double) (cIndex >> 1); + if (fIntersections->hasT(conicT)) { + continue; + } + double lineT = SkDLine::NearPointV(fConic[cIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + fIntersections->insert(conicT, lineT, fConic[cIndex]); + } + this->addLineNearEndPoints(); + } + + double findLineT(double t) { + SkDPoint xy = fConic.ptAtT(t); + double dx = (*fLine)[1].fX - (*fLine)[0].fX; + double dy = (*fLine)[1].fY - (*fLine)[0].fY; + if (fabs(dx) > fabs(dy)) { + return (xy.fX - (*fLine)[0].fX) / dx; + } + return (xy.fY - (*fLine)[0].fY) / dy; + } + + bool pinTs(double* conicT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { + if (!approximately_one_or_less_double(*lineT)) { + return false; + } + if (!approximately_zero_or_more_double(*lineT)) { + return false; + } + double qT = *conicT = SkPinT(*conicT); + double lT = *lineT = SkPinT(*lineT); + if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && qT != 0 && qT != 1)) { + *pt = (*fLine).ptAtT(lT); + } else if (ptSet == kPointUninitialized) { + *pt = fConic.ptAtT(qT); + } + SkPoint gridPt = pt->asSkPoint(); + if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[0].asSkPoint())) { + *pt = (*fLine)[0]; + *lineT = 0; + } else if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[1].asSkPoint())) { + *pt = (*fLine)[1]; + *lineT = 1; + } + if (fIntersections->used() > 0 && approximately_equal((*fIntersections)[1][0], *lineT)) { + return false; + } + if (gridPt == fConic[0].asSkPoint()) { + *pt = fConic[0]; + *conicT = 0; + } else if (gridPt == fConic[2].asSkPoint()) { + *pt = fConic[2]; + *conicT = 1; + } + return true; + } + + bool uniqueAnswer(double conicT, const SkDPoint& pt) { + for (int inner = 0; inner < fIntersections->used(); ++inner) { + if (fIntersections->pt(inner) != pt) { + continue; + } + double existingConicT = (*fIntersections)[0][inner]; + if (conicT == existingConicT) { + return false; + } + // check if midway on conic is also same point. If so, discard this + double conicMidT = (existingConicT + conicT) / 2; + SkDPoint conicMidPt = fConic.ptAtT(conicMidT); + if (conicMidPt.approximatelyEqual(pt)) { + return false; + } + } +#if ONE_OFF_DEBUG + SkDPoint qPt = fConic.ptAtT(conicT); + SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, + qPt.fX, qPt.fY); +#endif + return true; + } + +private: + const SkDConic& fConic; + const SkDLine* fLine; + SkIntersections* fIntersections; + bool fAllowNear; +}; + +int SkIntersections::horizontal(const SkDConic& conic, double left, double right, double y, + bool flipped) { + SkDLine line = {{{ left, y }, { right, y }}}; + LineConicIntersections c(conic, line, this); + return c.horizontalIntersect(y, left, right, flipped); +} + +int SkIntersections::vertical(const SkDConic& conic, double top, double bottom, double x, + bool flipped) { + SkDLine line = {{{ x, top }, { x, bottom }}}; + LineConicIntersections c(conic, line, this); + return c.verticalIntersect(x, top, bottom, flipped); +} + +int SkIntersections::intersect(const SkDConic& conic, const SkDLine& line) { + LineConicIntersections c(conic, line, this); + c.allowNear(fAllowNear); + return c.intersect(); +} + +int SkIntersections::intersectRay(const SkDConic& conic, const SkDLine& line) { + LineConicIntersections c(conic, line, this); + fUsed = c.intersectRay(fT[0]); + for (int index = 0; index < fUsed; ++index) { + fPt[index] = conic.ptAtT(fT[0][index]); + } + return fUsed; +} + +int SkIntersections::HorizontalIntercept(const SkDConic& conic, SkScalar y, double* roots) { + LineConicIntersections c(conic); + return c.horizontalIntersect(y, roots); +} + +int SkIntersections::VerticalIntercept(const SkDConic& conic, SkScalar x, double* roots) { + LineConicIntersections c(conic); + return c.verticalIntersect(x, roots); +} diff --git a/gfx/skia/skia/src/pathops/SkDCubicLineIntersection.cpp b/gfx/skia/skia/src/pathops/SkDCubicLineIntersection.cpp new file mode 100644 index 000000000..fd060de64 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDCubicLineIntersection.cpp @@ -0,0 +1,454 @@ +/* + * 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 "SkIntersections.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsCurve.h" +#include "SkPathOpsLine.h" + +/* +Find the interection of a line and cubic by solving for valid t values. + +Analogous to line-quadratic intersection, solve line-cubic intersection by +representing the cubic as: + x = a(1-t)^3 + 2b(1-t)^2t + c(1-t)t^2 + dt^3 + y = e(1-t)^3 + 2f(1-t)^2t + g(1-t)t^2 + ht^3 +and the line as: + y = i*x + j (if the line is more horizontal) +or: + x = i*y + j (if the line is more vertical) + +Then using Mathematica, solve for the values of t where the cubic intersects the +line: + + (in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - x, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - i*x - j, x] + (out) -e + j + + 3 e t - 3 f t - + 3 e t^2 + 6 f t^2 - 3 g t^2 + + e t^3 - 3 f t^3 + 3 g t^3 - h t^3 + + i ( a - + 3 a t + 3 b t + + 3 a t^2 - 6 b t^2 + 3 c t^2 - + a t^3 + 3 b t^3 - 3 c t^3 + d t^3 ) + +if i goes to infinity, we can rewrite the line in terms of x. Mathematica: + + (in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - i*y - j, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y] + (out) a - j - + 3 a t + 3 b t + + 3 a t^2 - 6 b t^2 + 3 c t^2 - + a t^3 + 3 b t^3 - 3 c t^3 + d t^3 - + i ( e - + 3 e t + 3 f t + + 3 e t^2 - 6 f t^2 + 3 g t^2 - + e t^3 + 3 f t^3 - 3 g t^3 + h t^3 ) + +Solving this with Mathematica produces an expression with hundreds of terms; +instead, use Numeric Solutions recipe to solve the cubic. + +The near-horizontal case, in terms of: Ax^3 + Bx^2 + Cx + D == 0 + A = (-(-e + 3*f - 3*g + h) + i*(-a + 3*b - 3*c + d) ) + B = 3*(-( e - 2*f + g ) + i*( a - 2*b + c ) ) + C = 3*(-(-e + f ) + i*(-a + b ) ) + D = (-( e ) + i*( a ) + j ) + +The near-vertical case, in terms of: Ax^3 + Bx^2 + Cx + D == 0 + A = ( (-a + 3*b - 3*c + d) - i*(-e + 3*f - 3*g + h) ) + B = 3*( ( a - 2*b + c ) - i*( e - 2*f + g ) ) + C = 3*( (-a + b ) - i*(-e + f ) ) + D = ( ( a ) - i*( e ) - j ) + +For horizontal lines: +(in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - j, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y] +(out) e - j - + 3 e t + 3 f t + + 3 e t^2 - 6 f t^2 + 3 g t^2 - + e t^3 + 3 f t^3 - 3 g t^3 + h t^3 + */ + +class LineCubicIntersections { +public: + enum PinTPoint { + kPointUninitialized, + kPointInitialized + }; + + LineCubicIntersections(const SkDCubic& c, const SkDLine& l, SkIntersections* i) + : fCubic(c) + , fLine(l) + , fIntersections(i) + , fAllowNear(true) { + i->setMax(4); + } + + void allowNear(bool allow) { + fAllowNear = allow; + } + + void checkCoincident() { + int last = fIntersections->used() - 1; + for (int index = 0; index < last; ) { + double cubicMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; + SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); + double t = fLine.nearPoint(cubicMidPt, nullptr); + if (t < 0) { + ++index; + continue; + } + if (fIntersections->isCoincident(index)) { + fIntersections->removeOne(index); + --last; + } else if (fIntersections->isCoincident(index + 1)) { + fIntersections->removeOne(index + 1); + --last; + } else { + fIntersections->setCoincident(index++); + } + fIntersections->setCoincident(index); + } + } + + // see parallel routine in line quadratic intersections + int intersectRay(double roots[3]) { + double adj = fLine[1].fX - fLine[0].fX; + double opp = fLine[1].fY - fLine[0].fY; + SkDCubic c; + for (int n = 0; n < 4; ++n) { + c[n].fX = (fCubic[n].fY - fLine[0].fY) * adj - (fCubic[n].fX - fLine[0].fX) * opp; + } + double A, B, C, D; + SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D); + int count = SkDCubic::RootsValidT(A, B, C, D, roots); + for (int index = 0; index < count; ++index) { + SkDPoint calcPt = c.ptAtT(roots[index]); + if (!approximately_zero(calcPt.fX)) { + for (int n = 0; n < 4; ++n) { + c[n].fY = (fCubic[n].fY - fLine[0].fY) * opp + + (fCubic[n].fX - fLine[0].fX) * adj; + } + double extremeTs[6]; + int extrema = SkDCubic::FindExtrema(&c[0].fX, extremeTs); + count = c.searchRoots(extremeTs, extrema, 0, SkDCubic::kXAxis, roots); + break; + } + } + return count; + } + + int intersect() { + addExactEndPoints(); + if (fAllowNear) { + addNearEndPoints(); + } + double rootVals[3]; + int roots = intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double cubicT = rootVals[index]; + double lineT = findLineT(cubicT); + SkDPoint pt; + if (pinTs(&cubicT, &lineT, &pt, kPointUninitialized) && uniqueAnswer(cubicT, pt)) { + fIntersections->insert(cubicT, lineT, pt); + } + } + checkCoincident(); + return fIntersections->used(); + } + + static int HorizontalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) { + double A, B, C, D; + SkDCubic::Coefficients(&c[0].fY, &A, &B, &C, &D); + D -= axisIntercept; + int count = SkDCubic::RootsValidT(A, B, C, D, roots); + for (int index = 0; index < count; ++index) { + SkDPoint calcPt = c.ptAtT(roots[index]); + if (!approximately_equal(calcPt.fY, axisIntercept)) { + double extremeTs[6]; + int extrema = SkDCubic::FindExtrema(&c[0].fY, extremeTs); + count = c.searchRoots(extremeTs, extrema, axisIntercept, SkDCubic::kYAxis, roots); + break; + } + } + return count; + } + + int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { + addExactHorizontalEndPoints(left, right, axisIntercept); + if (fAllowNear) { + addNearHorizontalEndPoints(left, right, axisIntercept); + } + double roots[3]; + int count = HorizontalIntersect(fCubic, axisIntercept, roots); + for (int index = 0; index < count; ++index) { + double cubicT = roots[index]; + SkDPoint pt = { fCubic.ptAtT(cubicT).fX, axisIntercept }; + double lineT = (pt.fX - left) / (right - left); + if (pinTs(&cubicT, &lineT, &pt, kPointInitialized) && uniqueAnswer(cubicT, pt)) { + fIntersections->insert(cubicT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + checkCoincident(); + return fIntersections->used(); + } + + bool uniqueAnswer(double cubicT, const SkDPoint& pt) { + for (int inner = 0; inner < fIntersections->used(); ++inner) { + if (fIntersections->pt(inner) != pt) { + continue; + } + double existingCubicT = (*fIntersections)[0][inner]; + if (cubicT == existingCubicT) { + return false; + } + // check if midway on cubic is also same point. If so, discard this + double cubicMidT = (existingCubicT + cubicT) / 2; + SkDPoint cubicMidPt = fCubic.ptAtT(cubicMidT); + if (cubicMidPt.approximatelyEqual(pt)) { + return false; + } + } +#if ONE_OFF_DEBUG + SkDPoint cPt = fCubic.ptAtT(cubicT); + SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, + cPt.fX, cPt.fY); +#endif + return true; + } + + static int VerticalIntersect(const SkDCubic& c, double axisIntercept, double roots[3]) { + double A, B, C, D; + SkDCubic::Coefficients(&c[0].fX, &A, &B, &C, &D); + D -= axisIntercept; + int count = SkDCubic::RootsValidT(A, B, C, D, roots); + for (int index = 0; index < count; ++index) { + SkDPoint calcPt = c.ptAtT(roots[index]); + if (!approximately_equal(calcPt.fX, axisIntercept)) { + double extremeTs[6]; + int extrema = SkDCubic::FindExtrema(&c[0].fX, extremeTs); + count = c.searchRoots(extremeTs, extrema, axisIntercept, SkDCubic::kXAxis, roots); + break; + } + } + return count; + } + + int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { + addExactVerticalEndPoints(top, bottom, axisIntercept); + if (fAllowNear) { + addNearVerticalEndPoints(top, bottom, axisIntercept); + } + double roots[3]; + int count = VerticalIntersect(fCubic, axisIntercept, roots); + for (int index = 0; index < count; ++index) { + double cubicT = roots[index]; + SkDPoint pt = { axisIntercept, fCubic.ptAtT(cubicT).fY }; + double lineT = (pt.fY - top) / (bottom - top); + if (pinTs(&cubicT, &lineT, &pt, kPointInitialized) && uniqueAnswer(cubicT, pt)) { + fIntersections->insert(cubicT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + checkCoincident(); + return fIntersections->used(); + } + + protected: + + void addExactEndPoints() { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double lineT = fLine.exactPoint(fCubic[cIndex]); + if (lineT < 0) { + continue; + } + double cubicT = (double) (cIndex >> 1); + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + } + + /* Note that this does not look for endpoints of the line that are near the cubic. + These points are found later when check ends looks for missing points */ + void addNearEndPoints() { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double cubicT = (double) (cIndex >> 1); + if (fIntersections->hasT(cubicT)) { + continue; + } + double lineT = fLine.nearPoint(fCubic[cIndex], nullptr); + if (lineT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double cubicT = ((SkDCurve*) &fCubic)->nearPoint(SkPath::kCubic_Verb, + fLine[lIndex], fLine[!lIndex]); + if (cubicT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fLine[lIndex]); + } + } + + void addExactHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double lineT = SkDLine::ExactPointH(fCubic[cIndex], left, right, y); + if (lineT < 0) { + continue; + } + double cubicT = (double) (cIndex >> 1); + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + } + + void addNearHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double cubicT = (double) (cIndex >> 1); + if (fIntersections->hasT(cubicT)) { + continue; + } + double lineT = SkDLine::NearPointH(fCubic[cIndex], left, right, y); + if (lineT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + this->addLineNearEndPoints(); + } + + void addExactVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double lineT = SkDLine::ExactPointV(fCubic[cIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + double cubicT = (double) (cIndex >> 1); + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + } + + void addNearVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + double cubicT = (double) (cIndex >> 1); + if (fIntersections->hasT(cubicT)) { + continue; + } + double lineT = SkDLine::NearPointV(fCubic[cIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + fIntersections->insert(cubicT, lineT, fCubic[cIndex]); + } + this->addLineNearEndPoints(); + } + + double findLineT(double t) { + SkDPoint xy = fCubic.ptAtT(t); + double dx = fLine[1].fX - fLine[0].fX; + double dy = fLine[1].fY - fLine[0].fY; + if (fabs(dx) > fabs(dy)) { + return (xy.fX - fLine[0].fX) / dx; + } + return (xy.fY - fLine[0].fY) / dy; + } + + bool pinTs(double* cubicT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { + if (!approximately_one_or_less(*lineT)) { + return false; + } + if (!approximately_zero_or_more(*lineT)) { + return false; + } + double cT = *cubicT = SkPinT(*cubicT); + double lT = *lineT = SkPinT(*lineT); + SkDPoint lPt = fLine.ptAtT(lT); + SkDPoint cPt = fCubic.ptAtT(cT); + if (!lPt.roughlyEqual(cPt)) { + return false; + } + // FIXME: if points are roughly equal but not approximately equal, need to do + // a binary search like quad/quad intersection to find more precise t values + if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && cT != 0 && cT != 1)) { + *pt = lPt; + } else if (ptSet == kPointUninitialized) { + *pt = cPt; + } + SkPoint gridPt = pt->asSkPoint(); + if (gridPt == fLine[0].asSkPoint()) { + *lineT = 0; + } else if (gridPt == fLine[1].asSkPoint()) { + *lineT = 1; + } + if (gridPt == fCubic[0].asSkPoint() && approximately_equal(*cubicT, 0)) { + *cubicT = 0; + } else if (gridPt == fCubic[3].asSkPoint() && approximately_equal(*cubicT, 1)) { + *cubicT = 1; + } + return true; + } + +private: + const SkDCubic& fCubic; + const SkDLine& fLine; + SkIntersections* fIntersections; + bool fAllowNear; +}; + +int SkIntersections::horizontal(const SkDCubic& cubic, double left, double right, double y, + bool flipped) { + SkDLine line = {{{ left, y }, { right, y }}}; + LineCubicIntersections c(cubic, line, this); + return c.horizontalIntersect(y, left, right, flipped); +} + +int SkIntersections::vertical(const SkDCubic& cubic, double top, double bottom, double x, + bool flipped) { + SkDLine line = {{{ x, top }, { x, bottom }}}; + LineCubicIntersections c(cubic, line, this); + return c.verticalIntersect(x, top, bottom, flipped); +} + +int SkIntersections::intersect(const SkDCubic& cubic, const SkDLine& line) { + LineCubicIntersections c(cubic, line, this); + c.allowNear(fAllowNear); + return c.intersect(); +} + +int SkIntersections::intersectRay(const SkDCubic& cubic, const SkDLine& line) { + LineCubicIntersections c(cubic, line, this); + fUsed = c.intersectRay(fT[0]); + for (int index = 0; index < fUsed; ++index) { + fPt[index] = cubic.ptAtT(fT[0][index]); + } + return fUsed; +} + +// SkDCubic accessors to Intersection utilities + +int SkDCubic::horizontalIntersect(double yIntercept, double roots[3]) const { + return LineCubicIntersections::HorizontalIntersect(*this, yIntercept, roots); +} + +int SkDCubic::verticalIntersect(double xIntercept, double roots[3]) const { + return LineCubicIntersections::VerticalIntersect(*this, xIntercept, roots); +} diff --git a/gfx/skia/skia/src/pathops/SkDCubicToQuads.cpp b/gfx/skia/skia/src/pathops/SkDCubicToQuads.cpp new file mode 100644 index 000000000..272b997d6 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDCubicToQuads.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +/* +http://stackoverflow.com/questions/2009160/how-do-i-convert-the-2-control-points-of-a-cubic-curve-to-the-single-control-poi +*/ + +/* +Let's call the control points of the cubic Q0..Q3 and the control points of the quadratic P0..P2. +Then for degree elevation, the equations are: + +Q0 = P0 +Q1 = 1/3 P0 + 2/3 P1 +Q2 = 2/3 P1 + 1/3 P2 +Q3 = P2 +In your case you have Q0..Q3 and you're solving for P0..P2. There are two ways to compute P1 from + the equations above: + +P1 = 3/2 Q1 - 1/2 Q0 +P1 = 3/2 Q2 - 1/2 Q3 +If this is a degree-elevated cubic, then both equations will give the same answer for P1. Since + it's likely not, your best bet is to average them. So, + +P1 = -1/4 Q0 + 3/4 Q1 + 3/4 Q2 - 1/4 Q3 +*/ + +#include "SkPathOpsCubic.h" +#include "SkPathOpsQuad.h" + +// used for testing only +SkDQuad SkDCubic::toQuad() const { + SkDQuad quad; + quad[0] = fPts[0]; + const SkDPoint fromC1 = {(3 * fPts[1].fX - fPts[0].fX) / 2, (3 * fPts[1].fY - fPts[0].fY) / 2}; + const SkDPoint fromC2 = {(3 * fPts[2].fX - fPts[3].fX) / 2, (3 * fPts[2].fY - fPts[3].fY) / 2}; + quad[1].fX = (fromC1.fX + fromC2.fX) / 2; + quad[1].fY = (fromC1.fY + fromC2.fY) / 2; + quad[2] = fPts[3]; + return quad; +} diff --git a/gfx/skia/skia/src/pathops/SkDLineIntersection.cpp b/gfx/skia/skia/src/pathops/SkDLineIntersection.cpp new file mode 100644 index 000000000..71e2a064d --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDLineIntersection.cpp @@ -0,0 +1,333 @@ +/* + * 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 "SkIntersections.h" +#include "SkPathOpsLine.h" + +void SkIntersections::cleanUpParallelLines(bool parallel) { + while (fUsed > 2) { + removeOne(1); + } + if (fUsed == 2 && !parallel) { + bool startMatch = fT[0][0] == 0 || zero_or_one(fT[1][0]); + bool endMatch = fT[0][1] == 1 || zero_or_one(fT[1][1]); + if ((!startMatch && !endMatch) || approximately_equal(fT[0][0], fT[0][1])) { + SkASSERT(startMatch || endMatch); + if (startMatch && endMatch && (fT[0][0] != 0 || !zero_or_one(fT[1][0])) + && fT[0][1] == 1 && zero_or_one(fT[1][1])) { + removeOne(0); + } else { + removeOne(endMatch); + } + } + } + if (fUsed == 2) { + fIsCoincident[0] = fIsCoincident[1] = 0x03; + } +} + +void SkIntersections::computePoints(const SkDLine& line, int used) { + fPt[0] = line.ptAtT(fT[0][0]); + if ((fUsed = used) == 2) { + fPt[1] = line.ptAtT(fT[0][1]); + } +} + +int SkIntersections::intersectRay(const SkDLine& a, const SkDLine& b) { + fMax = 2; + SkDVector aLen = a[1] - a[0]; + SkDVector bLen = b[1] - b[0]; + /* Slopes match when denom goes to zero: + axLen / ayLen == bxLen / byLen + (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen + byLen * axLen == ayLen * bxLen + byLen * axLen - ayLen * bxLen == 0 ( == denom ) + */ + double denom = bLen.fY * aLen.fX - aLen.fY * bLen.fX; + SkDVector ab0 = a[0] - b[0]; + double numerA = ab0.fY * bLen.fX - bLen.fY * ab0.fX; + double numerB = ab0.fY * aLen.fX - aLen.fY * ab0.fX; + numerA /= denom; + numerB /= denom; + int used; + if (!approximately_zero(denom)) { + fT[0][0] = numerA; + fT[1][0] = numerB; + used = 1; + } else { + /* See if the axis intercepts match: + ay - ax * ayLen / axLen == by - bx * ayLen / axLen + axLen * (ay - ax * ayLen / axLen) == axLen * (by - bx * ayLen / axLen) + axLen * ay - ax * ayLen == axLen * by - bx * ayLen + */ + if (!AlmostEqualUlps(aLen.fX * a[0].fY - aLen.fY * a[0].fX, + aLen.fX * b[0].fY - aLen.fY * b[0].fX)) { + return fUsed = 0; + } + // there's no great answer for intersection points for coincident rays, but return something + fT[0][0] = fT[1][0] = 0; + fT[1][0] = fT[1][1] = 1; + used = 2; + } + computePoints(a, used); + return fUsed; +} + +// note that this only works if both lines are neither horizontal nor vertical +int SkIntersections::intersect(const SkDLine& a, const SkDLine& b) { + fMax = 3; // note that we clean up so that there is no more than two in the end + // see if end points intersect the opposite line + double t; + for (int iA = 0; iA < 2; ++iA) { + if ((t = b.exactPoint(a[iA])) >= 0) { + insert(iA, t, a[iA]); + } + } + for (int iB = 0; iB < 2; ++iB) { + if ((t = a.exactPoint(b[iB])) >= 0) { + insert(t, iB, b[iB]); + } + } + /* Determine the intersection point of two line segments + Return FALSE if the lines don't intersect + from: http://paulbourke.net/geometry/lineline2d/ */ + double axLen = a[1].fX - a[0].fX; + double ayLen = a[1].fY - a[0].fY; + double bxLen = b[1].fX - b[0].fX; + double byLen = b[1].fY - b[0].fY; + /* Slopes match when denom goes to zero: + axLen / ayLen == bxLen / byLen + (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen + byLen * axLen == ayLen * bxLen + byLen * axLen - ayLen * bxLen == 0 ( == denom ) + */ + double axByLen = axLen * byLen; + double ayBxLen = ayLen * bxLen; + // detect parallel lines the same way here and in SkOpAngle operator < + // so that non-parallel means they are also sortable + bool unparallel = fAllowNear ? NotAlmostEqualUlps_Pin(axByLen, ayBxLen) + : NotAlmostDequalUlps(axByLen, ayBxLen); + if (unparallel && fUsed == 0) { + double ab0y = a[0].fY - b[0].fY; + double ab0x = a[0].fX - b[0].fX; + double numerA = ab0y * bxLen - byLen * ab0x; + double numerB = ab0y * axLen - ayLen * ab0x; + double denom = axByLen - ayBxLen; + if (between(0, numerA, denom) && between(0, numerB, denom)) { + fT[0][0] = numerA / denom; + fT[1][0] = numerB / denom; + computePoints(a, 1); + } + } +/* Allow tracking that both sets of end points are near each other -- the lines are entirely + coincident -- even when the end points are not exactly the same. + Mark this as a 'wild card' for the end points, so that either point is considered totally + coincident. Then, avoid folding the lines over each other, but allow either end to mate + to the next set of lines. + */ + if (fAllowNear || !unparallel) { + double aNearB[2]; + double bNearA[2]; + bool aNotB[2] = {false, false}; + bool bNotA[2] = {false, false}; + int nearCount = 0; + for (int index = 0; index < 2; ++index) { + aNearB[index] = t = b.nearPoint(a[index], &aNotB[index]); + nearCount += t >= 0; + bNearA[index] = t = a.nearPoint(b[index], &bNotA[index]); + nearCount += t >= 0; + } + if (nearCount > 0) { + // Skip if each segment contributes to one end point. + if (nearCount != 2 || aNotB[0] == aNotB[1]) { + for (int iA = 0; iA < 2; ++iA) { + if (!aNotB[iA]) { + continue; + } + int nearer = aNearB[iA] > 0.5; + if (!bNotA[nearer]) { + continue; + } + SkASSERT(a[iA] != b[nearer]); + SkASSERT(iA == (bNearA[nearer] > 0.5)); + insertNear(iA, nearer, a[iA], b[nearer]); + aNearB[iA] = -1; + bNearA[nearer] = -1; + nearCount -= 2; + } + } + if (nearCount > 0) { + for (int iA = 0; iA < 2; ++iA) { + if (aNearB[iA] >= 0) { + insert(iA, aNearB[iA], a[iA]); + } + } + for (int iB = 0; iB < 2; ++iB) { + if (bNearA[iB] >= 0) { + insert(bNearA[iB], iB, b[iB]); + } + } + } + } + } + cleanUpParallelLines(!unparallel); + SkASSERT(fUsed <= 2); + return fUsed; +} + +static int horizontal_coincident(const SkDLine& line, double y) { + double min = line[0].fY; + double max = line[1].fY; + if (min > max) { + SkTSwap(min, max); + } + if (min > y || max < y) { + return 0; + } + if (AlmostEqualUlps(min, max) && max - min < fabs(line[0].fX - line[1].fX)) { + return 2; + } + return 1; +} + +double SkIntersections::HorizontalIntercept(const SkDLine& line, double y) { + return SkPinT((y - line[0].fY) / (line[1].fY - line[0].fY)); +} + +int SkIntersections::horizontal(const SkDLine& line, double left, double right, + double y, bool flipped) { + fMax = 3; // clean up parallel at the end will limit the result to 2 at the most + // see if end points intersect the opposite line + double t; + const SkDPoint leftPt = { left, y }; + if ((t = line.exactPoint(leftPt)) >= 0) { + insert(t, (double) flipped, leftPt); + } + if (left != right) { + const SkDPoint rightPt = { right, y }; + if ((t = line.exactPoint(rightPt)) >= 0) { + insert(t, (double) !flipped, rightPt); + } + for (int index = 0; index < 2; ++index) { + if ((t = SkDLine::ExactPointH(line[index], left, right, y)) >= 0) { + insert((double) index, flipped ? 1 - t : t, line[index]); + } + } + } + int result = horizontal_coincident(line, y); + if (result == 1 && fUsed == 0) { + fT[0][0] = HorizontalIntercept(line, y); + double xIntercept = line[0].fX + fT[0][0] * (line[1].fX - line[0].fX); + if (between(left, xIntercept, right)) { + fT[1][0] = (xIntercept - left) / (right - left); + if (flipped) { + // OPTIMIZATION: ? instead of swapping, pass original line, use [1].fX - [0].fX + for (int index = 0; index < result; ++index) { + fT[1][index] = 1 - fT[1][index]; + } + } + fPt[0].fX = xIntercept; + fPt[0].fY = y; + fUsed = 1; + } + } + if (fAllowNear || result == 2) { + if ((t = line.nearPoint(leftPt, nullptr)) >= 0) { + insert(t, (double) flipped, leftPt); + } + if (left != right) { + const SkDPoint rightPt = { right, y }; + if ((t = line.nearPoint(rightPt, nullptr)) >= 0) { + insert(t, (double) !flipped, rightPt); + } + for (int index = 0; index < 2; ++index) { + if ((t = SkDLine::NearPointH(line[index], left, right, y)) >= 0) { + insert((double) index, flipped ? 1 - t : t, line[index]); + } + } + } + } + cleanUpParallelLines(result == 2); + return fUsed; +} + +static int vertical_coincident(const SkDLine& line, double x) { + double min = line[0].fX; + double max = line[1].fX; + if (min > max) { + SkTSwap(min, max); + } + if (!precisely_between(min, x, max)) { + return 0; + } + if (AlmostEqualUlps(min, max)) { + return 2; + } + return 1; +} + +double SkIntersections::VerticalIntercept(const SkDLine& line, double x) { + return SkPinT((x - line[0].fX) / (line[1].fX - line[0].fX)); +} + +int SkIntersections::vertical(const SkDLine& line, double top, double bottom, + double x, bool flipped) { + fMax = 3; // cleanup parallel lines will bring this back line + // see if end points intersect the opposite line + double t; + SkDPoint topPt = { x, top }; + if ((t = line.exactPoint(topPt)) >= 0) { + insert(t, (double) flipped, topPt); + } + if (top != bottom) { + SkDPoint bottomPt = { x, bottom }; + if ((t = line.exactPoint(bottomPt)) >= 0) { + insert(t, (double) !flipped, bottomPt); + } + for (int index = 0; index < 2; ++index) { + if ((t = SkDLine::ExactPointV(line[index], top, bottom, x)) >= 0) { + insert((double) index, flipped ? 1 - t : t, line[index]); + } + } + } + int result = vertical_coincident(line, x); + if (result == 1 && fUsed == 0) { + fT[0][0] = VerticalIntercept(line, x); + double yIntercept = line[0].fY + fT[0][0] * (line[1].fY - line[0].fY); + if (between(top, yIntercept, bottom)) { + fT[1][0] = (yIntercept - top) / (bottom - top); + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].fY - [0].fY + for (int index = 0; index < result; ++index) { + fT[1][index] = 1 - fT[1][index]; + } + } + fPt[0].fX = x; + fPt[0].fY = yIntercept; + fUsed = 1; + } + } + if (fAllowNear || result == 2) { + if ((t = line.nearPoint(topPt, nullptr)) >= 0) { + insert(t, (double) flipped, topPt); + } + if (top != bottom) { + SkDPoint bottomPt = { x, bottom }; + if ((t = line.nearPoint(bottomPt, nullptr)) >= 0) { + insert(t, (double) !flipped, bottomPt); + } + for (int index = 0; index < 2; ++index) { + if ((t = SkDLine::NearPointV(line[index], top, bottom, x)) >= 0) { + insert((double) index, flipped ? 1 - t : t, line[index]); + } + } + } + } + cleanUpParallelLines(result == 2); + SkASSERT(fUsed <= 2); + return fUsed; +} + diff --git a/gfx/skia/skia/src/pathops/SkDQuadLineIntersection.cpp b/gfx/skia/skia/src/pathops/SkDQuadLineIntersection.cpp new file mode 100644 index 000000000..8d5baf694 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkDQuadLineIntersection.cpp @@ -0,0 +1,470 @@ +/* + * 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 "SkIntersections.h" +#include "SkPathOpsCurve.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" + +/* +Find the interection of a line and quadratic by solving for valid t values. + +From http://stackoverflow.com/questions/1853637/how-to-find-the-mathematical-function-defining-a-bezier-curve + +"A Bezier curve is a parametric function. A quadratic Bezier curve (i.e. three +control points) can be expressed as: F(t) = A(1 - t)^2 + B(1 - t)t + Ct^2 where +A, B and C are points and t goes from zero to one. + +This will give you two equations: + + x = a(1 - t)^2 + b(1 - t)t + ct^2 + y = d(1 - t)^2 + e(1 - t)t + ft^2 + +If you add for instance the line equation (y = kx + m) to that, you'll end up +with three equations and three unknowns (x, y and t)." + +Similar to above, the quadratic is represented as + x = a(1-t)^2 + 2b(1-t)t + ct^2 + y = d(1-t)^2 + 2e(1-t)t + ft^2 +and the line as + y = g*x + h + +Using Mathematica, solve for the values of t where the quadratic intersects the +line: + + (in) t1 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - x, + d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - g*x - h, x] + (out) -d + h + 2 d t - 2 e t - d t^2 + 2 e t^2 - f t^2 + + g (a - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2) + (in) Solve[t1 == 0, t] + (out) { + {t -> (-2 d + 2 e + 2 a g - 2 b g - + Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 - + 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) / + (2 (-d + 2 e - f + a g - 2 b g + c g)) + }, + {t -> (-2 d + 2 e + 2 a g - 2 b g + + Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 - + 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) / + (2 (-d + 2 e - f + a g - 2 b g + c g)) + } + } + +Using the results above (when the line tends towards horizontal) + A = (-(d - 2*e + f) + g*(a - 2*b + c) ) + B = 2*( (d - e ) - g*(a - b ) ) + C = (-(d ) + g*(a ) + h ) + +If g goes to infinity, we can rewrite the line in terms of x. + x = g'*y + h' + +And solve accordingly in Mathematica: + + (in) t2 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - g'*y - h', + d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - y, y] + (out) a - h' - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2 - + g' (d - 2 d t + 2 e t + d t^2 - 2 e t^2 + f t^2) + (in) Solve[t2 == 0, t] + (out) { + {t -> (2 a - 2 b - 2 d g' + 2 e g' - + Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 - + 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')]) / + (2 (a - 2 b + c - d g' + 2 e g' - f g')) + }, + {t -> (2 a - 2 b - 2 d g' + 2 e g' + + Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 - + 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')])/ + (2 (a - 2 b + c - d g' + 2 e g' - f g')) + } + } + +Thus, if the slope of the line tends towards vertical, we use: + A = ( (a - 2*b + c) - g'*(d - 2*e + f) ) + B = 2*(-(a - b ) + g'*(d - e ) ) + C = ( (a ) - g'*(d ) - h' ) + */ + +class LineQuadraticIntersections { +public: + enum PinTPoint { + kPointUninitialized, + kPointInitialized + }; + + LineQuadraticIntersections(const SkDQuad& q, const SkDLine& l, SkIntersections* i) + : fQuad(q) + , fLine(&l) + , fIntersections(i) + , fAllowNear(true) { + i->setMax(5); // allow short partial coincidence plus discrete intersections + } + + LineQuadraticIntersections(const SkDQuad& q) + : fQuad(q) + SkDEBUGPARAMS(fLine(nullptr)) + SkDEBUGPARAMS(fIntersections(nullptr)) + SkDEBUGPARAMS(fAllowNear(false)) { + } + + void allowNear(bool allow) { + fAllowNear = allow; + } + + void checkCoincident() { + int last = fIntersections->used() - 1; + for (int index = 0; index < last; ) { + double quadMidT = ((*fIntersections)[0][index] + (*fIntersections)[0][index + 1]) / 2; + SkDPoint quadMidPt = fQuad.ptAtT(quadMidT); + double t = fLine->nearPoint(quadMidPt, nullptr); + if (t < 0) { + ++index; + continue; + } + if (fIntersections->isCoincident(index)) { + fIntersections->removeOne(index); + --last; + } else if (fIntersections->isCoincident(index + 1)) { + fIntersections->removeOne(index + 1); + --last; + } else { + fIntersections->setCoincident(index++); + } + fIntersections->setCoincident(index); + } + } + + int intersectRay(double roots[2]) { + /* + solve by rotating line+quad so line is horizontal, then finding the roots + set up matrix to rotate quad to x-axis + |cos(a) -sin(a)| + |sin(a) cos(a)| + note that cos(a) = A(djacent) / Hypoteneuse + sin(a) = O(pposite) / Hypoteneuse + since we are computing Ts, we can ignore hypoteneuse, the scale factor: + | A -O | + | O A | + A = line[1].fX - line[0].fX (adjacent side of the right triangle) + O = line[1].fY - line[0].fY (opposite side of the right triangle) + for each of the three points (e.g. n = 0 to 2) + quad[n].fY' = (quad[n].fY - line[0].fY) * A - (quad[n].fX - line[0].fX) * O + */ + double adj = (*fLine)[1].fX - (*fLine)[0].fX; + double opp = (*fLine)[1].fY - (*fLine)[0].fY; + double r[3]; + for (int n = 0; n < 3; ++n) { + r[n] = (fQuad[n].fY - (*fLine)[0].fY) * adj - (fQuad[n].fX - (*fLine)[0].fX) * opp; + } + double A = r[2]; + double B = r[1]; + double C = r[0]; + A += C - 2 * B; // A = a - 2*b + c + B -= C; // B = -(b - c) + return SkDQuad::RootsValidT(A, 2 * B, C, roots); + } + + int intersect() { + addExactEndPoints(); + if (fAllowNear) { + addNearEndPoints(); + } + double rootVals[2]; + int roots = intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + double lineT = findLineT(quadT); + SkDPoint pt; + if (pinTs(&quadT, &lineT, &pt, kPointUninitialized) && uniqueAnswer(quadT, pt)) { + fIntersections->insert(quadT, lineT, pt); + } + } + checkCoincident(); + return fIntersections->used(); + } + + int horizontalIntersect(double axisIntercept, double roots[2]) { + double D = fQuad[2].fY; // f + double E = fQuad[1].fY; // e + double F = fQuad[0].fY; // d + D += F - 2 * E; // D = d - 2*e + f + E -= F; // E = -(d - e) + F -= axisIntercept; + return SkDQuad::RootsValidT(D, 2 * E, F, roots); + } + + int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { + addExactHorizontalEndPoints(left, right, axisIntercept); + if (fAllowNear) { + addNearHorizontalEndPoints(left, right, axisIntercept); + } + double rootVals[2]; + int roots = horizontalIntersect(axisIntercept, rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + SkDPoint pt = fQuad.ptAtT(quadT); + double lineT = (pt.fX - left) / (right - left); + if (pinTs(&quadT, &lineT, &pt, kPointInitialized) && uniqueAnswer(quadT, pt)) { + fIntersections->insert(quadT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + checkCoincident(); + return fIntersections->used(); + } + + bool uniqueAnswer(double quadT, const SkDPoint& pt) { + for (int inner = 0; inner < fIntersections->used(); ++inner) { + if (fIntersections->pt(inner) != pt) { + continue; + } + double existingQuadT = (*fIntersections)[0][inner]; + if (quadT == existingQuadT) { + return false; + } + // check if midway on quad is also same point. If so, discard this + double quadMidT = (existingQuadT + quadT) / 2; + SkDPoint quadMidPt = fQuad.ptAtT(quadMidT); + if (quadMidPt.approximatelyEqual(pt)) { + return false; + } + } +#if ONE_OFF_DEBUG + SkDPoint qPt = fQuad.ptAtT(quadT); + SkDebugf("%s pt=(%1.9g,%1.9g) cPt=(%1.9g,%1.9g)\n", __FUNCTION__, pt.fX, pt.fY, + qPt.fX, qPt.fY); +#endif + return true; + } + + int verticalIntersect(double axisIntercept, double roots[2]) { + double D = fQuad[2].fX; // f + double E = fQuad[1].fX; // e + double F = fQuad[0].fX; // d + D += F - 2 * E; // D = d - 2*e + f + E -= F; // E = -(d - e) + F -= axisIntercept; + return SkDQuad::RootsValidT(D, 2 * E, F, roots); + } + + int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { + addExactVerticalEndPoints(top, bottom, axisIntercept); + if (fAllowNear) { + addNearVerticalEndPoints(top, bottom, axisIntercept); + } + double rootVals[2]; + int roots = verticalIntersect(axisIntercept, rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + SkDPoint pt = fQuad.ptAtT(quadT); + double lineT = (pt.fY - top) / (bottom - top); + if (pinTs(&quadT, &lineT, &pt, kPointInitialized) && uniqueAnswer(quadT, pt)) { + fIntersections->insert(quadT, lineT, pt); + } + } + if (flipped) { + fIntersections->flip(); + } + checkCoincident(); + return fIntersections->used(); + } + +protected: + // add endpoints first to get zero and one t values exactly + void addExactEndPoints() { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double lineT = fLine->exactPoint(fQuad[qIndex]); + if (lineT < 0) { + continue; + } + double quadT = (double) (qIndex >> 1); + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + } + + void addNearEndPoints() { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double quadT = (double) (qIndex >> 1); + if (fIntersections->hasT(quadT)) { + continue; + } + double lineT = fLine->nearPoint(fQuad[qIndex], nullptr); + if (lineT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + this->addLineNearEndPoints(); + } + + void addLineNearEndPoints() { + for (int lIndex = 0; lIndex < 2; ++lIndex) { + double lineT = (double) lIndex; + if (fIntersections->hasOppT(lineT)) { + continue; + } + double quadT = ((SkDCurve*) &fQuad)->nearPoint(SkPath::kQuad_Verb, + (*fLine)[lIndex], (*fLine)[!lIndex]); + if (quadT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, (*fLine)[lIndex]); + } + } + + void addExactHorizontalEndPoints(double left, double right, double y) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double lineT = SkDLine::ExactPointH(fQuad[qIndex], left, right, y); + if (lineT < 0) { + continue; + } + double quadT = (double) (qIndex >> 1); + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + } + + void addNearHorizontalEndPoints(double left, double right, double y) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double quadT = (double) (qIndex >> 1); + if (fIntersections->hasT(quadT)) { + continue; + } + double lineT = SkDLine::NearPointH(fQuad[qIndex], left, right, y); + if (lineT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + this->addLineNearEndPoints(); + } + + void addExactVerticalEndPoints(double top, double bottom, double x) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double lineT = SkDLine::ExactPointV(fQuad[qIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + double quadT = (double) (qIndex >> 1); + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + } + + void addNearVerticalEndPoints(double top, double bottom, double x) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + double quadT = (double) (qIndex >> 1); + if (fIntersections->hasT(quadT)) { + continue; + } + double lineT = SkDLine::NearPointV(fQuad[qIndex], top, bottom, x); + if (lineT < 0) { + continue; + } + fIntersections->insert(quadT, lineT, fQuad[qIndex]); + } + this->addLineNearEndPoints(); + } + + double findLineT(double t) { + SkDPoint xy = fQuad.ptAtT(t); + double dx = (*fLine)[1].fX - (*fLine)[0].fX; + double dy = (*fLine)[1].fY - (*fLine)[0].fY; + if (fabs(dx) > fabs(dy)) { + return (xy.fX - (*fLine)[0].fX) / dx; + } + return (xy.fY - (*fLine)[0].fY) / dy; + } + + bool pinTs(double* quadT, double* lineT, SkDPoint* pt, PinTPoint ptSet) { + if (!approximately_one_or_less_double(*lineT)) { + return false; + } + if (!approximately_zero_or_more_double(*lineT)) { + return false; + } + double qT = *quadT = SkPinT(*quadT); + double lT = *lineT = SkPinT(*lineT); + if (lT == 0 || lT == 1 || (ptSet == kPointUninitialized && qT != 0 && qT != 1)) { + *pt = (*fLine).ptAtT(lT); + } else if (ptSet == kPointUninitialized) { + *pt = fQuad.ptAtT(qT); + } + SkPoint gridPt = pt->asSkPoint(); + if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[0].asSkPoint())) { + *pt = (*fLine)[0]; + *lineT = 0; + } else if (SkDPoint::ApproximatelyEqual(gridPt, (*fLine)[1].asSkPoint())) { + *pt = (*fLine)[1]; + *lineT = 1; + } + if (fIntersections->used() > 0 && approximately_equal((*fIntersections)[1][0], *lineT)) { + return false; + } + if (gridPt == fQuad[0].asSkPoint()) { + *pt = fQuad[0]; + *quadT = 0; + } else if (gridPt == fQuad[2].asSkPoint()) { + *pt = fQuad[2]; + *quadT = 1; + } + return true; + } + +private: + const SkDQuad& fQuad; + const SkDLine* fLine; + SkIntersections* fIntersections; + bool fAllowNear; +}; + +int SkIntersections::horizontal(const SkDQuad& quad, double left, double right, double y, + bool flipped) { + SkDLine line = {{{ left, y }, { right, y }}}; + LineQuadraticIntersections q(quad, line, this); + return q.horizontalIntersect(y, left, right, flipped); +} + +int SkIntersections::vertical(const SkDQuad& quad, double top, double bottom, double x, + bool flipped) { + SkDLine line = {{{ x, top }, { x, bottom }}}; + LineQuadraticIntersections q(quad, line, this); + return q.verticalIntersect(x, top, bottom, flipped); +} + +int SkIntersections::intersect(const SkDQuad& quad, const SkDLine& line) { + LineQuadraticIntersections q(quad, line, this); + q.allowNear(fAllowNear); + return q.intersect(); +} + +int SkIntersections::intersectRay(const SkDQuad& quad, const SkDLine& line) { + LineQuadraticIntersections q(quad, line, this); + fUsed = q.intersectRay(fT[0]); + for (int index = 0; index < fUsed; ++index) { + fPt[index] = quad.ptAtT(fT[0][index]); + } + return fUsed; +} + +int SkIntersections::HorizontalIntercept(const SkDQuad& quad, SkScalar y, double* roots) { + LineQuadraticIntersections q(quad); + return q.horizontalIntersect(y, roots); +} + +int SkIntersections::VerticalIntercept(const SkDQuad& quad, SkScalar x, double* roots) { + LineQuadraticIntersections q(quad); + return q.verticalIntersect(x, roots); +} + +// SkDQuad accessors to Intersection utilities + +int SkDQuad::horizontalIntersect(double yIntercept, double roots[2]) const { + return SkIntersections::HorizontalIntercept(*this, yIntercept, roots); +} + +int SkDQuad::verticalIntersect(double xIntercept, double roots[2]) const { + return SkIntersections::VerticalIntercept(*this, xIntercept, roots); +} diff --git a/gfx/skia/skia/src/pathops/SkIntersectionHelper.h b/gfx/skia/skia/src/pathops/SkIntersectionHelper.h new file mode 100644 index 000000000..9a8a582af --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkIntersectionHelper.h @@ -0,0 +1,113 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkIntersectionHelper_DEFINED +#define SkIntersectionHelper_DEFINED + +#include "SkOpContour.h" +#include "SkOpSegment.h" +#include "SkPath.h" + +#ifdef SK_DEBUG +#include "SkPathOpsPoint.h" +#endif + +class SkIntersectionHelper { +public: + enum SegmentType { + kHorizontalLine_Segment = -1, + kVerticalLine_Segment = 0, + kLine_Segment = SkPath::kLine_Verb, + kQuad_Segment = SkPath::kQuad_Verb, + kConic_Segment = SkPath::kConic_Verb, + kCubic_Segment = SkPath::kCubic_Verb, + }; + + bool advance() { + fSegment = fSegment->next(); + return fSegment != nullptr; + } + + SkScalar bottom() const { + return bounds().fBottom; + } + + const SkPathOpsBounds& bounds() const { + return fSegment->bounds(); + } + + SkOpContour* contour() const { + return fSegment->contour(); + } + + void init(SkOpContour* contour) { + fSegment = contour->first(); + } + + SkScalar left() const { + return bounds().fLeft; + } + + const SkPoint* pts() const { + return fSegment->pts(); + } + + SkScalar right() const { + return bounds().fRight; + } + + SkOpSegment* segment() const { + return fSegment; + } + + SegmentType segmentType() const { + SegmentType type = (SegmentType) fSegment->verb(); + if (type != kLine_Segment) { + return type; + } + if (fSegment->isHorizontal()) { + return kHorizontalLine_Segment; + } + if (fSegment->isVertical()) { + return kVerticalLine_Segment; + } + return kLine_Segment; + } + + bool startAfter(const SkIntersectionHelper& after) { + fSegment = after.fSegment->next(); + return fSegment != nullptr; + } + + SkScalar top() const { + return bounds().fTop; + } + + SkScalar weight() const { + return fSegment->weight(); + } + + SkScalar x() const { + return bounds().fLeft; + } + + bool xFlipped() const { + return x() != pts()[0].fX; + } + + SkScalar y() const { + return bounds().fTop; + } + + bool yFlipped() const { + return y() != pts()[0].fY; + } + +private: + SkOpSegment* fSegment; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkIntersections.cpp b/gfx/skia/skia/src/pathops/SkIntersections.cpp new file mode 100644 index 000000000..9683796a5 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkIntersections.cpp @@ -0,0 +1,160 @@ +/* + * 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 "SkIntersections.h" + +int SkIntersections::closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, + double* closestDist) const { + int closest = -1; + *closestDist = SK_ScalarMax; + for (int index = 0; index < fUsed; ++index) { + if (!between(rangeStart, fT[0][index], rangeEnd)) { + continue; + } + const SkDPoint& iPt = fPt[index]; + double dist = testPt.distanceSquared(iPt); + if (*closestDist > dist) { + *closestDist = dist; + closest = index; + } + } + return closest; +} + +void SkIntersections::flip() { + for (int index = 0; index < fUsed; ++index) { + fT[1][index] = 1 - fT[1][index]; + } +} + +int SkIntersections::insert(double one, double two, const SkDPoint& pt) { + if (fIsCoincident[0] == 3 && between(fT[0][0], one, fT[0][1])) { + // For now, don't allow a mix of coincident and non-coincident intersections + return -1; + } + SkASSERT(fUsed <= 1 || fT[0][0] <= fT[0][1]); + int index; + for (index = 0; index < fUsed; ++index) { + double oldOne = fT[0][index]; + double oldTwo = fT[1][index]; + if (one == oldOne && two == oldTwo) { + return -1; + } + if (more_roughly_equal(oldOne, one) && more_roughly_equal(oldTwo, two)) { + if ((precisely_zero(one) && !precisely_zero(oldOne)) + || (precisely_equal(one, 1) && !precisely_equal(oldOne, 1)) + || (precisely_zero(two) && !precisely_zero(oldTwo)) + || (precisely_equal(two, 1) && !precisely_equal(oldTwo, 1))) { + SkASSERT(one >= 0 && one <= 1); + SkASSERT(two >= 0 && two <= 1); + fT[0][index] = one; + fT[1][index] = two; + fPt[index] = pt; + } + return -1; + } + #if ONE_OFF_DEBUG + if (pt.roughlyEqual(fPt[index])) { + SkDebugf("%s t=%1.9g pts roughly equal\n", __FUNCTION__, one); + } + #endif + if (fT[0][index] > one) { + break; + } + } + if (fUsed >= fMax) { + SkASSERT(0); // FIXME : this error, if it is to be handled at runtime in release, must + // be propagated all the way back down to the caller, and return failure. + fUsed = 0; + return 0; + } + int remaining = fUsed - index; + if (remaining > 0) { + memmove(&fPt[index + 1], &fPt[index], sizeof(fPt[0]) * remaining); + memmove(&fT[0][index + 1], &fT[0][index], sizeof(fT[0][0]) * remaining); + memmove(&fT[1][index + 1], &fT[1][index], sizeof(fT[1][0]) * remaining); + int clearMask = ~((1 << index) - 1); + fIsCoincident[0] += fIsCoincident[0] & clearMask; + fIsCoincident[1] += fIsCoincident[1] & clearMask; + } + fPt[index] = pt; + SkASSERT(one >= 0 && one <= 1); + SkASSERT(two >= 0 && two <= 1); + fT[0][index] = one; + fT[1][index] = two; + ++fUsed; + SkASSERT(fUsed <= SK_ARRAY_COUNT(fPt)); + return index; +} + +void SkIntersections::insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2) { + SkASSERT(one == 0 || one == 1); + SkASSERT(two == 0 || two == 1); + SkASSERT(pt1 != pt2); + fNearlySame[one ? 1 : 0] = true; + (void) insert(one, two, pt1); + fPt2[one ? 1 : 0] = pt2; +} + +int SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) { + int index = insertSwap(one, two, pt); + if (index >= 0) { + setCoincident(index); + } + return index; +} + +void SkIntersections::setCoincident(int index) { + SkASSERT(index >= 0); + int bit = 1 << index; + fIsCoincident[0] |= bit; + fIsCoincident[1] |= bit; +} + +void SkIntersections::merge(const SkIntersections& a, int aIndex, const SkIntersections& b, + int bIndex) { + this->reset(); + fT[0][0] = a.fT[0][aIndex]; + fT[1][0] = b.fT[0][bIndex]; + fPt[0] = a.fPt[aIndex]; + fPt2[0] = b.fPt[bIndex]; + fUsed = 1; +} + +int SkIntersections::mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const { + int result = -1; + for (int index = 0; index < fUsed; ++index) { + if (!between(rangeStart, fT[0][index], rangeEnd)) { + continue; + } + if (result < 0) { + result = index; + continue; + } + SkDVector best = fPt[result] - origin; + SkDVector test = fPt[index] - origin; + if (test.crossCheck(best) < 0) { + result = index; + } + } + return result; +} + +void SkIntersections::removeOne(int index) { + int remaining = --fUsed - index; + if (remaining <= 0) { + return; + } + memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining); + memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining); + memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining); +// SkASSERT(fIsCoincident[0] == 0); + int coBit = fIsCoincident[0] & (1 << index); + fIsCoincident[0] -= ((fIsCoincident[0] >> 1) & ~((1 << index) - 1)) + coBit; + SkASSERT(!(coBit ^ (fIsCoincident[1] & (1 << index)))); + fIsCoincident[1] -= ((fIsCoincident[1] >> 1) & ~((1 << index) - 1)) + coBit; +} diff --git a/gfx/skia/skia/src/pathops/SkIntersections.h b/gfx/skia/skia/src/pathops/SkIntersections.h new file mode 100644 index 000000000..abc10e19d --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkIntersections.h @@ -0,0 +1,329 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkIntersections_DEFINE +#define SkIntersections_DEFINE + +#include "SkPathOpsConic.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsPoint.h" +#include "SkPathOpsQuad.h" + +class SkIntersections { +public: + SkIntersections(SkDEBUGCODE(SkOpGlobalState* globalState = nullptr)) + : fSwap(0) +#ifdef SK_DEBUG + SkDEBUGPARAMS(fDebugGlobalState(globalState)) + , fDepth(0) +#endif + { + sk_bzero(fPt, sizeof(fPt)); + sk_bzero(fPt2, sizeof(fPt2)); + sk_bzero(fT, sizeof(fT)); + sk_bzero(fNearlySame, sizeof(fNearlySame)); +#if DEBUG_T_SECT_LOOP_COUNT + sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); +#endif + reset(); + fMax = 0; // require that the caller set the max + } + + class TArray { + public: + explicit TArray(const double ts[10]) : fTArray(ts) {} + double operator[](int n) const { + return fTArray[n]; + } + const double* fTArray; + }; + TArray operator[](int n) const { return TArray(fT[n]); } + + void allowNear(bool nearAllowed) { + fAllowNear = nearAllowed; + } + + void clearCoincidence(int index) { + SkASSERT(index >= 0); + int bit = 1 << index; + fIsCoincident[0] &= ~bit; + fIsCoincident[1] &= ~bit; + } + + int conicHorizontal(const SkPoint a[3], SkScalar weight, SkScalar left, SkScalar right, + SkScalar y, bool flipped) { + SkDConic conic; + conic.set(a, weight); + fMax = 2; + return horizontal(conic, left, right, y, flipped); + } + + int conicVertical(const SkPoint a[3], SkScalar weight, SkScalar top, SkScalar bottom, + SkScalar x, bool flipped) { + SkDConic conic; + conic.set(a, weight); + fMax = 2; + return vertical(conic, top, bottom, x, flipped); + } + + int conicLine(const SkPoint a[3], SkScalar weight, const SkPoint b[2]) { + SkDConic conic; + conic.set(a, weight); + SkDLine line; + line.set(b); + fMax = 3; // 2; permit small coincident segment + non-coincident intersection + return intersect(conic, line); + } + + int cubicHorizontal(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDCubic cubic; + cubic.set(a); + fMax = 3; + return horizontal(cubic, left, right, y, flipped); + } + + int cubicVertical(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDCubic cubic; + cubic.set(a); + fMax = 3; + return vertical(cubic, top, bottom, x, flipped); + } + + int cubicLine(const SkPoint a[4], const SkPoint b[2]) { + SkDCubic cubic; + cubic.set(a); + SkDLine line; + line.set(b); + fMax = 3; + return intersect(cubic, line); + } + +#ifdef SK_DEBUG + SkOpGlobalState* debugGlobalState() { return fDebugGlobalState; } +#endif + + bool hasT(double t) const { + SkASSERT(t == 0 || t == 1); + return fUsed > 0 && (t == 0 ? fT[0][0] == 0 : fT[0][fUsed - 1] == 1); + } + + bool hasOppT(double t) const { + SkASSERT(t == 0 || t == 1); + return fUsed > 0 && (fT[1][0] == t || fT[1][fUsed - 1] == t); + } + + int insertSwap(double one, double two, const SkDPoint& pt) { + if (fSwap) { + return insert(two, one, pt); + } else { + return insert(one, two, pt); + } + } + + bool isCoincident(int index) { + return (fIsCoincident[0] & 1 << index) != 0; + } + + int lineHorizontal(const SkPoint a[2], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDLine line; + line.set(a); + fMax = 2; + return horizontal(line, left, right, y, flipped); + } + + int lineVertical(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDLine line; + line.set(a); + fMax = 2; + return vertical(line, top, bottom, x, flipped); + } + + int lineLine(const SkPoint a[2], const SkPoint b[2]) { + SkDLine aLine, bLine; + aLine.set(a); + bLine.set(b); + fMax = 2; + return intersect(aLine, bLine); + } + + bool nearlySame(int index) const { + SkASSERT(index == 0 || index == 1); + return fNearlySame[index]; + } + + const SkDPoint& pt(int index) const { + return fPt[index]; + } + + const SkDPoint& pt2(int index) const { + return fPt2[index]; + } + + int quadHorizontal(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDQuad quad; + quad.set(a); + fMax = 2; + return horizontal(quad, left, right, y, flipped); + } + + int quadVertical(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDQuad quad; + quad.set(a); + fMax = 2; + return vertical(quad, top, bottom, x, flipped); + } + + int quadLine(const SkPoint a[3], const SkPoint b[2]) { + SkDQuad quad; + quad.set(a); + SkDLine line; + line.set(b); + return intersect(quad, line); + } + + // leaves swap, max alone + void reset() { + fAllowNear = true; + fUsed = 0; + sk_bzero(fIsCoincident, sizeof(fIsCoincident)); + } + + void set(bool swap, int tIndex, double t) { + fT[(int) swap][tIndex] = t; + } + + void setMax(int max) { + SkASSERT(max <= (int) SK_ARRAY_COUNT(fPt)); + fMax = max; + } + + void swap() { + fSwap ^= true; + } + + bool swapped() const { + return fSwap; + } + + int used() const { + return fUsed; + } + + void downDepth() { + SkASSERT(--fDepth >= 0); + } + + bool unBumpT(int index) { + SkASSERT(fUsed == 1); + fT[0][index] = fT[0][index] * (1 + BUMP_EPSILON * 2) - BUMP_EPSILON; + if (!between(0, fT[0][index], 1)) { + fUsed = 0; + return false; + } + return true; + } + + void upDepth() { + SkASSERT(++fDepth < 16); + } + + void alignQuadPts(const SkPoint a[3], const SkPoint b[3]); + int cleanUpCoincidence(); + int closestTo(double rangeStart, double rangeEnd, const SkDPoint& testPt, double* dist) const; + void cubicInsert(double one, double two, const SkDPoint& pt, const SkDCubic& c1, + const SkDCubic& c2); + void flip(); + int horizontal(const SkDLine&, double left, double right, double y, bool flipped); + int horizontal(const SkDQuad&, double left, double right, double y, bool flipped); + int horizontal(const SkDQuad&, double left, double right, double y, double tRange[2]); + int horizontal(const SkDCubic&, double y, double tRange[3]); + int horizontal(const SkDConic&, double left, double right, double y, bool flipped); + int horizontal(const SkDCubic&, double left, double right, double y, bool flipped); + int horizontal(const SkDCubic&, double left, double right, double y, double tRange[3]); + static double HorizontalIntercept(const SkDLine& line, double y); + static int HorizontalIntercept(const SkDQuad& quad, SkScalar y, double* roots); + static int HorizontalIntercept(const SkDConic& conic, SkScalar y, double* roots); + // FIXME : does not respect swap + int insert(double one, double two, const SkDPoint& pt); + void insertNear(double one, double two, const SkDPoint& pt1, const SkDPoint& pt2); + // start if index == 0 : end if index == 1 + int insertCoincident(double one, double two, const SkDPoint& pt); + int intersect(const SkDLine&, const SkDLine&); + int intersect(const SkDQuad&, const SkDLine&); + int intersect(const SkDQuad&, const SkDQuad&); + int intersect(const SkDConic&, const SkDLine&); + int intersect(const SkDConic&, const SkDQuad&); + int intersect(const SkDConic&, const SkDConic&); + int intersect(const SkDCubic&, const SkDLine&); + int intersect(const SkDCubic&, const SkDQuad&); + int intersect(const SkDCubic&, const SkDConic&); + int intersect(const SkDCubic&, const SkDCubic&); + int intersectRay(const SkDLine&, const SkDLine&); + int intersectRay(const SkDQuad&, const SkDLine&); + int intersectRay(const SkDConic&, const SkDLine&); + int intersectRay(const SkDCubic&, const SkDLine&); + void merge(const SkIntersections& , int , const SkIntersections& , int ); + int mostOutside(double rangeStart, double rangeEnd, const SkDPoint& origin) const; + void removeOne(int index); + void setCoincident(int index); + int vertical(const SkDLine&, double top, double bottom, double x, bool flipped); + int vertical(const SkDQuad&, double top, double bottom, double x, bool flipped); + int vertical(const SkDConic&, double top, double bottom, double x, bool flipped); + int vertical(const SkDCubic&, double top, double bottom, double x, bool flipped); + static double VerticalIntercept(const SkDLine& line, double x); + static int VerticalIntercept(const SkDQuad& quad, SkScalar x, double* roots); + static int VerticalIntercept(const SkDConic& conic, SkScalar x, double* roots); + + int depth() const { +#ifdef SK_DEBUG + return fDepth; +#else + return 0; +#endif + } + + enum DebugLoop { + kIterations_DebugLoop, + kCoinCheck_DebugLoop, + kComputePerp_DebugLoop, + }; + + void debugBumpLoopCount(DebugLoop ); + int debugCoincidentUsed() const; + int debugLoopCount(DebugLoop ) const; + void debugResetLoopCount(); + void dump() const; // implemented for testing only + +private: + bool cubicCheckCoincidence(const SkDCubic& c1, const SkDCubic& c2); + bool cubicExactEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2); + void cubicNearEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, const SkDRect& ); + void cleanUpParallelLines(bool parallel); + void computePoints(const SkDLine& line, int used); + + SkDPoint fPt[12]; // FIXME: since scans store points as SkPoint, this should also + SkDPoint fPt2[2]; // used by nearly same to store alternate intersection point + double fT[2][12]; + uint16_t fIsCoincident[2]; // bit set for each curve's coincident T + bool fNearlySame[2]; // true if end points nearly match + unsigned char fUsed; + unsigned char fMax; + bool fAllowNear; + bool fSwap; +#ifdef SK_DEBUG + SkOpGlobalState* fDebugGlobalState; + int fDepth; +#endif +#if DEBUG_T_SECT_LOOP_COUNT + int fDebugLoopCount[3]; +#endif +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkLineParameters.h b/gfx/skia/skia/src/pathops/SkLineParameters.h new file mode 100644 index 000000000..073d03602 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkLineParameters.h @@ -0,0 +1,181 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkLineParameters_DEFINED +#define SkLineParameters_DEFINED + +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" + +// Sources +// computer-aided design - volume 22 number 9 november 1990 pp 538 - 549 +// online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf + +// This turns a line segment into a parameterized line, of the form +// ax + by + c = 0 +// When a^2 + b^2 == 1, the line is normalized. +// The distance to the line for (x, y) is d(x,y) = ax + by + c +// +// Note that the distances below are not necessarily normalized. To get the true +// distance, it's necessary to either call normalize() after xxxEndPoints(), or +// divide the result of xxxDistance() by sqrt(normalSquared()) + +class SkLineParameters { +public: + + bool cubicEndPoints(const SkDCubic& pts) { + int endIndex = 1; + cubicEndPoints(pts, 0, endIndex); + if (dy() != 0) { + return true; + } + if (dx() == 0) { + cubicEndPoints(pts, 0, ++endIndex); + SkASSERT(endIndex == 2); + if (dy() != 0) { + return true; + } + if (dx() == 0) { + cubicEndPoints(pts, 0, ++endIndex); // line + SkASSERT(endIndex == 3); + return false; + } + } + // FIXME: after switching to round sort, remove bumping fA + if (dx() < 0) { // only worry about y bias when breaking cw/ccw tie + return true; + } + // if cubic tangent is on x axis, look at next control point to break tie + // control point may be approximate, so it must move significantly to account for error + if (NotAlmostEqualUlps(pts[0].fY, pts[++endIndex].fY)) { + if (pts[0].fY > pts[endIndex].fY) { + fA = DBL_EPSILON; // push it from 0 to slightly negative (y() returns -a) + } + return true; + } + if (endIndex == 3) { + return true; + } + SkASSERT(endIndex == 2); + if (pts[0].fY > pts[3].fY) { + fA = DBL_EPSILON; // push it from 0 to slightly negative (y() returns -a) + } + return true; + } + + void cubicEndPoints(const SkDCubic& pts, int s, int e) { + fA = pts[s].fY - pts[e].fY; + fB = pts[e].fX - pts[s].fX; + fC = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY; + } + + double cubicPart(const SkDCubic& part) { + cubicEndPoints(part); + if (part[0] == part[1] || ((const SkDLine& ) part[0]).nearRay(part[2])) { + return pointDistance(part[3]); + } + return pointDistance(part[2]); + } + + void lineEndPoints(const SkDLine& pts) { + fA = pts[0].fY - pts[1].fY; + fB = pts[1].fX - pts[0].fX; + fC = pts[0].fX * pts[1].fY - pts[1].fX * pts[0].fY; + } + + bool quadEndPoints(const SkDQuad& pts) { + quadEndPoints(pts, 0, 1); + if (dy() != 0) { + return true; + } + if (dx() == 0) { + quadEndPoints(pts, 0, 2); + return false; + } + if (dx() < 0) { // only worry about y bias when breaking cw/ccw tie + return true; + } + // FIXME: after switching to round sort, remove this + if (pts[0].fY > pts[2].fY) { + fA = DBL_EPSILON; + } + return true; + } + + void quadEndPoints(const SkDQuad& pts, int s, int e) { + fA = pts[s].fY - pts[e].fY; + fB = pts[e].fX - pts[s].fX; + fC = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY; + } + + double quadPart(const SkDQuad& part) { + quadEndPoints(part); + return pointDistance(part[2]); + } + + double normalSquared() const { + return fA * fA + fB * fB; + } + + bool normalize() { + double normal = sqrt(normalSquared()); + if (approximately_zero(normal)) { + fA = fB = fC = 0; + return false; + } + double reciprocal = 1 / normal; + fA *= reciprocal; + fB *= reciprocal; + fC *= reciprocal; + return true; + } + + void cubicDistanceY(const SkDCubic& pts, SkDCubic& distance) const { + double oneThird = 1 / 3.0; + for (int index = 0; index < 4; ++index) { + distance[index].fX = index * oneThird; + distance[index].fY = fA * pts[index].fX + fB * pts[index].fY + fC; + } + } + + void quadDistanceY(const SkDQuad& pts, SkDQuad& distance) const { + double oneHalf = 1 / 2.0; + for (int index = 0; index < 3; ++index) { + distance[index].fX = index * oneHalf; + distance[index].fY = fA * pts[index].fX + fB * pts[index].fY + fC; + } + } + + double controlPtDistance(const SkDCubic& pts, int index) const { + SkASSERT(index == 1 || index == 2); + return fA * pts[index].fX + fB * pts[index].fY + fC; + } + + double controlPtDistance(const SkDQuad& pts) const { + return fA * pts[1].fX + fB * pts[1].fY + fC; + } + + double pointDistance(const SkDPoint& pt) const { + return fA * pt.fX + fB * pt.fY + fC; + } + + double dx() const { + return fB; + } + + double dy() const { + return -fA; + } + +private: + double fA; + double fB; + double fC; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpAngle.cpp b/gfx/skia/skia/src/pathops/SkOpAngle.cpp new file mode 100644 index 000000000..820b5dcee --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpAngle.cpp @@ -0,0 +1,995 @@ +/* + * 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 "SkOpAngle.h" +#include "SkOpSegment.h" +#include "SkPathOpsCurve.h" +#include "SkTSort.h" + +/* Angles are sorted counterclockwise. The smallest angle has a positive x and the smallest + positive y. The largest angle has a positive x and a zero y. */ + +#if DEBUG_ANGLE + static bool CompareResult(const char* func, SkString* bugOut, SkString* bugPart, int append, + bool compare) { + SkDebugf("%s %c %d\n", bugOut->c_str(), compare ? 'T' : 'F', append); + SkDebugf("%sPart %s\n", func, bugPart[0].c_str()); + SkDebugf("%sPart %s\n", func, bugPart[1].c_str()); + SkDebugf("%sPart %s\n", func, bugPart[2].c_str()); + return compare; + } + + #define COMPARE_RESULT(append, compare) CompareResult(__FUNCTION__, &bugOut, bugPart, append, \ + compare) +#else + #define COMPARE_RESULT(append, compare) compare +#endif + +/* quarter angle values for sector + +31 x > 0, y == 0 horizontal line (to the right) +0 x > 0, y == epsilon quad/cubic horizontal tangent eventually going +y +1 x > 0, y > 0, x > y nearer horizontal angle +2 x + e == y quad/cubic 45 going horiz +3 x > 0, y > 0, x == y 45 angle +4 x == y + e quad/cubic 45 going vert +5 x > 0, y > 0, x < y nearer vertical angle +6 x == epsilon, y > 0 quad/cubic vertical tangent eventually going +x +7 x == 0, y > 0 vertical line (to the top) + + 8 7 6 + 9 | 5 + 10 | 4 + 11 | 3 + 12 \ | / 2 + 13 | 1 + 14 | 0 + 15 --------------+------------- 31 + 16 | 30 + 17 | 29 + 18 / | \ 28 + 19 | 27 + 20 | 26 + 21 | 25 + 22 23 24 +*/ + +// return true if lh < this < rh +bool SkOpAngle::after(SkOpAngle* test) { + SkOpAngle* lh = test; + SkOpAngle* rh = lh->fNext; + SkASSERT(lh != rh); + fPart.fCurve = fOriginalCurvePart; + lh->fPart.fCurve = lh->fOriginalCurvePart; + lh->fPart.fCurve.offset(lh->segment()->verb(), fPart.fCurve[0] - lh->fPart.fCurve[0]); + rh->fPart.fCurve = rh->fOriginalCurvePart; + rh->fPart.fCurve.offset(rh->segment()->verb(), fPart.fCurve[0] - rh->fPart.fCurve[0]); + +#if DEBUG_ANGLE + SkString bugOut; + bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" + " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" + " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__, + lh->segment()->debugID(), lh->debugID(), lh->fSectorStart, lh->fSectorEnd, + lh->fStart->t(), lh->fEnd->t(), + segment()->debugID(), debugID(), fSectorStart, fSectorEnd, fStart->t(), fEnd->t(), + rh->segment()->debugID(), rh->debugID(), rh->fSectorStart, rh->fSectorEnd, + rh->fStart->t(), rh->fEnd->t()); + SkString bugPart[3] = { lh->debugPart(), this->debugPart(), rh->debugPart() }; +#endif + if (lh->fComputeSector && !lh->computeSector()) { + return COMPARE_RESULT(1, true); + } + if (fComputeSector && !this->computeSector()) { + return COMPARE_RESULT(2, true); + } + if (rh->fComputeSector && !rh->computeSector()) { + return COMPARE_RESULT(3, true); + } +#if DEBUG_ANGLE // reset bugOut with computed sectors + bugOut.printf("%s [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" + " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g" + " < [%d/%d] %d/%d tStart=%1.9g tEnd=%1.9g ", __FUNCTION__, + lh->segment()->debugID(), lh->debugID(), lh->fSectorStart, lh->fSectorEnd, + lh->fStart->t(), lh->fEnd->t(), + segment()->debugID(), debugID(), fSectorStart, fSectorEnd, fStart->t(), fEnd->t(), + rh->segment()->debugID(), rh->debugID(), rh->fSectorStart, rh->fSectorEnd, + rh->fStart->t(), rh->fEnd->t()); +#endif + bool ltrOverlap = (lh->fSectorMask | rh->fSectorMask) & fSectorMask; + bool lrOverlap = lh->fSectorMask & rh->fSectorMask; + int lrOrder; // set to -1 if either order works + if (!lrOverlap) { // no lh/rh sector overlap + if (!ltrOverlap) { // no lh/this/rh sector overlap + return COMPARE_RESULT(4, (lh->fSectorEnd > rh->fSectorStart) + ^ (fSectorStart > lh->fSectorEnd) ^ (fSectorStart > rh->fSectorStart)); + } + int lrGap = (rh->fSectorStart - lh->fSectorStart + 32) & 0x1f; + /* A tiny change can move the start +/- 4. The order can only be determined if + lr gap is not 12 to 20 or -12 to -20. + -31 ..-21 1 + -20 ..-12 -1 + -11 .. -1 0 + 0 shouldn't get here + 11 .. 1 1 + 12 .. 20 -1 + 21 .. 31 0 + */ + lrOrder = lrGap > 20 ? 0 : lrGap > 11 ? -1 : 1; + } else { + lrOrder = (int) lh->orderable(rh); + if (!ltrOverlap) { + return COMPARE_RESULT(5, !lrOrder); + } + } + int ltOrder; + SkASSERT((lh->fSectorMask & fSectorMask) || (rh->fSectorMask & fSectorMask)); + if (lh->fSectorMask & fSectorMask) { + ltOrder = (int) lh->orderable(this); + } else { + int ltGap = (fSectorStart - lh->fSectorStart + 32) & 0x1f; + ltOrder = ltGap > 20 ? 0 : ltGap > 11 ? -1 : 1; + } + int trOrder; + if (rh->fSectorMask & fSectorMask) { + trOrder = (int) orderable(rh); + } else { + int trGap = (rh->fSectorStart - fSectorStart + 32) & 0x1f; + trOrder = trGap > 20 ? 0 : trGap > 11 ? -1 : 1; + } + if (lrOrder >= 0 && ltOrder >= 0 && trOrder >= 0) { + return COMPARE_RESULT(7, lrOrder ? (ltOrder & trOrder) : (ltOrder | trOrder)); + } + SkASSERT(lrOrder >= 0 || ltOrder >= 0 || trOrder >= 0); +// There's not enough information to sort. Get the pairs of angles in opposite planes. +// If an order is < 0, the pair is already in an opposite plane. Check the remaining pairs. + // FIXME : once all variants are understood, rewrite this more simply + if (ltOrder == 0 && lrOrder == 0) { + SkASSERT(trOrder < 0); + // FIXME : once this is verified to work, remove one opposite angle call + SkDEBUGCODE(bool lrOpposite = lh->oppositePlanes(rh)); + bool ltOpposite = lh->oppositePlanes(this); + SkASSERT(lrOpposite != ltOpposite); + return COMPARE_RESULT(8, ltOpposite); + } else if (ltOrder == 1 && trOrder == 0) { + SkASSERT(lrOrder < 0); + bool trOpposite = oppositePlanes(rh); + return COMPARE_RESULT(9, trOpposite); + } else if (lrOrder == 1 && trOrder == 1) { + SkASSERT(ltOrder < 0); + SkDEBUGCODE(bool trOpposite = oppositePlanes(rh)); + bool lrOpposite = lh->oppositePlanes(rh); + SkASSERT(lrOpposite != trOpposite); + return COMPARE_RESULT(10, lrOpposite); + } + if (lrOrder < 0) { + if (ltOrder < 0) { + return COMPARE_RESULT(11, trOrder); + } + return COMPARE_RESULT(12, ltOrder); + } + return COMPARE_RESULT(13, !lrOrder); +} + +// given a line, see if the opposite curve's convex hull is all on one side +// returns -1=not on one side 0=this CW of test 1=this CCW of test +int SkOpAngle::allOnOneSide(const SkOpAngle* test) { + SkASSERT(!fPart.isCurve()); + SkASSERT(test->fPart.isCurve()); + SkDPoint origin = fPart.fCurve[0]; + SkDVector line = fPart.fCurve[1] - origin; + double crosses[3]; + SkPath::Verb testVerb = test->segment()->verb(); + int iMax = SkPathOpsVerbToPoints(testVerb); +// SkASSERT(origin == test.fCurveHalf[0]); + const SkDCurve& testCurve = test->fPart.fCurve; + for (int index = 1; index <= iMax; ++index) { + double xy1 = line.fX * (testCurve[index].fY - origin.fY); + double xy2 = line.fY * (testCurve[index].fX - origin.fX); + crosses[index - 1] = AlmostBequalUlps(xy1, xy2) ? 0 : xy1 - xy2; + } + if (crosses[0] * crosses[1] < 0) { + return -1; + } + if (SkPath::kCubic_Verb == testVerb) { + if (crosses[0] * crosses[2] < 0 || crosses[1] * crosses[2] < 0) { + return -1; + } + } + if (crosses[0]) { + return crosses[0] < 0; + } + if (crosses[1]) { + return crosses[1] < 0; + } + if (SkPath::kCubic_Verb == testVerb && crosses[2]) { + return crosses[2] < 0; + } + fUnorderable = true; + return -1; +} + +bool SkOpAngle::checkCrossesZero() const { + int start = SkTMin(fSectorStart, fSectorEnd); + int end = SkTMax(fSectorStart, fSectorEnd); + bool crossesZero = end - start > 16; + return crossesZero; +} + +bool SkOpAngle::checkParallel(SkOpAngle* rh) { + SkDVector scratch[2]; + const SkDVector* sweep, * tweep; + if (this->fPart.isOrdered()) { + sweep = this->fPart.fSweep; + } else { + scratch[0] = this->fPart.fCurve[1] - this->fPart.fCurve[0]; + sweep = &scratch[0]; + } + if (rh->fPart.isOrdered()) { + tweep = rh->fPart.fSweep; + } else { + scratch[1] = rh->fPart.fCurve[1] - rh->fPart.fCurve[0]; + tweep = &scratch[1]; + } + double s0xt0 = sweep->crossCheck(*tweep); + if (tangentsDiverge(rh, s0xt0)) { + return s0xt0 < 0; + } + // compute the perpendicular to the endpoints and see where it intersects the opposite curve + // if the intersections within the t range, do a cross check on those + bool inside; + if (!fEnd->contains(rh->fEnd)) { + if (this->endToSide(rh, &inside)) { + return inside; + } + if (rh->endToSide(this, &inside)) { + return !inside; + } + } + if (this->midToSide(rh, &inside)) { + return inside; + } + if (rh->midToSide(this, &inside)) { + return !inside; + } + // compute the cross check from the mid T values (last resort) + SkDVector m0 = segment()->dPtAtT(this->midT()) - this->fPart.fCurve[0]; + SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fPart.fCurve[0]; + double m0xm1 = m0.crossCheck(m1); + if (m0xm1 == 0) { + this->fUnorderable = true; + rh->fUnorderable = true; + return true; + } + return m0xm1 < 0; +} + +// the original angle is too short to get meaningful sector information +// lengthen it until it is long enough to be meaningful or leave it unset if lengthening it +// would cause it to intersect one of the adjacent angles +bool SkOpAngle::computeSector() { + if (fComputedSector) { + return !fUnorderable; + } + fComputedSector = true; + bool stepUp = fStart->t() < fEnd->t(); + SkOpSpanBase* checkEnd = fEnd; + if (checkEnd->final() && stepUp) { + fUnorderable = true; + return false; + } + do { +// advance end + const SkOpSegment* other = checkEnd->segment(); + const SkOpSpanBase* oSpan = other->head(); + do { + if (oSpan->segment() != segment()) { + continue; + } + if (oSpan == checkEnd) { + continue; + } + if (!approximately_equal(oSpan->t(), checkEnd->t())) { + continue; + } + goto recomputeSector; + } while (!oSpan->final() && (oSpan = oSpan->upCast()->next())); + checkEnd = stepUp ? !checkEnd->final() + ? checkEnd->upCast()->next() : nullptr + : checkEnd->prev(); + } while (checkEnd); +recomputeSector: + SkOpSpanBase* computedEnd = stepUp ? checkEnd ? checkEnd->prev() : fEnd->segment()->head() + : checkEnd ? checkEnd->upCast()->next() : fEnd->segment()->tail(); + if (checkEnd == fEnd || computedEnd == fEnd || computedEnd == fStart) { + fUnorderable = true; + return false; + } + if (stepUp != (fStart->t() < computedEnd->t())) { + fUnorderable = true; + return false; + } + SkOpSpanBase* saveEnd = fEnd; + fComputedEnd = fEnd = computedEnd; + setSpans(); + setSector(); + fEnd = saveEnd; + return !fUnorderable; +} + +int SkOpAngle::convexHullOverlaps(const SkOpAngle* rh) const { + const SkDVector* sweep = this->fPart.fSweep; + const SkDVector* tweep = rh->fPart.fSweep; + double s0xs1 = sweep[0].crossCheck(sweep[1]); + double s0xt0 = sweep[0].crossCheck(tweep[0]); + double s1xt0 = sweep[1].crossCheck(tweep[0]); + bool tBetweenS = s0xs1 > 0 ? s0xt0 > 0 && s1xt0 < 0 : s0xt0 < 0 && s1xt0 > 0; + double s0xt1 = sweep[0].crossCheck(tweep[1]); + double s1xt1 = sweep[1].crossCheck(tweep[1]); + tBetweenS |= s0xs1 > 0 ? s0xt1 > 0 && s1xt1 < 0 : s0xt1 < 0 && s1xt1 > 0; + double t0xt1 = tweep[0].crossCheck(tweep[1]); + if (tBetweenS) { + return -1; + } + if ((s0xt0 == 0 && s1xt1 == 0) || (s1xt0 == 0 && s0xt1 == 0)) { // s0 to s1 equals t0 to t1 + return -1; + } + bool sBetweenT = t0xt1 > 0 ? s0xt0 < 0 && s0xt1 > 0 : s0xt0 > 0 && s0xt1 < 0; + sBetweenT |= t0xt1 > 0 ? s1xt0 < 0 && s1xt1 > 0 : s1xt0 > 0 && s1xt1 < 0; + if (sBetweenT) { + return -1; + } + // if all of the sweeps are in the same half plane, then the order of any pair is enough + if (s0xt0 >= 0 && s0xt1 >= 0 && s1xt0 >= 0 && s1xt1 >= 0) { + return 0; + } + if (s0xt0 <= 0 && s0xt1 <= 0 && s1xt0 <= 0 && s1xt1 <= 0) { + return 1; + } + // if the outside sweeps are greater than 180 degress: + // first assume the inital tangents are the ordering + // if the midpoint direction matches the inital order, that is enough + SkDVector m0 = this->segment()->dPtAtT(this->midT()) - this->fPart.fCurve[0]; + SkDVector m1 = rh->segment()->dPtAtT(rh->midT()) - rh->fPart.fCurve[0]; + double m0xm1 = m0.crossCheck(m1); + if (s0xt0 > 0 && m0xm1 > 0) { + return 0; + } + if (s0xt0 < 0 && m0xm1 < 0) { + return 1; + } + if (tangentsDiverge(rh, s0xt0)) { + return s0xt0 < 0; + } + return m0xm1 < 0; +} + +// OPTIMIZATION: longest can all be either lazily computed here or precomputed in setup +double SkOpAngle::distEndRatio(double dist) const { + double longest = 0; + const SkOpSegment& segment = *this->segment(); + int ptCount = SkPathOpsVerbToPoints(segment.verb()); + const SkPoint* pts = segment.pts(); + for (int idx1 = 0; idx1 <= ptCount - 1; ++idx1) { + for (int idx2 = idx1 + 1; idx2 <= ptCount; ++idx2) { + if (idx1 == idx2) { + continue; + } + SkDVector v; + v.set(pts[idx2] - pts[idx1]); + double lenSq = v.lengthSquared(); + longest = SkTMax(longest, lenSq); + } + } + return sqrt(longest) / dist; +} + +bool SkOpAngle::endsIntersect(SkOpAngle* rh) { + SkPath::Verb lVerb = this->segment()->verb(); + SkPath::Verb rVerb = rh->segment()->verb(); + int lPts = SkPathOpsVerbToPoints(lVerb); + int rPts = SkPathOpsVerbToPoints(rVerb); + SkDLine rays[] = {{{this->fPart.fCurve[0], rh->fPart.fCurve[rPts]}}, + {{this->fPart.fCurve[0], this->fPart.fCurve[lPts]}}}; + if (this->fEnd->contains(rh->fEnd)) { + return checkParallel(rh); + } + double smallTs[2] = {-1, -1}; + bool limited[2] = {false, false}; + for (int index = 0; index < 2; ++index) { + SkPath::Verb cVerb = index ? rVerb : lVerb; + // if the curve is a line, then the line and the ray intersect only at their crossing + if (cVerb == SkPath::kLine_Verb) { + continue; + } + const SkOpSegment& segment = index ? *rh->segment() : *this->segment(); + SkIntersections i; + (*CurveIntersectRay[cVerb])(segment.pts(), segment.weight(), rays[index], &i); + double tStart = index ? rh->fStart->t() : this->fStart->t(); + double tEnd = index ? rh->fComputedEnd->t() : this->fComputedEnd->t(); + bool testAscends = tStart < (index ? rh->fComputedEnd->t() : this->fComputedEnd->t()); + double t = testAscends ? 0 : 1; + for (int idx2 = 0; idx2 < i.used(); ++idx2) { + double testT = i[0][idx2]; + if (!approximately_between_orderable(tStart, testT, tEnd)) { + continue; + } + if (approximately_equal_orderable(tStart, testT)) { + continue; + } + smallTs[index] = t = testAscends ? SkTMax(t, testT) : SkTMin(t, testT); + limited[index] = approximately_equal_orderable(t, tEnd); + } + } + bool sRayLonger = false; + SkDVector sCept = {0, 0}; + double sCeptT = -1; + int sIndex = -1; + bool useIntersect = false; + for (int index = 0; index < 2; ++index) { + if (smallTs[index] < 0) { + continue; + } + const SkOpSegment& segment = index ? *rh->segment() : *this->segment(); + const SkDPoint& dPt = segment.dPtAtT(smallTs[index]); + SkDVector cept = dPt - rays[index][0]; + // If this point is on the curve, it should have been detected earlier by ordinary + // curve intersection. This may be hard to determine in general, but for lines, + // the point could be close to or equal to its end, but shouldn't be near the start. + if ((index ? lPts : rPts) == 1) { + SkDVector total = rays[index][1] - rays[index][0]; + if (cept.lengthSquared() * 2 < total.lengthSquared()) { + continue; + } + } + SkDVector end = rays[index][1] - rays[index][0]; + if (cept.fX * end.fX < 0 || cept.fY * end.fY < 0) { + continue; + } + double rayDist = cept.length(); + double endDist = end.length(); + bool rayLonger = rayDist > endDist; + if (limited[0] && limited[1] && rayLonger) { + useIntersect = true; + sRayLonger = rayLonger; + sCept = cept; + sCeptT = smallTs[index]; + sIndex = index; + break; + } + double delta = fabs(rayDist - endDist); + double minX, minY, maxX, maxY; + minX = minY = SK_ScalarInfinity; + maxX = maxY = -SK_ScalarInfinity; + const SkDCurve& curve = index ? rh->fPart.fCurve : this->fPart.fCurve; + int ptCount = index ? rPts : lPts; + for (int idx2 = 0; idx2 <= ptCount; ++idx2) { + minX = SkTMin(minX, curve[idx2].fX); + minY = SkTMin(minY, curve[idx2].fY); + maxX = SkTMax(maxX, curve[idx2].fX); + maxY = SkTMax(maxY, curve[idx2].fY); + } + double maxWidth = SkTMax(maxX - minX, maxY - minY); + delta /= maxWidth; + if (delta > 1e-3 && (useIntersect ^= true)) { // FIXME: move this magic number + sRayLonger = rayLonger; + sCept = cept; + sCeptT = smallTs[index]; + sIndex = index; + } + } + if (useIntersect) { + const SkDCurve& curve = sIndex ? rh->fPart.fCurve : this->fPart.fCurve; + const SkOpSegment& segment = sIndex ? *rh->segment() : *this->segment(); + double tStart = sIndex ? rh->fStart->t() : fStart->t(); + SkDVector mid = segment.dPtAtT(tStart + (sCeptT - tStart) / 2) - curve[0]; + double septDir = mid.crossCheck(sCept); + if (!septDir) { + return checkParallel(rh); + } + return sRayLonger ^ (sIndex == 0) ^ (septDir < 0); + } else { + return checkParallel(rh); + } +} + +bool SkOpAngle::endToSide(const SkOpAngle* rh, bool* inside) const { + const SkOpSegment* segment = this->segment(); + SkPath::Verb verb = segment->verb(); + SkDLine rayEnd; + rayEnd[0].set(this->fEnd->pt()); + rayEnd[1] = rayEnd[0]; + SkDVector slopeAtEnd = (*CurveDSlopeAtT[verb])(segment->pts(), segment->weight(), + this->fEnd->t()); + rayEnd[1].fX += slopeAtEnd.fY; + rayEnd[1].fY -= slopeAtEnd.fX; + SkIntersections iEnd; + const SkOpSegment* oppSegment = rh->segment(); + SkPath::Verb oppVerb = oppSegment->verb(); + (*CurveIntersectRay[oppVerb])(oppSegment->pts(), oppSegment->weight(), rayEnd, &iEnd); + double endDist; + int closestEnd = iEnd.closestTo(rh->fStart->t(), rh->fEnd->t(), rayEnd[0], &endDist); + if (closestEnd < 0) { + return false; + } + if (!endDist) { + return false; + } + SkDPoint start; + start.set(this->fStart->pt()); + // OPTIMIZATION: multiple times in the code we find the max scalar + double minX, minY, maxX, maxY; + minX = minY = SK_ScalarInfinity; + maxX = maxY = -SK_ScalarInfinity; + const SkDCurve& curve = rh->fPart.fCurve; + int oppPts = SkPathOpsVerbToPoints(oppVerb); + for (int idx2 = 0; idx2 <= oppPts; ++idx2) { + minX = SkTMin(minX, curve[idx2].fX); + minY = SkTMin(minY, curve[idx2].fY); + maxX = SkTMax(maxX, curve[idx2].fX); + maxY = SkTMax(maxY, curve[idx2].fY); + } + double maxWidth = SkTMax(maxX - minX, maxY - minY); + endDist /= maxWidth; + if (endDist < 5e-12) { // empirically found + return false; + } + const SkDPoint* endPt = &rayEnd[0]; + SkDPoint oppPt = iEnd.pt(closestEnd); + SkDVector vLeft = *endPt - start; + SkDVector vRight = oppPt - start; + double dir = vLeft.crossNoNormalCheck(vRight); + if (!dir) { + return false; + } + *inside = dir < 0; + return true; +} + +/* y<0 y==0 y>0 x<0 x==0 x>0 xy<0 xy==0 xy>0 + 0 x x x + 1 x x x + 2 x x x + 3 x x x + 4 x x x + 5 x x x + 6 x x x + 7 x x x + 8 x x x + 9 x x x + 10 x x x + 11 x x x + 12 x x x + 13 x x x + 14 x x x + 15 x x x +*/ +int SkOpAngle::findSector(SkPath::Verb verb, double x, double y) const { + double absX = fabs(x); + double absY = fabs(y); + double xy = SkPath::kLine_Verb == verb || !AlmostEqualUlps(absX, absY) ? absX - absY : 0; + // If there are four quadrants and eight octants, and since the Latin for sixteen is sedecim, + // one could coin the term sedecimant for a space divided into 16 sections. + // http://english.stackexchange.com/questions/133688/word-for-something-partitioned-into-16-parts + static const int sedecimant[3][3][3] = { + // y<0 y==0 y>0 + // x<0 x==0 x>0 x<0 x==0 x>0 x<0 x==0 x>0 + {{ 4, 3, 2}, { 7, -1, 15}, {10, 11, 12}}, // abs(x) < abs(y) + {{ 5, -1, 1}, {-1, -1, -1}, { 9, -1, 13}}, // abs(x) == abs(y) + {{ 6, 3, 0}, { 7, -1, 15}, { 8, 11, 14}}, // abs(x) > abs(y) + }; + int sector = sedecimant[(xy >= 0) + (xy > 0)][(y >= 0) + (y > 0)][(x >= 0) + (x > 0)] * 2 + 1; +// SkASSERT(SkPath::kLine_Verb == verb || sector >= 0); + return sector; +} + +SkOpGlobalState* SkOpAngle::globalState() const { + return this->segment()->globalState(); +} + + +// OPTIMIZE: if this loops to only one other angle, after first compare fails, insert on other side +// OPTIMIZE: return where insertion succeeded. Then, start next insertion on opposite side +void SkOpAngle::insert(SkOpAngle* angle) { + if (angle->fNext) { + if (loopCount() >= angle->loopCount()) { + if (!merge(angle)) { + return; + } + } else if (fNext) { + if (!angle->merge(this)) { + return; + } + } else { + angle->insert(this); + } + return; + } + bool singleton = nullptr == fNext; + if (singleton) { + fNext = this; + } + SkOpAngle* next = fNext; + if (next->fNext == this) { + if (singleton || angle->after(this)) { + this->fNext = angle; + angle->fNext = next; + } else { + next->fNext = angle; + angle->fNext = this; + } + debugValidateNext(); + return; + } + SkOpAngle* last = this; + do { + SkASSERT(last->fNext == next); + if (angle->after(last)) { + last->fNext = angle; + angle->fNext = next; + debugValidateNext(); + return; + } + last = next; + next = next->fNext; + } while (true); +} + +SkOpSpanBase* SkOpAngle::lastMarked() const { + if (fLastMarked) { + if (fLastMarked->chased()) { + return nullptr; + } + fLastMarked->setChased(true); + } + return fLastMarked; +} + +bool SkOpAngle::loopContains(const SkOpAngle* angle) const { + if (!fNext) { + return false; + } + const SkOpAngle* first = this; + const SkOpAngle* loop = this; + const SkOpSegment* tSegment = angle->fStart->segment(); + double tStart = angle->fStart->t(); + double tEnd = angle->fEnd->t(); + do { + const SkOpSegment* lSegment = loop->fStart->segment(); + if (lSegment != tSegment) { + continue; + } + double lStart = loop->fStart->t(); + if (lStart != tEnd) { + continue; + } + double lEnd = loop->fEnd->t(); + if (lEnd == tStart) { + return true; + } + } while ((loop = loop->fNext) != first); + return false; +} + +int SkOpAngle::loopCount() const { + int count = 0; + const SkOpAngle* first = this; + const SkOpAngle* next = this; + do { + next = next->fNext; + ++count; + } while (next && next != first); + return count; +} + +bool SkOpAngle::merge(SkOpAngle* angle) { + SkASSERT(fNext); + SkASSERT(angle->fNext); + SkOpAngle* working = angle; + do { + if (this == working) { + return false; + } + working = working->fNext; + } while (working != angle); + do { + SkOpAngle* next = working->fNext; + working->fNext = nullptr; + insert(working); + working = next; + } while (working != angle); + // it's likely that a pair of the angles are unorderable + debugValidateNext(); + return true; +} + +double SkOpAngle::midT() const { + return (fStart->t() + fEnd->t()) / 2; +} + +bool SkOpAngle::midToSide(const SkOpAngle* rh, bool* inside) const { + const SkOpSegment* segment = this->segment(); + SkPath::Verb verb = segment->verb(); + const SkPoint& startPt = this->fStart->pt(); + const SkPoint& endPt = this->fEnd->pt(); + SkDPoint dStartPt; + dStartPt.set(startPt); + SkDLine rayMid; + rayMid[0].fX = (startPt.fX + endPt.fX) / 2; + rayMid[0].fY = (startPt.fY + endPt.fY) / 2; + rayMid[1].fX = rayMid[0].fX + (endPt.fY - startPt.fY); + rayMid[1].fY = rayMid[0].fY - (endPt.fX - startPt.fX); + SkIntersections iMid; + (*CurveIntersectRay[verb])(segment->pts(), segment->weight(), rayMid, &iMid); + int iOutside = iMid.mostOutside(this->fStart->t(), this->fEnd->t(), dStartPt); + if (iOutside < 0) { + return false; + } + const SkOpSegment* oppSegment = rh->segment(); + SkPath::Verb oppVerb = oppSegment->verb(); + SkIntersections oppMid; + (*CurveIntersectRay[oppVerb])(oppSegment->pts(), oppSegment->weight(), rayMid, &oppMid); + int oppOutside = oppMid.mostOutside(rh->fStart->t(), rh->fEnd->t(), dStartPt); + if (oppOutside < 0) { + return false; + } + SkDVector iSide = iMid.pt(iOutside) - dStartPt; + SkDVector oppSide = oppMid.pt(oppOutside) - dStartPt; + double dir = iSide.crossCheck(oppSide); + if (!dir) { + return false; + } + *inside = dir < 0; + return true; +} + +bool SkOpAngle::oppositePlanes(const SkOpAngle* rh) const { + int startSpan = SkTAbs(rh->fSectorStart - fSectorStart); + return startSpan >= 8; +} + +bool SkOpAngle::orderable(SkOpAngle* rh) { + int result; + if (!fPart.isCurve()) { + if (!rh->fPart.isCurve()) { + double leftX = fTangentHalf.dx(); + double leftY = fTangentHalf.dy(); + double rightX = rh->fTangentHalf.dx(); + double rightY = rh->fTangentHalf.dy(); + double x_ry = leftX * rightY; + double rx_y = rightX * leftY; + if (x_ry == rx_y) { + if (leftX * rightX < 0 || leftY * rightY < 0) { + return true; // exactly 180 degrees apart + } + goto unorderable; + } + SkASSERT(x_ry != rx_y); // indicates an undetected coincidence -- worth finding earlier + return x_ry < rx_y; + } + if ((result = this->allOnOneSide(rh)) >= 0) { + return result; + } + if (fUnorderable || approximately_zero(rh->fSide)) { + goto unorderable; + } + } else if (!rh->fPart.isCurve()) { + if ((result = rh->allOnOneSide(this)) >= 0) { + return !result; + } + if (rh->fUnorderable || approximately_zero(fSide)) { + goto unorderable; + } + } else if ((result = this->convexHullOverlaps(rh)) >= 0) { + return result; + } + return this->endsIntersect(rh); +unorderable: + fUnorderable = true; + rh->fUnorderable = true; + return true; +} + +// OPTIMIZE: if this shows up in a profile, add a previous pointer +// as is, this should be rarely called +SkOpAngle* SkOpAngle::previous() const { + SkOpAngle* last = fNext; + do { + SkOpAngle* next = last->fNext; + if (next == this) { + return last; + } + last = next; + } while (true); +} + +SkOpSegment* SkOpAngle::segment() const { + return fStart->segment(); +} + +void SkOpAngle::set(SkOpSpanBase* start, SkOpSpanBase* end) { + fStart = start; + fComputedEnd = fEnd = end; + SkASSERT(start != end); + fNext = nullptr; + fComputeSector = fComputedSector = fCheckCoincidence = false; + setSpans(); + setSector(); + SkDEBUGCODE(fID = start ? start->globalState()->nextAngleID() : -1); +} + +void SkOpAngle::setSpans() { + fUnorderable = false; + fLastMarked = nullptr; + if (!fStart) { + fUnorderable = true; + return; + } + const SkOpSegment* segment = fStart->segment(); + const SkPoint* pts = segment->pts(); + SkDEBUGCODE(fPart.fCurve.fVerb = SkPath::kCubic_Verb); // required for SkDCurve debug check + SkDEBUGCODE(fPart.fCurve[2].fX = fPart.fCurve[2].fY = fPart.fCurve[3].fX = fPart.fCurve[3].fY + = SK_ScalarNaN); // make the non-line part uninitialized + SkDEBUGCODE(fPart.fCurve.fVerb = segment->verb()); // set the curve type for real + segment->subDivide(fStart, fEnd, &fPart.fCurve); // set at least the line part if not more + fOriginalCurvePart = fPart.fCurve; + const SkPath::Verb verb = segment->verb(); + fPart.setCurveHullSweep(verb); + if (SkPath::kLine_Verb != verb && !fPart.isCurve()) { + SkDLine lineHalf; + fPart.fCurve[1] = fPart.fCurve[SkPathOpsVerbToPoints(verb)]; + fOriginalCurvePart[1] = fPart.fCurve[1]; + lineHalf[0].set(fPart.fCurve[0].asSkPoint()); + lineHalf[1].set(fPart.fCurve[1].asSkPoint()); + fTangentHalf.lineEndPoints(lineHalf); + fSide = 0; + } + switch (verb) { + case SkPath::kLine_Verb: { + SkASSERT(fStart != fEnd); + const SkPoint& cP1 = pts[fStart->t() < fEnd->t()]; + SkDLine lineHalf; + lineHalf[0].set(fStart->pt()); + lineHalf[1].set(cP1); + fTangentHalf.lineEndPoints(lineHalf); + fSide = 0; + } return; + case SkPath::kQuad_Verb: + case SkPath::kConic_Verb: { + SkLineParameters tangentPart; + (void) tangentPart.quadEndPoints(fPart.fCurve.fQuad); + fSide = -tangentPart.pointDistance(fPart.fCurve[2]); // not normalized -- compare sign only + } break; + case SkPath::kCubic_Verb: { + SkLineParameters tangentPart; + (void) tangentPart.cubicPart(fPart.fCurve.fCubic); + fSide = -tangentPart.pointDistance(fPart.fCurve[3]); + double testTs[4]; + // OPTIMIZATION: keep inflections precomputed with cubic segment? + int testCount = SkDCubic::FindInflections(pts, testTs); + double startT = fStart->t(); + double endT = fEnd->t(); + double limitT = endT; + int index; + for (index = 0; index < testCount; ++index) { + if (!::between(startT, testTs[index], limitT)) { + testTs[index] = -1; + } + } + testTs[testCount++] = startT; + testTs[testCount++] = endT; + SkTQSort<double>(testTs, &testTs[testCount - 1]); + double bestSide = 0; + int testCases = (testCount << 1) - 1; + index = 0; + while (testTs[index] < 0) { + ++index; + } + index <<= 1; + for (; index < testCases; ++index) { + int testIndex = index >> 1; + double testT = testTs[testIndex]; + if (index & 1) { + testT = (testT + testTs[testIndex + 1]) / 2; + } + // OPTIMIZE: could avoid call for t == startT, endT + SkDPoint pt = dcubic_xy_at_t(pts, segment->weight(), testT); + SkLineParameters tangentPart; + tangentPart.cubicEndPoints(fPart.fCurve.fCubic); + double testSide = tangentPart.pointDistance(pt); + if (fabs(bestSide) < fabs(testSide)) { + bestSide = testSide; + } + } + fSide = -bestSide; // compare sign only + } break; + default: + SkASSERT(0); + } +} + +void SkOpAngle::setSector() { + if (!fStart) { + fUnorderable = true; + return; + } + const SkOpSegment* segment = fStart->segment(); + SkPath::Verb verb = segment->verb(); + fSectorStart = this->findSector(verb, fPart.fSweep[0].fX, fPart.fSweep[0].fY); + if (fSectorStart < 0) { + goto deferTilLater; + } + if (!fPart.isCurve()) { // if it's a line or line-like, note that both sectors are the same + SkASSERT(fSectorStart >= 0); + fSectorEnd = fSectorStart; + fSectorMask = 1 << fSectorStart; + return; + } + SkASSERT(SkPath::kLine_Verb != verb); + fSectorEnd = this->findSector(verb, fPart.fSweep[1].fX, fPart.fSweep[1].fY); + if (fSectorEnd < 0) { +deferTilLater: + fSectorStart = fSectorEnd = -1; + fSectorMask = 0; + fComputeSector = true; // can't determine sector until segment length can be found + return; + } + if (fSectorEnd == fSectorStart + && (fSectorStart & 3) != 3) { // if the sector has no span, it can't be an exact angle + fSectorMask = 1 << fSectorStart; + return; + } + bool crossesZero = this->checkCrossesZero(); + int start = SkTMin(fSectorStart, fSectorEnd); + bool curveBendsCCW = (fSectorStart == start) ^ crossesZero; + // bump the start and end of the sector span if they are on exact compass points + if ((fSectorStart & 3) == 3) { + fSectorStart = (fSectorStart + (curveBendsCCW ? 1 : 31)) & 0x1f; + } + if ((fSectorEnd & 3) == 3) { + fSectorEnd = (fSectorEnd + (curveBendsCCW ? 31 : 1)) & 0x1f; + } + crossesZero = this->checkCrossesZero(); + start = SkTMin(fSectorStart, fSectorEnd); + int end = SkTMax(fSectorStart, fSectorEnd); + if (!crossesZero) { + fSectorMask = (unsigned) -1 >> (31 - end + start) << start; + } else { + fSectorMask = (unsigned) -1 >> (31 - start) | ((unsigned) -1 << end); + } +} + +SkOpSpan* SkOpAngle::starter() { + return fStart->starter(fEnd); +} + +bool SkOpAngle::tangentsDiverge(const SkOpAngle* rh, double s0xt0) const { + if (s0xt0 == 0) { + return false; + } + // if the ctrl tangents are not nearly parallel, use them + // solve for opposite direction displacement scale factor == m + // initial dir = v1.cross(v2) == v2.x * v1.y - v2.y * v1.x + // displacement of q1[1] : dq1 = { -m * v1.y, m * v1.x } + q1[1] + // straight angle when : v2.x * (dq1.y - q1[0].y) == v2.y * (dq1.x - q1[0].x) + // v2.x * (m * v1.x + v1.y) == v2.y * (-m * v1.y + v1.x) + // - m * (v2.x * v1.x + v2.y * v1.y) == v2.x * v1.y - v2.y * v1.x + // m = (v2.y * v1.x - v2.x * v1.y) / (v2.x * v1.x + v2.y * v1.y) + // m = v1.cross(v2) / v1.dot(v2) + const SkDVector* sweep = fPart.fSweep; + const SkDVector* tweep = rh->fPart.fSweep; + double s0dt0 = sweep[0].dot(tweep[0]); + if (!s0dt0) { + return true; + } + SkASSERT(s0dt0 != 0); + double m = s0xt0 / s0dt0; + double sDist = sweep[0].length() * m; + double tDist = tweep[0].length() * m; + bool useS = fabs(sDist) < fabs(tDist); + double mFactor = fabs(useS ? this->distEndRatio(sDist) : rh->distEndRatio(tDist)); + return mFactor < 50; // empirically found limit +} diff --git a/gfx/skia/skia/src/pathops/SkOpAngle.h b/gfx/skia/skia/src/pathops/SkOpAngle.h new file mode 100644 index 000000000..cbdadf103 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpAngle.h @@ -0,0 +1,137 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpAngle_DEFINED +#define SkOpAngle_DEFINED + +#include "SkLineParameters.h" +#include "SkPathOpsCurve.h" +#if DEBUG_ANGLE +#include "SkString.h" +#endif + +class SkOpContour; +class SkOpPtT; +class SkOpSegment; +class SkOpSpanBase; +class SkOpSpan; + +class SkOpAngle { +public: + enum IncludeType { + kUnaryWinding, + kUnaryXor, + kBinarySingle, + kBinaryOpp, + }; + + const SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; + SkOpContour* debugContour(int id) const; + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + +#if DEBUG_SORT + void debugLoop() const; +#endif + +#if DEBUG_ANGLE + bool debugCheckCoincidence() const { return fCheckCoincidence; } + void debugCheckNearCoincidence() const; + SkString debugPart() const; +#endif + const SkOpPtT* debugPtT(int id) const; + const SkOpSegment* debugSegment(int id) const; + int debugSign() const; + const SkOpSpanBase* debugSpan(int id) const; + void debugValidate() const; + void debugValidateNext() const; // in debug builds, verify that angle loop is uncorrupted + double distEndRatio(double dist) const; + // available to testing only + void dump() const; + void dumpCurves() const; + void dumpLoop() const; + void dumpOne(bool functionHeader) const; + void dumpTo(const SkOpSegment* fromSeg, const SkOpAngle* ) const; + void dumpTest() const; + + SkOpSpanBase* end() const { + return fEnd; + } + + void insert(SkOpAngle* ); + SkOpSpanBase* lastMarked() const; + bool loopContains(const SkOpAngle* ) const; + int loopCount() const; + + SkOpAngle* next() const { + return fNext; + } + + SkOpAngle* previous() const; + SkOpSegment* segment() const; + void set(SkOpSpanBase* start, SkOpSpanBase* end); + + void setLastMarked(SkOpSpanBase* marked) { + fLastMarked = marked; + } + + SkOpSpanBase* start() const { + return fStart; + } + + SkOpSpan* starter(); + + bool unorderable() const { + return fUnorderable; + } + +private: + bool after(SkOpAngle* test); + int allOnOneSide(const SkOpAngle* test); + bool checkCrossesZero() const; + bool checkParallel(SkOpAngle* ); + bool computeSector(); + int convexHullOverlaps(const SkOpAngle* ) const; + bool endToSide(const SkOpAngle* rh, bool* inside) const; + bool endsIntersect(SkOpAngle* ); + int findSector(SkPath::Verb verb, double x, double y) const; + SkOpGlobalState* globalState() const; + bool merge(SkOpAngle* ); + double midT() const; + bool midToSide(const SkOpAngle* rh, bool* inside) const; + bool oppositePlanes(const SkOpAngle* rh) const; + bool orderable(SkOpAngle* rh); // false == this < rh ; true == this > rh + void setSector(); + void setSpans(); + bool tangentsDiverge(const SkOpAngle* rh, double s0xt0) const; + + SkDCurve fOriginalCurvePart; // the curve from start to end + SkDCurveSweep fPart; // the curve from start to end offset as needed + double fSide; + SkLineParameters fTangentHalf; // used only to sort a pair of lines or line-like sections + SkOpAngle* fNext; + SkOpSpanBase* fLastMarked; + SkOpSpanBase* fStart; + SkOpSpanBase* fEnd; + SkOpSpanBase* fComputedEnd; + int fSectorMask; + int8_t fSectorStart; // in 32nds of a circle + int8_t fSectorEnd; + bool fUnorderable; + bool fComputeSector; + bool fComputedSector; + bool fCheckCoincidence; + SkDEBUGCODE(int fID); + + friend class PathOpsAngleTester; +}; + + + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpBuilder.cpp b/gfx/skia/skia/src/pathops/SkOpBuilder.cpp new file mode 100644 index 000000000..011d6a6ab --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpBuilder.cpp @@ -0,0 +1,186 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkMatrix.h" +#include "SkOpEdgeBuilder.h" +#include "SkPathPriv.h" +#include "SkPathOps.h" +#include "SkPathOpsCommon.h" + +static bool one_contour(const SkPath& path) { + SkChunkAlloc allocator(256); + int verbCount = path.countVerbs(); + uint8_t* verbs = (uint8_t*) allocator.alloc(sizeof(uint8_t) * verbCount, + SkChunkAlloc::kThrow_AllocFailType); + (void) path.getVerbs(verbs, verbCount); + for (int index = 1; index < verbCount; ++index) { + if (verbs[index] == SkPath::kMove_Verb) { + return false; + } + } + return true; +} + +bool FixWinding(SkPath* path) { + SkPath::FillType fillType = path->getFillType(); + if (fillType == SkPath::kInverseEvenOdd_FillType) { + fillType = SkPath::kInverseWinding_FillType; + } else if (fillType == SkPath::kEvenOdd_FillType) { + fillType = SkPath::kWinding_FillType; + } + SkPathPriv::FirstDirection dir; + if (one_contour(*path) && SkPathPriv::CheapComputeFirstDirection(*path, &dir)) { + if (dir != SkPathPriv::kCCW_FirstDirection) { + SkPath temp; + temp.reverseAddPath(*path); + *path = temp; + } + path->setFillType(fillType); + return true; + } + SkChunkAlloc allocator(4096); + SkOpContourHead contourHead; + SkOpGlobalState globalState(&contourHead, &allocator SkDEBUGPARAMS(false) + SkDEBUGPARAMS(nullptr)); + SkOpEdgeBuilder builder(*path, &contourHead, &globalState); + if (builder.unparseable() || !builder.finish()) { + return false; + } + if (!contourHead.count()) { + return true; + } + if (!contourHead.next()) { + return false; + } + contourHead.joinAllSegments(); + contourHead.resetReverse(); + bool writePath = false; + SkOpSpan* topSpan; + globalState.setPhase(SkOpPhase::kFixWinding); + while ((topSpan = FindSortableTop(&contourHead))) { + SkOpSegment* topSegment = topSpan->segment(); + SkOpContour* topContour = topSegment->contour(); + SkASSERT(topContour->isCcw() >= 0); +#if DEBUG_WINDING + SkDebugf("%s id=%d nested=%d ccw=%d\n", __FUNCTION__, + topSegment->debugID(), globalState.nested(), topContour->isCcw()); +#endif + if ((globalState.nested() & 1) != SkToBool(topContour->isCcw())) { + topContour->setReverse(); + writePath = true; + } + topContour->markAllDone(); + globalState.clearNested(); + } + if (!writePath) { + path->setFillType(fillType); + return true; + } + SkPath empty; + SkPathWriter woundPath(empty); + SkOpContour* test = &contourHead; + do { + if (test->reversed()) { + test->toReversePath(&woundPath); + } else { + test->toPath(&woundPath); + } + } while ((test = test->next())); + *path = *woundPath.nativePath(); + path->setFillType(fillType); + return true; +} + +void SkOpBuilder::add(const SkPath& path, SkPathOp op) { + if (0 == fOps.count() && op != kUnion_SkPathOp) { + fPathRefs.push_back() = SkPath(); + *fOps.append() = kUnion_SkPathOp; + } + fPathRefs.push_back() = path; + *fOps.append() = op; +} + +void SkOpBuilder::reset() { + fPathRefs.reset(); + fOps.reset(); +} + +/* OPTIMIZATION: Union doesn't need to be all-or-nothing. A run of three or more convex + paths with union ops could be locally resolved and still improve over doing the + ops one at a time. */ +bool SkOpBuilder::resolve(SkPath* result) { + SkPath original = *result; + int count = fOps.count(); + bool allUnion = true; + SkPathPriv::FirstDirection firstDir = SkPathPriv::kUnknown_FirstDirection; + for (int index = 0; index < count; ++index) { + SkPath* test = &fPathRefs[index]; + if (kUnion_SkPathOp != fOps[index] || test->isInverseFillType()) { + allUnion = false; + break; + } + // If all paths are convex, track direction, reversing as needed. + if (test->isConvex()) { + SkPathPriv::FirstDirection dir; + if (!SkPathPriv::CheapComputeFirstDirection(*test, &dir)) { + allUnion = false; + break; + } + if (firstDir == SkPathPriv::kUnknown_FirstDirection) { + firstDir = dir; + } else if (firstDir != dir) { + SkPath temp; + temp.reverseAddPath(*test); + *test = temp; + } + continue; + } + // If the path is not convex but its bounds do not intersect the others, simplify is enough. + const SkRect& testBounds = test->getBounds(); + for (int inner = 0; inner < index; ++inner) { + // OPTIMIZE: check to see if the contour bounds do not intersect other contour bounds? + if (SkRect::Intersects(fPathRefs[inner].getBounds(), testBounds)) { + allUnion = false; + break; + } + } + } + if (!allUnion) { + *result = fPathRefs[0]; + for (int index = 1; index < count; ++index) { + if (!Op(*result, fPathRefs[index], fOps[index], result)) { + reset(); + *result = original; + return false; + } + } + reset(); + return true; + } + SkPath sum; + for (int index = 0; index < count; ++index) { + if (!Simplify(fPathRefs[index], &fPathRefs[index])) { + reset(); + *result = original; + return false; + } + if (!fPathRefs[index].isEmpty()) { + // convert the even odd result back to winding form before accumulating it + if (!FixWinding(&fPathRefs[index])) { + *result = original; + return false; + } + sum.addPath(fPathRefs[index]); + } + } + reset(); + bool success = Simplify(sum, result); + if (!success) { + *result = original; + } + return success; +} diff --git a/gfx/skia/skia/src/pathops/SkOpCoincidence.cpp b/gfx/skia/skia/src/pathops/SkOpCoincidence.cpp new file mode 100755 index 000000000..f0481ab24 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpCoincidence.cpp @@ -0,0 +1,1363 @@ +/* + * Copyright 2015 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 "SkOpSegment.h" +#include "SkPathOpsTSect.h" + +// returns true if coincident span's start and end are the same +bool SkCoincidentSpans::collapsed(const SkOpPtT* test) const { + return (fCoinPtTStart == test && fCoinPtTEnd->contains(test)) + || (fCoinPtTEnd == test && fCoinPtTStart->contains(test)) + || (fOppPtTStart == test && fOppPtTEnd->contains(test)) + || (fOppPtTEnd == test && fOppPtTStart->contains(test)); +} + +// sets the span's end to the ptT referenced by the previous-next +void SkCoincidentSpans::correctOneEnd( + const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::*setEnd)(const SkOpPtT* ptT) ) { + const SkOpPtT* origPtT = (this->*getEnd)(); + const SkOpSpanBase* origSpan = origPtT->span(); + const SkOpSpan* prev = origSpan->prev(); + const SkOpPtT* testPtT = prev ? prev->next()->ptT() + : origSpan->upCast()->next()->prev()->ptT(); + if (origPtT != testPtT) { + (this->*setEnd)(testPtT); + } +} + +/* Please keep this in sync with debugCorrectEnds */ +// FIXME: member pointers have fallen out of favor and can be replaced with +// an alternative approach. +// makes all span ends agree with the segment's spans that define them +void SkCoincidentSpans::correctEnds() { + this->correctOneEnd(&SkCoincidentSpans::coinPtTStart, &SkCoincidentSpans::setCoinPtTStart); + this->correctOneEnd(&SkCoincidentSpans::coinPtTEnd, &SkCoincidentSpans::setCoinPtTEnd); + this->correctOneEnd(&SkCoincidentSpans::oppPtTStart, &SkCoincidentSpans::setOppPtTStart); + this->correctOneEnd(&SkCoincidentSpans::oppPtTEnd, &SkCoincidentSpans::setOppPtTEnd); +} + +/* Please keep this in sync with debugExpand */ +// expand the range by checking adjacent spans for coincidence +bool SkCoincidentSpans::expand() { + bool expanded = false; + const SkOpSegment* segment = coinPtTStart()->segment(); + const SkOpSegment* oppSegment = oppPtTStart()->segment(); + do { + const SkOpSpan* start = coinPtTStart()->span()->upCast(); + const SkOpSpan* prev = start->prev(); + const SkOpPtT* oppPtT; + if (!prev || !(oppPtT = prev->contains(oppSegment))) { + break; + } + double midT = (prev->t() + start->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + setStarts(prev->ptT(), oppPtT); + expanded = true; + } while (true); + do { + const SkOpSpanBase* end = coinPtTEnd()->span(); + SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); + if (next && next->deleted()) { + break; + } + const SkOpPtT* oppPtT; + if (!next || !(oppPtT = next->contains(oppSegment))) { + break; + } + double midT = (end->t() + next->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + setEnds(next->ptT(), oppPtT); + expanded = true; + } while (true); + return expanded; +} + +// increase the range of this span +bool SkCoincidentSpans::extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + bool result = false; + if (fCoinPtTStart->fT > coinPtTStart->fT || (this->flipped() + ? fOppPtTStart->fT < oppPtTStart->fT : fOppPtTStart->fT > oppPtTStart->fT)) { + this->setStarts(coinPtTStart, oppPtTStart); + result = true; + } + if (fCoinPtTEnd->fT < coinPtTEnd->fT || (this->flipped() + ? fOppPtTEnd->fT > oppPtTEnd->fT : fOppPtTEnd->fT < oppPtTEnd->fT)) { + this->setEnds(coinPtTEnd, oppPtTEnd); + result = true; + } + return result; +} + +// set the range of this span +void SkCoincidentSpans::set(SkCoincidentSpans* next, const SkOpPtT* coinPtTStart, + const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + SkASSERT(SkOpCoincidence::Ordered(coinPtTStart, oppPtTStart)); + fNext = next; + this->setStarts(coinPtTStart, oppPtTStart); + this->setEnds(coinPtTEnd, oppPtTEnd); +} + +// returns true if both points are inside this +bool SkCoincidentSpans::contains(const SkOpPtT* s, const SkOpPtT* e) const { + if (s->fT > e->fT) { + SkTSwap(s, e); + } + if (s->segment() == fCoinPtTStart->segment()) { + return fCoinPtTStart->fT <= s->fT && e->fT <= fCoinPtTEnd->fT; + } else { + SkASSERT(s->segment() == fOppPtTStart->segment()); + double oppTs = fOppPtTStart->fT; + double oppTe = fOppPtTEnd->fT; + if (oppTs > oppTe) { + SkTSwap(oppTs, oppTe); + } + return oppTs <= s->fT && e->fT <= oppTe; + } +} + +// A coincident span is unordered if the pairs of points in the main and opposite curves' +// t values do not ascend or descend. For instance, if a tightly arced quadratic is +// coincident with another curve, it may intersect it out of order. +bool SkCoincidentSpans::ordered() const { + const SkOpSpanBase* start = this->coinPtTStart()->span(); + const SkOpSpanBase* end = this->coinPtTEnd()->span(); + const SkOpSpanBase* next = start->upCast()->next(); + if (next == end) { + return true; + } + bool flipped = this->flipped(); + const SkOpSegment* oppSeg = this->oppPtTStart()->segment(); + double oppLastT = fOppPtTStart->fT; + do { + const SkOpPtT* opp = next->contains(oppSeg); + if (!opp) { + SkASSERT(0); // may assert if coincident span isn't fully processed + continue; + } + if ((oppLastT > opp->fT) != flipped) { + return false; + } + oppLastT = opp->fT; + if (next == end) { + break; + } + if (!next->upCastable()) { + return false; + } + next = next->upCast()->next(); + } while (true); + return true; +} + +// if there is an existing pair that overlaps the addition, extend it +bool SkOpCoincidence::extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { + SkCoincidentSpans* test = fHead; + if (!test) { + return false; + } + const SkOpSegment* coinSeg = coinPtTStart->segment(); + const SkOpSegment* oppSeg = oppPtTStart->segment(); + if (!Ordered(coinPtTStart, oppPtTStart)) { + SkTSwap(coinSeg, oppSeg); + SkTSwap(coinPtTStart, oppPtTStart); + SkTSwap(coinPtTEnd, oppPtTEnd); + if (coinPtTStart->fT > coinPtTEnd->fT) { + SkTSwap(coinPtTStart, coinPtTEnd); + SkTSwap(oppPtTStart, oppPtTEnd); + } + } + double oppMinT = SkTMin(oppPtTStart->fT, oppPtTEnd->fT); + SkDEBUGCODE(double oppMaxT = SkTMax(oppPtTStart->fT, oppPtTEnd->fT)); + do { + if (coinSeg != test->coinPtTStart()->segment()) { + continue; + } + if (oppSeg != test->oppPtTStart()->segment()) { + continue; + } + double oTestMinT = SkTMin(test->oppPtTStart()->fT, test->oppPtTEnd()->fT); + double oTestMaxT = SkTMax(test->oppPtTStart()->fT, test->oppPtTEnd()->fT); + // if debug check triggers, caller failed to check if extended already exists + SkASSERT(test->coinPtTStart()->fT > coinPtTStart->fT + || coinPtTEnd->fT > test->coinPtTEnd()->fT + || oTestMinT > oppMinT || oppMaxT > oTestMaxT); + if ((test->coinPtTStart()->fT <= coinPtTEnd->fT + && coinPtTStart->fT <= test->coinPtTEnd()->fT) + || (oTestMinT <= oTestMaxT && oppMinT <= oTestMaxT)) { + test->extend(coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + return true; + } + } while ((test = test->next())); + return false; +} + +// verifies that the coincidence hasn't already been added +static void DebugCheckAdd(const SkCoincidentSpans* check, const SkOpPtT* coinPtTStart, + const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) { +#if DEBUG_COINCIDENCE + while (check) { + SkASSERT(check->coinPtTStart() != coinPtTStart || check->coinPtTEnd() != coinPtTEnd + || check->oppPtTStart() != oppPtTStart || check->oppPtTEnd() != oppPtTEnd); + SkASSERT(check->coinPtTStart() != oppPtTStart || check->coinPtTEnd() != oppPtTEnd + || check->oppPtTStart() != coinPtTStart || check->oppPtTEnd() != coinPtTEnd); + check = check->next(); + } +#endif +} + +// adds a new coincident pair +void SkOpCoincidence::add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, + SkOpPtT* oppPtTEnd) { + // OPTIMIZE: caller should have already sorted + if (!Ordered(coinPtTStart, oppPtTStart)) { + if (oppPtTStart->fT < oppPtTEnd->fT) { + this->add(oppPtTStart, oppPtTEnd, coinPtTStart, coinPtTEnd); + } else { + this->add(oppPtTEnd, oppPtTStart, coinPtTEnd, coinPtTStart); + } + return; + } + SkASSERT(Ordered(coinPtTStart, oppPtTStart)); + // choose the ptT at the front of the list to track + coinPtTStart = coinPtTStart->span()->ptT(); + coinPtTEnd = coinPtTEnd->span()->ptT(); + oppPtTStart = oppPtTStart->span()->ptT(); + oppPtTEnd = oppPtTEnd->span()->ptT(); + SkASSERT(coinPtTStart->fT < coinPtTEnd->fT); + SkASSERT(oppPtTStart->fT != oppPtTEnd->fT); + SkOPASSERT(!coinPtTStart->deleted()); + SkOPASSERT(!coinPtTEnd->deleted()); + SkOPASSERT(!oppPtTStart->deleted()); + SkOPASSERT(!oppPtTEnd->deleted()); + DebugCheckAdd(fHead, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + DebugCheckAdd(fTop, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + SkCoincidentSpans* coinRec = SkOpTAllocator<SkCoincidentSpans>::Allocate( + this->globalState()->allocator()); + coinRec->init(SkDEBUGCODE(fGlobalState)); + coinRec->set(this->fHead, coinPtTStart, coinPtTEnd, oppPtTStart, oppPtTEnd); + fHead = coinRec; +} + +// description below +bool SkOpCoincidence::addEndMovedSpans(const SkOpSpan* base, const SkOpSpanBase* testSpan) { + const SkOpPtT* testPtT = testSpan->ptT(); + const SkOpPtT* stopPtT = testPtT; + const SkOpSegment* baseSeg = base->segment(); + while ((testPtT = testPtT->next()) != stopPtT) { + const SkOpSegment* testSeg = testPtT->segment(); + if (testPtT->deleted()) { + continue; + } + if (testSeg == baseSeg) { + continue; + } + if (testPtT->span()->ptT() != testPtT) { + continue; + } + if (this->contains(baseSeg, testSeg, testPtT->fT)) { + continue; + } + // intersect perp with base->ptT() with testPtT->segment() + SkDVector dxdy = baseSeg->dSlopeAtT(base->t()); + const SkPoint& pt = base->pt(); + SkDLine ray = {{{pt.fX, pt.fY}, {pt.fX + dxdy.fY, pt.fY - dxdy.fX}}}; + SkIntersections i; + (*CurveIntersectRay[testSeg->verb()])(testSeg->pts(), testSeg->weight(), ray, &i); + for (int index = 0; index < i.used(); ++index) { + double t = i[0][index]; + if (!between(0, t, 1)) { + continue; + } + SkDPoint oppPt = i.pt(index); + if (!oppPt.approximatelyEqual(pt)) { + continue; + } + SkOpSegment* writableSeg = const_cast<SkOpSegment*>(testSeg); + SkOpPtT* oppStart = writableSeg->addT(t); + if (oppStart == testPtT) { + continue; + } + SkOpSpan* writableBase = const_cast<SkOpSpan*>(base); + oppStart->span()->addOpp(writableBase); + if (oppStart->deleted()) { + continue; + } + SkOpSegment* coinSeg = base->segment(); + SkOpSegment* oppSeg = oppStart->segment(); + double coinTs, coinTe, oppTs, oppTe; + if (Ordered(coinSeg, oppSeg)) { + coinTs = base->t(); + coinTe = testSpan->t(); + oppTs = oppStart->fT; + oppTe = testPtT->fT; + } else { + SkTSwap(coinSeg, oppSeg); + coinTs = oppStart->fT; + coinTe = testPtT->fT; + oppTs = base->t(); + oppTe = testSpan->t(); + } + if (coinTs > coinTe) { + SkTSwap(coinTs, coinTe); + SkTSwap(oppTs, oppTe); + } + bool added; + if (!this->addOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &added)) { + return false; + } + } + } + return true; +} + +// description below +bool SkOpCoincidence::addEndMovedSpans(const SkOpPtT* ptT) { + FAIL_IF(!ptT->span()->upCastable()); + const SkOpSpan* base = ptT->span()->upCast(); + const SkOpSpan* prev = base->prev(); + FAIL_IF(!prev); + if (!prev->isCanceled()) { + if (!this->addEndMovedSpans(base, base->prev())) { + return false; + } + } + if (!base->isCanceled()) { + if (!this->addEndMovedSpans(base, base->next())) { + return false; + } + } + return true; +} + +/* If A is coincident with B and B includes an endpoint, and A's matching point + is not the endpoint (i.e., there's an implied line connecting B-end and A) + then assume that the same implied line may intersect another curve close to B. + Since we only care about coincidence that was undetected, look at the + ptT list on B-segment adjacent to the B-end/A ptT loop (not in the loop, but + next door) and see if the A matching point is close enough to form another + coincident pair. If so, check for a new coincident span between B-end/A ptT loop + and the adjacent ptT loop. +*/ +bool SkOpCoincidence::addEndMovedSpans(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* span = fHead; + if (!span) { + return true; + } + fTop = span; + fHead = nullptr; + do { + if (span->coinPtTStart()->fPt != span->oppPtTStart()->fPt) { + FAIL_IF(1 == span->coinPtTStart()->fT); + bool onEnd = span->coinPtTStart()->fT == 0; + bool oOnEnd = zero_or_one(span->oppPtTStart()->fT); + if (onEnd) { + if (!oOnEnd) { // if both are on end, any nearby intersect was already found + if (!this->addEndMovedSpans(span->oppPtTStart())) { + return false; + } + } + } else if (oOnEnd) { + if (!this->addEndMovedSpans(span->coinPtTStart())) { + return false; + } + } + } + if (span->coinPtTEnd()->fPt != span->oppPtTEnd()->fPt) { + bool onEnd = span->coinPtTEnd()->fT == 1; + bool oOnEnd = zero_or_one(span->oppPtTEnd()->fT); + if (onEnd) { + if (!oOnEnd) { + if (!this->addEndMovedSpans(span->oppPtTEnd())) { + return false; + } + } + } else if (oOnEnd) { + if (!this->addEndMovedSpans(span->coinPtTEnd())) { + return false; + } + } + } + } while ((span = span->next())); + this->restoreHead(); + return true; +} + +/* Please keep this in sync with debugAddExpanded */ +// for each coincident pair, match the spans +// if the spans don't match, add the missing pt to the segment and loop it in the opposite span +bool SkOpCoincidence::addExpanded(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = this->fHead; + if (!coin) { + return true; + } + do { + const SkOpPtT* startPtT = coin->coinPtTStart(); + const SkOpPtT* oStartPtT = coin->oppPtTStart(); + double priorT = startPtT->fT; + double oPriorT = oStartPtT->fT; + FAIL_IF(!startPtT->contains(oStartPtT)); + SkOPASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); + const SkOpSpanBase* start = startPtT->span(); + const SkOpSpanBase* oStart = oStartPtT->span(); + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); + const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span(); + FAIL_IF(oEnd->deleted()); + FAIL_IF(!start->upCastable()); + const SkOpSpanBase* test = start->upCast()->next(); + FAIL_IF(!coin->flipped() && !oStart->upCastable()); + const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next(); + FAIL_IF(!oTest); + SkOpSegment* seg = start->segment(); + SkOpSegment* oSeg = oStart->segment(); + while (test != end || oTest != oEnd) { + const SkOpPtT* containedOpp = test->ptT()->contains(oSeg); + const SkOpPtT* containedThis = oTest->ptT()->contains(seg); + if (!containedOpp || !containedThis) { + // choose the ends, or the first common pt-t list shared by both + double nextT, oNextT; + if (containedOpp) { + nextT = test->t(); + oNextT = containedOpp->fT; + } else if (containedThis) { + nextT = containedThis->fT; + oNextT = oTest->t(); + } else { + // iterate through until a pt-t list found that contains the other + const SkOpSpanBase* walk = test; + const SkOpPtT* walkOpp; + do { + FAIL_IF(!walk->upCastable()); + walk = walk->upCast()->next(); + } while (!(walkOpp = walk->ptT()->contains(oSeg)) + && walk != coin->coinPtTEnd()->span()); + FAIL_IF(!walkOpp); + nextT = walk->t(); + oNextT = walkOpp->fT; + } + // use t ranges to guess which one is missing + double startRange = nextT - priorT; + FAIL_IF(!startRange); + double startPart = (test->t() - priorT) / startRange; + double oStartRange = oNextT - oPriorT; + FAIL_IF(!oStartRange); + double oStartPart = (oTest->t() - oPriorT) / oStartRange; + FAIL_IF(startPart == oStartPart); + bool addToOpp = !containedOpp && !containedThis ? startPart < oStartPart + : !!containedThis; + bool startOver = false; + bool success = addToOpp ? oSeg->addExpanded( + oPriorT + oStartRange * startPart, test, &startOver) + : seg->addExpanded( + priorT + startRange * oStartPart, oTest, &startOver); + FAIL_IF(!success); + if (startOver) { + test = start; + oTest = oStart; + } + end = coin->coinPtTEnd()->span(); + oEnd = coin->oppPtTEnd()->span(); + } + if (test != end) { + FAIL_IF(!test->upCastable()); + priorT = test->t(); + test = test->upCast()->next(); + } + if (oTest != oEnd) { + oPriorT = oTest->t(); + oTest = coin->flipped() ? oTest->prev() : oTest->upCast()->next(); + FAIL_IF(!oTest); + } + + } + } while ((coin = coin->next())); + return true; +} + +// given a t span, map the same range on the coincident span +/* +the curves may not scale linearly, so interpolation may only happen within known points +remap over1s, over1e, cointPtTStart, coinPtTEnd to smallest range that captures over1s +then repeat to capture over1e +*/ +double SkOpCoincidence::TRange(const SkOpPtT* overS, double t, + const SkOpSegment* coinSeg SkDEBUGPARAMS(const SkOpPtT* overE)) { + const SkOpSpanBase* work = overS->span(); + const SkOpPtT* foundStart = nullptr; + const SkOpPtT* foundEnd = nullptr; + const SkOpPtT* coinStart = nullptr; + const SkOpPtT* coinEnd = nullptr; + do { + const SkOpPtT* contained = work->contains(coinSeg); + if (!contained) { + if (work->final()) { + break; + } + continue; + } + if (work->t() <= t) { + coinStart = contained; + foundStart = work->ptT(); + } + if (work->t() >= t) { + coinEnd = contained; + foundEnd = work->ptT(); + break; + } + SkASSERT(work->ptT() != overE); + } while ((work = work->upCast()->next())); + if (!coinStart || !coinEnd) { + return 1; + } + // while overS->fT <=t and overS contains coinSeg + double denom = foundEnd->fT - foundStart->fT; + double sRatio = denom ? (t - foundStart->fT) / denom : 1; + return coinStart->fT + (coinEnd->fT - coinStart->fT) * sRatio; +} + +// return true if span overlaps existing and needs to adjust the coincident list +bool SkOpCoincidence::checkOverlap(SkCoincidentSpans* check, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + SkTDArray<SkCoincidentSpans*>* overlaps) const { + if (!Ordered(coinSeg, oppSeg)) { + if (oppTs < oppTe) { + return this->checkOverlap(check, oppSeg, coinSeg, oppTs, oppTe, coinTs, coinTe, + overlaps); + } + return this->checkOverlap(check, oppSeg, coinSeg, oppTe, oppTs, coinTe, coinTs, overlaps); + } + bool swapOpp = oppTs > oppTe; + if (swapOpp) { + SkTSwap(oppTs, oppTe); + } + do { + if (check->coinPtTStart()->segment() != coinSeg) { + continue; + } + if (check->oppPtTStart()->segment() != oppSeg) { + continue; + } + double checkTs = check->coinPtTStart()->fT; + double checkTe = check->coinPtTEnd()->fT; + bool coinOutside = coinTe < checkTs || coinTs > checkTe; + double oCheckTs = check->oppPtTStart()->fT; + double oCheckTe = check->oppPtTEnd()->fT; + if (swapOpp) { + if (oCheckTs <= oCheckTe) { + return false; + } + SkTSwap(oCheckTs, oCheckTe); + } + bool oppOutside = oppTe < oCheckTs || oppTs > oCheckTe; + if (coinOutside && oppOutside) { + continue; + } + bool coinInside = coinTe <= checkTe && coinTs >= checkTs; + bool oppInside = oppTe <= oCheckTe && oppTs >= oCheckTs; + if (coinInside && oppInside) { // already included, do nothing + return false; + } + *overlaps->append() = check; // partial overlap, extend existing entry + } while ((check = check->next())); + return true; +} + +/* Please keep this in sync with debugAddIfMissing() */ +// note that over1s, over1e, over2s, over2e are ordered +bool SkOpCoincidence::addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over2s, + double tStart, double tEnd, SkOpSegment* coinSeg, SkOpSegment* oppSeg, bool* added + SkDEBUGPARAMS(const SkOpPtT* over1e) SkDEBUGPARAMS(const SkOpPtT* over2e)) { + SkASSERT(tStart < tEnd); + SkASSERT(over1s->fT < over1e->fT); + SkASSERT(between(over1s->fT, tStart, over1e->fT)); + SkASSERT(between(over1s->fT, tEnd, over1e->fT)); + SkASSERT(over2s->fT < over2e->fT); + SkASSERT(between(over2s->fT, tStart, over2e->fT)); + SkASSERT(between(over2s->fT, tEnd, over2e->fT)); + SkASSERT(over1s->segment() == over1e->segment()); + SkASSERT(over2s->segment() == over2e->segment()); + SkASSERT(over1s->segment() == over2s->segment()); + SkASSERT(over1s->segment() != coinSeg); + SkASSERT(over1s->segment() != oppSeg); + SkASSERT(coinSeg != oppSeg); + double coinTs, coinTe, oppTs, oppTe; + coinTs = TRange(over1s, tStart, coinSeg SkDEBUGPARAMS(over1e)); + coinTe = TRange(over1s, tEnd, coinSeg SkDEBUGPARAMS(over1e)); + if (coinSeg->collapsed(coinTs, coinTe)) { + return true; + } + oppTs = TRange(over2s, tStart, oppSeg SkDEBUGPARAMS(over2e)); + oppTe = TRange(over2s, tEnd, oppSeg SkDEBUGPARAMS(over2e)); + if (oppSeg->collapsed(oppTs, oppTe)) { + return true; + } + if (coinTs > coinTe) { + SkTSwap(coinTs, coinTe); + SkTSwap(oppTs, oppTe); + } + return this->addOrOverlap(coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, added); +} + +/* Please keep this in sync with debugAddOrOverlap() */ +// If this is called by addEndMovedSpans(), a returned false propogates out to an abort. +// If this is called by AddIfMissing(), a returned false indicates there was nothing to add +bool SkOpCoincidence::addOrOverlap(SkOpSegment* coinSeg, SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, bool* added) { + SkTDArray<SkCoincidentSpans*> overlaps; + FAIL_IF(!fTop); + if (!this->checkOverlap(fTop, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &overlaps)) { + return true; + } + if (fHead && !this->checkOverlap(fHead, coinSeg, oppSeg, coinTs, + coinTe, oppTs, oppTe, &overlaps)) { + return true; + } + SkCoincidentSpans* overlap = overlaps.count() ? overlaps[0] : nullptr; + for (int index = 1; index < overlaps.count(); ++index) { // combine overlaps before continuing + SkCoincidentSpans* test = overlaps[index]; + if (overlap->coinPtTStart()->fT > test->coinPtTStart()->fT) { + overlap->setCoinPtTStart(test->coinPtTStart()); + } + if (overlap->coinPtTEnd()->fT < test->coinPtTEnd()->fT) { + overlap->setCoinPtTEnd(test->coinPtTEnd()); + } + if (overlap->flipped() + ? overlap->oppPtTStart()->fT < test->oppPtTStart()->fT + : overlap->oppPtTStart()->fT > test->oppPtTStart()->fT) { + overlap->setOppPtTStart(test->oppPtTStart()); + } + if (overlap->flipped() + ? overlap->oppPtTEnd()->fT > test->oppPtTEnd()->fT + : overlap->oppPtTEnd()->fT < test->oppPtTEnd()->fT) { + overlap->setOppPtTEnd(test->oppPtTEnd()); + } + if (!fHead || !this->release(fHead, test)) { + SkAssertResult(this->release(fTop, test)); + } + } + const SkOpPtT* cs = coinSeg->existing(coinTs, oppSeg); + const SkOpPtT* ce = coinSeg->existing(coinTe, oppSeg); + if (overlap && cs && ce && overlap->contains(cs, ce)) { + return true; + } + FAIL_IF(cs == ce && cs); + const SkOpPtT* os = oppSeg->existing(oppTs, coinSeg); + const SkOpPtT* oe = oppSeg->existing(oppTe, coinSeg); + if (overlap && os && oe && overlap->contains(os, oe)) { + return true; + } + SkASSERT(!cs || !cs->deleted()); + SkASSERT(!os || !os->deleted()); + SkASSERT(!ce || !ce->deleted()); + SkASSERT(!oe || !oe->deleted()); + const SkOpPtT* csExisting = !cs ? coinSeg->existing(coinTs, nullptr) : nullptr; + const SkOpPtT* ceExisting = !ce ? coinSeg->existing(coinTe, nullptr) : nullptr; + FAIL_IF(csExisting && csExisting == ceExisting); +// FAIL_IF(csExisting && (csExisting == ce || +// csExisting->contains(ceExisting ? ceExisting : ce))); + FAIL_IF(ceExisting && (ceExisting == cs || + ceExisting->contains(csExisting ? csExisting : cs))); + const SkOpPtT* osExisting = !os ? oppSeg->existing(oppTs, nullptr) : nullptr; + const SkOpPtT* oeExisting = !oe ? oppSeg->existing(oppTe, nullptr) : nullptr; + FAIL_IF(osExisting && osExisting == oeExisting); + FAIL_IF(osExisting && (osExisting == oe || + osExisting->contains(oeExisting ? oeExisting : oe))); + FAIL_IF(oeExisting && (oeExisting == os || + oeExisting->contains(osExisting ? osExisting : os))); + // extra line in debug code + this->debugValidate(); + if (!cs || !os) { + SkOpPtT* csWritable = cs ? const_cast<SkOpPtT*>(cs) + : coinSeg->addT(coinTs); + if (csWritable == ce) { + return true; + } + SkOpPtT* osWritable = os ? const_cast<SkOpPtT*>(os) + : oppSeg->addT(oppTs); + FAIL_IF(!csWritable || !osWritable); + csWritable->span()->addOpp(osWritable->span()); + cs = csWritable; + os = osWritable->active(); + FAIL_IF((ce && ce->deleted()) || (oe && oe->deleted())); + } + if (!ce || !oe) { + SkOpPtT* ceWritable = ce ? const_cast<SkOpPtT*>(ce) + : coinSeg->addT(coinTe); + SkOpPtT* oeWritable = oe ? const_cast<SkOpPtT*>(oe) + : oppSeg->addT(oppTe); + ceWritable->span()->addOpp(oeWritable->span()); + ce = ceWritable; + oe = oeWritable; + } + this->debugValidate(); + FAIL_IF(cs->deleted()); + FAIL_IF(os->deleted()); + FAIL_IF(ce->deleted()); + FAIL_IF(oe->deleted()); + FAIL_IF(cs->contains(ce) || os->contains(oe)); + bool result = true; + if (overlap) { + if (overlap->coinPtTStart()->segment() == coinSeg) { + result = overlap->extend(cs, ce, os, oe); + } else { + if (os->fT > oe->fT) { + SkTSwap(cs, ce); + SkTSwap(os, oe); + } + result = overlap->extend(os, oe, cs, ce); + } +#if DEBUG_COINCIDENCE_VERBOSE + if (result) { + overlaps[0]->debugShow(); + } +#endif + } else { + this->add(cs, ce, os, oe); +#if DEBUG_COINCIDENCE_VERBOSE + fHead->debugShow(); +#endif + } + this->debugValidate(); + if (result) { + *added = true; + } + return true; +} + +// Please keep this in sync with debugAddMissing() +/* detects overlaps of different coincident runs on same segment */ +/* does not detect overlaps for pairs without any segments in common */ +// returns true if caller should loop again +bool SkOpCoincidence::addMissing(bool* added DEBUG_COIN_DECLARE_PARAMS()) { + SkCoincidentSpans* outer = fHead; + *added = false; + if (!outer) { + return true; + } + fTop = outer; + fHead = nullptr; + do { + // addifmissing can modify the list that this is walking + // save head so that walker can iterate over old data unperturbed + // addifmissing adds to head freely then add saved head in the end + const SkOpPtT* ocs = outer->coinPtTStart(); + SkASSERT(!ocs->deleted()); + const SkOpSegment* outerCoin = ocs->segment(); + SkASSERT(!outerCoin->done()); // if it's done, should have already been removed from list + const SkOpPtT* oos = outer->oppPtTStart(); + if (oos->deleted()) { + return true; + } + const SkOpSegment* outerOpp = oos->segment(); + SkASSERT(!outerOpp->done()); + SkOpSegment* outerCoinWritable = const_cast<SkOpSegment*>(outerCoin); + SkOpSegment* outerOppWritable = const_cast<SkOpSegment*>(outerOpp); + SkCoincidentSpans* inner = outer; + while ((inner = inner->next())) { + this->debugValidate(); + double overS, overE; + const SkOpPtT* ics = inner->coinPtTStart(); + SkASSERT(!ics->deleted()); + const SkOpSegment* innerCoin = ics->segment(); + SkASSERT(!innerCoin->done()); + const SkOpPtT* ios = inner->oppPtTStart(); + SkASSERT(!ios->deleted()); + const SkOpSegment* innerOpp = ios->segment(); + SkASSERT(!innerOpp->done()); + SkOpSegment* innerCoinWritable = const_cast<SkOpSegment*>(innerCoin); + SkOpSegment* innerOppWritable = const_cast<SkOpSegment*>(innerOpp); + if (outerCoin == innerCoin) { + const SkOpPtT* oce = outer->coinPtTEnd(); + if (oce->deleted()) { + return true; + } + const SkOpPtT* ice = inner->coinPtTEnd(); + SkASSERT(!ice->deleted()); + if (outerOpp != innerOpp && this->overlap(ocs, oce, ics, ice, &overS, &overE)) { + (void) this->addIfMissing(ocs->starter(oce), ics->starter(ice), + overS, overE, outerOppWritable, innerOppWritable, added + SkDEBUGPARAMS(ocs->debugEnder(oce)) + SkDEBUGPARAMS(ics->debugEnder(ice))); + } + } else if (outerCoin == innerOpp) { + const SkOpPtT* oce = outer->coinPtTEnd(); + SkASSERT(!oce->deleted()); + const SkOpPtT* ioe = inner->oppPtTEnd(); + SkASSERT(!ioe->deleted()); + if (outerOpp != innerCoin && this->overlap(ocs, oce, ios, ioe, &overS, &overE)) { + (void) this->addIfMissing(ocs->starter(oce), ios->starter(ioe), + overS, overE, outerOppWritable, innerCoinWritable, added + SkDEBUGPARAMS(ocs->debugEnder(oce)) + SkDEBUGPARAMS(ios->debugEnder(ioe))); + } + } else if (outerOpp == innerCoin) { + const SkOpPtT* ooe = outer->oppPtTEnd(); + SkASSERT(!ooe->deleted()); + const SkOpPtT* ice = inner->coinPtTEnd(); + SkASSERT(!ice->deleted()); + SkASSERT(outerCoin != innerOpp); + if (this->overlap(oos, ooe, ics, ice, &overS, &overE)) { + (void) this->addIfMissing(oos->starter(ooe), ics->starter(ice), + overS, overE, outerCoinWritable, innerOppWritable, added + SkDEBUGPARAMS(oos->debugEnder(ooe)) + SkDEBUGPARAMS(ics->debugEnder(ice))); + } + } else if (outerOpp == innerOpp) { + const SkOpPtT* ooe = outer->oppPtTEnd(); + SkASSERT(!ooe->deleted()); + const SkOpPtT* ioe = inner->oppPtTEnd(); + if (ioe->deleted()) { + return true; + } + SkASSERT(outerCoin != innerCoin); + if (this->overlap(oos, ooe, ios, ioe, &overS, &overE)) { + (void) this->addIfMissing(oos->starter(ooe), ios->starter(ioe), + overS, overE, outerCoinWritable, innerCoinWritable, added + SkDEBUGPARAMS(oos->debugEnder(ooe)) + SkDEBUGPARAMS(ios->debugEnder(ioe))); + } + } + this->debugValidate(); + } + } while ((outer = outer->next())); + this->restoreHead(); + return true; +} + +bool SkOpCoincidence::addOverlap(const SkOpSegment* seg1, const SkOpSegment* seg1o, + const SkOpSegment* seg2, const SkOpSegment* seg2o, + const SkOpPtT* overS, const SkOpPtT* overE) { + const SkOpPtT* s1 = overS->find(seg1); + const SkOpPtT* e1 = overE->find(seg1); + if (!s1->starter(e1)->span()->upCast()->windValue()) { + s1 = overS->find(seg1o); + e1 = overE->find(seg1o); + if (!s1->starter(e1)->span()->upCast()->windValue()) { + return true; + } + } + const SkOpPtT* s2 = overS->find(seg2); + const SkOpPtT* e2 = overE->find(seg2); + if (!s2->starter(e2)->span()->upCast()->windValue()) { + s2 = overS->find(seg2o); + e2 = overE->find(seg2o); + if (!s2->starter(e2)->span()->upCast()->windValue()) { + return true; + } + } + if (s1->segment() == s2->segment()) { + return true; + } + if (s1->fT > e1->fT) { + SkTSwap(s1, e1); + SkTSwap(s2, e2); + } + this->add(s1, e1, s2, e2); + return true; +} + +bool SkOpCoincidence::contains(const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const { + if (this->contains(fHead, seg, opp, oppT)) { + return true; + } + if (this->contains(fTop, seg, opp, oppT)) { + return true; + } + return false; +} + +bool SkOpCoincidence::contains(const SkCoincidentSpans* coin, const SkOpSegment* seg, + const SkOpSegment* opp, double oppT) const { + if (!coin) { + return false; + } + do { + if (coin->coinPtTStart()->segment() == seg && coin->oppPtTStart()->segment() == opp + && between(coin->oppPtTStart()->fT, oppT, coin->oppPtTEnd()->fT)) { + return true; + } + if (coin->oppPtTStart()->segment() == seg && coin->coinPtTStart()->segment() == opp + && between(coin->coinPtTStart()->fT, oppT, coin->coinPtTEnd()->fT)) { + return true; + } + } while ((coin = coin->next())); + return false; +} + +bool SkOpCoincidence::contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const { + const SkCoincidentSpans* test = fHead; + if (!test) { + return false; + } + const SkOpSegment* coinSeg = coinPtTStart->segment(); + const SkOpSegment* oppSeg = oppPtTStart->segment(); + if (!Ordered(coinPtTStart, oppPtTStart)) { + SkTSwap(coinSeg, oppSeg); + SkTSwap(coinPtTStart, oppPtTStart); + SkTSwap(coinPtTEnd, oppPtTEnd); + if (coinPtTStart->fT > coinPtTEnd->fT) { + SkTSwap(coinPtTStart, coinPtTEnd); + SkTSwap(oppPtTStart, oppPtTEnd); + } + } + double oppMinT = SkTMin(oppPtTStart->fT, oppPtTEnd->fT); + double oppMaxT = SkTMax(oppPtTStart->fT, oppPtTEnd->fT); + do { + if (coinSeg != test->coinPtTStart()->segment()) { + continue; + } + if (coinPtTStart->fT < test->coinPtTStart()->fT) { + continue; + } + if (coinPtTEnd->fT > test->coinPtTEnd()->fT) { + continue; + } + if (oppSeg != test->oppPtTStart()->segment()) { + continue; + } + if (oppMinT < SkTMin(test->oppPtTStart()->fT, test->oppPtTEnd()->fT)) { + continue; + } + if (oppMaxT > SkTMax(test->oppPtTStart()->fT, test->oppPtTEnd()->fT)) { + continue; + } + return true; + } while ((test = test->next())); + return false; +} + +void SkOpCoincidence::correctEnds(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + coin->correctEnds(); + } while ((coin = coin->next())); +} + +// walk span sets in parallel, moving winding from one to the other +void SkOpCoincidence::apply(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast(); + if (start->deleted()) { + continue; + } + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); + SkASSERT(start == start->starter(end)); + bool flipped = coin->flipped(); + SkOpSpan* oStart = (flipped ? coin->oppPtTEndWritable() + : coin->oppPtTStartWritable())->span()->upCast(); + if (oStart->deleted()) { + continue; + } + const SkOpSpanBase* oEnd = (flipped ? coin->oppPtTStart() : coin->oppPtTEnd())->span(); + SkASSERT(oStart == oStart->starter(oEnd)); + SkOpSegment* segment = start->segment(); + SkOpSegment* oSegment = oStart->segment(); + bool operandSwap = segment->operand() != oSegment->operand(); + if (flipped) { + if (oEnd->deleted()) { + continue; + } + do { + SkOpSpanBase* oNext = oStart->next(); + if (oNext == oEnd) { + break; + } + oStart = oNext->upCast(); + } while (true); + } + do { + int windValue = start->windValue(); + int oppValue = start->oppValue(); + int oWindValue = oStart->windValue(); + int oOppValue = oStart->oppValue(); + // winding values are added or subtracted depending on direction and wind type + // same or opposite values are summed depending on the operand value + int windDiff = operandSwap ? oOppValue : oWindValue; + int oWindDiff = operandSwap ? oppValue : windValue; + if (!flipped) { + windDiff = -windDiff; + oWindDiff = -oWindDiff; + } + bool addToStart = windValue && (windValue > windDiff || (windValue == windDiff + && oWindValue <= oWindDiff)); + if (addToStart ? start->done() : oStart->done()) { + addToStart ^= true; + } + if (addToStart) { + if (operandSwap) { + SkTSwap(oWindValue, oOppValue); + } + if (flipped) { + windValue -= oWindValue; + oppValue -= oOppValue; + } else { + windValue += oWindValue; + oppValue += oOppValue; + } + if (segment->isXor()) { + windValue &= 1; + } + if (segment->oppXor()) { + oppValue &= 1; + } + oWindValue = oOppValue = 0; + } else { + if (operandSwap) { + SkTSwap(windValue, oppValue); + } + if (flipped) { + oWindValue -= windValue; + oOppValue -= oppValue; + } else { + oWindValue += windValue; + oOppValue += oppValue; + } + if (oSegment->isXor()) { + oWindValue &= 1; + } + if (oSegment->oppXor()) { + oOppValue &= 1; + } + windValue = oppValue = 0; + } +#if 0 && DEBUG_COINCIDENCE + SkDebugf("seg=%d span=%d windValue=%d oppValue=%d\n", segment->debugID(), + start->debugID(), windValue, oppValue); + SkDebugf("seg=%d span=%d windValue=%d oppValue=%d\n", oSegment->debugID(), + oStart->debugID(), oWindValue, oOppValue); +#endif + start->setWindValue(windValue); + start->setOppValue(oppValue); + oStart->setWindValue(oWindValue); + oStart->setOppValue(oOppValue); + if (!windValue && !oppValue) { + segment->markDone(start); + } + if (!oWindValue && !oOppValue) { + oSegment->markDone(oStart); + } + SkOpSpanBase* next = start->next(); + SkOpSpanBase* oNext = flipped ? oStart->prev() : oStart->next(); + if (next == end) { + break; + } + start = next->upCast(); + // if the opposite ran out too soon, just reuse the last span + if (!oNext || !oNext->upCastable()) { + oNext = oStart; + } + oStart = oNext->upCast(); + } while (true); + } while ((coin = coin->next())); +} + +// Please keep this in sync with debugRelease() +bool SkOpCoincidence::release(SkCoincidentSpans* coin, SkCoincidentSpans* remove) { + SkCoincidentSpans* head = coin; + SkCoincidentSpans* prev = nullptr; + SkCoincidentSpans* next; + do { + next = coin->next(); + if (coin == remove) { + if (prev) { + prev->setNext(next); + } else if (head == fHead) { + fHead = next; + } else { + fTop = next; + } + break; + } + prev = coin; + } while ((coin = next)); + return coin != nullptr; +} + +void SkOpCoincidence::releaseDeleted(SkCoincidentSpans* coin) { + if (!coin) { + return; + } + SkCoincidentSpans* head = coin; + SkCoincidentSpans* prev = nullptr; + SkCoincidentSpans* next; + do { + next = coin->next(); + if (coin->coinPtTStart()->deleted()) { + SkOPASSERT(coin->flipped() ? coin->oppPtTEnd()->deleted() : + coin->oppPtTStart()->deleted()); + if (prev) { + prev->setNext(next); + } else if (head == fHead) { + fHead = next; + } else { + fTop = next; + } + } else { + SkOPASSERT(coin->flipped() ? !coin->oppPtTEnd()->deleted() : + !coin->oppPtTStart()->deleted()); + prev = coin; + } + } while ((coin = next)); +} + +void SkOpCoincidence::releaseDeleted() { + this->releaseDeleted(fHead); + this->releaseDeleted(fTop); +} + +void SkOpCoincidence::restoreHead() { + SkCoincidentSpans** headPtr = &fHead; + while (*headPtr) { + headPtr = (*headPtr)->nextPtr(); + } + *headPtr = fTop; + fTop = nullptr; + // segments may have collapsed in the meantime; remove empty referenced segments + headPtr = &fHead; + while (*headPtr) { + SkCoincidentSpans* test = *headPtr; + if (test->coinPtTStart()->segment()->done() || test->oppPtTStart()->segment()->done()) { + *headPtr = test->next(); + continue; + } + headPtr = (*headPtr)->nextPtr(); + } +} + +// Please keep this in sync with debugExpand() +// expand the range by checking adjacent spans for coincidence +bool SkOpCoincidence::expand(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = fHead; + if (!coin) { + return false; + } + bool expanded = false; + do { + if (coin->expand()) { + // check to see if multiple spans expanded so they are now identical + SkCoincidentSpans* test = fHead; + do { + if (coin == test) { + continue; + } + if (coin->coinPtTStart() == test->coinPtTStart() + && coin->oppPtTStart() == test->oppPtTStart()) { + this->release(fHead, test); + break; + } + } while ((test = test->next())); + expanded = true; + } + } while ((coin = coin->next())); + return expanded; +} + +void SkOpCoincidence::findOverlaps(SkOpCoincidence* overlaps DEBUG_COIN_DECLARE_PARAMS()) const { + DEBUG_SET_PHASE(); + overlaps->fHead = overlaps->fTop = nullptr; + SkCoincidentSpans* outer = fHead; + while (outer) { + const SkOpSegment* outerCoin = outer->coinPtTStart()->segment(); + const SkOpSegment* outerOpp = outer->oppPtTStart()->segment(); + SkCoincidentSpans* inner = outer; + while ((inner = inner->next())) { + const SkOpSegment* innerCoin = inner->coinPtTStart()->segment(); + if (outerCoin == innerCoin) { + continue; // both winners are the same segment, so there's no additional overlap + } + const SkOpSegment* innerOpp = inner->oppPtTStart()->segment(); + const SkOpPtT* overlapS; + const SkOpPtT* overlapE; + if ((outerOpp == innerCoin && SkOpPtT::Overlaps(outer->oppPtTStart(), + outer->oppPtTEnd(),inner->coinPtTStart(), inner->coinPtTEnd(), &overlapS, + &overlapE)) + || (outerCoin == innerOpp && SkOpPtT::Overlaps(outer->coinPtTStart(), + outer->coinPtTEnd(), inner->oppPtTStart(), inner->oppPtTEnd(), + &overlapS, &overlapE)) + || (outerOpp == innerOpp && SkOpPtT::Overlaps(outer->oppPtTStart(), + outer->oppPtTEnd(), inner->oppPtTStart(), inner->oppPtTEnd(), + &overlapS, &overlapE))) { + SkAssertResult(overlaps->addOverlap(outerCoin, outerOpp, innerCoin, innerOpp, + overlapS, overlapE)); + } + } + outer = outer->next(); + } +} + +void SkOpCoincidence::fixUp(SkOpPtT* deleted, const SkOpPtT* kept) { + SkOPASSERT(deleted != kept); + if (fHead) { + this->fixUp(fHead, deleted, kept); + } + if (fTop) { + this->fixUp(fTop, deleted, kept); + } +} + +void SkOpCoincidence::fixUp(SkCoincidentSpans* coin, SkOpPtT* deleted, const SkOpPtT* kept) { + SkCoincidentSpans* head = coin; + do { + if (coin->coinPtTStart() == deleted) { + if (coin->coinPtTEnd()->span() == kept->span()) { + this->release(head, coin); + continue; + } + coin->setCoinPtTStart(kept); + } + if (coin->coinPtTEnd() == deleted) { + if (coin->coinPtTStart()->span() == kept->span()) { + this->release(head, coin); + continue; + } + coin->setCoinPtTEnd(kept); + } + if (coin->oppPtTStart() == deleted) { + if (coin->oppPtTEnd()->span() == kept->span()) { + this->release(head, coin); + continue; + } + coin->setOppPtTStart(kept); + } + if (coin->oppPtTEnd() == deleted) { + if (coin->oppPtTStart()->span() == kept->span()) { + this->release(head, coin); + continue; + } + coin->setOppPtTEnd(kept); + } + } while ((coin = coin->next())); +} + +// Please keep this in sync with debugMark() +/* this sets up the coincidence links in the segments when the coincidence crosses multiple spans */ +void SkOpCoincidence::mark(DEBUG_COIN_DECLARE_ONLY_PARAMS()) { + DEBUG_SET_PHASE(); + SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast(); + SkASSERT(!start->deleted()); + SkOpSpanBase* end = coin->coinPtTEndWritable()->span(); + SkASSERT(!end->deleted()); + SkOpSpanBase* oStart = coin->oppPtTStartWritable()->span(); + SkASSERT(!oStart->deleted()); + SkOpSpanBase* oEnd = coin->oppPtTEndWritable()->span(); + SkASSERT(!oEnd->deleted()); + bool flipped = coin->flipped(); + if (flipped) { + SkTSwap(oStart, oEnd); + } + /* coin and opp spans may not match up. Mark the ends, and then let the interior + get marked as many times as the spans allow */ + start->insertCoincidence(oStart->upCast()); + end->insertCoinEnd(oEnd); + const SkOpSegment* segment = start->segment(); + const SkOpSegment* oSegment = oStart->segment(); + SkOpSpanBase* next = start; + SkOpSpanBase* oNext = oStart; + bool ordered = coin->ordered(); + while ((next = next->upCast()->next()) != end) { + SkAssertResult(next->upCast()->insertCoincidence(oSegment, flipped, ordered)); + } + while ((oNext = oNext->upCast()->next()) != oEnd) { + SkAssertResult(oNext->upCast()->insertCoincidence(segment, flipped, ordered)); + } + } while ((coin = coin->next())); +} + +// Please keep in sync with debugMarkCollapsed() +void SkOpCoincidence::markCollapsed(SkCoincidentSpans* coin, SkOpPtT* test) { + SkCoincidentSpans* head = coin; + while (coin) { + if (coin->collapsed(test)) { + if (zero_or_one(coin->coinPtTStart()->fT) && zero_or_one(coin->coinPtTEnd()->fT)) { + coin->coinPtTStartWritable()->segment()->markAllDone(); + } + if (zero_or_one(coin->oppPtTStart()->fT) && zero_or_one(coin->oppPtTEnd()->fT)) { + coin->oppPtTStartWritable()->segment()->markAllDone(); + } + this->release(head, coin); + } + coin = coin->next(); + } +} + +// Please keep in sync with debugMarkCollapsed() +void SkOpCoincidence::markCollapsed(SkOpPtT* test) { + markCollapsed(fHead, test); + markCollapsed(fTop, test); +} + +bool SkOpCoincidence::Ordered(const SkOpSegment* coinSeg, const SkOpSegment* oppSeg) { + if (coinSeg->verb() < oppSeg->verb()) { + return true; + } + if (coinSeg->verb() > oppSeg->verb()) { + return false; + } + int count = (SkPathOpsVerbToPoints(coinSeg->verb()) + 1) * 2; + const SkScalar* cPt = &coinSeg->pts()[0].fX; + const SkScalar* oPt = &oppSeg->pts()[0].fX; + for (int index = 0; index < count; ++index) { + if (*cPt < *oPt) { + return true; + } + if (*cPt > *oPt) { + return false; + } + ++cPt; + ++oPt; + } + return true; +} + +bool SkOpCoincidence::overlap(const SkOpPtT* coin1s, const SkOpPtT* coin1e, + const SkOpPtT* coin2s, const SkOpPtT* coin2e, double* overS, double* overE) const { + SkASSERT(coin1s->segment() == coin2s->segment()); + *overS = SkTMax(SkTMin(coin1s->fT, coin1e->fT), SkTMin(coin2s->fT, coin2e->fT)); + *overE = SkTMin(SkTMax(coin1s->fT, coin1e->fT), SkTMax(coin2s->fT, coin2e->fT)); + return *overS < *overE; +} + +// Commented-out lines keep this in sync with debugRelease() +void SkOpCoincidence::release(const SkOpSegment* deleted) { + SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + if (coin->coinPtTStart()->segment() == deleted + || coin->coinPtTEnd()->segment() == deleted + || coin->oppPtTStart()->segment() == deleted + || coin->oppPtTEnd()->segment() == deleted) { + this->release(fHead, coin); + } + } while ((coin = coin->next())); +} diff --git a/gfx/skia/skia/src/pathops/SkOpCoincidence.h b/gfx/skia/skia/src/pathops/SkOpCoincidence.h new file mode 100644 index 000000000..244035323 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpCoincidence.h @@ -0,0 +1,303 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpCoincidence_DEFINED +#define SkOpCoincidence_DEFINED + +#include "SkTDArray.h" +#include "SkOpTAllocator.h" +#include "SkOpSpan.h" +#include "SkPathOpsTypes.h" + +class SkOpPtT; +class SkOpSpanBase; + +class SkCoincidentSpans { +public: + const SkOpPtT* coinPtTEnd() const { return fCoinPtTEnd; } + const SkOpPtT* coinPtTStart() const { return fCoinPtTStart; } + + // These return non-const pointers so that, as copies, they can be added + // to a new span pair + SkOpPtT* coinPtTEndWritable() const { return const_cast<SkOpPtT*>(fCoinPtTEnd); } + SkOpPtT* coinPtTStartWritable() const { return const_cast<SkOpPtT*>(fCoinPtTStart); } + + bool collapsed(const SkOpPtT* ) const; + bool contains(const SkOpPtT* s, const SkOpPtT* e) const; + void correctEnds(); + void correctOneEnd(const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::* setEnd)(const SkOpPtT* ptT) ); + +#if DEBUG_COIN + void debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const; + void debugCorrectOneEnd(SkPathOpsDebug::GlitchLog* log, + const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::* setEnd)(const SkOpPtT* ptT) const) const; + bool debugExpand(SkPathOpsDebug::GlitchLog* log) const; +#endif + + const char* debugID() const { +#if DEBUG_COIN + return fGlobalState->debugCoinDictEntry().fFunctionName; +#else + return nullptr; +#endif + } + + void debugShow() const; +#ifdef SK_DEBUG + void debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, + const SkOpGlobalState* debugState) const; +#endif + void dump() const; + bool expand(); + bool extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); + bool flipped() const { return fOppPtTStart->fT > fOppPtTEnd->fT; } + SkDEBUGCODE(SkOpGlobalState* globalState() { return fGlobalState; }) + + void init(SkDEBUGCODE(SkOpGlobalState* globalState)) { + sk_bzero(this, sizeof(*this)); + SkDEBUGCODE(fGlobalState = globalState); + } + + SkCoincidentSpans* next() { return fNext; } + const SkCoincidentSpans* next() const { return fNext; } + SkCoincidentSpans** nextPtr() { return &fNext; } + const SkOpPtT* oppPtTStart() const { return fOppPtTStart; } + const SkOpPtT* oppPtTEnd() const { return fOppPtTEnd; } + // These return non-const pointers so that, as copies, they can be added + // to a new span pair + SkOpPtT* oppPtTStartWritable() const { return const_cast<SkOpPtT*>(fOppPtTStart); } + SkOpPtT* oppPtTEndWritable() const { return const_cast<SkOpPtT*>(fOppPtTEnd); } + bool ordered() const; + + void set(SkCoincidentSpans* next, const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd); + + void setCoinPtTEnd(const SkOpPtT* ptT) { + SkOPASSERT(ptT == ptT->span()->ptT()); + SkASSERT(!fCoinPtTStart || ptT->fT != fCoinPtTStart->fT); + SkASSERT(!fCoinPtTStart || fCoinPtTStart->segment() == ptT->segment()); + fCoinPtTEnd = ptT; + ptT->setCoincident(); + } + + void setCoinPtTStart(const SkOpPtT* ptT) { + SkASSERT(ptT == ptT->span()->ptT()); + SkOPASSERT(!fCoinPtTEnd || ptT->fT != fCoinPtTEnd->fT); + SkASSERT(!fCoinPtTEnd || fCoinPtTEnd->segment() == ptT->segment()); + fCoinPtTStart = ptT; + ptT->setCoincident(); + } + + void setEnds(const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTEnd) { + this->setCoinPtTEnd(coinPtTEnd); + this->setOppPtTEnd(oppPtTEnd); + } + + void setOppPtTEnd(const SkOpPtT* ptT) { + SkOPASSERT(ptT == ptT->span()->ptT()); + SkOPASSERT(!fOppPtTStart || ptT->fT != fOppPtTStart->fT); + SkASSERT(!fOppPtTStart || fOppPtTStart->segment() == ptT->segment()); + fOppPtTEnd = ptT; + ptT->setCoincident(); + } + + void setOppPtTStart(const SkOpPtT* ptT) { + SkASSERT(ptT == ptT->span()->ptT()); + SkASSERT(!fOppPtTEnd || ptT->fT != fOppPtTEnd->fT); + SkASSERT(!fOppPtTEnd || fOppPtTEnd->segment() == ptT->segment()); + fOppPtTStart = ptT; + ptT->setCoincident(); + } + + void setStarts(const SkOpPtT* coinPtTStart, const SkOpPtT* oppPtTStart) { + this->setCoinPtTStart(coinPtTStart); + this->setOppPtTStart(oppPtTStart); + } + + void setNext(SkCoincidentSpans* next) { fNext = next; } + +private: + SkCoincidentSpans* fNext; + const SkOpPtT* fCoinPtTStart; + const SkOpPtT* fCoinPtTEnd; + const SkOpPtT* fOppPtTStart; + const SkOpPtT* fOppPtTEnd; + SkDEBUGCODE(SkOpGlobalState* fGlobalState); +}; + +class SkOpCoincidence { +public: + SkOpCoincidence(SkOpGlobalState* globalState) + : fHead(nullptr) + , fTop(nullptr) + , fGlobalState(globalState) + , fContinue(false) + , fSpanDeleted(false) + , fPtAllocated(false) + , fCoinExtended(false) + , fSpanMerged(false) { + globalState->setCoincidence(this); + } + + void add(SkOpPtT* coinPtTStart, SkOpPtT* coinPtTEnd, SkOpPtT* oppPtTStart, + SkOpPtT* oppPtTEnd); + bool addEndMovedSpans(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + bool addExpanded(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + bool addMissing(bool* added DEBUG_COIN_DECLARE_PARAMS()); + void apply(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + bool contains(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, + const SkOpPtT* oppPtTStart, const SkOpPtT* oppPtTEnd) const; + void correctEnds(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + +#if DEBUG_COIN + void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log) const; + void debugAddExpanded(SkPathOpsDebug::GlitchLog* ) const; + void debugAddMissing(SkPathOpsDebug::GlitchLog* , bool* added) const; + void debugAddOrOverlap(SkPathOpsDebug::GlitchLog* log, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + bool* added) const; +#endif + + const SkOpAngle* debugAngle(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugAngle(id), nullptr); + } + + void debugCheckBetween() const; + +#if DEBUG_COIN + void debugCheckValid(SkPathOpsDebug::GlitchLog* log) const; +#endif + + SkOpContour* debugContour(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugContour(id), nullptr); + } + +#if DEBUG_COIN + void debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const; + bool debugExpand(SkPathOpsDebug::GlitchLog* ) const; + void debugMark(SkPathOpsDebug::GlitchLog* ) const; + void debugMarkCollapsed(SkPathOpsDebug::GlitchLog* , + const SkCoincidentSpans* coin, const SkOpPtT* test) const; + void debugMarkCollapsed(SkPathOpsDebug::GlitchLog* , const SkOpPtT* test) const; +#endif + + const SkOpPtT* debugPtT(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugPtT(id), nullptr); + } + + const SkOpSegment* debugSegment(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugSegment(id), nullptr); + } + +#if DEBUG_COIN + void debugRelease(SkPathOpsDebug::GlitchLog* , const SkCoincidentSpans* , + const SkCoincidentSpans* ) const; + void debugRelease(SkPathOpsDebug::GlitchLog* , const SkOpSegment* ) const; +#endif + void debugShowCoincidence() const; + + const SkOpSpanBase* debugSpan(int id) const { + return SkDEBUGRELEASE(fGlobalState->debugSpan(id), nullptr); + } + + void debugValidate() const; + void dump() const; + bool expand(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + bool extend(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, + const SkOpPtT* oppPtTEnd); + void findOverlaps(SkOpCoincidence* DEBUG_COIN_DECLARE_PARAMS()) const; + void fixUp(SkOpPtT* deleted, const SkOpPtT* kept); + + SkOpGlobalState* globalState() { + return fGlobalState; + } + + const SkOpGlobalState* globalState() const { + return fGlobalState; + } + + bool isEmpty() const { + return !fHead && !fTop; + } + + void mark(DEBUG_COIN_DECLARE_ONLY_PARAMS()); + void markCollapsed(SkOpPtT* ); + + static bool Ordered(const SkOpPtT* coinPtTStart, const SkOpPtT* oppPtTStart) { + return Ordered(coinPtTStart->segment(), oppPtTStart->segment()); + } + + static bool Ordered(const SkOpSegment* coin, const SkOpSegment* opp); + void release(const SkOpSegment* ); + void releaseDeleted(); + +private: + void add(const SkOpPtT* coinPtTStart, const SkOpPtT* coinPtTEnd, const SkOpPtT* oppPtTStart, + const SkOpPtT* oppPtTEnd) { + this->add(const_cast<SkOpPtT*>(coinPtTStart), const_cast<SkOpPtT*>(coinPtTEnd), + const_cast<SkOpPtT*>(oppPtTStart), const_cast<SkOpPtT*>(oppPtTEnd)); + } + + bool addEndMovedSpans(const SkOpSpan* base, const SkOpSpanBase* testSpan); + bool addEndMovedSpans(const SkOpPtT* ptT); + + bool addIfMissing(const SkOpPtT* over1s, const SkOpPtT* over2s, + double tStart, double tEnd, SkOpSegment* coinSeg, SkOpSegment* oppSeg, + bool* added + SkDEBUGPARAMS(const SkOpPtT* over1e) SkDEBUGPARAMS(const SkOpPtT* over2e)); + bool addOrOverlap(SkOpSegment* coinSeg, SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, bool* added); + bool addOverlap(const SkOpSegment* seg1, const SkOpSegment* seg1o, + const SkOpSegment* seg2, const SkOpSegment* seg2o, + const SkOpPtT* overS, const SkOpPtT* overE); + bool checkOverlap(SkCoincidentSpans* check, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, + SkTDArray<SkCoincidentSpans*>* overlaps) const; + bool contains(const SkOpSegment* seg, const SkOpSegment* opp, double oppT) const; + bool contains(const SkCoincidentSpans* coin, const SkOpSegment* seg, + const SkOpSegment* opp, double oppT) const; +#if DEBUG_COIN + void debugAddIfMissing(SkPathOpsDebug::GlitchLog* , + const SkCoincidentSpans* outer, const SkOpPtT* over1s, + const SkOpPtT* over1e) const; + void debugAddIfMissing(SkPathOpsDebug::GlitchLog* , + const SkOpPtT* over1s, const SkOpPtT* over2s, + double tStart, double tEnd, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, bool* added, + const SkOpPtT* over1e, const SkOpPtT* over2e) const; + void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* , + const SkOpSpan* base, const SkOpSpanBase* testSpan) const; + void debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* , + const SkOpPtT* ptT) const; +#endif + void fixUp(SkCoincidentSpans* coin, SkOpPtT* deleted, const SkOpPtT* kept); + void markCollapsed(SkCoincidentSpans* head, SkOpPtT* test); + bool overlap(const SkOpPtT* coinStart1, const SkOpPtT* coinEnd1, + const SkOpPtT* coinStart2, const SkOpPtT* coinEnd2, + double* overS, double* overE) const; + bool release(SkCoincidentSpans* coin, SkCoincidentSpans* ); + void releaseDeleted(SkCoincidentSpans* ); + void restoreHead(); + // return coinPtT->segment()->t mapped from overS->fT <= t <= overE->fT + static double TRange(const SkOpPtT* overS, double t, const SkOpSegment* coinPtT + SkDEBUGPARAMS(const SkOpPtT* overE)); + + SkCoincidentSpans* fHead; + SkCoincidentSpans* fTop; + SkOpGlobalState* fGlobalState; + bool fContinue; + bool fSpanDeleted; + bool fPtAllocated; + bool fCoinExtended; + bool fSpanMerged; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpContour.cpp b/gfx/skia/skia/src/pathops/SkOpContour.cpp new file mode 100644 index 000000000..981bd2957 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpContour.cpp @@ -0,0 +1,70 @@ +/* +* Copyright 2013 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ +#include "SkOpContour.h" +#include "SkOpTAllocator.h" +#include "SkPathWriter.h" +#include "SkReduceOrder.h" +#include "SkTSort.h" + +SkOpSegment* SkOpContour::addCurve(SkPath::Verb verb, const SkPoint pts[4], SkScalar weight) { + SkChunkAlloc* allocator = this->globalState()->allocator(); + switch (verb) { + case SkPath::kLine_Verb: { + SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 2); + memcpy(ptStorage, pts, sizeof(SkPoint) * 2); + return appendSegment().addLine(ptStorage, this); + } break; + case SkPath::kQuad_Verb: { + SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 3); + memcpy(ptStorage, pts, sizeof(SkPoint) * 3); + return appendSegment().addQuad(ptStorage, this); + } break; + case SkPath::kConic_Verb: { + SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 3); + memcpy(ptStorage, pts, sizeof(SkPoint) * 3); + return appendSegment().addConic(ptStorage, weight, this); + } break; + case SkPath::kCubic_Verb: { + SkPoint* ptStorage = SkOpTAllocator<SkPoint>::AllocateArray(allocator, 4); + memcpy(ptStorage, pts, sizeof(SkPoint) * 4); + return appendSegment().addCubic(ptStorage, this); + } break; + default: + SkASSERT(0); + } + return nullptr; +} + +void SkOpContour::toPath(SkPathWriter* path) const { + const SkOpSegment* segment = &fHead; + do { + SkAssertResult(segment->addCurveTo(segment->head(), segment->tail(), path)); + } while ((segment = segment->next())); + path->finishContour(); + path->assemble(); +} + +void SkOpContour::toReversePath(SkPathWriter* path) const { + const SkOpSegment* segment = fTail; + do { + SkAssertResult(segment->addCurveTo(segment->tail(), segment->head(), path)); + } while ((segment = segment->prev())); + path->finishContour(); + path->assemble(); +} + +SkOpSegment* SkOpContour::undoneSegment(SkOpSpanBase** startPtr, SkOpSpanBase** endPtr) { + SkOpSegment* segment = &fHead; + do { + if (segment->done()) { + continue; + } + segment->undoneSpan(startPtr, endPtr); + return segment; + } while ((segment = segment->next())); + return nullptr; +} diff --git a/gfx/skia/skia/src/pathops/SkOpContour.h b/gfx/skia/skia/src/pathops/SkOpContour.h new file mode 100644 index 000000000..dc07c5304 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpContour.h @@ -0,0 +1,431 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpContour_DEFINED +#define SkOpContour_DEFINED + +#include "SkOpSegment.h" +#include "SkTDArray.h" +#include "SkTSort.h" + +enum class SkOpRayDir; +struct SkOpRayHit; +class SkPathWriter; + +class SkOpContour { +public: + SkOpContour() { + reset(); + } + + ~SkOpContour() { + if (fNext) { + fNext->~SkOpContour(); + } + } + + bool operator<(const SkOpContour& rh) const { + return fBounds.fTop == rh.fBounds.fTop + ? fBounds.fLeft < rh.fBounds.fLeft + : fBounds.fTop < rh.fBounds.fTop; + } + + void addConic(SkPoint pts[3], SkScalar weight) { + appendSegment().addConic(pts, weight, this); + } + + void addCubic(SkPoint pts[4]) { + appendSegment().addCubic(pts, this); + } + + SkOpSegment* addCurve(SkPath::Verb verb, const SkPoint pts[4], SkScalar weight = 1); + + SkOpSegment* addLine(SkPoint pts[2]) { + SkASSERT(pts[0] != pts[1]); + return appendSegment().addLine(pts, this); + } + + void addQuad(SkPoint pts[3]) { + appendSegment().addQuad(pts, this); + } + + SkOpSegment& appendSegment() { + SkOpSegment* result = fCount++ + ? SkOpTAllocator<SkOpSegment>::Allocate(this->globalState()->allocator()) : &fHead; + result->setPrev(fTail); + if (fTail) { + fTail->setNext(result); + } + fTail = result; + return *result; + } + + const SkPathOpsBounds& bounds() const { + return fBounds; + } + + void calcAngles() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + do { + segment->calcAngles(); + } while ((segment = segment->next())); + } + + void complete() { + setBounds(); + } + + int count() const { + return fCount; + } + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + + int debugIndent() const { + return SkDEBUGRELEASE(fDebugIndent, 0); + } + + + const SkOpAngle* debugAngle(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugAngle(id), nullptr); + } + + const SkOpCoincidence* debugCoincidence() const { + return this->globalState()->coincidence(); + } + +#if DEBUG_COIN + void debugCheckHealth(SkPathOpsDebug::GlitchLog* ) const; +#endif + + SkOpContour* debugContour(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugContour(id), nullptr); + } + +#if DEBUG_COIN + void debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const; + void debugMoveMultiples(SkPathOpsDebug::GlitchLog* ) const; + void debugMoveNearby(SkPathOpsDebug::GlitchLog* log) const; +#endif + + const SkOpPtT* debugPtT(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugPtT(id), nullptr); + } + + const SkOpSegment* debugSegment(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugSegment(id), nullptr); + } + +#if DEBUG_ACTIVE_SPANS + void debugShowActiveSpans() { + SkOpSegment* segment = &fHead; + do { + segment->debugShowActiveSpans(); + } while ((segment = segment->next())); + } +#endif + + const SkOpSpanBase* debugSpan(int id) const { + return SkDEBUGRELEASE(this->globalState()->debugSpan(id), nullptr); + } + + SkOpGlobalState* globalState() const { + return fState; + } + + void debugValidate() const { +#if DEBUG_VALIDATE + const SkOpSegment* segment = &fHead; + const SkOpSegment* prior = nullptr; + do { + segment->debugValidate(); + SkASSERT(segment->prev() == prior); + prior = segment; + } while ((segment = segment->next())); + SkASSERT(prior == fTail); +#endif + } + + bool done() const { + return fDone; + } + + void dump() const; + void dumpAll() const; + void dumpAngles() const; + void dumpContours() const; + void dumpContoursAll() const; + void dumpContoursAngles() const; + void dumpContoursPts() const; + void dumpContoursPt(int segmentID) const; + void dumpContoursSegment(int segmentID) const; + void dumpContoursSpan(int segmentID) const; + void dumpContoursSpans() const; + void dumpPt(int ) const; + void dumpPts(const char* prefix = "seg") const; + void dumpPtsX(const char* prefix) const; + void dumpSegment(int ) const; + void dumpSegments(const char* prefix = "seg", SkPathOp op = (SkPathOp) -1) const; + void dumpSpan(int ) const; + void dumpSpans() const; + + const SkPoint& end() const { + return fTail->pts()[SkPathOpsVerbToPoints(fTail->verb())]; + } + + SkOpSpan* findSortableTop(SkOpContour* ); + + SkOpSegment* first() { + SkASSERT(fCount > 0); + return &fHead; + } + + const SkOpSegment* first() const { + SkASSERT(fCount > 0); + return &fHead; + } + + void indentDump() const { + SkDEBUGCODE(fDebugIndent += 2); + } + + void init(SkOpGlobalState* globalState, bool operand, bool isXor) { + fState = globalState; + fOperand = operand; + fXor = isXor; + SkDEBUGCODE(fID = globalState->nextContourID()); + } + + int isCcw() const { + return fCcw; + } + + bool isXor() const { + return fXor; + } + + void joinSegments() { + SkOpSegment* segment = &fHead; + SkOpSegment* next; + do { + next = segment->next(); + segment->joinEnds(next ? next : &fHead); + } while ((segment = next)); + } + + void markAllDone() { + SkOpSegment* segment = &fHead; + do { + segment->markAllDone(); + } while ((segment = segment->next())); + } + + // Please keep this aligned with debugMissingCoincidence() + bool missingCoincidence() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + bool result = false; + do { + if (segment->missingCoincidence()) { + result = true; + } + segment = segment->next(); + } while (segment); + return result; + } + + bool moveMultiples() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + do { + if (!segment->moveMultiples()) { + return false; + } + } while ((segment = segment->next())); + return true; + } + + void moveNearby() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + do { + segment->moveNearby(); + } while ((segment = segment->next())); + } + + SkOpContour* next() { + return fNext; + } + + const SkOpContour* next() const { + return fNext; + } + + bool operand() const { + return fOperand; + } + + bool oppXor() const { + return fOppXor; + } + + void outdentDump() const { + SkDEBUGCODE(fDebugIndent -= 2); + } + + void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkChunkAlloc* ); + + void reset() { + fTail = nullptr; + fNext = nullptr; + fCount = 0; + fDone = false; + SkDEBUGCODE(fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin)); + SkDEBUGCODE(fFirstSorted = -1); + SkDEBUGCODE(fDebugIndent = 0); + } + + void resetReverse() { + SkOpContour* next = this; + do { + next->fCcw = -1; + next->fReverse = false; + } while ((next = next->next())); + } + + bool reversed() const { + return fReverse; + } + + void setBounds() { + SkASSERT(fCount > 0); + const SkOpSegment* segment = &fHead; + fBounds = segment->bounds(); + while ((segment = segment->next())) { + fBounds.add(segment->bounds()); + } + } + + void setCcw(int ccw) { + fCcw = ccw; + } + + void setGlobalState(SkOpGlobalState* state) { + fState = state; + } + + void setNext(SkOpContour* contour) { +// SkASSERT(!fNext == !!contour); + fNext = contour; + } + + void setOperand(bool isOp) { + fOperand = isOp; + } + + void setOppXor(bool isOppXor) { + fOppXor = isOppXor; + } + + void setReverse() { + fReverse = true; + } + + void setXor(bool isXor) { + fXor = isXor; + } + + void sortAngles() { + SkASSERT(fCount > 0); + SkOpSegment* segment = &fHead; + do { + segment->sortAngles(); + } while ((segment = segment->next())); + } + + const SkPoint& start() const { + return fHead.pts()[0]; + } + + void toPartialBackward(SkPathWriter* path) const { + const SkOpSegment* segment = fTail; + do { + SkAssertResult(segment->addCurveTo(segment->tail(), segment->head(), path)); + } while ((segment = segment->prev())); + } + + void toPartialForward(SkPathWriter* path) const { + const SkOpSegment* segment = &fHead; + do { + SkAssertResult(segment->addCurveTo(segment->head(), segment->tail(), path)); + } while ((segment = segment->next())); + } + + void toReversePath(SkPathWriter* path) const; + void toPath(SkPathWriter* path) const; + SkOpSegment* undoneSegment(SkOpSpanBase** startPtr, SkOpSpanBase** endPtr); + +private: + SkOpGlobalState* fState; + SkOpSegment fHead; + SkOpSegment* fTail; + SkOpContour* fNext; + SkPathOpsBounds fBounds; + int fCcw; + int fCount; + int fFirstSorted; + bool fDone; // set by find top segment + bool fOperand; // true for the second argument to a binary operator + bool fReverse; // true if contour should be reverse written to path (used only by fix winding) + bool fXor; // set if original path had even-odd fill + bool fOppXor; // set if opposite path had even-odd fill + SkDEBUGCODE(int fID); + SkDEBUGCODE(mutable int fDebugIndent); +}; + +class SkOpContourHead : public SkOpContour { +public: + SkOpContour* appendContour() { + SkOpContour* contour = SkOpTAllocator<SkOpContour>::New(this->globalState()->allocator()); + contour->setNext(nullptr); + SkOpContour* prev = this; + SkOpContour* next; + while ((next = prev->next())) { + prev = next; + } + prev->setNext(contour); + return contour; + } + + void joinAllSegments() { + SkOpContour* next = this; + do { + next->joinSegments(); + } while ((next = next->next())); + } + + void remove(SkOpContour* contour) { + if (contour == this) { + SkASSERT(this->count() == 0); + return; + } + SkASSERT(contour->next() == nullptr); + SkOpContour* prev = this; + SkOpContour* next; + while ((next = prev->next()) != contour) { + SkASSERT(next); + prev = next; + } + SkASSERT(prev); + prev->setNext(nullptr); + } + +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpCubicHull.cpp b/gfx/skia/skia/src/pathops/SkOpCubicHull.cpp new file mode 100644 index 000000000..6b17608e8 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpCubicHull.cpp @@ -0,0 +1,150 @@ +/* + * 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 "SkPathOpsCubic.h" + +static bool rotate(const SkDCubic& cubic, int zero, int index, SkDCubic& rotPath) { + double dy = cubic[index].fY - cubic[zero].fY; + double dx = cubic[index].fX - cubic[zero].fX; + if (approximately_zero(dy)) { + if (approximately_zero(dx)) { + return false; + } + rotPath = cubic; + if (dy) { + rotPath[index].fY = cubic[zero].fY; + int mask = other_two(index, zero); + int side1 = index ^ mask; + int side2 = zero ^ mask; + if (approximately_equal(cubic[side1].fY, cubic[zero].fY)) { + rotPath[side1].fY = cubic[zero].fY; + } + if (approximately_equal(cubic[side2].fY, cubic[zero].fY)) { + rotPath[side2].fY = cubic[zero].fY; + } + } + return true; + } + for (int index = 0; index < 4; ++index) { + rotPath[index].fX = cubic[index].fX * dx + cubic[index].fY * dy; + rotPath[index].fY = cubic[index].fY * dx - cubic[index].fX * dy; + } + return true; +} + + +// Returns 0 if negative, 1 if zero, 2 if positive +static int side(double x) { + return (x > 0) + (x >= 0); +} + +/* Given a cubic, find the convex hull described by the end and control points. + The hull may have 3 or 4 points. Cubics that degenerate into a point or line + are not considered. + + The hull is computed by assuming that three points, if unique and non-linear, + form a triangle. The fourth point may replace one of the first three, may be + discarded if in the triangle or on an edge, or may be inserted between any of + the three to form a convex quadralateral. + + The indices returned in order describe the convex hull. +*/ +int SkDCubic::convexHull(char order[4]) const { + size_t index; + // find top point + size_t yMin = 0; + for (index = 1; index < 4; ++index) { + if (fPts[yMin].fY > fPts[index].fY || (fPts[yMin].fY == fPts[index].fY + && fPts[yMin].fX > fPts[index].fX)) { + yMin = index; + } + } + order[0] = yMin; + int midX = -1; + int backupYMin = -1; + for (int pass = 0; pass < 2; ++pass) { + for (index = 0; index < 4; ++index) { + if (index == yMin) { + continue; + } + // rotate line from (yMin, index) to axis + // see if remaining two points are both above or below + // use this to find mid + int mask = other_two(yMin, index); + int side1 = yMin ^ mask; + int side2 = index ^ mask; + SkDCubic rotPath; + if (!rotate(*this, yMin, index, rotPath)) { // ! if cbc[yMin]==cbc[idx] + order[1] = side1; + order[2] = side2; + return 3; + } + int sides = side(rotPath[side1].fY - rotPath[yMin].fY); + sides ^= side(rotPath[side2].fY - rotPath[yMin].fY); + if (sides == 2) { // '2' means one remaining point <0, one >0 + if (midX >= 0) { + // one of the control points is equal to an end point + order[0] = 0; + order[1] = 3; + if (fPts[1] == fPts[0] || fPts[1] == fPts[3]) { + order[2] = 2; + return 3; + } + if (fPts[2] == fPts[0] || fPts[2] == fPts[3]) { + order[2] = 1; + return 3; + } + // one of the control points may be very nearly but not exactly equal -- + double dist1_0 = fPts[1].distanceSquared(fPts[0]); + double dist1_3 = fPts[1].distanceSquared(fPts[3]); + double dist2_0 = fPts[2].distanceSquared(fPts[0]); + double dist2_3 = fPts[2].distanceSquared(fPts[3]); + double smallest1distSq = SkTMin(dist1_0, dist1_3); + double smallest2distSq = SkTMin(dist2_0, dist2_3); + if (approximately_zero(SkTMin(smallest1distSq, smallest2distSq))) { + order[2] = smallest1distSq < smallest2distSq ? 2 : 1; + return 3; + } + } + midX = index; + } else if (sides == 0) { // '0' means both to one side or the other + backupYMin = index; + } + } + if (midX >= 0) { + break; + } + if (backupYMin < 0) { + break; + } + yMin = backupYMin; + backupYMin = -1; + } + if (midX < 0) { + midX = yMin ^ 3; // choose any other point + } + int mask = other_two(yMin, midX); + int least = yMin ^ mask; + int most = midX ^ mask; + order[0] = yMin; + order[1] = least; + + // see if mid value is on same side of line (least, most) as yMin + SkDCubic midPath; + if (!rotate(*this, least, most, midPath)) { // ! if cbc[least]==cbc[most] + order[2] = midX; + return 3; + } + int midSides = side(midPath[yMin].fY - midPath[least].fY); + midSides ^= side(midPath[midX].fY - midPath[least].fY); + if (midSides != 2) { // if mid point is not between + order[2] = most; + return 3; // result is a triangle + } + order[2] = midX; + order[3] = most; + return 4; // result is a quadralateral +} diff --git a/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.cpp b/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.cpp new file mode 100644 index 000000000..ab2aca0a7 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.cpp @@ -0,0 +1,305 @@ +/* + * 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 "SkGeometry.h" +#include "SkOpEdgeBuilder.h" +#include "SkReduceOrder.h" + +void SkOpEdgeBuilder::init() { + fCurrentContour = fContoursHead; + fOperand = false; + fXorMask[0] = fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask + : kWinding_PathOpsMask; + fUnparseable = false; + fSecondHalf = preFetch(); +} + +// very tiny points cause numerical instability : don't allow them +static void force_small_to_zero(SkPoint* pt) { + if (SkScalarAbs(pt->fX) < FLT_EPSILON_ORDERABLE_ERR) { + pt->fX = 0; + } + if (SkScalarAbs(pt->fY) < FLT_EPSILON_ORDERABLE_ERR) { + pt->fY = 0; + } +} + +static bool can_add_curve(SkPath::Verb verb, SkPoint* curve) { + if (SkPath::kMove_Verb == verb) { + return false; + } + for (int index = 0; index <= SkPathOpsVerbToPoints(verb); ++index) { + force_small_to_zero(&curve[index]); + } + return SkPath::kLine_Verb != verb || !SkDPoint::ApproximatelyEqual(curve[0], curve[1]); +} + +void SkOpEdgeBuilder::addOperand(const SkPath& path) { + SkASSERT(fPathVerbs.count() > 0 && fPathVerbs.end()[-1] == SkPath::kDone_Verb); + fPathVerbs.pop(); + fPath = &path; + fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask + : kWinding_PathOpsMask; + preFetch(); +} + +bool SkOpEdgeBuilder::finish() { + fOperand = false; + if (fUnparseable || !walk()) { + return false; + } + complete(); + if (fCurrentContour && !fCurrentContour->count()) { + fContoursHead->remove(fCurrentContour); + } + return true; +} + +void SkOpEdgeBuilder::closeContour(const SkPoint& curveEnd, const SkPoint& curveStart) { + if (!SkDPoint::ApproximatelyEqual(curveEnd, curveStart)) { + *fPathVerbs.append() = SkPath::kLine_Verb; + *fPathPts.append() = curveStart; + } else { + int verbCount = fPathVerbs.count(); + int ptsCount = fPathPts.count(); + if (SkPath::kLine_Verb == fPathVerbs[verbCount - 1] + && fPathPts[ptsCount - 2] == curveStart) { + fPathVerbs.pop(); + fPathPts.pop(); + } else { + fPathPts[ptsCount - 1] = curveStart; + } + } + *fPathVerbs.append() = SkPath::kClose_Verb; +} + +int SkOpEdgeBuilder::preFetch() { + if (!fPath->isFinite()) { + fUnparseable = true; + return 0; + } + SkPath::RawIter iter(*fPath); + SkPoint curveStart; + SkPoint curve[4]; + SkPoint pts[4]; + SkPath::Verb verb; + bool lastCurve = false; + do { + verb = iter.next(pts); + switch (verb) { + case SkPath::kMove_Verb: + if (!fAllowOpenContours && lastCurve) { + closeContour(curve[0], curveStart); + } + *fPathVerbs.append() = verb; + force_small_to_zero(&pts[0]); + *fPathPts.append() = pts[0]; + curveStart = curve[0] = pts[0]; + lastCurve = false; + continue; + case SkPath::kLine_Verb: + force_small_to_zero(&pts[1]); + if (SkDPoint::ApproximatelyEqual(curve[0], pts[1])) { + uint8_t lastVerb = fPathVerbs.top(); + if (lastVerb != SkPath::kLine_Verb && lastVerb != SkPath::kMove_Verb) { + fPathPts.top() = pts[1]; + } + continue; // skip degenerate points + } + break; + case SkPath::kQuad_Verb: + force_small_to_zero(&pts[1]); + force_small_to_zero(&pts[2]); + curve[1] = pts[1]; + curve[2] = pts[2]; + verb = SkReduceOrder::Quad(curve, pts); + if (verb == SkPath::kMove_Verb) { + continue; // skip degenerate points + } + break; + case SkPath::kConic_Verb: + force_small_to_zero(&pts[1]); + force_small_to_zero(&pts[2]); + curve[1] = pts[1]; + curve[2] = pts[2]; + verb = SkReduceOrder::Quad(curve, pts); + if (SkPath::kQuad_Verb == verb && 1 != iter.conicWeight()) { + verb = SkPath::kConic_Verb; + } else if (verb == SkPath::kMove_Verb) { + continue; // skip degenerate points + } + break; + case SkPath::kCubic_Verb: + force_small_to_zero(&pts[1]); + force_small_to_zero(&pts[2]); + force_small_to_zero(&pts[3]); + curve[1] = pts[1]; + curve[2] = pts[2]; + curve[3] = pts[3]; + verb = SkReduceOrder::Cubic(curve, pts); + if (verb == SkPath::kMove_Verb) { + continue; // skip degenerate points + } + break; + case SkPath::kClose_Verb: + closeContour(curve[0], curveStart); + lastCurve = false; + continue; + case SkPath::kDone_Verb: + continue; + } + *fPathVerbs.append() = verb; + int ptCount = SkPathOpsVerbToPoints(verb); + fPathPts.append(ptCount, &pts[1]); + if (verb == SkPath::kConic_Verb) { + *fWeights.append() = iter.conicWeight(); + } + curve[0] = pts[ptCount]; + lastCurve = true; + } while (verb != SkPath::kDone_Verb); + if (!fAllowOpenContours && lastCurve) { + closeContour(curve[0], curveStart); + } + *fPathVerbs.append() = SkPath::kDone_Verb; + return fPathVerbs.count() - 1; +} + +bool SkOpEdgeBuilder::close() { + complete(); + return true; +} + +bool SkOpEdgeBuilder::walk() { + uint8_t* verbPtr = fPathVerbs.begin(); + uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf]; + SkPoint* pointsPtr = fPathPts.begin() - 1; + SkScalar* weightPtr = fWeights.begin(); + SkPath::Verb verb; + while ((verb = (SkPath::Verb) *verbPtr) != SkPath::kDone_Verb) { + if (verbPtr == endOfFirstHalf) { + fOperand = true; + } + verbPtr++; + switch (verb) { + case SkPath::kMove_Verb: + if (fCurrentContour && fCurrentContour->count()) { + if (fAllowOpenContours) { + complete(); + } else if (!close()) { + return false; + } + } + if (!fCurrentContour) { + fCurrentContour = fContoursHead->appendContour(); + } + fCurrentContour->init(fGlobalState, fOperand, + fXorMask[fOperand] == kEvenOdd_PathOpsMask); + pointsPtr += 1; + continue; + case SkPath::kLine_Verb: + fCurrentContour->addLine(pointsPtr); + break; + case SkPath::kQuad_Verb: + { + SkVector v1 = pointsPtr[1] - pointsPtr[0]; + SkVector v2 = pointsPtr[2] - pointsPtr[1]; + if (v1.dot(v2) < 0) { + SkPoint pair[5]; + if (SkChopQuadAtMaxCurvature(pointsPtr, pair) == 1) { + goto addOneQuad; + } + if (!SkScalarsAreFinite(&pair[0].fX, SK_ARRAY_COUNT(pair) * 2)) { + return false; + } + SkPoint cStorage[2][2]; + SkPath::Verb v1 = SkReduceOrder::Quad(&pair[0], cStorage[0]); + SkPath::Verb v2 = SkReduceOrder::Quad(&pair[2], cStorage[1]); + SkPoint* curve1 = v1 != SkPath::kLine_Verb ? &pair[0] : cStorage[0]; + SkPoint* curve2 = v2 != SkPath::kLine_Verb ? &pair[2] : cStorage[1]; + if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { + fCurrentContour->addCurve(v1, curve1); + fCurrentContour->addCurve(v2, curve2); + break; + } + } + } + addOneQuad: + fCurrentContour->addQuad(pointsPtr); + break; + case SkPath::kConic_Verb: { + SkVector v1 = pointsPtr[1] - pointsPtr[0]; + SkVector v2 = pointsPtr[2] - pointsPtr[1]; + SkScalar weight = *weightPtr++; + if (v1.dot(v2) < 0) { + // FIXME: max curvature for conics hasn't been implemented; use placeholder + SkScalar maxCurvature = SkFindQuadMaxCurvature(pointsPtr); + if (maxCurvature > 0) { + SkConic conic(pointsPtr, weight); + SkConic pair[2]; + if (!conic.chopAt(maxCurvature, pair)) { + // if result can't be computed, use original + fCurrentContour->addConic(pointsPtr, weight); + break; + } + SkPoint cStorage[2][3]; + SkPath::Verb v1 = SkReduceOrder::Conic(pair[0], cStorage[0]); + SkPath::Verb v2 = SkReduceOrder::Conic(pair[1], cStorage[1]); + SkPoint* curve1 = v1 != SkPath::kLine_Verb ? pair[0].fPts : cStorage[0]; + SkPoint* curve2 = v2 != SkPath::kLine_Verb ? pair[1].fPts : cStorage[1]; + if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { + fCurrentContour->addCurve(v1, curve1, pair[0].fW); + fCurrentContour->addCurve(v2, curve2, pair[1].fW); + break; + } + } + } + fCurrentContour->addConic(pointsPtr, weight); + } break; + case SkPath::kCubic_Verb: + { + // Split complex cubics (such as self-intersecting curves or + // ones with difficult curvature) in two before proceeding. + // This can be required for intersection to succeed. + SkScalar splitT; + if (SkDCubic::ComplexBreak(pointsPtr, &splitT)) { + SkPoint pair[7]; + SkChopCubicAt(pointsPtr, pair, splitT); + if (!SkScalarsAreFinite(&pair[0].fX, SK_ARRAY_COUNT(pair) * 2)) { + return false; + } + SkPoint cStorage[2][4]; + SkPath::Verb v1 = SkReduceOrder::Cubic(&pair[0], cStorage[0]); + SkPath::Verb v2 = SkReduceOrder::Cubic(&pair[3], cStorage[1]); + SkPoint* curve1 = v1 == SkPath::kCubic_Verb ? &pair[0] : cStorage[0]; + SkPoint* curve2 = v2 == SkPath::kCubic_Verb ? &pair[3] : cStorage[1]; + if (can_add_curve(v1, curve1) && can_add_curve(v2, curve2)) { + fCurrentContour->addCurve(v1, curve1); + fCurrentContour->addCurve(v2, curve2); + break; + } + } + } + fCurrentContour->addCubic(pointsPtr); + break; + case SkPath::kClose_Verb: + SkASSERT(fCurrentContour); + if (!close()) { + return false; + } + continue; + default: + SkDEBUGFAIL("bad verb"); + return false; + } + SkASSERT(fCurrentContour); + fCurrentContour->debugValidate(); + pointsPtr += SkPathOpsVerbToPoints(verb); + } + if (fCurrentContour && fCurrentContour->count() &&!fAllowOpenContours && !close()) { + return false; + } + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.h b/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.h new file mode 100644 index 000000000..c6fc7dcb0 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpEdgeBuilder.h @@ -0,0 +1,71 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpEdgeBuilder_DEFINED +#define SkOpEdgeBuilder_DEFINED + +#include "SkOpContour.h" +#include "SkPathWriter.h" + +class SkOpEdgeBuilder { +public: + SkOpEdgeBuilder(const SkPathWriter& path, SkOpContourHead* contours2, + SkOpGlobalState* globalState) + : fGlobalState(globalState) + , fPath(path.nativePath()) + , fContoursHead(contours2) + , fAllowOpenContours(true) { + init(); + } + + SkOpEdgeBuilder(const SkPath& path, SkOpContourHead* contours2, SkOpGlobalState* globalState) + : fGlobalState(globalState) + , fPath(&path) + , fContoursHead(contours2) + , fAllowOpenContours(false) { + init(); + } + + void addOperand(const SkPath& path); + + void complete() { + if (fCurrentContour && fCurrentContour->count()) { + fCurrentContour->complete(); + fCurrentContour = nullptr; + } + } + + bool finish(); + + const SkOpContour* head() const { + return fContoursHead; + } + + void init(); + bool unparseable() const { return fUnparseable; } + SkPathOpsMask xorMask() const { return fXorMask[fOperand]; } + +private: + void closeContour(const SkPoint& curveEnd, const SkPoint& curveStart); + bool close(); + int preFetch(); + bool walk(); + + SkOpGlobalState* fGlobalState; + const SkPath* fPath; + SkTDArray<SkPoint> fPathPts; + SkTDArray<SkScalar> fWeights; + SkTDArray<uint8_t> fPathVerbs; + SkOpContour* fCurrentContour; + SkOpContourHead* fContoursHead; + SkPathOpsMask fXorMask[2]; + int fSecondHalf; + bool fOperand; + bool fAllowOpenContours; + bool fUnparseable; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpSegment.cpp b/gfx/skia/skia/src/pathops/SkOpSegment.cpp new file mode 100644 index 000000000..2246f36ff --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpSegment.cpp @@ -0,0 +1,1695 @@ +/* + * 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) { + if (SkOpAngle* result = activeAngleInner(start, startPtr, endPtr, done)) { + return result; + } + if (SkOpAngle* result = activeAngleOther(start, startPtr, endPtr, done)) { + return result; + } + return nullptr; +} + +SkOpAngle* SkOpSegment::activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done) { + 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 nullptr; +} + +SkOpAngle* SkOpSegment::activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done) { + SkOpPtT* oPtT = start->ptT()->next(); + SkOpSegment* other = oPtT->segment(); + SkOpSpanBase* oSpan = oPtT->span(); + return other->activeAngleInner(oSpan, startPtr, endPtr, done); +} + +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; +} + +bool SkOpSegment::addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, + SkPathWriter* path) const { + FAIL_IF(start->starter(end)->alreadyAdded()); + SkDCurveSweep curvePart; + start->segment()->subDivide(start, end, &curvePart.fCurve); + curvePart.setCurveHullSweep(fVerb); + SkPath::Verb verb = curvePart.isCurve() ? fVerb : SkPath::kLine_Verb; + path->deferredMove(start->ptT()); + switch (verb) { + case SkPath::kLine_Verb: + path->deferredLine(end->ptT()); + break; + case SkPath::kQuad_Verb: + path->quadTo(curvePart.fCurve.fQuad.fPts[1].asSkPoint(), end->ptT()); + break; + case SkPath::kConic_Verb: + path->conicTo(curvePart.fCurve.fConic.fPts[1].asSkPoint(), end->ptT(), + curvePart.fCurve.fConic.fWeight); + break; + case SkPath::kCubic_Verb: + path->cubicTo(curvePart.fCurve.fCubic.fPts[1].asSkPoint(), + curvePart.fCurve.fCubic.fPts[2].asSkPoint(), end->ptT()); + break; + default: + SkASSERT(0); + } + return true; +} + +const SkOpPtT* SkOpSegment::existing(double t, const SkOpSegment* opp) const { + const SkOpSpanBase* test = &fHead; + const SkOpPtT* testPtT; + SkPoint pt = this->ptAtT(t); + do { + testPtT = test->ptT(); + if (testPtT->fT == t) { + break; + } + if (!this->match(testPtT, this, t, pt)) { + if (t < testPtT->fT) { + return nullptr; + } + continue; + } + if (!opp) { + return testPtT; + } + const SkOpPtT* loop = testPtT->next(); + while (loop != testPtT) { + if (loop->segment() == this && loop->fT == t && loop->fPt == pt) { + goto foundMatch; + } + loop = loop->next(); + } + return nullptr; + } while ((test = test->upCast()->next())); +foundMatch: + return opp && !test->contains(opp) ? nullptr : testPtT; +} + +// break the span so that the coincident part does not change the angle of the remainder +bool SkOpSegment::addExpanded(double newT, const SkOpSpanBase* test, bool* startOver) { + if (this->contains(newT)) { + return true; + } + this->globalState()->resetAllocatedOpSpan(); + SkOpPtT* newPtT = this->addT(newT); + *startOver |= this->globalState()->allocatedOpSpan(); + if (!newPtT) { + return false; + } + newPtT->fPt = this->ptAtT(newT); + SkOpPtT* oppPrev = test->ptT()->oppPrev(newPtT); + if (oppPrev) { + // const cast away to change linked list; pt/t values stays unchanged + SkOpSpanBase* writableTest = const_cast<SkOpSpanBase*>(test); + writableTest->mergeMatches(newPtT->span()); + writableTest->ptT()->addOpp(newPtT, oppPrev); + writableTest->checkForCollapsedCoincidence(); + } + return true; +} + +// Please keep this in sync with debugAddT() +SkOpPtT* SkOpSegment::addT(double t) { + debugValidate(); + SkPoint pt = this->ptAtT(t); + SkOpSpanBase* spanBase = &fHead; + do { + SkOpPtT* result = spanBase->ptT(); + if (t == result->fT || (!zero_or_one(t) && this->match(result, this, t, pt))) { + spanBase->bumpSpanAdds(); + return result; + } + if (t < result->fT) { + SkOpSpan* prev = result->span()->prev(); + FAIL_WITH_NULL_IF(!prev); + // marks in global state that new op span has been allocated + SkOpSpan* span = this->insert(prev); + 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 + span->bumpSpanAdds(); + return span->ptT(); + } + FAIL_WITH_NULL_IF(spanBase == &fTail); + } while ((spanBase = spanBase->upCast()->next())); + SkASSERT(0); + return nullptr; // we never get here, but need this to satisfy compiler +} + +void SkOpSegment::calcAngles() { + bool activePrior = !fHead.isCanceled(); + if (activePrior && !fHead.simple()) { + addStartSpan(); + } + SkOpSpan* prior = &fHead; + SkOpSpanBase* spanBase = fHead.next(); + while (spanBase != &fTail) { + if (activePrior) { + SkOpAngle* priorAngle = SkOpTAllocator<SkOpAngle>::Allocate( + this->globalState()->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( + this->globalState()->allocator()); + angle->set(span, next); + span->setToAngle(angle); + } + activePrior = active; + prior = span; + spanBase = next; + } + if (activePrior && !fTail.simple()) { + addEndSpan(); + } +} + +// Please keep this in sync with debugClearAll() +void SkOpSegment::clearAll() { + SkOpSpan* span = &fHead; + do { + this->clearOne(span); + } while ((span = span->next()->upCastable())); + this->globalState()->coincidence()->release(this); +} + +// Please keep this in sync with debugClearOne() +void SkOpSegment::clearOne(SkOpSpan* span) { + span->setWindValue(0); + span->setOppValue(0); + this->markDone(span); +} + +bool SkOpSegment::collapsed(double s, double e) const { + const SkOpSpanBase* span = &fHead; + do { + if (span->collapsed(s, e)) { + return true; + } + } while (span->upCastable() && (span = span->upCast()->next())); + return false; +} + +void SkOpSegment::ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType includeType) { + 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(SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType includeType) { + 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 (nullptr == firstAngle || nullptr == 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 = nullptr; + 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 = nullptr; + 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 : nullptr; + } + } while (next != firstAngle); + if (baseAngle && SK_MinS32 == firstAngle->starter()->windSum()) { + firstAngle = baseAngle; + tryReverse = true; + } + if (tryReverse) { + baseAngle = nullptr; + SkOpAngle* prior = firstAngle; + do { + angle = prior; + prior = angle->previous(); + SkASSERT(prior->next() == angle); + next = angle->next(); + if (prior->unorderable() || angle->unorderable() || next->unorderable()) { + baseAngle = nullptr; + 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 : nullptr; + } + } while (prior != firstAngle); + } + return start->starter(end)->windSum(); +} + +bool SkOpSegment::contains(double newT) const { + const SkOpSpanBase* spanBase = &fHead; + do { + if (spanBase->ptT()->contains(this, newT)) { + return true; + } + if (spanBase == &fTail) { + break; + } + spanBase = spanBase->upCast()->next(); + } while (true); + return false; +} + +void SkOpSegment::release(const SkOpSpan* span) { + if (span->done()) { + --fDoneCount; + } + --fCount; + SkOPASSERT(fCount >= fDoneCount); +} + +#if DEBUG_ANGLE +// called only by debugCheckNearCoincidence +double SkOpSegment::distSq(double t, const SkOpAngle* oppAngle) const { + 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; + const SkOpSegment* oppSegment = oppAngle->segment(); + (*CurveIntersectRay[oppSegment->verb()])(oppSegment->pts(), oppSegment->weight(), 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; +} +#endif + +/* + 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 nullptr; + } + 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 nullptr; + } + SkOpAngle* angle = this->spanToAngle(end, start); + if (angle->unorderable()) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } +#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 nullptr; + } + int sumSuWinding = updateOppWinding(end, start); + if (operand()) { + SkTSwap<int>(sumMiWinding, sumSuWinding); + } + SkOpAngle* nextAngle = angle->next(); + const SkOpAngle* foundAngle = nullptr; + 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 nullptr; + } + *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 nullptr; + } + 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 nullptr; + } + SkOpAngle* angle = this->spanToAngle(end, start); + if (angle->unorderable()) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } +#if DEBUG_SORT + SkDebugf("%s\n", __FUNCTION__); + angle->debugLoop(); +#endif + int sumWinding = updateWinding(end, start); + SkOpAngle* nextAngle = angle->next(); + const SkOpAngle* foundAngle = nullptr; + 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 nullptr; + } + *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 nullptr; + } + 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 || angle->unorderable()) { + *unsortable = true; + markDone(start->starter(end)); + return nullptr; + } +#if DEBUG_SORT + SkDebugf("%s\n", __FUNCTION__); + angle->debugLoop(); +#endif + SkOpAngle* nextAngle = angle->next(); + const SkOpAngle* foundAngle = nullptr; + 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 nullptr; + } + *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; +} + +SkOpGlobalState* SkOpSegment::globalState() const { + return contour()->globalState(); +} + +void SkOpSegment::init(SkPoint pts[], SkScalar weight, SkOpContour* contour, SkPath::Verb verb) { + fContour = contour; + fNext = nullptr; + fPts = pts; + fWeight = weight; + fVerb = verb; + fCount = 0; + fDoneCount = 0; + fVisited = false; + SkOpSpan* zeroSpan = &fHead; + zeroSpan->init(this, nullptr, 0, fPts[0]); + SkOpSpanBase* oneSpan = &fTail; + zeroSpan->setNext(oneSpan); + oneSpan->initBase(this, zeroSpan, 1, fPts[SkPathOpsVerbToPoints(fVerb)]); + SkDEBUGCODE(fID = globalState()->nextSegmentID()); +} + +bool SkOpSegment::isClose(double t, const SkOpSegment* opp) const { + SkDPoint cPt = this->dPtAtT(t); + SkDVector dxdy = (*CurveDSlopeAtT[this->verb()])(this->pts(), this->weight(), t); + SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; + SkIntersections i; + (*CurveIntersectRay[opp->verb()])(opp->pts(), opp->weight(), 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(); +} + +void SkOpSegment::markAllDone() { + SkOpSpan* span = this->head(); + do { + this->markDone(span); + } while ((span = span->next()->upCastable())); +} + +SkOpSpanBase* SkOpSegment::markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end) { + int step = start->step(end); + SkOpSpan* minSpan = start->starter(end); + markDone(minSpan); + SkOpSpanBase* last = nullptr; + 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 = nullptr; + 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 = nullptr; + SkOpSegment* other = this; + while ((other = other->nextChase(&start, &step, &spanStart, &last))) { + if (spanStart->windSum() != SK_MinS32) { + if (this->operand() == other->operand()) { + if (spanStart->windSum() != winding || 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 = nullptr; + // 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 { + SkASSERT(this == base->segment()); + if (this == testParent) { + if (precisely_equal(base->fT, testT)) { + return true; + } + } + if (!SkDPoint::ApproximatelyEqual(testPt, base->fPt)) { + return false; + } + return this != testParent || !this->ptsDisjoint(base->fT, base->fPt, testT, testPt); +} + +static SkOpSegment* set_last(SkOpSpanBase** last, SkOpSpanBase* endSpan) { + if (last) { + *last = endSpan; + } + return nullptr; +} + +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 == nullptr) { + if (endSpan->t() != 0 && endSpan->t() != 1) { + return nullptr; + } + SkOpPtT* otherPtT = endSpan->ptT()->next(); + other = otherPtT->segment(); + foundSpan = otherPtT->span(); + otherEnd = step > 0 + ? foundSpan->upCastable() ? foundSpan->upCast()->next() : nullptr + : foundSpan->prev(); + } else { + int loopCount = angle->loopCount(); + if (loopCount > 2) { + return set_last(last, endSpan); + } + const SkOpAngle* next = angle->next(); + if (nullptr == next) { + return nullptr; + } +#if DEBUG_WINDING + if (angle->debugSign() != next->debugSign() && !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(); + } + if (!otherEnd) { + return nullptr; + } + int foundStep = foundSpan->step(otherEnd); + if (*stepPtr != foundStep) { + return set_last(last, endSpan); + } + SkASSERT(*startPtr); + if (!otherEnd) { + return nullptr; + } +// 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; +} + +// Please keep this in sync with DebugClearVisited() +void SkOpSegment::ClearVisited(SkOpSpanBase* 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->final() && (span = span->upCast()->next())); +} + +// Please keep this in sync with debugMissingCoincidence() +// look for pairs of undetected coincident curves +// assumes that segments going in have visited flag clear +// Even though pairs of curves correct detect coincident runs, a run may be missed +// if the coincidence is a product of multiple intersections. For instance, given +// curves A, B, and C: +// A-B intersect at a point 1; A-C and B-C intersect at point 2, so near +// the end of C that the intersection is replaced with the end of C. +// Even though A-B correctly do not detect an intersection at point 2, +// the resulting run from point 1 to point 2 is coincident on A and B. +bool SkOpSegment::missingCoincidence() { + if (this->done()) { + return false; + } + SkOpSpan* prior = nullptr; + SkOpSpanBase* spanBase = &fHead; + bool result = false; + do { + SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; + SkOPASSERT(ptT->span() == spanBase); + while ((ptT = ptT->next()) != spanStopPtT) { + if (ptT->deleted()) { + continue; + } + SkOpSegment* opp = ptT->span()->segment(); + if (opp->done()) { + continue; + } + // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence + if (!opp->visited()) { + continue; + } + if (spanBase == &fHead) { + continue; + } + if (ptT->segment() == this) { + continue; + } + SkOpSpan* span = spanBase->upCastable(); + // FIXME?: this assumes that if the opposite segment is coincident then no more + // coincidence needs to be detected. This may not be true. + if (span && span->containsCoincidence(opp)) { + continue; + } + if (spanBase->containsCoinEnd(opp)) { + continue; + } + SkOpPtT* priorPtT = nullptr, * priorStopPtT; + // find prior span containing opp segment + SkOpSegment* priorOpp = nullptr; + SkOpSpan* priorTest = spanBase->prev(); + while (!priorOpp && priorTest) { + priorStopPtT = priorPtT = priorTest->ptT(); + while ((priorPtT = priorPtT->next()) != priorStopPtT) { + if (priorPtT->deleted()) { + continue; + } + SkOpSegment* segment = priorPtT->span()->segment(); + if (segment == opp) { + prior = priorTest; + priorOpp = opp; + break; + } + } + priorTest = priorTest->prev(); + } + if (!priorOpp) { + continue; + } + if (priorPtT == ptT) { + continue; + } + SkOpPtT* oppStart = prior->ptT(); + SkOpPtT* oppEnd = spanBase->ptT(); + bool swapped = priorPtT->fT > ptT->fT; + if (swapped) { + SkTSwap(priorPtT, ptT); + SkTSwap(oppStart, oppEnd); + } + SkOpCoincidence* coincidences = this->globalState()->coincidence(); + SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); + SkOpPtT* rootPtT = ptT->span()->ptT(); + SkOpPtT* rootOppStart = oppStart->span()->ptT(); + SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); + if (coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { + goto swapBack; + } + if (this->testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { + // mark coincidence +#if DEBUG_COINCIDENCE_VERBOSE + SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, + rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), + rootOppEnd->debugID()); +#endif + if (!coincidences->extend(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { + coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); + } +#if DEBUG_COINCIDENCE + SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)); +#endif + result = true; + } + swapBack: + if (swapped) { + SkTSwap(priorPtT, ptT); + } + } + } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); + ClearVisited(&fHead); + return result; +} + +// please keep this in sync with debugMoveMultiples() +// if a span has more than one intersection, merge the other segments' span as needed +bool SkOpSegment::moveMultiples() { + debugValidate(); + SkOpSpanBase* test = &fHead; + do { + int addCount = test->spanAddsCount(); + FAIL_IF(addCount < 1); + if (addCount == 1) { + continue; + } + SkOpPtT* startPtT = test->ptT(); + SkOpPtT* testPtT = startPtT; + do { // iterate through all spans associated with start + SkOpSpanBase* oppSpan = testPtT->span(); + if (oppSpan->spanAddsCount() == addCount) { + continue; + } + if (oppSpan->deleted()) { + continue; + } + SkOpSegment* oppSegment = oppSpan->segment(); + if (oppSegment == this) { + continue; + } + // find range of spans to consider merging + SkOpSpanBase* oppPrev = oppSpan; + SkOpSpanBase* oppFirst = oppSpan; + while ((oppPrev = oppPrev->prev())) { + if (!roughly_equal(oppPrev->t(), oppSpan->t())) { + break; + } + if (oppPrev->spanAddsCount() == addCount) { + continue; + } + if (oppPrev->deleted()) { + continue; + } + oppFirst = oppPrev; + } + SkOpSpanBase* oppNext = oppSpan; + SkOpSpanBase* oppLast = oppSpan; + while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { + if (!roughly_equal(oppNext->t(), oppSpan->t())) { + break; + } + if (oppNext->spanAddsCount() == addCount) { + continue; + } + if (oppNext->deleted()) { + continue; + } + oppLast = oppNext; + } + if (oppFirst == oppLast) { + continue; + } + SkOpSpanBase* oppTest = oppFirst; + do { + if (oppTest == oppSpan) { + continue; + } + // check to see if the candidate meets specific criteria: + // it contains spans of segments in test's loop but not including 'this' + SkOpPtT* oppStartPtT = oppTest->ptT(); + SkOpPtT* oppPtT = oppStartPtT; + while ((oppPtT = oppPtT->next()) != oppStartPtT) { + SkOpSegment* oppPtTSegment = oppPtT->segment(); + if (oppPtTSegment == this) { + goto tryNextSpan; + } + SkOpPtT* matchPtT = startPtT; + do { + if (matchPtT->segment() == oppPtTSegment) { + goto foundMatch; + } + } while ((matchPtT = matchPtT->next()) != startPtT); + goto tryNextSpan; + foundMatch: // merge oppTest and oppSpan + oppSegment->debugValidate(); + oppTest->mergeMatches(oppSpan); + oppTest->addOpp(oppSpan); + oppSegment->debugValidate(); + goto checkNextSpan; + } + tryNextSpan: + ; + } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); + } while ((testPtT = testPtT->next()) != startPtT); +checkNextSpan: + ; + } while ((test = test->final() ? nullptr : test->upCast()->next())); + debugValidate(); + return true; +} + +// adjacent spans may have points close by +bool SkOpSegment::spansNearby(const SkOpSpanBase* refSpan, const SkOpSpanBase* checkSpan) const { + const SkOpPtT* refHead = refSpan->ptT(); + const SkOpPtT* checkHead = checkSpan->ptT(); +// if the first pt pair from adjacent spans are far apart, assume that all are far enough apart + if (!SkDPoint::WayRoughlyEqual(refHead->fPt, checkHead->fPt)) { +#if DEBUG_COINCIDENCE + // verify that no combination of points are close + const SkOpPtT* dBugRef = refHead; + do { + const SkOpPtT* dBugCheck = checkHead; + do { + SkOPASSERT(!SkDPoint::ApproximatelyEqual(dBugRef->fPt, dBugCheck->fPt)); + dBugCheck = dBugCheck->next(); + } while (dBugCheck != checkHead); + dBugRef = dBugRef->next(); + } while (dBugRef != refHead); +#endif + return false; + } + // check only unique points + SkScalar distSqBest = SK_ScalarMax; + const SkOpPtT* refBest = nullptr; + const SkOpPtT* checkBest = nullptr; + const SkOpPtT* ref = refHead; + do { + if (ref->deleted()) { + continue; + } + while (ref->ptAlreadySeen(refHead)) { + ref = ref->next(); + if (ref == refHead) { + goto doneCheckingDistance; + } + } + const SkOpPtT* check = checkHead; + const SkOpSegment* refSeg = ref->segment(); + do { + if (check->deleted()) { + continue; + } + while (check->ptAlreadySeen(checkHead)) { + check = check->next(); + if (check == checkHead) { + goto nextRef; + } + } + SkScalar distSq = ref->fPt.distanceToSqd(check->fPt); + if (distSqBest > distSq && (refSeg != check->segment() + || !refSeg->ptsDisjoint(*ref, *check))) { + distSqBest = distSq; + refBest = ref; + checkBest = check; + } + } while ((check = check->next()) != checkHead); +nextRef: + ; + } while ((ref = ref->next()) != refHead); +doneCheckingDistance: + return checkBest && refBest->segment()->match(refBest, checkBest->segment(), checkBest->fT, + checkBest->fPt); +} + +// Please keep this function in sync with debugMoveNearby() +// Move nearby t values and pts so they all hang off the same span. Alignment happens later. +void SkOpSegment::moveNearby() { + debugValidate(); + // release undeleted spans pointing to this seg that are linked to the primary span + SkOpSpanBase* spanBase = &fHead; + do { + SkOpPtT* ptT = spanBase->ptT(); + const SkOpPtT* headPtT = ptT; + while ((ptT = ptT->next()) != headPtT) { + SkOpSpanBase* test = ptT->span(); + if (ptT->segment() == this && !ptT->deleted() && test != spanBase + && test->ptT() == ptT) { + if (test->final()) { + if (spanBase == &fHead) { + this->clearAll(); + return; + } + spanBase->upCast()->release(ptT); + } else if (test->prev()) { + test->upCast()->release(headPtT); + } + break; + } + } + spanBase = spanBase->upCast()->next(); + } while (!spanBase->final()); + + // This loop looks for adjacent spans which are near by + spanBase = &fHead; + do { // iterate through all spans associated with start + SkOpSpanBase* test = spanBase->upCast()->next(); + if (this->spansNearby(spanBase, test)) { + if (test->final()) { + if (spanBase->prev()) { + test->merge(spanBase->upCast()); + } else { + this->clearAll(); + return; + } + } else { + spanBase->merge(test->upCast()); + } + } + spanBase = test; + } while (!spanBase->final()); + debugValidate(); +} + +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 || SkTAbs(*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 || SkTAbs(*sumWinding) <= DEBUG_LIMIT_WIND_SUM); + SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(*oppSumWinding) <= DEBUG_LIMIT_WIND_SUM); +} + +void SkOpSegment::sortAngles() { + SkOpSpanBase* span = &this->fHead; + do { + SkOpAngle* fromAngle = span->fromAngle(); + SkOpAngle* toAngle = span->final() ? nullptr : 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(nullptr); + if (toAngle) { + span->upCast()->setToAngle(nullptr); + } + baseAngle = nullptr; + } +#if DEBUG_SORT + SkASSERT(!baseAngle || baseAngle->loopCount() > 1); +#endif + } while (!span->final() && (span = span->upCast()->next())); +} + +bool SkOpSegment::subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, + SkDCurve* edge) const { + SkASSERT(start != end); + const SkOpPtT& startPtT = *start->ptT(); + const SkOpPtT& endPtT = *end->ptT(); + SkDEBUGCODE(edge->fVerb = fVerb); + edge->fCubic[0].set(startPtT.fPt); + int points = SkPathOpsVerbToPoints(fVerb); + edge->fCubic[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) { + edge->fLine[1].set(fPts[1]); + return false; + } + if (fVerb == SkPath::kConic_Verb) { + edge->fConic[1].set(fPts[1]); + edge->fConic.fWeight = fWeight; + return false; + } + SkASSERT(fVerb == SkPath::kCubic_Verb); + if (startT == 0) { + edge->fCubic[1].set(fPts[1]); + edge->fCubic[2].set(fPts[2]); + return false; + } + edge->fCubic[1].set(fPts[2]); + edge->fCubic[2].set(fPts[1]); + return false; + } + if (fVerb == SkPath::kQuad_Verb) { + edge->fQuad[1] = SkDQuad::SubDivide(fPts, edge->fQuad[0], edge->fQuad[2], startT, endT); + } else if (fVerb == SkPath::kConic_Verb) { + edge->fConic[1] = SkDConic::SubDivide(fPts, fWeight, edge->fQuad[0], edge->fQuad[2], + startT, endT, &edge->fConic.fWeight); + } else { + SkASSERT(fVerb == SkPath::kCubic_Verb); + SkDCubic::SubDivide(fPts, edge->fCubic[0], edge->fCubic[3], startT, endT, &edge->fCubic[1]); + } + return true; +} + +bool SkOpSegment::testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT, + const SkOpSpanBase* prior, const SkOpSpanBase* spanBase, const SkOpSegment* opp) const { + // average t, find mid pt + double midT = (prior->t() + spanBase->t()) / 2; + SkPoint midPt = this->ptAtT(midT); + bool 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)) { + if (priorPtT->span() == ptT->span()) { + return false; + } + coincident = false; + SkIntersections i; + SkDCurve curvePart; + this->subDivide(prior, spanBase, &curvePart); + SkDVector dxdy = (*CurveDDSlopeAtT[fVerb])(curvePart, 0.5f); + SkDPoint partMidPt = (*CurveDDPointAtT[fVerb])(curvePart, 0.5f); + SkDLine ray = {{{midPt.fX, midPt.fY}, {partMidPt.fX + dxdy.fY, partMidPt.fY - dxdy.fX}}}; + SkDCurve oppPart; + opp->subDivide(priorPtT->span(), ptT->span(), &oppPart); + (*CurveDIntersectRay[opp->verb()])(oppPart, ray, &i); + // measure distance and see if it's small enough to denote coincidence + for (int index = 0; index < i.used(); ++index) { + if (!between(0, i[0][index], 1)) { + continue; + } + SkDPoint oppPt = i.pt(index); + if (oppPt.approximatelyDEqual(midPt)) { + // the coincidence can occur at almost any angle + coincident = true; + } + } + } + return coincident; +} + +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(SkOpSpanBase* start, SkOpSpanBase* end) { + SkOpSpan* lesser = start->starter(end); + int winding = lesser->windSum(); + if (winding == SK_MinS32) { + winding = lesser->computeWindSum(); + } + 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(SkOpAngle* angle) { + SkOpSpanBase* startSpan = angle->start(); + SkOpSpanBase* endSpan = angle->end(); + return updateWinding(endSpan, startSpan); +} + +int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) { + SkOpSpanBase* startSpan = angle->start(); + 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 = SkTAbs(outerWinding); + int absIn = SkTAbs(innerWinding); + bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn; + return result; +} + +int SkOpSegment::windSum(const SkOpAngle* angle) const { + const SkOpSpan* minSpan = angle->start()->starter(angle->end()); + return minSpan->windSum(); +} diff --git a/gfx/skia/skia/src/pathops/SkOpSegment.h b/gfx/skia/skia/src/pathops/SkOpSegment.h new file mode 100644 index 000000000..b6e771401 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpSegment.h @@ -0,0 +1,458 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpSegment_DEFINE +#define SkOpSegment_DEFINE + +#include "SkOpAngle.h" +#include "SkOpSpan.h" +#include "SkOpTAllocator.h" +#include "SkPathOpsBounds.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsCurve.h" + +struct SkDCurve; +class SkOpCoincidence; +class SkOpContour; +enum class SkOpRayDir; +struct SkOpRayHit; +class SkPathWriter; + +class SkOpSegment { +public: + bool operator<(const SkOpSegment& rh) const { + return fBounds.fTop < rh.fBounds.fTop; + } + + SkOpAngle* activeAngle(SkOpSpanBase* start, SkOpSpanBase** startPtr, SkOpSpanBase** endPtr, + bool* done); + SkOpAngle* activeAngleInner(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done); + SkOpAngle* activeAngleOther(SkOpSpanBase* start, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr, bool* done); + bool activeOp(SkOpSpanBase* start, SkOpSpanBase* end, int xorMiMask, int xorSuMask, + SkPathOp op); + bool activeOp(int xorMiMask, int xorSuMask, SkOpSpanBase* start, SkOpSpanBase* end, SkPathOp op, + int* sumMiWinding, int* sumSuWinding); + + bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end); + bool activeWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* sumWinding); + + SkOpSegment* addConic(SkPoint pts[3], SkScalar weight, SkOpContour* parent) { + init(pts, weight, parent, SkPath::kConic_Verb); + SkDCurve curve; + curve.fConic.set(pts, weight); + curve.setConicBounds(pts, weight, 0, 1, &fBounds); + return this; + } + + SkOpSegment* addCubic(SkPoint pts[4], SkOpContour* parent) { + init(pts, 1, parent, SkPath::kCubic_Verb); + SkDCurve curve; + curve.fCubic.set(pts); + curve.setCubicBounds(pts, 1, 0, 1, &fBounds); + return this; + } + + bool addCurveTo(const SkOpSpanBase* start, const SkOpSpanBase* end, SkPathWriter* path) const; + + SkOpAngle* addEndSpan() { + SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(this->globalState()->allocator()); + angle->set(&fTail, fTail.prev()); + fTail.setFromAngle(angle); + return angle; + } + + bool addExpanded(double newT, const SkOpSpanBase* test, bool* startOver); + + SkOpSegment* addLine(SkPoint pts[2], SkOpContour* parent) { + SkASSERT(pts[0] != pts[1]); + init(pts, 1, parent, SkPath::kLine_Verb); + fBounds.set(pts, 2); + return this; + } + + SkOpPtT* addMissing(double t, SkOpSegment* opp, bool* allExist); + + SkOpAngle* addStartSpan() { + SkOpAngle* angle = SkOpTAllocator<SkOpAngle>::Allocate(this->globalState()->allocator()); + angle->set(&fHead, fHead.next()); + fHead.setToAngle(angle); + return angle; + } + + SkOpSegment* addQuad(SkPoint pts[3], SkOpContour* parent) { + init(pts, 1, parent, SkPath::kQuad_Verb); + SkDCurve curve; + curve.fQuad.set(pts); + curve.setQuadBounds(pts, 1, 0, 1, &fBounds); + return this; + } + + SkOpPtT* addT(double t); + + template<typename T> T* allocateArray(int count) { + return SkOpTAllocator<T>::AllocateArray(this->globalState()->allocator(), count); + } + + const SkPathOpsBounds& bounds() const { + return fBounds; + } + + void bumpCount() { + ++fCount; + } + + void calcAngles(); + bool collapsed(double startT, double endT) const; + static void ComputeOneSum(const SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType ); + static void ComputeOneSumReverse(SkOpAngle* baseAngle, SkOpAngle* nextAngle, + SkOpAngle::IncludeType ); + int computeSum(SkOpSpanBase* start, SkOpSpanBase* end, SkOpAngle::IncludeType includeType); + + void clearAll(); + void clearOne(SkOpSpan* span); + static void ClearVisited(SkOpSpanBase* span); + bool contains(double t) const; + + SkOpContour* contour() const { + return fContour; + } + + int count() const { + return fCount; + } + + void debugAddAngle(double startT, double endT); +#if DEBUG_COIN + const SkOpPtT* debugAddT(double t, SkPathOpsDebug::GlitchLog* ) const; +#endif + const SkOpAngle* debugAngle(int id) const; +#if DEBUG_ANGLE + void debugCheckAngleCoin() const; +#endif +#if DEBUG_COIN + void debugCheckHealth(SkPathOpsDebug::GlitchLog* ) const; + void debugClearAll(SkPathOpsDebug::GlitchLog* glitches) const; + void debugClearOne(const SkOpSpan* span, SkPathOpsDebug::GlitchLog* glitches) const; +#endif + const SkOpCoincidence* debugCoincidence() const; + SkOpContour* debugContour(int id) const; + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + + SkOpAngle* debugLastAngle(); +#if DEBUG_COIN + void debugMissingCoincidence(SkPathOpsDebug::GlitchLog* glitches) const; + void debugMoveMultiples(SkPathOpsDebug::GlitchLog* glitches) const; + void debugMoveNearby(SkPathOpsDebug::GlitchLog* glitches) const; +#endif + const SkOpPtT* debugPtT(int id) const; + void debugReset(); + const SkOpSegment* debugSegment(int id) const; + +#if DEBUG_ACTIVE_SPANS + void debugShowActiveSpans() const; +#endif +#if DEBUG_MARK_DONE + void debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding); + void debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding, int oppWinding); +#endif + + const SkOpSpanBase* debugSpan(int id) const; + void debugValidate() const; + +#if DEBUG_COINCIDENCE_ORDER + void debugResetCoinT() const; + void debugSetCoinT(int, SkScalar ) const; +#endif + +#if DEBUG_COIN + static void DebugClearVisited(const SkOpSpanBase* span); + + bool debugVisited() const { + if (!fDebugVisited) { + fDebugVisited = true; + return false; + } + return true; + } +#endif + +#if DEBUG_ANGLE + double distSq(double t, const SkOpAngle* opp) const; +#endif + + bool done() const { + SkOPASSERT(fDoneCount <= fCount); + return fDoneCount == fCount; + } + + bool done(const SkOpAngle* angle) const { + return angle->start()->starter(angle->end())->done(); + } + + SkDPoint dPtAtT(double mid) const { + return (*CurveDPointAtT[fVerb])(fPts, fWeight, mid); + } + + SkDVector dSlopeAtT(double mid) const { + return (*CurveDSlopeAtT[fVerb])(fPts, fWeight, mid); + } + + void dump() const; + void dumpAll() const; + void dumpAngles() const; + void dumpCoin() const; + void dumpPts(const char* prefix = "seg") const; + void dumpPtsInner(const char* prefix = "seg") const; + + const SkOpPtT* existing(double t, const SkOpSegment* opp) const; + SkOpSegment* findNextOp(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** nextStart, + SkOpSpanBase** nextEnd, bool* unsortable, SkPathOp op, + int xorMiMask, int xorSuMask); + SkOpSegment* findNextWinding(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** nextStart, + SkOpSpanBase** nextEnd, bool* unsortable); + SkOpSegment* findNextXor(SkOpSpanBase** nextStart, SkOpSpanBase** nextEnd, bool* unsortable); + SkOpSpan* findSortableTop(SkOpContour* ); + SkOpGlobalState* globalState() const; + + const SkOpSpan* head() const { + return &fHead; + } + + SkOpSpan* head() { + return &fHead; + } + + void init(SkPoint pts[], SkScalar weight, SkOpContour* parent, SkPath::Verb verb); + + SkOpSpan* insert(SkOpSpan* prev) { + SkOpGlobalState* globalState = this->globalState(); + globalState->setAllocatedOpSpan(); + SkOpSpan* result = SkOpTAllocator<SkOpSpan>::Allocate(globalState->allocator()); + SkOpSpanBase* next = prev->next(); + result->setPrev(prev); + prev->setNext(result); + SkDEBUGCODE(result->ptT()->fT = 0); + result->setNext(next); + if (next) { + next->setPrev(result); + } + return result; + } + + bool isClose(double t, const SkOpSegment* opp) const; + + bool isHorizontal() const { + return fBounds.fTop == fBounds.fBottom; + } + + SkOpSegment* isSimple(SkOpSpanBase** end, int* step) { + return nextChase(end, step, nullptr, nullptr); + } + + bool isVertical() const { + return fBounds.fLeft == fBounds.fRight; + } + + bool isVertical(SkOpSpanBase* start, SkOpSpanBase* end) const { + return (*CurveIsVertical[fVerb])(fPts, fWeight, start->t(), end->t()); + } + + bool isXor() const; + + void joinEnds(SkOpSegment* start) { + fTail.ptT()->addOpp(start->fHead.ptT(), start->fHead.ptT()); + } + + const SkPoint& lastPt() const { + return fPts[SkPathOpsVerbToPoints(fVerb)]; + } + + void markAllDone(); + SkOpSpanBase* markAndChaseDone(SkOpSpanBase* start, SkOpSpanBase* end); + bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, + SkOpSpanBase** lastPtr); + bool markAndChaseWinding(SkOpSpanBase* start, SkOpSpanBase* end, int winding, + int oppWinding, SkOpSpanBase** lastPtr); + SkOpSpanBase* markAngle(int maxWinding, int sumWinding, const SkOpAngle* angle); + SkOpSpanBase* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, + const SkOpAngle* angle); + void markDone(SkOpSpan* ); + bool markWinding(SkOpSpan* , int winding); + bool markWinding(SkOpSpan* , int winding, int oppWinding); + bool match(const SkOpPtT* span, const SkOpSegment* parent, double t, const SkPoint& pt) const; + bool missingCoincidence(); + bool moveMultiples(); + void moveNearby(); + + SkOpSegment* next() const { + return fNext; + } + + SkOpSegment* nextChase(SkOpSpanBase** , int* step, SkOpSpan** , SkOpSpanBase** last) const; + bool operand() const; + + static int OppSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { + int result = start->t() < end->t() ? -start->upCast()->oppValue() + : end->upCast()->oppValue(); + return result; + } + + bool oppXor() const; + + const SkOpSegment* prev() const { + return fPrev; + } + + SkPoint ptAtT(double mid) const { + return (*CurvePointAtT[fVerb])(fPts, fWeight, mid); + } + + const SkPoint* pts() const { + return fPts; + } + + bool ptsDisjoint(const SkOpPtT& span, const SkOpPtT& test) const { + SkASSERT(this == span.segment()); + SkASSERT(this == test.segment()); + return ptsDisjoint(span.fT, span.fPt, test.fT, test.fPt); + } + + bool ptsDisjoint(const SkOpPtT& span, double t, const SkPoint& pt) const { + SkASSERT(this == span.segment()); + return ptsDisjoint(span.fT, span.fPt, t, pt); + } + + bool ptsDisjoint(double t1, const SkPoint& pt1, double t2, const SkPoint& pt2) const; + + void rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, SkChunkAlloc*); + void release(const SkOpSpan* ); + +#if DEBUG_COIN + void resetDebugVisited() const { + fDebugVisited = false; + } +#endif + + void resetVisited() { + fVisited = false; + } + + void setContour(SkOpContour* contour) { + fContour = contour; + } + + void setNext(SkOpSegment* next) { + fNext = next; + } + + void setPrev(SkOpSegment* prev) { + fPrev = prev; + } + + void setUpWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* maxWinding, int* sumWinding) { + int deltaSum = SpanSign(start, end); + *maxWinding = *sumWinding; + if (*sumWinding == SK_MinS32) { + return; + } + *sumWinding -= deltaSum; + } + + void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, + int* maxWinding, int* sumWinding); + void setUpWindings(SkOpSpanBase* start, SkOpSpanBase* end, int* sumMiWinding, int* sumSuWinding, + int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); + void sortAngles(); + bool spansNearby(const SkOpSpanBase* ref, const SkOpSpanBase* check) const; + + static int SpanSign(const SkOpSpanBase* start, const SkOpSpanBase* end) { + int result = start->t() < end->t() ? -start->upCast()->windValue() + : end->upCast()->windValue(); + return result; + } + + SkOpAngle* spanToAngle(SkOpSpanBase* start, SkOpSpanBase* end) { + SkASSERT(start != end); + return start->t() < end->t() ? start->upCast()->toAngle() : start->fromAngle(); + } + + bool subDivide(const SkOpSpanBase* start, const SkOpSpanBase* end, SkDCurve* result) const; + + const SkOpSpanBase* tail() const { + return &fTail; + } + + SkOpSpanBase* tail() { + return &fTail; + } + + bool testForCoincidence(const SkOpPtT* priorPtT, const SkOpPtT* ptT, const SkOpSpanBase* prior, + const SkOpSpanBase* spanBase, const SkOpSegment* opp) const; + + void undoneSpan(SkOpSpanBase** start, SkOpSpanBase** end); + int updateOppWinding(const SkOpSpanBase* start, const SkOpSpanBase* end) const; + int updateOppWinding(const SkOpAngle* angle) const; + int updateOppWindingReverse(const SkOpAngle* angle) const; + int updateWinding(SkOpSpanBase* start, SkOpSpanBase* end); + int updateWinding(SkOpAngle* angle); + int updateWindingReverse(const SkOpAngle* angle); + + static bool UseInnerWinding(int outerWinding, int innerWinding); + + SkPath::Verb verb() const { + return fVerb; + } + + // look for two different spans that point to the same opposite segment + bool visited() { + if (!fVisited) { + fVisited = true; + return false; + } + return true; + } + + SkScalar weight() const { + return fWeight; + } + + SkOpSpan* windingSpanAtT(double tHit); + int windSum(const SkOpAngle* angle) const; + +private: + SkOpSpan fHead; // the head span always has its t set to zero + SkOpSpanBase fTail; // the tail span always has its t set to one + SkOpContour* fContour; + SkOpSegment* fNext; // forward-only linked list used by contour to walk the segments + const SkOpSegment* fPrev; + SkPoint* fPts; // pointer into array of points owned by edge builder that may be tweaked + SkPathOpsBounds fBounds; // tight bounds + SkScalar fWeight; + int fCount; // number of spans (one for a non-intersecting segment) + int fDoneCount; // number of processed spans (zero initially) + SkPath::Verb fVerb; + bool fVisited; // used by missing coincidence check +#if DEBUG_COIN + mutable bool fDebugVisited; // used by debug missing coincidence check +#endif +#if DEBUG_COINCIDENCE_ORDER + mutable int fDebugBaseIndex; + mutable SkScalar fDebugBaseMin; // if > 0, the 1st t value in this seg vis-a-vis the ref seg + mutable SkScalar fDebugBaseMax; + mutable int fDebugLastIndex; + mutable SkScalar fDebugLastMin; // if > 0, the last t -- next t val - base has same sign + mutable SkScalar fDebugLastMax; +#endif + SkDEBUGCODE(int fID); +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpSpan.cpp b/gfx/skia/skia/src/pathops/SkOpSpan.cpp new file mode 100755 index 000000000..2abc44e24 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpSpan.cpp @@ -0,0 +1,475 @@ +/* + * Copyright 2014 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" + +bool SkOpPtT::alias() const { + return this->span()->ptT() != this; +} + +const SkOpPtT* SkOpPtT::active() const { + if (!fDeleted) { + return this; + } + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->fSpan == fSpan && !ptT->fDeleted) { + return ptT; + } + } + SkASSERT(0); // should never return deleted + return this; +} + +bool SkOpPtT::contains(const SkOpPtT* check) const { + SkOPASSERT(this != check); + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT == check) { + return true; + } + } + return false; +} + +bool SkOpPtT::contains(const SkOpSegment* segment, const SkPoint& pt) const { + SkASSERT(this->segment() != segment); + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->fPt == pt && ptT->segment() == segment) { + return true; + } + } + return false; +} + +bool SkOpPtT::contains(const SkOpSegment* segment, double t) const { + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->fT == t && ptT->segment() == segment) { + return true; + } + } + return false; +} + +const SkOpPtT* SkOpPtT::contains(const SkOpSegment* check) const { + SkASSERT(this->segment() != check); + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + if (ptT->segment() == check && !ptT->deleted()) { + return ptT; + } + } + return nullptr; +} + +SkOpContour* SkOpPtT::contour() const { + return segment()->contour(); +} + +const SkOpPtT* SkOpPtT::find(const SkOpSegment* segment) const { + const SkOpPtT* ptT = this; + const SkOpPtT* stopPtT = ptT; + do { + if (ptT->segment() == segment && !ptT->deleted()) { + return ptT; + } + ptT = ptT->fNext; + } while (stopPtT != ptT); +// SkASSERT(0); + return nullptr; +} + +SkOpGlobalState* SkOpPtT::globalState() const { + return contour()->globalState(); +} + +void SkOpPtT::init(SkOpSpanBase* span, double t, const SkPoint& pt, bool duplicate) { + fT = t; + fPt = pt; + fSpan = span; + fNext = this; + fDuplicatePt = duplicate; + fDeleted = false; + fCoincident = false; + SkDEBUGCODE(fID = span->globalState()->nextPtTID()); +} + +bool SkOpPtT::onEnd() const { + const SkOpSpanBase* span = this->span(); + if (span->ptT() != this) { + return false; + } + const SkOpSegment* segment = this->segment(); + return span == segment->head() || span == segment->tail(); +} + +bool SkOpPtT::ptAlreadySeen(const SkOpPtT* check) const { + while (this != check) { + if (this->fPt == check->fPt) { + return true; + } + check = check->fNext; + } + return false; +} + +SkOpPtT* SkOpPtT::prev() { + SkOpPtT* result = this; + SkOpPtT* next = this; + while ((next = next->fNext) != this) { + result = next; + } + SkASSERT(result->fNext == this); + return result; +} + +const SkOpSegment* SkOpPtT::segment() const { + return span()->segment(); +} + +SkOpSegment* SkOpPtT::segment() { + return span()->segment(); +} + +void SkOpPtT::setDeleted() { + SkASSERT(this->span()->debugDeleted() || this->span()->ptT() != this); + SkOPASSERT(!fDeleted); + fDeleted = true; +} + +void SkOpSpanBase::addOpp(SkOpSpanBase* opp) { + SkOpPtT* oppPrev = this->ptT()->oppPrev(opp->ptT()); + if (!oppPrev) { + return; + } + this->mergeMatches(opp); + this->ptT()->addOpp(opp->ptT(), oppPrev); + this->checkForCollapsedCoincidence(); +} + +bool SkOpSpanBase::collapsed(double s, double e) const { + const SkOpPtT* start = &fPtT; + const SkOpPtT* walk = start; + double min = walk->fT; + double max = min; + const SkOpSegment* segment = this->segment(); + while ((walk = walk->next()) != start) { + if (walk->segment() != segment) { + continue; + } + min = SkTMin(min, walk->fT); + max = SkTMax(max, walk->fT); + if (between(min, s, max) && between(min, e, max)) { + return true; + } + } + return false; +} + +bool SkOpSpanBase::contains(const SkOpSpanBase* span) const { + const SkOpPtT* start = &fPtT; + const SkOpPtT* check = &span->fPtT; + SkOPASSERT(start != check); + const SkOpPtT* walk = start; + while ((walk = walk->next()) != start) { + if (walk == check) { + return true; + } + } + return false; +} + +const SkOpPtT* SkOpSpanBase::contains(const SkOpSegment* segment) const { + const SkOpPtT* start = &fPtT; + const SkOpPtT* walk = start; + while ((walk = walk->next()) != start) { + if (walk->deleted()) { + continue; + } + if (walk->segment() == segment && walk->span()->ptT() == walk) { + return walk; + } + } + return nullptr; +} + +bool SkOpSpanBase::containsCoinEnd(const SkOpSegment* segment) const { + SkASSERT(this->segment() != segment); + const SkOpSpanBase* next = this; + while ((next = next->fCoinEnd) != this) { + if (next->segment() == segment) { + return true; + } + } + return false; +} + +SkOpContour* SkOpSpanBase::contour() const { + return segment()->contour(); +} + +SkOpGlobalState* SkOpSpanBase::globalState() const { + return contour()->globalState(); +} + +void SkOpSpanBase::initBase(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { + fSegment = segment; + fPtT.init(this, t, pt, false); + fCoinEnd = this; + fFromAngle = nullptr; + fPrev = prev; + fSpanAdds = 0; + fAligned = true; + fChased = false; + SkDEBUGCODE(fCount = 1); + SkDEBUGCODE(fID = globalState()->nextSpanID()); + SkDEBUGCODE(fDebugDeleted = false); +} + +// this pair of spans share a common t value or point; merge them and eliminate duplicates +// this does not compute the best t or pt value; this merely moves all data into a single list +void SkOpSpanBase::merge(SkOpSpan* span) { + SkOpPtT* spanPtT = span->ptT(); + SkASSERT(this->t() != spanPtT->fT); + SkASSERT(!zero_or_one(spanPtT->fT)); + span->release(this->ptT()); + if (this->contains(span)) { + SkOPASSERT(0); // check to see if this ever happens -- should have been found earlier + return; // merge is already in the ptT loop + } + SkOpPtT* remainder = spanPtT->next(); + this->ptT()->insert(spanPtT); + while (remainder != spanPtT) { + SkOpPtT* next = remainder->next(); + SkOpPtT* compare = spanPtT->next(); + while (compare != spanPtT) { + SkOpPtT* nextC = compare->next(); + if (nextC->span() == remainder->span() && nextC->fT == remainder->fT) { + goto tryNextRemainder; + } + compare = nextC; + } + spanPtT->insert(remainder); +tryNextRemainder: + remainder = next; + } + fSpanAdds += span->fSpanAdds; +} + +SkOpSpanBase* SkOpSpanBase::active() { + SkOpSpanBase* result = fPrev ? fPrev->next() : upCast()->next()->prev(); + SkASSERT(this == result || fDebugDeleted); + return result; +} + +// please keep in sync with debugCheckForCollapsedCoincidence() +void SkOpSpanBase::checkForCollapsedCoincidence() { + SkOpCoincidence* coins = this->globalState()->coincidence(); + if (coins->isEmpty()) { + return; + } +// the insert above may have put both ends of a coincident run in the same span +// for each coincident ptT in loop; see if its opposite in is also in the loop +// this implementation is the motivation for marking that a ptT is referenced by a coincident span + SkOpPtT* head = this->ptT(); + SkOpPtT* test = head; + do { + if (!test->coincident()) { + continue; + } + coins->markCollapsed(test); + } while ((test = test->next()) != head); + coins->releaseDeleted(); +} + +// please keep in sync with debugMergeMatches() +// Look to see if pt-t linked list contains same segment more than once +// if so, and if each pt-t is directly pointed to by spans in that segment, +// merge them +// keep the points, but remove spans so that the segment doesn't have 2 or more +// spans pointing to the same pt-t loop at different loop elements +void SkOpSpanBase::mergeMatches(SkOpSpanBase* opp) { + SkOpPtT* test = &fPtT; + SkOpPtT* testNext; + const SkOpPtT* stop = test; + do { + testNext = test->next(); + if (test->deleted()) { + continue; + } + SkOpSpanBase* testBase = test->span(); + SkASSERT(testBase->ptT() == test); + SkOpSegment* segment = test->segment(); + if (segment->done()) { + continue; + } + SkOpPtT* inner = opp->ptT(); + const SkOpPtT* innerStop = inner; + do { + if (inner->segment() != segment) { + continue; + } + if (inner->deleted()) { + continue; + } + SkOpSpanBase* innerBase = inner->span(); + SkASSERT(innerBase->ptT() == inner); + // when the intersection is first detected, the span base is marked if there are + // more than one point in the intersection. + if (!zero_or_one(inner->fT)) { + innerBase->upCast()->release(test); + } else { + SkOPASSERT(inner->fT != test->fT); + if (!zero_or_one(test->fT)) { + testBase->upCast()->release(inner); + } else { + segment->markAllDone(); // mark segment as collapsed + SkDEBUGCODE(testBase->debugSetDeleted()); + test->setDeleted(); + SkDEBUGCODE(innerBase->debugSetDeleted()); + inner->setDeleted(); + } + } +#ifdef SK_DEBUG // assert if another undeleted entry points to segment + const SkOpPtT* debugInner = inner; + while ((debugInner = debugInner->next()) != innerStop) { + if (debugInner->segment() != segment) { + continue; + } + if (debugInner->deleted()) { + continue; + } + SkOPASSERT(0); + } +#endif + break; + } while ((inner = inner->next()) != innerStop); + } while ((test = testNext) != stop); + this->checkForCollapsedCoincidence(); +} + +int SkOpSpan::computeWindSum() { + SkOpGlobalState* globals = this->globalState(); + SkOpContour* contourHead = globals->contourHead(); + int windTry = 0; + while (!this->sortableTop(contourHead) && ++windTry < SkOpGlobalState::kMaxWindingTries) { + ; + } + return this->windSum(); +} + +bool SkOpSpan::containsCoincidence(const SkOpSegment* segment) const { + SkASSERT(this->segment() != segment); + const SkOpSpan* next = fCoincident; + do { + if (next->segment() == segment) { + return true; + } + } while ((next = next->fCoincident) != this); + return false; +} + +void SkOpSpan::init(SkOpSegment* segment, SkOpSpan* prev, double t, const SkPoint& pt) { + SkASSERT(t != 1); + initBase(segment, prev, t, pt); + fCoincident = this; + fToAngle = nullptr; + fWindSum = fOppSum = SK_MinS32; + fWindValue = 1; + fOppValue = 0; + fTopTTry = 0; + fChased = fDone = false; + segment->bumpCount(); + fAlreadyAdded = false; +} + +// Please keep this in sync with debugInsertCoincidence() +bool SkOpSpan::insertCoincidence(const SkOpSegment* segment, bool flipped, bool ordered) { + if (this->containsCoincidence(segment)) { + return true; + } + SkOpPtT* next = &fPtT; + while ((next = next->next()) != &fPtT) { + if (next->segment() == segment) { + SkOpSpan* span; + SkOpSpanBase* base = next->span(); + if (!ordered) { + const SkOpSpanBase* spanEnd = fNext->contains(segment)->span(); + const SkOpPtT* start = base->ptT()->starter(spanEnd->ptT()); + FAIL_IF(!start->span()->upCastable()); + span = const_cast<SkOpSpan*>(start->span()->upCast()); + } else if (flipped) { + span = base->prev(); + FAIL_IF(!span); + } else { + FAIL_IF(!base->upCastable()); + span = base->upCast(); + } + this->insertCoincidence(span); + return true; + } + } +#if DEBUG_COINCIDENCE + SkASSERT(0); // FIXME? if we get here, the span is missing its opposite segment... +#endif + return true; +} + +void SkOpSpan::release(const SkOpPtT* kept) { + SkDEBUGCODE(fDebugDeleted = true); + SkOPASSERT(kept->span() != this); + SkASSERT(!final()); + SkOpSpan* prev = this->prev(); + SkASSERT(prev); + SkOpSpanBase* next = this->next(); + SkASSERT(next); + prev->setNext(next); + next->setPrev(prev); + this->segment()->release(this); + SkOpCoincidence* coincidence = this->globalState()->coincidence(); + if (coincidence) { + coincidence->fixUp(this->ptT(), kept); + } + this->ptT()->setDeleted(); + SkOpPtT* stopPtT = this->ptT(); + SkOpPtT* testPtT = stopPtT; + const SkOpSpanBase* keptSpan = kept->span(); + do { + if (this == testPtT->span()) { + testPtT->setSpan(keptSpan); + } + } while ((testPtT = testPtT->next()) != stopPtT); +} + +void SkOpSpan::setOppSum(int oppSum) { + SkASSERT(!final()); + if (fOppSum != SK_MinS32 && fOppSum != oppSum) { + this->globalState()->setWindingFailed(); + return; + } + SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(oppSum) <= DEBUG_LIMIT_WIND_SUM); + fOppSum = oppSum; +} + +void SkOpSpan::setWindSum(int windSum) { + SkASSERT(!final()); + if (fWindSum != SK_MinS32 && fWindSum != windSum) { + this->globalState()->setWindingFailed(); + return; + } + SkASSERT(!DEBUG_LIMIT_WIND_SUM || SkTAbs(windSum) <= DEBUG_LIMIT_WIND_SUM); + fWindSum = windSum; +} diff --git a/gfx/skia/skia/src/pathops/SkOpSpan.h b/gfx/skia/skia/src/pathops/SkOpSpan.h new file mode 100644 index 000000000..023e7acfb --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpSpan.h @@ -0,0 +1,570 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpSpan_DEFINED +#define SkOpSpan_DEFINED + +#include "SkPathOpsDebug.h" +#include "SkPathOpsTypes.h" +#include "SkPoint.h" + +class SkChunkAlloc; +class SkOpAngle; +class SkOpContour; +class SkOpGlobalState; +class SkOpSegment; +class SkOpSpanBase; +class SkOpSpan; +struct SkPathOpsBounds; + +// subset of op span used by terminal span (when t is equal to one) +class SkOpPtT { +public: + enum { + kIsAlias = 1, + kIsDuplicate = 1 + }; + + const SkOpPtT* active() const; + + // please keep in sync with debugAddOpp() + void addOpp(SkOpPtT* opp, SkOpPtT* oppPrev) { + SkOpPtT* oldNext = this->fNext; + SkASSERT(this != opp); + this->fNext = opp; + SkASSERT(oppPrev != oldNext); + oppPrev->fNext = oldNext; + } + + bool alias() const; + bool coincident() const { return fCoincident; } + bool contains(const SkOpPtT* ) const; + bool contains(const SkOpSegment*, const SkPoint& ) const; + bool contains(const SkOpSegment*, double t) const; + const SkOpPtT* contains(const SkOpSegment* ) const; + SkOpContour* contour() const; + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + + void debugAddOpp(const SkOpPtT* opp, const SkOpPtT* oppPrev) const; + const SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; + bool debugContains(const SkOpPtT* ) const; + const SkOpPtT* debugContains(const SkOpSegment* check) const; + SkOpContour* debugContour(int id) const; + const SkOpPtT* debugEnder(const SkOpPtT* end) const; + int debugLoopLimit(bool report) const; + bool debugMatchID(int id) const; + const SkOpPtT* debugOppPrev(const SkOpPtT* opp) const; + const SkOpPtT* debugPtT(int id) const; + void debugResetCoinT() const; + const SkOpSegment* debugSegment(int id) const; + void debugSetCoinT(int ) const; + const SkOpSpanBase* debugSpan(int id) const; + void debugValidate() const; + + bool deleted() const { + return fDeleted; + } + + bool duplicate() const { + return fDuplicatePt; + } + + void dump() const; // available to testing only + void dumpAll() const; + void dumpBase() const; + + const SkOpPtT* find(const SkOpSegment* ) const; + SkOpGlobalState* globalState() const; + void init(SkOpSpanBase* , double t, const SkPoint& , bool dup); + + void insert(SkOpPtT* span) { + SkASSERT(span != this); + span->fNext = fNext; + fNext = span; + } + + const SkOpPtT* next() const { + return fNext; + } + + SkOpPtT* next() { + return fNext; + } + + bool onEnd() const; + + // returns nullptr if this is already in the opp ptT loop + SkOpPtT* oppPrev(const SkOpPtT* opp) const { + // find the fOpp ptr to opp + SkOpPtT* oppPrev = opp->fNext; + if (oppPrev == this) { + return nullptr; + } + while (oppPrev->fNext != opp) { + oppPrev = oppPrev->fNext; + if (oppPrev == this) { + return nullptr; + } + } + return oppPrev; + } + + static bool Overlaps(const SkOpPtT* s1, const SkOpPtT* e1, const SkOpPtT* s2, + const SkOpPtT* e2, const SkOpPtT** sOut, const SkOpPtT** eOut) { + const SkOpPtT* start1 = s1->fT < e1->fT ? s1 : e1; + const SkOpPtT* start2 = s2->fT < e2->fT ? s2 : e2; + *sOut = between(s1->fT, start2->fT, e1->fT) ? start2 + : between(s2->fT, start1->fT, e2->fT) ? start1 : nullptr; + const SkOpPtT* end1 = s1->fT < e1->fT ? e1 : s1; + const SkOpPtT* end2 = s2->fT < e2->fT ? e2 : s2; + *eOut = between(s1->fT, end2->fT, e1->fT) ? end2 + : between(s2->fT, end1->fT, e2->fT) ? end1 : nullptr; + if (*sOut == *eOut) { + SkASSERT(start1->fT >= end2->fT || start2->fT >= end1->fT); + return false; + } + SkASSERT(!*sOut || *sOut != *eOut); + return *sOut && *eOut; + } + + bool ptAlreadySeen(const SkOpPtT* head) const; + SkOpPtT* prev(); + + const SkOpSegment* segment() const; + SkOpSegment* segment(); + + void setCoincident() const { + SkOPASSERT(!fDeleted); + fCoincident = true; + } + + void setDeleted(); + + void setSpan(const SkOpSpanBase* span) { + fSpan = const_cast<SkOpSpanBase*>(span); + } + + const SkOpSpanBase* span() const { + return fSpan; + } + + SkOpSpanBase* span() { + return fSpan; + } + + const SkOpPtT* starter(const SkOpPtT* end) const { + return fT < end->fT ? this : end; + } + + double fT; + SkPoint fPt; // cache of point value at this t +protected: + SkOpSpanBase* fSpan; // contains winding data + SkOpPtT* fNext; // intersection on opposite curve or alias on this curve + bool fDeleted; // set if removed from span list + bool fDuplicatePt; // set if identical pt is somewhere in the next loop + // below mutable since referrer is otherwise always const + mutable bool fCoincident; // set if at some point a coincident span pointed here + SkDEBUGCODE(int fID); +}; + +class SkOpSpanBase { +public: + SkOpSpanBase* active(); + void addOpp(SkOpSpanBase* opp); + + void bumpSpanAdds() { + ++fSpanAdds; + } + + bool chased() const { + return fChased; + } + + void checkForCollapsedCoincidence(); + + const SkOpSpanBase* coinEnd() const { + return fCoinEnd; + } + + bool collapsed(double s, double e) const; + bool contains(const SkOpSpanBase* ) const; + const SkOpPtT* contains(const SkOpSegment* ) const; + + bool containsCoinEnd(const SkOpSpanBase* coin) const { + SkASSERT(this != coin); + const SkOpSpanBase* next = this; + while ((next = next->fCoinEnd) != this) { + if (next == coin) { + return true; + } + } + return false; + } + + bool containsCoinEnd(const SkOpSegment* ) const; + SkOpContour* contour() const; + + int debugBumpCount() { + return SkDEBUGRELEASE(++fCount, -1); + } + + int debugID() const { + return SkDEBUGRELEASE(fID, -1); + } + +#if DEBUG_COIN + void debugAddOpp(SkPathOpsDebug::GlitchLog* , const SkOpSpanBase* opp) const; +#endif + bool debugAlignedEnd(double t, const SkPoint& pt) const; + bool debugAlignedInner() const; + const SkOpAngle* debugAngle(int id) const; +#if DEBUG_COIN + void debugCheckForCollapsedCoincidence(SkPathOpsDebug::GlitchLog* ) const; +#endif + const SkOpCoincidence* debugCoincidence() const; + bool debugCoinEndLoopCheck() const; + SkOpContour* debugContour(int id) const; +#ifdef SK_DEBUG + bool debugDeleted() const { return fDebugDeleted; } +#endif +#if DEBUG_COIN + void debugInsertCoinEnd(SkPathOpsDebug::GlitchLog* , + const SkOpSpanBase* ) const; + void debugMergeMatches(SkPathOpsDebug::GlitchLog* log, + const SkOpSpanBase* opp) const; +#endif + const SkOpPtT* debugPtT(int id) const; + void debugResetCoinT() const; + const SkOpSegment* debugSegment(int id) const; + void debugSetCoinT(int ) const; +#ifdef SK_DEBUG + void debugSetDeleted() { fDebugDeleted = true; } +#endif + const SkOpSpanBase* debugSpan(int id) const; + const SkOpSpan* debugStarter(SkOpSpanBase const** endPtr) const; + SkOpGlobalState* globalState() const; + void debugValidate() const; + + bool deleted() const { + return fPtT.deleted(); + } + + void dump() const; // available to testing only + void dumpCoin() const; + void dumpAll() const; + void dumpBase() const; + void dumpHead() const; + + bool final() const { + return fPtT.fT == 1; + } + + SkOpAngle* fromAngle() const { + return fFromAngle; + } + + void initBase(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); + + // Please keep this in sync with debugInsertCoinEnd() + void insertCoinEnd(SkOpSpanBase* coin) { + if (containsCoinEnd(coin)) { + SkASSERT(coin->containsCoinEnd(this)); + return; + } + debugValidate(); + SkASSERT(this != coin); + SkOpSpanBase* coinNext = coin->fCoinEnd; + coin->fCoinEnd = this->fCoinEnd; + this->fCoinEnd = coinNext; + debugValidate(); + } + + void merge(SkOpSpan* span); + void mergeMatches(SkOpSpanBase* opp); + + const SkOpSpan* prev() const { + return fPrev; + } + + SkOpSpan* prev() { + return fPrev; + } + + const SkPoint& pt() const { + return fPtT.fPt; + } + + const SkOpPtT* ptT() const { + return &fPtT; + } + + SkOpPtT* ptT() { + return &fPtT; + } + + SkOpSegment* segment() const { + return fSegment; + } + + void setAligned() { + fAligned = true; + } + + void setChased(bool chased) { + fChased = chased; + } + + void setFromAngle(SkOpAngle* angle) { + fFromAngle = angle; + } + + void setPrev(SkOpSpan* prev) { + fPrev = prev; + } + + bool simple() const { + fPtT.debugValidate(); + return fPtT.next()->next() == &fPtT; + } + + int spanAddsCount() const { + return fSpanAdds; + } + + const SkOpSpan* starter(const SkOpSpanBase* end) const { + const SkOpSpanBase* result = t() < end->t() ? this : end; + return result->upCast(); + } + + SkOpSpan* starter(SkOpSpanBase* end) { + SkASSERT(this->segment() == end->segment()); + SkOpSpanBase* result = t() < end->t() ? this : end; + return result->upCast(); + } + + SkOpSpan* starter(SkOpSpanBase** endPtr) { + SkOpSpanBase* end = *endPtr; + SkASSERT(this->segment() == end->segment()); + SkOpSpanBase* result; + if (t() < end->t()) { + result = this; + } else { + result = end; + *endPtr = this; + } + return result->upCast(); + } + + int step(const SkOpSpanBase* end) const { + return t() < end->t() ? 1 : -1; + } + + double t() const { + return fPtT.fT; + } + + void unaligned() { + fAligned = false; + } + + SkOpSpan* upCast() { + SkASSERT(!final()); + return (SkOpSpan*) this; + } + + const SkOpSpan* upCast() const { + SkOPASSERT(!final()); + return (const SkOpSpan*) this; + } + + SkOpSpan* upCastable() { + return final() ? nullptr : upCast(); + } + + const SkOpSpan* upCastable() const { + return final() ? nullptr : upCast(); + } + +private: + void alignInner(); + +protected: // no direct access to internals to avoid treating a span base as a span + SkOpPtT fPtT; // list of points and t values associated with the start of this span + SkOpSegment* fSegment; // segment that contains this span + SkOpSpanBase* fCoinEnd; // linked list of coincident spans that end here (may point to itself) + SkOpAngle* fFromAngle; // points to next angle from span start to end + SkOpSpan* fPrev; // previous intersection point + int fSpanAdds; // number of times intersections have been added to span + bool fAligned; + bool fChased; // set after span has been added to chase array + SkDEBUGCODE(int fCount); // number of pt/t pairs added + SkDEBUGCODE(int fID); + SkDEBUGCODE(bool fDebugDeleted); // set when span was merged with another span +}; + +class SkOpSpan : public SkOpSpanBase { +public: + bool alreadyAdded() const { + if (fAlreadyAdded) { + return true; + } + fAlreadyAdded = true; + return false; + } + + bool clearCoincident() { + SkASSERT(!final()); + if (fCoincident == this) { + return false; + } + fCoincident = this; + return true; + } + + int computeWindSum(); + bool containsCoincidence(const SkOpSegment* ) const; + + bool containsCoincidence(const SkOpSpan* coin) const { + SkASSERT(this != coin); + const SkOpSpan* next = this; + while ((next = next->fCoincident) != this) { + if (next == coin) { + return true; + } + } + return false; + } + + bool debugCoinLoopCheck() const; +#if DEBUG_COIN + void debugInsertCoincidence(SkPathOpsDebug::GlitchLog* , const SkOpSpan* ) const; + void debugInsertCoincidence(SkPathOpsDebug::GlitchLog* , + const SkOpSegment* , bool flipped, bool ordered) const; +#endif + void dumpCoin() const; + bool dumpSpan() const; + + bool done() const { + SkASSERT(!final()); + return fDone; + } + + void init(SkOpSegment* parent, SkOpSpan* prev, double t, const SkPoint& pt); + bool insertCoincidence(const SkOpSegment* , bool flipped, bool ordered); + + // Please keep this in sync with debugInsertCoincidence() + void insertCoincidence(SkOpSpan* coin) { + if (containsCoincidence(coin)) { + SkASSERT(coin->containsCoincidence(this)); + return; + } + debugValidate(); + SkASSERT(this != coin); + SkOpSpan* coinNext = coin->fCoincident; + coin->fCoincident = this->fCoincident; + this->fCoincident = coinNext; + debugValidate(); + } + + bool isCanceled() const { + SkASSERT(!final()); + return fWindValue == 0 && fOppValue == 0; + } + + bool isCoincident() const { + SkASSERT(!final()); + return fCoincident != this; + } + + SkOpSpanBase* next() const { + SkASSERT(!final()); + return fNext; + } + + int oppSum() const { + SkASSERT(!final()); + return fOppSum; + } + + int oppValue() const { + SkASSERT(!final()); + return fOppValue; + } + + void release(const SkOpPtT* ); + + SkOpPtT* setCoinStart(SkOpSpan* oldCoinStart, SkOpSegment* oppSegment); + + void setDone(bool done) { + SkASSERT(!final()); + fDone = done; + } + + void setNext(SkOpSpanBase* nextT) { + SkASSERT(!final()); + fNext = nextT; + } + + void setOppSum(int oppSum); + + void setOppValue(int oppValue) { + SkASSERT(!final()); + SkASSERT(fOppSum == SK_MinS32); + SkASSERT(!oppValue || !fDone); + fOppValue = oppValue; + } + + void setToAngle(SkOpAngle* angle) { + SkASSERT(!final()); + fToAngle = angle; + } + + void setWindSum(int windSum); + + void setWindValue(int windValue) { + SkASSERT(!final()); + SkASSERT(windValue >= 0); + SkASSERT(fWindSum == SK_MinS32); + SkOPASSERT(!windValue || !fDone); + fWindValue = windValue; + } + + bool sortableTop(SkOpContour* ); + + SkOpAngle* toAngle() const { + SkASSERT(!final()); + return fToAngle; + } + + int windSum() const { + SkASSERT(!final()); + return fWindSum; + } + + int windValue() const { + SkOPASSERT(!final()); + return fWindValue; + } + +private: // no direct access to internals to avoid treating a span base as a span + SkOpSpan* fCoincident; // linked list of spans coincident with this one (may point to itself) + SkOpAngle* fToAngle; // points to next angle from span start to end + SkOpSpanBase* fNext; // next intersection point + int fWindSum; // accumulated from contours surrounding this one. + int fOppSum; // for binary operators: the opposite winding sum + int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident + int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here + int fTopTTry; // specifies direction and t value to try next + bool fDone; // if set, this span to next higher T has been processed + mutable bool fAlreadyAdded; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkOpTAllocator.h b/gfx/skia/skia/src/pathops/SkOpTAllocator.h new file mode 100644 index 000000000..e8835f02e --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkOpTAllocator.h @@ -0,0 +1,33 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpTAllocator_DEFINED +#define SkOpTAllocator_DEFINED + +#include "SkChunkAlloc.h" + +// T is SkOpAngle2, SkOpSpan2, or SkOpSegment2 +template<typename T> +class SkOpTAllocator { +public: + static T* Allocate(SkChunkAlloc* allocator) { + void* ptr = allocator->allocThrow(sizeof(T)); + T* record = (T*) ptr; + return record; + } + + static T* AllocateArray(SkChunkAlloc* allocator, int count) { + void* ptr = allocator->allocThrow(sizeof(T) * count); + T* record = (T*) ptr; + return record; + } + + static T* New(SkChunkAlloc* allocator) { + return new (Allocate(allocator)) T(); + } +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsBounds.h b/gfx/skia/skia/src/pathops/SkPathOpsBounds.h new file mode 100644 index 000000000..610d7233a --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsBounds.h @@ -0,0 +1,65 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpBounds_DEFINED +#define SkPathOpBounds_DEFINED + +#include "SkPathOpsRect.h" +#include "SkRect.h" + +// SkPathOpsBounds, unlike SkRect, does not consider a line to be empty. +struct SkPathOpsBounds : public SkRect { + static bool Intersects(const SkPathOpsBounds& a, const SkPathOpsBounds& b) { + return AlmostLessOrEqualUlps(a.fLeft, b.fRight) + && AlmostLessOrEqualUlps(b.fLeft, a.fRight) + && AlmostLessOrEqualUlps(a.fTop, b.fBottom) + && AlmostLessOrEqualUlps(b.fTop, a.fBottom); + } + + // Note that add(), unlike SkRect::join() or SkRect::growToInclude() + // does not treat the bounds of horizontal and vertical lines as + // empty rectangles. + void add(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) { + if (left < fLeft) fLeft = left; + if (top < fTop) fTop = top; + if (right > fRight) fRight = right; + if (bottom > fBottom) fBottom = bottom; + } + + void add(const SkPathOpsBounds& toAdd) { + add(toAdd.fLeft, toAdd.fTop, toAdd.fRight, toAdd.fBottom); + } + + void add(const SkPoint& pt) { + if (pt.fX < fLeft) fLeft = pt.fX; + if (pt.fY < fTop) fTop = pt.fY; + if (pt.fX > fRight) fRight = pt.fX; + if (pt.fY > fBottom) fBottom = pt.fY; + } + + void add(const SkDPoint& pt) { + if (pt.fX < fLeft) fLeft = SkDoubleToScalar(pt.fX); + if (pt.fY < fTop) fTop = SkDoubleToScalar(pt.fY); + if (pt.fX > fRight) fRight = SkDoubleToScalar(pt.fX); + if (pt.fY > fBottom) fBottom = SkDoubleToScalar(pt.fY); + } + + bool almostContains(const SkPoint& pt) const { + return AlmostLessOrEqualUlps(fLeft, pt.fX) + && AlmostLessOrEqualUlps(pt.fX, fRight) + && AlmostLessOrEqualUlps(fTop, pt.fY) + && AlmostLessOrEqualUlps(pt.fY, fBottom); + } + + bool contains(const SkPoint& pt) const { + return fLeft <= pt.fX && fTop <= pt.fY && + fRight >= pt.fX && fBottom >= pt.fY; + } + + typedef SkRect INHERITED; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCommon.cpp b/gfx/skia/skia/src/pathops/SkPathOpsCommon.cpp new file mode 100644 index 000000000..3d6ba4dda --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCommon.cpp @@ -0,0 +1,335 @@ +/* + * 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 "SkAddIntersections.h" +#include "SkOpCoincidence.h" +#include "SkOpEdgeBuilder.h" +#include "SkPathOpsCommon.h" +#include "SkPathWriter.h" +#include "SkTSort.h" + +SkScalar ScaleFactor(const SkPath& path) { + static const SkScalar twoTo10 = 1024.f; + SkScalar largest = 0; + const SkScalar* oneBounds = &path.getBounds().fLeft; + for (int index = 0; index < 4; ++index) { + largest = SkTMax(largest, SkScalarAbs(oneBounds[index])); + } + SkScalar scale = twoTo10; + SkScalar next; + while ((next = scale * twoTo10) < largest) { + scale = next; + } + return scale == twoTo10 ? SK_Scalar1 : scale; +} + +void ScalePath(const SkPath& path, SkScalar scale, SkPath* scaled) { + SkMatrix matrix; + matrix.setScale(scale, scale); + *scaled = path; + scaled->transform(matrix); +} + +const SkOpAngle* AngleWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* windingPtr, + bool* sortablePtr) { + // find first angle, initialize winding to computed fWindSum + SkOpSegment* segment = start->segment(); + const SkOpAngle* angle = segment->spanToAngle(start, end); + if (!angle) { + *windingPtr = SK_MinS32; + return nullptr; + } + bool computeWinding = false; + const SkOpAngle* firstAngle = angle; + bool loop = false; + bool unorderable = false; + int winding = SK_MinS32; + do { + angle = angle->next(); + if (!angle) { + return nullptr; + } + unorderable |= angle->unorderable(); + if ((computeWinding = unorderable || (angle == firstAngle && loop))) { + break; // if we get here, there's no winding, loop is unorderable + } + loop |= angle == firstAngle; + segment = angle->segment(); + winding = segment->windSum(angle); + } while (winding == SK_MinS32); + // if the angle loop contains an unorderable span, the angle order may be useless + // directly compute the winding in this case for each span + if (computeWinding) { + firstAngle = angle; + winding = SK_MinS32; + do { + SkOpSpanBase* startSpan = angle->start(); + SkOpSpanBase* endSpan = angle->end(); + SkOpSpan* lesser = startSpan->starter(endSpan); + int testWinding = lesser->windSum(); + if (testWinding == SK_MinS32) { + testWinding = lesser->computeWindSum(); + } + if (testWinding != SK_MinS32) { + segment = angle->segment(); + winding = testWinding; + } + angle = angle->next(); + } while (angle != firstAngle); + } + *sortablePtr = !unorderable; + *windingPtr = winding; + return angle; +} + +SkOpSegment* FindUndone(SkOpContourHead* contourList, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr) { + SkOpSegment* result; + SkOpContour* contour = contourList; + do { + result = contour->undoneSegment(startPtr, endPtr); + if (result) { + return result; + } + } while ((contour = contour->next())); + return nullptr; +} + +SkOpSegment* FindChase(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr) { + while (chase->count()) { + SkOpSpanBase* span; + chase->pop(&span); + SkOpSegment* segment = span->segment(); + *startPtr = span->ptT()->next()->span(); + bool done = true; + *endPtr = nullptr; + if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done)) { + *startPtr = last->start(); + *endPtr = last->end(); + #if TRY_ROTATE + *chase->insert(0) = span; + #else + *chase->append() = span; + #endif + return last->segment(); + } + if (done) { + continue; + } + // find first angle, initialize winding to computed wind sum + int winding; + bool sortable; + const SkOpAngle* angle = AngleWinding(*startPtr, *endPtr, &winding, &sortable); + if (!angle) { + return nullptr; + } + if (winding == SK_MinS32) { + continue; + } + int sumWinding SK_INIT_TO_AVOID_WARNING; + if (sortable) { + segment = angle->segment(); + sumWinding = segment->updateWindingReverse(angle); + } + SkOpSegment* first = nullptr; + const SkOpAngle* firstAngle = angle; + while ((angle = angle->next()) != firstAngle) { + segment = angle->segment(); + SkOpSpanBase* start = angle->start(); + SkOpSpanBase* end = angle->end(); + int maxWinding SK_INIT_TO_AVOID_WARNING; + if (sortable) { + segment->setUpWinding(start, end, &maxWinding, &sumWinding); + } + if (!segment->done(angle)) { + if (!first && (sortable || start->starter(end)->windSum() != SK_MinS32)) { + first = segment; + *startPtr = start; + *endPtr = end; + } + // OPTIMIZATION: should this also add to the chase? + if (sortable) { + (void) segment->markAngle(maxWinding, sumWinding, angle); + } + } + } + if (first) { + #if TRY_ROTATE + *chase->insert(0) = span; + #else + *chase->append() = span; + #endif + return first; + } + } + return nullptr; +} + +bool SortContourList(SkOpContourHead** contourList, bool evenOdd, bool oppEvenOdd) { + SkTDArray<SkOpContour* > list; + SkOpContour* contour = *contourList; + do { + if (contour->count()) { + contour->setOppXor(contour->operand() ? evenOdd : oppEvenOdd); + *list.append() = contour; + } + } while ((contour = contour->next())); + int count = list.count(); + if (!count) { + return false; + } + if (count > 1) { + SkTQSort<SkOpContour>(list.begin(), list.end() - 1); + } + contour = list[0]; + SkOpContourHead* contourHead = static_cast<SkOpContourHead*>(contour); + contour->globalState()->setContourHead(contourHead); + *contourList = contourHead; + for (int index = 1; index < count; ++index) { + SkOpContour* next = list[index]; + contour->setNext(next); + contour = next; + } + contour->setNext(nullptr); + return true; +} + +static void calc_angles(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { + DEBUG_STATIC_SET_PHASE(contourList); + SkOpContour* contour = contourList; + do { + contour->calcAngles(); + } while ((contour = contour->next())); +} + +static bool missing_coincidence(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { + DEBUG_STATIC_SET_PHASE(contourList); + SkOpContour* contour = contourList; + bool result = false; + do { + result |= contour->missingCoincidence(); + } while ((contour = contour->next())); + return result; +} + +static bool move_multiples(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { + DEBUG_STATIC_SET_PHASE(contourList); + SkOpContour* contour = contourList; + do { + if (!contour->moveMultiples()) { + return false; + } + } while ((contour = contour->next())); + return true; +} + +static void move_nearby(SkOpContourHead* contourList DEBUG_COIN_DECLARE_PARAMS()) { + DEBUG_STATIC_SET_PHASE(contourList); + SkOpContour* contour = contourList; + do { + contour->moveNearby(); + } while ((contour = contour->next())); +} + +static void sort_angles(SkOpContourHead* contourList) { + SkOpContour* contour = contourList; + do { + contour->sortAngles(); + } while ((contour = contour->next())); +} + +bool HandleCoincidence(SkOpContourHead* contourList, SkOpCoincidence* coincidence) { + SkOpGlobalState* globalState = contourList->globalState(); + // match up points within the coincident runs + if (!coincidence->addExpanded(DEBUG_PHASE_ONLY_PARAMS(kIntersecting))) { + return false; + } + // combine t values when multiple intersections occur on some segments but not others + if (!move_multiples(contourList DEBUG_PHASE_PARAMS(kWalking))) { + return false; + } + // move t values and points together to eliminate small/tiny gaps + move_nearby(contourList DEBUG_COIN_PARAMS()); + // add coincidence formed by pairing on curve points and endpoints + coincidence->correctEnds(DEBUG_PHASE_ONLY_PARAMS(kIntersecting)); + if (!coincidence->addEndMovedSpans(DEBUG_COIN_ONLY_PARAMS())) { + return false; + } + const int SAFETY_COUNT = 3; + int safetyHatch = SAFETY_COUNT; + // look for coincidence present in A-B and A-C but missing in B-C + do { + bool added; + if (!coincidence->addMissing(&added DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch))) { + return false; + } + if (!added) { + break; + } + if (!--safetyHatch) { + SkASSERT(globalState->debugSkipAssert()); + return false; + } + move_nearby(contourList DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch - 1)); + } while (true); + // check to see if, loosely, coincident ranges may be expanded + if (coincidence->expand(DEBUG_COIN_ONLY_PARAMS())) { + bool added; + if (!coincidence->addMissing(&added DEBUG_COIN_PARAMS())) { + return false; + } + if (!coincidence->addExpanded(DEBUG_COIN_ONLY_PARAMS())) { + return false; + } + if (!move_multiples(contourList DEBUG_COIN_PARAMS())) { + return false; + } + move_nearby(contourList DEBUG_COIN_PARAMS()); + } + // the expanded ranges may not align -- add the missing spans + if (!coincidence->addExpanded(DEBUG_PHASE_ONLY_PARAMS(kWalking))) { + return false; + } + // mark spans of coincident segments as coincident + coincidence->mark(DEBUG_COIN_ONLY_PARAMS()); + // look for coincidence lines and curves undetected by intersection + if (missing_coincidence(contourList DEBUG_COIN_PARAMS())) { + (void) coincidence->expand(DEBUG_PHASE_ONLY_PARAMS(kIntersecting)); + if (!coincidence->addExpanded(DEBUG_COIN_ONLY_PARAMS())) { + return false; + } + coincidence->mark(DEBUG_PHASE_ONLY_PARAMS(kWalking)); + } else { + (void) coincidence->expand(DEBUG_COIN_ONLY_PARAMS()); + } + (void) coincidence->expand(DEBUG_COIN_ONLY_PARAMS()); + + SkOpCoincidence overlaps(globalState); + safetyHatch = SAFETY_COUNT; + do { + SkOpCoincidence* pairs = overlaps.isEmpty() ? coincidence : &overlaps; + // adjust the winding value to account for coincident edges + pairs->apply(DEBUG_ITER_ONLY_PARAMS(SAFETY_COUNT - safetyHatch)); + // For each coincident pair that overlaps another, when the receivers (the 1st of the pair) + // are different, construct a new pair to resolve their mutual span + pairs->findOverlaps(&overlaps DEBUG_ITER_PARAMS(SAFETY_COUNT - safetyHatch)); + if (!--safetyHatch) { + SkASSERT(globalState->debugSkipAssert()); + return false; + } + } while (!overlaps.isEmpty()); + calc_angles(contourList DEBUG_COIN_PARAMS()); + sort_angles(contourList); +#if DEBUG_COINCIDENCE_VERBOSE + coincidence->debugShowCoincidence(); +#endif +#if DEBUG_COINCIDENCE + coincidence->debugValidate(); +#endif + SkPathOpsDebug::ShowActiveSpans(contourList); + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCommon.h b/gfx/skia/skia/src/pathops/SkPathOpsCommon.h new file mode 100644 index 000000000..beffb8522 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCommon.h @@ -0,0 +1,33 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsCommon_DEFINED +#define SkPathOpsCommon_DEFINED + +#include "SkOpAngle.h" +#include "SkTDArray.h" + +class SkOpCoincidence; +class SkOpContour; +class SkPathWriter; + +const SkOpAngle* AngleWinding(SkOpSpanBase* start, SkOpSpanBase* end, int* windingPtr, + bool* sortable); +SkOpSegment* FindChase(SkTDArray<SkOpSpanBase*>* chase, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr); +SkOpSpan* FindSortableTop(SkOpContourHead* ); +SkOpSegment* FindUndone(SkOpContourHead* , SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr); +bool FixWinding(SkPath* path); +bool SortContourList(SkOpContourHead** , bool evenOdd, bool oppEvenOdd); +bool HandleCoincidence(SkOpContourHead* , SkOpCoincidence* ); +bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result + SkDEBUGPARAMS(bool skipAssert) + SkDEBUGPARAMS(const char* testName)); +SkScalar ScaleFactor(const SkPath& path); +void ScalePath(const SkPath& path, SkScalar scale, SkPath* scaled); + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsConic.cpp b/gfx/skia/skia/src/pathops/SkPathOpsConic.cpp new file mode 100644 index 000000000..dd523211d --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsConic.cpp @@ -0,0 +1,169 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkIntersections.h" +#include "SkLineParameters.h" +#include "SkPathOpsConic.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsQuad.h" + +// cribbed from the float version in SkGeometry.cpp +static void conic_deriv_coeff(const double src[], + SkScalar w, + double coeff[3]) { + const double P20 = src[4] - src[0]; + const double P10 = src[2] - src[0]; + const double wP10 = w * P10; + coeff[0] = w * P20 - P20; + coeff[1] = P20 - 2 * wP10; + coeff[2] = wP10; +} + +static double conic_eval_tan(const double coord[], SkScalar w, double t) { + double coeff[3]; + conic_deriv_coeff(coord, w, coeff); + return t * (t * coeff[0] + coeff[1]) + coeff[2]; +} + +int SkDConic::FindExtrema(const double src[], SkScalar w, double t[1]) { + double coeff[3]; + conic_deriv_coeff(src, w, coeff); + + double tValues[2]; + int roots = SkDQuad::RootsValidT(coeff[0], coeff[1], coeff[2], tValues); + // In extreme cases, the number of roots returned can be 2. Pathops + // will fail later on, so there's no advantage to plumbing in an error + // return here. + // SkASSERT(0 == roots || 1 == roots); + + if (1 == roots) { + t[0] = tValues[0]; + return 1; + } + return 0; +} + +SkDVector SkDConic::dxdyAtT(double t) const { + SkDVector result = { + conic_eval_tan(&fPts[0].fX, fWeight, t), + conic_eval_tan(&fPts[0].fY, fWeight, t) + }; + if (result.fX == 0 && result.fY == 0) { + if (zero_or_one(t)) { + result = fPts[2] - fPts[0]; + } else { + // incomplete + SkDebugf("!k"); + } + } + return result; +} + +static double conic_eval_numerator(const double src[], SkScalar w, double t) { + SkASSERT(src); + SkASSERT(t >= 0 && t <= 1); + double src2w = src[2] * w; + double C = src[0]; + double A = src[4] - 2 * src2w + C; + double B = 2 * (src2w - C); + return (A * t + B) * t + C; +} + + +static double conic_eval_denominator(SkScalar w, double t) { + double B = 2 * (w - 1); + double C = 1; + double A = -B; + return (A * t + B) * t + C; +} + +bool SkDConic::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { + return cubic.hullIntersects(*this, isLinear); +} + +SkDPoint SkDConic::ptAtT(double t) const { + if (t == 0) { + return fPts[0]; + } + if (t == 1) { + return fPts[2]; + } + double denominator = conic_eval_denominator(fWeight, t); + SkDPoint result = { + conic_eval_numerator(&fPts[0].fX, fWeight, t) / denominator, + conic_eval_numerator(&fPts[0].fY, fWeight, t) / denominator + }; + return result; +} + +/* see quad subdivide for point rationale */ +/* w rationale : the mid point between t1 and t2 could be determined from the computed a/b/c + values if the computed w was known. Since we know the mid point at (t1+t2)/2, we'll assume + that it is the same as the point on the new curve t==(0+1)/2. + + d / dz == conic_poly(dst, unknownW, .5) / conic_weight(unknownW, .5); + + conic_poly(dst, unknownW, .5) + = a / 4 + (b * unknownW) / 2 + c / 4 + = (a + c) / 4 + (bx * unknownW) / 2 + + conic_weight(unknownW, .5) + = unknownW / 2 + 1 / 2 + + d / dz == ((a + c) / 2 + b * unknownW) / (unknownW + 1) + d / dz * (unknownW + 1) == (a + c) / 2 + b * unknownW + unknownW = ((a + c) / 2 - d / dz) / (d / dz - b) + + Thus, w is the ratio of the distance from the mid of end points to the on-curve point, and the + distance of the on-curve point to the control point. + */ +SkDConic SkDConic::subDivide(double t1, double t2) const { + double ax, ay, az; + if (t1 == 0) { + ax = fPts[0].fX; + ay = fPts[0].fY; + az = 1; + } else if (t1 != 1) { + ax = conic_eval_numerator(&fPts[0].fX, fWeight, t1); + ay = conic_eval_numerator(&fPts[0].fY, fWeight, t1); + az = conic_eval_denominator(fWeight, t1); + } else { + ax = fPts[2].fX; + ay = fPts[2].fY; + az = 1; + } + double midT = (t1 + t2) / 2; + double dx = conic_eval_numerator(&fPts[0].fX, fWeight, midT); + double dy = conic_eval_numerator(&fPts[0].fY, fWeight, midT); + double dz = conic_eval_denominator(fWeight, midT); + double cx, cy, cz; + if (t2 == 1) { + cx = fPts[2].fX; + cy = fPts[2].fY; + cz = 1; + } else if (t2 != 0) { + cx = conic_eval_numerator(&fPts[0].fX, fWeight, t2); + cy = conic_eval_numerator(&fPts[0].fY, fWeight, t2); + cz = conic_eval_denominator(fWeight, t2); + } else { + cx = fPts[0].fX; + cy = fPts[0].fY; + cz = 1; + } + double bx = 2 * dx - (ax + cx) / 2; + double by = 2 * dy - (ay + cy) / 2; + double bz = 2 * dz - (az + cz) / 2; + SkDConic dst = {{{{ax / az, ay / az}, {bx / bz, by / bz}, {cx / cz, cy / cz}}}, + SkDoubleToScalar(bz / sqrt(az * cz)) }; + return dst; +} + +SkDPoint SkDConic::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2, + SkScalar* weight) const { + SkDConic chopped = this->subDivide(t1, t2); + *weight = chopped.fWeight; + return chopped[1]; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsConic.h b/gfx/skia/skia/src/pathops/SkPathOpsConic.h new file mode 100644 index 000000000..4cbe147b4 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsConic.h @@ -0,0 +1,123 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsConic_DEFINED +#define SkPathOpsConic_DEFINED + +#include "SkPathOpsPoint.h" +#include "SkPathOpsQuad.h" + +struct SkDConic { + static const int kPointCount = 3; + static const int kPointLast = kPointCount - 1; + static const int kMaxIntersections = 4; + + SkDQuad fPts; + SkScalar fWeight; + + bool collapsed() const { + return fPts.collapsed(); + } + + bool controlsInside() const { + return fPts.controlsInside(); + } + + void debugInit() { + fPts.debugInit(); + } + + SkDConic flip() const { + SkDConic result = {{{fPts[2], fPts[1], fPts[0]}}, fWeight}; + return result; + } + + static bool IsConic() { return true; } + + const SkDConic& set(const SkPoint pts[kPointCount], SkScalar weight) { + fPts.set(pts); + fWeight = weight; + return *this; + } + + const SkDPoint& operator[](int n) const { return fPts[n]; } + SkDPoint& operator[](int n) { return fPts[n]; } + + static int AddValidTs(double s[], int realRoots, double* t) { + return SkDQuad::AddValidTs(s, realRoots, t); + } + + void align(int endIndex, SkDPoint* dstPt) const { + fPts.align(endIndex, dstPt); + } + + SkDVector dxdyAtT(double t) const; + static int FindExtrema(const double src[], SkScalar weight, double tValue[1]); + + bool hullIntersects(const SkDQuad& quad, bool* isLinear) const { + return fPts.hullIntersects(quad, isLinear); + } + + bool hullIntersects(const SkDConic& conic, bool* isLinear) const { + return fPts.hullIntersects(conic.fPts, isLinear); + } + + bool hullIntersects(const SkDCubic& cubic, bool* isLinear) const; + + bool isLinear(int startIndex, int endIndex) const { + return fPts.isLinear(startIndex, endIndex); + } + + bool monotonicInX() const { + return fPts.monotonicInX(); + } + + bool monotonicInY() const { + return fPts.monotonicInY(); + } + + void otherPts(int oddMan, const SkDPoint* endPt[2]) const { + fPts.otherPts(oddMan, endPt); + } + + SkDPoint ptAtT(double t) const; + + static int RootsReal(double A, double B, double C, double t[2]) { + return SkDQuad::RootsReal(A, B, C, t); + } + + static int RootsValidT(const double A, const double B, const double C, double s[2]) { + return SkDQuad::RootsValidT(A, B, C, s); + } + + SkDConic subDivide(double t1, double t2) const; + + static SkDConic SubDivide(const SkPoint a[kPointCount], SkScalar weight, double t1, double t2) { + SkDConic conic; + conic.set(a, weight); + return conic.subDivide(t1, t2); + } + + SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2, + SkScalar* weight) const; + + static SkDPoint SubDivide(const SkPoint pts[kPointCount], SkScalar weight, + const SkDPoint& a, const SkDPoint& c, + double t1, double t2, SkScalar* newWeight) { + SkDConic conic; + conic.set(pts, weight); + return conic.subDivide(a, c, t1, t2, newWeight); + } + + // utilities callable by the user from the debugger when the implementation code is linked in + void dump() const; + void dumpID(int id) const; + void dumpInner() const; +}; + + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCubic.cpp b/gfx/skia/skia/src/pathops/SkPathOpsCubic.cpp new file mode 100644 index 000000000..bdae492de --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCubic.cpp @@ -0,0 +1,706 @@ +/* + * 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 "SkGeometry.h" +#include "SkLineParameters.h" +#include "SkPathOpsConic.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsCurve.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" +#include "SkPathOpsRect.h" +#include "SkTSort.h" + +const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework + +void SkDCubic::align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const { + if (fPts[endIndex].fX == fPts[ctrlIndex].fX) { + dstPt->fX = fPts[endIndex].fX; + } + if (fPts[endIndex].fY == fPts[ctrlIndex].fY) { + dstPt->fY = fPts[endIndex].fY; + } +} + +// give up when changing t no longer moves point +// also, copy point rather than recompute it when it does change +double SkDCubic::binarySearch(double min, double max, double axisIntercept, + SearchAxis xAxis) const { + double t = (min + max) / 2; + double step = (t - min) / 2; + SkDPoint cubicAtT = ptAtT(t); + double calcPos = (&cubicAtT.fX)[xAxis]; + double calcDist = calcPos - axisIntercept; + do { + double priorT = t - step; + SkASSERT(priorT >= min); + SkDPoint lessPt = ptAtT(priorT); + if (approximately_equal_half(lessPt.fX, cubicAtT.fX) + && approximately_equal_half(lessPt.fY, cubicAtT.fY)) { + return -1; // binary search found no point at this axis intercept + } + double lessDist = (&lessPt.fX)[xAxis] - axisIntercept; +#if DEBUG_CUBIC_BINARY_SEARCH + SkDebugf("t=%1.9g calc=%1.9g dist=%1.9g step=%1.9g less=%1.9g\n", t, calcPos, calcDist, + step, lessDist); +#endif + double lastStep = step; + step /= 2; + if (calcDist > 0 ? calcDist > lessDist : calcDist < lessDist) { + t = priorT; + } else { + double nextT = t + lastStep; + if (nextT > max) { + return -1; + } + SkDPoint morePt = ptAtT(nextT); + if (approximately_equal_half(morePt.fX, cubicAtT.fX) + && approximately_equal_half(morePt.fY, cubicAtT.fY)) { + return -1; // binary search found no point at this axis intercept + } + double moreDist = (&morePt.fX)[xAxis] - axisIntercept; + if (calcDist > 0 ? calcDist <= moreDist : calcDist >= moreDist) { + continue; + } + t = nextT; + } + SkDPoint testAtT = ptAtT(t); + cubicAtT = testAtT; + calcPos = (&cubicAtT.fX)[xAxis]; + calcDist = calcPos - axisIntercept; + } while (!approximately_equal(calcPos, axisIntercept)); + return t; +} + +// FIXME: cache keep the bounds and/or precision with the caller? +double SkDCubic::calcPrecision() const { + SkDRect dRect; + dRect.setBounds(*this); // OPTIMIZATION: just use setRawBounds ? + double width = dRect.fRight - dRect.fLeft; + double height = dRect.fBottom - dRect.fTop; + return (width > height ? width : height) / gPrecisionUnit; +} + + +/* classic one t subdivision */ +static void interp_cubic_coords(const double* src, double* dst, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double cd = SkDInterp(src[4], src[6], t); + double abc = SkDInterp(ab, bc, t); + double bcd = SkDInterp(bc, cd, t); + double abcd = SkDInterp(abc, bcd, t); + + dst[0] = src[0]; + dst[2] = ab; + dst[4] = abc; + dst[6] = abcd; + dst[8] = bcd; + dst[10] = cd; + dst[12] = src[6]; +} + +SkDCubicPair SkDCubic::chopAt(double t) const { + SkDCubicPair dst; + if (t == 0.5) { + dst.pts[0] = fPts[0]; + dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2; + dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2; + dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4; + dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4; + dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8; + dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8; + dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4; + dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4; + dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2; + dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2; + dst.pts[6] = fPts[3]; + return dst; + } + interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t); + interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t); + return dst; +} + +void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) { + *A = src[6]; // d + *B = src[4] * 3; // 3*c + *C = src[2] * 3; // 3*b + *D = src[0]; // a + *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d + *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c + *C -= 3 * *D; // C = -3*a + 3*b +} + +bool SkDCubic::endsAreExtremaInXOrY() const { + return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX) + && between(fPts[0].fX, fPts[2].fX, fPts[3].fX)) + || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY) + && between(fPts[0].fY, fPts[2].fY, fPts[3].fY)); +} + +// Do a quick reject by rotating all points relative to a line formed by +// a pair of one cubic's points. If the 2nd cubic's points +// are on the line or on the opposite side from the 1st cubic's 'odd man', the +// curves at most intersect at the endpoints. +/* if returning true, check contains true if cubic's hull collapsed, making the cubic linear + if returning false, check contains true if the the cubic pair have only the end point in common +*/ +bool SkDCubic::hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const { + bool linear = true; + char hullOrder[4]; + int hullCount = convexHull(hullOrder); + int end1 = hullOrder[0]; + int hullIndex = 0; + const SkDPoint* endPt[2]; + endPt[0] = &fPts[end1]; + do { + hullIndex = (hullIndex + 1) % hullCount; + int end2 = hullOrder[hullIndex]; + endPt[1] = &fPts[end2]; + double origX = endPt[0]->fX; + double origY = endPt[0]->fY; + double adj = endPt[1]->fX - origX; + double opp = endPt[1]->fY - origY; + int oddManMask = other_two(end1, end2); + int oddMan = end1 ^ oddManMask; + double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp; + int oddMan2 = end2 ^ oddManMask; + double sign2 = (fPts[oddMan2].fY - origY) * adj - (fPts[oddMan2].fX - origX) * opp; + if (sign * sign2 < 0) { + continue; + } + if (approximately_zero(sign)) { + sign = sign2; + if (approximately_zero(sign)) { + continue; + } + } + linear = false; + bool foundOutlier = false; + for (int n = 0; n < ptCount; ++n) { + double test = (pts[n].fY - origY) * adj - (pts[n].fX - origX) * opp; + if (test * sign > 0 && !precisely_zero(test)) { + foundOutlier = true; + break; + } + } + if (!foundOutlier) { + return false; + } + endPt[0] = endPt[1]; + end1 = end2; + } while (hullIndex); + *isLinear = linear; + return true; +} + +bool SkDCubic::hullIntersects(const SkDCubic& c2, bool* isLinear) const { + return hullIntersects(c2.fPts, c2.kPointCount, isLinear); +} + +bool SkDCubic::hullIntersects(const SkDQuad& quad, bool* isLinear) const { + return hullIntersects(quad.fPts, quad.kPointCount, isLinear); +} + +bool SkDCubic::hullIntersects(const SkDConic& conic, bool* isLinear) const { + + return hullIntersects(conic.fPts, isLinear); +} + +bool SkDCubic::isLinear(int startIndex, int endIndex) const { + if (fPts[0].approximatelyDEqual(fPts[3])) { + return ((const SkDQuad *) this)->isLinear(0, 2); + } + SkLineParameters lineParameters; + lineParameters.cubicEndPoints(*this, startIndex, endIndex); + // FIXME: maybe it's possible to avoid this and compare non-normalized + lineParameters.normalize(); + double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), + fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY); + double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), + fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY), fPts[3].fX), fPts[3].fY); + largest = SkTMax(largest, -tiniest); + double distance = lineParameters.controlPtDistance(*this, 1); + if (!approximately_zero_when_compared_to(distance, largest)) { + return false; + } + distance = lineParameters.controlPtDistance(*this, 2); + return approximately_zero_when_compared_to(distance, largest); +} + +bool SkDCubic::ComplexBreak(const SkPoint pointsPtr[4], SkScalar* t) { + SkScalar d[3]; + SkCubicType cubicType = SkClassifyCubic(pointsPtr, d); + if (cubicType == kLoop_SkCubicType) { + // crib code from gpu path utils that finds t values where loop self-intersects + // use it to find mid of t values which should be a friendly place to chop + SkScalar tempSqrt = SkScalarSqrt(4.f * d[0] * d[2] - 3.f * d[1] * d[1]); + SkScalar ls = d[1] - tempSqrt; + SkScalar lt = 2.f * d[0]; + SkScalar ms = d[1] + tempSqrt; + SkScalar mt = 2.f * d[0]; + if (roughly_between(0, ls, lt) && roughly_between(0, ms, mt)) { + ls = ls / lt; + ms = ms / mt; + SkASSERT(roughly_between(0, ls, 1) && roughly_between(0, ms, 1)); + *t = (ls + ms) / 2; + SkASSERT(roughly_between(0, *t, 1)); + return *t > 0 && *t < 1; + } + } else if (kSerpentine_SkCubicType == cubicType || kCusp_SkCubicType == cubicType) { + SkDCubic cubic; + cubic.set(pointsPtr); + double inflectionTs[2]; + int infTCount = cubic.findInflections(inflectionTs); + if (infTCount == 2) { + double maxCurvature[3]; + int roots = cubic.findMaxCurvature(maxCurvature); +#if DEBUG_CUBIC_SPLIT + SkDebugf("%s\n", __FUNCTION__); + cubic.dump(); + for (int index = 0; index < infTCount; ++index) { + SkDebugf("inflectionsTs[%d]=%1.9g ", index, inflectionTs[index]); + SkDPoint pt = cubic.ptAtT(inflectionTs[index]); + SkDVector dPt = cubic.dxdyAtT(inflectionTs[index]); + SkDLine perp = {{pt - dPt, pt + dPt}}; + perp.dump(); + } + for (int index = 0; index < roots; ++index) { + SkDebugf("maxCurvature[%d]=%1.9g ", index, maxCurvature[index]); + SkDPoint pt = cubic.ptAtT(maxCurvature[index]); + SkDVector dPt = cubic.dxdyAtT(maxCurvature[index]); + SkDLine perp = {{pt - dPt, pt + dPt}}; + perp.dump(); + } +#endif + for (int index = 0; index < roots; ++index) { + if (between(inflectionTs[0], maxCurvature[index], inflectionTs[1])) { + *t = maxCurvature[index]; + return *t > 0 && *t < 1; + } + } + } else if (infTCount == 1) { + *t = inflectionTs[0]; + return *t > 0 && *t < 1; + } + } + return false; +} + +bool SkDCubic::monotonicInX() const { + return precisely_between(fPts[0].fX, fPts[1].fX, fPts[3].fX) + && precisely_between(fPts[0].fX, fPts[2].fX, fPts[3].fX); +} + +bool SkDCubic::monotonicInY() const { + return precisely_between(fPts[0].fY, fPts[1].fY, fPts[3].fY) + && precisely_between(fPts[0].fY, fPts[2].fY, fPts[3].fY); +} + +void SkDCubic::otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const { + int offset = (int) !SkToBool(index); + o1Pts[0] = &fPts[offset]; + o1Pts[1] = &fPts[++offset]; + o1Pts[2] = &fPts[++offset]; +} + +int SkDCubic::searchRoots(double extremeTs[6], int extrema, double axisIntercept, + SearchAxis xAxis, double* validRoots) const { + extrema += findInflections(&extremeTs[extrema]); + extremeTs[extrema++] = 0; + extremeTs[extrema] = 1; + SkASSERT(extrema < 6); + SkTQSort(extremeTs, extremeTs + extrema); + int validCount = 0; + for (int index = 0; index < extrema; ) { + double min = extremeTs[index]; + double max = extremeTs[++index]; + if (min == max) { + continue; + } + double newT = binarySearch(min, max, axisIntercept, xAxis); + if (newT >= 0) { + if (validCount >= 3) { + return 0; + } + validRoots[validCount++] = newT; + } + } + return validCount; +} + +// cubic roots + +static const double PI = 3.141592653589793; + +// from SkGeometry.cpp (and Numeric Solutions, 5.6) +int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) { + double s[3]; + int realRoots = RootsReal(A, B, C, D, s); + int foundRoots = SkDQuad::AddValidTs(s, realRoots, t); + for (int index = 0; index < realRoots; ++index) { + double tValue = s[index]; + if (!approximately_one_or_less(tValue) && between(1, tValue, 1.00005)) { + for (int idx2 = 0; idx2 < foundRoots; ++idx2) { + if (approximately_equal(t[idx2], 1)) { + goto nextRoot; + } + } + SkASSERT(foundRoots < 3); + t[foundRoots++] = 1; + } else if (!approximately_zero_or_more(tValue) && between(-0.00005, tValue, 0)) { + for (int idx2 = 0; idx2 < foundRoots; ++idx2) { + if (approximately_equal(t[idx2], 0)) { + goto nextRoot; + } + } + SkASSERT(foundRoots < 3); + t[foundRoots++] = 0; + } +nextRoot: + ; + } + return foundRoots; +} + +int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) { +#ifdef SK_DEBUG + // create a string mathematica understands + // GDB set print repe 15 # if repeated digits is a bother + // set print elements 400 # if line doesn't fit + char str[1024]; + sk_bzero(str, sizeof(str)); + SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]", + A, B, C, D); + SkPathOpsDebug::MathematicaIze(str, sizeof(str)); +#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA + SkDebugf("%s\n", str); +#endif +#endif + if (approximately_zero(A) + && approximately_zero_when_compared_to(A, B) + && approximately_zero_when_compared_to(A, C) + && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic + return SkDQuad::RootsReal(B, C, D, s); + } + if (approximately_zero_when_compared_to(D, A) + && approximately_zero_when_compared_to(D, B) + && approximately_zero_when_compared_to(D, C)) { // 0 is one root + int num = SkDQuad::RootsReal(A, B, C, s); + for (int i = 0; i < num; ++i) { + if (approximately_zero(s[i])) { + return num; + } + } + s[num++] = 0; + return num; + } + if (approximately_zero(A + B + C + D)) { // 1 is one root + int num = SkDQuad::RootsReal(A, A + B, -D, s); + for (int i = 0; i < num; ++i) { + if (AlmostDequalUlps(s[i], 1)) { + return num; + } + } + s[num++] = 1; + return num; + } + double a, b, c; + { + double invA = 1 / A; + a = B * invA; + b = C * invA; + c = D * invA; + } + double a2 = a * a; + double Q = (a2 - b * 3) / 9; + double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54; + double R2 = R * R; + double Q3 = Q * Q * Q; + double R2MinusQ3 = R2 - Q3; + double adiv3 = a / 3; + double r; + double* roots = s; + if (R2MinusQ3 < 0) { // we have 3 real roots + // the divide/root can, due to finite precisions, be slightly outside of -1...1 + double theta = acos(SkTPin(R / sqrt(Q3), -1., 1.)); + double neg2RootQ = -2 * sqrt(Q); + + r = neg2RootQ * cos(theta / 3) - adiv3; + *roots++ = r; + + r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3; + if (!AlmostDequalUlps(s[0], r)) { + *roots++ = r; + } + r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3; + if (!AlmostDequalUlps(s[0], r) && (roots - s == 1 || !AlmostDequalUlps(s[1], r))) { + *roots++ = r; + } + } else { // we have 1 real root + double sqrtR2MinusQ3 = sqrt(R2MinusQ3); + double A = fabs(R) + sqrtR2MinusQ3; + A = SkDCubeRoot(A); + if (R > 0) { + A = -A; + } + if (A != 0) { + A += Q / A; + } + r = A - adiv3; + *roots++ = r; + if (AlmostDequalUlps((double) R2, (double) Q3)) { + r = -A / 2 - adiv3; + if (!AlmostDequalUlps(s[0], r)) { + *roots++ = r; + } + } + } + return static_cast<int>(roots - s); +} + +// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf +// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3 +// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2 +// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2 +static double derivative_at_t(const double* src, double t) { + double one_t = 1 - t; + double a = src[0]; + double b = src[2]; + double c = src[4]; + double d = src[6]; + return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); +} + +// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t? +SkDVector SkDCubic::dxdyAtT(double t) const { + SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) }; + if (result.fX == 0 && result.fY == 0) { + if (t == 0) { + result = fPts[2] - fPts[0]; + } else if (t == 1) { + result = fPts[3] - fPts[1]; + } else { + // incomplete + SkDebugf("!c"); + } + if (result.fX == 0 && result.fY == 0 && zero_or_one(t)) { + result = fPts[3] - fPts[0]; + } + } + return result; +} + +// OPTIMIZE? share code with formulate_F1DotF2 +int SkDCubic::findInflections(double tValues[]) const { + double Ax = fPts[1].fX - fPts[0].fX; + double Ay = fPts[1].fY - fPts[0].fY; + double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX; + double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY; + double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX; + double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY; + return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues); +} + +static void formulate_F1DotF2(const double src[], double coeff[4]) { + double a = src[2] - src[0]; + double b = src[4] - 2 * src[2] + src[0]; + double c = src[6] + 3 * (src[2] - src[4]) - src[0]; + coeff[0] = c * c; + coeff[1] = 3 * b * c; + coeff[2] = 2 * b * b + c * a; + coeff[3] = a * b; +} + +/** SkDCubic'(t) = At^2 + Bt + C, where + A = 3(-a + 3(b - c) + d) + B = 6(a - 2b + c) + C = 3(b - a) + Solve for t, keeping only those that fit between 0 < t < 1 +*/ +int SkDCubic::FindExtrema(const double src[], double tValues[2]) { + // we divide A,B,C by 3 to simplify + double a = src[0]; + double b = src[2]; + double c = src[4]; + double d = src[6]; + double A = d - a + 3 * (b - c); + double B = 2 * (a - b - b + c); + double C = b - a; + + return SkDQuad::RootsValidT(A, B, C, tValues); +} + +/* from SkGeometry.cpp + Looking for F' dot F'' == 0 + + A = b - a + B = c - 2b + a + C = d - 3c + 3b - a + + F' = 3Ct^2 + 6Bt + 3A + F'' = 6Ct + 6B + + F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB +*/ +int SkDCubic::findMaxCurvature(double tValues[]) const { + double coeffX[4], coeffY[4]; + int i; + formulate_F1DotF2(&fPts[0].fX, coeffX); + formulate_F1DotF2(&fPts[0].fY, coeffY); + for (i = 0; i < 4; i++) { + coeffX[i] = coeffX[i] + coeffY[i]; + } + return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues); +} + +SkDPoint SkDCubic::ptAtT(double t) const { + if (0 == t) { + return fPts[0]; + } + if (1 == t) { + return fPts[3]; + } + double one_t = 1 - t; + double one_t2 = one_t * one_t; + double a = one_t2 * one_t; + double b = 3 * one_t2 * t; + double t2 = t * t; + double c = 3 * one_t * t2; + double d = t2 * t; + SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY}; + return result; +} + +/* + Given a cubic c, t1, and t2, find a small cubic segment. + + The new cubic is defined as points A, B, C, and D, where + s1 = 1 - t1 + s2 = 1 - t2 + A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1 + D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2 + + We don't have B or C. So We define two equations to isolate them. + First, compute two reference T values 1/3 and 2/3 from t1 to t2: + + c(at (2*t1 + t2)/3) == E + c(at (t1 + 2*t2)/3) == F + + Next, compute where those values must be if we know the values of B and C: + + _12 = A*2/3 + B*1/3 + 12_ = A*1/3 + B*2/3 + _23 = B*2/3 + C*1/3 + 23_ = B*1/3 + C*2/3 + _34 = C*2/3 + D*1/3 + 34_ = C*1/3 + D*2/3 + _123 = (A*2/3 + B*1/3)*2/3 + (B*2/3 + C*1/3)*1/3 = A*4/9 + B*4/9 + C*1/9 + 123_ = (A*1/3 + B*2/3)*1/3 + (B*1/3 + C*2/3)*2/3 = A*1/9 + B*4/9 + C*4/9 + _234 = (B*2/3 + C*1/3)*2/3 + (C*2/3 + D*1/3)*1/3 = B*4/9 + C*4/9 + D*1/9 + 234_ = (B*1/3 + C*2/3)*1/3 + (C*1/3 + D*2/3)*2/3 = B*1/9 + C*4/9 + D*4/9 + _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3 + = A*8/27 + B*12/27 + C*6/27 + D*1/27 + = E + 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3 + = A*1/27 + B*6/27 + C*12/27 + D*8/27 + = F + E*27 = A*8 + B*12 + C*6 + D + F*27 = A + B*6 + C*12 + D*8 + +Group the known values on one side: + + M = E*27 - A*8 - D = B*12 + C* 6 + N = F*27 - A - D*8 = B* 6 + C*12 + M*2 - N = B*18 + N*2 - M = C*18 + B = (M*2 - N)/18 + C = (N*2 - M)/18 + */ + +static double interp_cubic_coords(const double* src, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double cd = SkDInterp(src[4], src[6], t); + double abc = SkDInterp(ab, bc, t); + double bcd = SkDInterp(bc, cd, t); + double abcd = SkDInterp(abc, bcd, t); + return abcd; +} + +SkDCubic SkDCubic::subDivide(double t1, double t2) const { + if (t1 == 0 || t2 == 1) { + if (t1 == 0 && t2 == 1) { + return *this; + } + SkDCubicPair pair = chopAt(t1 == 0 ? t2 : t1); + SkDCubic dst = t1 == 0 ? pair.first() : pair.second(); + return dst; + } + SkDCubic dst; + double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1); + double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1); + double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3); + double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3); + double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3); + double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3); + double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2); + double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2); + double mx = ex * 27 - ax * 8 - dx; + double my = ey * 27 - ay * 8 - dy; + double nx = fx * 27 - ax - dx * 8; + double ny = fy * 27 - ay - dy * 8; + /* bx = */ dst[1].fX = (mx * 2 - nx) / 18; + /* by = */ dst[1].fY = (my * 2 - ny) / 18; + /* cx = */ dst[2].fX = (nx * 2 - mx) / 18; + /* cy = */ dst[2].fY = (ny * 2 - my) / 18; + // FIXME: call align() ? + return dst; +} + +void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d, + double t1, double t2, SkDPoint dst[2]) const { + SkASSERT(t1 != t2); + // this approach assumes that the control points computed directly are accurate enough + SkDCubic sub = subDivide(t1, t2); + dst[0] = sub[1] + (a - sub[0]); + dst[1] = sub[2] + (d - sub[3]); + if (t1 == 0 || t2 == 0) { + align(0, 1, t1 == 0 ? &dst[0] : &dst[1]); + } + if (t1 == 1 || t2 == 1) { + align(3, 2, t1 == 1 ? &dst[0] : &dst[1]); + } + if (AlmostBequalUlps(dst[0].fX, a.fX)) { + dst[0].fX = a.fX; + } + if (AlmostBequalUlps(dst[0].fY, a.fY)) { + dst[0].fY = a.fY; + } + if (AlmostBequalUlps(dst[1].fX, d.fX)) { + dst[1].fX = d.fX; + } + if (AlmostBequalUlps(dst[1].fY, d.fY)) { + dst[1].fY = d.fY; + } +} + +double SkDCubic::top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const { + double extremeTs[2]; + double topT = -1; + int roots = SkDCubic::FindExtrema(&fPts[0].fY, extremeTs); + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * extremeTs[index]; + SkDPoint mid = dCurve.ptAtT(t); + if (topPt->fY > mid.fY || (topPt->fY == mid.fY && topPt->fX > mid.fX)) { + topT = t; + *topPt = mid; + } + } + return topT; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCubic.h b/gfx/skia/skia/src/pathops/SkPathOpsCubic.h new file mode 100644 index 000000000..16bca7953 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCubic.h @@ -0,0 +1,150 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsCubic_DEFINED +#define SkPathOpsCubic_DEFINED + +#include "SkPath.h" +#include "SkPathOpsPoint.h" + +struct SkDCubicPair { + const SkDCubic& first() const { return (const SkDCubic&) pts[0]; } + const SkDCubic& second() const { return (const SkDCubic&) pts[3]; } + SkDPoint pts[7]; +}; + +struct SkDCubic { + static const int kPointCount = 4; + static const int kPointLast = kPointCount - 1; + static const int kMaxIntersections = 9; + + enum SearchAxis { + kXAxis, + kYAxis + }; + + bool collapsed() const { + return fPts[0].approximatelyEqual(fPts[1]) && fPts[0].approximatelyEqual(fPts[2]) + && fPts[0].approximatelyEqual(fPts[3]); + } + + bool controlsInside() const { + SkDVector v01 = fPts[0] - fPts[1]; + SkDVector v02 = fPts[0] - fPts[2]; + SkDVector v03 = fPts[0] - fPts[3]; + SkDVector v13 = fPts[1] - fPts[3]; + SkDVector v23 = fPts[2] - fPts[3]; + return v03.dot(v01) > 0 && v03.dot(v02) > 0 && v03.dot(v13) > 0 && v03.dot(v23) > 0; + } + + static bool IsConic() { return false; } + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + + void align(int endIndex, int ctrlIndex, SkDPoint* dstPt) const; + double binarySearch(double min, double max, double axisIntercept, SearchAxis xAxis) const; + double calcPrecision() const; + SkDCubicPair chopAt(double t) const; + static void Coefficients(const double* cubic, double* A, double* B, double* C, double* D); + static bool ComplexBreak(const SkPoint pts[4], SkScalar* t); + int convexHull(char order[kPointCount]) const; + + void debugInit() { + sk_bzero(fPts, sizeof(fPts)); + } + + void dump() const; // callable from the debugger when the implementation code is linked in + void dumpID(int id) const; + void dumpInner() const; + SkDVector dxdyAtT(double t) const; + bool endsAreExtremaInXOrY() const; + static int FindExtrema(const double src[], double tValue[2]); + int findInflections(double tValues[2]) const; + + static int FindInflections(const SkPoint a[kPointCount], double tValues[2]) { + SkDCubic cubic; + return cubic.set(a).findInflections(tValues); + } + + int findMaxCurvature(double tValues[]) const; + bool hullIntersects(const SkDCubic& c2, bool* isLinear) const; + bool hullIntersects(const SkDConic& c, bool* isLinear) const; + bool hullIntersects(const SkDQuad& c2, bool* isLinear) const; + bool hullIntersects(const SkDPoint* pts, int ptCount, bool* isLinear) const; + bool isLinear(int startIndex, int endIndex) const; + bool monotonicInX() const; + bool monotonicInY() const; + void otherPts(int index, const SkDPoint* o1Pts[kPointCount - 1]) const; + SkDPoint ptAtT(double t) const; + static int RootsReal(double A, double B, double C, double D, double t[3]); + static int RootsValidT(const double A, const double B, const double C, double D, double s[3]); + + int searchRoots(double extremes[6], int extrema, double axisIntercept, + SearchAxis xAxis, double* validRoots) const; + + /** + * Return the number of valid roots (0 < root < 1) for this cubic intersecting the + * specified horizontal line. + */ + int horizontalIntersect(double yIntercept, double roots[3]) const; + /** + * Return the number of valid roots (0 < root < 1) for this cubic intersecting the + * specified vertical line. + */ + int verticalIntersect(double xIntercept, double roots[3]) const; + + const SkDCubic& set(const SkPoint pts[kPointCount]) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + fPts[2] = pts[2]; + fPts[3] = pts[3]; + return *this; + } + + SkDCubic subDivide(double t1, double t2) const; + + static SkDCubic SubDivide(const SkPoint a[kPointCount], double t1, double t2) { + SkDCubic cubic; + return cubic.set(a).subDivide(t1, t2); + } + + void subDivide(const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint p[2]) const; + + static void SubDivide(const SkPoint pts[kPointCount], const SkDPoint& a, const SkDPoint& d, double t1, + double t2, SkDPoint p[2]) { + SkDCubic cubic; + cubic.set(pts).subDivide(a, d, t1, t2, p); + } + + double top(const SkDCubic& dCurve, double startT, double endT, SkDPoint*topPt) const; + SkDQuad toQuad() const; + + static const int gPrecisionUnit; + + SkDPoint fPts[kPointCount]; +}; + +/* Given the set [0, 1, 2, 3], and two of the four members, compute an XOR mask + that computes the other two. Note that: + + one ^ two == 3 for (0, 3), (1, 2) + one ^ two < 3 for (0, 1), (0, 2), (1, 3), (2, 3) + 3 - (one ^ two) is either 0, 1, or 2 + 1 >> (3 - (one ^ two)) is either 0 or 1 +thus: + returned == 2 for (0, 3), (1, 2) + returned == 3 for (0, 1), (0, 2), (1, 3), (2, 3) +given that: + (0, 3) ^ 2 -> (2, 1) (1, 2) ^ 2 -> (3, 0) + (0, 1) ^ 3 -> (3, 2) (0, 2) ^ 3 -> (3, 1) (1, 3) ^ 3 -> (2, 0) (2, 3) ^ 3 -> (1, 0) +*/ +inline int other_two(int one, int two) { + return 1 >> (3 - (one ^ two)) ^ 3; +} + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCurve.cpp b/gfx/skia/skia/src/pathops/SkPathOpsCurve.cpp new file mode 100644 index 000000000..503c140aa --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCurve.cpp @@ -0,0 +1,145 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkPathOpsBounds.h" +#include "SkPathOpsRect.h" +#include "SkPathOpsCurve.h" + + // this cheats and assumes that the perpendicular to the point is the closest ray to the curve + // this case (where the line and the curve are nearly coincident) may be the only case that counts +double SkDCurve::nearPoint(SkPath::Verb verb, const SkDPoint& xy, const SkDPoint& opp) const { + int count = SkPathOpsVerbToPoints(verb); + double minX = fCubic.fPts[0].fX; + double maxX = minX; + for (int index = 1; index <= count; ++index) { + minX = SkTMin(minX, fCubic.fPts[index].fX); + maxX = SkTMax(maxX, fCubic.fPts[index].fX); + } + if (!AlmostBetweenUlps(minX, xy.fX, maxX)) { + return -1; + } + double minY = fCubic.fPts[0].fY; + double maxY = minY; + for (int index = 1; index <= count; ++index) { + minY = SkTMin(minY, fCubic.fPts[index].fY); + maxY = SkTMax(maxY, fCubic.fPts[index].fY); + } + if (!AlmostBetweenUlps(minY, xy.fY, maxY)) { + return -1; + } + SkIntersections i; + SkDLine perp = {{ xy, { xy.fX + opp.fY - xy.fY, xy.fY + xy.fX - opp.fX }}}; + (*CurveDIntersectRay[verb])(*this, perp, &i); + int minIndex = -1; + double minDist = FLT_MAX; + for (int index = 0; index < i.used(); ++index) { + double dist = xy.distance(i.pt(index)); + if (minDist > dist) { + minDist = dist; + minIndex = index; + } + } + if (minIndex < 0) { + return -1; + } + double largest = SkTMax(SkTMax(maxX, maxY), -SkTMin(minX, minY)); + if (!AlmostEqualUlps_Pin(largest, largest + minDist)) { // is distance within ULPS tolerance? + return -1; + } + return SkPinT(i[0][minIndex]); +} + +void SkDCurve::offset(SkPath::Verb verb, const SkDVector& off) { + int count = SkPathOpsVerbToPoints(verb); + for (int index = 0; index <= count; ++index) { + fCubic.fPts[index] += off; + } +} + +void SkDCurve::setConicBounds(const SkPoint curve[3], SkScalar curveWeight, + double tStart, double tEnd, SkPathOpsBounds* bounds) { + SkDConic dCurve; + dCurve.set(curve, curveWeight); + SkDRect dRect; + dRect.setBounds(dCurve, fConic, tStart, tEnd); + bounds->set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), + SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); +} + +void SkDCurve::setCubicBounds(const SkPoint curve[4], SkScalar , + double tStart, double tEnd, SkPathOpsBounds* bounds) { + SkDCubic dCurve; + dCurve.set(curve); + SkDRect dRect; + dRect.setBounds(dCurve, fCubic, tStart, tEnd); + bounds->set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), + SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); +} + +void SkDCurve::setQuadBounds(const SkPoint curve[3], SkScalar , + double tStart, double tEnd, SkPathOpsBounds* bounds) { + SkDQuad dCurve; + dCurve.set(curve); + SkDRect dRect; + dRect.setBounds(dCurve, fQuad, tStart, tEnd); + bounds->set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), + SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); +} + +void SkDCurveSweep::setCurveHullSweep(SkPath::Verb verb) { + fOrdered = true; + fSweep[0] = fCurve[1] - fCurve[0]; + if (SkPath::kLine_Verb == verb) { + fSweep[1] = fSweep[0]; + fIsCurve = false; + return; + } + fSweep[1] = fCurve[2] - fCurve[0]; + // OPTIMIZE: I do the following float check a lot -- probably need a + // central place for this val-is-small-compared-to-curve check + double maxVal = 0; + for (int index = 0; index <= SkPathOpsVerbToPoints(verb); ++index) { + maxVal = SkTMax(maxVal, SkTMax(SkTAbs(fCurve[index].fX), + SkTAbs(fCurve[index].fY))); + } + { + if (SkPath::kCubic_Verb != verb) { + if (roughly_zero_when_compared_to(fSweep[0].fX, maxVal) + && roughly_zero_when_compared_to(fSweep[0].fY, maxVal)) { + fSweep[0] = fSweep[1]; + } + goto setIsCurve; + } + SkDVector thirdSweep = fCurve[3] - fCurve[0]; + if (fSweep[0].fX == 0 && fSweep[0].fY == 0) { + fSweep[0] = fSweep[1]; + fSweep[1] = thirdSweep; + if (roughly_zero_when_compared_to(fSweep[0].fX, maxVal) + && roughly_zero_when_compared_to(fSweep[0].fY, maxVal)) { + fSweep[0] = fSweep[1]; + fCurve[1] = fCurve[3]; + } + goto setIsCurve; + } + double s1x3 = fSweep[0].crossCheck(thirdSweep); + double s3x2 = thirdSweep.crossCheck(fSweep[1]); + if (s1x3 * s3x2 >= 0) { // if third vector is on or between first two vectors + goto setIsCurve; + } + double s2x1 = fSweep[1].crossCheck(fSweep[0]); + // FIXME: If the sweep of the cubic is greater than 180 degrees, we're in trouble + // probably such wide sweeps should be artificially subdivided earlier so that never happens + SkASSERT(s1x3 * s2x1 < 0 || s1x3 * s3x2 < 0); + if (s3x2 * s2x1 < 0) { + SkASSERT(s2x1 * s1x3 > 0); + fSweep[0] = fSweep[1]; + fOrdered = false; + } + fSweep[1] = thirdSweep; + } +setIsCurve: + fIsCurve = fSweep[0].crossCheck(fSweep[1]) != 0; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsCurve.h b/gfx/skia/skia/src/pathops/SkPathOpsCurve.h new file mode 100644 index 000000000..2b50864e5 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsCurve.h @@ -0,0 +1,415 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsCurve_DEFINE +#define SkPathOpsCurve_DEFINE + +#include "SkIntersections.h" + +#ifndef SK_RELEASE +#include "SkPath.h" +#endif + +struct SkPathOpsBounds; + +struct SkOpCurve { + SkPoint fPts[4]; + SkScalar fWeight; + SkDEBUGCODE(SkPath::Verb fVerb); + + const SkPoint& operator[](int n) const { + SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); + return fPts[n]; + } + + void dump() const; + + void set(const SkDQuad& quad) { + for (int index = 0; index < SkDQuad::kPointCount; ++index) { + fPts[index] = quad[index].asSkPoint(); + } + SkDEBUGCODE(fWeight = 1); + SkDEBUGCODE(fVerb = SkPath::kQuad_Verb); + } + + void set(const SkDCubic& cubic) { + for (int index = 0; index < SkDCubic::kPointCount; ++index) { + fPts[index] = cubic[index].asSkPoint(); + } + SkDEBUGCODE(fWeight = 1); + SkDEBUGCODE(fVerb = SkPath::kCubic_Verb); + } + +}; + +struct SkDCurve { + union { + SkDLine fLine; + SkDQuad fQuad; + SkDConic fConic; + SkDCubic fCubic; + }; + SkDEBUGCODE(SkPath::Verb fVerb); + + const SkDPoint& operator[](int n) const { + SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); + return fCubic[n]; + } + + SkDPoint& operator[](int n) { + SkASSERT(n >= 0 && n <= SkPathOpsVerbToPoints(fVerb)); + return fCubic[n]; + } + + SkDPoint conicTop(const SkPoint curve[3], SkScalar curveWeight, + double s, double e, double* topT); + SkDPoint cubicTop(const SkPoint curve[4], SkScalar , double s, double e, double* topT); + void dump() const; + void dumpID(int ) const; + SkDPoint lineTop(const SkPoint[2], SkScalar , double , double , double* topT); + double nearPoint(SkPath::Verb verb, const SkDPoint& xy, const SkDPoint& opp) const; + void offset(SkPath::Verb verb, const SkDVector& ); + SkDPoint quadTop(const SkPoint curve[3], SkScalar , double s, double e, double* topT); + + void setConicBounds(const SkPoint curve[3], SkScalar curveWeight, + double s, double e, SkPathOpsBounds* ); + void setCubicBounds(const SkPoint curve[4], SkScalar , + double s, double e, SkPathOpsBounds* ); + void setQuadBounds(const SkPoint curve[3], SkScalar , + double s, double e, SkPathOpsBounds*); +}; + +class SkDCurveSweep { +public: + bool isCurve() const { return fIsCurve; } + bool isOrdered() const { return fOrdered; } + void setCurveHullSweep(SkPath::Verb verb); + + SkDCurve fCurve; + SkDVector fSweep[2]; +private: + bool fIsCurve; + bool fOrdered; // cleared when a cubic's control point isn't between the sweep vectors + +}; + +extern SkDPoint (SkDCurve::* const Top[])(const SkPoint curve[], SkScalar cWeight, + double tStart, double tEnd, double* topT); + +static SkDPoint dline_xy_at_t(const SkPoint a[2], SkScalar , double t) { + SkDLine line; + line.set(a); + return line.ptAtT(t); +} + +static SkDPoint dquad_xy_at_t(const SkPoint a[3], SkScalar , double t) { + SkDQuad quad; + quad.set(a); + return quad.ptAtT(t); +} + +static SkDPoint dconic_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { + SkDConic conic; + conic.set(a, weight); + return conic.ptAtT(t); +} + +static SkDPoint dcubic_xy_at_t(const SkPoint a[4], SkScalar , double t) { + SkDCubic cubic; + cubic.set(a); + return cubic.ptAtT(t); +} + +static SkDPoint (* const CurveDPointAtT[])(const SkPoint[], SkScalar , double ) = { + nullptr, + dline_xy_at_t, + dquad_xy_at_t, + dconic_xy_at_t, + dcubic_xy_at_t +}; + +static SkDPoint ddline_xy_at_t(const SkDCurve& c, double t) { + return c.fLine.ptAtT(t); +} + +static SkDPoint ddquad_xy_at_t(const SkDCurve& c, double t) { + return c.fQuad.ptAtT(t); +} + +static SkDPoint ddconic_xy_at_t(const SkDCurve& c, double t) { + return c.fConic.ptAtT(t); +} + +static SkDPoint ddcubic_xy_at_t(const SkDCurve& c, double t) { + return c.fCubic.ptAtT(t); +} + +static SkDPoint (* const CurveDDPointAtT[])(const SkDCurve& , double ) = { + nullptr, + ddline_xy_at_t, + ddquad_xy_at_t, + ddconic_xy_at_t, + ddcubic_xy_at_t +}; + +static SkPoint fline_xy_at_t(const SkPoint a[2], SkScalar weight, double t) { + return dline_xy_at_t(a, weight, t).asSkPoint(); +} + +static SkPoint fquad_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { + return dquad_xy_at_t(a, weight, t).asSkPoint(); +} + +static SkPoint fconic_xy_at_t(const SkPoint a[3], SkScalar weight, double t) { + return dconic_xy_at_t(a, weight, t).asSkPoint(); +} + +static SkPoint fcubic_xy_at_t(const SkPoint a[4], SkScalar weight, double t) { + return dcubic_xy_at_t(a, weight, t).asSkPoint(); +} + +static SkPoint (* const CurvePointAtT[])(const SkPoint[], SkScalar , double ) = { + nullptr, + fline_xy_at_t, + fquad_xy_at_t, + fconic_xy_at_t, + fcubic_xy_at_t +}; + +static SkDVector dline_dxdy_at_t(const SkPoint a[2], SkScalar , double ) { + SkDLine line; + line.set(a); + return line[1] - line[0]; +} + +static SkDVector dquad_dxdy_at_t(const SkPoint a[3], SkScalar , double t) { + SkDQuad quad; + quad.set(a); + return quad.dxdyAtT(t); +} + +static SkDVector dconic_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { + SkDConic conic; + conic.set(a, weight); + return conic.dxdyAtT(t); +} + +static SkDVector dcubic_dxdy_at_t(const SkPoint a[4], SkScalar , double t) { + SkDCubic cubic; + cubic.set(a); + return cubic.dxdyAtT(t); +} + +static SkDVector (* const CurveDSlopeAtT[])(const SkPoint[], SkScalar , double ) = { + nullptr, + dline_dxdy_at_t, + dquad_dxdy_at_t, + dconic_dxdy_at_t, + dcubic_dxdy_at_t +}; + +static SkDVector ddline_dxdy_at_t(const SkDCurve& c, double ) { + return c.fLine.fPts[1] - c.fLine.fPts[0]; +} + +static SkDVector ddquad_dxdy_at_t(const SkDCurve& c, double t) { + return c.fQuad.dxdyAtT(t); +} + +static SkDVector ddconic_dxdy_at_t(const SkDCurve& c, double t) { + return c.fConic.dxdyAtT(t); +} + +static SkDVector ddcubic_dxdy_at_t(const SkDCurve& c, double t) { + return c.fCubic.dxdyAtT(t); +} + +static SkDVector (* const CurveDDSlopeAtT[])(const SkDCurve& , double ) = { + nullptr, + ddline_dxdy_at_t, + ddquad_dxdy_at_t, + ddconic_dxdy_at_t, + ddcubic_dxdy_at_t +}; + +static SkVector fline_dxdy_at_t(const SkPoint a[2], SkScalar , double ) { + return a[1] - a[0]; +} + +static SkVector fquad_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { + return dquad_dxdy_at_t(a, weight, t).asSkVector(); +} + +static SkVector fconic_dxdy_at_t(const SkPoint a[3], SkScalar weight, double t) { + return dconic_dxdy_at_t(a, weight, t).asSkVector(); +} + +static SkVector fcubic_dxdy_at_t(const SkPoint a[4], SkScalar weight, double t) { + return dcubic_dxdy_at_t(a, weight, t).asSkVector(); +} + +static SkVector (* const CurveSlopeAtT[])(const SkPoint[], SkScalar , double ) = { + nullptr, + fline_dxdy_at_t, + fquad_dxdy_at_t, + fconic_dxdy_at_t, + fcubic_dxdy_at_t +}; + +static bool line_is_vertical(const SkPoint a[2], SkScalar , double startT, double endT) { + SkDLine line; + line.set(a); + SkDPoint dst[2] = { line.ptAtT(startT), line.ptAtT(endT) }; + return AlmostEqualUlps(dst[0].fX, dst[1].fX); +} + +static bool quad_is_vertical(const SkPoint a[3], SkScalar , double startT, double endT) { + SkDQuad quad; + quad.set(a); + SkDQuad dst = quad.subDivide(startT, endT); + return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX); +} + +static bool conic_is_vertical(const SkPoint a[3], SkScalar weight, double startT, double endT) { + SkDConic conic; + conic.set(a, weight); + SkDConic dst = conic.subDivide(startT, endT); + return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX); +} + +static bool cubic_is_vertical(const SkPoint a[4], SkScalar , double startT, double endT) { + SkDCubic cubic; + cubic.set(a); + SkDCubic dst = cubic.subDivide(startT, endT); + return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX) + && AlmostEqualUlps(dst[2].fX, dst[3].fX); +} + +static bool (* const CurveIsVertical[])(const SkPoint[], SkScalar , double , double) = { + nullptr, + line_is_vertical, + quad_is_vertical, + conic_is_vertical, + cubic_is_vertical +}; + +static void line_intersect_ray(const SkPoint a[2], SkScalar , const SkDLine& ray, + SkIntersections* i) { + SkDLine line; + line.set(a); + i->intersectRay(line, ray); +} + +static void quad_intersect_ray(const SkPoint a[3], SkScalar , const SkDLine& ray, + SkIntersections* i) { + SkDQuad quad; + quad.set(a); + i->intersectRay(quad, ray); +} + +static void conic_intersect_ray(const SkPoint a[3], SkScalar weight, const SkDLine& ray, + SkIntersections* i) { + SkDConic conic; + conic.set(a, weight); + i->intersectRay(conic, ray); +} + +static void cubic_intersect_ray(const SkPoint a[4], SkScalar , const SkDLine& ray, + SkIntersections* i) { + SkDCubic cubic; + cubic.set(a); + i->intersectRay(cubic, ray); +} + +static void (* const CurveIntersectRay[])(const SkPoint[] , SkScalar , const SkDLine& , + SkIntersections* ) = { + nullptr, + line_intersect_ray, + quad_intersect_ray, + conic_intersect_ray, + cubic_intersect_ray +}; + +static void dline_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fLine, ray); +} + +static void dquad_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fQuad, ray); +} + +static void dconic_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fConic, ray); +} + +static void dcubic_intersect_ray(const SkDCurve& c, const SkDLine& ray, SkIntersections* i) { + i->intersectRay(c.fCubic, ray); +} + +static void (* const CurveDIntersectRay[])(const SkDCurve& , const SkDLine& , SkIntersections* ) = { + nullptr, + dline_intersect_ray, + dquad_intersect_ray, + dconic_intersect_ray, + dcubic_intersect_ray +}; + +static int line_intercept_h(const SkPoint a[2], SkScalar , SkScalar y, double* roots) { + SkDLine line; + roots[0] = SkIntersections::HorizontalIntercept(line.set(a), y); + return between(0, roots[0], 1); +} + +static int line_intercept_v(const SkPoint a[2], SkScalar , SkScalar x, double* roots) { + SkDLine line; + roots[0] = SkIntersections::VerticalIntercept(line.set(a), x); + return between(0, roots[0], 1); +} + +static int quad_intercept_h(const SkPoint a[2], SkScalar , SkScalar y, double* roots) { + SkDQuad quad; + return SkIntersections::HorizontalIntercept(quad.set(a), y, roots); +} + +static int quad_intercept_v(const SkPoint a[2], SkScalar , SkScalar x, double* roots) { + SkDQuad quad; + return SkIntersections::VerticalIntercept(quad.set(a), x, roots); +} + +static int conic_intercept_h(const SkPoint a[2], SkScalar w, SkScalar y, double* roots) { + SkDConic conic; + return SkIntersections::HorizontalIntercept(conic.set(a, w), y, roots); +} + +static int conic_intercept_v(const SkPoint a[2], SkScalar w, SkScalar x, double* roots) { + SkDConic conic; + return SkIntersections::VerticalIntercept(conic.set(a, w), x, roots); +} + +static int cubic_intercept_h(const SkPoint a[3], SkScalar , SkScalar y, double* roots) { + SkDCubic cubic; + return cubic.set(a).horizontalIntersect(y, roots); +} + +static int cubic_intercept_v(const SkPoint a[3], SkScalar , SkScalar x, double* roots) { + SkDCubic cubic; + return cubic.set(a).verticalIntersect(x, roots); +} + +static int (* const CurveIntercept[])(const SkPoint[] , SkScalar , SkScalar , double* ) = { + nullptr, + nullptr, + line_intercept_h, + line_intercept_v, + quad_intercept_h, + quad_intercept_v, + conic_intercept_h, + conic_intercept_v, + cubic_intercept_h, + cubic_intercept_v, +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsDebug.cpp b/gfx/skia/skia/src/pathops/SkPathOpsDebug.cpp new file mode 100644 index 000000000..e744c7565 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsDebug.cpp @@ -0,0 +1,2913 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkMutex.h" +#include "SkOpCoincidence.h" +#include "SkOpContour.h" +#include "SkPath.h" +#include "SkPathOpsDebug.h" +#include "SkString.h" + +#undef FAIL_IF +#define FAIL_IF(cond, coin) \ + do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, coin); } while (false) + +#undef FAIL_WITH_NULL_IF +#define FAIL_WITH_NULL_IF(cond, span) \ + do { if (cond) log->record(SkPathOpsDebug::kFail_Glitch, span); } while (false) + +#undef RETURN_FALSE_IF +#define RETURN_FALSE_IF(cond, span) \ + do { if (cond) log->record(SkPathOpsDebug::kReturnFalse_Glitch, span); \ + } while (false) + +class SkCoincidentSpans; + +#if DEBUG_VALIDATE +extern bool FLAGS_runFail; +#endif + +#if DEBUG_SORT +int SkPathOpsDebug::gSortCountDefault = SK_MaxS32; +int SkPathOpsDebug::gSortCount; +#endif + +#if DEBUG_ACTIVE_OP +const char* SkPathOpsDebug::kPathOpStr[] = {"diff", "sect", "union", "xor"}; +#endif + +#if defined SK_DEBUG || !FORCE_RELEASE + +const char* SkPathOpsDebug::kLVerbStr[] = {"", "line", "quad", "cubic"}; + +int SkPathOpsDebug::gContourID = 0; +int SkPathOpsDebug::gSegmentID = 0; + +bool SkPathOpsDebug::ChaseContains(const SkTDArray<SkOpSpanBase* >& chaseArray, + const SkOpSpanBase* span) { + for (int index = 0; index < chaseArray.count(); ++index) { + const SkOpSpanBase* entry = chaseArray[index]; + if (entry == span) { + return true; + } + } + return false; +} +#endif + +#if DEBUG_COIN + +SkPathOpsDebug::CoinDict SkPathOpsDebug::gCoinSumChangedDict; +SkPathOpsDebug::CoinDict SkPathOpsDebug::gCoinSumVisitedDict; + +static const int kGlitchType_Count = SkPathOpsDebug::kUnalignedTail_Glitch + 1; + +struct SpanGlitch { + const SkOpSpanBase* fBase; + const SkOpSpanBase* fSuspect; + const SkOpSegment* fSegment; + const SkOpSegment* fOppSegment; + const SkOpPtT* fCoinSpan; + const SkOpPtT* fEndSpan; + const SkOpPtT* fOppSpan; + const SkOpPtT* fOppEndSpan; + double fStartT; + double fEndT; + double fOppStartT; + double fOppEndT; + SkPoint fPt; + SkPathOpsDebug::GlitchType fType; + + void dumpType() const; +}; + +struct SkPathOpsDebug::GlitchLog { + void init(const SkOpGlobalState* state) { + fGlobalState = state; + } + + SpanGlitch* recordCommon(GlitchType type) { + SpanGlitch* glitch = fGlitches.push(); + glitch->fBase = nullptr; + glitch->fSuspect = nullptr; + glitch->fSegment = nullptr; + glitch->fOppSegment = nullptr; + glitch->fCoinSpan = nullptr; + glitch->fEndSpan = nullptr; + glitch->fOppSpan = nullptr; + glitch->fOppEndSpan = nullptr; + glitch->fStartT = SK_ScalarNaN; + glitch->fEndT = SK_ScalarNaN; + glitch->fOppStartT = SK_ScalarNaN; + glitch->fOppEndT = SK_ScalarNaN; + glitch->fPt = { SK_ScalarNaN, SK_ScalarNaN }; + glitch->fType = type; + return glitch; + } + + void record(GlitchType type, const SkOpSpanBase* base, + const SkOpSpanBase* suspect = NULL) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fSuspect = suspect; + } + + void record(GlitchType type, const SkOpSpanBase* base, + const SkOpPtT* ptT) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fCoinSpan = ptT; + } + + void record(GlitchType type, const SkCoincidentSpans* coin, + const SkCoincidentSpans* opp = NULL) { + SpanGlitch* glitch = recordCommon(type); + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); + if (opp) { + glitch->fOppSpan = opp->coinPtTStart(); + glitch->fOppEndSpan = opp->coinPtTEnd(); + } + } + + void record(GlitchType type, const SkOpSpanBase* base, + const SkOpSegment* seg, double t, SkPoint pt) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fSegment = seg; + glitch->fStartT = t; + glitch->fPt = pt; + } + + void record(GlitchType type, const SkOpSpanBase* base, double t, + SkPoint pt) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fStartT = t; + glitch->fPt = pt; + } + + void record(GlitchType type, const SkCoincidentSpans* coin, + const SkOpPtT* coinSpan, const SkOpPtT* endSpan) { + SpanGlitch* glitch = recordCommon(type); + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); + glitch->fEndSpan = endSpan; + glitch->fOppSpan = coinSpan; + glitch->fOppEndSpan = endSpan; + } + + void record(GlitchType type, const SkCoincidentSpans* coin, + const SkOpSpanBase* base) { + SpanGlitch* glitch = recordCommon(type); + glitch->fBase = base; + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = coin->coinPtTEnd(); + } + + void record(GlitchType type, const SkOpPtT* ptTS, const SkOpPtT* ptTE, + const SkOpPtT* oPtTS, const SkOpPtT* oPtTE) { + SpanGlitch* glitch = recordCommon(type); + glitch->fCoinSpan = ptTS; + glitch->fEndSpan = ptTE; + glitch->fOppSpan = oPtTS; + glitch->fOppEndSpan = oPtTE; + } + + void record(GlitchType type, const SkOpSegment* seg, double startT, + double endT, const SkOpSegment* oppSeg, double oppStartT, double oppEndT) { + SpanGlitch* glitch = recordCommon(type); + glitch->fSegment = seg; + glitch->fStartT = startT; + glitch->fEndT = endT; + glitch->fOppSegment = oppSeg; + glitch->fOppStartT = oppStartT; + glitch->fOppEndT = oppEndT; + } + + void record(GlitchType type, const SkOpSegment* seg, + const SkOpSpan* span) { + SpanGlitch* glitch = recordCommon(type); + glitch->fSegment = seg; + glitch->fBase = span; + } + + void record(GlitchType type, double t, const SkOpSpanBase* span) { + SpanGlitch* glitch = recordCommon(type); + glitch->fStartT = t; + glitch->fBase = span; + } + + void record(GlitchType type, const SkOpSegment* seg) { + SpanGlitch* glitch = recordCommon(type); + glitch->fSegment = seg; + } + + void record(GlitchType type, const SkCoincidentSpans* coin, + const SkOpPtT* ptT) { + SpanGlitch* glitch = recordCommon(type); + glitch->fCoinSpan = coin->coinPtTStart(); + glitch->fEndSpan = ptT; + } + + SkTDArray<SpanGlitch> fGlitches; + const SkOpGlobalState* fGlobalState; +}; + + +void SkPathOpsDebug::CoinDict::add(const SkPathOpsDebug::CoinDict& dict) { + int count = dict.fDict.count(); + for (int index = 0; index < count; ++index) { + this->add(dict.fDict[index]); + } +} + +void SkPathOpsDebug::CoinDict::add(const CoinDictEntry& key) { + int count = fDict.count(); + for (int index = 0; index < count; ++index) { + CoinDictEntry* entry = &fDict[index]; + if (entry->fIteration == key.fIteration && entry->fLineNumber == key.fLineNumber) { + SkASSERT(!strcmp(entry->fFunctionName, key.fFunctionName)); + if (entry->fGlitchType == kUninitialized_Glitch) { + entry->fGlitchType = key.fGlitchType; + } + return; + } + } + *fDict.append() = key; +} + +#endif + +#if DEBUG_COIN +static void missing_coincidence(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { + const SkOpContour* contour = contourList; + // bool result = false; + do { + /* result |= */ contour->debugMissingCoincidence(glitches); + } while ((contour = contour->next())); + return; +} + +static void move_multiples(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { + const SkOpContour* contour = contourList; + do { + if (contour->debugMoveMultiples(glitches), false) { + return; + } + } while ((contour = contour->next())); + return; +} + +static void move_nearby(SkPathOpsDebug::GlitchLog* glitches, const SkOpContourHead* contourList) { + const SkOpContour* contour = contourList; + do { + contour->debugMoveNearby(glitches); + } while ((contour = contour->next())); +} + + +#endif + +#if DEBUG_COIN +void SkOpGlobalState::debugAddToCoinChangedDict() { + +#if DEBUG_COINCIDENCE + CheckHealth(contourList); +#endif + // see if next coincident operation makes a change; if so, record it + SkPathOpsDebug::GlitchLog glitches; + const char* funcName = fCoinDictEntry.fFunctionName; + if (!strcmp("calc_angles", funcName)) { + ; + } else if (!strcmp("missing_coincidence", funcName)) { + missing_coincidence(&glitches, fContourHead); + } else if (!strcmp("move_multiples", funcName)) { + move_multiples(&glitches, fContourHead); + } else if (!strcmp("move_nearby", funcName)) { + move_nearby(&glitches, fContourHead); + } else if (!strcmp("addExpanded", funcName)) { + fCoincidence->debugAddExpanded(&glitches); + } else if (!strcmp("addMissing", funcName)) { + bool added; + fCoincidence->debugAddMissing(&glitches, &added); + } else if (!strcmp("addEndMovedSpans", funcName)) { + fCoincidence->debugAddEndMovedSpans(&glitches); + } else if (!strcmp("correctEnds", funcName)) { + fCoincidence->debugCorrectEnds(&glitches); + } else if (!strcmp("expand", funcName)) { + fCoincidence->debugExpand(&glitches); + } else if (!strcmp("findOverlaps", funcName)) { + ; + } else if (!strcmp("mark", funcName)) { + fCoincidence->debugMark(&glitches); + } else if (!strcmp("apply", funcName)) { + ; + } else { + SkASSERT(0); // add missing case + } + if (glitches.fGlitches.count()) { + fCoinDictEntry.fGlitchType = glitches.fGlitches[0].fType; + } + fCoinChangedDict.add(fCoinDictEntry); +} +#endif + +void SkPathOpsDebug::ShowActiveSpans(SkOpContourHead* contourList) { +#if DEBUG_ACTIVE_SPANS + SkOpContour* contour = contourList; + do { + contour->debugShowActiveSpans(); + } while ((contour = contour->next())); +#endif +} + +#if DEBUG_COINCIDENCE || DEBUG_COIN +void SkPathOpsDebug::CheckHealth(SkOpContourHead* contourList) { +#if DEBUG_COINCIDENCE + contourList->globalState()->debugSetCheckHealth(true); +#endif +#if DEBUG_COIN + GlitchLog glitches; + const SkOpContour* contour = contourList; + const SkOpCoincidence* coincidence = contour->globalState()->coincidence(); + coincidence->debugCheckValid(&glitches); // don't call validate; spans may be inconsistent + do { + contour->debugCheckHealth(&glitches); + contour->debugMissingCoincidence(&glitches); + } while ((contour = contour->next())); + bool added; + coincidence->debugAddMissing(&glitches, &added); + coincidence->debugExpand(&glitches); + coincidence->debugAddExpanded(&glitches); + coincidence->debugMark(&glitches); + unsigned mask = 0; + for (int index = 0; index < glitches.fGlitches.count(); ++index) { + const SpanGlitch& glitch = glitches.fGlitches[index]; + mask |= 1 << glitch.fType; + } + for (int index = 0; index < kGlitchType_Count; ++index) { + SkDebugf(mask & (1 << index) ? "x" : "-"); + } + for (int index = 0; index < glitches.fGlitches.count(); ++index) { + const SpanGlitch& glitch = glitches.fGlitches[index]; + SkDebugf("%02d: ", index); + if (glitch.fBase) { + SkDebugf(" seg/base=%d/%d", glitch.fBase->segment()->debugID(), + glitch.fBase->debugID()); + } + if (glitch.fSuspect) { + SkDebugf(" seg/base=%d/%d", glitch.fSuspect->segment()->debugID(), + glitch.fSuspect->debugID()); + } + if (glitch.fSegment) { + SkDebugf(" segment=%d", glitch.fSegment->debugID()); + } + if (glitch.fCoinSpan) { + SkDebugf(" coinSeg/Span/PtT=%d/%d/%d", glitch.fCoinSpan->segment()->debugID(), + glitch.fCoinSpan->span()->debugID(), glitch.fCoinSpan->debugID()); + } + if (glitch.fEndSpan) { + SkDebugf(" endSpan=%d", glitch.fEndSpan->debugID()); + } + if (glitch.fOppSpan) { + SkDebugf(" oppSeg/Span/PtT=%d/%d/%d", glitch.fOppSpan->segment()->debugID(), + glitch.fOppSpan->span()->debugID(), glitch.fOppSpan->debugID()); + } + if (glitch.fOppEndSpan) { + SkDebugf(" oppEndSpan=%d", glitch.fOppEndSpan->debugID()); + } + if (!SkScalarIsNaN(glitch.fStartT)) { + SkDebugf(" startT=%g", glitch.fStartT); + } + if (!SkScalarIsNaN(glitch.fEndT)) { + SkDebugf(" endT=%g", glitch.fEndT); + } + if (glitch.fOppSegment) { + SkDebugf(" segment=%d", glitch.fOppSegment->debugID()); + } + if (!SkScalarIsNaN(glitch.fOppStartT)) { + SkDebugf(" oppStartT=%g", glitch.fOppStartT); + } + if (!SkScalarIsNaN(glitch.fOppEndT)) { + SkDebugf(" oppEndT=%g", glitch.fOppEndT); + } + if (!SkScalarIsNaN(glitch.fPt.fX) || !SkScalarIsNaN(glitch.fPt.fY)) { + SkDebugf(" pt=%g,%g", glitch.fPt.fX, glitch.fPt.fY); + } + DumpGlitchType(glitch.fType); + SkDebugf("\n"); + } +#if DEBUG_COINCIDENCE + contourList->globalState()->debugSetCheckHealth(false); +#endif +#if 01 && DEBUG_ACTIVE_SPANS +// SkDebugf("active after %s:\n", id); + ShowActiveSpans(contourList); +#endif +#endif +} +#endif + +#if DEBUG_COIN +void SkPathOpsDebug::DumpGlitchType(GlitchType glitchType) { + switch (glitchType) { + case kAddCorruptCoin_Glitch: SkDebugf(" AddCorruptCoin"); break; + case kAddExpandedCoin_Glitch: SkDebugf(" AddExpandedCoin"); break; + case kAddExpandedFail_Glitch: SkDebugf(" AddExpandedFail"); break; + case kAddIfCollapsed_Glitch: SkDebugf(" AddIfCollapsed"); break;; break; + case kAddIfMissingCoin_Glitch: SkDebugf(" AddIfMissingCoin"); break; + case kAddMissingCoin_Glitch: SkDebugf(" AddMissingCoin"); break; + case kAddMissingExtend_Glitch: SkDebugf(" AddMissingExtend"); break; + case kAddOrOverlap_Glitch: SkDebugf(" AAddOrOverlap"); break; + case kCollapsedCoin_Glitch: SkDebugf(" CollapsedCoin"); break; + case kCollapsedDone_Glitch: SkDebugf(" CollapsedDone"); break; + case kCollapsedOppValue_Glitch: SkDebugf(" CollapsedOppValue"); break; + case kCollapsedSpan_Glitch: SkDebugf(" CollapsedSpan"); break; + case kCollapsedWindValue_Glitch: SkDebugf(" CollapsedWindValue"); break; + case kCorrectEnd_Glitch: SkDebugf(" CorrectEnd"); break; + case kDeletedCoin_Glitch: SkDebugf(" DeletedCoin"); break; + case kExpandCoin_Glitch: SkDebugf(" ExpandCoin"); break; + case kFail_Glitch: SkDebugf(" Fail"); break; + case kMarkCoinEnd_Glitch: SkDebugf(" MarkCoinEnd"); break; + case kMarkCoinInsert_Glitch: SkDebugf(" MarkCoinInsert"); break; + case kMarkCoinMissing_Glitch: SkDebugf(" MarkCoinMissing"); break; + case kMarkCoinStart_Glitch: SkDebugf(" MarkCoinStart"); break; + case kMergeMatches_Glitch: SkDebugf(" MergeMatches"); break; + case kMissingCoin_Glitch: SkDebugf(" MissingCoin"); break; + case kMissingDone_Glitch: SkDebugf(" MissingDone"); break; + case kMissingIntersection_Glitch: SkDebugf(" MissingIntersection"); break; + case kMoveMultiple_Glitch: SkDebugf(" MoveMultiple"); break; + case kMoveNearbyClearAll_Glitch: SkDebugf(" MoveNearbyClearAll"); break; + case kMoveNearbyClearAll2_Glitch: SkDebugf(" MoveNearbyClearAll2"); break; + case kMoveNearbyMerge_Glitch: SkDebugf(" MoveNearbyMerge"); break; + case kMoveNearbyMergeFinal_Glitch: SkDebugf(" MoveNearbyMergeFinal"); break; + case kMoveNearbyRelease_Glitch: SkDebugf(" MoveNearbyRelease"); break; + case kMoveNearbyReleaseFinal_Glitch: SkDebugf(" MoveNearbyReleaseFinal"); break; + case kReleasedSpan_Glitch: SkDebugf(" ReleasedSpan"); break; + case kReturnFalse_Glitch: SkDebugf(" ReturnFalse"); break; + case kUnaligned_Glitch: SkDebugf(" Unaligned"); break; + case kUnalignedHead_Glitch: SkDebugf(" UnalignedHead"); break; + case kUnalignedTail_Glitch: SkDebugf(" UnalignedTail"); break; + case kUninitialized_Glitch: break; + default: SkASSERT(0); + } +} +#endif + +#if defined SK_DEBUG || !FORCE_RELEASE +void SkPathOpsDebug::MathematicaIze(char* str, size_t bufferLen) { + size_t len = strlen(str); + bool num = false; + for (size_t idx = 0; idx < len; ++idx) { + if (num && str[idx] == 'e') { + if (len + 2 >= bufferLen) { + return; + } + memmove(&str[idx + 2], &str[idx + 1], len - idx); + str[idx] = '*'; + str[idx + 1] = '^'; + ++len; + } + num = str[idx] >= '0' && str[idx] <= '9'; + } +} + +#if DEBUG_VALIDATE +void SkPathOpsDebug::SetPhase(SkOpContourHead* contourList, CoinID next, + int lineNumber, SkOpPhase phase) { + AddedCoin(contourList, next, 0, lineNumber); + contourList->globalState()->setPhase(phase); +} +#endif + +bool SkPathOpsDebug::ValidWind(int wind) { + return wind > SK_MinS32 + 0xFFFF && wind < SK_MaxS32 - 0xFFFF; +} + +void SkPathOpsDebug::WindingPrintf(int wind) { + if (wind == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", wind); + } +} +#endif // defined SK_DEBUG || !FORCE_RELEASE + + +#if DEBUG_SHOW_TEST_NAME +void* SkPathOpsDebug::CreateNameStr() { return new char[DEBUG_FILENAME_STRING_LENGTH]; } + +void SkPathOpsDebug::DeleteNameStr(void* v) { delete[] reinterpret_cast<char*>(v); } + +void SkPathOpsDebug::BumpTestName(char* test) { + char* num = test + strlen(test); + while (num[-1] >= '0' && num[-1] <= '9') { + --num; + } + if (num[0] == '\0') { + return; + } + int dec = atoi(num); + if (dec == 0) { + return; + } + ++dec; + SK_SNPRINTF(num, DEBUG_FILENAME_STRING_LENGTH - (num - test), "%d", dec); +} +#endif + +static void show_function_header(const char* functionName) { + SkDebugf("\nstatic void %s(skiatest::Reporter* reporter, const char* filename) {\n", functionName); + if (strcmp("skphealth_com76", functionName) == 0) { + SkDebugf("found it\n"); + } +} + +static const char* gOpStrs[] = { + "kDifference_SkPathOp", + "kIntersect_SkPathOp", + "kUnion_SkPathOp", + "kXOR_PathOp", + "kReverseDifference_SkPathOp", +}; + +const char* SkPathOpsDebug::OpStr(SkPathOp op) { + return gOpStrs[op]; +} + +static void show_op(SkPathOp op, const char* pathOne, const char* pathTwo) { + SkDebugf(" testPathOp(reporter, %s, %s, %s, filename);\n", pathOne, pathTwo, gOpStrs[op]); + SkDebugf("}\n"); +} + +SK_DECLARE_STATIC_MUTEX(gTestMutex); + +void SkPathOpsDebug::ShowPath(const SkPath& a, const SkPath& b, SkPathOp shapeOp, + const char* testName) { + SkAutoMutexAcquire ac(gTestMutex); + show_function_header(testName); + ShowOnePath(a, "path", true); + ShowOnePath(b, "pathB", true); + show_op(shapeOp, "path", "pathB"); +} + +#include "SkPathOpsTypes.h" +#include "SkIntersectionHelper.h" +#include "SkIntersections.h" + +#if DEBUG_COIN + +SK_DECLARE_STATIC_MUTEX(gCoinDictMutex); + +void SkOpGlobalState::debugAddToGlobalCoinDicts() { + SkAutoMutexAcquire ac(&gCoinDictMutex); + SkPathOpsDebug::gCoinSumChangedDict.add(fCoinChangedDict); + SkPathOpsDebug::gCoinSumVisitedDict.add(fCoinVisitedDict); +} + +#endif + +#if DEBUG_T_SECT_LOOP_COUNT +void SkOpGlobalState::debugAddLoopCount(SkIntersections* i, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn) { + for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { + SkIntersections::DebugLoop looper = (SkIntersections::DebugLoop) index; + if (fDebugLoopCount[index] >= i->debugLoopCount(looper)) { + continue; + } + fDebugLoopCount[index] = i->debugLoopCount(looper); + fDebugWorstVerb[index * 2] = wt.segment()->verb(); + fDebugWorstVerb[index * 2 + 1] = wn.segment()->verb(); + sk_bzero(&fDebugWorstPts[index * 8], sizeof(SkPoint) * 8); + memcpy(&fDebugWorstPts[index * 2 * 4], wt.pts(), + (SkPathOpsVerbToPoints(wt.segment()->verb()) + 1) * sizeof(SkPoint)); + memcpy(&fDebugWorstPts[(index * 2 + 1) * 4], wn.pts(), + (SkPathOpsVerbToPoints(wn.segment()->verb()) + 1) * sizeof(SkPoint)); + fDebugWorstWeight[index * 2] = wt.weight(); + fDebugWorstWeight[index * 2 + 1] = wn.weight(); + } + i->debugResetLoopCount(); +} + +void SkOpGlobalState::debugDoYourWorst(SkOpGlobalState* local) { + for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { + if (fDebugLoopCount[index] >= local->fDebugLoopCount[index]) { + continue; + } + fDebugLoopCount[index] = local->fDebugLoopCount[index]; + fDebugWorstVerb[index * 2] = local->fDebugWorstVerb[index * 2]; + fDebugWorstVerb[index * 2 + 1] = local->fDebugWorstVerb[index * 2 + 1]; + memcpy(&fDebugWorstPts[index * 2 * 4], &local->fDebugWorstPts[index * 2 * 4], + sizeof(SkPoint) * 8); + fDebugWorstWeight[index * 2] = local->fDebugWorstWeight[index * 2]; + fDebugWorstWeight[index * 2 + 1] = local->fDebugWorstWeight[index * 2 + 1]; + } + local->debugResetLoopCounts(); +} + +static void dump_curve(SkPath::Verb verb, const SkPoint& pts, float weight) { + if (!verb) { + return; + } + const char* verbs[] = { "", "line", "quad", "conic", "cubic" }; + SkDebugf("%s: {{", verbs[verb]); + int ptCount = SkPathOpsVerbToPoints(verb); + for (int index = 0; index <= ptCount; ++index) { + SkDPoint::Dump((&pts)[index]); + if (index < ptCount - 1) { + SkDebugf(", "); + } + } + SkDebugf("}"); + if (weight != 1) { + SkDebugf(", "); + if (weight == floorf(weight)) { + SkDebugf("%.0f", weight); + } else { + SkDebugf("%1.9gf", weight); + } + } + SkDebugf("}\n"); +} + +void SkOpGlobalState::debugLoopReport() { + const char* loops[] = { "iterations", "coinChecks", "perpCalcs" }; + SkDebugf("\n"); + for (int index = 0; index < (int) SK_ARRAY_COUNT(fDebugLoopCount); ++index) { + SkDebugf("%s: %d\n", loops[index], fDebugLoopCount[index]); + dump_curve(fDebugWorstVerb[index * 2], fDebugWorstPts[index * 2 * 4], + fDebugWorstWeight[index * 2]); + dump_curve(fDebugWorstVerb[index * 2 + 1], fDebugWorstPts[(index * 2 + 1) * 4], + fDebugWorstWeight[index * 2 + 1]); + } +} + +void SkOpGlobalState::debugResetLoopCounts() { + sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); + sk_bzero(fDebugWorstVerb, sizeof(fDebugWorstVerb)); + sk_bzero(fDebugWorstPts, sizeof(fDebugWorstPts)); + sk_bzero(fDebugWorstWeight, sizeof(fDebugWorstWeight)); +} +#endif + +#ifdef SK_DEBUG +bool SkOpGlobalState::debugRunFail() const { +#if DEBUG_VALIDATE + return FLAGS_runFail; +#else + return false; +#endif +} +#endif + +// this is const so it can be called by const methods that overwise don't alter state +#if DEBUG_VALIDATE || DEBUG_COIN +void SkOpGlobalState::debugSetPhase(const char* funcName DEBUG_COIN_DECLARE_PARAMS()) const { + auto writable = const_cast<SkOpGlobalState*>(this); +#if DEBUG_VALIDATE + writable->setPhase(phase); +#endif +#if DEBUG_COIN + SkPathOpsDebug::CoinDictEntry* entry = &writable->fCoinDictEntry; + writable->fPreviousFuncName = entry->fFunctionName; + entry->fIteration = iteration; + entry->fLineNumber = lineNo; + entry->fGlitchType = SkPathOpsDebug::kUninitialized_Glitch; + entry->fFunctionName = funcName; + writable->fCoinVisitedDict.add(*entry); + writable->debugAddToCoinChangedDict(); +#endif +} +#endif + +#if DEBUG_T_SECT_LOOP_COUNT +void SkIntersections::debugBumpLoopCount(DebugLoop index) { + fDebugLoopCount[index]++; +} + +int SkIntersections::debugLoopCount(DebugLoop index) const { + return fDebugLoopCount[index]; +} + +void SkIntersections::debugResetLoopCount() { + sk_bzero(fDebugLoopCount, sizeof(fDebugLoopCount)); +} +#endif + +#include "SkPathOpsCubic.h" +#include "SkPathOpsQuad.h" + +SkDCubic SkDQuad::debugToCubic() const { + SkDCubic cubic; + cubic[0] = fPts[0]; + cubic[2] = fPts[1]; + cubic[3] = fPts[2]; + cubic[1].fX = (cubic[0].fX + cubic[2].fX * 2) / 3; + cubic[1].fY = (cubic[0].fY + cubic[2].fY * 2) / 3; + cubic[2].fX = (cubic[3].fX + cubic[2].fX * 2) / 3; + cubic[2].fY = (cubic[3].fY + cubic[2].fY * 2) / 3; + return cubic; +} + +void SkDRect::debugInit() { + fLeft = fTop = fRight = fBottom = SK_ScalarNaN; +} + +#include "SkOpAngle.h" +#include "SkOpSegment.h" + +#if DEBUG_COIN +// commented-out lines keep this in sync with addT() + const SkOpPtT* SkOpSegment::debugAddT(double t, SkPathOpsDebug::GlitchLog* log) const { + debugValidate(); + SkPoint pt = this->ptAtT(t); + const SkOpSpanBase* span = &fHead; + do { + const SkOpPtT* result = span->ptT(); + if (t == result->fT || this->match(result, this, t, pt)) { +// span->bumpSpanAdds(); + return result; + } + if (t < result->fT) { + const SkOpSpan* prev = result->span()->prev(); + FAIL_WITH_NULL_IF(!prev, span); + // marks in global state that new op span has been allocated + this->globalState()->setAllocatedOpSpan(); +// 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 +// span->bumpSpanAdds(); + return nullptr; + } + FAIL_WITH_NULL_IF(span != &fTail, span); + } while ((span = span->upCast()->next())); + SkASSERT(0); + return nullptr; // we never get here, but need this to satisfy compiler +} +#endif + +#if DEBUG_ANGLE +void SkOpSegment::debugCheckAngleCoin() const { + const SkOpSpanBase* base = &fHead; + const SkOpSpan* span; + do { + const SkOpAngle* angle = base->fromAngle(); + if (angle && angle->debugCheckCoincidence()) { + angle->debugCheckNearCoincidence(); + } + if (base->final()) { + break; + } + span = base->upCast(); + angle = span->toAngle(); + if (angle && angle->debugCheckCoincidence()) { + angle->debugCheckNearCoincidence(); + } + } while ((base = span->next())); +} +#endif + +#if DEBUG_COIN +// this mimics the order of the checks in handle coincidence +void SkOpSegment::debugCheckHealth(SkPathOpsDebug::GlitchLog* glitches) const { + debugMoveMultiples(glitches); + debugMoveNearby(glitches); + debugMissingCoincidence(glitches); +} + +// commented-out lines keep this in sync with clearAll() +void SkOpSegment::debugClearAll(SkPathOpsDebug::GlitchLog* glitches) const { + const SkOpSpan* span = &fHead; + do { + this->debugClearOne(span, glitches); + } while ((span = span->next()->upCastable())); + this->globalState()->coincidence()->debugRelease(glitches, this); +} + +// commented-out lines keep this in sync with clearOne() +void SkOpSegment::debugClearOne(const SkOpSpan* span, SkPathOpsDebug::GlitchLog* glitches) const { + if (span->windValue()) glitches->record(SkPathOpsDebug::kCollapsedWindValue_Glitch, span); + if (span->oppValue()) glitches->record(SkPathOpsDebug::kCollapsedOppValue_Glitch, span); + if (!span->done()) glitches->record(SkPathOpsDebug::kCollapsedDone_Glitch, span); +} +#endif + +SkOpAngle* SkOpSegment::debugLastAngle() { + SkOpAngle* result = nullptr; + SkOpSpan* span = this->head(); + do { + if (span->toAngle()) { + SkASSERT(!result); + result = span->toAngle(); + } + } while ((span = span->next()->upCastable())); + SkASSERT(result); + return result; +} + +#if DEBUG_COIN +// commented-out lines keep this in sync with ClearVisited +void SkOpSegment::DebugClearVisited(const SkOpSpanBase* span) { + // reset visited flag back to false + do { + const SkOpPtT* ptT = span->ptT(), * stopPtT = ptT; + while ((ptT = ptT->next()) != stopPtT) { + const SkOpSegment* opp = ptT->segment(); + opp->resetDebugVisited(); + } + } while (!span->final() && (span = span->upCast()->next())); +} +#endif + +#if DEBUG_COIN +// commented-out lines keep this in sync with missingCoincidence() +// look for pairs of undetected coincident curves +// assumes that segments going in have visited flag clear +// Even though pairs of curves correct detect coincident runs, a run may be missed +// if the coincidence is a product of multiple intersections. For instance, given +// curves A, B, and C: +// A-B intersect at a point 1; A-C and B-C intersect at point 2, so near +// the end of C that the intersection is replaced with the end of C. +// Even though A-B correctly do not detect an intersection at point 2, +// the resulting run from point 1 to point 2 is coincident on A and B. +void SkOpSegment::debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const { + if (this->done()) { + return; + } + const SkOpSpan* prior = nullptr; + const SkOpSpanBase* spanBase = &fHead; +// bool result = false; + do { + const SkOpPtT* ptT = spanBase->ptT(), * spanStopPtT = ptT; + SkASSERT(ptT->span() == spanBase); + while ((ptT = ptT->next()) != spanStopPtT) { + if (ptT->deleted()) { + continue; + } + const SkOpSegment* opp = ptT->span()->segment(); + if (opp->done()) { + continue; + } + // when opp is encounted the 1st time, continue; on 2nd encounter, look for coincidence + if (!opp->debugVisited()) { + continue; + } + if (spanBase == &fHead) { + continue; + } + if (ptT->segment() == this) { + continue; + } + const SkOpSpan* span = spanBase->upCastable(); + // FIXME?: this assumes that if the opposite segment is coincident then no more + // coincidence needs to be detected. This may not be true. + if (span && span->segment() != opp && span->containsCoincidence(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted + continue; + } + if (spanBase->segment() != opp && spanBase->containsCoinEnd(opp)) { // debug has additional condition since it may be called before inner duplicate points have been deleted + continue; + } + const SkOpPtT* priorPtT = nullptr, * priorStopPtT; + // find prior span containing opp segment + const SkOpSegment* priorOpp = nullptr; + const SkOpSpan* priorTest = spanBase->prev(); + while (!priorOpp && priorTest) { + priorStopPtT = priorPtT = priorTest->ptT(); + while ((priorPtT = priorPtT->next()) != priorStopPtT) { + if (priorPtT->deleted()) { + continue; + } + const SkOpSegment* segment = priorPtT->span()->segment(); + if (segment == opp) { + prior = priorTest; + priorOpp = opp; + break; + } + } + priorTest = priorTest->prev(); + } + if (!priorOpp) { + continue; + } + if (priorPtT == ptT) { + continue; + } + const SkOpPtT* oppStart = prior->ptT(); + const SkOpPtT* oppEnd = spanBase->ptT(); + bool swapped = priorPtT->fT > ptT->fT; + if (swapped) { + SkTSwap(priorPtT, ptT); + SkTSwap(oppStart, oppEnd); + } + const SkOpCoincidence* coincidence = this->globalState()->coincidence(); + const SkOpPtT* rootPriorPtT = priorPtT->span()->ptT(); + const SkOpPtT* rootPtT = ptT->span()->ptT(); + const SkOpPtT* rootOppStart = oppStart->span()->ptT(); + const SkOpPtT* rootOppEnd = oppEnd->span()->ptT(); + if (coincidence->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd)) { + goto swapBack; + } + if (testForCoincidence(rootPriorPtT, rootPtT, prior, spanBase, opp)) { + // mark coincidence +#if DEBUG_COINCIDENCE_VERBOSE +// SkDebugf("%s coinSpan=%d endSpan=%d oppSpan=%d oppEndSpan=%d\n", __FUNCTION__, +// rootPriorPtT->debugID(), rootPtT->debugID(), rootOppStart->debugID(), +// rootOppEnd->debugID()); +#endif + log->record(SkPathOpsDebug::kMissingCoin_Glitch, priorPtT, ptT, oppStart, oppEnd); + // coincidences->add(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); + // } +#if DEBUG_COINCIDENCE +// SkASSERT(coincidences->contains(rootPriorPtT, rootPtT, rootOppStart, rootOppEnd); +#endif + // result = true; + } + swapBack: + if (swapped) { + SkTSwap(priorPtT, ptT); + } + } + } while ((spanBase = spanBase->final() ? nullptr : spanBase->upCast()->next())); + DebugClearVisited(&fHead); + return; +} + +// commented-out lines keep this in sync with moveMultiples() +// if a span has more than one intersection, merge the other segments' span as needed +void SkOpSegment::debugMoveMultiples(SkPathOpsDebug::GlitchLog* glitches) const { + debugValidate(); + const SkOpSpanBase* test = &fHead; + do { + int addCount = test->spanAddsCount(); + SkASSERT(addCount >= 1); + if (addCount == 1) { + continue; + } + const SkOpPtT* startPtT = test->ptT(); + const SkOpPtT* testPtT = startPtT; + do { // iterate through all spans associated with start + const SkOpSpanBase* oppSpan = testPtT->span(); + if (oppSpan->spanAddsCount() == addCount) { + continue; + } + if (oppSpan->deleted()) { + continue; + } + const SkOpSegment* oppSegment = oppSpan->segment(); + if (oppSegment == this) { + continue; + } + // find range of spans to consider merging + const SkOpSpanBase* oppPrev = oppSpan; + const SkOpSpanBase* oppFirst = oppSpan; + while ((oppPrev = oppPrev->prev())) { + if (!roughly_equal(oppPrev->t(), oppSpan->t())) { + break; + } + if (oppPrev->spanAddsCount() == addCount) { + continue; + } + if (oppPrev->deleted()) { + continue; + } + oppFirst = oppPrev; + } + const SkOpSpanBase* oppNext = oppSpan; + const SkOpSpanBase* oppLast = oppSpan; + while ((oppNext = oppNext->final() ? nullptr : oppNext->upCast()->next())) { + if (!roughly_equal(oppNext->t(), oppSpan->t())) { + break; + } + if (oppNext->spanAddsCount() == addCount) { + continue; + } + if (oppNext->deleted()) { + continue; + } + oppLast = oppNext; + } + if (oppFirst == oppLast) { + continue; + } + const SkOpSpanBase* oppTest = oppFirst; + do { + if (oppTest == oppSpan) { + continue; + } + // check to see if the candidate meets specific criteria: + // it contains spans of segments in test's loop but not including 'this' + const SkOpPtT* oppStartPtT = oppTest->ptT(); + const SkOpPtT* oppPtT = oppStartPtT; + while ((oppPtT = oppPtT->next()) != oppStartPtT) { + const SkOpSegment* oppPtTSegment = oppPtT->segment(); + if (oppPtTSegment == this) { + goto tryNextSpan; + } + const SkOpPtT* matchPtT = startPtT; + do { + if (matchPtT->segment() == oppPtTSegment) { + goto foundMatch; + } + } while ((matchPtT = matchPtT->next()) != startPtT); + goto tryNextSpan; + foundMatch: // merge oppTest and oppSpan + oppSegment->debugValidate(); + oppTest->debugMergeMatches(glitches, oppSpan); + oppTest->debugAddOpp(glitches, oppSpan); + oppSegment->debugValidate(); + goto checkNextSpan; + } + tryNextSpan: + ; + } while (oppTest != oppLast && (oppTest = oppTest->upCast()->next())); + } while ((testPtT = testPtT->next()) != startPtT); +checkNextSpan: + ; + } while ((test = test->final() ? nullptr : test->upCast()->next())); + debugValidate(); + return; +} + +// commented-out lines keep this in sync with moveNearby() +// Move nearby t values and pts so they all hang off the same span. Alignment happens later. +void SkOpSegment::debugMoveNearby(SkPathOpsDebug::GlitchLog* glitches) const { + debugValidate(); + // release undeleted spans pointing to this seg that are linked to the primary span + const SkOpSpanBase* spanBase = &fHead; + do { + const SkOpPtT* ptT = spanBase->ptT(); + const SkOpPtT* headPtT = ptT; + while ((ptT = ptT->next()) != headPtT) { + const SkOpSpanBase* test = ptT->span(); + if (ptT->segment() == this && !ptT->deleted() && test != spanBase + && test->ptT() == ptT) { + if (test->final()) { + if (spanBase == &fHead) { + glitches->record(SkPathOpsDebug::kMoveNearbyClearAll_Glitch, this); +// return; + } + glitches->record(SkPathOpsDebug::kMoveNearbyReleaseFinal_Glitch, spanBase, ptT); + } else if (test->prev()) { + glitches->record(SkPathOpsDebug::kMoveNearbyRelease_Glitch, test, headPtT); + } +// break; + } + } + spanBase = spanBase->upCast()->next(); + } while (!spanBase->final()); + + // This loop looks for adjacent spans which are near by + spanBase = &fHead; + do { // iterate through all spans associated with start + const SkOpSpanBase* test = spanBase->upCast()->next(); + if (this->spansNearby(spanBase, test)) { + if (test->final()) { + if (spanBase->prev()) { + glitches->record(SkPathOpsDebug::kMoveNearbyMergeFinal_Glitch, test); + } else { + glitches->record(SkPathOpsDebug::kMoveNearbyClearAll2_Glitch, this); + // return + } + } else { + glitches->record(SkPathOpsDebug::kMoveNearbyMerge_Glitch, spanBase); + } + } + spanBase = test; + } while (!spanBase->final()); + debugValidate(); +} +#endif + +void SkOpSegment::debugReset() { + this->init(this->fPts, this->fWeight, this->contour(), this->verb()); +} + +#if DEBUG_COINCIDENCE_ORDER +void SkOpSegment::debugSetCoinT(int index, SkScalar t) const { + if (fDebugBaseMax < 0 || fDebugBaseIndex == index) { + fDebugBaseIndex = index; + fDebugBaseMin = SkTMin(t, fDebugBaseMin); + fDebugBaseMax = SkTMax(t, fDebugBaseMax); + return; + } + SkASSERT(fDebugBaseMin >= t || t >= fDebugBaseMax); + if (fDebugLastMax < 0 || fDebugLastIndex == index) { + fDebugLastIndex = index; + fDebugLastMin = SkTMin(t, fDebugLastMin); + fDebugLastMax = SkTMax(t, fDebugLastMax); + return; + } + SkASSERT(fDebugLastMin >= t || t >= fDebugLastMax); + SkASSERT((t - fDebugBaseMin > 0) == (fDebugLastMin - fDebugBaseMin > 0)); +} +#endif + +#if DEBUG_ACTIVE_SPANS +void SkOpSegment::debugShowActiveSpans() const { + debugValidate(); + if (done()) { + return; + } + int lastId = -1; + double lastT = -1; + const SkOpSpan* span = &fHead; + do { + if (span->done()) { + continue; + } + if (lastId == this->debugID() && lastT == span->t()) { + continue; + } + lastId = this->debugID(); + lastT = span->t(); + SkDebugf("%s id=%d", __FUNCTION__, this->debugID()); + // since endpoints may have be adjusted, show actual computed curves + SkDCurve curvePart; + this->subDivide(span, span->next(), &curvePart); + const SkDPoint* pts = curvePart.fCubic.fPts; + SkDebugf(" (%1.9g,%1.9g", pts[0].fX, pts[0].fY); + for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { + SkDebugf(" %1.9g,%1.9g", pts[vIndex].fX, pts[vIndex].fY); + } + if (SkPath::kConic_Verb == fVerb) { + SkDebugf(" %1.9gf", curvePart.fConic.fWeight); + } + SkDebugf(") t=%1.9g tEnd=%1.9g", span->t(), span->next()->t()); + if (span->windSum() == SK_MinS32) { + SkDebugf(" windSum=?"); + } else { + SkDebugf(" windSum=%d", span->windSum()); + } + if (span->oppValue() && span->oppSum() == SK_MinS32) { + SkDebugf(" oppSum=?"); + } else if (span->oppValue() || span->oppSum() != SK_MinS32) { + SkDebugf(" oppSum=%d", span->oppSum()); + } + SkDebugf(" windValue=%d", span->windValue()); + if (span->oppValue() || span->oppSum() != SK_MinS32) { + SkDebugf(" oppValue=%d", span->oppValue()); + } + SkDebugf("\n"); + } while ((span = span->next()->upCastable())); +} +#endif + +#if DEBUG_MARK_DONE +void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding) { + const SkPoint& pt = span->ptT()->fPt; + SkDebugf("%s id=%d", fun, this->debugID()); + SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); + for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { + SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); + } + SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=", + span->t(), span->debugID(), pt.fX, pt.fY, span->next()->t()); + if (winding == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", winding); + } + SkDebugf(" windSum="); + if (span->windSum() == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span->windSum()); + } + SkDebugf(" windValue=%d\n", span->windValue()); +} + +void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan* span, int winding, + int oppWinding) { + const SkPoint& pt = span->ptT()->fPt; + SkDebugf("%s id=%d", fun, this->debugID()); + SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); + for (int vIndex = 1; vIndex <= SkPathOpsVerbToPoints(fVerb); ++vIndex) { + SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); + } + SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=", + span->t(), span->debugID(), pt.fX, pt.fY, span->next()->t(), winding, oppWinding); + if (winding == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", winding); + } + SkDebugf(" newOppSum="); + if (oppWinding == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", oppWinding); + } + SkDebugf(" oppSum="); + if (span->oppSum() == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span->oppSum()); + } + SkDebugf(" windSum="); + if (span->windSum() == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span->windSum()); + } + SkDebugf(" windValue=%d oppValue=%d\n", span->windValue(), span->oppValue()); +} + +#endif + +// loop looking for a pair of angle parts that are too close to be sorted +/* This is called after other more simple intersection and angle sorting tests have been exhausted. + This should be rarely called -- the test below is thorough and time consuming. + This checks the distance between start points; the distance between +*/ +#if DEBUG_ANGLE +void SkOpAngle::debugCheckNearCoincidence() const { + const SkOpAngle* test = this; + do { + const SkOpSegment* testSegment = test->segment(); + double testStartT = test->start()->t(); + SkDPoint testStartPt = testSegment->dPtAtT(testStartT); + double testEndT = test->end()->t(); + SkDPoint testEndPt = testSegment->dPtAtT(testEndT); + double testLenSq = testStartPt.distanceSquared(testEndPt); + SkDebugf("%s testLenSq=%1.9g id=%d\n", __FUNCTION__, testLenSq, testSegment->debugID()); + double testMidT = (testStartT + testEndT) / 2; + const SkOpAngle* next = test; + while ((next = next->fNext) != this) { + SkOpSegment* nextSegment = next->segment(); + double testMidDistSq = testSegment->distSq(testMidT, next); + double testEndDistSq = testSegment->distSq(testEndT, next); + double nextStartT = next->start()->t(); + SkDPoint nextStartPt = nextSegment->dPtAtT(nextStartT); + double distSq = testStartPt.distanceSquared(nextStartPt); + double nextEndT = next->end()->t(); + double nextMidT = (nextStartT + nextEndT) / 2; + double nextMidDistSq = nextSegment->distSq(nextMidT, test); + double nextEndDistSq = nextSegment->distSq(nextEndT, test); + SkDebugf("%s distSq=%1.9g testId=%d nextId=%d\n", __FUNCTION__, distSq, + testSegment->debugID(), nextSegment->debugID()); + SkDebugf("%s testMidDistSq=%1.9g\n", __FUNCTION__, testMidDistSq); + SkDebugf("%s testEndDistSq=%1.9g\n", __FUNCTION__, testEndDistSq); + SkDebugf("%s nextMidDistSq=%1.9g\n", __FUNCTION__, nextMidDistSq); + SkDebugf("%s nextEndDistSq=%1.9g\n", __FUNCTION__, nextEndDistSq); + SkDPoint nextEndPt = nextSegment->dPtAtT(nextEndT); + double nextLenSq = nextStartPt.distanceSquared(nextEndPt); + SkDebugf("%s nextLenSq=%1.9g\n", __FUNCTION__, nextLenSq); + SkDebugf("\n"); + } + test = test->fNext; + } while (test->fNext != this); +} +#endif + +#if DEBUG_ANGLE +SkString SkOpAngle::debugPart() const { + SkString result; + switch (this->segment()->verb()) { + case SkPath::kLine_Verb: + result.printf(LINE_DEBUG_STR " id=%d", LINE_DEBUG_DATA(fPart.fCurve), + this->segment()->debugID()); + break; + case SkPath::kQuad_Verb: + result.printf(QUAD_DEBUG_STR " id=%d", QUAD_DEBUG_DATA(fPart.fCurve), + this->segment()->debugID()); + break; + case SkPath::kConic_Verb: + result.printf(CONIC_DEBUG_STR " id=%d", + CONIC_DEBUG_DATA(fPart.fCurve, fPart.fCurve.fConic.fWeight), + this->segment()->debugID()); + break; + case SkPath::kCubic_Verb: + result.printf(CUBIC_DEBUG_STR " id=%d", CUBIC_DEBUG_DATA(fPart.fCurve), + this->segment()->debugID()); + break; + default: + SkASSERT(0); + } + return result; +} +#endif + +#if DEBUG_SORT +void SkOpAngle::debugLoop() const { + const SkOpAngle* first = this; + const SkOpAngle* next = this; + do { + next->dumpOne(true); + SkDebugf("\n"); + next = next->fNext; + } while (next && next != first); + next = first; + do { + next->debugValidate(); + next = next->fNext; + } while (next && next != first); +} +#endif + +void SkOpAngle::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif +#if DEBUG_VALIDATE + const SkOpAngle* first = this; + const SkOpAngle* next = this; + int wind = 0; + int opp = 0; + int lastXor = -1; + int lastOppXor = -1; + do { + if (next->unorderable()) { + return; + } + const SkOpSpan* minSpan = next->start()->starter(next->end()); + if (minSpan->windValue() == SK_MinS32) { + return; + } + bool op = next->segment()->operand(); + bool isXor = next->segment()->isXor(); + bool oppXor = next->segment()->oppXor(); + SkASSERT(!DEBUG_LIMIT_WIND_SUM || between(0, minSpan->windValue(), DEBUG_LIMIT_WIND_SUM)); + SkASSERT(!DEBUG_LIMIT_WIND_SUM + || between(-DEBUG_LIMIT_WIND_SUM, minSpan->oppValue(), DEBUG_LIMIT_WIND_SUM)); + bool useXor = op ? oppXor : isXor; + SkASSERT(lastXor == -1 || lastXor == (int) useXor); + lastXor = (int) useXor; + wind += next->debugSign() * (op ? minSpan->oppValue() : minSpan->windValue()); + if (useXor) { + wind &= 1; + } + useXor = op ? isXor : oppXor; + SkASSERT(lastOppXor == -1 || lastOppXor == (int) useXor); + lastOppXor = (int) useXor; + opp += next->debugSign() * (op ? minSpan->windValue() : minSpan->oppValue()); + if (useXor) { + opp &= 1; + } + next = next->fNext; + } while (next && next != first); + SkASSERT(wind == 0 || !FLAGS_runFail); + SkASSERT(opp == 0 || !FLAGS_runFail); +#endif +} + +void SkOpAngle::debugValidateNext() const { +#if !FORCE_RELEASE + const SkOpAngle* first = this; + const SkOpAngle* next = first; + SkTDArray<const SkOpAngle*>(angles); + do { +// SkASSERT_RELEASE(next->fSegment->debugContains(next)); + angles.push(next); + next = next->next(); + if (next == first) { + break; + } + SkASSERT_RELEASE(!angles.contains(next)); + if (!next) { + return; + } + } while (true); +#endif +} + +#ifdef SK_DEBUG +void SkCoincidentSpans::debugStartCheck(const SkOpSpanBase* outer, const SkOpSpanBase* over, + const SkOpGlobalState* debugState) const { + SkASSERT(coinPtTEnd()->span() == over || !debugState->debugRunFail()); + SkASSERT(oppPtTEnd()->span() == outer || !debugState->debugRunFail()); +} +#endif + +#if DEBUG_COIN +// sets the span's end to the ptT referenced by the previous-next +void SkCoincidentSpans::debugCorrectOneEnd(SkPathOpsDebug::GlitchLog* log, + const SkOpPtT* (SkCoincidentSpans::* getEnd)() const, + void (SkCoincidentSpans::*setEnd)(const SkOpPtT* ptT) const ) const { + const SkOpPtT* origPtT = (this->*getEnd)(); + const SkOpSpanBase* origSpan = origPtT->span(); + const SkOpSpan* prev = origSpan->prev(); + const SkOpPtT* testPtT = prev ? prev->next()->ptT() + : origSpan->upCast()->next()->prev()->ptT(); + if (origPtT != testPtT) { + log->record(SkPathOpsDebug::kCorrectEnd_Glitch, this, origPtT, testPtT); + } +} + + +/* Commented-out lines keep this in sync with correctEnds */ +// FIXME: member pointers have fallen out of favor and can be replaced with +// an alternative approach. +// makes all span ends agree with the segment's spans that define them +void SkCoincidentSpans::debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const { + this->debugCorrectOneEnd(log, &SkCoincidentSpans::coinPtTStart, nullptr); + this->debugCorrectOneEnd(log, &SkCoincidentSpans::coinPtTEnd, nullptr); + this->debugCorrectOneEnd(log, &SkCoincidentSpans::oppPtTStart, nullptr); + this->debugCorrectOneEnd(log, &SkCoincidentSpans::oppPtTEnd, nullptr); +} + +/* Commented-out lines keep this in sync with expand */ +// expand the range by checking adjacent spans for coincidence +bool SkCoincidentSpans::debugExpand(SkPathOpsDebug::GlitchLog* log) const { + bool expanded = false; + const SkOpSegment* segment = coinPtTStart()->segment(); + const SkOpSegment* oppSegment = oppPtTStart()->segment(); + do { + const SkOpSpan* start = coinPtTStart()->span()->upCast(); + const SkOpSpan* prev = start->prev(); + const SkOpPtT* oppPtT; + if (!prev || !(oppPtT = prev->contains(oppSegment))) { + break; + } + double midT = (prev->t() + start->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, this, prev->ptT(), oppPtT); + expanded = true; + } while (false); // actual continues while expansion is possible + do { + const SkOpSpanBase* end = coinPtTEnd()->span(); + SkOpSpanBase* next = end->final() ? nullptr : end->upCast()->next(); + if (next && next->deleted()) { + break; + } + const SkOpPtT* oppPtT; + if (!next || !(oppPtT = next->contains(oppSegment))) { + break; + } + double midT = (end->t() + next->t()) / 2; + if (!segment->isClose(midT, oppSegment)) { + break; + } + if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, this, next->ptT(), oppPtT); + expanded = true; + } while (false); // actual continues while expansion is possible + return expanded; +} + +// description below +void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log, const SkOpSpan* base, const SkOpSpanBase* testSpan) const { + const SkOpPtT* testPtT = testSpan->ptT(); + const SkOpPtT* stopPtT = testPtT; + const SkOpSegment* baseSeg = base->segment(); + while ((testPtT = testPtT->next()) != stopPtT) { + const SkOpSegment* testSeg = testPtT->segment(); + if (testPtT->deleted()) { + continue; + } + if (testSeg == baseSeg) { + continue; + } + if (testPtT->span()->ptT() != testPtT) { + continue; + } + if (this->contains(baseSeg, testSeg, testPtT->fT)) { + continue; + } + // intersect perp with base->ptT() with testPtT->segment() + SkDVector dxdy = baseSeg->dSlopeAtT(base->t()); + const SkPoint& pt = base->pt(); + SkDLine ray = {{{pt.fX, pt.fY}, {pt.fX + dxdy.fY, pt.fY - dxdy.fX}}}; + SkIntersections i; + (*CurveIntersectRay[testSeg->verb()])(testSeg->pts(), testSeg->weight(), ray, &i); + for (int index = 0; index < i.used(); ++index) { + double t = i[0][index]; + if (!between(0, t, 1)) { + continue; + } + SkDPoint oppPt = i.pt(index); + if (!oppPt.approximatelyEqual(pt)) { + continue; + } + SkOpSegment* writableSeg = const_cast<SkOpSegment*>(testSeg); + SkOpPtT* oppStart = writableSeg->addT(t); + if (oppStart == testPtT) { + continue; + } + SkOpSpan* writableBase = const_cast<SkOpSpan*>(base); + oppStart->span()->addOpp(writableBase); + if (oppStart->deleted()) { + continue; + } + SkOpSegment* coinSeg = base->segment(); + SkOpSegment* oppSeg = oppStart->segment(); + double coinTs, coinTe, oppTs, oppTe; + if (Ordered(coinSeg, oppSeg)) { + coinTs = base->t(); + coinTe = testSpan->t(); + oppTs = oppStart->fT; + oppTe = testPtT->fT; + } else { + SkTSwap(coinSeg, oppSeg); + coinTs = oppStart->fT; + coinTe = testPtT->fT; + oppTs = base->t(); + oppTe = testSpan->t(); + } + if (coinTs > coinTe) { + SkTSwap(coinTs, coinTe); + SkTSwap(oppTs, oppTe); + } + bool added; + if (this->debugAddOrOverlap(log, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, &added), false) { + return; + } + } + } + return; +} + +// description below +void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* ptT) const { + FAIL_IF(!ptT->span()->upCastable(), ptT->span()); + const SkOpSpan* base = ptT->span()->upCast(); + const SkOpSpan* prev = base->prev(); + FAIL_IF(!prev, ptT->span()); + if (!prev->isCanceled()) { + if (this->debugAddEndMovedSpans(log, base, base->prev()), false) { + return; + } + } + if (!base->isCanceled()) { + if (this->debugAddEndMovedSpans(log, base, base->next()), false) { + return; + } + } + return; +} + +/* If A is coincident with B and B includes an endpoint, and A's matching point + is not the endpoint (i.e., there's an implied line connecting B-end and A) + then assume that the same implied line may intersect another curve close to B. + Since we only care about coincidence that was undetected, look at the + ptT list on B-segment adjacent to the B-end/A ptT loop (not in the loop, but + next door) and see if the A matching point is close enough to form another + coincident pair. If so, check for a new coincident span between B-end/A ptT loop + and the adjacent ptT loop. +*/ +void SkOpCoincidence::debugAddEndMovedSpans(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* span = fHead; + if (!span) { + return; + } +// fTop = span; +// fHead = nullptr; + do { + if (span->coinPtTStart()->fPt != span->oppPtTStart()->fPt) { + FAIL_IF(1 == span->coinPtTStart()->fT, span); + bool onEnd = span->coinPtTStart()->fT == 0; + bool oOnEnd = zero_or_one(span->oppPtTStart()->fT); + if (onEnd) { + if (!oOnEnd) { // if both are on end, any nearby intersect was already found + if (this->debugAddEndMovedSpans(log, span->oppPtTStart()), false) { + return; + } + } + } else if (oOnEnd) { + if (this->debugAddEndMovedSpans(log, span->coinPtTStart()), false) { + return; + } + } + } + if (span->coinPtTEnd()->fPt != span->oppPtTEnd()->fPt) { + bool onEnd = span->coinPtTEnd()->fT == 1; + bool oOnEnd = zero_or_one(span->oppPtTEnd()->fT); + if (onEnd) { + if (!oOnEnd) { + if (this->debugAddEndMovedSpans(log, span->oppPtTEnd()), false) { + return; + } + } + } else if (oOnEnd) { + if (this->debugAddEndMovedSpans(log, span->coinPtTEnd()), false) { + return; + } + } + } + } while ((span = span->next())); +// this->restoreHead(); + return; +} + +/* Commented-out lines keep this in sync with addExpanded */ +// for each coincident pair, match the spans +// if the spans don't match, add the mssing pt to the segment and loop it in the opposite span +void SkOpCoincidence::debugAddExpanded(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* coin = this->fHead; + if (!coin) { + return; + } + do { + const SkOpPtT* startPtT = coin->coinPtTStart(); + const SkOpPtT* oStartPtT = coin->oppPtTStart(); + double priorT = startPtT->fT; + double oPriorT = oStartPtT->fT; + FAIL_IF(startPtT->contains(oStartPtT), coin); + SkOPASSERT(coin->coinPtTEnd()->contains(coin->oppPtTEnd())); + const SkOpSpanBase* start = startPtT->span(); + const SkOpSpanBase* oStart = oStartPtT->span(); + const SkOpSpanBase* end = coin->coinPtTEnd()->span(); + const SkOpSpanBase* oEnd = coin->oppPtTEnd()->span(); + FAIL_IF(oEnd->deleted(), coin); + FAIL_IF(!start->upCastable(), coin); + const SkOpSpanBase* test = start->upCast()->next(); + FAIL_IF(!coin->flipped() && !oStart->upCastable(), coin); + const SkOpSpanBase* oTest = coin->flipped() ? oStart->prev() : oStart->upCast()->next(); + FAIL_IF(!oTest, coin); + const SkOpSegment* seg = start->segment(); + const SkOpSegment* oSeg = oStart->segment(); + while (test != end || oTest != oEnd) { + const SkOpPtT* containedOpp = test->ptT()->contains(oSeg); + const SkOpPtT* containedThis = oTest->ptT()->contains(seg); + if (!containedOpp || !containedThis) { + // choose the ends, or the first common pt-t list shared by both + double nextT, oNextT; + if (containedOpp) { + nextT = test->t(); + oNextT = containedOpp->fT; + } else if (containedThis) { + nextT = containedThis->fT; + oNextT = oTest->t(); + } else { + // iterate through until a pt-t list found that contains the other + const SkOpSpanBase* walk = test; + const SkOpPtT* walkOpp; + do { + FAIL_IF(!walk->upCastable(), coin); + walk = walk->upCast()->next(); + } while (!(walkOpp = walk->ptT()->contains(oSeg)) + && walk != coin->coinPtTEnd()->span()); + nextT = walk->t(); + oNextT = walkOpp->fT; + } + // use t ranges to guess which one is missing + double startRange = coin->coinPtTEnd()->fT - startPtT->fT; + FAIL_IF(!startRange, coin); + double startPart = (test->t() - startPtT->fT) / startRange; + double oStartRange = coin->oppPtTEnd()->fT - oStartPtT->fT; + FAIL_IF(!oStartRange, coin); + double oStartPart = (oTest->t() - oStartPtT->fT) / oStartRange; + FAIL_IF(startPart == oStartPart, coin); + bool addToOpp = !containedOpp && !containedThis ? startPart < oStartPart + : !!containedThis; + bool startOver = false; + addToOpp ? log->record(SkPathOpsDebug::kAddExpandedCoin_Glitch, + oPriorT + oStartRange * startPart, test) + : log->record(SkPathOpsDebug::kAddExpandedCoin_Glitch, + priorT + startRange * oStartPart, oTest); + // FAIL_IF(!success, coin); + if (startOver) { + test = start; + oTest = oStart; + } + end = coin->coinPtTEnd()->span(); + oEnd = coin->oppPtTEnd()->span(); + } + if (test != end) { + FAIL_IF(!test->upCastable(), coin); + priorT = test->t(); + test = test->upCast()->next(); + } + if (oTest != oEnd) { + oPriorT = oTest->t(); + oTest = coin->flipped() ? oTest->prev() : oTest->upCast()->next(); + FAIL_IF(!oTest, coin); + } + } + } while ((coin = coin->next())); + return; +} + +/* Commented-out lines keep this in sync with addIfMissing() */ +void SkOpCoincidence::debugAddIfMissing(SkPathOpsDebug::GlitchLog* log, const SkCoincidentSpans* outer, const SkOpPtT* over1s, + const SkOpPtT* over1e) const { +// SkASSERT(fTop); + if (fTop && alreadyAdded(fTop, outer, over1s, over1e)) { // in debug, fTop may be null + return; + } + if (fHead && alreadyAdded(fHead, outer, over1s, over1e)) { + return; + } + log->record(SkPathOpsDebug::kAddIfMissingCoin_Glitch, outer->coinPtTStart(), outer->coinPtTEnd(), over1s, over1e); + this->debugValidate(); + return; +} + +/* Commented-out lines keep this in sync addIfMissing() */ +// note that over1s, over1e, over2s, over2e are ordered +void SkOpCoincidence::debugAddIfMissing(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* over1s, const SkOpPtT* over2s, + double tStart, double tEnd, const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, bool* added, + const SkOpPtT* over1e, const SkOpPtT* over2e) const { + SkASSERT(tStart < tEnd); + SkASSERT(over1s->fT < over1e->fT); + SkASSERT(between(over1s->fT, tStart, over1e->fT)); + SkASSERT(between(over1s->fT, tEnd, over1e->fT)); + SkASSERT(over2s->fT < over2e->fT); + SkASSERT(between(over2s->fT, tStart, over2e->fT)); + SkASSERT(between(over2s->fT, tEnd, over2e->fT)); + SkASSERT(over1s->segment() == over1e->segment()); + SkASSERT(over2s->segment() == over2e->segment()); + SkASSERT(over1s->segment() == over2s->segment()); + SkASSERT(over1s->segment() != coinSeg); + SkASSERT(over1s->segment() != oppSeg); + SkASSERT(coinSeg != oppSeg); + double coinTs, coinTe, oppTs, oppTe; + coinTs = TRange(over1s, tStart, coinSeg SkDEBUGPARAMS(over1e)); + coinTe = TRange(over1s, tEnd, coinSeg SkDEBUGPARAMS(over1e)); + if (coinSeg->collapsed(coinTs, coinTe)) { + return log->record(SkPathOpsDebug::kAddIfCollapsed_Glitch, coinSeg); + } + oppTs = TRange(over2s, tStart, oppSeg SkDEBUGPARAMS(over2e)); + oppTe = TRange(over2s, tEnd, oppSeg SkDEBUGPARAMS(over2e)); + if (oppSeg->collapsed(oppTs, oppTe)) { + return log->record(SkPathOpsDebug::kAddIfCollapsed_Glitch, oppSeg); + } + if (coinTs > coinTe) { + SkTSwap(coinTs, coinTe); + SkTSwap(oppTs, oppTe); + } + return this->debugAddOrOverlap(log, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, added + ); +} + +/* Commented-out lines keep this in sync addOrOverlap() */ +// If this is called by addEndMovedSpans(), a returned false propogates out to an abort. +// If this is called by AddIfMissing(), a returned false indicates there was nothing to add +void SkOpCoincidence::debugAddOrOverlap(SkPathOpsDebug::GlitchLog* log, + const SkOpSegment* coinSeg, const SkOpSegment* oppSeg, + double coinTs, double coinTe, double oppTs, double oppTe, bool* added) const { + SkTDArray<SkCoincidentSpans*> overlaps; + SkOPASSERT(!fTop); // this is (correctly) reversed in addifMissing() + if (fTop && !this->checkOverlap(fTop, coinSeg, oppSeg, coinTs, coinTe, oppTs, oppTe, + &overlaps)) { + return; + } + if (fHead && !this->checkOverlap(fHead, coinSeg, oppSeg, coinTs, + coinTe, oppTs, oppTe, &overlaps)) { + return; + } + const SkCoincidentSpans* overlap = overlaps.count() ? overlaps[0] : nullptr; + for (int index = 1; index < overlaps.count(); ++index) { // combine overlaps before continuing + const SkCoincidentSpans* test = overlaps[index]; + if (overlap->coinPtTStart()->fT > test->coinPtTStart()->fT) { + log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->coinPtTStart()); + } + if (overlap->coinPtTEnd()->fT < test->coinPtTEnd()->fT) { + log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->coinPtTEnd()); + } + if (overlap->flipped() + ? overlap->oppPtTStart()->fT < test->oppPtTStart()->fT + : overlap->oppPtTStart()->fT > test->oppPtTStart()->fT) { + log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->oppPtTStart()); + } + if (overlap->flipped() + ? overlap->oppPtTEnd()->fT > test->oppPtTEnd()->fT + : overlap->oppPtTEnd()->fT < test->oppPtTEnd()->fT) { + log->record(SkPathOpsDebug::kAddOrOverlap_Glitch, overlap, test->oppPtTEnd()); + } + if (!fHead) { this->debugRelease(log, fHead, test); + this->debugRelease(log, fTop, test); + } + } + const SkOpPtT* cs = coinSeg->existing(coinTs, oppSeg); + const SkOpPtT* ce = coinSeg->existing(coinTe, oppSeg); + RETURN_FALSE_IF(overlap && cs && ce && overlap->contains(cs, ce), coinSeg); + RETURN_FALSE_IF(cs != ce || !cs, coinSeg); + const SkOpPtT* os = oppSeg->existing(oppTs, coinSeg); + const SkOpPtT* oe = oppSeg->existing(oppTe, coinSeg); + RETURN_FALSE_IF(overlap && os && oe && overlap->contains(os, oe), oppSeg); + SkASSERT(true || !cs || !cs->deleted()); + SkASSERT(true || !os || !os->deleted()); + SkASSERT(true || !ce || !ce->deleted()); + SkASSERT(true || !oe || !oe->deleted()); + const SkOpPtT* csExisting = !cs ? coinSeg->existing(coinTs, nullptr) : nullptr; + const SkOpPtT* ceExisting = !ce ? coinSeg->existing(coinTe, nullptr) : nullptr; + RETURN_FALSE_IF(csExisting && csExisting == ceExisting, coinSeg); + RETURN_FALSE_IF(csExisting && (csExisting == ce || + csExisting->contains(ceExisting ? ceExisting : ce)), coinSeg); + RETURN_FALSE_IF(ceExisting && (ceExisting == cs || + ceExisting->contains(csExisting ? csExisting : cs)), coinSeg); + const SkOpPtT* osExisting = !os ? oppSeg->existing(oppTs, nullptr) : nullptr; + const SkOpPtT* oeExisting = !oe ? oppSeg->existing(oppTe, nullptr) : nullptr; + RETURN_FALSE_IF(osExisting && osExisting == oeExisting, oppSeg); + RETURN_FALSE_IF(osExisting && (osExisting == oe || + osExisting->contains(oeExisting ? oeExisting : oe)), oppSeg); + RETURN_FALSE_IF(oeExisting && (oeExisting == os || + oeExisting->contains(osExisting ? osExisting : os)), oppSeg); + bool csDeleted = false, osDeleted = false, ceDeleted = false, oeDeleted = false; + this->debugValidate(); + if (!cs || !os) { + if (!cs) + cs = coinSeg->debugAddT(coinTs, log); + if (!os) + os = oppSeg->debugAddT(oppTs, log); +// RETURN_FALSE_IF(callerAborts, !csWritable || !osWritable); + if (cs && os) cs->span()->debugAddOpp(log, os->span()); +// cs = csWritable; +// os = osWritable->active(); + RETURN_FALSE_IF((ce && ce->deleted()) || (oe && oe->deleted()), coinSeg); + } + if (!ce || !oe) { + if (!ce) + ce = coinSeg->debugAddT(coinTe, log); + if (!oe) + oe = oppSeg->debugAddT(oppTe, log); + if (ce && oe) ce->span()->debugAddOpp(log, oe->span()); +// ce = ceWritable; +// oe = oeWritable; + } + this->debugValidate(); + RETURN_FALSE_IF(csDeleted, coinSeg); + RETURN_FALSE_IF(osDeleted, oppSeg); + RETURN_FALSE_IF(ceDeleted, coinSeg); + RETURN_FALSE_IF(oeDeleted, oppSeg); + RETURN_FALSE_IF(!cs || !ce || cs == ce || cs->contains(ce) || !os || !oe || os == oe || os->contains(oe), coinSeg); + bool result = true; + if (overlap) { + if (overlap->coinPtTStart()->segment() == coinSeg) { + log->record(SkPathOpsDebug::kAddMissingExtend_Glitch, coinSeg, coinTs, coinTe, oppSeg, oppTs, oppTe); + } else { + if (oppTs > oppTe) { + SkTSwap(coinTs, coinTe); + SkTSwap(oppTs, oppTe); + } + log->record(SkPathOpsDebug::kAddMissingExtend_Glitch, oppSeg, oppTs, oppTe, coinSeg, coinTs, coinTe); + } +#if 0 && DEBUG_COINCIDENCE_VERBOSE + if (result) { + overlap->debugShow(); + } +#endif + } else { + log->record(SkPathOpsDebug::kAddMissingCoin_Glitch, coinSeg, coinTs, coinTe, oppSeg, oppTs, oppTe); +#if 0 && DEBUG_COINCIDENCE_VERBOSE + fHead->debugShow(); +#endif + } + this->debugValidate(); + return (void) result; +} + +// Extra commented-out lines keep this in sync with addMissing() +/* detects overlaps of different coincident runs on same segment */ +/* does not detect overlaps for pairs without any segments in common */ +// returns true if caller should loop again +void SkOpCoincidence::debugAddMissing(SkPathOpsDebug::GlitchLog* log, bool* added) const { + const SkCoincidentSpans* outer = fHead; + *added = false; + if (!outer) { + return; + } + // fTop = outer; + // fHead = nullptr; + do { + // addifmissing can modify the list that this is walking + // save head so that walker can iterate over old data unperturbed + // addifmissing adds to head freely then add saved head in the end + const SkOpPtT* ocs = outer->coinPtTStart(); + SkASSERT(!ocs->deleted()); + const SkOpSegment* outerCoin = ocs->segment(); + SkASSERT(!outerCoin->done()); // if it's done, should have already been removed from list + const SkOpPtT* oos = outer->oppPtTStart(); + if (oos->deleted()) { + return; + } + const SkOpSegment* outerOpp = oos->segment(); + SkASSERT(!outerOpp->done()); +// SkOpSegment* outerCoinWritable = const_cast<SkOpSegment*>(outerCoin); +// SkOpSegment* outerOppWritable = const_cast<SkOpSegment*>(outerOpp); + const SkCoincidentSpans* inner = outer; + while ((inner = inner->next())) { + this->debugValidate(); + double overS, overE; + const SkOpPtT* ics = inner->coinPtTStart(); + SkASSERT(!ics->deleted()); + const SkOpSegment* innerCoin = ics->segment(); + SkASSERT(!innerCoin->done()); + const SkOpPtT* ios = inner->oppPtTStart(); + SkASSERT(!ios->deleted()); + const SkOpSegment* innerOpp = ios->segment(); + SkASSERT(!innerOpp->done()); +// SkOpSegment* innerCoinWritable = const_cast<SkOpSegment*>(innerCoin); +// SkOpSegment* innerOppWritable = const_cast<SkOpSegment*>(innerOpp); + if (outerCoin == innerCoin) { + const SkOpPtT* oce = outer->coinPtTEnd(); + if (oce->deleted()) { + return; + } + const SkOpPtT* ice = inner->coinPtTEnd(); + SkASSERT(!ice->deleted()); + if (outerOpp != innerOpp && this->overlap(ocs, oce, ics, ice, &overS, &overE)) { + this->debugAddIfMissing(log, ocs->starter(oce), ics->starter(ice), + overS, overE, outerOpp, innerOpp, added, + ocs->debugEnder(oce), + ics->debugEnder(ice)); + } + } else if (outerCoin == innerOpp) { + const SkOpPtT* oce = outer->coinPtTEnd(); + SkASSERT(!oce->deleted()); + const SkOpPtT* ioe = inner->oppPtTEnd(); + SkASSERT(!ioe->deleted()); + if (outerOpp != innerCoin && this->overlap(ocs, oce, ios, ioe, &overS, &overE)) { + this->debugAddIfMissing(log, ocs->starter(oce), ios->starter(ioe), + overS, overE, outerOpp, innerCoin, added, + ocs->debugEnder(oce), + ios->debugEnder(ioe)); + } + } else if (outerOpp == innerCoin) { + const SkOpPtT* ooe = outer->oppPtTEnd(); + SkASSERT(!ooe->deleted()); + const SkOpPtT* ice = inner->coinPtTEnd(); + SkASSERT(!ice->deleted()); + SkASSERT(outerCoin != innerOpp); + if (this->overlap(oos, ooe, ics, ice, &overS, &overE)) { + this->debugAddIfMissing(log, oos->starter(ooe), ics->starter(ice), + overS, overE, outerCoin, innerOpp, added, + oos->debugEnder(ooe), + ics->debugEnder(ice)); + } + } else if (outerOpp == innerOpp) { + const SkOpPtT* ooe = outer->oppPtTEnd(); + SkASSERT(!ooe->deleted()); + const SkOpPtT* ioe = inner->oppPtTEnd(); + if (ioe->deleted()) { + return; + } + SkASSERT(outerCoin != innerCoin); + if (this->overlap(oos, ooe, ios, ioe, &overS, &overE)) { + this->debugAddIfMissing(log, oos->starter(ooe), ios->starter(ioe), + overS, overE, outerCoin, innerCoin, added, + oos->debugEnder(ooe), + ios->debugEnder(ioe)); + } + } + this->debugValidate(); + } + } while ((outer = outer->next())); + // this->restoreHead(); + return; +} + +// Commented-out lines keep this in sync with release() +void SkOpCoincidence::debugRelease(SkPathOpsDebug::GlitchLog* log, const SkCoincidentSpans* coin, const SkCoincidentSpans* remove) const { + const SkCoincidentSpans* head = coin; + const SkCoincidentSpans* prev = nullptr; + const SkCoincidentSpans* next; + do { + next = coin->next(); + if (coin == remove) { + if (prev) { +// prev->setNext(next); + } else if (head == fHead) { +// fHead = next; + } else { +// fTop = next; + } + log->record(SkPathOpsDebug::kReleasedSpan_Glitch, coin); + } + prev = coin; + } while ((coin = next)); + return; +} + +void SkOpCoincidence::debugRelease(SkPathOpsDebug::GlitchLog* log, const SkOpSegment* deleted) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + if (coin->coinPtTStart()->segment() == deleted + || coin->coinPtTEnd()->segment() == deleted + || coin->oppPtTStart()->segment() == deleted + || coin->oppPtTEnd()->segment() == deleted) { + log->record(SkPathOpsDebug::kReleasedSpan_Glitch, coin); + } + } while ((coin = coin->next())); +} + +// Commented-out lines keep this in sync with expand() +// expand the range by checking adjacent spans for coincidence +bool SkOpCoincidence::debugExpand(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return false; + } + bool expanded = false; + do { + if (coin->debugExpand(log)) { + // check to see if multiple spans expanded so they are now identical + const SkCoincidentSpans* test = fHead; + do { + if (coin == test) { + continue; + } + if (coin->coinPtTStart() == test->coinPtTStart() + && coin->oppPtTStart() == test->oppPtTStart()) { + if (log) log->record(SkPathOpsDebug::kExpandCoin_Glitch, fHead, test->coinPtTStart()); + break; + } + } while ((test = test->next())); + expanded = true; + } + } while ((coin = coin->next())); + return expanded; +} + +// Commented-out lines keep this in sync with mark() +/* this sets up the coincidence links in the segments when the coincidence crosses multiple spans */ +void SkOpCoincidence::debugMark(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + FAIL_IF(!coin->coinPtTStartWritable()->span()->upCastable(), coin); + const SkOpSpan* start = coin->coinPtTStartWritable()->span()->upCast(); +// SkASSERT(start->deleted()); + const SkOpSpanBase* end = coin->coinPtTEndWritable()->span(); +// SkASSERT(end->deleted()); + const SkOpSpanBase* oStart = coin->oppPtTStartWritable()->span(); +// SkASSERT(oStart->deleted()); + const SkOpSpanBase* oEnd = coin->oppPtTEndWritable()->span(); +// SkASSERT(oEnd->deleted()); + bool flipped = coin->flipped(); + if (flipped) { + SkTSwap(oStart, oEnd); + } + /* coin and opp spans may not match up. Mark the ends, and then let the interior + get marked as many times as the spans allow */ + start->debugInsertCoincidence(log, oStart->upCast()); + end->debugInsertCoinEnd(log, oEnd); + const SkOpSegment* segment = start->segment(); + const SkOpSegment* oSegment = oStart->segment(); + const SkOpSpanBase* next = start; + const SkOpSpanBase* oNext = oStart; + bool ordered = coin->ordered(); + while ((next = next->upCast()->next()) != end) { + FAIL_IF(!next->upCastable(), coin); + if (next->upCast()->debugInsertCoincidence(log, oSegment, flipped, ordered), false) { + return; + } + } + while ((oNext = oNext->upCast()->next()) != oEnd) { + FAIL_IF(!oNext->upCastable(), coin); + if (oNext->upCast()->debugInsertCoincidence(log, segment, flipped, ordered), false) { + return; + } + } + } while ((coin = coin->next())); + return; +} +#endif + +#if DEBUG_COIN +// Commented-out lines keep this in sync with markCollapsed() +void SkOpCoincidence::debugMarkCollapsed(SkPathOpsDebug::GlitchLog* log, const SkCoincidentSpans* coin, const SkOpPtT* test) const { + const SkCoincidentSpans* head = coin; + while (coin) { + if (coin->collapsed(test)) { + if (zero_or_one(coin->coinPtTStart()->fT) && zero_or_one(coin->coinPtTEnd()->fT)) { + log->record(SkPathOpsDebug::kCollapsedCoin_Glitch, coin); + } + if (zero_or_one(coin->oppPtTStart()->fT) && zero_or_one(coin->oppPtTEnd()->fT)) { + log->record(SkPathOpsDebug::kCollapsedCoin_Glitch, coin); + } + this->debugRelease(log, head, coin); + } + coin = coin->next(); + } +} + +// Commented-out lines keep this in sync with markCollapsed() +void SkOpCoincidence::debugMarkCollapsed(SkPathOpsDebug::GlitchLog* log, const SkOpPtT* test) const { + this->debugMarkCollapsed(log, fHead, test); + this->debugMarkCollapsed(log, fTop, test); +} +#endif + +void SkCoincidentSpans::debugShow() const { + SkDebugf("coinSpan - id=%d t=%1.9g tEnd=%1.9g\n", coinPtTStart()->segment()->debugID(), + coinPtTStart()->fT, coinPtTEnd()->fT); + SkDebugf("coinSpan + id=%d t=%1.9g tEnd=%1.9g\n", oppPtTStart()->segment()->debugID(), + oppPtTStart()->fT, oppPtTEnd()->fT); +} + +void SkOpCoincidence::debugShowCoincidence() const { +#if DEBUG_COINCIDENCE + const SkCoincidentSpans* span = fHead; + while (span) { + span->debugShow(); + span = span->next(); + } +#endif +} + +#if DEBUG_COIN +static void DebugCheckBetween(const SkOpSpanBase* next, const SkOpSpanBase* end, + double oStart, double oEnd, const SkOpSegment* oSegment, + SkPathOpsDebug::GlitchLog* log) { + SkASSERT(next != end); + SkASSERT(!next->contains(end) || log); + if (next->t() > end->t()) { + SkTSwap(next, end); + } + do { + const SkOpPtT* ptT = next->ptT(); + int index = 0; + bool somethingBetween = false; + do { + ++index; + ptT = ptT->next(); + const SkOpPtT* checkPtT = next->ptT(); + if (ptT == checkPtT) { + break; + } + bool looped = false; + for (int check = 0; check < index; ++check) { + if ((looped = checkPtT == ptT)) { + break; + } + checkPtT = checkPtT->next(); + } + if (looped) { + SkASSERT(0); + break; + } + if (ptT->deleted()) { + continue; + } + if (ptT->segment() != oSegment) { + continue; + } + somethingBetween |= between(oStart, ptT->fT, oEnd); + } while (true); + SkASSERT(somethingBetween); + } while (next != end && (next = next->upCast()->next())); +} + +static void DebugCheckOverlap(const SkCoincidentSpans* test, const SkCoincidentSpans* list, + SkPathOpsDebug::GlitchLog* log) { + if (!list) { + return; + } + const SkOpSegment* coinSeg = test->coinPtTStart()->segment(); + SkASSERT(coinSeg == test->coinPtTEnd()->segment()); + const SkOpSegment* oppSeg = test->oppPtTStart()->segment(); + SkASSERT(oppSeg == test->oppPtTEnd()->segment()); + SkASSERT(coinSeg != test->oppPtTStart()->segment()); + SkDEBUGCODE(double tcs = test->coinPtTStart()->fT); + SkASSERT(between(0, tcs, 1)); + SkDEBUGCODE(double tce = test->coinPtTEnd()->fT); + SkASSERT(between(0, tce, 1)); + SkASSERT(tcs < tce); + double tos = test->oppPtTStart()->fT; + SkASSERT(between(0, tos, 1)); + double toe = test->oppPtTEnd()->fT; + SkASSERT(between(0, toe, 1)); + SkASSERT(tos != toe); + if (tos > toe) { + SkTSwap(tos, toe); + } + do { + double lcs, lce, los, loe; + if (coinSeg == list->coinPtTStart()->segment()) { + if (oppSeg != list->oppPtTStart()->segment()) { + continue; + } + lcs = list->coinPtTStart()->fT; + lce = list->coinPtTEnd()->fT; + los = list->oppPtTStart()->fT; + loe = list->oppPtTEnd()->fT; + if (los > loe) { + SkTSwap(los, loe); + } + } else if (coinSeg == list->oppPtTStart()->segment()) { + if (oppSeg != list->coinPtTStart()->segment()) { + continue; + } + lcs = list->oppPtTStart()->fT; + lce = list->oppPtTEnd()->fT; + if (lcs > lce) { + SkTSwap(lcs, lce); + } + los = list->coinPtTStart()->fT; + loe = list->coinPtTEnd()->fT; + } else { + continue; + } + SkASSERT(tce < lcs || lce < tcs); + SkASSERT(toe < los || loe < tos); + } while ((list = list->next())); +} + + +static void DebugCheckOverlapTop(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, + SkPathOpsDebug::GlitchLog* log) { + // check for overlapping coincident spans + const SkCoincidentSpans* test = head; + while (test) { + const SkCoincidentSpans* next = test->next(); + DebugCheckOverlap(test, next, log); + DebugCheckOverlap(test, opt, log); + test = next; + } +} + +static void DebugValidate(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, + SkPathOpsDebug::GlitchLog* log) { + // look for pts inside coincident spans that are not inside the opposite spans + const SkCoincidentSpans* coin = head; + while (coin) { + SkASSERT(SkOpCoincidence::Ordered(coin->coinPtTStart()->segment(), + coin->oppPtTStart()->segment())); + SkASSERT(coin->coinPtTStart()->span()->ptT() == coin->coinPtTStart()); + SkASSERT(coin->coinPtTEnd()->span()->ptT() == coin->coinPtTEnd()); + SkASSERT(coin->oppPtTStart()->span()->ptT() == coin->oppPtTStart()); + SkASSERT(coin->oppPtTEnd()->span()->ptT() == coin->oppPtTEnd()); + coin = coin->next(); + } + DebugCheckOverlapTop(head, opt, log); +} +#endif + +void SkOpCoincidence::debugValidate() const { +#if DEBUG_COINCIDENCE + DebugValidate(fHead, fTop, nullptr); + DebugValidate(fTop, nullptr, nullptr); +#endif +} + +#if DEBUG_COIN +static void DebugCheckBetween(const SkCoincidentSpans* head, const SkCoincidentSpans* opt, + SkPathOpsDebug::GlitchLog* log) { + // look for pts inside coincident spans that are not inside the opposite spans + const SkCoincidentSpans* coin = head; + while (coin) { + DebugCheckBetween(coin->coinPtTStart()->span(), coin->coinPtTEnd()->span(), + coin->oppPtTStart()->fT, coin->oppPtTEnd()->fT, coin->oppPtTStart()->segment(), + log); + DebugCheckBetween(coin->oppPtTStart()->span(), coin->oppPtTEnd()->span(), + coin->coinPtTStart()->fT, coin->coinPtTEnd()->fT, coin->coinPtTStart()->segment(), + log); + coin = coin->next(); + } + DebugCheckOverlapTop(head, opt, log); +} +#endif + +void SkOpCoincidence::debugCheckBetween() const { +#if DEBUG_COINCIDENCE + if (fGlobalState->debugCheckHealth()) { + return; + } + DebugCheckBetween(fHead, fTop, nullptr); + DebugCheckBetween(fTop, nullptr, nullptr); +#endif +} + +#if DEBUG_COIN +void SkOpContour::debugCheckHealth(SkPathOpsDebug::GlitchLog* log) const { + const SkOpSegment* segment = &fHead; + do { + segment->debugCheckHealth(log); + } while ((segment = segment->next())); +} + +void SkOpCoincidence::debugCheckValid(SkPathOpsDebug::GlitchLog* log) const { +#if DEBUG_VALIDATE + DebugValidate(fHead, fTop, log); + DebugValidate(fTop, nullptr, log); +#endif +} + +void SkOpCoincidence::debugCorrectEnds(SkPathOpsDebug::GlitchLog* log) const { + const SkCoincidentSpans* coin = fHead; + if (!coin) { + return; + } + do { + coin->debugCorrectEnds(log); + } while ((coin = coin->next())); +} + +// commmented-out lines keep this aligned with missingCoincidence() +void SkOpContour::debugMissingCoincidence(SkPathOpsDebug::GlitchLog* log) const { +// SkASSERT(fCount > 0); + const SkOpSegment* segment = &fHead; +// bool result = false; + do { + if (segment->debugMissingCoincidence(log), false) { +// result = true; + } + segment = segment->next(); + } while (segment); + return; +} + +void SkOpContour::debugMoveMultiples(SkPathOpsDebug::GlitchLog* log) const { + SkASSERT(fCount > 0); + const SkOpSegment* segment = &fHead; + do { + if (segment->debugMoveMultiples(log), false) { + return; + } + } while ((segment = segment->next())); + return; +} + +void SkOpContour::debugMoveNearby(SkPathOpsDebug::GlitchLog* log) const { + SkASSERT(fCount > 0); + const SkOpSegment* segment = &fHead; + do { + segment->debugMoveNearby(log); + } while ((segment = segment->next())); +} +#endif + +#if DEBUG_COINCIDENCE_ORDER +void SkOpSegment::debugResetCoinT() const { + fDebugBaseIndex = -1; + fDebugBaseMin = 1; + fDebugBaseMax = -1; + fDebugLastIndex = -1; + fDebugLastMin = 1; + fDebugLastMax = -1; +} +#endif + +void SkOpSegment::debugValidate() const { +#if DEBUG_COINCIDENCE_ORDER + { + const SkOpSpanBase* span = &fHead; + do { + span->debugResetCoinT(); + } while (!span->final() && (span = span->upCast()->next())); + span = &fHead; + int index = 0; + do { + span->debugSetCoinT(index++); + } while (!span->final() && (span = span->upCast()->next())); + } +#endif +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif +#if DEBUG_VALIDATE + const SkOpSpanBase* span = &fHead; + double lastT = -1; + const SkOpSpanBase* prev = nullptr; + int count = 0; + int done = 0; + do { + if (!span->final()) { + ++count; + done += span->upCast()->done() ? 1 : 0; + } + SkASSERT(span->segment() == this); + SkASSERT(!prev || prev->upCast()->next() == span); + SkASSERT(!prev || prev == span->prev()); + prev = span; + double t = span->ptT()->fT; + SkASSERT(lastT < t); + lastT = t; + span->debugValidate(); + } while (!span->final() && (span = span->upCast()->next())); + SkASSERT(count == fCount); + SkASSERT(done == fDoneCount); + SkASSERT(count >= fDoneCount); + SkASSERT(span->final()); + span->debugValidate(); +#endif +} + +#if DEBUG_COIN + +// Commented-out lines keep this in sync with addOpp() +void SkOpSpanBase::debugAddOpp(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* opp) const { + const SkOpPtT* oppPrev = this->ptT()->oppPrev(opp->ptT()); + if (!oppPrev) { + return; + } + this->debugMergeMatches(log, opp); + this->ptT()->debugAddOpp(opp->ptT(), oppPrev); + this->debugCheckForCollapsedCoincidence(log); +} + +// Commented-out lines keep this in sync with checkForCollapsedCoincidence() +void SkOpSpanBase::debugCheckForCollapsedCoincidence(SkPathOpsDebug::GlitchLog* log) const { + const SkOpCoincidence* coins = this->globalState()->coincidence(); + if (coins->isEmpty()) { + return; + } +// the insert above may have put both ends of a coincident run in the same span +// for each coincident ptT in loop; see if its opposite in is also in the loop +// this implementation is the motivation for marking that a ptT is referenced by a coincident span + const SkOpPtT* head = this->ptT(); + const SkOpPtT* test = head; + do { + if (!test->coincident()) { + continue; + } + coins->debugMarkCollapsed(log, test); + } while ((test = test->next()) != head); +} +#endif + +bool SkOpSpanBase::debugCoinEndLoopCheck() const { + int loop = 0; + const SkOpSpanBase* next = this; + SkOpSpanBase* nextCoin; + do { + nextCoin = next->fCoinEnd; + SkASSERT(nextCoin == this || nextCoin->fCoinEnd != nextCoin); + for (int check = 1; check < loop - 1; ++check) { + const SkOpSpanBase* checkCoin = this->fCoinEnd; + const SkOpSpanBase* innerCoin = checkCoin; + for (int inner = check + 1; inner < loop; ++inner) { + innerCoin = innerCoin->fCoinEnd; + if (checkCoin == innerCoin) { + SkDebugf("*** bad coincident end loop ***\n"); + return false; + } + } + } + ++loop; + } while ((next = nextCoin) && next != this); + return true; +} + +#if DEBUG_COIN +// Commented-out lines keep this in sync with insertCoinEnd() +void SkOpSpanBase::debugInsertCoinEnd(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* coin) const { + if (containsCoinEnd(coin)) { +// SkASSERT(coin->containsCoinEnd(this)); + return; + } + debugValidate(); +// SkASSERT(this != coin); + log->record(SkPathOpsDebug::kMarkCoinEnd_Glitch, this, coin); +// coin->fCoinEnd = this->fCoinEnd; +// this->fCoinEnd = coinNext; + debugValidate(); +} + +// Commented-out lines keep this in sync with mergeMatches() +// Look to see if pt-t linked list contains same segment more than once +// if so, and if each pt-t is directly pointed to by spans in that segment, +// merge them +// keep the points, but remove spans so that the segment doesn't have 2 or more +// spans pointing to the same pt-t loop at different loop elements +void SkOpSpanBase::debugMergeMatches(SkPathOpsDebug::GlitchLog* log, const SkOpSpanBase* opp) const { + const SkOpPtT* test = &fPtT; + const SkOpPtT* testNext; + const SkOpPtT* stop = test; + do { + testNext = test->next(); + if (test->deleted()) { + continue; + } + const SkOpSpanBase* testBase = test->span(); + SkASSERT(testBase->ptT() == test); + const SkOpSegment* segment = test->segment(); + if (segment->done()) { + continue; + } + const SkOpPtT* inner = opp->ptT(); + const SkOpPtT* innerStop = inner; + do { + if (inner->segment() != segment) { + continue; + } + if (inner->deleted()) { + continue; + } + const SkOpSpanBase* innerBase = inner->span(); + SkASSERT(innerBase->ptT() == inner); + // when the intersection is first detected, the span base is marked if there are + // more than one point in the intersection. +// if (!innerBase->hasMultipleHint() && !testBase->hasMultipleHint()) { + if (!zero_or_one(inner->fT)) { + log->record(SkPathOpsDebug::kMergeMatches_Glitch, innerBase, test); + } else { + SkASSERT(inner->fT != test->fT); + if (!zero_or_one(test->fT)) { + log->record(SkPathOpsDebug::kMergeMatches_Glitch, testBase, inner); + } else { + log->record(SkPathOpsDebug::kMergeMatches_Glitch, segment); +// SkDEBUGCODE(testBase->debugSetDeleted()); +// test->setDeleted(); +// SkDEBUGCODE(innerBase->debugSetDeleted()); +// inner->setDeleted(); + } + } +#ifdef SK_DEBUG // assert if another undeleted entry points to segment + const SkOpPtT* debugInner = inner; + while ((debugInner = debugInner->next()) != innerStop) { + if (debugInner->segment() != segment) { + continue; + } + if (debugInner->deleted()) { + continue; + } + SkOPASSERT(0); + } +#endif + break; +// } + break; + } while ((inner = inner->next()) != innerStop); + } while ((test = testNext) != stop); + this->debugCheckForCollapsedCoincidence(log); +} + +#endif + +void SkOpSpanBase::debugResetCoinT() const { +#if DEBUG_COINCIDENCE_ORDER + const SkOpPtT* ptT = &fPtT; + do { + ptT->debugResetCoinT(); + ptT = ptT->next(); + } while (ptT != &fPtT); +#endif +} + +void SkOpSpanBase::debugSetCoinT(int index) const { +#if DEBUG_COINCIDENCE_ORDER + const SkOpPtT* ptT = &fPtT; + do { + if (!ptT->deleted()) { + ptT->debugSetCoinT(index); + } + ptT = ptT->next(); + } while (ptT != &fPtT); +#endif +} + +const SkOpSpan* SkOpSpanBase::debugStarter(SkOpSpanBase const** endPtr) const { + const SkOpSpanBase* end = *endPtr; + SkASSERT(this->segment() == end->segment()); + const SkOpSpanBase* result; + if (t() < end->t()) { + result = this; + } else { + result = end; + *endPtr = this; + } + return result->upCast(); +} + +void SkOpSpanBase::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif +#if DEBUG_VALIDATE + const SkOpPtT* ptT = &fPtT; + SkASSERT(ptT->span() == this); + do { +// SkASSERT(SkDPoint::RoughlyEqual(fPtT.fPt, ptT->fPt)); + ptT->debugValidate(); + ptT = ptT->next(); + } while (ptT != &fPtT); + SkASSERT(this->debugCoinEndLoopCheck()); + if (!this->final()) { + SkASSERT(this->upCast()->debugCoinLoopCheck()); + } + if (fFromAngle) { + fFromAngle->debugValidate(); + } + if (!this->final() && this->upCast()->toAngle()) { + this->upCast()->toAngle()->debugValidate(); + } +#endif +} + +bool SkOpSpan::debugCoinLoopCheck() const { + int loop = 0; + const SkOpSpan* next = this; + SkOpSpan* nextCoin; + do { + nextCoin = next->fCoincident; + SkASSERT(nextCoin == this || nextCoin->fCoincident != nextCoin); + for (int check = 1; check < loop - 1; ++check) { + const SkOpSpan* checkCoin = this->fCoincident; + const SkOpSpan* innerCoin = checkCoin; + for (int inner = check + 1; inner < loop; ++inner) { + innerCoin = innerCoin->fCoincident; + if (checkCoin == innerCoin) { + SkDebugf("*** bad coincident loop ***\n"); + return false; + } + } + } + ++loop; + } while ((next = nextCoin) && next != this); + return true; +} + +#if DEBUG_COIN +// Commented-out lines keep this in sync with insertCoincidence() in header +void SkOpSpan::debugInsertCoincidence(SkPathOpsDebug::GlitchLog* log, const SkOpSpan* coin) const { + if (containsCoincidence(coin)) { +// SkASSERT(coin->containsCoincidence(this)); + return; + } + debugValidate(); +// SkASSERT(this != coin); + log->record(SkPathOpsDebug::kMarkCoinStart_Glitch, this, coin); +// coin->fCoincident = this->fCoincident; +// this->fCoincident = coinNext; + debugValidate(); +} + +// Commented-out lines keep this in sync with insertCoincidence() +void SkOpSpan::debugInsertCoincidence(SkPathOpsDebug::GlitchLog* log, const SkOpSegment* segment, bool flipped, bool ordered) const { + if (this->containsCoincidence(segment)) { + return; + } + const SkOpPtT* next = &fPtT; + while ((next = next->next()) != &fPtT) { + if (next->segment() == segment) { + const SkOpSpan* span; + const SkOpSpanBase* base = next->span(); + if (!ordered) { + const SkOpSpanBase* spanEnd = fNext->contains(segment)->span(); + const SkOpPtT* start = base->ptT()->starter(spanEnd->ptT()); + FAIL_IF(!start->span()->upCastable(), this); + span = const_cast<SkOpSpan*>(start->span()->upCast()); + } + else if (flipped) { + span = base->prev(); + FAIL_IF(!span, this); + } + else { + FAIL_IF(!base->upCastable(), this); + span = base->upCast(); + } + log->record(SkPathOpsDebug::kMarkCoinInsert_Glitch, span); + return; + } + } +#if DEBUG_COIN + log->record(SkPathOpsDebug::kMarkCoinMissing_Glitch, segment, this); +#endif + return; +} +#endif + +// called only by test code +int SkIntersections::debugCoincidentUsed() const { + if (!fIsCoincident[0]) { + SkASSERT(!fIsCoincident[1]); + return 0; + } + int count = 0; + SkDEBUGCODE(int count2 = 0;) + for (int index = 0; index < fUsed; ++index) { + if (fIsCoincident[0] & (1 << index)) { + ++count; + } +#ifdef SK_DEBUG + if (fIsCoincident[1] & (1 << index)) { + ++count2; + } +#endif + } + SkASSERT(count == count2); + return count; +} + +#include "SkOpContour.h" + +// Commented-out lines keep this in sync with addOpp() +void SkOpPtT::debugAddOpp(const SkOpPtT* opp, const SkOpPtT* oppPrev) const { + SkDEBUGCODE(const SkOpPtT* oldNext = this->fNext); + SkASSERT(this != opp); +// this->fNext = opp; + SkASSERT(oppPrev != oldNext); +// oppPrev->fNext = oldNext; +} + +bool SkOpPtT::debugContains(const SkOpPtT* check) const { + SkASSERT(this != check); + const SkOpPtT* ptT = this; + int links = 0; + do { + ptT = ptT->next(); + if (ptT == check) { + return true; + } + ++links; + const SkOpPtT* test = this; + for (int index = 0; index < links; ++index) { + if (ptT == test) { + return false; + } + test = test->next(); + } + } while (true); +} + +const SkOpPtT* SkOpPtT::debugContains(const SkOpSegment* check) const { + SkASSERT(this->segment() != check); + const SkOpPtT* ptT = this; + int links = 0; + do { + ptT = ptT->next(); + if (ptT->segment() == check) { + return ptT; + } + ++links; + const SkOpPtT* test = this; + for (int index = 0; index < links; ++index) { + if (ptT == test) { + return nullptr; + } + test = test->next(); + } + } while (true); +} + +const SkOpPtT* SkOpPtT::debugEnder(const SkOpPtT* end) const { + return fT < end->fT ? end : this; +} + +int SkOpPtT::debugLoopLimit(bool report) const { + int loop = 0; + const SkOpPtT* next = this; + do { + for (int check = 1; check < loop - 1; ++check) { + const SkOpPtT* checkPtT = this->fNext; + const SkOpPtT* innerPtT = checkPtT; + for (int inner = check + 1; inner < loop; ++inner) { + innerPtT = innerPtT->fNext; + if (checkPtT == innerPtT) { + if (report) { + SkDebugf("*** bad ptT loop ***\n"); + } + return loop; + } + } + } + // there's nothing wrong with extremely large loop counts -- but this may appear to hang + // by taking a very long time to figure out that no loop entry is a duplicate + // -- and it's likely that a large loop count is indicative of a bug somewhere + if (++loop > 1000) { + SkDebugf("*** loop count exceeds 1000 ***\n"); + return 1000; + } + } while ((next = next->fNext) && next != this); + return 0; +} + +const SkOpPtT* SkOpPtT::debugOppPrev(const SkOpPtT* opp) const { + return this->oppPrev(const_cast<SkOpPtT*>(opp)); +} + +void SkOpPtT::debugResetCoinT() const { +#if DEBUG_COINCIDENCE_ORDER + this->segment()->debugResetCoinT(); +#endif +} + +void SkOpPtT::debugSetCoinT(int index) const { +#if DEBUG_COINCIDENCE_ORDER + this->segment()->debugSetCoinT(index, fT); +#endif +} + +void SkOpPtT::debugValidate() const { +#if DEBUG_COINCIDENCE + if (this->globalState()->debugCheckHealth()) { + return; + } +#endif +#if DEBUG_VALIDATE + SkOpPhase phase = contour()->globalState()->phase(); + if (phase == SkOpPhase::kIntersecting || phase == SkOpPhase::kFixWinding) { + return; + } + SkASSERT(fNext); + SkASSERT(fNext != this); + SkASSERT(fNext->fNext); + SkASSERT(debugLoopLimit(false) == 0); +#endif +} + +static void output_scalar(SkScalar num) { + if (num == (int) num) { + SkDebugf("%d", (int) num); + } else { + SkString str; + str.printf("%1.9g", num); + int width = (int) str.size(); + const char* cStr = str.c_str(); + while (cStr[width - 1] == '0') { + --width; + } + str.resize(width); + SkDebugf("%sf", str.c_str()); + } +} + +static void output_points(const SkPoint* pts, int count) { + for (int index = 0; index < count; ++index) { + output_scalar(pts[index].fX); + SkDebugf(", "); + output_scalar(pts[index].fY); + if (index + 1 < count) { + SkDebugf(", "); + } + } +} + +static void showPathContours(SkPath::RawIter& iter, const char* pathName) { + uint8_t verb; + SkPoint pts[4]; + while ((verb = iter.next(pts)) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kMove_Verb: + SkDebugf(" %s.moveTo(", pathName); + output_points(&pts[0], 1); + SkDebugf(");\n"); + continue; + case SkPath::kLine_Verb: + SkDebugf(" %s.lineTo(", pathName); + output_points(&pts[1], 1); + SkDebugf(");\n"); + break; + case SkPath::kQuad_Verb: + SkDebugf(" %s.quadTo(", pathName); + output_points(&pts[1], 2); + SkDebugf(");\n"); + break; + case SkPath::kConic_Verb: + SkDebugf(" %s.conicTo(", pathName); + output_points(&pts[1], 2); + SkDebugf(", %1.9gf);\n", iter.conicWeight()); + break; + case SkPath::kCubic_Verb: + SkDebugf(" %s.cubicTo(", pathName); + output_points(&pts[1], 3); + SkDebugf(");\n"); + break; + case SkPath::kClose_Verb: + SkDebugf(" %s.close();\n", pathName); + break; + default: + SkDEBUGFAIL("bad verb"); + return; + } + } +} + +static const char* gFillTypeStr[] = { + "kWinding_FillType", + "kEvenOdd_FillType", + "kInverseWinding_FillType", + "kInverseEvenOdd_FillType" +}; + +void SkPathOpsDebug::ShowOnePath(const SkPath& path, const char* name, bool includeDeclaration) { + SkPath::RawIter iter(path); +#define SUPPORT_RECT_CONTOUR_DETECTION 0 +#if SUPPORT_RECT_CONTOUR_DETECTION + int rectCount = path.isRectContours() ? path.rectContours(nullptr, nullptr) : 0; + if (rectCount > 0) { + SkTDArray<SkRect> rects; + SkTDArray<SkPath::Direction> directions; + rects.setCount(rectCount); + directions.setCount(rectCount); + path.rectContours(rects.begin(), directions.begin()); + for (int contour = 0; contour < rectCount; ++contour) { + const SkRect& rect = rects[contour]; + SkDebugf("path.addRect(%1.9g, %1.9g, %1.9g, %1.9g, %s);\n", rect.fLeft, rect.fTop, + rect.fRight, rect.fBottom, directions[contour] == SkPath::kCCW_Direction + ? "SkPath::kCCW_Direction" : "SkPath::kCW_Direction"); + } + return; + } +#endif + SkPath::FillType fillType = path.getFillType(); + SkASSERT(fillType >= SkPath::kWinding_FillType && fillType <= SkPath::kInverseEvenOdd_FillType); + if (includeDeclaration) { + SkDebugf(" SkPath %s;\n", name); + } + SkDebugf(" %s.setFillType(SkPath::%s);\n", name, gFillTypeStr[fillType]); + iter.setPath(path); + showPathContours(iter, name); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsDebug.h b/gfx/skia/skia/src/pathops/SkPathOpsDebug.h new file mode 100644 index 000000000..f07d7d052 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsDebug.h @@ -0,0 +1,382 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsDebug_DEFINED +#define SkPathOpsDebug_DEFINED + +#include "SkPathOps.h" +#include "SkTypes.h" + +#include <stdlib.h> +#include <stdio.h> + +enum class SkOpPhase : char; +class SkOpContourHead; + +#ifdef SK_RELEASE +#define FORCE_RELEASE 1 +#else +#define FORCE_RELEASE 1 // set force release to 1 for multiple thread -- no debugging +#endif + +#define DEBUG_UNDER_DEVELOPMENT 1 + +#define ONE_OFF_DEBUG 0 +#define ONE_OFF_DEBUG_MATHEMATICA 0 + +#if defined(SK_BUILD_FOR_WIN) || defined(SK_BUILD_FOR_ANDROID) + #define SK_RAND(seed) rand() +#else + #define SK_RAND(seed) rand_r(&seed) +#endif +#ifdef SK_BUILD_FOR_WIN + #define SK_SNPRINTF _snprintf +#else + #define SK_SNPRINTF snprintf +#endif + +#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) + +#if FORCE_RELEASE + +#define DEBUG_ACTIVE_OP 0 +#define DEBUG_ACTIVE_SPANS 0 +#define DEBUG_ADD_INTERSECTING_TS 0 +#define DEBUG_ADD_T 0 +#define DEBUG_ALIGNMENT 0 +#define DEBUG_ANGLE 0 +#define DEBUG_ASSEMBLE 0 +#define DEBUG_COINCIDENCE 0 // sanity checking +#define DEBUG_COINCIDENCE_DUMP 0 // accumulate and dump which algorithms fired +#define DEBUG_COINCIDENCE_ORDER 0 // for well behaved curves, check if pairs match up in t-order +#define DEBUG_COINCIDENCE_VERBOSE 0 // usually whether the next function generates coincidence +#define DEBUG_CUBIC_BINARY_SEARCH 0 +#define DEBUG_CUBIC_SPLIT 0 +#define DEBUG_DUMP_SEGMENTS 0 +#define DEBUG_FLOW 0 +#define DEBUG_LIMIT_WIND_SUM 0 +#define DEBUG_MARK_DONE 0 +#define DEBUG_PATH_CONSTRUCTION 0 +#define DEBUG_PERP 0 +#define DEBUG_SHOW_TEST_NAME 0 +#define DEBUG_SORT 0 +#define DEBUG_T_SECT 0 +#define DEBUG_T_SECT_DUMP 0 +#define DEBUG_T_SECT_LOOP_COUNT 0 +#define DEBUG_VALIDATE 0 +#define DEBUG_WINDING 0 +#define DEBUG_WINDING_AT_T 0 + +#else + +#define DEBUG_ACTIVE_OP 1 +#define DEBUG_ACTIVE_SPANS 1 +#define DEBUG_ADD_INTERSECTING_TS 1 +#define DEBUG_ADD_T 1 +#define DEBUG_ALIGNMENT 0 +#define DEBUG_ANGLE 1 +#define DEBUG_ASSEMBLE 1 +#define DEBUG_COINCIDENCE 01 +#define DEBUG_COINCIDENCE_DUMP 0 +#define DEBUG_COINCIDENCE_ORDER 0 // tight arc quads may generate out-of-order coincdence spans +#define DEBUG_COINCIDENCE_VERBOSE 01 +#define DEBUG_CUBIC_BINARY_SEARCH 0 +#define DEBUG_CUBIC_SPLIT 1 +#define DEBUG_DUMP_SEGMENTS 1 +#define DEBUG_FLOW 1 +#define DEBUG_LIMIT_WIND_SUM 15 +#define DEBUG_MARK_DONE 1 +#define DEBUG_PATH_CONSTRUCTION 1 +#define DEBUG_PERP 1 +#define DEBUG_SHOW_TEST_NAME 1 +#define DEBUG_SORT 1 +#define DEBUG_T_SECT 0 +#define DEBUG_T_SECT_DUMP 0 // Use 1 normally. Use 2 to number segments, 3 for script output +#define DEBUG_T_SECT_LOOP_COUNT 0 +#define DEBUG_VALIDATE 1 +#define DEBUG_WINDING 1 +#define DEBUG_WINDING_AT_T 1 + +#endif + +#ifdef SK_RELEASE + #define SkDEBUGRELEASE(a, b) b + #define SkDEBUGPARAMS(...) +#else + #define SkDEBUGRELEASE(a, b) a + #define SkDEBUGPARAMS(...) , __VA_ARGS__ +#endif + +#if DEBUG_VALIDATE == 0 + #define PATH_OPS_DEBUG_VALIDATE_PARAMS(...) +#else + #define PATH_OPS_DEBUG_VALIDATE_PARAMS(...) , __VA_ARGS__ +#endif + +#if DEBUG_T_SECT == 0 + #define PATH_OPS_DEBUG_T_SECT_RELEASE(a, b) b + #define PATH_OPS_DEBUG_T_SECT_PARAMS(...) + #define PATH_OPS_DEBUG_T_SECT_CODE(...) +#else + #define PATH_OPS_DEBUG_T_SECT_RELEASE(a, b) a + #define PATH_OPS_DEBUG_T_SECT_PARAMS(...) , __VA_ARGS__ + #define PATH_OPS_DEBUG_T_SECT_CODE(...) __VA_ARGS__ +#endif + +#if DEBUG_T_SECT_DUMP > 1 + extern int gDumpTSectNum; +#endif + +#if DEBUG_COINCIDENCE || DEBUG_COINCIDENCE_DUMP + #define DEBUG_COIN 1 +#else + #define DEBUG_COIN 0 +#endif + +#if DEBUG_COIN + #define DEBUG_COIN_DECLARE_ONLY_PARAMS() \ + int lineNo, SkOpPhase phase, int iteration + #define DEBUG_COIN_DECLARE_PARAMS() \ + , DEBUG_COIN_DECLARE_ONLY_PARAMS() + #define DEBUG_COIN_ONLY_PARAMS() \ + __LINE__, SkOpPhase::kNoChange, 0 + #define DEBUG_COIN_PARAMS() \ + , DEBUG_COIN_ONLY_PARAMS() + #define DEBUG_ITER_ONLY_PARAMS(iteration) \ + __LINE__, SkOpPhase::kNoChange, iteration + #define DEBUG_ITER_PARAMS(iteration) \ + , DEBUG_ITER_ONLY_PARAMS(iteration) + #define DEBUG_PHASE_ONLY_PARAMS(phase) \ + __LINE__, SkOpPhase::phase, 0 + #define DEBUG_PHASE_PARAMS(phase) \ + , DEBUG_PHASE_ONLY_PARAMS(phase) + #define DEBUG_SET_PHASE() \ + this->globalState()->debugSetPhase(__func__, lineNo, phase, iteration) + #define DEBUG_STATIC_SET_PHASE(obj) \ + obj->globalState()->debugSetPhase(__func__, lineNo, phase, iteration) +#elif DEBUG_VALIDATE + #define DEBUG_COIN_DECLARE_ONLY_PARAMS() \ + SkOpPhase phase + #define DEBUG_COIN_DECLARE_PARAMS() \ + , DEBUG_COIN_DECLARE_ONLY_PARAMS() + #define DEBUG_COIN_ONLY_PARAMS() \ + SkOpPhase::kNoChange + #define DEBUG_COIN_PARAMS() \ + , DEBUG_COIN_ONLY_PARAMS() + #define DEBUG_ITER_ONLY_PARAMS(iteration) \ + SkOpPhase::kNoChange + #define DEBUG_ITER_PARAMS(iteration) \ + , DEBUG_ITER_ONLY_PARAMS(iteration) + #define DEBUG_PHASE_ONLY_PARAMS(phase) \ + SkOpPhase::phase + #define DEBUG_PHASE_PARAMS(phase) \ + , DEBUG_PHASE_ONLY_PARAMS(phase) + #define DEBUG_SET_PHASE() \ + this->globalState()->debugSetPhase(phase) + #define DEBUG_STATIC_SET_PHASE(obj) \ + obj->globalState()->debugSetPhase(phase) +#else + #define DEBUG_COIN_DECLARE_ONLY_PARAMS() + #define DEBUG_COIN_DECLARE_PARAMS() + #define DEBUG_COIN_ONLY_PARAMS() + #define DEBUG_COIN_PARAMS() + #define DEBUG_ITER_ONLY_PARAMS(iteration) + #define DEBUG_ITER_PARAMS(iteration) + #define DEBUG_PHASE_ONLY_PARAMS(phase) + #define DEBUG_PHASE_PARAMS(phase) + #define DEBUG_SET_PHASE() + #define DEBUG_STATIC_SET_PHASE(obj) +#endif + +#define CUBIC_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" +#define CONIC_DEBUG_STR "{{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}, %1.9g}" +#define QUAD_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}, {%1.9g,%1.9g}}}" +#define LINE_DEBUG_STR "{{{%1.9g,%1.9g}, {%1.9g,%1.9g}}}" +#define PT_DEBUG_STR "{{%1.9g,%1.9g}}" + +#define T_DEBUG_STR(t, n) #t "[" #n "]=%1.9g" +#define TX_DEBUG_STR(t) #t "[%d]=%1.9g" +#define CUBIC_DEBUG_DATA(c) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, c[3].fX, c[3].fY +#define CONIC_DEBUG_DATA(c, w) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, w +#define QUAD_DEBUG_DATA(q) q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY +#define LINE_DEBUG_DATA(l) l[0].fX, l[0].fY, l[1].fX, l[1].fY +#define PT_DEBUG_DATA(i, n) i.pt(n).asSkPoint().fX, i.pt(n).asSkPoint().fY + +#ifndef DEBUG_TEST +#define DEBUG_TEST 0 +#endif + +#if DEBUG_SHOW_TEST_NAME +#include "SkTLS.h" +#endif + +// Tests with extreme numbers may fail, but all other tests should never fail. +#define FAIL_IF(cond) \ + do { bool fail = (cond); SkOPASSERT(!fail); if (fail) return false; } while (false) + +#define FAIL_WITH_NULL_IF(cond) \ + do { bool fail = (cond); SkOPASSERT(!fail); if (fail) return nullptr; } while (false) + +// Some functions serve two masters: one allows the function to fail, the other expects success +// always. If abort is true, tests with normal numbers may not fail and assert if they do so. +// If abort is false, both normal and extreme numbers may return false without asserting. +#define RETURN_FALSE_IF(abort, cond) \ + do { bool fail = (cond); SkOPASSERT(!(abort) || !fail); if (fail) return false; \ + } while (false) + +class SkPathOpsDebug { +public: + static const char* kLVerbStr[]; + +#if DEBUG_COIN + struct GlitchLog; + + enum GlitchType { + kUninitialized_Glitch, + kAddCorruptCoin_Glitch, + kAddExpandedCoin_Glitch, + kAddExpandedFail_Glitch, + kAddIfCollapsed_Glitch, + kAddIfMissingCoin_Glitch, + kAddMissingCoin_Glitch, + kAddMissingExtend_Glitch, + kAddOrOverlap_Glitch, + kCollapsedCoin_Glitch, + kCollapsedDone_Glitch, + kCollapsedOppValue_Glitch, + kCollapsedSpan_Glitch, + kCollapsedWindValue_Glitch, + kCorrectEnd_Glitch, + kDeletedCoin_Glitch, + kExpandCoin_Glitch, + kFail_Glitch, + kMarkCoinEnd_Glitch, + kMarkCoinInsert_Glitch, + kMarkCoinMissing_Glitch, + kMarkCoinStart_Glitch, + kMergeMatches_Glitch, + kMissingCoin_Glitch, + kMissingDone_Glitch, + kMissingIntersection_Glitch, + kMoveMultiple_Glitch, + kMoveNearbyClearAll_Glitch, + kMoveNearbyClearAll2_Glitch, + kMoveNearbyMerge_Glitch, + kMoveNearbyMergeFinal_Glitch, + kMoveNearbyRelease_Glitch, + kMoveNearbyReleaseFinal_Glitch, + kReleasedSpan_Glitch, + kReturnFalse_Glitch, + kUnaligned_Glitch, + kUnalignedHead_Glitch, + kUnalignedTail_Glitch, + }; + + struct CoinDictEntry { + int fIteration; + int fLineNumber; + GlitchType fGlitchType; + const char* fFunctionName; + }; + + struct CoinDict { + void add(const CoinDictEntry& key); + void add(const CoinDict& dict); + void dump(const char* str, bool visitCheck) const; + SkTDArray<CoinDictEntry> fDict; + }; + + static CoinDict gCoinSumChangedDict; + static CoinDict gCoinSumVisitedDict; + static CoinDict gCoinVistedDict; +#endif + +#if defined(SK_DEBUG) || !FORCE_RELEASE + static int gContourID; + static int gSegmentID; +#endif + +#if DEBUG_SORT + static int gSortCountDefault; + static int gSortCount; +#endif + +#if DEBUG_ACTIVE_OP + static const char* kPathOpStr[]; +#endif + + static void MathematicaIze(char* str, size_t bufferSize); + static bool ValidWind(int winding); + static void WindingPrintf(int winding); + +#if DEBUG_SHOW_TEST_NAME + static void* CreateNameStr(); + static void DeleteNameStr(void* v); +#define DEBUG_FILENAME_STRING_LENGTH 64 +#define DEBUG_FILENAME_STRING (reinterpret_cast<char* >(SkTLS::Get(SkPathOpsDebug::CreateNameStr, \ + SkPathOpsDebug::DeleteNameStr))) + static void BumpTestName(char* ); +#endif + static const char* OpStr(SkPathOp ); + static void ShowActiveSpans(SkOpContourHead* contourList); + static void ShowOnePath(const SkPath& path, const char* name, bool includeDeclaration); + static void ShowPath(const SkPath& one, const SkPath& two, SkPathOp op, const char* name); + + static bool ChaseContains(const SkTDArray<class SkOpSpanBase*>& , const class SkOpSpanBase* ); + + static void CheckHealth(class SkOpContourHead* contourList); + + static const class SkOpAngle* DebugAngleAngle(const class SkOpAngle*, int id); + static class SkOpContour* DebugAngleContour(class SkOpAngle*, int id); + static const class SkOpPtT* DebugAnglePtT(const class SkOpAngle*, int id); + static const class SkOpSegment* DebugAngleSegment(const class SkOpAngle*, int id); + static const class SkOpSpanBase* DebugAngleSpan(const class SkOpAngle*, int id); + + static const class SkOpAngle* DebugContourAngle(class SkOpContour*, int id); + static class SkOpContour* DebugContourContour(class SkOpContour*, int id); + static const class SkOpPtT* DebugContourPtT(class SkOpContour*, int id); + static const class SkOpSegment* DebugContourSegment(class SkOpContour*, int id); + static const class SkOpSpanBase* DebugContourSpan(class SkOpContour*, int id); + + static const class SkOpAngle* DebugCoincidenceAngle(class SkOpCoincidence*, int id); + static class SkOpContour* DebugCoincidenceContour(class SkOpCoincidence*, int id); + static const class SkOpPtT* DebugCoincidencePtT(class SkOpCoincidence*, int id); + static const class SkOpSegment* DebugCoincidenceSegment(class SkOpCoincidence*, int id); + static const class SkOpSpanBase* DebugCoincidenceSpan(class SkOpCoincidence*, int id); + + static const class SkOpAngle* DebugPtTAngle(const class SkOpPtT*, int id); + static class SkOpContour* DebugPtTContour(class SkOpPtT*, int id); + static const class SkOpPtT* DebugPtTPtT(const class SkOpPtT*, int id); + static const class SkOpSegment* DebugPtTSegment(const class SkOpPtT*, int id); + static const class SkOpSpanBase* DebugPtTSpan(const class SkOpPtT*, int id); + + static const class SkOpAngle* DebugSegmentAngle(const class SkOpSegment*, int id); + static class SkOpContour* DebugSegmentContour(class SkOpSegment*, int id); + static const class SkOpPtT* DebugSegmentPtT(const class SkOpSegment*, int id); + static const class SkOpSegment* DebugSegmentSegment(const class SkOpSegment*, int id); + static const class SkOpSpanBase* DebugSegmentSpan(const class SkOpSegment*, int id); + + static const class SkOpAngle* DebugSpanAngle(const class SkOpSpanBase*, int id); + static class SkOpContour* DebugSpanContour(class SkOpSpanBase*, int id); + static const class SkOpPtT* DebugSpanPtT(const class SkOpSpanBase*, int id); + static const class SkOpSegment* DebugSpanSegment(const class SkOpSpanBase*, int id); + static const class SkOpSpanBase* DebugSpanSpan(const class SkOpSpanBase*, int id); + +#if DEBUG_COIN + static void DumpCoinDict(); + static void DumpGlitchType(GlitchType ); +#endif +}; + +struct SkDQuad; + +// generates tools/path_sorter.htm and path_visualizer.htm compatible data +void DumpQ(const SkDQuad& quad1, const SkDQuad& quad2, int testNo); +void DumpT(const SkDQuad& quad, double t); + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsLine.cpp b/gfx/skia/skia/src/pathops/SkPathOpsLine.cpp new file mode 100644 index 000000000..6fa091db8 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsLine.cpp @@ -0,0 +1,149 @@ +/* + * 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 "SkPathOpsLine.h" + +SkDPoint SkDLine::ptAtT(double t) const { + if (0 == t) { + return fPts[0]; + } + if (1 == t) { + return fPts[1]; + } + double one_t = 1 - t; + SkDPoint result = { one_t * fPts[0].fX + t * fPts[1].fX, one_t * fPts[0].fY + t * fPts[1].fY }; + return result; +} + +double SkDLine::exactPoint(const SkDPoint& xy) const { + if (xy == fPts[0]) { // do cheapest test first + return 0; + } + if (xy == fPts[1]) { + return 1; + } + return -1; +} + +double SkDLine::nearPoint(const SkDPoint& xy, bool* unequal) const { + if (!AlmostBetweenUlps(fPts[0].fX, xy.fX, fPts[1].fX) + || !AlmostBetweenUlps(fPts[0].fY, xy.fY, fPts[1].fY)) { + return -1; + } + // project a perpendicular ray from the point to the line; find the T on the line + SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line + double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay + SkDVector ab0 = xy - fPts[0]; + double numer = len.fX * ab0.fX + ab0.fY * len.fY; + if (!between(0, numer, denom)) { + return -1; + } + if (!denom) { + return 0; + } + double t = numer / denom; + SkDPoint realPt = ptAtT(t); + double dist = realPt.distance(xy); // OPTIMIZATION: can we compare against distSq instead ? + // find the ordinal in the original line with the largest unsigned exponent + double tiniest = SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); + double largest = SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); + largest = SkTMax(largest, -tiniest); + if (!AlmostEqualUlps_Pin(largest, largest + dist)) { // is the dist within ULPS tolerance? + return -1; + } + if (unequal) { + *unequal = (float) largest != (float) (largest + dist); + } + t = SkPinT(t); // a looser pin breaks skpwww_lptemp_com_3 + SkASSERT(between(0, t, 1)); + return t; +} + +bool SkDLine::nearRay(const SkDPoint& xy) const { + // project a perpendicular ray from the point to the line; find the T on the line + SkDVector len = fPts[1] - fPts[0]; // the x/y magnitudes of the line + double denom = len.fX * len.fX + len.fY * len.fY; // see DLine intersectRay + SkDVector ab0 = xy - fPts[0]; + double numer = len.fX * ab0.fX + ab0.fY * len.fY; + double t = numer / denom; + SkDPoint realPt = ptAtT(t); + double dist = realPt.distance(xy); // OPTIMIZATION: can we compare against distSq instead ? + // find the ordinal in the original line with the largest unsigned exponent + double tiniest = SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); + double largest = SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), fPts[1].fX), fPts[1].fY); + largest = SkTMax(largest, -tiniest); + return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? +} + +double SkDLine::ExactPointH(const SkDPoint& xy, double left, double right, double y) { + if (xy.fY == y) { + if (xy.fX == left) { + return 0; + } + if (xy.fX == right) { + return 1; + } + } + return -1; +} + +double SkDLine::NearPointH(const SkDPoint& xy, double left, double right, double y) { + if (!AlmostBequalUlps(xy.fY, y)) { + return -1; + } + if (!AlmostBetweenUlps(left, xy.fX, right)) { + return -1; + } + double t = (xy.fX - left) / (right - left); + t = SkPinT(t); + SkASSERT(between(0, t, 1)); + double realPtX = (1 - t) * left + t * right; + SkDVector distU = {xy.fY - y, xy.fX - realPtX}; + double distSq = distU.fX * distU.fX + distU.fY * distU.fY; + double dist = sqrt(distSq); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(y, left), right); + double largest = SkTMax(SkTMax(y, left), right); + largest = SkTMax(largest, -tiniest); + if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance? + return -1; + } + return t; +} + +double SkDLine::ExactPointV(const SkDPoint& xy, double top, double bottom, double x) { + if (xy.fX == x) { + if (xy.fY == top) { + return 0; + } + if (xy.fY == bottom) { + return 1; + } + } + return -1; +} + +double SkDLine::NearPointV(const SkDPoint& xy, double top, double bottom, double x) { + if (!AlmostBequalUlps(xy.fX, x)) { + return -1; + } + if (!AlmostBetweenUlps(top, xy.fY, bottom)) { + return -1; + } + double t = (xy.fY - top) / (bottom - top); + t = SkPinT(t); + SkASSERT(between(0, t, 1)); + double realPtY = (1 - t) * top + t * bottom; + SkDVector distU = {xy.fX - x, xy.fY - realPtY}; + double distSq = distU.fX * distU.fX + distU.fY * distU.fY; + double dist = sqrt(distSq); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(x, top), bottom); + double largest = SkTMax(SkTMax(x, top), bottom); + largest = SkTMax(largest, -tiniest); + if (!AlmostEqualUlps(largest, largest + dist)) { // is the dist within ULPS tolerance? + return -1; + } + return t; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsLine.h b/gfx/skia/skia/src/pathops/SkPathOpsLine.h new file mode 100644 index 000000000..882dadc1f --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsLine.h @@ -0,0 +1,39 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsLine_DEFINED +#define SkPathOpsLine_DEFINED + +#include "SkPathOpsPoint.h" + +struct SkDLine { + SkDPoint fPts[2]; + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 2); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 2); return fPts[n]; } + + const SkDLine& set(const SkPoint pts[2]) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + return *this; + } + + double exactPoint(const SkDPoint& xy) const; + static double ExactPointH(const SkDPoint& xy, double left, double right, double y); + static double ExactPointV(const SkDPoint& xy, double top, double bottom, double x); + + double nearPoint(const SkDPoint& xy, bool* unequal) const; + bool nearRay(const SkDPoint& xy) const; + static double NearPointH(const SkDPoint& xy, double left, double right, double y); + static double NearPointV(const SkDPoint& xy, double top, double bottom, double x); + SkDPoint ptAtT(double t) const; + + void dump() const; + void dumpID(int ) const; + void dumpInner() const; +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsOp.cpp b/gfx/skia/skia/src/pathops/SkPathOpsOp.cpp new file mode 100644 index 000000000..e622451a9 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsOp.cpp @@ -0,0 +1,472 @@ +/* + * 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 "SkAddIntersections.h" +#include "SkOpCoincidence.h" +#include "SkOpEdgeBuilder.h" +#include "SkPathOpsCommon.h" +#include "SkPathWriter.h" + +static SkOpSegment* findChaseOp(SkTDArray<SkOpSpanBase*>& chase, SkOpSpanBase** startPtr, + SkOpSpanBase** endPtr) { + while (chase.count()) { + SkOpSpanBase* span; + chase.pop(&span); + // OPTIMIZE: prev makes this compatible with old code -- but is it necessary? + *startPtr = span->ptT()->prev()->span(); + SkOpSegment* segment = (*startPtr)->segment(); + bool done = true; + *endPtr = nullptr; + if (SkOpAngle* last = segment->activeAngle(*startPtr, startPtr, endPtr, &done)) { + *startPtr = last->start(); + *endPtr = last->end(); + #if TRY_ROTATE + *chase.insert(0) = span; + #else + *chase.append() = span; + #endif + return last->segment(); + } + if (done) { + continue; + } + int winding; + bool sortable; + const SkOpAngle* angle = AngleWinding(*startPtr, *endPtr, &winding, &sortable); + if (!angle) { + return nullptr; + } + if (winding == SK_MinS32) { + continue; + } + int sumMiWinding, sumSuWinding; + if (sortable) { + segment = angle->segment(); + sumMiWinding = segment->updateWindingReverse(angle); + if (sumMiWinding == SK_MinS32) { + SkASSERT(segment->globalState()->debugSkipAssert()); + return nullptr; + } + sumSuWinding = segment->updateOppWindingReverse(angle); + if (sumSuWinding == SK_MinS32) { + SkASSERT(segment->globalState()->debugSkipAssert()); + return nullptr; + } + if (segment->operand()) { + SkTSwap<int>(sumMiWinding, sumSuWinding); + } + } + SkOpSegment* first = nullptr; + const SkOpAngle* firstAngle = angle; + while ((angle = angle->next()) != firstAngle) { + segment = angle->segment(); + SkOpSpanBase* start = angle->start(); + SkOpSpanBase* end = angle->end(); + int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; + if (sortable) { + segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding, + &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + } + if (!segment->done(angle)) { + if (!first && (sortable || start->starter(end)->windSum() != SK_MinS32)) { + first = segment; + *startPtr = start; + *endPtr = end; + } + // OPTIMIZATION: should this also add to the chase? + if (sortable) { + (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding, + oppSumWinding, angle); + } + } + } + if (first) { + #if TRY_ROTATE + *chase.insert(0) = span; + #else + *chase.append() = span; + #endif + return first; + } + } + return nullptr; +} + +static bool bridgeOp(SkOpContourHead* contourList, const SkPathOp op, + const int xorMask, const int xorOpMask, SkPathWriter* simple) { + bool unsortable = false; + do { + SkOpSpan* span = FindSortableTop(contourList); + if (!span) { + break; + } + SkOpSegment* current = span->segment(); + SkOpSpanBase* start = span->next(); + SkOpSpanBase* end = span; + SkTDArray<SkOpSpanBase*> chase; + do { + if (current->activeOp(start, end, xorMask, xorOpMask, op)) { + do { + if (!unsortable && current->done()) { + break; + } + SkASSERT(unsortable || !current->done()); + SkOpSpanBase* nextStart = start; + SkOpSpanBase* nextEnd = end; + SkOpSegment* next = current->findNextOp(&chase, &nextStart, &nextEnd, + &unsortable, op, xorMask, xorOpMask); + if (!next) { + if (!unsortable && simple->hasMove() + && current->verb() != SkPath::kLine_Verb + && !simple->isClosed()) { + if (!current->addCurveTo(start, end, simple)) { + return false; + } + if (!simple->isClosed()) { + SkPathOpsDebug::ShowActiveSpans(contourList); + } + } + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), start->pt().fX, start->pt().fY, + end->pt().fX, end->pt().fY); + #endif + if (!current->addCurveTo(start, end, simple)) { + return false; + } + current = next; + start = nextStart; + end = nextEnd; + } while (!simple->isClosed() && (!unsortable || !start->starter(end)->done())); + if (current->activeWinding(start, end) && !simple->isClosed()) { + SkOpSpan* spanStart = start->starter(end); + if (!spanStart->done()) { + if (!current->addCurveTo(start, end, simple)) { + return false; + } + current->markDone(spanStart); + } + } + simple->finishContour(); + } else { + SkOpSpanBase* last = current->markAndChaseDone(start, end); + if (last && !last->chased()) { + last->setChased(true); + SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); + *chase.append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); + if (!last->final()) { + SkDebugf(" windSum=%d", last->upCast()->windSum()); + } + SkDebugf("\n"); +#endif + } + } + current = findChaseOp(chase, &start, &end); + SkPathOpsDebug::ShowActiveSpans(contourList); + if (!current) { + break; + } + } while (true); + } while (true); + return true; +} + +// pretty picture: +// https://docs.google.com/a/google.com/drawings/d/1sPV8rPfpEFXymBp3iSbDRWAycp1b-7vD9JP2V-kn9Ss/edit?usp=sharing +static const SkPathOp gOpInverse[kReverseDifference_SkPathOp + 1][2][2] = { +// inside minuend outside minuend +// inside subtrahend outside subtrahend inside subtrahend outside subtrahend +{{ kDifference_SkPathOp, kIntersect_SkPathOp }, { kUnion_SkPathOp, kReverseDifference_SkPathOp }}, +{{ kIntersect_SkPathOp, kDifference_SkPathOp }, { kReverseDifference_SkPathOp, kUnion_SkPathOp }}, +{{ kUnion_SkPathOp, kReverseDifference_SkPathOp }, { kDifference_SkPathOp, kIntersect_SkPathOp }}, +{{ kXOR_SkPathOp, kXOR_SkPathOp }, { kXOR_SkPathOp, kXOR_SkPathOp }}, +{{ kReverseDifference_SkPathOp, kUnion_SkPathOp }, { kIntersect_SkPathOp, kDifference_SkPathOp }}, +}; + +static const bool gOutInverse[kReverseDifference_SkPathOp + 1][2][2] = { + {{ false, false }, { true, false }}, // diff + {{ false, false }, { false, true }}, // sect + {{ false, true }, { true, true }}, // union + {{ false, true }, { true, false }}, // xor + {{ false, true }, { false, false }}, // rev diff +}; + +#define DEBUGGING_PATHOPS_FROM_HOST 0 // enable to debug svg in chrome -- note path hardcoded below +#if DEBUGGING_PATHOPS_FROM_HOST +#include "SkData.h" +#include "SkStream.h" + +static void dump_path(FILE* file, const SkPath& path, bool force, bool dumpAsHex) { + SkDynamicMemoryWStream wStream; + path.dump(&wStream, force, dumpAsHex); + sk_sp<SkData> data(wStream.detachAsData()); + fprintf(file, "%.*s\n", (int) data->size(), (char*) data->data()); +} + +static int dumpID = 0; + +static void dump_op(const SkPath& one, const SkPath& two, SkPathOp op) { +#if SK_BUILD_FOR_MAC + FILE* file = fopen("/Users/caryclark/Documents/svgop.txt", "w"); +#else + FILE* file = fopen("/usr/local/google/home/caryclark/Documents/svgop.txt", "w"); +#endif + fprintf(file, + "\nstatic void fuzz763_%d(skiatest::Reporter* reporter, const char* filename) {\n", + ++dumpID); + fprintf(file, " SkPath path;\n"); + fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", one.getFillType()); + dump_path(file, one, false, true); + fprintf(file, " SkPath path1(path);\n"); + fprintf(file, " path.reset();\n"); + fprintf(file, " path.setFillType((SkPath::FillType) %d);\n", two.getFillType()); + dump_path(file, two, false, true); + fprintf(file, " SkPath path2(path);\n"); + fprintf(file, " testPathOp(reporter, path1, path2, (SkPathOp) %d, filename);\n", op); + fprintf(file, "}\n"); + fclose(file); +} +#endif + + +#if DEBUG_T_SECT_LOOP_COUNT + +#include "SkMutex.h" + +SK_DECLARE_STATIC_MUTEX(debugWorstLoop); + +SkOpGlobalState debugWorstState(nullptr, nullptr SkDEBUGPARAMS(false) SkDEBUGPARAMS(nullptr) + SkDEBUGPARAMS(nullptr)); + +void ReportPathOpsDebugging() { + debugWorstState.debugLoopReport(); +} + +extern void (*gVerboseFinalize)(); + +#endif + +bool OpDebug(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result + SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) { + SkChunkAlloc allocator(4096); // FIXME: add a constant expression here, tune + SkOpContour contour; + SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour); + SkOpGlobalState globalState(contourList, &allocator + SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); + SkOpCoincidence coincidence(&globalState); +#if DEBUGGING_PATHOPS_FROM_HOST + dump_op(one, two, op); +#endif + op = gOpInverse[op][one.isInverseFillType()][two.isInverseFillType()]; + SkPath::FillType fillType = gOutInverse[op][one.isInverseFillType()][two.isInverseFillType()] + ? SkPath::kInverseEvenOdd_FillType : SkPath::kEvenOdd_FillType; + SkScalar scaleFactor = SkTMax(ScaleFactor(one), ScaleFactor(two)); + SkPath scaledOne, scaledTwo; + const SkPath* minuend, * subtrahend; + if (scaleFactor > SK_Scalar1) { + ScalePath(one, 1.f / scaleFactor, &scaledOne); + minuend = &scaledOne; + ScalePath(two, 1.f / scaleFactor, &scaledTwo); + subtrahend = &scaledTwo; + } else { + minuend = &one; + subtrahend = &two; + } + if (op == kReverseDifference_SkPathOp) { + SkTSwap(minuend, subtrahend); + op = kDifference_SkPathOp; + } +#if DEBUG_SORT + SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; +#endif + // turn path into list of segments + SkOpEdgeBuilder builder(*minuend, contourList, &globalState); + if (builder.unparseable()) { + return false; + } + const int xorMask = builder.xorMask(); + builder.addOperand(*subtrahend); + if (!builder.finish()) { + return false; + } +#if DEBUG_DUMP_SEGMENTS + contourList->dumpSegments("seg", op); +#endif + + const int xorOpMask = builder.xorMask(); + if (!SortContourList(&contourList, xorMask == kEvenOdd_PathOpsMask, + xorOpMask == kEvenOdd_PathOpsMask)) { + result->reset(); + result->setFillType(fillType); + return true; + } + // find all intersections between segments + SkOpContour* current = contourList; + do { + SkOpContour* next = current; + while (AddIntersectTs(current, next, &coincidence) + && (next = next->next())) + ; + } while ((current = current->next())); +#if DEBUG_VALIDATE + globalState.setPhase(SkOpPhase::kWalking); +#endif + bool success = HandleCoincidence(contourList, &coincidence); +#if DEBUG_COIN + globalState.debugAddToGlobalCoinDicts(); +#endif + if (!success) { + return false; + } +#if DEBUG_ALIGNMENT + contourList->dumpSegments("aligned"); +#endif + // construct closed contours + result->reset(); + result->setFillType(fillType); + SkPathWriter wrapper(*result); + if (!bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper)) { + return false; + } + wrapper.assemble(); // if some edges could not be resolved, assemble remaining +#if DEBUG_T_SECT_LOOP_COUNT + { + SkAutoMutexAcquire autoM(debugWorstLoop); + if (!gVerboseFinalize) { + gVerboseFinalize = &ReportPathOpsDebugging; + } + debugWorstState.debugDoYourWorst(&globalState); + } +#endif + if (scaleFactor > 1) { + ScalePath(*result, scaleFactor, result); + } + return true; +} + +#define DEBUG_VERIFY 0 + +#if DEBUG_VERIFY +#include "SkBitmap.h" +#include "SkCanvas.h" +#include "SkPaint.h" + +const int bitWidth = 64; +const int bitHeight = 64; + +static void debug_scale_matrix(const SkPath& one, const SkPath& two, SkMatrix& scale) { + SkRect larger = one.getBounds(); + larger.join(two.getBounds()); + SkScalar largerWidth = larger.width(); + if (largerWidth < 4) { + largerWidth = 4; + } + SkScalar largerHeight = larger.height(); + if (largerHeight < 4) { + largerHeight = 4; + } + SkScalar hScale = (bitWidth - 2) / largerWidth; + SkScalar vScale = (bitHeight - 2) / largerHeight; + scale.reset(); + scale.preScale(hScale, vScale); + larger.fLeft *= hScale; + larger.fRight *= hScale; + larger.fTop *= vScale; + larger.fBottom *= vScale; + SkScalar dx = -16000 > larger.fLeft ? -16000 - larger.fLeft + : 16000 < larger.fRight ? 16000 - larger.fRight : 0; + SkScalar dy = -16000 > larger.fTop ? -16000 - larger.fTop + : 16000 < larger.fBottom ? 16000 - larger.fBottom : 0; + scale.preTranslate(dx, dy); +} + +static int debug_paths_draw_the_same(const SkPath& one, const SkPath& two, SkBitmap& bits) { + if (bits.width() == 0) { + bits.allocN32Pixels(bitWidth * 2, bitHeight); + } + SkCanvas canvas(bits); + canvas.drawColor(SK_ColorWHITE); + SkPaint paint; + canvas.save(); + const SkRect& bounds1 = one.getBounds(); + canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1); + canvas.drawPath(one, paint); + canvas.restore(); + canvas.save(); + canvas.translate(-bounds1.fLeft + 1 + bitWidth, -bounds1.fTop + 1); + canvas.drawPath(two, paint); + canvas.restore(); + int errors = 0; + for (int y = 0; y < bitHeight - 1; ++y) { + uint32_t* addr1 = bits.getAddr32(0, y); + uint32_t* addr2 = bits.getAddr32(0, y + 1); + uint32_t* addr3 = bits.getAddr32(bitWidth, y); + uint32_t* addr4 = bits.getAddr32(bitWidth, y + 1); + for (int x = 0; x < bitWidth - 1; ++x) { + // count 2x2 blocks + bool err = addr1[x] != addr3[x]; + if (err) { + errors += addr1[x + 1] != addr3[x + 1] + && addr2[x] != addr4[x] && addr2[x + 1] != addr4[x + 1]; + } + } + } + return errors; +} + +#endif + +bool Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) { +#if DEBUG_VERIFY + if (!OpDebug(one, two, op, result SkDEBUGPARAMS(nullptr))) { + SkDebugf("%s did not expect failure\none: fill=%d\n", __FUNCTION__, one.getFillType()); + one.dumpHex(); + SkDebugf("two: fill=%d\n", two.getFillType()); + two.dumpHex(); + SkASSERT(0); + return false; + } + SkPath pathOut, scaledPathOut; + SkRegion rgnA, rgnB, openClip, rgnOut; + openClip.setRect(-16000, -16000, 16000, 16000); + rgnA.setPath(one, openClip); + rgnB.setPath(two, openClip); + rgnOut.op(rgnA, rgnB, (SkRegion::Op) op); + rgnOut.getBoundaryPath(&pathOut); + SkMatrix scale; + debug_scale_matrix(one, two, scale); + SkRegion scaledRgnA, scaledRgnB, scaledRgnOut; + SkPath scaledA, scaledB; + scaledA.addPath(one, scale); + scaledA.setFillType(one.getFillType()); + scaledB.addPath(two, scale); + scaledB.setFillType(two.getFillType()); + scaledRgnA.setPath(scaledA, openClip); + scaledRgnB.setPath(scaledB, openClip); + scaledRgnOut.op(scaledRgnA, scaledRgnB, (SkRegion::Op) op); + scaledRgnOut.getBoundaryPath(&scaledPathOut); + SkBitmap bitmap; + SkPath scaledOut; + scaledOut.addPath(*result, scale); + scaledOut.setFillType(result->getFillType()); + int errors = debug_paths_draw_the_same(scaledPathOut, scaledOut, bitmap); + const int MAX_ERRORS = 9; + if (errors > MAX_ERRORS) { + SkDebugf("%s did not expect failure\none: fill=%d\n", __FUNCTION__, one.getFillType()); + one.dumpHex(); + SkDebugf("two: fill=%d\n", two.getFillType()); + two.dumpHex(); + SkASSERT(0); + } + return true; +#else + return OpDebug(one, two, op, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); +#endif +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsPoint.cpp b/gfx/skia/skia/src/pathops/SkPathOpsPoint.cpp new file mode 100644 index 000000000..e0f175dac --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsPoint.cpp @@ -0,0 +1,12 @@ +/* + * 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 "SkPathOpsPoint.h" + +SkDVector operator-(const SkDPoint& a, const SkDPoint& b) { + SkDVector v = {a.fX - b.fX, a.fY - b.fY}; + return v; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsPoint.h b/gfx/skia/skia/src/pathops/SkPathOpsPoint.h new file mode 100644 index 000000000..f314f69d0 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsPoint.h @@ -0,0 +1,271 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsPoint_DEFINED +#define SkPathOpsPoint_DEFINED + +#include "SkPathOpsTypes.h" +#include "SkPoint.h" + +inline bool AlmostEqualUlps(const SkPoint& pt1, const SkPoint& pt2) { + return AlmostEqualUlps(pt1.fX, pt2.fX) && AlmostEqualUlps(pt1.fY, pt2.fY); +} + +struct SkDVector { + double fX; + double fY; + + void set(const SkVector& pt) { + fX = pt.fX; + fY = pt.fY; + } + + // only used by testing + void operator+=(const SkDVector& v) { + fX += v.fX; + fY += v.fY; + } + + // only called by nearestT, which is currently only used by testing + void operator-=(const SkDVector& v) { + fX -= v.fX; + fY -= v.fY; + } + + // only used by testing + void operator/=(const double s) { + fX /= s; + fY /= s; + } + + // only used by testing + void operator*=(const double s) { + fX *= s; + fY *= s; + } + + SkVector asSkVector() const { + SkVector v = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)}; + return v; + } + + // only used by testing + double cross(const SkDVector& a) const { + return fX * a.fY - fY * a.fX; + } + + // similar to cross, this bastardization considers nearly coincident to be zero + // uses ulps epsilon == 16 + double crossCheck(const SkDVector& a) const { + double xy = fX * a.fY; + double yx = fY * a.fX; + return AlmostEqualUlps(xy, yx) ? 0 : xy - yx; + } + + // allow tinier numbers + double crossNoNormalCheck(const SkDVector& a) const { + double xy = fX * a.fY; + double yx = fY * a.fX; + return AlmostEqualUlpsNoNormalCheck(xy, yx) ? 0 : xy - yx; + } + + double dot(const SkDVector& a) const { + return fX * a.fX + fY * a.fY; + } + + double length() const { + return sqrt(lengthSquared()); + } + + double lengthSquared() const { + return fX * fX + fY * fY; + } + + void normalize() { + double inverseLength = 1 / this->length(); + fX *= inverseLength; + fY *= inverseLength; + } +}; + +struct SkDPoint { + double fX; + double fY; + + void set(const SkPoint& pt) { + fX = pt.fX; + fY = pt.fY; + } + + friend SkDVector operator-(const SkDPoint& a, const SkDPoint& b); + + friend bool operator==(const SkDPoint& a, const SkDPoint& b) { + return a.fX == b.fX && a.fY == b.fY; + } + + friend bool operator!=(const SkDPoint& a, const SkDPoint& b) { + return a.fX != b.fX || a.fY != b.fY; + } + + void operator=(const SkPoint& pt) { + fX = pt.fX; + fY = pt.fY; + } + + // only used by testing + void operator+=(const SkDVector& v) { + fX += v.fX; + fY += v.fY; + } + + // only used by testing + void operator-=(const SkDVector& v) { + fX -= v.fX; + fY -= v.fY; + } + + // only used by testing + SkDPoint operator+(const SkDVector& v) { + SkDPoint result = *this; + result += v; + return result; + } + + // only used by testing + SkDPoint operator-(const SkDVector& v) { + SkDPoint result = *this; + result -= v; + return result; + } + + // note: this can not be implemented with + // return approximately_equal(a.fY, fY) && approximately_equal(a.fX, fX); + // because that will not take the magnitude of the values into account + bool approximatelyDEqual(const SkDPoint& a) const { + if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { + return true; + } + if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) { + return false; + } + double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); + double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); + largest = SkTMax(largest, -tiniest); + return AlmostDequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? + } + + bool approximatelyDEqual(const SkPoint& a) const { + SkDPoint dA; + dA.set(a); + return approximatelyDEqual(dA); + } + + bool approximatelyEqual(const SkDPoint& a) const { + if (approximately_equal(fX, a.fX) && approximately_equal(fY, a.fY)) { + return true; + } + if (!RoughlyEqualUlps(fX, a.fX) || !RoughlyEqualUlps(fY, a.fY)) { + return false; + } + double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); + double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); + largest = SkTMax(largest, -tiniest); + return AlmostPequalUlps(largest, largest + dist); // is the dist within ULPS tolerance? + } + + bool approximatelyEqual(const SkPoint& a) const { + SkDPoint dA; + dA.set(a); + return approximatelyEqual(dA); + } + + static bool ApproximatelyEqual(const SkPoint& a, const SkPoint& b) { + if (approximately_equal(a.fX, b.fX) && approximately_equal(a.fY, b.fY)) { + return true; + } + if (!RoughlyEqualUlps(a.fX, b.fX) || !RoughlyEqualUlps(a.fY, b.fY)) { + return false; + } + SkDPoint dA, dB; + dA.set(a); + dB.set(b); + double dist = dA.distance(dB); // OPTIMIZATION: can we compare against distSq instead ? + float tiniest = SkTMin(SkTMin(SkTMin(a.fX, b.fX), a.fY), b.fY); + float largest = SkTMax(SkTMax(SkTMax(a.fX, b.fX), a.fY), b.fY); + largest = SkTMax(largest, -tiniest); + return AlmostDequalUlps((double) largest, largest + dist); // is dist within ULPS tolerance? + } + + // only used by testing + bool approximatelyZero() const { + return approximately_zero(fX) && approximately_zero(fY); + } + + SkPoint asSkPoint() const { + SkPoint pt = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)}; + return pt; + } + + double distance(const SkDPoint& a) const { + SkDVector temp = *this - a; + return temp.length(); + } + + double distanceSquared(const SkDPoint& a) const { + SkDVector temp = *this - a; + return temp.lengthSquared(); + } + + static SkDPoint Mid(const SkDPoint& a, const SkDPoint& b) { + SkDPoint result; + result.fX = (a.fX + b.fX) / 2; + result.fY = (a.fY + b.fY) / 2; + return result; + } + + bool roughlyEqual(const SkDPoint& a) const { + if (roughly_equal(fX, a.fX) && roughly_equal(fY, a.fY)) { + return true; + } + double dist = distance(a); // OPTIMIZATION: can we compare against distSq instead ? + double tiniest = SkTMin(SkTMin(SkTMin(fX, a.fX), fY), a.fY); + double largest = SkTMax(SkTMax(SkTMax(fX, a.fX), fY), a.fY); + largest = SkTMax(largest, -tiniest); + return RoughlyEqualUlps(largest, largest + dist); // is the dist within ULPS tolerance? + } + + static bool RoughlyEqual(const SkPoint& a, const SkPoint& b) { + if (!RoughlyEqualUlps(a.fX, b.fX) && !RoughlyEqualUlps(a.fY, b.fY)) { + return false; + } + SkDPoint dA, dB; + dA.set(a); + dB.set(b); + double dist = dA.distance(dB); // OPTIMIZATION: can we compare against distSq instead ? + float tiniest = SkTMin(SkTMin(SkTMin(a.fX, b.fX), a.fY), b.fY); + float largest = SkTMax(SkTMax(SkTMax(a.fX, b.fX), a.fY), b.fY); + largest = SkTMax(largest, -tiniest); + return RoughlyEqualUlps((double) largest, largest + dist); // is dist within ULPS tolerance? + } + + // very light weight check, should only be used for inequality check + static bool WayRoughlyEqual(const SkPoint& a, const SkPoint& b) { + float largestNumber = SkTMax(SkTAbs(a.fX), SkTMax(SkTAbs(a.fY), + SkTMax(SkTAbs(b.fX), SkTAbs(b.fY)))); + SkVector diffs = a - b; + float largestDiff = SkTMax(diffs.fX, diffs.fY); + return roughly_zero_when_compared_to(largestDiff, largestNumber); + } + + // utilities callable by the user from the debugger when the implementation code is linked in + void dump() const; + static void Dump(const SkPoint& pt); + static void DumpHex(const SkPoint& pt); +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsQuad.cpp b/gfx/skia/skia/src/pathops/SkPathOpsQuad.cpp new file mode 100644 index 000000000..ab1ba05c5 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsQuad.cpp @@ -0,0 +1,390 @@ +/* + * 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 "SkIntersections.h" +#include "SkLineParameters.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsCurve.h" +#include "SkPathOpsQuad.h" + +// from blackpawn.com/texts/pointinpoly +static bool pointInTriangle(const SkDPoint fPts[3], const SkDPoint& test) { + SkDVector v0 = fPts[2] - fPts[0]; + SkDVector v1 = fPts[1] - fPts[0]; + SkDVector v2 = test - fPts[0]; + double dot00 = v0.dot(v0); + double dot01 = v0.dot(v1); + double dot02 = v0.dot(v2); + double dot11 = v1.dot(v1); + double dot12 = v1.dot(v2); + // Compute barycentric coordinates + double invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + double u = (dot11 * dot02 - dot01 * dot12) * invDenom; + double v = (dot00 * dot12 - dot01 * dot02) * invDenom; + // Check if point is in triangle + return u >= 0 && v >= 0 && u + v < 1; +} + +static bool matchesEnd(const SkDPoint fPts[3], const SkDPoint& test) { + return fPts[0] == test || fPts[2] == test; +} + +/* started with at_most_end_pts_in_common from SkDQuadIntersection.cpp */ +// Do a quick reject by rotating all points relative to a line formed by +// a pair of one quad's points. If the 2nd quad's points +// are on the line or on the opposite side from the 1st quad's 'odd man', the +// curves at most intersect at the endpoints. +/* if returning true, check contains true if quad's hull collapsed, making the cubic linear + if returning false, check contains true if the the quad pair have only the end point in common +*/ +bool SkDQuad::hullIntersects(const SkDQuad& q2, bool* isLinear) const { + bool linear = true; + for (int oddMan = 0; oddMan < kPointCount; ++oddMan) { + const SkDPoint* endPt[2]; + this->otherPts(oddMan, endPt); + double origX = endPt[0]->fX; + double origY = endPt[0]->fY; + double adj = endPt[1]->fX - origX; + double opp = endPt[1]->fY - origY; + double sign = (fPts[oddMan].fY - origY) * adj - (fPts[oddMan].fX - origX) * opp; + if (approximately_zero(sign)) { + continue; + } + linear = false; + bool foundOutlier = false; + for (int n = 0; n < kPointCount; ++n) { + double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; + if (test * sign > 0 && !precisely_zero(test)) { + foundOutlier = true; + break; + } + } + if (!foundOutlier) { + return false; + } + } + if (linear && !matchesEnd(fPts, q2.fPts[0]) && !matchesEnd(fPts, q2.fPts[2])) { + // if the end point of the opposite quad is inside the hull that is nearly a line, + // then representing the quad as a line may cause the intersection to be missed. + // Check to see if the endpoint is in the triangle. + if (pointInTriangle(fPts, q2.fPts[0]) || pointInTriangle(fPts, q2.fPts[2])) { + linear = false; + } + } + *isLinear = linear; + return true; +} + +bool SkDQuad::hullIntersects(const SkDConic& conic, bool* isLinear) const { + return conic.hullIntersects(*this, isLinear); +} + +bool SkDQuad::hullIntersects(const SkDCubic& cubic, bool* isLinear) const { + return cubic.hullIntersects(*this, isLinear); +} + +/* bit twiddling for finding the off curve index (x&~m is the pair in [0,1,2] excluding oddMan) +oddMan opp x=oddMan^opp x=x-oddMan m=x>>2 x&~m + 0 1 1 1 0 1 + 2 2 2 0 2 + 1 1 0 -1 -1 0 + 2 3 2 0 2 + 2 1 3 1 0 1 + 2 0 -2 -1 0 +*/ +void SkDQuad::otherPts(int oddMan, const SkDPoint* endPt[2]) const { + for (int opp = 1; opp < kPointCount; ++opp) { + int end = (oddMan ^ opp) - oddMan; // choose a value not equal to oddMan + end &= ~(end >> 2); // if the value went negative, set it to zero + endPt[opp - 1] = &fPts[end]; + } +} + +int SkDQuad::AddValidTs(double s[], int realRoots, double* t) { + int foundRoots = 0; + for (int index = 0; index < realRoots; ++index) { + double tValue = s[index]; + if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) { + if (approximately_less_than_zero(tValue)) { + tValue = 0; + } else if (approximately_greater_than_one(tValue)) { + tValue = 1; + } + for (int idx2 = 0; idx2 < foundRoots; ++idx2) { + if (approximately_equal(t[idx2], tValue)) { + goto nextRoot; + } + } + t[foundRoots++] = tValue; + } +nextRoot: + {} + } + return foundRoots; +} + +// note: caller expects multiple results to be sorted smaller first +// note: http://en.wikipedia.org/wiki/Loss_of_significance has an interesting +// analysis of the quadratic equation, suggesting why the following looks at +// the sign of B -- and further suggesting that the greatest loss of precision +// is in b squared less two a c +int SkDQuad::RootsValidT(double A, double B, double C, double t[2]) { + double s[2]; + int realRoots = RootsReal(A, B, C, s); + int foundRoots = AddValidTs(s, realRoots, t); + return foundRoots; +} + +/* +Numeric Solutions (5.6) suggests to solve the quadratic by computing + Q = -1/2(B + sgn(B)Sqrt(B^2 - 4 A C)) +and using the roots + t1 = Q / A + t2 = C / Q +*/ +// this does not discard real roots <= 0 or >= 1 +int SkDQuad::RootsReal(const double A, const double B, const double C, double s[2]) { + const double p = B / (2 * A); + const double q = C / A; + if (!A || (approximately_zero(A) && (approximately_zero_inverse(p) + || approximately_zero_inverse(q)))) { + if (approximately_zero(B)) { + s[0] = 0; + return C == 0; + } + s[0] = -C / B; + return 1; + } + /* normal form: x^2 + px + q = 0 */ + const double p2 = p * p; + if (!AlmostDequalUlps(p2, q) && p2 < q) { + return 0; + } + double sqrt_D = 0; + if (p2 > q) { + sqrt_D = sqrt(p2 - q); + } + s[0] = sqrt_D - p; + s[1] = -sqrt_D - p; + return 1 + !AlmostDequalUlps(s[0], s[1]); +} + +bool SkDQuad::isLinear(int startIndex, int endIndex) const { + SkLineParameters lineParameters; + lineParameters.quadEndPoints(*this, startIndex, endIndex); + // FIXME: maybe it's possible to avoid this and compare non-normalized + lineParameters.normalize(); + double distance = lineParameters.controlPtDistance(*this); + double tiniest = SkTMin(SkTMin(SkTMin(SkTMin(SkTMin(fPts[0].fX, fPts[0].fY), + fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY); + double largest = SkTMax(SkTMax(SkTMax(SkTMax(SkTMax(fPts[0].fX, fPts[0].fY), + fPts[1].fX), fPts[1].fY), fPts[2].fX), fPts[2].fY); + largest = SkTMax(largest, -tiniest); + return approximately_zero_when_compared_to(distance, largest); +} + +SkDVector SkDQuad::dxdyAtT(double t) const { + double a = t - 1; + double b = 1 - 2 * t; + double c = t; + SkDVector result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY }; + if (result.fX == 0 && result.fY == 0) { + if (zero_or_one(t)) { + result = fPts[2] - fPts[0]; + } else { + // incomplete + SkDebugf("!q"); + } + } + return result; +} + +// OPTIMIZE: assert if caller passes in t == 0 / t == 1 ? +SkDPoint SkDQuad::ptAtT(double t) const { + if (0 == t) { + return fPts[0]; + } + if (1 == t) { + return fPts[2]; + } + double one_t = 1 - t; + double a = one_t * one_t; + double b = 2 * one_t * t; + double c = t * t; + SkDPoint result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY }; + return result; +} + +static double interp_quad_coords(const double* src, double t) { + if (0 == t) { + return src[0]; + } + if (1 == t) { + return src[4]; + } + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double abc = SkDInterp(ab, bc, t); + return abc; +} + +bool SkDQuad::monotonicInX() const { + return between(fPts[0].fX, fPts[1].fX, fPts[2].fX); +} + +bool SkDQuad::monotonicInY() const { + return between(fPts[0].fY, fPts[1].fY, fPts[2].fY); +} + +/* +Given a quadratic q, t1, and t2, find a small quadratic segment. + +The new quadratic is defined by A, B, and C, where + A = c[0]*(1 - t1)*(1 - t1) + 2*c[1]*t1*(1 - t1) + c[2]*t1*t1 + C = c[3]*(1 - t1)*(1 - t1) + 2*c[2]*t1*(1 - t1) + c[1]*t1*t1 + +To find B, compute the point halfway between t1 and t2: + +q(at (t1 + t2)/2) == D + +Next, compute where D must be if we know the value of B: + +_12 = A/2 + B/2 +12_ = B/2 + C/2 +123 = A/4 + B/2 + C/4 + = D + +Group the known values on one side: + +B = D*2 - A/2 - C/2 +*/ + +// OPTIMIZE? : special case t1 = 1 && t2 = 0 +SkDQuad SkDQuad::subDivide(double t1, double t2) const { + if (0 == t1 && 1 == t2) { + return *this; + } + SkDQuad dst; + double ax = dst[0].fX = interp_quad_coords(&fPts[0].fX, t1); + double ay = dst[0].fY = interp_quad_coords(&fPts[0].fY, t1); + double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2); + double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2); + double cx = dst[2].fX = interp_quad_coords(&fPts[0].fX, t2); + double cy = dst[2].fY = interp_quad_coords(&fPts[0].fY, t2); + /* bx = */ dst[1].fX = 2 * dx - (ax + cx) / 2; + /* by = */ dst[1].fY = 2 * dy - (ay + cy) / 2; + return dst; +} + +void SkDQuad::align(int endIndex, SkDPoint* dstPt) const { + if (fPts[endIndex].fX == fPts[1].fX) { + dstPt->fX = fPts[endIndex].fX; + } + if (fPts[endIndex].fY == fPts[1].fY) { + dstPt->fY = fPts[endIndex].fY; + } +} + +SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const { + SkASSERT(t1 != t2); + SkDPoint b; + SkDQuad sub = subDivide(t1, t2); + SkDLine b0 = {{a, sub[1] + (a - sub[0])}}; + SkDLine b1 = {{c, sub[1] + (c - sub[2])}}; + SkIntersections i; + i.intersectRay(b0, b1); + if (i.used() == 1 && i[0][0] >= 0 && i[1][0] >= 0) { + b = i.pt(0); + } else { + SkASSERT(i.used() <= 2); + return SkDPoint::Mid(b0[1], b1[1]); + } + if (t1 == 0 || t2 == 0) { + align(0, &b); + } + if (t1 == 1 || t2 == 1) { + align(2, &b); + } + if (AlmostBequalUlps(b.fX, a.fX)) { + b.fX = a.fX; + } else if (AlmostBequalUlps(b.fX, c.fX)) { + b.fX = c.fX; + } + if (AlmostBequalUlps(b.fY, a.fY)) { + b.fY = a.fY; + } else if (AlmostBequalUlps(b.fY, c.fY)) { + b.fY = c.fY; + } + return b; +} + +/* classic one t subdivision */ +static void interp_quad_coords(const double* src, double* dst, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + dst[0] = src[0]; + dst[2] = ab; + dst[4] = SkDInterp(ab, bc, t); + dst[6] = bc; + dst[8] = src[4]; +} + +SkDQuadPair SkDQuad::chopAt(double t) const +{ + SkDQuadPair dst; + interp_quad_coords(&fPts[0].fX, &dst.pts[0].fX, t); + interp_quad_coords(&fPts[0].fY, &dst.pts[0].fY, t); + return dst; +} + +static int valid_unit_divide(double numer, double denom, double* ratio) +{ + if (numer < 0) { + numer = -numer; + denom = -denom; + } + if (denom == 0 || numer == 0 || numer >= denom) { + return 0; + } + double r = numer / denom; + if (r == 0) { // catch underflow if numer <<<< denom + return 0; + } + *ratio = r; + return 1; +} + +/** Quad'(t) = At + B, where + A = 2(a - 2b + c) + B = 2(b - a) + Solve for t, only if it fits between 0 < t < 1 +*/ +int SkDQuad::FindExtrema(const double src[], double tValue[1]) { + /* At + B == 0 + t = -B / A + */ + double a = src[0]; + double b = src[2]; + double c = src[4]; + return valid_unit_divide(a - b, a - b - b + c, tValue); +} + +/* Parameterization form, given A*t*t + 2*B*t*(1-t) + C*(1-t)*(1-t) + * + * a = A - 2*B + C + * b = 2*B - 2*C + * c = C + */ +void SkDQuad::SetABC(const double* quad, double* a, double* b, double* c) { + *a = quad[0]; // a = A + *b = 2 * quad[2]; // b = 2*B + *c = quad[4]; // c = C + *b -= *c; // b = 2*B - C + *a -= *b; // a = A - 2*B + C + *b -= *c; // b = 2*B - 2*C +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsQuad.h b/gfx/skia/skia/src/pathops/SkPathOpsQuad.h new file mode 100644 index 000000000..32cfe58ec --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsQuad.h @@ -0,0 +1,113 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsQuad_DEFINED +#define SkPathOpsQuad_DEFINED + +#include "SkPathOpsPoint.h" + +struct SkOpCurve; + +struct SkDQuadPair { + const SkDQuad& first() const { return (const SkDQuad&) pts[0]; } + const SkDQuad& second() const { return (const SkDQuad&) pts[2]; } + SkDPoint pts[5]; +}; + +struct SkDQuad { + static const int kPointCount = 3; + static const int kPointLast = kPointCount - 1; + static const int kMaxIntersections = 4; + + SkDPoint fPts[kPointCount]; + + bool collapsed() const { + return fPts[0].approximatelyEqual(fPts[1]) && fPts[0].approximatelyEqual(fPts[2]); + } + + bool controlsInside() const { + SkDVector v01 = fPts[0] - fPts[1]; + SkDVector v02 = fPts[0] - fPts[2]; + SkDVector v12 = fPts[1] - fPts[2]; + return v02.dot(v01) > 0 && v02.dot(v12) > 0; + } + + void debugInit() { + sk_bzero(fPts, sizeof(fPts)); + } + + SkDQuad flip() const { + SkDQuad result = {{fPts[2], fPts[1], fPts[0]}}; + return result; + } + + static bool IsConic() { return false; } + + const SkDQuad& set(const SkPoint pts[kPointCount]) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + fPts[2] = pts[2]; + return *this; + } + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < kPointCount); return fPts[n]; } + + static int AddValidTs(double s[], int realRoots, double* t); + void align(int endIndex, SkDPoint* dstPt) const; + SkDQuadPair chopAt(double t) const; + SkDVector dxdyAtT(double t) const; + static int FindExtrema(const double src[], double tValue[1]); + + /** + * Return the number of valid roots (0 < root < 1) for this cubic intersecting the + * specified horizontal line. + */ + int horizontalIntersect(double yIntercept, double roots[2]) const; + + bool hullIntersects(const SkDQuad& , bool* isLinear) const; + bool hullIntersects(const SkDConic& , bool* isLinear) const; + bool hullIntersects(const SkDCubic& , bool* isLinear) const; + bool isLinear(int startIndex, int endIndex) const; + bool monotonicInX() const; + bool monotonicInY() const; + void otherPts(int oddMan, const SkDPoint* endPt[2]) const; + SkDPoint ptAtT(double t) const; + static int RootsReal(double A, double B, double C, double t[2]); + static int RootsValidT(const double A, const double B, const double C, double s[2]); + static void SetABC(const double* quad, double* a, double* b, double* c); + SkDQuad subDivide(double t1, double t2) const; + static SkDQuad SubDivide(const SkPoint a[kPointCount], double t1, double t2) { + SkDQuad quad; + quad.set(a); + return quad.subDivide(t1, t2); + } + SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const; + static SkDPoint SubDivide(const SkPoint pts[kPointCount], const SkDPoint& a, const SkDPoint& c, + double t1, double t2) { + SkDQuad quad; + quad.set(pts); + return quad.subDivide(a, c, t1, t2); + } + + /** + * Return the number of valid roots (0 < root < 1) for this cubic intersecting the + * specified vertical line. + */ + int verticalIntersect(double xIntercept, double roots[2]) const; + + SkDCubic debugToCubic() const; + // utilities callable by the user from the debugger when the implementation code is linked in + void dump() const; + void dumpID(int id) const; + void dumpInner() const; + +private: +// static double Tangent(const double* quadratic, double t); // uncalled +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsRect.cpp b/gfx/skia/skia/src/pathops/SkPathOpsRect.cpp new file mode 100644 index 000000000..8c0115353 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsRect.cpp @@ -0,0 +1,62 @@ +/* + * 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 "SkPathOpsConic.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" +#include "SkPathOpsRect.h" + +void SkDRect::setBounds(const SkDQuad& curve, const SkDQuad& sub, double startT, double endT) { + set(sub[0]); + add(sub[2]); + double tValues[2]; + int roots = 0; + if (!sub.monotonicInX()) { + roots = SkDQuad::FindExtrema(&sub[0].fX, tValues); + } + if (!sub.monotonicInY()) { + roots += SkDQuad::FindExtrema(&sub[0].fY, &tValues[roots]); + } + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * tValues[index]; + add(curve.ptAtT(t)); + } +} + +void SkDRect::setBounds(const SkDConic& curve, const SkDConic& sub, double startT, double endT) { + set(sub[0]); + add(sub[2]); + double tValues[2]; + int roots = 0; + if (!sub.monotonicInX()) { + roots = SkDConic::FindExtrema(&sub[0].fX, sub.fWeight, tValues); + } + if (!sub.monotonicInY()) { + roots += SkDConic::FindExtrema(&sub[0].fY, sub.fWeight, &tValues[roots]); + } + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * tValues[index]; + add(curve.ptAtT(t)); + } +} + +void SkDRect::setBounds(const SkDCubic& curve, const SkDCubic& sub, double startT, double endT) { + set(sub[0]); + add(sub[3]); + double tValues[4]; + int roots = 0; + if (!sub.monotonicInX()) { + roots = SkDCubic::FindExtrema(&sub[0].fX, tValues); + } + if (!sub.monotonicInY()) { + roots += SkDCubic::FindExtrema(&sub[0].fY, &tValues[roots]); + } + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * tValues[index]; + add(curve.ptAtT(t)); + } +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsRect.h b/gfx/skia/skia/src/pathops/SkPathOpsRect.h new file mode 100644 index 000000000..d4e5f5489 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsRect.h @@ -0,0 +1,69 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsRect_DEFINED +#define SkPathOpsRect_DEFINED + +#include "SkPathOpsPoint.h" + +struct SkDRect { + double fLeft, fTop, fRight, fBottom; + + void add(const SkDPoint& pt) { + fLeft = SkTMin(fLeft, pt.fX); + fTop = SkTMin(fTop, pt.fY); + fRight = SkTMax(fRight, pt.fX); + fBottom = SkTMax(fBottom, pt.fY); + } + + bool contains(const SkDPoint& pt) const { + return approximately_between(fLeft, pt.fX, fRight) + && approximately_between(fTop, pt.fY, fBottom); + } + + void debugInit(); + + bool intersects(const SkDRect& r) const { + SkASSERT(fLeft <= fRight); + SkASSERT(fTop <= fBottom); + SkASSERT(r.fLeft <= r.fRight); + SkASSERT(r.fTop <= r.fBottom); + return r.fLeft <= fRight && fLeft <= r.fRight && r.fTop <= fBottom && fTop <= r.fBottom; + } + + void set(const SkDPoint& pt) { + fLeft = fRight = pt.fX; + fTop = fBottom = pt.fY; + } + + double width() const { + return fRight - fLeft; + } + + double height() const { + return fBottom - fTop; + } + + void setBounds(const SkDConic& curve) { + setBounds(curve, curve, 0, 1); + } + + void setBounds(const SkDConic& curve, const SkDConic& sub, double tStart, double tEnd); + + void setBounds(const SkDCubic& curve) { + setBounds(curve, curve, 0, 1); + } + + void setBounds(const SkDCubic& curve, const SkDCubic& sub, double tStart, double tEnd); + + void setBounds(const SkDQuad& curve) { + setBounds(curve, curve, 0, 1); + } + + void setBounds(const SkDQuad& curve, const SkDQuad& sub, double tStart, double tEnd); +}; + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsSimplify.cpp b/gfx/skia/skia/src/pathops/SkPathOpsSimplify.cpp new file mode 100644 index 000000000..f9f8f5c71 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsSimplify.cpp @@ -0,0 +1,222 @@ +/* + * 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 "SkAddIntersections.h" +#include "SkOpCoincidence.h" +#include "SkOpEdgeBuilder.h" +#include "SkPathOpsCommon.h" +#include "SkPathWriter.h" + +static bool bridgeWinding(SkOpContourHead* contourList, SkPathWriter* simple) { + bool unsortable = false; + do { + SkOpSpan* span = FindSortableTop(contourList); + if (!span) { + break; + } + SkOpSegment* current = span->segment(); + SkOpSpanBase* start = span->next(); + SkOpSpanBase* end = span; + SkTDArray<SkOpSpanBase*> chase; + do { + if (current->activeWinding(start, end)) { + do { + if (!unsortable && current->done()) { + break; + } + SkASSERT(unsortable || !current->done()); + SkOpSpanBase* nextStart = start; + SkOpSpanBase* nextEnd = end; + SkOpSegment* next = current->findNextWinding(&chase, &nextStart, &nextEnd, + &unsortable); + if (!next) { + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), start->pt().fX, start->pt().fY, + end->pt().fX, end->pt().fY); + #endif + if (!current->addCurveTo(start, end, simple)) { + return false; + } + current = next; + start = nextStart; + end = nextEnd; + } while (!simple->isClosed() && (!unsortable || !start->starter(end)->done())); + if (current->activeWinding(start, end) && !simple->isClosed()) { + SkOpSpan* spanStart = start->starter(end); + if (!spanStart->done()) { + if (!current->addCurveTo(start, end, simple)) { + return false; + } + current->markDone(spanStart); + } + } + simple->finishContour(); + } else { + SkOpSpanBase* last = current->markAndChaseDone(start, end); + if (last && !last->chased()) { + last->setChased(true); + SkASSERT(!SkPathOpsDebug::ChaseContains(chase, last)); + *chase.append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append id=%d", __FUNCTION__, last->segment()->debugID()); + if (!last->final()) { + SkDebugf(" windSum=%d", last->upCast()->windSum()); + } + SkDebugf("\n"); +#endif + } + } + current = FindChase(&chase, &start, &end); + SkPathOpsDebug::ShowActiveSpans(contourList); + if (!current) { + break; + } + } while (true); + } while (true); + return true; +} + +// returns true if all edges were processed +static bool bridgeXor(SkOpContourHead* contourList, SkPathWriter* simple) { + SkOpSegment* current; + SkOpSpanBase* start; + SkOpSpanBase* end; + bool unsortable = false; + while ((current = FindUndone(contourList, &start, &end))) { + do { + if (!unsortable && current->done()) { + SkPathOpsDebug::ShowActiveSpans(contourList); + } + SkASSERT(unsortable || !current->done()); + SkOpSpanBase* nextStart = start; + SkOpSpanBase* nextEnd = end; + SkOpSegment* next = current->findNextXor(&nextStart, &nextEnd, &unsortable); + if (!next) { + if (!unsortable && simple->hasMove() + && current->verb() != SkPath::kLine_Verb + && !simple->isClosed()) { + if (!current->addCurveTo(start, end, simple)) { + return false; + } + if (!simple->isClosed()) { + SkPathOpsDebug::ShowActiveSpans(contourList); + } + } + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), start->pt().fX, start->pt().fY, + end->pt().fX, end->pt().fY); + #endif + if (!current->addCurveTo(start, end, simple)) { + return false; + } + current = next; + start = nextStart; + end = nextEnd; + } while (!simple->isClosed() && (!unsortable || !start->starter(end)->done())); + if (!simple->isClosed()) { + SkASSERT(unsortable); + SkOpSpan* spanStart = start->starter(end); + if (!spanStart->done()) { + if (!current->addCurveTo(start, end, simple)) { + return false; + } + current->markDone(spanStart); + } + } + simple->finishContour(); + SkPathOpsDebug::ShowActiveSpans(contourList); + } + return true; +} + +// FIXME : add this as a member of SkPath +bool SimplifyDebug(const SkPath& path, SkPath* result + SkDEBUGPARAMS(bool skipAssert) SkDEBUGPARAMS(const char* testName)) { + // returns 1 for evenodd, -1 for winding, regardless of inverse-ness + SkPath::FillType fillType = path.isInverseFillType() ? SkPath::kInverseEvenOdd_FillType + : SkPath::kEvenOdd_FillType; + if (path.isConvex()) { + if (result != &path) { + *result = path; + } + result->setFillType(fillType); + return true; + } + // turn path into list of segments + SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune + SkOpContour contour; + SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour); + SkOpGlobalState globalState(contourList, &allocator + SkDEBUGPARAMS(skipAssert) SkDEBUGPARAMS(testName)); + SkOpCoincidence coincidence(&globalState); + SkScalar scaleFactor = ScaleFactor(path); + SkPath scaledPath; + const SkPath* workingPath; + if (scaleFactor > SK_Scalar1) { + ScalePath(path, 1.f / scaleFactor, &scaledPath); + workingPath = &scaledPath; + } else { + workingPath = &path; + } +#if DEBUG_SORT + SkPathOpsDebug::gSortCount = SkPathOpsDebug::gSortCountDefault; +#endif + SkOpEdgeBuilder builder(*workingPath, contourList, &globalState); + if (!builder.finish()) { + return false; + } +#if DEBUG_DUMP_SEGMENTS + contour.dumpSegments(); +#endif + if (!SortContourList(&contourList, false, false)) { + result->reset(); + result->setFillType(fillType); + return true; + } + // find all intersections between segments + SkOpContour* current = contourList; + do { + SkOpContour* next = current; + while (AddIntersectTs(current, next, &coincidence) + && (next = next->next())); + } while ((current = current->next())); +#if DEBUG_VALIDATE + globalState.setPhase(SkOpPhase::kWalking); +#endif + bool success = HandleCoincidence(contourList, &coincidence); +#if DEBUG_COIN + globalState.debugAddToGlobalCoinDicts(); +#endif + if (!success) { + return false; + } +#if DEBUG_DUMP_ALIGNMENT + contour.dumpSegments("aligned"); +#endif + // construct closed contours + result->reset(); + result->setFillType(fillType); + SkPathWriter wrapper(*result); + if (builder.xorMask() == kWinding_PathOpsMask ? !bridgeWinding(contourList, &wrapper) + : !bridgeXor(contourList, &wrapper)) { + return false; + } + wrapper.assemble(); // if some edges could not be resolved, assemble remaining + if (scaleFactor > 1) { + ScalePath(*result, scaleFactor, result); + } + return true; +} + +bool Simplify(const SkPath& path, SkPath* result) { + return SimplifyDebug(path, result SkDEBUGPARAMS(true) SkDEBUGPARAMS(nullptr)); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTSect.cpp b/gfx/skia/skia/src/pathops/SkPathOpsTSect.cpp new file mode 100644 index 000000000..3e7817ca9 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTSect.cpp @@ -0,0 +1,62 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPathOpsTSect.h" + +int SkIntersections::intersect(const SkDQuad& quad1, const SkDQuad& quad2) { + SkTSect<SkDQuad, SkDQuad> sect1(quad1 + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect<SkDQuad, SkDQuad> sect2(quad2 + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect<SkDQuad, SkDQuad>::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDConic& conic, const SkDQuad& quad) { + SkTSect<SkDConic, SkDQuad> sect1(conic + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect<SkDQuad, SkDConic> sect2(quad + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect<SkDConic, SkDQuad>::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDConic& conic1, const SkDConic& conic2) { + SkTSect<SkDConic, SkDConic> sect1(conic1 + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect<SkDConic, SkDConic> sect2(conic2 + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect<SkDConic, SkDConic>::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDCubic& cubic, const SkDQuad& quad) { + SkTSect<SkDCubic, SkDQuad> sect1(cubic + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect<SkDQuad, SkDCubic> sect2(quad + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect<SkDCubic, SkDQuad>::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDCubic& cubic, const SkDConic& conic) { + SkTSect<SkDCubic, SkDConic> sect1(cubic + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect<SkDConic, SkDCubic> sect2(conic + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect<SkDCubic, SkDConic>::BinarySearch(§1, §2, this); + return used(); +} + +int SkIntersections::intersect(const SkDCubic& cubic1, const SkDCubic& cubic2) { + SkTSect<SkDCubic, SkDCubic> sect1(cubic1 + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(1)); + SkTSect<SkDCubic, SkDCubic> sect2(cubic2 + SkDEBUGPARAMS(debugGlobalState()) PATH_OPS_DEBUG_T_SECT_PARAMS(2)); + SkTSect<SkDCubic, SkDCubic>::BinarySearch(§1, §2, this); + return used(); +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTSect.h b/gfx/skia/skia/src/pathops/SkPathOpsTSect.h new file mode 100644 index 000000000..a04a4e442 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTSect.h @@ -0,0 +1,2365 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsTSect_DEFINED +#define SkPathOpsTSect_DEFINED + +#include "SkChunkAlloc.h" +#include "SkPathOpsBounds.h" +#include "SkPathOpsRect.h" +#include "SkIntersections.h" +#include "SkTSort.h" + +#ifdef SK_DEBUG +typedef uint8_t SkOpDebugBool; +#else +typedef bool SkOpDebugBool; +#endif + +/* TCurve and OppCurve are one of { SkDQuadratic, SkDConic, SkDCubic } */ +template<typename TCurve, typename OppCurve> +class SkTCoincident { +public: + SkTCoincident() { + this->init(); + } + + void debugInit() { +#ifdef SK_DEBUG + this->fPerpPt.fX = this->fPerpPt.fY = SK_ScalarNaN; + this->fPerpT = SK_ScalarNaN; + this->fMatch = 0xFF; +#endif + } + + char dumpIsCoincidentStr() const; + void dump() const; + + bool isMatch() const { + SkASSERT(!!fMatch == fMatch); + return SkToBool(fMatch); + } + + void init() { + fPerpT = -1; + fMatch = false; + fPerpPt.fX = fPerpPt.fY = SK_ScalarNaN; + } + + void markCoincident() { + if (!fMatch) { + fPerpT = -1; + } + fMatch = true; + } + + const SkDPoint& perpPt() const { + return fPerpPt; + } + + double perpT() const { + return fPerpT; + } + + void setPerp(const TCurve& c1, double t, const SkDPoint& cPt, const OppCurve& ); + +private: + SkDPoint fPerpPt; + double fPerpT; // perpendicular intersection on opposite curve + SkOpDebugBool fMatch; +}; + +template<typename TCurve, typename OppCurve> class SkTSect; +template<typename TCurve, typename OppCurve> class SkTSpan; + +template<typename TCurve, typename OppCurve> +struct SkTSpanBounded { + SkTSpan<TCurve, OppCurve>* fBounded; + SkTSpanBounded* fNext; +}; + +/* Curve is either TCurve or SkDCubic */ +template<typename TCurve, typename OppCurve> +class SkTSpan { +public: + void addBounded(SkTSpan<OppCurve, TCurve>* , SkChunkAlloc* ); + double closestBoundedT(const SkDPoint& pt) const; + bool contains(double t) const; + + void debugInit() { + TCurve dummy; + dummy.debugInit(); + init(dummy); + initBounds(dummy); + fCoinStart.init(); + fCoinEnd.init(); + } + + const SkTSect<OppCurve, TCurve>* debugOpp() const; + +#ifdef SK_DEBUG + void debugSetGlobalState(SkOpGlobalState* state) { + fDebugGlobalState = state; + } +#endif + + const SkTSpan* debugSpan(int ) const; + const SkTSpan* debugT(double t) const; +#ifdef SK_DEBUG + bool debugIsBefore(const SkTSpan* span) const; +#endif + void dump() const; + void dumpAll() const; + void dumpBounded(int id) const; + void dumpBounds() const; + void dumpCoin() const; + + double endT() const { + return fEndT; + } + + SkTSpan<OppCurve, TCurve>* findOppSpan(const SkTSpan<OppCurve, TCurve>* opp) const; + + SkTSpan<OppCurve, TCurve>* findOppT(double t) const { + SkTSpan<OppCurve, TCurve>* result = oppT(t); + SkOPASSERT(result); + return result; + } + + SkDEBUGCODE(SkOpGlobalState* globalState() const { return fDebugGlobalState; }) + + bool hasOppT(double t) const { + return SkToBool(oppT(t)); + } + + int hullsIntersect(SkTSpan<OppCurve, TCurve>* span, bool* start, bool* oppStart); + void init(const TCurve& ); + void initBounds(const TCurve& ); + + bool isBounded() const { + return fBounded != nullptr; + } + + bool linearsIntersect(SkTSpan<OppCurve, TCurve>* span); + double linearT(const SkDPoint& ) const; + + void markCoincident() { + fCoinStart.markCoincident(); + fCoinEnd.markCoincident(); + } + + const SkTSpan* next() const { + return fNext; + } + + bool onlyEndPointsInCommon(const SkTSpan<OppCurve, TCurve>* opp, bool* start, + bool* oppStart, bool* ptsInCommon); + + const TCurve& part() const { + return fPart; + } + + bool removeAllBounded(); + bool removeBounded(const SkTSpan<OppCurve, TCurve>* opp); + + void reset() { + fBounded = nullptr; + } + + void resetBounds(const TCurve& curve) { + fIsLinear = fIsLine = false; + initBounds(curve); + } + + bool split(SkTSpan* work, SkChunkAlloc* heap) { + return splitAt(work, (work->fStartT + work->fEndT) * 0.5, heap); + } + + bool splitAt(SkTSpan* work, double t, SkChunkAlloc* heap); + + double startT() const { + return fStartT; + } + +private: + + // implementation is for testing only + int debugID() const { + return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1); + } + + void dumpID() const; + + int hullCheck(const SkTSpan<OppCurve, TCurve>* opp, bool* start, bool* oppStart); + int linearIntersects(const OppCurve& ) const; + SkTSpan<OppCurve, TCurve>* oppT(double t) const; + + void validate() const; + void validateBounded() const; + void validatePerpT(double oppT) const; + void validatePerpPt(double t, const SkDPoint& ) const; + + TCurve fPart; + SkTCoincident<TCurve, OppCurve> fCoinStart; + SkTCoincident<TCurve, OppCurve> fCoinEnd; + SkTSpanBounded<OppCurve, TCurve>* fBounded; + SkTSpan* fPrev; + SkTSpan* fNext; + SkDRect fBounds; + double fStartT; + double fEndT; + double fBoundsMax; + SkOpDebugBool fCollapsed; + SkOpDebugBool fHasPerp; + SkOpDebugBool fIsLinear; + SkOpDebugBool fIsLine; + SkOpDebugBool fDeleted; + SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); + SkDEBUGCODE(SkTSect<TCurve, OppCurve>* fDebugSect); + PATH_OPS_DEBUG_T_SECT_CODE(int fID); + friend class SkTSect<TCurve, OppCurve>; + friend class SkTSect<OppCurve, TCurve>; + friend class SkTSpan<OppCurve, TCurve>; +}; + +template<typename TCurve, typename OppCurve> +class SkTSect { +public: + SkTSect(const TCurve& c SkDEBUGPARAMS(SkOpGlobalState* ) PATH_OPS_DEBUG_T_SECT_PARAMS(int id)); + static void BinarySearch(SkTSect* sect1, SkTSect<OppCurve, TCurve>* sect2, + SkIntersections* intersections); + + SkDEBUGCODE(SkOpGlobalState* globalState() { return fDebugGlobalState; }) + // for testing only + bool debugHasBounded(const SkTSpan<OppCurve, TCurve>* ) const; + + const SkTSect<OppCurve, TCurve>* debugOpp() const { + return SkDEBUGRELEASE(fOppSect, nullptr); + } + + const SkTSpan<TCurve, OppCurve>* debugSpan(int id) const; + const SkTSpan<TCurve, OppCurve>* debugT(double t) const; + void dump() const; + void dumpBoth(SkTSect<OppCurve, TCurve>* ) const; + void dumpBounded(int id) const; + void dumpBounds() const; + void dumpCoin() const; + void dumpCoinCurves() const; + void dumpCurves() const; + +private: + enum { + kZeroS1Set = 1, + kOneS1Set = 2, + kZeroS2Set = 4, + kOneS2Set = 8 + }; + + SkTSpan<TCurve, OppCurve>* addFollowing(SkTSpan<TCurve, OppCurve>* prior); + void addForPerp(SkTSpan<OppCurve, TCurve>* span, double t); + SkTSpan<TCurve, OppCurve>* addOne(); + + SkTSpan<TCurve, OppCurve>* addSplitAt(SkTSpan<TCurve, OppCurve>* span, double t) { + SkTSpan<TCurve, OppCurve>* result = this->addOne(); + SkDEBUGCODE(result->debugSetGlobalState(this->globalState())); + result->splitAt(span, t, &fHeap); + result->initBounds(fCurve); + span->initBounds(fCurve); + return result; + } + + bool binarySearchCoin(SkTSect<OppCurve, TCurve>* , double tStart, double tStep, double* t, + double* oppT); + SkTSpan<TCurve, OppCurve>* boundsMax() const; + bool coincidentCheck(SkTSect<OppCurve, TCurve>* sect2); + void coincidentForce(SkTSect<OppCurve, TCurve>* sect2, double start1s, double start1e); + bool coincidentHasT(double t); + int collapsed() const; + void computePerpendiculars(SkTSect<OppCurve, TCurve>* sect2, SkTSpan<TCurve, OppCurve>* first, + SkTSpan<TCurve, OppCurve>* last); + int countConsecutiveSpans(SkTSpan<TCurve, OppCurve>* first, + SkTSpan<TCurve, OppCurve>** last) const; + + int debugID() const { + return PATH_OPS_DEBUG_T_SECT_RELEASE(fID, -1); + } + + bool deleteEmptySpans(); + void dumpCommon(const SkTSpan<TCurve, OppCurve>* ) const; + void dumpCommonCurves(const SkTSpan<TCurve, OppCurve>* ) const; + static int EndsEqual(const SkTSect* sect1, const SkTSect<OppCurve, TCurve>* sect2, + SkIntersections* ); + bool extractCoincident(SkTSect<OppCurve, TCurve>* sect2, SkTSpan<TCurve, OppCurve>* first, + SkTSpan<TCurve, OppCurve>* last, SkTSpan<TCurve, OppCurve>** result); + SkTSpan<TCurve, OppCurve>* findCoincidentRun(SkTSpan<TCurve, OppCurve>* first, + SkTSpan<TCurve, OppCurve>** lastPtr); + int intersects(SkTSpan<TCurve, OppCurve>* span, SkTSect<OppCurve, TCurve>* opp, + SkTSpan<OppCurve, TCurve>* oppSpan, int* oppResult); + bool isParallel(const SkDLine& thisLine, const SkTSect<OppCurve, TCurve>* opp) const; + int linesIntersect(SkTSpan<TCurve, OppCurve>* span, SkTSect<OppCurve, TCurve>* opp, + SkTSpan<OppCurve, TCurve>* oppSpan, SkIntersections* ); + bool markSpanGone(SkTSpan<TCurve, OppCurve>* span); + bool matchedDirection(double t, const SkTSect<OppCurve, TCurve>* sect2, double t2) const; + void matchedDirCheck(double t, const SkTSect<OppCurve, TCurve>* sect2, double t2, + bool* calcMatched, bool* oppMatched) const; + void mergeCoincidence(SkTSect<OppCurve, TCurve>* sect2); + SkTSpan<TCurve, OppCurve>* prev(SkTSpan<TCurve, OppCurve>* ) const; + void removeByPerpendicular(SkTSect<OppCurve, TCurve>* opp); + void recoverCollapsed(); + void removeCoincident(SkTSpan<TCurve, OppCurve>* span, bool isBetween); + void removeAllBut(const SkTSpan<OppCurve, TCurve>* keep, SkTSpan<TCurve, OppCurve>* span, + SkTSect<OppCurve, TCurve>* opp); + bool removeSpan(SkTSpan<TCurve, OppCurve>* span); + void removeSpanRange(SkTSpan<TCurve, OppCurve>* first, SkTSpan<TCurve, OppCurve>* last); + void removeSpans(SkTSpan<TCurve, OppCurve>* span, SkTSect<OppCurve, TCurve>* opp); + SkTSpan<TCurve, OppCurve>* spanAtT(double t, SkTSpan<TCurve, OppCurve>** priorSpan); + SkTSpan<TCurve, OppCurve>* tail(); + void trim(SkTSpan<TCurve, OppCurve>* span, SkTSect<OppCurve, TCurve>* opp); + void unlinkSpan(SkTSpan<TCurve, OppCurve>* span); + bool updateBounded(SkTSpan<TCurve, OppCurve>* first, SkTSpan<TCurve, OppCurve>* last, + SkTSpan<OppCurve, TCurve>* oppFirst); + void validate() const; + void validateBounded() const; + + const TCurve& fCurve; + SkChunkAlloc fHeap; + SkTSpan<TCurve, OppCurve>* fHead; + SkTSpan<TCurve, OppCurve>* fCoincident; + SkTSpan<TCurve, OppCurve>* fDeleted; + int fActiveCount; + bool fRemovedStartT; + bool fRemovedEndT; + SkDEBUGCODE(SkOpGlobalState* fDebugGlobalState); + SkDEBUGCODE(SkTSect<OppCurve, TCurve>* fOppSect); + PATH_OPS_DEBUG_T_SECT_CODE(int fID); + PATH_OPS_DEBUG_T_SECT_CODE(int fDebugCount); +#if DEBUG_T_SECT + int fDebugAllocatedCount; +#endif + friend class SkTSpan<TCurve, OppCurve>; + friend class SkTSpan<OppCurve, TCurve>; + friend class SkTSect<OppCurve, TCurve>; +}; + +#define COINCIDENT_SPAN_COUNT 9 + +template<typename TCurve, typename OppCurve> +void SkTCoincident<TCurve, OppCurve>::setPerp(const TCurve& c1, double t, + const SkDPoint& cPt, const OppCurve& c2) { + SkDVector dxdy = c1.dxdyAtT(t); + SkDLine perp = {{ cPt, {cPt.fX + dxdy.fY, cPt.fY - dxdy.fX} }}; + SkIntersections i; + int used = i.intersectRay(c2, perp); + // only keep closest + if (used == 0 || used == 3) { + this->init(); + return; + } + fPerpT = i[0][0]; + fPerpPt = i.pt(0); + SkASSERT(used <= 2); + if (used == 2) { + double distSq = (fPerpPt - cPt).lengthSquared(); + double dist2Sq = (i.pt(1) - cPt).lengthSquared(); + if (dist2Sq < distSq) { + fPerpT = i[0][1]; + fPerpPt = i.pt(1); + } + } +#if DEBUG_T_SECT + SkDebugf("setPerp t=%1.9g cPt=(%1.9g,%1.9g) %s oppT=%1.9g fPerpPt=(%1.9g,%1.9g)\n", + t, cPt.fX, cPt.fY, + cPt.approximatelyEqual(fPerpPt) ? "==" : "!=", fPerpT, fPerpPt.fX, fPerpPt.fY); +#endif + fMatch = cPt.approximatelyEqual(fPerpPt); +#if DEBUG_T_SECT + if (fMatch) { + SkDebugf(""); // allow setting breakpoint + } +#endif +} + +template<typename TCurve, typename OppCurve> +void SkTSpan<TCurve, OppCurve>::addBounded(SkTSpan<OppCurve, TCurve>* span, SkChunkAlloc* heap) { + SkTSpanBounded<OppCurve, TCurve>* bounded = new (heap->allocThrow( + sizeof(SkTSpanBounded<OppCurve, TCurve>)))(SkTSpanBounded<OppCurve, TCurve>); + bounded->fBounded = span; + bounded->fNext = fBounded; + fBounded = bounded; +} + +template<typename TCurve, typename OppCurve> +SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::addFollowing( + SkTSpan<TCurve, OppCurve>* prior) { + SkTSpan<TCurve, OppCurve>* result = this->addOne(); + SkDEBUGCODE(result->debugSetGlobalState(this->globalState())); + result->fStartT = prior ? prior->fEndT : 0; + SkTSpan<TCurve, OppCurve>* next = prior ? prior->fNext : fHead; + result->fEndT = next ? next->fStartT : 1; + result->fPrev = prior; + result->fNext = next; + if (prior) { + prior->fNext = result; + } else { + fHead = result; + } + if (next) { + next->fPrev = result; + } + result->resetBounds(fCurve); + result->validate(); + return result; +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::addForPerp(SkTSpan<OppCurve, TCurve>* span, double t) { + if (!span->hasOppT(t)) { + SkTSpan<TCurve, OppCurve>* priorSpan; + SkTSpan<TCurve, OppCurve>* opp = this->spanAtT(t, &priorSpan); + if (!opp) { + opp = this->addFollowing(priorSpan); +#if DEBUG_PERP + SkDebugf("%s priorSpan=%d t=%1.9g opp=%d\n", __FUNCTION__, priorSpan ? + priorSpan->debugID() : -1, t, opp->debugID()); +#endif + } +#if DEBUG_PERP + opp->dump(); SkDebugf("\n"); + SkDebugf("%s addBounded span=%d opp=%d\n", __FUNCTION__, priorSpan ? + priorSpan->debugID() : -1, opp->debugID()); +#endif + opp->addBounded(span, &fHeap); + span->addBounded(opp, &fHeap); + } + this->validate(); +#if DEBUG_T_SECT + span->validatePerpT(t); +#endif +} + +template<typename TCurve, typename OppCurve> +double SkTSpan<TCurve, OppCurve>::closestBoundedT(const SkDPoint& pt) const { + double result = -1; + double closest = DBL_MAX; + const SkTSpanBounded<OppCurve, TCurve>* testBounded = fBounded; + while (testBounded) { + const SkTSpan<OppCurve, TCurve>* test = testBounded->fBounded; + double startDist = test->fPart[0].distanceSquared(pt); + if (closest > startDist) { + closest = startDist; + result = test->fStartT; + } + double endDist = test->fPart[OppCurve::kPointLast].distanceSquared(pt); + if (closest > endDist) { + closest = endDist; + result = test->fEndT; + } + testBounded = testBounded->fNext; + } + SkASSERT(between(0, result, 1)); + return result; +} + +#ifdef SK_DEBUG +template<typename TCurve, typename OppCurve> +bool SkTSpan<TCurve, OppCurve>::debugIsBefore(const SkTSpan* span) const { + const SkTSpan* work = this; + do { + if (span == work) { + return true; + } + } while ((work = work->fNext)); + return false; +} +#endif + +template<typename TCurve, typename OppCurve> +bool SkTSpan<TCurve, OppCurve>::contains(double t) const { + const SkTSpan* work = this; + do { + if (between(work->fStartT, t, work->fEndT)) { + return true; + } + } while ((work = work->fNext)); + return false; +} + +template<typename TCurve, typename OppCurve> +const SkTSect<OppCurve, TCurve>* SkTSpan<TCurve, OppCurve>::debugOpp() const { + return SkDEBUGRELEASE(fDebugSect->debugOpp(), nullptr); +} + +template<typename TCurve, typename OppCurve> +SkTSpan<OppCurve, TCurve>* SkTSpan<TCurve, OppCurve>::findOppSpan( + const SkTSpan<OppCurve, TCurve>* opp) const { + SkTSpanBounded<OppCurve, TCurve>* bounded = fBounded; + while (bounded) { + SkTSpan<OppCurve, TCurve>* test = bounded->fBounded; + if (opp == test) { + return test; + } + bounded = bounded->fNext; + } + return nullptr; +} + +// returns 0 if no hull intersection +// 1 if hulls intersect +// 2 if hulls only share a common endpoint +// -1 if linear and further checking is required +template<typename TCurve, typename OppCurve> +int SkTSpan<TCurve, OppCurve>::hullCheck(const SkTSpan<OppCurve, TCurve>* opp, + bool* start, bool* oppStart) { + if (fIsLinear) { + return -1; + } + bool ptsInCommon; + if (onlyEndPointsInCommon(opp, start, oppStart, &ptsInCommon)) { + SkASSERT(ptsInCommon); + return 2; + } + bool linear; + if (fPart.hullIntersects(opp->fPart, &linear)) { + if (!linear) { // check set true if linear + return 1; + } + fIsLinear = true; + fIsLine = fPart.controlsInside(); + return ptsInCommon ? 1 : -1; + } else { // hull is not linear; check set true if intersected at the end points + return ((int) ptsInCommon) << 1; // 0 or 2 + } + return 0; +} + +// OPTIMIZE ? If at_most_end_pts_in_common detects that one quad is near linear, +// use line intersection to guess a better split than 0.5 +// OPTIMIZE Once at_most_end_pts_in_common detects linear, mark span so all future splits are linear +template<typename TCurve, typename OppCurve> +int SkTSpan<TCurve, OppCurve>::hullsIntersect(SkTSpan<OppCurve, TCurve>* opp, + bool* start, bool* oppStart) { + if (!fBounds.intersects(opp->fBounds)) { + return 0; + } + int hullSect = this->hullCheck(opp, start, oppStart); + if (hullSect >= 0) { + return hullSect; + } + hullSect = opp->hullCheck(this, oppStart, start); + if (hullSect >= 0) { + return hullSect; + } + return -1; +} + +template<typename TCurve, typename OppCurve> +void SkTSpan<TCurve, OppCurve>::init(const TCurve& c) { + fPrev = fNext = nullptr; + fStartT = 0; + fEndT = 1; + fBounded = nullptr; + resetBounds(c); +} + +template<typename TCurve, typename OppCurve> +void SkTSpan<TCurve, OppCurve>::initBounds(const TCurve& c) { + fPart = c.subDivide(fStartT, fEndT); + fBounds.setBounds(fPart); + fCoinStart.init(); + fCoinEnd.init(); + fBoundsMax = SkTMax(fBounds.width(), fBounds.height()); + fCollapsed = fPart.collapsed(); + fHasPerp = false; + fDeleted = false; +#if DEBUG_T_SECT + if (fCollapsed) { + SkDebugf(""); // for convenient breakpoints + } +#endif +} + +template<typename TCurve, typename OppCurve> +bool SkTSpan<TCurve, OppCurve>::linearsIntersect(SkTSpan<OppCurve, TCurve>* span) { + int result = this->linearIntersects(span->fPart); + if (result <= 1) { + return SkToBool(result); + } + SkASSERT(span->fIsLinear); + result = span->linearIntersects(this->fPart); +// SkASSERT(result <= 1); + return SkToBool(result); +} + +template<typename TCurve, typename OppCurve> +double SkTSpan<TCurve, OppCurve>::linearT(const SkDPoint& pt) const { + SkDVector len = fPart[TCurve::kPointLast] - fPart[0]; + return fabs(len.fX) > fabs(len.fY) + ? (pt.fX - fPart[0].fX) / len.fX + : (pt.fY - fPart[0].fY) / len.fY; +} + +template<typename TCurve, typename OppCurve> +int SkTSpan<TCurve, OppCurve>::linearIntersects(const OppCurve& q2) const { + // looks like q1 is near-linear + int start = 0, end = TCurve::kPointLast; // the outside points are usually the extremes + if (!fPart.controlsInside()) { + double dist = 0; // if there's any question, compute distance to find best outsiders + for (int outer = 0; outer < TCurve::kPointCount - 1; ++outer) { + for (int inner = outer + 1; inner < TCurve::kPointCount; ++inner) { + double test = (fPart[outer] - fPart[inner]).lengthSquared(); + if (dist > test) { + continue; + } + dist = test; + start = outer; + end = inner; + } + } + } + // see if q2 is on one side of the line formed by the extreme points + double origX = fPart[start].fX; + double origY = fPart[start].fY; + double adj = fPart[end].fX - origX; + double opp = fPart[end].fY - origY; + double maxPart = SkTMax(fabs(adj), fabs(opp)); + double sign = 0; // initialization to shut up warning in release build + for (int n = 0; n < OppCurve::kPointCount; ++n) { + double dx = q2[n].fY - origY; + double dy = q2[n].fX - origX; + double maxVal = SkTMax(maxPart, SkTMax(fabs(dx), fabs(dy))); + double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; + if (precisely_zero_when_compared_to(test, maxVal)) { + return 1; + } + if (approximately_zero_when_compared_to(test, maxVal)) { + return 3; + } + if (n == 0) { + sign = test; + continue; + } + if (test * sign < 0) { + return 1; + } + } + return 0; +} + +template<typename TCurve, typename OppCurve> +bool SkTSpan<TCurve, OppCurve>::onlyEndPointsInCommon(const SkTSpan<OppCurve, TCurve>* opp, + bool* start, bool* oppStart, bool* ptsInCommon) { + if (opp->fPart[0] == fPart[0]) { + *start = *oppStart = true; + } else if (opp->fPart[0] == fPart[TCurve::kPointLast]) { + *start = false; + *oppStart = true; + } else if (opp->fPart[OppCurve::kPointLast] == fPart[0]) { + *start = true; + *oppStart = false; + } else if (opp->fPart[OppCurve::kPointLast] == fPart[TCurve::kPointLast]) { + *start = *oppStart = false; + } else { + *ptsInCommon = false; + return false; + } + *ptsInCommon = true; + const SkDPoint* otherPts[TCurve::kPointCount - 1], * oppOtherPts[OppCurve::kPointCount - 1]; + int baseIndex = *start ? 0 : TCurve::kPointLast; + fPart.otherPts(baseIndex, otherPts); + opp->fPart.otherPts(*oppStart ? 0 : OppCurve::kPointLast, oppOtherPts); + const SkDPoint& base = fPart[baseIndex]; + for (int o1 = 0; o1 < (int) SK_ARRAY_COUNT(otherPts); ++o1) { + SkDVector v1 = *otherPts[o1] - base; + for (int o2 = 0; o2 < (int) SK_ARRAY_COUNT(oppOtherPts); ++o2) { + SkDVector v2 = *oppOtherPts[o2] - base; + if (v2.dot(v1) >= 0) { + return false; + } + } + } + return true; +} + +template<typename TCurve, typename OppCurve> +SkTSpan<OppCurve, TCurve>* SkTSpan<TCurve, OppCurve>::oppT(double t) const { + SkTSpanBounded<OppCurve, TCurve>* bounded = fBounded; + while (bounded) { + SkTSpan<OppCurve, TCurve>* test = bounded->fBounded; + if (between(test->fStartT, t, test->fEndT)) { + return test; + } + bounded = bounded->fNext; + } + return nullptr; +} + +template<typename TCurve, typename OppCurve> +bool SkTSpan<TCurve, OppCurve>::removeAllBounded() { + bool deleteSpan = false; + SkTSpanBounded<OppCurve, TCurve>* bounded = fBounded; + while (bounded) { + SkTSpan<OppCurve, TCurve>* opp = bounded->fBounded; + deleteSpan |= opp->removeBounded(this); + bounded = bounded->fNext; + } + return deleteSpan; +} + +template<typename TCurve, typename OppCurve> +bool SkTSpan<TCurve, OppCurve>::removeBounded(const SkTSpan<OppCurve, TCurve>* opp) { + if (fHasPerp) { + bool foundStart = false; + bool foundEnd = false; + SkTSpanBounded<OppCurve, TCurve>* bounded = fBounded; + while (bounded) { + SkTSpan<OppCurve, TCurve>* test = bounded->fBounded; + if (opp != test) { + foundStart |= between(test->fStartT, fCoinStart.perpT(), test->fEndT); + foundEnd |= between(test->fStartT, fCoinEnd.perpT(), test->fEndT); + } + bounded = bounded->fNext; + } + if (!foundStart || !foundEnd) { + fHasPerp = false; + fCoinStart.init(); + fCoinEnd.init(); + } + } + SkTSpanBounded<OppCurve, TCurve>* bounded = fBounded; + SkTSpanBounded<OppCurve, TCurve>* prev = nullptr; + while (bounded) { + SkTSpanBounded<OppCurve, TCurve>* boundedNext = bounded->fNext; + if (opp == bounded->fBounded) { + if (prev) { + prev->fNext = boundedNext; + return false; + } else { + fBounded = boundedNext; + return fBounded == nullptr; + } + } + prev = bounded; + bounded = boundedNext; + } + SkOPASSERT(0); + return false; +} + +template<typename TCurve, typename OppCurve> +bool SkTSpan<TCurve, OppCurve>::splitAt(SkTSpan* work, double t, SkChunkAlloc* heap) { + fStartT = t; + fEndT = work->fEndT; + if (fStartT == fEndT) { + fCollapsed = true; + return false; + } + work->fEndT = t; + if (work->fStartT == work->fEndT) { + work->fCollapsed = true; + return false; + } + fPrev = work; + fNext = work->fNext; + fIsLinear = work->fIsLinear; + fIsLine = work->fIsLine; + + work->fNext = this; + if (fNext) { + fNext->fPrev = this; + } + this->validate(); + SkTSpanBounded<OppCurve, TCurve>* bounded = work->fBounded; + fBounded = nullptr; + while (bounded) { + this->addBounded(bounded->fBounded, heap); + bounded = bounded->fNext; + } + bounded = fBounded; + while (bounded) { + bounded->fBounded->addBounded(this, heap); + bounded = bounded->fNext; + } + return true; +} + +template<typename TCurve, typename OppCurve> +void SkTSpan<TCurve, OppCurve>::validate() const { +#if DEBUG_VALIDATE + SkASSERT(this != fPrev); + SkASSERT(this != fNext); + SkASSERT(fNext == nullptr || fNext != fPrev); + SkASSERT(fNext == nullptr || this == fNext->fPrev); + SkASSERT(fPrev == nullptr || this == fPrev->fNext); + this->validateBounded(); +#endif +#if DEBUG_T_SECT + SkASSERT(fBounds.width() || fBounds.height() || fCollapsed); + SkASSERT(fBoundsMax == SkTMax(fBounds.width(), fBounds.height()) || fCollapsed == 0xFF); + SkASSERT(0 <= fStartT); + SkASSERT(fEndT <= 1); + SkASSERT(fStartT <= fEndT); + SkASSERT(fBounded || fCollapsed == 0xFF); + if (fHasPerp) { + if (fCoinStart.isMatch()) { + validatePerpT(fCoinStart.perpT()); + validatePerpPt(fCoinStart.perpT(), fCoinStart.perpPt()); + } + if (fCoinEnd.isMatch()) { + validatePerpT(fCoinEnd.perpT()); + validatePerpPt(fCoinEnd.perpT(), fCoinEnd.perpPt()); + } + } +#endif +} + +template<typename TCurve, typename OppCurve> +void SkTSpan<TCurve, OppCurve>::validateBounded() const { +#if DEBUG_VALIDATE + const SkTSpanBounded<OppCurve, TCurve>* testBounded = fBounded; + while (testBounded) { + SkDEBUGCODE(const SkTSpan<OppCurve, TCurve>* overlap = testBounded->fBounded); + SkASSERT(!overlap->fDeleted); +#if DEBUG_T_SECT + SkASSERT(((this->debugID() ^ overlap->debugID()) & 1) == 1); + SkASSERT(overlap->findOppSpan(this)); +#endif + testBounded = testBounded->fNext; + } +#endif +} + +template<typename TCurve, typename OppCurve> +void SkTSpan<TCurve, OppCurve>::validatePerpT(double oppT) const { + const SkTSpanBounded<OppCurve, TCurve>* testBounded = fBounded; + while (testBounded) { + const SkTSpan<OppCurve, TCurve>* overlap = testBounded->fBounded; + if (precisely_between(overlap->fStartT, oppT, overlap->fEndT)) { + return; + } + testBounded = testBounded->fNext; + } + SkASSERT(0); +} + +template<typename TCurve, typename OppCurve> +void SkTSpan<TCurve, OppCurve>::validatePerpPt(double t, const SkDPoint& pt) const { + SkASSERT(fDebugSect->fOppSect->fCurve.ptAtT(t) == pt); +} + + +template<typename TCurve, typename OppCurve> +SkTSect<TCurve, OppCurve>::SkTSect(const TCurve& c + SkDEBUGPARAMS(SkOpGlobalState* debugGlobalState) + PATH_OPS_DEBUG_T_SECT_PARAMS(int id)) + : fCurve(c) + , fHeap(sizeof(SkTSpan<TCurve, OppCurve>) * 4) + , fCoincident(nullptr) + , fDeleted(nullptr) + , fActiveCount(0) + SkDEBUGPARAMS(fDebugGlobalState(debugGlobalState)) + PATH_OPS_DEBUG_T_SECT_PARAMS(fID(id)) + PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugCount(0)) + PATH_OPS_DEBUG_T_SECT_PARAMS(fDebugAllocatedCount(0)) +{ + fHead = addOne(); + SkDEBUGCODE(fHead->debugSetGlobalState(debugGlobalState)); + fHead->init(c); +} + +template<typename TCurve, typename OppCurve> +SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::addOne() { + SkTSpan<TCurve, OppCurve>* result; + if (fDeleted) { + result = fDeleted; + fDeleted = result->fNext; + } else { + result = new (fHeap.allocThrow(sizeof(SkTSpan<TCurve, OppCurve>)))( + SkTSpan<TCurve, OppCurve>); +#if DEBUG_T_SECT + ++fDebugAllocatedCount; +#endif + } + result->reset(); + result->fHasPerp = false; + result->fDeleted = false; + ++fActiveCount; + PATH_OPS_DEBUG_T_SECT_CODE(result->fID = fDebugCount++ * 2 + fID); + SkDEBUGCODE(result->fDebugSect = this); +#ifdef SK_DEBUG + result->fPart.debugInit(); + result->fCoinStart.debugInit(); + result->fCoinEnd.debugInit(); + result->fPrev = result->fNext = nullptr; + result->fBounds.debugInit(); + result->fStartT = result->fEndT = result->fBoundsMax = SK_ScalarNaN; + result->fCollapsed = result->fIsLinear = result->fIsLine = 0xFF; +#endif + return result; +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::binarySearchCoin(SkTSect<OppCurve, TCurve>* sect2, double tStart, + double tStep, double* resultT, double* oppT) { + SkTSpan<TCurve, OppCurve> work; + double result = work.fStartT = work.fEndT = tStart; + SkDEBUGCODE(work.fDebugSect = this); + SkDPoint last = fCurve.ptAtT(tStart); + SkDPoint oppPt; + bool flip = false; + bool contained = false; + SkDEBUGCODE(bool down = tStep < 0); + const OppCurve& opp = sect2->fCurve; + do { + tStep *= 0.5; + work.fStartT += tStep; + if (flip) { + tStep = -tStep; + flip = false; + } + work.initBounds(fCurve); + if (work.fCollapsed) { + return false; + } + if (last.approximatelyEqual(work.fPart[0])) { + break; + } + last = work.fPart[0]; + work.fCoinStart.setPerp(fCurve, work.fStartT, last, opp); + if (work.fCoinStart.isMatch()) { +#if DEBUG_T_SECT + work.validatePerpPt(work.fCoinStart.perpT(), work.fCoinStart.perpPt()); +#endif + double oppTTest = work.fCoinStart.perpT(); + if (sect2->fHead->contains(oppTTest)) { + *oppT = oppTTest; + oppPt = work.fCoinStart.perpPt(); + contained = true; + SkASSERT(down ? result > work.fStartT : result < work.fStartT); + result = work.fStartT; + continue; + } + } + tStep = -tStep; + flip = true; + } while (true); + if (!contained) { + return false; + } + if (last.approximatelyEqual(fCurve[0])) { + result = 0; + } else if (last.approximatelyEqual(fCurve[TCurve::kPointLast])) { + result = 1; + } + if (oppPt.approximatelyEqual(opp[0])) { + *oppT = 0; + } else if (oppPt.approximatelyEqual(opp[OppCurve::kPointLast])) { + *oppT = 1; + } + *resultT = result; + return true; +} + +// OPTIMIZE ? keep a sorted list of sizes in the form of a doubly-linked list in quad span +// so that each quad sect has a pointer to the largest, and can update it as spans +// are split +template<typename TCurve, typename OppCurve> +SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::boundsMax() const { + SkTSpan<TCurve, OppCurve>* test = fHead; + SkTSpan<TCurve, OppCurve>* largest = fHead; + bool lCollapsed = largest->fCollapsed; + while ((test = test->fNext)) { + bool tCollapsed = test->fCollapsed; + if ((lCollapsed && !tCollapsed) || (lCollapsed == tCollapsed && + largest->fBoundsMax < test->fBoundsMax)) { + largest = test; + lCollapsed = test->fCollapsed; + } + } + return largest; +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::coincidentCheck(SkTSect<OppCurve, TCurve>* sect2) { + SkTSpan<TCurve, OppCurve>* first = fHead; + SkTSpan<TCurve, OppCurve>* last, * next; + do { + int consecutive = this->countConsecutiveSpans(first, &last); + next = last->fNext; + if (consecutive < COINCIDENT_SPAN_COUNT) { + continue; + } + this->validate(); + sect2->validate(); + this->computePerpendiculars(sect2, first, last); + this->validate(); + sect2->validate(); + // check to see if a range of points are on the curve + SkTSpan<TCurve, OppCurve>* coinStart = first; + do { + bool success = this->extractCoincident(sect2, coinStart, last, &coinStart); + if (!success) { + return false; + } + } while (coinStart && !last->fDeleted); + if (!fHead || !sect2->fHead) { + break; + } + if (!next || next->fDeleted) { + break; + } + } while ((first = next)); + return true; +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::coincidentForce(SkTSect<OppCurve, TCurve>* sect2, + double start1s, double start1e) { + SkTSpan<TCurve, OppCurve>* first = fHead; + SkTSpan<TCurve, OppCurve>* last = this->tail(); + SkTSpan<OppCurve, TCurve>* oppFirst = sect2->fHead; + SkTSpan<OppCurve, TCurve>* oppLast = sect2->tail(); + bool deleteEmptySpans = this->updateBounded(first, last, oppFirst); + deleteEmptySpans |= sect2->updateBounded(oppFirst, oppLast, first); + this->removeSpanRange(first, last); + sect2->removeSpanRange(oppFirst, oppLast); + first->fStartT = start1s; + first->fEndT = start1e; + first->resetBounds(fCurve); + first->fCoinStart.setPerp(fCurve, start1s, fCurve[0], sect2->fCurve); + first->fCoinEnd.setPerp(fCurve, start1e, fCurve[TCurve::kPointLast], sect2->fCurve); + bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); + double oppStartT = first->fCoinStart.perpT() == -1 ? 0 : SkTMax(0., first->fCoinStart.perpT()); + double oppEndT = first->fCoinEnd.perpT() == -1 ? 1 : SkTMin(1., first->fCoinEnd.perpT()); + if (!oppMatched) { + SkTSwap(oppStartT, oppEndT); + } + oppFirst->fStartT = oppStartT; + oppFirst->fEndT = oppEndT; + oppFirst->resetBounds(sect2->fCurve); + this->removeCoincident(first, false); + sect2->removeCoincident(oppFirst, true); + if (deleteEmptySpans) { + this->deleteEmptySpans(); + sect2->deleteEmptySpans(); + } +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::coincidentHasT(double t) { + SkTSpan<TCurve, OppCurve>* test = fCoincident; + while (test) { + if (between(test->fStartT, t, test->fEndT)) { + return true; + } + test = test->fNext; + } + return false; +} + +template<typename TCurve, typename OppCurve> +int SkTSect<TCurve, OppCurve>::collapsed() const { + int result = 0; + const SkTSpan<TCurve, OppCurve>* test = fHead; + while (test) { + if (test->fCollapsed) { + ++result; + } + test = test->next(); + } + return result; +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::computePerpendiculars(SkTSect<OppCurve, TCurve>* sect2, + SkTSpan<TCurve, OppCurve>* first, SkTSpan<TCurve, OppCurve>* last) { + const OppCurve& opp = sect2->fCurve; + SkTSpan<TCurve, OppCurve>* work = first; + SkTSpan<TCurve, OppCurve>* prior = nullptr; + do { + if (!work->fHasPerp && !work->fCollapsed) { + if (prior) { + work->fCoinStart = prior->fCoinEnd; + } else { + work->fCoinStart.setPerp(fCurve, work->fStartT, work->fPart[0], opp); + } + if (work->fCoinStart.isMatch()) { + double perpT = work->fCoinStart.perpT(); + if (sect2->coincidentHasT(perpT)) { + work->fCoinStart.init(); + } else { + sect2->addForPerp(work, perpT); + } + } + work->fCoinEnd.setPerp(fCurve, work->fEndT, work->fPart[TCurve::kPointLast], opp); + if (work->fCoinEnd.isMatch()) { + double perpT = work->fCoinEnd.perpT(); + if (sect2->coincidentHasT(perpT)) { + work->fCoinEnd.init(); + } else { + sect2->addForPerp(work, perpT); + } + } + work->fHasPerp = true; + } + if (work == last) { + break; + } + prior = work; + work = work->fNext; + SkASSERT(work); + } while (true); +} + +template<typename TCurve, typename OppCurve> +int SkTSect<TCurve, OppCurve>::countConsecutiveSpans(SkTSpan<TCurve, OppCurve>* first, + SkTSpan<TCurve, OppCurve>** lastPtr) const { + int consecutive = 1; + SkTSpan<TCurve, OppCurve>* last = first; + do { + SkTSpan<TCurve, OppCurve>* next = last->fNext; + if (!next) { + break; + } + if (next->fStartT > last->fEndT) { + break; + } + ++consecutive; + last = next; + } while (true); + *lastPtr = last; + return consecutive; +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::debugHasBounded(const SkTSpan<OppCurve, TCurve>* span) const { + const SkTSpan<TCurve, OppCurve>* test = fHead; + if (!test) { + return false; + } + do { + if (test->findOppSpan(span)) { + return true; + } + } while ((test = test->next())); + return false; +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::deleteEmptySpans() { + SkTSpan<TCurve, OppCurve>* test; + SkTSpan<TCurve, OppCurve>* next = fHead; + while ((test = next)) { + next = test->fNext; + if (!test->fBounded) { + if (!this->removeSpan(test)) { + return false; + } + } + } + return true; +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::extractCoincident( + SkTSect<OppCurve, TCurve>* sect2, + SkTSpan<TCurve, OppCurve>* first, SkTSpan<TCurve, OppCurve>* last, + SkTSpan<TCurve, OppCurve>** result) { + first = findCoincidentRun(first, &last); + if (!first || !last) { + *result = nullptr; + return true; + } + // march outwards to find limit of coincidence from here to previous and next spans + double startT = first->fStartT; + double oppStartT SK_INIT_TO_AVOID_WARNING; + double oppEndT SK_INIT_TO_AVOID_WARNING; + SkTSpan<TCurve, OppCurve>* prev = first->fPrev; + SkASSERT(first->fCoinStart.isMatch()); + SkTSpan<OppCurve, TCurve>* oppFirst = first->findOppT(first->fCoinStart.perpT()); + SkOPASSERT(last->fCoinEnd.isMatch()); + bool oppMatched = first->fCoinStart.perpT() < first->fCoinEnd.perpT(); + double coinStart; + SkDEBUGCODE(double coinEnd); + SkTSpan<OppCurve, TCurve>* cutFirst; + if (prev && prev->fEndT == startT + && this->binarySearchCoin(sect2, startT, prev->fStartT - startT, &coinStart, + &oppStartT) + && prev->fStartT < coinStart && coinStart < startT + && (cutFirst = prev->oppT(oppStartT))) { + oppFirst = cutFirst; + first = this->addSplitAt(prev, coinStart); + first->markCoincident(); + prev->fCoinEnd.markCoincident(); + if (oppFirst->fStartT < oppStartT && oppStartT < oppFirst->fEndT) { + SkTSpan<OppCurve, TCurve>* oppHalf = sect2->addSplitAt(oppFirst, oppStartT); + if (oppMatched) { + oppFirst->fCoinEnd.markCoincident(); + oppHalf->markCoincident(); + oppFirst = oppHalf; + } else { + oppFirst->markCoincident(); + oppHalf->fCoinStart.markCoincident(); + } + } + } else { + SkDEBUGCODE(coinStart = first->fStartT); + SkDEBUGCODE(oppStartT = oppMatched ? oppFirst->fStartT : oppFirst->fEndT); + } + // FIXME: incomplete : if we're not at the end, find end of coin + SkTSpan<OppCurve, TCurve>* oppLast; + SkOPASSERT(last->fCoinEnd.isMatch()); + oppLast = last->findOppT(last->fCoinEnd.perpT()); + SkDEBUGCODE(coinEnd = last->fEndT); +#ifdef SK_DEBUG + if (!this->globalState() || !this->globalState()->debugSkipAssert()) { + oppEndT = oppMatched ? oppLast->fEndT : oppLast->fStartT; + } +#endif + if (!oppMatched) { + SkTSwap(oppFirst, oppLast); + SkTSwap(oppStartT, oppEndT); + } + SkOPASSERT(oppStartT < oppEndT); + SkASSERT(coinStart == first->fStartT); + SkASSERT(coinEnd == last->fEndT); + SkOPASSERT(oppStartT == oppFirst->fStartT); + SkOPASSERT(oppEndT == oppLast->fEndT); + if (!oppFirst) { + *result = nullptr; + return true; + } + if (!oppLast) { + *result = nullptr; + return true; + } + // reduce coincident runs to single entries + this->validate(); + sect2->validate(); + bool deleteEmptySpans = this->updateBounded(first, last, oppFirst); + deleteEmptySpans |= sect2->updateBounded(oppFirst, oppLast, first); + this->removeSpanRange(first, last); + sect2->removeSpanRange(oppFirst, oppLast); + first->fEndT = last->fEndT; + first->resetBounds(this->fCurve); + first->fCoinStart.setPerp(fCurve, first->fStartT, first->fPart[0], sect2->fCurve); + first->fCoinEnd.setPerp(fCurve, first->fEndT, first->fPart[TCurve::kPointLast], sect2->fCurve); + oppStartT = first->fCoinStart.perpT(); + oppEndT = first->fCoinEnd.perpT(); + if (between(0, oppStartT, 1) && between(0, oppEndT, 1)) { + if (!oppMatched) { + SkTSwap(oppStartT, oppEndT); + } + oppFirst->fStartT = oppStartT; + oppFirst->fEndT = oppEndT; + oppFirst->resetBounds(sect2->fCurve); + } + this->validateBounded(); + sect2->validateBounded(); + last = first->fNext; + this->removeCoincident(first, false); + sect2->removeCoincident(oppFirst, true); + if (deleteEmptySpans) { + if (!this->deleteEmptySpans() || !sect2->deleteEmptySpans()) { + *result = nullptr; + return false; + } + } + this->validate(); + sect2->validate(); + *result = last && !last->fDeleted && fHead && sect2->fHead ? last : nullptr; + return true; +} + +template<typename TCurve, typename OppCurve> +SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::findCoincidentRun( + SkTSpan<TCurve, OppCurve>* first, SkTSpan<TCurve, OppCurve>** lastPtr) { + SkTSpan<TCurve, OppCurve>* work = first; + SkTSpan<TCurve, OppCurve>* lastCandidate = nullptr; + first = nullptr; + // find the first fully coincident span + do { + if (work->fCoinStart.isMatch()) { +#if DEBUG_T_SECT + work->validatePerpT(work->fCoinStart.perpT()); + work->validatePerpPt(work->fCoinStart.perpT(), work->fCoinStart.perpPt()); +#endif + SkASSERT(work->hasOppT(work->fCoinStart.perpT())); + if (!work->fCoinEnd.isMatch()) { + break; + } + lastCandidate = work; + if (!first) { + first = work; + } + } else if (first && work->fCollapsed) { + *lastPtr = lastCandidate; + return first; + } else { + lastCandidate = nullptr; + SkOPASSERT(!first); + } + if (work == *lastPtr) { + return first; + } + work = work->fNext; + if (!work) { + return nullptr; + } + } while (true); + if (lastCandidate) { + *lastPtr = lastCandidate; + } + return first; +} + +template<typename TCurve, typename OppCurve> +int SkTSect<TCurve, OppCurve>::intersects(SkTSpan<TCurve, OppCurve>* span, + SkTSect<OppCurve, TCurve>* opp, + SkTSpan<OppCurve, TCurve>* oppSpan, int* oppResult) { + bool spanStart, oppStart; + int hullResult = span->hullsIntersect(oppSpan, &spanStart, &oppStart); + if (hullResult >= 0) { + if (hullResult == 2) { // hulls have one point in common + if (!span->fBounded || !span->fBounded->fNext) { + SkASSERT(!span->fBounded || span->fBounded->fBounded == oppSpan); + if (spanStart) { + span->fEndT = span->fStartT; + } else { + span->fStartT = span->fEndT; + } + } else { + hullResult = 1; + } + if (!oppSpan->fBounded || !oppSpan->fBounded->fNext) { + SkASSERT(!oppSpan->fBounded || oppSpan->fBounded->fBounded == span); + if (oppStart) { + oppSpan->fEndT = oppSpan->fStartT; + } else { + oppSpan->fStartT = oppSpan->fEndT; + } + *oppResult = 2; + } else { + *oppResult = 1; + } + } else { + *oppResult = 1; + } + return hullResult; + } + if (span->fIsLine && oppSpan->fIsLine) { + SkIntersections i; + int sects = this->linesIntersect(span, opp, oppSpan, &i); + if (sects == 2) { + return *oppResult = 1; + } + if (!sects) { + return -1; + } + span->fStartT = span->fEndT = i[0][0]; + oppSpan->fStartT = oppSpan->fEndT = i[1][0]; + return *oppResult = 2; + } + if (span->fIsLinear || oppSpan->fIsLinear) { + return *oppResult = (int) span->linearsIntersect(oppSpan); + } + return *oppResult = 1; +} + +template<typename TCurve> +static bool is_parallel(const SkDLine& thisLine, const TCurve& opp) { + if (!opp.IsConic()) { + return false; // FIXME : breaks a lot of stuff now + } + int finds = 0; + SkDLine thisPerp; + thisPerp.fPts[0].fX = thisLine.fPts[1].fX + (thisLine.fPts[1].fY - thisLine.fPts[0].fY); + thisPerp.fPts[0].fY = thisLine.fPts[1].fY + (thisLine.fPts[0].fX - thisLine.fPts[1].fX); + thisPerp.fPts[1] = thisLine.fPts[1]; + SkIntersections perpRayI; + perpRayI.intersectRay(opp, thisPerp); + for (int pIndex = 0; pIndex < perpRayI.used(); ++pIndex) { + finds += perpRayI.pt(pIndex).approximatelyEqual(thisPerp.fPts[1]); + } + thisPerp.fPts[1].fX = thisLine.fPts[0].fX + (thisLine.fPts[1].fY - thisLine.fPts[0].fY); + thisPerp.fPts[1].fY = thisLine.fPts[0].fY + (thisLine.fPts[0].fX - thisLine.fPts[1].fX); + thisPerp.fPts[0] = thisLine.fPts[0]; + perpRayI.intersectRay(opp, thisPerp); + for (int pIndex = 0; pIndex < perpRayI.used(); ++pIndex) { + finds += perpRayI.pt(pIndex).approximatelyEqual(thisPerp.fPts[0]); + } + return finds >= 2; +} + +// while the intersection points are sufficiently far apart: +// construct the tangent lines from the intersections +// find the point where the tangent line intersects the opposite curve +template<typename TCurve, typename OppCurve> +int SkTSect<TCurve, OppCurve>::linesIntersect(SkTSpan<TCurve, OppCurve>* span, + SkTSect<OppCurve, TCurve>* opp, + SkTSpan<OppCurve, TCurve>* oppSpan, SkIntersections* i) { + SkIntersections thisRayI, oppRayI; + SkDLine thisLine = {{ span->fPart[0], span->fPart[TCurve::kPointLast] }}; + SkDLine oppLine = {{ oppSpan->fPart[0], oppSpan->fPart[OppCurve::kPointLast] }}; + int loopCount = 0; + double bestDistSq = DBL_MAX; + if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { + return 0; + } + if (!oppRayI.intersectRay(this->fCurve, oppLine)) { + return 0; + } + // if the ends of each line intersect the opposite curve, the lines are coincident + if (thisRayI.used() > 1) { + int ptMatches = 0; + for (int tIndex = 0; tIndex < thisRayI.used(); ++tIndex) { + for (int lIndex = 0; lIndex < (int) SK_ARRAY_COUNT(thisLine.fPts); ++lIndex) { + ptMatches += thisRayI.pt(tIndex).approximatelyEqual(thisLine.fPts[lIndex]); + } + } + if (ptMatches == 2 || is_parallel(thisLine, opp->fCurve)) { + return 2; + } + } + if (oppRayI.used() > 1) { + int ptMatches = 0; + for (int oIndex = 0; oIndex < oppRayI.used(); ++oIndex) { + for (int lIndex = 0; lIndex < (int) SK_ARRAY_COUNT(thisLine.fPts); ++lIndex) { + ptMatches += oppRayI.pt(oIndex).approximatelyEqual(oppLine.fPts[lIndex]); + } + } + if (ptMatches == 2|| is_parallel(oppLine, this->fCurve)) { + return 2; + } + } + do { + // pick the closest pair of points + double closest = DBL_MAX; + int closeIndex SK_INIT_TO_AVOID_WARNING; + int oppCloseIndex SK_INIT_TO_AVOID_WARNING; + for (int index = 0; index < oppRayI.used(); ++index) { + if (!roughly_between(span->fStartT, oppRayI[0][index], span->fEndT)) { + continue; + } + for (int oIndex = 0; oIndex < thisRayI.used(); ++oIndex) { + if (!roughly_between(oppSpan->fStartT, thisRayI[0][oIndex], oppSpan->fEndT)) { + continue; + } + double distSq = thisRayI.pt(index).distanceSquared(oppRayI.pt(oIndex)); + if (closest > distSq) { + closest = distSq; + closeIndex = index; + oppCloseIndex = oIndex; + } + } + } + if (closest == DBL_MAX) { + break; + } + const SkDPoint& oppIPt = thisRayI.pt(oppCloseIndex); + const SkDPoint& iPt = oppRayI.pt(closeIndex); + if (between(span->fStartT, oppRayI[0][closeIndex], span->fEndT) + && between(oppSpan->fStartT, thisRayI[0][oppCloseIndex], oppSpan->fEndT) + && oppIPt.approximatelyEqual(iPt)) { + i->merge(oppRayI, closeIndex, thisRayI, oppCloseIndex); + return i->used(); + } + double distSq = oppIPt.distanceSquared(iPt); + if (bestDistSq < distSq || ++loopCount > 5) { + return 0; + } + bestDistSq = distSq; + double oppStart = oppRayI[0][closeIndex]; + thisLine[0] = fCurve.ptAtT(oppStart); + thisLine[1] = thisLine[0] + fCurve.dxdyAtT(oppStart); + if (!thisRayI.intersectRay(opp->fCurve, thisLine)) { + break; + } + double start = thisRayI[0][oppCloseIndex]; + oppLine[0] = opp->fCurve.ptAtT(start); + oppLine[1] = oppLine[0] + opp->fCurve.dxdyAtT(start); + if (!oppRayI.intersectRay(this->fCurve, oppLine)) { + break; + } + } while (true); + // convergence may fail if the curves are nearly coincident + SkTCoincident<OppCurve, TCurve> oCoinS, oCoinE; + oCoinS.setPerp(opp->fCurve, oppSpan->fStartT, oppSpan->fPart[0], fCurve); + oCoinE.setPerp(opp->fCurve, oppSpan->fEndT, oppSpan->fPart[OppCurve::kPointLast], fCurve); + double tStart = oCoinS.perpT(); + double tEnd = oCoinE.perpT(); + bool swap = tStart > tEnd; + if (swap) { + SkTSwap(tStart, tEnd); + } + tStart = SkTMax(tStart, span->fStartT); + tEnd = SkTMin(tEnd, span->fEndT); + if (tStart > tEnd) { + return 0; + } + SkDVector perpS, perpE; + if (tStart == span->fStartT) { + SkTCoincident<TCurve, OppCurve> coinS; + coinS.setPerp(fCurve, span->fStartT, span->fPart[0], opp->fCurve); + perpS = span->fPart[0] - coinS.perpPt(); + } else if (swap) { + perpS = oCoinE.perpPt() - oppSpan->fPart[OppCurve::kPointLast]; + } else { + perpS = oCoinS.perpPt() - oppSpan->fPart[0]; + } + if (tEnd == span->fEndT) { + SkTCoincident<TCurve, OppCurve> coinE; + coinE.setPerp(fCurve, span->fEndT, span->fPart[TCurve::kPointLast], opp->fCurve); + perpE = span->fPart[TCurve::kPointLast] - coinE.perpPt(); + } else if (swap) { + perpE = oCoinS.perpPt() - oppSpan->fPart[0]; + } else { + perpE = oCoinE.perpPt() - oppSpan->fPart[OppCurve::kPointLast]; + } + if (perpS.dot(perpE) >= 0) { + return 0; + } + SkTCoincident<TCurve, OppCurve> coinW; + double workT = tStart; + double tStep = tEnd - tStart; + SkDPoint workPt; + do { + tStep *= 0.5; + if (precisely_zero(tStep)) { + return 0; + } + workT += tStep; + workPt = fCurve.ptAtT(workT); + coinW.setPerp(fCurve, workT, workPt, opp->fCurve); + double perpT = coinW.perpT(); + if (coinW.isMatch() ? !between(oppSpan->fStartT, perpT, oppSpan->fEndT) : perpT < 0) { + continue; + } + SkDVector perpW = workPt - coinW.perpPt(); + if ((perpS.dot(perpW) >= 0) == (tStep < 0)) { + tStep = -tStep; + } + if (workPt.approximatelyEqual(coinW.perpPt())) { + break; + } + } while (true); + double oppTTest = coinW.perpT(); + if (!opp->fHead->contains(oppTTest)) { + return 0; + } + i->setMax(1); + i->insert(workT, oppTTest, workPt); + return 1; +} + + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::markSpanGone(SkTSpan<TCurve, OppCurve>* span) { + if (--fActiveCount < 0) { + return false; + } + span->fNext = fDeleted; + fDeleted = span; + SkOPASSERT(!span->fDeleted); + span->fDeleted = true; + return true; +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::matchedDirection(double t, const SkTSect<OppCurve, TCurve>* sect2, + double t2) const { + SkDVector dxdy = this->fCurve.dxdyAtT(t); + SkDVector dxdy2 = sect2->fCurve.dxdyAtT(t2); + return dxdy.dot(dxdy2) >= 0; +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::matchedDirCheck(double t, const SkTSect<OppCurve, TCurve>* sect2, + double t2, bool* calcMatched, bool* oppMatched) const { + if (*calcMatched) { + SkASSERT(*oppMatched == this->matchedDirection(t, sect2, t2)); + } else { + *oppMatched = this->matchedDirection(t, sect2, t2); + *calcMatched = true; + } +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::mergeCoincidence(SkTSect<OppCurve, TCurve>* sect2) { + double smallLimit = 0; + do { + // find the smallest unprocessed span + SkTSpan<TCurve, OppCurve>* smaller = nullptr; + SkTSpan<TCurve, OppCurve>* test = fCoincident; + do { + if (test->fStartT < smallLimit) { + continue; + } + if (smaller && smaller->fEndT < test->fStartT) { + continue; + } + smaller = test; + } while ((test = test->fNext)); + if (!smaller) { + return; + } + smallLimit = smaller->fEndT; + // find next larger span + SkTSpan<TCurve, OppCurve>* prior = nullptr; + SkTSpan<TCurve, OppCurve>* larger = nullptr; + SkTSpan<TCurve, OppCurve>* largerPrior = nullptr; + test = fCoincident; + do { + if (test->fStartT < smaller->fEndT) { + continue; + } + SkASSERT(test->fStartT != smaller->fEndT); + if (larger && larger->fStartT < test->fStartT) { + continue; + } + largerPrior = prior; + larger = test; + } while ((prior = test), (test = test->fNext)); + if (!larger) { + continue; + } + // check middle t value to see if it is coincident as well + double midT = (smaller->fEndT + larger->fStartT) / 2; + SkDPoint midPt = fCurve.ptAtT(midT); + SkTCoincident<TCurve, OppCurve> coin; + coin.setPerp(fCurve, midT, midPt, sect2->fCurve); + if (coin.isMatch()) { + smaller->fEndT = larger->fEndT; + smaller->fCoinEnd = larger->fCoinEnd; + if (largerPrior) { + largerPrior->fNext = larger->fNext; + largerPrior->validate(); + } else { + fCoincident = larger->fNext; + } + } + } while (true); +} + +template<typename TCurve, typename OppCurve> +SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::prev( + SkTSpan<TCurve, OppCurve>* span) const { + SkTSpan<TCurve, OppCurve>* result = nullptr; + SkTSpan<TCurve, OppCurve>* test = fHead; + while (span != test) { + result = test; + test = test->fNext; + SkASSERT(test); + } + return result; +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::recoverCollapsed() { + SkTSpan<TCurve, OppCurve>* deleted = fDeleted; + while (deleted) { + SkTSpan<TCurve, OppCurve>* delNext = deleted->fNext; + if (deleted->fCollapsed) { + SkTSpan<TCurve, OppCurve>** spanPtr = &fHead; + while (*spanPtr && (*spanPtr)->fEndT <= deleted->fStartT) { + spanPtr = &(*spanPtr)->fNext; + } + deleted->fNext = *spanPtr; + *spanPtr = deleted; + } + deleted = delNext; + } +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::removeAllBut(const SkTSpan<OppCurve, TCurve>* keep, + SkTSpan<TCurve, OppCurve>* span, SkTSect<OppCurve, TCurve>* opp) { + const SkTSpanBounded<OppCurve, TCurve>* testBounded = span->fBounded; + while (testBounded) { + SkTSpan<OppCurve, TCurve>* bounded = testBounded->fBounded; + const SkTSpanBounded<OppCurve, TCurve>* next = testBounded->fNext; + // may have been deleted when opp did 'remove all but' + if (bounded != keep && !bounded->fDeleted) { + SkAssertResult(SkDEBUGCODE(!) span->removeBounded(bounded)); + if (bounded->removeBounded(span)) { + opp->removeSpan(bounded); + } + } + testBounded = next; + } + SkASSERT(!span->fDeleted); + SkASSERT(span->findOppSpan(keep)); + SkASSERT(keep->findOppSpan(span)); +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::removeByPerpendicular(SkTSect<OppCurve, TCurve>* opp) { + SkTSpan<TCurve, OppCurve>* test = fHead; + SkTSpan<TCurve, OppCurve>* next; + do { + next = test->fNext; + if (test->fCoinStart.perpT() < 0 || test->fCoinEnd.perpT() < 0) { + continue; + } + SkDVector startV = test->fCoinStart.perpPt() - test->fPart[0]; + SkDVector endV = test->fCoinEnd.perpPt() - test->fPart[TCurve::kPointLast]; +#if DEBUG_T_SECT + SkDebugf("%s startV=(%1.9g,%1.9g) endV=(%1.9g,%1.9g) dot=%1.9g\n", __FUNCTION__, + startV.fX, startV.fY, endV.fX, endV.fY, startV.dot(endV)); +#endif + if (startV.dot(endV) <= 0) { + continue; + } + this->removeSpans(test, opp); + } while ((test = next)); +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::removeCoincident(SkTSpan<TCurve, OppCurve>* span, bool isBetween) { + this->unlinkSpan(span); + if (isBetween || between(0, span->fCoinStart.perpT(), 1)) { + --fActiveCount; + span->fNext = fCoincident; + fCoincident = span; + } else { + this->markSpanGone(span); + } +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::removeSpan(SkTSpan<TCurve, OppCurve>* span) { + if (!span->fStartT) { + fRemovedStartT = true; + } + if (1 == span->fEndT) { + fRemovedEndT = true; + } + this->unlinkSpan(span); + return this->markSpanGone(span); +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::removeSpanRange(SkTSpan<TCurve, OppCurve>* first, + SkTSpan<TCurve, OppCurve>* last) { + if (first == last) { + return; + } + SkTSpan<TCurve, OppCurve>* span = first; + SkASSERT(span); + SkTSpan<TCurve, OppCurve>* final = last->fNext; + SkTSpan<TCurve, OppCurve>* next = span->fNext; + while ((span = next) && span != final) { + next = span->fNext; + this->markSpanGone(span); + } + if (final) { + final->fPrev = first; + } + first->fNext = final; + first->validate(); +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::removeSpans(SkTSpan<TCurve, OppCurve>* span, + SkTSect<OppCurve, TCurve>* opp) { + SkTSpanBounded<OppCurve, TCurve>* bounded = span->fBounded; + while (bounded) { + SkTSpan<OppCurve, TCurve>* spanBounded = bounded->fBounded; + SkTSpanBounded<OppCurve, TCurve>* next = bounded->fNext; + if (span->removeBounded(spanBounded)) { // shuffles last into position 0 + this->removeSpan(span); + } + if (spanBounded->removeBounded(span)) { + opp->removeSpan(spanBounded); + } + SkASSERT(!span->fDeleted || !opp->debugHasBounded(span)); + bounded = next; + } +} + +template<typename TCurve, typename OppCurve> +SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::spanAtT(double t, + SkTSpan<TCurve, OppCurve>** priorSpan) { + SkTSpan<TCurve, OppCurve>* test = fHead; + SkTSpan<TCurve, OppCurve>* prev = nullptr; + while (test && test->fEndT < t) { + prev = test; + test = test->fNext; + } + *priorSpan = prev; + return test && test->fStartT <= t ? test : nullptr; +} + +template<typename TCurve, typename OppCurve> +SkTSpan<TCurve, OppCurve>* SkTSect<TCurve, OppCurve>::tail() { + SkTSpan<TCurve, OppCurve>* result = fHead; + SkTSpan<TCurve, OppCurve>* next = fHead; + while ((next = next->fNext)) { + if (next->fEndT > result->fEndT) { + result = next; + } + } + return result; +} + +/* Each span has a range of opposite spans it intersects. After the span is split in two, + adjust the range to its new size */ +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::trim(SkTSpan<TCurve, OppCurve>* span, + SkTSect<OppCurve, TCurve>* opp) { + span->initBounds(fCurve); + const SkTSpanBounded<OppCurve, TCurve>* testBounded = span->fBounded; + while (testBounded) { + SkTSpan<OppCurve, TCurve>* test = testBounded->fBounded; + const SkTSpanBounded<OppCurve, TCurve>* next = testBounded->fNext; + int oppSects, sects = this->intersects(span, opp, test, &oppSects); + if (sects >= 1) { + if (oppSects == 2) { + test->initBounds(opp->fCurve); + opp->removeAllBut(span, test, this); + } + if (sects == 2) { + span->initBounds(fCurve); + this->removeAllBut(test, span, opp); + return; + } + } else { + if (span->removeBounded(test)) { + this->removeSpan(span); + } + if (test->removeBounded(span)) { + opp->removeSpan(test); + } + } + testBounded = next; + } +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::unlinkSpan(SkTSpan<TCurve, OppCurve>* span) { + SkTSpan<TCurve, OppCurve>* prev = span->fPrev; + SkTSpan<TCurve, OppCurve>* next = span->fNext; + if (prev) { + prev->fNext = next; + if (next) { + next->fPrev = prev; + next->validate(); + } + } else { + fHead = next; + if (next) { + next->fPrev = nullptr; + } + } +} + +template<typename TCurve, typename OppCurve> +bool SkTSect<TCurve, OppCurve>::updateBounded(SkTSpan<TCurve, OppCurve>* first, + SkTSpan<TCurve, OppCurve>* last, SkTSpan<OppCurve, TCurve>* oppFirst) { + SkTSpan<TCurve, OppCurve>* test = first; + const SkTSpan<TCurve, OppCurve>* final = last->next(); + bool deleteSpan = false; + do { + deleteSpan |= test->removeAllBounded(); + } while ((test = test->fNext) != final && test); + first->fBounded = nullptr; + first->addBounded(oppFirst, &fHeap); + // cannot call validate until remove span range is called + return deleteSpan; +} + + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::validate() const { +#if DEBUG_VALIDATE + int count = 0; + double last = 0; + if (fHead) { + const SkTSpan<TCurve, OppCurve>* span = fHead; + SkASSERT(!span->fPrev); + const SkTSpan<TCurve, OppCurve>* next; + do { + span->validate(); + SkASSERT(span->fStartT >= last); + last = span->fEndT; + ++count; + next = span->fNext; + SkASSERT(next != span); + } while ((span = next) != nullptr); + } + SkASSERT(count == fActiveCount); +#endif +#if DEBUG_T_SECT + SkASSERT(fActiveCount <= fDebugAllocatedCount); + int deletedCount = 0; + const SkTSpan<TCurve, OppCurve>* deleted = fDeleted; + while (deleted) { + ++deletedCount; + deleted = deleted->fNext; + } + const SkTSpan<TCurve, OppCurve>* coincident = fCoincident; + while (coincident) { + ++deletedCount; + coincident = coincident->fNext; + } + SkASSERT(fActiveCount + deletedCount == fDebugAllocatedCount); +#endif +} + +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::validateBounded() const { +#if DEBUG_VALIDATE + if (!fHead) { + return; + } + const SkTSpan<TCurve, OppCurve>* span = fHead; + do { + span->validateBounded(); + } while ((span = span->fNext) != nullptr); +#endif +} + +template<typename TCurve, typename OppCurve> +int SkTSect<TCurve, OppCurve>::EndsEqual(const SkTSect<TCurve, OppCurve>* sect1, + const SkTSect<OppCurve, TCurve>* sect2, SkIntersections* intersections) { + int zeroOneSet = 0; + if (sect1->fCurve[0] == sect2->fCurve[0]) { + zeroOneSet |= kZeroS1Set | kZeroS2Set; + intersections->insert(0, 0, sect1->fCurve[0]); + } + if (sect1->fCurve[0] == sect2->fCurve[OppCurve::kPointLast]) { + zeroOneSet |= kZeroS1Set | kOneS2Set; + intersections->insert(0, 1, sect1->fCurve[0]); + } + if (sect1->fCurve[TCurve::kPointLast] == sect2->fCurve[0]) { + zeroOneSet |= kOneS1Set | kZeroS2Set; + intersections->insert(1, 0, sect1->fCurve[TCurve::kPointLast]); + } + if (sect1->fCurve[TCurve::kPointLast] == sect2->fCurve[OppCurve::kPointLast]) { + zeroOneSet |= kOneS1Set | kOneS2Set; + intersections->insert(1, 1, sect1->fCurve[TCurve::kPointLast]); + } + // check for zero + if (!(zeroOneSet & (kZeroS1Set | kZeroS2Set)) + && sect1->fCurve[0].approximatelyEqual(sect2->fCurve[0])) { + zeroOneSet |= kZeroS1Set | kZeroS2Set; + intersections->insertNear(0, 0, sect1->fCurve[0], sect2->fCurve[0]); + } + if (!(zeroOneSet & (kZeroS1Set | kOneS2Set)) + && sect1->fCurve[0].approximatelyEqual(sect2->fCurve[OppCurve::kPointLast])) { + zeroOneSet |= kZeroS1Set | kOneS2Set; + intersections->insertNear(0, 1, sect1->fCurve[0], sect2->fCurve[OppCurve::kPointLast]); + } + // check for one + if (!(zeroOneSet & (kOneS1Set | kZeroS2Set)) + && sect1->fCurve[TCurve::kPointLast].approximatelyEqual(sect2->fCurve[0])) { + zeroOneSet |= kOneS1Set | kZeroS2Set; + intersections->insertNear(1, 0, sect1->fCurve[TCurve::kPointLast], sect2->fCurve[0]); + } + if (!(zeroOneSet & (kOneS1Set | kOneS2Set)) + && sect1->fCurve[TCurve::kPointLast].approximatelyEqual(sect2->fCurve[ + OppCurve::kPointLast])) { + zeroOneSet |= kOneS1Set | kOneS2Set; + intersections->insertNear(1, 1, sect1->fCurve[TCurve::kPointLast], + sect2->fCurve[OppCurve::kPointLast]); + } + return zeroOneSet; +} + +template<typename TCurve, typename OppCurve> +struct SkClosestRecord { + bool operator<(const SkClosestRecord& rh) const { + return fClosest < rh.fClosest; + } + + void addIntersection(SkIntersections* intersections) const { + double r1t = fC1Index ? fC1Span->endT() : fC1Span->startT(); + double r2t = fC2Index ? fC2Span->endT() : fC2Span->startT(); + intersections->insert(r1t, r2t, fC1Span->part()[fC1Index]); + } + + void findEnd(const SkTSpan<TCurve, OppCurve>* span1, const SkTSpan<OppCurve, TCurve>* span2, + int c1Index, int c2Index) { + const TCurve& c1 = span1->part(); + const OppCurve& c2 = span2->part(); + if (!c1[c1Index].approximatelyEqual(c2[c2Index])) { + return; + } + double dist = c1[c1Index].distanceSquared(c2[c2Index]); + if (fClosest < dist) { + return; + } + fC1Span = span1; + fC2Span = span2; + fC1StartT = span1->startT(); + fC1EndT = span1->endT(); + fC2StartT = span2->startT(); + fC2EndT = span2->endT(); + fC1Index = c1Index; + fC2Index = c2Index; + fClosest = dist; + } + + bool matesWith(const SkClosestRecord& mate SkDEBUGPARAMS(SkIntersections* i)) const { + SkASSERT(fC1Span == mate.fC1Span || fC1Span->endT() <= mate.fC1Span->startT() + || mate.fC1Span->endT() <= fC1Span->startT()); + SkOPOBJASSERT(i, fC2Span == mate.fC2Span || fC2Span->endT() <= mate.fC2Span->startT() + || mate.fC2Span->endT() <= fC2Span->startT()); + return fC1Span == mate.fC1Span || fC1Span->endT() == mate.fC1Span->startT() + || fC1Span->startT() == mate.fC1Span->endT() + || fC2Span == mate.fC2Span + || fC2Span->endT() == mate.fC2Span->startT() + || fC2Span->startT() == mate.fC2Span->endT(); + } + + void merge(const SkClosestRecord& mate) { + fC1Span = mate.fC1Span; + fC2Span = mate.fC2Span; + fClosest = mate.fClosest; + fC1Index = mate.fC1Index; + fC2Index = mate.fC2Index; + } + + void reset() { + fClosest = FLT_MAX; + SkDEBUGCODE(fC1Span = nullptr); + SkDEBUGCODE(fC2Span = nullptr); + SkDEBUGCODE(fC1Index = fC2Index = -1); + } + + void update(const SkClosestRecord& mate) { + fC1StartT = SkTMin(fC1StartT, mate.fC1StartT); + fC1EndT = SkTMax(fC1EndT, mate.fC1EndT); + fC2StartT = SkTMin(fC2StartT, mate.fC2StartT); + fC2EndT = SkTMax(fC2EndT, mate.fC2EndT); + } + + const SkTSpan<TCurve, OppCurve>* fC1Span; + const SkTSpan<OppCurve, TCurve>* fC2Span; + double fC1StartT; + double fC1EndT; + double fC2StartT; + double fC2EndT; + double fClosest; + int fC1Index; + int fC2Index; +}; + +template<typename TCurve, typename OppCurve> +struct SkClosestSect { + SkClosestSect() + : fUsed(0) { + fClosest.push_back().reset(); + } + + bool find(const SkTSpan<TCurve, OppCurve>* span1, const SkTSpan<OppCurve, TCurve>* span2 + SkDEBUGPARAMS(SkIntersections* i)) { + SkClosestRecord<TCurve, OppCurve>* record = &fClosest[fUsed]; + record->findEnd(span1, span2, 0, 0); + record->findEnd(span1, span2, 0, OppCurve::kPointLast); + record->findEnd(span1, span2, TCurve::kPointLast, 0); + record->findEnd(span1, span2, TCurve::kPointLast, OppCurve::kPointLast); + if (record->fClosest == FLT_MAX) { + return false; + } + for (int index = 0; index < fUsed; ++index) { + SkClosestRecord<TCurve, OppCurve>* test = &fClosest[index]; + if (test->matesWith(*record SkDEBUGPARAMS(i))) { + if (test->fClosest > record->fClosest) { + test->merge(*record); + } + test->update(*record); + record->reset(); + return false; + } + } + ++fUsed; + fClosest.push_back().reset(); + return true; + } + + void finish(SkIntersections* intersections) const { + SkSTArray<TCurve::kMaxIntersections * 3, + const SkClosestRecord<TCurve, OppCurve>*, true> closestPtrs; + for (int index = 0; index < fUsed; ++index) { + closestPtrs.push_back(&fClosest[index]); + } + SkTQSort<const SkClosestRecord<TCurve, OppCurve> >(closestPtrs.begin(), closestPtrs.end() + - 1); + for (int index = 0; index < fUsed; ++index) { + const SkClosestRecord<TCurve, OppCurve>* test = closestPtrs[index]; + test->addIntersection(intersections); + } + } + + // this is oversized so that an extra records can merge into final one + SkSTArray<TCurve::kMaxIntersections * 2, SkClosestRecord<TCurve, OppCurve>, true> fClosest; + int fUsed; +}; + +// returns true if the rect is too small to consider +template<typename TCurve, typename OppCurve> +void SkTSect<TCurve, OppCurve>::BinarySearch(SkTSect<TCurve, OppCurve>* sect1, + SkTSect<OppCurve, TCurve>* sect2, SkIntersections* intersections) { +#if DEBUG_T_SECT_DUMP > 1 + gDumpTSectNum = 0; +#endif + SkDEBUGCODE(sect1->fOppSect = sect2); + SkDEBUGCODE(sect2->fOppSect = sect1); + intersections->reset(); + intersections->setMax(TCurve::kMaxIntersections + 3); // give extra for slop + SkTSpan<TCurve, OppCurve>* span1 = sect1->fHead; + SkTSpan<OppCurve, TCurve>* span2 = sect2->fHead; + int oppSect, sect = sect1->intersects(span1, sect2, span2, &oppSect); +// SkASSERT(between(0, sect, 2)); + if (!sect) { + return; + } + if (sect == 2 && oppSect == 2) { + (void) EndsEqual(sect1, sect2, intersections); + return; + } + span1->addBounded(span2, §1->fHeap); + span2->addBounded(span1, §2->fHeap); + const int kMaxCoinLoopCount = 8; + int coinLoopCount = kMaxCoinLoopCount; + double start1s SK_INIT_TO_AVOID_WARNING; + double start1e SK_INIT_TO_AVOID_WARNING; + do { + // find the largest bounds + SkTSpan<TCurve, OppCurve>* largest1 = sect1->boundsMax(); + if (!largest1) { + break; + } + SkTSpan<OppCurve, TCurve>* largest2 = sect2->boundsMax(); + sect1->fRemovedStartT = sect1->fRemovedEndT = false; + sect2->fRemovedStartT = sect2->fRemovedEndT = false; + // split it + if (!largest2 || (largest1 && (largest1->fBoundsMax > largest2->fBoundsMax + || (!largest1->fCollapsed && largest2->fCollapsed)))) { + if (largest1->fCollapsed) { + break; + } + // trim parts that don't intersect the opposite + SkTSpan<TCurve, OppCurve>* half1 = sect1->addOne(); + SkDEBUGCODE(half1->debugSetGlobalState(sect1->globalState())); + if (!half1->split(largest1, §1->fHeap)) { + break; + } + sect1->trim(largest1, sect2); + sect1->trim(half1, sect2); + } else { + if (largest2->fCollapsed) { + break; + } + // trim parts that don't intersect the opposite + SkTSpan<OppCurve, TCurve>* half2 = sect2->addOne(); + SkDEBUGCODE(half2->debugSetGlobalState(sect2->globalState())); + if (!half2->split(largest2, §2->fHeap)) { + break; + } + sect2->trim(largest2, sect1); + sect2->trim(half2, sect1); + } + sect1->validate(); + sect2->validate(); +#if DEBUG_T_SECT_LOOP_COUNT + intersections->debugBumpLoopCount(SkIntersections::kIterations_DebugLoop); +#endif + // if there are 9 or more continuous spans on both sects, suspect coincidence + if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT + && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { + if (coinLoopCount == kMaxCoinLoopCount) { + start1s = sect1->fHead->fStartT; + start1e = sect1->tail()->fEndT; + } + if (!sect1->coincidentCheck(sect2)) { + return; + } + sect1->validate(); + sect2->validate(); +#if DEBUG_T_SECT_LOOP_COUNT + intersections->debugBumpLoopCount(SkIntersections::kCoinCheck_DebugLoop); +#endif + if (!--coinLoopCount && sect1->fHead && sect2->fHead) { + /* All known working cases resolve in two tries. Sadly, cubicConicTests[0] + gets stuck in a loop. It adds an extension to allow a coincident end + perpendicular to track its intersection in the opposite curve. However, + the bounding box of the extension does not intersect the original curve, + so the extension is discarded, only to be added again the next time around. */ + sect1->coincidentForce(sect2, start1s, start1e); + sect1->validate(); + sect2->validate(); + } + } + if (sect1->fActiveCount >= COINCIDENT_SPAN_COUNT + && sect2->fActiveCount >= COINCIDENT_SPAN_COUNT) { + sect1->computePerpendiculars(sect2, sect1->fHead, sect1->tail()); + sect2->computePerpendiculars(sect1, sect2->fHead, sect2->tail()); + sect1->removeByPerpendicular(sect2); + sect1->validate(); + sect2->validate(); +#if DEBUG_T_SECT_LOOP_COUNT + intersections->debugBumpLoopCount(SkIntersections::kComputePerp_DebugLoop); +#endif + if (sect1->collapsed() > TCurve::kMaxIntersections) { + break; + } + } +#if DEBUG_T_SECT_DUMP + sect1->dumpBoth(sect2); +#endif + if (!sect1->fHead || !sect2->fHead) { + break; + } + } while (true); + SkTSpan<TCurve, OppCurve>* coincident = sect1->fCoincident; + if (coincident) { + // if there is more than one coincident span, check loosely to see if they should be joined + if (coincident->fNext) { + sect1->mergeCoincidence(sect2); + coincident = sect1->fCoincident; + } + SkASSERT(sect2->fCoincident); // courtesy check : coincidence only looks at sect 1 + do { + if (!coincident->fCoinStart.isMatch()) { + continue; + } + if (!coincident->fCoinEnd.isMatch()) { + continue; + } + int index = intersections->insertCoincident(coincident->fStartT, + coincident->fCoinStart.perpT(), coincident->fPart[0]); + if ((intersections->insertCoincident(coincident->fEndT, + coincident->fCoinEnd.perpT(), + coincident->fPart[TCurve::kPointLast]) < 0) && index >= 0) { + intersections->clearCoincidence(index); + } + } while ((coincident = coincident->fNext)); + } + int zeroOneSet = EndsEqual(sect1, sect2, intersections); + if (!sect1->fHead || !sect2->fHead) { + // if the final iteration contains an end (0 or 1), + if (sect1->fRemovedStartT && !(zeroOneSet & kZeroS1Set)) { + SkTCoincident<TCurve, OppCurve> perp; // intersect perpendicular with opposite curve + perp.setPerp(sect1->fCurve, 0, sect1->fCurve.fPts[0], sect2->fCurve); + if (perp.isMatch()) { + intersections->insert(0, perp.perpT(), perp.perpPt()); + } + } + if (sect1->fRemovedEndT && !(zeroOneSet & kOneS1Set)) { + SkTCoincident<TCurve, OppCurve> perp; + perp.setPerp(sect1->fCurve, 1, sect1->fCurve.fPts[TCurve::kPointLast], sect2->fCurve); + if (perp.isMatch()) { + intersections->insert(1, perp.perpT(), perp.perpPt()); + } + } + if (sect2->fRemovedStartT && !(zeroOneSet & kZeroS2Set)) { + SkTCoincident<OppCurve, TCurve> perp; + perp.setPerp(sect2->fCurve, 0, sect2->fCurve.fPts[0], sect1->fCurve); + if (perp.isMatch()) { + intersections->insert(perp.perpT(), 0, perp.perpPt()); + } + } + if (sect2->fRemovedEndT && !(zeroOneSet & kOneS2Set)) { + SkTCoincident<OppCurve, TCurve> perp; + perp.setPerp(sect2->fCurve, 1, sect2->fCurve.fPts[OppCurve::kPointLast], sect1->fCurve); + if (perp.isMatch()) { + intersections->insert(perp.perpT(), 1, perp.perpPt()); + } + } + return; + } + sect1->recoverCollapsed(); + sect2->recoverCollapsed(); + SkTSpan<TCurve, OppCurve>* result1 = sect1->fHead; + // check heads and tails for zero and ones and insert them if we haven't already done so + const SkTSpan<TCurve, OppCurve>* head1 = result1; + if (!(zeroOneSet & kZeroS1Set) && approximately_less_than_zero(head1->fStartT)) { + const SkDPoint& start1 = sect1->fCurve[0]; + if (head1->isBounded()) { + double t = head1->closestBoundedT(start1); + if (sect2->fCurve.ptAtT(t).approximatelyEqual(start1)) { + intersections->insert(0, t, start1); + } + } + } + const SkTSpan<OppCurve, TCurve>* head2 = sect2->fHead; + if (!(zeroOneSet & kZeroS2Set) && approximately_less_than_zero(head2->fStartT)) { + const SkDPoint& start2 = sect2->fCurve[0]; + if (head2->isBounded()) { + double t = head2->closestBoundedT(start2); + if (sect1->fCurve.ptAtT(t).approximatelyEqual(start2)) { + intersections->insert(t, 0, start2); + } + } + } + const SkTSpan<TCurve, OppCurve>* tail1 = sect1->tail(); + if (!(zeroOneSet & kOneS1Set) && approximately_greater_than_one(tail1->fEndT)) { + const SkDPoint& end1 = sect1->fCurve[TCurve::kPointLast]; + if (tail1->isBounded()) { + double t = tail1->closestBoundedT(end1); + if (sect2->fCurve.ptAtT(t).approximatelyEqual(end1)) { + intersections->insert(1, t, end1); + } + } + } + const SkTSpan<OppCurve, TCurve>* tail2 = sect2->tail(); + if (!(zeroOneSet & kOneS2Set) && approximately_greater_than_one(tail2->fEndT)) { + const SkDPoint& end2 = sect2->fCurve[OppCurve::kPointLast]; + if (tail2->isBounded()) { + double t = tail2->closestBoundedT(end2); + if (sect1->fCurve.ptAtT(t).approximatelyEqual(end2)) { + intersections->insert(t, 1, end2); + } + } + } + SkClosestSect<TCurve, OppCurve> closest; + do { + while (result1 && result1->fCoinStart.isMatch() && result1->fCoinEnd.isMatch()) { + result1 = result1->fNext; + } + if (!result1) { + break; + } + SkTSpan<OppCurve, TCurve>* result2 = sect2->fHead; + bool found = false; + while (result2) { + found |= closest.find(result1, result2 SkDEBUGPARAMS(intersections)); + result2 = result2->fNext; + } + } while ((result1 = result1->fNext)); + closest.finish(intersections); + // if there is more than one intersection and it isn't already coincident, check + int last = intersections->used() - 1; + for (int index = 0; index < last; ) { + if (intersections->isCoincident(index) && intersections->isCoincident(index + 1)) { + ++index; + continue; + } + double midT = ((*intersections)[0][index] + (*intersections)[0][index + 1]) / 2; + SkDPoint midPt = sect1->fCurve.ptAtT(midT); + // intersect perpendicular with opposite curve + SkTCoincident<TCurve, OppCurve> perp; + perp.setPerp(sect1->fCurve, midT, midPt, sect2->fCurve); + if (!perp.isMatch()) { + ++index; + continue; + } + if (intersections->isCoincident(index)) { + intersections->removeOne(index); + --last; + } else if (intersections->isCoincident(index + 1)) { + intersections->removeOne(index + 1); + --last; + } else { + intersections->setCoincident(index++); + } + intersections->setCoincident(index); + } + SkASSERT(intersections->used() <= TCurve::kMaxIntersections); +} + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTightBounds.cpp b/gfx/skia/skia/src/pathops/SkPathOpsTightBounds.cpp new file mode 100644 index 000000000..d748ff538 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTightBounds.cpp @@ -0,0 +1,83 @@ +/* + * Copyright 2014 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkOpEdgeBuilder.h" +#include "SkPathOpsCommon.h" + +bool TightBounds(const SkPath& path, SkRect* result) { + SkPath::RawIter iter(path); + SkRect moveBounds = { SK_ScalarMax, SK_ScalarMax, SK_ScalarMin, SK_ScalarMin }; + bool wellBehaved = true; + SkPath::Verb verb; + do { + SkPoint pts[4]; + verb = iter.next(pts); + switch (verb) { + case SkPath::kMove_Verb: + moveBounds.fLeft = SkTMin(moveBounds.fLeft, pts[0].fX); + moveBounds.fTop = SkTMin(moveBounds.fTop, pts[0].fY); + moveBounds.fRight = SkTMax(moveBounds.fRight, pts[0].fX); + moveBounds.fBottom = SkTMax(moveBounds.fBottom, pts[0].fY); + break; + case SkPath::kQuad_Verb: + case SkPath::kConic_Verb: + if (!wellBehaved) { + break; + } + wellBehaved &= between(pts[0].fX, pts[1].fX, pts[2].fX); + wellBehaved &= between(pts[0].fY, pts[1].fY, pts[2].fY); + break; + case SkPath::kCubic_Verb: + if (!wellBehaved) { + break; + } + wellBehaved &= between(pts[0].fX, pts[1].fX, pts[3].fX); + wellBehaved &= between(pts[0].fY, pts[1].fY, pts[3].fY); + wellBehaved &= between(pts[0].fX, pts[2].fX, pts[3].fX); + wellBehaved &= between(pts[0].fY, pts[2].fY, pts[3].fY); + break; + default: + break; + } + } while (verb != SkPath::kDone_Verb); + if (wellBehaved) { + *result = path.getBounds(); + return true; + } + SkChunkAlloc allocator(4096); // FIXME: constant-ize, tune + SkOpContour contour; + SkOpContourHead* contourList = static_cast<SkOpContourHead*>(&contour); + SkOpGlobalState globalState(contourList, &allocator SkDEBUGPARAMS(false) + SkDEBUGPARAMS(nullptr)); + // turn path into list of segments + SkScalar scaleFactor = ScaleFactor(path); + SkPath scaledPath; + const SkPath* workingPath; + if (scaleFactor > SK_Scalar1) { + ScalePath(path, 1.f / scaleFactor, &scaledPath); + workingPath = &scaledPath; + } else { + workingPath = &path; + } + SkOpEdgeBuilder builder(*workingPath, contourList, &globalState); + if (!builder.finish()) { + return false; + } + if (!SortContourList(&contourList, false, false)) { + *result = moveBounds; + return true; + } + SkOpContour* current = contourList; + SkPathOpsBounds bounds = current->bounds(); + while ((current = current->next())) { + bounds.add(current->bounds()); + } + *result = bounds; + if (!moveBounds.isEmpty()) { + result->join(moveBounds); + } + return true; +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTypes.cpp b/gfx/skia/skia/src/pathops/SkPathOpsTypes.cpp new file mode 100644 index 000000000..5f87076c2 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTypes.cpp @@ -0,0 +1,251 @@ +/* + * 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 "SkFloatBits.h" +#include "SkOpCoincidence.h" +#include "SkPathOpsTypes.h" + +static bool arguments_denormalized(float a, float b, int epsilon) { + float denormalizedCheck = FLT_EPSILON * epsilon / 2; + return fabsf(a) <= denormalizedCheck && fabsf(b) <= denormalizedCheck; +} + +// from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ +// FIXME: move to SkFloatBits.h +static bool equal_ulps(float a, float b, int epsilon, int depsilon) { + if (arguments_denormalized(a, b, depsilon)) { + return true; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +static bool equal_ulps_no_normal_check(float a, float b, int epsilon, int depsilon) { + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +static bool equal_ulps_pin(float a, float b, int epsilon, int depsilon) { + if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) { + return false; + } + if (arguments_denormalized(a, b, depsilon)) { + return true; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +static bool d_equal_ulps(float a, float b, int epsilon) { + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon && bBits < aBits + epsilon; +} + +static bool not_equal_ulps(float a, float b, int epsilon) { + if (arguments_denormalized(a, b, epsilon)) { + return false; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits >= bBits + epsilon || bBits >= aBits + epsilon; +} + +static bool not_equal_ulps_pin(float a, float b, int epsilon) { + if (!SkScalarIsFinite(a) || !SkScalarIsFinite(b)) { + return false; + } + if (arguments_denormalized(a, b, epsilon)) { + return false; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits >= bBits + epsilon || bBits >= aBits + epsilon; +} + +static bool d_not_equal_ulps(float a, float b, int epsilon) { + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits >= bBits + epsilon || bBits >= aBits + epsilon; +} + +static bool less_ulps(float a, float b, int epsilon) { + if (arguments_denormalized(a, b, epsilon)) { + return a <= b - FLT_EPSILON * epsilon; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits <= bBits - epsilon; +} + +static bool less_or_equal_ulps(float a, float b, int epsilon) { + if (arguments_denormalized(a, b, epsilon)) { + return a < b + FLT_EPSILON * epsilon; + } + int aBits = SkFloatAs2sCompliment(a); + int bBits = SkFloatAs2sCompliment(b); + // Find the difference in ULPs. + return aBits < bBits + epsilon; +} + +// equality using the same error term as between +bool AlmostBequalUlps(float a, float b) { + const int UlpsEpsilon = 2; + return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool AlmostPequalUlps(float a, float b) { + const int UlpsEpsilon = 8; + return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool AlmostDequalUlps(float a, float b) { + const int UlpsEpsilon = 16; + return d_equal_ulps(a, b, UlpsEpsilon); +} + +bool AlmostDequalUlps(double a, double b) { + return AlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostEqualUlps(float a, float b) { + const int UlpsEpsilon = 16; + return equal_ulps(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool AlmostEqualUlpsNoNormalCheck(float a, float b) { + const int UlpsEpsilon = 16; + return equal_ulps_no_normal_check(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool AlmostEqualUlps_Pin(float a, float b) { + const int UlpsEpsilon = 16; + return equal_ulps_pin(a, b, UlpsEpsilon, UlpsEpsilon); +} + +bool NotAlmostEqualUlps(float a, float b) { + const int UlpsEpsilon = 16; + return not_equal_ulps(a, b, UlpsEpsilon); +} + +bool NotAlmostEqualUlps_Pin(float a, float b) { + const int UlpsEpsilon = 16; + return not_equal_ulps_pin(a, b, UlpsEpsilon); +} + +bool NotAlmostDequalUlps(float a, float b) { + const int UlpsEpsilon = 16; + return d_not_equal_ulps(a, b, UlpsEpsilon); +} + +bool RoughlyEqualUlps(float a, float b) { + const int UlpsEpsilon = 256; + const int DUlpsEpsilon = 1024; + return equal_ulps(a, b, UlpsEpsilon, DUlpsEpsilon); +} + +bool AlmostBetweenUlps(float a, float b, float c) { + const int UlpsEpsilon = 2; + return a <= c ? less_or_equal_ulps(a, b, UlpsEpsilon) && less_or_equal_ulps(b, c, UlpsEpsilon) + : less_or_equal_ulps(b, a, UlpsEpsilon) && less_or_equal_ulps(c, b, UlpsEpsilon); +} + +bool AlmostLessUlps(float a, float b) { + const int UlpsEpsilon = 16; + return less_ulps(a, b, UlpsEpsilon); +} + +bool AlmostLessOrEqualUlps(float a, float b) { + const int UlpsEpsilon = 16; + return less_or_equal_ulps(a, b, UlpsEpsilon); +} + +int UlpsDistance(float a, float b) { + SkFloatIntUnion floatIntA, floatIntB; + floatIntA.fFloat = a; + floatIntB.fFloat = b; + // Different signs means they do not match. + if ((floatIntA.fSignBitInt < 0) != (floatIntB.fSignBitInt < 0)) { + // Check for equality to make sure +0 == -0 + return a == b ? 0 : SK_MaxS32; + } + // Find the difference in ULPs. + return SkTAbs(floatIntA.fSignBitInt - floatIntB.fSignBitInt); +} + +// cube root approximation using bit hack for 64-bit float +// adapted from Kahan's cbrt +static double cbrt_5d(double d) { + const unsigned int B1 = 715094163; + double t = 0.0; + unsigned int* pt = (unsigned int*) &t; + unsigned int* px = (unsigned int*) &d; + pt[1] = px[1] / 3 + B1; + return t; +} + +// iterative cube root approximation using Halley's method (double) +static double cbrta_halleyd(const double a, const double R) { + const double a3 = a * a * a; + const double b = a * (a3 + R + R) / (a3 + a3 + R); + return b; +} + +// cube root approximation using 3 iterations of Halley's method (double) +static double halley_cbrt3d(double d) { + double a = cbrt_5d(d); + a = cbrta_halleyd(a, d); + a = cbrta_halleyd(a, d); + return cbrta_halleyd(a, d); +} + +double SkDCubeRoot(double x) { + if (approximately_zero_cubed(x)) { + return 0; + } + double result = halley_cbrt3d(fabs(x)); + if (x < 0) { + result = -result; + } + return result; +} + +SkOpGlobalState::SkOpGlobalState(SkOpContourHead* head, + SkChunkAlloc* allocator + SkDEBUGPARAMS(bool debugSkipAssert) + SkDEBUGPARAMS(const char* testName)) + : fAllocator(allocator) + , fCoincidence(nullptr) + , fContourHead(head) + , fNested(0) + , fWindingFailed(false) + , fPhase(SkOpPhase::kIntersecting) + SkDEBUGPARAMS(fDebugTestName(testName)) + SkDEBUGPARAMS(fAngleID(0)) + SkDEBUGPARAMS(fCoinID(0)) + SkDEBUGPARAMS(fContourID(0)) + SkDEBUGPARAMS(fPtTID(0)) + SkDEBUGPARAMS(fSegmentID(0)) + SkDEBUGPARAMS(fSpanID(0)) + SkDEBUGPARAMS(fDebugSkipAssert(debugSkipAssert)) { +#if DEBUG_T_SECT_LOOP_COUNT + debugResetLoopCounts(); +#endif +#if DEBUG_COIN + fPreviousFuncName = nullptr; +#endif +} diff --git a/gfx/skia/skia/src/pathops/SkPathOpsTypes.h b/gfx/skia/skia/src/pathops/SkPathOpsTypes.h new file mode 100644 index 000000000..786eb2288 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsTypes.h @@ -0,0 +1,618 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsTypes_DEFINED +#define SkPathOpsTypes_DEFINED + +#include <float.h> // for FLT_EPSILON +#include <math.h> // for fabs, sqrt + +#include "SkFloatingPoint.h" +#include "SkPath.h" +#include "SkPathOps.h" +#include "SkPathOpsDebug.h" +#include "SkScalar.h" + +enum SkPathOpsMask { + kWinding_PathOpsMask = -1, + kNo_PathOpsMask = 0, + kEvenOdd_PathOpsMask = 1 +}; + +class SkChunkAlloc; +class SkOpCoincidence; +class SkOpContour; +class SkOpContourHead; +class SkIntersections; +class SkIntersectionHelper; + +enum class SkOpPhase : char { + kNoChange, + kIntersecting, + kWalking, + kFixWinding, +}; + +class SkOpGlobalState { +public: + SkOpGlobalState(SkOpContourHead* head, + SkChunkAlloc* allocator SkDEBUGPARAMS(bool debugSkipAssert) + SkDEBUGPARAMS(const char* testName)); + + enum { + kMaxWindingTries = 10 + }; + + bool allocatedOpSpan() const { + return fAllocatedOpSpan; + } + + SkChunkAlloc* allocator() { + return fAllocator; + } + + void bumpNested() { + ++fNested; + } + + void clearNested() { + fNested = 0; + } + + SkOpCoincidence* coincidence() { + return fCoincidence; + } + + SkOpContourHead* contourHead() { + return fContourHead; + } + +#ifdef SK_DEBUG + const class SkOpAngle* debugAngle(int id) const; + const SkOpCoincidence* debugCoincidence() const; + SkOpContour* debugContour(int id) const; + const class SkOpPtT* debugPtT(int id) const; + bool debugRunFail() const; + const class SkOpSegment* debugSegment(int id) const; + bool debugSkipAssert() const { return fDebugSkipAssert; } + const class SkOpSpanBase* debugSpan(int id) const; + const char* debugTestName() const { return fDebugTestName; } +#endif + +#if DEBUG_T_SECT_LOOP_COUNT + void debugAddLoopCount(SkIntersections* , const SkIntersectionHelper& , + const SkIntersectionHelper& ); + void debugDoYourWorst(SkOpGlobalState* ); + void debugLoopReport(); + void debugResetLoopCounts(); +#endif + +#if DEBUG_COINCIDENCE + void debugSetCheckHealth(bool check) { fDebugCheckHealth = check; } + bool debugCheckHealth() const { return fDebugCheckHealth; } +#endif + +#if DEBUG_VALIDATE || DEBUG_COIN + void debugSetPhase(const char* funcName DEBUG_COIN_DECLARE_PARAMS()) const; +#endif + +#if DEBUG_COIN + void debugAddToCoinChangedDict(); + void debugAddToGlobalCoinDicts(); + SkPathOpsDebug::CoinDict* debugCoinChangedDict() { return &fCoinChangedDict; } + const SkPathOpsDebug::CoinDictEntry& debugCoinDictEntry() const { return fCoinDictEntry; } + + static void DumpCoinDict(); +#endif + + + int nested() const { + return fNested; + } + +#ifdef SK_DEBUG + int nextAngleID() { + return ++fAngleID; + } + + int nextCoinID() { + return ++fCoinID; + } + + int nextContourID() { + return ++fContourID; + } + + int nextPtTID() { + return ++fPtTID; + } + + int nextSegmentID() { + return ++fSegmentID; + } + + int nextSpanID() { + return ++fSpanID; + } +#endif + + SkOpPhase phase() const { + return fPhase; + } + + void resetAllocatedOpSpan() { + fAllocatedOpSpan = false; + } + + void setAllocatedOpSpan() { + fAllocatedOpSpan = true; + } + + void setCoincidence(SkOpCoincidence* coincidence) { + fCoincidence = coincidence; + } + + void setContourHead(SkOpContourHead* contourHead) { + fContourHead = contourHead; + } + + void setPhase(SkOpPhase phase) { + if (SkOpPhase::kNoChange == phase) { + return; + } + SkASSERT(fPhase != phase); + fPhase = phase; + } + + // called in very rare cases where angles are sorted incorrectly -- signfies op will fail + void setWindingFailed() { + fWindingFailed = true; + } + + bool windingFailed() const { + return fWindingFailed; + } + +private: + SkChunkAlloc* fAllocator; + SkOpCoincidence* fCoincidence; + SkOpContourHead* fContourHead; + int fNested; + bool fAllocatedOpSpan; + bool fWindingFailed; + SkOpPhase fPhase; +#ifdef SK_DEBUG + const char* fDebugTestName; + void* fDebugReporter; + int fAngleID; + int fCoinID; + int fContourID; + int fPtTID; + int fSegmentID; + int fSpanID; + bool fDebugSkipAssert; +#endif +#if DEBUG_T_SECT_LOOP_COUNT + int fDebugLoopCount[3]; + SkPath::Verb fDebugWorstVerb[6]; + SkPoint fDebugWorstPts[24]; + float fDebugWorstWeight[6]; +#endif +#if DEBUG_COIN + SkPathOpsDebug::CoinDict fCoinChangedDict; + SkPathOpsDebug::CoinDict fCoinVisitedDict; + SkPathOpsDebug::CoinDictEntry fCoinDictEntry; + const char* fPreviousFuncName; +#endif +#if DEBUG_COINCIDENCE + bool fDebugCheckHealth; +#endif +}; + +#ifdef SK_DEBUG +#if DEBUG_COINCIDENCE +#define SkOPASSERT(cond) SkASSERT((this->globalState() && \ + (this->globalState()->debugCheckHealth() || \ + this->globalState()->debugSkipAssert())) || (cond)) +#else +#define SkOPASSERT(cond) SkASSERT((this->globalState() && \ + this->globalState()->debugSkipAssert()) || (cond)) +#endif +#define SkOPOBJASSERT(obj, cond) SkASSERT((obj->debugGlobalState() && \ + obj->debugGlobalState()->debugSkipAssert()) || (cond)) +#else +#define SkOPASSERT(cond) +#define SkOPOBJASSERT(obj, cond) +#endif + +// Use Almost Equal when comparing coordinates. Use epsilon to compare T values. +bool AlmostEqualUlps(float a, float b); +inline bool AlmostEqualUlps(double a, double b) { + return AlmostEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostEqualUlpsNoNormalCheck(float a, float b); +inline bool AlmostEqualUlpsNoNormalCheck(double a, double b) { + return AlmostEqualUlpsNoNormalCheck(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostEqualUlps_Pin(float a, float b); +inline bool AlmostEqualUlps_Pin(double a, double b) { + return AlmostEqualUlps_Pin(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +// Use Almost Dequal when comparing should not special case denormalized values. +bool AlmostDequalUlps(float a, float b); +bool AlmostDequalUlps(double a, double b); + +bool NotAlmostEqualUlps(float a, float b); +inline bool NotAlmostEqualUlps(double a, double b) { + return NotAlmostEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool NotAlmostEqualUlps_Pin(float a, float b); +inline bool NotAlmostEqualUlps_Pin(double a, double b) { + return NotAlmostEqualUlps_Pin(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool NotAlmostDequalUlps(float a, float b); +inline bool NotAlmostDequalUlps(double a, double b) { + return NotAlmostDequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +// Use Almost Bequal when comparing coordinates in conjunction with between. +bool AlmostBequalUlps(float a, float b); +inline bool AlmostBequalUlps(double a, double b) { + return AlmostBequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostPequalUlps(float a, float b); +inline bool AlmostPequalUlps(double a, double b) { + return AlmostPequalUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool RoughlyEqualUlps(float a, float b); +inline bool RoughlyEqualUlps(double a, double b) { + return RoughlyEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostLessUlps(float a, float b); +inline bool AlmostLessUlps(double a, double b) { + return AlmostLessUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostLessOrEqualUlps(float a, float b); +inline bool AlmostLessOrEqualUlps(double a, double b) { + return AlmostLessOrEqualUlps(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +bool AlmostBetweenUlps(float a, float b, float c); +inline bool AlmostBetweenUlps(double a, double b, double c) { + return AlmostBetweenUlps(SkDoubleToScalar(a), SkDoubleToScalar(b), SkDoubleToScalar(c)); +} + +int UlpsDistance(float a, float b); +inline int UlpsDistance(double a, double b) { + return UlpsDistance(SkDoubleToScalar(a), SkDoubleToScalar(b)); +} + +// FLT_EPSILON == 1.19209290E-07 == 1 / (2 ^ 23) +// DBL_EPSILON == 2.22045e-16 +const double FLT_EPSILON_CUBED = FLT_EPSILON * FLT_EPSILON * FLT_EPSILON; +const double FLT_EPSILON_HALF = FLT_EPSILON / 2; +const double FLT_EPSILON_DOUBLE = FLT_EPSILON * 2; +const double FLT_EPSILON_ORDERABLE_ERR = FLT_EPSILON * 16; +const double FLT_EPSILON_SQUARED = FLT_EPSILON * FLT_EPSILON; +const double FLT_EPSILON_SQRT = sqrt(FLT_EPSILON); +const double FLT_EPSILON_INVERSE = 1 / FLT_EPSILON; +const double DBL_EPSILON_ERR = DBL_EPSILON * 4; // FIXME: tune -- allow a few bits of error +const double DBL_EPSILON_SUBDIVIDE_ERR = DBL_EPSILON * 16; +const double ROUGH_EPSILON = FLT_EPSILON * 64; +const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256; +const double WAY_ROUGH_EPSILON = FLT_EPSILON * 2048; +const double BUMP_EPSILON = FLT_EPSILON * 4096; + +const SkScalar INVERSE_NUMBER_RANGE = FLT_EPSILON_ORDERABLE_ERR; + +inline bool zero_or_one(double x) { + return x == 0 || x == 1; +} + +inline bool approximately_zero(double x) { + return fabs(x) < FLT_EPSILON; +} + +inline bool precisely_zero(double x) { + return fabs(x) < DBL_EPSILON_ERR; +} + +inline bool precisely_subdivide_zero(double x) { + return fabs(x) < DBL_EPSILON_SUBDIVIDE_ERR; +} + +inline bool approximately_zero(float x) { + return fabs(x) < FLT_EPSILON; +} + +inline bool approximately_zero_cubed(double x) { + return fabs(x) < FLT_EPSILON_CUBED; +} + +inline bool approximately_zero_half(double x) { + return fabs(x) < FLT_EPSILON_HALF; +} + +inline bool approximately_zero_double(double x) { + return fabs(x) < FLT_EPSILON_DOUBLE; +} + +inline bool approximately_zero_orderable(double x) { + return fabs(x) < FLT_EPSILON_ORDERABLE_ERR; +} + +inline bool approximately_zero_squared(double x) { + return fabs(x) < FLT_EPSILON_SQUARED; +} + +inline bool approximately_zero_sqrt(double x) { + return fabs(x) < FLT_EPSILON_SQRT; +} + +inline bool roughly_zero(double x) { + return fabs(x) < ROUGH_EPSILON; +} + +inline bool approximately_zero_inverse(double x) { + return fabs(x) > FLT_EPSILON_INVERSE; +} + +inline bool approximately_zero_when_compared_to(double x, double y) { + return x == 0 || fabs(x) < fabs(y * FLT_EPSILON); +} + +inline bool precisely_zero_when_compared_to(double x, double y) { + return x == 0 || fabs(x) < fabs(y * DBL_EPSILON); +} + +inline bool roughly_zero_when_compared_to(double x, double y) { + return x == 0 || fabs(x) < fabs(y * ROUGH_EPSILON); +} + +// Use this for comparing Ts in the range of 0 to 1. For general numbers (larger and smaller) use +// AlmostEqualUlps instead. +inline bool approximately_equal(double x, double y) { + return approximately_zero(x - y); +} + +inline bool precisely_equal(double x, double y) { + return precisely_zero(x - y); +} + +inline bool precisely_subdivide_equal(double x, double y) { + return precisely_subdivide_zero(x - y); +} + +inline bool approximately_equal_half(double x, double y) { + return approximately_zero_half(x - y); +} + +inline bool approximately_equal_double(double x, double y) { + return approximately_zero_double(x - y); +} + +inline bool approximately_equal_orderable(double x, double y) { + return approximately_zero_orderable(x - y); +} + +inline bool approximately_equal_squared(double x, double y) { + return approximately_equal(x, y); +} + +inline bool approximately_greater(double x, double y) { + return x - FLT_EPSILON >= y; +} + +inline bool approximately_greater_double(double x, double y) { + return x - FLT_EPSILON_DOUBLE >= y; +} + +inline bool approximately_greater_orderable(double x, double y) { + return x - FLT_EPSILON_ORDERABLE_ERR >= y; +} + +inline bool approximately_greater_or_equal(double x, double y) { + return x + FLT_EPSILON > y; +} + +inline bool approximately_greater_or_equal_double(double x, double y) { + return x + FLT_EPSILON_DOUBLE > y; +} + +inline bool approximately_greater_or_equal_orderable(double x, double y) { + return x + FLT_EPSILON_ORDERABLE_ERR > y; +} + +inline bool approximately_lesser(double x, double y) { + return x + FLT_EPSILON <= y; +} + +inline bool approximately_lesser_double(double x, double y) { + return x + FLT_EPSILON_DOUBLE <= y; +} + +inline bool approximately_lesser_orderable(double x, double y) { + return x + FLT_EPSILON_ORDERABLE_ERR <= y; +} + +inline bool approximately_lesser_or_equal(double x, double y) { + return x - FLT_EPSILON < y; +} + +inline bool approximately_lesser_or_equal_double(double x, double y) { + return x - FLT_EPSILON_DOUBLE < y; +} + +inline bool approximately_lesser_or_equal_orderable(double x, double y) { + return x - FLT_EPSILON_ORDERABLE_ERR < y; +} + +inline bool approximately_greater_than_one(double x) { + return x > 1 - FLT_EPSILON; +} + +inline bool precisely_greater_than_one(double x) { + return x > 1 - DBL_EPSILON_ERR; +} + +inline bool approximately_less_than_zero(double x) { + return x < FLT_EPSILON; +} + +inline bool precisely_less_than_zero(double x) { + return x < DBL_EPSILON_ERR; +} + +inline bool approximately_negative(double x) { + return x < FLT_EPSILON; +} + +inline bool approximately_negative_orderable(double x) { + return x < FLT_EPSILON_ORDERABLE_ERR; +} + +inline bool precisely_negative(double x) { + return x < DBL_EPSILON_ERR; +} + +inline bool approximately_one_or_less(double x) { + return x < 1 + FLT_EPSILON; +} + +inline bool approximately_one_or_less_double(double x) { + return x < 1 + FLT_EPSILON_DOUBLE; +} + +inline bool approximately_positive(double x) { + return x > -FLT_EPSILON; +} + +inline bool approximately_positive_squared(double x) { + return x > -(FLT_EPSILON_SQUARED); +} + +inline bool approximately_zero_or_more(double x) { + return x > -FLT_EPSILON; +} + +inline bool approximately_zero_or_more_double(double x) { + return x > -FLT_EPSILON_DOUBLE; +} + +inline bool approximately_between_orderable(double a, double b, double c) { + return a <= c + ? approximately_negative_orderable(a - b) && approximately_negative_orderable(b - c) + : approximately_negative_orderable(b - a) && approximately_negative_orderable(c - b); +} + +inline bool approximately_between(double a, double b, double c) { + return a <= c ? approximately_negative(a - b) && approximately_negative(b - c) + : approximately_negative(b - a) && approximately_negative(c - b); +} + +inline bool precisely_between(double a, double b, double c) { + return a <= c ? precisely_negative(a - b) && precisely_negative(b - c) + : precisely_negative(b - a) && precisely_negative(c - b); +} + +// returns true if (a <= b <= c) || (a >= b >= c) +inline bool between(double a, double b, double c) { + SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0) + || (precisely_zero(a) && precisely_zero(b) && precisely_zero(c))); + return (a - b) * (c - b) <= 0; +} + +inline bool roughly_equal(double x, double y) { + return fabs(x - y) < ROUGH_EPSILON; +} + +inline bool roughly_negative(double x) { + return x < ROUGH_EPSILON; +} + +inline bool roughly_between(double a, double b, double c) { + return a <= c ? roughly_negative(a - b) && roughly_negative(b - c) + : roughly_negative(b - a) && roughly_negative(c - b); +} + +inline bool more_roughly_equal(double x, double y) { + return fabs(x - y) < MORE_ROUGH_EPSILON; +} + +struct SkDPoint; +struct SkDVector; +struct SkDLine; +struct SkDQuad; +struct SkDConic; +struct SkDCubic; +struct SkDRect; + +inline SkPath::Verb SkPathOpsPointsToVerb(int points) { + int verb = (1 << points) >> 1; +#ifdef SK_DEBUG + switch (points) { + case 0: SkASSERT(SkPath::kMove_Verb == verb); break; + case 1: SkASSERT(SkPath::kLine_Verb == verb); break; + case 2: SkASSERT(SkPath::kQuad_Verb == verb); break; + case 3: SkASSERT(SkPath::kCubic_Verb == verb); break; + default: SkDEBUGFAIL("should not be here"); + } +#endif + return (SkPath::Verb)verb; +} + +inline int SkPathOpsVerbToPoints(SkPath::Verb verb) { + int points = (int) verb - (((int) verb + 1) >> 2); +#ifdef SK_DEBUG + switch (verb) { + case SkPath::kLine_Verb: SkASSERT(1 == points); break; + case SkPath::kQuad_Verb: SkASSERT(2 == points); break; + case SkPath::kConic_Verb: SkASSERT(2 == points); break; + case SkPath::kCubic_Verb: SkASSERT(3 == points); break; + default: SkDEBUGFAIL("should not get here"); + } +#endif + return points; +} + +inline double SkDInterp(double A, double B, double t) { + return A + (B - A) * t; +} + +double SkDCubeRoot(double x); + +/* Returns -1 if negative, 0 if zero, 1 if positive +*/ +inline int SkDSign(double x) { + return (x > 0) - (x < 0); +} + +/* Returns 0 if negative, 1 if zero, 2 if positive +*/ +inline int SKDSide(double x) { + return (x > 0) + (x >= 0); +} + +/* Returns 1 if negative, 2 if zero, 4 if positive +*/ +inline int SkDSideBit(double x) { + return 1 << SKDSide(x); +} + +inline double SkPinT(double t) { + return precisely_less_than_zero(t) ? 0 : precisely_greater_than_one(t) ? 1 : t; +} + +#endif diff --git a/gfx/skia/skia/src/pathops/SkPathOpsWinding.cpp b/gfx/skia/skia/src/pathops/SkPathOpsWinding.cpp new file mode 100644 index 000000000..35cabcf62 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathOpsWinding.cpp @@ -0,0 +1,416 @@ +/* + * Copyright 2015 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +// given a prospective edge, compute its initial winding by projecting a ray +// if the ray hits another edge + // if the edge doesn't have a winding yet, hop up to that edge and start over + // concern : check for hops forming a loop + // if the edge is unsortable, or + // the intersection is nearly at the ends, or + // the tangent at the intersection is nearly coincident to the ray, + // choose a different ray and try again + // concern : if it is unable to succeed after N tries, try another edge? direction? +// if no edge is hit, compute the winding directly + +// given the top span, project the most perpendicular ray and look for intersections + // let's try up and then down. What the hey + +// bestXY is initialized by caller with basePt + +#include "SkOpContour.h" +#include "SkOpSegment.h" +#include "SkPathOpsCurve.h" + +enum class SkOpRayDir { + kLeft, + kTop, + kRight, + kBottom, +}; + +#if DEBUG_WINDING +const char* gDebugRayDirName[] = { + "kLeft", + "kTop", + "kRight", + "kBottom" +}; +#endif + +static int xy_index(SkOpRayDir dir) { + return static_cast<int>(dir) & 1; +} + +static SkScalar pt_xy(const SkPoint& pt, SkOpRayDir dir) { + return (&pt.fX)[xy_index(dir)]; +} + +static SkScalar pt_yx(const SkPoint& pt, SkOpRayDir dir) { + return (&pt.fX)[!xy_index(dir)]; +} + +static double pt_dxdy(const SkDVector& v, SkOpRayDir dir) { + return (&v.fX)[xy_index(dir)]; +} + +static double pt_dydx(const SkDVector& v, SkOpRayDir dir) { + return (&v.fX)[!xy_index(dir)]; +} + +static SkScalar rect_side(const SkRect& r, SkOpRayDir dir) { + return (&r.fLeft)[static_cast<int>(dir)]; +} + +static bool sideways_overlap(const SkRect& rect, const SkPoint& pt, SkOpRayDir dir) { + int i = !xy_index(dir); + return approximately_between((&rect.fLeft)[i], (&pt.fX)[i], (&rect.fRight)[i]); +} + +static bool less_than(SkOpRayDir dir) { + return static_cast<bool>((static_cast<int>(dir) & 2) == 0); +} + +static bool ccw_dxdy(const SkDVector& v, SkOpRayDir dir) { + bool vPartPos = pt_dydx(v, dir) > 0; + bool leftBottom = ((static_cast<int>(dir) + 1) & 2) != 0; + return vPartPos == leftBottom; +} + +struct SkOpRayHit { + SkOpRayDir makeTestBase(SkOpSpan* span, double t) { + fNext = nullptr; + fSpan = span; + fT = span->t() * (1 - t) + span->next()->t() * t; + SkOpSegment* segment = span->segment(); + fSlope = segment->dSlopeAtT(fT); + fPt = segment->ptAtT(fT); + fValid = true; + return fabs(fSlope.fX) < fabs(fSlope.fY) ? SkOpRayDir::kLeft : SkOpRayDir::kTop; + } + + SkOpRayHit* fNext; + SkOpSpan* fSpan; + SkPoint fPt; + double fT; + SkDVector fSlope; + bool fValid; +}; + +void SkOpContour::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, + SkChunkAlloc* allocator) { + // if the bounds extreme is outside the best, we're done + SkScalar baseXY = pt_xy(base.fPt, dir); + SkScalar boundsXY = rect_side(fBounds, dir); + bool checkLessThan = less_than(dir); + if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) { + return; + } + SkOpSegment* testSegment = &fHead; + do { + testSegment->rayCheck(base, dir, hits, allocator); + } while ((testSegment = testSegment->next())); +} + +void SkOpSegment::rayCheck(const SkOpRayHit& base, SkOpRayDir dir, SkOpRayHit** hits, + SkChunkAlloc* allocator) { + if (!sideways_overlap(fBounds, base.fPt, dir)) { + return; + } + SkScalar baseXY = pt_xy(base.fPt, dir); + SkScalar boundsXY = rect_side(fBounds, dir); + bool checkLessThan = less_than(dir); + if (!approximately_equal(baseXY, boundsXY) && (baseXY < boundsXY) == checkLessThan) { + return; + } + double tVals[3]; + SkScalar baseYX = pt_yx(base.fPt, dir); + int roots = (*CurveIntercept[fVerb * 2 + xy_index(dir)])(fPts, fWeight, baseYX, tVals); + for (int index = 0; index < roots; ++index) { + double t = tVals[index]; + if (base.fSpan->segment() == this && approximately_equal(base.fT, t)) { + continue; + } + SkDVector slope; + SkPoint pt; + SkDEBUGCODE(sk_bzero(&slope, sizeof(slope))); + bool valid = false; + if (approximately_zero(t)) { + pt = fPts[0]; + } else if (approximately_equal(t, 1)) { + pt = fPts[SkPathOpsVerbToPoints(fVerb)]; + } else { + SkASSERT(between(0, t, 1)); + pt = this->ptAtT(t); + if (SkDPoint::ApproximatelyEqual(pt, base.fPt)) { + if (base.fSpan->segment() == this) { + continue; + } + } else { + SkScalar ptXY = pt_xy(pt, dir); + if (!approximately_equal(baseXY, ptXY) && (baseXY < ptXY) == checkLessThan) { + continue; + } + slope = this->dSlopeAtT(t); + if (fVerb == SkPath::kCubic_Verb && base.fSpan->segment() == this + && roughly_equal(base.fT, t) + && SkDPoint::RoughlyEqual(pt, base.fPt)) { + #if DEBUG_WINDING + SkDebugf("%s (rarely expect this)\n", __FUNCTION__); + #endif + continue; + } + if (fabs(pt_dydx(slope, dir) * 10000) > fabs(pt_dxdy(slope, dir))) { + valid = true; + } + } + } + SkOpSpan* span = this->windingSpanAtT(t); + if (!span) { + valid = false; + } else if (!span->windValue() && !span->oppValue()) { + continue; + } + SkOpRayHit* newHit = SkOpTAllocator<SkOpRayHit>::Allocate(allocator); + newHit->fNext = *hits; + newHit->fPt = pt; + newHit->fSlope = slope; + newHit->fSpan = span; + newHit->fT = t; + newHit->fValid = valid; + *hits = newHit; + } +} + +SkOpSpan* SkOpSegment::windingSpanAtT(double tHit) { + SkOpSpan* span = &fHead; + SkOpSpanBase* next; + do { + next = span->next(); + if (approximately_equal(tHit, next->t())) { + return nullptr; + } + if (tHit < next->t()) { + return span; + } + } while (!next->final() && (span = next->upCast())); + return nullptr; +} + +static bool hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) { + return a->fPt.fX < b->fPt.fX; +} + +static bool reverse_hit_compare_x(const SkOpRayHit* a, const SkOpRayHit* b) { + return b->fPt.fX < a->fPt.fX; +} + +static bool hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) { + return a->fPt.fY < b->fPt.fY; +} + +static bool reverse_hit_compare_y(const SkOpRayHit* a, const SkOpRayHit* b) { + return b->fPt.fY < a->fPt.fY; +} + +static double get_t_guess(int tTry, int* dirOffset) { + double t = 0.5; + *dirOffset = tTry & 1; + int tBase = tTry >> 1; + int tBits = 0; + while (tTry >>= 1) { + t /= 2; + ++tBits; + } + if (tBits) { + int tIndex = (tBase - 1) & ((1 << tBits) - 1); + t += t * 2 * tIndex; + } + return t; +} + +bool SkOpSpan::sortableTop(SkOpContour* contourHead) { + SkChunkAlloc allocator(1024); + int dirOffset; + double t = get_t_guess(fTopTTry++, &dirOffset); + SkOpRayHit hitBase; + SkOpRayDir dir = hitBase.makeTestBase(this, t); + if (hitBase.fSlope.fX == 0 && hitBase.fSlope.fY == 0) { + return false; + } + SkOpRayHit* hitHead = &hitBase; + dir = static_cast<SkOpRayDir>(static_cast<int>(dir) + dirOffset); + if (hitBase.fSpan && hitBase.fSpan->segment()->verb() > SkPath::kLine_Verb + && !pt_yx(hitBase.fSlope.asSkVector(), dir)) { + return false; + } + SkOpContour* contour = contourHead; + do { + contour->rayCheck(hitBase, dir, &hitHead, &allocator); + } while ((contour = contour->next())); + // sort hits + SkSTArray<1, SkOpRayHit*> sorted; + SkOpRayHit* hit = hitHead; + while (hit) { + sorted.push_back(hit); + hit = hit->fNext; + } + int count = sorted.count(); + SkTQSort(sorted.begin(), sorted.end() - 1, xy_index(dir) + ? less_than(dir) ? hit_compare_y : reverse_hit_compare_y + : less_than(dir) ? hit_compare_x : reverse_hit_compare_x); + // verify windings +#if DEBUG_WINDING + SkDebugf("%s dir=%s seg=%d t=%1.9g pt=(%1.9g,%1.9g)\n", __FUNCTION__, + gDebugRayDirName[static_cast<int>(dir)], hitBase.fSpan->segment()->debugID(), + hitBase.fT, hitBase.fPt.fX, hitBase.fPt.fY); + for (int index = 0; index < count; ++index) { + hit = sorted[index]; + SkOpSpan* span = hit->fSpan; + SkOpSegment* hitSegment = span ? span->segment() : nullptr; + bool operand = span ? hitSegment->operand() : false; + bool ccw = ccw_dxdy(hit->fSlope, dir); + SkDebugf("%s [%d] valid=%d operand=%d span=%d ccw=%d ", __FUNCTION__, index, + hit->fValid, operand, span ? span->debugID() : -1, ccw); + if (span) { + hitSegment->dumpPtsInner(); + } + SkDebugf(" t=%1.9g pt=(%1.9g,%1.9g) slope=(%1.9g,%1.9g)\n", hit->fT, + hit->fPt.fX, hit->fPt.fY, hit->fSlope.fX, hit->fSlope.fY); + } +#endif + const SkPoint* last = nullptr; + int wind = 0; + int oppWind = 0; + for (int index = 0; index < count; ++index) { + hit = sorted[index]; + if (!hit->fValid) { + return false; + } + bool ccw = ccw_dxdy(hit->fSlope, dir); +// SkASSERT(!approximately_zero(hit->fT) || !hit->fValid); + SkOpSpan* span = hit->fSpan; + if (!span) { + return false; + } + SkOpSegment* hitSegment = span->segment(); + if (span->windValue() == 0 && span->oppValue() == 0) { + continue; + } + if (last && SkDPoint::ApproximatelyEqual(*last, hit->fPt)) { + return false; + } + if (index < count - 1) { + const SkPoint& next = sorted[index + 1]->fPt; + if (SkDPoint::ApproximatelyEqual(next, hit->fPt)) { + return false; + } + } + bool operand = hitSegment->operand(); + if (operand) { + SkTSwap(wind, oppWind); + } + int lastWind = wind; + int lastOpp = oppWind; + int windValue = ccw ? -span->windValue() : span->windValue(); + int oppValue = ccw ? -span->oppValue() : span->oppValue(); + wind += windValue; + oppWind += oppValue; + bool sumSet = false; + int spanSum = span->windSum(); + int windSum = SkOpSegment::UseInnerWinding(lastWind, wind) ? wind : lastWind; + if (spanSum == SK_MinS32) { + span->setWindSum(windSum); + sumSet = true; + } else { + // the need for this condition suggests that UseInnerWinding is flawed + // happened when last = 1 wind = -1 +#if 0 + SkASSERT((hitSegment->isXor() ? (windSum & 1) == (spanSum & 1) : windSum == spanSum) + || (abs(wind) == abs(lastWind) + && (windSum ^ wind ^ lastWind) == spanSum)); +#endif + } + int oSpanSum = span->oppSum(); + int oppSum = SkOpSegment::UseInnerWinding(lastOpp, oppWind) ? oppWind : lastOpp; + if (oSpanSum == SK_MinS32) { + span->setOppSum(oppSum); + } else { +#if 0 + SkASSERT(hitSegment->oppXor() ? (oppSum & 1) == (oSpanSum & 1) : oppSum == oSpanSum + || (abs(oppWind) == abs(lastOpp) + && (oppSum ^ oppWind ^ lastOpp) == oSpanSum)); +#endif + } + if (sumSet) { + if (this->globalState()->phase() == SkOpPhase::kFixWinding) { + hitSegment->contour()->setCcw(ccw); + } else { + (void) hitSegment->markAndChaseWinding(span, span->next(), windSum, oppSum, nullptr); + (void) hitSegment->markAndChaseWinding(span->next(), span, windSum, oppSum, nullptr); + } + } + if (operand) { + SkTSwap(wind, oppWind); + } + last = &hit->fPt; + this->globalState()->bumpNested(); + } + return true; +} + +SkOpSpan* SkOpSegment::findSortableTop(SkOpContour* contourHead) { + SkOpSpan* span = &fHead; + SkOpSpanBase* next; + do { + next = span->next(); + if (span->done()) { + continue; + } + if (span->windSum() != SK_MinS32) { + return span; + } + if (span->sortableTop(contourHead)) { + return span; + } + } while (!next->final() && (span = next->upCast())); + return nullptr; +} + +SkOpSpan* SkOpContour::findSortableTop(SkOpContour* contourHead) { + SkOpSegment* testSegment = &fHead; + bool allDone = true; + do { + if (testSegment->done()) { + continue; + } + allDone = false; + SkOpSpan* result = testSegment->findSortableTop(contourHead); + if (result) { + return result; + } + } while ((testSegment = testSegment->next())); + if (allDone) { + fDone = true; + } + return nullptr; +} + +SkOpSpan* FindSortableTop(SkOpContourHead* contourHead) { + for (int index = 0; index < SkOpGlobalState::kMaxWindingTries; ++index) { + SkOpContour* contour = contourHead; + do { + if (contour->done()) { + continue; + } + SkOpSpan* result = contour->findSortableTop(contourHead); + if (result) { + return result; + } + } while ((contour = contour->next())); + } + return nullptr; +} diff --git a/gfx/skia/skia/src/pathops/SkPathWriter.cpp b/gfx/skia/skia/src/pathops/SkPathWriter.cpp new file mode 100644 index 000000000..1f6dddd13 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathWriter.cpp @@ -0,0 +1,362 @@ +/* + * 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 "SkOpSpan.h" +#include "SkPathOpsPoint.h" +#include "SkPathWriter.h" +#include "SkTSort.h" + +// wrap path to keep track of whether the contour is initialized and non-empty +SkPathWriter::SkPathWriter(SkPath& path) + : fPathPtr(&path) +{ + init(); +} + +void SkPathWriter::close() { + if (fCurrent.isEmpty()) { + return; + } + SkASSERT(this->isClosed()); +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.close();\n"); +#endif + fCurrent.close(); + fPathPtr->addPath(fCurrent); + fCurrent.reset(); + init(); +} + +void SkPathWriter::conicTo(const SkPoint& pt1, const SkOpPtT* pt2, SkScalar weight) { + this->update(pt2); +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.conicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g);\n", + pt1.fX, pt1.fY, pt2->fPt.fX, pt2->fPt.fY, weight); +#endif + fCurrent.conicTo(pt1, pt2->fPt, weight); +} + +void SkPathWriter::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkOpPtT* pt3) { + this->update(pt3); +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.cubicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g,%1.9g);\n", + pt1.fX, pt1.fY, pt2.fX, pt2.fY, pt3->fPt.fX, pt3->fPt.fY); +#endif + fCurrent.cubicTo(pt1, pt2, pt3->fPt); +} + +void SkPathWriter::deferredLine(const SkOpPtT* pt) { + SkASSERT(fFirstPtT); + SkASSERT(fDefer[0]); + if (fDefer[0] == pt) { + // FIXME: why we're adding a degenerate line? Caller should have preflighted this. + return; + } + if (pt->contains(fDefer[0])) { + // FIXME: why we're adding a degenerate line? + return; + } + SkASSERT(!this->matchedLast(pt)); + if (fDefer[1] && this->changedSlopes(pt)) { + this->lineTo(); + fDefer[0] = fDefer[1]; + } + fDefer[1] = pt; +} + +void SkPathWriter::deferredMove(const SkOpPtT* pt) { + if (!fDefer[1]) { + fFirstPtT = fDefer[0] = pt; + return; + } + SkASSERT(fDefer[0]); + if (!this->matchedLast(pt)) { + this->finishContour(); + fFirstPtT = fDefer[0] = pt; + } +} + +void SkPathWriter::finishContour() { + if (!this->matchedLast(fDefer[0])) { + if (!fDefer[1]) { + return; + } + this->lineTo(); + } + if (fCurrent.isEmpty()) { + return; + } + if (this->isClosed()) { + this->close(); + } else { + SkASSERT(fDefer[1]); + fEndPtTs.push(fFirstPtT); + fEndPtTs.push(fDefer[1]); + fPartials.push_back(fCurrent); + this->init(); + } +} + +void SkPathWriter::init() { + fCurrent.reset(); + fFirstPtT = fDefer[0] = fDefer[1] = nullptr; +} + +bool SkPathWriter::isClosed() const { + return this->matchedLast(fFirstPtT); +} + +void SkPathWriter::lineTo() { + if (fCurrent.isEmpty()) { + this->moveTo(); + } +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.lineTo(%1.9g,%1.9g);\n", fDefer[1]->fPt.fX, fDefer[1]->fPt.fY); +#endif + fCurrent.lineTo(fDefer[1]->fPt); +} + +bool SkPathWriter::matchedLast(const SkOpPtT* test) const { + if (test == fDefer[1]) { + return true; + } + if (!test) { + return false; + } + if (!fDefer[1]) { + return false; + } + return test->contains(fDefer[1]); +} + +void SkPathWriter::moveTo() { +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.moveTo(%1.9g,%1.9g);\n", fFirstPtT->fPt.fX, fFirstPtT->fPt.fY); +#endif + fCurrent.moveTo(fFirstPtT->fPt); +} + +void SkPathWriter::quadTo(const SkPoint& pt1, const SkOpPtT* pt2) { + this->update(pt2); +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n", + pt1.fX, pt1.fY, pt2->fPt.fX, pt2->fPt.fY); +#endif + fCurrent.quadTo(pt1, pt2->fPt); +} + +void SkPathWriter::update(const SkOpPtT* pt) { + if (!fDefer[1]) { + this->moveTo(); + } else if (!this->matchedLast(fDefer[0])) { + this->lineTo(); + } + fDefer[0] = fDefer[1] = pt; // set both to know that there is not a pending deferred line +} + +bool SkPathWriter::someAssemblyRequired() { + this->finishContour(); + return fEndPtTs.count() > 0; +} + +bool SkPathWriter::changedSlopes(const SkOpPtT* ptT) const { + if (matchedLast(fDefer[0])) { + return false; + } + SkVector deferDxdy = fDefer[1]->fPt - fDefer[0]->fPt; + SkVector lineDxdy = ptT->fPt - fDefer[1]->fPt; + return deferDxdy.fX * lineDxdy.fY != deferDxdy.fY * lineDxdy.fX; +} + +class DistanceLessThan { +public: + DistanceLessThan(double* distances) : fDistances(distances) { } + double* fDistances; + bool operator()(const int one, const int two) { + return fDistances[one] < fDistances[two]; + } +}; + + /* + check start and end of each contour + if not the same, record them + match them up + connect closest + reassemble contour pieces into new path + */ +void SkPathWriter::assemble() { +#if DEBUG_SHOW_TEST_NAME + SkDebugf("</div>\n"); +#endif + if (!this->someAssemblyRequired()) { + return; + } +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("%s\n", __FUNCTION__); +#endif + SkOpPtT const* const* runs = fEndPtTs.begin(); // starts, ends of partial contours + int endCount = fEndPtTs.count(); // all starts and ends + SkASSERT(endCount > 0); + SkASSERT(endCount == fPartials.count() * 2); +#if DEBUG_ASSEMBLE + for (int index = 0; index < endCount; index += 2) { + const SkOpPtT* eStart = runs[index]; + const SkOpPtT* eEnd = runs[index + 1]; + SkASSERT(eStart != eEnd); + SkASSERT(!eStart->contains(eEnd)); + SkDebugf("%s contour start=(%1.9g,%1.9g) end=(%1.9g,%1.9g)\n", __FUNCTION__, + eStart->fPt.fX, eStart->fPt.fY, eEnd->fPt.fX, eEnd->fPt.fY); + } +#endif + SkTDArray<int> sLink, eLink; + int linkCount = endCount / 2; // number of partial contours + sLink.append(linkCount); + eLink.append(linkCount); + int rIndex, iIndex; + for (rIndex = 0; rIndex < linkCount; ++rIndex) { + sLink[rIndex] = eLink[rIndex] = SK_MaxS32; + } + const int entries = endCount * (endCount - 1) / 2; // folded triangle + SkSTArray<8, double, true> distances(entries); + SkSTArray<8, int, true> sortedDist(entries); + SkSTArray<8, int, true> distLookup(entries); + int rRow = 0; + int dIndex = 0; + for (rIndex = 0; rIndex < endCount - 1; ++rIndex) { + const SkOpPtT* oPtT = runs[rIndex]; + for (iIndex = rIndex + 1; iIndex < endCount; ++iIndex) { + const SkOpPtT* iPtT = runs[iIndex]; + double dx = iPtT->fPt.fX - oPtT->fPt.fX; + double dy = iPtT->fPt.fY - oPtT->fPt.fY; + double dist = dx * dx + dy * dy; + distLookup.push_back(rRow + iIndex); + distances.push_back(dist); // oStart distance from iStart + sortedDist.push_back(dIndex++); + } + rRow += endCount; + } + SkASSERT(dIndex == entries); + SkTQSort<int>(sortedDist.begin(), sortedDist.end() - 1, DistanceLessThan(distances.begin())); + int remaining = linkCount; // number of start/end pairs + for (rIndex = 0; rIndex < entries; ++rIndex) { + int pair = sortedDist[rIndex]; + pair = distLookup[pair]; + int row = pair / endCount; + int col = pair - row * endCount; + int ndxOne = row >> 1; + bool endOne = row & 1; + int* linkOne = endOne ? eLink.begin() : sLink.begin(); + if (linkOne[ndxOne] != SK_MaxS32) { + continue; + } + int ndxTwo = col >> 1; + bool endTwo = col & 1; + int* linkTwo = endTwo ? eLink.begin() : sLink.begin(); + if (linkTwo[ndxTwo] != SK_MaxS32) { + continue; + } + SkASSERT(&linkOne[ndxOne] != &linkTwo[ndxTwo]); + bool flip = endOne == endTwo; + linkOne[ndxOne] = flip ? ~ndxTwo : ndxTwo; + linkTwo[ndxTwo] = flip ? ~ndxOne : ndxOne; + if (!--remaining) { + break; + } + } + SkASSERT(!remaining); +#if DEBUG_ASSEMBLE + for (rIndex = 0; rIndex < linkCount; ++rIndex) { + int s = sLink[rIndex]; + int e = eLink[rIndex]; + SkDebugf("%s %c%d <- s%d - e%d -> %c%d\n", __FUNCTION__, s < 0 ? 's' : 'e', + s < 0 ? ~s : s, rIndex, rIndex, e < 0 ? 'e' : 's', e < 0 ? ~e : e); + } +#endif + rIndex = 0; + do { + bool forward = true; + bool first = true; + int sIndex = sLink[rIndex]; + SkASSERT(sIndex != SK_MaxS32); + sLink[rIndex] = SK_MaxS32; + int eIndex; + if (sIndex < 0) { + eIndex = sLink[~sIndex]; + sLink[~sIndex] = SK_MaxS32; + } else { + eIndex = eLink[sIndex]; + eLink[sIndex] = SK_MaxS32; + } + SkASSERT(eIndex != SK_MaxS32); +#if DEBUG_ASSEMBLE + SkDebugf("%s sIndex=%c%d eIndex=%c%d\n", __FUNCTION__, sIndex < 0 ? 's' : 'e', + sIndex < 0 ? ~sIndex : sIndex, eIndex < 0 ? 's' : 'e', + eIndex < 0 ? ~eIndex : eIndex); +#endif + do { + const SkPath& contour = fPartials[rIndex]; + if (forward) { + fPathPtr->addPath(contour, + first ? SkPath::kAppend_AddPathMode : SkPath::kExtend_AddPathMode); + } else { + SkASSERT(!first); + fPathPtr->reverseAddPath(contour); + } + if (first) { + first = false; + } +#if DEBUG_ASSEMBLE + SkDebugf("%s rIndex=%d eIndex=%s%d close=%d\n", __FUNCTION__, rIndex, + eIndex < 0 ? "~" : "", eIndex < 0 ? ~eIndex : eIndex, + sIndex == ((rIndex != eIndex) ^ forward ? eIndex : ~eIndex)); +#endif + if (sIndex == ((rIndex != eIndex) ^ forward ? eIndex : ~eIndex)) { + fPathPtr->close(); + break; + } + if (forward) { + eIndex = eLink[rIndex]; + SkASSERT(eIndex != SK_MaxS32); + eLink[rIndex] = SK_MaxS32; + if (eIndex >= 0) { + SkASSERT(sLink[eIndex] == rIndex); + sLink[eIndex] = SK_MaxS32; + } else { + SkASSERT(eLink[~eIndex] == ~rIndex); + eLink[~eIndex] = SK_MaxS32; + } + } else { + eIndex = sLink[rIndex]; + SkASSERT(eIndex != SK_MaxS32); + sLink[rIndex] = SK_MaxS32; + if (eIndex >= 0) { + SkASSERT(eLink[eIndex] == rIndex); + eLink[eIndex] = SK_MaxS32; + } else { + SkASSERT(sLink[~eIndex] == ~rIndex); + sLink[~eIndex] = SK_MaxS32; + } + } + rIndex = eIndex; + if (rIndex < 0) { + forward ^= 1; + rIndex = ~rIndex; + } + } while (true); + for (rIndex = 0; rIndex < linkCount; ++rIndex) { + if (sLink[rIndex] != SK_MaxS32) { + break; + } + } + } while (rIndex < linkCount); +#if DEBUG_ASSEMBLE + for (rIndex = 0; rIndex < linkCount; ++rIndex) { + SkASSERT(sLink[rIndex] == SK_MaxS32); + SkASSERT(eLink[rIndex] == SK_MaxS32); + } +#endif + return; +} diff --git a/gfx/skia/skia/src/pathops/SkPathWriter.h b/gfx/skia/skia/src/pathops/SkPathWriter.h new file mode 100644 index 000000000..bd13c718a --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkPathWriter.h @@ -0,0 +1,54 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathWriter_DEFINED +#define SkPathWriter_DEFINED + +#include "SkPath.h" +#include "SkTArray.h" +#include "SkTDArray.h" + +class SkOpPtT; + +// Construct the path one contour at a time. +// If the contour is closed, copy it to the final output. +// Otherwise, keep the partial contour for later assembly. + +class SkPathWriter { +public: + SkPathWriter(SkPath& path); + void assemble(); + void conicTo(const SkPoint& pt1, const SkOpPtT* pt2, SkScalar weight); + void cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkOpPtT* pt3); + void deferredLine(const SkOpPtT* pt); + void deferredMove(const SkOpPtT* pt); + void finishContour(); + bool hasMove() const { return !fFirstPtT; } + void init(); + bool isClosed() const; + const SkPath* nativePath() const { return fPathPtr; } + void quadTo(const SkPoint& pt1, const SkOpPtT* pt2); + +private: + bool changedSlopes(const SkOpPtT* pt) const; + void close(); + const SkTDArray<const SkOpPtT*>& endPtTs() const { return fEndPtTs; } + void lineTo(); + bool matchedLast(const SkOpPtT*) const; + void moveTo(); + const SkTArray<SkPath>& partials() const { return fPartials; } + bool someAssemblyRequired(); + void update(const SkOpPtT* pt); + + SkPath fCurrent; // contour under construction + SkTArray<SkPath> fPartials; // contours with mismatched starts and ends + SkTDArray<const SkOpPtT*> fEndPtTs; // possible pt values for partial starts and ends + SkPath* fPathPtr; // closed contours are written here + const SkOpPtT* fDefer[2]; // [0] deferred move, [1] deferred line + const SkOpPtT* fFirstPtT; // first in current contour +}; + +#endif /* defined(__PathOps__SkPathWriter__) */ diff --git a/gfx/skia/skia/src/pathops/SkReduceOrder.cpp b/gfx/skia/skia/src/pathops/SkReduceOrder.cpp new file mode 100644 index 000000000..7f7ea11d3 --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkReduceOrder.cpp @@ -0,0 +1,283 @@ +/* + * 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 "SkGeometry.h" +#include "SkReduceOrder.h" + +int SkReduceOrder::reduce(const SkDLine& line) { + fLine[0] = line[0]; + int different = line[0] != line[1]; + fLine[1] = line[different]; + return 1 + different; +} + +static int coincident_line(const SkDQuad& quad, SkDQuad& reduction) { + reduction[0] = reduction[1] = quad[0]; + return 1; +} + +static int reductionLineCount(const SkDQuad& reduction) { + return 1 + !reduction[0].approximatelyEqual(reduction[1]); +} + +static int vertical_line(const SkDQuad& quad, SkDQuad& reduction) { + reduction[0] = quad[0]; + reduction[1] = quad[2]; + return reductionLineCount(reduction); +} + +static int horizontal_line(const SkDQuad& quad, SkDQuad& reduction) { + reduction[0] = quad[0]; + reduction[1] = quad[2]; + return reductionLineCount(reduction); +} + +static int check_linear(const SkDQuad& quad, + int minX, int maxX, int minY, int maxY, SkDQuad& reduction) { + if (!quad.isLinear(0, 2)) { + return 0; + } + // four are colinear: return line formed by outside + reduction[0] = quad[0]; + reduction[1] = quad[2]; + return reductionLineCount(reduction); +} + +// reduce to a quadratic or smaller +// look for identical points +// look for all four points in a line + // note that three points in a line doesn't simplify a cubic +// look for approximation with single quadratic + // save approximation with multiple quadratics for later +int SkReduceOrder::reduce(const SkDQuad& quad) { + int index, minX, maxX, minY, maxY; + int minXSet, minYSet; + minX = maxX = minY = maxY = 0; + minXSet = minYSet = 0; + for (index = 1; index < 3; ++index) { + if (quad[minX].fX > quad[index].fX) { + minX = index; + } + if (quad[minY].fY > quad[index].fY) { + minY = index; + } + if (quad[maxX].fX < quad[index].fX) { + maxX = index; + } + if (quad[maxY].fY < quad[index].fY) { + maxY = index; + } + } + for (index = 0; index < 3; ++index) { + if (AlmostEqualUlps(quad[index].fX, quad[minX].fX)) { + minXSet |= 1 << index; + } + if (AlmostEqualUlps(quad[index].fY, quad[minY].fY)) { + minYSet |= 1 << index; + } + } + if ((minXSet & 0x05) == 0x5 && (minYSet & 0x05) == 0x5) { // test for degenerate + // this quad starts and ends at the same place, so never contributes + // to the fill + return coincident_line(quad, fQuad); + } + if (minXSet == 0x7) { // test for vertical line + return vertical_line(quad, fQuad); + } + if (minYSet == 0x7) { // test for horizontal line + return horizontal_line(quad, fQuad); + } + int result = check_linear(quad, minX, maxX, minY, maxY, fQuad); + if (result) { + return result; + } + fQuad = quad; + return 3; +} + +//////////////////////////////////////////////////////////////////////////////////// + +static int coincident_line(const SkDCubic& cubic, SkDCubic& reduction) { + reduction[0] = reduction[1] = cubic[0]; + return 1; +} + +static int reductionLineCount(const SkDCubic& reduction) { + return 1 + !reduction[0].approximatelyEqual(reduction[1]); +} + +static int vertical_line(const SkDCubic& cubic, SkDCubic& reduction) { + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + return reductionLineCount(reduction); +} + +static int horizontal_line(const SkDCubic& cubic, SkDCubic& reduction) { + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + return reductionLineCount(reduction); +} + +// check to see if it is a quadratic or a line +static int check_quadratic(const SkDCubic& cubic, SkDCubic& reduction) { + double dx10 = cubic[1].fX - cubic[0].fX; + double dx23 = cubic[2].fX - cubic[3].fX; + double midX = cubic[0].fX + dx10 * 3 / 2; + double sideAx = midX - cubic[3].fX; + double sideBx = dx23 * 3 / 2; + if (approximately_zero(sideAx) ? !approximately_equal(sideAx, sideBx) + : !AlmostEqualUlps_Pin(sideAx, sideBx)) { + return 0; + } + double dy10 = cubic[1].fY - cubic[0].fY; + double dy23 = cubic[2].fY - cubic[3].fY; + double midY = cubic[0].fY + dy10 * 3 / 2; + double sideAy = midY - cubic[3].fY; + double sideBy = dy23 * 3 / 2; + if (approximately_zero(sideAy) ? !approximately_equal(sideAy, sideBy) + : !AlmostEqualUlps_Pin(sideAy, sideBy)) { + return 0; + } + reduction[0] = cubic[0]; + reduction[1].fX = midX; + reduction[1].fY = midY; + reduction[2] = cubic[3]; + return 3; +} + +static int check_linear(const SkDCubic& cubic, + int minX, int maxX, int minY, int maxY, SkDCubic& reduction) { + if (!cubic.isLinear(0, 3)) { + return 0; + } + // four are colinear: return line formed by outside + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + return reductionLineCount(reduction); +} + +/* food for thought: +http://objectmix.com/graphics/132906-fast-precision-driven-cubic-quadratic-piecewise-degree-reduction-algos-2-a.html + +Given points c1, c2, c3 and c4 of a cubic Bezier, the points of the +corresponding quadratic Bezier are (given in convex combinations of +points): + +q1 = (11/13)c1 + (3/13)c2 -(3/13)c3 + (2/13)c4 +q2 = -c1 + (3/2)c2 + (3/2)c3 - c4 +q3 = (2/13)c1 - (3/13)c2 + (3/13)c3 + (11/13)c4 + +Of course, this curve does not interpolate the end-points, but it would +be interesting to see the behaviour of such a curve in an applet. + +-- +Kalle Rutanen +http://kaba.hilvi.org + +*/ + +// reduce to a quadratic or smaller +// look for identical points +// look for all four points in a line + // note that three points in a line doesn't simplify a cubic +// look for approximation with single quadratic + // save approximation with multiple quadratics for later +int SkReduceOrder::reduce(const SkDCubic& cubic, Quadratics allowQuadratics) { + int index, minX, maxX, minY, maxY; + int minXSet, minYSet; + minX = maxX = minY = maxY = 0; + minXSet = minYSet = 0; + for (index = 1; index < 4; ++index) { + if (cubic[minX].fX > cubic[index].fX) { + minX = index; + } + if (cubic[minY].fY > cubic[index].fY) { + minY = index; + } + if (cubic[maxX].fX < cubic[index].fX) { + maxX = index; + } + if (cubic[maxY].fY < cubic[index].fY) { + maxY = index; + } + } + for (index = 0; index < 4; ++index) { + double cx = cubic[index].fX; + double cy = cubic[index].fY; + double denom = SkTMax(fabs(cx), SkTMax(fabs(cy), + SkTMax(fabs(cubic[minX].fX), fabs(cubic[minY].fY)))); + if (denom == 0) { + minXSet |= 1 << index; + minYSet |= 1 << index; + continue; + } + double inv = 1 / denom; + if (approximately_equal_half(cx * inv, cubic[minX].fX * inv)) { + minXSet |= 1 << index; + } + if (approximately_equal_half(cy * inv, cubic[minY].fY * inv)) { + minYSet |= 1 << index; + } + } + if (minXSet == 0xF) { // test for vertical line + if (minYSet == 0xF) { // return 1 if all four are coincident + return coincident_line(cubic, fCubic); + } + return vertical_line(cubic, fCubic); + } + if (minYSet == 0xF) { // test for horizontal line + return horizontal_line(cubic, fCubic); + } + int result = check_linear(cubic, minX, maxX, minY, maxY, fCubic); + if (result) { + return result; + } + if (allowQuadratics == SkReduceOrder::kAllow_Quadratics + && (result = check_quadratic(cubic, fCubic))) { + return result; + } + fCubic = cubic; + return 4; +} + +SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkPoint* reducePts) { + SkDQuad quad; + quad.set(a); + SkReduceOrder reducer; + int order = reducer.reduce(quad); + if (order == 2) { // quad became line + for (int index = 0; index < order; ++index) { + *reducePts++ = reducer.fLine[index].asSkPoint(); + } + } + return SkPathOpsPointsToVerb(order - 1); +} + +SkPath::Verb SkReduceOrder::Conic(const SkConic& c, SkPoint* reducePts) { + SkPath::Verb verb = SkReduceOrder::Quad(c.fPts, reducePts); + if (verb > SkPath::kLine_Verb && c.fW == 1) { + return SkPath::kQuad_Verb; + } + return verb == SkPath::kQuad_Verb ? SkPath::kConic_Verb : verb; +} + +SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkPoint* reducePts) { + if (SkDPoint::ApproximatelyEqual(a[0], a[1]) && SkDPoint::ApproximatelyEqual(a[0], a[2]) + && SkDPoint::ApproximatelyEqual(a[0], a[3])) { + reducePts[0] = a[0]; + return SkPath::kMove_Verb; + } + SkDCubic cubic; + cubic.set(a); + SkReduceOrder reducer; + int order = reducer.reduce(cubic, kAllow_Quadratics); + if (order == 2 || order == 3) { // cubic became line or quad + for (int index = 0; index < order; ++index) { + *reducePts++ = reducer.fQuad[index].asSkPoint(); + } + } + return SkPathOpsPointsToVerb(order - 1); +} diff --git a/gfx/skia/skia/src/pathops/SkReduceOrder.h b/gfx/skia/skia/src/pathops/SkReduceOrder.h new file mode 100644 index 000000000..7efb71d4f --- /dev/null +++ b/gfx/skia/skia/src/pathops/SkReduceOrder.h @@ -0,0 +1,35 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkReduceOrder_DEFINED +#define SkReduceOrder_DEFINED + +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" + +struct SkConic; + +union SkReduceOrder { + enum Quadratics { + kNo_Quadratics, + kAllow_Quadratics + }; + + int reduce(const SkDCubic& cubic, Quadratics); + int reduce(const SkDLine& line); + int reduce(const SkDQuad& quad); + + static SkPath::Verb Conic(const SkConic& conic, SkPoint* reducePts); + static SkPath::Verb Cubic(const SkPoint pts[4], SkPoint* reducePts); + static SkPath::Verb Quad(const SkPoint pts[3], SkPoint* reducePts); + + SkDLine fLine; + SkDQuad fQuad; + SkDCubic fCubic; +}; + +#endif |