react-native-fast-image 9.3.5 → 9.3.6
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 +5 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageGif.java +13 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +37 -13
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvgRenderer.java +8 -4
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +5 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +18 -6
- package/dist/index.d.ts +15 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -311,7 +311,7 @@ Changing it restarts the animation.
|
|
|
311
311
|
How the image is filtered when it's drawn smaller or larger than its size (like CSS's `image-rendering`):
|
|
312
312
|
|
|
313
313
|
- `'auto'`: the platform's usual filtering.
|
|
314
|
-
- `'smooth'`: iOS only. Keeps a large image drawn much smaller than its size (e.g. a big photo as a thumbnail, or fine lines and text) from looking jagged or noisy. Such an image is already decoded at about the view's size by default (see `downsample`), so this is for images shown at less than half their size with `downsample={false}`, or a little smaller than their size. **It uses more memory:** the image is also kept at smaller sizes for drawing, about a third more than the decoded image. On Android it's the same as `'auto'
|
|
314
|
+
- `'smooth'`: iOS only. Keeps a large image drawn much smaller than its size (e.g. a big photo as a thumbnail, or fine lines and text) from looking jagged or noisy. Such an image is already decoded at about the view's size by default (see `downsample`), so this is for images shown at less than half their size with `downsample={false}`, or a little smaller than their size. **It uses more memory:** the image is also kept at smaller sizes for drawing, about a third more than the decoded image. On Android it's the same as `'auto'`.
|
|
315
315
|
- `'pixelated'`: sharp pixels, without smoothing, e.g. for pixel art drawn larger than its size. On Android, animated images are still smoothed.
|
|
316
316
|
|
|
317
317
|
---
|
|
@@ -342,14 +342,14 @@ Downloads, local files (`file://`, `content://`) and bundled images (`require()`
|
|
|
342
342
|
|
|
343
343
|
#### `downsample`
|
|
344
344
|
|
|
345
|
-
**Type:** `boolean` · **Default:** `true`
|
|
345
|
+
**Type:** `boolean` · **Default:** `true`
|
|
346
346
|
|
|
347
347
|
Decodes a large image at about the size it's shown at, instead of at full size, so it takes much less memory.
|
|
348
348
|
|
|
349
|
-
- `true`: an image at least twice the size its view needs is decoded at about the view's size. If the view grows, the image is decoded again for its new size (from the disk cache).
|
|
350
|
-
- `false`: images are decoded at full size, e.g. for an image that's zoomed in on with a transform (a pinch-to-zoom viewer), which would otherwise show the smaller copy enlarged.
|
|
349
|
+
- `true`: an image at least twice the size its view needs is decoded at about the view's size (on Android, any image larger than its view). If the view grows, the image is decoded again for its new size (from the disk cache).
|
|
350
|
+
- `false`: images are decoded at full size, e.g. for an image that's zoomed in on with a transform (a pinch-to-zoom viewer), which would otherwise show the smaller copy enlarged. **It uses more memory:** a 12-megapixel photo takes about 48 MB decoded, however small it's shown. On Android, an image over 16 megapixels (4096 × 4096) is decoded at the largest size under that, which Android can still draw.
|
|
351
351
|
|
|
352
|
-
It doesn't change `onLoad`'s width and height (the image's own size) or the cached file.
|
|
352
|
+
It doesn't change `onLoad`'s width and height (the image's own size) or the cached file. On iOS it needs SDWebImage 5.19.7 or later: before 5.19 images are decoded at full size, and 5.19.0 to 5.19.6 show photos stored sideways with an EXIF orientation (most phone photos) sideways. Photo library images are always decoded at about the view's size. Android 16 and later decode animated WebP at full size either way, and scale it as they draw it.
|
|
353
353
|
|
|
354
354
|
If you control the images, serve them at the size they're shown (resized on your server or by an image CDN), which also saves bandwidth.
|
|
355
355
|
|
|
@@ -34,8 +34,10 @@ final class FastImageGif {
|
|
|
34
34
|
private FastImageGif() {}
|
|
35
35
|
|
|
36
36
|
// Null if the GIF can't be decoded again (then the view shows Glide's).
|
|
37
|
+
// Full size (downsample false): at the GIF's own size, or sampled down to
|
|
38
|
+
// at most FastImageSourceSize.MAX_PIXELS pixels.
|
|
37
39
|
@Nullable
|
|
38
|
-
static GifDrawable copy(Context context, GifDrawable gif, int width, int height) {
|
|
40
|
+
static GifDrawable copy(Context context, GifDrawable gif, int width, int height, boolean fullSize) {
|
|
39
41
|
if (width <= 0 || height <= 0) return null;
|
|
40
42
|
ByteBuffer data = gif.getBuffer();
|
|
41
43
|
GifHeader header = new GifHeaderParser().setData(data).parseHeader();
|
|
@@ -44,7 +46,7 @@ final class FastImageGif {
|
|
|
44
46
|
BitmapPool bitmapPool = glide.getBitmapPool();
|
|
45
47
|
GifDecoder decoder = new StandardGifDecoder(
|
|
46
48
|
new GifBitmapProvider(bitmapPool, glide.getArrayPool()),
|
|
47
|
-
header, gif.getBuffer(), sampleSize(header, width, height));
|
|
49
|
+
header, gif.getBuffer(), fullSize ? fullSizeSampleSize(header) : sampleSize(header, width, height));
|
|
48
50
|
decoder.setDefaultBitmapConfig(Bitmap.Config.ARGB_8888);
|
|
49
51
|
decoder.advance();
|
|
50
52
|
Bitmap firstFrame = decoder.getNextFrame();
|
|
@@ -101,6 +103,15 @@ final class FastImageGif {
|
|
|
101
103
|
}
|
|
102
104
|
}
|
|
103
105
|
|
|
106
|
+
private static int fullSizeSampleSize(GifHeader header) {
|
|
107
|
+
int sampleSize = 1;
|
|
108
|
+
while ((float) (header.getWidth() / sampleSize) * (header.getHeight() / sampleSize)
|
|
109
|
+
> FastImageSourceSize.MAX_PIXELS) {
|
|
110
|
+
sampleSize *= 2;
|
|
111
|
+
}
|
|
112
|
+
return sampleSize;
|
|
113
|
+
}
|
|
114
|
+
|
|
104
115
|
// ByteBufferGifDecoder's.
|
|
105
116
|
private static int sampleSize(GifHeader header, int width, int height) {
|
|
106
117
|
int exact = Math.min(header.getHeight() / height, header.getWidth() / width);
|
|
@@ -49,50 +49,72 @@ final class FastImageSourceSize {
|
|
|
49
49
|
// options, so the size is recorded by image rather than by request.
|
|
50
50
|
static final class Capture extends DownsampleStrategy {
|
|
51
51
|
private final DownsampleStrategy strategy;
|
|
52
|
+
// What an SVG is drawn at (see FastImageSvgRenderer): the strategy for
|
|
53
|
+
// the view's scale type, also when images are decoded at full size
|
|
54
|
+
// (downsample false). An SVG has no size of its own to decode at, and
|
|
55
|
+
// drawn at the view's size it's already sharp.
|
|
56
|
+
private final DownsampleStrategy vectorStrategy;
|
|
52
57
|
private final String key;
|
|
53
58
|
private boolean recorded;
|
|
54
59
|
|
|
55
60
|
Capture(DownsampleStrategy strategy, String key) {
|
|
61
|
+
this(strategy, strategy, key);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
Capture(DownsampleStrategy strategy, DownsampleStrategy vectorStrategy, String key) {
|
|
56
65
|
this.strategy = strategy;
|
|
66
|
+
this.vectorStrategy = vectorStrategy;
|
|
57
67
|
this.key = key;
|
|
58
68
|
}
|
|
59
69
|
|
|
60
|
-
|
|
61
|
-
public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {
|
|
70
|
+
private void record(int sourceWidth, int sourceHeight) {
|
|
62
71
|
// Called again with the sampled size; the first call has the image's.
|
|
63
72
|
if (!recorded) {
|
|
64
73
|
recorded = true;
|
|
65
74
|
SIZES.put(key, new int[]{sourceWidth, sourceHeight});
|
|
66
75
|
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
@Override
|
|
79
|
+
public float getScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {
|
|
80
|
+
record(sourceWidth, sourceHeight);
|
|
67
81
|
return strategy.getScaleFactor(sourceWidth, sourceHeight, requestedWidth, requestedHeight);
|
|
68
82
|
}
|
|
69
83
|
|
|
84
|
+
float getVectorScaleFactor(int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {
|
|
85
|
+
record(sourceWidth, sourceHeight);
|
|
86
|
+
return vectorStrategy.getScaleFactor(sourceWidth, sourceHeight, requestedWidth, requestedHeight);
|
|
87
|
+
}
|
|
88
|
+
|
|
70
89
|
@Override
|
|
71
90
|
public SampleSizeRounding getSampleSizeRounding(int sourceWidth, int sourceHeight, int requestedWidth, int requestedHeight) {
|
|
72
91
|
return strategy.getSampleSizeRounding(sourceWidth, sourceHeight, requestedWidth, requestedHeight);
|
|
73
92
|
}
|
|
74
93
|
|
|
75
94
|
// Glide's memory cache keys include the strategy, so match the wrapped
|
|
76
|
-
//
|
|
95
|
+
// ones to keep sharing cached images between views.
|
|
77
96
|
@Override
|
|
78
97
|
public boolean equals(Object o) {
|
|
79
|
-
return o instanceof Capture && ((Capture) o).strategy.equals(strategy)
|
|
98
|
+
return o instanceof Capture && ((Capture) o).strategy.equals(strategy)
|
|
99
|
+
&& ((Capture) o).vectorStrategy.equals(vectorStrategy);
|
|
80
100
|
}
|
|
81
101
|
|
|
82
102
|
@Override
|
|
83
103
|
public int hashCode() {
|
|
84
|
-
return strategy.hashCode();
|
|
104
|
+
return 31 * strategy.hashCode() + vectorStrategy.hashCode();
|
|
85
105
|
}
|
|
86
106
|
}
|
|
87
107
|
|
|
88
108
|
// What RequestBuilder.into(ImageView) applies for the view's scale type
|
|
89
109
|
// (it leaves a transformation that's already set alone), with the
|
|
90
110
|
// downsample strategy it implies wrapped by capture, and then blur if it
|
|
91
|
-
// isn't null.
|
|
92
|
-
|
|
111
|
+
// isn't null. Not downsampled (downsample false), the image isn't cropped
|
|
112
|
+
// or fitted to the view's size either: the view scales it as it draws it.
|
|
113
|
+
static RequestOptions scaleTypeOptions(@Nullable ImageView.ScaleType scaleType, Capture capture, @Nullable FastImageBlur blur,
|
|
114
|
+
boolean downsample) {
|
|
93
115
|
RequestOptions options = new RequestOptions();
|
|
94
116
|
BitmapTransformation transformation = null;
|
|
95
|
-
if (scaleType != null) {
|
|
117
|
+
if (scaleType != null && downsample) {
|
|
96
118
|
switch (scaleType) {
|
|
97
119
|
case CENTER_CROP:
|
|
98
120
|
options = options.optionalCenterCrop();
|
|
@@ -121,8 +143,9 @@ final class FastImageSourceSize {
|
|
|
121
143
|
return options.downsample(capture);
|
|
122
144
|
}
|
|
123
145
|
|
|
124
|
-
static Capture capture(@Nullable ImageView.ScaleType scaleType, Object model) {
|
|
125
|
-
|
|
146
|
+
static Capture capture(@Nullable ImageView.ScaleType scaleType, Object model, boolean downsample) {
|
|
147
|
+
DownsampleStrategy strategy = strategy(scaleType);
|
|
148
|
+
return new Capture(downsample ? strategy : FULL_SIZE, strategy, String.valueOf(model));
|
|
126
149
|
}
|
|
127
150
|
|
|
128
151
|
private static DownsampleStrategy strategy(@Nullable ImageView.ScaleType scaleType) {
|
|
@@ -145,11 +168,12 @@ final class FastImageSourceSize {
|
|
|
145
168
|
}
|
|
146
169
|
|
|
147
170
|
// At the image's own size (objectFit none shows it at that size, whatever
|
|
148
|
-
// the view's
|
|
149
|
-
//
|
|
171
|
+
// the view's, and downsample false decodes every image this way), or
|
|
172
|
+
// scaled down to at most MAX_PIXELS pixels, which a bitmap can be drawn at
|
|
173
|
+
// (Android won't draw one over 100 MB).
|
|
150
174
|
static final DownsampleStrategy FULL_SIZE = new FullSize();
|
|
151
175
|
|
|
152
|
-
|
|
176
|
+
static final float MAX_PIXELS = 4096f * 4096f;
|
|
153
177
|
|
|
154
178
|
private static final class FullSize extends DownsampleStrategy {
|
|
155
179
|
@Override
|
|
@@ -63,13 +63,17 @@ final class FastImageSvgRenderer {
|
|
|
63
63
|
int sourceHeight = Math.max(1, Math.round(ownHeight));
|
|
64
64
|
|
|
65
65
|
// The strategy also records the SVG's own size for onLoad
|
|
66
|
-
// (FastImageSourceSize.Capture)
|
|
66
|
+
// (FastImageSourceSize.Capture), and has the one for the view when
|
|
67
|
+
// images are decoded at full size (downsample false).
|
|
67
68
|
DownsampleStrategy strategy = options.get(DownsampleStrategy.OPTION);
|
|
68
69
|
int targetWidth = width > 0 ? width : sourceWidth;
|
|
69
70
|
int targetHeight = height > 0 ? height : sourceHeight;
|
|
70
|
-
float scale = strategy
|
|
71
|
-
? strategy.
|
|
72
|
-
|
|
71
|
+
float scale = strategy instanceof FastImageSourceSize.Capture
|
|
72
|
+
? ((FastImageSourceSize.Capture) strategy).getVectorScaleFactor(
|
|
73
|
+
sourceWidth, sourceHeight, targetWidth, targetHeight)
|
|
74
|
+
: strategy != null
|
|
75
|
+
? strategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight)
|
|
76
|
+
: 1f;
|
|
73
77
|
// objectFit scale-down and none: at its own size in dp (see
|
|
74
78
|
// FastImageSvg.DENSITY), and for scale-down no larger than the view.
|
|
75
79
|
Float density = options.get(FastImageSvg.DENSITY);
|
|
@@ -147,6 +147,11 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> {
|
|
|
147
147
|
view.setImageRendering(imageRendering);
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
+
@ReactProp(name = "downsample", defaultBoolean = true)
|
|
151
|
+
public void setDownsample(FastImageViewWithUrl view, boolean downsample) {
|
|
152
|
+
view.setDownsample(downsample);
|
|
153
|
+
}
|
|
154
|
+
|
|
150
155
|
@ReactProp(name = "blurRadius")
|
|
151
156
|
public void setBlurRadius(FastImageViewWithUrl view, float blurRadius) {
|
|
152
157
|
view.setBlurRadius(PixelUtil.toPixelFromDIP(blurRadius));
|
|
@@ -659,7 +659,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
659
659
|
}
|
|
660
660
|
if (resource instanceof GifDrawable) {
|
|
661
661
|
GifDrawable own = FastImageGif.copy(
|
|
662
|
-
getContext(), (GifDrawable) resource, mLoadingWidth, mLoadingHeight);
|
|
662
|
+
getContext(), (GifDrawable) resource, mLoadingWidth, mLoadingHeight, !mDownsample);
|
|
663
663
|
if (own != null) {
|
|
664
664
|
applyLoopCount(own);
|
|
665
665
|
super.onResourceReady(own, transition);
|
|
@@ -918,9 +918,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
918
918
|
|
|
919
919
|
// imageRendering="pixelated": no resampling by Glide, and drawn without
|
|
920
920
|
// filtering (sharp pixels). "smooth" is iOS only; here it's the same as
|
|
921
|
-
// "auto": Glide
|
|
922
|
-
// averaging it properly would need
|
|
923
|
-
// request, so a change reloads.
|
|
921
|
+
// "auto": Glide decodes a large image at about the view's size (unless
|
|
922
|
+
// downsample is false), and averaging it properly would need mipmaps or
|
|
923
|
+
// a full-size decode. Part of the request, so a change reloads.
|
|
924
924
|
private boolean mPixelated = false;
|
|
925
925
|
|
|
926
926
|
public void setImageRendering(@Nullable String imageRendering) {
|
|
@@ -930,6 +930,18 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
930
930
|
mNeedsReload = true;
|
|
931
931
|
}
|
|
932
932
|
|
|
933
|
+
// downsample false: images are decoded at their own size (capped, see
|
|
934
|
+
// FastImageSourceSize.FULL_SIZE), not cropped or fitted to the view's
|
|
935
|
+
// size, so a view zoomed in on with a transform shows the image's detail.
|
|
936
|
+
// Part of the request, so a change reloads.
|
|
937
|
+
private boolean mDownsample = true;
|
|
938
|
+
|
|
939
|
+
public void setDownsample(boolean downsample) {
|
|
940
|
+
if (downsample == mDownsample) return;
|
|
941
|
+
mDownsample = downsample;
|
|
942
|
+
mNeedsReload = true;
|
|
943
|
+
}
|
|
944
|
+
|
|
933
945
|
// blurRadius, in pixels (0 is no blur). Part of the request (see
|
|
934
946
|
// reblur for a change).
|
|
935
947
|
private float mBlurRadius = 0;
|
|
@@ -968,8 +980,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
|
|
|
968
980
|
.dontTransform();
|
|
969
981
|
if (blur != null) options = options.optionalTransform(blur);
|
|
970
982
|
} else {
|
|
971
|
-
options = FastImageSourceSize.scaleTypeOptions(
|
|
972
|
-
|
|
983
|
+
options = FastImageSourceSize.scaleTypeOptions(mResizeScaleType,
|
|
984
|
+
FastImageSourceSize.capture(mResizeScaleType, model, mDownsample), blur, mDownsample);
|
|
973
985
|
boolean ownSize = mResizeScaleType == ScaleType.CENTER_INSIDE || mResizeScaleType == ScaleType.CENTER;
|
|
974
986
|
// scale-down and none show it at its own size in dp: an SVG is
|
|
975
987
|
// drawn at that size, so it's sharp (not with a blur, which hides
|
package/dist/index.d.ts
CHANGED
|
@@ -376,8 +376,7 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
376
376
|
* at less than half their size with `downsample={false}`, or a little
|
|
377
377
|
* smaller than their size. **It uses more memory:** the image is also
|
|
378
378
|
* kept at smaller sizes for drawing, about a third more than the decoded
|
|
379
|
-
* image. On Android it's the same as `'auto'
|
|
380
|
-
* at about the view's size there).
|
|
379
|
+
* image. On Android it's the same as `'auto'`.
|
|
381
380
|
* - `'pixelated'`: sharp pixels, without smoothing, e.g. for pixel art
|
|
382
381
|
* drawn larger than its size. On Android, animated images are still
|
|
383
382
|
* smoothed.
|
|
@@ -416,25 +415,29 @@ export interface FastImageProps extends AccessibilityProps, ViewProps {
|
|
|
416
415
|
* full size, so it takes much less memory.
|
|
417
416
|
*
|
|
418
417
|
* - `true`: an image at least twice the size its view needs is decoded
|
|
419
|
-
* at about the view's size
|
|
420
|
-
* again for its new size
|
|
418
|
+
* at about the view's size (on Android, any image larger than its
|
|
419
|
+
* view). If the view grows, the image is decoded again for its new size
|
|
420
|
+
* (from the disk cache).
|
|
421
421
|
* - `false`: images are decoded at full size, e.g. for an image that's
|
|
422
422
|
* zoomed in on with a transform (a pinch-to-zoom viewer), which would
|
|
423
|
-
* otherwise show the smaller copy enlarged.
|
|
423
|
+
* otherwise show the smaller copy enlarged. **It uses more memory:** a
|
|
424
|
+
* 12-megapixel photo takes about 48 MB decoded, however small it's
|
|
425
|
+
* shown. On Android, an image over 16 megapixels (4096 × 4096) is
|
|
426
|
+
* decoded at the largest size under that, which Android can still
|
|
427
|
+
* draw.
|
|
424
428
|
*
|
|
425
429
|
* It doesn't change `onLoad`'s width and height (the image's own size) or
|
|
426
|
-
* the cached file.
|
|
427
|
-
* are decoded at full size, and 5.19.0 to 5.19.6 show photos
|
|
428
|
-
* sideways with an EXIF orientation (most phone photos) sideways.
|
|
429
|
-
* library images are always decoded
|
|
430
|
-
*
|
|
431
|
-
*
|
|
430
|
+
* the cached file. On iOS it needs SDWebImage 5.19.7 or later: before
|
|
431
|
+
* 5.19 images are decoded at full size, and 5.19.0 to 5.19.6 show photos
|
|
432
|
+
* stored sideways with an EXIF orientation (most phone photos) sideways.
|
|
433
|
+
* Photo library images are always decoded at about the view's size.
|
|
434
|
+
* Android 16 and later decode animated WebP at full size either way, and
|
|
435
|
+
* scale it as they draw it.
|
|
432
436
|
*
|
|
433
437
|
* If you control the images, serve them at the size they're shown
|
|
434
438
|
* (resized on your server or by an image CDN), which also saves
|
|
435
439
|
* bandwidth.
|
|
436
440
|
*
|
|
437
|
-
* @platform ios
|
|
438
441
|
* @default true
|
|
439
442
|
*/
|
|
440
443
|
downsample?: boolean;
|