Commit 51ba6b37 authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fix DecorationImage.centerSlice (#10257)

...and rearrange a bunch of code so that all these arguments/members
are always in the same order.
parent 61e938aa
...@@ -1239,9 +1239,9 @@ void paintImage({ ...@@ -1239,9 +1239,9 @@ void paintImage({
@required ui.Image image, @required ui.Image image,
ColorFilter colorFilter, ColorFilter colorFilter,
BoxFit fit, BoxFit fit,
ImageRepeat repeat: ImageRepeat.noRepeat, FractionalOffset alignment,
Rect centerSlice, Rect centerSlice,
FractionalOffset alignment ImageRepeat repeat: ImageRepeat.noRepeat,
}) { }) {
assert(canvas != null); assert(canvas != null);
assert(image != null); assert(image != null);
...@@ -1266,7 +1266,7 @@ void paintImage({ ...@@ -1266,7 +1266,7 @@ void paintImage({
destinationSize += sliceBorder; destinationSize += sliceBorder;
// We don't have the ability to draw a subset of the image at the same time // We don't have the ability to draw a subset of the image at the same time
// as we apply a nine-patch stretch. // 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) { if (repeat != ImageRepeat.noRepeat && destinationSize == outputSize) {
// There's no need to repeat the image because we're exactly filling the // There's no need to repeat the image because we're exactly filling the
...@@ -1315,11 +1315,11 @@ class DecorationImage { ...@@ -1315,11 +1315,11 @@ class DecorationImage {
/// The [image] argument must not be null. /// The [image] argument must not be null.
const DecorationImage({ const DecorationImage({
@required this.image, @required this.image,
this.fit,
this.repeat: ImageRepeat.noRepeat,
this.centerSlice,
this.colorFilter, this.colorFilter,
this.fit,
this.alignment, this.alignment,
this.centerSlice,
this.repeat: ImageRepeat.noRepeat,
}) : assert(image != null); }) : assert(image != null);
/// The image to be painted into the decoration. /// The image to be painted into the decoration.
...@@ -1328,6 +1328,9 @@ class DecorationImage { ...@@ -1328,6 +1328,9 @@ class DecorationImage {
/// application) or a [NetworkImage] (for an image obtained from the network). /// application) or a [NetworkImage] (for an image obtained from the network).
final ImageProvider image; 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. /// How the image should be inscribed into the box.
/// ///
/// The default is [BoxFit.scaleDown] if [centerSlice] is null, and /// The default is [BoxFit.scaleDown] if [centerSlice] is null, and
...@@ -1336,9 +1339,14 @@ class DecorationImage { ...@@ -1336,9 +1339,14 @@ class DecorationImage {
/// See the discussion at [paintImage] for more details. /// See the discussion at [paintImage] for more details.
final BoxFit fit; final BoxFit fit;
/// How to paint any portions of the box that would not otherwise be covered /// How to align the image within its bounds.
/// by the image. ///
final ImageRepeat repeat; /// 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. /// The center slice for a nine-patch image.
/// ///
...@@ -1357,17 +1365,9 @@ class DecorationImage { ...@@ -1357,17 +1365,9 @@ class DecorationImage {
/// scaling, as if it wasn't specified). /// scaling, as if it wasn't specified).
final Rect centerSlice; final Rect centerSlice;
/// A color filter to apply to the image before painting it. /// How to paint any portions of the box that would not otherwise be covered
final ColorFilter colorFilter; /// 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;
@override @override
bool operator ==(dynamic other) { bool operator ==(dynamic other) {
...@@ -1376,19 +1376,35 @@ class DecorationImage { ...@@ -1376,19 +1376,35 @@ class DecorationImage {
if (runtimeType != other.runtimeType) if (runtimeType != other.runtimeType)
return false; return false;
final DecorationImage typedOther = other; final DecorationImage typedOther = other;
return image == typedOther.image && return image == typedOther.image
fit == typedOther.fit && && colorFilter == typedOther.colorFilter
repeat == typedOther.repeat && && fit == typedOther.fit
centerSlice == typedOther.centerSlice && && alignment == typedOther.alignment
colorFilter == typedOther.colorFilter && && centerSlice == typedOther.centerSlice
alignment == typedOther.alignment; && repeat == typedOther.repeat;
} }
@override @override
int get hashCode => hashValues(image, fit, repeat, centerSlice, colorFilter, alignment); int get hashCode => hashValues(image, colorFilter, fit, alignment, centerSlice, repeat);
@override @override
String toString() => '$runtimeType($image, $fit, $repeat)'; String toString() {
final List<String> properties = <String>[];
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. /// An immutable description of how to paint a box.
...@@ -1452,7 +1468,7 @@ class BoxDecoration extends Decoration { ...@@ -1452,7 +1468,7 @@ class BoxDecoration extends Decoration {
this.borderRadius, this.borderRadius,
this.boxShadow, this.boxShadow,
this.gradient, this.gradient,
this.shape: BoxShape.rectangle this.shape: BoxShape.rectangle,
}); });
@override @override
...@@ -1505,7 +1521,7 @@ class BoxDecoration extends Decoration { ...@@ -1505,7 +1521,7 @@ class BoxDecoration extends Decoration {
borderRadius: BorderRadius.lerp(null, borderRadius, factor), borderRadius: BorderRadius.lerp(null, borderRadius, factor),
boxShadow: BoxShadow.lerpList(null, boxShadow, factor), boxShadow: BoxShadow.lerpList(null, boxShadow, factor),
gradient: gradient, gradient: gradient,
shape: shape shape: shape,
); );
} }
...@@ -1532,7 +1548,7 @@ class BoxDecoration extends Decoration { ...@@ -1532,7 +1548,7 @@ class BoxDecoration extends Decoration {
borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t), borderRadius: BorderRadius.lerp(a.borderRadius, b.borderRadius, t),
boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t), boxShadow: BoxShadow.lerpList(a.boxShadow, b.boxShadow, t),
gradient: b.gradient, gradient: b.gradient,
shape: b.shape shape: b.shape,
); );
} }
...@@ -1575,7 +1591,7 @@ class BoxDecoration extends Decoration { ...@@ -1575,7 +1591,7 @@ class BoxDecoration extends Decoration {
borderRadius, borderRadius,
boxShadow, boxShadow,
gradient, gradient,
shape shape,
); );
} }
...@@ -1741,9 +1757,10 @@ class _BoxDecorationPainter extends BoxPainter { ...@@ -1741,9 +1757,10 @@ class _BoxDecorationPainter extends BoxPainter {
rect: rect, rect: rect,
image: image, image: image,
colorFilter: backgroundImage.colorFilter, colorFilter: backgroundImage.colorFilter,
alignment: backgroundImage.alignment,
fit: backgroundImage.fit, fit: backgroundImage.fit,
repeat: backgroundImage.repeat alignment: backgroundImage.alignment,
centerSlice: backgroundImage.centerSlice,
repeat: backgroundImage.repeat,
); );
if (clipPath != null) if (clipPath != null)
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; 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/foundation.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
...@@ -33,7 +33,7 @@ class SynchronousTestImageProvider extends ImageProvider<int> { ...@@ -33,7 +33,7 @@ class SynchronousTestImageProvider extends ImageProvider<int> {
@override @override
ImageStreamCompleter load(int key) { ImageStreamCompleter load(int key) {
return new OneFrameImageStreamCompleter( return new OneFrameImageStreamCompleter(
new SynchronousFuture<ImageInfo>(new TestImageInfo(key)) new SynchronousFuture<ImageInfo>(new TestImageInfo(key, image: new TestImage(), scale: 1.0))
); );
} }
} }
...@@ -90,7 +90,7 @@ class TestImage extends ui.Image { ...@@ -90,7 +90,7 @@ class TestImage extends ui.Image {
} }
void main() { void main() {
test("Decoration.lerp()", () { test('Decoration.lerp()', () {
final BoxDecoration a = const BoxDecoration(color: const Color(0xFFFFFFFF)); final BoxDecoration a = const BoxDecoration(color: const Color(0xFFFFFFFF));
final BoxDecoration b = const BoxDecoration(color: const Color(0x00000000)); final BoxDecoration b = const BoxDecoration(color: const Color(0x00000000));
...@@ -104,7 +104,7 @@ void main() { ...@@ -104,7 +104,7 @@ void main() {
expect(c.color, equals(b.color)); expect(c.color, equals(b.color));
}); });
test("BoxDecorationImageListenerSync", () { test('BoxDecorationImageListenerSync', () {
final ImageProvider imageProvider = new SynchronousTestImageProvider(); final ImageProvider imageProvider = new SynchronousTestImageProvider();
final DecorationImage backgroundImage = new DecorationImage(image: imageProvider); final DecorationImage backgroundImage = new DecorationImage(image: imageProvider);
...@@ -122,7 +122,7 @@ void main() { ...@@ -122,7 +122,7 @@ void main() {
expect(onChangedCalled, equals(false)); expect(onChangedCalled, equals(false));
}); });
test("BoxDecorationImageListenerAsync", () { test('BoxDecorationImageListenerAsync', () {
new FakeAsync().run((FakeAsync async) { new FakeAsync().run((FakeAsync async) {
final ImageProvider imageProvider = new AsyncTestImageProvider(); final ImageProvider imageProvider = new AsyncTestImageProvider();
final DecorationImage backgroundImage = new DecorationImage(image: imageProvider); final DecorationImage backgroundImage = new DecorationImage(image: imageProvider);
...@@ -146,7 +146,7 @@ void main() { ...@@ -146,7 +146,7 @@ void main() {
// Regression test for https://github.com/flutter/flutter/issues/7289. // Regression test for https://github.com/flutter/flutter/issues/7289.
// A reference test would be better. // A reference test would be better.
test("BoxDecoration backgroundImage clip", () { test('BoxDecoration backgroundImage clip', () {
void testDecoration({ BoxShape shape, BorderRadius borderRadius, bool expectClip}) { void testDecoration({ BoxShape shape, BorderRadius borderRadius, bool expectClip}) {
new FakeAsync().run((FakeAsync async) { new FakeAsync().run((FakeAsync async) {
final DelayedImageProvider imageProvider = new DelayedImageProvider(); final DelayedImageProvider imageProvider = new DelayedImageProvider();
...@@ -198,4 +198,32 @@ void main() { ...@@ -198,4 +198,32 @@ void main() {
testDecoration(borderRadius: const BorderRadius.all(const Radius.circular(16.0)), expectClip: true); testDecoration(borderRadius: const BorderRadius.all(const Radius.circular(16.0)), expectClip: true);
testDecoration(expectClip: false); 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(<Invocation>[]);
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<TestImage>());
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<Paint>());
expect(call.positionalArguments[3].isAntiAlias, false);
expect(call.positionalArguments[3].colorFilter, colorFilter);
expect(call.positionalArguments[3].filterQuality, FilterQuality.low);
});
} }
...@@ -31,7 +31,7 @@ class TestCanvas implements Canvas { ...@@ -31,7 +31,7 @@ class TestCanvas implements Canvas {
} }
void main() { void main() {
test("Cover and align", () { test('Cover and align', () {
final TestImage image = new TestImage(width: 300, height: 300); final TestImage image = new TestImage(width: 300, height: 300);
final TestCanvas canvas = new TestCanvas(); final TestCanvas canvas = new TestCanvas();
paintImage( paintImage(
...@@ -51,4 +51,6 @@ void main() { ...@@ -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[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))); 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
} }
...@@ -9,13 +9,13 @@ import 'package:flutter/foundation.dart'; ...@@ -9,13 +9,13 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
class TestImageInfo implements ImageInfo { class TestImageInfo implements ImageInfo {
const TestImageInfo(this.value) : image = null, scale = null; const TestImageInfo(this.value, { this.image, this.scale });
@override @override
final ui.Image image; // ignored in test final ui.Image image;
@override @override
final double scale; // ignored in test final double scale;
final int value; final int value;
......
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