Flutter Engine
The Flutter Engine
Loading...
Searching...
No Matches
RoundRectTest.cpp
Go to the documentation of this file.
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
12#include "include/core/SkRect.h"
16#include "src/base/SkRandom.h"
19#include "tests/Test.h"
20
21#include <algorithm>
22#include <array>
23#include <cstddef>
24#include <cstdint>
25
27 {
28 // crbug.com/458522
29 SkRRect rr;
30 const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
31 const SkScalar rad = 12814;
32 const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } };
33 rr.setRectRadii(bounds, vec);
34 }
35
36 {
37 // crbug.com//463920
38 SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0);
39 SkVector radii[4] = {
40 { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f }
41 };
42 SkRRect rr;
43 rr.setRectRadii(r, radii);
44
47 rr.height());
48 }
49}
50
52 SkRRect rr;
53 const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
54 const SkScalar rad = 40;
55 rr.setRectXY(bounds, rad, rad);
56
57 SkRRect other;
58 SkMatrix matrix;
59 matrix.setScale(0, 1);
60 rr.transform(matrix, &other);
62}
63
64// Test that all the SkRRect entry points correctly handle un-sorted and
65// zero-sized input rects
67 static const SkRect oooRects[] = { // out of order
68 { 100, 0, 0, 100 }, // ooo horizontal
69 { 0, 100, 100, 0 }, // ooo vertical
70 { 100, 100, 0, 0 }, // ooo both
71 };
72
73 static const SkRect emptyRects[] = {
74 { 100, 100, 100, 200 }, // empty horizontal
75 { 100, 100, 200, 100 }, // empty vertical
76 { 100, 100, 100, 100 }, // empty both
77 { 0, 0, 0, 0 } // setEmpty-empty
78 };
79
80 static const SkVector radii[4] = { { 0, 1 }, { 2, 3 }, { 4, 5 }, { 6, 7 } };
81
82 SkRRect r;
83
84 for (size_t i = 0; i < std::size(oooRects); ++i) {
85 r.setRect(oooRects[i]);
87 REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted());
88
89 r.setOval(oooRects[i]);
91 REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted());
92
93 r.setRectXY(oooRects[i], 1, 2);
95 REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted());
96
97 r.setNinePatch(oooRects[i], 0, 1, 2, 3);
99 REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted());
100
101 r.setRectRadii(oooRects[i], radii);
103 REPORTER_ASSERT(reporter, r.rect() == oooRects[i].makeSorted());
104 }
105
106 for (size_t i = 0; i < std::size(emptyRects); ++i) {
107 r.setRect(emptyRects[i]);
109 REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]);
110
111 r.setOval(emptyRects[i]);
113 REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]);
114
115 r.setRectXY(emptyRects[i], 1, 2);
117 REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]);
118
119 r.setNinePatch(emptyRects[i], 0, 1, 2, 3);
121 REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]);
122
123 r.setRectRadii(emptyRects[i], radii);
125 REPORTER_ASSERT(reporter, r.rect() == emptyRects[i]);
126 }
127
128 r.setRect({SK_ScalarNaN, 10, 10, 20});
130 r.setRect({0, 10, 10, SK_ScalarInfinity});
132}
133
134static const SkScalar kWidth = 100.0f;
135static const SkScalar kHeight = 100.0f;
136
138 SkRRect rr, rr2;
139 SkRect r = { 0, 0, 100, 100 };
140
141 rr.setRect(r);
142 rr.inset(-20, -20, &rr2);
144
145 rr.inset(20, 20, &rr2);
147
148 rr.inset(r.width()/2, r.height()/2, &rr2);
150
151 rr.setRectXY(r, 20, 20);
152 rr.inset(19, 19, &rr2);
154 rr.inset(20, 20, &rr2);
156}
157
158
160 const SkRect& rect,
162 bool checkRadii) {
163 SkRRect rr;
164 rr.setNinePatch(rect, l, t, r, b);
165
167 REPORTER_ASSERT(reporter, rr.rect() == rect);
168
169 if (checkRadii) {
170 // This test doesn't hold if the radii will be rescaled by SkRRect
171 SkRect ninePatchRadii = { l, t, r, b };
172 SkPoint rquad[4];
173 ninePatchRadii.toQuad(rquad);
174 for (int i = 0; i < 4; ++i) {
175 REPORTER_ASSERT(reporter, rquad[i] == rr.radii((SkRRect::Corner) i));
176 }
177 }
178 SkRRect rr2; // construct the same RR using the most general set function
179 SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } };
180 rr2.setRectRadii(rect, radii);
181 REPORTER_ASSERT(reporter, rr2 == rr && rr2.getType() == rr.getType());
182}
183
184// Test out the basic API entry points
186 // Test out initialization methods
187 SkPoint zeroPt = { 0, 0 };
189
190 empty.setEmpty();
191
193 REPORTER_ASSERT(reporter, empty.rect().isEmpty());
194
195 for (int i = 0; i < 4; ++i) {
196 REPORTER_ASSERT(reporter, zeroPt == empty.radii((SkRRect::Corner) i));
197 }
198
199 //----
200 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
201
202 SkRRect rr1;
203 rr1.setRect(rect);
204
206 REPORTER_ASSERT(reporter, rr1.rect() == rect);
207
208 for (int i = 0; i < 4; ++i) {
209 REPORTER_ASSERT(reporter, zeroPt == rr1.radii((SkRRect::Corner) i));
210 }
211 SkRRect rr1_2; // construct the same RR using the most general set function
212 SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
213 rr1_2.setRectRadii(rect, rr1_2_radii);
214 REPORTER_ASSERT(reporter, rr1_2 == rr1 && rr1_2.getType() == rr1.getType());
215 SkRRect rr1_3; // construct the same RR using the nine patch set function
216 rr1_3.setNinePatch(rect, 0, 0, 0, 0);
217 REPORTER_ASSERT(reporter, rr1_3 == rr1 && rr1_3.getType() == rr1.getType());
218
219 //----
221 SkRRect rr2;
222 rr2.setOval(rect);
223
225 REPORTER_ASSERT(reporter, rr2.rect() == rect);
226
227 for (int i = 0; i < 4; ++i) {
230 halfPoint));
231 }
232 SkRRect rr2_2; // construct the same RR using the most general set function
233 SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY },
234 { halfPoint.fX, halfPoint.fY }, { halfPoint.fX, halfPoint.fY } };
235 rr2_2.setRectRadii(rect, rr2_2_radii);
236 REPORTER_ASSERT(reporter, rr2_2 == rr2 && rr2_2.getType() == rr2.getType());
237 SkRRect rr2_3; // construct the same RR using the nine patch set function
238 rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY);
239 REPORTER_ASSERT(reporter, rr2_3 == rr2 && rr2_3.getType() == rr2.getType());
240
241 //----
242 SkPoint p = { 5, 5 };
243 SkRRect rr3;
244 rr3.setRectXY(rect, p.fX, p.fY);
245
247 REPORTER_ASSERT(reporter, rr3.rect() == rect);
248
249 for (int i = 0; i < 4; ++i) {
251 }
252 SkRRect rr3_2; // construct the same RR using the most general set function
253 SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
254 rr3_2.setRectRadii(rect, rr3_2_radii);
255 REPORTER_ASSERT(reporter, rr3_2 == rr3 && rr3_2.getType() == rr3.getType());
256 SkRRect rr3_3; // construct the same RR using the nine patch set function
257 rr3_3.setNinePatch(rect, 5, 5, 5, 5);
258 REPORTER_ASSERT(reporter, rr3_3 == rr3 && rr3_3.getType() == rr3.getType());
259
260 //----
261 test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true);
262
263 {
264 // Test out the rrect from skia:3466
265 SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f, 0.806214333f);
266
268 rect2,
269 0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f,
270 false);
271 }
272
273 //----
274 SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
275
276 SkRRect rr5;
277 rr5.setRectRadii(rect, radii2);
278
280 REPORTER_ASSERT(reporter, rr5.rect() == rect);
281
282 for (int i = 0; i < 4; ++i) {
283 REPORTER_ASSERT(reporter, radii2[i] == rr5.radii((SkRRect::Corner) i));
284 }
285
286 // Test out == & !=
288 REPORTER_ASSERT(reporter, rr3 != rr5);
289}
290
291// Test out the cases when the RR degenerates to a rect
293 SkRect r;
294
295 //----
297
298 empty.setEmpty();
299
301 r = empty.rect();
302 REPORTER_ASSERT(reporter, 0 == r.fLeft && 0 == r.fTop && 0 == r.fRight && 0 == r.fBottom);
303
304 //----
305 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
306 SkRRect rr1;
307 rr1.setRectXY(rect, 0, 0);
308
310 r = rr1.rect();
311 REPORTER_ASSERT(reporter, rect == r);
312
313 //----
314 SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
315
316 SkRRect rr2;
317 rr2.setRectRadii(rect, radii);
318
320 r = rr2.rect();
321 REPORTER_ASSERT(reporter, rect == r);
322
323 //----
324 SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
325
326 SkRRect rr3;
327 rr3.setRectRadii(rect, radii2);
329}
330
331// Test out the cases when the RR degenerates to an oval
333 //----
334 SkRect oval;
335 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
336 SkRRect rr1;
338
340 oval = rr1.rect();
341 REPORTER_ASSERT(reporter, oval == rect);
342}
343
344// Test out the non-degenerate RR cases
346 //----
347 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
348 SkRRect rr1;
349 rr1.setRectXY(rect, 20, 20);
350
352
353 //----
354 SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
355
356 SkRRect rr2;
357 rr2.setRectRadii(rect, radii);
358
360}
361
362// Test out questionable-parameter handling
364
365 // When the radii exceed the base rect they are proportionally scaled down
366 // to fit
367 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
368 SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
369
370 SkRRect rr1;
371 rr1.setRectRadii(rect, radii);
372
374
376
379
380 // Negative radii should be capped at zero
381 SkRRect rr2;
382 rr2.setRectXY(rect, -10, -20);
383
385
387
388 REPORTER_ASSERT(reporter, 0.0f == p2.fX);
389 REPORTER_ASSERT(reporter, 0.0f == p2.fY);
390}
391
392// Move a small box from the start position by (stepX, stepY) 'numSteps' times
393// testing for containment in 'rr' at each step.
395 SkScalar initX, int stepX, SkScalar initY, int stepY,
396 int numSteps, const bool* contains) {
397 SkScalar x = initX, y = initY;
398 for (int i = 0; i < numSteps; ++i) {
400 stepX ? SkIntToScalar(stepX) : SK_Scalar1,
401 stepY ? SkIntToScalar(stepY) : SK_Scalar1);
402 test.sort();
403
405
406 x += stepX;
407 y += stepY;
408 }
409}
410
411// Exercise the RR's contains rect method
413
414 static const int kNumRRects = 4;
415 static const SkVector gRadii[kNumRRects][4] = {
416 { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }, // rect
417 { { 20, 20 }, { 20, 20 }, { 20, 20 }, { 20, 20 } }, // circle
418 { { 10, 10 }, { 10, 10 }, { 10, 10 }, { 10, 10 } }, // simple
419 { { 0, 0 }, { 20, 20 }, { 10, 10 }, { 30, 30 } } // complex
420 };
421
422 SkRRect rrects[kNumRRects];
423 for (int i = 0; i < kNumRRects; ++i) {
424 rrects[i].setRectRadii(SkRect::MakeWH(40, 40), gRadii[i]);
425 }
426
427 // First test easy outs - boxes that are obviously out on
428 // each corner and edge
429 static const SkRect easyOuts[] = {
430 { -5, -5, 5, 5 }, // NW
431 { 15, -5, 20, 5 }, // N
432 { 35, -5, 45, 5 }, // NE
433 { 35, 15, 45, 20 }, // E
434 { 35, 45, 35, 45 }, // SE
435 { 15, 35, 20, 45 }, // S
436 { -5, 35, 5, 45 }, // SW
437 { -5, 15, 5, 20 } // W
438 };
439
440 for (int i = 0; i < kNumRRects; ++i) {
441 for (size_t j = 0; j < std::size(easyOuts); ++j) {
442 REPORTER_ASSERT(reporter, !rrects[i].contains(easyOuts[j]));
443 }
444 }
445
446 // Now test non-trivial containment. For each compass
447 // point walk a 1x1 rect in from the edge of the bounding
448 // rect
449 static const int kNumSteps = 15;
450 bool answers[kNumRRects][8][kNumSteps] = {
451 // all the test rects are inside the degenerate rrect
452 {
453 // rect
454 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
455 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
456 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
457 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
458 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
459 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
460 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
461 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
462 },
463 // for the circle we expect 6 blocks to be out on the
464 // corners (then the rest in) and only the first block
465 // out on the vertical and horizontal axes (then
466 // the rest in)
467 {
468 // circle
469 { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
470 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
471 { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
472 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
473 { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
474 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
475 { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
476 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
477 },
478 // for the simple round rect we expect 3 out on
479 // the corners (then the rest in) and no blocks out
480 // on the vertical and horizontal axes
481 {
482 // simple RR
483 { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
484 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
485 { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
486 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
487 { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
488 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
489 { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
490 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
491 },
492 // for the complex case the answer is different for each direction
493 {
494 // complex RR
495 // all in for NW (rect) corner (same as rect case)
496 { 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
497 // only first block out for N (same as circle case)
498 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
499 // first 6 blocks out for NE (same as circle case)
500 { 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
501 // only first block out for E (same as circle case)
502 { 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
503 // first 3 blocks out for SE (same as simple case)
504 { 0, 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
505 // first two blocks out for S
506 { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
507 // first 9 blocks out for SW
508 { 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1 },
509 // first two blocks out for W (same as S)
510 { 0, 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1 },
511 }
512 };
513
514 for (int i = 0; i < kNumRRects; ++i) {
515 test_direction(reporter, rrects[i], 0, 1, 0, 1, kNumSteps, answers[i][0]); // NW
516 test_direction(reporter, rrects[i], 19.5f, 0, 0, 1, kNumSteps, answers[i][1]); // N
517 test_direction(reporter, rrects[i], 40, -1, 0, 1, kNumSteps, answers[i][2]); // NE
518 test_direction(reporter, rrects[i], 40, -1, 19.5f, 0, kNumSteps, answers[i][3]); // E
519 test_direction(reporter, rrects[i], 40, -1, 40, -1, kNumSteps, answers[i][4]); // SE
520 test_direction(reporter, rrects[i], 19.5f, 0, 40, -1, kNumSteps, answers[i][5]); // S
521 test_direction(reporter, rrects[i], 0, 1, 40, -1, kNumSteps, answers[i][6]); // SW
522 test_direction(reporter, rrects[i], 0, 1, 19.5f, 0, kNumSteps, answers[i][7]); // W
523 }
524}
525
526// Called for a matrix that should cause SkRRect::transform to fail.
528 const SkMatrix& matrix) {
529 // The test depends on the fact that the original is not empty.
530 SkASSERT(!orig.isEmpty());
531 SkRRect dst;
532 dst.setEmpty();
533
534 const SkRRect copyOfDst = dst;
535 const SkRRect copyOfOrig = orig;
536 bool success = orig.transform(matrix, &dst);
537 // This transform should fail.
538 REPORTER_ASSERT(reporter, !success);
539 // Since the transform failed, dst should be unchanged.
540 REPORTER_ASSERT(reporter, copyOfDst == dst);
541 // original should not be modified.
542 REPORTER_ASSERT(reporter, copyOfOrig == orig);
543 REPORTER_ASSERT(reporter, orig != dst);
544}
545
546#define GET_RADII \
547 const SkVector& origUL = orig.radii(SkRRect::kUpperLeft_Corner); \
548 const SkVector& origUR = orig.radii(SkRRect::kUpperRight_Corner); \
549 const SkVector& origLR = orig.radii(SkRRect::kLowerRight_Corner); \
550 const SkVector& origLL = orig.radii(SkRRect::kLowerLeft_Corner); \
551 const SkVector& dstUL = dst.radii(SkRRect::kUpperLeft_Corner); \
552 const SkVector& dstUR = dst.radii(SkRRect::kUpperRight_Corner); \
553 const SkVector& dstLR = dst.radii(SkRRect::kLowerRight_Corner); \
554 const SkVector& dstLL = dst.radii(SkRRect::kLowerLeft_Corner)
555
556// Called to test various transforms on a single SkRRect.
558 SkRRect dst;
559 dst.setEmpty();
560
561 // The identity matrix will duplicate the rrect.
562 bool success = orig.transform(SkMatrix::I(), &dst);
563 REPORTER_ASSERT(reporter, success);
564 REPORTER_ASSERT(reporter, orig == dst);
565
566 // Skew and Perspective make transform fail.
567 SkMatrix matrix;
568 matrix.reset();
569 matrix.setSkewX(SkIntToScalar(2));
570 assert_transform_failure(reporter, orig, matrix);
571
572 matrix.reset();
573 matrix.setSkewY(SkIntToScalar(3));
574 assert_transform_failure(reporter, orig, matrix);
575
576 matrix.reset();
577 matrix.setPerspX(4);
578 assert_transform_failure(reporter, orig, matrix);
579
580 matrix.reset();
581 matrix.setPerspY(5);
582 assert_transform_failure(reporter, orig, matrix);
583
584 // Rotation fails.
585 matrix.reset();
586 matrix.setRotate(SkIntToScalar(37));
587 assert_transform_failure(reporter, orig, matrix);
588
589 // Translate will keep the rect moved, but otherwise the same.
590 matrix.reset();
591 SkScalar translateX = SkIntToScalar(32);
592 SkScalar translateY = SkIntToScalar(15);
593 matrix.setTranslateX(translateX);
594 matrix.setTranslateY(translateY);
595 dst.setEmpty();
596 success = orig.transform(matrix, &dst);
597 REPORTER_ASSERT(reporter, success);
598 for (int i = 0; i < 4; ++i) {
600 orig.radii((SkRRect::Corner) i) == dst.radii((SkRRect::Corner) i));
601 }
602 REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
603 REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
604 REPORTER_ASSERT(reporter, dst.rect().left() == orig.rect().left() + translateX);
605 REPORTER_ASSERT(reporter, dst.rect().top() == orig.rect().top() + translateY);
606
607 // Keeping the translation, but adding skew will make transform fail.
608 matrix.setSkewY(SkIntToScalar(7));
609 assert_transform_failure(reporter, orig, matrix);
610
611 // Scaling in -x will flip the round rect horizontally.
612 matrix.reset();
613 matrix.setScaleX(SkIntToScalar(-1));
614 dst.setEmpty();
615 success = orig.transform(matrix, &dst);
616 REPORTER_ASSERT(reporter, success);
617 {
618 GET_RADII;
619 // Radii have swapped in x.
620 REPORTER_ASSERT(reporter, origUL == dstUR);
621 REPORTER_ASSERT(reporter, origUR == dstUL);
622 REPORTER_ASSERT(reporter, origLR == dstLL);
623 REPORTER_ASSERT(reporter, origLL == dstLR);
624 }
625 // Width and height remain the same.
626 REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
627 REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
628 // Right and left have swapped (sort of)
629 REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left());
630 // Top has stayed the same.
631 REPORTER_ASSERT(reporter, orig.rect().top() == dst.rect().top());
632
633 // Keeping the scale, but adding a persp will make transform fail.
634 matrix.setPerspX(7);
635 assert_transform_failure(reporter, orig, matrix);
636
637 // Scaling in -y will flip the round rect vertically.
638 matrix.reset();
639 matrix.setScaleY(SkIntToScalar(-1));
640 dst.setEmpty();
641 success = orig.transform(matrix, &dst);
642 REPORTER_ASSERT(reporter, success);
643 {
644 GET_RADII;
645 // Radii have swapped in y.
646 REPORTER_ASSERT(reporter, origUL == dstLL);
647 REPORTER_ASSERT(reporter, origUR == dstLR);
648 REPORTER_ASSERT(reporter, origLR == dstUR);
649 REPORTER_ASSERT(reporter, origLL == dstUL);
650 }
651 // Width and height remain the same.
652 REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
653 REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
654 // Top and bottom have swapped (sort of)
655 REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom());
656 // Left has stayed the same.
657 REPORTER_ASSERT(reporter, orig.rect().left() == dst.rect().left());
658
659 // Scaling in -x and -y will swap in both directions.
660 matrix.reset();
661 matrix.setScaleY(SkIntToScalar(-1));
662 matrix.setScaleX(SkIntToScalar(-1));
663 dst.setEmpty();
664 success = orig.transform(matrix, &dst);
665 REPORTER_ASSERT(reporter, success);
666 {
667 GET_RADII;
668 REPORTER_ASSERT(reporter, origUL == dstLR);
669 REPORTER_ASSERT(reporter, origUR == dstLL);
670 REPORTER_ASSERT(reporter, origLR == dstUL);
671 REPORTER_ASSERT(reporter, origLL == dstUR);
672 }
673 // Width and height remain the same.
674 REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().width());
675 REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().height());
676 REPORTER_ASSERT(reporter, orig.rect().top() == -dst.rect().bottom());
677 REPORTER_ASSERT(reporter, orig.rect().right() == -dst.rect().left());
678
679 // Scale in both directions.
680 SkScalar xScale = SkIntToScalar(3);
681 SkScalar yScale = 3.2f;
682 matrix.reset();
683 matrix.setScaleX(xScale);
684 matrix.setScaleY(yScale);
685 dst.setEmpty();
686 success = orig.transform(matrix, &dst);
687 REPORTER_ASSERT(reporter, success);
688 // Radii are scaled.
689 for (int i = 0; i < 4; ++i) {
691 orig.radii((SkRRect::Corner) i).fX * xScale));
693 orig.radii((SkRRect::Corner) i).fY * yScale));
694 }
695 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().width(),
696 orig.rect().width() * xScale));
697 REPORTER_ASSERT(reporter, SkScalarNearlyEqual(dst.rect().height(),
698 orig.rect().height() * yScale));
700 orig.rect().left() * xScale));
702 orig.rect().top() * yScale));
703
704
705 // a-----b d-----a
706 // | | -> | |
707 // | | Rotate 90 | |
708 // d-----c c-----b
709 matrix.reset();
710 matrix.setRotate(SkIntToScalar(90));
711 dst.setEmpty();
712 success = orig.transform(matrix, &dst);
713 REPORTER_ASSERT(reporter, success);
714 {
715 GET_RADII;
716 // Radii have cycled clockwise and swapped their x and y axis.
717 REPORTER_ASSERT(reporter, dstUL.x() == origLL.y());
718 REPORTER_ASSERT(reporter, dstUL.y() == origLL.x());
719 REPORTER_ASSERT(reporter, dstUR.x() == origUL.y());
720 REPORTER_ASSERT(reporter, dstUR.y() == origUL.x());
721 REPORTER_ASSERT(reporter, dstLR.x() == origUR.y());
722 REPORTER_ASSERT(reporter, dstLR.y() == origUR.x());
723 REPORTER_ASSERT(reporter, dstLL.x() == origLR.y());
724 REPORTER_ASSERT(reporter, dstLL.y() == origLR.x());
725 }
726 // Width and height would get swapped.
727 REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
728 REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
729
730 // a-----b b-----a c-----b
731 // | | -> | | -> | |
732 // | | Flip X | | Rotate 90 | |
733 // d-----c c-----d d-----a
734 matrix.reset();
735 matrix.setRotate(SkIntToScalar(90));
736 matrix.postScale(SkIntToScalar(-1), SkIntToScalar(1));
737 dst.setEmpty();
738 success = orig.transform(matrix, &dst);
739 REPORTER_ASSERT(reporter, success);
740 {
741 GET_RADII;
742 REPORTER_ASSERT(reporter, dstUL.x() == origLR.y());
743 REPORTER_ASSERT(reporter, dstUL.y() == origLR.x());
744 REPORTER_ASSERT(reporter, dstUR.x() == origUR.y());
745 REPORTER_ASSERT(reporter, dstUR.y() == origUR.x());
746 REPORTER_ASSERT(reporter, dstLR.x() == origUL.y());
747 REPORTER_ASSERT(reporter, dstLR.y() == origUL.x());
748 REPORTER_ASSERT(reporter, dstLL.x() == origLL.y());
749 REPORTER_ASSERT(reporter, dstLL.y() == origLL.x());
750 }
751 // Width and height would get swapped.
752 REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
753 REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
754
755 // a-----b d-----a c-----b
756 // | | -> | | -> | |
757 // | | Rotate 90 | | Flip Y | |
758 // d-----c c-----b d-----a
759 //
760 // This is the same as Flip X and Rotate 90.
761 matrix.reset();
762 matrix.setScale(SkIntToScalar(1), SkIntToScalar(-1));
763 matrix.postRotate(SkIntToScalar(90));
764 SkRRect dst2;
765 dst2.setEmpty();
766 success = orig.transform(matrix, &dst2);
767 REPORTER_ASSERT(reporter, success);
768 REPORTER_ASSERT(reporter, dst == dst2);
769
770 // a-----b b-----c c-----b
771 // | | -> | | -> | |
772 // | | Rotate 270 | | Flip X | |
773 // d-----c a-----d d-----a
774 matrix.reset();
775 matrix.setScale(SkIntToScalar(-1), SkIntToScalar(1));
776 matrix.postRotate(SkIntToScalar(270));
777 dst2.setEmpty();
778 success = orig.transform(matrix, &dst2);
779 REPORTER_ASSERT(reporter, success);
780 REPORTER_ASSERT(reporter, dst == dst2);
781
782 // a-----b d-----c c-----b
783 // | | -> | | -> | |
784 // | | Flip Y | | Rotate 270 | |
785 // d-----c a-----b d-----a
786 matrix.reset();
787 matrix.setRotate(SkIntToScalar(270));
788 matrix.postScale(SkIntToScalar(1), SkIntToScalar(-1));
789 dst2.setEmpty();
790 success = orig.transform(matrix, &dst2);
791 REPORTER_ASSERT(reporter, success);
792 REPORTER_ASSERT(reporter, dst == dst2);
793
794 // a-----b d-----a a-----d
795 // | | -> | | -> | |
796 // | | Rotate 90 | | Flip X | |
797 // d-----c c-----b b-----c
798 matrix.reset();
799 matrix.setScale(SkIntToScalar(-1), SkIntToScalar(1));
800 matrix.postRotate(SkIntToScalar(90));
801 dst.setEmpty();
802 success = orig.transform(matrix, &dst);
803 REPORTER_ASSERT(reporter, success);
804 {
805 GET_RADII;
806 REPORTER_ASSERT(reporter, dstUL.x() == origUL.y());
807 REPORTER_ASSERT(reporter, dstUL.y() == origUL.x());
808 REPORTER_ASSERT(reporter, dstUR.x() == origLL.y());
809 REPORTER_ASSERT(reporter, dstUR.y() == origLL.x());
810 REPORTER_ASSERT(reporter, dstLR.x() == origLR.y());
811 REPORTER_ASSERT(reporter, dstLR.y() == origLR.x());
812 REPORTER_ASSERT(reporter, dstLL.x() == origUR.y());
813 REPORTER_ASSERT(reporter, dstLL.y() == origUR.x());
814 }
815 // Width and height would get swapped.
816 REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
817 REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
818
819 // a-----b d-----c a-----d
820 // | | -> | | -> | |
821 // | | Flip Y | | Rotate 90 | |
822 // d-----c a-----b b-----c
823 // This is the same as rotate 90 and flip x.
824 matrix.reset();
825 matrix.setRotate(SkIntToScalar(90));
826 matrix.postScale(SkIntToScalar(1), SkIntToScalar(-1));
827 dst2.setEmpty();
828 success = orig.transform(matrix, &dst2);
829 REPORTER_ASSERT(reporter, success);
830 REPORTER_ASSERT(reporter, dst == dst2);
831
832 // a-----b b-----a a-----d
833 // | | -> | | -> | |
834 // | | Flip X | | Rotate 270 | |
835 // d-----c c-----d b-----c
836 matrix.reset();
837 matrix.setRotate(SkIntToScalar(270));
838 matrix.postScale(SkIntToScalar(-1), SkIntToScalar(1));
839 dst2.setEmpty();
840 success = orig.transform(matrix, &dst2);
841 REPORTER_ASSERT(reporter, success);
842 REPORTER_ASSERT(reporter, dst == dst2);
843
844 // a-----b b-----c a-----d
845 // | | -> | | -> | |
846 // | | Rotate 270 | | Flip Y | |
847 // d-----c a-----d b-----c
848 matrix.reset();
849 matrix.setScale(SkIntToScalar(1), SkIntToScalar(-1));
850 matrix.postRotate(SkIntToScalar(270));
851 dst2.setEmpty();
852 success = orig.transform(matrix, &dst2);
853 REPORTER_ASSERT(reporter, success);
854 REPORTER_ASSERT(reporter, dst == dst2);
855
856
857 // a-----b b-----a c-----d b-----c
858 // | | -> | | -> | | -> | |
859 // | | Flip X | | Flip Y | | Rotate 90 | |
860 // d-----c c-----d b-----a a-----d
861 //
862 // This is the same as rotation by 270.
863 matrix.reset();
864 matrix.setRotate(SkIntToScalar(90));
865 matrix.postScale(SkIntToScalar(-1), SkIntToScalar(-1));
866 dst.setEmpty();
867 success = orig.transform(matrix, &dst);
868 REPORTER_ASSERT(reporter, success);
869 {
870 GET_RADII;
871 // Radii have cycled clockwise and swapped their x and y axis.
872 REPORTER_ASSERT(reporter, dstUL.x() == origUR.y());
873 REPORTER_ASSERT(reporter, dstUL.y() == origUR.x());
874 REPORTER_ASSERT(reporter, dstUR.x() == origLR.y());
875 REPORTER_ASSERT(reporter, dstUR.y() == origLR.x());
876 REPORTER_ASSERT(reporter, dstLR.x() == origLL.y());
877 REPORTER_ASSERT(reporter, dstLR.y() == origLL.x());
878 REPORTER_ASSERT(reporter, dstLL.x() == origUL.y());
879 REPORTER_ASSERT(reporter, dstLL.y() == origUL.x());
880 }
881 // Width and height would get swapped.
882 REPORTER_ASSERT(reporter, orig.rect().width() == dst.rect().height());
883 REPORTER_ASSERT(reporter, orig.rect().height() == dst.rect().width());
884
885 // a-----b b-----c
886 // | | -> | |
887 // | | Rotate 270 | |
888 // d-----c a-----d
889 //
890 dst2.setEmpty();
891 matrix.reset();
892 matrix.setRotate(SkIntToScalar(270));
893 success = orig.transform(matrix, &dst2);
894 REPORTER_ASSERT(reporter, success);
895 REPORTER_ASSERT(reporter, dst == dst2);
896
897 // a-----b b-----a c-----d d-----a
898 // | | -> | | -> | | -> | |
899 // | | Flip X | | Flip Y | | Rotate 270 | |
900 // d-----c c-----d b-----a c-----b
901 //
902 // This is the same as rotation by 90 degrees.
903 matrix.reset();
904 matrix.setRotate(SkIntToScalar(270));
905 matrix.postScale(SkIntToScalar(-1), SkIntToScalar(-1));
906 dst.setEmpty();
907 success = orig.transform(matrix, &dst);
908 REPORTER_ASSERT(reporter, success);
909
910 matrix.reset();
911 matrix.setRotate(SkIntToScalar(90));
912 dst2.setEmpty();
913 success = orig.transform(matrix, &dst2);
914 REPORTER_ASSERT(reporter, dst == dst2);
915
916}
917
919 SkRRect rrect;
920 {
921 SkRect r = { 0, 0, kWidth, kHeight };
922 rrect.setRectXY(r, SkIntToScalar(4), SkIntToScalar(7));
924 }
925 {
926 SkRect r = { SkIntToScalar(5), SkIntToScalar(15),
927 SkIntToScalar(27), SkIntToScalar(34) };
928 SkVector radii[4] = { { 0, SkIntToScalar(1) },
929 { SkIntToScalar(2), SkIntToScalar(3) },
930 { SkIntToScalar(4), SkIntToScalar(5) },
931 { SkIntToScalar(6), SkIntToScalar(7) } };
932 rrect.setRectRadii(r, radii);
934 }
935}
936
937// Test out the case where an oval already off in space is translated/scaled
938// further off into space - yielding numerical issues when the rect & radii
939// are transformed separatly
940// BUG=skia:2696
942 SkRRect rrect;
943 SkRect r = { 28443.8594f, 53.1428604f, 28446.7148f, 56.0000038f };
944 rrect.setOval(r);
945
946 SkMatrix xform;
947 xform.setAll(2.44f, 0.0f, 485411.7f,
948 0.0f, 2.44f, -438.7f,
949 0.0f, 0.0f, 1.0f);
950 SkRRect dst;
951
952 bool success = rrect.transform(xform, &dst);
953 REPORTER_ASSERT(reporter, success);
954
955 SkScalar halfWidth = SkScalarHalf(dst.width());
956 SkScalar halfHeight = SkScalarHalf(dst.height());
957
958 for (int i = 0; i < 4; ++i) {
960 SkScalarNearlyEqual(dst.radii((SkRRect::Corner)i).fX, halfWidth));
962 SkScalarNearlyEqual(dst.radii((SkRRect::Corner)i).fY, halfHeight));
963 }
964}
965
966void test_read_rrect(skiatest::Reporter* reporter, const SkRRect& rrect, bool shouldEqualSrc) {
967 // It would be cleaner to call rrect.writeToMemory into a buffer. However, writeToMemory asserts
968 // that the rrect is valid and our caller may have fiddled with the internals of rrect to make
969 // it invalid.
970 const void* buffer = reinterpret_cast<const void*>(&rrect);
971 SkRRect deserialized;
972 size_t size = deserialized.readFromMemory(buffer, sizeof(SkRRect));
974 REPORTER_ASSERT(reporter, deserialized.isValid());
975 if (shouldEqualSrc) {
976 REPORTER_ASSERT(reporter, rrect == deserialized);
977 }
978}
979
981 static const SkRect kRect = {10.f, 10.f, 20.f, 20.f};
982 static const SkRect kNaNRect = {10.f, 10.f, 20.f, SK_ScalarNaN};
983 static const SkRect kInfRect = {10.f, 10.f, SK_ScalarInfinity, 20.f};
984 SkRRect rrect;
985
988 // These get coerced to empty.
991
992 rrect.setRect(kRect);
993 SkRect* innerRect = reinterpret_cast<SkRect*>(&rrect);
994 SkASSERT(*innerRect == kRect);
995 *innerRect = kInfRect;
996 test_read_rrect(reporter, rrect, false);
997 *innerRect = kNaNRect;
998 test_read_rrect(reporter, rrect, false);
999
1001 test_read_rrect(reporter, SkRRect::MakeOval(kInfRect), true);
1002 test_read_rrect(reporter, SkRRect::MakeOval(kNaNRect), true);
1003 rrect.setOval(kRect);
1004 *innerRect = kInfRect;
1005 test_read_rrect(reporter, rrect, false);
1006 *innerRect = kNaNRect;
1007 test_read_rrect(reporter, rrect, false);
1008
1010 // rrect should scale down the radii to make this legal
1012
1013 static const SkVector kRadii[4] = {{0.5f, 1.f}, {1.5f, 2.f}, {2.5f, 3.f}, {3.5f, 4.f}};
1014 rrect.setRectRadii(kRect, kRadii);
1015 test_read_rrect(reporter, rrect, true);
1016 SkScalar* innerRadius = reinterpret_cast<SkScalar*>(&rrect) + 6;
1017 SkASSERT(*innerRadius == 1.5f);
1018 *innerRadius = 400.f;
1019 test_read_rrect(reporter, rrect, false);
1020 *innerRadius = SK_ScalarInfinity;
1021 test_read_rrect(reporter, rrect, false);
1022 *innerRadius = SK_ScalarNaN;
1023 test_read_rrect(reporter, rrect, false);
1024 *innerRadius = -10.f;
1025 test_read_rrect(reporter, rrect, false);
1026}
1027
1029 // Because InnerBounds() insets the computed bounds slightly to correct for numerical inaccuracy
1030 // when finding the maximum inscribed point on a curve, we use a larger epsilon for comparing
1031 // expected areas.
1032 static constexpr SkScalar kEpsilon = 0.005f;
1033
1034 // Test that an empty rrect reports empty inner bounds
1036 // Test that a rect rrect reports itself as the inner bounds
1037 SkRect r = SkRect::MakeLTRB(0, 1, 2, 3);
1039 // Test that a circle rrect has an inner bounds area equal to 2*radius^2
1040 float radius = 5.f;
1042 2.f * radius)));
1044 2.f * radius * radius, kEpsilon));
1045
1046 float width = 20.f;
1047 float height = 25.f;
1049 // Test that a rrect with circular corners has an area equal to:
1050 float expectedArea =
1051 (2.f * radius * radius) + // area in the 4 circular corners
1052 (width-2.f*radius) * (height-2.f*radius) + // inner area excluding corners and edges
1053 SK_ScalarSqrt2 * radius * (width-2.f*radius) + // two horiz. rects between corners
1054 SK_ScalarSqrt2 * radius * (height-2.f*radius); // two vert. rects between corners
1055
1056 inner = SkRRectPriv::InnerBounds(SkRRect::MakeRectXY(r, radius, radius));
1058 expectedArea, kEpsilon));
1059
1060 // Test that a rrect with a small y radius but large x radius selects the horizontal interior
1061 SkRRect rr = SkRRect::MakeRectXY(r, 2.f * radius, 0.1f * radius);
1063 SkRect::MakeLTRB(0.f, 0.1f * radius, width, height - 0.1f * radius));
1064 // And vice versa with large y and small x radii
1065 rr = SkRRect::MakeRectXY(r, 0.1f * radius, 2.f * radius);
1067 SkRect::MakeLTRB(0.1f * radius, 0.f, width - 0.1f * radius, height));
1068
1069 // Test a variety of complex round rects produce a non-empty rect that is at least contained,
1070 // and larger than the inner area avoiding all corners.
1071 SkRandom rng;
1072 for (int i = 0; i < 1000; ++i) {
1073 float maxRadiusX = rng.nextRangeF(0.f, 40.f);
1074 float maxRadiusY = rng.nextRangeF(0.f, 40.f);
1075
1076 float innerWidth = rng.nextRangeF(0.f, 40.f);
1077 float innerHeight = rng.nextRangeF(0.f, 40.f);
1078
1079 SkVector radii[4] = {{rng.nextRangeF(0.f, maxRadiusX), rng.nextRangeF(0.f, maxRadiusY)},
1080 {rng.nextRangeF(0.f, maxRadiusX), rng.nextRangeF(0.f, maxRadiusY)},
1081 {rng.nextRangeF(0.f, maxRadiusX), rng.nextRangeF(0.f, maxRadiusY)},
1082 {rng.nextRangeF(0.f, maxRadiusX), rng.nextRangeF(0.f, maxRadiusY)}};
1083
1084 float maxLeft = std::max(radii[0].fX, radii[3].fX);
1085 float maxTop = std::max(radii[0].fY, radii[1].fY);
1086 float maxRight = std::max(radii[1].fX, radii[2].fX);
1087 float maxBottom = std::max(radii[2].fY, radii[3].fY);
1088
1089 SkRect outer = SkRect::MakeWH(maxLeft + maxRight + innerWidth,
1090 maxTop + maxBottom + innerHeight);
1091 rr.setRectRadii(outer, radii);
1092
1093 SkRect maxInner = SkRRectPriv::InnerBounds(rr);
1094 // Test upper limit on the size of 'maxInner'
1095 REPORTER_ASSERT(reporter, outer.contains(maxInner));
1096 REPORTER_ASSERT(reporter, rr.contains(maxInner));
1097
1098 // Test lower limit on the size of 'maxInner'
1099 inner = SkRect::MakeXYWH(maxLeft, maxTop, innerWidth, innerHeight);
1100 inner.inset(kEpsilon, kEpsilon);
1101
1102 if (inner.isSorted()) {
1103 REPORTER_ASSERT(reporter, maxInner.contains(inner));
1104 } else {
1105 // Flipped from the inset, just test two points of inner
1106 float midX = maxLeft + 0.5f * innerWidth;
1107 float midY = maxTop + 0.5f * innerHeight;
1108 REPORTER_ASSERT(reporter, maxInner.contains(midX, maxTop));
1109 REPORTER_ASSERT(reporter, maxInner.contains(midX, maxTop + innerHeight));
1110 REPORTER_ASSERT(reporter, maxInner.contains(maxLeft, midY));
1111 REPORTER_ASSERT(reporter, maxInner.contains(maxLeft + innerWidth, midY));
1112 }
1113 }
1114}
1115
1116namespace {
1117 // Helper to test expected intersection, relying on the fact that all round rect intersections
1118 // will have their bounds equal to the intersection of the bounds of the input round rects, and
1119 // their corner radii will be a one of A's, B's, or rectangular.
1120 enum CornerChoice : uint8_t {
1121 kA, kB, kRect
1122 };
1123
1124 static void verify_success(skiatest::Reporter* reporter, const SkRRect& a, const SkRRect& b,
1125 CornerChoice tl, CornerChoice tr, CornerChoice br, CornerChoice bl) {
1126 static const SkRRect kRect = SkRRect::MakeEmpty(); // has (0,0) for all corners
1127
1128 // Compute expected round rect intersection given bounds of A and B, and the specified
1129 // corner choices for the 4 corners.
1130 SkRect expectedBounds;
1131 SkAssertResult(expectedBounds.intersect(a.rect(), b.rect()));
1132
1133 SkVector radii[4] = {
1134 (tl == kA ? a : (tl == kB ? b : kRect)).radii(SkRRect::kUpperLeft_Corner),
1135 (tr == kA ? a : (tr == kB ? b : kRect)).radii(SkRRect::kUpperRight_Corner),
1136 (br == kA ? a : (br == kB ? b : kRect)).radii(SkRRect::kLowerRight_Corner),
1137 (bl == kA ? a : (bl == kB ? b : kRect)).radii(SkRRect::kLowerLeft_Corner)
1138 };
1139 SkRRect expected;
1140 expected.setRectRadii(expectedBounds, radii);
1141
1143 // Intersections are commutative so ba and ab should be the same
1145
1146 // Intersection of the result with either A or B should remain the intersection
1149
1150 // Bounds of intersection round rect should equal intersection of bounds of a and b
1151 REPORTER_ASSERT(reporter, actual.rect() == expectedBounds);
1152
1153 // Use PathOps to confirm that the explicit round rect is correct.
1154 SkPath aPath, bPath, expectedPath;
1155 aPath.addRRect(a);
1156 bPath.addRRect(b);
1157 SkAssertResult(Op(aPath, bPath, kIntersect_SkPathOp, &expectedPath));
1158
1159 // The isRRect() heuristics in SkPath are based on having called addRRect(), so a path from
1160 // path ops that is a rounded rectangle will return false. However, if test XOR expected is
1161 // empty, then we know that the shapes were the same.
1162 SkPath testPath;
1163 testPath.addRRect(actual);
1164
1165 SkPath empty;
1166 SkAssertResult(Op(testPath, expectedPath, kXOR_SkPathOp, &empty));
1167 REPORTER_ASSERT(reporter, empty.isEmpty());
1168 }
1169
1170 static void verify_failure(skiatest::Reporter* reporter, const SkRRect& a, const SkRRect& b) {
1172 // Expected the intersection to fail (no intersection or complex intersection is not
1173 // disambiguated).
1174 REPORTER_ASSERT(reporter, intersection.isEmpty());
1176 }
1177} // namespace
1178
1180 // Helper to inline making an inset round rect
1181 auto make_inset = [](const SkRRect& r, float dx, float dy) {
1182 SkRRect i = r;
1183 i.inset(dx, dy);
1184 return i;
1185 };
1186
1187 // A is a wide, short round rect
1188 SkRRect a = SkRRect::MakeRectXY({0.f, 4.f, 16.f, 12.f}, 2.f, 2.f);
1189 // B is a narrow, tall round rect
1190 SkRRect b = SkRRect::MakeRectXY({4.f, 0.f, 12.f, 16.f}, 3.f, 3.f);
1191 // NOTE: As positioned by default, A and B intersect as the rectangle {4, 4, 12, 12}.
1192 // There is a 2 px buffer between the corner curves of A and the vertical edges of B, and
1193 // a 1 px buffer between the corner curves of B and the horizontal edges of A. Since the shapes
1194 // form a symmetric rounded cross, we can easily test edge and corner combinations by simply
1195 // flipping signs and/or swapping x and y offsets.
1196
1197 // Successful intersection operations:
1198 // - for clarity these are formed by moving A around to intersect with B in different ways.
1199 // - the expected bounds of the round rect intersection is calculated automatically
1200 // in check_success, so all we have to specify are the expected corner radii
1201
1202 // A and B intersect as a rectangle
1203 verify_success(reporter, a, b, kRect, kRect, kRect, kRect);
1204 // Move A to intersect B on a vertical edge, preserving two corners of A inside B
1205 verify_success(reporter, a.makeOffset(6.f, 0.f), b, kA, kRect, kRect, kA);
1206 verify_success(reporter, a.makeOffset(-6.f, 0.f), b, kRect, kA, kA, kRect);
1207 // Move B to intersect A on a horizontal edge, preserving two corners of B inside A
1208 verify_success(reporter, a, b.makeOffset(0.f, 6.f), kB, kB, kRect, kRect);
1209 verify_success(reporter, a, b.makeOffset(0.f, -6.f), kRect, kRect, kB, kB);
1210 // Move A to intersect B on a corner, preserving one corner of A and one of B
1211 verify_success(reporter, a.makeOffset(-7.f, -8.f), b, kB, kRect, kA, kRect); // TL of B
1212 verify_success(reporter, a.makeOffset(7.f, -8.f), b, kRect, kB, kRect, kA); // TR of B
1213 verify_success(reporter, a.makeOffset(7.f, 8.f), b, kA, kRect, kB, kRect); // BR of B
1214 verify_success(reporter, a.makeOffset(-7.f, 8.f), b, kRect, kA, kRect, kB); // BL of B
1215 // An inset is contained inside the original (note that SkRRect::inset modifies radii too) so
1216 // is returned unmodified when intersected.
1217 verify_success(reporter, a, make_inset(a, 1.f, 1.f), kB, kB, kB, kB);
1218 verify_success(reporter, make_inset(b, 2.f, 2.f), b, kA, kA, kA, kA);
1219
1220 // A rectangle exactly matching the corners of the rrect bounds keeps the rrect radii,
1221 // regardless of whether or not it's the 1st or 2nd arg to ConservativeIntersect.
1222 SkRRect c = SkRRect::MakeRectXY({0.f, 0.f, 10.f, 10.f}, 2.f, 2.f);
1223 SkRRect cT = SkRRect::MakeRect({0.f, 0.f, 10.f, 5.f});
1224 verify_success(reporter, c, cT, kA, kA, kRect, kRect);
1225 verify_success(reporter, cT, c, kB, kB, kRect, kRect);
1226 SkRRect cB = SkRRect::MakeRect({0.f, 5.f, 10.f, 10.});
1227 verify_success(reporter, c, cB, kRect, kRect, kA, kA);
1228 verify_success(reporter, cB, c, kRect, kRect, kB, kB);
1229 SkRRect cL = SkRRect::MakeRect({0.f, 0.f, 5.f, 10.f});
1230 verify_success(reporter, c, cL, kA, kRect, kRect, kA);
1231 verify_success(reporter, cL, c, kB, kRect, kRect, kB);
1232 SkRRect cR = SkRRect::MakeRect({5.f, 0.f, 10.f, 10.f});
1233 verify_success(reporter, c, cR, kRect, kA, kA, kRect);
1234 verify_success(reporter, cR, c, kRect, kB, kB, kRect);
1235
1236 // Failed intersection operations:
1237
1238 // A and B's bounds do not intersect
1239 verify_failure(reporter, a.makeOffset(32.f, 0.f), b);
1240 // A and B's bounds intersect, but corner curves do not -> no intersection
1241 verify_failure(reporter, a.makeOffset(11.5f, -11.5f), b);
1242 // A is empty -> no intersection
1243 verify_failure(reporter, SkRRect::MakeEmpty(), b);
1244 // A is contained in B, but is too close to the corner curves for the conservative
1245 // approximations to construct a valid round rect intersection.
1246 verify_failure(reporter, make_inset(b, 0.3f, 0.3f), b);
1247 // A intersects a straight edge, but not far enough for B to contain A's corners
1248 verify_failure(reporter, a.makeOffset(2.5f, 0.f), b);
1249 verify_failure(reporter, a.makeOffset(-2.5f, 0.f), b);
1250 // And vice versa for B into A
1251 verify_failure(reporter, a, b.makeOffset(0.f, 1.5f));
1252 verify_failure(reporter, a, b.makeOffset(0.f, -1.5f));
1253 // A intersects a straight edge and part of B's corner
1254 verify_failure(reporter, a.makeOffset(5.f, -2.f), b);
1255 verify_failure(reporter, a.makeOffset(-5.f, -2.f), b);
1256 verify_failure(reporter, a.makeOffset(5.f, 2.f), b);
1257 verify_failure(reporter, a.makeOffset(-5.f, 2.f), b);
1258 // And vice versa
1259 verify_failure(reporter, a, b.makeOffset(3.f, -5.f));
1260 verify_failure(reporter, a, b.makeOffset(-3.f, -5.f));
1261 verify_failure(reporter, a, b.makeOffset(3.f, 5.f));
1262 verify_failure(reporter, a, b.makeOffset(-3.f, 5.f));
1263 // A intersects B on a corner, but the corner curves overlap each other
1264 verify_failure(reporter, a.makeOffset(8.f, 10.f), b);
1265 verify_failure(reporter, a.makeOffset(-8.f, 10.f), b);
1266 verify_failure(reporter, a.makeOffset(8.f, -10.f), b);
1267 verify_failure(reporter, a.makeOffset(-8.f, -10.f), b);
1268
1269 // Another variant of corners overlapping, this is two circles of radius r that overlap by r
1270 // pixels (e.g. the leftmost point of the right circle touches the center of the left circle).
1271 // The key difference with the above case is that the intersection of the circle bounds have
1272 // corners that are contained in both circles, but because it is only r wide, can not satisfy
1273 // all corners having radii = r.
1274 float r = 100.f;
1275 a = SkRRect::MakeOval(SkRect::MakeWH(2*r, 2*r));
1276 verify_failure(reporter, a, a.makeOffset(r, 0.f));
1277}
1278
1296
1297DEF_TEST(RRect_fuzzer_regressions, r) {
1298 {
1299 unsigned char buf[] = {
1300 0x0a, 0x00, 0x00, 0xff, 0x00, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x7f,
1301 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
1302 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f, 0x7f,
1303 0x7f, 0x7f, 0x7f, 0x02, 0x00, 0x00, 0x00, 0x00, 0x20, 0x00, 0x02, 0x00
1304 };
1305 REPORTER_ASSERT(r, sizeof(buf) == SkRRect{}.readFromMemory(buf, sizeof(buf)));
1306 }
1307
1308 {
1309 unsigned char buf[] = {
1310 0x5d, 0xff, 0xff, 0x5d, 0x0a, 0x60, 0x0a, 0x0a, 0x0a, 0x7e, 0x0a, 0x5a,
1311 0x0a, 0x12, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a,
1312 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x3a, 0x00, 0x00, 0x00, 0x0a,
1313 0x0a, 0x0a, 0x0a, 0x26, 0x0a, 0x0a, 0x0a, 0x0a, 0xff, 0xff, 0x0a, 0x0a
1314 };
1315 REPORTER_ASSERT(r, sizeof(buf) == SkRRect{}.readFromMemory(buf, sizeof(buf)));
1316 }
1317
1318 {
1319 unsigned char buf[] = {
1320 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0e, 0x04, 0xdd, 0xdd, 0x15,
1321 0xfe, 0x00, 0x00, 0x04, 0x05, 0x7e, 0x00, 0x00, 0x00, 0xff, 0x08, 0x04,
1322 0xff, 0xff, 0xfe, 0xfe, 0xff, 0x32, 0x32, 0x32, 0x32, 0x00, 0x32, 0x32,
1323 0x04, 0xdd, 0x3d, 0x1c, 0xfe, 0x89, 0x04, 0x0a, 0x0e, 0x05, 0x7e, 0x0a
1324 };
1325 REPORTER_ASSERT(r, sizeof(buf) == SkRRect{}.readFromMemory(buf, sizeof(buf)));
1326 }
1327}
reporter
static const double answers[][2]
static void test_round_rect_rects(skiatest::Reporter *reporter)
static void test_round_rect_transform(skiatest::Reporter *reporter)
static const SkScalar kHeight
static void test_round_rect_ovals(skiatest::Reporter *reporter)
static void test_9patch_rrect(skiatest::Reporter *reporter, const SkRect &rect, SkScalar l, SkScalar t, SkScalar r, SkScalar b, bool checkRadii)
static void test_tricky_radii(skiatest::Reporter *reporter)
static void test_inset(skiatest::Reporter *reporter)
static void test_round_rect_basic(skiatest::Reporter *reporter)
#define GET_RADII
static void test_round_rect_contains_rect(skiatest::Reporter *reporter)
static void test_issue_2696(skiatest::Reporter *reporter)
static const SkScalar kWidth
static void assert_transform_failure(skiatest::Reporter *reporter, const SkRRect &orig, const SkMatrix &matrix)
static void test_inner_bounds(skiatest::Reporter *reporter)
static void test_conservative_intersection(skiatest::Reporter *reporter)
static void test_direction(skiatest::Reporter *reporter, const SkRRect &rr, SkScalar initX, int stepX, SkScalar initY, int stepY, int numSteps, const bool *contains)
static void test_round_rect_iffy_parameters(skiatest::Reporter *reporter)
static void test_read(skiatest::Reporter *reporter)
static void test_empty_crbug_458524(skiatest::Reporter *reporter)
static void test_transform_helper(skiatest::Reporter *reporter, const SkRRect &orig)
void test_read_rrect(skiatest::Reporter *reporter, const SkRRect &rrect, bool shouldEqualSrc)
static void test_round_rect_general(skiatest::Reporter *reporter)
static void test_empty(skiatest::Reporter *reporter)
#define SkAssertResult(cond)
Definition SkAssert.h:123
#define SkASSERT(cond)
Definition SkAssert.h:116
static constexpr double kEpsilon
static bool contains(const SkRect &r, SkPoint p)
@ kIntersect_SkPathOp
intersect the two paths
Definition SkPathOps.h:24
@ kXOR_SkPathOp
exclusive-or the two paths
Definition SkPathOps.h:26
static bool SkScalarNearlyEqual(SkScalar x, SkScalar y, SkScalar tolerance=SK_ScalarNearlyZero)
Definition SkScalar.h:107
#define SK_Scalar1
Definition SkScalar.h:18
#define SK_ScalarNaN
Definition SkScalar.h:28
#define SkScalarHalf(a)
Definition SkScalar.h:75
#define SkIntToScalar(x)
Definition SkScalar.h:57
#define SK_ScalarSqrt2
Definition SkScalar.h:20
#define SK_ScalarInfinity
Definition SkScalar.h:26
#define DEF_TEST(name, reporter)
Definition Test.h:312
#define REPORTER_ASSERT(r, cond,...)
Definition Test.h:286
constexpr SkRect kRect
SkMatrix & setAll(SkScalar scaleX, SkScalar skewX, SkScalar transX, SkScalar skewY, SkScalar scaleY, SkScalar transY, SkScalar persp0, SkScalar persp1, SkScalar persp2)
Definition SkMatrix.h:562
static const SkMatrix & I()
SkPath & addRRect(const SkRRect &rrect, SkPathDirection dir=SkPathDirection::kCW)
Definition SkPath.cpp:990
static bool EqualsWithinTolerance(const SkPoint &p1, const SkPoint &p2)
Definition SkPointPriv.h:54
static SkRect InnerBounds(const SkRRect &rr)
Definition SkRRect.cpp:750
static SkRRect ConservativeIntersect(const SkRRect &a, const SkRRect &b)
Definition SkRRect.cpp:812
Type getType() const
Definition SkRRect.h:76
const SkRect & rect() const
Definition SkRRect.h:264
SkVector radii(Corner corner) const
Definition SkRRect.h:271
@ kOval_Type
non-zero width and height filled with radii
Definition SkRRect.h:69
@ kSimple_Type
non-zero width and height with equal radii
Definition SkRRect.h:70
@ kEmpty_Type
zero width or height
Definition SkRRect.h:67
@ kNinePatch_Type
non-zero width and height with axis-aligned radii
Definition SkRRect.h:71
@ kRect_Type
non-zero width and height, and zeroed radii
Definition SkRRect.h:68
@ kComplex_Type
non-zero width and height with arbitrary radii
Definition SkRRect.h:72
static SkRRect MakeOval(const SkRect &oval)
Definition SkRRect.h:162
void inset(SkScalar dx, SkScalar dy, SkRRect *dst) const
Definition SkRRect.cpp:562
void setEmpty()
Definition SkRRect.h:118
size_t readFromMemory(const void *buffer, size_t length)
Definition SkRRect.cpp:610
@ kUpperLeft_Corner
index of top-left corner radii
Definition SkRRect.h:252
@ kLowerRight_Corner
index of bottom-right corner radii
Definition SkRRect.h:254
@ kUpperRight_Corner
index of top-right corner radii
Definition SkRRect.h:253
@ kLowerLeft_Corner
index of bottom-left corner radii
Definition SkRRect.h:255
bool transform(const SkMatrix &matrix, SkRRect *dst) const
Definition SkRRect.cpp:436
static SkRRect MakeRect(const SkRect &r)
Definition SkRRect.h:149
void setOval(const SkRect &oval)
Definition SkRRect.cpp:30
static SkRRect MakeRectXY(const SkRect &rect, SkScalar xRad, SkScalar yRad)
Definition SkRRect.h:180
bool isRect() const
Definition SkRRect.h:84
void setRectRadii(const SkRect &rect, const SkVector radii[4])
Definition SkRRect.cpp:189
bool contains(const SkRect &rect) const
Definition SkRRect.cpp:360
Type type() const
Definition SkRRect.h:81
bool isEmpty() const
Definition SkRRect.h:83
void setRectXY(const SkRect &rect, SkScalar xRad, SkScalar yRad)
Definition SkRRect.cpp:52
SkScalar height() const
Definition SkRRect.h:102
void setNinePatch(const SkRect &rect, SkScalar leftRad, SkScalar topRad, SkScalar rightRad, SkScalar bottomRad)
Definition SkRRect.cpp:115
static constexpr size_t kSizeInMemory
Definition SkRRect.h:422
bool isSimple() const
Definition SkRRect.h:86
bool isValid() const
Definition SkRRect.cpp:663
void setRect(const SkRect &rect)
Definition SkRRect.h:126
static SkRRect MakeEmpty()
Definition SkRRect.h:142
float nextRangeF(float min, float max)
Definition SkRandom.h:64
float SkScalar
Definition extension.cpp:12
static bool b
struct MyStruct a[10]
EMSCRIPTEN_KEEPALIVE void empty()
static const uint8_t buffer[]
double y
double x
int32_t height
int32_t width
float fX
x-axis value
float fY
y-axis value
SkRect makeSorted() const
Definition SkRect.h:1330
SkScalar fBottom
larger y-axis bounds
Definition extension.cpp:17
void toQuad(SkPoint quad[4]) const
Definition SkRect.cpp:50
constexpr float left() const
Definition SkRect.h:734
void inset(float dx, float dy)
Definition SkRect.h:1060
constexpr float top() const
Definition SkRect.h:741
bool intersect(const SkRect &r)
Definition SkRect.cpp:114
SkScalar fLeft
smaller x-axis bounds
Definition extension.cpp:14
static constexpr SkRect MakeXYWH(float x, float y, float w, float h)
Definition SkRect.h:659
SkScalar fRight
larger x-axis bounds
Definition extension.cpp:16
bool contains(SkScalar x, SkScalar y) const
Definition extension.cpp:19
constexpr float height() const
Definition SkRect.h:769
constexpr float right() const
Definition SkRect.h:748
constexpr float width() const
Definition SkRect.h:762
static constexpr SkRect MakeWH(float w, float h)
Definition SkRect.h:609
static constexpr SkRect MakeLTRB(float l, float t, float r, float b)
Definition SkRect.h:646
bool isSorted() const
Definition SkRect.h:705
SkScalar fTop
smaller y-axis bounds
Definition extension.cpp:15