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 | /// @docImport 'dart:ui'; |
6 | /// |
7 | /// @docImport 'package:flutter/widgets.dart'; |
8 | /// @docImport 'package:flutter_driver/driver_extension.dart'; |
9 | library; |
10 | |
11 | import 'dart:ui' as ui; |
12 | import 'package:flutter/foundation.dart'; |
13 | import 'package:flutter/services.dart' show ServicesBinding; |
14 | |
15 | import 'image_cache.dart'; |
16 | import 'shader_warm_up.dart'; |
17 | |
18 | /// Binding for the painting library. |
19 | /// |
20 | /// Hooks into the cache eviction logic to clear the image cache. |
21 | /// |
22 | /// Requires the [ServicesBinding] to be mixed in earlier. |
23 | mixin PaintingBinding on BindingBase, ServicesBinding { |
24 | @override |
25 | void initInstances() { |
26 | super.initInstances(); |
27 | _instance = this; |
28 | _imageCache = createImageCache(); |
29 | shaderWarmUp?.execute(); |
30 | } |
31 | |
32 | /// The current [PaintingBinding], if one has been created. |
33 | /// |
34 | /// Provides access to the features exposed by this mixin. The binding must |
35 | /// be initialized before using this getter; this is typically done by calling |
36 | /// [runApp] or [WidgetsFlutterBinding.ensureInitialized]. |
37 | static PaintingBinding get instance => BindingBase.checkInstance(_instance); |
38 | static PaintingBinding? _instance; |
39 | |
40 | /// [ShaderWarmUp] instance to be executed during [initInstances]. |
41 | /// |
42 | /// Defaults to `null`, meaning no shader warm-up is done. Some platforms may |
43 | /// not support shader warm-up before at least one frame has been displayed. |
44 | /// |
45 | /// If the application has scenes that require the compilation of complex |
46 | /// shaders, it may cause jank in the middle of an animation or interaction. |
47 | /// In that case, setting [shaderWarmUp] to a custom [ShaderWarmUp] before |
48 | /// creating the binding (usually before [runApp] for normal Flutter apps, and |
49 | /// before [enableFlutterDriverExtension] for Flutter driver tests) may help |
50 | /// if that object paints the difficult scene in its |
51 | /// [ShaderWarmUp.warmUpOnCanvas] method, as this allows Flutter to |
52 | /// pre-compile and cache the required shaders during startup. |
53 | /// |
54 | /// Currently the warm-up happens synchronously on the raster thread which |
55 | /// means the rendering of the first frame on the raster thread will be |
56 | /// postponed until the warm-up is finished. |
57 | /// |
58 | /// The warm up is only costly (100ms-200ms, depending on the shaders to |
59 | /// compile) during the first run after the installation or a data wipe. The |
60 | /// warm up does not block the platform thread so there should be no |
61 | /// "Application Not Responding" warning. |
62 | /// |
63 | /// If this is null, no shader warm-up is executed. |
64 | /// |
65 | /// See also: |
66 | /// |
67 | /// * [ShaderWarmUp], the interface for implementing custom warm-up scenes. |
68 | /// * <https://docs.flutter.dev/perf/shader> |
69 | static ShaderWarmUp? shaderWarmUp; |
70 | |
71 | /// The singleton that implements the Flutter framework's image cache. |
72 | /// |
73 | /// The cache is used internally by [ImageProvider] and should generally not |
74 | /// be accessed directly. |
75 | /// |
76 | /// The image cache is created during startup by the [createImageCache] |
77 | /// method. |
78 | ImageCache get imageCache => _imageCache; |
79 | late ImageCache _imageCache; |
80 | |
81 | /// Creates the [ImageCache] singleton (accessible via [imageCache]). |
82 | /// |
83 | /// This method can be overridden to provide a custom image cache. |
84 | @protected |
85 | ImageCache createImageCache() => ImageCache(); |
86 | |
87 | /// Calls through to [dart:ui.instantiateImageCodecFromBuffer] from [ImageCache]. |
88 | /// |
89 | /// The [buffer] parameter should be an [ui.ImmutableBuffer] instance which can |
90 | /// be acquired from [ui.ImmutableBuffer.fromUint8List] or [ui.ImmutableBuffer.fromAsset]. |
91 | /// |
92 | /// The [cacheWidth] and [cacheHeight] parameters, when specified, indicate |
93 | /// the size to decode the image to. |
94 | /// |
95 | /// Both [cacheWidth] and [cacheHeight] must be positive values greater than |
96 | /// or equal to 1, or null. It is valid to specify only one of `cacheWidth` |
97 | /// and [cacheHeight] with the other remaining null, in which case the omitted |
98 | /// dimension will be scaled to maintain the aspect ratio of the original |
99 | /// dimensions. When both are null or omitted, the image will be decoded at |
100 | /// its native resolution. |
101 | /// |
102 | /// The [allowUpscaling] parameter determines whether the `cacheWidth` or |
103 | /// [cacheHeight] parameters are clamped to the intrinsic width and height of |
104 | /// the original image. By default, the dimensions are clamped to avoid |
105 | /// unnecessary memory usage for images. Callers that wish to display an image |
106 | /// above its native resolution should prefer scaling the canvas the image is |
107 | /// drawn into. |
108 | @Deprecated( |
109 | 'Use instantiateImageCodecWithSize instead. ' |
110 | 'This feature was deprecated after v3.7.0-1.4.pre.' , |
111 | ) |
112 | Future<ui.Codec> instantiateImageCodecFromBuffer( |
113 | ui.ImmutableBuffer buffer, { |
114 | int? cacheWidth, |
115 | int? cacheHeight, |
116 | bool allowUpscaling = false, |
117 | }) { |
118 | assert(cacheWidth == null || cacheWidth > 0); |
119 | assert(cacheHeight == null || cacheHeight > 0); |
120 | return ui.instantiateImageCodecFromBuffer( |
121 | buffer, |
122 | targetWidth: cacheWidth, |
123 | targetHeight: cacheHeight, |
124 | allowUpscaling: allowUpscaling, |
125 | ); |
126 | } |
127 | |
128 | /// Calls through to [dart:ui.instantiateImageCodecWithSize] from [ImageCache]. |
129 | /// |
130 | /// The [buffer] parameter should be an [ui.ImmutableBuffer] instance which can |
131 | /// be acquired from [ui.ImmutableBuffer.fromUint8List] or |
132 | /// [ui.ImmutableBuffer.fromAsset]. |
133 | /// |
134 | /// The [getTargetSize] parameter, when specified, will be invoked and passed |
135 | /// the image's intrinsic size to determine the size to decode the image to. |
136 | /// The width and the height of the size it returns must be positive values |
137 | /// greater than or equal to 1, or null. It is valid to return a [TargetImageSize] |
138 | /// that specifies only one of `width` and `height` with the other remaining |
139 | /// null, in which case the omitted dimension will be scaled to maintain the |
140 | /// aspect ratio of the original dimensions. When both are null or omitted, |
141 | /// the image will be decoded at its native resolution (as will be the case if |
142 | /// the [getTargetSize] parameter is omitted). |
143 | Future<ui.Codec> instantiateImageCodecWithSize( |
144 | ui.ImmutableBuffer buffer, { |
145 | ui.TargetImageSizeCallback? getTargetSize, |
146 | }) { |
147 | return ui.instantiateImageCodecWithSize(buffer, getTargetSize: getTargetSize); |
148 | } |
149 | |
150 | @override |
151 | void evict(String asset) { |
152 | super.evict(asset); |
153 | imageCache.clear(); |
154 | imageCache.clearLiveImages(); |
155 | } |
156 | |
157 | @override |
158 | void handleMemoryPressure() { |
159 | super.handleMemoryPressure(); |
160 | imageCache.clear(); |
161 | } |
162 | |
163 | /// Listenable that notifies when the available fonts on the system have |
164 | /// changed. |
165 | /// |
166 | /// System fonts can change when the system installs or removes new font. To |
167 | /// correctly reflect the change, it is important to relayout text related |
168 | /// widgets when this happens. |
169 | /// |
170 | /// Objects that show text and/or measure text (e.g. via [TextPainter] or |
171 | /// [Paragraph]) should listen to this and redraw/remeasure. |
172 | Listenable get systemFonts => _systemFonts; |
173 | final _SystemFontsNotifier _systemFonts = _SystemFontsNotifier(); |
174 | |
175 | @override |
176 | Future<void> handleSystemMessage(Object systemMessage) async { |
177 | await super.handleSystemMessage(systemMessage); |
178 | final Map<String, dynamic> message = systemMessage as Map<String, dynamic>; |
179 | final String type = message['type' ] as String; |
180 | switch (type) { |
181 | case 'fontsChange' : |
182 | _systemFonts.notifyListeners(); |
183 | } |
184 | return; |
185 | } |
186 | } |
187 | |
188 | class _SystemFontsNotifier extends Listenable { |
189 | final Set<VoidCallback> _systemFontsCallbacks = <VoidCallback>{}; |
190 | |
191 | void notifyListeners() { |
192 | for (final VoidCallback callback in _systemFontsCallbacks) { |
193 | callback(); |
194 | } |
195 | } |
196 | |
197 | @override |
198 | void addListener(VoidCallback listener) { |
199 | _systemFontsCallbacks.add(listener); |
200 | } |
201 | |
202 | @override |
203 | void removeListener(VoidCallback listener) { |
204 | _systemFontsCallbacks.remove(listener); |
205 | } |
206 | } |
207 | |
208 | /// The singleton that implements the Flutter framework's image cache. |
209 | /// |
210 | /// The cache is used internally by [ImageProvider] and should generally not be |
211 | /// accessed directly. |
212 | /// |
213 | /// The image cache is created during startup by the [PaintingBinding]'s |
214 | /// [PaintingBinding.createImageCache] method. |
215 | ImageCache get imageCache => PaintingBinding.instance.imageCache; |
216 | |