gfx/skia/trunk/src/core/SkRRect.cpp

Thu, 22 Jan 2015 13:21:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Thu, 22 Jan 2015 13:21:57 +0100
branch
TOR_BUG_9701
changeset 15
b8a032363ba2
permissions
-rw-r--r--

Incorporate requested changes from Mozilla in review:
https://bugzilla.mozilla.org/show_bug.cgi?id=1123480#c6

     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  */
     8 #include "SkRRect.h"
     9 #include "SkMatrix.h"
    11 ///////////////////////////////////////////////////////////////////////////////
    13 void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
    14     if (rect.isEmpty()) {
    15         this->setEmpty();
    16         return;
    17     }
    19     if (xRad <= 0 || yRad <= 0) {
    20         // all corners are square in this case
    21         this->setRect(rect);
    22         return;
    23     }
    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     }
    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     }
    43     SkDEBUGCODE(this->validate();)
    44 }
    46 void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
    47     if (rect.isEmpty()) {
    48         this->setEmpty();
    49         return;
    50     }
    52     fRect = rect;
    53     memcpy(fRadii, radii, sizeof(fRadii));
    55     bool allCornersSquare = true;
    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     }
    71     if (allCornersSquare) {
    72         this->setRect(rect);
    73         return;
    74     }
    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;
    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     }
   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     }
   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;
   117     SkDEBUGCODE(this->validate();)
   118 }
   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;
   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     }
   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 }
   175 bool SkRRect::allCornersCircular() const {
   176     return fRadii[0].fX == fRadii[0].fY &&
   177         fRadii[1].fX == fRadii[1].fY &&
   178         fRadii[2].fX == fRadii[2].fY &&
   179         fRadii[3].fX == fRadii[3].fY;
   180 }
   182 bool SkRRect::contains(const SkRect& rect) const {
   183     if (!this->getBounds().contains(rect)) {
   184         // If 'rect' isn't contained by the RR's bounds then the
   185         // RR definitely doesn't contain it
   186         return false;
   187     }
   189     if (this->isRect()) {
   190         // the prior test was sufficient
   191         return true;
   192     }
   194     // At this point we know all four corners of 'rect' are inside the
   195     // bounds of of this RR. Check to make sure all the corners are inside
   196     // all the curves
   197     return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
   198            this->checkCornerContainment(rect.fRight, rect.fTop) &&
   199            this->checkCornerContainment(rect.fRight, rect.fBottom) &&
   200            this->checkCornerContainment(rect.fLeft, rect.fBottom);
   201 }
   203 // There is a simplified version of this method in setRectXY
   204 void SkRRect::computeType() const {
   205     SkDEBUGCODE(this->validate();)
   207     if (fRect.isEmpty()) {
   208         fType = kEmpty_Type;
   209         return;
   210     }
   212     bool allRadiiEqual = true; // are all x radii equal and all y radii?
   213     bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
   215     for (int i = 1; i < 4; ++i) {
   216         if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
   217             // if either radius is zero the corner is square so both have to
   218             // be non-zero to have a rounded corner
   219             allCornersSquare = false;
   220         }
   221         if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
   222             allRadiiEqual = false;
   223         }
   224     }
   226     if (allCornersSquare) {
   227         fType = kRect_Type;
   228         return;
   229     }
   231     if (allRadiiEqual) {
   232         if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
   233             fRadii[0].fY >= SkScalarHalf(fRect.height())) {
   234             fType = kOval_Type;
   235         } else {
   236             fType = kSimple_Type;
   237         }
   238         return;
   239     }
   241     fType = kComplex_Type;
   242 }
   244 static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
   245     const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
   246                                     | SkMatrix::kPerspective_Mask);
   247     return (matrix.getType() & m) == 0;
   248 }
   250 bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
   251     if (NULL == dst) {
   252         return false;
   253     }
   255     // Assert that the caller is not trying to do this in place, which
   256     // would violate const-ness. Do not return false though, so that
   257     // if they know what they're doing and want to violate it they can.
   258     SkASSERT(dst != this);
   260     if (matrix.isIdentity()) {
   261         *dst = *this;
   262         return true;
   263     }
   265     // If transform supported 90 degree rotations (which it could), we could
   266     // use SkMatrix::rectStaysRect() to check for a valid transformation.
   267     if (!matrix_only_scale_and_translate(matrix)) {
   268         return false;
   269     }
   271     SkRect newRect;
   272     if (!matrix.mapRect(&newRect, fRect)) {
   273         return false;
   274     }
   276     // At this point, this is guaranteed to succeed, so we can modify dst.
   277     dst->fRect = newRect;
   279     // Now scale each corner
   280     SkScalar xScale = matrix.getScaleX();
   281     const bool flipX = xScale < 0;
   282     if (flipX) {
   283         xScale = -xScale;
   284     }
   285     SkScalar yScale = matrix.getScaleY();
   286     const bool flipY = yScale < 0;
   287     if (flipY) {
   288         yScale = -yScale;
   289     }
   291     // Scale the radii without respecting the flip.
   292     for (int i = 0; i < 4; ++i) {
   293         dst->fRadii[i].fX = SkScalarMul(fRadii[i].fX, xScale);
   294         dst->fRadii[i].fY = SkScalarMul(fRadii[i].fY, yScale);
   295     }
   297     // Now swap as necessary.
   298     if (flipX) {
   299         if (flipY) {
   300             // Swap with opposite corners
   301             SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
   302             SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
   303         } else {
   304             // Only swap in x
   305             SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
   306             SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
   307         }
   308     } else if (flipY) {
   309         // Only swap in y
   310         SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
   311         SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
   312     }
   314     // Since the only transforms that were allowed are scale and translate, the type
   315     // remains unchanged.
   316     dst->fType = fType;
   318     SkDEBUGCODE(dst->validate();)
   320     return true;
   321 }
   323 ///////////////////////////////////////////////////////////////////////////////
   325 void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
   326     SkRect r = fRect;
   328     r.inset(dx, dy);
   329     if (r.isEmpty()) {
   330         dst->setEmpty();
   331         return;
   332     }
   334     SkVector radii[4];
   335     memcpy(radii, fRadii, sizeof(radii));
   336     for (int i = 0; i < 4; ++i) {
   337         if (radii[i].fX) {
   338             radii[i].fX -= dx;
   339         }
   340         if (radii[i].fY) {
   341             radii[i].fY -= dy;
   342         }
   343     }
   344     dst->setRectRadii(r, radii);
   345 }
   347 ///////////////////////////////////////////////////////////////////////////////
   349 size_t SkRRect::writeToMemory(void* buffer) const {
   350     SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii));
   352     memcpy(buffer, &fRect, sizeof(SkRect));
   353     memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii));
   354     return kSizeInMemory;
   355 }
   357 size_t SkRRect::readFromMemory(const void* buffer, size_t length) {
   358     if (length < kSizeInMemory) {
   359         return 0;
   360     }
   362     SkScalar storage[12];
   363     SkASSERT(sizeof(storage) == kSizeInMemory);
   365     // we make a local copy, to ensure alignment before we cast
   366     memcpy(storage, buffer, kSizeInMemory);
   368     this->setRectRadii(*(const SkRect*)&storage[0],
   369                        (const SkVector*)&storage[4]);
   370     return kSizeInMemory;
   371 }
   373 ///////////////////////////////////////////////////////////////////////////////
   375 #ifdef SK_DEBUG
   376 void SkRRect::validate() const {
   377     bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
   378     bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
   379     bool allRadiiSame = true;
   381     for (int i = 1; i < 4; ++i) {
   382         if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
   383             allRadiiZero = false;
   384         }
   386         if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
   387             allRadiiSame = false;
   388         }
   390         if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
   391             allCornersSquare = false;
   392         }
   393     }
   395     switch (fType) {
   396         case kEmpty_Type:
   397             SkASSERT(fRect.isEmpty());
   398             SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
   400             SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop &&
   401                      0 == fRect.fRight && 0 == fRect.fBottom);
   402             break;
   403         case kRect_Type:
   404             SkASSERT(!fRect.isEmpty());
   405             SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
   406             break;
   407         case kOval_Type:
   408             SkASSERT(!fRect.isEmpty());
   409             SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
   411             for (int i = 0; i < 4; ++i) {
   412                 SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())));
   413                 SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height())));
   414             }
   415             break;
   416         case kSimple_Type:
   417             SkASSERT(!fRect.isEmpty());
   418             SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
   419             break;
   420         case kComplex_Type:
   421             SkASSERT(!fRect.isEmpty());
   422             SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
   423             break;
   424         case kUnknown_Type:
   425             // no limits on this
   426             break;
   427     }
   428 }
   429 #endif // SK_DEBUG
   431 ///////////////////////////////////////////////////////////////////////////////

mercurial