Commit e836a84e authored by Adam Barth's avatar Adam Barth Committed by GitHub

Add Image.memory for making images from typed data (#8877)

Fixes #7503
Fixes #8870
parent 9056ded2
......@@ -424,7 +424,7 @@ class FileImage extends ImageProvider<FileImage> {
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 @@ class FileImage extends ImageProvider<FileImage> {
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
......
......@@ -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 @@ import 'media_query.dart';
export 'package:flutter/services.dart' show
AssetImage,
ExactAssetImage,
MemoryImage,
NetworkImage,
FileImage;
......@@ -37,11 +39,12 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si
/// 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 @@ class Image extends StatefulWidget {
: 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;
......
// 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,
];
......@@ -8,17 +8,11 @@ import 'dart:ui' as ui;
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);
......
......@@ -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/services.dart';
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 @@ void main() {
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> {
......
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