Add Image.memory for making images from typed data (#8877)
Fixes #7503
Fixes #8870
diff --git a/packages/flutter/lib/src/services/image_provider.dart b/packages/flutter/lib/src/services/image_provider.dart
index ab83712..6148184 100644
--- a/packages/flutter/lib/src/services/image_provider.dart
+++ b/packages/flutter/lib/src/services/image_provider.dart
@@ -424,7 +424,7 @@
if (other.runtimeType != runtimeType)
return false;
final FileImage typedOther = other;
- return file?.path == file?.path
+ return file?.path == typedOther.file?.path
&& scale == typedOther.scale;
}
@@ -435,6 +435,58 @@
String toString() => '$runtimeType("${file?.path}", scale: $scale)';
}
+/// Decodes the given [Uint8List] buffer as an image, associating it with the
+/// given scale.
+class MemoryImage extends ImageProvider<MemoryImage> {
+ /// Creates an object that decodes a [Uint8List] buffer as an image.
+ ///
+ /// The arguments must not be null.
+ const MemoryImage(this.bytes, { this.scale: 1.0 });
+
+ /// The bytes to decode into an image.
+ final Uint8List bytes;
+
+ /// The scale to place in the [ImageInfo] object of the image.
+ final double scale;
+
+ @override
+ Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
+ return new SynchronousFuture<MemoryImage>(this);
+ }
+
+ @override
+ ImageStreamCompleter load(MemoryImage key) {
+ return new OneFrameImageStreamCompleter(_loadAsync(key));
+ }
+
+ Future<ImageInfo> _loadAsync(MemoryImage key) async {
+ assert(key == this);
+
+ final ui.Image image = await decodeImageFromList(bytes);
+ if (image == null)
+ return null;
+
+ return new ImageInfo(
+ image: image,
+ scale: key.scale,
+ );
+ }
+
+ @override
+ bool operator ==(dynamic other) {
+ if (other.runtimeType != runtimeType)
+ return false;
+ final MemoryImage typedOther = other;
+ return bytes == typedOther.bytes
+ && scale == typedOther.scale;
+ }
+
+ @override
+ int get hashCode => hashValues(bytes.hashCode, scale);
+
+ @override
+ String toString() => '$runtimeType(${bytes.runtimeType}#${bytes.hashCode}, scale: $scale)';
+}
/// Fetches an image from an [AssetBundle], associating it with the given scale.
///
/// This implementation requires an explicit final [name] and [scale] on
diff --git a/packages/flutter/lib/src/widgets/image.dart b/packages/flutter/lib/src/widgets/image.dart
index 85b668c..322ba71 100644
--- a/packages/flutter/lib/src/widgets/image.dart
+++ b/packages/flutter/lib/src/widgets/image.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:io' show File, Platform;
+import 'dart:typed_data';
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
@@ -14,6 +15,7 @@
export 'package:flutter/services.dart' show
AssetImage,
ExactAssetImage,
+ MemoryImage,
NetworkImage,
FileImage;
@@ -37,11 +39,12 @@
/// Several constructors are provided for the various ways that an image can be
/// specified:
///
-/// * [new Image], for obtaining an image from an [ImageProvider].
-/// * [new Image.asset], for obtaining an image from an [AssetBundle]
-/// using a key.
-/// * [new Image.network], for obtaining an image from a URL.
-/// * [new Image.file], for obtaining an image from a [File].
+/// * [new Image], for obtaining an image from an [ImageProvider].
+/// * [new Image.asset], for obtaining an image from an [AssetBundle]
+/// using a key.
+/// * [new Image.network], for obtaining an image from a URL.
+/// * [new Image.file], for obtaining an image from a [File].
+/// * [new Image.memory], for obtaining an image from a [Uint8List].
///
/// To automatically perform pixel-density-aware asset resolution, specify the
/// image using an [AssetImage] and make sure that a [MaterialApp], [WidgetsApp],
@@ -141,6 +144,23 @@
: new AssetImage(name, bundle: bundle),
super(key: key);
+ /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
+ ///
+ /// The [bytes], [scale], and [repeat] arguments must not be null.
+ Image.memory(Uint8List bytes, {
+ Key key,
+ double scale: 1.0,
+ this.width,
+ this.height,
+ this.color,
+ this.fit,
+ this.alignment,
+ this.repeat: ImageRepeat.noRepeat,
+ this.centerSlice,
+ this.gaplessPlayback: false
+ }) : image = new MemoryImage(bytes, scale: scale),
+ super(key: key);
+
/// The image to display.
final ImageProvider image;
diff --git a/packages/flutter/test/services/image_data.dart b/packages/flutter/test/services/image_data.dart
new file mode 100644
index 0000000..02680e2
--- /dev/null
+++ b/packages/flutter/test/services/image_data.dart
@@ -0,0 +1,11 @@
+// Copyright 2016 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+
+const List<int> kTransparentImage = const <int>[
+ 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49,
+ 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,
+ 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44,
+ 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D,
+ 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
+];
diff --git a/packages/flutter/test/services/image_decoder_test.dart b/packages/flutter/test/services/image_decoder_test.dart
index d0c28a7..28e388a 100644
--- a/packages/flutter/test/services/image_decoder_test.dart
+++ b/packages/flutter/test/services/image_decoder_test.dart
@@ -8,17 +8,11 @@
import 'package:flutter/services.dart';
import 'package:test/test.dart';
-const List<int> transparentImage = const <int>[
- 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A, 0x00, 0x00, 0x00, 0x0D, 0x49,
- 0x48, 0x44, 0x52, 0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x01, 0x08, 0x06,
- 0x00, 0x00, 0x00, 0x1F, 0x15, 0xC4, 0x89, 0x00, 0x00, 0x00, 0x0A, 0x49, 0x44,
- 0x41, 0x54, 0x78, 0x9C, 0x63, 0x00, 0x01, 0x00, 0x00, 0x05, 0x00, 0x01, 0x0D,
- 0x0A, 0x2D, 0xB4, 0x00, 0x00, 0x00, 0x00, 0x49, 0x45, 0x4E, 0x44, 0xAE,
-];
+import 'image_data.dart';
void main() {
test('Image decoder control test', () async {
- final ui.Image image = await decodeImageFromList(new Uint8List.fromList(transparentImage));
+ final ui.Image image = await decodeImageFromList(new Uint8List.fromList(kTransparentImage));
expect(image, isNotNull);
expect(image.width, 1);
expect(image.height, 1);
diff --git a/packages/flutter/test/widgets/image_test.dart b/packages/flutter/test/widgets/image_test.dart
index 9146e40..572f1b1 100644
--- a/packages/flutter/test/widgets/image_test.dart
+++ b/packages/flutter/test/widgets/image_test.dart
@@ -3,6 +3,7 @@
// found in the LICENSE file.
import 'dart:async';
+import 'dart:typed_data';
import 'dart:ui' as ui show Image;
import 'package:flutter/foundation.dart';
@@ -11,6 +12,8 @@
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
+import '../services/image_data.dart';
+
void main() {
testWidgets('Verify Image resets its RenderImage when changing providers', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey();
@@ -299,6 +302,9 @@
expect(image.toString(), matches(new RegExp(r'_ImageState#[0-9]+\(_StateLifecycle.defunct; not mounted; stream: ImageStream\(OneFrameImageStreamCompleter; \[100×100\] @ 1\.0x; 0 listeners\); pixels: \[100×100\] @ 1\.0x\)')));
});
+ testWidgets('Image.memory control test', (WidgetTester tester) async {
+ await tester.pumpWidget(new Image.memory(new Uint8List.fromList(kTransparentImage)));
+ });
}
class TestImageProvider extends ImageProvider<TestImageProvider> {