react-native-fast-image 9.3.7 → 10.1.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 +97 -61
- package/RNFastImage.podspec +14 -9
- package/android/build.gradle +21 -47
- package/android/consumer-rules.pro +7 -7
- package/android/src/main/AndroidManifest.xml +1 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimatable.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageAnimated.java +2 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageApng.java +2 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageBlur.java +4 -6
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheControl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheLimits.java +4 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageCacheWrite.java +1 -1
- package/android/src/main/java/com/fastimage/FastImageCrop.java +92 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageEvent.java +2 -3
- package/android/src/main/java/com/fastimage/FastImageEvents.java +74 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGif.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlide.java +13 -3
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageGlideModule.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageKeyedGlideUrl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageOkHttpProgressGlideModule.java +49 -14
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageRequestListener.java +6 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSharedDownloads.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSource.java +12 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSourceSize.java +12 -2
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvg.java +3 -3
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageSvgRenderer.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageUrl.java +1 -1
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewConverter.java +24 -9
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +591 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewModule.java +50 -28
- package/android/src/main/java/com/fastimage/FastImageViewPackage.java +51 -0
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageViewWithUrl.java +352 -126
- package/android/src/main/java/com/{dylanvann/fastimage → fastimage}/FastImageWebGlideUrl.java +1 -1
- package/app.plugin.js +2 -7
- package/dist/index.cjs.js +276 -157
- package/dist/index.cjs.js.flow +34 -17
- package/dist/index.cjs.web.js +211 -61
- package/dist/index.cjs.web.js.flow +34 -17
- package/dist/index.d.ts +108 -58
- package/dist/index.js +278 -159
- package/dist/index.js.flow +34 -17
- package/dist/index.web.js +212 -62
- package/dist/index.web.js.flow +34 -17
- package/dist/objectFit.d.ts +2 -2
- package/dist/objectPosition.d.ts +42 -0
- package/dist/specs/FastImageViewNativeComponent.d.ts +48 -0
- package/dist/specs/NativeFastImageModule.d.ts +11 -0
- package/dist/warnings.d.ts +3 -0
- package/ios/FastImage/FFFDownsampledImage.h +4 -8
- package/ios/FastImage/FFFDownsampledImage.m +0 -34
- package/ios/FastImage/FFFastImageModule.h +8 -0
- package/ios/FastImage/{FFFastImageViewManager.m → FFFastImageModule.mm} +89 -59
- package/ios/FastImage/FFFastImageSource.h +4 -0
- package/ios/FastImage/FFFastImageSource.m +5 -1
- package/ios/FastImage/FFFastImageView.h +36 -7
- package/ios/FastImage/FFFastImageView.m +229 -172
- package/ios/FastImage/FFFastImageViewComponentView.h +11 -0
- package/ios/FastImage/FFFastImageViewComponentView.mm +325 -0
- package/ios/FastImage/RCTConvert+FFFastImage.h +1 -1
- package/ios/FastImage/RCTConvert+FFFastImage.m +9 -10
- package/package.json +28 -9
- package/src/constants.ts +28 -0
- package/src/index.js.flow +180 -0
- package/src/index.tsx +1128 -0
- package/src/index.web.tsx +548 -0
- package/src/objectFit.ts +48 -0
- package/src/objectPosition.ts +243 -0
- package/src/specs/FastImageViewNativeComponent.ts +83 -0
- package/src/specs/NativeFastImageModule.ts +35 -0
- package/src/warnings.ts +37 -0
- package/android/src/main/AndroidManifestNew.xml +0 -3
- package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +0 -63
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +0 -49
- package/android/src/main/java/com/dylanvann/fastimage/FastImageProgressListener.java +0 -18
- package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +0 -21
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +0 -379
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewPackage.java +0 -27
- package/ios/FastImage/FFFastImageViewManager.h +0 -5
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
|
|
10
10
|
@interface FFFastImageView ()
|
|
11
11
|
|
|
12
|
-
@property(nonatomic, assign) BOOL hasSentOnLoadStart;
|
|
13
12
|
@property(nonatomic, assign) BOOL hasCompleted;
|
|
14
13
|
@property(nonatomic, assign) BOOL hasErrored;
|
|
15
14
|
// Whether the latest change of props requires the image to be reloaded
|
|
@@ -24,8 +23,6 @@
|
|
|
24
23
|
@property(nonatomic, assign) NSUInteger imageCount;
|
|
25
24
|
// The view's size when the image showing was blurred.
|
|
26
25
|
@property(nonatomic, assign) CGSize blurredForSize;
|
|
27
|
-
// The view's size when the image showing was tiled (resizeMode repeat).
|
|
28
|
-
@property(nonatomic, assign) CGSize tiledForSize;
|
|
29
26
|
// The blurred image showing (untinted), so a tint change can re-show it.
|
|
30
27
|
@property(nonatomic, strong) UIImage* blurredImage;
|
|
31
28
|
// The image being blurred fades in once it's blurred (transition).
|
|
@@ -58,6 +55,13 @@
|
|
|
58
55
|
@property(nonatomic, assign) BOOL decodedCover;
|
|
59
56
|
// Counts loads, so a quiet reload's completion can tell if it's still current.
|
|
60
57
|
@property(nonatomic, assign) NSUInteger loadCount;
|
|
58
|
+
// The frame the view was laid out at (the box the image fits in), and how far
|
|
59
|
+
// objectPosition moves the view from it (see updatePosition).
|
|
60
|
+
@property(nonatomic, assign) CGRect box;
|
|
61
|
+
@property(nonatomic, assign) CGPoint positionOffset;
|
|
62
|
+
// The view's own mask, which clips it to the box while it's moved, when React
|
|
63
|
+
// Native gives it none (the view doesn't clip: overflow visible).
|
|
64
|
+
@property(nonatomic, strong) CALayer* ownMask;
|
|
61
65
|
|
|
62
66
|
@end
|
|
63
67
|
|
|
@@ -72,6 +76,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
72
76
|
if (self != [FFFastImageView class]) {
|
|
73
77
|
return;
|
|
74
78
|
}
|
|
79
|
+
FFFApplyCacheLimits();
|
|
75
80
|
[[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidEnterBackgroundNotification
|
|
76
81
|
object: nil
|
|
77
82
|
queue: [NSOperationQueue mainQueue]
|
|
@@ -82,8 +87,11 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
82
87
|
|
|
83
88
|
- (id) init {
|
|
84
89
|
self = [super init];
|
|
85
|
-
|
|
90
|
+
// objectFit cover (FFFObjectFitCover, 0).
|
|
91
|
+
self.contentMode = UIViewContentModeScaleAspectFill;
|
|
86
92
|
self.clipsToBounds = YES;
|
|
93
|
+
_objectPositionX = 0.5;
|
|
94
|
+
_objectPositionY = 0.5;
|
|
87
95
|
// The tint (tintColor) stays as it is while an alert or sheet is shown,
|
|
88
96
|
// rather than dimming like the system's controls.
|
|
89
97
|
self.tintAdjustmentMode = UIViewTintAdjustmentModeNormal;
|
|
@@ -201,10 +209,18 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
201
209
|
[self.layer addAnimation: fadeIn forKey: @"FFFFadeIn"];
|
|
202
210
|
return;
|
|
203
211
|
}
|
|
204
|
-
|
|
212
|
+
// On the view it's in, which doesn't move, with the change itself not
|
|
213
|
+
// animated: objectPosition moves this view for the new image
|
|
214
|
+
// (updatePosition), which in a transition of this view was animated while
|
|
215
|
+
// its mask moved at once. The old image stays where it was while the new
|
|
216
|
+
// one fades in where it goes.
|
|
217
|
+
UIView* container = self.superview ?: self;
|
|
218
|
+
[UIView transitionWithView: container
|
|
205
219
|
duration: self.transitionDuration / 1000
|
|
206
220
|
options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction
|
|
207
|
-
animations:
|
|
221
|
+
animations: ^{
|
|
222
|
+
[UIView performWithoutAnimation: change];
|
|
223
|
+
}
|
|
208
224
|
completion: nil];
|
|
209
225
|
}
|
|
210
226
|
|
|
@@ -224,50 +240,157 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
224
240
|
}
|
|
225
241
|
}
|
|
226
242
|
|
|
227
|
-
- (void)
|
|
228
|
-
if (
|
|
229
|
-
|
|
230
|
-
_resizeMode = resizeMode;
|
|
243
|
+
- (void) setObjectFit: (FFFObjectFit)objectFit {
|
|
244
|
+
if (_objectFit != objectFit) {
|
|
245
|
+
_objectFit = objectFit;
|
|
231
246
|
[self updateContentMode];
|
|
232
|
-
// The blur is made for the size the image is shown at
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if ([self blurs: self.originalImage] || ((repeated || resizeMode == RCTResizeModeRepeat) && image)) {
|
|
236
|
-
[self setImage: image];
|
|
247
|
+
// The blur is made for the size the image is shown at: show it again.
|
|
248
|
+
if ([self blurs: self.originalImage]) {
|
|
249
|
+
[self setImage: self.originalImage];
|
|
237
250
|
}
|
|
238
251
|
}
|
|
239
252
|
}
|
|
240
253
|
|
|
241
|
-
// The content mode for
|
|
242
|
-
// view down to fit,
|
|
243
|
-
//
|
|
244
|
-
//
|
|
245
|
-
//
|
|
246
|
-
// size (cropped by the view).
|
|
254
|
+
// The content mode for objectFit. `scale-down` scales an image larger than
|
|
255
|
+
// the view down to fit, and shows a smaller one at its own size
|
|
256
|
+
// (UIViewContentModeCenter alone would show large images at full size,
|
|
257
|
+
// cropped), so it depends on the image and the view's size. `none` is the
|
|
258
|
+
// image at its own size, centered, whatever its size (cropped by the view).
|
|
247
259
|
- (void) updateContentMode {
|
|
248
|
-
UIViewContentMode contentMode
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
} else if (_resizeMode == RCTResizeModeRepeat) {
|
|
252
|
-
// The tiled image (see tiledImage:) fills the view.
|
|
253
|
-
contentMode = UIViewContentModeScaleToFill;
|
|
254
|
-
} else if (_resizeMode == RCTResizeModeCenter) {
|
|
255
|
-
CGSize imageSize = super.image.size;
|
|
256
|
-
CGSize viewSize = self.bounds.size;
|
|
257
|
-
if (imageSize.width > viewSize.width || imageSize.height > viewSize.height) {
|
|
260
|
+
UIViewContentMode contentMode;
|
|
261
|
+
switch (_objectFit) {
|
|
262
|
+
case FFFObjectFitContain:
|
|
258
263
|
contentMode = UIViewContentModeScaleAspectFit;
|
|
264
|
+
break;
|
|
265
|
+
case FFFObjectFitFill:
|
|
266
|
+
contentMode = UIViewContentModeScaleToFill;
|
|
267
|
+
break;
|
|
268
|
+
case FFFObjectFitNone:
|
|
269
|
+
contentMode = UIViewContentModeCenter;
|
|
270
|
+
break;
|
|
271
|
+
case FFFObjectFitScaleDown: {
|
|
272
|
+
CGSize imageSize = super.image.size;
|
|
273
|
+
CGSize viewSize = self.bounds.size;
|
|
274
|
+
contentMode = imageSize.width > viewSize.width || imageSize.height > viewSize.height
|
|
275
|
+
? UIViewContentModeScaleAspectFit
|
|
276
|
+
: UIViewContentModeCenter;
|
|
277
|
+
break;
|
|
259
278
|
}
|
|
279
|
+
case FFFObjectFitCover:
|
|
280
|
+
default:
|
|
281
|
+
contentMode = UIViewContentModeScaleAspectFill;
|
|
282
|
+
break;
|
|
260
283
|
}
|
|
261
284
|
if (self.contentMode != contentMode) {
|
|
262
285
|
self.contentMode = contentMode;
|
|
263
286
|
}
|
|
287
|
+
[self updatePosition];
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
- (void) setObjectPositionX: (CGFloat)objectPositionX {
|
|
291
|
+
_objectPositionX = objectPositionX;
|
|
292
|
+
[self updatePosition];
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
- (void) setObjectPositionXOffset: (CGFloat)objectPositionXOffset {
|
|
296
|
+
_objectPositionXOffset = objectPositionXOffset;
|
|
297
|
+
[self updatePosition];
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
- (void) setObjectPositionY: (CGFloat)objectPositionY {
|
|
301
|
+
_objectPositionY = objectPositionY;
|
|
302
|
+
[self updatePosition];
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
- (void) setObjectPositionYOffset: (CGFloat)objectPositionYOffset {
|
|
306
|
+
_objectPositionYOffset = objectPositionYOffset;
|
|
307
|
+
[self updatePosition];
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// The frame React Native lays the view out at: the box the image fits in. The
|
|
311
|
+
// view itself is moved from it by objectPosition (see updatePosition).
|
|
312
|
+
- (void) setFrame: (CGRect)frame {
|
|
313
|
+
self.box = frame;
|
|
314
|
+
[super setFrame: CGRectOffset(frame, self.positionOffset.x, self.positionOffset.y)];
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// objectPosition: where the image is in the view, as CSS places it. The
|
|
318
|
+
// content mode centers the image, so the view moves by the difference,
|
|
319
|
+
// keeping its size: React Native's mask for its rounded corners (made for its
|
|
320
|
+
// bounds) only has to move back (see updateMask). Not moved, as most images
|
|
321
|
+
// are, the view clips to its bounds as before.
|
|
322
|
+
- (void) updatePosition {
|
|
323
|
+
CGPoint offset = [self imageOffset];
|
|
324
|
+
if (!CGPointEqualToPoint(offset, self.positionOffset)) {
|
|
325
|
+
self.positionOffset = offset;
|
|
326
|
+
[super setFrame: CGRectOffset(self.box, offset.x, offset.y)];
|
|
327
|
+
}
|
|
328
|
+
[self updateMask];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// How far objectPosition moves the image from the center: on each axis its
|
|
332
|
+
// left or top edge is at (view - image) * fraction + offset, where the
|
|
333
|
+
// content mode centers it at (view - image) / 2. Nothing for fill, which
|
|
334
|
+
// fills the view.
|
|
335
|
+
- (CGPoint) imageOffset {
|
|
336
|
+
CGSize view = self.box.size;
|
|
337
|
+
CGSize size = super.image.size;
|
|
338
|
+
if (size.width <= 0 || size.height <= 0 || view.width <= 0 || view.height <= 0) {
|
|
339
|
+
return CGPointZero;
|
|
340
|
+
}
|
|
341
|
+
CGFloat scale;
|
|
342
|
+
switch (self.contentMode) {
|
|
343
|
+
case UIViewContentModeScaleAspectFill:
|
|
344
|
+
scale = MAX(view.width / size.width, view.height / size.height);
|
|
345
|
+
break;
|
|
346
|
+
case UIViewContentModeScaleAspectFit:
|
|
347
|
+
scale = MIN(view.width / size.width, view.height / size.height);
|
|
348
|
+
break;
|
|
349
|
+
case UIViewContentModeCenter:
|
|
350
|
+
scale = 1;
|
|
351
|
+
break;
|
|
352
|
+
default:
|
|
353
|
+
return CGPointZero;
|
|
354
|
+
}
|
|
355
|
+
CGFloat x = (view.width - size.width * scale) * (_objectPositionX - 0.5) + _objectPositionXOffset;
|
|
356
|
+
CGFloat y = (view.height - size.height * scale) * (_objectPositionY - 0.5) + _objectPositionYOffset;
|
|
357
|
+
return CGPointMake(isfinite(x) ? x : 0, isfinite(y) ? y : 0);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
- (void) updateMask {
|
|
361
|
+
CGPoint offset = self.positionOffset;
|
|
362
|
+
BOOL moved = offset.x != 0 || offset.y != 0;
|
|
363
|
+
// Moved, the view's bounds aren't the box: the mask clips it.
|
|
364
|
+
self.clipsToBounds = !moved;
|
|
365
|
+
CALayer* mask = self.layer.mask;
|
|
366
|
+
if (!moved && (!mask || CGPointEqualToPoint(mask.frame.origin, CGPointZero)) && mask != self.ownMask) {
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
[CATransaction begin];
|
|
370
|
+
[CATransaction setDisableActions: YES];
|
|
371
|
+
if (mask && mask != self.ownMask) {
|
|
372
|
+
// React Native's (the box with its rounded corners, in the view's
|
|
373
|
+
// bounds): back on the box.
|
|
374
|
+
CGRect frame = mask.frame;
|
|
375
|
+
frame.origin = CGPointMake(-offset.x, -offset.y);
|
|
376
|
+
mask.frame = frame;
|
|
377
|
+
} else if (moved) {
|
|
378
|
+
if (!self.ownMask) {
|
|
379
|
+
self.ownMask = [CALayer layer];
|
|
380
|
+
self.ownMask.backgroundColor = [UIColor blackColor].CGColor;
|
|
381
|
+
}
|
|
382
|
+
self.ownMask.frame = CGRectMake(-offset.x, -offset.y, self.box.size.width, self.box.size.height);
|
|
383
|
+
self.layer.mask = self.ownMask;
|
|
384
|
+
} else if (mask) {
|
|
385
|
+
self.layer.mask = nil;
|
|
386
|
+
}
|
|
387
|
+
[CATransaction commit];
|
|
264
388
|
}
|
|
265
389
|
|
|
266
390
|
- (void) layoutSubviews {
|
|
267
391
|
[super layoutSubviews];
|
|
268
392
|
[self updateContentMode];
|
|
269
393
|
[self blurAgainIfResized];
|
|
270
|
-
[self tileAgainIfResized];
|
|
271
394
|
if (self.waitsForSize) {
|
|
272
395
|
// An image in the memory cache shows in this frame. Others start
|
|
273
396
|
// loading from didSetProps's block, after layout, in the order the
|
|
@@ -282,40 +405,6 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
282
405
|
}
|
|
283
406
|
}
|
|
284
407
|
|
|
285
|
-
- (void) setOnFastImageLoadEnd: (RCTDirectEventBlock)onFastImageLoadEnd {
|
|
286
|
-
_onFastImageLoadEnd = onFastImageLoadEnd;
|
|
287
|
-
if (self.hasCompleted && _onFastImageLoadEnd) {
|
|
288
|
-
_onFastImageLoadEnd([self loadEndEvent: YES]);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
- (void) setOnFastImageLoad: (RCTDirectEventBlock)onFastImageLoad {
|
|
293
|
-
_onFastImageLoad = onFastImageLoad;
|
|
294
|
-
if (self.hasCompleted && _onFastImageLoad) {
|
|
295
|
-
_onFastImageLoad(self.onLoadEvent);
|
|
296
|
-
}
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
- (void) setOnFastImageError: (RCTDirectEventBlock)onFastImageError {
|
|
300
|
-
_onFastImageError = onFastImageError;
|
|
301
|
-
if (self.hasErrored && _onFastImageError) {
|
|
302
|
-
_onFastImageError(self.onErrorEvent);
|
|
303
|
-
}
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
- (void) setOnFastImageLoadStart: (RCTDirectEventBlock)onFastImageLoadStart {
|
|
307
|
-
// Send it for a load that has already started. When a reload is pending
|
|
308
|
-
// (e.g. source set in the same update), reloadImage sends it.
|
|
309
|
-
if (_source && !_needsReload && !self.hasSentOnLoadStart && onFastImageLoadStart) {
|
|
310
|
-
_onFastImageLoadStart = onFastImageLoadStart;
|
|
311
|
-
onFastImageLoadStart(@{});
|
|
312
|
-
self.hasSentOnLoadStart = YES;
|
|
313
|
-
} else {
|
|
314
|
-
_onFastImageLoadStart = onFastImageLoadStart;
|
|
315
|
-
self.hasSentOnLoadStart = NO;
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
|
|
319
408
|
- (void) setImageColor: (UIColor*)imageColor {
|
|
320
409
|
_imageColor = imageColor;
|
|
321
410
|
// A blurred image is a still image: show it with the new tint, without
|
|
@@ -336,12 +425,6 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
336
425
|
}
|
|
337
426
|
}
|
|
338
427
|
|
|
339
|
-
// Whether SDAnimatedImageView can tint the frames of an animated image as
|
|
340
|
-
// they're decoded (animationTransformer, SDWebImage 5.20+).
|
|
341
|
-
- (BOOL) tintsFrames {
|
|
342
|
-
return [self respondsToSelector: @selector(setAnimationTransformer:)];
|
|
343
|
-
}
|
|
344
|
-
|
|
345
428
|
- (void) setImage: (UIImage*)image {
|
|
346
429
|
self.imageCount++;
|
|
347
430
|
if ([self blurs: image]) {
|
|
@@ -350,8 +433,8 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
350
433
|
[self blurImage: image];
|
|
351
434
|
return;
|
|
352
435
|
}
|
|
353
|
-
// Kept to tint
|
|
354
|
-
self.originalImage = image &&
|
|
436
|
+
// Kept to tint it again.
|
|
437
|
+
self.originalImage = image && self.imageColor ? image : nil;
|
|
355
438
|
self.blurredImage = nil;
|
|
356
439
|
self.blursOverNothing = NO;
|
|
357
440
|
// The blur was removed before it was ready: the fade it was waiting for
|
|
@@ -389,17 +472,13 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
389
472
|
// template image in the view's tintColor, so no tinted copy of the image is
|
|
390
473
|
// made. SDAnimatedImageView draws the frames of an animated image itself,
|
|
391
474
|
// without the tint, so each frame is tinted as it's decoded (source-in, as the
|
|
392
|
-
// template is)
|
|
475
|
+
// template is).
|
|
393
476
|
- (void) showImage: (UIImage*)image {
|
|
394
477
|
UIColor* tint = image ? self.imageColor : nil;
|
|
395
|
-
|
|
396
|
-
BOOL repeats = image && _resizeMode == RCTResizeModeRepeat;
|
|
397
|
-
BOOL animated = !repeats && [image conformsToProtocol: @protocol(SDAnimatedImage)] && [(id<SDAnimatedImage>)image animatedImageFrameCount] > 1;
|
|
478
|
+
BOOL animated = [image conformsToProtocol: @protocol(SDAnimatedImage)] && [(id<SDAnimatedImage>)image animatedImageFrameCount] > 1;
|
|
398
479
|
self.tintColor = tint;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
}
|
|
402
|
-
if (tint && animated && [self tintsFrames]) {
|
|
480
|
+
self.animationTransformer = tint && animated ? [SDImageTintTransformer transformerWithColor: tint] : nil;
|
|
481
|
+
if (tint && animated) {
|
|
403
482
|
BOOL changed = super.image != image;
|
|
404
483
|
super.image = image;
|
|
405
484
|
// Shows the first frame tinted now, also while paused (the player
|
|
@@ -410,69 +489,18 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
410
489
|
}
|
|
411
490
|
} else {
|
|
412
491
|
if (tint) {
|
|
413
|
-
if (
|
|
414
|
-
image = [UIImage imageWithCGImage: image.CGImage scale: image.scale orientation: image.imageOrientation];
|
|
415
|
-
} else if (!image.CGImage && !repeats) {
|
|
492
|
+
if (!image.CGImage) {
|
|
416
493
|
// A vector image (an SVG), which UIKit doesn't draw as a
|
|
417
|
-
// template: drawn into a bitmap at the size it's shown at
|
|
418
|
-
// (tiledImage: draws one for repeat).
|
|
494
|
+
// template: drawn into a bitmap at the size it's shown at.
|
|
419
495
|
image = [self rasterizedImage: image];
|
|
420
496
|
}
|
|
421
497
|
image = [image imageWithRenderingMode: UIImageRenderingModeAlwaysTemplate];
|
|
422
498
|
}
|
|
423
|
-
if (repeats) {
|
|
424
|
-
image = [self tiledImage: image];
|
|
425
|
-
self.tiledForSize = self.bounds.size;
|
|
426
|
-
}
|
|
427
499
|
super.image = image;
|
|
428
500
|
}
|
|
429
501
|
[self updateContentMode];
|
|
430
502
|
}
|
|
431
503
|
|
|
432
|
-
// resizeMode repeat: the image repeated from the top-left at its own size in
|
|
433
|
-
// pixels (a scaled image, e.g. a bundled @3x one, at its size in points), an
|
|
434
|
-
// animated image's first frame, scaled down to fit the view if it's larger,
|
|
435
|
-
// as React Native's Image does (and Android here).
|
|
436
|
-
- (UIImage*) tiledImage: (UIImage*)image {
|
|
437
|
-
if (!image.CGImage && image.size.width > 0 && image.size.height > 0) {
|
|
438
|
-
// A vector image (an SVG): drawn at its own size in pixels (its width
|
|
439
|
-
// and height), as Android tiles it and as any other image's size is.
|
|
440
|
-
UIImage* vector = image;
|
|
441
|
-
UIGraphicsImageRendererFormat* format = [UIGraphicsImageRendererFormat preferredFormat];
|
|
442
|
-
format.scale = 1;
|
|
443
|
-
format.opaque = NO;
|
|
444
|
-
UIGraphicsImageRenderer* renderer = [[UIGraphicsImageRenderer alloc] initWithSize: vector.size format: format];
|
|
445
|
-
image = [[renderer imageWithActions: ^(UIGraphicsImageRendererContext* context) {
|
|
446
|
-
[vector drawInRect: CGRectMake(0, 0, vector.size.width, vector.size.height)];
|
|
447
|
-
}] imageWithRenderingMode: vector.renderingMode];
|
|
448
|
-
}
|
|
449
|
-
CGImageRef cgImage = image.CGImage;
|
|
450
|
-
if (!cgImage) {
|
|
451
|
-
return image;
|
|
452
|
-
}
|
|
453
|
-
CGFloat scale = image.scale > 1 ? image.scale : (self.window.screen.scale ?: [UIScreen mainScreen].scale);
|
|
454
|
-
CGSize size = CGSizeMake(image.size.width * image.scale / scale, image.size.height * image.scale / scale);
|
|
455
|
-
CGSize view = self.bounds.size;
|
|
456
|
-
if (view.width > 0 && view.height > 0 && (size.width > view.width || size.height > view.height)) {
|
|
457
|
-
scale *= MAX(size.width / view.width, size.height / view.height);
|
|
458
|
-
}
|
|
459
|
-
UIImage* still = [[UIImage imageWithCGImage: cgImage scale: scale orientation: image.imageOrientation]
|
|
460
|
-
imageWithRenderingMode: image.renderingMode];
|
|
461
|
-
return [still resizableImageWithCapInsets: UIEdgeInsetsZero resizingMode: UIImageResizingModeTile];
|
|
462
|
-
}
|
|
463
|
-
|
|
464
|
-
// The view's size changed since the image was tiled, which scales an image
|
|
465
|
-
// larger than the view down to fit it: tile it again.
|
|
466
|
-
- (void) tileAgainIfResized {
|
|
467
|
-
if (_resizeMode != RCTResizeModeRepeat || CGSizeEqualToSize(self.bounds.size, self.tiledForSize)) {
|
|
468
|
-
return;
|
|
469
|
-
}
|
|
470
|
-
UIImage* image = [self blurs: self.originalImage] ? self.blurredImage : self.originalImage;
|
|
471
|
-
if (image) {
|
|
472
|
-
[self showImage: image];
|
|
473
|
-
}
|
|
474
|
-
}
|
|
475
|
-
|
|
476
504
|
- (void) setBlurRadius: (CGFloat)blurRadius {
|
|
477
505
|
if (_blurRadius == blurRadius) {
|
|
478
506
|
return;
|
|
@@ -541,7 +569,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
541
569
|
}
|
|
542
570
|
|
|
543
571
|
// Points on screen per point of the image, for the view's size and
|
|
544
|
-
//
|
|
572
|
+
// objectFit, or 1 before the view has a size.
|
|
545
573
|
- (CGFloat) shownScale: (UIImage*)image {
|
|
546
574
|
CGSize size = image.size;
|
|
547
575
|
CGSize view = self.bounds.size;
|
|
@@ -550,14 +578,14 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
550
578
|
}
|
|
551
579
|
CGFloat x = view.width / size.width;
|
|
552
580
|
CGFloat y = view.height / size.height;
|
|
553
|
-
switch (
|
|
554
|
-
case
|
|
581
|
+
switch (_objectFit) {
|
|
582
|
+
case FFFObjectFitCover:
|
|
555
583
|
return MAX(x, y);
|
|
556
|
-
case
|
|
584
|
+
case FFFObjectFitContain:
|
|
557
585
|
return MIN(x, y);
|
|
558
|
-
case
|
|
586
|
+
case FFFObjectFitFill:
|
|
559
587
|
return sqrt(x * y);
|
|
560
|
-
case
|
|
588
|
+
case FFFObjectFitScaleDown:
|
|
561
589
|
return MIN(MIN(x, y), 1);
|
|
562
590
|
default:
|
|
563
591
|
return 1;
|
|
@@ -607,7 +635,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
607
635
|
uint32_t boxSize = factor == 1 ? (uint32_t) box | 1 : (uint32_t) round((box / factor - 1) / 2) * 2 + 1;
|
|
608
636
|
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
609
637
|
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace,
|
|
610
|
-
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
|
|
638
|
+
(CGBitmapInfo)kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
|
|
611
639
|
CGColorSpaceRelease(colorSpace);
|
|
612
640
|
if (!context) {
|
|
613
641
|
return image;
|
|
@@ -765,6 +793,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
765
793
|
// source is the same, nor a fade from it.
|
|
766
794
|
self.showsLoadedImage = NO;
|
|
767
795
|
[self.layer removeAllAnimations];
|
|
796
|
+
[self.superview.layer removeAnimationForKey: kCATransition];
|
|
768
797
|
_needsReload = YES;
|
|
769
798
|
}
|
|
770
799
|
}
|
|
@@ -776,7 +805,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
776
805
|
}
|
|
777
806
|
}
|
|
778
807
|
|
|
779
|
-
- (void) didSetProps
|
|
808
|
+
- (void) didSetProps {
|
|
780
809
|
if (_needsReload) {
|
|
781
810
|
// With downsample on, the image is decoded for the view's size, and
|
|
782
811
|
// with several sources one is picked for it, so a view that hasn't
|
|
@@ -803,16 +832,15 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
803
832
|
}
|
|
804
833
|
}
|
|
805
834
|
|
|
806
|
-
// Whether images are decoded at about the view's size (downsample,
|
|
807
|
-
//
|
|
808
|
-
//
|
|
809
|
-
//
|
|
810
|
-
// `none`, which shows it at its own size.
|
|
835
|
+
// Whether images are decoded at about the view's size (downsample, and always
|
|
836
|
+
// for photo library images, which are large and usually shown small: Photos
|
|
837
|
+
// makes them at the size asked for). Not for `none`, which shows it at its own
|
|
838
|
+
// size.
|
|
811
839
|
- (BOOL) downsamples {
|
|
812
|
-
if (
|
|
840
|
+
if (_objectFit == FFFObjectFitNone) {
|
|
813
841
|
return NO;
|
|
814
842
|
}
|
|
815
|
-
return [_source isPhotoLibrary] ||
|
|
843
|
+
return [_source isPhotoLibrary] || _downsample;
|
|
816
844
|
}
|
|
817
845
|
|
|
818
846
|
// Whether the view has been laid out with an area. One that's 0 wide or tall
|
|
@@ -833,7 +861,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
833
861
|
|
|
834
862
|
// Whether the image has to cover the box, rather than fit in it.
|
|
835
863
|
- (BOOL) decodeCovers {
|
|
836
|
-
return
|
|
864
|
+
return _objectFit == FFFObjectFitCover || _objectFit == FFFObjectFitFill;
|
|
837
865
|
}
|
|
838
866
|
|
|
839
867
|
// The view grew (by more than a fifth, as React Native's Image reloads), or
|
|
@@ -884,7 +912,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
884
912
|
// source doesn't start one), and ignore what it still sends (SDWebImage
|
|
885
913
|
// completes a cancelled load with an error; see downloadImage:).
|
|
886
914
|
self.loadCount++;
|
|
887
|
-
[self
|
|
915
|
+
[self sd_cancelLatestImageLoad];
|
|
888
916
|
|
|
889
917
|
if (_source) {
|
|
890
918
|
// Load base64 images.
|
|
@@ -892,9 +920,6 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
892
920
|
if (url && [url hasPrefix: @"data:image"]) {
|
|
893
921
|
if (self.onFastImageLoadStart) {
|
|
894
922
|
self.onFastImageLoadStart(@{});
|
|
895
|
-
self.hasSentOnLoadStart = YES;
|
|
896
|
-
} else {
|
|
897
|
-
self.hasSentOnLoadStart = NO;
|
|
898
923
|
}
|
|
899
924
|
// Use SDWebImage API to support external format like WebP images
|
|
900
925
|
UIImage* image = [UIImage sd_imageWithData: [NSData dataWithContentsOfURL: _source.url]];
|
|
@@ -912,7 +937,8 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
912
937
|
if (self.onFastImageProgress) {
|
|
913
938
|
self.onFastImageProgress(@{
|
|
914
939
|
@"loaded": @(1),
|
|
915
|
-
@"total": @(1)
|
|
940
|
+
@"total": @(1),
|
|
941
|
+
@"progress": @(1)
|
|
916
942
|
});
|
|
917
943
|
}
|
|
918
944
|
self.hasCompleted = YES;
|
|
@@ -937,9 +963,6 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
937
963
|
|
|
938
964
|
if (self.onFastImageLoadStart) {
|
|
939
965
|
self.onFastImageLoadStart(@{});
|
|
940
|
-
self.hasSentOnLoadStart = YES;
|
|
941
|
-
} else {
|
|
942
|
-
self.hasSentOnLoadStart = NO;
|
|
943
966
|
}
|
|
944
967
|
self.hasCompleted = NO;
|
|
945
968
|
self.hasErrored = NO;
|
|
@@ -982,8 +1005,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
982
1005
|
// Whether the view loads as it's laid out, so its image shows in this frame:
|
|
983
1006
|
// one in the memory cache, where SDWebImage finds it as the load starts, or
|
|
984
1007
|
// one that isn't downloaded (no source, which shows defaultSource, or a data
|
|
985
|
-
// uri). Also when that can't be told (an app's own image cache,
|
|
986
|
-
// SDWebImage without cacheKeyForURL:context:), as before.
|
|
1008
|
+
// uri). Also when that can't be told (an app's own image cache), as before.
|
|
987
1009
|
- (BOOL) loadsDuringLayout {
|
|
988
1010
|
FFFastImageSource* source = [self picksSource] ? [self sourceForSize] : _source;
|
|
989
1011
|
if (!source.url || [source.url.scheme isEqualToString: @"data"]) {
|
|
@@ -993,7 +1015,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
993
1015
|
return NO;
|
|
994
1016
|
}
|
|
995
1017
|
SDWebImageManager* manager = [SDWebImageManager sharedManager];
|
|
996
|
-
if (![manager.imageCache isKindOfClass: [SDImageCache class]]
|
|
1018
|
+
if (![manager.imageCache isKindOfClass: [SDImageCache class]]) {
|
|
997
1019
|
return YES;
|
|
998
1020
|
}
|
|
999
1021
|
CGSize box = [self decodeBox];
|
|
@@ -1009,10 +1031,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1009
1031
|
context[SDWebImageContextCacheKeyFilter] = source.cacheKeyFilter;
|
|
1010
1032
|
if (CGSizeEqualToSize(box, CGSizeZero)) {
|
|
1011
1033
|
[FFFDownsampledImage addFullSizeToContext: context];
|
|
1012
|
-
|
|
1013
|
-
// Only on disk, also when it comes from there.
|
|
1014
|
-
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeDisk);
|
|
1015
|
-
}
|
|
1034
|
+
[self applyCacheOptions: context source: source fullSize: YES];
|
|
1016
1035
|
[FFFDownsampledImage forgetSmallerFullSizeImageForURL: source.url context: context];
|
|
1017
1036
|
return context;
|
|
1018
1037
|
}
|
|
@@ -1024,12 +1043,35 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1024
1043
|
context[SDWebImageContextOriginalStoreCacheType] = @(SDImageCacheTypeNone);
|
|
1025
1044
|
context[SDWebImageContextOriginalQueryCacheType] = @(SDImageCacheTypeNone);
|
|
1026
1045
|
}
|
|
1046
|
+
[self applyCacheOptions: context source: source fullSize: NO];
|
|
1047
|
+
return context;
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
// The source's memoryCache and diskCache: which caches the image is looked up
|
|
1051
|
+
// in and kept in. fullSize: the image is decoded at its own size, rather than
|
|
1052
|
+
// smaller (addToContext keeps those in memory only, as the disk cache has the
|
|
1053
|
+
// downloaded file).
|
|
1054
|
+
- (void) applyCacheOptions: (SDWebImageMutableContext*)context source: (FFFastImageSource*)source fullSize: (BOOL)fullSize {
|
|
1055
|
+
if (!source.diskCache) {
|
|
1056
|
+
// Neither the downloaded file (the original, for a smaller decode)
|
|
1057
|
+
// nor the decoded image is read from or written to the disk cache. A
|
|
1058
|
+
// `web` one isn't kept in memory either, as on Android: it's
|
|
1059
|
+
// downloaded every time it loads.
|
|
1060
|
+
SDImageCacheType memory = source.memoryCache && source.cacheControl != FFFCacheControlWeb
|
|
1061
|
+
? SDImageCacheTypeMemory
|
|
1062
|
+
: SDImageCacheTypeNone;
|
|
1063
|
+
context[SDWebImageContextStoreCacheType] = @(memory);
|
|
1064
|
+
context[SDWebImageContextQueryCacheType] = @(memory);
|
|
1065
|
+
context[SDWebImageContextOriginalStoreCacheType] = @(SDImageCacheTypeNone);
|
|
1066
|
+
context[SDWebImageContextOriginalQueryCacheType] = @(SDImageCacheTypeNone);
|
|
1067
|
+
return;
|
|
1068
|
+
}
|
|
1027
1069
|
if (!source.memoryCache) {
|
|
1028
|
-
//
|
|
1029
|
-
// downloaded file is still
|
|
1030
|
-
|
|
1070
|
+
// Only on disk, also when it comes from there. A smaller copy is
|
|
1071
|
+
// only kept in memory, so it isn't kept (the downloaded file is still
|
|
1072
|
+
// on disk).
|
|
1073
|
+
context[SDWebImageContextStoreCacheType] = @(fullSize ? SDImageCacheTypeDisk : SDImageCacheTypeNone);
|
|
1031
1074
|
}
|
|
1032
|
-
return context;
|
|
1033
1075
|
}
|
|
1034
1076
|
|
|
1035
1077
|
// events: NO for a quiet reload at a new size (see reloadIfResized), which
|
|
@@ -1062,7 +1104,8 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1062
1104
|
if (onProgress) {
|
|
1063
1105
|
onProgress(@{
|
|
1064
1106
|
@"loaded": @(receivedSize),
|
|
1065
|
-
@"total": @(expectedSize)
|
|
1107
|
+
@"total": @(expectedSize),
|
|
1108
|
+
@"progress": @(MAX(0.0, MIN(1.0, (double)receivedSize / expectedSize)))
|
|
1066
1109
|
});
|
|
1067
1110
|
}
|
|
1068
1111
|
});
|
|
@@ -1190,9 +1233,23 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1190
1233
|
}
|
|
1191
1234
|
}
|
|
1192
1235
|
|
|
1236
|
+
- (void) stop {
|
|
1237
|
+
self.onFastImageLoadStart = nil;
|
|
1238
|
+
self.onFastImageProgress = nil;
|
|
1239
|
+
self.onFastImageLoad = nil;
|
|
1240
|
+
self.onFastImageError = nil;
|
|
1241
|
+
self.onFastImageLoadEnd = nil;
|
|
1242
|
+
// As a new load does (see reloadImage): a completion still on its way is
|
|
1243
|
+
// ignored, and a load waiting for the view's size doesn't start.
|
|
1244
|
+
self.loadCount++;
|
|
1245
|
+
self.waitsForSize = NO;
|
|
1246
|
+
[self stopWaitingForActive];
|
|
1247
|
+
[self sd_cancelLatestImageLoad];
|
|
1248
|
+
}
|
|
1249
|
+
|
|
1193
1250
|
- (void) dealloc {
|
|
1194
1251
|
[self stopWaitingForActive];
|
|
1195
|
-
[self
|
|
1252
|
+
[self sd_cancelLatestImageLoad];
|
|
1196
1253
|
}
|
|
1197
1254
|
|
|
1198
1255
|
@end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
#import <React/RCTViewComponentView.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
// The FastImageView component (src/specs) on the New Architecture: shows an
|
|
6
|
+
// FFFastImageView, and passes it the component's props and its events back.
|
|
7
|
+
@interface FFFastImageViewComponentView : RCTViewComponentView
|
|
8
|
+
|
|
9
|
+
@end
|
|
10
|
+
|
|
11
|
+
NS_ASSUME_NONNULL_END
|