blob: e5296d4e3a7d83ac871c063ee112dbcf9cbbc507 [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
8#include "SkRRect.h"
scroggo@google.com20e3cd22013-11-05 15:54:42 +00009#include "SkMatrix.h"
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000010
11///////////////////////////////////////////////////////////////////////////////
12
13void SkRRect::setRectXY(const SkRect& rect, SkScalar xRad, SkScalar yRad) {
14 if (rect.isEmpty()) {
15 this->setEmpty();
16 return;
17 }
18
19 if (xRad <= 0 || yRad <= 0) {
20 // all corners are square in this case
21 this->setRect(rect);
22 return;
23 }
24
25 if (rect.width() < xRad+xRad || rect.height() < yRad+yRad) {
26 SkScalar scale = SkMinScalar(SkScalarDiv(rect.width(), xRad + xRad),
27 SkScalarDiv(rect.height(), yRad + yRad));
28 SkASSERT(scale < SK_Scalar1);
29 xRad = SkScalarMul(xRad, scale);
30 yRad = SkScalarMul(yRad, scale);
31 }
32
33 fRect = rect;
34 for (int i = 0; i < 4; ++i) {
35 fRadii[i].set(xRad, yRad);
36 }
37 fType = kSimple_Type;
38 if (xRad >= SkScalarHalf(fRect.width()) && yRad >= SkScalarHalf(fRect.height())) {
39 fType = kOval_Type;
robertphillips@google.com5b332112013-01-30 20:33:12 +000040 // TODO: assert that all the x&y radii are already W/2 & H/2
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000041 }
42
43 SkDEBUGCODE(this->validate();)
44}
45
46void SkRRect::setRectRadii(const SkRect& rect, const SkVector radii[4]) {
47 if (rect.isEmpty()) {
48 this->setEmpty();
49 return;
50 }
51
52 fRect = rect;
53 memcpy(fRadii, radii, sizeof(fRadii));
54
55 bool allCornersSquare = true;
56
57 // Clamp negative radii to zero
58 for (int i = 0; i < 4; ++i) {
59 if (fRadii[i].fX <= 0 || fRadii[i].fY <= 0) {
60 // In this case we are being a little fast & loose. Since one of
61 // the radii is 0 the corner is square. However, the other radii
62 // could still be non-zero and play in the global scale factor
63 // computation.
64 fRadii[i].fX = 0;
65 fRadii[i].fY = 0;
66 } else {
67 allCornersSquare = false;
68 }
69 }
70
71 if (allCornersSquare) {
72 this->setRect(rect);
73 return;
74 }
75
76 // Proportionally scale down all radii to fit. Find the minimum ratio
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000077 // of a side and the radii on that side (for all four sides) and use
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000078 // that to scale down _all_ the radii. This algorithm is from the
79 // W3 spec (http://www.w3.org/TR/css3-background/) section 5.5 - Overlapping
80 // Curves:
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000081 // "Let f = min(Li/Si), where i is one of { top, right, bottom, left },
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000082 // Si is the sum of the two corresponding radii of the corners on side i,
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +000083 // and Ltop = Lbottom = the width of the box,
84 // and Lleft = Lright = the height of the box.
robertphillips@google.com5985e7c2012-11-29 13:24:55 +000085 // If f < 1, then all corner radii are reduced by multiplying them by f."
86 SkScalar scale = SK_Scalar1;
87
88 if (fRadii[0].fX + fRadii[1].fX > rect.width()) {
89 scale = SkMinScalar(scale,
90 SkScalarDiv(rect.width(), fRadii[0].fX + fRadii[1].fX));
91 }
92 if (fRadii[1].fY + fRadii[2].fY > rect.height()) {
93 scale = SkMinScalar(scale,
94 SkScalarDiv(rect.height(), fRadii[1].fY + fRadii[2].fY));
95 }
96 if (fRadii[2].fX + fRadii[3].fX > rect.width()) {
97 scale = SkMinScalar(scale,
98 SkScalarDiv(rect.width(), fRadii[2].fX + fRadii[3].fX));
99 }
100 if (fRadii[3].fY + fRadii[0].fY > rect.height()) {
101 scale = SkMinScalar(scale,
102 SkScalarDiv(rect.height(), fRadii[3].fY + fRadii[0].fY));
103 }
104
105 if (scale < SK_Scalar1) {
106 for (int i = 0; i < 4; ++i) {
107 fRadii[i].fX = SkScalarMul(fRadii[i].fX, scale);
108 fRadii[i].fY = SkScalarMul(fRadii[i].fY, scale);
109 }
110 }
111
112 // At this point we're either oval, simple, or complex (not empty or rect)
113 // but we lazily resolve the type to avoid the work if the information
114 // isn't required.
115 fType = (SkRRect::Type) kUnknown_Type;
116
117 SkDEBUGCODE(this->validate();)
118}
119
skia.committer@gmail.com2cf444f2013-04-26 07:00:58 +0000120// This method determines if a point known to be inside the RRect's bounds is
robertphillips@google.com32c1b662013-04-25 12:23:00 +0000121// inside all the corners.
122bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const {
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000123 SkPoint canonicalPt; // (x,y) translated to one of the quadrants
124 int index;
125
126 if (kOval_Type == this->type()) {
127 canonicalPt.set(x - fRect.centerX(), y - fRect.centerY());
128 index = kUpperLeft_Corner; // any corner will do in this case
129 } else {
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000130 if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX &&
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000131 y < fRect.fTop + fRadii[kUpperLeft_Corner].fY) {
132 // UL corner
133 index = kUpperLeft_Corner;
134 canonicalPt.set(x - (fRect.fLeft + fRadii[kUpperLeft_Corner].fX),
135 y - (fRect.fTop + fRadii[kUpperLeft_Corner].fY));
136 SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY < 0);
137 } else if (x < fRect.fLeft + fRadii[kLowerLeft_Corner].fX &&
138 y > fRect.fBottom - fRadii[kLowerLeft_Corner].fY) {
139 // LL corner
140 index = kLowerLeft_Corner;
141 canonicalPt.set(x - (fRect.fLeft + fRadii[kLowerLeft_Corner].fX),
142 y - (fRect.fBottom - fRadii[kLowerLeft_Corner].fY));
143 SkASSERT(canonicalPt.fX < 0 && canonicalPt.fY > 0);
144 } else if (x > fRect.fRight - fRadii[kUpperRight_Corner].fX &&
145 y < fRect.fTop + fRadii[kUpperRight_Corner].fY) {
146 // UR corner
147 index = kUpperRight_Corner;
148 canonicalPt.set(x - (fRect.fRight - fRadii[kUpperRight_Corner].fX),
149 y - (fRect.fTop + fRadii[kUpperRight_Corner].fY));
150 SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY < 0);
151 } else if (x > fRect.fRight - fRadii[kLowerRight_Corner].fX &&
152 y > fRect.fBottom - fRadii[kLowerRight_Corner].fY) {
153 // LR corner
154 index = kLowerRight_Corner;
155 canonicalPt.set(x - (fRect.fRight - fRadii[kLowerRight_Corner].fX),
156 y - (fRect.fBottom - fRadii[kLowerRight_Corner].fY));
157 SkASSERT(canonicalPt.fX > 0 && canonicalPt.fY > 0);
158 } else {
159 // not in any of the corners
160 return true;
161 }
162 }
163
164 // A point is in an ellipse (in standard position) if:
165 // x^2 y^2
166 // ----- + ----- <= 1
167 // a^2 b^2
robertphillips@google.com32c1b662013-04-25 12:23:00 +0000168 // or :
169 // b^2*x^2 + a^2*y^2 <= (ab)^2
170 SkScalar dist = SkScalarMul(SkScalarSquare(canonicalPt.fX), SkScalarSquare(fRadii[index].fY)) +
171 SkScalarMul(SkScalarSquare(canonicalPt.fY), SkScalarSquare(fRadii[index].fX));
172 return dist <= SkScalarSquare(SkScalarMul(fRadii[index].fX, fRadii[index].fY));
173}
174
175bool SkRRect::contains(const SkRect& rect) const {
176 if (!this->getBounds().contains(rect)) {
177 // If 'rect' isn't contained by the RR's bounds then the
178 // RR definitely doesn't contain it
179 return false;
180 }
181
182 if (this->isRect()) {
183 // the prior test was sufficient
184 return true;
185 }
186
187 // At this point we know all four corners of 'rect' are inside the
188 // bounds of of this RR. Check to make sure all the corners are inside
189 // all the curves
190 return this->checkCornerContainment(rect.fLeft, rect.fTop) &&
191 this->checkCornerContainment(rect.fRight, rect.fTop) &&
192 this->checkCornerContainment(rect.fRight, rect.fBottom) &&
193 this->checkCornerContainment(rect.fLeft, rect.fBottom);
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000194}
195
196// There is a simplified version of this method in setRectXY
197void SkRRect::computeType() const {
198 SkDEBUGCODE(this->validate();)
199
200 if (fRect.isEmpty()) {
201 fType = kEmpty_Type;
202 return;
203 }
204
205 bool allRadiiEqual = true; // are all x radii equal and all y radii?
206 bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY;
207
208 for (int i = 1; i < 4; ++i) {
209 if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
210 // if either radius is zero the corner is square so both have to
211 // be non-zero to have a rounded corner
212 allCornersSquare = false;
213 }
214 if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
215 allRadiiEqual = false;
216 }
217 }
218
219 if (allCornersSquare) {
220 fType = kRect_Type;
221 return;
222 }
223
224 if (allRadiiEqual) {
225 if (fRadii[0].fX >= SkScalarHalf(fRect.width()) &&
226 fRadii[0].fY >= SkScalarHalf(fRect.height())) {
227 fType = kOval_Type;
228 } else {
229 fType = kSimple_Type;
230 }
231 return;
232 }
233
234 fType = kComplex_Type;
235}
236
scroggo@google.com20e3cd22013-11-05 15:54:42 +0000237static bool matrix_only_scale_and_translate(const SkMatrix& matrix) {
238 const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask
239 | SkMatrix::kPerspective_Mask);
240 return (matrix.getType() & m) == 0;
241}
242
243bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const {
244 if (NULL == dst) {
245 return false;
246 }
247
248 // Assert that the caller is not trying to do this in place, which
249 // would violate const-ness. Do not return false though, so that
250 // if they know what they're doing and want to violate it they can.
251 SkASSERT(dst != this);
252
253 if (matrix.isIdentity()) {
254 *dst = *this;
255 return true;
256 }
257
258 // If transform supported 90 degree rotations (which it could), we could
259 // use SkMatrix::rectStaysRect() to check for a valid transformation.
260 if (!matrix_only_scale_and_translate(matrix)) {
261 return false;
262 }
263
264 SkRect newRect;
265 if (!matrix.mapRect(&newRect, fRect)) {
266 return false;
267 }
268
269 // At this point, this is guaranteed to succeed, so we can modify dst.
270 dst->fRect = newRect;
271
272 // Now scale each corner
273 SkScalar xScale = matrix.getScaleX();
274 const bool flipX = xScale < 0;
275 if (flipX) {
276 xScale = -xScale;
277 }
278 SkScalar yScale = matrix.getScaleY();
279 const bool flipY = yScale < 0;
280 if (flipY) {
281 yScale = -yScale;
282 }
283
284 // Scale the radii without respecting the flip.
285 for (int i = 0; i < 4; ++i) {
286 dst->fRadii[i].fX = SkScalarMul(fRadii[i].fX, xScale);
287 dst->fRadii[i].fY = SkScalarMul(fRadii[i].fY, yScale);
288 }
289
290 // Now swap as necessary.
291 if (flipX) {
292 if (flipY) {
293 // Swap with opposite corners
294 SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]);
295 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]);
296 } else {
297 // Only swap in x
298 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]);
299 SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]);
300 }
301 } else if (flipY) {
302 // Only swap in y
303 SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]);
304 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]);
305 }
306
307 // Since the only transforms that were allowed are scale and translate, the type
308 // remains unchanged.
309 dst->fType = fType;
310
311 SkDEBUGCODE(dst->validate();)
312
313 return true;
314}
315
reed@google.com4ed0fb72012-12-12 20:48:18 +0000316///////////////////////////////////////////////////////////////////////////////
mike@reedtribe.org37071642012-12-17 02:10:42 +0000317
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000318void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const {
mike@reedtribe.org37071642012-12-17 02:10:42 +0000319 SkRect r = fRect;
skia.committer@gmail.com1a60dab2012-12-24 02:01:25 +0000320
mike@reedtribe.org37071642012-12-17 02:10:42 +0000321 r.inset(dx, dy);
322 if (r.isEmpty()) {
323 dst->setEmpty();
324 return;
325 }
326
327 SkVector radii[4];
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000328 memcpy(radii, fRadii, sizeof(radii));
mike@reedtribe.org37071642012-12-17 02:10:42 +0000329 for (int i = 0; i < 4; ++i) {
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000330 if (radii[i].fX) {
331 radii[i].fX -= dx;
332 }
333 if (radii[i].fY) {
334 radii[i].fY -= dy;
335 }
mike@reedtribe.org37071642012-12-17 02:10:42 +0000336 }
337 dst->setRectRadii(r, radii);
338}
mike@reedtribe.orgbcbef572012-12-23 23:11:21 +0000339
mike@reedtribe.org37071642012-12-17 02:10:42 +0000340///////////////////////////////////////////////////////////////////////////////
341
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +0000342size_t SkRRect::writeToMemory(void* buffer) const {
reed@google.com4ed0fb72012-12-12 20:48:18 +0000343 SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii));
344
345 memcpy(buffer, &fRect, sizeof(SkRect));
346 memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii));
347 return kSizeInMemory;
348}
349
commit-bot@chromium.org4faa8692013-11-05 15:46:56 +0000350size_t SkRRect::readFromMemory(const void* buffer, size_t length) {
351 if (length < kSizeInMemory) {
352 return 0;
353 }
354
reed@google.com4ed0fb72012-12-12 20:48:18 +0000355 SkScalar storage[12];
356 SkASSERT(sizeof(storage) == kSizeInMemory);
357
358 // we make a local copy, to ensure alignment before we cast
359 memcpy(storage, buffer, kSizeInMemory);
360
361 this->setRectRadii(*(const SkRect*)&storage[0],
362 (const SkVector*)&storage[4]);
363 return kSizeInMemory;
364}
365
366///////////////////////////////////////////////////////////////////////////////
367
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000368#ifdef SK_DEBUG
369void SkRRect::validate() const {
370 bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY);
371 bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY);
372 bool allRadiiSame = true;
373
374 for (int i = 1; i < 4; ++i) {
375 if (0 != fRadii[i].fX || 0 != fRadii[i].fY) {
376 allRadiiZero = false;
377 }
378
379 if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) {
380 allRadiiSame = false;
381 }
382
383 if (0 != fRadii[i].fX && 0 != fRadii[i].fY) {
384 allCornersSquare = false;
385 }
386 }
387
388 switch (fType) {
389 case kEmpty_Type:
390 SkASSERT(fRect.isEmpty());
391 SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
392
skia.committer@gmail.comc3d7d902012-11-30 02:01:24 +0000393 SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop &&
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000394 0 == fRect.fRight && 0 == fRect.fBottom);
395 break;
396 case kRect_Type:
397 SkASSERT(!fRect.isEmpty());
398 SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare);
399 break;
400 case kOval_Type:
401 SkASSERT(!fRect.isEmpty());
402 SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
403
404 for (int i = 0; i < 4; ++i) {
405 SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width())));
406 SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height())));
407 }
408 break;
409 case kSimple_Type:
410 SkASSERT(!fRect.isEmpty());
411 SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare);
412 break;
413 case kComplex_Type:
414 SkASSERT(!fRect.isEmpty());
415 SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare);
416 break;
417 case kUnknown_Type:
418 // no limits on this
419 break;
robertphillips@google.com5985e7c2012-11-29 13:24:55 +0000420 }
421}
422#endif // SK_DEBUG
423
424///////////////////////////////////////////////////////////////////////////////