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, 300); }
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    }
94};
95
96///////////////////////////////////////////////////////////////////////////////
97
98static void make_unit_star(SkPath* path, int n) {
99    SkScalar rad = -SK_ScalarPI / 2;
100    const SkScalar drad = (n >> 1) * SK_ScalarPI * 2 / n;
101
102    path->moveTo(0, -SK_Scalar1);
103    for (int i = 1; i < n; i++) {
104        rad += drad;
105        SkScalar cosV, sinV = SkScalarSinCos(rad, &cosV);
106        path->lineTo(cosV, sinV);
107    }
108    path->close();
109}
110
111static void make_path_line(SkPath* path, const SkRect& bounds) {
112    path->moveTo(bounds.left(), bounds.top());
113    path->lineTo(bounds.right(), bounds.bottom());
114}
115
116static void make_path_rect(SkPath* path, const SkRect& bounds) {
117    path->addRect(bounds);
118}
119
120static void make_path_oval(SkPath* path, const SkRect& bounds) {
121    path->addOval(bounds);
122}
123
124static void make_path_star(SkPath* path, const SkRect& bounds) {
125    make_unit_star(path, 5);
126    SkMatrix matrix;
127    matrix.setRectToRect(path->getBounds(), bounds, SkMatrix::kCenter_ScaleToFit);
128    path->transform(matrix);
129}
130
131class Dashing2GM : public skiagm::GM {
132public:
133    Dashing2GM() {}
134
135protected:
136
137    SkString onShortName() {
138        return SkString("dashing2");
139    }
140
141    SkISize onISize() { return SkISize::Make(640, 480); }
142
143    virtual void onDraw(SkCanvas* canvas) {
144        static const int gIntervals[] = {
145            3,  // 3 dashes: each count [0] followed by intervals [1..count]
146            2,  10, 10,
147            4,  20, 5, 5, 5,
148            2,  2, 2
149        };
150
151        void (*gProc[])(SkPath*, const SkRect&) = {
152            make_path_line, make_path_rect, make_path_oval, make_path_star,
153        };
154
155        SkPaint paint;
156        paint.setAntiAlias(true);
157        paint.setStyle(SkPaint::kStroke_Style);
158        paint.setStrokeWidth(SkIntToScalar(6));
159
160        SkRect bounds = SkRect::MakeWH(SkIntToScalar(120), SkIntToScalar(120));
161        bounds.offset(SkIntToScalar(20), SkIntToScalar(20));
162        SkScalar dx = bounds.width() * 4 / 3;
163        SkScalar dy = bounds.height() * 4 / 3;
164
165        const int* intervals = &gIntervals[1];
166        for (int y = 0; y < gIntervals[0]; ++y) {
167            SkScalar vals[SK_ARRAY_COUNT(gIntervals)];  // more than enough
168            int count = *intervals++;
169            for (int i = 0; i < count; ++i) {
170                vals[i] = SkIntToScalar(*intervals++);
171            }
172            SkScalar phase = vals[0] / 2;
173            paint.setPathEffect(SkDashPathEffect::Create(vals, count, phase))->unref();
174
175            for (size_t x = 0; x < SK_ARRAY_COUNT(gProc); ++x) {
176                SkPath path;
177                SkRect r = bounds;
178                r.offset(x * dx, y * dy);
179                gProc[x](&path, r);
180
181                canvas->drawPath(path, paint);
182            }
183        }
184    }
185};
186
187//////////////////////////////////////////////////////////////////////////////
188
189// Test out the on/off line dashing Chrome if fond of
190class Dashing3GM : public skiagm::GM {
191public:
192    Dashing3GM() {}
193
194protected:
195
196    SkString onShortName() {
197        return SkString("dashing3");
198    }
199
200    SkISize onISize() { return SkISize::Make(640, 480); }
201
202    // Draw a 100x100 block of dashed lines. The horizontal ones are BW
203    // while the vertical ones are AA.
204    void drawDashedLines(SkCanvas* canvas,
205                         SkScalar lineLength,
206                         SkScalar phase,
207                         SkScalar dashLength,
208                         int strokeWidth,
209                         bool circles) {
210        SkPaint p;
211        p.setColor(SK_ColorBLACK);
212        p.setStyle(SkPaint::kStroke_Style);
213        p.setStrokeWidth(SkIntToScalar(strokeWidth));
214
215        if (circles) {
216            p.setStrokeCap(SkPaint::kRound_Cap);
217        }
218
219        SkScalar intervals[2] = { dashLength, dashLength };
220
221        p.setPathEffect(SkDashPathEffect::Create(intervals, 2, phase))->unref();
222
223        SkPoint pts[2];
224
225        for (int y = 0; y < 100; y += 10*strokeWidth) {
226            pts[0].set(0, SkIntToScalar(y));
227            pts[1].set(lineLength, SkIntToScalar(y));
228
229            canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
230        }
231
232        p.setAntiAlias(true);
233
234        for (int x = 0; x < 100; x += 14*strokeWidth) {
235            pts[0].set(SkIntToScalar(x), 0);
236            pts[1].set(SkIntToScalar(x), lineLength);
237
238            canvas->drawPoints(SkCanvas::kLines_PointMode, 2, pts, p);
239        }
240    }
241
242    virtual void onDraw(SkCanvas* canvas) {
243        // 1on/1off 1x1 squares with phase of 0 - points fastpath
244        canvas->save();
245            canvas->translate(2, 0);
246            this->drawDashedLines(canvas, 100, 0, SK_Scalar1, 1, false);
247        canvas->restore();
248
249        // 1on/1off 1x1 squares with phase of .5 - rects fastpath (due to partial squares)
250        canvas->save();
251            canvas->translate(112, 0);
252            this->drawDashedLines(canvas, 100, SK_ScalarHalf, SK_Scalar1, 1, false);
253        canvas->restore();
254
255        // 1on/1off 1x1 squares with phase of 1 - points fastpath
256        canvas->save();
257            canvas->translate(222, 0);
258            this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, false);
259        canvas->restore();
260
261        // 1on/1off 1x1 squares with phase of 1 and non-integer length - rects fastpath
262        canvas->save();
263            canvas->translate(332, 0);
264            this->drawDashedLines(canvas, 99.5f, SK_ScalarHalf, SK_Scalar1, 1, false);
265        canvas->restore();
266
267        // 255on/255off 1x1 squares with phase of 0 - rects fast path
268        canvas->save();
269            canvas->translate(446, 0);
270            this->drawDashedLines(canvas, 100, 0, SkIntToScalar(255), 1, false);
271        canvas->restore();
272
273        // 1on/1off 3x3 squares with phase of 0 - points fast path
274        canvas->save();
275            canvas->translate(2, 110);
276            this->drawDashedLines(canvas, 100, 0, SkIntToScalar(3), 3, false);
277        canvas->restore();
278
279        // 1on/1off 3x3 squares with phase of 1.5 - rects fast path
280        canvas->save();
281            canvas->translate(112, 110);
282            this->drawDashedLines(canvas, 100, 1.5f, SkIntToScalar(3), 3, false);
283        canvas->restore();
284
285        // 1on/1off 1x1 circles with phase of 1 - no fast path yet
286        canvas->save();
287            canvas->translate(2, 220);
288            this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, true);
289        canvas->restore();
290
291        // 1on/1off 3x3 circles with phase of 1 - no fast path yet
292        canvas->save();
293            canvas->translate(112, 220);
294            this->drawDashedLines(canvas, 100, 0, SkIntToScalar(3), 3, true);
295        canvas->restore();
296
297        // 1on/1off 1x1 squares with rotation - should break fast path
298        canvas->save();
299            canvas->translate(332+SK_ScalarRoot2Over2*100, 110+SK_ScalarRoot2Over2*100);
300            canvas->rotate(45);
301            canvas->translate(-50, -50);
302
303            this->drawDashedLines(canvas, 100, SK_Scalar1, SK_Scalar1, 1, false);
304        canvas->restore();
305
306        // 3on/3off 3x1 rects - should use rect fast path regardless of phase
307        for (int phase = 0; phase <= 3; ++phase) {
308            canvas->save();
309                canvas->translate(SkIntToScalar(phase*110+2),
310                                  SkIntToScalar(330));
311                this->drawDashedLines(canvas, 100, SkIntToScalar(phase), SkIntToScalar(3), 1, false);
312            canvas->restore();
313        }
314    }
315
316};
317
318//////////////////////////////////////////////////////////////////////////////
319
320class Dashing4GM : public skiagm::GM {
321public:
322    Dashing4GM() {}
323
324protected:
325
326    SkString onShortName() {
327        return SkString("dashing4");
328    }
329
330    SkISize onISize() { return SkISize::Make(640, 950); }
331
332    virtual void onDraw(SkCanvas* canvas) {
333        static const struct {
334            int fOnInterval;
335            int fOffInterval;
336        } gData[] = {
337            { 1, 1 },
338            { 4, 2 },
339            { 0, 4 }, // test for zero length on interval
340        };
341
342        SkPaint paint;
343        paint.setStyle(SkPaint::kStroke_Style);
344
345        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
346        canvas->translate(0, SK_ScalarHalf);
347
348        for (int width = 0; width <= 2; ++width) {
349            for (size_t data = 0; data < SK_ARRAY_COUNT(gData); ++data) {
350                for (int aa = 0; aa <= 1; ++aa) {
351                    for (int cap = 0; cap <= 1; ++cap) {
352                        int w = width * width * width;
353                        paint.setAntiAlias(SkToBool(aa));
354                        paint.setStrokeWidth(SkIntToScalar(w));
355
356                        SkToBool(cap) ? paint.setStrokeCap(SkPaint::kSquare_Cap)
357                            : paint.setStrokeCap(SkPaint::kRound_Cap);
358
359                        int scale = w ? w : 1;
360
361                        drawline(canvas, gData[data].fOnInterval * scale,
362                                 gData[data].fOffInterval * scale,
363                                 paint);
364                        canvas->translate(0, SkIntToScalar(20));
365                    }
366                }
367            }
368        }
369
370        for (int aa = 0; aa <= 1; ++aa) {
371            paint.setAntiAlias(SkToBool(aa));
372            paint.setStrokeWidth(8.f);
373            paint.setStrokeCap(SkPaint::kSquare_Cap);
374            // Single dash element that is cut off at start and end
375            drawline(canvas, 32, 16, paint, 20.f, 0, 5.f);
376            canvas->translate(0, SkIntToScalar(20));
377
378            // Two dash elements where each one is cut off at beginning and end respectively
379            drawline(canvas, 32, 16, paint, 56.f, 0, 5.f);
380            canvas->translate(0, SkIntToScalar(20));
381
382            // Many dash elements where first and last are cut off at beginning and end respectively
383            drawline(canvas, 32, 16, paint, 584.f, 0, 5.f);
384            canvas->translate(0, SkIntToScalar(20));
385
386            // Diagonal dash line where src pnts are not axis aligned (as apposed to being diagonal from
387            // a canvas rotation)
388            drawline(canvas, 32, 16, paint, 600.f, 30.f);
389            canvas->translate(0, SkIntToScalar(20));
390
391            // Case where only the off interval exists on the line. Thus nothing should be drawn
392            drawline(canvas, 32, 16, paint, 8.f, 0.f, 40.f);
393            canvas->translate(0, SkIntToScalar(20));
394        }
395    }
396};
397
398//////////////////////////////////////////////////////////////////////////////
399
400class Dashing5GM : public skiagm::GM {
401public:
402    Dashing5GM(bool doAA) : fDoAA(doAA) {}
403
404protected:
405
406    bool runAsBench() const override { return true; }
407
408    SkString onShortName() override {
409        if (fDoAA) {
410            return SkString("dashing5_aa");
411        } else {
412            return SkString("dashing5_bw");
413        }
414    }
415
416    SkISize onISize() override { return SkISize::Make(400, 200); }
417
418    void onDraw(SkCanvas* canvas) override {
419        static const int kOn = 4;
420        static const int kOff = 4;
421        static const int kIntervalLength = kOn + kOff;
422
423        static const SkColor gColors[kIntervalLength] = {
424            SK_ColorRED,
425            SK_ColorGREEN,
426            SK_ColorBLUE,
427            SK_ColorCYAN,
428            SK_ColorMAGENTA,
429            SK_ColorYELLOW,
430            SK_ColorGRAY,
431            SK_ColorDKGRAY
432        };
433
434        SkPaint paint;
435        paint.setStyle(SkPaint::kStroke_Style);
436
437        paint.setAntiAlias(fDoAA);
438
439        SkMatrix rot;
440        rot.setRotate(90);
441        SkASSERT(rot.rectStaysRect());
442
443        canvas->concat(rot);
444
445        int sign;       // used to toggle the direction of the lines
446        int phase = 0;
447
448        for (int x = 0; x < 200; x += 10) {
449            paint.setStrokeWidth(SkIntToScalar(phase+1));
450            paint.setColor(gColors[phase]);
451            sign = (x % 20) ? 1 : -1;
452            drawline(canvas, kOn, kOff, paint,
453                     SkIntToScalar(x), -sign * SkIntToScalar(10003),
454                     SkIntToScalar(phase),
455                     SkIntToScalar(x),  sign * SkIntToScalar(10003));
456            phase = (phase + 1) % kIntervalLength;
457        }
458
459        for (int y = -400; y < 0; y += 10) {
460            paint.setStrokeWidth(SkIntToScalar(phase+1));
461            paint.setColor(gColors[phase]);
462            sign = (y % 20) ? 1 : -1;
463            drawline(canvas, kOn, kOff, paint,
464                     -sign * SkIntToScalar(10003), SkIntToScalar(y),
465                     SkIntToScalar(phase),
466                      sign * SkIntToScalar(10003), SkIntToScalar(y));
467            phase = (phase + 1) % kIntervalLength;
468        }
469    }
470
471private:
472    bool fDoAA;
473};
474
475//////////////////////////////////////////////////////////////////////////////
476
477DEF_GM(return SkNEW(DashingGM);)
478DEF_GM(return SkNEW(Dashing2GM);)
479DEF_GM(return SkNEW(Dashing3GM);)
480DEF_GM(return SkNEW(Dashing4GM);)
481DEF_GM(return SkNEW_ARGS(Dashing5GM, (true));)
482DEF_GM(return SkNEW_ARGS(Dashing5GM, (false));)
483
484