Skip to content

Commit 3d629c5

Browse files
author
eyebrowsoffire
authored
Fix html gradient rendering (flutter#97762) (flutter#31355)
1 parent 3764a8f commit 3d629c5

File tree

7 files changed

+47
-26
lines changed

7 files changed

+47
-26
lines changed

lib/web_ui/lib/src/engine/html/render_vertices.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -317,7 +317,7 @@ class _WebGlRenderer implements GlRenderer {
317317
NormalizedGradient gradient, int widthInPixels, int heightInPixels) {
318318
drawRectToGl(
319319
targetRect, gl, glProgram, gradient, widthInPixels, heightInPixels);
320-
final Object? image = gl.readPatternData();
320+
final Object? image = gl.readPatternData(gradient.isOpaque);
321321
gl.bindArrayBuffer(null);
322322
gl.bindElementArrayBuffer(null);
323323
return image;

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -260,7 +260,7 @@ class EngineImageShader implements ui.ImageShader {
260260
gl.unbindVertexArray();
261261
}
262262

263-
final Object? bitmapImage = gl.readPatternData();
263+
final Object? bitmapImage = gl.readPatternData(false);
264264
gl.bindArrayBuffer(null);
265265
gl.bindElementArrayBuffer(null);
266266
return context!.createPattern(bitmapImage!, 'no-repeat')!;

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ class NormalizedGradient {
2626
final Float32List _bias;
2727
final Float32List _scale;
2828
final int thresholdCount;
29+
final bool isOpaque;
2930

3031
factory NormalizedGradient(List<ui.Color> colors, {List<double>? stops}) {
3132
// If colorStops is not provided, then only two stops, at 0.0 and 1.0,
@@ -34,6 +35,7 @@ class NormalizedGradient {
3435
stops ??= const <double>[0.0, 1.0];
3536
final int colorCount = colors.length;
3637
int normalizedCount = colorCount;
38+
final bool isOpaque = !colors.any((ui.Color c) => c.alpha < 1.0);
3739
final bool addFirst = stops[0] != 0.0;
3840
final bool addLast = stops.last != 1.0;
3941
if (addFirst) {
@@ -94,11 +96,11 @@ class NormalizedGradient {
9496
bias[colorIndex + 2] -= t * scale[colorIndex + 2];
9597
bias[colorIndex + 3] -= t * scale[colorIndex + 3];
9698
}
97-
return NormalizedGradient._(normalizedCount, thresholds, scale, bias);
99+
return NormalizedGradient._(normalizedCount, thresholds, scale, bias, isOpaque);
98100
}
99101

100102
NormalizedGradient._(
101-
this.thresholdCount, this._thresholds, this._scale, this._bias);
103+
this.thresholdCount, this._thresholds, this._scale, this._bias, this.isOpaque);
102104

103105
/// Sets uniforms for threshold, bias and scale for program.
104106
void setupUniforms(GlContext gl, GlProgram glProgram) {

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

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -79,11 +79,9 @@ class GradientSweep extends EngineGradient {
7979
final Object angleRange = gl.getUniformLocation(glProgram.program, 'angle_range');
8080
gl.setUniform2f(angleRange, startAngle, endAngle);
8181
normalizedGradient.setupUniforms(gl, glProgram);
82-
if (matrix4 != null) {
83-
final Object gradientMatrix =
82+
final Object gradientMatrix =
8483
gl.getUniformLocation(glProgram.program, 'm_gradient');
85-
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4!);
86-
}
84+
gl.setUniformMatrix4fv(gradientMatrix, false, matrix4 ?? Matrix4.identity().storage);
8785
if (createDataUrl) {
8886
return glRenderer!.drawRectToImageUrl(
8987
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
@@ -293,9 +291,7 @@ class GradientLinear extends EngineGradient {
293291
// We compute location based on gl_FragCoord to center distance which
294292
// returns 0.0 at center. To make sure we align center of gradient to this
295293
// point, we shift by 0.5 to get st value for center of gradient.
296-
if (tileMode != ui.TileMode.repeated) {
297-
gradientTransform.translate(0.5, 0);
298-
}
294+
gradientTransform.translate(0.5, 0);
299295
if (length > kFltEpsilon) {
300296
gradientTransform.scale(1.0 / length);
301297
}

lib/web_ui/lib/src/engine/safe_browser_api.dart

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -814,12 +814,14 @@ class GlContext {
814814

815815
/// Returns image data in a form that can be used to create Canvas
816816
/// context patterns.
817-
Object? readPatternData() {
817+
Object? readPatternData(bool isOpaque) {
818818
// When using OffscreenCanvas and transferToImageBitmap is supported by
819819
// browser create ImageBitmap otherwise use more expensive canvas
820-
// allocation.
820+
// allocation. However, transferToImageBitmap does not properly preserve
821+
// the alpha channel, so only use it if the pattern is opaque.
821822
if (_canvas != null &&
822-
js_util.hasProperty(_canvas!, 'transferToImageBitmap')) {
823+
js_util.hasProperty(_canvas!, 'transferToImageBitmap') &&
824+
isOpaque) {
823825
// TODO(yjbanov): find out why we need to call getContext and ignore the return value.
824826
js_util.callMethod<void>(_canvas!, 'getContext', <dynamic>['webgl2']);
825827
final Object? imageBitmap = js_util.callMethod(_canvas!, 'transferToImageBitmap',

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

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ Future<void> testMain() async {
8080
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
8181
colors, stops, TileMode.clamp,
8282
0, 360.0 / 180.0 * math.pi,
83-
Matrix4.rotationZ(math.pi / 6.0).storage);
83+
null);
8484

8585
final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
8686
colors, stops, TileMode.clamp,
@@ -105,7 +105,7 @@ Future<void> testMain() async {
105105
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
106106
colors, stops, TileMode.clamp,
107107
math.pi / 6, 3 * math.pi / 4,
108-
Matrix4.rotationZ(math.pi / 6.0).storage);
108+
null);
109109

110110
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
111111
canvas.drawRect(rectBounds,
@@ -117,7 +117,7 @@ Future<void> testMain() async {
117117
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
118118
colors, stops, TileMode.repeated,
119119
math.pi / 6, 3 * math.pi / 4,
120-
Matrix4.rotationZ(math.pi / 6.0).storage);
120+
null);
121121

122122
canvas.drawRect(rectBounds,
123123
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
@@ -128,7 +128,7 @@ Future<void> testMain() async {
128128
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
129129
colors, stops, TileMode.mirror,
130130
math.pi / 6, 3 * math.pi / 4,
131-
Matrix4.rotationZ(math.pi / 6.0).storage);
131+
null);
132132
canvas.drawRect(rectBounds,
133133
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
134134
canvas.drawRect(rectBounds, borderPaint);
@@ -159,7 +159,7 @@ Future<void> testMain() async {
159159
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
160160
colors, stops, TileMode.clamp,
161161
0, 360.0 / 180.0 * math.pi,
162-
Matrix4.rotationZ(math.pi / 6.0).storage);
162+
null);
163163

164164
final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
165165
colors, stops, TileMode.clamp,
@@ -184,7 +184,7 @@ Future<void> testMain() async {
184184
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
185185
colors, stops, TileMode.clamp,
186186
math.pi / 6, 3 * math.pi / 4,
187-
Matrix4.rotationZ(math.pi / 6.0).storage);
187+
null);
188188

189189
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
190190
canvas.drawOval(rectBounds,
@@ -196,7 +196,7 @@ Future<void> testMain() async {
196196
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
197197
colors, stops, TileMode.repeated,
198198
math.pi / 6, 3 * math.pi / 4,
199-
Matrix4.rotationZ(math.pi / 6.0).storage);
199+
null);
200200

201201
canvas.drawOval(rectBounds,
202202
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
@@ -207,7 +207,7 @@ Future<void> testMain() async {
207207
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
208208
colors, stops, TileMode.mirror,
209209
math.pi / 6, 3 * math.pi / 4,
210-
Matrix4.rotationZ(math.pi / 6.0).storage);
210+
null);
211211
canvas.drawOval(rectBounds,
212212
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
213213
canvas.drawRect(rectBounds, borderPaint);
@@ -238,7 +238,7 @@ Future<void> testMain() async {
238238
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
239239
colors, stops, TileMode.clamp,
240240
0, 360.0 / 180.0 * math.pi,
241-
Matrix4.rotationZ(math.pi / 6.0).storage);
241+
null);
242242

243243
final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
244244
colors, stops, TileMode.clamp,
@@ -265,7 +265,7 @@ Future<void> testMain() async {
265265
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
266266
colors, stops, TileMode.clamp,
267267
math.pi / 6, 3 * math.pi / 4,
268-
Matrix4.rotationZ(math.pi / 6.0).storage);
268+
null);
269269

270270
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
271271
path = samplePathFromRect(rectBounds);
@@ -278,7 +278,7 @@ Future<void> testMain() async {
278278
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
279279
colors, stops, TileMode.repeated,
280280
math.pi / 6, 3 * math.pi / 4,
281-
Matrix4.rotationZ(math.pi / 6.0).storage);
281+
null);
282282

283283
path = samplePathFromRect(rectBounds);
284284
canvas.drawPath(path,
@@ -290,7 +290,7 @@ Future<void> testMain() async {
290290
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
291291
colors, stops, TileMode.mirror,
292292
math.pi / 6, 3 * math.pi / 4,
293-
Matrix4.rotationZ(math.pi / 6.0).storage);
293+
null);
294294
path = samplePathFromRect(rectBounds);
295295
canvas.drawPath(path,
296296
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,27 @@ Future<void> testMain() async {
4545
maxDiffRatePercent: 0.01);
4646
});
4747

48+
test('Should blend linear gradient with alpha channel correctly.', () async {
49+
const Rect canvasRect = Rect.fromLTRB(0, 0, 500, 500);
50+
final RecordingCanvas rc =
51+
RecordingCanvas(canvasRect);
52+
final SurfacePaint backgroundPaint = SurfacePaint()
53+
..style = PaintingStyle.fill
54+
..color = const Color(0xFFFF0000);
55+
rc.drawRect(canvasRect, backgroundPaint);
56+
57+
const Rect shaderRect = Rect.fromLTRB(50, 50, 300, 300);
58+
final SurfacePaint paint = SurfacePaint()..shader = Gradient.linear(
59+
Offset(shaderRect.left, shaderRect.top),
60+
Offset(shaderRect.right, shaderRect.bottom),
61+
const <Color>[Color(0x00000000), Color(0xFF0000FF)]);
62+
rc.drawRect(shaderRect, paint);
63+
expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
64+
await canvasScreenshot(rc, 'linear_gradient_rect_alpha',
65+
region: screenRect,
66+
maxDiffRatePercent: 0.01);
67+
});
68+
4869
test('Should draw linear gradient with transform.', () async {
4970
final RecordingCanvas rc =
5071
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));

0 commit comments

Comments
 (0)