1/*
2 * Copyright 2013 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 "SkTArray.h"
11
12namespace skiagm {
13
14class HairlinesGM : public GM {
15protected:
16    virtual uint32_t onGetFlags() const SK_OVERRIDE {
17        return kSkipTiled_Flag;
18    }
19
20
21    virtual SkString onShortName() SK_OVERRIDE {
22        return SkString("hairlines");
23    }
24
25    virtual SkISize onISize() { return SkISize::Make(800, 600); }
26
27    virtual void onOnceBeforeDraw() SK_OVERRIDE {
28        {
29            SkPath* lineAnglesPath = &fPaths.push_back();
30            enum {
31                kNumAngles = 15,
32                kRadius = 40,
33            };
34            for (int i = 0; i < kNumAngles; ++i) {
35                SkScalar angle = SK_ScalarPI * SkIntToScalar(i) / kNumAngles;
36                SkScalar x = kRadius * SkScalarCos(angle);
37                SkScalar y = kRadius * SkScalarSin(angle);
38                lineAnglesPath->moveTo(x, y);
39                lineAnglesPath->lineTo(-x, -y);
40            }
41        }
42
43        {
44            SkPath* kindaTightQuad = &fPaths.push_back();
45            kindaTightQuad->moveTo(0, -10 * SK_Scalar1);
46            kindaTightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -10 * SK_Scalar1, 0);
47        }
48
49        {
50            SkPath* tightQuad = &fPaths.push_back();
51            tightQuad->moveTo(0, -5 * SK_Scalar1);
52            tightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -5 * SK_Scalar1, 0);
53        }
54
55        {
56            SkPath* tighterQuad = &fPaths.push_back();
57            tighterQuad->moveTo(0, -2 * SK_Scalar1);
58            tighterQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -2 * SK_Scalar1, 0);
59        }
60
61        {
62            SkPath* unevenTighterQuad = &fPaths.push_back();
63            unevenTighterQuad->moveTo(0, -1 * SK_Scalar1);
64            SkPoint p;
65            p.set(-2 * SK_Scalar1 + 3 * SkIntToScalar(102) / 4, SkIntToScalar(75));
66            unevenTighterQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), p.fX, p.fY);
67        }
68
69        {
70            SkPath* reallyTightQuad = &fPaths.push_back();
71            reallyTightQuad->moveTo(0, -1 * SK_Scalar1);
72            reallyTightQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), -1 * SK_Scalar1, 0);
73        }
74
75        {
76            SkPath* closedQuad = &fPaths.push_back();
77            closedQuad->moveTo(0, -0);
78            closedQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100), 0, 0);
79        }
80
81        {
82            SkPath* unevenClosedQuad = &fPaths.push_back();
83            unevenClosedQuad->moveTo(0, -0);
84            unevenClosedQuad->quadTo(SkIntToScalar(100), SkIntToScalar(100),
85                                     SkIntToScalar(75), SkIntToScalar(75));
86        }
87
88        // Two problem cases for gpu hairline renderer found by shapeops testing. These used
89        // to assert that the computed bounding box didn't contain all the vertices.
90        {
91            SkPath* problem1 = &fPaths.push_back();
92            problem1->moveTo(SkIntToScalar(4), SkIntToScalar(6));
93            problem1->cubicTo(SkIntToScalar(5), SkIntToScalar(6),
94                              SkIntToScalar(5), SkIntToScalar(4),
95                              SkIntToScalar(4), SkIntToScalar(0));
96            problem1->close();
97        }
98
99        {
100            SkPath* problem2 = &fPaths.push_back();
101            problem2->moveTo(SkIntToScalar(5), SkIntToScalar(1));
102            problem2->lineTo(4.32787323f, 1.67212653f);
103            problem2->cubicTo(2.75223875f, 3.24776125f,
104                              3.00581908f, 4.51236057f,
105                              3.7580452f, 4.37367964f);
106            problem2->cubicTo(4.66472578f, 3.888381f,
107                              5.f, 2.875f,
108                              5.f, 1.f);
109            problem2->close();
110        }
111
112        // Three paths that show the same bug (missing end caps)
113        {
114            // A caret (crbug.com/131770)
115            SkPath* bug0 = &fPaths.push_back();
116            bug0->moveTo(6.5f,5.5f);
117            bug0->lineTo(3.5f,0.5f);
118            bug0->moveTo(0.5f,5.5f);
119            bug0->lineTo(3.5f,0.5f);
120        }
121
122        {
123            // An X (crbug.com/137317)
124            SkPath* bug1 = &fPaths.push_back();
125
126            bug1->moveTo(1, 1);
127            bug1->lineTo(6, 6);
128            bug1->moveTo(1, 6);
129            bug1->lineTo(6, 1);
130        }
131
132        {
133            // A right angle (crbug.com/137465 and crbug.com/256776)
134            SkPath* bug2 = &fPaths.push_back();
135
136            bug2->moveTo(5.5f, 5.5f);
137            bug2->lineTo(5.5f, 0.5f);
138            bug2->lineTo(0.5f, 0.5f);
139        }
140
141        {
142            // Arc example to test imperfect truncation bug (crbug.com/295626)
143            static const SkScalar kRad = SkIntToScalar(2000);
144            static const SkScalar kStartAngle = 262.59717f;
145            static const SkScalar kSweepAngle = SkScalarHalf(17.188717f);
146
147            SkPath* bug = &fPaths.push_back();
148
149            // Add a circular arc
150            SkRect circle = SkRect::MakeLTRB(-kRad, -kRad, kRad, kRad);
151            bug->addArc(circle, kStartAngle, kSweepAngle);
152
153            // Now add the chord that should cap the circular arc
154            SkScalar cosV, sinV = SkScalarSinCos(SkDegreesToRadians(kStartAngle), &cosV);
155
156            SkPoint p0 = SkPoint::Make(kRad * cosV, kRad * sinV);
157
158            sinV = SkScalarSinCos(SkDegreesToRadians(kStartAngle + kSweepAngle), &cosV);
159
160            SkPoint p1 = SkPoint::Make(kRad * cosV, kRad * sinV);
161
162            bug->moveTo(p0);
163            bug->lineTo(p1);
164        }
165    }
166
167    virtual void onDraw(SkCanvas* canvas) SK_OVERRIDE {
168        static const SkAlpha kAlphaValue[] = { 0xFF, 0x40 };
169
170        enum {
171            kMargin = 5,
172        };
173        int wrapX = canvas->getDeviceSize().fWidth - kMargin;
174
175        SkScalar maxH = 0;
176        canvas->translate(SkIntToScalar(kMargin), SkIntToScalar(kMargin));
177        canvas->save();
178
179        SkScalar x = SkIntToScalar(kMargin);
180        for (int p = 0; p < fPaths.count(); ++p) {
181            for (size_t a = 0; a < SK_ARRAY_COUNT(kAlphaValue); ++a) {
182                for (int aa = 0; aa < 2; ++aa) {
183                    const SkRect& bounds = fPaths[p].getBounds();
184
185                    if (x + bounds.width() > wrapX) {
186                        canvas->restore();
187                        canvas->translate(0, maxH + SkIntToScalar(kMargin));
188                        canvas->save();
189                        maxH = 0;
190                        x = SkIntToScalar(kMargin);
191                    }
192
193                    SkPaint paint;
194                    paint.setARGB(kAlphaValue[a], 0, 0, 0);
195                    paint.setAntiAlias(SkToBool(aa));
196                    paint.setStyle(SkPaint::kStroke_Style);
197                    paint.setStrokeWidth(0);
198
199                    canvas->save();
200                    canvas->translate(-bounds.fLeft, -bounds.fTop);
201                    canvas->drawPath(fPaths[p], paint);
202                    canvas->restore();
203
204                    maxH = SkMaxScalar(maxH, bounds.height());
205
206                    SkScalar dx = bounds.width() + SkIntToScalar(kMargin);
207                    x += dx;
208                    canvas->translate(dx, 0);
209                }
210            }
211        }
212        canvas->restore();
213    }
214
215private:
216    SkTArray<SkPath> fPaths;
217    typedef GM INHERITED;
218};
219
220//////////////////////////////////////////////////////////////////////////////
221
222static GM* MyFactory(void*) { return new HairlinesGM; }
223static GMRegistry reg(MyFactory);
224
225}
226