@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.
Files changed (36) hide show
  1. package/README.md +2 -2
  2. package/RNFastImage.podspec +5 -2
  3. package/android/build.gradle +13 -0
  4. package/android/src/main/AndroidManifestNew.xml +3 -0
  5. package/android/src/main/java/com/dylanvann/fastimage/FastImageCookieHandler.java +84 -0
  6. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvent.java +45 -0
  7. package/android/src/main/java/com/dylanvann/fastimage/FastImageEvents.java +60 -0
  8. package/android/src/main/java/com/dylanvann/fastimage/FastImageGif.java +110 -0
  9. package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +65 -5
  10. package/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +65 -22
  11. package/android/src/main/java/com/dylanvann/fastimage/FastImageShadowNode.java +21 -0
  12. package/android/src/main/java/com/dylanvann/fastimage/FastImageSource.java +21 -1
  13. package/android/src/main/java/com/dylanvann/fastimage/FastImageSourceSize.java +237 -0
  14. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewConverter.java +14 -5
  15. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewManager.java +66 -22
  16. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewModule.java +130 -24
  17. package/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +359 -39
  18. package/android/src/main/java/com/dylanvann/fastimage/FastImageWebGlideUrl.java +13 -0
  19. package/changes.json +1 -1
  20. package/dist/index.cjs.js +166 -130
  21. package/dist/index.cjs.js.flow +33 -2
  22. package/dist/index.d.ts +77 -22
  23. package/dist/index.js +141 -121
  24. package/dist/index.js.flow +33 -2
  25. package/ios/FastImage/FFFDownsampledImage.h +28 -0
  26. package/ios/FastImage/FFFDownsampledImage.m +169 -0
  27. package/ios/FastImage/FFFastImageSource.h +6 -0
  28. package/ios/FastImage/FFFastImageSource.m +13 -0
  29. package/ios/FastImage/FFFastImageView.h +17 -0
  30. package/ios/FastImage/FFFastImageView.m +498 -71
  31. package/ios/FastImage/FFFastImageViewManager.m +106 -8
  32. package/ios/FastImage/RCTConvert+FFFastImage.m +4 -1
  33. package/package.json +31 -43
  34. package/dist/index.d.ts.map +0 -1
  35. package/dist/index.test.d.ts +0 -2
  36. package/dist/index.test.d.ts.map +0 -1
package/dist/index.js CHANGED
@@ -1,137 +1,157 @@
1
- import _extends from '@babel/runtime/helpers/extends';
2
- import React, { forwardRef, memo } from 'react';
3
- import { NativeModules, StyleSheet, requireNativeComponent, Image, View, Platform } from 'react-native';
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
- contain: 'contain',
7
- cover: 'cover',
8
- stretch: 'stretch',
9
- center: 'center'
5
+ contain: "contain",
6
+ cover: "cover",
7
+ stretch: "stretch",
8
+ center: "center"
10
9
  };
11
10
  const priority = {
12
- low: 'low',
13
- normal: 'normal',
14
- high: 'high'
11
+ low: "low",
12
+ normal: "normal",
13
+ high: "high"
15
14
  };
16
15
  const cacheControl = {
17
- // Ignore headers, use uri as cache key, fetch only if not in cache.
18
- immutable: 'immutable',
19
- // Respect http headers, no aggressive caching.
20
- web: 'web',
21
- // Only load from cache.
22
- cacheOnly: 'cacheOnly'
16
+ immutable: "immutable",
17
+ web: "web",
18
+ cacheOnly: "cacheOnly"
23
19
  };
24
-
25
- const resolveDefaultSource = defaultSource => {
26
- if (!defaultSource) {
27
- return null;
28
- }
29
-
30
- if (Platform.OS === 'android') {
31
- // Android receives a URI string, and resolves into a Drawable using RN's methods.
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
- function FastImageBase({
47
- source,
48
- defaultSource,
49
- tintColor,
50
- onLoadStart,
51
- onProgress,
52
- onLoad,
53
- onError,
54
- onLoadEnd,
55
- style,
56
- fallback,
57
- children,
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
- const FastImageMemo = /*#__PURE__*/memo(FastImageBase);
106
- const FastImageComponent = /*#__PURE__*/forwardRef((props, ref) => /*#__PURE__*/React.createElement(FastImageMemo, _extends({
107
- forwardedRef: ref
108
- }, props)));
109
- FastImageComponent.displayName = 'FastImage';
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
- FastImage.preload = sources => NativeModules.FastImageView.preload(sources);
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
- imageContainer: {
123
- overflow: 'hidden'
124
- }
125
- }); // Types of requireNativeComponent are not correct.
126
-
127
- const FastImageView = requireNativeComponent('FastImageView', FastImage, {
128
- nativeOnly: {
129
- onFastImageLoadStart: true,
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
- export default FastImage;
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 };
@@ -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 PreloadFn = (sources: Array<FastImageSource>) => void
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
+