image.dart 42 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5
import 'dart:async';
6
import 'dart:io' show File;
7
import 'dart:typed_data';
8

9
import 'package:flutter/foundation.dart';
10
import 'package:flutter/painting.dart';
11
import 'package:flutter/services.dart';
12
import 'package:flutter/semantics.dart';
13 14

import 'basic.dart';
15
import 'binding.dart';
16
import 'framework.dart';
Ian Hickson's avatar
Ian Hickson committed
17
import 'localizations.dart';
18
import 'media_query.dart';
19
import 'ticker_provider.dart';
20

21
export 'package:flutter/painting.dart' show
22 23
  AssetImage,
  ExactAssetImage,
24
  FileImage,
25
  FilterQuality,
26 27 28 29
  ImageConfiguration,
  ImageInfo,
  ImageStream,
  ImageProvider,
30
  MemoryImage,
31
  NetworkImage;
32

33 34 35 36 37
/// 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].
38 39 40
///
/// If this is not called from a build method, then it should be reinvoked
/// whenever the dependencies change, e.g. by calling it from
41
/// [State.didChangeDependencies], so that any changes in the environment are
42 43 44 45 46
/// picked up (e.g. if the device pixel ratio changes).
///
/// See also:
///
///  * [ImageProvider], which has an example showing how this might be used.
47
ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size size }) {
48
  return ImageConfiguration(
49
    bundle: DefaultAssetBundle.of(context),
50
    devicePixelRatio: MediaQuery.of(context, nullOk: true)?.devicePixelRatio ?? 1.0,
Ian Hickson's avatar
Ian Hickson committed
51 52
    locale: Localizations.localeOf(context, nullOk: true),
    textDirection: Directionality.of(context),
53
    size: size,
54
    platform: defaultTargetPlatform,
55 56 57
  );
}

58 59 60
/// Prefetches an image into the image cache.
///
/// Returns a [Future] that will complete when the first image yielded by the
61
/// [ImageProvider] is available or failed to load.
62 63
///
/// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage],
64 65
/// 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
66 67 68 69 70
/// as long as both images share the same key.
///
/// The [BuildContext] and [Size] are used to select an image configuration
/// (see [createLocalImageConfiguration]).
///
71 72
/// The `onError` argument can be used to manually handle errors while
/// pre-caching.
73
///
74 75
/// See also:
///
76
///  * [ImageCache], which holds images that may be reused.
77
Future<void> precacheImage(
78 79 80 81 82
  ImageProvider provider,
  BuildContext context, {
  Size size,
  ImageErrorListener onError,
}) {
83
  final ImageConfiguration config = createLocalImageConfiguration(context, size: size);
84
  final Completer<void> completer = Completer<void>();
85
  final ImageStream stream = provider.resolve(config);
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108
  ImageStreamListener listener;
  listener = ImageStreamListener(
    (ImageInfo image, bool sync) {
      completer.complete();
      stream.removeListener(listener);
    },
    onError: (dynamic exception, StackTrace stackTrace) {
      completer.complete();
      stream.removeListener(listener);
      if (onError != null) {
        onError(exception, stackTrace);
      } else {
        FlutterError.reportError(FlutterErrorDetails(
          context: ErrorDescription('image failed to precache'),
          library: 'image resource service',
          exception: exception,
          stack: stackTrace,
          silent: true,
        ));
      }
    },
  );
  stream.addListener(listener);
109 110 111
  return completer.future;
}

112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
/// Signature used by [Image.frameBuilder] to control the widget that will be
/// used when an [Image] is built.
///
/// The `child` argument contains the default image widget and is guaranteed to
/// be non-null. Typically, this builder will wrap the `child` widget in some
/// way and return the wrapped widget. If this builder returns `child` directly,
/// it will yield the same result as if [Image.frameBuilder] was null.
///
/// The `frame` argument specifies the index of the current image frame being
/// rendered. It will be null before the first image frame is ready, and zero
/// for the first image frame. For single-frame images, it will never be greater
/// than zero. For multi-frame images (such as animated GIFs), it will increase
/// by one every time a new image frame is shown (including when the image
/// animates in a loop).
///
/// The `wasSynchronouslyLoaded` argument specifies whether the image was
/// available synchronously (on the same
/// [rendering pipeline frame](rendering/RendererBinding/drawFrame.html) as the
/// `Image` widget itself was created) and thus able to be painted immediately.
/// If this is false, then there was one or more rendering pipeline frames where
/// the image wasn't yet available to be painted. For multi-frame images (such
/// as animated GIFs), the value of this argument will be the same for all image
/// frames. In other words, if the first image frame was available immediately,
/// then this argument will be true for all image frames.
///
/// This builder must not return null.
///
/// See also:
///
///  * [Image.frameBuilder], which makes use of this signature in the [Image]
///    widget.
typedef ImageFrameBuilder = Widget Function(
  BuildContext context,
  Widget child,
  int frame,
  bool wasSynchronouslyLoaded,
);

/// Signature used by [Image.loadingBuilder] to build a representation of the
/// image's loading progress.
///
/// This is useful for images that are incrementally loaded (e.g. over a local
/// file system or a network), and the application wishes to give the user an
/// indication of when the image will be displayed.
///
/// The `child` argument contains the default image widget and is guaranteed to
/// be non-null. Typically, this builder will wrap the `child` widget in some
/// way and return the wrapped widget. If this builder returns `child` directly,
/// it will yield the same result as if [Image.loadingBuilder] was null.
///
/// The `loadingProgress` argument contains the current progress towards loading
/// the image. This argument will be non-null while the image is loading, but it
/// will be null in the following cases:
///
///   * When the widget is first rendered before any bytes have been loaded.
///   * When an image has been fully loaded and is available to be painted.
///
/// If callers are implementing custom [ImageProvider] and [ImageStream]
/// instances (which is very rare), it's possible to produce image streams that
/// continue to fire image chunk events after an image frame has been loaded.
/// In such cases, the `child` parameter will represent the current
/// fully-loaded image frame.
///
/// This builder must not return null.
///
/// See also:
///
///  * [Image.loadingBuilder], which makes use of this signature in the [Image]
///    widget.
///  * [ImageChunkListener], a lower-level signature for listening to raw
///    [ImageChunkEvent]s.
typedef ImageLoadingBuilder = Widget Function(
  BuildContext context,
  Widget child,
  ImageChunkEvent loadingProgress,
);

189 190 191 192 193
/// A widget that displays an image.
///
/// Several constructors are provided for the various ways that an image can be
/// specified:
///
194 195 196 197 198 199
///  * [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].
200
///
201 202
/// The following image formats are supported: {@macro flutter.dart:ui.imageFormats}
///
203 204 205 206 207 208
/// 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.
209
///
210
/// {@tool snippet}
211 212 213 214 215 216 217 218 219 220 221 222
/// The default constructor can be used with any [ImageProvider], such as a
/// [NetworkImage], to display an image from the internet.
///
/// ![An image of an owl displayed by the image widget](https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg)
///
/// ```dart
/// const Image(
///   image: NetworkImage('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl.jpg'),
/// )
/// ```
/// {@end-tool}
///
223
/// {@tool snippet}
224 225 226 227 228 229 230 231 232 233 234
/// The [Image] Widget also provides several constructors to display different
/// types of images for convenience. In this example, use the [Image.network]
/// constructor to display an image from the internet.
///
/// ![An image of an owl displayed by the image widget using the shortcut constructor](https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg)
///
/// ```dart
/// Image.network('https://flutter.github.io/assets-for-api-docs/assets/widgets/owl-2.jpg')
/// ```
/// {@end-tool}
///
235 236 237 238 239 240 241 242 243 244 245
/// The [Image.asset], [Image.network], [Image.file], and [Image.memory]
/// constructors allow a custom decode size to be specified through
/// [cacheWidth] and [cacheHeight] parameters. The engine will decode the
/// image to the specified size, which is primarily intended to reduce the
/// memory usage of [ImageCache].
///
/// In the case where a network image is used on the Web platform, the
/// [cacheWidth] and [cacheHeight] parameters are ignored as the Web engine
/// delegates image decoding of network images to the Web, which does not support
/// custom decode sizes.
///
246 247
/// See also:
///
Ian Hickson's avatar
Ian Hickson committed
248 249 250 251
///  * [Icon], which shows an image from a font.
///  * [new Ink.image], which is the preferred way to show an image in a
///    material application (especially if the image is in a [Material] and will
///    have an [InkWell] on top of it).
252
///  * [Image](dart-ui/Image-class.html), the class in the [dart:ui] library.
253
///
254 255 256 257
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
258
  /// [new Image.network] and [new Image.asset] respectively.
259
  ///
Ian Hickson's avatar
Ian Hickson committed
260 261
  /// The [image], [alignment], [repeat], and [matchTextDirection] arguments
  /// must not be null.
Ian Hickson's avatar
Ian Hickson committed
262 263 264 265 266
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
267
  ///
268 269 270 271 272
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
273
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
274
  const Image({
275 276
    Key key,
    @required this.image,
277 278
    this.frameBuilder,
    this.loadingBuilder,
279 280
    this.semanticLabel,
    this.excludeFromSemantics = false,
281 282 283
    this.width,
    this.height,
    this.color,
284
    this.colorBlendMode,
285
    this.fit,
286 287
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
288
    this.centerSlice,
289 290
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
291
    this.filterQuality = FilterQuality.low,
292
  }) : assert(image != null),
Ian Hickson's avatar
Ian Hickson committed
293 294
       assert(alignment != null),
       assert(repeat != null),
295
       assert(filterQuality != null),
Ian Hickson's avatar
Ian Hickson committed
296
       assert(matchTextDirection != null),
297
       super(key: key);
298 299 300 301

  /// Creates a widget that displays an [ImageStream] obtained from the network.
  ///
  /// The [src], [scale], and [repeat] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
302 303 304 305 306
  ///
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
307 308
  ///
  /// All network images are cached regardless of HTTP headers.
Ian Hickson's avatar
Ian Hickson committed
309 310 311
  ///
  /// An optional [headers] argument can be used to send custom HTTP headers
  /// with the image request.
312
  ///
313 314 315 316 317
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
318
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
319 320 321 322 323 324 325 326 327 328 329 330 331
  ///
  /// If [cacheWidth] or [cacheHeight] are provided, it indicates to the
  /// engine that the image should be decoded at the specified size. The image
  /// will be rendered to the constraints of the layout or [width] and [height]
  /// regardless of these parameters. These parameters are primarily intended
  /// to reduce the memory usage of [ImageCache].
  ///
  /// In the case where the network image is on the Web platform, the [cacheWidth]
  /// and [cacheHeight] parameters are ignored as the web engine delegates
  /// image decoding to the web which does not support custom decode sizes.
  //
  // TODO(garyq): We should eventually support custom decoding of network images
  // on Web as well, see https://github.com/flutter/flutter/issues/42789.
332 333
  Image.network(
    String src, {
334
    Key key,
335
    double scale = 1.0,
336 337
    this.frameBuilder,
    this.loadingBuilder,
338 339
    this.semanticLabel,
    this.excludeFromSemantics = false,
340 341 342
    this.width,
    this.height,
    this.color,
343
    this.colorBlendMode,
344
    this.fit,
345 346
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
347
    this.centerSlice,
348 349
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
350
    this.filterQuality = FilterQuality.low,
351
    Map<String, String> headers,
352 353
    int cacheWidth,
    int cacheHeight,
354
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
Ian Hickson's avatar
Ian Hickson committed
355 356 357
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
358 359
       assert(cacheWidth == null || cacheWidth > 0),
       assert(cacheHeight == null || cacheHeight > 0),
360 361
       super(key: key);

Ian Hickson's avatar
Ian Hickson committed
362 363 364 365
  /// Creates a widget that displays an [ImageStream] obtained from a [File].
  ///
  /// The [file], [scale], and [repeat] arguments must not be null.
  ///
Ian Hickson's avatar
Ian Hickson committed
366 367 368 369 370
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
Ian Hickson's avatar
Ian Hickson committed
371 372
  /// On Android, this may require the
  /// `android.permission.READ_EXTERNAL_STORAGE` permission.
373
  ///
374 375 376 377 378
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
379
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
380 381 382 383 384 385
  ///
  /// If [cacheWidth] or [cacheHeight] are provided, it indicates to the
  /// engine that the image must be decoded at the specified size. The image
  /// will be rendered to the constraints of the layout or [width] and [height]
  /// regardless of these parameters. These parameters are primarily intended
  /// to reduce the memory usage of [ImageCache].
386 387
  Image.file(
    File file, {
Ian Hickson's avatar
Ian Hickson committed
388
    Key key,
389
    double scale = 1.0,
390
    this.frameBuilder,
391 392
    this.semanticLabel,
    this.excludeFromSemantics = false,
Ian Hickson's avatar
Ian Hickson committed
393 394 395
    this.width,
    this.height,
    this.color,
396
    this.colorBlendMode,
Ian Hickson's avatar
Ian Hickson committed
397
    this.fit,
398 399
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
Ian Hickson's avatar
Ian Hickson committed
400
    this.centerSlice,
401 402
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
403
    this.filterQuality = FilterQuality.low,
404 405
    int cacheWidth,
    int cacheHeight,
406
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
407
       loadingBuilder = null,
Ian Hickson's avatar
Ian Hickson committed
408 409
       assert(alignment != null),
       assert(repeat != null),
410
       assert(filterQuality != null),
Ian Hickson's avatar
Ian Hickson committed
411
       assert(matchTextDirection != null),
412 413
       assert(cacheWidth == null || cacheWidth > 0),
       assert(cacheHeight == null || cacheHeight > 0),
Ian Hickson's avatar
Ian Hickson committed
414 415
       super(key: key);

416 417 418 419 420 421 422 423 424 425

  // 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, locale, or directionality variants, the
  //   current platform, locale, and directionality are taken into account
  //   during asset resolution as well.
426 427 428
  /// Creates a widget that displays an [ImageStream] obtained from an asset
  /// bundle. The key for the image is given by the `name` argument.
  ///
429 430 431 432
  /// 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.
  ///
433 434 435
  /// If the `bundle` argument is omitted or null, then the
  /// [DefaultAssetBundle] will be used.
  ///
436 437
  /// By default, the pixel-density-aware asset resolution will be attempted. In
  /// addition:
438
  ///
439
  /// * If the `scale` argument is provided and is not null, then the exact
440 441
  /// asset specified will be used. To display an image variant with a specific
  /// density, the exact path must be provided (e.g. `images/2x/cat.png`).
442 443
  ///
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
444
  ///
445 446 447 448 449 450
  /// If [cacheWidth] or [cacheHeight] are provided, it indicates to the
  /// engine that the image must be decoded at the specified size. The image
  /// will be rendered to the constraints of the layout or [width] and [height]
  /// regardless of these parameters. These parameters are primarily intended
  /// to reduce the memory usage of [ImageCache].
  ///
451
  /// The [name] and [repeat] arguments must not be null.
452
  ///
Ian Hickson's avatar
Ian Hickson committed
453 454 455 456 457
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
  ///
458 459 460 461 462
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
463
  /// {@tool snippet}
464 465 466 467 468 469 470 471 472 473
  ///
  /// 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
  /// ```
474
  /// {@end-tool}
475 476 477 478 479
  ///
  /// On a screen with a device pixel ratio of 2.0, the following widget would
  /// render the `images/2x/cat.png` file:
  ///
  /// ```dart
480
  /// Image.asset('images/cat.png')
481 482 483 484 485 486
  /// ```
  ///
  /// 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).
  ///
487 488 489 490 491 492 493 494
  /// 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.
  ///
495 496 497 498 499 500 501
  ///
  /// ## 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` .
  ///
502
  /// {@tool snippet}
503 504 505
  /// Then to display the image, use:
  ///
  /// ```dart
506
  /// Image.asset('icons/heart.png', package: 'my_icons')
507
  /// ```
508
  /// {@end-tool}
509 510 511 512
  ///
  /// Assets used by the package itself should also be displayed using the
  /// [package] argument as above.
  ///
513
  /// If the desired asset is specified in the `pubspec.yaml` of the package, it
514
  /// is bundled automatically with the app. In particular, assets used by the
515
  /// package itself must be specified in its `pubspec.yaml`.
516 517
  ///
  /// A package can also choose to have assets in its 'lib/' folder that are not
518
  /// specified in its `pubspec.yaml`. In this case for those images to be
519 520 521 522 523 524 525
  /// 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
526
  /// ```
527
  ///
528
  /// To include, say the first image, the `pubspec.yaml` of the app should
529 530 531
  /// specify it in the assets section:
  ///
  /// ```yaml
532 533
  ///   assets:
  ///     - packages/fancy_backgrounds/backgrounds/background1.png
534 535
  /// ```
  ///
Ian Hickson's avatar
Ian Hickson committed
536
  /// The `lib/` is implied, so it should not be included in the asset path.
537 538
  ///
  ///
539 540 541 542 543 544
  /// 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.
545
  ///  * <https://flutter.dev/assets-and-images/>, an introduction to assets in
546
  ///    Flutter.
547 548
  Image.asset(
    String name, {
549 550
    Key key,
    AssetBundle bundle,
551
    this.frameBuilder,
552 553
    this.semanticLabel,
    this.excludeFromSemantics = false,
554 555 556 557
    double scale,
    this.width,
    this.height,
    this.color,
558
    this.colorBlendMode,
559
    this.fit,
560 561
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
562
    this.centerSlice,
563 564
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
Ian Hickson's avatar
Ian Hickson committed
565
    String package,
566
    this.filterQuality = FilterQuality.low,
567 568
    int cacheWidth,
    int cacheHeight,
569
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, scale != null
570
         ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
571 572
         : AssetImage(name, bundle: bundle, package: package)
       ),
573
       loadingBuilder = null,
Ian Hickson's avatar
Ian Hickson committed
574 575 576
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
577 578
       assert(cacheWidth == null || cacheWidth > 0),
       assert(cacheHeight == null || cacheHeight > 0),
Ian Hickson's avatar
Ian Hickson committed
579
       super(key: key);
580

581 582 583
  /// Creates a widget that displays an [ImageStream] obtained from a [Uint8List].
  ///
  /// The [bytes], [scale], and [repeat] arguments must not be null.
Ian Hickson's avatar
Ian Hickson committed
584
  ///
585 586 587 588
  /// This only accepts compressed image formats (e.g. PNG). Uncompressed
  /// formats like rawRgba (the default format of [ui.Image.toByteData]) will
  /// lead to exceptions.
  ///
Ian Hickson's avatar
Ian Hickson committed
589 590 591 592
  /// Either the [width] and [height] arguments should be specified, or the
  /// widget should be placed in a context that sets tight layout constraints.
  /// Otherwise, the image dimensions will change as the image is loaded, which
  /// will result in ugly layout changes.
593
  ///
594 595 596 597 598
  /// Use [filterQuality] to change the quality when scaling an image.
  /// Use the [FilterQuality.low] quality setting to scale the image,
  /// which corresponds to bilinear interpolation, rather than the default
  /// [FilterQuality.none] which corresponds to nearest-neighbor.
  ///
599
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
600 601 602 603 604 605
  ///
  /// If [cacheWidth] or [cacheHeight] are provided, it indicates to the
  /// engine that the image must be decoded at the specified size. The image
  /// will be rendered to the constraints of the layout or [width] and [height]
  /// regardless of these parameters. These parameters are primarily intended
  /// to reduce the memory usage of [ImageCache].
606 607
  Image.memory(
    Uint8List bytes, {
608
    Key key,
609
    double scale = 1.0,
610
    this.frameBuilder,
611 612
    this.semanticLabel,
    this.excludeFromSemantics = false,
613 614 615
    this.width,
    this.height,
    this.color,
616
    this.colorBlendMode,
617
    this.fit,
618 619
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
620
    this.centerSlice,
621 622
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
623
    this.filterQuality = FilterQuality.low,
624 625
    int cacheWidth,
    int cacheHeight,
626
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, MemoryImage(bytes, scale: scale)),
627
       loadingBuilder = null,
Ian Hickson's avatar
Ian Hickson committed
628 629 630
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
631 632
       assert(cacheWidth == null || cacheWidth > 0),
       assert(cacheHeight == null || cacheHeight > 0),
633 634
       super(key: key);

635 636 637
  /// The image to display.
  final ImageProvider image;

638 639 640 641 642 643 644 645 646 647 648 649 650 651 652 653 654 655 656 657 658 659 660 661 662 663 664 665 666 667 668 669 670 671 672 673 674 675 676 677 678 679 680 681 682 683 684
  /// A builder function responsible for creating the widget that represents
  /// this image.
  ///
  /// If this is null, this widget will display an image that is painted as
  /// soon as the first image frame is available (and will appear to "pop" in
  /// if it becomes available asynchronously). Callers might use this builder to
  /// add effects to the image (such as fading the image in when it becomes
  /// available) or to display a placeholder widget while the image is loading.
  ///
  /// To have finer-grained control over the way that an image's loading
  /// progress is communicated to the user, see [loadingBuilder].
  ///
  /// ## Chaining with [loadingBuilder]
  ///
  /// If a [loadingBuilder] has _also_ been specified for an image, the two
  /// builders will be chained together: the _result_ of this builder will
  /// be passed as the `child` argument to the [loadingBuilder]. For example,
  /// consider the following builders used in conjunction:
  ///
  /// {@template flutter.widgets.image.chainedBuildersExample}
  /// ```dart
  /// Image(
  ///   ...
  ///   frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) {
  ///     return Padding(
  ///       padding: EdgeInsets.all(8.0),
  ///       child: child,
  ///     );
  ///   },
  ///   loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) {
  ///     return Center(child: child);
  ///   },
  /// )
  /// ```
  ///
  /// In this example, the widget hierarchy will contain the following:
  ///
  /// ```dart
  /// Center(
  ///   Padding(
  ///     padding: EdgeInsets.all(8.0),
  ///     child: <image>,
  ///   ),
  /// )
  /// ```
  /// {@endtemplate}
  ///
685
  /// {@tool sample --template=stateless_widget_material}
686 687 688 689 690 691 692 693 694 695 696 697 698 699 700 701 702 703 704 705 706 707 708 709 710 711 712 713 714 715 716 717 718 719 720 721 722 723 724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744 745 746 747 748 749 750 751 752 753 754 755
  ///
  /// The following sample demonstrates how to use this builder to implement an
  /// image that fades in once it's been loaded.
  ///
  /// This sample contains a limited subset of the functionality that the
  /// [FadeInImage] widget provides out of the box.
  ///
  /// ```dart
  /// @override
  /// Widget build(BuildContext context) {
  ///   return DecoratedBox(
  ///     decoration: BoxDecoration(
  ///       color: Colors.white,
  ///       border: Border.all(),
  ///       borderRadius: BorderRadius.circular(20),
  ///     ),
  ///     child: Image.network(
  ///       'https://example.com/image.jpg',
  ///       frameBuilder: (BuildContext context, Widget child, int frame, bool wasSynchronouslyLoaded) {
  ///         if (wasSynchronouslyLoaded) {
  ///           return child;
  ///         }
  ///         return AnimatedOpacity(
  ///           child: child,
  ///           opacity: frame == null ? 0 : 1,
  ///           duration: const Duration(seconds: 1),
  ///           curve: Curves.easeOut,
  ///         );
  ///       },
  ///     ),
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  ///
  /// Run against a real-world image, the previous example renders the following
  /// image.
  ///
  /// {@animation 400 400 https://flutter.github.io/assets-for-api-docs/assets/widgets/frame_builder_image.mp4}
  final ImageFrameBuilder frameBuilder;

  /// A builder that specifies the widget to display to the user while an image
  /// is still loading.
  ///
  /// If this is null, and the image is loaded incrementally (e.g. over a
  /// network), the user will receive no indication of the progress as the
  /// bytes of the image are loaded.
  ///
  /// For more information on how to interpret the arguments that are passed to
  /// this builder, see the documentation on [ImageLoadingBuilder].
  ///
  /// ## Performance implications
  ///
  /// If a [loadingBuilder] is specified for an image, the [Image] widget is
  /// likely to be rebuilt on every
  /// [rendering pipeline frame](rendering/RendererBinding/drawFrame.html) until
  /// the image has loaded. This is useful for cases such as displaying a loading
  /// progress indicator, but for simpler cases such as displaying a placeholder
  /// widget that doesn't depend on the loading progress (e.g. static "loading"
  /// text), [frameBuilder] will likely work and not incur as much cost.
  ///
  /// ## Chaining with [frameBuilder]
  ///
  /// If a [frameBuilder] has _also_ been specified for an image, the two
  /// builders will be chained together: the `child` argument to this
  /// builder will contain the _result_ of the [frameBuilder]. For example,
  /// consider the following builders used in conjunction:
  ///
  /// {@macro flutter.widgets.image.chainedBuildersExample}
  ///
756
  /// {@tool sample --template=stateless_widget_material}
757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 783 784 785 786 787 788 789 790 791 792 793 794
  ///
  /// The following sample uses [loadingBuilder] to show a
  /// [CircularProgressIndicator] while an image loads over the network.
  ///
  /// ```dart
  /// Widget build(BuildContext context) {
  ///   return DecoratedBox(
  ///     decoration: BoxDecoration(
  ///       color: Colors.white,
  ///       border: Border.all(),
  ///       borderRadius: BorderRadius.circular(20),
  ///     ),
  ///     child: Image.network(
  ///       'https://example.com/image.jpg',
  ///       loadingBuilder: (BuildContext context, Widget child, ImageChunkEvent loadingProgress) {
  ///         if (loadingProgress == null)
  ///           return child;
  ///         return Center(
  ///           child: CircularProgressIndicator(
  ///             value: loadingProgress.expectedTotalBytes != null
  ///                 ? loadingProgress.cumulativeBytesLoaded / loadingProgress.expectedTotalBytes
  ///                 : null,
  ///           ),
  ///         );
  ///       },
  ///     ),
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  ///
  /// Run against a real-world image, the previous example renders the following
  /// loading progress indicator while the image loads before rendering the
  /// completed image.
  ///
  /// {@animation 400 400 https://flutter.github.io/assets-for-api-docs/assets/widgets/loading_progress_image.mp4}
  final ImageLoadingBuilder loadingBuilder;

795 796 797 798
  /// 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.
Ian Hickson's avatar
Ian Hickson committed
799 800 801 802 803 804
  ///
  /// It is strongly recommended that either both the [width] and the [height]
  /// be specified, or that the widget be placed in a context that sets tight
  /// layout constraints, so that the image does not change size as it loads.
  /// Consider using [fit] to adapt the image's rendering to fit the given width
  /// and height if the exact image dimensions are not known in advance.
805 806 807 808 809 810
  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.
Ian Hickson's avatar
Ian Hickson committed
811 812 813 814 815 816
  ///
  /// It is strongly recommended that either both the [width] and the [height]
  /// be specified, or that the widget be placed in a context that sets tight
  /// layout constraints, so that the image does not change size as it loads.
  /// Consider using [fit] to adapt the image's rendering to fit the given width
  /// and height if the exact image dimensions are not known in advance.
817 818
  final double height;

819
  /// If non-null, this color is blended with each image pixel using [colorBlendMode].
820 821
  final Color color;

822 823 824 825
  /// Used to set the [FilterQuality] of the image.
  ///
  /// Use the [FilterQuality.low] quality setting to scale the image with
  /// bilinear interpolation, or the [FilterQuality.none] which corresponds
826 827 828
  /// to nearest-neighbor.
  final FilterQuality filterQuality;

829 830 831 832 833 834 835 836 837 838
  /// 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;

Adam Barth's avatar
Adam Barth committed
839
  /// How to inscribe the image into the space allocated during layout.
840 841 842
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
843
  final BoxFit fit;
844 845 846

  /// How to align the image within its bounds.
  ///
Ian Hickson's avatar
Ian Hickson committed
847
  /// The alignment aligns the given position in the image to the given position
848 849
  /// in the layout bounds. For example, an [Alignment] alignment of (-1.0,
  /// -1.0) aligns the image to the top-left corner of its layout bounds, while an
850
  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
Ian Hickson's avatar
Ian Hickson committed
851
  /// image with the bottom right corner of its layout bounds. Similarly, an
852
  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
Ian Hickson's avatar
Ian Hickson committed
853 854 855 856 857 858
  /// middle of the bottom edge of its layout bounds.
  ///
  /// To display a subpart of an image, consider using a [CustomPainter] and
  /// [Canvas.drawImageRect].
  ///
  /// If the [alignment] is [TextDirection]-dependent (i.e. if it is a
859
  /// [AlignmentDirectional]), then an ambient [Directionality] widget
Ian Hickson's avatar
Ian Hickson committed
860 861
  /// must be in scope.
  ///
862
  /// Defaults to [Alignment.center].
863 864 865 866 867 868 869
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
870
  final AlignmentGeometry alignment;
871 872 873 874 875 876 877 878 879 880 881 882 883

  /// 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;

Ian Hickson's avatar
Ian Hickson committed
884 885 886 887 888 889 890 891 892 893 894 895 896 897 898 899 900
  /// Whether to paint the image in the direction of the [TextDirection].
  ///
  /// If this is true, then in [TextDirection.ltr] contexts, the image will be
  /// drawn with its origin in the top left (the "normal" painting direction for
  /// images); and in [TextDirection.rtl] contexts, the image will be drawn with
  /// a scaling factor of -1 in the horizontal direction so that the origin is
  /// in the top right.
  ///
  /// This is occasionally used with images in right-to-left environments, for
  /// images that were designed for left-to-right locales. Be careful, when
  /// using this, to not flip images with integral shadows, text, or other
  /// effects that will look incorrect when flipped.
  ///
  /// If this is true, there must be an ambient [Directionality] widget in
  /// scope.
  final bool matchTextDirection;

901 902 903 904
  /// Whether to continue showing the old image (true), or briefly show nothing
  /// (false), when the image provider changes.
  final bool gaplessPlayback;

905 906
  /// A Semantic description of the image.
  ///
907
  /// Used to provide a description of the image to TalkBack on Android, and
908 909 910 911 912 913 914 915 916
  /// VoiceOver on iOS.
  final String semanticLabel;

  /// Whether to exclude this image from semantics.
  ///
  /// Useful for images which do not contribute meaningful information to an
  /// application.
  final bool excludeFromSemantics;

917
  @override
918
  _ImageState createState() => _ImageState();
919 920

  @override
921 922
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
923
    properties.add(DiagnosticsProperty<ImageProvider>('image', image));
924 925
    properties.add(DiagnosticsProperty<Function>('frameBuilder', frameBuilder));
    properties.add(DiagnosticsProperty<Function>('loadingBuilder', loadingBuilder));
926 927
    properties.add(DoubleProperty('width', width, defaultValue: null));
    properties.add(DoubleProperty('height', height, defaultValue: null));
928
    properties.add(ColorProperty('color', color, defaultValue: null));
929 930 931 932 933 934 935 936
    properties.add(EnumProperty<BlendMode>('colorBlendMode', colorBlendMode, defaultValue: null));
    properties.add(EnumProperty<BoxFit>('fit', fit, defaultValue: null));
    properties.add(DiagnosticsProperty<AlignmentGeometry>('alignment', alignment, defaultValue: null));
    properties.add(EnumProperty<ImageRepeat>('repeat', repeat, defaultValue: ImageRepeat.noRepeat));
    properties.add(DiagnosticsProperty<Rect>('centerSlice', centerSlice, defaultValue: null));
    properties.add(FlagProperty('matchTextDirection', value: matchTextDirection, ifTrue: 'match text direction'));
    properties.add(StringProperty('semanticLabel', semanticLabel, defaultValue: null));
    properties.add(DiagnosticsProperty<bool>('this.excludeFromSemantics', excludeFromSemantics));
937
    properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
938 939 940
  }
}

941
class _ImageState extends State<Image> with WidgetsBindingObserver {
942 943
  ImageStream _imageStream;
  ImageInfo _imageInfo;
944
  ImageChunkEvent _loadingProgress;
945
  bool _isListeningToStream = false;
946
  bool _invertColors;
947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962
  int _frameNumber;
  bool _wasSynchronouslyLoaded;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
  }

  @override
  void dispose() {
    assert(_imageStream != null);
    WidgetsBinding.instance.removeObserver(this);
    _stopListeningToStream();
    super.dispose();
  }
963 964

  @override
965
  void didChangeDependencies() {
966
    _updateInvertColors();
967
    _resolveImage();
968 969 970 971 972 973

    if (TickerMode.of(context))
      _listenToStream();
    else
      _stopListeningToStream();

974
    super.didChangeDependencies();
975 976
  }

977
  @override
978
  void didUpdateWidget(Image oldWidget) {
979
    super.didUpdateWidget(oldWidget);
980 981 982 983 984
    if (_isListeningToStream &&
        (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
      _imageStream.removeListener(_getListener(oldWidget.loadingBuilder));
      _imageStream.addListener(_getListener());
    }
985
    if (widget.image != oldWidget.image)
986 987 988
      _resolveImage();
  }

989 990 991 992 993 994 995 996
  @override
  void didChangeAccessibilityFeatures() {
    super.didChangeAccessibilityFeatures();
    setState(() {
      _updateInvertColors();
    });
  }

997 998
  @override
  void reassemble() {
999
    _resolveImage(); // in case the image cache was flushed
1000 1001 1002
    super.reassemble();
  }

1003 1004 1005 1006 1007
  void _updateInvertColors() {
    _invertColors = MediaQuery.of(context, nullOk: true)?.invertColors
        ?? SemanticsBinding.instance.accessibilityFeatures.invertColors;
  }

1008
  void _resolveImage() {
1009 1010
    final ImageStream newStream =
      widget.image.resolve(createLocalImageConfiguration(
1011 1012
        context,
        size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,
1013 1014 1015
      ));
    assert(newStream != null);
    _updateSourceStream(newStream);
1016 1017
  }

1018 1019 1020 1021 1022 1023 1024 1025 1026
  ImageStreamListener _getListener([ImageLoadingBuilder loadingBuilder]) {
    loadingBuilder ??= widget.loadingBuilder;
    return ImageStreamListener(
      _handleImageFrame,
      onChunk: loadingBuilder == null ? null : _handleImageChunk,
    );
  }

  void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
1027 1028
    setState(() {
      _imageInfo = imageInfo;
1029 1030 1031 1032 1033 1034 1035 1036 1037 1038
      _loadingProgress = null;
      _frameNumber = _frameNumber == null ? 0 : _frameNumber + 1;
      _wasSynchronouslyLoaded |= synchronousCall;
    });
  }

  void _handleImageChunk(ImageChunkEvent event) {
    assert(widget.loadingBuilder != null);
    setState(() {
      _loadingProgress = event;
1039 1040 1041
    });
  }

1042
  // Updates _imageStream to newStream, and moves the stream listener
1043 1044 1045 1046 1047 1048 1049
  // registration from the old stream to the new stream (if a listener was
  // registered).
  void _updateSourceStream(ImageStream newStream) {
    if (_imageStream?.key == newStream?.key)
      return;

    if (_isListeningToStream)
1050
      _imageStream.removeListener(_getListener());
1051

1052 1053 1054
    if (!widget.gaplessPlayback)
      setState(() { _imageInfo = null; });

1055 1056 1057 1058 1059 1060
    setState(() {
      _loadingProgress = null;
      _frameNumber = null;
      _wasSynchronouslyLoaded = false;
    });

1061 1062
    _imageStream = newStream;
    if (_isListeningToStream)
1063
      _imageStream.addListener(_getListener());
1064 1065 1066 1067 1068
  }

  void _listenToStream() {
    if (_isListeningToStream)
      return;
1069
    _imageStream.addListener(_getListener());
1070 1071 1072 1073 1074 1075
    _isListeningToStream = true;
  }

  void _stopListeningToStream() {
    if (!_isListeningToStream)
      return;
1076
    _imageStream.removeListener(_getListener());
1077 1078 1079
    _isListeningToStream = false;
  }

1080 1081
  @override
  Widget build(BuildContext context) {
1082
    Widget result = RawImage(
1083
      image: _imageInfo?.image,
1084 1085
      width: widget.width,
      height: widget.height,
1086
      scale: _imageInfo?.scale ?? 1.0,
1087
      color: widget.color,
1088
      colorBlendMode: widget.colorBlendMode,
1089 1090 1091
      fit: widget.fit,
      alignment: widget.alignment,
      repeat: widget.repeat,
Ian Hickson's avatar
Ian Hickson committed
1092 1093
      centerSlice: widget.centerSlice,
      matchTextDirection: widget.matchTextDirection,
1094
      invertColors: _invertColors,
1095
      filterQuality: widget.filterQuality,
1096
    );
1097 1098 1099 1100 1101 1102 1103 1104 1105 1106 1107 1108 1109 1110 1111 1112 1113

    if (!widget.excludeFromSemantics) {
      result = Semantics(
        container: widget.semanticLabel != null,
        image: true,
        label: widget.semanticLabel ?? '',
        child: result,
      );
    }

    if (widget.frameBuilder != null)
      result = widget.frameBuilder(context, result, _frameNumber, _wasSynchronouslyLoaded);

    if (widget.loadingBuilder != null)
      result = widget.loadingBuilder(context, result, _loadingProgress);

    return result;
1114
  }
1115 1116

  @override
1117
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
1118
    super.debugFillProperties(description);
1119 1120
    description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream));
    description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo));
1121 1122 1123
    description.add(DiagnosticsProperty<ImageChunkEvent>('loadingProgress', _loadingProgress));
    description.add(DiagnosticsProperty<int>('frameNumber', _frameNumber));
    description.add(DiagnosticsProperty<bool>('wasSynchronouslyLoaded', _wasSynchronouslyLoaded));
1124
  }
1125
}