| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:async'; |
| import 'dart:typed_data'; |
| import 'dart:ui' as ui show Image, ColorFilter; |
| |
| import 'package:fake_async/fake_async.dart'; |
| import 'package:flutter/foundation.dart'; |
| import 'package:flutter/painting.dart'; |
| import 'package:flutter_test/flutter_test.dart'; |
| |
| import '../image_data.dart'; |
| import '../painting/mocks_for_image_cache.dart'; |
| import '../rendering/rendering_tester.dart'; |
| |
| class TestCanvas implements Canvas { |
| final List<Invocation> invocations = <Invocation>[]; |
| |
| @override |
| void noSuchMethod(Invocation invocation) { |
| invocations.add(invocation); |
| } |
| } |
| |
| class SynchronousTestImageProvider extends ImageProvider<int> { |
| const SynchronousTestImageProvider(this.image); |
| |
| final ui.Image image; |
| |
| @override |
| Future<int> obtainKey(ImageConfiguration configuration) { |
| return SynchronousFuture<int>(1); |
| } |
| |
| @override |
| ImageStreamCompleter load(int key, DecoderCallback decode) { |
| return OneFrameImageStreamCompleter( |
| SynchronousFuture<ImageInfo>(TestImageInfo(key, image: image)), |
| ); |
| } |
| } |
| |
| class SynchronousErrorTestImageProvider extends ImageProvider<int> { |
| const SynchronousErrorTestImageProvider(this.throwable); |
| |
| final Object throwable; |
| |
| @override |
| Future<int> obtainKey(ImageConfiguration configuration) { |
| throw throwable; |
| } |
| |
| @override |
| ImageStreamCompleter load(int key, DecoderCallback decode) { |
| throw throwable; |
| } |
| } |
| |
| class AsyncTestImageProvider extends ImageProvider<int> { |
| AsyncTestImageProvider(this.image); |
| |
| final ui.Image image; |
| |
| @override |
| Future<int> obtainKey(ImageConfiguration configuration) { |
| return Future<int>.value(2); |
| } |
| |
| @override |
| ImageStreamCompleter load(int key, DecoderCallback decode) { |
| return OneFrameImageStreamCompleter( |
| Future<ImageInfo>.value(TestImageInfo(key, image: image)), |
| ); |
| } |
| } |
| |
| class DelayedImageProvider extends ImageProvider<DelayedImageProvider> { |
| DelayedImageProvider(this.image); |
| |
| final ui.Image image; |
| |
| final Completer<ImageInfo> _completer = Completer<ImageInfo>(); |
| |
| @override |
| Future<DelayedImageProvider> obtainKey(ImageConfiguration configuration) { |
| return SynchronousFuture<DelayedImageProvider>(this); |
| } |
| |
| @override |
| ImageStreamCompleter load(DelayedImageProvider key, DecoderCallback decode) { |
| return OneFrameImageStreamCompleter(_completer.future); |
| } |
| |
| Future<void> complete() async { |
| _completer.complete(ImageInfo(image: image)); |
| } |
| |
| @override |
| String toString() => '${describeIdentity(this)}()'; |
| } |
| |
| class MultiFrameImageProvider extends ImageProvider<MultiFrameImageProvider> { |
| MultiFrameImageProvider(this.completer); |
| |
| final MultiImageCompleter completer; |
| |
| @override |
| Future<MultiFrameImageProvider> obtainKey(ImageConfiguration configuration) { |
| return SynchronousFuture<MultiFrameImageProvider>(this); |
| } |
| |
| @override |
| ImageStreamCompleter load(MultiFrameImageProvider key, DecoderCallback decode) { |
| return completer; |
| } |
| |
| @override |
| String toString() => '${describeIdentity(this)}()'; |
| } |
| |
| class MultiImageCompleter extends ImageStreamCompleter { |
| void testSetImage(ImageInfo info) { |
| setImage(info); |
| } |
| } |
| |
| void main() { |
| TestRenderingFlutterBinding(); // initializes the imageCache |
| |
| test('Decoration.lerp()', () { |
| const BoxDecoration a = BoxDecoration(color: Color(0xFFFFFFFF)); |
| const BoxDecoration b = BoxDecoration(color: Color(0x00000000)); |
| |
| BoxDecoration c = Decoration.lerp(a, b, 0.0)! as BoxDecoration; |
| expect(c.color, equals(a.color)); |
| |
| c = Decoration.lerp(a, b, 0.25)! as BoxDecoration; |
| expect(c.color, equals(Color.lerp(const Color(0xFFFFFFFF), const Color(0x00000000), 0.25))); |
| |
| c = Decoration.lerp(a, b, 1.0)! as BoxDecoration; |
| expect(c.color, equals(b.color)); |
| }); |
| |
| test('Decoration equality', () { |
| const BoxDecoration a = BoxDecoration( |
| color: Color(0xFFFFFFFF), |
| boxShadow: <BoxShadow>[BoxShadow()], |
| ); |
| |
| const BoxDecoration b = BoxDecoration( |
| color: Color(0xFFFFFFFF), |
| boxShadow: <BoxShadow>[BoxShadow()], |
| ); |
| |
| expect(a.hashCode, equals(b.hashCode)); |
| expect(a, equals(b)); |
| }); |
| |
| test('BoxDecorationImageListenerSync', () async { |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| final ImageProvider imageProvider = SynchronousTestImageProvider(image); |
| final DecorationImage backgroundImage = DecorationImage(image: imageProvider); |
| |
| final BoxDecoration boxDecoration = BoxDecoration(image: backgroundImage); |
| bool onChangedCalled = false; |
| final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { |
| onChangedCalled = true; |
| }); |
| |
| final TestCanvas canvas = TestCanvas(); |
| const ImageConfiguration imageConfiguration = ImageConfiguration(size: Size.zero); |
| boxPainter.paint(canvas, Offset.zero, imageConfiguration); |
| |
| // The onChanged callback should not be invoked during the call to boxPainter.paint |
| expect(onChangedCalled, equals(false)); |
| }); |
| |
| test('BoxDecorationImageListenerAsync', () async { |
| final ui.Image image = await createTestImage(width: 10, height: 10); |
| FakeAsync().run((FakeAsync async) { |
| final ImageProvider imageProvider = AsyncTestImageProvider(image); |
| final DecorationImage backgroundImage = DecorationImage(image: imageProvider); |
| |
| final BoxDecoration boxDecoration = BoxDecoration(image: backgroundImage); |
| bool onChangedCalled = false; |
| final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { |
| onChangedCalled = true; |
| }); |
| |
| final TestCanvas canvas = TestCanvas(); |
| const ImageConfiguration imageConfiguration = ImageConfiguration(size: Size.zero); |
| boxPainter.paint(canvas, Offset.zero, imageConfiguration); |
| |
| // The onChanged callback should be invoked asynchronously. |
| expect(onChangedCalled, equals(false)); |
| async.flushMicrotasks(); |
| expect(onChangedCalled, equals(true)); |
| }); |
| }); |
| |
| test('BoxDecorationImageListener does not change when image is clone', () async { |
| final ui.Image image1 = await createTestImage(width: 10, height: 10, cache: false); |
| final ui.Image image2 = await createTestImage(width: 10, height: 10, cache: false); |
| final MultiImageCompleter completer = MultiImageCompleter(); |
| final MultiFrameImageProvider imageProvider = MultiFrameImageProvider(completer); |
| final DecorationImage backgroundImage = DecorationImage(image: imageProvider); |
| |
| final BoxDecoration boxDecoration = BoxDecoration(image: backgroundImage); |
| bool onChangedCalled = false; |
| final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { |
| onChangedCalled = true; |
| }); |
| |
| final TestCanvas canvas = TestCanvas(); |
| const ImageConfiguration imageConfiguration = ImageConfiguration(size: Size.zero); |
| boxPainter.paint(canvas, Offset.zero, imageConfiguration); |
| |
| // The onChanged callback should be invoked asynchronously. |
| expect(onChangedCalled, equals(false)); |
| |
| completer.testSetImage(ImageInfo(image: image1.clone())); |
| await null; |
| |
| expect(onChangedCalled, equals(true)); |
| onChangedCalled = false; |
| completer.testSetImage(ImageInfo(image: image1.clone())); |
| await null; |
| |
| expect(onChangedCalled, equals(false)); |
| |
| completer.testSetImage(ImageInfo(image: image2.clone())); |
| await null; |
| |
| expect(onChangedCalled, equals(true)); |
| }); |
| |
| // Regression test for https://github.com/flutter/flutter/issues/7289. |
| // A reference test would be better. |
| test('BoxDecoration backgroundImage clip', () async { |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| void testDecoration({ BoxShape shape = BoxShape.rectangle, BorderRadius? borderRadius, required bool expectClip }) { |
| assert(shape != null); |
| FakeAsync().run((FakeAsync async) async { |
| final DelayedImageProvider imageProvider = DelayedImageProvider(image); |
| final DecorationImage backgroundImage = DecorationImage(image: imageProvider); |
| |
| final BoxDecoration boxDecoration = BoxDecoration( |
| shape: shape, |
| borderRadius: borderRadius, |
| image: backgroundImage, |
| ); |
| |
| final TestCanvas canvas = TestCanvas(); |
| const ImageConfiguration imageConfiguration = ImageConfiguration( |
| size: Size(100.0, 100.0), |
| ); |
| bool onChangedCalled = false; |
| final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { |
| onChangedCalled = true; |
| }); |
| |
| // _BoxDecorationPainter._paintDecorationImage() resolves the background |
| // image and adds a listener to the resolved image stream. |
| boxPainter.paint(canvas, Offset.zero, imageConfiguration); |
| await imageProvider.complete(); |
| |
| // Run the listener which calls onChanged() which saves an internal |
| // reference to the TestImage. |
| async.flushMicrotasks(); |
| expect(onChangedCalled, isTrue); |
| boxPainter.paint(canvas, Offset.zero, imageConfiguration); |
| |
| // We expect a clip to precede the drawImageRect call. |
| final List<Invocation> commands = canvas.invocations.where((Invocation invocation) { |
| return invocation.memberName == #clipPath || invocation.memberName == #drawImageRect; |
| }).toList(); |
| if (expectClip) { // We expect a clip to precede the drawImageRect call. |
| expect(commands.length, 2); |
| expect(commands[0].memberName, equals(#clipPath)); |
| expect(commands[1].memberName, equals(#drawImageRect)); |
| } else { |
| expect(commands.length, 1); |
| expect(commands[0].memberName, equals(#drawImageRect)); |
| } |
| }); |
| } |
| |
| testDecoration(shape: BoxShape.circle, expectClip: true); |
| testDecoration(borderRadius: const BorderRadius.all(Radius.circular(16.0)), expectClip: true); |
| testDecoration(expectClip: false); |
| }); |
| |
| test('DecorationImage test', () async { |
| const ColorFilter colorFilter = ui.ColorFilter.mode(Color(0xFF00FF00), BlendMode.src); |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| final DecorationImage backgroundImage = DecorationImage( |
| image: SynchronousTestImageProvider(image), |
| colorFilter: colorFilter, |
| fit: BoxFit.contain, |
| alignment: Alignment.bottomLeft, |
| centerSlice: const Rect.fromLTWH(10.0, 20.0, 30.0, 40.0), |
| repeat: ImageRepeat.repeatY, |
| opacity: 0.5, |
| filterQuality: FilterQuality.high, |
| invertColors: true, |
| isAntiAlias: true, |
| ); |
| |
| final BoxDecoration boxDecoration = BoxDecoration(image: backgroundImage); |
| final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { assert(false); }); |
| final TestCanvas canvas = TestCanvas(); |
| boxPainter.paint(canvas, Offset.zero, const ImageConfiguration(size: Size(100.0, 100.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], isA<ui.Image>()); |
| expect(call.positionalArguments[1], const Rect.fromLTRB(10.0, 20.0, 40.0, 60.0)); |
| expect(call.positionalArguments[2], const Rect.fromLTRB(0.0, 0.0, 100.0, 100.0)); |
| expect(call.positionalArguments[3], isA<Paint>()); |
| final Paint paint = call.positionalArguments[3] as Paint; |
| expect(paint.colorFilter, colorFilter); |
| expect(paint.color, const Color(0x7F000000)); // 0.5 opacity |
| expect(paint.filterQuality, FilterQuality.high); |
| expect(paint.isAntiAlias, true); |
| // TODO(craiglabenz): change to true when https://github.com/flutter/flutter/issues/88909 is fixed |
| expect(paint.invertColors, !kIsWeb); |
| }); |
| |
| test('DecorationImage with null textDirection configuration should throw Error', () async { |
| const ColorFilter colorFilter = ui.ColorFilter.mode(Color(0xFF00FF00), BlendMode.src); |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| final DecorationImage backgroundImage = DecorationImage( |
| image: SynchronousTestImageProvider(image), |
| colorFilter: colorFilter, |
| fit: BoxFit.contain, |
| centerSlice: const Rect.fromLTWH(10.0, 20.0, 30.0, 40.0), |
| repeat: ImageRepeat.repeatY, |
| matchTextDirection: true, |
| scale: 0.5, |
| opacity: 0.5, |
| invertColors: true, |
| isAntiAlias: true, |
| ); |
| final BoxDecoration boxDecoration = BoxDecoration(image: backgroundImage); |
| final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { |
| assert(false); |
| }); |
| final TestCanvas canvas = TestCanvas(); |
| late FlutterError error; |
| try { |
| boxPainter.paint(canvas, Offset.zero, const ImageConfiguration( |
| size: Size(100.0, 100.0), |
| )); |
| } on FlutterError catch (e) { |
| error = e; |
| } |
| expect(error, isNotNull); |
| expect(error.diagnostics.length, 4); |
| expect(error.diagnostics[2], isA<DiagnosticsProperty<DecorationImage>>()); |
| expect(error.diagnostics[3], isA<DiagnosticsProperty<ImageConfiguration>>()); |
| expect(error.toStringDeep(), |
| 'FlutterError\n' |
| ' DecorationImage.matchTextDirection can only be used when a\n' |
| ' TextDirection is available.\n' |
| ' When DecorationImagePainter.paint() was called, there was no text\n' |
| ' direction provided in the ImageConfiguration object to match.\n' |
| ' The DecorationImage was:\n' |
| ' DecorationImage(SynchronousTestImageProvider(),\n' |
| ' ColorFilter.mode(Color(0xff00ff00), BlendMode.src),\n' |
| ' BoxFit.contain, Alignment.center, centerSlice:\n' |
| ' Rect.fromLTRB(10.0, 20.0, 40.0, 60.0), ImageRepeat.repeatY,\n' |
| ' match text direction, scale 0.5, opacity 0.5,\n' |
| ' FilterQuality.low, invert colors, use anti-aliasing)\n' |
| ' The ImageConfiguration was:\n' |
| ' ImageConfiguration(size: Size(100.0, 100.0))\n', |
| ); |
| }, skip: kIsWeb); // https://github.com/flutter/flutter/issues/87364 |
| |
| test('DecorationImage - error listener', () async { |
| late String exception; |
| final DecorationImage backgroundImage = DecorationImage( |
| image: const SynchronousErrorTestImageProvider('threw'), |
| onError: (dynamic error, StackTrace? stackTrace) { |
| exception = error as String; |
| }, |
| ); |
| |
| backgroundImage.createPainter(() { }).paint( |
| TestCanvas(), |
| Rect.largest, |
| Path(), |
| ImageConfiguration.empty, |
| ); |
| // Yield so that the exception callback gets called before we check it. |
| await null; |
| expect(exception, 'threw'); |
| }); |
| |
| test('BoxDecoration.lerp - shapes', () { |
| // We don't lerp the shape, we just switch from one to the other at t=0.5. |
| // (Use a ShapeDecoration and ShapeBorder if you want to lerp the shapes...) |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(shape: BoxShape.circle), |
| -1.0, |
| ), |
| const BoxDecoration(), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(shape: BoxShape.circle), |
| 0.0, |
| ), |
| const BoxDecoration(), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(shape: BoxShape.circle), |
| 0.25, |
| ), |
| const BoxDecoration(), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(shape: BoxShape.circle), |
| 0.75, |
| ), |
| const BoxDecoration(shape: BoxShape.circle), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(shape: BoxShape.circle), |
| 1.0, |
| ), |
| const BoxDecoration(shape: BoxShape.circle), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(shape: BoxShape.circle), |
| 2.0, |
| ), |
| const BoxDecoration(shape: BoxShape.circle), |
| ); |
| }); |
| |
| test('BoxDecoration.lerp - gradients', () { |
| const Gradient gradient = LinearGradient(colors: <Color>[ Color(0x00000000), Color(0xFFFFFFFF) ]); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(gradient: gradient), |
| -1.0, |
| ), |
| const BoxDecoration(gradient: LinearGradient(colors: <Color>[ Color(0x00000000), Color(0x00FFFFFF) ])), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(gradient: gradient), |
| 0.0, |
| ), |
| const BoxDecoration(), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(gradient: gradient), |
| 0.25, |
| ), |
| const BoxDecoration(gradient: LinearGradient(colors: <Color>[ Color(0x00000000), Color(0x40FFFFFF) ])), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(gradient: gradient), |
| 0.75, |
| ), |
| const BoxDecoration(gradient: LinearGradient(colors: <Color>[ Color(0x00000000), Color(0xBFFFFFFF) ])), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(gradient: gradient), |
| 1.0, |
| ), |
| const BoxDecoration(gradient: gradient), |
| ); |
| expect( |
| BoxDecoration.lerp( |
| const BoxDecoration(), |
| const BoxDecoration(gradient: gradient), |
| 2.0, |
| ), |
| const BoxDecoration(gradient: gradient), |
| ); |
| }); |
| |
| test('Decoration.lerp with unrelated decorations', () { |
| expect(Decoration.lerp(const FlutterLogoDecoration(), const BoxDecoration(), 0.0), isA<FlutterLogoDecoration>()); |
| expect(Decoration.lerp(const FlutterLogoDecoration(), const BoxDecoration(), 0.25), isA<FlutterLogoDecoration>()); |
| expect(Decoration.lerp(const FlutterLogoDecoration(), const BoxDecoration(), 0.75), isA<BoxDecoration>()); |
| expect(Decoration.lerp(const FlutterLogoDecoration(), const BoxDecoration(), 1.0), isA<BoxDecoration>()); |
| }); |
| |
| test('paintImage BoxFit.none scale test', () async { |
| for (double scale = 1.0; scale <= 4.0; scale += 1.0) { |
| final TestCanvas canvas = TestCanvas(); |
| |
| const Rect outputRect = Rect.fromLTWH(30.0, 30.0, 250.0, 250.0); |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| |
| paintImage( |
| canvas: canvas, |
| rect: outputRect, |
| image: image, |
| scale: scale, |
| alignment: Alignment.bottomRight, |
| fit: BoxFit.none, |
| ); |
| |
| 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], isA<ui.Image>()); |
| |
| // 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 = Rect.fromPoints( |
| outputRect.bottomRight.translate(-expectedTileSize.width, -expectedTileSize.height), |
| outputRect.bottomRight, |
| ); |
| expect(call.positionalArguments[2], expectedTileRect); |
| |
| expect(call.positionalArguments[3], isA<Paint>()); |
| } |
| }); |
| |
| test('paintImage BoxFit.scaleDown scale test', () async { |
| for (double scale = 1.0; scale <= 4.0; scale += 1.0) { |
| final TestCanvas canvas = TestCanvas(); |
| |
| // container size > scaled image size |
| const Rect outputRect = Rect.fromLTWH(30.0, 30.0, 250.0, 250.0); |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| |
| paintImage( |
| canvas: canvas, |
| rect: outputRect, |
| image: image, |
| scale: scale, |
| alignment: Alignment.bottomRight, |
| fit: BoxFit.scaleDown, |
| ); |
| |
| 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], isA<ui.Image>()); |
| |
| // 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 = Rect.fromPoints( |
| outputRect.bottomRight.translate(-expectedTileSize.width, -expectedTileSize.height), |
| outputRect.bottomRight, |
| ); |
| expect(call.positionalArguments[2], expectedTileRect); |
| |
| expect(call.positionalArguments[3], isA<Paint>()); |
| } |
| }); |
| |
| test('paintImage BoxFit.scaleDown test', () async { |
| final TestCanvas canvas = TestCanvas(); |
| |
| // container height (20 px) < scaled image height (50 px) |
| const Rect outputRect = Rect.fromLTWH(30.0, 30.0, 250.0, 20.0); |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| |
| paintImage( |
| canvas: canvas, |
| rect: outputRect, |
| image: image, |
| scale: 2.0, |
| alignment: Alignment.bottomRight, |
| fit: BoxFit.scaleDown, |
| ); |
| |
| 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], isA<ui.Image>()); |
| |
| // sourceRect should contain all pixels of the source image |
| expect(call.positionalArguments[1], Offset.zero & imageSize); |
| |
| // Image should be scaled down to fit in height |
| // and be positioned in the bottom right of the outputRect |
| const Size expectedTileSize = Size(20.0, 20.0); |
| final Rect expectedTileRect = Rect.fromPoints( |
| outputRect.bottomRight.translate(-expectedTileSize.width, -expectedTileSize.height), |
| outputRect.bottomRight, |
| ); |
| expect(call.positionalArguments[2], expectedTileRect); |
| |
| expect(call.positionalArguments[3], isA<Paint>()); |
| }); |
| |
| test('paintImage boxFit, scale and alignment test', () async { |
| const List<BoxFit> boxFits = <BoxFit>[ |
| BoxFit.contain, |
| BoxFit.cover, |
| BoxFit.fitWidth, |
| BoxFit.fitWidth, |
| BoxFit.fitHeight, |
| BoxFit.none, |
| BoxFit.scaleDown, |
| ]; |
| |
| for (final BoxFit boxFit in boxFits) { |
| final TestCanvas canvas = TestCanvas(); |
| |
| const Rect outputRect = Rect.fromLTWH(30.0, 30.0, 250.0, 250.0); |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| |
| paintImage( |
| canvas: canvas, |
| rect: outputRect, |
| image: image, |
| scale: 3.0, |
| fit: boxFit, |
| ); |
| |
| 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 |
| // ignore: avoid_dynamic_calls |
| expect(call.positionalArguments[2].center, outputRect.center); |
| } |
| }); |
| |
| test('DecorationImage scale test', () async { |
| final ui.Image image = await createTestImage(width: 100, height: 100); |
| final DecorationImage backgroundImage = DecorationImage( |
| image: SynchronousTestImageProvider(image), |
| scale: 4, |
| alignment: Alignment.topLeft, |
| ); |
| |
| final BoxDecoration boxDecoration = BoxDecoration(image: backgroundImage); |
| final BoxPainter boxPainter = boxDecoration.createBoxPainter(() { assert(false); }); |
| final TestCanvas canvas = TestCanvas(); |
| boxPainter.paint(canvas, Offset.zero, const ImageConfiguration(size: Size(100.0, 100.0))); |
| |
| final Invocation call = canvas.invocations.firstWhere((Invocation call) => call.memberName == #drawImageRect); |
| // The image should scale down to Size(25.0, 25.0) from Size(100.0, 100.0) |
| // considering DecorationImage scale to be 4.0 and Image scale to be 1.0. |
| // ignore: avoid_dynamic_calls |
| expect(call.positionalArguments[2].size, const Size(25.0, 25.0)); |
| expect(call.positionalArguments[2], const Rect.fromLTRB(0.0, 0.0, 25.0, 25.0)); |
| }); |
| |
| test('DecorationImagePainter disposes of image when disposed', () async { |
| final ImageProvider provider = MemoryImage(Uint8List.fromList(kTransparentImage)); |
| |
| final ImageStream stream = provider.resolve(ImageConfiguration.empty); |
| |
| final Completer<ImageInfo> infoCompleter = Completer<ImageInfo>(); |
| void _listener(ImageInfo image, bool syncCall) { |
| assert(!infoCompleter.isCompleted); |
| infoCompleter.complete(image); |
| } |
| stream.addListener(ImageStreamListener(_listener)); |
| |
| final ImageInfo info = await infoCompleter.future; |
| final int baselineRefCount = info.image.debugGetOpenHandleStackTraces()!.length; |
| |
| final DecorationImagePainter painter = DecorationImage(image: provider).createPainter(() {}); |
| final Canvas canvas = TestCanvas(); |
| painter.paint(canvas, Rect.zero, Path(), ImageConfiguration.empty); |
| |
| expect(info.image.debugGetOpenHandleStackTraces()!.length, baselineRefCount + 1); |
| painter.dispose(); |
| expect(info.image.debugGetOpenHandleStackTraces()!.length, baselineRefCount); |
| |
| info.dispose(); |
| }, skip: kIsWeb); // https://github.com/flutter/flutter/issues/87442 |
| } |