1 /*
2  * Copyright 2011 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 "include/core/SkCanvas.h"
9 #include "include/core/SkColorPriv.h"
10 #include "include/core/SkFont.h"
11 #include "include/core/SkPaint.h"
12 #include "include/core/SkPathBuilder.h"
13 #include "include/utils/SkRandom.h"
14 #include "samplecode/Sample.h"
15 #include "src/core/SkClipOpPriv.h"
16 #include "src/core/SkPathPriv.h"
17 #include "tools/Resources.h"
18 
19 constexpr int W = 150;
20 constexpr int H = 200;
21 
show_text(SkCanvas * canvas,bool doAA)22 static void show_text(SkCanvas* canvas, bool doAA) {
23     SkRandom rand;
24     SkPaint paint;
25     SkFont font(nullptr, 20);
26     font.setEdging(doAA ? SkFont::Edging::kSubpixelAntiAlias : SkFont::Edging::kAlias);
27 
28     for (int i = 0; i < 200; ++i) {
29         paint.setColor((SK_A32_MASK << SK_A32_SHIFT) | rand.nextU());
30         canvas->drawString("Hamburgefons", rand.nextSScalar1() * W, rand.nextSScalar1() * H + 20,
31                            font, paint);
32     }
33 }
34 
show_fill(SkCanvas * canvas,bool doAA)35 static void show_fill(SkCanvas* canvas, bool doAA) {
36     SkRandom rand;
37     SkPaint paint;
38     paint.setAntiAlias(doAA);
39 
40     for (int i = 0; i < 50; ++i) {
41         SkRect r;
42 
43         r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H,
44                   rand.nextUScalar1() * W, rand.nextUScalar1() * H);
45         paint.setColor(rand.nextU());
46         canvas->drawRect(r, paint);
47 
48         r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H,
49                   rand.nextUScalar1() * W, rand.nextUScalar1() * H);
50         paint.setColor(rand.nextU());
51         canvas->drawOval(r, paint);
52     }
53 }
54 
randRange(SkRandom & rand,SkScalar min,SkScalar max)55 static SkScalar randRange(SkRandom& rand, SkScalar min, SkScalar max) {
56     SkASSERT(min <= max);
57     return min + rand.nextUScalar1() * (max - min);
58 }
59 
show_stroke(SkCanvas * canvas,bool doAA,SkScalar strokeWidth,int n)60 static void show_stroke(SkCanvas* canvas, bool doAA, SkScalar strokeWidth, int n) {
61     SkRandom rand;
62     SkPaint paint;
63     paint.setAntiAlias(doAA);
64     paint.setStyle(SkPaint::kStroke_Style);
65     paint.setStrokeWidth(strokeWidth);
66 
67     for (int i = 0; i < n; ++i) {
68         SkRect r;
69 
70         r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H,
71                   rand.nextUScalar1() * W, rand.nextUScalar1() * H);
72         paint.setColor(rand.nextU());
73         canvas->drawRect(r, paint);
74 
75         r.setXYWH(rand.nextSScalar1() * W, rand.nextSScalar1() * H,
76                   rand.nextUScalar1() * W, rand.nextUScalar1() * H);
77         paint.setColor(rand.nextU());
78         canvas->drawOval(r, paint);
79 
80         const SkScalar minx = -SkIntToScalar(W)/4;
81         const SkScalar maxx = 5*SkIntToScalar(W)/4;
82         const SkScalar miny = -SkIntToScalar(H)/4;
83         const SkScalar maxy = 5*SkIntToScalar(H)/4;
84         paint.setColor(rand.nextU());
85         canvas->drawLine(randRange(rand, minx, maxx), randRange(rand, miny, maxy),
86                          randRange(rand, minx, maxx), randRange(rand, miny, maxy),
87                          paint);
88     }
89 }
90 
show_hair(SkCanvas * canvas,bool doAA)91 static void show_hair(SkCanvas* canvas, bool doAA) {
92     show_stroke(canvas, doAA, 0, 150);
93 }
94 
show_thick(SkCanvas * canvas,bool doAA)95 static void show_thick(SkCanvas* canvas, bool doAA) {
96     show_stroke(canvas, doAA, SkIntToScalar(5), 50);
97 }
98 
99 typedef void (*CanvasProc)(SkCanvas*, bool);
100 
101 class ClipView : public Sample {
name()102     SkString name() override { return SkString("Clip"); }
103 
onDrawContent(SkCanvas * canvas)104     void onDrawContent(SkCanvas* canvas) override {
105         canvas->drawColor(SK_ColorWHITE);
106         canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
107 
108         static const CanvasProc gProc[] = {
109             show_text, show_thick, show_hair, show_fill
110         };
111 
112         SkRect r = { 0, 0, SkIntToScalar(W), SkIntToScalar(H) };
113         r.inset(SK_Scalar1 / 4, SK_Scalar1 / 4);
114         SkPath clipPath = SkPathBuilder().addRRect(SkRRect::MakeRectXY(r, 20, 20)).detach();
115 
116 //        clipPath.toggleInverseFillType();
117 
118         for (int aa = 0; aa <= 1; ++aa) {
119             canvas->save();
120             for (size_t i = 0; i < SK_ARRAY_COUNT(gProc); ++i) {
121                 canvas->save();
122                 canvas->clipPath(clipPath, kIntersect_SkClipOp, SkToBool(aa));
123 //                canvas->drawColor(SK_ColorWHITE);
124                 gProc[i](canvas, SkToBool(aa));
125                 canvas->restore();
126                 canvas->translate(W * SK_Scalar1 * 8 / 7, 0);
127             }
128             canvas->restore();
129             canvas->translate(0, H * SK_Scalar1 * 8 / 7);
130         }
131     }
132 };
133 
134 DEF_SAMPLE( return new ClipView(); )
135 
136 ///////////////////////////////////////////////////////////////////////////////
137 
138 struct SkHalfPlane {
139     SkScalar fA, fB, fC;
140 
evalSkHalfPlane141     SkScalar eval(SkScalar x, SkScalar y) const {
142         return fA * x + fB * y + fC;
143     }
operator ()SkHalfPlane144     SkScalar operator()(SkScalar x, SkScalar y) const { return this->eval(x, y); }
145 
twoPtsSkHalfPlane146     bool twoPts(SkPoint pts[2]) const {
147         // normalize plane to help with the perpendicular step, below
148         SkScalar len = SkScalarSqrt(fA*fA + fB*fB);
149         if (!len) {
150             return false;
151         }
152         SkScalar denom = SkScalarInvert(len);
153         SkScalar a = fA * denom;
154         SkScalar b = fB * denom;
155         SkScalar c = fC * denom;
156 
157         // We compute p0 on the half-plane by setting one of the components to 0
158         // We compute p1 by stepping from p0 along a perpendicular to the normal
159         if (b) {
160             pts[0] = { 0, -c / b };
161             pts[1] = { b, pts[0].fY - a};
162         } else if (a) {
163             pts[0] = { -c / a,        0 };
164             pts[1] = { pts[0].fX + b, -a };
165         } else {
166             return false;
167         }
168 
169         SkASSERT(SkScalarNearlyZero(this->operator()(pts[0].fX, pts[0].fY)));
170         SkASSERT(SkScalarNearlyZero(this->operator()(pts[1].fX, pts[1].fY)));
171         return true;
172     }
173 
174     enum Result {
175         kAllNegative,
176         kAllPositive,
177         kMixed
178     };
testSkHalfPlane179     Result test(const SkRect& bounds) const {
180         SkPoint diagMin, diagMax;
181         if (fA >= 0) {
182             diagMin.fX = bounds.fLeft;
183             diagMax.fX = bounds.fRight;
184         } else {
185             diagMin.fX = bounds.fRight;
186             diagMax.fX = bounds.fLeft;
187         }
188         if (fB >= 0) {
189             diagMin.fY = bounds.fTop;
190             diagMax.fY = bounds.fBottom;
191         } else {
192             diagMin.fY = bounds.fBottom;
193             diagMax.fY = bounds.fTop;
194         }
195         SkScalar test = this->eval(diagMin.fX, diagMin.fY);
196         SkScalar sign = test*this->eval(diagMax.fX, diagMin.fY);
197         if (sign > 0) {
198             // the path is either all on one side of the half-plane or the other
199             if (test < 0) {
200                 return kAllNegative;
201             } else {
202                 return kAllPositive;
203             }
204         }
205         return kMixed;
206     }
207 };
208 
209 #include "src/core/SkEdgeClipper.h"
210 
clip(const SkPath & path,SkPoint p0,SkPoint p1)211 static SkPath clip(const SkPath& path, SkPoint p0, SkPoint p1) {
212     SkMatrix mx, inv;
213     SkVector v = p1 - p0;
214     mx.setAll(v.fX, -v.fY, p0.fX,
215               v.fY,  v.fX, p0.fY,
216                  0,     0,     1);
217     SkAssertResult(mx.invert(&inv));
218 
219     SkPath rotated;
220     path.transform(inv, &rotated);
221 
222     SkScalar big = 1e28f;
223     SkRect clip = {-big, 0, big, big };
224 
225     struct Rec {
226         SkPathBuilder   fResult;
227         SkPoint         fPrev = {0, 0};
228     } rec;
229 
230     SkEdgeClipper::ClipPath(rotated, clip, false,
231                             [](SkEdgeClipper* clipper, bool newCtr, void* ctx) {
232         Rec* rec = (Rec*)ctx;
233 
234         bool addLineTo = false;
235         SkPoint      pts[4];
236         SkPath::Verb verb;
237         while ((verb = clipper->next(pts)) != SkPath::kDone_Verb) {
238             if (newCtr) {
239                 rec->fResult.moveTo(pts[0]);
240                 rec->fPrev = pts[0];
241                 newCtr = false;
242             }
243 
244             if (addLineTo || pts[0] != rec->fPrev) {
245                 rec->fResult.lineTo(pts[0]);
246             }
247 
248             switch (verb) {
249                 case SkPath::kLine_Verb:
250                     rec->fResult.lineTo(pts[1]);
251                     rec->fPrev = pts[1];
252                     break;
253                 case SkPath::kQuad_Verb:
254                     rec->fResult.quadTo(pts[1], pts[2]);
255                     rec->fPrev = pts[2];
256                     break;
257                 case SkPath::kCubic_Verb:
258                     rec->fResult.cubicTo(pts[1], pts[2], pts[3]);
259                     rec->fPrev = pts[3];
260                     break;
261                 default: break;
262             }
263             addLineTo = true;
264         }
265     }, &rec);
266 
267     return rec.fResult.detach().makeTransform(mx);
268 }
269 
draw_halfplane(SkCanvas * canvas,SkPoint p0,SkPoint p1,SkColor c)270 static void draw_halfplane(SkCanvas* canvas, SkPoint p0, SkPoint p1, SkColor c) {
271     SkVector v = p1 - p0;
272     p0 = p0 - v * 1000;
273     p1 = p1 + v * 1000;
274 
275     SkPaint paint;
276     paint.setColor(c);
277     canvas->drawLine(p0, p1, paint);
278 }
279 
make_path()280 static SkPath make_path() {
281     SkRandom rand;
282     auto rand_pt = [&rand]() {
283         auto x = rand.nextF();
284         auto y = rand.nextF();
285         return SkPoint{x * 400, y * 400};
286     };
287 
288     SkPathBuilder path;
289     for (int i = 0; i < 4; ++i) {
290         SkPoint pts[6];
291         for (auto& p : pts) {
292             p = rand_pt();
293         }
294         path.moveTo(pts[0]).quadTo(pts[1], pts[2]).quadTo(pts[3], pts[4]).lineTo(pts[5]);
295     }
296     return path.detach();
297 }
298 
299 class HalfPlaneView : public Sample {
300     SkPoint fPts[2];
301     SkPath fPath;
302 
name()303     SkString name() override { return SkString("halfplane"); }
304 
onOnceBeforeDraw()305     void onOnceBeforeDraw() override {
306         fPts[0] = {0, 0};
307         fPts[1] = {3, 2};
308         fPath = make_path();
309     }
310 
onDrawContent(SkCanvas * canvas)311     void onDrawContent(SkCanvas* canvas) override {
312         SkPaint paint;
313 
314         paint.setColor({0.5f, 0.5f, 0.5f, 1.0f}, nullptr);
315         canvas->drawPath(fPath, paint);
316 
317         paint.setColor({0, 0, 0, 1}, nullptr);
318 
319         canvas->drawPath(clip(fPath, fPts[0], fPts[1]), paint);
320 
321         draw_halfplane(canvas, fPts[0], fPts[1], SK_ColorRED);
322     }
323 
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey modi)324     Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
325         return new Click;
326     }
327 
onClick(Click * click)328     bool onClick(Click* click) override {
329         fPts[0] = click->fCurr;
330         fPts[1] = fPts[0] + SkPoint{3, 2};
331         return true;
332     }
333 };
DEF_SAMPLE(return new HalfPlaneView ();)334 DEF_SAMPLE( return new HalfPlaneView(); )
335 
336 static void draw_halfplane(SkCanvas* canvas, const SkHalfPlane& p, SkColor c) {
337     SkPoint pts[2];
338     p.twoPts(pts);
339     draw_halfplane(canvas, pts[0], pts[1], c);
340 }
341 
compute_half_planes(const SkMatrix & mx,SkScalar W,SkScalar H,SkHalfPlane planes[4])342 static void compute_half_planes(const SkMatrix& mx, SkScalar W, SkScalar H,
343                                 SkHalfPlane planes[4]) {
344     SkScalar a = mx[0], b = mx[1], c = mx[2],
345              d = mx[3], e = mx[4], f = mx[5],
346              g = mx[6], h = mx[7], i = mx[8];
347 
348     planes[0] = { 2*g - 2*a/W,  2*h - 2*b/W,  2*i - 2*c/W };
349     planes[1] = { 2*a/W,        2*b/W,        2*c/W };
350     planes[2] = { 2*g - 2*d/H,  2*h - 2*e/H,  2*i - 2*f/H };
351     planes[3] = { 2*d/H,        2*e/H,        2*f/H };
352 }
353 
354 class HalfPlaneView2 : public Sample {
355     SkPoint fPts[4];
356     SkPath fPath;
357 
name()358     SkString name() override { return SkString("halfplane2"); }
359 
onOnceBeforeDraw()360     void onOnceBeforeDraw() override {
361         fPath = make_path();
362         SkRect r = fPath.getBounds();
363         r.toQuad(fPts);
364     }
365 
onDrawContent(SkCanvas * canvas)366     void onDrawContent(SkCanvas* canvas) override {
367         SkMatrix mx;
368         {
369             SkRect r = fPath.getBounds();
370             SkPoint src[4];
371             r.toQuad(src);
372             mx.setPolyToPoly(src, fPts, 4);
373         }
374 
375         SkPaint paint;
376         canvas->drawPath(fPath, paint);
377 
378         canvas->save();
379         canvas->concat(mx);
380         paint.setColor(0x40FF0000);
381         canvas->drawPath(fPath, paint);
382         canvas->restore();
383 
384         // draw the frame
385         paint.setStrokeWidth(10);
386         paint.setColor(SK_ColorGREEN);
387         canvas->drawPoints(SkCanvas::kPoints_PointMode, 4, fPts, paint);
388 
389         // draw the half-planes
390         SkHalfPlane planes[4];
391         compute_half_planes(mx, 400, 400, planes);
392         for (auto& p : planes) {
393             draw_halfplane(canvas, p, SK_ColorRED);
394         }
395     }
396 
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey modi)397     Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
398         SkScalar r = 8;
399         SkRect rect = SkRect::MakeXYWH(x - r, y - r, 2*r, 2*r);
400         for (int i = 0; i < 4; ++i) {
401             if (rect.contains(fPts[i].fX, fPts[i].fY)) {
402                 Click* c = new Click;
403                 c->fMeta.setS32("index", i);
404                 return c;
405             }
406         }
407         return nullptr;
408     }
409 
onClick(Click * click)410     bool onClick(Click* click) override {
411         int32_t index;
412         SkAssertResult(click->fMeta.findS32("index", &index));
413         SkASSERT(index >= 0 && index < 4);
414         fPts[index] = click->fCurr;
415         return true;
416     }
417 };
DEF_SAMPLE(return new HalfPlaneView2 ();)418 DEF_SAMPLE( return new HalfPlaneView2(); )
419 
420 static SkM44 inv(const SkM44& m) {
421     SkM44 inverse;
422     SkAssertResult(m.invert(&inverse));
423     return inverse;
424 }
425 
half_plane_w0(const SkMatrix & m)426 static SkHalfPlane half_plane_w0(const SkMatrix& m) {
427     return { m[SkMatrix::kMPersp0], m[SkMatrix::kMPersp1], m[SkMatrix::kMPersp2] - 0.05f };
428 }
429 
430 class SampleCameraView : public Sample {
431     float   fNear = 0.05f;
432     float   fFar = 4;
433     float   fAngle = SK_ScalarPI / 4;
434 
435     SkV3    fEye { 0, 0, 1.0f/tan(fAngle/2) - 1 };
436     SkV3    fCOA { 0, 0, 0 };
437     SkV3    fUp  { 0, 1, 0 };
438 
439     SkM44  fRot;
440     SkV3   fTrans;
441 
rotate(float x,float y,float z)442     void rotate(float x, float y, float z) {
443         SkM44 r;
444         if (x) {
445             r.setRotateUnit({1, 0, 0}, x);
446         } else if (y) {
447             r.setRotateUnit({0, 1, 0}, y);
448         } else {
449             r.setRotateUnit({0, 0, 1}, z);
450         }
451         fRot = r * fRot;
452     }
453 
454 public:
get44(const SkRect & r) const455     SkM44 get44(const SkRect& r) const {
456         SkScalar w = r.width();
457         SkScalar h = r.height();
458 
459         SkM44 camera = Sk3LookAt(fEye, fCOA, fUp),
460               perspective = Sk3Perspective(fNear, fFar, fAngle),
461               translate = SkM44::Translate(fTrans.x, fTrans.y, fTrans.z),
462               viewport = SkM44::Translate(r.centerX(), r.centerY(), 0) *
463                          SkM44::Scale(w*0.5f, h*0.5f, 1);
464 
465         return viewport * perspective * camera * translate * fRot * inv(viewport);
466     }
467 
onChar(SkUnichar uni)468     bool onChar(SkUnichar uni) override {
469         float delta = SK_ScalarPI / 30;
470         switch (uni) {
471             case '8': this->rotate( delta, 0, 0); return true;
472             case '2': this->rotate(-delta, 0, 0); return true;
473             case '4': this->rotate(0,  delta, 0); return true;
474             case '6': this->rotate(0, -delta, 0); return true;
475             case '-': this->rotate(0, 0,  delta); return true;
476             case '+': this->rotate(0, 0, -delta); return true;
477 
478             case 'i': fTrans.z += 0.1f; SkDebugf("z %g\n", fTrans.z); return true;
479             case 'k': fTrans.z -= 0.1f; SkDebugf("z %g\n", fTrans.z); return true;
480 
481             case 'n': fNear += 0.1f; SkDebugf("near %g\n", fNear); return true;
482             case 'N': fNear -= 0.1f; SkDebugf("near %g\n", fNear); return true;
483             case 'f': fFar  += 0.1f; SkDebugf("far  %g\n", fFar); return true;
484             case 'F': fFar  -= 0.1f; SkDebugf("far  %g\n", fFar); return true;
485             default: break;
486         }
487         return false;
488     }
489 };
490 
491 class HalfPlaneView3 : public SampleCameraView {
492     SkPath fPath;
493     sk_sp<SkShader> fShader;
494     bool fShowUnclipped = false;
495 
name()496     SkString name() override { return SkString("halfplane3"); }
497 
onOnceBeforeDraw()498     void onOnceBeforeDraw() override {
499         fPath = make_path();
500         fShader = GetResourceAsImage("images/mandrill_128.png")
501                         ->makeShader(SkMatrix::Scale(3, 3));
502     }
503 
onChar(SkUnichar uni)504     bool onChar(SkUnichar uni) override {
505         switch (uni) {
506             case 'u': fShowUnclipped = !fShowUnclipped; return true;
507             default: break;
508         }
509         return this->SampleCameraView::onChar(uni);
510     }
511 
onDrawContent(SkCanvas * canvas)512     void onDrawContent(SkCanvas* canvas) override {
513         SkM44 mx = this->get44({0, 0, 400, 400});
514 
515         SkPaint paint;
516         paint.setColor({0.75, 0.75, 0.75, 1});
517         canvas->drawPath(fPath, paint);
518 
519         paint.setShader(fShader);
520 
521         if (fShowUnclipped) {
522             canvas->save();
523             canvas->concat(mx);
524             paint.setAlphaf(0.33f);
525             canvas->drawPath(fPath, paint);
526             paint.setAlphaf(1.f);
527             canvas->restore();
528         }
529 
530 
531         SkColor planeColor = SK_ColorBLUE;
532         SkPath clippedPath, *path = &fPath;
533         if (SkPathPriv::PerspectiveClip(fPath, mx.asM33(), &clippedPath)) {
534             path = &clippedPath;
535             planeColor = SK_ColorRED;
536         }
537         canvas->save();
538         canvas->concat(mx);
539         canvas->drawPath(*path, paint);
540         canvas->restore();
541 
542         SkHalfPlane hpw = half_plane_w0(mx.asM33());
543         draw_halfplane(canvas, hpw, planeColor);
544     }
545 };
546 DEF_SAMPLE( return new HalfPlaneView3(); )
547 
548 class HalfPlaneCoons : public SampleCameraView {
549     SkPoint fPatch[12];
550     SkColor fColors[4] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorBLACK };
551     SkPoint fTex[4]    = {{0, 0}, {256, 0}, {256, 256}, {0, 256}};
552     sk_sp<SkShader> fShader;
553 
554     bool fShowHandles = false;
555     bool fShowSkeleton = false;
556     bool fShowTex = false;
557 
name()558     SkString name() override { return SkString("halfplane-coons"); }
559 
onOnceBeforeDraw()560     void onOnceBeforeDraw() override {
561         fPatch[0] = {   0, 0 };
562         fPatch[1] = { 100, 0 };
563         fPatch[2] = { 200, 0 };
564         fPatch[3] = { 300, 0 };
565         fPatch[4] = { 300, 100 };
566         fPatch[5] = { 300, 200 };
567         fPatch[6] = { 300, 300 };
568         fPatch[7] = { 200, 300 };
569         fPatch[8] = { 100, 300 };
570         fPatch[9] = {   0, 300 };
571         fPatch[10] = {  0, 200 };
572         fPatch[11] = {  0, 100 };
573 
574         fShader = GetResourceAsImage("images/mandrill_256.png")->makeShader();
575     }
576 
onDrawContent(SkCanvas * canvas)577     void onDrawContent(SkCanvas* canvas) override {
578         SkPaint paint;
579 
580         canvas->save();
581         canvas->concat(this->get44({0, 0, 300, 300}));
582 
583         const SkPoint* tex = nullptr;
584         const SkColor* col = nullptr;
585         if (!fShowSkeleton) {
586             if (fShowTex) {
587                 paint.setShader(fShader);
588                 tex = fTex;
589             } else {
590                 col = fColors;
591             }
592         }
593         canvas->drawPatch(fPatch, col, tex, SkBlendMode::kSrc, paint);
594         paint.setShader(nullptr);
595 
596         if (fShowHandles) {
597             paint.setAntiAlias(true);
598             paint.setStrokeCap(SkPaint::kRound_Cap);
599             paint.setStrokeWidth(8);
600             canvas->drawPoints(SkCanvas::kPoints_PointMode, 12, fPatch, paint);
601             paint.setColor(SK_ColorWHITE);
602             paint.setStrokeWidth(6);
603             canvas->drawPoints(SkCanvas::kPoints_PointMode, 12, fPatch, paint);
604         }
605 
606         canvas->restore();
607     }
608 
onFindClickHandler(SkScalar x,SkScalar y,skui::ModifierKey modi)609     Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
610         auto dist = [](SkPoint a, SkPoint b) { return (b - a).length(); };
611 
612         const float tol = 15;
613         for (int i = 0; i < 12; ++i) {
614             if (dist({x,y}, fPatch[i]) <= tol) {
615                 return new Click([this, i](Click* c) {
616                     fPatch[i] = c->fCurr;
617                     return true;
618                 });
619             }
620         }
621         return nullptr;
622     }
623 
onChar(SkUnichar uni)624     bool onChar(SkUnichar uni) override {
625         switch (uni) {
626             case 'h': fShowHandles = !fShowHandles; return true;
627             case 'k': fShowSkeleton = !fShowSkeleton; return true;
628             case 't': fShowTex = !fShowTex; return true;
629             default: break;
630         }
631         return this->SampleCameraView::onChar(uni);
632     }
633 
634 };
635 DEF_SAMPLE( return new HalfPlaneCoons(); )
636