michael@0: /* michael@0: * Copyright 2012 Google Inc. michael@0: * michael@0: * Use of this source code is governed by a BSD-style license that can be michael@0: * found in the LICENSE file. michael@0: */ michael@0: michael@0: #include "SkRRect.h" michael@0: #include "SkMatrix.h" michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) { michael@0: if (rect.isEmpty()) { michael@0: this->setEmpty(); michael@0: return; michael@0: } michael@0: michael@0: if (xRad <= 0 || yRad <= 0) { michael@0: // all corners are square in this case michael@0: this->setRect(rect); michael@0: return; michael@0: } michael@0: michael@0: if (rect.width() < xRad+xRad || rect.height() < yRad+yRad) { michael@0: SkScalar scale = SkMinScalar(SkScalarDiv(rect.width(), xRad + xRad), michael@0: SkScalarDiv(rect.height(), yRad + yRad)); michael@0: SkASSERT(scale < SK_Scalar1); michael@0: xRad = SkScalarMul(xRad, scale); michael@0: yRad = SkScalarMul(yRad, scale); michael@0: } michael@0: michael@0: fRect = rect; michael@0: for (int i = 0; i < 4; ++i) { michael@0: fRadii[i].set(xRad, yRad); michael@0: } michael@0: fType = kSimple_Type; michael@0: if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) { michael@0: fType = kOval_Type; michael@0: // TODO: assert that all the x&y radii are already W/2 & H/2 michael@0: } michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) { michael@0: if (rect.isEmpty()) { michael@0: this->setEmpty(); michael@0: return; michael@0: } michael@0: michael@0: fRect = rect; michael@0: memcpy(fRadii, radii, sizeof(fRadii)); michael@0: michael@0: bool allCornersSquare = true; michael@0: michael@0: // Clamp negative radii to zero michael@0: for (int i = 0; i < 4; ++i) { michael@0: if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) { michael@0: // In this case we are being a little fast & loose. Since one of michael@0: // the radii is 0 the corner is square. However, the other radii michael@0: // could still be non-zero and play in the global scale factor michael@0: // computation. michael@0: fRadii[i].fX = 0; michael@0: fRadii[i].fY = 0; michael@0: } else { michael@0: allCornersSquare = false; michael@0: } michael@0: } michael@0: michael@0: if (allCornersSquare) { michael@0: this->setRect(rect); michael@0: return; michael@0: } michael@0: michael@0: // Proportionally scale down all radii to fit. Find the minimum ratio michael@0: // of a side and the radii on that side (for all four sides) and use michael@0: // that to scale down _all_ the radii. This algorithm is from the michael@0: // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping michael@0: // Curves: michael@0: // "Let f = min(Li/Si), where i is one of { top, right, bottom, left }, michael@0: // Si is the sum of the two corresponding radii of the corners on side i, michael@0: // and Ltop = Lbottom = the width of the box, michael@0: // and Lleft = Lright = the height of the box. michael@0: // If f < 1, then all corner radii are reduced by multiplying them by f." michael@0: SkScalar scale = SK_Scalar1; michael@0: michael@0: if (fRadii[0].fX + fRadii[1].fX > rect.width()) { michael@0: scale = SkMinScalar(scale, michael@0: SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX)); michael@0: } michael@0: if (fRadii[1].fY + fRadii[2].fY > rect.height()) { michael@0: scale = SkMinScalar(scale, michael@0: SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY)); michael@0: } michael@0: if (fRadii[2].fX + fRadii[3].fX > rect.width()) { michael@0: scale = SkMinScalar(scale, michael@0: SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX)); michael@0: } michael@0: if (fRadii[3].fY + fRadii[0].fY > rect.height()) { michael@0: scale = SkMinScalar(scale, michael@0: SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY)); michael@0: } michael@0: michael@0: if (scale < SK_Scalar1) { michael@0: for (int i = 0; i < 4; ++i) { michael@0: fRadii[i].fX = SkScalarMul(fRadii[i].fX, scale); michael@0: fRadii[i].fY = SkScalarMul(fRadii[i].fY, scale); michael@0: } michael@0: } michael@0: michael@0: // At this point we're either oval, simple, or complex (not empty or rect) michael@0: // but we lazily resolve the type to avoid the work if the information michael@0: // isn't required. michael@0: fType = (SkRRect::Type) kUnknown_Type; michael@0: michael@0: SkDEBUGCODE(this->validate();) michael@0: } michael@0: michael@0: // This method determines if a point known to be inside the RRect's bounds is michael@0: // inside all the corners. michael@0: bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const { michael@0: SkPoint canonicalPt; // (x,y) translated to one of the quadrants michael@0: int index; michael@0: michael@0: if (kOval_Type == this->type()) { michael@0: canonicalPt.set(x - fRect.centerX(), y - fRect.centerY()); michael@0: index = kUpperLeft_Corner; // any corner will do in this case michael@0: } else { michael@0: if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX && michael@0: y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) { michael@0: // UL corner michael@0: index = kUpperLeft_Corner; michael@0: canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX), michael@0: y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY)); michael@0: SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0); michael@0: } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX && michael@0: y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) { michael@0: // LL corner michael@0: index = kLowerLeft_Corner; michael@0: canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX), michael@0: y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY)); michael@0: SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0); michael@0: } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX && michael@0: y < fRect.fTop + fRadii[kUpperRight_Corner].fY) { michael@0: // UR corner michael@0: index = kUpperRight_Corner; michael@0: canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX), michael@0: y - (fRect.fTop + fRadii[kUpperRight_Corner].fY)); michael@0: SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0); michael@0: } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX && michael@0: y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) { michael@0: // LR corner michael@0: index = kLowerRight_Corner; michael@0: canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX), michael@0: y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY)); michael@0: SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0); michael@0: } else { michael@0: // not in any of the corners michael@0: return true; michael@0: } michael@0: } michael@0: michael@0: // A point is in an ellipse (in standard position) if: michael@0: // x^2 y^2 michael@0: // ----- + ----- <= 1 michael@0: // a^2 b^2 michael@0: // or : michael@0: // b^2*x^2 + a^2*y^2 <= (ab)^2 michael@0: SkScalar dist = SkScalarMul(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fY)) + michael@0: SkScalarMul(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fX)); michael@0: return dist <= SkScalarSquare(SkScalarMul(fRadii[index].fX, fRadii[index].fY)); michael@0: } michael@0: michael@0: bool SkRRect::allCornersCircular() const { michael@0: return fRadii[0].fX == fRadii[0].fY && michael@0: fRadii[1].fX == fRadii[1].fY && michael@0: fRadii[2].fX == fRadii[2].fY && michael@0: fRadii[3].fX == fRadii[3].fY; michael@0: } michael@0: michael@0: bool SkRRect::contains(const SkRect& rect) const { michael@0: if (!this->getBounds().contains(rect)) { michael@0: // If 'rect' isn't contained by the RR's bounds then the michael@0: // RR definitely doesn't contain it michael@0: return false; michael@0: } michael@0: michael@0: if (this->isRect()) { michael@0: // the prior test was sufficient michael@0: return true; michael@0: } michael@0: michael@0: // At this point we know all four corners of 'rect' are inside the michael@0: // bounds of of this RR. Check to make sure all the corners are inside michael@0: // all the curves michael@0: return this->checkCornerContainment(rect.fLeft, rect.fTop) && michael@0: this->checkCornerContainment(rect.fRight, rect.fTop) && michael@0: this->checkCornerContainment(rect.fRight, rect.fBottom) && michael@0: this->checkCornerContainment(rect.fLeft, rect.fBottom); michael@0: } michael@0: michael@0: // There is a simplified version of this method in setRectXY michael@0: void SkRRect::computeType() const { michael@0: SkDEBUGCODE(this->validate();) michael@0: michael@0: if (fRect.isEmpty()) { michael@0: fType = kEmpty_Type; michael@0: return; michael@0: } michael@0: michael@0: bool allRadiiEqual = true; // are all x radii equal and all y radii? michael@0: bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY; michael@0: michael@0: for (int i = 1; i < 4; ++i) { michael@0: if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { michael@0: // if either radius is zero the corner is square so both have to michael@0: // be non-zero to have a rounded corner michael@0: allCornersSquare = false; michael@0: } michael@0: if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { michael@0: allRadiiEqual = false; michael@0: } michael@0: } michael@0: michael@0: if (allCornersSquare) { michael@0: fType = kRect_Type; michael@0: return; michael@0: } michael@0: michael@0: if (allRadiiEqual) { michael@0: if (fRadii[0].fX >= SkScalarHalf(fRect.width()) && michael@0: fRadii[0].fY >= SkScalarHalf(fRect.height())) { michael@0: fType = kOval_Type; michael@0: } else { michael@0: fType = kSimple_Type; michael@0: } michael@0: return; michael@0: } michael@0: michael@0: fType = kComplex_Type; michael@0: } michael@0: michael@0: static bool matrix_only_scale_and_translate(const SkMatrix& matrix) { michael@0: const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask michael@0: | SkMatrix::kPerspective_Mask); michael@0: return (matrix.getType() & m) == 0; michael@0: } michael@0: michael@0: bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const { michael@0: if (NULL == dst) { michael@0: return false; michael@0: } michael@0: michael@0: // Assert that the caller is not trying to do this in place, which michael@0: // would violate const-ness. Do not return false though, so that michael@0: // if they know what they're doing and want to violate it they can. michael@0: SkASSERT(dst != this); michael@0: michael@0: if (matrix.isIdentity()) { michael@0: *dst = *this; michael@0: return true; michael@0: } michael@0: michael@0: // If transform supported 90 degree rotations (which it could), we could michael@0: // use SkMatrix::rectStaysRect() to check for a valid transformation. michael@0: if (!matrix_only_scale_and_translate(matrix)) { michael@0: return false; michael@0: } michael@0: michael@0: SkRect newRect; michael@0: if (!matrix.mapRect(&newRect, fRect)) { michael@0: return false; michael@0: } michael@0: michael@0: // At this point, this is guaranteed to succeed, so we can modify dst. michael@0: dst->fRect = newRect; michael@0: michael@0: // Now scale each corner michael@0: SkScalar xScale = matrix.getScaleX(); michael@0: const bool flipX = xScale < 0; michael@0: if (flipX) { michael@0: xScale = -xScale; michael@0: } michael@0: SkScalar yScale = matrix.getScaleY(); michael@0: const bool flipY = yScale < 0; michael@0: if (flipY) { michael@0: yScale = -yScale; michael@0: } michael@0: michael@0: // Scale the radii without respecting the flip. michael@0: for (int i = 0; i < 4; ++i) { michael@0: dst->fRadii[i].fX = SkScalarMul(fRadii[i].fX, xScale); michael@0: dst->fRadii[i].fY = SkScalarMul(fRadii[i].fY, yScale); michael@0: } michael@0: michael@0: // Now swap as necessary. michael@0: if (flipX) { michael@0: if (flipY) { michael@0: // Swap with opposite corners michael@0: SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]); michael@0: SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]); michael@0: } else { michael@0: // Only swap in x michael@0: SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]); michael@0: SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]); michael@0: } michael@0: } else if (flipY) { michael@0: // Only swap in y michael@0: SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]); michael@0: SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]); michael@0: } michael@0: michael@0: // Since the only transforms that were allowed are scale and translate, the type michael@0: // remains unchanged. michael@0: dst->fType = fType; michael@0: michael@0: SkDEBUGCODE(dst->validate();) michael@0: michael@0: return true; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const { michael@0: SkRect r = fRect; michael@0: michael@0: r.inset(dx, dy); michael@0: if (r.isEmpty()) { michael@0: dst->setEmpty(); michael@0: return; michael@0: } michael@0: michael@0: SkVector radii[4]; michael@0: memcpy(radii, fRadii, sizeof(radii)); michael@0: for (int i = 0; i < 4; ++i) { michael@0: if (radii[i].fX) { michael@0: radii[i].fX -= dx; michael@0: } michael@0: if (radii[i].fY) { michael@0: radii[i].fY -= dy; michael@0: } michael@0: } michael@0: dst->setRectRadii(r, radii); michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: size_t SkRRect::writeToMemory(void* buffer) const { michael@0: SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii)); michael@0: michael@0: memcpy(buffer, &fRect, sizeof(SkRect)); michael@0: memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii)); michael@0: return kSizeInMemory; michael@0: } michael@0: michael@0: size_t SkRRect::readFromMemory(const void* buffer, size_t length) { michael@0: if (length < kSizeInMemory) { michael@0: return 0; michael@0: } michael@0: michael@0: SkScalar storage[12]; michael@0: SkASSERT(sizeof(storage) == kSizeInMemory); michael@0: michael@0: // we make a local copy, to ensure alignment before we cast michael@0: memcpy(storage, buffer, kSizeInMemory); michael@0: michael@0: this->setRectRadii(*(const SkRect*)&storage[0], michael@0: (const SkVector*)&storage[4]); michael@0: return kSizeInMemory; michael@0: } michael@0: michael@0: /////////////////////////////////////////////////////////////////////////////// michael@0: michael@0: #ifdef SK_DEBUG michael@0: void SkRRect::validate() const { michael@0: bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY); michael@0: bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY); michael@0: bool allRadiiSame = true; michael@0: michael@0: for (int i = 1; i < 4; ++i) { michael@0: if (0 != fRadii[i].fX || 0 != fRadii[i].fY) { michael@0: allRadiiZero = false; michael@0: } michael@0: michael@0: if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { michael@0: allRadiiSame = false; michael@0: } michael@0: michael@0: if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { michael@0: allCornersSquare = false; michael@0: } michael@0: } michael@0: michael@0: switch (fType) { michael@0: case kEmpty_Type: michael@0: SkASSERT(fRect.isEmpty()); michael@0: SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare); michael@0: michael@0: SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop && michael@0: 0 == fRect.fRight && 0 == fRect.fBottom); michael@0: break; michael@0: case kRect_Type: michael@0: SkASSERT(!fRect.isEmpty()); michael@0: SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare); michael@0: break; michael@0: case kOval_Type: michael@0: SkASSERT(!fRect.isEmpty()); michael@0: SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare); michael@0: michael@0: for (int i = 0; i < 4; ++i) { michael@0: SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width()))); michael@0: SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height()))); michael@0: } michael@0: break; michael@0: case kSimple_Type: michael@0: SkASSERT(!fRect.isEmpty()); michael@0: SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare); michael@0: break; michael@0: case kComplex_Type: michael@0: SkASSERT(!fRect.isEmpty()); michael@0: SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare); michael@0: break; michael@0: case kUnknown_Type: michael@0: // no limits on this michael@0: break; michael@0: } michael@0: } michael@0: #endif // SK_DEBUG michael@0: michael@0: ///////////////////////////////////////////////////////////////////////////////