// Copyright 2015 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. import 'dart:async'; import 'dart:io' show File; import 'dart:typed_data'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'basic.dart'; import 'framework.dart'; import 'media_query.dart'; export 'package:flutter/services.dart' show ImageProvider, AssetImage, ExactAssetImage, MemoryImage, NetworkImage, FileImage; /// Creates an [ImageConfiguration] based on the given [BuildContext] (and /// optionally size). /// /// This is the object that must be passed to [BoxPainter.paint] and to /// [ImageProvider.resolve]. /// /// If this is not called from a build method, then it should be reinvoked /// whenever the dependencies change, e.g. by calling it from /// [State.didChangeDependencies], so that any changes in the environement are /// picked up (e.g. if the device pixel ratio changes). /// /// See also: /// /// * [ImageProvider], which has an example showing how this might be used. ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size size }) { return new ImageConfiguration( bundle: DefaultAssetBundle.of(context), devicePixelRatio: MediaQuery.of(context, nullOk: true)?.devicePixelRatio ?? 1.0, // TODO(ianh): provide the locale size: size, platform: defaultTargetPlatform, ); } /// Prefetches an image into the image cache. /// /// Returns a [Future] that will complete when the first image yielded by the /// [ImageProvider] is available. /// /// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage], /// it will probably be loaded faster. The consumer of the image does not need /// to use the same [ImageProvider] instance. The [ImageCache] will find the image /// as long as both images share the same key. /// /// The [BuildContext] and [Size] are used to select an image configuration /// (see [createLocalImageConfiguration]). /// /// See also: /// /// * [ImageCache], which holds images that may be reused. Future<Null> precacheImage(ImageProvider provider, BuildContext context, { Size size }) { final ImageConfiguration config = createLocalImageConfiguration(context, size: size); final Completer<Null> completer = new Completer<Null>(); final ImageStream stream = provider.resolve(config); void listener(ImageInfo image, bool sync) { completer.complete(); } stream.addListener(listener); completer.future.then((Null _) { stream.removeListener(listener); }); return completer.future; } /// A widget that displays an image. /// /// 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.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], /// or [MediaQuery] widget exists above the [Image] widget in the widget tree. /// /// The image is painted using [paintImage], which describes the meanings of the /// various fields on this class in more detail. /// /// See also: /// /// * [Icon] class Image extends StatefulWidget { /// Creates a widget that displays an image. /// /// To show an image from the network or from an asset bundle, consider using /// [new Image.network] and [new Image.asset] respectively. /// /// The [image] and [repeat] arguments must not be null. const Image({ Key key, @required this.image, this.width, this.height, this.color, this.colorBlendMode, this.fit, this.alignment, this.repeat: ImageRepeat.noRepeat, this.centerSlice, this.gaplessPlayback: false, this.package, }) : assert(image != null), super(key: key); /// Creates a widget that displays an [ImageStream] obtained from the network. /// /// The [src], [scale], and [repeat] arguments must not be null. Image.network(String src, { Key key, double scale: 1.0, this.width, this.height, this.color, this.colorBlendMode, this.fit, this.alignment, this.repeat: ImageRepeat.noRepeat, this.centerSlice, this.gaplessPlayback: false, this.package, }) : image = new NetworkImage(src, scale: scale), super(key: key); /// Creates a widget that displays an [ImageStream] obtained from a [File]. /// /// The [file], [scale], and [repeat] arguments must not be null. /// /// On Android, this may require the /// `android.permission.READ_EXTERNAL_STORAGE` permission. Image.file(File file, { Key key, double scale: 1.0, this.width, this.height, this.color, this.colorBlendMode, this.fit, this.alignment, this.repeat: ImageRepeat.noRepeat, this.centerSlice, this.gaplessPlayback: false, this.package, }) : image = new FileImage(file, scale: scale), super(key: key); /// Creates a widget that displays an [ImageStream] obtained from an asset /// bundle. The key for the image is given by the `name` argument. /// /// The `package` argument must be non-null when displaying an image from a /// package and null otherwise. See the `Assets in packages` section for /// details. /// /// If the `bundle` argument is omitted or null, then the /// [DefaultAssetBundle] will be used. /// /// By default, the pixel-density-aware asset resolution will be attempted. In /// addition: /// /// * If the `scale` argument is provided and is not null, then the exact /// asset specified will be used. // // TODO(ianh): Implement the following (see ../services/image_resolution.dart): // /// // /// * If [width] and [height] are both specified, and [scale] is not, then // /// size-aware asset resolution will be attempted also, with the given // /// dimensions interpreted as logical pixels. // /// // /// * If the images have platform or locale variants, the current platform // /// and locale is taken into account during asset resolution as well. /// /// The [name] and [repeat] arguments must not be null. /// /// ## Sample code /// /// Suppose that the project's `pubspec.yaml` file contains the following: /// /// ```yaml /// flutter: /// assets: /// - images/cat.png /// - images/2x/cat.png /// - images/3.5x/cat.png /// ``` /// /// On a screen with a device pixel ratio of 2.0, the following widget would /// render the `images/2x/cat.png` file: /// /// ```dart /// new Image.asset('images/cat.png') /// ``` /// /// This corresponds to the file that is in the project's `images/2x/` /// directory with the name `cat.png` (the paths are relative to the /// `pubspec.yaml` file). /// /// On a device with a 4.0 device pixel ratio, the `images/3.5x/cat.png` asset /// would be used. On a device with a 1.0 device pixel ratio, the /// `images/cat.png` resource would be used. /// /// The `images/cat.png` image can be omitted from disk (though it must still /// be present in the manifest). If it is omitted, then on a device with a 1.0 /// device pixel ratio, the `images/2x/cat.png` image would be used instead. /// /// /// ## Assets in packages /// /// To create the widget with an asset from a package, the [package] argument /// must be provided. For instance, suppose a package called `my_icons` has /// `icons/heart.png` . /// /// Then to display the image, use: /// /// ```dart /// new Image.asset('icons/heart.png', package: 'my_icons') /// ``` /// /// Assets used by the package itself should also be displayed using the /// [package] argument as above. /// /// If the desired asset is specified in the `pubspec.yaml` of the package, it /// is bundled automatically with the app. In particular, assets used by the /// package itself must be specified in its `pubspec.yaml`. /// /// A package can also choose to have assets in its 'lib/' folder that are not /// specified in its `pubspec.yaml`. In this case for those images to be /// bundled, the app has to specify which ones to include. For instance a /// package named `fancy_backgrounds` could have: /// /// ``` /// lib/backgrounds/background1.png /// lib/backgrounds/background2.png /// lib/backgrounds/background3.png ///``` /// /// To include, say the first image, the `pubspec.yaml` of the app should /// specify it in the assets section: /// /// ```yaml /// assets: /// - packages/fancy_backgrounds/backgrounds/background1.png /// ``` /// /// Note that the `lib/` is implied, so it should not be included in the asset /// path. /// /// /// See also: /// /// * [AssetImage], which is used to implement the behavior when the scale is /// omitted. /// * [ExactAssetImage], which is used to implement the behavior when the /// scale is present. /// * <https://flutter.io/assets-and-images/>, an introduction to assets in /// Flutter. Image.asset(String name, { Key key, AssetBundle bundle, double scale, this.width, this.height, this.color, this.colorBlendMode, this.fit, this.alignment, this.repeat: ImageRepeat.noRepeat, this.centerSlice, this.gaplessPlayback: false, this.package, }) : image = scale != null ? new ExactAssetImage(name, bundle: bundle, scale: scale, package: package) : new AssetImage(name, bundle: bundle, package: package), 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.colorBlendMode, this.fit, this.alignment, this.repeat: ImageRepeat.noRepeat, this.centerSlice, this.gaplessPlayback: false, this.package, }) : image = new MemoryImage(bytes, scale: scale), super(key: key); /// The image to display. final ImageProvider image; /// If non-null, require the image to have this width. /// /// If null, the image will pick a size that best preserves its intrinsic /// aspect ratio. final double width; /// If non-null, require the image to have this height. /// /// If null, the image will pick a size that best preserves its intrinsic /// aspect ratio. final double height; /// If non-null, this color is blended with each image pixel using [colorBlendMode]. final Color color; /// Used to combine [color] with this image. /// /// The default is [BlendMode.srcIn]. In terms of the blend mode, [color] is /// the source and this image is the destination. /// /// See also: /// /// * [BlendMode], which includes an illustration of the effect of each blend mode. final BlendMode colorBlendMode; /// How to inscribe the image into the space allocated during layout. /// /// The default varies based on the other fields. See the discussion at /// [paintImage]. final BoxFit fit; /// How to align the image within its bounds. /// /// An alignment of (0.0, 0.0) aligns the image to the top-left corner of its /// layout bounds. An alignment of (1.0, 0.5) aligns the image to the middle /// of the right edge of its layout bounds. final FractionalOffset alignment; /// How to paint any portions of the layout bounds not covered by the image. final ImageRepeat repeat; /// The center slice for a nine-patch image. /// /// The region of the image inside the center slice will be stretched both /// horizontally and vertically to fit the image into its destination. The /// region of the image above and below the center slice will be stretched /// only horizontally and the region of the image to the left and right of /// the center slice will be stretched only vertically. final Rect centerSlice; /// Whether to continue showing the old image (true), or briefly show nothing /// (false), when the image provider changes. final bool gaplessPlayback; /// The name of the package from which the image is included. See the /// documentation for the [Image.asset] constructor for details. final String package; @override _ImageState createState() => new _ImageState(); @override void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); description.add(new DiagnosticsProperty<ImageProvider>('image', image)); description.add(new DoubleProperty('width', width, defaultValue: null)); description.add(new DoubleProperty('height', height, defaultValue: null)); description.add(new DiagnosticsProperty<Color>('color', color, defaultValue: null)); description.add(new EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null)); description.add(new EnumProperty<BoxFit>('fit', fit, defaultValue: null)); description.add(new DiagnosticsProperty<FractionalOffset>('alignment', alignment, defaultValue: null)); description.add(new EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat)); description.add(new DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null)); } } class _ImageState extends State<Image> { ImageStream _imageStream; ImageInfo _imageInfo; @override void didChangeDependencies() { _resolveImage(); super.didChangeDependencies(); } @override void didUpdateWidget(Image oldWidget) { super.didUpdateWidget(oldWidget); if (widget.image != oldWidget.image) _resolveImage(); } @override void reassemble() { _resolveImage(); // in case the image cache was flushed super.reassemble(); } void _resolveImage() { final ImageStream oldImageStream = _imageStream; _imageStream = widget.image.resolve(createLocalImageConfiguration( context, size: widget.width != null && widget.height != null ? new Size(widget.width, widget.height) : null )); assert(_imageStream != null); if (_imageStream.key != oldImageStream?.key) { oldImageStream?.removeListener(_handleImageChanged); if (!widget.gaplessPlayback) setState(() { _imageInfo = null; }); _imageStream.addListener(_handleImageChanged); } } void _handleImageChanged(ImageInfo imageInfo, bool synchronousCall) { setState(() { _imageInfo = imageInfo; }); } @override void dispose() { assert(_imageStream != null); _imageStream.removeListener(_handleImageChanged); super.dispose(); } @override Widget build(BuildContext context) { return new RawImage( image: _imageInfo?.image, width: widget.width, height: widget.height, scale: _imageInfo?.scale ?? 1.0, color: widget.color, colorBlendMode: widget.colorBlendMode, fit: widget.fit, alignment: widget.alignment, repeat: widget.repeat, centerSlice: widget.centerSlice ); } @override void debugFillProperties(DiagnosticPropertiesBuilder description) { super.debugFillProperties(description); description.add(new DiagnosticsProperty<ImageStream>('stream', _imageStream)); description.add(new DiagnosticsProperty<ImageInfo>('pixels', _imageInfo)); } }