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