* found in the LICENSE file.
*/
+#include "CurveIntersection.h"
#include "LineIntersection.h"
#include "SkPath.h"
#include "SkRect.h"
#include "SkTDArray.h"
#include "TSearch.h"
-static int lineIntersect(const SkPoint a[2], const SkPoint b[2],
+static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
double aRange[2], double bRange[2]) {
_Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
_Line bLine = {{b[0].fX, b[0].fY}, {b[1].fX, b[1].fY}};
return intersect(aLine, bLine, aRange, bRange);
}
-static int lineIntersect(const SkPoint a[2], SkScalar y, double aRange[2]) {
+static int LineIntersect(const SkPoint a[2], SkScalar y, double aRange[2]) {
_Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
return horizontalIntersect(aLine, y, aRange);
}
+static SkScalar LineYAtT(const SkPoint a[2], double t) {
+ _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
+ double y;
+ xy_at_t(aLine, t, *(double*) 0, y);
+ return SkDoubleToScalar(y);
+}
+
+static void LineSubDivide(const SkPoint a[2], double startT, double endT,
+ SkPoint sub[2]) {
+ _Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
+ _Line dst;
+ sub_divide(aLine, startT, endT, dst);
+ sub[0].fX = SkDoubleToScalar(dst[0].x);
+ sub[0].fY = SkDoubleToScalar(dst[0].y);
+ sub[1].fX = SkDoubleToScalar(dst[1].x);
+ sub[1].fY = SkDoubleToScalar(dst[1].y);
+}
+
+
// functions
void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray);
void simplify(const SkPath& path, bool asFill, SkPath& simple);
}
}
+struct OutEdge {
+
+ SkTDArray<SkPoint> fPts;
+ SkTDArray<uint8_t> fVerbs;
+};
+
+class OutEdgeBuilder {
+public:
+ void addLine(SkPoint pts[2]) {
+ ;
+ OutEdge* edge;
+
+ edge = fEdges.append();
+
+ if (empty) {
+ *edge->fPts.append() = pts[0];
+ }
+ *edge->fPts.append() = pts[1];
+ }
+
+ SkTArray<OutEdge> fEdges;
+};
+
// Bounds, unlike Rect, does not consider a vertical line to be empty.
struct Bounds : public SkRect {
static bool Intersects(const Bounds& a, const Bounds& b) {
}
};
-struct Edge;
-
struct Intercepts {
SkTDArray<double> fTs;
};
-struct Edge {
- bool operator<(const Edge& rh) const {
+struct InEdge {
+ bool operator<(const InEdge& rh) const {
return fBounds.fTop == rh.fBounds.fTop
? fBounds.fLeft < rh.fBounds.fLeft
: fBounds.fTop < rh.fBounds.fTop;
}
}
- bool cached(const Edge* edge) {
+ bool cached(const InEdge* edge) {
// FIXME: in the pathological case where there is a ton of edges, binary search?
size_t count = fCached.count();
for (size_t index = 0; index < count; ++index) {
++ptPtr;
}
fIntercepts.push_back_n(1);
- fWinding = winding;
+ if ((fWinding = winding) < 0) { // reverse verbs, pts, if bottom to top
+ size_t index;
+ size_t last = fPts.count() - 1;
+ for (index = 0; index < last; ++index, --last) {
+ SkTSwap<SkPoint>(fPts[index], fPts[last]);
+ }
+ last = fVerbs.count() - 1;
+ for (index = 0; index < last; ++index, --last) {
+ SkTSwap<uint8_t>(fVerbs[index], fVerbs[last]);
+ }
+ }
+ fContainsIntercepts = false;
}
// temporary data : move this to a separate struct?
- SkTDArray<const Edge*> fCached; // list of edges already intercepted
+ SkTDArray<const InEdge*> fCached; // list of edges already intercepted
SkTArray<Intercepts> fIntercepts; // one per verb
-
// persistent data
SkTDArray<SkPoint> fPts;
SkTDArray<uint8_t> fVerbs;
Bounds fBounds;
signed char fWinding;
+ bool fContainsIntercepts;
};
-class EdgeBuilder {
+class InEdgeBuilder {
public:
-EdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<Edge>& edges)
+InEdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<InEdge>& edges)
: fPath(path)
, fCurrentEdge(NULL)
, fEdges(edges)
}
int last = count - 1;
return fPts[0].fY == fPts[last].fY
- ? fPts[0].fX == fPts[last].fX ? 0 : fPts[0].fX > fPts[last].fX
- ? 1 : -1 : fPts[0].fY > fPts[last].fY ? 1 : -1;
+ ? fPts[0].fX == fPts[last].fX ? 0 : fPts[0].fX < fPts[last].fX
+ ? 1 : -1 : fPts[0].fY < fPts[last].fY ? 1 : -1;
}
bool isHorizontal() {
private:
const SkPath& fPath;
- Edge* fCurrentEdge;
- SkTArray<Edge>& fEdges;
+ InEdge* fCurrentEdge;
+ SkTArray<InEdge>& fEdges;
SkPoint fPts[4];
SkPath::Verb fVerb;
int fPtCount;
bool fIgnoreHorizontal;
};
-class WorkEdge {
-public:
- WorkEdge(const Edge* edge) {
- fVerbStart = edge->fVerbs.begin();
- if ((fWinding = edge->fWinding) > 0) {
- fPts = edge->fPts.begin();
- fVerb = fVerbStart;
- fVerbEnd = edge->fVerbs.end();
- } else {
- fPts = edge->fPts.end();
- fVerb = edge->fVerbs.end();
- fVerbEnd = fVerbStart;
- next();
- }
- }
-
+struct WorkEdge {
SkScalar bottom() const {
- return fPts[fWinding > 0 ? verb() : 0].fY;
+ return fPts[verb()].fY;
}
- bool next() {
- if (fWinding > 0) {
- fPts += *fVerb;
- return ++fVerb != fVerbEnd;
- } else {
- if (fVerb == fVerbEnd) {
- return false;
- }
- fPts -= *--fVerb;
- return true;
- }
+ void init(const InEdge* edge) {
+ fEdge = edge;
+ fPts = edge->fPts.begin();
+ fVerb = edge->fVerbs.begin();
}
- const SkPoint* points() const {
- return fPts;
+ bool next() {
+ SkASSERT(fVerb < fEdge->fVerbs.end());
+ fPts += *fVerb++;
+ return fVerb != fEdge->fVerbs.end();
}
SkPath::Verb verb() const {
}
int verbIndex() const {
- return fVerb - fVerbStart;
+ return fVerb - fEdge->fVerbs.begin();
+ }
+
+ int winding() const {
+ return fEdge->fWinding;
}
-protected:
+ const InEdge* fEdge;
const SkPoint* fPts;
const uint8_t* fVerb;
- const uint8_t* fVerbEnd;
- const uint8_t* fVerbStart;
- int8_t fWinding;
};
+// always constructed with SkTDArray because new edges are inserted
+// this may be a inappropriate optimization, suggesting that a separate array of
+// ActiveEdge* may be faster to insert and search
struct ActiveEdge {
- void init(const Edge* test) {
- fEdge = test;
- if (!fEdge->fIntercepts.count()) {
- fBounds = test->fBounds;
- fPtStart = 0;
- fPtEnd = test->fPts.count();
- fVerbStart = 0;
- fVerbEnd = test->fVerbs.count();
- fTStart = 0;
- fTEnd = SK_Scalar1;
- } else {
- // FIXME: initialize from intercepts
+ void init(const InEdge* edge) {
+ fWorkEdge.init(edge);
+ initT();
+ }
+
+ void initT() {
+ fTs = &fWorkEdge.fEdge->fIntercepts[fWorkEdge.verbIndex()].fTs;
+ fTIndex = 0;
+ }
+
+ bool nextT() {
+ SkASSERT(fTIndex <= fTs->count());
+ return ++fTIndex == fTs->count() + 1;
+ }
+
+ bool next() {
+ bool result = fWorkEdge.next();
+ initT();
+ return result;
+ }
+ double t() {
+ if (fTIndex == 0) {
+ return 0;
+ }
+ if (fTIndex > fTs->count()) {
+ return 1;
}
+ return (*fTs)[fTIndex - 1];
}
- const Edge* fEdge;
- SkRect fBounds;
- int fPtStart;
- int fPtEnd;
- int fVerbStart;
- int fVerbEnd;
- SkScalar fTStart;
- SkScalar fTEnd;
+ WorkEdge fWorkEdge;
+ const SkTDArray<double>* fTs;
+ int fTIndex;
};
+static void addToActive(SkTDArray<ActiveEdge>& activeEdges, const InEdge* edge) {
+ // FIXME: in the pathological case where there is a ton of intercepts, binary search?
+ size_t count = activeEdges.count();
+ for (size_t index = 0; index < count; ++index) {
+ if (edge < activeEdges[index].fWorkEdge.fEdge) {
+ ActiveEdge* active = activeEdges.insert(index);
+ active->init(edge);
+ return;
+ }
+ if (edge == activeEdges[index].fWorkEdge.fEdge) {
+ return;
+ }
+ }
+ ActiveEdge* active = activeEdges.append();
+ active->init(edge);
+}
+
void simplify(const SkPath& path, bool asFill, SkPath& simple) {
// turn path into list of edges increasing in y
// if an edge is a quad or a cubic with a y extrema, note it, but leave it unbroken
// once we have a list, sort it, then walk the list (walk edges twice that have y extrema's on top)
// and detect crossings -- look for raw bounds that cross over, then tight bounds that cross
- SkTArray<Edge> edges;
- EdgeBuilder builder(path, asFill, edges);
+ SkTArray<InEdge> edges;
+ InEdgeBuilder builder(path, asFill, edges);
size_t edgeCount = edges.count();
simple.reset();
if (edgeCount == 0) {
}
// returns 1 for evenodd, -1 for winding, regardless of inverse-ness
int windingMask = (path.getFillType() & 1) ? 1 : -1;
- SkTDArray<Edge*> edgeList;
+ SkTDArray<InEdge*> edgeList;
for (size_t index = 0; index < edgeCount; ++index) {
*edgeList.append() = &edges[index];
}
- Edge edgeSentinel;
+ InEdge edgeSentinel;
edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
*edgeList.append() = &edgeSentinel;
++edgeCount;
- QSort<Edge>(edgeList.begin(), edgeCount);
- Edge** currentPtr = edgeList.begin();
- Edge* current = *currentPtr;
+ QSort<InEdge>(edgeList.begin(), edgeCount);
+ InEdge** currentPtr = edgeList.begin();
+ InEdge* current = *currentPtr;
SkScalar y = current->fBounds.fTop;
SkScalar bottom = current->fBounds.fBottom;
// walk the sorted edges from top to bottom, computing accumulated winding
+ SkTDArray<ActiveEdge> activeEdges;
+ OutEdgeBuilder outBuilder;
do {
// find the list of edges that cross y
- Edge** lastPtr = currentPtr; // find the edge below the bottom of the first set
- Edge* last = *lastPtr;
+ InEdge** lastPtr = currentPtr; // find the edge below the bottom of the first set
+ InEdge* last = *lastPtr;
while (lastPtr != edgeList.end()) {
if (bottom <= last->fBounds.fTop) {
break;
} else if (bottom > last->fBounds.fBottom) {
bottom = last->fBounds.fBottom;
}
+ addToActive(activeEdges, last);
last = *++lastPtr;
}
if (asFill && lastPtr - currentPtr <= 1) {
SkASSERT(0);
return;
}
+
// find any intersections in the range of active edges
- Edge** testPtr = currentPtr;
- Edge* test = *testPtr;
+ InEdge** testPtr = currentPtr;
+ InEdge* test = *testPtr;
while (testPtr != lastPtr) {
if (test->fBounds.fBottom > bottom) {
- WorkEdge wt(test);
+ WorkEdge wt;
+ wt.init(test);
do {
- // FIXME: add all combinations of curve types
+ // FIXME: add all curve types
+ // OPTIMIZATION: if bottom intersection does not change
+ // the winding on either side of the split, don't intersect
if (wt.verb() == SkPath::kLine_Verb) {
double wtTs[2];
- int pts = lineIntersect(wt.points(), bottom, wtTs);
+ int pts = LineIntersect(wt.fPts, bottom, wtTs);
if (pts) {
test->add(wtTs, pts, wt.verbIndex());
}
testPtr = currentPtr;
test = *testPtr;
while (testPtr != lastPtr - 1) {
- Edge* next = *++testPtr;
- // OPTIMIZATION: if test and next is inside the winding of outer
- // edges such that intersecting them is irrelevent, skip them.
+ InEdge* next = *++testPtr;
if (!test->cached(next)
&& Bounds::Intersects(test->fBounds, next->fBounds)) {
- WorkEdge wt(test);
- WorkEdge wn(next);
+ WorkEdge wt, wn;
+ wt.init(test);
+ wn.init(next);
do {
// FIXME: add all combinations of curve types
- if (wt.verb() == SkPath::kLine_Verb && wn.verb() == SkPath::kLine_Verb) {
+ if (wt.verb() == SkPath::kLine_Verb
+ && wn.verb() == SkPath::kLine_Verb) {
double wtTs[2], wnTs[2];
- int pts = lineIntersect(wt.points(), wn.points(), wtTs, wnTs);
+ int pts = LineIntersect(wt.fPts, wn.fPts, wtTs, wnTs);
if (pts) {
test->add(wtTs, pts, wt.verbIndex());
+ test->fContainsIntercepts = true;
next->add(wnTs, pts, wn.verbIndex());
+ next->fContainsIntercepts = true;
}
}
} while (wt.bottom() <= wn.bottom() ? wt.next() : wn.next());
}
test = next;
}
+
+ // compute bottom taking into account any intersected edges
+ ActiveEdge* activePtr = activeEdges.begin() - 1;
+ ActiveEdge* lastActive = activeEdges.end();
+ while (++activePtr != lastActive) {
+ const InEdge* test = activePtr->fWorkEdge.fEdge;
+ if (!test->fContainsIntercepts) {
+ continue;
+ }
+ WorkEdge wt;
+ wt.init(test);
+ do {
+ // FIXME: add all curve types
+ const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()];
+ const SkTDArray<double>& fTs = intercepts.fTs;
+ size_t count = fTs.count();
+ for (size_t index = 0; index < count; ++index) {
+ if (wt.verb() == SkPath::kLine_Verb) {
+ SkScalar yIntercept = LineYAtT(wt.fPts, fTs[index]);
+ if (bottom > yIntercept) {
+ bottom = yIntercept;
+ }
+ }
+ }
+ } while (wt.next());
+ }
+
// stitch edge and t range that satisfies operation
int winding = 0;
- testPtr = currentPtr;
- test = *testPtr;
- while (testPtr != lastPtr - 1) {
+ activePtr = activeEdges.begin() - 1;
+ lastActive = activeEdges.end();
+ SkDebugf("%s y=%g bottom=%g\n", __FUNCTION__, y, bottom);
+ while (++activePtr != lastActive) {
+ const WorkEdge& wt = activePtr->fWorkEdge;
int lastWinding = winding;
- winding += test->fWinding;
- if ((lastWinding & windingMask) == 0 || (winding & windingMask) == 0) {
- // append pts, verbs, in front of or behind output
- // a verb may have one or more inter-T value, but only break
- // curve if curve at t changes winding inclusion
- ;
+ winding += wt.winding();
+ if (!(lastWinding & windingMask) && !(winding & windingMask)) {
+ continue;
}
- test = *++testPtr;
+ do {
+ double currentT = activePtr->t();
+ const SkPoint* points = wt.fPts;
+ bool last;
+ do {
+ last = activePtr->nextT();
+ double nextT = activePtr->t();
+ // FIXME: add all combinations of curve types
+ if (wt.verb() == SkPath::kLine_Verb) {
+ SkPoint clippedPts[2];
+ const SkPoint* clipped;
+ if (currentT * nextT != 0 || currentT + nextT != 1) {
+ LineSubDivide(points, currentT, nextT, clippedPts);
+ clipped = clippedPts;
+ } else {
+ clipped = points;
+ }
+ SkDebugf("%s line %g,%g %g,%g\n", __FUNCTION__,
+ clipped[0].fX, clipped[0].fY,
+ clipped[1].fX, clipped[1].fY);
+ outBuilder->addLine(clipped);
+ if (clipped[1].fY >= bottom) {
+ goto nextEdge;
+ }
+ }
+ currentT = nextT;
+ } while (!last);
+ } while (activePtr->next());
+ nextEdge:
+ ;
}
+
y = bottom;
- while ((*currentPtr)->fBounds.fBottom >= y) {
+ while ((*currentPtr)->fBounds.fBottom <= y) {
++currentPtr;
}
} while (*currentPtr != &edgeSentinel);
+
// assemble output path from string of pts, verbs
;
}