1 /*
2 * Copyright 2015 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 "include/core/SkMatrix.h"
9 #include "include/core/SkPath.h"
10 #include "include/core/SkPathTypes.h"
11 #include "include/core/SkPoint.h"
12 #include "include/core/SkRRect.h"
13 #include "include/core/SkRect.h"
14 #include "include/core/SkRefCnt.h"
15 #include "include/core/SkScalar.h"
16 #include "include/core/SkTypes.h"
17 #include "include/private/SkPathRef.h"
18 #include "include/private/base/SkDebug.h"
19 #include "include/private/base/SkMalloc.h"
20 #include "src/base/SkFloatBits.h"
21 #include "src/core/SkPathPriv.h"
22 #include "tests/Test.h"
23
24 #include <cstdint>
25 #include <initializer_list>
26
path_contains_rrect(skiatest::Reporter * reporter,const SkPath & path,SkPathDirection * dir,unsigned * start)27 static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path,
28 SkPathDirection* dir, unsigned* start) {
29 SkRRect out;
30 REPORTER_ASSERT(reporter, SkPathPriv::IsRRect(path, &out, dir, start));
31 SkPath recreatedPath;
32 recreatedPath.addRRect(out, *dir, *start);
33 REPORTER_ASSERT(reporter, path == recreatedPath);
34 // Test that rotations/mirrors of the rrect path are still rrect paths and the returned
35 // parameters for the transformed paths are correct.
36 static const SkMatrix kMatrices[] = {
37 SkMatrix::Scale( 1, 1),
38 SkMatrix::Scale(-1, 1),
39 SkMatrix::Scale( 1, -1),
40 SkMatrix::Scale(-1, -1),
41 };
42 for (auto& m : kMatrices) {
43 SkPath xformed;
44 path.transform(m, &xformed);
45 SkRRect xrr = SkRRect::MakeRect(SkRect::MakeEmpty());
46 SkPathDirection xd = SkPathDirection::kCCW;
47 unsigned xs = ~0U;
48 REPORTER_ASSERT(reporter, SkPathPriv::IsRRect(xformed, &xrr, &xd, &xs));
49 recreatedPath.reset();
50 recreatedPath.addRRect(xrr, xd, xs);
51 REPORTER_ASSERT(reporter, recreatedPath == xformed);
52 }
53 return out;
54 }
55
inner_path_contains_rrect(skiatest::Reporter * reporter,const SkRRect & in,SkPathDirection dir,unsigned start)56 static SkRRect inner_path_contains_rrect(skiatest::Reporter* reporter, const SkRRect& in,
57 SkPathDirection dir, unsigned start) {
58 switch (in.getType()) {
59 case SkRRect::kEmpty_Type:
60 case SkRRect::kRect_Type:
61 case SkRRect::kOval_Type:
62 return in;
63 default:
64 break;
65 }
66 SkPath path;
67 path.addRRect(in, dir, start);
68 SkPathDirection outDir;
69 unsigned outStart;
70 SkRRect rrect = path_contains_rrect(reporter, path, &outDir, &outStart);
71 REPORTER_ASSERT(reporter, outDir == dir && outStart == start);
72 return rrect;
73 }
74
path_contains_rrect_check(skiatest::Reporter * reporter,const SkRRect & in,SkPathDirection dir,unsigned start)75 static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRRect& in,
76 SkPathDirection dir, unsigned start) {
77 SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
78 if (in != out) {
79 SkDebugf("%s", "");
80 }
81 REPORTER_ASSERT(reporter, in == out);
82 }
83
path_contains_rrect_nocheck(skiatest::Reporter * reporter,const SkRRect & in,SkPathDirection dir,unsigned start)84 static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in,
85 SkPathDirection dir, unsigned start) {
86 SkRRect out = inner_path_contains_rrect(reporter, in, dir, start);
87 if (in == out) {
88 SkDebugf("%s", "");
89 }
90 }
91
path_contains_rrect_check(skiatest::Reporter * reporter,const SkRect & r,SkVector v[4],SkPathDirection dir,unsigned start)92 static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r,
93 SkVector v[4], SkPathDirection dir, unsigned start) {
94 SkRRect rrect;
95 rrect.setRectRadii(r, v);
96 path_contains_rrect_check(reporter, rrect, dir, start);
97 }
98
99 class ForceIsRRect_Private {
100 public:
ForceIsRRect_Private(SkPath * path,SkPathDirection dir,unsigned start)101 ForceIsRRect_Private(SkPath* path, SkPathDirection dir, unsigned start) {
102 path->fPathRef->setIsRRect(dir == SkPathDirection::kCCW, start);
103 }
104 };
105
force_path_contains_rrect(skiatest::Reporter * reporter,SkPath & path,SkPathDirection dir,unsigned start)106 static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path,
107 SkPathDirection dir, unsigned start) {
108 ForceIsRRect_Private force_rrect(&path, dir, start);
109 SkPathDirection outDir;
110 unsigned outStart;
111 path_contains_rrect(reporter, path, &outDir, &outStart);
112 REPORTER_ASSERT(reporter, outDir == dir && outStart == start);
113 }
114
test_undetected_paths(skiatest::Reporter * reporter)115 static void test_undetected_paths(skiatest::Reporter* reporter) {
116 // We first get the exact conic weight used by SkPath for a circular arc. This
117 // allows our local, hand-crafted, artisanal round rect paths below to exactly match the
118 // factory made corporate paths produced by SkPath.
119 SkPath exactPath;
120 exactPath.addCircle(0, 0, 10);
121 REPORTER_ASSERT(reporter, SkPath::kMove_Verb == SkPathPriv::VerbData(exactPath)[0]);
122 REPORTER_ASSERT(reporter, SkPath::kConic_Verb == SkPathPriv::VerbData(exactPath)[1]);
123 const SkScalar weight = SkPathPriv::ConicWeightData(exactPath)[0];
124
125 SkPath path;
126 path.moveTo(0, 62.5f);
127 path.lineTo(0, 3.5f);
128 path.conicTo(0, 0, 3.5f, 0, weight);
129 path.lineTo(196.5f, 0);
130 path.conicTo(200, 0, 200, 3.5f, weight);
131 path.lineTo(200, 62.5f);
132 path.conicTo(200, 66, 196.5f, 66, weight);
133 path.lineTo(3.5f, 66);
134 path.conicTo(0, 66, 0, 62.5, weight);
135 path.close();
136 force_path_contains_rrect(reporter, path, SkPathDirection::kCW, 6);
137
138 path.reset();
139 path.moveTo(0, 81.5f);
140 path.lineTo(0, 3.5f);
141 path.conicTo(0, 0, 3.5f, 0, weight);
142 path.lineTo(149.5, 0);
143 path.conicTo(153, 0, 153, 3.5f, weight);
144 path.lineTo(153, 81.5f);
145 path.conicTo(153, 85, 149.5f, 85, weight);
146 path.lineTo(3.5f, 85);
147 path.conicTo(0, 85, 0, 81.5f, weight);
148 path.close();
149 force_path_contains_rrect(reporter, path, SkPathDirection::kCW, 6);
150
151 path.reset();
152 path.moveTo(14, 1189);
153 path.lineTo(14, 21);
154 path.conicTo(14, 14, 21, 14, weight);
155 path.lineTo(1363, 14);
156 path.conicTo(1370, 14, 1370, 21, weight);
157 path.lineTo(1370, 1189);
158 path.conicTo(1370, 1196, 1363, 1196, weight);
159 path.lineTo(21, 1196);
160 path.conicTo(14, 1196, 14, 1189, weight);
161 path.close();
162 force_path_contains_rrect(reporter, path, SkPathDirection::kCW, 6);
163
164 path.reset();
165 path.moveTo(14, 1743);
166 path.lineTo(14, 21);
167 path.conicTo(14, 14, 21, 14, weight);
168 path.lineTo(1363, 14);
169 path.conicTo(1370, 14, 1370, 21, weight);
170 path.lineTo(1370, 1743);
171 path.conicTo(1370, 1750, 1363, 1750, weight);
172 path.lineTo(21, 1750);
173 path.conicTo(14, 1750, 14, 1743, weight);
174 path.close();
175 force_path_contains_rrect(reporter, path, SkPathDirection::kCW, 6);
176 }
177
178 static const SkScalar kWidth = 100.0f;
179 static const SkScalar kHeight = 100.0f;
180
test_tricky_radii(skiatest::Reporter * reporter)181 static void test_tricky_radii(skiatest::Reporter* reporter) {
182 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
183 for (int start = 0; start < 8; ++start) {
184 {
185 // crbug.com/458522
186 SkRRect rr;
187 const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
188 const SkScalar rad = 12814;
189 const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } };
190 rr.setRectRadii(bounds, vec);
191 path_contains_rrect_check(reporter, rr, dir, start);
192 }
193
194 {
195 // crbug.com//463920
196 SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0);
197 SkVector radii[4] = {
198 { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f }
199 };
200 SkRRect rr;
201 rr.setRectRadii(r, radii);
202 path_contains_rrect_nocheck(reporter, rr, dir, start);
203 }
204 }
205 }
206 }
207
test_empty_crbug_458524(skiatest::Reporter * reporter)208 static void test_empty_crbug_458524(skiatest::Reporter* reporter) {
209 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
210 for (int start = 0; start < 8; ++start) {
211 SkRRect rr;
212 const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 };
213 const SkScalar rad = 40;
214 rr.setRectXY(bounds, rad, rad);
215 path_contains_rrect_check(reporter, rr, dir, start);
216
217 SkRRect other;
218 SkMatrix matrix;
219 matrix.setScale(0, 1);
220 rr.transform(matrix, &other);
221 path_contains_rrect_check(reporter, rr, dir, start);
222 }
223 }
224 }
225
test_inset(skiatest::Reporter * reporter)226 static void test_inset(skiatest::Reporter* reporter) {
227 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
228 for (int start = 0; start < 8; ++start) {
229 SkRRect rr, rr2;
230 SkRect r = { 0, 0, 100, 100 };
231
232 rr.setRect(r);
233 rr.inset(-20, -20, &rr2);
234 path_contains_rrect_check(reporter, rr, dir, start);
235
236 rr.inset(20, 20, &rr2);
237 path_contains_rrect_check(reporter, rr, dir, start);
238
239 rr.inset(r.width()/2, r.height()/2, &rr2);
240 path_contains_rrect_check(reporter, rr, dir, start);
241
242 rr.setRectXY(r, 20, 20);
243 rr.inset(19, 19, &rr2);
244 path_contains_rrect_check(reporter, rr, dir, start);
245 rr.inset(20, 20, &rr2);
246 path_contains_rrect_check(reporter, rr, dir, start);
247 }
248 }
249 }
250
251
test_9patch_rrect(skiatest::Reporter * reporter,const SkRect & rect,SkScalar l,SkScalar t,SkScalar r,SkScalar b,bool checkRadii)252 static void test_9patch_rrect(skiatest::Reporter* reporter,
253 const SkRect& rect,
254 SkScalar l, SkScalar t, SkScalar r, SkScalar b,
255 bool checkRadii) {
256 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
257 for (int start = 0; start < 8; ++start) {
258 SkRRect rr;
259 rr.setNinePatch(rect, l, t, r, b);
260 if (checkRadii) {
261 path_contains_rrect_check(reporter, rr, dir, start);
262 } else {
263 path_contains_rrect_nocheck(reporter, rr, dir, start);
264 }
265
266 SkRRect rr2; // construct the same RR using the most general set function
267 SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } };
268 rr2.setRectRadii(rect, radii);
269 if (checkRadii) {
270 path_contains_rrect_check(reporter, rr, dir, start);
271 } else {
272 path_contains_rrect_nocheck(reporter, rr, dir, start);
273 }
274 }
275 }
276 }
277
278 // Test out the basic API entry points
test_round_rect_basic(skiatest::Reporter * reporter)279 static void test_round_rect_basic(skiatest::Reporter* reporter) {
280 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
281 for (int start = 0; start < 8; ++start) {
282 //----
283 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
284
285 SkRRect rr1;
286 rr1.setRect(rect);
287 path_contains_rrect_check(reporter, rr1, dir, start);
288
289 SkRRect rr1_2; // construct the same RR using the most general set function
290 SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
291 rr1_2.setRectRadii(rect, rr1_2_radii);
292 path_contains_rrect_check(reporter, rr1_2, dir, start);
293 SkRRect rr1_3; // construct the same RR using the nine patch set function
294 rr1_3.setNinePatch(rect, 0, 0, 0, 0);
295 path_contains_rrect_check(reporter, rr1_2, dir, start);
296
297 //----
298 SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) };
299 SkRRect rr2;
300 rr2.setOval(rect);
301 path_contains_rrect_check(reporter, rr2, dir, start);
302
303 SkRRect rr2_2; // construct the same RR using the most general set function
304 SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY },
305 { halfPoint.fX, halfPoint.fY },
306 { halfPoint.fX, halfPoint.fY },
307 { halfPoint.fX, halfPoint.fY } };
308 rr2_2.setRectRadii(rect, rr2_2_radii);
309 path_contains_rrect_check(reporter, rr2_2, dir, start);
310 SkRRect rr2_3; // construct the same RR using the nine patch set function
311 rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY);
312 path_contains_rrect_check(reporter, rr2_3, dir, start);
313
314 //----
315 SkPoint p = { 5, 5 };
316 SkRRect rr3;
317 rr3.setRectXY(rect, p.fX, p.fY);
318 path_contains_rrect_check(reporter, rr3, dir, start);
319
320 SkRRect rr3_2; // construct the same RR using the most general set function
321 SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } };
322 rr3_2.setRectRadii(rect, rr3_2_radii);
323 path_contains_rrect_check(reporter, rr3_2, dir, start);
324 SkRRect rr3_3; // construct the same RR using the nine patch set function
325 rr3_3.setNinePatch(rect, 5, 5, 5, 5);
326 path_contains_rrect_check(reporter, rr3_3, dir, start);
327
328 //----
329 test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true);
330
331 {
332 // Test out the rrect from skia:3466
333 SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f,
334 0.806214333f);
335
336 test_9patch_rrect(reporter,
337 rect2,
338 0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f,
339 false);
340 }
341
342 //----
343 SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } };
344
345 SkRRect rr5;
346 rr5.setRectRadii(rect, radii2);
347 path_contains_rrect_check(reporter, rr5, dir, start);
348 }
349 }
350 }
351
352 // Test out the cases when the RR degenerates to a rect
test_round_rect_rects(skiatest::Reporter * reporter)353 static void test_round_rect_rects(skiatest::Reporter* reporter) {
354 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
355 for (int start = 0; start < 8; ++start) {
356 //----
357 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
358 SkRRect rr1;
359 rr1.setRectXY(rect, 0, 0);
360
361 path_contains_rrect_check(reporter, rr1, dir, start);
362
363 //----
364 SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } };
365
366 SkRRect rr2;
367 rr2.setRectRadii(rect, radii);
368
369 path_contains_rrect_check(reporter, rr2, dir, start);
370
371 //----
372 SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
373
374 SkRRect rr3;
375 rr3.setRectRadii(rect, radii2);
376 path_contains_rrect_check(reporter, rr3, dir, start);
377 }
378 }
379 }
380
381 // Test out the cases when the RR degenerates to an oval
test_round_rect_ovals(skiatest::Reporter * reporter)382 static void test_round_rect_ovals(skiatest::Reporter* reporter) {
383 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
384 for (int start = 0; start < 8; ++start) {
385 //----
386 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
387 SkRRect rr1;
388 rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight));
389
390 path_contains_rrect_check(reporter, rr1, dir, start);
391 }
392 }
393 }
394
395 // Test out the non-degenerate RR cases
test_round_rect_general(skiatest::Reporter * reporter)396 static void test_round_rect_general(skiatest::Reporter* reporter) {
397 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
398 for (int start = 0; start < 8; ++start) {
399 //----
400 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
401 SkRRect rr1;
402 rr1.setRectXY(rect, 20, 20);
403
404 path_contains_rrect_check(reporter, rr1, dir, start);
405
406 //----
407 SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } };
408
409 SkRRect rr2;
410 rr2.setRectRadii(rect, radii);
411
412 path_contains_rrect_check(reporter, rr2, dir, start);
413 }
414 }
415 }
416
test_round_rect_iffy_parameters(skiatest::Reporter * reporter)417 static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) {
418 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
419 for (int start = 0; start < 8; ++start) {
420 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight);
421 SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } };
422 SkRRect rr1;
423 rr1.setRectRadii(rect, radii);
424 path_contains_rrect_nocheck(reporter, rr1, dir, start);
425 }
426 }
427 }
428
set_radii(SkVector radii[4],int index,float rad)429 static void set_radii(SkVector radii[4], int index, float rad) {
430 sk_bzero(radii, sizeof(SkVector) * 4);
431 radii[index].set(rad, rad);
432 }
433
test_skbug_3239(skiatest::Reporter * reporter)434 static void test_skbug_3239(skiatest::Reporter* reporter) {
435 const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */
436 const float max = SkBits2Float(0x4b7f1c1d); /* 16718877.000000 */
437 const float big = SkBits2Float(0x4b7f1bd7); /* 16718807.000000 */
438
439 const float rad = 33436320;
440
441 const SkRect rectx = SkRect::MakeLTRB(min, min, max, big);
442 const SkRect recty = SkRect::MakeLTRB(min, min, big, max);
443
444 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
445 for (int start = 0; start < 8; ++start) {
446 SkVector radii[4];
447 for (int i = 0; i < 4; ++i) {
448 set_radii(radii, i, rad);
449 path_contains_rrect_check(reporter, rectx, radii, dir, start);
450 path_contains_rrect_check(reporter, recty, radii, dir, start);
451 }
452 }
453 }
454 }
455
test_mix(skiatest::Reporter * reporter)456 static void test_mix(skiatest::Reporter* reporter) {
457 for (auto dir : {SkPathDirection::kCW, SkPathDirection::kCCW}) {
458 for (int start = 0; start < 8; ++start) {
459 // Test out mixed degenerate and non-degenerate geometry with Conics
460 const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } };
461 SkRect r = SkRect::MakeWH(100, 100);
462 SkRRect rr;
463 rr.setRectRadii(r, radii);
464 path_contains_rrect_check(reporter, rr, dir, start);
465 }
466 }
467 }
468
DEF_TEST(RoundRectInPath,reporter)469 DEF_TEST(RoundRectInPath, reporter) {
470 test_tricky_radii(reporter);
471 test_empty_crbug_458524(reporter);
472 test_inset(reporter);
473 test_round_rect_basic(reporter);
474 test_round_rect_rects(reporter);
475 test_round_rect_ovals(reporter);
476 test_round_rect_general(reporter);
477 test_undetected_paths(reporter);
478 test_round_rect_iffy_parameters(reporter);
479 test_skbug_3239(reporter);
480 test_mix(reporter);
481 }
482
DEF_TEST(RRect_fragile,reporter)483 DEF_TEST(RRect_fragile, reporter) {
484 SkRect rect = {
485 SkBits2Float(0x1f800000), // 0x003F0000 was the starter value that also fails
486 SkBits2Float(0x1400001C),
487 SkBits2Float(0x3F000004),
488 SkBits2Float(0x3F000004),
489 };
490
491 SkPoint radii[] = {
492 { SkBits2Float(0x00000001), SkBits2Float(0x00000001) },
493 { SkBits2Float(0x00000020), SkBits2Float(0x00000001) },
494 { SkBits2Float(0x00000000), SkBits2Float(0x00000000) },
495 { SkBits2Float(0x3F000004), SkBits2Float(0x3F000004) },
496 };
497
498 SkRRect rr;
499 // please don't assert
500 if ((false)) { // disable until we fix this
501 SkDebugf("%g 0x%08X\n", rect.fLeft, SkFloat2Bits(rect.fLeft));
502 rr.setRectRadii(rect, radii);
503 }
504 }
505
506