diff options
Diffstat (limited to 'src/pathops')
55 files changed, 11796 insertions, 0 deletions
diff --git a/src/pathops/SkAddIntersections.cpp b/src/pathops/SkAddIntersections.cpp new file mode 100644 index 0000000000..9efd4d63eb --- /dev/null +++ b/src/pathops/SkAddIntersections.cpp @@ -0,0 +1,430 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkAddIntersections.h" +#include "SkPathOpsBounds.h" + +#if DEBUG_ADD_INTERSECTING_TS + +static void debugShowLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " LINE_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, LINE_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " LINE_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], LINE_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + if (pts == 2) { + SkDebugf(" " T_DEBUG_STR(wtTs, 1) " " PT_DEBUG_STR, i[0][1], PT_DEBUG_DATA(i, 1)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + if (pts == 2) { + SkDebugf(" " T_DEBUG_STR(wnTs, 1), i[1][1]); + } + SkDebugf("\n"); +} + +static void debugShowQuadLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, + const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " QUAD_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowQuadIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " QUAD_DEBUG_STR " " QUAD_DEBUG_STR "\n", + __FUNCTION__, QUAD_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " QUAD_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], QUAD_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicLineIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " LINE_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), LINE_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " LINE_DEBUG_STR, i[1][0], LINE_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicQuadIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " QUAD_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), QUAD_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " QUAD_DEBUG_STR, i[1][0], QUAD_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersectionHelper& wn, const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no intersect " CUBIC_DEBUG_STR " " CUBIC_DEBUG_STR "\n", + __FUNCTION__, CUBIC_DEBUG_DATA(wt.pts()), CUBIC_DEBUG_DATA(wn.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wtTs) " " PT_DEBUG_STR, n, i[0][n], PT_DEBUG_DATA(i, n)); + } + SkDebugf(" wnTs[0]=%g " CUBIC_DEBUG_STR, i[1][0], CUBIC_DEBUG_DATA(wn.pts())); + for (int n = 1; n < pts; ++n) { + SkDebugf(" " TX_DEBUG_STR(wnTs), n, i[1][n]); + } + SkDebugf("\n"); +} + +static void debugShowCubicIntersection(int pts, const SkIntersectionHelper& wt, + const SkIntersections& i) { + SkASSERT(i.used() == pts); + if (!pts) { + SkDebugf("%s no self intersect " CUBIC_DEBUG_STR "\n", __FUNCTION__, + CUBIC_DEBUG_DATA(wt.pts())); + return; + } + SkDebugf("%s " T_DEBUG_STR(wtTs, 0) " " CUBIC_DEBUG_STR " " PT_DEBUG_STR, __FUNCTION__, + i[0][0], CUBIC_DEBUG_DATA(wt.pts()), PT_DEBUG_DATA(i, 0)); + SkDebugf(" " T_DEBUG_STR(wtTs, 1), i[1][0]); + SkDebugf("\n"); +} + +#else +static void debugShowLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowQuadLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowQuadIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicLineIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicQuadIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicIntersection(int , const SkIntersectionHelper& , + const SkIntersectionHelper& , const SkIntersections& ) { +} + +static void debugShowCubicIntersection(int , const SkIntersectionHelper& , + const SkIntersections& ) { +} +#endif + +bool AddIntersectTs(SkOpContour* test, SkOpContour* next) { + if (test != next) { + if (test->bounds().fBottom < next->bounds().fTop) { + return false; + } + if (!SkPathOpsBounds::Intersects(test->bounds(), next->bounds())) { + return true; + } + } + SkIntersectionHelper wt; + wt.init(test); + bool foundCommonContour = test == next; + do { + SkIntersectionHelper wn; + wn.init(next); + if (test == next && !wn.startAfter(wt)) { + continue; + } + do { + if (!SkPathOpsBounds::Intersects(wt.bounds(), wn.bounds())) { + continue; + } + int pts = 0; + SkIntersections ts; + bool swap = false; + switch (wt.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + swap = true; + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + case SkIntersectionHelper::kVerticalLine_Segment: + case SkIntersectionHelper::kLine_Segment: { + pts = ts.lineHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.quadHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + pts = ts.cubicHorizontal(wn.pts(), wt.left(), + wt.right(), wt.y(), wt.xFlipped()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + } + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kVerticalLine_Segment: + swap = true; + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + case SkIntersectionHelper::kVerticalLine_Segment: + case SkIntersectionHelper::kLine_Segment: { + pts = ts.lineVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.quadVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + pts = ts.cubicVertical(wn.pts(), wt.top(), + wt.bottom(), wt.x(), wt.yFlipped()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + } + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kLine_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.lineHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.lineVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: { + pts = ts.lineLine(wt.pts(), wn.pts()); + debugShowLineIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kQuad_Segment: { + swap = true; + pts = ts.quadLine(wn.pts(), wt.pts()); + debugShowQuadLineIntersection(pts, wn, wt, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + swap = true; + pts = ts.cubicLine(wn.pts(), wt.pts()); + debugShowCubicLineIntersection(pts, wn, wt, ts); + break; + } + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kQuad_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.quadHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.quadVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: { + pts = ts.quadLine(wt.pts(), wn.pts()); + debugShowQuadLineIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.quadQuad(wt.pts(), wn.pts()); + debugShowQuadIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + swap = true; + pts = ts.cubicQuad(wn.pts(), wt.pts()); + debugShowCubicQuadIntersection(pts, wn, wt, ts); + break; + } + default: + SkASSERT(0); + } + break; + case SkIntersectionHelper::kCubic_Segment: + switch (wn.segmentType()) { + case SkIntersectionHelper::kHorizontalLine_Segment: + pts = ts.cubicHorizontal(wt.pts(), wn.left(), + wn.right(), wn.y(), wn.xFlipped()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kVerticalLine_Segment: + pts = ts.cubicVertical(wt.pts(), wn.top(), + wn.bottom(), wn.x(), wn.yFlipped()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + case SkIntersectionHelper::kLine_Segment: { + pts = ts.cubicLine(wt.pts(), wn.pts()); + debugShowCubicLineIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kQuad_Segment: { + pts = ts.cubicQuad(wt.pts(), wn.pts()); + debugShowCubicQuadIntersection(pts, wt, wn, ts); + break; + } + case SkIntersectionHelper::kCubic_Segment: { + pts = ts.cubicCubic(wt.pts(), wn.pts()); + debugShowCubicIntersection(pts, wt, wn, ts); + break; + } + default: + SkASSERT(0); + } + break; + default: + SkASSERT(0); + } + if (!foundCommonContour && pts > 0) { + test->addCross(next); + next->addCross(test); + foundCommonContour = true; + } + // in addition to recording T values, record matching segment + if (pts == 2) { + if (wn.segmentType() <= SkIntersectionHelper::kLine_Segment + && wt.segmentType() <= SkIntersectionHelper::kLine_Segment) { + wt.addCoincident(wn, ts, swap); + continue; + } + if (wn.segmentType() >= SkIntersectionHelper::kQuad_Segment + && wt.segmentType() >= SkIntersectionHelper::kQuad_Segment + && ts.isCoincident(0)) { + SkASSERT(ts.coincidentUsed() == 2); + wt.addCoincident(wn, ts, swap); + continue; + } + } + for (int pt = 0; pt < pts; ++pt) { + SkASSERT(ts[0][pt] >= 0 && ts[0][pt] <= 1); + SkASSERT(ts[1][pt] >= 0 && ts[1][pt] <= 1); + SkPoint point = ts.pt(pt).asSkPoint(); + int testTAt = wt.addT(wn, point, ts[swap][pt]); + int nextTAt = wn.addT(wt, point, ts[!swap][pt]); + wt.addOtherT(testTAt, ts[!swap][pt], nextTAt); + wn.addOtherT(nextTAt, ts[swap][pt], testTAt); + } + } while (wn.advance()); + } while (wt.advance()); + return true; +} + +void AddSelfIntersectTs(SkOpContour* test) { + SkIntersectionHelper wt; + wt.init(test); + do { + if (wt.segmentType() != SkIntersectionHelper::kCubic_Segment) { + continue; + } + SkIntersections ts; + int pts = ts.cubic(wt.pts()); + debugShowCubicIntersection(pts, wt, ts); + if (!pts) { + continue; + } + SkASSERT(pts == 1); + SkASSERT(ts[0][0] >= 0 && ts[0][0] <= 1); + SkASSERT(ts[1][0] >= 0 && ts[1][0] <= 1); + SkPoint point = ts.pt(0).asSkPoint(); + int testTAt = wt.addSelfT(wt, point, ts[0][0]); + int nextTAt = wt.addT(wt, point, ts[1][0]); + wt.addOtherT(testTAt, ts[1][0], nextTAt); + wt.addOtherT(nextTAt, ts[0][0], testTAt); + } while (wt.advance()); +} + +// resolve any coincident pairs found while intersecting, and +// see if coincidence is formed by clipping non-concident segments +void CoincidenceCheck(SkTDArray<SkOpContour*>* contourList, int total) { + int contourCount = (*contourList).count(); + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = (*contourList)[cIndex]; + contour->addCoincidentPoints(); + } + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = (*contourList)[cIndex]; + contour->calcCoincidentWinding(); + } + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = (*contourList)[cIndex]; + contour->findTooCloseToCall(); + } +} diff --git a/src/pathops/SkAddIntersections.h b/src/pathops/SkAddIntersections.h new file mode 100644 index 0000000000..26cfa9633f --- /dev/null +++ b/src/pathops/SkAddIntersections.h @@ -0,0 +1,19 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkAddIntersections_DEFINED +#define SkAddIntersections_DEFINED + +#include "SkIntersectionHelper.h" +#include "SkIntersections.h" +#include "SkTDArray.h" + +bool AddIntersectTs(SkOpContour* test, SkOpContour* next); +void AddSelfIntersectTs(SkOpContour* test); +void CoincidenceCheck(SkTDArray<SkOpContour*>* contourList, int total); + +#endif + diff --git a/src/pathops/SkDCubicIntersection.cpp b/src/pathops/SkDCubicIntersection.cpp new file mode 100644 index 0000000000..a31b1a4c52 --- /dev/null +++ b/src/pathops/SkDCubicIntersection.cpp @@ -0,0 +1,451 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkIntersections.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsPoint.h" +#include "SkPathOpsQuad.h" +#include "SkPathOpsRect.h" +#include "SkReduceOrder.h" +#include "SkTDArray.h" +#include "TSearch.h" + +#if ONE_OFF_DEBUG +static const double tLimits1[2][2] = {{0.36, 0.37}, {0.63, 0.64}}; +static const double tLimits2[2][2] = {{-0.865211397, -0.865215212}, {-0.865207696, -0.865208078}}; +#endif + +#define DEBUG_QUAD_PART 0 +#define SWAP_TOP_DEBUG 0 + +static int quadPart(const SkDCubic& cubic, double tStart, double tEnd, SkReduceOrder* reducer) { + SkDCubic part = cubic.subDivide(tStart, tEnd); + SkDQuad quad = part.toQuad(); + // FIXME: should reduceOrder be looser in this use case if quartic is going to blow up on an + // extremely shallow quadratic? + int order = reducer->reduce(quad, SkReduceOrder::kFill_Style); +#if DEBUG_QUAD_PART + SkDebugf("%s cubic=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g)" + " t=(%1.17g,%1.17g)\n", __FUNCTION__, cubic[0].fX, cubic[0].fY, + cubic[1].fX, cubic[1].fY, cubic[2].fX, cubic[2].fY, + cubic[3].fX, cubic[3].fY, tStart, tEnd); + SkDebugf("%s part=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g)" + " quad=(%1.17g,%1.17g %1.17g,%1.17g %1.17g,%1.17g)\n", __FUNCTION__, + part[0].fX, part[0].fY, part[1].fX, part[1].fY, part[2].fX, part[2].fY, + part[3].fX, part[3].fY, quad[0].fX, quad[0].fY, + quad[1].fX, quad[1].fY, quad[2].fX, quad[2].fY); + SkDebugf("%s simple=(%1.17g,%1.17g", __FUNCTION__, reducer->fQuad[0].fX, reducer->fQuad[0].fY); + if (order > 1) { + SkDebugf(" %1.17g,%1.17g", reducer->fQuad[1].fX, reducer->fQuad[1].fY); + } + if (order > 2) { + SkDebugf(" %1.17g,%1.17g", reducer->fQuad[2].fX, reducer->fQuad[2].fY); + } + SkDebugf(")\n"); + SkASSERT(order < 4 && order > 0); +#endif + return order; +} + +static void intersectWithOrder(const SkDQuad& simple1, int order1, const SkDQuad& simple2, + int order2, SkIntersections& i) { + if (order1 == 3 && order2 == 3) { + i.intersect(simple1, simple2); + } else if (order1 <= 2 && order2 <= 2) { + i.intersect((const SkDLine&) simple1, (const SkDLine&) simple2); + } else if (order1 == 3 && order2 <= 2) { + i.intersect(simple1, (const SkDLine&) simple2); + } else { + SkASSERT(order1 <= 2 && order2 == 3); + i.intersect(simple2, (const SkDLine&) simple1); + i.swapPts(); + } +} + +// this flavor centers potential intersections recursively. In contrast, '2' may inadvertently +// chase intersections near quadratic ends, requiring odd hacks to find them. +static void intersect(const SkDCubic& cubic1, double t1s, double t1e, const SkDCubic& cubic2, + double t2s, double t2e, double precisionScale, SkIntersections& i) { + i.upDepth(); + SkDCubic c1 = cubic1.subDivide(t1s, t1e); + SkDCubic c2 = cubic2.subDivide(t2s, t2e); + SkTDArray<double> ts1; + // OPTIMIZE: if c1 == c2, call once (happens when detecting self-intersection) + c1.toQuadraticTs(c1.calcPrecision() * precisionScale, &ts1); + SkTDArray<double> ts2; + c2.toQuadraticTs(c2.calcPrecision() * precisionScale, &ts2); + double t1Start = t1s; + int ts1Count = ts1.count(); + for (int i1 = 0; i1 <= ts1Count; ++i1) { + const double tEnd1 = i1 < ts1Count ? ts1[i1] : 1; + const double t1 = t1s + (t1e - t1s) * tEnd1; + SkReduceOrder s1; + int o1 = quadPart(cubic1, t1Start, t1, &s1); + double t2Start = t2s; + int ts2Count = ts2.count(); + for (int i2 = 0; i2 <= ts2Count; ++i2) { + const double tEnd2 = i2 < ts2Count ? ts2[i2] : 1; + const double t2 = t2s + (t2e - t2s) * tEnd2; + if (&cubic1 == &cubic2 && t1Start >= t2Start) { + t2Start = t2; + continue; + } + SkReduceOrder s2; + int o2 = quadPart(cubic2, t2Start, t2, &s2); + #if ONE_OFF_DEBUG + char tab[] = " "; + if (tLimits1[0][0] >= t1Start && tLimits1[0][1] <= t1 + && tLimits1[1][0] >= t2Start && tLimits1[1][1] <= t2) { + SkDCubic cSub1 = cubic1.subDivide(t1Start, t1); + SkDCubic cSub2 = cubic2.subDivide(t2Start, t2); + SkDebugf("%.*s %s t1=(%1.9g,%1.9g) t2=(%1.9g,%1.9g)", i.depth()*2, tab, + __FUNCTION__, t1Start, t1, t2Start, t2); + SkIntersections xlocals; + intersectWithOrder(s1.fQuad, o1, s2.fQuad, o2, xlocals); + SkDebugf(" xlocals.fUsed=%d\n", xlocals.used()); + } + #endif + SkIntersections locals; + intersectWithOrder(s1.fQuad, o1, s2.fQuad, o2, locals); + double coStart[2] = { -1 }; + SkDPoint coPoint; + int tCount = locals.used(); + for (int tIdx = 0; tIdx < tCount; ++tIdx) { + double to1 = t1Start + (t1 - t1Start) * locals[0][tIdx]; + double to2 = t2Start + (t2 - t2Start) * locals[1][tIdx]; + // if the computed t is not sufficiently precise, iterate + SkDPoint p1 = cubic1.xyAtT(to1); + SkDPoint p2 = cubic2.xyAtT(to2); + if (p1.approximatelyEqual(p2)) { + if (locals.isCoincident(tIdx)) { + if (coStart[0] < 0) { + coStart[0] = to1; + coStart[1] = to2; + coPoint = p1; + } else { + i.insertCoincidentPair(coStart[0], to1, coStart[1], to2, coPoint, p1); + coStart[0] = -1; + } + } else if (&cubic1 != &cubic2 || !approximately_equal(to1, to2)) { + if (i.swapped()) { // FIXME: insert should respect swap + i.insert(to2, to1, p1); + } else { + i.insert(to1, to2, p1); + } + } + } else { + double offset = precisionScale / 16; // FIME: const is arbitrary: test, refine +#if 1 + double c1Bottom = tIdx == 0 ? 0 : + (t1Start + (t1 - t1Start) * locals[0][tIdx - 1] + to1) / 2; + double c1Min = SkTMax<double>(c1Bottom, to1 - offset); + double c1Top = tIdx == tCount - 1 ? 1 : + (t1Start + (t1 - t1Start) * locals[0][tIdx + 1] + to1) / 2; + double c1Max = SkTMin<double>(c1Top, to1 + offset); + double c2Min = SkTMax<double>(0., to2 - offset); + double c2Max = SkTMin<double>(1., to2 + offset); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 1 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, + __FUNCTION__, + c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max + && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, + to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset + && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, + c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max + && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, + to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset + && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); + SkDebugf("%.*s %s 1 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" + " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", + i.depth()*2, tab, __FUNCTION__, c1Bottom, c1Top, 0., 1., + to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); + SkDebugf("%.*s %s 1 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" + " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, + c1Max, c2Min, c2Max); + #endif + intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 1 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, + i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1); + #endif + if (tCount > 1) { + c1Min = SkTMax<double>(0., to1 - offset); + c1Max = SkTMin<double>(1., to1 + offset); + double c2Bottom = tIdx == 0 ? to2 : + (t2Start + (t2 - t2Start) * locals[1][tIdx - 1] + to2) / 2; + double c2Top = tIdx == tCount - 1 ? to2 : + (t2Start + (t2 - t2Start) * locals[1][tIdx + 1] + to2) / 2; + if (c2Bottom > c2Top) { + SkTSwap(c2Bottom, c2Top); + } + if (c2Bottom == to2) { + c2Bottom = 0; + } + if (c2Top == to2) { + c2Top = 1; + } + c2Min = SkTMax<double>(c2Bottom, to2 - offset); + c2Max = SkTMin<double>(c2Top, to2 + offset); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 2 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, + __FUNCTION__, + c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max + && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, + to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset + && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, + c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max + && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, + to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset + && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); + SkDebugf("%.*s %s 2 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" + " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", + i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top, + to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); + SkDebugf("%.*s %s 2 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" + " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, + c1Max, c2Min, c2Max); + #endif + intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 2 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, + i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1); + #endif + c1Min = SkTMax<double>(c1Bottom, to1 - offset); + c1Max = SkTMin<double>(c1Top, to1 + offset); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 3 contains1=%d/%d contains2=%d/%d\n", i.depth()*2, tab, + __FUNCTION__, + c1Min <= tLimits1[0][1] && tLimits1[0][0] <= c1Max + && c2Min <= tLimits1[1][1] && tLimits1[1][0] <= c2Max, + to1 - offset <= tLimits1[0][1] && tLimits1[0][0] <= to1 + offset + && to2 - offset <= tLimits1[1][1] && tLimits1[1][0] <= to2 + offset, + c1Min <= tLimits2[0][1] && tLimits2[0][0] <= c1Max + && c2Min <= tLimits2[1][1] && tLimits2[1][0] <= c2Max, + to1 - offset <= tLimits2[0][1] && tLimits2[0][0] <= to1 + offset + && to2 - offset <= tLimits2[1][1] && tLimits2[1][0] <= to2 + offset); + SkDebugf("%.*s %s 3 c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" + " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", + i.depth()*2, tab, __FUNCTION__, 0., 1., c2Bottom, c2Top, + to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); + SkDebugf("%.*s %s 3 to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" + " c2Max=%1.9g\n", i.depth()*2, tab, __FUNCTION__, to1, to2, c1Min, + c1Max, c2Min, c2Max); + #endif + intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); + #if ONE_OFF_DEBUG + SkDebugf("%.*s %s 3 i.used=%d t=%1.9g\n", i.depth()*2, tab, __FUNCTION__, + i.used(), i.used() > 0 ? i[0][i.used() - 1] : -1); + #endif + } +#else + double c1Bottom = tIdx == 0 ? 0 : + (t1Start + (t1 - t1Start) * locals.fT[0][tIdx - 1] + to1) / 2; + double c1Min = SkTMax<double>(c1Bottom, to1 - offset); + double c1Top = tIdx == tCount - 1 ? 1 : + (t1Start + (t1 - t1Start) * locals.fT[0][tIdx + 1] + to1) / 2; + double c1Max = SkTMin<double>(c1Top, to1 + offset); + double c2Bottom = tIdx == 0 ? to2 : + (t2Start + (t2 - t2Start) * locals.fT[1][tIdx - 1] + to2) / 2; + double c2Top = tIdx == tCount - 1 ? to2 : + (t2Start + (t2 - t2Start) * locals.fT[1][tIdx + 1] + to2) / 2; + if (c2Bottom > c2Top) { + SkTSwap(c2Bottom, c2Top); + } + if (c2Bottom == to2) { + c2Bottom = 0; + } + if (c2Top == to2) { + c2Top = 1; + } + double c2Min = SkTMax<double>(c2Bottom, to2 - offset); + double c2Max = SkTMin<double>(c2Top, to2 + offset); + #if ONE_OFF_DEBUG + SkDebugf("%s contains1=%d/%d contains2=%d/%d\n", __FUNCTION__, + c1Min <= 0.210357794 && 0.210357794 <= c1Max + && c2Min <= 0.223476406 && 0.223476406 <= c2Max, + to1 - offset <= 0.210357794 && 0.210357794 <= to1 + offset + && to2 - offset <= 0.223476406 && 0.223476406 <= to2 + offset, + c1Min <= 0.211324707 && 0.211324707 <= c1Max + && c2Min <= 0.211327209 && 0.211327209 <= c2Max, + to1 - offset <= 0.211324707 && 0.211324707 <= to1 + offset + && to2 - offset <= 0.211327209 && 0.211327209 <= to2 + offset); + SkDebugf("%s c1Bottom=%1.9g c1Top=%1.9g c2Bottom=%1.9g c2Top=%1.9g" + " 1-o=%1.9g 1+o=%1.9g 2-o=%1.9g 2+o=%1.9g offset=%1.9g\n", + __FUNCTION__, c1Bottom, c1Top, c2Bottom, c2Top, + to1 - offset, to1 + offset, to2 - offset, to2 + offset, offset); + SkDebugf("%s to1=%1.9g to2=%1.9g c1Min=%1.9g c1Max=%1.9g c2Min=%1.9g" + " c2Max=%1.9g\n", __FUNCTION__, to1, to2, c1Min, c1Max, c2Min, c2Max); + #endif +#endif + intersect(cubic1, c1Min, c1Max, cubic2, c2Min, c2Max, offset, i); + // FIXME: if no intersection is found, either quadratics intersected where + // cubics did not, or the intersection was missed. In the former case, expect + // the quadratics to be nearly parallel at the point of intersection, and check + // for that. + } + } + SkASSERT(coStart[0] == -1); + t2Start = t2; + } + t1Start = t1; + } + i.downDepth(); +} + +#define LINE_FRACTION 0.1 + +// intersect the end of the cubic with the other. Try lines from the end to control and opposite +// end to determine range of t on opposite cubic. +static void intersectEnd(const SkDCubic& cubic1, bool start, const SkDCubic& cubic2, + const SkDRect& bounds2, SkIntersections& i) { + SkDLine line; + int t1Index = start ? 0 : 3; + line[0] = cubic1[t1Index]; + // don't bother if the two cubics are connnected + SkTDArray<double> tVals; // OPTIMIZE: replace with hard-sized array + for (int index = 0; index < 4; ++index) { + if (index == t1Index) { + continue; + } + SkDVector dxy1 = cubic1[index] - line[0]; + dxy1 /= SkDCubic::gPrecisionUnit; + line[1] = line[0] + dxy1; + SkDRect lineBounds; + lineBounds.setBounds(line); + if (!bounds2.intersects(&lineBounds)) { + continue; + } + SkIntersections local; + if (!local.intersect(cubic2, line)) { + continue; + } + for (int idx2 = 0; idx2 < local.used(); ++idx2) { + double foundT = local[0][idx2]; + if (approximately_less_than_zero(foundT) + || approximately_greater_than_one(foundT)) { + continue; + } + if (local.pt(idx2).approximatelyEqual(line[0])) { + if (i.swapped()) { // FIXME: insert should respect swap + i.insert(foundT, start ? 0 : 1, line[0]); + } else { + i.insert(start ? 0 : 1, foundT, line[0]); + } + } else { + *tVals.append() = local[0][idx2]; + } + } + } + if (tVals.count() == 0) { + return; + } + QSort<double>(tVals.begin(), tVals.end() - 1); + double tMin1 = start ? 0 : 1 - LINE_FRACTION; + double tMax1 = start ? LINE_FRACTION : 1; + int tIdx = 0; + do { + int tLast = tIdx; + while (tLast + 1 < tVals.count() && roughly_equal(tVals[tLast + 1], tVals[tIdx])) { + ++tLast; + } + double tMin2 = SkTMax<double>(tVals[tIdx] - LINE_FRACTION, 0.0); + double tMax2 = SkTMin<double>(tVals[tLast] + LINE_FRACTION, 1.0); + int lastUsed = i.used(); + intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, i); + if (lastUsed == i.used()) { + tMin2 = SkTMax<double>(tVals[tIdx] - (1.0 / SkDCubic::gPrecisionUnit), 0.0); + tMax2 = SkTMin<double>(tVals[tLast] + (1.0 / SkDCubic::gPrecisionUnit), 1.0); + intersect(cubic1, tMin1, tMax1, cubic2, tMin2, tMax2, 1, i); + } + tIdx = tLast + 1; + } while (tIdx < tVals.count()); + return; +} + +const double CLOSE_ENOUGH = 0.001; + +static bool closeStart(const SkDCubic& cubic, int cubicIndex, SkIntersections& i, SkDPoint& pt) { + if (i[cubicIndex][0] != 0 || i[cubicIndex][1] > CLOSE_ENOUGH) { + return false; + } + pt = cubic.xyAtT((i[cubicIndex][0] + i[cubicIndex][1]) / 2); + return true; +} + +static bool closeEnd(const SkDCubic& cubic, int cubicIndex, SkIntersections& i, SkDPoint& pt) { + int last = i.used() - 1; + if (i[cubicIndex][last] != 1 || i[cubicIndex][last - 1] < 1 - CLOSE_ENOUGH) { + return false; + } + pt = cubic.xyAtT((i[cubicIndex][last] + i[cubicIndex][last - 1]) / 2); + return true; +} + +int SkIntersections::intersect(const SkDCubic& c1, const SkDCubic& c2) { + ::intersect(c1, 0, 1, c2, 0, 1, 1, *this); + // FIXME: pass in cached bounds from caller + SkDRect c1Bounds, c2Bounds; + c1Bounds.setBounds(c1); // OPTIMIZE use setRawBounds ? + c2Bounds.setBounds(c2); + intersectEnd(c1, false, c2, c2Bounds, *this); + intersectEnd(c1, true, c2, c2Bounds, *this); + bool selfIntersect = &c1 == &c2; + if (!selfIntersect) { + swap(); + intersectEnd(c2, false, c1, c1Bounds, *this); + intersectEnd(c2, true, c1, c1Bounds, *this); + swap(); + } + // If an end point and a second point very close to the end is returned, the second + // point may have been detected because the approximate quads + // intersected at the end and close to it. Verify that the second point is valid. + if (fUsed <= 1 || coincidentUsed()) { + return fUsed; + } + SkDPoint pt[2]; + if (closeStart(c1, 0, *this, pt[0]) && closeStart(c2, 1, *this, pt[1]) + && pt[0].approximatelyEqual(pt[1])) { + removeOne(1); + } + if (closeEnd(c1, 0, *this, pt[0]) && closeEnd(c2, 1, *this, pt[1]) + && pt[0].approximatelyEqual(pt[1])) { + removeOne(used() - 2); + } + return fUsed; +} + +// Up promote the quad to a cubic. +// OPTIMIZATION If this is a common use case, optimize by duplicating +// the intersect 3 loop to avoid the promotion / demotion code +int SkIntersections::intersect(const SkDCubic& cubic, const SkDQuad& quad) { + SkDCubic up = quad.toCubic(); + (void) intersect(cubic, up); + return used(); +} + +/* http://www.ag.jku.at/compass/compasssample.pdf +( Self-Intersection Problems and Approximate Implicitization by Jan B. Thomassen +Centre of Mathematics for Applications, University of Oslo http://www.cma.uio.no janbth@math.uio.no +SINTEF Applied Mathematics http://www.sintef.no ) +describes a method to find the self intersection of a cubic by taking the gradient of the implicit +form dotted with the normal, and solving for the roots. My math foo is too poor to implement this.*/ + +int SkIntersections::intersect(const SkDCubic& c) { + // check to see if x or y end points are the extrema. Are other quick rejects possible? + if (c.endsAreExtremaInXOrY()) { + return false; + } + (void) intersect(c, c); + if (used() > 0) { + SkASSERT(used() == 1); + if (fT[0][0] > fT[1][0]) { + swapPts(); + } + } + return used(); +} diff --git a/src/pathops/SkDCubicLineIntersection.cpp b/src/pathops/SkDCubicLineIntersection.cpp new file mode 100644 index 0000000000..5df3acacfd --- /dev/null +++ b/src/pathops/SkDCubicLineIntersection.cpp @@ -0,0 +1,261 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkIntersections.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" + +/* +Find the interection of a line and cubic by solving for valid t values. + +Analogous to line-quadratic intersection, solve line-cubic intersection by +representing the cubic as: + x = a(1-t)^3 + 2b(1-t)^2t + c(1-t)t^2 + dt^3 + y = e(1-t)^3 + 2f(1-t)^2t + g(1-t)t^2 + ht^3 +and the line as: + y = i*x + j (if the line is more horizontal) +or: + x = i*y + j (if the line is more vertical) + +Then using Mathematica, solve for the values of t where the cubic intersects the +line: + + (in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - x, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - i*x - j, x] + (out) -e + j + + 3 e t - 3 f t - + 3 e t^2 + 6 f t^2 - 3 g t^2 + + e t^3 - 3 f t^3 + 3 g t^3 - h t^3 + + i ( a - + 3 a t + 3 b t + + 3 a t^2 - 6 b t^2 + 3 c t^2 - + a t^3 + 3 b t^3 - 3 c t^3 + d t^3 ) + +if i goes to infinity, we can rewrite the line in terms of x. Mathematica: + + (in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - i*y - j, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y] + (out) a - j - + 3 a t + 3 b t + + 3 a t^2 - 6 b t^2 + 3 c t^2 - + a t^3 + 3 b t^3 - 3 c t^3 + d t^3 - + i ( e - + 3 e t + 3 f t + + 3 e t^2 - 6 f t^2 + 3 g t^2 - + e t^3 + 3 f t^3 - 3 g t^3 + h t^3 ) + +Solving this with Mathematica produces an expression with hundreds of terms; +instead, use Numeric Solutions recipe to solve the cubic. + +The near-horizontal case, in terms of: Ax^3 + Bx^2 + Cx + D == 0 + A = (-(-e + 3*f - 3*g + h) + i*(-a + 3*b - 3*c + d) ) + B = 3*(-( e - 2*f + g ) + i*( a - 2*b + c ) ) + C = 3*(-(-e + f ) + i*(-a + b ) ) + D = (-( e ) + i*( a ) + j ) + +The near-vertical case, in terms of: Ax^3 + Bx^2 + Cx + D == 0 + A = ( (-a + 3*b - 3*c + d) - i*(-e + 3*f - 3*g + h) ) + B = 3*( ( a - 2*b + c ) - i*( e - 2*f + g ) ) + C = 3*( (-a + b ) - i*(-e + f ) ) + D = ( ( a ) - i*( e ) - j ) + +For horizontal lines: +(in) Resultant[ + a*(1 - t)^3 + 3*b*(1 - t)^2*t + 3*c*(1 - t)*t^2 + d*t^3 - j, + e*(1 - t)^3 + 3*f*(1 - t)^2*t + 3*g*(1 - t)*t^2 + h*t^3 - y, y] +(out) e - j - + 3 e t + 3 f t + + 3 e t^2 - 6 f t^2 + 3 g t^2 - + e t^3 + 3 f t^3 - 3 g t^3 + h t^3 + */ + +class LineCubicIntersections { +public: + +LineCubicIntersections(const SkDCubic& c, const SkDLine& l, SkIntersections& i) + : cubic(c) + , line(l) + , intersections(i) { +} + +// see parallel routine in line quadratic intersections +int intersectRay(double roots[3]) { + double adj = line[1].fX - line[0].fX; + double opp = line[1].fY - line[0].fY; + SkDCubic r; + for (int n = 0; n < 4; ++n) { + r[n].fX = (cubic[n].fY - line[0].fY) * adj - (cubic[n].fX - line[0].fX) * opp; + } + double A, B, C, D; + SkDCubic::Coefficients(&r[0].fX, &A, &B, &C, &D); + return SkDCubic::RootsValidT(A, B, C, D, roots); +} + +int intersect() { + addEndPoints(); + double rootVals[3]; + int roots = intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double cubicT = rootVals[index]; + double lineT = findLineT(cubicT); + if (pinTs(&cubicT, &lineT)) { + SkDPoint pt = line.xyAtT(lineT); + intersections.insert(cubicT, lineT, pt); + } + } + return intersections.used(); +} + +int horizontalIntersect(double axisIntercept, double roots[3]) { + double A, B, C, D; + SkDCubic::Coefficients(&cubic[0].fY, &A, &B, &C, &D); + D -= axisIntercept; + return SkDCubic::RootsValidT(A, B, C, D, roots); +} + +int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { + addHorizontalEndPoints(left, right, axisIntercept); + double rootVals[3]; + int roots = horizontalIntersect(axisIntercept, rootVals); + for (int index = 0; index < roots; ++index) { + double cubicT = rootVals[index]; + SkDPoint pt = cubic.xyAtT(cubicT); + double lineT = (pt.fX - left) / (right - left); + if (pinTs(&cubicT, &lineT)) { + intersections.insert(cubicT, lineT, pt); + } + } + if (flipped) { + intersections.flip(); + } + return intersections.used(); +} + +int verticalIntersect(double axisIntercept, double roots[3]) { + double A, B, C, D; + SkDCubic::Coefficients(&cubic[0].fX, &A, &B, &C, &D); + D -= axisIntercept; + return SkDCubic::RootsValidT(A, B, C, D, roots); +} + +int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { + addVerticalEndPoints(top, bottom, axisIntercept); + double rootVals[3]; + int roots = verticalIntersect(axisIntercept, rootVals); + for (int index = 0; index < roots; ++index) { + double cubicT = rootVals[index]; + SkDPoint pt = cubic.xyAtT(cubicT); + double lineT = (pt.fY - top) / (bottom - top); + if (pinTs(&cubicT, &lineT)) { + intersections.insert(cubicT, lineT, pt); + } + } + if (flipped) { + intersections.flip(); + } + return intersections.used(); +} + +protected: + +void addEndPoints() { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + for (int lIndex = 0; lIndex < 2; lIndex++) { + if (cubic[cIndex] == line[lIndex]) { + intersections.insert(cIndex >> 1, lIndex, line[lIndex]); + } + } + } +} + +void addHorizontalEndPoints(double left, double right, double y) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + if (cubic[cIndex].fY != y) { + continue; + } + if (cubic[cIndex].fX == left) { + intersections.insert(cIndex >> 1, 0, cubic[cIndex]); + } + if (cubic[cIndex].fX == right) { + intersections.insert(cIndex >> 1, 1, cubic[cIndex]); + } + } +} + +void addVerticalEndPoints(double top, double bottom, double x) { + for (int cIndex = 0; cIndex < 4; cIndex += 3) { + if (cubic[cIndex].fX != x) { + continue; + } + if (cubic[cIndex].fY == top) { + intersections.insert(cIndex >> 1, 0, cubic[cIndex]); + } + if (cubic[cIndex].fY == bottom) { + intersections.insert(cIndex >> 1, 1, cubic[cIndex]); + } + } +} + +double findLineT(double t) { + SkDPoint xy = cubic.xyAtT(t); + double dx = line[1].fX - line[0].fX; + double dy = line[1].fY - line[0].fY; + if (fabs(dx) > fabs(dy)) { + return (xy.fX - line[0].fX) / dx; + } + return (xy.fY - line[0].fY) / dy; +} + +static bool pinTs(double* cubicT, double* lineT) { + if (!approximately_one_or_less(*lineT)) { + return false; + } + if (!approximately_zero_or_more(*lineT)) { + return false; + } + if (precisely_less_than_zero(*cubicT)) { + *cubicT = 0; + } else if (precisely_greater_than_one(*cubicT)) { + *cubicT = 1; + } + if (precisely_less_than_zero(*lineT)) { + *lineT = 0; + } else if (precisely_greater_than_one(*lineT)) { + *lineT = 1; + } + return true; +} + +private: + +const SkDCubic& cubic; +const SkDLine& line; +SkIntersections& intersections; +}; + +int SkIntersections::horizontal(const SkDCubic& cubic, double left, double right, double y, + bool flipped) { + LineCubicIntersections c(cubic, *(static_cast<SkDLine*>(0)), *this); + return c.horizontalIntersect(y, left, right, flipped); +} + +int SkIntersections::vertical(const SkDCubic& cubic, double top, double bottom, double x, + bool flipped) { + LineCubicIntersections c(cubic, *(static_cast<SkDLine*>(0)), *this); + return c.verticalIntersect(x, top, bottom, flipped); +} + +int SkIntersections::intersect(const SkDCubic& cubic, const SkDLine& line) { + LineCubicIntersections c(cubic, line, *this); + return c.intersect(); +} + +int SkIntersections::intersectRay(const SkDCubic& cubic, const SkDLine& line) { + LineCubicIntersections c(cubic, line, *this); + return c.intersectRay(fT[0]); +} diff --git a/src/pathops/SkDCubicToQuads.cpp b/src/pathops/SkDCubicToQuads.cpp new file mode 100644 index 0000000000..b8a02c4294 --- /dev/null +++ b/src/pathops/SkDCubicToQuads.cpp @@ -0,0 +1,190 @@ +/* +http://stackoverflow.com/questions/2009160/how-do-i-convert-the-2-control-points-of-a-cubic-curve-to-the-single-control-poi +*/ + +/* +Let's call the control points of the cubic Q0..Q3 and the control points of the quadratic P0..P2. +Then for degree elevation, the equations are: + +Q0 = P0 +Q1 = 1/3 P0 + 2/3 P1 +Q2 = 2/3 P1 + 1/3 P2 +Q3 = P2 +In your case you have Q0..Q3 and you're solving for P0..P2. There are two ways to compute P1 from + the equations above: + +P1 = 3/2 Q1 - 1/2 Q0 +P1 = 3/2 Q2 - 1/2 Q3 +If this is a degree-elevated cubic, then both equations will give the same answer for P1. Since + it's likely not, your best bet is to average them. So, + +P1 = -1/4 Q0 + 3/4 Q1 + 3/4 Q2 - 1/4 Q3 + + +SkDCubic defined by: P1/2 - anchor points, C1/C2 control points +|x| is the euclidean norm of x +mid-point approx of cubic: a quad that shares the same anchors with the cubic and has the + control point at C = (3·C2 - P2 + 3·C1 - P1)/4 + +Algorithm + +pick an absolute precision (prec) +Compute the Tdiv as the root of (cubic) equation +sqrt(3)/18 · |P2 - 3·C2 + 3·C1 - P1|/2 · Tdiv ^ 3 = prec +if Tdiv < 0.5 divide the cubic at Tdiv. First segment [0..Tdiv] can be approximated with by a + quadratic, with a defect less than prec, by the mid-point approximation. + Repeat from step 2 with the second resulted segment (corresponding to 1-Tdiv) +0.5<=Tdiv<1 - simply divide the cubic in two. The two halves can be approximated by the mid-point + approximation +Tdiv>=1 - the entire cubic can be approximated by the mid-point approximation + +confirmed by (maybe stolen from) +http://www.caffeineowl.com/graphics/2d/vectorial/cubic2quad01.html +// maybe in turn derived from http://www.cccg.ca/proceedings/2004/36.pdf +// also stored at http://www.cis.usouthal.edu/~hain/general/Publications/Bezier/bezier%20cccg04%20paper.pdf + +*/ + +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" +#include "SkReduceOrder.h" +#include "SkTDArray.h" +#include "TSearch.h" + +#define USE_CUBIC_END_POINTS 1 + +static double calc_t_div(const SkDCubic& cubic, double precision, double start) { + const double adjust = sqrt(3.) / 36; + SkDCubic sub; + const SkDCubic* cPtr; + if (start == 0) { + cPtr = &cubic; + } else { + // OPTIMIZE: special-case half-split ? + sub = cubic.subDivide(start, 1); + cPtr = ⊂ + } + const SkDCubic& c = *cPtr; + double dx = c[3].fX - 3 * (c[2].fX - c[1].fX) - c[0].fX; + double dy = c[3].fY - 3 * (c[2].fY - c[1].fY) - c[0].fY; + double dist = sqrt(dx * dx + dy * dy); + double tDiv3 = precision / (adjust * dist); + double t = SkDCubeRoot(tDiv3); + if (start > 0) { + t = start + (1 - start) * t; + } + return t; +} + +SkDQuad SkDCubic::toQuad() const { + SkDQuad quad; + quad[0] = fPts[0]; + const SkDPoint fromC1 = {(3 * fPts[1].fX - fPts[0].fX) / 2, (3 * fPts[1].fY - fPts[0].fY) / 2}; + const SkDPoint fromC2 = {(3 * fPts[2].fX - fPts[3].fX) / 2, (3 * fPts[2].fY - fPts[3].fY) / 2}; + quad[1].fX = (fromC1.fX + fromC2.fX) / 2; + quad[1].fY = (fromC1.fY + fromC2.fY) / 2; + quad[2] = fPts[3]; + return quad; +} + +static bool add_simple_ts(const SkDCubic& cubic, double precision, SkTDArray<double>* ts) { + double tDiv = calc_t_div(cubic, precision, 0); + if (tDiv >= 1) { + return true; + } + if (tDiv >= 0.5) { + *ts->append() = 0.5; + return true; + } + return false; +} + +static void addTs(const SkDCubic& cubic, double precision, double start, double end, + SkTDArray<double>* ts) { + double tDiv = calc_t_div(cubic, precision, 0); + double parts = ceil(1.0 / tDiv); + for (double index = 0; index < parts; ++index) { + double newT = start + (index / parts) * (end - start); + if (newT > 0 && newT < 1) { + *ts->append() = newT; + } + } +} + +// flavor that returns T values only, deferring computing the quads until they are needed +// FIXME: when called from recursive intersect 2, this could take the original cubic +// and do a more precise job when calling chop at and sub divide by computing the fractional ts. +// it would still take the prechopped cubic for reduce order and find cubic inflections +void SkDCubic::toQuadraticTs(double precision, SkTDArray<double>* ts) const { + SkReduceOrder reducer; + int order = reducer.reduce(*this, SkReduceOrder::kAllow_Quadratics, SkReduceOrder::kFill_Style); + if (order < 3) { + return; + } + double inflectT[5]; + int inflections = findInflections(inflectT); + SkASSERT(inflections <= 2); + if (!endsAreExtremaInXOrY()) { + inflections += findMaxCurvature(&inflectT[inflections]); + SkASSERT(inflections <= 5); + } + QSort<double>(inflectT, &inflectT[inflections - 1]); + // OPTIMIZATION: is this filtering common enough that it needs to be pulled out into its + // own subroutine? + while (inflections && approximately_less_than_zero(inflectT[0])) { + memmove(inflectT, &inflectT[1], sizeof(inflectT[0]) * --inflections); + } + int start = 0; + do { + int next = start + 1; + if (next >= inflections) { + break; + } + if (!approximately_equal(inflectT[start], inflectT[next])) { + ++start; + continue; + } + memmove(&inflectT[start], &inflectT[next], sizeof(inflectT[0]) * (--inflections - start)); + } while (true); + while (inflections && approximately_greater_than_one(inflectT[inflections - 1])) { + --inflections; + } + SkDCubicPair pair; + if (inflections == 1) { + pair = chopAt(inflectT[0]); + int orderP1 = reducer.reduce(pair.first(), SkReduceOrder::kNo_Quadratics, + SkReduceOrder::kFill_Style); + if (orderP1 < 2) { + --inflections; + } else { + int orderP2 = reducer.reduce(pair.second(), SkReduceOrder::kNo_Quadratics, + SkReduceOrder::kFill_Style); + if (orderP2 < 2) { + --inflections; + } + } + } + if (inflections == 0 && add_simple_ts(*this, precision, ts)) { + return; + } + if (inflections == 1) { + pair = chopAt(inflectT[0]); + addTs(pair.first(), precision, 0, inflectT[0], ts); + addTs(pair.second(), precision, inflectT[0], 1, ts); + return; + } + if (inflections > 1) { + SkDCubic part = subDivide(0, inflectT[0]); + addTs(part, precision, 0, inflectT[0], ts); + int last = inflections - 1; + for (int idx = 0; idx < last; ++idx) { + part = subDivide(inflectT[idx], inflectT[idx + 1]); + addTs(part, precision, inflectT[idx], inflectT[idx + 1], ts); + } + part = subDivide(inflectT[last], 1); + addTs(part, precision, inflectT[last], 1, ts); + return; + } + addTs(*this, precision, 0, 1, ts); +} diff --git a/src/pathops/SkDLineIntersection.cpp b/src/pathops/SkDLineIntersection.cpp new file mode 100644 index 0000000000..8b02030fa6 --- /dev/null +++ b/src/pathops/SkDLineIntersection.cpp @@ -0,0 +1,282 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkIntersections.h" +#include "SkPathOpsLine.h" + +/* Determine the intersection point of two lines. This assumes the lines are not parallel, + and that that the lines are infinite. + From http://en.wikipedia.org/wiki/Line-line_intersection + */ +SkDPoint SkIntersections::Line(const SkDLine& a, const SkDLine& b) { + double axLen = a[1].fX - a[0].fX; + double ayLen = a[1].fY - a[0].fY; + double bxLen = b[1].fX - b[0].fX; + double byLen = b[1].fY - b[0].fY; + double denom = byLen * axLen - ayLen * bxLen; + SkASSERT(denom); + double term1 = a[1].fX * a[0].fY - a[1].fY * a[0].fX; + double term2 = b[1].fX * b[0].fY - b[1].fY * b[0].fX; + SkDPoint p; + p.fX = (term1 * bxLen - axLen * term2) / denom; + p.fY = (term1 * byLen - ayLen * term2) / denom; + return p; +} + +int SkIntersections::computePoints(const SkDLine& line, int used) { + fPt[0] = line.xyAtT(fT[0][0]); + if ((fUsed = used) == 2) { + fPt[1] = line.xyAtT(fT[0][1]); + } + return fUsed; +} + +/* + Determine the intersection point of two line segments + Return FALSE if the lines don't intersect + from: http://paulbourke.net/geometry/lineline2d/ + */ + +int SkIntersections::intersect(const SkDLine& a, const SkDLine& b) { + double axLen = a[1].fX - a[0].fX; + double ayLen = a[1].fY - a[0].fY; + double bxLen = b[1].fX - b[0].fX; + double byLen = b[1].fY - b[0].fY; + /* Slopes match when denom goes to zero: + axLen / ayLen == bxLen / byLen + (ayLen * byLen) * axLen / ayLen == (ayLen * byLen) * bxLen / byLen + byLen * axLen == ayLen * bxLen + byLen * axLen - ayLen * bxLen == 0 ( == denom ) + */ + double denom = byLen * axLen - ayLen * bxLen; + double ab0y = a[0].fY - b[0].fY; + double ab0x = a[0].fX - b[0].fX; + double numerA = ab0y * bxLen - byLen * ab0x; + double numerB = ab0y * axLen - ayLen * ab0x; + bool mayNotOverlap = (numerA < 0 && denom > numerA) || (numerA > 0 && denom < numerA) + || (numerB < 0 && denom > numerB) || (numerB > 0 && denom < numerB); + numerA /= denom; + numerB /= denom; + if ((!approximately_zero(denom) || (!approximately_zero_inverse(numerA) + && !approximately_zero_inverse(numerB))) && !sk_double_isnan(numerA) + && !sk_double_isnan(numerB)) { + if (mayNotOverlap) { + return fUsed = 0; + } + fT[0][0] = numerA; + fT[1][0] = numerB; + fPt[0] = a.xyAtT(numerA); + return computePoints(a, 1); + } + /* See if the axis intercepts match: + ay - ax * ayLen / axLen == by - bx * ayLen / axLen + axLen * (ay - ax * ayLen / axLen) == axLen * (by - bx * ayLen / axLen) + axLen * ay - ax * ayLen == axLen * by - bx * ayLen + */ + // FIXME: need to use AlmostEqualUlps variant instead + if (!approximately_equal_squared(axLen * a[0].fY - ayLen * a[0].fX, + axLen * b[0].fY - ayLen * b[0].fX)) { + return fUsed = 0; + } + const double* aPtr; + const double* bPtr; + if (fabs(axLen) > fabs(ayLen) || fabs(bxLen) > fabs(byLen)) { + aPtr = &a[0].fX; + bPtr = &b[0].fX; + } else { + aPtr = &a[0].fY; + bPtr = &b[0].fY; + } + double a0 = aPtr[0]; + double a1 = aPtr[2]; + double b0 = bPtr[0]; + double b1 = bPtr[2]; + // OPTIMIZATION: restructure to reject before the divide + // e.g., if ((a0 - b0) * (a0 - a1) < 0 || abs(a0 - b0) > abs(a0 - a1)) + // (except efficient) + double aDenom = a0 - a1; + if (approximately_zero(aDenom)) { + if (!between(b0, a0, b1)) { + return fUsed = 0; + } + fT[0][0] = fT[0][1] = 0; + } else { + double at0 = (a0 - b0) / aDenom; + double at1 = (a0 - b1) / aDenom; + if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) { + return fUsed = 0; + } + fT[0][0] = SkTMax<double>(SkTMin<double>(at0, 1.0), 0.0); + fT[0][1] = SkTMax<double>(SkTMin<double>(at1, 1.0), 0.0); + } + double bDenom = b0 - b1; + if (approximately_zero(bDenom)) { + fT[1][0] = fT[1][1] = 0; + } else { + int bIn = aDenom * bDenom < 0; + fT[1][bIn] = SkTMax<double>(SkTMin<double>((b0 - a0) / bDenom, 1.0), 0.0); + fT[1][!bIn] = SkTMax<double>(SkTMin<double>((b0 - a1) / bDenom, 1.0), 0.0); + } + bool second = fabs(fT[0][0] - fT[0][1]) > FLT_EPSILON; + SkASSERT((fabs(fT[1][0] - fT[1][1]) <= FLT_EPSILON) ^ second); + return computePoints(a, 1 + second); +} + +int SkIntersections::horizontal(const SkDLine& line, double y) { + double min = line[0].fY; + double max = line[1].fY; + if (min > max) { + SkTSwap(min, max); + } + if (min > y || max < y) { + return fUsed = 0; + } + if (AlmostEqualUlps(min, max)) { + fT[0][0] = 0; + fT[0][1] = 1; + return fUsed = 2; + } + fT[0][0] = (y - line[0].fY) / (line[1].fY - line[0].fY); + return fUsed = 1; +} + +// OPTIMIZATION Given: dy = line[1].fY - line[0].fY +// and: xIntercept / (y - line[0].fY) == (line[1].fX - line[0].fX) / dy +// then: xIntercept * dy == (line[1].fX - line[0].fX) * (y - line[0].fY) +// Assuming that dy is always > 0, the line segment intercepts if: +// left * dy <= xIntercept * dy <= right * dy +// thus: left * dy <= (line[1].fX - line[0].fX) * (y - line[0].fY) <= right * dy +// (clever as this is, it does not give us the t value, so may be useful only +// as a quick reject -- and maybe not then; it takes 3 muls, 3 adds, 2 cmps) +int SkIntersections::horizontal(const SkDLine& line, double left, double right, double y) { + int result = horizontal(line, y); + if (result != 1) { + SkASSERT(result == 0); // FIXME: this is incorrect if result == 2 + return result; + } + double xIntercept = line[0].fX + fT[0][0] * (line[1].fX - line[0].fX); + if (xIntercept > right || xIntercept < left) { + return fUsed = 0; + } + return result; +} + +int SkIntersections::horizontal(const SkDLine& line, double left, double right, + double y, bool flipped) { + int result = horizontal(line, y); + switch (result) { + case 0: + break; + case 1: { + double xIntercept = line[0].fX + fT[0][0] * (line[1].fX - line[0].fX); + if (xIntercept > right || xIntercept < left) { + return fUsed = 0; + } + fT[1][0] = (xIntercept - left) / (right - left); + break; + } + case 2: + double a0 = line[0].fX; + double a1 = line[1].fX; + double b0 = flipped ? right : left; + double b1 = flipped ? left : right; + // FIXME: share common code below + double at0 = (a0 - b0) / (a0 - a1); + double at1 = (a0 - b1) / (a0 - a1); + if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) { + return fUsed = 0; + } + fT[0][0] = SkTMax<double>(SkTMin<double>(at0, 1.0), 0.0); + fT[0][1] = SkTMax<double>(SkTMin<double>(at1, 1.0), 0.0); + int bIn = (a0 - a1) * (b0 - b1) < 0; + fT[1][bIn] = SkTMax<double>(SkTMin<double>((b0 - a0) / (b0 - b1), 1.0), 0.0); + fT[1][!bIn] = SkTMax<double>(SkTMin<double>((b0 - a1) / (b0 - b1), 1.0), 0.0); + bool second = fabs(fT[0][0] - fT[0][1]) > FLT_EPSILON; + SkASSERT((fabs(fT[1][0] - fT[1][1]) <= FLT_EPSILON) ^ second); + return computePoints(line, 1 + second); + } + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].fX - [0].fX + for (int index = 0; index < result; ++index) { + fT[1][index] = 1 - fT[1][index]; + } + } + return computePoints(line, result); +} + +int SkIntersections::vertical(const SkDLine& line, double x) { + double min = line[0].fX; + double max = line[1].fX; + if (min > max) { + SkTSwap(min, max); + } + if (min > x || max < x) { + return fUsed = 0; + } + if (AlmostEqualUlps(min, max)) { + fT[0][0] = 0; + fT[0][1] = 1; + return fUsed = 2; + } + fT[0][0] = (x - line[0].fX) / (line[1].fX - line[0].fX); + return fUsed = 1; +} + +int SkIntersections::vertical(const SkDLine& line, double top, double bottom, + double x, bool flipped) { + int result = vertical(line, x); + switch (result) { + case 0: + break; + case 1: { + double yIntercept = line[0].fY + fT[0][0] * (line[1].fY - line[0].fY); + if (yIntercept > bottom || yIntercept < top) { + return fUsed = 0; + } + fT[1][0] = (yIntercept - top) / (bottom - top); + break; + } + case 2: + double a0 = line[0].fY; + double a1 = line[1].fY; + double b0 = flipped ? bottom : top; + double b1 = flipped ? top : bottom; + // FIXME: share common code above + double at0 = (a0 - b0) / (a0 - a1); + double at1 = (a0 - b1) / (a0 - a1); + if ((at0 < 0 && at1 < 0) || (at0 > 1 && at1 > 1)) { + return fUsed = 0; + } + fT[0][0] = SkTMax<double>(SkTMin<double>(at0, 1.0), 0.0); + fT[0][1] = SkTMax<double>(SkTMin<double>(at1, 1.0), 0.0); + int bIn = (a0 - a1) * (b0 - b1) < 0; + fT[1][bIn] = SkTMax<double>(SkTMin<double>((b0 - a0) / (b0 - b1), 1.0), 0.0); + fT[1][!bIn] = SkTMax<double>(SkTMin<double>((b0 - a1) / (b0 - b1), 1.0), 0.0); + bool second = fabs(fT[0][0] - fT[0][1]) > FLT_EPSILON; + SkASSERT((fabs(fT[1][0] - fT[1][1]) <= FLT_EPSILON) ^ second); + return computePoints(line, 1 + second); + break; + } + if (flipped) { + // OPTIMIZATION: instead of swapping, pass original line, use [1].fY - [0].fY + for (int index = 0; index < result; ++index) { + fT[1][index] = 1 - fT[1][index]; + } + } + return computePoints(line, result); +} + +// from http://www.bryceboe.com/wordpress/wp-content/uploads/2006/10/intersect.py +// 4 subs, 2 muls, 1 cmp +static bool ccw(const SkDPoint& A, const SkDPoint& B, const SkDPoint& C) { + return (C.fY - A.fY) * (B.fX - A.fX) > (B.fY - A.fY) * (C.fX - A.fX); +} + +// 16 subs, 8 muls, 6 cmps +bool SkIntersections::Test(const SkDLine& a, const SkDLine& b) { + return ccw(a[0], b[0], b[1]) != ccw(a[1], b[0], b[1]) + && ccw(a[0], a[1], b[0]) != ccw(a[0], a[1], b[1]); +} diff --git a/src/pathops/SkDQuadImplicit.cpp b/src/pathops/SkDQuadImplicit.cpp new file mode 100644 index 0000000000..9f1e882654 --- /dev/null +++ b/src/pathops/SkDQuadImplicit.cpp @@ -0,0 +1,117 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkDQuadImplicit.h" + +/* from http://tom.cs.byu.edu/~tom/papers/cvgip84.pdf 4.1 + * + * This paper proves that Syvester's method can compute the implicit form of + * the quadratic from the parameterized form. + * + * Given x = a*t*t + b*t + c (the parameterized form) + * y = d*t*t + e*t + f + * + * we want to find an equation of the implicit form: + * + * A*x*x + B*x*y + C*y*y + D*x + E*y + F = 0 + * + * The implicit form can be expressed as a 4x4 determinant, as shown. + * + * The resultant obtained by Syvester's method is + * + * | a b (c - x) 0 | + * | 0 a b (c - x) | + * | d e (f - y) 0 | + * | 0 d e (f - y) | + * + * which expands to + * + * d*d*x*x + -2*a*d*x*y + a*a*y*y + * + (-2*c*d*d + b*e*d - a*e*e + 2*a*f*d)*x + * + (-2*f*a*a + e*b*a - d*b*b + 2*d*c*a)*y + * + + * | a b c 0 | + * | 0 a b c | == 0. + * | d e f 0 | + * | 0 d e f | + * + * Expanding the constant determinant results in + * + * | a b c | | b c 0 | + * a*| e f 0 | + d*| a b c | == + * | d e f | | d e f | + * + * a*(a*f*f + c*e*e - c*f*d - b*e*f) + d*(b*b*f + c*c*d - c*a*f - c*e*b) + * + */ + +// OPTIMIZATION: test, verify tricky arithmetic +static bool straight_forward = true; + +SkDQuadImplicit::SkDQuadImplicit(const SkDQuad& q) { + double a, b, c; + SkDQuad::SetABC(&q[0].fX, &a, &b, &c); + double d, e, f; + SkDQuad::SetABC(&q[0].fY, &d, &e, &f); + // compute the implicit coefficients + if (straight_forward) { // 42 muls, 13 adds + fP[kXx_Coeff] = d * d; + fP[kXy_Coeff] = -2 * a * d; + fP[kYy_Coeff] = a * a; + fP[kX_Coeff] = -2*c*d*d + b*e*d - a*e*e + 2*a*f*d; + fP[kY_Coeff] = -2*f*a*a + e*b*a - d*b*b + 2*d*c*a; + fP[kC_Coeff] = a*(a*f*f + c*e*e - c*f*d - b*e*f) + + d*(b*b*f + c*c*d - c*a*f - c*e*b); + } else { // 26 muls, 11 adds + double aa = a * a; + double ad = a * d; + double dd = d * d; + fP[kXx_Coeff] = dd; + fP[kXy_Coeff] = -2 * ad; + fP[kYy_Coeff] = aa; + double be = b * e; + double bde = be * d; + double cdd = c * dd; + double ee = e * e; + fP[kX_Coeff] = -2*cdd + bde - a*ee + 2*ad*f; + double aaf = aa * f; + double abe = a * be; + double ac = a * c; + double bb_2ac = b*b - 2*ac; + fP[kY_Coeff] = -2*aaf + abe - d*bb_2ac; + fP[kC_Coeff] = aaf*f + ac*ee + d*f*bb_2ac - abe*f + c*cdd - c*bde; + } +} + + /* Given a pair of quadratics, determine their parametric coefficients. + * If the scaled coefficients are nearly equal, then the part of the quadratics + * may be coincident. + * FIXME: optimization -- since comparison short-circuits on no match, + * lazily compute the coefficients, comparing the easiest to compute first. + * xx and yy first; then xy; and so on. + */ +bool SkDQuadImplicit::match(const SkDQuadImplicit& p2) const { + int first = 0; + for (int index = 0; index <= kC_Coeff; ++index) { + if (approximately_zero(fP[index]) && approximately_zero(p2.fP[index])) { + first += first == index; + continue; + } + if (first == index) { + continue; + } + if (!AlmostEqualUlps(fP[index] * p2.fP[first], fP[first] * p2.fP[index])) { + return false; + } + } + return true; +} + +bool SkDQuadImplicit::Match(const SkDQuad& quad1, const SkDQuad& quad2) { + SkDQuadImplicit i1(quad1); // a'xx , b'xy , c'yy , d'x , e'y , f + SkDQuadImplicit i2(quad2); + return i1.match(i2); +} diff --git a/src/pathops/SkDQuadImplicit.h b/src/pathops/SkDQuadImplicit.h new file mode 100644 index 0000000000..24f1aac2ef --- /dev/null +++ b/src/pathops/SkDQuadImplicit.h @@ -0,0 +1,39 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkDQuadImplicit_DEFINED +#define SkDQuadImplicit_DEFINED + +#include "SkPathOpsQuad.h" + +class SkDQuadImplicit { +public: + explicit SkDQuadImplicit(const SkDQuad& q); + + bool match(const SkDQuadImplicit& two) const; + static bool Match(const SkDQuad& quad1, const SkDQuad& quad2); + + double x2() const { return fP[kXx_Coeff]; } + double xy() const { return fP[kXy_Coeff]; } + double y2() const { return fP[kYy_Coeff]; } + double x() const { return fP[kX_Coeff]; } + double y() const { return fP[kY_Coeff]; } + double c() const { return fP[kC_Coeff]; } + +private: + enum Coeffs { + kXx_Coeff, + kXy_Coeff, + kYy_Coeff, + kX_Coeff, + kY_Coeff, + kC_Coeff, + }; + + double fP[kC_Coeff + 1]; +}; + +#endif diff --git a/src/pathops/SkDQuadIntersection.cpp b/src/pathops/SkDQuadIntersection.cpp new file mode 100644 index 0000000000..d8e3f20a28 --- /dev/null +++ b/src/pathops/SkDQuadIntersection.cpp @@ -0,0 +1,496 @@ +// Another approach is to start with the implicit form of one curve and solve +// (seek implicit coefficients in QuadraticParameter.cpp +// by substituting in the parametric form of the other. +// The downside of this approach is that early rejects are difficult to come by. +// http://planetmath.org/encyclopedia/GaloisTheoreticDerivationOfTheQuarticFormula.html#step + + +#include "SkDQuadImplicit.h" +#include "SkIntersections.h" +#include "SkPathOpsLine.h" +#include "SkQuarticRoot.h" +#include "SkTDArray.h" +#include "TSearch.h" + +/* given the implicit form 0 = Ax^2 + Bxy + Cy^2 + Dx + Ey + F + * and given x = at^2 + bt + c (the parameterized form) + * y = dt^2 + et + f + * then + * 0 = A(at^2+bt+c)(at^2+bt+c)+B(at^2+bt+c)(dt^2+et+f)+C(dt^2+et+f)(dt^2+et+f)+D(at^2+bt+c)+E(dt^2+et+f)+F + */ + +static int findRoots(const SkDQuadImplicit& i, const SkDQuad& q2, double roots[4], + bool oneHint, int firstCubicRoot) { + double a, b, c; + SkDQuad::SetABC(&q2[0].fX, &a, &b, &c); + double d, e, f; + SkDQuad::SetABC(&q2[0].fY, &d, &e, &f); + const double t4 = i.x2() * a * a + + i.xy() * a * d + + i.y2() * d * d; + const double t3 = 2 * i.x2() * a * b + + i.xy() * (a * e + b * d) + + 2 * i.y2() * d * e; + const double t2 = i.x2() * (b * b + 2 * a * c) + + i.xy() * (c * d + b * e + a * f) + + i.y2() * (e * e + 2 * d * f) + + i.x() * a + + i.y() * d; + const double t1 = 2 * i.x2() * b * c + + i.xy() * (c * e + b * f) + + 2 * i.y2() * e * f + + i.x() * b + + i.y() * e; + const double t0 = i.x2() * c * c + + i.xy() * c * f + + i.y2() * f * f + + i.x() * c + + i.y() * f + + i.c(); + int rootCount = SkReducedQuarticRoots(t4, t3, t2, t1, t0, oneHint, roots); + if (rootCount >= 0) { + return rootCount; + } + return SkQuarticRootsReal(firstCubicRoot, t4, t3, t2, t1, t0, roots); +} + +static int addValidRoots(const double roots[4], const int count, double valid[4]) { + int result = 0; + int index; + for (index = 0; index < count; ++index) { + if (!approximately_zero_or_more(roots[index]) || !approximately_one_or_less(roots[index])) { + continue; + } + double t = 1 - roots[index]; + if (approximately_less_than_zero(t)) { + t = 0; + } else if (approximately_greater_than_one(t)) { + t = 1; + } + valid[result++] = t; + } + return result; +} + +static bool only_end_pts_in_common(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) { +// the idea here is to see at minimum do a quick reject by rotating all points +// to either side of the line formed by connecting the endpoints +// if the opposite curves points are on the line or on the other side, the +// curves at most intersect at the endpoints + for (int oddMan = 0; oddMan < 3; ++oddMan) { + const SkDPoint* endPt[2]; + for (int opp = 1; opp < 3; ++opp) { + int end = oddMan ^ opp; + if (end == 3) { + end = opp; + } + endPt[opp - 1] = &q1[end]; + } + double origX = endPt[0]->fX; + double origY = endPt[0]->fY; + double adj = endPt[1]->fX - origX; + double opp = endPt[1]->fY - origY; + double sign = (q1[oddMan].fY - origY) * adj - (q1[oddMan].fX - origX) * opp; + if (approximately_zero(sign)) { + goto tryNextHalfPlane; + } + for (int n = 0; n < 3; ++n) { + double test = (q2[n].fY - origY) * adj - (q2[n].fX - origX) * opp; + if (test * sign > 0) { + goto tryNextHalfPlane; + } + } + for (int i1 = 0; i1 < 3; i1 += 2) { + for (int i2 = 0; i2 < 3; i2 += 2) { + if (q1[i1] == q2[i2]) { + i->insert(i1 >> 1, i2 >> 1, q1[i1]); + } + } + } + SkASSERT(i->used() < 3); + return true; +tryNextHalfPlane: + ; + } + return false; +} + +// returns false if there's more than one intercept or the intercept doesn't match the point +// returns true if the intercept was successfully added or if the +// original quads need to be subdivided +static bool add_intercept(const SkDQuad& q1, const SkDQuad& q2, double tMin, double tMax, + SkIntersections* i, bool* subDivide) { + double tMid = (tMin + tMax) / 2; + SkDPoint mid = q2.xyAtT(tMid); + SkDLine line; + line[0] = line[1] = mid; + SkDVector dxdy = q2.dxdyAtT(tMid); + line[0] -= dxdy; + line[1] += dxdy; + SkIntersections rootTs; + int roots = rootTs.intersect(q1, line); + if (roots == 0) { + if (subDivide) { + *subDivide = true; + } + return true; + } + if (roots == 2) { + return false; + } + SkDPoint pt2 = q1.xyAtT(rootTs[0][0]); + if (!pt2.approximatelyEqualHalf(mid)) { + return false; + } + i->insertSwap(rootTs[0][0], tMid, pt2); + return true; +} + +static bool is_linear_inner(const SkDQuad& q1, double t1s, double t1e, const SkDQuad& q2, + double t2s, double t2e, SkIntersections* i, bool* subDivide) { + SkDQuad hull = q1.subDivide(t1s, t1e); + SkDLine line = {{hull[2], hull[0]}}; + const SkDLine* testLines[] = { &line, (const SkDLine*) &hull[0], (const SkDLine*) &hull[1] }; + size_t testCount = sizeof(testLines) / sizeof(testLines[0]); + SkTDArray<double> tsFound; + for (size_t index = 0; index < testCount; ++index) { + SkIntersections rootTs; + int roots = rootTs.intersect(q2, *testLines[index]); + for (int idx2 = 0; idx2 < roots; ++idx2) { + double t = rootTs[0][idx2]; +#ifdef SK_DEBUG + SkDPoint qPt = q2.xyAtT(t); + SkDPoint lPt = testLines[index]->xyAtT(rootTs[1][idx2]); + SkASSERT(qPt.approximatelyEqual(lPt)); +#endif + if (approximately_negative(t - t2s) || approximately_positive(t - t2e)) { + continue; + } + *tsFound.append() = rootTs[0][idx2]; + } + } + int tCount = tsFound.count(); + if (tCount <= 0) { + return true; + } + double tMin, tMax; + if (tCount == 1) { + tMin = tMax = tsFound[0]; + } else if (tCount > 1) { + QSort<double>(tsFound.begin(), tsFound.end() - 1); + tMin = tsFound[0]; + tMax = tsFound[tsFound.count() - 1]; + } + SkDPoint end = q2.xyAtT(t2s); + bool startInTriangle = hull.pointInHull(end); + if (startInTriangle) { + tMin = t2s; + } + end = q2.xyAtT(t2e); + bool endInTriangle = hull.pointInHull(end); + if (endInTriangle) { + tMax = t2e; + } + int split = 0; + SkDVector dxy1, dxy2; + if (tMin != tMax || tCount > 2) { + dxy2 = q2.dxdyAtT(tMin); + for (int index = 1; index < tCount; ++index) { + dxy1 = dxy2; + dxy2 = q2.dxdyAtT(tsFound[index]); + double dot = dxy1.dot(dxy2); + if (dot < 0) { + split = index - 1; + break; + } + } + } + if (split == 0) { // there's one point + if (add_intercept(q1, q2, tMin, tMax, i, subDivide)) { + return true; + } + i->swap(); + return is_linear_inner(q2, tMin, tMax, q1, t1s, t1e, i, subDivide); + } + // At this point, we have two ranges of t values -- treat each separately at the split + bool result; + if (add_intercept(q1, q2, tMin, tsFound[split - 1], i, subDivide)) { + result = true; + } else { + i->swap(); + result = is_linear_inner(q2, tMin, tsFound[split - 1], q1, t1s, t1e, i, subDivide); + } + if (add_intercept(q1, q2, tsFound[split], tMax, i, subDivide)) { + result = true; + } else { + i->swap(); + result |= is_linear_inner(q2, tsFound[split], tMax, q1, t1s, t1e, i, subDivide); + } + return result; +} + +static double flat_measure(const SkDQuad& q) { + SkDVector mid = q[1] - q[0]; + SkDVector dxy = q[2] - q[0]; + double length = dxy.length(); // OPTIMIZE: get rid of sqrt + return fabs(mid.cross(dxy) / length); +} + +// FIXME ? should this measure both and then use the quad that is the flattest as the line? +static bool is_linear(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) { + double measure = flat_measure(q1); + // OPTIMIZE: (get rid of sqrt) use approximately_zero + if (!approximately_zero_sqrt(measure)) { + return false; + } + return is_linear_inner(q1, 0, 1, q2, 0, 1, i, NULL); +} + +// FIXME: if flat measure is sufficiently large, then probably the quartic solution failed +static void relaxed_is_linear(const SkDQuad& q1, const SkDQuad& q2, SkIntersections* i) { + double m1 = flat_measure(q1); + double m2 = flat_measure(q2); +#if DEBUG_FLAT_QUADS + double min = SkTMin<double>(m1, m2); + if (min > 5) { + SkDebugf("%s maybe not flat enough.. %1.9g\n", __FUNCTION__, min); + } +#endif + i->reset(); + const SkDQuad& rounder = m2 < m1 ? q1 : q2; + const SkDQuad& flatter = m2 < m1 ? q2 : q1; + bool subDivide = false; + is_linear_inner(flatter, 0, 1, rounder, 0, 1, i, &subDivide); + if (subDivide) { + SkDQuadPair pair = flatter.chopAt(0.5); + SkIntersections firstI, secondI; + relaxed_is_linear(pair.first(), rounder, &firstI); + for (int index = 0; index < firstI.used(); ++index) { + i->insert(firstI[0][index] * 0.5, firstI[1][index], firstI.pt(index)); + } + relaxed_is_linear(pair.second(), rounder, &secondI); + for (int index = 0; index < secondI.used(); ++index) { + i->insert(0.5 + secondI[0][index] * 0.5, secondI[1][index], secondI.pt(index)); + } + } + if (m2 < m1) { + i->swapPts(); + } +} + +// each time through the loop, this computes values it had from the last loop +// if i == j == 1, the center values are still good +// otherwise, for i != 1 or j != 1, four of the values are still good +// and if i == 1 ^ j == 1, an additional value is good +static bool binary_search(const SkDQuad& quad1, const SkDQuad& quad2, double* t1Seed, + double* t2Seed, SkDPoint* pt) { + double tStep = ROUGH_EPSILON; + SkDPoint t1[3], t2[3]; + int calcMask = ~0; + do { + if (calcMask & (1 << 1)) t1[1] = quad1.xyAtT(*t1Seed); + if (calcMask & (1 << 4)) t2[1] = quad2.xyAtT(*t2Seed); + if (t1[1].approximatelyEqual(t2[1])) { + *pt = t1[1]; + #if ONE_OFF_DEBUG + SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) == (%1.9g,%1.9g)\n", __FUNCTION__, + t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY); + #endif + return true; + } + if (calcMask & (1 << 0)) t1[0] = quad1.xyAtT(*t1Seed - tStep); + if (calcMask & (1 << 2)) t1[2] = quad1.xyAtT(*t1Seed + tStep); + if (calcMask & (1 << 3)) t2[0] = quad2.xyAtT(*t2Seed - tStep); + if (calcMask & (1 << 5)) t2[2] = quad2.xyAtT(*t2Seed + tStep); + double dist[3][3]; + // OPTIMIZE: using calcMask value permits skipping some distance calcuations + // if prior loop's results are moved to correct slot for reuse + dist[1][1] = t1[1].distanceSquared(t2[1]); + int best_i = 1, best_j = 1; + for (int i = 0; i < 3; ++i) { + for (int j = 0; j < 3; ++j) { + if (i == 1 && j == 1) { + continue; + } + dist[i][j] = t1[i].distanceSquared(t2[j]); + if (dist[best_i][best_j] > dist[i][j]) { + best_i = i; + best_j = j; + } + } + } + if (best_i == 1 && best_j == 1) { + tStep /= 2; + if (tStep < FLT_EPSILON_HALF) { + break; + } + calcMask = (1 << 0) | (1 << 2) | (1 << 3) | (1 << 5); + continue; + } + if (best_i == 0) { + *t1Seed -= tStep; + t1[2] = t1[1]; + t1[1] = t1[0]; + calcMask = 1 << 0; + } else if (best_i == 2) { + *t1Seed += tStep; + t1[0] = t1[1]; + t1[1] = t1[2]; + calcMask = 1 << 2; + } else { + calcMask = 0; + } + if (best_j == 0) { + *t2Seed -= tStep; + t2[2] = t2[1]; + t2[1] = t2[0]; + calcMask |= 1 << 3; + } else if (best_j == 2) { + *t2Seed += tStep; + t2[0] = t2[1]; + t2[1] = t2[2]; + calcMask |= 1 << 5; + } + } while (true); +#if ONE_OFF_DEBUG + SkDebugf("%s t1=%1.9g t2=%1.9g (%1.9g,%1.9g) != (%1.9g,%1.9g) %s\n", __FUNCTION__, + t1Seed, t2Seed, t1[1].fX, t1[1].fY, t1[2].fX, t1[2].fY); +#endif + return false; +} + +int SkIntersections::intersect(const SkDQuad& q1, const SkDQuad& q2) { + // if the quads share an end point, check to see if they overlap + + if (only_end_pts_in_common(q1, q2, this)) { + return fUsed; + } + if (only_end_pts_in_common(q2, q1, this)) { + swapPts(); + return fUsed; + } + // see if either quad is really a line + if (is_linear(q1, q2, this)) { + return fUsed; + } + if (is_linear(q2, q1, this)) { + swapPts(); + return fUsed; + } + SkDQuadImplicit i1(q1); + SkDQuadImplicit i2(q2); + if (i1.match(i2)) { + // FIXME: compute T values + // compute the intersections of the ends to find the coincident span + bool useVertical = fabs(q1[0].fX - q1[2].fX) < fabs(q1[0].fY - q1[2].fY); + double t; + if ((t = SkIntersections::Axial(q1, q2[0], useVertical)) >= 0) { + insertCoincident(t, 0, q2[0]); + } + if ((t = SkIntersections::Axial(q1, q2[2], useVertical)) >= 0) { + insertCoincident(t, 1, q2[2]); + } + useVertical = fabs(q2[0].fX - q2[2].fX) < fabs(q2[0].fY - q2[2].fY); + if ((t = SkIntersections::Axial(q2, q1[0], useVertical)) >= 0) { + insertCoincident(0, t, q1[0]); + } + if ((t = SkIntersections::Axial(q2, q1[2], useVertical)) >= 0) { + insertCoincident(1, t, q1[2]); + } + SkASSERT(coincidentUsed() <= 2); + return fUsed; + } + int index; + bool useCubic = q1[0] == q2[0] || q1[0] == q2[2] || q1[2] == q2[0]; + double roots1[4]; + int rootCount = findRoots(i2, q1, roots1, useCubic, 0); + // OPTIMIZATION: could short circuit here if all roots are < 0 or > 1 + double roots1Copy[4]; + int r1Count = addValidRoots(roots1, rootCount, roots1Copy); + SkDPoint pts1[4]; + for (index = 0; index < r1Count; ++index) { + pts1[index] = q1.xyAtT(roots1Copy[index]); + } + double roots2[4]; + int rootCount2 = findRoots(i1, q2, roots2, useCubic, 0); + double roots2Copy[4]; + int r2Count = addValidRoots(roots2, rootCount2, roots2Copy); + SkDPoint pts2[4]; + for (index = 0; index < r2Count; ++index) { + pts2[index] = q2.xyAtT(roots2Copy[index]); + } + if (r1Count == r2Count && r1Count <= 1) { + if (r1Count == 1) { + if (pts1[0].approximatelyEqualHalf(pts2[0])) { + insert(roots1Copy[0], roots2Copy[0], pts1[0]); + } else if (pts1[0].moreRoughlyEqual(pts2[0])) { + // experiment: try to find intersection by chasing t + rootCount = findRoots(i2, q1, roots1, useCubic, 0); + (void) addValidRoots(roots1, rootCount, roots1Copy); + rootCount2 = findRoots(i1, q2, roots2, useCubic, 0); + (void) addValidRoots(roots2, rootCount2, roots2Copy); + if (binary_search(q1, q2, roots1Copy, roots2Copy, pts1)) { + insert(roots1Copy[0], roots2Copy[0], pts1[0]); + } + } + } + return fUsed; + } + int closest[4]; + double dist[4]; + bool foundSomething = false; + for (index = 0; index < r1Count; ++index) { + dist[index] = DBL_MAX; + closest[index] = -1; + for (int ndex2 = 0; ndex2 < r2Count; ++ndex2) { + if (!pts2[ndex2].approximatelyEqualHalf(pts1[index])) { + continue; + } + double dx = pts2[ndex2].fX - pts1[index].fX; + double dy = pts2[ndex2].fY - pts1[index].fY; + double distance = dx * dx + dy * dy; + if (dist[index] <= distance) { + continue; + } + for (int outer = 0; outer < index; ++outer) { + if (closest[outer] != ndex2) { + continue; + } + if (dist[outer] < distance) { + goto next; + } + closest[outer] = -1; + } + dist[index] = distance; + closest[index] = ndex2; + foundSomething = true; + next: + ; + } + } + if (r1Count && r2Count && !foundSomething) { + relaxed_is_linear(q1, q2, this); + return fUsed; + } + int used = 0; + do { + double lowest = DBL_MAX; + int lowestIndex = -1; + for (index = 0; index < r1Count; ++index) { + if (closest[index] < 0) { + continue; + } + if (roots1Copy[index] < lowest) { + lowestIndex = index; + lowest = roots1Copy[index]; + } + } + if (lowestIndex < 0) { + break; + } + insert(roots1Copy[lowestIndex], roots2Copy[closest[lowestIndex]], + pts1[lowestIndex]); + closest[lowestIndex] = -1; + } while (++used < r1Count); + return fUsed; +} diff --git a/src/pathops/SkDQuadLineIntersection.cpp b/src/pathops/SkDQuadLineIntersection.cpp new file mode 100644 index 0000000000..e7e77e69e2 --- /dev/null +++ b/src/pathops/SkDQuadLineIntersection.cpp @@ -0,0 +1,333 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkIntersections.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" + +/* +Find the interection of a line and quadratic by solving for valid t values. + +From http://stackoverflow.com/questions/1853637/how-to-find-the-mathematical-function-defining-a-bezier-curve + +"A Bezier curve is a parametric function. A quadratic Bezier curve (i.e. three +control points) can be expressed as: F(t) = A(1 - t)^2 + B(1 - t)t + Ct^2 where +A, B and C are points and t goes from zero to one. + +This will give you two equations: + + x = a(1 - t)^2 + b(1 - t)t + ct^2 + y = d(1 - t)^2 + e(1 - t)t + ft^2 + +If you add for instance the line equation (y = kx + m) to that, you'll end up +with three equations and three unknowns (x, y and t)." + +Similar to above, the quadratic is represented as + x = a(1-t)^2 + 2b(1-t)t + ct^2 + y = d(1-t)^2 + 2e(1-t)t + ft^2 +and the line as + y = g*x + h + +Using Mathematica, solve for the values of t where the quadratic intersects the +line: + + (in) t1 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - x, + d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - g*x - h, x] + (out) -d + h + 2 d t - 2 e t - d t^2 + 2 e t^2 - f t^2 + + g (a - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2) + (in) Solve[t1 == 0, t] + (out) { + {t -> (-2 d + 2 e + 2 a g - 2 b g - + Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 - + 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) / + (2 (-d + 2 e - f + a g - 2 b g + c g)) + }, + {t -> (-2 d + 2 e + 2 a g - 2 b g + + Sqrt[(2 d - 2 e - 2 a g + 2 b g)^2 - + 4 (-d + 2 e - f + a g - 2 b g + c g) (-d + a g + h)]) / + (2 (-d + 2 e - f + a g - 2 b g + c g)) + } + } + +Using the results above (when the line tends towards horizontal) + A = (-(d - 2*e + f) + g*(a - 2*b + c) ) + B = 2*( (d - e ) - g*(a - b ) ) + C = (-(d ) + g*(a ) + h ) + +If g goes to infinity, we can rewrite the line in terms of x. + x = g'*y + h' + +And solve accordingly in Mathematica: + + (in) t2 = Resultant[a*(1 - t)^2 + 2*b*(1 - t)*t + c*t^2 - g'*y - h', + d*(1 - t)^2 + 2*e*(1 - t)*t + f*t^2 - y, y] + (out) a - h' - 2 a t + 2 b t + a t^2 - 2 b t^2 + c t^2 - + g' (d - 2 d t + 2 e t + d t^2 - 2 e t^2 + f t^2) + (in) Solve[t2 == 0, t] + (out) { + {t -> (2 a - 2 b - 2 d g' + 2 e g' - + Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 - + 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')]) / + (2 (a - 2 b + c - d g' + 2 e g' - f g')) + }, + {t -> (2 a - 2 b - 2 d g' + 2 e g' + + Sqrt[(-2 a + 2 b + 2 d g' - 2 e g')^2 - + 4 (a - 2 b + c - d g' + 2 e g' - f g') (a - d g' - h')])/ + (2 (a - 2 b + c - d g' + 2 e g' - f g')) + } + } + +Thus, if the slope of the line tends towards vertical, we use: + A = ( (a - 2*b + c) - g'*(d - 2*e + f) ) + B = 2*(-(a - b ) + g'*(d - e ) ) + C = ( (a ) - g'*(d ) - h' ) + */ + + +class LineQuadraticIntersections { +public: + LineQuadraticIntersections(const SkDQuad& q, const SkDLine& l, SkIntersections* i) + : quad(q) + , line(l) + , intersections(i) { + } + + int intersectRay(double roots[2]) { + /* + solve by rotating line+quad so line is horizontal, then finding the roots + set up matrix to rotate quad to x-axis + |cos(a) -sin(a)| + |sin(a) cos(a)| + note that cos(a) = A(djacent) / Hypoteneuse + sin(a) = O(pposite) / Hypoteneuse + since we are computing Ts, we can ignore hypoteneuse, the scale factor: + | A -O | + | O A | + A = line[1].fX - line[0].fX (adjacent side of the right triangle) + O = line[1].fY - line[0].fY (opposite side of the right triangle) + for each of the three points (e.g. n = 0 to 2) + quad[n].fY' = (quad[n].fY - line[0].fY) * A - (quad[n].fX - line[0].fX) * O + */ + double adj = line[1].fX - line[0].fX; + double opp = line[1].fY - line[0].fY; + double r[3]; + for (int n = 0; n < 3; ++n) { + r[n] = (quad[n].fY - line[0].fY) * adj - (quad[n].fX - line[0].fX) * opp; + } + double A = r[2]; + double B = r[1]; + double C = r[0]; + A += C - 2 * B; // A = a - 2*b + c + B -= C; // B = -(b - c) + return SkDQuad::RootsValidT(A, 2 * B, C, roots); + } + + int intersect() { + addEndPoints(); + double rootVals[2]; + int roots = intersectRay(rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + double lineT = findLineT(quadT); + if (PinTs(&quadT, &lineT)) { + SkDPoint pt = line.xyAtT(lineT); + intersections->insert(quadT, lineT, pt); + } + } + return intersections->used(); + } + + int horizontalIntersect(double axisIntercept, double roots[2]) { + double D = quad[2].fY; // f + double E = quad[1].fY; // e + double F = quad[0].fY; // d + D += F - 2 * E; // D = d - 2*e + f + E -= F; // E = -(d - e) + F -= axisIntercept; + return SkDQuad::RootsValidT(D, 2 * E, F, roots); + } + + int horizontalIntersect(double axisIntercept, double left, double right, bool flipped) { + addHorizontalEndPoints(left, right, axisIntercept); + double rootVals[2]; + int roots = horizontalIntersect(axisIntercept, rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + SkDPoint pt = quad.xyAtT(quadT); + double lineT = (pt.fX - left) / (right - left); + if (PinTs(&quadT, &lineT)) { + intersections->insert(quadT, lineT, pt); + } + } + if (flipped) { + intersections->flip(); + } + return intersections->used(); + } + + int verticalIntersect(double axisIntercept, double roots[2]) { + double D = quad[2].fX; // f + double E = quad[1].fX; // e + double F = quad[0].fX; // d + D += F - 2 * E; // D = d - 2*e + f + E -= F; // E = -(d - e) + F -= axisIntercept; + return SkDQuad::RootsValidT(D, 2 * E, F, roots); + } + + int verticalIntersect(double axisIntercept, double top, double bottom, bool flipped) { + addVerticalEndPoints(top, bottom, axisIntercept); + double rootVals[2]; + int roots = verticalIntersect(axisIntercept, rootVals); + for (int index = 0; index < roots; ++index) { + double quadT = rootVals[index]; + SkDPoint pt = quad.xyAtT(quadT); + double lineT = (pt.fY - top) / (bottom - top); + if (PinTs(&quadT, &lineT)) { + intersections->insert(quadT, lineT, pt); + } + } + if (flipped) { + intersections->flip(); + } + return intersections->used(); + } + +protected: + // add endpoints first to get zero and one t values exactly + void addEndPoints() { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + for (int lIndex = 0; lIndex < 2; lIndex++) { + if (quad[qIndex] == line[lIndex]) { + intersections->insert(qIndex >> 1, lIndex, line[lIndex]); + } + } + } + } + + void addHorizontalEndPoints(double left, double right, double y) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + if (quad[qIndex].fY != y) { + continue; + } + if (quad[qIndex].fX == left) { + intersections->insert(qIndex >> 1, 0, quad[qIndex]); + } + if (quad[qIndex].fX == right) { + intersections->insert(qIndex >> 1, 1, quad[qIndex]); + } + } + } + + void addVerticalEndPoints(double top, double bottom, double x) { + for (int qIndex = 0; qIndex < 3; qIndex += 2) { + if (quad[qIndex].fX != x) { + continue; + } + if (quad[qIndex].fY == top) { + intersections->insert(qIndex >> 1, 0, quad[qIndex]); + } + if (quad[qIndex].fY == bottom) { + intersections->insert(qIndex >> 1, 1, quad[qIndex]); + } + } + } + + double findLineT(double t) { + SkDPoint xy = quad.xyAtT(t); + double dx = line[1].fX - line[0].fX; + double dy = line[1].fY - line[0].fY; + if (fabs(dx) > fabs(dy)) { + return (xy.fX - line[0].fX) / dx; + } + return (xy.fY - line[0].fY) / dy; + } + + static bool PinTs(double* quadT, double* lineT) { + if (!approximately_one_or_less(*lineT)) { + return false; + } + if (!approximately_zero_or_more(*lineT)) { + return false; + } + if (precisely_less_than_zero(*quadT)) { + *quadT = 0; + } else if (precisely_greater_than_one(*quadT)) { + *quadT = 1; + } + if (precisely_less_than_zero(*lineT)) { + *lineT = 0; + } else if (precisely_greater_than_one(*lineT)) { + *lineT = 1; + } + return true; + } + +private: + const SkDQuad& quad; + const SkDLine& line; + SkIntersections* intersections; +}; + +// utility for pairs of coincident quads +static double horizontalIntersect(const SkDQuad& quad, const SkDPoint& pt) { + LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)), + static_cast<SkIntersections*>(0)); + double rootVals[2]; + int roots = q.horizontalIntersect(pt.fY, rootVals); + for (int index = 0; index < roots; ++index) { + double t = rootVals[index]; + SkDPoint qPt = quad.xyAtT(t); + if (AlmostEqualUlps(qPt.fX, pt.fX)) { + return t; + } + } + return -1; +} + +static double verticalIntersect(const SkDQuad& quad, const SkDPoint& pt) { + LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)), + static_cast<SkIntersections*>(0)); + double rootVals[2]; + int roots = q.verticalIntersect(pt.fX, rootVals); + for (int index = 0; index < roots; ++index) { + double t = rootVals[index]; + SkDPoint qPt = quad.xyAtT(t); + if (AlmostEqualUlps(qPt.fY, pt.fY)) { + return t; + } + } + return -1; +} + +double SkIntersections::Axial(const SkDQuad& q1, const SkDPoint& p, bool vertical) { + if (vertical) { + return verticalIntersect(q1, p); + } + return horizontalIntersect(q1, p); +} + +int SkIntersections::horizontal(const SkDQuad& quad, double left, double right, double y, + bool flipped) { + LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)), this); + return q.horizontalIntersect(y, left, right, flipped); +} + +int SkIntersections::vertical(const SkDQuad& quad, double top, double bottom, double x, + bool flipped) { + LineQuadraticIntersections q(quad, *(static_cast<SkDLine*>(0)), this); + return q.verticalIntersect(x, top, bottom, flipped); +} + +int SkIntersections::intersect(const SkDQuad& quad, const SkDLine& line) { + LineQuadraticIntersections q(quad, line, this); + return q.intersect(); +} + +int SkIntersections::intersectRay(const SkDQuad& quad, const SkDLine& line) { + LineQuadraticIntersections q(quad, line, this); + return q.intersectRay(fT[0]); +} diff --git a/src/pathops/SkIntersectionHelper.h b/src/pathops/SkIntersectionHelper.h new file mode 100644 index 0000000000..5d8ebcd590 --- /dev/null +++ b/src/pathops/SkIntersectionHelper.h @@ -0,0 +1,135 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkOpContour.h" +#include "SkPath.h" + +class SkIntersectionHelper { +public: + enum SegmentType { + kHorizontalLine_Segment = -1, + kVerticalLine_Segment = 0, + kLine_Segment = SkPath::kLine_Verb, + kQuad_Segment = SkPath::kQuad_Verb, + kCubic_Segment = SkPath::kCubic_Verb, + }; + + void addCoincident(SkIntersectionHelper& other, const SkIntersections& ts, bool swap) { + fContour->addCoincident(fIndex, other.fContour, other.fIndex, ts, swap); + } + + // FIXME: does it make sense to write otherIndex now if we're going to + // fix it up later? + void addOtherT(int index, double otherT, int otherIndex) { + fContour->addOtherT(fIndex, index, otherT, otherIndex); + } + + // Avoid collapsing t values that are close to the same since + // we walk ts to describe consecutive intersections. Since a pair of ts can + // be nearly equal, any problems caused by this should be taken care + // of later. + // On the edge or out of range values are negative; add 2 to get end + int addT(const SkIntersectionHelper& other, const SkPoint& pt, double newT) { + return fContour->addT(fIndex, other.fContour, other.fIndex, pt, newT); + } + + int addSelfT(const SkIntersectionHelper& other, const SkPoint& pt, double newT) { + return fContour->addSelfT(fIndex, other.fContour, other.fIndex, pt, newT); + } + + int addUnsortableT(const SkIntersectionHelper& other, bool start, const SkPoint& pt, + double newT) { + return fContour->addUnsortableT(fIndex, other.fContour, other.fIndex, start, pt, newT); + } + + bool advance() { + return ++fIndex < fLast; + } + + SkScalar bottom() const { + return bounds().fBottom; + } + + const SkPathOpsBounds& bounds() const { + return fContour->segments()[fIndex].bounds(); + } + + void init(SkOpContour* contour) { + fContour = contour; + fIndex = 0; + fLast = contour->segments().count(); + } + + bool isAdjacent(const SkIntersectionHelper& next) { + return fContour == next.fContour && fIndex + 1 == next.fIndex; + } + + bool isFirstLast(const SkIntersectionHelper& next) { + return fContour == next.fContour && fIndex == 0 + && next.fIndex == fLast - 1; + } + + SkScalar left() const { + return bounds().fLeft; + } + + const SkPoint* pts() const { + return fContour->segments()[fIndex].pts(); + } + + SkScalar right() const { + return bounds().fRight; + } + + SegmentType segmentType() const { + const SkOpSegment& segment = fContour->segments()[fIndex]; + SegmentType type = (SegmentType) segment.verb(); + if (type != kLine_Segment) { + return type; + } + if (segment.isHorizontal()) { + return kHorizontalLine_Segment; + } + if (segment.isVertical()) { + return kVerticalLine_Segment; + } + return kLine_Segment; + } + + bool startAfter(const SkIntersectionHelper& after) { + fIndex = after.fIndex; + return advance(); + } + + SkScalar top() const { + return bounds().fTop; + } + + SkPath::Verb verb() const { + return fContour->segments()[fIndex].verb(); + } + + SkScalar x() const { + return bounds().fLeft; + } + + bool xFlipped() const { + return x() != pts()[0].fX; + } + + SkScalar y() const { + return bounds().fTop; + } + + bool yFlipped() const { + return y() != pts()[0].fY; + } + +private: + SkOpContour* fContour; + int fIndex; + int fLast; +}; diff --git a/src/pathops/SkIntersections.cpp b/src/pathops/SkIntersections.cpp new file mode 100644 index 0000000000..205308f646 --- /dev/null +++ b/src/pathops/SkIntersections.cpp @@ -0,0 +1,261 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkIntersections.h" + +int (SkIntersections::*CurveVertical[])(const SkPoint[], SkScalar, SkScalar, SkScalar, bool) = { + NULL, + &SkIntersections::verticalLine, + &SkIntersections::verticalQuad, + &SkIntersections::verticalCubic +}; + +int (SkIntersections::*CurveRay[])(const SkPoint[], const SkDLine&) = { + NULL, + NULL, + &SkIntersections::quadRay, + &SkIntersections::cubicRay +}; + +int SkIntersections::coincidentUsed() const { + if (!fIsCoincident[0]) { + SkASSERT(!fIsCoincident[0]); + return 0; + } + int count = 0; + SkDEBUGCODE(int count2 = 0;) + for (int index = 0; index < fUsed; ++index) { + if (fIsCoincident[0] & (1 << index)) { + ++count; + } +#ifdef SK_DEBUG + if (fIsCoincident[1] & (1 << index)) { + ++count2; + } +#endif + } + SkASSERT(count == count2); + return count; +} + +int SkIntersections::cubicRay(const SkPoint pts[4], const SkDLine& line) { + SkDCubic cubic; + cubic.set(pts); + return intersectRay(cubic, line); +} + +void SkIntersections::flip() { + for (int index = 0; index < fUsed; ++index) { + fT[1][index] = 1 - fT[1][index]; + } +} + +void SkIntersections::insertCoincidentPair(double s1, double e1, double s2, double e2, + const SkDPoint& startPt, const SkDPoint& endPt) { + if (fSwap) { + remove(s2, e2, startPt, endPt); + } else { + remove(s1, e1, startPt, endPt); + } + SkASSERT(coincidentUsed() == fUsed); + SkASSERT((coincidentUsed() & 1) != 1); + int i1 = 0; + int i2 = 0; + do { + while (i1 < fUsed && !(fIsCoincident[fSwap] & (1 << i1))) { + ++i1; + } + if (i1 == fUsed) { + break; + } + SkASSERT(i1 < fUsed); + int iEnd1 = i1 + 1; + while (!(fIsCoincident[fSwap] & (1 << iEnd1))) { + ++iEnd1; + } + SkASSERT(iEnd1 < fUsed); + double cs1 = fT[fSwap][i1]; + double ce1 = fT[fSwap][iEnd1]; + bool s1in = between(cs1, s1, ce1) || startPt.approximatelyEqual(fPt[i1]) + || startPt.approximatelyEqual(fPt[iEnd1]); + bool e1in = between(cs1, e1, ce1) || endPt.approximatelyEqual(fPt[i1]) + || endPt.approximatelyEqual(fPt[iEnd1]); + while (i2 < fUsed && !(fIsCoincident[fSwap ^ 1] & (1 << i2))) { + ++i2; + } + int iEnd2 = i2 + 1; + while (!(fIsCoincident[fSwap ^ 1] & (1 << iEnd2))) { + ++iEnd2; + } + SkASSERT(iEnd2 < fUsed); + double cs2 = fT[fSwap ^ 1][i2]; + double ce2 = fT[fSwap ^ 1][iEnd2]; + bool s2in = between(cs2, s2, ce2) || startPt.approximatelyEqual(fPt[i2]) + || startPt.approximatelyEqual(fPt[iEnd2]); + bool e2in = between(cs2, e2, ce2) || endPt.approximatelyEqual(fPt[i2]) + || endPt.approximatelyEqual(fPt[iEnd2]); + if ((s1in | e1in) & (s2in | e2in)) { + if (s1 < cs1) { + fT[fSwap][i1] = s1; + fPt[i1] = startPt; + } else if (e1 < cs1) { + fT[fSwap][i1] = e1; + fPt[i1] = endPt; + } + if (s1 > ce1) { + fT[fSwap][iEnd1] = s1; + fPt[iEnd1] = startPt; + } else if (e1 > ce1) { + fT[fSwap][iEnd1] = e1; + fPt[iEnd1] = endPt; + } + if (s2 > e2) { + SkTSwap(cs2, ce2); + SkTSwap(i2, iEnd2); + } + if (s2 < cs2) { + fT[fSwap ^ 1][i2] = s2; + } else if (e2 < cs2) { + fT[fSwap ^ 1][i2] = e2; + } + if (s2 > ce2) { + fT[fSwap ^ 1][iEnd2] = s2; + } else if (e2 > ce2) { + fT[fSwap ^ 1][iEnd2] = e2; + } + return; + } + } while (true); + SkASSERT(fUsed < 9); + insertCoincident(s1, s2, startPt); + insertCoincident(e1, e2, endPt); +} + +int SkIntersections::insert(double one, double two, const SkDPoint& pt) { + SkASSERT(fUsed <= 1 || fT[0][0] <= fT[0][1]); + int index; + for (index = 0; index < fUsed; ++index) { + double oldOne = fT[0][index]; + double oldTwo = fT[1][index]; + if (roughly_equal(oldOne, one) && roughly_equal(oldTwo, two)) { + if ((precisely_zero(one) && !precisely_zero(oldOne)) + || (precisely_equal(one, 1) && !precisely_equal(oldOne, 1)) + || (precisely_zero(two) && !precisely_zero(oldTwo)) + || (precisely_equal(two, 1) && !precisely_equal(oldTwo, 1))) { + fT[0][index] = one; + fT[1][index] = two; + fPt[index] = pt; + } + return -1; + } + #if ONE_OFF_DEBUG + if (pt.roughlyEqual(fPt[index])) { + SkDebugf("%s t=%1.9g pts roughly equal\n", __FUNCTION__, one); + } + #endif + if (fT[0][index] > one) { + break; + } + } + SkASSERT(fUsed < 9); + int remaining = fUsed - index; + if (remaining > 0) { + memmove(&fPt[index + 1], &fPt[index], sizeof(fPt[0]) * remaining); + memmove(&fT[0][index + 1], &fT[0][index], sizeof(fT[0][0]) * remaining); + memmove(&fT[1][index + 1], &fT[1][index], sizeof(fT[1][0]) * remaining); + fIsCoincident[0] += fIsCoincident[0] & ~((1 << index) - 1); + fIsCoincident[1] += fIsCoincident[1] & ~((1 << index) - 1); + } + fPt[index] = pt; + fT[0][index] = one; + fT[1][index] = two; + ++fUsed; + return index; +} + +void SkIntersections::insertCoincident(double one, double two, const SkDPoint& pt) { + int index = insertSwap(one, two, pt); + int bit = 1 << index; + fIsCoincident[0] |= bit; + fIsCoincident[1] |= bit; +} + +void SkIntersections::offset(int base, double start, double end) { + for (int index = base; index < fUsed; ++index) { + double val = fT[fSwap][index]; + val *= end - start; + val += start; + fT[fSwap][index] = val; + } +} + +int SkIntersections::quadRay(const SkPoint pts[3], const SkDLine& line) { + SkDQuad quad; + quad.set(pts); + return intersectRay(quad, line); +} + +void SkIntersections::quickRemoveOne(int index, int replace) { + if (index < replace) { + fT[0][index] = fT[0][replace]; + } +} + +void SkIntersections::remove(double one, double two, const SkDPoint& startPt, + const SkDPoint& endPt) { + for (int index = fUsed - 1; index >= 0; --index) { + if (!(fIsCoincident[0] & (1 << index)) && (between(one, fT[fSwap][index], two) + || startPt.approximatelyEqual(fPt[index]) + || endPt.approximatelyEqual(fPt[index]))) { + SkASSERT(fUsed > 0); + removeOne(index); + } + } +} + +void SkIntersections::removeOne(int index) { + int remaining = --fUsed - index; + if (remaining <= 0) { + return; + } + memmove(&fPt[index], &fPt[index + 1], sizeof(fPt[0]) * remaining); + memmove(&fT[0][index], &fT[0][index + 1], sizeof(fT[0][0]) * remaining); + memmove(&fT[1][index], &fT[1][index + 1], sizeof(fT[1][0]) * remaining); + SkASSERT(fIsCoincident[0] == 0); + int coBit = fIsCoincident[0] & (1 << index); + fIsCoincident[0] -= ((fIsCoincident[0] >> 1) & ~((1 << index) - 1)) + coBit; + SkASSERT(!(coBit ^ (fIsCoincident[1] & (1 << index)))); + fIsCoincident[1] -= ((fIsCoincident[1] >> 1) & ~((1 << index) - 1)) + coBit; +} + +void SkIntersections::swapPts() { + int index; + for (index = 0; index < fUsed; ++index) { + SkTSwap(fT[0][index], fT[1][index]); + } +} + +int SkIntersections::verticalLine(const SkPoint a[2], SkScalar top, SkScalar bottom, + SkScalar x, bool flipped) { + SkDLine line; + line.set(a); + return vertical(line, top, bottom, x, flipped); +} + +int SkIntersections::verticalQuad(const SkPoint a[3], SkScalar top, SkScalar bottom, + SkScalar x, bool flipped) { + SkDQuad quad; + quad.set(a); + return vertical(quad, top, bottom, x, flipped); +} + +int SkIntersections::verticalCubic(const SkPoint a[4], SkScalar top, SkScalar bottom, + SkScalar x, bool flipped) { + SkDCubic cubic; + cubic.set(a); + return vertical(cubic, top, bottom, x, flipped); +} diff --git a/src/pathops/SkIntersections.h b/src/pathops/SkIntersections.h new file mode 100644 index 0000000000..c2f05fe936 --- /dev/null +++ b/src/pathops/SkIntersections.h @@ -0,0 +1,243 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkIntersections_DEFINE +#define SkIntersections_DEFINE + +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsPoint.h" +#include "SkPathOpsQuad.h" + +class SkIntersections { +public: + SkIntersections() + : fSwap(0) +#ifdef SK_DEBUG + , fDepth(0) +#endif + { + sk_bzero(fPt, sizeof(fPt)); + sk_bzero(fT, sizeof(fT)); + sk_bzero(fIsCoincident, sizeof(fIsCoincident)); + reset(); + } + + class TArray { + public: + explicit TArray(const double ts[9]) : fTArray(ts) {} + double operator[](int n) const { + return fTArray[n]; + } + const double* fTArray; + }; + TArray operator[](int n) const { return TArray(fT[n]); } + + int cubic(const SkPoint a[4]) { + SkDCubic cubic; + cubic.set(a); + return intersect(cubic); + } + + int cubicCubic(const SkPoint a[4], const SkPoint b[4]) { + SkDCubic aCubic; + aCubic.set(a); + SkDCubic bCubic; + bCubic.set(b); + return intersect(aCubic, bCubic); + } + + int cubicHorizontal(const SkPoint a[4], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDCubic cubic; + cubic.set(a); + return horizontal(cubic, left, right, y, flipped); + } + + int cubicVertical(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDCubic cubic; + cubic.set(a); + return vertical(cubic, top, bottom, x, flipped); + } + + int cubicLine(const SkPoint a[4], const SkPoint b[2]) { + SkDCubic cubic; + cubic.set(a); + SkDLine line; + line.set(b); + return intersect(cubic, line); + } + + int cubicQuad(const SkPoint a[4], const SkPoint b[3]) { + SkDCubic cubic; + cubic.set(a); + SkDQuad quad; + quad.set(b); + return intersect(cubic, quad); + } + + int insertSwap(double one, double two, const SkDPoint& pt) { + if (fSwap) { + return insert(two, one, pt); + } else { + return insert(one, two, pt); + } + } + + bool isCoincident(int index) { + return (fIsCoincident[0] & 1 << index) != 0; + } + + int lineHorizontal(const SkPoint a[2], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDLine line; + line.set(a); + return horizontal(line, left, right, y, flipped); + } + + int lineVertical(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDLine line; + line.set(a); + return vertical(line, top, bottom, x, flipped); + } + + int lineLine(const SkPoint a[2], const SkPoint b[2]) { + SkDLine aLine, bLine; + aLine.set(a); + bLine.set(b); + return intersect(aLine, bLine); + } + + const SkDPoint& pt(int index) const { + return fPt[index]; + } + + int quadHorizontal(const SkPoint a[3], SkScalar left, SkScalar right, SkScalar y, + bool flipped) { + SkDQuad quad; + quad.set(a); + return horizontal(quad, left, right, y, flipped); + } + + int quadVertical(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped) { + SkDQuad quad; + quad.set(a); + return vertical(quad, top, bottom, x, flipped); + } + + int quadLine(const SkPoint a[3], const SkPoint b[2]) { + SkDQuad quad; + quad.set(a); + SkDLine line; + line.set(b); + return intersect(quad, line); + } + + int quadQuad(const SkPoint a[3], const SkPoint b[3]) { + SkDQuad aQuad; + aQuad.set(a); + SkDQuad bQuad; + bQuad.set(b); + return intersect(aQuad, bQuad); + } + + int quadRay(const SkPoint pts[3], const SkDLine& line); + void removeOne(int index); + + // leaves flip, swap alone + void reset() { + fUsed = 0; + } + + void swap() { + fSwap ^= true; + } + + void swapPts(); + + bool swapped() const { + return fSwap; + } + + int used() const { + return fUsed; + } + + void downDepth() { + SkASSERT(--fDepth >= 0); + } + + void upDepth() { + SkASSERT(++fDepth < 16); + } + + static double Axial(const SkDQuad& , const SkDPoint& , bool vertical); + int coincidentUsed() const; + int cubicRay(const SkPoint pts[4], const SkDLine& line); + void flip(); + int horizontal(const SkDLine&, double y); + int horizontal(const SkDLine&, double left, double right, double y); + int horizontal(const SkDLine&, double left, double right, double y, bool flipped); + int horizontal(const SkDQuad&, double left, double right, double y, bool flipped); + int horizontal(const SkDQuad&, double left, double right, double y, double tRange[2]); + int horizontal(const SkDCubic&, double y, double tRange[3]); + int horizontal(const SkDCubic&, double left, double right, double y, bool flipped); + int horizontal(const SkDCubic&, double left, double right, double y, double tRange[3]); + // FIXME : does not respect swap + int insert(double one, double two, const SkDPoint& pt); + // start if index == 0 : end if index == 1 + void insertCoincident(double one, double two, const SkDPoint& pt); + void insertCoincidentPair(double s1, double e1, double s2, double e2, + const SkDPoint& startPt, const SkDPoint& endPt); + int intersect(const SkDLine&, const SkDLine&); + int intersect(const SkDQuad&, const SkDLine&); + int intersect(const SkDQuad&, const SkDQuad&); + int intersect(const SkDCubic&); // return true if cubic self-intersects + int intersect(const SkDCubic&, const SkDLine&); + int intersect(const SkDCubic&, const SkDQuad&); + int intersect(const SkDCubic&, const SkDCubic&); + int intersectRay(const SkDCubic& , const SkDLine&); + int intersectRay(const SkDQuad& , const SkDLine&); + static SkDPoint Line(const SkDLine& , const SkDLine&); + void offset(int base, double start, double end); + void quickRemoveOne(int index, int replace); + static bool Test(const SkDLine& , const SkDLine&); + int vertical(const SkDLine&, double x); + int vertical(const SkDLine&, double top, double bottom, double x, bool flipped); + int vertical(const SkDQuad&, double top, double bottom, double x, bool flipped); + int vertical(const SkDCubic&, double top, double bottom, double x, bool flipped); + int verticalCubic(const SkPoint a[4], SkScalar top, SkScalar bottom, SkScalar x, bool flipped); + int verticalLine(const SkPoint a[2], SkScalar top, SkScalar bottom, SkScalar x, bool flipped); + int verticalQuad(const SkPoint a[3], SkScalar top, SkScalar bottom, SkScalar x, bool flipped); + + int depth() const { +#ifdef SK_DEBUG + return fDepth; +#else + return 0; +#endif + } + +private: + int computePoints(const SkDLine& line, int used); + // used by addCoincident to remove ordinary intersections in range + void remove(double one, double two, const SkDPoint& startPt, const SkDPoint& endPt); + + SkDPoint fPt[9]; + double fT[2][9]; + uint16_t fIsCoincident[2]; // bit arrays, one bit set for each coincident T + unsigned char fUsed; + bool fSwap; +#ifdef SK_DEBUG + int fDepth; +#endif +}; + +extern int (SkIntersections::*CurveRay[])(const SkPoint[], const SkDLine& ); +extern int (SkIntersections::*CurveVertical[])(const SkPoint[], SkScalar top, SkScalar bottom, + SkScalar x, bool flipped); + +#endif diff --git a/src/pathops/SkLineParameters.h b/src/pathops/SkLineParameters.h new file mode 100644 index 0000000000..5139c6c6cf --- /dev/null +++ b/src/pathops/SkLineParameters.h @@ -0,0 +1,110 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" + +// Sources +// computer-aided design - volume 22 number 9 november 1990 pp 538 - 549 +// online at http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf + +// This turns a line segment into a parameterized line, of the form +// ax + by + c = 0 +// When a^2 + b^2 == 1, the line is normalized. +// The distance to the line for (x, y) is d(x,y) = ax + by + c +// +// Note that the distances below are not necessarily normalized. To get the true +// distance, it's necessary to either call normalize() after xxxEndPoints(), or +// divide the result of xxxDistance() by sqrt(normalSquared()) + +class SkLineParameters { +public: + void cubicEndPoints(const SkDCubic& pts) { + cubicEndPoints(pts, 0, 3); + } + + void cubicEndPoints(const SkDCubic& pts, int s, int e) { + a = approximately_pin(pts[s].fY - pts[e].fY); + b = approximately_pin(pts[e].fX - pts[s].fX); + c = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY; + } + + void lineEndPoints(const SkDLine& pts) { + a = approximately_pin(pts[0].fY - pts[1].fY); + b = approximately_pin(pts[1].fX - pts[0].fX); + c = pts[0].fX * pts[1].fY - pts[1].fX * pts[0].fY; + } + + void quadEndPoints(const SkDQuad& pts) { + quadEndPoints(pts, 0, 2); + } + + void quadEndPoints(const SkDQuad& pts, int s, int e) { + a = approximately_pin(pts[s].fY - pts[e].fY); + b = approximately_pin(pts[e].fX - pts[s].fX); + c = pts[s].fX * pts[e].fY - pts[e].fX * pts[s].fY; + } + + double normalSquared() const { + return a * a + b * b; + } + + bool normalize() { + double normal = sqrt(normalSquared()); + if (approximately_zero(normal)) { + a = b = c = 0; + return false; + } + double reciprocal = 1 / normal; + a *= reciprocal; + b *= reciprocal; + c *= reciprocal; + return true; + } + + void cubicDistanceY(const SkDCubic& pts, SkDCubic& distance) const { + double oneThird = 1 / 3.0; + for (int index = 0; index < 4; ++index) { + distance[index].fX = index * oneThird; + distance[index].fY = a * pts[index].fX + b * pts[index].fY + c; + } + } + + void quadDistanceY(const SkDQuad& pts, SkDQuad& distance) const { + double oneHalf = 1 / 2.0; + for (int index = 0; index < 3; ++index) { + distance[index].fX = index * oneHalf; + distance[index].fY = a * pts[index].fX + b * pts[index].fY + c; + } + } + + double controlPtDistance(const SkDCubic& pts, int index) const { + SkASSERT(index == 1 || index == 2); + return a * pts[index].fX + b * pts[index].fY + c; + } + + double controlPtDistance(const SkDQuad& pts) const { + return a * pts[1].fX + b * pts[1].fY + c; + } + + double pointDistance(const SkDPoint& pt) const { + return a * pt.fX + b * pt.fY + c; + } + + double dx() const { + return b; + } + + double dy() const { + return -a; + } + +private: + double a; + double b; + double c; +}; diff --git a/src/pathops/SkOpAngle.cpp b/src/pathops/SkOpAngle.cpp new file mode 100644 index 0000000000..fd1e0f4cd0 --- /dev/null +++ b/src/pathops/SkOpAngle.cpp @@ -0,0 +1,255 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkIntersections.h" +#include "SkOpAngle.h" +#include "SkPathOpsCurve.h" + +// FIXME: this is bogus for quads and cubics +// if the quads and cubics' line from end pt to ctrl pt are coincident, +// there's no obvious way to determine the curve ordering from the +// derivatives alone. In particular, if one quadratic's coincident tangent +// is longer than the other curve, the final control point can place the +// longer curve on either side of the shorter one. +// Using Bezier curve focus http://cagd.cs.byu.edu/~tom/papers/bezclip.pdf +// may provide some help, but nothing has been figured out yet. + +/*( +for quads and cubics, set up a parameterized line (e.g. LineParameters ) +for points [0] to [1]. See if point [2] is on that line, or on one side +or the other. If it both quads' end points are on the same side, choose +the shorter tangent. If the tangents are equal, choose the better second +tangent angle + +maybe I could set up LineParameters lazily +*/ +bool SkOpAngle::operator<(const SkOpAngle& rh) const { + double y = dy(); + double ry = rh.dy(); + if ((y < 0) ^ (ry < 0)) { // OPTIMIZATION: better to use y * ry < 0 ? + return y < 0; + } + double x = dx(); + double rx = rh.dx(); + if (y == 0 && ry == 0 && x * rx < 0) { + return x < rx; + } + double x_ry = x * ry; + double rx_y = rx * y; + double cmp = x_ry - rx_y; + if (!approximately_zero(cmp)) { + return cmp < 0; + } + if (approximately_zero(x_ry) && approximately_zero(rx_y) + && !approximately_zero_squared(cmp)) { + return cmp < 0; + } + // at this point, the initial tangent line is coincident + // see if edges curl away from each other + if (fSide * rh.fSide <= 0 && (!approximately_zero(fSide) + || !approximately_zero(rh.fSide))) { + // FIXME: running demo will trigger this assertion + // (don't know if commenting out will trigger further assertion or not) + // commenting it out allows demo to run in release, though + return fSide < rh.fSide; + } + // see if either curve can be lengthened and try the tangent compare again + if (cmp && (*fSpans)[fEnd].fOther != rh.fSegment // tangents not absolutely identical + && (*rh.fSpans)[rh.fEnd].fOther != fSegment) { // and not intersecting + SkOpAngle longer = *this; + SkOpAngle rhLonger = rh; + if (longer.lengthen() | rhLonger.lengthen()) { + return longer < rhLonger; + } + } + if ((fVerb == SkPath::kLine_Verb && approximately_zero(x) && approximately_zero(y)) + || (rh.fVerb == SkPath::kLine_Verb + && approximately_zero(rx) && approximately_zero(ry))) { + // See general unsortable comment below. This case can happen when + // one line has a non-zero change in t but no change in x and y. + fUnsortable = true; + rh.fUnsortable = true; + return this < &rh; // even with no solution, return a stable sort + } + if ((*rh.fSpans)[SkMin32(rh.fStart, rh.fEnd)].fTiny + || (*fSpans)[SkMin32(fStart, fEnd)].fTiny) { + fUnsortable = true; + rh.fUnsortable = true; + return this < &rh; // even with no solution, return a stable sort + } + SkASSERT(fVerb >= SkPath::kQuad_Verb); + SkASSERT(rh.fVerb >= SkPath::kQuad_Verb); + // FIXME: until I can think of something better, project a ray from the + // end of the shorter tangent to midway between the end points + // through both curves and use the resulting angle to sort + // FIXME: some of this setup can be moved to set() if it works, or cached if it's expensive + double len = fTangent1.normalSquared(); + double rlen = rh.fTangent1.normalSquared(); + SkDLine ray; + SkIntersections i, ri; + int roots, rroots; + bool flip = false; + do { + bool useThis = (len < rlen) ^ flip; + const SkDCubic& part = useThis ? fCurvePart : rh.fCurvePart; + SkPath::Verb partVerb = useThis ? fVerb : rh.fVerb; + ray[0] = partVerb == SkPath::kCubic_Verb && part[0].approximatelyEqual(part[1]) ? + part[2] : part[1]; + ray[1].fX = (part[0].fX + part[partVerb].fX) / 2; + ray[1].fY = (part[0].fY + part[partVerb].fY) / 2; + SkASSERT(ray[0] != ray[1]); + roots = (i.*CurveRay[fVerb])(fPts, ray); + rroots = (ri.*CurveRay[rh.fVerb])(rh.fPts, ray); + } while ((roots == 0 || rroots == 0) && (flip ^= true)); + if (roots == 0 || rroots == 0) { + // FIXME: we don't have a solution in this case. The interim solution + // is to mark the edges as unsortable, exclude them from this and + // future computations, and allow the returned path to be fragmented + fUnsortable = true; + rh.fUnsortable = true; + return this < &rh; // even with no solution, return a stable sort + } + SkDPoint loc; + double best = SK_ScalarInfinity; + SkDVector dxy; + double dist; + int index; + for (index = 0; index < roots; ++index) { + loc = (*CurveDPointAtT[fVerb])(fPts, i[0][index]); + dxy = loc - ray[0]; + dist = dxy.lengthSquared(); + if (best > dist) { + best = dist; + } + } + for (index = 0; index < rroots; ++index) { + loc = (*CurveDPointAtT[rh.fVerb])(rh.fPts, ri[0][index]); + dxy = loc - ray[0]; + dist = dxy.lengthSquared(); + if (best > dist) { + return fSide < 0; + } + } + return fSide > 0; +} + +bool SkOpAngle::lengthen() { + int newEnd = fEnd; + if (fStart < fEnd ? ++newEnd < fSpans->count() : --newEnd >= 0) { + fEnd = newEnd; + setSpans(); + return true; + } + return false; +} + +bool SkOpAngle::reverseLengthen() { + if (fReversed) { + return false; + } + int newEnd = fStart; + if (fStart > fEnd ? ++newEnd < fSpans->count() : --newEnd >= 0) { + fEnd = newEnd; + fReversed = true; + setSpans(); + return true; + } + return false; +} + +void SkOpAngle::set(const SkPoint* orig, SkPath::Verb verb, const SkOpSegment* segment, + int start, int end, const SkTDArray<SkOpSpan>& spans) { + fSegment = segment; + fStart = start; + fEnd = end; + fPts = orig; + fVerb = verb; + fSpans = &spans; + fReversed = false; + fUnsortable = false; + setSpans(); +} + + +void SkOpAngle::setSpans() { + double startT = (*fSpans)[fStart].fT; + double endT = (*fSpans)[fEnd].fT; + switch (fVerb) { + case SkPath::kLine_Verb: { + SkDLine l = SkDLine::SubDivide(fPts, startT, endT); + // OPTIMIZATION: for pure line compares, we never need fTangent1.c + fTangent1.lineEndPoints(l); + fSide = 0; + } break; + case SkPath::kQuad_Verb: { + SkDQuad& quad = *SkTCast<SkDQuad*>(&fCurvePart); + quad = SkDQuad::SubDivide(fPts, startT, endT); + fTangent1.quadEndPoints(quad, 0, 1); + if (dx() == 0 && dy() == 0) { + fTangent1.quadEndPoints(quad); + } + fSide = -fTangent1.pointDistance(fCurvePart[2]); // not normalized -- compare sign only + } break; + case SkPath::kCubic_Verb: { + int nextC = 2; + fCurvePart = SkDCubic::SubDivide(fPts, startT, endT); + fTangent1.cubicEndPoints(fCurvePart, 0, 1); + if (dx() == 0 && dy() == 0) { + fTangent1.cubicEndPoints(fCurvePart, 0, 2); + nextC = 3; + if (dx() == 0 && dy() == 0) { + fTangent1.cubicEndPoints(fCurvePart, 0, 3); + } + } + fSide = -fTangent1.pointDistance(fCurvePart[nextC]); // compare sign only + if (nextC == 2 && approximately_zero(fSide)) { + fSide = -fTangent1.pointDistance(fCurvePart[3]); + } + } break; + default: + SkASSERT(0); + } + fUnsortable = dx() == 0 && dy() == 0; + if (fUnsortable) { + return; + } + SkASSERT(fStart != fEnd); + int step = fStart < fEnd ? 1 : -1; // OPTIMIZE: worth fStart - fEnd >> 31 type macro? + for (int index = fStart; index != fEnd; index += step) { +#if 1 + const SkOpSpan& thisSpan = (*fSpans)[index]; + const SkOpSpan& nextSpan = (*fSpans)[index + step]; + if (thisSpan.fTiny || precisely_equal(thisSpan.fT, nextSpan.fT)) { + continue; + } + fUnsortable = step > 0 ? thisSpan.fUnsortableStart : nextSpan.fUnsortableEnd; +#if DEBUG_UNSORTABLE + if (fUnsortable) { + SkPoint iPt = (*CurvePointAtT[fVerb])(fPts, thisSpan.fT); + SkPoint ePt = (*CurvePointAtT[fVerb])(fPts, nextSpan.fT); + SkDebugf("%s unsortable [%d] (%1.9g,%1.9g) [%d] (%1.9g,%1.9g)\n", __FUNCTION__, + index, iPt.fX, iPt.fY, fEnd, ePt.fX, ePt.fY); + } +#endif + return; +#else + if ((*fSpans)[index].fUnsortableStart) { + fUnsortable = true; + return; + } +#endif + } +#if 1 +#if DEBUG_UNSORTABLE + SkPoint iPt = (*CurvePointAtT[fVerb])(fPts, startT); + SkPoint ePt = (*CurvePointAtT[fVerb])(fPts, endT); + SkDebugf("%s all tiny unsortable [%d] (%1.9g,%1.9g) [%d] (%1.9g,%1.9g)\n", __FUNCTION__, + fStart, iPt.fX, iPt.fY, fEnd, ePt.fX, ePt.fY); +#endif + fUnsortable = true; +#endif +} + diff --git a/src/pathops/SkOpAngle.h b/src/pathops/SkOpAngle.h new file mode 100644 index 0000000000..d599dea7c2 --- /dev/null +++ b/src/pathops/SkOpAngle.h @@ -0,0 +1,91 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpAngle_DEFINED +#define SkOpAngle_DEFINED + +#include "SkLineParameters.h" +#include "SkOpSpan.h" +#include "SkPath.h" +#include "SkPathOpsCubic.h" +#include "SkTDArray.h" + +// sorting angles +// given angles of {dx dy ddx ddy dddx dddy} sort them +class SkOpAngle { +public: + bool operator<(const SkOpAngle& rh) const; + double dx() const { + return fTangent1.dx(); + } + + double dy() const { + return fTangent1.dy(); + } + + int end() const { + return fEnd; + } + + bool isHorizontal() const { + return dy() == 0 && fVerb == SkPath::kLine_Verb; + } + + bool lengthen(); + bool reverseLengthen(); + void set(const SkPoint* orig, SkPath::Verb verb, const SkOpSegment* segment, + int start, int end, const SkTDArray<SkOpSpan>& spans); + + void setSpans(); + SkOpSegment* segment() const { + return const_cast<SkOpSegment*>(fSegment); + } + + int sign() const { + return SkSign32(fStart - fEnd); + } + + const SkTDArray<SkOpSpan>* spans() const { + return fSpans; + } + + int start() const { + return fStart; + } + + bool unsortable() const { + return fUnsortable; + } + +#if DEBUG_ANGLE + const SkPoint* pts() const { + return fPts; + } + + SkPath::Verb verb() const { + return fVerb; + } + + void debugShow(const SkPoint& a) const { + SkDebugf(" d=(%1.9g,%1.9g) side=%1.9g\n", dx(), dy(), fSide); + } +#endif + +private: + const SkPoint* fPts; + SkDCubic fCurvePart; + SkPath::Verb fVerb; + double fSide; + SkLineParameters fTangent1; + const SkTDArray<SkOpSpan>* fSpans; + const SkOpSegment* fSegment; + int fStart; + int fEnd; + bool fReversed; + mutable bool fUnsortable; // this alone is editable by the less than operator +}; + +#endif diff --git a/src/pathops/SkOpContour.cpp b/src/pathops/SkOpContour.cpp new file mode 100644 index 0000000000..e68341cfd0 --- /dev/null +++ b/src/pathops/SkOpContour.cpp @@ -0,0 +1,265 @@ +/* +* Copyright 2013 Google Inc. +* +* Use of this source code is governed by a BSD-style license that can be +* found in the LICENSE file. +*/ +#include "SkIntersections.h" +#include "SkOpContour.h" +#include "SkPathWriter.h" +#include "TSearch.h" + +void SkOpContour::addCoincident(int index, SkOpContour* other, int otherIndex, + const SkIntersections& ts, bool swap) { + SkCoincidence& coincidence = *fCoincidences.append(); + coincidence.fContours[0] = this; // FIXME: no need to store + coincidence.fContours[1] = other; + coincidence.fSegments[0] = index; + coincidence.fSegments[1] = otherIndex; + coincidence.fTs[swap][0] = ts[0][0]; + coincidence.fTs[swap][1] = ts[0][1]; + coincidence.fTs[!swap][0] = ts[1][0]; + coincidence.fTs[!swap][1] = ts[1][1]; + coincidence.fPts[0] = ts.pt(0).asSkPoint(); + coincidence.fPts[1] = ts.pt(1).asSkPoint(); +} + +SkOpSegment* SkOpContour::nonVerticalSegment(int* start, int* end) { + int segmentCount = fSortedSegments.count(); + SkASSERT(segmentCount > 0); + for (int sortedIndex = fFirstSorted; sortedIndex < segmentCount; ++sortedIndex) { + SkOpSegment* testSegment = fSortedSegments[sortedIndex]; + if (testSegment->done()) { + continue; + } + *start = *end = 0; + while (testSegment->nextCandidate(start, end)) { + if (!testSegment->isVertical(*start, *end)) { + return testSegment; + } + } + } + return NULL; +} + +// first pass, add missing T values +// second pass, determine winding values of overlaps +void SkOpContour::addCoincidentPoints() { + int count = fCoincidences.count(); + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = fCoincidences[index]; + SkASSERT(coincidence.fContours[0] == this); + int thisIndex = coincidence.fSegments[0]; + SkOpSegment& thisOne = fSegments[thisIndex]; + SkOpContour* otherContour = coincidence.fContours[1]; + int otherIndex = coincidence.fSegments[1]; + SkOpSegment& other = otherContour->fSegments[otherIndex]; + if ((thisOne.done() || other.done()) && thisOne.complete() && other.complete()) { + // OPTIMIZATION: remove from array + continue; + } + #if DEBUG_CONCIDENT + thisOne.debugShowTs(); + other.debugShowTs(); + #endif + double startT = coincidence.fTs[0][0]; + double endT = coincidence.fTs[0][1]; + bool cancelers; + if ((cancelers = startT > endT)) { + SkTSwap(startT, endT); + SkTSwap(coincidence.fPts[0], coincidence.fPts[1]); + } + SkASSERT(!approximately_negative(endT - startT)); + double oStartT = coincidence.fTs[1][0]; + double oEndT = coincidence.fTs[1][1]; + if (oStartT > oEndT) { + SkTSwap<double>(oStartT, oEndT); + cancelers ^= true; + } + SkASSERT(!approximately_negative(oEndT - oStartT)); + bool opp = fOperand ^ otherContour->fOperand; + if (cancelers && !opp) { + // make sure startT and endT have t entries + if (startT > 0 || oEndT < 1 + || thisOne.isMissing(startT) || other.isMissing(oEndT)) { + thisOne.addTPair(startT, &other, oEndT, true, coincidence.fPts[0]); + } + if (oStartT > 0 || endT < 1 + || thisOne.isMissing(endT) || other.isMissing(oStartT)) { + other.addTPair(oStartT, &thisOne, endT, true, coincidence.fPts[1]); + } + } else { + if (startT > 0 || oStartT > 0 + || thisOne.isMissing(startT) || other.isMissing(oStartT)) { + thisOne.addTPair(startT, &other, oStartT, true, coincidence.fPts[0]); + } + if (endT < 1 || oEndT < 1 + || thisOne.isMissing(endT) || other.isMissing(oEndT)) { + other.addTPair(oEndT, &thisOne, endT, true, coincidence.fPts[1]); + } + } + #if DEBUG_CONCIDENT + thisOne.debugShowTs(); + other.debugShowTs(); + #endif + } +} + +void SkOpContour::calcCoincidentWinding() { + int count = fCoincidences.count(); + for (int index = 0; index < count; ++index) { + SkCoincidence& coincidence = fCoincidences[index]; + SkASSERT(coincidence.fContours[0] == this); + int thisIndex = coincidence.fSegments[0]; + SkOpSegment& thisOne = fSegments[thisIndex]; + if (thisOne.done()) { + continue; + } + SkOpContour* otherContour = coincidence.fContours[1]; + int otherIndex = coincidence.fSegments[1]; + SkOpSegment& other = otherContour->fSegments[otherIndex]; + if (other.done()) { + continue; + } + double startT = coincidence.fTs[0][0]; + double endT = coincidence.fTs[0][1]; + bool cancelers; + if ((cancelers = startT > endT)) { + SkTSwap<double>(startT, endT); + } + SkASSERT(!approximately_negative(endT - startT)); + double oStartT = coincidence.fTs[1][0]; + double oEndT = coincidence.fTs[1][1]; + if (oStartT > oEndT) { + SkTSwap<double>(oStartT, oEndT); + cancelers ^= true; + } + SkASSERT(!approximately_negative(oEndT - oStartT)); + bool opp = fOperand ^ otherContour->fOperand; + if (cancelers && !opp) { + // make sure startT and endT have t entries + if (!thisOne.done() && !other.done()) { + thisOne.addTCancel(startT, endT, &other, oStartT, oEndT); + } + } else { + if (!thisOne.done() && !other.done()) { + thisOne.addTCoincident(startT, endT, &other, oStartT, oEndT); + } + } + #if DEBUG_CONCIDENT + thisOne.debugShowTs(); + other.debugShowTs(); + #endif + } +} + +void SkOpContour::sortSegments() { + int segmentCount = fSegments.count(); + fSortedSegments.setReserve(segmentCount); + for (int test = 0; test < segmentCount; ++test) { + *fSortedSegments.append() = &fSegments[test]; + } + QSort<SkOpSegment>(fSortedSegments.begin(), fSortedSegments.end() - 1); + fFirstSorted = 0; +} + +void SkOpContour::toPath(SkPathWriter* path) const { + int segmentCount = fSegments.count(); + const SkPoint& pt = fSegments.front().pts()[0]; + path->deferredMove(pt); + for (int test = 0; test < segmentCount; ++test) { + fSegments[test].addCurveTo(0, 1, path, true); + } + path->close(); +} + +void SkOpContour::topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY, + SkOpSegment** topStart) { + int segmentCount = fSortedSegments.count(); + SkASSERT(segmentCount > 0); + int sortedIndex = fFirstSorted; + fDone = true; // may be cleared below + for ( ; sortedIndex < segmentCount; ++sortedIndex) { + SkOpSegment* testSegment = fSortedSegments[sortedIndex]; + if (testSegment->done()) { + if (sortedIndex == fFirstSorted) { + ++fFirstSorted; + } + continue; + } + fDone = false; + SkPoint testXY = testSegment->activeLeftTop(true, NULL); + if (*topStart) { + if (testXY.fY < topLeft.fY) { + continue; + } + if (testXY.fY == topLeft.fY && testXY.fX < topLeft.fX) { + continue; + } + if (bestXY->fY < testXY.fY) { + continue; + } + if (bestXY->fY == testXY.fY && bestXY->fX < testXY.fX) { + continue; + } + } + *topStart = testSegment; + *bestXY = testXY; + } +} + +SkOpSegment* SkOpContour::undoneSegment(int* start, int* end) { + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + SkOpSegment* testSegment = &fSegments[test]; + if (testSegment->done()) { + continue; + } + testSegment->undoneSpan(start, end); + return testSegment; + } + return NULL; +} + +#if DEBUG_SHOW_WINDING +int SkOpContour::debugShowWindingValues(int totalSegments, int ofInterest) { + int count = fSegments.count(); + int sum = 0; + for (int index = 0; index < count; ++index) { + sum += fSegments[index].debugShowWindingValues(totalSegments, ofInterest); + } +// SkDebugf("%s sum=%d\n", __FUNCTION__, sum); + return sum; +} + +static void SkOpContour::debugShowWindingValues(const SkTDArray<SkOpContour*>& contourList) { +// int ofInterest = 1 << 1 | 1 << 5 | 1 << 9 | 1 << 13; +// int ofInterest = 1 << 4 | 1 << 8 | 1 << 12 | 1 << 16; + int ofInterest = 1 << 5 | 1 << 8; + int total = 0; + int index; + for (index = 0; index < contourList.count(); ++index) { + total += contourList[index]->segments().count(); + } + int sum = 0; + for (index = 0; index < contourList.count(); ++index) { + sum += contourList[index]->debugShowWindingValues(total, ofInterest); + } +// SkDebugf("%s total=%d\n", __FUNCTION__, sum); +} +#endif + +void SkOpContour::setBounds() { + int count = fSegments.count(); + if (count == 0) { + SkDebugf("%s empty contour\n", __FUNCTION__); + SkASSERT(0); + // FIXME: delete empty contour? + return; + } + fBounds = fSegments.front().bounds(); + for (int index = 1; index < count; ++index) { + fBounds.add(fSegments[index].bounds()); + } +} + diff --git a/src/pathops/SkOpContour.h b/src/pathops/SkOpContour.h new file mode 100644 index 0000000000..2f1c48175a --- /dev/null +++ b/src/pathops/SkOpContour.h @@ -0,0 +1,241 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpContour_DEFINED +#define SkOpContour_DEFINED + +#include "SkOpSegment.h" +#include "SkTArray.h" + +class SkIntersections; +class SkOpContour; +class SkPathWriter; + +struct SkCoincidence { + SkOpContour* fContours[2]; + int fSegments[2]; + double fTs[2][2]; + SkPoint fPts[2]; +}; + +class SkOpContour { +public: + SkOpContour() { + reset(); +#if DEBUG_DUMP + fID = ++gContourID; +#endif + } + + bool operator<(const SkOpContour& rh) const { + return fBounds.fTop == rh.fBounds.fTop + ? fBounds.fLeft < rh.fBounds.fLeft + : fBounds.fTop < rh.fBounds.fTop; + } + + void addCoincident(int index, SkOpContour* other, int otherIndex, + const SkIntersections& ts, bool swap); + void addCoincidentPoints(); + + void addCross(const SkOpContour* crosser) { +#ifdef DEBUG_CROSS + for (int index = 0; index < fCrosses.count(); ++index) { + SkASSERT(fCrosses[index] != crosser); + } +#endif + *fCrosses.append() = crosser; + } + + void addCubic(const SkPoint pts[4]) { + fSegments.push_back().addCubic(pts, fOperand, fXor); + fContainsCurves = fContainsCubics = true; + } + + int addLine(const SkPoint pts[2]) { + fSegments.push_back().addLine(pts, fOperand, fXor); + return fSegments.count(); + } + + void addOtherT(int segIndex, int tIndex, double otherT, int otherIndex) { + fSegments[segIndex].addOtherT(tIndex, otherT, otherIndex); + } + + int addQuad(const SkPoint pts[3]) { + fSegments.push_back().addQuad(pts, fOperand, fXor); + fContainsCurves = true; + return fSegments.count(); + } + + int addT(int segIndex, SkOpContour* other, int otherIndex, const SkPoint& pt, double newT) { + setContainsIntercepts(); + return fSegments[segIndex].addT(&other->fSegments[otherIndex], pt, newT); + } + + int addSelfT(int segIndex, SkOpContour* other, int otherIndex, const SkPoint& pt, double newT) { + setContainsIntercepts(); + return fSegments[segIndex].addSelfT(&other->fSegments[otherIndex], pt, newT); + } + + int addUnsortableT(int segIndex, SkOpContour* other, int otherIndex, bool start, + const SkPoint& pt, double newT) { + return fSegments[segIndex].addUnsortableT(&other->fSegments[otherIndex], start, pt, newT); + } + + const SkPathOpsBounds& bounds() const { + return fBounds; + } + + void calcCoincidentWinding(); + + void complete() { + setBounds(); + fContainsIntercepts = false; + } + + bool containsCubics() const { + return fContainsCubics; + } + + bool crosses(const SkOpContour* crosser) const { + for (int index = 0; index < fCrosses.count(); ++index) { + if (fCrosses[index] == crosser) { + return true; + } + } + return false; + } + + bool done() const { + return fDone; + } + + const SkPoint& end() const { + const SkOpSegment& segment = fSegments.back(); + return segment.pts()[segment.verb()]; + } + + void findTooCloseToCall() { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + fSegments[sIndex].findTooCloseToCall(); + } + } + + void fixOtherTIndex() { + int segmentCount = fSegments.count(); + for (int sIndex = 0; sIndex < segmentCount; ++sIndex) { + fSegments[sIndex].fixOtherTIndex(); + } + } + + SkOpSegment* nonVerticalSegment(int* start, int* end); + + bool operand() const { + return fOperand; + } + + void reset() { + fSegments.reset(); + fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); + fContainsCurves = fContainsCubics = fContainsIntercepts = fDone = false; + } + + SkTArray<SkOpSegment>& segments() { + return fSegments; + } + + void setContainsIntercepts() { + fContainsIntercepts = true; + } + + void setOperand(bool isOp) { + fOperand = isOp; + } + + void setOppXor(bool isOppXor) { + fOppXor = isOppXor; + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + fSegments[test].setOppXor(isOppXor); + } + } + + void setXor(bool isXor) { + fXor = isXor; + } + + void sortSegments(); + + const SkPoint& start() const { + return fSegments.front().pts()[0]; + } + + void toPath(SkPathWriter* path) const; + + void toPartialBackward(SkPathWriter* path) const { + int segmentCount = fSegments.count(); + for (int test = segmentCount - 1; test >= 0; --test) { + fSegments[test].addCurveTo(1, 0, path, true); + } + } + + void toPartialForward(SkPathWriter* path) const { + int segmentCount = fSegments.count(); + for (int test = 0; test < segmentCount; ++test) { + fSegments[test].addCurveTo(0, 1, path, true); + } + } + + void topSortableSegment(const SkPoint& topLeft, SkPoint* bestXY, SkOpSegment** topStart); + SkOpSegment* undoneSegment(int* start, int* end); + + int updateSegment(int index, const SkPoint* pts) { + SkOpSegment& segment = fSegments[index]; + segment.updatePts(pts); + return segment.verb() + 1; + } + +#if DEBUG_TEST + SkTArray<SkOpSegment>& debugSegments() { + return fSegments; + } +#endif + +#if DEBUG_ACTIVE_SPANS + void debugShowActiveSpans() { + for (int index = 0; index < fSegments.count(); ++index) { + fSegments[index].debugShowActiveSpans(); + } + } +#endif + +#if DEBUG_SHOW_WINDING + int debugShowWindingValues(int totalSegments, int ofInterest); + static void debugShowWindingValues(const SkTDArray<SkOpContour*>& contourList); +#endif + +private: + void setBounds(); + + SkTArray<SkOpSegment> fSegments; + SkTDArray<SkOpSegment*> fSortedSegments; + int fFirstSorted; + SkTDArray<SkCoincidence> fCoincidences; + SkTDArray<const SkOpContour*> fCrosses; + SkPathOpsBounds fBounds; + bool fContainsIntercepts; // FIXME: is this used by anybody? + bool fContainsCubics; + bool fContainsCurves; + bool fDone; + bool fOperand; // true for the second argument to a binary operator + bool fXor; + bool fOppXor; +#if DEBUG_DUMP + int fID; +#endif +}; + +#endif diff --git a/src/pathops/SkOpEdgeBuilder.cpp b/src/pathops/SkOpEdgeBuilder.cpp new file mode 100644 index 0000000000..56e7c20043 --- /dev/null +++ b/src/pathops/SkOpEdgeBuilder.cpp @@ -0,0 +1,148 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkOpEdgeBuilder.h" +#include "SkReduceOrder.h" + +void SkOpEdgeBuilder::init() { + fCurrentContour = NULL; + fOperand = false; + fXorMask[0] = fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask + : kWinding_PathOpsMask; +#if DEBUG_DUMP + gContourID = 0; + gSegmentID = 0; +#endif + fSecondHalf = preFetch(); +} + +void SkOpEdgeBuilder::addOperand(const SkPath& path) { + SkASSERT(fPathVerbs.count() > 0 && fPathVerbs.end()[-1] == SkPath::kDone_Verb); + fPathVerbs.pop(); + fPath = &path; + fXorMask[1] = (fPath->getFillType() & 1) ? kEvenOdd_PathOpsMask + : kWinding_PathOpsMask; + preFetch(); +} + +void SkOpEdgeBuilder::finish() { + walk(); + complete(); + if (fCurrentContour && !fCurrentContour->segments().count()) { + fContours.pop_back(); + } + // correct pointers in contours since fReducePts may have moved as it grew + int cIndex = 0; + int extraCount = fExtra.count(); + SkASSERT(extraCount == 0 || fExtra[0] == -1); + int eIndex = 0; + int rIndex = 0; + while (++eIndex < extraCount) { + int offset = fExtra[eIndex]; + if (offset < 0) { + ++cIndex; + continue; + } + fCurrentContour = &fContours[cIndex]; + rIndex += fCurrentContour->updateSegment(offset - 1, + &fReducePts[rIndex]); + } + fExtra.reset(); // we're done with this +} + +// FIXME:remove once we can access path pts directly +int SkOpEdgeBuilder::preFetch() { + SkPath::RawIter iter(*fPath); // FIXME: access path directly when allowed + SkPoint pts[4]; + SkPath::Verb verb; + do { + verb = iter.next(pts); + *fPathVerbs.append() = verb; + if (verb == SkPath::kMove_Verb) { + *fPathPts.append() = pts[0]; + } else if (verb >= SkPath::kLine_Verb && verb <= SkPath::kCubic_Verb) { + fPathPts.append(verb, &pts[1]); + } + } while (verb != SkPath::kDone_Verb); + return fPathVerbs.count() - 1; +} + +void SkOpEdgeBuilder::walk() { + SkPath::Verb reducedVerb; + uint8_t* verbPtr = fPathVerbs.begin(); + uint8_t* endOfFirstHalf = &verbPtr[fSecondHalf]; + const SkPoint* pointsPtr = fPathPts.begin(); + const SkPoint* finalCurveStart = NULL; + const SkPoint* finalCurveEnd = NULL; + SkPath::Verb verb; + while ((verb = (SkPath::Verb) *verbPtr++) != SkPath::kDone_Verb) { + switch (verb) { + case SkPath::kMove_Verb: + complete(); + if (!fCurrentContour) { + fCurrentContour = fContours.push_back_n(1); + fCurrentContour->setOperand(fOperand); + fCurrentContour->setXor(fXorMask[fOperand] == kEvenOdd_PathOpsMask); + *fExtra.append() = -1; // start new contour + } + finalCurveEnd = pointsPtr++; + goto nextVerb; + case SkPath::kLine_Verb: + // skip degenerate points + if (pointsPtr[-1].fX != pointsPtr[0].fX || pointsPtr[-1].fY != pointsPtr[0].fY) { + fCurrentContour->addLine(&pointsPtr[-1]); + } + break; + case SkPath::kQuad_Verb: + reducedVerb = SkReduceOrder::Quad(&pointsPtr[-1], &fReducePts); + if (reducedVerb == 0) { + break; // skip degenerate points + } + if (reducedVerb == 1) { + *fExtra.append() = + fCurrentContour->addLine(fReducePts.end() - 2); + break; + } + fCurrentContour->addQuad(&pointsPtr[-1]); + break; + case SkPath::kCubic_Verb: + reducedVerb = SkReduceOrder::Cubic(&pointsPtr[-1], &fReducePts); + if (reducedVerb == 0) { + break; // skip degenerate points + } + if (reducedVerb == 1) { + *fExtra.append() = fCurrentContour->addLine(fReducePts.end() - 2); + break; + } + if (reducedVerb == 2) { + *fExtra.append() = fCurrentContour->addQuad(fReducePts.end() - 3); + break; + } + fCurrentContour->addCubic(&pointsPtr[-1]); + break; + case SkPath::kClose_Verb: + SkASSERT(fCurrentContour); + if (finalCurveStart && finalCurveEnd + && *finalCurveStart != *finalCurveEnd) { + *fReducePts.append() = *finalCurveStart; + *fReducePts.append() = *finalCurveEnd; + *fExtra.append() = fCurrentContour->addLine(fReducePts.end() - 2); + } + complete(); + goto nextVerb; + default: + SkDEBUGFAIL("bad verb"); + return; + } + finalCurveStart = &pointsPtr[verb - 1]; + pointsPtr += verb; + SkASSERT(fCurrentContour); + nextVerb: + if (verbPtr == endOfFirstHalf) { + fOperand = true; + } + } +} diff --git a/src/pathops/SkOpEdgeBuilder.h b/src/pathops/SkOpEdgeBuilder.h new file mode 100644 index 0000000000..68afc9394b --- /dev/null +++ b/src/pathops/SkOpEdgeBuilder.h @@ -0,0 +1,60 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpEdgeBuilder_DEFINED +#define SkOpEdgeBuilder_DEFINED + +#include "SkOpContour.h" +#include "SkPathWriter.h" +#include "SkTArray.h" +#include "SkTDArray.h" + +class SkOpEdgeBuilder { +public: + SkOpEdgeBuilder(const SkPathWriter& path, SkTArray<SkOpContour>& contours) + : fPath(path.nativePath()) + , fContours(contours) { + init(); + } + + SkOpEdgeBuilder(const SkPath& path, SkTArray<SkOpContour>& contours) + : fPath(&path) + , fContours(contours) { + init(); + } + + void complete() { + if (fCurrentContour && fCurrentContour->segments().count()) { + fCurrentContour->complete(); + fCurrentContour = NULL; + } + } + + SkPathOpsMask xorMask() const { + return fXorMask[fOperand]; + } + + void addOperand(const SkPath& path); + void finish(); + void init(); + +private: + int preFetch(); + void walk(); + + const SkPath* fPath; + SkTDArray<SkPoint> fPathPts; // FIXME: point directly to path pts instead + SkTDArray<uint8_t> fPathVerbs; // FIXME: remove + SkOpContour* fCurrentContour; + SkTArray<SkOpContour>& fContours; + SkTDArray<SkPoint> fReducePts; // segments created on the fly + SkTDArray<int> fExtra; // -1 marks new contour, > 0 offsets into contour + SkPathOpsMask fXorMask[2]; + int fSecondHalf; + bool fOperand; +}; + +#endif diff --git a/src/pathops/SkOpSegment.cpp b/src/pathops/SkOpSegment.cpp new file mode 100644 index 0000000000..627b0af9d4 --- /dev/null +++ b/src/pathops/SkOpSegment.cpp @@ -0,0 +1,2795 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkIntersections.h" +#include "SkOpSegment.h" +#include "SkPathWriter.h" +#include "TSearch.h" + +#define F (false) // discard the edge +#define T (true) // keep the edge + +static const bool gUnaryActiveEdge[2][2] = { +// from=0 from=1 +// to=0,1 to=0,1 + {F, T}, {T, F}, +}; + +// FIXME: add support for kReverseDifference_Op +static const bool gActiveEdge[kXOR_PathOp + 1][2][2][2][2] = { +// miFrom=0 miFrom=1 +// miTo=0 miTo=1 miTo=0 miTo=1 +// suFrom=0 1 suFrom=0 1 suFrom=0 1 suFrom=0 1 +// suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 suTo=0,1 + {{{{F, F}, {F, F}}, {{T, F}, {T, F}}}, {{{T, T}, {F, F}}, {{F, T}, {T, F}}}}, // mi - su + {{{{F, F}, {F, F}}, {{F, T}, {F, T}}}, {{{F, F}, {T, T}}, {{F, T}, {T, F}}}}, // mi & su + {{{{F, T}, {T, F}}, {{T, T}, {F, F}}}, {{{T, F}, {T, F}}, {{F, F}, {F, F}}}}, // mi | su + {{{{F, T}, {T, F}}, {{T, F}, {F, T}}}, {{{T, F}, {F, T}}, {{F, T}, {T, F}}}}, // mi ^ su +}; + +#undef F +#undef T + +// OPTIMIZATION: does the following also work, and is it any faster? +// return outerWinding * innerWinding > 0 +// || ((outerWinding + innerWinding < 0) ^ ((outerWinding - innerWinding) < 0))) +bool SkOpSegment::UseInnerWinding(int outerWinding, int innerWinding) { + SkASSERT(outerWinding != SK_MaxS32); + SkASSERT(innerWinding != SK_MaxS32); + int absOut = abs(outerWinding); + int absIn = abs(innerWinding); + bool result = absOut == absIn ? outerWinding < 0 : absOut < absIn; + return result; +} + +bool SkOpSegment::activeAngle(int index, int* done, SkTDArray<SkOpAngle>* angles) { + if (activeAngleInner(index, done, angles)) { + return true; + } + int lesser = index; + while (--lesser >= 0 && equalPoints(index, lesser)) { + if (activeAngleOther(lesser, done, angles)) { + return true; + } + } + lesser = index; + do { + if (activeAngleOther(index, done, angles)) { + return true; + } + } while (++index < fTs.count() && equalPoints(index, lesser)); + return false; +} + +bool SkOpSegment::activeAngleOther(int index, int* done, SkTDArray<SkOpAngle>* angles) { + SkOpSpan* span = &fTs[index]; + SkOpSegment* other = span->fOther; + int oIndex = span->fOtherIndex; + return other->activeAngleInner(oIndex, done, angles); +} + +bool SkOpSegment::activeAngleInner(int index, int* done, SkTDArray<SkOpAngle>* angles) { + int next = nextExactSpan(index, 1); + if (next > 0) { + SkOpSpan& upSpan = fTs[index]; + if (upSpan.fWindValue || upSpan.fOppValue) { + addAngle(angles, index, next); + if (upSpan.fDone || upSpan.fUnsortableEnd) { + (*done)++; + } else if (upSpan.fWindSum != SK_MinS32) { + return true; + } + } else if (!upSpan.fDone) { + upSpan.fDone = true; + fDoneSpans++; + } + } + int prev = nextExactSpan(index, -1); + // edge leading into junction + if (prev >= 0) { + SkOpSpan& downSpan = fTs[prev]; + if (downSpan.fWindValue || downSpan.fOppValue) { + addAngle(angles, index, prev); + if (downSpan.fDone) { + (*done)++; + } else if (downSpan.fWindSum != SK_MinS32) { + return true; + } + } else if (!downSpan.fDone) { + downSpan.fDone = true; + fDoneSpans++; + } + } + return false; +} + +SkPoint SkOpSegment::activeLeftTop(bool onlySortable, int* firstT) const { + SkASSERT(!done()); + SkPoint topPt = {SK_ScalarMax, SK_ScalarMax}; + int count = fTs.count(); + // see if either end is not done since we want smaller Y of the pair + bool lastDone = true; + bool lastUnsortable = false; + double lastT = -1; + for (int index = 0; index < count; ++index) { + const SkOpSpan& span = fTs[index]; + if (onlySortable && (span.fUnsortableStart || lastUnsortable)) { + goto next; + } + if (span.fDone && lastDone) { + goto next; + } + if (approximately_negative(span.fT - lastT)) { + goto next; + } + { + const SkPoint& xy = xyAtT(&span); + if (topPt.fY > xy.fY || (topPt.fY == xy.fY && topPt.fX > xy.fX)) { + topPt = xy; + if (firstT) { + *firstT = index; + } + } + if (fVerb != SkPath::kLine_Verb && !lastDone) { + SkPoint curveTop = (*CurveTop[fVerb])(fPts, lastT, span.fT); + if (topPt.fY > curveTop.fY || (topPt.fY == curveTop.fY + && topPt.fX > curveTop.fX)) { + topPt = curveTop; + if (firstT) { + *firstT = index; + } + } + } + lastT = span.fT; + } +next: + lastDone = span.fDone; + lastUnsortable = span.fUnsortableEnd; + } + return topPt; +} + +bool SkOpSegment::activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op) { + int sumMiWinding = updateWinding(endIndex, index); + int sumSuWinding = updateOppWinding(endIndex, index); + if (fOperand) { + SkTSwap<int>(sumMiWinding, sumSuWinding); + } + int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; + return activeOp(xorMiMask, xorSuMask, index, endIndex, op, &sumMiWinding, &sumSuWinding, + &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); +} + +bool SkOpSegment::activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op, + int* sumMiWinding, int* sumSuWinding, + int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding) { + setUpWindings(index, endIndex, sumMiWinding, sumSuWinding, + maxWinding, sumWinding, oppMaxWinding, oppSumWinding); + bool miFrom; + bool miTo; + bool suFrom; + bool suTo; + if (operand()) { + miFrom = (*oppMaxWinding & xorMiMask) != 0; + miTo = (*oppSumWinding & xorMiMask) != 0; + suFrom = (*maxWinding & xorSuMask) != 0; + suTo = (*sumWinding & xorSuMask) != 0; + } else { + miFrom = (*maxWinding & xorMiMask) != 0; + miTo = (*sumWinding & xorMiMask) != 0; + suFrom = (*oppMaxWinding & xorSuMask) != 0; + suTo = (*oppSumWinding & xorSuMask) != 0; + } + bool result = gActiveEdge[op][miFrom][miTo][suFrom][suTo]; +#if DEBUG_ACTIVE_OP + SkDebugf("%s op=%s miFrom=%d miTo=%d suFrom=%d suTo=%d result=%d\n", __FUNCTION__, + kPathOpStr[op], miFrom, miTo, suFrom, suTo, result); +#endif + return result; +} + +bool SkOpSegment::activeWinding(int index, int endIndex) { + int sumWinding = updateWinding(endIndex, index); + int maxWinding; + return activeWinding(index, endIndex, &maxWinding, &sumWinding); +} + +bool SkOpSegment::activeWinding(int index, int endIndex, int* maxWinding, int* sumWinding) { + setUpWinding(index, endIndex, maxWinding, sumWinding); + bool from = *maxWinding != 0; + bool to = *sumWinding != 0; + bool result = gUnaryActiveEdge[from][to]; + return result; +} + +void SkOpSegment::addAngle(SkTDArray<SkOpAngle>* anglesPtr, int start, int end) const { + SkASSERT(start != end); + SkOpAngle* angle = anglesPtr->append(); +#if DEBUG_ANGLE + SkTDArray<SkOpAngle>& angles = *anglesPtr; + if (angles.count() > 1 && !fTs[start].fTiny) { + SkPoint angle0Pt = (*CurvePointAtT[angles[0].verb()])(angles[0].pts(), + (*angles[0].spans())[angles[0].start()].fT); + SkPoint newPt = (*CurvePointAtT[fVerb])(fPts, fTs[start].fT); + SkASSERT(AlmostEqualUlps(angle0Pt.fX, newPt.fX)); + SkASSERT(AlmostEqualUlps(angle0Pt.fY, newPt.fY)); + } +#endif + angle->set(fPts, fVerb, this, start, end, fTs); +} + +void SkOpSegment::addCancelOutsides(double tStart, double oStart, SkOpSegment* other, double oEnd) { + int tIndex = -1; + int tCount = fTs.count(); + int oIndex = -1; + int oCount = other->fTs.count(); + do { + ++tIndex; + } while (!approximately_negative(tStart - fTs[tIndex].fT) && tIndex < tCount); + int tIndexStart = tIndex; + do { + ++oIndex; + } while (!approximately_negative(oStart - other->fTs[oIndex].fT) && oIndex < oCount); + int oIndexStart = oIndex; + double nextT; + do { + nextT = fTs[++tIndex].fT; + } while (nextT < 1 && approximately_negative(nextT - tStart)); + double oNextT; + do { + oNextT = other->fTs[++oIndex].fT; + } while (oNextT < 1 && approximately_negative(oNextT - oStart)); + // at this point, spans before and after are at: + // fTs[tIndexStart - 1], fTs[tIndexStart], fTs[tIndex] + // if tIndexStart == 0, no prior span + // if nextT == 1, no following span + + // advance the span with zero winding + // if the following span exists (not past the end, non-zero winding) + // connect the two edges + if (!fTs[tIndexStart].fWindValue) { + if (tIndexStart > 0 && fTs[tIndexStart - 1].fWindValue) { +#if DEBUG_CONCIDENT + SkDebugf("%s 1 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n", + __FUNCTION__, fID, other->fID, tIndexStart - 1, + fTs[tIndexStart].fT, xyAtT(tIndexStart).fX, + xyAtT(tIndexStart).fY); +#endif + addTPair(fTs[tIndexStart].fT, other, other->fTs[oIndex].fT, false, + fTs[tIndexStart].fPt); + } + if (nextT < 1 && fTs[tIndex].fWindValue) { +#if DEBUG_CONCIDENT + SkDebugf("%s 2 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n", + __FUNCTION__, fID, other->fID, tIndex, + fTs[tIndex].fT, xyAtT(tIndex).fX, + xyAtT(tIndex).fY); +#endif + addTPair(fTs[tIndex].fT, other, other->fTs[oIndexStart].fT, false, fTs[tIndex].fPt); + } + } else { + SkASSERT(!other->fTs[oIndexStart].fWindValue); + if (oIndexStart > 0 && other->fTs[oIndexStart - 1].fWindValue) { +#if DEBUG_CONCIDENT + SkDebugf("%s 3 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n", + __FUNCTION__, fID, other->fID, oIndexStart - 1, + other->fTs[oIndexStart].fT, other->xyAtT(oIndexStart).fX, + other->xyAtT(oIndexStart).fY); + other->debugAddTPair(other->fTs[oIndexStart].fT, *this, fTs[tIndex].fT); +#endif + } + if (oNextT < 1 && other->fTs[oIndex].fWindValue) { +#if DEBUG_CONCIDENT + SkDebugf("%s 4 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n", + __FUNCTION__, fID, other->fID, oIndex, + other->fTs[oIndex].fT, other->xyAtT(oIndex).fX, + other->xyAtT(oIndex).fY); + other->debugAddTPair(other->fTs[oIndex].fT, *this, fTs[tIndexStart].fT); +#endif + } + } +} + +void SkOpSegment::addCoinOutsides(const SkTDArray<double>& outsideTs, SkOpSegment* other, + double oEnd) { + // walk this to outsideTs[0] + // walk other to outsideTs[1] + // if either is > 0, add a pointer to the other, copying adjacent winding + int tIndex = -1; + int oIndex = -1; + double tStart = outsideTs[0]; + double oStart = outsideTs[1]; + do { + ++tIndex; + } while (!approximately_negative(tStart - fTs[tIndex].fT)); + SkPoint ptStart = fTs[tIndex].fPt; + do { + ++oIndex; + } while (!approximately_negative(oStart - other->fTs[oIndex].fT)); + if (tIndex > 0 || oIndex > 0 || fOperand != other->fOperand) { + addTPair(tStart, other, oStart, false, ptStart); + } + tStart = fTs[tIndex].fT; + oStart = other->fTs[oIndex].fT; + do { + double nextT; + do { + nextT = fTs[++tIndex].fT; + } while (approximately_negative(nextT - tStart)); + tStart = nextT; + ptStart = fTs[tIndex].fPt; + do { + nextT = other->fTs[++oIndex].fT; + } while (approximately_negative(nextT - oStart)); + oStart = nextT; + if (tStart == 1 && oStart == 1 && fOperand == other->fOperand) { + break; + } + addTPair(tStart, other, oStart, false, ptStart); + } while (tStart < 1 && oStart < 1 && !approximately_negative(oEnd - oStart)); +} + +void SkOpSegment::addCubic(const SkPoint pts[4], bool operand, bool evenOdd) { + init(pts, SkPath::kCubic_Verb, operand, evenOdd); + fBounds.setCubicBounds(pts); +} + +void SkOpSegment::addCurveTo(int start, int end, SkPathWriter* path, bool active) const { + SkPoint edge[4]; + const SkPoint* ePtr; + int lastT = fTs.count() - 1; + if (lastT < 0 || (start == 0 && end == lastT) || (start == lastT && end == 0)) { + ePtr = fPts; + } else { + // OPTIMIZE? if not active, skip remainder and return xyAtT(end) + subDivide(start, end, edge); + ePtr = edge; + } + if (active) { + bool reverse = ePtr == fPts && start != 0; + if (reverse) { + path->deferredMoveLine(ePtr[fVerb]); + switch (fVerb) { + case SkPath::kLine_Verb: + path->deferredLine(ePtr[0]); + break; + case SkPath::kQuad_Verb: + path->quadTo(ePtr[1], ePtr[0]); + break; + case SkPath::kCubic_Verb: + path->cubicTo(ePtr[2], ePtr[1], ePtr[0]); + break; + default: + SkASSERT(0); + } + // return ePtr[0]; + } else { + path->deferredMoveLine(ePtr[0]); + switch (fVerb) { + case SkPath::kLine_Verb: + path->deferredLine(ePtr[1]); + break; + case SkPath::kQuad_Verb: + path->quadTo(ePtr[1], ePtr[2]); + break; + case SkPath::kCubic_Verb: + path->cubicTo(ePtr[1], ePtr[2], ePtr[3]); + break; + default: + SkASSERT(0); + } + } + } + // return ePtr[fVerb]; +} + +void SkOpSegment::addLine(const SkPoint pts[2], bool operand, bool evenOdd) { + init(pts, SkPath::kLine_Verb, operand, evenOdd); + fBounds.set(pts, 2); +} + +// add 2 to edge or out of range values to get T extremes +void SkOpSegment::addOtherT(int index, double otherT, int otherIndex) { + SkOpSpan& span = fTs[index]; +#if PIN_ADD_T + if (precisely_less_than_zero(otherT)) { + otherT = 0; + } else if (precisely_greater_than_one(otherT)) { + otherT = 1; + } +#endif + span.fOtherT = otherT; + span.fOtherIndex = otherIndex; +} + +void SkOpSegment::addQuad(const SkPoint pts[3], bool operand, bool evenOdd) { + init(pts, SkPath::kQuad_Verb, operand, evenOdd); + fBounds.setQuadBounds(pts); +} + + // Defer all coincident edge processing until + // after normal intersections have been computed + +// no need to be tricky; insert in normal T order +// resolve overlapping ts when considering coincidence later + + // add non-coincident intersection. Resulting edges are sorted in T. +int SkOpSegment::addT(SkOpSegment* other, const SkPoint& pt, double newT) { + // FIXME: in the pathological case where there is a ton of intercepts, + // binary search? + int insertedAt = -1; + size_t tCount = fTs.count(); + for (size_t index = 0; index < tCount; ++index) { + // OPTIMIZATION: if there are three or more identical Ts, then + // the fourth and following could be further insertion-sorted so + // that all the edges are clockwise or counterclockwise. + // This could later limit segment tests to the two adjacent + // neighbors, although it doesn't help with determining which + // circular direction to go in. + if (newT < fTs[index].fT) { + insertedAt = index; + break; + } + } + SkOpSpan* span; + if (insertedAt >= 0) { + span = fTs.insert(insertedAt); + } else { + insertedAt = tCount; + span = fTs.append(); + } + span->fT = newT; + span->fOther = other; + span->fPt = pt; + span->fWindSum = SK_MinS32; + span->fOppSum = SK_MinS32; + span->fWindValue = 1; + span->fOppValue = 0; + span->fTiny = false; + span->fLoop = false; + if ((span->fDone = newT == 1)) { + ++fDoneSpans; + } + span->fUnsortableStart = false; + span->fUnsortableEnd = false; + int less = -1; + while (&span[less + 1] - fTs.begin() > 0 && xyAtT(&span[less]) == xyAtT(span)) { + if (span[less].fDone) { + break; + } + double tInterval = newT - span[less].fT; + if (precisely_negative(tInterval)) { + break; + } + if (fVerb == SkPath::kCubic_Verb) { + double tMid = newT - tInterval / 2; + SkDPoint midPt = dcubic_xy_at_t(fPts, tMid); + if (!midPt.approximatelyEqual(xyAtT(span))) { + break; + } + } + span[less].fTiny = true; + span[less].fDone = true; + if (approximately_negative(newT - span[less].fT)) { + if (approximately_greater_than_one(newT)) { + SkASSERT(&span[less] - fTs.begin() < fTs.count()); + span[less].fUnsortableStart = true; + if (&span[less - 1] - fTs.begin() >= 0) { + span[less - 1].fUnsortableEnd = true; + } + } + if (approximately_less_than_zero(span[less].fT)) { + SkASSERT(&span[less + 1] - fTs.begin() < fTs.count()); + SkASSERT(&span[less] - fTs.begin() >= 0); + span[less + 1].fUnsortableStart = true; + span[less].fUnsortableEnd = true; + } + } + ++fDoneSpans; + --less; + } + int more = 1; + while (fTs.end() - &span[more - 1] > 1 && xyAtT(&span[more]) == xyAtT(span)) { + if (span[more - 1].fDone) { + break; + } + double tEndInterval = span[more].fT - newT; + if (precisely_negative(tEndInterval)) { + break; + } + if (fVerb == SkPath::kCubic_Verb) { + double tMid = newT - tEndInterval / 2; + SkDPoint midEndPt = dcubic_xy_at_t(fPts, tMid); + if (!midEndPt.approximatelyEqual(xyAtT(span))) { + break; + } + } + span[more - 1].fTiny = true; + span[more - 1].fDone = true; + if (approximately_negative(span[more].fT - newT)) { + if (approximately_greater_than_one(span[more].fT)) { + span[more + 1].fUnsortableStart = true; + span[more].fUnsortableEnd = true; + } + if (approximately_less_than_zero(newT)) { + span[more].fUnsortableStart = true; + span[more - 1].fUnsortableEnd = true; + } + } + ++fDoneSpans; + ++more; + } + return insertedAt; +} + +// set spans from start to end to decrement by one +// note this walks other backwards +// FIMXE: there's probably an edge case that can be constructed where +// two span in one segment are separated by float epsilon on one span but +// not the other, if one segment is very small. For this +// case the counts asserted below may or may not be enough to separate the +// spans. Even if the counts work out, what if the spans aren't correctly +// sorted? It feels better in such a case to match the span's other span +// pointer since both coincident segments must contain the same spans. +void SkOpSegment::addTCancel(double startT, double endT, SkOpSegment* other, + double oStartT, double oEndT) { + SkASSERT(!approximately_negative(endT - startT)); + SkASSERT(!approximately_negative(oEndT - oStartT)); + bool binary = fOperand != other->fOperand; + int index = 0; + while (!approximately_negative(startT - fTs[index].fT)) { + ++index; + } + int oIndex = other->fTs.count(); + while (approximately_positive(other->fTs[--oIndex].fT - oEndT)) + ; + double tRatio = (oEndT - oStartT) / (endT - startT); + SkOpSpan* test = &fTs[index]; + SkOpSpan* oTest = &other->fTs[oIndex]; + SkTDArray<double> outsideTs; + SkTDArray<double> oOutsideTs; + do { + bool decrement = test->fWindValue && oTest->fWindValue && !binary; + bool track = test->fWindValue || oTest->fWindValue; + double testT = test->fT; + double oTestT = oTest->fT; + SkOpSpan* span = test; + do { + if (decrement) { + decrementSpan(span); + } else if (track && span->fT < 1 && oTestT < 1) { + TrackOutside(&outsideTs, span->fT, oTestT); + } + span = &fTs[++index]; + } while (approximately_negative(span->fT - testT)); + SkOpSpan* oSpan = oTest; + double otherTMatchStart = oEndT - (span->fT - startT) * tRatio; + double otherTMatchEnd = oEndT - (test->fT - startT) * tRatio; + SkDEBUGCODE(int originalWindValue = oSpan->fWindValue); + while (approximately_negative(otherTMatchStart - oSpan->fT) + && !approximately_negative(otherTMatchEnd - oSpan->fT)) { + #ifdef SK_DEBUG + SkASSERT(originalWindValue == oSpan->fWindValue); + #endif + if (decrement) { + other->decrementSpan(oSpan); + } else if (track && oSpan->fT < 1 && testT < 1) { + TrackOutside(&oOutsideTs, oSpan->fT, testT); + } + if (!oIndex) { + break; + } + oSpan = &other->fTs[--oIndex]; + } + test = span; + oTest = oSpan; + } while (!approximately_negative(endT - test->fT)); + SkASSERT(!oIndex || approximately_negative(oTest->fT - oStartT)); + // FIXME: determine if canceled edges need outside ts added + if (!done() && outsideTs.count()) { + double tStart = outsideTs[0]; + double oStart = outsideTs[1]; + addCancelOutsides(tStart, oStart, other, oEndT); + int count = outsideTs.count(); + if (count > 2) { + double tStart = outsideTs[count - 2]; + double oStart = outsideTs[count - 1]; + addCancelOutsides(tStart, oStart, other, oEndT); + } + } + if (!other->done() && oOutsideTs.count()) { + double tStart = oOutsideTs[0]; + double oStart = oOutsideTs[1]; + other->addCancelOutsides(tStart, oStart, this, endT); + } +} + +int SkOpSegment::addSelfT(SkOpSegment* other, const SkPoint& pt, double newT) { + int result = addT(other, pt, newT); + SkOpSpan* span = &fTs[result]; + span->fLoop = true; + return result; +} + +int SkOpSegment::addUnsortableT(SkOpSegment* other, bool start, const SkPoint& pt, double newT) { + int result = addT(other, pt, newT); + SkOpSpan* span = &fTs[result]; + if (start) { + if (result > 0) { + span[result - 1].fUnsortableEnd = true; + } + span[result].fUnsortableStart = true; + } else { + span[result].fUnsortableEnd = true; + if (result + 1 < fTs.count()) { + span[result + 1].fUnsortableStart = true; + } + } + return result; +} + +int SkOpSegment::bumpCoincidentThis(const SkOpSpan& oTest, bool opp, int index, + SkTDArray<double>* outsideTs) { + int oWindValue = oTest.fWindValue; + int oOppValue = oTest.fOppValue; + if (opp) { + SkTSwap<int>(oWindValue, oOppValue); + } + SkOpSpan* const test = &fTs[index]; + SkOpSpan* end = test; + const double oStartT = oTest.fT; + do { + if (bumpSpan(end, oWindValue, oOppValue)) { + TrackOutside(outsideTs, end->fT, oStartT); + } + end = &fTs[++index]; + } while (approximately_negative(end->fT - test->fT)); + return index; +} + +// because of the order in which coincidences are resolved, this and other +// may not have the same intermediate points. Compute the corresponding +// intermediate T values (using this as the master, other as the follower) +// and walk other conditionally -- hoping that it catches up in the end +int SkOpSegment::bumpCoincidentOther(const SkOpSpan& test, double oEndT, int& oIndex, + SkTDArray<double>* oOutsideTs) { + SkOpSpan* const oTest = &fTs[oIndex]; + SkOpSpan* oEnd = oTest; + const double startT = test.fT; + const double oStartT = oTest->fT; + while (!approximately_negative(oEndT - oEnd->fT) + && approximately_negative(oEnd->fT - oStartT)) { + zeroSpan(oEnd); + TrackOutside(oOutsideTs, oEnd->fT, startT); + oEnd = &fTs[++oIndex]; + } + return oIndex; +} + +// FIXME: need to test this case: +// contourA has two segments that are coincident +// contourB has two segments that are coincident in the same place +// each ends up with +2/0 pairs for winding count +// since logic below doesn't transfer count (only increments/decrements) can this be +// resolved to +4/0 ? + +// set spans from start to end to increment the greater by one and decrement +// the lesser +void SkOpSegment::addTCoincident(double startT, double endT, SkOpSegment* other, double oStartT, + double oEndT) { + SkASSERT(!approximately_negative(endT - startT)); + SkASSERT(!approximately_negative(oEndT - oStartT)); + bool opp = fOperand ^ other->fOperand; + int index = 0; + while (!approximately_negative(startT - fTs[index].fT)) { + ++index; + } + int oIndex = 0; + while (!approximately_negative(oStartT - other->fTs[oIndex].fT)) { + ++oIndex; + } + SkOpSpan* test = &fTs[index]; + SkOpSpan* oTest = &other->fTs[oIndex]; + SkTDArray<double> outsideTs; + SkTDArray<double> oOutsideTs; + do { + // if either span has an opposite value and the operands don't match, resolve first + // SkASSERT(!test->fDone || !oTest->fDone); + if (test->fDone || oTest->fDone) { + index = advanceCoincidentThis(oTest, opp, index); + oIndex = other->advanceCoincidentOther(test, oEndT, oIndex); + } else { + index = bumpCoincidentThis(*oTest, opp, index, &outsideTs); + oIndex = other->bumpCoincidentOther(*test, oEndT, oIndex, &oOutsideTs); + } + test = &fTs[index]; + oTest = &other->fTs[oIndex]; + } while (!approximately_negative(endT - test->fT)); + SkASSERT(approximately_negative(oTest->fT - oEndT)); + SkASSERT(approximately_negative(oEndT - oTest->fT)); + if (!done() && outsideTs.count()) { + addCoinOutsides(outsideTs, other, oEndT); + } + if (!other->done() && oOutsideTs.count()) { + other->addCoinOutsides(oOutsideTs, this, endT); + } +} + +// FIXME: this doesn't prevent the same span from being added twice +// fix in caller, SkASSERT here? +void SkOpSegment::addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, + const SkPoint& pt) { + int tCount = fTs.count(); + for (int tIndex = 0; tIndex < tCount; ++tIndex) { + const SkOpSpan& span = fTs[tIndex]; + if (!approximately_negative(span.fT - t)) { + break; + } + if (approximately_negative(span.fT - t) && span.fOther == other + && approximately_equal(span.fOtherT, otherT)) { +#if DEBUG_ADD_T_PAIR + SkDebugf("%s addTPair duplicate this=%d %1.9g other=%d %1.9g\n", + __FUNCTION__, fID, t, other->fID, otherT); +#endif + return; + } + } +#if DEBUG_ADD_T_PAIR + SkDebugf("%s addTPair this=%d %1.9g other=%d %1.9g\n", + __FUNCTION__, fID, t, other->fID, otherT); +#endif + int insertedAt = addT(other, pt, t); + int otherInsertedAt = other->addT(this, pt, otherT); + addOtherT(insertedAt, otherT, otherInsertedAt); + other->addOtherT(otherInsertedAt, t, insertedAt); + matchWindingValue(insertedAt, t, borrowWind); + other->matchWindingValue(otherInsertedAt, otherT, borrowWind); +} + +void SkOpSegment::addTwoAngles(int start, int end, SkTDArray<SkOpAngle>* angles) const { + // add edge leading into junction + int min = SkMin32(end, start); + if (fTs[min].fWindValue > 0 || fTs[min].fOppValue > 0) { + addAngle(angles, end, start); + } + // add edge leading away from junction + int step = SkSign32(end - start); + int tIndex = nextExactSpan(end, step); + min = SkMin32(end, tIndex); + if (tIndex >= 0 && (fTs[min].fWindValue > 0 || fTs[min].fOppValue > 0)) { + addAngle(angles, end, tIndex); + } +} + +int SkOpSegment::advanceCoincidentThis(const SkOpSpan* oTest, bool opp, int index) { + SkOpSpan* const test = &fTs[index]; + SkOpSpan* end; + do { + end = &fTs[++index]; + } while (approximately_negative(end->fT - test->fT)); + return index; +} + +int SkOpSegment::advanceCoincidentOther(const SkOpSpan* test, double oEndT, int oIndex) { + SkOpSpan* const oTest = &fTs[oIndex]; + SkOpSpan* oEnd = oTest; + const double oStartT = oTest->fT; + while (!approximately_negative(oEndT - oEnd->fT) + && approximately_negative(oEnd->fT - oStartT)) { + oEnd = &fTs[++oIndex]; + } + return oIndex; +} + +bool SkOpSegment::betweenTs(int lesser, double testT, int greater) const { + if (lesser > greater) { + SkTSwap<int>(lesser, greater); + } + return approximately_between(fTs[lesser].fT, testT, fTs[greater].fT); +} + +void SkOpSegment::buildAngles(int index, SkTDArray<SkOpAngle>* angles, bool includeOpp) const { + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && (includeOpp || fTs[lesser].fOther->fOperand == fOperand) + && precisely_negative(referenceT - fTs[lesser].fT)) { + buildAnglesInner(lesser, angles); + } + do { + buildAnglesInner(index, angles); + } while (++index < fTs.count() && (includeOpp || fTs[index].fOther->fOperand == fOperand) + && precisely_negative(fTs[index].fT - referenceT)); +} + +void SkOpSegment::buildAnglesInner(int index, SkTDArray<SkOpAngle>* angles) const { + const SkOpSpan* span = &fTs[index]; + SkOpSegment* other = span->fOther; +// if there is only one live crossing, and no coincidence, continue +// in the same direction +// if there is coincidence, the only choice may be to reverse direction + // find edge on either side of intersection + int oIndex = span->fOtherIndex; + // if done == -1, prior span has already been processed + int step = 1; + int next = other->nextExactSpan(oIndex, step); + if (next < 0) { + step = -step; + next = other->nextExactSpan(oIndex, step); + } + // add candidate into and away from junction + other->addTwoAngles(next, oIndex, angles); +} + +int SkOpSegment::computeSum(int startIndex, int endIndex, bool binary) { + SkTDArray<SkOpAngle> angles; + addTwoAngles(startIndex, endIndex, &angles); + buildAngles(endIndex, &angles, false); + // OPTIMIZATION: check all angles to see if any have computed wind sum + // before sorting (early exit if none) + SkTDArray<SkOpAngle*> sorted; + bool sortable = SortAngles(angles, &sorted); +#if DEBUG_SORT + sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0); +#endif + if (!sortable) { + return SK_MinS32; + } + int angleCount = angles.count(); + const SkOpAngle* angle; + const SkOpSegment* base; + int winding; + int oWinding; + int firstIndex = 0; + do { + angle = sorted[firstIndex]; + base = angle->segment(); + winding = base->windSum(angle); + if (winding != SK_MinS32) { + oWinding = base->oppSum(angle); + break; + } + if (++firstIndex == angleCount) { + return SK_MinS32; + } + } while (true); + // turn winding into contourWinding + int spanWinding = base->spanSign(angle); + bool inner = UseInnerWinding(winding + spanWinding, winding); +#if DEBUG_WINDING + SkDebugf("%s spanWinding=%d winding=%d sign=%d inner=%d result=%d\n", __FUNCTION__, + spanWinding, winding, angle->sign(), inner, + inner ? winding + spanWinding : winding); +#endif + if (inner) { + winding += spanWinding; + } +#if DEBUG_SORT + base->debugShowSort(__FUNCTION__, sorted, firstIndex, winding, oWinding); +#endif + int nextIndex = firstIndex + 1; + int lastIndex = firstIndex != 0 ? firstIndex : angleCount; + winding -= base->spanSign(angle); + oWinding -= base->oppSign(angle); + do { + if (nextIndex == angleCount) { + nextIndex = 0; + } + angle = sorted[nextIndex]; + SkOpSegment* segment = angle->segment(); + bool opp = base->fOperand ^ segment->fOperand; + int maxWinding, oMaxWinding; + int spanSign = segment->spanSign(angle); + int oppoSign = segment->oppSign(angle); + if (opp) { + oMaxWinding = oWinding; + oWinding -= spanSign; + maxWinding = winding; + if (oppoSign) { + winding -= oppoSign; + } + } else { + maxWinding = winding; + winding -= spanSign; + oMaxWinding = oWinding; + if (oppoSign) { + oWinding -= oppoSign; + } + } + if (segment->windSum(angle) == SK_MinS32) { + if (opp) { + if (UseInnerWinding(oMaxWinding, oWinding)) { + oMaxWinding = oWinding; + } + if (oppoSign && UseInnerWinding(maxWinding, winding)) { + maxWinding = winding; + } + (void) segment->markAndChaseWinding(angle, oMaxWinding, maxWinding); + } else { + if (UseInnerWinding(maxWinding, winding)) { + maxWinding = winding; + } + if (oppoSign && UseInnerWinding(oMaxWinding, oWinding)) { + oMaxWinding = oWinding; + } + (void) segment->markAndChaseWinding(angle, maxWinding, + binary ? oMaxWinding : 0); + } + } + } while (++nextIndex != lastIndex); + int minIndex = SkMin32(startIndex, endIndex); + return windSum(minIndex); +} + +int SkOpSegment::crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, + bool* hitSomething, double mid, bool opp, bool current) const { + SkScalar bottom = fBounds.fBottom; + int bestTIndex = -1; + if (bottom <= *bestY) { + return bestTIndex; + } + SkScalar top = fBounds.fTop; + if (top >= basePt.fY) { + return bestTIndex; + } + if (fBounds.fLeft > basePt.fX) { + return bestTIndex; + } + if (fBounds.fRight < basePt.fX) { + return bestTIndex; + } + if (fBounds.fLeft == fBounds.fRight) { + // if vertical, and directly above test point, wait for another one + return AlmostEqualUlps(basePt.fX, fBounds.fLeft) ? SK_MinS32 : bestTIndex; + } + // intersect ray starting at basePt with edge + SkIntersections intersections; + // OPTIMIZE: use specialty function that intersects ray with curve, + // returning t values only for curve (we don't care about t on ray) + int pts = (intersections.*CurveVertical[fVerb])(fPts, top, bottom, basePt.fX, false); + if (pts == 0 || (current && pts == 1)) { + return bestTIndex; + } + if (current) { + SkASSERT(pts > 1); + int closestIdx = 0; + double closest = fabs(intersections[0][0] - mid); + for (int idx = 1; idx < pts; ++idx) { + double test = fabs(intersections[0][idx] - mid); + if (closest > test) { + closestIdx = idx; + closest = test; + } + } + intersections.quickRemoveOne(closestIdx, --pts); + } + double bestT = -1; + for (int index = 0; index < pts; ++index) { + double foundT = intersections[0][index]; + if (approximately_less_than_zero(foundT) + || approximately_greater_than_one(foundT)) { + continue; + } + SkScalar testY = (*CurvePointAtT[fVerb])(fPts, foundT).fY; + if (approximately_negative(testY - *bestY) + || approximately_negative(basePt.fY - testY)) { + continue; + } + if (pts > 1 && fVerb == SkPath::kLine_Verb) { + return SK_MinS32; // if the intersection is edge on, wait for another one + } + if (fVerb > SkPath::kLine_Verb) { + SkScalar dx = (*CurveSlopeAtT[fVerb])(fPts, foundT).fX; + if (approximately_zero(dx)) { + return SK_MinS32; // hit vertical, wait for another one + } + } + *bestY = testY; + bestT = foundT; + } + if (bestT < 0) { + return bestTIndex; + } + SkASSERT(bestT >= 0); + SkASSERT(bestT <= 1); + int start; + int end = 0; + do { + start = end; + end = nextSpan(start, 1); + } while (fTs[end].fT < bestT); + // FIXME: see next candidate for a better pattern to find the next start/end pair + while (start + 1 < end && fTs[start].fDone) { + ++start; + } + if (!isCanceled(start)) { + *hitT = bestT; + bestTIndex = start; + *hitSomething = true; + } + return bestTIndex; +} + +void SkOpSegment::decrementSpan(SkOpSpan* span) { + SkASSERT(span->fWindValue > 0); + if (--(span->fWindValue) == 0) { + if (!span->fOppValue && !span->fDone) { + span->fDone = true; + ++fDoneSpans; + } + } +} + +bool SkOpSegment::bumpSpan(SkOpSpan* span, int windDelta, int oppDelta) { + SkASSERT(!span->fDone); + span->fWindValue += windDelta; + SkASSERT(span->fWindValue >= 0); + span->fOppValue += oppDelta; + SkASSERT(span->fOppValue >= 0); + if (fXor) { + span->fWindValue &= 1; + } + if (fOppXor) { + span->fOppValue &= 1; + } + if (!span->fWindValue && !span->fOppValue) { + span->fDone = true; + ++fDoneSpans; + return true; + } + return false; +} + +bool SkOpSegment::equalPoints(int greaterTIndex, int lesserTIndex) { + SkASSERT(greaterTIndex >= lesserTIndex); + double greaterT = fTs[greaterTIndex].fT; + double lesserT = fTs[lesserTIndex].fT; + if (greaterT == lesserT) { + return true; + } + if (!approximately_negative(greaterT - lesserT)) { + return false; + } + return xyAtT(greaterTIndex) == xyAtT(lesserTIndex); +} + +/* + The M and S variable name parts stand for the operators. + Mi stands for Minuend (see wiki subtraction, analogous to difference) + Su stands for Subtrahend + The Opp variable name part designates that the value is for the Opposite operator. + Opposite values result from combining coincident spans. + */ +SkOpSegment* SkOpSegment::findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd, + bool* unsortable, SkPathOp op, const int xorMiMask, + const int xorSuMask) { + const int startIndex = *nextStart; + const int endIndex = *nextEnd; + SkASSERT(startIndex != endIndex); + SkDEBUGCODE(const int count = fTs.count()); + SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); + const int step = SkSign32(endIndex - startIndex); + const int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); + SkOpSpan* endSpan = &fTs[end]; + SkOpSegment* other; + if (isSimple(end)) { + // mark the smaller of startIndex, endIndex done, and all adjacent + // spans with the same T value (but not 'other' spans) +#if DEBUG_WINDING + SkDebugf("%s simple\n", __FUNCTION__); +#endif + int min = SkMin32(startIndex, endIndex); + if (fTs[min].fDone) { + return NULL; + } + markDoneBinary(min); + other = endSpan->fOther; + *nextStart = endSpan->fOtherIndex; + double startT = other->fTs[*nextStart].fT; + *nextEnd = *nextStart; + do { + *nextEnd += step; + } + while (precisely_zero(startT - other->fTs[*nextEnd].fT)); + SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count()); + return other; + } + // more than one viable candidate -- measure angles to find best + SkTDArray<SkOpAngle> angles; + SkASSERT(startIndex - endIndex != 0); + SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); + addTwoAngles(startIndex, end, &angles); + buildAngles(end, &angles, true); + SkTDArray<SkOpAngle*> sorted; + bool sortable = SortAngles(angles, &sorted); + int angleCount = angles.count(); + int firstIndex = findStartingEdge(sorted, startIndex, end); + SkASSERT(firstIndex >= 0); +#if DEBUG_SORT + debugShowSort(__FUNCTION__, sorted, firstIndex); +#endif + if (!sortable) { + *unsortable = true; + return NULL; + } + SkASSERT(sorted[firstIndex]->segment() == this); +#if DEBUG_WINDING + SkDebugf("%s firstIndex=[%d] sign=%d\n", __FUNCTION__, firstIndex, + sorted[firstIndex]->sign()); +#endif + int sumMiWinding = updateWinding(endIndex, startIndex); + int sumSuWinding = updateOppWinding(endIndex, startIndex); + if (operand()) { + SkTSwap<int>(sumMiWinding, sumSuWinding); + } + int nextIndex = firstIndex + 1; + int lastIndex = firstIndex != 0 ? firstIndex : angleCount; + const SkOpAngle* foundAngle = NULL; + bool foundDone = false; + // iterate through the angle, and compute everyone's winding + SkOpSegment* nextSegment; + int activeCount = 0; + do { + SkASSERT(nextIndex != firstIndex); + if (nextIndex == angleCount) { + nextIndex = 0; + } + const SkOpAngle* nextAngle = sorted[nextIndex]; + nextSegment = nextAngle->segment(); + int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; + bool activeAngle = nextSegment->activeOp(xorMiMask, xorSuMask, nextAngle->start(), + nextAngle->end(), op, &sumMiWinding, &sumSuWinding, + &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + if (activeAngle) { + ++activeCount; + if (!foundAngle || (foundDone && activeCount & 1)) { + if (nextSegment->tiny(nextAngle)) { + *unsortable = true; + return NULL; + } + foundAngle = nextAngle; + foundDone = nextSegment->done(nextAngle) && !nextSegment->tiny(nextAngle); + } + } + if (nextSegment->done()) { + continue; + } + if (nextSegment->windSum(nextAngle) != SK_MinS32) { + continue; + } + SkOpSpan* last = nextSegment->markAngle(maxWinding, sumWinding, oppMaxWinding, + oppSumWinding, activeAngle, nextAngle); + if (last) { + *chase->append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append id=%d\n", __FUNCTION__, + last->fOther->fTs[last->fOtherIndex].fOther->debugID()); +#endif + } + } while (++nextIndex != lastIndex); + markDoneBinary(SkMin32(startIndex, endIndex)); + if (!foundAngle) { + return NULL; + } + *nextStart = foundAngle->start(); + *nextEnd = foundAngle->end(); + nextSegment = foundAngle->segment(); + +#if DEBUG_WINDING + SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n", + __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); + #endif + return nextSegment; +} + +SkOpSegment* SkOpSegment::findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, + int* nextEnd, bool* unsortable) { + const int startIndex = *nextStart; + const int endIndex = *nextEnd; + SkASSERT(startIndex != endIndex); + SkDEBUGCODE(const int count = fTs.count()); + SkASSERT(startIndex < endIndex ? startIndex < count - 1 : startIndex > 0); + const int step = SkSign32(endIndex - startIndex); + const int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); + SkOpSpan* endSpan = &fTs[end]; + SkOpSegment* other; + if (isSimple(end)) { + // mark the smaller of startIndex, endIndex done, and all adjacent + // spans with the same T value (but not 'other' spans) +#if DEBUG_WINDING + SkDebugf("%s simple\n", __FUNCTION__); +#endif + int min = SkMin32(startIndex, endIndex); + if (fTs[min].fDone) { + return NULL; + } + markDoneUnary(min); + other = endSpan->fOther; + *nextStart = endSpan->fOtherIndex; + double startT = other->fTs[*nextStart].fT; + *nextEnd = *nextStart; + do { + *nextEnd += step; + } + while (precisely_zero(startT - other->fTs[*nextEnd].fT)); + SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count()); + return other; + } + // more than one viable candidate -- measure angles to find best + SkTDArray<SkOpAngle> angles; + SkASSERT(startIndex - endIndex != 0); + SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); + addTwoAngles(startIndex, end, &angles); + buildAngles(end, &angles, true); + SkTDArray<SkOpAngle*> sorted; + bool sortable = SortAngles(angles, &sorted); + int angleCount = angles.count(); + int firstIndex = findStartingEdge(sorted, startIndex, end); + SkASSERT(firstIndex >= 0); +#if DEBUG_SORT + debugShowSort(__FUNCTION__, sorted, firstIndex); +#endif + if (!sortable) { + *unsortable = true; + return NULL; + } + SkASSERT(sorted[firstIndex]->segment() == this); +#if DEBUG_WINDING + SkDebugf("%s firstIndex=[%d] sign=%d\n", __FUNCTION__, firstIndex, + sorted[firstIndex]->sign()); +#endif + int sumWinding = updateWinding(endIndex, startIndex); + int nextIndex = firstIndex + 1; + int lastIndex = firstIndex != 0 ? firstIndex : angleCount; + const SkOpAngle* foundAngle = NULL; + bool foundDone = false; + // iterate through the angle, and compute everyone's winding + SkOpSegment* nextSegment; + int activeCount = 0; + do { + SkASSERT(nextIndex != firstIndex); + if (nextIndex == angleCount) { + nextIndex = 0; + } + const SkOpAngle* nextAngle = sorted[nextIndex]; + nextSegment = nextAngle->segment(); + int maxWinding; + bool activeAngle = nextSegment->activeWinding(nextAngle->start(), nextAngle->end(), + &maxWinding, &sumWinding); + if (activeAngle) { + ++activeCount; + if (!foundAngle || (foundDone && activeCount & 1)) { + if (nextSegment->tiny(nextAngle)) { + *unsortable = true; + return NULL; + } + foundAngle = nextAngle; + foundDone = nextSegment->done(nextAngle); + } + } + if (nextSegment->done()) { + continue; + } + if (nextSegment->windSum(nextAngle) != SK_MinS32) { + continue; + } + SkOpSpan* last = nextSegment->markAngle(maxWinding, sumWinding, activeAngle, nextAngle); + if (last) { + *chase->append() = last; +#if DEBUG_WINDING + SkDebugf("%s chase.append id=%d\n", __FUNCTION__, + last->fOther->fTs[last->fOtherIndex].fOther->debugID()); +#endif + } + } while (++nextIndex != lastIndex); + markDoneUnary(SkMin32(startIndex, endIndex)); + if (!foundAngle) { + return NULL; + } + *nextStart = foundAngle->start(); + *nextEnd = foundAngle->end(); + nextSegment = foundAngle->segment(); +#if DEBUG_WINDING + SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n", + __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); + #endif + return nextSegment; +} + +SkOpSegment* SkOpSegment::findNextXor(int* nextStart, int* nextEnd, bool* unsortable) { + const int startIndex = *nextStart; + const int endIndex = *nextEnd; + SkASSERT(startIndex != endIndex); + SkDEBUGCODE(int count = fTs.count()); + SkASSERT(startIndex < endIndex ? startIndex < count - 1 + : startIndex > 0); + int step = SkSign32(endIndex - startIndex); + int end = nextExactSpan(startIndex, step); + SkASSERT(end >= 0); + SkOpSpan* endSpan = &fTs[end]; + SkOpSegment* other; + if (isSimple(end)) { +#if DEBUG_WINDING + SkDebugf("%s simple\n", __FUNCTION__); +#endif + int min = SkMin32(startIndex, endIndex); + if (fTs[min].fDone) { + return NULL; + } + markDone(min, 1); + other = endSpan->fOther; + *nextStart = endSpan->fOtherIndex; + double startT = other->fTs[*nextStart].fT; + // FIXME: I don't know why the logic here is difference from the winding case + SkDEBUGCODE(bool firstLoop = true;) + if ((approximately_less_than_zero(startT) && step < 0) + || (approximately_greater_than_one(startT) && step > 0)) { + step = -step; + SkDEBUGCODE(firstLoop = false;) + } + do { + *nextEnd = *nextStart; + do { + *nextEnd += step; + } + while (precisely_zero(startT - other->fTs[*nextEnd].fT)); + if (other->fTs[SkMin32(*nextStart, *nextEnd)].fWindValue) { + break; + } +#ifdef SK_DEBUG + SkASSERT(firstLoop); +#endif + SkDEBUGCODE(firstLoop = false;) + step = -step; + } while (true); + SkASSERT(step < 0 ? *nextEnd >= 0 : *nextEnd < other->fTs.count()); + return other; + } + SkTDArray<SkOpAngle> angles; + SkASSERT(startIndex - endIndex != 0); + SkASSERT((startIndex - endIndex < 0) ^ (step < 0)); + addTwoAngles(startIndex, end, &angles); + buildAngles(end, &angles, false); + SkTDArray<SkOpAngle*> sorted; + bool sortable = SortAngles(angles, &sorted); + if (!sortable) { + *unsortable = true; +#if DEBUG_SORT + debugShowSort(__FUNCTION__, sorted, findStartingEdge(sorted, startIndex, end), 0, 0); +#endif + return NULL; + } + int angleCount = angles.count(); + int firstIndex = findStartingEdge(sorted, startIndex, end); + SkASSERT(firstIndex >= 0); +#if DEBUG_SORT + debugShowSort(__FUNCTION__, sorted, firstIndex, 0, 0); +#endif + SkASSERT(sorted[firstIndex]->segment() == this); + int nextIndex = firstIndex + 1; + int lastIndex = firstIndex != 0 ? firstIndex : angleCount; + const SkOpAngle* foundAngle = NULL; + bool foundDone = false; + SkOpSegment* nextSegment; + int activeCount = 0; + do { + SkASSERT(nextIndex != firstIndex); + if (nextIndex == angleCount) { + nextIndex = 0; + } + const SkOpAngle* nextAngle = sorted[nextIndex]; + nextSegment = nextAngle->segment(); + ++activeCount; + if (!foundAngle || (foundDone && activeCount & 1)) { + if (nextSegment->tiny(nextAngle)) { + *unsortable = true; + return NULL; + } + foundAngle = nextAngle; + foundDone = nextSegment->done(nextAngle); + } + if (nextSegment->done()) { + continue; + } + } while (++nextIndex != lastIndex); + markDone(SkMin32(startIndex, endIndex), 1); + if (!foundAngle) { + return NULL; + } + *nextStart = foundAngle->start(); + *nextEnd = foundAngle->end(); + nextSegment = foundAngle->segment(); +#if DEBUG_WINDING + SkDebugf("%s from:[%d] to:[%d] start=%d end=%d\n", + __FUNCTION__, debugID(), nextSegment->debugID(), *nextStart, *nextEnd); + #endif + return nextSegment; +} + +int SkOpSegment::findStartingEdge(const SkTDArray<SkOpAngle*>& sorted, int start, int end) { + int angleCount = sorted.count(); + int firstIndex = -1; + for (int angleIndex = 0; angleIndex < angleCount; ++angleIndex) { + const SkOpAngle* angle = sorted[angleIndex]; + if (angle->segment() == this && angle->start() == end && + angle->end() == start) { + firstIndex = angleIndex; + break; + } + } + return firstIndex; +} + +// FIXME: this is tricky code; needs its own unit test +// note that fOtherIndex isn't computed yet, so it can't be used here +void SkOpSegment::findTooCloseToCall() { + int count = fTs.count(); + if (count < 3) { // require t=0, x, 1 at minimum + return; + } + int matchIndex = 0; + int moCount; + SkOpSpan* match; + SkOpSegment* mOther; + do { + match = &fTs[matchIndex]; + mOther = match->fOther; + // FIXME: allow quads, cubics to be near coincident? + if (mOther->fVerb == SkPath::kLine_Verb) { + moCount = mOther->fTs.count(); + if (moCount >= 3) { + break; + } + } + if (++matchIndex >= count) { + return; + } + } while (true); // require t=0, x, 1 at minimum + // OPTIMIZATION: defer matchPt until qualifying toCount is found? + const SkPoint* matchPt = &xyAtT(match); + // look for a pair of nearby T values that map to the same (x,y) value + // if found, see if the pair of other segments share a common point. If + // so, the span from here to there is coincident. + for (int index = matchIndex + 1; index < count; ++index) { + SkOpSpan* test = &fTs[index]; + if (test->fDone) { + continue; + } + SkOpSegment* tOther = test->fOther; + if (tOther->fVerb != SkPath::kLine_Verb) { + continue; // FIXME: allow quads, cubics to be near coincident? + } + int toCount = tOther->fTs.count(); + if (toCount < 3) { // require t=0, x, 1 at minimum + continue; + } + const SkPoint* testPt = &xyAtT(test); + if (*matchPt != *testPt) { + matchIndex = index; + moCount = toCount; + match = test; + mOther = tOther; + matchPt = testPt; + continue; + } + int moStart = -1; + int moEnd = -1; + double moStartT = 0; + double moEndT = 0; + for (int moIndex = 0; moIndex < moCount; ++moIndex) { + SkOpSpan& moSpan = mOther->fTs[moIndex]; + if (moSpan.fDone) { + continue; + } + if (moSpan.fOther == this) { + if (moSpan.fOtherT == match->fT) { + moStart = moIndex; + moStartT = moSpan.fT; + } + continue; + } + if (moSpan.fOther == tOther) { + if (tOther->windValueAt(moSpan.fOtherT) == 0) { + moStart = -1; + break; + } + SkASSERT(moEnd == -1); + moEnd = moIndex; + moEndT = moSpan.fT; + } + } + if (moStart < 0 || moEnd < 0) { + continue; + } + // FIXME: if moStartT, moEndT are initialized to NaN, can skip this test + if (approximately_equal(moStartT, moEndT)) { + continue; + } + int toStart = -1; + int toEnd = -1; + double toStartT = 0; + double toEndT = 0; + for (int toIndex = 0; toIndex < toCount; ++toIndex) { + SkOpSpan& toSpan = tOther->fTs[toIndex]; + if (toSpan.fDone) { + continue; + } + if (toSpan.fOther == this) { + if (toSpan.fOtherT == test->fT) { + toStart = toIndex; + toStartT = toSpan.fT; + } + continue; + } + if (toSpan.fOther == mOther && toSpan.fOtherT == moEndT) { + if (mOther->windValueAt(toSpan.fOtherT) == 0) { + moStart = -1; + break; + } + SkASSERT(toEnd == -1); + toEnd = toIndex; + toEndT = toSpan.fT; + } + } + // FIXME: if toStartT, toEndT are initialized to NaN, can skip this test + if (toStart <= 0 || toEnd <= 0) { + continue; + } + if (approximately_equal(toStartT, toEndT)) { + continue; + } + // test to see if the segment between there and here is linear + if (!mOther->isLinear(moStart, moEnd) + || !tOther->isLinear(toStart, toEnd)) { + continue; + } + bool flipped = (moStart - moEnd) * (toStart - toEnd) < 1; + if (flipped) { + mOther->addTCancel(moStartT, moEndT, tOther, toEndT, toStartT); + } else { + mOther->addTCoincident(moStartT, moEndT, tOther, toStartT, toEndT); + } + } +} + +// FIXME: either: +// a) mark spans with either end unsortable as done, or +// b) rewrite findTop / findTopSegment / findTopContour to iterate further +// when encountering an unsortable span + +// OPTIMIZATION : for a pair of lines, can we compute points at T (cached) +// and use more concise logic like the old edge walker code? +// FIXME: this needs to deal with coincident edges +SkOpSegment* SkOpSegment::findTop(int* tIndexPtr, int* endIndexPtr, bool* unsortable, + bool onlySortable) { + // iterate through T intersections and return topmost + // topmost tangent from y-min to first pt is closer to horizontal + SkASSERT(!done()); + int firstT = -1; + /* SkPoint topPt = */ activeLeftTop(onlySortable, &firstT); + if (firstT < 0) { + *unsortable = true; + firstT = 0; + while (fTs[firstT].fDone) { + SkASSERT(firstT < fTs.count()); + ++firstT; + } + *tIndexPtr = firstT; + *endIndexPtr = nextExactSpan(firstT, 1); + return this; + } + // sort the edges to find the leftmost + int step = 1; + int end = nextSpan(firstT, step); + if (end == -1) { + step = -1; + end = nextSpan(firstT, step); + SkASSERT(end != -1); + } + // if the topmost T is not on end, or is three-way or more, find left + // look for left-ness from tLeft to firstT (matching y of other) + SkTDArray<SkOpAngle> angles; + SkASSERT(firstT - end != 0); + addTwoAngles(end, firstT, &angles); + buildAngles(firstT, &angles, true); + SkTDArray<SkOpAngle*> sorted; + bool sortable = SortAngles(angles, &sorted); + int first = SK_MaxS32; + SkScalar top = SK_ScalarMax; + int count = sorted.count(); + for (int index = 0; index < count; ++index) { + const SkOpAngle* angle = sorted[index]; + SkOpSegment* next = angle->segment(); + SkPathOpsBounds bounds; + next->subDivideBounds(angle->end(), angle->start(), &bounds); + if (approximately_greater(top, bounds.fTop)) { + top = bounds.fTop; + first = index; + } + } + SkASSERT(first < SK_MaxS32); +#if DEBUG_SORT // || DEBUG_SWAP_TOP + sorted[first]->segment()->debugShowSort(__FUNCTION__, sorted, first, 0, 0); +#endif + if (onlySortable && !sortable) { + *unsortable = true; + return NULL; + } + // skip edges that have already been processed + firstT = first - 1; + SkOpSegment* leftSegment; + do { + if (++firstT == count) { + firstT = 0; + } + const SkOpAngle* angle = sorted[firstT]; + SkASSERT(!onlySortable || !angle->unsortable()); + leftSegment = angle->segment(); + *tIndexPtr = angle->end(); + *endIndexPtr = angle->start(); + } while (leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fDone); + if (leftSegment->verb() >= SkPath::kQuad_Verb) { + const int tIndex = *tIndexPtr; + const int endIndex = *endIndexPtr; + if (!leftSegment->clockwise(tIndex, endIndex)) { + bool swap = !leftSegment->monotonicInY(tIndex, endIndex) + && !leftSegment->serpentine(tIndex, endIndex); + #if DEBUG_SWAP_TOP + SkDebugf("%s swap=%d serpentine=%d containedByEnds=%d monotonic=%d\n", __FUNCTION__, + swap, + leftSegment->serpentine(tIndex, endIndex), + leftSegment->controlsContainedByEnds(tIndex, endIndex), + leftSegment->monotonicInY(tIndex, endIndex)); + #endif + if (swap) { + // FIXME: I doubt it makes sense to (necessarily) swap if the edge was not the first + // sorted but merely the first not already processed (i.e., not done) + SkTSwap(*tIndexPtr, *endIndexPtr); + } + } + } + SkASSERT(!leftSegment->fTs[SkMin32(*tIndexPtr, *endIndexPtr)].fTiny); + return leftSegment; +} + +// FIXME: not crazy about this +// when the intersections are performed, the other index is into an +// incomplete array. As the array grows, the indices become incorrect +// while the following fixes the indices up again, it isn't smart about +// skipping segments whose indices are already correct +// assuming we leave the code that wrote the index in the first place +void SkOpSegment::fixOtherTIndex() { + int iCount = fTs.count(); + for (int i = 0; i < iCount; ++i) { + SkOpSpan& iSpan = fTs[i]; + double oT = iSpan.fOtherT; + SkOpSegment* other = iSpan.fOther; + int oCount = other->fTs.count(); + for (int o = 0; o < oCount; ++o) { + SkOpSpan& oSpan = other->fTs[o]; + if (oT == oSpan.fT && this == oSpan.fOther && oSpan.fOtherT == iSpan.fT) { + iSpan.fOtherIndex = o; + break; + } + } + } +} + +void SkOpSegment::init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd) { + fDoneSpans = 0; + fOperand = operand; + fXor = evenOdd; + fPts = pts; + fVerb = verb; +} + +void SkOpSegment::initWinding(int start, int end) { + int local = spanSign(start, end); + int oppLocal = oppSign(start, end); + (void) markAndChaseWinding(start, end, local, oppLocal); + // OPTIMIZATION: the reverse mark and chase could skip the first marking + (void) markAndChaseWinding(end, start, local, oppLocal); +} + +/* +when we start with a vertical intersect, we try to use the dx to determine if the edge is to +the left or the right of vertical. This determines if we need to add the span's +sign or not. However, this isn't enough. +If the supplied sign (winding) is zero, then we didn't hit another vertical span, so dx is needed. +If there was a winding, then it may or may not need adjusting. If the span the winding was borrowed +from has the same x direction as this span, the winding should change. If the dx is opposite, then +the same winding is shared by both. +*/ +void SkOpSegment::initWinding(int start, int end, double tHit, int winding, SkScalar hitDx, + int oppWind, SkScalar hitOppDx) { + SkASSERT(hitDx || !winding); + SkScalar dx = (*CurveSlopeAtT[fVerb])(fPts, tHit).fX; + SkASSERT(dx); + int windVal = windValue(SkMin32(start, end)); +#if DEBUG_WINDING_AT_T + SkDebugf("%s oldWinding=%d hitDx=%c dx=%c windVal=%d", __FUNCTION__, winding, + hitDx ? hitDx > 0 ? '+' : '-' : '0', dx > 0 ? '+' : '-', windVal); +#endif + if (!winding) { + winding = dx < 0 ? windVal : -windVal; + } else if (winding * dx < 0) { + int sideWind = winding + (dx < 0 ? windVal : -windVal); + if (abs(winding) < abs(sideWind)) { + winding = sideWind; + } + } +#if DEBUG_WINDING_AT_T + SkDebugf(" winding=%d\n", winding); +#endif + SkDEBUGCODE(int oppLocal = oppSign(start, end)); + SkASSERT(hitOppDx || !oppWind || !oppLocal); + int oppWindVal = oppValue(SkMin32(start, end)); + if (!oppWind) { + oppWind = dx < 0 ? oppWindVal : -oppWindVal; + } else if (hitOppDx * dx >= 0) { + int oppSideWind = oppWind + (dx < 0 ? oppWindVal : -oppWindVal); + if (abs(oppWind) < abs(oppSideWind)) { + oppWind = oppSideWind; + } + } + (void) markAndChaseWinding(start, end, winding, oppWind); +} + +bool SkOpSegment::isLinear(int start, int end) const { + if (fVerb == SkPath::kLine_Verb) { + return true; + } + if (fVerb == SkPath::kQuad_Verb) { + SkDQuad qPart = SkDQuad::SubDivide(fPts, fTs[start].fT, fTs[end].fT); + return qPart.isLinear(0, 2); + } else { + SkASSERT(fVerb == SkPath::kCubic_Verb); + SkDCubic cPart = SkDCubic::SubDivide(fPts, fTs[start].fT, fTs[end].fT); + return cPart.isLinear(0, 3); + } +} + +// OPTIMIZE: successive calls could start were the last leaves off +// or calls could specialize to walk forwards or backwards +bool SkOpSegment::isMissing(double startT) const { + size_t tCount = fTs.count(); + for (size_t index = 0; index < tCount; ++index) { + if (approximately_zero(startT - fTs[index].fT)) { + return false; + } + } + return true; +} + +bool SkOpSegment::isSimple(int end) const { + int count = fTs.count(); + if (count == 2) { + return true; + } + double t = fTs[end].fT; + if (approximately_less_than_zero(t)) { + return !approximately_less_than_zero(fTs[1].fT); + } + if (approximately_greater_than_one(t)) { + return !approximately_greater_than_one(fTs[count - 2].fT); + } + return false; +} + +// this span is excluded by the winding rule -- chase the ends +// as long as they are unambiguous to mark connections as done +// and give them the same winding value +SkOpSpan* SkOpSegment::markAndChaseDone(int index, int endIndex, int winding) { + int step = SkSign32(endIndex - index); + int min = SkMin32(index, endIndex); + markDone(min, winding); + SkOpSpan* last; + SkOpSegment* other = this; + while ((other = other->nextChase(&index, step, &min, &last))) { + other->markDone(min, winding); + } + return last; +} + +SkOpSpan* SkOpSegment::markAndChaseDoneBinary(const SkOpAngle* angle, int winding, int oppWinding) { + int index = angle->start(); + int endIndex = angle->end(); + int step = SkSign32(endIndex - index); + int min = SkMin32(index, endIndex); + markDoneBinary(min, winding, oppWinding); + SkOpSpan* last; + SkOpSegment* other = this; + while ((other = other->nextChase(&index, step, &min, &last))) { + other->markDoneBinary(min, winding, oppWinding); + } + return last; +} + +SkOpSpan* SkOpSegment::markAndChaseDoneBinary(int index, int endIndex) { + int step = SkSign32(endIndex - index); + int min = SkMin32(index, endIndex); + markDoneBinary(min); + SkOpSpan* last; + SkOpSegment* other = this; + while ((other = other->nextChase(&index, step, &min, &last))) { + if (other->done()) { + return NULL; + } + other->markDoneBinary(min); + } + return last; +} + +SkOpSpan* SkOpSegment::markAndChaseDoneUnary(int index, int endIndex) { + int step = SkSign32(endIndex - index); + int min = SkMin32(index, endIndex); + markDoneUnary(min); + SkOpSpan* last; + SkOpSegment* other = this; + while ((other = other->nextChase(&index, step, &min, &last))) { + if (other->done()) { + return NULL; + } + other->markDoneUnary(min); + } + return last; +} + +SkOpSpan* SkOpSegment::markAndChaseDoneUnary(const SkOpAngle* angle, int winding) { + int index = angle->start(); + int endIndex = angle->end(); + return markAndChaseDone(index, endIndex, winding); +} + +SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, const int winding) { + int index = angle->start(); + int endIndex = angle->end(); + int step = SkSign32(endIndex - index); + int min = SkMin32(index, endIndex); + markWinding(min, winding); + SkOpSpan* last; + SkOpSegment* other = this; + while ((other = other->nextChase(&index, step, &min, &last))) { + if (other->fTs[min].fWindSum != SK_MinS32) { + SkASSERT(other->fTs[min].fWindSum == winding); + return NULL; + } + other->markWinding(min, winding); + } + return last; +} + +SkOpSpan* SkOpSegment::markAndChaseWinding(int index, int endIndex, int winding, int oppWinding) { + int min = SkMin32(index, endIndex); + int step = SkSign32(endIndex - index); + markWinding(min, winding, oppWinding); + SkOpSpan* last; + SkOpSegment* other = this; + while ((other = other->nextChase(&index, step, &min, &last))) { + if (other->fTs[min].fWindSum != SK_MinS32) { + SkASSERT(other->fTs[min].fWindSum == winding || other->fTs[min].fLoop); + return NULL; + } + other->markWinding(min, winding, oppWinding); + } + return last; +} + +SkOpSpan* SkOpSegment::markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding) { + int start = angle->start(); + int end = angle->end(); + return markAndChaseWinding(start, end, winding, oppWinding); +} + +SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, bool activeAngle, + const SkOpAngle* angle) { + SkASSERT(angle->segment() == this); + if (UseInnerWinding(maxWinding, sumWinding)) { + maxWinding = sumWinding; + } + SkOpSpan* last; + if (activeAngle) { + last = markAndChaseWinding(angle, maxWinding); + } else { + last = markAndChaseDoneUnary(angle, maxWinding); + } + return last; +} + +SkOpSpan* SkOpSegment::markAngle(int maxWinding, int sumWinding, int oppMaxWinding, + int oppSumWinding, bool activeAngle, const SkOpAngle* angle) { + SkASSERT(angle->segment() == this); + if (UseInnerWinding(maxWinding, sumWinding)) { + maxWinding = sumWinding; + } + if (oppMaxWinding != oppSumWinding && UseInnerWinding(oppMaxWinding, oppSumWinding)) { + oppMaxWinding = oppSumWinding; + } + SkOpSpan* last; + if (activeAngle) { + last = markAndChaseWinding(angle, maxWinding, oppMaxWinding); + } else { + last = markAndChaseDoneBinary(angle, maxWinding, oppMaxWinding); + } + return last; +} + +// FIXME: this should also mark spans with equal (x,y) +// This may be called when the segment is already marked done. While this +// wastes time, it shouldn't do any more than spin through the T spans. +// OPTIMIZATION: abort on first done found (assuming that this code is +// always called to mark segments done). +void SkOpSegment::markDone(int index, int winding) { + // SkASSERT(!done()); + SkASSERT(winding); + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneDone(__FUNCTION__, lesser, winding); + } + do { + markOneDone(__FUNCTION__, index, winding); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); +} + +void SkOpSegment::markDoneBinary(int index, int winding, int oppWinding) { + // SkASSERT(!done()); + SkASSERT(winding || oppWinding); + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneDoneBinary(__FUNCTION__, lesser, winding, oppWinding); + } + do { + markOneDoneBinary(__FUNCTION__, index, winding, oppWinding); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); +} + +void SkOpSegment::markDoneBinary(int index) { + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneDoneBinary(__FUNCTION__, lesser); + } + do { + markOneDoneBinary(__FUNCTION__, index); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); +} + +void SkOpSegment::markDoneUnary(int index) { + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneDoneUnary(__FUNCTION__, lesser); + } + do { + markOneDoneUnary(__FUNCTION__, index); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); +} + +void SkOpSegment::markOneDone(const char* funName, int tIndex, int winding) { + SkOpSpan* span = markOneWinding(funName, tIndex, winding); + if (!span) { + return; + } + span->fDone = true; + fDoneSpans++; +} + +void SkOpSegment::markOneDoneBinary(const char* funName, int tIndex) { + SkOpSpan* span = verifyOneWinding(funName, tIndex); + if (!span) { + return; + } + span->fDone = true; + fDoneSpans++; +} + +void SkOpSegment::markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding) { + SkOpSpan* span = markOneWinding(funName, tIndex, winding, oppWinding); + if (!span) { + return; + } + span->fDone = true; + fDoneSpans++; +} + +void SkOpSegment::markOneDoneUnary(const char* funName, int tIndex) { + SkOpSpan* span = verifyOneWindingU(funName, tIndex); + if (!span) { + return; + } + span->fDone = true; + fDoneSpans++; +} + +SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding) { + SkOpSpan& span = fTs[tIndex]; + if (span.fDone) { + return NULL; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(funName, span, winding); +#endif + SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding); +#ifdef SK_DEBUG + SkASSERT(abs(winding) <= gDebugMaxWindSum); +#endif + span.fWindSum = winding; + return &span; +} + +SkOpSpan* SkOpSegment::markOneWinding(const char* funName, int tIndex, int winding, + int oppWinding) { + SkOpSpan& span = fTs[tIndex]; + if (span.fDone) { + return NULL; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(funName, span, winding, oppWinding); +#endif + SkASSERT(span.fWindSum == SK_MinS32 || span.fWindSum == winding); +#ifdef SK_DEBUG + SkASSERT(abs(winding) <= gDebugMaxWindSum); +#endif + span.fWindSum = winding; + SkASSERT(span.fOppSum == SK_MinS32 || span.fOppSum == oppWinding); +#ifdef SK_DEBUG + SkASSERT(abs(oppWinding) <= gDebugMaxWindSum); +#endif + span.fOppSum = oppWinding; + return &span; +} + +// from http://stackoverflow.com/questions/1165647/how-to-determine-if-a-list-of-polygon-points-are-in-clockwise-order +bool SkOpSegment::clockwise(int tStart, int tEnd) const { + SkASSERT(fVerb != SkPath::kLine_Verb); + SkPoint edge[4]; + subDivide(tStart, tEnd, edge); + double sum = (edge[0].fX - edge[fVerb].fX) * (edge[0].fY + edge[fVerb].fY); + if (fVerb == SkPath::kCubic_Verb) { + SkScalar lesser = SkTMin<SkScalar>(edge[0].fY, edge[3].fY); + if (edge[1].fY < lesser && edge[2].fY < lesser) { + SkDLine tangent1 = {{ {edge[0].fX, edge[0].fY}, {edge[1].fX, edge[1].fY} }}; + SkDLine tangent2 = {{ {edge[2].fX, edge[2].fY}, {edge[3].fX, edge[3].fY} }}; + if (SkIntersections::Test(tangent1, tangent2)) { + SkPoint topPt = cubic_top(fPts, fTs[tStart].fT, fTs[tEnd].fT); + sum += (topPt.fX - edge[0].fX) * (topPt.fY + edge[0].fY); + sum += (edge[3].fX - topPt.fX) * (edge[3].fY + topPt.fY); + return sum <= 0; + } + } + } + for (int idx = 0; idx < fVerb; ++idx){ + sum += (edge[idx + 1].fX - edge[idx].fX) * (edge[idx + 1].fY + edge[idx].fY); + } + return sum <= 0; +} + +bool SkOpSegment::monotonicInY(int tStart, int tEnd) const { + if (fVerb == SkPath::kLine_Verb) { + return false; + } + if (fVerb == SkPath::kQuad_Verb) { + SkDQuad dst = SkDQuad::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); + return dst.monotonicInY(); + } + SkASSERT(fVerb == SkPath::kCubic_Verb); + SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); + return dst.monotonicInY(); +} + +bool SkOpSegment::serpentine(int tStart, int tEnd) const { + if (fVerb != SkPath::kCubic_Verb) { + return false; + } + SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); + return dst.serpentine(); +} + +SkOpSpan* SkOpSegment::verifyOneWinding(const char* funName, int tIndex) { + SkOpSpan& span = fTs[tIndex]; + if (span.fDone) { + return NULL; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(funName, span, span.fWindSum, span.fOppSum); +#endif + SkASSERT(span.fWindSum != SK_MinS32); + SkASSERT(span.fOppSum != SK_MinS32); + return &span; +} + +SkOpSpan* SkOpSegment::verifyOneWindingU(const char* funName, int tIndex) { + SkOpSpan& span = fTs[tIndex]; + if (span.fDone) { + return NULL; + } +#if DEBUG_MARK_DONE + debugShowNewWinding(funName, span, span.fWindSum); +#endif + SkASSERT(span.fWindSum != SK_MinS32); + return &span; +} + +// note that just because a span has one end that is unsortable, that's +// not enough to mark it done. The other end may be sortable, allowing the +// span to be added. +// FIXME: if abs(start - end) > 1, mark intermediates as unsortable on both ends +void SkOpSegment::markUnsortable(int start, int end) { + SkOpSpan* span = &fTs[start]; + if (start < end) { +#if DEBUG_UNSORTABLE + debugShowNewWinding(__FUNCTION__, *span, 0); +#endif + span->fUnsortableStart = true; + } else { + --span; +#if DEBUG_UNSORTABLE + debugShowNewWinding(__FUNCTION__, *span, 0); +#endif + span->fUnsortableEnd = true; + } + if (!span->fUnsortableStart || !span->fUnsortableEnd || span->fDone) { + return; + } + span->fDone = true; + fDoneSpans++; +} + +void SkOpSegment::markWinding(int index, int winding) { +// SkASSERT(!done()); + SkASSERT(winding); + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneWinding(__FUNCTION__, lesser, winding); + } + do { + markOneWinding(__FUNCTION__, index, winding); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); +} + +void SkOpSegment::markWinding(int index, int winding, int oppWinding) { +// SkASSERT(!done()); + SkASSERT(winding || oppWinding); + double referenceT = fTs[index].fT; + int lesser = index; + while (--lesser >= 0 && precisely_negative(referenceT - fTs[lesser].fT)) { + markOneWinding(__FUNCTION__, lesser, winding, oppWinding); + } + do { + markOneWinding(__FUNCTION__, index, winding, oppWinding); + } while (++index < fTs.count() && precisely_negative(fTs[index].fT - referenceT)); +} + +void SkOpSegment::matchWindingValue(int tIndex, double t, bool borrowWind) { + int nextDoorWind = SK_MaxS32; + int nextOppWind = SK_MaxS32; + if (tIndex > 0) { + const SkOpSpan& below = fTs[tIndex - 1]; + if (approximately_negative(t - below.fT)) { + nextDoorWind = below.fWindValue; + nextOppWind = below.fOppValue; + } + } + if (nextDoorWind == SK_MaxS32 && tIndex + 1 < fTs.count()) { + const SkOpSpan& above = fTs[tIndex + 1]; + if (approximately_negative(above.fT - t)) { + nextDoorWind = above.fWindValue; + nextOppWind = above.fOppValue; + } + } + if (nextDoorWind == SK_MaxS32 && borrowWind && tIndex > 0 && t < 1) { + const SkOpSpan& below = fTs[tIndex - 1]; + nextDoorWind = below.fWindValue; + nextOppWind = below.fOppValue; + } + if (nextDoorWind != SK_MaxS32) { + SkOpSpan& newSpan = fTs[tIndex]; + newSpan.fWindValue = nextDoorWind; + newSpan.fOppValue = nextOppWind; + if (!nextDoorWind && !nextOppWind && !newSpan.fDone) { + newSpan.fDone = true; + ++fDoneSpans; + } + } +} + +// return span if when chasing, two or more radiating spans are not done +// OPTIMIZATION: ? multiple spans is detected when there is only one valid +// candidate and the remaining spans have windValue == 0 (canceled by +// coincidence). The coincident edges could either be removed altogether, +// or this code could be more complicated in detecting this case. Worth it? +bool SkOpSegment::multipleSpans(int end) const { + return end > 0 && end < fTs.count() - 1; +} + +bool SkOpSegment::nextCandidate(int* start, int* end) const { + while (fTs[*end].fDone) { + if (fTs[*end].fT == 1) { + return false; + } + ++(*end); + } + *start = *end; + *end = nextExactSpan(*start, 1); + return true; +} + +SkOpSegment* SkOpSegment::nextChase(int* index, const int step, int* min, SkOpSpan** last) { + int end = nextExactSpan(*index, step); + SkASSERT(end >= 0); + if (multipleSpans(end)) { + *last = &fTs[end]; + return NULL; + } + const SkOpSpan& endSpan = fTs[end]; + SkOpSegment* other = endSpan.fOther; + *index = endSpan.fOtherIndex; + SkASSERT(index >= 0); + int otherEnd = other->nextExactSpan(*index, step); + SkASSERT(otherEnd >= 0); + *min = SkMin32(*index, otherEnd); + return other; +} + +// This has callers for two different situations: one establishes the end +// of the current span, and one establishes the beginning of the next span +// (thus the name). When this is looking for the end of the current span, +// coincidence is found when the beginning Ts contain -step and the end +// contains step. When it is looking for the beginning of the next, the +// first Ts found can be ignored and the last Ts should contain -step. +// OPTIMIZATION: probably should split into two functions +int SkOpSegment::nextSpan(int from, int step) const { + const SkOpSpan& fromSpan = fTs[from]; + int count = fTs.count(); + int to = from; + while (step > 0 ? ++to < count : --to >= 0) { + const SkOpSpan& span = fTs[to]; + if (approximately_zero(span.fT - fromSpan.fT)) { + continue; + } + return to; + } + return -1; +} + +// FIXME +// this returns at any difference in T, vs. a preset minimum. It may be +// that all callers to nextSpan should use this instead. +// OPTIMIZATION splitting this into separate loops for up/down steps +// would allow using precisely_negative instead of precisely_zero +int SkOpSegment::nextExactSpan(int from, int step) const { + const SkOpSpan& fromSpan = fTs[from]; + int count = fTs.count(); + int to = from; + while (step > 0 ? ++to < count : --to >= 0) { + const SkOpSpan& span = fTs[to]; + if (precisely_zero(span.fT - fromSpan.fT)) { + continue; + } + return to; + } + return -1; +} + +void SkOpSegment::setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding, + int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding) { + int deltaSum = spanSign(index, endIndex); + int oppDeltaSum = oppSign(index, endIndex); + if (operand()) { + *maxWinding = *sumSuWinding; + *sumWinding = *sumSuWinding -= deltaSum; + *oppMaxWinding = *sumMiWinding; + *oppSumWinding = *sumMiWinding -= oppDeltaSum; + } else { + *maxWinding = *sumMiWinding; + *sumWinding = *sumMiWinding -= deltaSum; + *oppMaxWinding = *sumSuWinding; + *oppSumWinding = *sumSuWinding -= oppDeltaSum; + } +} + +// This marks all spans unsortable so that this info is available for early +// exclusion in find top and others. This could be optimized to only mark +// adjacent spans that unsortable. However, this makes it difficult to later +// determine starting points for edge detection in find top and the like. +bool SkOpSegment::SortAngles(const SkTDArray<SkOpAngle>& angles, + SkTDArray<SkOpAngle*>* angleList) { + bool sortable = true; + int angleCount = angles.count(); + int angleIndex; + angleList->setReserve(angleCount); + for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) { + const SkOpAngle& angle = angles[angleIndex]; + *angleList->append() = const_cast<SkOpAngle*>(&angle); + sortable &= !angle.unsortable(); + } + if (sortable) { + QSort<SkOpAngle>(angleList->begin(), angleList->end() - 1); + for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) { + if (angles[angleIndex].unsortable()) { + sortable = false; + break; + } + } + } + if (!sortable) { + for (angleIndex = 0; angleIndex < angleCount; ++angleIndex) { + const SkOpAngle& angle = angles[angleIndex]; + angle.segment()->markUnsortable(angle.start(), angle.end()); + } + } + return sortable; +} + +void SkOpSegment::subDivide(int start, int end, SkPoint edge[4]) const { + edge[0] = fTs[start].fPt; + edge[fVerb] = fTs[end].fPt; + if (fVerb == SkPath::kQuad_Verb || fVerb == SkPath::kCubic_Verb) { + SkDPoint sub[2] = {{ edge[0].fX, edge[0].fY}, {edge[fVerb].fX, edge[fVerb].fY }}; + if (fVerb == SkPath::kQuad_Verb) { + edge[1] = SkDQuad::SubDivide(fPts, sub[0], sub[1], fTs[start].fT, + fTs[end].fT).asSkPoint(); + } else { + SkDCubic::SubDivide(fPts, sub[0], sub[1], fTs[start].fT, fTs[end].fT, sub); + edge[1] = sub[0].asSkPoint(); + edge[2] = sub[1].asSkPoint(); + } + } +} + +void SkOpSegment::subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const { + SkPoint edge[4]; + subDivide(start, end, edge); + (bounds->*SetCurveBounds[fVerb])(edge); +} + +bool SkOpSegment::tiny(const SkOpAngle* angle) const { + int start = angle->start(); + int end = angle->end(); + const SkOpSpan& mSpan = fTs[SkMin32(start, end)]; + return mSpan.fTiny; +} + +void SkOpSegment::TrackOutside(SkTDArray<double>* outsideTs, double end, double start) { + int outCount = outsideTs->count(); + if (outCount == 0 || !approximately_negative(end - (*outsideTs)[outCount - 2])) { + *outsideTs->append() = end; + *outsideTs->append() = start; + } +} + +void SkOpSegment::undoneSpan(int* start, int* end) { + size_t tCount = fTs.count(); + size_t index; + for (index = 0; index < tCount; ++index) { + if (!fTs[index].fDone) { + break; + } + } + SkASSERT(index < tCount - 1); + *start = index; + double startT = fTs[index].fT; + while (approximately_negative(fTs[++index].fT - startT)) + SkASSERT(index < tCount); + SkASSERT(index < tCount); + *end = index; +} + +int SkOpSegment::updateOppWinding(int index, int endIndex) const { + int lesser = SkMin32(index, endIndex); + int oppWinding = oppSum(lesser); + int oppSpanWinding = oppSign(index, endIndex); + if (oppSpanWinding && UseInnerWinding(oppWinding - oppSpanWinding, oppWinding) + && oppWinding != SK_MaxS32) { + oppWinding -= oppSpanWinding; + } + return oppWinding; +} + +int SkOpSegment::updateOppWinding(const SkOpAngle* angle) const { + int startIndex = angle->start(); + int endIndex = angle->end(); + return updateOppWinding(endIndex, startIndex); +} + +int SkOpSegment::updateOppWindingReverse(const SkOpAngle* angle) const { + int startIndex = angle->start(); + int endIndex = angle->end(); + return updateOppWinding(startIndex, endIndex); +} + +int SkOpSegment::updateWinding(int index, int endIndex) const { + int lesser = SkMin32(index, endIndex); + int winding = windSum(lesser); + int spanWinding = spanSign(index, endIndex); + if (winding && UseInnerWinding(winding - spanWinding, winding) && winding != SK_MaxS32) { + winding -= spanWinding; + } + return winding; +} + +int SkOpSegment::updateWinding(const SkOpAngle* angle) const { + int startIndex = angle->start(); + int endIndex = angle->end(); + return updateWinding(endIndex, startIndex); +} + +int SkOpSegment::updateWindingReverse(const SkOpAngle* angle) const { + int startIndex = angle->start(); + int endIndex = angle->end(); + return updateWinding(startIndex, endIndex); +} + +int SkOpSegment::windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const { + if (approximately_zero(tHit - t(tIndex))) { // if we hit the end of a span, disregard + return SK_MinS32; + } + int winding = crossOpp ? oppSum(tIndex) : windSum(tIndex); + SkASSERT(winding != SK_MinS32); + int windVal = crossOpp ? oppValue(tIndex) : windValue(tIndex); +#if DEBUG_WINDING_AT_T + SkDebugf("%s oldWinding=%d windValue=%d", __FUNCTION__, winding, windVal); +#endif + // see if a + change in T results in a +/- change in X (compute x'(T)) + *dx = (*CurveSlopeAtT[fVerb])(fPts, tHit).fX; + if (fVerb > SkPath::kLine_Verb && approximately_zero(*dx)) { + *dx = fPts[2].fX - fPts[1].fX - *dx; + } + if (*dx == 0) { +#if DEBUG_WINDING_AT_T + SkDebugf(" dx=0 winding=SK_MinS32\n"); +#endif + return SK_MinS32; + } + if (winding * *dx > 0) { // if same signs, result is negative + winding += *dx > 0 ? -windVal : windVal; + } +#if DEBUG_WINDING_AT_T + SkDebugf(" dx=%c winding=%d\n", *dx > 0 ? '+' : '-', winding); +#endif + return winding; +} + +int SkOpSegment::windSum(const SkOpAngle* angle) const { + int start = angle->start(); + int end = angle->end(); + int index = SkMin32(start, end); + return windSum(index); +} + +int SkOpSegment::windValue(const SkOpAngle* angle) const { + int start = angle->start(); + int end = angle->end(); + int index = SkMin32(start, end); + return windValue(index); +} + +int SkOpSegment::windValueAt(double t) const { + int count = fTs.count(); + for (int index = 0; index < count; ++index) { + if (fTs[index].fT == t) { + return fTs[index].fWindValue; + } + } + SkASSERT(0); + return 0; +} + +void SkOpSegment::zeroSpan(SkOpSpan* span) { + SkASSERT(span->fWindValue > 0 || span->fOppValue > 0); + span->fWindValue = 0; + span->fOppValue = 0; + SkASSERT(!span->fDone); + span->fDone = true; + ++fDoneSpans; +} + +#if DEBUG_SWAP_TOP +bool SkOpSegment::controlsContainedByEnds(int tStart, int tEnd) const { + if (fVerb != SkPath::kCubic_Verb) { + return false; + } + SkDCubic dst = SkDCubic::SubDivide(fPts, fTs[tStart].fT, fTs[tEnd].fT); + return dst.controlsContainedByEnds(); +} +#endif + +#if DEBUG_CONCIDENT +// SkASSERT if pair has not already been added +void SkOpSegment::debugAddTPair(double t, const SkOpSegment& other, double otherT) const { + for (int i = 0; i < fTs.count(); ++i) { + if (fTs[i].fT == t && fTs[i].fOther == &other && fTs[i].fOtherT == otherT) { + return; + } + } + SkASSERT(0); +} +#endif + +#if DEBUG_CONCIDENT +void SkOpSegment::debugShowTs() const { + SkDebugf("%s id=%d", __FUNCTION__, fID); + int lastWind = -1; + int lastOpp = -1; + double lastT = -1; + int i; + for (i = 0; i < fTs.count(); ++i) { + bool change = lastT != fTs[i].fT || lastWind != fTs[i].fWindValue + || lastOpp != fTs[i].fOppValue; + if (change && lastWind >= 0) { + SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]", + lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp); + } + if (change) { + SkDebugf(" [o=%d", fTs[i].fOther->fID); + lastWind = fTs[i].fWindValue; + lastOpp = fTs[i].fOppValue; + lastT = fTs[i].fT; + } else { + SkDebugf(",%d", fTs[i].fOther->fID); + } + } + if (i <= 0) { + return; + } + SkDebugf(" t=%1.3g %1.9g,%1.9g w=%d o=%d]", + lastT, xyAtT(i - 1).fX, xyAtT(i - 1).fY, lastWind, lastOpp); + if (fOperand) { + SkDebugf(" operand"); + } + if (done()) { + SkDebugf(" done"); + } + SkDebugf("\n"); +} +#endif + +#if DEBUG_ACTIVE_SPANS +void SkOpSegment::debugShowActiveSpans() const { + if (done()) { + return; + } +#if DEBUG_ACTIVE_SPANS_SHORT_FORM + int lastId = -1; + double lastT = -1; +#endif + for (int i = 0; i < fTs.count(); ++i) { + SkASSERT(&fTs[i] == &fTs[i].fOther->fTs[fTs[i].fOtherIndex].fOther-> + fTs[fTs[i].fOther->fTs[fTs[i].fOtherIndex].fOtherIndex]); + if (fTs[i].fDone) { + continue; + } +#if DEBUG_ACTIVE_SPANS_SHORT_FORM + if (lastId == fID && lastT == fTs[i].fT) { + continue; + } + lastId = fID; + lastT = fTs[i].fT; +#endif + SkDebugf("%s id=%d", __FUNCTION__, fID); + SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); + for (int vIndex = 1; vIndex <= fVerb; ++vIndex) { + SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); + } + const SkOpSpan* span = &fTs[i]; + SkDebugf(") t=%1.9g (%1.9g,%1.9g)", span->fT, xAtT(span), yAtT(span)); + int iEnd = i + 1; + while (fTs[iEnd].fT < 1 && approximately_equal(fTs[i].fT, fTs[iEnd].fT)) { + ++iEnd; + } + SkDebugf(" tEnd=%1.9g", fTs[iEnd].fT); + const SkOpSegment* other = fTs[i].fOther; + SkDebugf(" other=%d otherT=%1.9g otherIndex=%d windSum=", + other->fID, fTs[i].fOtherT, fTs[i].fOtherIndex); + if (fTs[i].fWindSum == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", fTs[i].fWindSum); + } + SkDebugf(" windValue=%d oppValue=%d\n", fTs[i].fWindValue, fTs[i].fOppValue); + } +} +#endif + + +#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE +void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding) { + const SkPoint& pt = xyAtT(&span); + SkDebugf("%s id=%d", fun, fID); + SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); + for (int vIndex = 1; vIndex <= fVerb; ++vIndex) { + SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); + } + SkASSERT(&span == &span.fOther->fTs[span.fOtherIndex].fOther-> + fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]); + SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=%d windSum=", + span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY, + (&span)[1].fT, winding); + if (span.fWindSum == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span.fWindSum); + } + SkDebugf(" windValue=%d\n", span.fWindValue); +} + +void SkOpSegment::debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, + int oppWinding) { + const SkPoint& pt = xyAtT(&span); + SkDebugf("%s id=%d", fun, fID); + SkDebugf(" (%1.9g,%1.9g", fPts[0].fX, fPts[0].fY); + for (int vIndex = 1; vIndex <= fVerb; ++vIndex) { + SkDebugf(" %1.9g,%1.9g", fPts[vIndex].fX, fPts[vIndex].fY); + } + SkASSERT(&span == &span.fOther->fTs[span.fOtherIndex].fOther-> + fTs[span.fOther->fTs[span.fOtherIndex].fOtherIndex]); + SkDebugf(") t=%1.9g [%d] (%1.9g,%1.9g) tEnd=%1.9g newWindSum=%d newOppSum=%d oppSum=", + span.fT, span.fOther->fTs[span.fOtherIndex].fOtherIndex, pt.fX, pt.fY, + (&span)[1].fT, winding, oppWinding); + if (span.fOppSum == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span.fOppSum); + } + SkDebugf(" windSum="); + if (span.fWindSum == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", span.fWindSum); + } + SkDebugf(" windValue=%d\n", span.fWindValue); +} +#endif + +#if DEBUG_SORT || DEBUG_SWAP_TOP +void SkOpSegment::debugShowSort(const char* fun, const SkTDArray<SkOpAngle*>& angles, int first, + const int contourWinding, const int oppContourWinding) const { + if (--gDebugSortCount < 0) { + return; + } + SkASSERT(angles[first]->segment() == this); + SkASSERT(angles.count() > 1); + int lastSum = contourWinding; + int oppLastSum = oppContourWinding; + const SkOpAngle* firstAngle = angles[first]; + int windSum = lastSum - spanSign(firstAngle); + int oppoSign = oppSign(firstAngle); + int oppWindSum = oppLastSum - oppoSign; + #define WIND_AS_STRING(x) char x##Str[12]; if (!valid_wind(x)) strcpy(x##Str, "?"); \ + else snprintf(x##Str, sizeof(x##Str), "%d", x) + WIND_AS_STRING(contourWinding); + WIND_AS_STRING(oppContourWinding); + SkDebugf("%s %s contourWinding=%s oppContourWinding=%s sign=%d\n", fun, __FUNCTION__, + contourWindingStr, oppContourWindingStr, spanSign(angles[first])); + int index = first; + bool firstTime = true; + do { + const SkOpAngle& angle = *angles[index]; + const SkOpSegment& segment = *angle.segment(); + int start = angle.start(); + int end = angle.end(); + const SkOpSpan& sSpan = segment.fTs[start]; + const SkOpSpan& eSpan = segment.fTs[end]; + const SkOpSpan& mSpan = segment.fTs[SkMin32(start, end)]; + bool opp = segment.fOperand ^ fOperand; + if (!firstTime) { + oppoSign = segment.oppSign(&angle); + if (opp) { + oppLastSum = oppWindSum; + oppWindSum -= segment.spanSign(&angle); + if (oppoSign) { + lastSum = windSum; + windSum -= oppoSign; + } + } else { + lastSum = windSum; + windSum -= segment.spanSign(&angle); + if (oppoSign) { + oppLastSum = oppWindSum; + oppWindSum -= oppoSign; + } + } + } + SkDebugf("%s [%d] %s", __FUNCTION__, index, + angle.unsortable() ? "*** UNSORTABLE *** " : ""); + #if COMPACT_DEBUG_SORT + SkDebugf("id=%d %s start=%d (%1.9g,%,1.9g) end=%d (%1.9g,%,1.9g)", + segment.fID, kLVerbStr[segment.fVerb], + start, segment.xAtT(&sSpan), segment.yAtT(&sSpan), end, + segment.xAtT(&eSpan), segment.yAtT(&eSpan)); + #else + switch (segment.fVerb) { + case SkPath::kLine_Verb: + SkDebugf(LINE_DEBUG_STR, LINE_DEBUG_DATA(segment.fPts)); + break; + case SkPath::kQuad_Verb: + SkDebugf(QUAD_DEBUG_STR, QUAD_DEBUG_DATA(segment.fPts)); + break; + case SkPath::kCubic_Verb: + SkDebugf(CUBIC_DEBUG_STR, CUBIC_DEBUG_DATA(segment.fPts)); + break; + default: + SkASSERT(0); + } + SkDebugf(" tStart=%1.9g tEnd=%1.9g", sSpan.fT, eSpan.fT); + #endif + SkDebugf(" sign=%d windValue=%d windSum=", angle.sign(), mSpan.fWindValue); + #ifdef SK_DEBUG + winding_printf(mSpan.fWindSum); + #endif + int last, wind; + if (opp) { + last = oppLastSum; + wind = oppWindSum; + } else { + last = lastSum; + wind = windSum; + } + bool useInner = valid_wind(last) && valid_wind(wind) && UseInnerWinding(last, wind); + WIND_AS_STRING(last); + WIND_AS_STRING(wind); + WIND_AS_STRING(lastSum); + WIND_AS_STRING(oppLastSum); + WIND_AS_STRING(windSum); + WIND_AS_STRING(oppWindSum); + #undef WIND_AS_STRING + if (!oppoSign) { + SkDebugf(" %s->%s (max=%s)", lastStr, windStr, useInner ? windStr : lastStr); + } else { + SkDebugf(" %s->%s (%s->%s)", lastStr, windStr, opp ? lastSumStr : oppLastSumStr, + opp ? windSumStr : oppWindSumStr); + } + SkDebugf(" done=%d tiny=%d opp=%d\n", mSpan.fDone, mSpan.fTiny, opp); +#if false && DEBUG_ANGLE + angle.debugShow(segment.xyAtT(&sSpan)); +#endif + ++index; + if (index == angles.count()) { + index = 0; + } + if (firstTime) { + firstTime = false; + } + } while (index != first); +} + +void SkOpSegment::debugShowSort(const char* fun, const SkTDArray<SkOpAngle*>& angles, int first) { + const SkOpAngle* firstAngle = angles[first]; + const SkOpSegment* segment = firstAngle->segment(); + int winding = segment->updateWinding(firstAngle); + int oppWinding = segment->updateOppWinding(firstAngle); + debugShowSort(fun, angles, first, winding, oppWinding); +} + +#endif + +#if DEBUG_SHOW_WINDING +int SkOpSegment::debugShowWindingValues(int slotCount, int ofInterest) const { + if (!(1 << fID & ofInterest)) { + return 0; + } + int sum = 0; + SkTDArray<char> slots; + slots.setCount(slotCount * 2); + memset(slots.begin(), ' ', slotCount * 2); + for (int i = 0; i < fTs.count(); ++i) { + // if (!(1 << fTs[i].fOther->fID & ofInterest)) { + // continue; + // } + sum += fTs[i].fWindValue; + slots[fTs[i].fOther->fID - 1] = as_digit(fTs[i].fWindValue); + sum += fTs[i].fOppValue; + slots[slotCount + fTs[i].fOther->fID - 1] = as_digit(fTs[i].fOppValue); + } + SkDebugf("%s id=%2d %.*s | %.*s\n", __FUNCTION__, fID, slotCount, slots.begin(), slotCount, + slots.begin() + slotCount); + return sum; +} +#endif diff --git a/src/pathops/SkOpSegment.h b/src/pathops/SkOpSegment.h new file mode 100644 index 0000000000..1e9eb4b09a --- /dev/null +++ b/src/pathops/SkOpSegment.h @@ -0,0 +1,392 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpSegment_DEFINE +#define SkOpSegment_DEFINE + +#include "SkOpAngle.h" +#include "SkPathOpsBounds.h" +#include "SkPathOpsCurve.h" +#include "SkTDArray.h" + +class SkPathWriter; + +class SkOpSegment { +public: + SkOpSegment() { +#if DEBUG_DUMP + fID = ++gSegmentID; +#endif + } + + bool operator<(const SkOpSegment& rh) const { + return fBounds.fTop < rh.fBounds.fTop; + } + + const SkPathOpsBounds& bounds() const { + return fBounds; + } + + // OPTIMIZE + // when the edges are initially walked, they don't automatically get the prior and next + // edges assigned to positions t=0 and t=1. Doing that would remove the need for this check, + // and would additionally remove the need for similar checks in condition edges. It would + // also allow intersection code to assume end of segment intersections (maybe?) + bool complete() const { + int count = fTs.count(); + return count > 1 && fTs[0].fT == 0 && fTs[--count].fT == 1; + } + + bool done() const { + SkASSERT(fDoneSpans <= fTs.count()); + return fDoneSpans == fTs.count(); + } + + bool done(int min) const { + return fTs[min].fDone; + } + + bool done(const SkOpAngle* angle) const { + return done(SkMin32(angle->start(), angle->end())); + } + + SkVector dxdy(int index) const { + return (*CurveSlopeAtT[fVerb])(fPts, fTs[index].fT); + } + + SkScalar dy(int index) const { + return dxdy(index).fY; + } + + bool intersected() const { + return fTs.count() > 0; + } + + bool isCanceled(int tIndex) const { + return fTs[tIndex].fWindValue == 0 && fTs[tIndex].fOppValue == 0; + } + + bool isConnected(int startIndex, int endIndex) const { + return fTs[startIndex].fWindSum != SK_MinS32 || fTs[endIndex].fWindSum != SK_MinS32; + } + + bool isHorizontal() const { + return fBounds.fTop == fBounds.fBottom; + } + + bool isVertical() const { + return fBounds.fLeft == fBounds.fRight; + } + + bool isVertical(int start, int end) const { + return (*CurveIsVertical[fVerb])(fPts, start, end); + } + + bool operand() const { + return fOperand; + } + + int oppSign(const SkOpAngle* angle) const { + SkASSERT(angle->segment() == this); + return oppSign(angle->start(), angle->end()); + } + + int oppSign(int startIndex, int endIndex) const { + int result = startIndex < endIndex ? -fTs[startIndex].fOppValue : fTs[endIndex].fOppValue; +#if DEBUG_WIND_BUMP + SkDebugf("%s oppSign=%d\n", __FUNCTION__, result); +#endif + return result; + } + + int oppSum(int tIndex) const { + return fTs[tIndex].fOppSum; + } + + int oppSum(const SkOpAngle* angle) const { + int lesser = SkMin32(angle->start(), angle->end()); + return fTs[lesser].fOppSum; + } + + int oppValue(int tIndex) const { + return fTs[tIndex].fOppValue; + } + + int oppValue(const SkOpAngle* angle) const { + int lesser = SkMin32(angle->start(), angle->end()); + return fTs[lesser].fOppValue; + } + + const SkPoint* pts() const { + return fPts; + } + + void reset() { + init(NULL, (SkPath::Verb) -1, false, false); + fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax); + fTs.reset(); + } + + void setOppXor(bool isOppXor) { + fOppXor = isOppXor; + } + + void setSpanT(int index, double t) { + SkOpSpan& span = fTs[index]; + span.fT = t; + span.fOther->fTs[span.fOtherIndex].fOtherT = t; + } + + void setUpWinding(int index, int endIndex, int* maxWinding, int* sumWinding) { + int deltaSum = spanSign(index, endIndex); + *maxWinding = *sumWinding; + *sumWinding -= deltaSum; + } + + // OPTIMIZATION: mark as debugging only if used solely by tests + const SkOpSpan& span(int tIndex) const { + return fTs[tIndex]; + } + + int spanSign(const SkOpAngle* angle) const { + SkASSERT(angle->segment() == this); + return spanSign(angle->start(), angle->end()); + } + + int spanSign(int startIndex, int endIndex) const { + int result = startIndex < endIndex ? -fTs[startIndex].fWindValue : fTs[endIndex].fWindValue; +#if DEBUG_WIND_BUMP + SkDebugf("%s spanSign=%d\n", __FUNCTION__, result); +#endif + return result; + } + + // OPTIMIZATION: mark as debugging only if used solely by tests + double t(int tIndex) const { + return fTs[tIndex].fT; + } + + double tAtMid(int start, int end, double mid) const { + return fTs[start].fT * (1 - mid) + fTs[end].fT * mid; + } + + bool unsortable(int index) const { + return fTs[index].fUnsortableStart || fTs[index].fUnsortableEnd; + } + + void updatePts(const SkPoint pts[]) { + fPts = pts; + } + + SkPath::Verb verb() const { + return fVerb; + } + + int windSum(int tIndex) const { + return fTs[tIndex].fWindSum; + } + + int windValue(int tIndex) const { + return fTs[tIndex].fWindValue; + } + + SkScalar xAtT(int index) const { + return xAtT(&fTs[index]); + } + + SkScalar xAtT(const SkOpSpan* span) const { + return xyAtT(span).fX; + } + + const SkPoint& xyAtT(const SkOpSpan* span) const { + return span->fPt; + } + + // used only by right angle winding finding + SkPoint xyAtT(double mid) const { + return (*CurvePointAtT[fVerb])(fPts, mid); + } + + const SkPoint& xyAtT(int index) const { + return xyAtT(&fTs[index]); + } + + SkScalar yAtT(int index) const { + return yAtT(&fTs[index]); + } + + SkScalar yAtT(const SkOpSpan* span) const { + return xyAtT(span).fY; + } + + bool activeAngle(int index, int* done, SkTDArray<SkOpAngle>* angles); + SkPoint activeLeftTop(bool onlySortable, int* firstT) const; + bool activeOp(int index, int endIndex, int xorMiMask, int xorSuMask, SkPathOp op); + bool activeOp(int xorMiMask, int xorSuMask, int index, int endIndex, SkPathOp op, + int* sumMiWinding, int* sumSuWinding, int* maxWinding, int* sumWinding, + int* oppMaxWinding, int* oppSumWinding); + bool activeWinding(int index, int endIndex); + bool activeWinding(int index, int endIndex, int* maxWinding, int* sumWinding); + void addCubic(const SkPoint pts[4], bool operand, bool evenOdd); + void addCurveTo(int start, int end, SkPathWriter* path, bool active) const; + void addLine(const SkPoint pts[2], bool operand, bool evenOdd); + void addOtherT(int index, double otherT, int otherIndex); + void addQuad(const SkPoint pts[3], bool operand, bool evenOdd); + int addSelfT(SkOpSegment* other, const SkPoint& pt, double newT); + int addT(SkOpSegment* other, const SkPoint& pt, double newT); + void addTCancel(double startT, double endT, SkOpSegment* other, double oStartT, double oEndT); + void addTCoincident(double startT, double endT, SkOpSegment* other, double oStartT, + double oEndT); + void addTPair(double t, SkOpSegment* other, double otherT, bool borrowWind, const SkPoint& pt); + int addUnsortableT(SkOpSegment* other, bool start, const SkPoint& pt, double newT); + bool betweenTs(int lesser, double testT, int greater) const; + int computeSum(int startIndex, int endIndex, bool binary); + int crossedSpanY(const SkPoint& basePt, SkScalar* bestY, double* hitT, bool* hitSomething, + double mid, bool opp, bool current) const; + SkOpSegment* findNextOp(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd, + bool* unsortable, SkPathOp op, const int xorMiMask, + const int xorSuMask); + SkOpSegment* findNextWinding(SkTDArray<SkOpSpan*>* chase, int* nextStart, int* nextEnd, + bool* unsortable); + SkOpSegment* findNextXor(int* nextStart, int* nextEnd, bool* unsortable); + void findTooCloseToCall(); + SkOpSegment* findTop(int* tIndex, int* endIndex, bool* unsortable, bool onlySortable); + void fixOtherTIndex(); + void initWinding(int start, int end); + void initWinding(int start, int end, double tHit, int winding, SkScalar hitDx, int oppWind, + SkScalar hitOppDx); + bool isLinear(int start, int end) const; + bool isMissing(double startT) const; + bool isSimple(int end) const; + SkOpSpan* markAndChaseDoneBinary(int index, int endIndex); + SkOpSpan* markAndChaseDoneUnary(int index, int endIndex); + SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, int winding, int oppWinding); + SkOpSpan* markAngle(int maxWinding, int sumWinding, int oppMaxWinding, int oppSumWinding, + bool activeAngle, const SkOpAngle* angle); + void markDone(int index, int winding); + void markDoneBinary(int index); + void markDoneUnary(int index); + SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding); + SkOpSpan* markOneWinding(const char* funName, int tIndex, int winding, int oppWinding); + void markWinding(int index, int winding); + void markWinding(int index, int winding, int oppWinding); + bool nextCandidate(int* start, int* end) const; + int nextExactSpan(int from, int step) const; + int nextSpan(int from, int step) const; + void setUpWindings(int index, int endIndex, int* sumMiWinding, int* sumSuWinding, + int* maxWinding, int* sumWinding, int* oppMaxWinding, int* oppSumWinding); + static bool SortAngles(const SkTDArray<SkOpAngle>& angles, SkTDArray<SkOpAngle*>* angleList); + void subDivide(int start, int end, SkPoint edge[4]) const; + void undoneSpan(int* start, int* end); + int updateOppWindingReverse(const SkOpAngle* angle) const; + int updateWindingReverse(const SkOpAngle* angle) const; + static bool UseInnerWinding(int outerWinding, int innerWinding); + int windingAtT(double tHit, int tIndex, bool crossOpp, SkScalar* dx) const; + int windSum(const SkOpAngle* angle) const; + int windValue(const SkOpAngle* angle) const; + +#if DEBUG_DUMP + int debugID() const { + return fID; + } +#endif +#if DEBUG_ACTIVE_SPANS + void debugShowActiveSpans() const; +#endif +#if DEBUG_SORT || DEBUG_SWAP_TOP + void debugShowSort(const char* fun, const SkTDArray<SkOpAngle*>& angles, int first, + const int contourWinding, const int oppContourWinding) const; + void debugShowSort(const char* fun, const SkTDArray<SkOpAngle*>& angles, int first); +#endif +#if DEBUG_CONCIDENT + void debugShowTs() const; +#endif +#if DEBUG_SHOW_WINDING + int debugShowWindingValues(int slotCount, int ofInterest) const; +#endif + +private: + bool activeAngleOther(int index, int* done, SkTDArray<SkOpAngle>* angles); + bool activeAngleInner(int index, int* done, SkTDArray<SkOpAngle>* angles); + void addAngle(SkTDArray<SkOpAngle>* angles, int start, int end) const; + void addCancelOutsides(double tStart, double oStart, SkOpSegment* other, double oEnd); + void addCoinOutsides(const SkTDArray<double>& outsideTs, SkOpSegment* other, double oEnd); + void addTwoAngles(int start, int end, SkTDArray<SkOpAngle>* angles) const; + int advanceCoincidentOther(const SkOpSpan* test, double oEndT, int oIndex); + int advanceCoincidentThis(const SkOpSpan* oTest, bool opp, int index); + void buildAngles(int index, SkTDArray<SkOpAngle>* angles, bool includeOpp) const; + void buildAnglesInner(int index, SkTDArray<SkOpAngle>* angles) const; + int bumpCoincidentThis(const SkOpSpan& oTest, bool opp, int index, + SkTDArray<double>* outsideTs); + int bumpCoincidentOther(const SkOpSpan& test, double oEndT, int& oIndex, + SkTDArray<double>* oOutsideTs); + bool bumpSpan(SkOpSpan* span, int windDelta, int oppDelta); + bool clockwise(int tStart, int tEnd) const; + void decrementSpan(SkOpSpan* span); + bool equalPoints(int greaterTIndex, int lesserTIndex); + int findStartingEdge(const SkTDArray<SkOpAngle*>& sorted, int start, int end); + void init(const SkPoint pts[], SkPath::Verb verb, bool operand, bool evenOdd); + void matchWindingValue(int tIndex, double t, bool borrowWind); + SkOpSpan* markAndChaseDone(int index, int endIndex, int winding); + SkOpSpan* markAndChaseDoneBinary(const SkOpAngle* angle, int winding, int oppWinding); + SkOpSpan* markAndChaseWinding(const SkOpAngle* angle, const int winding); + SkOpSpan* markAndChaseWinding(int index, int endIndex, int winding, int oppWinding); + SkOpSpan* markAngle(int maxWinding, int sumWinding, bool activeAngle, const SkOpAngle* angle); + void markDoneBinary(int index, int winding, int oppWinding); + SkOpSpan* markAndChaseDoneUnary(const SkOpAngle* angle, int winding); + void markOneDone(const char* funName, int tIndex, int winding); + void markOneDoneBinary(const char* funName, int tIndex); + void markOneDoneBinary(const char* funName, int tIndex, int winding, int oppWinding); + void markOneDoneUnary(const char* funName, int tIndex); + void markUnsortable(int start, int end); + bool monotonicInY(int tStart, int tEnd) const; + bool multipleSpans(int end) const; + SkOpSegment* nextChase(int* index, const int step, int* min, SkOpSpan** last); + bool serpentine(int tStart, int tEnd) const; + void subDivideBounds(int start, int end, SkPathOpsBounds* bounds) const; + bool tiny(const SkOpAngle* angle) const; + static void TrackOutside(SkTDArray<double>* outsideTs, double end, double start); + int updateOppWinding(int index, int endIndex) const; + int updateOppWinding(const SkOpAngle* angle) const; + int updateWinding(int index, int endIndex) const; + int updateWinding(const SkOpAngle* angle) const; + SkOpSpan* verifyOneWinding(const char* funName, int tIndex); + SkOpSpan* verifyOneWindingU(const char* funName, int tIndex); + int windValueAt(double t) const; + void zeroSpan(SkOpSpan* span); + +#if DEBUG_SWAP_TOP + bool controlsContainedByEnds(int tStart, int tEnd) const; +#endif +#if DEBUG_CONCIDENT + void debugAddTPair(double t, const SkOpSegment& other, double otherT) const; +#endif +#if DEBUG_MARK_DONE || DEBUG_UNSORTABLE + void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding); + void debugShowNewWinding(const char* fun, const SkOpSpan& span, int winding, int oppWinding); +#endif +#if DEBUG_WINDING + static char as_digit(int value) { + return value < 0 ? '?' : value <= 9 ? '0' + value : '+'; + } +#endif + + const SkPoint* fPts; + SkPathOpsBounds fBounds; + SkTDArray<SkOpSpan> fTs; // two or more (always includes t=0 t=1) + // OPTIMIZATION: could pack donespans, verb, operand, xor into 1 int-sized value + int fDoneSpans; // quick check that segment is finished + // OPTIMIZATION: force the following to be byte-sized + SkPath::Verb fVerb; + bool fOperand; + bool fXor; // set if original contour had even-odd fill + bool fOppXor; // set if opposite operand had even-odd fill +#if DEBUG_DUMP + int fID; +#endif +}; + +#endif diff --git a/src/pathops/SkOpSpan.h b/src/pathops/SkOpSpan.h new file mode 100644 index 0000000000..3666623fbe --- /dev/null +++ b/src/pathops/SkOpSpan.h @@ -0,0 +1,31 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpSpan_DEFINED +#define SkOpSpan_DEFINED + +#include "SkPoint.h" + +class SkOpSegment; + +struct SkOpSpan { + SkOpSegment* fOther; + SkPoint fPt; // computed when the curves are intersected + double fT; + double fOtherT; // value at fOther[fOtherIndex].fT + int fOtherIndex; // can't be used during intersection + int fWindSum; // accumulated from contours surrounding this one. + int fOppSum; // for binary operators: the opposite winding sum + int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident + int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here + bool fDone; // if set, this span to next higher T has been processed + bool fUnsortableStart; // set when start is part of an unsortable pair + bool fUnsortableEnd; // set when end is part of an unsortable pair + bool fTiny; // if set, span may still be considered once for edge following + bool fLoop; // set when a cubic loops back to this point +}; + +#endif diff --git a/src/pathops/SkPathOpsBounds.cpp b/src/pathops/SkPathOpsBounds.cpp new file mode 100644 index 0000000000..106cd30076 --- /dev/null +++ b/src/pathops/SkPathOpsBounds.cpp @@ -0,0 +1,40 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkPathOpsBounds.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" + +void SkPathOpsBounds::setCubicBounds(const SkPoint a[4]) { + SkDCubic cubic; + cubic.set(a); + SkDRect dRect; + dRect.setBounds(cubic); + set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), + SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); +} + +void SkPathOpsBounds::setLineBounds(const SkPoint a[2]) { + setPointBounds(a[0]); + add(a[1]); +} + +void SkPathOpsBounds::setQuadBounds(const SkPoint a[3]) { + SkDQuad quad; + quad.set(a); + SkDRect dRect; + dRect.setBounds(quad); + set(SkDoubleToScalar(dRect.fLeft), SkDoubleToScalar(dRect.fTop), + SkDoubleToScalar(dRect.fRight), SkDoubleToScalar(dRect.fBottom)); +} + +void (SkPathOpsBounds::*SetCurveBounds[])(const SkPoint[]) = { + NULL, + &SkPathOpsBounds::setLineBounds, + &SkPathOpsBounds::setQuadBounds, + &SkPathOpsBounds::setCubicBounds +}; diff --git a/src/pathops/SkPathOpsBounds.h b/src/pathops/SkPathOpsBounds.h new file mode 100644 index 0000000000..8102032813 --- /dev/null +++ b/src/pathops/SkPathOpsBounds.h @@ -0,0 +1,61 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpBounds_DEFINED +#define SkPathOpBounds_DEFINED + +#include "SkPathOpsRect.h" +#include "SkRect.h" + +// SkPathOpsBounds, unlike SkRect, does not consider a line to be empty. +struct SkPathOpsBounds : public SkRect { + static bool Intersects(const SkPathOpsBounds& a, const SkPathOpsBounds& b) { + return a.fLeft <= b.fRight && b.fLeft <= a.fRight && + a.fTop <= b.fBottom && b.fTop <= a.fBottom; + } + + // FIXME: add() is generically useful and could be added directly to SkRect + void add(SkScalar left, SkScalar top, SkScalar right, SkScalar bottom) { + if (left < fLeft) fLeft = left; + if (top < fTop) fTop = top; + if (right > fRight) fRight = right; + if (bottom > fBottom) fBottom = bottom; + } + + void add(const SkPathOpsBounds& toAdd) { + add(toAdd.fLeft, toAdd.fTop, toAdd.fRight, toAdd.fBottom); + } + + void add(const SkPoint& pt) { + if (pt.fX < fLeft) fLeft = pt.fX; + if (pt.fY < fTop) fTop = pt.fY; + if (pt.fX > fRight) fRight = pt.fX; + if (pt.fY > fBottom) fBottom = pt.fY; + } + + // unlike isEmpty(), this permits lines, but not points + // FIXME: unused for now + bool isReallyEmpty() const { + // use !<= instead of > to detect NaN values + return !(fLeft <= fRight) || !(fTop <= fBottom) + || (fLeft == fRight && fTop == fBottom); + } + + void setCubicBounds(const SkPoint a[4]); + void setLineBounds(const SkPoint a[2]); + void setQuadBounds(const SkPoint a[3]); + + void setPointBounds(const SkPoint& pt) { + fLeft = fRight = pt.fX; + fTop = fBottom = pt.fY; + } + + typedef SkRect INHERITED; +}; + +extern void (SkPathOpsBounds::*SetCurveBounds[])(const SkPoint[]); + +#endif diff --git a/src/pathops/SkPathOpsCommon.cpp b/src/pathops/SkPathOpsCommon.cpp new file mode 100644 index 0000000000..0a65bc9256 --- /dev/null +++ b/src/pathops/SkPathOpsCommon.cpp @@ -0,0 +1,578 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkOpEdgeBuilder.h" +#include "SkPathOpsCommon.h" +#include "SkPathWriter.h" +#include "TSearch.h" + +static int contourRangeCheckY(const SkTDArray<SkOpContour*>& contourList, SkOpSegment** currentPtr, + int* indexPtr, int* endIndexPtr, double* bestHit, SkScalar* bestDx, + bool* tryAgain, double* midPtr, bool opp) { + const int index = *indexPtr; + const int endIndex = *endIndexPtr; + const double mid = *midPtr; + const SkOpSegment* current = *currentPtr; + double tAtMid = current->tAtMid(index, endIndex, mid); + SkPoint basePt = current->xyAtT(tAtMid); + int contourCount = contourList.count(); + SkScalar bestY = SK_ScalarMin; + SkOpSegment* bestSeg = NULL; + int bestTIndex; + bool bestOpp; + bool hitSomething = false; + for (int cTest = 0; cTest < contourCount; ++cTest) { + SkOpContour* contour = contourList[cTest]; + bool testOpp = contour->operand() ^ current->operand() ^ opp; + if (basePt.fY < contour->bounds().fTop) { + continue; + } + if (bestY > contour->bounds().fBottom) { + continue; + } + int segmentCount = contour->segments().count(); + for (int test = 0; test < segmentCount; ++test) { + SkOpSegment* testSeg = &contour->segments()[test]; + SkScalar testY = bestY; + double testHit; + int testTIndex = testSeg->crossedSpanY(basePt, &testY, &testHit, &hitSomething, tAtMid, + testOpp, testSeg == current); + if (testTIndex < 0) { + if (testTIndex == SK_MinS32) { + hitSomething = true; + bestSeg = NULL; + goto abortContours; // vertical encountered, return and try different point + } + continue; + } + if (testSeg == current && current->betweenTs(index, testHit, endIndex)) { + double baseT = current->t(index); + double endT = current->t(endIndex); + double newMid = (testHit - baseT) / (endT - baseT); +#if DEBUG_WINDING + double midT = current->tAtMid(index, endIndex, mid); + SkPoint midXY = current->xyAtT(midT); + double newMidT = current->tAtMid(index, endIndex, newMid); + SkPoint newXY = current->xyAtT(newMidT); + SkDebugf("%s [%d] mid=%1.9g->%1.9g s=%1.9g (%1.9g,%1.9g) m=%1.9g (%1.9g,%1.9g)" + " n=%1.9g (%1.9g,%1.9g) e=%1.9g (%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), mid, newMid, + baseT, current->xAtT(index), current->yAtT(index), + baseT + mid * (endT - baseT), midXY.fX, midXY.fY, + baseT + newMid * (endT - baseT), newXY.fX, newXY.fY, + endT, current->xAtT(endIndex), current->yAtT(endIndex)); +#endif + *midPtr = newMid * 2; // calling loop with divide by 2 before continuing + return SK_MinS32; + } + bestSeg = testSeg; + *bestHit = testHit; + bestOpp = testOpp; + bestTIndex = testTIndex; + bestY = testY; + } + } +abortContours: + int result; + if (!bestSeg) { + result = hitSomething ? SK_MinS32 : 0; + } else { + if (bestSeg->windSum(bestTIndex) == SK_MinS32) { + *currentPtr = bestSeg; + *indexPtr = bestTIndex; + *endIndexPtr = bestSeg->nextSpan(bestTIndex, 1); + SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0); + *tryAgain = true; + return 0; + } + result = bestSeg->windingAtT(*bestHit, bestTIndex, bestOpp, bestDx); + SkASSERT(*bestDx); + } + double baseT = current->t(index); + double endT = current->t(endIndex); + *bestHit = baseT + mid * (endT - baseT); + return result; +} + +SkOpSegment* FindUndone(SkTDArray<SkOpContour*>& contourList, int* start, int* end) { + int contourCount = contourList.count(); + SkOpSegment* result; + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = contourList[cIndex]; + result = contour->undoneSegment(start, end); + if (result) { + return result; + } + } + return NULL; +} + +SkOpSegment* FindChase(SkTDArray<SkOpSpan*>& chase, int& tIndex, int& endIndex) { + while (chase.count()) { + SkOpSpan* span; + chase.pop(&span); + const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex); + SkOpSegment* segment = backPtr.fOther; + tIndex = backPtr.fOtherIndex; + SkTDArray<SkOpAngle> angles; + int done = 0; + if (segment->activeAngle(tIndex, &done, &angles)) { + SkOpAngle* last = angles.end() - 1; + tIndex = last->start(); + endIndex = last->end(); + #if TRY_ROTATE + *chase.insert(0) = span; + #else + *chase.append() = span; + #endif + return last->segment(); + } + if (done == angles.count()) { + continue; + } + SkTDArray<SkOpAngle*> sorted; + bool sortable = SkOpSegment::SortAngles(angles, &sorted); + int angleCount = sorted.count(); +#if DEBUG_SORT + sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0, 0, 0); +#endif + if (!sortable) { + continue; + } + // find first angle, initialize winding to computed fWindSum + int firstIndex = -1; + const SkOpAngle* angle; + int winding; + do { + angle = sorted[++firstIndex]; + segment = angle->segment(); + winding = segment->windSum(angle); + } while (winding == SK_MinS32); + int spanWinding = segment->spanSign(angle->start(), angle->end()); + #if DEBUG_WINDING + SkDebugf("%s winding=%d spanWinding=%d\n", + __FUNCTION__, winding, spanWinding); + #endif + // turn span winding into contour winding + if (spanWinding * winding < 0) { + winding += spanWinding; + } + #if DEBUG_SORT + segment->debugShowSort(__FUNCTION__, sorted, firstIndex, winding, 0); + #endif + // we care about first sign and whether wind sum indicates this + // edge is inside or outside. Maybe need to pass span winding + // or first winding or something into this function? + // advance to first undone angle, then return it and winding + // (to set whether edges are active or not) + int nextIndex = firstIndex + 1; + int lastIndex = firstIndex != 0 ? firstIndex : angleCount; + angle = sorted[firstIndex]; + winding -= angle->segment()->spanSign(angle); + do { + SkASSERT(nextIndex != firstIndex); + if (nextIndex == angleCount) { + nextIndex = 0; + } + angle = sorted[nextIndex]; + segment = angle->segment(); + int maxWinding = winding; + winding -= segment->spanSign(angle); + #if DEBUG_SORT + SkDebugf("%s id=%d maxWinding=%d winding=%d sign=%d\n", __FUNCTION__, + segment->debugID(), maxWinding, winding, angle->sign()); + #endif + tIndex = angle->start(); + endIndex = angle->end(); + int lesser = SkMin32(tIndex, endIndex); + const SkOpSpan& nextSpan = segment->span(lesser); + if (!nextSpan.fDone) { + // FIXME: this be wrong? assign startWinding if edge is in + // same direction. If the direction is opposite, winding to + // assign is flipped sign or +/- 1? + if (SkOpSegment::UseInnerWinding(maxWinding, winding)) { + maxWinding = winding; + } + segment->markAndChaseWinding(angle, maxWinding, 0); + break; + } + } while (++nextIndex != lastIndex); + *chase.insert(0) = span; + return segment; + } + return NULL; +} + +#if DEBUG_ACTIVE_SPANS +void DebugShowActiveSpans(SkTDArray<SkOpContour*>& contourList) { + int index; + for (index = 0; index < contourList.count(); ++ index) { + contourList[index]->debugShowActiveSpans(); + } +} +#endif + +static SkOpSegment* findSortableTop(const SkTDArray<SkOpContour*>& contourList, + int* index, int* endIndex, SkPoint* topLeft, bool* unsortable, + bool* done, bool onlySortable) { + SkOpSegment* result; + do { + SkPoint bestXY = {SK_ScalarMax, SK_ScalarMax}; + int contourCount = contourList.count(); + SkOpSegment* topStart = NULL; + *done = true; + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = contourList[cIndex]; + if (contour->done()) { + continue; + } + const SkPathOpsBounds& bounds = contour->bounds(); + if (bounds.fBottom < topLeft->fY) { + *done = false; + continue; + } + if (bounds.fBottom == topLeft->fY && bounds.fRight < topLeft->fX) { + *done = false; + continue; + } + contour->topSortableSegment(*topLeft, &bestXY, &topStart); + if (!contour->done()) { + *done = false; + } + } + if (!topStart) { + return NULL; + } + *topLeft = bestXY; + result = topStart->findTop(index, endIndex, unsortable, onlySortable); + } while (!result); + return result; +} + +static int rightAngleWinding(const SkTDArray<SkOpContour*>& contourList, + SkOpSegment** current, int* index, int* endIndex, double* tHit, + SkScalar* hitDx, bool* tryAgain, bool opp) { + double test = 0.9; + int contourWinding; + do { + contourWinding = contourRangeCheckY(contourList, current, index, endIndex, tHit, hitDx, + tryAgain, &test, opp); + if (contourWinding != SK_MinS32 || *tryAgain) { + return contourWinding; + } + test /= 2; + } while (!approximately_negative(test)); + SkASSERT(0); // should be OK to comment out, but interested when this hits + return contourWinding; +} + +static void skipVertical(const SkTDArray<SkOpContour*>& contourList, + SkOpSegment** current, int* index, int* endIndex) { + if (!(*current)->isVertical(*index, *endIndex)) { + return; + } + int contourCount = contourList.count(); + for (int cIndex = 0; cIndex < contourCount; ++cIndex) { + SkOpContour* contour = contourList[cIndex]; + if (contour->done()) { + continue; + } + *current = contour->nonVerticalSegment(index, endIndex); + if (*current) { + return; + } + } +} + +SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour*>& contourList, bool* firstContour, + int* indexPtr, int* endIndexPtr, SkPoint* topLeft, bool* unsortable, + bool* done, bool binary) { + SkOpSegment* current = findSortableTop(contourList, indexPtr, endIndexPtr, topLeft, unsortable, + done, true); + if (!current) { + return NULL; + } + const int index = *indexPtr; + const int endIndex = *endIndexPtr; + if (*firstContour) { + current->initWinding(index, endIndex); + *firstContour = false; + return current; + } + int minIndex = SkMin32(index, endIndex); + int sumWinding = current->windSum(minIndex); + if (sumWinding != SK_MinS32) { + return current; + } + sumWinding = current->computeSum(index, endIndex, binary); + if (sumWinding != SK_MinS32) { + return current; + } + int contourWinding; + int oppContourWinding = 0; + // the simple upward projection of the unresolved points hit unsortable angles + // shoot rays at right angles to the segment to find its winding, ignoring angle cases + bool tryAgain; + double tHit; + SkScalar hitDx = 0; + SkScalar hitOppDx = 0; + do { + // if current is vertical, find another candidate which is not + // if only remaining candidates are vertical, then they can be marked done + SkASSERT(*indexPtr != *endIndexPtr && *indexPtr >= 0 && *endIndexPtr >= 0); + skipVertical(contourList, ¤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 0000000000..5a468074ff --- /dev/null +++ b/src/pathops/SkPathOpsCommon.h @@ -0,0 +1,29 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsCommon_DEFINED +#define SkPathOpsCommon_DEFINED + +#include "SkOpContour.h" + +class SkPathWriter; + +void Assemble(const SkPathWriter& path, SkPathWriter* simple); +SkOpSegment* FindChase(SkTDArray<SkOpSpan*>& chase, int& tIndex, int& endIndex); +SkOpSegment* FindSortableTop(const SkTDArray<SkOpContour*>& contourList, bool* firstContour, + int* index, int* endIndex, SkPoint* topLeft, bool* unsortable, + bool* done, bool binary); +SkOpSegment* FindUndone(SkTDArray<SkOpContour*>& contourList, int* start, int* end); +void FixOtherTIndex(SkTDArray<SkOpContour*>* contourList); +void MakeContourList(SkTArray<SkOpContour>& contours, SkTDArray<SkOpContour*>& list, + bool evenOdd, bool oppEvenOdd); +void SortSegments(SkTDArray<SkOpContour*>* contourList); + +#if DEBUG_ACTIVE_SPANS +void DebugShowActiveSpans(SkTDArray<SkOpContour*>& contourList); +#endif + +#endif diff --git a/src/pathops/SkPathOpsCubic.cpp b/src/pathops/SkPathOpsCubic.cpp new file mode 100644 index 0000000000..674213c383 --- /dev/null +++ b/src/pathops/SkPathOpsCubic.cpp @@ -0,0 +1,463 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkLineParameters.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" +#include "SkPathOpsRect.h" + +const int SkDCubic::gPrecisionUnit = 256; // FIXME: test different values in test framework + +// FIXME: cache keep the bounds and/or precision with the caller? +double SkDCubic::calcPrecision() const { + SkDRect dRect; + dRect.setBounds(*this); // OPTIMIZATION: just use setRawBounds ? + double width = dRect.fRight - dRect.fLeft; + double height = dRect.fBottom - dRect.fTop; + return (width > height ? width : height) / gPrecisionUnit; +} + +bool SkDCubic::clockwise() const { + double sum = (fPts[0].fX - fPts[3].fX) * (fPts[0].fY + fPts[3].fY); + for (int idx = 0; idx < 3; ++idx) { + sum += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY); + } + return sum <= 0; +} + +void SkDCubic::Coefficients(const double* src, double* A, double* B, double* C, double* D) { + *A = src[6]; // d + *B = src[4] * 3; // 3*c + *C = src[2] * 3; // 3*b + *D = src[0]; // a + *A -= *D - *C + *B; // A = -a + 3*b - 3*c + d + *B += 3 * *D - 2 * *C; // B = 3*a - 6*b + 3*c + *C -= 3 * *D; // C = -3*a + 3*b +} + +bool SkDCubic::controlsContainedByEnds() const { + SkDVector startTan = fPts[1] - fPts[0]; + if (startTan.fX == 0 && startTan.fY == 0) { + startTan = fPts[2] - fPts[0]; + } + SkDVector endTan = fPts[2] - fPts[3]; + if (endTan.fX == 0 && endTan.fY == 0) { + endTan = fPts[1] - fPts[3]; + } + if (startTan.dot(endTan) >= 0) { + return false; + } + SkDLine startEdge = {{fPts[0], fPts[0]}}; + startEdge[1].fX -= startTan.fY; + startEdge[1].fY += startTan.fX; + SkDLine endEdge = {{fPts[3], fPts[3]}}; + endEdge[1].fX -= endTan.fY; + endEdge[1].fY += endTan.fX; + double leftStart1 = startEdge.isLeft(fPts[1]); + if (leftStart1 * startEdge.isLeft(fPts[2]) < 0) { + return false; + } + double leftEnd1 = endEdge.isLeft(fPts[1]); + if (leftEnd1 * endEdge.isLeft(fPts[2]) < 0) { + return false; + } + return leftStart1 * leftEnd1 >= 0; +} + +bool SkDCubic::endsAreExtremaInXOrY() const { + return (between(fPts[0].fX, fPts[1].fX, fPts[3].fX) + && between(fPts[0].fX, fPts[2].fX, fPts[3].fX)) + || (between(fPts[0].fY, fPts[1].fY, fPts[3].fY) + && between(fPts[0].fY, fPts[2].fY, fPts[3].fY)); +} + +bool SkDCubic::isLinear(int startIndex, int endIndex) const { + SkLineParameters lineParameters; + lineParameters.cubicEndPoints(*this, startIndex, endIndex); + // FIXME: maybe it's possible to avoid this and compare non-normalized + lineParameters.normalize(); + double distance = lineParameters.controlPtDistance(*this, 1); + if (!approximately_zero(distance)) { + return false; + } + distance = lineParameters.controlPtDistance(*this, 2); + return approximately_zero(distance); +} + +bool SkDCubic::monotonicInY() const { + return between(fPts[0].fY, fPts[1].fY, fPts[3].fY) + && between(fPts[0].fY, fPts[2].fY, fPts[3].fY); +} + +bool SkDCubic::serpentine() const { + if (!controlsContainedByEnds()) { + return false; + } + double wiggle = (fPts[0].fX - fPts[2].fX) * (fPts[0].fY + fPts[2].fY); + for (int idx = 0; idx < 2; ++idx) { + wiggle += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY); + } + double waggle = (fPts[1].fX - fPts[3].fX) * (fPts[1].fY + fPts[3].fY); + for (int idx = 1; idx < 3; ++idx) { + waggle += (fPts[idx + 1].fX - fPts[idx].fX) * (fPts[idx + 1].fY + fPts[idx].fY); + } + return wiggle * waggle < 0; +} + +// cubic roots + +static const double PI = 3.141592653589793; + +// from SkGeometry.cpp (and Numeric Solutions, 5.6) +int SkDCubic::RootsValidT(double A, double B, double C, double D, double t[3]) { + double s[3]; + int realRoots = RootsReal(A, B, C, D, s); + int foundRoots = SkDQuad::AddValidTs(s, realRoots, t); + return foundRoots; +} + +int SkDCubic::RootsReal(double A, double B, double C, double D, double s[3]) { +#ifdef SK_DEBUG + // create a string mathematica understands + // GDB set print repe 15 # if repeated digits is a bother + // set print elements 400 # if line doesn't fit + char str[1024]; + sk_bzero(str, sizeof(str)); + SK_SNPRINTF(str, sizeof(str), "Solve[%1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]", + A, B, C, D); + mathematica_ize(str, sizeof(str)); +#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA + SkDebugf("%s\n", str); +#endif +#endif + if (approximately_zero(A) + && approximately_zero_when_compared_to(A, B) + && approximately_zero_when_compared_to(A, C) + && approximately_zero_when_compared_to(A, D)) { // we're just a quadratic + return SkDQuad::RootsReal(B, C, D, s); + } + if (approximately_zero_when_compared_to(D, A) + && approximately_zero_when_compared_to(D, B) + && approximately_zero_when_compared_to(D, C)) { // 0 is one root + int num = SkDQuad::RootsReal(A, B, C, s); + for (int i = 0; i < num; ++i) { + if (approximately_zero(s[i])) { + return num; + } + } + s[num++] = 0; + return num; + } + if (approximately_zero(A + B + C + D)) { // 1 is one root + int num = SkDQuad::RootsReal(A, A + B, -D, s); + for (int i = 0; i < num; ++i) { + if (AlmostEqualUlps(s[i], 1)) { + return num; + } + } + s[num++] = 1; + return num; + } + double a, b, c; + { + double invA = 1 / A; + a = B * invA; + b = C * invA; + c = D * invA; + } + double a2 = a * a; + double Q = (a2 - b * 3) / 9; + double R = (2 * a2 * a - 9 * a * b + 27 * c) / 54; + double R2 = R * R; + double Q3 = Q * Q * Q; + double R2MinusQ3 = R2 - Q3; + double adiv3 = a / 3; + double r; + double* roots = s; + if (R2MinusQ3 < 0) { // we have 3 real roots + double theta = acos(R / sqrt(Q3)); + double neg2RootQ = -2 * sqrt(Q); + + r = neg2RootQ * cos(theta / 3) - adiv3; + *roots++ = r; + + r = neg2RootQ * cos((theta + 2 * PI) / 3) - adiv3; + if (!AlmostEqualUlps(s[0], r)) { + *roots++ = r; + } + r = neg2RootQ * cos((theta - 2 * PI) / 3) - adiv3; + if (!AlmostEqualUlps(s[0], r) && (roots - s == 1 || !AlmostEqualUlps(s[1], r))) { + *roots++ = r; + } + } else { // we have 1 real root + double sqrtR2MinusQ3 = sqrt(R2MinusQ3); + double A = fabs(R) + sqrtR2MinusQ3; + A = SkDCubeRoot(A); + if (R > 0) { + A = -A; + } + if (A != 0) { + A += Q / A; + } + r = A - adiv3; + *roots++ = r; + if (AlmostEqualUlps(R2, Q3)) { + r = -A / 2 - adiv3; + if (!AlmostEqualUlps(s[0], r)) { + *roots++ = r; + } + } + } + return static_cast<int>(roots - s); +} + +// from http://www.cs.sunysb.edu/~qin/courses/geometry/4.pdf +// c(t) = a(1-t)^3 + 3bt(1-t)^2 + 3c(1-t)t^2 + dt^3 +// c'(t) = -3a(1-t)^2 + 3b((1-t)^2 - 2t(1-t)) + 3c(2t(1-t) - t^2) + 3dt^2 +// = 3(b-a)(1-t)^2 + 6(c-b)t(1-t) + 3(d-c)t^2 +static double derivative_at_t(const double* src, double t) { + double one_t = 1 - t; + double a = src[0]; + double b = src[2]; + double c = src[4]; + double d = src[6]; + return 3 * ((b - a) * one_t * one_t + 2 * (c - b) * t * one_t + (d - c) * t * t); +} + +// OPTIMIZE? compute t^2, t(1-t), and (1-t)^2 and pass them to another version of derivative at t? +SkDVector SkDCubic::dxdyAtT(double t) const { + SkDVector result = { derivative_at_t(&fPts[0].fX, t), derivative_at_t(&fPts[0].fY, t) }; + return result; +} + +// OPTIMIZE? share code with formulate_F1DotF2 +int SkDCubic::findInflections(double tValues[]) const { + double Ax = fPts[1].fX - fPts[0].fX; + double Ay = fPts[1].fY - fPts[0].fY; + double Bx = fPts[2].fX - 2 * fPts[1].fX + fPts[0].fX; + double By = fPts[2].fY - 2 * fPts[1].fY + fPts[0].fY; + double Cx = fPts[3].fX + 3 * (fPts[1].fX - fPts[2].fX) - fPts[0].fX; + double Cy = fPts[3].fY + 3 * (fPts[1].fY - fPts[2].fY) - fPts[0].fY; + return SkDQuad::RootsValidT(Bx * Cy - By * Cx, Ax * Cy - Ay * Cx, Ax * By - Ay * Bx, tValues); +} + +static void formulate_F1DotF2(const double src[], double coeff[4]) { + double a = src[2] - src[0]; + double b = src[4] - 2 * src[2] + src[0]; + double c = src[6] + 3 * (src[2] - src[4]) - src[0]; + coeff[0] = c * c; + coeff[1] = 3 * b * c; + coeff[2] = 2 * b * b + c * a; + coeff[3] = a * b; +} + +/** SkDCubic'(t) = At^2 + Bt + C, where + A = 3(-a + 3(b - c) + d) + B = 6(a - 2b + c) + C = 3(b - a) + Solve for t, keeping only those that fit between 0 < t < 1 +*/ +int SkDCubic::FindExtrema(double a, double b, double c, double d, double tValues[2]) { + // we divide A,B,C by 3 to simplify + double A = d - a + 3*(b - c); + double B = 2*(a - b - b + c); + double C = b - a; + + return SkDQuad::RootsValidT(A, B, C, tValues); +} + +/* from SkGeometry.cpp + Looking for F' dot F'' == 0 + + A = b - a + B = c - 2b + a + C = d - 3c + 3b - a + + F' = 3Ct^2 + 6Bt + 3A + F'' = 6Ct + 6B + + F' dot F'' -> CCt^3 + 3BCt^2 + (2BB + CA)t + AB +*/ +int SkDCubic::findMaxCurvature(double tValues[]) const { + double coeffX[4], coeffY[4]; + int i; + formulate_F1DotF2(&fPts[0].fX, coeffX); + formulate_F1DotF2(&fPts[0].fY, coeffY); + for (i = 0; i < 4; i++) { + coeffX[i] = coeffX[i] + coeffY[i]; + } + return RootsValidT(coeffX[0], coeffX[1], coeffX[2], coeffX[3], tValues); +} + +SkDPoint SkDCubic::top(double startT, double endT) const { + SkDCubic sub = subDivide(startT, endT); + SkDPoint topPt = sub[0]; + if (topPt.fY > sub[3].fY || (topPt.fY == sub[3].fY && topPt.fX > sub[3].fX)) { + topPt = sub[3]; + } + double extremeTs[2]; + if (!sub.monotonicInY()) { + int roots = FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, sub[3].fY, extremeTs); + for (int index = 0; index < roots; ++index) { + double t = startT + (endT - startT) * extremeTs[index]; + SkDPoint mid = xyAtT(t); + if (topPt.fY > mid.fY || (topPt.fY == mid.fY && topPt.fX > mid.fX)) { + topPt = mid; + } + } + } + return topPt; +} + +SkDPoint SkDCubic::xyAtT(double t) const { + double one_t = 1 - t; + double one_t2 = one_t * one_t; + double a = one_t2 * one_t; + double b = 3 * one_t2 * t; + double t2 = t * t; + double c = 3 * one_t * t2; + double d = t2 * t; + SkDPoint result = {a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX + d * fPts[3].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY + d * fPts[3].fY}; + return result; +} + +/* + Given a cubic c, t1, and t2, find a small cubic segment. + + The new cubic is defined as points A, B, C, and D, where + s1 = 1 - t1 + s2 = 1 - t2 + A = c[0]*s1*s1*s1 + 3*c[1]*s1*s1*t1 + 3*c[2]*s1*t1*t1 + c[3]*t1*t1*t1 + D = c[0]*s2*s2*s2 + 3*c[1]*s2*s2*t2 + 3*c[2]*s2*t2*t2 + c[3]*t2*t2*t2 + + We don't have B or C. So We define two equations to isolate them. + First, compute two reference T values 1/3 and 2/3 from t1 to t2: + + c(at (2*t1 + t2)/3) == E + c(at (t1 + 2*t2)/3) == F + + Next, compute where those values must be if we know the values of B and C: + + _12 = A*2/3 + B*1/3 + 12_ = A*1/3 + B*2/3 + _23 = B*2/3 + C*1/3 + 23_ = B*1/3 + C*2/3 + _34 = C*2/3 + D*1/3 + 34_ = C*1/3 + D*2/3 + _123 = (A*2/3 + B*1/3)*2/3 + (B*2/3 + C*1/3)*1/3 = A*4/9 + B*4/9 + C*1/9 + 123_ = (A*1/3 + B*2/3)*1/3 + (B*1/3 + C*2/3)*2/3 = A*1/9 + B*4/9 + C*4/9 + _234 = (B*2/3 + C*1/3)*2/3 + (C*2/3 + D*1/3)*1/3 = B*4/9 + C*4/9 + D*1/9 + 234_ = (B*1/3 + C*2/3)*1/3 + (C*1/3 + D*2/3)*2/3 = B*1/9 + C*4/9 + D*4/9 + _1234 = (A*4/9 + B*4/9 + C*1/9)*2/3 + (B*4/9 + C*4/9 + D*1/9)*1/3 + = A*8/27 + B*12/27 + C*6/27 + D*1/27 + = E + 1234_ = (A*1/9 + B*4/9 + C*4/9)*1/3 + (B*1/9 + C*4/9 + D*4/9)*2/3 + = A*1/27 + B*6/27 + C*12/27 + D*8/27 + = F + E*27 = A*8 + B*12 + C*6 + D + F*27 = A + B*6 + C*12 + D*8 + +Group the known values on one side: + + M = E*27 - A*8 - D = B*12 + C* 6 + N = F*27 - A - D*8 = B* 6 + C*12 + M*2 - N = B*18 + N*2 - M = C*18 + B = (M*2 - N)/18 + C = (N*2 - M)/18 + */ + +static double interp_cubic_coords(const double* src, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double cd = SkDInterp(src[4], src[6], t); + double abc = SkDInterp(ab, bc, t); + double bcd = SkDInterp(bc, cd, t); + double abcd = SkDInterp(abc, bcd, t); + return abcd; +} + +SkDCubic SkDCubic::subDivide(double t1, double t2) const { + if (t1 == 0 && t2 == 1) { + return *this; + } + SkDCubic dst; + double ax = dst[0].fX = interp_cubic_coords(&fPts[0].fX, t1); + double ay = dst[0].fY = interp_cubic_coords(&fPts[0].fY, t1); + double ex = interp_cubic_coords(&fPts[0].fX, (t1*2+t2)/3); + double ey = interp_cubic_coords(&fPts[0].fY, (t1*2+t2)/3); + double fx = interp_cubic_coords(&fPts[0].fX, (t1+t2*2)/3); + double fy = interp_cubic_coords(&fPts[0].fY, (t1+t2*2)/3); + double dx = dst[3].fX = interp_cubic_coords(&fPts[0].fX, t2); + double dy = dst[3].fY = interp_cubic_coords(&fPts[0].fY, t2); + double mx = ex * 27 - ax * 8 - dx; + double my = ey * 27 - ay * 8 - dy; + double nx = fx * 27 - ax - dx * 8; + double ny = fy * 27 - ay - dy * 8; + /* bx = */ dst[1].fX = (mx * 2 - nx) / 18; + /* by = */ dst[1].fY = (my * 2 - ny) / 18; + /* cx = */ dst[2].fX = (nx * 2 - mx) / 18; + /* cy = */ dst[2].fY = (ny * 2 - my) / 18; + return dst; +} + +void SkDCubic::subDivide(const SkDPoint& a, const SkDPoint& d, + double t1, double t2, SkDPoint dst[2]) const { + double ex = interp_cubic_coords(&fPts[0].fX, (t1 * 2 + t2) / 3); + double ey = interp_cubic_coords(&fPts[0].fY, (t1 * 2 + t2) / 3); + double fx = interp_cubic_coords(&fPts[0].fX, (t1 + t2 * 2) / 3); + double fy = interp_cubic_coords(&fPts[0].fY, (t1 + t2 * 2) / 3); + double mx = ex * 27 - a.fX * 8 - d.fX; + double my = ey * 27 - a.fY * 8 - d.fY; + double nx = fx * 27 - a.fX - d.fX * 8; + double ny = fy * 27 - a.fY - d.fY * 8; + /* bx = */ dst[0].fX = (mx * 2 - nx) / 18; + /* by = */ dst[0].fY = (my * 2 - ny) / 18; + /* cx = */ dst[1].fX = (nx * 2 - mx) / 18; + /* cy = */ dst[1].fY = (ny * 2 - my) / 18; +} + +/* classic one t subdivision */ +static void interp_cubic_coords(const double* src, double* dst, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double cd = SkDInterp(src[4], src[6], t); + double abc = SkDInterp(ab, bc, t); + double bcd = SkDInterp(bc, cd, t); + double abcd = SkDInterp(abc, bcd, t); + + dst[0] = src[0]; + dst[2] = ab; + dst[4] = abc; + dst[6] = abcd; + dst[8] = bcd; + dst[10] = cd; + dst[12] = src[6]; +} + +SkDCubicPair SkDCubic::chopAt(double t) const { + SkDCubicPair dst; + if (t == 0.5) { + dst.pts[0] = fPts[0]; + dst.pts[1].fX = (fPts[0].fX + fPts[1].fX) / 2; + dst.pts[1].fY = (fPts[0].fY + fPts[1].fY) / 2; + dst.pts[2].fX = (fPts[0].fX + 2 * fPts[1].fX + fPts[2].fX) / 4; + dst.pts[2].fY = (fPts[0].fY + 2 * fPts[1].fY + fPts[2].fY) / 4; + dst.pts[3].fX = (fPts[0].fX + 3 * (fPts[1].fX + fPts[2].fX) + fPts[3].fX) / 8; + dst.pts[3].fY = (fPts[0].fY + 3 * (fPts[1].fY + fPts[2].fY) + fPts[3].fY) / 8; + dst.pts[4].fX = (fPts[1].fX + 2 * fPts[2].fX + fPts[3].fX) / 4; + dst.pts[4].fY = (fPts[1].fY + 2 * fPts[2].fY + fPts[3].fY) / 4; + dst.pts[5].fX = (fPts[2].fX + fPts[3].fX) / 2; + dst.pts[5].fY = (fPts[2].fY + fPts[3].fY) / 2; + dst.pts[6] = fPts[3]; + return dst; + } + interp_cubic_coords(&fPts[0].fX, &dst.pts[0].fX, t); + interp_cubic_coords(&fPts[0].fY, &dst.pts[0].fY, t); + return dst; +} diff --git a/src/pathops/SkPathOpsCubic.h b/src/pathops/SkPathOpsCubic.h new file mode 100644 index 0000000000..48280fd32f --- /dev/null +++ b/src/pathops/SkPathOpsCubic.h @@ -0,0 +1,71 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsCubic_DEFINED +#define SkPathOpsCubic_DEFINED + +#include "SkPathOpsPoint.h" +#include "SkTDArray.h" + +struct SkDCubicPair { + const SkDCubic& first() const { return (const SkDCubic&) pts[0]; } + const SkDCubic& second() const { return (const SkDCubic&) pts[3]; } + SkDPoint pts[7]; +}; + +struct SkDCubic { + SkDPoint fPts[4]; + + void set(const SkPoint pts[4]) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + fPts[2] = pts[2]; + fPts[3] = pts[3]; + } + + static const int gPrecisionUnit; + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 4); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 4); return fPts[n]; } + + double calcPrecision() const; + SkDCubicPair chopAt(double t) const; + bool clockwise() const; + static void Coefficients(const double* cubic, double* A, double* B, double* C, double* D); + bool controlsContainedByEnds() const; + SkDVector dxdyAtT(double t) const; + bool endsAreExtremaInXOrY() const; + static int FindExtrema(double a, double b, double c, double d, double tValue[2]); + int findInflections(double tValues[]) const; + int findMaxCurvature(double tValues[]) const; + bool isLinear(int startIndex, int endIndex) const; + bool monotonicInY() const; + static int RootsReal(double A, double B, double C, double D, double t[3]); + static int RootsValidT(const double A, const double B, const double C, double D, double s[3]); + bool serpentine() const; + SkDCubic subDivide(double t1, double t2) const; + static SkDCubic SubDivide(const SkPoint a[4], double t1, double t2) { + SkDCubic cubic; + cubic.set(a); + return cubic.subDivide(t1, t2); + } + void subDivide(const SkDPoint& a, const SkDPoint& d, double t1, double t2, SkDPoint p[2]) const; + + static void SubDivide(const SkPoint pts[4], const SkDPoint& a, const SkDPoint& d, double t1, + double t2, SkDPoint p[2]) { + SkDCubic cubic; + cubic.set(pts); + cubic.subDivide(a, d, t1, t2, p); + } + + SkDPoint top(double startT, double endT) const; + void toQuadraticTs(double precision, SkTDArray<double>* ts) const; + SkDQuad toQuad() const; + SkDPoint xyAtT(double t) const; +}; + +#endif diff --git a/src/pathops/SkPathOpsCurve.h b/src/pathops/SkPathOpsCurve.h new file mode 100644 index 0000000000..ca68a664e6 --- /dev/null +++ b/src/pathops/SkPathOpsCurve.h @@ -0,0 +1,152 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsCurve_DEFINE +#define SkPathOpsCurve_DEFINE + +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" + +static SkDPoint dline_xy_at_t(const SkPoint a[2], double t) { + SkDLine line; + line.set(a); + return line.xyAtT(t); +} + +static SkDPoint dquad_xy_at_t(const SkPoint a[3], double t) { + SkDQuad quad; + quad.set(a); + return quad.xyAtT(t); +} + +static SkDPoint dcubic_xy_at_t(const SkPoint a[4], double t) { + SkDCubic cubic; + cubic.set(a); + return cubic.xyAtT(t); +} + +static SkDPoint (* const CurveDPointAtT[])(const SkPoint[], double ) = { + NULL, + dline_xy_at_t, + dquad_xy_at_t, + dcubic_xy_at_t +}; + +static SkPoint fline_xy_at_t(const SkPoint a[2], double t) { + return dline_xy_at_t(a, t).asSkPoint(); +} + +static SkPoint fquad_xy_at_t(const SkPoint a[3], double t) { + return dquad_xy_at_t(a, t).asSkPoint(); +} + +static SkPoint fcubic_xy_at_t(const SkPoint a[4], double t) { + return dcubic_xy_at_t(a, t).asSkPoint(); +} + +static SkPoint (* const CurvePointAtT[])(const SkPoint[], double ) = { + NULL, + fline_xy_at_t, + fquad_xy_at_t, + fcubic_xy_at_t +}; + +static SkDVector dline_dxdy_at_t(const SkPoint a[2], double ) { + SkDLine line; + line.set(a); + return line[1] - line[0]; +} + +static SkDVector dquad_dxdy_at_t(const SkPoint a[3], double t) { + SkDQuad quad; + quad.set(a); + return quad.dxdyAtT(t); +} + +static SkDVector dcubic_dxdy_at_t(const SkPoint a[4], double t) { + SkDCubic cubic; + cubic.set(a); + return cubic.dxdyAtT(t); +} + +static SkDVector (* const CurveDSlopeAtT[])(const SkPoint[], double ) = { + NULL, + dline_dxdy_at_t, + dquad_dxdy_at_t, + dcubic_dxdy_at_t +}; + +static SkVector fline_dxdy_at_t(const SkPoint a[2], double ) { + return a[1] - a[0]; +} + +static SkVector fquad_dxdy_at_t(const SkPoint a[3], double t) { + return dquad_dxdy_at_t(a, t).asSkVector(); +} + +static SkVector fcubic_dxdy_at_t(const SkPoint a[4], double t) { + return dcubic_dxdy_at_t(a, t).asSkVector(); +} + +static SkVector (* const CurveSlopeAtT[])(const SkPoint[], double ) = { + NULL, + fline_dxdy_at_t, + fquad_dxdy_at_t, + fcubic_dxdy_at_t +}; + +static SkPoint quad_top(const SkPoint a[3], double startT, double endT) { + SkDQuad quad; + quad.set(a); + SkDPoint topPt = quad.top(startT, endT); + return topPt.asSkPoint(); +} + +static SkPoint cubic_top(const SkPoint a[4], double startT, double endT) { + SkDCubic cubic; + cubic.set(a); + SkDPoint topPt = cubic.top(startT, endT); + return topPt.asSkPoint(); +} + +static SkPoint (* const CurveTop[])(const SkPoint[], double , double ) = { + NULL, + NULL, + quad_top, + cubic_top +}; + +static bool line_is_vertical(const SkPoint a[2], double startT, double endT) { + SkDLine line; + line.set(a); + SkDPoint dst[2] = { line.xyAtT(startT), line.xyAtT(endT) }; + return AlmostEqualUlps(dst[0].fX, dst[1].fX); +} + +static bool quad_is_vertical(const SkPoint a[3], double startT, double endT) { + SkDQuad quad; + quad.set(a); + SkDQuad dst = quad.subDivide(startT, endT); + return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX); +} + +static bool cubic_is_vertical(const SkPoint a[4], double startT, double endT) { + SkDCubic cubic; + cubic.set(a); + SkDCubic dst = cubic.subDivide(startT, endT); + return AlmostEqualUlps(dst[0].fX, dst[1].fX) && AlmostEqualUlps(dst[1].fX, dst[2].fX) + && AlmostEqualUlps(dst[2].fX, dst[3].fX); +} + +static bool (* const CurveIsVertical[])(const SkPoint[], double , double) = { + NULL, + line_is_vertical, + quad_is_vertical, + cubic_is_vertical +}; + +#endif diff --git a/src/pathops/SkPathOpsDebug.cpp b/src/pathops/SkPathOpsDebug.cpp new file mode 100644 index 0000000000..bef129c1b9 --- /dev/null +++ b/src/pathops/SkPathOpsDebug.cpp @@ -0,0 +1,59 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPathOpsDebug.h" + +#if defined SK_DEBUG || !FORCE_RELEASE + +int gDebugMaxWindSum = SK_MaxS32; +int gDebugMaxWindValue = SK_MaxS32; + +void mathematica_ize(char* str, size_t bufferLen) { + size_t len = strlen(str); + bool num = false; + for (size_t idx = 0; idx < len; ++idx) { + if (num && str[idx] == 'e') { + if (len + 2 >= bufferLen) { + return; + } + memmove(&str[idx + 2], &str[idx + 1], len - idx); + str[idx] = '*'; + str[idx + 1] = '^'; + ++len; + } + num = str[idx] >= '0' && str[idx] <= '9'; + } +} + +bool valid_wind(int wind) { + return wind > SK_MinS32 + 0xFFFF && wind < SK_MaxS32 - 0xFFFF; +} + +void winding_printf(int wind) { + if (wind == SK_MinS32) { + SkDebugf("?"); + } else { + SkDebugf("%d", wind); + } +} +#endif + +#if DEBUG_DUMP +const char* kLVerbStr[] = {"", "line", "quad", "cubic"}; +// static const char* kUVerbStr[] = {"", "Line", "Quad", "Cubic"}; +int gContourID; +int gSegmentID; +#endif + +#if DEBUG_SORT || DEBUG_SWAP_TOP +int gDebugSortCountDefault = SK_MaxS32; +int gDebugSortCount; +#endif + +#if DEBUG_ACTIVE_OP +const char* kPathOpStr[] = {"diff", "sect", "union", "xor"}; +#endif diff --git a/src/pathops/SkPathOpsDebug.h b/src/pathops/SkPathOpsDebug.h new file mode 100644 index 0000000000..16ca176831 --- /dev/null +++ b/src/pathops/SkPathOpsDebug.h @@ -0,0 +1,132 @@ +/* + * Copyright 2013 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsDebug_DEFINED +#define SkPathOpsDebug_DEFINED + +#include "SkTypes.h" + +#ifdef SK_RELEASE +#define FORCE_RELEASE 1 +#else +#define FORCE_RELEASE 1 // set force release to 1 for multiple thread -- no debugging +#endif + +#define ONE_OFF_DEBUG 0 +#define ONE_OFF_DEBUG_MATHEMATICA 0 + +#if defined SK_DEBUG || !FORCE_RELEASE + +#ifdef SK_BUILD_FOR_WIN + #define SK_RAND(seed) rand() + #define SK_SNPRINTF _snprintf +#else + #define SK_RAND(seed) rand_r(&seed) + #define SK_SNPRINTF snprintf +#endif + +void mathematica_ize(char* str, size_t bufferSize); +bool valid_wind(int winding); +void winding_printf(int winding); + +extern int gDebugMaxWindSum; +extern int gDebugMaxWindValue; +#endif + +#if FORCE_RELEASE + +#define DEBUG_ACTIVE_OP 0 +#define DEBUG_ACTIVE_SPANS 0 +#define DEBUG_ACTIVE_SPANS_SHORT_FORM 0 +#define DEBUG_ADD_INTERSECTING_TS 0 +#define DEBUG_ADD_T_PAIR 0 +#define DEBUG_ANGLE 0 +#define DEBUG_AS_C_CODE 1 +#define DEBUG_ASSEMBLE 0 +#define DEBUG_CONCIDENT 0 +#define DEBUG_CROSS 0 +#define DEBUG_FLAT_QUADS 0 +#define DEBUG_FLOW 0 +#define DEBUG_MARK_DONE 0 +#define DEBUG_PATH_CONSTRUCTION 0 +#define DEBUG_SHOW_TEST_PROGRESS 0 +#define DEBUG_SHOW_WINDING 0 +#define DEBUG_SORT 0 +#define DEBUG_SWAP_TOP 0 +#define DEBUG_UNSORTABLE 0 +#define DEBUG_WIND_BUMP 0 +#define DEBUG_WINDING 0 +#define DEBUG_WINDING_AT_T 0 + +#else + +#define DEBUG_ACTIVE_OP 1 +#define DEBUG_ACTIVE_SPANS 1 +#define DEBUG_ACTIVE_SPANS_SHORT_FORM 0 +#define DEBUG_ADD_INTERSECTING_TS 1 +#define DEBUG_ADD_T_PAIR 1 +#define DEBUG_ANGLE 1 +#define DEBUG_AS_C_CODE 1 +#define DEBUG_ASSEMBLE 1 +#define DEBUG_CONCIDENT 1 +#define DEBUG_CROSS 0 +#define DEBUG_FLAT_QUADS 0 +#define DEBUG_FLOW 1 +#define DEBUG_MARK_DONE 1 +#define DEBUG_PATH_CONSTRUCTION 1 +#define DEBUG_SHOW_TEST_PROGRESS 1 +#define DEBUG_SHOW_WINDING 0 +#define DEBUG_SORT 1 +#define DEBUG_SWAP_TOP 1 +#define DEBUG_UNSORTABLE 1 +#define DEBUG_WIND_BUMP 0 +#define DEBUG_WINDING 1 +#define DEBUG_WINDING_AT_T 1 + +#endif + +#define DEBUG_DUMP (DEBUG_ACTIVE_OP | DEBUG_ACTIVE_SPANS | DEBUG_CONCIDENT | DEBUG_SORT | \ + DEBUG_PATH_CONSTRUCTION) + +#if DEBUG_AS_C_CODE +#define CUBIC_DEBUG_STR "{{%1.17g,%1.17g}, {%1.17g,%1.17g}, {%1.17g,%1.17g}, {%1.17g,%1.17g}}" +#define QUAD_DEBUG_STR "{{%1.17g,%1.17g}, {%1.17g,%1.17g}, {%1.17g,%1.17g}}" +#define LINE_DEBUG_STR "{{%1.17g,%1.17g}, {%1.17g,%1.17g}}" +#define PT_DEBUG_STR "{{%1.17g,%1.17g}}" +#else +#define CUBIC_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)" +#define QUAD_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g %1.9g,%1.9g)" +#define LINE_DEBUG_STR "(%1.9g,%1.9g %1.9g,%1.9g)" +#define PT_DEBUG_STR "(%1.9g,%1.9g)" +#endif +#define T_DEBUG_STR(t, n) #t "[" #n "]=%1.9g" +#define TX_DEBUG_STR(t) #t "[%d]=%1.9g" +#define CUBIC_DEBUG_DATA(c) c[0].fX, c[0].fY, c[1].fX, c[1].fY, c[2].fX, c[2].fY, c[3].fX, c[3].fY +#define QUAD_DEBUG_DATA(q) q[0].fX, q[0].fY, q[1].fX, q[1].fY, q[2].fX, q[2].fY +#define LINE_DEBUG_DATA(l) l[0].fX, l[0].fY, l[1].fX, l[1].fY +#define PT_DEBUG_DATA(i, n) i.pt(n).fX, i.pt(n).fY + +#if DEBUG_DUMP +extern const char* kLVerbStr[]; +// extern const char* kUVerbStr[]; +extern int gContourID; +extern int gSegmentID; +#endif + +#if DEBUG_SORT || DEBUG_SWAP_TOP +extern int gDebugSortCountDefault; +extern int gDebugSortCount; +#endif + +#if DEBUG_ACTIVE_OP +extern const char* kPathOpStr[]; +#endif + +#ifndef DEBUG_TEST +#define DEBUG_TEST 0 +#endif + +#endif diff --git a/src/pathops/SkPathOpsLine.cpp b/src/pathops/SkPathOpsLine.cpp new file mode 100644 index 0000000000..b7c91c991d --- /dev/null +++ b/src/pathops/SkPathOpsLine.cpp @@ -0,0 +1,48 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkPathOpsLine.h" + +SkDLine SkDLine::subDivide(double t1, double t2) const { + SkDVector delta = tangent(); + SkDLine dst = {{{ + fPts[0].fX - t1 * delta.fX, fPts[0].fY - t1 * delta.fY}, { + fPts[0].fX - t2 * delta.fX, fPts[0].fY - t2 * delta.fY}}}; + return dst; +} + +// may have this below somewhere else already: +// copying here because I thought it was clever + +// Copyright 2001, softSurfer (www.softsurfer.com) +// This code may be freely used and modified for any purpose +// providing that this copyright notice is included with it. +// SoftSurfer makes no warranty for this code, and cannot be held +// liable for any real or imagined damage resulting from its use. +// Users of this code must verify correctness for their application. + +// Assume that a class is already given for the object: +// Point with coordinates {float x, y;} +//=================================================================== + +// isLeft(): tests if a point is Left|On|Right of an infinite line. +// Input: three points P0, P1, and P2 +// Return: >0 for P2 left of the line through P0 and P1 +// =0 for P2 on the line +// <0 for P2 right of the line +// See: the January 2001 Algorithm on Area of Triangles +// return (float) ((P1.x - P0.x)*(P2.y - P0.y) - (P2.x - P0.x)*(P1.y - P0.y)); +double SkDLine::isLeft(const SkDPoint& pt) const { + SkDVector p0 = fPts[1] - fPts[0]; + SkDVector p2 = pt - fPts[0]; + return p0.cross(p2); +} + +SkDPoint SkDLine::xyAtT(double t) const { + double one_t = 1 - t; + SkDPoint result = { one_t * fPts[0].fX + t * fPts[1].fX, one_t * fPts[0].fY + t * fPts[1].fY }; + return result; +} diff --git a/src/pathops/SkPathOpsLine.h b/src/pathops/SkPathOpsLine.h new file mode 100644 index 0000000000..34bb6587d3 --- /dev/null +++ b/src/pathops/SkPathOpsLine.h @@ -0,0 +1,35 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsLine_DEFINED +#define SkPathOpsLine_DEFINED + +#include "SkPathOpsPoint.h" + +struct SkDLine { + SkDPoint fPts[2]; + + void set(const SkPoint pts[2]) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + } + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 2); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 2); return fPts[n]; } + + double isLeft(const SkDPoint& pt) const; + SkDLine subDivide(double t1, double t2) const; + static SkDLine SubDivide(const SkPoint a[2], double t1, double t2) { + SkDLine line; + line.set(a); + return line.subDivide(t1, t2); + } + SkDPoint xyAtT(double t) const; +private: + SkDVector tangent() const { return fPts[0] - fPts[1]; } +}; + +#endif diff --git a/src/pathops/SkPathOpsOp.cpp b/src/pathops/SkPathOpsOp.cpp new file mode 100644 index 0000000000..5a3576ff6c --- /dev/null +++ b/src/pathops/SkPathOpsOp.cpp @@ -0,0 +1,272 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkAddIntersections.h" +#include "SkOpEdgeBuilder.h" +#include "SkPathOpsCommon.h" +#include "SkPathWriter.h" + +// FIXME: this and find chase should be merge together, along with +// other code that walks winding in angles +// OPTIMIZATION: Probably, the walked winding should be rolled into the angle structure +// so it isn't duplicated by walkers like this one +static SkOpSegment* findChaseOp(SkTDArray<SkOpSpan*>& chase, int& nextStart, int& nextEnd) { + while (chase.count()) { + SkOpSpan* span; + chase.pop(&span); + const SkOpSpan& backPtr = span->fOther->span(span->fOtherIndex); + SkOpSegment* segment = backPtr.fOther; + nextStart = backPtr.fOtherIndex; + SkTDArray<SkOpAngle> angles; + int done = 0; + if (segment->activeAngle(nextStart, &done, &angles)) { + SkOpAngle* last = angles.end() - 1; + nextStart = last->start(); + nextEnd = last->end(); + #if TRY_ROTATE + *chase.insert(0) = span; + #else + *chase.append() = span; + #endif + return last->segment(); + } + if (done == angles.count()) { + continue; + } + SkTDArray<SkOpAngle*> sorted; + bool sortable = SkOpSegment::SortAngles(angles, &sorted); + int angleCount = sorted.count(); +#if DEBUG_SORT + sorted[0]->segment()->debugShowSort(__FUNCTION__, sorted, 0); +#endif + if (!sortable) { + continue; + } + // find first angle, initialize winding to computed fWindSum + int firstIndex = -1; + const SkOpAngle* angle; + do { + angle = sorted[++firstIndex]; + segment = angle->segment(); + } while (segment->windSum(angle) == SK_MinS32); + #if DEBUG_SORT + segment->debugShowSort(__FUNCTION__, sorted, firstIndex); + #endif + int sumMiWinding = segment->updateWindingReverse(angle); + int sumSuWinding = segment->updateOppWindingReverse(angle); + if (segment->operand()) { + SkTSwap<int>(sumMiWinding, sumSuWinding); + } + int nextIndex = firstIndex + 1; + int lastIndex = firstIndex != 0 ? firstIndex : angleCount; + SkOpSegment* first = NULL; + do { + SkASSERT(nextIndex != firstIndex); + if (nextIndex == angleCount) { + nextIndex = 0; + } + angle = sorted[nextIndex]; + segment = angle->segment(); + int start = angle->start(); + int end = angle->end(); + int maxWinding, sumWinding, oppMaxWinding, oppSumWinding; + segment->setUpWindings(start, end, &sumMiWinding, &sumSuWinding, + &maxWinding, &sumWinding, &oppMaxWinding, &oppSumWinding); + if (!segment->done(angle)) { + if (!first) { + first = segment; + nextStart = start; + nextEnd = end; + } + (void) segment->markAngle(maxWinding, sumWinding, oppMaxWinding, + oppSumWinding, true, angle); + } + } while (++nextIndex != lastIndex); + if (first) { + #if TRY_ROTATE + *chase.insert(0) = span; + #else + *chase.append() = span; + #endif + return first; + } + } + return NULL; +} + +/* +static bool windingIsActive(int winding, int oppWinding, int spanWinding, int oppSpanWinding, + bool windingIsOp, PathOp op) { + bool active = windingIsActive(winding, spanWinding); + if (!active) { + return false; + } + if (oppSpanWinding && windingIsActive(oppWinding, oppSpanWinding)) { + switch (op) { + case kIntersect_Op: + case kUnion_Op: + return true; + case kDifference_Op: { + int absSpan = abs(spanWinding); + int absOpp = abs(oppSpanWinding); + return windingIsOp ? absSpan < absOpp : absSpan > absOpp; + } + case kXor_Op: + return spanWinding != oppSpanWinding; + default: + SkASSERT(0); + } + } + bool opActive = oppWinding != 0; + return gOpLookup[op][opActive][windingIsOp]; +} +*/ + +static bool bridgeOp(SkTDArray<SkOpContour*>& contourList, const SkPathOp op, + const int xorMask, const int xorOpMask, SkPathWriter* simple) { + bool firstContour = true; + bool unsortable = false; + bool topUnsortable = false; + SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin}; + do { + int index, endIndex; + bool done; + SkOpSegment* current = FindSortableTop(contourList, &firstContour, &index, &endIndex, + &topLeft, &topUnsortable, &done, true); + if (!current) { + if (topUnsortable || !done) { + topUnsortable = false; + SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin); + topLeft.fX = topLeft.fY = SK_ScalarMin; + continue; + } + break; + } + SkTDArray<SkOpSpan*> chaseArray; + do { + if (current->activeOp(index, endIndex, xorMask, xorOpMask, op)) { + do { + #if DEBUG_ACTIVE_SPANS + if (!unsortable && current->done()) { + DebugShowActiveSpans(contourList); + } + #endif + SkASSERT(unsortable || !current->done()); + int nextStart = index; + int nextEnd = endIndex; + SkOpSegment* next = current->findNextOp(&chaseArray, &nextStart, &nextEnd, + &unsortable, op, xorMask, xorOpMask); + if (!next) { + if (!unsortable && simple->hasMove() + && current->verb() != SkPath::kLine_Verb + && !simple->isClosed()) { + current->addCurveTo(index, endIndex, simple, true); + SkASSERT(simple->isClosed()); + } + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY, + current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY); + #endif + current->addCurveTo(index, endIndex, simple, true); + current = next; + index = nextStart; + endIndex = nextEnd; + } while (!simple->isClosed() && ((!unsortable) + || !current->done(SkMin32(index, endIndex)))); + if (current->activeWinding(index, endIndex) && !simple->isClosed()) { + SkASSERT(unsortable); + int min = SkMin32(index, endIndex); + if (!current->done(min)) { + current->addCurveTo(index, endIndex, simple, true); + current->markDoneBinary(min); + } + } + simple->close(); + } else { + SkOpSpan* last = current->markAndChaseDoneBinary(index, endIndex); + if (last && !last->fLoop) { + *chaseArray.append() = last; + } + } + current = findChaseOp(chaseArray, index, endIndex); + #if DEBUG_ACTIVE_SPANS + DebugShowActiveSpans(contourList); + #endif + if (!current) { + break; + } + } while (true); + } while (true); + return simple->someAssemblyRequired(); +} + +void Op(const SkPath& one, const SkPath& two, SkPathOp op, SkPath* result) { +#if DEBUG_SORT || DEBUG_SWAP_TOP + gDebugSortCount = gDebugSortCountDefault; +#endif + result->reset(); + result->setFillType(SkPath::kEvenOdd_FillType); + // turn path into list of segments + SkTArray<SkOpContour> contours; + // FIXME: add self-intersecting cubics' T values to segment + SkOpEdgeBuilder builder(one, contours); + const int xorMask = builder.xorMask(); + builder.addOperand(two); + builder.finish(); + const int xorOpMask = builder.xorMask(); + SkTDArray<SkOpContour*> contourList; + MakeContourList(contours, contourList, xorMask == kEvenOdd_PathOpsMask, + xorOpMask == kEvenOdd_PathOpsMask); + SkOpContour** currentPtr = contourList.begin(); + if (!currentPtr) { + return; + } + SkOpContour** listEnd = contourList.end(); + // find all intersections between segments + do { + SkOpContour** nextPtr = currentPtr; + SkOpContour* current = *currentPtr++; + if (current->containsCubics()) { + AddSelfIntersectTs(current); + } + SkOpContour* next; + do { + next = *nextPtr++; + } while (AddIntersectTs(current, next) && nextPtr != listEnd); + } while (currentPtr != listEnd); + // eat through coincident edges + + int total = 0; + int index; + for (index = 0; index < contourList.count(); ++index) { + total += contourList[index]->segments().count(); + } +#if DEBUG_SHOW_WINDING + SkOpContour::debugShowWindingValues(contourList); +#endif + CoincidenceCheck(&contourList, total); +#if DEBUG_SHOW_WINDING + SkOpContour::debugShowWindingValues(contourList); +#endif + FixOtherTIndex(&contourList); + SortSegments(&contourList); +#if DEBUG_ACTIVE_SPANS + DebugShowActiveSpans(contourList); +#endif + // construct closed contours + SkPathWriter wrapper(*result); + bridgeOp(contourList, op, xorMask, xorOpMask, &wrapper); + { // if some edges could not be resolved, assemble remaining fragments + SkPath temp; + temp.setFillType(SkPath::kEvenOdd_FillType); + SkPathWriter assembled(temp); + Assemble(wrapper, &assembled); + *result = *assembled.nativePath(); + } +} diff --git a/src/pathops/SkPathOpsPoint.cpp b/src/pathops/SkPathOpsPoint.cpp new file mode 100644 index 0000000000..dc9cde55a3 --- /dev/null +++ b/src/pathops/SkPathOpsPoint.cpp @@ -0,0 +1,17 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkPathOpsPoint.h" + +SkDVector operator-(const SkDPoint& a, const SkDPoint& b) { + SkDVector v = {a.fX - b.fX, a.fY - b.fY}; + return v; +} + +SkDPoint operator+(const SkDPoint& a, const SkDVector& b) { + SkDPoint v = {a.fX + b.fX, a.fY + b.fY}; + return v; +} diff --git a/src/pathops/SkPathOpsPoint.h b/src/pathops/SkPathOpsPoint.h new file mode 100644 index 0000000000..38051005ea --- /dev/null +++ b/src/pathops/SkPathOpsPoint.h @@ -0,0 +1,159 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsPoint_DEFINED +#define SkPathOpsPoint_DEFINED + +#include "SkPathOpsTypes.h" +#include "SkPoint.h" + +struct SkDVector { + double fX, fY; + + friend SkDPoint operator+(const SkDPoint& a, const SkDVector& b); + + void operator+=(const SkDVector& v) { + fX += v.fX; + fY += v.fY; + } + + void operator-=(const SkDVector& v) { + fX -= v.fX; + fY -= v.fY; + } + + void operator/=(const double s) { + fX /= s; + fY /= s; + } + + void operator*=(const double s) { + fX *= s; + fY *= s; + } + + SkVector asSkVector() const { + SkVector v = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)}; + return v; + } + + double cross(const SkDVector& a) const { + return fX * a.fY - fY * a.fX; + } + + double dot(const SkDVector& a) const { + return fX * a.fX + fY * a.fY; + } + + double length() const { + return sqrt(lengthSquared()); + } + + double lengthSquared() const { + return fX * fX + fY * fY; + } +}; + +struct SkDPoint { + double fX; + double fY; + + void set(const SkPoint& pt) { + fX = pt.fX; + fY = pt.fY; + } + + friend SkDVector operator-(const SkDPoint& a, const SkDPoint& b); + + friend bool operator==(const SkDPoint& a, const SkDPoint& b) { + return a.fX == b.fX && a.fY == b.fY; + } + + friend bool operator!=(const SkDPoint& a, const SkDPoint& b) { + return a.fX != b.fX || a.fY != b.fY; + } + + void operator=(const SkPoint& pt) { + fX = pt.fX; + fY = pt.fY; + } + + + void operator+=(const SkDVector& v) { + fX += v.fX; + fY += v.fY; + } + + void operator-=(const SkDVector& v) { + fX -= v.fX; + fY -= v.fY; + } + + // note: this can not be implemented with + // return approximately_equal(a.fY, fY) && approximately_equal(a.fX, fX); + // because that will not take the magnitude of the values + bool approximatelyEqual(const SkDPoint& a) const { + double denom = SkTMax<double>(fabs(fX), SkTMax<double>(fabs(fY), + SkTMax<double>(fabs(a.fX), fabs(a.fY)))); + if (denom == 0) { + return true; + } + double inv = 1 / denom; + return approximately_equal(fX * inv, a.fX * inv) + && approximately_equal(fY * inv, a.fY * inv); + } + + bool approximatelyEqual(const SkPoint& a) const { + double denom = SkTMax<double>(fabs(fX), SkTMax<double>(fabs(fY), + SkScalarToDouble(SkTMax<SkScalar>(fabs(a.fX), fabs(a.fY))))); + if (denom == 0) { + return true; + } + double inv = 1 / denom; + return approximately_equal(fX * inv, a.fX * inv) + && approximately_equal(fY * inv, a.fY * inv); + } + + bool approximatelyEqualHalf(const SkDPoint& a) const { + double denom = SkTMax<double>(fabs(fX), SkTMax<double>(fabs(fY), + SkTMax<double>(fabs(a.fX), fabs(a.fY)))); + if (denom == 0) { + return true; + } + double inv = 1 / denom; + return approximately_equal_half(fX * inv, a.fX * inv) + && approximately_equal_half(fY * inv, a.fY * inv); + } + + bool approximatelyZero() const { + return approximately_zero(fX) && approximately_zero(fY); + } + + SkPoint asSkPoint() const { + SkPoint pt = {SkDoubleToScalar(fX), SkDoubleToScalar(fY)}; + return pt; + } + + double distance(const SkDPoint& a) const { + SkDVector temp = *this - a; + return temp.length(); + } + + double distanceSquared(const SkDPoint& a) const { + SkDVector temp = *this - a; + return temp.lengthSquared(); + } + + double moreRoughlyEqual(const SkDPoint& a) const { + return more_roughly_equal(a.fY, fY) && more_roughly_equal(a.fX, fX); + } + + double roughlyEqual(const SkDPoint& a) const { + return roughly_equal(a.fY, fY) && roughly_equal(a.fX, fX); + } +}; + +#endif diff --git a/src/pathops/SkPathOpsQuad.cpp b/src/pathops/SkPathOpsQuad.cpp new file mode 100644 index 0000000000..cbba2a3fe5 --- /dev/null +++ b/src/pathops/SkPathOpsQuad.cpp @@ -0,0 +1,293 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkLineParameters.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsQuad.h" +#include "SkPathOpsTriangle.h" + +// from http://blog.gludion.com/2009/08/distance-to-quadratic-bezier-curve.html +// (currently only used by testing) +double SkDQuad::nearestT(const SkDPoint& pt) const { + SkDVector pos = fPts[0] - pt; + // search points P of bezier curve with PM.(dP / dt) = 0 + // a calculus leads to a 3d degree equation : + SkDVector A = fPts[1] - fPts[0]; + SkDVector B = fPts[2] - fPts[1]; + B -= A; + double a = B.dot(B); + double b = 3 * A.dot(B); + double c = 2 * A.dot(A) + pos.dot(B); + double d = pos.dot(A); + double ts[3]; + int roots = SkDCubic::RootsValidT(a, b, c, d, ts); + double d0 = pt.distanceSquared(fPts[0]); + double d2 = pt.distanceSquared(fPts[2]); + double distMin = SkTMin<double>(d0, d2); + int bestIndex = -1; + for (int index = 0; index < roots; ++index) { + SkDPoint onQuad = xyAtT(ts[index]); + double dist = pt.distanceSquared(onQuad); + if (distMin > dist) { + distMin = dist; + bestIndex = index; + } + } + if (bestIndex >= 0) { + return ts[bestIndex]; + } + return d0 < d2 ? 0 : 1; +} + +bool SkDQuad::pointInHull(const SkDPoint& pt) const { + return ((const SkDTriangle&) fPts).contains(pt); +} + +SkDPoint SkDQuad::top(double startT, double endT) const { + SkDQuad sub = subDivide(startT, endT); + SkDPoint topPt = sub[0]; + if (topPt.fY > sub[2].fY || (topPt.fY == sub[2].fY && topPt.fX > sub[2].fX)) { + topPt = sub[2]; + } + if (!between(sub[0].fY, sub[1].fY, sub[2].fY)) { + double extremeT; + if (FindExtrema(sub[0].fY, sub[1].fY, sub[2].fY, &extremeT)) { + extremeT = startT + (endT - startT) * extremeT; + SkDPoint test = xyAtT(extremeT); + if (topPt.fY > test.fY || (topPt.fY == test.fY && topPt.fX > test.fX)) { + topPt = test; + } + } + } + return topPt; +} + +int SkDQuad::AddValidTs(double s[], int realRoots, double* t) { + int foundRoots = 0; + for (int index = 0; index < realRoots; ++index) { + double tValue = s[index]; + if (approximately_zero_or_more(tValue) && approximately_one_or_less(tValue)) { + if (approximately_less_than_zero(tValue)) { + tValue = 0; + } else if (approximately_greater_than_one(tValue)) { + tValue = 1; + } + for (int idx2 = 0; idx2 < foundRoots; ++idx2) { + if (approximately_equal(t[idx2], tValue)) { + goto nextRoot; + } + } + t[foundRoots++] = tValue; + } +nextRoot: + {} + } + return foundRoots; +} + +// note: caller expects multiple results to be sorted smaller first +// note: http://en.wikipedia.org/wiki/Loss_of_significance has an interesting +// analysis of the quadratic equation, suggesting why the following looks at +// the sign of B -- and further suggesting that the greatest loss of precision +// is in b squared less two a c +int SkDQuad::RootsValidT(double A, double B, double C, double t[2]) { + double s[2]; + int realRoots = RootsReal(A, B, C, s); + int foundRoots = AddValidTs(s, realRoots, t); + return foundRoots; +} + +/* +Numeric Solutions (5.6) suggests to solve the quadratic by computing + Q = -1/2(B + sgn(B)Sqrt(B^2 - 4 A C)) +and using the roots + t1 = Q / A + t2 = C / Q +*/ +// this does not discard real roots <= 0 or >= 1 +int SkDQuad::RootsReal(const double A, const double B, const double C, double s[2]) { + const double p = B / (2 * A); + const double q = C / A; + if (approximately_zero(A) && (approximately_zero_inverse(p) || approximately_zero_inverse(q))) { + if (approximately_zero(B)) { + s[0] = 0; + return C == 0; + } + s[0] = -C / B; + return 1; + } + /* normal form: x^2 + px + q = 0 */ + const double p2 = p * p; + if (!AlmostEqualUlps(p2, q) && p2 < q) { + return 0; + } + double sqrt_D = 0; + if (p2 > q) { + sqrt_D = sqrt(p2 - q); + } + s[0] = sqrt_D - p; + s[1] = -sqrt_D - p; + return 1 + !AlmostEqualUlps(s[0], s[1]); +} + +bool SkDQuad::isLinear(int startIndex, int endIndex) const { + SkLineParameters lineParameters; + lineParameters.quadEndPoints(*this, startIndex, endIndex); + // FIXME: maybe it's possible to avoid this and compare non-normalized + lineParameters.normalize(); + double distance = lineParameters.controlPtDistance(*this); + return approximately_zero(distance); +} + +SkDCubic SkDQuad::toCubic() const { + SkDCubic cubic; + cubic[0] = fPts[0]; + cubic[2] = fPts[1]; + cubic[3] = fPts[2]; + cubic[1].fX = (cubic[0].fX + cubic[2].fX * 2) / 3; + cubic[1].fY = (cubic[0].fY + cubic[2].fY * 2) / 3; + cubic[2].fX = (cubic[3].fX + cubic[2].fX * 2) / 3; + cubic[2].fY = (cubic[3].fY + cubic[2].fY * 2) / 3; + return cubic; +} + +SkDVector SkDQuad::dxdyAtT(double t) const { + double a = t - 1; + double b = 1 - 2 * t; + double c = t; + SkDVector result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY }; + return result; +} + +SkDPoint SkDQuad::xyAtT(double t) const { + double one_t = 1 - t; + double a = one_t * one_t; + double b = 2 * one_t * t; + double c = t * t; + SkDPoint result = { a * fPts[0].fX + b * fPts[1].fX + c * fPts[2].fX, + a * fPts[0].fY + b * fPts[1].fY + c * fPts[2].fY }; + return result; +} + +/* +Given a quadratic q, t1, and t2, find a small quadratic segment. + +The new quadratic is defined by A, B, and C, where + A = c[0]*(1 - t1)*(1 - t1) + 2*c[1]*t1*(1 - t1) + c[2]*t1*t1 + C = c[3]*(1 - t1)*(1 - t1) + 2*c[2]*t1*(1 - t1) + c[1]*t1*t1 + +To find B, compute the point halfway between t1 and t2: + +q(at (t1 + t2)/2) == D + +Next, compute where D must be if we know the value of B: + +_12 = A/2 + B/2 +12_ = B/2 + C/2 +123 = A/4 + B/2 + C/4 + = D + +Group the known values on one side: + +B = D*2 - A/2 - C/2 +*/ + +static double interp_quad_coords(const double* src, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double abc = SkDInterp(ab, bc, t); + return abc; +} + +bool SkDQuad::monotonicInY() const { + return between(fPts[0].fY, fPts[1].fY, fPts[2].fY); +} + +SkDQuad SkDQuad::subDivide(double t1, double t2) const { + SkDQuad dst; + double ax = dst[0].fX = interp_quad_coords(&fPts[0].fX, t1); + double ay = dst[0].fY = interp_quad_coords(&fPts[0].fY, t1); + double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2); + double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2); + double cx = dst[2].fX = interp_quad_coords(&fPts[0].fX, t2); + double cy = dst[2].fY = interp_quad_coords(&fPts[0].fY, t2); + /* bx = */ dst[1].fX = 2*dx - (ax + cx)/2; + /* by = */ dst[1].fY = 2*dy - (ay + cy)/2; + return dst; +} + +SkDPoint SkDQuad::subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const { + SkDPoint b; + double dx = interp_quad_coords(&fPts[0].fX, (t1 + t2) / 2); + double dy = interp_quad_coords(&fPts[0].fY, (t1 + t2) / 2); + b.fX = 2 * dx - (a.fX + c.fX) / 2; + b.fY = 2 * dy - (a.fY + c.fY) / 2; + return b; +} + +/* classic one t subdivision */ +static void interp_quad_coords(const double* src, double* dst, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + dst[0] = src[0]; + dst[2] = ab; + dst[4] = SkDInterp(ab, bc, t); + dst[6] = bc; + dst[8] = src[4]; +} + +SkDQuadPair SkDQuad::chopAt(double t) const +{ + SkDQuadPair dst; + interp_quad_coords(&fPts[0].fX, &dst.pts[0].fX, t); + interp_quad_coords(&fPts[0].fY, &dst.pts[0].fY, t); + return dst; +} + +static int valid_unit_divide(double numer, double denom, double* ratio) +{ + if (numer < 0) { + numer = -numer; + denom = -denom; + } + if (denom == 0 || numer == 0 || numer >= denom) { + return 0; + } + double r = numer / denom; + if (r == 0) { // catch underflow if numer <<<< denom + return 0; + } + *ratio = r; + return 1; +} + +/** Quad'(t) = At + B, where + A = 2(a - 2b + c) + B = 2(b - a) + Solve for t, only if it fits between 0 < t < 1 +*/ +int SkDQuad::FindExtrema(double a, double b, double c, double tValue[1]) { + /* At + B == 0 + t = -B / A + */ + return valid_unit_divide(a - b, a - b - b + c, tValue); +} + +/* Parameterization form, given A*t*t + 2*B*t*(1-t) + C*(1-t)*(1-t) + * + * a = A - 2*B + C + * b = 2*B - 2*C + * c = C + */ +void SkDQuad::SetABC(const double* quad, double* a, double* b, double* c) { + *a = quad[0]; // a = A + *b = 2 * quad[2]; // b = 2*B + *c = quad[4]; // c = C + *b -= *c; // b = 2*B - C + *a -= *b; // a = A - 2*B + C + *b -= *c; // b = 2*B - 2*C +} diff --git a/src/pathops/SkPathOpsQuad.h b/src/pathops/SkPathOpsQuad.h new file mode 100644 index 0000000000..d5ca0c7443 --- /dev/null +++ b/src/pathops/SkPathOpsQuad.h @@ -0,0 +1,62 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsQuad_DEFINED +#define SkPathOpsQuad_DEFINED + +#include "SkPathOpsPoint.h" + +struct SkDQuadPair { + const SkDQuad& first() const { return (const SkDQuad&) pts[0]; } + const SkDQuad& second() const { return (const SkDQuad&) pts[2]; } + SkDPoint pts[5]; +}; + +struct SkDQuad { + SkDPoint fPts[3]; + + void set(const SkPoint pts[3]) { + fPts[0] = pts[0]; + fPts[1] = pts[1]; + fPts[2] = pts[2]; + } + + const SkDPoint& operator[](int n) const { SkASSERT(n >= 0 && n < 3); return fPts[n]; } + SkDPoint& operator[](int n) { SkASSERT(n >= 0 && n < 3); return fPts[n]; } + + static int AddValidTs(double s[], int realRoots, double* t); + SkDQuadPair chopAt(double t) const; + SkDVector dxdyAtT(double t) const; + static int FindExtrema(double a, double b, double c, double tValue[1]); + bool isLinear(int startIndex, int endIndex) const; + bool monotonicInY() const; + double nearestT(const SkDPoint&) const; + bool pointInHull(const SkDPoint&) const; + static int RootsReal(double A, double B, double C, double t[2]); + static int RootsValidT(const double A, const double B, const double C, double s[2]); + static void SetABC(const double* quad, double* a, double* b, double* c); + SkDQuad subDivide(double t1, double t2) const; + static SkDQuad SubDivide(const SkPoint a[3], double t1, double t2) { + SkDQuad quad; + quad.set(a); + return quad.subDivide(t1, t2); + } + SkDPoint subDivide(const SkDPoint& a, const SkDPoint& c, double t1, double t2) const; + static SkDPoint SubDivide(const SkPoint pts[3], const SkDPoint& a, const SkDPoint& c, + double t1, double t2) { + SkDQuad quad; + quad.set(pts); + return quad.subDivide(a, c, t1, t2); + } + SkDCubic toCubic() const; + SkDPoint top(double startT, double endT) const; + SkDPoint xyAtT(double t) const; +private: +// static double Tangent(const double* quadratic, double t); // uncalled +}; + +#endif diff --git a/src/pathops/SkPathOpsRect.cpp b/src/pathops/SkPathOpsRect.cpp new file mode 100644 index 0000000000..9d7f876cac --- /dev/null +++ b/src/pathops/SkPathOpsRect.cpp @@ -0,0 +1,65 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" +#include "SkPathOpsRect.h" + +void SkDRect::setBounds(const SkDLine& line) { + set(line[0]); + add(line[1]); +} + +void SkDRect::setBounds(const SkDQuad& quad) { + set(quad[0]); + add(quad[2]); + double tValues[2]; + int roots = 0; + if (!between(quad[0].fX, quad[1].fX, quad[2].fX)) { + roots = SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, tValues); + } + if (!between(quad[0].fY, quad[1].fY, quad[2].fY)) { + roots += SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValues[roots]); + } + for (int x = 0; x < roots; ++x) { + add(quad.xyAtT(tValues[x])); + } +} + +void SkDRect::setRawBounds(const SkDQuad& quad) { + set(quad[0]); + for (int x = 1; x < 3; ++x) { + add(quad[x]); + } +} + +static bool is_bounded_by_end_points(double a, double b, double c, double d) { + return between(a, b, d) && between(a, c, d); +} + +void SkDRect::setBounds(const SkDCubic& c) { + set(c[0]); + add(c[3]); + double tValues[4]; + int roots = 0; + if (!is_bounded_by_end_points(c[0].fX, c[1].fX, c[2].fX, c[3].fX)) { + roots = SkDCubic::FindExtrema(c[0].fX, c[1].fX, c[2].fX, c[3].fX, tValues); + } + if (!is_bounded_by_end_points(c[0].fY, c[1].fY, c[2].fY, c[3].fY)) { + roots += SkDCubic::FindExtrema(c[0].fY, c[1].fY, c[2].fY, c[3].fY, &tValues[roots]); + } + for (int x = 0; x < roots; ++x) { + add(c.xyAtT(tValues[x])); + } +} + +void SkDRect::setRawBounds(const SkDCubic& cubic) { + set(cubic[0]); + for (int x = 1; x < 4; ++x) { + add(cubic[x]); + } +} diff --git a/src/pathops/SkPathOpsRect.h b/src/pathops/SkPathOpsRect.h new file mode 100644 index 0000000000..7b516a40f5 --- /dev/null +++ b/src/pathops/SkPathOpsRect.h @@ -0,0 +1,56 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsRect_DEFINED +#define SkPathOpsRect_DEFINED + +#include "SkPathOpsPoint.h" + +struct SkDRect { + double fLeft, fTop, fRight, fBottom; + + void add(const SkDPoint& pt) { + if (fLeft > pt.fX) { + fLeft = pt.fX; + } + if (fTop > pt.fY) { + fTop = pt.fY; + } + if (fRight < pt.fX) { + fRight = pt.fX; + } + if (fBottom < pt.fY) { + fBottom = pt.fY; + } + } + + // FIXME: used by debugging only ? + bool contains(const SkDPoint& pt) const { + return approximately_between(fLeft, pt.fX, fRight) + && approximately_between(fTop, pt.fY, fBottom); + } + + bool intersects(SkDRect* r) const { + SkASSERT(fLeft <= fRight); + SkASSERT(fTop <= fBottom); + SkASSERT(r->fLeft <= r->fRight); + SkASSERT(r->fTop <= r->fBottom); + return r->fLeft <= fRight && fLeft <= r->fRight && r->fTop <= fBottom && fTop <= r->fBottom; + } + + void set(const SkDPoint& pt) { + fLeft = fRight = pt.fX; + fTop = fBottom = pt.fY; + } + + void setBounds(const SkDLine&); + void setBounds(const SkDCubic&); + void setBounds(const SkDQuad&); + void setRawBounds(const SkDCubic&); + void setRawBounds(const SkDQuad&); +}; + +#endif diff --git a/src/pathops/SkPathOpsSimplify.cpp b/src/pathops/SkPathOpsSimplify.cpp new file mode 100644 index 0000000000..2213c68334 --- /dev/null +++ b/src/pathops/SkPathOpsSimplify.cpp @@ -0,0 +1,195 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkAddIntersections.h" +#include "SkOpEdgeBuilder.h" +#include "SkPathOpsCommon.h" +#include "SkPathWriter.h" + +static bool bridgeWinding(SkTDArray<SkOpContour*>& contourList, SkPathWriter* simple) { + bool firstContour = true; + bool unsortable = false; + bool topUnsortable = false; + SkPoint topLeft = {SK_ScalarMin, SK_ScalarMin}; + do { + int index, endIndex; + bool topDone; + SkOpSegment* current = FindSortableTop(contourList, &firstContour, &index, &endIndex, + &topLeft, &topUnsortable, &topDone, false); + if (!current) { + if (topUnsortable || !topDone) { + topUnsortable = false; + SkASSERT(topLeft.fX != SK_ScalarMin && topLeft.fY != SK_ScalarMin); + topLeft.fX = topLeft.fY = SK_ScalarMin; + continue; + } + break; + } + SkTDArray<SkOpSpan*> chaseArray; + do { + if (current->activeWinding(index, endIndex)) { + do { + #if DEBUG_ACTIVE_SPANS + if (!unsortable && current->done()) { + DebugShowActiveSpans(contourList); + } + #endif + SkASSERT(unsortable || !current->done()); + int nextStart = index; + int nextEnd = endIndex; + SkOpSegment* next = current->findNextWinding(&chaseArray, &nextStart, &nextEnd, + &unsortable); + if (!next) { + if (!unsortable && simple->hasMove() + && current->verb() != SkPath::kLine_Verb + && !simple->isClosed()) { + current->addCurveTo(index, endIndex, simple, true); + SkASSERT(simple->isClosed()); + } + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), current->xyAtT(index).fX, current->xyAtT(index).fY, + current->xyAtT(endIndex).fX, current->xyAtT(endIndex).fY); + #endif + current->addCurveTo(index, endIndex, simple, true); + current = next; + index = nextStart; + endIndex = nextEnd; + } while (!simple->isClosed() && (!unsortable + || !current->done(SkMin32(index, endIndex)))); + if (current->activeWinding(index, endIndex) && !simple->isClosed()) { + SkASSERT(unsortable); + int min = SkMin32(index, endIndex); + if (!current->done(min)) { + current->addCurveTo(index, endIndex, simple, true); + current->markDoneUnary(min); + } + } + simple->close(); + } else { + SkOpSpan* last = current->markAndChaseDoneUnary(index, endIndex); + if (last && !last->fLoop) { + *chaseArray.append() = last; + } + } + current = FindChase(chaseArray, index, endIndex); + #if DEBUG_ACTIVE_SPANS + DebugShowActiveSpans(contourList); + #endif + if (!current) { + break; + } + } while (true); + } while (true); + return simple->someAssemblyRequired(); +} + +// returns true if all edges were processed +static bool bridgeXor(SkTDArray<SkOpContour*>& contourList, SkPathWriter* simple) { + SkOpSegment* current; + int start, end; + bool unsortable = false; + bool closable = true; + while ((current = FindUndone(contourList, &start, &end))) { + do { + #if DEBUG_ACTIVE_SPANS + if (!unsortable && current->done()) { + DebugShowActiveSpans(contourList); + } + #endif + SkASSERT(unsortable || !current->done()); + int nextStart = start; + int nextEnd = end; + SkOpSegment* next = current->findNextXor(&nextStart, &nextEnd, &unsortable); + if (!next) { + if (!unsortable && simple->hasMove() + && current->verb() != SkPath::kLine_Verb + && !simple->isClosed()) { + current->addCurveTo(start, end, simple, true); + SkASSERT(simple->isClosed()); + } + break; + } + #if DEBUG_FLOW + SkDebugf("%s current id=%d from=(%1.9g,%1.9g) to=(%1.9g,%1.9g)\n", __FUNCTION__, + current->debugID(), current->xyAtT(start).fX, current->xyAtT(start).fY, + current->xyAtT(end).fX, current->xyAtT(end).fY); + #endif + current->addCurveTo(start, end, simple, true); + current = next; + start = nextStart; + end = nextEnd; + } while (!simple->isClosed() && (!unsortable || !current->done(SkMin32(start, end)))); + if (!simple->isClosed()) { + SkASSERT(unsortable); + int min = SkMin32(start, end); + if (!current->done(min)) { + current->addCurveTo(start, end, simple, true); + current->markDone(min, 1); + } + closable = false; + } + simple->close(); + #if DEBUG_ACTIVE_SPANS + DebugShowActiveSpans(contourList); + #endif + } + return closable; +} + +// FIXME : add this as a member of SkPath +void Simplify(const SkPath& path, SkPath* result) { +#if DEBUG_SORT || DEBUG_SWAP_TOP + gDebugSortCount = gDebugSortCountDefault; +#endif + // returns 1 for evenodd, -1 for winding, regardless of inverse-ness + result->reset(); + result->setFillType(SkPath::kEvenOdd_FillType); + SkPathWriter simple(*result); + + // turn path into list of segments + SkTArray<SkOpContour> contours; + SkOpEdgeBuilder builder(path, contours); + builder.finish(); + SkTDArray<SkOpContour*> contourList; + MakeContourList(contours, contourList, false, false); + SkOpContour** currentPtr = contourList.begin(); + if (!currentPtr) { + return; + } + SkOpContour** listEnd = contourList.end(); + // find all intersections between segments + do { + SkOpContour** nextPtr = currentPtr; + SkOpContour* current = *currentPtr++; + if (current->containsCubics()) { + AddSelfIntersectTs(current); + } + SkOpContour* next; + do { + next = *nextPtr++; + } while (AddIntersectTs(current, next) && nextPtr != listEnd); + } while (currentPtr != listEnd); + // eat through coincident edges + CoincidenceCheck(&contourList, 0); + FixOtherTIndex(&contourList); + SortSegments(&contourList); +#if DEBUG_ACTIVE_SPANS + DebugShowActiveSpans(contourList); +#endif + // construct closed contours + if (builder.xorMask() == kWinding_PathOpsMask ? bridgeWinding(contourList, &simple) + : !bridgeXor(contourList, &simple)) + { // if some edges could not be resolved, assemble remaining fragments + SkPath temp; + temp.setFillType(SkPath::kEvenOdd_FillType); + SkPathWriter assembled(temp); + Assemble(simple, &assembled); + *result = *assembled.nativePath(); + } +} diff --git a/src/pathops/SkPathOpsSpan.h b/src/pathops/SkPathOpsSpan.h new file mode 100644 index 0000000000..33965922ae --- /dev/null +++ b/src/pathops/SkPathOpsSpan.h @@ -0,0 +1,31 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkOpSpan_DEFINED +#define SkOpSpan_DEFINED + +#include "SkPoint.h" + +class SkOpSegment; + +struct SkOpSpan { + SkOpSegment* fOther; + SkPoint fPt; // computed when the curves are intersected + double fT; + double fOtherT; // value at fOther[fOtherIndex].fT + int fOtherIndex; // can't be used during intersection + int fWindSum; // accumulated from contours surrounding this one. + int fOppSum; // for binary operators: the opposite winding sum + int fWindValue; // 0 == canceled; 1 == normal; >1 == coincident + int fOppValue; // normally 0 -- when binary coincident edges combine, opp value goes here + bool fDone; // if set, this span to next higher T has been processed + bool fUnsortableStart; // set when start is part of an unsortable pair + bool fUnsortableEnd; // set when end is part of an unsortable pair + bool fTiny; // if set, span may still be considered once for edge following + bool fLoop; // set when a cubic loops back to this point +}; + +#endif diff --git a/src/pathops/SkPathOpsTriangle.cpp b/src/pathops/SkPathOpsTriangle.cpp new file mode 100644 index 0000000000..49391667ac --- /dev/null +++ b/src/pathops/SkPathOpsTriangle.cpp @@ -0,0 +1,31 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "SkPathOpsTriangle.h" + +// http://www.blackpawn.com/texts/pointinpoly/default.html +bool SkDTriangle::contains(const SkDPoint& pt) const { +// Compute vectors + SkDVector v0 = fPts[2] - fPts[0]; + SkDVector v1 = fPts[1] - fPts[0]; + SkDVector v2 = pt - fPts[0]; + +// Compute dot products + double dot00 = v0.dot(v0); + double dot01 = v0.dot(v1); + double dot02 = v0.dot(v2); + double dot11 = v1.dot(v1); + double dot12 = v1.dot(v2); + +// Compute barycentric coordinates + double invDenom = 1 / (dot00 * dot11 - dot01 * dot01); + double u = (dot11 * dot02 - dot01 * dot12) * invDenom; + double v = (dot00 * dot12 - dot01 * dot02) * invDenom; + +// Check if point is in triangle + return (u >= 0) && (v >= 0) && (u + v < 1); +} diff --git a/src/pathops/SkPathOpsTriangle.h b/src/pathops/SkPathOpsTriangle.h new file mode 100644 index 0000000000..cbeea8cc76 --- /dev/null +++ b/src/pathops/SkPathOpsTriangle.h @@ -0,0 +1,20 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#ifndef SkPathOpsTriangle_DEFINED +#define SkPathOpsTriangle_DEFINED + +#include "SkPathOpsPoint.h" + +struct SkDTriangle { + SkDPoint fPts[3]; + + bool contains(const SkDPoint& pt) const; + +}; + +#endif diff --git a/src/pathops/SkPathOpsTypes.cpp b/src/pathops/SkPathOpsTypes.cpp new file mode 100644 index 0000000000..199d7be14b --- /dev/null +++ b/src/pathops/SkPathOpsTypes.cpp @@ -0,0 +1,69 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkPathOpsTypes.h" + +const int UlpsEpsilon = 16; + +// from http://randomascii.wordpress.com/2012/02/25/comparing-floating-point-numbers-2012-edition/ +union SkPathOpsUlpsFloat { + int32_t fInt; + float fFloat; + + SkPathOpsUlpsFloat(float num = 0.0f) : fFloat(num) {} + bool negative() const { return fInt < 0; } +}; + +bool AlmostEqualUlps(float A, float B) { + SkPathOpsUlpsFloat uA(A); + SkPathOpsUlpsFloat uB(B); + // Different signs means they do not match. + if (uA.negative() != uB.negative()) + { + // Check for equality to make sure +0 == -0 + return A == B; + } + // Find the difference in ULPs. + int ulpsDiff = abs(uA.fInt - uB.fInt); + return ulpsDiff <= UlpsEpsilon; +} + +// cube root approximation using bit hack for 64-bit float +// adapted from Kahan's cbrt +static double cbrt_5d(double d) { + const unsigned int B1 = 715094163; + double t = 0.0; + unsigned int* pt = (unsigned int*) &t; + unsigned int* px = (unsigned int*) &d; + pt[1] = px[1] / 3 + B1; + return t; +} + +// iterative cube root approximation using Halley's method (double) +static double cbrta_halleyd(const double a, const double R) { + const double a3 = a * a * a; + const double b = a * (a3 + R + R) / (a3 + a3 + R); + return b; +} + +// cube root approximation using 3 iterations of Halley's method (double) +static double halley_cbrt3d(double d) { + double a = cbrt_5d(d); + a = cbrta_halleyd(a, d); + a = cbrta_halleyd(a, d); + return cbrta_halleyd(a, d); +} + +double SkDCubeRoot(double x) { + if (approximately_zero_cubed(x)) { + return 0; + } + double result = halley_cbrt3d(fabs(x)); + if (x < 0) { + result = -result; + } + return result; +} diff --git a/src/pathops/SkPathOpsTypes.h b/src/pathops/SkPathOpsTypes.h new file mode 100644 index 0000000000..aadaca8ccb --- /dev/null +++ b/src/pathops/SkPathOpsTypes.h @@ -0,0 +1,226 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathOpsTypes_DEFINED +#define SkPathOpsTypes_DEFINED + +#include <float.h> // for FLT_EPSILON +#include <math.h> // for fabs, sqrt + +#include "SkFloatingPoint.h" +#include "SkPathOps.h" +#include "SkPathOpsDebug.h" +#include "SkScalar.h" + +// FIXME: move these into SkTypes.h +template <typename T> inline T SkTMax(T a, T b) { + if (a < b) + a = b; + return a; +} + +template <typename T> inline T SkTMin(T a, T b) { + if (a > b) + a = b; + return a; +} + +// FIXME: move this into SkFloatingPoint.h +#define sk_double_isnan(a) sk_float_isnan(a) + +enum SkPathOpsMask { + kWinding_PathOpsMask = -1, + kNo_PathOpsMask = 0, + kEvenOdd_PathOpsMask = 1 +}; + +extern bool AlmostEqualUlps(float A, float B); +inline bool AlmostEqualUlps(double A, double B) { + return AlmostEqualUlps(SkDoubleToScalar(A), SkDoubleToScalar(B)); +} + +// FLT_EPSILON == 1.19209290E-07 == 1 / (2 ^ 23) +// DBL_EPSILON == 2.22045e-16 +const double FLT_EPSILON_CUBED = FLT_EPSILON * FLT_EPSILON * FLT_EPSILON; +const double FLT_EPSILON_HALF = FLT_EPSILON / 2; +const double FLT_EPSILON_SQUARED = FLT_EPSILON * FLT_EPSILON; +const double FLT_EPSILON_SQRT = sqrt(FLT_EPSILON); +const double FLT_EPSILON_INVERSE = 1 / FLT_EPSILON; +const double DBL_EPSILON_ERR = DBL_EPSILON * 4; // FIXME: tune -- allow a few bits of error +const double ROUGH_EPSILON = FLT_EPSILON * 64; +const double MORE_ROUGH_EPSILON = FLT_EPSILON * 256; + +inline bool approximately_zero(double x) { + return fabs(x) < FLT_EPSILON; +} + +inline bool precisely_zero(double x) { + return fabs(x) < DBL_EPSILON_ERR; +} + +inline bool approximately_zero(float x) { + return fabs(x) < FLT_EPSILON; +} + +inline bool approximately_zero_cubed(double x) { + return fabs(x) < FLT_EPSILON_CUBED; +} + +inline bool approximately_zero_half(double x) { + return fabs(x) < FLT_EPSILON_HALF; +} + +inline bool approximately_zero_squared(double x) { + return fabs(x) < FLT_EPSILON_SQUARED; +} + +inline bool approximately_zero_sqrt(double x) { + return fabs(x) < FLT_EPSILON_SQRT; +} + +inline bool approximately_zero_inverse(double x) { + return fabs(x) > FLT_EPSILON_INVERSE; +} + +// OPTIMIZATION: if called multiple times with the same denom, we want to pass 1/y instead +inline bool approximately_zero_when_compared_to(double x, double y) { + return x == 0 || fabs(x / y) < FLT_EPSILON; +} + +// Use this for comparing Ts in the range of 0 to 1. For general numbers (larger and smaller) use +// AlmostEqualUlps instead. +inline bool approximately_equal(double x, double y) { + return approximately_zero(x - y); +} + +inline bool precisely_equal(double x, double y) { + return precisely_zero(x - y); +} + +inline bool approximately_equal_half(double x, double y) { + return approximately_zero_half(x - y); +} + +inline bool approximately_equal_squared(double x, double y) { + return approximately_equal(x, y); +} + +inline bool approximately_greater(double x, double y) { + return x - FLT_EPSILON >= y; +} + +inline bool approximately_greater_or_equal(double x, double y) { + return x + FLT_EPSILON > y; +} + +inline bool approximately_lesser(double x, double y) { + return x + FLT_EPSILON <= y; +} + +inline bool approximately_lesser_or_equal(double x, double y) { + return x - FLT_EPSILON < y; +} + +inline double approximately_pin(double x) { + return approximately_zero(x) ? 0 : x; +} + +inline float approximately_pin(float x) { + return approximately_zero(x) ? 0 : x; +} + +inline bool approximately_greater_than_one(double x) { + return x > 1 - FLT_EPSILON; +} + +inline bool precisely_greater_than_one(double x) { + return x > 1 - DBL_EPSILON_ERR; +} + +inline bool approximately_less_than_zero(double x) { + return x < FLT_EPSILON; +} + +inline bool precisely_less_than_zero(double x) { + return x < DBL_EPSILON_ERR; +} + +inline bool approximately_negative(double x) { + return x < FLT_EPSILON; +} + +inline bool precisely_negative(double x) { + return x < DBL_EPSILON_ERR; +} + +inline bool approximately_one_or_less(double x) { + return x < 1 + FLT_EPSILON; +} + +inline bool approximately_positive(double x) { + return x > -FLT_EPSILON; +} + +inline bool approximately_positive_squared(double x) { + return x > -(FLT_EPSILON_SQUARED); +} + +inline bool approximately_zero_or_more(double x) { + return x > -FLT_EPSILON; +} + +inline bool approximately_between(double a, double b, double c) { + return a <= c ? approximately_negative(a - b) && approximately_negative(b - c) + : approximately_negative(b - a) && approximately_negative(c - b); +} + +// returns true if (a <= b <= c) || (a >= b >= c) +inline bool between(double a, double b, double c) { + SkASSERT(((a <= b && b <= c) || (a >= b && b >= c)) == ((a - b) * (c - b) <= 0)); + return (a - b) * (c - b) <= 0; +} + +inline bool more_roughly_equal(double x, double y) { + return fabs(x - y) < MORE_ROUGH_EPSILON; +} + +inline bool roughly_equal(double x, double y) { + return fabs(x - y) < ROUGH_EPSILON; +} + +struct SkDPoint; +struct SkDVector; +struct SkDLine; +struct SkDQuad; +struct SkDTriangle; +struct SkDCubic; +struct SkDRect; + +inline double SkDInterp(double A, double B, double t) { + return A + (B - A) * t; +} + +double SkDCubeRoot(double x); + +/* Returns -1 if negative, 0 if zero, 1 if positive +*/ +inline int SkDSign(double x) { + return (x > 0) - (x < 0); +} + +/* Returns 0 if negative, 1 if zero, 2 if positive +*/ +inline int SKDSide(double x) { + return (x > 0) + (x >= 0); +} + +/* Returns 1 if negative, 2 if zero, 4 if positive +*/ +inline int SkDSideBit(double x) { + return 1 << SKDSide(x); +} + +#endif diff --git a/src/pathops/SkPathWriter.cpp b/src/pathops/SkPathWriter.cpp new file mode 100644 index 0000000000..e367228836 --- /dev/null +++ b/src/pathops/SkPathWriter.cpp @@ -0,0 +1,157 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkPathOpsTypes.h" +#include "SkPathWriter.h" + +// wrap path to keep track of whether the contour is initialized and non-empty +SkPathWriter::SkPathWriter(SkPath& path) + : fPathPtr(&path) + , fCloses(0) + , fMoves(0) +{ + init(); +} + +void SkPathWriter::close() { + if (!fHasMove) { + return; + } + bool callClose = isClosed(); + lineTo(); + if (fEmpty) { + return; + } + if (callClose) { +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.close();\n"); +#endif + fPathPtr->close(); + fCloses++; + } + init(); +} + +void SkPathWriter::cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3) { + lineTo(); + moveTo(); + fDefer[1] = pt3; + nudge(); + fDefer[0] = fDefer[1]; +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.cubicTo(%1.9g,%1.9g, %1.9g,%1.9g, %1.9g,%1.9g);\n", + pt1.fX, pt1.fY, pt2.fX, pt2.fY, fDefer[1].fX, fDefer[1].fY); +#endif + fPathPtr->cubicTo(pt1.fX, pt1.fY, pt2.fX, pt2.fY, fDefer[1].fX, fDefer[1].fY); + fEmpty = false; +} + +void SkPathWriter::deferredLine(const SkPoint& pt) { + if (pt == fDefer[1]) { + return; + } + if (changedSlopes(pt)) { + lineTo(); + fDefer[0] = fDefer[1]; + } + fDefer[1] = pt; +} + +void SkPathWriter::deferredMove(const SkPoint& pt) { + fMoved = true; + fHasMove = true; + fEmpty = true; + fDefer[0] = fDefer[1] = pt; +} + +void SkPathWriter::deferredMoveLine(const SkPoint& pt) { + if (!fHasMove) { + deferredMove(pt); + } + deferredLine(pt); +} + +bool SkPathWriter::hasMove() const { + return fHasMove; +} + +void SkPathWriter::init() { + fEmpty = true; + fHasMove = false; + fMoved = false; +} + +bool SkPathWriter::isClosed() const { + return !fEmpty && fFirstPt == fDefer[1]; +} + +void SkPathWriter::lineTo() { + if (fDefer[0] == fDefer[1]) { + return; + } + moveTo(); + nudge(); + fEmpty = false; +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.lineTo(%1.9g,%1.9g);\n", fDefer[1].fX, fDefer[1].fY); +#endif + fPathPtr->lineTo(fDefer[1].fX, fDefer[1].fY); + fDefer[0] = fDefer[1]; +} + +const SkPath* SkPathWriter::nativePath() const { + return fPathPtr; +} + +void SkPathWriter::nudge() { + if (fEmpty || !AlmostEqualUlps(fDefer[1].fX, fFirstPt.fX) + || !AlmostEqualUlps(fDefer[1].fY, fFirstPt.fY)) { + return; + } + fDefer[1] = fFirstPt; +} + +void SkPathWriter::quadTo(const SkPoint& pt1, const SkPoint& pt2) { + lineTo(); + moveTo(); + fDefer[1] = pt2; + nudge(); + fDefer[0] = fDefer[1]; +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.quadTo(%1.9g,%1.9g, %1.9g,%1.9g);\n", + pt1.fX, pt1.fY, fDefer[1].fX, fDefer[1].fY); +#endif + fPathPtr->quadTo(pt1.fX, pt1.fY, fDefer[1].fX, fDefer[1].fY); + fEmpty = false; +} + +bool SkPathWriter::someAssemblyRequired() const { + return fCloses < fMoves; +} + +bool SkPathWriter::changedSlopes(const SkPoint& pt) const { + if (fDefer[0] == fDefer[1]) { + return false; + } + SkScalar deferDx = fDefer[1].fX - fDefer[0].fX; + SkScalar deferDy = fDefer[1].fY - fDefer[0].fY; + SkScalar lineDx = pt.fX - fDefer[1].fX; + SkScalar lineDy = pt.fY - fDefer[1].fY; + return deferDx * lineDy != deferDy * lineDx; +} + +void SkPathWriter::moveTo() { + if (!fMoved) { + return; + } + fFirstPt = fDefer[0]; +#if DEBUG_PATH_CONSTRUCTION + SkDebugf("path.moveTo(%1.9g,%1.9g);\n", fDefer[0].fX, fDefer[0].fY); +#endif + fPathPtr->moveTo(fDefer[0].fX, fDefer[0].fY); + fMoved = false; + fMoves++; +} diff --git a/src/pathops/SkPathWriter.h b/src/pathops/SkPathWriter.h new file mode 100644 index 0000000000..2989b357ff --- /dev/null +++ b/src/pathops/SkPathWriter.h @@ -0,0 +1,44 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkPathWriter_DEFINED +#define SkPathWriter_DEFINED + +#include "SkPath.h" + +class SkPathWriter { +public: + SkPathWriter(SkPath& path); + void close(); + void cubicTo(const SkPoint& pt1, const SkPoint& pt2, const SkPoint& pt3); + void deferredLine(const SkPoint& pt); + void deferredMove(const SkPoint& pt); + void deferredMoveLine(const SkPoint& pt); + bool hasMove() const; + void init(); + bool isClosed() const; + void lineTo(); + const SkPath* nativePath() const; + void nudge(); + void quadTo(const SkPoint& pt1, const SkPoint& pt2); + bool someAssemblyRequired() const; + +private: + bool changedSlopes(const SkPoint& pt) const; + void moveTo(); + + SkPath* fPathPtr; + SkPoint fDefer[2]; + SkPoint fFirstPt; + int fCloses; + int fMoves; + bool fEmpty; + bool fHasMove; + bool fMoved; +}; + + +#endif /* defined(__PathOps__SkPathWriter__) */ diff --git a/src/pathops/SkQuarticRoot.cpp b/src/pathops/SkQuarticRoot.cpp new file mode 100644 index 0000000000..09a92c66a2 --- /dev/null +++ b/src/pathops/SkQuarticRoot.cpp @@ -0,0 +1,165 @@ +// from http://tog.acm.org/resources/GraphicsGems/gems/Roots3And4.c +/* + * Roots3And4.c + * + * Utility functions to find cubic and quartic roots, + * coefficients are passed like this: + * + * c[0] + c[1]*x + c[2]*x^2 + c[3]*x^3 + c[4]*x^4 = 0 + * + * The functions return the number of non-complex roots and + * put the values into the s array. + * + * Author: Jochen Schwarze (schwarze@isa.de) + * + * Jan 26, 1990 Version for Graphics Gems + * Oct 11, 1990 Fixed sign problem for negative q's in SolveQuartic + * (reported by Mark Podlipec), + * Old-style function definitions, + * IsZero() as a macro + * Nov 23, 1990 Some systems do not declare acos() and cbrt() in + * <math.h>, though the functions exist in the library. + * If large coefficients are used, EQN_EPS should be + * reduced considerably (e.g. to 1E-30), results will be + * correct but multiple roots might be reported more + * than once. + */ + +#include "SkPathOpsCubic.h" +#include "SkPathOpsQuad.h" +#include "SkQuarticRoot.h" + +int SkReducedQuarticRoots(const double t4, const double t3, const double t2, const double t1, + const double t0, const bool oneHint, double roots[4]) { +#ifdef SK_DEBUG + // create a string mathematica understands + // GDB set print repe 15 # if repeated digits is a bother + // set print elements 400 # if line doesn't fit + char str[1024]; + sk_bzero(str, sizeof(str)); + SK_SNPRINTF(str, sizeof(str), + "Solve[%1.19g x^4 + %1.19g x^3 + %1.19g x^2 + %1.19g x + %1.19g == 0, x]", + t4, t3, t2, t1, t0); + mathematica_ize(str, sizeof(str)); +#if ONE_OFF_DEBUG && ONE_OFF_DEBUG_MATHEMATICA + SkDebugf("%s\n", str); +#endif +#endif + if (approximately_zero_when_compared_to(t4, t0) // 0 is one root + && approximately_zero_when_compared_to(t4, t1) + && approximately_zero_when_compared_to(t4, t2)) { + if (approximately_zero_when_compared_to(t3, t0) + && approximately_zero_when_compared_to(t3, t1) + && approximately_zero_when_compared_to(t3, t2)) { + return SkDQuad::RootsReal(t2, t1, t0, roots); + } + if (approximately_zero_when_compared_to(t4, t3)) { + return SkDCubic::RootsReal(t3, t2, t1, t0, roots); + } + } + if ((approximately_zero_when_compared_to(t0, t1) || approximately_zero(t1)) // 0 is one root + // && approximately_zero_when_compared_to(t0, t2) + && approximately_zero_when_compared_to(t0, t3) + && approximately_zero_when_compared_to(t0, t4)) { + int num = SkDCubic::RootsReal(t4, t3, t2, t1, roots); + for (int i = 0; i < num; ++i) { + if (approximately_zero(roots[i])) { + return num; + } + } + roots[num++] = 0; + return num; + } + if (oneHint) { + SkASSERT(approximately_zero(t4 + t3 + t2 + t1 + t0)); // 1 is one root + // note that -C == A + B + D + E + int num = SkDCubic::RootsReal(t4, t4 + t3, -(t1 + t0), -t0, roots); + for (int i = 0; i < num; ++i) { + if (approximately_equal(roots[i], 1)) { + return num; + } + } + roots[num++] = 1; + return num; + } + return -1; +} + +int SkQuarticRootsReal(int firstCubicRoot, const double A, const double B, const double C, + const double D, const double E, double s[4]) { + double u, v; + /* normal form: x^4 + Ax^3 + Bx^2 + Cx + D = 0 */ + const double invA = 1 / A; + const double a = B * invA; + const double b = C * invA; + const double c = D * invA; + const double d = E * invA; + /* substitute x = y - a/4 to eliminate cubic term: + x^4 + px^2 + qx + r = 0 */ + const double a2 = a * a; + const double p = -3 * a2 / 8 + b; + const double q = a2 * a / 8 - a * b / 2 + c; + const double r = -3 * a2 * a2 / 256 + a2 * b / 16 - a * c / 4 + d; + int num; + if (approximately_zero(r)) { + /* no absolute term: y(y^3 + py + q) = 0 */ + num = SkDCubic::RootsReal(1, 0, p, q, s); + s[num++] = 0; + } else { + /* solve the resolvent cubic ... */ + double cubicRoots[3]; + int roots = SkDCubic::RootsReal(1, -p / 2, -r, r * p / 2 - q * q / 8, cubicRoots); + int index; + /* ... and take one real solution ... */ + double z; + num = 0; + int num2 = 0; + for (index = firstCubicRoot; index < roots; ++index) { + z = cubicRoots[index]; + /* ... to build two quadric equations */ + u = z * z - r; + v = 2 * z - p; + if (approximately_zero_squared(u)) { + u = 0; + } else if (u > 0) { + u = sqrt(u); + } else { + continue; + } + if (approximately_zero_squared(v)) { + v = 0; + } else if (v > 0) { + v = sqrt(v); + } else { + continue; + } + num = SkDQuad::RootsReal(1, q < 0 ? -v : v, z - u, s); + num2 = SkDQuad::RootsReal(1, q < 0 ? v : -v, z + u, s + num); + if (!((num | num2) & 1)) { + break; // prefer solutions without single quad roots + } + } + num += num2; + if (!num) { + return 0; // no valid cubic root + } + } + /* resubstitute */ + const double sub = a / 4; + for (int i = 0; i < num; ++i) { + s[i] -= sub; + } + // eliminate duplicates + for (int i = 0; i < num - 1; ++i) { + for (int j = i + 1; j < num; ) { + if (AlmostEqualUlps(s[i], s[j])) { + if (j < --num) { + s[j] = s[num]; + } + } else { + ++j; + } + } + } + return num; +} diff --git a/src/pathops/SkQuarticRoot.h b/src/pathops/SkQuarticRoot.h new file mode 100644 index 0000000000..6ce08671e2 --- /dev/null +++ b/src/pathops/SkQuarticRoot.h @@ -0,0 +1,16 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkDQuarticRoot_DEFINED +#define SkDQuarticRoot_DEFINED + +int SkReducedQuarticRoots(const double t4, const double t3, const double t2, const double t1, + const double t0, const bool oneHint, double s[4]); + +int SkQuarticRootsReal(int firstCubicRoot, const double A, const double B, const double C, + const double D, const double E, double s[4]); + +#endif diff --git a/src/pathops/SkReduceOrder.cpp b/src/pathops/SkReduceOrder.cpp new file mode 100644 index 0000000000..14da4cc1f1 --- /dev/null +++ b/src/pathops/SkReduceOrder.cpp @@ -0,0 +1,450 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#include "SkReduceOrder.h" + +int SkReduceOrder::reduce(const SkDLine& line) { + fLine[0] = line[0]; + int different = line[0] != line[1]; + fLine[1] = line[different]; + return 1 + different; +} + +static double interp_quad_coords(double a, double b, double c, double t) { + double ab = SkDInterp(a, b, t); + double bc = SkDInterp(b, c, t); + return SkDInterp(ab, bc, t); +} + +static int coincident_line(const SkDQuad& quad, SkDQuad& reduction) { + reduction[0] = reduction[1] = quad[0]; + return 1; +} + +static int reductionLineCount(const SkDQuad& reduction) { + return 1 + !reduction[0].approximatelyEqual(reduction[1]); +} + +static int vertical_line(const SkDQuad& quad, SkReduceOrder::Style reduceStyle, + SkDQuad& reduction) { + double tValue; + reduction[0] = quad[0]; + reduction[1] = quad[2]; + if (reduceStyle == SkReduceOrder::kFill_Style) { + return reductionLineCount(reduction); + } + int smaller = reduction[1].fY > reduction[0].fY; + int larger = smaller ^ 1; + if (SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValue)) { + double yExtrema = interp_quad_coords(quad[0].fY, quad[1].fY, quad[2].fY, tValue); + if (reduction[smaller].fY > yExtrema) { + reduction[smaller].fY = yExtrema; + } else if (reduction[larger].fY < yExtrema) { + reduction[larger].fY = yExtrema; + } + } + return reductionLineCount(reduction); +} + +static int horizontal_line(const SkDQuad& quad, SkReduceOrder::Style reduceStyle, + SkDQuad& reduction) { + double tValue; + reduction[0] = quad[0]; + reduction[1] = quad[2]; + if (reduceStyle == SkReduceOrder::kFill_Style) { + return reductionLineCount(reduction); + } + int smaller = reduction[1].fX > reduction[0].fX; + int larger = smaller ^ 1; + if (SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, &tValue)) { + double xExtrema = interp_quad_coords(quad[0].fX, quad[1].fX, quad[2].fX, tValue); + if (reduction[smaller].fX > xExtrema) { + reduction[smaller].fX = xExtrema; + } else if (reduction[larger].fX < xExtrema) { + reduction[larger].fX = xExtrema; + } + } + return reductionLineCount(reduction); +} + +static int check_linear(const SkDQuad& quad, SkReduceOrder::Style reduceStyle, + int minX, int maxX, int minY, int maxY, SkDQuad& reduction) { + int startIndex = 0; + int endIndex = 2; + while (quad[startIndex].approximatelyEqual(quad[endIndex])) { + --endIndex; + if (endIndex == 0) { + SkDebugf("%s shouldn't get here if all four points are about equal", __FUNCTION__); + SkASSERT(0); + } + } + if (!quad.isLinear(startIndex, endIndex)) { + return 0; + } + // four are colinear: return line formed by outside + reduction[0] = quad[0]; + reduction[1] = quad[2]; + if (reduceStyle == SkReduceOrder::kFill_Style) { + return reductionLineCount(reduction); + } + int sameSide; + bool useX = quad[maxX].fX - quad[minX].fX >= quad[maxY].fY - quad[minY].fY; + if (useX) { + sameSide = SkDSign(quad[0].fX - quad[1].fX) + SkDSign(quad[2].fX - quad[1].fX); + } else { + sameSide = SkDSign(quad[0].fY - quad[1].fY) + SkDSign(quad[2].fY - quad[1].fY); + } + if ((sameSide & 3) != 2) { + return reductionLineCount(reduction); + } + double tValue; + int root; + if (useX) { + root = SkDQuad::FindExtrema(quad[0].fX, quad[1].fX, quad[2].fX, &tValue); + } else { + root = SkDQuad::FindExtrema(quad[0].fY, quad[1].fY, quad[2].fY, &tValue); + } + if (root) { + SkDPoint extrema; + extrema.fX = interp_quad_coords(quad[0].fX, quad[1].fX, quad[2].fX, tValue); + extrema.fY = interp_quad_coords(quad[0].fY, quad[1].fY, quad[2].fY, tValue); + // sameSide > 0 means mid is smaller than either [0] or [2], so replace smaller + int replace; + if (useX) { + if ((extrema.fX < quad[0].fX) ^ (extrema.fX < quad[2].fX)) { + return reductionLineCount(reduction); + } + replace = ((extrema.fX < quad[0].fX) | (extrema.fX < quad[2].fX)) + ^ (quad[0].fX < quad[2].fX); + } else { + if ((extrema.fY < quad[0].fY) ^ (extrema.fY < quad[2].fY)) { + return reductionLineCount(reduction); + } + replace = ((extrema.fY < quad[0].fY) | (extrema.fY < quad[2].fY)) + ^ (quad[0].fY < quad[2].fY); + } + reduction[replace] = extrema; + } + return reductionLineCount(reduction); +} + +// reduce to a quadratic or smaller +// look for identical points +// look for all four points in a line + // note that three points in a line doesn't simplify a cubic +// look for approximation with single quadratic + // save approximation with multiple quadratics for later +int SkReduceOrder::reduce(const SkDQuad& quad, Style reduceStyle) { + int index, minX, maxX, minY, maxY; + int minXSet, minYSet; + minX = maxX = minY = maxY = 0; + minXSet = minYSet = 0; + for (index = 1; index < 3; ++index) { + if (quad[minX].fX > quad[index].fX) { + minX = index; + } + if (quad[minY].fY > quad[index].fY) { + minY = index; + } + if (quad[maxX].fX < quad[index].fX) { + maxX = index; + } + if (quad[maxY].fY < quad[index].fY) { + maxY = index; + } + } + for (index = 0; index < 3; ++index) { + if (AlmostEqualUlps(quad[index].fX, quad[minX].fX)) { + minXSet |= 1 << index; + } + if (AlmostEqualUlps(quad[index].fY, quad[minY].fY)) { + minYSet |= 1 << index; + } + } + if (minXSet == 0x7) { // test for vertical line + if (minYSet == 0x7) { // return 1 if all four are coincident + return coincident_line(quad, fQuad); + } + return vertical_line(quad, reduceStyle, fQuad); + } + if (minYSet == 0xF) { // test for horizontal line + return horizontal_line(quad, reduceStyle, fQuad); + } + int result = check_linear(quad, reduceStyle, minX, maxX, minY, maxY, fQuad); + if (result) { + return result; + } + fQuad = quad; + return 3; +} + +//////////////////////////////////////////////////////////////////////////////////// + +static double interp_cubic_coords(const double* src, double t) { + double ab = SkDInterp(src[0], src[2], t); + double bc = SkDInterp(src[2], src[4], t); + double cd = SkDInterp(src[4], src[6], t); + double abc = SkDInterp(ab, bc, t); + double bcd = SkDInterp(bc, cd, t); + return SkDInterp(abc, bcd, t); +} + +static int coincident_line(const SkDCubic& cubic, SkDCubic& reduction) { + reduction[0] = reduction[1] = cubic[0]; + return 1; +} + +static int reductionLineCount(const SkDCubic& reduction) { + return 1 + !reduction[0].approximatelyEqual(reduction[1]); +} + +static int vertical_line(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle, + SkDCubic& reduction) { + double tValues[2]; + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + if (reduceStyle == SkReduceOrder::kFill_Style) { + return reductionLineCount(reduction); + } + int smaller = reduction[1].fY > reduction[0].fY; + int larger = smaller ^ 1; + int roots = SkDCubic::FindExtrema(cubic[0].fY, cubic[1].fY, cubic[2].fY, cubic[3].fY, tValues); + for (int index = 0; index < roots; ++index) { + double yExtrema = interp_cubic_coords(&cubic[0].fY, tValues[index]); + if (reduction[smaller].fY > yExtrema) { + reduction[smaller].fY = yExtrema; + continue; + } + if (reduction[larger].fY < yExtrema) { + reduction[larger].fY = yExtrema; + } + } + return reductionLineCount(reduction); +} + +static int horizontal_line(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle, + SkDCubic& reduction) { + double tValues[2]; + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + if (reduceStyle == SkReduceOrder::kFill_Style) { + return reductionLineCount(reduction); + } + int smaller = reduction[1].fX > reduction[0].fX; + int larger = smaller ^ 1; + int roots = SkDCubic::FindExtrema(cubic[0].fX, cubic[1].fX, cubic[2].fX, cubic[3].fX, tValues); + for (int index = 0; index < roots; ++index) { + double xExtrema = interp_cubic_coords(&cubic[0].fX, tValues[index]); + if (reduction[smaller].fX > xExtrema) { + reduction[smaller].fX = xExtrema; + continue; + } + if (reduction[larger].fX < xExtrema) { + reduction[larger].fX = xExtrema; + } + } + return reductionLineCount(reduction); +} + +// check to see if it is a quadratic or a line +static int check_quadratic(const SkDCubic& cubic, SkDCubic& reduction) { + double dx10 = cubic[1].fX - cubic[0].fX; + double dx23 = cubic[2].fX - cubic[3].fX; + double midX = cubic[0].fX + dx10 * 3 / 2; + if (!AlmostEqualUlps(midX - cubic[3].fX, dx23 * 3 / 2)) { + return 0; + } + double dy10 = cubic[1].fY - cubic[0].fY; + double dy23 = cubic[2].fY - cubic[3].fY; + double midY = cubic[0].fY + dy10 * 3 / 2; + if (!AlmostEqualUlps(midY - cubic[3].fY, dy23 * 3 / 2)) { + return 0; + } + reduction[0] = cubic[0]; + reduction[1].fX = midX; + reduction[1].fY = midY; + reduction[2] = cubic[3]; + return 3; +} + +static int check_linear(const SkDCubic& cubic, SkReduceOrder::Style reduceStyle, + int minX, int maxX, int minY, int maxY, SkDCubic& reduction) { + int startIndex = 0; + int endIndex = 3; + while (cubic[startIndex].approximatelyEqual(cubic[endIndex])) { + --endIndex; + if (endIndex == 0) { + SkDebugf("%s shouldn't get here if all four points are about equal\n", __FUNCTION__); + SkASSERT(0); + } + } + if (!cubic.isLinear(startIndex, endIndex)) { + return 0; + } + // four are colinear: return line formed by outside + reduction[0] = cubic[0]; + reduction[1] = cubic[3]; + if (reduceStyle == SkReduceOrder::kFill_Style) { + return reductionLineCount(reduction); + } + int sameSide1; + int sameSide2; + bool useX = cubic[maxX].fX - cubic[minX].fX >= cubic[maxY].fY - cubic[minY].fY; + if (useX) { + sameSide1 = SkDSign(cubic[0].fX - cubic[1].fX) + SkDSign(cubic[3].fX - cubic[1].fX); + sameSide2 = SkDSign(cubic[0].fX - cubic[2].fX) + SkDSign(cubic[3].fX - cubic[2].fX); + } else { + sameSide1 = SkDSign(cubic[0].fY - cubic[1].fY) + SkDSign(cubic[3].fY - cubic[1].fY); + sameSide2 = SkDSign(cubic[0].fY - cubic[2].fY) + SkDSign(cubic[3].fY - cubic[2].fY); + } + if (sameSide1 == sameSide2 && (sameSide1 & 3) != 2) { + return reductionLineCount(reduction); + } + double tValues[2]; + int roots; + if (useX) { + roots = SkDCubic::FindExtrema(cubic[0].fX, cubic[1].fX, cubic[2].fX, cubic[3].fX, tValues); + } else { + roots = SkDCubic::FindExtrema(cubic[0].fY, cubic[1].fY, cubic[2].fY, cubic[3].fY, tValues); + } + for (int index = 0; index < roots; ++index) { + SkDPoint extrema; + extrema.fX = interp_cubic_coords(&cubic[0].fX, tValues[index]); + extrema.fY = interp_cubic_coords(&cubic[0].fY, tValues[index]); + // sameSide > 0 means mid is smaller than either [0] or [3], so replace smaller + int replace; + if (useX) { + if ((extrema.fX < cubic[0].fX) ^ (extrema.fX < cubic[3].fX)) { + continue; + } + replace = ((extrema.fX < cubic[0].fX) | (extrema.fX < cubic[3].fX)) + ^ (cubic[0].fX < cubic[3].fX); + } else { + if ((extrema.fY < cubic[0].fY) ^ (extrema.fY < cubic[3].fY)) { + continue; + } + replace = ((extrema.fY < cubic[0].fY) | (extrema.fY < cubic[3].fY)) + ^ (cubic[0].fY < cubic[3].fY); + } + reduction[replace] = extrema; + } + return reductionLineCount(reduction); +} + +/* food for thought: +http://objectmix.com/graphics/132906-fast-precision-driven-cubic-quadratic-piecewise-degree-reduction-algos-2-a.html + +Given points c1, c2, c3 and c4 of a cubic Bezier, the points of the +corresponding quadratic Bezier are (given in convex combinations of +points): + +q1 = (11/13)c1 + (3/13)c2 -(3/13)c3 + (2/13)c4 +q2 = -c1 + (3/2)c2 + (3/2)c3 - c4 +q3 = (2/13)c1 - (3/13)c2 + (3/13)c3 + (11/13)c4 + +Of course, this curve does not interpolate the end-points, but it would +be interesting to see the behaviour of such a curve in an applet. + +-- +Kalle Rutanen +http://kaba.hilvi.org + +*/ + +// reduce to a quadratic or smaller +// look for identical points +// look for all four points in a line + // note that three points in a line doesn't simplify a cubic +// look for approximation with single quadratic + // save approximation with multiple quadratics for later +int SkReduceOrder::reduce(const SkDCubic& cubic, Quadratics allowQuadratics, + Style reduceStyle) { + int index, minX, maxX, minY, maxY; + int minXSet, minYSet; + minX = maxX = minY = maxY = 0; + minXSet = minYSet = 0; + for (index = 1; index < 4; ++index) { + if (cubic[minX].fX > cubic[index].fX) { + minX = index; + } + if (cubic[minY].fY > cubic[index].fY) { + minY = index; + } + if (cubic[maxX].fX < cubic[index].fX) { + maxX = index; + } + if (cubic[maxY].fY < cubic[index].fY) { + maxY = index; + } + } + for (index = 0; index < 4; ++index) { + double cx = cubic[index].fX; + double cy = cubic[index].fY; + double denom = SkTMax(fabs(cx), SkTMax(fabs(cy), + SkTMax(fabs(cubic[minX].fX), fabs(cubic[minY].fY)))); + if (denom == 0) { + minXSet |= 1 << index; + minYSet |= 1 << index; + continue; + } + double inv = 1 / denom; + if (approximately_equal_half(cx * inv, cubic[minX].fX * inv)) { + minXSet |= 1 << index; + } + if (approximately_equal_half(cy * inv, cubic[minY].fY * inv)) { + minYSet |= 1 << index; + } + } + if (minXSet == 0xF) { // test for vertical line + if (minYSet == 0xF) { // return 1 if all four are coincident + return coincident_line(cubic, fCubic); + } + return vertical_line(cubic, reduceStyle, fCubic); + } + if (minYSet == 0xF) { // test for horizontal line + return horizontal_line(cubic, reduceStyle, fCubic); + } + int result = check_linear(cubic, reduceStyle, minX, maxX, minY, maxY, fCubic); + if (result) { + return result; + } + if (allowQuadratics == SkReduceOrder::kAllow_Quadratics + && (result = check_quadratic(cubic, fCubic))) { + return result; + } + fCubic = cubic; + return 4; +} + +SkPath::Verb SkReduceOrder::Quad(const SkPoint a[3], SkTDArray<SkPoint>* reducePts) { + SkDQuad quad; + quad.set(a); + SkReduceOrder reducer; + int order = reducer.reduce(quad, kFill_Style); + if (order == 2) { // quad became line + for (int index = 0; index < order; ++index) { + SkPoint* pt = reducePts->append(); + pt->fX = SkDoubleToScalar(reducer.fLine[index].fX); + pt->fY = SkDoubleToScalar(reducer.fLine[index].fY); + } + } + return (SkPath::Verb) (order - 1); +} + +SkPath::Verb SkReduceOrder::Cubic(const SkPoint a[4], SkTDArray<SkPoint>* reducePts) { + SkDCubic cubic; + cubic.set(a); + SkReduceOrder reducer; + int order = reducer.reduce(cubic, kAllow_Quadratics, kFill_Style); + if (order == 2 || order == 3) { // cubic became line or quad + for (int index = 0; index < order; ++index) { + SkPoint* pt = reducePts->append(); + pt->fX = SkDoubleToScalar(reducer.fQuad[index].fX); + pt->fY = SkDoubleToScalar(reducer.fQuad[index].fY); + } + } + return (SkPath::Verb) (order - 1); +} diff --git a/src/pathops/SkReduceOrder.h b/src/pathops/SkReduceOrder.h new file mode 100644 index 0000000000..62b4af9367 --- /dev/null +++ b/src/pathops/SkReduceOrder.h @@ -0,0 +1,38 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef SkReduceOrder_DEFINED +#define SkReduceOrder_DEFINED + +#include "SkPath.h" +#include "SkPathOpsCubic.h" +#include "SkPathOpsLine.h" +#include "SkPathOpsQuad.h" +#include "SkTDArray.h" + +union SkReduceOrder { + enum Quadratics { + kNo_Quadratics, + kAllow_Quadratics + }; + enum Style { + kStroke_Style, + kFill_Style + }; + + int reduce(const SkDCubic& cubic, Quadratics, Style); + int reduce(const SkDLine& line); + int reduce(const SkDQuad& quad, Style); + + static SkPath::Verb Cubic(const SkPoint pts[4], SkTDArray<SkPoint>* reducePts); + static SkPath::Verb Quad(const SkPoint pts[3], SkTDArray<SkPoint>* reducePts); + + SkDLine fLine; + SkDQuad fQuad; + SkDCubic fCubic; +}; + +#endif diff --git a/src/pathops/TSearch.h b/src/pathops/TSearch.h new file mode 100644 index 0000000000..2550448c9c --- /dev/null +++ b/src/pathops/TSearch.h @@ -0,0 +1,101 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ +#ifndef TSearch_DEFINED +#define TSearch_DEFINED + +// FIXME: Move this templated version into SKTSearch.h + +template <typename T> +static T* QSort_Partition(T* left, T* right, T* pivot) +{ + T pivotValue = *pivot; + SkTSwap(*pivot, *right); + T* newPivot = left; + while (left < right) { + if (*left < pivotValue) { + SkTSwap(*left, *newPivot); + newPivot += 1; + } + left += 1; + } + SkTSwap(*newPivot, *right); + return newPivot; +} + +template <typename T> +void QSort(T* left, T* right) +{ + if (left >= right) { + return; + } + T* pivot = left + ((right - left) >> 1); + pivot = QSort_Partition(left, right, pivot); + QSort(left, pivot - 1); + QSort(pivot + 1, right); +} + +template <typename T> +static T** QSort_Partition(T** left, T** right, T** pivot) +{ + T* pivotValue = *pivot; + SkTSwap(*pivot, *right); + T** newPivot = left; + while (left < right) { + if (**left < *pivotValue) { + SkTSwap(*left, *newPivot); + newPivot += 1; + } + left += 1; + } + SkTSwap(*newPivot, *right); + return newPivot; +} + +template <typename T> +void QSort(T** left, T** right) +{ + if (left >= right) { + return; + } + T** pivot = left + ((right - left) >> 1); + pivot = QSort_Partition(left, right, pivot); + QSort(left, pivot - 1); + QSort(pivot + 1, right); +} + +template <typename S, typename T> +static T* QSort_Partition(S& context, T* left, T* right, T* pivot, + bool (*lessThan)(S&, const T, const T)) +{ + T pivotValue = *pivot; + SkTSwap(*pivot, *right); + T* newPivot = left; + while (left < right) { + if (lessThan(context, *left, pivotValue)) { + SkTSwap(*left, *newPivot); + newPivot += 1; + } + left += 1; + } + SkTSwap(*newPivot, *right); + return newPivot; +} + +template <typename S, typename T> +void QSort(S& context, T* left, T* right, + bool (*lessThan)(S& , const T, const T)) +{ + if (left >= right) { + return; + } + T* pivot = left + ((right - left) >> 1); + pivot = QSort_Partition(context, left, right, pivot, lessThan); + QSort(context, left, pivot - 1, lessThan); + QSort(context, pivot + 1, right, lessThan); +} + +#endif diff --git a/src/pathops/main.cpp b/src/pathops/main.cpp new file mode 100644 index 0000000000..65479c2528 --- /dev/null +++ b/src/pathops/main.cpp @@ -0,0 +1,16 @@ +/* + * Copyright 2012 Google Inc. + * + * Use of this source code is governed by a BSD-style license that can be + * found in the LICENSE file. + */ + +#include "Test.h" + +using namespace skiatest; + +int main(int /*argc*/, char** /*argv*/) { + Test tester; + tester.run(); + return 0; +} |