react-native-fast-image 8.20.0 → 8.22.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 +37 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageBlur.java +213 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +6 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +17 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +21 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +287 -12
- package/dist/index.cjs.js +15 -1
- package/dist/index.cjs.js.flow +8 -0
- package/dist/index.d.ts +44 -0
- package/dist/index.js +15 -1
- package/dist/index.js.flow +8 -0
- package/ios/FastImage/FFFastImageView.h +8 -0
- package/ios/FastImage/FFFastImageView.m +324 -17
- package/ios/FastImage/FFFastImageViewManager.m +4 -0
- package/package.json +1 -1
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
#import <SDWebImage/SDWebImageError.h>
|
|
6
6
|
#import <SDWebImage/SDImageTransformer.h>
|
|
7
7
|
#import "FFFDownsampledImage.h"
|
|
8
|
+
#import <Accelerate/Accelerate.h>
|
|
8
9
|
|
|
9
10
|
@interface FFFastImageView ()
|
|
10
11
|
|
|
@@ -16,9 +17,20 @@
|
|
|
16
17
|
|
|
17
18
|
@property(nonatomic, strong) NSDictionary* onLoadEvent;
|
|
18
19
|
@property(nonatomic, strong) NSDictionary* onErrorEvent;
|
|
19
|
-
// The image before tinting, kept while
|
|
20
|
-
// changed or removed. nil when
|
|
21
|
-
@property(nonatomic, strong) UIImage*
|
|
20
|
+
// The image before tinting and blurring, kept while either is applied so they
|
|
21
|
+
// can be changed or removed. nil when neither is (super.image is the image).
|
|
22
|
+
@property(nonatomic, strong) UIImage* originalImage;
|
|
23
|
+
// Counts images set, so a blur that finishes can tell if it's still current.
|
|
24
|
+
@property(nonatomic, assign) NSUInteger imageCount;
|
|
25
|
+
// The view's size when the image showing was blurred.
|
|
26
|
+
@property(nonatomic, assign) CGSize blurredForSize;
|
|
27
|
+
// The blurred image showing (untinted), so a tint change can re-show it.
|
|
28
|
+
@property(nonatomic, strong) UIImage* blurredImage;
|
|
29
|
+
// The image being blurred fades in once it's blurred (transition).
|
|
30
|
+
@property(nonatomic, assign) BOOL fadesBlurredImage;
|
|
31
|
+
// The image being blurred is the first loaded one the view shows: until it's
|
|
32
|
+
// blurred, the view shows no loaded image (nothing, or defaultSource).
|
|
33
|
+
@property(nonatomic, assign) BOOL blursOverNothing;
|
|
22
34
|
// Whether the current load was already restarted after the app came back
|
|
23
35
|
// from the background (see downloadImage:).
|
|
24
36
|
@property(nonatomic, assign) BOOL retriedAfterBackground;
|
|
@@ -41,6 +53,8 @@
|
|
|
41
53
|
// When the app last went to the background (CACurrentMediaTime), or 0.
|
|
42
54
|
static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
43
55
|
|
|
56
|
+
static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, BOOL downscale);
|
|
57
|
+
|
|
44
58
|
@implementation FFFastImageView
|
|
45
59
|
|
|
46
60
|
+ (void) initialize {
|
|
@@ -108,6 +122,77 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
|
108
122
|
}
|
|
109
123
|
}
|
|
110
124
|
|
|
125
|
+
// Whether a loaded image fades in (the `transition` prop). Over a loaded
|
|
126
|
+
// image (a new source) only with betweenImages: otherwise it replaces it at
|
|
127
|
+
// once, as Android's image libraries fade an image in from nothing and never
|
|
128
|
+
// between images. Then as SDWebImage's transitions and those libraries
|
|
129
|
+
// decide: from the memory cache it shows at once (skipOnCacheHit 'memory',
|
|
130
|
+
// the default), and from the disk cache too with 'all'. Downloads, local
|
|
131
|
+
// files and bundled images fade.
|
|
132
|
+
- (BOOL) fadesImageFromCache: (SDImageCacheType)cacheType {
|
|
133
|
+
if (self.transitionDuration <= 0 || ([self displaysLoadedImage] && !self.transitionBetweenImages)) {
|
|
134
|
+
return NO;
|
|
135
|
+
}
|
|
136
|
+
NSString* skip = self.transitionSkipOnCacheHit;
|
|
137
|
+
if ([skip isEqualToString: @"none"]) {
|
|
138
|
+
return YES;
|
|
139
|
+
}
|
|
140
|
+
if (cacheType == SDImageCacheTypeMemory) {
|
|
141
|
+
return NO;
|
|
142
|
+
}
|
|
143
|
+
// SDWebImage also keeps local files and bundled images in its disk cache,
|
|
144
|
+
// so with 'all' they usually only fade the first time.
|
|
145
|
+
return !(cacheType == SDImageCacheTypeDisk && [skip isEqualToString: @"all"]);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
// Whether the view shows a loaded image now: a loaded image that's still
|
|
149
|
+
// being blurred isn't showing yet.
|
|
150
|
+
- (BOOL) displaysLoadedImage {
|
|
151
|
+
return self.showsLoadedImage && !self.blursOverNothing;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Shows a loaded image, fading it in over what the view shows if `fade`. A
|
|
155
|
+
// blurred image shows, and fades in, once it's blurred.
|
|
156
|
+
- (void) showLoadedImage: (UIImage*)image fade: (BOOL)fade {
|
|
157
|
+
if ([self blurs: image]) {
|
|
158
|
+
self.blursOverNothing = ![self displaysLoadedImage];
|
|
159
|
+
self.fadesBlurredImage = fade;
|
|
160
|
+
self.image = image;
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
self.blursOverNothing = NO;
|
|
164
|
+
self.fadesBlurredImage = NO;
|
|
165
|
+
if (fade) {
|
|
166
|
+
[self fadeIn: ^{
|
|
167
|
+
self.image = image;
|
|
168
|
+
}];
|
|
169
|
+
} else {
|
|
170
|
+
self.image = image;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// Makes the change (showing another image), fading it in over what the view
|
|
175
|
+
// showed.
|
|
176
|
+
- (void) fadeIn: (void (^)(void))change {
|
|
177
|
+
if (!super.image || !self.layer.presentationLayer) {
|
|
178
|
+
// Nothing to cross-dissolve from: the view shows no image, or isn't
|
|
179
|
+
// on screen yet (e.g. a new view whose image loads as it mounts),
|
|
180
|
+
// where a cross-dissolve has nothing drawn to start from and shows
|
|
181
|
+
// the image at once. Fade the view in instead.
|
|
182
|
+
change();
|
|
183
|
+
CABasicAnimation* fadeIn = [CABasicAnimation animationWithKeyPath: @"opacity"];
|
|
184
|
+
fadeIn.fromValue = @0;
|
|
185
|
+
fadeIn.duration = self.transitionDuration / 1000;
|
|
186
|
+
[self.layer addAnimation: fadeIn forKey: @"FFFFadeIn"];
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
[UIView transitionWithView: self
|
|
190
|
+
duration: self.transitionDuration / 1000
|
|
191
|
+
options: UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction
|
|
192
|
+
animations: change
|
|
193
|
+
completion: nil];
|
|
194
|
+
}
|
|
195
|
+
|
|
111
196
|
- (void) setPaused: (BOOL)paused {
|
|
112
197
|
if (_paused == paused) {
|
|
113
198
|
return;
|
|
@@ -128,6 +213,10 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
|
128
213
|
if (_resizeMode != resizeMode) {
|
|
129
214
|
_resizeMode = resizeMode;
|
|
130
215
|
[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
|
+
}
|
|
131
220
|
}
|
|
132
221
|
}
|
|
133
222
|
|
|
@@ -153,6 +242,7 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
|
153
242
|
- (void) layoutSubviews {
|
|
154
243
|
[super layoutSubviews];
|
|
155
244
|
[self updateContentMode];
|
|
245
|
+
[self blurAgainIfResized];
|
|
156
246
|
if (self.waitsForSize) {
|
|
157
247
|
if ([self hasSize]) {
|
|
158
248
|
[self reloadImage];
|
|
@@ -198,8 +288,16 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
|
198
288
|
|
|
199
289
|
- (void) setImageColor: (UIColor*)imageColor {
|
|
200
290
|
_imageColor = imageColor;
|
|
201
|
-
//
|
|
202
|
-
|
|
291
|
+
// A blurred image is a still image: show it with the new tint, without
|
|
292
|
+
// blurring it again.
|
|
293
|
+
if ([self blurs: self.originalImage]) {
|
|
294
|
+
if (self.blurredImage) {
|
|
295
|
+
[self showImage: self.blurredImage];
|
|
296
|
+
}
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
// Re-apply to the original image, so the tint can change or be removed.
|
|
300
|
+
UIImage* image = self.originalImage ?: super.image;
|
|
203
301
|
if (image) {
|
|
204
302
|
// SDAnimatedImageView ignores the image it already shows, which would
|
|
205
303
|
// keep an animated image's frames as they were tinted.
|
|
@@ -215,14 +313,36 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
|
215
313
|
}
|
|
216
314
|
|
|
217
315
|
- (void) setImage: (UIImage*)image {
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
316
|
+
self.imageCount++;
|
|
317
|
+
if ([self blurs: image]) {
|
|
318
|
+
self.originalImage = image;
|
|
319
|
+
self.blurredImage = nil;
|
|
320
|
+
[self blurImage: image];
|
|
321
|
+
return;
|
|
322
|
+
}
|
|
323
|
+
self.originalImage = image && self.imageColor ? image : nil;
|
|
324
|
+
self.blurredImage = nil;
|
|
325
|
+
self.blursOverNothing = NO;
|
|
326
|
+
// The blur was removed before it was ready: the fade it was waiting for
|
|
327
|
+
// happens now.
|
|
328
|
+
if (self.fadesBlurredImage) {
|
|
329
|
+
self.fadesBlurredImage = NO;
|
|
330
|
+
[self fadeIn: ^{
|
|
331
|
+
[self showImage: image];
|
|
332
|
+
}];
|
|
333
|
+
} else {
|
|
334
|
+
[self showImage: image];
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Shows the image, tinted as React Native's Image does it: UIKit draws a
|
|
339
|
+
// template image in the view's tintColor, so no tinted copy of the image is
|
|
340
|
+
// made. SDAnimatedImageView draws the frames of an animated image itself,
|
|
341
|
+
// without the tint, so each frame is tinted as it's decoded (source-in, as the
|
|
342
|
+
// template is), or, before SDWebImage 5.20, the first frame shows.
|
|
343
|
+
- (void) showImage: (UIImage*)image {
|
|
223
344
|
UIColor* tint = image ? self.imageColor : nil;
|
|
224
345
|
BOOL animated = [image conformsToProtocol: @protocol(SDAnimatedImage)] && [(id<SDAnimatedImage>)image animatedImageFrameCount] > 1;
|
|
225
|
-
self.untintedImage = tint ? image : nil;
|
|
226
346
|
self.tintColor = tint;
|
|
227
347
|
if ([self tintsFrames]) {
|
|
228
348
|
[self setValue: tint && animated ? [SDImageTintTransformer transformerWithColor: tint] : nil forKey: @"animationTransformer"];
|
|
@@ -247,6 +367,187 @@ static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
|
247
367
|
[self updateContentMode];
|
|
248
368
|
}
|
|
249
369
|
|
|
370
|
+
- (void) setBlurRadius: (CGFloat)blurRadius {
|
|
371
|
+
if (_blurRadius == blurRadius) {
|
|
372
|
+
return;
|
|
373
|
+
}
|
|
374
|
+
_blurRadius = blurRadius;
|
|
375
|
+
// Re-apply to the original image, so the blur can change or be removed.
|
|
376
|
+
UIImage* image = self.originalImage ?: super.image;
|
|
377
|
+
if (image) {
|
|
378
|
+
[self setImage: image];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Whether the image is blurred: a loaded image, not defaultSource (as with
|
|
383
|
+
// React Native's Image, and on Android).
|
|
384
|
+
- (BOOL) blurs: (UIImage*)image {
|
|
385
|
+
return _blurRadius > 0 && image && image != _defaultSource;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Blurs the image off the main thread, and shows it (tinted as any image) unless
|
|
389
|
+
// another image was set meanwhile. Until then the view keeps showing what it
|
|
390
|
+
// showed: never the image without the blur. An animated image shows its first
|
|
391
|
+
// frame.
|
|
392
|
+
- (void) blurImage: (UIImage*)image {
|
|
393
|
+
// Not laid out yet (an image from the memory cache is set before the
|
|
394
|
+
// view's first layout): it's blurred for its size once it has one
|
|
395
|
+
// (blurAgainIfResized), instead of twice.
|
|
396
|
+
if (![self hasSize]) {
|
|
397
|
+
self.blurredForSize = CGSizeZero;
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
400
|
+
CGFloat screenScale = self.window.screen.scale ?: [UIScreen mainScreen].scale;
|
|
401
|
+
// How many pixels on screen a pixel of the image takes.
|
|
402
|
+
CGFloat shownScale = [self shownScale: image] * screenScale / image.scale;
|
|
403
|
+
// Blurred at the size it's shown at (a blurred image needs no more
|
|
404
|
+
// detail), or at its own size if it's shown larger.
|
|
405
|
+
CGFloat scale = MIN(shownScale, 1);
|
|
406
|
+
// The radius in pixels of the image that's blurred.
|
|
407
|
+
CGFloat radius = _blurRadius * screenScale * scale / shownScale;
|
|
408
|
+
// Pixelated images are drawn larger without smoothing, so a smaller copy
|
|
409
|
+
// would show its pixels.
|
|
410
|
+
BOOL downscale = ![_imageRendering isEqualToString: @"pixelated"];
|
|
411
|
+
NSUInteger count = self.imageCount;
|
|
412
|
+
self.blurredForSize = self.bounds.size;
|
|
413
|
+
__weak typeof(self) weakSelf = self;
|
|
414
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
415
|
+
UIImage* blurred = FFFBlurredImage(image, scale, radius, downscale);
|
|
416
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
417
|
+
[weakSelf showBlurredImage: blurred count: count];
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
- (void) showBlurredImage: (UIImage*)image count: (NSUInteger)count {
|
|
423
|
+
if (self.imageCount == count) {
|
|
424
|
+
self.blurredImage = image;
|
|
425
|
+
self.blursOverNothing = NO;
|
|
426
|
+
if (self.fadesBlurredImage) {
|
|
427
|
+
self.fadesBlurredImage = NO;
|
|
428
|
+
[self fadeIn: ^{
|
|
429
|
+
[self showImage: image];
|
|
430
|
+
}];
|
|
431
|
+
} else {
|
|
432
|
+
[self showImage: image];
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// Points on screen per point of the image, for the view's size and
|
|
438
|
+
// resizeMode, or 1 before the view has a size.
|
|
439
|
+
- (CGFloat) shownScale: (UIImage*)image {
|
|
440
|
+
CGSize size = image.size;
|
|
441
|
+
CGSize view = self.bounds.size;
|
|
442
|
+
if (size.width <= 0 || size.height <= 0 || view.width <= 0 || view.height <= 0) {
|
|
443
|
+
return 1;
|
|
444
|
+
}
|
|
445
|
+
CGFloat x = view.width / size.width;
|
|
446
|
+
CGFloat y = view.height / size.height;
|
|
447
|
+
switch (_resizeMode) {
|
|
448
|
+
case RCTResizeModeCover:
|
|
449
|
+
return MAX(x, y);
|
|
450
|
+
case RCTResizeModeContain:
|
|
451
|
+
return MIN(x, y);
|
|
452
|
+
case RCTResizeModeStretch:
|
|
453
|
+
return sqrt(x * y);
|
|
454
|
+
case RCTResizeModeCenter:
|
|
455
|
+
return MIN(MIN(x, y), 1);
|
|
456
|
+
default:
|
|
457
|
+
return 1;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
// The view's size changed a lot (by more than a fifth either way) since the
|
|
462
|
+
// image was blurred, which was for the size it was shown at then: blur it
|
|
463
|
+
// again for the new size.
|
|
464
|
+
- (void) blurAgainIfResized {
|
|
465
|
+
if (![self blurs: self.originalImage] || ![self hasSize]) {
|
|
466
|
+
return;
|
|
467
|
+
}
|
|
468
|
+
CGSize view = self.bounds.size;
|
|
469
|
+
CGSize blurred = self.blurredForSize;
|
|
470
|
+
if (blurred.width > 0 && blurred.height > 0 &&
|
|
471
|
+
view.width <= blurred.width * 1.2 && blurred.width <= view.width * 1.2 &&
|
|
472
|
+
view.height <= blurred.height * 1.2 && blurred.height <= view.height * 1.2) {
|
|
473
|
+
return;
|
|
474
|
+
}
|
|
475
|
+
[self setImage: self.originalImage];
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
// The image (its first frame, for an animated image) drawn at scale times its
|
|
479
|
+
// size in pixels, and blurred as React Native's Image blurs on iOS: three box
|
|
480
|
+
// blurs, close to a Gaussian blur, with the box size worked out from the
|
|
481
|
+
// radius (in pixels) as the SVG spec describes and halved, as React Native
|
|
482
|
+
// does. A large box blurs a copy 2, 4 or 8 times smaller instead, with a box
|
|
483
|
+
// that much smaller, of at least 11 pixels: away from the edges that matches
|
|
484
|
+
// blurring at full size within a few levels of 255 (about as much as a
|
|
485
|
+
// slightly different radius changes it), for a quarter to a sixty-fourth of
|
|
486
|
+
// the work. It keeps the image's size in points.
|
|
487
|
+
static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, BOOL downscale)
|
|
488
|
+
{
|
|
489
|
+
if (image.size.width <= 0 || image.size.height <= 0) {
|
|
490
|
+
return image;
|
|
491
|
+
}
|
|
492
|
+
double fullWidth = MAX(1, round(image.size.width * image.scale * scale));
|
|
493
|
+
double fullHeight = MAX(1, round(image.size.height * image.scale * scale));
|
|
494
|
+
// At most the image's larger side (a larger box looks the same, and an
|
|
495
|
+
// absurd radius would overflow).
|
|
496
|
+
double box = MIN(floor((radius * 3 * sqrt(2 * M_PI) / 4 + 0.5) / 2), MAX(fullWidth, fullHeight));
|
|
497
|
+
double factor = !downscale ? 1 : box >= 88 ? 8 : box >= 44 ? 4 : box >= 22 ? 2 : 1;
|
|
498
|
+
size_t width = MAX(1, (size_t) round(fullWidth / factor));
|
|
499
|
+
size_t height = MAX(1, (size_t) round(fullHeight / factor));
|
|
500
|
+
// Odd, so it's centered.
|
|
501
|
+
uint32_t boxSize = factor == 1 ? (uint32_t) box | 1 : (uint32_t) round((box / factor - 1) / 2) * 2 + 1;
|
|
502
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
503
|
+
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, colorSpace,
|
|
504
|
+
kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host);
|
|
505
|
+
CGColorSpaceRelease(colorSpace);
|
|
506
|
+
if (!context) {
|
|
507
|
+
return image;
|
|
508
|
+
}
|
|
509
|
+
// Drawn as UIKit draws (top-left origin), so it follows the image's
|
|
510
|
+
// orientation.
|
|
511
|
+
CGContextTranslateCTM(context, 0, height);
|
|
512
|
+
CGContextScaleCTM(context, 1, -1);
|
|
513
|
+
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
|
|
514
|
+
UIGraphicsPushContext(context);
|
|
515
|
+
[image drawInRect: CGRectMake(0, 0, width, height)];
|
|
516
|
+
UIGraphicsPopContext();
|
|
517
|
+
|
|
518
|
+
if (boxSize > 1) {
|
|
519
|
+
vImage_Buffer buffer = {
|
|
520
|
+
.data = CGBitmapContextGetData(context),
|
|
521
|
+
.height = height,
|
|
522
|
+
.width = width,
|
|
523
|
+
.rowBytes = CGBitmapContextGetBytesPerRow(context),
|
|
524
|
+
};
|
|
525
|
+
vImage_Buffer other = buffer;
|
|
526
|
+
other.data = malloc(buffer.rowBytes * height);
|
|
527
|
+
vImage_Error tempSize = vImageBoxConvolve_ARGB8888(&buffer, &other, NULL, 0, 0, boxSize, boxSize, NULL,
|
|
528
|
+
kvImageGetTempBufferSize | kvImageEdgeExtend);
|
|
529
|
+
void* temp = tempSize > 0 ? malloc(tempSize) : NULL;
|
|
530
|
+
if (other.data && temp) {
|
|
531
|
+
vImageBoxConvolve_ARGB8888(&buffer, &other, temp, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
|
|
532
|
+
vImageBoxConvolve_ARGB8888(&other, &buffer, temp, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
|
|
533
|
+
vImageBoxConvolve_ARGB8888(&buffer, &other, temp, 0, 0, boxSize, boxSize, NULL, kvImageEdgeExtend);
|
|
534
|
+
memcpy(buffer.data, other.data, buffer.rowBytes * height);
|
|
535
|
+
}
|
|
536
|
+
free(other.data);
|
|
537
|
+
free(temp);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
CGImageRef cgImage = CGBitmapContextCreateImage(context);
|
|
541
|
+
CGContextRelease(context);
|
|
542
|
+
if (!cgImage) {
|
|
543
|
+
return image;
|
|
544
|
+
}
|
|
545
|
+
// The same size in points, also when it's smaller.
|
|
546
|
+
UIImage* blurred = [UIImage imageWithCGImage: cgImage scale: width / image.size.width orientation: UIImageOrientationUp];
|
|
547
|
+
CGImageRelease(cgImage);
|
|
548
|
+
return blurred;
|
|
549
|
+
}
|
|
550
|
+
|
|
250
551
|
// The error's description, with the HTTP status code when there is one (as on
|
|
251
552
|
// Android): SDWebImage keeps the code out of the description. Also for preload
|
|
252
553
|
// results.
|
|
@@ -306,8 +607,9 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
306
607
|
if (changed) {
|
|
307
608
|
// The view shows other content now: don't keep the current image
|
|
308
609
|
// while the next one loads (reloadImage clears it), even if the
|
|
309
|
-
// source is the same.
|
|
610
|
+
// source is the same, nor a fade from it.
|
|
310
611
|
self.showsLoadedImage = NO;
|
|
612
|
+
[self.layer removeAllAnimations];
|
|
311
613
|
_needsReload = YES;
|
|
312
614
|
}
|
|
313
615
|
}
|
|
@@ -381,7 +683,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
381
683
|
return;
|
|
382
684
|
}
|
|
383
685
|
// Already at full size (it's no larger than the view it was decoded for).
|
|
384
|
-
UIImage* image = self.
|
|
686
|
+
UIImage* image = self.originalImage ?: super.image;
|
|
385
687
|
if (self.hasCompleted && CGSizeEqualToSize(image.size, [FFFDownsampledImage sourceSizeOfImage: image])) {
|
|
386
688
|
return;
|
|
387
689
|
}
|
|
@@ -439,7 +741,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
439
741
|
}
|
|
440
742
|
return;
|
|
441
743
|
}
|
|
442
|
-
[self
|
|
744
|
+
[self showLoadedImage: image fade: [self fadesImageFromCache: SDImageCacheTypeNone]];
|
|
443
745
|
if (self.onFastImageProgress) {
|
|
444
746
|
self.onFastImageProgress(@{
|
|
445
747
|
@"loaded": @(1),
|
|
@@ -592,13 +894,18 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
592
894
|
// class to SDAnimatedImage (loadContext sets it). Only while this is the
|
|
593
895
|
// current load: SDWebImage sets the placeholder when a load it cancelled
|
|
594
896
|
// completes, which could replace a newer image.
|
|
897
|
+
UIImage* placeholder = _defaultSource;
|
|
595
898
|
SDSetImageBlock setImage = ^(UIImage* _Nullable image, NSData* _Nullable data, SDImageCacheType cacheType, NSURL* _Nullable imageURL) {
|
|
596
|
-
if (weakSelf.loadCount
|
|
597
|
-
|
|
899
|
+
if (weakSelf.loadCount != load) {
|
|
900
|
+
return;
|
|
598
901
|
}
|
|
902
|
+
// The loaded image, not defaultSource (shown while it loads, or if it
|
|
903
|
+
// fails).
|
|
904
|
+
BOOL loaded = image && image != placeholder;
|
|
905
|
+
[weakSelf showLoadedImage: image fade: loaded && [weakSelf fadesImageFromCache: cacheType]];
|
|
599
906
|
};
|
|
600
907
|
[self sd_internalSetImageWithURL: url
|
|
601
|
-
placeholderImage:
|
|
908
|
+
placeholderImage: placeholder
|
|
602
909
|
options: options
|
|
603
910
|
context: context
|
|
604
911
|
setImageBlock: setImage
|
|
@@ -107,7 +107,11 @@ RCT_EXPORT_VIEW_PROPERTY(recyclingKey, NSString)
|
|
|
107
107
|
RCT_EXPORT_VIEW_PROPERTY(loopCount, NSInteger)
|
|
108
108
|
RCT_EXPORT_VIEW_PROPERTY(imageRendering, NSString)
|
|
109
109
|
RCT_EXPORT_VIEW_PROPERTY(paused, BOOL)
|
|
110
|
+
RCT_EXPORT_VIEW_PROPERTY(transitionDuration, double)
|
|
111
|
+
RCT_EXPORT_VIEW_PROPERTY(transitionBetweenImages, BOOL)
|
|
112
|
+
RCT_EXPORT_VIEW_PROPERTY(transitionSkipOnCacheHit, NSString)
|
|
110
113
|
RCT_EXPORT_VIEW_PROPERTY(downsample, BOOL)
|
|
114
|
+
RCT_EXPORT_VIEW_PROPERTY(blurRadius, CGFloat)
|
|
111
115
|
RCT_EXPORT_VIEW_PROPERTY(onFastImageLoadStart, RCTDirectEventBlock)
|
|
112
116
|
RCT_EXPORT_VIEW_PROPERTY(onFastImageProgress, RCTDirectEventBlock)
|
|
113
117
|
RCT_EXPORT_VIEW_PROPERTY(onFastImageError, RCTDirectEventBlock)
|