react-native-fast-image 8.30.1 → 8.31.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +460 -210
- package/android/build.gradle +13 -1
- package/android/src/glide-4.15/java/com/dylanvann/fastimage/FastImageAnimated.java +2 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageApng.java +2 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageGif.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageGlide.java +76 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageGlideModule.java +1 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +25 -51
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +1 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +2 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageUrl.java +36 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +2 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +13 -10
- package/android/src/main/java/com/dylanvann/fastimage/FastImageWebGlideUrl.java +1 -0
- package/app.plugin.js +1 -1
- package/dist/index.d.ts +358 -63
- package/ios/FastImage/FFFAnimatedAVIFCoder.h +11 -0
- package/ios/FastImage/FFFAnimatedAVIFCoder.m +103 -0
- package/ios/FastImage/FFFastImageSource.h +4 -0
- package/ios/FastImage/FFFastImageSource.m +36 -2
- package/ios/FastImage/FFFastImageViewManager.m +1 -2
- package/package.json +4 -2
package/dist/index.d.ts
CHANGED
|
@@ -1,40 +1,103 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
import {
|
|
2
|
+
import { StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
|
|
3
3
|
import { cacheControl, priority, resizeMode } from './constants';
|
|
4
4
|
type ColorValue = NonNullable<ViewStyle['backgroundColor']>;
|
|
5
|
+
/** How the image fits the view: see [`resizeMode`](#resizemode). */
|
|
5
6
|
export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center' | 'repeat';
|
|
7
|
+
/** An image's load priority: see [`source.priority`](#sourcepriority). */
|
|
6
8
|
export type Priority = 'low' | 'normal' | 'high';
|
|
9
|
+
/** How fresh an image must be: see [`source.cache`](#sourcecache). */
|
|
7
10
|
export type Cache = 'immutable' | 'web' | 'cacheOnly';
|
|
11
|
+
/** A remote image to load, and how to load and cache it. */
|
|
8
12
|
export type Source = {
|
|
13
|
+
/**
|
|
14
|
+
* Remote url to load the image from. e.g. `'https://example.com/image.jpg'`.
|
|
15
|
+
*
|
|
16
|
+
* Also loads local files (`file://`, and on Android `content://`), photo
|
|
17
|
+
* library images on iOS (`ph://`, see
|
|
18
|
+
* [Photo library images](#photo-library-images-ios)) and SVG images (see
|
|
19
|
+
* [SVG images](docs/formats.md#svg-images)).
|
|
20
|
+
*/
|
|
9
21
|
uri?: string;
|
|
22
|
+
/** Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`. */
|
|
10
23
|
headers?: {
|
|
11
24
|
[key: string]: string;
|
|
12
25
|
};
|
|
26
|
+
/**
|
|
27
|
+
* A hint for which images to start loading first when several are
|
|
28
|
+
* waiting: `'high'` ones before `'normal'` ones, and `'low'` ones after.
|
|
29
|
+
* It's best effort, not an order: several images load at once, and when
|
|
30
|
+
* each finishes depends on its size and the network.
|
|
31
|
+
*
|
|
32
|
+
* - `'low'`: e.g. images further down a list.
|
|
33
|
+
* - `'normal'`: the default.
|
|
34
|
+
* - `'high'`: e.g. the image the screen is about.
|
|
35
|
+
*
|
|
36
|
+
* @default 'normal'
|
|
37
|
+
*/
|
|
13
38
|
priority?: Priority;
|
|
39
|
+
/**
|
|
40
|
+
* How fresh the image must be. See
|
|
41
|
+
* [how caching is handled](docs/how-is-caching-handled.md) for how the
|
|
42
|
+
* options fit together.
|
|
43
|
+
*
|
|
44
|
+
* - `'immutable'`: loads the image once, then shows the cached copy until
|
|
45
|
+
* its url (or `cacheKey`) changes.
|
|
46
|
+
* - `'web'`: follows the server's HTTP cache headers, as a browser does,
|
|
47
|
+
* checking with the server when it loads. These responses are kept in
|
|
48
|
+
* their own HTTP cache (50 MB on each platform), which `clearDiskCache`
|
|
49
|
+
* also clears.
|
|
50
|
+
* - `'cacheOnly'`: only shows a cached image, without making a request.
|
|
51
|
+
*
|
|
52
|
+
* @default 'immutable'
|
|
53
|
+
*/
|
|
14
54
|
cache?: Cache;
|
|
15
55
|
/**
|
|
16
|
-
* The key the image is cached under, instead of its uri.
|
|
17
|
-
* change while the image stays the same, e.g. signed urls with a
|
|
18
|
-
* an expiry
|
|
56
|
+
* The key the image is cached under, instead of its uri. Use it for urls
|
|
57
|
+
* that change while the image stays the same, e.g. signed urls with a
|
|
58
|
+
* token or an expiry. Use something that identifies the image, and change
|
|
59
|
+
* it when the image changes (e.g. include a version or the time it was
|
|
60
|
+
* updated), or the old image keeps showing.
|
|
61
|
+
*
|
|
62
|
+
* Not used with `cache: 'web'`, which follows the HTTP cache (keyed by
|
|
63
|
+
* url).
|
|
64
|
+
*
|
|
65
|
+
* @example
|
|
66
|
+
* ```jsx
|
|
67
|
+
* <FastImage
|
|
68
|
+
* source={{
|
|
69
|
+
* uri: signedUrl,
|
|
70
|
+
* cacheKey: `avatar-${user.id}-${user.avatarUpdatedAt}`,
|
|
71
|
+
* }}
|
|
72
|
+
* />
|
|
73
|
+
* ```
|
|
19
74
|
*/
|
|
20
75
|
cacheKey?: string;
|
|
21
76
|
/**
|
|
22
|
-
* Whether the decoded image is kept in the memory cache
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
77
|
+
* Whether the decoded image is kept in the memory cache. With `false`
|
|
78
|
+
* it's only kept on disk: a view doesn't leave it in memory once it stops
|
|
79
|
+
* showing it, and `FastImage.preload` downloads it without decoding it.
|
|
80
|
+
* Use it for large images that are shown once or rarely, like a
|
|
81
|
+
* full-screen photo: a decoded photo can take tens of MB of memory. Images
|
|
82
|
+
* shown again, like a list scrolled back, are decoded from disk again.
|
|
83
|
+
*
|
|
84
|
+
* @default true
|
|
27
85
|
*/
|
|
28
86
|
memoryCache?: boolean;
|
|
29
87
|
/**
|
|
30
|
-
* With several sources (`source` as an array), the image's
|
|
31
|
-
* uri in pixels (
|
|
32
|
-
* loads the source closest to its own size.
|
|
88
|
+
* With several sources (`source` as an array), the image's width at this
|
|
89
|
+
* uri, in pixels (times `scale`, if given).
|
|
33
90
|
*/
|
|
34
91
|
width?: number;
|
|
92
|
+
/**
|
|
93
|
+
* With several sources (`source` as an array), the image's height at this
|
|
94
|
+
* uri, in pixels (times `scale`, if given).
|
|
95
|
+
*/
|
|
35
96
|
height?: number;
|
|
97
|
+
/** With several sources, the scale `width` and `height` are multiplied by. */
|
|
36
98
|
scale?: number;
|
|
37
99
|
};
|
|
100
|
+
/** How the image fades in: see [`transition`](#transition). */
|
|
38
101
|
export type Transition = {
|
|
39
102
|
/**
|
|
40
103
|
* How long the fade takes, in milliseconds; 0 means no fade. Defaults to
|
|
@@ -64,6 +127,11 @@ export type Transition = {
|
|
|
64
127
|
*/
|
|
65
128
|
skipOnCacheHit?: 'none' | 'memory' | 'all' | null;
|
|
66
129
|
};
|
|
130
|
+
/**
|
|
131
|
+
* `onLoad`'s event: `nativeEvent` has the image's `width` and `height`, in
|
|
132
|
+
* pixels, and `target`, the view's React tag (missing on Android with the
|
|
133
|
+
* legacy architecture).
|
|
134
|
+
*/
|
|
67
135
|
export interface OnLoadEvent {
|
|
68
136
|
nativeEvent: {
|
|
69
137
|
width: number;
|
|
@@ -71,11 +139,19 @@ export interface OnLoadEvent {
|
|
|
71
139
|
target?: number;
|
|
72
140
|
};
|
|
73
141
|
}
|
|
142
|
+
/**
|
|
143
|
+
* `onError`'s event: `nativeEvent.error` says what went wrong, e.g. an HTTP
|
|
144
|
+
* status code or an image that can't be decoded.
|
|
145
|
+
*/
|
|
74
146
|
export interface OnErrorEvent {
|
|
75
147
|
nativeEvent: {
|
|
76
148
|
error: string;
|
|
77
149
|
};
|
|
78
150
|
}
|
|
151
|
+
/**
|
|
152
|
+
* A load's result, as `onLoadEnd` gets it: `ok` with the image's size, or not
|
|
153
|
+
* `ok` with the error (as `onLoad` and `onError` get them).
|
|
154
|
+
*/
|
|
79
155
|
export type LoadResult = {
|
|
80
156
|
ok: true;
|
|
81
157
|
width: number;
|
|
@@ -84,17 +160,47 @@ export type LoadResult = {
|
|
|
84
160
|
ok: false;
|
|
85
161
|
error: string;
|
|
86
162
|
};
|
|
163
|
+
/**
|
|
164
|
+
* `configureCache`'s changes: a number (0 for no limit), or `null` to go back
|
|
165
|
+
* to the app's native config (Info.plist, AndroidManifest.xml) or the
|
|
166
|
+
* platform's default. Leave one out to keep it. Changes are saved, and used on
|
|
167
|
+
* the next launches too. Android only has `maxDiskSize`.
|
|
168
|
+
*/
|
|
87
169
|
export interface CacheLimits {
|
|
170
|
+
/**
|
|
171
|
+
* The most bytes of images kept on disk. When it's over, the least
|
|
172
|
+
* recently used are removed (on iOS, until it's half this size). Default:
|
|
173
|
+
* no limit on iOS, 250 MB on Android.
|
|
174
|
+
*/
|
|
88
175
|
maxDiskSize?: number | null;
|
|
176
|
+
/**
|
|
177
|
+
* iOS: the seconds an image is kept on disk after it was last used, or 0
|
|
178
|
+
* to keep them until `maxDiskSize` removes them. Default: 1 week.
|
|
179
|
+
*/
|
|
89
180
|
maxDiskAge?: number | null;
|
|
181
|
+
/**
|
|
182
|
+
* iOS: the most bytes of decoded images kept in memory. Default: no limit
|
|
183
|
+
* (they're removed when the system is low on memory).
|
|
184
|
+
*/
|
|
90
185
|
maxMemorySize?: number | null;
|
|
91
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* `configureCache`'s result: the limits in effect (0 for no limit), and the
|
|
189
|
+
* bytes the disk cache uses now. Android only has `maxDiskSize` and
|
|
190
|
+
* `diskSize`, and neither if the app has its own `AppGlideModule` (or
|
|
191
|
+
* expo-image, which has one).
|
|
192
|
+
*/
|
|
92
193
|
export interface CacheState {
|
|
93
194
|
maxDiskSize?: number;
|
|
94
195
|
maxDiskAge?: number;
|
|
95
196
|
maxMemorySize?: number;
|
|
197
|
+
/** The bytes the disk cache uses now. */
|
|
96
198
|
diskSize?: number;
|
|
97
199
|
}
|
|
200
|
+
/**
|
|
201
|
+
* `getCachePath`'s and `writeToCache`'s result: `ok` with the file's path, or
|
|
202
|
+
* not `ok` with the error.
|
|
203
|
+
*/
|
|
98
204
|
export type CachePathResult = {
|
|
99
205
|
ok: true;
|
|
100
206
|
path: string;
|
|
@@ -102,6 +208,10 @@ export type CachePathResult = {
|
|
|
102
208
|
ok: false;
|
|
103
209
|
error: string;
|
|
104
210
|
};
|
|
211
|
+
/**
|
|
212
|
+
* `onProgress`'s event: `nativeEvent` has the bytes `loaded` and the `total`,
|
|
213
|
+
* and `progress`, `loaded / total` from 0 to 1.
|
|
214
|
+
*/
|
|
105
215
|
export interface OnProgressEvent {
|
|
106
216
|
nativeEvent: {
|
|
107
217
|
loaded: number;
|
|
@@ -115,116 +225,301 @@ export interface ImageStyle extends ViewStyle {
|
|
|
115
225
|
}
|
|
116
226
|
export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
117
227
|
/**
|
|
118
|
-
*
|
|
119
|
-
* same image at different sizes
|
|
120
|
-
*
|
|
228
|
+
* Source for the remote image to load: a `Source`, a `require()`d image,
|
|
229
|
+
* or an array of the same image at different sizes.
|
|
230
|
+
*
|
|
231
|
+
* When `source` changes, the image that's showing stays until the new one
|
|
232
|
+
* has loaded. In views that get reused for other content, such as rows in
|
|
233
|
+
* FlashList or recyclerlistview, set `recyclingKey` so a reused row
|
|
234
|
+
* doesn't show the previous row's image.
|
|
235
|
+
*
|
|
236
|
+
* **Several sizes of an image.** `source` can also be an array of the
|
|
237
|
+
* same image at different sizes, each with its `width` and `height` in
|
|
238
|
+
* pixels (times `scale`, if it has one). The view loads the one whose size
|
|
239
|
+
* is closest to its own, in pixels, so a small view downloads a small
|
|
240
|
+
* image:
|
|
241
|
+
*
|
|
242
|
+
* ```jsx
|
|
243
|
+
* <FastImage
|
|
244
|
+
* style={{ width: 120, height: 120 }}
|
|
245
|
+
* source={[
|
|
246
|
+
* { uri: 'https://example.com/photo-200.jpg', width: 200, height: 200 },
|
|
247
|
+
* { uri: 'https://example.com/photo-800.jpg', width: 800, height: 800 },
|
|
248
|
+
* ]}
|
|
249
|
+
* />
|
|
250
|
+
* ```
|
|
251
|
+
*
|
|
252
|
+
* - The view picks once it has been laid out. One that has no size (e.g.
|
|
253
|
+
* sized from `onLoad`) loads the largest.
|
|
254
|
+
* - When the view's size changes so that another size fits better, it
|
|
255
|
+
* loads that one (with its load events), and keeps showing the current
|
|
256
|
+
* image until then, without a `transition`.
|
|
257
|
+
* - Each size is cached separately. A `cacheKey` applies to its own entry:
|
|
258
|
+
* give each size its own key (or none), or the sizes would replace each
|
|
259
|
+
* other in the cache.
|
|
260
|
+
* - `FastImage.preload`, `getCachePath` and `writeToCache` take one size:
|
|
261
|
+
* pass the one a view will show. An array fails with
|
|
262
|
+
* `{ ok: false, error }`.
|
|
263
|
+
* - An array of one is the same as that source.
|
|
121
264
|
*/
|
|
122
265
|
source?: Source | Source[] | ImageRequireSource;
|
|
266
|
+
/**
|
|
267
|
+
* An asset loaded with `require(...)`, shown while the first image loads,
|
|
268
|
+
* and if an image fails to load. When `source` changes, the previous image
|
|
269
|
+
* shows while the new one loads instead (see `source` and
|
|
270
|
+
* `recyclingKey`).
|
|
271
|
+
*
|
|
272
|
+
* On Android, `defaultSource` doesn't show in debug builds: there the dev
|
|
273
|
+
* server serves `require()`d images, and `defaultSource` is only loaded
|
|
274
|
+
* from the app's resources.
|
|
275
|
+
*/
|
|
123
276
|
defaultSource?: ImageRequireSource;
|
|
277
|
+
/**
|
|
278
|
+
* How the image fills the view.
|
|
279
|
+
*
|
|
280
|
+
* - `'contain'`: scales it uniformly (keeping its aspect ratio) so all of
|
|
281
|
+
* it fits in the view (minus padding).
|
|
282
|
+
* - `'cover'`: scales it uniformly (keeping its aspect ratio) so it covers
|
|
283
|
+
* the view (minus padding), cropping what doesn't fit.
|
|
284
|
+
* - `'stretch'`: scales its width and height separately to fill the view,
|
|
285
|
+
* which can change its aspect ratio.
|
|
286
|
+
* - `'center'`: centers it at its own size, scaled down uniformly to fit
|
|
287
|
+
* if it's larger than the view.
|
|
288
|
+
* - `'repeat'`: repeats it to cover the view, from its top-left corner, at
|
|
289
|
+
* the image's own size in pixels (a bundled image at its size in
|
|
290
|
+
* points), scaled down to fit if it's larger than the view. An animated
|
|
291
|
+
* image repeats its first frame, and `defaultSource` repeats too.
|
|
292
|
+
*
|
|
293
|
+
* @default 'cover'
|
|
294
|
+
*/
|
|
124
295
|
resizeMode?: ResizeMode;
|
|
296
|
+
/**
|
|
297
|
+
* If true, the image is shown with React Native's `Image` instead, styled
|
|
298
|
+
* and laid out the same way. FastImage's own features, such as its
|
|
299
|
+
* caching options, `priority` and `transition`, don't apply.
|
|
300
|
+
*/
|
|
125
301
|
fallback?: boolean;
|
|
126
302
|
/**
|
|
127
|
-
*
|
|
128
|
-
*
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
*
|
|
132
|
-
*
|
|
303
|
+
* For views that get reused for other content, such as rows in FlashList
|
|
304
|
+
* or recyclerlistview. Set it to something that identifies the content,
|
|
305
|
+
* e.g. the item's id. When it changes, the image is cleared right away (to
|
|
306
|
+
* `defaultSource`, or blank) instead of staying until the new one has
|
|
307
|
+
* loaded. Unlike changing `key`, this keeps the view, which is what list
|
|
308
|
+
* recycling is for.
|
|
309
|
+
*
|
|
310
|
+
* @example
|
|
311
|
+
* ```jsx
|
|
312
|
+
* <FastImage recyclingKey={item.id} source={{ uri: item.imageUrl }} />
|
|
313
|
+
* ```
|
|
133
314
|
*/
|
|
134
315
|
recyclingKey?: string | null;
|
|
135
316
|
/**
|
|
136
|
-
* How many times an animated image (GIF, animated WebP, APNG,
|
|
137
|
-
* AVIF
|
|
138
|
-
*
|
|
139
|
-
*
|
|
317
|
+
* How many times an animated image (GIF, animated WebP, APNG, or animated
|
|
318
|
+
* AVIF) plays:
|
|
319
|
+
*
|
|
320
|
+
* - Not set: as many times as the file says (like a browser).
|
|
321
|
+
* - `true`: forever.
|
|
322
|
+
* - `false`: once.
|
|
323
|
+
* - A number: that many times.
|
|
324
|
+
*
|
|
325
|
+
* Changing it restarts the animation.
|
|
140
326
|
*/
|
|
141
327
|
loop?: boolean | number;
|
|
142
328
|
/**
|
|
143
329
|
* How the image is filtered when it's drawn smaller or larger than its
|
|
144
|
-
* size
|
|
145
|
-
*
|
|
146
|
-
*
|
|
147
|
-
*
|
|
330
|
+
* size (like CSS's `image-rendering`):
|
|
331
|
+
*
|
|
332
|
+
* - `'auto'`: the platform's usual filtering.
|
|
333
|
+
* - `'smooth'`: iOS only. Keeps a large image drawn much smaller than its
|
|
334
|
+
* size (e.g. a big photo as a thumbnail, or fine lines and text) from
|
|
335
|
+
* looking jagged or noisy. Such an image is already decoded at about the
|
|
336
|
+
* view's size by default (see `downsample`), so this is for images shown
|
|
337
|
+
* at less than half their size with `downsample={false}`, or a little
|
|
338
|
+
* smaller than their size. **It uses more memory:** the image is also
|
|
339
|
+
* kept at smaller sizes for drawing, about a third more than the decoded
|
|
340
|
+
* image. On Android it's the same as `'auto'` (images are always decoded
|
|
341
|
+
* at about the view's size there).
|
|
342
|
+
* - `'pixelated'`: sharp pixels, without smoothing, e.g. for pixel art
|
|
343
|
+
* drawn larger than its size. On Android, animated images are still
|
|
344
|
+
* smoothed.
|
|
345
|
+
*
|
|
346
|
+
* @default 'auto'
|
|
148
347
|
*/
|
|
149
348
|
imageRendering?: 'auto' | 'smooth' | 'pixelated';
|
|
150
349
|
/**
|
|
151
|
-
* Pauses an animated image (GIF, animated WebP, APNG,
|
|
152
|
-
*
|
|
350
|
+
* Pauses an animated image (GIF, animated WebP, APNG, or animated AVIF) on
|
|
351
|
+
* the frame it's showing; `false` plays it again from there
|
|
153
352
|
* (on Android, an animated WebP or AVIF plays again from its first frame:
|
|
154
353
|
* Android can't resume one). Each image animates on its own, so pausing
|
|
155
354
|
* one doesn't pause others showing the same file.
|
|
156
355
|
*/
|
|
157
356
|
paused?: boolean;
|
|
158
357
|
/**
|
|
159
|
-
* Fades the image in when it loads
|
|
160
|
-
* a duration in milliseconds, or a `Transition
|
|
161
|
-
*
|
|
162
|
-
*
|
|
358
|
+
* Fades the image in when it loads. `true` uses the platform's usual
|
|
359
|
+
* fade, a number is the duration in milliseconds, or pass a `Transition`.
|
|
360
|
+
*
|
|
361
|
+
* Downloads, local files (`file://`, `content://`) and bundled images
|
|
362
|
+
* (`require()`) fade in. In lists that reuse views (e.g. FlashList), set
|
|
363
|
+
* `recyclingKey`, so a reused view starts empty and its image fades in,
|
|
364
|
+
* instead of showing the previous item's image until it loads.
|
|
365
|
+
*
|
|
366
|
+
* @default false
|
|
367
|
+
* @example
|
|
368
|
+
* ```jsx
|
|
369
|
+
* <FastImage source={{ uri }} transition />
|
|
370
|
+
* <FastImage source={{ uri }} transition={500} />
|
|
371
|
+
* <FastImage source={{ uri }} transition={{ betweenImages: true, skipOnCacheHit: 'none' }} />
|
|
372
|
+
* ```
|
|
163
373
|
*/
|
|
164
374
|
transition?: boolean | number | Transition | null;
|
|
165
375
|
/**
|
|
166
|
-
*
|
|
167
|
-
* takes much less memory.
|
|
168
|
-
*
|
|
169
|
-
* the
|
|
376
|
+
* Decodes a large image at about the size it's shown at, instead of at
|
|
377
|
+
* full size, so it takes much less memory.
|
|
378
|
+
*
|
|
379
|
+
* - `true`: an image at least twice the size its view needs is decoded
|
|
380
|
+
* at about the view's size. If the view grows, the image is decoded
|
|
381
|
+
* again for its new size (from the disk cache).
|
|
382
|
+
* - `false`: images are decoded at full size, e.g. for an image that's
|
|
383
|
+
* zoomed in on with a transform (a pinch-to-zoom viewer), which would
|
|
384
|
+
* otherwise show the smaller copy enlarged.
|
|
385
|
+
*
|
|
386
|
+
* It doesn't change `onLoad`'s width and height (the image's own size) or
|
|
387
|
+
* the cached file. Needs SDWebImage 5.19.7 or later: before 5.19 images
|
|
388
|
+
* are decoded at full size, and 5.19.0 to 5.19.6 show photos stored
|
|
389
|
+
* sideways with an EXIF orientation (most phone photos) sideways. Photo
|
|
390
|
+
* library images are always decoded this way, and on Android images are
|
|
391
|
+
* always decoded at about the view's size (but Android 16 and later
|
|
392
|
+
* decode animated WebP at full size, and scale it as they draw it).
|
|
393
|
+
*
|
|
394
|
+
* If you control the images, serve them at the size they're shown
|
|
395
|
+
* (resized on your server or by an image CDN), which also saves
|
|
396
|
+
* bandwidth.
|
|
397
|
+
*
|
|
398
|
+
* @platform ios
|
|
399
|
+
* @default true
|
|
170
400
|
*/
|
|
171
401
|
downsample?: boolean;
|
|
172
402
|
/**
|
|
173
|
-
* Blurs the image by this radius, in points
|
|
174
|
-
*
|
|
175
|
-
*
|
|
176
|
-
*
|
|
177
|
-
*
|
|
403
|
+
* Blurs the image by this radius, in points (the same radius looks about
|
|
404
|
+
* the same on iOS and Android). `0` is no blur.
|
|
405
|
+
*
|
|
406
|
+
* It's for still images, or a radius that changes now and then (e.g.
|
|
407
|
+
* blurring a photo behind a sheet). Each change blurs the image again on
|
|
408
|
+
* the CPU, so don't animate it.
|
|
409
|
+
*
|
|
410
|
+
* - Only the loaded image is blurred, not `defaultSource`.
|
|
411
|
+
* - An animated image (GIF, animated WebP, APNG or AVIF) shows its first frame,
|
|
412
|
+
* blurred, and doesn't animate.
|
|
413
|
+
* - With `tintColor`, the blurred image is tinted.
|
|
414
|
+
* - The image is blurred at about the size it's shown at, off the main
|
|
415
|
+
* thread. The cached file stays the original image, so `getCachePath`
|
|
416
|
+
* and other views of it aren't affected.
|
|
417
|
+
* - Changing it blurs the image that's showing again, without sending the
|
|
418
|
+
* load events again.
|
|
419
|
+
*
|
|
420
|
+
* To animate a blur, or to blur an animated image, use React Native's
|
|
421
|
+
* `filter` style instead, which the GPU draws:
|
|
422
|
+
* `style={{ filter: [{ blur: 6 }] }}`. It needs the New Architecture.
|
|
423
|
+
* React Native's docs list `blur` for Android 12+ only; on iOS it's behind
|
|
424
|
+
* an experimental React Native feature flag (`enableSwiftUIBasedFilters`,
|
|
425
|
+
* SwiftUI-based filters).
|
|
426
|
+
*
|
|
427
|
+
* @default 0
|
|
178
428
|
*/
|
|
179
429
|
blurRadius?: number;
|
|
430
|
+
/** Called when the image starts to load. */
|
|
180
431
|
onLoadStart?(): void;
|
|
181
|
-
onProgress?(event: OnProgressEvent): void;
|
|
182
|
-
onLoad?(event: OnLoadEvent): void;
|
|
183
|
-
onError?(event: OnErrorEvent): void;
|
|
184
432
|
/**
|
|
185
|
-
* Called
|
|
186
|
-
*
|
|
433
|
+
* Called while the image downloads, with the bytes `loaded` so far, the
|
|
434
|
+
* `total`, and `progress` (`loaded / total`, from 0 to 1; the last event
|
|
435
|
+
* has 1). Not called while the total is unknown (a response without a
|
|
436
|
+
* `Content-Length`).
|
|
437
|
+
*
|
|
438
|
+
* @example
|
|
439
|
+
* ```jsx
|
|
440
|
+
* onProgress={e => console.log(e.nativeEvent.progress)}
|
|
441
|
+
* ```
|
|
187
442
|
*/
|
|
188
|
-
|
|
443
|
+
onProgress?(event: OnProgressEvent): void;
|
|
189
444
|
/**
|
|
190
|
-
*
|
|
445
|
+
* Called on a successful image fetch. Called with the width and height of
|
|
446
|
+
* the image itself, not of the view (on iOS, in points: an `@2x` asset
|
|
447
|
+
* reports half its pixel size).
|
|
191
448
|
*
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
* {
|
|
449
|
+
* @example
|
|
450
|
+
* ```jsx
|
|
451
|
+
* onLoad={e => console.log(e.nativeEvent.width, e.nativeEvent.height)}
|
|
452
|
+
* ```
|
|
195
453
|
*/
|
|
196
|
-
|
|
454
|
+
onLoad?(event: OnLoadEvent): void;
|
|
197
455
|
/**
|
|
456
|
+
* Called on an image fetching error, with a message describing it (e.g.
|
|
457
|
+
* the HTTP status code).
|
|
198
458
|
*
|
|
199
|
-
*
|
|
459
|
+
* @example
|
|
460
|
+
* ```jsx
|
|
461
|
+
* onError={e => console.log(e.nativeEvent.error)}
|
|
462
|
+
* ```
|
|
200
463
|
*/
|
|
201
|
-
|
|
464
|
+
onError?(event: OnErrorEvent): void;
|
|
202
465
|
/**
|
|
203
|
-
*
|
|
466
|
+
* Called when the image finishes loading, whether it was successful or an
|
|
467
|
+
* error, with the result: `{ ok: true, width, height }` (the image's size,
|
|
468
|
+
* as `onLoad` gets it) or `{ ok: false, error }` (as `onError` gets it).
|
|
469
|
+
* TypeScript makes you check `ok` before reading the size or the error.
|
|
204
470
|
*
|
|
205
|
-
*
|
|
471
|
+
* @example
|
|
472
|
+
* ```jsx
|
|
473
|
+
* <FastImage
|
|
474
|
+
* source={{ uri }}
|
|
475
|
+
* onLoadEnd={(result) => {
|
|
476
|
+
* if (result.ok) setAspectRatio(result.width / result.height)
|
|
477
|
+
* else setFailed(result.error)
|
|
478
|
+
* }}
|
|
479
|
+
* />
|
|
480
|
+
* ```
|
|
206
481
|
*/
|
|
207
|
-
|
|
482
|
+
onLoadEnd?(result: LoadResult): void;
|
|
483
|
+
/**
|
|
484
|
+
* The image's style: View's style props (`borderRadius` clips the image),
|
|
485
|
+
* and `tintColor`, as with React Native's `Image` (the `tintColor` prop
|
|
486
|
+
* wins).
|
|
487
|
+
*/
|
|
488
|
+
style?: StyleProp<ImageStyle>;
|
|
208
489
|
/**
|
|
209
|
-
*
|
|
490
|
+
* If supplied, changes the color of all the non-transparent pixels to the
|
|
491
|
+
* given color.
|
|
210
492
|
*/
|
|
211
|
-
|
|
493
|
+
tintColor?: ColorValue;
|
|
212
494
|
/**
|
|
213
|
-
* Render children within the image.
|
|
495
|
+
* Render children within the image. In the next major version,
|
|
496
|
+
* `FastImage` won't render children: use `FastImageBackground`.
|
|
214
497
|
*/
|
|
215
498
|
children?: React.ReactNode;
|
|
216
499
|
}
|
|
500
|
+
/** A preloaded source that loaded (and is now cached). */
|
|
217
501
|
export interface PreloadSuccess {
|
|
502
|
+
/** The source's uri. */
|
|
218
503
|
uri: string;
|
|
219
504
|
ok: true;
|
|
505
|
+
/** The image's width (as in `onLoad`). */
|
|
220
506
|
width: number;
|
|
507
|
+
/** The image's height (as in `onLoad`). */
|
|
221
508
|
height: number;
|
|
222
509
|
}
|
|
510
|
+
/** A preloaded source that failed to load. */
|
|
223
511
|
export interface PreloadFailure {
|
|
512
|
+
/** The source's uri (none for a source without one, e.g. `null`). */
|
|
224
513
|
uri?: string;
|
|
225
514
|
ok: false;
|
|
515
|
+
/** What went wrong. */
|
|
226
516
|
error: string;
|
|
227
517
|
}
|
|
518
|
+
/**
|
|
519
|
+
* `preload`'s result for a source. Check `ok` to tell which it is: e.g.
|
|
520
|
+
* `if (result.ok)` narrows it to a `PreloadSuccess`, with its size. Reading
|
|
521
|
+
* `width` or `error` without checking is a type error.
|
|
522
|
+
*/
|
|
228
523
|
export type PreloadResult = PreloadSuccess | PreloadFailure;
|
|
229
524
|
export interface FastImageStaticProperties {
|
|
230
525
|
resizeMode: typeof resizeMode;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#import <SDWebImage/SDImageIOAnimatedCoder.h>
|
|
2
|
+
|
|
3
|
+
// Animated AVIFs (AVIF image sequences), decoded with ImageIO, which reads
|
|
4
|
+
// them where it reads AVIF (iOS 16 and later): SDWebImage's own coders decode
|
|
5
|
+
// an AVIF's first frame only. Still AVIFs keep SDWebImage's ImageIO coder.
|
|
6
|
+
// Registered with SDWebImage's coders by FFFastImageSource.
|
|
7
|
+
@interface FFFAnimatedAVIFCoder : SDImageIOAnimatedCoder
|
|
8
|
+
|
|
9
|
+
@property (nonatomic, class, readonly, nonnull) FFFAnimatedAVIFCoder* sharedCoder;
|
|
10
|
+
|
|
11
|
+
@end
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#import "FFFAnimatedAVIFCoder.h"
|
|
2
|
+
#import <ImageIO/ImageIO.h>
|
|
3
|
+
|
|
4
|
+
// SDWebImage has no format for AVIF: this is the value SDWebImageAVIFCoder
|
|
5
|
+
// uses, so an image reports the same format with either coder.
|
|
6
|
+
static const SDImageFormat FFFImageFormatAVIF = 15;
|
|
7
|
+
|
|
8
|
+
// Whether the data is an animated AVIF: a file whose first box is `ftyp`
|
|
9
|
+
// with the `avis` (image sequence) brand, as its major brand or a compatible
|
|
10
|
+
// one. The header comes first, so a partial download can be checked too.
|
|
11
|
+
static BOOL FFFIsAnimatedAVIF(NSData* data) {
|
|
12
|
+
if (data.length < 16) {
|
|
13
|
+
return NO;
|
|
14
|
+
}
|
|
15
|
+
const uint8_t* bytes = data.bytes;
|
|
16
|
+
if (memcmp(bytes + 4, "ftyp", 4) != 0) {
|
|
17
|
+
return NO;
|
|
18
|
+
}
|
|
19
|
+
if (memcmp(bytes + 8, "avis", 4) == 0) {
|
|
20
|
+
return YES;
|
|
21
|
+
}
|
|
22
|
+
// The compatible brands, after the major brand and its version.
|
|
23
|
+
uint32_t size = (uint32_t)bytes[0] << 24 | (uint32_t)bytes[1] << 16 | (uint32_t)bytes[2] << 8 | bytes[3];
|
|
24
|
+
NSUInteger end = MIN((NSUInteger)size, data.length);
|
|
25
|
+
for (NSUInteger offset = 16; offset + 4 <= end; offset += 4) {
|
|
26
|
+
if (memcmp(bytes + offset, "avis", 4) == 0) {
|
|
27
|
+
return YES;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return NO;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Whether ImageIO reads AVIF image sequences on this version of iOS.
|
|
34
|
+
static BOOL FFFCanDecodeAVIFSequences(void) {
|
|
35
|
+
static BOOL supported;
|
|
36
|
+
static dispatch_once_t once;
|
|
37
|
+
dispatch_once(&once, ^{
|
|
38
|
+
NSArray* types = CFBridgingRelease(CGImageSourceCopyTypeIdentifiers());
|
|
39
|
+
supported = [types containsObject: @"public.avis"];
|
|
40
|
+
});
|
|
41
|
+
return supported;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@implementation FFFAnimatedAVIFCoder
|
|
45
|
+
|
|
46
|
+
+ (FFFAnimatedAVIFCoder*) sharedCoder {
|
|
47
|
+
static FFFAnimatedAVIFCoder* coder;
|
|
48
|
+
static dispatch_once_t once;
|
|
49
|
+
dispatch_once(&once, ^{
|
|
50
|
+
coder = [FFFAnimatedAVIFCoder new];
|
|
51
|
+
});
|
|
52
|
+
return coder;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
- (BOOL) canDecodeFromData: (NSData*)data {
|
|
56
|
+
return FFFIsAnimatedAVIF(data) && FFFCanDecodeAVIFSequences();
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
- (BOOL) canIncrementalDecodeFromData: (NSData*)data {
|
|
60
|
+
return [self canDecodeFromData: data];
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Only for decoding.
|
|
64
|
+
- (BOOL) canEncodeToFormat: (SDImageFormat)format {
|
|
65
|
+
return NO;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#pragma mark - SDImageIOAnimatedCoder
|
|
69
|
+
|
|
70
|
+
+ (SDImageFormat) imageFormat {
|
|
71
|
+
return FFFImageFormatAVIF;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
+ (NSString*) imageUTType {
|
|
75
|
+
return @"public.avis";
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ImageIO's keys for AVIF sequences (kCGImagePropertyAVIS…), as strings so
|
|
79
|
+
// they build with any SDK. LoopCount is the number of plays, 0 for forever,
|
|
80
|
+
// as for GIF and APNG.
|
|
81
|
+
+ (NSString*) dictionaryProperty {
|
|
82
|
+
return @"{AVIS}";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
+ (NSString*) unclampedDelayTimeProperty {
|
|
86
|
+
return @"UnclampedDelayTime";
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
+ (NSString*) delayTimeProperty {
|
|
90
|
+
return @"DelayTime";
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
+ (NSString*) loopCountProperty {
|
|
94
|
+
return @"LoopCount";
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// A sequence without a repetition count plays forever (ImageIO reports
|
|
98
|
+
// LoopCount 0 for one).
|
|
99
|
+
+ (NSUInteger) defaultLoopCount {
|
|
100
|
+
return 0;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@end
|