From e836a84e30760179bfcc85755ac90840f957e6ea Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Mon, 20 Mar 2017 11:14:41 -0700 Subject: [PATCH] Add Image.memory for making images from typed data (#8877) Fixes #7503 Fixes #8870 --- .../lib/src/services/image_provider.dart | 54 ++++++++++++++++++- packages/flutter/lib/src/widgets/image.dart | 30 +++++++++-- .../flutter/test/services/image_data.dart | 11 ++++ .../test/services/image_decoder_test.dart | 10 +--- packages/flutter/test/widgets/image_test.dart | 6 +++ 5 files changed, 97 insertions(+), 14 deletions(-) create mode 100644 packages/flutter/test/services/image_data.dart diff --git a/packages/flutter/lib/src/services/image_provider.dart b/packages/flutter/lib/src/services/image_provider.dart index ab837124f5..6148184845 100644 --- a/packages/flutter/lib/src/services/image_provider.dart +++ b/packages/flutter/lib/src/services/image_provider.dart @@ -424,7 +424,7 @@ class FileImage extends ImageProvider { 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 { 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 { + /// 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 obtainKey(ImageConfiguration configuration) { + return new SynchronousFuture(this); + } + + @override + ImageStreamCompleter load(MemoryImage key) { + return new OneFrameImageStreamCompleter(_loadAsync(key)); + } + + Future _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 85b668ce99..322ba71cc5 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 @@ 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; diff --git a/packages/flutter/test/services/image_data.dart b/packages/flutter/test/services/image_data.dart new file mode 100644 index 0000000000..02680e2c37 --- /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 kTransparentImage = const [ + 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 d0c28a7dec..28e388af63 100644 --- a/packages/flutter/test/services/image_decoder_test.dart +++ b/packages/flutter/test/services/image_decoder_test.dart @@ -8,17 +8,11 @@ import 'dart:ui' as ui; import 'package:flutter/services.dart'; import 'package:test/test.dart'; -const List transparentImage = const [ - 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 9146e40b60..572f1b194d 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/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 {