xref: /aosp_15_r20/external/skia/tests/RRectInPathTest.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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