Unverified Commit b319938e authored by Todd Volkert's avatar Todd Volkert Committed by GitHub

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
parent 202e9027
...@@ -58,7 +58,7 @@ class DelayedBase64Image extends ImageProvider<int> { ...@@ -58,7 +58,7 @@ class DelayedBase64Image extends ImageProvider<int> {
} }
@override @override
ImageStreamCompleter loadBuffer(int key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(int key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: Future<ui.Codec>.delayed( codec: Future<ui.Codec>.delayed(
delay, delay,
......
...@@ -9,15 +9,15 @@ import 'package:flutter/material.dart'; ...@@ -9,15 +9,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
// This class allows loadBuffer, a protected method, to be called with a custom // This class allows loadImage, a protected method, to be called with a custom
// DecoderBufferCallback function. // ImageDecoderCallback function.
class LoadTestImageProvider extends ImageProvider<Object> { class LoadTestImageProvider extends ImageProvider<Object> {
LoadTestImageProvider(this.provider); LoadTestImageProvider(this.provider);
final ImageProvider provider; final ImageProvider provider;
ImageStreamCompleter testLoad(Object key, DecoderBufferCallback decode) { ImageStreamCompleter testLoad(Object key, ImageDecoderCallback decode) {
return provider.loadBuffer(key, decode); return provider.loadImage(key, decode);
} }
@override @override
...@@ -26,7 +26,7 @@ class LoadTestImageProvider extends ImageProvider<Object> { ...@@ -26,7 +26,7 @@ class LoadTestImageProvider extends ImageProvider<Object> {
} }
@override @override
ImageStreamCompleter loadBuffer(Object key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) {
throw UnimplementedError(); throw UnimplementedError();
} }
} }
...@@ -47,12 +47,15 @@ void main() { ...@@ -47,12 +47,15 @@ void main() {
bool called = false; bool called = false;
Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) { Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {ui.TargetImageSizeCallback? getTargetSize}) {
expect(cacheHeight, expectedCacheHeight); return PaintingBinding.instance.instantiateImageCodecWithSize(buffer, getTargetSize: (int intrinsicWidth, int intrinsicHeight) {
expect(cacheWidth, expectedCacheWidth); expect(getTargetSize, isNotNull);
expect(allowUpscaling, false); final ui.TargetImageSize targetSize = getTargetSize!(intrinsicWidth, intrinsicHeight);
called = true; expect(targetSize.width, expectedCacheWidth);
return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling); expect(targetSize.height, expectedCacheHeight);
called = true;
return targetSize;
});
} }
final ImageProvider resizeImage = image.image; final ImageProvider resizeImage = image.image;
......
...@@ -43,7 +43,7 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -43,7 +43,7 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>(); final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as NetworkImage, chunkEvents, null, decode), codec: _loadAsync(key as NetworkImage, chunkEvents, decodeDeprecated: decode),
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
...@@ -62,7 +62,26 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -62,7 +62,26 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>(); final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter( 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: () => <DiagnosticsNode>[
DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
DiagnosticsProperty<image_provider.NetworkImage>('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<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as NetworkImage, chunkEvents, decode: decode),
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
...@@ -92,10 +111,11 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -92,10 +111,11 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
Future<ui.Codec> _loadAsync( Future<ui.Codec> _loadAsync(
NetworkImage key, NetworkImage key,
StreamController<ImageChunkEvent> chunkEvents, StreamController<ImageChunkEvent> chunkEvents, {
image_provider.DecoderBufferCallback? decode, image_provider.ImageDecoderCallback? decode,
image_provider.DecoderCallback? decodeDepreacted, image_provider.DecoderBufferCallback? decodeBufferDeprecated,
) async { image_provider.DecoderCallback? decodeDeprecated,
}) async {
try { try {
assert(key == this); assert(key == this);
...@@ -131,9 +151,12 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -131,9 +151,12 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
if (decode != null) { if (decode != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer); return decode(buffer);
} else if (decodeBufferDeprecated != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decodeBufferDeprecated(buffer);
} else { } else {
assert(decodeDepreacted != null); assert(decodeDeprecated != null);
return decodeDepreacted!(bytes); return decodeDeprecated!(bytes);
} }
} catch (e) { } catch (e) {
// Depending on where the exception was thrown, the image cache may not // Depending on where the exception was thrown, the image cache may not
......
...@@ -65,7 +65,7 @@ class NetworkImage ...@@ -65,7 +65,7 @@ class NetworkImage
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
codec: _loadAsync(key as NetworkImage, null, decode, chunkEvents), codec: _loadAsync(key as NetworkImage, null, null, decode, chunkEvents),
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(key), informationCollector: _imageStreamInformationCollector(key),
...@@ -82,7 +82,23 @@ class NetworkImage ...@@ -82,7 +82,23 @@ class NetworkImage
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
codec: _loadAsync(key as NetworkImage, decode, null, chunkEvents), codec: _loadAsync(key as NetworkImage, null, decode, null, chunkEvents),
scale: key.scale,
debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(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<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
chunkEvents: chunkEvents.stream,
codec: _loadAsync(key as NetworkImage, decode, null, null, chunkEvents),
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(key), informationCollector: _imageStreamInformationCollector(key),
...@@ -106,8 +122,9 @@ class NetworkImage ...@@ -106,8 +122,9 @@ class NetworkImage
// directly in place of the typical `instantiateImageCodec` method. // directly in place of the typical `instantiateImageCodec` method.
Future<ui.Codec> _loadAsync( Future<ui.Codec> _loadAsync(
NetworkImage key, NetworkImage key,
image_provider.DecoderBufferCallback? decode, image_provider.ImageDecoderCallback? decode,
image_provider.DecoderCallback? decodeDepreacted, image_provider.DecoderBufferCallback? decodeBufferDeprecated,
image_provider.DecoderCallback? decodeDeprecated,
StreamController<ImageChunkEvent> chunkEvents, StreamController<ImageChunkEvent> chunkEvents,
) async { ) async {
assert(key == this); assert(key == this);
...@@ -165,9 +182,12 @@ class NetworkImage ...@@ -165,9 +182,12 @@ class NetworkImage
if (decode != null) { if (decode != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer); return decode(buffer);
} else if (decodeBufferDeprecated != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decodeBufferDeprecated(buffer);
} else { } else {
assert(decodeDepreacted != null); assert(decodeDeprecated != null);
return decodeDepreacted!(bytes); return decodeDeprecated!(bytes);
} }
} else { } else {
// This API only exists in the web engine implementation and is not // This API only exists in the web engine implementation and is not
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // 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/foundation.dart';
import 'package:flutter/services.dart' show ServicesBinding; import 'package:flutter/services.dart' show ServicesBinding;
...@@ -100,7 +100,7 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -100,7 +100,7 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
/// above its native resolution should prefer scaling the canvas the image is /// above its native resolution should prefer scaling the canvas the image is
/// drawn into. /// drawn into.
@Deprecated( @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.', 'This feature was deprecated after v2.13.0-1.0.pre.',
) )
Future<ui.Codec> instantiateImageCodec( Future<ui.Codec> instantiateImageCodec(
...@@ -140,6 +140,10 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -140,6 +140,10 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
/// unnecessary memory usage for images. Callers that wish to display an image /// unnecessary memory usage for images. Callers that wish to display an image
/// above its native resolution should prefer scaling the canvas the image is /// above its native resolution should prefer scaling the canvas the image is
/// drawn into. /// drawn into.
@Deprecated(
'Use instantiateImageCodecWithSize instead. '
'This feature was deprecated after v3.7.0-1.4.pre.',
)
Future<ui.Codec> instantiateImageCodecFromBuffer( Future<ui.Codec> instantiateImageCodecFromBuffer(
ui.ImmutableBuffer buffer, { ui.ImmutableBuffer buffer, {
int? cacheWidth, int? cacheWidth,
...@@ -156,6 +160,28 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -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<ui.Codec> instantiateImageCodecWithSize(
ui.ImmutableBuffer buffer, {
ui.TargetImageSizeCallback? getTargetSize,
}) {
return ui.instantiateImageCodecWithSize(buffer, getTargetSize: getTargetSize);
}
@override @override
void evict(String asset) { void evict(String asset) {
super.evict(asset); super.evict(asset);
......
...@@ -4,7 +4,7 @@ ...@@ -4,7 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:ui' as ui show Codec, ImmutableBuffer; import 'dart:ui' as ui;
import 'dart:ui' show Locale, Size, TextDirection; import 'dart:ui' show Locale, Size, TextDirection;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -175,12 +175,11 @@ class ImageConfiguration { ...@@ -175,12 +175,11 @@ class ImageConfiguration {
/// * [ResizeImage], which uses this to override the `cacheWidth`, /// * [ResizeImage], which uses this to override the `cacheWidth`,
/// `cacheHeight`, and `allowUpscaling` parameters. /// `cacheHeight`, and `allowUpscaling` parameters.
@Deprecated( @Deprecated(
'Use DecoderBufferCallback with ImageProvider.loadBuffer instead. ' 'Use ImageDecoderCallback with ImageProvider.loadImage instead. '
'This feature was deprecated after v2.13.0-1.0.pre.', 'This feature was deprecated after v2.13.0-1.0.pre.',
) )
typedef DecoderCallback = Future<ui.Codec> Function(Uint8List buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling}); typedef DecoderCallback = Future<ui.Codec> Function(Uint8List buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
/// Performs the decode process for use in [ImageProvider.loadBuffer]. /// Performs the decode process for use in [ImageProvider.loadBuffer].
/// ///
/// This callback allows decoupling of the `cacheWidth`, `cacheHeight`, and /// This callback allows decoupling of the `cacheWidth`, `cacheHeight`, and
...@@ -191,8 +190,25 @@ typedef DecoderCallback = Future<ui.Codec> Function(Uint8List buffer, {int? cach ...@@ -191,8 +190,25 @@ typedef DecoderCallback = Future<ui.Codec> Function(Uint8List buffer, {int? cach
/// ///
/// * [ResizeImage], which uses this to override the `cacheWidth`, /// * [ResizeImage], which uses this to override the `cacheWidth`,
/// `cacheHeight`, and `allowUpscaling` parameters. /// `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<ui.Codec> Function(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling}); typedef DecoderBufferCallback = Future<ui.Codec> 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<ui.Codec> Function(
ui.ImmutableBuffer buffer, {
ui.TargetImageSizeCallback? getTargetSize,
});
/// Identifies an image without committing to the precise final asset. This /// 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 /// 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. /// be resolved based on the environment, e.g. the device pixel ratio.
...@@ -453,9 +469,9 @@ abstract class ImageProvider<T extends Object> { ...@@ -453,9 +469,9 @@ abstract class ImageProvider<T extends Object> {
return; return;
} }
if (!didError) { if (!didError) {
didError = true;
errorCallback(obtainedKey, exception, stack); errorCallback(obtainedKey, exception, stack);
} }
didError = true;
} }
Future<T> key; Future<T> key;
...@@ -508,7 +524,22 @@ abstract class ImageProvider<T extends Object> { ...@@ -508,7 +524,22 @@ abstract class ImageProvider<T extends Object> {
} }
final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent( final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
key, 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, onError: handleError,
); );
if (completer != null) { if (completer != null) {
...@@ -592,7 +623,7 @@ abstract class ImageProvider<T extends Object> { ...@@ -592,7 +623,7 @@ abstract class ImageProvider<T extends Object> {
/// * [ResizeImage], for modifying the key to account for cache dimensions. /// * [ResizeImage], for modifying the key to account for cache dimensions.
@protected @protected
@Deprecated( @Deprecated(
'Implement loadBuffer for faster image loading. ' 'Implement loadImage for faster image loading. '
'This feature was deprecated after v2.13.0-1.0.pre.', 'This feature was deprecated after v2.13.0-1.0.pre.',
) )
ImageStreamCompleter load(T key, DecoderCallback decode) { ImageStreamCompleter load(T key, DecoderCallback decode) {
...@@ -602,9 +633,10 @@ abstract class ImageProvider<T extends Object> { ...@@ -602,9 +633,10 @@ abstract class ImageProvider<T extends Object> {
/// Converts a key into an [ImageStreamCompleter], and begins fetching the /// Converts a key into an [ImageStreamCompleter], and begins fetching the
/// image. /// image.
/// ///
/// For backwards-compatibility the default implementation of this method calls /// For backwards-compatibility the default implementation of this method returns
/// through to [ImageProvider.load]. However, implementors of this interface should /// an object that will cause [resolveStreamForKey] to consult [load]. However,
/// only override this method and not [ImageProvider.load], which is deprecated. /// 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 /// The [decode] callback provides the logic to obtain the codec for the
/// image. /// image.
...@@ -613,14 +645,42 @@ abstract class ImageProvider<T extends Object> { ...@@ -613,14 +645,42 @@ abstract class ImageProvider<T extends Object> {
/// ///
/// * [ResizeImage], for modifying the key to account for cache dimensions. /// * [ResizeImage], for modifying the key to account for cache dimensions.
@protected @protected
@Deprecated(
'Implement loadImage for image loading. '
'This feature was deprecated after v3.7.0-1.4.pre.',
)
ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) { 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 @override
String toString() => '${objectRuntimeType(this, 'ImageConfiguration')}()'; 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]. /// Key for the image obtained by an [AssetImage] or [ExactAssetImage].
/// ///
/// This is used to identify the precise resource in the [imageCache]. /// This is used to identify the precise resource in the [imageCache].
...@@ -675,6 +735,24 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -675,6 +735,24 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
/// const constructors so that they can be used in const expressions. /// const constructors so that they can be used in const expressions.
const AssetBundleImageProvider(); const AssetBundleImageProvider();
@override
ImageStreamCompleter loadImage(AssetBundleImageKey key, ImageDecoderCallback decode) {
InformationCollector? collector;
assert(() {
collector = () => <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>('Image provider', this),
DiagnosticsProperty<AssetBundleImageKey>('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 /// Converts a key into an [ImageStreamCompleter], and begins fetching the
/// image. /// image.
@override @override
...@@ -688,7 +766,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -688,7 +766,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
return true; return true;
}()); }());
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode, null), codec: _loadAsync(key, decodeBufferDeprecated: decode),
scale: key.scale, scale: key.scale,
debugLabel: key.name, debugLabel: key.name,
informationCollector: collector, informationCollector: collector,
...@@ -706,7 +784,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -706,7 +784,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
return true; return true;
}()); }());
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, null, decode), codec: _loadAsync(key, decodeDeprecated: decode),
scale: key.scale, scale: key.scale,
debugLabel: key.name, debugLabel: key.name,
informationCollector: collector, informationCollector: collector,
...@@ -718,9 +796,14 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -718,9 +796,14 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
/// ///
/// This function is used by [load]. /// This function is used by [load].
@protected @protected
Future<ui.Codec> _loadAsync(AssetBundleImageKey key, DecoderBufferCallback? decode, DecoderCallback? decodeDepreacted) async { Future<ui.Codec> _loadAsync(
AssetBundleImageKey key, {
ImageDecoderCallback? decode,
DecoderBufferCallback? decodeBufferDeprecated,
DecoderCallback? decodeDeprecated,
}) async {
if (decode != null) { if (decode != null) {
ui.ImmutableBuffer? buffer; ui.ImmutableBuffer buffer;
// Hot reload/restart could change whether an asset bundle or key in a // Hot reload/restart could change whether an asset bundle or key in a
// bundle are available, or if it is a network backed bundle. // bundle are available, or if it is a network backed bundle.
try { try {
...@@ -731,6 +814,18 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -731,6 +814,18 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
} }
return decode(buffer); return decode(buffer);
} }
if (decodeBufferDeprecated != null) {
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 {
buffer = await key.bundle.loadBuffer(key.name);
} on FlutterError {
PaintingBinding.instance.imageCache.evict(key);
rethrow;
}
return decodeBufferDeprecated(buffer);
}
ByteData data; ByteData data;
// Hot reload/restart could change whether an asset bundle or key in a // Hot reload/restart could change whether an asset bundle or key in a
// bundle are available, or if it is a network backed bundle. // bundle are available, or if it is a network backed bundle.
...@@ -740,7 +835,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -740,7 +835,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
PaintingBinding.instance.imageCache.evict(key); PaintingBinding.instance.imageCache.evict(key);
rethrow; rethrow;
} }
return decodeDepreacted!(data.buffer.asUint8List()); return decodeDeprecated!(data.buffer.asUint8List());
} }
} }
...@@ -850,6 +945,7 @@ class ResizeImage extends ImageProvider<ResizeImageKey> { ...@@ -850,6 +945,7 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
); );
return decode(buffer, cacheWidth: width, cacheHeight: height, allowUpscaling: this.allowUpscaling); return decode(buffer, cacheWidth: width, cacheHeight: height, allowUpscaling: this.allowUpscaling);
} }
final ImageStreamCompleter completer = imageProvider.loadBuffer(key._providerCacheKey, decodeResize); final ImageStreamCompleter completer = imageProvider.loadBuffer(key._providerCacheKey, decodeResize);
if (!kReleaseMode) { if (!kReleaseMode) {
completer.debugLabel = '${completer.debugLabel} - Resized(${key._width}×${key._height})'; completer.debugLabel = '${completer.debugLabel} - Resized(${key._width}×${key._height})';
...@@ -857,6 +953,36 @@ class ResizeImage extends ImageProvider<ResizeImageKey> { ...@@ -857,6 +953,36 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
return completer; return completer;
} }
@override
ImageStreamCompleter loadImage(ResizeImageKey key, ImageDecoderCallback decode) {
Future<ui.Codec> 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 @override
Future<ResizeImageKey> obtainKey(ImageConfiguration configuration) { Future<ResizeImageKey> obtainKey(ImageConfiguration configuration) {
Completer<ResizeImageKey>? completer; Completer<ResizeImageKey>? completer;
...@@ -921,6 +1047,9 @@ abstract class NetworkImage extends ImageProvider<NetworkImage> { ...@@ -921,6 +1047,9 @@ abstract class NetworkImage extends ImageProvider<NetworkImage> {
@override @override
ImageStreamCompleter loadBuffer(NetworkImage key, DecoderBufferCallback decode); 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 /// Decodes the given [File] object as an image, associating it with the given
...@@ -953,7 +1082,7 @@ class FileImage extends ImageProvider<FileImage> { ...@@ -953,7 +1082,7 @@ class FileImage extends ImageProvider<FileImage> {
@override @override
ImageStreamCompleter load(FileImage key, DecoderCallback decode) { ImageStreamCompleter load(FileImage key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, null, decode), codec: _loadAsync(key, decodeDeprecated: decode),
scale: key.scale, scale: key.scale,
debugLabel: key.file.path, debugLabel: key.file.path,
informationCollector: () => <DiagnosticsNode>[ informationCollector: () => <DiagnosticsNode>[
...@@ -965,7 +1094,20 @@ class FileImage extends ImageProvider<FileImage> { ...@@ -965,7 +1094,20 @@ class FileImage extends ImageProvider<FileImage> {
@override @override
ImageStreamCompleter loadBuffer(FileImage key, DecoderBufferCallback decode) { ImageStreamCompleter loadBuffer(FileImage key, DecoderBufferCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode, null), codec: _loadAsync(key, decodeBufferDeprecated: decode),
scale: key.scale,
debugLabel: key.file.path,
informationCollector: () => <DiagnosticsNode>[
ErrorDescription('Path: ${file.path}'),
],
);
}
@override
@protected
ImageStreamCompleter loadImage(FileImage key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode: decode),
scale: key.scale, scale: key.scale,
debugLabel: key.file.path, debugLabel: key.file.path,
informationCollector: () => <DiagnosticsNode>[ informationCollector: () => <DiagnosticsNode>[
...@@ -974,7 +1116,12 @@ class FileImage extends ImageProvider<FileImage> { ...@@ -974,7 +1116,12 @@ class FileImage extends ImageProvider<FileImage> {
); );
} }
Future<ui.Codec> _loadAsync(FileImage key, DecoderBufferCallback? decode, DecoderCallback? decodeDeprecated) async { Future<ui.Codec> _loadAsync(
FileImage key, {
ImageDecoderCallback? decode,
DecoderBufferCallback? decodeBufferDeprecated,
DecoderCallback? decodeDeprecated,
}) async {
assert(key == this); assert(key == this);
// TODO(jonahwilliams): making this sync caused test failures that seem to // TODO(jonahwilliams): making this sync caused test failures that seem to
...@@ -993,6 +1140,12 @@ class FileImage extends ImageProvider<FileImage> { ...@@ -993,6 +1140,12 @@ class FileImage extends ImageProvider<FileImage> {
} }
return decode(await ui.ImmutableBuffer.fromUint8List(await file.readAsBytes())); 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()); return decodeDeprecated!(await file.readAsBytes());
} }
...@@ -1058,7 +1211,7 @@ class MemoryImage extends ImageProvider<MemoryImage> { ...@@ -1058,7 +1211,7 @@ class MemoryImage extends ImageProvider<MemoryImage> {
@override @override
ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) { ImageStreamCompleter load(MemoryImage key, DecoderCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, null, decode), codec: _loadAsync(key, decodeDeprecated: decode),
scale: key.scale, scale: key.scale,
debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})', debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
); );
...@@ -1067,19 +1220,37 @@ class MemoryImage extends ImageProvider<MemoryImage> { ...@@ -1067,19 +1220,37 @@ class MemoryImage extends ImageProvider<MemoryImage> {
@override @override
ImageStreamCompleter loadBuffer(MemoryImage key, DecoderBufferCallback decode) { ImageStreamCompleter loadBuffer(MemoryImage key, DecoderBufferCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode, null), codec: _loadAsync(key, decodeBufferDeprecated: decode),
scale: key.scale, scale: key.scale,
debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})', debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
); );
} }
Future<ui.Codec> _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<ui.Codec> _loadAsync(
MemoryImage key, {
ImageDecoderCallback? decode,
DecoderBufferCallback? decodeBufferDeprecated,
DecoderCallback? decodeDeprecated,
}) async {
assert(key == this); assert(key == this);
if (decode != null) { if (decode != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer); 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 @override
......
...@@ -111,6 +111,9 @@ class ScrollAwareImageProvider<T extends Object> extends ImageProvider<T> { ...@@ -111,6 +111,9 @@ class ScrollAwareImageProvider<T extends Object> extends ImageProvider<T> {
@override @override
ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode); ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode);
@override
ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) => imageProvider.loadImage(key, decode);
@override @override
Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration); Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
} }
...@@ -30,11 +30,16 @@ class TestImageProvider extends ImageProvider<TestImageProvider> { ...@@ -30,11 +30,16 @@ class TestImageProvider extends ImageProvider<TestImageProvider> {
@override @override
ImageStreamCompleter load(TestImageProvider key, DecoderCallback decode) { ImageStreamCompleter load(TestImageProvider key, DecoderCallback decode) {
throw UnsupportedError('Use ImageProvider.loadBuffer instead.'); throw UnsupportedError('Use ImageProvider.loadImage instead.');
} }
@override @override
ImageStreamCompleter loadBuffer(TestImageProvider key, DecoderBufferCallback decode) { ImageStreamCompleter loadBuffer(TestImageProvider key, DecoderBufferCallback decode) {
throw UnsupportedError('Use ImageProvider.loadImage instead.');
}
@override
ImageStreamCompleter loadImage(TestImageProvider key, ImageDecoderCallback decode) {
loadCallCount += 1; loadCallCount += 1;
return OneFrameImageStreamCompleter(_completer.future); return OneFrameImageStreamCompleter(_completer.future);
} }
......
...@@ -85,6 +85,11 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) { ...@@ -85,6 +85,11 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) {
} }
class ErrorImageProvider extends ImageProvider<ErrorImageProvider> { class ErrorImageProvider extends ImageProvider<ErrorImageProvider> {
@override
ImageStreamCompleter loadImage(ErrorImageProvider key, ImageDecoderCallback decode) {
throw Error();
}
@override @override
ImageStreamCompleter loadBuffer(ErrorImageProvider key, DecoderBufferCallback decode) { ImageStreamCompleter loadBuffer(ErrorImageProvider key, DecoderBufferCallback decode) {
throw Error(); throw Error();
...@@ -103,10 +108,15 @@ class ErrorImageProvider extends ImageProvider<ErrorImageProvider> { ...@@ -103,10 +108,15 @@ class ErrorImageProvider extends ImageProvider<ErrorImageProvider> {
class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvider> { class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvider> {
@override @override
ImageStreamCompleter loadBuffer(ObtainKeyErrorImageProvider key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(ObtainKeyErrorImageProvider key, ImageDecoderCallback decode) {
throw Error(); throw Error();
} }
@override
ImageStreamCompleter loadBuffer(ObtainKeyErrorImageProvider key, DecoderBufferCallback decode) {
throw UnimplementedError();
}
@override @override
Future<ObtainKeyErrorImageProvider> obtainKey(ImageConfiguration configuration) { Future<ObtainKeyErrorImageProvider> obtainKey(ImageConfiguration configuration) {
throw Error(); throw Error();
...@@ -120,10 +130,15 @@ class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvi ...@@ -120,10 +130,15 @@ class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvi
class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> { class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> {
@override @override
ImageStreamCompleter loadBuffer(LoadErrorImageProvider key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(LoadErrorImageProvider key, ImageDecoderCallback decode) {
throw Error(); throw Error();
} }
@override
ImageStreamCompleter loadBuffer(LoadErrorImageProvider key, DecoderBufferCallback decode) {
throw UnimplementedError();
}
@override @override
Future<LoadErrorImageProvider> obtainKey(ImageConfiguration configuration) { Future<LoadErrorImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<LoadErrorImageProvider>(this); return SynchronousFuture<LoadErrorImageProvider>(this);
......
...@@ -85,7 +85,7 @@ class TestAssetImage extends AssetImage { ...@@ -85,7 +85,7 @@ class TestAssetImage extends AssetImage {
final Map<double, ui.Image> images; final Map<double, ui.Image> images;
@override @override
ImageStreamCompleter loadBuffer(AssetBundleImageKey key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(AssetBundleImageKey key, ImageDecoderCallback decode) {
late ImageInfo imageInfo; late ImageInfo imageInfo;
key.bundle.load(key.name).then<void>((ByteData data) { key.bundle.load(key.name).then<void>((ByteData data) {
final ui.Image image = images[scaleOf(data)]!; final ui.Image image = images[scaleOf(data)]!;
......
...@@ -2173,7 +2173,7 @@ class _DebouncingImageProvider extends ImageProvider<Object> { ...@@ -2173,7 +2173,7 @@ class _DebouncingImageProvider extends ImageProvider<Object> {
Future<Object> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration); Future<Object> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
@override @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<int> { class _FailingImageProvider extends ImageProvider<int> {
......
...@@ -322,7 +322,7 @@ void main() { ...@@ -322,7 +322,7 @@ void main() {
// If we miss the early return, we will fail. // If we miss the early return, we will fail.
testImageProvider.complete(); 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. // We've stopped scrolling fast.
physics.recommendDeferredLoadingValue = false; physics.recommendDeferredLoadingValue = false;
await tester.idle(); await tester.idle();
...@@ -377,7 +377,7 @@ void main() { ...@@ -377,7 +377,7 @@ void main() {
// Complete the original image while we're still scrolling fast. // Complete the original image while we're still scrolling fast.
testImageProvider.complete(); 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 // Verify that this hasn't changed the cache state yet
expect(imageCache.containsKey(testImageProvider), false); expect(imageCache.containsKey(testImageProvider), false);
......
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