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
8 #include "Sample.h"
9
10 #include "GrQuad.h"
11 #include "ops/GrQuadPerEdgeAA.h"
12
13 #include "SkCanvas.h"
14 #include "SkDashPathEffect.h"
15 #include "SkPaint.h"
16 #include "SkPathOps.h"
17
18 // Draw a line through the two points, outset by a fixed length in screen space
draw_extended_line(SkCanvas * canvas,const SkPaint paint,const SkPoint & p0,const SkPoint & p1)19 static 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
make_aa_line(const SkPoint & p0,const SkPoint & p1,bool aaOn,bool outset,SkPoint line[2])36 static 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
signed_distance(const SkPoint & p,const SkPoint & l0,const SkPoint & l1)50 static 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
get_area_coverage(const bool edgeAA[4],const SkPoint corners[4],const SkPoint & point)58 static 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,
get_edge_dist_coverage(const bool edgeAA[4],const SkPoint corners[4],const SkPoint outsetLines[8],const SkPoint insetLines[8],const SkPoint & point)111 static 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]);
121 if (d >= 0.f) {
122 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
inside_triangle(const SkPoint & point,const SkPoint & t0,const SkPoint & t1,const SkPoint & t2,SkScalar bary[3])150 static 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
get_framed_coverage(const SkPoint outer[4],const SkScalar outerCoverages[4],const SkPoint inner[4],const SkScalar innerCoverages[4],const SkPoint & point)188 static SkScalar get_framed_coverage(const SkPoint outer[4], const SkScalar outerCoverages[4],
189 const SkPoint inner[4], const SkScalar innerCoverages[4],
190 const SkPoint& point) {
191 // 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
216 return bary[0] * c0 + bary[1] * c1 + bary[2] * c2;
217 }
218 }
219 // Not inside any triangle
220 return 0.f;
221 }
222
223 static constexpr SkScalar kViewScale = 100.f;
224 static constexpr SkScalar kViewOffset = 200.f;
225
226 class DegenerateQuadSample : public Sample {
227 public:
DegenerateQuadSample(const SkRect & rect)228 DegenerateQuadSample(const SkRect& rect)
229 : fOuterRect(rect)
230 , fCoverageMode(CoverageMode::kArea) {
231 fOuterRect.toQuad(fCorners);
232 for (int i = 0; i < 4; ++i) {
233 fEdgeAA[i] = true;
234 }
235 }
236
onDrawContent(SkCanvas * canvas)237 void onDrawContent(SkCanvas* canvas) override {
238 static const SkScalar kDotParams[2] = {1.f / kViewScale, 12.f / kViewScale};
239 sk_sp<SkPathEffect> dots = SkDashPathEffect::Make(kDotParams, 2, 0.f);
240 static const SkScalar kDashParams[2] = {8.f / kViewScale, 12.f / kViewScale};
241 sk_sp<SkPathEffect> dashes = SkDashPathEffect::Make(kDashParams, 2, 0.f);
242
243 SkPaint circlePaint;
244 circlePaint.setAntiAlias(true);
245
246 SkPaint linePaint;
247 linePaint.setAntiAlias(true);
248 linePaint.setStyle(SkPaint::kStroke_Style);
249 linePaint.setStrokeWidth(4.f / kViewScale);
250 linePaint.setStrokeJoin(SkPaint::kRound_Join);
251 linePaint.setStrokeCap(SkPaint::kRound_Cap);
252
253 canvas->translate(kViewOffset, kViewOffset);
254 canvas->scale(kViewScale, kViewScale);
255
256 // Draw the outer rectangle as a dotted line
257 linePaint.setPathEffect(dots);
258 canvas->drawRect(fOuterRect, linePaint);
259
260 bool valid = this->isValid();
261
262 if (valid) {
263 SkPoint outsets[8];
264 SkPoint insets[8];
265 // Calculate inset and outset lines for edge-distance visualization
266 for (int i = 0; i < 4; ++i) {
267 make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], true, outsets + i * 2);
268 make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], false, insets + i * 2);
269 }
270
271 // Calculate inner and outer meshes for GPU visualization
272 SkPoint gpuOutset[4];
273 SkScalar gpuOutsetCoverage[4];
274 SkPoint gpuInset[4];
275 SkScalar gpuInsetCoverage[4];
276 this->getTessellatedPoints(gpuInset, gpuInsetCoverage, gpuOutset, gpuOutsetCoverage);
277
278 // Visualize the coverage values across the clamping rectangle, but test pixels outside
279 // of the "outer" rect since some quad edges can be outset extra far.
280 SkPaint pixelPaint;
281 pixelPaint.setAntiAlias(true);
282 SkRect covRect = fOuterRect.makeOutset(2.f, 2.f);
283 for (SkScalar py = covRect.fTop; py < covRect.fBottom; py += 1.f) {
284 for (SkScalar px = covRect.fLeft; px < covRect.fRight; px += 1.f) {
285 // px and py are the top-left corner of the current pixel, so get center's
286 // coordinate
287 SkPoint pixelCenter = {px + 0.5f, py + 0.5f};
288 SkScalar coverage;
289 if (fCoverageMode == CoverageMode::kArea) {
290 coverage = get_area_coverage(fEdgeAA, fCorners, pixelCenter);
291 } else if (fCoverageMode == CoverageMode::kEdgeDistance) {
292 coverage = get_edge_dist_coverage(fEdgeAA, fCorners, outsets, insets,
293 pixelCenter);
294 } else {
295 SkASSERT(fCoverageMode == CoverageMode::kGPUMesh);
296 coverage = get_framed_coverage(gpuOutset, gpuOutsetCoverage,
297 gpuInset, gpuInsetCoverage, pixelCenter);
298 }
299
300 SkRect pixelRect = SkRect::MakeXYWH(px, py, 1.f, 1.f);
301 pixelRect.inset(0.1f, 0.1f);
302
303 SkScalar a = 1.f - 0.5f * coverage;
304 pixelPaint.setColor4f({a, a, a, 1.f}, nullptr);
305 canvas->drawRect(pixelRect, pixelPaint);
306
307 pixelPaint.setColor(coverage > 0.f ? SK_ColorGREEN : SK_ColorRED);
308 pixelRect.inset(0.38f, 0.38f);
309 canvas->drawRect(pixelRect, pixelPaint);
310 }
311 }
312
313 linePaint.setPathEffect(dashes);
314 // Draw the inset/outset "infinite" lines
315 if (fCoverageMode == CoverageMode::kEdgeDistance) {
316 for (int i = 0; i < 4; ++i) {
317 if (fEdgeAA[i]) {
318 linePaint.setColor(SK_ColorBLUE);
319 draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]);
320 linePaint.setColor(SK_ColorGREEN);
321 draw_extended_line(canvas, linePaint, insets[i * 2], insets[i * 2 + 1]);
322 } else {
323 // Both outset and inset are the same line, so only draw one in cyan
324 linePaint.setColor(SK_ColorCYAN);
325 draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]);
326 }
327 }
328 }
329
330 linePaint.setPathEffect(nullptr);
331 // What is tessellated using GrQuadPerEdgeAA
332 if (fCoverageMode == CoverageMode::kGPUMesh) {
333 SkPath outsetPath;
334 outsetPath.addPoly(gpuOutset, 4, true);
335 linePaint.setColor(SK_ColorBLUE);
336 canvas->drawPath(outsetPath, linePaint);
337
338 SkPath insetPath;
339 insetPath.addPoly(gpuInset, 4, true);
340 linePaint.setColor(SK_ColorGREEN);
341 canvas->drawPath(insetPath, linePaint);
342 }
343
344 // Draw the edges of the true quad as a solid line
345 SkPath path;
346 path.addPoly(fCorners, 4, true);
347 linePaint.setColor(SK_ColorBLACK);
348 canvas->drawPath(path, linePaint);
349 } else {
350 // Draw the edges of the true quad as a solid *red* line
351 SkPath path;
352 path.addPoly(fCorners, 4, true);
353 linePaint.setColor(SK_ColorRED);
354 linePaint.setPathEffect(nullptr);
355 canvas->drawPath(path, linePaint);
356 }
357
358 // Draw the four clickable corners as circles
359 circlePaint.setColor(valid ? SK_ColorBLACK : SK_ColorRED);
360 for (int i = 0; i < 4; ++i) {
361 canvas->drawCircle(fCorners[i], 5.f / kViewScale, circlePaint);
362 }
363 }
364
365 Sample::Click* onFindClickHandler(SkScalar x, SkScalar y,
366 unsigned) override;
367 bool onClick(Sample::Click*) override;
368 bool onQuery(Sample::Event* evt) override;
369
370 private:
371 class Click;
372
373 enum class CoverageMode {
374 kArea, kEdgeDistance, kGPUMesh
375 };
376
377 const SkRect fOuterRect;
378 SkPoint fCorners[4]; // TL, TR, BR, BL
379 bool fEdgeAA[4]; // T, R, B, L
380 CoverageMode fCoverageMode;
381
isValid() const382 bool isValid() const {
383 SkPath path;
384 path.addPoly(fCorners, 4, true);
385 return path.isConvex();
386 }
387
getTessellatedPoints(SkPoint inset[4],SkScalar insetCoverage[4],SkPoint outset[4],SkScalar outsetCoverage[4]) const388 void getTessellatedPoints(SkPoint inset[4], SkScalar insetCoverage[4], SkPoint outset[4],
389 SkScalar outsetCoverage[4]) const {
390 // Fixed vertex spec for extracting the picture frame geometry
391 static const GrQuadPerEdgeAA::VertexSpec kSpec =
392 {GrQuadType::kStandard, GrQuadPerEdgeAA::ColorType::kNone,
393 GrQuadType::kRect, false, GrQuadPerEdgeAA::Domain::kNo,
394 GrAAType::kCoverage, false};
395 static const GrPerspQuad kIgnored(SkRect::MakeEmpty());
396
397 GrQuadAAFlags flags = GrQuadAAFlags::kNone;
398 flags |= fEdgeAA[0] ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone;
399 flags |= fEdgeAA[1] ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone;
400 flags |= fEdgeAA[2] ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone;
401 flags |= fEdgeAA[3] ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone;
402
403 GrPerspQuad quad = GrPerspQuad::MakeFromSkQuad(fCorners, SkMatrix::I());
404
405 float vertices[24]; // 2 quads, with x, y, and coverage
406 GrQuadPerEdgeAA::Tessellate(vertices, kSpec, quad, {1.f, 1.f, 1.f, 1.f},
407 GrPerspQuad(SkRect::MakeEmpty()), SkRect::MakeEmpty(), flags);
408
409 // The first quad in vertices is the inset, then the outset, but they
410 // are ordered TL, BL, TR, BR so un-interleave coverage and re-arrange
411 inset[0] = {vertices[0], vertices[1]}; // TL
412 insetCoverage[0] = vertices[2];
413 inset[3] = {vertices[3], vertices[4]}; // BL
414 insetCoverage[3] = vertices[5];
415 inset[1] = {vertices[6], vertices[7]}; // TR
416 insetCoverage[1] = vertices[8];
417 inset[2] = {vertices[9], vertices[10]}; // BR
418 insetCoverage[2] = vertices[11];
419
420 outset[0] = {vertices[12], vertices[13]}; // TL
421 outsetCoverage[0] = vertices[14];
422 outset[3] = {vertices[15], vertices[16]}; // BL
423 outsetCoverage[3] = vertices[17];
424 outset[1] = {vertices[18], vertices[19]}; // TR
425 outsetCoverage[1] = vertices[20];
426 outset[2] = {vertices[21], vertices[22]}; // BR
427 outsetCoverage[2] = vertices[23];
428 }
429
430 typedef Sample INHERITED;
431 };
432
433 class DegenerateQuadSample::Click : public Sample::Click {
434 public:
Click(Sample * target,const SkRect & clamp,int index)435 Click(Sample* target, const SkRect& clamp, int index)
436 : Sample::Click(target)
437 , fOuterRect(clamp)
438 , fIndex(index) {}
439
doClick(SkPoint points[4])440 void doClick(SkPoint points[4]) {
441 if (fIndex >= 0) {
442 this->drag(&points[fIndex]);
443 } else {
444 for (int i = 0; i < 4; ++i) {
445 this->drag(&points[i]);
446 }
447 }
448 }
449
450 private:
451 SkRect fOuterRect;
452 int fIndex;
453
drag(SkPoint * point)454 void drag(SkPoint* point) {
455 SkIPoint delta = fICurr - fIPrev;
456 *point += SkPoint::Make(delta.x() / kViewScale, delta.y() / kViewScale);
457 point->fX = SkMinScalar(fOuterRect.fRight, SkMaxScalar(point->fX, fOuterRect.fLeft));
458 point->fY = SkMinScalar(fOuterRect.fBottom, SkMaxScalar(point->fY, fOuterRect.fTop));
459 }
460 };
461
onFindClickHandler(SkScalar x,SkScalar y,unsigned)462 Sample::Click* DegenerateQuadSample::onFindClickHandler(SkScalar x, SkScalar y, unsigned) {
463 SkPoint inCTM = SkPoint::Make((x - kViewOffset) / kViewScale, (y - kViewOffset) / kViewScale);
464 for (int i = 0; i < 4; ++i) {
465 if ((fCorners[i] - inCTM).length() < 10.f / kViewScale) {
466 return new Click(this, fOuterRect, i);
467 }
468 }
469 return new Click(this, fOuterRect, -1);
470 }
471
onClick(Sample::Click * click)472 bool DegenerateQuadSample::onClick(Sample::Click* click) {
473 Click* myClick = (Click*) click;
474 myClick->doClick(fCorners);
475 return true;
476 }
477
onQuery(Sample::Event * event)478 bool DegenerateQuadSample::onQuery(Sample::Event* event) {
479 if (Sample::TitleQ(*event)) {
480 Sample::TitleR(event, "DegenerateQuad");
481 return true;
482 }
483 SkUnichar code;
484 if (Sample::CharQ(*event, &code)) {
485 switch(code) {
486 case '1':
487 fEdgeAA[0] = !fEdgeAA[0];
488 return true;
489 case '2':
490 fEdgeAA[1] = !fEdgeAA[1];
491 return true;
492 case '3':
493 fEdgeAA[2] = !fEdgeAA[2];
494 return true;
495 case '4':
496 fEdgeAA[3] = !fEdgeAA[3];
497 return true;
498 case 'q':
499 fCoverageMode = CoverageMode::kArea;
500 return true;
501 case 'w':
502 fCoverageMode = CoverageMode::kEdgeDistance;
503 return true;
504 case 'e':
505 fCoverageMode = CoverageMode::kGPUMesh;
506 return true;
507 }
508 }
509 return this->INHERITED::onQuery(event);
510 }
511
512 DEF_SAMPLE(return new DegenerateQuadSample(SkRect::MakeWH(4.f, 4.f));)
513