convex_all_line_paths.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  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 "gm/gm.h"
  8. #include "include/core/SkCanvas.h"
  9. #include "include/core/SkColor.h"
  10. #include "include/core/SkMatrix.h"
  11. #include "include/core/SkPaint.h"
  12. #include "include/core/SkPath.h"
  13. #include "include/core/SkPoint.h"
  14. #include "include/core/SkRect.h"
  15. #include "include/core/SkScalar.h"
  16. #include "include/core/SkSize.h"
  17. #include "include/core/SkString.h"
  18. #include "include/core/SkTypes.h"
  19. #include "src/core/SkPathPriv.h"
  20. #include <memory>
  21. static void create_ngon(int n, SkPoint* pts, SkScalar width, SkScalar height) {
  22. float angleStep = 360.0f / n, angle = 0.0f;
  23. if ((n % 2) == 1) {
  24. angle = angleStep/2.0f;
  25. }
  26. for (int i = 0; i < n; ++i) {
  27. pts[i].fX = -SkScalarSin(SkDegreesToRadians(angle)) * width;
  28. pts[i].fY = SkScalarCos(SkDegreesToRadians(angle)) * height;
  29. angle += angleStep;
  30. }
  31. }
  32. namespace ConvexLineOnlyData {
  33. // narrow rect
  34. const SkPoint gPoints0[] = {
  35. { -1.5f, -50.0f },
  36. { 1.5f, -50.0f },
  37. { 1.5f, 50.0f },
  38. { -1.5f, 50.0f }
  39. };
  40. // narrow rect on an angle
  41. const SkPoint gPoints1[] = {
  42. { -50.0f, -49.0f },
  43. { -49.0f, -50.0f },
  44. { 50.0f, 49.0f },
  45. { 49.0f, 50.0f }
  46. };
  47. // trap - narrow on top - wide on bottom
  48. const SkPoint gPoints2[] = {
  49. { -10.0f, -50.0f },
  50. { 10.0f, -50.0f },
  51. { 50.0f, 50.0f },
  52. { -50.0f, 50.0f }
  53. };
  54. // wide skewed rect
  55. const SkPoint gPoints3[] = {
  56. { -50.0f, -50.0f },
  57. { 0.0f, -50.0f },
  58. { 50.0f, 50.0f },
  59. { 0.0f, 50.0f }
  60. };
  61. // thin rect with colinear-ish lines
  62. const SkPoint gPoints4[] = {
  63. { -6.0f, -50.0f },
  64. { 4.0f, -50.0f },
  65. { 5.0f, -25.0f }, // remove if collinear diagonal points are not concave
  66. { 6.0f, 0.0f },
  67. { 5.0f, 25.0f }, // remove if collinear diagonal points are not concave
  68. { 4.0f, 50.0f },
  69. { -4.0f, 50.0f }
  70. };
  71. // degenerate
  72. const SkPoint gPoints5[] = {
  73. { -0.025f, -0.025f },
  74. { 0.025f, -0.025f },
  75. { 0.025f, 0.025f },
  76. { -0.025f, 0.025f }
  77. };
  78. // Triangle in which the first point should fuse with last
  79. const SkPoint gPoints6[] = {
  80. { -20.0f, -13.0f },
  81. { -20.0f, -13.05f },
  82. { 20.0f, -13.0f },
  83. { 20.0f, 27.0f }
  84. };
  85. // thin rect with colinear lines
  86. const SkPoint gPoints7[] = {
  87. { -10.0f, -50.0f },
  88. { 10.0f, -50.0f },
  89. { 10.0f, -25.0f },
  90. { 10.0f, 0.0f },
  91. { 10.0f, 25.0f },
  92. { 10.0f, 50.0f },
  93. { -10.0f, 50.0f }
  94. };
  95. // capped teardrop
  96. const SkPoint gPoints8[] = {
  97. { 50.00f, 50.00f },
  98. { 0.00f, 50.00f },
  99. { -15.45f, 47.55f },
  100. { -29.39f, 40.45f },
  101. { -40.45f, 29.39f },
  102. { -47.55f, 15.45f },
  103. { -50.00f, 0.00f },
  104. { -47.55f, -15.45f },
  105. { -40.45f, -29.39f },
  106. { -29.39f, -40.45f },
  107. { -15.45f, -47.55f },
  108. { 0.00f, -50.00f },
  109. { 50.00f, -50.00f }
  110. };
  111. // teardrop
  112. const SkPoint gPoints9[] = {
  113. { 4.39f, 40.45f },
  114. { -9.55f, 47.55f },
  115. { -25.00f, 50.00f },
  116. { -40.45f, 47.55f },
  117. { -54.39f, 40.45f },
  118. { -65.45f, 29.39f },
  119. { -72.55f, 15.45f },
  120. { -75.00f, 0.00f },
  121. { -72.55f, -15.45f },
  122. { -65.45f, -29.39f },
  123. { -54.39f, -40.45f },
  124. { -40.45f, -47.55f },
  125. { -25.0f, -50.0f },
  126. { -9.55f, -47.55f },
  127. { 4.39f, -40.45f },
  128. { 75.00f, 0.00f }
  129. };
  130. // clipped triangle
  131. const SkPoint gPoints10[] = {
  132. { -10.0f, -50.0f },
  133. { 10.0f, -50.0f },
  134. { 50.0f, 31.0f },
  135. { 40.0f, 50.0f },
  136. { -40.0f, 50.0f },
  137. { -50.0f, 31.0f },
  138. };
  139. const SkPoint* gPoints[] = {
  140. gPoints0, gPoints1, gPoints2, gPoints3, gPoints4, gPoints5, gPoints6,
  141. gPoints7, gPoints8, gPoints9, gPoints10,
  142. };
  143. const size_t gSizes[] = {
  144. SK_ARRAY_COUNT(gPoints0),
  145. SK_ARRAY_COUNT(gPoints1),
  146. SK_ARRAY_COUNT(gPoints2),
  147. SK_ARRAY_COUNT(gPoints3),
  148. SK_ARRAY_COUNT(gPoints4),
  149. SK_ARRAY_COUNT(gPoints5),
  150. SK_ARRAY_COUNT(gPoints6),
  151. SK_ARRAY_COUNT(gPoints7),
  152. SK_ARRAY_COUNT(gPoints8),
  153. SK_ARRAY_COUNT(gPoints9),
  154. SK_ARRAY_COUNT(gPoints10),
  155. };
  156. static_assert(SK_ARRAY_COUNT(gSizes) == SK_ARRAY_COUNT(gPoints), "array_mismatch");
  157. }
  158. namespace skiagm {
  159. // This GM is intended to exercise Ganesh's handling of convex line-only
  160. // paths
  161. class ConvexLineOnlyPathsGM : public GM {
  162. public:
  163. ConvexLineOnlyPathsGM(bool doStrokeAndFill) : fDoStrokeAndFill(doStrokeAndFill) {
  164. this->setBGColor(0xFFFFFFFF);
  165. }
  166. protected:
  167. SkString onShortName() override {
  168. if (fDoStrokeAndFill) {
  169. return SkString("convex-lineonly-paths-stroke-and-fill");
  170. }
  171. return SkString("convex-lineonly-paths");
  172. }
  173. SkISize onISize() override { return SkISize::Make(kGMWidth, kGMHeight); }
  174. bool runAsBench() const override { return true; }
  175. static SkPath GetPath(int index, SkPath::Direction dir) {
  176. std::unique_ptr<SkPoint[]> data(nullptr);
  177. const SkPoint* points;
  178. int numPts;
  179. if (index < (int) SK_ARRAY_COUNT(ConvexLineOnlyData::gPoints)) {
  180. // manually specified
  181. points = ConvexLineOnlyData::gPoints[index];
  182. numPts = (int)ConvexLineOnlyData::gSizes[index];
  183. } else {
  184. // procedurally generated
  185. SkScalar width = kMaxPathHeight/2;
  186. SkScalar height = kMaxPathHeight/2;
  187. switch (index-SK_ARRAY_COUNT(ConvexLineOnlyData::gPoints)) {
  188. case 0:
  189. numPts = 3;
  190. break;
  191. case 1:
  192. numPts = 4;
  193. break;
  194. case 2:
  195. numPts = 5;
  196. break;
  197. case 3: // squashed pentagon
  198. numPts = 5;
  199. width = kMaxPathHeight/5;
  200. break;
  201. case 4:
  202. numPts = 6;
  203. break;
  204. case 5:
  205. numPts = 8;
  206. break;
  207. case 6: // squashed octogon
  208. numPts = 8;
  209. width = kMaxPathHeight/5;
  210. break;
  211. case 7:
  212. numPts = 20;
  213. break;
  214. case 8:
  215. numPts = 100;
  216. break;
  217. default:
  218. numPts = 3;
  219. break;
  220. }
  221. data.reset(new SkPoint[numPts]);
  222. create_ngon(numPts, data.get(), width, height);
  223. points = data.get();
  224. }
  225. SkPath path;
  226. if (SkPath::kCW_Direction == dir) {
  227. path.moveTo(points[0]);
  228. for (int i = 1; i < numPts; ++i) {
  229. path.lineTo(points[i]);
  230. }
  231. } else {
  232. path.moveTo(points[numPts-1]);
  233. for (int i = numPts-2; i >= 0; --i) {
  234. path.lineTo(points[i]);
  235. }
  236. }
  237. path.close();
  238. #ifdef SK_DEBUG
  239. // Each path this method returns should be convex, only composed of
  240. // lines, wound the right direction, and short enough to fit in one
  241. // of the GMs rows.
  242. SkASSERT(path.isConvex());
  243. SkASSERT(SkPath::kLine_SegmentMask == path.getSegmentMasks());
  244. SkPathPriv::FirstDirection actualDir;
  245. SkASSERT(SkPathPriv::CheapComputeFirstDirection(path, &actualDir));
  246. SkASSERT(SkPathPriv::AsFirstDirection(dir) == actualDir);
  247. SkRect bounds = path.getBounds();
  248. SkASSERT(SkScalarNearlyEqual(bounds.centerX(), 0.0f));
  249. SkASSERT(bounds.height() <= kMaxPathHeight);
  250. #endif
  251. return path;
  252. }
  253. // Draw a single path several times, shrinking it, flipping its direction
  254. // and changing its start vertex each time.
  255. void drawPath(SkCanvas* canvas, int index, SkPoint* offset) {
  256. SkPoint center;
  257. {
  258. SkPath path = GetPath(index, SkPath::kCW_Direction);
  259. if (offset->fX+path.getBounds().width() > kGMWidth) {
  260. offset->fX = 0;
  261. offset->fY += kMaxPathHeight;
  262. if (fDoStrokeAndFill) {
  263. offset->fX += kStrokeWidth / 2.0f;
  264. offset->fY += kStrokeWidth / 2.0f;
  265. }
  266. }
  267. center = { offset->fX + SkScalarHalf(path.getBounds().width()), offset->fY};
  268. offset->fX += path.getBounds().width();
  269. if (fDoStrokeAndFill) {
  270. offset->fX += kStrokeWidth;
  271. }
  272. }
  273. const SkColor colors[2] = { SK_ColorBLACK, SK_ColorWHITE };
  274. const SkPath::Direction dirs[2] = { SkPath::kCW_Direction, SkPath::kCCW_Direction };
  275. const float scales[] = { 1.0f, 0.75f, 0.5f, 0.25f, 0.1f, 0.01f, 0.001f };
  276. const SkPaint::Join joins[3] = { SkPaint::kRound_Join,
  277. SkPaint::kBevel_Join,
  278. SkPaint::kMiter_Join };
  279. SkPaint paint;
  280. paint.setAntiAlias(true);
  281. for (size_t i = 0; i < SK_ARRAY_COUNT(scales); ++i) {
  282. SkPath path = GetPath(index, dirs[i%2]);
  283. if (fDoStrokeAndFill) {
  284. paint.setStyle(SkPaint::kStrokeAndFill_Style);
  285. paint.setStrokeJoin(joins[i%3]);
  286. paint.setStrokeWidth(SkIntToScalar(kStrokeWidth));
  287. }
  288. canvas->save();
  289. canvas->translate(center.fX, center.fY);
  290. canvas->scale(scales[i], scales[i]);
  291. paint.setColor(colors[i%2]);
  292. canvas->drawPath(path, paint);
  293. canvas->restore();
  294. }
  295. }
  296. void onDraw(SkCanvas* canvas) override {
  297. // the right edge of the last drawn path
  298. SkPoint offset = { 0, SkScalarHalf(kMaxPathHeight) };
  299. if (fDoStrokeAndFill) {
  300. offset.fX += kStrokeWidth / 2.0f;
  301. offset.fY += kStrokeWidth / 2.0f;
  302. }
  303. for (int i = 0; i < kNumPaths; ++i) {
  304. this->drawPath(canvas, i, &offset);
  305. }
  306. {
  307. // Repro for crbug.com/472723 (Missing AA on portions of graphic with GPU rasterization)
  308. SkPaint p;
  309. p.setAntiAlias(true);
  310. if (fDoStrokeAndFill) {
  311. p.setStyle(SkPaint::kStrokeAndFill_Style);
  312. p.setStrokeJoin(SkPaint::kMiter_Join);
  313. p.setStrokeWidth(SkIntToScalar(kStrokeWidth));
  314. }
  315. SkPath p1;
  316. p1.moveTo(60.8522949f, 364.671021f);
  317. p1.lineTo(59.4380493f, 364.671021f);
  318. p1.lineTo(385.414276f, 690.647217f);
  319. p1.lineTo(386.121399f, 689.940125f);
  320. canvas->save();
  321. canvas->translate(356.0f, 50.0f);
  322. canvas->drawPath(p1, p);
  323. canvas->restore();
  324. // Repro for crbug.com/869172 (SVG path incorrectly simplified when using GPU
  325. // Rasterization). This will only draw anything in the stroke-and-fill version.
  326. SkPath p2;
  327. p2.moveTo(10.f, 0.f);
  328. p2.lineTo(38.f, 0.f);
  329. p2.lineTo(66.f, 0.f);
  330. p2.lineTo(94.f, 0.f);
  331. p2.lineTo(122.f, 0.f);
  332. p2.lineTo(150.f, 0.f);
  333. p2.lineTo(150.f, 0.f);
  334. p2.lineTo(122.f, 0.f);
  335. p2.lineTo(94.f, 0.f);
  336. p2.lineTo(66.f, 0.f);
  337. p2.lineTo(38.f, 0.f);
  338. p2.lineTo(10.f, 0.f);
  339. p2.close();
  340. canvas->save();
  341. canvas->translate(0.0f, 500.0f);
  342. canvas->drawPath(p2, p);
  343. canvas->restore();
  344. // Repro for crbug.com/856137. This path previously caused GrAAConvexTessellator to turn
  345. // inset rings into outsets when adjacent bisector angles converged outside the previous
  346. // ring due to accumulated error.
  347. SkPath p3;
  348. p3.setFillType(SkPath::kEvenOdd_FillType);
  349. p3.moveTo(1184.96f, 982.557f);
  350. p3.lineTo(1183.71f, 982.865f);
  351. p3.lineTo(1180.99f, 982.734f);
  352. p3.lineTo(1178.5f, 981.541f);
  353. p3.lineTo(1176.35f, 979.367f);
  354. p3.lineTo(1178.94f, 938.854f);
  355. p3.lineTo(1181.35f, 936.038f);
  356. p3.lineTo(1183.96f, 934.117f);
  357. p3.lineTo(1186.67f, 933.195f);
  358. p3.lineTo(1189.36f, 933.342f);
  359. p3.lineTo(1191.58f, 934.38f);
  360. p3.close();
  361. canvas->save();
  362. SkMatrix m;
  363. m.setAll(0.0893210843f, 0, 79.1197586f, 0, 0.0893210843f, 300, 0, 0, 1);
  364. canvas->concat(m);
  365. canvas->drawPath(p3, p);
  366. canvas->restore();
  367. }
  368. }
  369. private:
  370. static constexpr int kStrokeWidth = 10;
  371. static constexpr int kNumPaths = 20;
  372. static constexpr int kMaxPathHeight = 100;
  373. static constexpr int kGMWidth = 512;
  374. static constexpr int kGMHeight = 512;
  375. bool fDoStrokeAndFill;
  376. typedef GM INHERITED;
  377. };
  378. //////////////////////////////////////////////////////////////////////////////
  379. DEF_GM(return new ConvexLineOnlyPathsGM(false);)
  380. DEF_GM(return new ConvexLineOnlyPathsGM(true);)
  381. }