SkRRect.cpp revision eb221268ab1067af7c48e04a75147d4bcca87191
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 10/////////////////////////////////////////////////////////////////////////////// 11 12void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) { 13 if (rect.isEmpty()) { 14 this->setEmpty(); 15 return; 16 } 17 18 if (xRad <= 0 || yRad <= 0) { 19 // all corners are square in this case 20 this->setRect(rect); 21 return; 22 } 23 24 if (rect.width() < xRad+xRad || rect.height() < yRad+yRad) { 25 SkScalar scale = SkMinScalar(SkScalarDiv(rect.width(), xRad + xRad), 26 SkScalarDiv(rect.height(), yRad + yRad)); 27 SkASSERT(scale < SK_Scalar1); 28 xRad = SkScalarMul(xRad, scale); 29 yRad = SkScalarMul(yRad, scale); 30 } 31 32 fRect = rect; 33 for (int i = 0; i < 4; ++i) { 34 fRadii[i].set(xRad, yRad); 35 } 36 fType = kSimple_Type; 37 if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) { 38 fType = kOval_Type; 39 // TODO: assert that all the x&y radii are already W/2 & H/2 40 } 41 42 SkDEBUGCODE(this->validate();) 43} 44 45void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) { 46 if (rect.isEmpty()) { 47 this->setEmpty(); 48 return; 49 } 50 51 fRect = rect; 52 memcpy(fRadii, radii, sizeof(fRadii)); 53 54 bool allCornersSquare = true; 55 56 // Clamp negative radii to zero 57 for (int i = 0; i < 4; ++i) { 58 if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) { 59 // In this case we are being a little fast & loose. Since one of 60 // the radii is 0 the corner is square. However, the other radii 61 // could still be non-zero and play in the global scale factor 62 // computation. 63 fRadii[i].fX = 0; 64 fRadii[i].fY = 0; 65 } else { 66 allCornersSquare = false; 67 } 68 } 69 70 if (allCornersSquare) { 71 this->setRect(rect); 72 return; 73 } 74 75 // Proportionally scale down all radii to fit. Find the minimum ratio 76 // of a side and the radii on that side (for all four sides) and use 77 // that to scale down _all_ the radii. This algorithm is from the 78 // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping 79 // Curves: 80 // "Let f = min(Li/Si), where i is one of { top, right, bottom, left }, 81 // Si is the sum of the two corresponding radii of the corners on side i, 82 // and Ltop = Lbottom = the width of the box, 83 // and Lleft = Lright = the height of the box. 84 // If f < 1, then all corner radii are reduced by multiplying them by f." 85 SkScalar scale = SK_Scalar1; 86 87 if (fRadii[0].fX + fRadii[1].fX > rect.width()) { 88 scale = SkMinScalar(scale, 89 SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX)); 90 } 91 if (fRadii[1].fY + fRadii[2].fY > rect.height()) { 92 scale = SkMinScalar(scale, 93 SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY)); 94 } 95 if (fRadii[2].fX + fRadii[3].fX > rect.width()) { 96 scale = SkMinScalar(scale, 97 SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX)); 98 } 99 if (fRadii[3].fY + fRadii[0].fY > rect.height()) { 100 scale = SkMinScalar(scale, 101 SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY)); 102 } 103 104 if (scale < SK_Scalar1) { 105 for (int i = 0; i < 4; ++i) { 106 fRadii[i].fX = SkScalarMul(fRadii[i].fX, scale); 107 fRadii[i].fY = SkScalarMul(fRadii[i].fY, scale); 108 } 109 } 110 111 // At this point we're either oval, simple, or complex (not empty or rect) 112 // but we lazily resolve the type to avoid the work if the information 113 // isn't required. 114 fType = (SkRRect::Type) kUnknown_Type; 115 116 SkDEBUGCODE(this->validate();) 117} 118 119// This method determines if a point known to be inside the RRect's bounds is 120// inside all the corners. 121bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const { 122 SkPoint canonicalPt; // (x,y) translated to one of the quadrants 123 int index; 124 125 if (kOval_Type == this->type()) { 126 canonicalPt.set(x - fRect.centerX(), y - fRect.centerY()); 127 index = kUpperLeft_Corner; // any corner will do in this case 128 } else { 129 if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX && 130 y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) { 131 // UL corner 132 index = kUpperLeft_Corner; 133 canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX), 134 y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY)); 135 SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0); 136 } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX && 137 y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) { 138 // LL corner 139 index = kLowerLeft_Corner; 140 canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX), 141 y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY)); 142 SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0); 143 } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX && 144 y < fRect.fTop + fRadii[kUpperRight_Corner].fY) { 145 // UR corner 146 index = kUpperRight_Corner; 147 canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX), 148 y - (fRect.fTop + fRadii[kUpperRight_Corner].fY)); 149 SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0); 150 } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX && 151 y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) { 152 // LR corner 153 index = kLowerRight_Corner; 154 canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX), 155 y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY)); 156 SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0); 157 } else { 158 // not in any of the corners 159 return true; 160 } 161 } 162 163 // A point is in an ellipse (in standard position) if: 164 // x^2 y^2 165 // ----- + ----- <= 1 166 // a^2 b^2 167 // or : 168 // b^2*x^2 + a^2*y^2 <= (ab)^2 169 SkScalar dist = SkScalarMul(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fY)) + 170 SkScalarMul(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fX)); 171 return dist <= SkScalarSquare(SkScalarMul(fRadii[index].fX, fRadii[index].fY)); 172} 173 174bool SkRRect::contains(const SkRect& rect) const { 175 if (!this->getBounds().contains(rect)) { 176 // If 'rect' isn't contained by the RR's bounds then the 177 // RR definitely doesn't contain it 178 return false; 179 } 180 181 if (this->isRect()) { 182 // the prior test was sufficient 183 return true; 184 } 185 186 // At this point we know all four corners of 'rect' are inside the 187 // bounds of of this RR. Check to make sure all the corners are inside 188 // all the curves 189 return this->checkCornerContainment(rect.fLeft, rect.fTop) && 190 this->checkCornerContainment(rect.fRight, rect.fTop) && 191 this->checkCornerContainment(rect.fRight, rect.fBottom) && 192 this->checkCornerContainment(rect.fLeft, rect.fBottom); 193} 194 195// There is a simplified version of this method in setRectXY 196void SkRRect::computeType() const { 197 SkDEBUGCODE(this->validate();) 198 199 if (fRect.isEmpty()) { 200 fType = kEmpty_Type; 201 return; 202 } 203 204 bool allRadiiEqual = true; // are all x radii equal and all y radii? 205 bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY; 206 207 for (int i = 1; i < 4; ++i) { 208 if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { 209 // if either radius is zero the corner is square so both have to 210 // be non-zero to have a rounded corner 211 allCornersSquare = false; 212 } 213 if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { 214 allRadiiEqual = false; 215 } 216 } 217 218 if (allCornersSquare) { 219 fType = kRect_Type; 220 return; 221 } 222 223 if (allRadiiEqual) { 224 if (fRadii[0].fX >= SkScalarHalf(fRect.width()) && 225 fRadii[0].fY >= SkScalarHalf(fRect.height())) { 226 fType = kOval_Type; 227 } else { 228 fType = kSimple_Type; 229 } 230 return; 231 } 232 233 fType = kComplex_Type; 234} 235 236/////////////////////////////////////////////////////////////////////////////// 237 238void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const { 239 SkRect r = fRect; 240 241 r.inset(dx, dy); 242 if (r.isEmpty()) { 243 dst->setEmpty(); 244 return; 245 } 246 247 SkVector radii[4]; 248 memcpy(radii, fRadii, sizeof(radii)); 249 for (int i = 0; i < 4; ++i) { 250 if (radii[i].fX) { 251 radii[i].fX -= dx; 252 } 253 if (radii[i].fY) { 254 radii[i].fY -= dy; 255 } 256 } 257 dst->setRectRadii(r, radii); 258} 259 260/////////////////////////////////////////////////////////////////////////////// 261 262uint32_t SkRRect::writeToMemory(void* buffer) const { 263 SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii)); 264 265 memcpy(buffer, &fRect, sizeof(SkRect)); 266 memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii)); 267 return kSizeInMemory; 268} 269 270uint32_t SkRRect::readFromMemory(const void* buffer) { 271 SkScalar storage[12]; 272 SkASSERT(sizeof(storage) == kSizeInMemory); 273 274 // we make a local copy, to ensure alignment before we cast 275 memcpy(storage, buffer, kSizeInMemory); 276 277 this->setRectRadii(*(const SkRect*)&storage[0], 278 (const SkVector*)&storage[4]); 279 return kSizeInMemory; 280} 281 282/////////////////////////////////////////////////////////////////////////////// 283 284#ifdef SK_DEBUG 285void SkRRect::validate() const { 286 bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY); 287 bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY); 288 bool allRadiiSame = true; 289 290 for (int i = 1; i < 4; ++i) { 291 if (0 != fRadii[i].fX || 0 != fRadii[i].fY) { 292 allRadiiZero = false; 293 } 294 295 if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { 296 allRadiiSame = false; 297 } 298 299 if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { 300 allCornersSquare = false; 301 } 302 } 303 304 switch (fType) { 305 case kEmpty_Type: 306 SkASSERT(fRect.isEmpty()); 307 SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare); 308 309 SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop && 310 0 == fRect.fRight && 0 == fRect.fBottom); 311 break; 312 case kRect_Type: 313 SkASSERT(!fRect.isEmpty()); 314 SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare); 315 break; 316 case kOval_Type: 317 SkASSERT(!fRect.isEmpty()); 318 SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare); 319 320 for (int i = 0; i < 4; ++i) { 321 SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width()))); 322 SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height()))); 323 } 324 break; 325 case kSimple_Type: 326 SkASSERT(!fRect.isEmpty()); 327 SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare); 328 break; 329 case kComplex_Type: 330 SkASSERT(!fRect.isEmpty()); 331 SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare); 332 break; 333 case kUnknown_Type: 334 // no limits on this 335 break; 336 } 337} 338#endif // SK_DEBUG 339 340/////////////////////////////////////////////////////////////////////////////// 341