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 "SkRRect.h"
      9 #include "SkMatrix.h"
     10 
     11 ///////////////////////////////////////////////////////////////////////////////
     12 
     13 void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
     14     if (rect.isEmpty()) {
     15         this->setEmpty();
     16         return;
     17     }
     18 
     19     if (xRad <= 0 || yRad <= 0) {
     20         // all corners are square in this case
     21         this->setRect(rect);
     22         return;
     23     }
     24 
     25     if (rect.width() < xRad+xRad || rect.height() < yRad+yRad) {
     26         SkScalar scale = SkMinScalar(SkScalarDiv(rect.width(), xRad + xRad),
     27                                      SkScalarDiv(rect.height(), yRad + yRad));
     28         SkASSERT(scale < SK_Scalar1);
     29         xRad = SkScalarMul(xRad, scale);
     30         yRad = SkScalarMul(yRad, scale);
     31     }
     32 
     33     fRect = rect;
     34     for (int i = 0; i < 4; ++i) {
     35         fRadii[i].set(xRad, yRad);
     36     }
     37     fType = kSimple_Type;
     38     if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
     39         fType = kOval_Type;
     40         // TODO: assert that all the x&y radii are already W/2 & H/2
     41     }
     42 
     43     SkDEBUGCODE(this->validate();)
     44 }
     45 
     46 void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
     47     if (rect.isEmpty()) {
     48         this->setEmpty();
     49         return;
     50     }
     51 
     52     fRect = rect;
     53     memcpy(fRadii, radii, sizeof(fRadii));
     54 
     55     bool allCornersSquare = true;
     56 
     57     // Clamp negative radii to zero
     58     for (int i = 0; i < 4; ++i) {
     59         if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
     60             // In this case we are being a little fast & loose. Since one of
     61             // the radii is 0 the corner is square. However, the other radii
     62             // could still be non-zero and play in the global scale factor
     63             // computation.
     64             fRadii[i].fX = 0;
     65             fRadii[i].fY = 0;
     66         } else {
     67             allCornersSquare = false;
     68         }
     69     }
     70 
     71     if (allCornersSquare) {
     72         this->setRect(rect);
     73         return;
     74     }
     75 
     76     // Proportionally scale down all radii to fit. Find the minimum ratio
     77     // of a side and the radii on that side (for all four sides) and use
     78     // that to scale down _all_ the radii. This algorithm is from the
     79     // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
     80     // Curves:
     81     // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
     82     //   Si is the sum of the two corresponding radii of the corners on side i,
     83     //   and Ltop = Lbottom = the width of the box,
     84     //   and Lleft = Lright = the height of the box.
     85     // If f < 1, then all corner radii are reduced by multiplying them by f."
     86     SkScalar scale = SK_Scalar1;
     87 
     88     if (fRadii[0].fX + fRadii[1].fX > rect.width()) {
     89         scale = SkMinScalar(scale,
     90                             SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX));
     91     }
     92     if (fRadii[1].fY + fRadii[2].fY > rect.height()) {
     93         scale = SkMinScalar(scale,
     94                             SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY));
     95     }
     96     if (fRadii[2].fX + fRadii[3].fX > rect.width()) {
     97         scale = SkMinScalar(scale,
     98                             SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX));
     99     }
    100     if (fRadii[3].fY + fRadii[0].fY > rect.height()) {
    101         scale = SkMinScalar(scale,
    102                             SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY));
    103     }
    104 
    105     if (scale < SK_Scalar1) {
    106         for (int i = 0; i < 4; ++i) {
    107             fRadii[i].fX = SkScalarMul(fRadii[i].fX, scale);
    108             fRadii[i].fY = SkScalarMul(fRadii[i].fY, scale);
    109         }
    110     }
    111 
    112     // At this point we're either oval, simple, or complex (not empty or rect)
    113     // but we lazily resolve the type to avoid the work if the information
    114     // isn't required.
    115     fType = (SkRRect::Type) kUnknown_Type;
    116 
    117     SkDEBUGCODE(this->validate();)
    118 }
    119 
    120 // This method determines if a point known to be inside the RRect's bounds is
    121 // inside all the corners.
    122 bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
    123     SkPoint canonicalPt; // (x,y) translated to one of the quadrants
    124     int index;
    125 
    126     if (kOval_Type == this->type()) {
    127         canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
    128         index = kUpperLeft_Corner;  // any corner will do in this case
    129     } else {
    130         if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
    131             y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
    132             // UL corner
    133             index = kUpperLeft_Corner;
    134             canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
    135                             y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
    136             SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
    137         } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
    138                    y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
    139             // LL corner
    140             index = kLowerLeft_Corner;
    141             canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
    142                             y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
    143             SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
    144         } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
    145                    y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
    146             // UR corner
    147             index = kUpperRight_Corner;
    148             canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
    149                             y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
    150             SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
    151         } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
    152                    y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
    153             // LR corner
    154             index = kLowerRight_Corner;
    155             canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
    156                             y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
    157             SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
    158         } else {
    159             // not in any of the corners
    160             return true;
    161         }
    162     }
    163 
    164     // A point is in an ellipse (in standard position) if:
    165     //      x^2     y^2
    166     //     ----- + ----- <= 1
    167     //      a^2     b^2
    168     // or :
    169     //     b^2*x^2 + a^2*y^2 <= (ab)^2
    170     SkScalar dist =  SkScalarMul(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fY)) +
    171                      SkScalarMul(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fX));
    172     return dist <= SkScalarSquare(SkScalarMul(fRadii[index].fX, fRadii[index].fY));
    173 }
    174 
    175 bool SkRRect::contains(const SkRect& rect) const {
    176     if (!this->getBounds().contains(rect)) {
    177         // If 'rect' isn't contained by the RR's bounds then the
    178         // RR definitely doesn't contain it
    179         return false;
    180     }
    181 
    182     if (this->isRect()) {
    183         // the prior test was sufficient
    184         return true;
    185     }
    186 
    187     // At this point we know all four corners of 'rect' are inside the
    188     // bounds of of this RR. Check to make sure all the corners are inside
    189     // all the curves
    190     return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
    191            this->checkCornerContainment(rect.fRight, rect.fTop) &&
    192            this->checkCornerContainment(rect.fRight, rect.fBottom) &&
    193            this->checkCornerContainment(rect.fLeft, rect.fBottom);
    194 }
    195 
    196 // There is a simplified version of this method in setRectXY
    197 void SkRRect::computeType() const {
    198     SkDEBUGCODE(this->validate();)
    199 
    200     if (fRect.isEmpty()) {
    201         fType = kEmpty_Type;
    202         return;
    203     }
    204 
    205     bool allRadiiEqual = true; // are all x radii equal and all y radii?
    206     bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
    207 
    208     for (int i = 1; i < 4; ++i) {
    209         if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
    210             // if either radius is zero the corner is square so both have to
    211             // be non-zero to have a rounded corner
    212             allCornersSquare = false;
    213         }
    214         if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
    215             allRadiiEqual = false;
    216         }
    217     }
    218 
    219     if (allCornersSquare) {
    220         fType = kRect_Type;
    221         return;
    222     }
    223 
    224     if (allRadiiEqual) {
    225         if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
    226             fRadii[0].fY >= SkScalarHalf(fRect.height())) {
    227             fType = kOval_Type;
    228         } else {
    229             fType = kSimple_Type;
    230         }
    231         return;
    232     }
    233 
    234     fType = kComplex_Type;
    235 }
    236 
    237 static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
    238     const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
    239                                     | SkMatrix::kPerspective_Mask);
    240     return (matrix.getType() & m) == 0;
    241 }
    242 
    243 bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
    244     if (NULL == dst) {
    245         return false;
    246     }
    247 
    248     // Assert that the caller is not trying to do this in place, which
    249     // would violate const-ness. Do not return false though, so that
    250     // if they know what they're doing and want to violate it they can.
    251     SkASSERT(dst != this);
    252 
    253     if (matrix.isIdentity()) {
    254         *dst = *this;
    255         return true;
    256     }
    257 
    258     // If transform supported 90 degree rotations (which it could), we could
    259     // use SkMatrix::rectStaysRect() to check for a valid transformation.
    260     if (!matrix_only_scale_and_translate(matrix)) {
    261         return false;
    262     }
    263 
    264     SkRect newRect;
    265     if (!matrix.mapRect(&newRect, fRect)) {
    266         return false;
    267     }
    268 
    269     // At this point, this is guaranteed to succeed, so we can modify dst.
    270     dst->fRect = newRect;
    271 
    272     // Now scale each corner
    273     SkScalar xScale = matrix.getScaleX();
    274     const bool flipX = xScale < 0;
    275     if (flipX) {
    276         xScale = -xScale;
    277     }
    278     SkScalar yScale = matrix.getScaleY();
    279     const bool flipY = yScale < 0;
    280     if (flipY) {
    281         yScale = -yScale;
    282     }
    283 
    284     // Scale the radii without respecting the flip.
    285     for (int i = 0; i < 4; ++i) {
    286         dst->fRadii[i].fX = SkScalarMul(fRadii[i].fX, xScale);
    287         dst->fRadii[i].fY = SkScalarMul(fRadii[i].fY, yScale);
    288     }
    289 
    290     // Now swap as necessary.
    291     if (flipX) {
    292         if (flipY) {
    293             // Swap with opposite corners
    294             SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
    295             SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
    296         } else {
    297             // Only swap in x
    298             SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
    299             SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
    300         }
    301     } else if (flipY) {
    302         // Only swap in y
    303         SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
    304         SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
    305     }
    306 
    307     // Since the only transforms that were allowed are scale and translate, the type
    308     // remains unchanged.
    309     dst->fType = fType;
    310 
    311     SkDEBUGCODE(dst->validate();)
    312 
    313     return true;
    314 }
    315 
    316 ///////////////////////////////////////////////////////////////////////////////
    317 
    318 void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
    319     SkRect r = fRect;
    320 
    321     r.inset(dx, dy);
    322     if (r.isEmpty()) {
    323         dst->setEmpty();
    324         return;
    325     }
    326 
    327     SkVector radii[4];
    328     memcpy(radii, fRadii, sizeof(radii));
    329     for (int i = 0; i < 4; ++i) {
    330         if (radii[i].fX) {
    331             radii[i].fX -= dx;
    332         }
    333         if (radii[i].fY) {
    334             radii[i].fY -= dy;
    335         }
    336     }
    337     dst->setRectRadii(r, radii);
    338 }
    339 
    340 ///////////////////////////////////////////////////////////////////////////////
    341 
    342 size_t SkRRect::writeToMemory(void* buffer) const {
    343     SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii));
    344 
    345     memcpy(buffer, &fRect, sizeof(SkRect));
    346     memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii));
    347     return kSizeInMemory;
    348 }
    349 
    350 size_t SkRRect::readFromMemory(const void* buffer, size_t length) {
    351     if (length < kSizeInMemory) {
    352         return 0;
    353     }
    354 
    355     SkScalar storage[12];
    356     SkASSERT(sizeof(storage) == kSizeInMemory);
    357 
    358     // we make a local copy, to ensure alignment before we cast
    359     memcpy(storage, buffer, kSizeInMemory);
    360 
    361     this->setRectRadii(*(const SkRect*)&storage[0],
    362                        (const SkVector*)&storage[4]);
    363     return kSizeInMemory;
    364 }
    365 
    366 ///////////////////////////////////////////////////////////////////////////////
    367 
    368 #ifdef SK_DEBUG
    369 void SkRRect::validate() const {
    370     bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
    371     bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
    372     bool allRadiiSame = true;
    373 
    374     for (int i = 1; i < 4; ++i) {
    375         if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
    376             allRadiiZero = false;
    377         }
    378 
    379         if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
    380             allRadiiSame = false;
    381         }
    382 
    383         if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
    384             allCornersSquare = false;
    385         }
    386     }
    387 
    388     switch (fType) {
    389         case kEmpty_Type:
    390             SkASSERT(fRect.isEmpty());
    391             SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
    392 
    393             SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop &&
    394                      0 == fRect.fRight && 0 == fRect.fBottom);
    395             break;
    396         case kRect_Type:
    397             SkASSERT(!fRect.isEmpty());
    398             SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
    399             break;
    400         case kOval_Type:
    401             SkASSERT(!fRect.isEmpty());
    402             SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
    403 
    404             for (int i = 0; i < 4; ++i) {
    405                 SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())));
    406                 SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height())));
    407             }
    408             break;
    409         case kSimple_Type:
    410             SkASSERT(!fRect.isEmpty());
    411             SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
    412             break;
    413         case kComplex_Type:
    414             SkASSERT(!fRect.isEmpty());
    415             SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
    416             break;
    417         case kUnknown_Type:
    418             // no limits on this
    419             break;
    420     }
    421 }
    422 #endif // SK_DEBUG
    423 
    424 ///////////////////////////////////////////////////////////////////////////////
    425