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 "SkMatrix.h" 9#include "SkPath.h" 10#include "SkPathRef.h" 11#include "SkRRect.h" 12#include "Test.h" 13 14static SkRRect path_contains_rrect(skiatest::Reporter* reporter, const SkPath& path, 15 SkPath::Direction* dir, unsigned* start) { 16 SkRRect out; 17 REPORTER_ASSERT(reporter, path.isRRect(&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::MakeScale(1, 1), 25 SkMatrix::MakeScale(-1, 1), 26 SkMatrix::MakeScale(1, -1), 27 SkMatrix::MakeScale(-1, -1), 28 }; 29 for (auto& m : kMatrices) { 30 SkPath xformed; 31 path.transform(m, &xformed); 32 SkRRect xrr = SkRRect::MakeRect(SkRect::MakeEmpty()); 33 SkPath::Direction xd = SkPath::kCCW_Direction; 34 unsigned xs = ~0U; 35 REPORTER_ASSERT(reporter, xformed.isRRect(&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 SkPath::Direction 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 SkPath::Direction 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 SkPath::Direction dir, unsigned start) { 64 SkRRect out = inner_path_contains_rrect(reporter, in, dir, start); 65 if (in != out) { 66 SkDebugf(""); 67 } 68 REPORTER_ASSERT(reporter, in == out); 69} 70 71static void path_contains_rrect_nocheck(skiatest::Reporter* reporter, const SkRRect& in, 72 SkPath::Direction dir, unsigned start) { 73 SkRRect out = inner_path_contains_rrect(reporter, in, dir, start); 74 if (in == out) { 75 SkDebugf(""); 76 } 77} 78 79static void path_contains_rrect_check(skiatest::Reporter* reporter, const SkRect& r, 80 SkVector v[4], SkPath::Direction 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, SkPath::Direction dir, unsigned start) { 89 path->fPathRef->setIsRRect(true, dir == SkPath::kCCW_Direction, start); 90 } 91}; 92 93static void force_path_contains_rrect(skiatest::Reporter* reporter, SkPath& path, 94 SkPath::Direction dir, unsigned start) { 95 ForceIsRRect_Private force_rrect(&path, dir, start); 96 SkPath::Direction 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 use a dummy path to 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 dummyPath; 107 dummyPath.addCircle(0, 0, 10); 108 SkPath::RawIter iter(dummyPath); 109 SkPoint dummyPts[4]; 110 SkPath::Verb v = iter.next(dummyPts); 111 REPORTER_ASSERT(reporter, SkPath::kMove_Verb == v); 112 v = iter.next(dummyPts); 113 REPORTER_ASSERT(reporter, SkPath::kConic_Verb == v); 114 const SkScalar weight = iter.conicWeight(); 115 116 SkPath path; 117 path.moveTo(0, 62.5f); 118 path.lineTo(0, 3.5f); 119 path.conicTo(0, 0, 3.5f, 0, weight); 120 path.lineTo(196.5f, 0); 121 path.conicTo(200, 0, 200, 3.5f, weight); 122 path.lineTo(200, 62.5f); 123 path.conicTo(200, 66, 196.5f, 66, weight); 124 path.lineTo(3.5f, 66); 125 path.conicTo(0, 66, 0, 62.5, weight); 126 path.close(); 127 force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6); 128 129 path.reset(); 130 path.moveTo(0, 81.5f); 131 path.lineTo(0, 3.5f); 132 path.conicTo(0, 0, 3.5f, 0, weight); 133 path.lineTo(149.5, 0); 134 path.conicTo(153, 0, 153, 3.5f, weight); 135 path.lineTo(153, 81.5f); 136 path.conicTo(153, 85, 149.5f, 85, weight); 137 path.lineTo(3.5f, 85); 138 path.conicTo(0, 85, 0, 81.5f, weight); 139 path.close(); 140 force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6); 141 142 path.reset(); 143 path.moveTo(14, 1189); 144 path.lineTo(14, 21); 145 path.conicTo(14, 14, 21, 14, weight); 146 path.lineTo(1363, 14); 147 path.conicTo(1370, 14, 1370, 21, weight); 148 path.lineTo(1370, 1189); 149 path.conicTo(1370, 1196, 1363, 1196, weight); 150 path.lineTo(21, 1196); 151 path.conicTo(14, 1196, 14, 1189, weight); 152 path.close(); 153 force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6); 154 155 path.reset(); 156 path.moveTo(14, 1743); 157 path.lineTo(14, 21); 158 path.conicTo(14, 14, 21, 14, weight); 159 path.lineTo(1363, 14); 160 path.conicTo(1370, 14, 1370, 21, weight); 161 path.lineTo(1370, 1743); 162 path.conicTo(1370, 1750, 1363, 1750, weight); 163 path.lineTo(21, 1750); 164 path.conicTo(14, 1750, 14, 1743, weight); 165 path.close(); 166 force_path_contains_rrect(reporter, path, SkPath::kCW_Direction, 6); 167} 168 169static const SkScalar kWidth = 100.0f; 170static const SkScalar kHeight = 100.0f; 171 172static void test_tricky_radii(skiatest::Reporter* reporter) { 173 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 174 for (int start = 0; start < 8; ++start) { 175 { 176 // crbug.com/458522 177 SkRRect rr; 178 const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 }; 179 const SkScalar rad = 12814; 180 const SkVector vec[] = { { rad, rad }, { 0, rad }, { rad, rad }, { 0, rad } }; 181 rr.setRectRadii(bounds, vec); 182 path_contains_rrect_check(reporter, rr, dir, start); 183 } 184 185 { 186 // crbug.com//463920 187 SkRect r = SkRect::MakeLTRB(0, 0, 1009, 33554432.0); 188 SkVector radii[4] = { 189 { 13.0f, 8.0f }, { 170.0f, 2.0 }, { 256.0f, 33554432.0 }, { 110.0f, 5.0f } 190 }; 191 SkRRect rr; 192 rr.setRectRadii(r, radii); 193 path_contains_rrect_nocheck(reporter, rr, dir, start); 194 } 195 } 196 } 197} 198 199static void test_empty_crbug_458524(skiatest::Reporter* reporter) { 200 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 201 for (int start = 0; start < 8; ++start) { 202 SkRRect rr; 203 const SkRect bounds = { 3709, 3709, 3709 + 7402, 3709 + 29825 }; 204 const SkScalar rad = 40; 205 rr.setRectXY(bounds, rad, rad); 206 path_contains_rrect_check(reporter, rr, dir, start); 207 208 SkRRect other; 209 SkMatrix matrix; 210 matrix.setScale(0, 1); 211 rr.transform(matrix, &other); 212 path_contains_rrect_check(reporter, rr, dir, start); 213 } 214 } 215} 216 217static void test_inset(skiatest::Reporter* reporter) { 218 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 219 for (int start = 0; start < 8; ++start) { 220 SkRRect rr, rr2; 221 SkRect r = { 0, 0, 100, 100 }; 222 223 rr.setRect(r); 224 rr.inset(-20, -20, &rr2); 225 path_contains_rrect_check(reporter, rr, dir, start); 226 227 rr.inset(20, 20, &rr2); 228 path_contains_rrect_check(reporter, rr, dir, start); 229 230 rr.inset(r.width()/2, r.height()/2, &rr2); 231 path_contains_rrect_check(reporter, rr, dir, start); 232 233 rr.setRectXY(r, 20, 20); 234 rr.inset(19, 19, &rr2); 235 path_contains_rrect_check(reporter, rr, dir, start); 236 rr.inset(20, 20, &rr2); 237 path_contains_rrect_check(reporter, rr, dir, start); 238 } 239 } 240} 241 242 243static void test_9patch_rrect(skiatest::Reporter* reporter, 244 const SkRect& rect, 245 SkScalar l, SkScalar t, SkScalar r, SkScalar b, 246 bool checkRadii) { 247 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 248 for (int start = 0; start < 8; ++start) { 249 SkRRect rr; 250 rr.setNinePatch(rect, l, t, r, b); 251 if (checkRadii) { 252 path_contains_rrect_check(reporter, rr, dir, start); 253 } else { 254 path_contains_rrect_nocheck(reporter, rr, dir, start); 255 } 256 257 SkRRect rr2; // construct the same RR using the most general set function 258 SkVector radii[4] = { { l, t }, { r, t }, { r, b }, { l, b } }; 259 rr2.setRectRadii(rect, radii); 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 } 267} 268 269// Test out the basic API entry points 270static void test_round_rect_basic(skiatest::Reporter* reporter) { 271 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 272 for (int start = 0; start < 8; ++start) { 273 //---- 274 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); 275 276 SkRRect rr1; 277 rr1.setRect(rect); 278 path_contains_rrect_check(reporter, rr1, dir, start); 279 280 SkRRect rr1_2; // construct the same RR using the most general set function 281 SkVector rr1_2_radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; 282 rr1_2.setRectRadii(rect, rr1_2_radii); 283 path_contains_rrect_check(reporter, rr1_2, dir, start); 284 SkRRect rr1_3; // construct the same RR using the nine patch set function 285 rr1_3.setNinePatch(rect, 0, 0, 0, 0); 286 path_contains_rrect_check(reporter, rr1_2, dir, start); 287 288 //---- 289 SkPoint halfPoint = { SkScalarHalf(kWidth), SkScalarHalf(kHeight) }; 290 SkRRect rr2; 291 rr2.setOval(rect); 292 path_contains_rrect_check(reporter, rr2, dir, start); 293 294 SkRRect rr2_2; // construct the same RR using the most general set function 295 SkVector rr2_2_radii[4] = { { halfPoint.fX, halfPoint.fY }, 296 { halfPoint.fX, halfPoint.fY }, 297 { halfPoint.fX, halfPoint.fY }, 298 { halfPoint.fX, halfPoint.fY } }; 299 rr2_2.setRectRadii(rect, rr2_2_radii); 300 path_contains_rrect_check(reporter, rr2_2, dir, start); 301 SkRRect rr2_3; // construct the same RR using the nine patch set function 302 rr2_3.setNinePatch(rect, halfPoint.fX, halfPoint.fY, halfPoint.fX, halfPoint.fY); 303 path_contains_rrect_check(reporter, rr2_3, dir, start); 304 305 //---- 306 SkPoint p = { 5, 5 }; 307 SkRRect rr3; 308 rr3.setRectXY(rect, p.fX, p.fY); 309 path_contains_rrect_check(reporter, rr3, dir, start); 310 311 SkRRect rr3_2; // construct the same RR using the most general set function 312 SkVector rr3_2_radii[4] = { { 5, 5 }, { 5, 5 }, { 5, 5 }, { 5, 5 } }; 313 rr3_2.setRectRadii(rect, rr3_2_radii); 314 path_contains_rrect_check(reporter, rr3_2, dir, start); 315 SkRRect rr3_3; // construct the same RR using the nine patch set function 316 rr3_3.setNinePatch(rect, 5, 5, 5, 5); 317 path_contains_rrect_check(reporter, rr3_3, dir, start); 318 319 //---- 320 test_9patch_rrect(reporter, rect, 10, 9, 8, 7, true); 321 322 { 323 // Test out the rrect from skia:3466 324 SkRect rect2 = SkRect::MakeLTRB(0.358211994f, 0.755430222f, 0.872866154f, 325 0.806214333f); 326 327 test_9patch_rrect(reporter, 328 rect2, 329 0.926942348f, 0.642850280f, 0.529063463f, 0.587844372f, 330 false); 331 } 332 333 //---- 334 SkPoint radii2[4] = { { 0, 0 }, { 0, 0 }, { 50, 50 }, { 20, 50 } }; 335 336 SkRRect rr5; 337 rr5.setRectRadii(rect, radii2); 338 path_contains_rrect_check(reporter, rr5, dir, start); 339 } 340 } 341} 342 343// Test out the cases when the RR degenerates to a rect 344static void test_round_rect_rects(skiatest::Reporter* reporter) { 345 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 346 for (int start = 0; start < 8; ++start) { 347 //---- 348 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); 349 SkRRect rr1; 350 rr1.setRectXY(rect, 0, 0); 351 352 path_contains_rrect_check(reporter, rr1, dir, start); 353 354 //---- 355 SkPoint radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 } }; 356 357 SkRRect rr2; 358 rr2.setRectRadii(rect, radii); 359 360 path_contains_rrect_check(reporter, rr2, dir, start); 361 362 //---- 363 SkPoint radii2[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } }; 364 365 SkRRect rr3; 366 rr3.setRectRadii(rect, radii2); 367 path_contains_rrect_check(reporter, rr3, dir, start); 368 } 369 } 370} 371 372// Test out the cases when the RR degenerates to an oval 373static void test_round_rect_ovals(skiatest::Reporter* reporter) { 374 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 375 for (int start = 0; start < 8; ++start) { 376 //---- 377 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); 378 SkRRect rr1; 379 rr1.setRectXY(rect, SkScalarHalf(kWidth), SkScalarHalf(kHeight)); 380 381 path_contains_rrect_check(reporter, rr1, dir, start); 382 } 383 } 384} 385 386// Test out the non-degenerate RR cases 387static void test_round_rect_general(skiatest::Reporter* reporter) { 388 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 389 for (int start = 0; start < 8; ++start) { 390 //---- 391 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); 392 SkRRect rr1; 393 rr1.setRectXY(rect, 20, 20); 394 395 path_contains_rrect_check(reporter, rr1, dir, start); 396 397 //---- 398 SkPoint radii[4] = { { 0, 0 }, { 20, 20 }, { 50, 50 }, { 20, 50 } }; 399 400 SkRRect rr2; 401 rr2.setRectRadii(rect, radii); 402 403 path_contains_rrect_check(reporter, rr2, dir, start); 404 } 405 } 406} 407 408static void test_round_rect_iffy_parameters(skiatest::Reporter* reporter) { 409 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 410 for (int start = 0; start < 8; ++start) { 411 SkRect rect = SkRect::MakeLTRB(0, 0, kWidth, kHeight); 412 SkPoint radii[4] = { { 50, 100 }, { 100, 50 }, { 50, 100 }, { 100, 50 } }; 413 SkRRect rr1; 414 rr1.setRectRadii(rect, radii); 415 path_contains_rrect_nocheck(reporter, rr1, dir, start); 416 } 417 } 418} 419 420static void set_radii(SkVector radii[4], int index, float rad) { 421 sk_bzero(radii, sizeof(SkVector) * 4); 422 radii[index].set(rad, rad); 423} 424 425static void test_skbug_3239(skiatest::Reporter* reporter) { 426 const float min = SkBits2Float(0xcb7f16c8); /* -16717512.000000 */ 427 const float max = SkBits2Float(0x4b7f1c1d); /* 16718877.000000 */ 428 const float big = SkBits2Float(0x4b7f1bd7); /* 16718807.000000 */ 429 430 const float rad = 33436320; 431 432 const SkRect rectx = SkRect::MakeLTRB(min, min, max, big); 433 const SkRect recty = SkRect::MakeLTRB(min, min, big, max); 434 435 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 436 for (int start = 0; start < 8; ++start) { 437 SkVector radii[4]; 438 for (int i = 0; i < 4; ++i) { 439 set_radii(radii, i, rad); 440 path_contains_rrect_check(reporter, rectx, radii, dir, start); 441 path_contains_rrect_check(reporter, recty, radii, dir, start); 442 } 443 } 444 } 445} 446 447static void test_mix(skiatest::Reporter* reporter) { 448 for (auto dir : {SkPath::kCW_Direction, SkPath::kCCW_Direction}) { 449 for (int start = 0; start < 8; ++start) { 450 // Test out mixed degenerate and non-degenerate geometry with Conics 451 const SkVector radii[4] = { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 100, 100 } }; 452 SkRect r = SkRect::MakeWH(100, 100); 453 SkRRect rr; 454 rr.setRectRadii(r, radii); 455 path_contains_rrect_check(reporter, rr, dir, start); 456 } 457 } 458} 459 460DEF_TEST(RoundRectInPath, reporter) { 461 test_tricky_radii(reporter); 462 test_empty_crbug_458524(reporter); 463 test_inset(reporter); 464 test_round_rect_basic(reporter); 465 test_round_rect_rects(reporter); 466 test_round_rect_ovals(reporter); 467 test_round_rect_general(reporter); 468 test_undetected_paths(reporter); 469 test_round_rect_iffy_parameters(reporter); 470 test_skbug_3239(reporter); 471 test_mix(reporter); 472} 473