[web] Fix rendering of gradients in html mode (flutter/engine#40345)

![CleanShot 2023-03-16 at 20 44 01@2x](https://user-images.githubusercontent.com/15033141/225620947-18fe19aa-c5e2-45a5-a0cc-151275844af7.png)

<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:
xuty
2023-08-10 05:38:59 +08:00
committed by GitHub
parent 9fb889d3a8
commit 0c708f040f
3 changed files with 224 additions and 20 deletions

View File

@@ -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))

View File

@@ -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)

View File

@@ -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 {