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

Add more flexible image API (#118966)

This updates the framework to provide higher level wrappers around ui.instantiateImageCodecWithSize(). Functionally, this doesn't change anything (other than deprecating the older loadBuffer() method in favor of loadImage()), but it sets the stage for a simpler change that will allow us to provide a more flexible way to load sized images.

#118543
parent 202e9027
...@@ -58,7 +58,7 @@ class DelayedBase64Image extends ImageProvider<int> { ...@@ -58,7 +58,7 @@ class DelayedBase64Image extends ImageProvider<int> {
} }
@override @override
ImageStreamCompleter loadBuffer(int key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(int key, ImageDecoderCallback decode) {
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: Future<ui.Codec>.delayed( codec: Future<ui.Codec>.delayed(
delay, delay,
......
...@@ -9,15 +9,15 @@ import 'package:flutter/material.dart'; ...@@ -9,15 +9,15 @@ import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart'; import 'package:integration_test/integration_test.dart';
// This class allows loadBuffer, a protected method, to be called with a custom // This class allows loadImage, a protected method, to be called with a custom
// DecoderBufferCallback function. // ImageDecoderCallback function.
class LoadTestImageProvider extends ImageProvider<Object> { class LoadTestImageProvider extends ImageProvider<Object> {
LoadTestImageProvider(this.provider); LoadTestImageProvider(this.provider);
final ImageProvider provider; final ImageProvider provider;
ImageStreamCompleter testLoad(Object key, DecoderBufferCallback decode) { ImageStreamCompleter testLoad(Object key, ImageDecoderCallback decode) {
return provider.loadBuffer(key, decode); return provider.loadImage(key, decode);
} }
@override @override
...@@ -26,7 +26,7 @@ class LoadTestImageProvider extends ImageProvider<Object> { ...@@ -26,7 +26,7 @@ class LoadTestImageProvider extends ImageProvider<Object> {
} }
@override @override
ImageStreamCompleter loadBuffer(Object key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) {
throw UnimplementedError(); throw UnimplementedError();
} }
} }
...@@ -47,12 +47,15 @@ void main() { ...@@ -47,12 +47,15 @@ void main() {
bool called = false; bool called = false;
Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool allowUpscaling = false}) { Future<ui.Codec> decode(ui.ImmutableBuffer buffer, {ui.TargetImageSizeCallback? getTargetSize}) {
expect(cacheHeight, expectedCacheHeight); return PaintingBinding.instance.instantiateImageCodecWithSize(buffer, getTargetSize: (int intrinsicWidth, int intrinsicHeight) {
expect(cacheWidth, expectedCacheWidth); expect(getTargetSize, isNotNull);
expect(allowUpscaling, false); final ui.TargetImageSize targetSize = getTargetSize!(intrinsicWidth, intrinsicHeight);
called = true; expect(targetSize.width, expectedCacheWidth);
return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling); expect(targetSize.height, expectedCacheHeight);
called = true;
return targetSize;
});
} }
final ImageProvider resizeImage = image.image; final ImageProvider resizeImage = image.image;
......
...@@ -43,7 +43,7 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -43,7 +43,7 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>(); final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as NetworkImage, chunkEvents, null, decode), codec: _loadAsync(key as NetworkImage, chunkEvents, decodeDeprecated: decode),
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
...@@ -62,7 +62,26 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -62,7 +62,26 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>(); final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as NetworkImage, chunkEvents, decode, null), codec: _loadAsync(key as NetworkImage, chunkEvents, decodeBufferDeprecated: decode),
chunkEvents: chunkEvents.stream,
scale: key.scale,
debugLabel: key.url,
informationCollector: () => <DiagnosticsNode>[
DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
],
);
}
@override
ImageStreamCompleter loadImage(image_provider.NetworkImage key, image_provider.ImageDecoderCallback decode) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
codec: _loadAsync(key as NetworkImage, chunkEvents, decode: decode),
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
...@@ -92,10 +111,11 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -92,10 +111,11 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
Future<ui.Codec> _loadAsync( Future<ui.Codec> _loadAsync(
NetworkImage key, NetworkImage key,
StreamController<ImageChunkEvent> chunkEvents, StreamController<ImageChunkEvent> chunkEvents, {
image_provider.DecoderBufferCallback? decode, image_provider.ImageDecoderCallback? decode,
image_provider.DecoderCallback? decodeDepreacted, image_provider.DecoderBufferCallback? decodeBufferDeprecated,
) async { image_provider.DecoderCallback? decodeDeprecated,
}) async {
try { try {
assert(key == this); assert(key == this);
...@@ -131,9 +151,12 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm ...@@ -131,9 +151,12 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
if (decode != null) { if (decode != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer); return decode(buffer);
} else if (decodeBufferDeprecated != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decodeBufferDeprecated(buffer);
} else { } else {
assert(decodeDepreacted != null); assert(decodeDeprecated != null);
return decodeDepreacted!(bytes); return decodeDeprecated!(bytes);
} }
} catch (e) { } catch (e) {
// Depending on where the exception was thrown, the image cache may not // Depending on where the exception was thrown, the image cache may not
......
...@@ -65,7 +65,7 @@ class NetworkImage ...@@ -65,7 +65,7 @@ class NetworkImage
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
codec: _loadAsync(key as NetworkImage, null, decode, chunkEvents), codec: _loadAsync(key as NetworkImage, null, null, decode, chunkEvents),
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(key), informationCollector: _imageStreamInformationCollector(key),
...@@ -82,7 +82,23 @@ class NetworkImage ...@@ -82,7 +82,23 @@ class NetworkImage
return MultiFrameImageStreamCompleter( return MultiFrameImageStreamCompleter(
chunkEvents: chunkEvents.stream, chunkEvents: chunkEvents.stream,
codec: _loadAsync(key as NetworkImage, decode, null, chunkEvents), codec: _loadAsync(key as NetworkImage, null, decode, null, chunkEvents),
scale: key.scale,
debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(key),
);
}
@override
ImageStreamCompleter loadImage(image_provider.NetworkImage key, image_provider.ImageDecoderCallback decode) {
// Ownership of this controller is handed off to [_loadAsync]; it is that
// method's responsibility to close the controller's stream when the image
// has been loaded or an error is thrown.
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
return MultiFrameImageStreamCompleter(
chunkEvents: chunkEvents.stream,
codec: _loadAsync(key as NetworkImage, decode, null, null, chunkEvents),
scale: key.scale, scale: key.scale,
debugLabel: key.url, debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(key), informationCollector: _imageStreamInformationCollector(key),
...@@ -106,8 +122,9 @@ class NetworkImage ...@@ -106,8 +122,9 @@ class NetworkImage
// directly in place of the typical `instantiateImageCodec` method. // directly in place of the typical `instantiateImageCodec` method.
Future<ui.Codec> _loadAsync( Future<ui.Codec> _loadAsync(
NetworkImage key, NetworkImage key,
image_provider.DecoderBufferCallback? decode, image_provider.ImageDecoderCallback? decode,
image_provider.DecoderCallback? decodeDepreacted, image_provider.DecoderBufferCallback? decodeBufferDeprecated,
image_provider.DecoderCallback? decodeDeprecated,
StreamController<ImageChunkEvent> chunkEvents, StreamController<ImageChunkEvent> chunkEvents,
) async { ) async {
assert(key == this); assert(key == this);
...@@ -165,9 +182,12 @@ class NetworkImage ...@@ -165,9 +182,12 @@ class NetworkImage
if (decode != null) { if (decode != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes); final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decode(buffer); return decode(buffer);
} else if (decodeBufferDeprecated != null) {
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
return decodeBufferDeprecated(buffer);
} else { } else {
assert(decodeDepreacted != null); assert(decodeDeprecated != null);
return decodeDepreacted!(bytes); return decodeDeprecated!(bytes);
} }
} else { } else {
// This API only exists in the web engine implementation and is not // This API only exists in the web engine implementation and is not
......
...@@ -2,7 +2,7 @@ ...@@ -2,7 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:ui' as ui show Codec, ImmutableBuffer, instantiateImageCodec, instantiateImageCodecFromBuffer; import 'dart:ui' as ui;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart' show ServicesBinding; import 'package:flutter/services.dart' show ServicesBinding;
...@@ -100,7 +100,7 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -100,7 +100,7 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
/// above its native resolution should prefer scaling the canvas the image is /// above its native resolution should prefer scaling the canvas the image is
/// drawn into. /// drawn into.
@Deprecated( @Deprecated(
'Use instantiateImageCodecFromBuffer with an ImmutableBuffer instance instead. ' 'Use instantiateImageCodecWithSize with an ImmutableBuffer instance instead. '
'This feature was deprecated after v2.13.0-1.0.pre.', 'This feature was deprecated after v2.13.0-1.0.pre.',
) )
Future<ui.Codec> instantiateImageCodec( Future<ui.Codec> instantiateImageCodec(
...@@ -140,6 +140,10 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -140,6 +140,10 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
/// unnecessary memory usage for images. Callers that wish to display an image /// unnecessary memory usage for images. Callers that wish to display an image
/// above its native resolution should prefer scaling the canvas the image is /// above its native resolution should prefer scaling the canvas the image is
/// drawn into. /// drawn into.
@Deprecated(
'Use instantiateImageCodecWithSize instead. '
'This feature was deprecated after v3.7.0-1.4.pre.',
)
Future<ui.Codec> instantiateImageCodecFromBuffer( Future<ui.Codec> instantiateImageCodecFromBuffer(
ui.ImmutableBuffer buffer, { ui.ImmutableBuffer buffer, {
int? cacheWidth, int? cacheWidth,
...@@ -156,6 +160,28 @@ mixin PaintingBinding on BindingBase, ServicesBinding { ...@@ -156,6 +160,28 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
); );
} }
/// Calls through to [dart:ui.instantiateImageCodecWithSize] from [ImageCache].
///
/// The [buffer] parameter should be an [ui.ImmutableBuffer] instance which can
/// be acquired from [ui.ImmutableBuffer.fromUint8List] or
/// [ui.ImmutableBuffer.fromAsset].
///
/// The [getTargetSize] parameter, when specified, will be invoked and passed
/// the image's intrinsic size to determine the size to decode the image to.
/// The width and the height of the size it returns must be positive values
/// greater than or equal to 1, or null. It is valid to return a [TargetImageSize]
/// that specifies only one of `width` and `height` with the other remaining
/// null, in which case the omitted dimension will be scaled to maintain the
/// aspect ratio of the original dimensions. When both are null or omitted,
/// the image will be decoded at its native resolution (as will be the case if
/// the [getTargetSize] parameter is omitted).
Future<ui.Codec> instantiateImageCodecWithSize(
ui.ImmutableBuffer buffer, {
ui.TargetImageSizeCallback? getTargetSize,
}) {
return ui.instantiateImageCodecWithSize(buffer, getTargetSize: getTargetSize);
}
@override @override
void evict(String asset) { void evict(String asset) {
super.evict(asset); super.evict(asset);
......
...@@ -111,6 +111,9 @@ class ScrollAwareImageProvider<T extends Object> extends ImageProvider<T> { ...@@ -111,6 +111,9 @@ class ScrollAwareImageProvider<T extends Object> extends ImageProvider<T> {
@override @override
ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode); ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode);
@override
ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) => imageProvider.loadImage(key, decode);
@override @override
Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration); Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
} }
...@@ -30,11 +30,16 @@ class TestImageProvider extends ImageProvider<TestImageProvider> { ...@@ -30,11 +30,16 @@ class TestImageProvider extends ImageProvider<TestImageProvider> {
@override @override
ImageStreamCompleter load(TestImageProvider key, DecoderCallback decode) { ImageStreamCompleter load(TestImageProvider key, DecoderCallback decode) {
throw UnsupportedError('Use ImageProvider.loadBuffer instead.'); throw UnsupportedError('Use ImageProvider.loadImage instead.');
} }
@override @override
ImageStreamCompleter loadBuffer(TestImageProvider key, DecoderBufferCallback decode) { ImageStreamCompleter loadBuffer(TestImageProvider key, DecoderBufferCallback decode) {
throw UnsupportedError('Use ImageProvider.loadImage instead.');
}
@override
ImageStreamCompleter loadImage(TestImageProvider key, ImageDecoderCallback decode) {
loadCallCount += 1; loadCallCount += 1;
return OneFrameImageStreamCompleter(_completer.future); return OneFrameImageStreamCompleter(_completer.future);
} }
......
...@@ -85,6 +85,11 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) { ...@@ -85,6 +85,11 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) {
} }
class ErrorImageProvider extends ImageProvider<ErrorImageProvider> { class ErrorImageProvider extends ImageProvider<ErrorImageProvider> {
@override
ImageStreamCompleter loadImage(ErrorImageProvider key, ImageDecoderCallback decode) {
throw Error();
}
@override @override
ImageStreamCompleter loadBuffer(ErrorImageProvider key, DecoderBufferCallback decode) { ImageStreamCompleter loadBuffer(ErrorImageProvider key, DecoderBufferCallback decode) {
throw Error(); throw Error();
...@@ -103,10 +108,15 @@ class ErrorImageProvider extends ImageProvider<ErrorImageProvider> { ...@@ -103,10 +108,15 @@ class ErrorImageProvider extends ImageProvider<ErrorImageProvider> {
class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvider> { class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvider> {
@override @override
ImageStreamCompleter loadBuffer(ObtainKeyErrorImageProvider key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(ObtainKeyErrorImageProvider key, ImageDecoderCallback decode) {
throw Error(); throw Error();
} }
@override
ImageStreamCompleter loadBuffer(ObtainKeyErrorImageProvider key, DecoderBufferCallback decode) {
throw UnimplementedError();
}
@override @override
Future<ObtainKeyErrorImageProvider> obtainKey(ImageConfiguration configuration) { Future<ObtainKeyErrorImageProvider> obtainKey(ImageConfiguration configuration) {
throw Error(); throw Error();
...@@ -120,10 +130,15 @@ class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvi ...@@ -120,10 +130,15 @@ class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvi
class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> { class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> {
@override @override
ImageStreamCompleter loadBuffer(LoadErrorImageProvider key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(LoadErrorImageProvider key, ImageDecoderCallback decode) {
throw Error(); throw Error();
} }
@override
ImageStreamCompleter loadBuffer(LoadErrorImageProvider key, DecoderBufferCallback decode) {
throw UnimplementedError();
}
@override @override
Future<LoadErrorImageProvider> obtainKey(ImageConfiguration configuration) { Future<LoadErrorImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<LoadErrorImageProvider>(this); return SynchronousFuture<LoadErrorImageProvider>(this);
......
...@@ -85,7 +85,7 @@ class TestAssetImage extends AssetImage { ...@@ -85,7 +85,7 @@ class TestAssetImage extends AssetImage {
final Map<double, ui.Image> images; final Map<double, ui.Image> images;
@override @override
ImageStreamCompleter loadBuffer(AssetBundleImageKey key, DecoderBufferCallback decode) { ImageStreamCompleter loadImage(AssetBundleImageKey key, ImageDecoderCallback decode) {
late ImageInfo imageInfo; late ImageInfo imageInfo;
key.bundle.load(key.name).then<void>((ByteData data) { key.bundle.load(key.name).then<void>((ByteData data) {
final ui.Image image = images[scaleOf(data)]!; final ui.Image image = images[scaleOf(data)]!;
......
...@@ -2173,7 +2173,7 @@ class _DebouncingImageProvider extends ImageProvider<Object> { ...@@ -2173,7 +2173,7 @@ class _DebouncingImageProvider extends ImageProvider<Object> {
Future<Object> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration); Future<Object> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
@override @override
ImageStreamCompleter loadBuffer(Object key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode); ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) => imageProvider.loadImage(key, decode);
} }
class _FailingImageProvider extends ImageProvider<int> { class _FailingImageProvider extends ImageProvider<int> {
......
...@@ -322,7 +322,7 @@ void main() { ...@@ -322,7 +322,7 @@ void main() {
// If we miss the early return, we will fail. // If we miss the early return, we will fail.
testImageProvider.complete(); testImageProvider.complete();
imageCache.putIfAbsent(testImageProvider, () => testImageProvider.loadBuffer(testImageProvider, PaintingBinding.instance.instantiateImageCodecFromBuffer)); imageCache.putIfAbsent(testImageProvider, () => testImageProvider.loadImage(testImageProvider, PaintingBinding.instance.instantiateImageCodecWithSize));
// We've stopped scrolling fast. // We've stopped scrolling fast.
physics.recommendDeferredLoadingValue = false; physics.recommendDeferredLoadingValue = false;
await tester.idle(); await tester.idle();
...@@ -377,7 +377,7 @@ void main() { ...@@ -377,7 +377,7 @@ void main() {
// Complete the original image while we're still scrolling fast. // Complete the original image while we're still scrolling fast.
testImageProvider.complete(); testImageProvider.complete();
stream.setCompleter(testImageProvider.loadBuffer(testImageProvider, PaintingBinding.instance.instantiateImageCodecFromBuffer)); stream.setCompleter(testImageProvider.loadImage(testImageProvider, PaintingBinding.instance.instantiateImageCodecWithSize));
// Verify that this hasn't changed the cache state yet // Verify that this hasn't changed the cache state yet
expect(imageCache.containsKey(testImageProvider), false); expect(imageCache.containsKey(testImageProvider), false);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment