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