Skip to content

Commit 791edc2

Browse files
authored
always pass filterQuality specified in the Image widget to canvas (flutter#74854)
1 parent dbb1958 commit 791edc2

File tree

4 files changed

+81
-27
lines changed

4 files changed

+81
-27
lines changed

packages/flutter/lib/src/painting/decoration_image.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -473,9 +473,7 @@ void paintImage({
473473
final Paint paint = Paint()..isAntiAlias = isAntiAlias;
474474
if (colorFilter != null)
475475
paint.colorFilter = colorFilter;
476-
if (sourceSize != destinationSize) {
477-
paint.filterQuality = filterQuality;
478-
}
476+
paint.filterQuality = filterQuality;
479477
paint.invertColors = invertColors;
480478
final double halfWidthDelta = (outputSize.width - destinationSize.width) / 2.0;
481479
final double halfHeightDelta = (outputSize.height - destinationSize.height) / 2.0;

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

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -312,10 +312,9 @@ class Image extends StatefulWidget {
312312
/// Otherwise, the image dimensions will change as the image is loaded, which
313313
/// will result in ugly layout changes.
314314
///
315-
/// Use [filterQuality] to change the quality when scaling an image.
316-
/// Use the [FilterQuality.low] quality setting to scale the image,
317-
/// which corresponds to bilinear interpolation, rather than the default
318-
/// [FilterQuality.none] which corresponds to nearest-neighbor.
315+
/// {@template flutter.widgets.image.filterQualityParameter}
316+
/// Use [filterQuality] to specify the rendering quality of the image.
317+
/// {@endtemplate}
319318
///
320319
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
321320
const Image({
@@ -360,10 +359,7 @@ class Image extends StatefulWidget {
360359
/// An optional [headers] argument can be used to send custom HTTP headers
361360
/// with the image request.
362361
///
363-
/// Use [filterQuality] to change the quality when scaling an image.
364-
/// Use the [FilterQuality.low] quality setting to scale the image,
365-
/// which corresponds to bilinear interpolation, rather than the default
366-
/// [FilterQuality.none] which corresponds to nearest-neighbor.
362+
/// {@macro flutter.widgets.image.filterQualityParameter}
367363
///
368364
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
369365
///
@@ -424,10 +420,7 @@ class Image extends StatefulWidget {
424420
/// On Android, this may require the
425421
/// `android.permission.READ_EXTERNAL_STORAGE` permission.
426422
///
427-
/// Use [filterQuality] to change the quality when scaling an image.
428-
/// Use the [FilterQuality.low] quality setting to scale the image,
429-
/// which corresponds to bilinear interpolation, rather than the default
430-
/// [FilterQuality.none] which corresponds to nearest-neighbor.
423+
/// {@macro flutter.widgets.image.filterQualityParameter}
431424
///
432425
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
433426
///
@@ -520,10 +513,7 @@ class Image extends StatefulWidget {
520513
/// Otherwise, the image dimensions will change as the image is loaded, which
521514
/// will result in ugly layout changes.
522515
///
523-
/// Use [filterQuality] to change the quality when scaling an image.
524-
/// Use the [FilterQuality.low] quality setting to scale the image,
525-
/// which corresponds to bilinear interpolation, rather than the default
526-
/// [FilterQuality.none] which corresponds to nearest-neighbor.
516+
/// {@macro flutter.widgets.image.filterQualityParameter}
527517
///
528518
/// {@tool snippet}
529519
///
@@ -667,10 +657,7 @@ class Image extends StatefulWidget {
667657
/// Otherwise, the image dimensions will change as the image is loaded, which
668658
/// will result in ugly layout changes.
669659
///
670-
/// Use [filterQuality] to change the quality when scaling an image.
671-
/// Use the [FilterQuality.low] quality setting to scale the image,
672-
/// which corresponds to bilinear interpolation, rather than the default
673-
/// [FilterQuality.none] which corresponds to nearest-neighbor.
660+
/// {@macro flutter.widgets.image.filterQualityParameter}
674661
///
675662
/// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
676663
///
@@ -930,11 +917,22 @@ class Image extends StatefulWidget {
930917
/// If non-null, this color is blended with each image pixel using [colorBlendMode].
931918
final Color? color;
932919

933-
/// Used to set the [FilterQuality] of the image.
920+
/// The rendering quality of the image.
921+
///
922+
/// If the image is of a high quality and its pixels are perfectly aligned
923+
/// with the physical screen pixels, extra quality enhancement may not be
924+
/// necessary. If so, then [FilterQuality.none] would be the most efficient.
925+
///
926+
/// If the pixels are not perfectly aligned with the screen pixels, or if the
927+
/// image itself is of a low quality, [FilterQuality.none] may produce
928+
/// undesirable artifacts. Consider using other [FilterQuality] values to
929+
/// improve the rendered image quality in this case. Pixels may be misaligned
930+
/// with the screen pixels as a result of transforms or scaling.
931+
///
932+
/// See also:
934933
///
935-
/// Use the [FilterQuality.low] quality setting to scale the image with
936-
/// bilinear interpolation, or the [FilterQuality.none] which corresponds
937-
/// to nearest-neighbor.
934+
/// * [FilterQuality], the enum containing all possible filter quality
935+
/// options.
938936
final FilterQuality filterQuality;
939937

940938
/// Used to combine [color] with this image.

packages/flutter/test/widgets/image_test.dart

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1877,6 +1877,61 @@ void main() {
18771877
expect(tester.widget<Padding>(find.byType(Padding)).child, isA<RawImage>());
18781878
expect(find.byKey(errorKey), findsNothing);
18791879
});
1880+
1881+
testWidgets('Image at default filterQuality', (WidgetTester tester) async {
1882+
await testImageQuality(tester, null);
1883+
});
1884+
1885+
testWidgets('Image at high filterQuality', (WidgetTester tester) async {
1886+
await testImageQuality(tester, ui.FilterQuality.high);
1887+
});
1888+
1889+
testWidgets('Image at none filterQuality', (WidgetTester tester) async {
1890+
await testImageQuality(tester, ui.FilterQuality.none);
1891+
});
1892+
}
1893+
1894+
Future<void> testImageQuality(WidgetTester tester, ui.FilterQuality? quality) async {
1895+
await tester.binding.setSurfaceSize(const ui.Size(3, 3));
1896+
// A 3x3 image encoded as PNG with white background and black pixels on the diagonal:
1897+
// ┌──────┐
1898+
// │▓▓ │
1899+
// │ ▓▓ │
1900+
// │ ▓▓│
1901+
// └──────┘
1902+
// At different levels of quality these pixels are blurred differently.
1903+
final Uint8List test3x3Image = Uint8List.fromList(<int>[
1904+
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a, 0x00, 0x00, 0x00, 0x0d,
1905+
0x49, 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x03, 0x00, 0x00, 0x00, 0x03,
1906+
0x08, 0x02, 0x00, 0x00, 0x00, 0xd9, 0x4a, 0x22, 0xe8, 0x00, 0x00, 0x00,
1907+
0x1b, 0x49, 0x44, 0x41, 0x54, 0x08, 0xd7, 0x63, 0x64, 0x60, 0x60, 0xf8,
1908+
0xff, 0xff, 0x3f, 0x03, 0x9c, 0xfa, 0xff, 0xff, 0x3f, 0xc3, 0xff, 0xff,
1909+
0xff, 0x21, 0x1c, 0x00, 0xcb, 0x70, 0x0e, 0xf3, 0x5d, 0x11, 0xc2, 0xf8,
1910+
0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4e, 0x44, 0xae, 0x42, 0x60, 0x82,
1911+
]);
1912+
final ui.Image image = (await tester.runAsync(() async {
1913+
final ui.Codec codec = await ui.instantiateImageCodec(test3x3Image);
1914+
return (await codec.getNextFrame()).image;
1915+
}))!;
1916+
expect(image.width, 3);
1917+
expect(image.height, 3);
1918+
final TestImageStreamCompleter streamCompleter = TestImageStreamCompleter();
1919+
streamCompleter.setData(imageInfo: ImageInfo(image: image));
1920+
final TestImageProvider imageProvider = TestImageProvider(streamCompleter: streamCompleter);
1921+
1922+
await tester.pumpWidget(
1923+
quality == null
1924+
? Image(image: imageProvider)
1925+
: Image(
1926+
image: imageProvider,
1927+
filterQuality: quality,
1928+
),
1929+
);
1930+
1931+
await expectLater(
1932+
find.byType(Image),
1933+
matchesGoldenFile('image_quality_${quality ?? 'default'}.png'),
1934+
);
18801935
}
18811936

18821937
class ImagePainter extends CustomPainter {

packages/flutter_test/lib/src/animation_sheet.dart

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,9 @@ class AnimationSheetBuilder {
169169
image: image.clone(),
170170
width: frameSize.width,
171171
height: frameSize.height,
172+
// Disable quality enhancement because the point of this class is to
173+
// precisely record what the widget looks like.
174+
filterQuality: ui.FilterQuality.none,
172175
)).toList(),
173176
);
174177
}

0 commit comments

Comments
 (0)