Home | History | Annotate | Download | only in core
      1 /*
      2  * Copyright 2012 Google Inc.
      3  *
      4  * Use of this source code is governed by a BSD-style license that can be
      5  * found in the LICENSE file.
      6  */
      7 
      8 #include <cmath>
      9 #include "SkRRect.h"
     10 #include "SkMatrix.h"
     11 #include "SkScaleToSides.h"
     12 
     13 ///////////////////////////////////////////////////////////////////////////////
     14 
     15 void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
     16     fRect = rect;
     17     fRect.sort();
     18 
     19     if (fRect.isEmpty() || !fRect.isFinite()) {
     20         this->setEmpty();
     21         return;
     22     }
     23 
     24     if (!SkScalarsAreFinite(xRad, yRad)) {
     25         xRad = yRad = 0;    // devolve into a simple rect
     26     }
     27     if (xRad <= 0 || yRad <= 0) {
     28         // all corners are square in this case
     29         this->setRect(rect);
     30         return;
     31     }
     32 
     33     if (fRect.width() < xRad+xRad || fRect.height() < yRad+yRad) {
     34         SkScalar scale = SkMinScalar(fRect.width() / (xRad + xRad), fRect.height() / (yRad + yRad));
     35         SkASSERT(scale < SK_Scalar1);
     36         xRad *= scale;
     37         yRad *= scale;
     38     }
     39 
     40     for (int i = 0; i < 4; ++i) {
     41         fRadii[i].set(xRad, yRad);
     42     }
     43     fType = kSimple_Type;
     44     if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
     45         fType = kOval_Type;
     46         // TODO: assert that all the x&y radii are already W/2 & H/2
     47     }
     48 
     49     SkASSERT(this->isValid());
     50 }
     51 
     52 void SkRRect::setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
     53                            SkScalar rightRad, SkScalar bottomRad) {
     54     fRect = rect;
     55     fRect.sort();
     56 
     57     if (fRect.isEmpty() || !fRect.isFinite()) {
     58         this->setEmpty();
     59         return;
     60     }
     61 
     62     const SkScalar array[4] = { leftRad, topRad, rightRad, bottomRad };
     63     if (!SkScalarsAreFinite(array, 4)) {
     64         this->setRect(rect);    // devolve into a simple rect
     65         return;
     66     }
     67 
     68     leftRad = SkMaxScalar(leftRad, 0);
     69     topRad = SkMaxScalar(topRad, 0);
     70     rightRad = SkMaxScalar(rightRad, 0);
     71     bottomRad = SkMaxScalar(bottomRad, 0);
     72 
     73     SkScalar scale = SK_Scalar1;
     74     if (leftRad + rightRad > fRect.width()) {
     75         scale = fRect.width() / (leftRad + rightRad);
     76     }
     77     if (topRad + bottomRad > fRect.height()) {
     78         scale = SkMinScalar(scale, fRect.height() / (topRad + bottomRad));
     79     }
     80 
     81     if (scale < SK_Scalar1) {
     82         leftRad *= scale;
     83         topRad *= scale;
     84         rightRad *= scale;
     85         bottomRad *= scale;
     86     }
     87 
     88     if (leftRad == rightRad && topRad == bottomRad) {
     89         if (leftRad >= SkScalarHalf(fRect.width()) && topRad >= SkScalarHalf(fRect.height())) {
     90             fType = kOval_Type;
     91         } else if (0 == leftRad || 0 == topRad) {
     92             // If the left and (by equality check above) right radii are zero then it is a rect.
     93             // Same goes for top/bottom.
     94             fType = kRect_Type;
     95             leftRad = 0;
     96             topRad = 0;
     97             rightRad = 0;
     98             bottomRad = 0;
     99         } else {
    100             fType = kSimple_Type;
    101         }
    102     } else {
    103         fType = kNinePatch_Type;
    104     }
    105 
    106     fRadii[kUpperLeft_Corner].set(leftRad, topRad);
    107     fRadii[kUpperRight_Corner].set(rightRad, topRad);
    108     fRadii[kLowerRight_Corner].set(rightRad, bottomRad);
    109     fRadii[kLowerLeft_Corner].set(leftRad, bottomRad);
    110 
    111     SkASSERT(this->isValid());
    112 }
    113 
    114 // These parameters intentionally double. Apropos crbug.com/463920, if one of the
    115 // radii is huge while the other is small, single precision math can completely
    116 // miss the fact that a scale is required.
    117 static double compute_min_scale(double rad1, double rad2, double limit, double curMin) {
    118     if ((rad1 + rad2) > limit) {
    119         return SkTMin(curMin, limit / (rad1 + rad2));
    120     }
    121     return curMin;
    122 }
    123 
    124 void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
    125     fRect = rect;
    126     fRect.sort();
    127 
    128     if (fRect.isEmpty() || !fRect.isFinite()) {
    129         this->setEmpty();
    130         return;
    131     }
    132 
    133     if (!SkScalarsAreFinite(&radii[0].fX, 8)) {
    134         this->setRect(rect);    // devolve into a simple rect
    135         return;
    136     }
    137 
    138     memcpy(fRadii, radii, sizeof(fRadii));
    139 
    140     bool allCornersSquare = true;
    141 
    142     // Clamp negative radii to zero
    143     for (int i = 0; i < 4; ++i) {
    144         if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
    145             // In this case we are being a little fast & loose. Since one of
    146             // the radii is 0 the corner is square. However, the other radii
    147             // could still be non-zero and play in the global scale factor
    148             // computation.
    149             fRadii[i].fX = 0;
    150             fRadii[i].fY = 0;
    151         } else {
    152             allCornersSquare = false;
    153         }
    154     }
    155 
    156     if (allCornersSquare) {
    157         this->setRect(rect);
    158         return;
    159     }
    160 
    161     this->scaleRadii();
    162 }
    163 
    164 void SkRRect::scaleRadii() {
    165 
    166     // Proportionally scale down all radii to fit. Find the minimum ratio
    167     // of a side and the radii on that side (for all four sides) and use
    168     // that to scale down _all_ the radii. This algorithm is from the
    169     // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
    170     // Curves:
    171     // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
    172     //   Si is the sum of the two corresponding radii of the corners on side i,
    173     //   and Ltop = Lbottom = the width of the box,
    174     //   and Lleft = Lright = the height of the box.
    175     // If f < 1, then all corner radii are reduced by multiplying them by f."
    176     double scale = 1.0;
    177 
    178     // The sides of the rectangle may be larger than a float.
    179     double width = (double)fRect.fRight - (double)fRect.fLeft;
    180     double height = (double)fRect.fBottom - (double)fRect.fTop;
    181     scale = compute_min_scale(fRadii[0].fX, fRadii[1].fX, width,  scale);
    182     scale = compute_min_scale(fRadii[1].fY, fRadii[2].fY, height, scale);
    183     scale = compute_min_scale(fRadii[2].fX, fRadii[3].fX, width,  scale);
    184     scale = compute_min_scale(fRadii[3].fY, fRadii[0].fY, height, scale);
    185 
    186     if (scale < 1.0) {
    187         SkScaleToSides::AdjustRadii(width,  scale, &fRadii[0].fX, &fRadii[1].fX);
    188         SkScaleToSides::AdjustRadii(height, scale, &fRadii[1].fY, &fRadii[2].fY);
    189         SkScaleToSides::AdjustRadii(width,  scale, &fRadii[2].fX, &fRadii[3].fX);
    190         SkScaleToSides::AdjustRadii(height, scale, &fRadii[3].fY, &fRadii[0].fY);
    191     }
    192 
    193     // At this point we're either oval, simple, or complex (not empty or rect).
    194     this->computeType();
    195 
    196     SkASSERT(this->isValid());
    197 }
    198 
    199 // This method determines if a point known to be inside the RRect's bounds is
    200 // inside all the corners.
    201 bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
    202     SkPoint canonicalPt; // (x,y) translated to one of the quadrants
    203     int index;
    204 
    205     if (kOval_Type == this->type()) {
    206         canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
    207         index = kUpperLeft_Corner;  // any corner will do in this case
    208     } else {
    209         if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
    210             y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
    211             // UL corner
    212             index = kUpperLeft_Corner;
    213             canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
    214                             y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
    215             SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
    216         } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
    217                    y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
    218             // LL corner
    219             index = kLowerLeft_Corner;
    220             canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
    221                             y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
    222             SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
    223         } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
    224                    y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
    225             // UR corner
    226             index = kUpperRight_Corner;
    227             canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
    228                             y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
    229             SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
    230         } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
    231                    y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
    232             // LR corner
    233             index = kLowerRight_Corner;
    234             canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
    235                             y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
    236             SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
    237         } else {
    238             // not in any of the corners
    239             return true;
    240         }
    241     }
    242 
    243     // A point is in an ellipse (in standard position) if:
    244     //      x^2     y^2
    245     //     ----- + ----- <= 1
    246     //      a^2     b^2
    247     // or :
    248     //     b^2*x^2 + a^2*y^2 <= (ab)^2
    249     SkScalar dist =  SkScalarSquare(canonicalPt.fX) * SkScalarSquare(fRadii[index].fY) +
    250                      SkScalarSquare(canonicalPt.fY) * SkScalarSquare(fRadii[index].fX);
    251     return dist <= SkScalarSquare(fRadii[index].fX * fRadii[index].fY);
    252 }
    253 
    254 bool SkRRect::allCornersCircular(SkScalar tolerance) const {
    255     return SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY, tolerance) &&
    256            SkScalarNearlyEqual(fRadii[1].fX, fRadii[1].fY, tolerance) &&
    257            SkScalarNearlyEqual(fRadii[2].fX, fRadii[2].fY, tolerance) &&
    258            SkScalarNearlyEqual(fRadii[3].fX, fRadii[3].fY, tolerance);
    259 }
    260 
    261 bool SkRRect::contains(const SkRect& rect) const {
    262     if (!this->getBounds().contains(rect)) {
    263         // If 'rect' isn't contained by the RR's bounds then the
    264         // RR definitely doesn't contain it
    265         return false;
    266     }
    267 
    268     if (this->isRect()) {
    269         // the prior test was sufficient
    270         return true;
    271     }
    272 
    273     // At this point we know all four corners of 'rect' are inside the
    274     // bounds of of this RR. Check to make sure all the corners are inside
    275     // all the curves
    276     return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
    277            this->checkCornerContainment(rect.fRight, rect.fTop) &&
    278            this->checkCornerContainment(rect.fRight, rect.fBottom) &&
    279            this->checkCornerContainment(rect.fLeft, rect.fBottom);
    280 }
    281 
    282 static bool radii_are_nine_patch(const SkVector radii[4]) {
    283     return radii[SkRRect::kUpperLeft_Corner].fX == radii[SkRRect::kLowerLeft_Corner].fX &&
    284            radii[SkRRect::kUpperLeft_Corner].fY == radii[SkRRect::kUpperRight_Corner].fY &&
    285            radii[SkRRect::kUpperRight_Corner].fX == radii[SkRRect::kLowerRight_Corner].fX &&
    286            radii[SkRRect::kLowerLeft_Corner].fY == radii[SkRRect::kLowerRight_Corner].fY;
    287 }
    288 
    289 // There is a simplified version of this method in setRectXY
    290 void SkRRect::computeType() {
    291     struct Validator {
    292         Validator(const SkRRect* r) : fR(r) {}
    293         ~Validator() { SkASSERT(fR->isValid()); }
    294         const SkRRect* fR;
    295     } autoValidate(this);
    296 
    297     if (fRect.isEmpty()) {
    298         fType = kEmpty_Type;
    299         return;
    300     }
    301 
    302     bool allRadiiEqual = true; // are all x radii equal and all y radii?
    303     bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
    304 
    305     for (int i = 1; i < 4; ++i) {
    306         if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
    307             // if either radius is zero the corner is square so both have to
    308             // be non-zero to have a rounded corner
    309             allCornersSquare = false;
    310         }
    311         if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
    312             allRadiiEqual = false;
    313         }
    314     }
    315 
    316     if (allCornersSquare) {
    317         fType = kRect_Type;
    318         return;
    319     }
    320 
    321     if (allRadiiEqual) {
    322         if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
    323             fRadii[0].fY >= SkScalarHalf(fRect.height())) {
    324             fType = kOval_Type;
    325         } else {
    326             fType = kSimple_Type;
    327         }
    328         return;
    329     }
    330 
    331     if (radii_are_nine_patch(fRadii)) {
    332         fType = kNinePatch_Type;
    333     } else {
    334         fType = kComplex_Type;
    335     }
    336 }
    337 
    338 static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
    339     const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
    340                                     | SkMatrix::kPerspective_Mask);
    341     return (matrix.getType() & m) == 0;
    342 }
    343 
    344 bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
    345     if (nullptr == dst) {
    346         return false;
    347     }
    348 
    349     // Assert that the caller is not trying to do this in place, which
    350     // would violate const-ness. Do not return false though, so that
    351     // if they know what they're doing and want to violate it they can.
    352     SkASSERT(dst != this);
    353 
    354     if (matrix.isIdentity()) {
    355         *dst = *this;
    356         return true;
    357     }
    358 
    359     // If transform supported 90 degree rotations (which it could), we could
    360     // use SkMatrix::rectStaysRect() to check for a valid transformation.
    361     if (!matrix_only_scale_and_translate(matrix)) {
    362         return false;
    363     }
    364 
    365     SkRect newRect;
    366     if (!matrix.mapRect(&newRect, fRect)) {
    367         return false;
    368     }
    369 
    370     // The matrix may have scaled us to zero (or due to float madness, we now have collapsed
    371     // some dimension of the rect, so we need to check for that.
    372     if (newRect.isEmpty()) {
    373         dst->setEmpty();
    374         return true;
    375     }
    376 
    377     // At this point, this is guaranteed to succeed, so we can modify dst.
    378     dst->fRect = newRect;
    379 
    380     // Since the only transforms that were allowed are scale and translate, the type
    381     // remains unchanged.
    382     dst->fType = fType;
    383 
    384     if (kOval_Type == fType) {
    385         for (int i = 0; i < 4; ++i) {
    386             dst->fRadii[i].fX = SkScalarHalf(newRect.width());
    387             dst->fRadii[i].fY = SkScalarHalf(newRect.height());
    388         }
    389         SkASSERT(dst->isValid());
    390         return true;
    391     }
    392 
    393     // Now scale each corner
    394     SkScalar xScale = matrix.getScaleX();
    395     const bool flipX = xScale < 0;
    396     if (flipX) {
    397         xScale = -xScale;
    398     }
    399     SkScalar yScale = matrix.getScaleY();
    400     const bool flipY = yScale < 0;
    401     if (flipY) {
    402         yScale = -yScale;
    403     }
    404 
    405     // Scale the radii without respecting the flip.
    406     for (int i = 0; i < 4; ++i) {
    407         dst->fRadii[i].fX = fRadii[i].fX * xScale;
    408         dst->fRadii[i].fY = fRadii[i].fY * yScale;
    409     }
    410 
    411     // Now swap as necessary.
    412     if (flipX) {
    413         if (flipY) {
    414             // Swap with opposite corners
    415             SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
    416             SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
    417         } else {
    418             // Only swap in x
    419             SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
    420             SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
    421         }
    422     } else if (flipY) {
    423         // Only swap in y
    424         SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
    425         SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
    426     }
    427 
    428     dst->scaleRadii();
    429 
    430     return true;
    431 }
    432 
    433 ///////////////////////////////////////////////////////////////////////////////
    434 
    435 void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
    436     const SkRect r = fRect.makeInset(dx, dy);
    437 
    438     if (r.isEmpty()) {
    439         dst->setEmpty();
    440         return;
    441     }
    442 
    443     SkVector radii[4];
    444     memcpy(radii, fRadii, sizeof(radii));
    445     for (int i = 0; i < 4; ++i) {
    446         if (radii[i].fX) {
    447             radii[i].fX -= dx;
    448         }
    449         if (radii[i].fY) {
    450             radii[i].fY -= dy;
    451         }
    452     }
    453     dst->setRectRadii(r, radii);
    454 }
    455 
    456 ///////////////////////////////////////////////////////////////////////////////
    457 
    458 size_t SkRRect::writeToMemory(void* buffer) const {
    459     // Serialize only the rect and corners, but not the derived type tag.
    460     memcpy(buffer, this, kSizeInMemory);
    461     return kSizeInMemory;
    462 }
    463 
    464 size_t SkRRect::readFromMemory(const void* buffer, size_t length) {
    465     if (length < kSizeInMemory) {
    466         return 0;
    467     }
    468 
    469     // Deserialize rect and corners, then rederive the type tag.
    470     memcpy(this, buffer, kSizeInMemory);
    471     this->computeType();
    472 
    473     return kSizeInMemory;
    474 }
    475 
    476 #include "SkString.h"
    477 #include "SkStringUtils.h"
    478 
    479 void SkRRect::dump(bool asHex) const {
    480     SkScalarAsStringType asType = asHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
    481 
    482     fRect.dump(asHex);
    483     SkString line("const SkPoint corners[] = {\n");
    484     for (int i = 0; i < 4; ++i) {
    485         SkString strX, strY;
    486         SkAppendScalar(&strX, fRadii[i].x(), asType);
    487         SkAppendScalar(&strY, fRadii[i].y(), asType);
    488         line.appendf("    { %s, %s },", strX.c_str(), strY.c_str());
    489         if (asHex) {
    490             line.appendf(" /* %f %f */", fRadii[i].x(), fRadii[i].y());
    491         }
    492         line.append("\n");
    493     }
    494     line.append("};");
    495     SkDebugf("%s\n", line.c_str());
    496 }
    497 
    498 ///////////////////////////////////////////////////////////////////////////////
    499 
    500 /**
    501  *  We need all combinations of predicates to be true to have a "safe" radius value.
    502  */
    503 static bool are_radius_check_predicates_valid(SkScalar rad, SkScalar min, SkScalar max) {
    504     return (min <= max) && (rad <= max - min) && (min + rad <= max) && (max - rad >= min);
    505 }
    506 
    507 bool SkRRect::isValid() const {
    508     bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
    509     bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
    510     bool allRadiiSame = true;
    511 
    512     for (int i = 1; i < 4; ++i) {
    513         if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
    514             allRadiiZero = false;
    515         }
    516 
    517         if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
    518             allRadiiSame = false;
    519         }
    520 
    521         if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
    522             allCornersSquare = false;
    523         }
    524     }
    525     bool patchesOfNine = radii_are_nine_patch(fRadii);
    526 
    527     switch (fType) {
    528         case kEmpty_Type:
    529             if (!fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) {
    530                 return false;
    531             }
    532             break;
    533         case kRect_Type:
    534             if (fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) {
    535                 return false;
    536             }
    537             break;
    538         case kOval_Type:
    539             if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) {
    540                 return false;
    541             }
    542 
    543             for (int i = 0; i < 4; ++i) {
    544                 if (!SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())) ||
    545                     !SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height()))) {
    546                     return false;
    547                 }
    548             }
    549             break;
    550         case kSimple_Type:
    551             if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) {
    552                 return false;
    553             }
    554             break;
    555         case kNinePatch_Type:
    556             if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare ||
    557                 !patchesOfNine) {
    558                 return false;
    559             }
    560             break;
    561         case kComplex_Type:
    562             if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare ||
    563                 patchesOfNine) {
    564                 return false;
    565             }
    566             break;
    567     }
    568 
    569     for (int i = 0; i < 4; ++i) {
    570         if (!are_radius_check_predicates_valid(fRadii[i].fX, fRect.fLeft, fRect.fRight) ||
    571             !are_radius_check_predicates_valid(fRadii[i].fY, fRect.fTop, fRect.fBottom)) {
    572             return false;
    573         }
    574     }
    575 
    576     return true;
    577 }
    578 
    579 ///////////////////////////////////////////////////////////////////////////////
    580