Adding quickContains API method to SkClipStack

BUG=http://code.google.com/p/chromium/issues/detail?id=164580
TEST=unit test ClipStack/quickContains
Review URL: https://codereview.appspot.com/6919044

git-svn-id: http://skia.googlecode.com/svn/trunk@6760 2bbb7eff-a529-9590-31e7-b0007b416f81
diff --git a/include/core/SkClipStack.h b/include/core/SkClipStack.h
index 52b1b22..b39c5af 100644
--- a/include/core/SkClipStack.h
+++ b/include/core/SkClipStack.h
@@ -292,6 +292,13 @@
      */
     bool intersectRectWithClip(SkRect* devRect) const;
 
+    /**
+     * Returns true if the input rect in device space is entirely contained
+     * by the clip. A return value of false does not guarantee that the rect
+     * is not contained by the clip.
+     */
+    bool quickContains(const SkRect& devRect) const;
+
     void clipDevRect(const SkIRect& ir, SkRegion::Op op) {
         SkRect r;
         r.set(ir);
diff --git a/src/core/SkClipStack.cpp b/src/core/SkClipStack.cpp
index bb4ea08..9afef3f 100644
--- a/src/core/SkClipStack.cpp
+++ b/src/core/SkClipStack.cpp
@@ -497,6 +497,31 @@
     }
 }
 
+bool SkClipStack::quickContains(const SkRect& rect) const {
+
+    Iter iter(*this, Iter::kTop_IterStart);
+    const Element* element = iter.prev();
+    while (element != NULL) {
+        if (SkRegion::kIntersect_Op != element->getOp() && SkRegion::kReplace_Op != element->getOp())
+            return false;
+        if (element->isInverseFilled()) {
+            // Part of 'rect' could be trimmed off by the inverse-filled clip element
+            if (SkRect::Intersects(element->getBounds(), rect)) {
+                return false;
+            }
+        } else {
+            if (!element->contains(rect)) {
+                return false;
+            }
+        }
+        if (SkRegion::kReplace_Op == element->getOp()) {
+            break;
+        }
+        element = iter.prev();
+    }
+    return true;
+}
+
 void SkClipStack::clipDevRect(const SkRect& rect, SkRegion::Op op, bool doAA) {
 
     // Use reverse iterator instead of back because Rect path may need previous
diff --git a/tests/ClipStackTest.cpp b/tests/ClipStackTest.cpp
index 03da7f1..5e5c87f 100644
--- a/tests/ClipStackTest.cpp
+++ b/tests/ClipStackTest.cpp
@@ -497,6 +497,184 @@
     }
 }
 
+static void test_quickContains(skiatest::Reporter* reporter) {
+    SkRect testRect = SkRect::MakeLTRB(10, 10, 40, 40);
+    SkRect insideRect = SkRect::MakeLTRB(20, 20, 30, 30);
+    SkRect intersectingRect = SkRect::MakeLTRB(25, 25, 50, 50);
+    SkRect outsideRect = SkRect::MakeLTRB(0, 0, 50, 50);
+    SkRect nonIntersectingRect = SkRect::MakeLTRB(100, 100, 110, 110);
+
+    SkPath insideCircle;
+    insideCircle.addCircle(25, 25, 5);
+    SkPath intersectingCircle;
+    intersectingCircle.addCircle(25, 40, 10);
+    SkPath outsideCircle;
+    outsideCircle.addCircle(25, 25, 50);
+    SkPath nonIntersectingCircle;
+    nonIntersectingCircle.addCircle(100, 100, 5);
+
+    {
+        SkClipStack stack;
+        stack.clipDevRect(outsideRect, SkRegion::kDifference_Op, false);
+        // return false because quickContains currently does not care for kDifference_Op
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+    
+    // Replace Op tests
+    {
+        SkClipStack stack;
+        stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false);
+        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
+        stack.save(); // To prevent in-place substitution by replace OP
+        stack.clipDevRect(outsideRect, SkRegion::kReplace_Op, false);
+        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+        stack.restore();
+    }
+
+    {
+        SkClipStack stack;
+        stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false);
+        stack.save(); // To prevent in-place substitution by replace OP
+        stack.clipDevRect(insideRect, SkRegion::kReplace_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+        stack.restore();
+    }
+
+    // Verify proper traversal of multi-element clip
+    {
+        SkClipStack stack;
+        stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
+        // Use a path for second clip to prevent in-place intersection
+        stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    // Intersect Op tests with rectangles
+    {
+        SkClipStack stack;
+        stack.clipDevRect(outsideRect, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        stack.clipDevRect(insideRect, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        stack.clipDevRect(intersectingRect, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        stack.clipDevRect(nonIntersectingRect, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    // Intersect Op tests with circle paths
+    {
+        SkClipStack stack;
+        stack.clipDevPath(outsideCircle, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        stack.clipDevPath(insideCircle, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        stack.clipDevPath(intersectingCircle, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        stack.clipDevPath(nonIntersectingCircle, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    // Intersect Op tests with inverse filled rectangles
+    {
+        SkClipStack stack;
+        SkPath path;
+        path.addRect(outsideRect);
+        path.toggleInverseFillType();
+        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        SkPath path;
+        path.addRect(insideRect);
+        path.toggleInverseFillType();
+        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        SkPath path;
+        path.addRect(intersectingRect);
+        path.toggleInverseFillType();
+        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        SkPath path;
+        path.addRect(nonIntersectingRect);
+        path.toggleInverseFillType();
+        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+    }
+
+    // Intersect Op tests with inverse filled circles
+    {
+        SkClipStack stack;
+        SkPath path = outsideCircle;
+        path.toggleInverseFillType();
+        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        SkPath path = insideCircle;
+        path.toggleInverseFillType();
+        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        SkPath path = intersectingCircle;
+        path.toggleInverseFillType();
+        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, false == stack.quickContains(testRect));
+    }
+
+    {
+        SkClipStack stack;
+        SkPath path = nonIntersectingCircle;
+        path.toggleInverseFillType();
+        stack.clipDevPath(path, SkRegion::kIntersect_Op, false);
+        REPORTER_ASSERT(reporter, true == stack.quickContains(testRect));
+    }
+}
+
 ///////////////////////////////////////////////////////////////////////////////////////////////////
 
 #if SK_SUPPORT_GPU
@@ -773,6 +951,7 @@
     test_isWideOpen(reporter);
     test_rect_merging(reporter);
     test_rect_inverse_fill(reporter);
+    test_quickContains(reporter);
 #if SK_SUPPORT_GPU
     test_reduced_clip_stack(reporter);
 #endif