Commit 35a5116a authored by 陈业泓's avatar 陈业泓

更新了SDK代码

parent b1011432
......@@ -157,56 +157,56 @@ PODS:
- MBProgressHUD (1.2.0)
- MJExtension (3.2.2)
- MJRefresh (3.5.0)
- SDWebImage (5.9.1):
- SDWebImage/Core (= 5.9.1)
- SDWebImage/Core (5.9.1)
- SDWebImage (5.13.2):
- SDWebImage/Core (= 5.13.2)
- SDWebImage/Core (5.13.2)
- SGQRCode (3.0.1)
- STCBinder (1.0.0)
- SYCSDK (0.1.9):
- SYCSDK/BRPickerView (= 0.1.9)
- SYCSDK/DACircularProgress (= 0.1.9)
- SYCSDK/IQKeyboardManager (= 0.1.9)
- SYCSDK/JKCategories (= 0.1.9)
- SYCSDK/JXCategoryView (= 0.1.9)
- SYCSDK/JXPagingViewPager (= 0.1.9)
- SYCSDK/MBProgressHUD (= 0.1.9)
- SYCSDK/MJExtension (= 0.1.9)
- SYCSDK/MJRefresh (= 0.1.9)
- SYCSDK/SDWebImage (= 0.1.9)
- SYCSDK/SGQRCode (= 0.1.9)
- SYCSDK/STCBinder (= 0.1.9)
- SYCSDK/TTGTagCollectionView (= 0.1.9)
- SYCSDK/ViewUtil (= 0.1.9)
- SYCSDK/YYText (= 0.1.9)
- SYCSDK/BRPickerView (0.1.9):
- SYCSDK (0.2.0):
- SYCSDK/BRPickerView (= 0.2.0)
- SYCSDK/DACircularProgress (= 0.2.0)
- SYCSDK/IQKeyboardManager (= 0.2.0)
- SYCSDK/JKCategories (= 0.2.0)
- SYCSDK/JXCategoryView (= 0.2.0)
- SYCSDK/JXPagingViewPager (= 0.2.0)
- SYCSDK/MBProgressHUD (= 0.2.0)
- SYCSDK/MJExtension (= 0.2.0)
- SYCSDK/MJRefresh (= 0.2.0)
- SYCSDK/SDWebImage (= 0.2.0)
- SYCSDK/SGQRCode (= 0.2.0)
- SYCSDK/STCBinder (= 0.2.0)
- SYCSDK/TTGTagCollectionView (= 0.2.0)
- SYCSDK/ViewUtil (= 0.2.0)
- SYCSDK/YYText (= 0.2.0)
- SYCSDK/BRPickerView (0.2.0):
- BRPickerView (= 2.7.3)
- SYCSDK/DACircularProgress (0.1.9):
- SYCSDK/DACircularProgress (0.2.0):
- DACircularProgress (= 2.3.1)
- SYCSDK/IQKeyboardManager (0.1.9):
- SYCSDK/IQKeyboardManager (0.2.0):
- IQKeyboardManager (= 6.5.6)
- SYCSDK/JKCategories (0.1.9):
- SYCSDK/JKCategories (0.2.0):
- JKCategories (= 1.9.3)
- SYCSDK/JXCategoryView (0.1.9):
- SYCSDK/JXCategoryView (0.2.0):
- JXCategoryView (= 1.5.8)
- SYCSDK/JXPagingViewPager (0.1.9):
- SYCSDK/JXPagingViewPager (0.2.0):
- JXPagingView/Pager (= 2.1.0)
- SYCSDK/MBProgressHUD (0.1.9):
- SYCSDK/MBProgressHUD (0.2.0):
- MBProgressHUD (= 1.2.0)
- SYCSDK/MJExtension (0.1.9):
- SYCSDK/MJExtension (0.2.0):
- MJExtension (= 3.2.2)
- SYCSDK/MJRefresh (0.1.9):
- SYCSDK/MJRefresh (0.2.0):
- MJRefresh (= 3.5.0)
- SYCSDK/SDWebImage (0.1.9):
- SDWebImage (= 5.9.1)
- SYCSDK/SGQRCode (0.1.9):
- SYCSDK/SDWebImage (0.2.0):
- SDWebImage (= 5.13.2)
- SYCSDK/SGQRCode (0.2.0):
- SGQRCode (= 3.0.1)
- SYCSDK/STCBinder (0.1.9):
- SYCSDK/STCBinder (0.2.0):
- STCBinder (= 1.0.0)
- SYCSDK/TTGTagCollectionView (0.1.9):
- SYCSDK/TTGTagCollectionView (0.2.0):
- TTGTagCollectionView (~> 1.11.1)
- SYCSDK/ViewUtil (0.1.9):
- SYCSDK/ViewUtil (0.2.0):
- Masonry
- SYCSDK/YYText (0.1.9):
- SYCSDK/YYText (0.2.0):
- YYText (= 1.0.7)
- TTGTagCollectionView (1.11.2)
- YYText (1.0.7)
......@@ -247,13 +247,13 @@ SPEC CHECKSUMS:
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJExtension: d9b9c74cbdeb724c1e9ecbb157b318276e62e876
MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16
SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
SGQRCode: bf02d60e18d6660a9d75ff1c8b9b467f064f054e
STCBinder: 439487688e719d92d7690c0b12c0c1dab8ddcb07
SYCSDK: 8202881550dddcaf55cc38841f4b146b8d3455c3
SYCSDK: a20e69372a159d21543f6f28a6b3088908a51846
TTGTagCollectionView: 949030356ed27954f39dd29d78978c0f5c35a745
YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
PODFILE CHECKSUM: 32da94b045a2579a8e3c39aebfad5abc91c9f0f4
COCOAPODS: 1.11.2
COCOAPODS: 1.11.3
{
"name": "SYCSDK",
"version": "0.1.9",
"version": "0.2.0",
"summary": "A short description of SYCSDK.",
"description": "TODO: common three party.",
"homepage": "http://www.shengyc.com/",
......@@ -13,7 +13,7 @@
},
"source": {
"git": "http://139.159.244.151:2900/chenyehong/SYCSDK.git",
"tag": "0.1.9"
"tag": "0.2.0"
},
"platforms": {
"ios": "9.0"
......@@ -128,7 +128,7 @@
"name": "SDWebImage",
"dependencies": {
"SDWebImage": [
"5.9.1"
"5.13.2"
]
}
},
......
......@@ -157,56 +157,56 @@ PODS:
- MBProgressHUD (1.2.0)
- MJExtension (3.2.2)
- MJRefresh (3.5.0)
- SDWebImage (5.9.1):
- SDWebImage/Core (= 5.9.1)
- SDWebImage/Core (5.9.1)
- SDWebImage (5.13.2):
- SDWebImage/Core (= 5.13.2)
- SDWebImage/Core (5.13.2)
- SGQRCode (3.0.1)
- STCBinder (1.0.0)
- SYCSDK (0.1.9):
- SYCSDK/BRPickerView (= 0.1.9)
- SYCSDK/DACircularProgress (= 0.1.9)
- SYCSDK/IQKeyboardManager (= 0.1.9)
- SYCSDK/JKCategories (= 0.1.9)
- SYCSDK/JXCategoryView (= 0.1.9)
- SYCSDK/JXPagingViewPager (= 0.1.9)
- SYCSDK/MBProgressHUD (= 0.1.9)
- SYCSDK/MJExtension (= 0.1.9)
- SYCSDK/MJRefresh (= 0.1.9)
- SYCSDK/SDWebImage (= 0.1.9)
- SYCSDK/SGQRCode (= 0.1.9)
- SYCSDK/STCBinder (= 0.1.9)
- SYCSDK/TTGTagCollectionView (= 0.1.9)
- SYCSDK/ViewUtil (= 0.1.9)
- SYCSDK/YYText (= 0.1.9)
- SYCSDK/BRPickerView (0.1.9):
- SYCSDK (0.2.0):
- SYCSDK/BRPickerView (= 0.2.0)
- SYCSDK/DACircularProgress (= 0.2.0)
- SYCSDK/IQKeyboardManager (= 0.2.0)
- SYCSDK/JKCategories (= 0.2.0)
- SYCSDK/JXCategoryView (= 0.2.0)
- SYCSDK/JXPagingViewPager (= 0.2.0)
- SYCSDK/MBProgressHUD (= 0.2.0)
- SYCSDK/MJExtension (= 0.2.0)
- SYCSDK/MJRefresh (= 0.2.0)
- SYCSDK/SDWebImage (= 0.2.0)
- SYCSDK/SGQRCode (= 0.2.0)
- SYCSDK/STCBinder (= 0.2.0)
- SYCSDK/TTGTagCollectionView (= 0.2.0)
- SYCSDK/ViewUtil (= 0.2.0)
- SYCSDK/YYText (= 0.2.0)
- SYCSDK/BRPickerView (0.2.0):
- BRPickerView (= 2.7.3)
- SYCSDK/DACircularProgress (0.1.9):
- SYCSDK/DACircularProgress (0.2.0):
- DACircularProgress (= 2.3.1)
- SYCSDK/IQKeyboardManager (0.1.9):
- SYCSDK/IQKeyboardManager (0.2.0):
- IQKeyboardManager (= 6.5.6)
- SYCSDK/JKCategories (0.1.9):
- SYCSDK/JKCategories (0.2.0):
- JKCategories (= 1.9.3)
- SYCSDK/JXCategoryView (0.1.9):
- SYCSDK/JXCategoryView (0.2.0):
- JXCategoryView (= 1.5.8)
- SYCSDK/JXPagingViewPager (0.1.9):
- SYCSDK/JXPagingViewPager (0.2.0):
- JXPagingView/Pager (= 2.1.0)
- SYCSDK/MBProgressHUD (0.1.9):
- SYCSDK/MBProgressHUD (0.2.0):
- MBProgressHUD (= 1.2.0)
- SYCSDK/MJExtension (0.1.9):
- SYCSDK/MJExtension (0.2.0):
- MJExtension (= 3.2.2)
- SYCSDK/MJRefresh (0.1.9):
- SYCSDK/MJRefresh (0.2.0):
- MJRefresh (= 3.5.0)
- SYCSDK/SDWebImage (0.1.9):
- SDWebImage (= 5.9.1)
- SYCSDK/SGQRCode (0.1.9):
- SYCSDK/SDWebImage (0.2.0):
- SDWebImage (= 5.13.2)
- SYCSDK/SGQRCode (0.2.0):
- SGQRCode (= 3.0.1)
- SYCSDK/STCBinder (0.1.9):
- SYCSDK/STCBinder (0.2.0):
- STCBinder (= 1.0.0)
- SYCSDK/TTGTagCollectionView (0.1.9):
- SYCSDK/TTGTagCollectionView (0.2.0):
- TTGTagCollectionView (~> 1.11.1)
- SYCSDK/ViewUtil (0.1.9):
- SYCSDK/ViewUtil (0.2.0):
- Masonry
- SYCSDK/YYText (0.1.9):
- SYCSDK/YYText (0.2.0):
- YYText (= 1.0.7)
- TTGTagCollectionView (1.11.2)
- YYText (1.0.7)
......@@ -247,13 +247,13 @@ SPEC CHECKSUMS:
MBProgressHUD: 3ee5efcc380f6a79a7cc9b363dd669c5e1ae7406
MJExtension: d9b9c74cbdeb724c1e9ecbb157b318276e62e876
MJRefresh: 6afc955813966afb08305477dd7a0d9ad5e79a16
SDWebImage: a990c053fff71e388a10f3357edb0be17929c9c5
SDWebImage: 72f86271a6f3139cc7e4a89220946489d4b9a866
SGQRCode: bf02d60e18d6660a9d75ff1c8b9b467f064f054e
STCBinder: 439487688e719d92d7690c0b12c0c1dab8ddcb07
SYCSDK: 8202881550dddcaf55cc38841f4b146b8d3455c3
SYCSDK: a20e69372a159d21543f6f28a6b3088908a51846
TTGTagCollectionView: 949030356ed27954f39dd29d78978c0f5c35a745
YYText: 5c461d709e24d55a182d1441c41dc639a18a4849
PODFILE CHECKSUM: 32da94b045a2579a8e3c39aebfad5abc91c9f0f4
COCOAPODS: 1.11.2
COCOAPODS: 1.11.3
......@@ -6671,6 +6671,37 @@
};
name = Debug;
};
6220EF3052B8964FDDCB97127C9FBA03 /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 03FC9E66D646BFC925B3D57E0783B7DF /* SDWebImage.release.xcconfig */;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap";
PRODUCT_MODULE_NAME = SDWebImage;
PRODUCT_NAME = SDWebImage;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
63A8B36BADDCE8263FA5C03D412A0D01 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 03F410873AF9DBC3A8CF3067BD14C976 /* Pods-SYCSDK_Example.debug.xcconfig */;
......@@ -6918,37 +6949,6 @@
};
name = Debug;
};
B17BCC9842F86427766EF15C2427D2BA /* Release */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 03FC9E66D646BFC925B3D57E0783B7DF /* SDWebImage.release.xcconfig */;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
"CODE_SIGN_IDENTITY[sdk=watchos*]" = "";
CURRENT_PROJECT_VERSION = 1;
DEFINES_MODULE = YES;
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap";
PRODUCT_MODULE_NAME = SDWebImage;
PRODUCT_NAME = SDWebImage;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
SWIFT_VERSION = 4.0;
TARGETED_DEVICE_FAMILY = "1,2";
VALIDATE_PRODUCT = YES;
VERSIONING_SYSTEM = "apple-generic";
VERSION_INFO_PREFIX = "";
};
name = Release;
};
B6B324EF8406D9723FD9A08989EAFC03 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 36789CC34571896C1CF56EF0D3023C10 /* TTGTagCollectionView.debug.xcconfig */;
......@@ -6979,9 +6979,9 @@
};
name = Debug;
};
C0517E1CCB9EE888964F639762020D05 /* Debug */ = {
C437DB9E31D4D0C9067317F7DDA15949 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = 5E18277D69CA0D94A7F12D34762C8F74 /* SDWebImage.debug.xcconfig */;
baseConfigurationReference = EC7565F90F0A76C51A1DC90FCA3ED1A7 /* Masonry.debug.xcconfig */;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
......@@ -6991,14 +6991,14 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist";
GCC_PREFIX_HEADER = "Target Support Files/Masonry/Masonry-prefix.pch";
INFOPLIST_FILE = "Target Support Files/Masonry/Masonry-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap";
PRODUCT_MODULE_NAME = SDWebImage;
PRODUCT_NAME = SDWebImage;
MODULEMAP_FILE = "Target Support Files/Masonry/Masonry.modulemap";
PRODUCT_MODULE_NAME = Masonry;
PRODUCT_NAME = Masonry;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
......@@ -7009,9 +7009,9 @@
};
name = Debug;
};
C437DB9E31D4D0C9067317F7DDA15949 /* Debug */ = {
C54376061B0B868404C6ABC57F98241E /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = EC7565F90F0A76C51A1DC90FCA3ED1A7 /* Masonry.debug.xcconfig */;
baseConfigurationReference = C8AE5A3B8A1DD200FC95FEE283DBFD6F /* SGQRCode.debug.xcconfig */;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
......@@ -7021,14 +7021,14 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/Masonry/Masonry-prefix.pch";
INFOPLIST_FILE = "Target Support Files/Masonry/Masonry-Info.plist";
GCC_PREFIX_HEADER = "Target Support Files/SGQRCode/SGQRCode-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SGQRCode/SGQRCode-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/Masonry/Masonry.modulemap";
PRODUCT_MODULE_NAME = Masonry;
PRODUCT_NAME = Masonry;
MODULEMAP_FILE = "Target Support Files/SGQRCode/SGQRCode.modulemap";
PRODUCT_MODULE_NAME = SGQRCode;
PRODUCT_NAME = SGQRCode;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
......@@ -7039,9 +7039,9 @@
};
name = Debug;
};
C54376061B0B868404C6ABC57F98241E /* Debug */ = {
C5BD2E1647D5DF97106129666460C5D8 /* Debug */ = {
isa = XCBuildConfiguration;
baseConfigurationReference = C8AE5A3B8A1DD200FC95FEE283DBFD6F /* SGQRCode.debug.xcconfig */;
baseConfigurationReference = 5E18277D69CA0D94A7F12D34762C8F74 /* SDWebImage.debug.xcconfig */;
buildSettings = {
"CODE_SIGN_IDENTITY[sdk=appletvos*]" = "";
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "";
......@@ -7051,14 +7051,14 @@
DYLIB_COMPATIBILITY_VERSION = 1;
DYLIB_CURRENT_VERSION = 1;
DYLIB_INSTALL_NAME_BASE = "@rpath";
GCC_PREFIX_HEADER = "Target Support Files/SGQRCode/SGQRCode-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SGQRCode/SGQRCode-Info.plist";
GCC_PREFIX_HEADER = "Target Support Files/SDWebImage/SDWebImage-prefix.pch";
INFOPLIST_FILE = "Target Support Files/SDWebImage/SDWebImage-Info.plist";
INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks";
IPHONEOS_DEPLOYMENT_TARGET = 8.0;
IPHONEOS_DEPLOYMENT_TARGET = 9.0;
LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks";
MODULEMAP_FILE = "Target Support Files/SGQRCode/SGQRCode.modulemap";
PRODUCT_MODULE_NAME = SGQRCode;
PRODUCT_NAME = SGQRCode;
MODULEMAP_FILE = "Target Support Files/SDWebImage/SDWebImage.modulemap";
PRODUCT_MODULE_NAME = SDWebImage;
PRODUCT_NAME = SDWebImage;
SDKROOT = iphoneos;
SKIP_INSTALL = YES;
SWIFT_ACTIVE_COMPILATION_CONDITIONS = "$(inherited) ";
......@@ -7619,8 +7619,8 @@
BABD4A24CFCBE5B6FE4680AA062FCF04 /* Build configuration list for PBXNativeTarget "SDWebImage" */ = {
isa = XCConfigurationList;
buildConfigurations = (
C0517E1CCB9EE888964F639762020D05 /* Debug */,
B17BCC9842F86427766EF15C2427D2BA /* Release */,
C5BD2E1647D5DF97106129666460C5D8 /* Debug */,
6220EF3052B8964FDDCB97127C9FBA03 /* Release */,
);
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
......
......@@ -37,8 +37,9 @@ This library provides an async image downloader with cache support. For convenie
## Supported Image Formats
- Image formats supported by Apple system (JPEG, PNG, TIFF, HEIC, ...), including GIF/APNG/HEIC animation
- WebP format, including animated WebP (use the [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) project). Note iOS 14/macOS 11.0 supports built-in WebP decoding (no encoding).
- Image formats supported by Apple system (JPEG, PNG, TIFF, BMP, ...), including [GIF](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#gif-coder)/[APNG](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#apng-coder) animated image
- HEIC format from iOS 11/macOS 10.13, including animated HEIC from iOS 13/macOS 10.15 via [SDWebImageHEICCoder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#heic-coder). For lower firmware, use coder plugin [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder)
- WebP format from iOS 14/macOS 11.0 via [SDWebImageAWebPCoder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#awebp-coder). For lower firmware, use coder plugin [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder)
- Support extendable coder plugins for new image formats like BPG, AVIF. And vector format like PDF, SVG. See all the list in [Image coder plugin List](https://github.com/SDWebImage/SDWebImage/wiki/Coder-Plugin-List)
## Additional modules and Ecosystem
......@@ -74,7 +75,7 @@ The new framework introduce two View structs `WebImage` and `AnimatedImage` for
#### Integration with 3rd party libraries
- [SDWebImageLottiePlugin](https://github.com/SDWebImage/SDWebImageLottiePlugin) - plugin to support [Lottie-iOS](https://github.com/airbnb/lottie-ios), vector animation rending with remote JSON files
- [SDWebImageSVGKitPlugin](https://github.com/SDWebImage/SDWebImageLottiePlugin) - plugin to support [SVGKit](https://github.com/SVGKit/SVGKit), SVG rendering using Core Animation, iOS 8+/macOS 10.10+ support
- [SDWebImageSVGKitPlugin](https://github.com/SDWebImage/SDWebImageSVGKitPlugin) - plugin to support [SVGKit](https://github.com/SVGKit/SVGKit), SVG rendering using Core Animation, iOS 8+/macOS 10.10+ support
- [SDWebImageFLPlugin](https://github.com/SDWebImage/SDWebImageFLPlugin) - plugin to support [FLAnimatedImage](https://github.com/Flipboard/FLAnimatedImage) as the engine for animated GIFs
- [SDWebImageYYPlugin](https://github.com/SDWebImage/SDWebImageYYPlugin) - plugin to integrate [YYImage](https://github.com/ibireme/YYImage) & [YYCache](https://github.com/ibireme/YYCache) for image rendering & caching
......@@ -93,17 +94,18 @@ You can use those directly, or create similar components of your own, by using t
## Requirements
- iOS 8.0 or later
- iOS 9.0 or later
- tvOS 9.0 or later
- watchOS 2.0 or later
- macOS 10.10 or later (10.15 for Catalyst)
- Xcode 10.0 or later
- macOS 10.11 or later (10.15 for Catalyst)
- Xcode 11.0 or later
#### Backwards compatibility
- For iOS 8, macOS 10.10 or Xcode < 11, use [any 5.x version up to 5.9.5](https://github.com/SDWebImage/SDWebImage/releases/tag/5.9.5)
- For iOS 7, macOS 10.9 or Xcode < 8, use [any 4.x version up to 4.4.6](https://github.com/SDWebImage/SDWebImage/releases/tag/4.4.6)
- For macOS 10.8, use [any 4.x version up to 4.3.0](https://github.com/SDWebImage/SDWebImage/releases/tag/4.3.0)
- For iOS 5 and 6, use [any 3.x version up to 3.7.6](https://github.com/SDWebImage/SDWebImage/tag/3.7.6)
- For iOS 5 and 6, use [any 3.x version up to 3.7.6](https://github.com/SDWebImage/SDWebImage/releases/tag/3.7.6)
- For iOS < 5.0, please use the last [2.0 version](https://github.com/SDWebImage/SDWebImage/tree/2.0-compat).
## Getting Started
......@@ -164,7 +166,7 @@ This animated image solution is available for `iOS`/`tvOS`/`macOS`. The `SDAnima
The `SDAnimatedImageView` supports the familiar image loading category methods, works like drop-in replacement for `UIImageView/NSImageView`.
Don't have UIView (like WatchKit or CALayer)? you can still use `SDAnimatedPlayer` the player engine for advanced playback and rendering.
Don't have `UIView` (like `WatchKit` or `CALayer`)? you can still use `SDAnimatedPlayer` the player engine for advanced playback and rendering.
See [Animated Image](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for more detailed information.
......@@ -207,9 +209,9 @@ pod 'SDWebImage', '~> 5.0'
##### Swift and static framework
Swift project previously have to use `use_frameworks!` to make all Pods into dynamic framework to let CocoaPods works.
Swift project previously had to use `use_frameworks!` to make all Pods into dynamic framework to let CocoaPods work.
However, start with `CocoaPods 1.5.0+` (with `Xcode 9+`), which supports to build both Objective-C && Swift code into static framework. You can use modular headers to use SDWebImage as static framework, without the need of `use_frameworks!`:
However, starting with `CocoaPods 1.5.0+` (with `Xcode 9+`), which supports to build both Objective-C && Swift code into static framework. You can use modular headers to use SDWebImage as static framework, without the need of `use_frameworks!`:
```
platform :ios, '8.0'
......@@ -238,7 +240,7 @@ Podfile example:
pod 'SDWebImage/MapKit'
```
### Installation with Carthage (iOS 8+)
### Installation with Carthage
[Carthage](https://github.com/Carthage/Carthage) is a lightweight dependency manager for Swift and Objective-C. It leverages CocoaTouch modules and is less invasive than CocoaPods.
......@@ -295,6 +297,9 @@ It's also recommend to use the module import syntax, available for CocoaPods(ena
At this point your workspace should build without error. If you are having problem, post to the Issue and the
community can help you solve it.
## Data Collection Practices
As required by the [App privacy details on the App Store](https://developer.apple.com/app-store/app-privacy-details/), here's SDWebImage's list of [Data Collection Practices](https://sdwebimage.github.io/DataCollection/index.html).
## Author
- [Olivier Poitrey](https://github.com/rs)
......
......@@ -149,7 +149,7 @@ static NSString * const SDAlternateImageOperationKey = @"NSButtonAlternateImageO
[self sd_cancelImageLoadOperationWithKey:SDAlternateImageOperationKey];
}
#pragma mar - Private
#pragma mark - Private
- (NSURL *)sd_currentImageURL {
return objc_getAssociatedObject(self, @selector(sd_currentImageURL));
......
......@@ -45,7 +45,7 @@ static const SDImageFormat SDImageFormatSVG = 8;
*
* @param format Format as SDImageFormat
* @return The UTType as CFStringRef
* @note For unknown format, `kUTTypeImage` abstract type will return
* @note For unknown format, `kSDUTTypeImage` abstract type will return
*/
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format CF_RETURNS_NOT_RETAINED NS_SWIFT_NAME(sd_UTType(from:));
......
......@@ -87,16 +87,16 @@
CFStringRef UTType;
switch (format) {
case SDImageFormatJPEG:
UTType = kUTTypeJPEG;
UTType = kSDUTTypeJPEG;
break;
case SDImageFormatPNG:
UTType = kUTTypePNG;
UTType = kSDUTTypePNG;
break;
case SDImageFormatGIF:
UTType = kUTTypeGIF;
UTType = kSDUTTypeGIF;
break;
case SDImageFormatTIFF:
UTType = kUTTypeTIFF;
UTType = kSDUTTypeTIFF;
break;
case SDImageFormatWebP:
UTType = kSDUTTypeWebP;
......@@ -108,14 +108,14 @@
UTType = kSDUTTypeHEIF;
break;
case SDImageFormatPDF:
UTType = kUTTypePDF;
UTType = kSDUTTypePDF;
break;
case SDImageFormatSVG:
UTType = kUTTypeScalableVectorGraphics;
UTType = kSDUTTypeSVG;
break;
default:
// default is kUTTypeImage abstract type
UTType = kUTTypeImage;
UTType = kSDUTTypeImage;
break;
}
return UTType;
......@@ -126,13 +126,13 @@
return SDImageFormatUndefined;
}
SDImageFormat imageFormat;
if (CFStringCompare(uttype, kUTTypeJPEG, 0) == kCFCompareEqualTo) {
if (CFStringCompare(uttype, kSDUTTypeJPEG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatJPEG;
} else if (CFStringCompare(uttype, kUTTypePNG, 0) == kCFCompareEqualTo) {
} else if (CFStringCompare(uttype, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatPNG;
} else if (CFStringCompare(uttype, kUTTypeGIF, 0) == kCFCompareEqualTo) {
} else if (CFStringCompare(uttype, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatGIF;
} else if (CFStringCompare(uttype, kUTTypeTIFF, 0) == kCFCompareEqualTo) {
} else if (CFStringCompare(uttype, kSDUTTypeTIFF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatTIFF;
} else if (CFStringCompare(uttype, kSDUTTypeWebP, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatWebP;
......@@ -140,9 +140,9 @@
imageFormat = SDImageFormatHEIC;
} else if (CFStringCompare(uttype, kSDUTTypeHEIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatHEIF;
} else if (CFStringCompare(uttype, kUTTypePDF, 0) == kCFCompareEqualTo) {
} else if (CFStringCompare(uttype, kSDUTTypePDF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatPDF;
} else if (CFStringCompare(uttype, kUTTypeScalableVectorGraphics, 0) == kCFCompareEqualTo) {
} else if (CFStringCompare(uttype, kSDUTTypeSVG, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatSVG;
} else {
imageFormat = SDImageFormatUndefined;
......
......@@ -35,8 +35,8 @@
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
CGFloat width = imageRep.size.width;
CGFloat height = imageRep.size.height;
NSUInteger pixelWidth = imageRep.pixelsWide;
NSUInteger pixelHeight = imageRep.pixelsHigh;
CGFloat pixelWidth = (CGFloat)imageRep.pixelsWide;
CGFloat pixelHeight = (CGFloat)imageRep.pixelsHigh;
if (width > 0 && height > 0) {
CGFloat widthScale = pixelWidth / width;
CGFloat heightScale = pixelHeight / height;
......
......@@ -126,7 +126,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
data = [data copy]; // avoid mutable data
id<SDAnimatedImageCoder> animatedCoder = nil;
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
if ([coder canDecodeFromData:data]) {
if (!options) {
......@@ -207,7 +207,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
CGFloat scale = self.scale;
id<SDAnimatedImageCoder> animatedCoder = nil;
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
if ([coder canDecodeFromData:animatedImageData]) {
animatedCoder = [[[coder class] alloc] initWithAnimatedImageData:animatedImageData options:@{SDImageCoderDecodeScaleFactor : @(scale)}];
......@@ -314,6 +314,10 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
return;
}
- (NSUInteger)sd_imageFrameCount {
return self.animatedImageFrameCount;
}
- (SDImageFormat)sd_imageFormat {
return self.animatedImageFormat;
}
......
......@@ -10,6 +10,25 @@
#import "SDWebImageCompat.h"
#import "SDImageCoder.h"
typedef NS_ENUM(NSUInteger, SDAnimatedImagePlaybackMode) {
/**
* From first to last frame and stop or next loop.
*/
SDAnimatedImagePlaybackModeNormal = 0,
/**
* From last frame to first frame and stop or next loop.
*/
SDAnimatedImagePlaybackModeReverse,
/**
* From first frame to last frame and reverse again, like reciprocating.
*/
SDAnimatedImagePlaybackModeBounce,
/**
* From last frame to first frame and reverse again, like reversed reciprocating.
*/
SDAnimatedImagePlaybackModeReversedBounce,
};
/// A player to control the playback of animated image, which can be used to drive Animated ImageView or any rendering usage, like CALayer/WatchKit/SwiftUI rendering.
@interface SDAnimatedImagePlayer : NSObject
......@@ -37,6 +56,9 @@
/// `< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
@property (nonatomic, assign) double playbackRate;
/// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal.
@property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode;
/// Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0.
/// `0` means automatically adjust by calculating current memory usage.
/// `1` means without any buffer cache, each of frames will be decoded and then be freed after rendering. (Lowest Memory and Highest CPU)
......
......@@ -13,6 +13,7 @@
#import "SDInternalMacros.h"
@interface SDAnimatedImagePlayer () {
SD_LOCK_DECLARE(_lock);
NSRunLoopMode _runLoopMode;
}
......@@ -24,9 +25,9 @@
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@property (nonatomic, assign) BOOL shouldReverse;
@property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) dispatch_semaphore_t lock;
@property (nonatomic, strong) SDDisplayLink *displayLink;
@end
......@@ -46,6 +47,7 @@
self.totalLoopCount = provider.animatedImageLoopCount;
self.animatedProvider = provider;
self.playbackRate = 1.0;
SD_LOCK_INIT(_lock);
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
......@@ -70,7 +72,7 @@
[_fetchQueue cancelAllOperations];
[_fetchQueue addOperationWithBlock:^{
NSNumber *currentFrameIndex = @(self.currentFrameIndex);
SD_LOCK(self.lock);
SD_LOCK(self->_lock);
NSArray *keys = self.frameBuffer.allKeys;
// only keep the next frame for later rendering
for (NSNumber * key in keys) {
......@@ -78,7 +80,7 @@
[self.frameBuffer removeObjectForKey:key];
}
}
SD_UNLOCK(self.lock);
SD_UNLOCK(self->_lock);
}];
}
......@@ -98,13 +100,6 @@
return _frameBuffer;
}
- (dispatch_semaphore_t)lock {
if (!_lock) {
_lock = dispatch_semaphore_create(1);
}
return _lock;
}
- (SDDisplayLink *)displayLink {
if (!_displayLink) {
_displayLink = [SDDisplayLink displayLinkWithTarget:self selector:@selector(displayDidRefresh:)];
......@@ -142,7 +137,12 @@
if (self.currentFrameIndex != 0) {
return;
}
if ([self.animatedProvider isKindOfClass:[UIImage class]]) {
if (self.playbackMode == SDAnimatedImagePlaybackModeReverse ||
self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
self.currentFrameIndex = self.totalFrameCount - 1;
}
if (!self.currentFrame && [self.animatedProvider isKindOfClass:[UIImage class]]) {
UIImage *image = (UIImage *)self.animatedProvider;
// Use the poster image if available
#if SD_MAC
......@@ -152,37 +152,36 @@
#endif
if (posterFrame) {
self.currentFrame = posterFrame;
SD_LOCK(self.lock);
SD_LOCK(self->_lock);
self.frameBuffer[@(self.currentFrameIndex)] = self.currentFrame;
SD_UNLOCK(self.lock);
SD_UNLOCK(self->_lock);
[self handleFrameChange];
}
}
}
- (void)resetCurrentFrameIndex {
self.currentFrame = nil;
self.currentFrameIndex = 0;
self.currentLoopCount = 0;
self.currentTime = 0;
self.bufferMiss = NO;
self.needsDisplayWhenImageBecomesAvailable = NO;
[self handleFrameChange];
- (void)resetCurrentFrameStatus {
// These should not trigger KVO, user don't need to receive an `index == 0, image == nil` callback.
_currentFrame = nil;
_currentFrameIndex = 0;
_currentLoopCount = 0;
_currentTime = 0;
_bufferMiss = NO;
_needsDisplayWhenImageBecomesAvailable = NO;
}
- (void)clearFrameBuffer {
SD_LOCK(self.lock);
SD_LOCK(_lock);
[_frameBuffer removeAllObjects];
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
}
#pragma mark - Animation Control
- (void)startPlaying {
[self.displayLink start];
// Setup frame
if (self.currentFrameIndex == 0 && !self.currentFrame) {
[self setupCurrentFrame];
}
[self setupCurrentFrame];
// Calculate max buffer size
[self calculateMaxBufferCount];
}
......@@ -191,7 +190,8 @@
[_fetchQueue cancelAllOperations];
// Using `_displayLink` here because when UIImageView dealloc, it may trigger `[self stopAnimating]`, we already release the display link in SDAnimatedImageView's dealloc method.
[_displayLink stop];
[self resetCurrentFrameIndex];
// We need to reset the frame status, but not trigger any handle. This can ensure next time's playing status correct.
[self resetCurrentFrameStatus];
}
- (void)pausePlaying {
......@@ -241,17 +241,32 @@
NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
if (self.playbackMode == SDAnimatedImagePlaybackModeReverse) {
nextFrameIndex = currentFrameIndex == 0 ? (totalFrameCount - 1) : (currentFrameIndex - 1) % totalFrameCount;
} else if (self.playbackMode == SDAnimatedImagePlaybackModeBounce ||
self.playbackMode == SDAnimatedImagePlaybackModeReversedBounce) {
if (currentFrameIndex == 0) {
self.shouldReverse = false;
} else if (currentFrameIndex == totalFrameCount - 1) {
self.shouldReverse = true;
}
nextFrameIndex = self.shouldReverse ? (currentFrameIndex - 1) : (currentFrameIndex + 1);
nextFrameIndex %= totalFrameCount;
}
// Check if we need to display new frame firstly
BOOL bufferFull = NO;
if (self.needsDisplayWhenImageBecomesAvailable) {
UIImage *currentFrame;
SD_LOCK(self.lock);
SD_LOCK(_lock);
currentFrame = self.frameBuffer[@(currentFrameIndex)];
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
// Update the current frame
if (currentFrame) {
SD_LOCK(self.lock);
SD_LOCK(_lock);
// Remove the frame buffer if need
if (self.frameBuffer.count > self.maxBufferCount) {
self.frameBuffer[@(currentFrameIndex)] = nil;
......@@ -260,7 +275,7 @@
if (self.frameBuffer.count == totalFrameCount) {
bufferFull = YES;
}
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
// Update the current frame immediately
self.currentFrame = currentFrame;
......@@ -321,9 +336,9 @@
// 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);
SD_LOCK(_lock);
fetchFrame = self.bufferMiss? nil : self.frameBuffer[@(nextFrameIndex)];
SD_UNLOCK(self.lock);
SD_UNLOCK(_lock);
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue
......@@ -338,9 +353,9 @@
BOOL isAnimating = self.displayLink.isRunning;
if (isAnimating) {
SD_LOCK(self.lock);
SD_LOCK(self->_lock);
self.frameBuffer[@(fetchFrameIndex)] = frame;
SD_UNLOCK(self.lock);
SD_UNLOCK(self->_lock);
}
}];
[self.fetchQueue addOperation:operation];
......
......@@ -27,6 +27,12 @@
}
}
- (instancetype)copyWithZone:(NSZone *)zone {
SDAnimatedImageRep *imageRep = [super copyWithZone:zone];
CFRetain(imageRep->_imageSource);
return imageRep;
}
// `NSBitmapImageRep`'s `imageRepWithData:` is not designed initializer
+ (instancetype)imageRepWithData:(NSData *)data {
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
......@@ -52,13 +58,13 @@
if (!type) {
return self;
}
if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) {
if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
// GIF
// Fix the `NSBitmapImageRep` GIF loop count calculation issue
// Which will use 0 when there are no loop count information metadata in GIF data
NSUInteger loopCount = [SDImageGIFCoder imageLoopCountWithSource:imageSource];
[self setProperty:NSImageLoopCount withValue:@(loopCount)];
} else if (CFStringCompare(type, kUTTypePNG, 0) == kCFCompareEqualTo) {
} else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
// APNG
// Do initialize about frame count, current frame/duration and loop count
[self setProperty:NSImageFrameCount withValue:@(frameCount)];
......@@ -100,10 +106,10 @@
}
NSUInteger index = [value unsignedIntegerValue];
NSTimeInterval frameDuration = 0;
if (CFStringCompare(type, kUTTypeGIF, 0) == kCFCompareEqualTo) {
if (CFStringCompare(type, kSDUTTypeGIF, 0) == kCFCompareEqualTo) {
// GIF
frameDuration = [SDImageGIFCoder frameDurationAtIndex:index source:imageSource];
} else if (CFStringCompare(type, kUTTypePNG, 0) == kCFCompareEqualTo) {
} else if (CFStringCompare(type, kSDUTTypePNG, 0) == kCFCompareEqualTo) {
// APNG
frameDuration = [SDImageAPNGCoder frameDurationAtIndex:index source:imageSource];
} else if (CFStringCompare(type, kSDUTTypeHEICS, 0) == kCFCompareEqualTo) {
......
......@@ -11,14 +11,21 @@
#if SD_UIKIT || SD_MAC
#import "SDAnimatedImage.h"
#import "SDAnimatedImagePlayer.h"
/**
A drop-in replacement for UIImageView/NSImageView, you can use this for animated image rendering.
Call `setImage:` with `UIImage(NSImage)` which conform to `SDAnimatedImage` protocol will start animated image rendering. Call with normal UIImage(NSImage) will back to normal UIImageView(NSImageView) rendering
Call `setImage:` with `UIImage(NSImage)` which conforms to `SDAnimatedImage` protocol will start animated image rendering. Call with normal UIImage(NSImage) will back to normal UIImageView(NSImageView) rendering
For UIKit: use `-startAnimating`, `-stopAnimating` to control animating. `isAnimating` to check animation state.
For AppKit: use `-setAnimates:` to control animating, `animates` to check animation state. This view is layer-backed.
*/
@interface SDAnimatedImageView : UIImageView
/**
The internal animation player.
This property is only used for advanced usage, like inspecting/debugging animation status, control progressive loading, complicated animation frame index control, etc.
@warning Pay attention if you directly update the player's property like `totalFrameCount`, `totalLoopCount`, the same property on `SDAnimatedImageView` may not get synced.
*/
@property (nonatomic, strong, readonly, nullable) SDAnimatedImagePlayer *player;
/**
Current display frame image. This value is KVO Compliance.
......@@ -52,6 +59,10 @@
`< 0.0` is not supported currently and stop animation. (may support reverse playback in the future)
*/
@property (nonatomic, assign) double playbackRate;
/// Asynchronous setup animation playback mode. Default mode is SDAnimatedImagePlaybackModeNormal.
@property (nonatomic, assign) SDAnimatedImagePlaybackMode playbackMode;
/**
Provide a max buffer size by bytes. This is used to adjust frame buffer count and can be useful when the decoding cost is expensive (such as Animated WebP software decoding). Default is 0.
`0` means automatically adjust by calculating current memory usage.
......@@ -82,7 +93,7 @@
@property (nonatomic, assign) BOOL resetFrameIndexWhenStopped;
/**
If the image has more than one frame, set this value to `YES` will automatically
If the image which conforms to `SDAnimatedImage` protocol has more than one frame, set this value to `YES` will automatically
play/stop the animation when the view become visible/invisible.
Default is YES.
*/
......
......@@ -10,7 +10,6 @@
#if SD_UIKIT || SD_MAC
#import "SDAnimatedImagePlayer.h"
#import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h"
#import "SDInternalMacros.h"
......@@ -24,14 +23,15 @@
NSRunLoopMode _runLoopMode;
NSUInteger _maxBufferSize;
double _playbackRate;
SDAnimatedImagePlaybackMode _playbackMode;
}
@property (nonatomic, strong, readwrite) SDAnimatedImagePlayer *player;
@property (nonatomic, strong, readwrite) UIImage *currentFrame;
@property (nonatomic, assign, readwrite) NSUInteger currentFrameIndex;
@property (nonatomic, assign, readwrite) NSUInteger currentLoopCount;
@property (nonatomic, assign) BOOL shouldAnimate;
@property (nonatomic, assign) BOOL isProgressive;
@property (nonatomic,strong) SDAnimatedImagePlayer *player; // The animation player.
@property (nonatomic) CALayer *imageViewLayer; // The actual rendering layer.
@end
......@@ -164,6 +164,9 @@
// Play Rate
self.player.playbackRate = self.playbackRate;
// Play Mode
self.player.playbackMode = self.playbackMode;
// Setup handler
@weakify(self);
self.player.animationFrameHandler = ^(NSUInteger index, UIImage * frame) {
......@@ -239,6 +242,19 @@
return _playbackRate;
}
- (void)setPlaybackMode:(SDAnimatedImagePlaybackMode)playbackMode {
_playbackMode = playbackMode;
self.player.playbackMode = playbackMode;
}
- (SDAnimatedImagePlaybackMode)playbackMode {
if (!_initFinished) {
return SDAnimatedImagePlaybackModeNormal; // Default mode is normal
}
return _playbackMode;
}
- (BOOL)shouldIncrementalLoad
{
if (!_initFinished) {
......@@ -402,7 +418,8 @@
/// Check if it should be played
- (void)checkPlay
{
if (self.autoPlayAnimatedImage) {
// Only handle for SDAnimatedImage, leave UIAnimatedImage or animationImages for super implementation control
if (self.player && self.autoPlayAnimatedImage) {
[self updateShouldAnimate];
if (self.shouldAnimate) {
[self startAnimating];
......
......@@ -87,7 +87,7 @@ static NSString * const SDDiskCacheExtendedAttributeName = @"com.hackemist.SDDis
// get cache Path for image key
NSString *cachePathForKey = [self cachePathForKey:key];
// transform to NSURL
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey];
NSURL *fileURL = [NSURL fileURLWithPath:cachePathForKey isDirectory:NO];
[data writeToURL:fileURL options:self.config.diskCacheWritingOptions error:nil];
......
......@@ -132,7 +132,13 @@
#elif SD_UIKIT
CGFloat screenScale = [UIScreen mainScreen].scale;
#elif SD_MAC
CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor;
NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
CGFloat screenScale = mainScreen.backingScaleFactor ?: 1.0f;
#endif
self.scale = screenScale;
self.opaque = NO;
......@@ -166,7 +172,13 @@
#elif SD_UIKIT
CGFloat screenScale = [UIScreen mainScreen].scale;
#elif SD_MAC
CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor;
NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
CGFloat screenScale = mainScreen.backingScaleFactor ?: 1.0f;
#endif
self.scale = screenScale;
self.opaque = NO;
......
......@@ -7,20 +7,13 @@
*/
#import "SDImageAPNGCoder.h"
#import "SDImageIOAnimatedCoderInternal.h"
#if SD_MAC
#import <CoreServices/CoreServices.h>
#else
#import <MobileCoreServices/MobileCoreServices.h>
#endif
// iOS 8 Image/IO framework binary does not contains these APNG constants, so we define them. Thanks Apple :)
// We can not use runtime @available check for this issue, because it's a global symbol and should be loaded during launch time by dyld. So hack if the min deployment target version < iOS 9.0, whatever it running on iOS 9+ or not.
#if (__IPHONE_OS_VERSION_MIN_REQUIRED && __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_9_0)
const CFStringRef kCGImagePropertyAPNGLoopCount = (__bridge CFStringRef)@"LoopCount";
const CFStringRef kCGImagePropertyAPNGDelayTime = (__bridge CFStringRef)@"DelayTime";
const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef)@"UnclampedDelayTime";
#endif
@implementation SDImageAPNGCoder
+ (instancetype)sharedCoder {
......@@ -39,7 +32,7 @@ const CFStringRef kCGImagePropertyAPNGUnclampedDelayTime = (__bridge CFStringRef
}
+ (NSString *)imageUTType {
return (__bridge NSString *)kUTTypePNG;
return (__bridge NSString *)kSDUTTypePNG;
}
+ (NSString *)dictionaryProperty {
......
......@@ -55,6 +55,23 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
SDImageCacheMatchAnimatedImageClass = 1 << 7,
};
/**
* A token associated with each cache query. Can be used to cancel a cache query
*/
@interface SDImageCacheToken : NSObject <SDWebImageOperation>
/**
Cancel the current cache query.
*/
- (void)cancel;
/**
The query's cache key.
*/
@property (nonatomic, strong, nullable, readonly) NSString *key;
@end
/**
* SDImageCache maintains a memory cache and a disk cache. Disk cache write operations are performed
* asynchronous so it doesn’t add unnecessary latency to the UI.
......@@ -179,6 +196,17 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
toDisk:(BOOL)toDisk
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Asynchronously store an image data into disk cache at the given key.
*
* @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL
* @param completionBlock A block executed after the operation is finished
*/
- (void)storeImageData:(nullable NSData *)imageData
forKey:(nullable NSString *)key
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Asynchronously store an image into memory and disk cache at the given key.
*
......@@ -198,7 +226,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
completion:(nullable SDWebImageNoParamsBlock)completionBlock;
/**
* Synchronously store image into memory cache at the given key.
* Synchronously store an image into memory cache at the given key.
*
* @param image The image to store
* @param key The unique image cache key, usually it's image absolute URL
......@@ -207,7 +235,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
forKey:(nullable NSString *)key;
/**
* Synchronously store image data into disk cache at the given key.
* Synchronously store an image data into disk cache at the given key.
*
* @param imageData The image data to store
* @param key The unique image cache key, usually it's image absolute URL
......@@ -259,9 +287,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @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
* @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/**
* Asynchronously queries the cache with operation and call the completion when done.
......@@ -270,9 +298,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @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
*
* @return a NSOperation instance containing the cache op
* @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/**
* Asynchronously queries the cache with operation and call the completion when done.
......@@ -282,9 +310,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @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
*
* @return a NSOperation instance containing the cache op
* @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancellederation, will callback immediately when cancelled
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
- (nullable SDImageCacheToken *)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.
......@@ -295,9 +323,9 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @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 immediately.
* @param doneBlock The completion block. Will not get called if the operation is cancelled
*
* @return a NSOperation instance containing the cache op
* @return a SDImageCacheToken instance containing the cache operation, will callback immediately when cancelled
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
- (nullable SDImageCacheToken *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/**
* Synchronously query the memory cache.
......@@ -341,7 +369,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @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;;
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;
#pragma mark - Remove Ops
......
......@@ -57,7 +57,8 @@ typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
/*
* The option to control weak memory cache for images. When enable, `SDImageCache`'s memory cache will use a weak maptable to store the image at the same time when it stored to memory, and get removed at the same time.
* However when memory warning is triggered, since the weak maptable does not hold a strong reference to image instance, even when the memory cache itself is purged, some images which are held strongly by UIImageViews or other live instances can be recovered again, to avoid later re-query from disk cache or network. This may be helpful for the case, for example, when app enter background and memory is purged, cause cell flashing after re-enter foreground.
* Defaults to YES. You can change this option dynamically.
* When enabling this option, we will sync back the image from weak maptable to strong cache during next time top level `sd_setImage` function call.
* Defaults to NO (YES before 5.12.0 version). You can change this option dynamically.
*/
@property (assign, nonatomic) BOOL shouldUseWeakMemoryCache;
......@@ -67,6 +68,12 @@ typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
*/
@property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenEnterBackground;
/**
* Whether or not to remove the expired disk data when application been terminated. This operation is processed in sync to ensure clean up.
* Defaults to YES.
*/
@property (assign, nonatomic) BOOL shouldRemoveExpiredDataWhenTerminate;
/**
* The reading options while reading cache from disk.
* Defaults to 0. You can set this to `NSDataReadingMappedIfSafe` to improve performance.
......
......@@ -27,8 +27,9 @@ static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week
if (self = [super init]) {
_shouldDisableiCloud = YES;
_shouldCacheImagesInMemory = YES;
_shouldUseWeakMemoryCache = YES;
_shouldUseWeakMemoryCache = NO;
_shouldRemoveExpiredDataWhenEnterBackground = YES;
_shouldRemoveExpiredDataWhenTerminate = YES;
_diskCacheReadingOptions = 0;
_diskCacheWritingOptions = NSDataWritingAtomic;
_maxDiskAge = kDefaultCacheMaxDiskAge;
......@@ -46,6 +47,7 @@ static const NSInteger kDefaultCacheMaxDiskAge = 60 * 60 * 24 * 7; // 1 week
config.shouldCacheImagesInMemory = self.shouldCacheImagesInMemory;
config.shouldUseWeakMemoryCache = self.shouldUseWeakMemoryCache;
config.shouldRemoveExpiredDataWhenEnterBackground = self.shouldRemoveExpiredDataWhenEnterBackground;
config.shouldRemoveExpiredDataWhenTerminate = self.shouldRemoveExpiredDataWhenTerminate;
config.diskCacheReadingOptions = self.diskCacheReadingOptions;
config.diskCacheWritingOptions = self.diskCacheWritingOptions;
config.maxDiskAge = self.maxDiskAge;
......
......@@ -10,6 +10,7 @@
#import "SDWebImageCompat.h"
#import "SDWebImageOperation.h"
#import "SDWebImageDefine.h"
#import "SDImageCoder.h"
/// Image Cache Type
typedef NS_ENUM(NSInteger, SDImageCacheType) {
......@@ -54,6 +55,12 @@ typedef void(^SDImageCacheContainsCompletionBlock)(SDImageCacheType containsCach
*/
FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context);
/// Get the decode options from the loading context options and cache key. This is the built-in translate between the web loading part to the decoding part (which does not depens on).
/// @param context The options arg from the input
/// @param options The context arg from the input
/// @param cacheKey The image cache key from the input. Should not be nil
FOUNDATION_EXPORT SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * _Nullable context, SDWebImageOptions options, NSString * _Nonnull cacheKey);
/**
This is the image cache protocol to provide custom image cache for `SDWebImageManager`.
Though the best practice to custom image cache, is to write your own class which conform `SDMemoryCache` or `SDDiskCache` protocol for `SDImageCache` class (See more on `SDImageCacheConfig.memoryCacheClass & SDImageCacheConfig.diskCacheClass`).
......
......@@ -13,8 +13,7 @@
#import "UIImage+Metadata.h"
#import "SDInternalMacros.h"
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
UIImage *image;
SDImageCoderOptions * _Nonnull SDGetDecodeOptionsFromContext(SDWebImageContext * _Nullable context, SDWebImageOptions options, NSString * _Nonnull cacheKey) {
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
......@@ -38,6 +37,17 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
mutableCoderOptions[SDImageCoderWebImageContext] = context;
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
return coderOptions;
}
UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSString * _Nonnull cacheKey, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
NSCParameterAssert(imageData);
NSCParameterAssert(cacheKey);
UIImage *image;
SDImageCoderOptions *coderOptions = SDGetDecodeOptionsFromContext(context, options, cacheKey);
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue];
// Grab the image coder
id<SDImageCoder> imageCoder;
if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) {
......@@ -79,6 +89,8 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
}
return image;
......
......@@ -13,13 +13,12 @@
@interface SDImageCachesManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t cachesLock;
@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCache>> *imageCaches;
@end
@implementation SDImageCachesManager
{
NSMutableArray<id<SDImageCache>> *_imageCaches;
@implementation SDImageCachesManager {
SD_LOCK_DECLARE(_cachesLock);
}
+ (SDImageCachesManager *)sharedManager {
......@@ -41,25 +40,25 @@
self.clearOperationPolicy = SDImageCachesManagerOperationPolicyConcurrent;
// initialize with default image caches
_imageCaches = [NSMutableArray arrayWithObject:[SDImageCache sharedImageCache]];
_cachesLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_cachesLock);
}
return self;
}
- (NSArray<id<SDImageCache>> *)caches {
SD_LOCK(self.cachesLock);
SD_LOCK(_cachesLock);
NSArray<id<SDImageCache>> *caches = [_imageCaches copy];
SD_UNLOCK(self.cachesLock);
SD_UNLOCK(_cachesLock);
return caches;
}
- (void)setCaches:(NSArray<id<SDImageCache>> *)caches {
SD_LOCK(self.cachesLock);
SD_LOCK(_cachesLock);
[_imageCaches removeAllObjects];
if (caches.count) {
[_imageCaches addObjectsFromArray:caches];
}
SD_UNLOCK(self.cachesLock);
SD_UNLOCK(_cachesLock);
}
#pragma mark - Cache IO operations
......@@ -68,18 +67,18 @@
if (![cache conformsToProtocol:@protocol(SDImageCache)]) {
return;
}
SD_LOCK(self.cachesLock);
SD_LOCK(_cachesLock);
[_imageCaches addObject:cache];
SD_UNLOCK(self.cachesLock);
SD_UNLOCK(_cachesLock);
}
- (void)removeCache:(id<SDImageCache>)cache {
if (![cache conformsToProtocol:@protocol(SDImageCache)]) {
return;
}
SD_LOCK(self.cachesLock);
SD_LOCK(_cachesLock);
[_imageCaches removeObject:cache];
SD_UNLOCK(self.cachesLock);
SD_UNLOCK(_cachesLock);
}
#pragma mark - SDImageCache
......
......@@ -93,7 +93,7 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumb
But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data information only.
See `SDWebImageContext` for more detailed information.
*/
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));;
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
/**
......
......@@ -10,6 +10,15 @@
#import "SDWebImageCompat.h"
#import "SDImageFrame.h"
typedef NS_ENUM(NSUInteger, SDImageCoderDecodeSolution) {
/// automatically choose the solution based on image format, hardware, OS version. This keep balance for compatibility and performance. Default after SDWebImage 5.13.0
SDImageCoderDecodeSolutionAutomatic,
/// always use CoreGraphics to draw on bitmap context and trigger decode. Best compatibility. Default before SDWebImage 5.13.0
SDImageCoderDecodeSolutionCoreGraphics,
/// available on iOS/tvOS 15+, use UIKit's new CGImageDecompressor/CMPhoto to decode. Best performance. If failed, will fallback to CoreGraphics as well
SDImageCoderDecodeSolutionUIKit
};
/**
Provide some common helper methods for building the image decoder/encoder.
*/
......@@ -76,6 +85,7 @@
/**
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.
@note If you need to keep aspect ratio, you can calculate the scale size by using `scaledSizeWithImageSize` first.
@param cgImage The CGImage
@param size The scale size in pixel.
......@@ -83,6 +93,16 @@
*/
+ (CGImageRef _Nullable)CGImageCreateScaled:(_Nonnull CGImageRef)cgImage size:(CGSize)size CF_RETURNS_RETAINED;
/** Scale the image size based on provided scale size, whether or not to preserve aspect ratio, whether or not to scale up.
@note For example, if you implements thumnail decoding, pass `shouldScaleUp` to NO to avoid the calculated size larger than image size.
@param imageSize The image size (in pixel or point defined by caller)
@param scaleSize The scale size (in pixel or point defined by caller)
@param preserveAspectRatio Whether or not to preserve aspect ratio
@param shouldScaleUp Whether or not to scale up (or scale down only)
*/
+ (CGSize)scaledSizeWithImageSize:(CGSize)imageSize scaleSize:(CGSize)scaleSize preserveAspectRatio:(BOOL)preserveAspectRatio shouldScaleUp:(BOOL)shouldScaleUp;
/**
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
......@@ -100,6 +120,12 @@
*/
+ (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes;
/**
Control the default force decode solution. Available solutions in `SDImageCoderDecodeSolution`.
@note Defaults to `SDImageCoderDecodeSolutionAutomatic`, which prefers to use UIKit for JPEG/HEIF, and fallback on CoreGraphics. If you want control on your hand, set the other solution.
*/
@property (class, readwrite) SDImageCoderDecodeSolution defaultDecodeSolution;
/**
Control the default limit bytes to scale down largest images.
This value must be larger than 4 Bytes (at least 1x1 pixel). Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS.
......
......@@ -15,13 +15,12 @@
@interface SDImageCodersManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t codersLock;
@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageCoder>> *imageCoders;
@end
@implementation SDImageCodersManager
{
NSMutableArray<id<SDImageCoder>> *_imageCoders;
@implementation SDImageCodersManager {
SD_LOCK_DECLARE(_codersLock);
}
+ (nonnull instancetype)sharedManager {
......@@ -37,27 +36,25 @@
if (self = [super init]) {
// initialize with default coders
_imageCoders = [NSMutableArray arrayWithArray:@[[SDImageIOCoder sharedCoder], [SDImageGIFCoder sharedCoder], [SDImageAPNGCoder sharedCoder]]];
_codersLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_codersLock);
}
return self;
}
- (NSArray<id<SDImageCoder>> *)coders
{
SD_LOCK(self.codersLock);
- (NSArray<id<SDImageCoder>> *)coders {
SD_LOCK(_codersLock);
NSArray<id<SDImageCoder>> *coders = [_imageCoders copy];
SD_UNLOCK(self.codersLock);
SD_UNLOCK(_codersLock);
return coders;
}
- (void)setCoders:(NSArray<id<SDImageCoder>> *)coders
{
SD_LOCK(self.codersLock);
- (void)setCoders:(NSArray<id<SDImageCoder>> *)coders {
SD_LOCK(_codersLock);
[_imageCoders removeAllObjects];
if (coders.count) {
[_imageCoders addObjectsFromArray:coders];
}
SD_UNLOCK(self.codersLock);
SD_UNLOCK(_codersLock);
}
#pragma mark - Coder IO operations
......@@ -66,18 +63,18 @@
if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
return;
}
SD_LOCK(self.codersLock);
SD_LOCK(_codersLock);
[_imageCoders addObject:coder];
SD_UNLOCK(self.codersLock);
SD_UNLOCK(_codersLock);
}
- (void)removeCoder:(nonnull id<SDImageCoder>)coder {
if (![coder conformsToProtocol:@protocol(SDImageCoder)]) {
return;
}
SD_LOCK(self.codersLock);
SD_LOCK(_codersLock);
[_imageCoders removeObject:coder];
SD_UNLOCK(self.codersLock);
SD_UNLOCK(_codersLock);
}
#pragma mark - SDImageCoder
......
......@@ -7,6 +7,7 @@
*/
#import "SDImageGIFCoder.h"
#import "SDImageIOAnimatedCoderInternal.h"
#if SD_MAC
#import <CoreServices/CoreServices.h>
#else
......@@ -31,7 +32,7 @@
}
+ (NSString *)imageUTType {
return (__bridge NSString *)kUTTypeGIF;
return (__bridge NSString *)kSDUTTypeGIF;
}
+ (NSString *)dictionaryProperty {
......
......@@ -8,6 +8,7 @@
#import "SDImageGraphics.h"
#import "NSImage+Compatibility.h"
#import "SDImageCoderHelper.h"
#import "objc/runtime.h"
#if SD_MAC
......@@ -16,17 +17,32 @@ static void *kNSGraphicsContextScaleFactorKey;
static CGContextRef SDCGContextCreateBitmapContext(CGSize size, BOOL opaque, CGFloat scale) {
if (scale == 0) {
// Match `UIGraphicsBeginImageContextWithOptions`, reset to the scale factor of the devices main screen if scale is 0.
scale = [NSScreen mainScreen].backingScaleFactor;
NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
scale = mainScreen.backingScaleFactor ?: 1.0f;
}
size_t width = ceil(size.width * scale);
size_t height = ceil(size.height * scale);
if (width < 1 || height < 1) return NULL;
//pre-multiplied BGRA for non-opaque, BGRX for opaque, 8-bits per component, as Apple's doc
CGColorSpaceRef space = CGColorSpaceCreateDeviceRGB();
CGImageAlphaInfo alphaInfo = kCGBitmapByteOrder32Host | (opaque ? kCGImageAlphaNoneSkipFirst : kCGImageAlphaPremultipliedFirst);
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, kCGBitmapByteOrderDefault | alphaInfo);
CGColorSpaceRelease(space);
CGColorSpaceRef space = [SDImageCoderHelper colorSpaceGetDeviceRGB];
// kCGImageAlphaNone is not supported in CGBitmapContextCreate.
// Check #3330 for more detail about why this bitmap is choosen.
CGBitmapInfo bitmapInfo;
if (!opaque) {
// iPhone GPU prefer to use BGRA8888, see: https://forums.raywenderlich.com/t/why-mtlpixelformat-bgra8unorm/53489
// BGRA8888
bitmapInfo = kCGBitmapByteOrder32Host | kCGImageAlphaPremultipliedFirst;
} else {
// BGR888 previously works on iOS 8~iOS 14, however, iOS 15+ will result a black image. FB9958017
// RGB888
bitmapInfo = kCGBitmapByteOrderDefault | kCGImageAlphaNoneSkipLast;
}
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, space, bitmapInfo);
if (!context) {
return NULL;
}
......@@ -96,7 +112,13 @@ UIImage * SDGraphicsGetImageFromCurrentImageContext(void) {
}
if (!scale) {
// reset to the scale factor of the devices main screen if scale is 0.
scale = [NSScreen mainScreen].backingScaleFactor;
NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
scale = mainScreen.backingScaleFactor ?: 1.0f;
}
NSImage *image = [[NSImage alloc] initWithCGImage:imageRef scale:scale orientation:kCGImagePropertyOrientationUp];
CGImageRelease(imageRef);
......
......@@ -18,8 +18,6 @@ static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTi
@implementation SDImageHEICCoder
+ (void)initialize {
#if __IPHONE_13_0 || __TVOS_13_0 || __MAC_10_15 || __WATCHOS_6_0
// Xcode 11
if (@available(iOS 13, tvOS 13, macOS 10.15, watchOS 6, *)) {
// Use SDK instead of raw value
kSDCGImagePropertyHEICSDictionary = (__bridge NSString *)kCGImagePropertyHEICSDictionary;
......@@ -27,7 +25,6 @@ static NSString * kSDCGImagePropertyHEICSUnclampedDelayTime = @"UnclampedDelayTi
kSDCGImagePropertyHEICSDelayTime = (__bridge NSString *)kCGImagePropertyHEICSDelayTime;
kSDCGImagePropertyHEICSUnclampedDelayTime = (__bridge NSString *)kCGImagePropertyHEICSUnclampedDelayTime;
}
#endif
}
+ (instancetype)sharedCoder {
......
......@@ -24,7 +24,7 @@
*/
@property (class, readonly) SDImageFormat imageFormat;
/**
The supported image format UTI Type. Such as `kUTTypeGIF`.
The supported image format UTI Type. Such as `kSDUTTypeGIF`.
This can be used for cases when we can not detect `SDImageFormat. Such as progressive decoding's hint format `kCGImageSourceTypeIdentifierHint`.
@note Subclass override.
*/
......
......@@ -191,19 +191,12 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
// 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];
CGFloat pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] doubleValue];
CGFloat pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] doubleValue];
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) {
......@@ -214,22 +207,6 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
CGImageRef imageRef;
BOOL createFullImage = thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height);
if (createFullImage) {
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 copy]);
} else {
decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
......@@ -238,9 +215,9 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
if (pixelRatio > thumbnailRatio) {
maxPixelSize = thumbnailSize.width;
maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.width / pixelRatio);
} else {
maxPixelSize = thumbnailSize.height;
maxPixelSize = MAX(thumbnailSize.height, thumbnailSize.height * pixelRatio);
}
} else {
maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
......@@ -311,11 +288,14 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
// 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;
if (imageRep) {
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
imageRep.size = size;
NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
[animatedImage addRepresentation:imageRep];
animatedImage.sd_imageFormat = self.class.imageFormat;
return animatedImage;
}
}
#endif
......@@ -496,16 +476,18 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif
}
NSUInteger pixelWidth = CGImageGetWidth(imageRef);
NSUInteger pixelHeight = CGImageGetHeight(imageRef);
CGFloat pixelWidth = (CGFloat)CGImageGetWidth(imageRef);
CGFloat pixelHeight = (CGFloat)CGImageGetHeight(imageRef);
CGFloat finalPixelSize = 0;
if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > maxPixelSize.width && pixelHeight > maxPixelSize.height) {
BOOL encodeFullImage = maxPixelSize.width == 0 || maxPixelSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= maxPixelSize.width && pixelHeight <= maxPixelSize.height);
if (!encodeFullImage) {
// Thumbnail Encoding
CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
if (pixelRatio > maxPixelSizeRatio) {
finalPixelSize = maxPixelSize.width;
finalPixelSize = MAX(maxPixelSize.width, maxPixelSize.width / pixelRatio);
} else {
finalPixelSize = maxPixelSize.height;
finalPixelSize = MAX(maxPixelSize.height, maxPixelSize.height * pixelRatio);
}
properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
}
......@@ -655,7 +637,7 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
return nil;
}
image.sd_imageFormat = self.class.imageFormat;
image.sd_isDecoded = YES;;
image.sd_isDecoded = YES;
return image;
}
......
......@@ -13,6 +13,8 @@
#import "UIImage+Metadata.h"
#import "SDImageIOAnimatedCoderInternal.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";
......@@ -52,6 +54,31 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
return coder;
}
#pragma mark - Utils
+ (CGRect)boxRectFromPDFFData:(nonnull NSData *)data {
CGDataProviderRef provider = CGDataProviderCreateWithCFData((__bridge CFDataRef)data);
if (!provider) {
return CGRectZero;
}
CGPDFDocumentRef document = CGPDFDocumentCreateWithProvider(provider);
CGDataProviderRelease(provider);
if (!document) {
return CGRectZero;
}
// `CGPDFDocumentGetPage` page number is 1-indexed.
CGPDFPageRef page = CGPDFDocumentGetPage(document, 1);
if (!page) {
CGPDFDocumentRelease(document);
return CGRectZero;
}
CGRect boxRect = CGPDFPageGetBoxRect(page, kCGPDFMediaBox);
CGPDFDocumentRelease(document);
return boxRect;
}
#pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data {
return YES;
......@@ -88,13 +115,39 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
return nil;
}
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
CFStringRef uttype = CGImageSourceGetType(source);
SDImageFormat imageFormat = [NSData sd_imageFormatFromUTType:uttype];
// Check vector format
NSDictionary *decodingOptions = nil;
if (imageFormat == SDImageFormatPDF) {
// Use 72 DPI (1:1 inch to pixel) by default, matching Apple's PDFKit behavior
NSUInteger rasterizationDPI = 72;
CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
if (maxPixelSize > 0) {
// Calculate DPI based on PDF box and pixel size
CGRect boxRect = [self.class boxRectFromPDFFData:data];
CGFloat maxBoxSize = MAX(boxRect.size.width, boxRect.size.height);
if (maxBoxSize > 0) {
rasterizationDPI = rasterizationDPI * (maxPixelSize / maxBoxSize);
}
}
decodingOptions = @{
// This option will cause ImageIO return the pixel size from `CGImageSourceCopyProperties`
// If not provided, it always return 0 size
kSDCGImageSourceRasterizationDPI : @(rasterizationDPI),
};
// Already calculated DPI, avoid re-calculation based on thumbnail information
preserveAspectRatio = YES;
thumbnailSize = CGSizeZero;
}
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:decodingOptions];
CFRelease(source);
if (!image) {
return nil;
}
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
image.sd_imageFormat = imageFormat;
return image;
}
......@@ -250,16 +303,18 @@ static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestination
maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif
}
NSUInteger pixelWidth = CGImageGetWidth(imageRef);
NSUInteger pixelHeight = CGImageGetHeight(imageRef);
if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > maxPixelSize.width && pixelHeight > maxPixelSize.height) {
CGFloat pixelWidth = (CGFloat)CGImageGetWidth(imageRef);
CGFloat pixelHeight = (CGFloat)CGImageGetHeight(imageRef);
CGFloat finalPixelSize = 0;
BOOL encodeFullImage = maxPixelSize.width == 0 || maxPixelSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= maxPixelSize.width && pixelHeight <= maxPixelSize.height);
if (!encodeFullImage) {
// Thumbnail Encoding
CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
CGFloat finalPixelSize;
if (pixelRatio > maxPixelSizeRatio) {
finalPixelSize = maxPixelSize.width;
finalPixelSize = MAX(maxPixelSize.width, maxPixelSize.width / pixelRatio);
} else {
finalPixelSize = maxPixelSize.height;
finalPixelSize = MAX(maxPixelSize.height, maxPixelSize.height * pixelRatio);
}
properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
}
......
......@@ -9,6 +9,7 @@
#import "SDWebImageCompat.h"
#import "SDWebImageDefine.h"
#import "SDWebImageOperation.h"
#import "SDImageCoder.h"
typedef void(^SDImageLoaderProgressBlock)(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL);
typedef void(^SDImageLoaderCompletedBlock)(UIImage * _Nullable image, NSData * _Nullable data, NSError * _Nullable error, BOOL finished);
......@@ -50,6 +51,18 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Non
*/
FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, BOOL finished, id<SDWebImageOperation> _Nonnull operation, SDWebImageOptions options, SDWebImageContext * _Nullable context);
/**
This function get the progressive decoder for current loading operation. If no progressive decoding is happended or decoder is not able to construct, return nil.
@return The progressive decoder associated with the loading operation.
*/
FOUNDATION_EXPORT id<SDProgressiveImageCoder> _Nullable SDImageLoaderGetProgressiveCoder(id<SDWebImageOperation> _Nonnull operation);
/**
This function set the progressive decoder for current loading operation. If no progressive decoding is happended, pass nil.
@param operation The loading operation to associate the progerssive decoder.
*/
FOUNDATION_EXPORT void SDImageLoaderSetProgressiveCoder(id<SDWebImageOperation> _Nonnull operation, id<SDProgressiveImageCoder> _Nullable progressiveCoder);
#pragma mark - SDImageLoader
/**
......@@ -60,6 +73,7 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
*/
@protocol SDImageLoader <NSObject>
@required
/**
Whether current image loader supports to load the provide image URL.
This will be checked every time a new image request come for loader. If this return NO, we will mark this image load as failed. If return YES, we will start to call `requestImageWithURL:options:context:progress:completed:`.
......@@ -67,8 +81,23 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
@param url The image URL to be loaded.
@return YES to continue download, NO to stop download.
*/
- (BOOL)canRequestImageForURL:(nullable NSURL *)url;
- (BOOL)canRequestImageForURL:(nullable NSURL *)url API_DEPRECATED("Use canRequestImageForURL:options:context: 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));
@optional
/**
Whether current image loader supports to load the provide image URL, with associated options and context.
This will be checked every time a new image request come for loader. If this return NO, we will mark this image load as failed. If return YES, we will start to call `requestImageWithURL:options:context:progress:completed:`.
@param url The image URL to be loaded.
@param options A mask to specify options to use for this request
@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 YES to continue download, NO to stop download.
*/
- (BOOL)canRequestImageForURL:(nullable NSURL *)url
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context;
@required
/**
Load the image and image data with the given URL and return the image data. You're responsible for producing the image instance.
......@@ -96,6 +125,22 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NS
@return Whether to block this url or not. Return YES to mark this URL as failed.
*/
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error;
error:(nonnull NSError *)error API_DEPRECATED("Use shouldBlockFailedURLWithURL:error:options:context: 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));
@optional
/**
Whether the error from image loader should be marked indeed un-recoverable or not, with associated options and context.
If this return YES, failed URL which does not using `SDWebImageRetryFailed` will be blocked into black list. Else not.
@param url The URL represent the image. Note this may not be a HTTP URL
@param error The URL's loading error, from previous `requestImageWithURL:options:context:progress:completed:` completedBlock's error.
@param options A mask to specify options to use for this request
@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 Whether to block this url or not. Return YES to mark this URL as failed.
*/
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context;
@end
......@@ -13,10 +13,23 @@
#import "SDAnimatedImage.h"
#import "UIImage+Metadata.h"
#import "SDInternalMacros.h"
#import "SDImageCacheDefine.h"
#import "objc/runtime.h"
SDWebImageContextOption const SDWebImageContextLoaderCachedImage = @"loaderCachedImage";
static void * SDImageLoaderProgressiveCoderKey = &SDImageLoaderProgressiveCoderKey;
id<SDProgressiveImageCoder> SDImageLoaderGetProgressiveCoder(id<SDWebImageOperation> operation) {
NSCParameterAssert(operation);
return objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey);
}
void SDImageLoaderSetProgressiveCoder(id<SDWebImageOperation> operation, id<SDProgressiveImageCoder> progressiveCoder) {
NSCParameterAssert(operation);
objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NSURL * _Nonnull imageURL, SDWebImageOptions options, SDWebImageContext * _Nullable context) {
NSCParameterAssert(imageData);
NSCParameterAssert(imageURL);
......@@ -29,28 +42,9 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
} else {
cacheKey = imageURL.absoluteString;
}
SDImageCoderOptions *coderOptions = SDGetDecodeOptionsFromContext(context, options, cacheKey);
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
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];
CGFloat scale = [coderOptions[SDImageCoderDecodeScaleFactor] doubleValue];
// Grab the image coder
id<SDImageCoder> imageCoder;
......@@ -94,6 +88,8 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
}
return image;
......@@ -136,7 +132,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
// Grab the progressive image coder
id<SDProgressiveImageCoder> progressiveCoder = objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey);
id<SDProgressiveImageCoder> progressiveCoder = SDImageLoaderGetProgressiveCoder(operation);
if (!progressiveCoder) {
id<SDProgressiveImageCoder> imageCoder = context[SDWebImageContextImageCoder];
// Check the progressive coder if provided
......@@ -152,7 +148,7 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
}
}
}
objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
SDImageLoaderSetProgressiveCoder(operation, progressiveCoder);
}
// If we can't find any progressive coder, disable progressive download
if (!progressiveCoder) {
......@@ -190,11 +186,11 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
if (shouldDecode) {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
// mark the image as progressive (completionBlock one are not mark as progressive)
image.sd_isIncremental = YES;
// mark the image as progressive (completed one are not mark as progressive)
image.sd_isIncremental = !finished;
// assign the decode options, to let manager check whether to re-decode if needed
image.sd_decodeOptions = coderOptions;
}
return image;
}
SDWebImageContextOption const SDWebImageContextLoaderCachedImage = @"loaderCachedImage";
......@@ -12,13 +12,12 @@
@interface SDImageLoadersManager ()
@property (nonatomic, strong, nonnull) dispatch_semaphore_t loadersLock;
@property (nonatomic, strong, nonnull) NSMutableArray<id<SDImageLoader>> *imageLoaders;
@end
@implementation SDImageLoadersManager
{
NSMutableArray<id<SDImageLoader>>* _imageLoaders;
@implementation SDImageLoadersManager {
SD_LOCK_DECLARE(_loadersLock);
}
+ (SDImageLoadersManager *)sharedManager {
......@@ -35,25 +34,25 @@
if (self) {
// initialize with default image loaders
_imageLoaders = [NSMutableArray arrayWithObject:[SDWebImageDownloader sharedDownloader]];
_loadersLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_loadersLock);
}
return self;
}
- (NSArray<id<SDImageLoader>> *)loaders {
SD_LOCK(self.loadersLock);
SD_LOCK(_loadersLock);
NSArray<id<SDImageLoader>>* loaders = [_imageLoaders copy];
SD_UNLOCK(self.loadersLock);
SD_UNLOCK(_loadersLock);
return loaders;
}
- (void)setLoaders:(NSArray<id<SDImageLoader>> *)loaders {
SD_LOCK(self.loadersLock);
SD_LOCK(_loadersLock);
[_imageLoaders removeAllObjects];
if (loaders.count) {
[_imageLoaders addObjectsFromArray:loaders];
}
SD_UNLOCK(self.loadersLock);
SD_UNLOCK(_loadersLock);
}
#pragma mark - Loader Property
......@@ -62,27 +61,37 @@
if (![loader conformsToProtocol:@protocol(SDImageLoader)]) {
return;
}
SD_LOCK(self.loadersLock);
SD_LOCK(_loadersLock);
[_imageLoaders addObject:loader];
SD_UNLOCK(self.loadersLock);
SD_UNLOCK(_loadersLock);
}
- (void)removeLoader:(id<SDImageLoader>)loader {
if (![loader conformsToProtocol:@protocol(SDImageLoader)]) {
return;
}
SD_LOCK(self.loadersLock);
SD_LOCK(_loadersLock);
[_imageLoaders removeObject:loader];
SD_UNLOCK(self.loadersLock);
SD_UNLOCK(_loadersLock);
}
#pragma mark - SDImageLoader
- (BOOL)canRequestImageForURL:(nullable NSURL *)url {
return [self canRequestImageForURL:url options:0 context:nil];
}
- (BOOL)canRequestImageForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
NSArray<id<SDImageLoader>> *loaders = self.loaders;
for (id<SDImageLoader> loader in loaders.reverseObjectEnumerator) {
if ([loader canRequestImageForURL:url]) {
return YES;
if ([loader respondsToSelector:@selector(canRequestImageForURL:options:context:)]) {
if ([loader canRequestImageForURL:url options:options context:context]) {
return YES;
}
} else {
if ([loader canRequestImageForURL:url]) {
return YES;
}
}
}
return NO;
......
......@@ -13,12 +13,15 @@
static void * SDMemoryCacheContext = &SDMemoryCacheContext;
@interface SDMemoryCache <KeyType, ObjectType> ()
@interface SDMemoryCache <KeyType, ObjectType> () {
#if SD_UIKIT
SD_LOCK_DECLARE(_weakCacheLock); // a lock to keep the access to `weakCache` thread-safe
#endif
}
@property (nonatomic, strong, nullable) SDImageCacheConfig *config;
#if SD_UIKIT
@property (nonatomic, strong, nonnull) NSMapTable<KeyType, ObjectType> *weakCache; // strong-weak cache
@property (nonatomic, strong, nonnull) dispatch_semaphore_t weakCacheLock; // a lock to keep the access to `weakCache` thread-safe
#endif
@end
......@@ -61,7 +64,7 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
#if SD_UIKIT
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_weakCacheLock);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
......@@ -85,9 +88,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
}
if (key && obj) {
// Store weak cache
SD_LOCK(self.weakCacheLock);
SD_LOCK(_weakCacheLock);
[self.weakCache setObject:obj forKey:key];
SD_UNLOCK(self.weakCacheLock);
SD_UNLOCK(_weakCacheLock);
}
}
......@@ -98,9 +101,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
}
if (key && !obj) {
// Check weak cache
SD_LOCK(self.weakCacheLock);
SD_LOCK(_weakCacheLock);
obj = [self.weakCache objectForKey:key];
SD_UNLOCK(self.weakCacheLock);
SD_UNLOCK(_weakCacheLock);
if (obj) {
// Sync cache
NSUInteger cost = 0;
......@@ -120,9 +123,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
}
if (key) {
// Remove weak cache
SD_LOCK(self.weakCacheLock);
SD_LOCK(_weakCacheLock);
[self.weakCache removeObjectForKey:key];
SD_UNLOCK(self.weakCacheLock);
SD_UNLOCK(_weakCacheLock);
}
}
......@@ -132,9 +135,9 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
return;
}
// Manually remove should also remove weak cache
SD_LOCK(self.weakCacheLock);
SD_LOCK(_weakCacheLock);
[self.weakCache removeAllObjects];
SD_UNLOCK(self.weakCacheLock);
SD_UNLOCK(_weakCacheLock);
}
#endif
......
......@@ -105,6 +105,8 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
/**
* By default, placeholder images are loaded while the image is loading. This flag will delay the loading
* of the placeholder image until after the image has finished loading.
* @note This is used to treate placeholder as an **Error Placeholder** but not **Loading Placeholder** by defaults. if the image loading is cancelled or error, the placeholder will be always set.
* @note Therefore, if you want both **Error Placeholder** and **Loading Placeholder** exist, use `SDWebImageAvoidAutoSetImage` to manually set the two placeholders and final loaded image by your hand depends on loading result.
*/
SDWebImageDelayPlaceholder = 1 << 8,
......@@ -275,18 +277,23 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextStoreC
/**
The same behavior like `SDWebImageContextQueryCacheType`, but control the query cache type for the original image when you use image transformer feature. This allows the detail control of cache query for these two images. For example, if you want to query the transformed image from both memory/disk cache, query the original image from disk cache only, use `[.queryCacheType : .all, .originalQueryCacheType : .disk]`
If not provide or the value is invalid, we will use `SDImageCacheTypeNone`, which does not query the original image from cache. (NSNumber)
If not provide or the value is invalid, we will use `SDImageCacheTypeDisk`, which query the original full image data from disk cache after transformed image cache miss. This is suitable for most common cases to avoid re-downloading the full data for different transform variants. (NSNumber)
@note Which means, if you set this value to not be `.none`, we will query the original image from cache, then do transform with transformer, instead of actual downloading, which can save bandwidth usage.
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalQueryCacheType;
/**
The same behavior like `SDWebImageContextStoreCacheType`, but control the store cache type for the original image when you use image transformer feature. This allows the detail control of cache storage for these two images. For example, if you want to store the transformed image into both memory/disk cache, store the original image into disk cache only, use `[.storeCacheType : .all, .originalStoreCacheType : .disk]`
If not provide or the value is invalid, we will use `SDImageCacheTypeNone`, which does not store the original image into cache. (NSNumber)
If not provide or the value is invalid, we will use `SDImageCacheTypeDisk`, which store the original full image data into disk cache after storing the transformed image. This is suitable for most common cases to avoid re-downloading the full data for different transform variants. (NSNumber)
@note This only store the original image, if you want to use the original image without downloading in next query, specify `SDWebImageContextOriginalQueryCacheType` as well.
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalStoreCacheType;
/**
A id<SDImageCache> instance which conforms to `SDImageCache` protocol. It's used to control the cache for original image when using the transformer. If you provide one, the original image (full size image) will query and write from that cache instance instead, the transformed image will query and write from the default `SDWebImageContextImageCache` instead. (id<SDImageCache>)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextOriginalImageCache;
/**
A Class object which the instance is a `UIImage/NSImage` subclass and adopt `SDAnimatedImage` protocol. We will call `initWithData:scale:options:` to create the instance (or `initWithAnimatedCoder:scale:` when using progressive download) . If the instance create failed, fallback to normal `UIImage/NSImage`.
This can be used to improve animated images rendering performance (especially memory usage on big animated images) with `SDAnimatedImageView` (Class).
......
......@@ -28,7 +28,13 @@ inline CGFloat SDImageScaleFactorForKey(NSString * _Nullable key) {
#elif SD_UIKIT
if ([[UIScreen mainScreen] respondsToSelector:@selector(scale)])
#elif SD_MAC
if ([[NSScreen mainScreen] respondsToSelector:@selector(backingScaleFactor)])
NSScreen *mainScreen = nil;
if (@available(macOS 10.12, *)) {
mainScreen = [NSScreen mainScreen];
} else {
mainScreen = [NSScreen screens].firstObject;
}
if ([mainScreen respondsToSelector:@selector(backingScaleFactor)])
#endif
{
// a@2x.png -> 8
......@@ -131,6 +137,7 @@ SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType
SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType";
SDWebImageContextOption const SDWebImageContextOriginalQueryCacheType = @"originalQueryCacheType";
SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType";
SDWebImageContextOption const SDWebImageContextOriginalImageCache = @"originalImageCache";
SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass";
SDWebImageContextOption const SDWebImageContextDownloadRequestModifier = @"downloadRequestModifier";
SDWebImageContextOption const SDWebImageContextDownloadResponseModifier = @"downloadResponseModifier";
......
......@@ -157,7 +157,7 @@ typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;
/**
* Set the response modifier to modify the original download response during image load.
* This request modifier method will be called for each downloading image response. Return the original response means no modification. Return nil will mark current download as cancelled.
* This response modifier method will be called for each downloading image response. Return the original response means no modification. Return nil will mark current download as cancelled.
* Defaults to nil, means does not modify the original download response.
* @note If you want to modify single response, consider using `SDWebImageContextDownloadResponseModifier` context option.
*/
......
......@@ -40,15 +40,16 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
@property (strong, nonatomic, nonnull) NSOperationQueue *downloadQueue;
@property (strong, nonatomic, nonnull) NSMutableDictionary<NSURL *, NSOperation<SDWebImageDownloaderOperation> *> *URLOperations;
@property (strong, nonatomic, nullable) NSMutableDictionary<NSString *, NSString *> *HTTPHeaders;
@property (strong, nonatomic, nonnull) dispatch_semaphore_t HTTPHeadersLock; // A lock to keep the access to `HTTPHeaders` thread-safe
@property (strong, nonatomic, nonnull) dispatch_semaphore_t operationsLock; // A lock to keep the access to `URLOperations` thread-safe
// The session in which data tasks will run
@property (strong, nonatomic) NSURLSession *session;
@end
@implementation SDWebImageDownloader
@implementation SDWebImageDownloader {
SD_LOCK_DECLARE(_HTTPHeadersLock); // A lock to keep the access to `HTTPHeaders` thread-safe
SD_LOCK_DECLARE(_operationsLock); // A lock to keep the access to `URLOperations` thread-safe
}
+ (void)initialize {
// Bind SDNetworkActivityIndicator if available (download it here: http://github.com/rs/SDNetworkActivityIndicator )
......@@ -120,8 +121,8 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
}
headerDictionary[@"Accept"] = @"image/*,*/*;q=0.8";
_HTTPHeaders = headerDictionary;
_HTTPHeadersLock = dispatch_semaphore_create(1);
_operationsLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_HTTPHeadersLock);
SD_LOCK_INIT(_operationsLock);
NSURLSessionConfiguration *sessionConfiguration = _config.sessionConfiguration;
if (!sessionConfiguration) {
sessionConfiguration = [NSURLSessionConfiguration defaultSessionConfiguration];
......@@ -139,11 +140,12 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
}
- (void)dealloc {
[self.session invalidateAndCancel];
self.session = nil;
[self.downloadQueue cancelAllOperations];
[self.config removeObserver:self forKeyPath:NSStringFromSelector(@selector(maxConcurrentDownloads)) context:SDWebImageDownloaderContext];
// Invalide the URLSession after all operations been cancelled
[self.session invalidateAndCancel];
self.session = nil;
}
- (void)invalidateSessionAndCancel:(BOOL)cancelPendingOperations {
......@@ -161,18 +163,18 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
if (!field) {
return;
}
SD_LOCK(self.HTTPHeadersLock);
SD_LOCK(_HTTPHeadersLock);
[self.HTTPHeaders setValue:value forKey:field];
SD_UNLOCK(self.HTTPHeadersLock);
SD_UNLOCK(_HTTPHeadersLock);
}
- (nullable NSString *)valueForHTTPHeaderField:(nullable NSString *)field {
if (!field) {
return nil;
}
SD_LOCK(self.HTTPHeadersLock);
SD_LOCK(_HTTPHeadersLock);
NSString *value = [self.HTTPHeaders objectForKey:field];
SD_UNLOCK(self.HTTPHeadersLock);
SD_UNLOCK(_HTTPHeadersLock);
return value;
}
......@@ -202,14 +204,14 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
return nil;
}
SD_LOCK(self.operationsLock);
SD_LOCK(_operationsLock);
id downloadOperationCancelToken;
NSOperation<SDWebImageDownloaderOperation> *operation = [self.URLOperations objectForKey:url];
// There is a case that the operation may be marked as finished or cancelled, but not been removed from `self.URLOperations`.
if (!operation || operation.isFinished || operation.isCancelled) {
operation = [self createDownloaderOperationWithUrl:url options:options context:context];
if (!operation) {
SD_UNLOCK(self.operationsLock);
SD_UNLOCK(_operationsLock);
if (completedBlock) {
NSError *error = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadOperation userInfo:@{NSLocalizedDescriptionKey : @"Downloader operation is nil"}];
completedBlock(nil, nil, error, YES);
......@@ -222,9 +224,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
if (!self) {
return;
}
SD_LOCK(self.operationsLock);
SD_LOCK(self->_operationsLock);
[self.URLOperations removeObjectForKey:url];
SD_UNLOCK(self.operationsLock);
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.
......@@ -248,7 +250,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
}
}
}
SD_UNLOCK(self.operationsLock);
SD_UNLOCK(_operationsLock);
SDWebImageDownloadToken *token = [[SDWebImageDownloadToken alloc] initWithDownloadOperation:operation];
token.url = url;
......@@ -271,9 +273,9 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:url cachePolicy:cachePolicy timeoutInterval:timeoutInterval];
mutableRequest.HTTPShouldHandleCookies = SD_OPTIONS_CONTAINS(options, SDWebImageDownloaderHandleCookies);
mutableRequest.HTTPShouldUsePipelining = YES;
SD_LOCK(self.HTTPHeadersLock);
SD_LOCK(_HTTPHeadersLock);
mutableRequest.allHTTPHeaderFields = self.HTTPHeaders;
SD_UNLOCK(self.HTTPHeadersLock);
SD_UNLOCK(_HTTPHeadersLock);
// Context Option
SDWebImageMutableContext *mutableContext;
......@@ -347,6 +349,14 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
operation.minimumProgressInterval = MIN(MAX(self.config.minimumProgressInterval, 0), 1);
}
if ([operation respondsToSelector:@selector(setAcceptableStatusCodes:)]) {
operation.acceptableStatusCodes = self.config.acceptableStatusCodes;
}
if ([operation respondsToSelector:@selector(setAcceptableContentTypes:)]) {
operation.acceptableContentTypes = self.config.acceptableContentTypes;
}
if (options & SDWebImageDownloaderHighPriority) {
operation.queuePriority = NSOperationQueuePriorityHigh;
} else if (options & SDWebImageDownloaderLowPriority) {
......@@ -561,6 +571,10 @@ didReceiveResponse:(NSURLResponse *)response
@implementation SDWebImageDownloader (SDImageLoader)
- (BOOL)canRequestImageForURL:(NSURL *)url {
return [self canRequestImageForURL:url options:0 context:nil];
}
- (BOOL)canRequestImageForURL:(NSURL *)url options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
if (!url) {
return NO;
}
......@@ -596,6 +610,10 @@ didReceiveResponse:(NSURLResponse *)response
}
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error {
return [self shouldBlockFailedURLWithURL:url error:error options:0 context:nil];
}
- (BOOL)shouldBlockFailedURLWithURL:(NSURL *)url error:(NSError *)error options:(SDWebImageOptions)options context:(SDWebImageContext *)context {
BOOL shouldBlockFailedURL;
// Filter the error domain and check error codes
if ([error.domain isEqualToString:SDWebImageErrorDomain]) {
......
......@@ -95,4 +95,19 @@ typedef NS_ENUM(NSInteger, SDWebImageDownloaderExecutionOrder) {
*/
@property (nonatomic, copy, nullable) NSString *password;
/**
* Set the acceptable HTTP Response status code. The status code which beyond the range will mark the download operation failed.
* For example, if we config [200, 400) but server response is 503, the download will fail with error code `SDWebImageErrorInvalidDownloadStatusCode`.
* Defaults to [200,400). Nil means no validation at all.
*/
@property (nonatomic, copy, nullable) NSIndexSet *acceptableStatusCodes;
/**
* Set the acceptable HTTP Response content type. The content type beyond the set will mark the download operation failed.
* For example, if we config ["image/png"] but server response is "application/json", the download will fail with error code `SDWebImageErrorInvalidDownloadContentType`.
* Normally you don't need this for image format detection because we use image's data file signature magic bytes: https://en.wikipedia.org/wiki/List_of_file_signatures
* Defaults to nil. Nil means no validation at all.
*/
@property (nonatomic, copy, nullable) NSSet<NSString *> *acceptableContentTypes;
@end
......@@ -26,6 +26,7 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig;
_maxConcurrentDownloads = 6;
_downloadTimeout = 15.0;
_executionOrder = SDWebImageDownloaderFIFOExecutionOrder;
_acceptableStatusCodes = [NSIndexSet indexSetWithIndexesInRange:NSMakeRange(200, 100)];
}
return self;
}
......@@ -41,6 +42,8 @@ static SDWebImageDownloaderConfig * _defaultDownloaderConfig;
config.urlCredential = self.urlCredential;
config.username = self.username;
config.password = self.password;
config.acceptableStatusCodes = self.acceptableStatusCodes;
config.acceptableContentTypes = self.acceptableContentTypes;
return config;
}
......
......@@ -37,8 +37,12 @@
@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));
// These operation-level config was inherited from downloader. See `SDWebImageDownloaderConfig` for documentation.
@property (strong, nonatomic, nullable) NSURLCredential *credential;
@property (assign, nonatomic) double minimumProgressInterval;
@property (copy, nonatomic, nullable) NSIndexSet *acceptableStatusCodes;
@property (copy, nonatomic, nullable) NSSet<NSString *> *acceptableContentTypes;
@end
......@@ -85,6 +89,21 @@
*/
@property (assign, nonatomic) double minimumProgressInterval;
/**
* Set the acceptable HTTP Response status code. The status code which beyond the range will mark the download operation failed.
* For example, if we config [200, 400) but server response is 503, the download will fail with error code `SDWebImageErrorInvalidDownloadStatusCode`.
* Defaults to [200,400). Nil means no validation at all.
*/
@property (copy, nonatomic, nullable) NSIndexSet *acceptableStatusCodes;
/**
* Set the acceptable HTTP Response content type. The content type beyond the set will mark the download operation failed.
* For example, if we config ["image/png"] but server response is "application/json", the download will fail with error code `SDWebImageErrorInvalidDownloadContentType`.
* Normally you don't need this for image format detection because we use image's data file signature magic bytes: https://en.wikipedia.org/wiki/List_of_file_signatures
* Defaults to nil. Nil means no validation at all.
*/
@property (copy, nonatomic, nullable) NSSet<NSString *> *acceptableContentTypes;
/**
* The options for the receiver.
*/
......@@ -128,7 +147,7 @@
context:(nullable SDWebImageContext *)context NS_DESIGNATED_INITIALIZER;
/**
* Adds handlers for progress and completion. Returns a tokent that can be passed to -cancel: to cancel this set of
* Adds handlers for progress and completion. Returns a token that can be passed to -cancel: to cancel this set of
* callbacks.
*
* @param progressBlock the block executed when a new chunk of data arrives.
......
......@@ -11,8 +11,12 @@
FOUNDATION_EXPORT NSErrorDomain const _Nonnull SDWebImageErrorDomain;
/// The response instance for invalid download response (NSURLResponse *)
FOUNDATION_EXPORT NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadResponseKey;
/// The HTTP status code for invalid download response (NSNumber *)
FOUNDATION_EXPORT NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadStatusCodeKey;
/// The HTTP MIME content type for invalid download response (NSString *)
FOUNDATION_EXPORT NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadContentTypeKey;
/// SDWebImage error domain and codes
typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) {
......@@ -24,4 +28,5 @@ typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) {
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 failed.
SDWebImageErrorInvalidDownloadContentType = 2004, // The image download response a invalid content type. You can check the MIME content type in error's userInfo under `SDWebImageErrorDownloadContentTypeKey`
};
......@@ -10,4 +10,7 @@
#import "SDWebImageError.h"
NSErrorDomain const _Nonnull SDWebImageErrorDomain = @"SDWebImageErrorDomain";
NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadResponseKey = @"SDWebImageErrorDownloadResponseKey";
NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadStatusCodeKey = @"SDWebImageErrorDownloadStatusCodeKey";
NSErrorUserInfoKey const _Nonnull SDWebImageErrorDownloadContentTypeKey = @"SDWebImageErrorDownloadContentTypeKey";
......@@ -12,16 +12,7 @@
#if SD_MAC
#import <QuartzCore/QuartzCore.h>
#endif
#if SD_UIKIT
#if __IPHONE_13_0 || __TVOS_13_0 || __MAC_10_15
// Xcode 11
#else
// Supports Xcode 10 users, for those users, define these enum
static NSInteger UIActivityIndicatorViewStyleMedium = 100;
static NSInteger UIActivityIndicatorViewStyleLarge = 101;
#endif
#import <CoreImage/CIFilter.h>
#endif
#pragma mark - Activity Indicator
......
......@@ -11,8 +11,14 @@
/// A protocol represents cancelable operation.
@protocol SDWebImageOperation <NSObject>
/// Cancel the operation
- (void)cancel;
@optional
/// Whether the operation has been cancelled.
@property (nonatomic, assign, readonly, getter=isCancelled) BOOL cancelled;
@end
/// NSOperation conform to `SDWebImageOperation`.
......
......@@ -22,8 +22,8 @@
unsigned long _totalCount;
// Used to ensure NSPointerArray thread safe
dispatch_semaphore_t _prefetchOperationsLock;
dispatch_semaphore_t _loadOperationsLock;
SD_LOCK_DECLARE(_prefetchOperationsLock);
SD_LOCK_DECLARE(_loadOperationsLock);
}
@property (nonatomic, copy, readwrite) NSArray<NSURL *> *urls;
......@@ -268,8 +268,8 @@
- (instancetype)init {
self = [super init];
if (self) {
_prefetchOperationsLock = dispatch_semaphore_create(1);
_loadOperationsLock = dispatch_semaphore_create(1);
SD_LOCK_INIT(_prefetchOperationsLock);
SD_LOCK_INIT(_loadOperationsLock);
}
return self;
}
......
......@@ -89,6 +89,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)fadeTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCrossDissolve | UIViewAnimationOptionAllowUserInteraction;
#else
......@@ -103,6 +104,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromLeftTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromLeft | UIViewAnimationOptionAllowUserInteraction;
#else
......@@ -117,6 +119,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromRightTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromRight | UIViewAnimationOptionAllowUserInteraction;
#else
......@@ -131,6 +134,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromTopTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromTop | UIViewAnimationOptionAllowUserInteraction;
#else
......@@ -145,6 +149,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)flipFromBottomTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionFlipFromBottom | UIViewAnimationOptionAllowUserInteraction;
#else
......@@ -159,6 +164,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)curlUpTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCurlUp | UIViewAnimationOptionAllowUserInteraction;
#else
......@@ -173,6 +179,7 @@ CATransition * SDTransitionFromAnimationOptions(SDWebImageAnimationOptions optio
+ (SDWebImageTransition *)curlDownTransitionWithDuration:(NSTimeInterval)duration {
SDWebImageTransition *transition = [SDWebImageTransition new];
transition.duration = duration;
#if SD_UIKIT
transition.animationOptions = UIViewAnimationOptionTransitionCurlDown | UIViewAnimationOptionAllowUserInteraction;
#else
......
......@@ -9,12 +9,30 @@
#import "UIImage+ForceDecode.h"
#import "SDImageCoderHelper.h"
#import "objc/runtime.h"
#import "NSImage+Compatibility.h"
@implementation UIImage (ForceDecode)
- (BOOL)sd_isDecoded {
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_isDecoded));
return value.boolValue;
if (value != nil) {
return value.boolValue;
} else {
// Assume only CGImage based can use lazy decoding
CGImageRef cgImage = self.CGImage;
if (cgImage) {
CFStringRef uttype = CGImageGetUTType(self.CGImage);
if (uttype) {
// Only ImageIO can set `com.apple.ImageIO.imageSourceTypeIdentifier`
return NO;
} else {
// Thumbnail or CGBitmapContext drawn image
return YES;
}
}
}
// Assume others as non-decoded
return NO;
}
- (void)setSd_isDecoded:(BOOL)sd_isDecoded {
......
......@@ -20,7 +20,8 @@ FOUNDATION_STATIC_INLINE NSUInteger SDMemoryCacheCostForImage(UIImage *image) {
#if SD_MAC
frameCount = 1;
#elif SD_UIKIT || SD_WATCH
frameCount = image.images.count > 0 ? image.images.count : 1;
// Filter the same frame in `_UIAnimatedImage`.
frameCount = image.images.count > 1 ? [NSSet setWithArray:image.images].count : 1;
#endif
NSUInteger cost = bytesPerFrame * frameCount;
return cost;
......
......@@ -8,6 +8,7 @@
#import "SDWebImageCompat.h"
#import "NSData+ImageContentType.h"
#import "SDImageCoder.h"
/**
UIImage category for image metadata, including animation, loop count, format, incremental, etc.
......@@ -20,12 +21,23 @@
* For animated image format, 0 means infinite looping.
* Note that because of the limitations of categories this property can get out of sync if you create another instance with CGImage or other methods.
* AppKit:
* NSImage currently only support animated via GIF imageRep unlike UIImage.
* The getter of this property will get the loop count from GIF imageRep
* The setter of this property will set the loop count from GIF imageRep
* NSImage currently only support animated via `NSBitmapImageRep`(GIF) or `SDAnimatedImageRep`(APNG/GIF/WebP) unlike UIImage.
* The getter of this property will get the loop count from animated imageRep
* The setter of this property will set the loop count from animated imageRep
*/
@property (nonatomic, assign) NSUInteger sd_imageLoopCount;
/**
* UIKit:
* Returns the `images`'s count by unapply the patch for the different frame durations. Which matches the real visible frame count when displaying on UIImageView.
* See more in `SDImageCoderHelper.animatedImageWithFrames`.
* Returns 1 for static image.
* AppKit:
* Returns the underlaying `NSBitmapImageRep` or `SDAnimatedImageRep` frame count.
* Returns 1 for static image.
*/
@property (nonatomic, assign, readonly) NSUInteger sd_imageFrameCount;
/**
* UIKit:
* Check the `images` array property.
......@@ -54,4 +66,12 @@
*/
@property (nonatomic, assign) BOOL sd_isIncremental;
/**
A dictionary value contains the decode options when decoded from SDWebImage loading system (say, `SDImageCacheDecodeImageData/SDImageLoaderDecode[Progressive]ImageData`)
It may not always available and only image decoding related options will be saved. (including [.decodeScaleFactor, .decodeThumbnailPixelSize, .decodePreserveAspectRatio, .decodeFirstFrameOnly])
@note This is used to identify and check the image from downloader when multiple different request (which want different image thumbnail size, image class, etc) share the same URLOperation.
@warning This API exist only because of current SDWebImageDownloader bad design which does not callback the context we call it. There will be refactory in future (API break) and you SHOULD NOT rely on this property at all.
*/
@property (nonatomic, copy) SDImageCoderOptions *sd_decodeOptions;
@end
......@@ -29,6 +29,32 @@
objc_setAssociatedObject(self, @selector(sd_imageLoopCount), value, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (NSUInteger)sd_imageFrameCount {
NSArray<UIImage *> *animatedImages = self.images;
if (!animatedImages || animatedImages.count <= 1) {
return 1;
}
NSNumber *value = objc_getAssociatedObject(self, @selector(sd_imageFrameCount));
if ([value isKindOfClass:[NSNumber class]]) {
return [value unsignedIntegerValue];
}
__block NSUInteger frameCount = 1;
__block UIImage *previousImage = animatedImages.firstObject;
[animatedImages enumerateObjectsUsingBlock:^(UIImage * _Nonnull image, NSUInteger idx, BOOL * _Nonnull stop) {
// ignore first
if (idx == 0) {
return;
}
if (![image isEqual:previousImage]) {
frameCount++;
}
previousImage = image;
}];
objc_setAssociatedObject(self, @selector(sd_imageFrameCount), @(frameCount), OBJC_ASSOCIATION_RETAIN_NONATOMIC);
return frameCount;
}
- (BOOL)sd_isAnimated {
return (self.images != nil);
}
......@@ -87,6 +113,19 @@
}
}
- (NSUInteger)sd_imageFrameCount {
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
NSBitmapImageRep *bitmapImageRep;
if ([imageRep isKindOfClass:[NSBitmapImageRep class]]) {
bitmapImageRep = (NSBitmapImageRep *)imageRep;
}
if (bitmapImageRep) {
return [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
}
return 1;
}
- (BOOL)sd_isAnimated {
BOOL isAnimated = NO;
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
......@@ -127,10 +166,8 @@
return imageFormat;
}
// Check CGImage's UTType, may return nil for non-Image/IO based image
if (@available(iOS 9.0, tvOS 9.0, macOS 10.11, watchOS 2.0, *)) {
CFStringRef uttype = CGImageGetUTType(self.CGImage);
imageFormat = [NSData sd_imageFormatFromUTType:uttype];
}
CFStringRef uttype = CGImageGetUTType(self.CGImage);
imageFormat = [NSData sd_imageFormatFromUTType:uttype];
return imageFormat;
}
......@@ -147,4 +184,16 @@
return value.boolValue;
}
- (void)setSd_decodeOptions:(SDImageCoderOptions *)sd_decodeOptions {
objc_setAssociatedObject(self, @selector(sd_decodeOptions), sd_decodeOptions, OBJC_ASSOCIATION_COPY_NONATOMIC);
}
- (SDImageCoderOptions *)sd_decodeOptions {
SDImageCoderOptions *value = objc_getAssociatedObject(self, @selector(sd_decodeOptions));
if ([value isKindOfClass:NSDictionary.class]) {
return value;
}
return nil;
}
@end
......@@ -57,7 +57,89 @@ static inline CGRect SDCGRectFitWithScaleMode(CGRect rect, CGSize size, SDImageS
return rect;
}
static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitmapInfo) {
static inline UIColor * SDGetColorFromGrayscale(Pixel_88 pixel, CGBitmapInfo bitmapInfo) {
// Get alpha info, byteOrder info
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
CGFloat w = 0, a = 1;
BOOL byteOrderNormal = NO;
switch (byteOrderInfo) {
case kCGBitmapByteOrderDefault: {
byteOrderNormal = YES;
} break;
case kCGBitmapByteOrder32Little: {
} break;
case kCGBitmapByteOrder32Big: {
byteOrderNormal = YES;
} break;
default: break;
}
switch (alphaInfo) {
case kCGImageAlphaPremultipliedFirst:
case kCGImageAlphaFirst: {
if (byteOrderNormal) {
// AW
a = pixel[0] / 255.0;
w = pixel[1] / 255.0;
} else {
// WA
w = pixel[0] / 255.0;
a = pixel[1] / 255.0;
}
}
break;
case kCGImageAlphaPremultipliedLast:
case kCGImageAlphaLast: {
if (byteOrderNormal) {
// WA
w = pixel[0] / 255.0;
a = pixel[1] / 255.0;
} else {
// AW
a = pixel[0] / 255.0;
w = pixel[1] / 255.0;
}
}
break;
case kCGImageAlphaNone: {
// W
w = pixel[0] / 255.0;
}
break;
case kCGImageAlphaNoneSkipLast: {
if (byteOrderNormal) {
// WX
w = pixel[0] / 255.0;
} else {
// XW
a = pixel[1] / 255.0;
}
}
break;
case kCGImageAlphaNoneSkipFirst: {
if (byteOrderNormal) {
// XW
a = pixel[1] / 255.0;
} else {
// WX
a = pixel[0] / 255.0;
}
}
break;
case kCGImageAlphaOnly: {
// A
a = pixel[0] / 255.0;
}
break;
default:
break;
}
return [UIColor colorWithWhite:w alpha:a];
}
static inline UIColor * SDGetColorFromRGBA(Pixel_8888 pixel, CGBitmapInfo bitmapInfo) {
// Get alpha info, byteOrder info
CGImageAlphaInfo alphaInfo = bitmapInfo & kCGBitmapAlphaInfoMask;
CGBitmapInfo byteOrderInfo = bitmapInfo & kCGBitmapByteOrderMask;
......@@ -470,18 +552,34 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4);
CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, components);
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
return SDGetColorFromPixel(pixel, bitmapInfo);
// greyscale
if (components == 2) {
Pixel_88 pixel = {0};
CFDataGetBytes(data, range, pixel);
CFRelease(data);
CGImageRelease(imageRef);
// Convert to color
return SDGetColorFromGrayscale(pixel, bitmapInfo);
} else if (components == 3 || components == 4) {
// RGB/RGBA
Pixel_8888 pixel = {0};
CFDataGetBytes(data, range, pixel);
CFRelease(data);
CGImageRelease(imageRef);
// Convert to color
return SDGetColorFromRGBA(pixel, bitmapInfo);
} else {
NSLog(@"Unsupported components: %zu", components);
CFRelease(data);
CGImageRelease(imageRef);
return nil;
}
}
- (nullable NSArray<UIColor *> *)sd_colorsWithRect:(CGRect)rect {
......@@ -539,17 +637,32 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
// Convert to color
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
NSMutableArray<UIColor *> *colors = [NSMutableArray arrayWithCapacity:CGRectGetWidth(rect) * CGRectGetHeight(rect)];
for (size_t index = start; index < end; index += 4) {
for (size_t index = start; index < end; index += components) {
if (index >= row * bytesPerRow + col * components) {
// Index beyond the end of current row, go next row
row++;
index = row * bytesPerRow + CGRectGetMinX(rect) * components;
index -= 4;
index -= components;
continue;
}
Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], pixels[index+3]};
UIColor *color = SDGetColorFromPixel(pixel, bitmapInfo);
[colors addObject:color];
UIColor *color;
if (components == 2) {
Pixel_88 pixel = {pixels[index], pixel[index+1]};
color = SDGetColorFromGrayscale(pixel, bitmapInfo);
} else {
if (components == 3) {
Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], 0};
color = SDGetColorFromRGBA(pixel, bitmapInfo);
} else if (components == 4) {
Pixel_8888 pixel = {pixels[index], pixels[index+1], pixels[index+2], pixels[index+3]};
color = SDGetColorFromRGBA(pixel, bitmapInfo);
} else {
NSLog(@"Unsupported components: %zu", components);
}
}
if (color) {
[colors addObject:color];
}
}
CFRelease(data);
CGImageRelease(imageRef);
......@@ -609,7 +722,7 @@ static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer.
.version = 0,
.decode = NULL,
.renderingIntent = kCGRenderingIntentDefault
.renderingIntent = CGImageGetRenderingIntent(imageRef)
};
vImage_Error err;
......
......@@ -71,14 +71,15 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
* block is called a last time with the full image and the last parameter set to YES.
*
* The last parameter is the original image URL
* @return The returned operation for cancelling cache and download operation, typically type is `SDWebImageCombinedOperation`
*/
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
- (nullable id<SDWebImageOperation>)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock;
/**
* Cancel the current image load
......
......@@ -12,6 +12,7 @@
#import "SDWebImageError.h"
#import "SDInternalMacros.h"
#import "SDWebImageTransitionInternal.h"
#import "SDImageCache.h"
const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
......@@ -46,13 +47,13 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
objc_setAssociatedObject(self, @selector(sd_imageProgress), sd_imageProgress, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
- (void)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
- (nullable id<SDWebImageOperation>)sd_internalSetImageWithURL:(nullable NSURL *)url
placeholderImage:(nullable UIImage *)placeholder
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
if (context) {
// copy to avoid mutable object
context = [context copy];
......@@ -71,12 +72,35 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
self.sd_imageURL = url;
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];
}
BOOL shouldUseWeakCache = NO;
if ([manager.imageCache isKindOfClass:SDImageCache.class]) {
shouldUseWeakCache = ((SDImageCache *)manager.imageCache).config.shouldUseWeakMemoryCache;
}
if (!(options & SDWebImageDelayPlaceholder)) {
if (shouldUseWeakCache) {
NSString *key = [manager cacheKeyForURL:url context:context];
// call memory cache to trigger weak cache sync logic, ignore the return value and go on normal query
// this unfortunately will cause twice memory cache query, but it's fast enough
// in the future the weak cache feature may be re-design or removed
[((SDImageCache *)manager.imageCache) imageFromMemoryCacheForKey:key];
}
dispatch_main_async_safe(^{
[self sd_setImage:placeholder imageData:nil basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:SDImageCacheTypeNone imageURL:url];
});
}
id <SDWebImageOperation> operation = nil;
if (url) {
// reset the progress
NSProgress *imageProgress = objc_getAssociatedObject(self, @selector(sd_imageProgress));
......@@ -90,15 +114,6 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
[self sd_startImageIndicator];
id<SDWebImageIndicator> 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) {
if (imageProgress) {
......@@ -122,7 +137,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
}
};
@weakify(self);
id <SDWebImageOperation> operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
operation = [manager loadImageWithURL:url options:options context:context progress:combinedProgressBlock completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
@strongify(self);
if (!self) { return; }
// if the progress not been updated, mark it to complete state
......@@ -141,7 +156,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
BOOL shouldCallCompletedBlock = finished || (options & SDWebImageAvoidAutoSetImage);
BOOL shouldNotSetImage = ((image && (options & SDWebImageAvoidAutoSetImage)) ||
(!image && !(options & SDWebImageDelayPlaceholder)));
SDWebImageNoParamsBlock callCompletedBlockClojure = ^{
SDWebImageNoParamsBlock callCompletedBlockClosure = ^{
if (!self) { return; }
if (!shouldNotSetImage) {
[self sd_setNeedsLayout];
......@@ -155,7 +170,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
// OR
// case 1b: we got no image and the SDWebImageDelayPlaceholder is not set
if (shouldNotSetImage) {
dispatch_main_async_safe(callCompletedBlockClojure);
dispatch_main_async_safe(callCompletedBlockClosure);
return;
}
......@@ -206,7 +221,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
#else
[self sd_setImage:targetImage imageData:targetData basedOnClassOrViaCustomSetImageBlock:setImageBlock cacheType:cacheType imageURL:imageURL];
#endif
callCompletedBlockClojure();
callCompletedBlockClosure();
});
}];
[self sd_setImageLoadOperation:operation forKey:validOperationKey];
......@@ -221,6 +236,8 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
}
});
}
return operation;
}
- (void)sd_cancelCurrentImageLoad {
......@@ -272,18 +289,20 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
#endif
if (transition) {
NSString *originalOperationKey = view.sd_latestOperationKey;
#if SD_UIKIT
[UIView transitionWithView:view duration:0 options:0 animations:^{
if (!view.sd_latestOperationKey) {
if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return;
}
// 0 duration to let UIKit render placeholder and prepares block
if (transition.prepares) {
transition.prepares(view, image, imageData, cacheType, imageURL);
}
} completion:^(BOOL finished) {
} completion:^(BOOL tempFinished) {
[UIView transitionWithView:view duration:transition.duration options:transition.animationOptions animations:^{
if (!view.sd_latestOperationKey) {
if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return;
}
if (finalSetImageBlock && !transition.avoidAutoSetImage) {
......@@ -293,7 +312,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
transition.animations(view, image);
}
} completion:^(BOOL finished) {
if (!view.sd_latestOperationKey) {
if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return;
}
if (transition.completion) {
......@@ -303,7 +322,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
}];
#elif SD_MAC
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull prepareContext) {
if (!view.sd_latestOperationKey) {
if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return;
}
// 0 duration to let AppKit render placeholder and prepares block
......@@ -313,7 +332,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
}
} completionHandler:^{
[NSAnimationContext runAnimationGroup:^(NSAnimationContext * _Nonnull context) {
if (!view.sd_latestOperationKey) {
if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return;
}
context.duration = transition.duration;
......@@ -337,7 +356,7 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
transition.animations(view, image);
}
} completionHandler:^{
if (!view.sd_latestOperationKey) {
if (!view.sd_latestOperationKey || ![originalOperationKey isEqualToString:view.sd_latestOperationKey]) {
return;
}
if (transition.completion) {
......
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment