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