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 "SkGradientShader.h"
10#include "SkLinearGradient.h"
11
12namespace skiagm {
13
14struct GradData {
15    int             fCount;
16    const SkColor*  fColors;
17    const SkScalar* fPos;
18};
19
20static const SkColor gColors[] = {
21    SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE, SK_ColorBLACK
22};
23static const SkScalar gPos0[] = { 0, SK_Scalar1 };
24static const SkScalar gPos1[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
25static const SkScalar gPos2[] = {
26    0, SK_Scalar1/8, SK_Scalar1/2, SK_Scalar1*7/8, SK_Scalar1
27};
28
29static const SkScalar gPosClamp[]   = {0.0f, 0.0f, 1.0f, 1.0f};
30static const SkColor  gColorClamp[] = {
31    SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorBLUE
32};
33
34static const GradData gGradData[] = {
35    { 2, gColors, nullptr },
36    { 2, gColors, gPos0 },
37    { 2, gColors, gPos1 },
38    { 5, gColors, nullptr },
39    { 5, gColors, gPos2 },
40    { 4, gColorClamp, gPosClamp }
41};
42
43static SkShader* MakeLinear(const SkPoint pts[2], const GradData& data,
44                            SkShader::TileMode tm, const SkMatrix& localMatrix) {
45    return SkGradientShader::CreateLinear(pts, data.fColors, data.fPos,
46                                          data.fCount, tm, 0, &localMatrix);
47}
48
49static SkShader* MakeRadial(const SkPoint pts[2], const GradData& data,
50                            SkShader::TileMode tm, const SkMatrix& localMatrix) {
51    SkPoint center;
52    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
53               SkScalarAve(pts[0].fY, pts[1].fY));
54    return SkGradientShader::CreateRadial(center, center.fX, data.fColors,
55                                          data.fPos, data.fCount, tm, 0, &localMatrix);
56}
57
58static SkShader* MakeSweep(const SkPoint pts[2], const GradData& data,
59                           SkShader::TileMode, const SkMatrix& localMatrix) {
60    SkPoint center;
61    center.set(SkScalarAve(pts[0].fX, pts[1].fX),
62               SkScalarAve(pts[0].fY, pts[1].fY));
63    return SkGradientShader::CreateSweep(center.fX, center.fY, data.fColors,
64                                         data.fPos, data.fCount, 0, &localMatrix);
65}
66
67static SkShader* Make2Radial(const SkPoint pts[2], const GradData& data,
68                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
69    SkPoint center0, center1;
70    center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
71                SkScalarAve(pts[0].fY, pts[1].fY));
72    center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
73                SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
74    return SkGradientShader::CreateTwoPointConical(
75                                                   center1, (pts[1].fX - pts[0].fX) / 7,
76                                                   center0, (pts[1].fX - pts[0].fX) / 2,
77                                                   data.fColors, data.fPos, data.fCount, tm,
78                                                   0, &localMatrix);
79}
80
81static SkShader* Make2Conical(const SkPoint pts[2], const GradData& data,
82                             SkShader::TileMode tm, const SkMatrix& localMatrix) {
83    SkPoint center0, center1;
84    SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
85    SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
86    center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
87    center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
88    return SkGradientShader::CreateTwoPointConical(center1, radius1,
89                                                   center0, radius0,
90                                                   data.fColors, data.fPos,
91                                                   data.fCount, tm, 0, &localMatrix);
92}
93
94typedef SkShader* (*GradMaker)(const SkPoint pts[2], const GradData& data,
95                               SkShader::TileMode tm, const SkMatrix& localMatrix);
96static const GradMaker gGradMakers[] = {
97    MakeLinear, MakeRadial, MakeSweep, Make2Radial, Make2Conical
98};
99
100///////////////////////////////////////////////////////////////////////////////
101
102class GradientsGM : public GM {
103public:
104    GradientsGM(bool dither) : fDither(dither) {
105        this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
106    }
107
108protected:
109
110    SkString onShortName() {
111        return SkString(fDither ? "gradients" : "gradients_nodither");
112    }
113
114    virtual SkISize onISize() { return SkISize::Make(840, 815); }
115
116    virtual void onDraw(SkCanvas* canvas) {
117
118        SkPoint pts[2] = {
119            { 0, 0 },
120            { SkIntToScalar(100), SkIntToScalar(100) }
121        };
122        SkShader::TileMode tm = SkShader::kClamp_TileMode;
123        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
124        SkPaint paint;
125        paint.setAntiAlias(true);
126        paint.setDither(fDither);
127
128        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
129        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
130            canvas->save();
131            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
132                SkMatrix scale = SkMatrix::I();
133
134                if (i == 5) { // if the clamp case
135                    scale.setScale(0.5f, 0.5f);
136                    scale.postTranslate(25.f, 25.f);
137                }
138
139                SkShader* shader = gGradMakers[j](pts, gGradData[i], tm, scale);
140
141                paint.setShader(shader);
142                canvas->drawRect(r, paint);
143                shader->unref();
144                canvas->translate(0, SkIntToScalar(120));
145            }
146            canvas->restore();
147            canvas->translate(SkIntToScalar(120), 0);
148        }
149    }
150
151protected:
152    bool fDither;
153
154private:
155    typedef GM INHERITED;
156};
157DEF_GM( return new GradientsGM(true); )
158DEF_GM( return new GradientsGM(false); )
159
160// Based on the original gradient slide, but with perspective applied to the
161// gradient shaders' local matrices
162class GradientsLocalPerspectiveGM : public GM {
163public:
164    GradientsLocalPerspectiveGM(bool dither) : fDither(dither) {
165        this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
166    }
167
168protected:
169
170    SkString onShortName() {
171        return SkString(fDither ? "gradients_local_perspective" :
172                                  "gradients_local_perspective_nodither");
173    }
174
175    virtual SkISize onISize() { return SkISize::Make(840, 815); }
176
177    virtual void onDraw(SkCanvas* canvas) {
178
179        SkPoint pts[2] = {
180            { 0, 0 },
181            { SkIntToScalar(100), SkIntToScalar(100) }
182        };
183        SkShader::TileMode tm = SkShader::kClamp_TileMode;
184        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
185        SkPaint paint;
186        paint.setAntiAlias(true);
187        paint.setDither(fDither);
188
189        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
190        for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
191            canvas->save();
192            for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
193                // apply an increasing y perspective as we move to the right
194                SkMatrix perspective;
195                perspective.setIdentity();
196                perspective.setPerspY(SkIntToScalar(i+1) / 500);
197                perspective.setSkewX(SkIntToScalar(i+1) / 10);
198
199                SkShader* shader = gGradMakers[j](pts, gGradData[i], tm, perspective);
200
201                paint.setShader(shader);
202                canvas->drawRect(r, paint);
203                shader->unref();
204                canvas->translate(0, SkIntToScalar(120));
205            }
206            canvas->restore();
207            canvas->translate(SkIntToScalar(120), 0);
208        }
209    }
210
211private:
212    bool fDither;
213
214    typedef GM INHERITED;
215};
216DEF_GM( return new GradientsLocalPerspectiveGM(true); )
217DEF_GM( return new GradientsLocalPerspectiveGM(false); )
218
219// Based on the original gradient slide, but with perspective applied to
220// the view matrix
221class GradientsViewPerspectiveGM : public GradientsGM {
222public:
223    GradientsViewPerspectiveGM(bool dither) : INHERITED(dither) { }
224
225protected:
226    SkString onShortName() {
227        return SkString(fDither ? "gradients_view_perspective" :
228                                  "gradients_view_perspective_nodither");
229    }
230
231    virtual SkISize onISize() { return SkISize::Make(840, 500); }
232
233    virtual void onDraw(SkCanvas* canvas) {
234        SkMatrix perspective;
235        perspective.setIdentity();
236        perspective.setPerspY(0.001f);
237        perspective.setSkewX(SkIntToScalar(8) / 25);
238        canvas->concat(perspective);
239        INHERITED::onDraw(canvas);
240    }
241
242private:
243    typedef GradientsGM INHERITED;
244};
245DEF_GM( return new GradientsViewPerspectiveGM(true); )
246DEF_GM( return new GradientsViewPerspectiveGM(false); )
247
248/*
249 Inspired by this <canvas> javascript, where we need to detect that we are not
250 solving a quadratic equation, but must instead solve a linear (since our X^2
251 coefficient is 0)
252
253 ctx.fillStyle = '#f00';
254 ctx.fillRect(0, 0, 100, 50);
255
256 var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
257 g.addColorStop(0, '#f00');
258 g.addColorStop(0.01, '#0f0');
259 g.addColorStop(0.99, '#0f0');
260 g.addColorStop(1, '#f00');
261 ctx.fillStyle = g;
262 ctx.fillRect(0, 0, 100, 50);
263 */
264class GradientsDegenrate2PointGM : public GM {
265public:
266    GradientsDegenrate2PointGM(bool dither) : fDither(dither) {}
267
268protected:
269    SkString onShortName() {
270        return SkString(fDither ? "gradients_degenerate_2pt" : "gradients_degenerate_2pt_nodither");
271    }
272
273    virtual SkISize onISize() { return SkISize::Make(320, 320); }
274
275    void drawBG(SkCanvas* canvas) {
276        canvas->drawColor(SK_ColorBLUE);
277    }
278
279    virtual void onDraw(SkCanvas* canvas) {
280        this->drawBG(canvas);
281
282        SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorRED };
283        SkScalar pos[] = { 0, 0.01f, 0.99f, SK_Scalar1 };
284        SkPoint c0;
285        c0.iset(-80, 25);
286        SkScalar r0 = SkIntToScalar(70);
287        SkPoint c1;
288        c1.iset(0, 25);
289        SkScalar r1 = SkIntToScalar(150);
290        SkShader* s = SkGradientShader::CreateTwoPointConical(c0, r0, c1, r1, colors,
291                                                              pos, SK_ARRAY_COUNT(pos),
292                                                              SkShader::kClamp_TileMode);
293        SkPaint paint;
294        paint.setDither(fDither);
295        paint.setShader(s)->unref();
296        canvas->drawPaint(paint);
297    }
298
299private:
300    bool fDither;
301
302    typedef GM INHERITED;
303};
304DEF_GM( return new GradientsDegenrate2PointGM(true); )
305DEF_GM( return new GradientsDegenrate2PointGM(false); )
306
307/// Tests correctness of *optimized* codepaths in gradients.
308
309class ClampedGradientsGM : public GM {
310public:
311    ClampedGradientsGM(bool dither) : fDither(dither) {}
312
313protected:
314    SkString onShortName() {
315        return SkString(fDither ? "clamped_gradients" : "clamped_gradients_nodither");
316    }
317
318    virtual SkISize onISize() { return SkISize::Make(640, 510); }
319
320    void drawBG(SkCanvas* canvas) {
321        canvas->drawColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
322    }
323
324    virtual void onDraw(SkCanvas* canvas) {
325        this->drawBG(canvas);
326
327        SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(300) };
328        SkPaint paint;
329        paint.setDither(fDither);
330        paint.setAntiAlias(true);
331
332        SkPoint center;
333        center.iset(0, 300);
334        canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
335        SkShader* shader = SkGradientShader::CreateRadial(
336            SkPoint(center),
337            SkIntToScalar(200), gColors, nullptr, 5,
338            SkShader::kClamp_TileMode);
339        paint.setShader(shader);
340        canvas->drawRect(r, paint);
341        shader->unref();
342    }
343
344private:
345    bool fDither;
346
347    typedef GM INHERITED;
348};
349DEF_GM( return new ClampedGradientsGM(true); )
350DEF_GM( return new ClampedGradientsGM(false); )
351
352/// Checks quality of large radial gradients, which may display
353/// some banding.
354
355class RadialGradientGM : public GM {
356public:
357    RadialGradientGM() {}
358
359protected:
360
361    SkString onShortName() override { return SkString("radial_gradient"); }
362    SkISize onISize() override { return SkISize::Make(1280, 1280); }
363    void drawBG(SkCanvas* canvas) {
364        canvas->drawColor(0xFF000000);
365    }
366    void onDraw(SkCanvas* canvas) override {
367        const SkISize dim = this->getISize();
368
369        this->drawBG(canvas);
370
371        SkPaint paint;
372        paint.setDither(true);
373        SkPoint center;
374        center.set(SkIntToScalar(dim.width())/2, SkIntToScalar(dim.height())/2);
375        SkScalar radius = SkIntToScalar(dim.width())/2;
376        const SkColor colors[] = { 0x7f7f7f7f, 0x7f7f7f7f, 0xb2000000 };
377        const SkScalar pos[] = { 0.0f,
378                             0.35f,
379                             1.0f };
380        SkShader* shader =
381            SkGradientShader::CreateRadial(center, radius, colors,
382                                           pos, SK_ARRAY_COUNT(pos),
383                                           SkShader::kClamp_TileMode);
384        paint.setShader(shader)->unref();
385        SkRect r = {
386            0, 0, SkIntToScalar(dim.width()), SkIntToScalar(dim.height())
387        };
388        canvas->drawRect(r, paint);
389    }
390private:
391    typedef GM INHERITED;
392};
393DEF_GM( return new RadialGradientGM; )
394
395class RadialGradient2GM : public GM {
396public:
397    RadialGradient2GM(bool dither) : fDither(dither) {}
398
399protected:
400
401    SkString onShortName() override {
402        return SkString(fDither ? "radial_gradient2" : "radial_gradient2_nodither");
403    }
404
405    SkISize onISize() override { return SkISize::Make(800, 400); }
406    void drawBG(SkCanvas* canvas) {
407        canvas->drawColor(0xFF000000);
408    }
409
410    // Reproduces the example given in bug 7671058.
411    void onDraw(SkCanvas* canvas) override {
412        SkPaint paint1, paint2, paint3;
413        paint1.setStyle(SkPaint::kFill_Style);
414        paint2.setStyle(SkPaint::kFill_Style);
415        paint3.setStyle(SkPaint::kFill_Style);
416
417        const SkColor sweep_colors[] =
418            { 0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000 };
419        const SkColor colors1[] = { 0xFFFFFFFF, 0x00000000 };
420        const SkColor colors2[] = { 0xFF000000, 0x00000000 };
421
422        const SkScalar cx = 200, cy = 200, radius = 150;
423        SkPoint center;
424        center.set(cx, cy);
425
426        // We can either interpolate endpoints and premultiply each point (default, more precision),
427        // or premultiply the endpoints first, avoiding the need to premultiply each point (cheap).
428        const uint32_t flags[] = { 0, SkGradientShader::kInterpolateColorsInPremul_Flag };
429
430        for (size_t i = 0; i < SK_ARRAY_COUNT(flags); i++) {
431            SkAutoTUnref<SkShader> sweep(
432                    SkGradientShader::CreateSweep(cx, cy, sweep_colors,
433                                                  nullptr, SK_ARRAY_COUNT(sweep_colors),
434                                                  flags[i], nullptr));
435            SkAutoTUnref<SkShader> radial1(
436                    SkGradientShader::CreateRadial(center, radius, colors1,
437                                                   nullptr, SK_ARRAY_COUNT(colors1),
438                                                   SkShader::kClamp_TileMode,
439                                                   flags[i], nullptr));
440            SkAutoTUnref<SkShader> radial2(
441                    SkGradientShader::CreateRadial(center, radius, colors2,
442                                                   nullptr, SK_ARRAY_COUNT(colors2),
443                                                   SkShader::kClamp_TileMode,
444                                                   flags[i], nullptr));
445            paint1.setShader(sweep);
446            paint1.setDither(fDither);
447            paint2.setShader(radial1);
448            paint2.setDither(fDither);
449            paint3.setShader(radial2);
450            paint3.setDither(fDither);
451
452            canvas->drawCircle(cx, cy, radius, paint1);
453            canvas->drawCircle(cx, cy, radius, paint3);
454            canvas->drawCircle(cx, cy, radius, paint2);
455
456            canvas->translate(400, 0);
457        }
458    }
459
460private:
461    bool fDither;
462
463    typedef GM INHERITED;
464};
465DEF_GM( return new RadialGradient2GM(true); )
466DEF_GM( return new RadialGradient2GM(false); )
467
468// Shallow radial (shows banding on raster)
469class RadialGradient3GM : public GM {
470public:
471    RadialGradient3GM(bool dither) : fDither(dither) { }
472
473protected:
474    SkString onShortName() override {
475        return SkString(fDither ? "radial_gradient3" : "radial_gradient3_nodither");
476    }
477
478    SkISize onISize() override { return SkISize::Make(500, 500); }
479
480    bool runAsBench() const override { return true; }
481
482    void onOnceBeforeDraw() override {
483        const SkPoint center = { 0, 0 };
484        const SkScalar kRadius = 3000;
485        const SkColor gColors[] = { 0xFFFFFFFF, 0xFF000000 };
486        fShader.reset(SkGradientShader::CreateRadial(center, kRadius, gColors, nullptr, 2,
487                                                     SkShader::kClamp_TileMode));
488    }
489
490    void onDraw(SkCanvas* canvas) override {
491        SkPaint paint;
492        paint.setShader(fShader);
493        paint.setDither(fDither);
494        canvas->drawRect(SkRect::MakeWH(500, 500), paint);
495    }
496
497private:
498    SkAutoTUnref<SkShader> fShader;
499    bool fDither;
500
501    typedef GM INHERITED;
502};
503DEF_GM( return new RadialGradient3GM(true); )
504DEF_GM( return new RadialGradient3GM(false); )
505
506class RadialGradient4GM : public GM {
507public:
508    RadialGradient4GM(bool dither) : fDither(dither) { }
509
510protected:
511    SkString onShortName() override {
512        return SkString(fDither ? "radial_gradient4" : "radial_gradient4_nodither");
513    }
514
515    SkISize onISize() override { return SkISize::Make(500, 500); }
516
517    void onOnceBeforeDraw() override {
518        const SkPoint center = { 250, 250 };
519        const SkScalar kRadius = 250;
520        const SkColor colors[] = { SK_ColorRED, SK_ColorRED, SK_ColorWHITE, SK_ColorWHITE,
521                SK_ColorRED };
522        const SkScalar pos[] = { 0, .4f, .4f, .8f, .8f, 1 };
523        fShader.reset(SkGradientShader::CreateRadial(center, kRadius, colors, pos,
524                SK_ARRAY_COUNT(gColors), SkShader::kClamp_TileMode));
525    }
526
527    void onDraw(SkCanvas* canvas) override {
528        SkPaint paint;
529        paint.setAntiAlias(true);
530        paint.setDither(fDither);
531        paint.setShader(fShader);
532        canvas->drawRect(SkRect::MakeWH(500, 500), paint);
533    }
534
535private:
536    SkAutoTUnref<SkShader> fShader;
537    bool fDither;
538
539    typedef GM INHERITED;
540};
541DEF_GM( return new RadialGradient4GM(true); )
542DEF_GM( return new RadialGradient4GM(false); )
543
544class LinearGradientGM : public GM {
545public:
546    LinearGradientGM(bool dither) : fDither(dither) { }
547
548protected:
549    SkString onShortName() override {
550        return SkString(fDither ? "linear_gradient" : "linear_gradient_nodither");
551    }
552
553    const SkScalar kWidthBump = 30.f;
554    const SkScalar kHeight = 5.f;
555    const SkScalar kMinWidth = 540.f;
556
557    SkISize onISize() override { return SkISize::Make(500, 500); }
558
559    void onOnceBeforeDraw() override {
560        SkPoint pts[2] = { {0, 0}, {0, 0} };
561        const SkColor colors[] = { SK_ColorWHITE, SK_ColorWHITE, 0xFF008200, 0xFF008200,
562                SK_ColorWHITE, SK_ColorWHITE };
563        const SkScalar unitPos[] = { 0, 50, 70, 500, 540 };
564        SkScalar pos[6];
565        pos[5] = 1;
566        for (int index = 0; index < (int) SK_ARRAY_COUNT(fShader); ++index) {
567            pts[1].fX = 500.f + index * kWidthBump;
568            for (int inner = 0; inner < (int) SK_ARRAY_COUNT(unitPos); ++inner) {
569                pos[inner] = unitPos[inner] / (kMinWidth + index * kWidthBump);
570            }
571            fShader[index].reset(SkGradientShader::CreateLinear(pts, colors, pos,
572                    SK_ARRAY_COUNT(gColors), SkShader::kClamp_TileMode));
573        }
574    }
575
576    void onDraw(SkCanvas* canvas) override {
577        SkPaint paint;
578        paint.setAntiAlias(true);
579        paint.setDither(fDither);
580        for (int index = 0; index < (int) SK_ARRAY_COUNT(fShader); ++index) {
581            paint.setShader(fShader[index]);
582            canvas->drawRect(SkRect::MakeLTRB(0, index * kHeight, kMinWidth + index * kWidthBump,
583                    (index + 1) * kHeight), paint);
584        }
585    }
586
587private:
588    SkAutoTUnref<SkShader> fShader[100];
589    bool fDither;
590
591    typedef GM INHERITED;
592};
593DEF_GM( return new LinearGradientGM(true); )
594DEF_GM( return new LinearGradientGM(false); )
595
596class LinearGradientTinyGM : public GM {
597public:
598    LinearGradientTinyGM(uint32_t flags, const char* suffix = nullptr)
599    : fName("linear_gradient_tiny")
600    , fFlags(flags) {
601        fName.append(suffix);
602    }
603
604protected:
605    SkString onShortName() override {
606        return fName;
607    }
608
609    SkISize onISize() override {
610        return SkISize::Make(600, 500);
611    }
612
613    void onDraw(SkCanvas* canvas) override {
614        const SkScalar kRectSize = 100;
615        const unsigned kStopCount = 3;
616        const SkColor colors[kStopCount] = { SK_ColorGREEN, SK_ColorRED, SK_ColorGREEN };
617        const struct {
618            SkPoint pts[2];
619            SkScalar pos[kStopCount];
620        } configs[] = {
621            { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.999999f,    1 }},
622            { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.000001f,    1 }},
623            { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.999999999f, 1 }},
624            { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.000000001f, 1 }},
625
626            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.999999f,    1 }},
627            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.000001f,    1 }},
628            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.999999999f, 1 }},
629            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.000000001f, 1 }},
630
631            { { SkPoint::Make(0, 0),        SkPoint::Make(0.00001f, 0) }, { 0, 0.5f, 1 }},
632            { { SkPoint::Make(9.99999f, 0), SkPoint::Make(10, 0) },       { 0, 0.5f, 1 }},
633            { { SkPoint::Make(0, 0),        SkPoint::Make(0, 0.00001f) }, { 0, 0.5f, 1 }},
634            { { SkPoint::Make(0, 9.99999f), SkPoint::Make(0, 10) },       { 0, 0.5f, 1 }},
635        };
636
637        SkPaint paint;
638        for (unsigned i = 0; i < SK_ARRAY_COUNT(configs); ++i) {
639            SkAutoCanvasRestore acr(canvas, true);
640            SkAutoTUnref<SkShader> gradient(
641                SkGradientShader::CreateLinear(configs[i].pts, colors, configs[i].pos, kStopCount,
642                                               SkShader::kClamp_TileMode, fFlags, nullptr));
643            canvas->translate(kRectSize * ((i % 4) * 1.5f + 0.25f),
644                              kRectSize * ((i / 4) * 1.5f + 0.25f));
645
646            paint.setShader(gradient);
647            canvas->drawRect(SkRect::MakeWH(kRectSize, kRectSize), paint);
648        }
649    }
650
651private:
652    typedef GM INHERITED;
653
654    SkString fName;
655    uint32_t fFlags;
656};
657DEF_GM( return new LinearGradientTinyGM(0); )
658DEF_GM( return new LinearGradientTinyGM(SkLinearGradient::kForce4fContext_PrivateFlag, "_4f"); )
659}
660
661///////////////////////////////////////////////////////////////////////////////////////////////////
662
663struct GradRun {
664    SkColor  fColors[4];
665    SkScalar fPos[4];
666    int      fCount;
667};
668
669#define SIZE 121
670
671static SkShader* make_linear(const GradRun& run, SkShader::TileMode mode) {
672    const SkPoint pts[] { { 30, 30 }, { SIZE - 30, SIZE - 30 } };
673    return SkGradientShader::CreateLinear(pts, run.fColors, run.fPos, run.fCount, mode);
674}
675
676static SkShader* make_radial(const GradRun& run, SkShader::TileMode mode) {
677    const SkScalar half = SIZE * 0.5f;
678    return SkGradientShader::CreateRadial({half,half}, half - 10,
679                                          run.fColors, run.fPos, run.fCount, mode);
680}
681
682static SkShader* make_conical(const GradRun& run, SkShader::TileMode mode) {
683    const SkScalar half = SIZE * 0.5f;
684    const SkPoint center { half, half };
685    return SkGradientShader::CreateTwoPointConical(center, 20, center, half - 10,
686                                          run.fColors, run.fPos, run.fCount, mode);
687}
688
689static SkShader* make_sweep(const GradRun& run, SkShader::TileMode) {
690    const SkScalar half = SIZE * 0.5f;
691    return SkGradientShader::CreateSweep(half, half, run.fColors, run.fPos, run.fCount);
692}
693
694/*
695 *  Exercise duplicate color-stops, at the ends, and in the middle
696 *
697 *  At the time of this writing, only Linear correctly deals with duplicates at the ends,
698 *  and then only correctly on CPU backend.
699 */
700DEF_SIMPLE_GM(gradients_dup_color_stops, canvas, 704, 564) {
701    const SkColor preColor  = 0xFFFF0000;   // clamp color before start
702    const SkColor postColor = 0xFF0000FF;   // clamp color after end
703    const SkColor color0    = 0xFF000000;
704    const SkColor color1    = 0xFF00FF00;
705    const SkColor badColor  = 0xFF3388BB;   // should never be seen, fills out fixed-size array
706
707    const GradRun runs[] = {
708        {   { color0, color1, badColor, badColor },
709            { 0, 1, -1, -1 },
710            2,
711        },
712        {   { preColor, color0, color1, badColor },
713            { 0, 0, 1, -1 },
714            3,
715        },
716        {   { color0, color1, postColor, badColor },
717            { 0, 1, 1, -1 },
718            3,
719        },
720        {   { preColor, color0, color1, postColor },
721            { 0, 0, 1, 1 },
722            4,
723        },
724        {   { color0, color0, color1, color1 },
725            { 0, 0.5f, 0.5f, 1 },
726            4,
727        },
728    };
729    SkShader* (*factories[])(const GradRun&, SkShader::TileMode) {
730        make_linear, make_radial, make_conical, make_sweep
731    };
732
733    const SkRect rect = SkRect::MakeWH(SIZE, SIZE);
734    const SkScalar dx = SIZE + 20;
735    const SkScalar dy = SIZE + 20;
736    const SkShader::TileMode mode = SkShader::kClamp_TileMode;
737
738    SkPaint paint;
739    canvas->translate(10, 10 - dy);
740    for (auto factory : factories) {
741        canvas->translate(0, dy);
742        SkAutoCanvasRestore acr(canvas, true);
743        for (const auto& run : runs) {
744            paint.setShader(factory(run, mode))->unref();
745            canvas->drawRect(rect, paint);
746            canvas->translate(dx, 0);
747        }
748    }
749}
750
751static void draw_many_stops(SkCanvas* canvas, uint32_t flags) {
752    const unsigned kStopCount = 200;
753    const SkPoint pts[] = { {50, 50}, {450, 465}};
754
755    SkColor colors[kStopCount];
756    for (unsigned i = 0; i < kStopCount; i++) {
757        switch (i % 5) {
758        case 0: colors[i] = SK_ColorRED; break;
759        case 1: colors[i] = SK_ColorGREEN; break;
760        case 2: colors[i] = SK_ColorGREEN; break;
761        case 3: colors[i] = SK_ColorBLUE; break;
762        case 4: colors[i] = SK_ColorRED; break;
763        }
764    }
765
766    SkAutoTUnref<SkShader> shader(SkGradientShader::CreateLinear(
767        pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode, flags, nullptr));
768
769    SkPaint p;
770    p.setShader(shader);
771
772    canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
773}
774
775DEF_SIMPLE_GM(gradient_many_stops, canvas, 500, 500) {
776    draw_many_stops(canvas, 0);
777}
778
779DEF_SIMPLE_GM(gradient_many_stops_4f, canvas, 500, 500) {
780    draw_many_stops(canvas, SkLinearGradient::kForce4fContext_PrivateFlag);
781}
782