react-native-fast-image 9.3.5 → 9.3.7

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 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'` (images are always decoded at about the view's size there).
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` · iOS only
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. 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 this way, and on Android images are always decoded at about the view's size (but Android 16 and later decode animated WebP at full size, and scale it as they draw it).
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
- @Override
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
- // one to keep sharing cached images between views.
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
- static RequestOptions scaleTypeOptions(@Nullable ImageView.ScaleType scaleType, Capture capture, @Nullable FastImageBlur blur) {
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
- return new Capture(strategy(scaleType), String.valueOf(model));
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), 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).
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
- private static final float MAX_PIXELS = 4096f * 4096f;
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 != null
71
- ? strategy.getScaleFactor(sourceWidth, sourceHeight, targetWidth, targetHeight)
72
- : 1f;
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);
@@ -780,13 +780,15 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
780
780
  private ScaleType mLayoutScaleType = ScaleType.FIT_CENTER;
781
781
 
782
782
  // Glide crops or fits the bitmap for the scale type when it loads, so a
783
- // new resizeMode needs a reload to take effect (#762).
783
+ // new resizeMode needs a reload to take effect (#762): the image that's
784
+ // showing, loaded again quietly (see reblur), as for a new blurRadius,
785
+ // without the load events, as on iOS.
784
786
  public void setResizeMode(ScaleType scaleType, boolean repeat, boolean inPixels) {
785
787
  if (scaleType == mResizeScaleType && repeat == mRepeat && inPixels == mInPixels) return;
786
788
  mResizeScaleType = scaleType;
787
789
  mInPixels = inPixels;
788
790
  mRepeat = repeat;
789
- mNeedsReload = true;
791
+ mFitChanged = true;
790
792
  // A loaded image on screen was cropped or fitted for the old scale
791
793
  // type: it keeps its layout until the image for the new one is ready
792
794
  // (onImageLoaded), or the load fails. Laid out for the new one, a
@@ -918,9 +920,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
918
920
 
919
921
  // imageRendering="pixelated": no resampling by Glide, and drawn without
920
922
  // filtering (sharp pixels). "smooth" is iOS only; here it's the same as
921
- // "auto": Glide already decodes a large image at about the view's size, and
922
- // averaging it properly would need a full-size decode. Part of the
923
- // request, so a change reloads.
923
+ // "auto": Glide decodes a large image at about the view's size (unless
924
+ // downsample is false), and averaging it properly would need mipmaps or
925
+ // a full-size decode. Part of the request, so a change reloads.
924
926
  private boolean mPixelated = false;
925
927
 
926
928
  public void setImageRendering(@Nullable String imageRendering) {
@@ -930,10 +932,24 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
930
932
  mNeedsReload = true;
931
933
  }
932
934
 
935
+ // downsample false: images are decoded at their own size (capped, see
936
+ // FastImageSourceSize.FULL_SIZE), not cropped or fitted to the view's
937
+ // size, so a view zoomed in on with a transform shows the image's detail.
938
+ // Part of the request, so a change reloads.
939
+ private boolean mDownsample = true;
940
+
941
+ public void setDownsample(boolean downsample) {
942
+ if (downsample == mDownsample) return;
943
+ mDownsample = downsample;
944
+ mNeedsReload = true;
945
+ }
946
+
933
947
  // blurRadius, in pixels (0 is no blur). Part of the request (see
934
948
  // reblur for a change).
935
949
  private float mBlurRadius = 0;
936
950
  private boolean mBlurChanged = false;
951
+ // resizeMode or objectFit changed (setResizeMode).
952
+ private boolean mFitChanged = false;
937
953
 
938
954
  public void setBlurRadius(float blurRadius) {
939
955
  if (blurRadius == mBlurRadius) return;
@@ -968,8 +984,8 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
968
984
  .dontTransform();
969
985
  if (blur != null) options = options.optionalTransform(blur);
970
986
  } else {
971
- options = FastImageSourceSize.scaleTypeOptions(
972
- mResizeScaleType, FastImageSourceSize.capture(mResizeScaleType, model), blur);
987
+ options = FastImageSourceSize.scaleTypeOptions(mResizeScaleType,
988
+ FastImageSourceSize.capture(mResizeScaleType, model, mDownsample), blur, mDownsample);
973
989
  boolean ownSize = mResizeScaleType == ScaleType.CENTER_INSIDE || mResizeScaleType == ScaleType.CENTER;
974
990
  // scale-down and none show it at its own size in dp: an SVG is
975
991
  // drawn at that size, so it's sharp (not with a blur, which hides
@@ -1111,10 +1127,11 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
1111
1127
  into(shown, builder, meanwhile, true);
1112
1128
  }
1113
1129
 
1114
- // blurRadius changed, and nothing else that needs a reload: the image
1115
- // that's showing, blurred again, with no events, as reloadForSize does: it's
1116
- // decoded again from the cache and blurred on Glide's threads, and the view
1117
- // keeps the current image until then. If that fails, it shows the image
1130
+ // blurRadius or resizeMode changed, and nothing else that needs a reload:
1131
+ // the image that's showing, blurred or fitted again, with no events, as
1132
+ // reloadForSize does: it's decoded again from the cache and blurred or
1133
+ // fitted on Glide's threads, and the view keeps the current image (and its
1134
+ // layout, see onImageLoaded) until then. If that fails, it shows the image
1118
1135
  // from Glide's caches; cache: 'web' skips them, so the view shows
1119
1136
  // defaultSource (or nothing) then. Also while it's loading again that way
1120
1137
  // (e.g. the blur changes every frame): that load is replaced. A load that
@@ -1228,8 +1245,9 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
1228
1245
  @Nullable RequestManager requestManager) {
1229
1246
  if (mDropped) return;
1230
1247
  mManager = manager;
1231
- if (mBlurChanged && !mNeedsReload) {
1248
+ if ((mBlurChanged || mFitChanged) && !mNeedsReload) {
1232
1249
  mBlurChanged = false;
1250
+ mFitChanged = false;
1233
1251
  reblur();
1234
1252
  }
1235
1253
  if (!mNeedsReload)
@@ -1260,6 +1278,7 @@ class FastImageViewWithUrl extends AppCompatImageView implements ReactPointerEve
1260
1278
  // defaultSource, resizeMode), not for every prop update.
1261
1279
  mNeedsReload = false;
1262
1280
  mBlurChanged = false;
1281
+ mFitChanged = false;
1263
1282
  boolean switching = mSwitchesSource;
1264
1283
  mSwitchesSource = false;
1265
1284
  boolean restarting = mRestarting;
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'` (images are always decoded
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. If the view grows, the image is decoded
420
- * again for its new size (from the disk cache).
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. Needs SDWebImage 5.19.7 or later: before 5.19 images
427
- * are decoded at full size, and 5.19.0 to 5.19.6 show photos stored
428
- * sideways with an EXIF orientation (most phone photos) sideways. Photo
429
- * library images are always decoded this way, and on Android images are
430
- * always decoded at about the view's size (but Android 16 and later
431
- * decode animated WebP at full size, and scale it as they draw it).
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;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "9.3.5",
3
+ "version": "9.3.7",
4
4
  "description": "FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",