Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Commit f260e6d

Browse files
authored
[web] Fix rendering of gradients in html mode (#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: flutter/flutter#84245
1 parent 7bcf9f4 commit f260e6d

File tree

3 files changed

+224
-20
lines changed

3 files changed

+224
-20
lines changed

lib/web_ui/lib/src/engine/html/shaders/shader.dart

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,23 @@ class GradientSweep extends EngineGradient {
9898
final double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width);
9999
final double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height);
100100
gl.setUniform2f(tileOffset, 2 * (shaderBounds.width * (centerX - 0.5)),
101-
2 * (shaderBounds.height * (centerY - 0.5)));
101+
2 * (shaderBounds.height * (0.5 - centerY)));
102102
final Object angleRange = gl.getUniformLocation(glProgram.program, 'angle_range');
103103
gl.setUniform2f(angleRange, startAngle, endAngle);
104104
normalizedGradient.setupUniforms(gl, glProgram);
105+
105106
final Object gradientMatrix =
106107
gl.getUniformLocation(glProgram.program, 'm_gradient');
107-
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4 ?? Matrix4.identity().storage);
108+
final Matrix4 gradientTransform = Matrix4.identity();
109+
if (matrix4 != null) {
110+
final Matrix4 m4 = Matrix4.zero()
111+
..copyInverse(Matrix4.fromFloat32List(matrix4!));
112+
gradientTransform.translate(-center.dx, -center.dy);
113+
gradientTransform.multiply(m4);
114+
gradientTransform.translate(center.dx, center.dy);
115+
}
116+
gl.setUniformMatrix4fv(gradientMatrix, false, gradientTransform.storage);
117+
108118
final Object result = () {
109119
if (createDataUrl) {
110120
return glRenderer!.drawRectToImageUrl(
@@ -149,7 +159,7 @@ class GradientSweep extends EngineGradient {
149159
// Sweep gradient
150160
method.addStatement('vec2 center = 0.5 * (u_resolution + u_tile_offset);');
151161
method.addStatement(
152-
'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;');
162+
'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);');
153163
method.addStatement(
154164
'float angle = atan(-localCoord.y, -localCoord.x) + ${math.pi};');
155165
method.addStatement('float sweep = angle_range.y - angle_range.x;');
@@ -317,14 +327,12 @@ class GradientLinear extends EngineGradient {
317327
// with flipped y axis.
318328
// We flip y axis, translate to center, multiply matrix and translate
319329
// and flip back so it is applied correctly.
320-
final Matrix4 m4 = Matrix4.fromFloat32List(matrix4!.matrix);
321-
gradientTransform.scale(1, -1);
322-
gradientTransform.translate(
323-
-shaderBounds.center.dx, -shaderBounds.center.dy);
330+
final Matrix4 m4 = Matrix4.zero()
331+
..copyInverse(Matrix4.fromFloat32List(matrix4!.matrix));
332+
final ui.Offset center = shaderBounds.center;
333+
gradientTransform.translate(-center.dx, -center.dy);
324334
gradientTransform.multiply(m4);
325-
gradientTransform.translate(
326-
shaderBounds.center.dx, shaderBounds.center.dy);
327-
gradientTransform.scale(1, -1);
335+
gradientTransform.translate(center.dx, center.dy);
328336
}
329337

330338
gradientTransform.multiply(rotationZ);
@@ -465,6 +473,12 @@ String _writeSharedGradientShader(ShaderBuilder builder, ShaderMethod method,
465473
sourcePrefix: 'threshold',
466474
biasName: 'bias',
467475
scaleName: 'scale');
476+
if (tileMode == ui.TileMode.decal) {
477+
method.addStatement('if (st < 0.0 || st > 1.0) {');
478+
method.addStatement(' ${builder.fragmentColor.name} = vec4(0, 0, 0, 0);');
479+
method.addStatement(' return;');
480+
method.addStatement('}');
481+
}
468482
return probeName;
469483
}
470484

@@ -483,7 +497,7 @@ class GradientRadial extends EngineGradient {
483497
@override
484498
Object createPaintStyle(DomCanvasRenderingContext2D? ctx,
485499
ui.Rect? shaderBounds, double density) {
486-
if (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal) {
500+
if (matrix4 == null && (tileMode == ui.TileMode.clamp || tileMode == ui.TileMode.decal)) {
487501
return _createCanvasGradient(ctx, shaderBounds, density);
488502
} else {
489503
return _createGlGradient(ctx, shaderBounds, density);
@@ -533,15 +547,24 @@ class GradientRadial extends EngineGradient {
533547
final double centerX = (center.dx - shaderBounds.left) / (shaderBounds.width);
534548
final double centerY = (center.dy - shaderBounds.top) / (shaderBounds.height);
535549
gl.setUniform2f(tileOffset, 2 * (shaderBounds.width * (centerX - 0.5)),
536-
2 * (shaderBounds.height * (centerY - 0.5)));
550+
2 * (shaderBounds.height * (0.5 - centerY)));
537551
final Object radiusUniform = gl.getUniformLocation(glProgram.program, 'u_radius');
538552
gl.setUniform1f(radiusUniform, radius);
539553
normalizedGradient.setupUniforms(gl, glProgram);
540554

541555
final Object gradientMatrix =
542556
gl.getUniformLocation(glProgram.program, 'm_gradient');
543-
gl.setUniformMatrix4fv(gradientMatrix, false,
544-
matrix4 == null ? Matrix4.identity().storage : matrix4!);
557+
558+
final Matrix4 gradientTransform = Matrix4.identity();
559+
560+
if (matrix4 != null) {
561+
final Matrix4 m4 = Matrix4.zero()
562+
..copyInverse(Matrix4.fromFloat32List(matrix4!));
563+
gradientTransform.translate(-center.dx, -center.dy);
564+
gradientTransform.multiply(m4);
565+
gradientTransform.translate(center.dx, center.dy);
566+
}
567+
gl.setUniformMatrix4fv(gradientMatrix, false, gradientTransform.storage);
545568

546569
final Object result = () {
547570
if (createDataUrl) {
@@ -587,7 +610,7 @@ class GradientRadial extends EngineGradient {
587610
// Sweep gradient
588611
method.addStatement('vec2 center = 0.5 * (u_resolution + u_tile_offset);');
589612
method.addStatement(
590-
'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;');
613+
'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);');
591614
method.addStatement('float dist = length(localCoord);');
592615
method.addStatement(
593616
'float st = abs(dist / u_radius);');
@@ -666,7 +689,7 @@ class GradientConical extends GradientRadial {
666689
// Sweep gradient
667690
method.addStatement('vec2 center = 0.5 * (u_resolution + u_tile_offset);');
668691
method.addStatement(
669-
'vec4 localCoord = vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1) * m_gradient;');
692+
'vec4 localCoord = m_gradient * vec4(gl_FragCoord.x - center.x, center.y - gl_FragCoord.y, 0, 1);');
670693
method.addStatement('float dist = length(localCoord);');
671694
final String f = (focalRadius /
672695
(math.min(shaderBounds.width, shaderBounds.height) / 2.0))

lib/web_ui/test/html/shaders/gradient_golden_test.dart

Lines changed: 184 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -352,7 +352,7 @@ Future<void> testMain() async {
352352
RenderStrategy());
353353
canvas.endRecording();
354354
canvas.apply(engineCanvas, screenRect);
355-
});
355+
}, skip: isFirefox);
356356

357357
test("Creating lots of gradients doesn't create too many webgl contexts",
358358
() async {
@@ -431,7 +431,7 @@ Future<void> testMain() async {
431431

432432
canvas.restore();
433433
await canvasScreenshot(canvas, 'linear_gradient_rect_clamp_rotated', canvasRect: screenRect, region: region);
434-
});
434+
}, skip: isFirefox);
435435

436436
test('Paints linear gradient properly when within svg context', () async {
437437
final RecordingCanvas canvas =
@@ -465,7 +465,176 @@ Future<void> testMain() async {
465465

466466
canvas.restore();
467467
await canvasScreenshot(canvas, 'linear_gradient_in_svg_context', canvasRect: screenRect, region: region);
468-
});
468+
}, skip: isFirefox);
469+
470+
test('Paints transformed linear gradient', () async {
471+
final RecordingCanvas canvas =
472+
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
473+
canvas.save();
474+
475+
const List<Color> colors = <Color>[
476+
Color(0xFF000000),
477+
Color(0xFFFF3C38),
478+
Color(0xFFFF8C42),
479+
Color(0xFFFFF275),
480+
Color(0xFF6699CC),
481+
Color(0xFF656D78),
482+
];
483+
484+
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
485+
486+
final Matrix4 transform = Matrix4.identity()
487+
..translate(50, 50)
488+
..scale(0.3, 0.7)
489+
..rotateZ(0.5);
490+
491+
final GradientLinear linearGradient = GradientLinear(
492+
const Offset(5, 5),
493+
const Offset(200, 130),
494+
colors,
495+
stops,
496+
TileMode.clamp,
497+
transform.storage,
498+
);
499+
500+
const double kBoxWidth = 150;
501+
const double kBoxHeight = 80;
502+
503+
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
504+
canvas.drawRect(
505+
rectBounds,
506+
SurfacePaint()
507+
..shader = engineLinearGradientToShader(linearGradient, rectBounds),
508+
);
509+
510+
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
511+
canvas.drawOval(
512+
rectBounds,
513+
SurfacePaint()
514+
..shader = engineLinearGradientToShader(linearGradient, rectBounds),
515+
);
516+
517+
canvas.restore();
518+
await canvasScreenshot(
519+
canvas,
520+
'linear_gradient_clamp_transformed',
521+
canvasRect: screenRect,
522+
region: region,
523+
);
524+
}, skip: isFirefox);
525+
526+
test('Paints transformed sweep gradient', () async {
527+
final RecordingCanvas canvas =
528+
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
529+
canvas.save();
530+
531+
const List<Color> colors = <Color>[
532+
Color(0xFF000000),
533+
Color(0xFFFF3C38),
534+
Color(0xFFFF8C42),
535+
Color(0xFFFFF275),
536+
Color(0xFF6699CC),
537+
Color(0xFF656D78),
538+
];
539+
540+
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
541+
542+
final Matrix4 transform = Matrix4.identity()
543+
..translate(100, 150)
544+
..scale(0.3, 0.7)
545+
..rotateZ(0.5);
546+
547+
final GradientSweep sweepGradient = GradientSweep(
548+
const Offset(0.5, 0.5),
549+
colors,
550+
stops,
551+
TileMode.clamp,
552+
0.0,
553+
2 * math.pi,
554+
transform.storage,
555+
);
556+
557+
const double kBoxWidth = 150;
558+
const double kBoxHeight = 80;
559+
560+
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
561+
canvas.drawRect(
562+
rectBounds,
563+
SurfacePaint()
564+
..shader = engineGradientToShader(sweepGradient, rectBounds),
565+
);
566+
567+
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
568+
canvas.drawOval(
569+
rectBounds,
570+
SurfacePaint()
571+
..shader = engineGradientToShader(sweepGradient, rectBounds),
572+
);
573+
574+
canvas.restore();
575+
await canvasScreenshot(
576+
canvas,
577+
'sweep_gradient_clamp_transformed',
578+
canvasRect: screenRect,
579+
region: region,
580+
);
581+
}, skip: isFirefox);
582+
583+
test('Paints transformed radial gradient', () async {
584+
final RecordingCanvas canvas =
585+
RecordingCanvas(const Rect.fromLTRB(0, 0, 400, 300));
586+
canvas.save();
587+
588+
const List<Color> colors = <Color>[
589+
Color(0xFF000000),
590+
Color(0xFFFF3C38),
591+
Color(0xFFFF8C42),
592+
Color(0xFFFFF275),
593+
Color(0xFF6699CC),
594+
Color(0xFF656D78),
595+
];
596+
597+
const List<double> stops = <double>[0.0, 0.05, 0.4, 0.6, 0.9, 1.0];
598+
599+
final Matrix4 transform = Matrix4.identity()
600+
..translate(50, 50)
601+
..scale(0.3, 0.7)
602+
..rotateZ(0.5);
603+
604+
final GradientRadial radialGradient = GradientRadial(
605+
const Offset(0.5, 0.5),
606+
400,
607+
colors,
608+
stops,
609+
TileMode.clamp,
610+
transform.storage,
611+
);
612+
613+
const double kBoxWidth = 150;
614+
const double kBoxHeight = 80;
615+
616+
Rect rectBounds = const Rect.fromLTWH(10, 20, kBoxWidth, kBoxHeight);
617+
canvas.drawRect(
618+
rectBounds,
619+
SurfacePaint()
620+
..shader = engineRadialGradientToShader(radialGradient, rectBounds),
621+
);
622+
623+
rectBounds = const Rect.fromLTWH(10, 110, kBoxWidth, kBoxHeight);
624+
canvas.drawOval(
625+
rectBounds,
626+
SurfacePaint()
627+
..shader = engineRadialGradientToShader(radialGradient, rectBounds),
628+
);
629+
630+
canvas.restore();
631+
await canvasScreenshot(
632+
canvas,
633+
'radial_gradient_clamp_transformed',
634+
canvasRect: screenRect,
635+
region: region,
636+
);
637+
}, skip: isFirefox);
469638
}
470639

471640
Shader engineGradientToShader(GradientSweep gradient, Rect rect) {
@@ -488,6 +657,18 @@ Shader engineLinearGradientToShader(GradientLinear gradient, Rect rect) {
488657
);
489658
}
490659

660+
Shader engineRadialGradientToShader(GradientRadial gradient, Rect rect) {
661+
return Gradient.radial(
662+
Offset(rect.left + gradient.center.dx * rect.width,
663+
rect.top + gradient.center.dy * rect.height),
664+
gradient.radius,
665+
gradient.colors,
666+
gradient.colorStops,
667+
gradient.tileMode,
668+
gradient.matrix4 == null ? null : Float64List.fromList(gradient.matrix4!),
669+
);
670+
}
671+
491672
Path samplePathFromRect(Rect rectBounds) =>
492673
Path()
493674
..moveTo(rectBounds.center.dx, rectBounds.top)

lib/web_ui/test/html/shaders/linear_gradient_golden_test.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ Future<void> testMain() async {
8484
}
8585
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
8686
await canvasScreenshot(rc, 'linear_gradient_oval_matrix');
87-
});
87+
}, skip: isFirefox);
8888

8989
// Regression test for https://github.com/flutter/flutter/issues/50010
9090
test('Should draw linear gradient using rounded rect.', () async {

0 commit comments

Comments
 (0)