react-native-fast-image 8.23.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 +21 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +5 -2
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +43 -4
- package/dist/constants.d.ts +1 -0
- package/dist/index.cjs.js +2 -1
- package/dist/index.cjs.js.flow +1 -0
- package/dist/index.cjs.web.js +2 -1
- package/dist/index.cjs.web.js.flow +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -1
- package/dist/index.js.flow +1 -0
- package/dist/index.web.js +2 -1
- package/dist/index.web.js.flow +1 -0
- package/ios/FastImage/FFFDownsampledImage.m +6 -1
- package/ios/FastImage/FFFastImageSource.h +10 -1
- package/ios/FastImage/FFFastImageSource.m +157 -0
- package/ios/FastImage/FFFastImageView.m +78 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -105,6 +105,25 @@ When `source` changes, the image that's showing stays until the new one has load
|
|
|
105
105
|
|
|
106
106
|
Remote url to load the image from. e.g. `'https://facebook.github.io/react/img/logo_og.png'`.
|
|
107
107
|
|
|
108
|
+
#### Photo library images (iOS)
|
|
109
|
+
|
|
110
|
+
A photo library url (`ph://<localIdentifier>`, as camera roll libraries give) loads when the app has SDWebImagePhotosPlugin. Add it to the app's `ios/Podfile` and run `pod install`:
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
pod 'SDWebImagePhotosPlugin'
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
With Expo, add it with [expo-build-properties](https://docs.expo.dev/versions/latest/sdk/build-properties/):
|
|
117
|
+
|
|
118
|
+
```json
|
|
119
|
+
[
|
|
120
|
+
"expo-build-properties",
|
|
121
|
+
{ "ios": { "extraPods": [{ "name": "SDWebImagePhotosPlugin" }] } }
|
|
122
|
+
]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
The app needs access to the photo library, which it has if it got the url from there. A photo library image is decoded at about the view's size, since photos are large and usually shown small; `onLoad` still reports the photo's own size. `FastImage.preload` of a `ph://` source loads the full-size photo (there's no view to size it for), which doesn't make a view's smaller copy load faster, so preloading photo library images usually isn't worth it. Without the plugin, a `ph://` source fails with `onError`, saying so. `assets-library://` urls aren't supported. On Android, photo pickers give `content://` urls, which load as they are.
|
|
126
|
+
|
|
108
127
|
---
|
|
109
128
|
|
|
110
129
|
### `source.headers?: object`
|
|
@@ -180,6 +199,7 @@ For views that get reused for other content, such as rows in FlashList or recycl
|
|
|
180
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).
|
|
181
200
|
- `FastImage.resizeMode.stretch` - Scale width and height independently, This may change the aspect ratio of the src.
|
|
182
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.
|
|
183
203
|
|
|
184
204
|
---
|
|
185
205
|
|
|
@@ -239,7 +259,7 @@ iOS only. Decodes a large image at about the size it's shown at, instead of at f
|
|
|
239
259
|
|
|
240
260
|
Use it when you show images much larger than their views and can't get them at the right size, e.g. user uploads or other people's URLs in a list. If you control the images, serve them at the size they're shown instead (resized on your server or by an image CDN), which also saves bandwidth.
|
|
241
261
|
|
|
242
|
-
Decoding a smaller copy can take a little longer, so use it where the memory matters. Needs SDWebImage 5.19.7 or later. On Android images are already decoded at about the view's size.
|
|
262
|
+
Decoding a smaller copy can take a little longer, so use it where the memory matters. Needs SDWebImage 5.19.7 or later. Photo library images are always decoded this way. On Android images are already decoded at about the view's size.
|
|
243
263
|
|
|
244
264
|
---
|
|
245
265
|
|
|
@@ -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
|
-
|
|
131
|
-
|
|
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 (
|
|
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()
|
package/dist/constants.d.ts
CHANGED
package/dist/index.cjs.js
CHANGED
package/dist/index.cjs.js.flow
CHANGED
package/dist/index.cjs.web.js
CHANGED
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
package/dist/index.js.flow
CHANGED
package/dist/index.web.js
CHANGED
package/dist/index.web.js.flow
CHANGED
|
@@ -89,7 +89,12 @@ static CGSize FFFPixelSize(NSData* data) {
|
|
|
89
89
|
// After a load that isn't downsampled (e.g. a preload), this class
|
|
90
90
|
// wouldn't be used, and the box would be decoded as SDWebImage does (to
|
|
91
91
|
// fill it, stretched). With its own url, a downsampled load only shares
|
|
92
|
-
// downloads with other downsampled ones.
|
|
92
|
+
// downloads with other downsampled ones. Not a photo library url, which
|
|
93
|
+
// isn't downloaded, and whose loader reads everything after ph:// as the
|
|
94
|
+
// photo's identifier.
|
|
95
|
+
if ([url.scheme isEqualToString: @"ph"]) {
|
|
96
|
+
return url;
|
|
97
|
+
}
|
|
93
98
|
NSURLComponents* components = [NSURLComponents componentsWithURL: url resolvingAgainstBaseURL: NO];
|
|
94
99
|
if (!components) {
|
|
95
100
|
return url;
|
|
@@ -53,9 +53,18 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
|
|
|
53
53
|
- (SDWebImageOptions)cacheOptions;
|
|
54
54
|
|
|
55
55
|
// The loader for `cache: 'web'` images, whose HTTP cache is their own (see
|
|
56
|
-
// webURLCache),
|
|
56
|
+
// webURLCache), for photo library images (ph://, see FFFPhotosLoader), or nil
|
|
57
|
+
// for SDWebImage's shared one.
|
|
57
58
|
- (id<SDImageLoader>)imageLoader;
|
|
58
59
|
|
|
60
|
+
// Whether the source is a photo library image (ph://<localIdentifier>),
|
|
61
|
+
// which is decoded at about the view's size.
|
|
62
|
+
- (BOOL)isPhotoLibrary;
|
|
63
|
+
|
|
64
|
+
// A photo library image's own size in pixels (for onLoad when it comes from
|
|
65
|
+
// a cache, where the size it was decoded from isn't recorded), or zero.
|
|
66
|
+
- (CGSize)photoPixelSize;
|
|
67
|
+
|
|
59
68
|
// The HTTP cache of `cache: 'web'` images: their own rather than the app's
|
|
60
69
|
// shared NSURLCache, so clearDiskCache can empty it without the app's other
|
|
61
70
|
// responses. 50 MB, as on Android.
|
|
@@ -5,9 +5,150 @@
|
|
|
5
5
|
#import <SDWebImage/SDWebImageError.h>
|
|
6
6
|
#import <SDWebImage/SDWebImageDownloaderOperation.h>
|
|
7
7
|
#import <SDWebImage/NSData+ImageContentType.h>
|
|
8
|
+
#import <objc/message.h>
|
|
8
9
|
|
|
9
10
|
static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
|
|
10
11
|
|
|
12
|
+
// The photo's own size in pixels, looked up by its identifier (the url after
|
|
13
|
+
// ph://), or zero. Through the runtime, as the Photos framework is only there
|
|
14
|
+
// when the app links it (with SDWebImagePhotosPlugin). Kept once looked up.
|
|
15
|
+
static CGSize FFFLookUpPhotoPixelSize(NSURL *url)
|
|
16
|
+
{
|
|
17
|
+
Class assetClass = NSClassFromString(@"PHAsset");
|
|
18
|
+
SEL fetch = NSSelectorFromString(@"fetchAssetsWithLocalIdentifiers:options:");
|
|
19
|
+
NSString *prefix = @"ph://";
|
|
20
|
+
NSString *string = url.absoluteString;
|
|
21
|
+
if (!assetClass || ![assetClass respondsToSelector:fetch] || ![string hasPrefix:prefix]) {
|
|
22
|
+
return CGSizeZero;
|
|
23
|
+
}
|
|
24
|
+
id (*fetchAssets)(id, SEL, NSArray *, id) = (void *)objc_msgSend;
|
|
25
|
+
id result = fetchAssets(assetClass, fetch, @[[string substringFromIndex:prefix.length]], nil);
|
|
26
|
+
id asset = [result respondsToSelector:@selector(firstObject)] ? [result firstObject] : nil;
|
|
27
|
+
if (!asset) {
|
|
28
|
+
return CGSizeZero;
|
|
29
|
+
}
|
|
30
|
+
return CGSizeMake([[asset valueForKey:@"pixelWidth"] doubleValue], [[asset valueForKey:@"pixelHeight"] doubleValue]);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
static NSCache<NSString *, NSValue *> *FFFPhotoSizes(void)
|
|
34
|
+
{
|
|
35
|
+
static NSCache *sizes;
|
|
36
|
+
static dispatch_once_t once;
|
|
37
|
+
dispatch_once(&once, ^{
|
|
38
|
+
sizes = [NSCache new];
|
|
39
|
+
});
|
|
40
|
+
return sizes;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
static CGSize FFFPhotoPixelSize(NSURL *url)
|
|
44
|
+
{
|
|
45
|
+
NSString *key = url.absoluteString;
|
|
46
|
+
NSValue *known = key ? [FFFPhotoSizes() objectForKey:key] : nil;
|
|
47
|
+
if (known) {
|
|
48
|
+
return known.CGSizeValue;
|
|
49
|
+
}
|
|
50
|
+
CGSize size = FFFLookUpPhotoPixelSize(url);
|
|
51
|
+
if (key && size.width > 0 && size.height > 0) {
|
|
52
|
+
[FFFPhotoSizes() setObject:[NSValue valueWithCGSize:size] forKey:key];
|
|
53
|
+
}
|
|
54
|
+
return size;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Loads photo library images (ph://<localIdentifier>) with
|
|
58
|
+
// SDWebImagePhotosPlugin's loader when the app has it (the pod): FastImage
|
|
59
|
+
// finds it at runtime, so it doesn't depend on it or on the Photos framework.
|
|
60
|
+
// Its own instance, so the app's use of the plugin is left as it is. Without
|
|
61
|
+
// the plugin, or for a source it can't load (assets-library://), the load
|
|
62
|
+
// fails with `failure`.
|
|
63
|
+
@interface FFFPhotosLoader : NSObject <SDImageLoader>
|
|
64
|
+
@property (nonatomic, strong) id<SDImageLoader> plugin;
|
|
65
|
+
@property (nonatomic, copy) NSString *failure;
|
|
66
|
+
@end
|
|
67
|
+
|
|
68
|
+
@implementation FFFPhotosLoader
|
|
69
|
+
|
|
70
|
+
+ (FFFPhotosLoader *)photosLoader
|
|
71
|
+
{
|
|
72
|
+
static FFFPhotosLoader *loader;
|
|
73
|
+
static dispatch_once_t once;
|
|
74
|
+
dispatch_once(&once, ^{
|
|
75
|
+
loader = [FFFPhotosLoader new];
|
|
76
|
+
Class pluginClass = NSClassFromString(@"SDImagePhotosLoader");
|
|
77
|
+
id<SDImageLoader> plugin = pluginClass ? [pluginClass new] : nil;
|
|
78
|
+
if ([plugin conformsToProtocol:@protocol(SDImageLoader)]) {
|
|
79
|
+
// One image per load, at full quality. By default the plugin
|
|
80
|
+
// first sends a quick low quality one, which a view would show
|
|
81
|
+
// (and fade in) before the one it asked for.
|
|
82
|
+
// PHImageRequestOptionsDeliveryModeHighQualityFormat. Skipped if
|
|
83
|
+
// a later version of the plugin renames these.
|
|
84
|
+
NSObject *requestOptions = [(NSObject *)plugin respondsToSelector:NSSelectorFromString(@"imageRequestOptions")]
|
|
85
|
+
? [(NSObject *)plugin valueForKey:@"imageRequestOptions"]
|
|
86
|
+
: nil;
|
|
87
|
+
if ([requestOptions respondsToSelector:NSSelectorFromString(@"setDeliveryMode:")]) {
|
|
88
|
+
[requestOptions setValue:@1 forKey:@"deliveryMode"];
|
|
89
|
+
}
|
|
90
|
+
loader.plugin = plugin;
|
|
91
|
+
} else {
|
|
92
|
+
loader.failure = @"Photo library images (ph://) need SDWebImagePhotosPlugin: add pod 'SDWebImagePhotosPlugin' to the app's Podfile";
|
|
93
|
+
}
|
|
94
|
+
});
|
|
95
|
+
return loader;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
+ (FFFPhotosLoader *)assetsLibraryLoader
|
|
99
|
+
{
|
|
100
|
+
static FFFPhotosLoader *loader;
|
|
101
|
+
static dispatch_once_t once;
|
|
102
|
+
dispatch_once(&once, ^{
|
|
103
|
+
loader = [FFFPhotosLoader new];
|
|
104
|
+
loader.failure = @"assets-library:// urls aren't supported: use the photo's ph:// url (with SDWebImagePhotosPlugin)";
|
|
105
|
+
});
|
|
106
|
+
return loader;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
- (BOOL)canRequestImageForURL:(NSURL *)url
|
|
110
|
+
{
|
|
111
|
+
return YES;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
- (id<SDWebImageOperation>)requestImageWithURL:(NSURL *)url
|
|
115
|
+
options:(SDWebImageOptions)options
|
|
116
|
+
context:(SDWebImageContext *)context
|
|
117
|
+
progress:(SDImageLoaderProgressBlock)progressBlock
|
|
118
|
+
completed:(SDImageLoaderCompletedBlock)completedBlock
|
|
119
|
+
{
|
|
120
|
+
if (!self.plugin) {
|
|
121
|
+
if (completedBlock) {
|
|
122
|
+
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidURL userInfo:@{NSLocalizedDescriptionKey: self.failure}];
|
|
123
|
+
completedBlock(nil, nil, error, YES);
|
|
124
|
+
}
|
|
125
|
+
return nil;
|
|
126
|
+
}
|
|
127
|
+
return [self.plugin requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *image, NSData *data, NSError *error, BOOL finished) {
|
|
128
|
+
if (!image || !completedBlock) {
|
|
129
|
+
if (completedBlock) {
|
|
130
|
+
completedBlock(image, data, error, finished);
|
|
131
|
+
}
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// Looks up the photo's own size for onLoad here, off the main thread
|
|
135
|
+
// (the image is decoded at about the view's size).
|
|
136
|
+
dispatch_async(dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0), ^{
|
|
137
|
+
FFFPhotoPixelSize(url);
|
|
138
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
139
|
+
completedBlock(image, data, error, finished);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
}];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error
|
|
146
|
+
{
|
|
147
|
+
return self.plugin ? [self.plugin shouldBlockFailedURLWithURL:url error:error] : NO;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@end
|
|
151
|
+
|
|
11
152
|
// Downloads `cache: 'web'` images. The HTTP cache only stores a response whose
|
|
12
153
|
// body is an image: a page sent instead (e.g. a captive portal's, with status
|
|
13
154
|
// 200) would be served from there until it expired, and each load would fail.
|
|
@@ -75,8 +216,24 @@ static NSUInteger const FFFWebCacheSize = 50 * 1024 * 1024;
|
|
|
75
216
|
return 0;
|
|
76
217
|
}
|
|
77
218
|
|
|
219
|
+
- (BOOL)isPhotoLibrary
|
|
220
|
+
{
|
|
221
|
+
return [_url.scheme isEqualToString:@"ph"];
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
- (CGSize)photoPixelSize
|
|
225
|
+
{
|
|
226
|
+
return [self isPhotoLibrary] ? FFFPhotoPixelSize(_url) : CGSizeZero;
|
|
227
|
+
}
|
|
228
|
+
|
|
78
229
|
- (id<SDImageLoader>)imageLoader
|
|
79
230
|
{
|
|
231
|
+
if ([self isPhotoLibrary]) {
|
|
232
|
+
return [FFFPhotosLoader photosLoader];
|
|
233
|
+
}
|
|
234
|
+
if ([_url.scheme isEqualToString:@"assets-library"]) {
|
|
235
|
+
return [FFFPhotosLoader assetsLibraryLoader];
|
|
236
|
+
}
|
|
80
237
|
if (_cacheControl != FFFCacheControlWeb) {
|
|
81
238
|
return nil;
|
|
82
239
|
}
|
|
@@ -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
|
-
|
|
218
|
-
|
|
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 ==
|
|
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
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -570,7 +619,9 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
570
619
|
|
|
571
620
|
- (void) sendOnLoad: (UIImage*)image {
|
|
572
621
|
// The full image's size, also when it was decoded smaller.
|
|
573
|
-
|
|
622
|
+
// A photo library image's own size (it's decoded at about the view's
|
|
623
|
+
// size), looked up when it loaded.
|
|
624
|
+
CGSize size = [_source isPhotoLibrary] ? [_source photoPixelSize] : [FFFDownsampledImage sourceSizeOfImage: image];
|
|
574
625
|
if (CGSizeEqualToSize(size, CGSizeZero)) {
|
|
575
626
|
size = image.size;
|
|
576
627
|
}
|
|
@@ -646,11 +697,15 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
646
697
|
}
|
|
647
698
|
}
|
|
648
699
|
|
|
649
|
-
// Whether images are decoded at about the view's size (downsample
|
|
650
|
-
//
|
|
651
|
-
//
|
|
700
|
+
// Whether images are decoded at about the view's size (downsample, from
|
|
701
|
+
// SDWebImage 5.19, and always for photo library images, which are large and
|
|
702
|
+
// usually shown small: Photos makes them at the size asked for, on any
|
|
703
|
+
// version). Not for `repeat`, which tiles the image at its own size.
|
|
652
704
|
- (BOOL) downsamples {
|
|
653
|
-
|
|
705
|
+
if (_resizeMode == RCTResizeModeRepeat) {
|
|
706
|
+
return NO;
|
|
707
|
+
}
|
|
708
|
+
return [_source isPhotoLibrary] || (_downsample && [FFFDownsampledImage isSupported]);
|
|
654
709
|
}
|
|
655
710
|
|
|
656
711
|
// Whether the view has been laid out with an area. One that's 0 wide or tall
|
|
@@ -823,6 +878,13 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
823
878
|
}
|
|
824
879
|
NSString* key = _source.cacheKeyFilter ? _source.cacheKey : _source.url.absoluteString;
|
|
825
880
|
[FFFDownsampledImage addToContext: context forKey: key box: box cover: self.decodedCover];
|
|
881
|
+
if ([_source isPhotoLibrary]) {
|
|
882
|
+
// Photos makes the photo at the size asked for, with no file
|
|
883
|
+
// downloaded: SDWebImage would keep this smaller copy on disk as the
|
|
884
|
+
// photo's original, and a larger view would get it from there.
|
|
885
|
+
context[SDWebImageContextOriginalStoreCacheType] = @(SDImageCacheTypeNone);
|
|
886
|
+
context[SDWebImageContextOriginalQueryCacheType] = @(SDImageCacheTypeNone);
|
|
887
|
+
}
|
|
826
888
|
if (!_source.memoryCache) {
|
|
827
889
|
// The smaller copy is only kept in memory, so it isn't kept (the
|
|
828
890
|
// downloaded file is still on disk).
|