Home | History | Annotate | Download | only in samplecode
      1 /*
      2  * Copyright 2012 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 "SkBlendMode.h"
     10 #include "SkCanvas.h"
     11 #include "SkColor.h"
     12 #include "SkFont.h"
     13 #include "SkGeometry.h"
     14 #include "SkImageInfo.h"
     15 #include "SkMatrix.h"
     16 #include "SkPaint.h"
     17 #include "SkPath.h"
     18 #include "SkPathMeasure.h"
     19 #include "SkPoint.h"
     20 #include "SkPointPriv.h"
     21 #include "SkRRect.h"
     22 #include "SkRect.h"
     23 #include "SkRefCnt.h"
     24 #include "SkScalar.h"
     25 #include "SkShader.h"
     26 #include "SkString.h"
     27 #include "SkStroke.h"
     28 #include "SkSurface.h"
     29 #include "SkTArray.h"
     30 #include "SkTemplates.h"
     31 #include "SkTextUtils.h"
     32 #include "SkTypes.h"
     33 #include "sk_tool_utils.h"
     34 
     35 #include <cfloat>
     36 
     37 class SkEvent;
     38 
     39 static bool hittest(const SkPoint& target, SkScalar x, SkScalar y) {
     40     const SkScalar TOL = 7;
     41     return SkPoint::Distance(target, SkPoint::Make(x, y)) <= TOL;
     42 }
     43 
     44 static int getOnCurvePoints(const SkPath& path, SkPoint storage[]) {
     45     SkPath::RawIter iter(path);
     46     SkPoint pts[4];
     47     SkPath::Verb verb;
     48 
     49     int count = 0;
     50     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
     51         switch (verb) {
     52             case SkPath::kMove_Verb:
     53             case SkPath::kLine_Verb:
     54             case SkPath::kQuad_Verb:
     55             case SkPath::kConic_Verb:
     56             case SkPath::kCubic_Verb:
     57                 storage[count++] = pts[0];
     58                 break;
     59             default:
     60                 break;
     61         }
     62     }
     63     return count;
     64 }
     65 
     66 static void getContourCounts(const SkPath& path, SkTArray<int>* contourCounts) {
     67     SkPath::RawIter iter(path);
     68     SkPoint pts[4];
     69     SkPath::Verb verb;
     70 
     71     int count = 0;
     72     while ((verb = iter.next(pts)) != SkPath::kDone_Verb) {
     73         switch (verb) {
     74             case SkPath::kMove_Verb:
     75             case SkPath::kLine_Verb:
     76                 count += 1;
     77                 break;
     78             case SkPath::kQuad_Verb:
     79             case SkPath::kConic_Verb:
     80                 count += 2;
     81                 break;
     82             case SkPath::kCubic_Verb:
     83                 count += 3;
     84                 break;
     85             case SkPath::kClose_Verb:
     86                 contourCounts->push_back(count);
     87                 count = 0;
     88                 break;
     89             default:
     90                 break;
     91         }
     92     }
     93     if (count > 0) {
     94         contourCounts->push_back(count);
     95     }
     96 }
     97 
     98 static void erase(const sk_sp<SkSurface>& surface) {
     99     SkCanvas* canvas = surface->getCanvas();
    100     if (canvas) {
    101         canvas->clear(SK_ColorTRANSPARENT);
    102     }
    103 }
    104 
    105 struct StrokeTypeButton {
    106     SkRect fBounds;
    107     char fLabel;
    108     bool fEnabled;
    109 };
    110 
    111 struct CircleTypeButton : public StrokeTypeButton {
    112     bool fFill;
    113 };
    114 
    115 class QuadStrokerView : public Sample {
    116     enum {
    117         SKELETON_COLOR = 0xFF0000FF,
    118         WIREFRAME_COLOR = 0x80FF0000
    119     };
    120 
    121     enum {
    122         kCount = 18
    123     };
    124     SkPoint fPts[kCount];
    125     SkRect fWeightControl;
    126     SkRect fRadiusControl;
    127     SkRect fErrorControl;
    128     SkRect fWidthControl;
    129     SkRect fBounds;
    130     SkMatrix fMatrix, fInverse;
    131     sk_sp<SkShader> fShader;
    132     sk_sp<SkSurface> fMinSurface;
    133     sk_sp<SkSurface> fMaxSurface;
    134     StrokeTypeButton fCubicButton;
    135     StrokeTypeButton fConicButton;
    136     StrokeTypeButton fQuadButton;
    137     StrokeTypeButton fArcButton;
    138     StrokeTypeButton fRRectButton;
    139     CircleTypeButton fCircleButton;
    140     StrokeTypeButton fTextButton;
    141     SkString fText;
    142     SkScalar fTextSize;
    143     SkScalar fWeight;
    144     SkScalar fRadius;
    145     SkScalar fWidth, fDWidth;
    146     SkScalar fWidthScale;
    147     int fW, fH, fZoom;
    148     bool fAnimate;
    149     bool fDrawRibs;
    150     bool fDrawTangents;
    151     bool fDrawTDivs;
    152 #ifdef SK_DEBUG
    153     #define kStrokerErrorMin 0.001f
    154     #define kStrokerErrorMax 5
    155 #endif
    156     #define kWidthMin 1
    157     #define kWidthMax 100
    158 public:
    159     QuadStrokerView() {
    160         this->setBGColor(SK_ColorLTGRAY);
    161 
    162         fPts[0].set(50, 200);  // cubic
    163         fPts[1].set(50, 100);
    164         fPts[2].set(150, 50);
    165         fPts[3].set(300, 50);
    166 
    167         fPts[4].set(350, 200);  // conic
    168         fPts[5].set(350, 100);
    169         fPts[6].set(450, 50);
    170 
    171         fPts[7].set(150, 300);  // quad
    172         fPts[8].set(150, 200);
    173         fPts[9].set(250, 150);
    174 
    175         fPts[10].set(250, 200);  // arc
    176         fPts[11].set(250, 300);
    177         fPts[12].set(150, 350);
    178 
    179         fPts[13].set(200, 200); // rrect
    180         fPts[14].set(400, 400);
    181 
    182         fPts[15].set(250, 250);  // oval
    183         fPts[16].set(450, 450);
    184 
    185         fText = "a";
    186         fTextSize = 12;
    187         fWidth = 50;
    188         fDWidth = 0.25f;
    189         fWeight = 1;
    190         fRadius = 150;
    191 
    192         fCubicButton.fLabel = 'C';
    193         fCubicButton.fEnabled = false;
    194         fConicButton.fLabel = 'K';
    195         fConicButton.fEnabled = false;
    196         fQuadButton.fLabel = 'Q';
    197         fQuadButton.fEnabled = false;
    198         fArcButton.fLabel = 'A';
    199         fArcButton.fEnabled = true;
    200         fRRectButton.fLabel = 'R';
    201         fRRectButton.fEnabled = false;
    202         fCircleButton.fLabel = 'O';
    203         fCircleButton.fEnabled = true;
    204         fCircleButton.fFill = true;
    205         fTextButton.fLabel = 'T';
    206         fTextButton.fEnabled = false;
    207         fAnimate = false;
    208         setAsNeeded();
    209     }
    210 
    211 protected:
    212     bool onQuery(Sample::Event* evt) override {
    213         if (Sample::TitleQ(*evt)) {
    214             Sample::TitleR(evt, "QuadStroker");
    215             return true;
    216         }
    217         SkUnichar uni;
    218         if (fTextButton.fEnabled && Sample::CharQ(*evt, &uni)) {
    219             switch (uni) {
    220                 case ' ':
    221                     fText = "";
    222                     break;
    223                 case '-':
    224                     fTextSize = SkTMax(1.0f, fTextSize - 1);
    225                     break;
    226                 case '+':
    227                 case '=':
    228                     fTextSize += 1;
    229                     break;
    230                 default:
    231                     fText.appendUnichar(uni);
    232             }
    233             return true;
    234         }
    235         return this->INHERITED::onQuery(evt);
    236     }
    237 
    238     void onSizeChange() override {
    239         fRadiusControl.setXYWH(this->width() - 200, 30, 30, 400);
    240         fWeightControl.setXYWH(this->width() - 150, 30, 30, 400);
    241         fErrorControl.setXYWH(this->width() - 100, 30, 30, 400);
    242         fWidthControl.setXYWH(this->width() -  50, 30, 30, 400);
    243         int buttonOffset = 450;
    244         fCubicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
    245         buttonOffset += 50;
    246         fConicButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
    247         buttonOffset += 50;
    248         fQuadButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
    249         buttonOffset += 50;
    250         fArcButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
    251         buttonOffset += 50;
    252         fRRectButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
    253         buttonOffset += 50;
    254         fCircleButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
    255         buttonOffset += 50;
    256         fTextButton.fBounds.setXYWH(this->width() - 50, SkIntToScalar(buttonOffset), 30, 30);
    257         this->INHERITED::onSizeChange();
    258     }
    259 
    260      void copyMinToMax() {
    261         erase(fMaxSurface);
    262         SkCanvas* canvas = fMaxSurface->getCanvas();
    263         canvas->save();
    264         canvas->concat(fMatrix);
    265         fMinSurface->draw(canvas, 0, 0, nullptr);
    266         canvas->restore();
    267 
    268         SkPaint paint;
    269         paint.setBlendMode(SkBlendMode::kClear);
    270         for (int iy = 1; iy < fH; ++iy) {
    271             SkScalar y = SkIntToScalar(iy * fZoom);
    272             canvas->drawLine(0, y - SK_ScalarHalf, 999, y - SK_ScalarHalf, paint);
    273         }
    274         for (int ix = 1; ix < fW; ++ix) {
    275             SkScalar x = SkIntToScalar(ix * fZoom);
    276             canvas->drawLine(x - SK_ScalarHalf, 0, x - SK_ScalarHalf, 999, paint);
    277         }
    278     }
    279 
    280    void setWHZ(int width, int height, int zoom) {
    281         fZoom = zoom;
    282         fBounds.set(0, 0, SkIntToScalar(width * zoom), SkIntToScalar(height * zoom));
    283         fMatrix.setScale(SkIntToScalar(zoom), SkIntToScalar(zoom));
    284         fInverse.setScale(SK_Scalar1 / zoom, SK_Scalar1 / zoom);
    285         fShader = sk_tool_utils::create_checkerboard_shader(0xFFCCCCCC, 0xFFFFFFFF, zoom);
    286 
    287         SkImageInfo info = SkImageInfo::MakeN32Premul(width, height);
    288         fMinSurface = SkSurface::MakeRaster(info);
    289         info = info.makeWH(width * zoom, height * zoom);
    290         fMaxSurface = SkSurface::MakeRaster(info);
    291     }
    292 
    293     void draw_points(SkCanvas* canvas, const SkPath& path, SkColor color,
    294                      bool show_lines) {
    295         SkPaint paint;
    296         paint.setColor(color);
    297         paint.setAlpha(0x80);
    298         paint.setAntiAlias(true);
    299         int n = path.countPoints();
    300         SkAutoSTArray<32, SkPoint> pts(n);
    301         if (show_lines && fDrawTangents) {
    302             SkTArray<int> contourCounts;
    303             getContourCounts(path, &contourCounts);
    304             SkPoint* ptPtr = pts.get();
    305             for (int i = 0; i < contourCounts.count(); ++i) {
    306                 int count = contourCounts[i];
    307                 path.getPoints(ptPtr, count);
    308                 canvas->drawPoints(SkCanvas::kPolygon_PointMode, count, ptPtr, paint);
    309                 ptPtr += count;
    310             }
    311         } else {
    312             n = getOnCurvePoints(path, pts.get());
    313         }
    314         paint.setStrokeWidth(5);
    315         canvas->drawPoints(SkCanvas::kPoints_PointMode, n, pts.get(), paint);
    316     }
    317 
    318     void draw_ribs(SkCanvas* canvas, const SkPath& path, SkScalar width,
    319                    SkColor color) {
    320         const SkScalar radius = width / 2;
    321 
    322         SkPathMeasure meas(path, false);
    323         SkScalar total = meas.getLength();
    324 
    325         SkScalar delta = 8;
    326         SkPaint paint, labelP;
    327         paint.setColor(color);
    328         labelP.setColor(color & 0xff5f9f5f);
    329         SkFont font;
    330         SkPoint pos, tan;
    331         int index = 0;
    332         for (SkScalar dist = 0; dist <= total; dist += delta) {
    333             if (meas.getPosTan(dist, &pos, &tan)) {
    334                 tan.scale(radius);
    335                 SkPointPriv::RotateCCW(&tan);
    336                 canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(),
    337                                  pos.x() - tan.x(), pos.y() - tan.y(), paint);
    338                 if (0 == index % 10) {
    339                     SkString label;
    340                     label.appendS32(index);
    341                     SkRect dot = SkRect::MakeXYWH(pos.x() - 2, pos.y() - 2, 4, 4);
    342                     canvas->drawRect(dot, labelP);
    343                     canvas->drawString(label,
    344                         pos.x() - tan.x() * 1.25f, pos.y() - tan.y() * 1.25f, font, labelP);
    345                 }
    346             }
    347             ++index;
    348         }
    349     }
    350 
    351     void draw_t_divs(SkCanvas* canvas, const SkPath& path, SkScalar width, SkColor color) {
    352         const SkScalar radius = width / 2;
    353         SkPaint paint;
    354         paint.setColor(color);
    355         SkPathMeasure meas(path, false);
    356         SkScalar total = meas.getLength();
    357         SkScalar delta = 8;
    358         int ribs = 0;
    359         for (SkScalar dist = 0; dist <= total; dist += delta) {
    360             ++ribs;
    361         }
    362         SkPath::RawIter iter(path);
    363         SkPoint pts[4];
    364         if (SkPath::kMove_Verb != iter.next(pts)) {
    365             SkASSERT(0);
    366             return;
    367         }
    368         SkPath::Verb verb = iter.next(pts);
    369         SkASSERT(SkPath::kLine_Verb <= verb && verb <= SkPath::kCubic_Verb);
    370         SkPoint pos, tan;
    371         for (int index = 0; index < ribs; ++index) {
    372             SkScalar t = (SkScalar) index / ribs;
    373             switch (verb) {
    374                 case SkPath::kLine_Verb:
    375                     tan = pts[1] - pts[0];
    376                     pos = pts[0];
    377                     pos.fX += tan.fX * t;
    378                     pos.fY += tan.fY * t;
    379                     break;
    380                 case SkPath::kQuad_Verb:
    381                     pos = SkEvalQuadAt(pts, t);
    382                     tan = SkEvalQuadTangentAt(pts, t);
    383                     break;
    384                 case SkPath::kConic_Verb: {
    385                     SkConic conic(pts, iter.conicWeight());
    386                     pos = conic.evalAt(t);
    387                     tan = conic.evalTangentAt(t);
    388                     } break;
    389                 case SkPath::kCubic_Verb:
    390                     SkEvalCubicAt(pts, t, &pos, &tan, nullptr);
    391                     break;
    392                 default:
    393                     SkASSERT(0);
    394                     return;
    395             }
    396             tan.setLength(radius);
    397             SkPointPriv::RotateCCW(&tan);
    398             canvas->drawLine(pos.x() + tan.x(), pos.y() + tan.y(),
    399                                 pos.x() - tan.x(), pos.y() - tan.y(), paint);
    400             if (0 == index % 10) {
    401                 SkString label;
    402                 label.appendS32(index);
    403                 canvas->drawString(label,
    404                     pos.x() + tan.x() * 1.25f, pos.y() + tan.y() * 1.25f, SkFont(), paint);
    405             }
    406         }
    407     }
    408 
    409     void draw_stroke(SkCanvas* canvas, const SkPath& path, SkScalar width, SkScalar scale,
    410             bool drawText) {
    411         if (path.isEmpty()) {
    412             return;
    413         }
    414         SkRect bounds = path.getBounds();
    415         this->setWHZ(SkScalarCeilToInt(bounds.right()), drawText
    416                 ? SkScalarRoundToInt(scale * 3 / 2) : SkScalarRoundToInt(scale),
    417                 SkScalarRoundToInt(950.0f / scale));
    418         erase(fMinSurface);
    419         SkPaint paint;
    420         paint.setColor(0x1f1f0f0f);
    421         paint.setStyle(SkPaint::kStroke_Style);
    422         paint.setStrokeWidth(width * scale * scale);
    423         paint.setColor(0x3f0f1f3f);
    424         if (drawText) {
    425             fMinSurface->getCanvas()->drawPath(path, paint);
    426             this->copyMinToMax();
    427             fMaxSurface->draw(canvas, 0, 0, nullptr);
    428         }
    429         paint.setAntiAlias(true);
    430         paint.setStyle(SkPaint::kStroke_Style);
    431         paint.setStrokeWidth(1);
    432 
    433         paint.setColor(SKELETON_COLOR);
    434         SkPath scaled;
    435         SkMatrix matrix;
    436         matrix.reset();
    437         matrix.setScale(950 / scale, 950 / scale);
    438         if (drawText) {
    439             path.transform(matrix, &scaled);
    440         } else {
    441             scaled = path;
    442         }
    443         canvas->drawPath(scaled, paint);
    444         draw_points(canvas, scaled, SKELETON_COLOR, true);
    445 
    446         if (fDrawRibs) {
    447             draw_ribs(canvas, scaled, width, 0xFF00FF00);
    448         }
    449 
    450         if (fDrawTDivs) {
    451             draw_t_divs(canvas, scaled, width, 0xFF3F3F00);
    452         }
    453 
    454         SkPath fill;
    455 
    456         SkPaint p;
    457         p.setStyle(SkPaint::kStroke_Style);
    458         if (drawText) {
    459             p.setStrokeWidth(width * scale * scale);
    460         } else {
    461             p.setStrokeWidth(width);
    462         }
    463         p.getFillPath(path, &fill);
    464         SkPath scaledFill;
    465         if (drawText) {
    466             fill.transform(matrix, &scaledFill);
    467         } else {
    468             scaledFill = fill;
    469         }
    470         paint.setColor(WIREFRAME_COLOR);
    471         canvas->drawPath(scaledFill, paint);
    472         draw_points(canvas, scaledFill, WIREFRAME_COLOR, false);
    473     }
    474 
    475     void draw_fill(SkCanvas* canvas, const SkRect& rect, SkScalar width) {
    476         if (rect.isEmpty()) {
    477             return;
    478         }
    479         SkPaint paint;
    480         paint.setColor(0x1f1f0f0f);
    481         paint.setStyle(SkPaint::kStroke_Style);
    482         paint.setStrokeWidth(width);
    483         SkPath path;
    484         SkScalar maxSide = SkTMax(rect.width(), rect.height()) / 2;
    485         SkPoint center = { rect.fLeft + maxSide, rect.fTop + maxSide };
    486         path.addCircle(center.fX, center.fY, maxSide);
    487         canvas->drawPath(path, paint);
    488         paint.setStyle(SkPaint::kFill_Style);
    489         path.reset();
    490         path.addCircle(center.fX, center.fY, maxSide - width / 2);
    491         paint.setColor(0x3f0f1f3f);
    492         canvas->drawPath(path, paint);
    493         path.reset();
    494         path.setFillType(SkPath::kEvenOdd_FillType);
    495         path.addCircle(center.fX, center.fY, maxSide + width / 2);
    496         SkRect outside = SkRect::MakeXYWH(center.fX - maxSide - width, center.fY - maxSide - width,
    497                 (maxSide + width) * 2, (maxSide + width) * 2);
    498         path.addRect(outside);
    499         canvas->drawPath(path, paint);
    500     }
    501 
    502     void draw_button(SkCanvas* canvas, const StrokeTypeButton& button) {
    503         SkPaint paint;
    504         paint.setAntiAlias(true);
    505         paint.setStyle(SkPaint::kStroke_Style);
    506         paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000);
    507         canvas->drawRect(button.fBounds, paint);
    508         paint.setColor(button.fEnabled ? 0xFF3F0000 : 0x6F3F0000);
    509         paint.setStyle(SkPaint::kFill_Style);
    510         SkFont font;
    511         font.setSize(25.0f);
    512         SkTextUtils::Draw(canvas, &button.fLabel, 1, kUTF8_SkTextEncoding,
    513                 button.fBounds.centerX(), button.fBounds.fBottom - 5,
    514                 font, paint, SkTextUtils::kCenter_Align);
    515     }
    516 
    517     void draw_control(SkCanvas* canvas, const SkRect& bounds, SkScalar value,
    518             SkScalar min, SkScalar max, const char* name) {
    519         SkPaint paint;
    520         paint.setAntiAlias(true);
    521         paint.setStyle(SkPaint::kStroke_Style);
    522         canvas->drawRect(bounds, paint);
    523         SkScalar scale = max - min;
    524         SkScalar yPos = bounds.fTop + (value - min) * bounds.height() / scale;
    525         paint.setColor(0xFFFF0000);
    526         canvas->drawLine(bounds.fLeft - 5, yPos, bounds.fRight + 5, yPos, paint);
    527         SkString label;
    528         label.printf("%0.3g", value);
    529         paint.setColor(0xFF000000);
    530         paint.setStyle(SkPaint::kFill_Style);
    531         SkFont font(nullptr, 11.0f);
    532         canvas->drawString(label, bounds.fLeft + 5, yPos - 5, font, paint);
    533         font.setSize(13.0f);
    534         canvas->drawString(name, bounds.fLeft, bounds.bottom() + 11, font, paint);
    535     }
    536 
    537     void setForGeometry() {
    538         fDrawRibs = true;
    539         fDrawTangents = true;
    540         fDrawTDivs = false;
    541         fWidthScale = 1;
    542     }
    543 
    544     void setForText() {
    545         fDrawRibs = fDrawTangents = fDrawTDivs = false;
    546         fWidthScale = 0.002f;
    547     }
    548 
    549     void setForSingles() {
    550         setForGeometry();
    551         fDrawTDivs = true;
    552     }
    553 
    554     void setAsNeeded() {
    555         if (fConicButton.fEnabled || fCubicButton.fEnabled || fQuadButton.fEnabled) {
    556             setForSingles();
    557         } else if (fRRectButton.fEnabled || fCircleButton.fEnabled || fArcButton.fEnabled) {
    558             setForGeometry();
    559         } else {
    560             setForText();
    561         }
    562     }
    563 
    564     bool arcCenter(SkPoint* center) {
    565         SkPath path;
    566         path.moveTo(fPts[10]);
    567         path.arcTo(fPts[11], fPts[12], fRadius);
    568         SkPath::Iter iter(path, false);
    569         SkPoint pts[4];
    570         iter.next(pts);
    571         if (SkPath::kLine_Verb == iter.next(pts)) {
    572             iter.next(pts);
    573         }
    574         SkVector before = pts[0] - pts[1];
    575         SkVector after = pts[1] - pts[2];
    576         before.setLength(fRadius);
    577         after.setLength(fRadius);
    578         SkVector beforeCCW, afterCCW;
    579         SkPointPriv::RotateCCW(before, &beforeCCW);
    580         SkPointPriv::RotateCCW(after, &afterCCW);
    581         beforeCCW += pts[0];
    582         afterCCW += pts[2];
    583         *center = beforeCCW;
    584         if (SkScalarNearlyEqual(beforeCCW.fX, afterCCW.fX)
    585                 && SkScalarNearlyEqual(beforeCCW.fY, afterCCW.fY)) {
    586             return true;
    587         }
    588         SkVector beforeCW, afterCW;
    589         SkPointPriv::RotateCW(before, &beforeCW);
    590         SkPointPriv::RotateCW(after, &afterCW);
    591         beforeCW += pts[0];
    592         afterCW += pts[2];
    593         *center = beforeCW;
    594         return SkScalarNearlyEqual(beforeCW.fX, afterCW.fX)
    595                 && SkScalarNearlyEqual(beforeCCW.fY, afterCW.fY);
    596     }
    597 
    598     void onDrawContent(SkCanvas* canvas) override {
    599         SkPath path;
    600         SkScalar width = fWidth;
    601 
    602         if (fCubicButton.fEnabled) {
    603             path.moveTo(fPts[0]);
    604             path.cubicTo(fPts[1], fPts[2], fPts[3]);
    605             setForSingles();
    606             draw_stroke(canvas, path, width, 950, false);
    607         }
    608 
    609         if (fConicButton.fEnabled) {
    610             path.reset();
    611             path.moveTo(fPts[4]);
    612             path.conicTo(fPts[5], fPts[6], fWeight);
    613             setForSingles();
    614             draw_stroke(canvas, path, width, 950, false);
    615         }
    616 
    617         if (fQuadButton.fEnabled) {
    618             path.reset();
    619             path.moveTo(fPts[7]);
    620             path.quadTo(fPts[8], fPts[9]);
    621             setForSingles();
    622             draw_stroke(canvas, path, width, 950, false);
    623         }
    624 
    625         if (fArcButton.fEnabled) {
    626             path.reset();
    627             path.moveTo(fPts[10]);
    628             path.arcTo(fPts[11], fPts[12], fRadius);
    629             setForGeometry();
    630             draw_stroke(canvas, path, width, 950, false);
    631             SkPath pathPts;
    632             pathPts.moveTo(fPts[10]);
    633             pathPts.lineTo(fPts[11]);
    634             pathPts.lineTo(fPts[12]);
    635             draw_points(canvas, pathPts, SK_ColorDKGRAY, true);
    636         }
    637 
    638         if (fRRectButton.fEnabled) {
    639             SkScalar rad = 32;
    640             SkRect r;
    641             r.set(&fPts[13], 2);
    642             path.reset();
    643             SkRRect rr;
    644             rr.setRectXY(r, rad, rad);
    645             path.addRRect(rr);
    646             setForGeometry();
    647             draw_stroke(canvas, path, width, 950, false);
    648 
    649             path.reset();
    650             SkRRect rr2;
    651             rr.inset(width/2, width/2, &rr2);
    652             path.addRRect(rr2, SkPath::kCCW_Direction);
    653             rr.inset(-width/2, -width/2, &rr2);
    654             path.addRRect(rr2, SkPath::kCW_Direction);
    655             SkPaint paint;
    656             paint.setAntiAlias(true);
    657             paint.setColor(0x40FF8844);
    658             canvas->drawPath(path, paint);
    659         }
    660 
    661         if (fCircleButton.fEnabled) {
    662             path.reset();
    663             SkRect r;
    664             r.set(&fPts[15], 2);
    665             path.addOval(r);
    666             setForGeometry();
    667             if (fCircleButton.fFill) {
    668                 if (fArcButton.fEnabled) {
    669                     SkPoint center;
    670                     if (arcCenter(&center)) {
    671                         r.set(center.fX - fRadius, center.fY - fRadius, center.fX + fRadius,
    672                                 center.fY + fRadius);
    673                     }
    674                 }
    675                 draw_fill(canvas, r, width);
    676             } else {
    677                 draw_stroke(canvas, path, width, 950, false);
    678             }
    679         }
    680 
    681         if (fTextButton.fEnabled) {
    682             path.reset();
    683             SkFont font;
    684             font.setSize(fTextSize);
    685             SkTextUtils::GetPath(fText.c_str(), fText.size(), kUTF8_SkTextEncoding,
    686                                  0, fTextSize, font, &path);
    687             setForText();
    688             draw_stroke(canvas, path, width * fWidthScale / fTextSize, fTextSize, true);
    689         }
    690 
    691         if (fAnimate) {
    692             fWidth += fDWidth;
    693             if (fDWidth > 0 && fWidth > kWidthMax) {
    694                 fDWidth = -fDWidth;
    695             } else if (fDWidth < 0 && fWidth < kWidthMin) {
    696                 fDWidth = -fDWidth;
    697             }
    698         }
    699         setAsNeeded();
    700         if (fConicButton.fEnabled) {
    701             draw_control(canvas, fWeightControl, fWeight, 0, 5, "weight");
    702         }
    703         if (fArcButton.fEnabled) {
    704             draw_control(canvas, fRadiusControl, fRadius, 0, 500, "radius");
    705         }
    706 #ifdef SK_DEBUG
    707         draw_control(canvas, fErrorControl, gDebugStrokerError, kStrokerErrorMin, kStrokerErrorMax,
    708                 "error");
    709 #endif
    710         draw_control(canvas, fWidthControl, fWidth * fWidthScale, kWidthMin * fWidthScale,
    711                 kWidthMax * fWidthScale, "width");
    712         draw_button(canvas, fQuadButton);
    713         draw_button(canvas, fCubicButton);
    714         draw_button(canvas, fConicButton);
    715         draw_button(canvas, fArcButton);
    716         draw_button(canvas, fRRectButton);
    717         draw_button(canvas, fCircleButton);
    718         draw_button(canvas, fTextButton);
    719     }
    720 
    721     class MyClick : public Click {
    722     public:
    723         int fIndex;
    724         MyClick(Sample* target, int index) : Click(target), fIndex(index) {}
    725     };
    726 
    727     virtual Sample::Click* onFindClickHandler(SkScalar x, SkScalar y,
    728                                               unsigned modi) override {
    729         for (size_t i = 0; i < SK_ARRAY_COUNT(fPts); ++i) {
    730             if (hittest(fPts[i], x, y)) {
    731                 return new MyClick(this, (int)i);
    732             }
    733         }
    734         const SkRect& rectPt = SkRect::MakeXYWH(x, y, 1, 1);
    735         if (fWeightControl.contains(rectPt)) {
    736             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 1);
    737         }
    738         if (fRadiusControl.contains(rectPt)) {
    739             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 2);
    740         }
    741 #ifdef SK_DEBUG
    742         if (fErrorControl.contains(rectPt)) {
    743             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 3);
    744         }
    745 #endif
    746         if (fWidthControl.contains(rectPt)) {
    747             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 4);
    748         }
    749         if (fCubicButton.fBounds.contains(rectPt)) {
    750             fCubicButton.fEnabled ^= true;
    751             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 5);
    752         }
    753         if (fConicButton.fBounds.contains(rectPt)) {
    754             fConicButton.fEnabled ^= true;
    755             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 6);
    756         }
    757         if (fQuadButton.fBounds.contains(rectPt)) {
    758             fQuadButton.fEnabled ^= true;
    759             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 7);
    760         }
    761         if (fArcButton.fBounds.contains(rectPt)) {
    762             fArcButton.fEnabled ^= true;
    763             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 8);
    764         }
    765         if (fRRectButton.fBounds.contains(rectPt)) {
    766             fRRectButton.fEnabled ^= true;
    767             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 9);
    768         }
    769         if (fCircleButton.fBounds.contains(rectPt)) {
    770             bool wasEnabled = fCircleButton.fEnabled;
    771             fCircleButton.fEnabled = !fCircleButton.fFill;
    772             fCircleButton.fFill = wasEnabled && !fCircleButton.fFill;
    773             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 10);
    774         }
    775         if (fTextButton.fBounds.contains(rectPt)) {
    776             fTextButton.fEnabled ^= true;
    777             return new MyClick(this, (int) SK_ARRAY_COUNT(fPts) + 11);
    778         }
    779         return this->INHERITED::onFindClickHandler(x, y, modi);
    780     }
    781 
    782     static SkScalar MapScreenYtoValue(int y, const SkRect& control, SkScalar min,
    783             SkScalar max) {
    784         return (SkIntToScalar(y) - control.fTop) / control.height() * (max - min) + min;
    785     }
    786 
    787     bool onClick(Click* click) override {
    788         int index = ((MyClick*)click)->fIndex;
    789         if (index < (int) SK_ARRAY_COUNT(fPts)) {
    790             fPts[index].offset(SkIntToScalar(click->fICurr.fX - click->fIPrev.fX),
    791                                SkIntToScalar(click->fICurr.fY - click->fIPrev.fY));
    792         } else if (index == (int) SK_ARRAY_COUNT(fPts) + 1) {
    793             fWeight = MapScreenYtoValue(click->fICurr.fY, fWeightControl, 0, 5);
    794         } else if (index == (int) SK_ARRAY_COUNT(fPts) + 2) {
    795             fRadius = MapScreenYtoValue(click->fICurr.fY, fRadiusControl, 0, 500);
    796         }
    797 #ifdef SK_DEBUG
    798         else if (index == (int) SK_ARRAY_COUNT(fPts) + 3) {
    799             gDebugStrokerError = SkTMax(FLT_EPSILON, MapScreenYtoValue(click->fICurr.fY,
    800                     fErrorControl, kStrokerErrorMin, kStrokerErrorMax));
    801             gDebugStrokerErrorSet = true;
    802         }
    803 #endif
    804         else if (index == (int) SK_ARRAY_COUNT(fPts) + 4) {
    805             fWidth = SkTMax(FLT_EPSILON, MapScreenYtoValue(click->fICurr.fY, fWidthControl,
    806                     kWidthMin, kWidthMax));
    807             fAnimate = fWidth <= kWidthMin;
    808         }
    809         return true;
    810     }
    811 
    812 private:
    813     typedef Sample INHERITED;
    814 };
    815 
    816 ///////////////////////////////////////////////////////////////////////////////
    817 
    818 DEF_SAMPLE( return new QuadStrokerView(); )
    819