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