react-native-fast-image 9.2.2 → 9.3.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 +36 -7
- package/android/src/main/java/com/dylanvann/fastimage/FastImageBlur.java +18 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +5 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSharedDownloads.java +64 -19
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +22 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +27 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvgRenderer.java +10 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +5 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +1 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +157 -14
- package/dist/index.cjs.js +50 -18
- package/dist/index.cjs.js.flow +3 -0
- package/dist/index.cjs.web.js +241 -130
- package/dist/index.cjs.web.js.flow +3 -0
- package/dist/index.d.ts +36 -6
- package/dist/index.js +50 -18
- package/dist/index.js.flow +3 -0
- package/dist/index.web.js +243 -132
- package/dist/index.web.js.flow +3 -0
- package/dist/objectFit.d.ts +3 -0
- package/ios/FastImage/FFFDownsampledImage.h +8 -0
- package/ios/FastImage/FFFDownsampledImage.m +16 -0
- package/ios/FastImage/FFFastImageView.h +7 -1
- package/ios/FastImage/FFFastImageView.m +10 -5
- package/ios/FastImage/FFFastImageViewManager.m +3 -1
- package/ios/FastImage/RCTConvert+FFFastImage.h +3 -0
- package/ios/FastImage/RCTConvert+FFFastImage.m +11 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -91,7 +91,7 @@ const YourImage = () => (
|
|
|
91
91
|
headers: { Authorization: 'someAuthToken' },
|
|
92
92
|
priority: 'normal',
|
|
93
93
|
}}
|
|
94
|
-
|
|
94
|
+
objectFit="contain"
|
|
95
95
|
/>
|
|
96
96
|
)
|
|
97
97
|
```
|
|
@@ -235,16 +235,36 @@ On Android, `defaultSource` doesn't show in debug builds: there the dev server s
|
|
|
235
235
|
|
|
236
236
|
---
|
|
237
237
|
|
|
238
|
+
#### `objectFit`
|
|
239
|
+
|
|
240
|
+
**Type:** `ObjectFit` · **Default:** `'cover'`
|
|
241
|
+
|
|
242
|
+
How the image fits the view, as CSS's `object-fit` does.
|
|
243
|
+
|
|
244
|
+
- `'cover'`: scales it uniformly (keeping its aspect ratio) so it covers the view (minus padding), cropping what doesn't fit.
|
|
245
|
+
- `'contain'`: scales it uniformly (keeping its aspect ratio) so all of it fits in the view (minus padding).
|
|
246
|
+
- `'fill'`: scales its width and height separately to fill the view, which can change its aspect ratio.
|
|
247
|
+
- `'none'`: shows it at its own size, centered, cropped if it's larger than the view.
|
|
248
|
+
- `'scale-down'`: shows it at its own size, centered, or scaled down uniformly to fit if it's larger than the view (the smaller of `'none'` and `'contain'`).
|
|
249
|
+
|
|
250
|
+
An image's own size is its size in pixels, as points (dp on Android), as CSS counts an image's pixels: a 300 × 200 image is 300 × 200 points. A bundled image (`require()`) is its size in points.
|
|
251
|
+
|
|
252
|
+
It can also be set in `style`; the prop wins. Either one overrides `resizeMode`.
|
|
253
|
+
|
|
254
|
+
---
|
|
255
|
+
|
|
238
256
|
#### `resizeMode`
|
|
239
257
|
|
|
240
258
|
**Type:** `ResizeMode` · **Default:** `'cover'`
|
|
241
259
|
|
|
260
|
+
**Deprecated:** Use `objectFit` instead.
|
|
261
|
+
|
|
242
262
|
How the image fills the view.
|
|
243
263
|
|
|
244
|
-
- `'contain'`:
|
|
245
|
-
- `'cover'`:
|
|
246
|
-
- `'stretch'`:
|
|
247
|
-
- `'center'`:
|
|
264
|
+
- `'contain'`: as `objectFit="contain"`.
|
|
265
|
+
- `'cover'`: as `objectFit="cover"`.
|
|
266
|
+
- `'stretch'`: as `objectFit="fill"`.
|
|
267
|
+
- `'center'`: as `objectFit="scale-down"`, except that on Android an image smaller than the view is shown at its size in pixels on the screen, so smaller than on iOS and the web.
|
|
248
268
|
- `'repeat'`: repeats it to cover the view, from its top-left corner, at the image's own size in pixels (a bundled image at its size in points), scaled down to fit if it's larger than the view. An animated image repeats its first frame, and `defaultSource` repeats too.
|
|
249
269
|
|
|
250
270
|
---
|
|
@@ -686,6 +706,14 @@ A load's result, as `onLoadEnd` gets it: `ok` with the image's size, or not `ok`
|
|
|
686
706
|
|
|
687
707
|
---
|
|
688
708
|
|
|
709
|
+
### `ObjectFit`
|
|
710
|
+
|
|
711
|
+
**Type:** `'fill' | 'contain' | 'cover' | 'none' | 'scale-down'`
|
|
712
|
+
|
|
713
|
+
How the image fits the view: see [`objectFit`](#objectfit).
|
|
714
|
+
|
|
715
|
+
---
|
|
716
|
+
|
|
689
717
|
### `OnErrorEvent`
|
|
690
718
|
|
|
691
719
|
`onError`'s event: `nativeEvent.error` says what went wrong, e.g. an HTTP status code or an image that can't be decoded.
|
|
@@ -826,9 +854,10 @@ FastImage works on the web with [react-native-web](https://necolas.github.io/rea
|
|
|
826
854
|
|
|
827
855
|
Support is minimal:
|
|
828
856
|
|
|
829
|
-
- Props that work: `source` (a `uri`, a `require()`d image, or several sizes), `defaultSource`, `resizeMode`, `tintColor`, `blurRadius`, `style`, children, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
|
|
857
|
+
- Props that work: `source` (a `uri`, a `require()`d image, or several sizes), `defaultSource`, `objectFit`, `resizeMode`, `tintColor`, `blurRadius`, `style`, children, `onLoadStart`, `onLoad`, `onError`, `onLoadEnd`, and View props such as `testID`, accessibility props, `onLayout` and `pointerEvents`.
|
|
830
858
|
- Ignored: `source.headers` (a browser can't send them for an image), `source.priority`, `source.cache`, `source.cacheKey`, `source.memoryCache`, `recyclingKey`, `loop`, `imageRendering`, `paused`, `transition`, `downsample`, `fallback` and `onProgress`.
|
|
831
|
-
-
|
|
859
|
+
- The image is an `<img>` element that fills the view inside its borders, with `objectFit` as its CSS `object-fit` (and `resizeMode`'s `stretch` as `fill`, `center` as `scale-down`). With `resizeMode="repeat"`, it's a repeated CSS background, with a transparent `<img>` over it. `tintColor` is an SVG filter and `blurRadius` a CSS `blur()`, and `defaultSource` is another `<img>`, under the image until it has loaded. A new `source` gets a new `<img>`, so the previous image doesn't stay while it loads (with several sizes, the browser shows it until the new one has loaded).
|
|
860
|
+
- Several sizes are shown with a lazily loaded `<img>` whose `srcset` lists them, with `sizes="auto, 100vw"`: the browser loads the one for the width the image is shown at, in device pixels (usually the smallest that's at least as wide). Browsers that don't support `sizes="auto"` use the viewport's width. `resizeMode="repeat"` shows them as `cover`.
|
|
832
861
|
- `FastImage.preload` loads the images into the browser's cache and resolves with a result per source. `clearMemoryCache`, `clearDiskCache` and `configureCache` resolve without doing anything (the browser manages its cache), and `getCachePath` and `writeToCache` resolve with `{ ok: false, error: 'Not supported on the web' }`.
|
|
833
862
|
|
|
834
863
|
## Troubleshooting
|
|
@@ -32,10 +32,17 @@ final class FastImageBlur extends BitmapTransformation {
|
|
|
32
32
|
// In pixels on screen.
|
|
33
33
|
private final float radius;
|
|
34
34
|
private final ImageView.ScaleType scaleType;
|
|
35
|
+
// Pixels on screen per pixel of the image at its own size (scale-down and
|
|
36
|
+
// none show it at that size: see FastImageViewWithUrl.updateImageMatrix;
|
|
37
|
+
// 1 for resizeMode center).
|
|
38
|
+
private final float density;
|
|
35
39
|
|
|
36
|
-
FastImageBlur(float radius, ImageView.ScaleType scaleType) {
|
|
40
|
+
FastImageBlur(float radius, ImageView.ScaleType scaleType, float density) {
|
|
37
41
|
this.radius = radius;
|
|
38
42
|
this.scaleType = scaleType;
|
|
43
|
+
this.density = scaleType == ImageView.ScaleType.CENTER_INSIDE || scaleType == ImageView.ScaleType.CENTER
|
|
44
|
+
? density
|
|
45
|
+
: 1;
|
|
39
46
|
}
|
|
40
47
|
|
|
41
48
|
@Override
|
|
@@ -80,7 +87,9 @@ final class FastImageBlur extends BitmapTransformation {
|
|
|
80
87
|
case FIT_XY:
|
|
81
88
|
return (float) Math.sqrt(x * y);
|
|
82
89
|
case CENTER_INSIDE:
|
|
83
|
-
return Math.min(Math.min(x, y),
|
|
90
|
+
return Math.min(Math.min(x, y), density);
|
|
91
|
+
case CENTER:
|
|
92
|
+
return density;
|
|
84
93
|
default:
|
|
85
94
|
return 1;
|
|
86
95
|
}
|
|
@@ -197,17 +206,21 @@ final class FastImageBlur extends BitmapTransformation {
|
|
|
197
206
|
public boolean equals(Object o) {
|
|
198
207
|
return o instanceof FastImageBlur
|
|
199
208
|
&& ((FastImageBlur) o).radius == radius
|
|
200
|
-
&& ((FastImageBlur) o).scaleType == scaleType
|
|
209
|
+
&& ((FastImageBlur) o).scaleType == scaleType
|
|
210
|
+
&& ((FastImageBlur) o).density == density;
|
|
201
211
|
}
|
|
202
212
|
|
|
203
213
|
@Override
|
|
204
214
|
public int hashCode() {
|
|
205
|
-
return ID.hashCode() * 31 + Float.floatToIntBits(radius) * 31 + (scaleType == null ? 0 : scaleType.ordinal())
|
|
215
|
+
return (ID.hashCode() * 31 + Float.floatToIntBits(radius) * 31 + (scaleType == null ? 0 : scaleType.ordinal())) * 31
|
|
216
|
+
+ Float.floatToIntBits(density);
|
|
206
217
|
}
|
|
207
218
|
|
|
208
219
|
@Override
|
|
209
220
|
public void updateDiskCacheKey(@NonNull MessageDigest messageDigest) {
|
|
210
221
|
messageDigest.update(ID_BYTES);
|
|
211
|
-
|
|
222
|
+
String key = radius + "," + scaleType;
|
|
223
|
+
if (density != 1) key += "," + density;
|
|
224
|
+
messageDigest.update(key.getBytes(Charset.forName("UTF-8")));
|
|
212
225
|
}
|
|
213
226
|
}
|
|
@@ -82,6 +82,8 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
82
82
|
boolean local = !(model instanceof FastImageUrl);
|
|
83
83
|
int[] size = FastImageSourceSize.get(resource, model, local,
|
|
84
84
|
dataSource == DataSource.RESOURCE_DISK_CACHE);
|
|
85
|
+
// Before the view shows it (the target does once this returns).
|
|
86
|
+
view.onImageSize(size);
|
|
85
87
|
if (size != null) {
|
|
86
88
|
sendLoad(view, size);
|
|
87
89
|
return false;
|
|
@@ -92,7 +94,9 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
|
|
|
92
94
|
FastImageSourceSize.readLocal(view.getContext(), source, resource, model, new FastImageSourceSize.Callback() {
|
|
93
95
|
@Override
|
|
94
96
|
public void onSize(int[] size) {
|
|
95
|
-
if (target.getRequest()
|
|
97
|
+
if (target.getRequest() != request) return;
|
|
98
|
+
view.onImageSize(size);
|
|
99
|
+
sendLoad(view, size);
|
|
96
100
|
}
|
|
97
101
|
});
|
|
98
102
|
return false;
|
|
@@ -22,10 +22,13 @@ import java.util.ArrayList;
|
|
|
22
22
|
import java.util.Arrays;
|
|
23
23
|
import java.util.Comparator;
|
|
24
24
|
import java.util.HashMap;
|
|
25
|
+
import java.util.HashSet;
|
|
26
|
+
import java.util.Iterator;
|
|
25
27
|
import java.util.List;
|
|
26
28
|
import java.util.Map;
|
|
27
|
-
import java.util.
|
|
29
|
+
import java.util.Set;
|
|
28
30
|
import java.util.TreeMap;
|
|
31
|
+
import java.util.TreeSet;
|
|
29
32
|
import java.util.concurrent.ConcurrentHashMap;
|
|
30
33
|
import java.util.concurrent.LinkedBlockingQueue;
|
|
31
34
|
import java.util.concurrent.ThreadFactory;
|
|
@@ -64,13 +67,18 @@ import okio.BufferedSource;
|
|
|
64
67
|
// once every request has read what it needed.
|
|
65
68
|
//
|
|
66
69
|
// At most MAX_RUNNING downloads run at once, as SDWebImage's downloader limits
|
|
67
|
-
// them
|
|
68
|
-
//
|
|
69
|
-
//
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
//
|
|
73
|
-
//
|
|
70
|
+
// them, and at most as many wait for their response from a host as the
|
|
71
|
+
// client's dispatcher sends to a host at once (5 by default): OkHttp would
|
|
72
|
+
// hold the others in the order they came, where a more urgent request can't
|
|
73
|
+
// move them up. The others wait to send their request, most urgent first
|
|
74
|
+
// (Glide's priority: views before preloads), and the most urgent one waiting
|
|
75
|
+
// for a host's room keeps a running slot, so less urgent downloads to other
|
|
76
|
+
// hosts can't take them all. A running download's response is read as soon
|
|
77
|
+
// as it arrives, on its own thread (OkHttp counts a request to its host until
|
|
78
|
+
// its callback returns): a response left unread on an HTTP/2 connection
|
|
79
|
+
// would hold back the others on it. A download that stalls fails like any
|
|
80
|
+
// other, with the timeouts of the clients (FastImageOkHttpProgressGlideModule),
|
|
81
|
+
// which only count once it has started.
|
|
74
82
|
final class FastImageSharedDownloads {
|
|
75
83
|
// The longest response kept in memory, and the most bytes held in memory
|
|
76
84
|
// for downloads at once: encoded images (not decoded bitmaps, which
|
|
@@ -98,9 +106,9 @@ final class FastImageSharedDownloads {
|
|
|
98
106
|
// lock).
|
|
99
107
|
private static final Map<String, Download> downloads = new HashMap<>();
|
|
100
108
|
// Guarded by downloads: downloads waiting to start, most urgent first, then
|
|
101
|
-
// in the order they were asked for; how many are running
|
|
102
|
-
// held in memory.
|
|
103
|
-
private static final
|
|
109
|
+
// in the order they were asked for; how many are running, and how many
|
|
110
|
+
// wait for their response from each host; and the bytes held in memory.
|
|
111
|
+
private static final TreeSet<Download> queue = new TreeSet<>(new Comparator<Download>() {
|
|
104
112
|
@Override
|
|
105
113
|
public int compare(Download a, Download b) {
|
|
106
114
|
int byPriority = a.priority.compareTo(b.priority);
|
|
@@ -109,6 +117,7 @@ final class FastImageSharedDownloads {
|
|
|
109
117
|
});
|
|
110
118
|
private static long orders = 0;
|
|
111
119
|
private static int running = 0;
|
|
120
|
+
private static final Map<String, Integer> sentTo = new HashMap<>();
|
|
112
121
|
private static long held = 0;
|
|
113
122
|
// Stores not yet released (see busy()).
|
|
114
123
|
private static final AtomicInteger open = new AtomicInteger();
|
|
@@ -165,9 +174,10 @@ final class FastImageSharedDownloads {
|
|
|
165
174
|
static String busy() {
|
|
166
175
|
synchronized (downloads) {
|
|
167
176
|
int stores = open.get();
|
|
168
|
-
if (downloads.isEmpty() && queue.isEmpty() && running == 0 &&
|
|
177
|
+
if (downloads.isEmpty() && queue.isEmpty() && running == 0 && sentTo.isEmpty() && held == 0
|
|
178
|
+
&& stores == 0) return null;
|
|
169
179
|
return downloads.size() + " downloads, " + queue.size() + " waiting, " + running + " running, "
|
|
170
|
-
+ held + " bytes in memory, " + stores + " stores open";
|
|
180
|
+
+ sentTo + " waiting for a response, " + held + " bytes in memory, " + stores + " stores open";
|
|
171
181
|
}
|
|
172
182
|
}
|
|
173
183
|
|
|
@@ -253,13 +263,25 @@ final class FastImageSharedDownloads {
|
|
|
253
263
|
}
|
|
254
264
|
}
|
|
255
265
|
|
|
256
|
-
// Starts the downloads waiting that can run now
|
|
266
|
+
// Starts the downloads waiting that can run now, most urgent first: those
|
|
267
|
+
// whose host has room, leaving a slot for each host without (see above).
|
|
257
268
|
private static void startQueued() {
|
|
258
269
|
List<Download> starting = new ArrayList<>();
|
|
259
270
|
List<Request> requests = new ArrayList<>();
|
|
260
271
|
synchronized (downloads) {
|
|
261
|
-
|
|
262
|
-
|
|
272
|
+
Set<String> full = new HashSet<>();
|
|
273
|
+
Iterator<Download> waiting = queue.iterator();
|
|
274
|
+
while (running + full.size() < MAX_RUNNING && waiting.hasNext()) {
|
|
275
|
+
Download download = waiting.next();
|
|
276
|
+
String host = download.request.url().host();
|
|
277
|
+
Integer sent = sentTo.get(host);
|
|
278
|
+
if (sent != null && sent >= download.client.dispatcher().getMaxRequestsPerHost()) {
|
|
279
|
+
full.add(host);
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
waiting.remove();
|
|
283
|
+
sentTo.put(host, sent == null ? 1 : sent + 1);
|
|
284
|
+
download.host = host;
|
|
263
285
|
download.queued = false;
|
|
264
286
|
download.running = true;
|
|
265
287
|
running++;
|
|
@@ -539,6 +561,10 @@ final class FastImageSharedDownloads {
|
|
|
539
561
|
private final long order;
|
|
540
562
|
private boolean queued = false;
|
|
541
563
|
private boolean running = false;
|
|
564
|
+
// Guarded by downloads. The host its request was sent to, until its
|
|
565
|
+
// response (or failure) arrives.
|
|
566
|
+
@Nullable
|
|
567
|
+
private String host;
|
|
542
568
|
// Guarded by this. The call, once started, and whether the download
|
|
543
569
|
// was cancelled (before it started, it doesn't).
|
|
544
570
|
@Nullable
|
|
@@ -585,7 +611,9 @@ final class FastImageSharedDownloads {
|
|
|
585
611
|
return;
|
|
586
612
|
}
|
|
587
613
|
Download download;
|
|
588
|
-
|
|
614
|
+
// Queued one, or moved a waiting one to another url (whose host
|
|
615
|
+
// can have room).
|
|
616
|
+
boolean queueChanged = false;
|
|
589
617
|
InputStream ready = null;
|
|
590
618
|
synchronized (downloads) {
|
|
591
619
|
// Cancelled before it got here.
|
|
@@ -601,7 +629,7 @@ final class FastImageSharedDownloads {
|
|
|
601
629
|
downloads.put(fetcher.key, download);
|
|
602
630
|
download.queued = true;
|
|
603
631
|
queue.add(download);
|
|
604
|
-
|
|
632
|
+
queueChanged = true;
|
|
605
633
|
} else if (download.queued) {
|
|
606
634
|
// A more urgent request moves it up (until it leaves).
|
|
607
635
|
if (fetcher.priority.compareTo(download.priority) < 0) download.reprioritize(fetcher.priority);
|
|
@@ -612,6 +640,7 @@ final class FastImageSharedDownloads {
|
|
|
612
640
|
download.url = fetcher.url;
|
|
613
641
|
download.client = fetcher.client;
|
|
614
642
|
download.request = request;
|
|
643
|
+
queueChanged = true;
|
|
615
644
|
}
|
|
616
645
|
}
|
|
617
646
|
fetcher.download = download;
|
|
@@ -627,7 +656,7 @@ final class FastImageSharedDownloads {
|
|
|
627
656
|
}
|
|
628
657
|
}
|
|
629
658
|
if (ready != null) fetcher.deliver(ready, callback);
|
|
630
|
-
if (
|
|
659
|
+
if (queueChanged) startQueued();
|
|
631
660
|
}
|
|
632
661
|
|
|
633
662
|
// Sends the request (off the main thread, and outside the locks:
|
|
@@ -656,10 +685,24 @@ final class FastImageSharedDownloads {
|
|
|
656
685
|
if (started != null) started.cancel();
|
|
657
686
|
}
|
|
658
687
|
|
|
688
|
+
// With downloads locked: its request has its response, failed, or
|
|
689
|
+
// wasn't sent, so another can be sent to its host.
|
|
690
|
+
private void responded() {
|
|
691
|
+
if (host == null) return;
|
|
692
|
+
int sent = sentTo.get(host) - 1;
|
|
693
|
+
if (sent == 0) {
|
|
694
|
+
sentTo.remove(host);
|
|
695
|
+
} else {
|
|
696
|
+
sentTo.put(host, sent);
|
|
697
|
+
}
|
|
698
|
+
host = null;
|
|
699
|
+
}
|
|
700
|
+
|
|
659
701
|
// It no longer runs: the next download waiting can start.
|
|
660
702
|
private void stopped() {
|
|
661
703
|
synchronized (downloads) {
|
|
662
704
|
if (!running) return;
|
|
705
|
+
responded();
|
|
663
706
|
running = false;
|
|
664
707
|
FastImageSharedDownloads.running--;
|
|
665
708
|
}
|
|
@@ -853,6 +896,7 @@ final class FastImageSharedDownloads {
|
|
|
853
896
|
fromHttpCache = response.networkResponse() == null && response.cacheResponse() != null;
|
|
854
897
|
final long length = body.contentLength();
|
|
855
898
|
synchronized (downloads) {
|
|
899
|
+
responded();
|
|
856
900
|
// No progress without a length.
|
|
857
901
|
if (length <= 0) silent = true;
|
|
858
902
|
}
|
|
@@ -862,6 +906,7 @@ final class FastImageSharedDownloads {
|
|
|
862
906
|
readInto(body, length);
|
|
863
907
|
}
|
|
864
908
|
});
|
|
909
|
+
startQueued();
|
|
865
910
|
}
|
|
866
911
|
|
|
867
912
|
// Sends a progress step, unless the download has stopped: checked and
|
|
@@ -128,6 +128,8 @@ final class FastImageSourceSize {
|
|
|
128
128
|
private static DownsampleStrategy strategy(@Nullable ImageView.ScaleType scaleType) {
|
|
129
129
|
if (scaleType == null) return DownsampleStrategy.DEFAULT;
|
|
130
130
|
switch (scaleType) {
|
|
131
|
+
case CENTER:
|
|
132
|
+
return FULL_SIZE;
|
|
131
133
|
case CENTER_CROP:
|
|
132
134
|
return DownsampleStrategy.CENTER_OUTSIDE;
|
|
133
135
|
case CENTER_INSIDE:
|
|
@@ -142,6 +144,26 @@ final class FastImageSourceSize {
|
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
146
|
|
|
147
|
+
// At the image's own size (objectFit none shows it at that size, whatever
|
|
148
|
+
// the view's), or scaled down to at most MAX_PIXELS pixels, which a
|
|
149
|
+
// bitmap can be drawn at (Android won't draw one over 100 MB).
|
|
150
|
+
static final DownsampleStrategy FULL_SIZE = new FullSize();
|
|
151
|
+
|
|
152
|
+
private static final float MAX_PIXELS = 4096f * 4096f;
|
|
153
|
+
|
|
154
|
+
private static final class FullSize extends DownsampleStrategy {
|
|
155
|
+
@Override
|
|
156
|
+
public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {
|
|
157
|
+
float pixels = (float) sourceWidth * sourceHeight;
|
|
158
|
+
return pixels > MAX_PIXELS ? (float) Math.sqrt(MAX_PIXELS / pixels) : 1;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
@Override
|
|
162
|
+
public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {
|
|
163
|
+
return SampleSizeRounding.QUALITY;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
145
167
|
// The image's size, or null when it has to be read from a local image's
|
|
146
168
|
// header first (see readLocal).
|
|
147
169
|
@Nullable
|
|
@@ -6,6 +6,7 @@ import androidx.annotation.NonNull;
|
|
|
6
6
|
import androidx.annotation.Nullable;
|
|
7
7
|
|
|
8
8
|
import com.bumptech.glide.Registry;
|
|
9
|
+
import com.bumptech.glide.load.Option;
|
|
9
10
|
import com.bumptech.glide.load.Options;
|
|
10
11
|
import com.bumptech.glide.load.ResourceDecoder;
|
|
11
12
|
import com.bumptech.glide.load.engine.Resource;
|
|
@@ -17,6 +18,7 @@ import java.io.IOException;
|
|
|
17
18
|
import java.io.InputStream;
|
|
18
19
|
import java.nio.ByteBuffer;
|
|
19
20
|
import java.nio.charset.StandardCharsets;
|
|
21
|
+
import java.security.MessageDigest;
|
|
20
22
|
import java.util.Locale;
|
|
21
23
|
|
|
22
24
|
// SVG images (static), drawn with AndroidSVG, which FastImage includes
|
|
@@ -36,6 +38,31 @@ final class FastImageSvg {
|
|
|
36
38
|
@Nullable
|
|
37
39
|
private static Boolean available;
|
|
38
40
|
|
|
41
|
+
// objectFit scale-down and none show an image at its own size in dp
|
|
42
|
+
// (FastImageViewWithUrl.updateImageMatrix): an SVG is drawn at that size,
|
|
43
|
+
// the screen's pixels per dp (DENSITY) times its own, rather than at its
|
|
44
|
+
// size in pixels and then enlarged. With scale-down (FITS), no larger than
|
|
45
|
+
// the view. Part of the disk cache key, since the bitmap's size depends on
|
|
46
|
+
// them. Unset (0) for the other fits, which draw it for the view's size.
|
|
47
|
+
static final Option<Float> DENSITY = Option.disk(
|
|
48
|
+
"com.dylanvann.fastimage.FastImageSvg.Density", 0f,
|
|
49
|
+
new Option.CacheKeyUpdater<Float>() {
|
|
50
|
+
@Override
|
|
51
|
+
public void update(@NonNull byte[] keyBytes, @NonNull Float value, @NonNull MessageDigest messageDigest) {
|
|
52
|
+
messageDigest.update(keyBytes);
|
|
53
|
+
messageDigest.update(ByteBuffer.allocate(4).putFloat(value).array());
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
static final Option<Boolean> FITS = Option.disk(
|
|
57
|
+
"com.dylanvann.fastimage.FastImageSvg.Fits", false,
|
|
58
|
+
new Option.CacheKeyUpdater<Boolean>() {
|
|
59
|
+
@Override
|
|
60
|
+
public void update(@NonNull byte[] keyBytes, @NonNull Boolean value, @NonNull MessageDigest messageDigest) {
|
|
61
|
+
messageDigest.update(keyBytes);
|
|
62
|
+
messageDigest.update((byte) (value ? 1 : 0));
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
|
|
39
66
|
private FastImageSvg() {
|
|
40
67
|
}
|
|
41
68
|
|
|
@@ -70,6 +70,16 @@ final class FastImageSvgRenderer {
|
|
|
70
70
|
float scale = strategy != null
|
|
71
71
|
? strategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight)
|
|
72
72
|
: 1f;
|
|
73
|
+
// objectFit scale-down and none: at its own size in dp (see
|
|
74
|
+
// FastImageSvg.DENSITY), and for scale-down no larger than the view.
|
|
75
|
+
Float density = options.get(FastImageSvg.DENSITY);
|
|
76
|
+
if (density != null && density > 0) {
|
|
77
|
+
scale = density;
|
|
78
|
+
if (Boolean.TRUE.equals(options.get(FastImageSvg.FITS))) {
|
|
79
|
+
scale = Math.min(scale, Math.min(
|
|
80
|
+
targetWidth / (float) sourceWidth, targetHeight / (float) sourceHeight));
|
|
81
|
+
}
|
|
82
|
+
}
|
|
73
83
|
if (Float.isNaN(scale) || Float.isInfinite(scale) || scale <= 0) scale = 1f;
|
|
74
84
|
float pixels = sourceWidth * scale * sourceHeight * scale;
|
|
75
85
|
if (pixels > MAX_PIXELS) scale *= (float) Math.sqrt(MAX_PIXELS / pixels);
|
|
@@ -49,6 +49,11 @@ class FastImageViewConverter {
|
|
|
49
49
|
put("cover", ScaleType.CENTER_CROP);
|
|
50
50
|
put("stretch", ScaleType.FIT_XY);
|
|
51
51
|
put("center", ScaleType.CENTER_INSIDE);
|
|
52
|
+
// objectFit="scale-down": as center, laid out in dp (see
|
|
53
|
+
// FastImageViewWithUrl.setResizeMode).
|
|
54
|
+
put("scale-down", ScaleType.CENTER_INSIDE);
|
|
55
|
+
// objectFit="none": the image at its own size, centered.
|
|
56
|
+
put("none", ScaleType.CENTER);
|
|
52
57
|
}};
|
|
53
58
|
|
|
54
59
|
// Whether the source has a uri to load (not null, missing or blank).
|
|
@@ -184,7 +184,7 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> {
|
|
|
184
184
|
boolean repeat = "repeat".equals(resizeMode);
|
|
185
185
|
final FastImageViewWithUrl.ScaleType scaleType =
|
|
186
186
|
repeat ? FastImageViewWithUrl.ScaleType.FIT_XY : FastImageViewConverter.getScaleType(resizeMode);
|
|
187
|
-
view.setResizeMode(scaleType, repeat);
|
|
187
|
+
view.setResizeMode(scaleType, repeat, "center".equals(resizeMode));
|
|
188
188
|
}
|
|
189
189
|
|
|
190
190
|
@Override
|