@depup/react-native-fast-image 8.6.3-depup.0 → 8.13.1-depup.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 +2 -2
- package/RNFastImage.podspec +5 -2
- package/android/build.gradle +13 -0
- package/android/src/main/AndroidManifestNew.xml +3 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +84 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvent.java +45 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +60 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageGif.java +110 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +65 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +65 -22
- package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +21 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +21 -1
- package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +237 -0
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +14 -5
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +66 -22
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +130 -24
- package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +359 -39
- package/android/src/main/java/com/dylanvann/fastimage/FastImageWebGlideUrl.java +13 -0
- package/changes.json +1 -1
- package/dist/index.cjs.js +166 -130
- package/dist/index.cjs.js.flow +33 -2
- package/dist/index.d.ts +77 -22
- package/dist/index.js +141 -121
- package/dist/index.js.flow +33 -2
- package/ios/FastImage/FFFDownsampledImage.h +28 -0
- package/ios/FastImage/FFFDownsampledImage.m +169 -0
- package/ios/FastImage/FFFastImageSource.h +6 -0
- package/ios/FastImage/FFFastImageSource.m +13 -0
- package/ios/FastImage/FFFastImageView.h +17 -0
- package/ios/FastImage/FFFastImageView.m +498 -71
- package/ios/FastImage/FFFastImageViewManager.m +106 -8
- package/ios/FastImage/RCTConvert+FFFastImage.m +4 -1
- package/package.json +31 -43
- package/dist/index.d.ts.map +0 -1
- package/dist/index.test.d.ts +0 -2
- package/dist/index.test.d.ts.map +0 -1
package/dist/index.js
CHANGED
|
@@ -1,137 +1,157 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
import React, { forwardRef, memo } from "react";
|
|
2
|
+
import { Image, NativeModules, Platform, StyleSheet, View, requireNativeComponent } from "react-native";
|
|
3
|
+
//#region src/index.tsx
|
|
5
4
|
const resizeMode = {
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
5
|
+
contain: "contain",
|
|
6
|
+
cover: "cover",
|
|
7
|
+
stretch: "stretch",
|
|
8
|
+
center: "center"
|
|
10
9
|
};
|
|
11
10
|
const priority = {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
11
|
+
low: "low",
|
|
12
|
+
normal: "normal",
|
|
13
|
+
high: "high"
|
|
15
14
|
};
|
|
16
15
|
const cacheControl = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
web: 'web',
|
|
21
|
-
// Only load from cache.
|
|
22
|
-
cacheOnly: 'cacheOnly'
|
|
16
|
+
immutable: "immutable",
|
|
17
|
+
web: "web",
|
|
18
|
+
cacheOnly: "cacheOnly"
|
|
23
19
|
};
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
const resolved = Image.resolveAssetSource(defaultSource);
|
|
33
|
-
|
|
34
|
-
if (resolved) {
|
|
35
|
-
return resolved.uri;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
return null;
|
|
39
|
-
} // iOS or other number mapped assets
|
|
40
|
-
// In iOS the number is passed, and bridged automatically into a UIImage
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
return defaultSource;
|
|
20
|
+
const resolveDefaultSource = (defaultSource) => {
|
|
21
|
+
if (!defaultSource) return null;
|
|
22
|
+
if (Platform.OS === "android") {
|
|
23
|
+
const resolved = Image.resolveAssetSource(defaultSource);
|
|
24
|
+
if (resolved) return resolved.uri;
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
return defaultSource;
|
|
44
28
|
};
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
// eslint-disable-next-line no-shadow
|
|
59
|
-
resizeMode = 'cover',
|
|
60
|
-
forwardedRef,
|
|
61
|
-
...props
|
|
62
|
-
}) {
|
|
63
|
-
if (fallback) {
|
|
64
|
-
const cleanedSource = { ...source
|
|
65
|
-
};
|
|
66
|
-
delete cleanedSource.cache;
|
|
67
|
-
const resolvedSource = Image.resolveAssetSource(cleanedSource);
|
|
68
|
-
return /*#__PURE__*/React.createElement(View, {
|
|
69
|
-
style: [styles.imageContainer, style],
|
|
70
|
-
ref: forwardedRef
|
|
71
|
-
}, /*#__PURE__*/React.createElement(Image, _extends({}, props, {
|
|
72
|
-
style: [StyleSheet.absoluteFill, {
|
|
73
|
-
tintColor
|
|
74
|
-
}],
|
|
75
|
-
source: resolvedSource,
|
|
76
|
-
defaultSource: defaultSource,
|
|
77
|
-
onLoadStart: onLoadStart,
|
|
78
|
-
onProgress: onProgress,
|
|
79
|
-
onLoad: onLoad,
|
|
80
|
-
onError: onError,
|
|
81
|
-
onLoadEnd: onLoadEnd,
|
|
82
|
-
resizeMode: resizeMode
|
|
83
|
-
})), children);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const resolvedSource = Image.resolveAssetSource(source);
|
|
87
|
-
const resolvedDefaultSource = resolveDefaultSource(defaultSource);
|
|
88
|
-
return /*#__PURE__*/React.createElement(View, {
|
|
89
|
-
style: [styles.imageContainer, style],
|
|
90
|
-
ref: forwardedRef
|
|
91
|
-
}, /*#__PURE__*/React.createElement(FastImageView, _extends({}, props, {
|
|
92
|
-
tintColor: tintColor,
|
|
93
|
-
style: StyleSheet.absoluteFill,
|
|
94
|
-
source: resolvedSource,
|
|
95
|
-
defaultSource: resolvedDefaultSource,
|
|
96
|
-
onFastImageLoadStart: onLoadStart,
|
|
97
|
-
onFastImageProgress: onProgress,
|
|
98
|
-
onFastImageLoad: onLoad,
|
|
99
|
-
onFastImageError: onError,
|
|
100
|
-
onFastImageLoadEnd: onLoadEnd,
|
|
101
|
-
resizeMode: resizeMode
|
|
102
|
-
})), children);
|
|
29
|
+
function tintColorFromStyle(style) {
|
|
30
|
+
if (Array.isArray(style)) {
|
|
31
|
+
for (let i = style.length - 1; i >= 0; i--) {
|
|
32
|
+
const found = tintColorFromStyle(style[i]);
|
|
33
|
+
if (found !== void 0) return found;
|
|
34
|
+
}
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (typeof style === "number") {
|
|
38
|
+
const flattened = StyleSheet.flatten(style);
|
|
39
|
+
return flattened ? flattened.tintColor : void 0;
|
|
40
|
+
}
|
|
41
|
+
return style && typeof style === "object" ? style.tintColor : void 0;
|
|
103
42
|
}
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
43
|
+
function loopCount(loop) {
|
|
44
|
+
if (loop === void 0) return -1;
|
|
45
|
+
if (loop === true) return 0;
|
|
46
|
+
if (loop === false) return 1;
|
|
47
|
+
return Number.isFinite(loop) ? Math.max(1, Math.floor(loop)) : 0;
|
|
48
|
+
}
|
|
49
|
+
function withoutCache(source) {
|
|
50
|
+
const { cache: _cache, ...rest } = source || {};
|
|
51
|
+
return rest;
|
|
52
|
+
}
|
|
53
|
+
function FastImageBase({ source, defaultSource, tintColor, onLoadStart, onProgress, onLoad, onError, onLoadEnd, style, fallback, children, resizeMode = "cover", loop, forwardedRef, onLayout, pointerEvents, ...viewProps }) {
|
|
54
|
+
const { onClick, ...props } = viewProps;
|
|
55
|
+
const wrapperProps = {
|
|
56
|
+
onLayout,
|
|
57
|
+
onClick,
|
|
58
|
+
pointerEvents
|
|
59
|
+
};
|
|
60
|
+
const imageProps = {
|
|
61
|
+
...props,
|
|
62
|
+
pointerEvents: pointerEvents === "box-none" ? "none" : void 0
|
|
63
|
+
};
|
|
64
|
+
const resolvedTintColor = tintColor != null ? tintColor : tintColorFromStyle(style);
|
|
65
|
+
if (fallback) {
|
|
66
|
+
const cleanedSource = typeof source === "number" ? source : withoutCache(source);
|
|
67
|
+
const resolvedSource = Image.resolveAssetSource(cleanedSource);
|
|
68
|
+
return /* @__PURE__ */ React.createElement(View, {
|
|
69
|
+
style: [styles.imageContainer, style],
|
|
70
|
+
...wrapperProps,
|
|
71
|
+
ref: forwardedRef
|
|
72
|
+
}, /* @__PURE__ */ React.createElement(Image, {
|
|
73
|
+
...imageProps,
|
|
74
|
+
style: [styles.fallbackImage, { tintColor: resolvedTintColor }],
|
|
75
|
+
source: resolvedSource,
|
|
76
|
+
defaultSource,
|
|
77
|
+
onLoadStart,
|
|
78
|
+
onProgress,
|
|
79
|
+
onLoad,
|
|
80
|
+
onError,
|
|
81
|
+
onLoadEnd,
|
|
82
|
+
resizeMode
|
|
83
|
+
}), children);
|
|
84
|
+
}
|
|
85
|
+
const resolvedSource = Image.resolveAssetSource(source);
|
|
86
|
+
const resolvedDefaultSource = resolveDefaultSource(defaultSource);
|
|
87
|
+
return /* @__PURE__ */ React.createElement(View, {
|
|
88
|
+
style: [styles.imageContainer, style],
|
|
89
|
+
...wrapperProps,
|
|
90
|
+
ref: forwardedRef
|
|
91
|
+
}, /* @__PURE__ */ React.createElement(FastImageView, {
|
|
92
|
+
...imageProps,
|
|
93
|
+
tintColor: resolvedTintColor,
|
|
94
|
+
loopCount: loopCount(loop),
|
|
95
|
+
style: StyleSheet.absoluteFill,
|
|
96
|
+
source: resolvedSource,
|
|
97
|
+
defaultSource: resolvedDefaultSource,
|
|
98
|
+
onFastImageLoadStart: onLoadStart,
|
|
99
|
+
onFastImageProgress: onProgress,
|
|
100
|
+
onFastImageLoad: onLoad,
|
|
101
|
+
onFastImageError: onError,
|
|
102
|
+
onFastImageLoadEnd: onLoadEnd,
|
|
103
|
+
resizeMode
|
|
104
|
+
}), children);
|
|
105
|
+
}
|
|
106
|
+
const FastImageMemo = memo(FastImageBase);
|
|
107
|
+
const FastImageComponent = forwardRef((props, ref) => /* @__PURE__ */ React.createElement(FastImageMemo, {
|
|
108
|
+
forwardedRef: ref,
|
|
109
|
+
...props
|
|
110
|
+
}));
|
|
111
|
+
FastImageComponent.displayName = "FastImage";
|
|
112
|
+
const noResult = {
|
|
113
|
+
ok: false,
|
|
114
|
+
error: "No result"
|
|
115
|
+
};
|
|
110
116
|
const FastImage = FastImageComponent;
|
|
111
117
|
FastImage.resizeMode = resizeMode;
|
|
112
118
|
FastImage.cacheControl = cacheControl;
|
|
113
119
|
FastImage.priority = priority;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
120
|
+
FastImage.preload = (sources) => Promise.resolve(NativeModules.FastImageView.preload(sources.map((s) => s || {}))).then((results) => sources.map((source, i) => {
|
|
121
|
+
var _results$i;
|
|
122
|
+
const uri = source ? source.uri : void 0;
|
|
123
|
+
const result = (_results$i = results === null || results === void 0 ? void 0 : results[i]) !== null && _results$i !== void 0 ? _results$i : noResult;
|
|
124
|
+
if (!result.ok) return {
|
|
125
|
+
...result,
|
|
126
|
+
uri
|
|
127
|
+
};
|
|
128
|
+
return typeof uri === "string" ? {
|
|
129
|
+
...result,
|
|
130
|
+
uri
|
|
131
|
+
} : {
|
|
132
|
+
ok: false,
|
|
133
|
+
error: "Invalid source: no uri",
|
|
134
|
+
uri
|
|
135
|
+
};
|
|
136
|
+
}));
|
|
117
137
|
FastImage.clearMemoryCache = () => NativeModules.FastImageView.clearMemoryCache();
|
|
118
|
-
|
|
119
138
|
FastImage.clearDiskCache = () => NativeModules.FastImageView.clearDiskCache();
|
|
120
|
-
|
|
121
139
|
const styles = StyleSheet.create({
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
onFastImageProgress: true,
|
|
131
|
-
onFastImageLoad: true,
|
|
132
|
-
onFastImageError: true,
|
|
133
|
-
onFastImageLoadEnd: true
|
|
134
|
-
}
|
|
140
|
+
fallbackImage: {
|
|
141
|
+
position: "absolute",
|
|
142
|
+
top: 0,
|
|
143
|
+
left: 0,
|
|
144
|
+
width: "100%",
|
|
145
|
+
height: "100%"
|
|
146
|
+
},
|
|
147
|
+
imageContainer: { overflow: "hidden" }
|
|
135
148
|
});
|
|
136
|
-
|
|
137
|
-
|
|
149
|
+
const FastImageView = requireNativeComponent("FastImageView", FastImage, { nativeOnly: {
|
|
150
|
+
onFastImageLoadStart: true,
|
|
151
|
+
onFastImageProgress: true,
|
|
152
|
+
onFastImageLoad: true,
|
|
153
|
+
onFastImageError: true,
|
|
154
|
+
onFastImageLoadEnd: true
|
|
155
|
+
} });
|
|
156
|
+
//#endregion
|
|
157
|
+
export { FastImage as default };
|
package/dist/index.js.flow
CHANGED
|
@@ -7,9 +7,18 @@ export type OnLoadEvent = SyntheticEvent<
|
|
|
7
7
|
$ReadOnly<{
|
|
8
8
|
width: number,
|
|
9
9
|
height: number,
|
|
10
|
+
// The view's React tag. Missing on Android with the legacy architecture.
|
|
11
|
+
// TODO: make it required once the New Architecture is the minimum.
|
|
12
|
+
target?: number,
|
|
10
13
|
}>,
|
|
11
14
|
>
|
|
12
15
|
|
|
16
|
+
export type OnErrorEvent = SyntheticEvent<
|
|
17
|
+
$ReadOnly<{|
|
|
18
|
+
error: string,
|
|
19
|
+
|}>,
|
|
20
|
+
>
|
|
21
|
+
|
|
13
22
|
export type OnProgressEvent = SyntheticEvent<
|
|
14
23
|
$ReadOnly<{|
|
|
15
24
|
loaded: number,
|
|
@@ -40,7 +49,24 @@ export type ResizeModes = $Values<ResizeMode>
|
|
|
40
49
|
export type Priorities = $Values<Priority>
|
|
41
50
|
export type CacheControls = $Values<CacheControl>
|
|
42
51
|
|
|
43
|
-
export type
|
|
52
|
+
export type PreloadSuccess = {|
|
|
53
|
+
uri: string,
|
|
54
|
+
ok: true,
|
|
55
|
+
width: number,
|
|
56
|
+
height: number,
|
|
57
|
+
|}
|
|
58
|
+
|
|
59
|
+
export type PreloadFailure = {|
|
|
60
|
+
uri?: string,
|
|
61
|
+
ok: false,
|
|
62
|
+
error: string,
|
|
63
|
+
|}
|
|
64
|
+
|
|
65
|
+
export type PreloadResult = PreloadSuccess | PreloadFailure
|
|
66
|
+
|
|
67
|
+
export type PreloadFn = (
|
|
68
|
+
sources: Array<FastImageSource>,
|
|
69
|
+
) => Promise<Array<PreloadResult>>
|
|
44
70
|
export type FastImageSource = {
|
|
45
71
|
uri?: string,
|
|
46
72
|
headers?: Object,
|
|
@@ -50,7 +76,7 @@ export type FastImageSource = {
|
|
|
50
76
|
|
|
51
77
|
export type FastImageProps = $ReadOnly<{|
|
|
52
78
|
...ViewProps,
|
|
53
|
-
onError?: ?() => void,
|
|
79
|
+
onError?: ?(event: OnErrorEvent) => void,
|
|
54
80
|
onLoad?: ?(event: OnLoadEvent) => void,
|
|
55
81
|
onLoadEnd?: ?() => void,
|
|
56
82
|
onLoadStart?: ?() => void,
|
|
@@ -62,6 +88,11 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
62
88
|
tintColor?: number | string,
|
|
63
89
|
resizeMode?: ?ResizeModes,
|
|
64
90
|
fallback?: ?boolean,
|
|
91
|
+
recyclingKey?: ?string,
|
|
92
|
+
loop?: ?(boolean | number),
|
|
93
|
+
imageRendering?: ?('auto' | 'smooth' | 'pixelated'),
|
|
94
|
+
paused?: ?boolean,
|
|
95
|
+
downsample?: ?boolean,
|
|
65
96
|
testID?: ?string,
|
|
66
97
|
|}>
|
|
67
98
|
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#import <SDWebImage/SDAnimatedImage.h>
|
|
2
|
+
#import <SDWebImage/SDWebImageDefine.h>
|
|
3
|
+
|
|
4
|
+
// An image decoded at about the size it's shown at (the `downsample`
|
|
5
|
+
// prop), instead of at full size. The view asks for it with SDWebImage's
|
|
6
|
+
// thumbnail context: the box to fill (the view's size in pixels), and
|
|
7
|
+
// preserveAspectRatio NO when the image has to cover the box (resizeMode
|
|
8
|
+
// cover or stretch) rather than fit in it (contain or center). SDWebImage's
|
|
9
|
+
// own thumbnail decoding can only fit, so this class works out the size from
|
|
10
|
+
// the image's header before it decodes, as React Native's Image does.
|
|
11
|
+
@interface FFFDownsampledImage : SDAnimatedImage
|
|
12
|
+
|
|
13
|
+
// The url to load a source's url with, when it's downsampled.
|
|
14
|
+
+ (NSURL*) loadURLForURL: (NSURL*)url;
|
|
15
|
+
|
|
16
|
+
// Sets a load's context to decode the image for a box (in pixels) to cover
|
|
17
|
+
// or fit in.
|
|
18
|
+
+ (void) addToContext: (SDWebImageMutableContext*)context forURL: (NSURL*)url box: (CGSize)box cover: (BOOL)cover;
|
|
19
|
+
|
|
20
|
+
// The size of the full image (in points, like UIImage's size) that an image
|
|
21
|
+
// loaded this way was decoded from, for onLoad, or zero for other images.
|
|
22
|
+
+ (CGSize) sourceSizeOfImage: (UIImage*)image;
|
|
23
|
+
|
|
24
|
+
// Whether this version of SDWebImage can decode through this class: it
|
|
25
|
+
// decodes static images through the animated image class since 5.19.
|
|
26
|
+
+ (BOOL) isSupported;
|
|
27
|
+
|
|
28
|
+
@end
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
#import "FFFDownsampledImage.h"
|
|
2
|
+
#import <ImageIO/ImageIO.h>
|
|
3
|
+
#import <SDWebImage/SDImageCoder.h>
|
|
4
|
+
#import <SDWebImage/SDImageCacheDefine.h>
|
|
5
|
+
#import <SDWebImage/SDWebImageCacheKeyFilter.h>
|
|
6
|
+
#import <SDWebImage/UIImage+Metadata.h>
|
|
7
|
+
|
|
8
|
+
// The decode option that carries the full image's size to the view.
|
|
9
|
+
static SDImageCoderOption const FFFDecodeSourceSize = @"FFFDecodeSourceSize";
|
|
10
|
+
// Added to the url's fragment, which isn't sent to the server.
|
|
11
|
+
static NSString* const FFFDownsampledFragment = @"fastimage-downsampled";
|
|
12
|
+
|
|
13
|
+
// The image's size in pixels as it's shown (EXIF orientations 5 to 8 turn it
|
|
14
|
+
// sideways), from its header, or zero if ImageIO can't read it.
|
|
15
|
+
static CGSize FFFPixelSize(NSData* data) {
|
|
16
|
+
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef) data, NULL);
|
|
17
|
+
if (!source) {
|
|
18
|
+
return CGSizeZero;
|
|
19
|
+
}
|
|
20
|
+
NSDictionary* properties = (__bridge_transfer NSDictionary*) CGImageSourceCopyPropertiesAtIndex(source, 0, NULL);
|
|
21
|
+
CFRelease(source);
|
|
22
|
+
CGFloat width = [properties[(__bridge NSString*) kCGImagePropertyPixelWidth] doubleValue];
|
|
23
|
+
CGFloat height = [properties[(__bridge NSString*) kCGImagePropertyPixelHeight] doubleValue];
|
|
24
|
+
NSUInteger orientation = [properties[(__bridge NSString*) kCGImagePropertyOrientation] unsignedIntegerValue];
|
|
25
|
+
if (orientation >= kCGImagePropertyOrientationLeftMirrored) {
|
|
26
|
+
return CGSizeMake(height, width);
|
|
27
|
+
}
|
|
28
|
+
return CGSizeMake(width, height);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Where the decode puts the full image's size, passed in the load's decode
|
|
32
|
+
// options. SDWebImage attaches those options to the image it ends up with
|
|
33
|
+
// (sd_decodeOptions), also when its force decoding replaces the decoded image
|
|
34
|
+
// with another one, which drops anything kept on the image itself.
|
|
35
|
+
@interface FFFSourceSize : NSObject
|
|
36
|
+
@property (atomic, assign) CGSize size;
|
|
37
|
+
@end
|
|
38
|
+
|
|
39
|
+
@implementation FFFSourceSize
|
|
40
|
+
|
|
41
|
+
// Any two are equal: it's where the result goes, not part of the request, so
|
|
42
|
+
// the same request from several views is still decoded once (SDWebImage's
|
|
43
|
+
// downloader groups them by their decode options).
|
|
44
|
+
- (BOOL) isEqual: (id)object {
|
|
45
|
+
return [object isKindOfClass: [FFFSourceSize class]];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
- (NSUInteger) hash {
|
|
49
|
+
return 0;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
@end
|
|
53
|
+
|
|
54
|
+
@implementation FFFDownsampledImage
|
|
55
|
+
|
|
56
|
+
+ (BOOL) isSupported {
|
|
57
|
+
static BOOL supported = NO;
|
|
58
|
+
static dispatch_once_t once;
|
|
59
|
+
dispatch_once(&once, ^{
|
|
60
|
+
// No animated image coder takes JPEG, so before SDWebImage 5.19
|
|
61
|
+
// SDAnimatedImage returned nil for one, and SDWebImage decoded it with
|
|
62
|
+
// its thumbnail options as they are (stretched to the box).
|
|
63
|
+
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
|
|
64
|
+
CGContextRef context = CGBitmapContextCreate(NULL, 1, 1, 8, 4, colorSpace, kCGImageAlphaNoneSkipLast);
|
|
65
|
+
CGColorSpaceRelease(colorSpace);
|
|
66
|
+
CGImageRef pixel = context ? CGBitmapContextCreateImage(context) : NULL;
|
|
67
|
+
if (context) {
|
|
68
|
+
CGContextRelease(context);
|
|
69
|
+
}
|
|
70
|
+
if (!pixel) {
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
NSMutableData* jpeg = [NSMutableData data];
|
|
74
|
+
CGImageDestinationRef destination = CGImageDestinationCreateWithData((__bridge CFMutableDataRef) jpeg, CFSTR("public.jpeg"), 1, NULL);
|
|
75
|
+
if (destination) {
|
|
76
|
+
CGImageDestinationAddImage(destination, pixel, NULL);
|
|
77
|
+
CGImageDestinationFinalize(destination);
|
|
78
|
+
CFRelease(destination);
|
|
79
|
+
}
|
|
80
|
+
CGImageRelease(pixel);
|
|
81
|
+
supported = jpeg.length > 0 && [[SDAnimatedImage alloc] initWithData: jpeg scale: 1 options: nil] != nil;
|
|
82
|
+
});
|
|
83
|
+
return supported;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
+ (NSURL*) loadURLForURL: (NSURL*)url {
|
|
87
|
+
// SDWebImage's downloader shares a download between loads of the same
|
|
88
|
+
// url, and decodes each load's image with the first load's image class.
|
|
89
|
+
// After a load that isn't downsampled (e.g. a preload), this class
|
|
90
|
+
// wouldn't be used, and the box would be decoded as SDWebImage does (to
|
|
91
|
+
// fill it, stretched). With its own url, a downsampled load only shares
|
|
92
|
+
// downloads with other downsampled ones.
|
|
93
|
+
NSURLComponents* components = [NSURLComponents componentsWithURL: url resolvingAgainstBaseURL: NO];
|
|
94
|
+
if (!components) {
|
|
95
|
+
return url;
|
|
96
|
+
}
|
|
97
|
+
components.fragment = components.fragment.length > 0
|
|
98
|
+
? [components.fragment stringByAppendingFormat: @"-%@", FFFDownsampledFragment]
|
|
99
|
+
: FFFDownsampledFragment;
|
|
100
|
+
return components.URL ?: url;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
+ (void) addToContext: (SDWebImageMutableContext*)context forURL: (NSURL*)url box: (CGSize)box cover: (BOOL)cover {
|
|
104
|
+
context[SDWebImageContextAnimatedImageClass] = [FFFDownsampledImage class];
|
|
105
|
+
// Cached under the url itself (see loadURLForURL:), so the disk cache
|
|
106
|
+
// keeps one download for every size and for loads that aren't downsampled.
|
|
107
|
+
NSString* key = url.absoluteString;
|
|
108
|
+
context[SDWebImageContextCacheKeyFilter] = [SDWebImageCacheKeyFilter cacheKeyFilterWithBlock: ^NSString* (NSURL* _Nonnull loadURL) {
|
|
109
|
+
return key;
|
|
110
|
+
}];
|
|
111
|
+
// The memory cache key is the url with these (SDWebImage's thumbnail
|
|
112
|
+
// key), so views of other sizes don't get this image.
|
|
113
|
+
context[SDWebImageContextImageThumbnailPixelSize] = [NSValue valueWithCGSize: box];
|
|
114
|
+
context[SDWebImageContextImagePreserveAspectRatio] = @(!cover);
|
|
115
|
+
context[SDWebImageContextImageDecodeOptions] = @{FFFDecodeSourceSize: [FFFSourceSize new]};
|
|
116
|
+
// Only in memory: the disk cache has the downloaded file.
|
|
117
|
+
context[SDWebImageContextStoreCacheType] = @(SDImageCacheTypeMemory);
|
|
118
|
+
context[SDWebImageContextQueryCacheType] = @(SDImageCacheTypeMemory);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
+ (CGSize) sourceSizeOfImage: (UIImage*)image {
|
|
122
|
+
id sourceSize = image.sd_decodeOptions[FFFDecodeSourceSize];
|
|
123
|
+
return [sourceSize isKindOfClass: [FFFSourceSize class]] ? ((FFFSourceSize*) sourceSize).size : CGSizeZero;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
- (instancetype) initWithData: (NSData*)data scale: (CGFloat)scale options: (SDImageCoderOptions*)options {
|
|
127
|
+
NSValue* boxValue = options[SDImageCoderDecodeThumbnailPixelSize];
|
|
128
|
+
CGSize box = boxValue ? boxValue.CGSizeValue : CGSizeZero;
|
|
129
|
+
NSNumber* preserveAspectRatio = options[SDImageCoderDecodePreserveAspectRatio];
|
|
130
|
+
BOOL cover = preserveAspectRatio != nil && !preserveAspectRatio.boolValue;
|
|
131
|
+
CGSize pixelSize = FFFPixelSize(data);
|
|
132
|
+
|
|
133
|
+
// Decode at full size unless the image is at least twice as large as it
|
|
134
|
+
// needs to be (or its size can't be read). Decoding smaller is done in
|
|
135
|
+
// software, while newer iPhones decode JPEG and HEIC at full size in
|
|
136
|
+
// hardware: on an iPhone 15 Pro Max a 12 MP JPEG took about 20 ms at full
|
|
137
|
+
// size, and 120-130 ms with a peak of 100-125 MB decoded to 60-90% of its
|
|
138
|
+
// size (1-6 MB and 27-40 ms for a thumbnail).
|
|
139
|
+
NSMutableDictionary* decodeOptions = options ? [options mutableCopy] : [NSMutableDictionary dictionary];
|
|
140
|
+
[decodeOptions removeObjectForKey: SDImageCoderDecodeThumbnailPixelSize];
|
|
141
|
+
decodeOptions[SDImageCoderDecodePreserveAspectRatio] = @YES;
|
|
142
|
+
if (box.width > 0 && box.height > 0 && pixelSize.width > 0 && pixelSize.height > 0) {
|
|
143
|
+
CGFloat widthRatio = box.width / pixelSize.width;
|
|
144
|
+
CGFloat heightRatio = box.height / pixelSize.height;
|
|
145
|
+
CGFloat ratio = cover ? MAX(widthRatio, heightRatio) : MIN(widthRatio, heightRatio);
|
|
146
|
+
if (ratio <= 0.5) {
|
|
147
|
+
// ImageIO takes the longest side (kCGImageSourceThumbnailMaxPixelSize).
|
|
148
|
+
// SDWebImage works it out from the box with the image's stored
|
|
149
|
+
// width and height, before EXIF orientation, so a square box is
|
|
150
|
+
// the one that gives this side for any orientation.
|
|
151
|
+
CGFloat longest = ceil(MAX(pixelSize.width, pixelSize.height) * ratio);
|
|
152
|
+
decodeOptions[SDImageCoderDecodeThumbnailPixelSize] = [NSValue valueWithCGSize: CGSizeMake(longest, longest)];
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
self = [super initWithData: data scale: scale options: decodeOptions];
|
|
157
|
+
if (self) {
|
|
158
|
+
CGFloat imageScale = MAX(scale, 1);
|
|
159
|
+
FFFSourceSize* sourceSize = options[FFFDecodeSourceSize];
|
|
160
|
+
if ([sourceSize isKindOfClass: [FFFSourceSize class]]) {
|
|
161
|
+
sourceSize.size = pixelSize.width > 0 && pixelSize.height > 0
|
|
162
|
+
? CGSizeMake(pixelSize.width / imageScale, pixelSize.height / imageScale)
|
|
163
|
+
: self.size;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return self;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#import <Foundation/Foundation.h>
|
|
2
2
|
#import <UIKit/UIKit.h>
|
|
3
|
+
#import <SDWebImage/SDWebImageDownloaderRequestModifier.h>
|
|
3
4
|
|
|
4
5
|
typedef NS_ENUM(NSInteger, FFFPriority) {
|
|
5
6
|
FFFPriorityLow,
|
|
@@ -20,6 +21,8 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
|
|
|
20
21
|
@property (nonatomic) NSURL* url;
|
|
21
22
|
// priority for image request
|
|
22
23
|
@property (nonatomic) FFFPriority priority;
|
|
24
|
+
// whether the source set a priority (preload is low priority otherwise)
|
|
25
|
+
@property (nonatomic) BOOL hasPriority;
|
|
23
26
|
// headers for the image request
|
|
24
27
|
@property (nonatomic) NSDictionary *headers;
|
|
25
28
|
// cache control mode
|
|
@@ -30,4 +33,7 @@ typedef NS_ENUM(NSInteger, FFFCacheControl) {
|
|
|
30
33
|
headers:(NSDictionary *)headers
|
|
31
34
|
cacheControl:(FFFCacheControl)cacheControl;
|
|
32
35
|
|
|
36
|
+
// Adds this source's headers to its image requests.
|
|
37
|
+
- (SDWebImageDownloaderRequestModifier *)requestModifier;
|
|
38
|
+
|
|
33
39
|
@end
|
|
@@ -17,4 +17,17 @@
|
|
|
17
17
|
return self;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
- (SDWebImageDownloaderRequestModifier *)requestModifier
|
|
21
|
+
{
|
|
22
|
+
NSDictionary* headers = _headers;
|
|
23
|
+
return [SDWebImageDownloaderRequestModifier requestModifierWithBlock: ^NSURLRequest* _Nullable (NSURLRequest* _Nonnull request) {
|
|
24
|
+
NSMutableURLRequest* mutableRequest = [request mutableCopy];
|
|
25
|
+
for (NSString* header in headers) {
|
|
26
|
+
NSString* value = headers[header];
|
|
27
|
+
[mutableRequest setValue: value forHTTPHeaderField: header];
|
|
28
|
+
}
|
|
29
|
+
return [mutableRequest copy];
|
|
30
|
+
}];
|
|
31
|
+
}
|
|
32
|
+
|
|
20
33
|
@end
|
|
@@ -19,6 +19,23 @@
|
|
|
19
19
|
@property (nonatomic, strong) FFFastImageSource *source;
|
|
20
20
|
@property (nonatomic, strong) UIImage *defaultSource;
|
|
21
21
|
@property (nonatomic, strong) UIColor *imageColor;
|
|
22
|
+
// When it changes, the next image doesn't replace the current one: the view
|
|
23
|
+
// clears first (for views reused for other content, like list rows).
|
|
24
|
+
@property (nonatomic, copy) NSString *recyclingKey;
|
|
25
|
+
// How many times animated images play: -1 for the file's own loop count (the
|
|
26
|
+
// `loop` prop not set), 0 for forever, or a number of times.
|
|
27
|
+
@property (nonatomic, assign) NSInteger loopCount;
|
|
28
|
+
// How the image is filtered when drawn at another size: auto, smooth or
|
|
29
|
+
// pixelated.
|
|
30
|
+
@property (nonatomic, copy) NSString *imageRendering;
|
|
31
|
+
// Pauses animated images on the frame they're showing.
|
|
32
|
+
@property (nonatomic, assign) BOOL paused;
|
|
33
|
+
// Decodes images at about the view's size instead of at full size.
|
|
34
|
+
@property (nonatomic, assign) BOOL downsample;
|
|
22
35
|
|
|
23
36
|
@end
|
|
24
37
|
|
|
38
|
+
// An error's description, with the HTTP status code when there is one.
|
|
39
|
+
FOUNDATION_EXTERN NSString *FFFErrorMessage(NSError *error);
|
|
40
|
+
|
|
41
|
+
|