From 3d629c581c0b860dbaf1769b0dfc0a414713b181 Mon Sep 17 00:00:00 2001 From: eyebrowsoffire Date: Thu, 10 Feb 2022 09:50:12 -0800 Subject: [PATCH] Fix html gradient rendering (https://github.com/flutter/flutter/issues/97762) (#31355) --- .../lib/src/engine/html/render_vertices.dart | 2 +- .../src/engine/html/shaders/image_shader.dart | 2 +- .../html/shaders/normalized_gradient.dart | 6 +++-- .../lib/src/engine/html/shaders/shader.dart | 10 +++----- .../lib/src/engine/safe_browser_api.dart | 8 ++++--- .../html/shaders/gradient_golden_test.dart | 24 +++++++++---------- .../shaders/linear_gradient_golden_test.dart | 21 ++++++++++++++++ 7 files changed, 47 insertions(+), 26 deletions(-) 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));