From a873629e09beefc4f17e2bb4273a481a703cadba Mon Sep 17 00:00:00 2001 From: xuty Date: Thu, 16 Mar 2023 20:38:45 +0800 Subject: [PATCH 1/9] [web] Fix rendering of gradients in html mode --- .../lib/src/engine/html/shaders/shader.dart | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index ac83b020c8b75..45c6ccc141826 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -98,13 +98,22 @@ class GradientSweep extends EngineGradient { final double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width); final double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height); gl.setUniform2f(tileOffset, 2 * (shaderBounds.width * (centerX - 0.5)), - 2 * (shaderBounds.height * (centerY - 0.5))); + 2 * (shaderBounds.height * (0.5 - centerY))); final Object angleRange = gl.getUniformLocation(glProgram.program, 'angle_range'); gl.setUniform2f(angleRange, startAngle, endAngle); normalizedGradient.setupUniforms(gl, glProgram); + final Object gradientMatrix = gl.getUniformLocation(glProgram.program, 'm_gradient'); - gl.setUniformMatrix4fv(gradientMatrix, false, matrix4 ?? Matrix4.identity().storage); + final Matrix4 gradientTransform = Matrix4.identity(); + if (matrix4 != null) { + final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!)..invert(); + gradientTransform.translate(-center.dx, -center.dy); + gradientTransform.multiply(m4); + gradientTransform.translate(center.dx, center.dy); + } + gl.setUniformMatrix4fv(gradientMatrix, false, gradientTransform.storage); + final Object result = () { if (createDataUrl) { return glRenderer!.drawRectToImageUrl( @@ -149,7 +158,7 @@ class GradientSweep extends EngineGradient { // Sweep gradient method.addStatement('vec2 center = 0.5 * (u_resolution + u_tile_offset);'); method.addStatement( - 'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;'); + 'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);'); method.addStatement( 'float angle = atan(-localCoord.y, -localCoord.x) + ${math.pi};'); method.addStatement('float sweep = angle_range.y - angle_range.x;'); @@ -201,7 +210,7 @@ class GradientLinear extends EngineGradient { @override Object createPaintStyle(DomCanvasRenderingContext2D? ctx, ui.Rect? shaderBounds, double density) { - if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) { + if (matrix4 == null && (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal)) { return _createCanvasGradient(ctx, shaderBounds, density); } else { return _createGlGradient(ctx, shaderBounds, density); @@ -315,14 +324,11 @@ class GradientLinear extends EngineGradient { // with flipped y axis. // We flip y axis, translate to center, multiply matrix and translate // and flip back so it is applied correctly. - final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!.matrix); - gradientTransform.scale(1, -1); - gradientTransform.translate( - -shaderBounds.center.dx, -shaderBounds.center.dy); + final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!.matrix)..invert(); + final ui.Offset center = shaderBounds.center; + gradientTransform.translate(-center.dx, -center.dy); gradientTransform.multiply(m4); - gradientTransform.translate( - shaderBounds.center.dx, shaderBounds.center.dy); - gradientTransform.scale(1, -1); + gradientTransform.translate(center.dx, center.dy); } gradientTransform.multiply(rotationZ); @@ -466,6 +472,12 @@ String _writeSharedGradientShader(ShaderBuilder builder, ShaderMethod method, sourcePrefix: 'threshold', biasName: 'bias', scaleName: 'scale'); + if (tileMode == ui.TileMode.decal) { + method.addStatement('if (st < 0.0 || st > 1.0) {'); + method.addStatement(' ${builder.fragmentColor.name} = vec4(0, 0, 0, 0);'); + method.addStatement(' return;'); + method.addStatement('}'); + } return probeName; } @@ -484,7 +496,7 @@ class GradientRadial extends EngineGradient { @override Object createPaintStyle(DomCanvasRenderingContext2D? ctx, ui.Rect? shaderBounds, double density) { - if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) { + if (matrix4 == null && (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal)) { return _createCanvasGradient(ctx, shaderBounds, density); } else { return _createGlGradient(ctx, shaderBounds, density); @@ -534,15 +546,23 @@ class GradientRadial extends EngineGradient { final double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width); final double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height); gl.setUniform2f(tileOffset, 2 * (shaderBounds.width * (centerX - 0.5)), - 2 * (shaderBounds.height * (centerY - 0.5))); + 2 * (shaderBounds.height * (0.5 - centerY))); final Object radiusUniform = gl.getUniformLocation(glProgram.program, 'u_radius'); gl.setUniform1f(radiusUniform, radius); normalizedGradient.setupUniforms(gl, glProgram); final Object gradientMatrix = gl.getUniformLocation(glProgram.program, 'm_gradient'); - gl.setUniformMatrix4fv(gradientMatrix, false, - matrix4 == null ? Matrix4.identity().storage : matrix4!); + + final Matrix4 gradientTransform = Matrix4.identity(); + + if (matrix4 != null) { + final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!)..invert(); + gradientTransform.translate(-center.dx, -center.dy); + gradientTransform.multiply(m4); + gradientTransform.translate(center.dx, center.dy); + } + gl.setUniformMatrix4fv(gradientMatrix, false, gradientTransform.storage); final Object result = () { if (createDataUrl) { @@ -588,7 +608,7 @@ class GradientRadial extends EngineGradient { // Sweep gradient method.addStatement('vec2 center = 0.5 * (u_resolution + u_tile_offset);'); method.addStatement( - 'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;'); + 'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);'); method.addStatement('float dist = length(localCoord);'); method.addStatement( 'float st = abs(dist / u_radius);'); @@ -667,7 +687,7 @@ class GradientConical extends GradientRadial { // Sweep gradient method.addStatement('vec2 center = 0.5 * (u_resolution + u_tile_offset);'); method.addStatement( - 'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;'); + 'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);'); method.addStatement('float dist = length(localCoord);'); final String f = (focalRadius / (math.min(shaderBounds.width, shaderBounds.height) / 2.0)) From d2ad57884fb2105c4a75f2518389b94e7739b8b7 Mon Sep 17 00:00:00 2001 From: xuty Date: Thu, 16 Mar 2023 21:08:05 +0800 Subject: [PATCH 2/9] Add golden tests --- .../html/shaders/gradient_golden_test.dart | 182 ++++++++++++++++++ 1 file changed, 182 insertions(+) diff --git a/lib/web_ui/test/html/shaders/gradient_golden_test.dart b/lib/web_ui/test/html/shaders/gradient_golden_test.dart index 5aebc78ef0070..9fc28a26fa86b 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -469,6 +469,176 @@ Future testMain() async { canvas.restore(); await canvasScreenshot(canvas, 'linear_gradient_in_svg_context', canvasRect: screenRect, region: region); }); + + test('Paints transformed linear gradient', () async { + final RecordingCanvas canvas = + RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300)); + canvas.save(); + + const List colors = [ + Color(0xFF000000), + Color(0xFFFF3C38), + Color(0xFFFF8C42), + Color(0xFFFFF275), + Color(0xFF6699CC), + Color(0xFF656D78), + ]; + + const List stops = [0.0, 0.05, 0.4, 0.6, 0.9, 1.0]; + + final Matrix4 transform = Matrix4.identity() + ..translate(100, 150) + ..scale(0.3, 0.7) + ..rotateZ(0.5); + + final GradientLinear linearGradient = GradientLinear( + const Offset(50, 50), + const Offset(200, 130), + colors, + stops, + TileMode.clamp, + transform.storage, + ); + + const double kBoxWidth = 150; + const double kBoxHeight = 80; + + Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight); + canvas.drawRect( + rectBounds, + SurfacePaint() + ..shader = engineLinearGradientToShader(linearGradient, rectBounds), + ); + + rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight); + canvas.drawOval( + rectBounds, + SurfacePaint() + ..shader = engineLinearGradientToShader(linearGradient, rectBounds), + ); + + canvas.restore(); + await canvasScreenshot( + canvas, + 'linear_gradient_clamp_transformed', + canvasRect: screenRect, + region: region, + ); + }); + + test('Paints transformed sweep gradient', () async { + final RecordingCanvas canvas = + RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300)); + canvas.save(); + + const List colors = [ + Color(0xFF000000), + Color(0xFFFF3C38), + Color(0xFFFF8C42), + Color(0xFFFFF275), + Color(0xFF6699CC), + Color(0xFF656D78), + ]; + + const List stops = [0.0, 0.05, 0.4, 0.6, 0.9, 1.0]; + + final Matrix4 transform = Matrix4.identity() + ..translate(100, 150) + ..scale(0.3, 0.7) + ..rotateZ(0.5); + + final GradientSweep sweepGradient = GradientSweep( + const Offset(0.5, 0.5), + colors, + stops, + TileMode.clamp, + 0.0, + 2 * math.pi, + transform.storage, + ); + + const double kBoxWidth = 150; + const double kBoxHeight = 80; + + Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight); + canvas.drawRect( + rectBounds, + SurfacePaint() + ..shader = engineGradientToShader(sweepGradient, rectBounds), + ); + + rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight); + canvas.drawOval( + rectBounds, + SurfacePaint() + ..shader = engineGradientToShader(sweepGradient, rectBounds), + ); + + canvas.restore(); + await canvasScreenshot( + canvas, + 'sweep_gradient_clamp_transformed', + canvasRect: screenRect, + region: region, + ); + }); + + test('Paints transformed radial gradient', () async { + final RecordingCanvas canvas = + RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300)); + canvas.save(); + + const List colors = [ + Color(0xFF000000), + Color(0xFFFF3C38), + Color(0xFFFF8C42), + Color(0xFFFFF275), + Color(0xFF6699CC), + Color(0xFF656D78), + ]; + + const List stops = [0.0, 0.05, 0.4, 0.6, 0.9, 1.0]; + + final Matrix4 transform = Matrix4.identity() + ..translate(100, 150) + ..scale(0.3, 0.7) + ..rotateZ(0.5); + + final GradientRadial radialGradient = GradientRadial( + const Offset(0.5, 0.5), + 0.5, + colors, + stops, + TileMode.clamp, + transform.storage, + ); + + const double kBoxWidth = 150; + const double kBoxHeight = 80; + + Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight); + canvas.drawRect( + rectBounds, + SurfacePaint() + ..shader = engineRadialGradientToShader(radialGradient, rectBounds), + ); + + rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight); + canvas.drawOval( + rectBounds, + SurfacePaint() + ..shader = engineRadialGradientToShader(radialGradient, rectBounds), + ); + + canvas.restore(); + await canvasScreenshot( + canvas, + 'radial_gradient_clamp_transformed', + canvasRect: screenRect, + region: region, + ); + }); + } Shader engineGradientToShader(GradientSweep gradient, Rect rect) { @@ -491,6 +661,18 @@ Shader engineLinearGradientToShader(GradientLinear gradient, Rect rect) { ); } +Shader engineRadialGradientToShader(GradientRadial gradient, Rect rect) { + return Gradient.radial( + Offset(rect.left + gradient.center.dx * rect.width, + rect.top + gradient.center.dy * rect.height), + gradient.radius, + gradient.colors, + gradient.colorStops, + gradient.tileMode, + gradient.matrix4 == null ? null : Float64List.fromList(gradient.matrix4!), + ); +} + Path samplePathFromRect(Rect rectBounds) => Path() ..moveTo(rectBounds.center.dx, rectBounds.top) From 5f2a6cf515d63324ebcdf3ad84c1a212b43706a0 Mon Sep 17 00:00:00 2001 From: xuty Date: Thu, 16 Mar 2023 22:25:17 +0800 Subject: [PATCH 3/9] Skip tests on firefox --- lib/web_ui/test/html/shaders/gradient_golden_test.dart | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/web_ui/test/html/shaders/gradient_golden_test.dart b/lib/web_ui/test/html/shaders/gradient_golden_test.dart index 9fc28a26fa86b..a791e06806425 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -524,7 +524,7 @@ Future testMain() async { canvasRect: screenRect, region: region, ); - }); + }, skip: isFirefox); test('Paints transformed sweep gradient', () async { final RecordingCanvas canvas = @@ -581,7 +581,7 @@ Future testMain() async { canvasRect: screenRect, region: region, ); - }); + }, skip: isFirefox); test('Paints transformed radial gradient', () async { final RecordingCanvas canvas = @@ -637,8 +637,7 @@ Future testMain() async { canvasRect: screenRect, region: region, ); - }); - + }, skip: isFirefox); } Shader engineGradientToShader(GradientSweep gradient, Rect rect) { From b9e29553afaf888e18b7696994768fc744a32ea6 Mon Sep 17 00:00:00 2001 From: xuty Date: Thu, 16 Mar 2023 22:26:52 +0800 Subject: [PATCH 4/9] Skip tests on firefox --- lib/web_ui/test/html/shaders/gradient_golden_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/test/html/shaders/gradient_golden_test.dart b/lib/web_ui/test/html/shaders/gradient_golden_test.dart index a791e06806425..f463200cf4b3a 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -355,7 +355,7 @@ Future testMain() async { RenderStrategy()); canvas.endRecording(); canvas.apply(engineCanvas, screenRect); - }); + }, skip: isFirefox); test("Creating lots of gradients doesn't create too many webgl contexts", () async { From 5909add036ca301dae56aec3583a55ab27017fcb Mon Sep 17 00:00:00 2001 From: xuty Date: Thu, 16 Mar 2023 23:57:04 +0800 Subject: [PATCH 5/9] Skip tests on firefox --- lib/web_ui/test/html/shaders/gradient_golden_test.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/test/html/shaders/gradient_golden_test.dart b/lib/web_ui/test/html/shaders/gradient_golden_test.dart index f463200cf4b3a..a58f65df4031b 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -434,7 +434,7 @@ Future testMain() async { canvas.restore(); await canvasScreenshot(canvas, 'linear_gradient_rect_clamp_rotated', canvasRect: screenRect, region: region); - }); + }, skip: isFirefox); test('Paints linear gradient properly when within svg context', () async { final RecordingCanvas canvas = @@ -468,7 +468,7 @@ Future testMain() async { canvas.restore(); await canvasScreenshot(canvas, 'linear_gradient_in_svg_context', canvasRect: screenRect, region: region); - }); + }, skip: isFirefox); test('Paints transformed linear gradient', () async { final RecordingCanvas canvas = From b3623feb9d96e800353df75901f31241bb88e7ec Mon Sep 17 00:00:00 2001 From: xuty Date: Thu, 16 Mar 2023 23:58:51 +0800 Subject: [PATCH 6/9] Skip tests on Firefox --- lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart b/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart index 742067e4914e0..28bec7cb8e764 100644 --- a/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart @@ -86,7 +86,7 @@ Future testMain() async { } expect(rc.renderStrategy.hasArbitraryPaint, isTrue); await canvasScreenshot(rc, 'linear_gradient_oval_matrix'); - }); + }, skip: isFirefox); // Regression test for https://github.com/flutter/flutter/issues/50010 test('Should draw linear gradient using rounded rect.', () async { From 093e0b589345791bf9b8ff82d3f5a141c384925c Mon Sep 17 00:00:00 2001 From: xuty Date: Fri, 17 Mar 2023 20:53:25 +0800 Subject: [PATCH 7/9] Avoid replace the original matrix --- lib/web_ui/lib/src/engine/html/shaders/shader.dart | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index 45c6ccc141826..629354a8b6353 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -107,7 +107,8 @@ class GradientSweep extends EngineGradient { gl.getUniformLocation(glProgram.program, 'm_gradient'); final Matrix4 gradientTransform = Matrix4.identity(); if (matrix4 != null) { - final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!)..invert(); + final Matrix4 m4 = Matrix4.zero() + ..copyInverse(Matrix4.fromFloat32List(matrix4!)); gradientTransform.translate(-center.dx, -center.dy); gradientTransform.multiply(m4); gradientTransform.translate(center.dx, center.dy); @@ -324,7 +325,8 @@ class GradientLinear extends EngineGradient { // with flipped y axis. // We flip y axis, translate to center, multiply matrix and translate // and flip back so it is applied correctly. - final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!.matrix)..invert(); + final Matrix4 m4 = Matrix4.zero() + ..copyInverse(Matrix4.fromFloat32List(matrix4!.matrix)); final ui.Offset center = shaderBounds.center; gradientTransform.translate(-center.dx, -center.dy); gradientTransform.multiply(m4); @@ -557,7 +559,8 @@ class GradientRadial extends EngineGradient { final Matrix4 gradientTransform = Matrix4.identity(); if (matrix4 != null) { - final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!)..invert(); + final Matrix4 m4 = Matrix4.zero() + ..copyInverse(Matrix4.fromFloat32List(matrix4!)); gradientTransform.translate(-center.dx, -center.dy); gradientTransform.multiply(m4); gradientTransform.translate(center.dx, center.dy); From e1300fbf5814491b86be261b0d064fb3a5e23fda Mon Sep 17 00:00:00 2001 From: xuty Date: Mon, 7 Aug 2023 14:46:36 +0800 Subject: [PATCH 8/9] Fix broken tests --- lib/web_ui/lib/src/engine/html/shaders/shader.dart | 2 +- lib/web_ui/test/html/shaders/gradient_golden_test.dart | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/shaders/shader.dart b/lib/web_ui/lib/src/engine/html/shaders/shader.dart index dc4d753c76f7b..8faa06617c309 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -213,7 +213,7 @@ class GradientLinear extends EngineGradient { @override Object createPaintStyle(DomCanvasRenderingContext2D? ctx, ui.Rect? shaderBounds, double density) { - if (matrix4 == null && (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal)) { + if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) { return _createCanvasGradient(ctx, shaderBounds, density); } else { return _createGlGradient(ctx, shaderBounds, density); diff --git a/lib/web_ui/test/html/shaders/gradient_golden_test.dart b/lib/web_ui/test/html/shaders/gradient_golden_test.dart index 89bfc0a9291b0..a7afcc1f856fe 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -603,7 +603,7 @@ Future testMain() async { final GradientRadial radialGradient = GradientRadial( const Offset(0.5, 0.5), - 0.5, + 50, colors, stops, TileMode.clamp, From c2e31858abc0db0cfb0b0f73ba4c02fc6588abf5 Mon Sep 17 00:00:00 2001 From: xuty Date: Mon, 7 Aug 2023 16:41:43 +0800 Subject: [PATCH 9/9] Adjust golden tests --- lib/web_ui/test/html/shaders/gradient_golden_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/web_ui/test/html/shaders/gradient_golden_test.dart b/lib/web_ui/test/html/shaders/gradient_golden_test.dart index a7afcc1f856fe..e56de2d87be7e 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -484,12 +484,12 @@ Future testMain() async { const List stops = [0.0, 0.05, 0.4, 0.6, 0.9, 1.0]; final Matrix4 transform = Matrix4.identity() - ..translate(100, 150) + ..translate(50, 50) ..scale(0.3, 0.7) ..rotateZ(0.5); final GradientLinear linearGradient = GradientLinear( - const Offset(50, 50), + const Offset(5, 5), const Offset(200, 130), colors, stops, @@ -597,13 +597,13 @@ Future testMain() async { const List stops = [0.0, 0.05, 0.4, 0.6, 0.9, 1.0]; final Matrix4 transform = Matrix4.identity() - ..translate(100, 150) + ..translate(50, 50) ..scale(0.3, 0.7) ..rotateZ(0.5); final GradientRadial radialGradient = GradientRadial( const Offset(0.5, 0.5), - 50, + 400, colors, stops, TileMode.clamp,