blob: 4f4fc9b94d1f270254c5d920f1c70b193a52fd1a [file] [log] [blame]
/*
* 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 "CurveIntersection.h"
#include "LineIntersection.h"
#include "SkPath.h"
#include "SkRect.h"
#include "SkTArray.h"
#include "SkTDArray.h"
#include "TSearch.h"
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]) {
_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);
/*
list of edges
bounds for edge
sort
active T
if a contour's bounds is outside of the active area, no need to create edges
*/
/* given one or more paths,
find the bounds of each contour, select the active contours
for each active contour, compute a set of edges
each edge corresponds to one or more lines and curves
leave edges unbroken as long as possible
when breaking edges, compute the t at the break but leave the control points alone
*/
void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray) {
SkPath::Iter iter(path, false);
SkPoint pts[4];
SkPath::Verb verb;
SkRect bounds;
bounds.setEmpty();
int count = 0;
while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
switch (verb) {
case SkPath::kMove_Verb:
if (!bounds.isEmpty()) {
*boundsArray.append() = bounds;
}
bounds.set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
count = 0;
break;
case SkPath::kLine_Verb:
count = 1;
break;
case SkPath::kQuad_Verb:
count = 2;
break;
case SkPath::kCubic_Verb:
count = 3;
break;
case SkPath::kClose_Verb:
count = 0;
break;
default:
SkDEBUGFAIL("bad verb");
return;
}
for (int i = 1; i <= count; ++i) {
bounds.growToInclude(pts[i].fX, pts[i].fY);
}
}
}
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) {
return a.fLeft <= b.fRight && b.fLeft <= a.fRight &&
a.fTop <= b.fBottom && b.fTop <= a.fBottom;
}
};
struct Intercepts {
SkTDArray<double> fTs;
};
struct InEdge {
bool operator<(const InEdge& rh) const {
return fBounds.fTop == rh.fBounds.fTop
? fBounds.fLeft < rh.fBounds.fLeft
: fBounds.fTop < rh.fBounds.fTop;
}
void add(double* ts, size_t count, int verbIndex) {
Intercepts& intercepts = fIntercepts[verbIndex];
// FIXME: in the pathological case where there is a ton of intercepts, binary search?
for (size_t index = 0; index < count; ++index) {
double t = ts[index];
size_t tCount = intercepts.fTs.count();
for (size_t idx2 = 0; idx2 < tCount; ++idx2) {
if (t <= intercepts.fTs[idx2]) {
if (t < intercepts.fTs[idx2]) {
*intercepts.fTs.insert(idx2) = t;
break;
}
}
}
if (tCount == 0 || t > intercepts.fTs[tCount - 1]) {
*intercepts.fTs.append() = t;
}
}
}
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) {
if (edge == fCached[index]) {
return true;
}
if (edge < fCached[index]) {
*fCached.insert(index) = edge;
return false;
}
}
*fCached.append() = edge;
return false;
}
void complete(signed char winding) {
SkPoint* ptPtr = fPts.begin();
SkPoint* ptLast = fPts.end();
if (ptPtr == ptLast) {
SkDebugf("empty edge\n");
SkASSERT(0);
// FIXME: delete empty edge?
return;
}
fBounds.set(ptPtr->fX, ptPtr->fY, ptPtr->fX, ptPtr->fY);
++ptPtr;
while (ptPtr != ptLast) {
fBounds.growToInclude(ptPtr->fX, ptPtr->fY);
++ptPtr;
}
fIntercepts.push_back_n(1);
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 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 InEdgeBuilder {
public:
InEdgeBuilder(const SkPath& path, bool ignoreHorizontal, SkTArray<InEdge>& edges)
: fPath(path)
, fCurrentEdge(NULL)
, fEdges(edges)
, fIgnoreHorizontal(ignoreHorizontal)
{
walk();
}
protected:
void addEdge() {
fCurrentEdge->fPts.append(fPtCount - fPtOffset, &fPts[fPtOffset]);
fPtOffset = 1;
*fCurrentEdge->fVerbs.append() = fVerb;
}
int direction(int count) {
fPtCount = count;
fIgnorableHorizontal = fIgnoreHorizontal && isHorizontal();
if (fIgnorableHorizontal) {
return 0;
}
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;
}
bool isHorizontal() {
SkScalar y = fPts[0].fY;
for (int i = 1; i < fPtCount; ++i) {
if (fPts[i].fY != y) {
return false;
}
}
return true;
}
void startEdge() {
fCurrentEdge = fEdges.push_back_n(1);
fWinding = 0;
fPtOffset = 0;
}
void walk() {
SkPath::Iter iter(fPath, true);
int winding = 0;
while ((fVerb = iter.next(fPts)) != SkPath::kDone_Verb) {
switch (fVerb) {
case SkPath::kMove_Verb:
winding = 0;
startEdge();
continue;
case SkPath::kLine_Verb:
winding = direction(2);
break;
case SkPath::kQuad_Verb:
winding = direction(3);
break;
case SkPath::kCubic_Verb:
winding = direction(4);
break;
case SkPath::kClose_Verb:
if (fCurrentEdge->fVerbs.count()) {
fCurrentEdge->complete(fWinding);
}
continue;
default:
SkDEBUGFAIL("bad verb");
return;
}
if (fIgnorableHorizontal) {
continue;
}
if (fWinding + winding == 0) {
// FIXME: if prior verb or this verb is a horizontal line, reverse
// it instead of starting a new edge
fCurrentEdge->complete(fWinding);
startEdge();
}
fWinding = winding;
addEdge();
}
fCurrentEdge->complete(fWinding);
}
private:
const SkPath& fPath;
InEdge* fCurrentEdge;
SkTArray<InEdge>& fEdges;
SkPoint fPts[4];
SkPath::Verb fVerb;
int fPtCount;
int fPtOffset;
int8_t fWinding;
bool fIgnorableHorizontal;
bool fIgnoreHorizontal;
};
struct WorkEdge {
SkScalar bottom() const {
return fPts[verb()].fY;
}
void init(const InEdge* edge) {
fEdge = edge;
fPts = edge->fPts.begin();
fVerb = edge->fVerbs.begin();
}
bool next() {
SkASSERT(fVerb < fEdge->fVerbs.end());
fPts += *fVerb++;
return fVerb != fEdge->fVerbs.end();
}
SkPath::Verb verb() const {
return (SkPath::Verb) *fVerb;
}
int verbIndex() const {
return fVerb - fEdge->fVerbs.begin();
}
int winding() const {
return fEdge->fWinding;
}
const InEdge* fEdge;
const SkPoint* fPts;
const uint8_t* fVerb;
};
// 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 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];
}
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<InEdge> edges;
InEdgeBuilder builder(path, asFill, edges);
size_t edgeCount = edges.count();
simple.reset();
if (edgeCount == 0) {
return;
}
// returns 1 for evenodd, -1 for winding, regardless of inverse-ness
int windingMask = (path.getFillType() & 1) ? 1 : -1;
SkTDArray<InEdge*> edgeList;
for (size_t index = 0; index < edgeCount; ++index) {
*edgeList.append() = &edges[index];
}
InEdge edgeSentinel;
edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
*edgeList.append() = &edgeSentinel;
++edgeCount;
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
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;
}
SkScalar lastTop = last->fBounds.fTop;
// OPTIMIZATION: Shortening the bottom is only interesting when filling
// and when the edge is to the left of a longer edge. If it's a framing
// edge, or part of the right, it won't effect the longer edges.
if (lastTop > y) {
if (bottom > lastTop) {
bottom = lastTop;
break;
}
} else if (bottom > last->fBounds.fBottom) {
bottom = last->fBounds.fBottom;
}
addToActive(activeEdges, last);
last = *++lastPtr;
}
if (asFill && lastPtr - currentPtr <= 1) {
SkDebugf("expect 2 or more edges\n");
SkASSERT(0);
return;
}
// find any intersections in the range of active edges
InEdge** testPtr = currentPtr;
InEdge* test = *testPtr;
while (testPtr != lastPtr) {
if (test->fBounds.fBottom > bottom) {
WorkEdge wt;
wt.init(test);
do {
// 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.fPts, bottom, wtTs);
if (pts) {
test->add(wtTs, pts, wt.verbIndex());
}
}
} while (wt.next());
}
test = *++testPtr;
}
testPtr = currentPtr;
test = *testPtr;
while (testPtr != lastPtr - 1) {
InEdge* next = *++testPtr;
if (!test->cached(next)
&& Bounds::Intersects(test->fBounds, next->fBounds)) {
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) {
double wtTs[2], wnTs[2];
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;
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 += wt.winding();
if (!(lastWinding & windingMask) && !(winding & windingMask)) {
continue;
}
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) {
++currentPtr;
}
} while (*currentPtr != &edgeSentinel);
// assemble output path from string of pts, verbs
;
}
void testSimplify();
void testSimplify() {
SkPath path, out;
path.setFillType(SkPath::kWinding_FillType);
path.addRect(10, 10, 30, 30);
path.addRect(20, 20, 40, 40);
simplify(path, true, out);
path = out;
path.addRect(30, 10, 40, 20);
path.addRect(10, 30, 20, 40);
simplify(path, true, out);
path = out;
path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
simplify(path, true, out);
if (!out.isEmpty()) {
SkDebugf("expected empty\n");
}
}