Unverified Commit b475eaf8 authored by liyuqian's avatar liyuqian Committed by GitHub

Set AA flag for painting images (#51656)

parent 1593788c
...@@ -386,12 +386,14 @@ void paintImage({ ...@@ -386,12 +386,14 @@ void paintImage({
bool flipHorizontally = false, bool flipHorizontally = false,
bool invertColors = false, bool invertColors = false,
FilterQuality filterQuality = FilterQuality.low, FilterQuality filterQuality = FilterQuality.low,
bool isAntiAlias = false,
}) { }) {
assert(canvas != null); assert(canvas != null);
assert(image != null); assert(image != null);
assert(alignment != null); assert(alignment != null);
assert(repeat != null); assert(repeat != null);
assert(flipHorizontally != null); assert(flipHorizontally != null);
assert(isAntiAlias != null);
if (rect.isEmpty) if (rect.isEmpty)
return; return;
Size outputSize = rect.size; Size outputSize = rect.size;
...@@ -422,7 +424,7 @@ void paintImage({ ...@@ -422,7 +424,7 @@ void paintImage({
// output rect with the image. // output rect with the image.
repeat = ImageRepeat.noRepeat; repeat = ImageRepeat.noRepeat;
} }
final Paint paint = Paint()..isAntiAlias = false; final Paint paint = Paint()..isAntiAlias = isAntiAlias;
if (colorFilter != null) if (colorFilter != null)
paint.colorFilter = colorFilter; paint.colorFilter = colorFilter;
if (sourceSize != destinationSize) { if (sourceSize != destinationSize) {
......
...@@ -38,12 +38,14 @@ class RenderImage extends RenderBox { ...@@ -38,12 +38,14 @@ class RenderImage extends RenderBox {
bool matchTextDirection = false, bool matchTextDirection = false,
TextDirection textDirection, TextDirection textDirection,
bool invertColors = false, bool invertColors = false,
bool isAntiAlias = false,
FilterQuality filterQuality = FilterQuality.low, FilterQuality filterQuality = FilterQuality.low,
}) : assert(scale != null), }) : assert(scale != null),
assert(repeat != null), assert(repeat != null),
assert(alignment != null), assert(alignment != null),
assert(filterQuality != null), assert(filterQuality != null),
assert(matchTextDirection != null), assert(matchTextDirection != null),
assert(isAntiAlias != null),
_image = image, _image = image,
_width = width, _width = width,
_height = height, _height = height,
...@@ -57,6 +59,7 @@ class RenderImage extends RenderBox { ...@@ -57,6 +59,7 @@ class RenderImage extends RenderBox {
_matchTextDirection = matchTextDirection, _matchTextDirection = matchTextDirection,
_invertColors = invertColors, _invertColors = invertColors,
_textDirection = textDirection, _textDirection = textDirection,
_isAntiAlias = isAntiAlias,
_filterQuality = filterQuality { _filterQuality = filterQuality {
_updateColorFilter(); _updateColorFilter();
} }
...@@ -287,6 +290,20 @@ class RenderImage extends RenderBox { ...@@ -287,6 +290,20 @@ class RenderImage extends RenderBox {
_markNeedResolution(); _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. /// Find a size for the render image within the given constraints.
/// ///
/// - The dimensions of the RenderImage must fit within the constraints. /// - The dimensions of the RenderImage must fit within the constraints.
...@@ -367,6 +384,7 @@ class RenderImage extends RenderBox { ...@@ -367,6 +384,7 @@ class RenderImage extends RenderBox {
flipHorizontally: _flipHorizontally, flipHorizontally: _flipHorizontally,
invertColors: invertColors, invertColors: invertColors,
filterQuality: _filterQuality, filterQuality: _filterQuality,
isAntiAlias: _isAntiAlias,
); );
} }
......
...@@ -5230,10 +5230,12 @@ class RawImage extends LeafRenderObjectWidget { ...@@ -5230,10 +5230,12 @@ class RawImage extends LeafRenderObjectWidget {
this.matchTextDirection = false, this.matchTextDirection = false,
this.invertColors = false, this.invertColors = false,
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
this.isAntiAlias = false,
}) : assert(scale != null), }) : assert(scale != null),
assert(alignment != null), assert(alignment != null),
assert(repeat != null), assert(repeat != null),
assert(matchTextDirection != null), assert(matchTextDirection != null),
assert(isAntiAlias != null),
super(key: key); super(key: key);
/// The image to display. /// The image to display.
...@@ -5348,6 +5350,11 @@ class RawImage extends LeafRenderObjectWidget { ...@@ -5348,6 +5350,11 @@ class RawImage extends LeafRenderObjectWidget {
/// * [Paint.invertColors], for the dart:ui implementation. /// * [Paint.invertColors], for the dart:ui implementation.
final bool invertColors; 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 @override
RenderImage createRenderObject(BuildContext context) { RenderImage createRenderObject(BuildContext context) {
assert((!matchTextDirection && alignment is Alignment) || debugCheckHasDirectionality(context)); assert((!matchTextDirection && alignment is Alignment) || debugCheckHasDirectionality(context));
...@@ -5366,6 +5373,7 @@ class RawImage extends LeafRenderObjectWidget { ...@@ -5366,6 +5373,7 @@ class RawImage extends LeafRenderObjectWidget {
textDirection: matchTextDirection || alignment is! Alignment ? Directionality.of(context) : null, textDirection: matchTextDirection || alignment is! Alignment ? Directionality.of(context) : null,
invertColors: invertColors, invertColors: invertColors,
filterQuality: filterQuality, filterQuality: filterQuality,
isAntiAlias: isAntiAlias,
); );
} }
......
...@@ -332,12 +332,14 @@ class Image extends StatefulWidget { ...@@ -332,12 +332,14 @@ class Image extends StatefulWidget {
this.centerSlice, this.centerSlice,
this.matchTextDirection = false, this.matchTextDirection = false,
this.gaplessPlayback = false, this.gaplessPlayback = false,
this.isAntiAlias = false,
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
}) : assert(image != null), }) : assert(image != null),
assert(alignment != null), assert(alignment != null),
assert(repeat != null), assert(repeat != null),
assert(filterQuality != null), assert(filterQuality != null),
assert(matchTextDirection != null), assert(matchTextDirection != null),
assert(isAntiAlias != null),
super(key: key); super(key: key);
/// Creates a widget that displays an [ImageStream] obtained from the network. /// Creates a widget that displays an [ImageStream] obtained from the network.
...@@ -393,6 +395,7 @@ class Image extends StatefulWidget { ...@@ -393,6 +395,7 @@ class Image extends StatefulWidget {
this.matchTextDirection = false, this.matchTextDirection = false,
this.gaplessPlayback = false, this.gaplessPlayback = false,
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
this.isAntiAlias = false,
Map<String, String> headers, Map<String, String> headers,
int cacheWidth, int cacheWidth,
int cacheHeight, int cacheHeight,
...@@ -402,6 +405,7 @@ class Image extends StatefulWidget { ...@@ -402,6 +405,7 @@ class Image extends StatefulWidget {
assert(matchTextDirection != null), assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0), assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0), assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key); super(key: key);
/// Creates a widget that displays an [ImageStream] obtained from a [File]. /// Creates a widget that displays an [ImageStream] obtained from a [File].
...@@ -446,6 +450,7 @@ class Image extends StatefulWidget { ...@@ -446,6 +450,7 @@ class Image extends StatefulWidget {
this.centerSlice, this.centerSlice,
this.matchTextDirection = false, this.matchTextDirection = false,
this.gaplessPlayback = false, this.gaplessPlayback = false,
this.isAntiAlias = false,
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
int cacheWidth, int cacheWidth,
int cacheHeight, int cacheHeight,
...@@ -457,6 +462,7 @@ class Image extends StatefulWidget { ...@@ -457,6 +462,7 @@ class Image extends StatefulWidget {
assert(matchTextDirection != null), assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0), assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0), assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key); super(key: key);
...@@ -609,6 +615,7 @@ class Image extends StatefulWidget { ...@@ -609,6 +615,7 @@ class Image extends StatefulWidget {
this.centerSlice, this.centerSlice,
this.matchTextDirection = false, this.matchTextDirection = false,
this.gaplessPlayback = false, this.gaplessPlayback = false,
this.isAntiAlias = false,
String package, String package,
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
int cacheWidth, int cacheWidth,
...@@ -623,6 +630,7 @@ class Image extends StatefulWidget { ...@@ -623,6 +630,7 @@ class Image extends StatefulWidget {
assert(matchTextDirection != null), assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0), assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0), assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key); super(key: key);
/// Creates a widget that displays an [ImageStream] obtained from a [Uint8List]. /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
...@@ -668,6 +676,7 @@ class Image extends StatefulWidget { ...@@ -668,6 +676,7 @@ class Image extends StatefulWidget {
this.centerSlice, this.centerSlice,
this.matchTextDirection = false, this.matchTextDirection = false,
this.gaplessPlayback = false, this.gaplessPlayback = false,
this.isAntiAlias = false,
this.filterQuality = FilterQuality.low, this.filterQuality = FilterQuality.low,
int cacheWidth, int cacheWidth,
int cacheHeight, int cacheHeight,
...@@ -678,6 +687,7 @@ class Image extends StatefulWidget { ...@@ -678,6 +687,7 @@ class Image extends StatefulWidget {
assert(matchTextDirection != null), assert(matchTextDirection != null),
assert(cacheWidth == null || cacheWidth > 0), assert(cacheWidth == null || cacheWidth > 0),
assert(cacheHeight == null || cacheHeight > 0), assert(cacheHeight == null || cacheHeight > 0),
assert(isAntiAlias != null),
super(key: key); super(key: key);
/// The image to display. /// The image to display.
...@@ -994,6 +1004,11 @@ class Image extends StatefulWidget { ...@@ -994,6 +1004,11 @@ class Image extends StatefulWidget {
/// application. /// application.
final bool excludeFromSemantics; 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 @override
_ImageState createState() => _ImageState(); _ImageState createState() => _ImageState();
...@@ -1196,6 +1211,7 @@ class _ImageState extends State<Image> with WidgetsBindingObserver { ...@@ -1196,6 +1211,7 @@ class _ImageState extends State<Image> with WidgetsBindingObserver {
centerSlice: widget.centerSlice, centerSlice: widget.centerSlice,
matchTextDirection: widget.matchTextDirection, matchTextDirection: widget.matchTextDirection,
invertColors: _invertColors, invertColors: _invertColors,
isAntiAlias: widget.isAntiAlias,
filterQuality: widget.filterQuality, filterQuality: widget.filterQuality,
); );
......
...@@ -23,3 +23,52 @@ const List<int> kAnimatedGif = <int> [ ...@@ -23,3 +23,52 @@ const List<int> kAnimatedGif = <int> [
0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01, 0xf9, 0x04, 0x00, 0x0a, 0x00, 0xff, 0x00, 0x2c, 0x00, 0x00, 0x00, 0x00, 0x01,
0x00, 0x01, 0x00, 0x00, 0x02, 0x02, 0x44, 0x01, 0x00, 0x3b, 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
];
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
...@@ -1620,6 +1621,63 @@ void main() { ...@@ -1620,6 +1621,63 @@ void main() {
expect(tester.takeException(), 'threw'); 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 @immutable
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment