| /* | |
| * Copyright 2011 Google Inc. | |
| * | |
| * Use of this source code is governed by a BSD-style license that can be | |
| * found in the LICENSE file. | |
| */ | |
| #include "gm/gm.h" | |
| #include "include/core/SkBlendMode.h" | |
| #include "include/core/SkCanvas.h" | |
| #include "include/core/SkColor.h" | |
| #include "include/core/SkColorSpace.h" | |
| #include "include/core/SkFont.h" | |
| #include "include/core/SkMatrix.h" | |
| #include "include/core/SkPaint.h" | |
| #include "include/core/SkPicture.h" | |
| #include "include/core/SkPictureRecorder.h" | |
| #include "include/core/SkPoint.h" | |
| #include "include/core/SkRect.h" | |
| #include "include/core/SkRefCnt.h" | |
| #include "include/core/SkScalar.h" | |
| #include "include/core/SkShader.h" | |
| #include "include/core/SkSize.h" | |
| #include "include/core/SkString.h" | |
| #include "include/core/SkTileMode.h" | |
| #include "include/core/SkTypes.h" | |
| #include "include/effects/SkGradient.h" | |
| #include "include/private/SkAssert.h" | |
| #include "include/private/SkFloatingPoint.h" | |
| #include "src/core/SkColorPriv.h" | |
| #include "tools/ToolUtils.h" | |
| #include "tools/fonts/FontToolUtils.h" | |
| #include <array> | |
| #include <initializer_list> | |
| #include <math.h> | |
| namespace { | |
| struct GradData { | |
| SkSpan<const SkColor4f> fColors; | |
| SkSpan<const float> fPos; | |
| }; | |
| constexpr SkColor4f gColors[] ={ | |
| SkColors::kRed, | |
| SkColors::kGreen, | |
| SkColors::kBlue, | |
| SkColors::kWhite, | |
| SkColors::kBlack, | |
| }; | |
| constexpr SkScalar gPos0[] = { 0, SK_Scalar1 }; | |
| constexpr SkScalar gPos1[] = { SK_Scalar1/4, SK_Scalar1*3/4 }; | |
| constexpr SkScalar gPos2[] = { | |
| 0, SK_Scalar1/8, SK_Scalar1/2, SK_Scalar1*7/8, SK_Scalar1 | |
| }; | |
| constexpr SkScalar gPosClamp[] = {0.0f, 0.0f, 1.0f, 1.0f}; | |
| constexpr SkColor4f gColor4fClamp[] = { | |
| SkColors::kRed, | |
| SkColors::kGreen, | |
| SkColors::kGreen, | |
| SkColors::kBlue, | |
| }; | |
| static constexpr auto gGradData = std::to_array<GradData>({ | |
| GradData{ {gColors, 2}, {}}, | |
| GradData{ {gColors, 2}, gPos0}, | |
| GradData{ {gColors, 2}, gPos1}, | |
| GradData{ {gColors, 5}, {}}, | |
| GradData{ {gColors, 5}, gPos2}, | |
| GradData{gColor4fClamp, gPosClamp} | |
| }); | |
| static sk_sp<SkShader> MakeLinear(const SkPoint pts[2], const GradData& data, | |
| SkTileMode tm, const SkMatrix& localMatrix) { | |
| return SkShaders::LinearGradient(pts, {{data.fColors, data.fPos, tm}, {}}, &localMatrix); | |
| } | |
| static sk_sp<SkShader> MakeRadial(const SkPoint pts[2], const GradData& data, | |
| SkTileMode tm, const SkMatrix& localMatrix) { | |
| SkPoint center; | |
| center.set(sk_float_midpoint(pts[0].fX, pts[1].fX), | |
| sk_float_midpoint(pts[0].fY, pts[1].fY)); | |
| return SkShaders::RadialGradient(center, center.fX, {{data.fColors, data.fPos, tm}, {}}, | |
| &localMatrix); | |
| } | |
| static sk_sp<SkShader> MakeSweep(const SkPoint pts[2], const GradData& data, | |
| SkTileMode tm, const SkMatrix& localMatrix) { | |
| SkPoint center; | |
| center.set(sk_float_midpoint(pts[0].fX, pts[1].fX), | |
| sk_float_midpoint(pts[0].fY, pts[1].fY)); | |
| return SkShaders::SweepGradient(center, {{data.fColors, data.fPos, tm}, {}}, &localMatrix); | |
| } | |
| static sk_sp<SkShader> Make2Radial(const SkPoint pts[2], const GradData& data, | |
| SkTileMode tm, const SkMatrix& localMatrix) { | |
| SkPoint center0, center1; | |
| center0.set(sk_float_midpoint(pts[0].fX, pts[1].fX), | |
| sk_float_midpoint(pts[0].fY, pts[1].fY)); | |
| center1.set(SkScalarInterp(pts[0].fX, pts[1].fX, SkIntToScalar(3)/5), | |
| SkScalarInterp(pts[0].fY, pts[1].fY, SkIntToScalar(1)/4)); | |
| return SkShaders::TwoPointConicalGradient(center1, (pts[1].fX - pts[0].fX) / 7, | |
| center0, (pts[1].fX - pts[0].fX) / 2, | |
| {{data.fColors, data.fPos, tm}, {}}, &localMatrix); | |
| } | |
| static sk_sp<SkShader> Make2Conical(const SkPoint pts[2], const GradData& data, | |
| SkTileMode tm, const SkMatrix& localMatrix) { | |
| SkPoint center0, center1; | |
| SkScalar radius0 = (pts[1].fX - pts[0].fX) / 10; | |
| SkScalar radius1 = (pts[1].fX - pts[0].fX) / 3; | |
| center0.set(pts[0].fX + radius0, pts[0].fY + radius0); | |
| center1.set(pts[1].fX - radius1, pts[1].fY - radius1); | |
| return SkShaders::TwoPointConicalGradient(center1, radius1, center0, radius0, | |
| {{data.fColors, data.fPos, tm}, {}}, &localMatrix); | |
| } | |
| typedef sk_sp<SkShader> (*GradMaker)(const SkPoint pts[2], const GradData& data, | |
| SkTileMode tm, const SkMatrix& localMatrix); | |
| static constexpr auto gGradMakers = std::to_array<GradMaker>({ | |
| MakeLinear, | |
| MakeRadial, | |
| MakeSweep, | |
| Make2Radial, | |
| Make2Conical, | |
| }); | |
| /////////////////////////////////////////////////////////////////////////////// | |
| class GradientsGM : public skiagm::GM { | |
| public: | |
| GradientsGM(bool dither) : fDither(dither) {} | |
| protected: | |
| const bool fDither; | |
| void onDraw(SkCanvas* canvas) override { | |
| SkPoint pts[2] = { | |
| { 0, 0 }, | |
| { SkIntToScalar(100), SkIntToScalar(100) } | |
| }; | |
| SkTileMode tm = SkTileMode::kClamp; | |
| SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) }; | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setDither(fDither); | |
| canvas->translate(SkIntToScalar(20), SkIntToScalar(20)); | |
| for (size_t i = 0; i < std::size(gGradData); i++) { | |
| canvas->save(); | |
| for (size_t j = 0; j < std::size(gGradMakers); j++) { | |
| SkMatrix scale = SkMatrix::I(); | |
| if (i == 5) { // if the clamp case | |
| scale.setScale(0.5f, 0.5f); | |
| scale.postTranslate(25.f, 25.f); | |
| } | |
| paint.setShader(gGradMakers[j](pts, gGradData[i], tm, scale)); | |
| canvas->drawRect(r, paint); | |
| canvas->translate(0, SkIntToScalar(120)); | |
| } | |
| canvas->restore(); | |
| canvas->translate(SkIntToScalar(120), 0); | |
| } | |
| } | |
| private: | |
| void onOnceBeforeDraw() override { this->setBGColor(0xFFDDDDDD); } | |
| SkString getName() const override { | |
| return SkString(fDither ? "gradients" : "gradients_nodither"); | |
| } | |
| SkISize getISize() override { return {840, 815}; } | |
| }; | |
| DEF_GM( return new GradientsGM(true); ) | |
| DEF_GM( return new GradientsGM(false); ) | |
| // Based on the original gradient slide, but with perspective applied to the | |
| // gradient shaders' local matrices | |
| class GradientsLocalPerspectiveGM : public skiagm::GM { | |
| public: | |
| GradientsLocalPerspectiveGM(bool dither) : fDither(dither) { | |
| this->setBGColor(0xFFDDDDDD); | |
| } | |
| private: | |
| SkString getName() const override { | |
| return SkString(fDither ? "gradients_local_perspective" : | |
| "gradients_local_perspective_nodither"); | |
| } | |
| SkISize getISize() override { return {840, 815}; } | |
| void onDraw(SkCanvas* canvas) override { | |
| SkPoint pts[2] = { | |
| { 0, 0 }, | |
| { SkIntToScalar(100), SkIntToScalar(100) } | |
| }; | |
| SkTileMode tm = SkTileMode::kClamp; | |
| SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(100) }; | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setDither(fDither); | |
| canvas->translate(SkIntToScalar(20), SkIntToScalar(20)); | |
| for (size_t i = 0; i < std::size(gGradData); i++) { | |
| canvas->save(); | |
| for (size_t j = 0; j < std::size(gGradMakers); j++) { | |
| // apply an increasing y perspective as we move to the right | |
| SkMatrix perspective; | |
| perspective.setIdentity(); | |
| perspective.setPerspY(SkIntToScalar(i+1) / 500); | |
| perspective.setSkewX(SkIntToScalar(i+1) / 10); | |
| paint.setShader(gGradMakers[j](pts, gGradData[i], tm, perspective)); | |
| canvas->drawRect(r, paint); | |
| canvas->translate(0, SkIntToScalar(120)); | |
| } | |
| canvas->restore(); | |
| canvas->translate(SkIntToScalar(120), 0); | |
| } | |
| } | |
| bool fDither; | |
| }; | |
| DEF_GM( return new GradientsLocalPerspectiveGM(true); ) | |
| DEF_GM( return new GradientsLocalPerspectiveGM(false); ) | |
| // Based on the original gradient slide, but with perspective applied to | |
| // the view matrix | |
| class GradientsViewPerspectiveGM : public GradientsGM { | |
| public: | |
| GradientsViewPerspectiveGM(bool dither) : INHERITED(dither) { } | |
| private: | |
| SkString getName() const override { | |
| return SkString(fDither ? "gradients_view_perspective" : | |
| "gradients_view_perspective_nodither"); | |
| } | |
| SkISize getISize() override { return {840, 500}; } | |
| void onDraw(SkCanvas* canvas) override { | |
| SkMatrix perspective; | |
| perspective.setIdentity(); | |
| perspective.setPerspY(0.001f); | |
| perspective.setSkewX(SkIntToScalar(8) / 25); | |
| canvas->concat(perspective); | |
| this->INHERITED::onDraw(canvas); | |
| } | |
| private: | |
| using INHERITED = GradientsGM; | |
| }; | |
| DEF_GM( return new GradientsViewPerspectiveGM(true); ) | |
| DEF_GM( return new GradientsViewPerspectiveGM(false); ) | |
| /* | |
| Inspired by this <canvas> javascript, where we need to detect that we are not | |
| solving a quadratic equation, but must instead solve a linear (since our X^2 | |
| coefficient is 0) | |
| ctx.fillStyle = '#f00'; | |
| ctx.fillRect(0, 0, 100, 50); | |
| var g = ctx.createRadialGradient(-80, 25, 70, 0, 25, 150); | |
| g.addColorStop(0, '#f00'); | |
| g.addColorStop(0.01, '#0f0'); | |
| g.addColorStop(0.99, '#0f0'); | |
| g.addColorStop(1, '#f00'); | |
| ctx.fillStyle = g; | |
| ctx.fillRect(0, 0, 100, 50); | |
| */ | |
| class GradientsDegenrate2PointGM : public skiagm::GM { | |
| public: | |
| GradientsDegenrate2PointGM(bool dither) : fDither(dither) {} | |
| private: | |
| SkString getName() const override { | |
| return SkString(fDither ? "gradients_degenerate_2pt" : "gradients_degenerate_2pt_nodither"); | |
| } | |
| SkISize getISize() override { return {320, 320}; } | |
| void onDraw(SkCanvas* canvas) override { | |
| canvas->drawColor(SK_ColorBLUE); | |
| SkColor4f colors[] = { SkColors::kRed, SkColors::kGreen, SkColors::kGreen, SkColors::kRed }; | |
| SkScalar pos[] = { 0, 0.01f, 0.99f, SK_Scalar1 }; | |
| SkPoint c0; | |
| c0.iset(-80, 25); | |
| SkScalar r0 = SkIntToScalar(70); | |
| SkPoint c1; | |
| c1.iset(0, 25); | |
| SkScalar r1 = SkIntToScalar(150); | |
| SkPaint paint; | |
| paint.setShader(SkShaders::TwoPointConicalGradient(c0, r0, c1, r1, | |
| {{colors, pos, SkTileMode::kClamp}, {}})); | |
| paint.setDither(fDither); | |
| canvas->drawPaint(paint); | |
| } | |
| bool fDither; | |
| }; | |
| DEF_GM( return new GradientsDegenrate2PointGM(true); ) | |
| DEF_GM( return new GradientsDegenrate2PointGM(false); ) | |
| /* skbug.com/40031542 | |
| <canvas id="canvas"></canvas> | |
| <script> | |
| var c = document.getElementById("canvas"); | |
| var ctx = c.getContext("2d"); | |
| ctx.fillStyle = '#ff0'; | |
| ctx.fillRect(0, 0, 100, 50); | |
| var g = ctx.createRadialGradient(200, 25, 20, 200, 25, 10); | |
| g.addColorStop(0, '#0f0'); | |
| g.addColorStop(0.003, '#f00'); // 0.004 makes this work | |
| g.addColorStop(1, '#ff0'); | |
| ctx.fillStyle = g; | |
| ctx.fillRect(0, 0, 100, 50); | |
| </script> | |
| */ | |
| // should draw only green | |
| DEF_SIMPLE_GM(small_color_stop, canvas, 100, 150) { | |
| SkColor4f colors[] = { SkColors::kGreen, SkColors::kRed, SkColors::kYellow }; | |
| SkScalar pos[] = { 0, 0.003f, SK_Scalar1 }; // 0.004f makes this work | |
| SkPoint c0 = { 200, 25 }; | |
| SkScalar r0 = 20; | |
| SkPoint c1 = { 200, 25 }; | |
| SkScalar r1 = 10; | |
| SkPaint paint; | |
| paint.setColor(SK_ColorYELLOW); | |
| canvas->drawRect(SkRect::MakeWH(100, 150), paint); | |
| paint.setShader(SkShaders::TwoPointConicalGradient(c0, r0, c1, r1, | |
| {{colors, pos, SkTileMode::kClamp}, {}})); | |
| canvas->drawRect(SkRect::MakeWH(100, 150), paint); | |
| } | |
| /// Tests correctness of *optimized* codepaths in gradients. | |
| class ClampedGradientsGM : public skiagm::GM { | |
| public: | |
| ClampedGradientsGM(bool dither) : fDither(dither) {} | |
| private: | |
| SkString getName() const override { | |
| return SkString(fDither ? "clamped_gradients" : "clamped_gradients_nodither"); | |
| } | |
| SkISize getISize() override { return {640, 510}; } | |
| void onDraw(SkCanvas* canvas) override { | |
| canvas->drawColor(0xFFDDDDDD); | |
| SkRect r = { 0, 0, SkIntToScalar(100), SkIntToScalar(300) }; | |
| SkPaint paint; | |
| paint.setDither(fDither); | |
| paint.setAntiAlias(true); | |
| SkPoint center; | |
| center.iset(0, 300); | |
| canvas->translate(SkIntToScalar(20), SkIntToScalar(20)); | |
| paint.setShader(SkShaders::RadialGradient( | |
| center, 200, {{gColors, {}, SkTileMode::kClamp}, {}})); | |
| canvas->drawRect(r, paint); | |
| } | |
| bool fDither; | |
| }; | |
| DEF_GM( return new ClampedGradientsGM(true); ) | |
| DEF_GM( return new ClampedGradientsGM(false); ) | |
| /// Checks quality of large radial gradients, which may display | |
| /// some banding. | |
| class RadialGradientGM : public skiagm::GM { | |
| SkString getName() const override { return SkString("radial_gradient"); } | |
| SkISize getISize() override { return {1280, 1280}; } | |
| void onDraw(SkCanvas* canvas) override { | |
| const SkISize dim = this->getISize(); | |
| canvas->drawColor(0xFF000000); | |
| SkPaint paint; | |
| paint.setDither(true); | |
| SkPoint center; | |
| center.set(SkIntToScalar(dim.width())/2, SkIntToScalar(dim.height())/2); | |
| SkScalar radius = SkIntToScalar(dim.width())/2; | |
| const SkScalar pos[] = { 0.0f, | |
| 0.35f, | |
| 1.0f }; | |
| SkColorConverter conv({ 0x7f7f7f7f, 0x7f7f7f7f, 0xb2000000 }); | |
| paint.setShader(SkShaders::RadialGradient(center, radius, | |
| {{conv.colors4f(), pos, SkTileMode::kClamp}, {}})); | |
| SkRect r = { | |
| 0, 0, SkIntToScalar(dim.width()), SkIntToScalar(dim.height()) | |
| }; | |
| canvas->drawRect(r, paint); | |
| } | |
| }; | |
| DEF_GM( return new RadialGradientGM; ) | |
| class RadialGradient2GM : public skiagm::GM { | |
| public: | |
| RadialGradient2GM(bool dither) : fDither(dither) {} | |
| private: | |
| SkString getName() const override { | |
| return SkString(fDither ? "radial_gradient2" : "radial_gradient2_nodither"); | |
| } | |
| SkISize getISize() override { return {800, 400}; } | |
| // Reproduces the example given in b/7671058. | |
| void onDraw(SkCanvas* canvas) override { | |
| SkPaint paint1, paint2, paint3; | |
| paint1.setStyle(SkPaint::kFill_Style); | |
| paint2.setStyle(SkPaint::kFill_Style); | |
| paint3.setStyle(SkPaint::kFill_Style); | |
| const SkColor4f sweep_colors[] = | |
| { {1,0,0,1}, {1,1,0,1}, {0,1,0,1}, {0,1,1,1}, {0,0,1,1}, {1,0,1,1}, {1,0,0,1} }; | |
| const SkColor4f colors1[] = { {1,1,1,1}, {0,0,0,0} }; | |
| const SkColor4f colors2[] = { {0,0,0,1}, {0,0,0,0} }; | |
| const SkScalar cx = 200, cy = 200, radius = 150; | |
| SkPoint center; | |
| center.set(cx, cy); | |
| // We can either interpolate endpoints and premultiply each point (default, more precision), | |
| // or premultiply the endpoints first, avoiding the need to premultiply each point (cheap). | |
| static constexpr auto flags = std::to_array<SkGradient::Interpolation::InPremul>({ | |
| SkGradient::Interpolation::InPremul::kNo, | |
| SkGradient::Interpolation::InPremul::kYes, | |
| }); | |
| const SkTileMode tm = SkTileMode::kClamp; | |
| for (size_t i = 0; i < std::size(flags); i++) { | |
| SkGradient::Interpolation terp{flags[i]}; | |
| paint1.setShader(SkShaders::SweepGradient(center, {{sweep_colors, {}, tm}, terp})); | |
| paint2.setShader(SkShaders::RadialGradient(center, radius, {{colors1, {}, tm}, terp})); | |
| paint3.setShader(SkShaders::RadialGradient(center, radius, {{colors2, {}, tm}, terp})); | |
| paint1.setDither(fDither); | |
| paint2.setDither(fDither); | |
| paint3.setDither(fDither); | |
| canvas->drawCircle(cx, cy, radius, paint1); | |
| canvas->drawCircle(cx, cy, radius, paint3); | |
| canvas->drawCircle(cx, cy, radius, paint2); | |
| canvas->translate(400, 0); | |
| } | |
| } | |
| private: | |
| bool fDither; | |
| using INHERITED = GM; | |
| }; | |
| DEF_GM( return new RadialGradient2GM(true); ) | |
| DEF_GM( return new RadialGradient2GM(false); ) | |
| // Shallow radial (shows banding on raster) | |
| class RadialGradient3GM : public skiagm::GM { | |
| public: | |
| RadialGradient3GM(bool dither) : fDither(dither) { } | |
| private: | |
| SkString getName() const override { | |
| return SkString(fDither ? "radial_gradient3" : "radial_gradient3_nodither"); | |
| } | |
| SkISize getISize() override { return {500, 500}; } | |
| bool runAsBench() const override { return true; } | |
| void onOnceBeforeDraw() override { | |
| const SkPoint center = { 0, 0 }; | |
| const SkScalar kRadius = 3000; | |
| const SkColor4f kColors[] = { {1,1,1,1}, {0,0,0,1} }; | |
| fShader = SkShaders::RadialGradient(center, kRadius, | |
| {{kColors, {}, SkTileMode::kClamp}, {}}); | |
| } | |
| void onDraw(SkCanvas* canvas) override { | |
| SkPaint paint; | |
| paint.setShader(fShader); | |
| paint.setDither(fDither); | |
| canvas->drawRect(SkRect::MakeWH(500, 500), paint); | |
| } | |
| private: | |
| sk_sp<SkShader> fShader; | |
| bool fDither; | |
| using INHERITED = GM; | |
| }; | |
| DEF_GM( return new RadialGradient3GM(true); ) | |
| DEF_GM( return new RadialGradient3GM(false); ) | |
| class RadialGradient4GM : public skiagm::GM { | |
| public: | |
| RadialGradient4GM(bool dither) : fDither(dither) { } | |
| private: | |
| SkString getName() const override { | |
| return SkString(fDither ? "radial_gradient4" : "radial_gradient4_nodither"); | |
| } | |
| SkISize getISize() override { return {500, 500}; } | |
| void onOnceBeforeDraw() override { | |
| const SkPoint center = { 250, 250 }; | |
| const SkScalar kRadius = 250; | |
| const SkColor4f colors[] = { | |
| SkColors::kRed, SkColors::kRed, SkColors::kWhite, SkColors::kWhite,SkColors::kRed }; | |
| const SkScalar pos[] = { 0, .4f, .4f, .8f, .8f }; | |
| fShader = SkShaders::RadialGradient(center, kRadius, | |
| {{colors, pos, SkTileMode::kClamp}, {}}); | |
| } | |
| void onDraw(SkCanvas* canvas) override { | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setDither(fDither); | |
| paint.setShader(fShader); | |
| canvas->drawRect(SkRect::MakeWH(500, 500), paint); | |
| } | |
| private: | |
| sk_sp<SkShader> fShader; | |
| bool fDither; | |
| using INHERITED = GM; | |
| }; | |
| DEF_GM( return new RadialGradient4GM(true); ) | |
| DEF_GM( return new RadialGradient4GM(false); ) | |
| class LinearGradientGM : public skiagm::GM { | |
| public: | |
| LinearGradientGM(bool dither) : fDither(dither) { } | |
| private: | |
| SkString getName() const override { | |
| return SkString(fDither ? "linear_gradient" : "linear_gradient_nodither"); | |
| } | |
| const SkScalar kWidthBump = 30.f; | |
| const SkScalar kHeight = 5.f; | |
| const SkScalar kMinWidth = 540.f; | |
| SkISize getISize() override { return {500, 500}; } | |
| void onOnceBeforeDraw() override { | |
| SkPoint pts[2] = { {0, 0}, {0, 0} }; | |
| const SkColor4f colors[] = { | |
| SkColors::kWhite, SkColors::kWhite, | |
| SkColor4f::FromColor(0xFF008200), SkColor4f::FromColor(0xFF008200), | |
| SkColors::kWhite, SkColors::kWhite, | |
| }; | |
| static constexpr auto unitPos = std::to_array<SkScalar>({0, 50, 70, 500, 540}); | |
| std::array<SkScalar, 6> pos; | |
| pos[5] = 1; | |
| for (int index = 0; index < (int) std::size(fShader); ++index) { | |
| pts[1].fX = 500.f + index * kWidthBump; | |
| for (int inner = 0; inner < (int) std::size(unitPos); ++inner) { | |
| pos[inner] = unitPos[inner] / (kMinWidth + index * kWidthBump); | |
| } | |
| fShader[index] = SkShaders::LinearGradient(pts, | |
| {{colors, pos, SkTileMode::kClamp}, {}}); | |
| } | |
| } | |
| void onDraw(SkCanvas* canvas) override { | |
| SkPaint paint; | |
| paint.setAntiAlias(true); | |
| paint.setDither(fDither); | |
| for (int index = 0; index < (int) std::size(fShader); ++index) { | |
| paint.setShader(fShader[index]); | |
| canvas->drawRect(SkRect::MakeLTRB(0, index * kHeight, kMinWidth + index * kWidthBump, | |
| (index + 1) * kHeight), paint); | |
| } | |
| } | |
| private: | |
| std::array<sk_sp<SkShader>, 100> fShader; | |
| bool fDither; | |
| using INHERITED = GM; | |
| }; | |
| DEF_GM( return new LinearGradientGM(true); ) | |
| DEF_GM( return new LinearGradientGM(false); ) | |
| class LinearGradientTinyGM : public skiagm::GM { | |
| SkString getName() const override { return SkString("linear_gradient_tiny"); } | |
| SkISize getISize() override { return {600, 500}; } | |
| void onDraw(SkCanvas* canvas) override { | |
| const SkScalar kRectSize = 100; | |
| const unsigned kStopCount = 3; | |
| const SkColor4f colors[kStopCount] = { SkColors::kGreen, SkColors::kRed, SkColors::kGreen }; | |
| struct Configs { | |
| SkPoint pts[2]; | |
| SkScalar pos[kStopCount]; | |
| }; | |
| static constexpr auto configs = std::to_array<Configs>({ | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(10, 0)}, {0, 0.999999f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(10, 0)}, {0, 0.000001f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(10, 0)}, {0, 0.999999999f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(10, 0)}, {0, 0.000000001f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(0, 10)}, {0, 0.999999f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(0, 10)}, {0, 0.000001f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(0, 10)}, {0, 0.999999999f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(0, 10)}, {0, 0.000000001f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(0.00001f, 0)}, {0, 0.5f, 1}}, | |
| Configs{{SkPoint::Make(9.99999f, 0), SkPoint::Make(10, 0)}, {0, 0.5f, 1}}, | |
| Configs{ {SkPoint::Make(0, 0), SkPoint::Make(0, 0.00001f)}, {0, 0.5f, 1}}, | |
| Configs{{SkPoint::Make(0, 9.99999f), SkPoint::Make(0, 10)}, {0, 0.5f, 1}}, | |
| }); | |
| SkPaint paint; | |
| for (unsigned i = 0; i < std::size(configs); ++i) { | |
| SkAutoCanvasRestore acr(canvas, true); | |
| paint.setShader(SkShaders::LinearGradient(configs[i].pts, | |
| {{colors, configs[i].pos, SkTileMode::kClamp}, | |
| {}})); | |
| canvas->translate(kRectSize * ((i % 4) * 1.5f + 0.25f), | |
| kRectSize * ((i / 4) * 1.5f + 0.25f)); | |
| canvas->drawRect(SkRect::MakeWH(kRectSize, kRectSize), paint); | |
| } | |
| } | |
| }; | |
| DEF_GM( return new LinearGradientTinyGM; ) | |
| } // namespace | |
| /////////////////////////////////////////////////////////////////////////////////////////////////// | |
| struct GradRun { | |
| SkColor4f fColors[4]; | |
| SkScalar fPos[4]; | |
| size_t fCount; | |
| SkGradient grad(SkTileMode tm) const { | |
| return {{{fColors, fCount}, {fPos, fCount}, tm}, {}}; | |
| } | |
| }; | |
| #define SIZE 121 | |
| static sk_sp<SkShader> make_linear(const GradRun& run, SkTileMode mode) { | |
| const SkPoint pts[] { { 30, 30 }, { SIZE - 30, SIZE - 30 } }; | |
| return SkShaders::LinearGradient(pts, run.grad(mode)); | |
| } | |
| static sk_sp<SkShader> make_radial(const GradRun& run, SkTileMode mode) { | |
| const SkScalar half = SIZE * 0.5f; | |
| return SkShaders::RadialGradient({half,half}, half - 10, run.grad(mode)); | |
| } | |
| static sk_sp<SkShader> make_conical(const GradRun& run, SkTileMode mode) { | |
| const SkScalar half = SIZE * 0.5f; | |
| const SkPoint center { half, half }; | |
| return SkShaders::TwoPointConicalGradient(center, 20, center, half - 10, run.grad(mode)); | |
| } | |
| static sk_sp<SkShader> make_sweep(const GradRun& run, SkTileMode mode) { | |
| const SkScalar half = SIZE * 0.5f; | |
| return SkShaders::SweepGradient({half, half}, run.grad(mode)); | |
| } | |
| /* | |
| * Exercise duplicate color-stops, at the ends, and in the middle | |
| * | |
| * At the time of this writing, only Linear correctly deals with duplicates at the ends, | |
| * and then only correctly on CPU backend. | |
| */ | |
| DEF_SIMPLE_GM(gradients_dup_color_stops, canvas, 704, 564) { | |
| const SkColor4f preColor = SkColors::kRed; // clamp color before start | |
| const SkColor4f postColor = SkColors::kBlue; // clamp color after end | |
| const SkColor4f color0 = SkColors::kBlack; | |
| const SkColor4f color1 = SkColors::kGreen; | |
| // should never be seen, fills out fixed-size array | |
| const SkColor4f badColor = SkColor4f::FromColor(0xFF3388BB); | |
| const GradRun runs[] = { | |
| { { color0, color1, badColor, badColor }, | |
| { 0, 1, -1, -1 }, | |
| 2, | |
| }, | |
| { { preColor, color0, color1, badColor }, | |
| { 0, 0, 1, -1 }, | |
| 3, | |
| }, | |
| { { color0, color1, postColor, badColor }, | |
| { 0, 1, 1, -1 }, | |
| 3, | |
| }, | |
| { { preColor, color0, color1, postColor }, | |
| { 0, 0, 1, 1 }, | |
| 4, | |
| }, | |
| { { color0, color0, color1, color1 }, | |
| { 0, 0.5f, 0.5f, 1 }, | |
| 4, | |
| }, | |
| }; | |
| sk_sp<SkShader> (*factories[])(const GradRun&, SkTileMode) { | |
| make_linear, make_radial, make_conical, make_sweep | |
| }; | |
| const SkRect rect = SkRect::MakeWH(SIZE, SIZE); | |
| const SkScalar dx = SIZE + 20; | |
| const SkScalar dy = SIZE + 20; | |
| const SkTileMode mode = SkTileMode::kClamp; | |
| SkPaint paint; | |
| canvas->translate(10, 10 - dy); | |
| for (auto factory : factories) { | |
| canvas->translate(0, dy); | |
| SkAutoCanvasRestore acr(canvas, true); | |
| for (const auto& run : runs) { | |
| paint.setShader(factory(run, mode)); | |
| canvas->drawRect(rect, paint); | |
| canvas->translate(dx, 0); | |
| } | |
| } | |
| } | |
| static void draw_many_stops(SkCanvas* canvas) { | |
| const unsigned kStopCount = 200; | |
| const SkPoint pts[] = { {50, 50}, {450, 450}}; | |
| std::array<SkColor4f, kStopCount> colors; | |
| for (unsigned i = 0; i < kStopCount; i++) { | |
| switch (i % 5) { | |
| case 0: colors[i] = SkColors::kRed; break; | |
| case 1: colors[i] = SkColors::kGreen; break; | |
| case 2: colors[i] = SkColors::kGreen; break; | |
| case 3: colors[i] = SkColors::kBlue; break; | |
| case 4: colors[i] = SkColors::kRed; break; | |
| } | |
| } | |
| SkPaint p; | |
| p.setShader(SkShaders::LinearGradient(pts, {{colors, {}, SkTileMode::kClamp}, {}})); | |
| canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p); | |
| } | |
| DEF_SIMPLE_GM(gradient_many_stops, canvas, 500, 500) { | |
| draw_many_stops(canvas); | |
| } | |
| static void draw_many_hard_stops(SkCanvas* canvas) { | |
| const unsigned kStopCount = 300; | |
| const SkPoint pts[] = {{50, 50}, {450, 450}}; | |
| std::array<SkColor4f, kStopCount> colors; | |
| std::array<SkScalar, kStopCount> pos; | |
| for (unsigned i = 0; i < kStopCount; i++) { | |
| switch (i % 6) { | |
| case 0: colors[i] = SkColors::kRed; break; | |
| case 1: colors[i] = SkColors::kGreen; break; | |
| case 2: colors[i] = SkColors::kGreen; break; | |
| case 3: colors[i] = SkColors::kBlue; break; | |
| case 4: colors[i] = SkColors::kBlue; break; | |
| case 5: colors[i] = SkColors::kRed; break; | |
| } | |
| pos[i] = (2.0f * (i / 2)) / kStopCount; | |
| } | |
| SkPaint p; | |
| p.setShader(SkShaders::LinearGradient(pts, {{colors, pos, SkTileMode::kClamp}, {}})); | |
| canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p); | |
| } | |
| DEF_SIMPLE_GM(gradient_many_hard_stops, canvas, 500, 500) { | |
| draw_many_hard_stops(canvas); | |
| } | |
| static void draw_circle_shader(SkCanvas* canvas, SkScalar cx, SkScalar cy, SkScalar r, | |
| sk_sp<SkShader> (*shaderFunc)()) { | |
| SkPaint p; | |
| p.setAntiAlias(true); | |
| p.setShader(shaderFunc()); | |
| canvas->drawCircle(cx, cy, r, p); | |
| p.setShader(nullptr); | |
| p.setColor(SK_ColorGRAY); | |
| p.setStyle(SkPaint::kStroke_Style); | |
| p.setStrokeWidth(2); | |
| canvas->drawCircle(cx, cy, r, p); | |
| } | |
| DEF_SIMPLE_GM(fancy_gradients, canvas, 800, 300) { | |
| draw_circle_shader(canvas, 150, 150, 100, []() -> sk_sp<SkShader> { | |
| // Checkerboard using two linear gradients + picture shader. | |
| SkScalar kTileSize = 80 / sqrtf(2); | |
| SkColor4f colors1[] = { {0,0,0,1}, {0,0,0,1}, | |
| {1,1,1,1}, {1,1,1,1}, | |
| {0,0,0,1}, {0,0,0,1} }; | |
| SkColor4f colors2[] = { {0,0,0,1}, {0,0,0,1}, | |
| {0,0,0,0}, {0,0,0,0}, | |
| {0,0,0,1}, {0,0,0,1} }; | |
| SkScalar pos[] = { 0, .25f, .25f, .75f, .75f, 1 }; | |
| static_assert(std::size(colors1) == std::size(pos), "color/pos size mismatch"); | |
| static_assert(std::size(colors2) == std::size(pos), "color/pos size mismatch"); | |
| SkPictureRecorder recorder; | |
| recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize)); | |
| SkPaint p; | |
| SkPoint pts1[] = { { 0, 0 }, { kTileSize, kTileSize }}; | |
| p.setShader(SkShaders::LinearGradient(pts1, {{colors1, pos, SkTileMode::kClamp}, {}})); | |
| recorder.getRecordingCanvas()->drawPaint(p); | |
| SkPoint pts2[] = { { 0, kTileSize }, { kTileSize, 0 }}; | |
| p.setShader(SkShaders::LinearGradient(pts2, {{colors2, pos, SkTileMode::kClamp}, {}})); | |
| recorder.getRecordingCanvas()->drawPaint(p); | |
| SkMatrix m = SkMatrix::I(); | |
| m.preRotate(45); | |
| return recorder.finishRecordingAsPicture()->makeShader( | |
| SkTileMode::kRepeat, SkTileMode::kRepeat, | |
| SkFilterMode::kNearest, &m, nullptr); | |
| }); | |
| draw_circle_shader(canvas, 400, 150, 100, []() -> sk_sp<SkShader> { | |
| // Checkerboard using a sweep gradient + picture shader. | |
| SkScalar kTileSize = 80; | |
| SkColor4f colors[] = { {0,0,0,1}, {0,0,0,1}, | |
| {1,1,1,1}, {1,1,1,1}, | |
| {0,0,0,1}, {0,0,0,1}, | |
| {1,1,1,1}, {1,1,1,1} }; | |
| SkScalar pos[] = { 0, .25f, .25f, .5f, .5f, .75f, .75f, 1 }; | |
| static_assert(std::size(colors) == std::size(pos), "color/pos size mismatch"); | |
| SkPaint p; | |
| p.setShader(SkShaders::SweepGradient({kTileSize / 2, kTileSize / 2}, | |
| {{colors, pos, SkTileMode::kClamp}, {}})); | |
| SkPictureRecorder recorder; | |
| recorder.beginRecording(SkRect::MakeWH(kTileSize, kTileSize))->drawPaint(p); | |
| return recorder.finishRecordingAsPicture()->makeShader( | |
| SkTileMode::kRepeat, | |
| SkTileMode::kRepeat, SkFilterMode::kNearest); | |
| }); | |
| draw_circle_shader(canvas, 650, 150, 100, []() -> sk_sp<SkShader> { | |
| // Dartboard using sweep + radial. | |
| const SkColor4f a = {1,1,1,1}; | |
| const SkColor4f b = {0,0,0,1}; | |
| SkColor4f colors[] = { a, a, b, b, a, a, b, b, a, a, b, b, a, a, b, b}; | |
| SkScalar pos[] = { 0, .125f, .125f, .25f, .25f, .375f, .375f, .5f, .5f, | |
| .625f, .625f, .75f, .75f, .875f, .875f, 1}; | |
| static_assert(std::size(colors) == std::size(pos), "color/pos size mismatch"); | |
| SkPoint center = { 650, 150 }; | |
| sk_sp<SkShader> sweep1 = SkShaders::SweepGradient(center, | |
| {{colors, pos, SkTileMode::kClamp}, {}}); | |
| SkMatrix m = SkMatrix::I(); | |
| m.preRotate(22.5f, center.x(), center.y()); | |
| sk_sp<SkShader> sweep2 = SkShaders::SweepGradient(center, | |
| {{colors, pos, SkTileMode::kClamp}, {}}, &m); | |
| sk_sp<SkShader> sweep(SkShaders::Blend(SkBlendMode::kExclusion, sweep1, sweep2)); | |
| SkScalar radialPos[] = { 0, .02f, .02f, .04f, .04f, .08f, .08f, .16f, .16f, .31f, .31f, | |
| .62f, .62f, 1, 1, 1 }; | |
| static_assert(std::size(colors) == std::size(radialPos), | |
| "color/pos size mismatch"); | |
| return SkShaders::Blend(SkBlendMode::kExclusion, sweep, | |
| SkShaders::RadialGradient(center, 100, | |
| {{colors, radialPos, SkTileMode::kClamp}, {}})); | |
| }); | |
| } | |
| DEF_SIMPLE_GM(sweep_tiling, canvas, 690, 512) { | |
| static constexpr SkScalar size = 160; | |
| static constexpr SkColor4f colors[] = { SkColors::kBlue, SkColors::kYellow, SkColors::kGreen }; | |
| static constexpr SkScalar pos[] = { 0, .25f, .50f }; | |
| static_assert(std::size(colors) == std::size(pos), "size mismatch"); | |
| static constexpr SkTileMode modes[] = { SkTileMode::kClamp, | |
| SkTileMode::kRepeat, | |
| SkTileMode::kMirror }; | |
| static const struct { | |
| SkScalar start, end; | |
| } angles[] = { | |
| { -330, -270 }, | |
| { 30, 90 }, | |
| { 390, 450 }, | |
| { -30, 800 }, | |
| }; | |
| SkPaint p; | |
| const SkRect r = SkRect::MakeWH(size, size); | |
| for (auto mode : modes) { | |
| { | |
| SkAutoCanvasRestore acr(canvas, true); | |
| for (auto angle : angles) { | |
| p.setShader(SkShaders::SweepGradient({size / 2, size / 2}, angle.start, angle.end, | |
| {{colors, pos, mode}, {}})); | |
| canvas->drawRect(r, p); | |
| canvas->translate(size * 1.1f, 0); | |
| } | |
| } | |
| canvas->translate(0, size * 1.1f); | |
| } | |
| } | |
| DEF_SIMPLE_GM(rgbw_sweep_gradient, canvas, 100, 100) { | |
| static constexpr SkScalar size = 100; | |
| static constexpr SkColor4f colors[] = {SkColors::kWhite, SkColors::kWhite, | |
| SkColors::kBlue, SkColors::kBlue, | |
| SkColors::kRed, SkColors::kRed, | |
| SkColors::kGreen, SkColors::kGreen}; | |
| static constexpr SkScalar pos[] = { 0, .25f, .25f, .50f, .50f, .75, .75, 1 }; | |
| static_assert(std::size(colors) == std::size(pos), "size mismatch"); | |
| SkPaint p; | |
| p.setShader(SkShaders::SweepGradient({size / 2, size / 2}, | |
| {{colors, pos, SkTileMode::kClamp}, {}})); | |
| canvas->drawRect(SkRect::MakeWH(size, size), p); | |
| } | |
| // Exercises the special-case Ganesh gradient effects. | |
| DEF_SIMPLE_GM(gradients_interesting, canvas, 640, 1300) { | |
| static const SkColor4f colors2[] = { SkColors::kRed, SkColors::kBlue }; | |
| static const SkColor4f colors3[] = { SkColors::kRed, SkColors::kYellow, SkColors::kBlue }; | |
| static const SkColor4f colors4[] = { | |
| SkColors::kRed, SkColors::kYellow, SkColors::kYellow, SkColors::kBlue }; | |
| static const SkScalar softRight[] = { 0, .999f, 1 }; // Based on Android launcher "clipping" | |
| static const SkScalar hardLeft[] = { 0, 0, 1 }; | |
| static const SkScalar hardRight[] = { 0, 1, 1 }; | |
| static const SkScalar hardCenter[] = { 0, .5f, .5f, 1 }; | |
| static const struct { | |
| SkSpan<const SkColor4f> colors; | |
| SkSpan<const float> pos; | |
| } configs[] = { | |
| { colors2, {} }, // kTwo_ColorType | |
| { colors3, {} }, // kThree_ColorType (simple) | |
| { colors3, softRight }, // kThree_ColorType (tricky) | |
| { colors3, hardLeft }, // kHardStopLeftEdged_ColorType | |
| { colors3, hardRight }, // kHardStopRightEdged_ColorType | |
| { colors4, hardCenter }, // kSingleHardStop_ColorType | |
| }; | |
| static const SkTileMode modes[] = { | |
| SkTileMode::kClamp, | |
| SkTileMode::kRepeat, | |
| SkTileMode::kMirror, | |
| }; | |
| static constexpr SkScalar size = 200; | |
| static const SkPoint pts[] = { { size / 3, size / 3 }, { size * 2 / 3, size * 2 / 3} }; | |
| SkPaint p; | |
| for (const auto& cfg : configs) { | |
| { | |
| SkAutoCanvasRestore acr(canvas, true); | |
| for (auto mode : modes) { | |
| SkGradient grad = {{cfg.colors, cfg.pos, mode}, {}}; | |
| p.setShader(SkShaders::LinearGradient(pts, grad)); | |
| canvas->drawRect(SkRect::MakeWH(size, size), p); | |
| canvas->translate(size * 1.1f, 0); | |
| } | |
| } | |
| canvas->translate(0, size * 1.1f); | |
| } | |
| } | |
| // TODO(skbug.com/40044214): Still need to test degenerate gradients in strange color spaces | |
| DEF_SIMPLE_GM_BG(gradients_color_space, canvas, 265, 355, SK_ColorGRAY) { | |
| using CS = SkGradient::Interpolation::ColorSpace; | |
| struct Config { | |
| CS fColorSpace; | |
| const char* fLabel; | |
| }; | |
| static const Config kConfigs[] = { | |
| {CS::kSRGB, "sRGB"}, | |
| {CS::kSRGBLinear, "Linear"}, | |
| {CS::kLab, "Lab"}, | |
| {CS::kOKLab, "OKLab"}, | |
| {CS::kOKLabGamutMap, "OKLabGamutMap"}, | |
| {CS::kLCH, "LCH"}, | |
| {CS::kOKLCH, "OKLCH"}, | |
| {CS::kOKLCHGamutMap, "OKLCHGamutMap"}, | |
| {CS::kHSL, "HSL"}, | |
| {CS::kHWB, "HWB"}, | |
| {CS::kA98RGB, "a98RGB"}, | |
| {CS::kProphotoRGB, "ProPhotoRGB"}, | |
| {CS::kDisplayP3, "DisplayP3"}, | |
| {CS::kRec2020, "Rec2020"}, | |
| }; | |
| SkPoint pts[] = {{0, 0}, {200, 0}}; | |
| SkColor4f colors[] = {SkColors::kBlue, SkColors::kYellow}; | |
| SkPaint labelPaint; | |
| SkPaint p; | |
| SkGradient::Interpolation interpolation; | |
| canvas->translate(5, 5); | |
| SkFont font = ToolUtils::DefaultPortableFont(); | |
| for (const Config& config : kConfigs) { | |
| interpolation.fColorSpace = config.fColorSpace; | |
| SkGradient g = {{colors, {}, SkTileMode::kClamp, SkColorSpace::MakeSRGB()}, interpolation}; | |
| p.setShader(SkShaders::LinearGradient(pts, g)); | |
| canvas->drawRect({0, 0, 200, 20}, p); | |
| canvas->drawSimpleText(config.fLabel, strlen(config.fLabel), SkTextEncoding::kUTF8, 210, 15, | |
| font, labelPaint); | |
| canvas->translate(0, 25); | |
| } | |
| } | |
| DEF_SIMPLE_GM_BG(gradients_hue_method, canvas, 285, 155, SK_ColorGRAY) { | |
| using HM = SkGradient::Interpolation::HueMethod; | |
| struct Config { | |
| HM fHueMethod; | |
| const char* fLabel; | |
| }; | |
| static const Config kConfigs[] = { | |
| { HM::kShorter, "Shorter" }, | |
| { HM::kLonger, "Longer" }, | |
| { HM::kIncreasing, "Increasing" }, | |
| { HM::kDecreasing, "Decreasing" }, | |
| }; | |
| SkPoint pts[] = {{0, 0}, {200, 0}}; | |
| SkColor4f colors[] = {SkColors::kRed, SkColors::kGreen, SkColors::kRed, SkColors::kRed }; | |
| SkPaint labelPaint; | |
| SkPaint p; | |
| SkGradient::Interpolation interpolation; | |
| interpolation.fColorSpace = SkGradient::Interpolation::ColorSpace::kHSL; | |
| canvas->translate(5, 5); | |
| SkFont font = ToolUtils::DefaultPortableFont(); | |
| for (const Config& config : kConfigs) { | |
| interpolation.fHueMethod = config.fHueMethod; | |
| SkGradient g = {{colors, {}, SkTileMode::kClamp, SkColorSpace::MakeSRGB()}, interpolation}; | |
| p.setShader(SkShaders::LinearGradient(pts, g)); | |
| canvas->drawRect({0, 0, 200, 20}, p); | |
| canvas->drawSimpleText(config.fLabel, strlen(config.fLabel), SkTextEncoding::kUTF8, 210, 15, | |
| font, labelPaint); | |
| canvas->translate(0, 25); | |
| } | |
| // Test a bug (skbug.com/40044215) with how gradient shaders handle explicit positions. | |
| // If there are no explicit positions at 0 or 1, those are automatically added, with copies of | |
| // the first/last color. When using kLonger, this can produce extra gradient that should | |
| // actually be solid. This gradient *should* be: | |
| // |- solid red -|- red to green, the long way -|- solid green -| | |
| interpolation.fHueMethod = HM::kLonger; | |
| SkScalar middlePos[] = { 0.3f, 0.7f }; | |
| SkGradient g = {{{colors, 2}, middlePos, SkTileMode::kClamp, SkColorSpace::MakeSRGB()}, | |
| interpolation}; | |
| p.setShader(SkShaders::LinearGradient(pts, g)); | |
| canvas->drawRect({0, 0, 200, 20}, p); | |
| canvas->translate(0, 25); | |
| // However... if the user explicitly includes those duplicate color stops in kLonger mode, | |
| // we expect the gradient to do a full rotation in those regions: | |
| // |- full circle, red to red -|- red to green -|- full circle, green to green -| | |
| colors[0] = colors[1] = SkColors::kRed; | |
| colors[2] = colors[3] = SkColors::kGreen; | |
| SkScalar allPos[] = { 0.0f, 0.3f, 0.7f, 1.0f }; | |
| g = {{colors, allPos, SkTileMode::kClamp, SkColorSpace::MakeSRGB()}, interpolation}; | |
| p.setShader(SkShaders::LinearGradient(pts, g)); | |
| canvas->drawRect({0, 0, 200, 20}, p); | |
| canvas->translate(0, 25); | |
| } | |
| DEF_SIMPLE_GM_BG(gradients_color_space_tilemode, canvas, 360, 105, SK_ColorGRAY) { | |
| // Test exotic (CSS) gradient color spaces in conjunction with tile modes. Rather than test | |
| // every combination, we pick one color space that has a sufficiently strange interpolated | |
| // representation (OKLCH) and just use that. We're mostly interested in making sure that things | |
| // like decal mode are implemented at the correct time in the pipeline, relative to hue | |
| // conversion, re-premultiplication, etc. | |
| SkPoint pts[] = {{20, 0}, {120, 0}}; | |
| SkColor4f colors[] = {SkColors::kBlue, SkColors::kYellow}; | |
| SkPaint p; | |
| SkGradient::Interpolation interpolation; | |
| interpolation.fColorSpace = SkGradient::Interpolation::ColorSpace::kOKLCH; | |
| canvas->translate(5, 5); | |
| for (int tm = 0; tm < kSkTileModeCount; ++tm) { | |
| SkGradient g = {{colors, {}, static_cast<SkTileMode>(tm)}, interpolation}; | |
| p.setShader(SkShaders::LinearGradient(pts, g)); | |
| canvas->drawRect({0, 0, 350, 20}, p); | |
| canvas->translate(0, 25); | |
| } | |
| } | |
| DEF_SIMPLE_GM_BG(gradients_color_space_many_stops, canvas, 500, 500, SK_ColorGRAY) { | |
| // Test exotic (CSS) gradient color spaces with many stops. Rather than test every combination, | |
| // we pick one color space that has a sufficiently strange interpolated representation (OKLCH) | |
| // and just use that. We're mostly interested in making sure that the texture fallback on GPU | |
| // works correctly. | |
| const SkPoint pts[] = { {50, 50}, {450, 465}}; | |
| const unsigned kStopCount = 200; | |
| std::array<SkColor4f, kStopCount> colors; | |
| for (unsigned i = 0; i < kStopCount; i++) { | |
| switch (i % 5) { | |
| case 0: colors[i] = SkColors::kRed; break; | |
| case 1: colors[i] = SkColors::kGreen; break; | |
| case 2: colors[i] = SkColors::kGreen; break; | |
| case 3: colors[i] = SkColors::kBlue; break; | |
| case 4: colors[i] = SkColors::kRed; break; | |
| } | |
| } | |
| SkPaint p; | |
| SkGradient::Interpolation interpolation; | |
| interpolation.fColorSpace = SkGradient::Interpolation::ColorSpace::kOKLCH; | |
| p.setShader(SkShaders::LinearGradient(pts, | |
| {{colors, {}, SkTileMode::kClamp, SkColorSpace::MakeSRGB()}, interpolation})); | |
| canvas->drawRect(SkRect::MakeXYWH(0, 0, 500, 500), p); | |
| } | |
| DEF_SIMPLE_GM(gradients_alpha_many_stops, canvas, 100, 100) { | |
| static const SkPoint kPts[] = {{0.f, 0.f}, {0.f, 100.f}}; | |
| // From https://issues.chromium.org/issues/401546700, this encounters Graphite's | |
| // storage buffer option for storing gradient buffers AND uses colors that emphasize | |
| // premul vs. unpremul handling of the color data. | |
| static const float kPos[] = {0.f, 0.19f, 0.34f, 0.47f, 0.565f, 0.65f, | |
| 0.73f, 0.802f, 0.861f, 0.91f, 0.952f, 0.982f, 1.f}; | |
| static constexpr float kG = 34 / 255.f; | |
| static const SkColor4f kColors[] = {{kG, kG, kG, 1.f}, | |
| {kG, kG, kG, 0.738f}, | |
| {kG, kG, kG, 0.541f}, | |
| {kG, kG, kG, 0.382f}, | |
| {kG, kG, kG, 0.278f}, | |
| {kG, kG, kG, 0.194f}, | |
| {kG, kG, kG, 0.126f}, | |
| {kG, kG, kG, 0.075f}, | |
| {kG, kG, kG, 0.042f}, | |
| {kG, kG, kG, 0.021f}, | |
| {kG, kG, kG, 0.008f}, | |
| {kG, kG, kG, 0.002f}, | |
| {kG, kG, kG, 0.f}}; | |
| canvas->clear(SkColor4f{0.5f, 0.5f, 0.5f, 1.f}); | |
| SkPaint paint; | |
| paint.setShader(SkShaders::LinearGradient(kPts, {{kColors, kPos, SkTileMode::kClamp}, {}})); | |
| canvas->drawPaint(paint); | |
| } | |
| static void draw_powerless_hue_gradients(SkCanvas* canvas, | |
| SkGradient::Interpolation::ColorSpace colorSpace) { | |
| ToolUtils::draw_checkerboard(canvas); | |
| auto nextRow = [=]() { | |
| canvas->restore(); | |
| canvas->translate(0, 25); | |
| canvas->save(); | |
| }; | |
| auto gradient = [&](std::initializer_list<SkColor4f> colors, | |
| std::initializer_list<float> pos, | |
| bool inPremul = false) { | |
| using Interpolation = SkGradient::Interpolation; | |
| SkASSERT(pos.size() == 0 || pos.size() == colors.size()); | |
| SkPaint paint; | |
| SkPoint pts[] = {{0, 0}, {200, 0}}; | |
| Interpolation interpolation; | |
| interpolation.fColorSpace = colorSpace; | |
| interpolation.fInPremul = static_cast<Interpolation::InPremul>(inPremul); | |
| paint.setShader(SkShaders::LinearGradient(pts, {{colors, pos, SkTileMode::kClamp}, | |
| interpolation})); | |
| canvas->drawRect({0, 0, 200, 20}, paint); | |
| canvas->translate(205, 0); // next column | |
| }; | |
| canvas->translate(5, 5); | |
| canvas->save(); | |
| // For each test case, the first gradient (first column) has an under-specified result due to a | |
| // powerless component after conversion to LCH. The second gradient (second column) "hints" the | |
| // correct result, by slightly tinting the otherwise powerless color. | |
| gradient({SkColors::kWhite, SkColors::kBlue}, {}); | |
| gradient({{0.99f, 0.99f, 1.00f, 1.0f}, SkColors::kBlue}, {}); // white, with blue hue | |
| nextRow(); | |
| gradient({SkColors::kBlack, SkColors::kBlue}, {}); | |
| gradient({{0.00f, 0.00f, 0.01f, 1.0f}, SkColors::kBlue}, {}); // black, with blue hue | |
| nextRow(); | |
| // Transparent cases are done in both premul and unpremul interpolation: | |
| gradient({SkColors::kTransparent, SkColors::kBlue}, {}, /*inPremul=*/false); | |
| gradient({{0.00f, 0.00f, 0.01f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/false); | |
| nextRow(); | |
| gradient({SkColors::kTransparent, SkColors::kBlue}, {}, /*inPremul=*/true); | |
| gradient({{0.00f, 0.00f, 0.01f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/true); | |
| nextRow(); | |
| gradient({{1.00f, 1.00f, 1.00f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/false); | |
| gradient({{0.99f, 0.99f, 1.00f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/false); | |
| nextRow(); | |
| gradient({{1.00f, 1.00f, 1.00f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/true); | |
| gradient({{0.99f, 0.99f, 1.00f, 0.0f}, SkColors::kBlue}, {}, /*inPremul=*/true); | |
| nextRow(); | |
| // Now we test three-stop gradients, where the middle stop needs to be "split" to handle the | |
| // different hues on either side. Again, the second column explicitly injects those to produce | |
| // a reference result. See: https://github.com/w3c/csswg-drafts/issues/9295 | |
| gradient({SkColors::kRed, SkColors::kWhite, SkColors::kBlue}, {}); | |
| gradient({SkColors::kRed, | |
| {1.00f, 0.99f, 0.99f, 1.0f}, | |
| {0.99f, 0.99f, 1.00f, 1.0f}, | |
| SkColors::kBlue}, | |
| {0.0f, 0.5f, 0.5f, 1.0f}); | |
| nextRow(); | |
| gradient({SkColors::kRed, SkColors::kBlack, SkColors::kBlue}, {}); | |
| gradient({SkColors::kRed, | |
| {0.01f, 0.00f, 0.00f, 1.0f}, | |
| {0.00f, 0.00f, 0.01f, 1.0f}, | |
| SkColors::kBlue}, | |
| {0.0f, 0.5f, 0.5f, 1.0f}); | |
| nextRow(); | |
| gradient({SkColors::kRed, SkColors::kTransparent, SkColors::kBlue}, {}); | |
| gradient({SkColors::kRed, | |
| {0.01f, 0.00f, 0.00f, 0.0f}, | |
| {0.00f, 0.00f, 0.01f, 0.0f}, | |
| SkColors::kBlue}, | |
| {0.0f, 0.5f, 0.5f, 1.0f}); | |
| nextRow(); | |
| // Now do a few black-white tests, to ensure that the hue propagation works correctly, even | |
| // when there isn't any hue in the adjacent stops. | |
| using HueMethod = SkGradient::Interpolation::HueMethod; | |
| auto blackWhiteGradient = [&](HueMethod hm) { | |
| using Interpolation = SkGradient::Interpolation; | |
| SkPaint paint; | |
| SkPoint pts[] = {{0, 0}, {405, 0}}; | |
| Interpolation interpolation; | |
| interpolation.fColorSpace = colorSpace; | |
| interpolation.fHueMethod = hm; | |
| const SkColor4f colors[] = {SkColors::kWhite, SkColors::kGray, | |
| SkColors::kWhite, SkColors::kDkGray, | |
| SkColors::kWhite, SkColors::kBlack}; | |
| paint.setShader(SkShaders::LinearGradient(pts, {{colors, {}, SkTileMode::kClamp}, | |
| interpolation})); | |
| canvas->drawRect({0, 0, 405, 20}, paint); | |
| nextRow(); | |
| }; | |
| blackWhiteGradient(HueMethod::kShorter); | |
| blackWhiteGradient(HueMethod::kIncreasing); | |
| blackWhiteGradient(HueMethod::kDecreasing); | |
| blackWhiteGradient(HueMethod::kLonger); | |
| } | |
| #define DEF_POWERLESS_HUE_GM(colorSpace) \ | |
| DEF_SIMPLE_GM(gradients_powerless_hue_##colorSpace, canvas, 415, 330) { \ | |
| draw_powerless_hue_gradients(canvas, \ | |
| SkGradient::Interpolation::ColorSpace::k##colorSpace); \ | |
| } | |
| DEF_POWERLESS_HUE_GM(LCH) | |
| DEF_POWERLESS_HUE_GM(OKLCH) | |
| DEF_POWERLESS_HUE_GM(HSL) | |
| DEF_POWERLESS_HUE_GM(HWB) |