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