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