diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 9abff13f9e5a8..9e3ef90741413 100644 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -452,6 +452,7 @@ FILE: ../../../flutter/lib/web_ui/lib/src/engine/shadow.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/clip.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/debug_canvas_reuse_overlay.dart +FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/image_filter.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/offset.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/opacity.dart FILE: ../../../flutter/lib/web_ui/lib/src/engine/surface/painting.dart diff --git a/lib/web_ui/dev/goldens_lock.yaml b/lib/web_ui/dev/goldens_lock.yaml index 3a6e72bb95980..6697a40e4bd90 100644 --- a/lib/web_ui/dev/goldens_lock.yaml +++ b/lib/web_ui/dev/goldens_lock.yaml @@ -1,2 +1,2 @@ repository: https://github.com/flutter/goldens.git -revision: ab1f2da642d6c5188b312965759ce7157fd073b9 +revision: a121ff1169a1b478274a3e34c95d0a1d2d685948 diff --git a/lib/web_ui/lib/src/engine.dart b/lib/web_ui/lib/src/engine.dart index 092a6cbcb621b..14808e413d86a 100644 --- a/lib/web_ui/lib/src/engine.dart +++ b/lib/web_ui/lib/src/engine.dart @@ -88,6 +88,7 @@ part 'engine/shadow.dart'; part 'engine/surface/backdrop_filter.dart'; part 'engine/surface/clip.dart'; part 'engine/surface/debug_canvas_reuse_overlay.dart'; +part 'engine/surface/image_filter.dart'; part 'engine/surface/offset.dart'; part 'engine/surface/opacity.dart'; part 'engine/surface/painting.dart'; diff --git a/lib/web_ui/lib/src/engine/compositor/layer.dart b/lib/web_ui/lib/src/engine/compositor/layer.dart index 8656ee8907b0e..2c0b4f0ddfc6d 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer.dart @@ -343,6 +343,23 @@ class TransformLayer extends ContainerLayer } } +/// A layer that applies an [ui.ImageFilter] to its children. +class ImageFilterLayer extends ContainerLayer implements ui.OpacityEngineLayer { + ImageFilterLayer(this._filter); + + final ui.ImageFilter _filter; + + @override + void paint(PaintContext paintContext) { + assert(needsPainting); + final ui.Paint paint = ui.Paint(); + paint.imageFilter = _filter; + paintContext.internalNodesCanvas.saveLayer(paintBounds, paint); + paintChildren(paintContext); + paintContext.internalNodesCanvas.restore(); + } +} + /// A layer containing a [Picture]. class PictureLayer extends Layer { /// The picture to paint into the canvas. diff --git a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart index 568b29860f44a..d1b8de526c396 100644 --- a/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart +++ b/lib/web_ui/lib/src/engine/compositor/layer_scene_builder.dart @@ -147,7 +147,8 @@ class LayerSceneBuilder implements ui.SceneBuilder { ui.ImageFilterEngineLayer oldLayer, }) { assert(filter != null); - throw UnimplementedError(); + pushLayer(ImageFilterLayer(filter)); + return null; } @override diff --git a/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart b/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart index efb7b79de3af8..2813b495a973e 100644 --- a/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart +++ b/lib/web_ui/lib/src/engine/surface/backdrop_filter.dart @@ -10,7 +10,7 @@ class PersistedBackdropFilter extends PersistedContainerSurface PersistedBackdropFilter(PersistedBackdropFilter oldLayer, this.filter) : super(oldLayer); - final ui.ImageFilter filter; + final EngineImageFilter filter; /// The dedicated child container element that's separate from the /// [rootElement] is used to host child in front of [filterElement] that @@ -24,10 +24,6 @@ class PersistedBackdropFilter extends PersistedContainerSurface // Reference to transform last used to cache [_invertedTransform]. Matrix4 _previousTransform; - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - @override void adoptElements(PersistedBackdropFilter oldSurface) { super.adoptElements(oldSurface); @@ -85,12 +81,10 @@ class PersistedBackdropFilter extends PersistedContainerSurface ..backgroundColor = '#000' ..opacity = '0.2'; } else { - final EngineImageFilter engineFilter = filter; // CSS uses pixel radius for blur. Flutter & SVG use sigma parameters. For // Gaussian blur with standard deviation (normal distribution), // the blur will fall within 2 * sigma pixels. - domRenderer.setElementStyle(_filterElement, 'backdrop-filter', - 'blur(${math.max(engineFilter.sigmaX, engineFilter.sigmaY) * 2}px)'); + domRenderer.setElementStyle(_filterElement, 'backdrop-filter', _imageFilterToCss(filter)); } } diff --git a/lib/web_ui/lib/src/engine/surface/clip.dart b/lib/web_ui/lib/src/engine/surface/clip.dart index d22d74c6576ba..8bafbc58c1d3f 100644 --- a/lib/web_ui/lib/src/engine/surface/clip.dart +++ b/lib/web_ui/lib/src/engine/surface/clip.dart @@ -71,10 +71,6 @@ class PersistedClipRect extends PersistedContainerSurface _projectedClip = null; } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - @override html.Element createElement() { return super.createElement()..setAttribute('clip-type', 'rect'); @@ -122,10 +118,6 @@ class PersistedClipRRect extends PersistedContainerSurface _projectedClip = null; } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - @override html.Element createElement() { return super.createElement()..setAttribute('clip-type', 'rrect'); @@ -193,10 +185,6 @@ class PersistedPhysicalShape extends PersistedContainerSurface _projectedClip = null; } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - void _applyColor() { rootElement.style.backgroundColor = color.toCssString(); } @@ -350,10 +338,6 @@ class PersistedClipPath extends PersistedContainerSurface _localClipBounds ??= clipPath.getBounds(); } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - @override void apply() { if (clipPath == null) { diff --git a/lib/web_ui/lib/src/engine/surface/image_filter.dart b/lib/web_ui/lib/src/engine/surface/image_filter.dart new file mode 100644 index 0000000000000..105d1d868f7a4 --- /dev/null +++ b/lib/web_ui/lib/src/engine/surface/image_filter.dart @@ -0,0 +1,32 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +part of engine; + +/// A surface that applies an [imageFilter] to its children. +class PersistedImageFilter extends PersistedContainerSurface + implements ui.ImageFilterEngineLayer { + PersistedImageFilter(PersistedImageFilter oldLayer, this.filter) : super(oldLayer); + + final ui.ImageFilter filter; + + @override + html.Element createElement() { + return defaultCreateElement('flt-image-filter'); + } + + @override + void apply() { + rootElement.style.filter = _imageFilterToCss(filter); + } + + @override + void update(PersistedImageFilter oldSurface) { + super.update(oldSurface); + + if (oldSurface.filter != filter) { + apply(); + } + } +} diff --git a/lib/web_ui/lib/src/engine/surface/picture.dart b/lib/web_ui/lib/src/engine/surface/picture.dart index af7d20c61325f..a26b2fcd4e68e 100644 --- a/lib/web_ui/lib/src/engine/surface/picture.dart +++ b/lib/web_ui/lib/src/engine/surface/picture.dart @@ -100,10 +100,6 @@ class PersistedHoudiniPicture extends PersistedPicture { return existingSurface.picture == picture ? 0.0 : 1.0; } - @override - Matrix4 get localTransformInverse => - _localTransformInverse ??= Matrix4.identity(); - static void _registerCssPainter() { _cssPainterRegistered = true; final dynamic css = js_util.getProperty(html.window, 'CSS'); diff --git a/lib/web_ui/lib/src/engine/surface/scene_builder.dart b/lib/web_ui/lib/src/engine/surface/scene_builder.dart index bda572b06145d..b3aaa14d75dad 100644 --- a/lib/web_ui/lib/src/engine/surface/scene_builder.dart +++ b/lib/web_ui/lib/src/engine/surface/scene_builder.dart @@ -189,7 +189,7 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { ui.ImageFilterEngineLayer oldLayer, }) { assert(filter != null); - throw UnimplementedError(); + return _pushSurface(PersistedImageFilter(oldLayer, filter)); } /// Pushes a backdrop filter operation onto the operation stack. @@ -537,3 +537,10 @@ class SurfaceSceneBuilder implements ui.SceneBuilder { throw UnimplementedError(); } } + +// TODO(yjbanov): in HTML the blur looks too aggressive. The current +// implementation was copied from the existing backdrop-filter +// but probably needs a revision. +String _imageFilterToCss(EngineImageFilter filter) { + return 'blur(${math.max(filter.sigmaX, filter.sigmaY) * 2}px)'; +} diff --git a/lib/web_ui/lib/src/engine/surface/surface.dart b/lib/web_ui/lib/src/engine/surface/surface.dart index dc2df2d557416..2aceb0a0f982d 100644 --- a/lib/web_ui/lib/src/engine/surface/surface.dart +++ b/lib/web_ui/lib/src/engine/surface/surface.dart @@ -804,7 +804,12 @@ abstract class PersistedSurface implements ui.EngineLayer { // Matrix only contains local transform (not chain multiplied since root). Matrix4 _localTransformInverse; - Matrix4 get localTransformInverse; + /// The inverse of the local transform that this surface applies to its children. + /// + /// The default implementation is identity transform. Concrete + /// implementations may override this getter to supply a different transform. + Matrix4 get localTransformInverse => + _localTransformInverse ??= Matrix4.identity(); /// Recomputes [transform] and [globalClip] fields. /// diff --git a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart index 568896a3882a6..b45200c07d164 100644 --- a/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart +++ b/lib/web_ui/test/golden_tests/engine/compositing_golden_test.dart @@ -98,6 +98,19 @@ void main() async { await matchGoldenFile('compositing_shifted_physical_shape_clip.png', region: region); }, timeout: const Timeout(Duration(seconds: 10))); + test('pushImageFilter', () async { + final SurfaceSceneBuilder builder = SurfaceSceneBuilder(); + builder.pushImageFilter( + ImageFilter.blur(sigmaX: 1, sigmaY: 3), + ); + _drawTestPicture(builder); + builder.pop(); + + html.document.body.append(builder.build().webOnlyRootElement); + + await matchGoldenFile('compositing_image_filter.png', region: region); + }, timeout: const Timeout(Duration(seconds: 10))); + group('Cull rect computation', () { _testCullRectComputation(); });