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