static bool gShowDebugf = true; // FIXME: remove once debugging is complete
static bool gShowPath = false;
-static bool gDebugLessThan = false;
+static bool gDebugLessThan = true;
static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
double aRange[2], double bRange[2]) {
return horizontalIntersect(aLine, y, aRange);
}
-static SkScalar LineXAtT(const SkPoint a[2], double t) {
+static void LineXYAtT(const SkPoint a[2], double t, SkPoint* out) {
_Line aLine = {{a[0].fX, a[0].fY}, {a[1].fX, a[1].fY}};
- double x;
- xy_at_t(aLine, t, x, *(double*) 0);
- return SkDoubleToScalar(x);
+ double x, y;
+ xy_at_t(aLine, t, x, y);
+ out->fX = SkDoubleToScalar(x);
+ out->fY = SkDoubleToScalar(y);
}
static SkScalar LineYAtT(const SkPoint a[2], double t) {
size_t tCount = intercepts.fTs.count();
for (size_t idx2 = 0; idx2 < tCount; ++idx2) {
if (t <= intercepts.fTs[idx2]) {
- if (t < intercepts.fTs[idx2]) {
+ double delta = intercepts.fTs[idx2] - t;
+ if (delta > 0) {
*intercepts.fTs.insert(idx2) = t;
- break;
}
+ return foundIntercept;
}
}
if (tCount == 0 || t > intercepts.fTs[tCount - 1]) {
fPts += *fVerb++;
return fVerb != fEdge->fVerbs.end();
}
+
+ SkPath::Verb lastVerb() const {
+ SkASSERT(fVerb > fEdge->fVerbs.begin());
+ return (SkPath::Verb) fVerb[-1];
+ }
+
SkPath::Verb verb() const {
return (SkPath::Verb) *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
+
+// OPTIMIZATION: Brian suggests that global sorting should be unnecessary, since
+// as active edges are introduced, only local sorting should be required
struct ActiveEdge {
+ // OPTIMIZATION: fold return statements into one
bool operator<(const ActiveEdge& rh) const {
- return fXAbove != rh.fXAbove ? fXAbove < rh.fXAbove
- : fXBelow < rh.fXBelow;
+ if (rh.fAbove.fY - fAbove.fY > fBelow.fY - rh.fAbove.fY
+ && fBelow.fY < rh.fBelow.fY
+ || fAbove.fY - rh.fAbove.fY < rh.fBelow.fY - fAbove.fY
+ && rh.fBelow.fY < fBelow.fY) {
+ // FIXME: need to compute distance, not check for points equal
+ const SkPoint& check = rh.fBelow.fY <= fBelow.fY
+ && fBelow != rh.fBelow ? rh.fBelow :
+ rh.fAbove;
+ if (gDebugLessThan) {
+ SkDebugf("%s < %c %cthis (%d){%1.2g,%1.2g %1.2g,%1.2g}"
+ " < rh (%d){%1.2g,%1.2g %1.2g,%1.2g}\n", __FUNCTION__,
+ rh.fBelow.fY <= fBelow.fY && fBelow != rh.fBelow ? 'B' : 'A',
+ (check.fY - fAbove.fY) * (fBelow.fX - fAbove.fX)
+ < (fBelow.fY - fAbove.fY) * (check.fX - fAbove.fX)
+ ? ' ' : '!',
+ fIndex, fAbove.fX, fAbove.fY, fBelow.fX, fBelow.fY,
+ rh.fIndex, rh.fAbove.fX, rh.fAbove.fY,
+ rh.fBelow.fX, rh.fBelow.fY);
+ }
+ return (check.fY - fAbove.fY) * (fBelow.fX - fAbove.fX)
+ < (fBelow.fY - fAbove.fY) * (check.fX - fAbove.fX);
+ }
+ // FIXME: need to compute distance, not check for points equal
+ const SkPoint& check = fBelow.fY <= rh.fBelow.fY
+ && fBelow != rh.fBelow ? fBelow : fAbove;
+ if (gDebugLessThan) {
+ SkDebugf("%s > %c %cthis (%d){%1.2g,%1.2g %1.2g,%1.2g}"
+ " < rh (%d){%1.2g,%1.2g %1.2g,%1.2g}\n", __FUNCTION__,
+ fBelow.fY <= rh.fBelow.fY & fBelow != rh.fBelow ? 'B' : 'A',
+ (rh.fBelow.fY - rh.fAbove.fY) * (check.fX - rh.fAbove.fX)
+ < (check.fY - rh.fAbove.fY) * (rh.fBelow.fX - rh.fAbove.fX)
+ ? ' ' : '!',
+ fIndex, fAbove.fX, fAbove.fY, fBelow.fX, fBelow.fY,
+ rh.fIndex, rh.fAbove.fX, rh.fAbove.fY,
+ rh.fBelow.fX, rh.fBelow.fY);
+ }
+ return (rh.fBelow.fY - rh.fAbove.fY) * (check.fX - rh.fAbove.fX)
+ < (check.fY - rh.fAbove.fY) * (rh.fBelow.fX - rh.fAbove.fX);
}
- void calcLeft() {
+ bool advanceT() {
+ SkASSERT(fTIndex <= fTs->count());
+ return ++fTIndex <= fTs->count();
+ }
+
+ bool advance() {
+ // FIXME: flip sense of next
+ bool result = fWorkEdge.advance();
+ fDone = !result;
+ initT();
+ return result;
+ }
+
+ void calcLeft(SkScalar y) {
// OPTIMIZE: put a kDone_Verb at the end of the verb list?
- if (fDone)
+ if (done(y))
return; // nothing to do; use last
+ calcLeft();
+ }
+
+ void calcLeft() {
switch (fWorkEdge.verb()) {
case SkPath::kLine_Verb: {
// OPTIMIZATION: if fXAbove, fXBelow have already been computed
// If both edges have T values < 1, check x at next T (fXBelow).
int add = (fTIndex <= fTs->count()) - 1;
double tAbove = t(fTIndex + add);
- fXAbove = LineXAtT(fWorkEdge.fPts, tAbove);
+ // OPTIMIZATION: may not need Y
+ LineXYAtT(fWorkEdge.fPts, tAbove, &fAbove);
double tBelow = t(fTIndex - ~add);
- fXBelow = LineXAtT(fWorkEdge.fPts, tBelow);
+ LineXYAtT(fWorkEdge.fPts, tBelow, &fBelow);
break;
}
default:
}
}
+ bool done(SkScalar y) {
+ return fDone || fYBottom > y;
+ }
+
void init(const InEdge* edge) {
fWorkEdge.init(edge);
initT();
fTIndex = 0;
}
- bool isCoincidentWith(const ActiveEdge* edge) const {
- if (fXAbove != edge->fXAbove || fXBelow != edge->fXBelow) {
+ // OPTIMIZATION: record if two edges are coincident when the are intersected
+ // It's unclear how to do this -- seems more complicated than recording the
+ // t values, since the same t values could exist intersecting non-coincident
+ // edges.
+ bool isCoincidentWith(const ActiveEdge* edge, SkScalar y) const {
+ if (fAbove.fX != edge->fAbove.fX || fBelow.fX != edge->fBelow.fX) {
return false;
}
- switch (fWorkEdge.verb()) {
+ uint8_t verb = fDone ? fWorkEdge.lastVerb() : fWorkEdge.verb();
+ uint8_t edgeVerb = edge->fDone ? edge->fWorkEdge.lastVerb()
+ : edge->fWorkEdge.verb();
+ if (verb != edgeVerb) {
+ return false;
+ }
+ switch (verb) {
case SkPath::kLine_Verb: {
- return (fWorkEdge.fPts[0].fX - fWorkEdge.fPts[1].fX) *
- (edge->fWorkEdge.fPts[0].fY - edge->fWorkEdge.fPts[1].fY) ==
- (fWorkEdge.fPts[0].fY - fWorkEdge.fPts[1].fY) *
- (edge->fWorkEdge.fPts[0].fX - edge->fWorkEdge.fPts[1].fX);
+ int offset = fDone ? -1 : 1;
+ int edgeOffset = edge->fDone ? -1 : 1;
+ const SkPoint* pts = fWorkEdge.fPts;
+ const SkPoint* edgePts = edge->fWorkEdge.fPts;
+ return (pts->fX - pts[offset].fX)
+ * (edgePts->fY - edgePts[edgeOffset].fY)
+ == (pts->fY - pts[offset].fY)
+ * (edgePts->fX - edgePts[edgeOffset].fX);
}
default:
// FIXME: add support for all curve types
}
return false;
}
-
- bool advanceT() {
- SkASSERT(fTIndex <= fTs->count());
- return ++fTIndex <= fTs->count();
- }
- bool advance() {
- // FIXME: flip sense of next
- bool result = fWorkEdge.advance();
- fDone = !result;
- initT();
- return result;
- }
-
- bool done(SkScalar y) {
- return fDone || fYBottom > y;
+ bool swapCoincident(const ActiveEdge* edge, SkScalar bottom) const {
+ if (fBelow.fY >= bottom || fDone || edge->fDone) {
+ return false;
+ }
+ ActiveEdge thisWork = *this;
+ ActiveEdge edgeWork = *edge;
+ while ((thisWork.advanceT() || thisWork.advance())
+ && (edgeWork.advanceT() || edgeWork.advance())) {
+ thisWork.calcLeft();
+ edgeWork.calcLeft();
+ if (thisWork < edgeWork) {
+ return false;
+ }
+ if (edgeWork < thisWork) {
+ return true;
+ }
+ }
+ return false;
}
-
+
double nextT() {
SkASSERT(fTIndex <= fTs->count());
return t(fTIndex + 1);
WorkEdge fWorkEdge;
const SkTDArray<double>* fTs;
- SkScalar fXAbove;
- SkScalar fXBelow;
+ SkPoint fAbove;
+ SkPoint fBelow;
SkScalar fYBottom;
int fTIndex;
bool fSkip;
bool fDone;
+ int fIndex; // REMOVE: debugging only
};
static void addToActive(SkTDArray<ActiveEdge>& activeEdges, const InEdge* edge) {
}
edgeSentinel.fBounds.set(SK_ScalarMax, SK_ScalarMax, SK_ScalarMax, SK_ScalarMax);
*edgeList.append() = &edgeSentinel;
- ++edgeCount;
- QSort<InEdge>(edgeList.begin(), edgeCount);
+ QSort<InEdge>(edgeList.begin(), edgeList.end() - 1);
}
}
static void sortHorizontal(SkTDArray<ActiveEdge>& activeEdges,
- SkTDArray<ActiveEdge*>& edgeList, int windingMask) {
+ SkTDArray<ActiveEdge*>& edgeList, int windingMask, SkScalar y,
+ SkScalar bottom) {
size_t edgeCount = activeEdges.count();
if (edgeCount == 0) {
return;
size_t index;
for (index = 0; index < edgeCount; ++index) {
ActiveEdge& activeEdge = activeEdges[index];
- activeEdge.calcLeft();
+ activeEdge.calcLeft(y);
activeEdge.fSkip = false;
+ activeEdge.fIndex = index; // REMOVE: debugging only
*edgeList.append() = &activeEdge;
}
- QSort<ActiveEdge>(edgeList.begin(), edgeCount);
+ QSort<ActiveEdge>(edgeList.begin(), edgeList.end() - 1);
// remove coincident edges
// OPTIMIZE: remove edges? This is tricky because the current logic expects
// the winding count to be maintained while skipping coincident edges. In
for (index = 1; index < edgeCount; ++index) {
winding += activePtr->fWorkEdge.winding();
ActiveEdge* nextPtr = edgeList[index];
- if (activePtr->isCoincidentWith(nextPtr)) {
+ if (activePtr->isCoincidentWith(nextPtr, y)) {
+ // the coincident edges may not have been sorted above -- advance
+ // the edges and resort if needed
+ // OPTIMIZE: if sorting is done incrementally as new edges are added
+ // and not all at once as is done here, fold this test into the
+ // current less than test.
+ if (activePtr->swapCoincident(nextPtr, bottom)) {
+ SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]);
+ SkTSwap<ActiveEdge*>(activePtr, nextPtr);
+ }
if (!firstCoincident) {
firstCoincident = activePtr;
}
int lastWinding = winding;
winding += wt.winding();
if (activePtr->done(y)) {
- continue;
+ // FIXME: if this is successful, rewrite done to take bottom as well
+ if (activePtr->fDone) {
+ continue;
+ }
+ if (activePtr->fYBottom >= bottom) {
+ continue;
+ }
+ if (0) {
+ SkDebugf("%s bot %g,%g\n", __FUNCTION__, activePtr->fYBottom, bottom);
+ }
}
int opener = (lastWinding & windingMask) == 0;
bool closer = (winding & windingMask) == 0;
}
outBuilder.addLine(clipped);
}
+ activePtr->fSkip = false;
} else {
// FIXME: add all curve types
SkASSERT(0);
addIntersectingTs(currentPtr, lastPtr);
computeInterceptBottom(activeEdges, y, bottom);
SkTDArray<ActiveEdge*> activeEdgeList;
- sortHorizontal(activeEdges, activeEdgeList, windingMask);
+ sortHorizontal(activeEdges, activeEdgeList, windingMask, y, bottom);
stitchEdge(activeEdgeList, y, bottom, windingMask, outBuilder);
}
y = bottom;
--- /dev/null
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+
+static void testSimplifyTriangle() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.moveTo(10,10); // triangle |\ .
+ path.lineTo(10,30); // |_\ .
+ path.lineTo(20,30);
+ path.close();
+ path.moveTo(20,10); // triangle /|
+ path.lineTo(10,30); // /_|
+ path.lineTo(20,30);
+ path.close();
+ simplify(path, true, out); // expect |\/|
+ comparePaths(path, out); // |__|
+}
+
+static void testSimplifyTriangle3() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(3, 1);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle4() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(2, 1);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle5() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 1);
+ path.lineTo(2, 1);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle6() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.lineTo(3, 1);
+ path.lineTo(0, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle7() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 1);
+ path.lineTo(0, 2);
+ path.lineTo(0, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle8() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 1);
+ path.lineTo(1, 2);
+ path.lineTo(1, 3);
+ path.lineTo(0, 1);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle9() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 1);
+ path.lineTo(2, 1);
+ path.lineTo(0, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle10() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 1);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 0);
+ path.lineTo(0, 1);
+ path.lineTo(0, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle11() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 2);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(2, 1);
+ path.lineTo(2, 2);
+ path.lineTo(0, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle12() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 2);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(0, 3);
+ path.lineTo(1, 1);
+ path.lineTo(2, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle13() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 3);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(3, 0);
+ path.lineTo(0, 3);
+ path.lineTo(1, 1);
+ path.lineTo(3, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle14() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(0, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle15() {
+ SkPath path, out;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.lineTo(2, 2);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle16() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 3);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle17() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(1, 3);
+ path.lineTo(0, 1);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyWindingParallelogram() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.moveTo(20,10); // parallelogram _
+ path.lineTo(30,30); // \ \ .
+ path.lineTo(40,30); // \_\ .
+ path.lineTo(30,10);
+ path.close();
+ path.moveTo(20,10); // parallelogram _
+ path.lineTo(10,30); // / /
+ path.lineTo(20,30); // /_/
+ path.lineTo(30,10);
+ path.close();
+ simplify(path, true, out); // expect _
+ comparePaths(path, out); // / \ .
+} // /___\ .
+
+static void testSimplifyXorParallelogram() {
+ SkPath path, out;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(20,10); // parallelogram _
+ path.lineTo(30,30); // \ \ .
+ path.lineTo(40,30); // \_\ .
+ path.lineTo(30,10);
+ path.close();
+ path.moveTo(20,10); // parallelogram _
+ path.lineTo(10,30); // / /
+ path.lineTo(20,30); // /_/
+ path.lineTo(30,10);
+ path.close();
+ simplify(path, true, out); // expect _
+ comparePaths(path, out); // \ /
+} //
+
+static void testSimplifyTriangle2() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.moveTo(10,10); // triangle |\ .
+ path.lineTo(10,30); // |_\ .
+ path.lineTo(20,30);
+ path.close();
+ path.moveTo(10,10); // triangle _
+ path.lineTo(20,10); // \ |
+ path.lineTo(20,30); // \|
+ path.close(); // _
+ simplify(path, true, out); // expect | |
+ comparePaths(path, out); // |_|
+}
+
+static void testSimplifyNondegenerate4x4Triangles() {
+ char pathStr[1024];
+ bzero(pathStr, sizeof(pathStr));
+ for (int a = 0; a < 15; ++a) {
+ int ax = a & 0x03;
+ int ay = a >> 2;
+ for (int b = a + 1; b < 16; ++b) {
+ int bx = b & 0x03;
+ int by = b >> 2;
+ for (int c = a + 1; c < 16; ++c) {
+ if (b == c) {
+ continue;
+ }
+ int cx = c & 0x03;
+ int cy = c >> 2;
+ if ((bx - ax) * (cy - ay) == (by - ay) * (cx - ax)) {
+ continue;
+ }
+ for (int d = 0; d < 15; ++d) {
+ int dx = d & 0x03;
+ int dy = d >> 2;
+ for (int e = d + 1; e < 16; ++e) {
+ int ex = e & 0x03;
+ int ey = e >> 2;
+ for (int f = d + 1; f < 16; ++f) {
+ if (e == f) {
+ continue;
+ }
+ int fx = f & 0x03;
+ int fy = f >> 2;
+ if ((ex - dx) * (fy - dy) == (ey - dy) * (fx - dx)) {
+ continue;
+ }
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.moveTo(ax, ay);
+ path.lineTo(bx, by);
+ path.lineTo(cx, cy);
+ path.close();
+ path.moveTo(dx, dy);
+ path.lineTo(ex, ey);
+ path.lineTo(fx, fy);
+ path.close();
+ if (1) {
+ char* str = pathStr;
+ str += sprintf(str, " path.moveTo(%d, %d);\n", ax, ay);
+ str += sprintf(str, " path.lineTo(%d, %d);\n", bx, by);
+ str += sprintf(str, " path.lineTo(%d, %d);\n", cx, cy);
+ str += sprintf(str, " path.close();\n");
+ str += sprintf(str, " path.moveTo(%d, %d);\n", dx, dy);
+ str += sprintf(str, " path.lineTo(%d, %d);\n", ex, ey);
+ str += sprintf(str, " path.lineTo(%d, %d);\n", fx, fy);
+ str += sprintf(str, " path.close();");
+ }
+ simplify(path, true, out);
+ comparePaths(path, out);
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ simplify(path, true, out);
+ comparePaths(path, out);
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static void testPathTriangleRendering() {
+ SkPath one, two;
+ one.moveTo(0, 0);
+ one.lineTo(3, 3);
+ one.lineTo(0, 3);
+ one.lineTo(1, 2);
+ one.close();
+ for (float x = .1f; x <= 2.9f; x += .1f) {
+ SkDebugf("%s x=%g\n", __FUNCTION__, x);
+ two.moveTo(0, 0);
+ two.lineTo(x, x);
+ two.lineTo(3, 3);
+ two.lineTo(0, 3);
+ two.lineTo(1, 2);
+ two.close();
+ comparePaths(one, two);
+ two.reset();
+ }
+}
+
+static void (*simplifyTests[])() = {
+ testSimplifyTriangle17,
+ testSimplifyTriangle16,
+ testSimplifyTriangle15,
+ testSimplifyTriangle14,
+ testSimplifyTriangle13,
+ testSimplifyTriangle12,
+ testSimplifyTriangle11,
+ testSimplifyTriangle10,
+ testSimplifyTriangle7,
+ testSimplifyTriangle9,
+ testSimplifyTriangle8,
+ testSimplifyTriangle6,
+ testSimplifyTriangle5,
+ testSimplifyTriangle4,
+ testSimplifyTriangle3,
+ testSimplifyTriangle,
+ testSimplifyTriangle2,
+ testSimplifyWindingParallelogram,
+ testSimplifyXorParallelogram,
+ testSimplifyNondegenerate4x4Triangles,
+ testPathTriangleRendering,
+};
+
+static size_t simplifyTestsCount = sizeof(simplifyTests) / sizeof(simplifyTests[0]);
+
+static void (*firstTest)() = 0;
+
+void SimplifyPolygonPaths_Test() {
+ size_t index = 0;
+ if (firstTest) {
+ while (index < simplifyTestsCount && simplifyTests[index] != firstTest) {
+ ++index;
+ }
+ }
+ for ( ; index < simplifyTestsCount; ++index) {
+ (*simplifyTests[index])();
+ }
+}
+
--- /dev/null
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+
+static void testSimplifyCoincidentVertical() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(10, 10, 30, 30);
+ path.addRect(10, 30, 30, 40);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 30, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyCoincidentHorizontal() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(10, 10, 30, 30);
+ path.addRect(30, 10, 40, 30);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 30)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyMulti() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect(10, 10, 30, 30);
+ path.addRect(20, 20, 40, 40);
+ simplify(path, true, out);
+ SkPath expected;
+ expected.setFillType(SkPath::kEvenOdd_FillType);
+ expected.moveTo(10,10); // two cutout corners
+ expected.lineTo(10,30);
+ expected.lineTo(20,30);
+ expected.lineTo(20,40);
+ expected.lineTo(40,40);
+ expected.lineTo(40,20);
+ expected.lineTo(30,20);
+ expected.lineTo(30,10);
+ expected.lineTo(10,10);
+ expected.close();
+ if (out != expected) {
+ SkDebugf("%s expected equal\n", __FUNCTION__);
+ }
+
+ path = out;
+ path.addRect(30, 10, 40, 20);
+ path.addRect(10, 30, 20, 40);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+
+ path = out;
+ path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
+ simplify(path, true, out);
+ if (!out.isEmpty()) {
+ SkDebugf("%s expected empty\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyAddL() {
+ SkPath path, out;
+ path.moveTo(10,10); // 'L' shape
+ path.lineTo(10,40);
+ path.lineTo(40,40);
+ path.lineTo(40,20);
+ path.lineTo(30,20);
+ path.lineTo(30,10);
+ path.lineTo(10,10);
+ path.close();
+ path.addRect(30, 10, 40, 20); // missing notch of 'L'
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyCoincidentCCW() {
+ SkPath path, out;
+ path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
+ path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyCoincidentCW() {
+ SkPath path, out;
+ path.addRect(10, 10, 40, 40, SkPath::kCCW_Direction);
+ path.addRect(10, 10, 40, 40, SkPath::kCW_Direction);
+ simplify(path, true, out);
+ if (!out.isEmpty()) {
+ SkDebugf("%s expected empty\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyCorner() {
+ SkPath path, out;
+ path.addRect(10, 10, 20, 20, SkPath::kCCW_Direction);
+ path.addRect(20, 20, 40, 40, SkPath::kCW_Direction);
+ simplify(path, true, out);
+ SkTDArray<SkRect> boundsArray;
+ contourBounds(out, boundsArray);
+ if (boundsArray.count() != 2) {
+ SkDebugf("%s expected 2 contours\n", __FUNCTION__);
+ return;
+ }
+ SkRect one = SkRect::MakeLTRB(10, 10, 20, 20);
+ SkRect two = SkRect::MakeLTRB(20, 20, 40, 40);
+ if (boundsArray[0] != one && boundsArray[0] != two
+ || boundsArray[1] != one && boundsArray[1] != two) {
+ SkDebugf("%s expected match\n", __FUNCTION__);
+ }
+}
+
+static void testSimplifyDiagonal() {
+ SkRect rect2 = SkRect::MakeXYWH(10, 10, 10, 10);
+ for (size_t outDir = SkPath::kCW_Direction; outDir <= SkPath::kCCW_Direction; ++outDir) {
+ for (size_t inDir = SkPath::kCW_Direction; inDir <= SkPath::kCCW_Direction; ++inDir) {
+ for (int x = 0; x <= 20; x += 20) {
+ for (int y = 0; y <= 20; y += 20) {
+ SkPath path, out;
+ SkRect rect1 = SkRect::MakeXYWH(x, y, 10, 10);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ SkPath::Iter iter(out, false);
+ SkPoint pts[4], lastLine[2];
+ SkPath::Verb verb;
+ SkRect bounds[2];
+ bounds[0].setEmpty();
+ bounds[1].setEmpty();
+ SkRect* boundsPtr = bounds;
+ int count = 0, segments = 0;
+ bool lastLineSet = false;
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ if (!boundsPtr->isEmpty()) {
+ SkASSERT(boundsPtr == bounds);
+ ++boundsPtr;
+ }
+ boundsPtr->set(pts[0].fX, pts[0].fY, pts[0].fX, pts[0].fY);
+ count = 0;
+ lastLineSet = false;
+ break;
+ case SkPath::kLine_Verb:
+ if (lastLineSet) {
+ SkASSERT((lastLine[1].fX - lastLine[0].fX) *
+ (pts[1].fY - lastLine[0].fY) !=
+ (lastLine[1].fY - lastLine[0].fY) *
+ (pts[1].fX - lastLine[0].fX));
+ }
+ lastLineSet = true;
+ lastLine[0] = pts[0];
+ lastLine[1] = pts[1];
+ count = 1;
+ ++segments;
+ break;
+ case SkPath::kClose_Verb:
+ count = 0;
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ for (int i = 1; i <= count; ++i) {
+ boundsPtr->growToInclude(pts[i].fX, pts[i].fY);
+ }
+ }
+ if (boundsPtr != bounds) {
+ SkASSERT((bounds[0] == rect1 || bounds[1] == rect1)
+ && (bounds[0] == rect2 || bounds[1] == rect2));
+ } else {
+ SkASSERT(segments == 8);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void assertOneContour(const SkPath& out, bool edge, bool extend) {
+ SkPath::Iter iter(out, 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:
+ SkASSERT(count == 0);
+ break;
+ case SkPath::kLine_Verb:
+ SkASSERT(pts[0].fX == pts[1].fX || pts[0].fY == pts[1].fY);
+ ++count;
+ break;
+ case SkPath::kClose_Verb:
+ break;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ }
+ SkASSERT(count == (extend ? 4 : edge ? 6 : 8));
+}
+
+static void testSimplifyCoincident() {
+ // outside to inside, outside to right, outside to outside
+ // left to inside, left to right, left to outside
+ // inside to right, inside to outside
+ // repeat above for left, right, bottom
+ SkScalar start[] = { 0, 10, 20 };
+ size_t startCount = sizeof(start) / sizeof(start[0]);
+ SkScalar stop[] = { 30, 40, 50 };
+ size_t stopCount = sizeof(stop) / sizeof(stop[0]);
+ SkRect rect2 = SkRect::MakeXYWH(10, 10, 30, 30);
+ for (size_t outDir = SkPath::kCW_Direction; outDir <= SkPath::kCCW_Direction; ++outDir) {
+ for (size_t inDir = SkPath::kCW_Direction; inDir <= SkPath::kCCW_Direction; ++inDir) {
+ for (size_t startIndex = 0; startIndex < startCount; ++startIndex) {
+ for (size_t stopIndex = 0; stopIndex < stopCount; ++stopIndex) {
+ bool extend = start[startIndex] == rect2.fLeft && stop[stopIndex] == rect2.fRight;
+ bool edge = start[startIndex] == rect2.fLeft || stop[stopIndex] == rect2.fRight;
+ SkRect rect1 = SkRect::MakeLTRB(start[startIndex], 0, stop[stopIndex], 10);
+ SkPath path, out;
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ assertOneContour(out, edge, extend);
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(start[startIndex], 40, stop[stopIndex], 50);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ assertOneContour(out, edge, extend);
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(0, start[startIndex], 10, stop[stopIndex]);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ assertOneContour(out, edge, extend);
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(40, start[startIndex], 50, stop[stopIndex]);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ assertOneContour(out, edge, extend);
+ }
+ }
+ }
+ }
+}
+
+static void testSimplifyOverlap() {
+ SkScalar start[] = { 0, 10, 20 };
+ size_t startCount = sizeof(start) / sizeof(start[0]);
+ SkScalar stop[] = { 30, 40, 50 };
+ size_t stopCount = sizeof(stop) / sizeof(stop[0]);
+ SkRect rect2 = SkRect::MakeXYWH(10, 10, 30, 30);
+ for (size_t dir = SkPath::kCW_Direction; dir <= SkPath::kCCW_Direction; ++dir) {
+ for (size_t lefty = 0; lefty < startCount; ++lefty) {
+ for (size_t righty = 0; righty < stopCount; ++righty) {
+ for (size_t toppy = 0; toppy < startCount; ++toppy) {
+ for (size_t botty = 0; botty < stopCount; ++botty) {
+ SkRect rect1 = SkRect::MakeLTRB(start[lefty], start[toppy],
+ stop[righty], stop[botty]);
+ SkPath path, out;
+ path.addRect(rect1, static_cast<SkPath::Direction>(dir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(dir));
+ simplify(path, true, out);
+ comparePaths(path, out);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void testSimplifyOverlapTiny() {
+ SkScalar start[] = { 0, 1, 2 };
+ size_t startCount = sizeof(start) / sizeof(start[0]);
+ SkScalar stop[] = { 3, 4, 5 };
+ size_t stopCount = sizeof(stop) / sizeof(stop[0]);
+ SkRect rect2 = SkRect::MakeXYWH(1, 1, 3, 3);
+ for (size_t dir = SkPath::kCW_Direction; dir <= SkPath::kCCW_Direction; ++dir) {
+ for (size_t lefty = 0; lefty < startCount; ++lefty) {
+ for (size_t righty = 0; righty < stopCount; ++righty) {
+ for (size_t toppy = 0; toppy < startCount; ++toppy) {
+ for (size_t botty = 0; botty < stopCount; ++botty) {
+ SkRect rect1 = SkRect::MakeLTRB(start[lefty], start[toppy],
+ stop[righty], stop[botty]);
+ SkPath path, out;
+ path.addRect(rect1, static_cast<SkPath::Direction>(dir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(dir));
+ simplify(path, true, out);
+ comparePathsTiny(path, out);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void testSimplifyDegenerate() {
+ SkScalar start[] = { 0, 10, 20 };
+ size_t startCount = sizeof(start) / sizeof(start[0]);
+ SkScalar stop[] = { 30, 40, 50 };
+ size_t stopCount = sizeof(stop) / sizeof(stop[0]);
+ SkRect rect2 = SkRect::MakeXYWH(10, 10, 30, 30);
+ for (size_t outDir = SkPath::kCW_Direction; outDir <= SkPath::kCCW_Direction; ++outDir) {
+ for (size_t inDir = SkPath::kCW_Direction; inDir <= SkPath::kCCW_Direction; ++inDir) {
+ for (size_t startIndex = 0; startIndex < startCount; ++startIndex) {
+ for (size_t stopIndex = 0; stopIndex < stopCount; ++stopIndex) {
+ SkRect rect1 = SkRect::MakeLTRB(start[startIndex], 0, stop[stopIndex], 0);
+ SkPath path, out;
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s 1 expected rect\n", __FUNCTION__);
+ }
+ if (rect != rect2) {
+ SkDebugf("%s 1 expected union\n", __FUNCTION__);
+ }
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(start[startIndex], 40, stop[stopIndex], 40);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s 2 expected rect\n", __FUNCTION__);
+ }
+ if (rect != rect2) {
+ SkDebugf("%s 2 expected union\n", __FUNCTION__);
+ }
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(0, start[startIndex], 0, stop[stopIndex]);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s 3 expected rect\n", __FUNCTION__);
+ }
+ if (rect != rect2) {
+ SkDebugf("%s 3 expected union\n", __FUNCTION__);
+ }
+
+ path.reset();
+ rect1 = SkRect::MakeLTRB(40, start[startIndex], 40, stop[stopIndex]);
+ path.addRect(rect1, static_cast<SkPath::Direction>(outDir));
+ path.addRect(rect2, static_cast<SkPath::Direction>(inDir));
+ simplify(path, true, out);
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s 4 expected rect\n", __FUNCTION__);
+ }
+ if (rect != rect2) {
+ SkDebugf("%s 4 expected union\n", __FUNCTION__);
+ }
+ }
+ }
+ }
+ }
+}
+
+static void testSimplifyDegenerate1() {
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.addRect( 0, 0, 0, 30);
+ path.addRect(10, 10, 40, 40);
+ simplify(path, true, out);
+ SkRect rect;
+ if (!out.isRect(&rect)) {
+ SkDebugf("%s expected rect\n", __FUNCTION__);
+ }
+ if (rect != SkRect::MakeLTRB(10, 10, 40, 40)) {
+ SkDebugf("%s expected union\n", __FUNCTION__);
+ }
+}
+
+static void (*simplifyTests[])() = {
+ testSimplifyOverlapTiny,
+ testSimplifyDegenerate1,
+ testSimplifyCorner,
+ testSimplifyDegenerate,
+ testSimplifyOverlap,
+ testSimplifyDiagonal,
+ testSimplifyCoincident,
+ testSimplifyCoincidentCW,
+ testSimplifyCoincidentCCW,
+ testSimplifyCoincidentVertical,
+ testSimplifyCoincidentHorizontal,
+ testSimplifyAddL,
+ testSimplifyMulti,
+};
+
+static size_t simplifyTestsCount = sizeof(simplifyTests) / sizeof(simplifyTests[0]);
+
+static void (*firstTest)() = 0;
+
+void SimplifyRectangularPaths_Test() {
+ size_t index = 0;
+ if (firstTest) {
+ while (index < simplifyTestsCount && simplifyTests[index] != firstTest) {
+ ++index;
+ }
+ }
+ for ( ; index < simplifyTestsCount; ++index) {
+ if (simplifyTests[index] == testSimplifyCorner) {
+ // testSimplifyCorner fails because it expects two contours, where
+ // only one is returned. Both results are reasonable, but if two
+ // contours are desirable, or if we provide an option to choose
+ // between longer contours and more contours, turn this back on. For
+ // the moment, testSimplifyDiagonal also checks the test case, and
+ // permits either two rects or one non-crossing poly as valid
+ // unreported results.
+ continue;
+ }
+ (*simplifyTests[index])();
+ }
+}
+
--- /dev/null
+
+
+#include "SkPath.h"
+
+extern void contourBounds(const SkPath& path, SkTDArray<SkRect>& boundsArray);
+extern void comparePaths(const SkPath& one, const SkPath& two);
+extern void comparePathsTiny(const SkPath& one, const SkPath& two);
+extern void simplify(const SkPath& path, bool asFill, SkPath& simple);
+
--- /dev/null
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+#include "SkBitmap.h"
+#include "SkCanvas.h"
+#include "SkPaint.h"
+
+static bool gDrawLastAsciiPaths = true;
+static bool gDrawAllAsciiPaths = false;
+static bool gShowPath = true;
+
+static void showPath(const char* str, const SkPath& path) {
+ if (!gShowPath) {
+ return;
+ }
+ SkDebugf("%s\n", str);
+ SkPath::Iter iter(path, true);
+ uint8_t verb;
+ SkPoint pts[4];
+ while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
+ switch (verb) {
+ case SkPath::kMove_Verb:
+ SkDebugf("path.moveTo(%g, %g);\n", pts[0].fX, pts[0].fY);
+ continue;
+ case SkPath::kLine_Verb:
+ SkDebugf("path.lineTo(%g, %g);\n", pts[1].fX, pts[1].fY);
+ break;
+ case SkPath::kQuad_Verb:
+ SkDebugf("path.quadTo(%g, %g, %g, %g);\n",
+ pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY);
+ break;
+ case SkPath::kCubic_Verb:
+ SkDebugf("path.cubicTo(%g, %g, %g, %g);\n",
+ pts[1].fX, pts[1].fY, pts[2].fX, pts[2].fY,
+ pts[3].fX, pts[3].fY);
+ break;
+ case SkPath::kClose_Verb:
+ SkDebugf("path.close();\n");
+ continue;
+ default:
+ SkDEBUGFAIL("bad verb");
+ return;
+ }
+ }
+}
+
+static bool pathsDrawTheSame(const SkPath& one, const SkPath& two) {
+ const SkRect& bounds1 = one.getBounds();
+ const SkRect& bounds2 = two.getBounds();
+ SkRect larger = bounds1;
+ larger.join(bounds2);
+ SkBitmap bits;
+ int bitWidth = SkScalarCeil(larger.width()) + 2;
+ int bitHeight = SkScalarCeil(larger.height()) + 2;
+ bits.setConfig(SkBitmap::kARGB_8888_Config, bitWidth * 2, bitHeight);
+ bits.allocPixels();
+ SkCanvas canvas(bits);
+ canvas.drawColor(SK_ColorWHITE);
+ SkPaint paint;
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1);
+ canvas.drawPath(one, paint);
+ canvas.restore();
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1 + bitWidth, -bounds1.fTop + 1);
+ canvas.drawPath(two, paint);
+ canvas.restore();
+ for (int y = 0; y < bitHeight; ++y) {
+ uint32_t* addr1 = bits.getAddr32(0, y);
+ uint32_t* addr2 = bits.getAddr32(bitWidth, y);
+ for (int x = 0; x < bitWidth; ++x) {
+ if (addr1[x] != addr2[x]) {
+ return false;
+ break;
+ }
+ }
+ }
+ return true;
+}
+
+static void drawAsciiPaths(const SkPath& one, const SkPath& two,
+ bool drawPaths) {
+ if (!drawPaths) {
+ return;
+ }
+ if (0) {
+ showPath("one:", one);
+ showPath("two:", two);
+ }
+ const SkRect& bounds1 = one.getBounds();
+ const SkRect& bounds2 = two.getBounds();
+ SkRect larger = bounds1;
+ larger.join(bounds2);
+ SkBitmap bits;
+ int bitWidth = SkScalarCeil(larger.width()) + 2;
+ int bitHeight = SkScalarCeil(larger.height()) + 2;
+ bits.setConfig(SkBitmap::kARGB_8888_Config, bitWidth * 2, bitHeight);
+ bits.allocPixels();
+ SkCanvas canvas(bits);
+ canvas.drawColor(SK_ColorWHITE);
+ SkPaint paint;
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1);
+ canvas.drawPath(one, paint);
+ canvas.restore();
+ canvas.save();
+ canvas.translate(-bounds2.fLeft + 1 + bitWidth, -bounds2.fTop + 1);
+ canvas.drawPath(two, paint);
+ canvas.restore();
+ char out[1024];
+ SkASSERT(bitWidth * 2 + 1 < (int) sizeof(out));
+ for (int y = 0; y < bitHeight; ++y) {
+ uint32_t* addr1 = bits.getAddr32(0, y);
+ int x;
+ char* outPtr = out;
+ for (x = 0; x < bitWidth; ++x) {
+ *outPtr++ = addr1[x] == (uint32_t) -1 ? '_' : 'x';
+ }
+ *outPtr++ = '|';
+ for (x = bitWidth; x < bitWidth * 2; ++x) {
+ *outPtr++ = addr1[x] == (uint32_t) -1 ? '_' : 'x';
+ }
+ *outPtr++ = '\0';
+ SkDebugf("%s\n", out);
+ }
+}
+
+static bool scaledDrawTheSame(const SkPath& one, const SkPath& two,
+ int a, int b, bool drawPaths) {
+ SkMatrix scale;
+ scale.reset();
+ scale.preScale(a * 1.21f, b * 1.11f);
+ SkPath scaledOne, scaledTwo;
+ one.transform(scale, &scaledOne);
+ two.transform(scale, &scaledTwo);
+ if (pathsDrawTheSame(scaledOne, scaledTwo)) {
+ return true;
+ }
+ drawAsciiPaths(scaledOne, scaledTwo, drawPaths);
+ return false;
+}
+
+void comparePaths(const SkPath& one, const SkPath& two) {
+ if (pathsDrawTheSame(one, two)) {
+ return;
+ }
+ drawAsciiPaths(one, two, gDrawAllAsciiPaths);
+ for (int x = 9; x <= 33; ++x) {
+ if (scaledDrawTheSame(one, two, x, x - (x >> 2), gDrawAllAsciiPaths)) {
+ return;
+ }
+ }
+ if (!gDrawAllAsciiPaths) {
+ scaledDrawTheSame(one, two, 9, 7, gDrawLastAsciiPaths);
+ }
+ showPath("original:", one);
+ showPath("simplified:", two);
+ SkASSERT(0);
+}
+
+// doesn't work yet
+void comparePathsTiny(const SkPath& one, const SkPath& two) {
+ const SkRect& bounds1 = one.getBounds();
+ const SkRect& bounds2 = two.getBounds();
+ SkRect larger = bounds1;
+ larger.join(bounds2);
+ SkBitmap bits;
+ int bitWidth = SkScalarCeil(larger.width()) + 2;
+ int bitHeight = SkScalarCeil(larger.height()) + 2;
+ bits.setConfig(SkBitmap::kA1_Config, bitWidth * 2, bitHeight);
+ bits.allocPixels();
+ SkCanvas canvas(bits);
+ canvas.drawColor(SK_ColorWHITE);
+ SkPaint paint;
+ canvas.save();
+ canvas.translate(-bounds1.fLeft + 1, -bounds1.fTop + 1);
+ canvas.drawPath(one, paint);
+ canvas.restore();
+ canvas.save();
+ canvas.translate(-bounds2.fLeft + 1, -bounds2.fTop + 1);
+ canvas.drawPath(two, paint);
+ canvas.restore();
+ for (int y = 0; y < bitHeight; ++y) {
+ uint8_t* addr1 = bits.getAddr1(0, y);
+ uint8_t* addr2 = bits.getAddr1(bitWidth, y);
+ for (int x = 0; x < bits.rowBytes(); ++x) {
+ SkASSERT(addr1[x] == addr2[x]);
+ }
+ }
+}
+
// FIXME: Move this templated version into SKTSearch.h
template <typename T>
-static void QSort_Partition(T** first, T** last)
+static T** QSort_Partition(T** left, T** right, T** pivot)
{
- T** left = first;
- T** rite = last;
- T** pivot = left;
-
- while (left <= rite) {
- while (left < last && **left < **pivot)
- left += 1;
- while (first < rite && **pivot < **rite)
- rite -= 1;
- if (left <= rite) {
- if (left < rite) {
- SkTSwap(*left, *rite);
- }
- left += 1;
- rite -= 1;
+ T* pivotValue = *pivot;
+ SkTSwap(*pivot, *right);
+ T** newPivot = left;
+ while (left < right) {
+ if (**left < *pivotValue) {
+ SkTSwap(*left, *newPivot);
+ newPivot += 1;
}
+ left += 1;
}
- if (first < rite)
- QSort_Partition(first, rite);
- if (left < last)
- QSort_Partition(left, last);
+ SkTSwap(*newPivot, *right);
+ return newPivot;
}
template <typename T>
-void QSort(T** base, size_t count)
+void QSort(T** left, T** right)
{
- SkASSERT(base);
-
- if (count <= 1) {
+ if (left >= right) {
return;
}
- QSort_Partition(base, base + (count - 1));
+ 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>