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