blob: 81184b15176d2dde5cd8a0346784dad0961608af [file] [log] [blame]
robertphillips@google.com5985e7c2012-11-29 13:24:55 +00001/*
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
herb7cf12dd2016-01-11 08:08:56 -08008#include <cmath>
robertphillips@google.com5985e7c2012-11-29 13:24:55 +00009#include "SkRRect.h"
Brian Salomon1e3b79e2017-09-21 12:29:24 -040010#include "SkBuffer.h"
scroggo@google.com20e3cd22013-11-05 15:54:42 +000011#include "SkMatrix.h"
herb7cf12dd2016-01-11 08:08:56 -080012#include "SkScaleToSides.h"
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000013
14///////////////////////////////////////////////////////////////////////////////
15
16void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
robertphillips05302f82015-09-29 11:24:07 -070017 fRect = rect;
18 fRect.sort();
19
20 if (fRect.isEmpty() || !fRect.isFinite()) {
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000021 this->setEmpty();
22 return;
23 }
24
reed454fa712015-02-10 08:46:22 -080025 if (!SkScalarsAreFinite(xRad, yRad)) {
26 xRad = yRad = 0; // devolve into a simple rect
27 }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000028 if (xRad <= 0 || yRad <= 0) {
29 // all corners are square in this case
30 this->setRect(rect);
31 return;
32 }
33
robertphillips05302f82015-09-29 11:24:07 -070034 if (fRect.width() < xRad+xRad || fRect.height() < yRad+yRad) {
35 SkScalar scale = SkMinScalar(fRect.width() / (xRad + xRad), fRect.height() / (yRad + yRad));
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000036 SkASSERT(scale < SK_Scalar1);
Mike Reeda99b6ce2017-02-04 11:04:26 -050037 xRad *= scale;
38 yRad *= scale;
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000039 }
40
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000041 for (int i = 0; i < 4; ++i) {
42 fRadii[i].set(xRad, yRad);
43 }
44 fType = kSimple_Type;
45 if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
46 fType = kOval_Type;
robertphillips@google.com5b332112013-01-30 20:33:12 +000047 // TODO: assert that all the x&y radii are already W/2 & H/2
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000048 }
49
Robert Phillips49da3342016-09-23 14:23:22 -040050 SkASSERT(this->isValid());
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000051}
52
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +000053void SkRRect::setNinePatch(const SkRect& rect, SkScalar leftRad, SkScalar topRad,
54 SkScalar rightRad, SkScalar bottomRad) {
robertphillips05302f82015-09-29 11:24:07 -070055 fRect = rect;
56 fRect.sort();
57
58 if (fRect.isEmpty() || !fRect.isFinite()) {
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +000059 this->setEmpty();
60 return;
61 }
62
reed454fa712015-02-10 08:46:22 -080063 const SkScalar array[4] = { leftRad, topRad, rightRad, bottomRad };
64 if (!SkScalarsAreFinite(array, 4)) {
65 this->setRect(rect); // devolve into a simple rect
66 return;
67 }
68
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +000069 leftRad = SkMaxScalar(leftRad, 0);
70 topRad = SkMaxScalar(topRad, 0);
71 rightRad = SkMaxScalar(rightRad, 0);
72 bottomRad = SkMaxScalar(bottomRad, 0);
73
74 SkScalar scale = SK_Scalar1;
robertphillips05302f82015-09-29 11:24:07 -070075 if (leftRad + rightRad > fRect.width()) {
76 scale = fRect.width() / (leftRad + rightRad);
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +000077 }
robertphillips05302f82015-09-29 11:24:07 -070078 if (topRad + bottomRad > fRect.height()) {
79 scale = SkMinScalar(scale, fRect.height() / (topRad + bottomRad));
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +000080 }
81
82 if (scale < SK_Scalar1) {
Mike Reeda99b6ce2017-02-04 11:04:26 -050083 leftRad *= scale;
84 topRad *= scale;
85 rightRad *= scale;
86 bottomRad *= scale;
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +000087 }
88
89 if (leftRad == rightRad && topRad == bottomRad) {
robertphillips05302f82015-09-29 11:24:07 -070090 if (leftRad >= SkScalarHalf(fRect.width()) && topRad >= SkScalarHalf(fRect.height())) {
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +000091 fType = kOval_Type;
92 } else if (0 == leftRad || 0 == topRad) {
93 // If the left and (by equality check above) right radii are zero then it is a rect.
94 // Same goes for top/bottom.
95 fType = kRect_Type;
96 leftRad = 0;
97 topRad = 0;
98 rightRad = 0;
99 bottomRad = 0;
100 } else {
101 fType = kSimple_Type;
102 }
103 } else {
104 fType = kNinePatch_Type;
105 }
106
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000107 fRadii[kUpperLeft_Corner].set(leftRad, topRad);
108 fRadii[kUpperRight_Corner].set(rightRad, topRad);
109 fRadii[kLowerRight_Corner].set(rightRad, bottomRad);
110 fRadii[kLowerLeft_Corner].set(leftRad, bottomRad);
111
Robert Phillips49da3342016-09-23 14:23:22 -0400112 SkASSERT(this->isValid());
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000113}
114
robertphillips2a679ae2015-03-13 09:53:01 -0700115// These parameters intentionally double. Apropos crbug.com/463920, if one of the
116// radii is huge while the other is small, single precision math can completely
117// miss the fact that a scale is required.
118static double compute_min_scale(double rad1, double rad2, double limit, double curMin) {
119 if ((rad1 + rad2) > limit) {
120 return SkTMin(curMin, limit / (rad1 + rad2));
121 }
122 return curMin;
123}
124
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000125void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
robertphillips05302f82015-09-29 11:24:07 -0700126 fRect = rect;
127 fRect.sort();
128
129 if (fRect.isEmpty() || !fRect.isFinite()) {
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000130 this->setEmpty();
131 return;
132 }
133
reed454fa712015-02-10 08:46:22 -0800134 if (!SkScalarsAreFinite(&radii[0].fX, 8)) {
135 this->setRect(rect); // devolve into a simple rect
136 return;
137 }
138
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000139 memcpy(fRadii, radii, sizeof(fRadii));
140
141 bool allCornersSquare = true;
142
143 // Clamp negative radii to zero
144 for (int i = 0; i < 4; ++i) {
145 if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
146 // In this case we are being a little fast & loose. Since one of
147 // the radii is 0 the corner is square. However, the other radii
148 // could still be non-zero and play in the global scale factor
149 // computation.
150 fRadii[i].fX = 0;
151 fRadii[i].fY = 0;
152 } else {
153 allCornersSquare = false;
154 }
155 }
156
157 if (allCornersSquare) {
158 this->setRect(rect);
159 return;
160 }
161
caryclark5a70bc72016-02-23 10:32:40 -0800162 this->scaleRadii();
163}
164
165void SkRRect::scaleRadii() {
166
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000167 // Proportionally scale down all radii to fit. Find the minimum ratio
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000168 // of a side and the radii on that side (for all four sides) and use
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000169 // that to scale down _all_ the radii. This algorithm is from the
170 // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
171 // Curves:
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000172 // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000173 // Si is the sum of the two corresponding radii of the corners on side i,
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000174 // and Ltop = Lbottom = the width of the box,
175 // and Lleft = Lright = the height of the box.
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000176 // If f < 1, then all corner radii are reduced by multiplying them by f."
robertphillips2a679ae2015-03-13 09:53:01 -0700177 double scale = 1.0;
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000178
herb7cf12dd2016-01-11 08:08:56 -0800179 // The sides of the rectangle may be larger than a float.
180 double width = (double)fRect.fRight - (double)fRect.fLeft;
181 double height = (double)fRect.fBottom - (double)fRect.fTop;
182 scale = compute_min_scale(fRadii[0].fX, fRadii[1].fX, width, scale);
183 scale = compute_min_scale(fRadii[1].fY, fRadii[2].fY, height, scale);
184 scale = compute_min_scale(fRadii[2].fX, fRadii[3].fX, width, scale);
185 scale = compute_min_scale(fRadii[3].fY, fRadii[0].fY, height, scale);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000186
robertphillips2a679ae2015-03-13 09:53:01 -0700187 if (scale < 1.0) {
herb97293c62016-01-22 11:58:55 -0800188 SkScaleToSides::AdjustRadii(width, scale, &fRadii[0].fX, &fRadii[1].fX);
189 SkScaleToSides::AdjustRadii(height, scale, &fRadii[1].fY, &fRadii[2].fY);
190 SkScaleToSides::AdjustRadii(width, scale, &fRadii[2].fX, &fRadii[3].fX);
191 SkScaleToSides::AdjustRadii(height, scale, &fRadii[3].fY, &fRadii[0].fY);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000192 }
193
mtklein8aacf202014-12-18 13:29:54 -0800194 // At this point we're either oval, simple, or complex (not empty or rect).
195 this->computeType();
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000196
Robert Phillips49da3342016-09-23 14:23:22 -0400197 SkASSERT(this->isValid());
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000198}
199
skia.committer@gmail.com2cf444f2013-04-26 07:00:58 +0000200// This method determines if a point known to be inside the RRect's bounds is
robertphillips@google.com32c1b662013-04-25 12:23:00 +0000201// inside all the corners.
202bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000203 SkPoint canonicalPt; // (x,y) translated to one of the quadrants
204 int index;
205
206 if (kOval_Type == this->type()) {
207 canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
208 index = kUpperLeft_Corner; // any corner will do in this case
209 } else {
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000210 if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000211 y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
212 // UL corner
213 index = kUpperLeft_Corner;
214 canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
215 y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
216 SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
217 } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
218 y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
219 // LL corner
220 index = kLowerLeft_Corner;
221 canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
222 y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
223 SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
224 } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
225 y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
226 // UR corner
227 index = kUpperRight_Corner;
228 canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
229 y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
230 SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
231 } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
232 y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
233 // LR corner
234 index = kLowerRight_Corner;
235 canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
236 y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
237 SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
238 } else {
239 // not in any of the corners
240 return true;
241 }
242 }
243
244 // A point is in an ellipse (in standard position) if:
245 // x^2 y^2
246 // ----- + ----- <= 1
247 // a^2 b^2
robertphillips@google.com32c1b662013-04-25 12:23:00 +0000248 // or :
249 // b^2*x^2 + a^2*y^2 <= (ab)^2
Mike Reeda99b6ce2017-02-04 11:04:26 -0500250 SkScalar dist = SkScalarSquare(canonicalPt.fX) * SkScalarSquare(fRadii[index].fY) +
251 SkScalarSquare(canonicalPt.fY) * SkScalarSquare(fRadii[index].fX);
252 return dist <= SkScalarSquare(fRadii[index].fX * fRadii[index].fY);
robertphillips@google.com32c1b662013-04-25 12:23:00 +0000253}
254
Jim Van Verth8f7dc9f2017-04-20 15:48:37 -0400255bool SkRRect::allCornersCircular(SkScalar tolerance) const {
256 return SkScalarNearlyEqual(fRadii[0].fX, fRadii[0].fY, tolerance) &&
257 SkScalarNearlyEqual(fRadii[1].fX, fRadii[1].fY, tolerance) &&
258 SkScalarNearlyEqual(fRadii[2].fX, fRadii[2].fY, tolerance) &&
259 SkScalarNearlyEqual(fRadii[3].fX, fRadii[3].fY, tolerance);
commit-bot@chromium.org82139702014-03-10 22:53:20 +0000260}
261
robertphillips@google.com32c1b662013-04-25 12:23:00 +0000262bool SkRRect::contains(const SkRect& rect) const {
263 if (!this->getBounds().contains(rect)) {
264 // If 'rect' isn't contained by the RR's bounds then the
265 // RR definitely doesn't contain it
266 return false;
267 }
268
269 if (this->isRect()) {
270 // the prior test was sufficient
271 return true;
272 }
273
274 // At this point we know all four corners of 'rect' are inside the
275 // bounds of of this RR. Check to make sure all the corners are inside
276 // all the curves
277 return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
278 this->checkCornerContainment(rect.fRight, rect.fTop) &&
279 this->checkCornerContainment(rect.fRight, rect.fBottom) &&
280 this->checkCornerContainment(rect.fLeft, rect.fBottom);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000281}
282
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000283static bool radii_are_nine_patch(const SkVector radii[4]) {
284 return radii[SkRRect::kUpperLeft_Corner].fX == radii[SkRRect::kLowerLeft_Corner].fX &&
285 radii[SkRRect::kUpperLeft_Corner].fY == radii[SkRRect::kUpperRight_Corner].fY &&
286 radii[SkRRect::kUpperRight_Corner].fX == radii[SkRRect::kLowerRight_Corner].fX &&
287 radii[SkRRect::kLowerLeft_Corner].fY == radii[SkRRect::kLowerRight_Corner].fY;
288}
289
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000290// There is a simplified version of this method in setRectXY
mtklein8aacf202014-12-18 13:29:54 -0800291void SkRRect::computeType() {
292 struct Validator {
293 Validator(const SkRRect* r) : fR(r) {}
Robert Phillips49da3342016-09-23 14:23:22 -0400294 ~Validator() { SkASSERT(fR->isValid()); }
mtklein8aacf202014-12-18 13:29:54 -0800295 const SkRRect* fR;
296 } autoValidate(this);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000297
298 if (fRect.isEmpty()) {
299 fType = kEmpty_Type;
300 return;
301 }
302
303 bool allRadiiEqual = true; // are all x radii equal and all y radii?
304 bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
305
306 for (int i = 1; i < 4; ++i) {
307 if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
308 // if either radius is zero the corner is square so both have to
309 // be non-zero to have a rounded corner
310 allCornersSquare = false;
311 }
312 if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
313 allRadiiEqual = false;
314 }
315 }
316
317 if (allCornersSquare) {
318 fType = kRect_Type;
319 return;
320 }
321
322 if (allRadiiEqual) {
323 if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
324 fRadii[0].fY >= SkScalarHalf(fRect.height())) {
325 fType = kOval_Type;
326 } else {
327 fType = kSimple_Type;
328 }
329 return;
330 }
331
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000332 if (radii_are_nine_patch(fRadii)) {
333 fType = kNinePatch_Type;
334 } else {
335 fType = kComplex_Type;
336 }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000337}
338
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000339static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
340 const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
341 | SkMatrix::kPerspective_Mask);
342 return (matrix.getType() & m) == 0;
343}
344
345bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
halcanary96fcdcc2015-08-27 07:41:13 -0700346 if (nullptr == dst) {
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000347 return false;
348 }
349
350 // Assert that the caller is not trying to do this in place, which
351 // would violate const-ness. Do not return false though, so that
352 // if they know what they're doing and want to violate it they can.
353 SkASSERT(dst != this);
354
355 if (matrix.isIdentity()) {
356 *dst = *this;
357 return true;
358 }
359
360 // If transform supported 90 degree rotations (which it could), we could
361 // use SkMatrix::rectStaysRect() to check for a valid transformation.
362 if (!matrix_only_scale_and_translate(matrix)) {
363 return false;
364 }
365
366 SkRect newRect;
367 if (!matrix.mapRect(&newRect, fRect)) {
368 return false;
369 }
370
reed694b0d12015-02-13 14:33:02 -0800371 // The matrix may have scaled us to zero (or due to float madness, we now have collapsed
372 // some dimension of the rect, so we need to check for that.
373 if (newRect.isEmpty()) {
374 dst->setEmpty();
375 return true;
376 }
377
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000378 // At this point, this is guaranteed to succeed, so we can modify dst.
379 dst->fRect = newRect;
380
robertphillipse5c1e3c2014-06-27 08:59:26 -0700381 // Since the only transforms that were allowed are scale and translate, the type
382 // remains unchanged.
383 dst->fType = fType;
384
385 if (kOval_Type == fType) {
386 for (int i = 0; i < 4; ++i) {
387 dst->fRadii[i].fX = SkScalarHalf(newRect.width());
388 dst->fRadii[i].fY = SkScalarHalf(newRect.height());
389 }
Robert Phillips49da3342016-09-23 14:23:22 -0400390 SkASSERT(dst->isValid());
robertphillipse5c1e3c2014-06-27 08:59:26 -0700391 return true;
392 }
393
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000394 // Now scale each corner
395 SkScalar xScale = matrix.getScaleX();
396 const bool flipX = xScale < 0;
397 if (flipX) {
398 xScale = -xScale;
399 }
400 SkScalar yScale = matrix.getScaleY();
401 const bool flipY = yScale < 0;
402 if (flipY) {
403 yScale = -yScale;
404 }
405
406 // Scale the radii without respecting the flip.
407 for (int i = 0; i < 4; ++i) {
Mike Reeda99b6ce2017-02-04 11:04:26 -0500408 dst->fRadii[i].fX = fRadii[i].fX * xScale;
409 dst->fRadii[i].fY = fRadii[i].fY * yScale;
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000410 }
411
412 // Now swap as necessary.
413 if (flipX) {
414 if (flipY) {
415 // Swap with opposite corners
416 SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
417 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
418 } else {
419 // Only swap in x
420 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
421 SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
422 }
423 } else if (flipY) {
424 // Only swap in y
425 SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
426 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
427 }
428
caryclark5a70bc72016-02-23 10:32:40 -0800429 dst->scaleRadii();
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000430
431 return true;
432}
433
reed@google.com4ed0fb72012-12-12 20:48:18 +0000434///////////////////////////////////////////////////////////////////////////////
mike@reedtribe.org37071642012-12-17 02:10:42 +0000435
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000436void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
reed11fa2242015-03-13 06:08:28 -0700437 const SkRect r = fRect.makeInset(dx, dy);
skia.committer@gmail.com1a60dab2012-12-24 02:01:25 +0000438
mike@reedtribe.org37071642012-12-17 02:10:42 +0000439 if (r.isEmpty()) {
440 dst->setEmpty();
441 return;
442 }
443
444 SkVector radii[4];
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000445 memcpy(radii, fRadii, sizeof(radii));
mike@reedtribe.org37071642012-12-17 02:10:42 +0000446 for (int i = 0; i < 4; ++i) {
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000447 if (radii[i].fX) {
448 radii[i].fX -= dx;
449 }
450 if (radii[i].fY) {
451 radii[i].fY -= dy;
452 }
mike@reedtribe.org37071642012-12-17 02:10:42 +0000453 }
454 dst->setRectRadii(r, radii);
455}
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000456
mike@reedtribe.org37071642012-12-17 02:10:42 +0000457///////////////////////////////////////////////////////////////////////////////
458
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +0000459size_t SkRRect::writeToMemory(void* buffer) const {
Mike Kleinca878cc2017-04-06 22:34:59 -0400460 // Serialize only the rect and corners, but not the derived type tag.
461 memcpy(buffer, this, kSizeInMemory);
reed@google.com4ed0fb72012-12-12 20:48:18 +0000462 return kSizeInMemory;
463}
464
Brian Salomon1e3b79e2017-09-21 12:29:24 -0400465void SkRRect::writeToBuffer(SkWBuffer* buffer) const {
466 // Serialize only the rect and corners, but not the derived type tag.
467 buffer->write(this, kSizeInMemory);
468}
469
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +0000470size_t SkRRect::readFromMemory(const void* buffer, size_t length) {
471 if (length < kSizeInMemory) {
472 return 0;
473 }
Brian Salomonfb6a7892017-09-20 11:05:49 -0400474 // Note that the buffer may be smaller than SkRRect. It is important not to access
475 // bufferAsRRect->fType.
476 const SkRRect* bufferAsRRect = reinterpret_cast<const SkRRect*>(buffer);
477 if (!AreRectAndRadiiValid(bufferAsRRect->fRect, bufferAsRRect->fRadii)) {
478 return 0;
479 }
Mike Kleinca878cc2017-04-06 22:34:59 -0400480 // Deserialize rect and corners, then rederive the type tag.
481 memcpy(this, buffer, kSizeInMemory);
482 this->computeType();
reed@google.com4ed0fb72012-12-12 20:48:18 +0000483
reed@google.com4ed0fb72012-12-12 20:48:18 +0000484 return kSizeInMemory;
485}
486
Brian Salomon1e3b79e2017-09-21 12:29:24 -0400487bool SkRRect::readFromBuffer(SkRBuffer* buffer) {
488 if (buffer->available() < kSizeInMemory) {
489 return false;
490 }
491 SkRRect readData;
492 buffer->read(&readData, kSizeInMemory);
493 if (!AreRectAndRadiiValid(readData.fRect, readData.fRadii)) {
494 return false;
495 }
496 memcpy(this, &readData, kSizeInMemory);
497 this->computeType();
498 return true;
499}
500
reede05fed02014-12-15 07:59:53 -0800501#include "SkString.h"
502#include "SkStringUtils.h"
503
504void SkRRect::dump(bool asHex) const {
505 SkScalarAsStringType asType = asHex ? kHex_SkScalarAsStringType : kDec_SkScalarAsStringType;
506
507 fRect.dump(asHex);
508 SkString line("const SkPoint corners[] = {\n");
509 for (int i = 0; i < 4; ++i) {
510 SkString strX, strY;
511 SkAppendScalar(&strX, fRadii[i].x(), asType);
512 SkAppendScalar(&strY, fRadii[i].y(), asType);
513 line.appendf(" { %s, %s },", strX.c_str(), strY.c_str());
514 if (asHex) {
515 line.appendf(" /* %f %f */", fRadii[i].x(), fRadii[i].y());
516 }
517 line.append("\n");
518 }
519 line.append("};");
520 SkDebugf("%s\n", line.c_str());
bsalomonb6b02522014-06-09 07:59:06 -0700521}
bsalomonb6b02522014-06-09 07:59:06 -0700522
reed@google.com4ed0fb72012-12-12 20:48:18 +0000523///////////////////////////////////////////////////////////////////////////////
524
reed694b0d12015-02-13 14:33:02 -0800525/**
526 * We need all combinations of predicates to be true to have a "safe" radius value.
527 */
Robert Phillips49da3342016-09-23 14:23:22 -0400528static bool are_radius_check_predicates_valid(SkScalar rad, SkScalar min, SkScalar max) {
529 return (min <= max) && (rad <= max - min) && (min + rad <= max) && (max - rad >= min);
reed694b0d12015-02-13 14:33:02 -0800530}
531
Robert Phillips49da3342016-09-23 14:23:22 -0400532bool SkRRect::isValid() const {
Brian Salomonfb6a7892017-09-20 11:05:49 -0400533 if (!AreRectAndRadiiValid(fRect, fRadii)) {
534 return false;
535 }
536
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000537 bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
538 bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
539 bool allRadiiSame = true;
540
541 for (int i = 1; i < 4; ++i) {
542 if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
543 allRadiiZero = false;
544 }
545
546 if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
547 allRadiiSame = false;
548 }
549
550 if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
551 allCornersSquare = false;
552 }
553 }
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000554 bool patchesOfNine = radii_are_nine_patch(fRadii);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000555
Adrienne Walkerba9741d2017-08-23 13:26:32 -0700556 if (fType > kLastType) {
557 return false;
558 }
559
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000560 switch (fType) {
561 case kEmpty_Type:
Robert Phillips49da3342016-09-23 14:23:22 -0400562 if (!fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) {
563 return false;
564 }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000565 break;
566 case kRect_Type:
Robert Phillips49da3342016-09-23 14:23:22 -0400567 if (fRect.isEmpty() || !allRadiiZero || !allRadiiSame || !allCornersSquare) {
568 return false;
569 }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000570 break;
571 case kOval_Type:
Robert Phillips49da3342016-09-23 14:23:22 -0400572 if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) {
573 return false;
574 }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000575
576 for (int i = 0; i < 4; ++i) {
Robert Phillips49da3342016-09-23 14:23:22 -0400577 if (!SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())) ||
578 !SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height()))) {
579 return false;
580 }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000581 }
582 break;
583 case kSimple_Type:
Robert Phillips49da3342016-09-23 14:23:22 -0400584 if (fRect.isEmpty() || allRadiiZero || !allRadiiSame || allCornersSquare) {
585 return false;
586 }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000587 break;
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000588 case kNinePatch_Type:
Robert Phillips49da3342016-09-23 14:23:22 -0400589 if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare ||
590 !patchesOfNine) {
591 return false;
592 }
commit-bot@chromium.orgf338d7c2014-03-17 21:17:30 +0000593 break;
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000594 case kComplex_Type:
Robert Phillips49da3342016-09-23 14:23:22 -0400595 if (fRect.isEmpty() || allRadiiZero || allRadiiSame || allCornersSquare ||
596 patchesOfNine) {
597 return false;
598 }
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000599 break;
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000600 }
reed694b0d12015-02-13 14:33:02 -0800601
Robert Phillips49da3342016-09-23 14:23:22 -0400602 return true;
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000603}
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000604
Brian Salomonfb6a7892017-09-20 11:05:49 -0400605bool SkRRect::AreRectAndRadiiValid(const SkRect& rect, const SkVector radii[4]) {
606 if (!rect.isFinite()) {
607 return false;
608 }
609 for (int i = 0; i < 4; ++i) {
610 if (!are_radius_check_predicates_valid(radii[i].fX, rect.fLeft, rect.fRight) ||
611 !are_radius_check_predicates_valid(radii[i].fY, rect.fTop, rect.fBottom)) {
612 return false;
613 }
614 }
615 return true;
616}
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000617///////////////////////////////////////////////////////////////////////////////