1/*
2 * Copyright 2015 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 "SkPath.h"
9#include "SkStream.h"
10#include "gm.h"
11
12
13// Test how short paths are stroked with various caps
14class StrokeZeroGM : public skiagm::GM {
15    SkPath fPaths[8];
16    SkPath fClipL, fClipR, fClipS;
17
18protected:
19    void onOnceBeforeDraw() override {
20        fClipL.moveTo(0, 0);
21        fClipL.lineTo(3, 0);
22        fClipL.lineTo(2.5f, 1);
23        fClipL.lineTo(3.5f, 2.5f);
24        fClipL.lineTo(2.5f, 4);
25        fClipL.lineTo(3, 5);
26        fClipL.lineTo(0, 5);
27        fClipL.close();
28
29        fClipR.moveTo(34, 0);
30        fClipR.lineTo(34, 5);
31        fClipR.lineTo(31, 5);
32        fClipR.lineTo(30.5, 4);
33        fClipR.lineTo(31.5, 2.5);
34        fClipR.lineTo(30.5, 1);
35        fClipR.lineTo(31, 0);
36        fClipR.close();
37
38        fClipS.addRect(SkRect::MakeIWH(4, 5));
39
40        fPaths[0].moveTo(30, 0);  // single line segment
41        fPaths[0].rLineTo(30, 0);
42
43        fPaths[1].moveTo(90, 0);  // single line segment with close (does not draw caps)
44        fPaths[1].rLineTo(30, 0);
45        fPaths[1].close();
46
47        fPaths[2].moveTo(150, 0);  // zero-length line
48        fPaths[2].rLineTo(0, 0);
49
50        fPaths[3].moveTo(180, 0);  // zero-length line with close (expected not to draw)
51        fPaths[3].rLineTo(0, 0);
52        fPaths[3].close();
53
54        fPaths[4].moveTo(210, 0);  // close only, no line
55        fPaths[4].close();
56
57        fPaths[5].moveTo(30, 90);  // all combos below should draw two caps
58        fPaths[5].rLineTo(0, 0);
59        fPaths[5].moveTo(60, 90);
60        fPaths[5].rLineTo(0, 0);
61
62        fPaths[6].moveTo(90, 90);
63        fPaths[6].close();
64        fPaths[6].moveTo(120, 90);
65        fPaths[6].close();
66
67        fPaths[7].moveTo(150, 90);
68        fPaths[7].rLineTo(0, 0);
69        fPaths[7].moveTo(180, 90);
70        fPaths[7].close();
71    }
72
73
74    SkString onShortName() override {
75        return SkString("path_stroke_with_zero_length");
76    }
77
78    SkISize onISize() override {
79        return SkISize::Make(1120, 840);
80    }
81
82    void onDraw(SkCanvas* canvas) override {
83        SkPaint bkgrnd;
84        bkgrnd.setColor(SK_ColorWHITE);
85        canvas->drawRect(SkRect::MakeIWH(onISize().fWidth, onISize().fHeight), bkgrnd);
86
87         auto drawPaths = [&](SkPaint& paint, int indexMask) {
88            canvas->translate(0, 30.0f);
89            int index = 0;
90            for (const SkPath& path : fPaths) {
91                if (indexMask & (1 << index)) {
92                    canvas->drawPath(path, paint);
93                }
94                if (paint.getStrokeWidth() < 2) {
95                    drawFat(canvas, path, paint, index);
96                }
97                ++index;
98            }
99        };
100
101        if (false) { // debugging variant that draws a single element
102            SkScalar width = 0;
103            bool antialias = true;
104
105            SkPaint butt;
106            butt.setAntiAlias(antialias);
107            butt.setStyle(SkPaint::kStroke_Style);
108            butt.setStrokeWidth(width);
109
110            SkPaint round(butt);
111            round.setStrokeCap(SkPaint::kRound_Cap);
112            drawPaths(round, 1 << 7);
113            return;
114        }
115
116        SkScalar widths[] = { 0, .999f, 1, 1.001f, 20 };
117        bool aliases[] = { false, true };
118        for (bool antialias : aliases) {
119            canvas->save();
120            for (SkScalar width : widths) {
121                canvas->save();
122                SkPaint butt;
123                butt.setAntiAlias(antialias);
124                butt.setStyle(SkPaint::kStroke_Style);
125                butt.setStrokeWidth(width);
126                drawPaths(butt, -1);
127
128                SkPaint round(butt);
129                round.setStrokeCap(SkPaint::kRound_Cap);
130                drawPaths(round, -1);
131
132                SkPaint square(butt);
133                square.setStrokeCap(SkPaint::kSquare_Cap);
134                drawPaths(square, -1);
135                canvas->restore();
136                canvas->translate(220, 0);
137            }
138            canvas->restore();
139            canvas->translate(0, 210);
140        }
141    }
142
143private:
144    void drawFat(SkCanvas* canvas, const SkPath& path, const SkPaint& paint, int index) {
145        const SkScalar scale = 10;
146        SkRect bounds = path.getBounds();
147        SkBitmap offscreen;
148        offscreen.allocN32Pixels(SkScalarRoundToInt(bounds.width() + 4),
149                SkScalarRoundToInt(bounds.height() + 4));
150        offscreen.eraseColor(SK_ColorWHITE);
151        SkScalar pathX = bounds.fLeft - 2;
152        SkScalar pathY = bounds.fTop - 2;
153        SkMatrix cMatrix = canvas->getTotalMatrix();
154        if (!canvas->readPixels(&offscreen, SkScalarRoundToInt(pathX + cMatrix.getTranslateX()),
155                SkScalarRoundToInt(pathY + cMatrix.getTranslateY()))) {
156            return;
157        }
158
159        canvas->save();
160        SkMatrix clipM;
161        clipM.reset();
162        clipM.preScale(scale, scale);
163        clipM.postTranslate(bounds.fLeft - 17, bounds.fTop - 24.5f + 420);
164        SkPath clip;
165        if (index < 2) {
166            fClipL.transform(clipM, &clip);
167        } else {
168            fClipS.transform(clipM, &clip);
169        }
170        canvas->clipPath(clip, SkRegion::kIntersect_Op, true);
171        canvas->scale(scale, scale);
172        canvas->drawBitmap(offscreen, (bounds.fLeft - 17) / scale,
173                    (bounds.fTop - 20 + 420) / scale);
174        canvas->restore();
175
176        if (bounds.width() > 20) {
177            canvas->save();
178            clipM.reset();
179            clipM.preScale(scale, scale);
180            clipM.postTranslate(bounds.fLeft - 17 - 275, bounds.fTop - 24.5f + 420);
181            SkPath clip;
182            fClipR.transform(clipM, &clip);
183            canvas->clipPath(clip, SkRegion::kIntersect_Op, true);
184            canvas->scale(10.f, 10.f);
185            canvas->drawBitmap(offscreen, (bounds.fLeft - 17 - 275
186                    + (index >= 5 ? 5 : 0)) / scale, (bounds.fTop - 20 + 420) / scale);
187            canvas->restore();
188        }
189    }
190
191};
192
193///////////////////////////////////////////////////////////////////////////////
194
195DEF_GM( return new StrokeZeroGM(); )
196
197