shape ops work in progress

git-svn-id: http://skia.googlecode.com/svn/trunk@4815 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
caryclark@google.com 2012-07-27 18:26:38 +00:00
parent be8cefcc07
commit 27c449af06
6 changed files with 244 additions and 86 deletions

View File

@ -26,6 +26,7 @@ struct State4 {
int b;
int c;
int d;
int testsRun;
char filename[256];
pthread_t threadID;
SkCanvas* canvas;

View File

@ -50,7 +50,7 @@ const bool gRunTestsInOneThread = true;
#define DEBUG_ACTIVE_SPANS 1
#define DEBUG_ADD_INTERSECTING_TS 0
#define DEBUG_ADD_T_PAIR 0
#define DEBUG_CONCIDENT 01
#define DEBUG_CONCIDENT 0
#define DEBUG_CROSS 1
#define DEBUG_DUMP 1
#define DEBUG_MARK_DONE 1
@ -656,7 +656,7 @@ struct Bounds : public SkRect {
struct Span {
Segment* fOther;
mutable SkPoint const* fPt; // lazily computed as needed
mutable SkPoint fPt; // lazily computed as needed
double fT;
double fOtherT; // value at fOther[fOtherIndex].fT
int fOtherIndex; // can't be used during intersection
@ -792,10 +792,10 @@ public:
#if DEBUG_CONCIDENT
SkDebugf("%s 1 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
__FUNCTION__, fID, other.fID, tIndexStart - 1,
fTs[tIndexStart - 1].fT, xyAtT(tIndexStart - 1).fX,
xyAtT(tIndexStart - 1).fY);
fTs[tIndexStart].fT, xyAtT(tIndexStart).fX,
xyAtT(tIndexStart).fY);
#endif
SkASSERT(0); // incomplete
addTPair(fTs[tIndexStart].fT, other, other.fTs[oIndex].fT);
}
if (nextT < 1 && fTs[tIndex].fWindValue) {
#if DEBUG_CONCIDENT
@ -812,10 +812,10 @@ public:
#if DEBUG_CONCIDENT
SkDebugf("%s 3 this=%d other=%d t [%d] %1.9g (%1.9g,%1.9g)\n",
__FUNCTION__, fID, other.fID, oIndexStart - 1,
other.fTs[oIndexStart - 1].fT, other.xyAtT(oIndexStart - 1).fX,
other.xyAtT(oIndexStart - 1).fY);
other.fTs[oIndexStart].fT, other.xyAtT(oIndexStart).fX,
other.xyAtT(oIndexStart).fY);
other.debugAddTPair(other.fTs[oIndexStart].fT, *this, fTs[tIndex].fT);
#endif
SkASSERT(0); // incomplete
}
if (oNextT < 1 && other.fTs[oIndex].fWindValue) {
#if DEBUG_CONCIDENT
@ -965,7 +965,7 @@ public:
}
span->fT = newT;
span->fOther = other;
span->fPt = NULL;
span->fPt.fX = SK_ScalarNaN;
span->fWindSum = SK_MinS32;
span->fWindValue = 1;
if ((span->fDone = newT == 1)) {
@ -1068,7 +1068,7 @@ public:
do {
if (transfer) {
if (decrementOther) {
SkASSERT(abs(end->fWindValue) < gDebugMaxWindValue);
SkASSERT(abs(end->fWindValue) <= gDebugMaxWindValue);
++(end->fWindValue);
} else if (decrementSpan(end)) {
TrackOutside(outsideTs, end->fT, oStartT);
@ -1085,7 +1085,7 @@ public:
do {
if (transfer) {
if (!decrementOther) {
SkASSERT(abs(oEnd->fWindValue) < gDebugMaxWindValue);
SkASSERT(abs(oEnd->fWindValue) <= gDebugMaxWindValue);
++(oEnd->fWindValue);
} else if (other.decrementSpan(oEnd)) {
TrackOutside(oOutsideTs, oEnd->fT, startT);
@ -1320,28 +1320,21 @@ public:
// it is guaranteed to have an end which describes a non-zero length (?)
// winding -1 means ccw, 1 means cw
// firstFind allows coincident edges to be treated differently
Segment* findNext(SkTDArray<Span*>& chase, int winding,
int contourWinding, bool firstFind, bool active,
Segment* findNext(SkTDArray<Span*>& chase, bool firstFind, bool active,
const int startIndex, const int endIndex, int& nextStart,
int& nextEnd, int& spanWinding) {
start here;
// winding is a mess
// try to simplify what we got
int flipped = 1;
int& nextEnd, int& winding, int& spanWinding) {
int sumWinding = winding + spanWinding;
if (sumWinding == 0 || (false && contourWinding && !firstFind)) {
if (sumWinding == 0) {
sumWinding = spanWinding;
}
bool insideContour = contourWinding && contourWinding * sumWinding < 0;
if (insideContour && (true || !firstFind)) {
sumWinding = contourWinding;
bool insideContour = active && winding && winding * sumWinding < 0;
if (insideContour) {
sumWinding = winding;
}
#if DEBUG_WINDING
SkDebugf("%s winding=%d contourWinding=%d spanWinding=%d sumWinding=%d\n",
__FUNCTION__, winding, contourWinding, spanWinding, sumWinding);
SkDebugf("%s winding=%d spanWinding=%d sumWinding=%d\n",
__FUNCTION__, winding, spanWinding, sumWinding);
#endif
SkASSERT(startIndex != endIndex);
int count = fTs.count();
@ -1378,7 +1371,7 @@ public:
int firstIndex = findStartingEdge(sorted, startIndex, end);
SkASSERT(firstIndex >= 0);
#if DEBUG_SORT
debugShowSort(sorted, firstIndex, contourWinding, sumWinding);
debugShowSort(sorted, firstIndex, winding, sumWinding);
#endif
bool doBump = sorted[firstIndex]->firstBump(sumWinding);
#if DEBUG_WINDING
@ -1396,8 +1389,9 @@ public:
const Angle* foundAngle = NULL;
bool foundDone = false;
// iterate through the angle, and compute everyone's winding
bool firstEdge = true;
bool flopped = false;
bool toggleWinding = false;
bool flipFound = false;
int flipped = 1;
Segment* nextSegment;
do {
if (nextIndex == angleCount) {
@ -1413,13 +1407,12 @@ public:
maxWinding, sumWinding, nextAngle->sign());
#endif
if (maxWinding * sumWinding < 0) {
flipped = -flipped;
flopped = true;
flipFound ^= true;
#if DEBUG_WINDING
SkDebugf("flipped sign %d %d\n", maxWinding, sumWinding);
SkDebugf("flipFound maxWinding=%d sumWinding=%d\n",
maxWinding, sumWinding);
#endif
}
firstEdge = false;
if (!sumWinding) {
if (!active) {
markDone(SkMin32(startIndex, endIndex), startWinding);
@ -1433,10 +1426,11 @@ public:
if (!foundAngle || foundDone) {
foundAngle = nextAngle;
foundDone = nextSegment->done(*nextAngle);
if (!flopped && maxWinding * startWinding < 0) {
if (flipFound || (maxWinding * startWinding < 0)) {
flipped = -flipped;
#if DEBUG_WINDING
SkDebugf("flopped sign %d %d\n", maxWinding, startWinding);
SkDebugf("flipped flipFound=%d maxWinding=%d startWinding=%d\n",
flipFound, maxWinding, startWinding);
#endif
}
}
@ -1444,10 +1438,20 @@ public:
}
if (!maxWinding && innerSwap && !foundAngle) {
if (sumWinding * startWinding < 0 && flipped > 0) {
SkDebugf("%s flip?\n");
// flipped = -flipped;
#if DEBUG_WINDING
SkDebugf("%s toggleWinding\n");
#endif
toggleWinding = true;
} else if (startWinding != sumWinding) {
winding = sumWinding;
}
foundAngle = nextAngle;
if (flipFound) {
flipped = -1;
#if DEBUG_WINDING
SkDebugf("flipped flipFound=%d\n", flipFound);
#endif
}
}
if (nextSegment->done()) {
continue;
@ -1481,6 +1485,20 @@ public:
nextSegment = foundAngle->segment();
spanWinding = SkSign32(spanWinding) * flipped * nextSegment->windValue(
SkMin32(nextStart, nextEnd));
if (toggleWinding) {
if (winding) {
winding = 0;
} else {
winding = -startWinding;
}
}
#if 0
int min = SkMin32(nextStart, nextEnd);
int sign = foundAngle->sign();
int windSum = nextSegment->windSum(min);
int windValue = nextSegment->windValue(min);
SkASSERT(winding + sign * windValue == windSum);
#endif
#if DEBUG_WINDING
SkDebugf("%s spanWinding=%d\n", __FUNCTION__, spanWinding);
#endif
@ -2013,18 +2031,16 @@ public:
}
const SkPoint& xyAtT(const Span* span) const {
if (!span->fPt) {
if (SkScalarIsNaN(span->fPt.fX)) {
if (span->fT == 0) {
span->fPt = &fPts[0];
span->fPt = fPts[0];
} else if (span->fT == 1) {
span->fPt = &fPts[fVerb];
span->fPt = fPts[fVerb];
} else {
SkPoint* pt = fIntersections.append();
(*SegmentXYAtT[fVerb])(fPts, span->fT, pt);
span->fPt = pt;
(*SegmentXYAtT[fVerb])(fPts, span->fT, &span->fPt);
}
}
return *span->fPt;
return span->fPt;
}
SkScalar yAtT(int index) const {
@ -2153,10 +2169,6 @@ private:
SkPath::Verb fVerb;
Bounds fBounds;
SkTDArray<Span> fTs; // two or more (always includes t=0 t=1)
// OPTIMIZATION:if intersections array is a pointer, the it could only
// be allocated as needed instead of always initialized -- though maybe
// the initialization is lightweight enough that it hardly matters
mutable SkTDArray<SkPoint> fIntersections;
int fDoneSpans; // used for quick check that segment is finished
#if DEBUG_DUMP
int fID;
@ -3250,6 +3262,11 @@ static void debugShowActiveSpans(SkTDArray<Contour*>& contourList) {
}
#endif
static bool windingIsActive(int winding, int spanWinding) {
return winding * spanWinding <= 0 && abs(winding) <= abs(spanWinding)
&& (!winding || !spanWinding || winding == -spanWinding);
}
// Each segment may have an inside or an outside. Segments contained within
// winding may have insides on either side, and form a contour that should be
// ignored. Segments that are coincident with opposing direction segments may
@ -3286,21 +3303,26 @@ static void bridge(SkTDArray<Contour*>& contourList, SkPath& simple) {
bool firstTime = true;
int winding = contourWinding;
int spanWinding = current->spanSign(index, endIndex);
// int firstWinding = contourWinding + spanWinding;
int spanWindSum = current->windSum(SkMin32(index, endIndex));
if (spanWindSum != SK_MinS32) {
int calcWinding = spanWindSum;
if (spanWinding > 0) {
// calcWinding -= spanWinding;
}
SkDebugf("%s *** winding=%d calcWinding=%d\n", __FUNCTION__,
winding, calcWinding);
winding = calcWinding;
}
// FIXME: needs work. While it works in limited situations, it does
// not always compute winding correctly. Active should be removed and instead
// the initial winding should be correctly passed in so that if the
// inner contour is wound the same way, it never finds an accumulated
// winding of zero. Inside 'find next', we need to look for transitions
// other than zero when resolving sorted angles.
bool active = windingIsActive(winding, spanWinding);
SkTDArray<Span*> chaseArray;
do {
bool active = winding * spanWinding <= 0
&& abs(winding) <= abs(spanWinding);
#if DEBUG_WINDING
if (abs(winding) > abs(spanWinding) && winding * spanWinding < 0) {
SkDebugf("%s *** unexpected active?\n", __FUNCTION__);
}
SkDebugf("%s active=%s winding=%d spanWinding=%d\n",
__FUNCTION__, active ? "true" : "false",
winding, spanWinding);
@ -3309,9 +3331,9 @@ static void bridge(SkTDArray<Contour*>& contourList, SkPath& simple) {
do {
SkASSERT(!current->done());
int nextStart, nextEnd;
Segment* next = current->findNext(chaseArray, winding,
contourWinding, firstTime, active, index, endIndex,
nextStart, nextEnd, spanWinding);
Segment* next = current->findNext(chaseArray,
firstTime, active, index, endIndex,
nextStart, nextEnd, winding, spanWinding);
if (!next) {
break;
}
@ -3341,30 +3363,27 @@ static void bridge(SkTDArray<Contour*>& contourList, SkPath& simple) {
spanWinding = current->windSum(lesser);
int spanValue = current->windValue(lesser);
SkASSERT(spanWinding != SK_MinS32);
int spanSign = current->spanSign(index, endIndex);
#if DEBUG_WINDING
SkDebugf("%s spanWinding=%d spanSign=%d winding=%d spanValue=%d\n",
__FUNCTION__, spanWinding, spanSign, winding, spanValue);
SkDebugf("%s spanWinding=%d winding=%d spanValue=%d\n",
__FUNCTION__, spanWinding, winding, spanValue);
#endif
if (spanWinding * spanSign < 0) {
#if DEBUG_WINDING
SkDebugf("%s spanWinding * spanSign < 0\n", __FUNCTION__);
#endif
// SkTSwap<int>(index, endIndex);
}
if (abs(spanWinding) > spanValue) {
if (abs(spanWinding) != spanValue) {
winding = spanWinding;
spanWinding = spanValue * SkSign32(spanWinding);
winding -= spanWinding;
#if DEBUG_WINDING
SkDebugf("%s spanWinding=%d winding=%d\n", __FUNCTION__,
SkDebugf("%s != spanWinding=%d winding=%d\n", __FUNCTION__,
spanWinding, winding);
#endif
} else {
active = windingIsActive(winding, spanWinding);
} else if (winding) {
#if DEBUG_WINDING
SkDebugf("%s ->0 contourWinding=%d winding=%d\n", __FUNCTION__,
contourWinding, winding);
#endif
// start here;
// set active=false if it was false when chase was created
active = abs(winding) <= abs(spanWinding);
winding = 0;
}
} while (true);

View File

@ -18,7 +18,7 @@ namespace SimplifyFindNextTest {
#include "Intersection_Tests.h"
static const SimplifyFindNextTest::Segment* testCommon(
int winding, int startIndex, int endIndex,
int contourWinding, int spanWinding, int startIndex, int endIndex,
SkTArray<SimplifyFindNextTest::Contour>& contours) {
SkTDArray<SimplifyFindNextTest::Contour*> contourList;
makeContourList(contours, contourList);
@ -34,8 +34,9 @@ static const SimplifyFindNextTest::Segment* testCommon(
pts[0] = segment.xyAtT(&segment.span(endIndex));
int nextStart, nextEnd;
SkTDArray<SimplifyFindNextTest::Span*> chaseArray;
SimplifyFindNextTest::Segment* next = segment.findNext(chaseArray, winding,
0, true, true, startIndex, endIndex, nextStart, nextEnd, winding);
SimplifyFindNextTest::Segment* next = segment.findNext(chaseArray,
true, true, startIndex, endIndex, nextStart, nextEnd,
contourWinding, spanWinding);
pts[1] = next->xyAtT(&next->span(nextStart));
SkASSERT(pts[0] == pts[1]);
return next;
@ -44,17 +45,19 @@ static const SimplifyFindNextTest::Segment* testCommon(
static void test(const SkPath& path) {
SkTArray<SimplifyFindNextTest::Contour> contours;
SimplifyFindNextTest::EdgeBuilder builder(path, contours);
int winding = 0;
int contourWinding = 0;
int spanWinding = 1;
int start = 0;
int end = 1;
testCommon(winding, start, end, contours);
testCommon(contourWinding, spanWinding, start, end, contours);
}
static void test(const SkPath& path, int start, int end) {
SkTArray<SimplifyFindNextTest::Contour> contours;
SimplifyFindNextTest::EdgeBuilder builder(path, contours);
int winding = 0;
testCommon(winding, start, end, contours);
int contourWinding = 0;
int spanWinding = 1;
testCommon(contourWinding, spanWinding, start, end, contours);
}
static void testLine1() {

View File

@ -582,12 +582,78 @@ static void testLine59() {
testSimplifyx(path);
}
static void (*firstTest)() = 0;
static void testLine60() {
SkPath path, simple;
path.addRect(0, 0, 20, 20, (SkPath::Direction) 0);
path.addRect(6, 12, 18, 18, (SkPath::Direction) 1);
path.addRect(4, 12, 13, 13, (SkPath::Direction) 1);
testSimplifyx(path);
}
static void testLine61() {
SkPath path, simple;
path.addRect(0, 0, 20, 20, (SkPath::Direction) 0);
path.addRect(12, 0, 24, 24, (SkPath::Direction) 1);
path.addRect(12, 0, 21, 21, (SkPath::Direction) 1);
testSimplifyx(path);
}
static void testLine62() {
SkPath path, simple;
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(0, 0, 20, 20, (SkPath::Direction) 0);
path.addRect(0, 12, 12, 12, (SkPath::Direction) 0);
path.addRect(4, 12, 13, 13, (SkPath::Direction) 1);
testSimplifyx(path);
}
static void testLine63() {
SkPath path, simple;
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(0, 10, 20, 20, (SkPath::Direction) 0);
path.addRect(0, 6, 12, 12, (SkPath::Direction) 1);
path.addRect(0, 32, 9, 36, (SkPath::Direction) 1);
testSimplifyx(path);
}
static void testLine64() {
SkPath path, simple;
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(10, 40, 30, 30, (SkPath::Direction) 0);
path.addRect(18, 6, 30, 30, (SkPath::Direction) 0);
testSimplifyx(path);
}
static void testLine65() {
SkPath path, simple;
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(10, 0, 30, 30, (SkPath::Direction) 0);
path.addRect(24, 0, 36, 36, (SkPath::Direction) 0);
path.addRect(32, 6, 36, 41, (SkPath::Direction) 1);
testSimplifyx(path);
}
static void testLine66() {
SkPath path, simple;
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(0, 30, 20, 20, (SkPath::Direction) 0);
path.addRect(12, 20, 24, 30, (SkPath::Direction) 0);
testSimplifyx(path);
}
static void (*firstTest)() = testLine66;
static struct {
void (*fun)();
const char* str;
} tests[] = {
TEST(testLine66),
TEST(testLine65),
TEST(testLine64),
TEST(testLine63),
TEST(testLine62),
TEST(testLine61),
TEST(testLine60),
TEST(testLine59),
TEST(testLine58),
TEST(testLine57),
@ -661,8 +727,8 @@ void SimplifyNew_Test() {
return;
}
#ifdef SK_DEBUG
gDebugMaxWindSum = 3;
gDebugMaxWindValue = 3;
gDebugMaxWindSum = 4;
gDebugMaxWindValue = 4;
#endif
size_t index = testCount - 1;
if (firstTest) {

View File

@ -11,6 +11,7 @@
#include "SkCanvas.h"
#include "SkStream.h"
#include <assert.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
@ -29,7 +30,11 @@ static const char marker[] =
"\n"
"var testDivs = [\n";
static const char testLineStr[] = " testLine";
#if 0
static const char filename[] = "../../experimental/Intersection/debugXX.txt";
#else
static const char filename[] = "/flash/debug/XX.txt";
#endif
static int testNumber;
static void* testSimplify4x4RectsMain(void* data)
@ -38,6 +43,7 @@ static void* testSimplify4x4RectsMain(void* data)
bzero(pathStr, sizeof(pathStr));
SkASSERT(data);
State4& state = *(State4*) data;
state.testsRun = 0;
int aShape = state.a & 0x03;
int aCW = state.a >> 2;
int bShape = state.b & 0x03;
@ -179,6 +185,7 @@ static void* testSimplify4x4RectsMain(void* data)
getcwd(pwd, sizeof(pwd));
SkDebugf("%s\n", pwd);
#endif
#if 1
SkFILEWStream outFile(state.filename);
if (!outFile.isValid()) {
continue;
@ -211,8 +218,10 @@ static void* testSimplify4x4RectsMain(void* data)
outFile.writeDecAsText(testNumber);
outFile.writeText("),\n");
outFile.flush();
#endif
}
testSimplifyx(path, out, state.bitmap, state.canvas);
state.testsRun++;
}
}
}
@ -229,15 +238,16 @@ const int maxThreadsAllocated = 32;
void Simplify4x4RectsThreaded_Test()
{
#ifdef SK_DEBUG
gDebugMaxWindSum = 3;
gDebugMaxWindValue = 3;
gDebugMaxWindSum = 4;
gDebugMaxWindValue = 4;
#endif
int maxThreads = 1;
if (!gRunTestsInOneThread) {
size_t size;
int threads = -1;
size_t size = sizeof(threads);
sysctlbyname("hw.logicalcpu_max", &threads, &size, NULL, 0);
SkDebugf("%s size=%d processors=%d\n", __FUNCTION__, size, threads);
SkDebugf("%s errno=%d size=%d processors=%d\n", __FUNCTION__,
errno, size, threads);
if (threads > 0) {
maxThreads = threads;
} else {
@ -272,6 +282,7 @@ void Simplify4x4RectsThreaded_Test()
statePtr->filename[sizeof(filename) - 6] = '0' + threadIndex % 10;
}
threadIndex = 0;
int testsRun = 0;
for (int a = 0; a < 8; ++a) { // outermost
for (int b = a ; b < 8; ++b) {
for (int c = b ; c < 8; ++c) {
@ -285,6 +296,9 @@ void Simplify4x4RectsThreaded_Test()
createThread(statePtr, testSimplify4x4RectsMain);
if (++threadIndex >= maxThreads) {
waitForCompletion(threadState, threadIndex);
for (int index = 0; index < maxThreads; ++index) {
testsRun += threadState[index].testsRun;
}
}
} else {
testSimplify4x4RectsMain(statePtr);
@ -298,6 +312,9 @@ void Simplify4x4RectsThreaded_Test()
if (!gRunTestsInOneThread) SkDebugf("\n\n%d", a);
}
waitForCompletion(threadState, threadIndex);
for (int index = 0; index < maxThreads; ++index) {
testsRun += threadState[index].testsRun;
}
#ifdef SK_DEBUG
gDebugMaxWindSum = SK_MaxS32;
gDebugMaxWindValue = SK_MaxS32;

View File

@ -587,11 +587,63 @@ path.close();
path.addRect(4, 4, 13, 13, (SkPath::Direction) 1);
</div>
<div id="testLine60">
path.addRect(0, 0, 20, 20, (SkPath::Direction) 0);
path.addRect(6, 12, 18, 18, (SkPath::Direction) 1);
path.addRect(4, 12, 13, 13, (SkPath::Direction) 1);
</div>
<div id="testLine61">
path.addRect(0, 0, 20, 20, (SkPath::Direction) 0);
path.addRect(12, 0, 24, 24, (SkPath::Direction) 1);
path.addRect(12, 0, 21, 21, (SkPath::Direction) 1);
</div>
<div id="testLine62">
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(0, 0, 20, 20, (SkPath::Direction) 0);
path.addRect(0, 12, 12, 12, (SkPath::Direction) 0);
path.addRect(4, 12, 13, 13, (SkPath::Direction) 1);
</div>
<div id="testLine63">
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(0, 10, 20, 20, (SkPath::Direction) 0);
path.addRect(0, 6, 12, 12, (SkPath::Direction) 1);
path.addRect(0, 32, 9, 36, (SkPath::Direction) 1);
</div>
<div id="testLine64">
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(10, 40, 30, 30, (SkPath::Direction) 0);
path.addRect(18, 6, 30, 30, (SkPath::Direction) 0);
</div>
<div id="testLine65">
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(10, 0, 30, 30, (SkPath::Direction) 0);
path.addRect(24, 0, 36, 36, (SkPath::Direction) 0);
path.addRect(32, 6, 36, 41, (SkPath::Direction) 1);
</div>
<div id="testLine66">
path.addRect(0, 0, 60, 60, (SkPath::Direction) 0);
path.addRect(0, 30, 20, 20, (SkPath::Direction) 0);
path.addRect(12, 20, 24, 30, (SkPath::Direction) 0);
</div>
</div>
<script type="text/javascript">
var testDivs = [
testLine66,
testLine65,
testLine64,
testLine63,
testLine62,
testLine61,
testLine60,
testLine59,
testLine58,
testLine57,