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