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/SkPathBuilder.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 SkPathBuilder lineAngles;
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 lineAngles.moveTo(x, y).lineTo(-x, -y);
45 }
46 fPaths.push_back(lineAngles.detach());
47 }
48
49 fPaths.push_back(SkPathBuilder().moveTo(0, -10)
50 .quadTo(100, 100, -10, 0)
51 .detach());
52
53 fPaths.push_back(SkPathBuilder().moveTo(0, -5)
54 .quadTo(100, 100, -5, 0)
55 .detach());
56
57 fPaths.push_back(SkPathBuilder().moveTo(0, -2)
58 .quadTo(100, 100, -2, 0)
59 .detach());
60
61 fPaths.push_back(SkPathBuilder().moveTo(0, -1)
62 .quadTo(100, 100, -2 + 306.0f / 4, 75)
63 .detach());
64
65 fPaths.push_back(SkPathBuilder().moveTo(0, -1)
66 .quadTo(100, 100, -1, 0)
67 .detach());
68
69 fPaths.push_back(SkPathBuilder().moveTo(0, -0)
70 .quadTo(100, 100, 0, 0)
71 .detach());
72
73 fPaths.push_back(SkPathBuilder().moveTo(0, -0)
74 .quadTo(100, 100, 75, 75)
75 .detach());
76
77 // Two problem cases for gpu hairline renderer found by shapeops testing. These used
78 // to assert that the computed bounding box didn't contain all the vertices.
79
80 fPaths.push_back(SkPathBuilder().moveTo(4, 6)
81 .cubicTo(5, 6, 5, 4, 4, 0)
82 .close()
83 .detach());
84
85 fPaths.push_back(SkPathBuilder().moveTo(5, 1)
86 .lineTo( 4.32787323f, 1.67212653f)
87 .cubicTo(2.75223875f, 3.24776125f,
88 3.00581908f, 4.51236057f,
89 3.7580452f, 4.37367964f)
90 .cubicTo(4.66472578f, 3.888381f,
91 5.f, 2.875f,
92 5.f, 1.f)
93 .close()
94 .detach());
95
96 // Three paths that show the same bug (missing end caps)
97
98 fPaths.push_back(SkPathBuilder().moveTo(6.5f,5.5f)
99 .lineTo(3.5f,0.5f)
100 .moveTo(0.5f,5.5f)
101 .lineTo(3.5f,0.5f)
102 .detach());
103
104 // An X (crbug.com/137317)
105 fPaths.push_back(SkPathBuilder().moveTo(1, 1)
106 .lineTo(6, 6)
107 .moveTo(1, 6)
108 .lineTo(6, 1)
109 .detach());
110
111 // A right angle (crbug.com/137465 and crbug.com/256776)
112 fPaths.push_back(SkPathBuilder().moveTo(5.5f, 5.5f)
113 .lineTo(5.5f, 0.5f)
114 .lineTo(0.5f, 0.5f)
115 .detach());
116
117 {
118 // Arc example to test imperfect truncation bug (crbug.com/295626)
119 constexpr SkScalar kRad = SkIntToScalar(2000);
120 constexpr SkScalar kStartAngle = 262.59717f;
121 constexpr SkScalar kSweepAngle = SkScalarHalf(17.188717f);
122
123 SkPathBuilder bug;
124
125 // Add a circular arc
126 SkRect circle = SkRect::MakeLTRB(-kRad, -kRad, kRad, kRad);
127 bug.addArc(circle, kStartAngle, kSweepAngle);
128
129 // Now add the chord that should cap the circular arc
130 SkPoint p0 = { kRad * SkScalarCos(SkDegreesToRadians(kStartAngle)),
131 kRad * SkScalarSin(SkDegreesToRadians(kStartAngle)) };
132
133 SkPoint p1 = { kRad * SkScalarCos(SkDegreesToRadians(kStartAngle + kSweepAngle)),
134 kRad * SkScalarSin(SkDegreesToRadians(kStartAngle + kSweepAngle)) };
135
136 bug.moveTo(p0);
137 bug.lineTo(p1);
138 fPaths.push_back(bug.detach());
139 }
140 }
141
onDraw(SkCanvas * canvas)142 void onDraw(SkCanvas* canvas) override {
143 constexpr SkAlpha kAlphaValue[] = { 0xFF, 0x40 };
144 constexpr SkScalar kWidths[] = { 0, 0.5f, 1.5f };
145
146 enum {
147 kMargin = 5,
148 };
149 int wrapX = 1250 - kMargin;
150
151 SkScalar maxH = 0;
152 canvas->translate(SkIntToScalar(kMargin), SkIntToScalar(kMargin));
153 canvas->save();
154
155 SkScalar x = SkIntToScalar(kMargin);
156 for (int p = 0; p < fPaths.count(); ++p) {
157 for (size_t a = 0; a < SK_ARRAY_COUNT(kAlphaValue); ++a) {
158 for (int aa = 0; aa < 2; ++aa) {
159 for (size_t w = 0; w < SK_ARRAY_COUNT(kWidths); w++) {
160 const SkRect& bounds = fPaths[p].getBounds();
161
162 if (x + bounds.width() > wrapX) {
163 canvas->restore();
164 canvas->translate(0, maxH + SkIntToScalar(kMargin));
165 canvas->save();
166 maxH = 0;
167 x = SkIntToScalar(kMargin);
168 }
169
170 SkPaint paint;
171 paint.setARGB(kAlphaValue[a], 0, 0, 0);
172 paint.setAntiAlias(SkToBool(aa));
173 paint.setStyle(SkPaint::kStroke_Style);
174 paint.setStrokeWidth(kWidths[w]);
175
176 canvas->save();
177 canvas->translate(-bounds.fLeft, -bounds.fTop);
178 canvas->drawPath(fPaths[p], paint);
179 canvas->restore();
180
181 maxH = std::max(maxH, bounds.height());
182
183 SkScalar dx = bounds.width() + SkIntToScalar(kMargin);
184 x += dx;
185 canvas->translate(dx, 0);
186 }
187 }
188 }
189 }
190 canvas->restore();
191 }
192
193 private:
194 SkTArray<SkPath> fPaths;
195 using INHERITED = GM;
196 };
197
draw_squarehair_tests(SkCanvas * canvas,SkScalar width,SkPaint::Cap cap,bool aa)198 static void draw_squarehair_tests(SkCanvas* canvas, SkScalar width, SkPaint::Cap cap, bool aa) {
199 SkPaint paint;
200 paint.setStrokeCap(cap);
201 paint.setStrokeWidth(width);
202 paint.setAntiAlias(aa);
203 paint.setStyle(SkPaint::kStroke_Style);
204 canvas->drawLine(10, 10, 20, 10, paint);
205 canvas->drawLine(30, 10, 30, 20, paint);
206 canvas->drawLine(40, 10, 50, 20, paint);
207 SkPathBuilder path;
208 path.moveTo(60, 10);
209 path.quadTo(60, 20, 70, 20);
210 path.conicTo(70, 10, 80, 10, 0.707f);
211 canvas->drawPath(path.detach(), paint);
212
213 path.moveTo(90, 10);
214 path.cubicTo(90, 20, 100, 20, 100, 10);
215 path.lineTo(110, 10);
216 canvas->drawPath(path.detach(), paint);
217 canvas->translate(0, 30);
218 }
219
220 DEF_SIMPLE_GM(squarehair, canvas, 240, 360) {
221 const bool aliases[] = { false, true };
222 const SkScalar widths[] = { 0, 0.999f, 1, 1.001f };
223 const SkPaint::Cap caps[] = { SkPaint::kButt_Cap, SkPaint::kSquare_Cap, SkPaint::kRound_Cap };
224 for (auto alias : aliases) {
225 canvas->save();
226 for (auto width : widths) {
227 for (auto cap : caps) {
228 draw_squarehair_tests(canvas, width, cap, alias);
229 }
230 }
231 canvas->restore();
232 canvas->translate(120, 0);
233 }
234 }
235
236 // GM to test subdivision of hairlines
draw_subdivided_quad(SkCanvas * canvas,int x0,int y0,int x1,int y1,SkColor color)237 static void draw_subdivided_quad(SkCanvas* canvas, int x0, int y0, int x1, int y1, SkColor color) {
238 SkPaint paint;
239 paint.setStrokeWidth(1);
240 paint.setAntiAlias(true);
241 paint.setStyle(SkPaint::kStroke_Style);
242 paint.setColor(color);
243
244 canvas->drawPath(SkPathBuilder().moveTo(0,0)
245 .quadTo(SkIntToScalar(x0), SkIntToScalar(y0),
246 SkIntToScalar(x1), SkIntToScalar(y1))
247 .detach(),
248 paint);
249 }
250
251 DEF_SIMPLE_GM(hairline_subdiv, canvas, 512, 256) {
252 // no subdivisions
253 canvas->translate(45, -25);
254 draw_subdivided_quad(canvas, 334, 334, 467, 267, SK_ColorBLACK);
255
256 // one subdivision
257 canvas->translate(-185, -150);
258 draw_subdivided_quad(canvas, 472, 472, 660, 378, SK_ColorRED);
259
260 // two subdivisions
261 canvas->translate(-275, -200);
262 draw_subdivided_quad(canvas, 668, 668, 934, 535, SK_ColorGREEN);
263
264 // three subdivisions
265 canvas->translate(-385, -260);
266 draw_subdivided_quad(canvas, 944, 944, 1320, 756, SK_ColorBLUE);
267 }
268
269 //////////////////////////////////////////////////////////////////////////////
270
271 DEF_GM( return new HairlinesGM; )
272
273 } // namespace skiagm
274