react-native-fast-image 8.25.0 → 8.27.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 +49 -1
- package/android/build.gradle +3 -0
- package/android/consumer-rules.pro +5 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +6 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +5 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +17 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvg.java +161 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSvgRenderer.java +86 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +6 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +112 -3
- package/dist/index.cjs.js +20 -5
- package/dist/index.cjs.js.flow +4 -1
- package/dist/index.cjs.web.js +78 -3
- package/dist/index.cjs.web.js.flow +4 -1
- package/dist/index.d.ts +14 -1
- package/dist/index.js +20 -5
- package/dist/index.js.flow +4 -1
- package/dist/index.web.js +79 -4
- package/dist/index.web.js.flow +4 -1
- package/ios/FastImage/FFFastImageSource.h +7 -0
- package/ios/FastImage/FFFastImageSource.m +46 -0
- package/ios/FastImage/FFFastImageView.h +3 -0
- package/ios/FastImage/FFFastImageView.m +111 -12
- package/ios/FastImage/FFFastImageViewManager.m +3 -2
- package/ios/FastImage/RCTConvert+FFFastImage.h +1 -0
- package/ios/FastImage/RCTConvert+FFFastImage.m +2 -0
- package/package.json +1 -1
|
@@ -41,8 +41,17 @@
|
|
|
41
41
|
// Whether the view shows an image that loaded (not defaultSource or nothing).
|
|
42
42
|
// A new source then keeps it until the new image has loaded (see reloadImage).
|
|
43
43
|
@property(nonatomic, assign) BOOL showsLoadedImage;
|
|
44
|
-
// downsample: a load waits for the view's size (see
|
|
44
|
+
// downsample, or several sources: a load waits for the view's size (see
|
|
45
|
+
// didSetProps).
|
|
45
46
|
@property(nonatomic, assign) BOOL waitsForSize;
|
|
47
|
+
// The `source` prop. With several `sources`, _source is the one picked for
|
|
48
|
+
// the view's size instead.
|
|
49
|
+
@property(nonatomic, strong) FFFastImageSource* propSource;
|
|
50
|
+
// Several sources: the next load switches to another one for a new view
|
|
51
|
+
// size, and the one after it is loading. It's the same picture at another
|
|
52
|
+
// size, so it doesn't fade in.
|
|
53
|
+
@property(nonatomic, assign) BOOL switchesSource;
|
|
54
|
+
@property(nonatomic, assign) BOOL loadsSwitchedSource;
|
|
46
55
|
// The size (in pixels) the image showing or loading was decoded for, and
|
|
47
56
|
// whether it covers it; zero for a full-size image.
|
|
48
57
|
@property(nonatomic, assign) CGSize decodedBox;
|
|
@@ -132,7 +141,8 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
132
141
|
// the default), and from the disk cache too with 'all'. Downloads, local
|
|
133
142
|
// files and bundled images fade.
|
|
134
143
|
- (BOOL) fadesImageFromCache: (SDImageCacheType)cacheType {
|
|
135
|
-
if (self.transitionDuration <= 0 ||
|
|
144
|
+
if (self.transitionDuration <= 0 || self.loadsSwitchedSource ||
|
|
145
|
+
([self displaysLoadedImage] && !self.transitionBetweenImages)) {
|
|
136
146
|
return NO;
|
|
137
147
|
}
|
|
138
148
|
NSString* skip = self.transitionSkipOnCacheHit;
|
|
@@ -256,7 +266,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
256
266
|
if ([self hasSize]) {
|
|
257
267
|
[self reloadImage];
|
|
258
268
|
}
|
|
259
|
-
} else {
|
|
269
|
+
} else if (![self switchSourceIfResized]) {
|
|
260
270
|
[self reloadIfResized];
|
|
261
271
|
}
|
|
262
272
|
}
|
|
@@ -345,6 +355,25 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
345
355
|
}
|
|
346
356
|
}
|
|
347
357
|
|
|
358
|
+
// A vector image drawn into a bitmap, with its size in points, at a scale
|
|
359
|
+
// that's sharp at the size the view shows it (or its own size before the view
|
|
360
|
+
// has one).
|
|
361
|
+
- (UIImage*) rasterizedImage: (UIImage*)image {
|
|
362
|
+
CGSize size = image.size;
|
|
363
|
+
if (size.width <= 0 || size.height <= 0) {
|
|
364
|
+
return image;
|
|
365
|
+
}
|
|
366
|
+
CGSize view = self.bounds.size;
|
|
367
|
+
CGFloat shown = MAX(1, MAX(view.width / size.width, view.height / size.height));
|
|
368
|
+
UIGraphicsImageRendererFormat* format = [UIGraphicsImageRendererFormat preferredFormat];
|
|
369
|
+
format.scale = (self.window.screen.scale ?: [UIScreen mainScreen].scale) * shown;
|
|
370
|
+
format.opaque = NO;
|
|
371
|
+
UIGraphicsImageRenderer* renderer = [[UIGraphicsImageRenderer alloc] initWithSize: size format: format];
|
|
372
|
+
return [renderer imageWithActions: ^(UIGraphicsImageRendererContext* context) {
|
|
373
|
+
[image drawInRect: CGRectMake(0, 0, size.width, size.height)];
|
|
374
|
+
}];
|
|
375
|
+
}
|
|
376
|
+
|
|
348
377
|
// Shows the image, tinted as React Native's Image does it: UIKit draws a
|
|
349
378
|
// template image in the view's tintColor, so no tinted copy of the image is
|
|
350
379
|
// made. SDAnimatedImageView draws the frames of an animated image itself,
|
|
@@ -372,6 +401,11 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
372
401
|
if (tint) {
|
|
373
402
|
if (animated) {
|
|
374
403
|
image = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: image.imageOrientation];
|
|
404
|
+
} else if (!image.CGImage && !repeats) {
|
|
405
|
+
// A vector image (an SVG), which UIKit doesn't draw as a
|
|
406
|
+
// template: drawn into a bitmap at the size it's shown at
|
|
407
|
+
// (tiledImage: draws one for repeat).
|
|
408
|
+
image = [self rasterizedImage: image];
|
|
375
409
|
}
|
|
376
410
|
image = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
|
|
377
411
|
}
|
|
@@ -389,6 +423,18 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
389
423
|
// animated image's first frame, scaled down to fit the view if it's larger,
|
|
390
424
|
// as React Native's Image does (and Android here).
|
|
391
425
|
- (UIImage*) tiledImage: (UIImage*)image {
|
|
426
|
+
if (!image.CGImage && image.size.width > 0 && image.size.height > 0) {
|
|
427
|
+
// A vector image (an SVG): drawn at its own size in pixels (its width
|
|
428
|
+
// and height), as Android tiles it and as any other image's size is.
|
|
429
|
+
UIImage* vector = image;
|
|
430
|
+
UIGraphicsImageRendererFormat* format = [UIGraphicsImageRendererFormat preferredFormat];
|
|
431
|
+
format.scale = 1;
|
|
432
|
+
format.opaque = NO;
|
|
433
|
+
UIGraphicsImageRenderer* renderer = [[UIGraphicsImageRenderer alloc] initWithSize: vector.size format: format];
|
|
434
|
+
image = [[renderer imageWithActions: ^(UIGraphicsImageRendererContext* context) {
|
|
435
|
+
[vector drawInRect: CGRectMake(0, 0, vector.size.width, vector.size.height)];
|
|
436
|
+
}] imageWithRenderingMode: vector.renderingMode];
|
|
437
|
+
}
|
|
392
438
|
CGImageRef cgImage = image.CGImage;
|
|
393
439
|
if (!cgImage) {
|
|
394
440
|
return image;
|
|
@@ -643,12 +689,59 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
643
689
|
}
|
|
644
690
|
|
|
645
691
|
- (void) setSource: (FFFastImageSource*)source {
|
|
646
|
-
if (
|
|
647
|
-
|
|
692
|
+
if (self.propSource != source) {
|
|
693
|
+
self.propSource = source;
|
|
694
|
+
if (![self picksSource]) {
|
|
695
|
+
_source = source;
|
|
696
|
+
}
|
|
648
697
|
_needsReload = YES;
|
|
649
698
|
}
|
|
650
699
|
}
|
|
651
700
|
|
|
701
|
+
- (void) setSources: (NSArray<FFFastImageSource*>*)sources {
|
|
702
|
+
if (_sources != sources) {
|
|
703
|
+
_sources = [sources copy];
|
|
704
|
+
if (![self picksSource]) {
|
|
705
|
+
_source = self.propSource;
|
|
706
|
+
}
|
|
707
|
+
_needsReload = YES;
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Whether the view picks one of several sources for its size.
|
|
712
|
+
- (BOOL) picksSource {
|
|
713
|
+
return _sources.count > 1;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// Of several sources, the one whose size in pixels is closest to the view's
|
|
717
|
+
// (by pixel count), or the largest while the view has no size (e.g. one sized
|
|
718
|
+
// from onLoad).
|
|
719
|
+
- (FFFastImageSource*) sourceForSize {
|
|
720
|
+
CGFloat scale = self.window.screen.scale ?: [UIScreen mainScreen].scale;
|
|
721
|
+
CGFloat viewPixels = self.bounds.size.width * self.bounds.size.height * scale * scale;
|
|
722
|
+
FFFastImageSource* best = nil;
|
|
723
|
+
CGFloat bestFit = CGFLOAT_MAX;
|
|
724
|
+
for (FFFastImageSource* source in _sources) {
|
|
725
|
+
CGFloat fit = viewPixels > 0 ? ABS(1 - source.pixelCount / viewPixels) : -source.pixelCount;
|
|
726
|
+
if (!best || fit < bestFit) {
|
|
727
|
+
best = source;
|
|
728
|
+
bestFit = fit;
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
return best;
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
// Several sources: another one fits the view's new size better. Loads it,
|
|
735
|
+
// keeping the image showing until then, without a fade (see switchesSource).
|
|
736
|
+
- (BOOL) switchSourceIfResized {
|
|
737
|
+
if (![self picksSource] || _needsReload || ![self hasSize] || [self sourceForSize] == _source) {
|
|
738
|
+
return NO;
|
|
739
|
+
}
|
|
740
|
+
self.switchesSource = YES;
|
|
741
|
+
[self reloadImage];
|
|
742
|
+
return YES;
|
|
743
|
+
}
|
|
744
|
+
|
|
652
745
|
- (void) setRecyclingKey: (NSString*)recyclingKey {
|
|
653
746
|
if (_recyclingKey == recyclingKey || [_recyclingKey isEqualToString: recyclingKey]) {
|
|
654
747
|
return;
|
|
@@ -674,12 +767,13 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
674
767
|
|
|
675
768
|
- (void) didSetProps: (NSArray<NSString*>*)changedProps {
|
|
676
769
|
if (_needsReload) {
|
|
677
|
-
// With downsample on, the image is decoded for the view's size,
|
|
678
|
-
//
|
|
679
|
-
//
|
|
680
|
-
//
|
|
681
|
-
//
|
|
682
|
-
|
|
770
|
+
// With downsample on, the image is decoded for the view's size, and
|
|
771
|
+
// with several sources one is picked for it, so a view that hasn't
|
|
772
|
+
// been laid out yet loads once it has. Props and layout are applied
|
|
773
|
+
// in the same update, so that's before the next frame (in
|
|
774
|
+
// layoutSubviews). A view that still has no size then (e.g. one sized
|
|
775
|
+
// from onLoad) loads at full size, or the largest source.
|
|
776
|
+
if (([self downsamples] || [self picksSource]) && ![self hasSize]) {
|
|
683
777
|
if (!self.waitsForSize) {
|
|
684
778
|
self.waitsForSize = YES;
|
|
685
779
|
__weak typeof(self) weakSelf = self;
|
|
@@ -766,6 +860,11 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
766
860
|
- (void) reloadImage {
|
|
767
861
|
_needsReload = NO;
|
|
768
862
|
self.waitsForSize = NO;
|
|
863
|
+
self.loadsSwitchedSource = self.switchesSource;
|
|
864
|
+
self.switchesSource = NO;
|
|
865
|
+
if ([self picksSource]) {
|
|
866
|
+
_source = [self sourceForSize];
|
|
867
|
+
}
|
|
769
868
|
self.decodedBox = CGSizeZero;
|
|
770
869
|
// The previous load, if it's still running, is for a source the view no
|
|
771
870
|
// longer shows: cancel it (a new download would, but a data uri or no
|
|
@@ -1002,7 +1101,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1002
1101
|
if (error) {
|
|
1003
1102
|
// SDWebImage shows the placeholder (defaultSource or nothing).
|
|
1004
1103
|
weakSelf.showsLoadedImage = NO;
|
|
1005
|
-
[weakSelf sendOnError:
|
|
1104
|
+
[weakSelf sendOnError: [source errorMessage: error]];
|
|
1006
1105
|
if (weakSelf.onFastImageLoadEnd) {
|
|
1007
1106
|
weakSelf.onFastImageLoadEnd([weakSelf loadEndEvent: NO]);
|
|
1008
1107
|
}
|
|
@@ -101,6 +101,7 @@ RCT_EXPORT_MODULE(FastImageView)
|
|
|
101
101
|
}
|
|
102
102
|
|
|
103
103
|
RCT_EXPORT_VIEW_PROPERTY(source, FFFastImageSource)
|
|
104
|
+
RCT_EXPORT_VIEW_PROPERTY(sources, FFFastImageSourceArray)
|
|
104
105
|
RCT_EXPORT_VIEW_PROPERTY(defaultSource, UIImage)
|
|
105
106
|
RCT_EXPORT_VIEW_PROPERTY(resizeMode, RCTResizeMode)
|
|
106
107
|
RCT_EXPORT_VIEW_PROPERTY(recyclingKey, NSString)
|
|
@@ -292,7 +293,7 @@ RCT_EXPORT_METHOD(preload:(nonnull NSArray<FFFastImageSource *> *)sources
|
|
|
292
293
|
FFFQueueLoad(source, options, [context copy], ^(UIImage *image, NSError *error) {
|
|
293
294
|
results[idx] = image
|
|
294
295
|
? @{@"ok": @YES, @"width": @(image.size.width), @"height": @(image.size.height)}
|
|
295
|
-
: FFFFailure(
|
|
296
|
+
: FFFFailure([source errorMessage:error]);
|
|
296
297
|
finishOne();
|
|
297
298
|
});
|
|
298
299
|
}];
|
|
@@ -350,7 +351,7 @@ RCT_EXPORT_METHOD(getCachePath:(FFFastImageSource *)source
|
|
|
350
351
|
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
|
|
351
352
|
FFFQueueLoad(source, options, [context copy], ^(UIImage *image, NSError *error) {
|
|
352
353
|
if (!image) {
|
|
353
|
-
resolve(FFFFailure(
|
|
354
|
+
resolve(FFFFailure([source errorMessage:error]));
|
|
354
355
|
return;
|
|
355
356
|
}
|
|
356
357
|
FFFFindCachedFile(key, resolveFile, ^{
|
|
@@ -51,6 +51,8 @@ RCT_ENUM_CONVERTER(FFFCacheControl, (@{
|
|
|
51
51
|
imageSource.cacheKey = cacheKey.length > 0 ? cacheKey : nil;
|
|
52
52
|
id memoryCache = json[@"memoryCache"];
|
|
53
53
|
imageSource.memoryCache = memoryCache && memoryCache != (id)kCFNull ? [self BOOL:memoryCache] : YES;
|
|
54
|
+
CGFloat scale = json[@"scale"] && json[@"scale"] != (id)kCFNull ? [self CGFloat:json[@"scale"]] : 1;
|
|
55
|
+
imageSource.pixelCount = [self CGFloat:json[@"width"]] * [self CGFloat:json[@"height"]] * scale * scale;
|
|
54
56
|
|
|
55
57
|
return imageSource;
|
|
56
58
|
}
|