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