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