Added "SkRRect::contains(const SkRect&) const"
authorrobertphillips@google.com <robertphillips@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Thu, 25 Apr 2013 12:23:00 +0000 (12:23 +0000)
committerrobertphillips@google.com <robertphillips@google.com@2bbb7eff-a529-9590-31e7-b0007b416f81>
Thu, 25 Apr 2013 12:23:00 +0000 (12:23 +0000)
https://codereview.chromium.org/14200044/

git-svn-id: http://skia.googlecode.com/svn/trunk@8854 2bbb7eff-a529-9590-31e7-b0007b416f81

include/core/SkRRect.h
src/core/SkRRect.cpp
tests/RoundRectTest.cpp

index fafe019..bce896a 100644 (file)
@@ -251,6 +251,12 @@ public:
         this->inset(-dx, -dy, this);
     }
 
+    /**
+     *  Returns true if 'rect' is wholy inside the RR, and both
+     *  are not empty.
+     */
+    bool contains(const SkRect& rect) const;
+
     SkDEBUGCODE(void validate() const;)
 
     enum {
@@ -280,6 +286,7 @@ private:
     // uninitialized data
 
     void computeType() const;
+    bool checkCornerContainment(SkScalar x, SkScalar y) const;
 
     // to access fRadii directly
     friend class SkPath;
index fc1a1cf..c8d0329 100644 (file)
@@ -134,6 +134,12 @@ bool SkRRect::contains(SkScalar x, SkScalar y) const {
 
     // We know the point is inside the RR's bounds. The only way it can
     // be out is if it outside one of the corners
+    return checkCornerContainment(x, y);
+}
+
+// This method determines if a point known to be inside the RRect's bounds is 
+// inside all the corners.
+bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
     SkPoint canonicalPt; // (x,y) translated to one of the quadrants
     int index;
 
@@ -179,9 +185,32 @@ bool SkRRect::contains(SkScalar x, SkScalar y) const {
     //      x^2     y^2
     //     ----- + ----- <= 1
     //      a^2     b^2
-    SkScalar dist =  SkScalarDiv(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fX)) +
-                     SkScalarDiv(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fY));
-    return dist <= SK_Scalar1;
+    // or :
+    //     b^2*x^2 + a^2*y^2 <= (ab)^2
+    SkScalar dist =  SkScalarMul(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fY)) +
+                     SkScalarMul(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fX));
+    return dist <= SkScalarSquare(SkScalarMul(fRadii[index].fX, fRadii[index].fY));
+}
+
+bool SkRRect::contains(const SkRect& rect) const {
+    if (!this->getBounds().contains(rect)) {
+        // If 'rect' isn't contained by the RR's bounds then the
+        // RR definitely doesn't contain it
+        return false;
+    }
+
+    if (this->isRect()) {
+        // the prior test was sufficient
+        return true;
+    }
+
+    // At this point we know all four corners of 'rect' are inside the
+    // bounds of of this RR. Check to make sure all the corners are inside
+    // all the curves
+    return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
+           this->checkCornerContainment(rect.fRight, rect.fTop) &&
+           this->checkCornerContainment(rect.fRight, rect.fBottom) &&
+           this->checkCornerContainment(rect.fLeft, rect.fBottom);
 }
 
 // There is a simplified version of this method in setRectXY
index a8387d5..486037c 100644 (file)
@@ -317,6 +317,140 @@ static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
     REPORTER_ASSERT(reporter, 0.0f == p2.fY);
 }
 
+// Move a small box from the start position by (stepX, stepY) 'numSteps' times
+// testing for containment in 'rr' at each step.
+static void test_direction(skiatest::Reporter* reporter, const SkRRect &rr,
+                           SkScalar initX, int stepX, SkScalar initY, int stepY, 
+                           int numSteps, const bool* contains) {
+    SkScalar x = initX, y = initY;
+    for (int i = 0; i < numSteps; ++i) {
+        SkRect test = SkRect::MakeXYWH(x, y, 
+                                       stepX ? SkIntToScalar(stepX) : SK_Scalar1, 
+                                       stepY ? SkIntToScalar(stepY) : SK_Scalar1);
+        test.sort();
+
+        REPORTER_ASSERT(reporter, contains[i] == rr.contains(test));
+
+        x += stepX;
+        y += stepY;
+    }
+}
+
+// Exercise the RR's contains rect method
+static void test_round_rect_contains_rect(skiatest::Reporter* reporter) {
+
+    static const int kNumRRects = 4;
+    static const SkVector gRadii[kNumRRects][4] = {
+        { {  0,  0 }, {  0,  0 }, {  0,  0 }, {  0,  0 } },  // rect
+        { { 20, 20 }, { 20, 20 }, { 20, 20 }, { 20, 20 } },  // circle
+        { { 10, 10 }, { 10, 10 }, { 10, 10 }, { 10, 10 } },  // simple
+        { {  0,  0 }, { 20, 20 }, { 10, 10 }, { 30, 30 } }   // complex
+    };
+
+    SkRRect rrects[kNumRRects];
+    for (int i = 0; i < kNumRRects; ++i) {
+        rrects[i].setRectRadii(SkRect::MakeWH(40, 40), gRadii[i]);
+    }
+
+    // First test easy outs - boxes that are obviously out on
+    // each corner and edge
+    static const SkRect easyOuts[] = {
+        { -5, -5,  5,  5 }, // NW
+        { 15, -5, 20,  5 }, // N
+        { 35, -5, 45,  5 }, // NE
+        { 35, 15, 45, 20 }, // E
+        { 35, 45, 35, 45 }, // SE
+        { 15, 35, 20, 45 }, // S
+        { -5, 35,  5, 45 }, // SW
+        { -5, 15,  5, 20 }  // W
+    };
+    
+    for (int i = 0; i < kNumRRects; ++i) {
+        for (size_t j = 0; j < SK_ARRAY_COUNT(easyOuts); ++j) {
+            REPORTER_ASSERT(reporter, !rrects[i].contains(easyOuts[j]));
+        }
+    }
+
+    // Now test non-trivial containment. For each compass 
+    // point walk a 1x1 rect in from the edge  of the bounding 
+    // rect
+    static const int kNumSteps = 15;
+    bool answers[kNumRRects][8][kNumSteps] = {
+        // all the test rects are inside the degenerate rrect
+        {
+            // rect
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+        },
+        // for the circle we expect 6 blocks to be out on the
+        // corners (then the rest in) and only the first block 
+        // out on the vertical and horizontal axes (then 
+        // the rest in)
+        {
+            // circle
+            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+        },
+        // for the simple round rect we expect 3 out on
+        // the corners (then the rest in) and no blocks out
+        // on the vertical and horizontal axes
+        {
+            // simple RR
+            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+        },
+        // for the complex case the answer is different for each direction
+        {
+            // complex RR
+            // all in for NW (rect) corner (same as rect case)
+            { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            // only first block out for N (same as circle case)
+            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            // first 6 blocks out for NE (same as circle case)
+            { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            // only first block out for E (same as circle case)
+            { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            // first 3 blocks out for SE (same as simple case)
+            { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            // first two blocks out for S
+            { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+            // first 9 blocks out for SW
+            { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 },
+            // first two blocks out for W (same as S)
+            { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
+         }
+    };
+
+    for (int i = 0; i < kNumRRects; ++i) {
+        test_direction(reporter, rrects[i],     0,  1,     0,  1, kNumSteps, answers[i][0]); // NW
+        test_direction(reporter, rrects[i], 19.5f,  0,     0,  1, kNumSteps, answers[i][1]); // N
+        test_direction(reporter, rrects[i],    40, -1,     0,  1, kNumSteps, answers[i][2]); // NE
+        test_direction(reporter, rrects[i],    40, -1, 19.5f,  0, kNumSteps, answers[i][3]); // E
+        test_direction(reporter, rrects[i],    40, -1,    40, -1, kNumSteps, answers[i][4]); // SE
+        test_direction(reporter, rrects[i], 19.5f,  0,    40, -1, kNumSteps, answers[i][5]); // S
+        test_direction(reporter, rrects[i],     0,  1,    40, -1, kNumSteps, answers[i][6]); // SW
+        test_direction(reporter, rrects[i],     0,  1, 19.5f,  0, kNumSteps, answers[i][7]); // W
+    }
+}
+
 static void TestRoundRect(skiatest::Reporter* reporter) {
     test_round_rect_basic(reporter);
     test_round_rect_rects(reporter);
@@ -324,6 +458,7 @@ static void TestRoundRect(skiatest::Reporter* reporter) {
     test_round_rect_general(reporter);
     test_round_rect_iffy_parameters(reporter);
     test_inset(reporter);
+    test_round_rect_contains_rect(reporter);
 }
 
 #include "TestClassDef.h"