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(), 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(), 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