2 * Copyright 2011 Google Inc.
4 * Use of this source code is governed by a BSD-style license that can be
5 * found in the LICENSE file.
9 #include "sk_tool_utils.h"
10 #include "SkGradientShader.h"
11 #include "SkLinearGradient.h"
17 const SkColor* fColors;
18 const SkColor4f* fColors4f;
22 constexpr SkColor gColors[] = {
23 SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE, SK_ColorWHITE, SK_ColorBLACK
25 constexpr SkColor4f gColors4f[] ={
26 { 1.0f, 0.0f, 0.0f, 1.0f }, // Red
27 { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
28 { 0.0f, 0.0f, 1.0f, 1.0f }, // Blue
29 { 1.0f, 1.0f, 1.0f, 1.0f }, // White
30 { 0.0f, 0.0f, 0.0f, 1.0f } // Black
32 constexpr SkScalar gPos0[] = { 0, SK_Scalar1 };
33 constexpr SkScalar gPos1[] = { SK_Scalar1/4, SK_Scalar1*3/4 };
34 constexpr SkScalar gPos2[] = {
35 0, SK_Scalar1/8, SK_Scalar1/2, SK_Scalar1*7/8, SK_Scalar1
38 constexpr SkScalar gPosClamp[] = {0.0f, 0.0f, 1.0f, 1.0f};
39 constexpr SkColor gColorClamp[] = {
40 SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorBLUE
42 constexpr SkColor4f gColor4fClamp[] ={
43 { 1.0f, 0.0f, 0.0f, 1.0f }, // Red
44 { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
45 { 0.0f, 1.0f, 0.0f, 1.0f }, // Green
46 { 0.0f, 0.0f, 1.0f, 1.0f } // Blue
48 constexpr GradData gGradData[] = {
49 { 2, gColors, gColors4f, nullptr },
50 { 2, gColors, gColors4f, gPos0 },
51 { 2, gColors, gColors4f, gPos1 },
52 { 5, gColors, gColors4f, nullptr },
53 { 5, gColors, gColors4f, gPos2 },
54 { 4, gColorClamp, gColor4fClamp, gPosClamp }
57 static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data,
58 SkShader::TileMode tm, const SkMatrix& localMatrix) {
59 return SkGradientShader::MakeLinear(pts, data.fColors, data.fPos, data.fCount, tm, 0,
63 static sk_sp<SkShader> MakeLinear4f(const SkPoint pts[2], const GradData& data,
64 SkShader::TileMode tm, const SkMatrix& localMatrix) {
65 auto srgb = SkColorSpace::MakeSRGBLinear();
66 return SkGradientShader::MakeLinear(pts, data.fColors4f, srgb, data.fPos, data.fCount, tm, 0,
70 static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data,
71 SkShader::TileMode tm, const SkMatrix& localMatrix) {
73 center.set(SkScalarAve(pts[0].fX, pts[1].fX),
74 SkScalarAve(pts[0].fY, pts[1].fY));
75 return SkGradientShader::MakeRadial(center, center.fX, data.fColors, data.fPos, data.fCount,
79 static sk_sp<SkShader> MakeRadial4f(const SkPoint pts[2], const GradData& data,
80 SkShader::TileMode tm, const SkMatrix& localMatrix) {
82 center.set(SkScalarAve(pts[0].fX, pts[1].fX),
83 SkScalarAve(pts[0].fY, pts[1].fY));
84 auto srgb = SkColorSpace::MakeSRGBLinear();
85 return SkGradientShader::MakeRadial(center, center.fX, data.fColors4f, srgb, data.fPos,
86 data.fCount, tm, 0, &localMatrix);
89 static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data,
90 SkShader::TileMode, const SkMatrix& localMatrix) {
92 center.set(SkScalarAve(pts[0].fX, pts[1].fX),
93 SkScalarAve(pts[0].fY, pts[1].fY));
94 return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors, data.fPos, data.fCount,
98 static sk_sp<SkShader> MakeSweep4f(const SkPoint pts[2], const GradData& data,
99 SkShader::TileMode, const SkMatrix& localMatrix) {
101 center.set(SkScalarAve(pts[0].fX, pts[1].fX),
102 SkScalarAve(pts[0].fY, pts[1].fY));
103 auto srgb = SkColorSpace::MakeSRGBLinear();
104 return SkGradientShader::MakeSweep(center.fX, center.fY, data.fColors4f, srgb, data.fPos,
105 data.fCount, 0, &localMatrix);
108 static sk_sp<SkShader> Make2Radial(const SkPoint pts[2], const GradData& data,
109 SkShader::TileMode tm, const SkMatrix& localMatrix) {
110 SkPoint center0, center1;
111 center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
112 SkScalarAve(pts[0].fY, pts[1].fY));
113 center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5),
114 SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4));
115 return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
116 center0, (pts[1].fX - pts[0].fX) / 2,
117 data.fColors, data.fPos, data.fCount, tm,
121 static sk_sp<SkShader> Make2Radial4f(const SkPoint pts[2], const GradData& data,
122 SkShader::TileMode tm, const SkMatrix& localMatrix) {
123 SkPoint center0, center1;
124 center0.set(SkScalarAve(pts[0].fX, pts[1].fX),
125 SkScalarAve(pts[0].fY, pts[1].fY));
126 center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3) / 5),
127 SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1) / 4));
128 auto srgb = SkColorSpace::MakeSRGBLinear();
129 return SkGradientShader::MakeTwoPointConical(center1, (pts[1].fX - pts[0].fX) / 7,
130 center0, (pts[1].fX - pts[0].fX) / 2,
131 data.fColors4f, srgb, data.fPos, data.fCount, tm,
135 static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data,
136 SkShader::TileMode tm, const SkMatrix& localMatrix) {
137 SkPoint center0, center1;
138 SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
139 SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
140 center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
141 center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
142 return SkGradientShader::MakeTwoPointConical(center1, radius1, center0, radius0,
143 data.fColors, data.fPos,
144 data.fCount, tm, 0, &localMatrix);
147 static sk_sp<SkShader> Make2Conical4f(const SkPoint pts[2], const GradData& data,
148 SkShader::TileMode tm, const SkMatrix& localMatrix) {
149 SkPoint center0, center1;
150 SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10;
151 SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3;
152 center0.set(pts[0].fX + radius0, pts[0].fY + radius0);
153 center1.set(pts[1].fX - radius1, pts[1].fY - radius1);
154 auto srgb = SkColorSpace::MakeSRGBLinear();
155 return SkGradientShader::MakeTwoPointConical(center1, radius1, center0, radius0,
156 data.fColors4f, srgb, data.fPos,
157 data.fCount, tm, 0, &localMatrix);
160 typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data,
161 SkShader::TileMode tm, const SkMatrix& localMatrix);
162 constexpr GradMaker gGradMakers[] = {
163 MakeLinear, MakeRadial, MakeSweep, Make2Radial, Make2Conical
165 constexpr GradMaker gGradMakers4f[] ={
166 MakeLinear4f, MakeRadial4f, MakeSweep4f, Make2Radial4f, Make2Conical4f
169 ///////////////////////////////////////////////////////////////////////////////
171 class GradientsGM : public GM {
173 GradientsGM(bool dither) : fDither(dither) {
174 this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
179 SkString onShortName() {
180 return SkString(fDither ? "gradients" : "gradients_nodither");
183 virtual SkISize onISize() { return SkISize::Make(840, 815); }
185 virtual void onDraw(SkCanvas* canvas) {
189 { SkIntToScalar(100), SkIntToScalar(100) }
191 SkShader::TileMode tm = SkShader::kClamp_TileMode;
192 SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
194 paint.setAntiAlias(true);
195 paint.setDither(fDither);
197 canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
198 for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
200 for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
201 SkMatrix scale = SkMatrix::I();
203 if (i == 5) { // if the clamp case
204 scale.setScale(0.5f, 0.5f);
205 scale.postTranslate(25.f, 25.f);
208 paint.setShader(gGradMakers[j](pts, gGradData[i], tm, scale));
209 canvas->drawRect(r, paint);
210 canvas->translate(0, SkIntToScalar(120));
213 canvas->translate(SkIntToScalar(120), 0);
221 typedef GM INHERITED;
223 DEF_GM( return new GradientsGM(true); )
224 DEF_GM( return new GradientsGM(false); )
226 // Like the original gradients GM, but using the SkColor4f shader factories. Should be identical.
227 class Gradients4fGM : public GM {
229 Gradients4fGM(bool dither) : fDither(dither) {
230 this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
235 SkString onShortName() {
236 return SkString(fDither ? "gradients4f" : "gradients4f_nodither");
239 virtual SkISize onISize() { return SkISize::Make(840, 815); }
241 virtual void onDraw(SkCanvas* canvas) {
245 { SkIntToScalar(100), SkIntToScalar(100) }
247 SkShader::TileMode tm = SkShader::kClamp_TileMode;
248 SkRect r ={ 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
250 paint.setAntiAlias(true);
251 paint.setDither(fDither);
253 canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
254 for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
256 for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers4f); j++) {
257 SkMatrix scale = SkMatrix::I();
259 if (i == 5) { // if the clamp case
260 scale.setScale(0.5f, 0.5f);
261 scale.postTranslate(25.f, 25.f);
264 paint.setShader(gGradMakers4f[j](pts, gGradData[i], tm, scale));
265 canvas->drawRect(r, paint);
266 canvas->translate(0, SkIntToScalar(120));
269 canvas->translate(SkIntToScalar(120), 0);
277 typedef GM INHERITED;
279 DEF_GM(return new Gradients4fGM(true); )
280 DEF_GM(return new Gradients4fGM(false); )
282 // Based on the original gradient slide, but with perspective applied to the
283 // gradient shaders' local matrices
284 class GradientsLocalPerspectiveGM : public GM {
286 GradientsLocalPerspectiveGM(bool dither) : fDither(dither) {
287 this->setBGColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
292 SkString onShortName() {
293 return SkString(fDither ? "gradients_local_perspective" :
294 "gradients_local_perspective_nodither");
297 virtual SkISize onISize() { return SkISize::Make(840, 815); }
299 virtual void onDraw(SkCanvas* canvas) {
303 { SkIntToScalar(100), SkIntToScalar(100) }
305 SkShader::TileMode tm = SkShader::kClamp_TileMode;
306 SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) };
308 paint.setAntiAlias(true);
309 paint.setDither(fDither);
311 canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
312 for (size_t i = 0; i < SK_ARRAY_COUNT(gGradData); i++) {
314 for (size_t j = 0; j < SK_ARRAY_COUNT(gGradMakers); j++) {
315 // apply an increasing y perspective as we move to the right
316 SkMatrix perspective;
317 perspective.setIdentity();
318 perspective.setPerspY(SkIntToScalar(i+1) / 500);
319 perspective.setSkewX(SkIntToScalar(i+1) / 10);
321 paint.setShader(gGradMakers[j](pts, gGradData[i], tm, perspective));
322 canvas->drawRect(r, paint);
323 canvas->translate(0, SkIntToScalar(120));
326 canvas->translate(SkIntToScalar(120), 0);
333 typedef GM INHERITED;
335 DEF_GM( return new GradientsLocalPerspectiveGM(true); )
336 DEF_GM( return new GradientsLocalPerspectiveGM(false); )
338 // Based on the original gradient slide, but with perspective applied to
340 class GradientsViewPerspectiveGM : public GradientsGM {
342 GradientsViewPerspectiveGM(bool dither) : INHERITED(dither) { }
345 SkString onShortName() {
346 return SkString(fDither ? "gradients_view_perspective" :
347 "gradients_view_perspective_nodither");
350 virtual SkISize onISize() { return SkISize::Make(840, 500); }
352 virtual void onDraw(SkCanvas* canvas) {
353 SkMatrix perspective;
354 perspective.setIdentity();
355 perspective.setPerspY(0.001f);
356 perspective.setSkewX(SkIntToScalar(8) / 25);
357 canvas->concat(perspective);
358 INHERITED::onDraw(canvas);
362 typedef GradientsGM INHERITED;
364 DEF_GM( return new GradientsViewPerspectiveGM(true); )
365 DEF_GM( return new GradientsViewPerspectiveGM(false); )
368 Inspired by this <canvas> javascript, where we need to detect that we are not
369 solving a quadratic equation, but must instead solve a linear (since our X^2
372 ctx.fillStyle = '#f00';
373 ctx.fillRect(0, 0, 100, 50);
375 var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150);
376 g.addColorStop(0, '#f00');
377 g.addColorStop(0.01, '#0f0');
378 g.addColorStop(0.99, '#0f0');
379 g.addColorStop(1, '#f00');
381 ctx.fillRect(0, 0, 100, 50);
383 class GradientsDegenrate2PointGM : public GM {
385 GradientsDegenrate2PointGM(bool dither) : fDither(dither) {}
388 SkString onShortName() {
389 return SkString(fDither ? "gradients_degenerate_2pt" : "gradients_degenerate_2pt_nodither");
392 virtual SkISize onISize() { return SkISize::Make(320, 320); }
394 void drawBG(SkCanvas* canvas) {
395 canvas->drawColor(SK_ColorBLUE);
398 virtual void onDraw(SkCanvas* canvas) {
399 this->drawBG(canvas);
401 SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorGREEN, SK_ColorRED };
402 SkScalar pos[] = { 0, 0.01f, 0.99f, SK_Scalar1 };
405 SkScalar r0 = SkIntToScalar(70);
408 SkScalar r1 = SkIntToScalar(150);
410 paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors,
411 pos, SK_ARRAY_COUNT(pos),
412 SkShader::kClamp_TileMode));
413 paint.setDither(fDither);
414 canvas->drawPaint(paint);
420 typedef GM INHERITED;
422 DEF_GM( return new GradientsDegenrate2PointGM(true); )
423 DEF_GM( return new GradientsDegenrate2PointGM(false); )
426 <canvas id="canvas"></canvas>
428 var c = document.getElementById("canvas");
429 var ctx = c.getContext("2d");
430 ctx.fillStyle = '#ff0';
431 ctx.fillRect(0, 0, 100, 50);
433 var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10);
434 g.addColorStop(0, '#0f0');
435 g.addColorStop(0.003, '#f00'); // 0.004 makes this work
436 g.addColorStop(1, '#ff0');
438 ctx.fillRect(0, 0, 100, 50);
442 // should draw only green
443 DEF_SIMPLE_GM(small_color_stop, canvas, 100, 150) {
444 SkColor colors[] = { SK_ColorGREEN, SK_ColorRED, SK_ColorYELLOW };
445 SkScalar pos[] = { 0, 0.003f, SK_Scalar1 }; // 0.004f makes this work
446 SkPoint c0 = { 200, 25 };
448 SkPoint c1 = { 200, 25 };
452 paint.setColor(SK_ColorYELLOW);
453 canvas->drawRect(SkRect::MakeWH(100, 150), paint);
454 paint.setShader(SkGradientShader::MakeTwoPointConical(c0, r0, c1, r1, colors, pos,
456 SkShader::kClamp_TileMode));
457 canvas->drawRect(SkRect::MakeWH(100, 150), paint);
461 /// Tests correctness of *optimized* codepaths in gradients.
463 class ClampedGradientsGM : public GM {
465 ClampedGradientsGM(bool dither) : fDither(dither) {}
468 SkString onShortName() {
469 return SkString(fDither ? "clamped_gradients" : "clamped_gradients_nodither");
472 virtual SkISize onISize() { return SkISize::Make(640, 510); }
474 void drawBG(SkCanvas* canvas) {
475 canvas->drawColor(sk_tool_utils::color_to_565(0xFFDDDDDD));
478 virtual void onDraw(SkCanvas* canvas) {
479 this->drawBG(canvas);
481 SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(300) };
483 paint.setDither(fDither);
484 paint.setAntiAlias(true);
488 canvas->translate(SkIntToScalar(20), SkIntToScalar(20));
489 paint.setShader(SkGradientShader::MakeRadial(
491 SkIntToScalar(200), gColors, nullptr, 5,
492 SkShader::kClamp_TileMode));
493 canvas->drawRect(r, paint);
499 typedef GM INHERITED;
501 DEF_GM( return new ClampedGradientsGM(true); )
502 DEF_GM( return new ClampedGradientsGM(false); )
504 /// Checks quality of large radial gradients, which may display
507 class RadialGradientGM : public GM {
509 RadialGradientGM() {}
513 SkString onShortName() override { return SkString("radial_gradient"); }
514 SkISize onISize() override { return SkISize::Make(1280, 1280); }
515 void drawBG(SkCanvas* canvas) {
516 canvas->drawColor(0xFF000000);
518 void onDraw(SkCanvas* canvas) override {
519 const SkISize dim = this->getISize();
521 this->drawBG(canvas);
524 paint.setDither(true);
526 center.set(SkIntToScalar(dim.width())/2, SkIntToScalar(dim.height())/2);
527 SkScalar radius = SkIntToScalar(dim.width())/2;
528 const SkColor colors[] = { 0x7f7f7f7f, 0x7f7f7f7f, 0xb2000000 };
529 const SkScalar pos[] = { 0.0f,
532 paint.setShader(SkGradientShader::MakeRadial(center, radius, colors, pos,
534 SkShader::kClamp_TileMode));
536 0, 0, SkIntToScalar(dim.width()), SkIntToScalar(dim.height())
538 canvas->drawRect(r, paint);
541 typedef GM INHERITED;
543 DEF_GM( return new RadialGradientGM; )
545 class RadialGradient2GM : public GM {
547 RadialGradient2GM(bool dither) : fDither(dither) {}
551 SkString onShortName() override {
552 return SkString(fDither ? "radial_gradient2" : "radial_gradient2_nodither");
555 SkISize onISize() override { return SkISize::Make(800, 400); }
556 void drawBG(SkCanvas* canvas) {
557 canvas->drawColor(0xFF000000);
560 // Reproduces the example given in bug 7671058.
561 void onDraw(SkCanvas* canvas) override {
562 SkPaint paint1, paint2, paint3;
563 paint1.setStyle(SkPaint::kFill_Style);
564 paint2.setStyle(SkPaint::kFill_Style);
565 paint3.setStyle(SkPaint::kFill_Style);
567 const SkColor sweep_colors[] =
568 { 0xFFFF0000, 0xFFFFFF00, 0xFF00FF00, 0xFF00FFFF, 0xFF0000FF, 0xFFFF00FF, 0xFFFF0000 };
569 const SkColor colors1[] = { 0xFFFFFFFF, 0x00000000 };
570 const SkColor colors2[] = { 0xFF000000, 0x00000000 };
572 const SkScalar cx = 200, cy = 200, radius = 150;
576 // We can either interpolate endpoints and premultiply each point (default, more precision),
577 // or premultiply the endpoints first, avoiding the need to premultiply each point (cheap).
578 const uint32_t flags[] = { 0, SkGradientShader::kInterpolateColorsInPremul_Flag };
580 for (size_t i = 0; i < SK_ARRAY_COUNT(flags); i++) {
581 paint1.setShader(SkGradientShader::MakeSweep(cx, cy, sweep_colors,
582 nullptr, SK_ARRAY_COUNT(sweep_colors),
584 paint2.setShader(SkGradientShader::MakeRadial(center, radius, colors1,
585 nullptr, SK_ARRAY_COUNT(colors1),
586 SkShader::kClamp_TileMode,
588 paint3.setShader(SkGradientShader::MakeRadial(center, radius, colors2,
589 nullptr, SK_ARRAY_COUNT(colors2),
590 SkShader::kClamp_TileMode,
592 paint1.setDither(fDither);
593 paint2.setDither(fDither);
594 paint3.setDither(fDither);
596 canvas->drawCircle(cx, cy, radius, paint1);
597 canvas->drawCircle(cx, cy, radius, paint3);
598 canvas->drawCircle(cx, cy, radius, paint2);
600 canvas->translate(400, 0);
607 typedef GM INHERITED;
609 DEF_GM( return new RadialGradient2GM(true); )
610 DEF_GM( return new RadialGradient2GM(false); )
612 // Shallow radial (shows banding on raster)
613 class RadialGradient3GM : public GM {
615 RadialGradient3GM(bool dither) : fDither(dither) { }
618 SkString onShortName() override {
619 return SkString(fDither ? "radial_gradient3" : "radial_gradient3_nodither");
622 SkISize onISize() override { return SkISize::Make(500, 500); }
624 bool runAsBench() const override { return true; }
626 void onOnceBeforeDraw() override {
627 const SkPoint center = { 0, 0 };
628 const SkScalar kRadius = 3000;
629 const SkColor gColors[] = { 0xFFFFFFFF, 0xFF000000 };
630 fShader = SkGradientShader::MakeRadial(center, kRadius, gColors, nullptr, 2,
631 SkShader::kClamp_TileMode);
634 void onDraw(SkCanvas* canvas) override {
636 paint.setShader(fShader);
637 paint.setDither(fDither);
638 canvas->drawRect(SkRect::MakeWH(500, 500), paint);
642 sk_sp<SkShader> fShader;
645 typedef GM INHERITED;
647 DEF_GM( return new RadialGradient3GM(true); )
648 DEF_GM( return new RadialGradient3GM(false); )
650 class RadialGradient4GM : public GM {
652 RadialGradient4GM(bool dither) : fDither(dither) { }
655 SkString onShortName() override {
656 return SkString(fDither ? "radial_gradient4" : "radial_gradient4_nodither");
659 SkISize onISize() override { return SkISize::Make(500, 500); }
661 void onOnceBeforeDraw() override {
662 const SkPoint center = { 250, 250 };
663 const SkScalar kRadius = 250;
664 const SkColor colors[] = { SK_ColorRED, SK_ColorRED, SK_ColorWHITE, SK_ColorWHITE,
666 const SkScalar pos[] = { 0, .4f, .4f, .8f, .8f, 1 };
667 fShader = SkGradientShader::MakeRadial(center, kRadius, colors, pos,
668 SK_ARRAY_COUNT(gColors), SkShader::kClamp_TileMode);
671 void onDraw(SkCanvas* canvas) override {
673 paint.setAntiAlias(true);
674 paint.setDither(fDither);
675 paint.setShader(fShader);
676 canvas->drawRect(SkRect::MakeWH(500, 500), paint);
680 sk_sp<SkShader> fShader;
683 typedef GM INHERITED;
685 DEF_GM( return new RadialGradient4GM(true); )
686 DEF_GM( return new RadialGradient4GM(false); )
688 class LinearGradientGM : public GM {
690 LinearGradientGM(bool dither) : fDither(dither) { }
693 SkString onShortName() override {
694 return SkString(fDither ? "linear_gradient" : "linear_gradient_nodither");
697 const SkScalar kWidthBump = 30.f;
698 const SkScalar kHeight = 5.f;
699 const SkScalar kMinWidth = 540.f;
701 SkISize onISize() override { return SkISize::Make(500, 500); }
703 void onOnceBeforeDraw() override {
704 SkPoint pts[2] = { {0, 0}, {0, 0} };
705 const SkColor colors[] = { SK_ColorWHITE, SK_ColorWHITE, 0xFF008200, 0xFF008200,
706 SK_ColorWHITE, SK_ColorWHITE };
707 const SkScalar unitPos[] = { 0, 50, 70, 500, 540 };
710 for (int index = 0; index < (int) SK_ARRAY_COUNT(fShader); ++index) {
711 pts[1].fX = 500.f + index * kWidthBump;
712 for (int inner = 0; inner < (int) SK_ARRAY_COUNT(unitPos); ++inner) {
713 pos[inner] = unitPos[inner] / (kMinWidth + index * kWidthBump);
715 fShader[index] = SkGradientShader::MakeLinear(pts, colors, pos,
716 SK_ARRAY_COUNT(gColors), SkShader::kClamp_TileMode);
720 void onDraw(SkCanvas* canvas) override {
722 paint.setAntiAlias(true);
723 paint.setDither(fDither);
724 for (int index = 0; index < (int) SK_ARRAY_COUNT(fShader); ++index) {
725 paint.setShader(fShader[index]);
726 canvas->drawRect(SkRect::MakeLTRB(0, index * kHeight, kMinWidth + index * kWidthBump,
727 (index + 1) * kHeight), paint);
732 sk_sp<SkShader> fShader[100];
735 typedef GM INHERITED;
737 DEF_GM( return new LinearGradientGM(true); )
738 DEF_GM( return new LinearGradientGM(false); )
740 class LinearGradientTinyGM : public GM {
742 LinearGradientTinyGM(uint32_t flags, const char* suffix = nullptr)
743 : fName("linear_gradient_tiny")
745 fName.append(suffix);
749 SkString onShortName() override {
753 SkISize onISize() override {
754 return SkISize::Make(600, 500);
757 void onDraw(SkCanvas* canvas) override {
758 const SkScalar kRectSize = 100;
759 const unsigned kStopCount = 3;
760 const SkColor colors[kStopCount] = { SK_ColorGREEN, SK_ColorRED, SK_ColorGREEN };
763 SkScalar pos[kStopCount];
765 { { SkPoint::Make(0, 0), SkPoint::Make(10, 0) }, { 0, 0.999999f, 1 }},
766 { { SkPoint::Make(0, 0), SkPoint::Make(10, 0) }, { 0, 0.000001f, 1 }},
767 { { SkPoint::Make(0, 0), SkPoint::Make(10, 0) }, { 0, 0.999999999f, 1 }},
768 { { SkPoint::Make(0, 0), SkPoint::Make(10, 0) }, { 0, 0.000000001f, 1 }},
770 { { SkPoint::Make(0, 0), SkPoint::Make(0, 10) }, { 0, 0.999999f, 1 }},
771 { { SkPoint::Make(0, 0), SkPoint::Make(0, 10) }, { 0, 0.000001f, 1 }},
772 { { SkPoint::Make(0, 0), SkPoint::Make(0, 10) }, { 0, 0.999999999f, 1 }},
773 { { SkPoint::Make(0, 0), SkPoint::Make(0, 10) }, { 0, 0.000000001f, 1 }},
775 { { SkPoint::Make(0, 0), SkPoint::Make(0.00001f, 0) }, { 0, 0.5f, 1 }},
776 { { SkPoint::Make(9.99999f, 0), SkPoint::Make(10, 0) }, { 0, 0.5f, 1 }},
777 { { SkPoint::Make(0, 0), SkPoint::Make(0, 0.00001f) }, { 0, 0.5f, 1 }},
778 { { SkPoint::Make(0, 9.99999f), SkPoint::Make(0, 10) }, { 0, 0.5f, 1 }},
782 for (unsigned i = 0; i < SK_ARRAY_COUNT(configs); ++i) {
783 SkAutoCanvasRestore acr(canvas, true);
784 paint.setShader(SkGradientShader::MakeLinear(configs[i].pts, colors, configs[i].pos,
785 kStopCount, SkShader::kClamp_TileMode,
787 canvas->translate(kRectSize * ((i % 4) * 1.5f + 0.25f),
788 kRectSize * ((i / 4) * 1.5f + 0.25f));
790 canvas->drawRect(SkRect::MakeWH(kRectSize, kRectSize), paint);
795 typedef GM INHERITED;
800 DEF_GM( return new LinearGradientTinyGM(0); )
801 DEF_GM( return new LinearGradientTinyGM(SkLinearGradient::kForce4fContext_PrivateFlag, "_4f"); )
804 ///////////////////////////////////////////////////////////////////////////////////////////////////
814 static sk_sp<SkShader> make_linear(const GradRun& run, SkShader::TileMode mode) {
815 const SkPoint pts[] { { 30, 30 }, { SIZE - 30, SIZE - 30 } };
816 return SkGradientShader::MakeLinear(pts, run.fColors, run.fPos, run.fCount, mode);
819 static sk_sp<SkShader> make_radial(const GradRun& run, SkShader::TileMode mode) {
820 const SkScalar half = SIZE * 0.5f;
821 return SkGradientShader::MakeRadial({half,half}, half - 10, run.fColors, run.fPos,
825 static sk_sp<SkShader> make_conical(const GradRun& run, SkShader::TileMode mode) {
826 const SkScalar half = SIZE * 0.5f;
827 const SkPoint center { half, half };
828 return SkGradientShader::MakeTwoPointConical(center, 20, center, half - 10,
829 run.fColors, run.fPos, run.fCount, mode);
832 static sk_sp<SkShader> make_sweep(const GradRun& run, SkShader::TileMode) {
833 const SkScalar half = SIZE * 0.5f;
834 return SkGradientShader::MakeSweep(half, half, run.fColors, run.fPos, run.fCount);
838 * Exercise duplicate color-stops, at the ends, and in the middle
840 * At the time of this writing, only Linear correctly deals with duplicates at the ends,
841 * and then only correctly on CPU backend.
843 DEF_SIMPLE_GM(gradients_dup_color_stops, canvas, 704, 564) {
844 const SkColor preColor = 0xFFFF0000; // clamp color before start
845 const SkColor postColor = 0xFF0000FF; // clamp color after end
846 const SkColor color0 = 0xFF000000;
847 const SkColor color1 = 0xFF00FF00;
848 const SkColor badColor = 0xFF3388BB; // should never be seen, fills out fixed-size array
850 const GradRun runs[] = {
851 { { color0, color1, badColor, badColor },
855 { { preColor, color0, color1, badColor },
859 { { color0, color1, postColor, badColor },
863 { { preColor, color0, color1, postColor },
867 { { color0, color0, color1, color1 },
868 { 0, 0.5f, 0.5f, 1 },
872 sk_sp<SkShader> (*factories[])(const GradRun&, SkShader::TileMode) {
873 make_linear, make_radial, make_conical, make_sweep
876 const SkRect rect = SkRect::MakeWH(SIZE, SIZE);
877 const SkScalar dx = SIZE + 20;
878 const SkScalar dy = SIZE + 20;
879 const SkShader::TileMode mode = SkShader::kClamp_TileMode;
882 canvas->translate(10, 10 - dy);
883 for (auto factory : factories) {
884 canvas->translate(0, dy);
885 SkAutoCanvasRestore acr(canvas, true);
886 for (const auto& run : runs) {
887 paint.setShader(factory(run, mode));
888 canvas->drawRect(rect, paint);
889 canvas->translate(dx, 0);
894 static void draw_many_stops(SkCanvas* canvas, uint32_t flags) {
895 const unsigned kStopCount = 200;
896 const SkPoint pts[] = { {50, 50}, {450, 465}};
898 SkColor colors[kStopCount];
899 for (unsigned i = 0; i < kStopCount; i++) {
901 case 0: colors[i] = SK_ColorRED; break;
902 case 1: colors[i] = SK_ColorGREEN; break;
903 case 2: colors[i] = SK_ColorGREEN; break;
904 case 3: colors[i] = SK_ColorBLUE; break;
905 case 4: colors[i] = SK_ColorRED; break;
910 p.setShader(SkGradientShader::MakeLinear(
911 pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kClamp_TileMode, flags, nullptr));
913 canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
916 DEF_SIMPLE_GM(gradient_many_stops, canvas, 500, 500) {
917 draw_many_stops(canvas, 0);
920 DEF_SIMPLE_GM(gradient_many_stops_4f, canvas, 500, 500) {
921 draw_many_stops(canvas, SkLinearGradient::kForce4fContext_PrivateFlag);
924 static void draw_subpixel_gradient(SkCanvas* canvas, uint32_t flags) {
925 const SkPoint pts[] = { {50, 50}, {50.1f, 50.1f}};
926 SkColor colors[] = { SK_ColorRED, SK_ColorGREEN, SK_ColorBLUE };
928 p.setShader(SkGradientShader::MakeLinear(
929 pts, colors, nullptr, SK_ARRAY_COUNT(colors), SkShader::kRepeat_TileMode, flags, nullptr));
930 canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p);
933 DEF_SIMPLE_GM(gradient_subpixel, canvas, 500, 500) {
934 draw_subpixel_gradient(canvas, 0);
937 DEF_SIMPLE_GM(gradient_subpixel_4f, canvas, 500, 500) {
938 draw_subpixel_gradient(canvas, SkLinearGradient::kForce4fContext_PrivateFlag);
941 #include "SkPictureRecorder.h"
943 static void draw_circle_shader(SkCanvas* canvas, SkScalar cx, SkScalar cy, SkScalar r,
944 sk_sp<SkShader> (*shaderFunc)()) {
946 p.setAntiAlias(true);
947 p.setShader(shaderFunc());
948 canvas->drawCircle(cx, cy, r, p);
950 p.setShader(nullptr);
951 p.setColor(SK_ColorGRAY);
952 p.setStyle(SkPaint::kStroke_Style);
954 canvas->drawCircle(cx, cy, r, p);
957 DEF_SIMPLE_GM(fancy_gradients, canvas, 800, 300) {
958 draw_circle_shader(canvas, 150, 150, 100, []() -> sk_sp<SkShader> {
959 // Checkerboard using two linear gradients + picture shader.
960 SkScalar kTileSize = 80 / sqrtf(2);
961 SkColor colors1[] = { 0xff000000, 0xff000000,
962 0xffffffff, 0xffffffff,
963 0xff000000, 0xff000000 };
964 SkColor colors2[] = { 0xff000000, 0xff000000,
965 0x00000000, 0x00000000,
966 0xff000000, 0xff000000 };
967 SkScalar pos[] = { 0, .25f, .25f, .75f, .75f, 1 };
968 static_assert(SK_ARRAY_COUNT(colors1) == SK_ARRAY_COUNT(pos), "color/pos size mismatch");
969 static_assert(SK_ARRAY_COUNT(colors2) == SK_ARRAY_COUNT(pos), "color/pos size mismatch");
971 SkPictureRecorder recorder;
972 recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize));
976 SkPoint pts1[] = { { 0, 0 }, { kTileSize, kTileSize }};
977 p.setShader(SkGradientShader::MakeLinear(pts1, colors1, pos, SK_ARRAY_COUNT(colors1),
978 SkShader::kClamp_TileMode, 0, nullptr));
979 recorder.getRecordingCanvas()->drawPaint(p);
981 SkPoint pts2[] = { { 0, kTileSize }, { kTileSize, 0 }};
982 p.setShader(SkGradientShader::MakeLinear(pts2, colors2, pos, SK_ARRAY_COUNT(colors2),
983 SkShader::kClamp_TileMode, 0, nullptr));
984 recorder.getRecordingCanvas()->drawPaint(p);
986 SkMatrix m = SkMatrix::I();
988 return SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
989 SkShader::kRepeat_TileMode,
990 SkShader::kRepeat_TileMode, &m, nullptr);
993 draw_circle_shader(canvas, 400, 150, 100, []() -> sk_sp<SkShader> {
994 // Checkerboard using a sweep gradient + picture shader.
995 SkScalar kTileSize = 80;
996 SkColor colors[] = { 0xff000000, 0xff000000,
997 0xffffffff, 0xffffffff,
998 0xff000000, 0xff000000,
999 0xffffffff, 0xffffffff };
1000 SkScalar pos[] = { 0, .25f, .25f, .5f, .5f, .75f, .75f, 1 };
1001 static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(pos), "color/pos size mismatch");
1004 p.setShader(SkGradientShader::MakeSweep(kTileSize / 2, kTileSize / 2,
1005 colors, pos, SK_ARRAY_COUNT(colors), 0, nullptr));
1006 SkPictureRecorder recorder;
1007 recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize))->drawPaint(p);
1009 return SkShader::MakePictureShader(recorder.finishRecordingAsPicture(),
1010 SkShader::kRepeat_TileMode,
1011 SkShader::kRepeat_TileMode, nullptr, nullptr);
1014 draw_circle_shader(canvas, 650, 150, 100, []() -> sk_sp<SkShader> {
1015 // Dartboard using sweep + radial.
1016 const SkColor a = 0xffffffff;
1017 const SkColor b = 0xff000000;
1018 SkColor colors[] = { a, a, b, b, a, a, b, b, a, a, b, b, a, a, b, b};
1019 SkScalar pos[] = { 0, .125f, .125f, .25f, .25f, .375f, .375f, .5f, .5f,
1020 .625f, .625f, .75f, .75f, .875f, .875f, 1};
1021 static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(pos), "color/pos size mismatch");
1023 SkPoint center = { 650, 150 };
1024 sk_sp<SkShader> sweep1 = SkGradientShader::MakeSweep(center.x(), center.y(), colors, pos,
1025 SK_ARRAY_COUNT(colors), 0, nullptr);
1026 SkMatrix m = SkMatrix::I();
1027 m.preRotate(22.5f, center.x(), center.y());
1028 sk_sp<SkShader> sweep2 = SkGradientShader::MakeSweep(center.x(), center.y(), colors, pos,
1029 SK_ARRAY_COUNT(colors), 0, &m);
1031 sk_sp<SkShader> sweep(SkShader::MakeComposeShader(sweep1, sweep2, SkBlendMode::kExclusion));
1033 SkScalar radialPos[] = { 0, .02f, .02f, .04f, .04f, .08f, .08f, .16f, .16f, .31f, .31f,
1034 .62f, .62f, 1, 1, 1 };
1035 static_assert(SK_ARRAY_COUNT(colors) == SK_ARRAY_COUNT(radialPos),
1036 "color/pos size mismatch");
1038 return SkShader::MakeComposeShader(sweep,
1039 SkGradientShader::MakeRadial(center, 100, colors,
1041 SK_ARRAY_COUNT(radialPos),
1042 SkShader::kClamp_TileMode),
1043 SkBlendMode::kExclusion);