add SkRegion::setRects(), and its unit tests
authorreed@android.com <reed@android.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 13 Jul 2010 18:35:14 +0000 (18:35 +0000)
committerreed@android.com <reed@android.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Tue, 13 Jul 2010 18:35:14 +0000 (18:35 +0000)
git-svn-id: http://skia.googlecode.com/svn/trunk@588 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkRect.h
include/core/SkRegion.h
src/core/SkRegion.cpp
tests/RegionTest.cpp [new file with mode: 0644]
tests/tests_files.mk

index fbd9f7f5022dd2304e26d9d1059ecbfd07824b3d..00e0aaf5767fdd57585b8a26cfdd2e6a8d11313d 100644 (file)
 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; }
index 8b15a893c87e8dc623fa8e784b4835ed9737c01e..103e2cc864b81d79e9afd255aa95dcfaada8e91f 100644 (file)
@@ -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&);
index a5a1555505215d57855e99a4ae56500d9a4acefe..376f79c255de4f48aae2fdc44bd6811eae46af6f 100644 (file)
@@ -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 )
diff --git a/tests/RegionTest.cpp b/tests/RegionTest.cpp
new file mode 100644 (file)
index 0000000..ee34d8b
--- /dev/null
@@ -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)
index 1850494b2f73711f2c0e8d65f7fc08ac658b2d79..98d703e36053b8722f6986777ebf0fe73d35358e 100644 (file)
@@ -12,6 +12,7 @@ SOURCE := \
     PaintTest.cpp \
     ParsePathTest.cpp \
     PathTest.cpp \
+    RegionTest.cpp \
     ClipCubicTest.cpp \
     SrcOverTest.cpp \
     StreamTest.cpp \