From 79cfe9f1c416f335c031b5e8f1791e5e637134f4 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 7 Mar 2022 15:08:46 -0800 Subject: [PATCH 1/4] Fix issues with nested gradients in html renderer. https://github.com/flutter/flutter/issues/99045 The DOM canvas does need to be able to handle gradient shaders, since if we are inside of a context that is affected by an SVG filter we can't fall back to the using canvas. This is because webkit doesn't apply the SVG filters to canvas elements for some reason. In these situations, just do a full GL render of the gradient and apply it as the background image on the DOM element. --- .../lib/src/engine/html/dom_canvas.dart | 29 ++++++++++++------- .../lib/src/engine/html/shader_mask.dart | 7 ++++- .../lib/src/engine/html/shaders/shader.dart | 8 ----- 3 files changed, 25 insertions(+), 19 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/dom_canvas.dart b/lib/web_ui/lib/src/engine/html/dom_canvas.dart index 0d3250e27586b..c834dc28c179d 100644 --- a/lib/web_ui/lib/src/engine/html/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/dom_canvas.dart @@ -19,6 +19,7 @@ import 'painting.dart'; import 'path/path.dart'; import 'path/path_to_svg.dart'; import 'shaders/image_shader.dart'; +import 'shaders/shader.dart'; /// A canvas that renders to DOM elements and CSS properties. class DomCanvas extends EngineCanvas with SaveElementStackTracking { @@ -162,7 +163,6 @@ ui.Color blurColor(ui.Color color, double sigma) { html.HtmlElement buildDrawRectElement( ui.Rect rect, SurfacePaintData paint, String tagName, Matrix4 transform) { - assert(paint.shader == null); final html.HtmlElement rectangle = html.document.createElement(tagName) as html.HtmlElement; assert(() { @@ -226,19 +226,28 @@ html.HtmlElement buildDrawRectElement( style ..width = '${right - left}px' ..height = '${bottom - top}px' - ..backgroundColor = cssColor; - - if (paint.shader != null && paint.shader is EngineImageShader) { - _applyImageShaderToElement(rectangle, paint.shader! as EngineImageShader); - } + ..backgroundColor = cssColor + ..backgroundImage = _getBackgroundImage(paint.shader, rect); } return rectangle; } -void _applyImageShaderToElement(html.HtmlElement targetElement, - EngineImageShader imageShader) { - final HtmlImage image = imageShader.image; - targetElement.style.backgroundImage = image.imgElement.src; +String _getBackgroundImage(ui.Shader? shader, ui.Rect bounds) { + final String url = _getBackgroundImageUrl(shader, bounds); + return (url != '') ? "url('$url'": ''; +} + +String _getBackgroundImageUrl(ui.Shader? shader, ui.Rect bounds) { + if(shader != null) { + if(shader is EngineImageShader) { + return shader.image.imgElement.src ?? ''; + } + + if(shader is EngineGradient) { + return shader.createImageBitmap(bounds, 1, true) as String; + } + } + return ''; } void applyRRectBorderRadius(html.CssStyleDeclaration style, ui.RRect rrect) { diff --git a/lib/web_ui/lib/src/engine/html/shader_mask.dart b/lib/web_ui/lib/src/engine/html/shader_mask.dart index 7f27cd7b22fa6..977f0f9c70bd4 100644 --- a/lib/web_ui/lib/src/engine/html/shader_mask.dart +++ b/lib/web_ui/lib/src/engine/html/shader_mask.dart @@ -108,8 +108,13 @@ class PersistedShaderMask extends PersistedContainerSurface void _applyGradientShader() { if (shader is EngineGradient) { final EngineGradient gradientShader = shader as EngineGradient; + + // The gradient shader's bounds are in the context of the element itself, + // rather than the global position, so translate it back to the origin. + final ui.Rect translatedRect = + maskRect.translate(-maskRect.left, -maskRect.top); final String imageUrl = - gradientShader.createImageBitmap(maskRect, 1, true) as String; + gradientShader.createImageBitmap(translatedRect, 1, true) as String; ui.BlendMode blendModeTemp = blendMode; switch (blendModeTemp) { case ui.BlendMode.clear: 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 c80cdfde046b7..9d54123b5e8c6 100644 --- a/lib/web_ui/lib/src/engine/html/shaders/shader.dart +++ b/lib/web_ui/lib/src/engine/html/shaders/shader.dart @@ -234,14 +234,6 @@ class GradientLinear extends EngineGradient { _createLinearFragmentShader(normalizedGradient, tileMode)); gl.useProgram(glProgram); - /// When creating an image to apply to a dom element, render - /// contents at 0,0 and adjust gradient vector for shaderBounds. - final bool translateToOrigin = createDataUrl; - - if (translateToOrigin) { - shaderBounds = shaderBounds.translate(-shaderBounds.left, -shaderBounds.top); - } - // Setup from/to uniforms. // // From/to is relative to shaderBounds. From 94bb40775c9e66857bcc058280300783f95563d5 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 7 Mar 2022 16:05:10 -0800 Subject: [PATCH 2/4] Add test that ensures we can render gradients from within an svg context. --- .../html/shaders/gradient_golden_test.dart | 34 +++++++++++++++++++ 1 file changed, 34 insertions(+) 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 599ae80754026..a04c7c859f680 100644 --- a/lib/web_ui/test/html/shaders/gradient_golden_test.dart +++ b/lib/web_ui/test/html/shaders/gradient_golden_test.dart @@ -430,6 +430,40 @@ Future testMain() async { canvas.restore(); await _checkScreenshot(canvas, 'linear_gradient_rect_clamp_rotated'); }); + + test('Paints linear gradient properly when within svg context', () async { + final RecordingCanvas canvas = + RecordingCanvas(const Rect.fromLTRB(0, 0, 500, 240)); + canvas.save(); + + canvas.renderStrategy.isInsideSvgFilterTree = true; + + final SurfacePaint borderPaint = SurfacePaint() + ..style = PaintingStyle.stroke + ..strokeWidth = 1 + ..color = const Color(0xFF000000); + + const List colors = [ + Color(0xFFFF0000), + Color(0xFF0000FF), + ]; + + final GradientLinear linearGradient = GradientLinear(const Offset(125, 75), + const Offset(175, 125), + colors, null, TileMode.clamp, + Matrix4.identity().storage); + + const double kBoxWidth = 150; + const double kBoxHeight = 100; + // Gradient with default center. + const Rect rectBounds = Rect.fromLTWH(100, 50, kBoxWidth, kBoxHeight); + canvas.drawRect(rectBounds, + SurfacePaint()..shader = engineLinearGradientToShader(linearGradient, rectBounds)); + canvas.drawRect(rectBounds, borderPaint); + + canvas.restore(); + await _checkScreenshot(canvas, 'linear_gradient_in_svg_context'); + }); } Shader engineGradientToShader(GradientSweep gradient, Rect rect) { From c00cd8d10347b80764fa7b0f0875a7c1e0a5a0a3 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Mon, 7 Mar 2022 17:16:16 -0800 Subject: [PATCH 3/4] Fix analyzer issue. --- lib/web_ui/lib/src/engine/html/dom_canvas.dart | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/web_ui/lib/src/engine/html/dom_canvas.dart b/lib/web_ui/lib/src/engine/html/dom_canvas.dart index c834dc28c179d..7ec92d97facc1 100644 --- a/lib/web_ui/lib/src/engine/html/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/dom_canvas.dart @@ -11,7 +11,6 @@ import 'package:ui/ui.dart' as ui; import '../browser_detection.dart'; import '../engine_canvas.dart'; -import '../html_image_codec.dart'; import '../text/canvas_paragraph.dart'; import '../util.dart'; import '../vector_math.dart'; From 3cdc502e96f76e9f927937109d1030afabfb8353 Mon Sep 17 00:00:00 2001 From: Jackson Gardner Date: Tue, 8 Mar 2022 16:25:44 -0800 Subject: [PATCH 4/4] Change function name. --- lib/web_ui/lib/src/engine/html/dom_canvas.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/web_ui/lib/src/engine/html/dom_canvas.dart b/lib/web_ui/lib/src/engine/html/dom_canvas.dart index 7ec92d97facc1..a07a15534664f 100644 --- a/lib/web_ui/lib/src/engine/html/dom_canvas.dart +++ b/lib/web_ui/lib/src/engine/html/dom_canvas.dart @@ -226,12 +226,12 @@ html.HtmlElement buildDrawRectElement( ..width = '${right - left}px' ..height = '${bottom - top}px' ..backgroundColor = cssColor - ..backgroundImage = _getBackgroundImage(paint.shader, rect); + ..backgroundImage = _getBackgroundImageCssValue(paint.shader, rect); } return rectangle; } -String _getBackgroundImage(ui.Shader? shader, ui.Rect bounds) { +String _getBackgroundImageCssValue(ui.Shader? shader, ui.Rect bounds) { final String url = _getBackgroundImageUrl(shader, bounds); return (url != '') ? "url('$url'": ''; }