Commit a4838a2a authored by sir-boformer's avatar sir-boformer Committed by Ian Hickson

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
parent 7a950eb3
......@@ -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;
......
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