ed856100c8
git-svn-id: http://skia.googlecode.com/svn/trunk@897 2bbb7eff-a529-9590-31e7-b0007b416f81
489 lines
14 KiB
C++
489 lines
14 KiB
C++
#include "GrPath.h"
|
|
|
|
GrPath::GrPath() {
|
|
fConvexHint = kNone_ConvexHint;
|
|
}
|
|
|
|
GrPath::GrPath(const GrPath& src) : INHERITED() {
|
|
GrPath::Iter iter(src);
|
|
this->resetFromIter(&iter);
|
|
}
|
|
|
|
GrPath::GrPath(GrPathIter& iter) {
|
|
this->resetFromIter(&iter);
|
|
}
|
|
|
|
GrPath::~GrPath() {
|
|
}
|
|
|
|
bool GrPath::operator ==(const GrPath& path) const {
|
|
if (fCmds.count() != path.fCmds.count() ||
|
|
fPts.count() != path.fPts.count()) {
|
|
return false;
|
|
}
|
|
|
|
for (int v = 0; v < fCmds.count(); ++v) {
|
|
if (fCmds[v] != path.fCmds[v]) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (int p = 0; p < fPts.count(); ++p) {
|
|
if (fPts[p] != path.fPts[p]) {
|
|
return false;
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void GrPath::ensureMoveTo() {
|
|
if (fCmds.isEmpty() || this->wasLastVerb(kClose_PathCmd)) {
|
|
*fCmds.append() = kMove_PathCmd;
|
|
fPts.append()->set(0, 0);
|
|
}
|
|
}
|
|
|
|
void GrPath::moveTo(GrScalar x, GrScalar y) {
|
|
if (this->wasLastVerb(kMove_PathCmd)) {
|
|
// overwrite prev kMove value
|
|
fPts[fPts.count() - 1].set(x, y);
|
|
} else {
|
|
*fCmds.append() = kMove_PathCmd;
|
|
fPts.append()->set(x, y);
|
|
}
|
|
}
|
|
|
|
void GrPath::lineTo(GrScalar x, GrScalar y) {
|
|
this->ensureMoveTo();
|
|
*fCmds.append() = kLine_PathCmd;
|
|
fPts.append()->set(x, y);
|
|
}
|
|
|
|
void GrPath::quadTo(GrScalar x0, GrScalar y0, GrScalar x1, GrScalar y1) {
|
|
this->ensureMoveTo();
|
|
*fCmds.append() = kQuadratic_PathCmd;
|
|
fPts.append()->set(x0, y0);
|
|
fPts.append()->set(x1, y1);
|
|
}
|
|
|
|
void GrPath::cubicTo(GrScalar x0, GrScalar y0, GrScalar x1, GrScalar y1,
|
|
GrScalar x2, GrScalar y2) {
|
|
this->ensureMoveTo();
|
|
*fCmds.append() = kCubic_PathCmd;
|
|
fPts.append()->set(x0, y0);
|
|
fPts.append()->set(x1, y1);
|
|
fPts.append()->set(x2, y2);
|
|
}
|
|
|
|
void GrPath::close() {
|
|
if (!fCmds.isEmpty() && !this->wasLastVerb(kClose_PathCmd)) {
|
|
// should we allow kMove followed by kClose?
|
|
*fCmds.append() = kClose_PathCmd;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
void GrPath::offset(GrScalar tx, GrScalar ty) {
|
|
if (!tx && !ty) {
|
|
return; // nothing to do
|
|
}
|
|
|
|
GrPoint* iter = fPts.begin();
|
|
GrPoint* stop = fPts.end();
|
|
while (iter < stop) {
|
|
iter->offset(tx, ty);
|
|
++iter;
|
|
}
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
static bool check_two_vecs(const GrVec& prevVec,
|
|
const GrVec& currVec,
|
|
GrScalar turnDir,
|
|
int* xDir,
|
|
int* yDir,
|
|
int* flipX,
|
|
int* flipY) {
|
|
if (currVec.fX * *xDir < 0) {
|
|
++*flipX;
|
|
if (*flipX > 2) {
|
|
return false;
|
|
}
|
|
*xDir = -*xDir;
|
|
}
|
|
if (currVec.fY * *yDir < 0) {
|
|
++*flipY;
|
|
if (*flipY > 2) {
|
|
return false;
|
|
}
|
|
*yDir = -*yDir;
|
|
}
|
|
GrScalar d = prevVec.cross(currVec);
|
|
return (d * turnDir) >= 0;
|
|
}
|
|
|
|
static void init_from_two_vecs(const GrVec& firstVec,
|
|
const GrVec& secondVec,
|
|
GrScalar* turnDir,
|
|
int* xDir, int* yDir) {
|
|
*turnDir = firstVec.cross(secondVec);
|
|
if (firstVec.fX > 0) {
|
|
*xDir = 1;
|
|
} else if (firstVec.fX < 0) {
|
|
*xDir = -1;
|
|
} else {
|
|
*xDir = 0;
|
|
}
|
|
if (firstVec.fY > 0) {
|
|
*yDir = 1;
|
|
} else if (firstVec.fY < 0) {
|
|
*yDir = -1;
|
|
} else {
|
|
*yDir = 0;
|
|
}
|
|
}
|
|
|
|
void GrPath::resetFromIter(GrPathIter* iter) {
|
|
fPts.reset();
|
|
fCmds.reset();
|
|
|
|
fConvexHint = iter->convexHint();
|
|
|
|
// first point of the subpath
|
|
GrPoint firstPt(0,0);
|
|
// first edge of the subpath
|
|
GrVec firstVec(0,0);
|
|
// vec of most recently processed edge, that wasn't degenerate
|
|
GrVec previousVec(0,0);
|
|
// most recently processed point
|
|
GrPoint previousPt(0,0);
|
|
|
|
// sign indicates whether we're bending left or right
|
|
GrScalar turnDir = 0;
|
|
// number of times the direction has flipped in x or y
|
|
|
|
// we track which direction we are moving in x/y and the
|
|
// number of times it changes.
|
|
int xDir = 0;
|
|
int yDir = 0;
|
|
int flipX = 0;
|
|
int flipY = 0;
|
|
|
|
// counts number of sub path pts that didn't add a degenerate edge.
|
|
int subPathPts = 0;
|
|
bool subPathClosed = false;
|
|
|
|
int numSubPaths = 0;
|
|
iter->rewind();
|
|
GrPathCmd cmd;
|
|
GrPoint pts[4];
|
|
do {
|
|
cmd = iter->next(pts);
|
|
// If the convexity test is ever updated to handle multiple subpaths
|
|
// the loop has to be adjusted to handle moving to a new subpath without
|
|
// closing the previous one. Currently the implicit closing vectors for a
|
|
// filled path would never be examined.
|
|
switch (cmd) {
|
|
case kMove_PathCmd:
|
|
this->moveTo(pts[0].fX, pts[0].fY);
|
|
subPathPts = 0;
|
|
subPathClosed = false;
|
|
break;
|
|
case kLine_PathCmd:
|
|
this->lineTo(pts[1].fX, pts[1].fY);
|
|
break;
|
|
case kQuadratic_PathCmd:
|
|
this->quadTo(pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
|
|
break;
|
|
case kCubic_PathCmd:
|
|
this->cubicTo(pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
|
|
pts[3].fX, pts[3].fY);
|
|
break;
|
|
case kClose_PathCmd:
|
|
this->close();
|
|
subPathClosed = true;
|
|
break;
|
|
case kEnd_PathCmd:
|
|
break;
|
|
}
|
|
int n = NumPathCmdPoints(cmd);
|
|
if (0 == subPathPts && n > 0) {
|
|
previousPt = pts[0];
|
|
firstPt = previousPt;
|
|
flipX = 0;
|
|
flipY = 0;
|
|
turnDir = 0;
|
|
subPathPts = 1;
|
|
++numSubPaths;
|
|
}
|
|
// either we skip the first pt because it is redundant with
|
|
// last point of the previous subpath cmd or we just ate it
|
|
// in the above if.
|
|
int consumed = 1;
|
|
if (numSubPaths < 2 && kNone_ConvexHint == fConvexHint) {
|
|
while (consumed < n) {
|
|
GrAssert(pts[consumed-1] == previousPt);
|
|
GrVec vec;
|
|
vec.setBetween(previousPt, pts[consumed]);
|
|
if (vec.fX || vec.fY) {
|
|
if (subPathPts >= 2) {
|
|
if (0 == turnDir) {
|
|
firstVec = previousVec;
|
|
init_from_two_vecs(firstVec, vec,
|
|
&turnDir, &xDir, &yDir);
|
|
// here we aren't checking whether the x/y dirs
|
|
// change between the first and second edge. It
|
|
// gets covered when the path is closed.
|
|
} else {
|
|
if (!check_two_vecs(previousVec, vec, turnDir,
|
|
&xDir, &yDir,
|
|
&flipX, &flipY)) {
|
|
fConvexHint = kConcave_ConvexHint;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
previousVec = vec;
|
|
previousPt = pts[consumed];
|
|
++subPathPts;
|
|
}
|
|
++consumed;
|
|
}
|
|
if (subPathPts > 2 && (kClose_PathCmd == cmd ||
|
|
(!subPathClosed && kEnd_PathCmd == cmd ))) {
|
|
// if an additional vector is needed to close the loop check
|
|
// that it validates against the previous vector.
|
|
GrVec vec;
|
|
vec.setBetween(previousPt, firstPt);
|
|
if (vec.fX || vec.fY) {
|
|
if (!check_two_vecs(previousVec, vec, turnDir,
|
|
&xDir, &yDir, &flipX, &flipY)) {
|
|
fConvexHint = kConcave_ConvexHint;
|
|
break;
|
|
}
|
|
previousVec = vec;
|
|
}
|
|
// check that closing vector validates against the first vector.
|
|
if (!check_two_vecs(previousVec, firstVec, turnDir,
|
|
&xDir, &yDir, &flipX, &flipY)) {
|
|
fConvexHint = kConcave_ConvexHint;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} while (cmd != kEnd_PathCmd);
|
|
if (kNone_ConvexHint == fConvexHint && numSubPaths < 2) {
|
|
fConvexHint = kConvex_ConvexHint;
|
|
} else {
|
|
bool recurse = false;
|
|
if (recurse) {
|
|
this->resetFromIter(iter);
|
|
}
|
|
}
|
|
}
|
|
|
|
void GrPath::ConvexUnitTest() {
|
|
GrPath testPath;
|
|
GrPath::Iter testIter;
|
|
|
|
GrPath pt;
|
|
pt.moveTo(0, 0);
|
|
pt.close();
|
|
|
|
testIter.reset(pt);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConvex_ConvexHint == testPath.getConvexHint());
|
|
|
|
GrPath line;
|
|
line.moveTo(GrIntToScalar(12), GrIntToScalar(20));
|
|
line.lineTo(GrIntToScalar(-12), GrIntToScalar(-20));
|
|
line.close();
|
|
|
|
testIter.reset(line);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConvex_ConvexHint == testPath.getConvexHint());
|
|
|
|
GrPath triLeft;
|
|
triLeft.moveTo(0, 0);
|
|
triLeft.lineTo(1, 0);
|
|
triLeft.lineTo(1, 1);
|
|
triLeft.close();
|
|
|
|
testIter.reset(triLeft);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConvex_ConvexHint == testPath.getConvexHint());
|
|
|
|
GrPath triRight;
|
|
triRight.moveTo(0, 0);
|
|
triRight.lineTo(-1, 0);
|
|
triRight.lineTo(1, 1);
|
|
triRight.close();
|
|
|
|
testIter.reset(triRight);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConvex_ConvexHint == testPath.getConvexHint());
|
|
|
|
GrPath square;
|
|
square.moveTo(0, 0);
|
|
square.lineTo(1, 0);
|
|
square.lineTo(1, 1);
|
|
square.lineTo(0, 1);
|
|
square.close();
|
|
|
|
testIter.reset(square);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConvex_ConvexHint == testPath.getConvexHint());
|
|
|
|
GrPath redundantSquare;
|
|
square.moveTo(0, 0);
|
|
square.lineTo(0, 0);
|
|
square.lineTo(0, 0);
|
|
square.lineTo(1, 0);
|
|
square.lineTo(1, 0);
|
|
square.lineTo(1, 0);
|
|
square.lineTo(1, 1);
|
|
square.lineTo(1, 1);
|
|
square.lineTo(1, 1);
|
|
square.lineTo(0, 1);
|
|
square.lineTo(0, 1);
|
|
square.lineTo(0, 1);
|
|
square.close();
|
|
|
|
testIter.reset(redundantSquare);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConvex_ConvexHint == testPath.getConvexHint());
|
|
|
|
GrPath bowTie;
|
|
bowTie.moveTo(0, 0);
|
|
bowTie.lineTo(0, 0);
|
|
bowTie.lineTo(0, 0);
|
|
bowTie.lineTo(1, 1);
|
|
bowTie.lineTo(1, 1);
|
|
bowTie.lineTo(1, 1);
|
|
bowTie.lineTo(1, 0);
|
|
bowTie.lineTo(1, 0);
|
|
bowTie.lineTo(1, 0);
|
|
bowTie.lineTo(0, 1);
|
|
bowTie.lineTo(0, 1);
|
|
bowTie.lineTo(0, 1);
|
|
bowTie.close();
|
|
|
|
testIter.reset(bowTie);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConcave_ConvexHint == testPath.getConvexHint());
|
|
|
|
GrPath spiral;
|
|
spiral.moveTo(0, 0);
|
|
spiral.lineTo(1, 0);
|
|
spiral.lineTo(1, 1);
|
|
spiral.lineTo(0, 1);
|
|
spiral.lineTo(0,.5);
|
|
spiral.lineTo(.5,.5);
|
|
spiral.lineTo(.5,.75);
|
|
spiral.close();
|
|
|
|
testIter.reset(spiral);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConcave_ConvexHint == testPath.getConvexHint());
|
|
|
|
GrPath dent;
|
|
dent.moveTo(0, 0);
|
|
dent.lineTo(1, 1);
|
|
dent.lineTo(0, 1);
|
|
dent.lineTo(-.5,2);
|
|
dent.lineTo(-2, 1);
|
|
dent.close();
|
|
|
|
testIter.reset(dent);
|
|
testPath.resetFromIter(&testIter);
|
|
GrAssert(kConcave_ConvexHint == testPath.getConvexHint());
|
|
}
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
|
|
GrPath::Iter::Iter() : fPath(NULL) {
|
|
}
|
|
|
|
GrPath::Iter::Iter(const GrPath& path) : fPath(&path) {
|
|
this->rewind();
|
|
}
|
|
|
|
GrPathCmd GrPath::Iter::next(GrPoint points[]) {
|
|
if (fCmdIndex == fPath->fCmds.count()) {
|
|
GrAssert(fPtIndex == fPath->fPts.count());
|
|
return kEnd_PathCmd;
|
|
} else {
|
|
GrAssert(fCmdIndex < fPath->fCmds.count());
|
|
}
|
|
|
|
GrPathCmd cmd = fPath->fCmds[fCmdIndex++];
|
|
const GrPoint* srcPts = fPath->fPts.begin() + fPtIndex;
|
|
|
|
switch (cmd) {
|
|
case kMove_PathCmd:
|
|
if (points) {
|
|
points[0] = srcPts[0];
|
|
}
|
|
fLastPt = srcPts[0];
|
|
GrAssert(fPtIndex <= fPath->fPts.count() + 1);
|
|
fPtIndex += 1;
|
|
break;
|
|
case kLine_PathCmd:
|
|
if (points) {
|
|
points[0] = fLastPt;
|
|
points[1] = srcPts[0];
|
|
}
|
|
fLastPt = srcPts[0];
|
|
GrAssert(fPtIndex <= fPath->fPts.count() + 1);
|
|
fPtIndex += 1;
|
|
break;
|
|
case kQuadratic_PathCmd:
|
|
if (points) {
|
|
points[0] = fLastPt;
|
|
points[1] = srcPts[0];
|
|
points[2] = srcPts[1];
|
|
}
|
|
fLastPt = srcPts[1];
|
|
GrAssert(fPtIndex <= fPath->fPts.count() + 2);
|
|
fPtIndex += 2;
|
|
break;
|
|
case kCubic_PathCmd:
|
|
if (points) {
|
|
points[0] = fLastPt;
|
|
points[1] = srcPts[0];
|
|
points[2] = srcPts[1];
|
|
points[3] = srcPts[2];
|
|
}
|
|
fLastPt = srcPts[2];
|
|
GrAssert(fPtIndex <= fPath->fPts.count() + 3);
|
|
fPtIndex += 3;
|
|
break;
|
|
case kClose_PathCmd:
|
|
break;
|
|
default:
|
|
GrAssert(!"unknown grpath cmd");
|
|
break;
|
|
}
|
|
return cmd;
|
|
}
|
|
|
|
GrConvexHint GrPath::Iter::convexHint() const {
|
|
return fPath->getConvexHint();
|
|
}
|
|
|
|
GrPathCmd GrPath::Iter::next() {
|
|
return this->next(NULL);
|
|
}
|
|
|
|
void GrPath::Iter::rewind() {
|
|
this->reset(*fPath);
|
|
}
|
|
|
|
void GrPath::Iter::reset(const GrPath& path) {
|
|
fPath = &path;
|
|
fCmdIndex = fPtIndex = 0;
|
|
}
|
|
|
|
|