add two nested rect detector to path

Tease apart existing one rect path detector so
that a new variant can detect two nested rects as well.

Add tests to verify that both one and two rect
detectors both work and return the correct results.

Suppress other warnings in PathTest.
Review URL: https://codereview.appspot.com/6850059

git-svn-id: http://skia.googlecode.com/svn/trunk@6475 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
caryclark@google.com 2012-11-19 13:06:06 +00:00
parent 1542354487
commit 56f233ab54
3 changed files with 256 additions and 11 deletions

View File

@ -245,6 +245,16 @@ public:
*/
bool isRect(SkRect* rect) const;
/** Returns true if the path specifies a pair of nested rectangles. If so, and if
rect is not null, set rect[0] to the outer rectangle and rect[1] to the inner
rectangle. If the path does not specify a pair of nested rectangles, return
false and ignore rect.
@param rect If not null, returns the path as a pair of nested rectangles
@return true if the path describes a pair of nested rectangles
*/
bool isNestedRects(SkRect rect[2]) const;
/** Return the number of points in the path
*/
int countPoints() const;
@ -906,6 +916,8 @@ private:
inline bool hasOnlyMoveTos() const;
Convexity internalGetConvexity() const;
bool isRectContour(bool allowPartial, int* currVerb, const SkPoint** pts) const;
friend class SkAutoPathBoundsUpdate;
friend class SkAutoDisableOvalCheck;

View File

@ -488,11 +488,11 @@ FIXME: Allow colinear quads and cubics to be treated like lines.
FIXME: If the API passes fill-only, return true if the filled stroke
is a rectangle, though the caller failed to close the path.
*/
bool SkPath::isRect(SkRect* rect) const {
SkDEBUGCODE(this->validate();)
bool SkPath::isRectContour(bool allowPartial, int* currVerb, const SkPoint** ptsPtr) const {
int corners = 0;
SkPoint first, last;
const SkPoint* pts = *ptsPtr;
const SkPoint* savePts;
first.set(0, 0);
last.set(0, 0);
int firstDirection = 0;
@ -500,13 +500,12 @@ bool SkPath::isRect(SkRect* rect) const {
int nextDirection = 0;
bool closedOrMoved = false;
bool autoClose = false;
const SkPoint* pts = fPathRef->points();
int verbCnt = fPathRef->countVerbs();
int currVerb = 0;
while (currVerb < verbCnt) {
switch (fPathRef->atVerb(currVerb++)) {
while (*currVerb < verbCnt && (!allowPartial || !autoClose)) {
switch (fPathRef->atVerb(*currVerb)) {
case kClose_Verb:
pts = fPathRef->points();
savePts = pts;
pts = *ptsPtr;
autoClose = true;
case kLine_Verb: {
SkScalar left = last.fX;
@ -561,16 +560,57 @@ bool SkPath::isRect(SkRect* rect) const {
closedOrMoved = true;
break;
}
*currVerb += 1;
lastDirection = nextDirection;
}
// Success if 4 corners and first point equals last
bool result = 4 == corners && first == last;
*ptsPtr = savePts;
return result;
}
bool SkPath::isRect(SkRect* rect) const {
SkDEBUGCODE(this->validate();)
int currVerb = 0;
const SkPoint* pts = fPathRef->points();
bool result = isRectContour(false, &currVerb, &pts);
if (result && rect) {
*rect = getBounds();
}
return result;
}
bool SkPath::isNestedRects(SkRect rects[2]) const {
SkDEBUGCODE(this->validate();)
int currVerb = 0;
const SkPoint* pts = fPathRef->points();
const SkPoint* first = pts;
if (!isRectContour(true, &currVerb, &pts)) {
return false;
}
const SkPoint* last = pts;
SkRect testRects[2];
if (isRectContour(false, &currVerb, &pts)) {
testRects[0].set(first, last - first);
testRects[1].set(last, pts - last);
if (testRects[0].contains(testRects[1])) {
if (rects) {
rects[0] = testRects[0];
rects[1] = testRects[1];
}
return true;
}
if (testRects[1].contains(testRects[0])) {
if (rects) {
rects[0] = testRects[1];
rects[1] = testRects[0];
}
return true;
}
}
return false;
}
int SkPath::countPoints() const {
return fPathRef->countPoints();
}

View File

@ -18,6 +18,12 @@
#include "SkWriter32.h"
#include "SkSurface.h"
#if defined(WIN32)
#define SUPPRESS_VISIBILITY_WARNING
#else
#define SUPPRESS_VISIBILITY_WARNING __attribute__((visibility("hidden")))
#endif
static SkSurface* new_surface(int w, int h) {
SkImage::Info info = {
w, h, SkImage::kPMColor_ColorType, SkImage::kPremul_AlphaType
@ -836,7 +842,7 @@ static void test_conservativelyContains(skiatest::Reporter* reporter) {
// round-rect radii
static const SkScalar kRRRadii[] = {SkIntToScalar(5), SkIntToScalar(3)};
static const struct {
static const struct SUPPRESS_VISIBILITY_WARNING {
SkRect fQueryRect;
bool fInRect;
bool fInCircle;
@ -912,7 +918,7 @@ static void test_conservativelyContains(skiatest::Reporter* reporter) {
};
for (int inv = 0; inv < 4; ++inv) {
for (int q = 0; q < SK_ARRAY_COUNT(kQueries); ++q) {
for (size_t q = 0; q < SK_ARRAY_COUNT(kQueries); ++q) {
SkRect qRect = kQueries[q].fQueryRect;
if (inv & 0x1) {
SkTSwap(qRect.fLeft, qRect.fRight);
@ -1042,6 +1048,12 @@ static void test_isRect(skiatest::Reporter* reporter) {
path.close();
}
REPORTER_ASSERT(reporter, fail ^ path.isRect(0));
if (!fail) {
SkRect computed, expected;
expected.set(tests[testIndex], testLen[testIndex] / sizeof(SkPoint));
REPORTER_ASSERT(reporter, path.isRect(&computed));
REPORTER_ASSERT(reporter, expected == computed);
}
if (tests[testIndex] == lastPass) {
fail = true;
}
@ -1106,6 +1118,186 @@ static void test_isRect(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, fail ^ path1.isRect(0));
}
static void test_isNestedRects(skiatest::Reporter* reporter) {
// passing tests (all moveTo / lineTo...
SkPoint r1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}};
SkPoint r2[] = {{1, 0}, {1, 1}, {0, 1}, {0, 0}};
SkPoint r3[] = {{1, 1}, {0, 1}, {0, 0}, {1, 0}};
SkPoint r4[] = {{0, 1}, {0, 0}, {1, 0}, {1, 1}};
SkPoint r5[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}};
SkPoint r6[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
SkPoint r7[] = {{1, 1}, {1, 0}, {0, 0}, {0, 1}};
SkPoint r8[] = {{1, 0}, {0, 0}, {0, 1}, {1, 1}};
SkPoint r9[] = {{0, 1}, {1, 1}, {1, 0}, {0, 0}};
SkPoint ra[] = {{0, 0}, {0, .5f}, {0, 1}, {.5f, 1}, {1, 1}, {1, .5f},
{1, 0}, {.5f, 0}};
SkPoint rb[] = {{0, 0}, {.5f, 0}, {1, 0}, {1, .5f}, {1, 1}, {.5f, 1},
{0, 1}, {0, .5f}};
SkPoint rc[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}};
SkPoint rd[] = {{0, 0}, {0, 1}, {1, 1}, {1, 0}, {0, 0}};
SkPoint re[] = {{0, 0}, {1, 0}, {1, 0}, {1, 1}, {0, 1}};
// failing tests
SkPoint f1[] = {{0, 0}, {1, 0}, {1, 1}}; // too few points
SkPoint f2[] = {{0, 0}, {1, 1}, {0, 1}, {1, 0}}; // diagonal
SkPoint f3[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 0}, {1, 0}}; // wraps
SkPoint f4[] = {{0, 0}, {1, 0}, {0, 0}, {1, 0}, {1, 1}, {0, 1}}; // backs up
SkPoint f5[] = {{0, 0}, {1, 0}, {1, 1}, {2, 0}}; // end overshoots
SkPoint f6[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}, {0, 2}}; // end overshoots
SkPoint f7[] = {{0, 0}, {1, 0}, {1, 1}, {0, 2}}; // end overshoots
SkPoint f8[] = {{0, 0}, {1, 0}, {1, 1}, {1, 0}}; // 'L'
// failing, no close
SkPoint c1[] = {{0, 0}, {1, 0}, {1, 1}, {0, 1}}; // close doesn't match
SkPoint c2[] = {{0, 0}, {1, 0}, {1, 2}, {0, 2}, {0, 1}}; // ditto
size_t testLen[] = {
sizeof(r1), sizeof(r2), sizeof(r3), sizeof(r4), sizeof(r5), sizeof(r6),
sizeof(r7), sizeof(r8), sizeof(r9), sizeof(ra), sizeof(rb), sizeof(rc),
sizeof(rd), sizeof(re),
sizeof(f1), sizeof(f2), sizeof(f3), sizeof(f4), sizeof(f5), sizeof(f6),
sizeof(f7), sizeof(f8),
sizeof(c1), sizeof(c2)
};
SkPoint* tests[] = {
r1, r2, r3, r4, r5, r6, r7, r8, r9, ra, rb, rc, rd, re,
f1, f2, f3, f4, f5, f6, f7, f8,
c1, c2
};
const SkPoint* lastPass = re;
const SkPoint* lastClose = f8;
const size_t testCount = sizeof(tests) / sizeof(tests[0]);
size_t index;
for (int rectFirst = 0; rectFirst <= 1; ++rectFirst) {
bool fail = false;
bool close = true;
for (size_t testIndex = 0; testIndex < testCount; ++testIndex) {
SkPath path;
if (rectFirst) {
path.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
}
path.moveTo(tests[testIndex][0].fX, tests[testIndex][0].fY);
for (index = 1; index < testLen[testIndex] / sizeof(SkPoint); ++index) {
path.lineTo(tests[testIndex][index].fX, tests[testIndex][index].fY);
}
if (close) {
path.close();
}
if (!rectFirst) {
path.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
}
REPORTER_ASSERT(reporter, fail ^ path.isNestedRects(0));
if (!fail) {
SkRect expected[2], computed[2];
SkRect testBounds;
testBounds.set(tests[testIndex], testLen[testIndex] / sizeof(SkPoint));
expected[0] = SkRect::MakeLTRB(-1, -1, 2, 2);
expected[1] = testBounds;
REPORTER_ASSERT(reporter, path.isNestedRects(computed));
REPORTER_ASSERT(reporter, expected[0] == computed[0]);
REPORTER_ASSERT(reporter, expected[1] == computed[1]);
}
if (tests[testIndex] == lastPass) {
fail = true;
}
if (tests[testIndex] == lastClose) {
close = false;
}
}
// fail, close then line
SkPath path1;
if (rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
}
path1.moveTo(r1[0].fX, r1[0].fY);
for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
path1.lineTo(r1[index].fX, r1[index].fY);
}
path1.close();
path1.lineTo(1, 0);
if (!rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
}
REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
// fail, move in the middle
path1.reset();
if (rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
}
path1.moveTo(r1[0].fX, r1[0].fY);
for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
if (index == 2) {
path1.moveTo(1, .5f);
}
path1.lineTo(r1[index].fX, r1[index].fY);
}
path1.close();
if (!rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
}
REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
// fail, move on the edge
path1.reset();
if (rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
}
for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
path1.moveTo(r1[index - 1].fX, r1[index - 1].fY);
path1.lineTo(r1[index].fX, r1[index].fY);
}
path1.close();
if (!rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
}
REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
// fail, quad
path1.reset();
if (rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
}
path1.moveTo(r1[0].fX, r1[0].fY);
for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
if (index == 2) {
path1.quadTo(1, .5f, 1, .5f);
}
path1.lineTo(r1[index].fX, r1[index].fY);
}
path1.close();
if (!rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
}
REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
// fail, cubic
path1.reset();
if (rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCW_Direction);
}
path1.moveTo(r1[0].fX, r1[0].fY);
for (index = 1; index < testLen[0] / sizeof(SkPoint); ++index) {
if (index == 2) {
path1.cubicTo(1, .5f, 1, .5f, 1, .5f);
}
path1.lineTo(r1[index].fX, r1[index].fY);
}
path1.close();
if (!rectFirst) {
path1.addRect(-1, -1, 2, 2, SkPath::kCCW_Direction);
}
REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
// fail, not nested
path1.reset();
path1.addRect(1, 1, 3, 3, SkPath::kCW_Direction);
path1.addRect(2, 2, 4, 4, SkPath::kCW_Direction);
REPORTER_ASSERT(reporter, fail ^ path1.isNestedRects(0));
}
}
static void write_and_read_back(skiatest::Reporter* reporter,
const SkPath& p) {
SkWriter32 writer(100);
@ -1208,7 +1400,7 @@ static void test_zero_length_paths(skiatest::Reporter* reporter) {
SkPath p;
uint8_t verbs[32];
struct zeroPathTestData {
struct SUPPRESS_VISIBILITY_WARNING zeroPathTestData {
const char* testPath;
const size_t numResultPts;
const SkRect resultBound;
@ -1962,6 +2154,7 @@ static void TestPath(skiatest::Reporter* reporter) {
test_isLine(reporter);
test_isRect(reporter);
test_isNestedRects(reporter);
test_zero_length_paths(reporter);
test_direction(reporter);
test_convexity(reporter);