xref: /aosp_15_r20/external/skia/gm/gradients.cpp (revision c8dee2aa9b3f27cf6c858bd81872bdeb2c07ed17)
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/SkFont.h"
14 #include "include/core/SkMatrix.h"
15 #include "include/core/SkPaint.h"
16 #include "include/core/SkPicture.h"
17 #include "include/core/SkPictureRecorder.h"
18 #include "include/core/SkPoint.h"
19 #include "include/core/SkRect.h"
20 #include "include/core/SkRefCnt.h"
21 #include "include/core/SkScalar.h"
22 #include "include/core/SkShader.h"
23 #include "include/core/SkSize.h"
24 #include "include/core/SkString.h"
25 #include "include/core/SkTileMode.h"
26 #include "include/core/SkTypes.h"
27 #include "include/effects/SkGradientShader.h"
28 #include "include/private/base/SkAssert.h"
29 #include "tools/ToolUtils.h"
30 #include "tools/fonts/FontToolUtils.h"
31 
32 #include <initializer_list>
33 #include <math.h>
34 
35 namespace {
36 
37 struct GradData {
38     int              fCount;
39     const SkColor*   fColors;
40     const SkColor4f* fColors4f;
41     const SkScalar*  fPos;
42 };
43 
44 constexpr SkColor gColors[] = {
45     SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE, SK_ColorBLACK
46 };
47 constexpr SkColor4f gColors4f[] ={
48     { 1.0f, 0.0f, 0.0f, 1.0f }, // Red
49     { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
50     { 0.0f, 0.0f, 1.0f, 1.0f }, // Blue
51     { 1.0f, 1.0f, 1.0f, 1.0f }, // White
52     { 0.0f, 0.0f, 0.0f, 1.0f }  // Black
53 };
54 constexpr SkScalar gPos0[] = { 0, SK_Scalar1 };
55 constexpr SkScalar gPos1[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
56 constexpr SkScalar gPos2[] = {
57     0, SK_Scalar1/8, SK_Scalar1/2, SK_Scalar1*7/8, SK_Scalar1
58 };
59 
60 constexpr SkScalar gPosClamp[]   = {0.0f, 0.0f, 1.0f, 1.0f};
61 constexpr SkColor  gColorClamp[] = {
62     SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorBLUE
63 };
64 constexpr SkColor4f gColor4fClamp[] ={
65     { 1.0f, 0.0f, 0.0f, 1.0f }, // Red
66     { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
67     { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
68     { 0.0f, 0.0f, 1.0f, 1.0f }  // Blue
69 };
70 constexpr GradData gGradData[] = {
71     { 2, gColors, gColors4f, nullptr },
72     { 2, gColors, gColors4f, gPos0 },
73     { 2, gColors, gColors4f, gPos1 },
74     { 5, gColors, gColors4f, nullptr },
75     { 5, gColors, gColors4f, gPos2 },
76     { 4, gColorClamp, gColor4fClamp, gPosClamp }
77 };
78 
MakeLinear(const SkPoint pts[2],const GradData & data,SkTileMode tm,const SkMatrix & localMatrix)79 static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data,
80                                   SkTileMode tm, const SkMatrix& localMatrix) {
81     return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm, 0,
82                                         &localMatrix);
83 }
84 
MakeLinear4f(const SkPoint pts[2],const GradData & data,SkTileMode tm,const SkMatrix & localMatrix)85 static sk_sp<SkShader> MakeLinear4f(const SkPoint pts[2], const GradData& data,
86                                     SkTileMode tm, const SkMatrix& localMatrix) {
87     auto srgb = SkColorSpace::MakeSRGB();
88     return SkGradientShader::MakeLinear(pts, data.fColors4f, srgb, data.fPos, data.fCount, tm, 0,
89                                         &localMatrix);
90 }
91 
MakeRadial(const SkPoint pts[2],const GradData & data,SkTileMode tm,const SkMatrix & localMatrix)92 static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data,
93                                   SkTileMode tm, const SkMatrix& localMatrix) {
94     SkPoint center;
95     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
96                SkScalarAve(pts[0].fY, pts[1].fY));
97     return SkGradientShader::MakeRadial(center, center.fX, data.fColors, data.fPos, data.fCount,
98                                         tm, 0, &localMatrix);
99 }
100 
MakeRadial4f(const SkPoint pts[2],const GradData & data,SkTileMode tm,const SkMatrix & localMatrix)101 static sk_sp<SkShader> MakeRadial4f(const SkPoint pts[2], const GradData& data,
102                                     SkTileMode tm, const SkMatrix& localMatrix) {
103     SkPoint center;
104     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
105                SkScalarAve(pts[0].fY, pts[1].fY));
106     auto srgb = SkColorSpace::MakeSRGB();
107     return SkGradientShader::MakeRadial(center, center.fX, data.fColors4f, srgb, data.fPos,
108                                         data.fCount, tm, 0, &localMatrix);
109 }
110 
MakeSweep(const SkPoint pts[2],const GradData & data,SkTileMode,const SkMatrix & localMatrix)111 static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data,
112                                  SkTileMode, const SkMatrix& localMatrix) {
113     SkPoint center;
114     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
115                SkScalarAve(pts[0].fY, pts[1].fY));
116     return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors, data.fPos, data.fCount,
117                                        0, &localMatrix);
118 }
119 
MakeSweep4f(const SkPoint pts[2],const GradData & data,SkTileMode,const SkMatrix & localMatrix)120 static sk_sp<SkShader> MakeSweep4f(const SkPoint pts[2], const GradData& data,
121                                    SkTileMode, const SkMatrix& localMatrix) {
122     SkPoint center;
123     center.set(SkScalarAve(pts[0].fX, pts[1].fX),
124                SkScalarAve(pts[0].fY, pts[1].fY));
125     auto srgb = SkColorSpace::MakeSRGB();
126     return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors4f, srgb, data.fPos,
127                                        data.fCount, 0, &localMatrix);
128 }
129 
Make2Radial(const SkPoint pts[2],const GradData & data,SkTileMode tm,const SkMatrix & localMatrix)130 static sk_sp<SkShader> Make2Radial(const SkPoint pts[2], const GradData& data,
131                                    SkTileMode tm, const SkMatrix& localMatrix) {
132     SkPoint center0, center1;
133     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
134                 SkScalarAve(pts[0].fY, pts[1].fY));
135     center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
136                 SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
137     return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
138                                                  center0, (pts[1].fX - pts[0].fX) / 2,
139                                                  data.fColors, data.fPos, data.fCount, tm,
140                                                  0, &localMatrix);
141 }
142 
Make2Radial4f(const SkPoint pts[2],const GradData & data,SkTileMode tm,const SkMatrix & localMatrix)143 static sk_sp<SkShader> Make2Radial4f(const SkPoint pts[2], const GradData& data,
144                                      SkTileMode tm, const SkMatrix& localMatrix) {
145     SkPoint center0, center1;
146     center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
147                 SkScalarAve(pts[0].fY, pts[1].fY));
148     center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3) / 5),
149                 SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1) / 4));
150     auto srgb = SkColorSpace::MakeSRGB();
151     return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
152                                                  center0, (pts[1].fX - pts[0].fX) / 2,
153                                                  data.fColors4f, srgb, data.fPos, data.fCount, tm,
154                                                  0, &localMatrix);
155 }
156 
Make2Conical(const SkPoint pts[2],const GradData & data,SkTileMode tm,const SkMatrix & localMatrix)157 static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data,
158                                     SkTileMode tm, const SkMatrix& localMatrix) {
159     SkPoint center0, center1;
160     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
161     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
162     center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
163     center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
164     return SkGradientShader::MakeTwoPointConical(center1, radius1, center0, radius0,
165                                                  data.fColors, data.fPos,
166                                                  data.fCount, tm, 0, &localMatrix);
167 }
168 
Make2Conical4f(const SkPoint pts[2],const GradData & data,SkTileMode tm,const SkMatrix & localMatrix)169 static sk_sp<SkShader> Make2Conical4f(const SkPoint pts[2], const GradData& data,
170                                       SkTileMode tm, const SkMatrix& localMatrix) {
171     SkPoint center0, center1;
172     SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
173     SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
174     center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
175     center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
176     auto srgb = SkColorSpace::MakeSRGB();
177     return SkGradientShader::MakeTwoPointConical(center1, radius1, center0, radius0,
178                                                  data.fColors4f, srgb, data.fPos,
179                                                  data.fCount, tm, 0, &localMatrix);
180 }
181 
182 typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data,
183                                      SkTileMode tm, const SkMatrix& localMatrix);
184 constexpr GradMaker gGradMakers[] = {
185     MakeLinear, MakeRadial, MakeSweep, Make2Radial, Make2Conical
186 };
187 constexpr GradMaker gGradMakers4f[] ={
188     MakeLinear4f, MakeRadial4f, MakeSweep4f, Make2Radial4f, Make2Conical4f
189 };
190 
191 ///////////////////////////////////////////////////////////////////////////////
192 
193 class GradientsGM : public skiagm::GM {
194 public:
GradientsGM(bool dither)195     GradientsGM(bool dither) : fDither(dither) {}
196 
197 protected:
198     const bool fDither;
199 
onDraw(SkCanvas * canvas)200     void onDraw(SkCanvas* canvas) override {
201         SkPoint pts[2] = {
202             { 0, 0 },
203             { SkIntToScalar(100), SkIntToScalar(100) }
204         };
205         SkTileMode tm = SkTileMode::kClamp;
206         SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
207         SkPaint paint;
208         paint.setAntiAlias(true);
209         paint.setDither(fDither);
210 
211         canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
212         for (size_t i = 0; i < std::size(gGradData); i++) {
213             canvas->save();
214             for (size_t j = 0; j < std::size(gGradMakers); j++) {
215                 SkMatrix scale = SkMatrix::I();
216 
217                 if (i == 5) { // if the clamp case
218                     scale.setScale(0.5f, 0.5f);
219                     scale.postTranslate(25.f, 25.f);
220                 }
221 
222                 paint.setShader(gGradMakers[j](pts, gGradData[i], tm, scale));
223                 canvas->drawRect(r, paint);
224                 canvas->translate(0, SkIntToScalar(120));
225             }
226             canvas->restore();
227             canvas->translate(SkIntToScalar(120), 0);
228         }
229     }
230 
231 private:
onOnceBeforeDraw()232     void onOnceBeforeDraw() override { this->setBGColor(0xFFDDDDDD); }
233 
getName() const234     SkString getName() const override {
235         return SkString(fDither ? "gradients" : "gradients_nodither");
236     }
237 
getISize()238     SkISize getISize() override { return {840, 815}; }
239 };
240 DEF_GM( return new GradientsGM(true); )
241 DEF_GM( return new GradientsGM(false); )
242 
243 // Like the original gradients GM, but using the SkColor4f shader factories. Should be identical.
244 class Gradients4fGM : public skiagm::GM {
245 public:
Gradients4fGM(bool dither)246     Gradients4fGM(bool dither) : fDither(dither) {}
247 
248 private:
onOnceBeforeDraw()249     void onOnceBeforeDraw() override { this->setBGColor(0xFFDDDDDD); }
250 
getName() const251     SkString getName() const override {
252         return SkString(fDither ? "gradients4f" : "gradients4f_nodither");
253     }
254 
getISize()255     SkISize getISize() override { return {840, 815}; }
256 
onDraw(SkCanvas * canvas)257     void onDraw(SkCanvas* canvas) override {
258         SkPoint pts[2] ={
259             { 0, 0 },
260             { SkIntToScalar(100), SkIntToScalar(100) }
261         };
262         SkTileMode tm = SkTileMode::kClamp;
263         SkRect r ={ 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
264         SkPaint paint;
265         paint.setAntiAlias(true);
266         paint.setDither(fDither);
267 
268         canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
269         for (size_t i = 0; i < std::size(gGradData); i++) {
270             canvas->save();
271             for (size_t j = 0; j < std::size(gGradMakers4f); j++) {
272                 SkMatrix scale = SkMatrix::I();
273 
274                 if (i == 5) { // if the clamp case
275                     scale.setScale(0.5f, 0.5f);
276                     scale.postTranslate(25.f, 25.f);
277                 }
278 
279                 paint.setShader(gGradMakers4f[j](pts, gGradData[i], tm, scale));
280                 canvas->drawRect(r, paint);
281                 canvas->translate(0, SkIntToScalar(120));
282             }
283             canvas->restore();
284             canvas->translate(SkIntToScalar(120), 0);
285         }
286     }
287 
288     bool fDither;
289 };
290 DEF_GM(return new Gradients4fGM(true); )
291 DEF_GM(return new Gradients4fGM(false); )
292 
293 // Based on the original gradient slide, but with perspective applied to the
294 // gradient shaders' local matrices
295 class GradientsLocalPerspectiveGM : public skiagm::GM {
296 public:
GradientsLocalPerspectiveGM(bool dither)297     GradientsLocalPerspectiveGM(bool dither) : fDither(dither) {
298         this->setBGColor(0xFFDDDDDD);
299     }
300 
301 private:
getName() const302     SkString getName() const override {
303         return SkString(fDither ? "gradients_local_perspective" :
304                                   "gradients_local_perspective_nodither");
305     }
306 
getISize()307     SkISize getISize() override { return {840, 815}; }
308 
onDraw(SkCanvas * canvas)309     void onDraw(SkCanvas* canvas) override {
310         SkPoint pts[2] = {
311             { 0, 0 },
312             { SkIntToScalar(100), SkIntToScalar(100) }
313         };
314         SkTileMode tm = SkTileMode::kClamp;
315         SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
316         SkPaint paint;
317         paint.setAntiAlias(true);
318         paint.setDither(fDither);
319 
320         canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
321         for (size_t i = 0; i < std::size(gGradData); i++) {
322             canvas->save();
323             for (size_t j = 0; j < std::size(gGradMakers); j++) {
324                 // apply an increasing y perspective as we move to the right
325                 SkMatrix perspective;
326                 perspective.setIdentity();
327                 perspective.setPerspY(SkIntToScalar(i+1) / 500);
328                 perspective.setSkewX(SkIntToScalar(i+1) / 10);
329 
330                 paint.setShader(gGradMakers[j](pts, gGradData[i], tm, perspective));
331                 canvas->drawRect(r, paint);
332                 canvas->translate(0, SkIntToScalar(120));
333             }
334             canvas->restore();
335             canvas->translate(SkIntToScalar(120), 0);
336         }
337     }
338 
339     bool fDither;
340 };
341 DEF_GM( return new GradientsLocalPerspectiveGM(true); )
342 DEF_GM( return new GradientsLocalPerspectiveGM(false); )
343 
344 // Based on the original gradient slide, but with perspective applied to
345 // the view matrix
346 class GradientsViewPerspectiveGM : public GradientsGM {
347 public:
GradientsViewPerspectiveGM(bool dither)348     GradientsViewPerspectiveGM(bool dither) : INHERITED(dither) { }
349 
350 private:
getName() const351     SkString getName() const override {
352         return SkString(fDither ? "gradients_view_perspective" :
353                                   "gradients_view_perspective_nodither");
354     }
355 
getISize()356     SkISize getISize() override { return {840, 500}; }
357 
onDraw(SkCanvas * canvas)358     void onDraw(SkCanvas* canvas) override {
359         SkMatrix perspective;
360         perspective.setIdentity();
361         perspective.setPerspY(0.001f);
362         perspective.setSkewX(SkIntToScalar(8) / 25);
363         canvas->concat(perspective);
364         this->INHERITED::onDraw(canvas);
365     }
366 
367 private:
368     using INHERITED = GradientsGM;
369 };
370 DEF_GM( return new GradientsViewPerspectiveGM(true); )
371 DEF_GM( return new GradientsViewPerspectiveGM(false); )
372 
373 /*
374  Inspired by this <canvas> javascript, where we need to detect that we are not
375  solving a quadratic equation, but must instead solve a linear (since our X^2
376  coefficient is 0)
377 
378  ctx.fillStyle = '#f00';
379  ctx.fillRect(0, 0, 100, 50);
380 
381  var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
382  g.addColorStop(0, '#f00');
383  g.addColorStop(0.01, '#0f0');
384  g.addColorStop(0.99, '#0f0');
385  g.addColorStop(1, '#f00');
386  ctx.fillStyle = g;
387  ctx.fillRect(0, 0, 100, 50);
388  */
389 class GradientsDegenrate2PointGM : public skiagm::GM {
390 public:
GradientsDegenrate2PointGM(bool dither)391     GradientsDegenrate2PointGM(bool dither) : fDither(dither) {}
392 
393 private:
getName() const394     SkString getName() const override {
395         return SkString(fDither ? "gradients_degenerate_2pt" : "gradients_degenerate_2pt_nodither");
396     }
397 
getISize()398     SkISize getISize() override { return {320, 320}; }
399 
onDraw(SkCanvas * canvas)400     void onDraw(SkCanvas* canvas) override {
401         canvas->drawColor(SK_ColorBLUE);
402 
403         SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorRED };
404         SkScalar pos[] = { 0, 0.01f, 0.99f, SK_Scalar1 };
405         SkPoint c0;
406         c0.iset(-80, 25);
407         SkScalar r0 = SkIntToScalar(70);
408         SkPoint c1;
409         c1.iset(0, 25);
410         SkScalar r1 = SkIntToScalar(150);
411         SkPaint paint;
412         paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors,
413                                                               pos, std::size(pos),
414                                                               SkTileMode::kClamp));
415         paint.setDither(fDither);
416         canvas->drawPaint(paint);
417     }
418 
419     bool fDither;
420 };
421 DEF_GM( return new GradientsDegenrate2PointGM(true); )
DEF_GM(return new GradientsDegenrate2PointGM (false);)422 DEF_GM( return new GradientsDegenrate2PointGM(false); )
423 
424 /* bug.skia.org/517
425 <canvas id="canvas"></canvas>
426 <script>
427 var c = document.getElementById("canvas");
428 var ctx = c.getContext("2d");
429 ctx.fillStyle = '#ff0';
430 ctx.fillRect(0, 0, 100, 50);
431 
432 var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
433 g.addColorStop(0, '#0f0');
434 g.addColorStop(0.003, '#f00');  // 0.004 makes this work
435 g.addColorStop(1, '#ff0');
436 ctx.fillStyle = g;
437 ctx.fillRect(0, 0, 100, 50);
438 </script>
439 */
440 
441 // should draw only green
442 DEF_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                                                           std::size(pos),
455                                                           SkTileMode::kClamp));
456     canvas->drawRect(SkRect::MakeWH(100, 150), paint);
457 }
458 
459 
460 /// Tests correctness of *optimized* codepaths in gradients.
461 
462 class ClampedGradientsGM : public skiagm::GM {
463 public:
ClampedGradientsGM(bool dither)464     ClampedGradientsGM(bool dither) : fDither(dither) {}
465 
466 private:
getName() const467     SkString getName() const override {
468         return SkString(fDither ? "clamped_gradients" : "clamped_gradients_nodither");
469     }
470 
getISize()471     SkISize getISize() override { return {640, 510}; }
472 
onDraw(SkCanvas * canvas)473     void onDraw(SkCanvas* canvas) override {
474         canvas->drawColor(0xFFDDDDDD);
475 
476         SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(300) };
477         SkPaint paint;
478         paint.setDither(fDither);
479         paint.setAntiAlias(true);
480 
481         SkPoint center;
482         center.iset(0, 300);
483         canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
484         paint.setShader(SkGradientShader::MakeRadial(
485             SkPoint(center),
486             SkIntToScalar(200), gColors, nullptr, 5,
487             SkTileMode::kClamp));
488         canvas->drawRect(r, paint);
489     }
490 
491     bool fDither;
492 };
493 DEF_GM( return new ClampedGradientsGM(true); )
494 DEF_GM( return new ClampedGradientsGM(false); )
495 
496 /// Checks quality of large radial gradients, which may display
497 /// some banding.
498 
499 class RadialGradientGM : public skiagm::GM {
getName() const500     SkString getName() const override { return SkString("radial_gradient"); }
501 
getISize()502     SkISize getISize() override { return {1280, 1280}; }
503 
onDraw(SkCanvas * canvas)504     void onDraw(SkCanvas* canvas) override {
505         const SkISize dim = this->getISize();
506 
507         canvas->drawColor(0xFF000000);
508 
509         SkPaint paint;
510         paint.setDither(true);
511         SkPoint center;
512         center.set(SkIntToScalar(dim.width())/2, SkIntToScalar(dim.height())/2);
513         SkScalar radius = SkIntToScalar(dim.width())/2;
514         const SkColor colors[] = { 0x7f7f7f7f, 0x7f7f7f7f, 0xb2000000 };
515         const SkScalar pos[] = { 0.0f,
516                              0.35f,
517                              1.0f };
518         paint.setShader(SkGradientShader::MakeRadial(center, radius, colors, pos,
519                                                      std::size(pos),
520                                                      SkTileMode::kClamp));
521         SkRect r = {
522             0, 0, SkIntToScalar(dim.width()), SkIntToScalar(dim.height())
523         };
524         canvas->drawRect(r, paint);
525     }
526 };
527 DEF_GM( return new RadialGradientGM; )
528 
529 class RadialGradient2GM : public skiagm::GM {
530 public:
RadialGradient2GM(bool dither)531     RadialGradient2GM(bool dither) : fDither(dither) {}
532 
533 private:
getName() const534     SkString getName() const override {
535         return SkString(fDither ? "radial_gradient2" : "radial_gradient2_nodither");
536     }
537 
getISize()538     SkISize getISize() override { return {800, 400}; }
539 
540     // Reproduces the example given in bug 7671058.
onDraw(SkCanvas * canvas)541     void onDraw(SkCanvas* canvas) override {
542         SkPaint paint1, paint2, paint3;
543         paint1.setStyle(SkPaint::kFill_Style);
544         paint2.setStyle(SkPaint::kFill_Style);
545         paint3.setStyle(SkPaint::kFill_Style);
546 
547         const SkColor sweep_colors[] =
548             { 0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000 };
549         const SkColor colors1[] = { 0xFFFFFFFF, 0x00000000 };
550         const SkColor colors2[] = { 0xFF000000, 0x00000000 };
551 
552         const SkScalar cx = 200, cy = 200, radius = 150;
553         SkPoint center;
554         center.set(cx, cy);
555 
556         // We can either interpolate endpoints and premultiply each point (default, more precision),
557         // or premultiply the endpoints first, avoiding the need to premultiply each point (cheap).
558         const uint32_t flags[] = { 0, SkGradientShader::kInterpolateColorsInPremul_Flag };
559 
560         for (size_t i = 0; i < std::size(flags); i++) {
561             paint1.setShader(SkGradientShader::MakeSweep(cx, cy, sweep_colors,
562                                                          nullptr, std::size(sweep_colors),
563                                                          flags[i], nullptr));
564             paint2.setShader(SkGradientShader::MakeRadial(center, radius, colors1,
565                                                           nullptr, std::size(colors1),
566                                                           SkTileMode::kClamp,
567                                                           flags[i], nullptr));
568             paint3.setShader(SkGradientShader::MakeRadial(center, radius, colors2,
569                                                           nullptr, std::size(colors2),
570                                                           SkTileMode::kClamp,
571                                                           flags[i], nullptr));
572             paint1.setDither(fDither);
573             paint2.setDither(fDither);
574             paint3.setDither(fDither);
575 
576             canvas->drawCircle(cx, cy, radius, paint1);
577             canvas->drawCircle(cx, cy, radius, paint3);
578             canvas->drawCircle(cx, cy, radius, paint2);
579 
580             canvas->translate(400, 0);
581         }
582     }
583 
584 private:
585     bool fDither;
586 
587     using INHERITED = GM;
588 };
589 DEF_GM( return new RadialGradient2GM(true); )
590 DEF_GM( return new RadialGradient2GM(false); )
591 
592 // Shallow radial (shows banding on raster)
593 class RadialGradient3GM : public skiagm::GM {
594 public:
RadialGradient3GM(bool dither)595     RadialGradient3GM(bool dither) : fDither(dither) { }
596 
597 private:
getName() const598     SkString getName() const override {
599         return SkString(fDither ? "radial_gradient3" : "radial_gradient3_nodither");
600     }
601 
getISize()602     SkISize getISize() override { return {500, 500}; }
603 
runAsBench() const604     bool runAsBench() const override { return true; }
605 
onOnceBeforeDraw()606     void onOnceBeforeDraw() override {
607         const SkPoint center = { 0, 0 };
608         const SkScalar kRadius = 3000;
609         const SkColor kColors[] = { 0xFFFFFFFF, 0xFF000000 };
610         fShader = SkGradientShader::MakeRadial(center, kRadius, kColors, nullptr, 2,
611                                                SkTileMode::kClamp);
612     }
613 
onDraw(SkCanvas * canvas)614     void onDraw(SkCanvas* canvas) override {
615         SkPaint paint;
616         paint.setShader(fShader);
617         paint.setDither(fDither);
618         canvas->drawRect(SkRect::MakeWH(500, 500), paint);
619     }
620 
621 private:
622     sk_sp<SkShader> fShader;
623     bool fDither;
624 
625     using INHERITED = GM;
626 };
627 DEF_GM( return new RadialGradient3GM(true); )
628 DEF_GM( return new RadialGradient3GM(false); )
629 
630 class RadialGradient4GM : public skiagm::GM {
631 public:
RadialGradient4GM(bool dither)632     RadialGradient4GM(bool dither) : fDither(dither) { }
633 
634 private:
getName() const635     SkString getName() const override {
636         return SkString(fDither ? "radial_gradient4" : "radial_gradient4_nodither");
637     }
638 
getISize()639     SkISize getISize() override { return {500, 500}; }
640 
onOnceBeforeDraw()641     void onOnceBeforeDraw() override {
642         const SkPoint center = { 250, 250 };
643         const SkScalar kRadius = 250;
644         const SkColor colors[] = { SK_ColorRED, SK_ColorRED, SK_ColorWHITE, SK_ColorWHITE,
645                 SK_ColorRED };
646         const SkScalar pos[] = { 0, .4f, .4f, .8f, .8f, 1 };
647         fShader = SkGradientShader::MakeRadial(center, kRadius, colors, pos,
648                                                std::size(gColors), SkTileMode::kClamp);
649     }
650 
onDraw(SkCanvas * canvas)651     void onDraw(SkCanvas* canvas) override {
652         SkPaint paint;
653         paint.setAntiAlias(true);
654         paint.setDither(fDither);
655         paint.setShader(fShader);
656         canvas->drawRect(SkRect::MakeWH(500, 500), paint);
657     }
658 
659 private:
660     sk_sp<SkShader> fShader;
661     bool fDither;
662 
663     using INHERITED = GM;
664 };
665 DEF_GM( return new RadialGradient4GM(true); )
666 DEF_GM( return new RadialGradient4GM(false); )
667 
668 class LinearGradientGM : public skiagm::GM {
669 public:
LinearGradientGM(bool dither)670     LinearGradientGM(bool dither) : fDither(dither) { }
671 
672 private:
getName() const673     SkString getName() const override {
674         return SkString(fDither ? "linear_gradient" : "linear_gradient_nodither");
675     }
676 
677     const SkScalar kWidthBump = 30.f;
678     const SkScalar kHeight = 5.f;
679     const SkScalar kMinWidth = 540.f;
680 
getISize()681     SkISize getISize() override { return {500, 500}; }
682 
onOnceBeforeDraw()683     void onOnceBeforeDraw() override {
684         SkPoint pts[2] = { {0, 0}, {0, 0} };
685         const SkColor colors[] = { SK_ColorWHITE, SK_ColorWHITE, 0xFF008200, 0xFF008200,
686                 SK_ColorWHITE, SK_ColorWHITE };
687         const SkScalar unitPos[] = { 0, 50, 70, 500, 540 };
688         SkScalar pos[6];
689         pos[5] = 1;
690         for (int index = 0; index < (int) std::size(fShader); ++index) {
691             pts[1].fX = 500.f + index * kWidthBump;
692             for (int inner = 0; inner < (int) std::size(unitPos); ++inner) {
693                 pos[inner] = unitPos[inner] / (kMinWidth + index * kWidthBump);
694             }
695             fShader[index] = SkGradientShader::MakeLinear(pts, colors, pos,
696                     std::size(gColors), SkTileMode::kClamp);
697         }
698     }
699 
onDraw(SkCanvas * canvas)700     void onDraw(SkCanvas* canvas) override {
701         SkPaint paint;
702         paint.setAntiAlias(true);
703         paint.setDither(fDither);
704         for (int index = 0; index < (int) std::size(fShader); ++index) {
705             paint.setShader(fShader[index]);
706             canvas->drawRect(SkRect::MakeLTRB(0, index * kHeight, kMinWidth + index * kWidthBump,
707                     (index + 1) * kHeight), paint);
708         }
709     }
710 
711 private:
712     sk_sp<SkShader> fShader[100];
713     bool fDither;
714 
715     using INHERITED = GM;
716 };
717 DEF_GM( return new LinearGradientGM(true); )
718 DEF_GM( return new LinearGradientGM(false); )
719 
720 class LinearGradientTinyGM : public skiagm::GM {
721     inline static constexpr uint32_t kFlags = 0;
722 
getName() const723     SkString getName() const override { return SkString("linear_gradient_tiny"); }
724 
getISize()725     SkISize getISize() override { return {600, 500}; }
726 
onDraw(SkCanvas * canvas)727     void onDraw(SkCanvas* canvas) override {
728         const SkScalar kRectSize = 100;
729         const unsigned kStopCount = 3;
730         const SkColor colors[kStopCount] = { SK_ColorGREEN, SK_ColorRED, SK_ColorGREEN };
731         const struct {
732             SkPoint pts[2];
733             SkScalar pos[kStopCount];
734         } configs[] = {
735             { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.999999f,    1 }},
736             { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.000001f,    1 }},
737             { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.999999999f, 1 }},
738             { { SkPoint::Make(0, 0),        SkPoint::Make(10, 0) },       { 0, 0.000000001f, 1 }},
739 
740             { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.999999f,    1 }},
741             { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.000001f,    1 }},
742             { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.999999999f, 1 }},
743             { { SkPoint::Make(0, 0),        SkPoint::Make(0, 10) },       { 0, 0.000000001f, 1 }},
744 
745             { { SkPoint::Make(0, 0),        SkPoint::Make(0.00001f, 0) }, { 0, 0.5f, 1 }},
746             { { SkPoint::Make(9.99999f, 0), SkPoint::Make(10, 0) },       { 0, 0.5f, 1 }},
747             { { SkPoint::Make(0, 0),        SkPoint::Make(0, 0.00001f) }, { 0, 0.5f, 1 }},
748             { { SkPoint::Make(0, 9.99999f), SkPoint::Make(0, 10) },       { 0, 0.5f, 1 }},
749         };
750 
751         SkPaint paint;
752         for (unsigned i = 0; i < std::size(configs); ++i) {
753             SkAutoCanvasRestore acr(canvas, true);
754             paint.setShader(SkGradientShader::MakeLinear(configs[i].pts, colors, configs[i].pos,
755                                                          kStopCount, SkTileMode::kClamp,
756                                                          kFlags, nullptr));
757             canvas->translate(kRectSize * ((i % 4) * 1.5f + 0.25f),
758                               kRectSize * ((i / 4) * 1.5f + 0.25f));
759 
760             canvas->drawRect(SkRect::MakeWH(kRectSize, kRectSize), paint);
761         }
762     }
763 };
764 
765 DEF_GM( return new LinearGradientTinyGM; )
766 }  // namespace
767 
768 ///////////////////////////////////////////////////////////////////////////////////////////////////
769 
770 struct GradRun {
771     SkColor  fColors[4];
772     SkScalar fPos[4];
773     int      fCount;
774 };
775 
776 #define SIZE 121
777 
make_linear(const GradRun & run,SkTileMode mode)778 static sk_sp<SkShader> make_linear(const GradRun& run, SkTileMode mode) {
779     const SkPoint pts[] { { 30, 30 }, { SIZE - 30, SIZE - 30 } };
780     return SkGradientShader::MakeLinear(pts, run.fColors, run.fPos, run.fCount, mode);
781 }
782 
make_radial(const GradRun & run,SkTileMode mode)783 static sk_sp<SkShader> make_radial(const GradRun& run, SkTileMode mode) {
784     const SkScalar half = SIZE * 0.5f;
785     return SkGradientShader::MakeRadial({half,half}, half - 10, run.fColors, run.fPos,
786                                         run.fCount, mode);
787 }
788 
make_conical(const GradRun & run,SkTileMode mode)789 static sk_sp<SkShader> make_conical(const GradRun& run, SkTileMode mode) {
790     const SkScalar half = SIZE * 0.5f;
791     const SkPoint center { half, half };
792     return SkGradientShader::MakeTwoPointConical(center, 20, center, half - 10,
793                                                  run.fColors, run.fPos, run.fCount, mode);
794 }
795 
make_sweep(const GradRun & run,SkTileMode)796 static sk_sp<SkShader> make_sweep(const GradRun& run, SkTileMode) {
797     const SkScalar half = SIZE * 0.5f;
798     return SkGradientShader::MakeSweep(half, half, run.fColors, run.fPos, run.fCount);
799 }
800 
801 /*
802  *  Exercise duplicate color-stops, at the ends, and in the middle
803  *
804  *  At the time of this writing, only Linear correctly deals with duplicates at the ends,
805  *  and then only correctly on CPU backend.
806  */
807 DEF_SIMPLE_GM(gradients_dup_color_stops, canvas, 704, 564) {
808     const SkColor preColor  = 0xFFFF0000;   // clamp color before start
809     const SkColor postColor = 0xFF0000FF;   // clamp color after end
810     const SkColor color0    = 0xFF000000;
811     const SkColor color1    = 0xFF00FF00;
812     const SkColor badColor  = 0xFF3388BB;   // should never be seen, fills out fixed-size array
813 
814     const GradRun runs[] = {
815         {   { color0, color1, badColor, badColor },
816             { 0, 1, -1, -1 },
817             2,
818         },
819         {   { preColor, color0, color1, badColor },
820             { 0, 0, 1, -1 },
821             3,
822         },
823         {   { color0, color1, postColor, badColor },
824             { 0, 1, 1, -1 },
825             3,
826         },
827         {   { preColor, color0, color1, postColor },
828             { 0, 0, 1, 1 },
829             4,
830         },
831         {   { color0, color0, color1, color1 },
832             { 0, 0.5f, 0.5f, 1 },
833             4,
834         },
835     };
836     sk_sp<SkShader> (*factories[])(const GradRun&, SkTileMode) {
837         make_linear, make_radial, make_conical, make_sweep
838     };
839 
840     const SkRect rect = SkRect::MakeWH(SIZE, SIZE);
841     const SkScalar dx = SIZE + 20;
842     const SkScalar dy = SIZE + 20;
843     const SkTileMode mode = SkTileMode::kClamp;
844 
845     SkPaint paint;
846     canvas->translate(10, 10 - dy);
847     for (auto factory : factories) {
848         canvas->translate(0, dy);
849         SkAutoCanvasRestore acr(canvas, true);
850         for (const auto& run : runs) {
851             paint.setShader(factory(run, mode));
852             canvas->drawRect(rect, paint);
853             canvas->translate(dx, 0);
854         }
855     }
856 }
857 
draw_many_stops(SkCanvas * canvas)858 static void draw_many_stops(SkCanvas* canvas) {
859     const unsigned kStopCount = 200;
860     const SkPoint pts[] = { {50, 50}, {450, 450}};
861 
862     SkColor colors[kStopCount];
863     for (unsigned i = 0; i < kStopCount; i++) {
864         switch (i % 5) {
865         case 0: colors[i] = SK_ColorRED;   break;
866         case 1: colors[i] = SK_ColorGREEN; break;
867         case 2: colors[i] = SK_ColorGREEN; break;
868         case 3: colors[i] = SK_ColorBLUE;  break;
869         case 4: colors[i] = SK_ColorRED;   break;
870         }
871     }
872 
873     SkPaint p;
874     p.setShader(SkGradientShader::MakeLinear(pts, colors, nullptr, std::size(colors),
875                                              SkTileMode::kClamp));
876 
877     canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
878 }
879 
880 DEF_SIMPLE_GM(gradient_many_stops, canvas, 500, 500) {
881     draw_many_stops(canvas);
882 }
883 
draw_many_hard_stops(SkCanvas * canvas)884 static void draw_many_hard_stops(SkCanvas* canvas) {
885     const unsigned kStopCount = 300;
886     const SkPoint pts[] = {{50, 50}, {450, 450}};
887 
888     SkColor colors[kStopCount];
889     SkScalar pos[kStopCount];
890     for (unsigned i = 0; i < kStopCount; i++) {
891         switch (i % 6) {
892             case 0: colors[i] = SK_ColorRED;   break;
893             case 1: colors[i] = SK_ColorGREEN; break;
894             case 2: colors[i] = SK_ColorGREEN; break;
895             case 3: colors[i] = SK_ColorBLUE;  break;
896             case 4: colors[i] = SK_ColorBLUE;  break;
897             case 5: colors[i] = SK_ColorRED;   break;
898         }
899         pos[i] = (2.0f * (i / 2)) / kStopCount;
900     }
901 
902     SkPaint p;
903     p.setShader(SkGradientShader::MakeLinear(pts, colors, pos, std::size(colors),
904                                              SkTileMode::kClamp));
905 
906     canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
907 }
908 
909 DEF_SIMPLE_GM(gradient_many_hard_stops, canvas, 500, 500) {
910     draw_many_hard_stops(canvas);
911 }
912 
draw_circle_shader(SkCanvas * canvas,SkScalar cx,SkScalar cy,SkScalar r,sk_sp<SkShader> (* shaderFunc)())913 static void draw_circle_shader(SkCanvas* canvas, SkScalar cx, SkScalar cy, SkScalar r,
914                                sk_sp<SkShader> (*shaderFunc)()) {
915     SkPaint p;
916     p.setAntiAlias(true);
917     p.setShader(shaderFunc());
918     canvas->drawCircle(cx, cy, r, p);
919 
920     p.setShader(nullptr);
921     p.setColor(SK_ColorGRAY);
922     p.setStyle(SkPaint::kStroke_Style);
923     p.setStrokeWidth(2);
924     canvas->drawCircle(cx, cy, r, p);
925 }
926 
927 DEF_SIMPLE_GM(fancy_gradients, canvas, 800, 300) {
__anon463408530302() 928     draw_circle_shader(canvas, 150, 150, 100, []() -> sk_sp<SkShader> {
929         // Checkerboard using two linear gradients + picture shader.
930         SkScalar kTileSize = 80 / sqrtf(2);
931         SkColor colors1[] = { 0xff000000, 0xff000000,
932                               0xffffffff, 0xffffffff,
933                               0xff000000, 0xff000000 };
934         SkColor colors2[] = { 0xff000000, 0xff000000,
935                               0x00000000, 0x00000000,
936                               0xff000000, 0xff000000 };
937         SkScalar pos[] = { 0, .25f, .25f, .75f, .75f, 1 };
938         static_assert(std::size(colors1) == std::size(pos), "color/pos size mismatch");
939         static_assert(std::size(colors2) == std::size(pos), "color/pos size mismatch");
940 
941         SkPictureRecorder recorder;
942         recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize));
943 
944         SkPaint p;
945 
946         SkPoint pts1[] = { { 0, 0 }, { kTileSize, kTileSize }};
947         p.setShader(SkGradientShader::MakeLinear(pts1, colors1, pos, std::size(colors1),
948                                                  SkTileMode::kClamp, 0, nullptr));
949         recorder.getRecordingCanvas()->drawPaint(p);
950 
951         SkPoint pts2[] = { { 0, kTileSize }, { kTileSize, 0 }};
952         p.setShader(SkGradientShader::MakeLinear(pts2, colors2, pos, std::size(colors2),
953                                                  SkTileMode::kClamp, 0, nullptr));
954         recorder.getRecordingCanvas()->drawPaint(p);
955 
956         SkMatrix m = SkMatrix::I();
957         m.preRotate(45);
958         return recorder.finishRecordingAsPicture()->makeShader(
959                                            SkTileMode::kRepeat, SkTileMode::kRepeat,
960                                            SkFilterMode::kNearest, &m, nullptr);
961     });
962 
__anon463408530402() 963     draw_circle_shader(canvas, 400, 150, 100, []() -> sk_sp<SkShader> {
964         // Checkerboard using a sweep gradient + picture shader.
965         SkScalar kTileSize = 80;
966         SkColor colors[] = { 0xff000000, 0xff000000,
967                              0xffffffff, 0xffffffff,
968                              0xff000000, 0xff000000,
969                              0xffffffff, 0xffffffff };
970         SkScalar pos[] = { 0, .25f, .25f, .5f, .5f, .75f, .75f, 1 };
971         static_assert(std::size(colors) == std::size(pos), "color/pos size mismatch");
972 
973         SkPaint p;
974         p.setShader(SkGradientShader::MakeSweep(kTileSize / 2, kTileSize / 2,
975                                                 colors, pos, std::size(colors), 0, nullptr));
976         SkPictureRecorder recorder;
977         recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize))->drawPaint(p);
978 
979         return recorder.finishRecordingAsPicture()->makeShader(
980                                            SkTileMode::kRepeat,
981                                            SkTileMode::kRepeat, SkFilterMode::kNearest);
982     });
983 
__anon463408530502() 984     draw_circle_shader(canvas, 650, 150, 100, []() -> sk_sp<SkShader> {
985         // Dartboard using sweep + radial.
986         const SkColor a = 0xffffffff;
987         const SkColor b = 0xff000000;
988         SkColor colors[] = { a, a, b, b, a, a, b, b, a, a, b, b, a, a, b, b};
989         SkScalar pos[] = { 0, .125f, .125f, .25f, .25f, .375f, .375f, .5f, .5f,
990                            .625f, .625f, .75f, .75f, .875f, .875f, 1};
991         static_assert(std::size(colors) == std::size(pos), "color/pos size mismatch");
992 
993         SkPoint center = { 650, 150 };
994         sk_sp<SkShader> sweep1 = SkGradientShader::MakeSweep(center.x(), center.y(), colors, pos,
995                                                              std::size(colors), 0, nullptr);
996         SkMatrix m = SkMatrix::I();
997         m.preRotate(22.5f, center.x(), center.y());
998         sk_sp<SkShader> sweep2 = SkGradientShader::MakeSweep(center.x(), center.y(), colors, pos,
999                                                              std::size(colors), 0, &m);
1000 
1001         sk_sp<SkShader> sweep(SkShaders::Blend(SkBlendMode::kExclusion, sweep1, sweep2));
1002 
1003         SkScalar radialPos[] = { 0, .02f, .02f, .04f, .04f, .08f, .08f, .16f, .16f, .31f, .31f,
1004                                  .62f, .62f, 1, 1, 1 };
1005         static_assert(std::size(colors) == std::size(radialPos),
1006                       "color/pos size mismatch");
1007 
1008         return SkShaders::Blend(SkBlendMode::kExclusion, sweep,
1009                                 SkGradientShader::MakeRadial(center, 100, colors,
1010                                                              radialPos,
1011                                                              std::size(radialPos),
1012                                                              SkTileMode::kClamp));
1013     });
1014 }
1015 
1016 DEF_SIMPLE_GM(sweep_tiling, canvas, 690, 512) {
1017     static constexpr SkScalar size = 160;
1018     static constexpr SkColor colors[] = { SK_ColorBLUE, SK_ColorYELLOW, SK_ColorGREEN };
1019     static constexpr SkScalar   pos[] = { 0, .25f, .50f };
1020     static_assert(std::size(colors) == std::size(pos), "size mismatch");
1021 
1022     static constexpr SkTileMode modes[] = { SkTileMode::kClamp,
1023                                             SkTileMode::kRepeat,
1024                                             SkTileMode::kMirror };
1025 
1026     static const struct {
1027         SkScalar start, end;
1028     } angles[] = {
1029         { -330, -270 },
1030         {   30,   90 },
1031         {  390,  450 },
1032         {  -30,  800 },
1033     };
1034 
1035     SkPaint p;
1036     const SkRect r = SkRect::MakeWH(size, size);
1037 
1038     for (auto mode : modes) {
1039         {
1040             SkAutoCanvasRestore acr(canvas, true);
1041 
1042             for (auto angle : angles) {
1043                 p.setShader(SkGradientShader::MakeSweep(size / 2, size / 2, colors, pos,
1044                                                         std::size(colors), mode,
1045                                                         angle.start, angle.end, 0, nullptr));
1046 
1047                 canvas->drawRect(r, p);
1048                 canvas->translate(size * 1.1f, 0);
1049             }
1050         }
1051         canvas->translate(0, size * 1.1f);
1052     }
1053 }
1054 
1055 DEF_SIMPLE_GM(rgbw_sweep_gradient, canvas, 100, 100) {
1056     static constexpr SkScalar size = 100;
1057     static constexpr SkColor colors[] = {SK_ColorWHITE, SK_ColorWHITE,
1058                                          SK_ColorBLUE, SK_ColorBLUE,
1059                                          SK_ColorRED, SK_ColorRED,
1060                                          SK_ColorGREEN, SK_ColorGREEN};
1061     static constexpr SkScalar   pos[] = { 0, .25f, .25f, .50f, .50f, .75, .75, 1 };
1062     static_assert(std::size(colors) == std::size(pos), "size mismatch");
1063 
1064     SkPaint p;
1065     p.setShader(SkGradientShader::MakeSweep(size / 2, size / 2, colors, pos, std::size(colors)));
1066     canvas->drawRect(SkRect::MakeWH(size, size), p);
1067 }
1068 
1069 // Exercises the special-case Ganesh gradient effects.
1070 DEF_SIMPLE_GM(gradients_interesting, canvas, 640, 1300) {
1071     static const SkColor colors2[] = { SK_ColorRED, SK_ColorBLUE };
1072     static const SkColor colors3[] = { SK_ColorRED, SK_ColorYELLOW, SK_ColorBLUE };
1073     static const SkColor colors4[] = { SK_ColorRED, SK_ColorYELLOW, SK_ColorYELLOW, SK_ColorBLUE };
1074 
1075     static const SkScalar softRight[]  = { 0, .999f,   1 }; // Based on Android launcher "clipping"
1076     static const SkScalar hardLeft[]   = { 0,     0,   1 };
1077     static const SkScalar hardRight[]  = { 0,     1,   1 };
1078     static const SkScalar hardCenter[] = { 0,   .5f, .5f, 1 };
1079 
1080     static const struct {
1081         const SkColor*  colors;
1082         const SkScalar* pos;
1083         int             count;
1084     } configs[] = {
1085         { colors2,    nullptr, 2 }, // kTwo_ColorType
1086         { colors3,    nullptr, 3 }, // kThree_ColorType (simple)
1087         { colors3,  softRight, 3 }, // kThree_ColorType (tricky)
1088         { colors3,   hardLeft, 3 }, // kHardStopLeftEdged_ColorType
1089         { colors3,  hardRight, 3 }, // kHardStopRightEdged_ColorType
1090         { colors4, hardCenter, 4 }, // kSingleHardStop_ColorType
1091     };
1092 
1093     static const SkTileMode modes[] = {
1094         SkTileMode::kClamp,
1095         SkTileMode::kRepeat,
1096         SkTileMode::kMirror,
1097     };
1098 
1099     static constexpr SkScalar size = 200;
1100     static const SkPoint pts[] = { { size / 3, size / 3 }, { size * 2 / 3, size * 2 / 3} };
1101 
1102     SkPaint p;
1103     for (const auto& cfg : configs) {
1104         {
1105             SkAutoCanvasRestore acr(canvas, true);
1106             for (auto mode : modes) {
1107                 p.setShader(SkGradientShader::MakeLinear(pts, cfg.colors, cfg.pos, cfg.count,
1108                                                          mode));
1109                 canvas->drawRect(SkRect::MakeWH(size, size), p);
1110                 canvas->translate(size * 1.1f, 0);
1111             }
1112         }
1113         canvas->translate(0, size * 1.1f);
1114     }
1115 }
1116 
1117 // TODO(skia:13774): Still need to test degenerate gradients in strange color spaces
1118 DEF_SIMPLE_GM_BG(gradients_color_space, canvas, 265, 255, SK_ColorGRAY) {
1119     using CS = SkGradientShader::Interpolation::ColorSpace;
1120 
1121     struct Config {
1122         CS fColorSpace;
1123         const char* fLabel;
1124     };
1125     static const Config kConfigs[] = {
1126         { CS::kSRGB,          "sRGB" },
1127         { CS::kSRGBLinear,    "Linear" },
1128         { CS::kLab,           "Lab" },
1129         { CS::kOKLab,         "OKLab" },
1130         { CS::kOKLabGamutMap, "OKLabGamutMap" },
1131         { CS::kLCH,           "LCH" },
1132         { CS::kOKLCH,         "OKLCH" },
1133         { CS::kOKLCHGamutMap, "OKLCHGamutMap" },
1134         { CS::kHSL,           "HSL" },
1135         { CS::kHWB,           "HWB" },
1136     };
1137 
1138     SkPoint pts[] = {{0, 0}, {200, 0}};
1139     SkColor4f colors[] = {SkColors::kBlue, SkColors::kYellow};
1140 
1141     SkPaint labelPaint;
1142     SkPaint p;
1143     SkGradientShader::Interpolation interpolation;
1144     canvas->translate(5, 5);
1145     SkFont font = ToolUtils::DefaultPortableFont();
1146 
1147     for (const Config& config : kConfigs) {
1148         interpolation.fColorSpace = config.fColorSpace;
1149         p.setShader(SkGradientShader::MakeLinear(pts, colors, SkColorSpace::MakeSRGB(), nullptr, 2,
1150                                                  SkTileMode::kClamp, interpolation, nullptr));
1151         canvas->drawRect({0, 0, 200, 20}, p);
1152         canvas->drawSimpleText(config.fLabel, strlen(config.fLabel), SkTextEncoding::kUTF8, 210, 15,
1153                                font, labelPaint);
1154         canvas->translate(0, 25);
1155     }
1156 }
1157 
1158 DEF_SIMPLE_GM_BG(gradients_hue_method, canvas, 285, 155, SK_ColorGRAY) {
1159     using HM = SkGradientShader::Interpolation::HueMethod;
1160 
1161     struct Config {
1162         HM fHueMethod;
1163         const char* fLabel;
1164     };
1165     static const Config kConfigs[] = {
1166         { HM::kShorter,    "Shorter" },
1167         { HM::kLonger,     "Longer" },
1168         { HM::kIncreasing, "Increasing" },
1169         { HM::kDecreasing, "Decreasing" },
1170     };
1171 
1172     SkPoint pts[] = {{0, 0}, {200, 0}};
1173     SkColor4f colors[] = {SkColors::kRed, SkColors::kGreen, SkColors::kRed, SkColors::kRed };
1174 
1175     SkPaint labelPaint;
1176     SkPaint p;
1177     SkGradientShader::Interpolation interpolation;
1178     interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kHSL;
1179     canvas->translate(5, 5);
1180     SkFont font = ToolUtils::DefaultPortableFont();
1181 
1182     for (const Config& config : kConfigs) {
1183         interpolation.fHueMethod = config.fHueMethod;
1184         p.setShader(SkGradientShader::MakeLinear(pts, colors, SkColorSpace::MakeSRGB(), nullptr, 4,
1185                                                  SkTileMode::kClamp, interpolation, nullptr));
1186         canvas->drawRect({0, 0, 200, 20}, p);
1187         canvas->drawSimpleText(config.fLabel, strlen(config.fLabel), SkTextEncoding::kUTF8, 210, 15,
1188                                font, labelPaint);
1189         canvas->translate(0, 25);
1190     }
1191 
1192     // Test a bug (skia:13941) with how gradient shaders handle explicit positions.
1193     // If there are no explicit positions at 0 or 1, those are automatically added, with copies of
1194     // the first/last color. When using kLonger, this can produce extra gradient that should
1195     // actually be solid. This gradient *should* be:
1196     //   |- solid red -|- red to green, the long way -|- solid green -|
1197     interpolation.fHueMethod = HM::kLonger;
1198     SkScalar middlePos[] = { 0.3f, 0.7f };
1199     p.setShader(SkGradientShader::MakeLinear(pts, colors, SkColorSpace::MakeSRGB(), middlePos, 2,
1200                                              SkTileMode::kClamp, interpolation, nullptr));
1201     canvas->drawRect({0, 0, 200, 20}, p);
1202     canvas->translate(0, 25);
1203 
1204     // However... if the user explicitly includes those duplicate color stops in kLonger mode,
1205     // we expect the gradient to do a full rotation in those regions:
1206     //  |- full circle, red to red -|- red to green -|- full circle, green to green -|
1207     colors[0] = colors[1] = SkColors::kRed;
1208     colors[2] = colors[3] = SkColors::kGreen;
1209     SkScalar allPos[] = { 0.0f, 0.3f, 0.7f, 1.0f };
1210     p.setShader(SkGradientShader::MakeLinear(pts, colors, SkColorSpace::MakeSRGB(), allPos, 4,
1211                                              SkTileMode::kClamp, interpolation, nullptr));
1212     canvas->drawRect({0, 0, 200, 20}, p);
1213     canvas->translate(0, 25);
1214 }
1215 
1216 DEF_SIMPLE_GM_BG(gradients_color_space_tilemode, canvas, 360, 105, SK_ColorGRAY) {
1217     // Test exotic (CSS) gradient color spaces in conjunction with tile modes. Rather than test
1218     // every combination, we pick one color space that has a sufficiently strange interpolated
1219     // representation (OKLCH) and just use that. We're mostly interested in making sure that things
1220     // like decal mode are implemented at the correct time in the pipeline, relative to hue
1221     // conversion, re-premultiplication, etc.
1222     SkPoint pts[] = {{20, 0}, {120, 0}};
1223     SkColor4f colors[] = {SkColors::kBlue, SkColors::kYellow};
1224 
1225     SkPaint p;
1226     SkGradientShader::Interpolation interpolation;
1227     interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLCH;
1228 
1229     canvas->translate(5, 5);
1230 
1231     for (int tm = 0; tm < kSkTileModeCount; ++tm) {
1232         p.setShader(SkGradientShader::MakeLinear(pts, colors, SkColorSpace::MakeSRGB(), nullptr, 2,
1233                                                  static_cast<SkTileMode>(tm), interpolation,
1234                                                  nullptr));
1235         canvas->drawRect({0, 0, 350, 20}, p);
1236         canvas->translate(0, 25);
1237     }
1238 }
1239 
1240 DEF_SIMPLE_GM_BG(gradients_color_space_many_stops, canvas, 500, 500, SK_ColorGRAY) {
1241     // Test exotic (CSS) gradient color spaces with many stops. Rather than test every combination,
1242     // we pick one color space that has a sufficiently strange interpolated representation (OKLCH)
1243     // and just use that. We're mostly interested in making sure that the texture fallback on GPU
1244     // works correctly.
1245     const SkPoint pts[] = { {50, 50}, {450, 465}};
1246 
1247     const unsigned kStopCount = 200;
1248     SkColor4f colors[kStopCount];
1249     for (unsigned i = 0; i < kStopCount; i++) {
1250         switch (i % 5) {
1251             case 0: colors[i] = SkColors::kRed; break;
1252             case 1: colors[i] = SkColors::kGreen; break;
1253             case 2: colors[i] = SkColors::kGreen; break;
1254             case 3: colors[i] = SkColors::kBlue; break;
1255             case 4: colors[i] = SkColors::kRed; break;
1256         }
1257     }
1258 
1259     SkPaint p;
1260 
1261     SkGradientShader::Interpolation interpolation;
1262     interpolation.fColorSpace = SkGradientShader::Interpolation::ColorSpace::kOKLCH;
1263     p.setShader(SkGradientShader::MakeLinear(pts, colors, SkColorSpace::MakeSRGB(), nullptr,
1264                                              std::size(colors), SkTileMode::kClamp, interpolation,
1265                                              nullptr));
1266 
1267     canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
1268 }
1269 
draw_powerless_hue_gradients(SkCanvas * canvas,SkGradientShader::Interpolation::ColorSpace colorSpace)1270 static void draw_powerless_hue_gradients(SkCanvas* canvas,
1271                                          SkGradientShader::Interpolation::ColorSpace colorSpace) {
1272     ToolUtils::draw_checkerboard(canvas);
1273 
1274     auto nextRow = [=]() {
1275         canvas->restore();
1276         canvas->translate(0, 25);
1277         canvas->save();
1278     };
1279 
1280     auto gradient = [&](std::initializer_list<SkColor4f> colors,
1281                         std::initializer_list<float> pos,
1282                         bool inPremul = false) {
1283         using Interpolation = SkGradientShader::Interpolation;
1284         SkASSERT(pos.size() == 0 || pos.size() == colors.size());
1285         SkPaint paint;
1286         SkPoint pts[] = {{0, 0}, {200, 0}};
1287         Interpolation interpolation;
1288         interpolation.fColorSpace = colorSpace;
1289         interpolation.fInPremul = static_cast<Interpolation::InPremul>(inPremul);
1290         paint.setShader(SkGradientShader::MakeLinear(pts,
1291                                                      colors.begin(),
1292                                                      SkColorSpace::MakeSRGB(),
1293                                                      pos.size() == 0 ? nullptr : pos.begin(),
1294                                                      colors.size(),
1295                                                      SkTileMode::kClamp,
1296                                                      interpolation,
1297                                                      nullptr));
1298         canvas->drawRect({0, 0, 200, 20}, paint);
1299         canvas->translate(205, 0); // next column
1300     };
1301 
1302     canvas->translate(5, 5);
1303     canvas->save();
1304 
1305     // For each test case, the first gradient (first column) has an under-specified result due to a
1306     // powerless component after conversion to LCH. The second gradient (second column) "hints" the
1307     // correct result, by slightly tinting the otherwise powerless color.
1308 
1309     gradient({SkColors::kWhite,            SkColors::kBlue}, {});
1310     gradient({{0.99f, 0.99f, 1.00f, 1.0f}, SkColors::kBlue}, {}); // white, with blue hue
1311     nextRow();
1312 
1313     gradient({SkColors::kBlack,            SkColors::kBlue}, {});
1314     gradient({{0.00f, 0.00f, 0.01f, 1.0f}, SkColors::kBlue}, {}); // black, with blue hue
1315     nextRow();
1316 
1317     // Transparent cases are done in both premul and unpremul interpolation:
1318 
1319     gradient({SkColors::kTransparent,      SkColors::kBlue}, {}, /*inPremul=*/false);
1320     gradient({{0.00f, 0.00f, 0.01f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/false);
1321     nextRow();
1322 
1323     gradient({SkColors::kTransparent,      SkColors::kBlue}, {}, /*inPremul=*/true);
1324     gradient({{0.00f, 0.00f, 0.01f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/true);
1325     nextRow();
1326 
1327     gradient({{1.00f, 1.00f, 1.00f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/false);
1328     gradient({{0.99f, 0.99f, 1.00f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/false);
1329     nextRow();
1330 
1331     gradient({{1.00f, 1.00f, 1.00f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/true);
1332     gradient({{0.99f, 0.99f, 1.00f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/true);
1333     nextRow();
1334 
1335     // Now we test three-stop gradients, where the middle stop needs to be "split" to handle the
1336     // different hues on either side. Again, the second column explicitly injects those to produce
1337     // a reference result. See: https://github.com/w3c/csswg-drafts/issues/9295
1338 
1339     gradient({SkColors::kRed, SkColors::kWhite, SkColors::kBlue}, {});
1340     gradient({SkColors::kRed,
1341               {1.00f, 0.99f, 0.99f, 1.0f},
1342               {0.99f, 0.99f, 1.00f, 1.0f},
1343               SkColors::kBlue},
1344              {0.0f, 0.5f, 0.5f, 1.0f});
1345     nextRow();
1346 
1347     gradient({SkColors::kRed, SkColors::kBlack, SkColors::kBlue}, {});
1348     gradient({SkColors::kRed,
1349               {0.01f, 0.00f, 0.00f, 1.0f},
1350               {0.00f, 0.00f, 0.01f, 1.0f},
1351               SkColors::kBlue},
1352              {0.0f, 0.5f, 0.5f, 1.0f});
1353     nextRow();
1354 
1355     gradient({SkColors::kRed, SkColors::kTransparent, SkColors::kBlue}, {});
1356     gradient({SkColors::kRed,
1357               {0.01f, 0.00f, 0.00f, 0.0f},
1358               {0.00f, 0.00f, 0.01f, 0.0f},
1359               SkColors::kBlue},
1360              {0.0f, 0.5f, 0.5f, 1.0f});
1361     nextRow();
1362 
1363     // Now do a few black-white tests, to ensure that the hue propagation works correctly, even
1364     // when there isn't any hue in the adjacent stops.
1365     using HueMethod = SkGradientShader::Interpolation::HueMethod;
1366     auto blackWhiteGradient = [&](HueMethod hm) {
1367         using Interpolation = SkGradientShader::Interpolation;
1368         SkPaint paint;
1369         SkPoint pts[] = {{0, 0}, {405, 0}};
1370         Interpolation interpolation;
1371         interpolation.fColorSpace = colorSpace;
1372         interpolation.fHueMethod = hm;
1373         const SkColor4f colors[] = {SkColors::kWhite, SkColors::kGray,
1374                                     SkColors::kWhite, SkColors::kDkGray,
1375                                     SkColors::kWhite, SkColors::kBlack};
1376         paint.setShader(SkGradientShader::MakeLinear(pts,
1377                                                      colors,
1378                                                      SkColorSpace::MakeSRGB(),
1379                                                      nullptr,
1380                                                      std::size(colors),
1381                                                      SkTileMode::kClamp,
1382                                                      interpolation,
1383                                                      nullptr));
1384         canvas->drawRect({0, 0, 405, 20}, paint);
1385         nextRow();
1386     };
1387 
1388     blackWhiteGradient(HueMethod::kShorter);
1389     blackWhiteGradient(HueMethod::kIncreasing);
1390     blackWhiteGradient(HueMethod::kDecreasing);
1391     blackWhiteGradient(HueMethod::kLonger);
1392 }
1393 
1394 #define DEF_POWERLESS_HUE_GM(colorSpace)                                                          \
1395     DEF_SIMPLE_GM(gradients_powerless_hue_##colorSpace, canvas, 415, 330) {                       \
1396         draw_powerless_hue_gradients(canvas,                                                      \
1397                                      SkGradientShader::Interpolation::ColorSpace::k##colorSpace); \
1398     }
1399 
1400 DEF_POWERLESS_HUE_GM(LCH)
1401 DEF_POWERLESS_HUE_GM(OKLCH)
1402 DEF_POWERLESS_HUE_GM(HSL)
1403 DEF_POWERLESS_HUE_GM(HWB)
1404