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