mirror of
https://github.com/flutter/flutter
synced 2024-10-14 04:02:56 +00:00
Set AA flag for painting images (#51656)
This commit is contained in:
parent
1593788cd9
commit
b475eaf8ba
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
);
|
||||
|
||||
|
|
|
@ -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
|
||||
];
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue