|
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 |
|
13 void 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; |
|
40 // TODO: assert that all the x&y radii are already W/2 & H/2 |
|
41 } |
|
42 |
|
43 SkDEBUGCODE(this->validate();) |
|
44 } |
|
45 |
|
46 void 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 |
|
77 // of a side and the radii on that side (for all four sides) and use |
|
78 // 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: |
|
81 // "Let f = min(Li/Si), where i is one of { top, right, bottom, left }, |
|
82 // Si is the sum of the two corresponding radii of the corners on side i, |
|
83 // and Ltop = Lbottom = the width of the box, |
|
84 // and Lleft = Lright = the height of the box. |
|
85 // 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 |
|
120 // This method determines if a point known to be inside the RRect's bounds is |
|
121 // inside all the corners. |
|
122 bool SkRRect::checkCornerContainment(SkScalar x, SkScalar y) const { |
|
123 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 { |
|
130 if (x < fRect.fLeft + fRadii[kUpperLeft_Corner].fX && |
|
131 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 |
|
168 // 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 |
|
175 bool SkRRect::allCornersCircular() const { |
|
176 return fRadii[0].fX == fRadii[0].fY && |
|
177 fRadii[1].fX == fRadii[1].fY && |
|
178 fRadii[2].fX == fRadii[2].fY && |
|
179 fRadii[3].fX == fRadii[3].fY; |
|
180 } |
|
181 |
|
182 bool SkRRect::contains(const SkRect& rect) const { |
|
183 if (!this->getBounds().contains(rect)) { |
|
184 // If 'rect' isn't contained by the RR's bounds then the |
|
185 // RR definitely doesn't contain it |
|
186 return false; |
|
187 } |
|
188 |
|
189 if (this->isRect()) { |
|
190 // the prior test was sufficient |
|
191 return true; |
|
192 } |
|
193 |
|
194 // At this point we know all four corners of 'rect' are inside the |
|
195 // bounds of of this RR. Check to make sure all the corners are inside |
|
196 // all the curves |
|
197 return this->checkCornerContainment(rect.fLeft, rect.fTop) && |
|
198 this->checkCornerContainment(rect.fRight, rect.fTop) && |
|
199 this->checkCornerContainment(rect.fRight, rect.fBottom) && |
|
200 this->checkCornerContainment(rect.fLeft, rect.fBottom); |
|
201 } |
|
202 |
|
203 // There is a simplified version of this method in setRectXY |
|
204 void SkRRect::computeType() const { |
|
205 SkDEBUGCODE(this->validate();) |
|
206 |
|
207 if (fRect.isEmpty()) { |
|
208 fType = kEmpty_Type; |
|
209 return; |
|
210 } |
|
211 |
|
212 bool allRadiiEqual = true; // are all x radii equal and all y radii? |
|
213 bool allCornersSquare = 0 == fRadii[0].fX || 0 == fRadii[0].fY; |
|
214 |
|
215 for (int i = 1; i < 4; ++i) { |
|
216 if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { |
|
217 // if either radius is zero the corner is square so both have to |
|
218 // be non-zero to have a rounded corner |
|
219 allCornersSquare = false; |
|
220 } |
|
221 if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { |
|
222 allRadiiEqual = false; |
|
223 } |
|
224 } |
|
225 |
|
226 if (allCornersSquare) { |
|
227 fType = kRect_Type; |
|
228 return; |
|
229 } |
|
230 |
|
231 if (allRadiiEqual) { |
|
232 if (fRadii[0].fX >= SkScalarHalf(fRect.width()) && |
|
233 fRadii[0].fY >= SkScalarHalf(fRect.height())) { |
|
234 fType = kOval_Type; |
|
235 } else { |
|
236 fType = kSimple_Type; |
|
237 } |
|
238 return; |
|
239 } |
|
240 |
|
241 fType = kComplex_Type; |
|
242 } |
|
243 |
|
244 static bool matrix_only_scale_and_translate(const SkMatrix& matrix) { |
|
245 const SkMatrix::TypeMask m = (SkMatrix::TypeMask) (SkMatrix::kAffine_Mask |
|
246 | SkMatrix::kPerspective_Mask); |
|
247 return (matrix.getType() & m) == 0; |
|
248 } |
|
249 |
|
250 bool SkRRect::transform(const SkMatrix& matrix, SkRRect* dst) const { |
|
251 if (NULL == dst) { |
|
252 return false; |
|
253 } |
|
254 |
|
255 // Assert that the caller is not trying to do this in place, which |
|
256 // would violate const-ness. Do not return false though, so that |
|
257 // if they know what they're doing and want to violate it they can. |
|
258 SkASSERT(dst != this); |
|
259 |
|
260 if (matrix.isIdentity()) { |
|
261 *dst = *this; |
|
262 return true; |
|
263 } |
|
264 |
|
265 // If transform supported 90 degree rotations (which it could), we could |
|
266 // use SkMatrix::rectStaysRect() to check for a valid transformation. |
|
267 if (!matrix_only_scale_and_translate(matrix)) { |
|
268 return false; |
|
269 } |
|
270 |
|
271 SkRect newRect; |
|
272 if (!matrix.mapRect(&newRect, fRect)) { |
|
273 return false; |
|
274 } |
|
275 |
|
276 // At this point, this is guaranteed to succeed, so we can modify dst. |
|
277 dst->fRect = newRect; |
|
278 |
|
279 // Now scale each corner |
|
280 SkScalar xScale = matrix.getScaleX(); |
|
281 const bool flipX = xScale < 0; |
|
282 if (flipX) { |
|
283 xScale = -xScale; |
|
284 } |
|
285 SkScalar yScale = matrix.getScaleY(); |
|
286 const bool flipY = yScale < 0; |
|
287 if (flipY) { |
|
288 yScale = -yScale; |
|
289 } |
|
290 |
|
291 // Scale the radii without respecting the flip. |
|
292 for (int i = 0; i < 4; ++i) { |
|
293 dst->fRadii[i].fX = SkScalarMul(fRadii[i].fX, xScale); |
|
294 dst->fRadii[i].fY = SkScalarMul(fRadii[i].fY, yScale); |
|
295 } |
|
296 |
|
297 // Now swap as necessary. |
|
298 if (flipX) { |
|
299 if (flipY) { |
|
300 // Swap with opposite corners |
|
301 SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerRight_Corner]); |
|
302 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerLeft_Corner]); |
|
303 } else { |
|
304 // Only swap in x |
|
305 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kUpperLeft_Corner]); |
|
306 SkTSwap(dst->fRadii[kLowerRight_Corner], dst->fRadii[kLowerLeft_Corner]); |
|
307 } |
|
308 } else if (flipY) { |
|
309 // Only swap in y |
|
310 SkTSwap(dst->fRadii[kUpperLeft_Corner], dst->fRadii[kLowerLeft_Corner]); |
|
311 SkTSwap(dst->fRadii[kUpperRight_Corner], dst->fRadii[kLowerRight_Corner]); |
|
312 } |
|
313 |
|
314 // Since the only transforms that were allowed are scale and translate, the type |
|
315 // remains unchanged. |
|
316 dst->fType = fType; |
|
317 |
|
318 SkDEBUGCODE(dst->validate();) |
|
319 |
|
320 return true; |
|
321 } |
|
322 |
|
323 /////////////////////////////////////////////////////////////////////////////// |
|
324 |
|
325 void SkRRect::inset(SkScalar dx, SkScalar dy, SkRRect* dst) const { |
|
326 SkRect r = fRect; |
|
327 |
|
328 r.inset(dx, dy); |
|
329 if (r.isEmpty()) { |
|
330 dst->setEmpty(); |
|
331 return; |
|
332 } |
|
333 |
|
334 SkVector radii[4]; |
|
335 memcpy(radii, fRadii, sizeof(radii)); |
|
336 for (int i = 0; i < 4; ++i) { |
|
337 if (radii[i].fX) { |
|
338 radii[i].fX -= dx; |
|
339 } |
|
340 if (radii[i].fY) { |
|
341 radii[i].fY -= dy; |
|
342 } |
|
343 } |
|
344 dst->setRectRadii(r, radii); |
|
345 } |
|
346 |
|
347 /////////////////////////////////////////////////////////////////////////////// |
|
348 |
|
349 size_t SkRRect::writeToMemory(void* buffer) const { |
|
350 SkASSERT(kSizeInMemory == sizeof(SkRect) + sizeof(fRadii)); |
|
351 |
|
352 memcpy(buffer, &fRect, sizeof(SkRect)); |
|
353 memcpy((char*)buffer + sizeof(SkRect), fRadii, sizeof(fRadii)); |
|
354 return kSizeInMemory; |
|
355 } |
|
356 |
|
357 size_t SkRRect::readFromMemory(const void* buffer, size_t length) { |
|
358 if (length < kSizeInMemory) { |
|
359 return 0; |
|
360 } |
|
361 |
|
362 SkScalar storage[12]; |
|
363 SkASSERT(sizeof(storage) == kSizeInMemory); |
|
364 |
|
365 // we make a local copy, to ensure alignment before we cast |
|
366 memcpy(storage, buffer, kSizeInMemory); |
|
367 |
|
368 this->setRectRadii(*(const SkRect*)&storage[0], |
|
369 (const SkVector*)&storage[4]); |
|
370 return kSizeInMemory; |
|
371 } |
|
372 |
|
373 /////////////////////////////////////////////////////////////////////////////// |
|
374 |
|
375 #ifdef SK_DEBUG |
|
376 void SkRRect::validate() const { |
|
377 bool allRadiiZero = (0 == fRadii[0].fX && 0 == fRadii[0].fY); |
|
378 bool allCornersSquare = (0 == fRadii[0].fX || 0 == fRadii[0].fY); |
|
379 bool allRadiiSame = true; |
|
380 |
|
381 for (int i = 1; i < 4; ++i) { |
|
382 if (0 != fRadii[i].fX || 0 != fRadii[i].fY) { |
|
383 allRadiiZero = false; |
|
384 } |
|
385 |
|
386 if (fRadii[i].fX != fRadii[i-1].fX || fRadii[i].fY != fRadii[i-1].fY) { |
|
387 allRadiiSame = false; |
|
388 } |
|
389 |
|
390 if (0 != fRadii[i].fX && 0 != fRadii[i].fY) { |
|
391 allCornersSquare = false; |
|
392 } |
|
393 } |
|
394 |
|
395 switch (fType) { |
|
396 case kEmpty_Type: |
|
397 SkASSERT(fRect.isEmpty()); |
|
398 SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare); |
|
399 |
|
400 SkASSERT(0 == fRect.fLeft && 0 == fRect.fTop && |
|
401 0 == fRect.fRight && 0 == fRect.fBottom); |
|
402 break; |
|
403 case kRect_Type: |
|
404 SkASSERT(!fRect.isEmpty()); |
|
405 SkASSERT(allRadiiZero && allRadiiSame && allCornersSquare); |
|
406 break; |
|
407 case kOval_Type: |
|
408 SkASSERT(!fRect.isEmpty()); |
|
409 SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare); |
|
410 |
|
411 for (int i = 0; i < 4; ++i) { |
|
412 SkASSERT(SkScalarNearlyEqual(fRadii[i].fX, SkScalarHalf(fRect.width()))); |
|
413 SkASSERT(SkScalarNearlyEqual(fRadii[i].fY, SkScalarHalf(fRect.height()))); |
|
414 } |
|
415 break; |
|
416 case kSimple_Type: |
|
417 SkASSERT(!fRect.isEmpty()); |
|
418 SkASSERT(!allRadiiZero && allRadiiSame && !allCornersSquare); |
|
419 break; |
|
420 case kComplex_Type: |
|
421 SkASSERT(!fRect.isEmpty()); |
|
422 SkASSERT(!allRadiiZero && !allRadiiSame && !allCornersSquare); |
|
423 break; |
|
424 case kUnknown_Type: |
|
425 // no limits on this |
|
426 break; |
|
427 } |
|
428 } |
|
429 #endif // SK_DEBUG |
|
430 |
|
431 /////////////////////////////////////////////////////////////////////////////// |