1/*
2 * Copyright 2011 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 "SkGradientShader.h"
11
12namespace skiagm {
13
14struct GradData {
15    int              fCount;
16    const SkColor*   fColors;
17    const SkColor4f* fColors4f;
18    const SkScalar*  fPos;
19};
20
21constexpr SkColor gColors[] = {
22    SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE, SK_ColorBLACK
23};
24constexpr SkColor4f gColors4f[] ={
25    { 1.0f, 0.0f, 0.0f, 1.0f }, // Red
26    { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
27    { 0.0f, 0.0f, 1.0f, 1.0f }, // Blue
28    { 1.0f, 1.0f, 1.0f, 1.0f }, // White
29    { 0.0f, 0.0f, 0.0f, 1.0f }  // Black
30};
31constexpr SkScalar gPos0[] = { 0, SK_Scalar1 };
32constexpr SkScalar gPos1[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
33constexpr SkScalar gPos2[] = {
34    0, SK_Scalar1/8, SK_Scalar1/2, SK_Scalar1*7/8, SK_Scalar1
35};
36
37constexpr SkScalar gPosClamp[]   = {0.0f, 0.0f, 1.0f, 1.0f};
38constexpr SkColor  gColorClamp[] = {
39    SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorBLUE
40};
41constexpr SkColor4f gColor4fClamp[] ={
42    { 1.0f, 0.0f, 0.0f, 1.0f }, // Red
43    { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
44    { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
45    { 0.0f, 0.0f, 1.0f, 1.0f }  // Blue
46};
47constexpr GradData gGradData[] = {
48    { 2, gColors, gColors4f, nullptr },
49    { 2, gColors, gColors4f, gPos0 },
50    { 2, gColors, gColors4f, gPos1 },
51    { 5, gColors, gColors4f, nullptr },
52    { 5, gColors, gColors4f, gPos2 },
53    { 4, gColorClamp, gColor4fClamp, gPosClamp }
54};
55
56static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data,
57                                  SkShader::TileMode tm, const SkMatrix& localMatrix) {
58    return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm, 0,
59                                        &localMatrix);
60}
61
62static sk_sp<SkShader> MakeLinear4f(const SkPoint pts[2], const GradData& data,
63                                    SkShader::TileMode tm, const SkMatrix& localMatrix) {
64    auto srgb = SkColorSpace::MakeSRGBLinear();
65    return SkGradientShader::MakeLinear(pts, data.fColors4f, srgb, data.fPos, data.fCount, tm, 0,
66                                        &localMatrix);
67}
68
69static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data,
70                                  SkShader::TileMode tm, const SkMatrix& localMatrix) {
71    SkPoint center;
72    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
73               SkScalarAve(pts[0].fY, pts[1].fY));
74    return SkGradientShader::MakeRadial(center, center.fX, data.fColors, data.fPos, data.fCount,
75                                        tm, 0, &localMatrix);
76}
77
78static sk_sp<SkShader> MakeRadial4f(const SkPoint pts[2], const GradData& data,
79                                    SkShader::TileMode tm, const SkMatrix& localMatrix) {
80    SkPoint center;
81    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
82               SkScalarAve(pts[0].fY, pts[1].fY));
83    auto srgb = SkColorSpace::MakeSRGBLinear();
84    return SkGradientShader::MakeRadial(center, center.fX, data.fColors4f, srgb, data.fPos,
85                                        data.fCount, tm, 0, &localMatrix);
86}
87
88static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data,
89                                 SkShader::TileMode, const SkMatrix& localMatrix) {
90    SkPoint center;
91    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
92               SkScalarAve(pts[0].fY, pts[1].fY));
93    return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors, data.fPos, data.fCount,
94                                       0, &localMatrix);
95}
96
97static sk_sp<SkShader> MakeSweep4f(const SkPoint pts[2], const GradData& data,
98                                   SkShader::TileMode, const SkMatrix& localMatrix) {
99    SkPoint center;
100    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
101               SkScalarAve(pts[0].fY, pts[1].fY));
102    auto srgb = SkColorSpace::MakeSRGBLinear();
103    return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors4f, srgb, data.fPos,
104                                       data.fCount, 0, &localMatrix);
105}
106
107static sk_sp<SkShader> Make2Radial(const SkPoint pts[2], const GradData& data,
108                                   SkShader::TileMode tm, const SkMatrix& localMatrix) {
109    SkPoint center0, center1;
110    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
111                SkScalarAve(pts[0].fY, pts[1].fY));
112    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
113                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
114    return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
115                                                 center0, (pts[1].fX - pts[0].fX) / 2,
116                                                 data.fColors, data.fPos, data.fCount, tm,
117                                                 0, &localMatrix);
118}
119
120static sk_sp<SkShader> Make2Radial4f(const SkPoint pts[2], const GradData& data,
121                                     SkShader::TileMode tm, const SkMatrix& localMatrix) {
122    SkPoint center0, center1;
123    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
124                SkScalarAve(pts[0].fY, pts[1].fY));
125    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3) / 5),
126                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1) / 4));
127    auto srgb = SkColorSpace::MakeSRGBLinear();
128    return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
129                                                 center0, (pts[1].fX - pts[0].fX) / 2,
130                                                 data.fColors4f, srgb, data.fPos, data.fCount, tm,
131                                                 0, &localMatrix);
132}
133
134static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data,
135                                    SkShader::TileMode tm, const SkMatrix& localMatrix) {
136    SkPoint center0, center1;
137    SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
138    SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
139    center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
140    center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
141    return SkGradientShader::MakeTwoPointConical(center1, radius1, center0, radius0,
142                                                 data.fColors, data.fPos,
143                                                 data.fCount, tm, 0, &localMatrix);
144}
145
146static sk_sp<SkShader> Make2Conical4f(const SkPoint pts[2], const GradData& data,
147                                      SkShader::TileMode tm, const SkMatrix& localMatrix) {
148    SkPoint center0, center1;
149    SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
150    SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
151    center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
152    center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
153    auto srgb = SkColorSpace::MakeSRGBLinear();
154    return SkGradientShader::MakeTwoPointConical(center1, radius1, center0, radius0,
155                                                 data.fColors4f, srgb, data.fPos,
156                                                 data.fCount, tm, 0, &localMatrix);
157}
158
159typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data,
160                                     SkShader::TileMode tm, const SkMatrix& localMatrix);
161constexpr GradMaker gGradMakers[] = {
162    MakeLinear, MakeRadial, MakeSweep, Make2Radial, Make2Conical
163};
164constexpr GradMaker gGradMakers4f[] ={
165    MakeLinear4f, MakeRadial4f, MakeSweep4f, Make2Radial4f, Make2Conical4f
166};
167
168///////////////////////////////////////////////////////////////////////////////
169
170class GradientsGM : public GM {
171public:
172    GradientsGM(bool dither) : fDither(dither) {
173        this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
174    }
175
176protected:
177
178    SkString onShortName() {
179        return SkString(fDither ? "gradients" : "gradients_nodither");
180    }
181
182    virtual SkISize onISize() { return SkISize::Make(840, 815); }
183
184    virtual void onDraw(SkCanvas* canvas) {
185
186        SkPoint pts[2] = {
187            { 0, 0 },
188            { SkIntToScalar(100), SkIntToScalar(100) }
189        };
190        SkShader::TileMode tm = SkShader::kClamp_TileMode;
191        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
192        SkPaint paint;
193        paint.setAntiAlias(true);
194        paint.setDither(fDither);
195
196        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
197        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
198            canvas->save();
199            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
200                SkMatrix scale = SkMatrix::I();
201
202                if (i == 5) { // if the clamp case
203                    scale.setScale(0.5f, 0.5f);
204                    scale.postTranslate(25.f, 25.f);
205                }
206
207                paint.setShader(gGradMakers[j](pts, gGradData[i], tm, scale));
208                canvas->drawRect(r, paint);
209                canvas->translate(0, SkIntToScalar(120));
210            }
211            canvas->restore();
212            canvas->translate(SkIntToScalar(120), 0);
213        }
214    }
215
216protected:
217    bool fDither;
218
219private:
220    typedef GM INHERITED;
221};
222DEF_GM( return new GradientsGM(true); )
223DEF_GM( return new GradientsGM(false); )
224
225// Like the original gradients GM, but using the SkColor4f shader factories. Should be identical.
226class Gradients4fGM : public GM {
227public:
228    Gradients4fGM(bool dither) : fDither(dither) {
229        this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
230    }
231
232protected:
233
234    SkString onShortName() {
235        return SkString(fDither ? "gradients4f" : "gradients4f_nodither");
236    }
237
238    virtual SkISize onISize() { return SkISize::Make(840, 815); }
239
240    virtual void onDraw(SkCanvas* canvas) {
241
242        SkPoint pts[2] ={
243            { 0, 0 },
244            { SkIntToScalar(100), SkIntToScalar(100) }
245        };
246        SkShader::TileMode tm = SkShader::kClamp_TileMode;
247        SkRect r ={ 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
248        SkPaint paint;
249        paint.setAntiAlias(true);
250        paint.setDither(fDither);
251
252        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
253        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
254            canvas->save();
255            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers4f); j++) {
256                SkMatrix scale = SkMatrix::I();
257
258                if (i == 5) { // if the clamp case
259                    scale.setScale(0.5f, 0.5f);
260                    scale.postTranslate(25.f, 25.f);
261                }
262
263                paint.setShader(gGradMakers4f[j](pts, gGradData[i], tm, scale));
264                canvas->drawRect(r, paint);
265                canvas->translate(0, SkIntToScalar(120));
266            }
267            canvas->restore();
268            canvas->translate(SkIntToScalar(120), 0);
269        }
270    }
271
272protected:
273    bool fDither;
274
275private:
276    typedef GM INHERITED;
277};
278DEF_GM(return new Gradients4fGM(true); )
279DEF_GM(return new Gradients4fGM(false); )
280
281// Based on the original gradient slide, but with perspective applied to the
282// gradient shaders' local matrices
283class GradientsLocalPerspectiveGM : public GM {
284public:
285    GradientsLocalPerspectiveGM(bool dither) : fDither(dither) {
286        this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
287    }
288
289protected:
290
291    SkString onShortName() {
292        return SkString(fDither ? "gradients_local_perspective" :
293                                  "gradients_local_perspective_nodither");
294    }
295
296    virtual SkISize onISize() { return SkISize::Make(840, 815); }
297
298    virtual void onDraw(SkCanvas* canvas) {
299
300        SkPoint pts[2] = {
301            { 0, 0 },
302            { SkIntToScalar(100), SkIntToScalar(100) }
303        };
304        SkShader::TileMode tm = SkShader::kClamp_TileMode;
305        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
306        SkPaint paint;
307        paint.setAntiAlias(true);
308        paint.setDither(fDither);
309
310        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
311        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
312            canvas->save();
313            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
314                // apply an increasing y perspective as we move to the right
315                SkMatrix perspective;
316                perspective.setIdentity();
317                perspective.setPerspY(SkIntToScalar(i+1) / 500);
318                perspective.setSkewX(SkIntToScalar(i+1) / 10);
319
320                paint.setShader(gGradMakers[j](pts, gGradData[i], tm, perspective));
321                canvas->drawRect(r, paint);
322                canvas->translate(0, SkIntToScalar(120));
323            }
324            canvas->restore();
325            canvas->translate(SkIntToScalar(120), 0);
326        }
327    }
328
329private:
330    bool fDither;
331
332    typedef GM INHERITED;
333};
334DEF_GM( return new GradientsLocalPerspectiveGM(true); )
335DEF_GM( return new GradientsLocalPerspectiveGM(false); )
336
337// Based on the original gradient slide, but with perspective applied to
338// the view matrix
339class GradientsViewPerspectiveGM : public GradientsGM {
340public:
341    GradientsViewPerspectiveGM(bool dither) : INHERITED(dither) { }
342
343protected:
344    SkString onShortName() {
345        return SkString(fDither ? "gradients_view_perspective" :
346                                  "gradients_view_perspective_nodither");
347    }
348
349    virtual SkISize onISize() { return SkISize::Make(840, 500); }
350
351    virtual void onDraw(SkCanvas* canvas) {
352        SkMatrix perspective;
353        perspective.setIdentity();
354        perspective.setPerspY(0.001f);
355        perspective.setSkewX(SkIntToScalar(8) / 25);
356        canvas->concat(perspective);
357        INHERITED::onDraw(canvas);
358    }
359
360private:
361    typedef GradientsGM INHERITED;
362};
363DEF_GM( return new GradientsViewPerspectiveGM(true); )
364DEF_GM( return new GradientsViewPerspectiveGM(false); )
365
366/*
367 Inspired by this <canvas> javascript, where we need to detect that we are not
368 solving a quadratic equation, but must instead solve a linear (since our X^2
369 coefficient is 0)
370
371 ctx.fillStyle = '#f00';
372 ctx.fillRect(0, 0, 100, 50);
373
374 var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
375 g.addColorStop(0, '#f00');
376 g.addColorStop(0.01, '#0f0');
377 g.addColorStop(0.99, '#0f0');
378 g.addColorStop(1, '#f00');
379 ctx.fillStyle = g;
380 ctx.fillRect(0, 0, 100, 50);
381 */
382class GradientsDegenrate2PointGM : public GM {
383public:
384    GradientsDegenrate2PointGM(bool dither) : fDither(dither) {}
385
386protected:
387    SkString onShortName() {
388        return SkString(fDither ? "gradients_degenerate_2pt" : "gradients_degenerate_2pt_nodither");
389    }
390
391    virtual SkISize onISize() { return SkISize::Make(320, 320); }
392
393    void drawBG(SkCanvas* canvas) {
394        canvas->drawColor(SK_ColorBLUE);
395    }
396
397    virtual void onDraw(SkCanvas* canvas) {
398        this->drawBG(canvas);
399
400        SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorRED };
401        SkScalar pos[] = { 0, 0.01f, 0.99f, SK_Scalar1 };
402        SkPoint c0;
403        c0.iset(-80, 25);
404        SkScalar r0 = SkIntToScalar(70);
405        SkPoint c1;
406        c1.iset(0, 25);
407        SkScalar r1 = SkIntToScalar(150);
408        SkPaint paint;
409        paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors,
410                                                              pos, SK_ARRAY_COUNT(pos),
411                                                              SkShader::kClamp_TileMode));
412        paint.setDither(fDither);
413        canvas->drawPaint(paint);
414    }
415
416private:
417    bool fDither;
418
419    typedef GM INHERITED;
420};
421DEF_GM( return new GradientsDegenrate2PointGM(true); )
422DEF_GM( return new GradientsDegenrate2PointGM(false); )
423
424/* bug.skia.org/517
425<canvas id="canvas"></canvas>
426<script>
427var c = document.getElementById("canvas");
428var ctx = c.getContext("2d");
429ctx.fillStyle = '#ff0';
430ctx.fillRect(0, 0, 100, 50);
431
432var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
433g.addColorStop(0, '#0f0');
434g.addColorStop(0.003, '#f00');  // 0.004 makes this work
435g.addColorStop(1, '#ff0');
436ctx.fillStyle = g;
437ctx.fillRect(0, 0, 100, 50);
438</script>
439*/
440
441// should draw only green
442DEF_SIMPLE_GM(small_color_stop, canvas, 100, 150) {
443    SkColor colors[] = { SK_ColorGREEN, SK_ColorRED, SK_ColorYELLOW };
444    SkScalar pos[] = { 0, 0.003f, SK_Scalar1 };  // 0.004f makes this work
445    SkPoint c0 = { 200, 25 };
446    SkScalar r0 = 20;
447    SkPoint c1 = { 200, 25 };
448    SkScalar r1 = 10;
449
450    SkPaint paint;
451    paint.setColor(SK_ColorYELLOW);
452    canvas->drawRect(SkRect::MakeWH(100, 150), paint);
453    paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors, pos,
454                                                          SK_ARRAY_COUNT(pos),
455                                                          SkShader::kClamp_TileMode));
456    canvas->drawRect(SkRect::MakeWH(100, 150), paint);
457}
458
459
460/// Tests correctness of *optimized* codepaths in gradients.
461
462class ClampedGradientsGM : public GM {
463public:
464    ClampedGradientsGM(bool dither) : fDither(dither) {}
465
466protected:
467    SkString onShortName() {
468        return SkString(fDither ? "clamped_gradients" : "clamped_gradients_nodither");
469    }
470
471    virtual SkISize onISize() { return SkISize::Make(640, 510); }
472
473    void drawBG(SkCanvas* canvas) {
474        canvas->drawColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
475    }
476
477    virtual void onDraw(SkCanvas* canvas) {
478        this->drawBG(canvas);
479
480        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(300) };
481        SkPaint paint;
482        paint.setDither(fDither);
483        paint.setAntiAlias(true);
484
485        SkPoint center;
486        center.iset(0, 300);
487        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
488        paint.setShader(SkGradientShader::MakeRadial(
489            SkPoint(center),
490            SkIntToScalar(200), gColors, nullptr, 5,
491            SkShader::kClamp_TileMode));
492        canvas->drawRect(r, paint);
493    }
494
495private:
496    bool fDither;
497
498    typedef GM INHERITED;
499};
500DEF_GM( return new ClampedGradientsGM(true); )
501DEF_GM( return new ClampedGradientsGM(false); )
502
503/// Checks quality of large radial gradients, which may display
504/// some banding.
505
506class RadialGradientGM : public GM {
507public:
508    RadialGradientGM() {}
509
510protected:
511
512    SkString onShortName() override { return SkString("radial_gradient"); }
513    SkISize onISize() override { return SkISize::Make(1280, 1280); }
514    void drawBG(SkCanvas* canvas) {
515        canvas->drawColor(0xFF000000);
516    }
517    void onDraw(SkCanvas* canvas) override {
518        const SkISize dim = this->getISize();
519
520        this->drawBG(canvas);
521
522        SkPaint paint;
523        paint.setDither(true);
524        SkPoint center;
525        center.set(SkIntToScalar(dim.width())/2, SkIntToScalar(dim.height())/2);
526        SkScalar radius = SkIntToScalar(dim.width())/2;
527        const SkColor colors[] = { 0x7f7f7f7f, 0x7f7f7f7f, 0xb2000000 };
528        const SkScalar pos[] = { 0.0f,
529                             0.35f,
530                             1.0f };
531        paint.setShader(SkGradientShader::MakeRadial(center, radius, colors, pos,
532                                                     SK_ARRAY_COUNT(pos),
533                                                     SkShader::kClamp_TileMode));
534        SkRect r = {
535            0, 0, SkIntToScalar(dim.width()), SkIntToScalar(dim.height())
536        };
537        canvas->drawRect(r, paint);
538    }
539private:
540    typedef GM INHERITED;
541};
542DEF_GM( return new RadialGradientGM; )
543
544class RadialGradient2GM : public GM {
545public:
546    RadialGradient2GM(bool dither) : fDither(dither) {}
547
548protected:
549
550    SkString onShortName() override {
551        return SkString(fDither ? "radial_gradient2" : "radial_gradient2_nodither");
552    }
553
554    SkISize onISize() override { return SkISize::Make(800, 400); }
555    void drawBG(SkCanvas* canvas) {
556        canvas->drawColor(0xFF000000);
557    }
558
559    // Reproduces the example given in bug 7671058.
560    void onDraw(SkCanvas* canvas) override {
561        SkPaint paint1, paint2, paint3;
562        paint1.setStyle(SkPaint::kFill_Style);
563        paint2.setStyle(SkPaint::kFill_Style);
564        paint3.setStyle(SkPaint::kFill_Style);
565
566        const SkColor sweep_colors[] =
567            { 0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000 };
568        const SkColor colors1[] = { 0xFFFFFFFF, 0x00000000 };
569        const SkColor colors2[] = { 0xFF000000, 0x00000000 };
570
571        const SkScalar cx = 200, cy = 200, radius = 150;
572        SkPoint center;
573        center.set(cx, cy);
574
575        // We can either interpolate endpoints and premultiply each point (default, more precision),
576        // or premultiply the endpoints first, avoiding the need to premultiply each point (cheap).
577        const uint32_t flags[] = { 0, SkGradientShader::kInterpolateColorsInPremul_Flag };
578
579        for (size_t i = 0; i < SK_ARRAY_COUNT(flags); i++) {
580            paint1.setShader(SkGradientShader::MakeSweep(cx, cy, sweep_colors,
581                                                         nullptr, SK_ARRAY_COUNT(sweep_colors),
582                                                         flags[i], nullptr));
583            paint2.setShader(SkGradientShader::MakeRadial(center, radius, colors1,
584                                                          nullptr, SK_ARRAY_COUNT(colors1),
585                                                          SkShader::kClamp_TileMode,
586                                                          flags[i], nullptr));
587            paint3.setShader(SkGradientShader::MakeRadial(center, radius, colors2,
588                                                          nullptr, SK_ARRAY_COUNT(colors2),
589                                                          SkShader::kClamp_TileMode,
590                                                          flags[i], nullptr));
591            paint1.setDither(fDither);
592            paint2.setDither(fDither);
593            paint3.setDither(fDither);
594
595            canvas->drawCircle(cx, cy, radius, paint1);
596            canvas->drawCircle(cx, cy, radius, paint3);
597            canvas->drawCircle(cx, cy, radius, paint2);
598
599            canvas->translate(400, 0);
600        }
601    }
602
603private:
604    bool fDither;
605
606    typedef GM INHERITED;
607};
608DEF_GM( return new RadialGradient2GM(true); )
609DEF_GM( return new RadialGradient2GM(false); )
610
611// Shallow radial (shows banding on raster)
612class RadialGradient3GM : public GM {
613public:
614    RadialGradient3GM(bool dither) : fDither(dither) { }
615
616protected:
617    SkString onShortName() override {
618        return SkString(fDither ? "radial_gradient3" : "radial_gradient3_nodither");
619    }
620
621    SkISize onISize() override { return SkISize::Make(500, 500); }
622
623    bool runAsBench() const override { return true; }
624
625    void onOnceBeforeDraw() override {
626        const SkPoint center = { 0, 0 };
627        const SkScalar kRadius = 3000;
628        const SkColor gColors[] = { 0xFFFFFFFF, 0xFF000000 };
629        fShader = SkGradientShader::MakeRadial(center, kRadius, gColors, nullptr, 2,
630                                               SkShader::kClamp_TileMode);
631    }
632
633    void onDraw(SkCanvas* canvas) override {
634        SkPaint paint;
635        paint.setShader(fShader);
636        paint.setDither(fDither);
637        canvas->drawRect(SkRect::MakeWH(500, 500), paint);
638    }
639
640private:
641    sk_sp<SkShader> fShader;
642    bool fDither;
643
644    typedef GM INHERITED;
645};
646DEF_GM( return new RadialGradient3GM(true); )
647DEF_GM( return new RadialGradient3GM(false); )
648
649class RadialGradient4GM : public GM {
650public:
651    RadialGradient4GM(bool dither) : fDither(dither) { }
652
653protected:
654    SkString onShortName() override {
655        return SkString(fDither ? "radial_gradient4" : "radial_gradient4_nodither");
656    }
657
658    SkISize onISize() override { return SkISize::Make(500, 500); }
659
660    void onOnceBeforeDraw() override {
661        const SkPoint center = { 250, 250 };
662        const SkScalar kRadius = 250;
663        const SkColor colors[] = { SK_ColorRED, SK_ColorRED, SK_ColorWHITE, SK_ColorWHITE,
664                SK_ColorRED };
665        const SkScalar pos[] = { 0, .4f, .4f, .8f, .8f, 1 };
666        fShader = SkGradientShader::MakeRadial(center, kRadius, colors, pos,
667                                               SK_ARRAY_COUNT(gColors), SkShader::kClamp_TileMode);
668    }
669
670    void onDraw(SkCanvas* canvas) override {
671        SkPaint paint;
672        paint.setAntiAlias(true);
673        paint.setDither(fDither);
674        paint.setShader(fShader);
675        canvas->drawRect(SkRect::MakeWH(500, 500), paint);
676    }
677
678private:
679    sk_sp<SkShader> fShader;
680    bool fDither;
681
682    typedef GM INHERITED;
683};
684DEF_GM( return new RadialGradient4GM(true); )
685DEF_GM( return new RadialGradient4GM(false); )
686
687class LinearGradientGM : public GM {
688public:
689    LinearGradientGM(bool dither) : fDither(dither) { }
690
691protected:
692    SkString onShortName() override {
693        return SkString(fDither ? "linear_gradient" : "linear_gradient_nodither");
694    }
695
696    const SkScalar kWidthBump = 30.f;
697    const SkScalar kHeight = 5.f;
698    const SkScalar kMinWidth = 540.f;
699
700    SkISize onISize() override { return SkISize::Make(500, 500); }
701
702    void onOnceBeforeDraw() override {
703        SkPoint pts[2] = { {0, 0}, {0, 0} };
704        const SkColor colors[] = { SK_ColorWHITE, SK_ColorWHITE, 0xFF008200, 0xFF008200,
705                SK_ColorWHITE, SK_ColorWHITE };
706        const SkScalar unitPos[] = { 0, 50, 70, 500, 540 };
707        SkScalar pos[6];
708        pos[5] = 1;
709        for (int index = 0; index < (int) SK_ARRAY_COUNT(fShader); ++index) {
710            pts[1].fX = 500.f + index * kWidthBump;
711            for (int inner = 0; inner < (int) SK_ARRAY_COUNT(unitPos); ++inner) {
712                pos[inner] = unitPos[inner] / (kMinWidth + index * kWidthBump);
713            }
714            fShader[index] = SkGradientShader::MakeLinear(pts, colors, pos,
715                    SK_ARRAY_COUNT(gColors), SkShader::kClamp_TileMode);
716        }
717    }
718
719    void onDraw(SkCanvas* canvas) override {
720        SkPaint paint;
721        paint.setAntiAlias(true);
722        paint.setDither(fDither);
723        for (int index = 0; index < (int) SK_ARRAY_COUNT(fShader); ++index) {
724            paint.setShader(fShader[index]);
725            canvas->drawRect(SkRect::MakeLTRB(0, index * kHeight, kMinWidth + index * kWidthBump,
726                    (index + 1) * kHeight), paint);
727        }
728    }
729
730private:
731    sk_sp<SkShader> fShader[100];
732    bool fDither;
733
734    typedef GM INHERITED;
735};
736DEF_GM( return new LinearGradientGM(true); )
737DEF_GM( return new LinearGradientGM(false); )
738
739class LinearGradientTinyGM : public GM {
740public:
741    LinearGradientTinyGM(uint32_t flags, const char* suffix = nullptr)
742    : fName("linear_gradient_tiny")
743    , fFlags(flags) {
744        fName.append(suffix);
745    }
746
747protected:
748    SkString onShortName() override {
749        return fName;
750    }
751
752    SkISize onISize() override {
753        return SkISize::Make(600, 500);
754    }
755
756    void onDraw(SkCanvas* canvas) override {
757        const SkScalar kRectSize = 100;
758        const unsigned kStopCount = 3;
759        const SkColor colors[kStopCount] = { SK_ColorGREEN, SK_ColorRED, SK_ColorGREEN };
760        const struct {
761            SkPoint pts[2];
762            SkScalar pos[kStopCount];
763        } configs[] = {
764            { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.999999f,    1 }},
765            { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.000001f,    1 }},
766            { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.999999999f, 1 }},
767            { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.000000001f, 1 }},
768
769            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.999999f,    1 }},
770            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.000001f,    1 }},
771            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.999999999f, 1 }},
772            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.000000001f, 1 }},
773
774            { { SkPoint::Make(0, 0),        SkPoint::Make(0.00001f, 0) }, { 0, 0.5f, 1 }},
775            { { SkPoint::Make(9.99999f, 0), SkPoint::Make(10, 0) },       { 0, 0.5f, 1 }},
776            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 0.00001f) }, { 0, 0.5f, 1 }},
777            { { SkPoint::Make(0, 9.99999f), SkPoint::Make(0, 10) },       { 0, 0.5f, 1 }},
778        };
779
780        SkPaint paint;
781        for (unsigned i = 0; i < SK_ARRAY_COUNT(configs); ++i) {
782            SkAutoCanvasRestore acr(canvas, true);
783            paint.setShader(SkGradientShader::MakeLinear(configs[i].pts, colors, configs[i].pos,
784                                                         kStopCount, SkShader::kClamp_TileMode,
785                                                         fFlags, nullptr));
786            canvas->translate(kRectSize * ((i % 4) * 1.5f + 0.25f),
787                              kRectSize * ((i / 4) * 1.5f + 0.25f));
788
789            canvas->drawRect(SkRect::MakeWH(kRectSize, kRectSize), paint);
790        }
791    }
792
793private:
794    typedef GM INHERITED;
795
796    SkString fName;
797    uint32_t fFlags;
798};
799DEF_GM( return new LinearGradientTinyGM(0); )
800}
801
802///////////////////////////////////////////////////////////////////////////////////////////////////
803
804struct GradRun {
805    SkColor  fColors[4];
806    SkScalar fPos[4];
807    int      fCount;
808};
809
810#define SIZE 121
811
812static sk_sp<SkShader> make_linear(const GradRun& run, SkShader::TileMode mode) {
813    const SkPoint pts[] { { 30, 30 }, { SIZE - 30, SIZE - 30 } };
814    return SkGradientShader::MakeLinear(pts, run.fColors, run.fPos, run.fCount, mode);
815}
816
817static sk_sp<SkShader> make_radial(const GradRun& run, SkShader::TileMode mode) {
818    const SkScalar half = SIZE * 0.5f;
819    return SkGradientShader::MakeRadial({half,half}, half - 10, run.fColors, run.fPos,
820                                        run.fCount, mode);
821}
822
823static sk_sp<SkShader> make_conical(const GradRun& run, SkShader::TileMode mode) {
824    const SkScalar half = SIZE * 0.5f;
825    const SkPoint center { half, half };
826    return SkGradientShader::MakeTwoPointConical(center, 20, center, half - 10,
827                                                 run.fColors, run.fPos, run.fCount, mode);
828}
829
830static sk_sp<SkShader> make_sweep(const GradRun& run, SkShader::TileMode) {
831    const SkScalar half = SIZE * 0.5f;
832    return SkGradientShader::MakeSweep(half, half, run.fColors, run.fPos, run.fCount);
833}
834
835/*
836 *  Exercise duplicate color-stops, at the ends, and in the middle
837 *
838 *  At the time of this writing, only Linear correctly deals with duplicates at the ends,
839 *  and then only correctly on CPU backend.
840 */
841DEF_SIMPLE_GM(gradients_dup_color_stops, canvas, 704, 564) {
842    const SkColor preColor  = 0xFFFF0000;   // clamp color before start
843    const SkColor postColor = 0xFF0000FF;   // clamp color after end
844    const SkColor color0    = 0xFF000000;
845    const SkColor color1    = 0xFF00FF00;
846    const SkColor badColor  = 0xFF3388BB;   // should never be seen, fills out fixed-size array
847
848    const GradRun runs[] = {
849        {   { color0, color1, badColor, badColor },
850            { 0, 1, -1, -1 },
851            2,
852        },
853        {   { preColor, color0, color1, badColor },
854            { 0, 0, 1, -1 },
855            3,
856        },
857        {   { color0, color1, postColor, badColor },
858            { 0, 1, 1, -1 },
859            3,
860        },
861        {   { preColor, color0, color1, postColor },
862            { 0, 0, 1, 1 },
863            4,
864        },
865        {   { color0, color0, color1, color1 },
866            { 0, 0.5f, 0.5f, 1 },
867            4,
868        },
869    };
870    sk_sp<SkShader> (*factories[])(const GradRun&, SkShader::TileMode) {
871        make_linear, make_radial, make_conical, make_sweep
872    };
873
874    const SkRect rect = SkRect::MakeWH(SIZE, SIZE);
875    const SkScalar dx = SIZE + 20;
876    const SkScalar dy = SIZE + 20;
877    const SkShader::TileMode mode = SkShader::kClamp_TileMode;
878
879    SkPaint paint;
880    canvas->translate(10, 10 - dy);
881    for (auto factory : factories) {
882        canvas->translate(0, dy);
883        SkAutoCanvasRestore acr(canvas, true);
884        for (const auto& run : runs) {
885            paint.setShader(factory(run, mode));
886            canvas->drawRect(rect, paint);
887            canvas->translate(dx, 0);
888        }
889    }
890}
891
892static void draw_many_stops(SkCanvas* canvas) {
893    const unsigned kStopCount = 200;
894    const SkPoint pts[] = { {50, 50}, {450, 465}};
895
896    SkColor colors[kStopCount];
897    for (unsigned i = 0; i < kStopCount; i++) {
898        switch (i % 5) {
899        case 0: colors[i] = SK_ColorRED; break;
900        case 1: colors[i] = SK_ColorGREEN; break;
901        case 2: colors[i] = SK_ColorGREEN; break;
902        case 3: colors[i] = SK_ColorBLUE; break;
903        case 4: colors[i] = SK_ColorRED; break;
904        }
905    }
906
907    SkPaint p;
908    p.setShader(SkGradientShader::MakeLinear(
909        pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode));
910
911    canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
912}
913
914DEF_SIMPLE_GM(gradient_many_stops, canvas, 500, 500) {
915    draw_many_stops(canvas);
916}
917
918static void draw_subpixel_gradient(SkCanvas* canvas) {
919    const SkPoint pts[] = { {50, 50}, {50.1f, 50.1f}};
920    SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
921    SkPaint p;
922    p.setShader(SkGradientShader::MakeLinear(
923        pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kRepeat_TileMode));
924    canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
925}
926
927DEF_SIMPLE_GM(gradient_subpixel, canvas, 500, 500) {
928    draw_subpixel_gradient(canvas);
929}
930
931#include "SkPictureRecorder.h"
932
933static void draw_circle_shader(SkCanvas* canvas, SkScalar cx, SkScalar cy, SkScalar r,
934                               sk_sp<SkShader> (*shaderFunc)()) {
935    SkPaint p;
936    p.setAntiAlias(true);
937    p.setShader(shaderFunc());
938    canvas->drawCircle(cx, cy, r, p);
939
940    p.setShader(nullptr);
941    p.setColor(SK_ColorGRAY);
942    p.setStyle(SkPaint::kStroke_Style);
943    p.setStrokeWidth(2);
944    canvas->drawCircle(cx, cy, r, p);
945}
946
947DEF_SIMPLE_GM(fancy_gradients, canvas, 800, 300) {
948    draw_circle_shader(canvas, 150, 150, 100, []() -> sk_sp<SkShader> {
949        // Checkerboard using two linear gradients + picture shader.
950        SkScalar kTileSize = 80 / sqrtf(2);
951        SkColor colors1[] = { 0xff000000, 0xff000000,
952                              0xffffffff, 0xffffffff,
953                              0xff000000, 0xff000000 };
954        SkColor colors2[] = { 0xff000000, 0xff000000,
955                              0x00000000, 0x00000000,
956                              0xff000000, 0xff000000 };
957        SkScalar pos[] = { 0, .25f, .25f, .75f, .75f, 1 };
958        static_assert(SK_ARRAY_COUNT(colors1) == SK_ARRAY_COUNT(pos), "color/pos size mismatch");
959        static_assert(SK_ARRAY_COUNT(colors2) == SK_ARRAY_COUNT(pos), "color/pos size mismatch");
960
961        SkPictureRecorder recorder;
962        recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize));
963
964        SkPaint p;
965
966        SkPoint pts1[] = { { 0, 0 }, { kTileSize, kTileSize }};
967        p.setShader(SkGradientShader::MakeLinear(pts1, colors1, pos, SK_ARRAY_COUNT(colors1),
968                                                 SkShader::kClamp_TileMode, 0, nullptr));
969        recorder.getRecordingCanvas()->drawPaint(p);
970
971        SkPoint pts2[] = { { 0, kTileSize }, { kTileSize, 0 }};
972        p.setShader(SkGradientShader::MakeLinear(pts2, colors2, pos, SK_ARRAY_COUNT(colors2),
973                                                 SkShader::kClamp_TileMode, 0, nullptr));
974        recorder.getRecordingCanvas()->drawPaint(p);
975
976        SkMatrix m = SkMatrix::I();
977        m.preRotate(45);
978        return SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
979                                           SkShader::kRepeat_TileMode,
980                                           SkShader::kRepeat_TileMode, &m, nullptr);
981    });
982
983    draw_circle_shader(canvas, 400, 150, 100, []() -> sk_sp<SkShader> {
984        // Checkerboard using a sweep gradient + picture shader.
985        SkScalar kTileSize = 80;
986        SkColor colors[] = { 0xff000000, 0xff000000,
987                             0xffffffff, 0xffffffff,
988                             0xff000000, 0xff000000,
989                             0xffffffff, 0xffffffff };
990        SkScalar pos[] = { 0, .25f, .25f, .5f, .5f, .75f, .75f, 1 };
991        static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(pos), "color/pos size mismatch");
992
993        SkPaint p;
994        p.setShader(SkGradientShader::MakeSweep(kTileSize / 2, kTileSize / 2,
995                                                colors, pos, SK_ARRAY_COUNT(colors), 0, nullptr));
996        SkPictureRecorder recorder;
997        recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize))->drawPaint(p);
998
999        return SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
1000                                           SkShader::kRepeat_TileMode,
1001                                           SkShader::kRepeat_TileMode, nullptr, nullptr);
1002    });
1003
1004    draw_circle_shader(canvas, 650, 150, 100, []() -> sk_sp<SkShader> {
1005        // Dartboard using sweep + radial.
1006        const SkColor a = 0xffffffff;
1007        const SkColor b = 0xff000000;
1008        SkColor colors[] = { a, a, b, b, a, a, b, b, a, a, b, b, a, a, b, b};
1009        SkScalar pos[] = { 0, .125f, .125f, .25f, .25f, .375f, .375f, .5f, .5f,
1010                           .625f, .625f, .75f, .75f, .875f, .875f, 1};
1011        static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(pos), "color/pos size mismatch");
1012
1013        SkPoint center = { 650, 150 };
1014        sk_sp<SkShader> sweep1 = SkGradientShader::MakeSweep(center.x(), center.y(), colors, pos,
1015                                                             SK_ARRAY_COUNT(colors), 0, nullptr);
1016        SkMatrix m = SkMatrix::I();
1017        m.preRotate(22.5f, center.x(), center.y());
1018        sk_sp<SkShader> sweep2 = SkGradientShader::MakeSweep(center.x(), center.y(), colors, pos,
1019                                                             SK_ARRAY_COUNT(colors), 0, &m);
1020
1021        sk_sp<SkShader> sweep(SkShader::MakeComposeShader(sweep1, sweep2, SkBlendMode::kExclusion));
1022
1023        SkScalar radialPos[] = { 0, .02f, .02f, .04f, .04f, .08f, .08f, .16f, .16f, .31f, .31f,
1024                                 .62f, .62f, 1, 1, 1 };
1025        static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(radialPos),
1026                      "color/pos size mismatch");
1027
1028        return SkShader::MakeComposeShader(sweep,
1029                                           SkGradientShader::MakeRadial(center, 100, colors,
1030                                                                        radialPos,
1031                                                                        SK_ARRAY_COUNT(radialPos),
1032                                                                        SkShader::kClamp_TileMode),
1033                                           SkBlendMode::kExclusion);
1034    });
1035}
1036
1037DEF_SIMPLE_GM(sweep_tiling, canvas, 690, 512) {
1038    static constexpr SkScalar size = 160;
1039    static constexpr SkColor colors[] = { SK_ColorBLUE, SK_ColorYELLOW, SK_ColorGREEN };
1040    static constexpr SkScalar   pos[] = { 0, .25f, .50f };
1041    static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(pos), "size mismatch");
1042
1043    static constexpr SkShader::TileMode modes[] = { SkShader::kClamp_TileMode,
1044                                                    SkShader::kRepeat_TileMode,
1045                                                    SkShader::kMirror_TileMode };
1046
1047    static const struct {
1048        SkScalar start, end;
1049    } angles[] = {
1050        { -330, -270 },
1051        {   30,   90 },
1052        {  390,  450 },
1053        {  -30,  800 },
1054    };
1055
1056    SkPaint p;
1057    const SkRect r = SkRect::MakeWH(size, size);
1058
1059    for (auto mode : modes) {
1060        {
1061            SkAutoCanvasRestore acr(canvas, true);
1062
1063            for (auto angle : angles) {
1064                p.setShader(SkGradientShader::MakeSweep(size / 2, size / 2, colors, pos,
1065                                                        SK_ARRAY_COUNT(colors), mode,
1066                                                        angle.start, angle.end, 0, nullptr));
1067
1068                canvas->drawRect(r, p);
1069                canvas->translate(size * 1.1f, 0);
1070            }
1071        }
1072        canvas->translate(0, size * 1.1f);
1073    }
1074}
1075
1076// Exercises the special-case Ganesh gradient effects.
1077DEF_SIMPLE_GM(gradients_interesting, canvas, 640, 1300) {
1078    static const SkColor colors2[] = { SK_ColorRED, SK_ColorBLUE };
1079    static const SkColor colors3[] = { SK_ColorRED, SK_ColorYELLOW, SK_ColorBLUE };
1080    static const SkColor colors4[] = { SK_ColorRED, SK_ColorYELLOW, SK_ColorYELLOW, SK_ColorBLUE };
1081
1082    static const SkScalar softRight[]  = { 0, .999f,   1 }; // Based on Android launcher "clipping"
1083    static const SkScalar hardLeft[]   = { 0,     0,   1 };
1084    static const SkScalar hardRight[]  = { 0,     1,   1 };
1085    static const SkScalar hardCenter[] = { 0,   .5f, .5f, 1 };
1086
1087    static const struct {
1088        const SkColor*  colors;
1089        const SkScalar* pos;
1090        int             count;
1091    } configs[] = {
1092        { colors2,    nullptr, 2 }, // kTwo_ColorType
1093        { colors3,    nullptr, 3 }, // kThree_ColorType (simple)
1094        { colors3,  softRight, 3 }, // kThree_ColorType (tricky)
1095        { colors3,   hardLeft, 3 }, // kHardStopLeftEdged_ColorType
1096        { colors3,  hardRight, 3 }, // kHardStopRightEdged_ColorType
1097        { colors4, hardCenter, 4 }, // kSingleHardStop_ColorType
1098    };
1099
1100    static const SkShader::TileMode modes[] = {
1101        SkShader::kClamp_TileMode,
1102        SkShader::kRepeat_TileMode,
1103        SkShader::kMirror_TileMode,
1104    };
1105
1106    static constexpr SkScalar size = 200;
1107    static const SkPoint pts[] = { { size / 3, size / 3 }, { size * 2 / 3, size * 2 / 3} };
1108
1109    SkPaint p;
1110    for (const auto& cfg : configs) {
1111        {
1112            SkAutoCanvasRestore acr(canvas, true);
1113            for (auto mode : modes) {
1114                p.setShader(SkGradientShader::MakeLinear(pts, cfg.colors, cfg.pos, cfg.count,
1115                                                         mode));
1116                canvas->drawRect(SkRect::MakeWH(size, size), p);
1117                canvas->translate(size * 1.1f, 0);
1118            }
1119        }
1120        canvas->translate(0, size * 1.1f);
1121    }
1122}
1123