Set AA flag for painting images (#51656)

This commit is contained in:
liyuqian 2020-04-08 19:02:04 -07:00 committed by GitHub
parent 1593788cd9
commit b475eaf8ba
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 152 additions and 1 deletions

View file

@ -386,12 +386,14 @@ void paintImage({
bool flipHorizontally = false,
bool invertColors = false,
FilterQuality filterQuality = FilterQuality.low,
bool isAntiAlias = false,
}) {
assert(canvas != null);
assert(image != null);
assert(alignment != null);
assert(repeat != null);
assert(flipHorizontally != null);
assert(isAntiAlias != null);
if (rect.isEmpty)
return;
Size outputSize = rect.size;
@ -422,7 +424,7 @@ void paintImage({
// output rect with the image.
repeat = ImageRepeat.noRepeat;
}
final Paint paint = Paint()..isAntiAlias = false;
final Paint paint = Paint()..isAntiAlias = isAntiAlias;
if (colorFilter != null)
paint.colorFilter = colorFilter;
if (sourceSize != destinationSize) {

View file

@ -38,12 +38,14 @@ class RenderImage extends RenderBox {
bool matchTextDirection = false,
TextDirection textDirection,
bool invertColors = false,
bool isAntiAlias = false,
FilterQuality filterQuality = FilterQuality.low,
}) : assert(scale != null),
assert(repeat != null),
assert(alignment != null),
assert(filterQuality != null),
assert(matchTextDirection != null),
assert(isAntiAlias != null),
_image = image,
_width = width,
_height = height,
@ -57,6 +59,7 @@ class RenderImage extends RenderBox {
_matchTextDirection = matchTextDirection,
_invertColors = invertColors,
_textDirection = textDirection,
_isAntiAlias = isAntiAlias,
_filterQuality = filterQuality {
_updateColorFilter();
}
@ -287,6 +290,20 @@ class RenderImage extends RenderBox {
_markNeedResolution();
}
/// Whether to paint the image with anti-aliasing.
///
/// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
bool get isAntiAlias => _isAntiAlias;
bool _isAntiAlias;
set isAntiAlias(bool value) {
if (_isAntiAlias == value) {
return;
}
assert(value != null);
_isAntiAlias = value;
markNeedsPaint();
}
/// Find a size for the render image within the given constraints.
///
/// - The dimensions of the RenderImage must fit within the constraints.
@ -367,6 +384,7 @@ class RenderImage extends RenderBox {
flipHorizontally: _flipHorizontally,
invertColors: invertColors,
filterQuality: _filterQuality,
isAntiAlias: _isAntiAlias,
);
}

View file

@ -5230,10 +5230,12 @@ class RawImage extends LeafRenderObjectWidget {
this.matchTextDirection = false,
this.invertColors = false,
this.filterQuality = FilterQuality.low,
this.isAntiAlias = false,
}) : assert(scale != null),
assert(alignment != null),
assert(repeat != null),
assert(matchTextDirection != null),
assert(isAntiAlias != null),
super(key: key);
/// The image to display.
@ -5348,6 +5350,11 @@ class RawImage extends LeafRenderObjectWidget {
/// * [Paint.invertColors], for the dart:ui implementation.
final bool invertColors;
/// Whether to paint the image with anti-aliasing.
///
/// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
final bool isAntiAlias;
@override
RenderImage createRenderObject(BuildContext context) {
assert((!matchTextDirection && alignment is Alignment) || debugCheckHasDirectionality(context));
@ -5366,6 +5373,7 @@ class RawImage extends LeafRenderObjectWidget {
textDirection: matchTextDirection || alignment is! Alignment ? Directionality.of(context) : null,
invertColors: invertColors,
filterQuality: filterQuality,
isAntiAlias: isAntiAlias,
);
}

View file

@ -332,12 +332,14 @@ class Image extends StatefulWidget {
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.isAntiAlias = false,
this.filterQuality = FilterQuality.low,
}) : assert(image != null),
assert(alignment != null),
assert(repeat != null),
assert(filterQuality != null),
assert(matchTextDirection != null),
assert(isAntiAlias != null),
super(key: key);
/// Creates a widget that displays an [ImageStream] obtained from the network.
@ -393,6 +395,7 @@ class Image extends StatefulWidget {
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.filterQuality = FilterQuality.low,
this.isAntiAlias = false,
Map<String, String> headers,
int cacheWidth,
int cacheHeight,
@ -402,6 +405,7 @@ class Image extends StatefulWidget {
assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key);
/// Creates a widget that displays an [ImageStream] obtained from a [File].
@ -446,6 +450,7 @@ class Image extends StatefulWidget {
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.isAntiAlias = false,
this.filterQuality = FilterQuality.low,
int cacheWidth,
int cacheHeight,
@ -457,6 +462,7 @@ class Image extends StatefulWidget {
assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key);
@ -609,6 +615,7 @@ class Image extends StatefulWidget {
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.isAntiAlias = false,
String package,
this.filterQuality = FilterQuality.low,
int cacheWidth,
@ -623,6 +630,7 @@ class Image extends StatefulWidget {
assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key);
/// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
@ -668,6 +676,7 @@ class Image extends StatefulWidget {
this.centerSlice,
this.matchTextDirection = false,
this.gaplessPlayback = false,
this.isAntiAlias = false,
this.filterQuality = FilterQuality.low,
int cacheWidth,
int cacheHeight,
@ -678,6 +687,7 @@ class Image extends StatefulWidget {
assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key);
/// The image to display.
@ -994,6 +1004,11 @@ class Image extends StatefulWidget {
/// application.
final bool excludeFromSemantics;
/// Whether to paint the image with anti-aliasing.
///
/// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
final bool isAntiAlias;
@override
_ImageState createState() => _ImageState();
@ -1196,6 +1211,7 @@ class _ImageState extends State<Image> with WidgetsBindingObserver {
centerSlice: widget.centerSlice,
matchTextDirection: widget.matchTextDirection,
invertColors: _invertColors,
isAntiAlias: widget.isAntiAlias,
filterQuality: widget.filterQuality,
);

View file

@ -23,3 +23,52 @@ const List<int> kAnimatedGif = <int> [
0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b,
];
// A PNG with 100x100 blue pixels.
//
// Constructed by the following code:
// ```dart
// Future<void> someTest(WidgetTester tester) async {
// Uint8List bytes;
// await tester.runAsync(() async {
// const int imageWidth = 100;
// const int imageHeight = 100;
// final Uint8List pixels = Uint8List.fromList(List<int>.generate(
// imageWidth * imageHeight * 4,
// (int i) => i % 4 < 2 ? 0x00 : 0xFF, // opaque blue
// ));
// final Completer<void> completer = Completer<void>();
// ui.decodeImageFromPixels(
// pixels, imageWidth, imageHeight, ui.PixelFormat.rgba8888,
// (ui.Image image) async {
// final ByteData byteData = await image.toByteData(
// format: ui.ImageByteFormat.png);
// bytes = byteData.buffer.asUint8List();
// completer.complete();
// },
// );
// await completer.future;
// });
// print(bytes);
// }
// ```
const List<int> kBlueRectPng = <int> [
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 100, 0,
0, 0, 100, 8, 6, 0, 0, 0, 112, 226, 149, 84, 0, 0, 0, 4, 115, 66, 73, 84, 8,
8, 8, 8, 124, 8, 100, 136, 0, 0, 1, 0, 73, 68, 65, 84, 120, 156, 237, 209, 65,
13, 0, 32, 16, 192, 176, 3, 255, 158, 225, 141, 2, 246, 104, 21, 44, 217, 154,
57, 103, 200, 216, 191, 3, 120, 25, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 204, 5, 234, 78, 2, 198, 180,
170, 48, 200, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130
];

View file

@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
import 'dart:math' as math;
import 'dart:typed_data';
import 'dart:ui' as ui;
@ -1620,6 +1621,63 @@ void main() {
expect(tester.takeException(), 'threw');
});
Future<void> _testRotatedImage(WidgetTester tester, bool isAntiAlias) async {
final Key key = UniqueKey();
await tester.pumpWidget(RepaintBoundary(
key: key,
child: Transform.rotate(
angle: math.pi / 180,
child: Image.memory(Uint8List.fromList(kBlueRectPng), isAntiAlias: isAntiAlias),
),
));
// precacheImage is needed, or the image in the golden file will be empty.
if (!kIsWeb) {
final Finder allImages = find.byType(Image);
for (final Element e in allImages.evaluate()) {
await tester.runAsync(() async {
final Image image = e.widget as Image;
await precacheImage(image.image, e);
});
}
await tester.pumpAndSettle();
}
await expectLater(
find.byKey(key),
matchesGoldenFile('rotated_image_${isAntiAlias ? 'aa' : 'noaa'}.png'),
);
}
testWidgets(
'Rotated images',
(WidgetTester tester) async {
await _testRotatedImage(tester, true);
await _testRotatedImage(tester, false);
},
// TODO(hterkelson): figure out why web timed out with `await precacheImage`
// so we can enable this test on web.
//
// See https://github.com/flutter/flutter/issues/54292.
skip: kIsWeb,
);
}
class ImagePainter extends CustomPainter {
ImagePainter(this.image);
@override
void paint(ui.Canvas canvas, ui.Size size) {
canvas.drawImage(image, Offset.zero, Paint());
}
@override
bool shouldRepaint(CustomPainter oldDelegate) {
return false;
}
final ui.Image image;
}
@immutable