```
+It's also recommend to use the module import syntax, available for CocoaPods(enable `modular_headers`)/Carthage/SwiftPM.
+
+```objecitivec
+@import SDWebImage;
+```
+
### Build Project
At this point your workspace should build without error. If you are having problem, post to the Issue and the
@@ -267,6 +317,8 @@ All source code is licensed under the [MIT License](https://github.com/SDWebImag
## Architecture
+To learn about SDWebImage's architecture design for contribution, read [The Core of SDWebImage v5.6 Architecture](https://github.com/SDWebImage/SDWebImage/wiki/5.6-Code-Architecture-Analysis). Thanks @looseyi for the post and translation.
+
#### High Level Diagram
diff --git a/SDWebImage.podspec b/SDWebImage.podspec
index abe5f7c7..f84fb4a3 100644
--- a/SDWebImage.podspec
+++ b/SDWebImage.podspec
@@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'SDWebImage'
- s.version = '5.3.1'
+ s.version = '5.7.3'
s.osx.deployment_target = '10.10'
s.ios.deployment_target = '8.0'
@@ -33,7 +33,6 @@ Pod::Spec.new do |s|
s.subspec 'Core' do |core|
core.source_files = 'SDWebImage/Core/*.{h,m}', 'WebImage/SDWebImage.h', 'SDWebImage/Private/*.{h,m}'
- core.exclude_files = 'SDWebImage/MapKit/*.{h,m}'
core.private_header_files = 'SDWebImage/Private/*.h'
end
@@ -41,7 +40,7 @@ Pod::Spec.new do |s|
mk.osx.deployment_target = '10.10'
mk.ios.deployment_target = '8.0'
mk.tvos.deployment_target = '9.2'
- mk.source_files = 'SDWebImage/MapKit/*.{h,m}'
+ mk.source_files = 'SDWebImageMapKit/MapKit/*.{h,m}'
mk.framework = 'MapKit'
mk.dependency 'SDWebImage/Core'
end
diff --git a/SDWebImage.xcodeproj/project.pbxproj b/SDWebImage.xcodeproj/project.pbxproj
index 1174fe97..43516df0 100644
--- a/SDWebImage.xcodeproj/project.pbxproj
+++ b/SDWebImage.xcodeproj/project.pbxproj
@@ -45,9 +45,16 @@
321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */ = {isa = PBXBuildFile; fileRef = 321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */; };
3237F9E820161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
3237F9EB20161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
+ 3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */; };
+ 3240BB6823968FE7003BA07D /* SDAssociatedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 3240BB6623968FE6003BA07D /* SDAssociatedObject.h */; settings = {ATTRIBUTES = (Private, ); }; };
+ 3240BB6923968FE7003BA07D /* SDAssociatedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 3240BB6723968FE6003BA07D /* SDAssociatedObject.m */; };
+ 3240BB6A23968FE7003BA07D /* SDAssociatedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 3240BB6723968FE6003BA07D /* SDAssociatedObject.m */; };
3244062C2296C5F400A36084 /* SDWebImageOptionsProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */; };
3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */; };
+ 3246A70323A567AC00FBEA10 /* SDGraphicsImageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 3246A70423A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */; };
+ 3246A70523A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */; };
3248475D201775F600AF9E5A /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32484757201775F600AF9E5A /* SDAnimatedImageView.m */; };
3248475F201775F600AF9E5A /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32484757201775F600AF9E5A /* SDAnimatedImageView.m */; };
32484765201775F600AF9E5A /* SDAnimatedImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 32484758201775F600AF9E5A /* SDAnimatedImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
@@ -83,12 +90,18 @@
325C460F223394D8004CAE11 /* SDImageCachesManagerOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */; settings = {ATTRIBUTES = (Private, ); }; };
325C4610223394D8004CAE11 /* SDImageCachesManagerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */; };
325C4611223394D8004CAE11 /* SDImageCachesManagerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */; };
- 325C46212233A02E004CAE11 /* UIColor+HexString.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C461E2233A02E004CAE11 /* UIColor+HexString.h */; settings = {ATTRIBUTES = (Private, ); }; };
- 325C46222233A02E004CAE11 /* UIColor+HexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C461F2233A02E004CAE11 /* UIColor+HexString.m */; };
- 325C46232233A02E004CAE11 /* UIColor+HexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C461F2233A02E004CAE11 /* UIColor+HexString.m */; };
- 325C46272233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */; settings = {ATTRIBUTES = (Private, ); }; };
- 325C46282233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */; };
- 325C46292233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */; };
+ 325C46212233A02E004CAE11 /* UIColor+SDHexString.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */; settings = {ATTRIBUTES = (Private, ); }; };
+ 325C46222233A02E004CAE11 /* UIColor+SDHexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C461F2233A02E004CAE11 /* UIColor+SDHexString.m */; };
+ 325C46232233A02E004CAE11 /* UIColor+SDHexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C461F2233A02E004CAE11 /* UIColor+SDHexString.m */; };
+ 325C46272233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C46242233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h */; settings = {ATTRIBUTES = (Private, ); }; };
+ 325C46282233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m */; };
+ 325C46292233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m */; };
+ 325F7CC623893B2E00AEDFCC /* SDFileAttributeHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */; settings = {ATTRIBUTES = (Private, ); }; };
+ 325F7CC723893B2E00AEDFCC /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */; };
+ 325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */; };
+ 325F7CCC2389463D00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */; };
+ 325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */; };
326E2F2E236F0B23006F847F /* SDAnimatedImagePlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 326E2F2C236F0B23006F847F /* SDAnimatedImagePlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
326E2F2F236F0B23006F847F /* SDAnimatedImagePlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */; };
326E2F30236F0B23006F847F /* SDAnimatedImagePlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */; };
@@ -99,6 +112,8 @@
327054D6206CD8B3006EA328 /* SDImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 327054D2206CD8B3006EA328 /* SDImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
327054DA206CD8B3006EA328 /* SDImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDImageAPNGCoder.m */; };
327054DC206CD8B3006EA328 /* SDImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDImageAPNGCoder.m */; };
+ 3287E6D1244C0C1400007311 /* MKAnnotationView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 3287E6CD244C0C1400007311 /* MKAnnotationView+WebCache.m */; };
+ 3287E6D2244C0C1400007311 /* MKAnnotationView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 3287E6CE244C0C1400007311 /* MKAnnotationView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
328BB69E2081FED200760D6C /* SDWebImageCacheKeyFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 328BB69A2081FED200760D6C /* SDWebImageCacheKeyFilter.h */; settings = {ATTRIBUTES = (Public, ); }; };
328BB6A22081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */; };
328BB6A42081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */; };
@@ -111,6 +126,7 @@
328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 328BB6BF2082581100760D6C /* SDMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
328BB6D32082581100760D6C /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB6C02082581100760D6C /* SDMemoryCache.m */; };
328BB6D52082581100760D6C /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB6C02082581100760D6C /* SDMemoryCache.m */; };
+ 328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */; };
3290FA061FA478AF0047D20C /* SDImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDImageFrame.m */; };
3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDImageFrame.m */; };
@@ -274,8 +290,6 @@
80B6DF822142B44400BCB334 /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */; };
80B6DF832142B44500BCB334 /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */; };
80B6DF842142B44600BCB334 /* NSButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
- 80B6DFA72142B71600BCB334 /* MKAnnotationView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FDE87A2088871B008D7530 /* MKAnnotationView+WebCache.m */; };
- 80B6DFCD2142B71600BCB334 /* MKAnnotationView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 32FDE87B2088871B008D7530 /* MKAnnotationView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CC6172DC28500419892 /* UIImage+GIF.m */; };
AB615306192DA24600A2D8E9 /* UIView+WebCacheOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AB615302192DA24600A2D8E9 /* UIView+WebCacheOperation.m */; };
ABBE71A818C43B4D00B75E91 /* UIImageView+HighlightedWebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = ABBE71A618C43B4D00B75E91 /* UIImageView+HighlightedWebCache.m */; };
@@ -298,6 +312,8 @@
dstPath = include/SDWebImage;
dstSubfolderSpec = 16;
files = (
+ 328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */,
+ 325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */,
326E2F36236F1E30006F847F /* SDAnimatedImagePlayer.h in Copy Headers */,
3250C9F12355E3DF0093A896 /* SDWebImageDownloaderDecryptor.h in Copy Headers */,
325427662355783C0042BAA4 /* SDWebImageDownloaderResponseModifier.h in Copy Headers */,
@@ -379,8 +395,12 @@
321E60A11F38E8F600405457 /* SDImageGIFCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageGIFCoder.m; path = Core/SDImageGIFCoder.m; sourceTree = ""; };
321E60BC1F38E91700405457 /* UIImage+ForceDecode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+ForceDecode.h"; path = "Core/UIImage+ForceDecode.h"; sourceTree = ""; };
321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ForceDecode.m"; path = "Core/UIImage+ForceDecode.m"; sourceTree = ""; };
+ 3240BB6623968FE6003BA07D /* SDAssociatedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDAssociatedObject.h; sourceTree = ""; };
+ 3240BB6723968FE6003BA07D /* SDAssociatedObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAssociatedObject.m; sourceTree = ""; };
324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageOptionsProcessor.h; path = Core/SDWebImageOptionsProcessor.h; sourceTree = ""; };
3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOptionsProcessor.m; path = Core/SDWebImageOptionsProcessor.m; sourceTree = ""; };
+ 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDGraphicsImageRenderer.h; path = Core/SDGraphicsImageRenderer.h; sourceTree = ""; };
+ 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDGraphicsImageRenderer.m; path = Core/SDGraphicsImageRenderer.m; sourceTree = ""; };
32484757201775F600AF9E5A /* SDAnimatedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageView.m; path = Core/SDAnimatedImageView.m; sourceTree = ""; };
32484758201775F600AF9E5A /* SDAnimatedImageView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SDAnimatedImageView+WebCache.h"; path = "Core/SDAnimatedImageView+WebCache.h"; sourceTree = ""; };
32484759201775F600AF9E5A /* SDAnimatedImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageView.h; path = Core/SDAnimatedImageView.h; sourceTree = ""; };
@@ -403,16 +423,22 @@
325C460722339426004CAE11 /* SDWeakProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWeakProxy.m; sourceTree = ""; };
325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDImageCachesManagerOperation.h; sourceTree = ""; };
325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDImageCachesManagerOperation.m; sourceTree = ""; };
- 325C461E2233A02E004CAE11 /* UIColor+HexString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+HexString.h"; sourceTree = ""; };
- 325C461F2233A02E004CAE11 /* UIColor+HexString.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+HexString.m"; sourceTree = ""; };
- 325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSBezierPath+RoundedCorners.h"; sourceTree = ""; };
- 325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSBezierPath+RoundedCorners.m"; sourceTree = ""; };
+ 325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+SDHexString.h"; sourceTree = ""; };
+ 325C461F2233A02E004CAE11 /* UIColor+SDHexString.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+SDHexString.m"; sourceTree = ""; };
+ 325C46242233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSBezierPath+SDRoundedCorners.h"; sourceTree = ""; };
+ 325C46252233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSBezierPath+SDRoundedCorners.m"; sourceTree = ""; };
+ 325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDFileAttributeHelper.h; sourceTree = ""; };
+ 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDFileAttributeHelper.m; sourceTree = ""; };
+ 325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIImage+ExtendedCacheData.h"; path = "Core/UIImage+ExtendedCacheData.h"; sourceTree = ""; };
+ 325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ExtendedCacheData.m"; path = "Core/UIImage+ExtendedCacheData.m"; sourceTree = ""; };
326E2F2C236F0B23006F847F /* SDAnimatedImagePlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImagePlayer.h; path = Core/SDAnimatedImagePlayer.h; sourceTree = ""; };
326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImagePlayer.m; path = Core/SDAnimatedImagePlayer.m; sourceTree = ""; };
326E2F31236F1D58006F847F /* SDDeviceHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDDeviceHelper.h; sourceTree = ""; };
326E2F32236F1D58006F847F /* SDDeviceHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDDeviceHelper.m; sourceTree = ""; };
327054D2206CD8B3006EA328 /* SDImageAPNGCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDImageAPNGCoder.h; path = Core/SDImageAPNGCoder.h; sourceTree = ""; };
327054D3206CD8B3006EA328 /* SDImageAPNGCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageAPNGCoder.m; path = Core/SDImageAPNGCoder.m; sourceTree = ""; };
+ 3287E6CD244C0C1400007311 /* MKAnnotationView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MKAnnotationView+WebCache.m"; sourceTree = ""; };
+ 3287E6CE244C0C1400007311 /* MKAnnotationView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MKAnnotationView+WebCache.h"; sourceTree = ""; };
328BB69A2081FED200760D6C /* SDWebImageCacheKeyFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageCacheKeyFilter.h; path = Core/SDWebImageCacheKeyFilter.h; sourceTree = ""; };
328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageCacheKeyFilter.m; path = Core/SDWebImageCacheKeyFilter.m; sourceTree = ""; };
328BB6A82081FEE500760D6C /* SDWebImageCacheSerializer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageCacheSerializer.h; path = Core/SDWebImageCacheSerializer.h; sourceTree = ""; };
@@ -456,8 +482,6 @@
32F7C06E2030114C00873181 /* SDImageTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDImageTransformer.m; path = Core/SDImageTransformer.m; sourceTree = ""; };
32F7C07C2030719600873181 /* UIImage+Transform.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Transform.m"; path = "Core/UIImage+Transform.m"; sourceTree = ""; };
32F7C07D2030719600873181 /* UIImage+Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+Transform.h"; path = "Core/UIImage+Transform.h"; sourceTree = ""; };
- 32FDE87A2088871B008D7530 /* MKAnnotationView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MKAnnotationView+WebCache.m"; sourceTree = ""; };
- 32FDE87B2088871B008D7530 /* MKAnnotationView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MKAnnotationView+WebCache.h"; sourceTree = ""; };
4369C2751D9807EC007E863A /* UIView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+WebCache.h"; path = "SDWebImage/Core/UIView+WebCache.h"; sourceTree = ""; };
4369C2761D9807EC007E863A /* UIView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+WebCache.m"; path = "SDWebImage/Core/UIView+WebCache.m"; sourceTree = ""; };
4397D2F41D0DE2DF00BB2784 /* NSImage+Compatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSImage+Compatibility.h"; path = "Core/NSImage+Compatibility.h"; sourceTree = ""; };
@@ -560,6 +584,8 @@
32CF1C061FA496B000004BD1 /* SDImageCoderHelper.m */,
3257EAF721898AED0097B271 /* SDImageGraphics.h */,
3257EAF821898AED0097B271 /* SDImageGraphics.m */,
+ 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */,
+ 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */,
);
name = Decoder;
sourceTree = "";
@@ -581,6 +607,23 @@
name = AnimatedImage;
sourceTree = "";
};
+ 3287E6C7244C0C1400007311 /* SDWebImageMapKit */ = {
+ isa = PBXGroup;
+ children = (
+ 3287E6CC244C0C1400007311 /* MapKit */,
+ );
+ path = SDWebImageMapKit;
+ sourceTree = "";
+ };
+ 3287E6CC244C0C1400007311 /* MapKit */ = {
+ isa = PBXGroup;
+ children = (
+ 3287E6CE244C0C1400007311 /* MKAnnotationView+WebCache.h */,
+ 3287E6CD244C0C1400007311 /* MKAnnotationView+WebCache.m */,
+ );
+ path = MapKit;
+ sourceTree = "";
+ };
328BB6972081FDAB00760D6C /* Manager */ = {
isa = PBXGroup;
children = (
@@ -619,6 +662,8 @@
children = (
32B5CC5E222F89C2005EB74E /* SDAsyncBlockOperation.h */,
32B5CC5F222F89C2005EB74E /* SDAsyncBlockOperation.m */,
+ 3240BB6623968FE6003BA07D /* SDAssociatedObject.h */,
+ 3240BB6723968FE6003BA07D /* SDAssociatedObject.m */,
325C460622339426004CAE11 /* SDWeakProxy.h */,
325C460722339426004CAE11 /* SDWeakProxy.m */,
32E6730F235765B500DB4987 /* SDDisplayLink.h */,
@@ -631,10 +676,12 @@
325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */,
32C78E39233371AD00C6B7F8 /* SDImageIOAnimatedCoderInternal.h */,
32986560233737C70071958B /* SDImageHEICCoderInternal.h */,
- 325C461E2233A02E004CAE11 /* UIColor+HexString.h */,
- 325C461F2233A02E004CAE11 /* UIColor+HexString.m */,
- 325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */,
- 325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */,
+ 325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */,
+ 325C461F2233A02E004CAE11 /* UIColor+SDHexString.m */,
+ 325C46242233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h */,
+ 325C46252233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m */,
+ 325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */,
+ 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */,
329F123F223FAD3400B309FD /* SDInternalMacros.h */,
329F123E223FAD3400B309FD /* SDInternalMacros.m */,
329F1235223FAA3B00B309FD /* SDmetamacros.h */,
@@ -642,15 +689,6 @@
path = Private;
sourceTree = "";
};
- 32FDE8792088871B008D7530 /* MapKit */ = {
- isa = PBXGroup;
- children = (
- 32FDE87B2088871B008D7530 /* MKAnnotationView+WebCache.h */,
- 32FDE87A2088871B008D7530 /* MKAnnotationView+WebCache.m */,
- );
- path = MapKit;
- sourceTree = "";
- };
4369C2851D9811BB007E863A /* WebCache Categories */ = {
isa = PBXGroup;
children = (
@@ -692,6 +730,7 @@
children = (
EA9E0C6A2195936400AFB434 /* Configs */,
53922D74148C55820056699D /* SDWebImage */,
+ 3287E6C7244C0C1400007311 /* SDWebImageMapKit */,
4A2CAE001AB4BB5300B6BC39 /* WebImage */,
53922D71148C55820056699D /* Frameworks */,
53922D70148C55820056699D /* Products */,
@@ -734,7 +773,6 @@
53922DA9148C562D0056699D /* Categories */,
4369C2851D9811BB007E863A /* WebCache Categories */,
32B5CC5D222F89C2005EB74E /* Private */,
- 32FDE8792088871B008D7530 /* MapKit */,
);
path = SDWebImage;
sourceTree = "";
@@ -744,6 +782,8 @@
children = (
5D5B9140188EE8DD006D06BD /* NSData+ImageContentType.h */,
5D5B9141188EE8DD006D06BD /* NSData+ImageContentType.m */,
+ 325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */,
+ 325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */,
A18A6CC5172DC28500419892 /* UIImage+GIF.h */,
A18A6CC6172DC28500419892 /* UIImage+GIF.m */,
329A18571FFF5DFD008C9A2F /* UIImage+Metadata.h */,
@@ -849,7 +889,8 @@
3257EAFA21898AED0097B271 /* SDImageGraphics.h in Headers */,
32D3CDD121DDE87300C4DB49 /* UIImage+MemoryCacheCost.h in Headers */,
328BB6AC2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */,
- 325C46272233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h in Headers */,
+ 325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */,
+ 325C46272233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h in Headers */,
321B378F2083290E00C0EA77 /* SDImageLoadersManager.h in Headers */,
329A185B1FFF5DFD008C9A2F /* UIImage+Metadata.h in Headers */,
4369C2791D9807EC007E863A /* UIView+WebCache.h in Headers */,
@@ -869,6 +910,7 @@
326E2F2E236F0B23006F847F /* SDAnimatedImagePlayer.h in Headers */,
807A122A1F89636300EC2A9B /* SDImageCodersManager.h in Headers */,
3244062C2296C5F400A36084 /* SDWebImageOptionsProcessor.h in Headers */,
+ 3240BB6823968FE7003BA07D /* SDAssociatedObject.h in Headers */,
4A2CAE211AB4BB7000B6BC39 /* SDWebImageManager.h in Headers */,
4A2CAE1F1AB4BB6C00B6BC39 /* SDImageCache.h in Headers */,
4A2CAE351AB4BB7500B6BC39 /* UIImageView+WebCache.h in Headers */,
@@ -880,6 +922,7 @@
4A2CAE1D1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.h in Headers */,
4A2CAE2B1AB4BB7500B6BC39 /* UIButton+WebCache.h in Headers */,
4A2CAE251AB4BB7000B6BC39 /* SDWebImagePrefetcher.h in Headers */,
+ 3246A70323A567AC00FBEA10 /* SDGraphicsImageRenderer.h in Headers */,
328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */,
325C460F223394D8004CAE11 /* SDImageCachesManagerOperation.h in Headers */,
321E60881F38E8C800405457 /* SDImageCoder.h in Headers */,
@@ -889,7 +932,7 @@
325C460922339426004CAE11 /* SDWeakProxy.h in Headers */,
80B6DF812142B43B00BCB334 /* SDAnimatedImageRep.h in Headers */,
4A2CAE2F1AB4BB7500B6BC39 /* UIImage+MultiFormat.h in Headers */,
- 325C46212233A02E004CAE11 /* UIColor+HexString.h in Headers */,
+ 325C46212233A02E004CAE11 /* UIColor+SDHexString.h in Headers */,
325312CA200F09910046BF1E /* SDWebImageTransition.h in Headers */,
4A2CAE1A1AB4BB6400B6BC39 /* SDWebImageOperation.h in Headers */,
32484765201775F600AF9E5A /* SDAnimatedImageView+WebCache.h in Headers */,
@@ -906,6 +949,7 @@
32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
32F7C0712030114C00873181 /* SDImageTransformer.h in Headers */,
32E67311235765B500DB4987 /* SDDisplayLink.h in Headers */,
+ 325F7CC623893B2E00AEDFCC /* SDFileAttributeHelper.h in Headers */,
4A2CAE2D1AB4BB7500B6BC39 /* UIImage+GIF.h in Headers */,
4A2CAE291AB4BB7500B6BC39 /* NSData+ImageContentType.h in Headers */,
328BB69E2081FED200760D6C /* SDWebImageCacheKeyFilter.h in Headers */,
@@ -916,8 +960,8 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
+ 3287E6D2244C0C1400007311 /* MKAnnotationView+WebCache.h in Headers */,
806BE07E2142C65200E02143 /* SDWebImageMapKit.h in Headers */,
- 80B6DFCD2142B71600BCB334 /* MKAnnotationView+WebCache.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -1097,7 +1141,9 @@
files = (
3257EAFD21898AED0097B271 /* SDImageGraphics.m in Sources */,
3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */,
- 325C46232233A02E004CAE11 /* UIColor+HexString.m in Sources */,
+ 325C46232233A02E004CAE11 /* UIColor+SDHexString.m in Sources */,
+ 325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */,
+ 3246A70523A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */,
321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */,
3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */,
3250C9F02355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */,
@@ -1105,6 +1151,7 @@
32E67313235765B500DB4987 /* SDDisplayLink.m in Sources */,
4A2CAE2E1AB4BB7500B6BC39 /* UIImage+GIF.m in Sources */,
326E2F35236F1D58006F847F /* SDDeviceHelper.m in Sources */,
+ 3240BB6A23968FE7003BA07D /* SDAssociatedObject.m in Sources */,
80B6DF822142B44400BCB334 /* NSButton+WebCache.m in Sources */,
32D3CDCF21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m in Sources */,
329F1241223FAD3400B309FD /* SDInternalMacros.m in Sources */,
@@ -1127,12 +1174,13 @@
321E609C1F38E8ED00405457 /* SDImageIOCoder.m in Sources */,
4A2CAE261AB4BB7000B6BC39 /* SDWebImagePrefetcher.m in Sources */,
328BB6C92082581100760D6C /* SDDiskCache.m in Sources */,
+ 325F7CC723893B2E00AEDFCC /* SDFileAttributeHelper.m in Sources */,
3248475F201775F600AF9E5A /* SDAnimatedImageView.m in Sources */,
32D1222C2080B2EB003685A3 /* SDImageCachesManager.m in Sources */,
32B9B53F206ED4230026769D /* SDWebImageDownloaderConfig.m in Sources */,
43A9186D1D8308FE00B3925F /* SDImageCacheConfig.m in Sources */,
32A09E42233358B700339F9D /* SDImageIOAnimatedCoder.m in Sources */,
- 325C46292233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */,
+ 325C46292233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m in Sources */,
3248477D201775F600AF9E5A /* SDAnimatedImageView+WebCache.m in Sources */,
321E60AA1F38E8F600405457 /* SDImageGIFCoder.m in Sources */,
321E608E1F38E8C800405457 /* SDImageCoder.m in Sources */,
@@ -1167,14 +1215,17 @@
files = (
3257EAFC21898AED0097B271 /* SDImageGraphics.m in Sources */,
3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */,
- 325C46222233A02E004CAE11 /* UIColor+HexString.m in Sources */,
+ 325C46222233A02E004CAE11 /* UIColor+SDHexString.m in Sources */,
321E60C41F38E91700405457 /* UIImage+ForceDecode.m in Sources */,
+ 3246A70423A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */,
3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */,
3250C9EF2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */,
+ 3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */,
328BB6A22081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */,
32E67312235765B500DB4987 /* SDDisplayLink.m in Sources */,
53761309155AD0D5005750A4 /* SDImageCache.m in Sources */,
326E2F34236F1D58006F847F /* SDDeviceHelper.m in Sources */,
+ 3240BB6923968FE7003BA07D /* SDAssociatedObject.m in Sources */,
80B6DF832142B44500BCB334 /* NSButton+WebCache.m in Sources */,
32D3CDCE21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m in Sources */,
329F1240223FAD3400B309FD /* SDInternalMacros.m in Sources */,
@@ -1198,11 +1249,12 @@
5376130D155AD0D5005750A4 /* SDWebImagePrefetcher.m in Sources */,
328BB6C72082581100760D6C /* SDDiskCache.m in Sources */,
3248475D201775F600AF9E5A /* SDAnimatedImageView.m in Sources */,
+ 325F7CCC2389463D00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */,
32D1222A2080B2EB003685A3 /* SDImageCachesManager.m in Sources */,
32B9B53D206ED4230026769D /* SDWebImageDownloaderConfig.m in Sources */,
43A9186B1D8308FE00B3925F /* SDImageCacheConfig.m in Sources */,
32A09E41233358B700339F9D /* SDImageIOAnimatedCoder.m in Sources */,
- 325C46282233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */,
+ 325C46282233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m in Sources */,
3248477B201775F600AF9E5A /* SDAnimatedImageView+WebCache.m in Sources */,
321E60A81F38E8F600405457 /* SDImageGIFCoder.m in Sources */,
321E608C1F38E8C800405457 /* SDImageCoder.m in Sources */,
@@ -1235,7 +1287,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
- 80B6DFA72142B71600BCB334 /* MKAnnotationView+WebCache.m in Sources */,
+ 3287E6D1244C0C1400007311 /* MKAnnotationView+WebCache.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
diff --git a/SDWebImage/Core/NSData+ImageContentType.h b/SDWebImage/Core/NSData+ImageContentType.h
index 5bbb4ae6..de2a6bf3 100644
--- a/SDWebImage/Core/NSData+ImageContentType.h
+++ b/SDWebImage/Core/NSData+ImageContentType.h
@@ -23,6 +23,8 @@ static const SDImageFormat SDImageFormatTIFF = 3;
static const SDImageFormat SDImageFormatWebP = 4;
static const SDImageFormat SDImageFormatHEIC = 5;
static const SDImageFormat SDImageFormatHEIF = 6;
+static const SDImageFormat SDImageFormatPDF = 7;
+static const SDImageFormat SDImageFormatSVG = 8;
/**
NSData category about the image content type and UTI.
@@ -43,6 +45,7 @@ static const SDImageFormat SDImageFormatHEIF = 6;
*
* @param format Format as SDImageFormat
* @return The UTType as CFStringRef
+ * @note For unknown format, `kUTTypeImage` abstract type will return
*/
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format CF_RETURNS_NOT_RETAINED NS_SWIFT_NAME(sd_UTType(from:));
@@ -51,6 +54,7 @@ static const SDImageFormat SDImageFormatHEIF = 6;
*
* @param uttype The UTType as CFStringRef
* @return The Format as SDImageFormat
+ * @note For unknown type, `SDImageFormatUndefined` will return
*/
+ (SDImageFormat)sd_imageFormatFromUTType:(nonnull CFStringRef)uttype;
diff --git a/SDWebImage/Core/NSData+ImageContentType.m b/SDWebImage/Core/NSData+ImageContentType.m
index 34dd4aa0..87d041ed 100644
--- a/SDWebImage/Core/NSData+ImageContentType.m
+++ b/SDWebImage/Core/NSData+ImageContentType.m
@@ -17,6 +17,7 @@
// Currently Image/IO does not support WebP
#define kSDUTTypeWebP ((__bridge CFStringRef)@"public.webp")
+#define kSVGTagEnd @""
@implementation NSData (ImageContentType)
@@ -65,6 +66,24 @@
}
break;
}
+ case 0x25: {
+ if (data.length >= 4) {
+ //%PDF
+ NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1, 3)] encoding:NSASCIIStringEncoding];
+ if ([testString isEqualToString:@"PDF"]) {
+ return SDImageFormatPDF;
+ }
+ }
+ }
+ case 0x3C: {
+ if (data.length > 100) {
+ // Check end with SVG tag
+ NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(data.length - 100, 100)] encoding:NSASCIIStringEncoding];
+ if ([testString containsString:kSVGTagEnd]) {
+ return SDImageFormatSVG;
+ }
+ }
+ }
}
return SDImageFormatUndefined;
}
@@ -93,9 +112,15 @@
case SDImageFormatHEIF:
UTType = kSDUTTypeHEIF;
break;
+ case SDImageFormatPDF:
+ UTType = kUTTypePDF;
+ break;
+ case SDImageFormatSVG:
+ UTType = kUTTypeScalableVectorGraphics;
+ break;
default:
- // default is kUTTypePNG
- UTType = kUTTypePNG;
+ // default is kUTTypeImage abstract type
+ UTType = kUTTypeImage;
break;
}
return UTType;
@@ -120,6 +145,10 @@
imageFormat = SDImageFormatHEIC;
} else if (CFStringCompare(uttype, kSDUTTypeHEIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatHEIF;
+ } else if (CFStringCompare(uttype, kUTTypePDF, 0) == kCFCompareEqualTo) {
+ imageFormat = SDImageFormatPDF;
+ } else if (CFStringCompare(uttype, kUTTypeScalableVectorGraphics, 0) == kCFCompareEqualTo) {
+ imageFormat = SDImageFormatSVG;
} else {
imageFormat = SDImageFormatUndefined;
}
diff --git a/SDWebImage/Core/NSImage+Compatibility.h b/SDWebImage/Core/NSImage+Compatibility.h
index dccc1ffa..0a562cc4 100644
--- a/SDWebImage/Core/NSImage+Compatibility.h
+++ b/SDWebImage/Core/NSImage+Compatibility.h
@@ -19,6 +19,10 @@
The underlying Core Graphics image object. This will actually use `CGImageForProposedRect` with the image size.
*/
@property (nonatomic, readonly, nullable) CGImageRef CGImage;
+/**
+ The underlying Core Image data. This will actually use `bestRepresentationForRect` with the image size to find the `NSCIImageRep`.
+ */
+@property (nonatomic, readonly, nullable) CIImage *CIImage;
/**
The scale factor of the image. This wil actually use `bestRepresentationForRect` with image size and pixel size to calculate the scale factor. If failed, use the default value 1.0. Should be greater than or equal to 1.0.
*/
@@ -38,6 +42,16 @@ The underlying Core Graphics image object. This will actually use `CGImageForPro
*/
- (nonnull instancetype)initWithCGImage:(nonnull CGImageRef)cgImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation;
+/**
+ Initializes and returns an image object with the specified Core Image object. The representation is `NSCIImageRep`.
+
+ @param ciImage A Core Image image object
+ @param scale The image scale factor
+ @param orientation The orientation of the image data
+ @return The image object
+ */
+- (nonnull instancetype)initWithCIImage:(nonnull CIImage *)ciImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation;
+
/**
Returns an image object with the scale factor. The representation is created from the image data.
@note The difference between these this and `initWithData:` is that `initWithData:` will always use `backingScaleFactor` as scale factor.
diff --git a/SDWebImage/Core/NSImage+Compatibility.m b/SDWebImage/Core/NSImage+Compatibility.m
index 83b80bc6..7de0c703 100644
--- a/SDWebImage/Core/NSImage+Compatibility.m
+++ b/SDWebImage/Core/NSImage+Compatibility.m
@@ -20,6 +20,15 @@
return cgImage;
}
+- (nullable CIImage *)CIImage {
+ NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
+ NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
+ if (![imageRep isKindOfClass:NSCIImageRep.class]) {
+ return nil;
+ }
+ return ((NSCIImageRep *)imageRep).CIImage;
+}
+
- (CGFloat)scale {
CGFloat scale = 1;
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
@@ -65,6 +74,28 @@
return self;
}
+- (instancetype)initWithCIImage:(nonnull CIImage *)ciImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation {
+ NSCIImageRep *imageRep;
+ if (orientation != kCGImagePropertyOrientationUp) {
+ CIImage *rotatedCIImage = [ciImage imageByApplyingOrientation:orientation];
+ imageRep = [[NSCIImageRep alloc] initWithCIImage:rotatedCIImage];
+ } else {
+ imageRep = [[NSCIImageRep alloc] initWithCIImage:ciImage];
+ }
+ if (scale < 1) {
+ scale = 1;
+ }
+ CGFloat pixelWidth = imageRep.pixelsWide;
+ CGFloat pixelHeight = imageRep.pixelsHigh;
+ NSSize size = NSMakeSize(pixelWidth / scale, pixelHeight / scale);
+ self = [self initWithSize:size];
+ if (self) {
+ imageRep.size = size;
+ [self addRepresentation:imageRep];
+ }
+ return self;
+}
+
- (instancetype)initWithData:(nonnull NSData *)data scale:(CGFloat)scale {
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithData:data];
if (!imageRep) {
diff --git a/SDWebImage/Core/SDAnimatedImage.h b/SDWebImage/Core/SDAnimatedImage.h
index feb118e6..a1e2fb19 100644
--- a/SDWebImage/Core/SDAnimatedImage.h
+++ b/SDWebImage/Core/SDAnimatedImage.h
@@ -72,6 +72,7 @@
// This class override these methods from UIImage(NSImage), and it supports NSSecureCoding.
// You should use these methods to create a new animated image. Use other methods just call super instead.
+// Pay attention, when the animated image frame count <= 1, all the `SDAnimatedImageProvider` protocol methods will return nil or 0 value, you'd better check the frame count before usage and keep fallback.
+ (nullable instancetype)imageNamed:(nonnull NSString *)name; // Cache in memory, no Asset Catalog support
#if __has_include()
+ (nullable instancetype)imageNamed:(nonnull NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection; // Cache in memory, no Asset Catalog support
diff --git a/SDWebImage/Core/SDAnimatedImage.m b/SDWebImage/Core/SDAnimatedImage.m
index ce86e331..d27e2c4b 100644
--- a/SDWebImage/Core/SDAnimatedImage.m
+++ b/SDWebImage/Core/SDAnimatedImage.m
@@ -12,6 +12,7 @@
#import "SDImageCodersManager.h"
#import "SDImageFrame.h"
#import "UIImage+MemoryCacheCost.h"
+#import "UIImage+Metadata.h"
#import "SDImageAssetManager.h"
#import "objc/runtime.h"
@@ -156,7 +157,10 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
#endif
if (self) {
- _animatedCoder = animatedCoder;
+ // Only keep the animated coder if frame count > 1, save RAM usage for non-animated image format (APNG/WebP)
+ if (animatedCoder.animatedImageFrameCount > 1) {
+ _animatedCoder = animatedCoder;
+ }
NSData *data = [animatedCoder animatedImageData];
SDImageFormat format = [NSData sd_imageFormatForImageData:data];
_animatedImageFormat = format;
@@ -166,6 +170,9 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
#pragma mark - Preload
- (void)preloadAllFrames {
+ if (!_animatedCoder) {
+ return;
+ }
if (!self.isAllFramesLoaded) {
NSMutableArray *frames = [NSMutableArray arrayWithCapacity:self.animatedImageFrameCount];
for (size_t i = 0; i < self.animatedImageFrameCount; i++) {
@@ -180,6 +187,9 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
- (void)unloadAllFrames {
+ if (!_animatedCoder) {
+ return;
+ }
if (self.isAllFramesLoaded) {
self.loadedAnimatedImageFrames = nil;
self.allFramesLoaded = NO;
@@ -190,11 +200,12 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
+ _animatedImageFormat = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(animatedImageFormat))];
NSData *animatedImageData = [aDecoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(animatedImageData))];
- CGFloat scale = self.scale;
if (!animatedImageData) {
return self;
}
+ CGFloat scale = self.scale;
id animatedCoder = nil;
for (idcoder in [SDImageCodersManager sharedManager].coders) {
if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
@@ -207,15 +218,16 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
if (!animatedCoder) {
return self;
}
- _animatedCoder = animatedCoder;
- SDImageFormat format = [NSData sd_imageFormatForImageData:animatedImageData];
- _animatedImageFormat = format;
+ if (animatedCoder.animatedImageFrameCount > 1) {
+ _animatedCoder = animatedCoder;
+ }
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
+ [aCoder encodeInteger:self.animatedImageFormat forKey:NSStringFromSelector(@selector(animatedImageFormat))];
NSData *animatedImageData = self.animatedImageData;
if (animatedImageData) {
[aCoder encodeObject:animatedImageData forKey:NSStringFromSelector(@selector(animatedImageData))];
@@ -226,7 +238,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
return YES;
}
-#pragma mark - SDAnimatedImage
+#pragma mark - SDAnimatedImageProvider
- (NSData *)animatedImageData {
return [self.animatedCoder animatedImageData];
@@ -287,3 +299,31 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
@end
+
+@implementation SDAnimatedImage (Metadata)
+
+- (BOOL)sd_isAnimated {
+ return YES;
+}
+
+- (NSUInteger)sd_imageLoopCount {
+ return self.animatedImageLoopCount;
+}
+
+- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
+ return;
+}
+
+- (SDImageFormat)sd_imageFormat {
+ return self.animatedImageFormat;
+}
+
+- (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat {
+ return;
+}
+
+- (BOOL)sd_isVector {
+ return NO;
+}
+
+@end
diff --git a/SDWebImage/Core/SDAnimatedImagePlayer.m b/SDWebImage/Core/SDAnimatedImagePlayer.m
index 2efd8805..54135094 100644
--- a/SDWebImage/Core/SDAnimatedImagePlayer.m
+++ b/SDWebImage/Core/SDAnimatedImagePlayer.m
@@ -23,6 +23,7 @@
@property (nonatomic, strong) NSMutableDictionary *frameBuffer;
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss;
+@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) dispatch_semaphore_t lock;
@@ -165,6 +166,7 @@
self.currentLoopCount = 0;
self.currentTime = 0;
self.bufferMiss = NO;
+ self.needsDisplayWhenImageBecomesAvailable = NO;
[self handleFrameChange];
}
@@ -177,12 +179,12 @@
#pragma mark - Animation Control
- (void)startPlaying {
[self.displayLink start];
- // Calculate max buffer size
- [self calculateMaxBufferCount];
// Setup frame
if (self.currentFrameIndex == 0 && !self.currentFrame) {
[self setupCurrentFrame];
}
+ // Calculate max buffer size
+ [self calculateMaxBufferCount];
}
- (void)stopPlaying {
@@ -217,8 +219,6 @@
if (!self.isPlaying) {
return;
}
- // Calculate refresh duration
- NSTimeInterval duration = self.displayLink.duration;
NSUInteger totalFrameCount = self.totalFrameCount;
if (totalFrameCount <= 1) {
@@ -226,8 +226,6 @@
[self stopPlaying];
return;
}
- NSUInteger currentFrameIndex = self.currentFrameIndex;
- NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
NSTimeInterval playbackRate = self.playbackRate;
if (playbackRate <= 0) {
@@ -236,7 +234,46 @@
return;
}
- // Check if we have the frame buffer firstly to improve performance
+ // Calculate refresh duration
+ NSTimeInterval duration = self.displayLink.duration;
+
+ NSUInteger currentFrameIndex = self.currentFrameIndex;
+ NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
+
+ // Check if we need to display new frame firstly
+ BOOL bufferFull = NO;
+ if (self.needsDisplayWhenImageBecomesAvailable) {
+ UIImage *currentFrame;
+ SD_LOCK(self.lock);
+ currentFrame = self.frameBuffer[@(currentFrameIndex)];
+ SD_UNLOCK(self.lock);
+
+ // Update the current frame
+ if (currentFrame) {
+ SD_LOCK(self.lock);
+ // Remove the frame buffer if need
+ if (self.frameBuffer.count > self.maxBufferCount) {
+ self.frameBuffer[@(currentFrameIndex)] = nil;
+ }
+ // Check whether we can stop fetch
+ if (self.frameBuffer.count == totalFrameCount) {
+ bufferFull = YES;
+ }
+ SD_UNLOCK(self.lock);
+
+ // Update the current frame immediately
+ self.currentFrame = currentFrame;
+ [self handleFrameChange];
+
+ self.bufferMiss = NO;
+ self.needsDisplayWhenImageBecomesAvailable = NO;
+ }
+ else {
+ self.bufferMiss = YES;
+ }
+ }
+
+ // Check if we have the frame buffer
if (!self.bufferMiss) {
// Then check if timestamp is reached
self.currentTime += duration;
@@ -246,6 +283,10 @@
// Current frame timestamp not reached, return
return;
}
+
+ // Otherwise, we shoudle be ready to display next frame
+ self.needsDisplayWhenImageBecomesAvailable = YES;
+ self.currentFrameIndex = nextFrameIndex;
self.currentTime -= currentDuration;
NSTimeInterval nextDuration = [self.animatedProvider animatedImageDurationAtIndex:nextFrameIndex];
nextDuration = nextDuration / playbackRate;
@@ -253,45 +294,19 @@
// Do not skip frame
self.currentTime = nextDuration;
}
- }
-
- // Update the current frame
- UIImage *currentFrame;
- UIImage *fetchFrame;
- SD_LOCK(self.lock);
- currentFrame = self.frameBuffer[@(currentFrameIndex)];
- fetchFrame = currentFrame ? self.frameBuffer[@(nextFrameIndex)] : nil;
- SD_UNLOCK(self.lock);
- BOOL bufferFull = NO;
- if (currentFrame) {
- SD_LOCK(self.lock);
- // Remove the frame buffer if need
- if (self.frameBuffer.count > self.maxBufferCount) {
- self.frameBuffer[@(currentFrameIndex)] = nil;
- }
- // Check whether we can stop fetch
- if (self.frameBuffer.count == totalFrameCount) {
- bufferFull = YES;
- }
- SD_UNLOCK(self.lock);
- self.currentFrame = currentFrame;
- [self handleFrameChange];
- self.currentFrameIndex = nextFrameIndex;
- self.bufferMiss = NO;
- } else {
- self.bufferMiss = YES;
- }
-
- // Update the loop count when last frame rendered
- if (nextFrameIndex == 0 && !self.bufferMiss) {
- // Update the loop count
- self.currentLoopCount++;
- [self handleLoopChnage];
- // if reached the max loop count, stop animating, 0 means loop indefinitely
- NSUInteger maxLoopCount = self.totalLoopCount;
- if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
- [self stopPlaying];
- return;
+
+ // Update the loop count when last frame rendered
+ if (nextFrameIndex == 0) {
+ // Update the loop count
+ self.currentLoopCount++;
+ [self handleLoopChnage];
+
+ // if reached the max loop count, stop animating, 0 means loop indefinitely
+ NSUInteger maxLoopCount = self.totalLoopCount;
+ if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
+ [self stopPlaying];
+ return;
+ }
}
}
@@ -301,14 +316,13 @@
}
// Check if we should prefetch next frame or current frame
- NSUInteger fetchFrameIndex;
- if (self.bufferMiss) {
- // When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
- fetchFrameIndex = currentFrameIndex;
- } else {
- // Or, most cases, the decode speed is faster than render speed, we fetch next frame
- fetchFrameIndex = nextFrameIndex;
- }
+ // When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
+ // Or, most cases, the decode speed is faster than render speed, we fetch next frame
+ NSUInteger fetchFrameIndex = self.bufferMiss? currentFrameIndex : nextFrameIndex;
+ UIImage *fetchFrame;
+ SD_LOCK(self.lock);
+ fetchFrame = self.bufferMiss? nil : self.frameBuffer[@(nextFrameIndex)];
+ SD_UNLOCK(self.lock);
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue
diff --git a/SDWebImage/Core/SDAnimatedImageView.m b/SDWebImage/Core/SDAnimatedImageView.m
index 181f2db9..e5cad14d 100644
--- a/SDWebImage/Core/SDAnimatedImageView.m
+++ b/SDWebImage/Core/SDAnimatedImageView.m
@@ -19,6 +19,7 @@
@interface SDAnimatedImageView () {
BOOL _initFinished; // Extra flag to mark the `commonInit` is called
NSRunLoopMode _runLoopMode;
+ NSUInteger _maxBufferSize;
double _playbackRate;
}
@@ -153,6 +154,9 @@
// RunLoop Mode
self.player.runLoopMode = self.runLoopMode;
+ // Max Buffer Size
+ self.player.maxBufferSize = self.maxBufferSize;
+
// Play Rate
self.player.playbackRate = self.playbackRate;
@@ -166,12 +170,13 @@
};
self.player.animationLoopHandler = ^(NSUInteger loopCount) {
@strongify(self);
- self.currentLoopCount = loopCount;
// Progressive image reach the current last frame index. Keep the state and pause animating. Wait for later restart
if (self.isProgressive) {
- NSUInteger lastFrameIndex = self.player.totalFrameCount;
+ NSUInteger lastFrameIndex = self.player.totalFrameCount - 1;
[self.player seekToFrameAtIndex:lastFrameIndex loopCount:0];
[self.player pausePlaying];
+ } else {
+ self.currentLoopCount = loopCount;
}
};
@@ -206,6 +211,16 @@
return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode;
}
+- (void)setMaxBufferSize:(NSUInteger)maxBufferSize
+{
+ _maxBufferSize = maxBufferSize;
+ self.player.maxBufferSize = maxBufferSize;
+}
+
+- (NSUInteger)maxBufferSize {
+ return _maxBufferSize; // Defaults to 0
+}
+
- (void)setPlaybackRate:(double)playbackRate
{
_playbackRate = playbackRate;
@@ -306,6 +321,19 @@
#pragma mark - UIImageView Method Overrides
#pragma mark Image Data
+- (void)setAnimationRepeatCount:(NSInteger)animationRepeatCount
+{
+#if SD_UIKIT
+ [super setAnimationRepeatCount:animationRepeatCount];
+#else
+ _animationRepeatCount = animationRepeatCount;
+#endif
+
+ if (self.shouldCustomLoopCount) {
+ self.player.totalLoopCount = animationRepeatCount;
+ }
+}
+
- (void)startAnimating
{
if (self.player) {
@@ -442,10 +470,10 @@
// NSImageView use a subview. We need this subview's layer for actual rendering.
// Why using this design may because of properties like `imageAlignment` and `imageScaling`, which it's not available for UIImageView.contentMode (it's impossible to align left and keep aspect ratio at the same time)
- (NSView *)imageView {
- NSImageView *imageView = imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageView"));
+ NSImageView *imageView = objc_getAssociatedObject(self, SD_SEL_SPI(imageView));
if (!imageView) {
// macOS 10.14
- imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageSubview"));
+ imageView = objc_getAssociatedObject(self, SD_SEL_SPI(imageSubview));
}
return imageView;
}
diff --git a/SDWebImage/Core/SDDiskCache.h b/SDWebImage/Core/SDDiskCache.h
index ffc440e5..dc5e1fae 100644
--- a/SDWebImage/Core/SDDiskCache.h
+++ b/SDWebImage/Core/SDDiskCache.h
@@ -54,6 +54,26 @@
*/
- (void)setData:(nullable NSData *)data forKey:(nonnull NSString *)key;
+/**
+ Returns the extended data associated with a given key.
+ This method may blocks the calling thread until file read finished.
+
+ @param key A string identifying the data. If nil, just return nil.
+ @return The value associated with key, or nil if no value is associated with key.
+ */
+- (nullable NSData *)extendedDataForKey:(nonnull NSString *)key;
+
+/**
+ Set extended data with a given key.
+
+ @discussion You can set any extended data to exist cache key. Without override the exist disk file data.
+ on UNIX, the common way for this is to use the Extended file attributes (xattr)
+
+ @param extendedData The extended data (pass nil to remove).
+ @param key The key with which to associate the value. If nil, this method has no effect.
+*/
+- (void)setExtendedData:(nullable NSData *)extendedData forKey:(nonnull NSString *)key;
+
/**
Removes the value of the specified key in the cache.
This method may blocks the calling thread until file delete finished.
diff --git a/SDWebImage/Core/SDDiskCache.m b/SDWebImage/Core/SDDiskCache.m
index 1d5ec44e..d7308dcc 100644
--- a/SDWebImage/Core/SDDiskCache.m
+++ b/SDWebImage/Core/SDDiskCache.m
@@ -8,8 +8,11 @@
#import "SDDiskCache.h"
#import "SDImageCacheConfig.h"
+#import "SDFileAttributeHelper.h"
#import
+static NSString * const SDDiskCacheExtendedAttributeName = @"com.hackemist.SDDiskCache";
+
@interface SDDiskCache ()
@property (nonatomic, copy) NSString *diskCachePath;
@@ -95,6 +98,31 @@
}
}
+- (NSData *)extendedDataForKey:(NSString *)key {
+ NSParameterAssert(key);
+
+ // get cache Path for image key
+ NSString *cachePathForKey = [self cachePathForKey:key];
+
+ NSData *extendedData = [SDFileAttributeHelper extendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
+
+ return extendedData;
+}
+
+- (void)setExtendedData:(NSData *)extendedData forKey:(NSString *)key {
+ NSParameterAssert(key);
+ // get cache Path for image key
+ NSString *cachePathForKey = [self cachePathForKey:key];
+
+ if (!extendedData) {
+ // Remove
+ [SDFileAttributeHelper removeExtendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
+ } else {
+ // Override
+ [SDFileAttributeHelper setExtendedAttribute:SDDiskCacheExtendedAttributeName value:extendedData atPath:cachePathForKey traverseLink:NO overwrite:YES error:nil];
+ }
+}
+
- (void)removeDataForKey:(NSString *)key {
NSParameterAssert(key);
NSString *filePath = [self cachePathForKey:key];
@@ -118,11 +146,15 @@
case SDImageCacheConfigExpireTypeAccessDate:
cacheContentDateKey = NSURLContentAccessDateKey;
break;
-
case SDImageCacheConfigExpireTypeModificationDate:
cacheContentDateKey = NSURLContentModificationDateKey;
break;
-
+ case SDImageCacheConfigExpireTypeCreationDate:
+ cacheContentDateKey = NSURLCreationDateKey;
+ break;
+ case SDImageCacheConfigExpireTypeChangeDate:
+ cacheContentDateKey = NSURLAttributeModificationDateKey;
+ break;
default:
break;
}
diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.h b/SDWebImage/Core/SDGraphicsImageRenderer.h
new file mode 100644
index 00000000..900acd76
--- /dev/null
+++ b/SDWebImage/Core/SDGraphicsImageRenderer.h
@@ -0,0 +1,73 @@
+/*
+* This file is part of the SDWebImage package.
+* (c) Olivier Poitrey
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+#import "SDWebImageCompat.h"
+
+/**
+ These following class are provided to use `UIGraphicsImageRenderer` with polyfill, which allows write cross-platform(AppKit/UIKit) code and avoid runtime version check.
+ Compared to `UIGraphicsBeginImageContext`, `UIGraphicsImageRenderer` use dynamic bitmap from your draw code to generate CGContext, not always use ARGB8888, which is more performant on RAM usage.
+ Which means, if you draw CGImage/CIImage which contains grayscale only, the underlaying bitmap context use grayscale, it's managed by system and not a fixed type. (actually, the `kCGContextTypeAutomatic`)
+ For usage, See more in Apple's documentation: https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer
+ For UIKit on iOS/tvOS 10+, these method just use the same `UIGraphicsImageRenderer` API.
+ For others (macOS/watchOS or iOS/tvOS 10-), these method use the `SDImageGraphics.h` to implements the same behavior (but without dynamic bitmap support)
+*/
+
+typedef void (^SDGraphicsImageDrawingActions)(CGContextRef _Nonnull context);
+typedef NS_ENUM(NSInteger, SDGraphicsImageRendererFormatRange) {
+ SDGraphicsImageRendererFormatRangeUnspecified = -1,
+ SDGraphicsImageRendererFormatRangeAutomatic = 0,
+ SDGraphicsImageRendererFormatRangeExtended,
+ SDGraphicsImageRendererFormatRangeStandard
+};
+
+/// A set of drawing attributes that represent the configuration of an image renderer context.
+@interface SDGraphicsImageRendererFormat : NSObject
+
+/// The display scale of the image renderer context.
+/// The default value is equal to the scale of the main screen.
+@property (nonatomic) CGFloat scale;
+
+/// A Boolean value indicating whether the underlying Core Graphics context has an alpha channel.
+/// The default value is NO.
+@property (nonatomic) BOOL opaque;
+
+/// Specifying whether the bitmap context should use extended color.
+/// For iOS 12+, the value is from system `preferredRange` property
+/// For iOS 10-11, the value is from system `prefersExtendedRange` property
+/// For iOS 9-, the value is `.standard`
+@property (nonatomic) SDGraphicsImageRendererFormatRange preferredRange;
+
+/// Init the default format. See each properties's default value.
+- (nonnull instancetype)init;
+
+/// Returns a new format best suited for the main screen’s current configuration.
++ (nonnull instancetype)preferredFormat;
+
+@end
+
+/// A graphics renderer for creating Core Graphics-backed images.
+@interface SDGraphicsImageRenderer : NSObject
+
+/// Creates an image renderer for drawing images of a given size.
+/// @param size The size of images output from the renderer, specified in points.
+/// @return An initialized image renderer.
+- (nonnull instancetype)initWithSize:(CGSize)size;
+
+/// Creates a new image renderer with a given size and format.
+/// @param size The size of images output from the renderer, specified in points.
+/// @param format A SDGraphicsImageRendererFormat object that encapsulates the format used to create the renderer context.
+/// @return An initialized image renderer.
+- (nonnull instancetype)initWithSize:(CGSize)size format:(nonnull SDGraphicsImageRendererFormat *)format;
+
+/// Creates an image by following a set of drawing instructions.
+/// @param actions A SDGraphicsImageDrawingActions block that, when invoked by the renderer, executes a set of drawing instructions to create the output image.
+/// @note You should not retain or use the context outside the block, it's non-escaping.
+/// @return A UIImage object created by the supplied drawing actions.
+- (nonnull UIImage *)imageWithActions:(nonnull NS_NOESCAPE SDGraphicsImageDrawingActions)actions;
+
+@end
diff --git a/SDWebImage/Core/SDGraphicsImageRenderer.m b/SDWebImage/Core/SDGraphicsImageRenderer.m
new file mode 100644
index 00000000..03aef3a5
--- /dev/null
+++ b/SDWebImage/Core/SDGraphicsImageRenderer.m
@@ -0,0 +1,244 @@
+/*
+* This file is part of the SDWebImage package.
+* (c) Olivier Poitrey
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+#import "SDGraphicsImageRenderer.h"
+#import "SDImageGraphics.h"
+
+@interface SDGraphicsImageRendererFormat ()
+#if SD_UIKIT
+@property (nonatomic, strong) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0), tvos(10.0));
+#endif
+@end
+
+@implementation SDGraphicsImageRendererFormat
+@synthesize scale = _scale;
+@synthesize opaque = _opaque;
+@synthesize preferredRange = _preferredRange;
+
+#pragma mark - Property
+- (CGFloat)scale {
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.10, *)) {
+ return self.uiformat.scale;
+ } else {
+ return _scale;
+ }
+#else
+ return _scale;
+#endif
+}
+
+- (void)setScale:(CGFloat)scale {
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.10, *)) {
+ self.uiformat.scale = scale;
+ } else {
+ _scale = scale;
+ }
+#else
+ _scale = scale;
+#endif
+}
+
+- (BOOL)opaque {
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.10, *)) {
+ return self.uiformat.opaque;
+ } else {
+ return _opaque;
+ }
+#else
+ return _opaque;
+#endif
+}
+
+- (void)setOpaque:(BOOL)opaque {
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.10, *)) {
+ self.uiformat.opaque = opaque;
+ } else {
+ _opaque = opaque;
+ }
+#else
+ _opaque = opaque;
+#endif
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+- (SDGraphicsImageRendererFormatRange)preferredRange {
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.10, *)) {
+ if (@available(iOS 12.0, tvOS 12.0, *)) {
+ return (SDGraphicsImageRendererFormatRange)self.uiformat.preferredRange;
+ } else {
+ BOOL prefersExtendedRange = self.uiformat.prefersExtendedRange;
+ if (prefersExtendedRange) {
+ return SDGraphicsImageRendererFormatRangeExtended;
+ } else {
+ return SDGraphicsImageRendererFormatRangeStandard;
+ }
+ }
+ } else {
+ return _preferredRange;
+ }
+#else
+ return _preferredRange;
+#endif
+}
+
+- (void)setPreferredRange:(SDGraphicsImageRendererFormatRange)preferredRange {
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.10, *)) {
+ if (@available(iOS 12.0, tvOS 12.0, *)) {
+ self.uiformat.preferredRange = (UIGraphicsImageRendererFormatRange)preferredRange;
+ } else {
+ switch (preferredRange) {
+ case SDGraphicsImageRendererFormatRangeExtended:
+ self.uiformat.prefersExtendedRange = YES;
+ break;
+ case SDGraphicsImageRendererFormatRangeStandard:
+ self.uiformat.prefersExtendedRange = NO;
+ default:
+ // Automatic means default
+ break;
+ }
+ }
+ } else {
+ _preferredRange = preferredRange;
+ }
+#else
+ _preferredRange = preferredRange;
+#endif
+}
+#pragma clang diagnostic pop
+
+- (instancetype)init {
+ self = [super init];
+ if (self) {
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.10, *)) {
+ UIGraphicsImageRendererFormat *uiformat = [[UIGraphicsImageRendererFormat alloc] init];
+ self.uiformat = uiformat;
+ } else {
+#endif
+#if SD_WATCH
+ CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale;
+#elif SD_UIKIT
+ CGFloat screenScale = [UIScreen mainScreen].scale;
+#elif SD_MAC
+ CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor;
+#endif
+ self.scale = screenScale;
+ self.opaque = NO;
+ self.preferredRange = SDGraphicsImageRendererFormatRangeStandard;
+#if SD_UIKIT
+ }
+#endif
+ }
+ return self;
+}
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunguarded-availability"
+- (instancetype)initForMainScreen {
+ self = [super init];
+ if (self) {
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.0, *)) {
+ UIGraphicsImageRendererFormat *uiformat;
+ // iOS 11.0.0 GM does have `preferredFormat`, but iOS 11 betas did not (argh!)
+ if ([UIGraphicsImageRenderer respondsToSelector:@selector(preferredFormat)]) {
+ uiformat = [UIGraphicsImageRendererFormat preferredFormat];
+ } else {
+ uiformat = [UIGraphicsImageRendererFormat defaultFormat];
+ }
+ self.uiformat = uiformat;
+ } else {
+#endif
+#if SD_WATCH
+ CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale;
+#elif SD_UIKIT
+ CGFloat screenScale = [UIScreen mainScreen].scale;
+#elif SD_MAC
+ CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor;
+#endif
+ self.scale = screenScale;
+ self.opaque = NO;
+ self.preferredRange = SDGraphicsImageRendererFormatRangeStandard;
+#if SD_UIKIT
+ }
+#endif
+ }
+ return self;
+}
+#pragma clang diagnostic pop
+
++ (instancetype)preferredFormat {
+ SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] initForMainScreen];
+ return format;
+}
+
+@end
+
+@interface SDGraphicsImageRenderer ()
+@property (nonatomic, assign) CGSize size;
+@property (nonatomic, strong) SDGraphicsImageRendererFormat *format;
+#if SD_UIKIT
+@property (nonatomic, strong) UIGraphicsImageRenderer *uirenderer API_AVAILABLE(ios(10.0), tvos(10.0));
+#endif
+@end
+
+@implementation SDGraphicsImageRenderer
+
+- (instancetype)initWithSize:(CGSize)size {
+ return [self initWithSize:size format:SDGraphicsImageRendererFormat.preferredFormat];
+}
+
+- (instancetype)initWithSize:(CGSize)size format:(SDGraphicsImageRendererFormat *)format {
+ NSParameterAssert(format);
+ self = [super init];
+ if (self) {
+ self.size = size;
+ self.format = format;
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.0, *)) {
+ UIGraphicsImageRendererFormat *uiformat = format.uiformat;
+ self.uirenderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:uiformat];
+ }
+#endif
+ }
+ return self;
+}
+
+- (UIImage *)imageWithActions:(NS_NOESCAPE SDGraphicsImageDrawingActions)actions {
+ NSParameterAssert(actions);
+#if SD_UIKIT
+ if (@available(iOS 10.0, tvOS 10.0, *)) {
+ UIGraphicsImageDrawingActions uiactions = ^(UIGraphicsImageRendererContext *rendererContext) {
+ if (actions) {
+ actions(rendererContext.CGContext);
+ }
+ };
+ return [self.uirenderer imageWithActions:uiactions];
+ } else {
+#endif
+ SDGraphicsBeginImageContextWithOptions(self.size, self.format.opaque, self.format.scale);
+ CGContextRef context = SDGraphicsGetCurrentContext();
+ if (actions) {
+ actions(context);
+ }
+ UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
+ SDGraphicsEndImageContext();
+ return image;
+#if SD_UIKIT
+ }
+#endif
+}
+
+@end
diff --git a/SDWebImage/Core/SDImageCache.h b/SDWebImage/Core/SDImageCache.h
index 688d3fc2..f2735a36 100644
--- a/SDWebImage/Core/SDImageCache.h
+++ b/SDWebImage/Core/SDImageCache.h
@@ -162,6 +162,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES. If NO, the completion block is called synchronously
* @param completionBlock A block executed after the operation is finished
+ * @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG.
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
@@ -178,6 +179,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES. If NO, the completion block is called synchronously
* @param completionBlock A block executed after the operation is finished
+ * @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG.
*/
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
@@ -225,8 +227,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
#pragma mark - Query and Retrieve Ops
/**
- * Asynchronously queries the cache with operation and call the completion when done.
- * Query the image data for the given key synchronously.
+ * Synchronously query the image data for the given key in disk cache. You can decode the image data to image after loaded.
*
* @param key The unique key used to store the wanted image
* @return The image data for the given key, or nil if not found.
@@ -234,9 +235,18 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key;
/**
- * Operation that queries the cache asynchronously and call the completion when done.
+ * Asynchronously query the image data for the given key in disk cache. You can decode the image data to image after loaded.
*
- * @param key The unique key used to store the wanted image
+ * @param key The unique key used to store the wanted image
+ * @param completionBlock the block to be executed when the query is done.
+ * @note the completion block will be always executed on the main queue
+ */
+- (void)diskImageDataQueryForKey:(nullable NSString *)key completion:(nullable SDImageCacheQueryDataCompletionBlock)completionBlock;
+
+/**
+ * Asynchronously queries the cache with operation and call the completion when done.
+ *
+ * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
* @param doneBlock The completion block. Will not get called if the operation is cancelled
*
* @return a NSOperation instance containing the cache op
@@ -246,7 +256,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
/**
* Asynchronously queries the cache with operation and call the completion when done.
*
- * @param key The unique key used to store the wanted image
+ * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
* @param options A mask to specify options to use for this cache query
* @param doneBlock The completion block. Will not get called if the operation is cancelled
*
@@ -257,7 +267,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
/**
* Asynchronously queries the cache with operation and call the completion when done.
*
- * @param key The unique key used to store the wanted image
+ * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
* @param options A mask to specify options to use for this cache query
* @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
* @param doneBlock The completion block. Will not get called if the operation is cancelled
@@ -266,6 +276,19 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
+/**
+ * Asynchronously queries the cache with operation and call the completion when done.
+ *
+ * @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
+ * @param options A mask to specify options to use for this cache query
+ * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
+ * @param queryCacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediatelly.
+ * @param doneBlock The completion block. Will not get called if the operation is cancelled
+ *
+ * @return a NSOperation instance containing the cache op
+ */
+- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
+
/**
* Synchronously query the memory cache.
*
@@ -282,6 +305,16 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
*/
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
+/**
+ * Synchronously query the disk cache. With the options and context which may effect the image generation. (Such as transformer, animated image, thumbnail, etc)
+ *
+ * @param key The unique key used to store the image
+ * @param options A mask to specify options to use for this cache query
+ * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
+ * @return The image for the given key, or nil if not found.
+ */
+- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;
+
/**
* Synchronously query the cache (memory and or disk) after checking the memory cache.
*
@@ -290,6 +323,16 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
*/
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;
+/**
+ * Synchronously query the cache (memory and or disk) after checking the memory cache. With the options and context which may effect the image generation. (Such as transformer, animated image, thumbnail, etc)
+ *
+ * @param key The unique key used to store the image
+ * @param options A mask to specify options to use for this cache query
+ * @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
+ * @return The image for the given key, or nil if not found.
+ */
+- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;;
+
#pragma mark - Remove Ops
/**
diff --git a/SDWebImage/Core/SDImageCache.m b/SDWebImage/Core/SDImageCache.m
index 4c16f763..076fa43c 100644
--- a/SDWebImage/Core/SDImageCache.m
+++ b/SDWebImage/Core/SDImageCache.m
@@ -9,11 +9,11 @@
#import "SDImageCache.h"
#import "NSImage+Compatibility.h"
#import "SDImageCodersManager.h"
-#import "SDImageTransformer.h"
#import "SDImageCoderHelper.h"
#import "SDAnimatedImage.h"
#import "UIImage+MemoryCacheCost.h"
#import "UIImage+Metadata.h"
+#import "UIImage+ExtendedCacheData.h"
@interface SDImageCache ()
@@ -186,17 +186,55 @@
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
+ if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) {
+ // If image is custom animated image class, prefer its original animated data
+ data = [((id)image) animatedImageData];
+ }
if (!data && image) {
- // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
- SDImageFormat format;
- if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
- format = SDImageFormatPNG;
- } else {
- format = SDImageFormatJPEG;
+ // Check image's associated image format, may return .undefined
+ SDImageFormat format = image.sd_imageFormat;
+ if (format == SDImageFormatUndefined) {
+ // If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
+ if (image.sd_isAnimated) {
+ format = SDImageFormatGIF;
+ } else {
+ // If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
+ if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
+ format = SDImageFormatPNG;
+ } else {
+ format = SDImageFormatJPEG;
+ }
+ }
}
data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
}
[self _storeImageDataToDisk:data forKey:key];
+ if (image) {
+ // Check extended data
+ id extendedObject = image.sd_extendedObject;
+ if ([extendedObject conformsToProtocol:@protocol(NSCoding)]) {
+ NSData *extendedData;
+ if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
+ NSError *error;
+ extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject requiringSecureCoding:NO error:&error];
+ if (error) {
+ NSLog(@"NSKeyedArchiver archive failed with error: %@", error);
+ }
+ } else {
+ @try {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject];
+#pragma clang diagnostic pop
+ } @catch (NSException *exception) {
+ NSLog(@"NSKeyedArchiver archive failed with exception: %@", exception);
+ }
+ }
+ if (extendedData) {
+ [self.diskCache setExtendedData:extendedData forKey:key];
+ }
+ }
+ }
}
if (completionBlock) {
@@ -275,6 +313,17 @@
return [self.diskCache containsDataForKey:key];
}
+- (void)diskImageDataQueryForKey:(NSString *)key completion:(SDImageCacheQueryDataCompletionBlock)completionBlock {
+ dispatch_async(self.ioQueue, ^{
+ NSData *imageData = [self diskImageDataBySearchingAllPathsForKey:key];
+ if (completionBlock) {
+ dispatch_async(dispatch_get_main_queue(), ^{
+ completionBlock(imageData);
+ });
+ }
+ });
+}
+
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key {
if (!key) {
return nil;
@@ -292,7 +341,12 @@
}
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
- UIImage *diskImage = [self diskImageForKey:key];
+ return [self imageFromDiskCacheForKey:key options:0 context:nil];
+}
+
+- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context {
+ NSData *data = [self diskImageDataForKey:key];
+ UIImage *diskImage = [self diskImageForKey:key data:data options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
@@ -302,6 +356,10 @@
}
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
+ return [self imageFromCacheForKey:key options:0 context:nil];
+}
+
+- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context {
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
@@ -309,7 +367,7 @@
}
// Second check the disk cache...
- image = [self imageFromDiskCacheForKey:key];
+ image = [self imageFromDiskCacheForKey:key options:options context:context];
return image;
}
@@ -346,6 +404,32 @@
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options context:(SDWebImageContext *)context {
if (data) {
UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context);
+ if (image) {
+ // Check extended data
+ NSData *extendedData = [self.diskCache extendedDataForKey:key];
+ if (extendedData) {
+ id extendedObject;
+ if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
+ NSError *error;
+ NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:extendedData error:&error];
+ unarchiver.requiresSecureCoding = NO;
+ extendedObject = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error];
+ if (error) {
+ NSLog(@"NSKeyedUnarchiver unarchive failed with error: %@", error);
+ }
+ } else {
+ @try {
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+ extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData:extendedData];
+#pragma clang diagnostic pop
+ } @catch (NSException *exception) {
+ NSLog(@"NSKeyedUnarchiver unarchive failed with exception: %@", exception);
+ }
+ }
+ image.sd_extendedObject = extendedObject;
+ }
+ }
return image;
} else {
return nil;
@@ -361,22 +445,29 @@
}
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
+ return [self queryCacheOperationForKey:key options:options context:context cacheType:SDImageCacheTypeAll done:doneBlock];
+}
+
+- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
-
- id transformer = context[SDWebImageContextImageTransformer];
- if (transformer) {
- // grab the transformed disk image if transformer provided
- NSString *transformerKey = [transformer transformerKey];
- key = SDTransformedKeyForKey(key, transformerKey);
+ // Invalid cache type
+ if (queryCacheType == SDImageCacheTypeNone) {
+ if (doneBlock) {
+ doneBlock(nil, nil, SDImageCacheTypeNone);
+ }
+ return nil;
}
// First check the in-memory cache...
- UIImage *image = [self imageFromMemoryCacheForKey:key];
+ UIImage *image;
+ if (queryCacheType != SDImageCacheTypeDisk) {
+ image = [self imageFromMemoryCacheForKey:key];
+ }
if (image) {
if (options & SDImageCacheDecodeFirstFrameOnly) {
@@ -399,7 +490,7 @@
}
}
- BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
+ BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
@@ -634,6 +725,10 @@
#pragma mark - SDImageCache
- (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
+ return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock];
+}
+
+- (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
@@ -644,7 +739,7 @@
if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass;
- return [self queryCacheOperationForKey:key options:cacheOptions context:context done:completionBlock];
+ return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
}
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
diff --git a/SDWebImage/Core/SDImageCacheConfig.h b/SDWebImage/Core/SDImageCacheConfig.h
index 460fd06b..e77e128c 100644
--- a/SDWebImage/Core/SDImageCacheConfig.h
+++ b/SDWebImage/Core/SDImageCacheConfig.h
@@ -12,13 +12,21 @@
/// Image Cache Expire Type
typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
/**
- * When the image is accessed it will update this value
+ * When the image cache is accessed it will update this value
*/
SDImageCacheConfigExpireTypeAccessDate,
/**
- * The image was obtained from the disk cache (Default)
+ * When the image cache is created or modified it will update this value (Default)
*/
- SDImageCacheConfigExpireTypeModificationDate
+ SDImageCacheConfigExpireTypeModificationDate,
+ /**
+ * When the image cache is created it will update this value
+ */
+ SDImageCacheConfigExpireTypeCreationDate,
+ /**
+ * When the image cache is created, modified, renamed, file attribute updated (like permission, xattr) it will update this value
+ */
+ SDImageCacheConfigExpireTypeChangeDate,
};
/**
diff --git a/SDWebImage/Core/SDImageCacheDefine.h b/SDWebImage/Core/SDImageCacheDefine.h
index be4e0211..e2449bfd 100644
--- a/SDWebImage/Core/SDImageCacheDefine.h
+++ b/SDWebImage/Core/SDImageCacheDefine.h
@@ -36,6 +36,7 @@ typedef NS_ENUM(NSInteger, SDImageCacheType) {
};
typedef void(^SDImageCacheCheckCompletionBlock)(BOOL isInCache);
+typedef void(^SDImageCacheQueryDataCompletionBlock)(NSData * _Nullable data);
typedef void(^SDImageCacheCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);
typedef NSString * _Nullable (^SDImageCacheAdditionalCachePathBlock)(NSString * _Nonnull key);
typedef void(^SDImageCacheQueryCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
@@ -76,6 +77,23 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonn
context:(nullable SDWebImageContext *)context
completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock;
+/**
+ Query the cached image from image cache for given key. The operation can be used to cancel the query.
+ If image is cached in memory, completion is called synchronously, else aynchronously and depends on the options arg (See `SDWebImageQueryDiskSync`)
+
+ @param key The image cache key
+ @param options A mask to specify options to use for this query
+ @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
+ @param cacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediatelly.
+ @param completionBlock The completion block. Will not get called if the operation is cancelled
+ @return The operation for this query
+ */
+- (nullable id)queryImageForKey:(nullable NSString *)key
+ options:(SDWebImageOptions)options
+ context:(nullable SDWebImageContext *)context
+ cacheType:(SDImageCacheType)cacheType
+ completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock;
+
/**
Store the image into image cache for the given key. If cache type is memory only, completion is called synchronously, else aynchronously.
diff --git a/SDWebImage/Core/SDImageCacheDefine.m b/SDWebImage/Core/SDImageCacheDefine.m
index 99e57f1a..19db161a 100644
--- a/SDWebImage/Core/SDImageCacheDefine.m
+++ b/SDWebImage/Core/SDImageCacheDefine.m
@@ -18,11 +18,32 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
- SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
- if (context) {
- SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
- [mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
- coderOptions = [mutableCoderOptions copy];
+ NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
+ NSValue *thumbnailSizeValue;
+ BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
+ if (shouldScaleDown) {
+ CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
+ CGFloat dimension = ceil(sqrt(thumbnailPixels));
+ thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
+ }
+ if (context[SDWebImageContextImageThumbnailPixelSize]) {
+ thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
+ }
+
+ SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
+ mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
+ mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
+ mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
+ mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
+ mutableCoderOptions[SDImageCoderWebImageContext] = context;
+ SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
+
+ // Grab the image coder
+ id imageCoder;
+ if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) {
+ imageCoder = context[SDWebImageContextImageCoder];
+ } else {
+ imageCoder = [SDImageCodersManager sharedManager];
}
if (!decodeFirstFrame) {
@@ -44,7 +65,7 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
}
}
if (!image) {
- image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions];
+ image = [imageCoder decodedImageWithData:imageData options:coderOptions];
}
if (image) {
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
@@ -56,12 +77,7 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
shouldDecode = NO;
}
if (shouldDecode) {
- BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
- if (shouldScaleDown) {
- image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
- } else {
- image = [SDImageCoderHelper decodedImageWithImage:image];
- }
+ image = [SDImageCoderHelper decodedImageWithImage:image];
}
}
diff --git a/SDWebImage/Core/SDImageCachesManager.m b/SDWebImage/Core/SDImageCachesManager.m
index 6b6f7d8a..b6b13c12 100644
--- a/SDWebImage/Core/SDImageCachesManager.m
+++ b/SDWebImage/Core/SDImageCachesManager.m
@@ -85,6 +85,10 @@
#pragma mark - SDImageCache
- (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock {
+ return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock];
+}
+
+- (id)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock {
if (!key) {
return nil;
}
@@ -93,30 +97,30 @@
if (count == 0) {
return nil;
} else if (count == 1) {
- return [caches.firstObject queryImageForKey:key options:options context:context completion:completionBlock];
+ return [caches.firstObject queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
switch (self.queryOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id cache = caches.lastObject;
- return [cache queryImageForKey:key options:options context:context completion:completionBlock];
+ return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id cache = caches.firstObject;
- return [cache queryImageForKey:key options:options context:context completion:completionBlock];
+ return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
- [self concurrentQueryImageForKey:key options:options context:context completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
+ [self concurrentQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
return operation;
}
break;
case SDImageCachesManagerOperationPolicySerial: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
- [self serialQueryImageForKey:key options:options context:context completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
+ [self serialQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
return operation;
}
break;
@@ -279,11 +283,11 @@
#pragma mark - Concurrent Operation
-- (void)concurrentQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
+- (void)concurrentQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
for (id cache in enumerator) {
- [cache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
+ [cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
if (operation.isCancelled) {
// Cancelled
return;
@@ -422,7 +426,7 @@
#pragma mark - Serial Operation
-- (void)serialQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
+- (void)serialQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
id cache = enumerator.nextObject;
@@ -435,7 +439,7 @@
return;
}
@weakify(self);
- [cache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
+ [cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
@strongify(self);
if (operation.isCancelled) {
// Cancelled
@@ -455,7 +459,7 @@
return;
}
// Next
- [self serialQueryImageForKey:key options:options context:context completion:completionBlock enumerator:enumerator operation:operation];
+ [self serialQueryImageForKey:key options:options context:context cacheType:queryCacheType completion:completionBlock enumerator:enumerator operation:operation];
}];
}
diff --git a/SDWebImage/Core/SDImageCoder.h b/SDWebImage/Core/SDImageCoder.h
index 3b2049e5..fe4c5f27 100644
--- a/SDWebImage/Core/SDImageCoder.h
+++ b/SDWebImage/Core/SDImageCoder.h
@@ -21,12 +21,30 @@ typedef NSMutableDictionary SDImageCoderMutableOptions;
@note works for `SDImageCoder`.
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeFirstFrameOnly;
+
/**
A CGFloat value which is greater than or equal to 1.0. This value specify the image scale factor for decoding. If not provide, use 1.0. (NSNumber)
@note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`.
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleFactor;
+/**
+ A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format).
+ Defaults to YES.
+ @note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`.
+ */
+FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodePreserveAspectRatio;
+
+/**
+ A CGSize value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.preserveAspectRatio`) the value size.
+ Defaults to CGSizeZero, which means no thumbnail generation at all.
+ @note Supports for animated image as well.
+ @note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
+ @note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`.
+ */
+FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeThumbnailPixelSize;
+
+
// These options are for image encoding
/**
A Boolean value indicating whether to encode the first frame only for animated image during encoding. (NSNumber). If not provide, encode animated image if need.
@@ -39,13 +57,43 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeFirstFrame
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeCompressionQuality;
+/**
+ A UIColor(NSColor) value to used for non-alpha image encoding when the input image has alpha channel, the background color will be used to compose the alpha one. If not provide, use white color.
+ @note works for `SDImageCoder`
+ */
+FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeBackgroundColor;
+
+/**
+ A CGSize value indicating the max image resolution in pixels during encoding. For vector image, this also effect the output vector data information about width and height. The encoder will not generate the encoded image larger than this limit. Note it always use the aspect ratio of input image..
+ Defaults to CGSizeZero, which means no max size limit at all.
+ @note Supports for animated image as well.
+ @note The ouput image's width is limited to pixel size's width, the output image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
+ @note works for `SDImageCoder`
+ */
+FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeMaxPixelSize;
+
+/**
+ A NSUInteger value specify the max ouput data bytes size after encoding. Some lossy format like JPEG/HEIF supports the hint for codec to automatically reduce the quality and match the file size you want. Note this option will override the `SDImageCoderEncodeCompressionQuality`, because now the quality is decided by the encoder. (NSNumber)
+ @note This is a hint, no gurantee for output size because of compression algorithm limit. And this options does not works for vector images.
+ @note works for `SDImageCoder`
+ */
+FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeMaxFileSize;
+
+/**
+ A Boolean value indicating the encoding format should contains a thumbnail image into the output data. Only some of image format (like JPEG/HEIF/AVIF) support this behavior. The embed thumbnail will be used during next time thumbnail decoding (provided `.thumbnailPixelSize`), which is faster than full image thumbnail decoding. (NSNumber)
+ Defaults to NO, which does not embed any thumbnail.
+ @note The thumbnail image's pixel size is not defined, the encoder can choose the proper pixel size which is suitable for encoding quality.
+ @note works for `SDImageCoder`
+ */
+FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumbnail;
+
/**
A SDWebImageContext object which hold the original context options from top-level API. (SDWebImageContext)
This option is ignored for all built-in coders and take no effect.
But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data inforamtion only.
See `SDWebImageContext` for more detailed information.
*/
-FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext;
+FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext API_DEPRECATED("The coder component will be seperated from Core subspec in the future. Update your code to not rely on this context option.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));;
#pragma mark - Coder
/**
diff --git a/SDWebImage/Core/SDImageCoder.m b/SDWebImage/Core/SDImageCoder.m
index c963376b..0fda1983 100644
--- a/SDWebImage/Core/SDImageCoder.m
+++ b/SDWebImage/Core/SDImageCoder.m
@@ -10,8 +10,14 @@
SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly";
SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor";
+SDImageCoderOption const SDImageCoderDecodePreserveAspectRatio = @"decodePreserveAspectRatio";
+SDImageCoderOption const SDImageCoderDecodeThumbnailPixelSize = @"decodeThumbnailPixelSize";
SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";
+SDImageCoderOption const SDImageCoderEncodeBackgroundColor = @"encodeBackgroundColor";
+SDImageCoderOption const SDImageCoderEncodeMaxPixelSize = @"encodeMaxPixelSize";
+SDImageCoderOption const SDImageCoderEncodeMaxFileSize = @"encodeMaxFileSize";
+SDImageCoderOption const SDImageCoderEncodeEmbedThumbnail = @"encodeEmbedThumbnail";
SDImageCoderOption const SDImageCoderWebImageContext = @"webImageContext";
diff --git a/SDWebImage/Core/SDImageCoderHelper.h b/SDWebImage/Core/SDImageCoderHelper.h
index dcf1da2b..5dbd523c 100644
--- a/SDWebImage/Core/SDImageCoderHelper.h
+++ b/SDWebImage/Core/SDImageCoderHelper.h
@@ -73,6 +73,16 @@
*/
+ (CGImageRef _Nullable)CGImageCreateDecoded:(_Nonnull CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation CF_RETURNS_RETAINED;
+/**
+ Create a scaled CGImage by the provided CGImage and size. This follows The Create Rule and you are response to call release after usage.
+ It will detect whether the image size matching the scale size, if not, stretch the image to the target size.
+
+ @param cgImage The CGImage
+ @param size The scale size in pixel.
+ @return A new created scaled image
+ */
++ (CGImageRef _Nullable)CGImageCreateScaled:(_Nonnull CGImageRef)cgImage size:(CGSize)size CF_RETURNS_RETAINED;
+
/**
Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image
@param image The image to be decoded
@@ -89,6 +99,12 @@
*/
+ (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes;
+/**
+ Control the default limit bytes to scale down larget images.
+ This value must be larger than or equal to 1MB. Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS.
+ */
+@property (class, readwrite) NSUInteger defaultScaleDownLimitBytes;
+
#if SD_UIKIT || SD_WATCH
/**
Convert an EXIF image orientation to an iOS one.
diff --git a/SDWebImage/Core/SDImageCoderHelper.m b/SDWebImage/Core/SDImageCoderHelper.m
index 52ab2ea3..c0547dc6 100644
--- a/SDWebImage/Core/SDImageCoderHelper.m
+++ b/SDWebImage/Core/SDImageCoderHelper.m
@@ -12,35 +12,35 @@
#import "NSData+ImageContentType.h"
#import "SDAnimatedImageRep.h"
#import "UIImage+ForceDecode.h"
+#import "SDAssociatedObject.h"
#import "UIImage+Metadata.h"
+#import "SDInternalMacros.h"
+#import
+
+static inline size_t SDByteAlign(size_t size, size_t alignment) {
+ return ((size + (alignment - 1)) / alignment) * alignment;
+}
-#if SD_UIKIT || SD_WATCH
static const size_t kBytesPerPixel = 4;
static const size_t kBitsPerComponent = 8;
+static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
+static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
/*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 60.
* Suggested value for iPad2 and iPhone 4: 120.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
*/
-static const CGFloat kDestImageSizeMB = 60.f;
-
-/*
- * Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
- * Suggested value for iPad1 and iPhone 3GS: 20.
- * Suggested value for iPad2 and iPhone 4: 40.
- * Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
- */
-static const CGFloat kSourceImageTileSizeMB = 20.f;
-
-static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
-static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
-static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
-static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
+#if SD_MAC
+static CGFloat kDestImageLimitBytes = 90.f * kBytesPerMB;
+#elif SD_UIKIT
+static CGFloat kDestImageLimitBytes = 60.f * kBytesPerMB;
+#elif SD_WATCH
+static CGFloat kDestImageLimitBytes = 30.f * kBytesPerMB;
+#endif
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
-#endif
@implementation SDImageCoderHelper
@@ -277,10 +277,57 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return newImageRef;
}
++ (CGImageRef)CGImageCreateScaled:(CGImageRef)cgImage size:(CGSize)size {
+ if (!cgImage) {
+ return NULL;
+ }
+ size_t width = CGImageGetWidth(cgImage);
+ size_t height = CGImageGetHeight(cgImage);
+ if (width == size.width && height == size.height) {
+ CGImageRetain(cgImage);
+ return cgImage;
+ }
+
+ __block vImage_Buffer input_buffer = {}, output_buffer = {};
+ @onExit {
+ if (input_buffer.data) free(input_buffer.data);
+ if (output_buffer.data) free(output_buffer.data);
+ };
+ BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
+ // iOS display alpha info (BGRA8888/BGRX8888)
+ CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
+ bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
+ vImage_CGImageFormat format = (vImage_CGImageFormat) {
+ .bitsPerComponent = 8,
+ .bitsPerPixel = 32,
+ .colorSpace = NULL,
+ .bitmapInfo = bitmapInfo,
+ .version = 0,
+ .decode = NULL,
+ .renderingIntent = kCGRenderingIntentDefault,
+ };
+
+ vImage_Error a_ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags);
+ if (a_ret != kvImageNoError) return NULL;
+ output_buffer.width = MAX(size.width, 0);
+ output_buffer.height = MAX(size.height, 0);
+ output_buffer.rowBytes = SDByteAlign(output_buffer.width * 4, 64);
+ output_buffer.data = malloc(output_buffer.rowBytes * output_buffer.height);
+ if (!output_buffer.data) return NULL;
+
+ vImage_Error ret = vImageScale_ARGB8888(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
+ if (ret != kvImageNoError) return NULL;
+
+ CGImageRef outputImage = vImageCreateCGImageFromBuffer(&output_buffer, &format, NULL, NULL, kvImageNoFlags, &ret);
+ if (ret != kvImageNoError) {
+ CGImageRelease(outputImage);
+ return NULL;
+ }
+
+ return outputImage;
+}
+
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
-#if SD_MAC
- return image;
-#else
if (![self shouldDecodeImage:image]) {
return image;
}
@@ -289,18 +336,18 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
if (!imageRef) {
return image;
}
+#if SD_MAC
+ UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
+#else
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
- CGImageRelease(imageRef);
- decodedImage.sd_isDecoded = YES;
- decodedImage.sd_imageFormat = image.sd_imageFormat;
- return decodedImage;
#endif
+ CGImageRelease(imageRef);
+ SDImageCopyAssociatedObject(image, decodedImage);
+ decodedImage.sd_isDecoded = YES;
+ return decodedImage;
}
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
-#if SD_MAC
- return image;
-#else
if (![self shouldDecodeImage:image]) {
return image;
}
@@ -311,13 +358,11 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
CGFloat destTotalPixels;
CGFloat tileTotalPixels;
- if (bytes > 0) {
- destTotalPixels = bytes / kBytesPerPixel;
- tileTotalPixels = destTotalPixels / 3;
- } else {
- destTotalPixels = kDestTotalPixels;
- tileTotalPixels = kTileTotalPixels;
+ if (bytes == 0) {
+ bytes = kDestImageLimitBytes;
}
+ destTotalPixels = bytes / kBytesPerPixel;
+ tileTotalPixels = destTotalPixels / 3;
CGContextRef destContext;
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
@@ -420,16 +465,30 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
if (destImageRef == NULL) {
return image;
}
+#if SD_MAC
+ UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
+#else
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
+#endif
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
+ SDImageCopyAssociatedObject(image, destImage);
destImage.sd_isDecoded = YES;
- destImage.sd_imageFormat = image.sd_imageFormat;
return destImage;
}
-#endif
+}
+
++ (NSUInteger)defaultScaleDownLimitBytes {
+ return kDestImageLimitBytes;
+}
+
++ (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes {
+ if (defaultScaleDownLimitBytes < kBytesPerMB) {
+ return;
+ }
+ kDestImageLimitBytes = defaultScaleDownLimitBytes;
}
#if SD_UIKIT || SD_WATCH
@@ -503,18 +562,21 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
#endif
#pragma mark - Helper Fuction
-#if SD_UIKIT || SD_WATCH
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
- // Avoid extra decode
- if (image.sd_isDecoded) {
- return NO;
- }
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
+ // Avoid extra decode
+ if (image.sd_isDecoded) {
+ return NO;
+ }
// do not decode animated images
- if (image.images != nil) {
+ if (image.sd_isAnimated) {
+ return NO;
+ }
+ // do not decode vector images
+ if (image.sd_isVector) {
return NO;
}
@@ -533,11 +595,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return NO;
}
CGFloat destTotalPixels;
- if (bytes > 0) {
- destTotalPixels = bytes / kBytesPerPixel;
- } else {
- destTotalPixels = kDestTotalPixels;
+ if (bytes == 0) {
+ bytes = kDestImageLimitBytes;
}
+ destTotalPixels = bytes / kBytesPerPixel;
if (destTotalPixels <= kPixelsPerMB) {
// Too small to scale down
return NO;
@@ -551,7 +612,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return shouldScaleDown;
}
-#endif
static inline CGAffineTransform SDCGContextTransformFromOrientation(CGImagePropertyOrientation orientation, CGSize size) {
// Inspiration from @libfeihu
diff --git a/SDWebImage/Core/SDImageGraphics.h b/SDWebImage/Core/SDImageGraphics.h
index 67019c5b..131d6850 100644
--- a/SDWebImage/Core/SDImageGraphics.h
+++ b/SDWebImage/Core/SDImageGraphics.h
@@ -13,6 +13,7 @@
These following graphics context method are provided to easily write cross-platform(AppKit/UIKit) code.
For UIKit, these methods just call the same method in `UIGraphics.h`. See the documentation for usage.
For AppKit, these methods use `NSGraphicsContext` to create image context and match the behavior like UIKit.
+ @note If you don't care bitmap format (ARGB8888) and just draw image, use `SDGraphicsImageRenderer` instead. It's more performant on RAM usage.`
*/
/// Returns the current graphics context.
diff --git a/SDWebImage/Core/SDImageIOAnimatedCoder.m b/SDWebImage/Core/SDImageIOAnimatedCoder.m
index b554fceb..df6415c3 100644
--- a/SDWebImage/Core/SDImageIOAnimatedCoder.m
+++ b/SDWebImage/Core/SDImageIOAnimatedCoder.m
@@ -12,6 +12,12 @@
#import "NSData+ImageContentType.h"
#import "SDImageCoderHelper.h"
#import "SDAnimatedImageRep.h"
+#import "UIImage+ForceDecode.h"
+
+// Specify DPI for vector format in CGImageSource, like PDF
+static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI";
+// Specify File Size for lossy format encoding, like JPEG
+static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
@interface SDImageIOCoderFrame : NSObject
@@ -32,6 +38,8 @@
NSUInteger _frameCount;
NSArray *_frames;
BOOL _finished;
+ BOOL _preserveAspectRatio;
+ CGSize _thumbnailSize;
}
- (void)dealloc
@@ -114,8 +122,12 @@
}
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
+ NSDictionary *options = @{
+ (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
+ (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
+ };
NSTimeInterval frameDuration = 0.1;
- CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
+ CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
if (!cfFrameProperties) {
return frameDuration;
}
@@ -145,6 +157,93 @@
return frameDuration;
}
++ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary *)options {
+ // Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
+ // Parse the image properties
+ NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
+ NSUInteger pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue];
+ NSUInteger pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue];
+ CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
+ if (!exifOrientation) {
+ exifOrientation = kCGImagePropertyOrientationUp;
+ }
+
+ CFStringRef uttype = CGImageSourceGetType(source);
+ // Check vector format
+ BOOL isVector = NO;
+ if ([NSData sd_imageFormatFromUTType:uttype] == SDImageFormatPDF) {
+ isVector = YES;
+ }
+
+ NSMutableDictionary *decodingOptions;
+ if (options) {
+ decodingOptions = [NSMutableDictionary dictionaryWithDictionary:options];
+ } else {
+ decodingOptions = [NSMutableDictionary dictionary];
+ }
+ CGImageRef imageRef;
+ if (thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) {
+ if (isVector) {
+ if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
+ // Provide the default pixel count for vector images, simply just use the screen size
+#if SD_WATCH
+ thumbnailSize = WKInterfaceDevice.currentDevice.screenBounds.size;
+#elif SD_UIKIT
+ thumbnailSize = UIScreen.mainScreen.bounds.size;
+#elif SD_MAC
+ thumbnailSize = NSScreen.mainScreen.frame.size;
+#endif
+ }
+ CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
+ NSUInteger DPIPerPixel = 2;
+ NSUInteger rasterizationDPI = maxPixelSize * DPIPerPixel;
+ decodingOptions[kSDCGImageSourceRasterizationDPI] = @(rasterizationDPI);
+ }
+ imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)decodingOptions);
+ } else {
+ decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
+ CGFloat maxPixelSize;
+ if (preserveAspectRatio) {
+ CGFloat pixelRatio = pixelWidth / pixelHeight;
+ CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
+ if (pixelRatio > thumbnailRatio) {
+ maxPixelSize = thumbnailSize.width;
+ } else {
+ maxPixelSize = thumbnailSize.height;
+ }
+ } else {
+ maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
+ }
+ decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize);
+ decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageIfAbsent] = @(YES);
+ imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)decodingOptions);
+ }
+ if (!imageRef) {
+ return nil;
+ }
+
+ if (thumbnailSize.width > 0 && thumbnailSize.height > 0) {
+ if (preserveAspectRatio) {
+ // kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice
+ exifOrientation = kCGImagePropertyOrientationUp;
+ } else {
+ // `CGImageSourceCreateThumbnailAtIndex` take only pixel dimension, if not `preserveAspectRatio`, we should manual scale to the target size
+ CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize];
+ CGImageRelease(imageRef);
+ imageRef = scaledImageRef;
+ }
+ }
+
+#if SD_UIKIT || SD_WATCH
+ UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
+ UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation];
+#else
+ UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
+#endif
+ CGImageRelease(imageRef);
+ return image;
+}
+
#pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
@@ -160,14 +259,34 @@
scale = MAX([scaleFactor doubleValue], 1);
}
+ CGSize thumbnailSize = CGSizeZero;
+ NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
+ if (thumbnailSizeValue != nil) {
#if SD_MAC
- SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
- NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
- imageRep.size = size;
- NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
- [animatedImage addRepresentation:imageRep];
- return animatedImage;
+ thumbnailSize = thumbnailSizeValue.sizeValue;
#else
+ thumbnailSize = thumbnailSizeValue.CGSizeValue;
+#endif
+ }
+
+ BOOL preserveAspectRatio = YES;
+ NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
+ if (preserveAspectRatioValue != nil) {
+ preserveAspectRatio = preserveAspectRatioValue.boolValue;
+ }
+
+#if SD_MAC
+ // If don't use thumbnail, prefers the built-in generation of frames (GIF/APNG)
+ // Which decode frames in time and reduce memory usage
+ if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
+ SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
+ NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
+ imageRep.size = size;
+ NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
+ [animatedImage addRepresentation:imageRep];
+ return animatedImage;
+ }
+#endif
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
@@ -178,19 +297,17 @@
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
if (decodeFirstFrame || count <= 1) {
- animatedImage = [[UIImage alloc] initWithData:data scale:scale];
+ animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
} else {
NSMutableArray *frames = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
- CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
- if (!imageRef) {
+ UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
+ if (!image) {
continue;
}
NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
- UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
- CGImageRelease(imageRef);
SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
@@ -205,7 +322,6 @@
CFRelease(source);
return animatedImage;
-#endif
}
#pragma mark - Progressive Decode
@@ -225,6 +341,22 @@
scale = MAX([scaleFactor doubleValue], 1);
}
_scale = scale;
+ CGSize thumbnailSize = CGSizeZero;
+ NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
+ if (thumbnailSizeValue != nil) {
+ #if SD_MAC
+ thumbnailSize = thumbnailSizeValue.sizeValue;
+ #else
+ thumbnailSize = thumbnailSizeValue.CGSizeValue;
+ #endif
+ }
+ _thumbnailSize = thumbnailSize;
+ BOOL preserveAspectRatio = YES;
+ NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
+ if (preserveAspectRatioValue != nil) {
+ preserveAspectRatio = preserveAspectRatioValue.boolValue;
+ }
+ _preserveAspectRatio = preserveAspectRatio;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
@@ -246,7 +378,11 @@
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
if (_width + _height == 0) {
- CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
+ NSDictionary *options = @{
+ (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
+ (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
+ };
+ CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, (__bridge CFDictionaryRef)options);
if (properties) {
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
@@ -265,20 +401,13 @@
if (_width + _height > 0) {
// Create the image
- CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
-
- if (partialImageRef) {
- CGFloat scale = _scale;
- NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
- if (scaleFactor != nil) {
- scale = MAX([scaleFactor doubleValue], 1);
- }
-#if SD_UIKIT || SD_WATCH
- image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:UIImageOrientationUp];
-#else
- image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:kCGImagePropertyOrientationUp];
-#endif
- CGImageRelease(partialImageRef);
+ CGFloat scale = _scale;
+ NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
+ if (scaleFactor != nil) {
+ scale = MAX([scaleFactor doubleValue], 1);
+ }
+ image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil];
+ if (image) {
image.sd_imageFormat = self.class.imageFormat;
}
}
@@ -295,6 +424,11 @@
if (!image) {
return nil;
}
+ CGImageRef imageRef = image.CGImage;
+ if (!imageRef) {
+ // Earily return, supports CGImage only
+ return nil;
+ }
if (format != self.class.imageFormat) {
return nil;
@@ -312,29 +446,69 @@
return nil;
}
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
+ // Encoding Options
double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
+ CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
+ if (backgroundColor) {
+ properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
+ }
+ CGSize maxPixelSize = CGSizeZero;
+ NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
+ if (maxPixelSizeValue != nil) {
+#if SD_MAC
+ maxPixelSize = maxPixelSizeValue.sizeValue;
+#else
+ maxPixelSize = maxPixelSizeValue.CGSizeValue;
+#endif
+ }
+ NSUInteger pixelWidth = CGImageGetWidth(imageRef);
+ NSUInteger pixelHeight = CGImageGetHeight(imageRef);
+ CGFloat finalPixelSize = 0;
+ if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > 0 && pixelHeight > 0) {
+ CGFloat pixelRatio = pixelWidth / pixelHeight;
+ CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
+ if (pixelRatio > maxPixelSizeRatio) {
+ finalPixelSize = maxPixelSize.width;
+ } else {
+ finalPixelSize = maxPixelSize.height;
+ }
+ properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
+ }
+ NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
+ if (maxFileSize > 0) {
+ properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
+ // Remove the quality if we have file size limit
+ properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
+ }
+ BOOL embedThumbnail = NO;
+ if (options[SDImageCoderEncodeEmbedThumbnail]) {
+ embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
+ }
+ properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue];
if (encodeFirstFrame || frames.count == 0) {
// for static single images
- CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
+ CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
} else {
// for animated images
NSUInteger loopCount = image.sd_imageLoopCount;
- NSDictionary *containerProperties = @{self.class.loopCountProperty : @(loopCount)};
- properties[self.class.dictionaryProperty] = containerProperties;
- CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)properties);
+ NSDictionary *containerProperties = @{
+ self.class.dictionaryProperty: @{self.class.loopCountProperty : @(loopCount)}
+ };
+ // container level properties (applies for `CGImageDestinationSetProperties`, not individual frames)
+ CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)containerProperties);
for (size_t i = 0; i < frames.count; i++) {
SDImageFrame *frame = frames[i];
NSTimeInterval frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
- NSDictionary *frameProperties = @{self.class.dictionaryProperty : @{self.class.delayTimeProperty : @(frameDuration)}};
- CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
+ properties[self.class.dictionaryProperty] = @{self.class.delayTimeProperty : @(frameDuration)};
+ CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)properties);
}
}
// Finalize the destination.
@@ -370,6 +544,22 @@
scale = MAX([scaleFactor doubleValue], 1);
}
_scale = scale;
+ CGSize thumbnailSize = CGSizeZero;
+ NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
+ if (thumbnailSizeValue != nil) {
+ #if SD_MAC
+ thumbnailSize = thumbnailSizeValue.sizeValue;
+ #else
+ thumbnailSize = thumbnailSizeValue.CGSizeValue;
+ #endif
+ }
+ _thumbnailSize = thumbnailSize;
+ BOOL preserveAspectRatio = YES;
+ NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
+ if (preserveAspectRatioValue != nil) {
+ preserveAspectRatio = preserveAspectRatioValue.boolValue;
+ }
+ _preserveAspectRatio = preserveAspectRatio;
_imageSource = imageSource;
_imageData = data;
#if SD_UIKIT
@@ -421,23 +611,20 @@
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
- CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
- if (!imageRef) {
+ if (index >= _frameCount) {
return nil;
}
- // Image/IO create CGImage does not decode, so we do this because this is called background queue, this can avoid main queue block when rendering(especially when one more imageViews use the same image instance)
- CGImageRef newImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
- if (!newImageRef) {
- newImageRef = imageRef;
- } else {
- CGImageRelease(imageRef);
+ // Animated Image should not use the CGContext solution to force decode. Prefers to use Image/IO built in method, which is safer and memory friendly, see https://github.com/SDWebImage/SDWebImage/issues/2961
+ NSDictionary *options = @{
+ (__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
+ (__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
+ };
+ UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:options];
+ if (!image) {
+ return nil;
}
-#if SD_MAC
- UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:kCGImagePropertyOrientationUp];
-#else
- UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:UIImageOrientationUp];
-#endif
- CGImageRelease(newImageRef);
+ image.sd_imageFormat = self.class.imageFormat;
+ image.sd_isDecoded = YES;;
return image;
}
diff --git a/SDWebImage/Core/SDImageIOCoder.m b/SDWebImage/Core/SDImageIOCoder.m
index a6fa10af..11565e12 100644
--- a/SDWebImage/Core/SDImageIOCoder.m
+++ b/SDWebImage/Core/SDImageIOCoder.m
@@ -12,6 +12,10 @@
#import
#import "UIImage+Metadata.h"
#import "SDImageHEICCoderInternal.h"
+#import "SDImageIOAnimatedCoderInternal.h"
+
+// Specify File Size for lossy format encoding, like JPEG
+static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
@implementation SDImageIOCoder {
size_t _width, _height;
@@ -19,6 +23,8 @@
CGImageSourceRef _imageSource;
CGFloat _scale;
BOOL _finished;
+ BOOL _preserveAspectRatio;
+ CGSize _thumbnailSize;
}
- (void)dealloc {
@@ -74,7 +80,33 @@
scale = MAX([scaleFactor doubleValue], 1) ;
}
- UIImage *image = [[UIImage alloc] initWithData:data scale:scale];
+ CGSize thumbnailSize = CGSizeZero;
+ NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
+ if (thumbnailSizeValue != nil) {
+#if SD_MAC
+ thumbnailSize = thumbnailSizeValue.sizeValue;
+#else
+ thumbnailSize = thumbnailSizeValue.CGSizeValue;
+#endif
+ }
+
+ BOOL preserveAspectRatio = YES;
+ NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
+ if (preserveAspectRatioValue != nil) {
+ preserveAspectRatio = preserveAspectRatioValue.boolValue;
+ }
+
+ CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
+ if (!source) {
+ return nil;
+ }
+
+ UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
+ CFRelease(source);
+ if (!image) {
+ return nil;
+ }
+
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
return image;
}
@@ -95,6 +127,22 @@
scale = MAX([scaleFactor doubleValue], 1);
}
_scale = scale;
+ CGSize thumbnailSize = CGSizeZero;
+ NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
+ if (thumbnailSizeValue != nil) {
+ #if SD_MAC
+ thumbnailSize = thumbnailSizeValue.sizeValue;
+ #else
+ thumbnailSize = thumbnailSizeValue.CGSizeValue;
+ #endif
+ }
+ _thumbnailSize = thumbnailSize;
+ BOOL preserveAspectRatio = YES;
+ NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
+ if (preserveAspectRatioValue != nil) {
+ preserveAspectRatio = preserveAspectRatioValue.boolValue;
+ }
+ _preserveAspectRatio = preserveAspectRatio;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
@@ -140,21 +188,13 @@
if (_width + _height > 0) {
// Create the image
- CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
-
- if (partialImageRef) {
- CGFloat scale = _scale;
- NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
- if (scaleFactor != nil) {
- scale = MAX([scaleFactor doubleValue], 1);
- }
-#if SD_UIKIT || SD_WATCH
- UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:_orientation];
- image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:imageOrientation];
-#else
- image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:_orientation];
-#endif
- CGImageRelease(partialImageRef);
+ CGFloat scale = _scale;
+ NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
+ if (scaleFactor != nil) {
+ scale = MAX([scaleFactor doubleValue], 1);
+ }
+ image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil];
+ if (image) {
CFStringRef uttype = CGImageSourceGetType(_imageSource);
image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];
}
@@ -184,9 +224,14 @@
if (!image) {
return nil;
}
+ CGImageRef imageRef = image.CGImage;
+ if (!imageRef) {
+ // Earily return, supports CGImage only
+ return nil;
+ }
if (format == SDImageFormatUndefined) {
- BOOL hasAlpha = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage];
+ BOOL hasAlpha = [SDImageCoderHelper CGImageContainsAlpha:imageRef];
if (hasAlpha) {
format = SDImageFormatPNG;
} else {
@@ -211,14 +256,52 @@
CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
#endif
properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation);
+ // Encoding Options
double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
+ CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
+ if (backgroundColor) {
+ properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
+ }
+ CGSize maxPixelSize = CGSizeZero;
+ NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
+ if (maxPixelSizeValue != nil) {
+#if SD_MAC
+ maxPixelSize = maxPixelSizeValue.sizeValue;
+#else
+ maxPixelSize = maxPixelSizeValue.CGSizeValue;
+#endif
+ }
+ NSUInteger pixelWidth = CGImageGetWidth(imageRef);
+ NSUInteger pixelHeight = CGImageGetHeight(imageRef);
+ if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > 0 && pixelHeight > 0) {
+ CGFloat pixelRatio = pixelWidth / pixelHeight;
+ CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
+ CGFloat finalPixelSize;
+ if (pixelRatio > maxPixelSizeRatio) {
+ finalPixelSize = maxPixelSize.width;
+ } else {
+ finalPixelSize = maxPixelSize.height;
+ }
+ properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
+ }
+ NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
+ if (maxFileSize > 0) {
+ properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
+ // Remove the quality if we have file size limit
+ properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
+ }
+ BOOL embedThumbnail = NO;
+ if (options[SDImageCoderEncodeEmbedThumbnail]) {
+ embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
+ }
+ properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
// Add your image to the destination.
- CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
+ CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {
diff --git a/SDWebImage/Core/SDImageLoader.m b/SDWebImage/Core/SDImageLoader.m
index 8cbbe4e0..c529954e 100644
--- a/SDWebImage/Core/SDImageLoader.m
+++ b/SDWebImage/Core/SDImageLoader.m
@@ -32,11 +32,32 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
- SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
- if (context) {
- SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
- [mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
- coderOptions = [mutableCoderOptions copy];
+ NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
+ NSValue *thumbnailSizeValue;
+ BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
+ if (shouldScaleDown) {
+ CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
+ CGFloat dimension = ceil(sqrt(thumbnailPixels));
+ thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
+ }
+ if (context[SDWebImageContextImageThumbnailPixelSize]) {
+ thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
+ }
+
+ SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
+ mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
+ mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
+ mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
+ mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
+ mutableCoderOptions[SDImageCoderWebImageContext] = context;
+ SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
+
+ // Grab the image coder
+ id imageCoder;
+ if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) {
+ imageCoder = context[SDWebImageContextImageCoder];
+ } else {
+ imageCoder = [SDImageCodersManager sharedManager];
}
if (!decodeFirstFrame) {
@@ -58,7 +79,7 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
}
}
if (!image) {
- image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions];
+ image = [imageCoder decodedImageWithData:imageData options:coderOptions];
}
if (image) {
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
@@ -71,12 +92,7 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
}
if (shouldDecode) {
- BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
- if (shouldScaleDown) {
- image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
- } else {
- image = [SDImageCoderHelper decodedImageWithImage:image];
- }
+ image = [SDImageCoderHelper decodedImageWithImage:image];
}
}
@@ -99,21 +115,41 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
- SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
- if (context) {
- SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
- [mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
- coderOptions = [mutableCoderOptions copy];
+ NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
+ NSValue *thumbnailSizeValue;
+ BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
+ if (shouldScaleDown) {
+ CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
+ CGFloat dimension = ceil(sqrt(thumbnailPixels));
+ thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
+ }
+ if (context[SDWebImageContextImageThumbnailPixelSize]) {
+ thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
}
+ SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
+ mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
+ mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
+ mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
+ mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
+ mutableCoderOptions[SDImageCoderWebImageContext] = context;
+ SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
+
+ // Grab the progressive image coder
id progressiveCoder = objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey);
if (!progressiveCoder) {
- // We need to create a new instance for progressive decoding to avoid conflicts
- for (idcoder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
- if ([coder conformsToProtocol:@protocol(SDProgressiveImageCoder)] &&
- [((id)coder) canIncrementalDecodeFromData:imageData]) {
- progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:coderOptions];
- break;
+ id imageCoder = context[SDWebImageContextImageCoder];
+ // Check the progressive coder if provided
+ if ([imageCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) {
+ progressiveCoder = [[[imageCoder class] alloc] initIncrementalWithOptions:coderOptions];
+ } else {
+ // We need to create a new instance for progressive decoding to avoid conflicts
+ for (id coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
+ if ([coder conformsToProtocol:@protocol(SDProgressiveImageCoder)] &&
+ [((id)coder) canIncrementalDecodeFromData:imageData]) {
+ progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:coderOptions];
+ break;
+ }
}
}
objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
diff --git a/SDWebImage/Core/SDImageTransformer.h b/SDWebImage/Core/SDImageTransformer.h
index f165cce5..5b6d535a 100644
--- a/SDWebImage/Core/SDImageTransformer.h
+++ b/SDWebImage/Core/SDImageTransformer.h
@@ -18,6 +18,16 @@
*/
FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * _Nonnull transformerKey);
+/**
+ Return the thumbnailed cache key which applied with specify thumbnailSize and preserveAspectRatio control.
+ @param key The original cache key
+ @param thumbnailPixelSize The thumbnail pixel size
+ @param preserveAspectRatio The preserve aspect ratio option
+ @return The thumbnailed cache key
+ @note If you have both transformer and thumbnail applied for image, call `SDThumbnailedKeyForKey` firstly and then with `SDTransformedKeyForKey`.`
+ */
+FOUNDATION_EXPORT NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullable key, CGSize thumbnailPixelSize, BOOL preserveAspectRatio);
+
/**
A transformer protocol to transform the image load from cache or from download.
You can provide transformer to cache and manager (Through the `transformer` property or context option `SDWebImageContextImageTransformer`).
@@ -38,10 +48,10 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab
Transform the image to another image.
@param image The image to be transformed
- @param key The cache key associated to the image
+ @param key The cache key associated to the image. This arg is a hint for image source, not always useful and should be nullable. In the future we will remove this arg.
@return The transformed image, or nil if transform failed
*/
-- (nullable UIImage *)transformedImageWithImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key;
+- (nullable UIImage *)transformedImageWithImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key API_DEPRECATED("The key arg will be removed in the future. Update your code and don't rely on that.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
@end
diff --git a/SDWebImage/Core/SDImageTransformer.m b/SDWebImage/Core/SDImageTransformer.m
index 26ee45cd..8e7a3e2a 100644
--- a/SDWebImage/Core/SDImageTransformer.m
+++ b/SDWebImage/Core/SDImageTransformer.m
@@ -7,7 +7,7 @@
*/
#import "SDImageTransformer.h"
-#import "UIColor+HexString.h"
+#import "UIColor+SDHexString.h"
#if SD_UIKIT || SD_MAC
#import
#endif
@@ -38,6 +38,11 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString *
}
}
+NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullable key, CGSize thumbnailPixelSize, BOOL preserveAspectRatio) {
+ NSString *thumbnailKey = [NSString stringWithFormat:@"Thumbnail({%f,%f},%d)", thumbnailPixelSize.width, thumbnailPixelSize.height, preserveAspectRatio];
+ return SDTransformedKeyForKey(key, thumbnailKey);
+}
+
@interface SDImagePipelineTransformer ()
@property (nonatomic, copy, readwrite, nonnull) NSArray> *transformers;
diff --git a/SDWebImage/Core/SDMemoryCache.h b/SDWebImage/Core/SDMemoryCache.h
index 8b415816..43c39e84 100644
--- a/SDWebImage/Core/SDMemoryCache.h
+++ b/SDWebImage/Core/SDMemoryCache.h
@@ -15,6 +15,7 @@
@protocol SDMemoryCache
@required
+
/**
Create a new memory cache instance with the specify cache config. You can check `maxMemoryCost` and `maxMemoryCount` used for memory cache.
@@ -25,7 +26,7 @@
/**
Returns the value associated with a given key.
-
+
@param key An object identifying the value. If nil, just return nil.
@return The value associated with key, or nil if no value is associated with key.
*/
@@ -33,7 +34,7 @@
/**
Sets the value of the specified key in the cache (0 cost).
-
+
@param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
@param key The key with which to associate the value. If nil, this method has no effect.
@discussion Unlike an NSMutableDictionary object, a cache does not copy the key
@@ -44,7 +45,7 @@
/**
Sets the value of the specified key in the cache, and associates the key-value
pair with the specified cost.
-
+
@param object The object to store in the cache. If nil, it calls `removeObjectForKey`.
@param key The key with which to associate the value. If nil, this method has no effect.
@param cost The cost with which to associate the key-value pair.
@@ -55,7 +56,7 @@
/**
Removes the value of the specified key in the cache.
-
+
@param key The key identifying the value to be removed. If nil, this method has no effect.
*/
- (void)removeObjectForKey:(nonnull id)key;
diff --git a/SDWebImage/Core/SDMemoryCache.m b/SDWebImage/Core/SDMemoryCache.m
index e3991994..b354b495 100644
--- a/SDWebImage/Core/SDMemoryCache.m
+++ b/SDWebImage/Core/SDMemoryCache.m
@@ -30,6 +30,7 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
+ self.delegate = nil;
}
- (instancetype)init {
@@ -54,14 +55,14 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
SDImageCacheConfig *config = self.config;
self.totalCostLimit = config.maxMemoryCost;
self.countLimit = config.maxMemoryCount;
-
+
[config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext];
[config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext];
-
+
#if SD_UIKIT
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
-
+
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification
diff --git a/SDWebImage/Core/SDWebImageCacheSerializer.h b/SDWebImage/Core/SDWebImageCacheSerializer.h
index 84c92a37..3c271b1f 100644
--- a/SDWebImage/Core/SDWebImageCacheSerializer.h
+++ b/SDWebImage/Core/SDWebImageCacheSerializer.h
@@ -17,6 +17,10 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i
*/
@protocol SDWebImageCacheSerializer
+/// Provide the image data associated to the image and store to disk cache
+/// @param image The loaded image
+/// @param data The original loaded image data
+/// @param imageURL The image URL
- (nullable NSData *)cacheDataWithImage:(nonnull UIImage *)image originalData:(nullable NSData *)data imageURL:(nullable NSURL *)imageURL;
@end
diff --git a/SDWebImage/Core/SDWebImageDefine.h b/SDWebImage/Core/SDWebImageDefine.h
index 8e60469b..96f71d68 100644
--- a/SDWebImage/Core/SDWebImageDefine.h
+++ b/SDWebImage/Core/SDWebImageDefine.h
@@ -122,9 +122,12 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageAvoidAutoSetImage = 1 << 10,
/**
- * By default, images are decoded respecting their original size. On iOS, this flag will scale down the
- * images to a size compatible with the constrained memory of devices.
- * This flag take no effect if `SDWebImageAvoidDecodeImage` is set. And it will be ignored if `SDWebImageProgressiveLoad` is set.
+ * By default, images are decoded respecting their original size.
+ * This flag will scale down the images to a size compatible with the constrained memory of devices.
+ * To control the limit memory bytes, check `SDImageCoderHelper.defaultScaleDownLimitBytes` (Defaults to 60MB on iOS)
+ * This will actually translate to use context option `.imageThumbnailPixelSize` from v5.5.0 (Defaults to (3966, 3966) on iOS). Previously does not.
+ * This flags effect the progressive and animated images as well from v5.5.0. Previously does not.
+ * @note If you need detail controls, it's better to use context option `imageThumbnailPixelSize` and `imagePreserveAspectRatio` instead.
*/
SDWebImageScaleDownLargeImages = 1 << 11,
@@ -184,6 +187,20 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
* Note this options is not compatible with `SDWebImageDecodeFirstFrameOnly`, which always produce a UIImage/NSImage.
*/
SDWebImageMatchAnimatedImageClass = 1 << 21,
+
+ /**
+ * By default, when we load the image from network, the image will be written to the cache (memory and disk, controlled by your `storeCacheType` context option)
+ * This maybe an asynchronously operation and the final `SDInternalCompletionBlock` callback does not gurantee the disk cache written is finished and may cause logic error. (For example, you modify the disk data just in completion block, however, the disk cache is not ready)
+ * If you need to process with the disk cache in the completion block, you should use this option to ensure the disk cache already been written when callback.
+ * Note if you use this when using the custom cache serializer, or using the transformer, we will also wait until the output image data written is finished.
+ */
+ SDWebImageWaitStoreCache = 1 << 22,
+
+ /**
+ * We usually don't apply transform on vector images, because vector images supports dynamically changing to any size, rasterize to a fixed size will loss details. To modify vector images, you can process the vector data at runtime (such as modifying PDF tag / SVG element).
+ * Use this flag to transform them anyway.
+ */
+ SDWebImageTransformVectorImage = 1 << 23,
};
@@ -196,11 +213,31 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextSetIma
/**
A SDWebImageManager instance to control the image download and cache process using in UIImageView+WebCache category and likes. If not provided, use the shared manager (SDWebImageManager *)
+ @deprecated Deprecated in the future. This context options can be replaced by other context option control like `.imageCache`, `.imageLoader`, `.imageTransofmer` (See below), which already matches all the properties in SDWebImageManager.
*/
-FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager;
+FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager API_DEPRECATED("Use individual context option like .imageCache, .imageLoader and .imageTransformer instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
/**
- A id instance which conforms `SDImageTransformer` protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. (id)
+ A id instance which conforms to `SDImageCache` protocol. It's used to override the image mananger's cache during the image loading pipeline.
+ In other word, if you just want to specify a custom cache during image loading, you don't need to re-create a dummy SDWebImageManager instance with the cache. If not provided, use the image manager's cache (id)
+ */
+FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageCache;
+
+/**
+ A id instance which conforms to `SDImageLoader` protocol. It's used to override the image mananger's loader during the image loading pipeline.
+ In other word, if you just want to specify a custom loader during image loading, you don't need to re-create a dummy SDWebImageManager instance with the loader. If not provided, use the image manager's cache (id)
+*/
+FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageLoader;
+
+/**
+ A id instance which conforms to `SDImageCoder` protocol. It's used to override the default image codre for image decoding(including progressive) and encoding during the image loading process.
+ If you use this context option, we will not always use `SDImageCodersManager.shared` to loop through all registered coders and find the suitable one. Instead, we will arbitrarily use the exact provided coder without extra checking (We may not call `canDecodeFromData:`).
+ @note This is only useful for cases which you can ensure the loading url matches your coder, or you find it's too hard to write a common coder which can used for generic usage. This will bind the loading url with the coder logic, which is not always a good design, but possible. (id)
+*/
+FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageCoder;
+
+/**
+ A id instance which conforms `SDImageTransformer` protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. If you pass NSNull, the transformer feature will be disabled. (id)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageTransformer;
@@ -209,6 +246,25 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageT
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageScaleFactor;
+/**
+ A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format).
+ Defaults to YES. (NSNumber)
+ */
+FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImagePreserveAspectRatio;
+
+/**
+ A CGSize raw value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.imagePreserveAspectRatio`) the value size.
+ @note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
+ Defaults to CGSizeZero, which means no thumbnail generation at all. (NSValue)
+ */
+FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageThumbnailPixelSize;
+
+/**
+ A SDImageCacheType raw value which specify the source of cache to query. Specify `SDImageCacheTypeDisk` to query from disk cache only; `SDImageCacheTypeMemory` to query from memory only. And `SDImageCacheTypeAll` to query from both memory cache and disk cache. Specify `SDImageCacheTypeNone` is invalid and totally ignore the cache query.
+ If not provide or the value is invalid, we will use `SDImageCacheTypeAll`. (NSNumber)
+ */
+FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextQueryCacheType;
+
/**
A SDImageCacheType raw value which specify the store cache type when the image has just been downloaded and will be stored to the cache. Specify `SDImageCacheTypeNone` to disable cache storage; `SDImageCacheTypeDisk` to store in disk cache only; `SDImageCacheTypeMemory` to store in memory only. And `SDImageCacheTypeAll` to store in both memory cache and disk cache.
If you use image transformer feature, this actually apply for the transformed image, but not the original image itself. Use `SDWebImageContextOriginalStoreCacheType` if you want to control the original image's store cache type at the same time.
diff --git a/SDWebImage/Core/SDWebImageDefine.m b/SDWebImage/Core/SDWebImageDefine.m
index 0b2f2b5f..2809af69 100644
--- a/SDWebImage/Core/SDWebImageDefine.m
+++ b/SDWebImage/Core/SDWebImageDefine.m
@@ -9,6 +9,7 @@
#import "SDWebImageDefine.h"
#import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h"
+#import "SDAssociatedObject.h"
#pragma mark - Image scale
@@ -110,8 +111,7 @@ inline UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage *
scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:kCGImagePropertyOrientationUp];
#endif
}
- scaledImage.sd_isIncremental = image.sd_isIncremental;
- scaledImage.sd_imageFormat = image.sd_imageFormat;
+ SDImageCopyAssociatedObject(image, scaledImage);
return scaledImage;
}
@@ -120,8 +120,14 @@ inline UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage *
SDWebImageContextOption const SDWebImageContextSetImageOperationKey = @"setImageOperationKey";
SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
+SDWebImageContextOption const SDWebImageContextImageCache = @"imageCache";
+SDWebImageContextOption const SDWebImageContextImageLoader = @"imageLoader";
+SDWebImageContextOption const SDWebImageContextImageCoder = @"imageCoder";
SDWebImageContextOption const SDWebImageContextImageTransformer = @"imageTransformer";
SDWebImageContextOption const SDWebImageContextImageScaleFactor = @"imageScaleFactor";
+SDWebImageContextOption const SDWebImageContextImagePreserveAspectRatio = @"imagePreserveAspectRatio";
+SDWebImageContextOption const SDWebImageContextImageThumbnailPixelSize = @"imageThumbnailPixelSize";
+SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType";
SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType";
SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType";
SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass";
diff --git a/SDWebImage/Core/SDWebImageDownloader.h b/SDWebImage/Core/SDWebImageDownloader.h
index 571b72a2..a365395c 100644
--- a/SDWebImage/Core/SDWebImageDownloader.h
+++ b/SDWebImage/Core/SDWebImageDownloader.h
@@ -128,6 +128,11 @@ typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;
*/
@property (nonatomic, strong, nullable, readonly) NSURLResponse *response;
+/**
+ The download's metrics. This will be nil if download operation does not support metrics.
+ */
+@property (nonatomic, strong, nullable, readonly) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+
@end
diff --git a/SDWebImage/Core/SDWebImageDownloader.m b/SDWebImage/Core/SDWebImageDownloader.m
index 80d30b59..3d354add 100644
--- a/SDWebImage/Core/SDWebImageDownloader.m
+++ b/SDWebImage/Core/SDWebImageDownloader.m
@@ -24,6 +24,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
@property (nonatomic, strong, nullable, readwrite) NSURL *url;
@property (nonatomic, strong, nullable, readwrite) NSURLRequest *request;
@property (nonatomic, strong, nullable, readwrite) NSURLResponse *response;
+@property (nonatomic, strong, nullable, readwrite) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
@property (nonatomic, weak, nullable, readwrite) id downloadOperationCancelToken;
@property (nonatomic, weak, nullable) NSOperation *downloadOperation;
@property (nonatomic, assign, getter=isCancelled) BOOL cancelled;
@@ -226,10 +227,11 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
SD_UNLOCK(self.operationsLock);
};
self.URLOperations[url] = operation;
+ // Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
+ downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
[self.downloadQueue addOperation:operation];
- downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
} else {
// When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
@@ -403,7 +405,12 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
NSOperation *returnOperation = nil;
for (NSOperation *operation in self.downloadQueue.operations) {
if ([operation respondsToSelector:@selector(dataTask)]) {
- if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
+ // So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
+ NSURLSessionTask *operationTask;
+ @synchronized (operation) {
+ operationTask = operation.dataTask;
+ }
+ if (operationTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
@@ -492,30 +499,52 @@ didReceiveResponse:(NSURLResponse *)response
}
}
+- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
+
+ // Identify the operation that runs this task and pass it the delegate method
+ NSOperation *dataOperation = [self operationWithTask:task];
+ if ([dataOperation respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) {
+ [dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics];
+ }
+}
+
@end
@implementation SDWebImageDownloadToken
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:SDWebImageDownloadReceiveResponseNotification object:nil];
+ [[NSNotificationCenter defaultCenter] removeObserver:self name:SDWebImageDownloadStopNotification object:nil];
}
- (instancetype)initWithDownloadOperation:(NSOperation *)downloadOperation {
self = [super init];
if (self) {
_downloadOperation = downloadOperation;
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:downloadOperation];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadDidReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:downloadOperation];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadDidStop:) name:SDWebImageDownloadStopNotification object:downloadOperation];
}
return self;
}
-- (void)downloadReceiveResponse:(NSNotification *)notification {
+- (void)downloadDidReceiveResponse:(NSNotification *)notification {
NSOperation *downloadOperation = notification.object;
if (downloadOperation && downloadOperation == self.downloadOperation) {
self.response = downloadOperation.response;
}
}
+- (void)downloadDidStop:(NSNotification *)notification {
+ NSOperation *downloadOperation = notification.object;
+ if (downloadOperation && downloadOperation == self.downloadOperation) {
+ if ([downloadOperation respondsToSelector:@selector(metrics)]) {
+ if (@available(iOS 10.0, tvOS 10.0, macOS 10.12, watchOS 3.0, *)) {
+ self.metrics = downloadOperation.metrics;
+ }
+ }
+ }
+}
+
- (void)cancel {
@synchronized (self) {
if (self.isCancelled) {
diff --git a/SDWebImage/Core/SDWebImageDownloaderOperation.h b/SDWebImage/Core/SDWebImageDownloaderOperation.h
index e987ba42..3b93aa71 100644
--- a/SDWebImage/Core/SDWebImageDownloaderOperation.h
+++ b/SDWebImage/Core/SDWebImageDownloaderOperation.h
@@ -36,6 +36,7 @@
@optional
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
+@property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
@property (strong, nonatomic, nullable) NSURLCredential *credential;
@property (assign, nonatomic) double minimumProgressInterval;
@@ -62,6 +63,12 @@
*/
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
+/**
+ * The collected metrics from `-URLSession:task:didFinishCollectingMetrics:`.
+ * This can be used to collect the network metrics like download duration, DNS lookup duration, SSL handshake dureation, etc. See Apple's documentation: https://developer.apple.com/documentation/foundation/urlsessiontaskmetrics
+ */
+@property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+
/**
* The credential used for authentication challenges in `-URLSession:task:didReceiveChallenge:completionHandler:`.
*
diff --git a/SDWebImage/Core/SDWebImageDownloaderOperation.m b/SDWebImage/Core/SDWebImageDownloaderOperation.m
index 6527eddd..00d803f2 100644
--- a/SDWebImage/Core/SDWebImageDownloaderOperation.m
+++ b/SDWebImage/Core/SDWebImageDownloaderOperation.m
@@ -52,7 +52,9 @@ typedef NSMutableDictionary SDCallbacksDictionary;
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
-@property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue; // the queue to do image decoding
+@property (strong, nonatomic, readwrite, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
+
+@property (strong, nonatomic, nonnull) NSOperationQueue *coderQueue; // the serial operation queue to do image decoding
#if SD_UIKIT
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
@@ -87,7 +89,8 @@ typedef NSMutableDictionary SDCallbacksDictionary;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
- _coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
+ _coderQueue = [NSOperationQueue new];
+ _coderQueue.maxConcurrentOperationCount = 1;
#if SD_UIKIT
_backgroundTaskId = UIBackgroundTaskInvalid;
#endif
@@ -138,7 +141,7 @@ typedef NSMutableDictionary SDCallbacksDictionary;
SDWebImageDownloaderCompletedBlock completedBlock = [token valueForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
if (completedBlock) {
- completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil], YES);
+ completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}], YES);
}
});
}
@@ -150,7 +153,7 @@ typedef NSMutableDictionary SDCallbacksDictionary;
if (self.isCancelled) {
self.finished = YES;
// Operation cancelled by user before sending the request
- [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil]];
+ [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user before sending the request"}]];
[self reset];
return;
}
@@ -205,8 +208,13 @@ typedef NSMutableDictionary SDCallbacksDictionary;
if (self.dataTask) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
+ self.coderQueue.qualityOfService = NSQualityOfServiceUserInteractive;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
+ self.coderQueue.qualityOfService = NSQualityOfServiceBackground;
+ } else {
+ self.dataTask.priority = NSURLSessionTaskPriorityDefault;
+ self.coderQueue.qualityOfService = NSQualityOfServiceDefault;
}
[self.dataTask resume];
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
@@ -243,9 +251,10 @@ typedef NSMutableDictionary SDCallbacksDictionary;
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
+ } else {
+ // Operation cancelled by user during sending the request
+ [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}]];
}
- // Operation cancelled by user before sending the request
- [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil]];
[self reset];
}
@@ -307,7 +316,7 @@ didReceiveResponse:(NSURLResponse *)response
response = [self.responseModifier modifiedResponseWithResponse:response];
if (!response) {
valid = NO;
- self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadResponse userInfo:nil];
+ self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadResponse userInfo:@{NSLocalizedDescriptionKey : @"Download marked as failed because response is nil"}];
}
}
@@ -321,13 +330,13 @@ didReceiveResponse:(NSURLResponse *)response
BOOL statusCodeValid = statusCode >= 200 && statusCode < 400;
if (!statusCodeValid) {
valid = NO;
- self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadStatusCode userInfo:@{SDWebImageErrorDownloadStatusCodeKey : @(statusCode)}];
+ self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadStatusCode userInfo:@{NSLocalizedDescriptionKey : @"Download marked as failed because response status code is not in 200-400", SDWebImageErrorDownloadStatusCodeKey : @(statusCode)}];
}
//'304 Not Modified' is an exceptional one
//URLSession current behavior will return 200 status code when the server respond 304 and URLCache hit. But this is not a standard behavior and we just add a check
if (statusCode == 304 && !self.cachedData) {
valid = NO;
- self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:nil];
+ self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:@{NSLocalizedDescriptionKey : @"Download response status code is 304 not modified and ignored"}];
}
if (valid) {
@@ -381,17 +390,18 @@ didReceiveResponse:(NSURLResponse *)response
// Get the image data
NSData *imageData = [self.imageData copy];
- // progressive decode the image in coder queue
- dispatch_async(self.coderQueue, ^{
- @autoreleasepool {
+ // keep maxmium one progressive decode process during download
+ if (self.coderQueue.operationCount == 0) {
+ // NSOperation have autoreleasepool, don't need to create extra one
+ [self.coderQueue addOperationWithBlock:^{
UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
if (image) {
// We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
- }
- });
+ }];
+ }
}
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
@@ -453,24 +463,23 @@ didReceiveResponse:(NSURLResponse *)response
* then we should check if the cached data is equal to image data
*/
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
- self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:nil];
+ self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image is not modified and ignored"}];
// call completion block with not modified error
[self callCompletionBlocksWithError:self.responseError];
[self done];
} else {
- // decode the image in coder queue
- dispatch_async(self.coderQueue, ^{
- @autoreleasepool {
- UIImage *image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
- CGSize imageSize = image.size;
- if (imageSize.width == 0 || imageSize.height == 0) {
- [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
- } else {
- [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
- }
- [self done];
+ // decode the image in coder queue, cancel all previous decoding process
+ [self.coderQueue cancelAllOperations];
+ [self.coderQueue addOperationWithBlock:^{
+ UIImage *image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
+ CGSize imageSize = image.size;
+ if (imageSize.width == 0 || imageSize.height == 0) {
+ [self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
+ } else {
+ [self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
- });
+ [self done];
+ }];
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
@@ -512,6 +521,10 @@ didReceiveResponse:(NSURLResponse *)response
}
}
+- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
+ self.metrics = metrics;
+}
+
#pragma mark Helper methods
+ (SDWebImageOptions)imageOptionsFromDownloaderOptions:(SDWebImageDownloaderOptions)downloadOptions {
SDWebImageOptions options = 0;
diff --git a/SDWebImage/Core/SDWebImageError.h b/SDWebImage/Core/SDWebImageError.h
index 3c08cc9d..e935ac97 100644
--- a/SDWebImage/Core/SDWebImageError.h
+++ b/SDWebImage/Core/SDWebImageError.h
@@ -22,5 +22,5 @@ typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) {
SDWebImageErrorInvalidDownloadOperation = 2000, // The image download operation is invalid, such as nil operation or unexpected error occur when operation initialized
SDWebImageErrorInvalidDownloadStatusCode = 2001, // The image download response a invalid status code. You can check the status code in error's userInfo under `SDWebImageErrorDownloadStatusCodeKey`
SDWebImageErrorCancelled = 2002, // The image loading operation is cancelled before finished, during either async disk cache query, or waiting before actual network request. For actual network request error, check `NSURLErrorDomain` error domain and code.
- SDWebImageErrorInvalidDownloadResponse = 2003, // When using response modifier, the modified download response is nil and marked as cancelled.
+ SDWebImageErrorInvalidDownloadResponse = 2003, // When using response modifier, the modified download response is nil and marked as failed.
};
diff --git a/SDWebImage/Core/SDWebImageManager.h b/SDWebImage/Core/SDWebImageManager.h
index d1ab013e..c7e52ca9 100644
--- a/SDWebImage/Core/SDWebImageManager.h
+++ b/SDWebImage/Core/SDWebImageManager.h
@@ -87,7 +87,7 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager loadImageWithURL:imageURL
options:0
progress:nil
- completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
+ completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
@@ -262,8 +262,15 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager];
- (void)cancelAll;
/**
- * Return the cache key for a given URL
+ * Return the cache key for a given URL, does not considerate transformer or thumbnail.
+ * @note This method does not have context option, only use the url and manager level cacheKeyFilter to generate the cache key.
*/
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;
+/**
+ * Return the cache key for a given URL and context option.
+ * @note The context option like `.thumbnailPixelSize` and `.imageTransformer` will effect the generated cache key, using this if you have those context associated.
+*/
+- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context;
+
@end
diff --git a/SDWebImage/Core/SDWebImageManager.m b/SDWebImage/Core/SDWebImageManager.m
index dece6dbf..2adf2b64 100644
--- a/SDWebImage/Core/SDWebImageManager.m
+++ b/SDWebImage/Core/SDWebImageManager.m
@@ -10,6 +10,7 @@
#import "SDImageCache.h"
#import "SDWebImageDownloader.h"
#import "UIImage+Metadata.h"
+#import "SDAssociatedObject.h"
#import "SDWebImageError.h"
#import "SDInternalMacros.h"
@@ -94,19 +95,69 @@ static id _defaultImageLoader;
}
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
- return [self cacheKeyForURL:url cacheKeyFilter:self.cacheKeyFilter];
-}
-
-- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url cacheKeyFilter:(id)cacheKeyFilter {
if (!url) {
return @"";
}
-
+
+ NSString *key;
+ // Cache Key Filter
+ id cacheKeyFilter = self.cacheKeyFilter;
if (cacheKeyFilter) {
- return [cacheKeyFilter cacheKeyForURL:url];
+ key = [cacheKeyFilter cacheKeyForURL:url];
} else {
- return url.absoluteString;
+ key = url.absoluteString;
}
+
+ return key;
+}
+
+- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context {
+ if (!url) {
+ return @"";
+ }
+
+ NSString *key;
+ // Cache Key Filter
+ id cacheKeyFilter = self.cacheKeyFilter;
+ if (context[SDWebImageContextCacheKeyFilter]) {
+ cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
+ }
+ if (cacheKeyFilter) {
+ key = [cacheKeyFilter cacheKeyForURL:url];
+ } else {
+ key = url.absoluteString;
+ }
+
+ // Thumbnail Key Appending
+ NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
+ if (thumbnailSizeValue != nil) {
+ CGSize thumbnailSize = CGSizeZero;
+#if SD_MAC
+ thumbnailSize = thumbnailSizeValue.sizeValue;
+#else
+ thumbnailSize = thumbnailSizeValue.CGSizeValue;
+#endif
+ BOOL preserveAspectRatio = YES;
+ NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
+ if (preserveAspectRatioValue != nil) {
+ preserveAspectRatio = preserveAspectRatioValue.boolValue;
+ }
+ key = SDThumbnailedKeyForKey(key, thumbnailSize, preserveAspectRatio);
+ }
+
+ // Transformer Key Appending
+ id transformer = self.transformer;
+ if (context[SDWebImageContextImageTransformer]) {
+ transformer = context[SDWebImageContextImageTransformer];
+ if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
+ transformer = nil;
+ }
+ }
+ if (transformer) {
+ key = SDTransformedKeyForKey(key, transformer.transformerKey);
+ }
+
+ return key;
}
- (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
@@ -184,17 +235,28 @@ static id _defaultImageLoader;
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
+ // Grab the image cache to use
+ id imageCache;
+ if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
+ imageCache = context[SDWebImageContextImageCache];
+ } else {
+ imageCache = self.imageCache;
+ }
// Check whether we should query cache
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
+ // Get the query cache type
+ SDImageCacheType queryCacheType = SDImageCacheTypeAll;
+ if (context[SDWebImageContextQueryCacheType]) {
+ queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
+ }
if (shouldQueryCache) {
- id cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
- NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
+ NSString *key = [self cacheKeyForURL:url context:context];
@weakify(operation);
- operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
+ operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// Image combined operation cancelled by user
- [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
+ [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
}
@@ -217,11 +279,18 @@ static id _defaultImageLoader;
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
+ // Grab the image loader to use
+ id imageLoader;
+ if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
+ imageLoader = context[SDWebImageContextImageLoader];
+ } else {
+ imageLoader = self.imageLoader;
+ }
// Check whether we should download image from network
BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
- shouldDownload &= [self.imageLoader canRequestImageForURL:url];
+ shouldDownload &= [imageLoader canRequestImageForURL:url];
if (shouldDownload) {
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
@@ -239,11 +308,11 @@ static id _defaultImageLoader;
}
@weakify(operation);
- operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
+ operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// Image combined operation cancelled by user
- [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
+ [self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
@@ -251,7 +320,7 @@ static id _defaultImageLoader;
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
} else if (error) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
- BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
+ BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
@@ -264,7 +333,7 @@ static id _defaultImageLoader;
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
}
-
+ // Continue store cache process
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
@@ -302,12 +371,20 @@ static id _defaultImageLoader;
if (context[SDWebImageContextOriginalStoreCacheType]) {
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
}
- id cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
- NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
+ // origin cache key
+ SDWebImageMutableContext *originContext = [context mutableCopy];
+ // disable transformer for cache key generation
+ originContext[SDWebImageContextImageTransformer] = [NSNull null];
+ NSString *key = [self cacheKeyForURL:url context:originContext];
id transformer = context[SDWebImageContextImageTransformer];
+ if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
+ transformer = nil;
+ }
id cacheSerializer = context[SDWebImageContextCacheSerializer];
- BOOL shouldTransformImage = downloadedImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer;
+ BOOL shouldTransformImage = downloadedImage && transformer;
+ shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
+ shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage));
BOOL shouldCacheOriginal = downloadedImage && finished;
// if available, store original image to cache
@@ -318,37 +395,74 @@ static id _defaultImageLoader;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url];
- [self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key cacheType:targetStoreCacheType completion:nil];
+ [self storeImage:downloadedImage imageData:cacheData forKey:key cacheType:targetStoreCacheType options:options context:context completion:^{
+ // Continue transform process
+ [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
+ }];
}
});
} else {
- [self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:targetStoreCacheType completion:nil];
+ [self storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:targetStoreCacheType options:options context:context completion:^{
+ // Continue transform process
+ [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
+ }];
}
+ } else {
+ // Continue transform process
+ [self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
+}
+
+// Transform process
+- (void)callTransformProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
+ url:(nonnull NSURL *)url
+ options:(SDWebImageOptions)options
+ context:(SDWebImageContext *)context
+ originalImage:(nullable UIImage *)originalImage
+ originalData:(nullable NSData *)originalData
+ finished:(BOOL)finished
+ progress:(nullable SDImageLoaderProgressBlock)progressBlock
+ completed:(nullable SDInternalCompletionBlock)completedBlock {
+ // the target image store cache type
+ SDImageCacheType storeCacheType = SDImageCacheTypeAll;
+ if (context[SDWebImageContextStoreCacheType]) {
+ storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
+ }
+ // transformed cache key
+ NSString *key = [self cacheKeyForURL:url context:context];
+ id transformer = context[SDWebImageContextImageTransformer];
+ if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
+ transformer = nil;
+ }
+ id cacheSerializer = context[SDWebImageContextCacheSerializer];
+
+ BOOL shouldTransformImage = originalImage && transformer;
+ shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
+ shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage));
// if available, store transformed image to cache
if (shouldTransformImage) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
- UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key];
+ UIImage *transformedImage = [transformer transformedImageWithImage:originalImage forKey:key];
if (transformedImage && finished) {
- NSString *transformerKey = [transformer transformerKey];
- NSString *cacheKey = SDTransformedKeyForKey(key, transformerKey);
- BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
+ BOOL imageWasTransformed = ![transformedImage isEqual:originalImage];
NSData *cacheData;
// pass nil if the image was transformed, so we can recalculate the data from the image
if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
- cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : downloadedData) imageURL:url];
+ cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : originalData) imageURL:url];
} else {
- cacheData = (imageWasTransformed ? nil : downloadedData);
+ cacheData = (imageWasTransformed ? nil : originalData);
}
- [self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
+ [self storeImage:transformedImage imageData:cacheData forKey:key cacheType:storeCacheType options:options context:context completion:^{
+ [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
+ }];
+ } else {
+ [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
-
- [self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
});
} else {
- [self callCompletionBlockForOperation:operation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
+ [self callCompletionBlockForOperation:operation completion:completedBlock image:originalImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
@@ -363,6 +477,35 @@ static id _defaultImageLoader;
SD_UNLOCK(self.runningOperationsLock);
}
+- (void)storeImage:(nullable UIImage *)image
+ imageData:(nullable NSData *)data
+ forKey:(nullable NSString *)key
+ cacheType:(SDImageCacheType)cacheType
+ options:(SDWebImageOptions)options
+ context:(nullable SDWebImageContext *)context
+ completion:(nullable SDWebImageNoParamsBlock)completion {
+ id imageCache;
+ if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
+ imageCache = context[SDWebImageContextImageCache];
+ } else {
+ imageCache = self.imageCache;
+ }
+ BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
+ // Check whether we should wait the store cache finished. If not, callback immediately
+ [imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
+ if (waitStoreCache) {
+ if (completion) {
+ completion();
+ }
+ }
+ }];
+ if (!waitStoreCache) {
+ if (completion) {
+ completion();
+ }
+ }
+}
+
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
completion:(nullable SDInternalCompletionBlock)completionBlock
error:(nullable NSError *)error
@@ -386,13 +529,21 @@ static id _defaultImageLoader;
}
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
- error:(nonnull NSError *)error {
+ error:(nonnull NSError *)error
+ options:(SDWebImageOptions)options
+ context:(nullable SDWebImageContext *)context {
+ id imageLoader;
+ if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
+ imageLoader = context[SDWebImageContextImageLoader];
+ } else {
+ imageLoader = self.imageLoader;
+ }
// Check whether we should block failed url
BOOL shouldBlockFailedURL;
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
} else {
- shouldBlockFailedURL = [self.imageLoader shouldBlockFailedURLWithURL:url error:error];
+ shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error];
}
return shouldBlockFailedURL;
diff --git a/SDWebImage/Core/UIImage+ExtendedCacheData.h b/SDWebImage/Core/UIImage+ExtendedCacheData.h
new file mode 100644
index 00000000..482c8c40
--- /dev/null
+++ b/SDWebImage/Core/UIImage+ExtendedCacheData.h
@@ -0,0 +1,24 @@
+/*
+* This file is part of the SDWebImage package.
+* (c) Olivier Poitrey
+* (c) Fabrice Aneche
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+#import
+#import "SDWebImageCompat.h"
+
+@interface UIImage (ExtendedCacheData)
+
+/**
+ Read and Write the extended object and bind it to the image. Which can hold some extra metadata like Image's scale factor, URL rich link, date, etc.
+ The extended object should conforms to NSCoding, which we use `NSKeyedArchiver` and `NSKeyedUnarchiver` to archive it to data, and write to disk cache.
+ @note The disk cache preserve both of the data and extended data with the same cache key. For manual query, use the `SDDiskCache` protocol method `extendedDataForKey:` instead.
+ @note You can specify arbitrary object conforms to NSCoding (NSObject protocol here is used to support object using `NS_ROOT_CLASS`, which is not NSObject subclass). If you load image from disk cache, you should check the extended object class to avoid corrupted data.
+ @warning This object don't need to implements NSSecureCoding (but it's recommended), because we allows arbitrary class.
+ */
+@property (nonatomic, strong, nullable) id sd_extendedObject;
+
+@end
diff --git a/SDWebImage/Core/UIImage+ExtendedCacheData.m b/SDWebImage/Core/UIImage+ExtendedCacheData.m
new file mode 100644
index 00000000..05d29cff
--- /dev/null
+++ b/SDWebImage/Core/UIImage+ExtendedCacheData.m
@@ -0,0 +1,23 @@
+/*
+* This file is part of the SDWebImage package.
+* (c) Olivier Poitrey
+* (c) Fabrice Aneche
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+#import "UIImage+ExtendedCacheData.h"
+#import
+
+@implementation UIImage (ExtendedCacheData)
+
+- (id)sd_extendedObject {
+ return objc_getAssociatedObject(self, @selector(sd_extendedObject));
+}
+
+- (void)setSd_extendedObject:(id)sd_extendedObject {
+ objc_setAssociatedObject(self, @selector(sd_extendedObject), sd_extendedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
+}
+
+@end
diff --git a/SDWebImage/Core/UIImage+Metadata.h b/SDWebImage/Core/UIImage+Metadata.h
index e42ff697..8328c261 100644
--- a/SDWebImage/Core/UIImage+Metadata.h
+++ b/SDWebImage/Core/UIImage+Metadata.h
@@ -28,12 +28,20 @@
/**
* UIKit:
- * Check the `images` array property
+ * Check the `images` array property.
* AppKit:
* NSImage currently only support animated via GIF imageRep unlike UIImage. It will check the imageRep's frame count.
*/
@property (nonatomic, assign, readonly) BOOL sd_isAnimated;
+/**
+ * UIKit:
+ * Check the `isSymbolImage` property. Also check the system PDF(iOS 11+) && SVG(iOS 13+) support.
+ * AppKit:
+ * NSImage supports PDF && SVG && EPS imageRep, check the imageRep class.
+ */
+@property (nonatomic, assign, readonly) BOOL sd_isVector;
+
/**
* The image format represent the original compressed image data format.
* If you don't manually specify a format, this information is retrieve from CGImage using `CGImageGetUTType`, which may return nil for non-CG based image. At this time it will return `SDImageFormatUndefined` as default value.
diff --git a/SDWebImage/Core/UIImage+Metadata.m b/SDWebImage/Core/UIImage+Metadata.m
index 3c9bf929..09724236 100644
--- a/SDWebImage/Core/UIImage+Metadata.m
+++ b/SDWebImage/Core/UIImage+Metadata.m
@@ -8,6 +8,7 @@
#import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h"
+#import "SDInternalMacros.h"
#import "objc/runtime.h"
@implementation UIImage (Metadata)
@@ -32,6 +33,32 @@
return (self.images != nil);
}
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
+- (BOOL)sd_isVector {
+ if (@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
+ // Xcode 11 supports symbol image, keep Xcode 10 compatible currently
+ SEL SymbolSelector = NSSelectorFromString(@"isSymbolImage");
+ if ([self respondsToSelector:SymbolSelector] && [self performSelector:SymbolSelector]) {
+ return YES;
+ }
+ // SVG
+ SEL SVGSelector = SD_SEL_SPI(CGSVGDocument);
+ if ([self respondsToSelector:SVGSelector] && [self performSelector:SVGSelector]) {
+ return YES;
+ }
+ }
+ if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)) {
+ // PDF
+ SEL PDFSelector = SD_SEL_SPI(CGPDFPage);
+ if ([self respondsToSelector:PDFSelector] && [self performSelector:PDFSelector]) {
+ return YES;
+ }
+ }
+ return NO;
+}
+#pragma clang diagnostic pop
+
#else
- (NSUInteger)sd_imageLoopCount {
@@ -61,7 +88,7 @@
}
- (BOOL)sd_isAnimated {
- BOOL isGIF = NO;
+ BOOL isAnimated = NO;
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
NSBitmapImageRep *bitmapImageRep;
@@ -70,9 +97,24 @@
}
if (bitmapImageRep) {
NSUInteger frameCount = [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
- isGIF = frameCount > 1 ? YES : NO;
+ isAnimated = frameCount > 1 ? YES : NO;
}
- return isGIF;
+ return isAnimated;
+}
+
+- (BOOL)sd_isVector {
+ NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
+ NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
+ if ([imageRep isKindOfClass:[NSPDFImageRep class]]) {
+ return YES;
+ }
+ if ([imageRep isKindOfClass:[NSEPSImageRep class]]) {
+ return YES;
+ }
+ if ([NSStringFromClass(imageRep.class) hasSuffix:@"NSSVGImageRep"]) {
+ return YES;
+ }
+ return NO;
}
#endif
diff --git a/SDWebImage/Core/UIImage+Transform.m b/SDWebImage/Core/UIImage+Transform.m
index 8637b1a2..b27b3a95 100644
--- a/SDWebImage/Core/UIImage+Transform.m
+++ b/SDWebImage/Core/UIImage+Transform.m
@@ -9,7 +9,8 @@
#import "UIImage+Transform.h"
#import "NSImage+Compatibility.h"
#import "SDImageGraphics.h"
-#import "NSBezierPath+RoundedCorners.h"
+#import "SDGraphicsImageRenderer.h"
+#import "NSBezierPath+SDRoundedCorners.h"
#import
#if SD_UIKIT || SD_MAC
#import
@@ -163,13 +164,29 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
+#if SD_UIKIT || SD_MAC
+// Create-Rule, caller should call CGImageRelease
+static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull ciImage) {
+ CGImageRef imageRef = NULL;
+ if (@available(iOS 10, macOS 10.12, tvOS 10, *)) {
+ imageRef = ciImage.CGImage;
+ }
+ if (!imageRef) {
+ CIContext *context = [CIContext context];
+ imageRef = [context createCGImage:ciImage fromRect:ciImage.extent];
+ } else {
+ CGImageRetain(imageRef);
+ }
+ return imageRef;
+}
+#endif
+
@implementation UIImage (Transform)
-- (void)sd_drawInRect:(CGRect)rect withScaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips {
+- (void)sd_drawInRect:(CGRect)rect context:(CGContextRef)context scaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips {
CGRect drawRect = SDCGRectFitWithScaleMode(rect, self.size, scaleMode);
if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
if (clips) {
- CGContextRef context = SDGraphicsGetCurrentContext();
if (context) {
CGContextSaveGState(context);
CGContextAddRect(context, rect);
@@ -184,162 +201,215 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
- (nullable UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode {
if (size.width <= 0 || size.height <= 0) return nil;
- SDGraphicsBeginImageContextWithOptions(size, NO, self.scale);
- [self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) withScaleMode:scaleMode clipsToBounds:NO];
- UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
- SDGraphicsEndImageContext();
+ SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
+ format.scale = self.scale;
+ SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
+ UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
+ [self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) context:context scaleMode:scaleMode clipsToBounds:NO];
+ }];
return image;
}
- (nullable UIImage *)sd_croppedImageWithRect:(CGRect)rect {
- if (!self.CGImage) return nil;
rect.origin.x *= self.scale;
rect.origin.y *= self.scale;
rect.size.width *= self.scale;
rect.size.height *= self.scale;
if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
- CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
+
+#if SD_UIKIT || SD_MAC
+ // CIImage shortcut
+ if (self.CIImage) {
+ CGRect croppingRect = CGRectMake(rect.origin.x, self.size.height - CGRectGetMaxY(rect), rect.size.width, rect.size.height);
+ CIImage *ciImage = [self.CIImage imageByCroppingToRect:croppingRect];
+#if SD_UIKIT
+ UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
+#else
+ UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
+#endif
+ return image;
+ }
+#endif
+
+ CGImageRef imageRef = self.CGImage;
if (!imageRef) {
return nil;
}
+
+ CGImageRef croppedImageRef = CGImageCreateWithImageInRect(imageRef, rect);
+ if (!croppedImageRef) {
+ return nil;
+ }
#if SD_UIKIT || SD_WATCH
- UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
+ UIImage *image = [UIImage imageWithCGImage:croppedImageRef scale:self.scale orientation:self.imageOrientation];
#else
- UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
+ UIImage *image = [[UIImage alloc] initWithCGImage:croppedImageRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
- CGImageRelease(imageRef);
+ CGImageRelease(croppedImageRef);
return image;
}
- (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor {
- if (!self.CGImage) return nil;
- SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
- CGContextRef context = SDGraphicsGetCurrentContext();
- CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
-
- CGFloat minSize = MIN(self.size.width, self.size.height);
- if (borderWidth < minSize / 2) {
-#if SD_UIKIT || SD_WATCH
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
-#else
- NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius];
-#endif
- [path closePath];
+ SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
+ format.scale = self.scale;
+ SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format];
+ UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
+ CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
- CGContextSaveGState(context);
- [path addClip];
- [self drawInRect:rect];
- CGContextRestoreGState(context);
- }
-
- if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
- CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
- CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
- CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0;
+ CGFloat minSize = MIN(self.size.width, self.size.height);
+ if (borderWidth < minSize / 2) {
#if SD_UIKIT || SD_WATCH
- UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)];
+ UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
#else
- NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius];
+ NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius];
#endif
- [path closePath];
+ [path closePath];
+
+ CGContextSaveGState(context);
+ [path addClip];
+ [self drawInRect:rect];
+ CGContextRestoreGState(context);
+ }
- path.lineWidth = borderWidth;
- [borderColor setStroke];
- [path stroke];
- }
-
- UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
- SDGraphicsEndImageContext();
+ if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
+ CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
+ CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
+ CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0;
+#if SD_UIKIT || SD_WATCH
+ UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)];
+#else
+ NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius];
+#endif
+ [path closePath];
+
+ path.lineWidth = borderWidth;
+ [borderColor setStroke];
+ [path stroke];
+ }
+ }];
return image;
}
- (nullable UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize {
- if (!self.CGImage) return nil;
- size_t width = (size_t)CGImageGetWidth(self.CGImage);
- size_t height = (size_t)CGImageGetHeight(self.CGImage);
- CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
+ size_t width = self.size.width;
+ size_t height = self.size.height;
+ CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0, 0, width, height),
fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity);
-
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- CGContextRef context = CGBitmapContextCreate(NULL,
- (size_t)newRect.size.width,
- (size_t)newRect.size.height,
- 8,
- (size_t)newRect.size.width * 4,
- colorSpace,
- kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
- CGColorSpaceRelease(colorSpace);
- if (!context) return nil;
-
- CGContextSetShouldAntialias(context, true);
- CGContextSetAllowsAntialiasing(context, true);
- CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
-
- CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
- CGContextRotateCTM(context, angle);
-
- CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
- CGImageRef imgRef = CGBitmapContextCreateImage(context);
+
+#if SD_UIKIT || SD_MAC
+ // CIImage shortcut
+ if (self.CIImage) {
+ CIImage *ciImage = self.CIImage;
+ if (fitSize) {
+ CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
+ ciImage = [ciImage imageByApplyingTransform:transform];
+ } else {
+ CIFilter *filter = [CIFilter filterWithName:@"CIStraightenFilter"];
+ [filter setValue:ciImage forKey:kCIInputImageKey];
+ [filter setValue:@(angle) forKey:kCIInputAngleKey];
+ ciImage = filter.outputImage;
+ }
#if SD_UIKIT || SD_WATCH
- UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
+ UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
#else
- UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
+ UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
- CGImageRelease(imgRef);
- CGContextRelease(context);
- return img;
+ return image;
+ }
+#endif
+
+ SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
+ format.scale = self.scale;
+ SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:newRect.size format:format];
+ UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
+ CGContextSetShouldAntialias(context, true);
+ CGContextSetAllowsAntialiasing(context, true);
+ CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
+ CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
+#if SD_UIKIT || SD_WATCH
+ // Use UIKit coordinate system counterclockwise (⟲)
+ CGContextRotateCTM(context, -angle);
+#else
+ CGContextRotateCTM(context, angle);
+#endif
+
+ [self drawInRect:CGRectMake(-(width * 0.5), -(height * 0.5), width, height)];
+ }];
+ return image;
}
- (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
- if (!self.CGImage) return nil;
- size_t width = (size_t)CGImageGetWidth(self.CGImage);
- size_t height = (size_t)CGImageGetHeight(self.CGImage);
- size_t bytesPerRow = width * 4;
- CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
- CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
- CGColorSpaceRelease(colorSpace);
- if (!context) return nil;
-
- CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
- UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
- if (!data) {
- CGContextRelease(context);
- return nil;
- }
- vImage_Buffer src = { data, height, width, bytesPerRow };
- vImage_Buffer dest = { data, height, width, bytesPerRow };
- if (vertical) {
- vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
- }
- if (horizontal) {
- vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
- }
- CGImageRef imgRef = CGBitmapContextCreateImage(context);
- CGContextRelease(context);
-#if SD_UIKIT || SD_WATCH
- UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
+ size_t width = self.size.width;
+ size_t height = self.size.height;
+
+#if SD_UIKIT || SD_MAC
+ // CIImage shortcut
+ if (self.CIImage) {
+ CGAffineTransform transform = CGAffineTransformIdentity;
+ // Use UIKit coordinate system
+ if (horizontal) {
+ CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0);
+ transform = CGAffineTransformConcat(transform, flipHorizontal);
+ }
+ if (vertical) {
+ CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height);
+ transform = CGAffineTransformConcat(transform, flipVertical);
+ }
+ CIImage *ciImage = [self.CIImage imageByApplyingTransform:transform];
+#if SD_UIKIT
+ UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
#else
- UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
+ UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
- CGImageRelease(imgRef);
- return img;
+ return image;
+ }
+#endif
+
+ SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
+ format.scale = self.scale;
+ SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format];
+ UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
+ // Use UIKit coordinate system
+ if (horizontal) {
+ CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0);
+ CGContextConcatCTM(context, flipHorizontal);
+ }
+ if (vertical) {
+ CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height);
+ CGContextConcatCTM(context, flipVertical);
+ }
+ [self drawInRect:CGRectMake(0, 0, width, height)];
+ }];
+ return image;
}
#pragma mark - Image Blending
- (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor {
- if (!self.CGImage) return nil;
- if (!tintColor.CGColor) return nil;
-
BOOL hasTint = CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
if (!hasTint) {
-#if SD_UIKIT || SD_WATCH
- return [UIImage imageWithCGImage:self.CGImage scale:self.scale orientation:self.imageOrientation];
-#else
- return [[UIImage alloc] initWithCGImage:self.CGImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
-#endif
+ return self;
}
+#if SD_UIKIT || SD_MAC
+ // CIImage shortcut
+ if (self.CIImage) {
+ CIImage *ciImage = self.CIImage;
+ CIImage *colorImage = [CIImage imageWithColor:[[CIColor alloc] initWithColor:tintColor]];
+ colorImage = [colorImage imageByCroppingToRect:ciImage.extent];
+ CIFilter *filter = [CIFilter filterWithName:@"CISourceAtopCompositing"];
+ [filter setValue:colorImage forKey:kCIInputImageKey];
+ [filter setValue:ciImage forKey:kCIInputBackgroundImageKey];
+ ciImage = filter.outputImage;
+#if SD_UIKIT
+ UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
+#else
+ UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
+#endif
+ return image;
+ }
+#endif
+
CGSize size = self.size;
CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale;
@@ -347,23 +417,30 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
// blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing
CGBlendMode blendMode = kCGBlendModeSourceAtop;
- SDGraphicsBeginImageContextWithOptions(size, NO, scale);
- CGContextRef context = SDGraphicsGetCurrentContext();
- [self drawInRect:rect];
- CGContextSetBlendMode(context, blendMode);
- CGContextSetFillColorWithColor(context, tintColor.CGColor);
- CGContextFillRect(context, rect);
- UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
- SDGraphicsEndImageContext();
-
+ SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
+ format.scale = scale;
+ SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
+ UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
+ [self drawInRect:rect];
+ CGContextSetBlendMode(context, blendMode);
+ CGContextSetFillColorWithColor(context, tintColor.CGColor);
+ CGContextFillRect(context, rect);
+ }];
return image;
}
- (nullable UIColor *)sd_colorAtPoint:(CGPoint)point {
- if (!self) {
- return nil;
+ CGImageRef imageRef = NULL;
+ // CIImage compatible
+#if SD_UIKIT || SD_MAC
+ if (self.CIImage) {
+ imageRef = SDCreateCGImageFromCIImage(self.CIImage);
+ }
+#endif
+ if (!imageRef) {
+ imageRef = self.CGImage;
+ CGImageRetain(imageRef);
}
- CGImageRef imageRef = self.CGImage;
if (!imageRef) {
return nil;
}
@@ -372,42 +449,53 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
CGFloat width = CGImageGetWidth(imageRef);
CGFloat height = CGImageGetHeight(imageRef);
if (point.x < 0 || point.y < 0 || point.x >= width || point.y >= height) {
+ CGImageRelease(imageRef);
return nil;
}
// Get pixels
CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
if (!provider) {
+ CGImageRelease(imageRef);
return nil;
}
CFDataRef data = CGDataProviderCopyData(provider);
if (!data) {
+ CGImageRelease(imageRef);
return nil;
}
// Get pixel at point
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef);
+ CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4);
if (CFDataGetLength(data) < range.location + range.length) {
CFRelease(data);
+ CGImageRelease(imageRef);
return nil;
}
Pixel_8888 pixel = {0};
CFDataGetBytes(data, range, pixel);
CFRelease(data);
-
+ CGImageRelease(imageRef);
// Convert to color
- CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
return SDGetColorFromPixel(pixel, bitmapInfo);
}
- (nullable NSArray *)sd_colorsWithRect:(CGRect)rect {
- if (!self) {
- return nil;
+ CGImageRef imageRef = NULL;
+ // CIImage compatible
+#if SD_UIKIT || SD_MAC
+ if (self.CIImage) {
+ imageRef = SDCreateCGImageFromCIImage(self.CIImage);
+ }
+#endif
+ if (!imageRef) {
+ imageRef = self.CGImage;
+ CGImageRetain(imageRef);
}
- CGImageRef imageRef = self.CGImage;
if (!imageRef) {
return nil;
}
@@ -416,16 +504,19 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
CGFloat width = CGImageGetWidth(imageRef);
CGFloat height = CGImageGetHeight(imageRef);
if (CGRectGetWidth(rect) <= 0 || CGRectGetHeight(rect) <= 0 || CGRectGetMinX(rect) < 0 || CGRectGetMinY(rect) < 0 || CGRectGetMaxX(rect) > width || CGRectGetMaxY(rect) > height) {
+ CGImageRelease(imageRef);
return nil;
}
// Get pixels
CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
if (!provider) {
+ CGImageRelease(imageRef);
return nil;
}
CFDataRef data = CGDataProviderCopyData(provider);
if (!data) {
+ CGImageRelease(imageRef);
return nil;
}
@@ -437,6 +528,7 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
size_t end = bytesPerRow * (CGRectGetMaxY(rect) - 1) + components * CGRectGetMaxX(rect);
if (CFDataGetLength(data) < (CFIndex)end) {
CFRelease(data);
+ CGImageRelease(imageRef);
return nil;
}
@@ -460,29 +552,53 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
[colors addObject:color];
}
CFRelease(data);
+ CGImageRelease(imageRef);
return [colors copy];
}
#pragma mark - Image Effect
-// We use vImage to do box convolve for performance and support for watchOS. However, you can just use `CIFilter.CIBoxBlur`. For other blur effect, use any filter in `CICategoryBlur`
+// We use vImage to do box convolve for performance and support for watchOS. However, you can just use `CIFilter.CIGaussianBlur`. For other blur effect, use any filter in `CICategoryBlur`
- (nullable UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius {
if (self.size.width < 1 || self.size.height < 1) {
return nil;
}
- if (!self.CGImage) {
- return nil;
- }
-
BOOL hasBlur = blurRadius > __FLT_EPSILON__;
if (!hasBlur) {
return self;
}
CGFloat scale = self.scale;
+ CGFloat inputRadius = blurRadius * scale;
+#if SD_UIKIT || SD_MAC
+ if (self.CIImage) {
+ CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
+ [filter setValue:self.CIImage forKey:kCIInputImageKey];
+ [filter setValue:@(inputRadius) forKey:kCIInputRadiusKey];
+ CIImage *ciImage = filter.outputImage;
+ ciImage = [ciImage imageByCroppingToRect:CGRectMake(0, 0, self.size.width, self.size.height)];
+#if SD_UIKIT
+ UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
+#else
+ UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
+#endif
+ return image;
+ }
+#endif
+
CGImageRef imageRef = self.CGImage;
+ //convert to BGRA if it isn't
+ if (CGImageGetBitsPerPixel(imageRef) != 32 ||
+ CGImageGetBitsPerComponent(imageRef) != 8 ||
+ !((CGImageGetBitmapInfo(imageRef) & kCGBitmapAlphaInfoMask))) {
+ SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
+ [self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
+ imageRef = SDGraphicsGetImageFromCurrentImageContext().CGImage;
+ SDGraphicsEndImageContext();
+ }
+
vImage_Buffer effect = {}, scratch = {};
vImage_Buffer *input = NULL, *output = NULL;
@@ -490,14 +606,14 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
.bitsPerComponent = 8,
.bitsPerPixel = 32,
.colorSpace = NULL,
- .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
+ .bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer.
.version = 0,
.decode = NULL,
.renderingIntent = kCGRenderingIntentDefault
};
vImage_Error err;
- err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
+ err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImageNoFlags);
if (err != kvImageNoError) {
NSLog(@"UIImage+Transform error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
return nil;
@@ -524,9 +640,8 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
//
// ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
//
- CGFloat inputRadius = blurRadius * scale;
if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
- uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
+ uint32_t radius = floor(inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5);
radius |= 1; // force radius to be odd so that the three box-blur methodology works.
int iterations;
if (blurRadius * scale < 0.5) iterations = 1;
@@ -562,12 +677,19 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
#if SD_UIKIT || SD_MAC
- (nullable UIImage *)sd_filteredImageWithFilter:(nonnull CIFilter *)filter {
- if (!self.CGImage) return nil;
-
- CIContext *context = [CIContext context];
- CIImage *inputImage = [CIImage imageWithCGImage:self.CGImage];
+ CIImage *inputImage;
+ if (self.CIImage) {
+ inputImage = self.CIImage;
+ } else {
+ CGImageRef imageRef = self.CGImage;
+ if (!imageRef) {
+ return nil;
+ }
+ inputImage = [CIImage imageWithCGImage:imageRef];
+ }
if (!inputImage) return nil;
+ CIContext *context = [CIContext context];
[filter setValue:inputImage forKey:kCIInputImageKey];
CIImage *outputImage = filter.outputImage;
if (!outputImage) return nil;
diff --git a/SDWebImage/Core/UIView+WebCache.h b/SDWebImage/Core/UIView+WebCache.h
index d0a7966f..c7e12a47 100644
--- a/SDWebImage/Core/UIView+WebCache.h
+++ b/SDWebImage/Core/UIView+WebCache.h
@@ -31,6 +31,13 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
*/
@property (nonatomic, strong, readonly, nullable) NSURL *sd_imageURL;
+/**
+ * Get the current image operation key. Operation key is used to identify the different queries for one view instance (like UIButton).
+ * See more about this in `SDWebImageContextSetImageOperationKey`.
+ * @note You can use method `UIView+WebCacheOperation` to invesigate different queries' operation.
+ */
+@property (nonatomic, strong, readonly, nullable) NSString *sd_latestOperationKey;
+
/**
* The current image loading progress associated to the view. The unit count is the received size and excepted size of download.
* The `totalUnitCount` and `completedUnitCount` will be reset to 0 after a new image loading start (change from current queue). And they will be set to `SDWebImageProgressUnitCountUnknown` if the progressBlock not been called but the image loading success to mark the progress finished (change from main queue).
diff --git a/SDWebImage/Core/UIView+WebCache.m b/SDWebImage/Core/UIView+WebCache.m
index 311dd1ba..c12b43d7 100644
--- a/SDWebImage/Core/UIView+WebCache.m
+++ b/SDWebImage/Core/UIView+WebCache.m
@@ -52,10 +52,19 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
- context = [context copy]; // copy to avoid mutable object
+ if (context) {
+ // copy to avoid mutable object
+ context = [context copy];
+ } else {
+ context = [NSDictionary dictionary];
+ }
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
if (!validOperationKey) {
+ // pass through the operation key to downstream, which can used for tracing operation or image view class
validOperationKey = NSStringFromClass([self class]);
+ SDWebImageMutableContext *mutableContext = [context mutableCopy];
+ mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
+ context = [mutableContext copy];
}
self.sd_latestOperationKey = validOperationKey;
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
@@ -80,10 +89,14 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
[self sd_startImageIndicator];
id imageIndicator = self.sd_imageIndicator;
#endif
-
SDWebImageManager *manager = context[SDWebImageContextCustomManager];
if (!manager) {
manager = [SDWebImageManager sharedManager];
+ } else {
+ // remove this manager to avoid retain cycle (manger -> loader -> operation -> context -> manager)
+ SDWebImageMutableContext *mutableContext = [context mutableCopy];
+ mutableContext[SDWebImageContextCustomManager] = nil;
+ context = [mutableContext copy];
}
SDImageLoaderProgressBlock combinedProgressBlock = ^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
diff --git a/SDWebImage/Private/NSBezierPath+RoundedCorners.h b/SDWebImage/Private/NSBezierPath+SDRoundedCorners.h
similarity index 93%
rename from SDWebImage/Private/NSBezierPath+RoundedCorners.h
rename to SDWebImage/Private/NSBezierPath+SDRoundedCorners.h
index 224f2597..dfec18b7 100644
--- a/SDWebImage/Private/NSBezierPath+RoundedCorners.h
+++ b/SDWebImage/Private/NSBezierPath+SDRoundedCorners.h
@@ -12,7 +12,7 @@
#import "UIImage+Transform.h"
-@interface NSBezierPath (RoundedCorners)
+@interface NSBezierPath (SDRoundedCorners)
/**
Convenience way to create a bezier path with the specify rounding corners on macOS. Same as the one on `UIBezierPath`.
diff --git a/SDWebImage/Private/NSBezierPath+RoundedCorners.m b/SDWebImage/Private/NSBezierPath+SDRoundedCorners.m
similarity index 94%
rename from SDWebImage/Private/NSBezierPath+RoundedCorners.m
rename to SDWebImage/Private/NSBezierPath+SDRoundedCorners.m
index d217bf14..b6f7a011 100644
--- a/SDWebImage/Private/NSBezierPath+RoundedCorners.m
+++ b/SDWebImage/Private/NSBezierPath+SDRoundedCorners.m
@@ -6,11 +6,11 @@
* file that was distributed with this source code.
*/
-#import "NSBezierPath+RoundedCorners.h"
+#import "NSBezierPath+SDRoundedCorners.h"
#if SD_MAC
-@implementation NSBezierPath (RoundedCorners)
+@implementation NSBezierPath (SDRoundedCorners)
+ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius {
NSBezierPath *path = [NSBezierPath bezierPath];
diff --git a/SDWebImage/Private/SDAssociatedObject.h b/SDWebImage/Private/SDAssociatedObject.h
new file mode 100644
index 00000000..199cf4fc
--- /dev/null
+++ b/SDWebImage/Private/SDAssociatedObject.h
@@ -0,0 +1,14 @@
+/*
+* This file is part of the SDWebImage package.
+* (c) Olivier Poitrey
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+#import "SDWebImageCompat.h"
+
+/// Copy the associated object from source image to target image. The associated object including all the category read/write properties.
+/// @param source source
+/// @param target target
+FOUNDATION_EXPORT void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable target);
diff --git a/SDWebImage/Private/SDAssociatedObject.m b/SDWebImage/Private/SDAssociatedObject.m
new file mode 100644
index 00000000..a7c70763
--- /dev/null
+++ b/SDWebImage/Private/SDAssociatedObject.m
@@ -0,0 +1,27 @@
+/*
+* This file is part of the SDWebImage package.
+* (c) Olivier Poitrey
+*
+* For the full copyright and license information, please view the LICENSE
+* file that was distributed with this source code.
+*/
+
+#import "SDAssociatedObject.h"
+#import "UIImage+Metadata.h"
+#import "UIImage+ExtendedCacheData.h"
+#import "UIImage+MemoryCacheCost.h"
+#import "UIImage+ForceDecode.h"
+
+void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable target) {
+ if (!source || !target) {
+ return;
+ }
+ // Image Metadata
+ target.sd_isIncremental = source.sd_isIncremental;
+ target.sd_imageLoopCount = source.sd_imageLoopCount;
+ target.sd_imageFormat = source.sd_imageFormat;
+ // Force Decode
+ target.sd_isDecoded = source.sd_isDecoded;
+ // Extended Cache Data
+ target.sd_extendedObject = source.sd_extendedObject;
+}
diff --git a/SDWebImage/Private/SDAsyncBlockOperation.h b/SDWebImage/Private/SDAsyncBlockOperation.h
index ecc68be8..a3480deb 100644
--- a/SDWebImage/Private/SDAsyncBlockOperation.h
+++ b/SDWebImage/Private/SDAsyncBlockOperation.h
@@ -11,6 +11,7 @@
@class SDAsyncBlockOperation;
typedef void (^SDAsyncBlock)(SDAsyncBlockOperation * __nonnull asyncOperation);
+/// A async block operation, success after you call `completer` (not like `NSBlockOperation` which is for sync block, success on return)
@interface SDAsyncBlockOperation : NSOperation
- (nonnull instancetype)initWithBlock:(nonnull SDAsyncBlock)block;
diff --git a/SDWebImage/Private/SDDeviceHelper.h b/SDWebImage/Private/SDDeviceHelper.h
index 740fb2e3..5d5676b1 100644
--- a/SDWebImage/Private/SDDeviceHelper.h
+++ b/SDWebImage/Private/SDDeviceHelper.h
@@ -9,6 +9,7 @@
#import
#import "SDWebImageCompat.h"
+/// Device information helper methods
@interface SDDeviceHelper : NSObject
+ (NSUInteger)totalMemory;
diff --git a/SDWebImage/Private/SDDisplayLink.h b/SDWebImage/Private/SDDisplayLink.h
index 60d4e80e..3ee8c6fd 100644
--- a/SDWebImage/Private/SDDisplayLink.h
+++ b/SDWebImage/Private/SDDisplayLink.h
@@ -9,9 +9,8 @@
#import
#import "SDWebImageCompat.h"
-// Cross-platform display link wrapper. Do not retain the target
-// Use `CADisplayLink` on iOS/tvOS, `CVDisplayLink` on macOS, `NSTimer` on watchOS
-
+/// Cross-platform display link wrapper. Do not retain the target
+/// Use `CADisplayLink` on iOS/tvOS, `CVDisplayLink` on macOS, `NSTimer` on watchOS
@interface SDDisplayLink : NSObject
@property (readonly, nonatomic, weak, nullable) id target;
diff --git a/SDWebImage/Private/SDFileAttributeHelper.h b/SDWebImage/Private/SDFileAttributeHelper.h
new file mode 100644
index 00000000..3ce6bade
--- /dev/null
+++ b/SDWebImage/Private/SDFileAttributeHelper.h
@@ -0,0 +1,19 @@
+//
+// This file is from https://gist.github.com/zydeco/6292773
+//
+// Created by Jesús A. Álvarez on 2008-12-17.
+// Copyright 2008-2009 namedfork.net. All rights reserved.
+//
+
+#import
+
+/// File Extended Attribute (xattr) helper methods
+@interface SDFileAttributeHelper : NSObject
+
++ (nullable NSArray *)extendedAttributeNamesAtPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err;
++ (BOOL)hasExtendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err;
++ (nullable NSData *)extendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err;
++ (BOOL)setExtendedAttribute:(nonnull NSString *)name value:(nonnull NSData *)value atPath:(nonnull NSString *)path traverseLink:(BOOL)follow overwrite:(BOOL)overwrite error:(NSError * _Nullable * _Nullable)err;
++ (BOOL)removeExtendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err;
+
+@end
diff --git a/SDWebImage/Private/SDFileAttributeHelper.m b/SDWebImage/Private/SDFileAttributeHelper.m
new file mode 100644
index 00000000..5122089d
--- /dev/null
+++ b/SDWebImage/Private/SDFileAttributeHelper.m
@@ -0,0 +1,127 @@
+//
+// This file is from https://gist.github.com/zydeco/6292773
+//
+// Created by Jesús A. Álvarez on 2008-12-17.
+// Copyright 2008-2009 namedfork.net. All rights reserved.
+//
+
+#import "SDFileAttributeHelper.h"
+#import
+
+@implementation SDFileAttributeHelper
+
++ (NSArray*)extendedAttributeNamesAtPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err {
+ int flags = follow? 0 : XATTR_NOFOLLOW;
+
+ // get size of name list
+ ssize_t nameBuffLen = listxattr([path fileSystemRepresentation], NULL, 0, flags);
+ if (nameBuffLen == -1) {
+ if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
+ @{
+ @"error": [NSString stringWithUTF8String:strerror(errno)],
+ @"function": @"listxattr",
+ @":path": path,
+ @":traverseLink": @(follow)
+ }
+ ];
+ return nil;
+ } else if (nameBuffLen == 0) return @[];
+
+ // get name list
+ NSMutableData *nameBuff = [NSMutableData dataWithLength:nameBuffLen];
+ listxattr([path fileSystemRepresentation], [nameBuff mutableBytes], nameBuffLen, flags);
+
+ // convert to array
+ NSMutableArray * names = [NSMutableArray arrayWithCapacity:5];
+ char *nextName, *endOfNames = [nameBuff mutableBytes] + nameBuffLen;
+ for(nextName = [nameBuff mutableBytes]; nextName < endOfNames; nextName += 1+strlen(nextName))
+ [names addObject:[NSString stringWithUTF8String:nextName]];
+ return names.copy;
+}
+
++ (BOOL)hasExtendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err {
+ int flags = follow? 0 : XATTR_NOFOLLOW;
+
+ // get size of name list
+ ssize_t nameBuffLen = listxattr([path fileSystemRepresentation], NULL, 0, flags);
+ if (nameBuffLen == -1) {
+ if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
+ @{
+ @"error": [NSString stringWithUTF8String:strerror(errno)],
+ @"function": @"listxattr",
+ @":path": path,
+ @":traverseLink": @(follow)
+ }
+ ];
+ return NO;
+ } else if (nameBuffLen == 0) return NO;
+
+ // get name list
+ NSMutableData *nameBuff = [NSMutableData dataWithLength:nameBuffLen];
+ listxattr([path fileSystemRepresentation], [nameBuff mutableBytes], nameBuffLen, flags);
+
+ // find our name
+ char *nextName, *endOfNames = [nameBuff mutableBytes] + nameBuffLen;
+ for(nextName = [nameBuff mutableBytes]; nextName < endOfNames; nextName += 1+strlen(nextName))
+ if (strcmp(nextName, [name UTF8String]) == 0) return YES;
+ return NO;
+}
+
++ (NSData *)extendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err {
+ int flags = follow? 0 : XATTR_NOFOLLOW;
+ // get length
+ ssize_t attrLen = getxattr([path fileSystemRepresentation], [name UTF8String], NULL, 0, 0, flags);
+ if (attrLen == -1) {
+ if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
+ @{
+ @"error": [NSString stringWithUTF8String:strerror(errno)],
+ @"function": @"getxattr",
+ @":name": name,
+ @":path": path,
+ @":traverseLink": @(follow)
+ }
+ ];
+ return nil;
+ }
+
+ // get attribute data
+ NSMutableData *attrData = [NSMutableData dataWithLength:attrLen];
+ getxattr([path fileSystemRepresentation], [name UTF8String], [attrData mutableBytes], attrLen, 0, flags);
+ return attrData;
+}
+
++ (BOOL)setExtendedAttribute:(NSString *)name value:(NSData *)value atPath:(NSString *)path traverseLink:(BOOL)follow overwrite:(BOOL)overwrite error:(NSError **)err {
+ int flags = (follow? 0 : XATTR_NOFOLLOW) | (overwrite? 0 : XATTR_CREATE);
+ if (0 == setxattr([path fileSystemRepresentation], [name UTF8String], [value bytes], [value length], 0, flags)) return YES;
+ // error
+ if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
+ @{
+ @"error": [NSString stringWithUTF8String:strerror(errno)],
+ @"function": @"setxattr",
+ @":name": name,
+ @":value.length": @(value.length),
+ @":path": path,
+ @":traverseLink": @(follow),
+ @":overwrite": @(overwrite)
+ }
+ ];
+ return NO;
+}
+
++ (BOOL)removeExtendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err {
+ int flags = (follow? 0 : XATTR_NOFOLLOW);
+ if (0 == removexattr([path fileSystemRepresentation], [name UTF8String], flags)) return YES;
+ // error
+ if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
+ @{
+ @"error": [NSString stringWithUTF8String:strerror(errno)],
+ @"function": @"removexattr",
+ @":name": name,
+ @":path": path,
+ @":traverseLink": @(follow)
+ }
+ ];
+ return NO;
+}
+
+@end
diff --git a/SDWebImage/Private/SDImageAssetManager.h b/SDWebImage/Private/SDImageAssetManager.h
index 68184187..88dee489 100644
--- a/SDWebImage/Private/SDImageAssetManager.h
+++ b/SDWebImage/Private/SDImageAssetManager.h
@@ -9,8 +9,8 @@
#import
#import "SDWebImageCompat.h"
-// Apple parse the Asset Catalog compiled file(`Assets.car`) by CoreUI.framework, however it's a private framework and there are no other ways to directly get the data. So we just process the normal bundle files :)
-
+/// A Image-Asset manager to work like UIKit/AppKit's image cache behavior
+/// Apple parse the Asset Catalog compiled file(`Assets.car`) by CoreUI.framework, however it's a private framework and there are no other ways to directly get the data. So we just process the normal bundle files :)
@interface SDImageAssetManager : NSObject
@property (nonatomic, strong, nonnull) NSMapTable *imageTable;
diff --git a/SDWebImage/Private/SDImageCachesManagerOperation.h b/SDWebImage/Private/SDImageCachesManagerOperation.h
index fddf78c1..0debe6ca 100644
--- a/SDWebImage/Private/SDImageCachesManagerOperation.h
+++ b/SDWebImage/Private/SDImageCachesManagerOperation.h
@@ -9,7 +9,7 @@
#import
#import "SDWebImageCompat.h"
-// This is used for operation management, but not for operation queue execute
+/// This is used for operation management, but not for operation queue execute
@interface SDImageCachesManagerOperation : NSOperation
@property (nonatomic, assign, readonly) NSUInteger pendingCount;
diff --git a/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h b/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h
index 4b500131..cb4ee2c4 100644
--- a/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h
+++ b/SDWebImage/Private/SDImageIOAnimatedCoderInternal.h
@@ -13,5 +13,6 @@
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source;
+ (NSUInteger)imageLoopCountWithSource:(nonnull CGImageSourceRef)source;
++ (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(nullable NSDictionary *)options;
@end
diff --git a/SDWebImage/Private/SDInternalMacros.h b/SDWebImage/Private/SDInternalMacros.h
index 837d77b0..aad700f8 100644
--- a/SDWebImage/Private/SDInternalMacros.h
+++ b/SDWebImage/Private/SDInternalMacros.h
@@ -21,6 +21,18 @@
#define SD_OPTIONS_CONTAINS(options, value) (((options) & (value)) == (value))
#endif
+#ifndef SD_CSTRING
+#define SD_CSTRING(str) #str
+#endif
+
+#ifndef SD_NSSTRING
+#define SD_NSSTRING(str) @(SD_CSTRING(str))
+#endif
+
+#ifndef SD_SEL_SPI
+#define SD_SEL_SPI(name) NSSelectorFromString([NSString stringWithFormat:@"_%@", SD_NSSTRING(name)])
+#endif
+
#ifndef weakify
#define weakify(...) \
sd_keywordify \
diff --git a/SDWebImage/Private/SDWeakProxy.h b/SDWebImage/Private/SDWeakProxy.h
index 4fd16228..d92c682b 100644
--- a/SDWebImage/Private/SDWeakProxy.h
+++ b/SDWebImage/Private/SDWeakProxy.h
@@ -9,6 +9,7 @@
#import
#import "SDWebImageCompat.h"
+/// A weak proxy which forward all the message to the target
@interface SDWeakProxy : NSProxy
@property (nonatomic, weak, readonly, nullable) id target;
diff --git a/SDWebImage/Private/UIColor+HexString.h b/SDWebImage/Private/UIColor+SDHexString.h
similarity index 93%
rename from SDWebImage/Private/UIColor+HexString.h
rename to SDWebImage/Private/UIColor+SDHexString.h
index 2a8a3d80..cf67186f 100644
--- a/SDWebImage/Private/UIColor+HexString.h
+++ b/SDWebImage/Private/UIColor+SDHexString.h
@@ -8,7 +8,7 @@
#import "SDWebImageCompat.h"
-@interface UIColor (HexString)
+@interface UIColor (SDHexString)
/**
Convenience way to get hex string from color. The output should always be 32-bit RGBA hex string like `#00000000`.
diff --git a/SDWebImage/Private/UIColor+HexString.m b/SDWebImage/Private/UIColor+SDHexString.m
similarity index 93%
rename from SDWebImage/Private/UIColor+HexString.m
rename to SDWebImage/Private/UIColor+SDHexString.m
index aebb6e3b..7b43c411 100644
--- a/SDWebImage/Private/UIColor+HexString.m
+++ b/SDWebImage/Private/UIColor+SDHexString.m
@@ -6,9 +6,9 @@
* file that was distributed with this source code.
*/
-#import "UIColor+HexString.h"
+#import "UIColor+SDHexString.h"
-@implementation UIColor (HexString)
+@implementation UIColor (SDHexString)
- (NSString *)sd_hexString {
CGFloat red, green, blue, alpha;
diff --git a/SDWebImage/include/SDWebImage/NSButton+WebCache.h b/SDWebImage/include/SDWebImage/NSButton+WebCache.h
new file mode 120000
index 00000000..c1f89924
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/NSButton+WebCache.h
@@ -0,0 +1 @@
+../../Core/NSButton+WebCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/NSData+ImageContentType.h b/SDWebImage/include/SDWebImage/NSData+ImageContentType.h
new file mode 120000
index 00000000..c0852b87
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/NSData+ImageContentType.h
@@ -0,0 +1 @@
+../../Core/NSData+ImageContentType.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/NSImage+Compatibility.h b/SDWebImage/include/SDWebImage/NSImage+Compatibility.h
new file mode 120000
index 00000000..cfad8619
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/NSImage+Compatibility.h
@@ -0,0 +1 @@
+../../Core/NSImage+Compatibility.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDAnimatedImage.h b/SDWebImage/include/SDWebImage/SDAnimatedImage.h
new file mode 120000
index 00000000..f26f5de1
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDAnimatedImage.h
@@ -0,0 +1 @@
+../../Core/SDAnimatedImage.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDAnimatedImagePlayer.h b/SDWebImage/include/SDWebImage/SDAnimatedImagePlayer.h
new file mode 120000
index 00000000..6f713485
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDAnimatedImagePlayer.h
@@ -0,0 +1 @@
+../../Core/SDAnimatedImagePlayer.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDAnimatedImageRep.h b/SDWebImage/include/SDWebImage/SDAnimatedImageRep.h
new file mode 120000
index 00000000..b19ae282
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDAnimatedImageRep.h
@@ -0,0 +1 @@
+../../Core/SDAnimatedImageRep.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDAnimatedImageView+WebCache.h b/SDWebImage/include/SDWebImage/SDAnimatedImageView+WebCache.h
new file mode 120000
index 00000000..6dbc5fa9
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDAnimatedImageView+WebCache.h
@@ -0,0 +1 @@
+../../Core/SDAnimatedImageView+WebCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDAnimatedImageView.h b/SDWebImage/include/SDWebImage/SDAnimatedImageView.h
new file mode 120000
index 00000000..0a49071c
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDAnimatedImageView.h
@@ -0,0 +1 @@
+../../Core/SDAnimatedImageView.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDDiskCache.h b/SDWebImage/include/SDWebImage/SDDiskCache.h
new file mode 120000
index 00000000..ba20df74
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDDiskCache.h
@@ -0,0 +1 @@
+../../Core/SDDiskCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDGraphicsImageRenderer.h b/SDWebImage/include/SDWebImage/SDGraphicsImageRenderer.h
new file mode 120000
index 00000000..a9605ccd
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDGraphicsImageRenderer.h
@@ -0,0 +1 @@
+../../Core/SDGraphicsImageRenderer.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageAPNGCoder.h b/SDWebImage/include/SDWebImage/SDImageAPNGCoder.h
new file mode 120000
index 00000000..20cba727
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageAPNGCoder.h
@@ -0,0 +1 @@
+../../Core/SDImageAPNGCoder.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageCache.h b/SDWebImage/include/SDWebImage/SDImageCache.h
new file mode 120000
index 00000000..0783c1ce
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageCache.h
@@ -0,0 +1 @@
+../../Core/SDImageCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageCacheConfig.h b/SDWebImage/include/SDWebImage/SDImageCacheConfig.h
new file mode 120000
index 00000000..0a58b4f8
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageCacheConfig.h
@@ -0,0 +1 @@
+../../Core/SDImageCacheConfig.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageCacheDefine.h b/SDWebImage/include/SDWebImage/SDImageCacheDefine.h
new file mode 120000
index 00000000..0a469880
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageCacheDefine.h
@@ -0,0 +1 @@
+../../Core/SDImageCacheDefine.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageCachesManager.h b/SDWebImage/include/SDWebImage/SDImageCachesManager.h
new file mode 120000
index 00000000..d01f76f4
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageCachesManager.h
@@ -0,0 +1 @@
+../../Core/SDImageCachesManager.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageCoder.h b/SDWebImage/include/SDWebImage/SDImageCoder.h
new file mode 120000
index 00000000..75472fb1
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageCoder.h
@@ -0,0 +1 @@
+../../Core/SDImageCoder.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageCoderHelper.h b/SDWebImage/include/SDWebImage/SDImageCoderHelper.h
new file mode 120000
index 00000000..3e68b945
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageCoderHelper.h
@@ -0,0 +1 @@
+../../Core/SDImageCoderHelper.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageCodersManager.h b/SDWebImage/include/SDWebImage/SDImageCodersManager.h
new file mode 120000
index 00000000..48b8b531
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageCodersManager.h
@@ -0,0 +1 @@
+../../Core/SDImageCodersManager.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageFrame.h b/SDWebImage/include/SDWebImage/SDImageFrame.h
new file mode 120000
index 00000000..ab05e26e
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageFrame.h
@@ -0,0 +1 @@
+../../Core/SDImageFrame.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageGIFCoder.h b/SDWebImage/include/SDWebImage/SDImageGIFCoder.h
new file mode 120000
index 00000000..04191623
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageGIFCoder.h
@@ -0,0 +1 @@
+../../Core/SDImageGIFCoder.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageGraphics.h b/SDWebImage/include/SDWebImage/SDImageGraphics.h
new file mode 120000
index 00000000..823dbd70
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageGraphics.h
@@ -0,0 +1 @@
+../../Core/SDImageGraphics.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageHEICCoder.h b/SDWebImage/include/SDWebImage/SDImageHEICCoder.h
new file mode 120000
index 00000000..9352871d
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageHEICCoder.h
@@ -0,0 +1 @@
+../../Core/SDImageHEICCoder.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageIOAnimatedCoder.h b/SDWebImage/include/SDWebImage/SDImageIOAnimatedCoder.h
new file mode 120000
index 00000000..3053de3b
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageIOAnimatedCoder.h
@@ -0,0 +1 @@
+../../Core/SDImageIOAnimatedCoder.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageIOCoder.h b/SDWebImage/include/SDWebImage/SDImageIOCoder.h
new file mode 120000
index 00000000..9cd1e482
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageIOCoder.h
@@ -0,0 +1 @@
+../../Core/SDImageIOCoder.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageLoader.h b/SDWebImage/include/SDWebImage/SDImageLoader.h
new file mode 120000
index 00000000..ba476e52
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageLoader.h
@@ -0,0 +1 @@
+../../Core/SDImageLoader.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageLoadersManager.h b/SDWebImage/include/SDWebImage/SDImageLoadersManager.h
new file mode 120000
index 00000000..442dd4c7
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageLoadersManager.h
@@ -0,0 +1 @@
+../../Core/SDImageLoadersManager.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDImageTransformer.h b/SDWebImage/include/SDWebImage/SDImageTransformer.h
new file mode 120000
index 00000000..184361bd
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDImageTransformer.h
@@ -0,0 +1 @@
+../../Core/SDImageTransformer.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDMemoryCache.h b/SDWebImage/include/SDWebImage/SDMemoryCache.h
new file mode 120000
index 00000000..47abcc12
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDMemoryCache.h
@@ -0,0 +1 @@
+../../Core/SDMemoryCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImage.h b/SDWebImage/include/SDWebImage/SDWebImage.h
new file mode 120000
index 00000000..12cbb09a
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImage.h
@@ -0,0 +1 @@
+../../../WebImage/SDWebImage.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageCacheKeyFilter.h b/SDWebImage/include/SDWebImage/SDWebImageCacheKeyFilter.h
new file mode 120000
index 00000000..7481d7d3
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageCacheKeyFilter.h
@@ -0,0 +1 @@
+../../Core/SDWebImageCacheKeyFilter.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageCacheSerializer.h b/SDWebImage/include/SDWebImage/SDWebImageCacheSerializer.h
new file mode 120000
index 00000000..b7d69b29
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageCacheSerializer.h
@@ -0,0 +1 @@
+../../Core/SDWebImageCacheSerializer.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageCompat.h b/SDWebImage/include/SDWebImage/SDWebImageCompat.h
new file mode 120000
index 00000000..c3c301d0
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageCompat.h
@@ -0,0 +1 @@
+../../Core/SDWebImageCompat.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageDefine.h b/SDWebImage/include/SDWebImage/SDWebImageDefine.h
new file mode 120000
index 00000000..6112007b
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageDefine.h
@@ -0,0 +1 @@
+../../Core/SDWebImageDefine.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageDownloader.h b/SDWebImage/include/SDWebImage/SDWebImageDownloader.h
new file mode 120000
index 00000000..ffb6248e
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageDownloader.h
@@ -0,0 +1 @@
+../../Core/SDWebImageDownloader.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageDownloaderConfig.h b/SDWebImage/include/SDWebImage/SDWebImageDownloaderConfig.h
new file mode 120000
index 00000000..8959bbbc
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageDownloaderConfig.h
@@ -0,0 +1 @@
+../../Core/SDWebImageDownloaderConfig.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageDownloaderDecryptor.h b/SDWebImage/include/SDWebImage/SDWebImageDownloaderDecryptor.h
new file mode 120000
index 00000000..fd6de4d8
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageDownloaderDecryptor.h
@@ -0,0 +1 @@
+../../Core/SDWebImageDownloaderDecryptor.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageDownloaderOperation.h b/SDWebImage/include/SDWebImage/SDWebImageDownloaderOperation.h
new file mode 120000
index 00000000..ef18b7c2
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageDownloaderOperation.h
@@ -0,0 +1 @@
+../../Core/SDWebImageDownloaderOperation.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageDownloaderRequestModifier.h b/SDWebImage/include/SDWebImage/SDWebImageDownloaderRequestModifier.h
new file mode 120000
index 00000000..f7aa82b9
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageDownloaderRequestModifier.h
@@ -0,0 +1 @@
+../../Core/SDWebImageDownloaderRequestModifier.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageDownloaderResponseModifier.h b/SDWebImage/include/SDWebImage/SDWebImageDownloaderResponseModifier.h
new file mode 120000
index 00000000..be391197
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageDownloaderResponseModifier.h
@@ -0,0 +1 @@
+../../Core/SDWebImageDownloaderResponseModifier.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageError.h b/SDWebImage/include/SDWebImage/SDWebImageError.h
new file mode 120000
index 00000000..b088fc03
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageError.h
@@ -0,0 +1 @@
+../../Core/SDWebImageError.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageIndicator.h b/SDWebImage/include/SDWebImage/SDWebImageIndicator.h
new file mode 120000
index 00000000..60f464f2
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageIndicator.h
@@ -0,0 +1 @@
+../../Core/SDWebImageIndicator.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageManager.h b/SDWebImage/include/SDWebImage/SDWebImageManager.h
new file mode 120000
index 00000000..20ddf7c7
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageManager.h
@@ -0,0 +1 @@
+../../Core/SDWebImageManager.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageOperation.h b/SDWebImage/include/SDWebImage/SDWebImageOperation.h
new file mode 120000
index 00000000..e7bf5bad
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageOperation.h
@@ -0,0 +1 @@
+../../Core/SDWebImageOperation.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageOptionsProcessor.h b/SDWebImage/include/SDWebImage/SDWebImageOptionsProcessor.h
new file mode 120000
index 00000000..b1e48c72
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageOptionsProcessor.h
@@ -0,0 +1 @@
+../../Core/SDWebImageOptionsProcessor.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImagePrefetcher.h b/SDWebImage/include/SDWebImage/SDWebImagePrefetcher.h
new file mode 120000
index 00000000..1346c8d2
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImagePrefetcher.h
@@ -0,0 +1 @@
+../../Core/SDWebImagePrefetcher.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/SDWebImageTransition.h b/SDWebImage/include/SDWebImage/SDWebImageTransition.h
new file mode 120000
index 00000000..19b24b09
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/SDWebImageTransition.h
@@ -0,0 +1 @@
+../../Core/SDWebImageTransition.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIButton+WebCache.h b/SDWebImage/include/SDWebImage/UIButton+WebCache.h
new file mode 120000
index 00000000..2a9820d8
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIButton+WebCache.h
@@ -0,0 +1 @@
+../../Core/UIButton+WebCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImage+ExtendedCacheData.h b/SDWebImage/include/SDWebImage/UIImage+ExtendedCacheData.h
new file mode 120000
index 00000000..fab25a7e
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImage+ExtendedCacheData.h
@@ -0,0 +1 @@
+../../Core/UIImage+ExtendedCacheData.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImage+ForceDecode.h b/SDWebImage/include/SDWebImage/UIImage+ForceDecode.h
new file mode 120000
index 00000000..0ef28712
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImage+ForceDecode.h
@@ -0,0 +1 @@
+../../Core/UIImage+ForceDecode.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImage+GIF.h b/SDWebImage/include/SDWebImage/UIImage+GIF.h
new file mode 120000
index 00000000..30e9dd4a
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImage+GIF.h
@@ -0,0 +1 @@
+../../Core/UIImage+GIF.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImage+MemoryCacheCost.h b/SDWebImage/include/SDWebImage/UIImage+MemoryCacheCost.h
new file mode 120000
index 00000000..81d6161d
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImage+MemoryCacheCost.h
@@ -0,0 +1 @@
+../../Core/UIImage+MemoryCacheCost.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImage+Metadata.h b/SDWebImage/include/SDWebImage/UIImage+Metadata.h
new file mode 120000
index 00000000..65eb11f4
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImage+Metadata.h
@@ -0,0 +1 @@
+../../Core/UIImage+Metadata.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImage+MultiFormat.h b/SDWebImage/include/SDWebImage/UIImage+MultiFormat.h
new file mode 120000
index 00000000..5366a33a
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImage+MultiFormat.h
@@ -0,0 +1 @@
+../../Core/UIImage+MultiFormat.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImage+Transform.h b/SDWebImage/include/SDWebImage/UIImage+Transform.h
new file mode 120000
index 00000000..73bdf3fd
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImage+Transform.h
@@ -0,0 +1 @@
+../../Core/UIImage+Transform.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImageView+HighlightedWebCache.h b/SDWebImage/include/SDWebImage/UIImageView+HighlightedWebCache.h
new file mode 120000
index 00000000..a9898113
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImageView+HighlightedWebCache.h
@@ -0,0 +1 @@
+../../Core/UIImageView+HighlightedWebCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIImageView+WebCache.h b/SDWebImage/include/SDWebImage/UIImageView+WebCache.h
new file mode 120000
index 00000000..8e732df7
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIImageView+WebCache.h
@@ -0,0 +1 @@
+../../Core/UIImageView+WebCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIView+WebCache.h b/SDWebImage/include/SDWebImage/UIView+WebCache.h
new file mode 120000
index 00000000..6a1b138c
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIView+WebCache.h
@@ -0,0 +1 @@
+../../Core/UIView+WebCache.h
\ No newline at end of file
diff --git a/SDWebImage/include/SDWebImage/UIView+WebCacheOperation.h b/SDWebImage/include/SDWebImage/UIView+WebCacheOperation.h
new file mode 120000
index 00000000..5631d02a
--- /dev/null
+++ b/SDWebImage/include/SDWebImage/UIView+WebCacheOperation.h
@@ -0,0 +1 @@
+../../Core/UIView+WebCacheOperation.h
\ No newline at end of file
diff --git a/SDWebImage/MapKit/MKAnnotationView+WebCache.h b/SDWebImageMapKit/MapKit/MKAnnotationView+WebCache.h
similarity index 99%
rename from SDWebImage/MapKit/MKAnnotationView+WebCache.h
rename to SDWebImageMapKit/MapKit/MKAnnotationView+WebCache.h
index 59346041..30f5aed1 100644
--- a/SDWebImage/MapKit/MKAnnotationView+WebCache.h
+++ b/SDWebImageMapKit/MapKit/MKAnnotationView+WebCache.h
@@ -6,12 +6,11 @@
* file that was distributed with this source code.
*/
-#import "SDWebImageCompat.h"
+#import
#if SD_UIKIT || SD_MAC
#import
-#import "SDWebImageManager.h"
/**
* Integrates SDWebImage async downloading and caching of remote images with MKAnnotationView.
diff --git a/SDWebImage/MapKit/MKAnnotationView+WebCache.m b/SDWebImageMapKit/MapKit/MKAnnotationView+WebCache.m
similarity index 93%
rename from SDWebImage/MapKit/MKAnnotationView+WebCache.m
rename to SDWebImageMapKit/MapKit/MKAnnotationView+WebCache.m
index adc02a92..4969c0ba 100644
--- a/SDWebImage/MapKit/MKAnnotationView+WebCache.m
+++ b/SDWebImageMapKit/MapKit/MKAnnotationView+WebCache.m
@@ -10,11 +10,6 @@
#if SD_UIKIT || SD_MAC
-#import "objc/runtime.h"
-#import "UIView+WebCacheOperation.h"
-#import "UIView+WebCache.h"
-#import "SDInternalMacros.h"
-
@implementation MKAnnotationView (WebCache)
- (void)sd_setImageWithURL:(nullable NSURL *)url {
@@ -55,14 +50,13 @@
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDExternalCompletionBlock)completedBlock {
- @weakify(self);
+ __weak typeof(self) wself = self;
[self sd_internalSetImageWithURL:url
placeholderImage:placeholder
options:options
context:context
setImageBlock:^(UIImage * _Nullable image, NSData * _Nullable imageData, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
- @strongify(self);
- self.image = image;
+ wself.image = image;
}
progress:progressBlock
completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
diff --git a/SDWebImageMapKit/include/SDWebImageMapKit/MKAnnotationView+WebCache.h b/SDWebImageMapKit/include/SDWebImageMapKit/MKAnnotationView+WebCache.h
new file mode 120000
index 00000000..28a11933
--- /dev/null
+++ b/SDWebImageMapKit/include/SDWebImageMapKit/MKAnnotationView+WebCache.h
@@ -0,0 +1 @@
+../../MapKit/MKAnnotationView+WebCache.h
\ No newline at end of file
diff --git a/SDWebImageMapKit/include/SDWebImageMapKit/SDWebImageMapKit.h b/SDWebImageMapKit/include/SDWebImageMapKit/SDWebImageMapKit.h
new file mode 120000
index 00000000..8d192441
--- /dev/null
+++ b/SDWebImageMapKit/include/SDWebImageMapKit/SDWebImageMapKit.h
@@ -0,0 +1 @@
+../../../WebImage/SDWebImageMapKit.h
\ No newline at end of file
diff --git a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj
index c98bbde1..12e43aec 100644
--- a/Tests/SDWebImage Tests.xcodeproj/project.pbxproj
+++ b/Tests/SDWebImage Tests.xcodeproj/project.pbxproj
@@ -9,11 +9,17 @@
/* Begin PBXBuildFile section */
1E3C51E919B46E370092B5E6 /* SDWebImageDownloaderTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */; };
2D7AF0601F329763000083C2 /* SDTestCase.m in Sources */ = {isa = PBXBuildFile; fileRef = 2D7AF05F1F329763000083C2 /* SDTestCase.m */; };
+ 320224F72440C39B00E5B29D /* TestImageLarge.png in Resources */ = {isa = PBXBuildFile; fileRef = 320224F62440C39B00E5B29D /* TestImageLarge.png */; };
+ 320224F82440C39B00E5B29D /* TestImageLarge.png in Resources */ = {isa = PBXBuildFile; fileRef = 320224F62440C39B00E5B29D /* TestImageLarge.png */; };
+ 320224F92440C39B00E5B29D /* TestImageLarge.png in Resources */ = {isa = PBXBuildFile; fileRef = 320224F62440C39B00E5B29D /* TestImageLarge.png */; };
320630412085A37C006E0FA4 /* SDAnimatedImageTest.m in Sources */ = {isa = PBXBuildFile; fileRef = 32A571552037DB2D002EDAAE /* SDAnimatedImageTest.m */; };
3222417F2272F808002429DB /* SDUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3222417E2272F808002429DB /* SDUtilsTests.m */; };
322241802272F808002429DB /* SDUtilsTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 3222417E2272F808002429DB /* SDUtilsTests.m */; };
3226ECBB20754F7700FAFACF /* SDWebImageTestDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */; };
3226ECBC20754F7700FAFACF /* SDWebImageTestDownloadOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */; };
+ 3234306223E2BAC800C290C8 /* TestImage.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 3234306123E2BAC800C290C8 /* TestImage.pdf */; };
+ 3234306323E2BAC800C290C8 /* TestImage.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 3234306123E2BAC800C290C8 /* TestImage.pdf */; };
+ 3234306423E2BAC800C290C8 /* TestImage.pdf in Resources */ = {isa = PBXBuildFile; fileRef = 3234306123E2BAC800C290C8 /* TestImage.pdf */; };
323B8E1F20862322008952BE /* SDWebImageTestLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */; };
323B8E2020862322008952BE /* SDWebImageTestLoader.m in Sources */ = {isa = PBXBuildFile; fileRef = 323B8E1E20862322008952BE /* SDWebImageTestLoader.m */; };
324047442271956F007C53E1 /* TestEXIF.png in Resources */ = {isa = PBXBuildFile; fileRef = 324047432271956F007C53E1 /* TestEXIF.png */; };
@@ -104,9 +110,11 @@
1E3C51E819B46E370092B5E6 /* SDWebImageDownloaderTests.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDWebImageDownloaderTests.m; sourceTree = ""; };
2D7AF05E1F329763000083C2 /* SDTestCase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDTestCase.h; sourceTree = ""; };
2D7AF05F1F329763000083C2 /* SDTestCase.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDTestCase.m; sourceTree = ""; };
+ 320224F62440C39B00E5B29D /* TestImageLarge.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestImageLarge.png; sourceTree = ""; };
3222417E2272F808002429DB /* SDUtilsTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDUtilsTests.m; sourceTree = ""; };
3226ECB920754F7700FAFACF /* SDWebImageTestDownloadOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestDownloadOperation.h; sourceTree = ""; };
3226ECBA20754F7700FAFACF /* SDWebImageTestDownloadOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestDownloadOperation.m; sourceTree = ""; };
+ 3234306123E2BAC800C290C8 /* TestImage.pdf */ = {isa = PBXFileReference; lastKnownFileType = image.pdf; path = TestImage.pdf; sourceTree = ""; };
323B8E1D20862322008952BE /* SDWebImageTestLoader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDWebImageTestLoader.h; sourceTree = ""; };
323B8E1E20862322008952BE /* SDWebImageTestLoader.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWebImageTestLoader.m; sourceTree = ""; };
324047432271956F007C53E1 /* TestEXIF.png */ = {isa = PBXFileReference; lastKnownFileType = image.png; path = TestEXIF.png; sourceTree = ""; };
@@ -235,9 +243,11 @@
326E69462334C0C200B7252C /* TestLoopCount.gif */,
5F7F38AC1AE2A77A00B0E330 /* TestImage.jpg */,
43828A441DA67F9900000E62 /* TestImageLarge.jpg */,
+ 320224F62440C39B00E5B29D /* TestImageLarge.png */,
433BBBB81D7EF8260086B6E9 /* TestImage.png */,
327A418B211D660600495442 /* TestImage.heic */,
32905E63211D786E00460FCF /* TestImage.heif */,
+ 3234306123E2BAC800C290C8 /* TestImage.pdf */,
327054E1206CEFF3006EA328 /* TestImageAnimated.apng */,
3297A09E23374D1600814590 /* TestImageAnimated.heic */,
);
@@ -443,6 +453,8 @@
3299228B2365DC6C00EAFD97 /* TestImage.heic in Resources */,
329922872365DC6C00EAFD97 /* TestLoopCount.gif in Resources */,
3299228C2365DC6C00EAFD97 /* TestImage.heif in Resources */,
+ 3234306423E2BAC800C290C8 /* TestImage.pdf in Resources */,
+ 320224F92440C39B00E5B29D /* TestImageLarge.png in Resources */,
329922892365DC6C00EAFD97 /* TestImageLarge.jpg in Resources */,
3299228A2365DC6C00EAFD97 /* TestImage.png in Resources */,
329922842365DC6C00EAFD97 /* MonochromeTestImage.jpg in Resources */,
@@ -461,6 +473,8 @@
32B99EA3203B31360017FD66 /* TestImage.gif in Resources */,
324047452271956F007C53E1 /* TestEXIF.png in Resources */,
32B99EA4203B31360017FD66 /* TestImage.jpg in Resources */,
+ 3234306323E2BAC800C290C8 /* TestImage.pdf in Resources */,
+ 320224F82440C39B00E5B29D /* TestImageLarge.png in Resources */,
32B99EA6203B31360017FD66 /* TestImage.png in Resources */,
3297A0A023374D1700814590 /* TestImageAnimated.heic in Resources */,
32B99EA2203B31360017FD66 /* MonochromeTestImage.jpg in Resources */,
@@ -479,6 +493,8 @@
5F7F38AD1AE2A77A00B0E330 /* TestImage.jpg in Resources */,
32905E64211D786E00460FCF /* TestImage.heif in Resources */,
43828A451DA67F9900000E62 /* TestImageLarge.jpg in Resources */,
+ 3234306223E2BAC800C290C8 /* TestImage.pdf in Resources */,
+ 320224F72440C39B00E5B29D /* TestImageLarge.png in Resources */,
433BBBB71D7EF8200086B6E9 /* TestImage.gif in Resources */,
433BBBB91D7EF8260086B6E9 /* TestImage.png in Resources */,
3297A09F23374D1700814590 /* TestImageAnimated.heic in Resources */,
diff --git a/Tests/Tests/Images/TestImage.pdf b/Tests/Tests/Images/TestImage.pdf
new file mode 100644
index 00000000..132681b0
Binary files /dev/null and b/Tests/Tests/Images/TestImage.pdf differ
diff --git a/Tests/Tests/Images/TestImageLarge.png b/Tests/Tests/Images/TestImageLarge.png
new file mode 100644
index 00000000..4573dd8f
Binary files /dev/null and b/Tests/Tests/Images/TestImageLarge.png differ
diff --git a/Tests/Tests/SDAnimatedImageTest.m b/Tests/Tests/SDAnimatedImageTest.m
index b3fb53f2..0640e3dc 100644
--- a/Tests/Tests/SDAnimatedImageTest.m
+++ b/Tests/Tests/SDAnimatedImageTest.m
@@ -8,6 +8,7 @@
*/
#import "SDTestCase.h"
+#import "SDInternalMacros.h"
#import
static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop count
@@ -397,6 +398,76 @@ static const NSUInteger kTestGIFFrameCount = 5; // local TestImage.gif loop coun
[self waitForExpectationsWithCommonTimeout];
}
+- (void)test27AnimatedImageProgressiveAnimation {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"test SDAnimatedImageView progressive animation rendering"];
+
+ // Simulate progressive download
+ NSData *fullData = [self testAPNGPData];
+ NSUInteger length = fullData.length;
+
+ SDAnimatedImageView *imageView = [SDAnimatedImageView new];
+#if SD_UIKIT
+ [self.window addSubview:imageView];
+#else
+ [self.window.contentView addSubview:imageView];
+#endif
+
+ __block NSUInteger previousFrameIndex = 0;
+ @weakify(imageView);
+ // Observe to check rendering behavior using frame index
+ [self.KVOController observe:imageView keyPath:NSStringFromSelector(@selector(currentFrameIndex)) options:NSKeyValueObservingOptionNew block:^(id _Nullable observer, id _Nonnull object, NSDictionary * _Nonnull change) {
+ @strongify(imageView);
+ NSUInteger currentFrameIndex = [change[NSKeyValueChangeNewKey] unsignedIntegerValue];
+ printf("Animation Frame Index: %lu\n", (unsigned long)currentFrameIndex);
+
+ // The last time should not be progressive
+ if (currentFrameIndex == 0 && !imageView.isProgressive) {
+ [self.KVOController unobserve:imageView];
+ [expectation fulfill];
+ } else {
+ // Each progressive rendering should render new frame index, no backward and should stop at last frame index
+ expect(currentFrameIndex - previousFrameIndex).beGreaterThanOrEqualTo(0);
+ previousFrameIndex = currentFrameIndex;
+ }
+ }];
+
+ SDImageAPNGCoder *coder = [[SDImageAPNGCoder alloc] initIncrementalWithOptions:nil];
+ // Setup Data
+ NSData *setupData = [fullData subdataWithRange:NSMakeRange(0, length / 3.0)];
+ [coder updateIncrementalData:setupData finished:NO];
+ imageView.shouldIncrementalLoad = YES;
+ __block SDAnimatedImage *progressiveImage = [[SDAnimatedImage alloc] initWithAnimatedCoder:coder scale:1];
+ progressiveImage.sd_isIncremental = YES;
+ imageView.image = progressiveImage;
+ expect(imageView.isProgressive).beTruthy();
+
+ __block NSUInteger partialFrameCount;
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(2 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ // Partial Data
+ NSData *partialData = [fullData subdataWithRange:NSMakeRange(0, length * 2.0 / 3.0)];
+ [coder updateIncrementalData:partialData finished:NO];
+ partialFrameCount = [coder animatedImageFrameCount];
+ expect(partialFrameCount).beGreaterThan(1);
+ progressiveImage = [[SDAnimatedImage alloc] initWithAnimatedCoder:coder scale:1];
+ progressiveImage.sd_isIncremental = YES;
+ imageView.image = progressiveImage;
+ expect(imageView.isProgressive).beTruthy();
+ });
+
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
+ // Full Data
+ [coder updateIncrementalData:fullData finished:YES];
+ progressiveImage = [[SDAnimatedImage alloc] initWithAnimatedCoder:coder scale:1];
+ progressiveImage.sd_isIncremental = NO;
+ imageView.image = progressiveImage;
+ NSUInteger fullFrameCount = [coder animatedImageFrameCount];
+ expect(fullFrameCount).beGreaterThan(partialFrameCount);
+ expect(imageView.isProgressive).beFalsy();
+ });
+
+ [self waitForExpectationsWithCommonTimeout];
+}
+
#pragma mark - Helper
- (UIWindow *)window {
if (!_window) {
diff --git a/Tests/Tests/SDCategoriesTests.m b/Tests/Tests/SDCategoriesTests.m
index f38fe1bc..ee5aaf56 100644
--- a/Tests/Tests/SDCategoriesTests.m
+++ b/Tests/Tests/SDCategoriesTests.m
@@ -25,7 +25,7 @@
// Test invalid format
CFStringRef type = [NSData sd_UTTypeFromImageFormat:SDImageFormatUndefined];
- expect(CFStringCompare(kUTTypePNG, type, 0)).equal(kCFCompareEqualTo);
+ expect(CFStringCompare(kUTTypeImage, type, 0)).equal(kCFCompareEqualTo);
expect([NSData sd_imageFormatFromUTType:kUTTypeImage]).equal(SDImageFormatUndefined);
}
diff --git a/Tests/Tests/SDImageCacheTests.m b/Tests/Tests/SDImageCacheTests.m
index 3e54963e..96fb0f48 100644
--- a/Tests/Tests/SDImageCacheTests.m
+++ b/Tests/Tests/SDImageCacheTests.m
@@ -391,6 +391,99 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
[self waitForExpectationsWithCommonTimeout];
}
+- (void)test42StoreCacheWithImageAndFormatWithoutImageData {
+ XCTestExpectation *expectation1 = [self expectationWithDescription:@"StoreImage UIImage without sd_imageFormat should use PNG for alpha channel"];
+ XCTestExpectation *expectation2 = [self expectationWithDescription:@"StoreImage UIImage without sd_imageFormat should use JPEG for non-alpha channel"];
+ XCTestExpectation *expectation3 = [self expectationWithDescription:@"StoreImage UIImage/UIAnimatedImage with sd_imageFormat should use that format"];
+ XCTestExpectation *expectation4 = [self expectationWithDescription:@"StoreImage SDAnimatedImage should use animatedImageData"];
+ XCTestExpectation *expectation5 = [self expectationWithDescription:@"StoreImage UIAnimatedImage without sd_imageFormat should use GIF"];
+
+ NSString *kAnimatedImageKey1 = @"kAnimatedImageKey1";
+ NSString *kAnimatedImageKey2 = @"kAnimatedImageKey2";
+ NSString *kAnimatedImageKey3 = @"kAnimatedImageKey3";
+ NSString *kAnimatedImageKey4 = @"kAnimatedImageKey4";
+ NSString *kAnimatedImageKey5 = @"kAnimatedImageKey5";
+
+ // Case 1: UIImage without `sd_imageFormat` should use PNG for alpha channel
+ NSData *pngData = [NSData dataWithContentsOfFile:[self testPNGPath]];
+ UIImage *pngImage = [UIImage sd_imageWithData:pngData];
+ expect(pngImage.sd_isAnimated).beFalsy();
+ expect(pngImage.sd_imageFormat).equal(SDImageFormatPNG);
+ // Remove sd_imageFormat
+ pngImage.sd_imageFormat = SDImageFormatUndefined;
+ // Check alpha channel
+ expect([SDImageCoderHelper CGImageContainsAlpha:pngImage.CGImage]).beTruthy();
+
+ [SDImageCache.sharedImageCache storeImage:pngImage forKey:kAnimatedImageKey1 toDisk:YES completion:^{
+ UIImage *diskImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kAnimatedImageKey1];
+ // Should save to PNG
+ expect(diskImage.sd_isAnimated).beFalsy();
+ expect(diskImage.sd_imageFormat).equal(SDImageFormatPNG);
+ [expectation1 fulfill];
+ }];
+
+ // Case 2: UIImage without `sd_imageFormat` should use JPEG for non-alpha channel
+ SDGraphicsImageRendererFormat *format = [SDGraphicsImageRendererFormat preferredFormat];
+ format.opaque = YES;
+ SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:pngImage.size format:format];
+ // Non-alpha image, also test `SDGraphicsImageRenderer` behavior here :)
+ UIImage *nonAlphaImage = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
+ [pngImage drawInRect:CGRectMake(0, 0, pngImage.size.width, pngImage.size.height)];
+ }];
+ expect(nonAlphaImage).notTo.beNil();
+ expect([SDImageCoderHelper CGImageContainsAlpha:nonAlphaImage.CGImage]).beFalsy();
+
+ [SDImageCache.sharedImageCache storeImage:nonAlphaImage forKey:kAnimatedImageKey2 toDisk:YES completion:^{
+ UIImage *diskImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kAnimatedImageKey2];
+ // Should save to JPEG
+ expect(diskImage.sd_isAnimated).beFalsy();
+ expect(diskImage.sd_imageFormat).equal(SDImageFormatJPEG);
+ [expectation2 fulfill];
+ }];
+
+ NSData *gifData = [NSData dataWithContentsOfFile:[self testGIFPath]];
+ UIImage *gifImage = [UIImage sd_imageWithData:gifData]; // UIAnimatedImage
+ expect(gifImage.sd_isAnimated).beTruthy();
+ expect(gifImage.sd_imageFormat).equal(SDImageFormatGIF);
+
+ // Case 3: UIImage with `sd_imageFormat` should use that format
+ [SDImageCache.sharedImageCache storeImage:gifImage forKey:kAnimatedImageKey3 toDisk:YES completion:^{
+ UIImage *diskImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kAnimatedImageKey3];
+ // Should save to GIF
+ expect(diskImage.sd_isAnimated).beTruthy();
+ expect(diskImage.sd_imageFormat).equal(SDImageFormatGIF);
+ [expectation3 fulfill];
+ }];
+
+ // Case 4: SDAnimatedImage should use `animatedImageData`
+ SDAnimatedImage *animatedImage = [SDAnimatedImage imageWithData:gifData];
+ expect(animatedImage.animatedImageData).notTo.beNil();
+ [SDImageCache.sharedImageCache storeImage:animatedImage forKey:kAnimatedImageKey4 toDisk:YES completion:^{
+ NSData *data = [SDImageCache.sharedImageCache diskImageDataForKey:kAnimatedImageKey4];
+ // Should save with animatedImageData
+ expect(data).equal(animatedImage.animatedImageData);
+ [expectation4 fulfill];
+ }];
+
+ // Case 5: UIAnimatedImage without sd_imageFormat should use GIF not APNG
+ NSData *apngData = [NSData dataWithContentsOfFile:[self testAPNGPath]];
+ UIImage *apngImage = [UIImage sd_imageWithData:apngData];
+ expect(apngImage.sd_isAnimated).beTruthy();
+ expect(apngImage.sd_imageFormat).equal(SDImageFormatPNG);
+ // Remove sd_imageFormat
+ apngImage.sd_imageFormat = SDImageFormatUndefined;
+
+ [SDImageCache.sharedImageCache storeImage:apngImage forKey:kAnimatedImageKey5 toDisk:YES completion:^{
+ UIImage *diskImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kAnimatedImageKey5];
+ // Should save to GIF
+ expect(diskImage.sd_isAnimated).beTruthy();
+ expect(diskImage.sd_imageFormat).equal(SDImageFormatGIF);
+ [expectation5 fulfill];
+ }];
+
+ [self waitForExpectationsWithCommonTimeout];
+}
+
#pragma mark - SDMemoryCache & SDDiskCache
- (void)test42CustomMemoryCache {
SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init];
@@ -491,11 +584,33 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
}
#endif
+- (void)test47DiskCacheExtendedData {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache extended data read/write works"];
+ UIImage *image = [self testPNGImage];
+ NSDictionary *extendedObject = @{@"Test" : @"Object"};
+ image.sd_extendedObject = extendedObject;
+ [SDImageCache.sharedImageCache removeImageFromMemoryForKey:kTestImageKeyPNG];
+ [SDImageCache.sharedImageCache removeImageFromDiskForKey:kTestImageKeyPNG];
+ // Write extended data
+ [SDImageCache.sharedImageCache storeImage:image forKey:kTestImageKeyPNG completion:^{
+ NSData *extendedData = [SDImageCache.sharedImageCache.diskCache extendedDataForKey:kTestImageKeyPNG];
+ expect(extendedData).toNot.beNil();
+ // Read extended data
+ UIImage *newImage = [SDImageCache.sharedImageCache imageFromDiskCacheForKey:kTestImageKeyPNG];
+ id newExtendedObject = newImage.sd_extendedObject;
+ expect(extendedObject).equal(newExtendedObject);
+ // Remove extended data
+ [SDImageCache.sharedImageCache.diskCache setExtendedData:nil forKey:kTestImageKeyPNG];
+ [expectation fulfill];
+ }];
+ [self waitForExpectationsWithCommonTimeout];
+}
+
#pragma mark - SDImageCache & SDImageCachesManager
- (void)test50SDImageCacheQueryOp {
XCTestExpectation *expectation = [self expectationWithDescription:@"SDImageCache query op works"];
[[SDImageCache sharedImageCache] storeImage:[self testJPEGImage] forKey:kTestImageKeyJPEG toDisk:NO completion:nil];
- [[SDImageCachesManager sharedManager] queryImageForKey:kTestImageKeyJPEG options:0 context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
+ [[SDImageCachesManager sharedManager] queryImageForKey:kTestImageKeyJPEG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
expect(image).notTo.beNil();
[expectation fulfill];
}];
@@ -565,7 +680,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly;
cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly;
cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyLowestOnly;
- [cachesManager queryImageForKey:kTestImageKeyJPEG options:0 context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
+ [cachesManager queryImageForKey:kTestImageKeyJPEG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
expect(image).to.beNil();
}];
[cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kTestImageKeyJPEG cacheType:SDImageCacheTypeMemory completion:nil];
@@ -584,7 +699,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyHighestOnly;
- [cachesManager queryImageForKey:kTestImageKeyPNG options:0 context:nil completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
+ [cachesManager queryImageForKey:kTestImageKeyPNG options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
expect(image).to.beNil();
}];
[cachesManager storeImage:[self testPNGImage] imageData:nil forKey:kTestImageKeyPNG cacheType:SDImageCacheTypeMemory completion:nil];
@@ -617,7 +732,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
- [cachesManager queryImageForKey:kConcurrentTestImageKey options:0 context:nil completion:nil];
+ [cachesManager queryImageForKey:kConcurrentTestImageKey options:0 context:nil cacheType:SDImageCacheTypeAll completion:nil];
[cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kConcurrentTestImageKey cacheType:SDImageCacheTypeMemory completion:nil];
[cachesManager removeImageForKey:kConcurrentTestImageKey cacheType:SDImageCacheTypeMemory completion:nil];
[cachesManager clearWithCacheType:SDImageCacheTypeMemory completion:nil];
@@ -657,7 +772,7 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
cachesManager.removeOperationPolicy = SDImageCachesManagerOperationPolicySerial;
cachesManager.containsOperationPolicy = SDImageCachesManagerOperationPolicySerial;
cachesManager.clearOperationPolicy = SDImageCachesManagerOperationPolicySerial;
- [cachesManager queryImageForKey:kSerialTestImageKey options:0 context:nil completion:nil];
+ [cachesManager queryImageForKey:kSerialTestImageKey options:0 context:nil cacheType:SDImageCacheTypeAll completion:nil];
[cachesManager storeImage:[self testJPEGImage] imageData:nil forKey:kSerialTestImageKey cacheType:SDImageCacheTypeMemory completion:nil];
[cachesManager removeImageForKey:kSerialTestImageKey cacheType:SDImageCacheTypeMemory completion:nil];
[cachesManager clearWithCacheType:SDImageCacheTypeMemory completion:nil];
@@ -678,6 +793,41 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
[self waitForExpectationsWithCommonTimeout];
}
+- (void)test58CustomImageCache {
+ NSString *cachePath = [[self userCacheDirectory] stringByAppendingPathComponent:@"custom"];
+ SDImageCacheConfig *config = [[SDImageCacheConfig alloc] init];
+ SDWebImageTestCache *cache = [[SDWebImageTestCache alloc] initWithCachePath:cachePath config:config];
+ expect(cache.memoryCache).notTo.beNil();
+ expect(cache.diskCache).notTo.beNil();
+
+ // Clear
+ [cache clearWithCacheType:SDImageCacheTypeAll completion:nil];
+ // Store
+ UIImage *image1 = self.testJPEGImage;
+ NSString *key1 = @"testJPEGImage";
+ [cache storeImage:image1 imageData:nil forKey:key1 cacheType:SDImageCacheTypeAll completion:nil];
+ // Contain
+ [cache containsImageForKey:key1 cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) {
+ expect(containsCacheType).equal(SDImageCacheTypeMemory);
+ }];
+ // Query
+ [cache queryImageForKey:key1 options:0 context:nil cacheType:SDImageCacheTypeAll completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
+ expect(image).equal(image1);
+ expect(data).beNil();
+ expect(cacheType).equal(SDImageCacheTypeMemory);
+ }];
+ // Remove
+ [cache removeImageForKey:key1 cacheType:SDImageCacheTypeAll completion:nil];
+ // Contain
+ [cache containsImageForKey:key1 cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType containsCacheType) {
+ expect(containsCacheType).equal(SDImageCacheTypeNone);
+ }];
+ // Clear
+ [cache clearWithCacheType:SDImageCacheTypeAll completion:nil];
+ NSArray *cacheFiles = [cache.diskCache.fileManager contentsOfDirectoryAtPath:cachePath error:nil];
+ expect(cacheFiles.count).equal(0);
+}
+
#pragma mark Helper methods
- (UIImage *)testJPEGImage {
@@ -705,6 +855,15 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
return reusableImage;
}
+- (UIImage *)testAPNGImage {
+ static UIImage *reusableImage = nil;
+ if (!reusableImage) {
+ NSData *data = [NSData dataWithContentsOfFile:[self testAPNGPath]];
+ reusableImage = [UIImage sd_imageWithData:data];
+ }
+ return reusableImage;
+}
+
- (NSString *)testJPEGPath {
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];
@@ -721,6 +880,12 @@ static NSString *kTestImageKeyPNG = @"TestImageKey.png";
return testPath;
}
+- (NSString *)testAPNGPath {
+ NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
+ NSString *testPath = [testBundle pathForResource:@"TestImageAnimated" ofType:@"apng"];
+ return testPath;
+}
+
- (nullable NSString *)userCacheDirectory {
NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
return paths.firstObject;
diff --git a/Tests/Tests/SDImageCoderTests.m b/Tests/Tests/SDImageCoderTests.m
index 0320c2c2..eaf58ec9 100644
--- a/Tests/Tests/SDImageCoderTests.m
+++ b/Tests/Tests/SDImageCoderTests.m
@@ -8,6 +8,7 @@
*/
#import "SDTestCase.h"
+#import "UIColor+SDHexString.h"
@interface SDWebImageDecoderTests : SDTestCase
@@ -20,9 +21,8 @@
expect([UIImage sd_decodedAndScaledDownImageWithImage:nil]).to.beNil();
}
-#if SD_UIKIT
- (void)test02ThatDecodedImageWithImageWorksWithARegularJPGImage {
- NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
UIImage *decodedImage = [UIImage sd_decodedImageWithImage:image];
expect(decodedImage).toNot.beNil();
@@ -32,16 +32,20 @@
}
- (void)test03ThatDecodedImageWithImageDoesNotDecodeAnimatedImages {
- NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"gif"];
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"gif"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
+#if SD_MAC
+ UIImage *animatedImage = image;
+#else
UIImage *animatedImage = [UIImage animatedImageWithImages:@[image] duration:0];
+#endif
UIImage *decodedImage = [UIImage sd_decodedImageWithImage:animatedImage];
expect(decodedImage).toNot.beNil();
expect(decodedImage).to.equal(animatedImage);
}
- (void)test04ThatDecodedImageWithImageWorksWithAlphaImages {
- NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"png"];
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"png"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
UIImage *decodedImage = [UIImage sd_decodedImageWithImage:image];
expect(decodedImage).toNot.beNil();
@@ -49,7 +53,7 @@
}
- (void)test05ThatDecodedImageWithImageWorksEvenWithMonochromeImage {
- NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"MonochromeTestImage" ofType:@"jpg"];
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"MonochromeTestImage" ofType:@"jpg"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
UIImage *decodedImage = [UIImage sd_decodedImageWithImage:image];
expect(decodedImage).toNot.beNil();
@@ -59,9 +63,9 @@
}
- (void)test06ThatDecodeAndScaleDownImageWorks {
- NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
- UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image];
+ UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image limitBytes:(60 * 1024 * 1024)];
expect(decodedImage).toNot.beNil();
expect(decodedImage).toNot.equal(image);
expect(decodedImage.size.width).toNot.equal(image.size.width);
@@ -70,7 +74,7 @@
}
- (void)test07ThatDecodeAndScaleDownImageDoesNotScaleSmallerImage {
- NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"jpg"];
UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
UIImage *decodedImage = [UIImage sd_decodedAndScaledDownImageWithImage:image];
expect(decodedImage).toNot.beNil();
@@ -78,7 +82,65 @@
expect(decodedImage.size.width).to.equal(image.size.width);
expect(decodedImage.size.height).to.equal(image.size.height);
}
-#endif
+
+- (void)test08ThatEncodeAlphaImageToJPGWithBackgroundColor {
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImage" ofType:@"png"];
+ UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
+ UIColor *backgroundColor = [UIColor blackColor];
+ NSData *encodedData = [SDImageCodersManager.sharedManager encodedDataWithImage:image format:SDImageFormatJPEG options:@{SDImageCoderEncodeBackgroundColor : backgroundColor}];
+ expect(encodedData).notTo.beNil();
+ UIImage *decodedImage = [SDImageCodersManager.sharedManager decodedImageWithData:encodedData options:nil];
+ expect(decodedImage).notTo.beNil();
+ expect(decodedImage.size.width).to.equal(image.size.width);
+ expect(decodedImage.size.height).to.equal(image.size.height);
+ // Check background color, should not be white but the black color
+ UIColor *testColor = [decodedImage sd_colorAtPoint:CGPointMake(1, 1)];
+ expect(testColor.sd_hexString).equal(backgroundColor.sd_hexString);
+}
+
+- (void)test09ThatJPGImageEncodeWithMaxFileSize {
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"jpg"];
+ UIImage *image = [[UIImage alloc] initWithContentsOfFile:testImagePath];
+ // This large JPEG encoding size between (770KB ~ 2.23MB)
+ NSUInteger limitFileSize = 1 * 1024 * 1024; // 1MB
+ // 100 quality (biggest)
+ NSData *maxEncodedData = [SDImageCodersManager.sharedManager encodedDataWithImage:image format:SDImageFormatJPEG options:nil];
+ expect(maxEncodedData).notTo.beNil();
+ expect(maxEncodedData.length).beGreaterThan(limitFileSize);
+ // 0 quality (smallest)
+ NSData *minEncodedData = [SDImageCodersManager.sharedManager encodedDataWithImage:image format:SDImageFormatJPEG options:@{SDImageCoderEncodeCompressionQuality : @(0)}];
+ expect(minEncodedData).notTo.beNil();
+ expect(minEncodedData.length).beLessThan(limitFileSize);
+ NSData *limitEncodedData = [SDImageCodersManager.sharedManager encodedDataWithImage:image format:SDImageFormatJPEG options:@{SDImageCoderEncodeMaxFileSize : @(limitFileSize)}];
+ expect(limitEncodedData).notTo.beNil();
+ // So, if we limit the file size, the output data should in (770KB ~ 2.23MB)
+ expect(limitEncodedData.length).beLessThan(maxEncodedData.length);
+ expect(limitEncodedData.length).beGreaterThan(minEncodedData.length);
+}
+
+- (void)test10ThatAnimatedImageCacheImmediatelyWorks {
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestImageLarge" ofType:@"png"];
+ NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
+
+ // Check that animated image rendering should not use lazy decoding (performance related)
+ CFAbsoluteTime begin = CFAbsoluteTimeGetCurrent();
+ SDImageAPNGCoder *coder = [[SDImageAPNGCoder alloc] initWithAnimatedImageData:testImageData options:@{SDImageCoderDecodeFirstFrameOnly : @(NO)}];
+ UIImage *imageWithoutLazyDecoding = [coder animatedImageFrameAtIndex:0];
+ CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
+ CFAbsoluteTime duration = end - begin;
+ expect(imageWithoutLazyDecoding.sd_isDecoded).beTruthy();
+
+ // Check that static image rendering should use lazy decoding
+ CFAbsoluteTime begin2 = CFAbsoluteTimeGetCurrent();
+ SDImageAPNGCoder *coder2 = SDImageAPNGCoder.sharedCoder;
+ UIImage *imageWithLazyDecoding = [coder2 decodedImageWithData:testImageData options:@{SDImageCoderDecodeFirstFrameOnly : @(YES)}];
+ CFAbsoluteTime end2 = CFAbsoluteTimeGetCurrent();
+ CFAbsoluteTime duration2 = end2 - begin2;
+ expect(imageWithLazyDecoding.sd_isDecoded).beFalsy();
+
+ // lazy decoding need less time (10x)
+ expect(duration2 * 10.0).beLessThan(duration);
+}
- (void)test11ThatAPNGPCoderWorks {
NSURL *APNGURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImageAnimated" withExtension:@"apng"];
@@ -100,7 +162,7 @@
// When GIF metadata does not contains any loop count information (`kCGImagePropertyGIFLoopCount`'s value nil)
// The standard says it should just play once. See: http://www6.uniovi.es/gifanim/gifabout.htm
// This behavior is different from other modern animated image format like APNG/WebP. Which will play infinitely
- NSString * testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestLoopCount" ofType:@"gif"];
+ NSString *testImagePath = [[NSBundle bundleForClass:[self class]] pathForResource:@"TestLoopCount" ofType:@"gif"];
NSData *testImageData = [NSData dataWithContentsOfFile:testImagePath];
UIImage *image = [SDImageGIFCoder.sharedCoder decodedImageWithData:testImageData options:nil];
expect(image.sd_imageLoopCount).equal(1);
@@ -154,22 +216,80 @@
withLocalImageURL:heicURL
supportsEncoding:supportsEncoding
encodingFormat:SDImageFormatHEIC
- isAnimatedImage:isAnimatedImage];
+ isAnimatedImage:isAnimatedImage
+ isVectorImage:NO];
}
}
+- (void)test17ThatPDFWorks {
+ NSURL *pdfURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"pdf"];
+ [self verifyCoder:[SDImageIOCoder sharedCoder]
+ withLocalImageURL:pdfURL
+ supportsEncoding:NO
+ encodingFormat:SDImageFormatUndefined
+ isAnimatedImage:NO
+ isVectorImage:YES];
+}
+
+- (void)test18ThatImageIOAnimatedCoderAbstractClass {
+ SDImageIOAnimatedCoder *coder = [[SDImageIOAnimatedCoder alloc] init];
+ @try {
+ [coder canEncodeToFormat:SDImageFormatPNG];
+ XCTFail("Should throw exception");
+ } @catch (NSException *exception) {
+ expect(exception);
+ }
+}
+
+- (void)test19ThatEmbedThumbnailHEICWorks {
+ if (@available(iOS 11, macOS 10.13, *)) {
+ // The input HEIC does not contains any embed thumbnail
+ NSURL *heicURL = [[NSBundle bundleForClass:[self class]] URLForResource:@"TestImage" withExtension:@"heic"];
+ CGImageSourceRef source = CGImageSourceCreateWithURL((__bridge CFURLRef)heicURL, nil);
+ expect(source).notTo.beNil();
+ NSArray *thumbnailImages = [self thumbnailImagesFromImageSource:source];
+ expect(thumbnailImages.count).equal(0);
+
+ CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, 0, nil);
+#if SD_UIKIT
+ UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:1 orientation: UIImageOrientationUp];
+#else
+ UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:1 orientation:kCGImagePropertyOrientationUp];
+#endif
+ CGImageRelease(imageRef);
+ // Encode with embed thumbnail
+ NSData *encodedData = [SDImageIOCoder.sharedCoder encodedDataWithImage:image format:SDImageFormatHEIC options:@{SDImageCoderEncodeEmbedThumbnail : @(YES)}];
+
+ // The new HEIC contains one embed thumbnail
+ CGImageSourceRef source2 = CGImageSourceCreateWithData((__bridge CFDataRef)encodedData, nil);
+ expect(source2).notTo.beNil();
+ NSArray *thumbnailImages2 = [self thumbnailImagesFromImageSource:source2];
+ expect(thumbnailImages2.count).equal(1);
+
+ // Currently ImageIO has no control to custom embed thumbnail pixel size, just check the behavior :)
+ NSDictionary *thumbnailImageInfo = thumbnailImages2.firstObject;
+ NSUInteger thumbnailWidth = [thumbnailImageInfo[(__bridge NSString *)kCGImagePropertyWidth] unsignedIntegerValue];
+ NSUInteger thumbnailHeight = [thumbnailImageInfo[(__bridge NSString *)kCGImagePropertyHeight] unsignedIntegerValue];
+ expect(thumbnailWidth).equal(320);
+ expect(thumbnailHeight).equal(212);
+ }
+}
+
+#pragma mark - Utils
+
- (void)verifyCoder:(id)coder
withLocalImageURL:(NSURL *)imageUrl
supportsEncoding:(BOOL)supportsEncoding
isAnimatedImage:(BOOL)isAnimated {
- [self verifyCoder:coder withLocalImageURL:imageUrl supportsEncoding:supportsEncoding encodingFormat:SDImageFormatUndefined isAnimatedImage:isAnimated];
+ [self verifyCoder:coder withLocalImageURL:imageUrl supportsEncoding:supportsEncoding encodingFormat:SDImageFormatUndefined isAnimatedImage:isAnimated isVectorImage:NO];
}
- (void)verifyCoder:(id)coder
withLocalImageURL:(NSURL *)imageUrl
supportsEncoding:(BOOL)supportsEncoding
encodingFormat:(SDImageFormat)encodingFormat
- isAnimatedImage:(BOOL)isAnimated {
+ isAnimatedImage:(BOOL)isAnimated
+ isVectorImage:(BOOL)isVector {
NSData *inputImageData = [NSData dataWithContentsOfURL:imageUrl];
expect(inputImageData).toNot.beNil();
SDImageFormat inputImageFormat = [NSData sd_imageFormatForImageData:inputImageData];
@@ -197,14 +317,58 @@ withLocalImageURL:(NSURL *)imageUrl
#endif
}
+ // 3 - check thumbnail decoding
+ CGFloat pixelWidth = inputImage.size.width;
+ CGFloat pixelHeight = inputImage.size.height;
+ expect(pixelWidth).beGreaterThan(0);
+ expect(pixelHeight).beGreaterThan(0);
+ // check vector format supports thumbnail with screen size
+ if (isVector) {
+#if SD_UIKIT
+ CGFloat maxScreenSize = MAX(UIScreen.mainScreen.bounds.size.width, UIScreen.mainScreen.bounds.size.height);
+#else
+ CGFloat maxScreenSize = MAX(NSScreen.mainScreen.frame.size.width, NSScreen.mainScreen.frame.size.height);
+#endif
+ expect(pixelWidth).equal(maxScreenSize);
+ expect(pixelHeight).equal(maxScreenSize);
+ }
+
+ // check thumbnail with scratch
+ CGFloat thumbnailWidth = 50;
+ CGFloat thumbnailHeight = 50;
+ UIImage *thumbImage = [coder decodedImageWithData:inputImageData options:@{
+ SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(thumbnailWidth, thumbnailHeight)),
+ SDImageCoderDecodePreserveAspectRatio : @(NO)
+ }];
+ expect(thumbImage).toNot.beNil();
+ expect(thumbImage.size).equal(CGSizeMake(thumbnailWidth, thumbnailHeight));
+ // check thumbnail with aspect ratio limit
+ thumbImage = [coder decodedImageWithData:inputImageData options:@{
+ SDImageCoderDecodeThumbnailPixelSize : @(CGSizeMake(thumbnailWidth, thumbnailHeight)),
+ SDImageCoderDecodePreserveAspectRatio : @(YES)
+ }];
+ expect(thumbImage).toNot.beNil();
+ CGFloat ratio = pixelWidth / pixelHeight;
+ CGFloat thumbnailRatio = thumbnailWidth / thumbnailHeight;
+ CGSize thumbnailPixelSize;
+ if (ratio > thumbnailRatio) {
+ thumbnailPixelSize = CGSizeMake(thumbnailWidth, round(thumbnailWidth / ratio));
+ } else {
+ thumbnailPixelSize = CGSizeMake(round(thumbnailHeight * ratio), thumbnailHeight);
+ }
+ // Image/IO's thumbnail API does not always use round to preserve precision, we check ABS <= 1
+ expect(ABS(thumbImage.size.width - thumbnailPixelSize.width)).beLessThanOrEqualTo(1);
+ expect(ABS(thumbImage.size.height - thumbnailPixelSize.height)).beLessThanOrEqualTo(1);
+
+
if (supportsEncoding) {
- // 3 - check if we can encode to the original format
+ // 4 - check if we can encode to the original format
if (encodingFormat == SDImageFormatUndefined) {
encodingFormat = inputImageFormat;
}
expect([coder canEncodeToFormat:encodingFormat]).to.beTruthy();
- // 4 - encode from UIImage to NSData using the inputImageFormat and check it
+ // 5 - encode from UIImage to NSData using the inputImageFormat and check it
NSData *outputImageData = [coder encodedDataWithImage:inputImage format:encodingFormat options:nil];
expect(outputImageData).toNot.beNil();
UIImage *outputImage = [coder decodedImageWithData:outputImageData options:nil];
@@ -213,17 +377,36 @@ withLocalImageURL:(NSURL *)imageUrl
#if SD_UIKIT
expect(outputImage.images.count).to.equal(inputImage.images.count);
#endif
+
+ // check max pixel size encoding with scratch
+ CGFloat maxWidth = 50;
+ CGFloat maxHeight = 50;
+ CGFloat maxRatio = maxWidth / maxHeight;
+ CGSize maxPixelSize;
+ if (ratio > maxRatio) {
+ maxPixelSize = CGSizeMake(maxWidth, round(maxWidth / ratio));
+ } else {
+ maxPixelSize = CGSizeMake(round(maxHeight * ratio), maxHeight);
+ }
+ NSData *outputMaxImageData = [coder encodedDataWithImage:inputImage format:encodingFormat options:@{SDImageCoderEncodeMaxPixelSize : @(CGSizeMake(maxWidth, maxHeight))}];
+ UIImage *outputMaxImage = [coder decodedImageWithData:outputMaxImageData options:nil];
+ // Image/IO's thumbnail API does not always use round to preserve precision, we check ABS <= 1
+ expect(ABS(outputMaxImage.size.width - maxPixelSize.width)).beLessThanOrEqualTo(1);
+ expect(ABS(outputMaxImage.size.height - maxPixelSize.height)).beLessThanOrEqualTo(1);
+#if SD_UIKIT
+ expect(outputMaxImage.images.count).to.equal(inputImage.images.count);
+#endif
}
}
-- (void)test16ThatImageIOAnimatedCoderAbstractClass {
- SDImageIOAnimatedCoder *coder = [[SDImageIOAnimatedCoder alloc] init];
- @try {
- [coder canEncodeToFormat:SDImageFormatPNG];
- XCTFail("Should throw exception");
- } @catch (NSException *exception) {
- expect(exception);
- }
+- (NSArray *)thumbnailImagesFromImageSource:(CGImageSourceRef)source API_AVAILABLE(ios(11.0), tvos(11.0), macos(13.0)) {
+ NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyProperties(source, nil);
+ NSDictionary *fileProperties = properties[(__bridge NSString *)kCGImagePropertyFileContentsDictionary];
+ NSArray *imagesProperties = fileProperties[(__bridge NSString *)kCGImagePropertyImages];
+ NSDictionary *imageProperties = imagesProperties.firstObject;
+ NSArray *thumbnailImages = imageProperties[(__bridge NSString *)kCGImagePropertyThumbnailImages];
+
+ return thumbnailImages;
}
@end
diff --git a/Tests/Tests/SDImageTransformerTests.m b/Tests/Tests/SDImageTransformerTests.m
index d0105f57..94215e0e 100644
--- a/Tests/Tests/SDImageTransformerTests.m
+++ b/Tests/Tests/SDImageTransformerTests.m
@@ -8,12 +8,13 @@
*/
#import "SDTestCase.h"
-#import "UIColor+HexString.h"
+#import "UIColor+SDHexString.h"
#import
@interface SDImageTransformerTests : SDTestCase
-@property (nonatomic, strong) UIImage *testImage;
+@property (nonatomic, strong) UIImage *testImageCG;
+@property (nonatomic, strong) UIImage *testImageCI;
@end
@@ -22,21 +23,37 @@
#pragma mark - UIImage+Transform
// UIImage+Transform test is hard to write because it's more about visual effect. Current it's tied to the `TestImage.png`, please keep that image or write new test with new image
-- (void)test01UIImageTransformResize {
+- (void)test01UIImageTransformResizeCG {
+ [self test01UIImageTransformResizeWithImage:self.testImageCG];
+}
+
+- (void)test01UIImageTransformResizeCI {
+ [self test01UIImageTransformResizeWithImage:self.testImageCI];
+}
+
+- (void)test01UIImageTransformResizeWithImage:(UIImage *)testImage {
CGSize scaleDownSize = CGSizeMake(200, 100);
- UIImage *scaledDownImage = [self.testImage sd_resizedImageWithSize:scaleDownSize scaleMode:SDImageScaleModeFill];
+ UIImage *scaledDownImage = [testImage sd_resizedImageWithSize:scaleDownSize scaleMode:SDImageScaleModeFill];
expect(CGSizeEqualToSize(scaledDownImage.size, scaleDownSize)).beTruthy();
CGSize scaleUpSize = CGSizeMake(2000, 1000);
- UIImage *scaledUpImage = [self.testImage sd_resizedImageWithSize:scaleUpSize scaleMode:SDImageScaleModeAspectFit];
+ UIImage *scaledUpImage = [testImage sd_resizedImageWithSize:scaleUpSize scaleMode:SDImageScaleModeAspectFit];
expect(CGSizeEqualToSize(scaledUpImage.size, scaleUpSize)).beTruthy();
// Check image not inversion
UIColor *topCenterColor = [scaledUpImage sd_colorAtPoint:CGPointMake(1000, 50)];
expect([topCenterColor.sd_hexString isEqualToString:[UIColor blackColor].sd_hexString]).beTruthy();
}
-- (void)test02UIImageTransformCrop {
+- (void)test02UIImageTransformCropCG {
+ [self test02UIImageTransformCropWithImage:self.testImageCG];
+}
+
+- (void)test02UIImageTransformCropCI {
+ [self test02UIImageTransformCropWithImage:self.testImageCI];
+}
+
+- (void)test02UIImageTransformCropWithImage:(UIImage *)testImage {
CGRect rect = CGRectMake(50, 10, 200, 200);
- UIImage *croppedImage = [self.testImage sd_croppedImageWithRect:rect];
+ UIImage *croppedImage = [testImage sd_croppedImageWithRect:rect];
expect(CGSizeEqualToSize(croppedImage.size, CGSizeMake(200, 200))).beTruthy();
UIColor *startColor = [croppedImage sd_colorAtPoint:CGPointZero];
expect([startColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy();
@@ -45,7 +62,15 @@
expect([topCenterColor.sd_hexString isEqualToString:[UIColor blackColor].sd_hexString]).beTruthy();
}
-- (void)test03UIImageTransformRoundedCorner {
+- (void)test03UIImageTransformRoundedCornerCG {
+ [self test03UIImageTransformRoundedCornerWithImage:self.testImageCG];
+}
+
+- (void)test03UIImageTransformRoundedCornerCI {
+ [self test03UIImageTransformRoundedCornerWithImage:self.testImageCI];
+}
+
+- (void)test03UIImageTransformRoundedCornerWithImage:(UIImage *)testImage {
CGFloat radius = 50;
#if SD_UIKIT
SDRectCorner corners = UIRectCornerAllCorners;
@@ -54,7 +79,7 @@
#endif
CGFloat borderWidth = 1;
UIColor *borderColor = [UIColor blackColor];
- UIImage *roundedCornerImage = [self.testImage sd_roundedCornerImageWithRadius:radius corners:corners borderWidth:borderWidth borderColor:borderColor];
+ UIImage *roundedCornerImage = [testImage sd_roundedCornerImageWithRadius:radius corners:corners borderWidth:borderWidth borderColor:borderColor];
expect(CGSizeEqualToSize(roundedCornerImage.size, CGSizeMake(300, 300))).beTruthy();
UIColor *startColor = [roundedCornerImage sd_colorAtPoint:CGPointZero];
expect([startColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]).beTruthy();
@@ -66,25 +91,42 @@
expect([topCenterColor.sd_hexString isEqualToString:[UIColor blackColor].sd_hexString]).beTruthy();
}
-- (void)test04UIImageTransformRotate {
+- (void)test04UIImageTransformRotateCG {
+ [self test04UIImageTransformRotateWithImage:self.testImageCG];
+}
+
+- (void)test04UIImageTransformRotateCI {
+ [self test04UIImageTransformRotateWithImage:self.testImageCI];
+}
+
+- (void)test04UIImageTransformRotateWithImage:(UIImage *)testImage {
CGFloat angle = M_PI_4;
- UIImage *rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:NO];
+ UIImage *rotatedImage = [testImage sd_rotatedImageWithAngle:angle fitSize:NO];
// Not fit size and no change
- expect(CGSizeEqualToSize(rotatedImage.size, self.testImage.size)).beTruthy();
+ expect(CGSizeEqualToSize(rotatedImage.size, testImage.size)).beTruthy();
// Fit size, may change size
- rotatedImage = [self.testImage sd_rotatedImageWithAngle:angle fitSize:YES];
- CGSize rotatedSize = CGSizeMake(floor(300 * 1.414), floor(300 * 1.414)); // 45º, square length * sqrt(2)
- expect(CGSizeEqualToSize(rotatedImage.size, rotatedSize)).beTruthy();
+ rotatedImage = [testImage sd_rotatedImageWithAngle:angle fitSize:YES];
+ CGSize rotatedSize = CGSizeMake(ceil(300 * 1.414), ceil(300 * 1.414)); // 45º, square length * sqrt(2)
+ expect(rotatedImage.size.width - rotatedSize.width <= 1).beTruthy();
+ expect(rotatedImage.size.height - rotatedSize.height <= 1).beTruthy();
// Check image not inversion
UIColor *leftCenterColor = [rotatedImage sd_colorAtPoint:CGPointMake(60, 175)];
expect([leftCenterColor.sd_hexString isEqualToString:[UIColor blackColor].sd_hexString]).beTruthy();
}
-- (void)test05UIImageTransformFlip {
+- (void)test05UIImageTransformFlipCG {
+ [self test05UIImageTransformFlipWithImage:self.testImageCG];
+}
+
+- (void)test05UIImageTransformFlipCI {
+ [self test05UIImageTransformFlipWithImage:self.testImageCI];
+}
+
+- (void)test05UIImageTransformFlipWithImage:(UIImage *)testImage {
BOOL horizontal = YES;
BOOL vertical = YES;
- UIImage *flippedImage = [self.testImage sd_flippedImageWithHorizontal:horizontal vertical:vertical];
- expect(CGSizeEqualToSize(flippedImage.size, self.testImage.size)).beTruthy();
+ UIImage *flippedImage = [testImage sd_flippedImageWithHorizontal:horizontal vertical:vertical];
+ expect(CGSizeEqualToSize(flippedImage.size, testImage.size)).beTruthy();
// Test pixel colors method here
UIColor *checkColor = [flippedImage sd_colorAtPoint:CGPointMake(75, 75)];
expect(checkColor);
@@ -98,10 +140,18 @@
expect([bottomCenterColor.sd_hexString isEqualToString:[UIColor blackColor].sd_hexString]).beTruthy();
}
-- (void)test06UIImageTransformTint {
+- (void)test06UIImageTransformTintCG {
+ [self test06UIImageTransformTintWithImage:self.testImageCG];
+}
+
+- (void)test06UIImageTransformTintCI {
+ [self test06UIImageTransformTintWithImage:self.testImageCI];
+}
+
+- (void)test06UIImageTransformTintWithImage:(UIImage *)testImage {
UIColor *tintColor = [UIColor blackColor];
- UIImage *tintedImage = [self.testImage sd_tintedImageWithColor:tintColor];
- expect(CGSizeEqualToSize(tintedImage.size, self.testImage.size)).beTruthy();
+ UIImage *tintedImage = [testImage sd_tintedImageWithColor:tintColor];
+ expect(CGSizeEqualToSize(tintedImage.size, testImage.size)).beTruthy();
// Check center color, should keep clear
UIColor *centerColor = [tintedImage sd_colorAtPoint:CGPointMake(150, 150)];
expect([centerColor.sd_hexString isEqualToString:[UIColor clearColor].sd_hexString]);
@@ -113,10 +163,18 @@
expect([topCenterColor.sd_hexString isEqualToString:[UIColor blackColor].sd_hexString]).beTruthy();
}
-- (void)test07UIImageTransformBlur {
- CGFloat radius = 50;
- UIImage *blurredImage = [self.testImage sd_blurredImageWithRadius:radius];
- expect(CGSizeEqualToSize(blurredImage.size, self.testImage.size)).beTruthy();
+- (void)test07UIImageTransformBlurCG {
+ [self test07UIImageTransformBlurWithImage:self.testImageCG];
+}
+
+- (void)test07UIImageTransformBlurCI {
+ [self test07UIImageTransformBlurWithImage:self.testImageCI];
+}
+
+- (void)test07UIImageTransformBlurWithImage:(UIImage *)testImage {
+ CGFloat radius = 25;
+ UIImage *blurredImage = [testImage sd_blurredImageWithRadius:radius];
+ expect(CGSizeEqualToSize(blurredImage.size, testImage.size)).beTruthy();
// Check left color, should be blurred
UIColor *leftColor = [blurredImage sd_colorAtPoint:CGPointMake(80, 150)];
// Hard-code from the output
@@ -124,14 +182,23 @@
expect([leftColor.sd_hexString isEqualToString:expectedColor.sd_hexString]);
// Check rounded corner operation not inversion the image
UIColor *topCenterColor = [blurredImage sd_colorAtPoint:CGPointMake(150, 20)];
- expect([topCenterColor.sd_hexString isEqualToString:@"#9a430d06"]).beTruthy();
+ UIColor *bottomCenterColor = [blurredImage sd_colorAtPoint:CGPointMake(150, 280)];
+ expect([topCenterColor.sd_hexString isEqualToString:bottomCenterColor.sd_hexString]).beFalsy();
}
-- (void)test08UIImageTransformFilter {
+- (void)test08UIImageTransformFilterCG {
+ [self test08UIImageTransformFilterWithImage:self.testImageCG];
+}
+
+- (void)test08UIImageTransformFilterCI {
+ [self test08UIImageTransformFilterWithImage:self.testImageCI];
+}
+
+- (void)test08UIImageTransformFilterWithImage:(UIImage *)testImage {
// Invert color filter
CIFilter *filter = [CIFilter filterWithName:@"CIColorInvert"];
- UIImage *filteredImage = [self.testImage sd_filteredImageWithFilter:filter];
- expect(CGSizeEqualToSize(filteredImage.size, self.testImage.size)).beTruthy();
+ UIImage *filteredImage = [testImage sd_filteredImageWithFilter:filter];
+ expect(CGSizeEqualToSize(filteredImage.size, testImage.size)).beTruthy();
// Check left color, should be inverted
UIColor *leftColor = [filteredImage sd_colorAtPoint:CGPointMake(80, 150)];
// Hard-code from the output
@@ -198,7 +265,7 @@
NSString *transformerKey = [transformerKeys componentsJoinedByString:@"-"]; // SDImageTransformerKeySeparator
expect([pipelineTransformer.transformerKey isEqualToString:transformerKey]).beTruthy();
- UIImage *transformedImage = [pipelineTransformer transformedImageWithImage:self.testImage forKey:@"Test"];
+ UIImage *transformedImage = [pipelineTransformer transformedImageWithImage:self.testImageCG forKey:@"Test"];
expect(transformedImage).notTo.beNil();
expect(CGSizeEqualToSize(transformedImage.size, cropRect.size)).beTruthy();
}
@@ -239,6 +306,8 @@
expect(SDTransformedKeyForKey(key, transformerKey)).equal(@"ftp://root:password@foo.com/image-SDImageFlippingTransformer(1,0).png");
}
+#pragma mark - Coder Helper
+
- (void)test20CGImageCreateDecodedWithOrientation {
// Test EXIF orientation tag, you can open this image with `Preview.app`, open inspector (Command+I) and rotate (Command+L/R) to check
UIImage *image = [[UIImage alloc] initWithContentsOfFile:[self testPNGPathForName:@"TestEXIF"]];
@@ -331,11 +400,23 @@
#pragma mark - Helper
-- (UIImage *)testImage {
- if (!_testImage) {
- _testImage = [[UIImage alloc] initWithContentsOfFile:[self testPNGPathForName:@"TestImage"]];
+- (UIImage *)testImageCG {
+ if (!_testImageCG) {
+ _testImageCG = [[UIImage alloc] initWithContentsOfFile:[self testPNGPathForName:@"TestImage"]];
}
- return _testImage;
+ return _testImageCG;
+}
+
+- (UIImage *)testImageCI {
+ if (!_testImageCI) {
+ CIImage *ciImage = [[CIImage alloc] initWithContentsOfURL:[NSURL fileURLWithPath:[self testPNGPathForName:@"TestImage"]]];
+#if SD_UIKIT
+ _testImageCI = [[UIImage alloc] initWithCIImage:ciImage scale:1 orientation:UIImageOrientationUp];
+#else
+ _testImageCI = [[UIImage alloc] initWithCIImage:ciImage scale:1 orientation:kCGImagePropertyOrientationUp];
+#endif
+ }
+ return _testImageCI;
}
- (NSString *)testPNGPathForName:(NSString *)name {
diff --git a/Tests/Tests/SDUtilsTests.m b/Tests/Tests/SDUtilsTests.m
index e8b8d471..5b59c74c 100644
--- a/Tests/Tests/SDUtilsTests.m
+++ b/Tests/Tests/SDUtilsTests.m
@@ -11,6 +11,8 @@
#import "SDWeakProxy.h"
#import "SDDisplayLink.h"
#import "SDInternalMacros.h"
+#import "SDFileAttributeHelper.h"
+#import "UIColor+SDHexString.h"
@interface SDUtilsTests : SDTestCase
@@ -74,6 +76,64 @@
expect(duration).beLessThan(0.02);
}
+- (void)testSDFileAttributeHelper {
+ NSData *fileData = [@"File Data" dataUsingEncoding:NSUTF8StringEncoding];
+ NSData *extendedData = [@"Extended Data" dataUsingEncoding:NSUTF8StringEncoding];
+ NSString *filePath = @"/tmp/file.dat";
+ [NSFileManager.defaultManager removeItemAtPath:filePath error:nil];
+ [fileData writeToFile:filePath atomically:YES];
+ BOOL exist = [NSFileManager.defaultManager fileExistsAtPath:filePath];
+ expect(exist).beTruthy();
+
+ NSArray *names = [SDFileAttributeHelper extendedAttributeNamesAtPath:filePath traverseLink:NO error:nil];
+ expect(names.count).equal(0);
+
+ NSString *attr = @"com.hackemist.test";
+ [SDFileAttributeHelper setExtendedAttribute:attr value:extendedData atPath:filePath traverseLink:NO overwrite:YES error:nil];
+
+ BOOL hasAttr =[SDFileAttributeHelper hasExtendedAttribute:attr atPath:filePath traverseLink:NO error:nil];
+ expect(hasAttr).beTruthy();
+
+ names = [SDFileAttributeHelper extendedAttributeNamesAtPath:filePath traverseLink:NO error:nil];
+ expect(names.count).equal(1);
+ expect(names.firstObject).equal(attr);
+
+ NSData *queriedData = [SDFileAttributeHelper extendedAttribute:attr atPath:filePath traverseLink:NO error:nil];
+ expect(extendedData).equal(queriedData);
+
+ BOOL removed = [SDFileAttributeHelper removeExtendedAttribute:attr atPath:filePath traverseLink:NO error:nil];
+ expect(removed).beTruthy();
+
+ hasAttr = [SDFileAttributeHelper hasExtendedAttribute:attr atPath:filePath traverseLink:NO error:nil];
+ expect(hasAttr).beFalsy();
+}
+
+- (void)testSDGraphicsImageRenderer {
+ // Main Screen
+ SDGraphicsImageRendererFormat *format = SDGraphicsImageRendererFormat.preferredFormat;
+#if SD_UIKIT
+ CGFloat screenScale = [UIScreen mainScreen].scale;
+#elif SD_MAC
+ CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor;
+#endif
+ expect(format.scale).equal(screenScale);
+ expect(format.opaque).beFalsy();
+#if SD_UIKIT
+ expect(format.preferredRange).equal(SDGraphicsImageRendererFormatRangeAutomatic);
+#elif SD_MAC
+ expect(format.preferredRange).equal(SDGraphicsImageRendererFormatRangeStandard);
+#endif
+ CGSize size = CGSizeMake(100, 100);
+ SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
+ UIColor *color = UIColor.redColor;
+ UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
+ [color setFill];
+ CGContextFillRect(context, CGRectMake(0, 0, size.width, size.height));
+ }];
+ expect(image.scale).equal(format.scale);
+ expect([[image sd_colorAtPoint:CGPointMake(50, 50)].sd_hexString isEqualToString:color.sd_hexString]).beTruthy();
+}
+
- (void)testSDScaledImageForKey {
// Test nil
expect(SDScaledImageForKey(nil, nil)).beNil();
diff --git a/Tests/Tests/SDWebCacheCategoriesTests.m b/Tests/Tests/SDWebCacheCategoriesTests.m
index e91c5ece..96fc7a3e 100644
--- a/Tests/Tests/SDWebCacheCategoriesTests.m
+++ b/Tests/Tests/SDWebCacheCategoriesTests.m
@@ -350,6 +350,32 @@
[self waitForExpectationsWithCommonTimeout];
}
+- (void)testUIViewOperationKeyContextWorks {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"UIView operation key context should pass through"];
+
+ UIView *view = [[UIView alloc] init];
+ NSURL *originalImageURL = [NSURL URLWithString:kTestJPEGURL];
+ SDWebImageManager *customManager = [[SDWebImageManager alloc] initWithCache:SDImageCachesManager.sharedManager loader:SDImageLoadersManager.sharedManager];
+ customManager.optionsProcessor = [SDWebImageOptionsProcessor optionsProcessorWithBlock:^SDWebImageOptionsResult * _Nullable(NSURL * _Nullable url, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
+ // expect manager does not exist, avoid retain cycle
+ expect(context[SDWebImageContextCustomManager]).beNil();
+ // expect operation key to be the image view class
+ expect(context[SDWebImageContextSetImageOperationKey]).equal(NSStringFromClass(view.class));
+ return [[SDWebImageOptionsResult alloc] initWithOptions:options context:context];
+ }];
+ [view sd_internalSetImageWithURL:originalImageURL
+ placeholderImage:nil
+ options:0
+ context:@{SDWebImageContextCustomManager: customManager}
+ setImageBlock:nil
+ progress:nil
+ completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
+ [expectation fulfill];
+ }];
+
+ [self waitForExpectationsWithCommonTimeout];
+}
+
#pragma mark - Helper
- (UIWindow *)window {
if (!_window) {
diff --git a/Tests/Tests/SDWebImageDownloaderTests.m b/Tests/Tests/SDWebImageDownloaderTests.m
index e5f0a2b5..4cd877c1 100644
--- a/Tests/Tests/SDWebImageDownloaderTests.m
+++ b/Tests/Tests/SDWebImageDownloaderTests.m
@@ -187,10 +187,13 @@
- (void)test11ThatCancelAllDownloadWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"CancelAllDownloads"];
+ // Previous test case download may not finished, so we just check the download count should + 1 after new request
+ NSUInteger currentDownloadCount = [SDWebImageDownloader sharedDownloader].currentDownloadCount;
- NSURL *imageURL = [NSURL URLWithString:@"http://via.placeholder.com/1100x1100.png"];
+ // Choose a large image to avoid download too fast
+ NSURL *imageURL = [NSURL URLWithString:@"https://www.sample-videos.com/img/Sample-png-image-1mb.png"];
[[SDWebImageDownloader sharedDownloader] downloadImageWithURL:imageURL completed:nil];
- expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(1);
+ expect([SDWebImageDownloader sharedDownloader].currentDownloadCount).to.equal(currentDownloadCount + 1);
[[SDWebImageDownloader sharedDownloader] cancelAllDownloads];
@@ -471,7 +474,6 @@
[self waitForExpectationsWithCommonTimeout];
}
-#if SD_UIKIT
- (void)test22ThatCustomDecoderWorksForImageDownload {
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom decoder for SDWebImageDownloader not works"];
SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init];
@@ -484,8 +486,8 @@
UIImage *testJPEGImage = [[UIImage alloc] initWithContentsOfFile:testJPEGImagePath];
[downloader downloadImageWithURL:testImageURL options:0 progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
- NSData *data1 = UIImagePNGRepresentation(testJPEGImage);
- NSData *data2 = UIImagePNGRepresentation(image);
+ NSData *data1 = [testJPEGImage sd_imageDataAsFormat:SDImageFormatPNG];
+ NSData *data2 = [image sd_imageDataAsFormat:SDImageFormatPNG];
if (![data1 isEqualToData:data2]) {
XCTFail(@"The image data is not equal to cutom decoder, check -[SDWebImageTestDecoder decodedImageWithData:]");
}
@@ -496,7 +498,6 @@
[self waitForExpectationsWithCommonTimeout];
[downloader invalidateSessionAndCancel:YES];
}
-#endif
- (void)test23ThatDownloadRequestModifierWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"Download request modifier not works"];
@@ -642,6 +643,37 @@
}];
}
+- (void)test26DownloadURLSessionMetrics {
+ XCTestExpectation *expectation1 = [self expectationWithDescription:@"Download URLSessionMetrics works"];
+
+ SDWebImageDownloader *downloader = [[SDWebImageDownloader alloc] init];
+
+ __block SDWebImageDownloadToken *token;
+ token = [downloader downloadImageWithURL:[NSURL URLWithString:kTestJPEGURL] completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished) {
+ expect(error).beNil();
+ if (@available(iOS 10.0, tvOS 10.0, macOS 10.12, *)) {
+ NSURLSessionTaskMetrics *metrics = token.metrics;
+ expect(metrics).notTo.beNil();
+ expect(metrics.redirectCount).equal(0);
+ expect(metrics.transactionMetrics.count).equal(1);
+ NSURLSessionTaskTransactionMetrics *metric = metrics.transactionMetrics.firstObject;
+ // Metrcis Test
+ expect(metric.fetchStartDate).notTo.beNil();
+ expect(metric.connectStartDate).notTo.beNil();
+ expect(metric.connectEndDate).notTo.beNil();
+ expect(metric.networkProtocolName).equal(@"http/1.1");
+ expect(metric.resourceFetchType).equal(NSURLSessionTaskMetricsResourceFetchTypeNetworkLoad);
+ expect(metric.isProxyConnection).beFalsy();
+ expect(metric.isReusedConnection).beFalsy();
+ }
+ [expectation1 fulfill];
+ }];
+
+ [self waitForExpectationsWithCommonTimeoutUsingHandler:^(NSError * _Nullable error) {
+ [downloader invalidateSessionAndCancel:YES];
+ }];
+}
+
#pragma mark - SDWebImageLoader
- (void)test30CustomImageLoaderWorks {
XCTestExpectation *expectation = [self expectationWithDescription:@"Custom image not works"];
diff --git a/Tests/Tests/SDWebImageManagerTests.m b/Tests/Tests/SDWebImageManagerTests.m
index 143dc504..fc1a194a 100644
--- a/Tests/Tests/SDWebImageManagerTests.m
+++ b/Tests/Tests/SDWebImageManagerTests.m
@@ -8,6 +8,8 @@
#import "SDTestCase.h"
#import "SDWebImageTestTransformer.h"
+#import "SDWebImageTestCache.h"
+#import "SDWebImageTestLoader.h"
@interface SDWebImageManagerTests : SDTestCase
@@ -119,7 +121,7 @@
SDWebImageManager *manager = [[SDWebImageManager alloc] initWithCache:[SDImageCache sharedImageCache] loader:[SDWebImageDownloader sharedDownloader]];
manager.transformer = transformer;
[[SDImageCache sharedImageCache] removeImageForKey:kTestJPEGURL withCompletion:^{
- [manager loadImageWithURL:url options:SDWebImageTransformAnimatedImage progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
+ [manager loadImageWithURL:url options:SDWebImageTransformAnimatedImage | SDWebImageTransformVectorImage progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
expect(image).equal(transformer.testImage);
[expectation fulfill];
}];
@@ -218,6 +220,7 @@
// Use a fresh manager && cache to avoid get effected by other test cases
SDImageCache *cache = [[SDImageCache alloc] initWithNamespace:@"SDWebImageStoreCacheType"];
+ [cache clearDiskOnCompletion:nil];
SDWebImageManager *manager = [[SDWebImageManager alloc] initWithCache:cache loader:SDWebImageDownloader.sharedDownloader];
SDWebImageTestTransformer *transformer = [[SDWebImageTestTransformer alloc] init];
transformer.testImage = [[UIImage alloc] initWithContentsOfFile:[self testJPEGPath]];
@@ -225,21 +228,30 @@
// test: original image -> disk only, transformed image -> memory only
SDWebImageContext *context = @{SDWebImageContextOriginalStoreCacheType : @(SDImageCacheTypeDisk), SDWebImageContextStoreCacheType : @(SDImageCacheTypeMemory)};
- NSURL *url = [NSURL URLWithString:kTestJPEGURL];
+ NSURL *url = [NSURL URLWithString:kTestAPNGPURL];
NSString *originalKey = [manager cacheKeyForURL:url];
- NSString *transformedKey = SDTransformedKeyForKey(originalKey, transformer.transformerKey);
+ NSString *transformedKey = [manager cacheKeyForURL:url context:context];
[manager loadImageWithURL:url options:SDWebImageTransformAnimatedImage context:context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
expect(image).equal(transformer.testImage);
+ // the transformed image should not inherite any attribute from original one
+ expect(image.sd_imageFormat).equal(SDImageFormatJPEG);
+ expect(image.sd_isAnimated).beFalsy();
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 2*kMinDelayNanosecond), dispatch_get_main_queue(), ^{
// original -> disk only
- [manager.imageCache containsImageForKey:originalKey cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType originalCacheType) {
- expect(originalCacheType).equal(SDImageCacheTypeDisk);
- // transformed -> memory only
- [manager.imageCache containsImageForKey:transformedKey cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType transformedCacheType) {
- expect(transformedCacheType).equal(SDImageCacheTypeMemory);
- [expectation fulfill];
- }];
+ UIImage *originalImage = [cache imageFromMemoryCacheForKey:originalKey];
+ expect(originalImage).beNil();
+ NSData *originalData = [cache diskImageDataForKey:originalKey];
+ expect(originalData).notTo.beNil();
+ originalImage = [UIImage sd_imageWithData:originalData];
+ expect(originalImage).notTo.beNil();
+ expect(originalImage.sd_imageFormat).equal(SDImageFormatPNG);
+ expect(originalImage.sd_isAnimated).beTruthy();
+ // transformed -> memory only
+ [manager.imageCache containsImageForKey:transformedKey cacheType:SDImageCacheTypeAll completion:^(SDImageCacheType transformedCacheType) {
+ expect(transformedCacheType).equal(SDImageCacheTypeMemory);
+ [cache clearDiskOnCompletion:nil];
+ [expectation fulfill];
}];
});
}];
@@ -247,6 +259,74 @@
[self waitForExpectationsWithCommonTimeout];
}
+- (void)test13ThatScaleDownLargeImageUseThumbnailDecoding {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"SDWebImageScaleDownLargeImages should translate to thumbnail decoding"];
+ NSURL *originalImageURL = [NSURL URLWithString:@"http://via.placeholder.com/3999x3999.png"]; // Max size for this API
+ NSUInteger defaultLimitBytes = SDImageCoderHelper.defaultScaleDownLimitBytes;
+ SDImageCoderHelper.defaultScaleDownLimitBytes = 1000 * 1000 * 4; // Limit 1000x1000 pixel
+ // From v5.5.0, the `SDWebImageScaleDownLargeImages` translate to `SDWebImageContextImageThumbnailPixelSize`, and works for progressive loading
+ [SDWebImageManager.sharedManager loadImageWithURL:originalImageURL options:SDWebImageScaleDownLargeImages | SDWebImageProgressiveLoad progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
+ expect(image).notTo.beNil();
+ expect(image.size).equal(CGSizeMake(1000, 1000));
+ if (finished) {
+ [expectation fulfill];
+ } else {
+ expect(image.sd_isIncremental).beTruthy();
+ }
+ }];
+
+ [self waitForExpectationsWithCommonTimeoutUsingHandler:^(NSError * _Nullable error) {
+ SDImageCoderHelper.defaultScaleDownLimitBytes = defaultLimitBytes;
+ }];
+}
+
+- (void)test14ThatCustomCacheAndLoaderWorks {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Custom Cache and Loader during manger query"];
+ NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/100x100.png"];
+ SDWebImageContext *context = @{
+ SDWebImageContextImageCache : SDWebImageTestCache.sharedCache,
+ SDWebImageContextImageLoader : SDWebImageTestLoader.sharedLoader
+ };
+ [SDWebImageTestCache.sharedCache clearWithCacheType:SDImageCacheTypeAll completion:nil];
+ [SDWebImageManager.sharedManager loadImageWithURL:url options:SDWebImageWaitStoreCache context:context progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
+ expect(image).notTo.beNil();
+ expect(image.size.width).equal(100);
+ expect(image.size.height).equal(100);
+ expect(data).notTo.beNil();
+ NSString *cacheKey = [SDWebImageManager.sharedManager cacheKeyForURL:imageURL];
+ // Check Disk Cache (SDWebImageWaitStoreCache behavior)
+ [SDWebImageTestCache.sharedCache containsImageForKey:cacheKey cacheType:SDImageCacheTypeDisk completion:^(SDImageCacheType containsCacheType) {
+ expect(containsCacheType).equal(SDImageCacheTypeDisk);
+ [expectation fulfill];
+ }];
+ }];
+
+ [self waitForExpectationsWithCommonTimeout];
+}
+
+- (void)test15ThatQueryCacheTypeWork {
+ XCTestExpectation *expectation = [self expectationWithDescription:@"Image query cache type works"];
+ NSURL *url = [NSURL URLWithString:@"http://via.placeholder.com/101x101.png"];
+ NSString *key = [SDWebImageManager.sharedManager cacheKeyForURL:url];
+ NSData *testImageData = [NSData dataWithContentsOfFile:[self testJPEGPath]];
+ [SDImageCache.sharedImageCache storeImageDataToDisk:testImageData forKey:key];
+
+ // Query memory first
+ [SDWebImageManager.sharedManager loadImageWithURL:url options:SDWebImageFromCacheOnly context:@{SDWebImageContextQueryCacheType : @(SDImageCacheTypeMemory)} progress:nil completed:^(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL * _Nullable imageURL) {
+ expect(image).beNil();
+ expect(cacheType).equal(SDImageCacheTypeNone);
+ // Query disk secondly
+ [SDWebImageManager.sharedManager loadImageWithURL:url options:SDWebImageFromCacheOnly context:@{SDWebImageContextQueryCacheType : @(SDImageCacheTypeDisk)} progress:nil completed:^(UIImage * _Nullable image2, NSData * _Nullable data2, NSError * _Nullable error2, SDImageCacheType cacheType2, BOOL finished2, NSURL * _Nullable imageURL2) {
+ expect(image2).notTo.beNil();
+ expect(cacheType2).equal(SDImageCacheTypeDisk);
+ [SDImageCache.sharedImageCache removeImageFromDiskForKey:key];
+ [expectation fulfill];
+ }];
+ }];
+
+ [self waitForExpectationsWithCommonTimeout];
+}
+
- (NSString *)testJPEGPath {
NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
return [testBundle pathForResource:@"TestImage" ofType:@"jpg"];
diff --git a/Tests/Tests/SDWebImageTestCache.h b/Tests/Tests/SDWebImageTestCache.h
index 0736c698..6c900c6f 100644
--- a/Tests/Tests/SDWebImageTestCache.h
+++ b/Tests/Tests/SDWebImageTestCache.h
@@ -9,9 +9,9 @@
#import
#import
+#import
// A really naive implementation of custom memory cache and disk cache
-
@interface SDWebImageTestMemoryCache : NSObject
@property (nonatomic, strong, nonnull) SDImageCacheConfig *config;
@@ -26,3 +26,16 @@
@property (nonatomic, strong, nonnull) NSFileManager *fileManager;
@end
+
+// A really naive implementation of custom image cache using memory cache and disk cache
+@interface SDWebImageTestCache : NSObject
+
+@property (nonatomic, strong, nonnull) SDImageCacheConfig *config;
+@property (nonatomic, strong, nonnull) SDWebImageTestMemoryCache *memoryCache;
+@property (nonatomic, strong, nonnull) SDWebImageTestDiskCache *diskCache;
+
+- (nullable instancetype)initWithCachePath:(nonnull NSString *)cachePath config:(nonnull SDImageCacheConfig *)config;
+
+@property (nonatomic, class, readonly, nonnull) SDWebImageTestCache *sharedCache;
+
+@end
diff --git a/Tests/Tests/SDWebImageTestCache.m b/Tests/Tests/SDWebImageTestCache.m
index 7ef29cf7..762a2d9a 100644
--- a/Tests/Tests/SDWebImageTestCache.m
+++ b/Tests/Tests/SDWebImageTestCache.m
@@ -8,7 +8,10 @@
*/
#import "SDWebImageTestCache.h"
-#import
+#import
+#import "SDFileAttributeHelper.h"
+
+static NSString * const SDWebImageTestDiskCacheExtendedAttributeName = @"com.hackemist.SDWebImageTestDiskCache";
@implementation SDWebImageTestMemoryCache
@@ -46,7 +49,7 @@
@implementation SDWebImageTestDiskCache
- (nullable NSString *)cachePathForKey:(nonnull NSString *)key {
- return [self.cachePath stringByAppendingPathComponent:key];
+ return [self.cachePath stringByAppendingPathComponent:key.lastPathComponent];
}
- (BOOL)containsDataForKey:(nonnull NSString *)key {
@@ -69,7 +72,10 @@
}
- (void)removeAllData {
- [self.fileManager removeItemAtPath:self.cachePath error:nil];
+ for (NSString *path in [self.fileManager subpathsAtPath:self.cachePath]) {
+ NSString *filePath = [self.cachePath stringByAppendingPathComponent:path];
+ [self.fileManager removeItemAtPath:filePath error:nil];
+ }
}
- (void)removeDataForKey:(nonnull NSString *)key {
@@ -104,4 +110,185 @@
return size;
}
+- (nullable NSData *)extendedDataForKey:(nonnull NSString *)key {
+ NSString *cachePathForKey = [self cachePathForKey:key];
+ return [SDFileAttributeHelper extendedAttribute:SDWebImageTestDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
+}
+
+- (void)setExtendedData:(nullable NSData *)extendedData forKey:(nonnull NSString *)key {
+ NSString *cachePathForKey = [self cachePathForKey:key];
+ if (!extendedData) {
+ [SDFileAttributeHelper removeExtendedAttribute:SDWebImageTestDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
+ } else {
+ [SDFileAttributeHelper setExtendedAttribute:SDWebImageTestDiskCacheExtendedAttributeName value:extendedData atPath:cachePathForKey traverseLink:NO overwrite:YES error:nil];
+ }
+}
+
+@end
+
+@implementation SDWebImageTestCache
+
++ (SDWebImageTestCache *)sharedCache {
+ static dispatch_once_t onceToken;
+ static SDWebImageTestCache *cache;
+ dispatch_once(&onceToken, ^{
+ NSString *cachePath = [[self userCacheDirectory] stringByAppendingPathComponent:@"SDWebImageTestCache"];
+ SDImageCacheConfig *config = SDImageCacheConfig.defaultCacheConfig;
+ cache = [[SDWebImageTestCache alloc] initWithCachePath:cachePath config:config];
+ });
+ return cache;
+}
+
+- (instancetype)initWithCachePath:(NSString *)cachePath config:(SDImageCacheConfig *)config {
+ self = [super init];
+ if (self) {
+ self.config = config;
+ self.memoryCache = [[SDWebImageTestMemoryCache alloc] initWithConfig:config];
+ self.diskCache = [[SDWebImageTestDiskCache alloc] initWithCachePath:cachePath config:config];
+ }
+ return self;
+}
+
+- (void)clearWithCacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
+ switch (cacheType) {
+ case SDImageCacheTypeNone:
+ break;
+ case SDImageCacheTypeMemory:
+ [self.memoryCache removeAllObjects];
+ break;
+ case SDImageCacheTypeDisk:
+ [self.diskCache removeAllData];
+ break;
+ case SDImageCacheTypeAll:
+ [self.memoryCache removeAllObjects];
+ [self.diskCache removeAllData];
+ break;
+ default:
+ break;
+ }
+ if (completionBlock) {
+ completionBlock();
+ }
+}
+
+- (void)containsImageForKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheContainsCompletionBlock)completionBlock {
+ SDImageCacheType containsCacheType = SDImageCacheTypeNone;
+ switch (cacheType) {
+ case SDImageCacheTypeNone:
+ break;
+ case SDImageCacheTypeMemory:
+ containsCacheType = [self.memoryCache objectForKey:key] ? SDImageCacheTypeMemory : SDImageCacheTypeNone;
+ break;
+ case SDImageCacheTypeDisk:
+ containsCacheType = [self.diskCache containsDataForKey:key] ? SDImageCacheTypeDisk : SDImageCacheTypeNone;
+ break;
+ case SDImageCacheTypeAll:
+ if ([self.memoryCache objectForKey:key]) {
+ containsCacheType = SDImageCacheTypeMemory;
+ } else if ([self.diskCache containsDataForKey:key]) {
+ containsCacheType = SDImageCacheTypeDisk;
+ }
+ break;
+ default:
+ break;
+ }
+ if (completionBlock) {
+ completionBlock(containsCacheType);
+ }
+}
+
+- (nullable id)queryImageForKey:(nullable NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
+ return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock];
+}
+
+- (nullable id)queryImageForKey:(nullable NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
+ UIImage *image;
+ NSData *data;
+ SDImageCacheType resultCacheType = SDImageCacheTypeNone;
+ switch (cacheType) {
+ case SDImageCacheTypeNone:
+ break;
+ case SDImageCacheTypeMemory:
+ image = [self.memoryCache objectForKey:key];
+ if (image) {
+ resultCacheType = SDImageCacheTypeMemory;
+ }
+ break;
+ case SDImageCacheTypeDisk:
+ data = [self.diskCache dataForKey:key];
+ image = [UIImage sd_imageWithData:data];
+ if (data) {
+ resultCacheType = SDImageCacheTypeDisk;
+ }
+ break;
+ case SDImageCacheTypeAll:
+ image = [self.memoryCache objectForKey:key];
+ if (image) {
+ resultCacheType = SDImageCacheTypeMemory;
+ } else {
+ data = [self.diskCache dataForKey:key];
+ image = [UIImage sd_imageWithData:data];
+ if (data) {
+ resultCacheType = SDImageCacheTypeDisk;
+ }
+ }
+ break;
+ default:
+ break;
+ }
+ if (completionBlock) {
+ completionBlock(image, data, resultCacheType);
+ }
+ return nil;
+}
+
+- (void)removeImageForKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
+ switch (cacheType) {
+ case SDImageCacheTypeNone:
+ break;
+ case SDImageCacheTypeMemory:
+ [self.memoryCache removeObjectForKey:key];
+ break;
+ case SDImageCacheTypeDisk:
+ [self.diskCache removeDataForKey:key];
+ break;
+ case SDImageCacheTypeAll:
+ [self.memoryCache removeObjectForKey:key];
+ [self.diskCache removeDataForKey:key];
+ break;
+ default:
+ break;
+ }
+ if (completionBlock) {
+ completionBlock();
+ }
+}
+
+- (void)storeImage:(nullable UIImage *)image imageData:(nullable NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {
+ switch (cacheType) {
+ case SDImageCacheTypeNone:
+ break;
+ case SDImageCacheTypeMemory:
+ [self.memoryCache setObject:image forKey:key];
+ break;
+ case SDImageCacheTypeDisk:
+ [self.diskCache setData:imageData forKey:key];
+ break;
+ case SDImageCacheTypeAll:
+ [self.memoryCache setObject:image forKey:key];
+ [self.diskCache setData:imageData forKey:key];
+ break;
+ default:
+ break;
+ }
+ if (completionBlock) {
+ completionBlock();
+ }
+}
+
++ (nullable NSString *)userCacheDirectory {
+ NSArray *paths = NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES);
+ return paths.firstObject;
+}
+
@end
diff --git a/Tests/Tests/SDWebImageTestLoader.h b/Tests/Tests/SDWebImageTestLoader.h
index d6a3f5f9..bd343cd8 100644
--- a/Tests/Tests/SDWebImageTestLoader.h
+++ b/Tests/Tests/SDWebImageTestLoader.h
@@ -13,4 +13,6 @@
// A really naive implementation of custom image loader using `NSURLSession`
@interface SDWebImageTestLoader : NSObject
+@property (nonatomic, class, readonly, nonnull) SDWebImageTestLoader *sharedLoader;
+
@end
diff --git a/Tests/Tests/SDWebImageTestLoader.m b/Tests/Tests/SDWebImageTestLoader.m
index 22978edb..14f6f7e5 100644
--- a/Tests/Tests/SDWebImageTestLoader.m
+++ b/Tests/Tests/SDWebImageTestLoader.m
@@ -16,6 +16,15 @@
@implementation SDWebImageTestLoader
++ (SDWebImageTestLoader *)sharedLoader {
+ static dispatch_once_t onceToken;
+ static SDWebImageTestLoader *loader;
+ dispatch_once(&onceToken, ^{
+ loader = [[SDWebImageTestLoader alloc] init];
+ });
+ return loader;
+}
+
- (BOOL)canRequestImageForURL:(NSURL *)url {
return YES;
}
diff --git a/WebImage/Info.plist b/WebImage/Info.plist
index b3d623a4..b311cc23 100644
--- a/WebImage/Info.plist
+++ b/WebImage/Info.plist
@@ -15,11 +15,11 @@
CFBundlePackageType
FMWK
CFBundleShortVersionString
- 5.3.1
+ 5.7.3
CFBundleSignature
????
CFBundleVersion
- 5.3.1
+ 5.7.3
NSPrincipalClass
diff --git a/WebImage/SDWebImage.h b/WebImage/SDWebImage.h
index 6a683cd1..929f1b96 100644
--- a/WebImage/SDWebImage.h
+++ b/WebImage/SDWebImage.h
@@ -9,17 +9,13 @@
#import
-#if SD_UIKIT
-#import
-#endif
+//! Project version number for SDWebImage.
+FOUNDATION_EXPORT double SDWebImageVersionNumber;
-//! Project version number for WebImage.
-FOUNDATION_EXPORT double WebImageVersionNumber;
+//! Project version string for SDWebImage.
+FOUNDATION_EXPORT const unsigned char SDWebImageVersionString[];
-//! Project version string for WebImage.
-FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
-
-// In this header, you should import all the public headers of your framework using statements like #import
+// In this header, you should import all the public headers of your framework using statements like #import
#import
#import
@@ -46,6 +42,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
#import
#import
#import
+#import
#import
#import
#import
@@ -64,6 +61,7 @@ FOUNDATION_EXPORT const unsigned char WebImageVersionString[];
#import
#import
#import
+#import
#import
#import
#import
diff --git a/WebImage/SDWebImageMapKit.h b/WebImage/SDWebImageMapKit.h
index 99c14cd9..84e43156 100644
--- a/WebImage/SDWebImageMapKit.h
+++ b/WebImage/SDWebImageMapKit.h
@@ -9,11 +9,11 @@
#import
-//! Project version number for WebImage.
-FOUNDATION_EXPORT double WebImageMapKitVersionNumber;
+//! Project version number for SDWebImageMapKit.
+FOUNDATION_EXPORT double SDWebImageMapKitVersionNumber;
-//! Project version string for WebImage.
-FOUNDATION_EXPORT const unsigned char WebImageMapKitVersionString[];
+//! Project version string for SDWebImageMapKit.
+FOUNDATION_EXPORT const unsigned char SDWebImageMapKitVersionString[];
// In this header, you should import all the public headers of your framework using statements like #import