Add a conservativelyContainsRect function to SkPath.

Review URL: https://codereview.appspot.com/6852044

git-svn-id: http://skia.googlecode.com/svn/trunk@6411 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
bsalomon@google.com 2012-11-13 21:51:38 +00:00
parent fbb0ed959d
commit 9bee33afbe
4 changed files with 339 additions and 1 deletions

View File

@ -779,6 +779,90 @@ private:
typedef SkBenchmark INHERITED;
};
class ConservativelyContainsBench : public SkBenchmark {
public:
enum Type {
kRect_Type,
kRoundRect_Type,
kOval_Type,
};
ConservativelyContainsBench(void* param, Type type) : INHERITED(param) {
fIsRendering = false;
fParity = false;
fName = "conservatively_contains_";
switch (type) {
case kRect_Type:
fName.append("rect");
fPath.addRect(kBaseRect);
break;
case kRoundRect_Type:
fName.append("round_rect");
fPath.addRoundRect(kBaseRect, kRRRadii[0], kRRRadii[1]);
break;
case kOval_Type:
fName.append("oval");
fPath.addOval(kBaseRect);
break;
}
}
private:
virtual const char* onGetName() SK_OVERRIDE {
return fName.c_str();
}
virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
for (int i = 0; i < N; ++i) {
const SkRect& rect = fQueryRects[i % kQueryRectCnt];
fParity = fParity != fPath.conservativelyContainsRect(rect);
}
}
virtual void onPreDraw() SK_OVERRIDE {
fQueryRects.setCount(kQueryRectCnt);
SkRandom rand;
for (int i = 0; i < kQueryRectCnt; ++i) {
SkSize size;
SkPoint xy;
size.fWidth = rand.nextRangeScalar(kQueryMin.fWidth, kQueryMax.fWidth);
size.fHeight = rand.nextRangeScalar(kQueryMin.fHeight, kQueryMax.fHeight);
xy.fX = rand.nextRangeScalar(kBounds.fLeft, kBounds.fRight - size.fWidth);
xy.fY = rand.nextRangeScalar(kBounds.fTop, kBounds.fBottom - size.fHeight);
fQueryRects[i] = SkRect::MakeXYWH(xy.fX, xy.fY, size.fWidth, size.fHeight);
}
}
virtual void onPostDraw() SK_OVERRIDE {
fQueryRects.setCount(0);
}
enum {
N = SkBENCHLOOP(100000),
kQueryRectCnt = 400,
};
static const SkRect kBounds; // bounds for all random query rects
static const SkSize kQueryMin; // minimum query rect size, should be <= kQueryMax
static const SkSize kQueryMax; // max query rect size, should < kBounds
static const SkRect kBaseRect; // rect that is used to construct the path
static const SkScalar kRRRadii[2]; // x and y radii for round rect
SkString fName;
SkPath fPath;
bool fParity;
SkTDArray<SkRect> fQueryRects;
typedef SkBenchmark INHERITED;
};
const SkRect ConservativelyContainsBench::kBounds = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100));
const SkSize ConservativelyContainsBench::kQueryMin = SkSize::Make(SkIntToScalar(1), SkIntToScalar(1));
const SkSize ConservativelyContainsBench::kQueryMax = SkSize::Make(SkIntToScalar(40), SkIntToScalar(40));
const SkRect ConservativelyContainsBench::kBaseRect = SkRect::MakeXYWH(SkIntToScalar(25), SkIntToScalar(25), SkIntToScalar(50), SkIntToScalar(50));
const SkScalar ConservativelyContainsBench::kRRRadii[2] = {SkIntToScalar(5), SkIntToScalar(10)};
static SkBenchmark* FactT00(void* p) { return new TrianglePathBench(p, FLAGS00); }
static SkBenchmark* FactT01(void* p) { return new TrianglePathBench(p, FLAGS01); }
static SkBenchmark* FactT10(void* p) { return new TrianglePathBench(p, FLAGS10); }
@ -883,3 +967,12 @@ static BenchRegistry gRegArbRoundRectTest(ArbRoundRectTest);
static SkBenchmark* ZeroRadRoundRectTest(void* p) { return new ArbRoundRectBench(p, true); }
static BenchRegistry gRegZeroRadRoundRectTest(ZeroRadRoundRectTest);
static SkBenchmark* RectConservativelyContainsTest(void* p) { return new ConservativelyContainsBench(p, ConservativelyContainsBench::kRect_Type); }
static BenchRegistry gRegRectConservativelyContainsTest(RectConservativelyContainsTest);
static SkBenchmark* RoundRectConservativelyContainsTest(void* p) { return new ConservativelyContainsBench(p, ConservativelyContainsBench::kRoundRect_Type); }
static BenchRegistry gRegRoundRectConservativelyContainsTest(RoundRectConservativelyContainsTest);
static SkBenchmark* OvalConservativelyContainsTest(void* p) { return new ConservativelyContainsBench(p, ConservativelyContainsBench::kOval_Type); }
static BenchRegistry gRegOvalConservativelyContainsTest(OvalConservativelyContainsTest);

View File

@ -292,7 +292,7 @@ public:
}
/** Calling this will, if the internal cache of the bounds is out of date,
update it so that subsequent calls to getBounds will be instanteous.
update it so that subsequent calls to getBounds will be instantaneous.
This also means that any copies or simple transformations of the path
will inherit the cached bounds.
*/
@ -301,6 +301,14 @@ public:
this->getBounds();
}
/**
* Does a conservative test to see whether a rectangle is inside a path. Currently it only
* will ever return true for single convex contour paths. The empty-status of the rect is not
* considered (e.g. a rect that is a point can be inside a path). Points or line segments where
* the rect edge touches the path border are not considered containment violations.
*/
bool conservativelyContainsRect(const SkRect& rect) const;
// Construction methods
/** Hint to the path to prepare for adding more points. This can allow the

View File

@ -311,6 +311,86 @@ void SkPath::swap(SkPath& other) {
}
}
static inline bool check_edge_against_rect(const SkPoint& p0,
const SkPoint& p1,
const SkRect& rect,
SkPath::Direction dir) {
const SkPoint* edgeBegin;
SkVector v;
if (SkPath::kCW_Direction == dir) {
v = p1 - p0;
edgeBegin = &p0;
} else {
v = p0 - p1;
edgeBegin = &p1;
}
if (v.fX || v.fY) {
// check the cross product of v with the vec from edgeBegin to each rect corner
SkScalar yL = SkScalarMul(v.fY, rect.fLeft - edgeBegin->fX);
SkScalar xT = SkScalarMul(v.fX, rect.fTop - edgeBegin->fY);
SkScalar yR = SkScalarMul(v.fY, rect.fRight - edgeBegin->fX);
SkScalar xB = SkScalarMul(v.fX, rect.fBottom - edgeBegin->fY);
if ((xT < yL) || (xT < yR) || (xB < yL) || (xB < yR)) {
return false;
}
}
return true;
}
bool SkPath::conservativelyContainsRect(const SkRect& rect) const {
// This only handles non-degenerate convex paths currently.
if (kConvex_Convexity != this->getConvexity()) {
return false;
}
Direction direction;
if (!this->cheapComputeDirection(&direction)) {
return false;
}
SkPoint firstPt;
SkPoint prevPt;
RawIter iter(*this);
SkPath::Verb verb;
SkPoint pts[4];
SkDEBUGCODE(int moveCnt = 0;)
while ((verb = iter.next(pts)) != kDone_Verb) {
int nextPt = -1;
switch (verb) {
case kMove_Verb:
SkASSERT(!moveCnt);
SkDEBUGCODE(++moveCnt);
firstPt = prevPt = pts[0];
break;
case kLine_Verb:
nextPt = 1;
SkASSERT(moveCnt);
break;
case kQuad_Verb:
SkASSERT(moveCnt);
nextPt = 2;
break;
case kCubic_Verb:
SkASSERT(moveCnt);
nextPt = 3;
break;
case kClose_Verb:
break;
default:
SkDEBUGFAIL("unknown verb");
}
if (-1 != nextPt) {
if (!check_edge_against_rect(prevPt, pts[nextPt], rect, direction)) {
return false;
}
prevPt = pts[nextPt];
}
}
return check_edge_against_rect(prevPt, firstPt, rect, direction);
}
#ifdef SK_BUILD_FOR_ANDROID
uint32_t SkPath::getGenerationID() const {
return fGenerationID;

View File

@ -822,6 +822,162 @@ static void test_isLine(skiatest::Reporter* reporter) {
REPORTER_ASSERT(reporter, pts[1].equals(lineX, lineY));
}
static void test_conservativelyContains(skiatest::Reporter* reporter) {
SkPath path;
// kBaseRect is used to construct most our test paths: a rect, a circle, and a round-rect.
static const SkRect kBaseRect = SkRect::MakeWH(SkIntToScalar(100), SkIntToScalar(100));
// A circle that bounds kBaseRect (with a significant amount of slop)
SkScalar circleR = SkMaxScalar(kBaseRect.width(), kBaseRect.height());
circleR = SkScalarMul(circleR, SkFloatToScalar(1.75f)) / 2;
static const SkPoint kCircleC = {kBaseRect.centerX(), kBaseRect.centerY()};
// round-rect radii
static const SkScalar kRRRadii[] = {SkIntToScalar(5), SkIntToScalar(3)};
static const struct {
SkRect fQueryRect;
bool fInRect;
bool fInCircle;
bool fInRR;
} kQueries[] = {
{kBaseRect, true, true, false},
// rect well inside of kBaseRect
{SkRect::MakeLTRB(kBaseRect.fLeft + SkFloatToScalar(0.25f)*kBaseRect.width(),
kBaseRect.fTop + SkFloatToScalar(0.25f)*kBaseRect.height(),
kBaseRect.fRight - SkFloatToScalar(0.25f)*kBaseRect.width(),
kBaseRect.fBottom - SkFloatToScalar(0.25f)*kBaseRect.height()),
true, true, true},
// rects with edges off by one from kBaseRect's edges
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
kBaseRect.width(), kBaseRect.height() + 1),
false, true, false},
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
kBaseRect.width() + 1, kBaseRect.height()),
false, true, false},
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop,
kBaseRect.width() + 1, kBaseRect.height() + 1),
false, true, false},
{SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop,
kBaseRect.width(), kBaseRect.height()),
false, true, false},
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1,
kBaseRect.width(), kBaseRect.height()),
false, true, false},
{SkRect::MakeXYWH(kBaseRect.fLeft - 1, kBaseRect.fTop,
kBaseRect.width() + 2, kBaseRect.height()),
false, true, false},
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop - 1,
kBaseRect.width() + 2, kBaseRect.height()),
false, true, false},
// zero-w/h rects at each corner of kBaseRect
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fTop, 0, 0), true, true, false},
{SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fTop, 0, 0), true, true, false},
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.fBottom, 0, 0), true, true, false},
{SkRect::MakeXYWH(kBaseRect.fRight, kBaseRect.fBottom, 0, 0), true, true, false},
// far away rect
{SkRect::MakeXYWH(10 * kBaseRect.fRight, 10 * kBaseRect.fBottom,
SkIntToScalar(10), SkIntToScalar(10)),
false, false, false},
// very large rect containing kBaseRect
{SkRect::MakeXYWH(kBaseRect.fLeft - 5 * kBaseRect.width(),
kBaseRect.fTop - 5 * kBaseRect.height(),
11 * kBaseRect.width(), 11 * kBaseRect.height()),
false, false, false},
// skinny rect that spans same y-range as kBaseRect
{SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop,
SkIntToScalar(1), kBaseRect.height()),
true, true, true},
// short rect that spans same x-range as kBaseRect
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(), kBaseRect.width(), SkScalar(1)),
true, true, true},
// skinny rect that spans slightly larger y-range than kBaseRect
{SkRect::MakeXYWH(kBaseRect.centerX(), kBaseRect.fTop,
SkIntToScalar(1), kBaseRect.height() + 1),
false, true, false},
// short rect that spans slightly larger x-range than kBaseRect
{SkRect::MakeXYWH(kBaseRect.fLeft, kBaseRect.centerY(),
kBaseRect.width() + 1, SkScalar(1)),
false, true, false},
};
for (int inv = 0; inv < 4; ++inv) {
for (int q = 0; q < SK_ARRAY_COUNT(kQueries); ++q) {
SkRect qRect = kQueries[q].fQueryRect;
if (inv & 0x1) {
SkTSwap(qRect.fLeft, qRect.fRight);
}
if (inv & 0x2) {
SkTSwap(qRect.fTop, qRect.fBottom);
}
for (int d = 0; d < 2; ++d) {
SkPath::Direction dir = d ? SkPath::kCCW_Direction : SkPath::kCW_Direction;
path.reset();
path.addRect(kBaseRect, dir);
REPORTER_ASSERT(reporter, kQueries[q].fInRect ==
path.conservativelyContainsRect(qRect));
path.reset();
path.addCircle(kCircleC.fX, kCircleC.fY, circleR, dir);
REPORTER_ASSERT(reporter, kQueries[q].fInCircle ==
path.conservativelyContainsRect(qRect));
path.reset();
path.addRoundRect(kBaseRect, kRRRadii[0], kRRRadii[1], dir);
REPORTER_ASSERT(reporter, kQueries[q].fInRR ==
path.conservativelyContainsRect(qRect));
}
// Slightly non-convex shape, shouldn't contain any rects.
path.reset();
path.moveTo(0, 0);
path.lineTo(SkIntToScalar(50), SkFloatToScalar(0.05f));
path.lineTo(SkIntToScalar(100), 0);
path.lineTo(SkIntToScalar(100), SkIntToScalar(100));
path.lineTo(0, SkIntToScalar(100));
path.close();
REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(qRect));
}
}
// make sure a minimal convex shape works, a right tri with edges along pos x and y axes.
path.reset();
path.moveTo(0, 0);
path.lineTo(SkIntToScalar(100), 0);
path.lineTo(0, SkIntToScalar(100));
// inside, on along top edge
REPORTER_ASSERT(reporter, path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(50), 0,
SkIntToScalar(10),
SkIntToScalar(10))));
// above
REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(
SkRect::MakeXYWH(SkIntToScalar(50),
SkIntToScalar(-10),
SkIntToScalar(10),
SkIntToScalar(10))));
// to the left
REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(-10),
SkIntToScalar(5),
SkIntToScalar(5),
SkIntToScalar(5))));
// outside the diagonal edge
REPORTER_ASSERT(reporter, !path.conservativelyContainsRect(SkRect::MakeXYWH(SkIntToScalar(10),
SkIntToScalar(200),
SkIntToScalar(20),
SkIntToScalar(5))));
}
// Simple isRect test is inline TestPath, below.
// test_isRect provides more extensive testing.
static void test_isRect(skiatest::Reporter* reporter) {
@ -1810,6 +1966,7 @@ static void TestPath(skiatest::Reporter* reporter) {
test_direction(reporter);
test_convexity(reporter);
test_convexity2(reporter);
test_conservativelyContains(reporter);
test_close(reporter);
test_segment_masks(reporter);
test_flattening(reporter);