#include "SkTDArray.h"
#include "TSearch.h"
-static bool gShowDebugf = true; // FIXME: remove once debugging is complete
+static bool gShowDebugf = false; // FIXME: remove once debugging is complete
static bool gShowPath = false;
-static bool gDebugLessThan = true;
+static bool gDebugLessThan = false;
static int LineIntersect(const SkPoint a[2], const SkPoint b[2],
double aRange[2], double bRange[2]) {
if (listIndex >= listCount) {
break;
}
+ int closeEdgeIndex = -listIndex - 1;
SkPoint firstPt, lastLine[2];
bool doMove = true;
- bool closed = false;
int edgeIndex;
do {
SkPoint* ptArray = fEdges[listIndex].fPts;
lastLine[0] = *start;
lastLine[1] = *end;
doMove = false;
- closed = false;
break;
}
gap = lastLine[1] != *start;
lastLine[0] = *start;
}
lastLine[1] = *end;
- if (firstPt == *end) {
- simple.lineTo(end->fX, end->fY);
- simple.close();
- if (gShowDebugf) {
- SkDebugf("%s close 1 (%g, %g)\n", __FUNCTION__,
- end->fX, end->fY);
- }
- closed = true;
- }
break;
default:
// FIXME: add other curve types
edgeIndex = fBottoms[listIndex];
fBottoms[listIndex] = 0;
}
- if (!edgeIndex) {
+ if (edgeIndex) {
+ listIndex = abs(edgeIndex) - 1;
+ if (edgeIndex < 0) {
+ fTops[listIndex] = 0;
+ } else {
+ fBottoms[listIndex] = 0;
+ }
+ }
+ if (edgeIndex == closeEdgeIndex || edgeIndex == 0) {
+ if (lastLine[1] != firstPt) {
+ simple.lineTo(lastLine[1].fX, lastLine[1].fY);
+ }
simple.lineTo(firstPt.fX, firstPt.fY);
simple.close();
if (gShowDebugf) {
- SkDebugf("%s close 2 (%g,%g)\n", __FUNCTION__,
- firstPt.fX, firstPt.fY);
+ SkDebugf("%s close (%g, %g)\n", __FUNCTION__,
+ firstPt.fX, firstPt.fY);
}
break;
}
- listIndex = abs(edgeIndex) - 1;
- if (edgeIndex < 0) {
- fTops[listIndex] = 0;
- } else {
- fBottoms[listIndex] = 0;
- }
// if this and next edge go different directions
if (advance > 0 ^ edgeIndex < 0) {
advance = -advance;
}
- } while (edgeIndex && !closed);
+ } while (edgeIndex);
} while (true);
}
}
};
-struct Intercepts {
+class Intercepts {
+public:
+ Intercepts()
+ : fTopIntercepts(0)
+ , fBottomIntercepts(0) {
+ }
SkTDArray<double> fTs;
+ int fTopIntercepts;
+ int fBottomIntercepts;
};
struct InEdge {
: fBounds.fTop < rh.fBounds.fTop;
}
- bool add(double* ts, size_t count, ptrdiff_t verbIndex) {
+ void add(double* ts, size_t count, ptrdiff_t verbIndex) {
// FIXME: in the pathological case where there is a ton of intercepts, binary search?
bool foundIntercept = false;
Intercepts& intercepts = fIntercepts[verbIndex];
for (size_t index = 0; index < count; ++index) {
double t = ts[index];
- if (t <= 0 || t >= 1) {
+ if (t <= 0) {
+ fContainsIntercepts |= ++intercepts.fTopIntercepts > 1;
+ continue;
+ }
+ if (t >= 1) {
+ fContainsIntercepts |= ++intercepts.fBottomIntercepts > 1;
continue;
}
foundIntercept = true;
if (delta > 0) {
*intercepts.fTs.insert(idx2) = t;
}
- return foundIntercept;
+ fContainsIntercepts = true;
+ return;
}
}
if (tCount == 0 || t > intercepts.fTs[tCount - 1]) {
*intercepts.fTs.append() = t;
}
}
- return foundIntercept;
+ fContainsIntercepts |= foundIntercept;
}
bool cached(const InEdge* edge) {
// 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;
void calcLeft(SkScalar y) {
// OPTIMIZE: put a kDone_Verb at the end of the verb list?
- if (done(y))
+ if (fDone || fBelow.fY > y) {
return; // nothing to do; use last
+ }
calcLeft();
}
void init(const InEdge* edge) {
fWorkEdge.init(edge);
initT();
+ fBelow.fY = SK_ScalarMin;
fDone = false;
fYBottom = SK_ScalarMin;
}
// 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) {
+ if (fAbove != edge->fAbove || fBelow != edge->fBelow) {
return false;
}
uint8_t verb = fDone ? fWorkEdge.lastVerb() : fWorkEdge.verb();
}
switch (verb) {
case SkPath::kLine_Verb: {
- 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);
+ return true;
}
default:
// FIXME: add support for all curve types
active->init(edge);
}
- // find any intersections in the range of active edges
+// Find any intersections in the range of active edges. A pair of edges, on
+// either side of another edge, may change the winding contribution for part of
+// the edge.
+// OPTIMIZATION: Another approach would be to keep horizontal edges just for
+// the purpose of computing when edges change their winding contribution, since
+// this is essentially computing the horizontal intersection.
static void addBottomT(InEdge** currentPtr, InEdge** lastPtr, SkScalar bottom) {
InEdge** testPtr = currentPtr;
InEdge* test = *testPtr;
double wtTs[2];
int pts = LineIntersect(wt.fPts, bottom, wtTs);
if (pts) {
- test->fContainsIntercepts |= test->add(wtTs, pts,
- wt.verbIndex());
+ test->add(wtTs, pts, wt.verbIndex());
}
} else {
// FIXME: add all curve types
double wtTs[2], wnTs[2];
int pts = LineIntersect(wt.fPts, wn.fPts, wtTs, wnTs);
if (pts) {
- test->fContainsIntercepts |= test->add(wtTs, pts,
- wt.verbIndex());
- next->fContainsIntercepts |= next->add(wnTs, pts,
- wn.verbIndex());
+ test->add(wtTs, pts, wt.verbIndex());
+ next->add(wnTs, pts, wn.verbIndex());
}
} else {
// FIXME: add all combinations of curve types
wt.init(test);
do {
const Intercepts& intercepts = test->fIntercepts[wt.verbIndex()];
+ if (intercepts.fTopIntercepts > 1) {
+ SkScalar yTop = wt.fPts[0].fY;
+ if (yTop > y && bottom > yTop) {
+ bottom = yTop;
+ }
+ }
+ if (intercepts.fBottomIntercepts > 1) {
+ SkScalar yBottom = wt.fPts[wt.verb()].fY;
+ if (yBottom > y && bottom > yBottom) {
+ bottom = yBottom;
+ }
+ }
const SkTDArray<double>& fTs = intercepts.fTs;
size_t count = fTs.count();
for (size_t index = 0; index < count; ++index) {
// and not all at once as is done here, fold this test into the
// current less than test.
if (activePtr->swapCoincident(nextPtr, bottom)) {
+ winding -= activePtr->fWorkEdge.winding();
SkTSwap<ActiveEdge*>(edgeList[index - 1], edgeList[index]);
SkTSwap<ActiveEdge*>(activePtr, nextPtr);
+ winding += activePtr->fWorkEdge.winding();
}
if (!firstCoincident) {
firstCoincident = activePtr;
bool closer = (winding & windingMask) == 0;
SkASSERT(!opener | !closer);
bool inWinding = opener | closer;
- const SkPoint* clipped;
+ SkPoint clippedPts[2];
+ const SkPoint* clipped = NULL;
uint8_t verb = wt.verb();
bool moreToDo, aboveBottom;
do {
do {
nextT = activePtr->nextT();
if (verb == SkPath::kLine_Verb) {
- SkPoint clippedPts[2];
// FIXME: obtuse: want efficient way to say
// !currentT && currentT != 1 || !nextT && nextT != 1
if (currentT * nextT != 0 || currentT + nextT != 1) {
}
outBuilder.addLine(clipped);
}
+ if (clipped[1].fY > activePtr->fBelow.fY
+ && bottom >= activePtr->fBelow.fY ) {
+ activePtr->fAbove = activePtr->fBelow;
+ activePtr->fBelow = clipped[1];
+ }
activePtr->fSkip = false;
} else {
// FIXME: add all curve types
InEdge edgeSentinel;
makeEdgeList(edges, edgeSentinel, edgeList);
InEdge** currentPtr = edgeList.begin();
+ if (!currentPtr) {
+ return;
+ }
// walk the sorted edges from top to bottom, computing accumulated winding
SkTDArray<ActiveEdge> activeEdges;
OutEdgeBuilder outBuilder(asFill);
comparePaths(path, out);
}
+static void testSimplifyTriangle18() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(0, 1);
+ path.lineTo(0, 3);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle19() {
+ SkPath path, out;
+ path.setFillType(SkPath::kEvenOdd_FillType);
+ path.moveTo(0, 0);
+ path.lineTo(0, 1);
+ path.lineTo(3, 2);
+ 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 testSimplifyTriangle20() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(2, 1);
+ path.lineTo(1, 3);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(3, 2);
+ path.lineTo(0, 3);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyTriangle21() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(1, 2);
+ path.close();
+ path.moveTo(2, 0);
+ path.lineTo(2, 1);
+ path.lineTo(0, 3);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyDegenerateTriangle1() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(0, 0);
+ path.close();
+ path.moveTo(0, 0);
+ path.lineTo(0, 0);
+ path.lineTo(0, 0);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplifyDegenerateTriangle2() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 1);
+ path.lineTo(2, 2);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(2, 2);
+ path.lineTo(3, 3);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
static void testSimplifyWindingParallelogram() {
SkPath path, out;
path.setFillType(SkPath::kWinding_FillType);
}
}
+static void testSimplifyDegenerate4x4Triangles() {
+ char pathStr[1024];
+ bzero(pathStr, sizeof(pathStr));
+ for (int a = 0; a < 16; ++a) {
+ int ax = a & 0x03;
+ int ay = a >> 2;
+ for (int b = a ; b < 16; ++b) {
+ int bx = b & 0x03;
+ int by = b >> 2;
+ for (int c = a ; c < 16; ++c) {
+ int cx = c & 0x03;
+ int cy = c >> 2;
+ bool abcIsATriangle = (bx - ax) * (cy - ay)
+ != (by - ay) * (cx - ax);
+ for (int d = 0; d < 16; ++d) {
+ int dx = d & 0x03;
+ int dy = d >> 2;
+ for (int e = d ; e < 16; ++e) {
+ int ex = e & 0x03;
+ int ey = e >> 2;
+ for (int f = d ; f < 16; ++f) {
+ int fx = f & 0x03;
+ int fy = f >> 2;
+ if (abcIsATriangle && (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);
}
static void (*simplifyTests[])() = {
+ testSimplifyDegenerateTriangle2,
+ testSimplifyDegenerateTriangle1,
+ testSimplifyTriangle21,
+ testSimplifyTriangle20,
+ testSimplifyTriangle19,
+ testSimplifyTriangle18,
testSimplifyTriangle17,
testSimplifyTriangle16,
testSimplifyTriangle15,
testSimplifyTriangle2,
testSimplifyWindingParallelogram,
testSimplifyXorParallelogram,
+ testSimplifyDegenerate4x4Triangles,
testSimplifyNondegenerate4x4Triangles,
testPathTriangleRendering,
};
--- /dev/null
+#include "EdgeWalker_Test.h"
+#include "Intersection_Tests.h"
+
+static void testSimplifyQuad1() {
+ SkPath path, out;
+ path.moveTo(0, 0);
+ path.lineTo(1, 0);
+ path.lineTo(3, 2);
+ path.lineTo(3, 3);
+ path.close();
+ path.moveTo(1, 0);
+ path.lineTo(1, 3);
+ path.lineTo(1, 3);
+ path.lineTo(1, 3);
+ path.close();
+ simplify(path, true, out);
+ comparePaths(path, out);
+}
+
+static void testSimplify4x4Quadralaterals() {
+ char pathStr[1024];
+ bzero(pathStr, sizeof(pathStr));
+ for (int a = 0; a < 16; ++a) {
+ int ax = a & 0x03;
+ int ay = a >> 2;
+ for (int b = a ; b < 16; ++b) {
+ int bx = b & 0x03;
+ int by = b >> 2;
+ for (int c = b ; c < 16; ++c) {
+ int cx = c & 0x03;
+ int cy = c >> 2;
+ for (int d = c; d < 16; ++d) {
+ int dx = d & 0x03;
+ int dy = d >> 2;
+ for (int e = 0 ; e < 16; ++e) {
+ int ex = e & 0x03;
+ int ey = e >> 2;
+ for (int f = e ; f < 16; ++f) {
+ int fx = f & 0x03;
+ int fy = f >> 2;
+ for (int g = f ; g < 16; ++g) {
+ int gx = g & 0x03;
+ int gy = g >> 2;
+ for (int h = g ; g < 16; ++g) {
+ int hx = h & 0x03;
+ int hy = h >> 2;
+ SkPath path, out;
+ path.setFillType(SkPath::kWinding_FillType);
+ path.moveTo(ax, ay);
+ path.lineTo(bx, by);
+ path.lineTo(cx, cy);
+ path.lineTo(dx, dy);
+ path.close();
+ path.moveTo(ex, ey);
+ path.lineTo(fx, fy);
+ path.lineTo(gx, gy);
+ path.lineTo(hx, hy);
+ 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.lineTo(%d, %d);\n", dx, dy);
+ str += sprintf(str, " path.close();\n");
+ str += sprintf(str, " path.moveTo(%d, %d);\n", ex, ey);
+ str += sprintf(str, " path.lineTo(%d, %d);\n", fx, fy);
+ str += sprintf(str, " path.lineTo(%d, %d);\n", gx, gy);
+ str += sprintf(str, " path.lineTo(%d, %d);\n", hx, hy);
+ 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 (*simplifyTests[])() = {
+ testSimplifyQuad1,
+ testSimplify4x4Quadralaterals,
+};
+
+static size_t simplifyTestsCount = sizeof(simplifyTests) / sizeof(simplifyTests[0]);
+
+static void (*firstTest)() = 0;
+
+void SimplifyQuadralateralPaths_Test() {
+ size_t index = 0;
+ if (firstTest) {
+ while (index < simplifyTestsCount && simplifyTests[index] != firstTest) {
+ ++index;
+ }
+ }
+ for ( ; index < simplifyTestsCount; ++index) {
+ (*simplifyTests[index])();
+ }
+}
LineQuadraticIntersection_Test();
LineCubicIntersection_Test();
- SimplifyPolygonPaths_Test();
SimplifyRectangularPaths_Test();
+ SimplifyPolygonPaths_Test();
+ SimplifyQuadralateralPaths_Test();
QuadraticCoincidence_Test();
QuadraticReduceOrder_Test();
void LineParameter_Test();
void LineQuadraticIntersection_Test();
void SimplifyPolygonPaths_Test();
+void SimplifyQuadralateralPaths_Test();
void SimplifyRectangularPaths_Test();
void QuadraticBezierClip_Test();
void QuadraticCoincidence_Test();
bRange[0] = aMin <= bMin ? 0 : (aMin - bMin) / (bMax - bMin);
bRange[1] = aMax >= bMax ? 1 : (aMax - bMin) / (bMax - bMin);
}
- return 2;
+ return 1 + ((aRange[0] != aRange[1]) || (bRange[0] != bRange[1]));
}
}
double ab0y = a[0].y - b[0].y;
FECAA6C714BDCE9B00B35E2C /* QuadraticIntersection_Test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FECAA6C614BDCE9B00B35E2C /* QuadraticIntersection_Test.cpp */; };
FECAA6E114BDDF2D00B35E2C /* QuadraticReduceOrder_Test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FECAA6E014BDDF2D00B35E2C /* QuadraticReduceOrder_Test.cpp */; };
FED53C391483CB9400F6359E /* Inline_Tests.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FED53C381483CB9400F6359E /* Inline_Tests.cpp */; };
+ FED865F915056A79006F4508 /* EdgeWalkerQuadralaterals_Test.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FED865F815056A79006F4508 /* EdgeWalkerQuadralaterals_Test.cpp */; };
FEED7245144DD2250059E97B /* SkEventNotifier.mm in Sources */ = {isa = PBXBuildFile; fileRef = FEED723E144DD2250059E97B /* SkEventNotifier.mm */; };
FEED7292144DD4610059E97B /* libexperimental.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FEED726E144DD4050059E97B /* libexperimental.a */; };
FEED7293144DD4620059E97B /* libskgr.a in Frameworks */ = {isa = PBXBuildFile; fileRef = FEED7276144DD4140059E97B /* libskgr.a */; };
FECAAB7F14BDFAFD00B35E2C /* CubicParameterization_TestUtility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = CubicParameterization_TestUtility.cpp; sourceTree = "<group>"; };
FECAACA614BE1C6100B35E2C /* QuadraticParameterization_TestUtility.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = QuadraticParameterization_TestUtility.cpp; sourceTree = "<group>"; };
FED53C381483CB9400F6359E /* Inline_Tests.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Inline_Tests.cpp; sourceTree = "<group>"; };
+ FED865F815056A79006F4508 /* EdgeWalkerQuadralaterals_Test.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EdgeWalkerQuadralaterals_Test.cpp; sourceTree = "<group>"; };
FEED723C144DD2250059E97B /* SampleApp.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = SampleApp.xib; path = ../../src/utils/mac/SampleApp.xib; sourceTree = SOURCE_ROOT; };
FEED723D144DD2250059E97B /* SampleAppDelegate.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = SampleAppDelegate.mm; path = ../../src/utils/mac/SampleAppDelegate.mm; sourceTree = SOURCE_ROOT; };
FEED723E144DD2250059E97B /* SkEventNotifier.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = SkEventNotifier.mm; path = ../../src/utils/mac/SkEventNotifier.mm; sourceTree = SOURCE_ROOT; };
FEED7673144F2D770059E97B /* TestUtilities.h */,
FEED764B144F29BD0059E97B /* TestUtilities.cpp */,
FE7413DB14F6926D00056D7B /* EdgeWalker_Test.h */,
+ FED865F815056A79006F4508 /* EdgeWalkerQuadralaterals_Test.cpp */,
);
name = Tests;
sourceTree = "<group>";
FEA61B2C14F2AF6600B736CB /* EdgeWalkerRectangles_Test.cpp in Sources */,
FE7413D414F6915A00056D7B /* EdgeWalkerPolygons_Test.cpp in Sources */,
FE7413D814F691C200056D7B /* EdgeWalker_TestUtility.cpp in Sources */,
+ FED865F915056A79006F4508 /* EdgeWalkerQuadralaterals_Test.cpp in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};