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> { ...@@ -424,7 +424,7 @@ class FileImage extends ImageProvider<FileImage> {
if (other.runtimeType != runtimeType) if (other.runtimeType != runtimeType)
return false; return false;
final FileImage typedOther = other; final FileImage typedOther = other;
return file?.path == file?.path return file?.path == typedOther.file?.path
&& scale == typedOther.scale; && scale == typedOther.scale;
} }
...@@ -435,6 +435,58 @@ class FileImage extends ImageProvider<FileImage> { ...@@ -435,6 +435,58 @@ class FileImage extends ImageProvider<FileImage> {
String toString() => '$runtimeType("${file?.path}", scale: $scale)'; 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. /// Fetches an image from an [AssetBundle], associating it with the given scale.
/// ///
/// This implementation requires an explicit final [name] and [scale] on /// This implementation requires an explicit final [name] and [scale] on
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:io' show File, Platform; import 'dart:io' show File, Platform;
import 'dart:typed_data';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
...@@ -14,6 +15,7 @@ import 'media_query.dart'; ...@@ -14,6 +15,7 @@ import 'media_query.dart';
export 'package:flutter/services.dart' show export 'package:flutter/services.dart' show
AssetImage, AssetImage,
ExactAssetImage, ExactAssetImage,
MemoryImage,
NetworkImage, NetworkImage,
FileImage; FileImage;
...@@ -37,11 +39,12 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si ...@@ -37,11 +39,12 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si
/// Several constructors are provided for the various ways that an image can be /// Several constructors are provided for the various ways that an image can be
/// specified: /// specified:
/// ///
/// * [new Image], for obtaining an image from an [ImageProvider]. /// * [new Image], for obtaining an image from an [ImageProvider].
/// * [new Image.asset], for obtaining an image from an [AssetBundle] /// * [new Image.asset], for obtaining an image from an [AssetBundle]
/// using a key. /// using a key.
/// * [new Image.network], for obtaining an image from a URL. /// * [new Image.network], for obtaining an image from a URL.
/// * [new Image.file], for obtaining an image from a [File]. /// * [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 /// To automatically perform pixel-density-aware asset resolution, specify the
/// image using an [AssetImage] and make sure that a [MaterialApp], [WidgetsApp], /// image using an [AssetImage] and make sure that a [MaterialApp], [WidgetsApp],
...@@ -141,6 +144,23 @@ class Image extends StatefulWidget { ...@@ -141,6 +144,23 @@ class Image extends StatefulWidget {
: new AssetImage(name, bundle: bundle), : new AssetImage(name, bundle: bundle),
super(key: key); 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. /// The image to display.
final ImageProvider image; 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; ...@@ -8,17 +8,11 @@ import 'dart:ui' as ui;
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
const List<int> transparentImage = const <int>[ import 'image_data.dart';
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,
];
void main() { void main() {
test('Image decoder control test', () async { 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, isNotNull);
expect(image.width, 1); expect(image.width, 1);
expect(image.height, 1); expect(image.height, 1);
......
...@@ -3,6 +3,7 @@ ...@@ -3,6 +3,7 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:typed_data';
import 'dart:ui' as ui show Image; import 'dart:ui' as ui show Image;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -11,6 +12,8 @@ import 'package:flutter/services.dart'; ...@@ -11,6 +12,8 @@ import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import '../services/image_data.dart';
void main() { void main() {
testWidgets('Verify Image resets its RenderImage when changing providers', (WidgetTester tester) async { testWidgets('Verify Image resets its RenderImage when changing providers', (WidgetTester tester) async {
final GlobalKey key = new GlobalKey(); final GlobalKey key = new GlobalKey();
...@@ -299,6 +302,9 @@ void main() { ...@@ -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\)'))); 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> { 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