Unverified Commit 71c45709 authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

Add ResizeImage.policy (#121154)

* Add ResizeImage.policy

This adds a new `ResizeImage.policy` property that controls how `ResizeImage`
will interpret its `width` and `height` properties. The existing behavior is
preserved via `ResizeImagePolicy.exact` (default), but there is now the option
to use `ResizeImagePolicy.fit`, which satisfies the use case outlined in
flutter/flutter#118543.

The API doc assets were added in flutter/assets-for-api-docs#209

Fixes flutter/flutter#118543

* Docuemnt public member

* Remove protected annotation from overrides - was failing tests

* Fixed analysis of code in Dartdoc

* More dartdoc code analysis fixes

* One more fix

* Review comments
parent 9619c333
......@@ -4,6 +4,7 @@
import 'dart:async';
import 'dart:io';
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'dart:ui' show Locale, Size, TextDirection;
......@@ -846,11 +847,13 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
class ResizeImageKey {
// Private constructor so nobody from the outside can poison the image cache
// with this key. It's only accessible to [ResizeImage] internally.
const ResizeImageKey._(this._providerCacheKey, this._width, this._height);
const ResizeImageKey._(this._providerCacheKey, this._policy, this._width, this._height, this._allowUpscaling);
final Object _providerCacheKey;
final ResizeImagePolicy _policy;
final int? _width;
final int? _height;
final bool _allowUpscaling;
@override
bool operator ==(Object other) {
......@@ -859,12 +862,407 @@ class ResizeImageKey {
}
return other is ResizeImageKey
&& other._providerCacheKey == _providerCacheKey
&& other._policy == _policy
&& other._width == _width
&& other._height == _height;
&& other._height == _height
&& other._allowUpscaling == _allowUpscaling;
}
@override
int get hashCode => Object.hash(_providerCacheKey, _width, _height);
int get hashCode => Object.hash(_providerCacheKey, _policy, _width, _height, _allowUpscaling);
}
/// Configures the behavior for [ResizeImage].
///
/// This is used in [ResizeImage.policy] to affect how the [ResizeImage.width]
/// and [ResizeImage.height] properties are interpreted.
enum ResizeImagePolicy {
/// Sizes the image to the exact width and height specified by
/// [ResizeImage.width] and [ResizeImage.height].
///
/// If [ResizeImage.width] and [ResizeImage.height] are both non-null, the
/// output image will have the specified width and height (with the
/// corresponding aspect ratio) regardless of whether it matches the source
/// image's intrinsic aspect ratio. This case is similar to [BoxFit.fill].
///
/// If only one of `width` and `height` is non-null, then the output image
/// will be scaled to the associated width or height, and the other dimension
/// will take whatever value is needed to maintain the image's original aspect
/// ratio. These cases are simnilar to [BoxFit.fitWidth] and
/// [BoxFit.fitHeight], respectively.
///
/// If [ResizeImage.allowUpscaling] is false (the default), the width and the
/// height of the output image will each be clamped to the intrinsic width and
/// height of the image. This may result in a different aspect ratio than the
/// aspect ratio specified by the target width and height (e.g. if the height
/// gets clamped downwards but the width does not).
///
/// ## Examples
///
/// The examples below show how [ResizeImagePolicy.exact] works in various
/// scenarios. In each example, the source image has a size of 300x200
/// (landscape orientation), the red box is a 150x150 square, and the green
/// box is a 400x400 square.
///
/// <table>
/// <tr>
/// <td>Scenario</td>
/// <td>Output</td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// width: 150,
/// height: 150,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_150x150_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// width: 150,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_150xnull_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// height: 150,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_nullx150_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// width: 400,
/// height: 400,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_400x400_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// width: 400,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_400xnull_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// height: 400,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_nullx400_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// width: 400,
/// height: 400,
/// allowUpscaling: true,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_400x400_true.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// width: 400,
/// allowUpscaling: true,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_400xnull_true.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// height: 400,
/// allowUpscaling: true,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_nullx400_true.png)
///
/// </td>
/// </tr>
/// </table>
exact,
/// Scales the image as necessary to ensure that it fits within the bounding
/// box specified by [ResizeImage.width] and [ResizeImage.height] while
/// maintaining its aspect ratio.
///
/// If [ResizeImage.allowUpscaling] is true, the image will be scaled up or
/// down to best fit the bounding box; otherwise it will only ever be scaled
/// down.
///
/// This is conceptually similar to [BoxFit.contain].
///
/// ## Examples
///
/// The examples below show how [ResizeImagePolicy.fit] works in various
/// scenarios. In each example, the source image has a size of 300x200
/// (landscape orientation), the red box is a 150x150 square, and the green
/// box is a 400x400 square.
///
/// <table>
/// <tr>
/// <td>Scenario</td>
/// <td>Output</td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// width: 150,
/// height: 150,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_150x150_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// width: 150,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_150xnull_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// height: 150,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_nullx150_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// width: 400,
/// height: 400,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_400x400_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// width: 400,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_400xnull_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// height: 400,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_nullx400_false.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// width: 400,
/// height: 400,
/// allowUpscaling: true,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_400x400_true.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// width: 400,
/// allowUpscaling: true,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_400xnull_true.png)
///
/// </td>
/// </tr>
/// <tr>
/// <td>
///
/// ```dart
/// const ResizeImage(
/// AssetImage('dragon_cake.jpg'),
/// policy: ResizeImagePolicy.fit,
/// height: 400,
/// allowUpscaling: true,
/// )
/// ```
///
/// </td>
/// <td>
///
/// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_nullx400_true.png)
///
/// </td>
/// </tr>
/// </table>
fit,
}
/// Instructs Flutter to decode the image at the specified dimensions
......@@ -881,10 +1279,13 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
/// The cached image will be directly decoded and stored at the resolution
/// defined by `width` and `height`. The image will lose detail and
/// use less memory if resized to a size smaller than the native size.
///
/// At least one of `width` and `height` must be non-null.
const ResizeImage(
this.imageProvider, {
this.width,
this.height,
this.policy = ResizeImagePolicy.exact,
this.allowUpscaling = false,
}) : assert(width != null || height != null);
......@@ -892,11 +1293,20 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
final ImageProvider imageProvider;
/// The width the image should decode to and cache.
///
/// At least one of this and [height] must be non-null.
final int? width;
/// The height the image should decode to and cache.
///
/// At least one of this and [width] must be non-null.
final int? height;
/// The policy that determines how [width] and [height] are interpreted.
///
/// Defaults to [ResizeImagePolicy.exact].
final ResizeImagePolicy policy;
/// Whether the [width] and [height] parameters should be clamped to the
/// intrinsic width and height of the image.
///
......@@ -919,6 +1329,10 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
}
@override
@Deprecated(
'Implement loadImage for faster image loading. '
'This feature was deprecated after v2.13.0-1.0.pre.',
)
ImageStreamCompleter load(ResizeImageKey key, DecoderCallback decode) {
Future<ui.Codec> decodeResize(Uint8List buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
assert(
......@@ -936,6 +1350,10 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
}
@override
@Deprecated(
'Implement loadImage for image loading. '
'This feature was deprecated after v3.7.0-1.4.pre.',
)
ImageStreamCompleter loadBuffer(ResizeImageKey key, DecoderBufferCallback decode) {
Future<ui.Codec> decodeResize(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
assert(
......@@ -962,17 +1380,56 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
'getTargetSize.',
);
return decode(buffer, getTargetSize: (int intrinsicWidth, int intrinsicHeight) {
int? targetWidth = width;
int? targetHeight = height;
if (!allowUpscaling) {
if (targetWidth != null && targetWidth > intrinsicWidth) {
targetWidth = intrinsicWidth;
}
if (targetHeight != null && targetHeight > intrinsicHeight) {
targetHeight = intrinsicHeight;
}
switch (policy) {
case ResizeImagePolicy.exact:
int? targetWidth = width;
int? targetHeight = height;
if (!allowUpscaling) {
if (targetWidth != null && targetWidth > intrinsicWidth) {
targetWidth = intrinsicWidth;
}
if (targetHeight != null && targetHeight > intrinsicHeight) {
targetHeight = intrinsicHeight;
}
}
return ui.TargetImageSize(width: targetWidth, height: targetHeight);
case ResizeImagePolicy.fit:
final double aspectRatio = intrinsicWidth / intrinsicHeight;
final int maxWidth = width ?? intrinsicWidth;
final int maxHeight = height ?? intrinsicHeight;
int targetWidth = intrinsicWidth;
int targetHeight = intrinsicHeight;
if (targetWidth > maxWidth) {
targetWidth = maxWidth;
targetHeight = targetWidth ~/ aspectRatio;
}
if (targetHeight > maxHeight) {
targetHeight = maxHeight;
targetWidth = (targetHeight * aspectRatio).floor();
}
if (allowUpscaling) {
if (width == null) {
assert(height != null);
targetHeight = height!;
targetWidth = (targetHeight * aspectRatio).floor();
} else if (height == null) {
targetWidth = width!;
targetHeight = targetWidth ~/ aspectRatio;
} else {
final int derivedMaxWidth = (maxHeight * aspectRatio).floor();
final int derivedMaxHeight = maxWidth ~/ aspectRatio;
targetWidth = math.min(maxWidth, derivedMaxWidth);
targetHeight = math.min(maxHeight, derivedMaxHeight);
}
}
return ui.TargetImageSize(width: targetWidth, height: targetHeight);
}
return ui.TargetImageSize(width: targetWidth, height: targetHeight);
});
}
......@@ -993,10 +1450,10 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
if (completer == null) {
// This future has completed synchronously (completer was never assigned),
// so we can directly create the synchronous result to return.
result = SynchronousFuture<ResizeImageKey>(ResizeImageKey._(key, width, height));
result = SynchronousFuture<ResizeImageKey>(ResizeImageKey._(key, policy, width, height, allowUpscaling));
} else {
// This future did not synchronously complete.
completer.complete(ResizeImageKey._(key, width, height));
completer.complete(ResizeImageKey._(key, policy, width, height, allowUpscaling));
}
});
if (result != null) {
......
......@@ -48,7 +48,7 @@ const List<int> kAnimatedGif = <int> [
// Constructed by the following code:
// ```dart
// Future<void> someTest(WidgetTester tester) async {
// Uint8List bytes;
// Uint8List? bytes;
// await tester.runAsync(() async {
// const int imageWidth = 100;
// const int imageHeight = 100;
......@@ -60,9 +60,9 @@ const List<int> kAnimatedGif = <int> [
// ui.decodeImageFromPixels(
// pixels, imageWidth, imageHeight, ui.PixelFormat.rgba8888,
// (ui.Image image) async {
// final ByteData byteData = await image.toByteData(
// final ByteData? byteData = await image.toByteData(
// format: ui.ImageByteFormat.png);
// bytes = byteData.buffer.asUint8List();
// bytes = byteData!.buffer.asUint8List();
// completer.complete();
// },
// );
......@@ -91,3 +91,35 @@ const List<int> kBlueRectPng = <int> [
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 204, 5, 234, 78, 2, 198, 180,
170, 48, 200, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
];
/// A portrait-mode (50x100) PNG with blue pixels.
const List<int> kBluePortraitPng = <int> [
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 50, 0,
0, 0, 100, 8, 6, 0, 0, 0, 196, 232, 99, 91, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8,
8, 8, 124, 8, 100, 136, 0, 0, 0, 140, 73, 68, 65, 84, 120, 156, 237, 207, 1,
13, 192, 48, 12, 192, 176, 126, 252, 57, 247, 36, 46, 45, 186, 108, 4, 201,
51, 179, 59, 63, 112, 110, 7, 124, 197, 72, 141, 145, 26, 35, 53, 70, 106,
140, 212, 24, 169, 49, 82, 99, 164, 198, 72, 141, 145, 26, 35, 53, 70, 106,
140, 212, 24, 169, 49, 82, 99, 164, 198, 72, 141, 145, 26, 35, 53, 70, 106,
140, 212, 24, 169, 49, 82, 99, 164, 198, 72, 141, 145, 26, 35, 53, 70, 106,
140, 212, 24, 169, 49, 82, 99, 164, 198, 72, 141, 145, 26, 35, 53, 70, 106,
140, 212, 24, 169, 49, 82, 99, 164, 198, 72, 141, 145, 26, 35, 53, 70, 106,
140, 212, 24, 169, 49, 82, 99, 164, 198, 72, 205, 11, 105, 8, 2, 198, 99, 140,
153, 87, 0, 0, 0, 0, 73, 69, 78, 68, 174, 66, 96, 130,
];
/// A landscape-mode (100x50) PNG with blue pixels.
const List<int> kBlueLandscapePng = <int> [
137, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 100, 0,
0, 0, 50, 8, 6, 0, 0, 0, 170, 53, 126, 190, 0, 0, 0, 4, 115, 66, 73, 84, 8, 8,
8, 8, 124, 8, 100, 136, 0, 0, 0, 143, 73, 68, 65, 84, 120, 156, 237, 209, 65,
13, 0, 32, 16, 192, 176, 3, 255, 158, 225, 141, 2, 246, 104, 21, 44, 217, 154,
57, 103, 200, 216, 191, 3, 120, 25, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 24, 18, 99, 72, 140, 33, 49, 134, 196, 24, 18,
99, 72, 140, 33, 49, 134, 196, 92, 164, 190, 2, 98, 53, 3, 99, 174, 0, 0, 0,
0, 73, 69, 78, 68, 174, 66, 96, 130,
];
......@@ -20,122 +20,369 @@ void main() {
PaintingBinding.instance.imageCache.clearLiveImages();
});
test('ResizeImage resizes to the correct dimensions (up)', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage imageProvider = MemoryImage(bytes);
final Size rawImageSize = await _resolveAndGetSize(imageProvider);
expect(rawImageSize, const Size(1, 1));
const Size resizeDims = Size(14, 7);
final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round(), allowUpscaling: true);
const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
expect(resizedImageSize, resizeDims);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56312
test('ResizeImage resizes to the correct dimensions (down)', () async {
final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
final MemoryImage imageProvider = MemoryImage(bytes);
final Size rawImageSize = await _resolveAndGetSize(imageProvider);
expect(rawImageSize, const Size(50, 50));
const Size resizeDims = Size(25, 25);
final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round(), allowUpscaling: true);
const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
expect(resizedImageSize, resizeDims);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56312
test('ResizeImage resizes to the correct dimensions - no upscaling', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage imageProvider = MemoryImage(bytes);
final Size rawImageSize = await _resolveAndGetSize(imageProvider);
expect(rawImageSize, const Size(1, 1));
const Size resizeDims = Size(1, 1);
final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round());
const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
expect(resizedImageSize, resizeDims);
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/56312
test('ResizeImage does not resize when no size is passed', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage imageProvider = MemoryImage(bytes);
final Size rawImageSize = await _resolveAndGetSize(imageProvider);
expect(rawImageSize, const Size(1, 1));
// Cannot pass in two null arguments for cache dimensions, so will use the regular
// MemoryImage
final MemoryImage resizedImage = MemoryImage(bytes);
final Size resizedImageSize = await _resolveAndGetSize(resizedImage);
expect(resizedImageSize, const Size(1, 1));
});
group('ResizeImage', () {
group('resizing', () {
test('upscales to the correct dimensions', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage imageProvider = MemoryImage(bytes);
final Size rawImageSize = await _resolveAndGetSize(imageProvider);
expect(rawImageSize, const Size(1, 1));
test('ResizeImage stores values', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
memoryImage.resolve(ImageConfiguration.empty);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: 20);
expect(resizeImage.width, 10);
expect(resizeImage.height, 20);
expect(resizeImage.imageProvider, memoryImage);
expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
});
const Size resizeDims = Size(14, 7);
final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round(), allowUpscaling: true);
const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
expect(resizedImageSize, resizeDims);
});
test('ResizeImage takes one dim', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10);
expect(resizeImage.width, 10);
expect(resizeImage.height, null);
expect(resizeImage.imageProvider, memoryImage);
expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
});
test('ResizeImage forms closure', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
test('downscales to the correct dimensions', () async {
final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
final MemoryImage imageProvider = MemoryImage(bytes);
final Size rawImageSize = await _resolveAndGetSize(imageProvider);
expect(rawImageSize, const Size(50, 50));
Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) {
expect(cacheWidth, 123);
expect(cacheHeight, 321);
expect(allowUpscaling, false);
return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling);
}
const Size resizeDims = Size(25, 25);
final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round(), allowUpscaling: true);
const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
expect(resizedImageSize, resizeDims);
});
resizeImage.loadBuffer(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
});
test('refuses upscaling when allowUpscaling=false', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage imageProvider = MemoryImage(bytes);
final Size rawImageSize = await _resolveAndGetSize(imageProvider);
expect(rawImageSize, const Size(1, 1));
const Size resizeDims = Size(50, 50);
final ResizeImage resizedImage = ResizeImage(MemoryImage(bytes), width: resizeDims.width.round(), height: resizeDims.height.round());
const ImageConfiguration resizeConfig = ImageConfiguration(size: resizeDims);
final Size resizedImageSize = await _resolveAndGetSize(resizedImage, configuration: resizeConfig);
expect(resizedImageSize, const Size(1, 1));
});
group('with policy=fit and allowResize=false', () {
test('constrains square image to bounded portrait rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, height: 50, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 25));
});
test('constrains square image to bounded landscape rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 50, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 25));
});
test('constrains square image to bounded square', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 25));
});
test('constrains square image to bounded width', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 25));
});
test('constrains square image to bounded height', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 25));
});
test('constrains portrait image to bounded portrait rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, height: 60, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 50));
});
test('constrains portrait image to bounded landscape rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 60, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(12, 25));
});
test('constrains portrait image to bounded square', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(12, 25));
});
test('constrains portrait image to bounded width', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 50));
});
test('constrains portrait image to bounded height', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(12, 25));
});
test('constrains landscape image to bounded portrait rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, height: 60, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 12));
});
test('constrains landscape image to bounded landscape rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 60, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(50, 25));
});
test('constrains landscape image to bounded square', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 12));
});
test('constrains landscape image to bounded width', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(25, 12));
});
test('constrains landscape image to bounded height', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, height: 25, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(50, 25));
});
test('leaves image as-is if constraints are bigger than image', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 120, height: 100, policy: ResizeImagePolicy.fit);
await _expectImageSize(resizedImage, const Size(50, 50));
});
});
group('with policy=fit and allowResize=true', () {
test('constrains square image to bounded portrait rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 100, height: 200, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 100));
});
test('constrains square image to bounded landscape rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 200, height: 100, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 100));
});
test('constrains square image to bounded square', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 100, height: 100, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 100));
});
test('constrains square image to bounded width', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 100, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 100));
});
test('constrains square image to bounded height', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, height: 100, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 100));
});
test('ResizeImage handles sync obtainKey', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
test('constrains portrait image to bounded portrait rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 100, height: 250, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 200));
});
bool isAsync = false;
resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
expect(isAsync, false);
test('constrains portrait image to bounded landscape rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 400, height: 200, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 200));
});
test('constrains portrait image to bounded square', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 200, height: 200, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 200));
});
test('constrains portrait image to bounded width', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 100, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 200));
});
test('constrains portrait image to bounded height', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBluePortraitPng));
await _expectImageSize(rawImage, const Size(50, 100));
final ResizeImage resizedImage = ResizeImage(rawImage, height: 200, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(100, 200));
});
test('constrains landscape image to bounded portrait rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 200, height: 400, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(200, 100));
});
test('constrains landscape image to bounded landscape rect', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 250, height: 100, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(200, 100));
});
test('constrains landscape image to bounded square', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 200, height: 200, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(200, 100));
});
test('constrains landscape image to bounded width', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 200, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(200, 100));
});
test('constrains landscape image to bounded height', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueLandscapePng));
await _expectImageSize(rawImage, const Size(100, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, height: 100, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(200, 100));
});
test('shrinks image if constraints are smaller than image', () async {
final MemoryImage rawImage = MemoryImage(Uint8List.fromList(kBlueSquarePng));
await _expectImageSize(rawImage, const Size(50, 50));
final ResizeImage resizedImage = ResizeImage(rawImage, width: 25, height: 30, policy: ResizeImagePolicy.fit, allowUpscaling: true);
await _expectImageSize(resizedImage, const Size(25, 25));
});
});
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/73120);
test('does not resize when no size is passed', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage imageProvider = MemoryImage(bytes);
final Size rawImageSize = await _resolveAndGetSize(imageProvider);
expect(rawImageSize, const Size(1, 1));
final ImageProvider<Object> resizedImage = ResizeImage.resizeIfNeeded(null, null, imageProvider);
final Size resizedImageSize = await _resolveAndGetSize(resizedImage);
expect(resizedImageSize, const Size(1, 1));
});
test('stores values', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
memoryImage.resolve(ImageConfiguration.empty);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10, height: 20);
expect(resizeImage.width, 10);
expect(resizeImage.height, 20);
expect(resizeImage.imageProvider, memoryImage);
expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
});
test('takes one dim', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 10);
expect(resizeImage.width, 10);
expect(resizeImage.height, null);
expect(resizeImage.imageProvider, memoryImage);
expect(memoryImage.resolve(ImageConfiguration.empty) != resizeImage.resolve(ImageConfiguration.empty), true);
});
test('forms closure', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321, allowUpscaling: true);
Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {ui.TargetImageSizeCallback? getTargetSize}) {
return PaintingBinding.instance.instantiateImageCodecWithSize(buffer, getTargetSize: (int intrinsicWidth, int intrinsicHeight) {
expect(getTargetSize, isNotNull);
final ui.TargetImageSize targetSize = getTargetSize!(intrinsicWidth, intrinsicHeight);
expect(targetSize.width, 123);
expect(targetSize.height, 321);
return targetSize;
});
}
resizeImage.loadImage(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
});
isAsync = true;
expect(isAsync, true);
});
test('ResizeImage handles async obtainKey', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final _AsyncKeyMemoryImage memoryImage = _AsyncKeyMemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
test('handles sync obtainKey', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
bool isAsync = false;
resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
bool isAsync = false;
bool keyObtained = false;
resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
keyObtained = true;
expect(isAsync, false);
});
isAsync = true;
expect(isAsync, true);
expect(keyObtained, true);
});
test('handles async obtainKey', () async {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final _AsyncKeyMemoryImage memoryImage = _AsyncKeyMemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
bool isAsync = false;
final Completer<void> completer = Completer<void>();
resizeImage.obtainKey(ImageConfiguration.empty).then((Object key) {
try {
expect(isAsync, true);
} finally {
completer.complete();
}
});
isAsync = true;
await completer.future;
expect(isAsync, true);
});
isAsync = true;
expect(isAsync, true);
});
}
Future<void> _expectImageSize(ImageProvider<Object> imageProvider, Size size) async {
final Size actualSize = await _resolveAndGetSize(imageProvider);
expect(actualSize, size);
}
Future<Size> _resolveAndGetSize(
ImageProvider imageProvider, {
ImageConfiguration configuration = ImageConfiguration.empty,
......
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