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/SkBitmap.h"
9#include "include/core/SkCanvas.h"
10#include "include/core/SkColorFilter.h"
11#include "include/core/SkColorPriv.h"
12#include "include/core/SkFont.h"
13#include "include/core/SkGraphics.h"
14#include "include/core/SkPathBuilder.h"
15#include "include/core/SkRegion.h"
16#include "include/core/SkShader.h"
17#include "include/core/SkTime.h"
18#include "include/core/SkTypeface.h"
19#include "include/effects/SkGradientShader.h"
20#include "include/utils/SkParsePath.h"
21#include "samplecode/Sample.h"
22#include "src/utils/SkUTF.h"
23#include "tools/timer/TimeUtils.h"
24
25#include "src/core/SkGeometry.h"
26
27#include <stdlib.h>
28
29// http://code.google.com/p/skia/issues/detail?id=32
30static void test_cubic() {
31    SkPoint src[4] = {
32        { 556.25000f, 523.03003f },
33        { 556.23999f, 522.96002f },
34        { 556.21997f, 522.89001f },
35        { 556.21997f, 522.82001f }
36    };
37    SkPoint dst[11];
38    dst[10].set(42, -42);   // one past the end, that we don't clobber these
39    SkScalar tval[] = { 0.33333334f, 0.99999994f };
40
41    SkChopCubicAt(src, dst, tval, 2);
42
43#if 0
44    for (int i = 0; i < 11; i++) {
45        SkDebugf("--- %d [%g %g]\n", i, dst[i].fX, dst[i].fY);
46    }
47#endif
48}
49
50static void test_cubic2() {
51    const char* str = "M2242 -590088L-377758 9.94099e+07L-377758 9.94099e+07L2242 -590088Z";
52    SkPath path;
53    SkParsePath::FromSVGString(str, &path);
54
55    {
56        SkRect r = path.getBounds();
57        SkIRect ir;
58        r.round(&ir);
59        SkDebugf("[%g %g %g %g] [%x %x %x %x]\n",
60                SkScalarToDouble(r.fLeft), SkScalarToDouble(r.fTop),
61                SkScalarToDouble(r.fRight), SkScalarToDouble(r.fBottom),
62                ir.fLeft, ir.fTop, ir.fRight, ir.fBottom);
63    }
64
65    SkBitmap bitmap;
66    bitmap.allocN32Pixels(300, 200);
67
68    SkCanvas canvas(bitmap);
69    SkPaint paint;
70    paint.setAntiAlias(true);
71    canvas.drawPath(path, paint);
72}
73
74class PathView : public Sample {
75    SkScalar fPrevSecs;
76public:
77    SkScalar fDStroke, fStroke, fMinStroke, fMaxStroke;
78    SkPath fPath[6];
79    bool fShowHairline;
80    bool fOnce;
81
82    PathView() {
83        fPrevSecs = 0;
84        fOnce = false;
85    }
86
87    void init() {
88        if (fOnce) {
89            return;
90        }
91        fOnce = true;
92
93        test_cubic();
94        test_cubic2();
95
96        fShowHairline = false;
97
98        fDStroke = 1;
99        fStroke = 10;
100        fMinStroke = 10;
101        fMaxStroke = 180;
102
103        const SkScalar V = 85;
104
105        fPath[0].moveTo(40, 70);
106        fPath[0].lineTo(70, 70 + SK_ScalarHalf);
107        fPath[0].lineTo(110, 70);
108
109        fPath[1].moveTo(40, 70);
110        fPath[1].lineTo(70, 70 - SK_ScalarHalf);
111        fPath[1].lineTo(110, 70);
112
113        fPath[2].moveTo(V, V);
114        fPath[2].lineTo(50, V);
115        fPath[2].lineTo(50, 50);
116
117        fPath[3].moveTo(50, 50);
118        fPath[3].lineTo(50, V);
119        fPath[3].lineTo(V, V);
120
121        fPath[4].moveTo(50, 50);
122        fPath[4].lineTo(50, V);
123        fPath[4].lineTo(52, 50);
124
125        fPath[5].moveTo(52, 50);
126        fPath[5].lineTo(50, V);
127        fPath[5].lineTo(50, 50);
128
129        this->setBGColor(0xFFDDDDDD);
130    }
131
132protected:
133    SkString name() override { return SkString("Paths"); }
134
135    void drawPath(SkCanvas* canvas, const SkPath& path, SkPaint::Join j) {
136        SkPaint paint;
137
138        paint.setAntiAlias(true);
139        paint.setStyle(SkPaint::kStroke_Style);
140        paint.setStrokeJoin(j);
141        paint.setStrokeWidth(fStroke);
142
143        if (fShowHairline) {
144            SkPath  fill;
145
146            paint.getFillPath(path, &fill);
147            paint.setStrokeWidth(0);
148            canvas->drawPath(fill, paint);
149        } else {
150            canvas->drawPath(path, paint);
151        }
152
153        paint.setColor(SK_ColorRED);
154        paint.setStrokeWidth(0);
155        canvas->drawPath(path, paint);
156    }
157
158    void onDrawContent(SkCanvas* canvas) override {
159        this->init();
160        canvas->translate(50, 50);
161
162        static const SkPaint::Join gJoins[] = {
163            SkPaint::kBevel_Join,
164            SkPaint::kMiter_Join,
165            SkPaint::kRound_Join
166        };
167
168        for (size_t i = 0; i < SK_ARRAY_COUNT(gJoins); i++) {
169            canvas->save();
170            for (size_t j = 0; j < SK_ARRAY_COUNT(fPath); j++) {
171                this->drawPath(canvas, fPath[j], gJoins[i]);
172                canvas->translate(200, 0);
173            }
174            canvas->restore();
175
176            canvas->translate(0, 200);
177        }
178    }
179
180    bool onAnimate(double nanos) override {
181        SkScalar currSecs = TimeUtils::Scaled(1e-9 * nanos, 100);
182        SkScalar delta = currSecs - fPrevSecs;
183        fPrevSecs = currSecs;
184
185        fStroke += fDStroke * delta;
186        if (fStroke > fMaxStroke || fStroke < fMinStroke) {
187            fDStroke = -fDStroke;
188        }
189        return true;
190    }
191
192    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
193        fShowHairline = !fShowHairline;
194        return nullptr;
195    }
196
197private:
198    using INHERITED = Sample;
199};
200DEF_SAMPLE( return new PathView; )
201
202//////////////////////////////////////////////////////////////////////////////
203
204#include "include/effects/SkCornerPathEffect.h"
205#include "include/utils/SkRandom.h"
206
207class ArcToView : public Sample {
208    bool fDoFrame, fDoCorner, fDoConic;
209    SkPaint fPtsPaint, fSkeletonPaint, fCornerPaint;
210public:
211    enum {
212        N = 4
213    };
214    SkPoint fPts[N];
215
216    ArcToView()
217        : fDoFrame(false), fDoCorner(false), fDoConic(false)
218    {
219        SkRandom rand;
220        for (int i = 0; i < N; ++i) {
221            fPts[i].fX = 20 + rand.nextUScalar1() * 640;
222            fPts[i].fY = 20 + rand.nextUScalar1() * 480;
223        }
224
225        const SkScalar rad = 50;
226
227        fPtsPaint.setAntiAlias(true);
228        fPtsPaint.setStrokeWidth(15);
229        fPtsPaint.setStrokeCap(SkPaint::kRound_Cap);
230
231        fCornerPaint.setAntiAlias(true);
232        fCornerPaint.setStyle(SkPaint::kStroke_Style);
233        fCornerPaint.setStrokeWidth(13);
234        fCornerPaint.setColor(SK_ColorGREEN);
235        fCornerPaint.setPathEffect(SkCornerPathEffect::Make(rad*2));
236
237        fSkeletonPaint.setAntiAlias(true);
238        fSkeletonPaint.setStyle(SkPaint::kStroke_Style);
239        fSkeletonPaint.setColor(SK_ColorRED);
240    }
241
242    void toggle(bool& value) {
243        value = !value;
244    }
245
246protected:
247    SkString name() override { return SkString("ArcTo"); }
248
249    bool onChar(SkUnichar uni) override {
250            switch (uni) {
251                case '1': this->toggle(fDoFrame); return true;
252                case '2': this->toggle(fDoCorner); return true;
253                case '3': this->toggle(fDoConic); return true;
254                default: break;
255            }
256            return false;
257    }
258
259    void makePath(SkPath* path) {
260        path->moveTo(fPts[0]);
261        for (int i = 1; i < N; ++i) {
262            path->lineTo(fPts[i]);
263        }
264        if (!fDoFrame) {
265            path->close();
266        }
267    }
268
269    void onDrawContent(SkCanvas* canvas) override {
270        canvas->drawPoints(SkCanvas::kPoints_PointMode, N, fPts, fPtsPaint);
271
272        SkPath path;
273        this->makePath(&path);
274
275        if (fDoCorner) {
276            canvas->drawPath(path, fCornerPaint);
277        }
278
279        canvas->drawPath(path, fSkeletonPaint);
280    }
281
282    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
283        const SkScalar tol = 4;
284        const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2);
285        for (int i = 0; i < N; ++i) {
286            if (r.intersects(SkRect::MakeXYWH(fPts[i].fX, fPts[i].fY, 1, 1))) {
287                return new Click([this, i](Click* c) {
288                    fPts[i] = c->fCurr;
289                    return true;
290                });
291            }
292        }
293        return nullptr;
294    }
295
296private:
297    using INHERITED = Sample;
298};
299DEF_SAMPLE( return new ArcToView; )
300
301/////////////
302
303class FatStroke : public Sample {
304    bool fClosed, fShowStroke, fShowHidden, fShowSkeleton, fAsCurves = false;
305    int  fJoinType, fCapType;
306    float fWidth = 30;
307    SkPaint fPtsPaint, fHiddenPaint, fSkeletonPaint, fStrokePaint;
308public:
309    enum {
310        N = 4
311    };
312    SkPoint fPts[N];
313
314    FatStroke() : fClosed(false), fShowStroke(true), fShowHidden(false), fShowSkeleton(true),
315                  fJoinType(0), fCapType(0)
316    {
317        SkRandom rand;
318        for (int i = 0; i < N; ++i) {
319            fPts[i].fX = 20 + rand.nextUScalar1() * 640;
320            fPts[i].fY = 20 + rand.nextUScalar1() * 480;
321        }
322
323        fPtsPaint.setAntiAlias(true);
324        fPtsPaint.setStrokeWidth(10);
325        fPtsPaint.setStrokeCap(SkPaint::kRound_Cap);
326
327        fHiddenPaint.setAntiAlias(true);
328        fHiddenPaint.setStyle(SkPaint::kStroke_Style);
329        fHiddenPaint.setColor(0xFF0000FF);
330
331        fStrokePaint.setAntiAlias(true);
332        fStrokePaint.setStyle(SkPaint::kStroke_Style);
333        fStrokePaint.setStrokeWidth(50);
334        fStrokePaint.setColor(0x8000FF00);
335
336        fSkeletonPaint.setAntiAlias(true);
337        fSkeletonPaint.setStyle(SkPaint::kStroke_Style);
338        fSkeletonPaint.setColor(SK_ColorRED);
339    }
340
341    void toggle(bool& value) {
342        value = !value;
343    }
344
345    void toggle3(int& value) {
346        value = (value + 1) % 3;
347    }
348
349protected:
350    SkString name() override { return SkString("FatStroke"); }
351
352    bool onChar(SkUnichar uni) override {
353            switch (uni) {
354                case '1': this->toggle(fShowSkeleton); return true;
355                case '2': this->toggle(fShowStroke); return true;
356                case '3': this->toggle(fShowHidden); return true;
357                case '4': this->toggle3(fJoinType); return true;
358                case '5': this->toggle3(fCapType); return true;
359                case '6': this->toggle(fClosed); return true;
360                case 'c': this->toggle(fAsCurves); return true;
361                case '-': fWidth -= 5; return true;
362                case '=': fWidth += 5; return true;
363                default: break;
364            }
365            return false;
366    }
367
368    void makePath(SkPath* path) {
369        path->moveTo(fPts[0]);
370        if (fAsCurves) {
371            for (int i = 1; i < N-2; ++i) {
372                path->quadTo(fPts[i], (fPts[i+1] + fPts[i]) * 0.5f);
373            }
374            path->quadTo(fPts[N-2], fPts[N-1]);
375        } else {
376            for (int i = 1; i < N; ++i) {
377                path->lineTo(fPts[i]);
378            }
379        }
380        if (fClosed) {
381            path->close();
382        }
383    }
384
385    void onDrawContent(SkCanvas* canvas) override {
386        canvas->drawColor(0xFFEEEEEE);
387
388        SkPath path;
389        this->makePath(&path);
390
391        fStrokePaint.setStrokeWidth(fWidth);
392        fStrokePaint.setStrokeJoin((SkPaint::Join)fJoinType);
393        fStrokePaint.setStrokeCap((SkPaint::Cap)fCapType);
394
395        if (fShowStroke) {
396            canvas->drawPath(path, fStrokePaint);
397        }
398        if (fShowHidden) {
399            SkPath hidden;
400            fStrokePaint.getFillPath(path, &hidden);
401            canvas->drawPath(hidden, fHiddenPaint);
402        }
403        if (fShowSkeleton) {
404            canvas->drawPath(path, fSkeletonPaint);
405        }
406        canvas->drawPoints(SkCanvas::kPoints_PointMode, N, fPts, fPtsPaint);
407    }
408
409    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
410        const SkScalar tol = 4;
411        const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2);
412        for (int i = 0; i < N; ++i) {
413            if (r.intersects(SkRect::MakeXYWH(fPts[i].fX, fPts[i].fY, 1, 1))) {
414                return new Click([this, i](Click* c) {
415                    fPts[i] = c->fCurr;
416                    return true;
417                });
418            }
419        }
420        return nullptr;
421    }
422
423private:
424    using INHERITED = Sample;
425};
426DEF_SAMPLE( return new FatStroke; )
427
428static int compute_parallel_to_base(const SkPoint pts[4], SkScalar t[2]) {
429    // F = At^3 + Bt^2 + Ct + D
430    SkVector A = pts[3] - pts[0] + (pts[1] - pts[2]) * 3.0f;
431    SkVector B = (pts[0] - pts[1] - pts[1] + pts[2]) * 3.0f;
432    SkVector C = (pts[1] - pts[0]) * 3.0f;
433    SkVector DA = pts[3] - pts[0];
434
435    // F' = 3At^2 + 2Bt + C
436    SkScalar a = 3 * A.cross(DA);
437    SkScalar b = 2 * B.cross(DA);
438    SkScalar c = C.cross(DA);
439
440    int n = SkFindUnitQuadRoots(a, b, c, t);
441    SkString str;
442    for (int i = 0; i < n; ++i) {
443        str.appendf(" %g", t[i]);
444    }
445    SkDebugf("roots %s\n", str.c_str());
446    return n;
447}
448
449class CubicCurve : public Sample {
450public:
451    enum {
452        N = 4
453    };
454    SkPoint fPts[N];
455
456    CubicCurve() {
457        SkRandom rand;
458        for (int i = 0; i < N; ++i) {
459            fPts[i].fX = 20 + rand.nextUScalar1() * 640;
460            fPts[i].fY = 20 + rand.nextUScalar1() * 480;
461        }
462    }
463
464protected:
465    SkString name() override { return SkString("CubicCurve"); }
466
467    void onDrawContent(SkCanvas* canvas) override {
468        SkPaint paint;
469        paint.setAntiAlias(true);
470
471        {
472            SkPath path;
473            path.moveTo(fPts[0]);
474            path.cubicTo(fPts[1], fPts[2], fPts[3]);
475            paint.setStyle(SkPaint::kStroke_Style);
476            canvas->drawPath(path, paint);
477        }
478
479        {
480            paint.setColor(SK_ColorRED);
481            SkScalar t[2];
482            int n = compute_parallel_to_base(fPts, t);
483            SkPoint loc;
484            SkVector tan;
485            for (int i = 0; i < n; ++i) {
486                SkEvalCubicAt(fPts, t[i], &loc, &tan, nullptr);
487                tan.setLength(30);
488                canvas->drawLine(loc - tan, loc + tan, paint);
489            }
490            paint.setStrokeWidth(0.5f);
491            canvas->drawLine(fPts[0], fPts[3], paint);
492
493            paint.setColor(SK_ColorBLUE);
494            paint.setStrokeWidth(6);
495            SkEvalCubicAt(fPts, 0.5f, &loc, nullptr, nullptr);
496            canvas->drawPoint(loc, paint);
497
498            paint.setColor(0xFF008800);
499            SkEvalCubicAt(fPts, 1.0f/3, &loc, nullptr, nullptr);
500            canvas->drawPoint(loc, paint);
501            SkEvalCubicAt(fPts, 2.0f/3, &loc, nullptr, nullptr);
502            canvas->drawPoint(loc, paint);
503
504       //     n = SkFindCubicInflections(fPts, t);
505       //     printf("inflections %d %g %g\n", n, t[0], t[1]);
506        }
507
508        {
509            paint.setStyle(SkPaint::kFill_Style);
510            paint.setColor(SK_ColorRED);
511            for (SkPoint p : fPts) {
512                canvas->drawCircle(p.fX, p.fY, 8, paint);
513            }
514        }
515    }
516
517    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
518        const SkScalar tol = 8;
519        const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2);
520        for (int i = 0; i < N; ++i) {
521            if (r.intersects(SkRect::MakeXYWH(fPts[i].fX, fPts[i].fY, 1, 1))) {
522                return new Click([this, i](Click* c) {
523                    fPts[i] = c->fCurr;
524                    return true;
525                });
526            }
527        }
528        return this->INHERITED::onFindClickHandler(x, y, modi);
529    }
530
531private:
532    using INHERITED = Sample;
533};
534DEF_SAMPLE( return new CubicCurve; )
535
536static SkPoint lerp(SkPoint a, SkPoint b, float t) {
537    return a * (1 - t) + b * t;
538}
539
540static int find_max_deviation_cubic(const SkPoint src[4], SkScalar ts[2]) {
541    // deviation = F' x (d - a) == 0, solve for t(s)
542    // F = At^3 + Bt^2 + Ct + D
543    // F' = 3At^2 + 2Bt + C
544    // Z = d - a
545    // F' x Z = 3(A x Z)t^2 + 2(B x Z)t + (C x Z)
546    //
547    SkVector A = src[3] + (src[1] - src[2]) * 3 - src[0];
548    SkVector B = (src[2] - src[1] - src[1] + src[0]) * 3;
549    SkVector C = (src[1] - src[0]) * 3;
550    SkVector Z = src[3] - src[0];
551    // now forumlate the quadratic coefficients we need to solve for t : F' x Z
552    return SkFindUnitQuadRoots(3 * A.cross(Z), 2 * B.cross(Z), C.cross(Z), ts);
553}
554
555class CubicCurve2 : public Sample {
556public:
557    enum {
558        N = 7
559    };
560    SkPoint fPts[N];
561    SkPoint* fQuad = fPts + 4;
562    SkScalar fT = 0.5f;
563    bool fShowSub = false;
564    bool fShowFlatness = false;
565    bool fShowInnerQuads = false;
566    SkScalar fScale = 0.75;
567
568    CubicCurve2() {
569        fPts[0] = { 90, 300 };
570        fPts[1] = { 30, 60 };
571        fPts[2] = { 250, 30 };
572        fPts[3] = { 350, 200 };
573
574        fQuad[0] = fPts[0] + SkVector{ 300, 0};
575        fQuad[1] = fPts[1] + SkVector{ 300, 0};
576        fQuad[2] = fPts[2] + SkVector{ 300, 0};
577    }
578
579protected:
580    SkString name() override { return SkString("CubicCurve2"); }
581
582    bool onChar(SkUnichar uni) override {
583            switch (uni) {
584                case 's': fShowSub = !fShowSub; break;
585                case 'f': fShowFlatness = !fShowFlatness; break;
586                case '-': fT -= 1.0f / 32; break;
587                case '=': fT += 1.0f / 32; break;
588                case 'q': fShowInnerQuads = !fShowInnerQuads; break;
589                default: return false;
590            }
591            fT = std::min(1.0f, std::max(0.0f, fT));
592            return true;
593    }
594
595    static void Dot(SkCanvas* canvas, SkPoint p, SkScalar radius, SkColor c) {
596        SkPaint paint;
597        paint.setAntiAlias(true);
598        paint.setColor(c);
599        canvas->drawCircle(p.fX, p.fY, radius, paint);
600    }
601
602    void showFrame(SkCanvas* canvas, const SkPoint pts[], int count, const SkPaint& p) {
603        SkPaint paint(p);
604        SkPoint storage[3 + 2 + 1];
605        SkPoint* tmp = storage;
606        const SkPoint* prev = pts;
607        for (int n = count; n > 0; --n) {
608            for (int i = 0; i < n; ++i) {
609                canvas->drawLine(prev[i], prev[i+1], paint);
610                tmp[i] = lerp(prev[i], prev[i+1], fT);
611            }
612            prev = tmp;
613            tmp += n;
614        }
615
616        paint.setColor(SK_ColorBLUE);
617        paint.setStyle(SkPaint::kFill_Style);
618        int n = tmp - storage;
619        for (int i = 0; i < n; ++i) {
620            Dot(canvas, storage[i], 4, SK_ColorBLUE);
621        }
622    }
623
624    void showFlattness(SkCanvas* canvas) {
625        SkPaint paint;
626        paint.setStyle(SkPaint::kStroke_Style);
627        paint.setAntiAlias(true);
628
629        SkPaint paint2(paint);
630        paint2.setColor(0xFF008800);
631
632        paint.setColor(0xFF888888);
633        canvas->drawLine(fPts[0], fPts[3], paint);
634        canvas->drawLine(fQuad[0], fQuad[2], paint);
635
636        paint.setColor(0xFF0000FF);
637        SkPoint pts[2];
638        pts[0] = (fQuad[0] + fQuad[1] + fQuad[1] + fQuad[2])*0.25;
639        pts[1] = (fQuad[0] + fQuad[2]) * 0.5;
640        canvas->drawLine(pts[0], pts[1], paint);
641
642        // cubic
643
644        SkVector v0 = (fPts[0] - fPts[1] - fPts[1] + fPts[2]) * fScale;
645        SkVector v1 = (fPts[1] - fPts[2] - fPts[2] + fPts[3]) * fScale;
646        SkVector v = (v0 + v1) * 0.5f;
647
648        SkPoint anchor;
649        SkScalar ts[2];
650        int n = find_max_deviation_cubic(fPts, ts);
651        if (n > 0) {
652            SkEvalCubicAt(fPts, ts[0], &anchor, nullptr, nullptr);
653            canvas->drawLine(anchor, anchor + v, paint2);
654            canvas->drawLine(anchor, anchor + v0, paint);
655            if (n == 2) {
656                SkEvalCubicAt(fPts, ts[1], &anchor, nullptr, nullptr);
657                canvas->drawLine(anchor, anchor + v, paint2);
658            }
659            canvas->drawLine(anchor, anchor + v1, paint);
660        }
661        // not sure we can get here
662    }
663
664    void showInnerQuads(SkCanvas* canvas) {
665        auto draw_quad = [canvas](SkPoint a, SkPoint b, SkPoint c, SkColor color) {
666            SkPaint paint;
667            paint.setAntiAlias(true);
668            paint.setStroke(true);
669            paint.setColor(color);
670
671            canvas->drawPath(SkPathBuilder().moveTo(a).quadTo(b, c).detach(), paint);
672        };
673
674        SkPoint p0 = SkEvalQuadAt(&fPts[0], fT),
675                p1 = SkEvalQuadAt(&fPts[1], fT),
676                p2 = lerp(p0, p1, fT);
677
678        draw_quad(fPts[0], fPts[1], fPts[2], SK_ColorRED);
679        Dot(canvas, p0, 4, SK_ColorRED);
680
681        draw_quad(fPts[1], fPts[2], fPts[3], SK_ColorBLUE);
682        Dot(canvas, p1, 4, SK_ColorBLUE);
683
684        SkPaint paint;
685        paint.setAntiAlias(true);
686        paint.setColor(0xFF008800);
687        canvas->drawLine(p0, p1, paint);
688        Dot(canvas, p2, 4, 0xFF00AA00);
689    }
690
691    void onDrawContent(SkCanvas* canvas) override {
692        SkPaint paint;
693        paint.setAntiAlias(true);
694
695        {
696            paint.setStyle(SkPaint::kStroke_Style);
697            SkPath path;
698            path.moveTo(fPts[0]);
699            path.cubicTo(fPts[1], fPts[2], fPts[3]);
700            path.moveTo(fQuad[0]);
701            path.quadTo(fQuad[1], fQuad[2]);
702            canvas->drawPath(path, paint);
703        }
704
705        if (fShowSub) {
706            paint.setColor(SK_ColorRED);
707            paint.setStrokeWidth(1.7f);
708            this->showFrame(canvas, fPts, 3, paint);
709            this->showFrame(canvas, fQuad, 2, paint);
710
711            paint.setColor(SK_ColorBLACK);
712            paint.setStyle(SkPaint::kFill_Style);
713            SkFont font(nullptr, 20);
714            canvas->drawString(SkStringPrintf("t = %g", fT), 20, 20, font, paint);
715        }
716
717        if (fShowFlatness) {
718            this->showFlattness(canvas);
719        }
720
721        if (fShowInnerQuads) {
722            this->showInnerQuads(canvas);
723        }
724
725        paint.setColor(SK_ColorGRAY);
726        paint.setStroke(true);
727        canvas->drawPath(SkPathBuilder().addPolygon(fPts, 4, false).detach(), paint);
728        canvas->drawPath(SkPathBuilder().addPolygon(fQuad, 3, false).detach(), paint);
729
730        for (SkPoint p : fPts) {
731            Dot(canvas, p, 7, SK_ColorBLACK);
732        }
733
734        if (false) {
735            SkScalar ts[2];
736            int n = SkFindCubicInflections(fPts, ts);
737            for (int i = 0; i < n; ++i) {
738                SkPoint p;
739                SkEvalCubicAt(fPts, ts[i], &p, nullptr, nullptr);
740                canvas->drawCircle(p.fX, p.fY, 3, paint);
741            }
742        }
743
744    }
745
746    Sample::Click* onFindClickHandler(SkScalar x, SkScalar y, skui::ModifierKey modi) override {
747        const SkScalar tol = 8;
748        const SkRect r = SkRect::MakeXYWH(x - tol, y - tol, tol * 2, tol * 2);
749        for (int i = 0; i < N; ++i) {
750            if (r.intersects(SkRect::MakeXYWH(fPts[i].fX, fPts[i].fY, 1, 1))) {
751                return new Click([this, i](Click* c) {
752                    fPts[i] = c->fCurr;
753                    return true;
754                });
755            }
756        }
757        return this->INHERITED::onFindClickHandler(x, y, modi);
758    }
759
760private:
761    using INHERITED = Sample;
762};
763DEF_SAMPLE( return new CubicCurve2; )
764
765