Skip to content

Commit

Permalink
Add more flexible image API (#118966)
Browse files Browse the repository at this point in the history
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
  • Loading branch information
tvolkert authored Jan 26, 2023
1 parent 202e902 commit b319938
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -26,7 +26,7 @@ class LoadTestImageProvider extends ImageProvider<Object> {
}

@override
ImageStreamCompleter loadBuffer(Object key, DecoderBufferCallback decode) {
ImageStreamCompleter loadImage(Object key, ImageDecoderCallback decode) {
throw UnimplementedError();
}
}
Expand All @@ -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;
Expand Down
39 changes: 31 additions & 8 deletions packages/flutter/lib/src/painting/_network_image_io.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand Down Expand Up @@ -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);

Expand Down Expand Up @@ -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
Expand Down
32 changes: 26 additions & 6 deletions packages/flutter/lib/src/painting/_network_image_web.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand All @@ -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),
Expand All @@ -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);
Expand Down Expand Up @@ -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
Expand Down
30 changes: 28 additions & 2 deletions packages/flutter/lib/src/painting/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand All @@ -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);
Expand Down
Loading

0 comments on commit b319938

Please sign in to comment.