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