diff --git a/AUTHORS b/AUTHORS index c51aaf9546..d598d66524 100644 --- a/AUTHORS +++ b/AUTHORS @@ -26,3 +26,4 @@ Noah Groß Victor Choueiri Christian Mürtz Lukasz Piliszczuk +Felix Schmidt diff --git a/packages/flutter/lib/src/painting/decoration_image.dart b/packages/flutter/lib/src/painting/decoration_image.dart index 37e179979d..144fb85de8 100644 --- a/packages/flutter/lib/src/painting/decoration_image.dart +++ b/packages/flutter/lib/src/painting/decoration_image.dart @@ -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); diff --git a/packages/flutter/lib/src/rendering/image.dart b/packages/flutter/lib/src/rendering/image.dart index a6fa57a5f7..5da0e1d3f5 100644 --- a/packages/flutter/lib/src/rendering/image.dart +++ b/packages/flutter/lib/src/rendering/image.dart @@ -324,6 +324,7 @@ class RenderImage extends RenderBox { canvas: context.canvas, rect: offset & size, image: _image, + scale: _scale, colorFilter: _colorFilter, fit: _fit, alignment: _resolvedAlignment, diff --git a/packages/flutter/test/painting/decoration_test.dart b/packages/flutter/test/painting/decoration_test.dart index 78ac761685..1c0dd12620 100644 --- a/packages/flutter/test/painting/decoration_test.dart +++ b/packages/flutter/test/painting/decoration_test.dart @@ -347,4 +347,171 @@ void main() { expect(Decoration.lerp(const FlutterLogoDecoration(), const BoxDecoration(), 0.75), isInstanceOf()); // ignore: CONST_EVAL_THROWS_EXCEPTION expect(Decoration.lerp(const FlutterLogoDecoration(), const BoxDecoration(), 1.0), isInstanceOf()); // 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([]); + + 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()); + + // 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()); + } + }); + + test('paintImage BoxFit.scaleDown scale test', () { + for (double scale = 1.0; scale <= 4.0; scale += 1.0) { + final TestCanvas canvas = new TestCanvas([]); + + // 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()); + + // 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()); + } + }); + + test('paintImage BoxFit.scaleDown test', () { + final TestCanvas canvas = new TestCanvas([]); + + // 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()); + + // 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()); + }); + + test('paintImage boxFit, scale and alignment test', () { + const List boxFits = [ + BoxFit.contain, + BoxFit.cover, + BoxFit.fitWidth, + BoxFit.fitWidth, + BoxFit.fitHeight, + BoxFit.none, + BoxFit.scaleDown, + ]; + + for(BoxFit boxFit in boxFits) { + final TestCanvas canvas = new TestCanvas([]); + + 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); + } + }); } diff --git a/packages/flutter/test/painting/mocks_for_image_cache.dart b/packages/flutter/test/painting/mocks_for_image_cache.dart index 2a72cc3a1f..de2edcafb9 100644 --- a/packages/flutter/test/painting/mocks_for_image_cache.dart +++ b/packages/flutter/test/painting/mocks_for_image_cache.dart @@ -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;