react-native-fast-image 8.24.0 → 8.25.0

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
@@ -199,6 +199,7 @@ For views that get reused for other content, such as rows in FlashList or recycl
199
199
  - `FastImage.resizeMode.cover` **(Default)** - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or larger than the corresponding dimension of the view (minus padding).
200
200
  - `FastImage.resizeMode.stretch` - Scale width and height independently, This may change the aspect ratio of the src.
201
201
  - `FastImage.resizeMode.center` - Do not scale the image, keep centered.
202
+ - `FastImage.resizeMode.repeat` - Repeat the image 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.
202
203
 
203
204
  ---
204
205
 
@@ -127,8 +127,11 @@ class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> imple
127
127
 
128
128
  @ReactProp(name = "resizeMode")
129
129
  public void setResizeMode(FastImageViewWithUrl view, String resizeMode) {
130
- final FastImageViewWithUrl.ScaleType scaleType = FastImageViewConverter.getScaleType(resizeMode);
131
- view.setResizeMode(scaleType);
130
+ // repeat fills the view with the tiled image (see setImageDrawable).
131
+ boolean repeat = "repeat".equals(resizeMode);
132
+ final FastImageViewWithUrl.ScaleType scaleType =
133
+ repeat ? FastImageViewWithUrl.ScaleType.FIT_XY : FastImageViewConverter.getScaleType(resizeMode);
134
+ view.setResizeMode(scaleType, repeat);
132
135
  }
133
136
 
134
137
  @Override
@@ -12,6 +12,7 @@ import android.graphics.Canvas;
12
12
  import android.graphics.Paint;
13
13
  import android.graphics.PorterDuff;
14
14
  import android.graphics.PorterDuffXfermode;
15
+ import android.graphics.Shader;
15
16
  import android.graphics.drawable.BitmapDrawable;
16
17
  import android.graphics.drawable.Drawable;
17
18
 
@@ -354,14 +355,41 @@ class FastImageViewWithUrl extends AppCompatImageView {
354
355
  : mLoopCount);
355
356
  }
356
357
 
358
+ // resizeMode repeat: the image (a GIF's first frame) repeated from the
359
+ // top-left at its own size, scaled down to fit the view if it's larger,
360
+ // as React Native's Image does. The view fills with it (FIT_XY).
361
+ private boolean mRepeat = false;
362
+
357
363
  // Glide crops or fits the bitmap for the scale type when it loads, so a
358
364
  // new resizeMode needs a reload to take effect (#762).
359
- public void setResizeMode(ScaleType scaleType) {
360
- if (scaleType == getScaleType()) return;
365
+ public void setResizeMode(ScaleType scaleType, boolean repeat) {
366
+ if (scaleType == getScaleType() && repeat == mRepeat) return;
361
367
  setScaleType(scaleType);
368
+ mRepeat = repeat;
362
369
  mNeedsReload = true;
363
370
  }
364
371
 
372
+ // Repeats what the view shows (the loaded image, and defaultSource).
373
+ @Override
374
+ public void setImageDrawable(@Nullable Drawable drawable) {
375
+ super.setImageDrawable(mRepeat ? tiled(drawable) : drawable);
376
+ }
377
+
378
+ @Nullable
379
+ private Drawable tiled(@Nullable Drawable drawable) {
380
+ if (!(drawable instanceof BitmapDrawable)) return drawable;
381
+ BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
382
+ if (bitmapDrawable.getTileModeX() == Shader.TileMode.REPEAT) return drawable;
383
+ Bitmap bitmap = bitmapDrawable.getBitmap();
384
+ if (bitmap == null) return drawable;
385
+ // A copy of the drawable (not of the bitmap): Glide's may be shown by
386
+ // other views too.
387
+ BitmapDrawable tiled = new BitmapDrawable(getResources(), bitmap);
388
+ tiled.setTileModeXY(Shader.TileMode.REPEAT, Shader.TileMode.REPEAT);
389
+ if (mPixelated) tiled.setFilterBitmap(false);
390
+ return tiled;
391
+ }
392
+
365
393
  // imageRendering="pixelated": no resampling by Glide, and drawn without
366
394
  // filtering (sharp pixels). "smooth" is iOS only; here it's the same as
367
395
  // "auto": Glide already decodes a large image at about the view's size, and
@@ -391,9 +419,20 @@ class FastImageViewWithUrl extends AppCompatImageView {
391
419
  // pixelated, and the blur. A blurred animated image shows its first frame,
392
420
  // as on iOS.
393
421
  private RequestOptions renderingOptions(Object model) {
394
- FastImageBlur blur = mBlurRadius > 0 ? new FastImageBlur(mBlurRadius, getScaleType()) : null;
422
+ FastImageBlur blur = mBlurRadius > 0 ? new FastImageBlur(mBlurRadius, mRepeat ? null : getScaleType()) : null;
395
423
  RequestOptions options;
396
- if (mPixelated) {
424
+ if (mRepeat) {
425
+ // Decoded at its own size, or scaled down to fit the view if it's
426
+ // larger (not cropped or fitted for the scale type), and a GIF as
427
+ // its first frame (tiled by setImageDrawable).
428
+ options = new RequestOptions()
429
+ .downsample(new FastImageSourceSize.Capture(
430
+ mPixelated ? DownsampleStrategy.NONE : DownsampleStrategy.CENTER_INSIDE,
431
+ String.valueOf(model)))
432
+ .dontTransform()
433
+ .dontAnimate();
434
+ if (blur != null) options = options.optionalTransform(blur);
435
+ } else if (mPixelated) {
397
436
  // Decoded as it is, then scaled by the view without filtering (see
398
437
  // OwnGifTarget).
399
438
  options = new RequestOptions()
@@ -3,6 +3,7 @@ export declare const resizeMode: {
3
3
  readonly cover: 'cover';
4
4
  readonly stretch: 'stretch';
5
5
  readonly center: 'center';
6
+ readonly repeat: 'repeat';
6
7
  };
7
8
  export declare const priority: {
8
9
  readonly low: 'low';
package/dist/index.cjs.js CHANGED
@@ -32,7 +32,8 @@ const resizeMode = {
32
32
  contain: "contain",
33
33
  cover: "cover",
34
34
  stretch: "stretch",
35
- center: "center"
35
+ center: "center",
36
+ repeat: "repeat"
36
37
  };
37
38
  const priority = {
38
39
  low: "low",
@@ -33,6 +33,7 @@ export type ResizeMode = $ReadOnly<{|
33
33
  cover: 'cover',
34
34
  stretch: 'stretch',
35
35
  center: 'center',
36
+ repeat: 'repeat',
36
37
  |}>
37
38
 
38
39
  export type Priority = $ReadOnly<{|
@@ -32,7 +32,8 @@ const resizeMode = {
32
32
  contain: "contain",
33
33
  cover: "cover",
34
34
  stretch: "stretch",
35
- center: "center"
35
+ center: "center",
36
+ repeat: "repeat"
36
37
  };
37
38
  const priority = {
38
39
  low: "low",
@@ -33,6 +33,7 @@ export type ResizeMode = $ReadOnly<{|
33
33
  cover: 'cover',
34
34
  stretch: 'stretch',
35
35
  center: 'center',
36
+ repeat: 'repeat',
36
37
  |}>
37
38
 
38
39
  export type Priority = $ReadOnly<{|
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ import React from 'react';
2
2
  import { LayoutChangeEvent, StyleProp, ViewStyle, ImageRequireSource, AccessibilityProps, ViewProps } from 'react-native';
3
3
  import { cacheControl, priority, resizeMode } from './constants';
4
4
  type ColorValue = NonNullable<ViewStyle['backgroundColor']>;
5
- export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center';
5
+ export type ResizeMode = 'contain' | 'cover' | 'stretch' | 'center' | 'repeat';
6
6
  export type Priority = 'low' | 'normal' | 'high';
7
7
  export type Cache = 'immutable' | 'web' | 'cacheOnly';
8
8
  export type Source = {
package/dist/index.js CHANGED
@@ -5,7 +5,8 @@ const resizeMode = {
5
5
  contain: "contain",
6
6
  cover: "cover",
7
7
  stretch: "stretch",
8
- center: "center"
8
+ center: "center",
9
+ repeat: "repeat"
9
10
  };
10
11
  const priority = {
11
12
  low: "low",
@@ -33,6 +33,7 @@ export type ResizeMode = $ReadOnly<{|
33
33
  cover: 'cover',
34
34
  stretch: 'stretch',
35
35
  center: 'center',
36
+ repeat: 'repeat',
36
37
  |}>
37
38
 
38
39
  export type Priority = $ReadOnly<{|
package/dist/index.web.js CHANGED
@@ -5,7 +5,8 @@ const resizeMode = {
5
5
  contain: "contain",
6
6
  cover: "cover",
7
7
  stretch: "stretch",
8
- center: "center"
8
+ center: "center",
9
+ repeat: "repeat"
9
10
  };
10
11
  const priority = {
11
12
  low: "low",
@@ -33,6 +33,7 @@ export type ResizeMode = $ReadOnly<{|
33
33
  cover: 'cover',
34
34
  stretch: 'stretch',
35
35
  center: 'center',
36
+ repeat: 'repeat',
36
37
  |}>
37
38
 
38
39
  export type Priority = $ReadOnly<{|
@@ -24,6 +24,8 @@
24
24
  @property(nonatomic, assign) NSUInteger imageCount;
25
25
  // The view's size when the image showing was blurred.
26
26
  @property(nonatomic, assign) CGSize blurredForSize;
27
+ // The view's size when the image showing was tiled (resizeMode repeat).
28
+ @property(nonatomic, assign) CGSize tiledForSize;
27
29
  // The blurred image showing (untinted), so a tint change can re-show it.
28
30
  @property(nonatomic, strong) UIImage* blurredImage;
29
31
  // The image being blurred fades in once it's blurred (transition).
@@ -211,11 +213,14 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
211
213
 
212
214
  - (void) setResizeMode: (RCTResizeMode)resizeMode {
213
215
  if (_resizeMode != resizeMode) {
216
+ BOOL repeated = _resizeMode == RCTResizeModeRepeat;
214
217
  _resizeMode = resizeMode;
215
218
  [self updateContentMode];
216
- // The blur is made for the size the image is shown at.
217
- if ([self blurs: self.originalImage]) {
218
- [self setImage: self.originalImage];
219
+ // The blur is made for the size the image is shown at, and repeat
220
+ // shows the image tiled: show it again.
221
+ UIImage* image = self.originalImage ?: super.image;
222
+ if ([self blurs: self.originalImage] || ((repeated || resizeMode == RCTResizeModeRepeat) && image)) {
223
+ [self setImage: image];
219
224
  }
220
225
  }
221
226
  }
@@ -227,7 +232,10 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
227
232
  // view's size.
228
233
  - (void) updateContentMode {
229
234
  UIViewContentMode contentMode = (UIViewContentMode) _resizeMode;
230
- if (_resizeMode == RCTResizeModeCenter) {
235
+ if (_resizeMode == RCTResizeModeRepeat) {
236
+ // The tiled image (see tiledImage:) fills the view.
237
+ contentMode = UIViewContentModeScaleToFill;
238
+ } else if (_resizeMode == RCTResizeModeCenter) {
231
239
  CGSize imageSize = super.image.size;
232
240
  CGSize viewSize = self.bounds.size;
233
241
  if (imageSize.width > viewSize.width || imageSize.height > viewSize.height) {
@@ -243,6 +251,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
243
251
  [super layoutSubviews];
244
252
  [self updateContentMode];
245
253
  [self blurAgainIfResized];
254
+ [self tileAgainIfResized];
246
255
  if (self.waitsForSize) {
247
256
  if ([self hasSize]) {
248
257
  [self reloadImage];
@@ -320,7 +329,8 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
320
329
  [self blurImage: image];
321
330
  return;
322
331
  }
323
- self.originalImage = image && self.imageColor ? image : nil;
332
+ // Kept to tint, or tile (resizeMode repeat), it again.
333
+ self.originalImage = image && (self.imageColor || _resizeMode == RCTResizeModeRepeat) ? image : nil;
324
334
  self.blurredImage = nil;
325
335
  self.blursOverNothing = NO;
326
336
  // The blur was removed before it was ready: the fade it was waiting for
@@ -342,7 +352,9 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
342
352
  // template is), or, before SDWebImage 5.20, the first frame shows.
343
353
  - (void) showImage: (UIImage*)image {
344
354
  UIColor* tint = image ? self.imageColor : nil;
345
- BOOL animated = [image conformsToProtocol: @protocol(SDAnimatedImage)] && [(id<SDAnimatedImage>)image animatedImageFrameCount] > 1;
355
+ // resizeMode repeat tiles an animated image's first frame (tiledImage:).
356
+ BOOL repeats = image && _resizeMode == RCTResizeModeRepeat;
357
+ BOOL animated = !repeats && [image conformsToProtocol: @protocol(SDAnimatedImage)] && [(id<SDAnimatedImage>)image animatedImageFrameCount] > 1;
346
358
  self.tintColor = tint;
347
359
  if ([self tintsFrames]) {
348
360
  [self setValue: tint && animated ? [SDImageTintTransformer transformerWithColor: tint] : nil forKey: @"animationTransformer"];
@@ -356,17 +368,54 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
356
368
  if (changed) {
357
369
  [self.player seekToFrameAtIndex: 0 loopCount: 0];
358
370
  }
359
- } else if (tint) {
360
- if (animated) {
361
- image = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: image.imageOrientation];
362
- }
363
- super.image = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
364
371
  } else {
372
+ if (tint) {
373
+ if (animated) {
374
+ image = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: image.imageOrientation];
375
+ }
376
+ image = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
377
+ }
378
+ if (repeats) {
379
+ image = [self tiledImage: image];
380
+ self.tiledForSize = self.bounds.size;
381
+ }
365
382
  super.image = image;
366
383
  }
367
384
  [self updateContentMode];
368
385
  }
369
386
 
387
+ // resizeMode repeat: the image repeated from the top-left at its own size in
388
+ // pixels (a scaled image, e.g. a bundled @3x one, at its size in points), an
389
+ // animated image's first frame, scaled down to fit the view if it's larger,
390
+ // as React Native's Image does (and Android here).
391
+ - (UIImage*) tiledImage: (UIImage*)image {
392
+ CGImageRef cgImage = image.CGImage;
393
+ if (!cgImage) {
394
+ return image;
395
+ }
396
+ CGFloat scale = image.scale > 1 ? image.scale : (self.window.screen.scale ?: [UIScreen mainScreen].scale);
397
+ CGSize size = CGSizeMake(image.size.width * image.scale / scale, image.size.height * image.scale / scale);
398
+ CGSize view = self.bounds.size;
399
+ if (view.width > 0 && view.height > 0 && (size.width > view.width || size.height > view.height)) {
400
+ scale *= MAX(size.width / view.width, size.height / view.height);
401
+ }
402
+ UIImage* still = [[UIImage imageWithCGImage: cgImage scale: scale orientation: image.imageOrientation]
403
+ imageWithRenderingMode: image.renderingMode];
404
+ return [still resizableImageWithCapInsets: UIEdgeInsetsZero resizingMode: UIImageResizingModeTile];
405
+ }
406
+
407
+ // The view's size changed since the image was tiled, which scales an image
408
+ // larger than the view down to fit it: tile it again.
409
+ - (void) tileAgainIfResized {
410
+ if (_resizeMode != RCTResizeModeRepeat || CGSizeEqualToSize(self.bounds.size, self.tiledForSize)) {
411
+ return;
412
+ }
413
+ UIImage* image = [self blurs: self.originalImage] ? self.blurredImage : self.originalImage;
414
+ if (image) {
415
+ [self showImage: image];
416
+ }
417
+ }
418
+
370
419
  - (void) setBlurRadius: (CGFloat)blurRadius {
371
420
  if (_blurRadius == blurRadius) {
372
421
  return;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-fast-image",
3
- "version": "8.24.0",
3
+ "version": "8.25.0",
4
4
  "description": "🚩 FastImage, performant React Native image component.",
5
5
  "keywords": [
6
6
  "cache",