1// Copyright 2014 The Flutter Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5// Examples can assume:
6// late BuildContext context;
7
8/// @docImport 'package:flutter/widgets.dart';
9/// @docImport '_web_image_info_io.dart';
10library;
11
12import 'dart:async';
13import 'dart:io';
14import 'dart:math' as math;
15import 'dart:ui' as ui;
16
17import 'package:flutter/foundation.dart';
18import 'package:flutter/services.dart';
19
20import '_network_image_io.dart'
21 if (dart.library.js_util) '_network_image_web.dart'
22 as network_image;
23import 'binding.dart';
24import 'image_cache.dart';
25import 'image_stream.dart';
26
27/// Signature for the callback taken by [ImageProvider._createErrorHandlerAndKey].
28typedef _KeyAndErrorHandlerCallback<T> = void Function(T key, ImageErrorListener handleError);
29
30/// Signature used for error handling by [ImageProvider._createErrorHandlerAndKey].
31typedef _AsyncKeyErrorHandler<T> =
32 Future<void> Function(T key, Object exception, StackTrace? stack);
33
34/// Configuration information passed to the [ImageProvider.resolve] method to
35/// select a specific image.
36///
37/// See also:
38///
39/// * [createLocalImageConfiguration], which creates an [ImageConfiguration]
40/// based on ambient configuration in a [Widget] environment.
41/// * [ImageProvider], which uses [ImageConfiguration] objects to determine
42/// which image to obtain.
43@immutable
44class ImageConfiguration {
45 /// Creates an object holding the configuration information for an [ImageProvider].
46 ///
47 /// All the arguments are optional. Configuration information is merely
48 /// advisory and best-effort.
49 const ImageConfiguration({
50 this.bundle,
51 this.devicePixelRatio,
52 this.locale,
53 this.textDirection,
54 this.size,
55 this.platform,
56 });
57
58 /// Creates an object holding the configuration information for an [ImageProvider].
59 ///
60 /// All the arguments are optional. Configuration information is merely
61 /// advisory and best-effort.
62 ImageConfiguration copyWith({
63 AssetBundle? bundle,
64 double? devicePixelRatio,
65 ui.Locale? locale,
66 TextDirection? textDirection,
67 Size? size,
68 TargetPlatform? platform,
69 }) {
70 return ImageConfiguration(
71 bundle: bundle ?? this.bundle,
72 devicePixelRatio: devicePixelRatio ?? this.devicePixelRatio,
73 locale: locale ?? this.locale,
74 textDirection: textDirection ?? this.textDirection,
75 size: size ?? this.size,
76 platform: platform ?? this.platform,
77 );
78 }
79
80 /// The preferred [AssetBundle] to use if the [ImageProvider] needs one and
81 /// does not have one already selected.
82 final AssetBundle? bundle;
83
84 /// The device pixel ratio where the image will be shown.
85 final double? devicePixelRatio;
86
87 /// The language and region for which to select the image.
88 final ui.Locale? locale;
89
90 /// The reading direction of the language for which to select the image.
91 final TextDirection? textDirection;
92
93 /// The size at which the image will be rendered.
94 final Size? size;
95
96 /// The [TargetPlatform] for which assets should be used. This allows images
97 /// to be specified in a platform-neutral fashion yet use different assets on
98 /// different platforms, to match local conventions e.g. for color matching or
99 /// shadows.
100 final TargetPlatform? platform;
101
102 /// An image configuration that provides no additional information.
103 ///
104 /// Useful when resolving an [ImageProvider] without any context.
105 static const ImageConfiguration empty = ImageConfiguration();
106
107 @override
108 bool operator ==(Object other) {
109 if (other.runtimeType != runtimeType) {
110 return false;
111 }
112 return other is ImageConfiguration &&
113 other.bundle == bundle &&
114 other.devicePixelRatio == devicePixelRatio &&
115 other.locale == locale &&
116 other.textDirection == textDirection &&
117 other.size == size &&
118 other.platform == platform;
119 }
120
121 @override
122 int get hashCode => Object.hash(bundle, devicePixelRatio, locale, size, platform);
123
124 @override
125 String toString() {
126 final StringBuffer result = StringBuffer();
127 result.write('ImageConfiguration(');
128 bool hasArguments = false;
129 if (bundle != null) {
130 result.write('bundle: $bundle');
131 hasArguments = true;
132 }
133 if (devicePixelRatio != null) {
134 if (hasArguments) {
135 result.write(', ');
136 }
137 result.write('devicePixelRatio: ${devicePixelRatio!.toStringAsFixed(1)}');
138 hasArguments = true;
139 }
140 if (locale != null) {
141 if (hasArguments) {
142 result.write(', ');
143 }
144 result.write('locale: $locale');
145 hasArguments = true;
146 }
147 if (textDirection != null) {
148 if (hasArguments) {
149 result.write(', ');
150 }
151 result.write('textDirection: $textDirection');
152 hasArguments = true;
153 }
154 if (size != null) {
155 if (hasArguments) {
156 result.write(', ');
157 }
158 result.write('size: $size');
159 hasArguments = true;
160 }
161 if (platform != null) {
162 if (hasArguments) {
163 result.write(', ');
164 }
165 result.write('platform: ${platform!.name}');
166 hasArguments = true;
167 }
168 result.write(')');
169 return result.toString();
170 }
171}
172
173/// Performs the decode process for use in [ImageProvider.loadBuffer].
174///
175/// This callback allows decoupling of the `cacheWidth`, `cacheHeight`, and
176/// `allowUpscaling` parameters from implementations of [ImageProvider] that do
177/// not expose them.
178///
179/// See also:
180///
181/// * [ResizeImage], which uses this to override the `cacheWidth`,
182/// `cacheHeight`, and `allowUpscaling` parameters.
183@Deprecated(
184 'Use ImageDecoderCallback with ImageProvider.loadImage instead. '
185 'This feature was deprecated after v3.7.0-1.4.pre.',
186)
187typedef DecoderBufferCallback =
188 Future<ui.Codec> Function(
189 ui.ImmutableBuffer buffer, {
190 int? cacheWidth,
191 int? cacheHeight,
192 bool allowUpscaling,
193 });
194
195// Method signature for _loadAsync decode callbacks.
196typedef _SimpleDecoderCallback = Future<ui.Codec> Function(ui.ImmutableBuffer buffer);
197
198/// Performs the decode process for use in [ImageProvider.loadImage].
199///
200/// This callback allows decoupling of the `getTargetSize` parameter from
201/// implementations of [ImageProvider] that do not expose it.
202///
203/// See also:
204///
205/// * [ResizeImage], which uses this to load images at specific sizes.
206typedef ImageDecoderCallback =
207 Future<ui.Codec> Function(
208 ui.ImmutableBuffer buffer, {
209 ui.TargetImageSizeCallback? getTargetSize,
210 });
211
212/// Identifies an image without committing to the precise final asset. This
213/// allows a set of images to be identified and for the precise image to later
214/// be resolved based on the environment, e.g. the device pixel ratio.
215///
216/// To obtain an [ImageStream] from an [ImageProvider], call [resolve],
217/// passing it an [ImageConfiguration] object.
218///
219/// [ImageProvider] uses the global [imageCache] to cache images.
220///
221/// The type argument `T` is the type of the object used to represent a resolved
222/// configuration. This is also the type used for the key in the image cache. It
223/// should be immutable and implement the [==] operator and the [hashCode]
224/// getter. Subclasses should subclass a variant of [ImageProvider] with an
225/// explicit `T` type argument.
226///
227/// The type argument does not have to be specified when using the type as an
228/// argument (where any image provider is acceptable).
229///
230/// The following image formats are supported: {@macro dart.ui.imageFormats}
231///
232/// ## Lifecycle of resolving an image
233///
234/// The [ImageProvider] goes through the following lifecycle to resolve an
235/// image, once the [resolve] method is called:
236///
237/// 1. Create an [ImageStream] using [createStream] to return to the caller.
238/// This stream will be used to communicate back to the caller when the
239/// image is decoded and ready to display, or when an error occurs.
240/// 2. Obtain the key for the image using [obtainKey].
241/// Calling this method can throw exceptions into the zone asynchronously
242/// or into the call stack synchronously. To handle that, an error handler
243/// is created that catches both synchronous and asynchronous errors, to
244/// make sure errors can be routed to the correct consumers.
245/// The error handler is passed on to [resolveStreamForKey] and the
246/// [ImageCache].
247/// 3. If the key is successfully obtained, schedule resolution of the image
248/// using that key. This is handled by [resolveStreamForKey]. That method
249/// may fizzle if it determines the image is no longer necessary, use the
250/// provided [ImageErrorListener] to report an error, set the completer
251/// from the cache if possible, or call [loadImage] to fetch the encoded image
252/// bytes and schedule decoding.
253/// 4. The [loadImage] method is responsible for both fetching the encoded bytes
254/// and decoding them using the provided [ImageDecoderCallback]. It is called
255/// in a context that uses the [ImageErrorListener] to report errors back.
256///
257/// Subclasses normally only have to implement the [loadImage] and [obtainKey]
258/// methods. A subclass that needs finer grained control over the [ImageStream]
259/// type must override [createStream]. A subclass that needs finer grained
260/// control over the resolution, such as delaying calling [loadImage], must override
261/// [resolveStreamForKey].
262///
263/// The [resolve] method is marked as [nonVirtual] so that [ImageProvider]s can
264/// be properly composed, and so that the base class can properly set up error
265/// handling for subsequent methods.
266///
267/// ## Using an [ImageProvider]
268///
269/// {@tool snippet}
270///
271/// The following shows the code required to write a widget that fully conforms
272/// to the [ImageProvider] and [Widget] protocols. (It is essentially a
273/// bare-bones version of the [Image] widget.)
274///
275/// ```dart
276/// class MyImage extends StatefulWidget {
277/// const MyImage({
278/// super.key,
279/// required this.imageProvider,
280/// });
281///
282/// final ImageProvider imageProvider;
283///
284/// @override
285/// State<MyImage> createState() => _MyImageState();
286/// }
287///
288/// class _MyImageState extends State<MyImage> {
289/// ImageStream? _imageStream;
290/// ImageInfo? _imageInfo;
291///
292/// @override
293/// void didChangeDependencies() {
294/// super.didChangeDependencies();
295/// // We call _getImage here because createLocalImageConfiguration() needs to
296/// // be called again if the dependencies changed, in case the changes relate
297/// // to the DefaultAssetBundle, MediaQuery, etc, which that method uses.
298/// _getImage();
299/// }
300///
301/// @override
302/// void didUpdateWidget(MyImage oldWidget) {
303/// super.didUpdateWidget(oldWidget);
304/// if (widget.imageProvider != oldWidget.imageProvider) {
305/// _getImage();
306/// }
307/// }
308///
309/// void _getImage() {
310/// final ImageStream? oldImageStream = _imageStream;
311/// _imageStream = widget.imageProvider.resolve(createLocalImageConfiguration(context));
312/// if (_imageStream!.key != oldImageStream?.key) {
313/// // If the keys are the same, then we got the same image back, and so we don't
314/// // need to update the listeners. If the key changed, though, we must make sure
315/// // to switch our listeners to the new image stream.
316/// final ImageStreamListener listener = ImageStreamListener(_updateImage);
317/// oldImageStream?.removeListener(listener);
318/// _imageStream!.addListener(listener);
319/// }
320/// }
321///
322/// void _updateImage(ImageInfo imageInfo, bool synchronousCall) {
323/// setState(() {
324/// // Trigger a build whenever the image changes.
325/// _imageInfo?.dispose();
326/// _imageInfo = imageInfo;
327/// });
328/// }
329///
330/// @override
331/// void dispose() {
332/// _imageStream?.removeListener(ImageStreamListener(_updateImage));
333/// _imageInfo?.dispose();
334/// _imageInfo = null;
335/// super.dispose();
336/// }
337///
338/// @override
339/// Widget build(BuildContext context) {
340/// return RawImage(
341/// image: _imageInfo?.image, // this is a dart:ui Image object
342/// scale: _imageInfo?.scale ?? 1.0,
343/// );
344/// }
345/// }
346/// ```
347/// {@end-tool}
348///
349/// ## Creating an [ImageProvider]
350///
351/// {@tool dartpad}
352/// In this example, a variant of [NetworkImage] is created that passes all the
353/// [ImageConfiguration] information (locale, platform, size, etc) to the server
354/// using query arguments in the image URL.
355///
356/// ** See code in examples/api/lib/painting/image_provider/image_provider.0.dart **
357/// {@end-tool}
358@optionalTypeArgs
359abstract class ImageProvider<T extends Object> {
360 /// Abstract const constructor. This constructor enables subclasses to provide
361 /// const constructors so that they can be used in const expressions.
362 const ImageProvider();
363
364 /// Resolves this image provider using the given `configuration`, returning
365 /// an [ImageStream].
366 ///
367 /// This is the public entry-point of the [ImageProvider] class hierarchy.
368 ///
369 /// Subclasses should implement [obtainKey] and [loadImage], which are used by
370 /// this method. If they need to change the implementation of [ImageStream]
371 /// used, they should override [createStream]. If they need to manage the
372 /// actual resolution of the image, they should override [resolveStreamForKey].
373 ///
374 /// See the Lifecycle documentation on [ImageProvider] for more information.
375 @nonVirtual
376 ImageStream resolve(ImageConfiguration configuration) {
377 final ImageStream stream = createStream(configuration);
378 // Load the key (potentially asynchronously), set up an error handling zone,
379 // and call resolveStreamForKey.
380 _createErrorHandlerAndKey(
381 configuration,
382 (T key, ImageErrorListener errorHandler) {
383 resolveStreamForKey(configuration, stream, key, errorHandler);
384 },
385 (T? key, Object exception, StackTrace? stack) async {
386 await null; // wait an event turn in case a listener has been added to the image stream.
387 InformationCollector? collector;
388 assert(() {
389 collector = () => <DiagnosticsNode>[
390 DiagnosticsProperty<ImageProvider>('Image provider', this),
391 DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration),
392 DiagnosticsProperty<T>('Image key', key, defaultValue: null),
393 ];
394 return true;
395 }());
396 if (stream.completer == null) {
397 stream.setCompleter(_ErrorImageCompleter());
398 }
399 stream.completer!.reportError(
400 exception: exception,
401 stack: stack,
402 context: ErrorDescription('while resolving an image'),
403 silent: true, // could be a network error or whatnot
404 informationCollector: collector,
405 );
406 },
407 );
408 return stream;
409 }
410
411 /// Called by [resolve] to create the [ImageStream] it returns.
412 ///
413 /// Subclasses should override this instead of [resolve] if they need to
414 /// return some subclass of [ImageStream]. The stream created here will be
415 /// passed to [resolveStreamForKey].
416 @protected
417 ImageStream createStream(ImageConfiguration configuration) {
418 return ImageStream();
419 }
420
421 /// Returns the cache location for the key that this [ImageProvider] creates.
422 ///
423 /// The location may be [ImageCacheStatus.untracked], indicating that this
424 /// image provider's key is not available in the [ImageCache].
425 ///
426 /// If the `handleError` parameter is null, errors will be reported to
427 /// [FlutterError.onError], and the method will return null.
428 ///
429 /// A completed return value of null indicates that an error has occurred.
430 Future<ImageCacheStatus?> obtainCacheStatus({
431 required ImageConfiguration configuration,
432 ImageErrorListener? handleError,
433 }) {
434 final Completer<ImageCacheStatus?> completer = Completer<ImageCacheStatus?>();
435 _createErrorHandlerAndKey(
436 configuration,
437 (T key, ImageErrorListener innerHandleError) {
438 completer.complete(PaintingBinding.instance.imageCache.statusForKey(key));
439 },
440 (T? key, Object exception, StackTrace? stack) async {
441 if (handleError != null) {
442 handleError(exception, stack);
443 } else {
444 InformationCollector? collector;
445 assert(() {
446 collector = () => <DiagnosticsNode>[
447 DiagnosticsProperty<ImageProvider>('Image provider', this),
448 DiagnosticsProperty<ImageConfiguration>('Image configuration', configuration),
449 DiagnosticsProperty<T>('Image key', key, defaultValue: null),
450 ];
451 return true;
452 }());
453 FlutterError.reportError(
454 FlutterErrorDetails(
455 context: ErrorDescription('while checking the cache location of an image'),
456 informationCollector: collector,
457 exception: exception,
458 stack: stack,
459 ),
460 );
461 completer.complete();
462 }
463 },
464 );
465 return completer.future;
466 }
467
468 /// This method is used by both [resolve] and [obtainCacheStatus] to ensure
469 /// that errors thrown during key creation are handled whether synchronous or
470 /// asynchronous.
471 void _createErrorHandlerAndKey(
472 ImageConfiguration configuration,
473 _KeyAndErrorHandlerCallback<T> successCallback,
474 _AsyncKeyErrorHandler<T?> errorCallback,
475 ) {
476 T? obtainedKey;
477 bool didError = false;
478 Future<void> handleError(Object exception, StackTrace? stack) async {
479 if (didError) {
480 return;
481 }
482 if (!didError) {
483 didError = true;
484 errorCallback(obtainedKey, exception, stack);
485 }
486 }
487
488 Future<T> key;
489 try {
490 key = obtainKey(configuration);
491 } catch (error, stackTrace) {
492 handleError(error, stackTrace);
493 return;
494 }
495 key
496 .then<void>((T key) {
497 obtainedKey = key;
498 try {
499 successCallback(key, handleError);
500 } catch (error, stackTrace) {
501 handleError(error, stackTrace);
502 }
503 })
504 .catchError(handleError);
505 }
506
507 /// Called by [resolve] with the key returned by [obtainKey].
508 ///
509 /// Subclasses should override this method rather than calling [obtainKey] if
510 /// they need to use a key directly. The [resolve] method installs appropriate
511 /// error handling guards so that errors will bubble up to the right places in
512 /// the framework, and passes those guards along to this method via the
513 /// [handleError] parameter.
514 ///
515 /// It is safe for the implementation of this method to call [handleError]
516 /// multiple times if multiple errors occur, or if an error is thrown both
517 /// synchronously into the current part of the stack and thrown into the
518 /// enclosing [Zone].
519 ///
520 /// The default implementation uses the key to interact with the [ImageCache],
521 /// calling [ImageCache.putIfAbsent] and notifying listeners of the [stream].
522 /// Implementers that do not call super are expected to correctly use the
523 /// [ImageCache].
524 @protected
525 void resolveStreamForKey(
526 ImageConfiguration configuration,
527 ImageStream stream,
528 T key,
529 ImageErrorListener handleError,
530 ) {
531 // This is an unusual edge case where someone has told us that they found
532 // the image we want before getting to this method. We should avoid calling
533 // load again, but still update the image cache with LRU information.
534 if (stream.completer != null) {
535 final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
536 key,
537 () => stream.completer!,
538 onError: handleError,
539 );
540 assert(identical(completer, stream.completer));
541 return;
542 }
543 final ImageStreamCompleter? completer = PaintingBinding.instance.imageCache.putIfAbsent(
544 key,
545 () {
546 ImageStreamCompleter result = loadImage(
547 key,
548 PaintingBinding.instance.instantiateImageCodecWithSize,
549 );
550 // This check exists as a fallback for backwards compatibility until the
551 // deprecated `loadBuffer()` method is removed. Until then, ImageProvider
552 // subclasses may have only overridden `loadBuffer()`, in which case the
553 // base implementation of `loadWithSize()` will return a sentinel value
554 // of type `_AbstractImageStreamCompleter`.
555 if (result is _AbstractImageStreamCompleter) {
556 result = loadBuffer(key, PaintingBinding.instance.instantiateImageCodecFromBuffer);
557 }
558 return result;
559 },
560 onError: handleError,
561 );
562 if (completer != null) {
563 stream.setCompleter(completer);
564 }
565 }
566
567 /// Evicts an entry from the image cache.
568 ///
569 /// Returns a [Future] which indicates whether the value was successfully
570 /// removed.
571 ///
572 /// The [ImageProvider] used does not need to be the same instance that was
573 /// passed to an [Image] widget, but it does need to create a key which is
574 /// equal to one.
575 ///
576 /// The [cache] is optional and defaults to the global image cache.
577 ///
578 /// The [configuration] is optional and defaults to
579 /// [ImageConfiguration.empty].
580 ///
581 /// {@tool snippet}
582 ///
583 /// The following sample code shows how an image loaded using the [Image]
584 /// widget can be evicted using a [NetworkImage] with a matching URL.
585 ///
586 /// ```dart
587 /// class MyWidget extends StatelessWidget {
588 /// const MyWidget({
589 /// super.key,
590 /// this.url = ' ... ',
591 /// });
592 ///
593 /// final String url;
594 ///
595 /// @override
596 /// Widget build(BuildContext context) {
597 /// return Image.network(url);
598 /// }
599 ///
600 /// void evictImage() {
601 /// final NetworkImage provider = NetworkImage(url);
602 /// provider.evict().then<void>((bool success) {
603 /// if (success) {
604 /// debugPrint('removed image!');
605 /// }
606 /// });
607 /// }
608 /// }
609 /// ```
610 /// {@end-tool}
611 Future<bool> evict({
612 ImageCache? cache,
613 ImageConfiguration configuration = ImageConfiguration.empty,
614 }) async {
615 cache ??= imageCache;
616 final T key = await obtainKey(configuration);
617 return cache.evict(key);
618 }
619
620 /// Converts an [ImageProvider]'s settings plus an [ImageConfiguration] to a key
621 /// that describes the precise image to load.
622 ///
623 /// The type of the key is determined by the subclass. It is a value that
624 /// unambiguously identifies the image (_including its scale_) that the
625 /// [loadImage] method will fetch. Different [ImageProvider]s given the same
626 /// constructor arguments and [ImageConfiguration] objects should return keys
627 /// that are '==' to each other (possibly by using a class for the key that
628 /// itself implements [==]).
629 ///
630 /// If the result can be determined synchronously, this function should return
631 /// a [SynchronousFuture]. This allows image resolution to progress
632 /// synchronously during a frame rather than delaying image loading.
633 Future<T> obtainKey(ImageConfiguration configuration);
634
635 /// Converts a key into an [ImageStreamCompleter], and begins fetching the
636 /// image.
637 ///
638 /// This method is deprecated. Implement [loadImage] instead.
639 ///
640 /// The [decode] callback provides the logic to obtain the codec for the
641 /// image.
642 ///
643 /// See also:
644 ///
645 /// * [ResizeImage], for modifying the key to account for cache dimensions.
646 @protected
647 @Deprecated(
648 'Implement loadImage for image loading. '
649 'This feature was deprecated after v3.7.0-1.4.pre.',
650 )
651 ImageStreamCompleter loadBuffer(T key, DecoderBufferCallback decode) {
652 return _AbstractImageStreamCompleter();
653 }
654
655 /// Converts a key into an [ImageStreamCompleter], and begins fetching the
656 /// image.
657 ///
658 /// For backwards-compatibility the default implementation of this method returns
659 /// an object that will cause [resolveStreamForKey] to consult [loadBuffer].
660 /// However, implementors of this interface should only override this method
661 /// and not [loadBuffer], which is deprecated.
662 ///
663 /// The [decode] callback provides the logic to obtain the codec for the
664 /// image.
665 ///
666 /// See also:
667 ///
668 /// * [ResizeImage], for modifying the key to account for cache dimensions.
669 // TODO(tvolkert): make abstract (https://github.com/flutter/flutter/issues/119209)
670 @protected
671 ImageStreamCompleter loadImage(T key, ImageDecoderCallback decode) {
672 return _AbstractImageStreamCompleter();
673 }
674
675 @override
676 String toString() => '${objectRuntimeType(this, 'ImageConfiguration')}()';
677}
678
679/// A class that exists to facilitate backwards compatibility in the transition
680/// from [ImageProvider.loadBuffer] to [ImageProvider.loadImage]
681class _AbstractImageStreamCompleter extends ImageStreamCompleter {}
682
683/// Key for the image obtained by an [AssetImage] or [ExactAssetImage].
684///
685/// This is used to identify the precise resource in the [imageCache].
686@immutable
687class AssetBundleImageKey {
688 /// Creates the key for an [AssetImage] or [AssetBundleImageProvider].
689 const AssetBundleImageKey({required this.bundle, required this.name, required this.scale});
690
691 /// The bundle from which the image will be obtained.
692 ///
693 /// The image is obtained by calling [AssetBundle.load] on the given [bundle]
694 /// using the key given by [name].
695 final AssetBundle bundle;
696
697 /// The key to use to obtain the resource from the [bundle]. This is the
698 /// argument passed to [AssetBundle.load].
699 final String name;
700
701 /// The scale to place in the [ImageInfo] object of the image.
702 final double scale;
703
704 @override
705 bool operator ==(Object other) {
706 if (other.runtimeType != runtimeType) {
707 return false;
708 }
709 return other is AssetBundleImageKey &&
710 other.bundle == bundle &&
711 other.name == name &&
712 other.scale == scale;
713 }
714
715 @override
716 int get hashCode => Object.hash(bundle, name, scale);
717
718 @override
719 String toString() =>
720 '${objectRuntimeType(this, 'AssetBundleImageKey')}(bundle: $bundle, name: "$name", scale: $scale)';
721}
722
723/// A subclass of [ImageProvider] that knows about [AssetBundle]s.
724///
725/// This factors out the common logic of [AssetBundle]-based [ImageProvider]
726/// classes, simplifying what subclasses must implement to just [obtainKey].
727abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKey> {
728 /// Abstract const constructor. This constructor enables subclasses to provide
729 /// const constructors so that they can be used in const expressions.
730 const AssetBundleImageProvider();
731
732 @override
733 ImageStreamCompleter loadImage(AssetBundleImageKey key, ImageDecoderCallback decode) {
734 InformationCollector? collector;
735 assert(() {
736 collector = () => <DiagnosticsNode>[
737 DiagnosticsProperty<ImageProvider>('Image provider', this),
738 DiagnosticsProperty<AssetBundleImageKey>('Image key', key),
739 ];
740 return true;
741 }());
742 return MultiFrameImageStreamCompleter(
743 codec: _loadAsync(key, decode: decode),
744 scale: key.scale,
745 debugLabel: key.name,
746 informationCollector: collector,
747 );
748 }
749
750 /// Converts a key into an [ImageStreamCompleter], and begins fetching the
751 /// image.
752 @override
753 ImageStreamCompleter loadBuffer(AssetBundleImageKey key, DecoderBufferCallback decode) {
754 InformationCollector? collector;
755 assert(() {
756 collector = () => <DiagnosticsNode>[
757 DiagnosticsProperty<ImageProvider>('Image provider', this),
758 DiagnosticsProperty<AssetBundleImageKey>('Image key', key),
759 ];
760 return true;
761 }());
762 return MultiFrameImageStreamCompleter(
763 codec: _loadAsync(key, decode: decode),
764 scale: key.scale,
765 debugLabel: key.name,
766 informationCollector: collector,
767 );
768 }
769
770 /// Fetches the image from the asset bundle, decodes it, and returns a
771 /// corresponding [ImageInfo] object.
772 ///
773 /// This function is used by [loadImage].
774 @protected
775 Future<ui.Codec> _loadAsync(
776 AssetBundleImageKey key, {
777 required _SimpleDecoderCallback decode,
778 }) async {
779 final ui.ImmutableBuffer buffer;
780 // Hot reload/restart could change whether an asset bundle or key in a
781 // bundle are available, or if it is a network backed bundle.
782 try {
783 buffer = await key.bundle.loadBuffer(key.name);
784 } on FlutterError {
785 PaintingBinding.instance.imageCache.evict(key);
786 rethrow;
787 }
788 return decode(buffer);
789 }
790}
791
792/// Key used internally by [ResizeImage].
793///
794/// This is used to identify the precise resource in the [imageCache].
795@immutable
796class ResizeImageKey {
797 // Private constructor so nobody from the outside can poison the image cache
798 // with this key. It's only accessible to [ResizeImage] internally.
799 const ResizeImageKey._(
800 this._providerCacheKey,
801 this._policy,
802 this._width,
803 this._height,
804 this._allowUpscaling,
805 );
806
807 final Object _providerCacheKey;
808 final ResizeImagePolicy _policy;
809 final int? _width;
810 final int? _height;
811 final bool _allowUpscaling;
812
813 @override
814 bool operator ==(Object other) {
815 if (other.runtimeType != runtimeType) {
816 return false;
817 }
818 return other is ResizeImageKey &&
819 other._providerCacheKey == _providerCacheKey &&
820 other._policy == _policy &&
821 other._width == _width &&
822 other._height == _height &&
823 other._allowUpscaling == _allowUpscaling;
824 }
825
826 @override
827 int get hashCode => Object.hash(_providerCacheKey, _policy, _width, _height, _allowUpscaling);
828}
829
830/// Configures the behavior for [ResizeImage].
831///
832/// This is used in [ResizeImage.policy] to affect how the [ResizeImage.width]
833/// and [ResizeImage.height] properties are interpreted.
834enum ResizeImagePolicy {
835 /// Sizes the image to the exact width and height specified by
836 /// [ResizeImage.width] and [ResizeImage.height].
837 ///
838 /// If [ResizeImage.width] and [ResizeImage.height] are both non-null, the
839 /// output image will have the specified width and height (with the
840 /// corresponding aspect ratio) regardless of whether it matches the source
841 /// image's intrinsic aspect ratio. This case is similar to [BoxFit.fill].
842 ///
843 /// If only one of `width` and `height` is non-null, then the output image
844 /// will be scaled to the associated width or height, and the other dimension
845 /// will take whatever value is needed to maintain the image's original aspect
846 /// ratio. These cases are similar to [BoxFit.fitWidth] and
847 /// [BoxFit.fitHeight], respectively.
848 ///
849 /// If [ResizeImage.allowUpscaling] is false (the default), the width and the
850 /// height of the output image will each be clamped to the intrinsic width and
851 /// height of the image. This may result in a different aspect ratio than the
852 /// aspect ratio specified by the target width and height (e.g. if the height
853 /// gets clamped downwards but the width does not).
854 ///
855 /// ## Examples
856 ///
857 /// The examples below show how [ResizeImagePolicy.exact] works in various
858 /// scenarios. In each example, the source image has a size of 300x200
859 /// (landscape orientation), the red box is a 150x150 square, and the green
860 /// box is a 400x400 square.
861 ///
862 /// <table>
863 /// <tr>
864 /// <td>Scenario</td>
865 /// <td>Output</td>
866 /// </tr>
867 /// <tr>
868 /// <td>
869 ///
870 /// ```dart
871 /// const ResizeImage(
872 /// AssetImage('dragon_cake.jpg'),
873 /// width: 150,
874 /// height: 150,
875 /// )
876 /// ```
877 ///
878 /// </td>
879 /// <td>
880 ///
881 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_150x150_false.png)
882 ///
883 /// </td>
884 /// </tr>
885 /// <tr>
886 /// <td>
887 ///
888 /// ```dart
889 /// const ResizeImage(
890 /// AssetImage('dragon_cake.jpg'),
891 /// width: 150,
892 /// )
893 /// ```
894 ///
895 /// </td>
896 /// <td>
897 ///
898 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_150xnull_false.png)
899 ///
900 /// </td>
901 /// </tr>
902 /// <tr>
903 /// <td>
904 ///
905 /// ```dart
906 /// const ResizeImage(
907 /// AssetImage('dragon_cake.jpg'),
908 /// height: 150,
909 /// )
910 /// ```
911 ///
912 /// </td>
913 /// <td>
914 ///
915 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_nullx150_false.png)
916 ///
917 /// </td>
918 /// </tr>
919 /// <tr>
920 /// <td>
921 ///
922 /// ```dart
923 /// const ResizeImage(
924 /// AssetImage('dragon_cake.jpg'),
925 /// width: 400,
926 /// height: 400,
927 /// )
928 /// ```
929 ///
930 /// </td>
931 /// <td>
932 ///
933 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_400x400_false.png)
934 ///
935 /// </td>
936 /// </tr>
937 /// <tr>
938 /// <td>
939 ///
940 /// ```dart
941 /// const ResizeImage(
942 /// AssetImage('dragon_cake.jpg'),
943 /// width: 400,
944 /// )
945 /// ```
946 ///
947 /// </td>
948 /// <td>
949 ///
950 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_400xnull_false.png)
951 ///
952 /// </td>
953 /// </tr>
954 /// <tr>
955 /// <td>
956 ///
957 /// ```dart
958 /// const ResizeImage(
959 /// AssetImage('dragon_cake.jpg'),
960 /// height: 400,
961 /// )
962 /// ```
963 ///
964 /// </td>
965 /// <td>
966 ///
967 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_nullx400_false.png)
968 ///
969 /// </td>
970 /// </tr>
971 /// <tr>
972 /// <td>
973 ///
974 /// ```dart
975 /// const ResizeImage(
976 /// AssetImage('dragon_cake.jpg'),
977 /// width: 400,
978 /// height: 400,
979 /// allowUpscaling: true,
980 /// )
981 /// ```
982 ///
983 /// </td>
984 /// <td>
985 ///
986 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_400x400_true.png)
987 ///
988 /// </td>
989 /// </tr>
990 /// <tr>
991 /// <td>
992 ///
993 /// ```dart
994 /// const ResizeImage(
995 /// AssetImage('dragon_cake.jpg'),
996 /// width: 400,
997 /// allowUpscaling: true,
998 /// )
999 /// ```
1000 ///
1001 /// </td>
1002 /// <td>
1003 ///
1004 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_400xnull_true.png)
1005 ///
1006 /// </td>
1007 /// </tr>
1008 /// <tr>
1009 /// <td>
1010 ///
1011 /// ```dart
1012 /// const ResizeImage(
1013 /// AssetImage('dragon_cake.jpg'),
1014 /// height: 400,
1015 /// allowUpscaling: true,
1016 /// )
1017 /// ```
1018 ///
1019 /// </td>
1020 /// <td>
1021 ///
1022 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_exact_nullx400_true.png)
1023 ///
1024 /// </td>
1025 /// </tr>
1026 /// </table>
1027 exact,
1028
1029 /// Scales the image as necessary to ensure that it fits within the bounding
1030 /// box specified by [ResizeImage.width] and [ResizeImage.height] while
1031 /// maintaining its aspect ratio.
1032 ///
1033 /// If [ResizeImage.allowUpscaling] is true, the image will be scaled up or
1034 /// down to best fit the bounding box; otherwise it will only ever be scaled
1035 /// down.
1036 ///
1037 /// This is conceptually similar to [BoxFit.contain].
1038 ///
1039 /// ## Examples
1040 ///
1041 /// The examples below show how [ResizeImagePolicy.fit] works in various
1042 /// scenarios. In each example, the source image has a size of 300x200
1043 /// (landscape orientation), the red box is a 150x150 square, and the green
1044 /// box is a 400x400 square.
1045 ///
1046 /// <table>
1047 /// <tr>
1048 /// <td>Scenario</td>
1049 /// <td>Output</td>
1050 /// </tr>
1051 /// <tr>
1052 /// <td>
1053 ///
1054 /// ```dart
1055 /// const ResizeImage(
1056 /// AssetImage('dragon_cake.jpg'),
1057 /// policy: ResizeImagePolicy.fit,
1058 /// width: 150,
1059 /// height: 150,
1060 /// )
1061 /// ```
1062 ///
1063 /// </td>
1064 /// <td>
1065 ///
1066 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_150x150_false.png)
1067 ///
1068 /// </td>
1069 /// </tr>
1070 /// <tr>
1071 /// <td>
1072 ///
1073 /// ```dart
1074 /// const ResizeImage(
1075 /// AssetImage('dragon_cake.jpg'),
1076 /// policy: ResizeImagePolicy.fit,
1077 /// width: 150,
1078 /// )
1079 /// ```
1080 ///
1081 /// </td>
1082 /// <td>
1083 ///
1084 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_150xnull_false.png)
1085 ///
1086 /// </td>
1087 /// </tr>
1088 /// <tr>
1089 /// <td>
1090 ///
1091 /// ```dart
1092 /// const ResizeImage(
1093 /// AssetImage('dragon_cake.jpg'),
1094 /// policy: ResizeImagePolicy.fit,
1095 /// height: 150,
1096 /// )
1097 /// ```
1098 ///
1099 /// </td>
1100 /// <td>
1101 ///
1102 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_nullx150_false.png)
1103 ///
1104 /// </td>
1105 /// </tr>
1106 /// <tr>
1107 /// <td>
1108 ///
1109 /// ```dart
1110 /// const ResizeImage(
1111 /// AssetImage('dragon_cake.jpg'),
1112 /// policy: ResizeImagePolicy.fit,
1113 /// width: 400,
1114 /// height: 400,
1115 /// )
1116 /// ```
1117 ///
1118 /// </td>
1119 /// <td>
1120 ///
1121 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_400x400_false.png)
1122 ///
1123 /// </td>
1124 /// </tr>
1125 /// <tr>
1126 /// <td>
1127 ///
1128 /// ```dart
1129 /// const ResizeImage(
1130 /// AssetImage('dragon_cake.jpg'),
1131 /// policy: ResizeImagePolicy.fit,
1132 /// width: 400,
1133 /// )
1134 /// ```
1135 ///
1136 /// </td>
1137 /// <td>
1138 ///
1139 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_400xnull_false.png)
1140 ///
1141 /// </td>
1142 /// </tr>
1143 /// <tr>
1144 /// <td>
1145 ///
1146 /// ```dart
1147 /// const ResizeImage(
1148 /// AssetImage('dragon_cake.jpg'),
1149 /// policy: ResizeImagePolicy.fit,
1150 /// height: 400,
1151 /// )
1152 /// ```
1153 ///
1154 /// </td>
1155 /// <td>
1156 ///
1157 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_nullx400_false.png)
1158 ///
1159 /// </td>
1160 /// </tr>
1161 /// <tr>
1162 /// <td>
1163 ///
1164 /// ```dart
1165 /// const ResizeImage(
1166 /// AssetImage('dragon_cake.jpg'),
1167 /// policy: ResizeImagePolicy.fit,
1168 /// width: 400,
1169 /// height: 400,
1170 /// allowUpscaling: true,
1171 /// )
1172 /// ```
1173 ///
1174 /// </td>
1175 /// <td>
1176 ///
1177 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_400x400_true.png)
1178 ///
1179 /// </td>
1180 /// </tr>
1181 /// <tr>
1182 /// <td>
1183 ///
1184 /// ```dart
1185 /// const ResizeImage(
1186 /// AssetImage('dragon_cake.jpg'),
1187 /// policy: ResizeImagePolicy.fit,
1188 /// width: 400,
1189 /// allowUpscaling: true,
1190 /// )
1191 /// ```
1192 ///
1193 /// </td>
1194 /// <td>
1195 ///
1196 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_400xnull_true.png)
1197 ///
1198 /// </td>
1199 /// </tr>
1200 /// <tr>
1201 /// <td>
1202 ///
1203 /// ```dart
1204 /// const ResizeImage(
1205 /// AssetImage('dragon_cake.jpg'),
1206 /// policy: ResizeImagePolicy.fit,
1207 /// height: 400,
1208 /// allowUpscaling: true,
1209 /// )
1210 /// ```
1211 ///
1212 /// </td>
1213 /// <td>
1214 ///
1215 /// ![](https://flutter.github.io/assets-for-api-docs/assets/painting/resize_image_policy_fit_nullx400_true.png)
1216 ///
1217 /// </td>
1218 /// </tr>
1219 /// </table>
1220 fit,
1221}
1222
1223/// Instructs Flutter to decode the image at the specified dimensions
1224/// instead of at its native size.
1225///
1226/// This allows finer control of the size of the image in [ImageCache] and is
1227/// generally used to reduce the memory footprint of [ImageCache].
1228///
1229/// The decoded image may still be displayed at sizes other than the
1230/// cached size provided here.
1231///
1232/// The [width] and [height] parameters determine the image resolution.
1233/// These values also set the image's width & height in logical pixels
1234/// if it is unconstrained.
1235///
1236/// {@tool snippet}
1237/// This example shows how to size the image to half of the screen's width.
1238///
1239/// ```dart
1240/// Image(
1241/// image: ResizeImage(
1242/// FileImage(File('path/to/image')),
1243/// width: MediaQuery.widthOf(context) ~/ 2, // Half of the screen's width.
1244/// ),
1245/// );
1246/// ```
1247/// {@end-tool}
1248///
1249/// See also:
1250///
1251/// * [ui.FlutterView.devicePixelRatio], used to convert between physical and
1252/// logical pixels.
1253class ResizeImage extends ImageProvider<ResizeImageKey> {
1254 /// Creates an ImageProvider that decodes the image to the specified size.
1255 ///
1256 /// The cached image will be directly decoded and stored at the resolution
1257 /// defined by `width` and `height`. The image will lose detail and
1258 /// use less memory if resized to a size smaller than the native size.
1259 ///
1260 /// At least one of `width` and `height` must be non-null.
1261 const ResizeImage(
1262 this.imageProvider, {
1263 this.width,
1264 this.height,
1265 this.policy = ResizeImagePolicy.exact,
1266 this.allowUpscaling = false,
1267 }) : assert(width != null || height != null);
1268
1269 /// The [ImageProvider] that this class wraps.
1270 final ImageProvider imageProvider;
1271
1272 /// The width the image should decode to and cache.
1273 ///
1274 /// At least one of this and [height] must be non-null.
1275 final int? width;
1276
1277 /// The height the image should decode to and cache.
1278 ///
1279 /// At least one of this and [width] must be non-null.
1280 final int? height;
1281
1282 /// The policy that determines how [width] and [height] are interpreted.
1283 ///
1284 /// Defaults to [ResizeImagePolicy.exact].
1285 final ResizeImagePolicy policy;
1286
1287 /// Whether the [width] and [height] parameters should be clamped to the
1288 /// intrinsic width and height of the image.
1289 ///
1290 /// In general, it is better for memory usage to avoid scaling the image
1291 /// beyond its intrinsic dimensions when decoding it. If there is a need to
1292 /// scale an image larger, it is better to apply a scale to the canvas, or
1293 /// to use an appropriate [Image.fit].
1294 final bool allowUpscaling;
1295
1296 /// Composes the `provider` in a [ResizeImage] only when `cacheWidth` and
1297 /// `cacheHeight` are not both null.
1298 ///
1299 /// When `cacheWidth` and `cacheHeight` are both null, this will return the
1300 /// `provider` directly.
1301 static ImageProvider<Object> resizeIfNeeded(
1302 int? cacheWidth,
1303 int? cacheHeight,
1304 ImageProvider<Object> provider,
1305 ) {
1306 if (cacheWidth != null || cacheHeight != null) {
1307 return ResizeImage(provider, width: cacheWidth, height: cacheHeight);
1308 }
1309 return provider;
1310 }
1311
1312 @override
1313 @Deprecated(
1314 'Implement loadImage for image loading. '
1315 'This feature was deprecated after v3.7.0-1.4.pre.',
1316 )
1317 ImageStreamCompleter loadBuffer(ResizeImageKey key, DecoderBufferCallback decode) {
1318 Future<ui.Codec> decodeResize(
1319 ui.ImmutableBuffer buffer, {
1320 int? cacheWidth,
1321 int? cacheHeight,
1322 bool? allowUpscaling,
1323 }) {
1324 assert(
1325 cacheWidth == null && cacheHeight == null && allowUpscaling == null,
1326 'ResizeImage cannot be composed with another ImageProvider that applies '
1327 'cacheWidth, cacheHeight, or allowUpscaling.',
1328 );
1329 return decode(
1330 buffer,
1331 cacheWidth: width,
1332 cacheHeight: height,
1333 allowUpscaling: this.allowUpscaling,
1334 );
1335 }
1336
1337 final ImageStreamCompleter completer = imageProvider.loadBuffer(
1338 key._providerCacheKey,
1339 decodeResize,
1340 );
1341 if (!kReleaseMode) {
1342 completer.debugLabel = '${completer.debugLabel} - Resized(${key._width}×${key._height})';
1343 }
1344 _configureErrorListener(completer, key);
1345 return completer;
1346 }
1347
1348 @override
1349 ImageStreamCompleter loadImage(ResizeImageKey key, ImageDecoderCallback decode) {
1350 Future<ui.Codec> decodeResize(
1351 ui.ImmutableBuffer buffer, {
1352 ui.TargetImageSizeCallback? getTargetSize,
1353 }) {
1354 assert(
1355 getTargetSize == null,
1356 'ResizeImage cannot be composed with another ImageProvider that applies '
1357 'getTargetSize.',
1358 );
1359 return decode(
1360 buffer,
1361 getTargetSize: (int intrinsicWidth, int intrinsicHeight) {
1362 switch (policy) {
1363 case ResizeImagePolicy.exact:
1364 int? targetWidth = width;
1365 int? targetHeight = height;
1366
1367 if (!allowUpscaling) {
1368 if (targetWidth != null && targetWidth > intrinsicWidth) {
1369 targetWidth = intrinsicWidth;
1370 }
1371 if (targetHeight != null && targetHeight > intrinsicHeight) {
1372 targetHeight = intrinsicHeight;
1373 }
1374 }
1375
1376 return ui.TargetImageSize(width: targetWidth, height: targetHeight);
1377 case ResizeImagePolicy.fit:
1378 final double aspectRatio = intrinsicWidth / intrinsicHeight;
1379 final int maxWidth = width ?? intrinsicWidth;
1380 final int maxHeight = height ?? intrinsicHeight;
1381 int targetWidth = intrinsicWidth;
1382 int targetHeight = intrinsicHeight;
1383
1384 if (targetWidth > maxWidth) {
1385 targetWidth = maxWidth;
1386 targetHeight = targetWidth ~/ aspectRatio;
1387 }
1388
1389 if (targetHeight > maxHeight) {
1390 targetHeight = maxHeight;
1391 targetWidth = (targetHeight * aspectRatio).floor();
1392 }
1393
1394 if (allowUpscaling) {
1395 if (width == null) {
1396 assert(height != null);
1397 targetHeight = height!;
1398 targetWidth = (targetHeight * aspectRatio).floor();
1399 } else if (height == null) {
1400 targetWidth = width!;
1401 targetHeight = targetWidth ~/ aspectRatio;
1402 } else {
1403 final int derivedMaxWidth = (maxHeight * aspectRatio).floor();
1404 final int derivedMaxHeight = maxWidth ~/ aspectRatio;
1405 targetWidth = math.min(maxWidth, derivedMaxWidth);
1406 targetHeight = math.min(maxHeight, derivedMaxHeight);
1407 }
1408 }
1409
1410 return ui.TargetImageSize(width: targetWidth, height: targetHeight);
1411 }
1412 },
1413 );
1414 }
1415
1416 final ImageStreamCompleter completer = imageProvider.loadImage(
1417 key._providerCacheKey,
1418 decodeResize,
1419 );
1420 if (!kReleaseMode) {
1421 completer.debugLabel = '${completer.debugLabel} - Resized(${key._width}×${key._height})';
1422 }
1423 _configureErrorListener(completer, key);
1424 return completer;
1425 }
1426
1427 void _configureErrorListener(ImageStreamCompleter completer, ResizeImageKey key) {
1428 completer.addEphemeralErrorListener((Object exception, StackTrace? stackTrace) {
1429 // The microtask is scheduled because of the same reason as NetworkImage:
1430 // Depending on where the exception was thrown, the image cache may not
1431 // have had a chance to track the key in the cache at all.
1432 // Schedule a microtask to give the cache a chance to add the key.
1433 scheduleMicrotask(() {
1434 PaintingBinding.instance.imageCache.evict(key);
1435 });
1436 });
1437 }
1438
1439 @override
1440 Future<ResizeImageKey> obtainKey(ImageConfiguration configuration) {
1441 Completer<ResizeImageKey>? completer;
1442 // If the imageProvider.obtainKey future is synchronous, then we will be able to fill in result with
1443 // a value before completer is initialized below.
1444 SynchronousFuture<ResizeImageKey>? result;
1445 imageProvider.obtainKey(configuration).then((Object key) {
1446 if (completer == null) {
1447 // This future has completed synchronously (completer was never assigned),
1448 // so we can directly create the synchronous result to return.
1449 result = SynchronousFuture<ResizeImageKey>(
1450 ResizeImageKey._(key, policy, width, height, allowUpscaling),
1451 );
1452 } else {
1453 // This future did not synchronously complete.
1454 completer.complete(ResizeImageKey._(key, policy, width, height, allowUpscaling));
1455 }
1456 });
1457 if (result != null) {
1458 return result!;
1459 }
1460 // If the code reaches here, it means the imageProvider.obtainKey was not
1461 // completed sync, so we initialize the completer for completion later.
1462 completer = Completer<ResizeImageKey>();
1463 return completer.future;
1464 }
1465}
1466
1467/// The strategy for [Image.network] and [NetworkImage] to decide whether to
1468/// display images in HTML elements contained in a platform view instead of
1469/// fetching bytes.
1470///
1471/// See [Image.network] for more explanation on the impact.
1472///
1473/// This option is only effective on the Web platform. Other platforms always
1474/// display network images by fetching bytes.
1475enum WebHtmlElementStrategy {
1476 /// Only show images by fetching bytes, and report errors if the fetch
1477 /// encounters errors.
1478 never,
1479
1480 /// Prefer fetching bytes to display images, and fall back to HTML elements
1481 /// when fetching bytes is not available.
1482 ///
1483 /// This strategy uses HTML elements only if `headers` is empty and the fetch
1484 /// encounters errors. Errors may still be reported if neither approach works.
1485 fallback,
1486
1487 /// Prefer HTML elements to display images, and fall back to fetching bytes
1488 /// when HTML elements do not work.
1489 ///
1490 /// This strategy fetches bytes only if `headers` is not empty, since HTML
1491 /// elements do not support headers. Errors may still be reported if neither
1492 /// approach works.
1493 prefer,
1494}
1495
1496/// Fetches the given URL from the network, associating it with the given scale.
1497///
1498/// The image will be cached regardless of cache headers from the server.
1499///
1500/// Typically this class resolves to an image stream that ultimately produces
1501/// [dart:ui.Image]s. On the Web platform, the [webHtmlElementStrategy]
1502/// parameter can be used to make the image stream ultimately produce an
1503/// [WebImageInfo] instead, which makes [Image.network] display the image as an
1504/// HTML element in a platform view. The feature is by default turned off
1505/// ([WebHtmlElementStrategy.never]). See [Image.network] for more explanation.
1506///
1507/// See also:
1508///
1509/// * [Image.network] for a shorthand of an [Image] widget backed by [NetworkImage].
1510/// * The example at [ImageProvider], which shows a custom variant of this class
1511/// that applies different logic for fetching the image.
1512// TODO(ianh): Find some way to honor cache headers to the extent that when the
1513// last reference to an image is released, we proactively evict the image from
1514// our cache if the headers describe the image as having expired at that point.
1515abstract class NetworkImage extends ImageProvider<NetworkImage> {
1516 /// Creates an object that fetches the image at the given URL.
1517 ///
1518 /// The [scale] argument is the linear scale factor for drawing this image at
1519 /// its intended size. See [ImageInfo.scale] for more information.
1520 ///
1521 /// The [webHtmlElementStrategy] option is by default
1522 /// [WebHtmlElementStrategy.never].
1523 const factory NetworkImage(
1524 String url, {
1525 double scale,
1526 Map<String, String>? headers,
1527 WebHtmlElementStrategy webHtmlElementStrategy,
1528 }) = network_image.NetworkImage;
1529
1530 /// The URL from which the image will be fetched.
1531 String get url;
1532
1533 /// The scale to place in the [ImageInfo] object of the image.
1534 double get scale;
1535
1536 /// The HTTP headers that will be used with [HttpClient.get] to fetch image from network.
1537 ///
1538 /// When running Flutter on the web, headers are not used.
1539 Map<String, String>? get headers;
1540
1541 /// On the Web platform, specifies when the image is loaded as a
1542 /// [WebImageInfo], which causes [Image.network] to display the image in an
1543 /// HTML element in a platform view.
1544 ///
1545 /// See [Image.network] for more explanation.
1546 ///
1547 /// Defaults to [WebHtmlElementStrategy.never].
1548 ///
1549 /// Has no effect on other platforms, which always fetch bytes.
1550 WebHtmlElementStrategy get webHtmlElementStrategy;
1551
1552 @override
1553 ImageStreamCompleter loadBuffer(NetworkImage key, DecoderBufferCallback decode);
1554
1555 @override
1556 ImageStreamCompleter loadImage(NetworkImage key, ImageDecoderCallback decode);
1557}
1558
1559/// Decodes the given [File] object as an image, associating it with the given
1560/// scale.
1561///
1562/// The provider does not monitor the file for changes. If you expect the
1563/// underlying data to change, you should call the [evict] method.
1564///
1565/// See also:
1566///
1567/// * [Image.file] for a shorthand of an [Image] widget backed by [FileImage].
1568@immutable
1569class FileImage extends ImageProvider<FileImage> {
1570 /// Creates an object that decodes a [File] as an image.
1571 const FileImage(this.file, {this.scale = 1.0});
1572
1573 /// The file to decode into an image.
1574 final File file;
1575
1576 /// The scale to place in the [ImageInfo] object of the image.
1577 final double scale;
1578
1579 @override
1580 Future<FileImage> obtainKey(ImageConfiguration configuration) {
1581 return SynchronousFuture<FileImage>(this);
1582 }
1583
1584 @override
1585 ImageStreamCompleter loadBuffer(FileImage key, DecoderBufferCallback decode) {
1586 return MultiFrameImageStreamCompleter(
1587 codec: _loadAsync(key, decode: decode),
1588 scale: key.scale,
1589 debugLabel: key.file.path,
1590 informationCollector: () => <DiagnosticsNode>[ErrorDescription('Path: ${file.path}')],
1591 );
1592 }
1593
1594 @override
1595 @protected
1596 ImageStreamCompleter loadImage(FileImage key, ImageDecoderCallback decode) {
1597 return MultiFrameImageStreamCompleter(
1598 codec: _loadAsync(key, decode: decode),
1599 scale: key.scale,
1600 debugLabel: key.file.path,
1601 informationCollector: () => <DiagnosticsNode>[ErrorDescription('Path: ${file.path}')],
1602 );
1603 }
1604
1605 Future<ui.Codec> _loadAsync(FileImage key, {required _SimpleDecoderCallback decode}) async {
1606 assert(key == this);
1607 // TODO(jonahwilliams): making this sync caused test failures that seem to
1608 // indicate that we can fail to call evict unless at least one await has
1609 // occurred in the test.
1610 // https://github.com/flutter/flutter/issues/113044
1611 final int lengthInBytes = await file.length();
1612 if (lengthInBytes == 0) {
1613 // The file may become available later.
1614 PaintingBinding.instance.imageCache.evict(key);
1615 throw StateError('$file is empty and cannot be loaded as an image.');
1616 }
1617 return (file.runtimeType == File)
1618 ? decode(await ui.ImmutableBuffer.fromFilePath(file.path))
1619 : decode(await ui.ImmutableBuffer.fromUint8List(await file.readAsBytes()));
1620 }
1621
1622 @override
1623 bool operator ==(Object other) {
1624 if (other.runtimeType != runtimeType) {
1625 return false;
1626 }
1627 return other is FileImage && other.file.path == file.path && other.scale == scale;
1628 }
1629
1630 @override
1631 int get hashCode => Object.hash(file.path, scale);
1632
1633 @override
1634 String toString() =>
1635 '${objectRuntimeType(this, 'FileImage')}("${file.path}", scale: ${scale.toStringAsFixed(1)})';
1636}
1637
1638/// Decodes the given [Uint8List] buffer as an image, associating it with the
1639/// given scale.
1640///
1641/// The provided [bytes] buffer should not be changed after it is provided
1642/// to a [MemoryImage]. To provide an [ImageStream] that represents an image
1643/// that changes over time, consider creating a new subclass of [ImageProvider]
1644/// whose [loadImage] method returns a subclass of [ImageStreamCompleter] that
1645/// can handle providing multiple images.
1646///
1647/// See also:
1648///
1649/// * [Image.memory] for a shorthand of an [Image] widget backed by [MemoryImage].
1650@immutable
1651class MemoryImage extends ImageProvider<MemoryImage> {
1652 /// Creates an object that decodes a [Uint8List] buffer as an image.
1653 const MemoryImage(this.bytes, {this.scale = 1.0});
1654
1655 /// The bytes to decode into an image.
1656 ///
1657 /// The bytes represent encoded image bytes and can be encoded in any of the
1658 /// following supported image formats: {@macro dart.ui.imageFormats}
1659 ///
1660 /// See also:
1661 ///
1662 /// * [PaintingBinding.instantiateImageCodecWithSize]
1663 final Uint8List bytes;
1664
1665 /// The scale to place in the [ImageInfo] object of the image.
1666 ///
1667 /// See also:
1668 ///
1669 /// * [ImageInfo.scale], which gives more information on how this scale is
1670 /// applied.
1671 final double scale;
1672
1673 @override
1674 Future<MemoryImage> obtainKey(ImageConfiguration configuration) {
1675 return SynchronousFuture<MemoryImage>(this);
1676 }
1677
1678 @override
1679 ImageStreamCompleter loadBuffer(MemoryImage key, DecoderBufferCallback decode) {
1680 assert(key == this);
1681 return MultiFrameImageStreamCompleter(
1682 codec: _loadAsync(key, decode: decode),
1683 scale: key.scale,
1684 debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
1685 );
1686 }
1687
1688 @override
1689 ImageStreamCompleter loadImage(MemoryImage key, ImageDecoderCallback decode) {
1690 return MultiFrameImageStreamCompleter(
1691 codec: _loadAsync(key, decode: decode),
1692 scale: key.scale,
1693 debugLabel: 'MemoryImage(${describeIdentity(key.bytes)})',
1694 );
1695 }
1696
1697 Future<ui.Codec> _loadAsync(MemoryImage key, {required _SimpleDecoderCallback decode}) async {
1698 assert(key == this);
1699 return decode(await ui.ImmutableBuffer.fromUint8List(bytes));
1700 }
1701
1702 @override
1703 bool operator ==(Object other) {
1704 if (other.runtimeType != runtimeType) {
1705 return false;
1706 }
1707 return other is MemoryImage && other.bytes == bytes && other.scale == scale;
1708 }
1709
1710 @override
1711 int get hashCode => Object.hash(bytes.hashCode, scale);
1712
1713 @override
1714 String toString() =>
1715 '${objectRuntimeType(this, 'MemoryImage')}(${describeIdentity(bytes)}, scale: ${scale.toStringAsFixed(1)})';
1716}
1717
1718/// Fetches an image from an [AssetBundle], associating it with the given scale.
1719///
1720/// This implementation requires an explicit final [assetName] and [scale] on
1721/// construction, and ignores the device pixel ratio and size in the
1722/// configuration passed into [resolve]. For a resolution-aware variant that
1723/// uses the configuration to pick an appropriate image based on the device
1724/// pixel ratio and size, see [AssetImage].
1725///
1726/// ## Fetching assets
1727///
1728/// When fetching an image provided by the app itself, use the [assetName]
1729/// argument to name the asset to choose. For instance, consider a directory
1730/// `icons` with an image `heart.png`. First, the `pubspec.yaml` of the project
1731/// should specify its assets in the `flutter` section:
1732///
1733/// ```yaml
1734/// flutter:
1735/// assets:
1736/// - icons/heart.png
1737/// ```
1738///
1739/// Then, to fetch the image and associate it with scale `1.5`, use:
1740///
1741/// {@tool snippet}
1742/// ```dart
1743/// const ExactAssetImage('icons/heart.png', scale: 1.5)
1744/// ```
1745/// {@end-tool}
1746///
1747/// ## Assets in packages
1748///
1749/// To fetch an asset from a package, the [package] argument must be provided.
1750/// For instance, suppose the structure above is inside a package called
1751/// `my_icons`. Then to fetch the image, use:
1752///
1753/// {@tool snippet}
1754/// ```dart
1755/// const ExactAssetImage('icons/heart.png', scale: 1.5, package: 'my_icons')
1756/// ```
1757/// {@end-tool}
1758///
1759/// Assets used by the package itself should also be fetched using the [package]
1760/// argument as above.
1761///
1762/// If the desired asset is specified in the `pubspec.yaml` of the package, it
1763/// is bundled automatically with the app. In particular, assets used by the
1764/// package itself must be specified in its `pubspec.yaml`.
1765///
1766/// A package can also choose to have assets in its 'lib/' folder that are not
1767/// specified in its `pubspec.yaml`. In this case for those images to be
1768/// bundled, the app has to specify which ones to include. For instance a
1769/// package named `fancy_backgrounds` could have:
1770///
1771/// lib/backgrounds/background1.png
1772/// lib/backgrounds/background2.png
1773/// lib/backgrounds/background3.png
1774///
1775/// To include, say the first image, the `pubspec.yaml` of the app should specify
1776/// it in the `assets` section:
1777///
1778/// ```yaml
1779/// assets:
1780/// - packages/fancy_backgrounds/backgrounds/background1.png
1781/// ```
1782///
1783/// The `lib/` is implied, so it should not be included in the asset path.
1784///
1785/// See also:
1786///
1787/// * [Image.asset] for a shorthand of an [Image] widget backed by
1788/// [ExactAssetImage] when using a scale.
1789@immutable
1790class ExactAssetImage extends AssetBundleImageProvider {
1791 /// Creates an object that fetches the given image from an asset bundle.
1792 ///
1793 /// The [scale] argument defaults to 1. The [bundle] argument may be null, in
1794 /// which case the bundle provided in the [ImageConfiguration] passed to the
1795 /// [resolve] call will be used instead.
1796 ///
1797 /// The [package] argument must be non-null when fetching an asset that is
1798 /// included in a package. See the documentation for the [ExactAssetImage] class
1799 /// itself for details.
1800 const ExactAssetImage(this.assetName, {this.scale = 1.0, this.bundle, this.package});
1801
1802 /// The name of the asset.
1803 final String assetName;
1804
1805 /// The key to use to obtain the resource from the [bundle]. This is the
1806 /// argument passed to [AssetBundle.load].
1807 String get keyName => package == null ? assetName : 'packages/$package/$assetName';
1808
1809 /// The scale to place in the [ImageInfo] object of the image.
1810 final double scale;
1811
1812 /// The bundle from which the image will be obtained.
1813 ///
1814 /// If the provided [bundle] is null, the bundle provided in the
1815 /// [ImageConfiguration] passed to the [resolve] call will be used instead. If
1816 /// that is also null, the [rootBundle] is used.
1817 ///
1818 /// The image is obtained by calling [AssetBundle.load] on the given [bundle]
1819 /// using the key given by [keyName].
1820 final AssetBundle? bundle;
1821
1822 /// The name of the package from which the image is included. See the
1823 /// documentation for the [ExactAssetImage] class itself for details.
1824 final String? package;
1825
1826 @override
1827 Future<AssetBundleImageKey> obtainKey(ImageConfiguration configuration) {
1828 return SynchronousFuture<AssetBundleImageKey>(
1829 AssetBundleImageKey(
1830 bundle: bundle ?? configuration.bundle ?? rootBundle,
1831 name: keyName,
1832 scale: scale,
1833 ),
1834 );
1835 }
1836
1837 @override
1838 bool operator ==(Object other) {
1839 if (other.runtimeType != runtimeType) {
1840 return false;
1841 }
1842 return other is ExactAssetImage &&
1843 other.keyName == keyName &&
1844 other.scale == scale &&
1845 other.bundle == bundle;
1846 }
1847
1848 @override
1849 int get hashCode => Object.hash(keyName, scale, bundle);
1850
1851 @override
1852 String toString() =>
1853 '${objectRuntimeType(this, 'ExactAssetImage')}(name: "$keyName", scale: ${scale.toStringAsFixed(1)}, bundle: $bundle)';
1854}
1855
1856// A completer used when resolving an image fails sync.
1857class _ErrorImageCompleter extends ImageStreamCompleter {}
1858
1859/// The exception thrown when the HTTP request to load a network image fails.
1860class NetworkImageLoadException implements Exception {
1861 /// Creates a [NetworkImageLoadException] with the specified http [statusCode]
1862 /// and [uri].
1863 NetworkImageLoadException({required this.statusCode, required this.uri})
1864 : _message = 'HTTP request failed, statusCode: $statusCode, $uri';
1865
1866 /// The HTTP status code from the server.
1867 final int statusCode;
1868
1869 /// A human-readable error message.
1870 final String _message;
1871
1872 /// Resolved URL of the requested image.
1873 final Uri uri;
1874
1875 @override
1876 String toString() => _message;
1877}
1878