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);
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 PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling);
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);
......
......@@ -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