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