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 9a8fb3ac9874f..8faa06617c309 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,23 @@ 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.zero() + ..copyInverse(Matrix4.fromFloat32List(matrix4!)); + 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 +159,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;'); @@ -317,14 +327,12 @@ 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.zero() + ..copyInverse(Matrix4.fromFloat32List(matrix4!.matrix)); + 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); @@ -465,6 +473,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; } @@ -483,7 +497,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); @@ -533,15 +547,24 @@ 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.zero() + ..copyInverse(Matrix4.fromFloat32List(matrix4!)); + 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) { @@ -587,7 +610,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);'); @@ -666,7 +689,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)) 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 8dfa8fc66503f..e56de2d87be7e 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -352,7 +352,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 { @@ -431,7 +431,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 = @@ -465,7 +465,176 @@ 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 = + 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(50, 50) + ..scale(0.3, 0.7) + ..rotateZ(0.5); + + final GradientLinear linearGradient = GradientLinear( + const Offset(5, 5), + 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, + ); + }, skip: isFirefox); + + 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, + ); + }, skip: isFirefox); + + 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(50, 50) + ..scale(0.3, 0.7) + ..rotateZ(0.5); + + final GradientRadial radialGradient = GradientRadial( + const Offset(0.5, 0.5), + 400, + 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, + ); + }, skip: isFirefox); } Shader engineGradientToShader(GradientSweep gradient, Rect rect) { @@ -488,6 +657,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) 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 bf1ab3752cebd..433d697348535 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 @@ -84,7 +84,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 {