image.dart 46.8 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';
Dan Field's avatar
Dan Field committed
11
import 'package:flutter/scheduler.dart';
12
import 'package:flutter/services.dart';
13
import 'package:flutter/semantics.dart';
14 15

import 'basic.dart';
16
import 'binding.dart';
17
import 'disposable_build_context.dart';
18
import 'framework.dart';
Ian Hickson's avatar
Ian Hickson committed
19
import 'localizations.dart';
20
import 'media_query.dart';
21
import 'scroll_aware_image_provider.dart';
22
import 'ticker_provider.dart';
23

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

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

61 62 63
/// Prefetches an image into the image cache.
///
/// Returns a [Future] that will complete when the first image yielded by the
64
/// [ImageProvider] is available or failed to load.
65 66
///
/// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage],
67 68
/// 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
Dan Field's avatar
Dan Field committed
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
/// as long as both images share the same key, and the image is held by the
/// cache.
///
/// The cache may refuse to hold the image if it is disabled, the image is too
/// large, or some other criteria implemented by a custom [ImageCache]
/// implementation.
///
/// The [ImageCache] holds a reference to all images passed to [putIfAbsent] as
/// long as their [ImageStreamCompleter] has at least one listener. This method
/// will wait until the end of the frame after its future completes before
/// releasing its own listener. This gives callers a chance to listen to the
/// stream if necessary. A caller can determine if the image ended up in the
/// cache by calling [ImageProvider.obtainCacheStatus]. If it is only held as
/// [ImageCacheStatus.live], and the caller wishes to keep the resolved
/// image in memory, the caller should immediately call `provider.resolve` and
/// add a listener to the returned [ImageStream]. The image will remain pinned
/// in memory at least until the caller removes its listener from the stream,
/// even if it would not otherwise fit into the cache.
///
/// Callers should be cautious about pinning large images or a large number of
/// images in memory, as this can result in running out of memory and being
/// killed by the operating system. The lower the avilable physical memory, the
/// more susceptible callers will be to running into OOM issues. These issues
/// manifest as immediate process death, sometimes with no other error messages.
93 94 95 96
///
/// The [BuildContext] and [Size] are used to select an image configuration
/// (see [createLocalImageConfiguration]).
///
97 98
/// The `onError` argument can be used to manually handle errors while
/// pre-caching.
99
///
100 101
/// See also:
///
102
///  * [ImageCache], which holds images that may be reused.
103
Future<void> precacheImage(
104 105 106 107 108
  ImageProvider provider,
  BuildContext context, {
  Size size,
  ImageErrorListener onError,
}) {
109
  final ImageConfiguration config = createLocalImageConfiguration(context, size: size);
110
  final Completer<void> completer = Completer<void>();
111
  final ImageStream stream = provider.resolve(config);
112 113 114
  ImageStreamListener listener;
  listener = ImageStreamListener(
    (ImageInfo image, bool sync) {
115 116 117
      if (!completer.isCompleted) {
        completer.complete();
      }
Dan Field's avatar
Dan Field committed
118 119 120 121 122 123
      // Give callers until at least the end of the frame to subscribe to the
      // image stream.
      // See ImageCache._liveImages
      SchedulerBinding.instance.addPostFrameCallback((Duration timeStamp) {
        stream.removeListener(listener);
      });
124 125
    },
    onError: (dynamic exception, StackTrace stackTrace) {
126 127 128
      if (!completer.isCompleted) {
        completer.complete();
      }
129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
      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);
144 145 146
  return completer.future;
}

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 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223
/// 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,
);

224 225 226 227 228 229 230 231
/// Signature used by [Image.errorBuilder] to create a replacement widget to
/// render instead of the image.
typedef ImageErrorWidgetBuilder = Widget Function(
  BuildContext context,
  Object error,
  StackTrace stackTrace,
);

232 233 234 235 236
/// A widget that displays an image.
///
/// Several constructors are provided for the various ways that an image can be
/// specified:
///
237 238 239 240 241 242
///  * [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].
243
///
244 245
/// The following image formats are supported: {@macro flutter.dart:ui.imageFormats}
///
246 247 248 249 250 251
/// 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.
252
///
253
/// {@tool snippet}
254 255 256 257 258 259 260 261 262 263 264 265
/// 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}
///
266
/// {@tool snippet}
267 268 269 270 271 272 273 274 275 276 277
/// 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}
///
278 279 280 281 282 283 284 285 286 287 288
/// 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.
///
289 290
/// See also:
///
Ian Hickson's avatar
Ian Hickson committed
291 292 293 294
///  * [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).
295
///  * [Image](dart-ui/Image-class.html), the class in the [dart:ui] library.
296
///
297 298 299 300
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
301
  /// [new Image.network] and [new Image.asset] respectively.
302
  ///
Ian Hickson's avatar
Ian Hickson committed
303 304
  /// The [image], [alignment], [repeat], and [matchTextDirection] arguments
  /// must not be null.
Ian Hickson's avatar
Ian Hickson committed
305 306 307 308 309
  ///
  /// 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.
310
  ///
311 312 313 314 315
  /// 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.
  ///
316
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
317
  const Image({
318 319
    Key key,
    @required this.image,
320 321
    this.frameBuilder,
    this.loadingBuilder,
322
    this.errorBuilder,
323 324
    this.semanticLabel,
    this.excludeFromSemantics = false,
325 326 327
    this.width,
    this.height,
    this.color,
328
    this.colorBlendMode,
329
    this.fit,
330 331
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
332
    this.centerSlice,
333 334
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
335
    this.isAntiAlias = false,
336
    this.filterQuality = FilterQuality.low,
337
  }) : assert(image != null),
Ian Hickson's avatar
Ian Hickson committed
338 339
       assert(alignment != null),
       assert(repeat != null),
340
       assert(filterQuality != null),
Ian Hickson's avatar
Ian Hickson committed
341
       assert(matchTextDirection != null),
342
       assert(isAntiAlias != null),
343
       super(key: key);
344 345 346 347

  /// 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
348 349 350 351 352
  ///
  /// 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.
353 354
  ///
  /// All network images are cached regardless of HTTP headers.
Ian Hickson's avatar
Ian Hickson committed
355 356 357
  ///
  /// An optional [headers] argument can be used to send custom HTTP headers
  /// with the image request.
358
  ///
359 360 361 362 363
  /// 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.
  ///
364
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
365 366 367 368 369 370 371 372 373 374 375 376 377
  ///
  /// 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.
378 379
  Image.network(
    String src, {
380
    Key key,
381
    double scale = 1.0,
382 383
    this.frameBuilder,
    this.loadingBuilder,
384
    this.errorBuilder,
385 386
    this.semanticLabel,
    this.excludeFromSemantics = false,
387 388 389
    this.width,
    this.height,
    this.color,
390
    this.colorBlendMode,
391
    this.fit,
392 393
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
394
    this.centerSlice,
395 396
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
397
    this.filterQuality = FilterQuality.low,
398
    this.isAntiAlias = false,
399
    Map<String, String> headers,
400 401
    int cacheWidth,
    int cacheHeight,
402
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, NetworkImage(src, scale: scale, headers: headers)),
Ian Hickson's avatar
Ian Hickson committed
403 404 405
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
406 407
       assert(cacheWidth == null || cacheWidth > 0),
       assert(cacheHeight == null || cacheHeight > 0),
408
       assert(isAntiAlias != null),
409 410
       super(key: key);

Ian Hickson's avatar
Ian Hickson committed
411 412 413 414
  /// 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
415 416 417 418 419
  /// 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
420 421
  /// On Android, this may require the
  /// `android.permission.READ_EXTERNAL_STORAGE` permission.
422
  ///
423 424 425 426 427
  /// 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.
  ///
428
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
429 430 431 432 433 434
  ///
  /// 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].
435 436
  Image.file(
    File file, {
Ian Hickson's avatar
Ian Hickson committed
437
    Key key,
438
    double scale = 1.0,
439
    this.frameBuilder,
440
    this.errorBuilder,
441 442
    this.semanticLabel,
    this.excludeFromSemantics = false,
Ian Hickson's avatar
Ian Hickson committed
443 444 445
    this.width,
    this.height,
    this.color,
446
    this.colorBlendMode,
Ian Hickson's avatar
Ian Hickson committed
447
    this.fit,
448 449
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
Ian Hickson's avatar
Ian Hickson committed
450
    this.centerSlice,
451 452
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
453
    this.isAntiAlias = false,
454
    this.filterQuality = FilterQuality.low,
455 456
    int cacheWidth,
    int cacheHeight,
457
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, FileImage(file, scale: scale)),
458
       loadingBuilder = null,
Ian Hickson's avatar
Ian Hickson committed
459 460
       assert(alignment != null),
       assert(repeat != null),
461
       assert(filterQuality != null),
Ian Hickson's avatar
Ian Hickson committed
462
       assert(matchTextDirection != null),
463 464
       assert(cacheWidth == null || cacheWidth > 0),
       assert(cacheHeight == null || cacheHeight > 0),
465
       assert(isAntiAlias != null),
Ian Hickson's avatar
Ian Hickson committed
466 467
       super(key: key);

468 469 470 471 472 473 474 475 476 477

  // 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.
478 479 480
  /// Creates a widget that displays an [ImageStream] obtained from an asset
  /// bundle. The key for the image is given by the `name` argument.
  ///
481 482 483 484
  /// 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.
  ///
485 486 487
  /// If the `bundle` argument is omitted or null, then the
  /// [DefaultAssetBundle] will be used.
  ///
488 489
  /// By default, the pixel-density-aware asset resolution will be attempted. In
  /// addition:
490
  ///
491
  /// * If the `scale` argument is provided and is not null, then the exact
492 493
  /// 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`).
494 495
  ///
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
496
  ///
497 498 499 500 501 502
  /// 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].
  ///
503
  /// The [name] and [repeat] arguments must not be null.
504
  ///
Ian Hickson's avatar
Ian Hickson committed
505 506 507 508 509
  /// 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.
  ///
510 511 512 513 514
  /// 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.
  ///
515
  /// {@tool snippet}
516 517 518 519 520 521 522 523 524 525
  ///
  /// 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
  /// ```
526
  /// {@end-tool}
527 528 529 530 531
  ///
  /// On a screen with a device pixel ratio of 2.0, the following widget would
  /// render the `images/2x/cat.png` file:
  ///
  /// ```dart
532
  /// Image.asset('images/cat.png')
533 534 535 536 537 538
  /// ```
  ///
  /// 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).
  ///
539 540 541 542 543 544 545 546
  /// 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.
  ///
547 548 549 550 551 552 553
  ///
  /// ## 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` .
  ///
554
  /// {@tool snippet}
555 556 557
  /// Then to display the image, use:
  ///
  /// ```dart
558
  /// Image.asset('icons/heart.png', package: 'my_icons')
559
  /// ```
560
  /// {@end-tool}
561 562 563 564
  ///
  /// Assets used by the package itself should also be displayed using the
  /// [package] argument as above.
  ///
565
  /// If the desired asset is specified in the `pubspec.yaml` of the package, it
566
  /// is bundled automatically with the app. In particular, assets used by the
567
  /// package itself must be specified in its `pubspec.yaml`.
568 569
  ///
  /// A package can also choose to have assets in its 'lib/' folder that are not
570
  /// specified in its `pubspec.yaml`. In this case for those images to be
571 572 573 574 575 576 577
  /// 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
578
  /// ```
579
  ///
580
  /// To include, say the first image, the `pubspec.yaml` of the app should
581 582 583
  /// specify it in the assets section:
  ///
  /// ```yaml
584 585
  ///   assets:
  ///     - packages/fancy_backgrounds/backgrounds/background1.png
586 587
  /// ```
  ///
Ian Hickson's avatar
Ian Hickson committed
588
  /// The `lib/` is implied, so it should not be included in the asset path.
589 590
  ///
  ///
591 592 593 594 595 596
  /// 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.
597
  ///  * <https://flutter.dev/assets-and-images/>, an introduction to assets in
598
  ///    Flutter.
599 600
  Image.asset(
    String name, {
601 602
    Key key,
    AssetBundle bundle,
603
    this.frameBuilder,
604
    this.errorBuilder,
605 606
    this.semanticLabel,
    this.excludeFromSemantics = false,
607 608 609 610
    double scale,
    this.width,
    this.height,
    this.color,
611
    this.colorBlendMode,
612
    this.fit,
613 614
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
615
    this.centerSlice,
616 617
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
618
    this.isAntiAlias = false,
Ian Hickson's avatar
Ian Hickson committed
619
    String package,
620
    this.filterQuality = FilterQuality.low,
621 622
    int cacheWidth,
    int cacheHeight,
623
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, scale != null
624
         ? ExactAssetImage(name, bundle: bundle, scale: scale, package: package)
625 626
         : AssetImage(name, bundle: bundle, package: package)
       ),
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
       assert(isAntiAlias != null),
Ian Hickson's avatar
Ian Hickson committed
634
       super(key: key);
635

636 637 638
  /// 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
639
  ///
640 641 642 643
  /// 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
644 645 646 647
  /// 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.
648
  ///
649 650 651 652 653
  /// 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.
  ///
654
  /// If [excludeFromSemantics] is true, then [semanticLabel] will be ignored.
655 656 657 658 659 660
  ///
  /// 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].
661 662
  Image.memory(
    Uint8List bytes, {
663
    Key key,
664
    double scale = 1.0,
665
    this.frameBuilder,
666
    this.errorBuilder,
667 668
    this.semanticLabel,
    this.excludeFromSemantics = false,
669 670 671
    this.width,
    this.height,
    this.color,
672
    this.colorBlendMode,
673
    this.fit,
674 675
    this.alignment = Alignment.center,
    this.repeat = ImageRepeat.noRepeat,
676
    this.centerSlice,
677 678
    this.matchTextDirection = false,
    this.gaplessPlayback = false,
679
    this.isAntiAlias = false,
680
    this.filterQuality = FilterQuality.low,
681 682
    int cacheWidth,
    int cacheHeight,
683
  }) : image = ResizeImage.resizeIfNeeded(cacheWidth, cacheHeight, MemoryImage(bytes, scale: scale)),
684
       loadingBuilder = null,
Ian Hickson's avatar
Ian Hickson committed
685 686 687
       assert(alignment != null),
       assert(repeat != null),
       assert(matchTextDirection != null),
688 689
       assert(cacheWidth == null || cacheWidth > 0),
       assert(cacheHeight == null || cacheHeight > 0),
690
       assert(isAntiAlias != null),
691 692
       super(key: key);

693 694 695
  /// The image to display.
  final ImageProvider image;

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
  /// 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}
  ///
743
  /// {@tool dartpad --template=stateless_widget_material}
744 745 746 747 748 749 750 751 752 753 754 755 756 757 758 759 760
  ///
  /// 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(
761
  ///       'https://flutter.github.io/assets-for-api-docs/assets/widgets/puffin.jpg',
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 795 796 797 798 799 800 801 802 803 804 805 806 807 808
  ///       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}
  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}
  ///
809
  /// {@tool dartpad --template=stateless_widget_material}
810 811 812 813 814 815 816 817 818 819 820 821 822 823 824 825 826 827 828 829 830 831 832 833 834 835 836 837 838 839 840
  ///
  /// 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}
  ///
841 842 843
  /// Run against a real-world image on a slow network, the previous example
  /// renders the following loading progress indicator while the image loads
  /// before rendering the completed image.
844 845 846 847
  ///
  /// {@animation 400 400 https://flutter.github.io/assets-for-api-docs/assets/widgets/loading_progress_image.mp4}
  final ImageLoadingBuilder loadingBuilder;

848 849 850 851 852 853 854 855 856 857 858 859 860 861 862 863 864 865 866 867 868 869 870 871 872 873 874 875 876 877 878 879 880 881 882 883 884
  /// A builder function that is called if an error occurs during image loading.
  ///
  /// If this builder is not provided, any exceptions will be reported to
  /// [FlutterError.onError]. If it is provided, the caller should either handle
  /// the exception by providing a replacement widget, or rethrow the exception.
  ///
  /// {@tool dartpad --template=stateless_widget_material}
  ///
  /// The following sample uses [errorBuilder] to show a '😢' in place of the
  /// image that fails to load, and prints the error to the console.
  ///
  /// ```dart
  /// Widget build(BuildContext context) {
  ///   return DecoratedBox(
  ///     decoration: BoxDecoration(
  ///       color: Colors.white,
  ///       border: Border.all(),
  ///       borderRadius: BorderRadius.circular(20),
  ///     ),
  ///     child: Image.network(
  ///       'https://example.does.not.exist/image.jpg',
  ///       errorBuilder: (BuildContext context, Object exception, StackTrace stackTrace) {
  ///         // Appropriate logging or analytics, e.g.
  ///         // myAnalytics.recordError(
  ///         //   'An error occurred loading "https://example.does.not.exist/image.jpg"',
  ///         //   exception,
  ///         //   stackTrace,
  ///         // );
  ///         return Text('😢');
  ///       },
  ///     ),
  ///   );
  /// }
  /// ```
  /// {@end-tool}
  final ImageErrorWidgetBuilder errorBuilder;

885 886 887 888
  /// 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
889 890 891 892 893 894
  ///
  /// 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.
895 896 897 898 899 900
  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
901 902 903 904 905 906
  ///
  /// 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.
907 908
  final double height;

909
  /// If non-null, this color is blended with each image pixel using [colorBlendMode].
910 911
  final Color color;

912 913 914 915
  /// 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
916 917 918
  /// to nearest-neighbor.
  final FilterQuality filterQuality;

919 920 921 922 923 924 925 926 927 928
  /// 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
929
  /// How to inscribe the image into the space allocated during layout.
930 931 932
  ///
  /// The default varies based on the other fields. See the discussion at
  /// [paintImage].
933
  final BoxFit fit;
934 935 936

  /// How to align the image within its bounds.
  ///
Ian Hickson's avatar
Ian Hickson committed
937
  /// The alignment aligns the given position in the image to the given position
938 939
  /// 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
940
  /// [Alignment] alignment of (1.0, 1.0) aligns the bottom right of the
Ian Hickson's avatar
Ian Hickson committed
941
  /// image with the bottom right corner of its layout bounds. Similarly, an
942
  /// alignment of (0.0, 1.0) aligns the bottom middle of the image with the
Ian Hickson's avatar
Ian Hickson committed
943 944 945 946 947 948
  /// 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
949
  /// [AlignmentDirectional]), then an ambient [Directionality] widget
Ian Hickson's avatar
Ian Hickson committed
950 951
  /// must be in scope.
  ///
952
  /// Defaults to [Alignment.center].
953 954 955 956 957 958 959
  ///
  /// See also:
  ///
  ///  * [Alignment], a class with convenient constants typically used to
  ///    specify an [AlignmentGeometry].
  ///  * [AlignmentDirectional], like [Alignment] for specifying alignments
  ///    relative to text direction.
960
  final AlignmentGeometry alignment;
961 962 963 964 965 966 967 968 969 970 971 972 973

  /// 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
974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990
  /// 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;

991 992 993 994
  /// Whether to continue showing the old image (true), or briefly show nothing
  /// (false), when the image provider changes.
  final bool gaplessPlayback;

995 996
  /// A Semantic description of the image.
  ///
997
  /// Used to provide a description of the image to TalkBack on Android, and
998 999 1000 1001 1002 1003 1004 1005 1006
  /// 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;

1007 1008 1009 1010 1011
  /// Whether to paint the image with anti-aliasing.
  ///
  /// Anti-aliasing alleviates the sawtooth artifact when the image is rotated.
  final bool isAntiAlias;

1012
  @override
1013
  _ImageState createState() => _ImageState();
1014 1015

  @override
1016 1017
  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
    super.debugFillProperties(properties);
1018
    properties.add(DiagnosticsProperty<ImageProvider>('image', image));
1019 1020
    properties.add(DiagnosticsProperty<Function>('frameBuilder', frameBuilder));
    properties.add(DiagnosticsProperty<Function>('loadingBuilder', loadingBuilder));
1021 1022
    properties.add(DoubleProperty('width', width, defaultValue: null));
    properties.add(DoubleProperty('height', height, defaultValue: null));
1023
    properties.add(ColorProperty('color', color, defaultValue: null));
1024 1025 1026 1027 1028 1029 1030 1031
    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));
1032
    properties.add(EnumProperty<FilterQuality>('filterQuality', filterQuality));
1033 1034 1035
  }
}

1036
class _ImageState extends State<Image> with WidgetsBindingObserver {
1037 1038
  ImageStream _imageStream;
  ImageInfo _imageInfo;
1039
  ImageChunkEvent _loadingProgress;
1040
  bool _isListeningToStream = false;
1041
  bool _invertColors;
1042 1043
  int _frameNumber;
  bool _wasSynchronouslyLoaded;
1044
  DisposableBuildContext<State<Image>> _scrollAwareContext;
1045 1046
  Object _lastException;
  StackTrace _lastStack;
1047 1048 1049 1050 1051

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addObserver(this);
1052
    _scrollAwareContext = DisposableBuildContext<State<Image>>(this);
1053 1054 1055 1056 1057 1058 1059
  }

  @override
  void dispose() {
    assert(_imageStream != null);
    WidgetsBinding.instance.removeObserver(this);
    _stopListeningToStream();
1060
    _scrollAwareContext.dispose();
1061 1062
    super.dispose();
  }
1063 1064

  @override
1065
  void didChangeDependencies() {
1066
    _updateInvertColors();
1067
    _resolveImage();
1068 1069 1070 1071 1072 1073

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

1074
    super.didChangeDependencies();
1075 1076
  }

1077
  @override
1078
  void didUpdateWidget(Image oldWidget) {
1079
    super.didUpdateWidget(oldWidget);
1080 1081 1082 1083 1084
    if (_isListeningToStream &&
        (widget.loadingBuilder == null) != (oldWidget.loadingBuilder == null)) {
      _imageStream.removeListener(_getListener(oldWidget.loadingBuilder));
      _imageStream.addListener(_getListener());
    }
1085
    if (widget.image != oldWidget.image)
1086 1087 1088
      _resolveImage();
  }

1089 1090 1091 1092 1093 1094 1095 1096
  @override
  void didChangeAccessibilityFeatures() {
    super.didChangeAccessibilityFeatures();
    setState(() {
      _updateInvertColors();
    });
  }

1097 1098
  @override
  void reassemble() {
1099
    _resolveImage(); // in case the image cache was flushed
1100 1101 1102
    super.reassemble();
  }

1103 1104 1105 1106 1107
  void _updateInvertColors() {
    _invertColors = MediaQuery.of(context, nullOk: true)?.invertColors
        ?? SemanticsBinding.instance.accessibilityFeatures.invertColors;
  }

1108
  void _resolveImage() {
1109 1110 1111 1112
    final ScrollAwareImageProvider provider = ScrollAwareImageProvider<dynamic>(
      context: _scrollAwareContext,
      imageProvider: widget.image,
    );
1113
    final ImageStream newStream =
1114
      provider.resolve(createLocalImageConfiguration(
1115 1116
        context,
        size: widget.width != null && widget.height != null ? Size(widget.width, widget.height) : null,
1117 1118 1119
      ));
    assert(newStream != null);
    _updateSourceStream(newStream);
1120 1121
  }

1122 1123
  ImageStreamListener _getListener([ImageLoadingBuilder loadingBuilder]) {
    loadingBuilder ??= widget.loadingBuilder;
1124 1125
    _lastException = null;
    _lastStack = null;
1126 1127 1128
    return ImageStreamListener(
      _handleImageFrame,
      onChunk: loadingBuilder == null ? null : _handleImageChunk,
1129 1130 1131 1132 1133 1134 1135 1136
      onError: widget.errorBuilder != null
        ? (dynamic error, StackTrace stackTrace) {
            setState(() {
              _lastException = error;
              _lastStack = stackTrace;
            });
          }
        : null,
1137 1138 1139 1140
    );
  }

  void _handleImageFrame(ImageInfo imageInfo, bool synchronousCall) {
1141 1142
    setState(() {
      _imageInfo = imageInfo;
1143 1144 1145 1146 1147 1148 1149 1150 1151 1152
      _loadingProgress = null;
      _frameNumber = _frameNumber == null ? 0 : _frameNumber + 1;
      _wasSynchronouslyLoaded |= synchronousCall;
    });
  }

  void _handleImageChunk(ImageChunkEvent event) {
    assert(widget.loadingBuilder != null);
    setState(() {
      _loadingProgress = event;
1153 1154 1155
    });
  }

1156
  // Updates _imageStream to newStream, and moves the stream listener
1157 1158 1159 1160 1161 1162 1163
  // 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)
1164
      _imageStream.removeListener(_getListener());
1165

1166 1167 1168
    if (!widget.gaplessPlayback)
      setState(() { _imageInfo = null; });

1169 1170 1171 1172 1173 1174
    setState(() {
      _loadingProgress = null;
      _frameNumber = null;
      _wasSynchronouslyLoaded = false;
    });

1175 1176
    _imageStream = newStream;
    if (_isListeningToStream)
1177
      _imageStream.addListener(_getListener());
1178 1179 1180 1181 1182
  }

  void _listenToStream() {
    if (_isListeningToStream)
      return;
1183
    _imageStream.addListener(_getListener());
1184 1185 1186 1187 1188 1189
    _isListeningToStream = true;
  }

  void _stopListeningToStream() {
    if (!_isListeningToStream)
      return;
1190
    _imageStream.removeListener(_getListener());
1191 1192 1193
    _isListeningToStream = false;
  }

1194 1195
  @override
  Widget build(BuildContext context) {
1196 1197 1198 1199 1200
    if (_lastException  != null) {
      assert(widget.errorBuilder != null);
      return widget.errorBuilder(context, _lastException, _lastStack);
    }

1201
    Widget result = RawImage(
1202
      image: _imageInfo?.image,
1203 1204
      width: widget.width,
      height: widget.height,
1205
      scale: _imageInfo?.scale ?? 1.0,
1206
      color: widget.color,
1207
      colorBlendMode: widget.colorBlendMode,
1208 1209 1210
      fit: widget.fit,
      alignment: widget.alignment,
      repeat: widget.repeat,
Ian Hickson's avatar
Ian Hickson committed
1211 1212
      centerSlice: widget.centerSlice,
      matchTextDirection: widget.matchTextDirection,
1213
      invertColors: _invertColors,
1214
      isAntiAlias: widget.isAntiAlias,
1215
      filterQuality: widget.filterQuality,
1216
    );
1217 1218 1219 1220 1221 1222 1223 1224 1225 1226 1227 1228 1229 1230 1231 1232 1233

    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;
1234
  }
1235 1236

  @override
1237
  void debugFillProperties(DiagnosticPropertiesBuilder description) {
1238
    super.debugFillProperties(description);
1239 1240
    description.add(DiagnosticsProperty<ImageStream>('stream', _imageStream));
    description.add(DiagnosticsProperty<ImageInfo>('pixels', _imageInfo));
1241 1242 1243
    description.add(DiagnosticsProperty<ImageChunkEvent>('loadingProgress', _loadingProgress));
    description.add(DiagnosticsProperty<int>('frameNumber', _frameNumber));
    description.add(DiagnosticsProperty<bool>('wasSynchronouslyLoaded', _wasSynchronouslyLoaded));
1244
  }
1245
}