diff --git a/lib/web_ui/lib/src/engine/html/render_vertices.dart b/lib/web_ui/lib/src/engine/html/render_vertices.dart
index 73b612d84c23d..3ef24fc1b6938 100644
--- a/lib/web_ui/lib/src/engine/html/render_vertices.dart
+++ b/lib/web_ui/lib/src/engine/html/render_vertices.dart
@@ -317,7 +317,7 @@ class _WebGlRenderer implements GlRenderer {
NormalizedGradient gradient, int widthInPixels, int heightInPixels) {
drawRectToGl(
targetRect, gl, glProgram, gradient, widthInPixels, heightInPixels);
- final Object? image = gl.readPatternData();
+ final Object? image = gl.readPatternData(gradient.isOpaque);
gl.bindArrayBuffer(null);
gl.bindElementArrayBuffer(null);
return image;
diff --git a/lib/web_ui/lib/src/engine/html/shaders/image_shader.dart b/lib/web_ui/lib/src/engine/html/shaders/image_shader.dart
index 86aeb0a3e92f0..a884fcd3d704c 100644
--- a/lib/web_ui/lib/src/engine/html/shaders/image_shader.dart
+++ b/lib/web_ui/lib/src/engine/html/shaders/image_shader.dart
@@ -260,7 +260,7 @@ class EngineImageShader implements ui.ImageShader {
gl.unbindVertexArray();
}
- final Object? bitmapImage = gl.readPatternData();
+ final Object? bitmapImage = gl.readPatternData(false);
gl.bindArrayBuffer(null);
gl.bindElementArrayBuffer(null);
return context!.createPattern(bitmapImage!, 'no-repeat')!;
diff --git a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart
index 6cba78f11d6a7..138f207d2d058 100644
--- a/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart
+++ b/lib/web_ui/lib/src/engine/html/shaders/normalized_gradient.dart
@@ -26,6 +26,7 @@ class NormalizedGradient {
final Float32List _bias;
final Float32List _scale;
final int thresholdCount;
+ final bool isOpaque;
factory NormalizedGradient(List colors, {List? stops}) {
// If colorStops is not provided, then only two stops, at 0.0 and 1.0,
@@ -34,6 +35,7 @@ class NormalizedGradient {
stops ??= const [0.0, 1.0];
final int colorCount = colors.length;
int normalizedCount = colorCount;
+ final bool isOpaque = !colors.any((ui.Color c) => c.alpha < 1.0);
final bool addFirst = stops[0] != 0.0;
final bool addLast = stops.last != 1.0;
if (addFirst) {
@@ -94,11 +96,11 @@ class NormalizedGradient {
bias[colorIndex + 2] -= t * scale[colorIndex + 2];
bias[colorIndex + 3] -= t * scale[colorIndex + 3];
}
- return NormalizedGradient._(normalizedCount, thresholds, scale, bias);
+ return NormalizedGradient._(normalizedCount, thresholds, scale, bias, isOpaque);
}
NormalizedGradient._(
- this.thresholdCount, this._thresholds, this._scale, this._bias);
+ this.thresholdCount, this._thresholds, this._scale, this._bias, this.isOpaque);
/// Sets uniforms for threshold, bias and scale for program.
void setupUniforms(GlContext gl, GlProgram glProgram) {
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 0ae291470b0f1..c80cdfde046b7 100644
--- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart
+++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart
@@ -79,11 +79,9 @@ class GradientSweep extends EngineGradient {
final Object angleRange = gl.getUniformLocation(glProgram.program, 'angle_range');
gl.setUniform2f(angleRange, startAngle, endAngle);
normalizedGradient.setupUniforms(gl, glProgram);
- if (matrix4 != null) {
- final Object gradientMatrix =
+ final Object gradientMatrix =
gl.getUniformLocation(glProgram.program, 'm_gradient');
- gl.setUniformMatrix4fv(gradientMatrix, false, matrix4!);
- }
+ gl.setUniformMatrix4fv(gradientMatrix, false, matrix4 ?? Matrix4.identity().storage);
if (createDataUrl) {
return glRenderer!.drawRectToImageUrl(
ui.Rect.fromLTWH(0, 0, shaderBounds.width, shaderBounds.height),
@@ -293,9 +291,7 @@ class GradientLinear extends EngineGradient {
// We compute location based on gl_FragCoord to center distance which
// returns 0.0 at center. To make sure we align center of gradient to this
// point, we shift by 0.5 to get st value for center of gradient.
- if (tileMode != ui.TileMode.repeated) {
- gradientTransform.translate(0.5, 0);
- }
+ gradientTransform.translate(0.5, 0);
if (length > kFltEpsilon) {
gradientTransform.scale(1.0 / length);
}
diff --git a/lib/web_ui/lib/src/engine/safe_browser_api.dart b/lib/web_ui/lib/src/engine/safe_browser_api.dart
index 498428df10025..448d555f384fe 100644
--- a/lib/web_ui/lib/src/engine/safe_browser_api.dart
+++ b/lib/web_ui/lib/src/engine/safe_browser_api.dart
@@ -814,12 +814,14 @@ class GlContext {
/// Returns image data in a form that can be used to create Canvas
/// context patterns.
- Object? readPatternData() {
+ Object? readPatternData(bool isOpaque) {
// When using OffscreenCanvas and transferToImageBitmap is supported by
// browser create ImageBitmap otherwise use more expensive canvas
- // allocation.
+ // allocation. However, transferToImageBitmap does not properly preserve
+ // the alpha channel, so only use it if the pattern is opaque.
if (_canvas != null &&
- js_util.hasProperty(_canvas!, 'transferToImageBitmap')) {
+ js_util.hasProperty(_canvas!, 'transferToImageBitmap') &&
+ isOpaque) {
// TODO(yjbanov): find out why we need to call getContext and ignore the return value.
js_util.callMethod(_canvas!, 'getContext', ['webgl2']);
final Object? imageBitmap = js_util.callMethod(_canvas!, 'transferToImageBitmap',
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 72a2c353bafad..599ae80754026 100644
--- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart
+++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart
@@ -80,7 +80,7 @@ Future testMain() async {
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
0, 360.0 / 180.0 * math.pi,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
@@ -105,7 +105,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
canvas.drawRect(rectBounds,
@@ -117,7 +117,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.repeated,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
canvas.drawRect(rectBounds,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
@@ -128,7 +128,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.mirror,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
canvas.drawRect(rectBounds,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
canvas.drawRect(rectBounds, borderPaint);
@@ -159,7 +159,7 @@ Future testMain() async {
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
0, 360.0 / 180.0 * math.pi,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
@@ -184,7 +184,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
canvas.drawOval(rectBounds,
@@ -196,7 +196,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.repeated,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
canvas.drawOval(rectBounds,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
@@ -207,7 +207,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.mirror,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
canvas.drawOval(rectBounds,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
canvas.drawRect(rectBounds, borderPaint);
@@ -238,7 +238,7 @@ Future testMain() async {
GradientSweep sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
0, 360.0 / 180.0 * math.pi,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
final GradientSweep sweepGradientRotated = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
@@ -265,7 +265,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.clamp,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
rectBounds = rectBounds.translate(kBoxWidth + 10, 0);
path = samplePathFromRect(rectBounds);
@@ -278,7 +278,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.repeated,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
path = samplePathFromRect(rectBounds);
canvas.drawPath(path,
@@ -290,7 +290,7 @@ Future testMain() async {
sweepGradient = GradientSweep(const Offset(0.5, 0.5),
colors, stops, TileMode.mirror,
math.pi / 6, 3 * math.pi / 4,
- Matrix4.rotationZ(math.pi / 6.0).storage);
+ null);
path = samplePathFromRect(rectBounds);
canvas.drawPath(path,
SurfacePaint()..shader = engineGradientToShader(sweepGradient, rectBounds));
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 75ba08279356b..37837c8d96e9b 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
@@ -45,6 +45,27 @@ Future testMain() async {
maxDiffRatePercent: 0.01);
});
+ test('Should blend linear gradient with alpha channel correctly.', () async {
+ const Rect canvasRect = Rect.fromLTRB(0, 0, 500, 500);
+ final RecordingCanvas rc =
+ RecordingCanvas(canvasRect);
+ final SurfacePaint backgroundPaint = SurfacePaint()
+ ..style = PaintingStyle.fill
+ ..color = const Color(0xFFFF0000);
+ rc.drawRect(canvasRect, backgroundPaint);
+
+ const Rect shaderRect = Rect.fromLTRB(50, 50, 300, 300);
+ final SurfacePaint paint = SurfacePaint()..shader = Gradient.linear(
+ Offset(shaderRect.left, shaderRect.top),
+ Offset(shaderRect.right, shaderRect.bottom),
+ const [Color(0x00000000), Color(0xFF0000FF)]);
+ rc.drawRect(shaderRect, paint);
+ expect(rc.renderStrategy.hasArbitraryPaint, isTrue);
+ await canvasScreenshot(rc, 'linear_gradient_rect_alpha',
+ region: screenRect,
+ maxDiffRatePercent: 0.01);
+ });
+
test('Should draw linear gradient with transform.', () async {
final RecordingCanvas rc =
RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 500));