SampleDegenerateQuads.cpp 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. /*
  2. * Copyright 2019 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 "samplecode/Sample.h"
  8. #include "src/gpu/geometry/GrQuad.h"
  9. #include "src/gpu/ops/GrQuadPerEdgeAA.h"
  10. #include "include/core/SkCanvas.h"
  11. #include "include/core/SkPaint.h"
  12. #include "include/effects/SkDashPathEffect.h"
  13. #include "include/pathops/SkPathOps.h"
  14. // Draw a line through the two points, outset by a fixed length in screen space
  15. static void draw_extended_line(SkCanvas* canvas, const SkPaint paint,
  16. const SkPoint& p0, const SkPoint& p1) {
  17. SkVector v = p1 - p0;
  18. v.setLength(v.length() + 3.f);
  19. canvas->drawLine(p1 - v, p0 + v, paint);
  20. // Draw normal vector too
  21. SkPaint normalPaint = paint;
  22. normalPaint.setPathEffect(nullptr);
  23. normalPaint.setStrokeWidth(paint.getStrokeWidth() / 4.f);
  24. SkVector n = {v.fY, -v.fX};
  25. n.setLength(.25f);
  26. SkPoint m = (p0 + p1) * 0.5f;
  27. canvas->drawLine(m, m + n, normalPaint);
  28. }
  29. static void make_aa_line(const SkPoint& p0, const SkPoint& p1, bool aaOn,
  30. bool outset, SkPoint line[2]) {
  31. SkVector n = {0.f, 0.f};
  32. if (aaOn) {
  33. SkVector v = p1 - p0;
  34. n = outset ? SkVector::Make(v.fY, -v.fX) : SkVector::Make(-v.fY, v.fX);
  35. n.setLength(0.5f);
  36. }
  37. line[0] = p0 + n;
  38. line[1] = p1 + n;
  39. }
  40. // To the line through l0-l1, not capped at the end points of the segment
  41. static SkScalar signed_distance(const SkPoint& p, const SkPoint& l0, const SkPoint& l1) {
  42. SkVector v = l1 - l0;
  43. v.normalize();
  44. SkVector n = {v.fY, -v.fX};
  45. SkScalar c = -n.dot(l0);
  46. return n.dot(p) + c;
  47. }
  48. static SkScalar get_area_coverage(const bool edgeAA[4], const SkPoint corners[4],
  49. const SkPoint& point) {
  50. SkPath shape;
  51. shape.addPoly(corners, 4, true);
  52. SkPath pixel;
  53. pixel.addRect(SkRect::MakeXYWH(point.fX - 0.5f, point.fY - 0.5f, 1.f, 1.f));
  54. SkPath intersection;
  55. if (!Op(shape, pixel, kIntersect_SkPathOp, &intersection) || intersection.isEmpty()) {
  56. return 0.f;
  57. }
  58. // Calculate area of the convex polygon
  59. SkScalar area = 0.f;
  60. for (int i = 0; i < intersection.countPoints(); ++i) {
  61. SkPoint p0 = intersection.getPoint(i);
  62. SkPoint p1 = intersection.getPoint((i + 1) % intersection.countPoints());
  63. SkScalar det = p0.fX * p1.fY - p1.fX * p0.fY;
  64. area += det;
  65. }
  66. // Scale by 1/2, then take abs value (this area formula is signed based on point winding, but
  67. // since it's convex, just make it positive).
  68. area = SkScalarAbs(0.5f * area);
  69. // Now account for the edge AA. If the pixel center is outside of a non-AA edge, turn of its
  70. // coverage. If the pixel only intersects non-AA edges, then set coverage to 1.
  71. bool needsNonAA = false;
  72. SkScalar edgeD[4];
  73. for (int i = 0; i < 4; ++i) {
  74. SkPoint e0 = corners[i];
  75. SkPoint e1 = corners[(i + 1) % 4];
  76. edgeD[i] = -signed_distance(point, e0, e1);
  77. if (!edgeAA[i]) {
  78. if (edgeD[i] < -1e-4f) {
  79. return 0.f; // Outside of non-AA line
  80. }
  81. needsNonAA = true;
  82. }
  83. }
  84. // Otherwise inside the shape, so check if any AA edge exerts influence over nonAA
  85. if (needsNonAA) {
  86. for (int i = 0; i < 4; i++) {
  87. if (edgeAA[i] && edgeD[i] < 0.5f) {
  88. needsNonAA = false;
  89. break;
  90. }
  91. }
  92. }
  93. return needsNonAA ? 1.f : area;
  94. }
  95. // FIXME take into account max coverage properly,
  96. static SkScalar get_edge_dist_coverage(const bool edgeAA[4], const SkPoint corners[4],
  97. const SkPoint outsetLines[8], const SkPoint insetLines[8],
  98. const SkPoint& point) {
  99. bool flip = false;
  100. // If the quad has been inverted, the original corners will not all be on the negative side of
  101. // every outset line. When that happens, calculate coverage using the "inset" lines and flip
  102. // the signed distance
  103. for (int i = 0; i < 4; ++i) {
  104. for (int j = 0; j < 4; ++j) {
  105. SkScalar d = signed_distance(corners[i], outsetLines[j * 2], outsetLines[j * 2 + 1]);
  106. if (d > 1e-4f) {
  107. flip = true;
  108. break;
  109. }
  110. }
  111. if (flip) {
  112. break;
  113. }
  114. }
  115. const SkPoint* lines = flip ? insetLines : outsetLines;
  116. SkScalar minCoverage = 1.f;
  117. for (int i = 0; i < 4; ++i) {
  118. // Multiply by negative 1 so that outside points have negative distances
  119. SkScalar d = (flip ? 1 : -1) * signed_distance(point, lines[i * 2], lines[i * 2 + 1]);
  120. if (!edgeAA[i] && d >= -1e-4f) {
  121. d = 1.f;
  122. }
  123. if (d < minCoverage) {
  124. minCoverage = d;
  125. if (minCoverage < 0.f) {
  126. break; // Outside the shape
  127. }
  128. }
  129. }
  130. return minCoverage < 0.f ? 0.f : minCoverage;
  131. }
  132. static bool inside_triangle(const SkPoint& point, const SkPoint& t0, const SkPoint& t1,
  133. const SkPoint& t2, SkScalar bary[3]) {
  134. // Check sign of t0 to (t1,t2). If it is positive, that means the normals point into the
  135. // triangle otherwise the normals point outside the triangle so update edge distances as
  136. // necessary
  137. bool flip = signed_distance(t0, t1, t2) < 0.f;
  138. SkScalar d0 = (flip ? -1 : 1) * signed_distance(point, t0, t1);
  139. SkScalar d1 = (flip ? -1 : 1) * signed_distance(point, t1, t2);
  140. SkScalar d2 = (flip ? -1 : 1) * signed_distance(point, t2, t0);
  141. // Be a little forgiving
  142. if (d0 < -1e-4f || d1 < -1e-4f || d2 < -1e-4f) {
  143. return false;
  144. }
  145. // Inside, so calculate barycentric coords from the sideline distances
  146. SkScalar d01 = (t0 - t1).length();
  147. SkScalar d12 = (t1 - t2).length();
  148. SkScalar d20 = (t2 - t0).length();
  149. if (SkScalarNearlyZero(d12) || SkScalarNearlyZero(d20) || SkScalarNearlyZero(d01)) {
  150. // Empty degenerate triangle
  151. return false;
  152. }
  153. // Coordinates for a vertex use distances to the opposite edge
  154. bary[0] = d1 * d12;
  155. bary[1] = d2 * d20;
  156. bary[2] = d0 * d01;
  157. // And normalize
  158. SkScalar sum = bary[0] + bary[1] + bary[2];
  159. bary[0] /= sum;
  160. bary[1] /= sum;
  161. bary[2] /= sum;
  162. return true;
  163. }
  164. static SkScalar get_framed_coverage(const SkPoint outer[4], const SkScalar outerCoverages[4],
  165. const SkPoint inner[4], const SkScalar innerCoverages[4],
  166. const SkRect& geomDomain, const SkPoint& point) {
  167. // Triangles are ordered clock wise. Indices >= 4 refer to inner[i - 4]. Otherwise its outer[i].
  168. static const int kFrameTris[] = {
  169. 0, 1, 4, 4, 1, 5,
  170. 1, 2, 5, 5, 2, 6,
  171. 2, 3, 6, 6, 3, 7,
  172. 3, 0, 7, 7, 0, 4,
  173. 4, 5, 7, 7, 5, 6
  174. };
  175. static const int kNumTris = 10;
  176. SkScalar bary[3];
  177. for (int i = 0; i < kNumTris; ++i) {
  178. int i0 = kFrameTris[i * 3];
  179. int i1 = kFrameTris[i * 3 + 1];
  180. int i2 = kFrameTris[i * 3 + 2];
  181. SkPoint t0 = i0 >= 4 ? inner[i0 - 4] : outer[i0];
  182. SkPoint t1 = i1 >= 4 ? inner[i1 - 4] : outer[i1];
  183. SkPoint t2 = i2 >= 4 ? inner[i2 - 4] : outer[i2];
  184. if (inside_triangle(point, t0, t1, t2, bary)) {
  185. // Calculate coverage by barycentric interpolation of coverages
  186. SkScalar c0 = i0 >= 4 ? innerCoverages[i0 - 4] : outerCoverages[i0];
  187. SkScalar c1 = i1 >= 4 ? innerCoverages[i1 - 4] : outerCoverages[i1];
  188. SkScalar c2 = i2 >= 4 ? innerCoverages[i2 - 4] : outerCoverages[i2];
  189. SkScalar coverage = bary[0] * c0 + bary[1] * c1 + bary[2] * c2;
  190. if (coverage < 0.5f) {
  191. // Check distances to domain
  192. SkScalar l = SkScalarPin(point.fX - geomDomain.fLeft, 0.f, 1.f);
  193. SkScalar t = SkScalarPin(point.fY - geomDomain.fTop, 0.f, 1.f);
  194. SkScalar r = SkScalarPin(geomDomain.fRight - point.fX, 0.f, 1.f);
  195. SkScalar b = SkScalarPin(geomDomain.fBottom - point.fY, 0.f, 1.f);
  196. coverage = SkMinScalar(coverage, l * t * r * b);
  197. }
  198. return coverage;
  199. }
  200. }
  201. // Not inside any triangle
  202. return 0.f;
  203. }
  204. static constexpr SkScalar kViewScale = 100.f;
  205. static constexpr SkScalar kViewOffset = 200.f;
  206. class DegenerateQuadSample : public Sample {
  207. public:
  208. DegenerateQuadSample(const SkRect& rect)
  209. : fOuterRect(rect)
  210. , fCoverageMode(CoverageMode::kArea) {
  211. fOuterRect.toQuad(fCorners);
  212. for (int i = 0; i < 4; ++i) {
  213. fEdgeAA[i] = true;
  214. }
  215. }
  216. void onDrawContent(SkCanvas* canvas) override {
  217. static const SkScalar kDotParams[2] = {1.f / kViewScale, 12.f / kViewScale};
  218. sk_sp<SkPathEffect> dots = SkDashPathEffect::Make(kDotParams, 2, 0.f);
  219. static const SkScalar kDashParams[2] = {8.f / kViewScale, 12.f / kViewScale};
  220. sk_sp<SkPathEffect> dashes = SkDashPathEffect::Make(kDashParams, 2, 0.f);
  221. SkPaint circlePaint;
  222. circlePaint.setAntiAlias(true);
  223. SkPaint linePaint;
  224. linePaint.setAntiAlias(true);
  225. linePaint.setStyle(SkPaint::kStroke_Style);
  226. linePaint.setStrokeWidth(4.f / kViewScale);
  227. linePaint.setStrokeJoin(SkPaint::kRound_Join);
  228. linePaint.setStrokeCap(SkPaint::kRound_Cap);
  229. canvas->translate(kViewOffset, kViewOffset);
  230. canvas->scale(kViewScale, kViewScale);
  231. // Draw the outer rectangle as a dotted line
  232. linePaint.setPathEffect(dots);
  233. canvas->drawRect(fOuterRect, linePaint);
  234. bool valid = this->isValid();
  235. if (valid) {
  236. SkPoint outsets[8];
  237. SkPoint insets[8];
  238. // Calculate inset and outset lines for edge-distance visualization
  239. for (int i = 0; i < 4; ++i) {
  240. make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], true, outsets + i * 2);
  241. make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], false, insets + i * 2);
  242. }
  243. // Calculate inner and outer meshes for GPU visualization
  244. SkPoint gpuOutset[4];
  245. SkScalar gpuOutsetCoverage[4];
  246. SkPoint gpuInset[4];
  247. SkScalar gpuInsetCoverage[4];
  248. SkRect gpuDomain;
  249. this->getTessellatedPoints(gpuInset, gpuInsetCoverage, gpuOutset, gpuOutsetCoverage,
  250. &gpuDomain);
  251. // Visualize the coverage values across the clamping rectangle, but test pixels outside
  252. // of the "outer" rect since some quad edges can be outset extra far.
  253. SkPaint pixelPaint;
  254. pixelPaint.setAntiAlias(true);
  255. SkRect covRect = fOuterRect.makeOutset(2.f, 2.f);
  256. for (SkScalar py = covRect.fTop; py < covRect.fBottom; py += 1.f) {
  257. for (SkScalar px = covRect.fLeft; px < covRect.fRight; px += 1.f) {
  258. // px and py are the top-left corner of the current pixel, so get center's
  259. // coordinate
  260. SkPoint pixelCenter = {px + 0.5f, py + 0.5f};
  261. SkScalar coverage;
  262. if (fCoverageMode == CoverageMode::kArea) {
  263. coverage = get_area_coverage(fEdgeAA, fCorners, pixelCenter);
  264. } else if (fCoverageMode == CoverageMode::kEdgeDistance) {
  265. coverage = get_edge_dist_coverage(fEdgeAA, fCorners, outsets, insets,
  266. pixelCenter);
  267. } else {
  268. SkASSERT(fCoverageMode == CoverageMode::kGPUMesh);
  269. coverage = get_framed_coverage(gpuOutset, gpuOutsetCoverage,
  270. gpuInset, gpuInsetCoverage, gpuDomain,
  271. pixelCenter);
  272. }
  273. SkRect pixelRect = SkRect::MakeXYWH(px, py, 1.f, 1.f);
  274. pixelRect.inset(0.1f, 0.1f);
  275. SkScalar a = 1.f - 0.5f * coverage;
  276. pixelPaint.setColor4f({a, a, a, 1.f}, nullptr);
  277. canvas->drawRect(pixelRect, pixelPaint);
  278. pixelPaint.setColor(coverage > 0.f ? SK_ColorGREEN : SK_ColorRED);
  279. pixelRect.inset(0.38f, 0.38f);
  280. canvas->drawRect(pixelRect, pixelPaint);
  281. }
  282. }
  283. linePaint.setPathEffect(dashes);
  284. // Draw the inset/outset "infinite" lines
  285. if (fCoverageMode == CoverageMode::kEdgeDistance) {
  286. for (int i = 0; i < 4; ++i) {
  287. if (fEdgeAA[i]) {
  288. linePaint.setColor(SK_ColorBLUE);
  289. draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]);
  290. linePaint.setColor(SK_ColorGREEN);
  291. draw_extended_line(canvas, linePaint, insets[i * 2], insets[i * 2 + 1]);
  292. } else {
  293. // Both outset and inset are the same line, so only draw one in cyan
  294. linePaint.setColor(SK_ColorCYAN);
  295. draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]);
  296. }
  297. }
  298. }
  299. linePaint.setPathEffect(nullptr);
  300. // What is tessellated using GrQuadPerEdgeAA
  301. if (fCoverageMode == CoverageMode::kGPUMesh) {
  302. SkPath outsetPath;
  303. outsetPath.addPoly(gpuOutset, 4, true);
  304. linePaint.setColor(SK_ColorBLUE);
  305. canvas->drawPath(outsetPath, linePaint);
  306. SkPath insetPath;
  307. insetPath.addPoly(gpuInset, 4, true);
  308. linePaint.setColor(SK_ColorGREEN);
  309. canvas->drawPath(insetPath, linePaint);
  310. SkPaint domainPaint = linePaint;
  311. domainPaint.setStrokeWidth(2.f / kViewScale);
  312. domainPaint.setPathEffect(dashes);
  313. domainPaint.setColor(SK_ColorMAGENTA);
  314. canvas->drawRect(gpuDomain, domainPaint);
  315. }
  316. // Draw the edges of the true quad as a solid line
  317. SkPath path;
  318. path.addPoly(fCorners, 4, true);
  319. linePaint.setColor(SK_ColorBLACK);
  320. canvas->drawPath(path, linePaint);
  321. } else {
  322. // Draw the edges of the true quad as a solid *red* line
  323. SkPath path;
  324. path.addPoly(fCorners, 4, true);
  325. linePaint.setColor(SK_ColorRED);
  326. linePaint.setPathEffect(nullptr);
  327. canvas->drawPath(path, linePaint);
  328. }
  329. // Draw the four clickable corners as circles
  330. circlePaint.setColor(valid ? SK_ColorBLACK : SK_ColorRED);
  331. for (int i = 0; i < 4; ++i) {
  332. canvas->drawCircle(fCorners[i], 5.f / kViewScale, circlePaint);
  333. }
  334. }
  335. Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, ModifierKey) override;
  336. bool onClick(Sample::Click*) override;
  337. bool onChar(SkUnichar) override;
  338. SkString name() override { return SkString("DegenerateQuad"); }
  339. private:
  340. class Click;
  341. enum class CoverageMode {
  342. kArea, kEdgeDistance, kGPUMesh
  343. };
  344. const SkRect fOuterRect;
  345. SkPoint fCorners[4]; // TL, TR, BR, BL
  346. bool fEdgeAA[4]; // T, R, B, L
  347. CoverageMode fCoverageMode;
  348. bool isValid() const {
  349. SkPath path;
  350. path.addPoly(fCorners, 4, true);
  351. return path.isConvex();
  352. }
  353. void getTessellatedPoints(SkPoint inset[4], SkScalar insetCoverage[4], SkPoint outset[4],
  354. SkScalar outsetCoverage[4], SkRect* domain) const {
  355. // Fixed vertex spec for extracting the picture frame geometry
  356. static const GrQuadPerEdgeAA::VertexSpec kSpec =
  357. {GrQuad::Type::kGeneral, GrQuadPerEdgeAA::ColorType::kNone,
  358. GrQuad::Type::kAxisAligned, false, GrQuadPerEdgeAA::Domain::kNo,
  359. GrAAType::kCoverage, false};
  360. static const GrQuad kIgnored(SkRect::MakeEmpty());
  361. GrQuadAAFlags flags = GrQuadAAFlags::kNone;
  362. flags |= fEdgeAA[0] ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone;
  363. flags |= fEdgeAA[1] ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone;
  364. flags |= fEdgeAA[2] ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone;
  365. flags |= fEdgeAA[3] ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone;
  366. GrQuad quad = GrQuad::MakeFromSkQuad(fCorners, SkMatrix::I());
  367. float vertices[56]; // 2 quads, with x, y, coverage, and geometry domain (7 floats x 8 vert)
  368. GrQuadPerEdgeAA::Tessellate(vertices, kSpec, quad, {1.f, 1.f, 1.f, 1.f},
  369. GrQuad(SkRect::MakeEmpty()), SkRect::MakeEmpty(), flags);
  370. // The first quad in vertices is the inset, then the outset, but they
  371. // are ordered TL, BL, TR, BR so un-interleave coverage and re-arrange
  372. inset[0] = {vertices[0], vertices[1]}; // TL
  373. insetCoverage[0] = vertices[2];
  374. inset[3] = {vertices[7], vertices[8]}; // BL
  375. insetCoverage[3] = vertices[9];
  376. inset[1] = {vertices[14], vertices[15]}; // TR
  377. insetCoverage[1] = vertices[16];
  378. inset[2] = {vertices[21], vertices[22]}; // BR
  379. insetCoverage[2] = vertices[23];
  380. outset[0] = {vertices[28], vertices[29]}; // TL
  381. outsetCoverage[0] = vertices[30];
  382. outset[3] = {vertices[35], vertices[36]}; // BL
  383. outsetCoverage[3] = vertices[37];
  384. outset[1] = {vertices[42], vertices[43]}; // TR
  385. outsetCoverage[1] = vertices[44];
  386. outset[2] = {vertices[49], vertices[50]}; // BR
  387. outsetCoverage[2] = vertices[51];
  388. *domain = {vertices[52], vertices[53], vertices[54], vertices[55]};
  389. }
  390. typedef Sample INHERITED;
  391. };
  392. class DegenerateQuadSample::Click : public Sample::Click {
  393. public:
  394. Click(const SkRect& clamp, int index)
  395. : fOuterRect(clamp)
  396. , fIndex(index) {}
  397. void doClick(SkPoint points[4]) {
  398. if (fIndex >= 0) {
  399. this->drag(&points[fIndex]);
  400. } else {
  401. for (int i = 0; i < 4; ++i) {
  402. this->drag(&points[i]);
  403. }
  404. }
  405. }
  406. private:
  407. SkRect fOuterRect;
  408. int fIndex;
  409. void drag(SkPoint* point) {
  410. SkPoint delta = fCurr - fPrev;
  411. *point += SkPoint::Make(delta.x() / kViewScale, delta.y() / kViewScale);
  412. point->fX = SkMinScalar(fOuterRect.fRight, SkMaxScalar(point->fX, fOuterRect.fLeft));
  413. point->fY = SkMinScalar(fOuterRect.fBottom, SkMaxScalar(point->fY, fOuterRect.fTop));
  414. }
  415. };
  416. Sample::Click* DegenerateQuadSample::onFindClickHandler(SkScalar x, SkScalar y, ModifierKey) {
  417. SkPoint inCTM = SkPoint::Make((x - kViewOffset) / kViewScale, (y - kViewOffset) / kViewScale);
  418. for (int i = 0; i < 4; ++i) {
  419. if ((fCorners[i] - inCTM).length() < 10.f / kViewScale) {
  420. return new Click(fOuterRect, i);
  421. }
  422. }
  423. return new Click(fOuterRect, -1);
  424. }
  425. bool DegenerateQuadSample::onClick(Sample::Click* click) {
  426. Click* myClick = (Click*) click;
  427. myClick->doClick(fCorners);
  428. return true;
  429. }
  430. bool DegenerateQuadSample::onChar(SkUnichar code) {
  431. switch(code) {
  432. case '1':
  433. fEdgeAA[0] = !fEdgeAA[0];
  434. return true;
  435. case '2':
  436. fEdgeAA[1] = !fEdgeAA[1];
  437. return true;
  438. case '3':
  439. fEdgeAA[2] = !fEdgeAA[2];
  440. return true;
  441. case '4':
  442. fEdgeAA[3] = !fEdgeAA[3];
  443. return true;
  444. case 'q':
  445. fCoverageMode = CoverageMode::kArea;
  446. return true;
  447. case 'w':
  448. fCoverageMode = CoverageMode::kEdgeDistance;
  449. return true;
  450. case 'e':
  451. fCoverageMode = CoverageMode::kGPUMesh;
  452. return true;
  453. }
  454. return false;
  455. }
  456. DEF_SAMPLE(return new DegenerateQuadSample(SkRect::MakeWH(4.f, 4.f));)