From e331dcda1785982a9efbc762e08e9c617cd3f6ae Mon Sep 17 00:00:00 2001 From: Jonah Williams Date: Mon, 12 Dec 2022 15:30:17 -0800 Subject: [PATCH] [framework] make transform with filterQuality a rpb (#116792) * [framework] make transform with filterQuality a rpb * fix tests * ++ --- .../flutter/lib/src/rendering/proxy_box.dart | 104 +++++++++--------- .../animated_image_filtered_repaint_test.dart | 29 +++++ .../flutter/test/widgets/transform_test.dart | 2 +- 3 files changed, 81 insertions(+), 54 deletions(-) diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index f39436bc2007..15653800d6bb 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -2430,7 +2430,7 @@ class RenderTransform extends RenderProxyBox { return; } _origin = value; - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } @@ -2452,7 +2452,7 @@ class RenderTransform extends RenderProxyBox { return; } _alignment = value; - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } @@ -2467,10 +2467,13 @@ class RenderTransform extends RenderProxyBox { return; } _textDirection = value; - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } + @override + bool get isRepaintBoundary => alwaysNeedsCompositing; + @override bool get alwaysNeedsCompositing => child != null && _filterQuality != null; @@ -2495,7 +2498,7 @@ class RenderTransform extends RenderProxyBox { return; } _transform = Matrix4.copy(value); - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } @@ -2513,48 +2516,48 @@ class RenderTransform extends RenderProxyBox { if (didNeedCompositing != alwaysNeedsCompositing) { markNeedsCompositingBitsUpdate(); } - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); } /// Sets the transform to the identity matrix. void setIdentity() { _transform!.setIdentity(); - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } /// Concatenates a rotation about the x axis into the transform. void rotateX(double radians) { _transform!.rotateX(radians); - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } /// Concatenates a rotation about the y axis into the transform. void rotateY(double radians) { _transform!.rotateY(radians); - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } /// Concatenates a rotation about the z axis into the transform. void rotateZ(double radians) { _transform!.rotateZ(radians); - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } /// Concatenates a translation by (x, y, z) into the transform. void translate(double x, [ double y = 0.0, double z = 0.0 ]) { _transform!.translate(x, y, z); - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } /// Concatenates a scale into the transform. void scale(double x, [ double? y, double? z ]) { _transform!.scale(x, y, z); - markNeedsPaint(); + markNeedsCompositedLayerUpdate(); markNeedsSemanticsUpdate(); } @@ -2603,51 +2606,46 @@ class RenderTransform extends RenderProxyBox { ); } + @override + OffsetLayer updateCompositedLayer({required covariant ImageFilterLayer? oldLayer}) { + final ImageFilterLayer layer = oldLayer ?? ImageFilterLayer(); + layer.imageFilter = ui.ImageFilter.matrix( + _effectiveTransform!.storage, + filterQuality: filterQuality! + ); + return layer; + } + @override void paint(PaintingContext context, Offset offset) { - if (child != null) { - final Matrix4 transform = _effectiveTransform!; - if (filterQuality == null) { - final Offset? childOffset = MatrixUtils.getAsTranslation(transform); - if (childOffset == null) { - // if the matrix is singular the children would be compressed to a line or - // single point, instead short-circuit and paint nothing. - final double det = transform.determinant(); - if (det == 0 || !det.isFinite) { - layer = null; - return; - } - layer = context.pushTransform( - needsCompositing, - offset, - transform, - super.paint, - oldLayer: layer is TransformLayer ? layer as TransformLayer? : null, - ); - } else { - super.paint(context, offset + childOffset); - layer = null; - } - } else { - final Matrix4 effectiveTransform = Matrix4.translationValues(offset.dx, offset.dy, 0.0) - ..multiply(transform)..translate(-offset.dx, -offset.dy); - final ui.ImageFilter filter = ui.ImageFilter.matrix( - effectiveTransform.storage, - filterQuality: filterQuality!, - ); - if (layer is ImageFilterLayer) { - final ImageFilterLayer filterLayer = layer! as ImageFilterLayer; - filterLayer.imageFilter = filter; - } else { - layer = ImageFilterLayer(imageFilter: filter); - } - context.pushLayer(layer!, super.paint, offset); - assert(() { - layer!.debugCreator = debugCreator; - return true; - }()); - } + if (child == null) { + return; } + if (isRepaintBoundary) { + return super.paint(context, offset); + } + + final Matrix4 transform = _effectiveTransform!; + final Offset? childOffset = MatrixUtils.getAsTranslation(transform); + if (childOffset != null) { + super.paint(context, offset + childOffset); + layer = null; + return; + } + // if the matrix is singular the children would be compressed to a line or + // single point, instead short-circuit and paint nothing. + final double det = transform.determinant(); + if (det == 0 || !det.isFinite) { + layer = null; + return; + } + layer = context.pushTransform( + needsCompositing, + offset, + transform, + super.paint, + oldLayer: layer is TransformLayer ? layer as TransformLayer? : null, + ); } @override diff --git a/packages/flutter/test/widgets/animated_image_filtered_repaint_test.dart b/packages/flutter/test/widgets/animated_image_filtered_repaint_test.dart index afeecc8ad716..d3ed5601f499 100644 --- a/packages/flutter/test/widgets/animated_image_filtered_repaint_test.dart +++ b/packages/flutter/test/widgets/animated_image_filtered_repaint_test.dart @@ -35,6 +35,35 @@ void main() { expect(RenderTestObject.paintCount, 1); }); + + testWidgets('Transform with FilterQuality avoids repainting child as it animates', (WidgetTester tester) async { + RenderTestObject.paintCount = 0; + await tester.pumpWidget( + Container( + color: Colors.red, + child: Transform.translate( + offset: const Offset(5.0, 10.0), + filterQuality: FilterQuality.low, + child: const TestWidget(), + ), + ) + ); + + expect(RenderTestObject.paintCount, 1); + + await tester.pumpWidget( + Container( + color: Colors.red, + child: Transform.translate( + offset: const Offset(25.0, 0.0), + filterQuality: FilterQuality.low, + child: const TestWidget(), + ), + ) + ); + + expect(RenderTestObject.paintCount, 1); + }); } class TestWidget extends SingleChildRenderObjectWidget { diff --git a/packages/flutter/test/widgets/transform_test.dart b/packages/flutter/test/widgets/transform_test.dart index e677361c0b17..98758f8014c0 100644 --- a/packages/flutter/test/widgets/transform_test.dart +++ b/packages/flutter/test/widgets/transform_test.dart @@ -616,7 +616,7 @@ void main() { moreOrLessEquals(0.7071067811865476), moreOrLessEquals(0.7071067811865475), 0.0, 0.0, moreOrLessEquals(-0.7071067811865475), moreOrLessEquals(0.7071067811865476), 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, - moreOrLessEquals(329.28932188134524), moreOrLessEquals(-194.97474683058329), 0.0, 1.0, + moreOrLessEquals(50), moreOrLessEquals(-20.710678118654755), 0.0, 1.0, ]); });