aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--include/pathops/SkPathOps.h39
-rw-r--r--src/pathops/SkAddIntersections.cpp430
-rw-r--r--src/pathops/SkAddIntersections.h19
-rw-r--r--src/pathops/SkDCubicIntersection.cpp451
-rw-r--r--src/pathops/SkDCubicLineIntersection.cpp261
-rw-r--r--src/pathops/SkDCubicToQuads.cpp190
-rw-r--r--src/pathops/SkDLineIntersection.cpp282
-rw-r--r--src/pathops/SkDQuadImplicit.cpp117
-rw-r--r--src/pathops/SkDQuadImplicit.h39
-rw-r--r--src/pathops/SkDQuadIntersection.cpp496
-rw-r--r--src/pathops/SkDQuadLineIntersection.cpp333
-rw-r--r--src/pathops/SkIntersectionHelper.h135
-rw-r--r--src/pathops/SkIntersections.cpp261
-rw-r--r--src/pathops/SkIntersections.h243
-rw-r--r--src/pathops/SkLineParameters.h110
-rw-r--r--src/pathops/SkOpAngle.cpp255
-rw-r--r--src/pathops/SkOpAngle.h91
-rw-r--r--src/pathops/SkOpContour.cpp265
-rw-r--r--src/pathops/SkOpContour.h241
-rw-r--r--src/pathops/SkOpEdgeBuilder.cpp148
-rw-r--r--src/pathops/SkOpEdgeBuilder.h60
-rw-r--r--src/pathops/SkOpSegment.cpp2795
-rw-r--r--src/pathops/SkOpSegment.h392
-rw-r--r--src/pathops/SkOpSpan.h31
-rw-r--r--src/pathops/SkPathOpsBounds.cpp40
-rw-r--r--src/pathops/SkPathOpsBounds.h61
-rw-r--r--src/pathops/SkPathOpsCommon.cpp578
-rw-r--r--src/pathops/SkPathOpsCommon.h29
-rw-r--r--src/pathops/SkPathOpsCubic.cpp463
-rw-r--r--src/pathops/SkPathOpsCubic.h71
-rw-r--r--src/pathops/SkPathOpsCurve.h152
-rw-r--r--src/pathops/SkPathOpsDebug.cpp59
-rw-r--r--src/pathops/SkPathOpsDebug.h132
-rw-r--r--src/pathops/SkPathOpsLine.cpp48
-rw-r--r--src/pathops/SkPathOpsLine.h35
-rw-r--r--src/pathops/SkPathOpsOp.cpp272
-rw-r--r--src/pathops/SkPathOpsPoint.cpp17
-rw-r--r--src/pathops/SkPathOpsPoint.h159
-rw-r--r--src/pathops/SkPathOpsQuad.cpp293
-rw-r--r--src/pathops/SkPathOpsQuad.h62
-rw-r--r--src/pathops/SkPathOpsRect.cpp65
-rw-r--r--src/pathops/SkPathOpsRect.h56
-rw-r--r--src/pathops/SkPathOpsSimplify.cpp195
-rw-r--r--src/pathops/SkPathOpsSpan.h31
-rw-r--r--src/pathops/SkPathOpsTriangle.cpp31
-rw-r--r--src/pathops/SkPathOpsTriangle.h20
-rw-r--r--src/pathops/SkPathOpsTypes.cpp69
-rw-r--r--src/pathops/SkPathOpsTypes.h226
-rw-r--r--src/pathops/SkPathWriter.cpp157
-rw-r--r--src/pathops/SkPathWriter.h44
-rw-r--r--src/pathops/SkQuarticRoot.cpp165
-rw-r--r--src/pathops/SkQuarticRoot.h16
-rw-r--r--src/pathops/SkReduceOrder.cpp450
-rw-r--r--src/pathops/SkReduceOrder.h38
-rw-r--r--src/pathops/TSearch.h101
-rw-r--r--src/pathops/main.cpp16
-rw-r--r--tests/PathOpsBoundsTest.cpp106
-rw-r--r--tests/PathOpsDCubicTest.cpp30
-rw-r--r--tests/PathOpsDLineTest.cpp54
-rw-r--r--tests/PathOpsDPointTest.cpp50
-rw-r--r--tests/PathOpsDQuadTest.cpp53
-rw-r--r--tests/PathOpsDRectTest.cpp100
-rw-r--r--tests/PathOpsDTriangleTest.cpp47
-rw-r--r--tests/PathOpsDVectorTest.cpp50
64 files changed, 12325 insertions, 0 deletions
diff --git a/include/pathops/SkPathOps.h b/include/pathops/SkPathOps.h
new file mode 100644
index 0000000000..0ad6ef2e1c
--- /dev/null
+++ b/include/pathops/SkPathOps.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 SkPathOps_DEFINED
+#define SkPathOps_DEFINED
+
+class SkPath;
+
+// FIXME: move this into SkPaths.h or just use the equivalent in SkRegion.h
+enum SkPathOp {
+ kDifference_PathOp, //!< subtract the op path from the first path
+ kIntersect_PathOp, //!< intersect the two paths
+ kUnion_PathOp, //!< union (inclusive-or) the two paths
+ kXOR_PathOp, //!< exclusive-or the two paths
+ /** subtract the first path from the op path */
+ kReverseDifference_PathOp, // FIXME: unsupported
+ kReplace_PathOp //!< replace the dst path with the op FIXME: unsupported: should it be?
+};
+
+// FIXME: these functions become members of SkPath
+/**
+ * Set this path to the result of applying the Op to this path and the
+ * specified path: this = (this op operand). The resulting path will be constructed
+ * from non-overlapping contours. The curve order is reduced where possible so that cubics may
+ * be turned into quadratics, and quadratics maybe turned into lines.
+ */
+void Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result);
+
+/**
+ * Set this path to a set of non-overlapping contours that describe the same
+ * area as the original path. The curve order is reduced where possible so that cubics may
+ * be turned into quadratics, and quadratics maybe turned into lines.
+ */
+void Simplify(const SkPath& path, SkPath* result);
+
+#endif
diff --git a/src/pathops/SkAddIntersections.cpp b/src/pathops/SkAddIntersections.cpp
new file mode 100644
index 0000000000..9efd4d63eb
--- /dev/null
+++ b/src/pathops/SkAddIntersections.cpp
@@ -0,0 +1,430 @@
+/*
+ * 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 "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 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 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");
+}
+
+static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt,
+ const SkIntersections& i) {
+ SkASSERT(i.used() == pts);
+ if (!pts) {
+ SkDebugf("%s no self intersect " CUBIC_DEBUG_STR "\n", __FUNCTION__,
+ CUBIC_DEBUG_DATA(wt.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));
+ SkDebugf(" " T_DEBUG_STR(wtTs, 1), i[1][0]);
+ 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 debugShowCubicLineIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowCubicQuadIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowCubicIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersectionHelper& , const SkIntersections& ) {
+}
+
+static void debugShowCubicIntersection(int , const SkIntersectionHelper& ,
+ const SkIntersections& ) {
+}
+#endif
+
+bool AddIntersectTs(SkOpContour* test, SkOpContour* next) {
+ if (test != next) {
+ if (test->bounds().fBottom < next->bounds().fTop) {
+ return false;
+ }
+ if (!SkPathOpsBounds::Intersects(test->bounds(), next->bounds())) {
+ return true;
+ }
+ }
+ SkIntersectionHelper wt;
+ wt.init(test);
+ bool foundCommonContour = test == next;
+ do {
+ SkIntersectionHelper wn;
+ wn.init(next);
+ if (test == next && !wn.startAfter(wt)) {
+ continue;
+ }
+ do {
+ if (!SkPathOpsBounds::Intersects(wt.bounds(), wn.bounds())) {
+ continue;
+ }
+ int pts = 0;
+ SkIntersections ts;
+ bool swap = false;
+ 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, wt, wn, 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::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, wt, wn, 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::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::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.quadQuad(wt.pts(), wn.pts());
+ debugShowQuadIntersection(pts, wt, wn, ts);
+ break;
+ }
+ case SkIntersectionHelper::kCubic_Segment: {
+ swap = true;
+ pts = ts.cubicQuad(wn.pts(), wt.pts());
+ debugShowCubicQuadIntersection(pts, wn, wt, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ 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.cubicQuad(wt.pts(), wn.pts());
+ debugShowCubicQuadIntersection(pts, wt, wn, ts);
+ break;
+ }
+ case SkIntersectionHelper::kCubic_Segment: {
+ pts = ts.cubicCubic(wt.pts(), wn.pts());
+ debugShowCubicIntersection(pts, wt, wn, ts);
+ break;
+ }
+ default:
+ SkASSERT(0);
+ }
+ break;
+ default:
+ SkASSERT(0);
+ }
+ if (!foundCommonContour && pts > 0) {
+ test->addCross(next);
+ next->addCross(test);
+ foundCommonContour = true;
+ }
+ // in addition to recording T values, record matching segment
+ if (pts == 2) {
+ if (wn.segmentType() <= SkIntersectionHelper::kLine_Segment
+ && wt.segmentType() <= SkIntersectionHelper::kLine_Segment) {
+ wt.addCoincident(wn, ts, swap);
+ continue;
+ }
+ if (wn.segmentType() >= SkIntersectionHelper::kQuad_Segment
+ && wt.segmentType() >= SkIntersectionHelper::kQuad_Segment
+ && ts.isCoincident(0)) {
+ SkASSERT(ts.coincidentUsed() == 2);
+ wt.addCoincident(wn, ts, swap);
+ continue;
+ }
+ }
+ 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);
+ SkPoint point = ts.pt(pt).asSkPoint();
+ int testTAt = wt.addT(wn, point, ts[swap][pt]);
+ int nextTAt = wn.addT(wt, point, ts[!swap][pt]);
+ wt.addOtherT(testTAt, ts[!swap][pt], nextTAt);
+ wn.addOtherT(nextTAt, ts[swap][pt], testTAt);
+ }
+ } while (wn.advance());
+ } while (wt.advance());
+ return true;
+}
+
+void AddSelfIntersectTs(SkOpContour* test) {
+ SkIntersectionHelper wt;
+ wt.init(test);
+ do {
+ if (wt.segmentType() != SkIntersectionHelper::kCubic_Segment) {
+ continue;
+ }
+ SkIntersections ts;
+ int pts = ts.cubic(wt.pts());
+ debugShowCubicIntersection(pts, wt, ts);
+ if (!pts) {
+ continue;
+ }
+ SkASSERT(pts == 1);
+ SkASSERT(ts[0][0] >= 0 && ts[0][0] <= 1);
+ SkASSERT(ts[1][0] >= 0 && ts[1][0] <= 1);
+ SkPoint point = ts.pt(0).asSkPoint();
+ int testTAt = wt.addSelfT(wt, point, ts[0][0]);
+ int nextTAt = wt.addT(wt, point, ts[1][0]);
+ wt.addOtherT(testTAt, ts[1][0], nextTAt);
+ wt.addOtherT(nextTAt, ts[0][0], testTAt);
+ } while (wt.advance());
+}
+
+// resolve any coincident pairs found while intersecting, and
+// see if coincidence is formed by clipping non-concident segments
+void CoincidenceCheck(SkTDArray<SkOpContour*>* contourList, int total) {
+ int contourCount = (*contourList).count();
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = (*contourList)[cIndex];
+ contour->addCoincidentPoints();
+ }
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = (*contourList)[cIndex];
+ contour->calcCoincidentWinding();
+ }
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = (*contourList)[cIndex];
+ contour->findTooCloseToCall();
+ }
+}
diff --git a/src/pathops/SkAddIntersections.h b/src/pathops/SkAddIntersections.h
new file mode 100644
index 0000000000..26cfa9633f
--- /dev/null
+++ b/src/pathops/SkAddIntersections.h
@@ -0,0 +1,19 @@
+/*
+ * 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"
+#include "SkTDArray.h"
+
+bool AddIntersectTs(SkOpContour* test, SkOpContour* next);
+void AddSelfIntersectTs(SkOpContour* test);
+void CoincidenceCheck(SkTDArray<SkOpContour*>* contourList, int total);
+
+#endif
+
diff --git a/src/pathops/SkDCubicIntersection.cpp b/src/pathops/SkDCubicIntersection.cpp
new file mode 100644
index 0000000000..a31b1a4c52
--- /dev/null
+++ b/src/pathops/SkDCubicIntersection.cpp
@@ -0,0 +1,451 @@
+/*
+ * 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 "SkPathOpsLine.h"
+#include "SkPathOpsPoint.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsRect.h"
+#include "SkReduceOrder.h"
+#include "SkTDArray.h"
+#include "TSearch.h"
+
+#if ONE_OFF_DEBUG
+static const double tLimits1[2][2] = {{0.36, 0.37}, {0.63, 0.64}};
+static const double tLimits2[2][2] = {{-0.865211397, -0.865215212}, {-0.865207696, -0.865208078}};
+#endif
+
+#define DEBUG_QUAD_PART 0
+#define SWAP_TOP_DEBUG 0
+
+static int quadPart(const SkDCubic& cubic, double tStart, double tEnd, SkReduceOrder* reducer) {
+ SkDCubic part = cubic.subDivide(tStart, tEnd);
+ SkDQuad quad = part.toQuad();
+ // FIXME: should reduceOrder be looser in this use case if quartic is going to blow up on an
+ // extremely shallow quadratic?
+ int order = reducer->reduce(quad, SkReduceOrder::kFill_Style);
+#if DEBUG_QUAD_PART
+ SkDebugf("%s cubic=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g)"
+ " t=(%1.17g,%1.17g)\n", __FUNCTION__, cubic[0].fX, cubic[0].fY,
+ cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY,
+ cubic[3].fX, cubic[3].fY, tStart, tEnd);
+ SkDebugf("%s part=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g)"
+ " quad=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g)\n", __FUNCTION__,
+ part[0].fX, part[0].fY, part[1].fX, part[1].fY, part[2].fX, part[2].fY,
+ part[3].fX, part[3].fY, quad[0].fX, quad[0].fY,
+ quad[1].fX, quad[1].fY, quad[2].fX, quad[2].fY);
+ SkDebugf("%s simple=(%1.17g,%1.17g", __FUNCTION__, reducer->fQuad[0].fX, reducer->fQuad[0].fY);
+ if (order > 1) {
+ SkDebugf(" %1.17g,%1.17g", reducer->fQuad[1].fX, reducer->fQuad[1].fY);
+ }
+ if (order > 2) {
+ SkDebugf(" %1.17g,%1.17g", reducer->fQuad[2].fX, reducer->fQuad[2].fY);
+ }
+ SkDebugf(")\n");
+ SkASSERT(order < 4 && order > 0);
+#endif
+ return order;
+}
+
+static void intersectWithOrder(const SkDQuad& simple1, int order1, const SkDQuad& simple2,
+ int order2, SkIntersections& i) {
+ if (order1 == 3 && order2 == 3) {
+ i.intersect(simple1, simple2);
+ } else if (order1 <= 2 && order2 <= 2) {
+ i.intersect((const SkDLine&) simple1, (const SkDLine&) simple2);
+ } else if (order1 == 3 && order2 <= 2) {
+ i.intersect(simple1, (const SkDLine&) simple2);
+ } else {
+ SkASSERT(order1 <= 2 && order2 == 3);
+ i.intersect(simple2, (const SkDLine&) simple1);
+ i.swapPts();
+ }
+}
+
+// this flavor centers potential intersections recursively. In contrast, '2' may inadvertently
+// chase intersections near quadratic ends, requiring odd hacks to find them.
+static void intersect(const SkDCubic& cubic1, double t1s, double t1e, const SkDCubic& cubic2,
+ double t2s, double t2e, double precisionScale, SkIntersections& i) {
+ i.upDepth();
+ SkDCubic c1 = cubic1.subDivide(t1s, t1e);
+ SkDCubic c2 = cubic2.subDivide(t2s, t2e);
+ SkTDArray<double> ts1;
+ // OPTIMIZE: if c1 == c2, call once (happens when detecting self-intersection)
+ c1.toQuadraticTs(c1.calcPrecision() * precisionScale, &ts1);
+ SkTDArray<double> ts2;
+ c2.toQuadraticTs(c2.calcPrecision() * precisionScale, &ts2);
+ double t1Start = t1s;
+ int ts1Count = ts1.count();
+ for (int i1 = 0; i1 <= ts1Count; ++i1) {
+ const double tEnd1 = i1 < ts1Count ? ts1[i1] : 1;
+ const double t1 = t1s + (t1e - t1s) * tEnd1;
+ SkReduceOrder s1;
+ int o1 = quadPart(cubic1, t1Start, t1, &s1);
+ double t2Start = t2s;
+ int ts2Count = ts2.count();
+ for (int i2 = 0; i2 <= ts2Count; ++i2) {
+ const double tEnd2 = i2 < ts2Count ? ts2[i2] : 1;
+ const double t2 = t2s + (t2e - t2s) * tEnd2;
+ if (&cubic1 == &cubic2 && t1Start >= t2Start) {
+ t2Start = t2;
+ continue;
+ }
+ SkReduceOrder s2;
+ int o2 = quadPart(cubic2, t2Start, t2, &s2);
+ #if ONE_OFF_DEBUG
+ char tab[] = " ";
+ if (tLimits1[0][0] >= t1Start && tLimits1[0][1] <= t1
+ && tLimits1[1][0] >= t2Start && tLimits1[1][1] <= t2) {
+ SkDCubic cSub1 = cubic1.subDivide(t1Start, t1);
+ SkDCubic cSub2 = cubic2.subDivide(t2Start, t2);
+ SkDebugf("%.*s %s t1=(%1.9g,%1.9g) t2=(%1.9g,%1.9g)", i.depth()*2, tab,
+ __FUNCTION__, t1Start, t1, t2Start, t2);
+ SkIntersections xlocals;
+ intersectWithOrder(s1.fQuad, o1, s2.fQuad, o2, xlocals);
+ SkDebugf(" xlocals.fUsed=%d\n", xlocals.used());
+ }
+ #endif
+ SkIntersections locals;
+ intersectWithOrder(s1.fQuad, o1, s2.fQuad, o2, locals);
+ double coStart[2] = { -1 };
+ SkDPoint coPoint;
+ int tCount = locals.used();
+ for (int tIdx = 0; tIdx < tCount; ++tIdx) {
+ double to1 = t1Start + (t1 - t1Start) * locals[0][tIdx];
+ double to2 = t2Start + (t2 - t2Start) * locals[1][tIdx];
+ // if the computed t is not sufficiently precise, iterate
+ SkDPoint p1 = cubic1.xyAtT(to1);
+ SkDPoint p2 = cubic2.xyAtT(to2);
+ if (p1.approximatelyEqual(p2)) {
+ if (locals.isCoincident(tIdx)) {
+ if (coStart[0] < 0) {
+ coStart[0] = to1;
+ coStart[1] = to2;
+ coPoint = p1;
+ } else {
+ i.insertCoincidentPair(coStart[0], to1, coStart[1], to2, coPoint, p1);
+ coStart[0] = -1;
+ }
+ } else if (&cubic1 != &cubic2 || !approximately_equal(to1, to2)) {
+ if (i.swapped()) { // FIXME: insert should respect swap
+ i.insert(to2, to1, p1);
+ } else {
+ i.insert(to1, to2, p1);
+ }
+ }
+ } else {
+ double offset = precisionScale / 16; // FIME: const is arbitrary: test, refine
+#if 1
+ double c1Bottom = tIdx == 0 ? 0 :
+ (t1Start + (t1 - t1Start) * locals[0][tIdx - 1] + to1) / 2;
+ double c1Min = SkTMax<double>(c1Bottom, to1 - offset);
+ double c1Top = tIdx == tCount - 1 ? 1 :
+ (t1Start + (t1 - t1Start) * locals[0][tIdx + 1] + to1) / 2;
+ double c1Max = SkTMin<double>(c1Top, to1 + offset);
+ double c2Min = SkTMax<double>(0., to2 - offset);
+ double c2Max = SkTMin<double>(1., to2 + offset);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 1 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab,
+ __FUNCTION__,
+ c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max
+ && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max,
+ to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset
+ && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset,
+ c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max
+ && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max,
+ to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset
+ && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset);
+ SkDebugf("%.*s %s 1 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g"
+ " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n",
+ i.depth()*2, tab, __FUNCTION__, c1Bottom, c1Top, 0., 1.,
+ to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset);
+ SkDebugf("%.*s %s 1 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g"
+ " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min,
+ c1Max, c2Min, c2Max);
+ #endif
+ intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 1 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__,
+ i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1);
+ #endif
+ if (tCount > 1) {
+ c1Min = SkTMax<double>(0., to1 - offset);
+ c1Max = SkTMin<double>(1., to1 + offset);
+ double c2Bottom = tIdx == 0 ? to2 :
+ (t2Start + (t2 - t2Start) * locals[1][tIdx - 1] + to2) / 2;
+ double c2Top = tIdx == tCount - 1 ? to2 :
+ (t2Start + (t2 - t2Start) * locals[1][tIdx + 1] + to2) / 2;
+ if (c2Bottom > c2Top) {
+ SkTSwap(c2Bottom, c2Top);
+ }
+ if (c2Bottom == to2) {
+ c2Bottom = 0;
+ }
+ if (c2Top == to2) {
+ c2Top = 1;
+ }
+ c2Min = SkTMax<double>(c2Bottom, to2 - offset);
+ c2Max = SkTMin<double>(c2Top, to2 + offset);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 2 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab,
+ __FUNCTION__,
+ c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max
+ && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max,
+ to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset
+ && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset,
+ c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max
+ && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max,
+ to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset
+ && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset);
+ SkDebugf("%.*s %s 2 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g"
+ " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n",
+ i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top,
+ to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset);
+ SkDebugf("%.*s %s 2 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g"
+ " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min,
+ c1Max, c2Min, c2Max);
+ #endif
+ intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 2 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__,
+ i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1);
+ #endif
+ c1Min = SkTMax<double>(c1Bottom, to1 - offset);
+ c1Max = SkTMin<double>(c1Top, to1 + offset);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 3 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab,
+ __FUNCTION__,
+ c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max
+ && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max,
+ to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset
+ && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset,
+ c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max
+ && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max,
+ to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset
+ && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset);
+ SkDebugf("%.*s %s 3 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g"
+ " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n",
+ i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top,
+ to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset);
+ SkDebugf("%.*s %s 3 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g"
+ " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min,
+ c1Max, c2Min, c2Max);
+ #endif
+ intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%.*s %s 3 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__,
+ i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1);
+ #endif
+ }
+#else
+ double c1Bottom = tIdx == 0 ? 0 :
+ (t1Start + (t1 - t1Start) * locals.fT[0][tIdx - 1] + to1) / 2;
+ double c1Min = SkTMax<double>(c1Bottom, to1 - offset);
+ double c1Top = tIdx == tCount - 1 ? 1 :
+ (t1Start + (t1 - t1Start) * locals.fT[0][tIdx + 1] + to1) / 2;
+ double c1Max = SkTMin<double>(c1Top, to1 + offset);
+ double c2Bottom = tIdx == 0 ? to2 :
+ (t2Start + (t2 - t2Start) * locals.fT[1][tIdx - 1] + to2) / 2;
+ double c2Top = tIdx == tCount - 1 ? to2 :
+ (t2Start + (t2 - t2Start) * locals.fT[1][tIdx + 1] + to2) / 2;
+ if (c2Bottom > c2Top) {
+ SkTSwap(c2Bottom, c2Top);
+ }
+ if (c2Bottom == to2) {
+ c2Bottom = 0;
+ }
+ if (c2Top == to2) {
+ c2Top = 1;
+ }
+ double c2Min = SkTMax<double>(c2Bottom, to2 - offset);
+ double c2Max = SkTMin<double>(c2Top, to2 + offset);
+ #if ONE_OFF_DEBUG
+ SkDebugf("%s contains1=%d/%d contains2=%d/%d\n", __FUNCTION__,
+ c1Min <= 0.210357794 && 0.210357794 <= c1Max
+ && c2Min <= 0.223476406 && 0.223476406 <= c2Max,
+ to1 - offset <= 0.210357794 && 0.210357794 <= to1 + offset
+ && to2 - offset <= 0.223476406 && 0.223476406 <= to2 + offset,
+ c1Min <= 0.211324707 && 0.211324707 <= c1Max
+ && c2Min <= 0.211327209 && 0.211327209 <= c2Max,
+ to1 - offset <= 0.211324707 && 0.211324707 <= to1 + offset
+ && to2 - offset <= 0.211327209 && 0.211327209 <= to2 + offset);
+ SkDebugf("%s c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g"
+ " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n",
+ __FUNCTION__, c1Bottom, c1Top, c2Bottom, c2Top,
+ to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset);
+ SkDebugf("%s to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g"
+ " c2Max=%1.9g\n", __FUNCTION__, to1, to2, c1Min, c1Max, c2Min, c2Max);
+ #endif
+#endif
+ intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i);
+ // FIXME: if no intersection is found, either quadratics intersected where
+ // cubics did not, or the intersection was missed. In the former case, expect
+ // the quadratics to be nearly parallel at the point of intersection, and check
+ // for that.
+ }
+ }
+ SkASSERT(coStart[0] == -1);
+ t2Start = t2;
+ }
+ t1Start = t1;
+ }
+ i.downDepth();
+}
+
+#define LINE_FRACTION 0.1
+
+// intersect the end of the cubic with the other. Try lines from the end to control and opposite
+// end to determine range of t on opposite cubic.
+static void intersectEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2,
+ const SkDRect& bounds2, SkIntersections& i) {
+ SkDLine line;
+ int t1Index = start ? 0 : 3;
+ line[0] = cubic1[t1Index];
+ // don't bother if the two cubics are connnected
+ SkTDArray<double> tVals; // OPTIMIZE: replace with hard-sized array
+ for (int index = 0; index < 4; ++index) {
+ if (index == t1Index) {
+ continue;
+ }
+ SkDVector dxy1 = cubic1[index] - line[0];
+ dxy1 /= SkDCubic::gPrecisionUnit;
+ line[1] = line[0] + dxy1;
+ SkDRect lineBounds;
+ lineBounds.setBounds(line);
+ if (!bounds2.intersects(&lineBounds)) {
+ continue;
+ }
+ SkIntersections local;
+ if (!local.intersect(cubic2, line)) {
+ continue;
+ }
+ for (int idx2 = 0; idx2 < local.used(); ++idx2) {
+ double foundT = local[0][idx2];
+ if (approximately_less_than_zero(foundT)
+ || approximately_greater_than_one(foundT)) {
+ continue;
+ }
+ if (local.pt(idx2).approximatelyEqual(line[0])) {
+ if (i.swapped()) { // FIXME: insert should respect swap
+ i.insert(foundT, start ? 0 : 1, line[0]);
+ } else {
+ i.insert(start ? 0 : 1, foundT, line[0]);
+ }
+ } else {
+ *tVals.append() = local[0][idx2];
+ }
+ }
+ }
+ if (tVals.count() == 0) {
+ return;
+ }
+ QSort<double>(tVals.begin(), tVals.end() - 1);
+ double tMin1 = start ? 0 : 1 - LINE_FRACTION;
+ double tMax1 = start ? LINE_FRACTION : 1;
+ int tIdx = 0;
+ do {
+ int tLast = tIdx;
+ while (tLast + 1 < tVals.count() && roughly_equal(tVals[tLast + 1], tVals[tIdx])) {
+ ++tLast;
+ }
+ double tMin2 = SkTMax<double>(tVals[tIdx] - LINE_FRACTION, 0.0);
+ double tMax2 = SkTMin<double>(tVals[tLast] + LINE_FRACTION, 1.0);
+ int lastUsed = i.used();
+ intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, i);
+ if (lastUsed == i.used()) {
+ tMin2 = SkTMax<double>(tVals[tIdx] - (1.0 / SkDCubic::gPrecisionUnit), 0.0);
+ tMax2 = SkTMin<double>(tVals[tLast] + (1.0 / SkDCubic::gPrecisionUnit), 1.0);
+ intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, i);
+ }
+ tIdx = tLast + 1;
+ } while (tIdx < tVals.count());
+ return;
+}
+
+const double CLOSE_ENOUGH = 0.001;
+
+static bool closeStart(const SkDCubic& cubic, int cubicIndex, SkIntersections& i, SkDPoint& pt) {
+ if (i[cubicIndex][0] != 0 || i[cubicIndex][1] > CLOSE_ENOUGH) {
+ return false;
+ }
+ pt = cubic.xyAtT((i[cubicIndex][0] + i[cubicIndex][1]) / 2);
+ return true;
+}
+
+static bool closeEnd(const SkDCubic& cubic, int cubicIndex, SkIntersections& i, SkDPoint& pt) {
+ int last = i.used() - 1;
+ if (i[cubicIndex][last] != 1 || i[cubicIndex][last - 1] < 1 - CLOSE_ENOUGH) {
+ return false;
+ }
+ pt = cubic.xyAtT((i[cubicIndex][last] + i[cubicIndex][last - 1]) / 2);
+ return true;
+}
+
+int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) {
+ ::intersect(c1, 0, 1, c2, 0, 1, 1, *this);
+ // FIXME: pass in cached bounds from caller
+ SkDRect c1Bounds, c2Bounds;
+ c1Bounds.setBounds(c1); // OPTIMIZE use setRawBounds ?
+ c2Bounds.setBounds(c2);
+ intersectEnd(c1, false, c2, c2Bounds, *this);
+ intersectEnd(c1, true, c2, c2Bounds, *this);
+ bool selfIntersect = &c1 == &c2;
+ if (!selfIntersect) {
+ swap();
+ intersectEnd(c2, false, c1, c1Bounds, *this);
+ intersectEnd(c2, true, c1, c1Bounds, *this);
+ swap();
+ }
+ // If an end point and a second point very close to the end is returned, the second
+ // point may have been detected because the approximate quads
+ // intersected at the end and close to it. Verify that the second point is valid.
+ if (fUsed <= 1 || coincidentUsed()) {
+ return fUsed;
+ }
+ SkDPoint pt[2];
+ if (closeStart(c1, 0, *this, pt[0]) && closeStart(c2, 1, *this, pt[1])
+ && pt[0].approximatelyEqual(pt[1])) {
+ removeOne(1);
+ }
+ if (closeEnd(c1, 0, *this, pt[0]) && closeEnd(c2, 1, *this, pt[1])
+ && pt[0].approximatelyEqual(pt[1])) {
+ removeOne(used() - 2);
+ }
+ return fUsed;
+}
+
+// Up promote the quad to a cubic.
+// OPTIMIZATION If this is a common use case, optimize by duplicating
+// the intersect 3 loop to avoid the promotion / demotion code
+int SkIntersections::intersect(const SkDCubic& cubic, const SkDQuad& quad) {
+ SkDCubic up = quad.toCubic();
+ (void) intersect(cubic, up);
+ return used();
+}
+
+/* http://www.ag.jku.at/compass/compasssample.pdf
+( Self-Intersection Problems and Approximate Implicitization by Jan B. Thomassen
+Centre of Mathematics for Applications, University of Oslo http://www.cma.uio.no janbth@math.uio.no
+SINTEF Applied Mathematics http://www.sintef.no )
+describes a method to find the self intersection of a cubic by taking the gradient of the implicit
+form dotted with the normal, and solving for the roots. My math foo is too poor to implement this.*/
+
+int SkIntersections::intersect(const SkDCubic& c) {
+ // check to see if x or y end points are the extrema. Are other quick rejects possible?
+ if (c.endsAreExtremaInXOrY()) {
+ return false;
+ }
+ (void) intersect(c, c);
+ if (used() > 0) {
+ SkASSERT(used() == 1);
+ if (fT[0][0] > fT[1][0]) {
+ swapPts();
+ }
+ }
+ return used();
+}
diff --git a/src/pathops/SkDCubicLineIntersection.cpp b/src/pathops/SkDCubicLineIntersection.cpp
new file mode 100644
index 0000000000..5df3acacfd
--- /dev/null
+++ b/src/pathops/SkDCubicLineIntersection.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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 "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:
+
+LineCubicIntersections(const SkDCubic& c, const SkDLine& l, SkIntersections& i)
+ : cubic(c)
+ , line(l)
+ , intersections(i) {
+}
+
+// see parallel routine in line quadratic intersections
+int intersectRay(double roots[3]) {
+ double adj = line[1].fX - line[0].fX;
+ double opp = line[1].fY - line[0].fY;
+ SkDCubic r;
+ for (int n = 0; n < 4; ++n) {
+ r[n].fX = (cubic[n].fY - line[0].fY) * adj - (cubic[n].fX - line[0].fX) * opp;
+ }
+ double A, B, C, D;
+ SkDCubic::Coefficients(&r[0].fX, &A, &B, &C, &D);
+ return SkDCubic::RootsValidT(A, B, C, D, roots);
+}
+
+int intersect() {
+ addEndPoints();
+ double rootVals[3];
+ int roots = intersectRay(rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double cubicT = rootVals[index];
+ double lineT = findLineT(cubicT);
+ if (pinTs(&cubicT, &lineT)) {
+ SkDPoint pt = line.xyAtT(lineT);
+ intersections.insert(cubicT, lineT, pt);
+ }
+ }
+ return intersections.used();
+}
+
+int horizontalIntersect(double axisIntercept, double roots[3]) {
+ double A, B, C, D;
+ SkDCubic::Coefficients(&cubic[0].fY, &A, &B, &C, &D);
+ D -= axisIntercept;
+ return SkDCubic::RootsValidT(A, B, C, D, roots);
+}
+
+int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) {
+ addHorizontalEndPoints(left, right, axisIntercept);
+ double rootVals[3];
+ int roots = horizontalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double cubicT = rootVals[index];
+ SkDPoint pt = cubic.xyAtT(cubicT);
+ double lineT = (pt.fX - left) / (right - left);
+ if (pinTs(&cubicT, &lineT)) {
+ intersections.insert(cubicT, lineT, pt);
+ }
+ }
+ if (flipped) {
+ intersections.flip();
+ }
+ return intersections.used();
+}
+
+int verticalIntersect(double axisIntercept, double roots[3]) {
+ double A, B, C, D;
+ SkDCubic::Coefficients(&cubic[0].fX, &A, &B, &C, &D);
+ D -= axisIntercept;
+ return SkDCubic::RootsValidT(A, B, C, D, roots);
+}
+
+int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) {
+ addVerticalEndPoints(top, bottom, axisIntercept);
+ double rootVals[3];
+ int roots = verticalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double cubicT = rootVals[index];
+ SkDPoint pt = cubic.xyAtT(cubicT);
+ double lineT = (pt.fY - top) / (bottom - top);
+ if (pinTs(&cubicT, &lineT)) {
+ intersections.insert(cubicT, lineT, pt);
+ }
+ }
+ if (flipped) {
+ intersections.flip();
+ }
+ return intersections.used();
+}
+
+protected:
+
+void addEndPoints() {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ for (int lIndex = 0; lIndex < 2; lIndex++) {
+ if (cubic[cIndex] == line[lIndex]) {
+ intersections.insert(cIndex >> 1, lIndex, line[lIndex]);
+ }
+ }
+ }
+}
+
+void addHorizontalEndPoints(double left, double right, double y) {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ if (cubic[cIndex].fY != y) {
+ continue;
+ }
+ if (cubic[cIndex].fX == left) {
+ intersections.insert(cIndex >> 1, 0, cubic[cIndex]);
+ }
+ if (cubic[cIndex].fX == right) {
+ intersections.insert(cIndex >> 1, 1, cubic[cIndex]);
+ }
+ }
+}
+
+void addVerticalEndPoints(double top, double bottom, double x) {
+ for (int cIndex = 0; cIndex < 4; cIndex += 3) {
+ if (cubic[cIndex].fX != x) {
+ continue;
+ }
+ if (cubic[cIndex].fY == top) {
+ intersections.insert(cIndex >> 1, 0, cubic[cIndex]);
+ }
+ if (cubic[cIndex].fY == bottom) {
+ intersections.insert(cIndex >> 1, 1, cubic[cIndex]);
+ }
+ }
+}
+
+double findLineT(double t) {
+ SkDPoint xy = cubic.xyAtT(t);
+ double dx = line[1].fX - line[0].fX;
+ double dy = line[1].fY - line[0].fY;
+ if (fabs(dx) > fabs(dy)) {
+ return (xy.fX - line[0].fX) / dx;
+ }
+ return (xy.fY - line[0].fY) / dy;
+}
+
+static bool pinTs(double* cubicT, double* lineT) {
+ if (!approximately_one_or_less(*lineT)) {
+ return false;
+ }
+ if (!approximately_zero_or_more(*lineT)) {
+ return false;
+ }
+ if (precisely_less_than_zero(*cubicT)) {
+ *cubicT = 0;
+ } else if (precisely_greater_than_one(*cubicT)) {
+ *cubicT = 1;
+ }
+ if (precisely_less_than_zero(*lineT)) {
+ *lineT = 0;
+ } else if (precisely_greater_than_one(*lineT)) {
+ *lineT = 1;
+ }
+ return true;
+}
+
+private:
+
+const SkDCubic& cubic;
+const SkDLine& line;
+SkIntersections& intersections;
+};
+
+int SkIntersections::horizontal(const SkDCubic& cubic, double left, double right, double y,
+ bool flipped) {
+ LineCubicIntersections c(cubic, *(static_cast<SkDLine*>(0)), *this);
+ return c.horizontalIntersect(y, left, right, flipped);
+}
+
+int SkIntersections::vertical(const SkDCubic& cubic, double top, double bottom, double x,
+ bool flipped) {
+ LineCubicIntersections c(cubic, *(static_cast<SkDLine*>(0)), *this);
+ return c.verticalIntersect(x, top, bottom, flipped);
+}
+
+int SkIntersections::intersect(const SkDCubic& cubic, const SkDLine& line) {
+ LineCubicIntersections c(cubic, line, *this);
+ return c.intersect();
+}
+
+int SkIntersections::intersectRay(const SkDCubic& cubic, const SkDLine& line) {
+ LineCubicIntersections c(cubic, line, *this);
+ return c.intersectRay(fT[0]);
+}
diff --git a/src/pathops/SkDCubicToQuads.cpp b/src/pathops/SkDCubicToQuads.cpp
new file mode 100644
index 0000000000..b8a02c4294
--- /dev/null
+++ b/src/pathops/SkDCubicToQuads.cpp
@@ -0,0 +1,190 @@
+/*
+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
+
+
+SkDCubic defined by: P1/2 - anchor points, C1/C2 control points
+|x| is the euclidean norm of x
+mid-point approx of cubic: a quad that shares the same anchors with the cubic and has the
+ control point at C = (3·C2 - P2 + 3·C1 - P1)/4
+
+Algorithm
+
+pick an absolute precision (prec)
+Compute the Tdiv as the root of (cubic) equation
+sqrt(3)/18 · |P2 - 3·C2 + 3·C1 - P1|/2 · Tdiv ^ 3 = prec
+if Tdiv < 0.5 divide the cubic at Tdiv. First segment [0..Tdiv] can be approximated with by a
+ quadratic, with a defect less than prec, by the mid-point approximation.
+ Repeat from step 2 with the second resulted segment (corresponding to 1-Tdiv)
+0.5<=Tdiv<1 - simply divide the cubic in two. The two halves can be approximated by the mid-point
+ approximation
+Tdiv>=1 - the entire cubic can be approximated by the mid-point approximation
+
+confirmed by (maybe stolen from)
+http://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html
+// maybe in turn derived from http://www.cccg.ca/proceedings/2004/36.pdf
+// also stored at http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/bezier%20cccg04%20paper.pdf
+
+*/
+
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkReduceOrder.h"
+#include "SkTDArray.h"
+#include "TSearch.h"
+
+#define USE_CUBIC_END_POINTS 1
+
+static double calc_t_div(const SkDCubic& cubic, double precision, double start) {
+ const double adjust = sqrt(3.) / 36;
+ SkDCubic sub;
+ const SkDCubic* cPtr;
+ if (start == 0) {
+ cPtr = &cubic;
+ } else {
+ // OPTIMIZE: special-case half-split ?
+ sub = cubic.subDivide(start, 1);
+ cPtr = &sub;
+ }
+ const SkDCubic& c = *cPtr;
+ double dx = c[3].fX - 3 * (c[2].fX - c[1].fX) - c[0].fX;
+ double dy = c[3].fY - 3 * (c[2].fY - c[1].fY) - c[0].fY;
+ double dist = sqrt(dx * dx + dy * dy);
+ double tDiv3 = precision / (adjust * dist);
+ double t = SkDCubeRoot(tDiv3);
+ if (start > 0) {
+ t = start + (1 - start) * t;
+ }
+ return t;
+}
+
+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;
+}
+
+static bool add_simple_ts(const SkDCubic& cubic, double precision, SkTDArray<double>* ts) {
+ double tDiv = calc_t_div(cubic, precision, 0);
+ if (tDiv >= 1) {
+ return true;
+ }
+ if (tDiv >= 0.5) {
+ *ts->append() = 0.5;
+ return true;
+ }
+ return false;
+}
+
+static void addTs(const SkDCubic& cubic, double precision, double start, double end,
+ SkTDArray<double>* ts) {
+ double tDiv = calc_t_div(cubic, precision, 0);
+ double parts = ceil(1.0 / tDiv);
+ for (double index = 0; index < parts; ++index) {
+ double newT = start + (index / parts) * (end - start);
+ if (newT > 0 && newT < 1) {
+ *ts->append() = newT;
+ }
+ }
+}
+
+// flavor that returns T values only, deferring computing the quads until they are needed
+// FIXME: when called from recursive intersect 2, this could take the original cubic
+// and do a more precise job when calling chop at and sub divide by computing the fractional ts.
+// it would still take the prechopped cubic for reduce order and find cubic inflections
+void SkDCubic::toQuadraticTs(double precision, SkTDArray<double>* ts) const {
+ SkReduceOrder reducer;
+ int order = reducer.reduce(*this, SkReduceOrder::kAllow_Quadratics, SkReduceOrder::kFill_Style);
+ if (order < 3) {
+ return;
+ }
+ double inflectT[5];
+ int inflections = findInflections(inflectT);
+ SkASSERT(inflections <= 2);
+ if (!endsAreExtremaInXOrY()) {
+ inflections += findMaxCurvature(&inflectT[inflections]);
+ SkASSERT(inflections <= 5);
+ }
+ QSort<double>(inflectT, &inflectT[inflections - 1]);
+ // OPTIMIZATION: is this filtering common enough that it needs to be pulled out into its
+ // own subroutine?
+ while (inflections && approximately_less_than_zero(inflectT[0])) {
+ memmove(inflectT, &inflectT[1], sizeof(inflectT[0]) * --inflections);
+ }
+ int start = 0;
+ do {
+ int next = start + 1;
+ if (next >= inflections) {
+ break;
+ }
+ if (!approximately_equal(inflectT[start], inflectT[next])) {
+ ++start;
+ continue;
+ }
+ memmove(&inflectT[start], &inflectT[next], sizeof(inflectT[0]) * (--inflections - start));
+ } while (true);
+ while (inflections && approximately_greater_than_one(inflectT[inflections - 1])) {
+ --inflections;
+ }
+ SkDCubicPair pair;
+ if (inflections == 1) {
+ pair = chopAt(inflectT[0]);
+ int orderP1 = reducer.reduce(pair.first(), SkReduceOrder::kNo_Quadratics,
+ SkReduceOrder::kFill_Style);
+ if (orderP1 < 2) {
+ --inflections;
+ } else {
+ int orderP2 = reducer.reduce(pair.second(), SkReduceOrder::kNo_Quadratics,
+ SkReduceOrder::kFill_Style);
+ if (orderP2 < 2) {
+ --inflections;
+ }
+ }
+ }
+ if (inflections == 0 && add_simple_ts(*this, precision, ts)) {
+ return;
+ }
+ if (inflections == 1) {
+ pair = chopAt(inflectT[0]);
+ addTs(pair.first(), precision, 0, inflectT[0], ts);
+ addTs(pair.second(), precision, inflectT[0], 1, ts);
+ return;
+ }
+ if (inflections > 1) {
+ SkDCubic part = subDivide(0, inflectT[0]);
+ addTs(part, precision, 0, inflectT[0], ts);
+ int last = inflections - 1;
+ for (int idx = 0; idx < last; ++idx) {
+ part = subDivide(inflectT[idx], inflectT[idx + 1]);
+ addTs(part, precision, inflectT[idx], inflectT[idx + 1], ts);
+ }
+ part = subDivide(inflectT[last], 1);
+ addTs(part, precision, inflectT[last], 1, ts);
+ return;
+ }
+ addTs(*this, precision, 0, 1, ts);
+}
diff --git a/src/pathops/SkDLineIntersection.cpp b/src/pathops/SkDLineIntersection.cpp
new file mode 100644
index 0000000000..8b02030fa6
--- /dev/null
+++ b/src/pathops/SkDLineIntersection.cpp
@@ -0,0 +1,282 @@
+/*
+ * 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"
+
+/* Determine the intersection point of two lines. This assumes the lines are not parallel,
+ and that that the lines are infinite.
+ From http://en.wikipedia.org/wiki/Line-line_intersection
+ */
+SkDPoint SkIntersections::Line(const SkDLine& a, const SkDLine& b) {
+ 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;
+ double denom = byLen * axLen - ayLen * bxLen;
+ SkASSERT(denom);
+ double term1 = a[1].fX * a[0].fY - a[1].fY * a[0].fX;
+ double term2 = b[1].fX * b[0].fY - b[1].fY * b[0].fX;
+ SkDPoint p;
+ p.fX = (term1 * bxLen - axLen * term2) / denom;
+ p.fY = (term1 * byLen - ayLen * term2) / denom;
+ return p;
+}
+
+int SkIntersections::computePoints(const SkDLine& line, int used) {
+ fPt[0] = line.xyAtT(fT[0][0]);
+ if ((fUsed = used) == 2) {
+ fPt[1] = line.xyAtT(fT[0][1]);
+ }
+ return fUsed;
+}
+
+/*
+ Determine the intersection point of two line segments
+ Return FALSE if the lines don't intersect
+ from: http://paulbourke.net/geometry/lineline2d/
+ */
+
+int SkIntersections::intersect(const SkDLine& a, const SkDLine& b) {
+ 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 denom = byLen * axLen - ayLen * bxLen;
+ 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;
+ bool mayNotOverlap = (numerA < 0 && denom > numerA) || (numerA > 0 && denom < numerA)
+ || (numerB < 0 && denom > numerB) || (numerB > 0 && denom < numerB);
+ numerA /= denom;
+ numerB /= denom;
+ if ((!approximately_zero(denom) || (!approximately_zero_inverse(numerA)
+ && !approximately_zero_inverse(numerB))) && !sk_double_isnan(numerA)
+ && !sk_double_isnan(numerB)) {
+ if (mayNotOverlap) {
+ return fUsed = 0;
+ }
+ fT[0][0] = numerA;
+ fT[1][0] = numerB;
+ fPt[0] = a.xyAtT(numerA);
+ return computePoints(a, 1);
+ }
+ /* 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
+ */
+ // FIXME: need to use AlmostEqualUlps variant instead
+ if (!approximately_equal_squared(axLen * a[0].fY - ayLen * a[0].fX,
+ axLen * b[0].fY - ayLen * b[0].fX)) {
+ return fUsed = 0;
+ }
+ const double* aPtr;
+ const double* bPtr;
+ if (fabs(axLen) > fabs(ayLen) || fabs(bxLen) > fabs(byLen)) {
+ aPtr = &a[0].fX;
+ bPtr = &b[0].fX;
+ } else {
+ aPtr = &a[0].fY;
+ bPtr = &b[0].fY;
+ }
+ double a0 = aPtr[0];
+ double a1 = aPtr[2];
+ double b0 = bPtr[0];
+ double b1 = bPtr[2];
+ // OPTIMIZATION: restructure to reject before the divide
+ // e.g., if ((a0 - b0) * (a0 - a1) < 0 || abs(a0 - b0) > abs(a0 - a1))
+ // (except efficient)
+ double aDenom = a0 - a1;
+ if (approximately_zero(aDenom)) {
+ if (!between(b0, a0, b1)) {
+ return fUsed = 0;
+ }
+ fT[0][0] = fT[0][1] = 0;
+ } else {
+ double at0 = (a0 - b0) / aDenom;
+ double at1 = (a0 - b1) / aDenom;
+ if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) {
+ return fUsed = 0;
+ }
+ fT[0][0] = SkTMax<double>(SkTMin<double>(at0, 1.0), 0.0);
+ fT[0][1] = SkTMax<double>(SkTMin<double>(at1, 1.0), 0.0);
+ }
+ double bDenom = b0 - b1;
+ if (approximately_zero(bDenom)) {
+ fT[1][0] = fT[1][1] = 0;
+ } else {
+ int bIn = aDenom * bDenom < 0;
+ fT[1][bIn] = SkTMax<double>(SkTMin<double>((b0 - a0) / bDenom, 1.0), 0.0);
+ fT[1][!bIn] = SkTMax<double>(SkTMin<double>((b0 - a1) / bDenom, 1.0), 0.0);
+ }
+ bool second = fabs(fT[0][0] - fT[0][1]) > FLT_EPSILON;
+ SkASSERT((fabs(fT[1][0] - fT[1][1]) <= FLT_EPSILON) ^ second);
+ return computePoints(a, 1 + second);
+}
+
+int SkIntersections::horizontal(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 fUsed = 0;
+ }
+ if (AlmostEqualUlps(min, max)) {
+ fT[0][0] = 0;
+ fT[0][1] = 1;
+ return fUsed = 2;
+ }
+ fT[0][0] = (y - line[0].fY) / (line[1].fY - line[0].fY);
+ return fUsed = 1;
+}
+
+// OPTIMIZATION Given: dy = line[1].fY - line[0].fY
+// and: xIntercept / (y - line[0].fY) == (line[1].fX - line[0].fX) / dy
+// then: xIntercept * dy == (line[1].fX - line[0].fX) * (y - line[0].fY)
+// Assuming that dy is always > 0, the line segment intercepts if:
+// left * dy <= xIntercept * dy <= right * dy
+// thus: left * dy <= (line[1].fX - line[0].fX) * (y - line[0].fY) <= right * dy
+// (clever as this is, it does not give us the t value, so may be useful only
+// as a quick reject -- and maybe not then; it takes 3 muls, 3 adds, 2 cmps)
+int SkIntersections::horizontal(const SkDLine& line, double left, double right, double y) {
+ int result = horizontal(line, y);
+ if (result != 1) {
+ SkASSERT(result == 0); // FIXME: this is incorrect if result == 2
+ return result;
+ }
+ double xIntercept = line[0].fX + fT[0][0] * (line[1].fX - line[0].fX);
+ if (xIntercept > right || xIntercept < left) {
+ return fUsed = 0;
+ }
+ return result;
+}
+
+int SkIntersections::horizontal(const SkDLine& line, double left, double right,
+ double y, bool flipped) {
+ int result = horizontal(line, y);
+ switch (result) {
+ case 0:
+ break;
+ case 1: {
+ double xIntercept = line[0].fX + fT[0][0] * (line[1].fX - line[0].fX);
+ if (xIntercept > right || xIntercept < left) {
+ return fUsed = 0;
+ }
+ fT[1][0] = (xIntercept - left) / (right - left);
+ break;
+ }
+ case 2:
+ double a0 = line[0].fX;
+ double a1 = line[1].fX;
+ double b0 = flipped ? right : left;
+ double b1 = flipped ? left : right;
+ // FIXME: share common code below
+ double at0 = (a0 - b0) / (a0 - a1);
+ double at1 = (a0 - b1) / (a0 - a1);
+ if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) {
+ return fUsed = 0;
+ }
+ fT[0][0] = SkTMax<double>(SkTMin<double>(at0, 1.0), 0.0);
+ fT[0][1] = SkTMax<double>(SkTMin<double>(at1, 1.0), 0.0);
+ int bIn = (a0 - a1) * (b0 - b1) < 0;
+ fT[1][bIn] = SkTMax<double>(SkTMin<double>((b0 - a0) / (b0 - b1), 1.0), 0.0);
+ fT[1][!bIn] = SkTMax<double>(SkTMin<double>((b0 - a1) / (b0 - b1), 1.0), 0.0);
+ bool second = fabs(fT[0][0] - fT[0][1]) > FLT_EPSILON;
+ SkASSERT((fabs(fT[1][0] - fT[1][1]) <= FLT_EPSILON) ^ second);
+ return computePoints(line, 1 + second);
+ }
+ 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];
+ }
+ }
+ return computePoints(line, result);
+}
+
+int SkIntersections::vertical(const SkDLine& line, double x) {
+ double min = line[0].fX;
+ double max = line[1].fX;
+ if (min > max) {
+ SkTSwap(min, max);
+ }
+ if (min > x || max < x) {
+ return fUsed = 0;
+ }
+ if (AlmostEqualUlps(min, max)) {
+ fT[0][0] = 0;
+ fT[0][1] = 1;
+ return fUsed = 2;
+ }
+ fT[0][0] = (x - line[0].fX) / (line[1].fX - line[0].fX);
+ return fUsed = 1;
+}
+
+int SkIntersections::vertical(const SkDLine& line, double top, double bottom,
+ double x, bool flipped) {
+ int result = vertical(line, x);
+ switch (result) {
+ case 0:
+ break;
+ case 1: {
+ double yIntercept = line[0].fY + fT[0][0] * (line[1].fY - line[0].fY);
+ if (yIntercept > bottom || yIntercept < top) {
+ return fUsed = 0;
+ }
+ fT[1][0] = (yIntercept - top) / (bottom - top);
+ break;
+ }
+ case 2:
+ double a0 = line[0].fY;
+ double a1 = line[1].fY;
+ double b0 = flipped ? bottom : top;
+ double b1 = flipped ? top : bottom;
+ // FIXME: share common code above
+ double at0 = (a0 - b0) / (a0 - a1);
+ double at1 = (a0 - b1) / (a0 - a1);
+ if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) {
+ return fUsed = 0;
+ }
+ fT[0][0] = SkTMax<double>(SkTMin<double>(at0, 1.0), 0.0);
+ fT[0][1] = SkTMax<double>(SkTMin<double>(at1, 1.0), 0.0);
+ int bIn = (a0 - a1) * (b0 - b1) < 0;
+ fT[1][bIn] = SkTMax<double>(SkTMin<double>((b0 - a0) / (b0 - b1), 1.0), 0.0);
+ fT[1][!bIn] = SkTMax<double>(SkTMin<double>((b0 - a1) / (b0 - b1), 1.0), 0.0);
+ bool second = fabs(fT[0][0] - fT[0][1]) > FLT_EPSILON;
+ SkASSERT((fabs(fT[1][0] - fT[1][1]) <= FLT_EPSILON) ^ second);
+ return computePoints(line, 1 + second);
+ break;
+ }
+ 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];
+ }
+ }
+ return computePoints(line, result);
+}
+
+// from http://www.bryceboe.com/wordpress/wp-content/uploads/2006/10/intersect.py
+// 4 subs, 2 muls, 1 cmp
+static bool ccw(const SkDPoint& A, const SkDPoint& B, const SkDPoint& C) {
+ return (C.fY - A.fY) * (B.fX - A.fX) > (B.fY - A.fY) * (C.fX - A.fX);
+}
+
+// 16 subs, 8 muls, 6 cmps
+bool SkIntersections::Test(const SkDLine& a, const SkDLine& b) {
+ return ccw(a[0], b[0], b[1]) != ccw(a[1], b[0], b[1])
+ && ccw(a[0], a[1], b[0]) != ccw(a[0], a[1], b[1]);
+}
diff --git a/src/pathops/SkDQuadImplicit.cpp b/src/pathops/SkDQuadImplicit.cpp
new file mode 100644
index 0000000000..9f1e882654
--- /dev/null
+++ b/src/pathops/SkDQuadImplicit.cpp
@@ -0,0 +1,117 @@
+/*
+ * 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 "SkDQuadImplicit.h"
+
+/* from http://tom.cs.byu.edu/~tom/papers/cvgip84.pdf 4.1
+ *
+ * This paper proves that Syvester's method can compute the implicit form of
+ * the quadratic from the parameterized form.
+ *
+ * Given x = a*t*t + b*t + c (the parameterized form)
+ * y = d*t*t + e*t + f
+ *
+ * we want to find an equation of the implicit form:
+ *
+ * A*x*x + B*x*y + C*y*y + D*x + E*y + F = 0
+ *
+ * The implicit form can be expressed as a 4x4 determinant, as shown.
+ *
+ * The resultant obtained by Syvester's method is
+ *
+ * | a b (c - x) 0 |
+ * | 0 a b (c - x) |
+ * | d e (f - y) 0 |
+ * | 0 d e (f - y) |
+ *
+ * which expands to
+ *
+ * d*d*x*x + -2*a*d*x*y + a*a*y*y
+ * + (-2*c*d*d + b*e*d - a*e*e + 2*a*f*d)*x
+ * + (-2*f*a*a + e*b*a - d*b*b + 2*d*c*a)*y
+ * +
+ * | a b c 0 |
+ * | 0 a b c | == 0.
+ * | d e f 0 |
+ * | 0 d e f |
+ *
+ * Expanding the constant determinant results in
+ *
+ * | a b c | | b c 0 |
+ * a*| e f 0 | + d*| a b c | ==
+ * | d e f | | d e f |
+ *
+ * a*(a*f*f + c*e*e - c*f*d - b*e*f) + d*(b*b*f + c*c*d - c*a*f - c*e*b)
+ *
+ */
+
+// OPTIMIZATION: test, verify tricky arithmetic
+static bool straight_forward = true;
+
+SkDQuadImplicit::SkDQuadImplicit(const SkDQuad& q) {
+ double a, b, c;
+ SkDQuad::SetABC(&q[0].fX, &a, &b, &c);
+ double d, e, f;
+ SkDQuad::SetABC(&q[0].fY, &d, &e, &f);
+ // compute the implicit coefficients
+ if (straight_forward) { // 42 muls, 13 adds
+ fP[kXx_Coeff] = d * d;
+ fP[kXy_Coeff] = -2 * a * d;
+ fP[kYy_Coeff] = a * a;
+ fP[kX_Coeff] = -2*c*d*d + b*e*d - a*e*e + 2*a*f*d;
+ fP[kY_Coeff] = -2*f*a*a + e*b*a - d*b*b + 2*d*c*a;
+ fP[kC_Coeff] = a*(a*f*f + c*e*e - c*f*d - b*e*f)
+ + d*(b*b*f + c*c*d - c*a*f - c*e*b);
+ } else { // 26 muls, 11 adds
+ double aa = a * a;
+ double ad = a * d;
+ double dd = d * d;
+ fP[kXx_Coeff] = dd;
+ fP[kXy_Coeff] = -2 * ad;
+ fP[kYy_Coeff] = aa;
+ double be = b * e;
+ double bde = be * d;
+ double cdd = c * dd;
+ double ee = e * e;
+ fP[kX_Coeff] = -2*cdd + bde - a*ee + 2*ad*f;
+ double aaf = aa * f;
+ double abe = a * be;
+ double ac = a * c;
+ double bb_2ac = b*b - 2*ac;
+ fP[kY_Coeff] = -2*aaf + abe - d*bb_2ac;
+ fP[kC_Coeff] = aaf*f + ac*ee + d*f*bb_2ac - abe*f + c*cdd - c*bde;
+ }
+}
+
+ /* Given a pair of quadratics, determine their parametric coefficients.
+ * If the scaled coefficients are nearly equal, then the part of the quadratics
+ * may be coincident.
+ * FIXME: optimization -- since comparison short-circuits on no match,
+ * lazily compute the coefficients, comparing the easiest to compute first.
+ * xx and yy first; then xy; and so on.
+ */
+bool SkDQuadImplicit::match(const SkDQuadImplicit& p2) const {
+ int first = 0;
+ for (int index = 0; index <= kC_Coeff; ++index) {
+ if (approximately_zero(fP[index]) && approximately_zero(p2.fP[index])) {
+ first += first == index;
+ continue;
+ }
+ if (first == index) {
+ continue;
+ }
+ if (!AlmostEqualUlps(fP[index] * p2.fP[first], fP[first] * p2.fP[index])) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SkDQuadImplicit::Match(const SkDQuad& quad1, const SkDQuad& quad2) {
+ SkDQuadImplicit i1(quad1); // a'xx , b'xy , c'yy , d'x , e'y , f
+ SkDQuadImplicit i2(quad2);
+ return i1.match(i2);
+}
diff --git a/src/pathops/SkDQuadImplicit.h b/src/pathops/SkDQuadImplicit.h
new file mode 100644
index 0000000000..24f1aac2ef
--- /dev/null
+++ b/src/pathops/SkDQuadImplicit.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 SkDQuadImplicit_DEFINED
+#define SkDQuadImplicit_DEFINED
+
+#include "SkPathOpsQuad.h"
+
+class SkDQuadImplicit {
+public:
+ explicit SkDQuadImplicit(const SkDQuad& q);
+
+ bool match(const SkDQuadImplicit& two) const;
+ static bool Match(const SkDQuad& quad1, const SkDQuad& quad2);
+
+ double x2() const { return fP[kXx_Coeff]; }
+ double xy() const { return fP[kXy_Coeff]; }
+ double y2() const { return fP[kYy_Coeff]; }
+ double x() const { return fP[kX_Coeff]; }
+ double y() const { return fP[kY_Coeff]; }
+ double c() const { return fP[kC_Coeff]; }
+
+private:
+ enum Coeffs {
+ kXx_Coeff,
+ kXy_Coeff,
+ kYy_Coeff,
+ kX_Coeff,
+ kY_Coeff,
+ kC_Coeff,
+ };
+
+ double fP[kC_Coeff + 1];
+};
+
+#endif
diff --git a/src/pathops/SkDQuadIntersection.cpp b/src/pathops/SkDQuadIntersection.cpp
new file mode 100644
index 0000000000..d8e3f20a28
--- /dev/null
+++ b/src/pathops/SkDQuadIntersection.cpp
@@ -0,0 +1,496 @@
+// Another approach is to start with the implicit form of one curve and solve
+// (seek implicit coefficients in QuadraticParameter.cpp
+// by substituting in the parametric form of the other.
+// The downside of this approach is that early rejects are difficult to come by.
+// http://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html#step
+
+
+#include "SkDQuadImplicit.h"
+#include "SkIntersections.h"
+#include "SkPathOpsLine.h"
+#include "SkQuarticRoot.h"
+#include "SkTDArray.h"
+#include "TSearch.h"
+
+/* given the implicit form 0 = Ax^2 + Bxy + Cy^2 + Dx + Ey + F
+ * and given x = at^2 + bt + c (the parameterized form)
+ * y = dt^2 + et + f
+ * then
+ * 0 = A(at^2+bt+c)(at^2+bt+c)+B(at^2+bt+c)(dt^2+et+f)+C(dt^2+et+f)(dt^2+et+f)+D(at^2+bt+c)+E(dt^2+et+f)+F
+ */
+
+static int findRoots(const SkDQuadImplicit& i, const SkDQuad& q2, double roots[4],
+ bool oneHint, int firstCubicRoot) {
+ double a, b, c;
+ SkDQuad::SetABC(&q2[0].fX, &a, &b, &c);
+ double d, e, f;
+ SkDQuad::SetABC(&q2[0].fY, &d, &e, &f);
+ const double t4 = i.x2() * a * a
+ + i.xy() * a * d
+ + i.y2() * d * d;
+ const double t3 = 2 * i.x2() * a * b
+ + i.xy() * (a * e + b * d)
+ + 2 * i.y2() * d * e;
+ const double t2 = i.x2() * (b * b + 2 * a * c)
+ + i.xy() * (c * d + b * e + a * f)
+ + i.y2() * (e * e + 2 * d * f)
+ + i.x() * a
+ + i.y() * d;
+ const double t1 = 2 * i.x2() * b * c
+ + i.xy() * (c * e + b * f)
+ + 2 * i.y2() * e * f
+ + i.x() * b
+ + i.y() * e;
+ const double t0 = i.x2() * c * c
+ + i.xy() * c * f
+ + i.y2() * f * f
+ + i.x() * c
+ + i.y() * f
+ + i.c();
+ int rootCount = SkReducedQuarticRoots(t4, t3, t2, t1, t0, oneHint, roots);
+ if (rootCount >= 0) {
+ return rootCount;
+ }
+ return SkQuarticRootsReal(firstCubicRoot, t4, t3, t2, t1, t0, roots);
+}
+
+static int addValidRoots(const double roots[4], const int count, double valid[4]) {
+ int result = 0;
+ int index;
+ for (index = 0; index < count; ++index) {
+ if (!approximately_zero_or_more(roots[index]) || !approximately_one_or_less(roots[index])) {
+ continue;
+ }
+ double t = 1 - roots[index];
+ if (approximately_less_than_zero(t)) {
+ t = 0;
+ } else if (approximately_greater_than_one(t)) {
+ t = 1;
+ }
+ valid[result++] = t;
+ }
+ return result;
+}
+
+static bool only_end_pts_in_common(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) {
+// the idea here is to see at minimum do a quick reject by rotating all points
+// to either side of the line formed by connecting the endpoints
+// if the opposite curves points are on the line or on the other side, the
+// curves at most intersect at the endpoints
+ for (int oddMan = 0; oddMan < 3; ++oddMan) {
+ const SkDPoint* endPt[2];
+ for (int opp = 1; opp < 3; ++opp) {
+ int end = oddMan ^ opp;
+ if (end == 3) {
+ end = opp;
+ }
+ endPt[opp - 1] = &q1[end];
+ }
+ double origX = endPt[0]->fX;
+ double origY = endPt[0]->fY;
+ double adj = endPt[1]->fX - origX;
+ double opp = endPt[1]->fY - origY;
+ double sign = (q1[oddMan].fY - origY) * adj - (q1[oddMan].fX - origX) * opp;
+ if (approximately_zero(sign)) {
+ goto tryNextHalfPlane;
+ }
+ for (int n = 0; n < 3; ++n) {
+ double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp;
+ if (test * sign > 0) {
+ goto tryNextHalfPlane;
+ }
+ }
+ for (int i1 = 0; i1 < 3; i1 += 2) {
+ for (int i2 = 0; i2 < 3; i2 += 2) {
+ if (q1[i1] == q2[i2]) {
+ i->insert(i1 >> 1, i2 >> 1, q1[i1]);
+ }
+ }
+ }
+ SkASSERT(i->used() < 3);
+ return true;
+tryNextHalfPlane:
+ ;
+ }
+ return false;
+}
+
+// returns false if there's more than one intercept or the intercept doesn't match the point
+// returns true if the intercept was successfully added or if the
+// original quads need to be subdivided
+static bool add_intercept(const SkDQuad& q1, const SkDQuad& q2, double tMin, double tMax,
+ SkIntersections* i, bool* subDivide) {
+ double tMid = (tMin + tMax) / 2;
+ SkDPoint mid = q2.xyAtT(tMid);
+ SkDLine line;
+ line[0] = line[1] = mid;
+ SkDVector dxdy = q2.dxdyAtT(tMid);
+ line[0] -= dxdy;
+ line[1] += dxdy;
+ SkIntersections rootTs;
+ int roots = rootTs.intersect(q1, line);
+ if (roots == 0) {
+ if (subDivide) {
+ *subDivide = true;
+ }
+ return true;
+ }
+ if (roots == 2) {
+ return false;
+ }
+ SkDPoint pt2 = q1.xyAtT(rootTs[0][0]);
+ if (!pt2.approximatelyEqualHalf(mid)) {
+ return false;
+ }
+ i->insertSwap(rootTs[0][0], tMid, pt2);
+ return true;
+}
+
+static bool is_linear_inner(const SkDQuad& q1, double t1s, double t1e, const SkDQuad& q2,
+ double t2s, double t2e, SkIntersections* i, bool* subDivide) {
+ SkDQuad hull = q1.subDivide(t1s, t1e);
+ SkDLine line = {{hull[2], hull[0]}};
+ const SkDLine* testLines[] = { &line, (const SkDLine*) &hull[0], (const SkDLine*) &hull[1] };
+ size_t testCount = sizeof(testLines) / sizeof(testLines[0]);
+ SkTDArray<double> tsFound;
+ for (size_t index = 0; index < testCount; ++index) {
+ SkIntersections rootTs;
+ int roots = rootTs.intersect(q2, *testLines[index]);
+ for (int idx2 = 0; idx2 < roots; ++idx2) {
+ double t = rootTs[0][idx2];
+#ifdef SK_DEBUG
+ SkDPoint qPt = q2.xyAtT(t);
+ SkDPoint lPt = testLines[index]->xyAtT(rootTs[1][idx2]);
+ SkASSERT(qPt.approximatelyEqual(lPt));
+#endif
+ if (approximately_negative(t - t2s) || approximately_positive(t - t2e)) {
+ continue;
+ }
+ *tsFound.append() = rootTs[0][idx2];
+ }
+ }
+ int tCount = tsFound.count();
+ if (tCount <= 0) {
+ return true;
+ }
+ double tMin, tMax;
+ if (tCount == 1) {
+ tMin = tMax = tsFound[0];
+ } else if (tCount > 1) {
+ QSort<double>(tsFound.begin(), tsFound.end() - 1);
+ tMin = tsFound[0];
+ tMax = tsFound[tsFound.count() - 1];
+ }
+ SkDPoint end = q2.xyAtT(t2s);
+ bool startInTriangle = hull.pointInHull(end);
+ if (startInTriangle) {
+ tMin = t2s;
+ }
+ end = q2.xyAtT(t2e);
+ bool endInTriangle = hull.pointInHull(end);
+ if (endInTriangle) {
+ tMax = t2e;
+ }
+ int split = 0;
+ SkDVector dxy1, dxy2;
+ if (tMin != tMax || tCount > 2) {
+ dxy2 = q2.dxdyAtT(tMin);
+ for (int index = 1; index < tCount; ++index) {
+ dxy1 = dxy2;
+ dxy2 = q2.dxdyAtT(tsFound[index]);
+ double dot = dxy1.dot(dxy2);
+ if (dot < 0) {
+ split = index - 1;
+ break;
+ }
+ }
+ }
+ if (split == 0) { // there's one point
+ if (add_intercept(q1, q2, tMin, tMax, i, subDivide)) {
+ return true;
+ }
+ i->swap();
+ return is_linear_inner(q2, tMin, tMax, q1, t1s, t1e, i, subDivide);
+ }
+ // At this point, we have two ranges of t values -- treat each separately at the split
+ bool result;
+ if (add_intercept(q1, q2, tMin, tsFound[split - 1], i, subDivide)) {
+ result = true;
+ } else {
+ i->swap();
+ result = is_linear_inner(q2, tMin, tsFound[split - 1], q1, t1s, t1e, i, subDivide);
+ }
+ if (add_intercept(q1, q2, tsFound[split], tMax, i, subDivide)) {
+ result = true;
+ } else {
+ i->swap();
+ result |= is_linear_inner(q2, tsFound[split], tMax, q1, t1s, t1e, i, subDivide);
+ }
+ return result;
+}
+
+static double flat_measure(const SkDQuad& q) {
+ SkDVector mid = q[1] - q[0];
+ SkDVector dxy = q[2] - q[0];
+ double length = dxy.length(); // OPTIMIZE: get rid of sqrt
+ return fabs(mid.cross(dxy) / length);
+}
+
+// FIXME ? should this measure both and then use the quad that is the flattest as the line?
+static bool is_linear(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) {
+ double measure = flat_measure(q1);
+ // OPTIMIZE: (get rid of sqrt) use approximately_zero
+ if (!approximately_zero_sqrt(measure)) {
+ return false;
+ }
+ return is_linear_inner(q1, 0, 1, q2, 0, 1, i, NULL);
+}
+
+// FIXME: if flat measure is sufficiently large, then probably the quartic solution failed
+static void relaxed_is_linear(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) {
+ double m1 = flat_measure(q1);
+ double m2 = flat_measure(q2);
+#if DEBUG_FLAT_QUADS
+ double min = SkTMin<double>(m1, m2);
+ if (min > 5) {
+ SkDebugf("%s maybe not flat enough.. %1.9g\n", __FUNCTION__, min);
+ }
+#endif
+ i->reset();
+ const SkDQuad& rounder = m2 < m1 ? q1 : q2;
+ const SkDQuad& flatter = m2 < m1 ? q2 : q1;
+ bool subDivide = false;
+ is_linear_inner(flatter, 0, 1, rounder, 0, 1, i, &subDivide);
+ if (subDivide) {
+ SkDQuadPair pair = flatter.chopAt(0.5);
+ SkIntersections firstI, secondI;
+ relaxed_is_linear(pair.first(), rounder, &firstI);
+ for (int index = 0; index < firstI.used(); ++index) {
+ i->insert(firstI[0][index] * 0.5, firstI[1][index], firstI.pt(index));
+ }
+ relaxed_is_linear(pair.second(), rounder, &secondI);
+ for (int index = 0; index < secondI.used(); ++index) {
+ i->insert(0.5 + secondI[0][index] * 0.5, secondI[1][index], secondI.pt(index));
+ }
+ }
+ if (m2 < m1) {
+ i->swapPts();
+ }
+}
+
+// each time through the loop, this computes values it had from the last loop
+// if i == j == 1, the center values are still good
+// otherwise, for i != 1 or j != 1, four of the values are still good
+// and if i == 1 ^ j == 1, an additional value is good
+static bool binary_search(const SkDQuad& quad1, const SkDQuad& quad2, double* t1Seed,
+ double* t2Seed, SkDPoint* pt) {
+ double tStep = ROUGH_EPSILON;
+ SkDPoint t1[3], t2[3];
+ int calcMask = ~0;
+ do {
+ if (calcMask & (1 << 1)) t1[1] = quad1.xyAtT(*t1Seed);
+ if (calcMask & (1 << 4)) t2[1] = quad2.xyAtT(*t2Seed);
+ if (t1[1].approximatelyEqual(t2[1])) {
+ *pt = t1[1];
+ #if ONE_OFF_DEBUG
+ SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) == (%1.9g,%1.9g)\n", __FUNCTION__,
+ t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY);
+ #endif
+ return true;
+ }
+ if (calcMask & (1 << 0)) t1[0] = quad1.xyAtT(*t1Seed - tStep);
+ if (calcMask & (1 << 2)) t1[2] = quad1.xyAtT(*t1Seed + tStep);
+ if (calcMask & (1 << 3)) t2[0] = quad2.xyAtT(*t2Seed - tStep);
+ if (calcMask & (1 << 5)) t2[2] = quad2.xyAtT(*t2Seed + tStep);
+ double dist[3][3];
+ // OPTIMIZE: using calcMask value permits skipping some distance calcuations
+ // if prior loop's results are moved to correct slot for reuse
+ dist[1][1] = t1[1].distanceSquared(t2[1]);
+ int best_i = 1, best_j = 1;
+ for (int i = 0; i < 3; ++i) {
+ for (int j = 0; j < 3; ++j) {
+ if (i == 1 && j == 1) {
+ continue;
+ }
+ dist[i][j] = t1[i].distanceSquared(t2[j]);
+ if (dist[best_i][best_j] > dist[i][j]) {
+ best_i = i;
+ best_j = j;
+ }
+ }
+ }
+ if (best_i == 1 && best_j == 1) {
+ tStep /= 2;
+ if (tStep < FLT_EPSILON_HALF) {
+ break;
+ }
+ calcMask = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5);
+ continue;
+ }
+ if (best_i == 0) {
+ *t1Seed -= tStep;
+ t1[2] = t1[1];
+ t1[1] = t1[0];
+ calcMask = 1 << 0;
+ } else if (best_i == 2) {
+ *t1Seed += tStep;
+ t1[0] = t1[1];
+ t1[1] = t1[2];
+ calcMask = 1 << 2;
+ } else {
+ calcMask = 0;
+ }
+ if (best_j == 0) {
+ *t2Seed -= tStep;
+ t2[2] = t2[1];
+ t2[1] = t2[0];
+ calcMask |= 1 << 3;
+ } else if (best_j == 2) {
+ *t2Seed += tStep;
+ t2[0] = t2[1];
+ t2[1] = t2[2];
+ calcMask |= 1 << 5;
+ }
+ } while (true);
+#if ONE_OFF_DEBUG
+ SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) != (%1.9g,%1.9g) %s\n", __FUNCTION__,
+ t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY);
+#endif
+ return false;
+}
+
+int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) {
+ // if the quads share an end point, check to see if they overlap
+
+ if (only_end_pts_in_common(q1, q2, this)) {
+ return fUsed;
+ }
+ if (only_end_pts_in_common(q2, q1, this)) {
+ swapPts();
+ return fUsed;
+ }
+ // see if either quad is really a line
+ if (is_linear(q1, q2, this)) {
+ return fUsed;
+ }
+ if (is_linear(q2, q1, this)) {
+ swapPts();
+ return fUsed;
+ }
+ SkDQuadImplicit i1(q1);
+ SkDQuadImplicit i2(q2);
+ if (i1.match(i2)) {
+ // FIXME: compute T values
+ // compute the intersections of the ends to find the coincident span
+ bool useVertical = fabs(q1[0].fX - q1[2].fX) < fabs(q1[0].fY - q1[2].fY);
+ double t;
+ if ((t = SkIntersections::Axial(q1, q2[0], useVertical)) >= 0) {
+ insertCoincident(t, 0, q2[0]);
+ }
+ if ((t = SkIntersections::Axial(q1, q2[2], useVertical)) >= 0) {
+ insertCoincident(t, 1, q2[2]);
+ }
+ useVertical = fabs(q2[0].fX - q2[2].fX) < fabs(q2[0].fY - q2[2].fY);
+ if ((t = SkIntersections::Axial(q2, q1[0], useVertical)) >= 0) {
+ insertCoincident(0, t, q1[0]);
+ }
+ if ((t = SkIntersections::Axial(q2, q1[2], useVertical)) >= 0) {
+ insertCoincident(1, t, q1[2]);
+ }
+ SkASSERT(coincidentUsed() <= 2);
+ return fUsed;
+ }
+ int index;
+ bool useCubic = q1[0] == q2[0] || q1[0] == q2[2] || q1[2] == q2[0];
+ double roots1[4];
+ int rootCount = findRoots(i2, q1, roots1, useCubic, 0);
+ // OPTIMIZATION: could short circuit here if all roots are < 0 or > 1
+ double roots1Copy[4];
+ int r1Count = addValidRoots(roots1, rootCount, roots1Copy);
+ SkDPoint pts1[4];
+ for (index = 0; index < r1Count; ++index) {
+ pts1[index] = q1.xyAtT(roots1Copy[index]);
+ }
+ double roots2[4];
+ int rootCount2 = findRoots(i1, q2, roots2, useCubic, 0);
+ double roots2Copy[4];
+ int r2Count = addValidRoots(roots2, rootCount2, roots2Copy);
+ SkDPoint pts2[4];
+ for (index = 0; index < r2Count; ++index) {
+ pts2[index] = q2.xyAtT(roots2Copy[index]);
+ }
+ if (r1Count == r2Count && r1Count <= 1) {
+ if (r1Count == 1) {
+ if (pts1[0].approximatelyEqualHalf(pts2[0])) {
+ insert(roots1Copy[0], roots2Copy[0], pts1[0]);
+ } else if (pts1[0].moreRoughlyEqual(pts2[0])) {
+ // experiment: try to find intersection by chasing t
+ rootCount = findRoots(i2, q1, roots1, useCubic, 0);
+ (void) addValidRoots(roots1, rootCount, roots1Copy);
+ rootCount2 = findRoots(i1, q2, roots2, useCubic, 0);
+ (void) addValidRoots(roots2, rootCount2, roots2Copy);
+ if (binary_search(q1, q2, roots1Copy, roots2Copy, pts1)) {
+ insert(roots1Copy[0], roots2Copy[0], pts1[0]);
+ }
+ }
+ }
+ return fUsed;
+ }
+ int closest[4];
+ double dist[4];
+ bool foundSomething = false;
+ for (index = 0; index < r1Count; ++index) {
+ dist[index] = DBL_MAX;
+ closest[index] = -1;
+ for (int ndex2 = 0; ndex2 < r2Count; ++ndex2) {
+ if (!pts2[ndex2].approximatelyEqualHalf(pts1[index])) {
+ continue;
+ }
+ double dx = pts2[ndex2].fX - pts1[index].fX;
+ double dy = pts2[ndex2].fY - pts1[index].fY;
+ double distance = dx * dx + dy * dy;
+ if (dist[index] <= distance) {
+ continue;
+ }
+ for (int outer = 0; outer < index; ++outer) {
+ if (closest[outer] != ndex2) {
+ continue;
+ }
+ if (dist[outer] < distance) {
+ goto next;
+ }
+ closest[outer] = -1;
+ }
+ dist[index] = distance;
+ closest[index] = ndex2;
+ foundSomething = true;
+ next:
+ ;
+ }
+ }
+ if (r1Count && r2Count && !foundSomething) {
+ relaxed_is_linear(q1, q2, this);
+ return fUsed;
+ }
+ int used = 0;
+ do {
+ double lowest = DBL_MAX;
+ int lowestIndex = -1;
+ for (index = 0; index < r1Count; ++index) {
+ if (closest[index] < 0) {
+ continue;
+ }
+ if (roots1Copy[index] < lowest) {
+ lowestIndex = index;
+ lowest = roots1Copy[index];
+ }
+ }
+ if (lowestIndex < 0) {
+ break;
+ }
+ insert(roots1Copy[lowestIndex], roots2Copy[closest[lowestIndex]],
+ pts1[lowestIndex]);
+ closest[lowestIndex] = -1;
+ } while (++used < r1Count);
+ return fUsed;
+}
diff --git a/src/pathops/SkDQuadLineIntersection.cpp b/src/pathops/SkDQuadLineIntersection.cpp
new file mode 100644
index 0000000000..e7e77e69e2
--- /dev/null
+++ b/src/pathops/SkDQuadLineIntersection.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"
+#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:
+ LineQuadraticIntersections(const SkDQuad& q, const SkDLine& l, SkIntersections* i)
+ : quad(q)
+ , line(l)
+ , intersections(i) {
+ }
+
+ 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 = line[1].fX - line[0].fX;
+ double opp = line[1].fY - line[0].fY;
+ double r[3];
+ for (int n = 0; n < 3; ++n) {
+ r[n] = (quad[n].fY - line[0].fY) * adj - (quad[n].fX - line[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() {
+ addEndPoints();
+ double rootVals[2];
+ int roots = intersectRay(rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double quadT = rootVals[index];
+ double lineT = findLineT(quadT);
+ if (PinTs(&quadT, &lineT)) {
+ SkDPoint pt = line.xyAtT(lineT);
+ intersections->insert(quadT, lineT, pt);
+ }
+ }
+ return intersections->used();
+ }
+
+ int horizontalIntersect(double axisIntercept, double roots[2]) {
+ double D = quad[2].fY; // f
+ double E = quad[1].fY; // e
+ double F = quad[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) {
+ addHorizontalEndPoints(left, right, axisIntercept);
+ double rootVals[2];
+ int roots = horizontalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double quadT = rootVals[index];
+ SkDPoint pt = quad.xyAtT(quadT);
+ double lineT = (pt.fX - left) / (right - left);
+ if (PinTs(&quadT, &lineT)) {
+ intersections->insert(quadT, lineT, pt);
+ }
+ }
+ if (flipped) {
+ intersections->flip();
+ }
+ return intersections->used();
+ }
+
+ int verticalIntersect(double axisIntercept, double roots[2]) {
+ double D = quad[2].fX; // f
+ double E = quad[1].fX; // e
+ double F = quad[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) {
+ addVerticalEndPoints(top, bottom, axisIntercept);
+ double rootVals[2];
+ int roots = verticalIntersect(axisIntercept, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double quadT = rootVals[index];
+ SkDPoint pt = quad.xyAtT(quadT);
+ double lineT = (pt.fY - top) / (bottom - top);
+ if (PinTs(&quadT, &lineT)) {
+ intersections->insert(quadT, lineT, pt);
+ }
+ }
+ if (flipped) {
+ intersections->flip();
+ }
+ return intersections->used();
+ }
+
+protected:
+ // add endpoints first to get zero and one t values exactly
+ void addEndPoints() {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ for (int lIndex = 0; lIndex < 2; lIndex++) {
+ if (quad[qIndex] == line[lIndex]) {
+ intersections->insert(qIndex >> 1, lIndex, line[lIndex]);
+ }
+ }
+ }
+ }
+
+ void addHorizontalEndPoints(double left, double right, double y) {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ if (quad[qIndex].fY != y) {
+ continue;
+ }
+ if (quad[qIndex].fX == left) {
+ intersections->insert(qIndex >> 1, 0, quad[qIndex]);
+ }
+ if (quad[qIndex].fX == right) {
+ intersections->insert(qIndex >> 1, 1, quad[qIndex]);
+ }
+ }
+ }
+
+ void addVerticalEndPoints(double top, double bottom, double x) {
+ for (int qIndex = 0; qIndex < 3; qIndex += 2) {
+ if (quad[qIndex].fX != x) {
+ continue;
+ }
+ if (quad[qIndex].fY == top) {
+ intersections->insert(qIndex >> 1, 0, quad[qIndex]);
+ }
+ if (quad[qIndex].fY == bottom) {
+ intersections->insert(qIndex >> 1, 1, quad[qIndex]);
+ }
+ }
+ }
+
+ double findLineT(double t) {
+ SkDPoint xy = quad.xyAtT(t);
+ double dx = line[1].fX - line[0].fX;
+ double dy = line[1].fY - line[0].fY;
+ if (fabs(dx) > fabs(dy)) {
+ return (xy.fX - line[0].fX) / dx;
+ }
+ return (xy.fY - line[0].fY) / dy;
+ }
+
+ static bool PinTs(double* quadT, double* lineT) {
+ if (!approximately_one_or_less(*lineT)) {
+ return false;
+ }
+ if (!approximately_zero_or_more(*lineT)) {
+ return false;
+ }
+ if (precisely_less_than_zero(*quadT)) {
+ *quadT = 0;
+ } else if (precisely_greater_than_one(*quadT)) {
+ *quadT = 1;
+ }
+ if (precisely_less_than_zero(*lineT)) {
+ *lineT = 0;
+ } else if (precisely_greater_than_one(*lineT)) {
+ *lineT = 1;
+ }
+ return true;
+ }
+
+private:
+ const SkDQuad& quad;
+ const SkDLine& line;
+ SkIntersections* intersections;
+};
+
+// utility for pairs of coincident quads
+static double horizontalIntersect(const SkDQuad& quad, const SkDPoint& pt) {
+ LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)),
+ static_cast<SkIntersections*>(0));
+ double rootVals[2];
+ int roots = q.horizontalIntersect(pt.fY, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double t = rootVals[index];
+ SkDPoint qPt = quad.xyAtT(t);
+ if (AlmostEqualUlps(qPt.fX, pt.fX)) {
+ return t;
+ }
+ }
+ return -1;
+}
+
+static double verticalIntersect(const SkDQuad& quad, const SkDPoint& pt) {
+ LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)),
+ static_cast<SkIntersections*>(0));
+ double rootVals[2];
+ int roots = q.verticalIntersect(pt.fX, rootVals);
+ for (int index = 0; index < roots; ++index) {
+ double t = rootVals[index];
+ SkDPoint qPt = quad.xyAtT(t);
+ if (AlmostEqualUlps(qPt.fY, pt.fY)) {
+ return t;
+ }
+ }
+ return -1;
+}
+
+double SkIntersections::Axial(const SkDQuad& q1, const SkDPoint& p, bool vertical) {
+ if (vertical) {
+ return verticalIntersect(q1, p);
+ }
+ return horizontalIntersect(q1, p);
+}
+
+int SkIntersections::horizontal(const SkDQuad& quad, double left, double right, double y,
+ bool flipped) {
+ LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)), this);
+ return q.horizontalIntersect(y, left, right, flipped);
+}
+
+int SkIntersections::vertical(const SkDQuad& quad, double top, double bottom, double x,
+ bool flipped) {
+ LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)), this);
+ return q.verticalIntersect(x, top, bottom, flipped);
+}
+
+int SkIntersections::intersect(const SkDQuad& quad, const SkDLine& line) {
+ LineQuadraticIntersections q(quad, line, this);
+ return q.intersect();
+}
+
+int SkIntersections::intersectRay(const SkDQuad& quad, const SkDLine& line) {
+ LineQuadraticIntersections q(quad, line, this);
+ return q.intersectRay(fT[0]);
+}
diff --git a/src/pathops/SkIntersectionHelper.h b/src/pathops/SkIntersectionHelper.h
new file mode 100644
index 0000000000..5d8ebcd590
--- /dev/null
+++ b/src/pathops/SkIntersectionHelper.h
@@ -0,0 +1,135 @@
+/*
+ * 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 "SkOpContour.h"
+#include "SkPath.h"
+
+class SkIntersectionHelper {
+public:
+ enum SegmentType {
+ kHorizontalLine_Segment = -1,
+ kVerticalLine_Segment = 0,
+ kLine_Segment = SkPath::kLine_Verb,
+ kQuad_Segment = SkPath::kQuad_Verb,
+ kCubic_Segment = SkPath::kCubic_Verb,
+ };
+
+ void addCoincident(SkIntersectionHelper& other, const SkIntersections& ts, bool swap) {
+ fContour->addCoincident(fIndex, other.fContour, other.fIndex, ts, swap);
+ }
+
+ // FIXME: does it make sense to write otherIndex now if we're going to
+ // fix it up later?
+ void addOtherT(int index, double otherT, int otherIndex) {
+ fContour->addOtherT(fIndex, index, otherT, otherIndex);
+ }
+
+ // Avoid collapsing t values that are close to the same since
+ // we walk ts to describe consecutive intersections. Since a pair of ts can
+ // be nearly equal, any problems caused by this should be taken care
+ // of later.
+ // On the edge or out of range values are negative; add 2 to get end
+ int addT(const SkIntersectionHelper& other, const SkPoint& pt, double newT) {
+ return fContour->addT(fIndex, other.fContour, other.fIndex, pt, newT);
+ }
+
+ int addSelfT(const SkIntersectionHelper& other, const SkPoint& pt, double newT) {
+ return fContour->addSelfT(fIndex, other.fContour, other.fIndex, pt, newT);
+ }
+
+ int addUnsortableT(const SkIntersectionHelper& other, bool start, const SkPoint& pt,
+ double newT) {
+ return fContour->addUnsortableT(fIndex, other.fContour, other.fIndex, start, pt, newT);
+ }
+
+ bool advance() {
+ return ++fIndex < fLast;
+ }
+
+ SkScalar bottom() const {
+ return bounds().fBottom;
+ }
+
+ const SkPathOpsBounds& bounds() const {
+ return fContour->segments()[fIndex].bounds();
+ }
+
+ void init(SkOpContour* contour) {
+ fContour = contour;
+ fIndex = 0;
+ fLast = contour->segments().count();
+ }
+
+ bool isAdjacent(const SkIntersectionHelper& next) {
+ return fContour == next.fContour && fIndex + 1 == next.fIndex;
+ }
+
+ bool isFirstLast(const SkIntersectionHelper& next) {
+ return fContour == next.fContour && fIndex == 0
+ && next.fIndex == fLast - 1;
+ }
+
+ SkScalar left() const {
+ return bounds().fLeft;
+ }
+
+ const SkPoint* pts() const {
+ return fContour->segments()[fIndex].pts();
+ }
+
+ SkScalar right() const {
+ return bounds().fRight;
+ }
+
+ SegmentType segmentType() const {
+ const SkOpSegment& segment = fContour->segments()[fIndex];
+ SegmentType type = (SegmentType) segment.verb();
+ if (type != kLine_Segment) {
+ return type;
+ }
+ if (segment.isHorizontal()) {
+ return kHorizontalLine_Segment;
+ }
+ if (segment.isVertical()) {
+ return kVerticalLine_Segment;
+ }
+ return kLine_Segment;
+ }
+
+ bool startAfter(const SkIntersectionHelper& after) {
+ fIndex = after.fIndex;
+ return advance();
+ }
+
+ SkScalar top() const {
+ return bounds().fTop;
+ }
+
+ SkPath::Verb verb() const {
+ return fContour->segments()[fIndex].verb();
+ }
+
+ 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:
+ SkOpContour* fContour;
+ int fIndex;
+ int fLast;
+};
diff --git a/src/pathops/SkIntersections.cpp b/src/pathops/SkIntersections.cpp
new file mode 100644
index 0000000000..205308f646
--- /dev/null
+++ b/src/pathops/SkIntersections.cpp
@@ -0,0 +1,261 @@
+/*
+ * 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::*CurveVertical[])(const SkPoint[], SkScalar, SkScalar, SkScalar, bool) = {
+ NULL,
+ &SkIntersections::verticalLine,
+ &SkIntersections::verticalQuad,
+ &SkIntersections::verticalCubic
+};
+
+int (SkIntersections::*CurveRay[])(const SkPoint[], const SkDLine&) = {
+ NULL,
+ NULL,
+ &SkIntersections::quadRay,
+ &SkIntersections::cubicRay
+};
+
+int SkIntersections::coincidentUsed() const {
+ if (!fIsCoincident[0]) {
+ SkASSERT(!fIsCoincident[0]);
+ 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;
+}
+
+int SkIntersections::cubicRay(const SkPoint pts[4], const SkDLine& line) {
+ SkDCubic cubic;
+ cubic.set(pts);
+ return intersectRay(cubic, line);
+}
+
+void SkIntersections::flip() {
+ for (int index = 0; index < fUsed; ++index) {
+ fT[1][index] = 1 - fT[1][index];
+ }
+}
+
+void SkIntersections::insertCoincidentPair(double s1, double e1, double s2, double e2,
+ const SkDPoint& startPt, const SkDPoint& endPt) {
+ if (fSwap) {
+ remove(s2, e2, startPt, endPt);
+ } else {
+ remove(s1, e1, startPt, endPt);
+ }
+ SkASSERT(coincidentUsed() == fUsed);
+ SkASSERT((coincidentUsed() & 1) != 1);
+ int i1 = 0;
+ int i2 = 0;
+ do {
+ while (i1 < fUsed && !(fIsCoincident[fSwap] & (1 << i1))) {
+ ++i1;
+ }
+ if (i1 == fUsed) {
+ break;
+ }
+ SkASSERT(i1 < fUsed);
+ int iEnd1 = i1 + 1;
+ while (!(fIsCoincident[fSwap] & (1 << iEnd1))) {
+ ++iEnd1;
+ }
+ SkASSERT(iEnd1 < fUsed);
+ double cs1 = fT[fSwap][i1];
+ double ce1 = fT[fSwap][iEnd1];
+ bool s1in = between(cs1, s1, ce1) || startPt.approximatelyEqual(fPt[i1])
+ || startPt.approximatelyEqual(fPt[iEnd1]);
+ bool e1in = between(cs1, e1, ce1) || endPt.approximatelyEqual(fPt[i1])
+ || endPt.approximatelyEqual(fPt[iEnd1]);
+ while (i2 < fUsed && !(fIsCoincident[fSwap ^ 1] & (1 << i2))) {
+ ++i2;
+ }
+ int iEnd2 = i2 + 1;
+ while (!(fIsCoincident[fSwap ^ 1] & (1 << iEnd2))) {
+ ++iEnd2;
+ }
+ SkASSERT(iEnd2 < fUsed);
+ double cs2 = fT[fSwap ^ 1][i2];
+ double ce2 = fT[fSwap ^ 1][iEnd2];
+ bool s2in = between(cs2, s2, ce2) || startPt.approximatelyEqual(fPt[i2])
+ || startPt.approximatelyEqual(fPt[iEnd2]);
+ bool e2in = between(cs2, e2, ce2) || endPt.approximatelyEqual(fPt[i2])
+ || endPt.approximatelyEqual(fPt[iEnd2]);
+ if ((s1in | e1in) & (s2in | e2in)) {
+ if (s1 < cs1) {
+ fT[fSwap][i1] = s1;
+ fPt[i1] = startPt;
+ } else if (e1 < cs1) {
+ fT[fSwap][i1] = e1;
+ fPt[i1] = endPt;
+ }
+ if (s1 > ce1) {
+ fT[fSwap][iEnd1] = s1;
+ fPt[iEnd1] = startPt;
+ } else if (e1 > ce1) {
+ fT[fSwap][iEnd1] = e1;
+ fPt[iEnd1] = endPt;
+ }
+ if (s2 > e2) {
+ SkTSwap(cs2, ce2);
+ SkTSwap(i2, iEnd2);
+ }
+ if (s2 < cs2) {
+ fT[fSwap ^ 1][i2] = s2;
+ } else if (e2 < cs2) {
+ fT[fSwap ^ 1][i2] = e2;
+ }
+ if (s2 > ce2) {
+ fT[fSwap ^ 1][iEnd2] = s2;
+ } else if (e2 > ce2) {
+ fT[fSwap ^ 1][iEnd2] = e2;
+ }
+ return;
+ }
+ } while (true);
+ SkASSERT(fUsed < 9);
+ insertCoincident(s1, s2, startPt);
+ insertCoincident(e1, e2, endPt);
+}
+
+int SkIntersections::insert(double one, double two, const SkDPoint& pt) {
+ 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 (roughly_equal(oldOne, one) && 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))) {
+ 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;
+ }
+ }
+ SkASSERT(fUsed < 9);
+ 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);
+ fIsCoincident[0] += fIsCoincident[0] & ~((1 << index) - 1);
+ fIsCoincident[1] += fIsCoincident[1] & ~((1 << index) - 1);
+ }
+ fPt[index] = pt;
+ fT[0][index] = one;
+ fT[1][index] = two;
+ ++fUsed;
+ return index;
+}
+
+void SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) {
+ int index = insertSwap(one, two, pt);
+ int bit = 1 << index;
+ fIsCoincident[0] |= bit;
+ fIsCoincident[1] |= bit;
+}
+
+void SkIntersections::offset(int base, double start, double end) {
+ for (int index = base; index < fUsed; ++index) {
+ double val = fT[fSwap][index];
+ val *= end - start;
+ val += start;
+ fT[fSwap][index] = val;
+ }
+}
+
+int SkIntersections::quadRay(const SkPoint pts[3], const SkDLine& line) {
+ SkDQuad quad;
+ quad.set(pts);
+ return intersectRay(quad, line);
+}
+
+void SkIntersections::quickRemoveOne(int index, int replace) {
+ if (index < replace) {
+ fT[0][index] = fT[0][replace];
+ }
+}
+
+void SkIntersections::remove(double one, double two, const SkDPoint& startPt,
+ const SkDPoint& endPt) {
+ for (int index = fUsed - 1; index >= 0; --index) {
+ if (!(fIsCoincident[0] & (1 << index)) && (between(one, fT[fSwap][index], two)
+ || startPt.approximatelyEqual(fPt[index])
+ || endPt.approximatelyEqual(fPt[index]))) {
+ SkASSERT(fUsed > 0);
+ removeOne(index);
+ }
+ }
+}
+
+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;
+}
+
+void SkIntersections::swapPts() {
+ int index;
+ for (index = 0; index < fUsed; ++index) {
+ SkTSwap(fT[0][index], fT[1][index]);
+ }
+}
+
+int SkIntersections::verticalLine(const SkPoint a[2], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped) {
+ SkDLine line;
+ line.set(a);
+ return vertical(line, top, bottom, x, flipped);
+}
+
+int SkIntersections::verticalQuad(const SkPoint a[3], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped) {
+ SkDQuad quad;
+ quad.set(a);
+ return vertical(quad, top, bottom, x, flipped);
+}
+
+int SkIntersections::verticalCubic(const SkPoint a[4], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return vertical(cubic, top, bottom, x, flipped);
+}
diff --git a/src/pathops/SkIntersections.h b/src/pathops/SkIntersections.h
new file mode 100644
index 0000000000..c2f05fe936
--- /dev/null
+++ b/src/pathops/SkIntersections.h
@@ -0,0 +1,243 @@
+/*
+ * 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 "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsPoint.h"
+#include "SkPathOpsQuad.h"
+
+class SkIntersections {
+public:
+ SkIntersections()
+ : fSwap(0)
+#ifdef SK_DEBUG
+ , fDepth(0)
+#endif
+ {
+ sk_bzero(fPt, sizeof(fPt));
+ sk_bzero(fT, sizeof(fT));
+ sk_bzero(fIsCoincident, sizeof(fIsCoincident));
+ reset();
+ }
+
+ class TArray {
+ public:
+ explicit TArray(const double ts[9]) : fTArray(ts) {}
+ double operator[](int n) const {
+ return fTArray[n];
+ }
+ const double* fTArray;
+ };
+ TArray operator[](int n) const { return TArray(fT[n]); }
+
+ int cubic(const SkPoint a[4]) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return intersect(cubic);
+ }
+
+ int cubicCubic(const SkPoint a[4], const SkPoint b[4]) {
+ SkDCubic aCubic;
+ aCubic.set(a);
+ SkDCubic bCubic;
+ bCubic.set(b);
+ return intersect(aCubic, bCubic);
+ }
+
+ int cubicHorizontal(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y,
+ bool flipped) {
+ SkDCubic cubic;
+ cubic.set(a);
+ 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);
+ 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);
+ return intersect(cubic, line);
+ }
+
+ int cubicQuad(const SkPoint a[4], const SkPoint b[3]) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkDQuad quad;
+ quad.set(b);
+ return intersect(cubic, quad);
+ }
+
+ 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);
+ 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);
+ 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);
+ return intersect(aLine, bLine);
+ }
+
+ const SkDPoint& pt(int index) const {
+ return fPt[index];
+ }
+
+ int quadHorizontal(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y,
+ bool flipped) {
+ SkDQuad quad;
+ quad.set(a);
+ 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);
+ 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);
+ }
+
+ int quadQuad(const SkPoint a[3], const SkPoint b[3]) {
+ SkDQuad aQuad;
+ aQuad.set(a);
+ SkDQuad bQuad;
+ bQuad.set(b);
+ return intersect(aQuad, bQuad);
+ }
+
+ int quadRay(const SkPoint pts[3], const SkDLine& line);
+ void removeOne(int index);
+
+ // leaves flip, swap alone
+ void reset() {
+ fUsed = 0;
+ }
+
+ void swap() {
+ fSwap ^= true;
+ }
+
+ void swapPts();
+
+ bool swapped() const {
+ return fSwap;
+ }
+
+ int used() const {
+ return fUsed;
+ }
+
+ void downDepth() {
+ SkASSERT(--fDepth >= 0);
+ }
+
+ void upDepth() {
+ SkASSERT(++fDepth < 16);
+ }
+
+ static double Axial(const SkDQuad& , const SkDPoint& , bool vertical);
+ int coincidentUsed() const;
+ int cubicRay(const SkPoint pts[4], const SkDLine& line);
+ void flip();
+ int horizontal(const SkDLine&, double y);
+ int horizontal(const SkDLine&, double left, double right, double y);
+ 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 SkDCubic&, double left, double right, double y, bool flipped);
+ int horizontal(const SkDCubic&, double left, double right, double y, double tRange[3]);
+ // FIXME : does not respect swap
+ int insert(double one, double two, const SkDPoint& pt);
+ // start if index == 0 : end if index == 1
+ void insertCoincident(double one, double two, const SkDPoint& pt);
+ void insertCoincidentPair(double s1, double e1, double s2, double e2,
+ const SkDPoint& startPt, const SkDPoint& endPt);
+ int intersect(const SkDLine&, const SkDLine&);
+ int intersect(const SkDQuad&, const SkDLine&);
+ int intersect(const SkDQuad&, const SkDQuad&);
+ int intersect(const SkDCubic&); // return true if cubic self-intersects
+ int intersect(const SkDCubic&, const SkDLine&);
+ int intersect(const SkDCubic&, const SkDQuad&);
+ int intersect(const SkDCubic&, const SkDCubic&);
+ int intersectRay(const SkDCubic& , const SkDLine&);
+ int intersectRay(const SkDQuad& , const SkDLine&);
+ static SkDPoint Line(const SkDLine& , const SkDLine&);
+ void offset(int base, double start, double end);
+ void quickRemoveOne(int index, int replace);
+ static bool Test(const SkDLine& , const SkDLine&);
+ int vertical(const SkDLine&, double x);
+ 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 SkDCubic&, double top, double bottom, double x, bool flipped);
+ int verticalCubic(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped);
+ int verticalLine(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped);
+ int verticalQuad(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped);
+
+ int depth() const {
+#ifdef SK_DEBUG
+ return fDepth;
+#else
+ return 0;
+#endif
+ }
+
+private:
+ int computePoints(const SkDLine& line, int used);
+ // used by addCoincident to remove ordinary intersections in range
+ void remove(double one, double two, const SkDPoint& startPt, const SkDPoint& endPt);
+
+ SkDPoint fPt[9];
+ double fT[2][9];
+ uint16_t fIsCoincident[2]; // bit arrays, one bit set for each coincident T
+ unsigned char fUsed;
+ bool fSwap;
+#ifdef SK_DEBUG
+ int fDepth;
+#endif
+};
+
+extern int (SkIntersections::*CurveRay[])(const SkPoint[], const SkDLine& );
+extern int (SkIntersections::*CurveVertical[])(const SkPoint[], SkScalar top, SkScalar bottom,
+ SkScalar x, bool flipped);
+
+#endif
diff --git a/src/pathops/SkLineParameters.h b/src/pathops/SkLineParameters.h
new file mode 100644
index 0000000000..5139c6c6cf
--- /dev/null
+++ b/src/pathops/SkLineParameters.h
@@ -0,0 +1,110 @@
+/*
+ * 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"
+#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:
+ void cubicEndPoints(const SkDCubic& pts) {
+ cubicEndPoints(pts, 0, 3);
+ }
+
+ void cubicEndPoints(const SkDCubic& pts, int s, int e) {
+ a = approximately_pin(pts[s].fY - pts[e].fY);
+ b = approximately_pin(pts[e].fX - pts[s].fX);
+ c = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY;
+ }
+
+ void lineEndPoints(const SkDLine& pts) {
+ a = approximately_pin(pts[0].fY - pts[1].fY);
+ b = approximately_pin(pts[1].fX - pts[0].fX);
+ c = pts[0].fX * pts[1].fY - pts[1].fX * pts[0].fY;
+ }
+
+ void quadEndPoints(const SkDQuad& pts) {
+ quadEndPoints(pts, 0, 2);
+ }
+
+ void quadEndPoints(const SkDQuad& pts, int s, int e) {
+ a = approximately_pin(pts[s].fY - pts[e].fY);
+ b = approximately_pin(pts[e].fX - pts[s].fX);
+ c = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY;
+ }
+
+ double normalSquared() const {
+ return a * a + b * b;
+ }
+
+ bool normalize() {
+ double normal = sqrt(normalSquared());
+ if (approximately_zero(normal)) {
+ a = b = c = 0;
+ return false;
+ }
+ double reciprocal = 1 / normal;
+ a *= reciprocal;
+ b *= reciprocal;
+ c *= 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 = a * pts[index].fX + b * pts[index].fY + c;
+ }
+ }
+
+ 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 = a * pts[index].fX + b * pts[index].fY + c;
+ }
+ }
+
+ double controlPtDistance(const SkDCubic& pts, int index) const {
+ SkASSERT(index == 1 || index == 2);
+ return a * pts[index].fX + b * pts[index].fY + c;
+ }
+
+ double controlPtDistance(const SkDQuad& pts) const {
+ return a * pts[1].fX + b * pts[1].fY + c;
+ }
+
+ double pointDistance(const SkDPoint& pt) const {
+ return a * pt.fX + b * pt.fY + c;
+ }
+
+ double dx() const {
+ return b;
+ }
+
+ double dy() const {
+ return -a;
+ }
+
+private:
+ double a;
+ double b;
+ double c;
+};
diff --git a/src/pathops/SkOpAngle.cpp b/src/pathops/SkOpAngle.cpp
new file mode 100644
index 0000000000..fd1e0f4cd0
--- /dev/null
+++ b/src/pathops/SkOpAngle.cpp
@@ -0,0 +1,255 @@
+/*
+ * 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 "SkOpAngle.h"
+#include "SkPathOpsCurve.h"
+
+// FIXME: this is bogus for quads and cubics
+// if the quads and cubics' line from end pt to ctrl pt are coincident,
+// there's no obvious way to determine the curve ordering from the
+// derivatives alone. In particular, if one quadratic's coincident tangent
+// is longer than the other curve, the final control point can place the
+// longer curve on either side of the shorter one.
+// Using Bezier curve focus http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf
+// may provide some help, but nothing has been figured out yet.
+
+/*(
+for quads and cubics, set up a parameterized line (e.g. LineParameters )
+for points [0] to [1]. See if point [2] is on that line, or on one side
+or the other. If it both quads' end points are on the same side, choose
+the shorter tangent. If the tangents are equal, choose the better second
+tangent angle
+
+maybe I could set up LineParameters lazily
+*/
+bool SkOpAngle::operator<(const SkOpAngle& rh) const {
+ double y = dy();
+ double ry = rh.dy();
+ if ((y < 0) ^ (ry < 0)) { // OPTIMIZATION: better to use y * ry < 0 ?
+ return y < 0;
+ }
+ double x = dx();
+ double rx = rh.dx();
+ if (y == 0 && ry == 0 && x * rx < 0) {
+ return x < rx;
+ }
+ double x_ry = x * ry;
+ double rx_y = rx * y;
+ double cmp = x_ry - rx_y;
+ if (!approximately_zero(cmp)) {
+ return cmp < 0;
+ }
+ if (approximately_zero(x_ry) && approximately_zero(rx_y)
+ && !approximately_zero_squared(cmp)) {
+ return cmp < 0;
+ }
+ // at this point, the initial tangent line is coincident
+ // see if edges curl away from each other
+ if (fSide * rh.fSide <= 0 && (!approximately_zero(fSide)
+ || !approximately_zero(rh.fSide))) {
+ // FIXME: running demo will trigger this assertion
+ // (don't know if commenting out will trigger further assertion or not)
+ // commenting it out allows demo to run in release, though
+ return fSide < rh.fSide;
+ }
+ // see if either curve can be lengthened and try the tangent compare again
+ if (cmp && (*fSpans)[fEnd].fOther != rh.fSegment // tangents not absolutely identical
+ && (*rh.fSpans)[rh.fEnd].fOther != fSegment) { // and not intersecting
+ SkOpAngle longer = *this;
+ SkOpAngle rhLonger = rh;
+ if (longer.lengthen() | rhLonger.lengthen()) {
+ return longer < rhLonger;
+ }
+ }
+ if ((fVerb == SkPath::kLine_Verb && approximately_zero(x) && approximately_zero(y))
+ || (rh.fVerb == SkPath::kLine_Verb
+ && approximately_zero(rx) && approximately_zero(ry))) {
+ // See general unsortable comment below. This case can happen when
+ // one line has a non-zero change in t but no change in x and y.
+ fUnsortable = true;
+ rh.fUnsortable = true;
+ return this < &rh; // even with no solution, return a stable sort
+ }
+ if ((*rh.fSpans)[SkMin32(rh.fStart, rh.fEnd)].fTiny
+ || (*fSpans)[SkMin32(fStart, fEnd)].fTiny) {
+ fUnsortable = true;
+ rh.fUnsortable = true;
+ return this < &rh; // even with no solution, return a stable sort
+ }
+ SkASSERT(fVerb >= SkPath::kQuad_Verb);
+ SkASSERT(rh.fVerb >= SkPath::kQuad_Verb);
+ // FIXME: until I can think of something better, project a ray from the
+ // end of the shorter tangent to midway between the end points
+ // through both curves and use the resulting angle to sort
+ // FIXME: some of this setup can be moved to set() if it works, or cached if it's expensive
+ double len = fTangent1.normalSquared();
+ double rlen = rh.fTangent1.normalSquared();
+ SkDLine ray;
+ SkIntersections i, ri;
+ int roots, rroots;
+ bool flip = false;
+ do {
+ bool useThis = (len < rlen) ^ flip;
+ const SkDCubic& part = useThis ? fCurvePart : rh.fCurvePart;
+ SkPath::Verb partVerb = useThis ? fVerb : rh.fVerb;
+ ray[0] = partVerb == SkPath::kCubic_Verb && part[0].approximatelyEqual(part[1]) ?
+ part[2] : part[1];
+ ray[1].fX = (part[0].fX + part[partVerb].fX) / 2;
+ ray[1].fY = (part[0].fY + part[partVerb].fY) / 2;
+ SkASSERT(ray[0] != ray[1]);
+ roots = (i.*CurveRay[fVerb])(fPts, ray);
+ rroots = (ri.*CurveRay[rh.fVerb])(rh.fPts, ray);
+ } while ((roots == 0 || rroots == 0) && (flip ^= true));
+ if (roots == 0 || rroots == 0) {
+ // FIXME: we don't have a solution in this case. The interim solution
+ // is to mark the edges as unsortable, exclude them from this and
+ // future computations, and allow the returned path to be fragmented
+ fUnsortable = true;
+ rh.fUnsortable = true;
+ return this < &rh; // even with no solution, return a stable sort
+ }
+ SkDPoint loc;
+ double best = SK_ScalarInfinity;
+ SkDVector dxy;
+ double dist;
+ int index;
+ for (index = 0; index < roots; ++index) {
+ loc = (*CurveDPointAtT[fVerb])(fPts, i[0][index]);
+ dxy = loc - ray[0];
+ dist = dxy.lengthSquared();
+ if (best > dist) {
+ best = dist;
+ }
+ }
+ for (index = 0; index < rroots; ++index) {
+ loc = (*CurveDPointAtT[rh.fVerb])(rh.fPts, ri[0][index]);
+ dxy = loc - ray[0];
+ dist = dxy.lengthSquared();
+ if (best > dist) {
+ return fSide < 0;
+ }
+ }
+ return fSide > 0;
+}
+
+bool SkOpAngle::lengthen() {
+ int newEnd = fEnd;
+ if (fStart < fEnd ? ++newEnd < fSpans->count() : --newEnd >= 0) {
+ fEnd = newEnd;
+ setSpans();
+ return true;
+ }
+ return false;
+}
+
+bool SkOpAngle::reverseLengthen() {
+ if (fReversed) {
+ return false;
+ }
+ int newEnd = fStart;
+ if (fStart > fEnd ? ++newEnd < fSpans->count() : --newEnd >= 0) {
+ fEnd = newEnd;
+ fReversed = true;
+ setSpans();
+ return true;
+ }
+ return false;
+}
+
+void SkOpAngle::set(const SkPoint* orig, SkPath::Verb verb, const SkOpSegment* segment,
+ int start, int end, const SkTDArray<SkOpSpan>& spans) {
+ fSegment = segment;
+ fStart = start;
+ fEnd = end;
+ fPts = orig;
+ fVerb = verb;
+ fSpans = &spans;
+ fReversed = false;
+ fUnsortable = false;
+ setSpans();
+}
+
+
+void SkOpAngle::setSpans() {
+ double startT = (*fSpans)[fStart].fT;
+ double endT = (*fSpans)[fEnd].fT;
+ switch (fVerb) {
+ case SkPath::kLine_Verb: {
+ SkDLine l = SkDLine::SubDivide(fPts, startT, endT);
+ // OPTIMIZATION: for pure line compares, we never need fTangent1.c
+ fTangent1.lineEndPoints(l);
+ fSide = 0;
+ } break;
+ case SkPath::kQuad_Verb: {
+ SkDQuad& quad = *SkTCast<SkDQuad*>(&fCurvePart);
+ quad = SkDQuad::SubDivide(fPts, startT, endT);
+ fTangent1.quadEndPoints(quad, 0, 1);
+ if (dx() == 0 && dy() == 0) {
+ fTangent1.quadEndPoints(quad);
+ }
+ fSide = -fTangent1.pointDistance(fCurvePart[2]); // not normalized -- compare sign only
+ } break;
+ case SkPath::kCubic_Verb: {
+ int nextC = 2;
+ fCurvePart = SkDCubic::SubDivide(fPts, startT, endT);
+ fTangent1.cubicEndPoints(fCurvePart, 0, 1);
+ if (dx() == 0 && dy() == 0) {
+ fTangent1.cubicEndPoints(fCurvePart, 0, 2);
+ nextC = 3;
+ if (dx() == 0 && dy() == 0) {
+ fTangent1.cubicEndPoints(fCurvePart, 0, 3);
+ }
+ }
+ fSide = -fTangent1.pointDistance(fCurvePart[nextC]); // compare sign only
+ if (nextC == 2 && approximately_zero(fSide)) {
+ fSide = -fTangent1.pointDistance(fCurvePart[3]);
+ }
+ } break;
+ default:
+ SkASSERT(0);
+ }
+ fUnsortable = dx() == 0 && dy() == 0;
+ if (fUnsortable) {
+ return;
+ }
+ SkASSERT(fStart != fEnd);
+ int step = fStart < fEnd ? 1 : -1; // OPTIMIZE: worth fStart - fEnd >> 31 type macro?
+ for (int index = fStart; index != fEnd; index += step) {
+#if 1
+ const SkOpSpan& thisSpan = (*fSpans)[index];
+ const SkOpSpan& nextSpan = (*fSpans)[index + step];
+ if (thisSpan.fTiny || precisely_equal(thisSpan.fT, nextSpan.fT)) {
+ continue;
+ }
+ fUnsortable = step > 0 ? thisSpan.fUnsortableStart : nextSpan.fUnsortableEnd;
+#if DEBUG_UNSORTABLE
+ if (fUnsortable) {
+ SkPoint iPt = (*CurvePointAtT[fVerb])(fPts, thisSpan.fT);
+ SkPoint ePt = (*CurvePointAtT[fVerb])(fPts, nextSpan.fT);
+ SkDebugf("%s unsortable [%d] (%1.9g,%1.9g) [%d] (%1.9g,%1.9g)\n", __FUNCTION__,
+ index, iPt.fX, iPt.fY, fEnd, ePt.fX, ePt.fY);
+ }
+#endif
+ return;
+#else
+ if ((*fSpans)[index].fUnsortableStart) {
+ fUnsortable = true;
+ return;
+ }
+#endif
+ }
+#if 1
+#if DEBUG_UNSORTABLE
+ SkPoint iPt = (*CurvePointAtT[fVerb])(fPts, startT);
+ SkPoint ePt = (*CurvePointAtT[fVerb])(fPts, endT);
+ SkDebugf("%s all tiny unsortable [%d] (%1.9g,%1.9g) [%d] (%1.9g,%1.9g)\n", __FUNCTION__,
+ fStart, iPt.fX, iPt.fY, fEnd, ePt.fX, ePt.fY);
+#endif
+ fUnsortable = true;
+#endif
+}
+
diff --git a/src/pathops/SkOpAngle.h b/src/pathops/SkOpAngle.h
new file mode 100644
index 0000000000..d599dea7c2
--- /dev/null
+++ b/src/pathops/SkOpAngle.h
@@ -0,0 +1,91 @@
+/*
+ * 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 "SkOpSpan.h"
+#include "SkPath.h"
+#include "SkPathOpsCubic.h"
+#include "SkTDArray.h"
+
+// sorting angles
+// given angles of {dx dy ddx ddy dddx dddy} sort them
+class SkOpAngle {
+public:
+ bool operator<(const SkOpAngle& rh) const;
+ double dx() const {
+ return fTangent1.dx();
+ }
+
+ double dy() const {
+ return fTangent1.dy();
+ }
+
+ int end() const {
+ return fEnd;
+ }
+
+ bool isHorizontal() const {
+ return dy() == 0 && fVerb == SkPath::kLine_Verb;
+ }
+
+ bool lengthen();
+ bool reverseLengthen();
+ void set(const SkPoint* orig, SkPath::Verb verb, const SkOpSegment* segment,
+ int start, int end, const SkTDArray<SkOpSpan>& spans);
+
+ void setSpans();
+ SkOpSegment* segment() const {
+ return const_cast<SkOpSegment*>(fSegment);
+ }
+
+ int sign() const {
+ return SkSign32(fStart - fEnd);
+ }
+
+ const SkTDArray<SkOpSpan>* spans() const {
+ return fSpans;
+ }
+
+ int start() const {
+ return fStart;
+ }
+
+ bool unsortable() const {
+ return fUnsortable;
+ }
+
+#if DEBUG_ANGLE
+ const SkPoint* pts() const {
+ return fPts;
+ }
+
+ SkPath::Verb verb() const {
+ return fVerb;
+ }
+
+ void debugShow(const SkPoint& a) const {
+ SkDebugf(" d=(%1.9g,%1.9g) side=%1.9g\n", dx(), dy(), fSide);
+ }
+#endif
+
+private:
+ const SkPoint* fPts;
+ SkDCubic fCurvePart;
+ SkPath::Verb fVerb;
+ double fSide;
+ SkLineParameters fTangent1;
+ const SkTDArray<SkOpSpan>* fSpans;
+ const SkOpSegment* fSegment;
+ int fStart;
+ int fEnd;
+ bool fReversed;
+ mutable bool fUnsortable; // this alone is editable by the less than operator
+};
+
+#endif
diff --git a/src/pathops/SkOpContour.cpp b/src/pathops/SkOpContour.cpp
new file mode 100644
index 0000000000..e68341cfd0
--- /dev/null
+++ b/src/pathops/SkOpContour.cpp
@@ -0,0 +1,265 @@
+/*
+* 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 "SkIntersections.h"
+#include "SkOpContour.h"
+#include "SkPathWriter.h"
+#include "TSearch.h"
+
+void SkOpContour::addCoincident(int index, SkOpContour* other, int otherIndex,
+ const SkIntersections& ts, bool swap) {
+ SkCoincidence& coincidence = *fCoincidences.append();
+ coincidence.fContours[0] = this; // FIXME: no need to store
+ coincidence.fContours[1] = other;
+ coincidence.fSegments[0] = index;
+ coincidence.fSegments[1] = otherIndex;
+ coincidence.fTs[swap][0] = ts[0][0];
+ coincidence.fTs[swap][1] = ts[0][1];
+ coincidence.fTs[!swap][0] = ts[1][0];
+ coincidence.fTs[!swap][1] = ts[1][1];
+ coincidence.fPts[0] = ts.pt(0).asSkPoint();
+ coincidence.fPts[1] = ts.pt(1).asSkPoint();
+}
+
+SkOpSegment* SkOpContour::nonVerticalSegment(int* start, int* end) {
+ int segmentCount = fSortedSegments.count();
+ SkASSERT(segmentCount > 0);
+ for (int sortedIndex = fFirstSorted; sortedIndex < segmentCount; ++sortedIndex) {
+ SkOpSegment* testSegment = fSortedSegments[sortedIndex];
+ if (testSegment->done()) {
+ continue;
+ }
+ *start = *end = 0;
+ while (testSegment->nextCandidate(start, end)) {
+ if (!testSegment->isVertical(*start, *end)) {
+ return testSegment;
+ }
+ }
+ }
+ return NULL;
+}
+
+// first pass, add missing T values
+// second pass, determine winding values of overlaps
+void SkOpContour::addCoincidentPoints() {
+ int count = fCoincidences.count();
+ for (int index = 0; index < count; ++index) {
+ SkCoincidence& coincidence = fCoincidences[index];
+ SkASSERT(coincidence.fContours[0] == this);
+ int thisIndex = coincidence.fSegments[0];
+ SkOpSegment& thisOne = fSegments[thisIndex];
+ SkOpContour* otherContour = coincidence.fContours[1];
+ int otherIndex = coincidence.fSegments[1];
+ SkOpSegment& other = otherContour->fSegments[otherIndex];
+ if ((thisOne.done() || other.done()) && thisOne.complete() && other.complete()) {
+ // OPTIMIZATION: remove from array
+ continue;
+ }
+ #if DEBUG_CONCIDENT
+ thisOne.debugShowTs();
+ other.debugShowTs();
+ #endif
+ double startT = coincidence.fTs[0][0];
+ double endT = coincidence.fTs[0][1];
+ bool cancelers;
+ if ((cancelers = startT > endT)) {
+ SkTSwap(startT, endT);
+ SkTSwap(coincidence.fPts[0], coincidence.fPts[1]);
+ }
+ SkASSERT(!approximately_negative(endT - startT));
+ double oStartT = coincidence.fTs[1][0];
+ double oEndT = coincidence.fTs[1][1];
+ if (oStartT > oEndT) {
+ SkTSwap<double>(oStartT, oEndT);
+ cancelers ^= true;
+ }
+ SkASSERT(!approximately_negative(oEndT - oStartT));
+ bool opp = fOperand ^ otherContour->fOperand;
+ if (cancelers && !opp) {
+ // make sure startT and endT have t entries
+ if (startT > 0 || oEndT < 1
+ || thisOne.isMissing(startT) || other.isMissing(oEndT)) {
+ thisOne.addTPair(startT, &other, oEndT, true, coincidence.fPts[0]);
+ }
+ if (oStartT > 0 || endT < 1
+ || thisOne.isMissing(endT) || other.isMissing(oStartT)) {
+ other.addTPair(oStartT, &thisOne, endT, true, coincidence.fPts[1]);
+ }
+ } else {
+ if (startT > 0 || oStartT > 0
+ || thisOne.isMissing(startT) || other.isMissing(oStartT)) {
+ thisOne.addTPair(startT, &other, oStartT, true, coincidence.fPts[0]);
+ }
+ if (endT < 1 || oEndT < 1
+ || thisOne.isMissing(endT) || other.isMissing(oEndT)) {
+ other.addTPair(oEndT, &thisOne, endT, true, coincidence.fPts[1]);
+ }
+ }
+ #if DEBUG_CONCIDENT
+ thisOne.debugShowTs();
+ other.debugShowTs();
+ #endif
+ }
+}
+
+void SkOpContour::calcCoincidentWinding() {
+ int count = fCoincidences.count();
+ for (int index = 0; index < count; ++index) {
+ SkCoincidence& coincidence = fCoincidences[index];
+ SkASSERT(coincidence.fContours[0] == this);
+ int thisIndex = coincidence.fSegments[0];
+ SkOpSegment& thisOne = fSegments[thisIndex];
+ if (thisOne.done()) {
+ continue;
+ }
+ SkOpContour* otherContour = coincidence.fContours[1];
+ int otherIndex = coincidence.fSegments[1];
+ SkOpSegment& other = otherContour->fSegments[otherIndex];
+ if (other.done()) {
+ continue;
+ }
+ double startT = coincidence.fTs[0][0];
+ double endT = coincidence.fTs[0][1];
+ bool cancelers;
+ if ((cancelers = startT > endT)) {
+ SkTSwap<double>(startT, endT);
+ }
+ SkASSERT(!approximately_negative(endT - startT));
+ double oStartT = coincidence.fTs[1][0];
+ double oEndT = coincidence.fTs[1][1];
+ if (oStartT > oEndT) {
+ SkTSwap<double>(oStartT, oEndT);
+ cancelers ^= true;
+ }
+ SkASSERT(!approximately_negative(oEndT - oStartT));
+ bool opp = fOperand ^ otherContour->fOperand;
+ if (cancelers && !opp) {
+ // make sure startT and endT have t entries
+ if (!thisOne.done() && !other.done()) {
+ thisOne.addTCancel(startT, endT, &other, oStartT, oEndT);
+ }
+ } else {
+ if (!thisOne.done() && !other.done()) {
+ thisOne.addTCoincident(startT, endT, &other, oStartT, oEndT);
+ }
+ }
+ #if DEBUG_CONCIDENT
+ thisOne.debugShowTs();
+ other.debugShowTs();
+ #endif
+ }
+}
+
+void SkOpContour::sortSegments() {
+ int segmentCount = fSegments.count();
+ fSortedSegments.setReserve(segmentCount);
+ for (int test = 0; test < segmentCount; ++test) {
+ *fSortedSegments.append() = &fSegments[test];
+ }
+ QSort<SkOpSegment>(fSortedSegments.begin(), fSortedSegments.end() - 1);
+ fFirstSorted = 0;
+}
+
+void SkOpContour::toPath(SkPathWriter* path) const {
+ int segmentCount = fSegments.count();
+ const SkPoint& pt = fSegments.front().pts()[0];
+ path->deferredMove(pt);
+ for (int test = 0; test < segmentCount; ++test) {
+ fSegments[test].addCurveTo(0, 1, path, true);
+ }
+ path->close();
+}
+
+void SkOpContour::topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY,
+ SkOpSegment** topStart) {
+ int segmentCount = fSortedSegments.count();
+ SkASSERT(segmentCount > 0);
+ int sortedIndex = fFirstSorted;
+ fDone = true; // may be cleared below
+ for ( ; sortedIndex < segmentCount; ++sortedIndex) {
+ SkOpSegment* testSegment = fSortedSegments[sortedIndex];
+ if (testSegment->done()) {
+ if (sortedIndex == fFirstSorted) {
+ ++fFirstSorted;
+ }
+ continue;
+ }
+ fDone = false;
+ SkPoint testXY = testSegment->activeLeftTop(true, NULL);
+ if (*topStart) {
+ if (testXY.fY < topLeft.fY) {
+ continue;
+ }
+ if (testXY.fY == topLeft.fY && testXY.fX < topLeft.fX) {
+ continue;
+ }
+ if (bestXY->fY < testXY.fY) {
+ continue;
+ }
+ if (bestXY->fY == testXY.fY && bestXY->fX < testXY.fX) {
+ continue;
+ }
+ }
+ *topStart = testSegment;
+ *bestXY = testXY;
+ }
+}
+
+SkOpSegment* SkOpContour::undoneSegment(int* start, int* end) {
+ int segmentCount = fSegments.count();
+ for (int test = 0; test < segmentCount; ++test) {
+ SkOpSegment* testSegment = &fSegments[test];
+ if (testSegment->done()) {
+ continue;
+ }
+ testSegment->undoneSpan(start, end);
+ return testSegment;
+ }
+ return NULL;
+}
+
+#if DEBUG_SHOW_WINDING
+int SkOpContour::debugShowWindingValues(int totalSegments, int ofInterest) {
+ int count = fSegments.count();
+ int sum = 0;
+ for (int index = 0; index < count; ++index) {
+ sum += fSegments[index].debugShowWindingValues(totalSegments, ofInterest);
+ }
+// SkDebugf("%s sum=%d\n", __FUNCTION__, sum);
+ return sum;
+}
+
+static void SkOpContour::debugShowWindingValues(const SkTDArray<SkOpContour*>& contourList) {
+// int ofInterest = 1 << 1 | 1 << 5 | 1 << 9 | 1 << 13;
+// int ofInterest = 1 << 4 | 1 << 8 | 1 << 12 | 1 << 16;
+ int ofInterest = 1 << 5 | 1 << 8;
+ int total = 0;
+ int index;
+ for (index = 0; index < contourList.count(); ++index) {
+ total += contourList[index]->segments().count();
+ }
+ int sum = 0;
+ for (index = 0; index < contourList.count(); ++index) {
+ sum += contourList[index]->debugShowWindingValues(total, ofInterest);
+ }
+// SkDebugf("%s total=%d\n", __FUNCTION__, sum);
+}
+#endif
+
+void SkOpContour::setBounds() {
+ int count = fSegments.count();
+ if (count == 0) {
+ SkDebugf("%s empty contour\n", __FUNCTION__);
+ SkASSERT(0);
+ // FIXME: delete empty contour?
+ return;
+ }
+ fBounds = fSegments.front().bounds();
+ for (int index = 1; index < count; ++index) {
+ fBounds.add(fSegments[index].bounds());
+ }
+}
+
diff --git a/src/pathops/SkOpContour.h b/src/pathops/SkOpContour.h
new file mode 100644
index 0000000000..2f1c48175a
--- /dev/null
+++ b/src/pathops/SkOpContour.h
@@ -0,0 +1,241 @@
+/*
+ * 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 "SkTArray.h"
+
+class SkIntersections;
+class SkOpContour;
+class SkPathWriter;
+
+struct SkCoincidence {
+ SkOpContour* fContours[2];
+ int fSegments[2];
+ double fTs[2][2];
+ SkPoint fPts[2];
+};
+
+class SkOpContour {
+public:
+ SkOpContour() {
+ reset();
+#if DEBUG_DUMP
+ fID = ++gContourID;
+#endif
+ }
+
+ bool operator<(const SkOpContour& rh) const {
+ return fBounds.fTop == rh.fBounds.fTop
+ ? fBounds.fLeft < rh.fBounds.fLeft
+ : fBounds.fTop < rh.fBounds.fTop;
+ }
+
+ void addCoincident(int index, SkOpContour* other, int otherIndex,
+ const SkIntersections& ts, bool swap);
+ void addCoincidentPoints();
+
+ void addCross(const SkOpContour* crosser) {
+#ifdef DEBUG_CROSS
+ for (int index = 0; index < fCrosses.count(); ++index) {
+ SkASSERT(fCrosses[index] != crosser);
+ }
+#endif
+ *fCrosses.append() = crosser;
+ }
+
+ void addCubic(const SkPoint pts[4]) {
+ fSegments.push_back().addCubic(pts, fOperand, fXor);
+ fContainsCurves = fContainsCubics = true;
+ }
+
+ int addLine(const SkPoint pts[2]) {
+ fSegments.push_back().addLine(pts, fOperand, fXor);
+ return fSegments.count();
+ }
+
+ void addOtherT(int segIndex, int tIndex, double otherT, int otherIndex) {
+ fSegments[segIndex].addOtherT(tIndex, otherT, otherIndex);
+ }
+
+ int addQuad(const SkPoint pts[3]) {
+ fSegments.push_back().addQuad(pts, fOperand, fXor);
+ fContainsCurves = true;
+ return fSegments.count();
+ }
+
+ int addT(int segIndex, SkOpContour* other, int otherIndex, const SkPoint& pt, double newT) {
+ setContainsIntercepts();
+ return fSegments[segIndex].addT(&other->fSegments[otherIndex], pt, newT);
+ }
+
+ int addSelfT(int segIndex, SkOpContour* other, int otherIndex, const SkPoint& pt, double newT) {
+ setContainsIntercepts();
+ return fSegments[segIndex].addSelfT(&other->fSegments[otherIndex], pt, newT);
+ }
+
+ int addUnsortableT(int segIndex, SkOpContour* other, int otherIndex, bool start,
+ const SkPoint& pt, double newT) {
+ return fSegments[segIndex].addUnsortableT(&other->fSegments[otherIndex], start, pt, newT);
+ }
+
+ const SkPathOpsBounds& bounds() const {
+ return fBounds;
+ }
+
+ void calcCoincidentWinding();
+
+ void complete() {
+ setBounds();
+ fContainsIntercepts = false;
+ }
+
+ bool containsCubics() const {
+ return fContainsCubics;
+ }
+
+ bool crosses(const SkOpContour* crosser) const {
+ for (int index = 0; index < fCrosses.count(); ++index) {
+ if (fCrosses[index] == crosser) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ bool done() const {
+ return fDone;
+ }
+
+ const SkPoint& end() const {
+ const SkOpSegment& segment = fSegments.back();
+ return segment.pts()[segment.verb()];
+ }
+
+ void findTooCloseToCall() {
+ int segmentCount = fSegments.count();
+ for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
+ fSegments[sIndex].findTooCloseToCall();
+ }
+ }
+
+ void fixOtherTIndex() {
+ int segmentCount = fSegments.count();
+ for (int sIndex = 0; sIndex < segmentCount; ++sIndex) {
+ fSegments[sIndex].fixOtherTIndex();
+ }
+ }
+
+ SkOpSegment* nonVerticalSegment(int* start, int* end);
+
+ bool operand() const {
+ return fOperand;
+ }
+
+ void reset() {
+ fSegments.reset();
+ fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
+ fContainsCurves = fContainsCubics = fContainsIntercepts = fDone = false;
+ }
+
+ SkTArray<SkOpSegment>& segments() {
+ return fSegments;
+ }
+
+ void setContainsIntercepts() {
+ fContainsIntercepts = true;
+ }
+
+ void setOperand(bool isOp) {
+ fOperand = isOp;
+ }
+
+ void setOppXor(bool isOppXor) {
+ fOppXor = isOppXor;
+ int segmentCount = fSegments.count();
+ for (int test = 0; test < segmentCount; ++test) {
+ fSegments[test].setOppXor(isOppXor);
+ }
+ }
+
+ void setXor(bool isXor) {
+ fXor = isXor;
+ }
+
+ void sortSegments();
+
+ const SkPoint& start() const {
+ return fSegments.front().pts()[0];
+ }
+
+ void toPath(SkPathWriter* path) const;
+
+ void toPartialBackward(SkPathWriter* path) const {
+ int segmentCount = fSegments.count();
+ for (int test = segmentCount - 1; test >= 0; --test) {
+ fSegments[test].addCurveTo(1, 0, path, true);
+ }
+ }
+
+ void toPartialForward(SkPathWriter* path) const {
+ int segmentCount = fSegments.count();
+ for (int test = 0; test < segmentCount; ++test) {
+ fSegments[test].addCurveTo(0, 1, path, true);
+ }
+ }
+
+ void topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY, SkOpSegment** topStart);
+ SkOpSegment* undoneSegment(int* start, int* end);
+
+ int updateSegment(int index, const SkPoint* pts) {
+ SkOpSegment& segment = fSegments[index];
+ segment.updatePts(pts);
+ return segment.verb() + 1;
+ }
+
+#if DEBUG_TEST
+ SkTArray<SkOpSegment>& debugSegments() {
+ return fSegments;
+ }
+#endif
+
+#if DEBUG_ACTIVE_SPANS
+ void debugShowActiveSpans() {
+ for (int index = 0; index < fSegments.count(); ++index) {
+ fSegments[index].debugShowActiveSpans();
+ }
+ }
+#endif
+
+#if DEBUG_SHOW_WINDING
+ int debugShowWindingValues(int totalSegments, int ofInterest);
+ static void debugShowWindingValues(const SkTDArray<SkOpContour*>& contourList);
+#endif
+
+private:
+ void setBounds();
+
+ SkTArray<SkOpSegment> fSegments;
+ SkTDArray<SkOpSegment*> fSortedSegments;
+ int fFirstSorted;
+ SkTDArray<SkCoincidence> fCoincidences;
+ SkTDArray<const SkOpContour*> fCrosses;
+ SkPathOpsBounds fBounds;
+ bool fContainsIntercepts; // FIXME: is this used by anybody?
+ bool fContainsCubics;
+ bool fContainsCurves;
+ bool fDone;
+ bool fOperand; // true for the second argument to a binary operator
+ bool fXor;
+ bool fOppXor;
+#if DEBUG_DUMP
+ int fID;
+#endif
+};
+
+#endif
diff --git a/src/pathops/SkOpEdgeBuilder.cpp b/src/pathops/SkOpEdgeBuilder.cpp
new file mode 100644
index 0000000000..56e7c20043
--- /dev/null
+++ b/src/pathops/SkOpEdgeBuilder.cpp
@@ -0,0 +1,148 @@
+/*
+ * 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 "SkOpEdgeBuilder.h"
+#include "SkReduceOrder.h"
+
+void SkOpEdgeBuilder::init() {
+ fCurrentContour = NULL;
+ fOperand = false;
+ fXorMask[0] = fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask
+ : kWinding_PathOpsMask;
+#if DEBUG_DUMP
+ gContourID = 0;
+ gSegmentID = 0;
+#endif
+ fSecondHalf = preFetch();
+}
+
+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();
+}
+
+void SkOpEdgeBuilder::finish() {
+ walk();
+ complete();
+ if (fCurrentContour && !fCurrentContour->segments().count()) {
+ fContours.pop_back();
+ }
+ // correct pointers in contours since fReducePts may have moved as it grew
+ int cIndex = 0;
+ int extraCount = fExtra.count();
+ SkASSERT(extraCount == 0 || fExtra[0] == -1);
+ int eIndex = 0;
+ int rIndex = 0;
+ while (++eIndex < extraCount) {
+ int offset = fExtra[eIndex];
+ if (offset < 0) {
+ ++cIndex;
+ continue;
+ }
+ fCurrentContour = &fContours[cIndex];
+ rIndex += fCurrentContour->updateSegment(offset - 1,
+ &fReducePts[rIndex]);
+ }
+ fExtra.reset(); // we're done with this
+}
+
+// FIXME:remove once we can access path pts directly
+int SkOpEdgeBuilder::preFetch() {
+ SkPath::RawIter iter(*fPath); // FIXME: access path directly when allowed
+ SkPoint pts[4];
+ SkPath::Verb verb;
+ do {
+ verb = iter.next(pts);
+ *fPathVerbs.append() = verb;
+ if (verb == SkPath::kMove_Verb) {
+ *fPathPts.append() = pts[0];
+ } else if (verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) {
+ fPathPts.append(verb, &pts[1]);
+ }
+ } while (verb != SkPath::kDone_Verb);
+ return fPathVerbs.count() - 1;
+}
+
+void SkOpEdgeBuilder::walk() {
+ SkPath::Verb reducedVerb;
+ uint8_t* verbPtr = fPathVerbs.begin();
+ uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf];
+ const SkPoint* pointsPtr = fPathPts.begin();
+ const SkPoint* finalCurveStart = NULL;
+ const SkPoint* finalCurveEnd = NULL;
+ SkPath::Verb verb;
+ while ((verb = (SkPath::Verb) *verbPtr++) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ complete();
+ if (!fCurrentContour) {
+ fCurrentContour = fContours.push_back_n(1);
+ fCurrentContour->setOperand(fOperand);
+ fCurrentContour->setXor(fXorMask[fOperand] == kEvenOdd_PathOpsMask);
+ *fExtra.append() = -1; // start new contour
+ }
+ finalCurveEnd = pointsPtr++;
+ goto nextVerb;
+ case SkPath::kLine_Verb:
+ // skip degenerate points
+ if (pointsPtr[-1].fX != pointsPtr[0].fX || pointsPtr[-1].fY != pointsPtr[0].fY) {
+ fCurrentContour->addLine(&pointsPtr[-1]);
+ }
+ break;
+ case SkPath::kQuad_Verb:
+ reducedVerb = SkReduceOrder::Quad(&pointsPtr[-1], &fReducePts);
+ if (reducedVerb == 0) {
+ break; // skip degenerate points
+ }
+ if (reducedVerb == 1) {
+ *fExtra.append() =
+ fCurrentContour->addLine(fReducePts.end() - 2);
+ break;
+ }
+ fCurrentContour->addQuad(&pointsPtr[-1]);
+ break;
+ case SkPath::kCubic_Verb:
+ reducedVerb = SkReduceOrder::Cubic(&pointsPtr[-1], &fReducePts);
+ if (reducedVerb == 0) {
+ break; // skip degenerate points
+ }
+ if (reducedVerb == 1) {
+ *fExtra.append() = fCurrentContour->addLine(fReducePts.end() - 2);
+ break;
+ }
+ if (reducedVerb == 2) {
+ *fExtra.append() = fCurrentContour->addQuad(fReducePts.end() - 3);
+ break;
+ }
+ fCurrentContour->addCubic(&pointsPtr[-1]);
+ break;
+ case SkPath::kClose_Verb:
+ SkASSERT(fCurrentContour);
+ if (finalCurveStart && finalCurveEnd
+ && *finalCurveStart != *finalCurveEnd) {
+ *fReducePts.append() = *finalCurveStart;
+ *fReducePts.append() = *finalCurveEnd;
+ *fExtra.append() = fCurrentContour->addLine(fReducePts.end() - 2);
+ }
+ complete();
+ goto nextVerb;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ finalCurveStart = &pointsPtr[verb - 1];
+ pointsPtr += verb;
+ SkASSERT(fCurrentContour);
+ nextVerb:
+ if (verbPtr == endOfFirstHalf) {
+ fOperand = true;
+ }
+ }
+}
diff --git a/src/pathops/SkOpEdgeBuilder.h b/src/pathops/SkOpEdgeBuilder.h
new file mode 100644
index 0000000000..68afc9394b
--- /dev/null
+++ b/src/pathops/SkOpEdgeBuilder.h
@@ -0,0 +1,60 @@
+/*
+ * 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"
+#include "SkTArray.h"
+#include "SkTDArray.h"
+
+class SkOpEdgeBuilder {
+public:
+ SkOpEdgeBuilder(const SkPathWriter& path, SkTArray<SkOpContour>& contours)
+ : fPath(path.nativePath())
+ , fContours(contours) {
+ init();
+ }
+
+ SkOpEdgeBuilder(const SkPath& path, SkTArray<SkOpContour>& contours)
+ : fPath(&path)
+ , fContours(contours) {
+ init();
+ }
+
+ void complete() {
+ if (fCurrentContour && fCurrentContour->segments().count()) {
+ fCurrentContour->complete();
+ fCurrentContour = NULL;
+ }
+ }
+
+ SkPathOpsMask xorMask() const {
+ return fXorMask[fOperand];
+ }
+
+ void addOperand(const SkPath& path);
+ void finish();
+ void init();
+
+private:
+ int preFetch();
+ void walk();
+
+ const SkPath* fPath;
+ SkTDArray<SkPoint> fPathPts; // FIXME: point directly to path pts instead
+ SkTDArray<uint8_t> fPathVerbs; // FIXME: remove
+ SkOpContour* fCurrentContour;
+ SkTArray<SkOpContour>& fContours;
+ SkTDArray<SkPoint> fReducePts; // segments created on the fly
+ SkTDArray<int> fExtra; // -1 marks new contour, > 0 offsets into contour
+ SkPathOpsMask fXorMask[2];
+ int fSecondHalf;
+ bool fOperand;
+};
+
+#endif
diff --git a/src/pathops/SkOpSegment.cpp b/src/pathops/SkOpSegment.cpp
new file mode 100644
index 0000000000..627b0af9d4
--- /dev/null
+++ b/src/pathops/SkOpSegment.cpp
@@ -0,0 +1,2795 @@
+/*
+ * 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 "SkOpSegment.h"
+#include "SkPathWriter.h"
+#include "TSearch.h"
+
+#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},
+};
+
+// FIXME: add support for kReverseDifference_Op
+static const bool gActiveEdge[kXOR_PathOp + 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
+
+// OPTIMIZATION: does the following also work, and is it any faster?
+// return outerWinding * innerWinding > 0
+// || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0)))
+bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) {
+ SkASSERT(outerWinding != SK_MaxS32);
+ SkASSERT(innerWinding != SK_MaxS32);
+ int absOut = abs(outerWinding);
+ int absIn = abs(innerWinding);
+ bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn;
+ return result;
+}
+
+bool SkOpSegment::activeAngle(int index, int* done, SkTDArray<SkOpAngle>* angles) {
+ if (activeAngleInner(index, done, angles)) {
+ return true;
+ }
+ int lesser = index;
+ while (--lesser >= 0 && equalPoints(index, lesser)) {
+ if (activeAngleOther(lesser, done, angles)) {
+ return true;
+ }
+ }
+ lesser = index;
+ do {
+ if (activeAngleOther(index, done, angles)) {
+ return true;
+ }
+ } while (++index < fTs.count() && equalPoints(index, lesser));
+ return false;
+}
+
+bool SkOpSegment::activeAngleOther(int index, int* done, SkTDArray<SkOpAngle>* angles) {
+ SkOpSpan* span = &fTs[index];
+ SkOpSegment* other = span->fOther;
+ int oIndex = span->fOtherIndex;
+ return other->activeAngleInner(oIndex, done, angles);
+}
+
+bool SkOpSegment::activeAngleInner(int index, int* done, SkTDArray<SkOpAngle>* angles) {
+ int next = nextExactSpan(index, 1);
+ if (next > 0) {
+ SkOpSpan& upSpan = fTs[index];
+ if (upSpan.fWindValue || upSpan.fOppValue) {
+ addAngle(angles, index, next);
+ if (upSpan.fDone || upSpan.fUnsortableEnd) {
+ (*done)++;
+ } else if (upSpan.fWindSum != SK_MinS32) {
+ return true;
+ }
+ } else if (!upSpan.fDone) {
+ upSpan.fDone = true;
+ fDoneSpans++;
+ }
+ }
+ int prev = nextExactSpan(index, -1);
+ // edge leading into junction
+ if (prev >= 0) {
+ SkOpSpan& downSpan = fTs[prev];
+ if (downSpan.fWindValue || downSpan.fOppValue) {
+ addAngle(angles, index, prev);
+ if (downSpan.fDone) {
+ (*done)++;
+ } else if (downSpan.fWindSum != SK_MinS32) {
+ return true;
+ }
+ } else if (!downSpan.fDone) {
+ downSpan.fDone = true;
+ fDoneSpans++;
+ }
+ }
+ return false;
+}
+
+SkPoint SkOpSegment::activeLeftTop(bool onlySortable, int* firstT) const {
+ SkASSERT(!done());
+ SkPoint topPt = {SK_ScalarMax, SK_ScalarMax};
+ int count = fTs.count();
+ // see if either end is not done since we want smaller Y of the pair
+ bool lastDone = true;
+ bool lastUnsortable = false;
+ double lastT = -1;
+ for (int index = 0; index < count; ++index) {
+ const SkOpSpan& span = fTs[index];
+ if (onlySortable && (span.fUnsortableStart || lastUnsortable)) {
+ goto next;
+ }
+ if (span.fDone && lastDone) {
+ goto next;
+ }
+ if (approximately_negative(span.fT - lastT)) {
+ goto next;
+ }
+ {
+ const SkPoint& xy = xyAtT(&span);
+ if (topPt.fY > xy.fY || (topPt.fY == xy.fY && topPt.fX > xy.fX)) {
+ topPt = xy;
+ if (firstT) {
+ *firstT = index;
+ }
+ }
+ if (fVerb != SkPath::kLine_Verb && !lastDone) {
+ SkPoint curveTop = (*CurveTop[fVerb])(fPts, lastT, span.fT);
+ if (topPt.fY > curveTop.fY || (topPt.fY == curveTop.fY
+ && topPt.fX > curveTop.fX)) {
+ topPt = curveTop;
+ if (firstT) {
+ *firstT = index;
+ }
+ }
+ }
+ lastT = span.fT;
+ }
+next:
+ lastDone = span.fDone;
+ lastUnsortable = span.fUnsortableEnd;
+ }
+ return topPt;
+}
+
+bool SkOpSegment::activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op) {
+ int sumMiWinding = updateWinding(endIndex, index);
+ int sumSuWinding = updateOppWinding(endIndex, index);
+ if (fOperand) {
+ SkTSwap<int>(sumMiWinding, sumSuWinding);
+ }
+ int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
+ return activeOp(xorMiMask, xorSuMask, index, endIndex, op, &sumMiWinding, &sumSuWinding,
+ &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
+}
+
+bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op,
+ int* sumMiWinding, int* sumSuWinding,
+ int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding) {
+ setUpWindings(index, endIndex, 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 op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", __FUNCTION__,
+ kPathOpStr[op], miFrom, miTo, suFrom, suTo, result);
+#endif
+ return result;
+}
+
+bool SkOpSegment::activeWinding(int index, int endIndex) {
+ int sumWinding = updateWinding(endIndex, index);
+ int maxWinding;
+ return activeWinding(index, endIndex, &maxWinding, &sumWinding);
+}
+
+bool SkOpSegment::activeWinding(int index, int endIndex, int* maxWinding, int* sumWinding) {
+ setUpWinding(index, endIndex, maxWinding, sumWinding);
+ bool from = *maxWinding != 0;
+ bool to = *sumWinding != 0;
+ bool result = gUnaryActiveEdge[from][to];
+ return result;
+}
+
+void SkOpSegment::addAngle(SkTDArray<SkOpAngle>* anglesPtr, int start, int end) const {
+ SkASSERT(start != end);
+ SkOpAngle* angle = anglesPtr->append();
+#if DEBUG_ANGLE
+ SkTDArray<SkOpAngle>& angles = *anglesPtr;
+ if (angles.count() > 1 && !fTs[start].fTiny) {
+ SkPoint angle0Pt = (*CurvePointAtT[angles[0].verb()])(angles[0].pts(),
+ (*angles[0].spans())[angles[0].start()].fT);
+ SkPoint newPt = (*CurvePointAtT[fVerb])(fPts, fTs[start].fT);
+ SkASSERT(AlmostEqualUlps(angle0Pt.fX, newPt.fX));
+ SkASSERT(AlmostEqualUlps(angle0Pt.fY, newPt.fY));
+ }
+#endif
+ angle->set(fPts, fVerb, this, start, end, fTs);
+}
+
+void SkOpSegment::addCancelOutsides(double tStart, double oStart, SkOpSegment* other, double oEnd) {
+ int tIndex = -1;
+ int tCount = fTs.count();
+ int oIndex = -1;
+ int oCount = other->fTs.count();
+ do {
+ ++tIndex;
+ } while (!approximately_negative(tStart - fTs[tIndex].fT) && tIndex < tCount);
+ int tIndexStart = tIndex;
+ do {
+ ++oIndex;
+ } while (!approximately_negative(oStart - other->fTs[oIndex].fT) && oIndex < oCount);
+ int oIndexStart = oIndex;
+ double nextT;
+ do {
+ nextT = fTs[++tIndex].fT;
+ } while (nextT < 1 && approximately_negative(nextT - tStart));
+ double oNextT;
+ do {
+ oNextT = other->fTs[++oIndex].fT;
+ } while (oNextT < 1 && approximately_negative(oNextT - oStart));
+ // at this point, spans before and after are at:
+ // fTs[tIndexStart - 1], fTs[tIndexStart], fTs[tIndex]
+ // if tIndexStart == 0, no prior span
+ // if nextT == 1, no following span
+
+ // advance the span with zero winding
+ // if the following span exists (not past the end, non-zero winding)
+ // connect the two edges
+ if (!fTs[tIndexStart].fWindValue) {
+ if (tIndexStart > 0 && fTs[tIndexStart - 1].fWindValue) {
+#if DEBUG_CONCIDENT
+ SkDebugf("%s 1 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, other->fID, tIndexStart - 1,
+ fTs[tIndexStart].fT, xyAtT(tIndexStart).fX,
+ xyAtT(tIndexStart).fY);
+#endif
+ addTPair(fTs[tIndexStart].fT, other, other->fTs[oIndex].fT, false,
+ fTs[tIndexStart].fPt);
+ }
+ if (nextT < 1 && fTs[tIndex].fWindValue) {
+#if DEBUG_CONCIDENT
+ SkDebugf("%s 2 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, other->fID, tIndex,
+ fTs[tIndex].fT, xyAtT(tIndex).fX,
+ xyAtT(tIndex).fY);
+#endif
+ addTPair(fTs[tIndex].fT, other, other->fTs[oIndexStart].fT, false, fTs[tIndex].fPt);
+ }
+ } else {
+ SkASSERT(!other->fTs[oIndexStart].fWindValue);
+ if (oIndexStart > 0 && other->fTs[oIndexStart - 1].fWindValue) {
+#if DEBUG_CONCIDENT
+ SkDebugf("%s 3 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, other->fID, oIndexStart - 1,
+ other->fTs[oIndexStart].fT, other->xyAtT(oIndexStart).fX,
+ other->xyAtT(oIndexStart).fY);
+ other->debugAddTPair(other->fTs[oIndexStart].fT, *this, fTs[tIndex].fT);
+#endif
+ }
+ if (oNextT < 1 && other->fTs[oIndex].fWindValue) {
+#if DEBUG_CONCIDENT
+ SkDebugf("%s 4 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
+ __FUNCTION__, fID, other->fID, oIndex,
+ other->fTs[oIndex].fT, other->xyAtT(oIndex).fX,
+ other->xyAtT(oIndex).fY);
+ other->debugAddTPair(other->fTs[oIndex].fT, *this, fTs[tIndexStart].fT);
+#endif
+ }
+ }
+}
+
+void SkOpSegment::addCoinOutsides(const SkTDArray<double>& outsideTs, SkOpSegment* other,
+ double oEnd) {
+ // walk this to outsideTs[0]
+ // walk other to outsideTs[1]
+ // if either is > 0, add a pointer to the other, copying adjacent winding
+ int tIndex = -1;
+ int oIndex = -1;
+ double tStart = outsideTs[0];
+ double oStart = outsideTs[1];
+ do {
+ ++tIndex;
+ } while (!approximately_negative(tStart - fTs[tIndex].fT));
+ SkPoint ptStart = fTs[tIndex].fPt;
+ do {
+ ++oIndex;
+ } while (!approximately_negative(oStart - other->fTs[oIndex].fT));
+ if (tIndex > 0 || oIndex > 0 || fOperand != other->fOperand) {
+ addTPair(tStart, other, oStart, false, ptStart);
+ }
+ tStart = fTs[tIndex].fT;
+ oStart = other->fTs[oIndex].fT;
+ do {
+ double nextT;
+ do {
+ nextT = fTs[++tIndex].fT;
+ } while (approximately_negative(nextT - tStart));
+ tStart = nextT;
+ ptStart = fTs[tIndex].fPt;
+ do {
+ nextT = other->fTs[++oIndex].fT;
+ } while (approximately_negative(nextT - oStart));
+ oStart = nextT;
+ if (tStart == 1 && oStart == 1 && fOperand == other->fOperand) {
+ break;
+ }
+ addTPair(tStart, other, oStart, false, ptStart);
+ } while (tStart < 1 && oStart < 1 && !approximately_negative(oEnd - oStart));
+}
+
+void SkOpSegment::addCubic(const SkPoint pts[4], bool operand, bool evenOdd) {
+ init(pts, SkPath::kCubic_Verb, operand, evenOdd);
+ fBounds.setCubicBounds(pts);
+}
+
+void SkOpSegment::addCurveTo(int start, int end, SkPathWriter* path, bool active) const {
+ SkPoint edge[4];
+ const SkPoint* ePtr;
+ int lastT = fTs.count() - 1;
+ if (lastT < 0 || (start == 0 && end == lastT) || (start == lastT && end == 0)) {
+ ePtr = fPts;
+ } else {
+ // OPTIMIZE? if not active, skip remainder and return xyAtT(end)
+ subDivide(start, end, edge);
+ ePtr = edge;
+ }
+ if (active) {
+ bool reverse = ePtr == fPts && start != 0;
+ if (reverse) {
+ path->deferredMoveLine(ePtr[fVerb]);
+ switch (fVerb) {
+ case SkPath::kLine_Verb:
+ path->deferredLine(ePtr[0]);
+ break;
+ case SkPath::kQuad_Verb:
+ path->quadTo(ePtr[1], ePtr[0]);
+ break;
+ case SkPath::kCubic_Verb:
+ path->cubicTo(ePtr[2], ePtr[1], ePtr[0]);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ // return ePtr[0];
+ } else {
+ path->deferredMoveLine(ePtr[0]);
+ switch (fVerb) {
+ case SkPath::kLine_Verb:
+ path->deferredLine(ePtr[1]);
+ break;
+ case SkPath::kQuad_Verb:
+ path->quadTo(ePtr[1], ePtr[2]);
+ break;
+ case SkPath::kCubic_Verb:
+ path->cubicTo(ePtr[1], ePtr[2], ePtr[3]);
+ break;
+ default:
+ SkASSERT(0);
+ }
+ }
+ }
+ // return ePtr[fVerb];
+}
+
+void SkOpSegment::addLine(const SkPoint pts[2], bool operand, bool evenOdd) {
+ init(pts, SkPath::kLine_Verb, operand, evenOdd);
+ fBounds.set(pts, 2);
+}
+
+// add 2 to edge or out of range values to get T extremes
+void SkOpSegment::addOtherT(int index, double otherT, int otherIndex) {
+ SkOpSpan& span = fTs[index];
+#if PIN_ADD_T
+ if (precisely_less_than_zero(otherT)) {
+ otherT = 0;
+ } else if (precisely_greater_than_one(otherT)) {
+ otherT = 1;
+ }
+#endif
+ span.fOtherT = otherT;
+ span.fOtherIndex = otherIndex;
+}
+
+void SkOpSegment::addQuad(const SkPoint pts[3], bool operand, bool evenOdd) {
+ init(pts, SkPath::kQuad_Verb, operand, evenOdd);
+ fBounds.setQuadBounds(pts);
+}
+
+ // Defer all coincident edge processing until
+ // after normal intersections have been computed
+
+// no need to be tricky; insert in normal T order
+// resolve overlapping ts when considering coincidence later
+
+ // add non-coincident intersection. Resulting edges are sorted in T.
+int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) {
+ // FIXME: in the pathological case where there is a ton of intercepts,
+ // binary search?
+ int insertedAt = -1;
+ size_t tCount = fTs.count();
+ for (size_t index = 0; index < tCount; ++index) {
+ // OPTIMIZATION: if there are three or more identical Ts, then
+ // the fourth and following could be further insertion-sorted so
+ // that all the edges are clockwise or counterclockwise.
+ // This could later limit segment tests to the two adjacent
+ // neighbors, although it doesn't help with determining which
+ // circular direction to go in.
+ if (newT < fTs[index].fT) {
+ insertedAt = index;
+ break;
+ }
+ }
+ SkOpSpan* span;
+ if (insertedAt >= 0) {
+ span = fTs.insert(insertedAt);
+ } else {
+ insertedAt = tCount;
+ span = fTs.append();
+ }
+ span->fT = newT;
+ span->fOther = other;
+ span->fPt = pt;
+ span->fWindSum = SK_MinS32;
+ span->fOppSum = SK_MinS32;
+ span->fWindValue = 1;
+ span->fOppValue = 0;
+ span->fTiny = false;
+ span->fLoop = false;
+ if ((span->fDone = newT == 1)) {
+ ++fDoneSpans;
+ }
+ span->fUnsortableStart = false;
+ span->fUnsortableEnd = false;
+ int less = -1;
+ while (&span[less + 1] - fTs.begin() > 0 && xyAtT(&span[less]) == xyAtT(span)) {
+ if (span[less].fDone) {
+ break;
+ }
+ double tInterval = newT - span[less].fT;
+ if (precisely_negative(tInterval)) {
+ break;
+ }
+ if (fVerb == SkPath::kCubic_Verb) {
+ double tMid = newT - tInterval / 2;
+ SkDPoint midPt = dcubic_xy_at_t(fPts, tMid);
+ if (!midPt.approximatelyEqual(xyAtT(span))) {
+ break;
+ }
+ }
+ span[less].fTiny = true;
+ span[less].fDone = true;
+ if (approximately_negative(newT - span[less].fT)) {
+ if (approximately_greater_than_one(newT)) {
+ SkASSERT(&span[less] - fTs.begin() < fTs.count());
+ span[less].fUnsortableStart = true;
+ if (&span[less - 1] - fTs.begin() >= 0) {
+ span[less - 1].fUnsortableEnd = true;
+ }
+ }
+ if (approximately_less_than_zero(span[less].fT)) {
+ SkASSERT(&span[less + 1] - fTs.begin() < fTs.count());
+ SkASSERT(&span[less] - fTs.begin() >= 0);
+ span[less + 1].fUnsortableStart = true;
+ span[less].fUnsortableEnd = true;
+ }
+ }
+ ++fDoneSpans;
+ --less;
+ }
+ int more = 1;
+ while (fTs.end() - &span[more - 1] > 1 && xyAtT(&span[more]) == xyAtT(span)) {
+ if (span[more - 1].fDone) {
+ break;
+ }
+ double tEndInterval = span[more].fT - newT;
+ if (precisely_negative(tEndInterval)) {
+ break;
+ }
+ if (fVerb == SkPath::kCubic_Verb) {
+ double tMid = newT - tEndInterval / 2;
+ SkDPoint midEndPt = dcubic_xy_at_t(fPts, tMid);
+ if (!midEndPt.approximatelyEqual(xyAtT(span))) {
+ break;
+ }
+ }
+ span[more - 1].fTiny = true;
+ span[more - 1].fDone = true;
+ if (approximately_negative(span[more].fT - newT)) {
+ if (approximately_greater_than_one(span[more].fT)) {
+ span[more + 1].fUnsortableStart = true;
+ span[more].fUnsortableEnd = true;
+ }
+ if (approximately_less_than_zero(newT)) {
+ span[more].fUnsortableStart = true;
+ span[more - 1].fUnsortableEnd = true;
+ }
+ }
+ ++fDoneSpans;
+ ++more;
+ }
+ return insertedAt;
+}
+
+// set spans from start to end to decrement by one
+// note this walks other backwards
+// FIMXE: there's probably an edge case that can be constructed where
+// two span in one segment are separated by float epsilon on one span but
+// not the other, if one segment is very small. For this
+// case the counts asserted below may or may not be enough to separate the
+// spans. Even if the counts work out, what if the spans aren't correctly
+// sorted? It feels better in such a case to match the span's other span
+// pointer since both coincident segments must contain the same spans.
+void SkOpSegment::addTCancel(double startT, double endT, SkOpSegment* other,
+ double oStartT, double oEndT) {
+ SkASSERT(!approximately_negative(endT - startT));
+ SkASSERT(!approximately_negative(oEndT - oStartT));
+ bool binary = fOperand != other->fOperand;
+ int index = 0;
+ while (!approximately_negative(startT - fTs[index].fT)) {
+ ++index;
+ }
+ int oIndex = other->fTs.count();
+ while (approximately_positive(other->fTs[--oIndex].fT - oEndT))
+ ;
+ double tRatio = (oEndT - oStartT) / (endT - startT);
+ SkOpSpan* test = &fTs[index];
+ SkOpSpan* oTest = &other->fTs[oIndex];
+ SkTDArray<double> outsideTs;
+ SkTDArray<double> oOutsideTs;
+ do {
+ bool decrement = test->fWindValue && oTest->fWindValue && !binary;
+ bool track = test->fWindValue || oTest->fWindValue;
+ double testT = test->fT;
+ double oTestT = oTest->fT;
+ SkOpSpan* span = test;
+ do {
+ if (decrement) {
+ decrementSpan(span);
+ } else if (track && span->fT < 1 && oTestT < 1) {
+ TrackOutside(&outsideTs, span->fT, oTestT);
+ }
+ span = &fTs[++index];
+ } while (approximately_negative(span->fT - testT));
+ SkOpSpan* oSpan = oTest;
+ double otherTMatchStart = oEndT - (span->fT - startT) * tRatio;
+ double otherTMatchEnd = oEndT - (test->fT - startT) * tRatio;
+ SkDEBUGCODE(int originalWindValue = oSpan->fWindValue);
+ while (approximately_negative(otherTMatchStart - oSpan->fT)
+ && !approximately_negative(otherTMatchEnd - oSpan->fT)) {
+ #ifdef SK_DEBUG
+ SkASSERT(originalWindValue == oSpan->fWindValue);
+ #endif
+ if (decrement) {
+ other->decrementSpan(oSpan);
+ } else if (track && oSpan->fT < 1 && testT < 1) {
+ TrackOutside(&oOutsideTs, oSpan->fT, testT);
+ }
+ if (!oIndex) {
+ break;
+ }
+ oSpan = &other->fTs[--oIndex];
+ }
+ test = span;
+ oTest = oSpan;
+ } while (!approximately_negative(endT - test->fT));
+ SkASSERT(!oIndex || approximately_negative(oTest->fT - oStartT));
+ // FIXME: determine if canceled edges need outside ts added
+ if (!done() && outsideTs.count()) {
+ double tStart = outsideTs[0];
+ double oStart = outsideTs[1];
+ addCancelOutsides(tStart, oStart, other, oEndT);
+ int count = outsideTs.count();
+ if (count > 2) {
+ double tStart = outsideTs[count - 2];
+ double oStart = outsideTs[count - 1];
+ addCancelOutsides(tStart, oStart, other, oEndT);
+ }
+ }
+ if (!other->done() && oOutsideTs.count()) {
+ double tStart = oOutsideTs[0];
+ double oStart = oOutsideTs[1];
+ other->addCancelOutsides(tStart, oStart, this, endT);
+ }
+}
+
+int SkOpSegment::addSelfT(SkOpSegment* other, const SkPoint& pt, double newT) {
+ int result = addT(other, pt, newT);
+ SkOpSpan* span = &fTs[result];
+ span->fLoop = true;
+ return result;
+}
+
+int SkOpSegment::addUnsortableT(SkOpSegment* other, bool start, const SkPoint& pt, double newT) {
+ int result = addT(other, pt, newT);
+ SkOpSpan* span = &fTs[result];
+ if (start) {
+ if (result > 0) {
+ span[result - 1].fUnsortableEnd = true;
+ }
+ span[result].fUnsortableStart = true;
+ } else {
+ span[result].fUnsortableEnd = true;
+ if (result + 1 < fTs.count()) {
+ span[result + 1].fUnsortableStart = true;
+ }
+ }
+ return result;
+}
+
+int SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool opp, int index,
+ SkTDArray<double>* outsideTs) {
+ int oWindValue = oTest.fWindValue;
+ int oOppValue = oTest.fOppValue;
+ if (opp) {
+ SkTSwap<int>(oWindValue, oOppValue);
+ }
+ SkOpSpan* const test = &fTs[index];
+ SkOpSpan* end = test;
+ const double oStartT = oTest.fT;
+ do {
+ if (bumpSpan(end, oWindValue, oOppValue)) {
+ TrackOutside(outsideTs, end->fT, oStartT);
+ }
+ end = &fTs[++index];
+ } while (approximately_negative(end->fT - test->fT));
+ return index;
+}
+
+// because of the order in which coincidences are resolved, this and other
+// may not have the same intermediate points. Compute the corresponding
+// intermediate T values (using this as the master, other as the follower)
+// and walk other conditionally -- hoping that it catches up in the end
+int SkOpSegment::bumpCoincidentOther(const SkOpSpan& test, double oEndT, int& oIndex,
+ SkTDArray<double>* oOutsideTs) {
+ SkOpSpan* const oTest = &fTs[oIndex];
+ SkOpSpan* oEnd = oTest;
+ const double startT = test.fT;
+ const double oStartT = oTest->fT;
+ while (!approximately_negative(oEndT - oEnd->fT)
+ && approximately_negative(oEnd->fT - oStartT)) {
+ zeroSpan(oEnd);
+ TrackOutside(oOutsideTs, oEnd->fT, startT);
+ oEnd = &fTs[++oIndex];
+ }
+ return oIndex;
+}
+
+// FIXME: need to test this case:
+// contourA has two segments that are coincident
+// contourB has two segments that are coincident in the same place
+// each ends up with +2/0 pairs for winding count
+// since logic below doesn't transfer count (only increments/decrements) can this be
+// resolved to +4/0 ?
+
+// set spans from start to end to increment the greater by one and decrement
+// the lesser
+void SkOpSegment::addTCoincident(double startT, double endT, SkOpSegment* other, double oStartT,
+ double oEndT) {
+ SkASSERT(!approximately_negative(endT - startT));
+ SkASSERT(!approximately_negative(oEndT - oStartT));
+ bool opp = fOperand ^ other->fOperand;
+ int index = 0;
+ while (!approximately_negative(startT - fTs[index].fT)) {
+ ++index;
+ }
+ int oIndex = 0;
+ while (!approximately_negative(oStartT - other->fTs[oIndex].fT)) {
+ ++oIndex;
+ }
+ SkOpSpan* test = &fTs[index];
+ SkOpSpan* oTest = &other->fTs[oIndex];
+ SkTDArray<double> outsideTs;
+ SkTDArray<double> oOutsideTs;
+ do {
+ // if either span has an opposite value and the operands don't match, resolve first
+ // SkASSERT(!test->fDone || !oTest->fDone);
+ if (test->fDone || oTest->fDone) {
+ index = advanceCoincidentThis(oTest, opp, index);
+ oIndex = other->advanceCoincidentOther(test, oEndT, oIndex);
+ } else {
+ index = bumpCoincidentThis(*oTest, opp, index, &outsideTs);
+ oIndex = other->bumpCoincidentOther(*test, oEndT, oIndex, &oOutsideTs);
+ }
+ test = &fTs[index];
+ oTest = &other->fTs[oIndex];
+ } while (!approximately_negative(endT - test->fT));
+ SkASSERT(approximately_negative(oTest->fT - oEndT));
+ SkASSERT(approximately_negative(oEndT - oTest->fT));
+ if (!done() && outsideTs.count()) {
+ addCoinOutsides(outsideTs, other, oEndT);
+ }
+ if (!other->done() && oOutsideTs.count()) {
+ other->addCoinOutsides(oOutsideTs, this, endT);
+ }
+}
+
+// FIXME: this doesn't prevent the same span from being added twice
+// fix in caller, SkASSERT here?
+void SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind,
+ const SkPoint& pt) {
+ int tCount = fTs.count();
+ for (int tIndex = 0; tIndex < tCount; ++tIndex) {
+ const SkOpSpan& span = fTs[tIndex];
+ if (!approximately_negative(span.fT - t)) {
+ break;
+ }
+ if (approximately_negative(span.fT - t) && span.fOther == other
+ && approximately_equal(span.fOtherT, otherT)) {
+#if DEBUG_ADD_T_PAIR
+ SkDebugf("%s addTPair duplicate this=%d %1.9g other=%d %1.9g\n",
+ __FUNCTION__, fID, t, other->fID, otherT);
+#endif
+ return;
+ }
+ }
+#if DEBUG_ADD_T_PAIR
+ SkDebugf("%s addTPair this=%d %1.9g other=%d %1.9g\n",
+ __FUNCTION__, fID, t, other->fID, otherT);
+#endif
+ int insertedAt = addT(other, pt, t);
+ int otherInsertedAt = other->addT(this, pt, otherT);
+ addOtherT(insertedAt, otherT, otherInsertedAt);
+ other->addOtherT(otherInsertedAt, t, insertedAt);
+ matchWindingValue(insertedAt, t, borrowWind);
+ other->matchWindingValue(otherInsertedAt, otherT, borrowWind);
+}
+
+void SkOpSegment::addTwoAngles(int start, int end, SkTDArray<SkOpAngle>* angles) const {
+ // add edge leading into junction
+ int min = SkMin32(end, start);
+ if (fTs[min].fWindValue > 0 || fTs[min].fOppValue > 0) {
+ addAngle(angles, end, start);
+ }
+ // add edge leading away from junction
+ int step = SkSign32(end - start);
+ int tIndex = nextExactSpan(end, step);
+ min = SkMin32(end, tIndex);
+ if (tIndex >= 0 && (fTs[min].fWindValue > 0 || fTs[min].fOppValue > 0)) {
+ addAngle(angles, end, tIndex);
+ }
+}
+
+int SkOpSegment::advanceCoincidentThis(const SkOpSpan* oTest, bool opp, int index) {
+ SkOpSpan* const test = &fTs[index];
+ SkOpSpan* end;
+ do {
+ end = &fTs[++index];
+ } while (approximately_negative(end->fT - test->fT));
+ return index;
+}
+
+int SkOpSegment::advanceCoincidentOther(const SkOpSpan* test, double oEndT, int oIndex) {
+ SkOpSpan* const oTest = &fTs[oIndex];
+ SkOpSpan* oEnd = oTest;
+ const double oStartT = oTest->fT;
+ while (!approximately_negative(oEndT - oEnd->fT)
+ && approximately_negative(oEnd->fT - oStartT)) {
+ oEnd = &fTs[++oIndex];
+ }
+ return oIndex;
+}
+
+bool SkOpSegment::betweenTs(int lesser, double testT, int greater) const {
+ if (lesser > greater) {
+ SkTSwap<int>(lesser, greater);
+ }
+ return approximately_between(fTs[lesser].fT, testT, fTs[greater].fT);
+}
+
+void SkOpSegment::buildAngles(int index, SkTDArray<SkOpAngle>* angles, bool includeOpp) const {
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && (includeOpp || fTs[lesser].fOther->fOperand == fOperand)
+ && precisely_negative(referenceT - fTs[lesser].fT)) {
+ buildAnglesInner(lesser, angles);
+ }
+ do {
+ buildAnglesInner(index, angles);
+ } while (++index < fTs.count() && (includeOpp || fTs[index].fOther->fOperand == fOperand)
+ && precisely_negative(fTs[index].fT - referenceT));
+}
+
+void SkOpSegment::buildAnglesInner(int index, SkTDArray<SkOpAngle>* angles) const {
+ const SkOpSpan* span = &fTs[index];
+ SkOpSegment* other = span->fOther;
+// if there is only one live crossing, and no coincidence, continue
+// in the same direction
+// if there is coincidence, the only choice may be to reverse direction
+ // find edge on either side of intersection
+ int oIndex = span->fOtherIndex;
+ // if done == -1, prior span has already been processed
+ int step = 1;
+ int next = other->nextExactSpan(oIndex, step);
+ if (next < 0) {
+ step = -step;
+ next = other->nextExactSpan(oIndex, step);
+ }
+ // add candidate into and away from junction
+ other->addTwoAngles(next, oIndex, angles);
+}
+
+int SkOpSegment::computeSum(int startIndex, int endIndex, bool binary) {
+ SkTDArray<SkOpAngle> angles;
+ addTwoAngles(startIndex, endIndex, &angles);
+ buildAngles(endIndex, &angles, false);
+ // OPTIMIZATION: check all angles to see if any have computed wind sum
+ // before sorting (early exit if none)
+ SkTDArray<SkOpAngle*> sorted;
+ bool sortable = SortAngles(angles, &sorted);
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0);
+#endif
+ if (!sortable) {
+ return SK_MinS32;
+ }
+ int angleCount = angles.count();
+ const SkOpAngle* angle;
+ const SkOpSegment* base;
+ int winding;
+ int oWinding;
+ int firstIndex = 0;
+ do {
+ angle = sorted[firstIndex];
+ base = angle->segment();
+ winding = base->windSum(angle);
+ if (winding != SK_MinS32) {
+ oWinding = base->oppSum(angle);
+ break;
+ }
+ if (++firstIndex == angleCount) {
+ return SK_MinS32;
+ }
+ } while (true);
+ // turn winding into contourWinding
+ int spanWinding = base->spanSign(angle);
+ bool inner = UseInnerWinding(winding + spanWinding, winding);
+#if DEBUG_WINDING
+ SkDebugf("%s spanWinding=%d winding=%d sign=%d inner=%d result=%d\n", __FUNCTION__,
+ spanWinding, winding, angle->sign(), inner,
+ inner ? winding + spanWinding : winding);
+#endif
+ if (inner) {
+ winding += spanWinding;
+ }
+#if DEBUG_SORT
+ base->debugShowSort(__FUNCTION__, sorted, firstIndex, winding, oWinding);
+#endif
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ winding -= base->spanSign(angle);
+ oWinding -= base->oppSign(angle);
+ do {
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ angle = sorted[nextIndex];
+ SkOpSegment* segment = angle->segment();
+ bool opp = base->fOperand ^ segment->fOperand;
+ int maxWinding, oMaxWinding;
+ int spanSign = segment->spanSign(angle);
+ int oppoSign = segment->oppSign(angle);
+ if (opp) {
+ oMaxWinding = oWinding;
+ oWinding -= spanSign;
+ maxWinding = winding;
+ if (oppoSign) {
+ winding -= oppoSign;
+ }
+ } else {
+ maxWinding = winding;
+ winding -= spanSign;
+ oMaxWinding = oWinding;
+ if (oppoSign) {
+ oWinding -= oppoSign;
+ }
+ }
+ if (segment->windSum(angle) == SK_MinS32) {
+ if (opp) {
+ if (UseInnerWinding(oMaxWinding, oWinding)) {
+ oMaxWinding = oWinding;
+ }
+ if (oppoSign && UseInnerWinding(maxWinding, winding)) {
+ maxWinding = winding;
+ }
+ (void) segment->markAndChaseWinding(angle, oMaxWinding, maxWinding);
+ } else {
+ if (UseInnerWinding(maxWinding, winding)) {
+ maxWinding = winding;
+ }
+ if (oppoSign && UseInnerWinding(oMaxWinding, oWinding)) {
+ oMaxWinding = oWinding;
+ }
+ (void) segment->markAndChaseWinding(angle, maxWinding,
+ binary ? oMaxWinding : 0);
+ }
+ }
+ } while (++nextIndex != lastIndex);
+ int minIndex = SkMin32(startIndex, endIndex);
+ return windSum(minIndex);
+}
+
+int SkOpSegment::crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT,
+ bool* hitSomething, double mid, bool opp, bool current) const {
+ SkScalar bottom = fBounds.fBottom;
+ int bestTIndex = -1;
+ if (bottom <= *bestY) {
+ return bestTIndex;
+ }
+ SkScalar top = fBounds.fTop;
+ if (top >= basePt.fY) {
+ return bestTIndex;
+ }
+ if (fBounds.fLeft > basePt.fX) {
+ return bestTIndex;
+ }
+ if (fBounds.fRight < basePt.fX) {
+ return bestTIndex;
+ }
+ if (fBounds.fLeft == fBounds.fRight) {
+ // if vertical, and directly above test point, wait for another one
+ return AlmostEqualUlps(basePt.fX, fBounds.fLeft) ? SK_MinS32 : bestTIndex;
+ }
+ // intersect ray starting at basePt with edge
+ SkIntersections intersections;
+ // OPTIMIZE: use specialty function that intersects ray with curve,
+ // returning t values only for curve (we don't care about t on ray)
+ int pts = (intersections.*CurveVertical[fVerb])(fPts, top, bottom, basePt.fX, false);
+ if (pts == 0 || (current && pts == 1)) {
+ return bestTIndex;
+ }
+ if (current) {
+ SkASSERT(pts > 1);
+ int closestIdx = 0;
+ double closest = fabs(intersections[0][0] - mid);
+ for (int idx = 1; idx < pts; ++idx) {
+ double test = fabs(intersections[0][idx] - mid);
+ if (closest > test) {
+ closestIdx = idx;
+ closest = test;
+ }
+ }
+ intersections.quickRemoveOne(closestIdx, --pts);
+ }
+ double bestT = -1;
+ for (int index = 0; index < pts; ++index) {
+ double foundT = intersections[0][index];
+ if (approximately_less_than_zero(foundT)
+ || approximately_greater_than_one(foundT)) {
+ continue;
+ }
+ SkScalar testY = (*CurvePointAtT[fVerb])(fPts, foundT).fY;
+ if (approximately_negative(testY - *bestY)
+ || approximately_negative(basePt.fY - testY)) {
+ continue;
+ }
+ if (pts > 1 && fVerb == SkPath::kLine_Verb) {
+ return SK_MinS32; // if the intersection is edge on, wait for another one
+ }
+ if (fVerb > SkPath::kLine_Verb) {
+ SkScalar dx = (*CurveSlopeAtT[fVerb])(fPts, foundT).fX;
+ if (approximately_zero(dx)) {
+ return SK_MinS32; // hit vertical, wait for another one
+ }
+ }
+ *bestY = testY;
+ bestT = foundT;
+ }
+ if (bestT < 0) {
+ return bestTIndex;
+ }
+ SkASSERT(bestT >= 0);
+ SkASSERT(bestT <= 1);
+ int start;
+ int end = 0;
+ do {
+ start = end;
+ end = nextSpan(start, 1);
+ } while (fTs[end].fT < bestT);
+ // FIXME: see next candidate for a better pattern to find the next start/end pair
+ while (start + 1 < end && fTs[start].fDone) {
+ ++start;
+ }
+ if (!isCanceled(start)) {
+ *hitT = bestT;
+ bestTIndex = start;
+ *hitSomething = true;
+ }
+ return bestTIndex;
+}
+
+void SkOpSegment::decrementSpan(SkOpSpan* span) {
+ SkASSERT(span->fWindValue > 0);
+ if (--(span->fWindValue) == 0) {
+ if (!span->fOppValue && !span->fDone) {
+ span->fDone = true;
+ ++fDoneSpans;
+ }
+ }
+}
+
+bool SkOpSegment::bumpSpan(SkOpSpan* span, int windDelta, int oppDelta) {
+ SkASSERT(!span->fDone);
+ span->fWindValue += windDelta;
+ SkASSERT(span->fWindValue >= 0);
+ span->fOppValue += oppDelta;
+ SkASSERT(span->fOppValue >= 0);
+ if (fXor) {
+ span->fWindValue &= 1;
+ }
+ if (fOppXor) {
+ span->fOppValue &= 1;
+ }
+ if (!span->fWindValue && !span->fOppValue) {
+ span->fDone = true;
+ ++fDoneSpans;
+ return true;
+ }
+ return false;
+}
+
+bool SkOpSegment::equalPoints(int greaterTIndex, int lesserTIndex) {
+ SkASSERT(greaterTIndex >= lesserTIndex);
+ double greaterT = fTs[greaterTIndex].fT;
+ double lesserT = fTs[lesserTIndex].fT;
+ if (greaterT == lesserT) {
+ return true;
+ }
+ if (!approximately_negative(greaterT - lesserT)) {
+ return false;
+ }
+ return xyAtT(greaterTIndex) == xyAtT(lesserTIndex);
+}
+
+/*
+ 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<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
+ bool* unsortable, SkPathOp op, const int xorMiMask,
+ const int xorSuMask) {
+ const int startIndex = *nextStart;
+ const int endIndex = *nextEnd;
+ SkASSERT(startIndex != endIndex);
+ SkDEBUGCODE(const int count = fTs.count());
+ SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0);
+ const int step = SkSign32(endIndex - startIndex);
+ const int end = nextExactSpan(startIndex, step);
+ SkASSERT(end >= 0);
+ SkOpSpan* endSpan = &fTs[end];
+ SkOpSegment* other;
+ if (isSimple(end)) {
+ // 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
+ int min = SkMin32(startIndex, endIndex);
+ if (fTs[min].fDone) {
+ return NULL;
+ }
+ markDoneBinary(min);
+ other = endSpan->fOther;
+ *nextStart = endSpan->fOtherIndex;
+ double startT = other->fTs[*nextStart].fT;
+ *nextEnd = *nextStart;
+ do {
+ *nextEnd += step;
+ }
+ while (precisely_zero(startT - other->fTs[*nextEnd].fT));
+ SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count());
+ return other;
+ }
+ // more than one viable candidate -- measure angles to find best
+ SkTDArray<SkOpAngle> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, &angles);
+ buildAngles(end, &angles, true);
+ SkTDArray<SkOpAngle*> sorted;
+ bool sortable = SortAngles(angles, &sorted);
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+#if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex);
+#endif
+ if (!sortable) {
+ *unsortable = true;
+ return NULL;
+ }
+ SkASSERT(sorted[firstIndex]->segment() == this);
+#if DEBUG_WINDING
+ SkDebugf("%s firstIndex=[%d] sign=%d\n", __FUNCTION__, firstIndex,
+ sorted[firstIndex]->sign());
+#endif
+ int sumMiWinding = updateWinding(endIndex, startIndex);
+ int sumSuWinding = updateOppWinding(endIndex, startIndex);
+ if (operand()) {
+ SkTSwap<int>(sumMiWinding, sumSuWinding);
+ }
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const SkOpAngle* foundAngle = NULL;
+ bool foundDone = false;
+ // iterate through the angle, and compute everyone's winding
+ SkOpSegment* nextSegment;
+ int activeCount = 0;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const SkOpAngle* nextAngle = sorted[nextIndex];
+ nextSegment = nextAngle->segment();
+ int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
+ bool activeAngle = nextSegment->activeOp(xorMiMask, xorSuMask, nextAngle->start(),
+ nextAngle->end(), op, &sumMiWinding, &sumSuWinding,
+ &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
+ if (activeAngle) {
+ ++activeCount;
+ if (!foundAngle || (foundDone && activeCount & 1)) {
+ if (nextSegment->tiny(nextAngle)) {
+ *unsortable = true;
+ return NULL;
+ }
+ foundAngle = nextAngle;
+ foundDone = nextSegment->done(nextAngle) && !nextSegment->tiny(nextAngle);
+ }
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ if (nextSegment->windSum(nextAngle) != SK_MinS32) {
+ continue;
+ }
+ SkOpSpan* last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding,
+ oppSumWinding, activeAngle, nextAngle);
+ if (last) {
+ *chase->append() = last;
+#if DEBUG_WINDING
+ SkDebugf("%s chase.append id=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID());
+#endif
+ }
+ } while (++nextIndex != lastIndex);
+ markDoneBinary(SkMin32(startIndex, endIndex));
+ if (!foundAngle) {
+ return NULL;
+ }
+ *nextStart = foundAngle->start();
+ *nextEnd = foundAngle->end();
+ nextSegment = foundAngle->segment();
+
+#if DEBUG_WINDING
+ SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
+ __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd);
+ #endif
+ return nextSegment;
+}
+
+SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart,
+ int* nextEnd, bool* unsortable) {
+ const int startIndex = *nextStart;
+ const int endIndex = *nextEnd;
+ SkASSERT(startIndex != endIndex);
+ SkDEBUGCODE(const int count = fTs.count());
+ SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0);
+ const int step = SkSign32(endIndex - startIndex);
+ const int end = nextExactSpan(startIndex, step);
+ SkASSERT(end >= 0);
+ SkOpSpan* endSpan = &fTs[end];
+ SkOpSegment* other;
+ if (isSimple(end)) {
+ // 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
+ int min = SkMin32(startIndex, endIndex);
+ if (fTs[min].fDone) {
+ return NULL;
+ }
+ markDoneUnary(min);
+ other = endSpan->fOther;
+ *nextStart = endSpan->fOtherIndex;
+ double startT = other->fTs[*nextStart].fT;
+ *nextEnd = *nextStart;
+ do {
+ *nextEnd += step;
+ }
+ while (precisely_zero(startT - other->fTs[*nextEnd].fT));
+ SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count());
+ return other;
+ }
+ // more than one viable candidate -- measure angles to find best
+ SkTDArray<SkOpAngle> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, &angles);
+ buildAngles(end, &angles, true);
+ SkTDArray<SkOpAngle*> sorted;
+ bool sortable = SortAngles(angles, &sorted);
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+#if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex);
+#endif
+ if (!sortable) {
+ *unsortable = true;
+ return NULL;
+ }
+ SkASSERT(sorted[firstIndex]->segment() == this);
+#if DEBUG_WINDING
+ SkDebugf("%s firstIndex=[%d] sign=%d\n", __FUNCTION__, firstIndex,
+ sorted[firstIndex]->sign());
+#endif
+ int sumWinding = updateWinding(endIndex, startIndex);
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const SkOpAngle* foundAngle = NULL;
+ bool foundDone = false;
+ // iterate through the angle, and compute everyone's winding
+ SkOpSegment* nextSegment;
+ int activeCount = 0;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const SkOpAngle* nextAngle = sorted[nextIndex];
+ nextSegment = nextAngle->segment();
+ int maxWinding;
+ bool activeAngle = nextSegment->activeWinding(nextAngle->start(), nextAngle->end(),
+ &maxWinding, &sumWinding);
+ if (activeAngle) {
+ ++activeCount;
+ if (!foundAngle || (foundDone && activeCount & 1)) {
+ if (nextSegment->tiny(nextAngle)) {
+ *unsortable = true;
+ return NULL;
+ }
+ foundAngle = nextAngle;
+ foundDone = nextSegment->done(nextAngle);
+ }
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ if (nextSegment->windSum(nextAngle) != SK_MinS32) {
+ continue;
+ }
+ SkOpSpan* last = nextSegment->markAngle(maxWinding, sumWinding, activeAngle, nextAngle);
+ if (last) {
+ *chase->append() = last;
+#if DEBUG_WINDING
+ SkDebugf("%s chase.append id=%d\n", __FUNCTION__,
+ last->fOther->fTs[last->fOtherIndex].fOther->debugID());
+#endif
+ }
+ } while (++nextIndex != lastIndex);
+ markDoneUnary(SkMin32(startIndex, endIndex));
+ if (!foundAngle) {
+ return NULL;
+ }
+ *nextStart = foundAngle->start();
+ *nextEnd = foundAngle->end();
+ nextSegment = foundAngle->segment();
+#if DEBUG_WINDING
+ SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
+ __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd);
+ #endif
+ return nextSegment;
+}
+
+SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsortable) {
+ const int startIndex = *nextStart;
+ const int endIndex = *nextEnd;
+ SkASSERT(startIndex != endIndex);
+ SkDEBUGCODE(int count = fTs.count());
+ SkASSERT(startIndex < endIndex ? startIndex < count - 1
+ : startIndex > 0);
+ int step = SkSign32(endIndex - startIndex);
+ int end = nextExactSpan(startIndex, step);
+ SkASSERT(end >= 0);
+ SkOpSpan* endSpan = &fTs[end];
+ SkOpSegment* other;
+ if (isSimple(end)) {
+#if DEBUG_WINDING
+ SkDebugf("%s simple\n", __FUNCTION__);
+#endif
+ int min = SkMin32(startIndex, endIndex);
+ if (fTs[min].fDone) {
+ return NULL;
+ }
+ markDone(min, 1);
+ other = endSpan->fOther;
+ *nextStart = endSpan->fOtherIndex;
+ double startT = other->fTs[*nextStart].fT;
+ // FIXME: I don't know why the logic here is difference from the winding case
+ SkDEBUGCODE(bool firstLoop = true;)
+ if ((approximately_less_than_zero(startT) && step < 0)
+ || (approximately_greater_than_one(startT) && step > 0)) {
+ step = -step;
+ SkDEBUGCODE(firstLoop = false;)
+ }
+ do {
+ *nextEnd = *nextStart;
+ do {
+ *nextEnd += step;
+ }
+ while (precisely_zero(startT - other->fTs[*nextEnd].fT));
+ if (other->fTs[SkMin32(*nextStart, *nextEnd)].fWindValue) {
+ break;
+ }
+#ifdef SK_DEBUG
+ SkASSERT(firstLoop);
+#endif
+ SkDEBUGCODE(firstLoop = false;)
+ step = -step;
+ } while (true);
+ SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count());
+ return other;
+ }
+ SkTDArray<SkOpAngle> angles;
+ SkASSERT(startIndex - endIndex != 0);
+ SkASSERT((startIndex - endIndex < 0) ^ (step < 0));
+ addTwoAngles(startIndex, end, &angles);
+ buildAngles(end, &angles, false);
+ SkTDArray<SkOpAngle*> sorted;
+ bool sortable = SortAngles(angles, &sorted);
+ if (!sortable) {
+ *unsortable = true;
+#if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, findStartingEdge(sorted, startIndex, end), 0, 0);
+#endif
+ return NULL;
+ }
+ int angleCount = angles.count();
+ int firstIndex = findStartingEdge(sorted, startIndex, end);
+ SkASSERT(firstIndex >= 0);
+#if DEBUG_SORT
+ debugShowSort(__FUNCTION__, sorted, firstIndex, 0, 0);
+#endif
+ SkASSERT(sorted[firstIndex]->segment() == this);
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ const SkOpAngle* foundAngle = NULL;
+ bool foundDone = false;
+ SkOpSegment* nextSegment;
+ int activeCount = 0;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ const SkOpAngle* nextAngle = sorted[nextIndex];
+ nextSegment = nextAngle->segment();
+ ++activeCount;
+ if (!foundAngle || (foundDone && activeCount & 1)) {
+ if (nextSegment->tiny(nextAngle)) {
+ *unsortable = true;
+ return NULL;
+ }
+ foundAngle = nextAngle;
+ foundDone = nextSegment->done(nextAngle);
+ }
+ if (nextSegment->done()) {
+ continue;
+ }
+ } while (++nextIndex != lastIndex);
+ markDone(SkMin32(startIndex, endIndex), 1);
+ if (!foundAngle) {
+ return NULL;
+ }
+ *nextStart = foundAngle->start();
+ *nextEnd = foundAngle->end();
+ nextSegment = foundAngle->segment();
+#if DEBUG_WINDING
+ SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n",
+ __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd);
+ #endif
+ return nextSegment;
+}
+
+int SkOpSegment::findStartingEdge(const SkTDArray<SkOpAngle*>& sorted, int start, int end) {
+ int angleCount = sorted.count();
+ int firstIndex = -1;
+ for (int angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ const SkOpAngle* angle = sorted[angleIndex];
+ if (angle->segment() == this && angle->start() == end &&
+ angle->end() == start) {
+ firstIndex = angleIndex;
+ break;
+ }
+ }
+ return firstIndex;
+}
+
+// FIXME: this is tricky code; needs its own unit test
+// note that fOtherIndex isn't computed yet, so it can't be used here
+void SkOpSegment::findTooCloseToCall() {
+ int count = fTs.count();
+ if (count < 3) { // require t=0, x, 1 at minimum
+ return;
+ }
+ int matchIndex = 0;
+ int moCount;
+ SkOpSpan* match;
+ SkOpSegment* mOther;
+ do {
+ match = &fTs[matchIndex];
+ mOther = match->fOther;
+ // FIXME: allow quads, cubics to be near coincident?
+ if (mOther->fVerb == SkPath::kLine_Verb) {
+ moCount = mOther->fTs.count();
+ if (moCount >= 3) {
+ break;
+ }
+ }
+ if (++matchIndex >= count) {
+ return;
+ }
+ } while (true); // require t=0, x, 1 at minimum
+ // OPTIMIZATION: defer matchPt until qualifying toCount is found?
+ const SkPoint* matchPt = &xyAtT(match);
+ // look for a pair of nearby T values that map to the same (x,y) value
+ // if found, see if the pair of other segments share a common point. If
+ // so, the span from here to there is coincident.
+ for (int index = matchIndex + 1; index < count; ++index) {
+ SkOpSpan* test = &fTs[index];
+ if (test->fDone) {
+ continue;
+ }
+ SkOpSegment* tOther = test->fOther;
+ if (tOther->fVerb != SkPath::kLine_Verb) {
+ continue; // FIXME: allow quads, cubics to be near coincident?
+ }
+ int toCount = tOther->fTs.count();
+ if (toCount < 3) { // require t=0, x, 1 at minimum
+ continue;
+ }
+ const SkPoint* testPt = &xyAtT(test);
+ if (*matchPt != *testPt) {
+ matchIndex = index;
+ moCount = toCount;
+ match = test;
+ mOther = tOther;
+ matchPt = testPt;
+ continue;
+ }
+ int moStart = -1;
+ int moEnd = -1;
+ double moStartT = 0;
+ double moEndT = 0;
+ for (int moIndex = 0; moIndex < moCount; ++moIndex) {
+ SkOpSpan& moSpan = mOther->fTs[moIndex];
+ if (moSpan.fDone) {
+ continue;
+ }
+ if (moSpan.fOther == this) {
+ if (moSpan.fOtherT == match->fT) {
+ moStart = moIndex;
+ moStartT = moSpan.fT;
+ }
+ continue;
+ }
+ if (moSpan.fOther == tOther) {
+ if (tOther->windValueAt(moSpan.fOtherT) == 0) {
+ moStart = -1;
+ break;
+ }
+ SkASSERT(moEnd == -1);
+ moEnd = moIndex;
+ moEndT = moSpan.fT;
+ }
+ }
+ if (moStart < 0 || moEnd < 0) {
+ continue;
+ }
+ // FIXME: if moStartT, moEndT are initialized to NaN, can skip this test
+ if (approximately_equal(moStartT, moEndT)) {
+ continue;
+ }
+ int toStart = -1;
+ int toEnd = -1;
+ double toStartT = 0;
+ double toEndT = 0;
+ for (int toIndex = 0; toIndex < toCount; ++toIndex) {
+ SkOpSpan& toSpan = tOther->fTs[toIndex];
+ if (toSpan.fDone) {
+ continue;
+ }
+ if (toSpan.fOther == this) {
+ if (toSpan.fOtherT == test->fT) {
+ toStart = toIndex;
+ toStartT = toSpan.fT;
+ }
+ continue;
+ }
+ if (toSpan.fOther == mOther && toSpan.fOtherT == moEndT) {
+ if (mOther->windValueAt(toSpan.fOtherT) == 0) {
+ moStart = -1;
+ break;
+ }
+ SkASSERT(toEnd == -1);
+ toEnd = toIndex;
+ toEndT = toSpan.fT;
+ }
+ }
+ // FIXME: if toStartT, toEndT are initialized to NaN, can skip this test
+ if (toStart <= 0 || toEnd <= 0) {
+ continue;
+ }
+ if (approximately_equal(toStartT, toEndT)) {
+ continue;
+ }
+ // test to see if the segment between there and here is linear
+ if (!mOther->isLinear(moStart, moEnd)
+ || !tOther->isLinear(toStart, toEnd)) {
+ continue;
+ }
+ bool flipped = (moStart - moEnd) * (toStart - toEnd) < 1;
+ if (flipped) {
+ mOther->addTCancel(moStartT, moEndT, tOther, toEndT, toStartT);
+ } else {
+ mOther->addTCoincident(moStartT, moEndT, tOther, toStartT, toEndT);
+ }
+ }
+}
+
+// FIXME: either:
+// a) mark spans with either end unsortable as done, or
+// b) rewrite findTop / findTopSegment / findTopContour to iterate further
+// when encountering an unsortable span
+
+// OPTIMIZATION : for a pair of lines, can we compute points at T (cached)
+// and use more concise logic like the old edge walker code?
+// FIXME: this needs to deal with coincident edges
+SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsortable,
+ bool onlySortable) {
+ // iterate through T intersections and return topmost
+ // topmost tangent from y-min to first pt is closer to horizontal
+ SkASSERT(!done());
+ int firstT = -1;
+ /* SkPoint topPt = */ activeLeftTop(onlySortable, &firstT);
+ if (firstT < 0) {
+ *unsortable = true;
+ firstT = 0;
+ while (fTs[firstT].fDone) {
+ SkASSERT(firstT < fTs.count());
+ ++firstT;
+ }
+ *tIndexPtr = firstT;
+ *endIndexPtr = nextExactSpan(firstT, 1);
+ return this;
+ }
+ // sort the edges to find the leftmost
+ int step = 1;
+ int end = nextSpan(firstT, step);
+ if (end == -1) {
+ step = -1;
+ end = nextSpan(firstT, step);
+ SkASSERT(end != -1);
+ }
+ // if the topmost T is not on end, or is three-way or more, find left
+ // look for left-ness from tLeft to firstT (matching y of other)
+ SkTDArray<SkOpAngle> angles;
+ SkASSERT(firstT - end != 0);
+ addTwoAngles(end, firstT, &angles);
+ buildAngles(firstT, &angles, true);
+ SkTDArray<SkOpAngle*> sorted;
+ bool sortable = SortAngles(angles, &sorted);
+ int first = SK_MaxS32;
+ SkScalar top = SK_ScalarMax;
+ int count = sorted.count();
+ for (int index = 0; index < count; ++index) {
+ const SkOpAngle* angle = sorted[index];
+ SkOpSegment* next = angle->segment();
+ SkPathOpsBounds bounds;
+ next->subDivideBounds(angle->end(), angle->start(), &bounds);
+ if (approximately_greater(top, bounds.fTop)) {
+ top = bounds.fTop;
+ first = index;
+ }
+ }
+ SkASSERT(first < SK_MaxS32);
+#if DEBUG_SORT // || DEBUG_SWAP_TOP
+ sorted[first]->segment()->debugShowSort(__FUNCTION__, sorted, first, 0, 0);
+#endif
+ if (onlySortable && !sortable) {
+ *unsortable = true;
+ return NULL;
+ }
+ // skip edges that have already been processed
+ firstT = first - 1;
+ SkOpSegment* leftSegment;
+ do {
+ if (++firstT == count) {
+ firstT = 0;
+ }
+ const SkOpAngle* angle = sorted[firstT];
+ SkASSERT(!onlySortable || !angle->unsortable());
+ leftSegment = angle->segment();
+ *tIndexPtr = angle->end();
+ *endIndexPtr = angle->start();
+ } while (leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fDone);
+ if (leftSegment->verb() >= SkPath::kQuad_Verb) {
+ const int tIndex = *tIndexPtr;
+ const int endIndex = *endIndexPtr;
+ if (!leftSegment->clockwise(tIndex, endIndex)) {
+ bool swap = !leftSegment->monotonicInY(tIndex, endIndex)
+ && !leftSegment->serpentine(tIndex, endIndex);
+ #if DEBUG_SWAP_TOP
+ SkDebugf("%s swap=%d serpentine=%d containedByEnds=%d monotonic=%d\n", __FUNCTION__,
+ swap,
+ leftSegment->serpentine(tIndex, endIndex),
+ leftSegment->controlsContainedByEnds(tIndex, endIndex),
+ leftSegment->monotonicInY(tIndex, endIndex));
+ #endif
+ if (swap) {
+ // FIXME: I doubt it makes sense to (necessarily) swap if the edge was not the first
+ // sorted but merely the first not already processed (i.e., not done)
+ SkTSwap(*tIndexPtr, *endIndexPtr);
+ }
+ }
+ }
+ SkASSERT(!leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fTiny);
+ return leftSegment;
+}
+
+// FIXME: not crazy about this
+// when the intersections are performed, the other index is into an
+// incomplete array. As the array grows, the indices become incorrect
+// while the following fixes the indices up again, it isn't smart about
+// skipping segments whose indices are already correct
+// assuming we leave the code that wrote the index in the first place
+void SkOpSegment::fixOtherTIndex() {
+ int iCount = fTs.count();
+ for (int i = 0; i < iCount; ++i) {
+ SkOpSpan& iSpan = fTs[i];
+ double oT = iSpan.fOtherT;
+ SkOpSegment* other = iSpan.fOther;
+ int oCount = other->fTs.count();
+ for (int o = 0; o < oCount; ++o) {
+ SkOpSpan& oSpan = other->fTs[o];
+ if (oT == oSpan.fT && this == oSpan.fOther && oSpan.fOtherT == iSpan.fT) {
+ iSpan.fOtherIndex = o;
+ break;
+ }
+ }
+ }
+}
+
+void SkOpSegment::init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd) {
+ fDoneSpans = 0;
+ fOperand = operand;
+ fXor = evenOdd;
+ fPts = pts;
+ fVerb = verb;
+}
+
+void SkOpSegment::initWinding(int start, int end) {
+ int local = spanSign(start, end);
+ int oppLocal = oppSign(start, end);
+ (void) markAndChaseWinding(start, end, local, oppLocal);
+ // OPTIMIZATION: the reverse mark and chase could skip the first marking
+ (void) markAndChaseWinding(end, start, local, oppLocal);
+}
+
+/*
+when we start with a vertical intersect, we try to use the dx to determine if the edge is to
+the left or the right of vertical. This determines if we need to add the span's
+sign or not. However, this isn't enough.
+If the supplied sign (winding) is zero, then we didn't hit another vertical span, so dx is needed.
+If there was a winding, then it may or may not need adjusting. If the span the winding was borrowed
+from has the same x direction as this span, the winding should change. If the dx is opposite, then
+the same winding is shared by both.
+*/
+void SkOpSegment::initWinding(int start, int end, double tHit, int winding, SkScalar hitDx,
+ int oppWind, SkScalar hitOppDx) {
+ SkASSERT(hitDx || !winding);
+ SkScalar dx = (*CurveSlopeAtT[fVerb])(fPts, tHit).fX;
+ SkASSERT(dx);
+ int windVal = windValue(SkMin32(start, end));
+#if DEBUG_WINDING_AT_T
+ SkDebugf("%s oldWinding=%d hitDx=%c dx=%c windVal=%d", __FUNCTION__, winding,
+ hitDx ? hitDx > 0 ? '+' : '-' : '0', dx > 0 ? '+' : '-', windVal);
+#endif
+ if (!winding) {
+ winding = dx < 0 ? windVal : -windVal;
+ } else if (winding * dx < 0) {
+ int sideWind = winding + (dx < 0 ? windVal : -windVal);
+ if (abs(winding) < abs(sideWind)) {
+ winding = sideWind;
+ }
+ }
+#if DEBUG_WINDING_AT_T
+ SkDebugf(" winding=%d\n", winding);
+#endif
+ SkDEBUGCODE(int oppLocal = oppSign(start, end));
+ SkASSERT(hitOppDx || !oppWind || !oppLocal);
+ int oppWindVal = oppValue(SkMin32(start, end));
+ if (!oppWind) {
+ oppWind = dx < 0 ? oppWindVal : -oppWindVal;
+ } else if (hitOppDx * dx >= 0) {
+ int oppSideWind = oppWind + (dx < 0 ? oppWindVal : -oppWindVal);
+ if (abs(oppWind) < abs(oppSideWind)) {
+ oppWind = oppSideWind;
+ }
+ }
+ (void) markAndChaseWinding(start, end, winding, oppWind);
+}
+
+bool SkOpSegment::isLinear(int start, int end) const {
+ if (fVerb == SkPath::kLine_Verb) {
+ return true;
+ }
+ if (fVerb == SkPath::kQuad_Verb) {
+ SkDQuad qPart = SkDQuad::SubDivide(fPts, fTs[start].fT, fTs[end].fT);
+ return qPart.isLinear(0, 2);
+ } else {
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ SkDCubic cPart = SkDCubic::SubDivide(fPts, fTs[start].fT, fTs[end].fT);
+ return cPart.isLinear(0, 3);
+ }
+}
+
+// OPTIMIZE: successive calls could start were the last leaves off
+// or calls could specialize to walk forwards or backwards
+bool SkOpSegment::isMissing(double startT) const {
+ size_t tCount = fTs.count();
+ for (size_t index = 0; index < tCount; ++index) {
+ if (approximately_zero(startT - fTs[index].fT)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+bool SkOpSegment::isSimple(int end) const {
+ int count = fTs.count();
+ if (count == 2) {
+ return true;
+ }
+ double t = fTs[end].fT;
+ if (approximately_less_than_zero(t)) {
+ return !approximately_less_than_zero(fTs[1].fT);
+ }
+ if (approximately_greater_than_one(t)) {
+ return !approximately_greater_than_one(fTs[count - 2].fT);
+ }
+ return false;
+}
+
+// this span is excluded by the winding rule -- chase the ends
+// as long as they are unambiguous to mark connections as done
+// and give them the same winding value
+SkOpSpan* SkOpSegment::markAndChaseDone(int index, int endIndex, int winding) {
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDone(min, winding);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ other->markDone(min, winding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseDoneBinary(const SkOpAngle* angle, int winding, int oppWinding) {
+ int index = angle->start();
+ int endIndex = angle->end();
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDoneBinary(min, winding, oppWinding);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ other->markDoneBinary(min, winding, oppWinding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseDoneBinary(int index, int endIndex) {
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDoneBinary(min);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ if (other->done()) {
+ return NULL;
+ }
+ other->markDoneBinary(min);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseDoneUnary(int index, int endIndex) {
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markDoneUnary(min);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ if (other->done()) {
+ return NULL;
+ }
+ other->markDoneUnary(min);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseDoneUnary(const SkOpAngle* angle, int winding) {
+ int index = angle->start();
+ int endIndex = angle->end();
+ return markAndChaseDone(index, endIndex, winding);
+}
+
+SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, const int winding) {
+ int index = angle->start();
+ int endIndex = angle->end();
+ int step = SkSign32(endIndex - index);
+ int min = SkMin32(index, endIndex);
+ markWinding(min, winding);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ if (other->fTs[min].fWindSum != SK_MinS32) {
+ SkASSERT(other->fTs[min].fWindSum == winding);
+ return NULL;
+ }
+ other->markWinding(min, winding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding, int oppWinding) {
+ int min = SkMin32(index, endIndex);
+ int step = SkSign32(endIndex - index);
+ markWinding(min, winding, oppWinding);
+ SkOpSpan* last;
+ SkOpSegment* other = this;
+ while ((other = other->nextChase(&index, step, &min, &last))) {
+ if (other->fTs[min].fWindSum != SK_MinS32) {
+ SkASSERT(other->fTs[min].fWindSum == winding || other->fTs[min].fLoop);
+ return NULL;
+ }
+ other->markWinding(min, winding, oppWinding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding) {
+ int start = angle->start();
+ int end = angle->end();
+ return markAndChaseWinding(start, end, winding, oppWinding);
+}
+
+SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, bool activeAngle,
+ const SkOpAngle* angle) {
+ SkASSERT(angle->segment() == this);
+ if (UseInnerWinding(maxWinding, sumWinding)) {
+ maxWinding = sumWinding;
+ }
+ SkOpSpan* last;
+ if (activeAngle) {
+ last = markAndChaseWinding(angle, maxWinding);
+ } else {
+ last = markAndChaseDoneUnary(angle, maxWinding);
+ }
+ return last;
+}
+
+SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding,
+ int oppSumWinding, bool activeAngle, const SkOpAngle* angle) {
+ SkASSERT(angle->segment() == this);
+ if (UseInnerWinding(maxWinding, sumWinding)) {
+ maxWinding = sumWinding;
+ }
+ if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) {
+ oppMaxWinding = oppSumWinding;
+ }
+ SkOpSpan* last;
+ if (activeAngle) {
+ last = markAndChaseWinding(angle, maxWinding, oppMaxWinding);
+ } else {
+ last = markAndChaseDoneBinary(angle, maxWinding, oppMaxWinding);
+ }
+ return last;
+}
+
+// FIXME: this should also mark spans with equal (x,y)
+// This may be called when the segment is already marked done. While this
+// wastes time, it shouldn't do any more than spin through the T spans.
+// OPTIMIZATION: abort on first done found (assuming that this code is
+// always called to mark segments done).
+void SkOpSegment::markDone(int index, int winding) {
+ // SkASSERT(!done());
+ SkASSERT(winding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDone(__FUNCTION__, lesser, winding);
+ }
+ do {
+ markOneDone(__FUNCTION__, index, winding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+}
+
+void SkOpSegment::markDoneBinary(int index, int winding, int oppWinding) {
+ // SkASSERT(!done());
+ SkASSERT(winding || oppWinding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDoneBinary(__FUNCTION__, lesser, winding, oppWinding);
+ }
+ do {
+ markOneDoneBinary(__FUNCTION__, index, winding, oppWinding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+}
+
+void SkOpSegment::markDoneBinary(int index) {
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDoneBinary(__FUNCTION__, lesser);
+ }
+ do {
+ markOneDoneBinary(__FUNCTION__, index);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+}
+
+void SkOpSegment::markDoneUnary(int index) {
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneDoneUnary(__FUNCTION__, lesser);
+ }
+ do {
+ markOneDoneUnary(__FUNCTION__, index);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+}
+
+void SkOpSegment::markOneDone(const char* funName, int tIndex, int winding) {
+ SkOpSpan* span = markOneWinding(funName, tIndex, winding);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+void SkOpSegment::markOneDoneBinary(const char* funName, int tIndex) {
+ SkOpSpan* span = verifyOneWinding(funName, tIndex);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+void SkOpSegment::markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding) {
+ SkOpSpan* span = markOneWinding(funName, tIndex, winding, oppWinding);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+void SkOpSegment::markOneDoneUnary(const char* funName, int tIndex) {
+ SkOpSpan* span = verifyOneWindingU(funName, tIndex);
+ if (!span) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding) {
+ SkOpSpan& span = fTs[tIndex];
+ if (span.fDone) {
+ return NULL;
+ }
+#if DEBUG_MARK_DONE
+ debugShowNewWinding(funName, span, winding);
+#endif
+ SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding);
+#ifdef SK_DEBUG
+ SkASSERT(abs(winding) <= gDebugMaxWindSum);
+#endif
+ span.fWindSum = winding;
+ return &span;
+}
+
+SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding,
+ int oppWinding) {
+ SkOpSpan& span = fTs[tIndex];
+ if (span.fDone) {
+ return NULL;
+ }
+#if DEBUG_MARK_DONE
+ debugShowNewWinding(funName, span, winding, oppWinding);
+#endif
+ SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding);
+#ifdef SK_DEBUG
+ SkASSERT(abs(winding) <= gDebugMaxWindSum);
+#endif
+ span.fWindSum = winding;
+ SkASSERT(span.fOppSum == SK_MinS32 || span.fOppSum == oppWinding);
+#ifdef SK_DEBUG
+ SkASSERT(abs(oppWinding) <= gDebugMaxWindSum);
+#endif
+ span.fOppSum = oppWinding;
+ return &span;
+}
+
+// from http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order
+bool SkOpSegment::clockwise(int tStart, int tEnd) const {
+ SkASSERT(fVerb != SkPath::kLine_Verb);
+ SkPoint edge[4];
+ subDivide(tStart, tEnd, edge);
+ double sum = (edge[0].fX - edge[fVerb].fX) * (edge[0].fY + edge[fVerb].fY);
+ if (fVerb == SkPath::kCubic_Verb) {
+ SkScalar lesser = SkTMin<SkScalar>(edge[0].fY, edge[3].fY);
+ if (edge[1].fY < lesser && edge[2].fY < lesser) {
+ SkDLine tangent1 = {{ {edge[0].fX, edge[0].fY}, {edge[1].fX, edge[1].fY} }};
+ SkDLine tangent2 = {{ {edge[2].fX, edge[2].fY}, {edge[3].fX, edge[3].fY} }};
+ if (SkIntersections::Test(tangent1, tangent2)) {
+ SkPoint topPt = cubic_top(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ sum += (topPt.fX - edge[0].fX) * (topPt.fY + edge[0].fY);
+ sum += (edge[3].fX - topPt.fX) * (edge[3].fY + topPt.fY);
+ return sum <= 0;
+ }
+ }
+ }
+ for (int idx = 0; idx < fVerb; ++idx){
+ sum += (edge[idx + 1].fX - edge[idx].fX) * (edge[idx + 1].fY + edge[idx].fY);
+ }
+ return sum <= 0;
+}
+
+bool SkOpSegment::monotonicInY(int tStart, int tEnd) const {
+ if (fVerb == SkPath::kLine_Verb) {
+ return false;
+ }
+ if (fVerb == SkPath::kQuad_Verb) {
+ SkDQuad dst = SkDQuad::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ return dst.monotonicInY();
+ }
+ SkASSERT(fVerb == SkPath::kCubic_Verb);
+ SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ return dst.monotonicInY();
+}
+
+bool SkOpSegment::serpentine(int tStart, int tEnd) const {
+ if (fVerb != SkPath::kCubic_Verb) {
+ return false;
+ }
+ SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ return dst.serpentine();
+}
+
+SkOpSpan* SkOpSegment::verifyOneWinding(const char* funName, int tIndex) {
+ SkOpSpan& span = fTs[tIndex];
+ if (span.fDone) {
+ return NULL;
+ }
+#if DEBUG_MARK_DONE
+ debugShowNewWinding(funName, span, span.fWindSum, span.fOppSum);
+#endif
+ SkASSERT(span.fWindSum != SK_MinS32);
+ SkASSERT(span.fOppSum != SK_MinS32);
+ return &span;
+}
+
+SkOpSpan* SkOpSegment::verifyOneWindingU(const char* funName, int tIndex) {
+ SkOpSpan& span = fTs[tIndex];
+ if (span.fDone) {
+ return NULL;
+ }
+#if DEBUG_MARK_DONE
+ debugShowNewWinding(funName, span, span.fWindSum);
+#endif
+ SkASSERT(span.fWindSum != SK_MinS32);
+ return &span;
+}
+
+// note that just because a span has one end that is unsortable, that's
+// not enough to mark it done. The other end may be sortable, allowing the
+// span to be added.
+// FIXME: if abs(start - end) > 1, mark intermediates as unsortable on both ends
+void SkOpSegment::markUnsortable(int start, int end) {
+ SkOpSpan* span = &fTs[start];
+ if (start < end) {
+#if DEBUG_UNSORTABLE
+ debugShowNewWinding(__FUNCTION__, *span, 0);
+#endif
+ span->fUnsortableStart = true;
+ } else {
+ --span;
+#if DEBUG_UNSORTABLE
+ debugShowNewWinding(__FUNCTION__, *span, 0);
+#endif
+ span->fUnsortableEnd = true;
+ }
+ if (!span->fUnsortableStart || !span->fUnsortableEnd || span->fDone) {
+ return;
+ }
+ span->fDone = true;
+ fDoneSpans++;
+}
+
+void SkOpSegment::markWinding(int index, int winding) {
+// SkASSERT(!done());
+ SkASSERT(winding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneWinding(__FUNCTION__, lesser, winding);
+ }
+ do {
+ markOneWinding(__FUNCTION__, index, winding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+}
+
+void SkOpSegment::markWinding(int index, int winding, int oppWinding) {
+// SkASSERT(!done());
+ SkASSERT(winding || oppWinding);
+ double referenceT = fTs[index].fT;
+ int lesser = index;
+ while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) {
+ markOneWinding(__FUNCTION__, lesser, winding, oppWinding);
+ }
+ do {
+ markOneWinding(__FUNCTION__, index, winding, oppWinding);
+ } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT));
+}
+
+void SkOpSegment::matchWindingValue(int tIndex, double t, bool borrowWind) {
+ int nextDoorWind = SK_MaxS32;
+ int nextOppWind = SK_MaxS32;
+ if (tIndex > 0) {
+ const SkOpSpan& below = fTs[tIndex - 1];
+ if (approximately_negative(t - below.fT)) {
+ nextDoorWind = below.fWindValue;
+ nextOppWind = below.fOppValue;
+ }
+ }
+ if (nextDoorWind == SK_MaxS32 && tIndex + 1 < fTs.count()) {
+ const SkOpSpan& above = fTs[tIndex + 1];
+ if (approximately_negative(above.fT - t)) {
+ nextDoorWind = above.fWindValue;
+ nextOppWind = above.fOppValue;
+ }
+ }
+ if (nextDoorWind == SK_MaxS32 && borrowWind && tIndex > 0 && t < 1) {
+ const SkOpSpan& below = fTs[tIndex - 1];
+ nextDoorWind = below.fWindValue;
+ nextOppWind = below.fOppValue;
+ }
+ if (nextDoorWind != SK_MaxS32) {
+ SkOpSpan& newSpan = fTs[tIndex];
+ newSpan.fWindValue = nextDoorWind;
+ newSpan.fOppValue = nextOppWind;
+ if (!nextDoorWind && !nextOppWind && !newSpan.fDone) {
+ newSpan.fDone = true;
+ ++fDoneSpans;
+ }
+ }
+}
+
+// return span if when chasing, two or more radiating spans are not done
+// OPTIMIZATION: ? multiple spans is detected when there is only one valid
+// candidate and the remaining spans have windValue == 0 (canceled by
+// coincidence). The coincident edges could either be removed altogether,
+// or this code could be more complicated in detecting this case. Worth it?
+bool SkOpSegment::multipleSpans(int end) const {
+ return end > 0 && end < fTs.count() - 1;
+}
+
+bool SkOpSegment::nextCandidate(int* start, int* end) const {
+ while (fTs[*end].fDone) {
+ if (fTs[*end].fT == 1) {
+ return false;
+ }
+ ++(*end);
+ }
+ *start = *end;
+ *end = nextExactSpan(*start, 1);
+ return true;
+}
+
+SkOpSegment* SkOpSegment::nextChase(int* index, const int step, int* min, SkOpSpan** last) {
+ int end = nextExactSpan(*index, step);
+ SkASSERT(end >= 0);
+ if (multipleSpans(end)) {
+ *last = &fTs[end];
+ return NULL;
+ }
+ const SkOpSpan& endSpan = fTs[end];
+ SkOpSegment* other = endSpan.fOther;
+ *index = endSpan.fOtherIndex;
+ SkASSERT(index >= 0);
+ int otherEnd = other->nextExactSpan(*index, step);
+ SkASSERT(otherEnd >= 0);
+ *min = SkMin32(*index, otherEnd);
+ return other;
+}
+
+// This has callers for two different situations: one establishes the end
+// of the current span, and one establishes the beginning of the next span
+// (thus the name). When this is looking for the end of the current span,
+// coincidence is found when the beginning Ts contain -step and the end
+// contains step. When it is looking for the beginning of the next, the
+// first Ts found can be ignored and the last Ts should contain -step.
+// OPTIMIZATION: probably should split into two functions
+int SkOpSegment::nextSpan(int from, int step) const {
+ const SkOpSpan& fromSpan = fTs[from];
+ int count = fTs.count();
+ int to = from;
+ while (step > 0 ? ++to < count : --to >= 0) {
+ const SkOpSpan& span = fTs[to];
+ if (approximately_zero(span.fT - fromSpan.fT)) {
+ continue;
+ }
+ return to;
+ }
+ return -1;
+}
+
+// FIXME
+// this returns at any difference in T, vs. a preset minimum. It may be
+// that all callers to nextSpan should use this instead.
+// OPTIMIZATION splitting this into separate loops for up/down steps
+// would allow using precisely_negative instead of precisely_zero
+int SkOpSegment::nextExactSpan(int from, int step) const {
+ const SkOpSpan& fromSpan = fTs[from];
+ int count = fTs.count();
+ int to = from;
+ while (step > 0 ? ++to < count : --to >= 0) {
+ const SkOpSpan& span = fTs[to];
+ if (precisely_zero(span.fT - fromSpan.fT)) {
+ continue;
+ }
+ return to;
+ }
+ return -1;
+}
+
+void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding,
+ int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding) {
+ int deltaSum = spanSign(index, endIndex);
+ int oppDeltaSum = oppSign(index, endIndex);
+ if (operand()) {
+ *maxWinding = *sumSuWinding;
+ *sumWinding = *sumSuWinding -= deltaSum;
+ *oppMaxWinding = *sumMiWinding;
+ *oppSumWinding = *sumMiWinding -= oppDeltaSum;
+ } else {
+ *maxWinding = *sumMiWinding;
+ *sumWinding = *sumMiWinding -= deltaSum;
+ *oppMaxWinding = *sumSuWinding;
+ *oppSumWinding = *sumSuWinding -= oppDeltaSum;
+ }
+}
+
+// This marks all spans unsortable so that this info is available for early
+// exclusion in find top and others. This could be optimized to only mark
+// adjacent spans that unsortable. However, this makes it difficult to later
+// determine starting points for edge detection in find top and the like.
+bool SkOpSegment::SortAngles(const SkTDArray<SkOpAngle>& angles,
+ SkTDArray<SkOpAngle*>* angleList) {
+ bool sortable = true;
+ int angleCount = angles.count();
+ int angleIndex;
+ angleList->setReserve(angleCount);
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ const SkOpAngle& angle = angles[angleIndex];
+ *angleList->append() = const_cast<SkOpAngle*>(&angle);
+ sortable &= !angle.unsortable();
+ }
+ if (sortable) {
+ QSort<SkOpAngle>(angleList->begin(), angleList->end() - 1);
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ if (angles[angleIndex].unsortable()) {
+ sortable = false;
+ break;
+ }
+ }
+ }
+ if (!sortable) {
+ for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) {
+ const SkOpAngle& angle = angles[angleIndex];
+ angle.segment()->markUnsortable(angle.start(), angle.end());
+ }
+ }
+ return sortable;
+}
+
+void SkOpSegment::subDivide(int start, int end, SkPoint edge[4]) const {
+ edge[0] = fTs[start].fPt;
+ edge[fVerb] = fTs[end].fPt;
+ if (fVerb == SkPath::kQuad_Verb || fVerb == SkPath::kCubic_Verb) {
+ SkDPoint sub[2] = {{ edge[0].fX, edge[0].fY}, {edge[fVerb].fX, edge[fVerb].fY }};
+ if (fVerb == SkPath::kQuad_Verb) {
+ edge[1] = SkDQuad::SubDivide(fPts, sub[0], sub[1], fTs[start].fT,
+ fTs[end].fT).asSkPoint();
+ } else {
+ SkDCubic::SubDivide(fPts, sub[0], sub[1], fTs[start].fT, fTs[end].fT, sub);
+ edge[1] = sub[0].asSkPoint();
+ edge[2] = sub[1].asSkPoint();
+ }
+ }
+}
+
+void SkOpSegment::subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const {
+ SkPoint edge[4];
+ subDivide(start, end, edge);
+ (bounds->*SetCurveBounds[fVerb])(edge);
+}
+
+bool SkOpSegment::tiny(const SkOpAngle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ const SkOpSpan& mSpan = fTs[SkMin32(start, end)];
+ return mSpan.fTiny;
+}
+
+void SkOpSegment::TrackOutside(SkTDArray<double>* outsideTs, double end, double start) {
+ int outCount = outsideTs->count();
+ if (outCount == 0 || !approximately_negative(end - (*outsideTs)[outCount - 2])) {
+ *outsideTs->append() = end;
+ *outsideTs->append() = start;
+ }
+}
+
+void SkOpSegment::undoneSpan(int* start, int* end) {
+ size_t tCount = fTs.count();
+ size_t index;
+ for (index = 0; index < tCount; ++index) {
+ if (!fTs[index].fDone) {
+ break;
+ }
+ }
+ SkASSERT(index < tCount - 1);
+ *start = index;
+ double startT = fTs[index].fT;
+ while (approximately_negative(fTs[++index].fT - startT))
+ SkASSERT(index < tCount);
+ SkASSERT(index < tCount);
+ *end = index;
+}
+
+int SkOpSegment::updateOppWinding(int index, int endIndex) const {
+ int lesser = SkMin32(index, endIndex);
+ int oppWinding = oppSum(lesser);
+ int oppSpanWinding = oppSign(index, endIndex);
+ if (oppSpanWinding && UseInnerWinding(oppWinding - oppSpanWinding, oppWinding)
+ && oppWinding != SK_MaxS32) {
+ oppWinding -= oppSpanWinding;
+ }
+ return oppWinding;
+}
+
+int SkOpSegment::updateOppWinding(const SkOpAngle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateOppWinding(endIndex, startIndex);
+}
+
+int SkOpSegment::updateOppWindingReverse(const SkOpAngle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateOppWinding(startIndex, endIndex);
+}
+
+int SkOpSegment::updateWinding(int index, int endIndex) const {
+ int lesser = SkMin32(index, endIndex);
+ int winding = windSum(lesser);
+ int spanWinding = spanSign(index, endIndex);
+ if (winding && UseInnerWinding(winding - spanWinding, winding) && winding != SK_MaxS32) {
+ winding -= spanWinding;
+ }
+ return winding;
+}
+
+int SkOpSegment::updateWinding(const SkOpAngle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateWinding(endIndex, startIndex);
+}
+
+int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) const {
+ int startIndex = angle->start();
+ int endIndex = angle->end();
+ return updateWinding(startIndex, endIndex);
+}
+
+int SkOpSegment::windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const {
+ if (approximately_zero(tHit - t(tIndex))) { // if we hit the end of a span, disregard
+ return SK_MinS32;
+ }
+ int winding = crossOpp ? oppSum(tIndex) : windSum(tIndex);
+ SkASSERT(winding != SK_MinS32);
+ int windVal = crossOpp ? oppValue(tIndex) : windValue(tIndex);
+#if DEBUG_WINDING_AT_T
+ SkDebugf("%s oldWinding=%d windValue=%d", __FUNCTION__, winding, windVal);
+#endif
+ // see if a + change in T results in a +/- change in X (compute x'(T))
+ *dx = (*CurveSlopeAtT[fVerb])(fPts, tHit).fX;
+ if (fVerb > SkPath::kLine_Verb && approximately_zero(*dx)) {
+ *dx = fPts[2].fX - fPts[1].fX - *dx;
+ }
+ if (*dx == 0) {
+#if DEBUG_WINDING_AT_T
+ SkDebugf(" dx=0 winding=SK_MinS32\n");
+#endif
+ return SK_MinS32;
+ }
+ if (winding * *dx > 0) { // if same signs, result is negative
+ winding += *dx > 0 ? -windVal : windVal;
+ }
+#if DEBUG_WINDING_AT_T
+ SkDebugf(" dx=%c winding=%d\n", *dx > 0 ? '+' : '-', winding);
+#endif
+ return winding;
+}
+
+int SkOpSegment::windSum(const SkOpAngle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ int index = SkMin32(start, end);
+ return windSum(index);
+}
+
+int SkOpSegment::windValue(const SkOpAngle* angle) const {
+ int start = angle->start();
+ int end = angle->end();
+ int index = SkMin32(start, end);
+ return windValue(index);
+}
+
+int SkOpSegment::windValueAt(double t) const {
+ int count = fTs.count();
+ for (int index = 0; index < count; ++index) {
+ if (fTs[index].fT == t) {
+ return fTs[index].fWindValue;
+ }
+ }
+ SkASSERT(0);
+ return 0;
+}
+
+void SkOpSegment::zeroSpan(SkOpSpan* span) {
+ SkASSERT(span->fWindValue > 0 || span->fOppValue > 0);
+ span->fWindValue = 0;
+ span->fOppValue = 0;
+ SkASSERT(!span->fDone);
+ span->fDone = true;
+ ++fDoneSpans;
+}
+
+#if DEBUG_SWAP_TOP
+bool SkOpSegment::controlsContainedByEnds(int tStart, int tEnd) const {
+ if (fVerb != SkPath::kCubic_Verb) {
+ return false;
+ }
+ SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT);
+ return dst.controlsContainedByEnds();
+}
+#endif
+
+#if DEBUG_CONCIDENT
+// SkASSERT if pair has not already been added
+void SkOpSegment::debugAddTPair(double t, const SkOpSegment& other, double otherT) const {
+ for (int i = 0; i < fTs.count(); ++i) {
+ if (fTs[i].fT == t && fTs[i].fOther == &other && fTs[i].fOtherT == otherT) {
+ return;
+ }
+ }
+ SkASSERT(0);
+}
+#endif
+
+#if DEBUG_CONCIDENT
+void SkOpSegment::debugShowTs() const {
+ SkDebugf("%s id=%d", __FUNCTION__, fID);
+ int lastWind = -1;
+ int lastOpp = -1;
+ double lastT = -1;
+ int i;
+ for (i = 0; i < fTs.count(); ++i) {
+ bool change = lastT != fTs[i].fT || lastWind != fTs[i].fWindValue
+ || lastOpp != fTs[i].fOppValue;
+ if (change && lastWind >= 0) {
+ SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]",
+ lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp);
+ }
+ if (change) {
+ SkDebugf(" [o=%d", fTs[i].fOther->fID);
+ lastWind = fTs[i].fWindValue;
+ lastOpp = fTs[i].fOppValue;
+ lastT = fTs[i].fT;
+ } else {
+ SkDebugf(",%d", fTs[i].fOther->fID);
+ }
+ }
+ if (i <= 0) {
+ return;
+ }
+ SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]",
+ lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp);
+ if (fOperand) {
+ SkDebugf(" operand");
+ }
+ if (done()) {
+ SkDebugf(" done");
+ }
+ SkDebugf("\n");
+}
+#endif
+
+#if DEBUG_ACTIVE_SPANS
+void SkOpSegment::debugShowActiveSpans() const {
+ if (done()) {
+ return;
+ }
+#if DEBUG_ACTIVE_SPANS_SHORT_FORM
+ int lastId = -1;
+ double lastT = -1;
+#endif
+ for (int i = 0; i < fTs.count(); ++i) {
+ SkASSERT(&fTs[i] == &fTs[i].fOther->fTs[fTs[i].fOtherIndex].fOther->
+ fTs[fTs[i].fOther->fTs[fTs[i].fOtherIndex].fOtherIndex]);
+ if (fTs[i].fDone) {
+ continue;
+ }
+#if DEBUG_ACTIVE_SPANS_SHORT_FORM
+ if (lastId == fID && lastT == fTs[i].fT) {
+ continue;
+ }
+ lastId = fID;
+ lastT = fTs[i].fT;
+#endif
+ SkDebugf("%s id=%d", __FUNCTION__, fID);
+ SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY);
+ for (int vIndex = 1; vIndex <= fVerb; ++vIndex) {
+ SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY);
+ }
+ const SkOpSpan* span = &fTs[i];
+ SkDebugf(") t=%1.9g (%1.9g,%1.9g)", span->fT, xAtT(span), yAtT(span));
+ int iEnd = i + 1;
+ while (fTs[iEnd].fT < 1 && approximately_equal(fTs[i].fT, fTs[iEnd].fT)) {
+ ++iEnd;
+ }
+ SkDebugf(" tEnd=%1.9g", fTs[iEnd].fT);
+ const SkOpSegment* other = fTs[i].fOther;
+ SkDebugf(" other=%d otherT=%1.9g otherIndex=%d windSum=",
+ other->fID, fTs[i].fOtherT, fTs[i].fOtherIndex);
+ if (fTs[i].fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", fTs[i].fWindSum);
+ }
+ SkDebugf(" windValue=%d oppValue=%d\n", fTs[i].fWindValue, fTs[i].fOppValue);
+ }
+}
+#endif
+
+
+#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE
+void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding) {
+ const SkPoint& pt = xyAtT(&span);
+ SkDebugf("%s id=%d", fun, fID);
+ SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY);
+ for (int vIndex = 1; vIndex <= fVerb; ++vIndex) {
+ SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY);
+ }
+ SkASSERT(&span == &span.fOther->fTs[span.fOtherIndex].fOther->
+ fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]);
+ SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=%d windSum=",
+ span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY,
+ (&span)[1].fT, winding);
+ if (span.fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", span.fWindSum);
+ }
+ SkDebugf(" windValue=%d\n", span.fWindValue);
+}
+
+void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding,
+ int oppWinding) {
+ const SkPoint& pt = xyAtT(&span);
+ SkDebugf("%s id=%d", fun, fID);
+ SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY);
+ for (int vIndex = 1; vIndex <= fVerb; ++vIndex) {
+ SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY);
+ }
+ SkASSERT(&span == &span.fOther->fTs[span.fOtherIndex].fOther->
+ fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]);
+ SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=%d newOppSum=%d oppSum=",
+ span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY,
+ (&span)[1].fT, winding, oppWinding);
+ if (span.fOppSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", span.fOppSum);
+ }
+ SkDebugf(" windSum=");
+ if (span.fWindSum == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", span.fWindSum);
+ }
+ SkDebugf(" windValue=%d\n", span.fWindValue);
+}
+#endif
+
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+void SkOpSegment::debugShowSort(const char* fun, const SkTDArray<SkOpAngle*>& angles, int first,
+ const int contourWinding, const int oppContourWinding) const {
+ if (--gDebugSortCount < 0) {
+ return;
+ }
+ SkASSERT(angles[first]->segment() == this);
+ SkASSERT(angles.count() > 1);
+ int lastSum = contourWinding;
+ int oppLastSum = oppContourWinding;
+ const SkOpAngle* firstAngle = angles[first];
+ int windSum = lastSum - spanSign(firstAngle);
+ int oppoSign = oppSign(firstAngle);
+ int oppWindSum = oppLastSum - oppoSign;
+ #define WIND_AS_STRING(x) char x##Str[12]; if (!valid_wind(x)) strcpy(x##Str, "?"); \
+ else snprintf(x##Str, sizeof(x##Str), "%d", x)
+ WIND_AS_STRING(contourWinding);
+ WIND_AS_STRING(oppContourWinding);
+ SkDebugf("%s %s contourWinding=%s oppContourWinding=%s sign=%d\n", fun, __FUNCTION__,
+ contourWindingStr, oppContourWindingStr, spanSign(angles[first]));
+ int index = first;
+ bool firstTime = true;
+ do {
+ const SkOpAngle& angle = *angles[index];
+ const SkOpSegment& segment = *angle.segment();
+ int start = angle.start();
+ int end = angle.end();
+ const SkOpSpan& sSpan = segment.fTs[start];
+ const SkOpSpan& eSpan = segment.fTs[end];
+ const SkOpSpan& mSpan = segment.fTs[SkMin32(start, end)];
+ bool opp = segment.fOperand ^ fOperand;
+ if (!firstTime) {
+ oppoSign = segment.oppSign(&angle);
+ if (opp) {
+ oppLastSum = oppWindSum;
+ oppWindSum -= segment.spanSign(&angle);
+ if (oppoSign) {
+ lastSum = windSum;
+ windSum -= oppoSign;
+ }
+ } else {
+ lastSum = windSum;
+ windSum -= segment.spanSign(&angle);
+ if (oppoSign) {
+ oppLastSum = oppWindSum;
+ oppWindSum -= oppoSign;
+ }
+ }
+ }
+ SkDebugf("%s [%d] %s", __FUNCTION__, index,
+ angle.unsortable() ? "*** UNSORTABLE *** " : "");
+ #if COMPACT_DEBUG_SORT
+ SkDebugf("id=%d %s start=%d (%1.9g,%,1.9g) end=%d (%1.9g,%,1.9g)",
+ segment.fID, kLVerbStr[segment.fVerb],
+ start, segment.xAtT(&sSpan), segment.yAtT(&sSpan), end,
+ segment.xAtT(&eSpan), segment.yAtT(&eSpan));
+ #else
+ switch (segment.fVerb) {
+ case SkPath::kLine_Verb:
+ SkDebugf(LINE_DEBUG_STR, LINE_DEBUG_DATA(segment.fPts));
+ break;
+ case SkPath::kQuad_Verb:
+ SkDebugf(QUAD_DEBUG_STR, QUAD_DEBUG_DATA(segment.fPts));
+ break;
+ case SkPath::kCubic_Verb:
+ SkDebugf(CUBIC_DEBUG_STR, CUBIC_DEBUG_DATA(segment.fPts));
+ break;
+ default:
+ SkASSERT(0);
+ }
+ SkDebugf(" tStart=%1.9g tEnd=%1.9g", sSpan.fT, eSpan.fT);
+ #endif
+ SkDebugf(" sign=%d windValue=%d windSum=", angle.sign(), mSpan.fWindValue);
+ #ifdef SK_DEBUG
+ winding_printf(mSpan.fWindSum);
+ #endif
+ int last, wind;
+ if (opp) {
+ last = oppLastSum;
+ wind = oppWindSum;
+ } else {
+ last = lastSum;
+ wind = windSum;
+ }
+ bool useInner = valid_wind(last) && valid_wind(wind) && UseInnerWinding(last, wind);
+ WIND_AS_STRING(last);
+ WIND_AS_STRING(wind);
+ WIND_AS_STRING(lastSum);
+ WIND_AS_STRING(oppLastSum);
+ WIND_AS_STRING(windSum);
+ WIND_AS_STRING(oppWindSum);
+ #undef WIND_AS_STRING
+ if (!oppoSign) {
+ SkDebugf(" %s->%s (max=%s)", lastStr, windStr, useInner ? windStr : lastStr);
+ } else {
+ SkDebugf(" %s->%s (%s->%s)", lastStr, windStr, opp ? lastSumStr : oppLastSumStr,
+ opp ? windSumStr : oppWindSumStr);
+ }
+ SkDebugf(" done=%d tiny=%d opp=%d\n", mSpan.fDone, mSpan.fTiny, opp);
+#if false && DEBUG_ANGLE
+ angle.debugShow(segment.xyAtT(&sSpan));
+#endif
+ ++index;
+ if (index == angles.count()) {
+ index = 0;
+ }
+ if (firstTime) {
+ firstTime = false;
+ }
+ } while (index != first);
+}
+
+void SkOpSegment::debugShowSort(const char* fun, const SkTDArray<SkOpAngle*>& angles, int first) {
+ const SkOpAngle* firstAngle = angles[first];
+ const SkOpSegment* segment = firstAngle->segment();
+ int winding = segment->updateWinding(firstAngle);
+ int oppWinding = segment->updateOppWinding(firstAngle);
+ debugShowSort(fun, angles, first, winding, oppWinding);
+}
+
+#endif
+
+#if DEBUG_SHOW_WINDING
+int SkOpSegment::debugShowWindingValues(int slotCount, int ofInterest) const {
+ if (!(1 << fID & ofInterest)) {
+ return 0;
+ }
+ int sum = 0;
+ SkTDArray<char> slots;
+ slots.setCount(slotCount * 2);
+ memset(slots.begin(), ' ', slotCount * 2);
+ for (int i = 0; i < fTs.count(); ++i) {
+ // if (!(1 << fTs[i].fOther->fID & ofInterest)) {
+ // continue;
+ // }
+ sum += fTs[i].fWindValue;
+ slots[fTs[i].fOther->fID - 1] = as_digit(fTs[i].fWindValue);
+ sum += fTs[i].fOppValue;
+ slots[slotCount + fTs[i].fOther->fID - 1] = as_digit(fTs[i].fOppValue);
+ }
+ SkDebugf("%s id=%2d %.*s | %.*s\n", __FUNCTION__, fID, slotCount, slots.begin(), slotCount,
+ slots.begin() + slotCount);
+ return sum;
+}
+#endif
diff --git a/src/pathops/SkOpSegment.h b/src/pathops/SkOpSegment.h
new file mode 100644
index 0000000000..1e9eb4b09a
--- /dev/null
+++ b/src/pathops/SkOpSegment.h
@@ -0,0 +1,392 @@
+/*
+ * 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 "SkPathOpsBounds.h"
+#include "SkPathOpsCurve.h"
+#include "SkTDArray.h"
+
+class SkPathWriter;
+
+class SkOpSegment {
+public:
+ SkOpSegment() {
+#if DEBUG_DUMP
+ fID = ++gSegmentID;
+#endif
+ }
+
+ bool operator<(const SkOpSegment& rh) const {
+ return fBounds.fTop < rh.fBounds.fTop;
+ }
+
+ const SkPathOpsBounds& bounds() const {
+ return fBounds;
+ }
+
+ // OPTIMIZE
+ // when the edges are initially walked, they don't automatically get the prior and next
+ // edges assigned to positions t=0 and t=1. Doing that would remove the need for this check,
+ // and would additionally remove the need for similar checks in condition edges. It would
+ // also allow intersection code to assume end of segment intersections (maybe?)
+ bool complete() const {
+ int count = fTs.count();
+ return count > 1 && fTs[0].fT == 0 && fTs[--count].fT == 1;
+ }
+
+ bool done() const {
+ SkASSERT(fDoneSpans <= fTs.count());
+ return fDoneSpans == fTs.count();
+ }
+
+ bool done(int min) const {
+ return fTs[min].fDone;
+ }
+
+ bool done(const SkOpAngle* angle) const {
+ return done(SkMin32(angle->start(), angle->end()));
+ }
+
+ SkVector dxdy(int index) const {
+ return (*CurveSlopeAtT[fVerb])(fPts, fTs[index].fT);
+ }
+
+ SkScalar dy(int index) const {
+ return dxdy(index).fY;
+ }
+
+ bool intersected() const {
+ return fTs.count() > 0;
+ }
+
+ bool isCanceled(int tIndex) const {
+ return fTs[tIndex].fWindValue == 0 && fTs[tIndex].fOppValue == 0;
+ }
+
+ bool isConnected(int startIndex, int endIndex) const {
+ return fTs[startIndex].fWindSum != SK_MinS32 || fTs[endIndex].fWindSum != SK_MinS32;
+ }
+
+ bool isHorizontal() const {
+ return fBounds.fTop == fBounds.fBottom;
+ }
+
+ bool isVertical() const {
+ return fBounds.fLeft == fBounds.fRight;
+ }
+
+ bool isVertical(int start, int end) const {
+ return (*CurveIsVertical[fVerb])(fPts, start, end);
+ }
+
+ bool operand() const {
+ return fOperand;
+ }
+
+ int oppSign(const SkOpAngle* angle) const {
+ SkASSERT(angle->segment() == this);
+ return oppSign(angle->start(), angle->end());
+ }
+
+ int oppSign(int startIndex, int endIndex) const {
+ int result = startIndex < endIndex ? -fTs[startIndex].fOppValue : fTs[endIndex].fOppValue;
+#if DEBUG_WIND_BUMP
+ SkDebugf("%s oppSign=%d\n", __FUNCTION__, result);
+#endif
+ return result;
+ }
+
+ int oppSum(int tIndex) const {
+ return fTs[tIndex].fOppSum;
+ }
+
+ int oppSum(const SkOpAngle* angle) const {
+ int lesser = SkMin32(angle->start(), angle->end());
+ return fTs[lesser].fOppSum;
+ }
+
+ int oppValue(int tIndex) const {
+ return fTs[tIndex].fOppValue;
+ }
+
+ int oppValue(const SkOpAngle* angle) const {
+ int lesser = SkMin32(angle->start(), angle->end());
+ return fTs[lesser].fOppValue;
+ }
+
+ const SkPoint* pts() const {
+ return fPts;
+ }
+
+ void reset() {
+ init(NULL, (SkPath::Verb) -1, false, false);
+ fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
+ fTs.reset();
+ }
+
+ void setOppXor(bool isOppXor) {
+ fOppXor = isOppXor;
+ }
+
+ void setSpanT(int index, double t) {
+ SkOpSpan& span = fTs[index];
+ span.fT = t;
+ span.fOther->fTs[span.fOtherIndex].fOtherT = t;
+ }
+
+ void setUpWinding(int index, int endIndex, int* maxWinding, int* sumWinding) {
+ int deltaSum = spanSign(index, endIndex);
+ *maxWinding = *sumWinding;
+ *sumWinding -= deltaSum;
+ }
+
+ // OPTIMIZATION: mark as debugging only if used solely by tests
+ const SkOpSpan& span(int tIndex) const {
+ return fTs[tIndex];
+ }
+
+ int spanSign(const SkOpAngle* angle) const {
+ SkASSERT(angle->segment() == this);
+ return spanSign(angle->start(), angle->end());
+ }
+
+ int spanSign(int startIndex, int endIndex) const {
+ int result = startIndex < endIndex ? -fTs[startIndex].fWindValue : fTs[endIndex].fWindValue;
+#if DEBUG_WIND_BUMP
+ SkDebugf("%s spanSign=%d\n", __FUNCTION__, result);
+#endif
+ return result;
+ }
+
+ // OPTIMIZATION: mark as debugging only if used solely by tests
+ double t(int tIndex) const {
+ return fTs[tIndex].fT;
+ }
+
+ double tAtMid(int start, int end, double mid) const {
+ return fTs[start].fT * (1 - mid) + fTs[end].fT * mid;
+ }
+
+ bool unsortable(int index) const {
+ return fTs[index].fUnsortableStart || fTs[index].fUnsortableEnd;
+ }
+
+ void updatePts(const SkPoint pts[]) {
+ fPts = pts;
+ }
+
+ SkPath::Verb verb() const {
+ return fVerb;
+ }
+
+ int windSum(int tIndex) const {
+ return fTs[tIndex].fWindSum;
+ }
+
+ int windValue(int tIndex) const {
+ return fTs[tIndex].fWindValue;
+ }
+
+ SkScalar xAtT(int index) const {
+ return xAtT(&fTs[index]);
+ }
+
+ SkScalar xAtT(const SkOpSpan* span) const {
+ return xyAtT(span).fX;
+ }
+
+ const SkPoint& xyAtT(const SkOpSpan* span) const {
+ return span->fPt;
+ }
+
+ // used only by right angle winding finding
+ SkPoint xyAtT(double mid) const {
+ return (*CurvePointAtT[fVerb])(fPts, mid);
+ }
+
+ const SkPoint& xyAtT(int index) const {
+ return xyAtT(&fTs[index]);
+ }
+
+ SkScalar yAtT(int index) const {
+ return yAtT(&fTs[index]);
+ }
+
+ SkScalar yAtT(const SkOpSpan* span) const {
+ return xyAtT(span).fY;
+ }
+
+ bool activeAngle(int index, int* done, SkTDArray<SkOpAngle>* angles);
+ SkPoint activeLeftTop(bool onlySortable, int* firstT) const;
+ bool activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op);
+ bool activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op,
+ int* sumMiWinding, int* sumSuWinding, int* maxWinding, int* sumWinding,
+ int* oppMaxWinding, int* oppSumWinding);
+ bool activeWinding(int index, int endIndex);
+ bool activeWinding(int index, int endIndex, int* maxWinding, int* sumWinding);
+ void addCubic(const SkPoint pts[4], bool operand, bool evenOdd);
+ void addCurveTo(int start, int end, SkPathWriter* path, bool active) const;
+ void addLine(const SkPoint pts[2], bool operand, bool evenOdd);
+ void addOtherT(int index, double otherT, int otherIndex);
+ void addQuad(const SkPoint pts[3], bool operand, bool evenOdd);
+ int addSelfT(SkOpSegment* other, const SkPoint& pt, double newT);
+ int addT(SkOpSegment* other, const SkPoint& pt, double newT);
+ void addTCancel(double startT, double endT, SkOpSegment* other, double oStartT, double oEndT);
+ void addTCoincident(double startT, double endT, SkOpSegment* other, double oStartT,
+ double oEndT);
+ void addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, const SkPoint& pt);
+ int addUnsortableT(SkOpSegment* other, bool start, const SkPoint& pt, double newT);
+ bool betweenTs(int lesser, double testT, int greater) const;
+ int computeSum(int startIndex, int endIndex, bool binary);
+ int crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, bool* hitSomething,
+ double mid, bool opp, bool current) const;
+ SkOpSegment* findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
+ bool* unsortable, SkPathOp op, const int xorMiMask,
+ const int xorSuMask);
+ SkOpSegment* findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd,
+ bool* unsortable);
+ SkOpSegment* findNextXor(int* nextStart, int* nextEnd, bool* unsortable);
+ void findTooCloseToCall();
+ SkOpSegment* findTop(int* tIndex, int* endIndex, bool* unsortable, bool onlySortable);
+ void fixOtherTIndex();
+ void initWinding(int start, int end);
+ void initWinding(int start, int end, double tHit, int winding, SkScalar hitDx, int oppWind,
+ SkScalar hitOppDx);
+ bool isLinear(int start, int end) const;
+ bool isMissing(double startT) const;
+ bool isSimple(int end) const;
+ SkOpSpan* markAndChaseDoneBinary(int index, int endIndex);
+ SkOpSpan* markAndChaseDoneUnary(int index, int endIndex);
+ SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding);
+ SkOpSpan* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding,
+ bool activeAngle, const SkOpAngle* angle);
+ void markDone(int index, int winding);
+ void markDoneBinary(int index);
+ void markDoneUnary(int index);
+ SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding);
+ SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding, int oppWinding);
+ void markWinding(int index, int winding);
+ void markWinding(int index, int winding, int oppWinding);
+ bool nextCandidate(int* start, int* end) const;
+ int nextExactSpan(int from, int step) const;
+ int nextSpan(int from, int step) const;
+ void setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding,
+ int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding);
+ static bool SortAngles(const SkTDArray<SkOpAngle>& angles, SkTDArray<SkOpAngle*>* angleList);
+ void subDivide(int start, int end, SkPoint edge[4]) const;
+ void undoneSpan(int* start, int* end);
+ int updateOppWindingReverse(const SkOpAngle* angle) const;
+ int updateWindingReverse(const SkOpAngle* angle) const;
+ static bool UseInnerWinding(int outerWinding, int innerWinding);
+ int windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const;
+ int windSum(const SkOpAngle* angle) const;
+ int windValue(const SkOpAngle* angle) const;
+
+#if DEBUG_DUMP
+ int debugID() const {
+ return fID;
+ }
+#endif
+#if DEBUG_ACTIVE_SPANS
+ void debugShowActiveSpans() const;
+#endif
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+ void debugShowSort(const char* fun, const SkTDArray<SkOpAngle*>& angles, int first,
+ const int contourWinding, const int oppContourWinding) const;
+ void debugShowSort(const char* fun, const SkTDArray<SkOpAngle*>& angles, int first);
+#endif
+#if DEBUG_CONCIDENT
+ void debugShowTs() const;
+#endif
+#if DEBUG_SHOW_WINDING
+ int debugShowWindingValues(int slotCount, int ofInterest) const;
+#endif
+
+private:
+ bool activeAngleOther(int index, int* done, SkTDArray<SkOpAngle>* angles);
+ bool activeAngleInner(int index, int* done, SkTDArray<SkOpAngle>* angles);
+ void addAngle(SkTDArray<SkOpAngle>* angles, int start, int end) const;
+ void addCancelOutsides(double tStart, double oStart, SkOpSegment* other, double oEnd);
+ void addCoinOutsides(const SkTDArray<double>& outsideTs, SkOpSegment* other, double oEnd);
+ void addTwoAngles(int start, int end, SkTDArray<SkOpAngle>* angles) const;
+ int advanceCoincidentOther(const SkOpSpan* test, double oEndT, int oIndex);
+ int advanceCoincidentThis(const SkOpSpan* oTest, bool opp, int index);
+ void buildAngles(int index, SkTDArray<SkOpAngle>* angles, bool includeOpp) const;
+ void buildAnglesInner(int index, SkTDArray<SkOpAngle>* angles) const;
+ int bumpCoincidentThis(const SkOpSpan& oTest, bool opp, int index,
+ SkTDArray<double>* outsideTs);
+ int bumpCoincidentOther(const SkOpSpan& test, double oEndT, int& oIndex,
+ SkTDArray<double>* oOutsideTs);
+ bool bumpSpan(SkOpSpan* span, int windDelta, int oppDelta);
+ bool clockwise(int tStart, int tEnd) const;
+ void decrementSpan(SkOpSpan* span);
+ bool equalPoints(int greaterTIndex, int lesserTIndex);
+ int findStartingEdge(const SkTDArray<SkOpAngle*>& sorted, int start, int end);
+ void init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd);
+ void matchWindingValue(int tIndex, double t, bool borrowWind);
+ SkOpSpan* markAndChaseDone(int index, int endIndex, int winding);
+ SkOpSpan* markAndChaseDoneBinary(const SkOpAngle* angle, int winding, int oppWinding);
+ SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, const int winding);
+ SkOpSpan* markAndChaseWinding(int index, int endIndex, int winding, int oppWinding);
+ SkOpSpan* markAngle(int maxWinding, int sumWinding, bool activeAngle, const SkOpAngle* angle);
+ void markDoneBinary(int index, int winding, int oppWinding);
+ SkOpSpan* markAndChaseDoneUnary(const SkOpAngle* angle, int winding);
+ void markOneDone(const char* funName, int tIndex, int winding);
+ void markOneDoneBinary(const char* funName, int tIndex);
+ void markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding);
+ void markOneDoneUnary(const char* funName, int tIndex);
+ void markUnsortable(int start, int end);
+ bool monotonicInY(int tStart, int tEnd) const;
+ bool multipleSpans(int end) const;
+ SkOpSegment* nextChase(int* index, const int step, int* min, SkOpSpan** last);
+ bool serpentine(int tStart, int tEnd) const;
+ void subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const;
+ bool tiny(const SkOpAngle* angle) const;
+ static void TrackOutside(SkTDArray<double>* outsideTs, double end, double start);
+ int updateOppWinding(int index, int endIndex) const;
+ int updateOppWinding(const SkOpAngle* angle) const;
+ int updateWinding(int index, int endIndex) const;
+ int updateWinding(const SkOpAngle* angle) const;
+ SkOpSpan* verifyOneWinding(const char* funName, int tIndex);
+ SkOpSpan* verifyOneWindingU(const char* funName, int tIndex);
+ int windValueAt(double t) const;
+ void zeroSpan(SkOpSpan* span);
+
+#if DEBUG_SWAP_TOP
+ bool controlsContainedByEnds(int tStart, int tEnd) const;
+#endif
+#if DEBUG_CONCIDENT
+ void debugAddTPair(double t, const SkOpSegment& other, double otherT) const;
+#endif
+#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE
+ void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding);
+ void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, int oppWinding);
+#endif
+#if DEBUG_WINDING
+ static char as_digit(int value) {
+ return value < 0 ? '?' : value <= 9 ? '0' + value : '+';
+ }
+#endif
+
+ const SkPoint* fPts;
+ SkPathOpsBounds fBounds;
+ SkTDArray<SkOpSpan> fTs; // two or more (always includes t=0 t=1)
+ // OPTIMIZATION: could pack donespans, verb, operand, xor into 1 int-sized value
+ int fDoneSpans; // quick check that segment is finished
+ // OPTIMIZATION: force the following to be byte-sized
+ SkPath::Verb fVerb;
+ bool fOperand;
+ bool fXor; // set if original contour had even-odd fill
+ bool fOppXor; // set if opposite operand had even-odd fill
+#if DEBUG_DUMP
+ int fID;
+#endif
+};
+
+#endif
diff --git a/src/pathops/SkOpSpan.h b/src/pathops/SkOpSpan.h
new file mode 100644
index 0000000000..3666623fbe
--- /dev/null
+++ b/src/pathops/SkOpSpan.h
@@ -0,0 +1,31 @@
+/*
+ * 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 "SkPoint.h"
+
+class SkOpSegment;
+
+struct SkOpSpan {
+ SkOpSegment* fOther;
+ SkPoint fPt; // computed when the curves are intersected
+ double fT;
+ double fOtherT; // value at fOther[fOtherIndex].fT
+ int fOtherIndex; // can't be used during intersection
+ 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
+ bool fDone; // if set, this span to next higher T has been processed
+ bool fUnsortableStart; // set when start is part of an unsortable pair
+ bool fUnsortableEnd; // set when end is part of an unsortable pair
+ bool fTiny; // if set, span may still be considered once for edge following
+ bool fLoop; // set when a cubic loops back to this point
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsBounds.cpp b/src/pathops/SkPathOpsBounds.cpp
new file mode 100644
index 0000000000..106cd30076
--- /dev/null
+++ b/src/pathops/SkPathOpsBounds.cpp
@@ -0,0 +1,40 @@
+/*
+ * 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 "SkPathOpsBounds.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+
+void SkPathOpsBounds::setCubicBounds(const SkPoint a[4]) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkDRect dRect;
+ dRect.setBounds(cubic);
+ set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop),
+ SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom));
+}
+
+void SkPathOpsBounds::setLineBounds(const SkPoint a[2]) {
+ setPointBounds(a[0]);
+ add(a[1]);
+}
+
+void SkPathOpsBounds::setQuadBounds(const SkPoint a[3]) {
+ SkDQuad quad;
+ quad.set(a);
+ SkDRect dRect;
+ dRect.setBounds(quad);
+ set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop),
+ SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom));
+}
+
+void (SkPathOpsBounds::*SetCurveBounds[])(const SkPoint[]) = {
+ NULL,
+ &SkPathOpsBounds::setLineBounds,
+ &SkPathOpsBounds::setQuadBounds,
+ &SkPathOpsBounds::setCubicBounds
+};
diff --git a/src/pathops/SkPathOpsBounds.h b/src/pathops/SkPathOpsBounds.h
new file mode 100644
index 0000000000..8102032813
--- /dev/null
+++ b/src/pathops/SkPathOpsBounds.h
@@ -0,0 +1,61 @@
+/*
+ * 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 a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
+ a.fTop <= b.fBottom && b.fTop <= a.fBottom;
+ }
+
+ // FIXME: add() is generically useful and could be added directly to SkRect
+ 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;
+ }
+
+ // unlike isEmpty(), this permits lines, but not points
+ // FIXME: unused for now
+ bool isReallyEmpty() const {
+ // use !<= instead of > to detect NaN values
+ return !(fLeft <= fRight) || !(fTop <= fBottom)
+ || (fLeft == fRight && fTop == fBottom);
+ }
+
+ void setCubicBounds(const SkPoint a[4]);
+ void setLineBounds(const SkPoint a[2]);
+ void setQuadBounds(const SkPoint a[3]);
+
+ void setPointBounds(const SkPoint& pt) {
+ fLeft = fRight = pt.fX;
+ fTop = fBottom = pt.fY;
+ }
+
+ typedef SkRect INHERITED;
+};
+
+extern void (SkPathOpsBounds::*SetCurveBounds[])(const SkPoint[]);
+
+#endif
diff --git a/src/pathops/SkPathOpsCommon.cpp b/src/pathops/SkPathOpsCommon.cpp
new file mode 100644
index 0000000000..0a65bc9256
--- /dev/null
+++ b/src/pathops/SkPathOpsCommon.cpp
@@ -0,0 +1,578 @@
+/*
+ * 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 "SkOpEdgeBuilder.h"
+#include "SkPathOpsCommon.h"
+#include "SkPathWriter.h"
+#include "TSearch.h"
+
+static int contourRangeCheckY(const SkTDArray<SkOpContour*>& contourList, SkOpSegment** currentPtr,
+ int* indexPtr, int* endIndexPtr, double* bestHit, SkScalar* bestDx,
+ bool* tryAgain, double* midPtr, bool opp) {
+ const int index = *indexPtr;
+ const int endIndex = *endIndexPtr;
+ const double mid = *midPtr;
+ const SkOpSegment* current = *currentPtr;
+ double tAtMid = current->tAtMid(index, endIndex, mid);
+ SkPoint basePt = current->xyAtT(tAtMid);
+ int contourCount = contourList.count();
+ SkScalar bestY = SK_ScalarMin;
+ SkOpSegment* bestSeg = NULL;
+ int bestTIndex;
+ bool bestOpp;
+ bool hitSomething = false;
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ SkOpContour* contour = contourList[cTest];
+ bool testOpp = contour->operand() ^ current->operand() ^ opp;
+ if (basePt.fY < contour->bounds().fTop) {
+ continue;
+ }
+ if (bestY > contour->bounds().fBottom) {
+ continue;
+ }
+ int segmentCount = contour->segments().count();
+ for (int test = 0; test < segmentCount; ++test) {
+ SkOpSegment* testSeg = &contour->segments()[test];
+ SkScalar testY = bestY;
+ double testHit;
+ int testTIndex = testSeg->crossedSpanY(basePt, &testY, &testHit, &hitSomething, tAtMid,
+ testOpp, testSeg == current);
+ if (testTIndex < 0) {
+ if (testTIndex == SK_MinS32) {
+ hitSomething = true;
+ bestSeg = NULL;
+ goto abortContours; // vertical encountered, return and try different point
+ }
+ continue;
+ }
+ if (testSeg == current && current->betweenTs(index, testHit, endIndex)) {
+ double baseT = current->t(index);
+ double endT = current->t(endIndex);
+ double newMid = (testHit - baseT) / (endT - baseT);
+#if DEBUG_WINDING
+ double midT = current->tAtMid(index, endIndex, mid);
+ SkPoint midXY = current->xyAtT(midT);
+ double newMidT = current->tAtMid(index, endIndex, newMid);
+ SkPoint newXY = current->xyAtT(newMidT);
+ SkDebugf("%s [%d] mid=%1.9g->%1.9g s=%1.9g (%1.9g,%1.9g) m=%1.9g (%1.9g,%1.9g)"
+ " n=%1.9g (%1.9g,%1.9g) e=%1.9g (%1.9g,%1.9g)\n", __FUNCTION__,
+ current->debugID(), mid, newMid,
+ baseT, current->xAtT(index), current->yAtT(index),
+ baseT + mid * (endT - baseT), midXY.fX, midXY.fY,
+ baseT + newMid * (endT - baseT), newXY.fX, newXY.fY,
+ endT, current->xAtT(endIndex), current->yAtT(endIndex));
+#endif
+ *midPtr = newMid * 2; // calling loop with divide by 2 before continuing
+ return SK_MinS32;
+ }
+ bestSeg = testSeg;
+ *bestHit = testHit;
+ bestOpp = testOpp;
+ bestTIndex = testTIndex;
+ bestY = testY;
+ }
+ }
+abortContours:
+ int result;
+ if (!bestSeg) {
+ result = hitSomething ? SK_MinS32 : 0;
+ } else {
+ if (bestSeg->windSum(bestTIndex) == SK_MinS32) {
+ *currentPtr = bestSeg;
+ *indexPtr = bestTIndex;
+ *endIndexPtr = bestSeg->nextSpan(bestTIndex, 1);
+ SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0);
+ *tryAgain = true;
+ return 0;
+ }
+ result = bestSeg->windingAtT(*bestHit, bestTIndex, bestOpp, bestDx);
+ SkASSERT(*bestDx);
+ }
+ double baseT = current->t(index);
+ double endT = current->t(endIndex);
+ *bestHit = baseT + mid * (endT - baseT);
+ return result;
+}
+
+SkOpSegment* FindUndone(SkTDArray<SkOpContour*>& contourList, int* start, int* end) {
+ int contourCount = contourList.count();
+ SkOpSegment* result;
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = contourList[cIndex];
+ result = contour->undoneSegment(start, end);
+ if (result) {
+ return result;
+ }
+ }
+ return NULL;
+}
+
+SkOpSegment* FindChase(SkTDArray<SkOpSpan*>& chase, int& tIndex, int& endIndex) {
+ while (chase.count()) {
+ SkOpSpan* span;
+ chase.pop(&span);
+ const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex);
+ SkOpSegment* segment = backPtr.fOther;
+ tIndex = backPtr.fOtherIndex;
+ SkTDArray<SkOpAngle> angles;
+ int done = 0;
+ if (segment->activeAngle(tIndex, &done, &angles)) {
+ SkOpAngle* last = angles.end() - 1;
+ tIndex = last->start();
+ endIndex = last->end();
+ #if TRY_ROTATE
+ *chase.insert(0) = span;
+ #else
+ *chase.append() = span;
+ #endif
+ return last->segment();
+ }
+ if (done == angles.count()) {
+ continue;
+ }
+ SkTDArray<SkOpAngle*> sorted;
+ bool sortable = SkOpSegment::SortAngles(angles, &sorted);
+ int angleCount = sorted.count();
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0);
+#endif
+ if (!sortable) {
+ continue;
+ }
+ // find first angle, initialize winding to computed fWindSum
+ int firstIndex = -1;
+ const SkOpAngle* angle;
+ int winding;
+ do {
+ angle = sorted[++firstIndex];
+ segment = angle->segment();
+ winding = segment->windSum(angle);
+ } while (winding == SK_MinS32);
+ int spanWinding = segment->spanSign(angle->start(), angle->end());
+ #if DEBUG_WINDING
+ SkDebugf("%s winding=%d spanWinding=%d\n",
+ __FUNCTION__, winding, spanWinding);
+ #endif
+ // turn span winding into contour winding
+ if (spanWinding * winding < 0) {
+ winding += spanWinding;
+ }
+ #if DEBUG_SORT
+ segment->debugShowSort(__FUNCTION__, sorted, firstIndex, winding, 0);
+ #endif
+ // we care about first sign and whether wind sum indicates this
+ // edge is inside or outside. Maybe need to pass span winding
+ // or first winding or something into this function?
+ // advance to first undone angle, then return it and winding
+ // (to set whether edges are active or not)
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ angle = sorted[firstIndex];
+ winding -= angle->segment()->spanSign(angle);
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ angle = sorted[nextIndex];
+ segment = angle->segment();
+ int maxWinding = winding;
+ winding -= segment->spanSign(angle);
+ #if DEBUG_SORT
+ SkDebugf("%s id=%d maxWinding=%d winding=%d sign=%d\n", __FUNCTION__,
+ segment->debugID(), maxWinding, winding, angle->sign());
+ #endif
+ tIndex = angle->start();
+ endIndex = angle->end();
+ int lesser = SkMin32(tIndex, endIndex);
+ const SkOpSpan& nextSpan = segment->span(lesser);
+ if (!nextSpan.fDone) {
+ // FIXME: this be wrong? assign startWinding if edge is in
+ // same direction. If the direction is opposite, winding to
+ // assign is flipped sign or +/- 1?
+ if (SkOpSegment::UseInnerWinding(maxWinding, winding)) {
+ maxWinding = winding;
+ }
+ segment->markAndChaseWinding(angle, maxWinding, 0);
+ break;
+ }
+ } while (++nextIndex != lastIndex);
+ *chase.insert(0) = span;
+ return segment;
+ }
+ return NULL;
+}
+
+#if DEBUG_ACTIVE_SPANS
+void DebugShowActiveSpans(SkTDArray<SkOpContour*>& contourList) {
+ int index;
+ for (index = 0; index < contourList.count(); ++ index) {
+ contourList[index]->debugShowActiveSpans();
+ }
+}
+#endif
+
+static SkOpSegment* findSortableTop(const SkTDArray<SkOpContour*>& contourList,
+ int* index, int* endIndex, SkPoint* topLeft, bool* unsortable,
+ bool* done, bool onlySortable) {
+ SkOpSegment* result;
+ do {
+ SkPoint bestXY = {SK_ScalarMax, SK_ScalarMax};
+ int contourCount = contourList.count();
+ SkOpSegment* topStart = NULL;
+ *done = true;
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = contourList[cIndex];
+ if (contour->done()) {
+ continue;
+ }
+ const SkPathOpsBounds& bounds = contour->bounds();
+ if (bounds.fBottom < topLeft->fY) {
+ *done = false;
+ continue;
+ }
+ if (bounds.fBottom == topLeft->fY && bounds.fRight < topLeft->fX) {
+ *done = false;
+ continue;
+ }
+ contour->topSortableSegment(*topLeft, &bestXY, &topStart);
+ if (!contour->done()) {
+ *done = false;
+ }
+ }
+ if (!topStart) {
+ return NULL;
+ }
+ *topLeft = bestXY;
+ result = topStart->findTop(index, endIndex, unsortable, onlySortable);
+ } while (!result);
+ return result;
+}
+
+static int rightAngleWinding(const SkTDArray<SkOpContour*>& contourList,
+ SkOpSegment** current, int* index, int* endIndex, double* tHit,
+ SkScalar* hitDx, bool* tryAgain, bool opp) {
+ double test = 0.9;
+ int contourWinding;
+ do {
+ contourWinding = contourRangeCheckY(contourList, current, index, endIndex, tHit, hitDx,
+ tryAgain, &test, opp);
+ if (contourWinding != SK_MinS32 || *tryAgain) {
+ return contourWinding;
+ }
+ test /= 2;
+ } while (!approximately_negative(test));
+ SkASSERT(0); // should be OK to comment out, but interested when this hits
+ return contourWinding;
+}
+
+static void skipVertical(const SkTDArray<SkOpContour*>& contourList,
+ SkOpSegment** current, int* index, int* endIndex) {
+ if (!(*current)->isVertical(*index, *endIndex)) {
+ return;
+ }
+ int contourCount = contourList.count();
+ for (int cIndex = 0; cIndex < contourCount; ++cIndex) {
+ SkOpContour* contour = contourList[cIndex];
+ if (contour->done()) {
+ continue;
+ }
+ *current = contour->nonVerticalSegment(index, endIndex);
+ if (*current) {
+ return;
+ }
+ }
+}
+
+SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour*>& contourList, bool* firstContour,
+ int* indexPtr, int* endIndexPtr, SkPoint* topLeft, bool* unsortable,
+ bool* done, bool binary) {
+ SkOpSegment* current = findSortableTop(contourList, indexPtr, endIndexPtr, topLeft, unsortable,
+ done, true);
+ if (!current) {
+ return NULL;
+ }
+ const int index = *indexPtr;
+ const int endIndex = *endIndexPtr;
+ if (*firstContour) {
+ current->initWinding(index, endIndex);
+ *firstContour = false;
+ return current;
+ }
+ int minIndex = SkMin32(index, endIndex);
+ int sumWinding = current->windSum(minIndex);
+ if (sumWinding != SK_MinS32) {
+ return current;
+ }
+ sumWinding = current->computeSum(index, endIndex, binary);
+ if (sumWinding != SK_MinS32) {
+ return current;
+ }
+ int contourWinding;
+ int oppContourWinding = 0;
+ // the simple upward projection of the unresolved points hit unsortable angles
+ // shoot rays at right angles to the segment to find its winding, ignoring angle cases
+ bool tryAgain;
+ double tHit;
+ SkScalar hitDx = 0;
+ SkScalar hitOppDx = 0;
+ do {
+ // if current is vertical, find another candidate which is not
+ // if only remaining candidates are vertical, then they can be marked done
+ SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0);
+ skipVertical(contourList, &current, indexPtr, endIndexPtr);
+
+ SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0);
+ tryAgain = false;
+ contourWinding = rightAngleWinding(contourList, &current, indexPtr, endIndexPtr, &tHit,
+ &hitDx, &tryAgain, false);
+ if (tryAgain) {
+ continue;
+ }
+ if (!binary) {
+ break;
+ }
+ oppContourWinding = rightAngleWinding(contourList, &current, indexPtr, endIndexPtr, &tHit,
+ &hitOppDx, &tryAgain, true);
+ } while (tryAgain);
+ current->initWinding(*indexPtr, *endIndexPtr, tHit, contourWinding, hitDx, oppContourWinding,
+ hitOppDx);
+ return current;
+}
+
+void FixOtherTIndex(SkTDArray<SkOpContour*>* contourList) {
+ int contourCount = (*contourList).count();
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ SkOpContour* contour = (*contourList)[cTest];
+ contour->fixOtherTIndex();
+ }
+}
+
+void SortSegments(SkTDArray<SkOpContour*>* contourList) {
+ int contourCount = (*contourList).count();
+ for (int cTest = 0; cTest < contourCount; ++cTest) {
+ SkOpContour* contour = (*contourList)[cTest];
+ contour->sortSegments();
+ }
+}
+
+void MakeContourList(SkTArray<SkOpContour>& contours, SkTDArray<SkOpContour*>& list,
+ bool evenOdd, bool oppEvenOdd) {
+ int count = contours.count();
+ if (count == 0) {
+ return;
+ }
+ for (int index = 0; index < count; ++index) {
+ SkOpContour& contour = contours[index];
+ contour.setOppXor(contour.operand() ? evenOdd : oppEvenOdd);
+ *list.append() = &contour;
+ }
+ QSort<SkOpContour>(list.begin(), list.end() - 1);
+}
+
+static bool approximatelyEqual(const SkPoint& a, const SkPoint& b) {
+ return AlmostEqualUlps(a.fX, b.fX) && AlmostEqualUlps(a.fY, b.fY);
+}
+
+static bool lessThan(SkTDArray<double>& distances, const int one, const int two) {
+ return distances[one] < distances[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 Assemble(const SkPathWriter& path, SkPathWriter* simple) {
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("%s\n", __FUNCTION__);
+#endif
+ SkTArray<SkOpContour> contours;
+ SkOpEdgeBuilder builder(path, contours);
+ builder.finish();
+ int count = contours.count();
+ int outer;
+ SkTDArray<int> runs; // indices of partial contours
+ for (outer = 0; outer < count; ++outer) {
+ const SkOpContour& eContour = contours[outer];
+ const SkPoint& eStart = eContour.start();
+ const SkPoint& eEnd = eContour.end();
+#if DEBUG_ASSEMBLE
+ SkDebugf("%s contour", __FUNCTION__);
+ if (!approximatelyEqual(eStart, eEnd)) {
+ SkDebugf("[%d]", runs.count());
+ } else {
+ SkDebugf(" ");
+ }
+ SkDebugf(" start=(%1.9g,%1.9g) end=(%1.9g,%1.9g)\n",
+ eStart.fX, eStart.fY, eEnd.fX, eEnd.fY);
+#endif
+ if (approximatelyEqual(eStart, eEnd)) {
+ eContour.toPath(simple);
+ continue;
+ }
+ *runs.append() = outer;
+ }
+ count = runs.count();
+ if (count == 0) {
+ return;
+ }
+ SkTDArray<int> sLink, eLink;
+ sLink.setCount(count);
+ eLink.setCount(count);
+ int rIndex, iIndex;
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ sLink[rIndex] = eLink[rIndex] = SK_MaxS32;
+ }
+ SkTDArray<double> distances;
+ const int ends = count * 2; // all starts and ends
+ const int entries = (ends - 1) * count; // folded triangle : n * (n - 1) / 2
+ distances.setCount(entries);
+ for (rIndex = 0; rIndex < ends - 1; ++rIndex) {
+ outer = runs[rIndex >> 1];
+ const SkOpContour& oContour = contours[outer];
+ const SkPoint& oPt = rIndex & 1 ? oContour.end() : oContour.start();
+ const int row = rIndex < count - 1 ? rIndex * ends : (ends - rIndex - 2)
+ * ends - rIndex - 1;
+ for (iIndex = rIndex + 1; iIndex < ends; ++iIndex) {
+ int inner = runs[iIndex >> 1];
+ const SkOpContour& iContour = contours[inner];
+ const SkPoint& iPt = iIndex & 1 ? iContour.end() : iContour.start();
+ double dx = iPt.fX - oPt.fX;
+ double dy = iPt.fY - oPt.fY;
+ double dist = dx * dx + dy * dy;
+ distances[row + iIndex] = dist; // oStart distance from iStart
+ }
+ }
+ SkTDArray<int> sortedDist;
+ sortedDist.setCount(entries);
+ for (rIndex = 0; rIndex < entries; ++rIndex) {
+ sortedDist[rIndex] = rIndex;
+ }
+ QSort<SkTDArray<double>, int>(distances, sortedDist.begin(), sortedDist.end() - 1, lessThan);
+ int remaining = count; // number of start/end pairs
+ for (rIndex = 0; rIndex < entries; ++rIndex) {
+ int pair = sortedDist[rIndex];
+ int row = pair / ends;
+ int col = pair - row * ends;
+ int thingOne = row < col ? row : ends - row - 2;
+ int ndxOne = thingOne >> 1;
+ bool endOne = thingOne & 1;
+ int* linkOne = endOne ? eLink.begin() : sLink.begin();
+ if (linkOne[ndxOne] != SK_MaxS32) {
+ continue;
+ }
+ int thingTwo = row < col ? col : ends - row + col - 1;
+ int ndxTwo = thingTwo >> 1;
+ bool endTwo = thingTwo & 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 < count; ++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 {
+ outer = runs[rIndex];
+ const SkOpContour& contour = contours[outer];
+ if (first) {
+ first = false;
+ const SkPoint* startPtr = &contour.start();
+ simple->deferredMove(startPtr[0]);
+ }
+ if (forward) {
+ contour.toPartialForward(simple);
+ } else {
+ contour.toPartialBackward(simple);
+ }
+#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)) {
+ simple->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 < count; ++rIndex) {
+ if (sLink[rIndex] != SK_MaxS32) {
+ break;
+ }
+ }
+ } while (rIndex < count);
+#if DEBUG_ASSEMBLE
+ for (rIndex = 0; rIndex < count; ++rIndex) {
+ SkASSERT(sLink[rIndex] == SK_MaxS32);
+ SkASSERT(eLink[rIndex] == SK_MaxS32);
+ }
+#endif
+}
+
diff --git a/src/pathops/SkPathOpsCommon.h b/src/pathops/SkPathOpsCommon.h
new file mode 100644
index 0000000000..5a468074ff
--- /dev/null
+++ b/src/pathops/SkPathOpsCommon.h
@@ -0,0 +1,29 @@
+/*
+ * 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 "SkOpContour.h"
+
+class SkPathWriter;
+
+void Assemble(const SkPathWriter& path, SkPathWriter* simple);
+SkOpSegment* FindChase(SkTDArray<SkOpSpan*>& chase, int& tIndex, int& endIndex);
+SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour*>& contourList, bool* firstContour,
+ int* index, int* endIndex, SkPoint* topLeft, bool* unsortable,
+ bool* done, bool binary);
+SkOpSegment* FindUndone(SkTDArray<SkOpContour*>& contourList, int* start, int* end);
+void FixOtherTIndex(SkTDArray<SkOpContour*>* contourList);
+void MakeContourList(SkTArray<SkOpContour>& contours, SkTDArray<SkOpContour*>& list,
+ bool evenOdd, bool oppEvenOdd);
+void SortSegments(SkTDArray<SkOpContour*>* contourList);
+
+#if DEBUG_ACTIVE_SPANS
+void DebugShowActiveSpans(SkTDArray<SkOpContour*>& contourList);
+#endif
+
+#endif
diff --git a/src/pathops/SkPathOpsCubic.cpp b/src/pathops/SkPathOpsCubic.cpp
new file mode 100644
index 0000000000..674213c383
--- /dev/null
+++ b/src/pathops/SkPathOpsCubic.cpp
@@ -0,0 +1,463 @@
+/*
+ * 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 "SkLineParameters.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsRect.h"
+
+const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework
+
+// 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;
+}
+
+bool SkDCubic::clockwise() const {
+ double sum = (fPts[0].fX - fPts[3].fX) * (fPts[0].fY + fPts[3].fY);
+ for (int idx = 0; idx < 3; ++idx) {
+ sum += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY);
+ }
+ return sum <= 0;
+}
+
+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::controlsContainedByEnds() const {
+ SkDVector startTan = fPts[1] - fPts[0];
+ if (startTan.fX == 0 && startTan.fY == 0) {
+ startTan = fPts[2] - fPts[0];
+ }
+ SkDVector endTan = fPts[2] - fPts[3];
+ if (endTan.fX == 0 && endTan.fY == 0) {
+ endTan = fPts[1] - fPts[3];
+ }
+ if (startTan.dot(endTan) >= 0) {
+ return false;
+ }
+ SkDLine startEdge = {{fPts[0], fPts[0]}};
+ startEdge[1].fX -= startTan.fY;
+ startEdge[1].fY += startTan.fX;
+ SkDLine endEdge = {{fPts[3], fPts[3]}};
+ endEdge[1].fX -= endTan.fY;
+ endEdge[1].fY += endTan.fX;
+ double leftStart1 = startEdge.isLeft(fPts[1]);
+ if (leftStart1 * startEdge.isLeft(fPts[2]) < 0) {
+ return false;
+ }
+ double leftEnd1 = endEdge.isLeft(fPts[1]);
+ if (leftEnd1 * endEdge.isLeft(fPts[2]) < 0) {
+ return false;
+ }
+ return leftStart1 * leftEnd1 >= 0;
+}
+
+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));
+}
+
+bool SkDCubic::isLinear(int startIndex, int endIndex) const {
+ SkLineParameters lineParameters;
+ lineParameters.cubicEndPoints(*this, startIndex, endIndex);
+ // FIXME: maybe it's possible to avoid this and compare non-normalized
+ lineParameters.normalize();
+ double distance = lineParameters.controlPtDistance(*this, 1);
+ if (!approximately_zero(distance)) {
+ return false;
+ }
+ distance = lineParameters.controlPtDistance(*this, 2);
+ return approximately_zero(distance);
+}
+
+bool SkDCubic::monotonicInY() const {
+ return between(fPts[0].fY, fPts[1].fY, fPts[3].fY)
+ && between(fPts[0].fY, fPts[2].fY, fPts[3].fY);
+}
+
+bool SkDCubic::serpentine() const {
+ if (!controlsContainedByEnds()) {
+ return false;
+ }
+ double wiggle = (fPts[0].fX - fPts[2].fX) * (fPts[0].fY + fPts[2].fY);
+ for (int idx = 0; idx < 2; ++idx) {
+ wiggle += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY);
+ }
+ double waggle = (fPts[1].fX - fPts[3].fX) * (fPts[1].fY + fPts[3].fY);
+ for (int idx = 1; idx < 3; ++idx) {
+ waggle += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY);
+ }
+ return wiggle * waggle < 0;
+}
+
+// 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);
+ 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);
+ mathematica_ize(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 (AlmostEqualUlps(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
+ double theta = acos(R / sqrt(Q3));
+ double neg2RootQ = -2 * sqrt(Q);
+
+ r = neg2RootQ * cos(theta / 3) - adiv3;
+ *roots++ = r;
+
+ r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3;
+ if (!AlmostEqualUlps(s[0], r)) {
+ *roots++ = r;
+ }
+ r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3;
+ if (!AlmostEqualUlps(s[0], r) && (roots - s == 1 || !AlmostEqualUlps(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 (AlmostEqualUlps(R2, Q3)) {
+ r = -A / 2 - adiv3;
+ if (!AlmostEqualUlps(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) };
+ 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(double a, double b, double c, double d, double tValues[2]) {
+ // we divide A,B,C by 3 to simplify
+ 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::top(double startT, double endT) const {
+ SkDCubic sub = subDivide(startT, endT);
+ SkDPoint topPt = sub[0];
+ if (topPt.fY > sub[3].fY || (topPt.fY == sub[3].fY && topPt.fX > sub[3].fX)) {
+ topPt = sub[3];
+ }
+ double extremeTs[2];
+ if (!sub.monotonicInY()) {
+ int roots = FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, sub[3].fY, extremeTs);
+ for (int index = 0; index < roots; ++index) {
+ double t = startT + (endT - startT) * extremeTs[index];
+ SkDPoint mid = xyAtT(t);
+ if (topPt.fY > mid.fY || (topPt.fY == mid.fY && topPt.fX > mid.fX)) {
+ topPt = mid;
+ }
+ }
+ }
+ return topPt;
+}
+
+SkDPoint SkDCubic::xyAtT(double t) const {
+ 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) {
+ return *this;
+ }
+ 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;
+ return dst;
+}
+
+void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d,
+ double t1, double t2, SkDPoint dst[2]) const {
+ 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 mx = ex * 27 - a.fX * 8 - d.fX;
+ double my = ey * 27 - a.fY * 8 - d.fY;
+ double nx = fx * 27 - a.fX - d.fX * 8;
+ double ny = fy * 27 - a.fY - d.fY * 8;
+ /* bx = */ dst[0].fX = (mx * 2 - nx) / 18;
+ /* by = */ dst[0].fY = (my * 2 - ny) / 18;
+ /* cx = */ dst[1].fX = (nx * 2 - mx) / 18;
+ /* cy = */ dst[1].fY = (ny * 2 - my) / 18;
+}
+
+/* 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;
+}
diff --git a/src/pathops/SkPathOpsCubic.h b/src/pathops/SkPathOpsCubic.h
new file mode 100644
index 0000000000..48280fd32f
--- /dev/null
+++ b/src/pathops/SkPathOpsCubic.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 SkPathOpsCubic_DEFINED
+#define SkPathOpsCubic_DEFINED
+
+#include "SkPathOpsPoint.h"
+#include "SkTDArray.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 {
+ SkDPoint fPts[4];
+
+ void set(const SkPoint pts[4]) {
+ fPts[0] = pts[0];
+ fPts[1] = pts[1];
+ fPts[2] = pts[2];
+ fPts[3] = pts[3];
+ }
+
+ static const int gPrecisionUnit;
+
+ const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 4); return fPts[n]; }
+ SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 4); return fPts[n]; }
+
+ double calcPrecision() const;
+ SkDCubicPair chopAt(double t) const;
+ bool clockwise() const;
+ static void Coefficients(const double* cubic, double* A, double* B, double* C, double* D);
+ bool controlsContainedByEnds() const;
+ SkDVector dxdyAtT(double t) const;
+ bool endsAreExtremaInXOrY() const;
+ static int FindExtrema(double a, double b, double c, double d, double tValue[2]);
+ int findInflections(double tValues[]) const;
+ int findMaxCurvature(double tValues[]) const;
+ bool isLinear(int startIndex, int endIndex) const;
+ bool monotonicInY() 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]);
+ bool serpentine() const;
+ SkDCubic subDivide(double t1, double t2) const;
+ static SkDCubic SubDivide(const SkPoint a[4], double t1, double t2) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return cubic.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[4], const SkDPoint& a, const SkDPoint& d, double t1,
+ double t2, SkDPoint p[2]) {
+ SkDCubic cubic;
+ cubic.set(pts);
+ cubic.subDivide(a, d, t1, t2, p);
+ }
+
+ SkDPoint top(double startT, double endT) const;
+ void toQuadraticTs(double precision, SkTDArray<double>* ts) const;
+ SkDQuad toQuad() const;
+ SkDPoint xyAtT(double t) const;
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsCurve.h b/src/pathops/SkPathOpsCurve.h
new file mode 100644
index 0000000000..ca68a664e6
--- /dev/null
+++ b/src/pathops/SkPathOpsCurve.h
@@ -0,0 +1,152 @@
+/*
+ * 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 "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+
+static SkDPoint dline_xy_at_t(const SkPoint a[2], double t) {
+ SkDLine line;
+ line.set(a);
+ return line.xyAtT(t);
+}
+
+static SkDPoint dquad_xy_at_t(const SkPoint a[3], double t) {
+ SkDQuad quad;
+ quad.set(a);
+ return quad.xyAtT(t);
+}
+
+static SkDPoint dcubic_xy_at_t(const SkPoint a[4], double t) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return cubic.xyAtT(t);
+}
+
+static SkDPoint (* const CurveDPointAtT[])(const SkPoint[], double ) = {
+ NULL,
+ dline_xy_at_t,
+ dquad_xy_at_t,
+ dcubic_xy_at_t
+};
+
+static SkPoint fline_xy_at_t(const SkPoint a[2], double t) {
+ return dline_xy_at_t(a, t).asSkPoint();
+}
+
+static SkPoint fquad_xy_at_t(const SkPoint a[3], double t) {
+ return dquad_xy_at_t(a, t).asSkPoint();
+}
+
+static SkPoint fcubic_xy_at_t(const SkPoint a[4], double t) {
+ return dcubic_xy_at_t(a, t).asSkPoint();
+}
+
+static SkPoint (* const CurvePointAtT[])(const SkPoint[], double ) = {
+ NULL,
+ fline_xy_at_t,
+ fquad_xy_at_t,
+ fcubic_xy_at_t
+};
+
+static SkDVector dline_dxdy_at_t(const SkPoint a[2], double ) {
+ SkDLine line;
+ line.set(a);
+ return line[1] - line[0];
+}
+
+static SkDVector dquad_dxdy_at_t(const SkPoint a[3], double t) {
+ SkDQuad quad;
+ quad.set(a);
+ return quad.dxdyAtT(t);
+}
+
+static SkDVector dcubic_dxdy_at_t(const SkPoint a[4], double t) {
+ SkDCubic cubic;
+ cubic.set(a);
+ return cubic.dxdyAtT(t);
+}
+
+static SkDVector (* const CurveDSlopeAtT[])(const SkPoint[], double ) = {
+ NULL,
+ dline_dxdy_at_t,
+ dquad_dxdy_at_t,
+ dcubic_dxdy_at_t
+};
+
+static SkVector fline_dxdy_at_t(const SkPoint a[2], double ) {
+ return a[1] - a[0];
+}
+
+static SkVector fquad_dxdy_at_t(const SkPoint a[3], double t) {
+ return dquad_dxdy_at_t(a, t).asSkVector();
+}
+
+static SkVector fcubic_dxdy_at_t(const SkPoint a[4], double t) {
+ return dcubic_dxdy_at_t(a, t).asSkVector();
+}
+
+static SkVector (* const CurveSlopeAtT[])(const SkPoint[], double ) = {
+ NULL,
+ fline_dxdy_at_t,
+ fquad_dxdy_at_t,
+ fcubic_dxdy_at_t
+};
+
+static SkPoint quad_top(const SkPoint a[3], double startT, double endT) {
+ SkDQuad quad;
+ quad.set(a);
+ SkDPoint topPt = quad.top(startT, endT);
+ return topPt.asSkPoint();
+}
+
+static SkPoint cubic_top(const SkPoint a[4], double startT, double endT) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkDPoint topPt = cubic.top(startT, endT);
+ return topPt.asSkPoint();
+}
+
+static SkPoint (* const CurveTop[])(const SkPoint[], double , double ) = {
+ NULL,
+ NULL,
+ quad_top,
+ cubic_top
+};
+
+static bool line_is_vertical(const SkPoint a[2], double startT, double endT) {
+ SkDLine line;
+ line.set(a);
+ SkDPoint dst[2] = { line.xyAtT(startT), line.xyAtT(endT) };
+ return AlmostEqualUlps(dst[0].fX, dst[1].fX);
+}
+
+static bool quad_is_vertical(const SkPoint a[3], 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 cubic_is_vertical(const SkPoint a[4], 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[], double , double) = {
+ NULL,
+ line_is_vertical,
+ quad_is_vertical,
+ cubic_is_vertical
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp
new file mode 100644
index 0000000000..bef129c1b9
--- /dev/null
+++ b/src/pathops/SkPathOpsDebug.cpp
@@ -0,0 +1,59 @@
+/*
+ * 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 "SkPathOpsDebug.h"
+
+#if defined SK_DEBUG || !FORCE_RELEASE
+
+int gDebugMaxWindSum = SK_MaxS32;
+int gDebugMaxWindValue = SK_MaxS32;
+
+void mathematica_ize(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';
+ }
+}
+
+bool valid_wind(int wind) {
+ return wind > SK_MinS32 + 0xFFFF && wind < SK_MaxS32 - 0xFFFF;
+}
+
+void winding_printf(int wind) {
+ if (wind == SK_MinS32) {
+ SkDebugf("?");
+ } else {
+ SkDebugf("%d", wind);
+ }
+}
+#endif
+
+#if DEBUG_DUMP
+const char* kLVerbStr[] = {"", "line", "quad", "cubic"};
+// static const char* kUVerbStr[] = {"", "Line", "Quad", "Cubic"};
+int gContourID;
+int gSegmentID;
+#endif
+
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+int gDebugSortCountDefault = SK_MaxS32;
+int gDebugSortCount;
+#endif
+
+#if DEBUG_ACTIVE_OP
+const char* kPathOpStr[] = {"diff", "sect", "union", "xor"};
+#endif
diff --git a/src/pathops/SkPathOpsDebug.h b/src/pathops/SkPathOpsDebug.h
new file mode 100644
index 0000000000..16ca176831
--- /dev/null
+++ b/src/pathops/SkPathOpsDebug.h
@@ -0,0 +1,132 @@
+/*
+ * 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 "SkTypes.h"
+
+#ifdef SK_RELEASE
+#define FORCE_RELEASE 1
+#else
+#define FORCE_RELEASE 1 // set force release to 1 for multiple thread -- no debugging
+#endif
+
+#define ONE_OFF_DEBUG 0
+#define ONE_OFF_DEBUG_MATHEMATICA 0
+
+#if defined SK_DEBUG || !FORCE_RELEASE
+
+#ifdef SK_BUILD_FOR_WIN
+ #define SK_RAND(seed) rand()
+ #define SK_SNPRINTF _snprintf
+#else
+ #define SK_RAND(seed) rand_r(&seed)
+ #define SK_SNPRINTF snprintf
+#endif
+
+void mathematica_ize(char* str, size_t bufferSize);
+bool valid_wind(int winding);
+void winding_printf(int winding);
+
+extern int gDebugMaxWindSum;
+extern int gDebugMaxWindValue;
+#endif
+
+#if FORCE_RELEASE
+
+#define DEBUG_ACTIVE_OP 0
+#define DEBUG_ACTIVE_SPANS 0
+#define DEBUG_ACTIVE_SPANS_SHORT_FORM 0
+#define DEBUG_ADD_INTERSECTING_TS 0
+#define DEBUG_ADD_T_PAIR 0
+#define DEBUG_ANGLE 0
+#define DEBUG_AS_C_CODE 1
+#define DEBUG_ASSEMBLE 0
+#define DEBUG_CONCIDENT 0
+#define DEBUG_CROSS 0
+#define DEBUG_FLAT_QUADS 0
+#define DEBUG_FLOW 0
+#define DEBUG_MARK_DONE 0
+#define DEBUG_PATH_CONSTRUCTION 0
+#define DEBUG_SHOW_TEST_PROGRESS 0
+#define DEBUG_SHOW_WINDING 0
+#define DEBUG_SORT 0
+#define DEBUG_SWAP_TOP 0
+#define DEBUG_UNSORTABLE 0
+#define DEBUG_WIND_BUMP 0
+#define DEBUG_WINDING 0
+#define DEBUG_WINDING_AT_T 0
+
+#else
+
+#define DEBUG_ACTIVE_OP 1
+#define DEBUG_ACTIVE_SPANS 1
+#define DEBUG_ACTIVE_SPANS_SHORT_FORM 0
+#define DEBUG_ADD_INTERSECTING_TS 1
+#define DEBUG_ADD_T_PAIR 1
+#define DEBUG_ANGLE 1
+#define DEBUG_AS_C_CODE 1
+#define DEBUG_ASSEMBLE 1
+#define DEBUG_CONCIDENT 1
+#define DEBUG_CROSS 0
+#define DEBUG_FLAT_QUADS 0
+#define DEBUG_FLOW 1
+#define DEBUG_MARK_DONE 1
+#define DEBUG_PATH_CONSTRUCTION 1
+#define DEBUG_SHOW_TEST_PROGRESS 1
+#define DEBUG_SHOW_WINDING 0
+#define DEBUG_SORT 1
+#define DEBUG_SWAP_TOP 1
+#define DEBUG_UNSORTABLE 1
+#define DEBUG_WIND_BUMP 0
+#define DEBUG_WINDING 1
+#define DEBUG_WINDING_AT_T 1
+
+#endif
+
+#define DEBUG_DUMP (DEBUG_ACTIVE_OP | DEBUG_ACTIVE_SPANS | DEBUG_CONCIDENT | DEBUG_SORT | \
+ DEBUG_PATH_CONSTRUCTION)
+
+#if DEBUG_AS_C_CODE
+#define CUBIC_DEBUG_STR "{{%1.17g,%1.17g}, {%1.17g,%1.17g}, {%1.17g,%1.17g}, {%1.17g,%1.17g}}"
+#define QUAD_DEBUG_STR "{{%1.17g,%1.17g}, {%1.17g,%1.17g}, {%1.17g,%1.17g}}"
+#define LINE_DEBUG_STR "{{%1.17g,%1.17g}, {%1.17g,%1.17g}}"
+#define PT_DEBUG_STR "{{%1.17g,%1.17g}}"
+#else
+#define CUBIC_DEBUG_STR "(%1.9g,%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)"
+#endif
+#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 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).fX, i.pt(n).fY
+
+#if DEBUG_DUMP
+extern const char* kLVerbStr[];
+// extern const char* kUVerbStr[];
+extern int gContourID;
+extern int gSegmentID;
+#endif
+
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+extern int gDebugSortCountDefault;
+extern int gDebugSortCount;
+#endif
+
+#if DEBUG_ACTIVE_OP
+extern const char* kPathOpStr[];
+#endif
+
+#ifndef DEBUG_TEST
+#define DEBUG_TEST 0
+#endif
+
+#endif
diff --git a/src/pathops/SkPathOpsLine.cpp b/src/pathops/SkPathOpsLine.cpp
new file mode 100644
index 0000000000..b7c91c991d
--- /dev/null
+++ b/src/pathops/SkPathOpsLine.cpp
@@ -0,0 +1,48 @@
+/*
+ * 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"
+
+SkDLine SkDLine::subDivide(double t1, double t2) const {
+ SkDVector delta = tangent();
+ SkDLine dst = {{{
+ fPts[0].fX - t1 * delta.fX, fPts[0].fY - t1 * delta.fY}, {
+ fPts[0].fX - t2 * delta.fX, fPts[0].fY - t2 * delta.fY}}};
+ return dst;
+}
+
+// may have this below somewhere else already:
+// copying here because I thought it was clever
+
+// Copyright 2001, softSurfer (www.softsurfer.com)
+// This code may be freely used and modified for any purpose
+// providing that this copyright notice is included with it.
+// SoftSurfer makes no warranty for this code, and cannot be held
+// liable for any real or imagined damage resulting from its use.
+// Users of this code must verify correctness for their application.
+
+// Assume that a class is already given for the object:
+// Point with coordinates {float x, y;}
+//===================================================================
+
+// isLeft(): tests if a point is Left|On|Right of an infinite line.
+// Input: three points P0, P1, and P2
+// Return: >0 for P2 left of the line through P0 and P1
+// =0 for P2 on the line
+// <0 for P2 right of the line
+// See: the January 2001 Algorithm on Area of Triangles
+// return (float) ((P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y));
+double SkDLine::isLeft(const SkDPoint& pt) const {
+ SkDVector p0 = fPts[1] - fPts[0];
+ SkDVector p2 = pt - fPts[0];
+ return p0.cross(p2);
+}
+
+SkDPoint SkDLine::xyAtT(double t) const {
+ 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;
+}
diff --git a/src/pathops/SkPathOpsLine.h b/src/pathops/SkPathOpsLine.h
new file mode 100644
index 0000000000..34bb6587d3
--- /dev/null
+++ b/src/pathops/SkPathOpsLine.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 SkPathOpsLine_DEFINED
+#define SkPathOpsLine_DEFINED
+
+#include "SkPathOpsPoint.h"
+
+struct SkDLine {
+ SkDPoint fPts[2];
+
+ void set(const SkPoint pts[2]) {
+ fPts[0] = pts[0];
+ fPts[1] = pts[1];
+ }
+
+ 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]; }
+
+ double isLeft(const SkDPoint& pt) const;
+ SkDLine subDivide(double t1, double t2) const;
+ static SkDLine SubDivide(const SkPoint a[2], double t1, double t2) {
+ SkDLine line;
+ line.set(a);
+ return line.subDivide(t1, t2);
+ }
+ SkDPoint xyAtT(double t) const;
+private:
+ SkDVector tangent() const { return fPts[0] - fPts[1]; }
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsOp.cpp b/src/pathops/SkPathOpsOp.cpp
new file mode 100644
index 0000000000..5a3576ff6c
--- /dev/null
+++ b/src/pathops/SkPathOpsOp.cpp
@@ -0,0 +1,272 @@
+/*
+ * 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 "SkOpEdgeBuilder.h"
+#include "SkPathOpsCommon.h"
+#include "SkPathWriter.h"
+
+// FIXME: this and find chase should be merge together, along with
+// other code that walks winding in angles
+// OPTIMIZATION: Probably, the walked winding should be rolled into the angle structure
+// so it isn't duplicated by walkers like this one
+static SkOpSegment* findChaseOp(SkTDArray<SkOpSpan*>& chase, int& nextStart, int& nextEnd) {
+ while (chase.count()) {
+ SkOpSpan* span;
+ chase.pop(&span);
+ const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex);
+ SkOpSegment* segment = backPtr.fOther;
+ nextStart = backPtr.fOtherIndex;
+ SkTDArray<SkOpAngle> angles;
+ int done = 0;
+ if (segment->activeAngle(nextStart, &done, &angles)) {
+ SkOpAngle* last = angles.end() - 1;
+ nextStart = last->start();
+ nextEnd = last->end();
+ #if TRY_ROTATE
+ *chase.insert(0) = span;
+ #else
+ *chase.append() = span;
+ #endif
+ return last->segment();
+ }
+ if (done == angles.count()) {
+ continue;
+ }
+ SkTDArray<SkOpAngle*> sorted;
+ bool sortable = SkOpSegment::SortAngles(angles, &sorted);
+ int angleCount = sorted.count();
+#if DEBUG_SORT
+ sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0);
+#endif
+ if (!sortable) {
+ continue;
+ }
+ // find first angle, initialize winding to computed fWindSum
+ int firstIndex = -1;
+ const SkOpAngle* angle;
+ do {
+ angle = sorted[++firstIndex];
+ segment = angle->segment();
+ } while (segment->windSum(angle) == SK_MinS32);
+ #if DEBUG_SORT
+ segment->debugShowSort(__FUNCTION__, sorted, firstIndex);
+ #endif
+ int sumMiWinding = segment->updateWindingReverse(angle);
+ int sumSuWinding = segment->updateOppWindingReverse(angle);
+ if (segment->operand()) {
+ SkTSwap<int>(sumMiWinding, sumSuWinding);
+ }
+ int nextIndex = firstIndex + 1;
+ int lastIndex = firstIndex != 0 ? firstIndex : angleCount;
+ SkOpSegment* first = NULL;
+ do {
+ SkASSERT(nextIndex != firstIndex);
+ if (nextIndex == angleCount) {
+ nextIndex = 0;
+ }
+ angle = sorted[nextIndex];
+ segment = angle->segment();
+ int start = angle->start();
+ int end = angle->end();
+ int maxWinding, sumWinding, oppMaxWinding, oppSumWinding;
+ segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding,
+ &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding);
+ if (!segment->done(angle)) {
+ if (!first) {
+ first = segment;
+ nextStart = start;
+ nextEnd = end;
+ }
+ (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding,
+ oppSumWinding, true, angle);
+ }
+ } while (++nextIndex != lastIndex);
+ if (first) {
+ #if TRY_ROTATE
+ *chase.insert(0) = span;
+ #else
+ *chase.append() = span;
+ #endif
+ return first;
+ }
+ }
+ return NULL;
+}
+
+/*
+static bool windingIsActive(int winding, int oppWinding, int spanWinding, int oppSpanWinding,
+ bool windingIsOp, PathOp op) {
+ bool active = windingIsActive(winding, spanWinding);
+ if (!active) {
+ return false;
+ }
+ if (oppSpanWinding && windingIsActive(oppWinding, oppSpanWinding)) {
+ switch (op) {
+ case kIntersect_Op:
+ case kUnion_Op:
+ return true;
+ case kDifference_Op: {
+ int absSpan = abs(spanWinding);
+ int absOpp = abs(oppSpanWinding);
+ return windingIsOp ? absSpan < absOpp : absSpan > absOpp;
+ }
+ case kXor_Op:
+ return spanWinding != oppSpanWinding;
+ default:
+ SkASSERT(0);
+ }
+ }
+ bool opActive = oppWinding != 0;
+ return gOpLookup[op][opActive][windingIsOp];
+}
+*/
+
+static bool bridgeOp(SkTDArray<SkOpContour*>& contourList, const SkPathOp op,
+ const int xorMask, const int xorOpMask, SkPathWriter* simple) {
+ bool firstContour = true;
+ bool unsortable = false;
+ bool topUnsortable = false;
+ SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
+ do {
+ int index, endIndex;
+ bool done;
+ SkOpSegment* current = FindSortableTop(contourList, &firstContour, &index, &endIndex,
+ &topLeft, &topUnsortable, &done, true);
+ if (!current) {
+ if (topUnsortable || !done) {
+ topUnsortable = false;
+ SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin);
+ topLeft.fX = topLeft.fY = SK_ScalarMin;
+ continue;
+ }
+ break;
+ }
+ SkTDArray<SkOpSpan*> chaseArray;
+ do {
+ if (current->activeOp(index, endIndex, xorMask, xorOpMask, op)) {
+ do {
+ #if DEBUG_ACTIVE_SPANS
+ if (!unsortable && current->done()) {
+ DebugShowActiveSpans(contourList);
+ }
+ #endif
+ SkASSERT(unsortable || !current->done());
+ int nextStart = index;
+ int nextEnd = endIndex;
+ SkOpSegment* next = current->findNextOp(&chaseArray, &nextStart, &nextEnd,
+ &unsortable, op, xorMask, xorOpMask);
+ if (!next) {
+ if (!unsortable && simple->hasMove()
+ && current->verb() != SkPath::kLine_Verb
+ && !simple->isClosed()) {
+ current->addCurveTo(index, endIndex, simple, true);
+ SkASSERT(simple->isClosed());
+ }
+ break;
+ }
+ #if DEBUG_FLOW
+ SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__,
+ current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY,
+ current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY);
+ #endif
+ current->addCurveTo(index, endIndex, simple, true);
+ current = next;
+ index = nextStart;
+ endIndex = nextEnd;
+ } while (!simple->isClosed() && ((!unsortable)
+ || !current->done(SkMin32(index, endIndex))));
+ if (current->activeWinding(index, endIndex) && !simple->isClosed()) {
+ SkASSERT(unsortable);
+ int min = SkMin32(index, endIndex);
+ if (!current->done(min)) {
+ current->addCurveTo(index, endIndex, simple, true);
+ current->markDoneBinary(min);
+ }
+ }
+ simple->close();
+ } else {
+ SkOpSpan* last = current->markAndChaseDoneBinary(index, endIndex);
+ if (last && !last->fLoop) {
+ *chaseArray.append() = last;
+ }
+ }
+ current = findChaseOp(chaseArray, index, endIndex);
+ #if DEBUG_ACTIVE_SPANS
+ DebugShowActiveSpans(contourList);
+ #endif
+ if (!current) {
+ break;
+ }
+ } while (true);
+ } while (true);
+ return simple->someAssemblyRequired();
+}
+
+void Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) {
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+ gDebugSortCount = gDebugSortCountDefault;
+#endif
+ result->reset();
+ result->setFillType(SkPath::kEvenOdd_FillType);
+ // turn path into list of segments
+ SkTArray<SkOpContour> contours;
+ // FIXME: add self-intersecting cubics' T values to segment
+ SkOpEdgeBuilder builder(one, contours);
+ const int xorMask = builder.xorMask();
+ builder.addOperand(two);
+ builder.finish();
+ const int xorOpMask = builder.xorMask();
+ SkTDArray<SkOpContour*> contourList;
+ MakeContourList(contours, contourList, xorMask == kEvenOdd_PathOpsMask,
+ xorOpMask == kEvenOdd_PathOpsMask);
+ SkOpContour** currentPtr = contourList.begin();
+ if (!currentPtr) {
+ return;
+ }
+ SkOpContour** listEnd = contourList.end();
+ // find all intersections between segments
+ do {
+ SkOpContour** nextPtr = currentPtr;
+ SkOpContour* current = *currentPtr++;
+ if (current->containsCubics()) {
+ AddSelfIntersectTs(current);
+ }
+ SkOpContour* next;
+ do {
+ next = *nextPtr++;
+ } while (AddIntersectTs(current, next) && nextPtr != listEnd);
+ } while (currentPtr != listEnd);
+ // eat through coincident edges
+
+ int total = 0;
+ int index;
+ for (index = 0; index < contourList.count(); ++index) {
+ total += contourList[index]->segments().count();
+ }
+#if DEBUG_SHOW_WINDING
+ SkOpContour::debugShowWindingValues(contourList);
+#endif
+ CoincidenceCheck(&contourList, total);
+#if DEBUG_SHOW_WINDING
+ SkOpContour::debugShowWindingValues(contourList);
+#endif
+ FixOtherTIndex(&contourList);
+ SortSegments(&contourList);
+#if DEBUG_ACTIVE_SPANS
+ DebugShowActiveSpans(contourList);
+#endif
+ // construct closed contours
+ SkPathWriter wrapper(*result);
+ bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper);
+ { // if some edges could not be resolved, assemble remaining fragments
+ SkPath temp;
+ temp.setFillType(SkPath::kEvenOdd_FillType);
+ SkPathWriter assembled(temp);
+ Assemble(wrapper, &assembled);
+ *result = *assembled.nativePath();
+ }
+}
diff --git a/src/pathops/SkPathOpsPoint.cpp b/src/pathops/SkPathOpsPoint.cpp
new file mode 100644
index 0000000000..dc9cde55a3
--- /dev/null
+++ b/src/pathops/SkPathOpsPoint.cpp
@@ -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.
+ */
+#include "SkPathOpsPoint.h"
+
+SkDVector operator-(const SkDPoint& a, const SkDPoint& b) {
+ SkDVector v = {a.fX - b.fX, a.fY - b.fY};
+ return v;
+}
+
+SkDPoint operator+(const SkDPoint& a, const SkDVector& b) {
+ SkDPoint v = {a.fX + b.fX, a.fY + b.fY};
+ return v;
+}
diff --git a/src/pathops/SkPathOpsPoint.h b/src/pathops/SkPathOpsPoint.h
new file mode 100644
index 0000000000..38051005ea
--- /dev/null
+++ b/src/pathops/SkPathOpsPoint.h
@@ -0,0 +1,159 @@
+/*
+ * 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"
+
+struct SkDVector {
+ double fX, fY;
+
+ friend SkDPoint operator+(const SkDPoint& a, const SkDVector& b);
+
+ void operator+=(const SkDVector& v) {
+ fX += v.fX;
+ fY += v.fY;
+ }
+
+ void operator-=(const SkDVector& v) {
+ fX -= v.fX;
+ fY -= v.fY;
+ }
+
+ void operator/=(const double s) {
+ fX /= s;
+ fY /= s;
+ }
+
+ void operator*=(const double s) {
+ fX *= s;
+ fY *= s;
+ }
+
+ SkVector asSkVector() const {
+ SkVector v = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)};
+ return v;
+ }
+
+ double cross(const SkDVector& a) const {
+ return fX * a.fY - fY * a.fX;
+ }
+
+ 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;
+ }
+};
+
+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;
+ }
+
+
+ void operator+=(const SkDVector& v) {
+ fX += v.fX;
+ fY += v.fY;
+ }
+
+ void operator-=(const SkDVector& v) {
+ fX -= v.fX;
+ fY -= v.fY;
+ }
+
+ // 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
+ bool approximatelyEqual(const SkDPoint& a) const {
+ double denom = SkTMax<double>(fabs(fX), SkTMax<double>(fabs(fY),
+ SkTMax<double>(fabs(a.fX), fabs(a.fY))));
+ if (denom == 0) {
+ return true;
+ }
+ double inv = 1 / denom;
+ return approximately_equal(fX * inv, a.fX * inv)
+ && approximately_equal(fY * inv, a.fY * inv);
+ }
+
+ bool approximatelyEqual(const SkPoint& a) const {
+ double denom = SkTMax<double>(fabs(fX), SkTMax<double>(fabs(fY),
+ SkScalarToDouble(SkTMax<SkScalar>(fabs(a.fX), fabs(a.fY)))));
+ if (denom == 0) {
+ return true;
+ }
+ double inv = 1 / denom;
+ return approximately_equal(fX * inv, a.fX * inv)
+ && approximately_equal(fY * inv, a.fY * inv);
+ }
+
+ bool approximatelyEqualHalf(const SkDPoint& a) const {
+ double denom = SkTMax<double>(fabs(fX), SkTMax<double>(fabs(fY),
+ SkTMax<double>(fabs(a.fX), fabs(a.fY))));
+ if (denom == 0) {
+ return true;
+ }
+ double inv = 1 / denom;
+ return approximately_equal_half(fX * inv, a.fX * inv)
+ && approximately_equal_half(fY * inv, a.fY * inv);
+ }
+
+ 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();
+ }
+
+ double moreRoughlyEqual(const SkDPoint& a) const {
+ return more_roughly_equal(a.fY, fY) && more_roughly_equal(a.fX, fX);
+ }
+
+ double roughlyEqual(const SkDPoint& a) const {
+ return roughly_equal(a.fY, fY) && roughly_equal(a.fX, fX);
+ }
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsQuad.cpp b/src/pathops/SkPathOpsQuad.cpp
new file mode 100644
index 0000000000..cbba2a3fe5
--- /dev/null
+++ b/src/pathops/SkPathOpsQuad.cpp
@@ -0,0 +1,293 @@
+/*
+ * 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 "SkLineParameters.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsTriangle.h"
+
+// from http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html
+// (currently only used by testing)
+double SkDQuad::nearestT(const SkDPoint& pt) const {
+ SkDVector pos = fPts[0] - pt;
+ // search points P of bezier curve with PM.(dP / dt) = 0
+ // a calculus leads to a 3d degree equation :
+ SkDVector A = fPts[1] - fPts[0];
+ SkDVector B = fPts[2] - fPts[1];
+ B -= A;
+ double a = B.dot(B);
+ double b = 3 * A.dot(B);
+ double c = 2 * A.dot(A) + pos.dot(B);
+ double d = pos.dot(A);
+ double ts[3];
+ int roots = SkDCubic::RootsValidT(a, b, c, d, ts);
+ double d0 = pt.distanceSquared(fPts[0]);
+ double d2 = pt.distanceSquared(fPts[2]);
+ double distMin = SkTMin<double>(d0, d2);
+ int bestIndex = -1;
+ for (int index = 0; index < roots; ++index) {
+ SkDPoint onQuad = xyAtT(ts[index]);
+ double dist = pt.distanceSquared(onQuad);
+ if (distMin > dist) {
+ distMin = dist;
+ bestIndex = index;
+ }
+ }
+ if (bestIndex >= 0) {
+ return ts[bestIndex];
+ }
+ return d0 < d2 ? 0 : 1;
+}
+
+bool SkDQuad::pointInHull(const SkDPoint& pt) const {
+ return ((const SkDTriangle&) fPts).contains(pt);
+}
+
+SkDPoint SkDQuad::top(double startT, double endT) const {
+ SkDQuad sub = subDivide(startT, endT);
+ SkDPoint topPt = sub[0];
+ if (topPt.fY > sub[2].fY || (topPt.fY == sub[2].fY && topPt.fX > sub[2].fX)) {
+ topPt = sub[2];
+ }
+ if (!between(sub[0].fY, sub[1].fY, sub[2].fY)) {
+ double extremeT;
+ if (FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, &extremeT)) {
+ extremeT = startT + (endT - startT) * extremeT;
+ SkDPoint test = xyAtT(extremeT);
+ if (topPt.fY > test.fY || (topPt.fY == test.fY && topPt.fX > test.fX)) {
+ topPt = test;
+ }
+ }
+ }
+ return topPt;
+}
+
+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 (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 (!AlmostEqualUlps(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 + !AlmostEqualUlps(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);
+ return approximately_zero(distance);
+}
+
+SkDCubic SkDQuad::toCubic() 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;
+}
+
+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 };
+ return result;
+}
+
+SkDPoint SkDQuad::xyAtT(double t) const {
+ 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;
+}
+
+/*
+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
+*/
+
+static double interp_quad_coords(const double* src, double t) {
+ 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::monotonicInY() const {
+ return between(fPts[0].fY, fPts[1].fY, fPts[2].fY);
+}
+
+SkDQuad SkDQuad::subDivide(double t1, double t2) const {
+ 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;
+}
+
+SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const {
+ SkDPoint b;
+ double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2);
+ double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2);
+ b.fX = 2 * dx - (a.fX + c.fX) / 2;
+ b.fY = 2 * dy - (a.fY + c.fY) / 2;
+ 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(double a, double b, double c, double tValue[1]) {
+ /* At + B == 0
+ t = -B / A
+ */
+ 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/src/pathops/SkPathOpsQuad.h b/src/pathops/SkPathOpsQuad.h
new file mode 100644
index 0000000000..d5ca0c7443
--- /dev/null
+++ b/src/pathops/SkPathOpsQuad.h
@@ -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.
+ */
+
+#ifndef SkPathOpsQuad_DEFINED
+#define SkPathOpsQuad_DEFINED
+
+#include "SkPathOpsPoint.h"
+
+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 {
+ SkDPoint fPts[3];
+
+ void set(const SkPoint pts[3]) {
+ fPts[0] = pts[0];
+ fPts[1] = pts[1];
+ fPts[2] = pts[2];
+ }
+
+ const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 3); return fPts[n]; }
+ SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 3); return fPts[n]; }
+
+ static int AddValidTs(double s[], int realRoots, double* t);
+ SkDQuadPair chopAt(double t) const;
+ SkDVector dxdyAtT(double t) const;
+ static int FindExtrema(double a, double b, double c, double tValue[1]);
+ bool isLinear(int startIndex, int endIndex) const;
+ bool monotonicInY() const;
+ double nearestT(const SkDPoint&) const;
+ bool pointInHull(const SkDPoint&) 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[3], 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[3], const SkDPoint& a, const SkDPoint& c,
+ double t1, double t2) {
+ SkDQuad quad;
+ quad.set(pts);
+ return quad.subDivide(a, c, t1, t2);
+ }
+ SkDCubic toCubic() const;
+ SkDPoint top(double startT, double endT) const;
+ SkDPoint xyAtT(double t) const;
+private:
+// static double Tangent(const double* quadratic, double t); // uncalled
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsRect.cpp b/src/pathops/SkPathOpsRect.cpp
new file mode 100644
index 0000000000..9d7f876cac
--- /dev/null
+++ b/src/pathops/SkPathOpsRect.cpp
@@ -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.
+ */
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsRect.h"
+
+void SkDRect::setBounds(const SkDLine& line) {
+ set(line[0]);
+ add(line[1]);
+}
+
+void SkDRect::setBounds(const SkDQuad& quad) {
+ set(quad[0]);
+ add(quad[2]);
+ double tValues[2];
+ int roots = 0;
+ if (!between(quad[0].fX, quad[1].fX, quad[2].fX)) {
+ roots = SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, tValues);
+ }
+ if (!between(quad[0].fY, quad[1].fY, quad[2].fY)) {
+ roots += SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValues[roots]);
+ }
+ for (int x = 0; x < roots; ++x) {
+ add(quad.xyAtT(tValues[x]));
+ }
+}
+
+void SkDRect::setRawBounds(const SkDQuad& quad) {
+ set(quad[0]);
+ for (int x = 1; x < 3; ++x) {
+ add(quad[x]);
+ }
+}
+
+static bool is_bounded_by_end_points(double a, double b, double c, double d) {
+ return between(a, b, d) && between(a, c, d);
+}
+
+void SkDRect::setBounds(const SkDCubic& c) {
+ set(c[0]);
+ add(c[3]);
+ double tValues[4];
+ int roots = 0;
+ if (!is_bounded_by_end_points(c[0].fX, c[1].fX, c[2].fX, c[3].fX)) {
+ roots = SkDCubic::FindExtrema(c[0].fX, c[1].fX, c[2].fX, c[3].fX, tValues);
+ }
+ if (!is_bounded_by_end_points(c[0].fY, c[1].fY, c[2].fY, c[3].fY)) {
+ roots += SkDCubic::FindExtrema(c[0].fY, c[1].fY, c[2].fY, c[3].fY, &tValues[roots]);
+ }
+ for (int x = 0; x < roots; ++x) {
+ add(c.xyAtT(tValues[x]));
+ }
+}
+
+void SkDRect::setRawBounds(const SkDCubic& cubic) {
+ set(cubic[0]);
+ for (int x = 1; x < 4; ++x) {
+ add(cubic[x]);
+ }
+}
diff --git a/src/pathops/SkPathOpsRect.h b/src/pathops/SkPathOpsRect.h
new file mode 100644
index 0000000000..7b516a40f5
--- /dev/null
+++ b/src/pathops/SkPathOpsRect.h
@@ -0,0 +1,56 @@
+/*
+ * 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) {
+ if (fLeft > pt.fX) {
+ fLeft = pt.fX;
+ }
+ if (fTop > pt.fY) {
+ fTop = pt.fY;
+ }
+ if (fRight < pt.fX) {
+ fRight = pt.fX;
+ }
+ if (fBottom < pt.fY) {
+ fBottom = pt.fY;
+ }
+ }
+
+ // FIXME: used by debugging only ?
+ bool contains(const SkDPoint& pt) const {
+ return approximately_between(fLeft, pt.fX, fRight)
+ && approximately_between(fTop, pt.fY, fBottom);
+ }
+
+ bool intersects(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;
+ }
+
+ void setBounds(const SkDLine&);
+ void setBounds(const SkDCubic&);
+ void setBounds(const SkDQuad&);
+ void setRawBounds(const SkDCubic&);
+ void setRawBounds(const SkDQuad&);
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsSimplify.cpp b/src/pathops/SkPathOpsSimplify.cpp
new file mode 100644
index 0000000000..2213c68334
--- /dev/null
+++ b/src/pathops/SkPathOpsSimplify.cpp
@@ -0,0 +1,195 @@
+/*
+ * 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 "SkOpEdgeBuilder.h"
+#include "SkPathOpsCommon.h"
+#include "SkPathWriter.h"
+
+static bool bridgeWinding(SkTDArray<SkOpContour*>& contourList, SkPathWriter* simple) {
+ bool firstContour = true;
+ bool unsortable = false;
+ bool topUnsortable = false;
+ SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin};
+ do {
+ int index, endIndex;
+ bool topDone;
+ SkOpSegment* current = FindSortableTop(contourList, &firstContour, &index, &endIndex,
+ &topLeft, &topUnsortable, &topDone, false);
+ if (!current) {
+ if (topUnsortable || !topDone) {
+ topUnsortable = false;
+ SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin);
+ topLeft.fX = topLeft.fY = SK_ScalarMin;
+ continue;
+ }
+ break;
+ }
+ SkTDArray<SkOpSpan*> chaseArray;
+ do {
+ if (current->activeWinding(index, endIndex)) {
+ do {
+ #if DEBUG_ACTIVE_SPANS
+ if (!unsortable && current->done()) {
+ DebugShowActiveSpans(contourList);
+ }
+ #endif
+ SkASSERT(unsortable || !current->done());
+ int nextStart = index;
+ int nextEnd = endIndex;
+ SkOpSegment* next = current->findNextWinding(&chaseArray, &nextStart, &nextEnd,
+ &unsortable);
+ if (!next) {
+ if (!unsortable && simple->hasMove()
+ && current->verb() != SkPath::kLine_Verb
+ && !simple->isClosed()) {
+ current->addCurveTo(index, endIndex, simple, true);
+ SkASSERT(simple->isClosed());
+ }
+ break;
+ }
+ #if DEBUG_FLOW
+ SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__,
+ current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY,
+ current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY);
+ #endif
+ current->addCurveTo(index, endIndex, simple, true);
+ current = next;
+ index = nextStart;
+ endIndex = nextEnd;
+ } while (!simple->isClosed() && (!unsortable
+ || !current->done(SkMin32(index, endIndex))));
+ if (current->activeWinding(index, endIndex) && !simple->isClosed()) {
+ SkASSERT(unsortable);
+ int min = SkMin32(index, endIndex);
+ if (!current->done(min)) {
+ current->addCurveTo(index, endIndex, simple, true);
+ current->markDoneUnary(min);
+ }
+ }
+ simple->close();
+ } else {
+ SkOpSpan* last = current->markAndChaseDoneUnary(index, endIndex);
+ if (last && !last->fLoop) {
+ *chaseArray.append() = last;
+ }
+ }
+ current = FindChase(chaseArray, index, endIndex);
+ #if DEBUG_ACTIVE_SPANS
+ DebugShowActiveSpans(contourList);
+ #endif
+ if (!current) {
+ break;
+ }
+ } while (true);
+ } while (true);
+ return simple->someAssemblyRequired();
+}
+
+// returns true if all edges were processed
+static bool bridgeXor(SkTDArray<SkOpContour*>& contourList, SkPathWriter* simple) {
+ SkOpSegment* current;
+ int start, end;
+ bool unsortable = false;
+ bool closable = true;
+ while ((current = FindUndone(contourList, &start, &end))) {
+ do {
+ #if DEBUG_ACTIVE_SPANS
+ if (!unsortable && current->done()) {
+ DebugShowActiveSpans(contourList);
+ }
+ #endif
+ SkASSERT(unsortable || !current->done());
+ int nextStart = start;
+ int nextEnd = end;
+ SkOpSegment* next = current->findNextXor(&nextStart, &nextEnd, &unsortable);
+ if (!next) {
+ if (!unsortable && simple->hasMove()
+ && current->verb() != SkPath::kLine_Verb
+ && !simple->isClosed()) {
+ current->addCurveTo(start, end, simple, true);
+ SkASSERT(simple->isClosed());
+ }
+ break;
+ }
+ #if DEBUG_FLOW
+ SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__,
+ current->debugID(), current->xyAtT(start).fX, current->xyAtT(start).fY,
+ current->xyAtT(end).fX, current->xyAtT(end).fY);
+ #endif
+ current->addCurveTo(start, end, simple, true);
+ current = next;
+ start = nextStart;
+ end = nextEnd;
+ } while (!simple->isClosed() && (!unsortable || !current->done(SkMin32(start, end))));
+ if (!simple->isClosed()) {
+ SkASSERT(unsortable);
+ int min = SkMin32(start, end);
+ if (!current->done(min)) {
+ current->addCurveTo(start, end, simple, true);
+ current->markDone(min, 1);
+ }
+ closable = false;
+ }
+ simple->close();
+ #if DEBUG_ACTIVE_SPANS
+ DebugShowActiveSpans(contourList);
+ #endif
+ }
+ return closable;
+}
+
+// FIXME : add this as a member of SkPath
+void Simplify(const SkPath& path, SkPath* result) {
+#if DEBUG_SORT || DEBUG_SWAP_TOP
+ gDebugSortCount = gDebugSortCountDefault;
+#endif
+ // returns 1 for evenodd, -1 for winding, regardless of inverse-ness
+ result->reset();
+ result->setFillType(SkPath::kEvenOdd_FillType);
+ SkPathWriter simple(*result);
+
+ // turn path into list of segments
+ SkTArray<SkOpContour> contours;
+ SkOpEdgeBuilder builder(path, contours);
+ builder.finish();
+ SkTDArray<SkOpContour*> contourList;
+ MakeContourList(contours, contourList, false, false);
+ SkOpContour** currentPtr = contourList.begin();
+ if (!currentPtr) {
+ return;
+ }
+ SkOpContour** listEnd = contourList.end();
+ // find all intersections between segments
+ do {
+ SkOpContour** nextPtr = currentPtr;
+ SkOpContour* current = *currentPtr++;
+ if (current->containsCubics()) {
+ AddSelfIntersectTs(current);
+ }
+ SkOpContour* next;
+ do {
+ next = *nextPtr++;
+ } while (AddIntersectTs(current, next) && nextPtr != listEnd);
+ } while (currentPtr != listEnd);
+ // eat through coincident edges
+ CoincidenceCheck(&contourList, 0);
+ FixOtherTIndex(&contourList);
+ SortSegments(&contourList);
+#if DEBUG_ACTIVE_SPANS
+ DebugShowActiveSpans(contourList);
+#endif
+ // construct closed contours
+ if (builder.xorMask() == kWinding_PathOpsMask ? bridgeWinding(contourList, &simple)
+ : !bridgeXor(contourList, &simple))
+ { // if some edges could not be resolved, assemble remaining fragments
+ SkPath temp;
+ temp.setFillType(SkPath::kEvenOdd_FillType);
+ SkPathWriter assembled(temp);
+ Assemble(simple, &assembled);
+ *result = *assembled.nativePath();
+ }
+}
diff --git a/src/pathops/SkPathOpsSpan.h b/src/pathops/SkPathOpsSpan.h
new file mode 100644
index 0000000000..33965922ae
--- /dev/null
+++ b/src/pathops/SkPathOpsSpan.h
@@ -0,0 +1,31 @@
+/*
+ * 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 "SkPoint.h"
+
+class SkOpSegment;
+
+struct SkOpSpan {
+ SkOpSegment* fOther;
+ SkPoint fPt; // computed when the curves are intersected
+ double fT;
+ double fOtherT; // value at fOther[fOtherIndex].fT
+ int fOtherIndex; // can't be used during intersection
+ 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
+ bool fDone; // if set, this span to next higher T has been processed
+ bool fUnsortableStart; // set when start is part of an unsortable pair
+ bool fUnsortableEnd; // set when end is part of an unsortable pair
+ bool fTiny; // if set, span may still be considered once for edge following
+ bool fLoop; // set when a cubic loops back to this point
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsTriangle.cpp b/src/pathops/SkPathOpsTriangle.cpp
new file mode 100644
index 0000000000..49391667ac
--- /dev/null
+++ b/src/pathops/SkPathOpsTriangle.cpp
@@ -0,0 +1,31 @@
+/*
+ * 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 "SkPathOpsTriangle.h"
+
+// http://www.blackpawn.com/texts/pointinpoly/default.html
+bool SkDTriangle::contains(const SkDPoint& pt) const {
+// Compute vectors
+ SkDVector v0 = fPts[2] - fPts[0];
+ SkDVector v1 = fPts[1] - fPts[0];
+ SkDVector v2 = pt - fPts[0];
+
+// Compute dot products
+ 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);
+}
diff --git a/src/pathops/SkPathOpsTriangle.h b/src/pathops/SkPathOpsTriangle.h
new file mode 100644
index 0000000000..cbeea8cc76
--- /dev/null
+++ b/src/pathops/SkPathOpsTriangle.h
@@ -0,0 +1,20 @@
+/*
+ * 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 SkPathOpsTriangle_DEFINED
+#define SkPathOpsTriangle_DEFINED
+
+#include "SkPathOpsPoint.h"
+
+struct SkDTriangle {
+ SkDPoint fPts[3];
+
+ bool contains(const SkDPoint& pt) const;
+
+};
+
+#endif
diff --git a/src/pathops/SkPathOpsTypes.cpp b/src/pathops/SkPathOpsTypes.cpp
new file mode 100644
index 0000000000..199d7be14b
--- /dev/null
+++ b/src/pathops/SkPathOpsTypes.cpp
@@ -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.
+ */
+#include "SkPathOpsTypes.h"
+
+const int UlpsEpsilon = 16;
+
+// from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/
+union SkPathOpsUlpsFloat {
+ int32_t fInt;
+ float fFloat;
+
+ SkPathOpsUlpsFloat(float num = 0.0f) : fFloat(num) {}
+ bool negative() const { return fInt < 0; }
+};
+
+bool AlmostEqualUlps(float A, float B) {
+ SkPathOpsUlpsFloat uA(A);
+ SkPathOpsUlpsFloat uB(B);
+ // Different signs means they do not match.
+ if (uA.negative() != uB.negative())
+ {
+ // Check for equality to make sure +0 == -0
+ return A == B;
+ }
+ // Find the difference in ULPs.
+ int ulpsDiff = abs(uA.fInt - uB.fInt);
+ return ulpsDiff <= UlpsEpsilon;
+}
+
+// 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;
+}
diff --git a/src/pathops/SkPathOpsTypes.h b/src/pathops/SkPathOpsTypes.h
new file mode 100644
index 0000000000..aadaca8ccb
--- /dev/null
+++ b/src/pathops/SkPathOpsTypes.h
@@ -0,0 +1,226 @@
+/*
+ * 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 "SkPathOps.h"
+#include "SkPathOpsDebug.h"
+#include "SkScalar.h"
+
+// FIXME: move these into SkTypes.h
+template <typename T> inline T SkTMax(T a, T b) {
+ if (a < b)
+ a = b;
+ return a;
+}
+
+template <typename T> inline T SkTMin(T a, T b) {
+ if (a > b)
+ a = b;
+ return a;
+}
+
+// FIXME: move this into SkFloatingPoint.h
+#define sk_double_isnan(a) sk_float_isnan(a)
+
+enum SkPathOpsMask {
+ kWinding_PathOpsMask = -1,
+ kNo_PathOpsMask = 0,
+ kEvenOdd_PathOpsMask = 1
+};
+
+extern bool AlmostEqualUlps(float A, float B);
+inline bool AlmostEqualUlps(double A, double B) {
+ return AlmostEqualUlps(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_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 ROUGH_EPSILON = FLT_EPSILON * 64;
+const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256;
+
+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 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_squared(double x) {
+ return fabs(x) < FLT_EPSILON_SQUARED;
+}
+
+inline bool approximately_zero_sqrt(double x) {
+ return fabs(x) < FLT_EPSILON_SQRT;
+}
+
+inline bool approximately_zero_inverse(double x) {
+ return fabs(x) > FLT_EPSILON_INVERSE;
+}
+
+// OPTIMIZATION: if called multiple times with the same denom, we want to pass 1/y instead
+inline bool approximately_zero_when_compared_to(double x, double y) {
+ return x == 0 || fabs(x / y) < FLT_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 approximately_equal_half(double x, double y) {
+ return approximately_zero_half(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_or_equal(double x, double y) {
+ return x + FLT_EPSILON > y;
+}
+
+inline bool approximately_lesser(double x, double y) {
+ return x + FLT_EPSILON <= y;
+}
+
+inline bool approximately_lesser_or_equal(double x, double y) {
+ return x - FLT_EPSILON < y;
+}
+
+inline double approximately_pin(double x) {
+ return approximately_zero(x) ? 0 : x;
+}
+
+inline float approximately_pin(float x) {
+ return approximately_zero(x) ? 0 : x;
+}
+
+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 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_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_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);
+}
+
+// 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));
+ return (a - b) * (c - b) <= 0;
+}
+
+inline bool more_roughly_equal(double x, double y) {
+ return fabs(x - y) < MORE_ROUGH_EPSILON;
+}
+
+inline bool roughly_equal(double x, double y) {
+ return fabs(x - y) < ROUGH_EPSILON;
+}
+
+struct SkDPoint;
+struct SkDVector;
+struct SkDLine;
+struct SkDQuad;
+struct SkDTriangle;
+struct SkDCubic;
+struct SkDRect;
+
+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);
+}
+
+#endif
diff --git a/src/pathops/SkPathWriter.cpp b/src/pathops/SkPathWriter.cpp
new file mode 100644
index 0000000000..e367228836
--- /dev/null
+++ b/src/pathops/SkPathWriter.cpp
@@ -0,0 +1,157 @@
+/*
+ * 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 "SkPathOpsTypes.h"
+#include "SkPathWriter.h"
+
+// wrap path to keep track of whether the contour is initialized and non-empty
+SkPathWriter::SkPathWriter(SkPath& path)
+ : fPathPtr(&path)
+ , fCloses(0)
+ , fMoves(0)
+{
+ init();
+}
+
+void SkPathWriter::close() {
+ if (!fHasMove) {
+ return;
+ }
+ bool callClose = isClosed();
+ lineTo();
+ if (fEmpty) {
+ return;
+ }
+ if (callClose) {
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.close();\n");
+#endif
+ fPathPtr->close();
+ fCloses++;
+ }
+ init();
+}
+
+void SkPathWriter::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3) {
+ lineTo();
+ moveTo();
+ fDefer[1] = pt3;
+ nudge();
+ fDefer[0] = fDefer[1];
+#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, fDefer[1].fX, fDefer[1].fY);
+#endif
+ fPathPtr->cubicTo(pt1.fX, pt1.fY, pt2.fX, pt2.fY, fDefer[1].fX, fDefer[1].fY);
+ fEmpty = false;
+}
+
+void SkPathWriter::deferredLine(const SkPoint& pt) {
+ if (pt == fDefer[1]) {
+ return;
+ }
+ if (changedSlopes(pt)) {
+ lineTo();
+ fDefer[0] = fDefer[1];
+ }
+ fDefer[1] = pt;
+}
+
+void SkPathWriter::deferredMove(const SkPoint& pt) {
+ fMoved = true;
+ fHasMove = true;
+ fEmpty = true;
+ fDefer[0] = fDefer[1] = pt;
+}
+
+void SkPathWriter::deferredMoveLine(const SkPoint& pt) {
+ if (!fHasMove) {
+ deferredMove(pt);
+ }
+ deferredLine(pt);
+}
+
+bool SkPathWriter::hasMove() const {
+ return fHasMove;
+}
+
+void SkPathWriter::init() {
+ fEmpty = true;
+ fHasMove = false;
+ fMoved = false;
+}
+
+bool SkPathWriter::isClosed() const {
+ return !fEmpty && fFirstPt == fDefer[1];
+}
+
+void SkPathWriter::lineTo() {
+ if (fDefer[0] == fDefer[1]) {
+ return;
+ }
+ moveTo();
+ nudge();
+ fEmpty = false;
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.lineTo(%1.9g,%1.9g);\n", fDefer[1].fX, fDefer[1].fY);
+#endif
+ fPathPtr->lineTo(fDefer[1].fX, fDefer[1].fY);
+ fDefer[0] = fDefer[1];
+}
+
+const SkPath* SkPathWriter::nativePath() const {
+ return fPathPtr;
+}
+
+void SkPathWriter::nudge() {
+ if (fEmpty || !AlmostEqualUlps(fDefer[1].fX, fFirstPt.fX)
+ || !AlmostEqualUlps(fDefer[1].fY, fFirstPt.fY)) {
+ return;
+ }
+ fDefer[1] = fFirstPt;
+}
+
+void SkPathWriter::quadTo(const SkPoint& pt1, const SkPoint& pt2) {
+ lineTo();
+ moveTo();
+ fDefer[1] = pt2;
+ nudge();
+ fDefer[0] = fDefer[1];
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n",
+ pt1.fX, pt1.fY, fDefer[1].fX, fDefer[1].fY);
+#endif
+ fPathPtr->quadTo(pt1.fX, pt1.fY, fDefer[1].fX, fDefer[1].fY);
+ fEmpty = false;
+}
+
+bool SkPathWriter::someAssemblyRequired() const {
+ return fCloses < fMoves;
+}
+
+bool SkPathWriter::changedSlopes(const SkPoint& pt) const {
+ if (fDefer[0] == fDefer[1]) {
+ return false;
+ }
+ SkScalar deferDx = fDefer[1].fX - fDefer[0].fX;
+ SkScalar deferDy = fDefer[1].fY - fDefer[0].fY;
+ SkScalar lineDx = pt.fX - fDefer[1].fX;
+ SkScalar lineDy = pt.fY - fDefer[1].fY;
+ return deferDx * lineDy != deferDy * lineDx;
+}
+
+void SkPathWriter::moveTo() {
+ if (!fMoved) {
+ return;
+ }
+ fFirstPt = fDefer[0];
+#if DEBUG_PATH_CONSTRUCTION
+ SkDebugf("path.moveTo(%1.9g,%1.9g);\n", fDefer[0].fX, fDefer[0].fY);
+#endif
+ fPathPtr->moveTo(fDefer[0].fX, fDefer[0].fY);
+ fMoved = false;
+ fMoves++;
+}
diff --git a/src/pathops/SkPathWriter.h b/src/pathops/SkPathWriter.h
new file mode 100644
index 0000000000..2989b357ff
--- /dev/null
+++ b/src/pathops/SkPathWriter.h
@@ -0,0 +1,44 @@
+/*
+ * 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"
+
+class SkPathWriter {
+public:
+ SkPathWriter(SkPath& path);
+ void close();
+ void cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3);
+ void deferredLine(const SkPoint& pt);
+ void deferredMove(const SkPoint& pt);
+ void deferredMoveLine(const SkPoint& pt);
+ bool hasMove() const;
+ void init();
+ bool isClosed() const;
+ void lineTo();
+ const SkPath* nativePath() const;
+ void nudge();
+ void quadTo(const SkPoint& pt1, const SkPoint& pt2);
+ bool someAssemblyRequired() const;
+
+private:
+ bool changedSlopes(const SkPoint& pt) const;
+ void moveTo();
+
+ SkPath* fPathPtr;
+ SkPoint fDefer[2];
+ SkPoint fFirstPt;
+ int fCloses;
+ int fMoves;
+ bool fEmpty;
+ bool fHasMove;
+ bool fMoved;
+};
+
+
+#endif /* defined(__PathOps__SkPathWriter__) */
diff --git a/src/pathops/SkQuarticRoot.cpp b/src/pathops/SkQuarticRoot.cpp
new file mode 100644
index 0000000000..09a92c66a2
--- /dev/null
+++ b/src/pathops/SkQuarticRoot.cpp
@@ -0,0 +1,165 @@
+// from http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c
+/*
+ * Roots3And4.c
+ *
+ * Utility functions to find cubic and quartic roots,
+ * coefficients are passed like this:
+ *
+ * c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 + c[4]*x^4 = 0
+ *
+ * The functions return the number of non-complex roots and
+ * put the values into the s array.
+ *
+ * Author: Jochen Schwarze (schwarze@isa.de)
+ *
+ * Jan 26, 1990 Version for Graphics Gems
+ * Oct 11, 1990 Fixed sign problem for negative q's in SolveQuartic
+ * (reported by Mark Podlipec),
+ * Old-style function definitions,
+ * IsZero() as a macro
+ * Nov 23, 1990 Some systems do not declare acos() and cbrt() in
+ * <math.h>, though the functions exist in the library.
+ * If large coefficients are used, EQN_EPS should be
+ * reduced considerably (e.g. to 1E-30), results will be
+ * correct but multiple roots might be reported more
+ * than once.
+ */
+
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsQuad.h"
+#include "SkQuarticRoot.h"
+
+int SkReducedQuarticRoots(const double t4, const double t3, const double t2, const double t1,
+ const double t0, const bool oneHint, double roots[4]) {
+#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^4 + %1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]",
+ t4, t3, t2, t1, t0);
+ mathematica_ize(str, sizeof(str));
+#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA
+ SkDebugf("%s\n", str);
+#endif
+#endif
+ if (approximately_zero_when_compared_to(t4, t0) // 0 is one root
+ && approximately_zero_when_compared_to(t4, t1)
+ && approximately_zero_when_compared_to(t4, t2)) {
+ if (approximately_zero_when_compared_to(t3, t0)
+ && approximately_zero_when_compared_to(t3, t1)
+ && approximately_zero_when_compared_to(t3, t2)) {
+ return SkDQuad::RootsReal(t2, t1, t0, roots);
+ }
+ if (approximately_zero_when_compared_to(t4, t3)) {
+ return SkDCubic::RootsReal(t3, t2, t1, t0, roots);
+ }
+ }
+ if ((approximately_zero_when_compared_to(t0, t1) || approximately_zero(t1)) // 0 is one root
+ // && approximately_zero_when_compared_to(t0, t2)
+ && approximately_zero_when_compared_to(t0, t3)
+ && approximately_zero_when_compared_to(t0, t4)) {
+ int num = SkDCubic::RootsReal(t4, t3, t2, t1, roots);
+ for (int i = 0; i < num; ++i) {
+ if (approximately_zero(roots[i])) {
+ return num;
+ }
+ }
+ roots[num++] = 0;
+ return num;
+ }
+ if (oneHint) {
+ SkASSERT(approximately_zero(t4 + t3 + t2 + t1 + t0)); // 1 is one root
+ // note that -C == A + B + D + E
+ int num = SkDCubic::RootsReal(t4, t4 + t3, -(t1 + t0), -t0, roots);
+ for (int i = 0; i < num; ++i) {
+ if (approximately_equal(roots[i], 1)) {
+ return num;
+ }
+ }
+ roots[num++] = 1;
+ return num;
+ }
+ return -1;
+}
+
+int SkQuarticRootsReal(int firstCubicRoot, const double A, const double B, const double C,
+ const double D, const double E, double s[4]) {
+ double u, v;
+ /* normal form: x^4 + Ax^3 + Bx^2 + Cx + D = 0 */
+ const double invA = 1 / A;
+ const double a = B * invA;
+ const double b = C * invA;
+ const double c = D * invA;
+ const double d = E * invA;
+ /* substitute x = y - a/4 to eliminate cubic term:
+ x^4 + px^2 + qx + r = 0 */
+ const double a2 = a * a;
+ const double p = -3 * a2 / 8 + b;
+ const double q = a2 * a / 8 - a * b / 2 + c;
+ const double r = -3 * a2 * a2 / 256 + a2 * b / 16 - a * c / 4 + d;
+ int num;
+ if (approximately_zero(r)) {
+ /* no absolute term: y(y^3 + py + q) = 0 */
+ num = SkDCubic::RootsReal(1, 0, p, q, s);
+ s[num++] = 0;
+ } else {
+ /* solve the resolvent cubic ... */
+ double cubicRoots[3];
+ int roots = SkDCubic::RootsReal(1, -p / 2, -r, r * p / 2 - q * q / 8, cubicRoots);
+ int index;
+ /* ... and take one real solution ... */
+ double z;
+ num = 0;
+ int num2 = 0;
+ for (index = firstCubicRoot; index < roots; ++index) {
+ z = cubicRoots[index];
+ /* ... to build two quadric equations */
+ u = z * z - r;
+ v = 2 * z - p;
+ if (approximately_zero_squared(u)) {
+ u = 0;
+ } else if (u > 0) {
+ u = sqrt(u);
+ } else {
+ continue;
+ }
+ if (approximately_zero_squared(v)) {
+ v = 0;
+ } else if (v > 0) {
+ v = sqrt(v);
+ } else {
+ continue;
+ }
+ num = SkDQuad::RootsReal(1, q < 0 ? -v : v, z - u, s);
+ num2 = SkDQuad::RootsReal(1, q < 0 ? v : -v, z + u, s + num);
+ if (!((num | num2) & 1)) {
+ break; // prefer solutions without single quad roots
+ }
+ }
+ num += num2;
+ if (!num) {
+ return 0; // no valid cubic root
+ }
+ }
+ /* resubstitute */
+ const double sub = a / 4;
+ for (int i = 0; i < num; ++i) {
+ s[i] -= sub;
+ }
+ // eliminate duplicates
+ for (int i = 0; i < num - 1; ++i) {
+ for (int j = i + 1; j < num; ) {
+ if (AlmostEqualUlps(s[i], s[j])) {
+ if (j < --num) {
+ s[j] = s[num];
+ }
+ } else {
+ ++j;
+ }
+ }
+ }
+ return num;
+}
diff --git a/src/pathops/SkQuarticRoot.h b/src/pathops/SkQuarticRoot.h
new file mode 100644
index 0000000000..6ce08671e2
--- /dev/null
+++ b/src/pathops/SkQuarticRoot.h
@@ -0,0 +1,16 @@
+/*
+ * 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 SkDQuarticRoot_DEFINED
+#define SkDQuarticRoot_DEFINED
+
+int SkReducedQuarticRoots(const double t4, const double t3, const double t2, const double t1,
+ const double t0, const bool oneHint, double s[4]);
+
+int SkQuarticRootsReal(int firstCubicRoot, const double A, const double B, const double C,
+ const double D, const double E, double s[4]);
+
+#endif
diff --git a/src/pathops/SkReduceOrder.cpp b/src/pathops/SkReduceOrder.cpp
new file mode 100644
index 0000000000..14da4cc1f1
--- /dev/null
+++ b/src/pathops/SkReduceOrder.cpp
@@ -0,0 +1,450 @@
+/*
+ * 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 "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 double interp_quad_coords(double a, double b, double c, double t) {
+ double ab = SkDInterp(a, b, t);
+ double bc = SkDInterp(b, c, t);
+ return SkDInterp(ab, bc, t);
+}
+
+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, SkReduceOrder::Style reduceStyle,
+ SkDQuad& reduction) {
+ double tValue;
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int smaller = reduction[1].fY > reduction[0].fY;
+ int larger = smaller ^ 1;
+ if (SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValue)) {
+ double yExtrema = interp_quad_coords(quad[0].fY, quad[1].fY, quad[2].fY, tValue);
+ if (reduction[smaller].fY > yExtrema) {
+ reduction[smaller].fY = yExtrema;
+ } else if (reduction[larger].fY < yExtrema) {
+ reduction[larger].fY = yExtrema;
+ }
+ }
+ return reductionLineCount(reduction);
+}
+
+static int horizontal_line(const SkDQuad& quad, SkReduceOrder::Style reduceStyle,
+ SkDQuad& reduction) {
+ double tValue;
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int smaller = reduction[1].fX > reduction[0].fX;
+ int larger = smaller ^ 1;
+ if (SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, &tValue)) {
+ double xExtrema = interp_quad_coords(quad[0].fX, quad[1].fX, quad[2].fX, tValue);
+ if (reduction[smaller].fX > xExtrema) {
+ reduction[smaller].fX = xExtrema;
+ } else if (reduction[larger].fX < xExtrema) {
+ reduction[larger].fX = xExtrema;
+ }
+ }
+ return reductionLineCount(reduction);
+}
+
+static int check_linear(const SkDQuad& quad, SkReduceOrder::Style reduceStyle,
+ int minX, int maxX, int minY, int maxY, SkDQuad& reduction) {
+ int startIndex = 0;
+ int endIndex = 2;
+ while (quad[startIndex].approximatelyEqual(quad[endIndex])) {
+ --endIndex;
+ if (endIndex == 0) {
+ SkDebugf("%s shouldn't get here if all four points are about equal", __FUNCTION__);
+ SkASSERT(0);
+ }
+ }
+ if (!quad.isLinear(startIndex, endIndex)) {
+ return 0;
+ }
+ // four are colinear: return line formed by outside
+ reduction[0] = quad[0];
+ reduction[1] = quad[2];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int sameSide;
+ bool useX = quad[maxX].fX - quad[minX].fX >= quad[maxY].fY - quad[minY].fY;
+ if (useX) {
+ sameSide = SkDSign(quad[0].fX - quad[1].fX) + SkDSign(quad[2].fX - quad[1].fX);
+ } else {
+ sameSide = SkDSign(quad[0].fY - quad[1].fY) + SkDSign(quad[2].fY - quad[1].fY);
+ }
+ if ((sameSide & 3) != 2) {
+ return reductionLineCount(reduction);
+ }
+ double tValue;
+ int root;
+ if (useX) {
+ root = SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, &tValue);
+ } else {
+ root = SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValue);
+ }
+ if (root) {
+ SkDPoint extrema;
+ extrema.fX = interp_quad_coords(quad[0].fX, quad[1].fX, quad[2].fX, tValue);
+ extrema.fY = interp_quad_coords(quad[0].fY, quad[1].fY, quad[2].fY, tValue);
+ // sameSide > 0 means mid is smaller than either [0] or [2], so replace smaller
+ int replace;
+ if (useX) {
+ if ((extrema.fX < quad[0].fX) ^ (extrema.fX < quad[2].fX)) {
+ return reductionLineCount(reduction);
+ }
+ replace = ((extrema.fX < quad[0].fX) | (extrema.fX < quad[2].fX))
+ ^ (quad[0].fX < quad[2].fX);
+ } else {
+ if ((extrema.fY < quad[0].fY) ^ (extrema.fY < quad[2].fY)) {
+ return reductionLineCount(reduction);
+ }
+ replace = ((extrema.fY < quad[0].fY) | (extrema.fY < quad[2].fY))
+ ^ (quad[0].fY < quad[2].fY);
+ }
+ reduction[replace] = extrema;
+ }
+ 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, Style reduceStyle) {
+ 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 == 0x7) { // test for vertical line
+ if (minYSet == 0x7) { // return 1 if all four are coincident
+ return coincident_line(quad, fQuad);
+ }
+ return vertical_line(quad, reduceStyle, fQuad);
+ }
+ if (minYSet == 0xF) { // test for horizontal line
+ return horizontal_line(quad, reduceStyle, fQuad);
+ }
+ int result = check_linear(quad, reduceStyle, minX, maxX, minY, maxY, fQuad);
+ if (result) {
+ return result;
+ }
+ fQuad = quad;
+ return 3;
+}
+
+////////////////////////////////////////////////////////////////////////////////////
+
+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);
+ return SkDInterp(abc, bcd, t);
+}
+
+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, SkReduceOrder::Style reduceStyle,
+ SkDCubic& reduction) {
+ double tValues[2];
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int smaller = reduction[1].fY > reduction[0].fY;
+ int larger = smaller ^ 1;
+ int roots = SkDCubic::FindExtrema(cubic[0].fY, cubic[1].fY, cubic[2].fY, cubic[3].fY, tValues);
+ for (int index = 0; index < roots; ++index) {
+ double yExtrema = interp_cubic_coords(&cubic[0].fY, tValues[index]);
+ if (reduction[smaller].fY > yExtrema) {
+ reduction[smaller].fY = yExtrema;
+ continue;
+ }
+ if (reduction[larger].fY < yExtrema) {
+ reduction[larger].fY = yExtrema;
+ }
+ }
+ return reductionLineCount(reduction);
+}
+
+static int horizontal_line(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle,
+ SkDCubic& reduction) {
+ double tValues[2];
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int smaller = reduction[1].fX > reduction[0].fX;
+ int larger = smaller ^ 1;
+ int roots = SkDCubic::FindExtrema(cubic[0].fX, cubic[1].fX, cubic[2].fX, cubic[3].fX, tValues);
+ for (int index = 0; index < roots; ++index) {
+ double xExtrema = interp_cubic_coords(&cubic[0].fX, tValues[index]);
+ if (reduction[smaller].fX > xExtrema) {
+ reduction[smaller].fX = xExtrema;
+ continue;
+ }
+ if (reduction[larger].fX < xExtrema) {
+ reduction[larger].fX = xExtrema;
+ }
+ }
+ 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;
+ if (!AlmostEqualUlps(midX - cubic[3].fX, dx23 * 3 / 2)) {
+ 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;
+ if (!AlmostEqualUlps(midY - cubic[3].fY, dy23 * 3 / 2)) {
+ 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, SkReduceOrder::Style reduceStyle,
+ int minX, int maxX, int minY, int maxY, SkDCubic& reduction) {
+ int startIndex = 0;
+ int endIndex = 3;
+ while (cubic[startIndex].approximatelyEqual(cubic[endIndex])) {
+ --endIndex;
+ if (endIndex == 0) {
+ SkDebugf("%s shouldn't get here if all four points are about equal\n", __FUNCTION__);
+ SkASSERT(0);
+ }
+ }
+ if (!cubic.isLinear(startIndex, endIndex)) {
+ return 0;
+ }
+ // four are colinear: return line formed by outside
+ reduction[0] = cubic[0];
+ reduction[1] = cubic[3];
+ if (reduceStyle == SkReduceOrder::kFill_Style) {
+ return reductionLineCount(reduction);
+ }
+ int sameSide1;
+ int sameSide2;
+ bool useX = cubic[maxX].fX - cubic[minX].fX >= cubic[maxY].fY - cubic[minY].fY;
+ if (useX) {
+ sameSide1 = SkDSign(cubic[0].fX - cubic[1].fX) + SkDSign(cubic[3].fX - cubic[1].fX);
+ sameSide2 = SkDSign(cubic[0].fX - cubic[2].fX) + SkDSign(cubic[3].fX - cubic[2].fX);
+ } else {
+ sameSide1 = SkDSign(cubic[0].fY - cubic[1].fY) + SkDSign(cubic[3].fY - cubic[1].fY);
+ sameSide2 = SkDSign(cubic[0].fY - cubic[2].fY) + SkDSign(cubic[3].fY - cubic[2].fY);
+ }
+ if (sameSide1 == sameSide2 && (sameSide1 & 3) != 2) {
+ return reductionLineCount(reduction);
+ }
+ double tValues[2];
+ int roots;
+ if (useX) {
+ roots = SkDCubic::FindExtrema(cubic[0].fX, cubic[1].fX, cubic[2].fX, cubic[3].fX, tValues);
+ } else {
+ roots = SkDCubic::FindExtrema(cubic[0].fY, cubic[1].fY, cubic[2].fY, cubic[3].fY, tValues);
+ }
+ for (int index = 0; index < roots; ++index) {
+ SkDPoint extrema;
+ extrema.fX = interp_cubic_coords(&cubic[0].fX, tValues[index]);
+ extrema.fY = interp_cubic_coords(&cubic[0].fY, tValues[index]);
+ // sameSide > 0 means mid is smaller than either [0] or [3], so replace smaller
+ int replace;
+ if (useX) {
+ if ((extrema.fX < cubic[0].fX) ^ (extrema.fX < cubic[3].fX)) {
+ continue;
+ }
+ replace = ((extrema.fX < cubic[0].fX) | (extrema.fX < cubic[3].fX))
+ ^ (cubic[0].fX < cubic[3].fX);
+ } else {
+ if ((extrema.fY < cubic[0].fY) ^ (extrema.fY < cubic[3].fY)) {
+ continue;
+ }
+ replace = ((extrema.fY < cubic[0].fY) | (extrema.fY < cubic[3].fY))
+ ^ (cubic[0].fY < cubic[3].fY);
+ }
+ reduction[replace] = extrema;
+ }
+ 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,
+ Style reduceStyle) {
+ 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, reduceStyle, fCubic);
+ }
+ if (minYSet == 0xF) { // test for horizontal line
+ return horizontal_line(cubic, reduceStyle, fCubic);
+ }
+ int result = check_linear(cubic, reduceStyle, 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], SkTDArray<SkPoint>* reducePts) {
+ SkDQuad quad;
+ quad.set(a);
+ SkReduceOrder reducer;
+ int order = reducer.reduce(quad, kFill_Style);
+ if (order == 2) { // quad became line
+ for (int index = 0; index < order; ++index) {
+ SkPoint* pt = reducePts->append();
+ pt->fX = SkDoubleToScalar(reducer.fLine[index].fX);
+ pt->fY = SkDoubleToScalar(reducer.fLine[index].fY);
+ }
+ }
+ return (SkPath::Verb) (order - 1);
+}
+
+SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkTDArray<SkPoint>* reducePts) {
+ SkDCubic cubic;
+ cubic.set(a);
+ SkReduceOrder reducer;
+ int order = reducer.reduce(cubic, kAllow_Quadratics, kFill_Style);
+ if (order == 2 || order == 3) { // cubic became line or quad
+ for (int index = 0; index < order; ++index) {
+ SkPoint* pt = reducePts->append();
+ pt->fX = SkDoubleToScalar(reducer.fQuad[index].fX);
+ pt->fY = SkDoubleToScalar(reducer.fQuad[index].fY);
+ }
+ }
+ return (SkPath::Verb) (order - 1);
+}
diff --git a/src/pathops/SkReduceOrder.h b/src/pathops/SkReduceOrder.h
new file mode 100644
index 0000000000..62b4af9367
--- /dev/null
+++ b/src/pathops/SkReduceOrder.h
@@ -0,0 +1,38 @@
+/*
+ * 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 "SkPath.h"
+#include "SkPathOpsCubic.h"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkTDArray.h"
+
+union SkReduceOrder {
+ enum Quadratics {
+ kNo_Quadratics,
+ kAllow_Quadratics
+ };
+ enum Style {
+ kStroke_Style,
+ kFill_Style
+ };
+
+ int reduce(const SkDCubic& cubic, Quadratics, Style);
+ int reduce(const SkDLine& line);
+ int reduce(const SkDQuad& quad, Style);
+
+ static SkPath::Verb Cubic(const SkPoint pts[4], SkTDArray<SkPoint>* reducePts);
+ static SkPath::Verb Quad(const SkPoint pts[3], SkTDArray<SkPoint>* reducePts);
+
+ SkDLine fLine;
+ SkDQuad fQuad;
+ SkDCubic fCubic;
+};
+
+#endif
diff --git a/src/pathops/TSearch.h b/src/pathops/TSearch.h
new file mode 100644
index 0000000000..2550448c9c
--- /dev/null
+++ b/src/pathops/TSearch.h
@@ -0,0 +1,101 @@
+/*
+ * 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 TSearch_DEFINED
+#define TSearch_DEFINED
+
+// FIXME: Move this templated version into SKTSearch.h
+
+template <typename T>
+static T* QSort_Partition(T* left, T* right, T* pivot)
+{
+ T pivotValue = *pivot;
+ SkTSwap(*pivot, *right);
+ T* newPivot = left;
+ while (left < right) {
+ if (*left < pivotValue) {
+ SkTSwap(*left, *newPivot);
+ newPivot += 1;
+ }
+ left += 1;
+ }
+ SkTSwap(*newPivot, *right);
+ return newPivot;
+}
+
+template <typename T>
+void QSort(T* left, T* right)
+{
+ if (left >= right) {
+ return;
+ }
+ T* pivot = left + ((right - left) >> 1);
+ pivot = QSort_Partition(left, right, pivot);
+ QSort(left, pivot - 1);
+ QSort(pivot + 1, right);
+}
+
+template <typename T>
+static T** QSort_Partition(T** left, T** right, T** pivot)
+{
+ T* pivotValue = *pivot;
+ SkTSwap(*pivot, *right);
+ T** newPivot = left;
+ while (left < right) {
+ if (**left < *pivotValue) {
+ SkTSwap(*left, *newPivot);
+ newPivot += 1;
+ }
+ left += 1;
+ }
+ SkTSwap(*newPivot, *right);
+ return newPivot;
+}
+
+template <typename T>
+void QSort(T** left, T** right)
+{
+ if (left >= right) {
+ return;
+ }
+ T** pivot = left + ((right - left) >> 1);
+ pivot = QSort_Partition(left, right, pivot);
+ QSort(left, pivot - 1);
+ QSort(pivot + 1, right);
+}
+
+template <typename S, typename T>
+static T* QSort_Partition(S& context, T* left, T* right, T* pivot,
+ bool (*lessThan)(S&, const T, const T))
+{
+ T pivotValue = *pivot;
+ SkTSwap(*pivot, *right);
+ T* newPivot = left;
+ while (left < right) {
+ if (lessThan(context, *left, pivotValue)) {
+ SkTSwap(*left, *newPivot);
+ newPivot += 1;
+ }
+ left += 1;
+ }
+ SkTSwap(*newPivot, *right);
+ return newPivot;
+}
+
+template <typename S, typename T>
+void QSort(S& context, T* left, T* right,
+ bool (*lessThan)(S& , const T, const T))
+{
+ if (left >= right) {
+ return;
+ }
+ T* pivot = left + ((right - left) >> 1);
+ pivot = QSort_Partition(context, left, right, pivot, lessThan);
+ QSort(context, left, pivot - 1, lessThan);
+ QSort(context, pivot + 1, right, lessThan);
+}
+
+#endif
diff --git a/src/pathops/main.cpp b/src/pathops/main.cpp
new file mode 100644
index 0000000000..65479c2528
--- /dev/null
+++ b/src/pathops/main.cpp
@@ -0,0 +1,16 @@
+/*
+ * 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 "Test.h"
+
+using namespace skiatest;
+
+int main(int /*argc*/, char** /*argv*/) {
+ Test tester;
+ tester.run();
+ return 0;
+}
diff --git a/tests/PathOpsBoundsTest.cpp b/tests/PathOpsBoundsTest.cpp
new file mode 100644
index 0000000000..0ed19540ed
--- /dev/null
+++ b/tests/PathOpsBoundsTest.cpp
@@ -0,0 +1,106 @@
+/*
+ * 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 "SkPathOpsBounds.h"
+#include "Test.h"
+
+static const SkRect sectTests[][2] = {
+ {{2, 0, 4, 1}, {4, 0, 6, 1}},
+ {{2, 0, 4, 1}, {3, 0, 5, 1}},
+ {{2, 0, 4, 1}, {3, 0, 5, 0}},
+ {{2, 0, 4, 1}, {3, 1, 5, 2}},
+ {{2, 1, 4, 2}, {1, 0, 5, 3}},
+ {{2, 1, 5, 3}, {3, 1, 4, 2}},
+ {{2, 0, 4, 1}, {3, 0, 3, 0}}, // intersecting an empty bounds is OK
+ {{2, 0, 4, 1}, {4, 1, 5, 2}}, // touching just on a corner is OK
+};
+
+static const size_t sectTestsCount = sizeof(sectTests) / sizeof(sectTests[0]);
+
+static const SkRect noSectTests[][2] = {
+ {{2, 0, 4, 1}, {5, 0, 6, 1}},
+ {{2, 0, 4, 1}, {3, 2, 5, 2}},
+};
+
+static const size_t noSectTestsCount = sizeof(noSectTests) / sizeof(noSectTests[0]);
+
+static const SkRect reallyEmpty[] = {
+ {0, 0, 0, 0},
+ {1, 1, 1, 0},
+ {1, 1, 0, 1},
+ {1, 1, 0, 0},
+ {1, 2, 3, SK_ScalarNaN},
+};
+
+static const size_t emptyTestsCount = sizeof(reallyEmpty) / sizeof(reallyEmpty[0]);
+
+static const SkRect notReallyEmpty[] = {
+ {0, 0, 1, 0},
+ {0, 0, 0, 1},
+ {0, 0, 1, 1},
+};
+
+static const size_t notEmptyTestsCount = sizeof(notReallyEmpty) / sizeof(notReallyEmpty[0]);
+
+static void OpBoundsTest(skiatest::Reporter* reporter) {
+ for (size_t index = 0; index < sectTestsCount; ++index) {
+ const SkPathOpsBounds& bounds1 = static_cast<const SkPathOpsBounds&>(sectTests[index][0]);
+ const SkPathOpsBounds& bounds2 = static_cast<const SkPathOpsBounds&>(sectTests[index][1]);
+ bool touches = SkPathOpsBounds::Intersects(bounds1, bounds2);
+ REPORTER_ASSERT(reporter, touches);
+ }
+ for (size_t index = 0; index < noSectTestsCount; ++index) {
+ const SkPathOpsBounds& bounds1 = static_cast<const SkPathOpsBounds&>(noSectTests[index][0]);
+ const SkPathOpsBounds& bounds2 = static_cast<const SkPathOpsBounds&>(noSectTests[index][1]);
+ bool touches = SkPathOpsBounds::Intersects(bounds1, bounds2);
+ REPORTER_ASSERT(reporter, !touches);
+ }
+ SkPathOpsBounds bounds;
+ bounds.setEmpty();
+ bounds.add(1, 2, 3, 4);
+ SkPathOpsBounds expected;
+ expected.set(0, 0, 3, 4);
+ REPORTER_ASSERT(reporter, bounds == expected);
+ bounds.setEmpty();
+ SkPathOpsBounds ordinal;
+ ordinal.set(1, 2, 3, 4);
+ bounds.add(ordinal);
+ REPORTER_ASSERT(reporter, bounds == expected);
+ SkPoint topLeft = {0, 0};
+ bounds.setPointBounds(topLeft);
+ SkPoint botRight = {3, 4};
+ bounds.add(botRight);
+ REPORTER_ASSERT(reporter, bounds == expected);
+ for (size_t index = 0; index < emptyTestsCount; ++index) {
+ const SkPathOpsBounds& bounds = static_cast<const SkPathOpsBounds&>(reallyEmpty[index]);
+ bool empty = bounds.isReallyEmpty();
+ REPORTER_ASSERT(reporter, empty);
+ }
+ for (size_t index = 0; index < notEmptyTestsCount; ++index) {
+ const SkPathOpsBounds& bounds = static_cast<const SkPathOpsBounds&>(notReallyEmpty[index]);
+ bool empty = bounds.isReallyEmpty();
+ REPORTER_ASSERT(reporter, !empty);
+ }
+ const SkPoint curvePts[] = {{0, 0}, {1, 2}, {3, 4}, {5, 6}};
+ bounds.setLineBounds(curvePts);
+ expected.set(0, 0, 1, 2);
+ REPORTER_ASSERT(reporter, bounds == expected);
+ (bounds.*SetCurveBounds[1])(curvePts);
+ REPORTER_ASSERT(reporter, bounds == expected);
+ bounds.setQuadBounds(curvePts);
+ expected.set(0, 0, 3, 4);
+ REPORTER_ASSERT(reporter, bounds == expected);
+ (bounds.*SetCurveBounds[2])(curvePts);
+ REPORTER_ASSERT(reporter, bounds == expected);
+ bounds.setCubicBounds(curvePts);
+ expected.set(0, 0, 5, 6);
+ REPORTER_ASSERT(reporter, bounds == expected);
+ (bounds.*SetCurveBounds[3])(curvePts);
+ REPORTER_ASSERT(reporter, bounds == expected);
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PathOpsBounds", PathOpsBoundsClass, OpBoundsTest)
diff --git a/tests/PathOpsDCubicTest.cpp b/tests/PathOpsDCubicTest.cpp
new file mode 100644
index 0000000000..60b92dfa84
--- /dev/null
+++ b/tests/PathOpsDCubicTest.cpp
@@ -0,0 +1,30 @@
+/*
+ * 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"
+#include "Test.h"
+
+static const SkDCubic tests[] = {
+ {{{2, 0}, {3, 1}, {2, 2}, {1, 1}}},
+ {{{3, 1}, {2, 2}, {1, 1}, {2, 0}}},
+ {{{3, 0}, {2, 1}, {3, 2}, {1, 1}}},
+};
+
+static const size_t tests_count = sizeof(tests) / sizeof(tests[0]);
+
+static void DCubicTest(skiatest::Reporter* reporter) {
+ for (size_t index = 0; index < tests_count; ++index) {
+ const SkDCubic& cubic = tests[index];
+ bool result = cubic.clockwise();
+ if (!result) {
+ SkDebugf("%s [%d] expected clockwise\n", __FUNCTION__, index);
+ REPORTER_ASSERT(reporter, 0);
+ }
+ }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PathOpsDCubic", PathOpsDCubic, DCubicTest)
diff --git a/tests/PathOpsDLineTest.cpp b/tests/PathOpsDLineTest.cpp
new file mode 100644
index 0000000000..4108f70afd
--- /dev/null
+++ b/tests/PathOpsDLineTest.cpp
@@ -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.
+ */
+#include "SkPathOpsLine.h"
+#include "Test.h"
+
+static const SkDLine tests[] = {
+ {{{2, 1}, {2, 1}}},
+ {{{2, 1}, {1, 1}}},
+ {{{2, 1}, {2, 2}}},
+ {{{1, 1}, {2, 2}}},
+ {{{3, 0}, {2, 1}}},
+ {{{3, 2}, {1, 1}}},
+};
+
+static const SkDPoint left[] = {
+ {2, 1},
+ {1, 0},
+ {1, 1},
+ {1, 2},
+ {2, 0},
+ {2, 1}
+};
+
+static const size_t tests_count = sizeof(tests) / sizeof(tests[0]);
+
+static void DLineTest(skiatest::Reporter* reporter) {
+ for (size_t index = 0; index < tests_count; ++index) {
+ const SkDLine& line = tests[index];
+ SkDLine line2;
+ SkPoint pts[2] = {line[0].asSkPoint(), line[1].asSkPoint()};
+ line2.set(pts);
+ REPORTER_ASSERT(reporter, line[0] == line2[0] && line[1] == line2[1]);
+ const SkDPoint& pt = left[index];
+ double result = line.isLeft(pt);
+ if ((result <= 0 && index >= 1) || (result < 0 && index == 0)) {
+ SkDebugf("%s [%d] expected left\n", __FUNCTION__, index);
+ REPORTER_ASSERT(reporter, 0);
+ }
+ line2 = line.subDivide(1, 0);
+ REPORTER_ASSERT(reporter, line[0] == line2[1] && line[1] == line2[0]);
+ line2 = SkDLine::SubDivide(pts, 1, 0);
+ REPORTER_ASSERT(reporter, line[0] == line2[1] && line[1] == line2[0]);
+ SkDPoint mid = line.xyAtT(.5);
+ REPORTER_ASSERT(reporter, approximately_equal((line[0].fX + line[1].fX) / 2, mid.fX));
+ REPORTER_ASSERT(reporter, approximately_equal((line[0].fY + line[1].fY) / 2, mid.fY));
+ }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PathOpsLineUtilities", PathOpsLineUtilitiesClass, DLineTest)
diff --git a/tests/PathOpsDPointTest.cpp b/tests/PathOpsDPointTest.cpp
new file mode 100644
index 0000000000..551609be33
--- /dev/null
+++ b/tests/PathOpsDPointTest.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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"
+#include "Test.h"
+
+static const SkDPoint tests[] = {
+ {0, 0},
+ {1, 0},
+ {0, 1},
+ {2, 1},
+ {1, 2},
+ {1, 1},
+ {2, 2}
+};
+
+static const size_t tests_count = sizeof(tests) / sizeof(tests[0]);
+
+static void DPointTest(skiatest::Reporter* reporter) {
+ for (size_t index = 0; index < tests_count; ++index) {
+ const SkDPoint& pt = tests[index];
+ SkDPoint p = pt;
+ REPORTER_ASSERT(reporter, p == pt);
+ REPORTER_ASSERT(reporter, !(pt != pt));
+ SkDVector v = p - pt;
+ p += v;
+ REPORTER_ASSERT(reporter, p == pt);
+ p -= v;
+ REPORTER_ASSERT(reporter, p == pt);
+ REPORTER_ASSERT(reporter, p.approximatelyEqual(pt));
+ SkPoint sPt = pt.asSkPoint();
+ p.set(sPt);
+ REPORTER_ASSERT(reporter, p == pt);
+ REPORTER_ASSERT(reporter, p.approximatelyEqual(sPt));
+ REPORTER_ASSERT(reporter, p.roughlyEqual(pt));
+ REPORTER_ASSERT(reporter, p.moreRoughlyEqual(pt));
+ p.fX = p.fY = 0;
+ REPORTER_ASSERT(reporter, p.fX == 0 && p.fY == 0);
+ REPORTER_ASSERT(reporter, p.approximatelyZero());
+ REPORTER_ASSERT(reporter, pt.distanceSquared(p) == pt.fX * pt.fX + pt.fY * pt.fY);
+ REPORTER_ASSERT(reporter, approximately_equal(pt.distance(p),
+ sqrt(pt.fX * pt.fX + pt.fY * pt.fY)));
+ }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PathOpsDPoint", PathOpsDPointClass, DPointTest)
diff --git a/tests/PathOpsDQuadTest.cpp b/tests/PathOpsDQuadTest.cpp
new file mode 100644
index 0000000000..f78e2aa3e2
--- /dev/null
+++ b/tests/PathOpsDQuadTest.cpp
@@ -0,0 +1,53 @@
+/*
+ * 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 "SkPathOpsQuad.h"
+#include "Test.h"
+
+static const SkDQuad tests[] = {
+ {{{1, 1}, {2, 1}, {0, 2}}},
+ {{{0, 0}, {1, 1}, {3, 1}}},
+ {{{2, 0}, {1, 1}, {2, 2}}},
+ {{{4, 0}, {0, 1}, {4, 2}}},
+ {{{0, 0}, {0, 1}, {1, 1}}},
+};
+
+static const SkDPoint inPoint[]= {
+ {1, 1.2},
+ {1, 0.8},
+ {1.8, 1},
+ {1.5, 1},
+ {0.5, 0.5},
+};
+
+static const SkDPoint outPoint[]= {
+ {1, 1.6},
+ {1, 1.5},
+ {2.2, 1},
+ {1.5, 1.5},
+ {1.1, 0.5},
+};
+
+static const size_t tests_count = sizeof(tests) / sizeof(tests[0]);
+
+static void DQuadTest(skiatest::Reporter* reporter) {
+ for (size_t index = 0; index < tests_count; ++index) {
+ const SkDQuad& quad = tests[index];
+ bool result = quad.pointInHull(inPoint[index]);
+ if (!result) {
+ SkDebugf("%s [%d] expected in hull\n", __FUNCTION__, index);
+ REPORTER_ASSERT(reporter, 0);
+ }
+ result = quad.pointInHull(outPoint[index]);
+ if (result) {
+ SkDebugf("%s [%d] expected outside hull\n", __FUNCTION__, index);
+ REPORTER_ASSERT(reporter, 0);
+ }
+ }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PathOpsDQuad", PathOpsDQuadClass, DQuadTest)
diff --git a/tests/PathOpsDRectTest.cpp b/tests/PathOpsDRectTest.cpp
new file mode 100644
index 0000000000..881ee0b86b
--- /dev/null
+++ b/tests/PathOpsDRectTest.cpp
@@ -0,0 +1,100 @@
+/*
+ * 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"
+#include "SkPathOpsLine.h"
+#include "SkPathOpsQuad.h"
+#include "SkPathOpsRect.h"
+#include "Test.h"
+
+static const SkDLine lineTests[] = {
+ {{{2, 1}, {2, 1}}},
+ {{{2, 1}, {1, 1}}},
+ {{{2, 1}, {2, 2}}},
+ {{{1, 1}, {2, 2}}},
+ {{{3, 0}, {2, 1}}},
+ {{{3, 2}, {1, 1}}},
+};
+
+static const SkDQuad quadTests[] = {
+ {{{1, 1}, {2, 1}, {0, 2}}},
+ {{{0, 0}, {1, 1}, {3, 1}}},
+ {{{2, 0}, {1, 1}, {2, 2}}},
+ {{{4, 0}, {0, 1}, {4, 2}}},
+ {{{0, 0}, {0, 1}, {1, 1}}},
+};
+
+static const SkDCubic cubicTests[] = {
+ {{{2, 0}, {3, 1}, {2, 2}, {1, 1}}},
+ {{{3, 1}, {2, 2}, {1, 1}, {2, 0}}},
+ {{{3, 0}, {2, 1}, {3, 2}, {1, 1}}},
+};
+
+static const size_t lineTests_count = sizeof(lineTests) / sizeof(lineTests[0]);
+static const size_t quadTests_count = sizeof(quadTests) / sizeof(quadTests[0]);
+static const size_t cubicTests_count = sizeof(cubicTests) / sizeof(cubicTests[0]);
+
+static void DRectTest(skiatest::Reporter* reporter) {
+ size_t index;
+ SkDRect rect, rect2;
+ for (index = 0; index < lineTests_count; ++index) {
+ const SkDLine& line = lineTests[index];
+ rect.setBounds(line);
+ REPORTER_ASSERT(reporter, rect.fLeft == SkTMin<double>(line[0].fX, line[1].fX));
+ REPORTER_ASSERT(reporter, rect.fTop == SkTMin<double>(line[0].fY, line[1].fY));
+ REPORTER_ASSERT(reporter, rect.fRight == SkTMax<double>(line[0].fX, line[1].fX));
+ REPORTER_ASSERT(reporter, rect.fBottom == SkTMax<double>(line[0].fY, line[1].fY));
+ rect2.set(line[0]);
+ rect2.add(line[1]);
+ REPORTER_ASSERT(reporter, rect2.fLeft == SkTMin<double>(line[0].fX, line[1].fX));
+ REPORTER_ASSERT(reporter, rect2.fTop == SkTMin<double>(line[0].fY, line[1].fY));
+ REPORTER_ASSERT(reporter, rect2.fRight == SkTMax<double>(line[0].fX, line[1].fX));
+ REPORTER_ASSERT(reporter, rect2.fBottom == SkTMax<double>(line[0].fY, line[1].fY));
+ REPORTER_ASSERT(reporter, rect.contains(line[0]));
+ REPORTER_ASSERT(reporter, rect.intersects(&rect2));
+ }
+ for (index = 0; index < quadTests_count; ++index) {
+ const SkDQuad& quad = quadTests[index];
+ rect.setRawBounds(quad);
+ REPORTER_ASSERT(reporter, rect.fLeft == SkTMin<double>(quad[0].fX,
+ SkTMin<double>(quad[1].fX, quad[2].fX)));
+ REPORTER_ASSERT(reporter, rect.fTop == SkTMin<double>(quad[0].fY,
+ SkTMin<double>(quad[1].fY, quad[2].fY)));
+ REPORTER_ASSERT(reporter, rect.fRight == SkTMax<double>(quad[0].fX,
+ SkTMax<double>(quad[1].fX, quad[2].fX)));
+ REPORTER_ASSERT(reporter, rect.fBottom == SkTMax<double>(quad[0].fY,
+ SkTMax<double>(quad[1].fY, quad[2].fY)));
+ rect2.setBounds(quad);
+ REPORTER_ASSERT(reporter, rect.intersects(&rect2));
+ // FIXME: add a recursive box subdivision method to verify that tight bounds is correct
+ SkDPoint leftTop = {rect2.fLeft, rect2.fTop};
+ REPORTER_ASSERT(reporter, rect.contains(leftTop));
+ SkDPoint rightBottom = {rect2.fRight, rect2.fBottom};
+ REPORTER_ASSERT(reporter, rect.contains(rightBottom));
+ }
+ for (index = 0; index < cubicTests_count; ++index) {
+ const SkDCubic& cubic = cubicTests[index];
+ rect.setRawBounds(cubic);
+ REPORTER_ASSERT(reporter, rect.fLeft == SkTMin<double>(cubic[0].fX,
+ SkTMin<double>(cubic[1].fX, SkTMin<double>(cubic[2].fX, cubic[3].fX))));
+ REPORTER_ASSERT(reporter, rect.fTop == SkTMin<double>(cubic[0].fY,
+ SkTMin<double>(cubic[1].fY, SkTMin<double>(cubic[2].fY, cubic[3].fY))));
+ REPORTER_ASSERT(reporter, rect.fRight == SkTMax<double>(cubic[0].fX,
+ SkTMax<double>(cubic[1].fX, SkTMax<double>(cubic[2].fX, cubic[3].fX))));
+ REPORTER_ASSERT(reporter, rect.fBottom == SkTMax<double>(cubic[0].fY,
+ SkTMax<double>(cubic[1].fY, SkTMax<double>(cubic[2].fY, cubic[3].fY))));
+ rect2.setBounds(cubic);
+ REPORTER_ASSERT(reporter, rect.intersects(&rect2));
+ // FIXME: add a recursive box subdivision method to verify that tight bounds is correct
+ SkDPoint leftTop = {rect2.fLeft, rect2.fTop};
+ REPORTER_ASSERT(reporter, rect.contains(leftTop));
+ SkDPoint rightBottom = {rect2.fRight, rect2.fBottom};
+ REPORTER_ASSERT(reporter, rect.contains(rightBottom));
+ }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PathOpsDRect", PathOpsDRectClass, DRectTest)
diff --git a/tests/PathOpsDTriangleTest.cpp b/tests/PathOpsDTriangleTest.cpp
new file mode 100644
index 0000000000..3ca797df38
--- /dev/null
+++ b/tests/PathOpsDTriangleTest.cpp
@@ -0,0 +1,47 @@
+/*
+ * 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 "SkPathOpsTriangle.h"
+#include "Test.h"
+
+static const SkDTriangle tests[] = {
+ {{{2, 0}, {3, 1}, {2, 2}}},
+ {{{3, 1}, {2, 2}, {1, 1}}},
+ {{{3, 0}, {2, 1}, {3, 2}}},
+};
+
+static const SkDPoint inPoint[] = {
+ {2.5, 1},
+ {2, 1.5},
+ {2.5, 1},
+};
+
+static const SkDPoint outPoint[] = {
+ {3, 0},
+ {2.5, 2},
+ {2.5, 2},
+};
+
+static const size_t tests_count = sizeof(tests) / sizeof(tests[0]);
+
+static void TriangleUtilitiesTest(skiatest::Reporter* reporter) {
+ for (size_t index = 0; index < tests_count; ++index) {
+ const SkDTriangle& triangle = tests[index];
+ bool result = triangle.contains(inPoint[index]);
+ if (!result) {
+ SkDebugf("%s [%d] expected point in triangle\n", __FUNCTION__, index);
+ REPORTER_ASSERT(reporter, 0);
+ }
+ result = triangle.contains(outPoint[index]);
+ if (result) {
+ SkDebugf("%s [%d] expected point outside triangle\n", __FUNCTION__, index);
+ REPORTER_ASSERT(reporter, 0);
+ }
+ }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PathOpsTriangleUtilities", PathOpsTriangleUtilitiesClass, TriangleUtilitiesTest)
diff --git a/tests/PathOpsDVectorTest.cpp b/tests/PathOpsDVectorTest.cpp
new file mode 100644
index 0000000000..48df753a06
--- /dev/null
+++ b/tests/PathOpsDVectorTest.cpp
@@ -0,0 +1,50 @@
+/*
+ * 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"
+#include "Test.h"
+
+static const SkDPoint tests[] = {
+ {0, 0},
+ {1, 0},
+ {0, 1},
+ {2, 1},
+ {1, 2},
+ {1, 1},
+ {2, 2}
+};
+
+static const size_t tests_count = sizeof(tests) / sizeof(tests[0]);
+
+static void DVectorTest(skiatest::Reporter* reporter) {
+ for (size_t index = 0; index < tests_count - 1; ++index) {
+ SkDVector v1 = tests[index + 1] - tests[index];
+ SkDVector v2 = tests[index] - tests[index + 1];
+ v1 += v2;
+ REPORTER_ASSERT(reporter, v1.fX == 0 && v1.fY == 0);
+ SkDPoint p = tests[index + 1] + v2;
+ REPORTER_ASSERT(reporter, p == tests[index]);
+ v2 -= v2;
+ REPORTER_ASSERT(reporter, v2.fX == 0 && v2.fY == 0);
+ v1 = tests[index + 1] - tests[index];
+ v1 /= 2;
+ v1 *= 2;
+ v1 -= tests[index + 1] - tests[index];
+ REPORTER_ASSERT(reporter, v1.fX == 0 && v1.fY == 0);
+ SkVector sv = v1.asSkVector();
+ REPORTER_ASSERT(reporter, sv.fX == 0 && sv.fY == 0);
+ v1 = tests[index + 1] - tests[index];
+ double lenSq = v1.lengthSquared();
+ double v1Dot = v1.dot(v1);
+ REPORTER_ASSERT(reporter, lenSq == v1Dot);
+ REPORTER_ASSERT(reporter, approximately_equal(sqrt(lenSq), v1.length()));
+ double v1Cross = v1.cross(v1);
+ REPORTER_ASSERT(reporter, v1Cross == 0);
+ }
+}
+
+#include "TestClassDef.h"
+DEFINE_TESTCLASS("PathOpsDVector", PathOpsDVectorClass, DVectorTest)