Scale high-pixel-ratio images with BoxFit.none/BoxFit.scaleDown correctly (#20791)
* Scale high-pixel-ratio images with BoxFit.none/BoxFit.scaleDown correctly * add test cases for image scale * make analyzer happy * Make sure that test images always have a scale to make all tests pass * add a new author * use new keyword in tests
This commit is contained in:
parent
7a950eb3a7
commit
a4838a2adc
1
AUTHORS
1
AUTHORS
@ -26,3 +26,4 @@ Noah Groß <gross@ngsger.de>
|
||||
Victor Choueiri <victor@ctrlanddev.com>
|
||||
Christian Mürtz <teraarts@t-online.de>
|
||||
Lukasz Piliszczuk <lukasz@intheloup.io>
|
||||
Felix Schmidt <felix.free@gmx.de>
|
||||
|
@ -254,6 +254,7 @@ class DecorationImagePainter {
|
||||
canvas: canvas,
|
||||
rect: rect,
|
||||
image: _image.image,
|
||||
scale: _image.scale,
|
||||
colorFilter: _details.colorFilter,
|
||||
fit: _details.fit,
|
||||
alignment: _details.alignment.resolve(configuration.textDirection),
|
||||
@ -303,6 +304,8 @@ class DecorationImagePainter {
|
||||
///
|
||||
/// * `image`: The image to paint onto the canvas.
|
||||
///
|
||||
/// * `scale`: The number of image pixels for each logical pixel.
|
||||
///
|
||||
/// * `colorFilter`: If non-null, the color filter to apply when painting the
|
||||
/// image.
|
||||
///
|
||||
@ -339,7 +342,7 @@ class DecorationImagePainter {
|
||||
/// when using this, to not flip images with integral shadows, text, or other
|
||||
/// effects that will look incorrect when flipped.
|
||||
///
|
||||
/// The `canvas`, `rect`, `image`, `alignment`, `repeat`, and `flipHorizontally`
|
||||
/// The `canvas`, `rect`, `image`, `scale`, `alignment`, `repeat`, and `flipHorizontally`
|
||||
/// arguments must not be null.
|
||||
///
|
||||
/// See also:
|
||||
@ -351,6 +354,7 @@ void paintImage({
|
||||
@required Canvas canvas,
|
||||
@required Rect rect,
|
||||
@required ui.Image image,
|
||||
double scale = 1.0,
|
||||
ColorFilter colorFilter,
|
||||
BoxFit fit,
|
||||
Alignment alignment = Alignment.center,
|
||||
@ -378,8 +382,8 @@ void paintImage({
|
||||
}
|
||||
fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
|
||||
assert(centerSlice == null || (fit != BoxFit.none && fit != BoxFit.cover));
|
||||
final FittedSizes fittedSizes = applyBoxFit(fit, inputSize, outputSize);
|
||||
final Size sourceSize = fittedSizes.source;
|
||||
final FittedSizes fittedSizes = applyBoxFit(fit, inputSize / scale, outputSize);
|
||||
final Size sourceSize = fittedSizes.source * scale;
|
||||
Size destinationSize = fittedSizes.destination;
|
||||
if (centerSlice != null) {
|
||||
outputSize += sliceBorder;
|
||||
@ -421,7 +425,7 @@ void paintImage({
|
||||
}
|
||||
if (centerSlice == null) {
|
||||
final Rect sourceRect = alignment.inscribe(
|
||||
fittedSizes.source, Offset.zero & inputSize
|
||||
sourceSize, Offset.zero & inputSize
|
||||
);
|
||||
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
||||
canvas.drawImageRect(image, sourceRect, tileRect, paint);
|
||||
|
@ -324,6 +324,7 @@ class RenderImage extends RenderBox {
|
||||
canvas: context.canvas,
|
||||
rect: offset & size,
|
||||
image: _image,
|
||||
scale: _scale,
|
||||
colorFilter: _colorFilter,
|
||||
fit: _fit,
|
||||
alignment: _resolvedAlignment,
|
||||
|
@ -347,4 +347,171 @@ void main() {
|
||||
expect(Decoration.lerp(const FlutterLogoDecoration(), const BoxDecoration(), 0.75), isInstanceOf<BoxDecoration>()); // ignore: CONST_EVAL_THROWS_EXCEPTION
|
||||
expect(Decoration.lerp(const FlutterLogoDecoration(), const BoxDecoration(), 1.0), isInstanceOf<BoxDecoration>()); // ignore: CONST_EVAL_THROWS_EXCEPTION
|
||||
});
|
||||
|
||||
test('paintImage BoxFit.none scale test', () {
|
||||
for (double scale = 1.0; scale <= 4.0; scale += 1.0) {
|
||||
final TestCanvas canvas = new TestCanvas(<Invocation>[]);
|
||||
|
||||
final Rect outputRect = new Rect.fromLTWH(30.0, 30.0, 250.0, 250.0);
|
||||
final ui.Image image = new TestImage();
|
||||
|
||||
paintImage(
|
||||
canvas: canvas,
|
||||
rect: outputRect,
|
||||
image: image,
|
||||
scale: scale,
|
||||
alignment: Alignment.bottomRight,
|
||||
fit: BoxFit.none,
|
||||
repeat: ImageRepeat.noRepeat,
|
||||
flipHorizontally: false,
|
||||
);
|
||||
|
||||
const Size imageSize = Size(100.0, 100.0);
|
||||
|
||||
final Invocation call = canvas.invocations.firstWhere((Invocation call) => call.memberName == #drawImageRect);
|
||||
|
||||
expect(call.isMethod, isTrue);
|
||||
expect(call.positionalArguments, hasLength(4));
|
||||
|
||||
expect(call.positionalArguments[0], isInstanceOf<TestImage>());
|
||||
|
||||
// sourceRect should contain all pixels of the source image
|
||||
expect(call.positionalArguments[1], Offset.zero & imageSize);
|
||||
|
||||
// Image should be scaled down (divided by scale)
|
||||
// and be positioned in the bottom right of the outputRect
|
||||
final Size expectedTileSize = imageSize / scale;
|
||||
final Rect expectedTileRect = new Rect.fromPoints(
|
||||
outputRect.bottomRight.translate(-expectedTileSize.width, -expectedTileSize.height),
|
||||
outputRect.bottomRight,
|
||||
);
|
||||
expect(call.positionalArguments[2], expectedTileRect);
|
||||
|
||||
expect(call.positionalArguments[3], isInstanceOf<Paint>());
|
||||
}
|
||||
});
|
||||
|
||||
test('paintImage BoxFit.scaleDown scale test', () {
|
||||
for (double scale = 1.0; scale <= 4.0; scale += 1.0) {
|
||||
final TestCanvas canvas = new TestCanvas(<Invocation>[]);
|
||||
|
||||
// container size > scaled image size
|
||||
final Rect outputRect = new Rect.fromLTWH(30.0, 30.0, 250.0, 250.0);
|
||||
final ui.Image image = new TestImage();
|
||||
|
||||
paintImage(
|
||||
canvas: canvas,
|
||||
rect: outputRect,
|
||||
image: image,
|
||||
scale: scale,
|
||||
alignment: Alignment.bottomRight,
|
||||
fit: BoxFit.scaleDown,
|
||||
repeat: ImageRepeat.noRepeat,
|
||||
flipHorizontally: false,
|
||||
);
|
||||
|
||||
const Size imageSize = Size(100.0, 100.0);
|
||||
|
||||
final Invocation call = canvas.invocations.firstWhere((Invocation call) => call.memberName == #drawImageRect);
|
||||
|
||||
expect(call.isMethod, isTrue);
|
||||
expect(call.positionalArguments, hasLength(4));
|
||||
|
||||
expect(call.positionalArguments[0], isInstanceOf<TestImage>());
|
||||
|
||||
// sourceRect should contain all pixels of the source image
|
||||
expect(call.positionalArguments[1], Offset.zero & imageSize);
|
||||
|
||||
// Image should be scaled down (divided by scale)
|
||||
// and be positioned in the bottom right of the outputRect
|
||||
final Size expectedTileSize = imageSize / scale;
|
||||
final Rect expectedTileRect = new Rect.fromPoints(
|
||||
outputRect.bottomRight.translate(-expectedTileSize.width, -expectedTileSize.height),
|
||||
outputRect.bottomRight,
|
||||
);
|
||||
expect(call.positionalArguments[2], expectedTileRect);
|
||||
|
||||
expect(call.positionalArguments[3], isInstanceOf<Paint>());
|
||||
}
|
||||
});
|
||||
|
||||
test('paintImage BoxFit.scaleDown test', () {
|
||||
final TestCanvas canvas = new TestCanvas(<Invocation>[]);
|
||||
|
||||
// container height (20 px) < scaled image height (50 px)
|
||||
final Rect outputRect = new Rect.fromLTWH(30.0, 30.0, 250.0, 20.0);
|
||||
final ui.Image image = new TestImage();
|
||||
|
||||
paintImage(
|
||||
canvas: canvas,
|
||||
rect: outputRect,
|
||||
image: image,
|
||||
scale: 2.0,
|
||||
alignment: Alignment.bottomRight,
|
||||
fit: BoxFit.scaleDown,
|
||||
repeat: ImageRepeat.noRepeat,
|
||||
flipHorizontally: false,
|
||||
);
|
||||
|
||||
const Size imageSize = Size(100.0, 100.0);
|
||||
|
||||
final Invocation call = canvas.invocations.firstWhere((Invocation call) => call.memberName == #drawImageRect);
|
||||
|
||||
expect(call.isMethod, isTrue);
|
||||
expect(call.positionalArguments, hasLength(4));
|
||||
|
||||
expect(call.positionalArguments[0], isInstanceOf<TestImage>());
|
||||
|
||||
// sourceRect should contain all pixels of the source image
|
||||
expect(call.positionalArguments[1], Offset.zero & imageSize);
|
||||
|
||||
// Image should be scaled down to fit in hejght
|
||||
// and be positioned in the bottom right of the outputRect
|
||||
const Size expectedTileSize = Size(20.0, 20.0);
|
||||
final Rect expectedTileRect = new Rect.fromPoints(
|
||||
outputRect.bottomRight.translate(-expectedTileSize.width, -expectedTileSize.height),
|
||||
outputRect.bottomRight,
|
||||
);
|
||||
expect(call.positionalArguments[2], expectedTileRect);
|
||||
|
||||
expect(call.positionalArguments[3], isInstanceOf<Paint>());
|
||||
});
|
||||
|
||||
test('paintImage boxFit, scale and alignment test', () {
|
||||
const List<BoxFit> boxFits = <BoxFit>[
|
||||
BoxFit.contain,
|
||||
BoxFit.cover,
|
||||
BoxFit.fitWidth,
|
||||
BoxFit.fitWidth,
|
||||
BoxFit.fitHeight,
|
||||
BoxFit.none,
|
||||
BoxFit.scaleDown,
|
||||
];
|
||||
|
||||
for(BoxFit boxFit in boxFits) {
|
||||
final TestCanvas canvas = new TestCanvas(<Invocation>[]);
|
||||
|
||||
final Rect outputRect = new Rect.fromLTWH(30.0, 30.0, 250.0, 250.0);
|
||||
final ui.Image image = new TestImage();
|
||||
|
||||
paintImage(
|
||||
canvas: canvas,
|
||||
rect: outputRect,
|
||||
image: image,
|
||||
scale: 3.0,
|
||||
alignment: Alignment.center,
|
||||
fit: boxFit,
|
||||
repeat: ImageRepeat.noRepeat,
|
||||
flipHorizontally: false,
|
||||
);
|
||||
|
||||
final Invocation call = canvas.invocations.firstWhere((Invocation call) => call.memberName == #drawImageRect);
|
||||
|
||||
expect(call.isMethod, isTrue);
|
||||
expect(call.positionalArguments, hasLength(4));
|
||||
|
||||
// Image should be positioned in the center of the container
|
||||
expect(call.positionalArguments[2].center, outputRect.center);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
|
||||
class TestImageInfo implements ImageInfo {
|
||||
const TestImageInfo(this.value, { this.image, this.scale });
|
||||
const TestImageInfo(this.value, { this.image, this.scale = 1.0 });
|
||||
|
||||
@override
|
||||
final ui.Image image;
|
||||
|
Loading…
x
Reference in New Issue
Block a user