add SkRegion::setRects(), and its unit tests
git-svn-id: http://skia.googlecode.com/svn/trunk@588 2bbb7eff-a529-9590-31e7-b0007b416f81
This commit is contained in:
parent
930056ed27
commit
097a351353
@ -27,6 +27,36 @@
|
||||
struct SkIRect {
|
||||
int32_t fLeft, fTop, fRight, fBottom;
|
||||
|
||||
static SkIRect MakeEmpty() {
|
||||
SkIRect r;
|
||||
r.setEmpty();
|
||||
return r;
|
||||
}
|
||||
|
||||
static SkIRect MakeWH(int32_t w, int32_t h) {
|
||||
SkIRect r;
|
||||
r.set(0, 0, w, h);
|
||||
return r;
|
||||
}
|
||||
|
||||
static SkIRect MakeSize(const SkISize& size) {
|
||||
SkIRect r;
|
||||
r.set(0, 0, size.width(), size.height());
|
||||
return r;
|
||||
}
|
||||
|
||||
static SkIRect MakeLTRB(int32_t l, int32_t t, int32_t r, int32_t b) {
|
||||
SkIRect rect;
|
||||
rect.set(l, t, r, b);
|
||||
return rect;
|
||||
}
|
||||
|
||||
static SkIRect MakeXYWH(int32_t x, int32_t y, int32_t w, int32_t h) {
|
||||
SkIRect r;
|
||||
r.set(x, y, x + w, y + h);
|
||||
return r;
|
||||
}
|
||||
|
||||
/** Return true if the rectangle's width or height are <= 0
|
||||
*/
|
||||
bool isEmpty() const { return fLeft >= fRight || fTop >= fBottom; }
|
||||
|
@ -98,6 +98,13 @@ public:
|
||||
*/
|
||||
bool setRect(int32_t left, int32_t top, int32_t right, int32_t bottom);
|
||||
|
||||
/** Set this region to the union of an array of rects. This is generally
|
||||
faster than calling region.op(rect, kUnion_Op) in a loop. If count is
|
||||
0, then this region is set to the empty region.
|
||||
@return true if the resulting region is non-empty
|
||||
*/
|
||||
bool setRects(const SkIRect rects[], int count);
|
||||
|
||||
/** Set this region to the specified region, and return true if it is
|
||||
non-empty. */
|
||||
bool setRegion(const SkRegion&);
|
||||
|
@ -553,7 +553,235 @@ void SkRegion::translate(int dx, int dy, SkRegion* dst) const
|
||||
SkDEBUGCODE(this->validate();)
|
||||
}
|
||||
|
||||
/////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#include "SkChunkAlloc.h"
|
||||
#include "SkTDArray.h"
|
||||
#include "SkTSearch.h"
|
||||
#include "SkTemplates.h"
|
||||
|
||||
template <typename T> int SkTCmp2Int(const T& a, const T& b) {
|
||||
return (a < b) ? -1 : ((b < a) ? 1 : 0);
|
||||
}
|
||||
|
||||
struct VEdge {
|
||||
int32_t fX;
|
||||
int fDir; // 1, -1
|
||||
};
|
||||
|
||||
static VEdge* append_rect(VEdge* edge, const SkIRect& r) {
|
||||
edge[0].fX = r.fLeft;
|
||||
edge[0].fDir = -1;
|
||||
edge[1].fX = r.fRight;
|
||||
edge[1].fDir = 1;
|
||||
return edge + 2;
|
||||
}
|
||||
|
||||
class Accumulator {
|
||||
public:
|
||||
Accumulator(SkRegion::RunType top, int numRects);
|
||||
~Accumulator() {}
|
||||
|
||||
void append(int32_t bottom, const VEdge edge[], int count);
|
||||
|
||||
int count() const { return fTotalCount; }
|
||||
void copyTo(SkRegion::RunType dst[]);
|
||||
|
||||
private:
|
||||
struct Row {
|
||||
SkRegion::RunType* fPtr;
|
||||
SkRegion::RunType fBottom;
|
||||
int fCount; // just [L R] count
|
||||
};
|
||||
SkChunkAlloc fAlloc;
|
||||
SkTDArray<Row> fRows;
|
||||
SkRegion::RunType fTop;
|
||||
int fTotalCount;
|
||||
};
|
||||
|
||||
Accumulator::Accumulator(SkRegion::RunType top, int numRects)
|
||||
: fAlloc((1 + numRects * 2 + 1) * sizeof(int32_t)) {
|
||||
fTop = top;
|
||||
fTotalCount = 2; // Top + final sentinel
|
||||
}
|
||||
|
||||
static int union_edges(SkRegion::RunType dst[], const VEdge edge[], int count) {
|
||||
if (0 == count) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SkRegion::RunType* startDst = dst;
|
||||
const VEdge* stop = edge + count;
|
||||
|
||||
SkRegion::RunType currR;
|
||||
int dir = edge->fDir;
|
||||
|
||||
*dst++ = edge->fX; // initial L
|
||||
while (++edge < stop) {
|
||||
int prevDir = dir;
|
||||
dir += edge->fDir;
|
||||
if (0 == dir) { // we finished an interval
|
||||
currR = edge->fX;
|
||||
} else if (0 == prevDir && edge->fX > currR) {
|
||||
*dst++ = currR;
|
||||
*dst++ = edge->fX;
|
||||
}
|
||||
}
|
||||
SkASSERT(0 == dir);
|
||||
*dst++ = currR;
|
||||
SkDEBUGCODE(int resultCount = dst - startDst;)
|
||||
SkASSERT(resultCount <= count && (resultCount & 1) == 0);
|
||||
return dst - startDst;
|
||||
}
|
||||
|
||||
void Accumulator::append(int32_t bottom, const VEdge edge[], int count) {
|
||||
SkASSERT((count & 1) == 0);
|
||||
|
||||
size_t size = count * sizeof(SkRegion::RunType);
|
||||
int32_t* row = (SkRegion::RunType*)fAlloc.allocThrow(size);
|
||||
int rowCount = union_edges(row, edge, count);
|
||||
|
||||
Row* r = fRows.count() ? &fRows[fRows.count() - 1] : NULL;
|
||||
if (r && (r->fCount == rowCount) &&
|
||||
!memcmp(r->fPtr, row,
|
||||
rowCount * sizeof(SkRegion::RunType))) {
|
||||
r->fBottom = bottom; // update bottom
|
||||
fAlloc.unalloc(row);
|
||||
} else {
|
||||
Row* r = fRows.append();
|
||||
r->fPtr = row;
|
||||
r->fBottom = bottom;
|
||||
r->fCount = rowCount;
|
||||
fTotalCount += 1 + rowCount + 1;
|
||||
}
|
||||
}
|
||||
|
||||
void Accumulator::copyTo(SkRegion::RunType dst[]) {
|
||||
SkDEBUGCODE(SkRegion::RunType* startDst = dst;)
|
||||
|
||||
*dst++ = fTop;
|
||||
|
||||
const Row* curr = fRows.begin();
|
||||
const Row* stop = fRows.end();
|
||||
while (curr < stop) {
|
||||
*dst++ = curr->fBottom;
|
||||
memcpy(dst, curr->fPtr, curr->fCount * sizeof(SkRegion::RunType));
|
||||
dst += curr->fCount;
|
||||
*dst++ = SkRegion::kRunTypeSentinel;
|
||||
curr += 1;
|
||||
}
|
||||
*dst++ = SkRegion::kRunTypeSentinel;
|
||||
SkASSERT(dst - startDst == fTotalCount);
|
||||
}
|
||||
|
||||
/////////////
|
||||
|
||||
struct RNode {
|
||||
RNode* fNext;
|
||||
const SkIRect* fRect;
|
||||
};
|
||||
|
||||
static int compare_rnode_top(const void* e0, const void* e1) {
|
||||
const RNode* r0 = static_cast<const RNode*>(e0);
|
||||
const RNode* r1 = static_cast<const RNode*>(e1);
|
||||
return SkTCmp2Int<int32_t>(r0->fRect->fTop, r1->fRect->fTop);
|
||||
}
|
||||
|
||||
static int compare_vedge(const void* e0, const void* e1) {
|
||||
const VEdge* v0 = static_cast<const VEdge*>(e0);
|
||||
const VEdge* v1 = static_cast<const VEdge*>(e1);
|
||||
return SkTCmp2Int<int32_t>(v0->fX, v1->fX);
|
||||
}
|
||||
|
||||
bool SkRegion::setRects(const SkIRect rects[], int count) {
|
||||
if (0 == count) {
|
||||
return this->setEmpty();
|
||||
}
|
||||
if (1 == count) {
|
||||
return this->setRect(rects[0]);
|
||||
}
|
||||
|
||||
int i;
|
||||
SkIRect sentinelRect = SkIRect::MakeLTRB(kRunTypeSentinel, kRunTypeSentinel,
|
||||
kRunTypeSentinel, kRunTypeSentinel);
|
||||
|
||||
SkAutoTArray<RNode> nodeStorage(count + 1);
|
||||
RNode* nodes = nodeStorage.get();
|
||||
for (i = 0; i < count; i++) {
|
||||
if (!rects[i].isEmpty()) {
|
||||
nodes->fRect = &rects[i];
|
||||
nodes += 1;
|
||||
}
|
||||
}
|
||||
// recompute count, since we may have skipped empty rects
|
||||
count = nodes - nodeStorage.get();
|
||||
nodes = nodeStorage.get();
|
||||
// we are now done with the original rects[] parameter
|
||||
SkDEBUGCODE(rects = NULL;)
|
||||
|
||||
SkQSort(nodes, count, sizeof(nodes[0]), compare_rnode_top);
|
||||
for (i = 0; i < count; i++) {
|
||||
nodes[i].fNext = &nodes[i + 1];
|
||||
}
|
||||
nodes[count].fNext = NULL;
|
||||
nodes[count].fRect = &sentinelRect;
|
||||
|
||||
SkAutoTArray<VEdge> edgeStorage(count * 2);
|
||||
|
||||
VEdge* edges = edgeStorage.get();
|
||||
RNode* nodeHead = nodeStorage.get();
|
||||
|
||||
int32_t currY = nodeHead->fRect->fTop;
|
||||
Accumulator accum(currY, count);
|
||||
|
||||
while (nodeHead->fNext) {
|
||||
RNode* node = nodeHead;
|
||||
|
||||
int32_t nextY = kRunTypeSentinel;
|
||||
const SkIRect* r = node->fRect;
|
||||
for (;;) {
|
||||
if (r->fTop > currY) {
|
||||
nextY = SkMin32(nextY, r->fTop);
|
||||
break;
|
||||
}
|
||||
edges = append_rect(edges, *r);
|
||||
nextY = SkMin32(nextY, r->fBottom);
|
||||
node = node->fNext;
|
||||
r = node->fRect;
|
||||
}
|
||||
int edgeCount = edges - edgeStorage.get();
|
||||
edges = edgeStorage.get();
|
||||
SkASSERT(edgeCount <= count * 2);
|
||||
|
||||
SkQSort(edges, edgeCount, sizeof(edges[0]), compare_vedge);
|
||||
accum.append(nextY, edges, edgeCount);
|
||||
|
||||
RNode* prev = NULL;
|
||||
node = nodeHead;
|
||||
while (node->fRect->fTop <= currY) {
|
||||
RNode* next = node->fNext;
|
||||
if (node->fRect->fBottom <= nextY) {
|
||||
// drop this rect from our linklist
|
||||
SkASSERT(node->fRect->fBottom == nextY);
|
||||
if (prev) {
|
||||
prev->fNext = next;
|
||||
} else {
|
||||
nodeHead = next;
|
||||
}
|
||||
} else {
|
||||
prev = node;
|
||||
}
|
||||
node = next;
|
||||
}
|
||||
currY = nextY;
|
||||
}
|
||||
|
||||
SkAutoTArray<RunType> runs(accum.count());
|
||||
accum.copyTo(runs.get());
|
||||
return this->setRuns(runs.get(), accum.count());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#if defined _WIN32 && _MSC_VER >= 1300 // disable warning : local variable used without having been initialized
|
||||
#pragma warning ( push )
|
||||
|
63
tests/RegionTest.cpp
Normal file
63
tests/RegionTest.cpp
Normal file
@ -0,0 +1,63 @@
|
||||
#include "Test.h"
|
||||
#include "SkRegion.h"
|
||||
#include "SkRandom.h"
|
||||
|
||||
static void rand_rect(SkIRect* rect, SkRandom& rand) {
|
||||
int bits = 6;
|
||||
int shift = 32 - bits;
|
||||
rect->set(rand.nextU() >> shift, rand.nextU() >> shift,
|
||||
rand.nextU() >> shift, rand.nextU() >> shift);
|
||||
rect->sort();
|
||||
}
|
||||
|
||||
static bool test_rects(const SkIRect rect[], int count) {
|
||||
SkRegion rgn0, rgn1;
|
||||
|
||||
for (int i = 0; i < count; i++) {
|
||||
rgn0.op(rect[i], SkRegion::kUnion_Op);
|
||||
}
|
||||
rgn1.setRects(rect, count);
|
||||
|
||||
if (rgn0 != rgn1) {
|
||||
SkDebugf("\n");
|
||||
for (int i = 0; i < count; i++) {
|
||||
SkDebugf(" { %d, %d, %d, %d },\n",
|
||||
rect[i].fLeft, rect[i].fTop,
|
||||
rect[i].fRight, rect[i].fBottom);
|
||||
}
|
||||
SkDebugf("\n");
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
static void TestRegion(skiatest::Reporter* reporter) {
|
||||
const SkIRect r2[] = {
|
||||
{ 0, 0, 1, 1 },
|
||||
{ 2, 2, 3, 3 },
|
||||
};
|
||||
REPORTER_ASSERT(reporter, test_rects(r2, SK_ARRAY_COUNT(r2)));
|
||||
|
||||
const SkIRect rects[] = {
|
||||
{ 0, 0, 1, 2 },
|
||||
{ 2, 1, 3, 3 },
|
||||
{ 4, 0, 5, 1 },
|
||||
{ 6, 0, 7, 4 },
|
||||
};
|
||||
REPORTER_ASSERT(reporter, test_rects(rects, SK_ARRAY_COUNT(rects)));
|
||||
|
||||
SkRandom rand;
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
SkRegion rgn0, rgn1;
|
||||
|
||||
const int N = 8;
|
||||
SkIRect rect[N];
|
||||
for (int j = 0; j < N; j++) {
|
||||
rand_rect(&rect[j], rand);
|
||||
}
|
||||
REPORTER_ASSERT(reporter, test_rects(rect, N));
|
||||
}
|
||||
}
|
||||
|
||||
#include "TestClassDef.h"
|
||||
DEFINE_TESTCLASS("Region", RegionTestClass, TestRegion)
|
@ -12,6 +12,7 @@ SOURCE := \
|
||||
PaintTest.cpp \
|
||||
ParsePathTest.cpp \
|
||||
PathTest.cpp \
|
||||
RegionTest.cpp \
|
||||
ClipCubicTest.cpp \
|
||||
SrcOverTest.cpp \
|
||||
StreamTest.cpp \
|
||||
|
Loading…
Reference in New Issue
Block a user