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