• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
hittest(const SkPoint & target,SkScalar x,SkScalar y)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 
getOnCurvePoints(const SkPath & path,SkPoint storage[])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 
getContourCounts(const SkPath & path,SkTArray<int> * contourCounts)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 
erase(const sk_sp<SkSurface> & surface)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:
QuadStrokerView()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:
onQuery(Sample::Event * evt)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 
onSizeChange()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 
copyMinToMax()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 
setWHZ(int width,int height,int zoom)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 
draw_points(SkCanvas * canvas,const SkPath & path,SkColor color,bool show_lines)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 
draw_ribs(SkCanvas * canvas,const SkPath & path,SkScalar width,SkColor color)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 
draw_t_divs(SkCanvas * canvas,const SkPath & path,SkScalar width,SkColor color)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 
draw_stroke(SkCanvas * canvas,const SkPath & path,SkScalar width,SkScalar scale,bool drawText)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 
draw_fill(SkCanvas * canvas,const SkRect & rect,SkScalar width)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 
draw_button(SkCanvas * canvas,const StrokeTypeButton & button)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 
draw_control(SkCanvas * canvas,const SkRect & bounds,SkScalar value,SkScalar min,SkScalar max,const char * name)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 
setForGeometry()537     void setForGeometry() {
538         fDrawRibs = true;
539         fDrawTangents = true;
540         fDrawTDivs = false;
541         fWidthScale = 1;
542     }
543 
setForText()544     void setForText() {
545         fDrawRibs = fDrawTangents = fDrawTDivs = false;
546         fWidthScale = 0.002f;
547     }
548 
setForSingles()549     void setForSingles() {
550         setForGeometry();
551         fDrawTDivs = true;
552     }
553 
setAsNeeded()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 
arcCenter(SkPoint * center)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 
onDrawContent(SkCanvas * canvas)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;
MyClick(Sample * target,int index)724         MyClick(Sample* target, int index) : Click(target), fIndex(index) {}
725     };
726 
onFindClickHandler(SkScalar x,SkScalar y,unsigned modi)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 
MapScreenYtoValue(int y,const SkRect & control,SkScalar min,SkScalar max)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 
onClick(Click * click)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