|  | /* | 
|  | * Copyright 2019 Google Inc. | 
|  | * | 
|  | * Use of this source code is governed by a BSD-style license that can be | 
|  | * found in the LICENSE file. | 
|  | */ | 
|  |  | 
|  | #include "samplecode/Sample.h" | 
|  |  | 
|  | #include "src/gpu/geometry/GrQuad.h" | 
|  | #include "src/gpu/ops/GrQuadPerEdgeAA.h" | 
|  |  | 
|  | #include "include/core/SkCanvas.h" | 
|  | #include "include/core/SkPaint.h" | 
|  | #include "include/effects/SkDashPathEffect.h" | 
|  | #include "include/pathops/SkPathOps.h" | 
|  | #include "include/private/SkTPin.h" | 
|  |  | 
|  | // Draw a line through the two points, outset by a fixed length in screen space | 
|  | static void draw_extended_line(SkCanvas* canvas, const SkPaint paint, | 
|  | const SkPoint& p0, const SkPoint& p1) { | 
|  | SkVector v = p1 - p0; | 
|  | v.setLength(v.length() + 3.f); | 
|  | canvas->drawLine(p1 - v, p0 + v, paint); | 
|  |  | 
|  | // Draw normal vector too | 
|  | SkPaint normalPaint = paint; | 
|  | normalPaint.setPathEffect(nullptr); | 
|  | normalPaint.setStrokeWidth(paint.getStrokeWidth() / 4.f); | 
|  |  | 
|  | SkVector n = {v.fY, -v.fX}; | 
|  | n.setLength(.25f); | 
|  | SkPoint m = (p0 + p1) * 0.5f; | 
|  | canvas->drawLine(m, m + n, normalPaint); | 
|  | } | 
|  |  | 
|  | static void make_aa_line(const SkPoint& p0, const SkPoint& p1, bool aaOn, | 
|  | bool outset, SkPoint line[2]) { | 
|  | SkVector n = {0.f, 0.f}; | 
|  | if (aaOn) { | 
|  | SkVector v = p1 - p0; | 
|  | n = outset ? SkVector::Make(v.fY, -v.fX) : SkVector::Make(-v.fY, v.fX); | 
|  | n.setLength(0.5f); | 
|  | } | 
|  |  | 
|  | line[0] = p0 + n; | 
|  | line[1] = p1 + n; | 
|  | } | 
|  |  | 
|  | // To the line through l0-l1, not capped at the end points of the segment | 
|  | static SkScalar signed_distance(const SkPoint& p, const SkPoint& l0, const SkPoint& l1) { | 
|  | SkVector v = l1 - l0; | 
|  | v.normalize(); | 
|  | SkVector n = {v.fY, -v.fX}; | 
|  | SkScalar c = -n.dot(l0); | 
|  | return n.dot(p) + c; | 
|  | } | 
|  |  | 
|  | static SkScalar get_area_coverage(const bool edgeAA[4], const SkPoint corners[4], | 
|  | const SkPoint& point) { | 
|  | SkPath shape; | 
|  | shape.addPoly(corners, 4, true); | 
|  | SkPath pixel; | 
|  | pixel.addRect(SkRect::MakeXYWH(point.fX - 0.5f, point.fY - 0.5f, 1.f, 1.f)); | 
|  |  | 
|  | SkPath intersection; | 
|  | if (!Op(shape, pixel, kIntersect_SkPathOp, &intersection) || intersection.isEmpty()) { | 
|  | return 0.f; | 
|  | } | 
|  |  | 
|  | // Calculate area of the convex polygon | 
|  | SkScalar area = 0.f; | 
|  | for (int i = 0; i < intersection.countPoints(); ++i) { | 
|  | SkPoint p0 = intersection.getPoint(i); | 
|  | SkPoint p1 = intersection.getPoint((i + 1) % intersection.countPoints()); | 
|  | SkScalar det = p0.fX * p1.fY - p1.fX * p0.fY; | 
|  | area += det; | 
|  | } | 
|  |  | 
|  | // Scale by 1/2, then take abs value (this area formula is signed based on point winding, but | 
|  | // since it's convex, just make it positive). | 
|  | area = SkScalarAbs(0.5f * area); | 
|  |  | 
|  | // Now account for the edge AA. If the pixel center is outside of a non-AA edge, turn of its | 
|  | // coverage. If the pixel only intersects non-AA edges, then set coverage to 1. | 
|  | bool needsNonAA = false; | 
|  | SkScalar edgeD[4]; | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | SkPoint e0 = corners[i]; | 
|  | SkPoint e1 = corners[(i + 1) % 4]; | 
|  | edgeD[i] = -signed_distance(point, e0, e1); | 
|  | if (!edgeAA[i]) { | 
|  | if (edgeD[i] < -1e-4f) { | 
|  | return 0.f; // Outside of non-AA line | 
|  | } | 
|  | needsNonAA = true; | 
|  | } | 
|  | } | 
|  | // Otherwise inside the shape, so check if any AA edge exerts influence over nonAA | 
|  | if (needsNonAA) { | 
|  | for (int i = 0; i < 4; i++) { | 
|  | if (edgeAA[i] && edgeD[i] < 0.5f) { | 
|  | needsNonAA = false; | 
|  | break; | 
|  | } | 
|  | } | 
|  | } | 
|  | return needsNonAA ? 1.f : area; | 
|  | } | 
|  |  | 
|  | // FIXME take into account max coverage properly, | 
|  | static SkScalar get_edge_dist_coverage(const bool edgeAA[4], const SkPoint corners[4], | 
|  | const SkPoint outsetLines[8], const SkPoint insetLines[8], | 
|  | const SkPoint& point) { | 
|  | bool flip = false; | 
|  | // If the quad has been inverted, the original corners will not all be on the negative side of | 
|  | // every outset line. When that happens, calculate coverage using the "inset" lines and flip | 
|  | // the signed distance | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | for (int j = 0; j < 4; ++j) { | 
|  | SkScalar d = signed_distance(corners[i], outsetLines[j * 2], outsetLines[j * 2 + 1]); | 
|  | if (d > 1e-4f) { | 
|  | flip = true; | 
|  | break; | 
|  | } | 
|  | } | 
|  | if (flip) { | 
|  | break; | 
|  | } | 
|  | } | 
|  |  | 
|  | const SkPoint* lines = flip ? insetLines : outsetLines; | 
|  |  | 
|  | SkScalar minCoverage = 1.f; | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | // Multiply by negative 1 so that outside points have negative distances | 
|  | SkScalar d = (flip ? 1 : -1) * signed_distance(point, lines[i * 2], lines[i * 2 + 1]); | 
|  | if (!edgeAA[i] && d >= -1e-4f) { | 
|  | d = 1.f; | 
|  | } | 
|  | if (d < minCoverage) { | 
|  | minCoverage = d; | 
|  | if (minCoverage < 0.f) { | 
|  | break; // Outside the shape | 
|  | } | 
|  | } | 
|  | } | 
|  | return minCoverage < 0.f ? 0.f : minCoverage; | 
|  | } | 
|  |  | 
|  | static bool inside_triangle(const SkPoint& point, const SkPoint& t0, const SkPoint& t1, | 
|  | const SkPoint& t2, SkScalar bary[3]) { | 
|  | // Check sign of t0 to (t1,t2). If it is positive, that means the normals point into the | 
|  | // triangle otherwise the normals point outside the triangle so update edge distances as | 
|  | // necessary | 
|  | bool flip = signed_distance(t0, t1, t2) < 0.f; | 
|  |  | 
|  | SkScalar d0 = (flip ? -1 : 1) * signed_distance(point, t0, t1); | 
|  | SkScalar d1 = (flip ? -1 : 1) * signed_distance(point, t1, t2); | 
|  | SkScalar d2 = (flip ? -1 : 1) * signed_distance(point, t2, t0); | 
|  | // Be a little forgiving | 
|  | if (d0 < -1e-4f || d1 < -1e-4f || d2 < -1e-4f) { | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Inside, so calculate barycentric coords from the sideline distances | 
|  | SkScalar d01 = (t0 - t1).length(); | 
|  | SkScalar d12 = (t1 - t2).length(); | 
|  | SkScalar d20 = (t2 - t0).length(); | 
|  |  | 
|  | if (SkScalarNearlyZero(d12) || SkScalarNearlyZero(d20) || SkScalarNearlyZero(d01)) { | 
|  | // Empty degenerate triangle | 
|  | return false; | 
|  | } | 
|  |  | 
|  | // Coordinates for a vertex use distances to the opposite edge | 
|  | bary[0] = d1 * d12; | 
|  | bary[1] = d2 * d20; | 
|  | bary[2] = d0 * d01; | 
|  | // And normalize | 
|  | SkScalar sum = bary[0] + bary[1] + bary[2]; | 
|  | bary[0] /= sum; | 
|  | bary[1] /= sum; | 
|  | bary[2] /= sum; | 
|  |  | 
|  | return true; | 
|  | } | 
|  |  | 
|  | static SkScalar get_framed_coverage(const SkPoint outer[4], const SkScalar outerCoverages[4], | 
|  | const SkPoint inner[4], const SkScalar innerCoverages[4], | 
|  | const SkRect& geomDomain, const SkPoint& point) { | 
|  | // Triangles are ordered clock wise. Indices >= 4 refer to inner[i - 4]. Otherwise its outer[i]. | 
|  | static const int kFrameTris[] = { | 
|  | 0, 1, 4,   4, 1, 5, | 
|  | 1, 2, 5,   5, 2, 6, | 
|  | 2, 3, 6,   6, 3, 7, | 
|  | 3, 0, 7,   7, 0, 4, | 
|  | 4, 5, 7,   7, 5, 6 | 
|  | }; | 
|  | static const int kNumTris = 10; | 
|  |  | 
|  | SkScalar bary[3]; | 
|  | for (int i = 0; i < kNumTris; ++i) { | 
|  | int i0 = kFrameTris[i * 3]; | 
|  | int i1 = kFrameTris[i * 3 + 1]; | 
|  | int i2 = kFrameTris[i * 3 + 2]; | 
|  |  | 
|  | SkPoint t0 = i0 >= 4 ? inner[i0 - 4] : outer[i0]; | 
|  | SkPoint t1 = i1 >= 4 ? inner[i1 - 4] : outer[i1]; | 
|  | SkPoint t2 = i2 >= 4 ? inner[i2 - 4] : outer[i2]; | 
|  | if (inside_triangle(point, t0, t1, t2, bary)) { | 
|  | // Calculate coverage by barycentric interpolation of coverages | 
|  | SkScalar c0 = i0 >= 4 ? innerCoverages[i0 - 4] : outerCoverages[i0]; | 
|  | SkScalar c1 = i1 >= 4 ? innerCoverages[i1 - 4] : outerCoverages[i1]; | 
|  | SkScalar c2 = i2 >= 4 ? innerCoverages[i2 - 4] : outerCoverages[i2]; | 
|  |  | 
|  | SkScalar coverage = bary[0] * c0 + bary[1] * c1 + bary[2] * c2; | 
|  | if (coverage < 0.5f) { | 
|  | // Check distances to domain | 
|  | SkScalar l = SkTPin(point.fX - geomDomain.fLeft, 0.f, 1.f); | 
|  | SkScalar t = SkTPin(point.fY - geomDomain.fTop, 0.f, 1.f); | 
|  | SkScalar r = SkTPin(geomDomain.fRight - point.fX, 0.f, 1.f); | 
|  | SkScalar b = SkTPin(geomDomain.fBottom - point.fY, 0.f, 1.f); | 
|  | coverage = std::min(coverage, l * t * r * b); | 
|  | } | 
|  | return coverage; | 
|  | } | 
|  | } | 
|  | // Not inside any triangle | 
|  | return 0.f; | 
|  | } | 
|  |  | 
|  | static constexpr SkScalar kViewScale = 100.f; | 
|  | static constexpr SkScalar kViewOffset = 200.f; | 
|  |  | 
|  | class DegenerateQuadSample : public Sample { | 
|  | public: | 
|  | DegenerateQuadSample(const SkRect& rect) | 
|  | : fOuterRect(rect) | 
|  | , fCoverageMode(CoverageMode::kArea) { | 
|  | fOuterRect.toQuad(fCorners); | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | fEdgeAA[i] = true; | 
|  | } | 
|  | } | 
|  |  | 
|  | void onDrawContent(SkCanvas* canvas) override { | 
|  | static const SkScalar kDotParams[2] = {1.f / kViewScale, 12.f / kViewScale}; | 
|  | sk_sp<SkPathEffect> dots = SkDashPathEffect::Make(kDotParams, 2, 0.f); | 
|  | static const SkScalar kDashParams[2] = {8.f / kViewScale, 12.f / kViewScale}; | 
|  | sk_sp<SkPathEffect> dashes = SkDashPathEffect::Make(kDashParams, 2, 0.f); | 
|  |  | 
|  | SkPaint circlePaint; | 
|  | circlePaint.setAntiAlias(true); | 
|  |  | 
|  | SkPaint linePaint; | 
|  | linePaint.setAntiAlias(true); | 
|  | linePaint.setStyle(SkPaint::kStroke_Style); | 
|  | linePaint.setStrokeWidth(4.f / kViewScale); | 
|  | linePaint.setStrokeJoin(SkPaint::kRound_Join); | 
|  | linePaint.setStrokeCap(SkPaint::kRound_Cap); | 
|  |  | 
|  | canvas->translate(kViewOffset, kViewOffset); | 
|  | canvas->scale(kViewScale, kViewScale); | 
|  |  | 
|  | // Draw the outer rectangle as a dotted line | 
|  | linePaint.setPathEffect(dots); | 
|  | canvas->drawRect(fOuterRect, linePaint); | 
|  |  | 
|  | bool valid = this->isValid(); | 
|  |  | 
|  | if (valid) { | 
|  | SkPoint outsets[8]; | 
|  | SkPoint insets[8]; | 
|  | // Calculate inset and outset lines for edge-distance visualization | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], true, outsets + i * 2); | 
|  | make_aa_line(fCorners[i], fCorners[(i + 1) % 4], fEdgeAA[i], false, insets + i * 2); | 
|  | } | 
|  |  | 
|  | // Calculate inner and outer meshes for GPU visualization | 
|  | SkPoint gpuOutset[4]; | 
|  | SkScalar gpuOutsetCoverage[4]; | 
|  | SkPoint gpuInset[4]; | 
|  | SkScalar gpuInsetCoverage[4]; | 
|  | SkRect gpuDomain; | 
|  | this->getTessellatedPoints(gpuInset, gpuInsetCoverage, gpuOutset, gpuOutsetCoverage, | 
|  | &gpuDomain); | 
|  |  | 
|  | // Visualize the coverage values across the clamping rectangle, but test pixels outside | 
|  | // of the "outer" rect since some quad edges can be outset extra far. | 
|  | SkPaint pixelPaint; | 
|  | pixelPaint.setAntiAlias(true); | 
|  | SkRect covRect = fOuterRect.makeOutset(2.f, 2.f); | 
|  | for (SkScalar py = covRect.fTop; py < covRect.fBottom; py += 1.f) { | 
|  | for (SkScalar px = covRect.fLeft; px < covRect.fRight; px += 1.f) { | 
|  | // px and py are the top-left corner of the current pixel, so get center's | 
|  | // coordinate | 
|  | SkPoint pixelCenter = {px + 0.5f, py + 0.5f}; | 
|  | SkScalar coverage; | 
|  | if (fCoverageMode == CoverageMode::kArea) { | 
|  | coverage = get_area_coverage(fEdgeAA, fCorners, pixelCenter); | 
|  | } else if (fCoverageMode == CoverageMode::kEdgeDistance) { | 
|  | coverage = get_edge_dist_coverage(fEdgeAA, fCorners, outsets, insets, | 
|  | pixelCenter); | 
|  | } else { | 
|  | SkASSERT(fCoverageMode == CoverageMode::kGPUMesh); | 
|  | coverage = get_framed_coverage(gpuOutset, gpuOutsetCoverage, | 
|  | gpuInset, gpuInsetCoverage, gpuDomain, | 
|  | pixelCenter); | 
|  | } | 
|  |  | 
|  | SkRect pixelRect = SkRect::MakeXYWH(px, py, 1.f, 1.f); | 
|  | pixelRect.inset(0.1f, 0.1f); | 
|  |  | 
|  | SkScalar a = 1.f - 0.5f * coverage; | 
|  | pixelPaint.setColor4f({a, a, a, 1.f}, nullptr); | 
|  | canvas->drawRect(pixelRect, pixelPaint); | 
|  |  | 
|  | pixelPaint.setColor(coverage > 0.f ? SK_ColorGREEN : SK_ColorRED); | 
|  | pixelRect.inset(0.38f, 0.38f); | 
|  | canvas->drawRect(pixelRect, pixelPaint); | 
|  | } | 
|  | } | 
|  |  | 
|  | linePaint.setPathEffect(dashes); | 
|  | // Draw the inset/outset "infinite" lines | 
|  | if (fCoverageMode == CoverageMode::kEdgeDistance) { | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | if (fEdgeAA[i]) { | 
|  | linePaint.setColor(SK_ColorBLUE); | 
|  | draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]); | 
|  | linePaint.setColor(SK_ColorGREEN); | 
|  | draw_extended_line(canvas, linePaint, insets[i * 2], insets[i * 2 + 1]); | 
|  | } else { | 
|  | // Both outset and inset are the same line, so only draw one in cyan | 
|  | linePaint.setColor(SK_ColorCYAN); | 
|  | draw_extended_line(canvas, linePaint, outsets[i * 2], outsets[i * 2 + 1]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | linePaint.setPathEffect(nullptr); | 
|  | // What is tessellated using GrQuadPerEdgeAA | 
|  | if (fCoverageMode == CoverageMode::kGPUMesh) { | 
|  | SkPath outsetPath; | 
|  | outsetPath.addPoly(gpuOutset, 4, true); | 
|  | linePaint.setColor(SK_ColorBLUE); | 
|  | canvas->drawPath(outsetPath, linePaint); | 
|  |  | 
|  | SkPath insetPath; | 
|  | insetPath.addPoly(gpuInset, 4, true); | 
|  | linePaint.setColor(SK_ColorGREEN); | 
|  | canvas->drawPath(insetPath, linePaint); | 
|  |  | 
|  | SkPaint domainPaint = linePaint; | 
|  | domainPaint.setStrokeWidth(2.f / kViewScale); | 
|  | domainPaint.setPathEffect(dashes); | 
|  | domainPaint.setColor(SK_ColorMAGENTA); | 
|  | canvas->drawRect(gpuDomain, domainPaint); | 
|  | } | 
|  |  | 
|  | // Draw the edges of the true quad as a solid line | 
|  | SkPath path; | 
|  | path.addPoly(fCorners, 4, true); | 
|  | linePaint.setColor(SK_ColorBLACK); | 
|  | canvas->drawPath(path, linePaint); | 
|  | } else { | 
|  | // Draw the edges of the true quad as a solid *red* line | 
|  | SkPath path; | 
|  | path.addPoly(fCorners, 4, true); | 
|  | linePaint.setColor(SK_ColorRED); | 
|  | linePaint.setPathEffect(nullptr); | 
|  | canvas->drawPath(path, linePaint); | 
|  | } | 
|  |  | 
|  | // Draw the four clickable corners as circles | 
|  | circlePaint.setColor(valid ? SK_ColorBLACK : SK_ColorRED); | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | canvas->drawCircle(fCorners[i], 5.f / kViewScale, circlePaint); | 
|  | } | 
|  | } | 
|  |  | 
|  | Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) override; | 
|  | bool onClick(Sample::Click*) override; | 
|  | bool onChar(SkUnichar) override; | 
|  | SkString name() override { return SkString("DegenerateQuad"); } | 
|  |  | 
|  | private: | 
|  | class Click; | 
|  |  | 
|  | enum class CoverageMode { | 
|  | kArea, kEdgeDistance, kGPUMesh | 
|  | }; | 
|  |  | 
|  | const SkRect fOuterRect; | 
|  | SkPoint fCorners[4]; // TL, TR, BR, BL | 
|  | bool fEdgeAA[4]; // T, R, B, L | 
|  | CoverageMode fCoverageMode; | 
|  |  | 
|  | bool isValid() const { | 
|  | SkPath path; | 
|  | path.addPoly(fCorners, 4, true); | 
|  | return path.isConvex(); | 
|  | } | 
|  |  | 
|  | void getTessellatedPoints(SkPoint inset[4], SkScalar insetCoverage[4], SkPoint outset[4], | 
|  | SkScalar outsetCoverage[4], SkRect* domain) const { | 
|  | // Fixed vertex spec for extracting the picture frame geometry | 
|  | static const GrQuadPerEdgeAA::VertexSpec kSpec = | 
|  | {GrQuad::Type::kGeneral, GrQuadPerEdgeAA::ColorType::kNone, | 
|  | GrQuad::Type::kAxisAligned, false, GrQuadPerEdgeAA::Subset::kNo, | 
|  | GrAAType::kCoverage, false, GrQuadPerEdgeAA::IndexBufferOption::kPictureFramed}; | 
|  | static const GrQuad kIgnored(SkRect::MakeEmpty()); | 
|  |  | 
|  | GrQuadAAFlags flags = GrQuadAAFlags::kNone; | 
|  | flags |= fEdgeAA[0] ? GrQuadAAFlags::kTop : GrQuadAAFlags::kNone; | 
|  | flags |= fEdgeAA[1] ? GrQuadAAFlags::kRight : GrQuadAAFlags::kNone; | 
|  | flags |= fEdgeAA[2] ? GrQuadAAFlags::kBottom : GrQuadAAFlags::kNone; | 
|  | flags |= fEdgeAA[3] ? GrQuadAAFlags::kLeft : GrQuadAAFlags::kNone; | 
|  |  | 
|  | GrQuad quad = GrQuad::MakeFromSkQuad(fCorners, SkMatrix::I()); | 
|  |  | 
|  | float vertices[56]; // 2 quads, with x, y, coverage, and geometry domain (7 floats x 8 vert) | 
|  | GrQuadPerEdgeAA::Tessellator tessellator(kSpec, (char*) vertices); | 
|  | tessellator.append(&quad, nullptr, {1.f, 1.f, 1.f, 1.f}, | 
|  | SkRect::MakeEmpty(), flags); | 
|  |  | 
|  | // The first quad in vertices is the inset, then the outset, but they | 
|  | // are ordered TL, BL, TR, BR so un-interleave coverage and re-arrange | 
|  | inset[0] = {vertices[0], vertices[1]}; // TL | 
|  | insetCoverage[0] = vertices[2]; | 
|  | inset[3] = {vertices[7], vertices[8]}; // BL | 
|  | insetCoverage[3] = vertices[9]; | 
|  | inset[1] = {vertices[14], vertices[15]}; // TR | 
|  | insetCoverage[1] = vertices[16]; | 
|  | inset[2] = {vertices[21], vertices[22]}; // BR | 
|  | insetCoverage[2] = vertices[23]; | 
|  |  | 
|  | outset[0] = {vertices[28], vertices[29]}; // TL | 
|  | outsetCoverage[0] = vertices[30]; | 
|  | outset[3] = {vertices[35], vertices[36]}; // BL | 
|  | outsetCoverage[3] = vertices[37]; | 
|  | outset[1] = {vertices[42], vertices[43]}; // TR | 
|  | outsetCoverage[1] = vertices[44]; | 
|  | outset[2] = {vertices[49], vertices[50]}; // BR | 
|  | outsetCoverage[2] = vertices[51]; | 
|  |  | 
|  | *domain = {vertices[52], vertices[53], vertices[54], vertices[55]}; | 
|  | } | 
|  |  | 
|  | using INHERITED = Sample; | 
|  | }; | 
|  |  | 
|  | class DegenerateQuadSample::Click : public Sample::Click { | 
|  | public: | 
|  | Click(const SkRect& clamp, int index) | 
|  | : fOuterRect(clamp) | 
|  | , fIndex(index) {} | 
|  |  | 
|  | void doClick(SkPoint points[4]) { | 
|  | if (fIndex >= 0) { | 
|  | this->drag(&points[fIndex]); | 
|  | } else { | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | this->drag(&points[i]); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | private: | 
|  | SkRect fOuterRect; | 
|  | int fIndex; | 
|  |  | 
|  | void drag(SkPoint* point) { | 
|  | SkPoint delta = fCurr - fPrev; | 
|  | *point += SkPoint::Make(delta.x() / kViewScale, delta.y() / kViewScale); | 
|  | point->fX = std::min(fOuterRect.fRight, std::max(point->fX, fOuterRect.fLeft)); | 
|  | point->fY = std::min(fOuterRect.fBottom, std::max(point->fY, fOuterRect.fTop)); | 
|  | } | 
|  | }; | 
|  |  | 
|  | Sample::Click* DegenerateQuadSample::onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey) { | 
|  | SkPoint inCTM = SkPoint::Make((x - kViewOffset) / kViewScale, (y - kViewOffset) / kViewScale); | 
|  | for (int i = 0; i < 4; ++i) { | 
|  | if ((fCorners[i] - inCTM).length() < 10.f / kViewScale) { | 
|  | return new Click(fOuterRect, i); | 
|  | } | 
|  | } | 
|  | return new Click(fOuterRect, -1); | 
|  | } | 
|  |  | 
|  | bool DegenerateQuadSample::onClick(Sample::Click* click) { | 
|  | Click* myClick = (Click*) click; | 
|  | myClick->doClick(fCorners); | 
|  | return true; | 
|  | } | 
|  |  | 
|  | bool DegenerateQuadSample::onChar(SkUnichar code) { | 
|  | switch(code) { | 
|  | case '1': | 
|  | fEdgeAA[0] = !fEdgeAA[0]; | 
|  | return true; | 
|  | case '2': | 
|  | fEdgeAA[1] = !fEdgeAA[1]; | 
|  | return true; | 
|  | case '3': | 
|  | fEdgeAA[2] = !fEdgeAA[2]; | 
|  | return true; | 
|  | case '4': | 
|  | fEdgeAA[3] = !fEdgeAA[3]; | 
|  | return true; | 
|  | case 'q': | 
|  | fCoverageMode = CoverageMode::kArea; | 
|  | return true; | 
|  | case 'w': | 
|  | fCoverageMode = CoverageMode::kEdgeDistance; | 
|  | return true; | 
|  | case 'e': | 
|  | fCoverageMode = CoverageMode::kGPUMesh; | 
|  | return true; | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | DEF_SAMPLE(return new DegenerateQuadSample(SkRect::MakeWH(4.f, 4.f));) |