Add base types for path ops
Paths contain lines, quads, and cubics, which are
collectively curves.
To work with path intersections, intermediary curves
are constructed. For now, those intermediates use
doubles to guarantee sufficient precision.
The DVector, DPoint, DLine, DQuad, and DCubic
structs encapsulate these intermediate curves.
The DRect and DTriangle structs are created to
describe intersectable areas of interest.
The Bounds struct inherits from SkRect to create
a SkScalar-based rectangle that intersects shared
edges.
This also includes common math equalities and
debugging that the remainder of path ops builds on,
as well as a temporary top-level interface in
include/pathops/SkPathOps.h.
Review URL: https://codereview.chromium.org/12827020
git-svn-id: http://skia.googlecode.com/svn/trunk@8551 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/pathops/SkPathOps.h b/include/pathops/SkPathOps.h
new file mode 100644
index 0000000..0ad6ef2
--- /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 0000000..9efd4d6
--- /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 0000000..26cfa96
--- /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 0000000..a31b1a4
--- /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 0000000..5df3aca
--- /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 0000000..b8a02c4
--- /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 = ⊂
+ }
+ 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 0000000..8b02030
--- /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 0000000..9f1e882
--- /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 0000000..24f1aac
--- /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 0000000..d8e3f20
--- /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 0000000..e7e77e6
--- /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 0000000..5d8ebcd
--- /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 0000000..205308f
--- /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 0000000..c2f05fe
--- /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 0000000..5139c6c
--- /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 0000000..fd1e0f4
--- /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 0000000..d599dea
--- /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 0000000..e68341c
--- /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 0000000..2f1c481
--- /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 0000000..56e7c20
--- /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 0000000..68afc93
--- /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 0000000..627b0af
--- /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 0000000..1e9eb4b
--- /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 0000000..3666623
--- /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 0000000..106cd30
--- /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 0000000..8102032
--- /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 0000000..0a65bc9
--- /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, ¤t, indexPtr, endIndexPtr);
+
+ SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0);
+ tryAgain = false;
+ contourWinding = rightAngleWinding(contourList, ¤t, indexPtr, endIndexPtr, &tHit,
+ &hitDx, &tryAgain, false);
+ if (tryAgain) {
+ continue;
+ }
+ if (!binary) {
+ break;
+ }
+ oppContourWinding = rightAngleWinding(contourList, ¤t, 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 0000000..5a46807
--- /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 0000000..674213c
--- /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 0000000..48280fd
--- /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 0000000..ca68a66
--- /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 0000000..bef129c
--- /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 0000000..16ca176
--- /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 0000000..b7c91c9
--- /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 0000000..34bb658
--- /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 0000000..5a3576f
--- /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 0000000..dc9cde5
--- /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 0000000..3805100
--- /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 0000000..cbba2a3
--- /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 0000000..d5ca0c7
--- /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 0000000..9d7f876
--- /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 0000000..7b516a4
--- /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 0000000..2213c68
--- /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 0000000..3396592
--- /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 0000000..4939166
--- /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 0000000..cbeea8c
--- /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 0000000..199d7be
--- /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 0000000..aadaca8
--- /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 0000000..e367228
--- /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 0000000..2989b35
--- /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 0000000..09a92c6
--- /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 0000000..6ce0867
--- /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 0000000..14da4cc
--- /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 0000000..62b4af9
--- /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 0000000..2550448
--- /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 0000000..65479c2
--- /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 0000000..0ed1954
--- /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 0000000..60b92df
--- /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 0000000..4108f70
--- /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 0000000..551609b
--- /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 0000000..f78e2aa
--- /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 0000000..881ee0b
--- /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 0000000..3ca797d
--- /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 0000000..48df753
--- /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)