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