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