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 "gm.h"
9#include "SkCanvas.h"
10#include "SkPaint.h"
11#include "SkDashPathEffect.h"
12
13static void drawline(SkCanvas* canvas, int on, int off, const SkPaint& paint,
14                     SkScalar finalX = SkIntToScalar(600), SkScalar finalY = SkIntToScalar(0),
15                     SkScalar phase = SkIntToScalar(0),
16                     SkScalar startX = SkIntToScalar(0), SkScalar startY = SkIntToScalar(0)) {
17    SkPaint p(paint);
18
19    const SkScalar intervals[] = {
20        SkIntToScalar(on),
21        SkIntToScalar(off),
22    };
23
24    p.setPathEffect(SkDashPathEffect::Create(intervals, 2, phase))->unref();
25    canvas->drawLine(startX, startY, finalX, finalY, p);
26}
27
28// earlier bug stopped us from drawing very long single-segment dashes, because
29// SkPathMeasure was skipping very small delta-T values (nearlyzero). This is
30// now fixes, so this giant dash should appear.
31static void show_giant_dash(SkCanvas* canvas) {
32    SkPaint paint;
33
34    drawline(canvas, 1, 1, paint, SkIntToScalar(20 * 1000));
35}
36
37static void show_zero_len_dash(SkCanvas* canvas) {
38    SkPaint paint;
39
40    drawline(canvas, 2, 2, paint, SkIntToScalar(0));
41    paint.setStyle(SkPaint::kStroke_Style);
42    paint.setStrokeWidth(SkIntToScalar(2));
43    canvas->translate(0, SkIntToScalar(20));
44    drawline(canvas, 4, 4, paint, SkIntToScalar(0));
45}
46
47class DashingGM : public skiagm::GM {
48public:
49    DashingGM() {}
50
51protected:
52
53    SkString onShortName() {
54        return SkString("dashing");
55    }
56
57    SkISize onISize() { return SkISize::Make(640, 340); }
58
59    virtual void onDraw(SkCanvas* canvas) {
60        static const struct {
61            int fOnInterval;
62            int fOffInterval;
63        } gData[] = {
64            { 1, 1 },
65            { 4, 1 },
66        };
67
68        SkPaint paint;
69        paint.setStyle(SkPaint::kStroke_Style);
70
71        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
72        canvas->translate(0, SK_ScalarHalf);
73        for (int width = 0; width <= 2; ++width) {
74            for (size_t data = 0; data < SK_ARRAY_COUNT(gData); ++data) {
75                for (int aa = 0; aa <= 1; ++aa) {
76                    int w = width * width * width;
77                    paint.setAntiAlias(SkToBool(aa));
78                    paint.setStrokeWidth(SkIntToScalar(w));
79
80                    int scale = w ? w : 1;
81
82                    drawline(canvas, gData[data].fOnInterval * scale,
83                             gData[data].fOffInterval * scale,
84                             paint);
85                    canvas->translate(0, SkIntToScalar(20));
86                }
87            }
88        }
89
90        show_giant_dash(canvas);
91        canvas->translate(0, SkIntToScalar(20));
92        show_zero_len_dash(canvas);
93        canvas->translate(0, SkIntToScalar(20));
94        // Draw 0 on, 0 off dashed line
95        paint.setStrokeWidth(SkIntToScalar(8));
96        drawline(canvas, 0, 0, paint);
97    }
98};
99
100///////////////////////////////////////////////////////////////////////////////
101
102static void make_unit_star(SkPath* path, int n) {
103    SkScalar rad = -SK_ScalarPI / 2;
104    const SkScalar drad = (n >> 1) * SK_ScalarPI * 2 / n;
105
106    path->moveTo(0, -SK_Scalar1);
107    for (int i = 1; i < n; i++) {
108        rad += drad;
109        SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
110        path->lineTo(cosV, sinV);
111    }
112    path->close();
113}
114
115static void make_path_line(SkPath* path, const SkRect& bounds) {
116    path->moveTo(bounds.left(), bounds.top());
117    path->lineTo(bounds.right(), bounds.bottom());
118}
119
120static void make_path_rect(SkPath* path, const SkRect& bounds) {
121    path->addRect(bounds);
122}
123
124static void make_path_oval(SkPath* path, const SkRect& bounds) {
125    path->addOval(bounds);
126}
127
128static void make_path_star(SkPath* path, const SkRect& bounds) {
129    make_unit_star(path, 5);
130    SkMatrix matrix;
131    matrix.setRectToRect(path->getBounds(), bounds, SkMatrix::kCenter_ScaleToFit);
132    path->transform(matrix);
133}
134
135class Dashing2GM : public skiagm::GM {
136public:
137    Dashing2GM() {}
138
139protected:
140
141    SkString onShortName() {
142        return SkString("dashing2");
143    }
144
145    SkISize onISize() { return SkISize::Make(640, 480); }
146
147    virtual void onDraw(SkCanvas* canvas) {
148        static const int gIntervals[] = {
149            3,  // 3 dashes: each count [0] followed by intervals [1..count]
150            2,  10, 10,
151            4,  20, 5, 5, 5,
152            2,  2, 2
153        };
154
155        void (*gProc[])(SkPath*, const SkRect&) = {
156            make_path_line, make_path_rect, make_path_oval, make_path_star,
157        };
158
159        SkPaint paint;
160        paint.setAntiAlias(true);
161        paint.setStyle(SkPaint::kStroke_Style);
162        paint.setStrokeWidth(SkIntToScalar(6));
163
164        SkRect bounds = SkRect::MakeWH(SkIntToScalar(120), SkIntToScalar(120));
165        bounds.offset(SkIntToScalar(20), SkIntToScalar(20));
166        SkScalar dx = bounds.width() * 4 / 3;
167        SkScalar dy = bounds.height() * 4 / 3;
168
169        const int* intervals = &gIntervals[1];
170        for (int y = 0; y < gIntervals[0]; ++y) {
171            SkScalar vals[SK_ARRAY_COUNT(gIntervals)];  // more than enough
172            int count = *intervals++;
173            for (int i = 0; i < count; ++i) {
174                vals[i] = SkIntToScalar(*intervals++);
175            }
176            SkScalar phase = vals[0] / 2;
177            paint.setPathEffect(SkDashPathEffect::Create(vals, count, phase))->unref();
178
179            for (size_t x = 0; x < SK_ARRAY_COUNT(gProc); ++x) {
180                SkPath path;
181                SkRect r = bounds;
182                r.offset(x * dx, y * dy);
183                gProc[x](&path, r);
184
185                canvas->drawPath(path, paint);
186            }
187        }
188    }
189};
190
191//////////////////////////////////////////////////////////////////////////////
192
193// Test out the on/off line dashing Chrome if fond of
194class Dashing3GM : public skiagm::GM {
195public:
196    Dashing3GM() {}
197
198protected:
199
200    SkString onShortName() {
201        return SkString("dashing3");
202    }
203
204    SkISize onISize() { return SkISize::Make(640, 480); }
205
206    // Draw a 100x100 block of dashed lines. The horizontal ones are BW
207    // while the vertical ones are AA.
208    void drawDashedLines(SkCanvas* canvas,
209                         SkScalar lineLength,
210                         SkScalar phase,
211                         SkScalar dashLength,
212                         int strokeWidth,
213                         bool circles) {
214        SkPaint p;
215        p.setColor(SK_ColorBLACK);
216        p.setStyle(SkPaint::kStroke_Style);
217        p.setStrokeWidth(SkIntToScalar(strokeWidth));
218
219        if (circles) {
220            p.setStrokeCap(SkPaint::kRound_Cap);
221        }
222
223        SkScalar intervals[2] = { dashLength, dashLength };
224
225        p.setPathEffect(SkDashPathEffect::Create(intervals, 2, phase))->unref();
226
227        SkPoint pts[2];
228
229        for (int y = 0; y < 100; y += 10*strokeWidth) {
230            pts[0].set(0, SkIntToScalar(y));
231            pts[1].set(lineLength, SkIntToScalar(y));
232
233            canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
234        }
235
236        p.setAntiAlias(true);
237
238        for (int x = 0; x < 100; x += 14*strokeWidth) {
239            pts[0].set(SkIntToScalar(x), 0);
240            pts[1].set(SkIntToScalar(x), lineLength);
241
242            canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
243        }
244    }
245
246    virtual void onDraw(SkCanvas* canvas) {
247        // 1on/1off 1x1 squares with phase of 0 - points fastpath
248        canvas->save();
249            canvas->translate(2, 0);
250            this->drawDashedLines(canvas, 100, 0, SK_Scalar1, 1, false);
251        canvas->restore();
252
253        // 1on/1off 1x1 squares with phase of .5 - rects fastpath (due to partial squares)
254        canvas->save();
255            canvas->translate(112, 0);
256            this->drawDashedLines(canvas, 100, SK_ScalarHalf, SK_Scalar1, 1, false);
257        canvas->restore();
258
259        // 1on/1off 1x1 squares with phase of 1 - points fastpath
260        canvas->save();
261            canvas->translate(222, 0);
262            this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, false);
263        canvas->restore();
264
265        // 1on/1off 1x1 squares with phase of 1 and non-integer length - rects fastpath
266        canvas->save();
267            canvas->translate(332, 0);
268            this->drawDashedLines(canvas, 99.5f, SK_ScalarHalf, SK_Scalar1, 1, false);
269        canvas->restore();
270
271        // 255on/255off 1x1 squares with phase of 0 - rects fast path
272        canvas->save();
273            canvas->translate(446, 0);
274            this->drawDashedLines(canvas, 100, 0, SkIntToScalar(255), 1, false);
275        canvas->restore();
276
277        // 1on/1off 3x3 squares with phase of 0 - points fast path
278        canvas->save();
279            canvas->translate(2, 110);
280            this->drawDashedLines(canvas, 100, 0, SkIntToScalar(3), 3, false);
281        canvas->restore();
282
283        // 1on/1off 3x3 squares with phase of 1.5 - rects fast path
284        canvas->save();
285            canvas->translate(112, 110);
286            this->drawDashedLines(canvas, 100, 1.5f, SkIntToScalar(3), 3, false);
287        canvas->restore();
288
289        // 1on/1off 1x1 circles with phase of 1 - no fast path yet
290        canvas->save();
291            canvas->translate(2, 220);
292            this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, true);
293        canvas->restore();
294
295        // 1on/1off 3x3 circles with phase of 1 - no fast path yet
296        canvas->save();
297            canvas->translate(112, 220);
298            this->drawDashedLines(canvas, 100, 0, SkIntToScalar(3), 3, true);
299        canvas->restore();
300
301        // 1on/1off 1x1 squares with rotation - should break fast path
302        canvas->save();
303            canvas->translate(332+SK_ScalarRoot2Over2*100, 110+SK_ScalarRoot2Over2*100);
304            canvas->rotate(45);
305            canvas->translate(-50, -50);
306
307            this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, false);
308        canvas->restore();
309
310        // 3on/3off 3x1 rects - should use rect fast path regardless of phase
311        for (int phase = 0; phase <= 3; ++phase) {
312            canvas->save();
313                canvas->translate(SkIntToScalar(phase*110+2),
314                                  SkIntToScalar(330));
315                this->drawDashedLines(canvas, 100, SkIntToScalar(phase), SkIntToScalar(3), 1, false);
316            canvas->restore();
317        }
318    }
319
320};
321
322//////////////////////////////////////////////////////////////////////////////
323
324class Dashing4GM : public skiagm::GM {
325public:
326    Dashing4GM() {}
327
328protected:
329
330    SkString onShortName() {
331        return SkString("dashing4");
332    }
333
334    SkISize onISize() { return SkISize::Make(640, 950); }
335
336    virtual void onDraw(SkCanvas* canvas) {
337        static const struct {
338            int fOnInterval;
339            int fOffInterval;
340        } gData[] = {
341            { 1, 1 },
342            { 4, 2 },
343            { 0, 4 }, // test for zero length on interval
344        };
345
346        SkPaint paint;
347        paint.setStyle(SkPaint::kStroke_Style);
348
349        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
350        canvas->translate(0, SK_ScalarHalf);
351
352        for (int width = 0; width <= 2; ++width) {
353            for (size_t data = 0; data < SK_ARRAY_COUNT(gData); ++data) {
354                for (int aa = 0; aa <= 1; ++aa) {
355                    for (int cap = 0; cap <= 1; ++cap) {
356                        int w = width * width * width;
357                        paint.setAntiAlias(SkToBool(aa));
358                        paint.setStrokeWidth(SkIntToScalar(w));
359
360                        SkToBool(cap) ? paint.setStrokeCap(SkPaint::kSquare_Cap)
361                            : paint.setStrokeCap(SkPaint::kRound_Cap);
362
363                        int scale = w ? w : 1;
364
365                        drawline(canvas, gData[data].fOnInterval * scale,
366                                 gData[data].fOffInterval * scale,
367                                 paint);
368                        canvas->translate(0, SkIntToScalar(20));
369                    }
370                }
371            }
372        }
373
374        for (int aa = 0; aa <= 1; ++aa) {
375            paint.setAntiAlias(SkToBool(aa));
376            paint.setStrokeWidth(8.f);
377            paint.setStrokeCap(SkPaint::kSquare_Cap);
378            // Single dash element that is cut off at start and end
379            drawline(canvas, 32, 16, paint, 20.f, 0, 5.f);
380            canvas->translate(0, SkIntToScalar(20));
381
382            // Two dash elements where each one is cut off at beginning and end respectively
383            drawline(canvas, 32, 16, paint, 56.f, 0, 5.f);
384            canvas->translate(0, SkIntToScalar(20));
385
386            // Many dash elements where first and last are cut off at beginning and end respectively
387            drawline(canvas, 32, 16, paint, 584.f, 0, 5.f);
388            canvas->translate(0, SkIntToScalar(20));
389
390            // Diagonal dash line where src pnts are not axis aligned (as apposed to being diagonal from
391            // a canvas rotation)
392            drawline(canvas, 32, 16, paint, 600.f, 30.f);
393            canvas->translate(0, SkIntToScalar(20));
394
395            // Case where only the off interval exists on the line. Thus nothing should be drawn
396            drawline(canvas, 32, 16, paint, 8.f, 0.f, 40.f);
397            canvas->translate(0, SkIntToScalar(20));
398        }
399    }
400};
401
402//////////////////////////////////////////////////////////////////////////////
403
404class Dashing5GM : public skiagm::GM {
405public:
406    Dashing5GM(bool doAA) : fDoAA(doAA) {}
407
408protected:
409
410    bool runAsBench() const override { return true; }
411
412    SkString onShortName() override {
413        if (fDoAA) {
414            return SkString("dashing5_aa");
415        } else {
416            return SkString("dashing5_bw");
417        }
418    }
419
420    SkISize onISize() override { return SkISize::Make(400, 200); }
421
422    void onDraw(SkCanvas* canvas) override {
423        static const int kOn = 4;
424        static const int kOff = 4;
425        static const int kIntervalLength = kOn + kOff;
426
427        static const SkColor gColors[kIntervalLength] = {
428            SK_ColorRED,
429            SK_ColorGREEN,
430            SK_ColorBLUE,
431            SK_ColorCYAN,
432            SK_ColorMAGENTA,
433            SK_ColorYELLOW,
434            SK_ColorGRAY,
435            SK_ColorDKGRAY
436        };
437
438        SkPaint paint;
439        paint.setStyle(SkPaint::kStroke_Style);
440
441        paint.setAntiAlias(fDoAA);
442
443        SkMatrix rot;
444        rot.setRotate(90);
445        SkASSERT(rot.rectStaysRect());
446
447        canvas->concat(rot);
448
449        int sign;       // used to toggle the direction of the lines
450        int phase = 0;
451
452        for (int x = 0; x < 200; x += 10) {
453            paint.setStrokeWidth(SkIntToScalar(phase+1));
454            paint.setColor(gColors[phase]);
455            sign = (x % 20) ? 1 : -1;
456            drawline(canvas, kOn, kOff, paint,
457                     SkIntToScalar(x), -sign * SkIntToScalar(10003),
458                     SkIntToScalar(phase),
459                     SkIntToScalar(x),  sign * SkIntToScalar(10003));
460            phase = (phase + 1) % kIntervalLength;
461        }
462
463        for (int y = -400; y < 0; y += 10) {
464            paint.setStrokeWidth(SkIntToScalar(phase+1));
465            paint.setColor(gColors[phase]);
466            sign = (y % 20) ? 1 : -1;
467            drawline(canvas, kOn, kOff, paint,
468                     -sign * SkIntToScalar(10003), SkIntToScalar(y),
469                     SkIntToScalar(phase),
470                      sign * SkIntToScalar(10003), SkIntToScalar(y));
471            phase = (phase + 1) % kIntervalLength;
472        }
473    }
474
475private:
476    bool fDoAA;
477};
478
479DEF_SIMPLE_GM(longpathdash, canvas, 512, 512) {
480    SkPath lines;
481    for (int x = 32; x < 256; x += 16) {
482        for (SkScalar a = 0; a < 3.141592f * 2; a += 0.03141592f) {
483            SkPoint pts[2] = {
484                { 256 + (float) sin(a) * x,
485                  256 + (float) cos(a) * x },
486                { 256 + (float) sin(a + 3.141592 / 3) * (x + 64),
487                  256 + (float) cos(a + 3.141592 / 3) * (x + 64) }
488            };
489            lines.moveTo(pts[0]);
490            for (SkScalar i = 0; i < 1; i += 0.05f) {
491                lines.lineTo(pts[0].fX * (1 - i) + pts[1].fX * i,
492                             pts[0].fY * (1 - i) + pts[1].fY * i);
493            }
494        }
495    }
496    SkPaint p;
497    p.setAntiAlias(true);
498    p.setStyle(SkPaint::kStroke_Style);
499    p.setStrokeWidth(1);
500    const SkScalar intervals[] = { 1, 1 };
501    p.setPathEffect(SkDashPathEffect::Create(intervals, SK_ARRAY_COUNT(intervals), 0))->unref();
502    canvas->drawPath(lines, p);
503}
504
505DEF_SIMPLE_GM(longlinedash, canvas, 512, 512) {
506    SkPaint p;
507    p.setAntiAlias(true);
508    p.setStyle(SkPaint::kStroke_Style);
509    p.setStrokeWidth(80);
510
511    const SkScalar intervals[] = { 2, 2 };
512    p.setPathEffect(SkDashPathEffect::Create(intervals, SK_ARRAY_COUNT(intervals), 0))->unref();
513    canvas->drawRect(SkRect::MakeXYWH(-10000, 100, 20000, 20), p);
514}
515
516DEF_SIMPLE_GM(longwavyline, canvas, 512, 512) {
517    SkPaint p;
518    p.setAntiAlias(true);
519    p.setStyle(SkPaint::kStroke_Style);
520    p.setStrokeWidth(2);
521
522    SkPath wavy;
523    wavy.moveTo(-10000, 100);
524    for (SkScalar i = -10000; i < 10000; i += 20) {
525        wavy.quadTo(i + 5, 95, i + 10, 100);
526        wavy.quadTo(i + 15, 105, i + 20, 100);
527    }
528    canvas->drawPath(wavy, p);
529}
530
531//////////////////////////////////////////////////////////////////////////////
532
533DEF_GM(return new DashingGM;)
534DEF_GM(return new Dashing2GM;)
535DEF_GM(return new Dashing3GM;)
536DEF_GM(return new Dashing4GM;)
537DEF_GM(return new Dashing5GM(true);)
538DEF_GM(return new Dashing5GM(false);)
539