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 @@
import 'dart:convert';
import 'dart:ui' as ui show Codec;
import 'dart:ui';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
......@@ -57,11 +58,11 @@ class DelayedBase64Image extends ImageProvider<int> {
}
@override
ImageStreamCompleter load(int key, DecoderCallback decode) {
ImageStreamCompleter loadBuffer(int key, DecoderBufferCallback decode) {
return MultiFrameImageStreamCompleter(
codec: Future<ui.Codec>.delayed(
delay,
() => decode(base64.decode(data)),
() async => decode(await ImmutableBuffer.fromUint8List(base64.decode(data))),
),
scale: 1.0,
);
......
......@@ -87,9 +87,7 @@ Future<void> main() async {
for (int i = 0; i < 10; i += 1) {
await Future.wait(<Future<ui.ImmutableBuffer>>[
for (String asset in assets)
rootBundle.load(asset).then((ByteData data) {
return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List());
})
rootBundle.loadBuffer(asset)
]);
}
watch.stop();
......
......@@ -46,7 +46,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),
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,
scale: key.scale,
debugLabel: key.url,
......@@ -77,7 +96,8 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
Future<ui.Codec> _loadAsync(
NetworkImage key,
StreamController<ImageChunkEvent> chunkEvents,
image_provider.DecoderCallback decode,
image_provider.DecoderBufferCallback? decode,
image_provider.DecoderCallback? decodeDepreacted,
) async {
try {
assert(key == this);
......@@ -111,7 +131,13 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
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) {
// 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.
......
......@@ -66,7 +66,24 @@ class NetworkImage
return MultiFrameImageStreamCompleter(
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,
debugLabel: key.url,
informationCollector: _imageStreamInformationCollector(key),
......@@ -93,7 +110,8 @@ class NetworkImage
// directly in place of the typical `instantiateImageCodec` method.
Future<ui.Codec> _loadAsync(
NetworkImage key,
image_provider.DecoderCallback decode,
image_provider.DecoderBufferCallback? decode,
image_provider.DecoderCallback? decodeDepreacted,
StreamController<ImageChunkEvent> chunkEvents,
) async {
assert(key == this);
......@@ -144,7 +162,13 @@ class NetworkImage
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 {
// This API only exists in the web engine implementation and is not
// contained in the analyzer summary for Flutter.
......
......@@ -3,7 +3,7 @@
// found in the LICENSE file.
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/services.dart' show ServicesBinding;
......@@ -81,6 +81,9 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
/// 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 size to decode the image to.
///
......@@ -97,6 +100,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 instantiateImageCodecFromBuffer with an ImmutableBuffer instance instead. '
'This feature was deprecated after v2.13.0-1.0.pre.',
)
Future<ui.Codec> instantiateImageCodec(
Uint8List bytes, {
int? cacheWidth,
......@@ -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
void evict(String asset) {
super.evict(asset);
......
......@@ -2,11 +2,11 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
......@@ -55,6 +55,15 @@ abstract class AssetBundle {
/// Throws an exception if the asset is not found.
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.
///
/// Throws an exception if the asset is not found.
......@@ -228,6 +237,12 @@ abstract class CachingAssetBundle extends AssetBundle {
_stringCache.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.
......@@ -242,6 +257,19 @@ class PlatformAssetBundle extends CachingAssetBundle {
}
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() {
......
......@@ -109,6 +109,9 @@ class ScrollAwareImageProvider<T extends Object> extends ImageProvider<T> {
@override
ImageStreamCompleter load(T key, DecoderCallback decode) => imageProvider.load(key, decode);
@override
ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) => imageProvider.loadBuffer(key, decode);
@override
Future<T> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
}
......@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
......@@ -136,15 +135,15 @@ void main() {
});
test('Returns null if an error is caught resolving an image', () {
Future<ui.Codec> basicDecoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
Future<ui.Codec> basicDecoder(ui.ImmutableBuffer bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodecFromBuffer(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
}
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;
final ImageStreamCompleter? result = imageCache.putIfAbsent(
errorImage,
() => errorImage.load(errorImage, basicDecoder),
() => errorImage.loadBuffer(errorImage, basicDecoder),
onError: (dynamic error, StackTrace? stackTrace) {
caughtError = true;
},
......
......@@ -17,8 +17,8 @@ import 'mocks_for_image_cache.dart';
void main() {
TestRenderingFlutterBinding.ensureInitialized();
Future<ui.Codec> basicDecoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
Future<ui.Codec> basicDecoder(ui.ImmutableBuffer bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodecFromBuffer(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
}
FlutterExceptionHandler? oldError;
......@@ -76,7 +76,7 @@ void main() {
final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage imageProvider = MemoryImage(bytes);
final ImageStreamCompleter cacheStream = otherCache.putIfAbsent(
imageProvider, () => imageProvider.load(imageProvider, basicDecoder),
imageProvider, () => imageProvider.loadBuffer(imageProvider, basicDecoder),
)!;
final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
final Completer<void> completer = Completer<void>();
......
......@@ -6,7 +6,7 @@ import 'dart:async';
import 'dart:io';
import 'dart:math' as math;
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/painting.dart';
......@@ -18,8 +18,8 @@ import '../rendering/rendering_tester.dart';
void main() {
TestRenderingFlutterBinding.ensureInitialized();
Future<Codec> basicDecoder(Uint8List bytes, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
Future<Codec> basicDecoder(ImmutableBuffer buffer, {int? cacheWidth, int? cacheHeight, bool? allowUpscaling}) {
return PaintingBinding.instance.instantiateImageCodecFromBuffer(buffer, cacheWidth: cacheWidth, cacheHeight: cacheHeight, allowUpscaling: allowUpscaling ?? false);
}
late _FakeHttpClient httpClient;
......@@ -77,7 +77,7 @@ void main() {
Future<void> loadNetworkImage() async {
final NetworkImage networkImage = NetworkImage(nonconst('foo'));
final ImageStreamCompleter completer = networkImage.load(networkImage, basicDecoder);
final ImageStreamCompleter completer = networkImage.loadBuffer(networkImage, basicDecoder);
completer.addListener(ImageStreamListener(
(ImageInfo image, bool synchronousCall) { },
onError: (dynamic error, StackTrace? stackTrace) {
......@@ -189,7 +189,7 @@ void main() {
debugNetworkImageHttpClientProvider = null;
}, 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();
}
......@@ -207,7 +207,7 @@ void main() {
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);
});
......
......@@ -99,14 +99,14 @@ void main() {
final MemoryImage memoryImage = MemoryImage(bytes);
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(cacheHeight, 321);
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 {
......
......@@ -88,14 +88,14 @@ void main() {
final File file = fs.file('/empty.png')..createSync(recursive: true);
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());
}), isA<MultiFrameImageStreamCompleter>());
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();
}
......@@ -104,7 +104,7 @@ void main() {
final File file = fs.file('/blue.png')..createSync(recursive: true)..writeAsBytesSync(kBlueSquarePng);
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);
});
......@@ -113,7 +113,7 @@ void main() {
final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
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)})');
});
......@@ -122,7 +122,7 @@ void main() {
const String asset = 'images/blue.png';
final ExactAssetImage provider = ExactAssetImage(asset, bundle: _TestAssetBundle());
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);
});
......@@ -130,7 +130,7 @@ void main() {
test('Resize image sets tag', () async {
final Uint8List bytes = Uint8List.fromList(kBlueSquarePng);
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),
decoder,
) as MultiFrameImageStreamCompleter;
......
......@@ -4,6 +4,7 @@
import 'dart:convert';
import 'dart:typed_data';
import 'dart:ui' as ui;
import 'package:flutter/foundation.dart';
import 'package:flutter/painting.dart';
......@@ -34,6 +35,12 @@ class TestAssetBundle extends CachingAssetBundle {
}
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() {
......
......@@ -30,6 +30,11 @@ class TestImageProvider extends ImageProvider<TestImageProvider> {
@override
ImageStreamCompleter load(TestImageProvider key, DecoderCallback decode) {
throw UnsupportedError('Use ImageProvider.loadBuffer instead.');
}
@override
ImageStreamCompleter loadBuffer(TestImageProvider key, DecoderBufferCallback decode) {
loadCallCount += 1;
return OneFrameImageStreamCompleter(_completer.future);
}
......
......@@ -86,6 +86,11 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) {
}
class ErrorImageProvider extends ImageProvider<ErrorImageProvider> {
@override
ImageStreamCompleter loadBuffer(ErrorImageProvider key, DecoderBufferCallback decode) {
throw Error();
}
@override
ImageStreamCompleter load(ErrorImageProvider key, DecoderCallback decode) {
throw Error();
......@@ -99,7 +104,7 @@ class ErrorImageProvider extends ImageProvider<ErrorImageProvider> {
class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvider> {
@override
ImageStreamCompleter load(ObtainKeyErrorImageProvider key, DecoderCallback decode) {
ImageStreamCompleter loadBuffer(ObtainKeyErrorImageProvider key, DecoderBufferCallback decode) {
throw Error();
}
......@@ -107,11 +112,16 @@ class ObtainKeyErrorImageProvider extends ImageProvider<ObtainKeyErrorImageProvi
Future<ObtainKeyErrorImageProvider> obtainKey(ImageConfiguration configuration) {
throw Error();
}
@override
ImageStreamCompleter load(ObtainKeyErrorImageProvider key, DecoderCallback decode) {
throw UnimplementedError();
}
}
class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> {
@override
ImageStreamCompleter load(LoadErrorImageProvider key, DecoderCallback decode) {
ImageStreamCompleter loadBuffer(LoadErrorImageProvider key, DecoderBufferCallback decode) {
throw Error();
}
......@@ -119,6 +129,11 @@ class LoadErrorImageProvider extends ImageProvider<LoadErrorImageProvider> {
Future<LoadErrorImageProvider> obtainKey(ImageConfiguration configuration) {
return SynchronousFuture<LoadErrorImageProvider>(this);
}
@override
ImageStreamCompleter load(LoadErrorImageProvider key, DecoderCallback decode) {
throw UnimplementedError();
}
}
class LoadErrorCompleterImageProvider extends ImageProvider<LoadErrorCompleterImageProvider> {
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui;
......@@ -58,8 +59,8 @@ class LoadTestImageProvider extends ImageProvider<Object> {
final ImageProvider provider;
ImageStreamCompleter testLoad(Object key, DecoderCallback decode) {
return provider.load(key, decode);
ImageStreamCompleter testLoad(Object key, DecoderBufferCallback decode) {
return provider.loadBuffer(key, decode);
}
@override
......@@ -339,7 +340,7 @@ Future<void> main() async {
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 FadeInImage image = FadeInImage.memoryNetwork(
placeholder: testBytes,
......@@ -351,22 +352,29 @@ Future<void> main() async {
);
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(cacheHeight, 30);
expect(allowUpscaling, false);
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;
expect(image.placeholder, isA<ResizeImage>());
expect(called, false);
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);
});
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 FadeInImage image = FadeInImage.memoryNetwork(
placeholder: testBytes,
......@@ -374,19 +382,26 @@ Future<void> main() async {
);
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(cacheHeight, null);
expect(allowUpscaling, false);
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
final ImageProvider memoryImage = image.placeholder;
expect(image.placeholder, isA<MemoryImage>());
expect(called, false);
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);
});
});
......
......@@ -84,7 +84,7 @@ class TestAssetImage extends AssetImage {
final Map<double, ui.Image> images;
@override
ImageStreamCompleter load(AssetBundleImageKey key, DecoderCallback decode) {
ImageStreamCompleter loadBuffer(AssetBundleImageKey key, DecoderBufferCallback decode) {
late ImageInfo imageInfo;
key.bundle.load(key.name).then<void>((ByteData data) {
final ui.Image image = images[scaleOf(data)]!;
......
......@@ -2122,7 +2122,7 @@ class _DebouncingImageProvider extends ImageProvider<Object> {
Future<Object> obtainKey(ImageConfiguration configuration) => imageProvider.obtainKey(configuration);
@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> {
......
......@@ -322,7 +322,7 @@ void main() {
// If we miss the early return, we will fail.
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.
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.load(testImageProvider, PaintingBinding.instance.instantiateImageCodec));
stream.setCompleter(testImageProvider.loadBuffer(testImageProvider, PaintingBinding.instance.instantiateImageCodecFromBuffer));
// 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