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>
|
Victor Choueiri <victor@ctrlanddev.com>
|
||||||
Christian Mürtz <teraarts@t-online.de>
|
Christian Mürtz <teraarts@t-online.de>
|
||||||
Lukasz Piliszczuk <lukasz@intheloup.io>
|
Lukasz Piliszczuk <lukasz@intheloup.io>
|
||||||
|
Felix Schmidt <felix.free@gmx.de>
|
||||||
|
@ -254,6 +254,7 @@ class DecorationImagePainter {
|
|||||||
canvas: canvas,
|
canvas: canvas,
|
||||||
rect: rect,
|
rect: rect,
|
||||||
image: _image.image,
|
image: _image.image,
|
||||||
|
scale: _image.scale,
|
||||||
colorFilter: _details.colorFilter,
|
colorFilter: _details.colorFilter,
|
||||||
fit: _details.fit,
|
fit: _details.fit,
|
||||||
alignment: _details.alignment.resolve(configuration.textDirection),
|
alignment: _details.alignment.resolve(configuration.textDirection),
|
||||||
@ -303,6 +304,8 @@ class DecorationImagePainter {
|
|||||||
///
|
///
|
||||||
/// * `image`: The image to paint onto the canvas.
|
/// * `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
|
/// * `colorFilter`: If non-null, the color filter to apply when painting the
|
||||||
/// image.
|
/// image.
|
||||||
///
|
///
|
||||||
@ -339,7 +342,7 @@ class DecorationImagePainter {
|
|||||||
/// when using this, to not flip images with integral shadows, text, or other
|
/// when using this, to not flip images with integral shadows, text, or other
|
||||||
/// effects that will look incorrect when flipped.
|
/// 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.
|
/// arguments must not be null.
|
||||||
///
|
///
|
||||||
/// See also:
|
/// See also:
|
||||||
@ -351,6 +354,7 @@ void paintImage({
|
|||||||
@required Canvas canvas,
|
@required Canvas canvas,
|
||||||
@required Rect rect,
|
@required Rect rect,
|
||||||
@required ui.Image image,
|
@required ui.Image image,
|
||||||
|
double scale = 1.0,
|
||||||
ColorFilter colorFilter,
|
ColorFilter colorFilter,
|
||||||
BoxFit fit,
|
BoxFit fit,
|
||||||
Alignment alignment = Alignment.center,
|
Alignment alignment = Alignment.center,
|
||||||
@ -378,8 +382,8 @@ void paintImage({
|
|||||||
}
|
}
|
||||||
fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
|
fit ??= centerSlice == null ? BoxFit.scaleDown : BoxFit.fill;
|
||||||
assert(centerSlice == null || (fit != BoxFit.none && fit != BoxFit.cover));
|
assert(centerSlice == null || (fit != BoxFit.none && fit != BoxFit.cover));
|
||||||
final FittedSizes fittedSizes = applyBoxFit(fit, inputSize, outputSize);
|
final FittedSizes fittedSizes = applyBoxFit(fit, inputSize / scale, outputSize);
|
||||||
final Size sourceSize = fittedSizes.source;
|
final Size sourceSize = fittedSizes.source * scale;
|
||||||
Size destinationSize = fittedSizes.destination;
|
Size destinationSize = fittedSizes.destination;
|
||||||
if (centerSlice != null) {
|
if (centerSlice != null) {
|
||||||
outputSize += sliceBorder;
|
outputSize += sliceBorder;
|
||||||
@ -421,7 +425,7 @@ void paintImage({
|
|||||||
}
|
}
|
||||||
if (centerSlice == null) {
|
if (centerSlice == null) {
|
||||||
final Rect sourceRect = alignment.inscribe(
|
final Rect sourceRect = alignment.inscribe(
|
||||||
fittedSizes.source, Offset.zero & inputSize
|
sourceSize, Offset.zero & inputSize
|
||||||
);
|
);
|
||||||
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
for (Rect tileRect in _generateImageTileRects(rect, destinationRect, repeat))
|
||||||
canvas.drawImageRect(image, sourceRect, tileRect, paint);
|
canvas.drawImageRect(image, sourceRect, tileRect, paint);
|
||||||
|
@ -324,6 +324,7 @@ class RenderImage extends RenderBox {
|
|||||||
canvas: context.canvas,
|
canvas: context.canvas,
|
||||||
rect: offset & size,
|
rect: offset & size,
|
||||||
image: _image,
|
image: _image,
|
||||||
|
scale: _scale,
|
||||||
colorFilter: _colorFilter,
|
colorFilter: _colorFilter,
|
||||||
fit: _fit,
|
fit: _fit,
|
||||||
alignment: _resolvedAlignment,
|
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(), 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
|
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';
|
import 'package:flutter/painting.dart';
|
||||||
|
|
||||||
class TestImageInfo implements ImageInfo {
|
class TestImageInfo implements ImageInfo {
|
||||||
const TestImageInfo(this.value, { this.image, this.scale });
|
const TestImageInfo(this.value, { this.image, this.scale = 1.0 });
|
||||||
|
|
||||||
@override
|
@override
|
||||||
final ui.Image image;
|
final ui.Image image;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user