Unverified Commit 8d2978af authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

use immutable buffer for loading asset images (#103496)

parent 5cb9c219
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:ui' as ui show Codec; import 'dart:ui' as ui show Codec;
import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
...@@ -57,11 +58,11 @@ class DelayedBase64Image extends ImageProvider<int> { ...@@ -57,11 +58,11 @@ class DelayedBase64Image extends ImageProvider<int> {
} }
@override @override
ImageStreamCompleter load(int key, DecoderCallback decode) { ImageStreamCompleter loadBuffer(int key, DecoderBufferCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: Future<ui.Codec>.delayed( codec: Future<ui.Codec>.delayed(
delay, delay,
() => decode(base64.decode(data)), () async => decode(await ImmutableBuffer.fromUint8List(base64.decode(data))),
), ),
scale: 1.0, scale: 1.0,
); );
......
...@@ -87,9 +87,7 @@ Future<void> main() async { ...@@ -87,9 +87,7 @@ Future<void> main() async {
for (int i = 0; i < 10; i += 1) { for (int i = 0; i < 10; i += 1) {
await Future.wait(<Future<ui.ImmutableBuffer>>[ await Future.wait(<Future<ui.ImmutableBuffer>>[
for (String asset in assets) for (String asset in assets)
rootBundle.load(asset).then((ByteData data) { rootBundle.loadBuffer(asset)
return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List());
})
]); ]);
} }
watch.stop(); watch.stop();
......
...@@ -46,7 +46,26 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -46,7 +46,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), codec: _loadAsync(key as NetworkImage, chunkEvents, null, 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 loadBuffer(image_provider.NetworkImage key, image_provider.DecoderBufferCallback 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, null),
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
...@@ -77,7 +96,8 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -77,7 +96,8 @@ 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.DecoderCallback decode, image_provider.DecoderBufferCallback? decode,
image_provider.DecoderCallback? decodeDepreacted,
) async { ) async {
try { try {
assert(key == this); assert(key == this);
...@@ -111,7 +131,13 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -111,7 +131,13 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
throw Exception('NetworkImage is an empty file: $resolved'); throw Exception('NetworkImage is an empty file: $resolved');
} }
return decode(bytes); if (decode != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
} else {
assert(decodeDepreacted != null);
return decodeDepreacted!(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
// have had a chance to track the key in the cache at all. // have had a chance to track the key in the cache at all.
......
...@@ -66,7 +66,24 @@ class NetworkImage ...@@ -66,7 +66,24 @@ class NetworkImage
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
codec: _loadAsync(key as NetworkImage, decode, chunkEvents), codec: _loadAsync(key as NetworkImage, null, decode, chunkEvents),
scale: key.scale,
debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(key),
);
}
@override
ImageStreamCompleter loadBuffer(image_provider.NetworkImage key, image_provider.DecoderBufferCallback 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, chunkEvents),
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(key), informationCollector: _imageStreamInformationCollector(key),
...@@ -93,7 +110,8 @@ class NetworkImage ...@@ -93,7 +110,8 @@ 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.DecoderCallback decode, image_provider.DecoderBufferCallback? decode,
image_provider.DecoderCallback? decodeDepreacted,
StreamController<ImageChunkEvent> chunkEvents, StreamController<ImageChunkEvent> chunkEvents,
) async { ) async {
assert(key == this); assert(key == this);
...@@ -144,7 +162,13 @@ class NetworkImage ...@@ -144,7 +162,13 @@ class NetworkImage
statusCode: request.status!, uri: resolved); statusCode: request.status!, uri: resolved);
} }
return decode(bytes); if (decode != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
} else {
assert(decodeDepreacted != null);
return decodeDepreacted!(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
// contained in the analyzer summary for Flutter. // contained in the analyzer summary for Flutter.
......
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:typed_data' show Uint8List; import 'dart:typed_data' show Uint8List;
import 'dart:ui' as ui show instantiateImageCodec, Codec; import 'dart:ui' as ui show instantiateImageCodec, instantiateImageCodecFromBuffer, Codec, ImmutableBuffer;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show ServicesBinding; import 'package:flutter/services.dart' show ServicesBinding;
...@@ -81,6 +81,9 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -81,6 +81,9 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
/// Calls through to [dart:ui.instantiateImageCodec] from [ImageCache]. /// Calls through to [dart:ui.instantiateImageCodec] from [ImageCache].
/// ///
/// This method is deprecated. use [instantiateImageCodecFromBuffer] with an
/// [ImmutableBuffer] instance instead of this method.
///
/// The `cacheWidth` and `cacheHeight` parameters, when specified, indicate /// The `cacheWidth` and `cacheHeight` parameters, when specified, indicate
/// the size to decode the image to. /// the size to decode the image to.
/// ///
...@@ -97,6 +100,10 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -97,6 +100,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 instantiateImageCodecFromBuffer with an ImmutableBuffer instance instead. '
'This feature was deprecated after v2.13.0-1.0.pre.',
)
Future<ui.Codec> instantiateImageCodec( Future<ui.Codec> instantiateImageCodec(
Uint8List bytes, { Uint8List bytes, {
int? cacheWidth, int? cacheWidth,
...@@ -114,6 +121,44 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -114,6 +121,44 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
); );
} }
/// Calls through to [dart:ui.instantiateImageCodecFromBuffer] from [ImageCache].
///
/// The [buffer] parameter should be an [ui.ImmutableBuffer] instance which can
/// be acquired from [ui.ImmutableBuffer.fromUint8List] or [ui.ImmutableBuffer.fromAsset].
///
/// The [cacheWidth] and [cacheHeight] parameters, when specified, indicate
/// the size to decode the image to.
///
/// Both [cacheWidth] and [cacheHeight] must be positive values greater than
/// or equal to 1, or null. It is valid to specify only one of `cacheWidth`
/// and [cacheHeight] 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.
///
/// The [allowUpscaling] parameter determines whether the `cacheWidth` or
/// [cacheHeight] parameters are clamped to the intrinsic width and height of
/// the original image. By default, the dimensions are clamped to avoid
/// 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.
Future<ui.Codec> instantiateImageCodecFromBuffer(
ui.ImmutableBuffer buffer, {
int? cacheWidth,
int? cacheHeight,
bool allowUpscaling = false,
}) {
assert(cacheWidth == null || cacheWidth > 0);
assert(cacheHeight == null || cacheHeight > 0);
assert(allowUpscaling != null);
return ui.instantiateImageCodecFromBuffer(
buffer,
targetWidth: cacheWidth,
targetHeight: cacheHeight,
allowUpscaling: allowUpscaling,
);
}
@override @override
void evict(String asset) { void evict(String asset) {
super.evict(asset); super.evict(asset);
......
...@@ -5,7 +5,7 @@ ...@@ -5,7 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui show Codec; import 'dart:ui' as ui show Codec, ImmutableBuffer;
import 'dart:ui' show Size, Locale, TextDirection; import 'dart:ui' show Size, Locale, TextDirection;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -164,6 +164,26 @@ class ImageConfiguration { ...@@ -164,6 +164,26 @@ class ImageConfiguration {
/// Performs the decode process for use in [ImageProvider.load]. /// Performs the decode process for use in [ImageProvider.load].
/// ///
/// This typedef is deprecated. Use [DecoderBufferCallback] with
/// [ImageProvider.loadBuffer] instead.
///
/// This callback allows decoupling of the `cacheWidth`, `cacheHeight`, and
/// `allowUpscaling` parameters from implementations of [ImageProvider] that do
/// not expose them.
///
/// See also:
///
/// * [ResizeImage], which uses this to override the `cacheWidth`,
/// `cacheHeight`, and `allowUpscaling` parameters.
@Deprecated(
'Use DecoderBufferCallback with ImageProvider.loadBuffer instead. '
'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});
/// 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
/// `allowUpscaling` parameters from implementations of [ImageProvider] that do /// `allowUpscaling` parameters from implementations of [ImageProvider] that do
/// not expose them. /// not expose them.
...@@ -172,7 +192,7 @@ class ImageConfiguration { ...@@ -172,7 +192,7 @@ 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.
typedef DecoderCallback = Future<ui.Codec> Function(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool allowUpscaling}); typedef DecoderBufferCallback = Future<ui.Codec> Function(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling});
/// 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
...@@ -213,16 +233,16 @@ typedef DecoderCallback = Future<ui.Codec> Function(Uint8List bytes, {int? cache ...@@ -213,16 +233,16 @@ typedef DecoderCallback = Future<ui.Codec> Function(Uint8List bytes, {int? cache
/// using that key. This is handled by [resolveStreamForKey]. That method /// using that key. This is handled by [resolveStreamForKey]. That method
/// may fizzle if it determines the image is no longer necessary, use the /// may fizzle if it determines the image is no longer necessary, use the
/// provided [ImageErrorListener] to report an error, set the completer /// provided [ImageErrorListener] to report an error, set the completer
/// from the cache if possible, or call [load] to fetch the encoded image /// from the cache if possible, or call [loadBuffer] to fetch the encoded image
/// bytes and schedule decoding. /// bytes and schedule decoding.
/// 4. The [load] method is responsible for both fetching the encoded bytes /// 4. The [loadBuffer] method is responsible for both fetching the encoded bytes
/// and decoding them using the provided [DecoderCallback]. It is called /// and decoding them using the provided [DecoderCallback]. It is called
/// in a context that uses the [ImageErrorListener] to report errors back. /// in a context that uses the [ImageErrorListener] to report errors back.
/// ///
/// Subclasses normally only have to implement the [load] and [obtainKey] /// Subclasses normally only have to implement the [loadBuffer] and [obtainKey]
/// methods. A subclass that needs finer grained control over the [ImageStream] /// methods. A subclass that needs finer grained control over the [ImageStream]
/// type must override [createStream]. A subclass that needs finer grained /// type must override [createStream]. A subclass that needs finer grained
/// control over the resolution, such as delaying calling [load], must override /// control over the resolution, such as delaying calling [loadBuffer], must override
/// [resolveStreamForKey]. /// [resolveStreamForKey].
/// ///
/// The [resolve] method is marked as [nonVirtual] so that [ImageProvider]s can /// The [resolve] method is marked as [nonVirtual] so that [ImageProvider]s can
...@@ -491,7 +511,7 @@ abstract class ImageProvider<T extends Object> { ...@@ -491,7 +511,7 @@ abstract class ImageProvider<T extends Object> {
} }
final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent( final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
key, key,
() => load(key, PaintingBinding.instance.instantiateImageCodec), () => loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer),
onError: handleError, onError: handleError,
); );
if (completer != null) { if (completer != null) {
...@@ -563,6 +583,10 @@ abstract class ImageProvider<T extends Object> { ...@@ -563,6 +583,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.
/// ///
/// This method is deprecated. Implement [loadBuffer] for faster image
/// loading. Only one of [load] and [loadBuffer] must be implemented, and
/// [loadBuffer] is preferred.
///
/// 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.
/// ///
...@@ -570,7 +594,31 @@ abstract class ImageProvider<T extends Object> { ...@@ -570,7 +594,31 @@ 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
ImageStreamCompleter load(T key, DecoderCallback decode); @Deprecated(
'Implement loadBuffer for faster image loading. '
'This feature was deprecated after v2.13.0-1.0.pre.',
)
ImageStreamCompleter load(T key, DecoderCallback decode) {
throw UnsupportedError('Implement loadBuffer for faster image loading');
}
/// 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.
///
/// 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.
@protected
ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) {
return load(key, PaintingBinding.instance.instantiateImageCodec);
}
@override @override
String toString() => '${objectRuntimeType(this, 'ImageConfiguration')}()'; String toString() => '${objectRuntimeType(this, 'ImageConfiguration')}()';
...@@ -634,6 +682,24 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -634,6 +682,24 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
/// Converts a key into an [ImageStreamCompleter], and begins fetching the /// Converts a key into an [ImageStreamCompleter], and begins fetching the
/// image. /// image.
@override
ImageStreamCompleter loadBuffer(AssetBundleImageKey key, DecoderBufferCallback decode) {
InformationCollector? collector;
assert(() {
collector = () => <DiagnosticsNode>[
DiagnosticsProperty<ImageProvider>('Image provider', this),
DiagnosticsProperty<AssetBundleImageKey>('Image key', key),
];
return true;
}());
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode, null),
scale: key.scale,
debugLabel: key.name,
informationCollector: collector,
);
}
@override @override
ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) { ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) {
InformationCollector? collector; InformationCollector? collector;
...@@ -645,7 +711,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -645,7 +711,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
return true; return true;
}()); }());
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode), codec: _loadAsync(key, null, decode),
scale: key.scale, scale: key.scale,
debugLabel: key.name, debugLabel: key.name,
informationCollector: collector, informationCollector: collector,
...@@ -657,8 +723,24 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -657,8 +723,24 @@ 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, DecoderCallback decode) async { Future<ui.Codec> _loadAsync(AssetBundleImageKey key, DecoderBufferCallback? decode, DecoderCallback? decodeDepreacted) async {
ByteData? data; if (decode != 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;
}
if (buffer == null) {
PaintingBinding.instance.imageCache.evict(key);
throw StateError('Unable to read data');
}
return decode(buffer);
}
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.
try { try {
...@@ -671,7 +753,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe ...@@ -671,7 +753,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
PaintingBinding.instance.imageCache.evict(key); PaintingBinding.instance.imageCache.evict(key);
throw StateError('Unable to read data'); throw StateError('Unable to read data');
} }
return decode(data.buffer.asUint8List()); return decodeDepreacted!(data.buffer.asUint8List());
} }
} }
...@@ -757,13 +839,13 @@ class ResizeImage extends ImageProvider<ResizeImageKey> { ...@@ -757,13 +839,13 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
@override @override
ImageStreamCompleter load(ResizeImageKey key, DecoderCallback decode) { ImageStreamCompleter load(ResizeImageKey key, DecoderCallback decode) {
Future<ui.Codec> decodeResize(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) { Future<ui.Codec> decodeResize(Uint8List buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
assert( assert(
cacheWidth == null && cacheHeight == null && allowUpscaling == null, cacheWidth == null && cacheHeight == null && allowUpscaling == null,
'ResizeImage cannot be composed with another ImageProvider that applies ' 'ResizeImage cannot be composed with another ImageProvider that applies '
'cacheWidth, cacheHeight, or allowUpscaling.', 'cacheWidth, cacheHeight, or allowUpscaling.',
); );
return decode(bytes, cacheWidth: width, cacheHeight: height, allowUpscaling: this.allowUpscaling); return decode(buffer, cacheWidth: width, cacheHeight: height, allowUpscaling: this.allowUpscaling);
} }
final ImageStreamCompleter completer = imageProvider.load(key._providerCacheKey, decodeResize); final ImageStreamCompleter completer = imageProvider.load(key._providerCacheKey, decodeResize);
if (!kReleaseMode) { if (!kReleaseMode) {
...@@ -772,6 +854,23 @@ class ResizeImage extends ImageProvider<ResizeImageKey> { ...@@ -772,6 +854,23 @@ class ResizeImage extends ImageProvider<ResizeImageKey> {
return completer; return completer;
} }
@override
ImageStreamCompleter loadBuffer(ResizeImageKey key, DecoderBufferCallback decode) {
Future<ui.Codec> decodeResize(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
assert(
cacheWidth == null && cacheHeight == null && allowUpscaling == null,
'ResizeImage cannot be composed with another ImageProvider that applies '
'cacheWidth, cacheHeight, or allowUpscaling.',
);
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})';
}
return completer;
}
@override @override
Future<ResizeImageKey> obtainKey(ImageConfiguration configuration) { Future<ResizeImageKey> obtainKey(ImageConfiguration configuration) {
Completer<ResizeImageKey>? completer; Completer<ResizeImageKey>? completer;
...@@ -832,6 +931,9 @@ abstract class NetworkImage extends ImageProvider<NetworkImage> { ...@@ -832,6 +931,9 @@ abstract class NetworkImage extends ImageProvider<NetworkImage> {
@override @override
ImageStreamCompleter load(NetworkImage key, DecoderCallback decode); ImageStreamCompleter load(NetworkImage key, DecoderCallback decode);
@override
ImageStreamCompleter loadBuffer(NetworkImage key, DecoderBufferCallback 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
...@@ -866,7 +968,19 @@ class FileImage extends ImageProvider<FileImage> { ...@@ -866,7 +968,19 @@ 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, decode), codec: _loadAsync(key, null, decode),
scale: key.scale,
debugLabel: key.file.path,
informationCollector: () => <DiagnosticsNode>[
ErrorDescription('Path: ${file.path}'),
],
);
}
@override
ImageStreamCompleter loadBuffer(FileImage key, DecoderBufferCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode, null),
scale: key.scale, scale: key.scale,
debugLabel: key.file.path, debugLabel: key.file.path,
informationCollector: () => <DiagnosticsNode>[ informationCollector: () => <DiagnosticsNode>[
...@@ -875,18 +989,20 @@ class FileImage extends ImageProvider<FileImage> { ...@@ -875,18 +989,20 @@ class FileImage extends ImageProvider<FileImage> {
); );
} }
Future<ui.Codec> _loadAsync(FileImage key, DecoderCallback decode) async { Future<ui.Codec> _loadAsync(FileImage key, DecoderBufferCallback? decode, DecoderCallback? decodeDeprecated) async {
assert(key == this); assert(key == this);
final Uint8List bytes = await file.readAsBytes(); final Uint8List bytes = await file.readAsBytes();
if (bytes.lengthInBytes == 0) { if (bytes.lengthInBytes == 0) {
// The file may become available later. // The file may become available later.
PaintingBinding.instance.imageCache.evict(key); PaintingBinding.instance.imageCache.evict(key);
throw StateError('$file is empty and cannot be loaded as an image.'); throw StateError('$file is empty and cannot be loaded as an image.');
} }
return decode(bytes); if (decode != null) {
return decode(await ui.ImmutableBuffer.fromUint8List(bytes));
}
return decodeDeprecated!(bytes);
} }
@override @override
...@@ -953,16 +1069,28 @@ class MemoryImage extends ImageProvider<MemoryImage> { ...@@ -953,16 +1069,28 @@ 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, decode), codec: _loadAsync(key, null, decode),
scale: key.scale, scale: key.scale,
debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})', debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
); );
} }
Future<ui.Codec> _loadAsync(MemoryImage key, DecoderCallback decode) { @override
assert(key == this); ImageStreamCompleter loadBuffer(MemoryImage key, DecoderBufferCallback decode) {
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key, decode, null),
scale: key.scale,
debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
);
}
return decode(bytes); Future<ui.Codec> _loadAsync(MemoryImage key, DecoderBufferCallback? decode, DecoderCallback? decodeDepreacted) async {
assert(key == this);
if (decode != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer);
}
return decodeDepreacted!(bytes);
} }
@override @override
......
...@@ -2,11 +2,11 @@ ...@@ -2,11 +2,11 @@
// 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:async'; import 'dart:async';
import 'dart:convert'; import 'dart:convert';
import 'dart:io'; import 'dart:io';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -55,6 +55,15 @@ abstract class AssetBundle { ...@@ -55,6 +55,15 @@ abstract class AssetBundle {
/// Throws an exception if the asset is not found. /// Throws an exception if the asset is not found.
Future<ByteData> load(String key); Future<ByteData> load(String key);
/// Retrieve a binary resource from the asset bundle as an immutable
/// buffer.
///
/// Throws an exception if the asset is not found.
Future<ui.ImmutableBuffer> loadBuffer(String key) async {
final ByteData data = await load(key);
return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List());
}
/// Retrieve a string from the asset bundle. /// Retrieve a string from the asset bundle.
/// ///
/// Throws an exception if the asset is not found. /// Throws an exception if the asset is not found.
...@@ -228,6 +237,12 @@ abstract class CachingAssetBundle extends AssetBundle { ...@@ -228,6 +237,12 @@ abstract class CachingAssetBundle extends AssetBundle {
_stringCache.clear(); _stringCache.clear();
_structuredDataCache.clear(); _structuredDataCache.clear();
} }
@override
Future<ui.ImmutableBuffer> loadBuffer(String key) async {
final ByteData data = await load(key);
return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List());
}
} }
/// An [AssetBundle] that loads resources using platform messages. /// An [AssetBundle] that loads resources using platform messages.
...@@ -242,6 +257,19 @@ class PlatformAssetBundle extends CachingAssetBundle { ...@@ -242,6 +257,19 @@ class PlatformAssetBundle extends CachingAssetBundle {
} }
return asset; return asset;
} }
@override
Future<ui.ImmutableBuffer> loadBuffer(String key) async {
if (kIsWeb) {
final ByteData bytes = await load(key);
return ui.ImmutableBuffer.fromUint8List(bytes.buffer.asUint8List());
}
try {
return await ui.ImmutableBuffer.fromAsset(key);
} on Exception {
throw FlutterError('Unable to load asset: $key.');
}
}
} }
AssetBundle _initRootBundle() { AssetBundle _initRootBundle() {
......
...@@ -109,6 +109,9 @@ class ScrollAwareImageProvider<T extends Object> extends ImageProvider<T> { ...@@ -109,6 +109,9 @@ class ScrollAwareImageProvider<T extends Object> extends ImageProvider<T> {
@override @override
ImageStreamCompleter load(T key, DecoderCallback decode) => imageProvider.load(key, decode); ImageStreamCompleter load(T key, DecoderCallback decode) => imageProvider.load(key, decode);
@override
ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode);
@override @override
Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration); Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
} }
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// 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:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -136,15 +135,15 @@ void main() { ...@@ -136,15 +135,15 @@ void main() {
}); });
test('Returns null if an error is caught resolving an image', () { test('Returns null if an error is caught resolving an image', () {
Future<ui.Codec> basicDecoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) { Future<ui.Codec> basicDecoder(ui.ImmutableBuffer bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false); return PaintingBinding.instance.instantiateImageCodecFromBuffer(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
} }
final ErrorImageProvider errorImage = ErrorImageProvider(); final ErrorImageProvider errorImage = ErrorImageProvider();
expect(() => imageCache.putIfAbsent(errorImage, () => errorImage.load(errorImage, basicDecoder)), throwsA(isA<Error>())); expect(() => imageCache.putIfAbsent(errorImage, () => errorImage.loadBuffer(errorImage, basicDecoder)), throwsA(isA<Error>()));
bool caughtError = false; bool caughtError = false;
final ImageStreamCompleter? result = imageCache.putIfAbsent( final ImageStreamCompleter? result = imageCache.putIfAbsent(
errorImage, errorImage,
() => errorImage.load(errorImage, basicDecoder), () => errorImage.loadBuffer(errorImage, basicDecoder),
onError: (dynamic error, StackTrace? stackTrace) { onError: (dynamic error, StackTrace? stackTrace) {
caughtError = true; caughtError = true;
}, },
......
...@@ -17,8 +17,8 @@ import 'mocks_for_image_cache.dart'; ...@@ -17,8 +17,8 @@ import 'mocks_for_image_cache.dart';
void main() { void main() {
TestRenderingFlutterBinding.ensureInitialized(); TestRenderingFlutterBinding.ensureInitialized();
Future<ui.Codec> basicDecoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) { Future<ui.Codec> basicDecoder(ui.ImmutableBuffer bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false); return PaintingBinding.instance.instantiateImageCodecFromBuffer(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
} }
FlutterExceptionHandler? oldError; FlutterExceptionHandler? oldError;
...@@ -76,7 +76,7 @@ void main() { ...@@ -76,7 +76,7 @@ void main() {
final Uint8List bytes = Uint8List.fromList(kTransparentImage); final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage imageProvider = MemoryImage(bytes); final MemoryImage imageProvider = MemoryImage(bytes);
final ImageStreamCompleter cacheStream = otherCache.putIfAbsent( final ImageStreamCompleter cacheStream = otherCache.putIfAbsent(
imageProvider, () => imageProvider.load(imageProvider, basicDecoder), imageProvider, () => imageProvider.loadBuffer(imageProvider, basicDecoder),
)!; )!;
final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty); final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
final Completer<void> completer = Completer<void>(); final Completer<void> completer = Completer<void>();
......
...@@ -6,7 +6,7 @@ import 'dart:async'; ...@@ -6,7 +6,7 @@ import 'dart:async';
import 'dart:io'; import 'dart:io';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' show Codec, FrameInfo; import 'dart:ui' show Codec, FrameInfo, ImmutableBuffer;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
...@@ -18,8 +18,8 @@ import '../rendering/rendering_tester.dart'; ...@@ -18,8 +18,8 @@ import '../rendering/rendering_tester.dart';
void main() { void main() {
TestRenderingFlutterBinding.ensureInitialized(); TestRenderingFlutterBinding.ensureInitialized();
Future<Codec> basicDecoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) { Future<Codec> basicDecoder(ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false); return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
} }
late _FakeHttpClient httpClient; late _FakeHttpClient httpClient;
...@@ -77,7 +77,7 @@ void main() { ...@@ -77,7 +77,7 @@ void main() {
Future<void> loadNetworkImage() async { Future<void> loadNetworkImage() async {
final NetworkImage networkImage = NetworkImage(nonconst('foo')); final NetworkImage networkImage = NetworkImage(nonconst('foo'));
final ImageStreamCompleter completer = networkImage.load(networkImage, basicDecoder); final ImageStreamCompleter completer = networkImage.loadBuffer(networkImage, basicDecoder);
completer.addListener(ImageStreamListener( completer.addListener(ImageStreamListener(
(ImageInfo image, bool synchronousCall) { }, (ImageInfo image, bool synchronousCall) { },
onError: (dynamic error, StackTrace? stackTrace) { onError: (dynamic error, StackTrace? stackTrace) {
...@@ -189,7 +189,7 @@ void main() { ...@@ -189,7 +189,7 @@ void main() {
debugNetworkImageHttpClientProvider = null; debugNetworkImageHttpClientProvider = null;
}, skip: isBrowser); // [intended] Browser does not resolve images this way. }, skip: isBrowser); // [intended] Browser does not resolve images this way.
Future<Codec> decoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async { Future<Codec> decoder(ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async {
return FakeCodec(); return FakeCodec();
} }
...@@ -207,7 +207,7 @@ void main() { ...@@ -207,7 +207,7 @@ void main() {
const NetworkImage provider = NetworkImage(url); const NetworkImage provider = NetworkImage(url);
final MultiFrameImageStreamCompleter completer = provider.load(provider, decoder) as MultiFrameImageStreamCompleter; final MultiFrameImageStreamCompleter completer = provider.loadBuffer(provider, decoder) as MultiFrameImageStreamCompleter;
expect(completer.debugLabel, url); expect(completer.debugLabel, url);
}); });
......
...@@ -99,14 +99,14 @@ void main() { ...@@ -99,14 +99,14 @@ void main() {
final MemoryImage memoryImage = MemoryImage(bytes); final MemoryImage memoryImage = MemoryImage(bytes);
final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321); final ResizeImage resizeImage = ResizeImage(memoryImage, width: 123, height: 321);
Future<ui.Codec> decode(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) { Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) {
expect(cacheWidth, 123); expect(cacheWidth, 123);
expect(cacheHeight, 321); expect(cacheHeight, 321);
expect(allowUpscaling, false); expect(allowUpscaling, false);
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling); return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling);
} }
resizeImage.load(await resizeImage.obtainKey(ImageConfiguration.empty), decode); resizeImage.loadBuffer(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
}); });
test('ResizeImage handles sync obtainKey', () async { test('ResizeImage handles sync obtainKey', () async {
......
...@@ -88,14 +88,14 @@ void main() { ...@@ -88,14 +88,14 @@ void main() {
final File file = fs.file('/empty.png')..createSync(recursive: true); final File file = fs.file('/empty.png')..createSync(recursive: true);
final FileImage provider = FileImage(file); final FileImage provider = FileImage(file);
expect(provider.load(provider, (Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async { expect(provider.loadBuffer(provider, (ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async {
return Future<Codec>.value(FakeCodec()); return Future<Codec>.value(FakeCodec());
}), isA<MultiFrameImageStreamCompleter>()); }), isA<MultiFrameImageStreamCompleter>());
expect(await error.future, isStateError); expect(await error.future, isStateError);
}); });
Future<Codec> decoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async { Future<Codec> decoder(ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) async {
return FakeCodec(); return FakeCodec();
} }
...@@ -104,7 +104,7 @@ void main() { ...@@ -104,7 +104,7 @@ void main() {
final File file = fs.file('/blue.png')..createSync(recursive: true)..writeAsBytesSync(kBlueSquarePng); final File file = fs.file('/blue.png')..createSync(recursive: true)..writeAsBytesSync(kBlueSquarePng);
final FileImage provider = FileImage(file); final FileImage provider = FileImage(file);
final MultiFrameImageStreamCompleter completer = provider.load(provider, decoder) as MultiFrameImageStreamCompleter; final MultiFrameImageStreamCompleter completer = provider.loadBuffer(provider, decoder) as MultiFrameImageStreamCompleter;
expect(completer.debugLabel, file.path); expect(completer.debugLabel, file.path);
}); });
...@@ -113,7 +113,7 @@ void main() { ...@@ -113,7 +113,7 @@ void main() {
final Uint8List bytes = Uint8List.fromList(kBlueSquarePng); final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
final MemoryImage provider = MemoryImage(bytes); final MemoryImage provider = MemoryImage(bytes);
final MultiFrameImageStreamCompleter completer = provider.load(provider, decoder) as MultiFrameImageStreamCompleter; final MultiFrameImageStreamCompleter completer = provider.loadBuffer(provider, decoder) as MultiFrameImageStreamCompleter;
expect(completer.debugLabel, 'MemoryImage(${describeIdentity(bytes)})'); expect(completer.debugLabel, 'MemoryImage(${describeIdentity(bytes)})');
}); });
...@@ -122,7 +122,7 @@ void main() { ...@@ -122,7 +122,7 @@ void main() {
const String asset = 'images/blue.png'; const String asset = 'images/blue.png';
final ExactAssetImage provider = ExactAssetImage(asset, bundle: _TestAssetBundle()); final ExactAssetImage provider = ExactAssetImage(asset, bundle: _TestAssetBundle());
final AssetBundleImageKey key = await provider.obtainKey(ImageConfiguration.empty); final AssetBundleImageKey key = await provider.obtainKey(ImageConfiguration.empty);
final MultiFrameImageStreamCompleter completer = provider.load(key, decoder) as MultiFrameImageStreamCompleter; final MultiFrameImageStreamCompleter completer = provider.loadBuffer(key, decoder) as MultiFrameImageStreamCompleter;
expect(completer.debugLabel, asset); expect(completer.debugLabel, asset);
}); });
...@@ -130,7 +130,7 @@ void main() { ...@@ -130,7 +130,7 @@ void main() {
test('Resize image sets tag', () async { test('Resize image sets tag', () async {
final Uint8List bytes = Uint8List.fromList(kBlueSquarePng); final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
final ResizeImage provider = ResizeImage(MemoryImage(bytes), width: 40, height: 40); final ResizeImage provider = ResizeImage(MemoryImage(bytes), width: 40, height: 40);
final MultiFrameImageStreamCompleter completer = provider.load( final MultiFrameImageStreamCompleter completer = provider.loadBuffer(
await provider.obtainKey(ImageConfiguration.empty), await provider.obtainKey(ImageConfiguration.empty),
decoder, decoder,
) as MultiFrameImageStreamCompleter; ) as MultiFrameImageStreamCompleter;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:convert'; import 'dart:convert';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
...@@ -34,6 +35,12 @@ class TestAssetBundle extends CachingAssetBundle { ...@@ -34,6 +35,12 @@ class TestAssetBundle extends CachingAssetBundle {
} }
throw FlutterError('key not found'); throw FlutterError('key not found');
} }
@override
Future<ui.ImmutableBuffer> loadBuffer(String key) async {
final ByteData data = await load(key);
return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List());
}
} }
void main() { void main() {
......
...@@ -30,6 +30,11 @@ class TestImageProvider extends ImageProvider<TestImageProvider> { ...@@ -30,6 +30,11 @@ 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.');
}
@override
ImageStreamCompleter loadBuffer(TestImageProvider key, DecoderBufferCallback decode) {
loadCallCount += 1; loadCallCount += 1;
return OneFrameImageStreamCompleter(_completer.future); return OneFrameImageStreamCompleter(_completer.future);
} }
......
...@@ -86,6 +86,11 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) { ...@@ -86,6 +86,11 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) {
} }
class ErrorImageProvider extends ImageProvider<ErrorImageProvider> { class ErrorImageProvider extends ImageProvider<ErrorImageProvider> {
@override
ImageStreamCompleter loadBuffer(ErrorImageProvider key, DecoderBufferCallback decode) {
throw Error();
}
@override @override
ImageStreamCompleter load(ErrorImageProvider key, DecoderCallback decode) { ImageStreamCompleter load(ErrorImageProvider key, DecoderCallback decode) {
throw Error(); throw Error();
...@@ -99,7 +104,7 @@ class ErrorImageProvider extends ImageProvider<ErrorImageProvider> { ...@@ -99,7 +104,7 @@ class ErrorImageProvider extends ImageProvider<ErrorImageProvider> {
class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvider> { class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvider> {
@override @override
ImageStreamCompleter load(ObtainKeyErrorImageProvider key, DecoderCallback decode) { ImageStreamCompleter loadBuffer(ObtainKeyErrorImageProvider key, DecoderBufferCallback decode) {
throw Error(); throw Error();
} }
...@@ -107,11 +112,16 @@ class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvi ...@@ -107,11 +112,16 @@ class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvi
Future<ObtainKeyErrorImageProvider> obtainKey(ImageConfiguration configuration) { Future<ObtainKeyErrorImageProvider> obtainKey(ImageConfiguration configuration) {
throw Error(); throw Error();
} }
@override
ImageStreamCompleter load(ObtainKeyErrorImageProvider key, DecoderCallback decode) {
throw UnimplementedError();
}
} }
class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> { class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> {
@override @override
ImageStreamCompleter load(LoadErrorImageProvider key, DecoderCallback decode) { ImageStreamCompleter loadBuffer(LoadErrorImageProvider key, DecoderBufferCallback decode) {
throw Error(); throw Error();
} }
...@@ -119,6 +129,11 @@ class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> { ...@@ -119,6 +129,11 @@ class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> {
Future<LoadErrorImageProvider> obtainKey(ImageConfiguration configuration) { Future<LoadErrorImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<LoadErrorImageProvider>(this); return SynchronousFuture<LoadErrorImageProvider>(this);
} }
@override
ImageStreamCompleter load(LoadErrorImageProvider key, DecoderCallback decode) {
throw UnimplementedError();
}
} }
class LoadErrorCompleterImageProvider extends ImageProvider<LoadErrorCompleterImageProvider> { class LoadErrorCompleterImageProvider extends ImageProvider<LoadErrorCompleterImageProvider> {
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +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:async';
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:ui' as ui; import 'dart:ui' as ui;
...@@ -58,8 +59,8 @@ class LoadTestImageProvider extends ImageProvider<Object> { ...@@ -58,8 +59,8 @@ class LoadTestImageProvider extends ImageProvider<Object> {
final ImageProvider provider; final ImageProvider provider;
ImageStreamCompleter testLoad(Object key, DecoderCallback decode) { ImageStreamCompleter testLoad(Object key, DecoderBufferCallback decode) {
return provider.load(key, decode); return provider.loadBuffer(key, decode);
} }
@override @override
...@@ -339,7 +340,7 @@ Future<void> main() async { ...@@ -339,7 +340,7 @@ Future<void> main() async {
group('ImageProvider', () { group('ImageProvider', () {
testWidgets('memory placeholder cacheWidth and cacheHeight is passed through', (WidgetTester tester) async { test('memory placeholder cacheWidth and cacheHeight is passed through', () async {
final Uint8List testBytes = Uint8List.fromList(kTransparentImage); final Uint8List testBytes = Uint8List.fromList(kTransparentImage);
final FadeInImage image = FadeInImage.memoryNetwork( final FadeInImage image = FadeInImage.memoryNetwork(
placeholder: testBytes, placeholder: testBytes,
...@@ -351,22 +352,29 @@ Future<void> main() async { ...@@ -351,22 +352,29 @@ Future<void> main() async {
); );
bool called = false; bool called = false;
Future<ui.Codec> decode(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) { Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) {
expect(cacheWidth, 20); expect(cacheWidth, 20);
expect(cacheHeight, 30); expect(cacheHeight, 30);
expect(allowUpscaling, false); expect(allowUpscaling, false);
called = true; called = true;
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling); return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling);
} }
final ImageProvider resizeImage = image.placeholder; final ImageProvider resizeImage = image.placeholder;
expect(image.placeholder, isA<ResizeImage>()); expect(image.placeholder, isA<ResizeImage>());
expect(called, false); expect(called, false);
final LoadTestImageProvider testProvider = LoadTestImageProvider(image.placeholder); final LoadTestImageProvider testProvider = LoadTestImageProvider(image.placeholder);
testProvider.testLoad(await resizeImage.obtainKey(ImageConfiguration.empty), decode); final ImageStreamCompleter streamCompleter = testProvider.testLoad(await resizeImage.obtainKey(ImageConfiguration.empty), decode);
final Completer<void> completer = Completer<void>();
streamCompleter.addListener(ImageStreamListener((ImageInfo imageInfo, bool syncCall) {
completer.complete();
}));
await completer.future;
expect(called, true); expect(called, true);
}); });
testWidgets('do not resize when null cache dimensions', (WidgetTester tester) async { test('do not resize when null cache dimensions', () async {
final Uint8List testBytes = Uint8List.fromList(kTransparentImage); final Uint8List testBytes = Uint8List.fromList(kTransparentImage);
final FadeInImage image = FadeInImage.memoryNetwork( final FadeInImage image = FadeInImage.memoryNetwork(
placeholder: testBytes, placeholder: testBytes,
...@@ -374,19 +382,26 @@ Future<void> main() async { ...@@ -374,19 +382,26 @@ Future<void> main() async {
); );
bool called = false; bool called = false;
Future<ui.Codec> decode(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) { Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) {
expect(cacheWidth, null); expect(cacheWidth, null);
expect(cacheHeight, null); expect(cacheHeight, null);
expect(allowUpscaling, false); expect(allowUpscaling, false);
called = true; called = true;
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight); return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
} }
// image.placeholder should be an instance of MemoryImage instead of ResizeImage // image.placeholder should be an instance of MemoryImage instead of ResizeImage
final ImageProvider memoryImage = image.placeholder; final ImageProvider memoryImage = image.placeholder;
expect(image.placeholder, isA<MemoryImage>()); expect(image.placeholder, isA<MemoryImage>());
expect(called, false); expect(called, false);
final LoadTestImageProvider testProvider = LoadTestImageProvider(image.placeholder); final LoadTestImageProvider testProvider = LoadTestImageProvider(image.placeholder);
testProvider.testLoad(await memoryImage.obtainKey(ImageConfiguration.empty), decode); final ImageStreamCompleter streamCompleter = testProvider.testLoad(await memoryImage.obtainKey(ImageConfiguration.empty), decode);
final Completer<void> completer = Completer<void>();
streamCompleter.addListener(ImageStreamListener((ImageInfo imageInfo, bool syncCall) {
completer.complete();
}));
await completer.future;
expect(called, true); expect(called, true);
}); });
}); });
......
...@@ -84,7 +84,7 @@ class TestAssetImage extends AssetImage { ...@@ -84,7 +84,7 @@ class TestAssetImage extends AssetImage {
final Map<double, ui.Image> images; final Map<double, ui.Image> images;
@override @override
ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) { ImageStreamCompleter loadBuffer(AssetBundleImageKey key, DecoderBufferCallback 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)]!;
......
...@@ -2122,7 +2122,7 @@ class _DebouncingImageProvider extends ImageProvider<Object> { ...@@ -2122,7 +2122,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 load(Object key, DecoderCallback decode) => imageProvider.load(key, decode); ImageStreamCompleter loadBuffer(Object key, DecoderBufferCallback decode) => imageProvider.loadBuffer(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.load(testImageProvider, PaintingBinding.instance.instantiateImageCodec)); imageCache.putIfAbsent(testImageProvider, () => testImageProvider.loadBuffer(testImageProvider, PaintingBinding.instance.instantiateImageCodecFromBuffer));
// 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.load(testImageProvider, PaintingBinding.instance.instantiateImageCodec)); stream.setCompleter(testImageProvider.loadBuffer(testImageProvider, PaintingBinding.instance.instantiateImageCodecFromBuffer));
// 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