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 "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/SkPathMeasure.h"
14 #include "include/core/SkPoint.h"
15 #include "include/core/SkRect.h"
16 #include "include/core/SkScalar.h"
17 #include "include/core/SkSize.h"
18 #include "include/core/SkString.h"
19 #include "include/core/SkTypes.h"
20 #include "include/private/SkFloatingPoint.h"
21 #include "include/utils/SkRandom.h"
22 #include "tools/ToolUtils.h"
23 #include "tools/timer/TimeUtils.h"
24
25 class AddArcGM : public skiagm::GM {
26 public:
AddArcGM()27 AddArcGM() : fRotate(0) {}
28
29 protected:
onShortName()30 SkString onShortName() override { return SkString("addarc"); }
31
onISize()32 SkISize onISize() override { return SkISize::Make(1040, 1040); }
33
onDraw(SkCanvas * canvas)34 void onDraw(SkCanvas* canvas) override {
35 canvas->translate(20, 20);
36
37 SkRect r = SkRect::MakeWH(1000, 1000);
38
39 SkPaint paint;
40 paint.setAntiAlias(true);
41 paint.setStroke(true);
42 paint.setStrokeWidth(15);
43
44 const SkScalar inset = paint.getStrokeWidth() + 4;
45 const SkScalar sweepAngle = 345;
46 SkRandom rand;
47
48 SkScalar sign = 1;
49 while (r.width() > paint.getStrokeWidth() * 3) {
50 paint.setColor(ToolUtils::color_to_565(rand.nextU() | (0xFF << 24)));
51 SkScalar startAngle = rand.nextUScalar1() * 360;
52
53 SkScalar speed = SkScalarSqrt(16 / r.width()) * 0.5f;
54 startAngle += fRotate * 360 * speed * sign;
55
56 SkPathBuilder path;
57 path.addArc(r, startAngle, sweepAngle);
58 canvas->drawPath(path.detach().setIsVolatile(true), paint);
59
60 r.inset(inset, inset);
61 sign = -sign;
62 }
63 }
64
onAnimate(double nanos)65 bool onAnimate(double nanos) override {
66 fRotate = TimeUtils::Scaled(1e-9 * nanos, 1, 360);
67 return true;
68 }
69
70 private:
71 SkScalar fRotate;
72 using INHERITED = skiagm::GM;
73 };
74 DEF_GM( return new AddArcGM; )
75
76 ///////////////////////////////////////////////////
77
78 #define R 400
79
80 DEF_SIMPLE_GM(addarc_meas, canvas, 2*R + 40, 2*R + 40) {
81 canvas->translate(R + 20, R + 20);
82
83 SkPaint paint;
84 paint.setAntiAlias(true);
85 paint.setStroke(true);
86
87 SkPaint measPaint;
88 measPaint.setAntiAlias(true);
89 measPaint.setColor(SK_ColorRED);
90
91 const SkRect oval = SkRect::MakeLTRB(-R, -R, R, R);
92 canvas->drawOval(oval, paint);
93
94 for (SkScalar deg = 0; deg < 360; deg += 10) {
95 const SkScalar rad = SkDegreesToRadians(deg);
96 SkScalar rx = SkScalarCos(rad) * R;
97 SkScalar ry = SkScalarSin(rad) * R;
98
99 canvas->drawLine(0, 0, rx, ry, paint);
100
101 SkPathMeasure meas(SkPathBuilder().addArc(oval, 0, deg).detach(), false);
102 SkScalar arcLen = rad * R;
103 SkPoint pos;
104 if (meas.getPosTan(arcLen, &pos, nullptr)) {
105 canvas->drawLine({0, 0}, pos, measPaint);
106 }
107 }
108 }
109
110 ///////////////////////////////////////////////////
111
112 // Emphasize drawing a stroked oval (containing conics) and then scaling the results up,
113 // to ensure that we compute the stroke taking the CTM into account
114 //
115 class StrokeCircleGM : public skiagm::GM {
116 public:
StrokeCircleGM()117 StrokeCircleGM() : fRotate(0) {}
118
119 protected:
onShortName()120 SkString onShortName() override { return SkString("strokecircle"); }
121
onISize()122 SkISize onISize() override { return SkISize::Make(520, 520); }
123
onDraw(SkCanvas * canvas)124 void onDraw(SkCanvas* canvas) override {
125 canvas->scale(20, 20);
126 canvas->translate(13, 13);
127
128 SkPaint paint;
129 paint.setAntiAlias(true);
130 paint.setStroke(true);
131 paint.setStrokeWidth(SK_Scalar1 / 2);
132
133 const SkScalar delta = paint.getStrokeWidth() * 3 / 2;
134 SkRect r = SkRect::MakeXYWH(-12, -12, 24, 24);
135 SkRandom rand;
136
137 SkScalar sign = 1;
138 while (r.width() > paint.getStrokeWidth() * 2) {
139 SkAutoCanvasRestore acr(canvas, true);
140 canvas->rotate(fRotate * sign);
141
142 paint.setColor(ToolUtils::color_to_565(rand.nextU() | (0xFF << 24)));
143 canvas->drawOval(r, paint);
144 r.inset(delta, delta);
145 sign = -sign;
146 }
147 }
148
onAnimate(double nanos)149 bool onAnimate(double nanos) override {
150 fRotate = TimeUtils::Scaled(1e-9 * nanos, 60, 360);
151 return true;
152 }
153
154 private:
155 SkScalar fRotate;
156
157 using INHERITED = skiagm::GM;
158 };
159 DEF_GM( return new StrokeCircleGM; )
160
161 //////////////////////
162
163 // Fill circles and rotate them to test our Analytic Anti-Aliasing.
164 // This test is based on StrokeCircleGM.
165 class FillCircleGM : public skiagm::GM {
166 public:
FillCircleGM()167 FillCircleGM() : fRotate(0) {}
168
169 protected:
onShortName()170 SkString onShortName() override { return SkString("fillcircle"); }
171
onISize()172 SkISize onISize() override { return SkISize::Make(520, 520); }
173
onDraw(SkCanvas * canvas)174 void onDraw(SkCanvas* canvas) override {
175 canvas->scale(20, 20);
176 canvas->translate(13, 13);
177
178 SkPaint paint;
179 paint.setAntiAlias(true);
180 paint.setStroke(true);
181 paint.setStrokeWidth(SK_Scalar1 / 2);
182
183 const SkScalar strokeWidth = paint.getStrokeWidth();
184 const SkScalar delta = strokeWidth * 3 / 2;
185 SkRect r = SkRect::MakeXYWH(-12, -12, 24, 24);
186 SkRandom rand;
187
188 // Reset style to fill. We only need stroke stype for producing delta and strokeWidth
189 paint.setStroke(false);
190
191 SkScalar sign = 1;
192 while (r.width() > strokeWidth * 2) {
193 SkAutoCanvasRestore acr(canvas, true);
194 canvas->rotate(fRotate * sign);
195 paint.setColor(ToolUtils::color_to_565(rand.nextU() | (0xFF << 24)));
196 canvas->drawOval(r, paint);
197 r.inset(delta, delta);
198 sign = -sign;
199 }
200 }
201
onAnimate(double nanos)202 bool onAnimate(double nanos) override {
203 fRotate = TimeUtils::Scaled(1e-9 * nanos, 60, 360);
204 return true;
205 }
206
207 private:
208 SkScalar fRotate;
209
210 using INHERITED = skiagm::GM;
211 };
DEF_GM(return new FillCircleGM;)212 DEF_GM( return new FillCircleGM; )
213
214 //////////////////////
215
216 static void html_canvas_arc(SkPathBuilder* path, SkScalar x, SkScalar y, SkScalar r, SkScalar start,
217 SkScalar end, bool ccw, bool callArcTo) {
218 SkRect bounds = { x - r, y - r, x + r, y + r };
219 SkScalar sweep = ccw ? end - start : start - end;
220 if (callArcTo)
221 path->arcTo(bounds, start, sweep, false);
222 else
223 path->addArc(bounds, start, sweep);
224 }
225
226 // Lifted from canvas-arc-circumference-fill-diffs.html
227 DEF_SIMPLE_GM(manyarcs, canvas, 620, 330) {
228 SkPaint paint;
229 paint.setAntiAlias(true);
230 paint.setStroke(true);
231
232 canvas->translate(10, 10);
233
234 // 20 angles.
235 SkScalar sweepAngles[] = {
236 -123.7f, -2.3f, -2, -1, -0.3f, -0.000001f, 0, 0.000001f, 0.3f, 0.7f,
237 1, 1.3f, 1.5f, 1.7f, 1.99999f, 2, 2.00001f, 2.3f, 4.3f, 3934723942837.3f
238 };
239 for (size_t i = 0; i < SK_ARRAY_COUNT(sweepAngles); ++i) {
240 sweepAngles[i] *= 180;
241 }
242
243 SkScalar startAngles[] = { -1, -0.5f, 0, 0.5f };
244 for (size_t i = 0; i < SK_ARRAY_COUNT(startAngles); ++i) {
245 startAngles[i] *= 180;
246 }
247
248 bool anticlockwise = false;
249 SkScalar sign = 1;
250 for (size_t i = 0; i < SK_ARRAY_COUNT(startAngles) * 2; ++i) {
251 if (i == SK_ARRAY_COUNT(startAngles)) {
252 anticlockwise = true;
253 sign = -1;
254 }
255 SkScalar startAngle = startAngles[i % SK_ARRAY_COUNT(startAngles)] * sign;
256 canvas->save();
257 for (size_t j = 0; j < SK_ARRAY_COUNT(sweepAngles); ++j) {
258 SkPathBuilder path;
259 path.moveTo(0, 2);
260 html_canvas_arc(&path, 18, 15, 10, startAngle, startAngle + (sweepAngles[j] * sign),
261 anticlockwise, true);
262 path.lineTo(0, 28);
263 canvas->drawPath(path.detach().setIsVolatile(true), paint);
264 canvas->translate(30, 0);
265 }
266 canvas->restore();
267 canvas->translate(0, 40);
268 }
269 }
270
271 // Lifted from https://bugs.chromium.org/p/chromium/issues/detail?id=640031
272 DEF_SIMPLE_GM(tinyanglearcs, canvas, 620, 330) {
273 SkPaint paint;
274 paint.setAntiAlias(true);
275 paint.setStroke(true);
276
277 canvas->translate(50, 50);
278
279 SkScalar outerRadius = 100000.0f;
280 SkScalar innerRadius = outerRadius - 20.0f;
281 SkScalar centerX = 50;
282 SkScalar centerY = outerRadius;
283 SkScalar startAngles[] = { 1.5f * SK_ScalarPI , 1.501f * SK_ScalarPI };
284 SkScalar sweepAngle = 10.0f / outerRadius;
285
286 for (size_t i = 0; i < SK_ARRAY_COUNT(startAngles); ++i) {
287 SkPathBuilder path;
288 SkScalar endAngle = startAngles[i] + sweepAngle;
289 path.moveTo(centerX + innerRadius * sk_float_cos(startAngles[i]),
290 centerY + innerRadius * sk_float_sin(startAngles[i]));
291 path.lineTo(centerX + outerRadius * sk_float_cos(startAngles[i]),
292 centerY + outerRadius * sk_float_sin(startAngles[i]));
293 // A combination of tiny sweepAngle + large radius, we should draw a line.
294 html_canvas_arc(&path, centerX, outerRadius, outerRadius,
295 startAngles[i] * 180 / SK_ScalarPI, endAngle * 180 / SK_ScalarPI,
296 true, true);
297 path.lineTo(centerX + innerRadius * sk_float_cos(endAngle),
298 centerY + innerRadius * sk_float_sin(endAngle));
299 html_canvas_arc(&path, centerX, outerRadius, innerRadius,
300 endAngle * 180 / SK_ScalarPI, startAngles[i] * 180 / SK_ScalarPI,
301 true, false);
302 canvas->drawPath(path.detach(), paint);
303 canvas->translate(20, 0);
304 }
305 }
306