RRectInPathTest.cpp 18 KB

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