Skip to content

Commit 8e3ea14

Browse files
authored
Incorrect rendering of SnapshotWidget (#114400)
* add tests * try to fix * fix compile error * refactor code * rename * doc * refactor test * simple rename
1 parent 2e51077 commit 8e3ea14

File tree

3 files changed

+59
-12
lines changed

3 files changed

+59
-12
lines changed

packages/flutter/lib/src/material/page_transitions_theme.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -907,7 +907,7 @@ class _ZoomEnterTransitionPainter extends SnapshotPainter {
907907
}
908908

909909
@override
910-
void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, double pixelRatio) {
910+
void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, Size sourceSize, double pixelRatio) {
911911
_drawScrim(context, offset, size);
912912
_drawImageScaledAndCentered(context, image, scale.value, fade.value, pixelRatio);
913913
}
@@ -957,7 +957,7 @@ class _ZoomExitTransitionPainter extends SnapshotPainter {
957957
final LayerHandle<TransformLayer> _transformHandler = LayerHandle<TransformLayer>();
958958

959959
@override
960-
void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, double pixelRatio) {
960+
void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, Size sourceSize, double pixelRatio) {
961961
_drawImageScaledAndCentered(context, image, scale.value, fade.value, pixelRatio);
962962
}
963963

packages/flutter/lib/src/widgets/snapshot_widget.dart

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -231,6 +231,7 @@ class _RenderSnapshotWidget extends RenderProxyBox {
231231
}
232232

233233
ui.Image? _childRaster;
234+
Size? _childRasterSize;
234235
// Set to true if the snapshot mode was not forced and a platform view
235236
// was encountered while attempting to snapshot the child.
236237
bool _disableSnapshotAttempt = false;
@@ -249,6 +250,7 @@ class _RenderSnapshotWidget extends RenderProxyBox {
249250
painter.removeListener(markNeedsPaint);
250251
_childRaster?.dispose();
251252
_childRaster = null;
253+
_childRasterSize = null;
252254
super.detach();
253255
}
254256

@@ -258,13 +260,15 @@ class _RenderSnapshotWidget extends RenderProxyBox {
258260
painter.removeListener(markNeedsPaint);
259261
_childRaster?.dispose();
260262
_childRaster = null;
263+
_childRasterSize = null;
261264
super.dispose();
262265
}
263266

264267
void _onRasterValueChanged() {
265268
_disableSnapshotAttempt = false;
266269
_childRaster?.dispose();
267270
_childRaster = null;
271+
_childRasterSize = null;
268272
markNeedsPaint();
269273
}
270274

@@ -296,19 +300,24 @@ class _RenderSnapshotWidget extends RenderProxyBox {
296300
if (size.isEmpty) {
297301
_childRaster?.dispose();
298302
_childRaster = null;
303+
_childRasterSize = null;
299304
return;
300305
}
301306
if (!controller.allowSnapshotting || _disableSnapshotAttempt) {
302307
_childRaster?.dispose();
303308
_childRaster = null;
309+
_childRasterSize = null;
304310
painter.paint(context, offset, size, super.paint);
305311
return;
306312
}
307-
_childRaster ??= _paintAndDetachToImage();
313+
if (_childRaster == null) {
314+
_childRaster = _paintAndDetachToImage();
315+
_childRasterSize = size * devicePixelRatio;
316+
}
308317
if (_childRaster == null) {
309318
painter.paint(context, offset, size, super.paint);
310319
} else {
311-
painter.paintSnapshot(context, offset, size, _childRaster!, devicePixelRatio);
320+
painter.paintSnapshot(context, offset, size, _childRaster!, _childRasterSize!, devicePixelRatio);
312321
}
313322
}
314323
}
@@ -356,19 +365,21 @@ abstract class SnapshotPainter extends ChangeNotifier {
356365
/// [SnapshotPainter] paints the snapshot. This must account for the fact that the image
357366
/// width and height will be given in physical pixels, while the image must be painted with
358367
/// device independent pixels. That is, the width and height of the image is the widget and
359-
/// height of the provided `size`, multiplied by the `pixelRatio`:
368+
/// height of the provided `size`, multiplied by the `pixelRatio`. In addition, the actual
369+
/// size of the scene captured by the `image` is not `image.width` or `image.height`, but
370+
/// indeed `sourceSize`, because the former is a rounded inaccurate integer:
360371
///
361372
/// ```dart
362-
/// void paint(PaintingContext context, Offset offset, Size size, ui.Image image, double pixelRatio) {
363-
/// final Rect src = Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
373+
/// void paint(PaintingContext context, Offset offset, Size size, ui.Image image, Size sourceSize, double pixelRatio) {
374+
/// final Rect src = Rect.fromLTWH(0, 0, sourceSize.width, sourceSize.height);
364375
/// final Rect dst = Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height);
365376
/// final Paint paint = Paint()
366377
/// ..filterQuality = FilterQuality.low;
367378
/// context.canvas.drawImageRect(image, src, dst, paint);
368379
/// }
369380
/// ```
370381
/// {@end-tool}
371-
void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, double pixelRatio);
382+
void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, Size sourceSize, double pixelRatio);
372383

373384
/// Paint the child via [painter], applying any effects that would have been painted
374385
/// in [SnapshotPainter.paintSnapshot].
@@ -427,8 +438,8 @@ class _DefaultSnapshotPainter implements SnapshotPainter {
427438
}
428439

429440
@override
430-
void paintSnapshot(PaintingContext context, ui.Offset offset, ui.Size size, ui.Image image, double pixelRatio) {
431-
final Rect src = Rect.fromLTWH(0, 0, image.width.toDouble(), image.height.toDouble());
441+
void paintSnapshot(PaintingContext context, ui.Offset offset, ui.Size size, ui.Image image, Size sourceSize, double pixelRatio) {
442+
final Rect src = Rect.fromLTWH(0, 0, sourceSize.width, sourceSize.height);
432443
final Rect dst = Rect.fromLTWH(offset.dx, offset.dy, size.width, size.height);
433444
final Paint paint = Paint()
434445
..filterQuality = FilterQuality.low;

packages/flutter/test/widgets/snapshot_widget_test.dart

Lines changed: 38 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@
99
import 'dart:ui' as ui;
1010

1111
import 'package:flutter/foundation.dart';
12+
import 'package:flutter/material.dart';
1213
import 'package:flutter/rendering.dart';
13-
import 'package:flutter/widgets.dart';
1414
import 'package:flutter_test/flutter_test.dart';
1515

1616
void main() {
@@ -261,6 +261,42 @@ void main() {
261261
expect(tester.takeException(), isNull);
262262
expect(tester.layers.last, isA<PlatformViewLayer>());
263263
}, skip: kIsWeb); // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
264+
265+
testWidgets('SnapshotWidget should have same result when enabled', (WidgetTester tester) async {
266+
tester.binding.window
267+
..physicalSizeTestValue = const Size(10, 10)
268+
..devicePixelRatioTestValue = 1;
269+
addTearDown(() => tester.binding.window
270+
..clearPhysicalSizeTestValue()
271+
..clearDevicePixelRatioTestValue());
272+
273+
const ValueKey<String> repaintBoundaryKey = ValueKey<String>('boundary');
274+
final SnapshotController controller = SnapshotController();
275+
await tester.pumpWidget(RepaintBoundary(
276+
key: repaintBoundaryKey,
277+
child: MaterialApp(
278+
debugShowCheckedModeBanner: false,
279+
home: Container(
280+
color: Colors.black,
281+
padding: const EdgeInsets.only(right: 0.6, bottom: 0.6),
282+
child: SnapshotWidget(
283+
controller: controller,
284+
child: Container(
285+
margin: const EdgeInsets.only(right: 0.4, bottom: 0.4),
286+
color: Colors.blue,
287+
),
288+
),
289+
),
290+
),
291+
));
292+
293+
final ui.Image imageWhenDisabled = (tester.renderObject(find.byKey(repaintBoundaryKey)) as RenderRepaintBoundary).toImageSync();
294+
295+
controller.allowSnapshotting = true;
296+
await tester.pump();
297+
298+
await expectLater(find.byKey(repaintBoundaryKey), matchesReferenceImage(imageWhenDisabled));
299+
}, skip: kIsWeb); // TODO(jonahwilliams): https://github.com/flutter/flutter/issues/106689
264300
}
265301

266302
class TestController extends SnapshotController {
@@ -321,7 +357,7 @@ class TestPainter extends SnapshotPainter {
321357
}
322358

323359
@override
324-
void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, double pixelRatio) {
360+
void paintSnapshot(PaintingContext context, Offset offset, Size size, ui.Image image, Size sourceSize, double pixelRatio) {
325361
count += 1;
326362
lastImage = image;
327363
}

0 commit comments

Comments
 (0)