Skip to content

Commit 8d2978a

Browse files
author
Jonah Williams
authored
use immutable buffer for loading asset images (flutter#103496)
1 parent 5cb9c21 commit 8d2978a

20 files changed

+368
-74
lines changed

dev/benchmarks/macrobenchmarks/lib/src/animated_placeholder.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import 'dart:convert';
66
import 'dart:ui' as ui show Codec;
7+
import 'dart:ui';
78

89
import 'package:flutter/foundation.dart';
910
import 'package:flutter/material.dart';
@@ -57,11 +58,11 @@ class DelayedBase64Image extends ImageProvider<int> {
5758
}
5859

5960
@override
60-
ImageStreamCompleter load(int key, DecoderCallback decode) {
61+
ImageStreamCompleter loadBuffer(int key, DecoderBufferCallback decode) {
6162
return MultiFrameImageStreamCompleter(
6263
codec: Future<ui.Codec>.delayed(
6364
delay,
64-
() => decode(base64.decode(data)),
65+
() async => decode(await ImmutableBuffer.fromUint8List(base64.decode(data))),
6566
),
6667
scale: 1.0,
6768
);

dev/benchmarks/microbenchmarks/lib/ui/image_bench.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,9 +87,7 @@ Future<void> main() async {
8787
for (int i = 0; i < 10; i += 1) {
8888
await Future.wait(<Future<ui.ImmutableBuffer>>[
8989
for (String asset in assets)
90-
rootBundle.load(asset).then((ByteData data) {
91-
return ui.ImmutableBuffer.fromUint8List(data.buffer.asUint8List());
92-
})
90+
rootBundle.loadBuffer(asset)
9391
]);
9492
}
9593
watch.stop();

packages/flutter/lib/src/painting/_network_image_io.dart

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,26 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
4646
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
4747

4848
return MultiFrameImageStreamCompleter(
49-
codec: _loadAsync(key as NetworkImage, chunkEvents, decode),
49+
codec: _loadAsync(key as NetworkImage, chunkEvents, null, decode),
50+
chunkEvents: chunkEvents.stream,
51+
scale: key.scale,
52+
debugLabel: key.url,
53+
informationCollector: () => <DiagnosticsNode>[
54+
DiagnosticsProperty<image_provider.ImageProvider>('Image provider', this),
55+
DiagnosticsProperty<image_provider.NetworkImage>('Image key', key),
56+
],
57+
);
58+
}
59+
60+
@override
61+
ImageStreamCompleter loadBuffer(image_provider.NetworkImage key, image_provider.DecoderBufferCallback decode) {
62+
// Ownership of this controller is handed off to [_loadAsync]; it is that
63+
// method's responsibility to close the controller's stream when the image
64+
// has been loaded or an error is thrown.
65+
final StreamController<ImageChunkEvent> chunkEvents = StreamController<ImageChunkEvent>();
66+
67+
return MultiFrameImageStreamCompleter(
68+
codec: _loadAsync(key as NetworkImage, chunkEvents, decode, null),
5069
chunkEvents: chunkEvents.stream,
5170
scale: key.scale,
5271
debugLabel: key.url,
@@ -77,7 +96,8 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
7796
Future<ui.Codec> _loadAsync(
7897
NetworkImage key,
7998
StreamController<ImageChunkEvent> chunkEvents,
80-
image_provider.DecoderCallback decode,
99+
image_provider.DecoderBufferCallback? decode,
100+
image_provider.DecoderCallback? decodeDepreacted,
81101
) async {
82102
try {
83103
assert(key == this);
@@ -111,7 +131,13 @@ class NetworkImage extends image_provider.ImageProvider<image_provider.NetworkIm
111131
throw Exception('NetworkImage is an empty file: $resolved');
112132
}
113133

114-
return decode(bytes);
134+
if (decode != null) {
135+
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
136+
return decode(buffer);
137+
} else {
138+
assert(decodeDepreacted != null);
139+
return decodeDepreacted!(bytes);
140+
}
115141
} catch (e) {
116142
// Depending on where the exception was thrown, the image cache may not
117143
// have had a chance to track the key in the cache at all.

packages/flutter/lib/src/painting/_network_image_web.dart

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,24 @@ class NetworkImage
6666

6767
return MultiFrameImageStreamCompleter(
6868
chunkEvents: chunkEvents.stream,
69-
codec: _loadAsync(key as NetworkImage, decode, chunkEvents),
69+
codec: _loadAsync(key as NetworkImage, null, decode, chunkEvents),
70+
scale: key.scale,
71+
debugLabel: key.url,
72+
informationCollector: _imageStreamInformationCollector(key),
73+
);
74+
}
75+
76+
@override
77+
ImageStreamCompleter loadBuffer(image_provider.NetworkImage key, image_provider.DecoderBufferCallback decode) {
78+
// Ownership of this controller is handed off to [_loadAsync]; it is that
79+
// method's responsibility to close the controller's stream when the image
80+
// has been loaded or an error is thrown.
81+
final StreamController<ImageChunkEvent> chunkEvents =
82+
StreamController<ImageChunkEvent>();
83+
84+
return MultiFrameImageStreamCompleter(
85+
chunkEvents: chunkEvents.stream,
86+
codec: _loadAsync(key as NetworkImage, decode, null, chunkEvents),
7087
scale: key.scale,
7188
debugLabel: key.url,
7289
informationCollector: _imageStreamInformationCollector(key),
@@ -93,7 +110,8 @@ class NetworkImage
93110
// directly in place of the typical `instantiateImageCodec` method.
94111
Future<ui.Codec> _loadAsync(
95112
NetworkImage key,
96-
image_provider.DecoderCallback decode,
113+
image_provider.DecoderBufferCallback? decode,
114+
image_provider.DecoderCallback? decodeDepreacted,
97115
StreamController<ImageChunkEvent> chunkEvents,
98116
) async {
99117
assert(key == this);
@@ -144,7 +162,13 @@ class NetworkImage
144162
statusCode: request.status!, uri: resolved);
145163
}
146164

147-
return decode(bytes);
165+
if (decode != null) {
166+
final ui.ImmutableBuffer buffer = await ui.ImmutableBuffer.fromUint8List(bytes);
167+
return decode(buffer);
168+
} else {
169+
assert(decodeDepreacted != null);
170+
return decodeDepreacted!(bytes);
171+
}
148172
} else {
149173
// This API only exists in the web engine implementation and is not
150174
// contained in the analyzer summary for Flutter.

packages/flutter/lib/src/painting/binding.dart

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
// found in the LICENSE file.
44

55
import 'dart:typed_data' show Uint8List;
6-
import 'dart:ui' as ui show instantiateImageCodec, Codec;
6+
import 'dart:ui' as ui show instantiateImageCodec, instantiateImageCodecFromBuffer, Codec, ImmutableBuffer;
77
import 'package:flutter/foundation.dart';
88
import 'package:flutter/services.dart' show ServicesBinding;
99

@@ -81,6 +81,9 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
8181

8282
/// Calls through to [dart:ui.instantiateImageCodec] from [ImageCache].
8383
///
84+
/// This method is deprecated. use [instantiateImageCodecFromBuffer] with an
85+
/// [ImmutableBuffer] instance instead of this method.
86+
///
8487
/// The `cacheWidth` and `cacheHeight` parameters, when specified, indicate
8588
/// the size to decode the image to.
8689
///
@@ -97,6 +100,10 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
97100
/// unnecessary memory usage for images. Callers that wish to display an image
98101
/// above its native resolution should prefer scaling the canvas the image is
99102
/// drawn into.
103+
@Deprecated(
104+
'Use instantiateImageCodecFromBuffer with an ImmutableBuffer instance instead. '
105+
'This feature was deprecated after v2.13.0-1.0.pre.',
106+
)
100107
Future<ui.Codec> instantiateImageCodec(
101108
Uint8List bytes, {
102109
int? cacheWidth,
@@ -114,6 +121,44 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
114121
);
115122
}
116123

124+
/// Calls through to [dart:ui.instantiateImageCodecFromBuffer] from [ImageCache].
125+
///
126+
/// The [buffer] parameter should be an [ui.ImmutableBuffer] instance which can
127+
/// be acquired from [ui.ImmutableBuffer.fromUint8List] or [ui.ImmutableBuffer.fromAsset].
128+
///
129+
/// The [cacheWidth] and [cacheHeight] parameters, when specified, indicate
130+
/// the size to decode the image to.
131+
///
132+
/// Both [cacheWidth] and [cacheHeight] must be positive values greater than
133+
/// or equal to 1, or null. It is valid to specify only one of `cacheWidth`
134+
/// and [cacheHeight] with the other remaining null, in which case the omitted
135+
/// dimension will be scaled to maintain the aspect ratio of the original
136+
/// dimensions. When both are null or omitted, the image will be decoded at
137+
/// its native resolution.
138+
///
139+
/// The [allowUpscaling] parameter determines whether the `cacheWidth` or
140+
/// [cacheHeight] parameters are clamped to the intrinsic width and height of
141+
/// the original image. By default, the dimensions are clamped to avoid
142+
/// unnecessary memory usage for images. Callers that wish to display an image
143+
/// above its native resolution should prefer scaling the canvas the image is
144+
/// drawn into.
145+
Future<ui.Codec> instantiateImageCodecFromBuffer(
146+
ui.ImmutableBuffer buffer, {
147+
int? cacheWidth,
148+
int? cacheHeight,
149+
bool allowUpscaling = false,
150+
}) {
151+
assert(cacheWidth == null || cacheWidth > 0);
152+
assert(cacheHeight == null || cacheHeight > 0);
153+
assert(allowUpscaling != null);
154+
return ui.instantiateImageCodecFromBuffer(
155+
buffer,
156+
targetWidth: cacheWidth,
157+
targetHeight: cacheHeight,
158+
allowUpscaling: allowUpscaling,
159+
);
160+
}
161+
117162
@override
118163
void evict(String asset) {
119164
super.evict(asset);

0 commit comments

Comments
 (0)