diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart index fa53c4ab85..a30490416b 100644 --- a/packages/flutter/lib/src/painting/box_painter.dart +++ b/packages/flutter/lib/src/painting/box_painter.dart @@ -1239,9 +1239,9 @@ void paintImage({ @required ui.Image image, ColorFilter colorFilter, BoxFit fit, - ImageRepeat repeat: ImageRepeat.noRepeat, + FractionalOffset alignment, Rect centerSlice, - FractionalOffset alignment + ImageRepeat repeat: ImageRepeat.noRepeat, }) { assert(canvas != null); assert(image != null); @@ -1266,7 +1266,7 @@ void paintImage({ destinationSize += sliceBorder; // We don't have the ability to draw a subset of the image at the same time // as we apply a nine-patch stretch. - assert(sourceSize == inputSize); + assert(sourceSize == inputSize, 'centerSlice was used with a BoxFit that does not guarantee that the image is fully visible.'); } if (repeat != ImageRepeat.noRepeat && destinationSize == outputSize) { // There's no need to repeat the image because we're exactly filling the @@ -1315,11 +1315,11 @@ class DecorationImage { /// The [image] argument must not be null. const DecorationImage({ @required this.image, - this.fit, - this.repeat: ImageRepeat.noRepeat, - this.centerSlice, this.colorFilter, + this.fit, this.alignment, + this.centerSlice, + this.repeat: ImageRepeat.noRepeat, }) : assert(image != null); /// The image to be painted into the decoration. @@ -1328,6 +1328,9 @@ class DecorationImage { /// application) or a [NetworkImage] (for an image obtained from the network). final ImageProvider image; + /// A color filter to apply to the image before painting it. + final ColorFilter colorFilter; + /// How the image should be inscribed into the box. /// /// The default is [BoxFit.scaleDown] if [centerSlice] is null, and @@ -1336,9 +1339,14 @@ class DecorationImage { /// See the discussion at [paintImage] for more details. final BoxFit fit; - /// How to paint any portions of the box that would not otherwise be covered - /// by the image. - final ImageRepeat repeat; + /// How to align the image within its bounds. + /// + /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its + /// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle + /// of the right edge of its layout bounds. + /// + /// Defaults to [FractionalOffset.center]. + final FractionalOffset alignment; /// The center slice for a nine-patch image. /// @@ -1357,17 +1365,9 @@ class DecorationImage { /// scaling, as if it wasn't specified). final Rect centerSlice; - /// A color filter to apply to the image before painting it. - final ColorFilter colorFilter; - - /// How to align the image within its bounds. - /// - /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its - /// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle - /// of the right edge of its layout bounds. - /// - /// Defaults to [FractionalOffset.center]. - final FractionalOffset alignment; + /// How to paint any portions of the box that would not otherwise be covered + /// by the image. + final ImageRepeat repeat; @override bool operator ==(dynamic other) { @@ -1376,19 +1376,35 @@ class DecorationImage { if (runtimeType != other.runtimeType) return false; final DecorationImage typedOther = other; - return image == typedOther.image && - fit == typedOther.fit && - repeat == typedOther.repeat && - centerSlice == typedOther.centerSlice && - colorFilter == typedOther.colorFilter && - alignment == typedOther.alignment; + return image == typedOther.image + && colorFilter == typedOther.colorFilter + && fit == typedOther.fit + && alignment == typedOther.alignment + && centerSlice == typedOther.centerSlice + && repeat == typedOther.repeat; } @override - int get hashCode => hashValues(image, fit, repeat, centerSlice, colorFilter, alignment); + int get hashCode => hashValues(image, colorFilter, fit, alignment, centerSlice, repeat); @override - String toString() => '$runtimeType($image, $fit, $repeat)'; + String toString() { + final List properties = []; + properties.add('$image'); + if (colorFilter != null) + properties.add('$colorFilter'); + if (fit != null && + !(fit == BoxFit.fill && centerSlice != null) && + !(fit == BoxFit.scaleDown && centerSlice == null)) + properties.add('$fit'); + if (alignment != null) + properties.add('$alignment'); + if (centerSlice != null) + properties.add('centerSlice: $centerSlice'); + if (repeat != ImageRepeat.noRepeat) + properties.add('$repeat'); + return '$runtimeType(${properties.join(", ")})'; + } } /// An immutable description of how to paint a box. @@ -1452,7 +1468,7 @@ class BoxDecoration extends Decoration { this.borderRadius, this.boxShadow, this.gradient, - this.shape: BoxShape.rectangle + this.shape: BoxShape.rectangle, }); @override @@ -1505,7 +1521,7 @@ class BoxDecoration extends Decoration { borderRadius: BorderRadius.lerp(null, borderRadius, factor), boxShadow: BoxShadow.lerpList(null, boxShadow, factor), gradient: gradient, - shape: shape + shape: shape, ); } @@ -1532,7 +1548,7 @@ class BoxDecoration extends Decoration { borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t), boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t), gradient: b.gradient, - shape: b.shape + shape: b.shape, ); } @@ -1575,7 +1591,7 @@ class BoxDecoration extends Decoration { borderRadius, boxShadow, gradient, - shape + shape, ); } @@ -1741,9 +1757,10 @@ class _BoxDecorationPainter extends BoxPainter { rect: rect, image: image, colorFilter: backgroundImage.colorFilter, - alignment: backgroundImage.alignment, fit: backgroundImage.fit, - repeat: backgroundImage.repeat + alignment: backgroundImage.alignment, + centerSlice: backgroundImage.centerSlice, + repeat: backgroundImage.repeat, ); if (clipPath != null) diff --git a/packages/flutter/test/painting/decoration_test.dart b/packages/flutter/test/painting/decoration_test.dart index 92db077964..d1dc57381f 100644 --- a/packages/flutter/test/painting/decoration_test.dart +++ b/packages/flutter/test/painting/decoration_test.dart @@ -3,7 +3,7 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:ui' as ui show Image; +import 'dart:ui' as ui show Image, ColorFilter; import 'package:flutter/foundation.dart'; import 'package:flutter/painting.dart'; @@ -33,7 +33,7 @@ class SynchronousTestImageProvider extends ImageProvider { @override ImageStreamCompleter load(int key) { return new OneFrameImageStreamCompleter( - new SynchronousFuture(new TestImageInfo(key)) + new SynchronousFuture(new TestImageInfo(key, image: new TestImage(), scale: 1.0)) ); } } @@ -90,7 +90,7 @@ class TestImage extends ui.Image { } void main() { - test("Decoration.lerp()", () { + test('Decoration.lerp()', () { final BoxDecoration a = const BoxDecoration(color: const Color(0xFFFFFFFF)); final BoxDecoration b = const BoxDecoration(color: const Color(0x00000000)); @@ -104,7 +104,7 @@ void main() { expect(c.color, equals(b.color)); }); - test("BoxDecorationImageListenerSync", () { + test('BoxDecorationImageListenerSync', () { final ImageProvider imageProvider = new SynchronousTestImageProvider(); final DecorationImage backgroundImage = new DecorationImage(image: imageProvider); @@ -122,7 +122,7 @@ void main() { expect(onChangedCalled, equals(false)); }); - test("BoxDecorationImageListenerAsync", () { + test('BoxDecorationImageListenerAsync', () { new FakeAsync().run((FakeAsync async) { final ImageProvider imageProvider = new AsyncTestImageProvider(); final DecorationImage backgroundImage = new DecorationImage(image: imageProvider); @@ -146,7 +146,7 @@ void main() { // Regression test for https://github.com/flutter/flutter/issues/7289. // A reference test would be better. - test("BoxDecoration backgroundImage clip", () { + test('BoxDecoration backgroundImage clip', () { void testDecoration({ BoxShape shape, BorderRadius borderRadius, bool expectClip}) { new FakeAsync().run((FakeAsync async) { final DelayedImageProvider imageProvider = new DelayedImageProvider(); @@ -198,4 +198,32 @@ void main() { testDecoration(borderRadius: const BorderRadius.all(const Radius.circular(16.0)), expectClip: true); testDecoration(expectClip: false); }); + + test('DecorationImage test', () { + final ColorFilter colorFilter = const ui.ColorFilter.mode(const Color(0xFF00FF00), BlendMode.src); + final DecorationImage backgroundImage = new DecorationImage( + image: new SynchronousTestImageProvider(), + colorFilter: colorFilter, + fit: BoxFit.contain, + alignment: FractionalOffset.bottomLeft, + centerSlice: new Rect.fromLTWH(10.0, 20.0, 30.0, 40.0), + repeat: ImageRepeat.repeatY, + ); + + final BoxDecoration boxDecoration = new BoxDecoration(image: backgroundImage); + final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { assert(false); }); + final TestCanvas canvas = new TestCanvas([]); + boxPainter.paint(canvas, Offset.zero, const ImageConfiguration(size: const Size(10.0, 10.0))); + + final Invocation call = canvas.invocations.singleWhere((Invocation call) => call.memberName == #drawImageNine); + expect(call.isMethod, isTrue); + expect(call.positionalArguments, hasLength(4)); + expect(call.positionalArguments[0], const isInstanceOf()); + expect(call.positionalArguments[1], new Rect.fromLTRB(10.0, 20.0, 40.0, 60.0)); + expect(call.positionalArguments[2], new Rect.fromLTRB(0.0, 0.0, 32.5, 10.0)); + expect(call.positionalArguments[3], const isInstanceOf()); + expect(call.positionalArguments[3].isAntiAlias, false); + expect(call.positionalArguments[3].colorFilter, colorFilter); + expect(call.positionalArguments[3].filterQuality, FilterQuality.low); + }); } diff --git a/packages/flutter/test/painting/paint_image_test.dart b/packages/flutter/test/painting/paint_image_test.dart index bc8c2791bd..fcadbe3b75 100644 --- a/packages/flutter/test/painting/paint_image_test.dart +++ b/packages/flutter/test/painting/paint_image_test.dart @@ -31,7 +31,7 @@ class TestCanvas implements Canvas { } void main() { - test("Cover and align", () { + test('Cover and align', () { final TestImage image = new TestImage(width: 300, height: 300); final TestCanvas canvas = new TestCanvas(); paintImage( @@ -51,4 +51,6 @@ void main() { expect(command.positionalArguments[1], equals(new Rect.fromLTWH(0.0, 75.0, 300.0, 150.0))); expect(command.positionalArguments[2], equals(new Rect.fromLTWH(50.0, 75.0, 200.0, 100.0))); }); + + // See also the DecorationImage tests in: decoration_test.dart } diff --git a/packages/flutter/test/services/mocks_for_image_cache.dart b/packages/flutter/test/services/mocks_for_image_cache.dart index b595931ea7..5623fdb739 100644 --- a/packages/flutter/test/services/mocks_for_image_cache.dart +++ b/packages/flutter/test/services/mocks_for_image_cache.dart @@ -9,13 +9,13 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; class TestImageInfo implements ImageInfo { - const TestImageInfo(this.value) : image = null, scale = null; + const TestImageInfo(this.value, { this.image, this.scale }); @override - final ui.Image image; // ignored in test + final ui.Image image; @override - final double scale; // ignored in test + final double scale; final int value;