From b319938ecdd5efc747ea99b1e575e761df8913c0 Mon Sep 17 00:00:00 2001 From: Todd Volkert Date: Wed, 25 Jan 2023 21:21:28 -0800 Subject: [PATCH] Add more flexible image API (#118966) This updates the framework to provide higher level wrappers around ui.instantiateImageCodecWithSize(). Functionally, this doesn't change anything (other than deprecating the older loadBuffer() method in favor of loadImage()), but it sets the stage for a simpler change that will allow us to provide a more flexible way to load sized images. #118543 --- .../lib/src/animated_placeholder.dart | 2 +- .../cache_width_cache_height_integration.dart | 25 +- .../lib/src/painting/_network_image_io.dart | 39 +++- .../lib/src/painting/_network_image_web.dart | 32 ++- .../flutter/lib/src/painting/binding.dart | 30 ++- .../lib/src/painting/image_provider.dart | 215 ++++++++++++++++-- .../widgets/scroll_aware_image_provider.dart | 3 + .../test/painting/image_test_utils.dart | 7 +- .../test/painting/mocks_for_image_cache.dart | 19 +- .../test/widgets/image_resolution_test.dart | 2 +- packages/flutter/test/widgets/image_test.dart | 2 +- .../scroll_aware_image_provider_test.dart | 4 +- 12 files changed, 323 insertions(+), 57 deletions(-) diff --git a/dev/benchmarks/macrobenchmarks/lib/src/animated_placeholder.dart b/dev/benchmarks/macrobenchmarks/lib/src/animated_placeholder.dart index f6f071d72e..003e6b2869 100644 --- a/dev/benchmarks/macrobenchmarks/lib/src/animated_placeholder.dart +++ b/dev/benchmarks/macrobenchmarks/lib/src/animated_placeholder.dart @@ -58,7 +58,7 @@ class DelayedBase64Image extends ImageProvider { } @override - ImageStreamCompleter loadBuffer(int key, DecoderBufferCallback decode) { + ImageStreamCompleter loadImage(int key, ImageDecoderCallback decode) { return MultiFrameImageStreamCompleter( codec: Future.delayed( delay, diff --git a/dev/integration_tests/web_e2e_tests/test_driver/cache_width_cache_height_integration.dart b/dev/integration_tests/web_e2e_tests/test_driver/cache_width_cache_height_integration.dart index 97df31bfa5..431e816e71 100644 --- a/dev/integration_tests/web_e2e_tests/test_driver/cache_width_cache_height_integration.dart +++ b/dev/integration_tests/web_e2e_tests/test_driver/cache_width_cache_height_integration.dart @@ -9,15 +9,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; -// This class allows loadBuffer, a protected method, to be called with a custom -// DecoderBufferCallback function. +// This class allows loadImage, a protected method, to be called with a custom +// ImageDecoderCallback function. class LoadTestImageProvider extends ImageProvider { LoadTestImageProvider(this.provider); final ImageProvider provider; - ImageStreamCompleter testLoad(Object key, DecoderBufferCallback decode) { - return provider.loadBuffer(key, decode); + ImageStreamCompleter testLoad(Object key, ImageDecoderCallback decode) { + return provider.loadImage(key, decode); } @override @@ -26,7 +26,7 @@ class LoadTestImageProvider extends ImageProvider { } @override - ImageStreamCompleter loadBuffer(Object key, DecoderBufferCallback decode) { + ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) { throw UnimplementedError(); } } @@ -47,12 +47,15 @@ void main() { bool called = false; - Future decode(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) { - expect(cacheHeight, expectedCacheHeight); - expect(cacheWidth, expectedCacheWidth); - expect(allowUpscaling, false); - called = true; - return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling); + Future 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, expectedCacheWidth); + expect(targetSize.height, expectedCacheHeight); + called = true; + return targetSize; + }); } final ImageProvider resizeImage = image.image; diff --git a/packages/flutter/lib/src/painting/_network_image_io.dart b/packages/flutter/lib/src/painting/_network_image_io.dart index 354670d869..827c781dd7 100644 --- a/packages/flutter/lib/src/painting/_network_image_io.dart +++ b/packages/flutter/lib/src/painting/_network_image_io.dart @@ -43,7 +43,7 @@ class NetworkImage extends image_provider.ImageProvider chunkEvents = StreamController(); return MultiFrameImageStreamCompleter( - codec: _loadAsync(key as NetworkImage, chunkEvents, null, decode), + codec: _loadAsync(key as NetworkImage, chunkEvents, decodeDeprecated: decode), chunkEvents: chunkEvents.stream, scale: key.scale, debugLabel: key.url, @@ -62,7 +62,26 @@ class NetworkImage extends image_provider.ImageProvider chunkEvents = StreamController(); return MultiFrameImageStreamCompleter( - codec: _loadAsync(key as NetworkImage, chunkEvents, decode, null), + codec: _loadAsync(key as NetworkImage, chunkEvents, decodeBufferDeprecated: decode), + chunkEvents: chunkEvents.stream, + scale: key.scale, + debugLabel: key.url, + informationCollector: () => [ + DiagnosticsProperty('Image provider', this), + DiagnosticsProperty('Image key', key), + ], + ); + } + + @override + ImageStreamCompleter loadImage(image_provider.NetworkImage key, image_provider.ImageDecoderCallback decode) { + // Ownership of this controller is handed off to [_loadAsync]; it is that + // method's responsibility to close the controller's stream when the image + // has been loaded or an error is thrown. + final StreamController chunkEvents = StreamController(); + + return MultiFrameImageStreamCompleter( + codec: _loadAsync(key as NetworkImage, chunkEvents, decode: decode), chunkEvents: chunkEvents.stream, scale: key.scale, debugLabel: key.url, @@ -92,10 +111,11 @@ class NetworkImage extends image_provider.ImageProvider _loadAsync( NetworkImage key, - StreamController chunkEvents, - image_provider.DecoderBufferCallback? decode, - image_provider.DecoderCallback? decodeDepreacted, - ) async { + StreamController chunkEvents, { + image_provider.ImageDecoderCallback? decode, + image_provider.DecoderBufferCallback? decodeBufferDeprecated, + image_provider.DecoderCallback? decodeDeprecated, + }) async { try { assert(key == this); @@ -131,9 +151,12 @@ class NetworkImage extends image_provider.ImageProvider chunkEvents = StreamController(); + + return MultiFrameImageStreamCompleter( + chunkEvents: chunkEvents.stream, + codec: _loadAsync(key as NetworkImage, decode, null, null, chunkEvents), scale: key.scale, debugLabel: key.url, informationCollector: _imageStreamInformationCollector(key), @@ -106,8 +122,9 @@ class NetworkImage // directly in place of the typical `instantiateImageCodec` method. Future _loadAsync( NetworkImage key, - image_provider.DecoderBufferCallback? decode, - image_provider.DecoderCallback? decodeDepreacted, + image_provider.ImageDecoderCallback? decode, + image_provider.DecoderBufferCallback? decodeBufferDeprecated, + image_provider.DecoderCallback? decodeDeprecated, StreamController chunkEvents, ) async { assert(key == this); @@ -165,9 +182,12 @@ class NetworkImage if (decode != null) { final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); return decode(buffer); + } else if (decodeBufferDeprecated != null) { + final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); + return decodeBufferDeprecated(buffer); } else { - assert(decodeDepreacted != null); - return decodeDepreacted!(bytes); + assert(decodeDeprecated != null); + return decodeDeprecated!(bytes); } } else { // This API only exists in the web engine implementation and is not diff --git a/packages/flutter/lib/src/painting/binding.dart b/packages/flutter/lib/src/painting/binding.dart index acb5a2416c..fd54b3cd26 100644 --- a/packages/flutter/lib/src/painting/binding.dart +++ b/packages/flutter/lib/src/painting/binding.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:ui' as ui show Codec, ImmutableBuffer, instantiateImageCodec, instantiateImageCodecFromBuffer; +import 'dart:ui' as ui; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart' show ServicesBinding; @@ -100,7 +100,7 @@ mixin PaintingBinding on BindingBase, ServicesBinding { /// above its native resolution should prefer scaling the canvas the image is /// drawn into. @Deprecated( - 'Use instantiateImageCodecFromBuffer with an ImmutableBuffer instance instead. ' + 'Use instantiateImageCodecWithSize with an ImmutableBuffer instance instead. ' 'This feature was deprecated after v2.13.0-1.0.pre.', ) Future instantiateImageCodec( @@ -140,6 +140,10 @@ mixin PaintingBinding on BindingBase, ServicesBinding { /// unnecessary memory usage for images. Callers that wish to display an image /// above its native resolution should prefer scaling the canvas the image is /// drawn into. + @Deprecated( + 'Use instantiateImageCodecWithSize instead. ' + 'This feature was deprecated after v3.7.0-1.4.pre.', + ) Future instantiateImageCodecFromBuffer( ui.ImmutableBuffer buffer, { int? cacheWidth, @@ -156,6 +160,28 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ); } + /// Calls through to [dart:ui.instantiateImageCodecWithSize] from [ImageCache]. + /// + /// The [buffer] parameter should be an [ui.ImmutableBuffer] instance which can + /// be acquired from [ui.ImmutableBuffer.fromUint8List] or + /// [ui.ImmutableBuffer.fromAsset]. + /// + /// The [getTargetSize] parameter, when specified, will be invoked and passed + /// the image's intrinsic size to determine the size to decode the image to. + /// The width and the height of the size it returns must be positive values + /// greater than or equal to 1, or null. It is valid to return a [TargetImageSize] + /// that specifies only one of `width` and `height` with the other remaining + /// null, in which case the omitted dimension will be scaled to maintain the + /// aspect ratio of the original dimensions. When both are null or omitted, + /// the image will be decoded at its native resolution (as will be the case if + /// the [getTargetSize] parameter is omitted). + Future instantiateImageCodecWithSize( + ui.ImmutableBuffer buffer, { + ui.TargetImageSizeCallback? getTargetSize, + }) { + return ui.instantiateImageCodecWithSize(buffer, getTargetSize: getTargetSize); + } + @override void evict(String asset) { super.evict(asset); diff --git a/packages/flutter/lib/src/painting/image_provider.dart b/packages/flutter/lib/src/painting/image_provider.dart index ec165388d5..a989271599 100644 --- a/packages/flutter/lib/src/painting/image_provider.dart +++ b/packages/flutter/lib/src/painting/image_provider.dart @@ -4,7 +4,7 @@ import 'dart:async'; import 'dart:io'; -import 'dart:ui' as ui show Codec, ImmutableBuffer; +import 'dart:ui' as ui; import 'dart:ui' show Locale, Size, TextDirection; import 'package:flutter/foundation.dart'; @@ -175,12 +175,11 @@ class ImageConfiguration { /// * [ResizeImage], which uses this to override the `cacheWidth`, /// `cacheHeight`, and `allowUpscaling` parameters. @Deprecated( - 'Use DecoderBufferCallback with ImageProvider.loadBuffer instead. ' + 'Use ImageDecoderCallback with ImageProvider.loadImage instead. ' 'This feature was deprecated after v2.13.0-1.0.pre.', ) typedef DecoderCallback = Future Function(Uint8List buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling}); - /// Performs the decode process for use in [ImageProvider.loadBuffer]. /// /// This callback allows decoupling of the `cacheWidth`, `cacheHeight`, and @@ -191,8 +190,25 @@ typedef DecoderCallback = Future Function(Uint8List buffer, {int? cach /// /// * [ResizeImage], which uses this to override the `cacheWidth`, /// `cacheHeight`, and `allowUpscaling` parameters. +@Deprecated( + 'Use ImageDecoderCallback with ImageProvider.loadImage instead. ' + 'This feature was deprecated after v3.7.0-1.4.pre.', +) typedef DecoderBufferCallback = Future Function(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling}); +/// Performs the decode process for use in [ImageProvider.loadImage]. +/// +/// This callback allows decoupling of the `getTargetSize` parameter from +/// implementations of [ImageProvider] that do not expose it. +/// +/// See also: +/// +/// * [ResizeImage], which uses this to load images at specific sizes. +typedef ImageDecoderCallback = Future Function( + ui.ImmutableBuffer buffer, { + ui.TargetImageSizeCallback? getTargetSize, +}); + /// Identifies an image without committing to the precise final asset. This /// allows a set of images to be identified and for the precise image to later /// be resolved based on the environment, e.g. the device pixel ratio. @@ -453,9 +469,9 @@ abstract class ImageProvider { return; } if (!didError) { + didError = true; errorCallback(obtainedKey, exception, stack); } - didError = true; } Future key; @@ -508,7 +524,22 @@ abstract class ImageProvider { } final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent( key, - () => loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer), + () { + ImageStreamCompleter result = loadImage(key, PaintingBinding.instance.instantiateImageCodecWithSize); + // This check exists as a fallback for backwards compatibility until the + // deprecated `loadBuffer()` method is removed. Until then, ImageProvider + // subclasses may have only overridden `loadBuffer()`, in which case the + // base implementation of `loadWithSize()` will return a sentinel value + // of type `_AbstractImageStreamCompleter`. + if (result is _AbstractImageStreamCompleter) { + result = loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer); + if (result is _AbstractImageStreamCompleter) { + // Same fallback as above but for the deprecated `load()` method. + result = load(key, PaintingBinding.instance.instantiateImageCodec); + } + } + return result; + }, onError: handleError, ); if (completer != null) { @@ -592,7 +623,7 @@ abstract class ImageProvider { /// * [ResizeImage], for modifying the key to account for cache dimensions. @protected @Deprecated( - 'Implement loadBuffer for faster image loading. ' + 'Implement loadImage for faster image loading. ' 'This feature was deprecated after v2.13.0-1.0.pre.', ) ImageStreamCompleter load(T key, DecoderCallback decode) { @@ -602,9 +633,10 @@ abstract class ImageProvider { /// Converts a key into an [ImageStreamCompleter], and begins fetching the /// image. /// - /// For backwards-compatibility the default implementation of this method calls - /// through to [ImageProvider.load]. However, implementors of this interface should - /// only override this method and not [ImageProvider.load], which is deprecated. + /// For backwards-compatibility the default implementation of this method returns + /// an object that will cause [resolveStreamForKey] to consult [load]. However, + /// implementors of this interface should only override this method and not + /// [load], which is deprecated. /// /// The [decode] callback provides the logic to obtain the codec for the /// image. @@ -613,14 +645,42 @@ abstract class ImageProvider { /// /// * [ResizeImage], for modifying the key to account for cache dimensions. @protected + @Deprecated( + 'Implement loadImage for image loading. ' + 'This feature was deprecated after v3.7.0-1.4.pre.', + ) ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) { - return load(key, PaintingBinding.instance.instantiateImageCodec); + return _AbstractImageStreamCompleter(); + } + + /// Converts a key into an [ImageStreamCompleter], and begins fetching the + /// image. + /// + /// For backwards-compatibility the default implementation of this method returns + /// an object that will cause [resolveStreamForKey] to consult [loadBuffer]. + /// However, implementors of this interface should only override this method + /// and not [loadBuffer], which is deprecated. + /// + /// The [decode] callback provides the logic to obtain the codec for the + /// image. + /// + /// See also: + /// + /// * [ResizeImage], for modifying the key to account for cache dimensions. + // TODO(tvolkert): make abstract (https://github.com/flutter/flutter/issues/119209) + @protected + ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) { + return _AbstractImageStreamCompleter(); } @override String toString() => '${objectRuntimeType(this, 'ImageConfiguration')}()'; } +/// A class that exists to facilitate backwards compatibility in the transition +/// from [ImageProvider.load] to [ImageProvider.loadBuffer] to [ImageProvider.loadImage] +class _AbstractImageStreamCompleter extends ImageStreamCompleter {} + /// Key for the image obtained by an [AssetImage] or [ExactAssetImage]. /// /// This is used to identify the precise resource in the [imageCache]. @@ -675,6 +735,24 @@ abstract class AssetBundleImageProvider extends ImageProvider [ + DiagnosticsProperty('Image provider', this), + DiagnosticsProperty('Image key', key), + ]; + return true; + }()); + return MultiFrameImageStreamCompleter( + codec: _loadAsync(key, decode: decode), + scale: key.scale, + debugLabel: key.name, + informationCollector: collector, + ); + } + /// Converts a key into an [ImageStreamCompleter], and begins fetching the /// image. @override @@ -688,7 +766,7 @@ abstract class AssetBundleImageProvider extends ImageProvider _loadAsync(AssetBundleImageKey key, DecoderBufferCallback? decode, DecoderCallback? decodeDepreacted) async { + Future _loadAsync( + AssetBundleImageKey key, { + ImageDecoderCallback? decode, + DecoderBufferCallback? decodeBufferDeprecated, + DecoderCallback? decodeDeprecated, + }) async { if (decode != null) { - ui.ImmutableBuffer? buffer; + ui.ImmutableBuffer buffer; // Hot reload/restart could change whether an asset bundle or key in a // bundle are available, or if it is a network backed bundle. try { @@ -731,6 +814,18 @@ abstract class AssetBundleImageProvider extends ImageProvider { ); return decode(buffer, cacheWidth: width, cacheHeight: height, allowUpscaling: this.allowUpscaling); } + final ImageStreamCompleter completer = imageProvider.loadBuffer(key._providerCacheKey, decodeResize); if (!kReleaseMode) { completer.debugLabel = '${completer.debugLabel} - Resized(${key._width}×${key._height})'; @@ -857,6 +953,36 @@ class ResizeImage extends ImageProvider { return completer; } + @override + ImageStreamCompleter loadImage(ResizeImageKey key, ImageDecoderCallback decode) { + Future decodeResize(ui.ImmutableBuffer buffer, {ui.TargetImageSizeCallback? getTargetSize}) { + assert( + getTargetSize == null, + 'ResizeImage cannot be composed with another ImageProvider that applies ' + '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; + } + } + return ui.TargetImageSize(width: targetWidth, height: targetHeight); + }); + } + + final ImageStreamCompleter completer = imageProvider.loadImage(key._providerCacheKey, decodeResize); + if (!kReleaseMode) { + completer.debugLabel = '${completer.debugLabel} - Resized(${key._width}×${key._height})'; + } + return completer; + } + @override Future obtainKey(ImageConfiguration configuration) { Completer? completer; @@ -921,6 +1047,9 @@ abstract class NetworkImage extends ImageProvider { @override ImageStreamCompleter loadBuffer(NetworkImage key, DecoderBufferCallback decode); + + @override + ImageStreamCompleter loadImage(NetworkImage key, ImageDecoderCallback decode); } /// Decodes the given [File] object as an image, associating it with the given @@ -953,7 +1082,7 @@ class FileImage extends ImageProvider { @override ImageStreamCompleter load(FileImage key, DecoderCallback decode) { return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, null, decode), + codec: _loadAsync(key, decodeDeprecated: decode), scale: key.scale, debugLabel: key.file.path, informationCollector: () => [ @@ -965,7 +1094,7 @@ class FileImage extends ImageProvider { @override ImageStreamCompleter loadBuffer(FileImage key, DecoderBufferCallback decode) { return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, decode, null), + codec: _loadAsync(key, decodeBufferDeprecated: decode), scale: key.scale, debugLabel: key.file.path, informationCollector: () => [ @@ -974,7 +1103,25 @@ class FileImage extends ImageProvider { ); } - Future _loadAsync(FileImage key, DecoderBufferCallback? decode, DecoderCallback? decodeDeprecated) async { + @override + @protected + ImageStreamCompleter loadImage(FileImage key, ImageDecoderCallback decode) { + return MultiFrameImageStreamCompleter( + codec: _loadAsync(key, decode: decode), + scale: key.scale, + debugLabel: key.file.path, + informationCollector: () => [ + ErrorDescription('Path: ${file.path}'), + ], + ); + } + + Future _loadAsync( + FileImage key, { + ImageDecoderCallback? decode, + DecoderBufferCallback? decodeBufferDeprecated, + DecoderCallback? decodeDeprecated, + }) async { assert(key == this); // TODO(jonahwilliams): making this sync caused test failures that seem to @@ -993,6 +1140,12 @@ class FileImage extends ImageProvider { } return decode(await ui.ImmutableBuffer.fromUint8List(await file.readAsBytes())); } + if (decodeBufferDeprecated != null) { + if (file.runtimeType == File) { + return decodeBufferDeprecated(await ui.ImmutableBuffer.fromFilePath(file.path)); + } + return decodeBufferDeprecated(await ui.ImmutableBuffer.fromUint8List(await file.readAsBytes())); + } return decodeDeprecated!(await file.readAsBytes()); } @@ -1058,7 +1211,7 @@ class MemoryImage extends ImageProvider { @override ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) { return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, null, decode), + codec: _loadAsync(key, decodeDeprecated: decode), scale: key.scale, debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})', ); @@ -1067,19 +1220,37 @@ class MemoryImage extends ImageProvider { @override ImageStreamCompleter loadBuffer(MemoryImage key, DecoderBufferCallback decode) { return MultiFrameImageStreamCompleter( - codec: _loadAsync(key, decode, null), + codec: _loadAsync(key, decodeBufferDeprecated: decode), scale: key.scale, debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})', ); } - Future _loadAsync(MemoryImage key, DecoderBufferCallback? decode, DecoderCallback? decodeDepreacted) async { + @override + ImageStreamCompleter loadImage(MemoryImage key, ImageDecoderCallback decode) { + return MultiFrameImageStreamCompleter( + codec: _loadAsync(key, decode: decode), + scale: key.scale, + debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})', + ); + } + + Future _loadAsync( + MemoryImage key, { + ImageDecoderCallback? decode, + DecoderBufferCallback? decodeBufferDeprecated, + DecoderCallback? decodeDeprecated, + }) async { assert(key == this); if (decode != null) { final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); return decode(buffer); } - return decodeDepreacted!(bytes); + if (decodeBufferDeprecated != null) { + final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); + return decodeBufferDeprecated(buffer); + } + return decodeDeprecated!(bytes); } @override diff --git a/packages/flutter/lib/src/widgets/scroll_aware_image_provider.dart b/packages/flutter/lib/src/widgets/scroll_aware_image_provider.dart index 7ea73ac7a8..4009be91ed 100644 --- a/packages/flutter/lib/src/widgets/scroll_aware_image_provider.dart +++ b/packages/flutter/lib/src/widgets/scroll_aware_image_provider.dart @@ -111,6 +111,9 @@ class ScrollAwareImageProvider extends ImageProvider { @override ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode); + @override + ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) => imageProvider.loadImage(key, decode); + @override Future obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration); } diff --git a/packages/flutter/test/painting/image_test_utils.dart b/packages/flutter/test/painting/image_test_utils.dart index 5894efe98d..847984663e 100644 --- a/packages/flutter/test/painting/image_test_utils.dart +++ b/packages/flutter/test/painting/image_test_utils.dart @@ -30,11 +30,16 @@ class TestImageProvider extends ImageProvider { @override ImageStreamCompleter load(TestImageProvider key, DecoderCallback decode) { - throw UnsupportedError('Use ImageProvider.loadBuffer instead.'); + throw UnsupportedError('Use ImageProvider.loadImage instead.'); } @override ImageStreamCompleter loadBuffer(TestImageProvider key, DecoderBufferCallback decode) { + throw UnsupportedError('Use ImageProvider.loadImage instead.'); + } + + @override + ImageStreamCompleter loadImage(TestImageProvider key, ImageDecoderCallback decode) { loadCallCount += 1; return OneFrameImageStreamCompleter(_completer.future); } diff --git a/packages/flutter/test/painting/mocks_for_image_cache.dart b/packages/flutter/test/painting/mocks_for_image_cache.dart index 77b3242799..1273faa4c5 100644 --- a/packages/flutter/test/painting/mocks_for_image_cache.dart +++ b/packages/flutter/test/painting/mocks_for_image_cache.dart @@ -85,6 +85,11 @@ Future extractOneFrame(ImageStream stream) { } class ErrorImageProvider extends ImageProvider { + @override + ImageStreamCompleter loadImage(ErrorImageProvider key, ImageDecoderCallback decode) { + throw Error(); + } + @override ImageStreamCompleter loadBuffer(ErrorImageProvider key, DecoderBufferCallback decode) { throw Error(); @@ -103,10 +108,15 @@ class ErrorImageProvider extends ImageProvider { class ObtainKeyErrorImageProvider extends ImageProvider { @override - ImageStreamCompleter loadBuffer(ObtainKeyErrorImageProvider key, DecoderBufferCallback decode) { + ImageStreamCompleter loadImage(ObtainKeyErrorImageProvider key, ImageDecoderCallback decode) { throw Error(); } + @override + ImageStreamCompleter loadBuffer(ObtainKeyErrorImageProvider key, DecoderBufferCallback decode) { + throw UnimplementedError(); + } + @override Future obtainKey(ImageConfiguration configuration) { throw Error(); @@ -120,10 +130,15 @@ class ObtainKeyErrorImageProvider extends ImageProvider { @override - ImageStreamCompleter loadBuffer(LoadErrorImageProvider key, DecoderBufferCallback decode) { + ImageStreamCompleter loadImage(LoadErrorImageProvider key, ImageDecoderCallback decode) { throw Error(); } + @override + ImageStreamCompleter loadBuffer(LoadErrorImageProvider key, DecoderBufferCallback decode) { + throw UnimplementedError(); + } + @override Future obtainKey(ImageConfiguration configuration) { return SynchronousFuture(this); diff --git a/packages/flutter/test/widgets/image_resolution_test.dart b/packages/flutter/test/widgets/image_resolution_test.dart index 3bc24b1cd3..fe836c1ed4 100644 --- a/packages/flutter/test/widgets/image_resolution_test.dart +++ b/packages/flutter/test/widgets/image_resolution_test.dart @@ -85,7 +85,7 @@ class TestAssetImage extends AssetImage { final Map images; @override - ImageStreamCompleter loadBuffer(AssetBundleImageKey key, DecoderBufferCallback decode) { + ImageStreamCompleter loadImage(AssetBundleImageKey key, ImageDecoderCallback decode) { late ImageInfo imageInfo; key.bundle.load(key.name).then((ByteData data) { final ui.Image image = images[scaleOf(data)]!; diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart index 52addf0d38..232a45bd71 100644 --- a/packages/flutter/test/widgets/image_test.dart +++ b/packages/flutter/test/widgets/image_test.dart @@ -2173,7 +2173,7 @@ class _DebouncingImageProvider extends ImageProvider { Future obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration); @override - ImageStreamCompleter loadBuffer(Object key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode); + ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) => imageProvider.loadImage(key, decode); } class _FailingImageProvider extends ImageProvider { diff --git a/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart b/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart index a0e2be0bbb..51fa9d85ef 100644 --- a/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart +++ b/packages/flutter/test/widgets/scroll_aware_image_provider_test.dart @@ -322,7 +322,7 @@ void main() { // If we miss the early return, we will fail. testImageProvider.complete(); - imageCache.putIfAbsent(testImageProvider, () => testImageProvider.loadBuffer(testImageProvider, PaintingBinding.instance.instantiateImageCodecFromBuffer)); + imageCache.putIfAbsent(testImageProvider, () => testImageProvider.loadImage(testImageProvider, PaintingBinding.instance.instantiateImageCodecWithSize)); // We've stopped scrolling fast. physics.recommendDeferredLoadingValue = false; await tester.idle(); @@ -377,7 +377,7 @@ void main() { // Complete the original image while we're still scrolling fast. testImageProvider.complete(); - stream.setCompleter(testImageProvider.loadBuffer(testImageProvider, PaintingBinding.instance.instantiateImageCodecFromBuffer)); + stream.setCompleter(testImageProvider.loadImage(testImageProvider, PaintingBinding.instance.instantiateImageCodecWithSize)); // Verify that this hasn't changed the cache state yet expect(imageCache.containsKey(testImageProvider), false);