[web] Fix rendering of gradients in html mode (flutter/engine#40345)
 <details> <summary> Code Example</summary> ```dart import 'package:flutter/material.dart'; void main() { runApp(const MyApp()); } class DemoGradientTransform implements GradientTransform { @override Matrix4? transform(Rect bounds, {TextDirection? textDirection}) { return Matrix4.identity() ..scale(1.2, 1.7) ..rotateZ(0.25); } } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext context) { var colors = <Color>[ Colors.red, Colors.green, Colors.blue, Colors.yellow, ]; const stops = <double>[0.0, 0.25, 0.5, 1.0]; return MaterialApp( debugShowCheckedModeBanner: false, home: GridView( gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( crossAxisCount: TileMode.values.length, ), children: <Widget>[ for (final mode in TileMode.values) DecoratedBox( decoration: BoxDecoration( gradient: LinearGradient( colors: colors, stops: stops, tileMode: mode, transform: DemoGradientTransform(), ), ), ), for (final mode in TileMode.values) DecoratedBox( decoration: BoxDecoration( gradient: RadialGradient( center: Alignment.topLeft, radius: 0.5, colors: colors, stops: stops, tileMode: mode, transform: DemoGradientTransform(), ), ), ), for (final mode in TileMode.values) DecoratedBox( decoration: BoxDecoration( gradient: SweepGradient( center: Alignment.topLeft, startAngle: 0.0, endAngle: 3.14, colors: colors, stops: stops, tileMode: mode, transform: DemoGradientTransform(), ), ), ), ], ), ); } } ``` </details> Fixes: https://github.com/flutter/flutter/issues/84245
This commit is contained in:
@@ -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))
|
||||
|
||||
@@ -352,7 +352,7 @@ Future<void> 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<void> 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<void> 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<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
|
||||
const List<double> stops = <double>[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<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
|
||||
const List<double> stops = <double>[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<Color> colors = <Color>[
|
||||
Color(0xFF000000),
|
||||
Color(0xFFFF3C38),
|
||||
Color(0xFFFF8C42),
|
||||
Color(0xFFFFF275),
|
||||
Color(0xFF6699CC),
|
||||
Color(0xFF656D78),
|
||||
];
|
||||
|
||||
const List<double> stops = <double>[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)
|
||||
|
||||
@@ -84,7 +84,7 @@ Future<void> 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 {
|
||||
|
||||
Reference in New Issue
Block a user