Merge pull request #1 from SDWebImage/master

update the latest code
This commit is contained in:
Numb 2020-04-26 17:36:18 +08:00 committed by GitHub
commit 9e3bcf19e3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
153 changed files with 3838 additions and 673 deletions

2
.gitignore vendored
View File

@ -1,3 +1,5 @@
# OS X
.DS_Store
# Xcode
#
build/

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<Workspace
version = "1.0">
<FileRef
location = "self:">
</FileRef>
</Workspace>

View File

@ -1,3 +1,182 @@
## [5.7.3 - 5.7 Patch, on Apr 21st, 2020](https://github.com/SDWebImage/SDWebImage/releases/tag/5.7.3)
See [all tickets marked for the 5.7.3 release](https://github.com/SDWebImage/SDWebImage/milestone/70)
### Fixes
- Fix the encoding options which does not passthrough the correct value to ImageIO #2989
## [5.7.2 - 5.7 Patch, on Apr 11th, 2020](https://github.com/SDWebImage/SDWebImage/releases/tag/5.7.2)
See [all tickets marked for the 5.7.2 release](https://github.com/SDWebImage/SDWebImage/milestone/68)
### Fixes
- SDAnimatedImageView animation rendering should not use CGContext force decoding, use `kCGImageSourceShouldCacheImmediately` instead which can avoid OOM for large number of GIFs #2977
- Fix that when first play animated image and use maxBufferSize to 0, the calculation does not works (The CGImage is nil) #2982
### Project
- Rename the private header `UIColor+HexString` and `NSBezierPath+SDRoundedCorners` with SD prefix, to avoid the conflict when using CocoaPods #2983
## [5.7.1 - 5.7 Patch, on Apr 8th, 2020](https://github.com/SDWebImage/SDWebImage/releases/tag/5.7.1)
See [all tickets marked for the 5.7.1 release](https://github.com/SDWebImage/SDWebImage/milestone/67)
### Fixes
- Don't copy attributes from originalImage to transformedImage when caching transformedImage #2976. Thanks @bdaz
- Fix the wrong value assignment for SDAnimatedImageView code on macOS, warning #2974
## [5.7.0 - Query Cache Type and Encoding Options, on Apr 4th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.7.0)
See [all tickets marked for the 5.7.0 release](https://github.com/SDWebImage/SDWebImage/milestone/66)
### Features
#### Cache
- Added the async version API to query disk image data only
- Added the sync API to query disk image with context and options, which matches the async version
#### Coder
- Feature supports encoding options like max file size, max pixel size, as well as background color when using JPEG for alpha image #2972
- You can use `.encodeMaxFileSize` to limit the desired lossy file size, better than compression quality
- You can use `.encodeMaxPixelSize` to limit the pixel size, like thumbnail encoding
#### Transformer
- Refactory the current thumbnail && transformer about cache key. Developer should have the API to calculate the cache key from thumbnail or transformer, not hard-coded. #2966
#### Context Option
- Added new query cache type support, including the SDImageCache API and context option #2968
- You use `.queryCacheType` to query image from memory/disk/both cache during image pipeline loading
### Fixes
- Fix the issue for Carthage/SwiftPM framework version symbols, this should match the framework name SDWebImage, or will get a link error when used #2971 #2969
- Simplify the xattr helper method's code with modern Objective-C syntax #2967. Thanks @huangboju
### Changes
- Change the behavior to return the abstract type for unknown image format, this can solve the accident issue for custom coder who provide a new format #2973
## [5.6.1 - 5.6 Patch, on Mar 13th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.6.1)
See [all tickets marked for the 5.6.1 release](https://github.com/SDWebImage/SDWebImage/milestone/65)
### Performances
- Keep the progressive decoding process only exist one per image download. Cancel the unused progressive decoding when full pixel data is available. #2483
### Fixes
- Fix the NotificationCenter does not remove the observer and little private header garden #2959
## [5.6.0 - URLSession Metrics && Vector Format, on Mar 5th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.6.0)
See [all tickets marked for the 5.6.0 release](https://github.com/SDWebImage/SDWebImage/milestone/63)
### Features
#### URLSession Metrics
- Added the URLSessionTaskMetrics support for downloader && operation, which can be used for network metrics #2937
- Typically you use custom operation class to collect all metrics in your app. You can also collect metrics for single url request level. Check the #2937 example code to grab the download token and check metrics.
#### Vector Image
- Feature - better support for vector format detection, now PDF rasterized bitmap is built-in #2936
- Pass `.thumbnailPixelSize` to control the PDF bitmap size. If you want vector PDF rendering, you still need to use [SDWebImagePDFCoder](https://github.com/SDWebImage/SDWebImagePDFCoder).
- Vector image like SVG (via [SDWebImageSVGCoder](https://github.com/SDWebImage/SDWebImageSVGCoder)) and PDF (via [SDWebImagePDFCoder](https://github.com/SDWebImage/SDWebImagePDFCoder)), or system symbol images, can be detected by new API `sd_isVector`.
- Vector image does not pass to transformer by default, because they support dynamic size changing. Pass `.transformVectorImage` option to allow transformation.
#### Cache
- Add a better check to handle the cases when call `storeImage` without imageData #2953
- Which means, if you store image to disk without data, we will use extra information via `sd_imageFormat` or custom image class, to choose the the image format (including GIF and PDF) for encoding. Previously we only encode it into PNG or JPEG.
#### Context Option
- Feature add context option for cache, loader and coder, deprecated SDWebImageContextCustomManager #2955
- This makes it easy to use custom loader, cache, and decoder, without need to create a dummy SDWebImageManager instance.
### Fixes
- Fix the rare case when call `SDWebImageDownloaderOperation.cancel`, the completion block may callback twice #2954
### Warnings
- Suppress the deprecation warning when min deployment target version set to iOS 13+ or macCatalyst
- Complete all the SDWebImage error code with the localized description, make it easy for debugging #2948
## [5.5.2 - 5.5 Patch, on Jan 26th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.5.2)
See [all tickets marked for the 5.5.2 release](https://github.com/SDWebImage/SDWebImage/milestone/62)
### Fixes
- Fix the issue that `maxBufferSize` property does not correctly works for `SDAnimatedImageView` #2934
## [5.5.1 - 5.5 Patch, on Jan 18th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.5.1)
See [all tickets marked for the 5.5.1 release](https://github.com/SDWebImage/SDWebImage/milestone/59)
### Fixes
- Fix the SDAnimatedImageView's progressive animation bug, which reset the frame index to 0 each time new frames available #2931
## [5.5.0 - Thumbnail Decoding && Core Image, on Jan 16th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.5.0)
See [all tickets marked for the 5.5.0 release](https://github.com/SDWebImage/SDWebImage/milestone/55)
### Features
#### Thumbnail Decoding
- Supports to load the large web image with thumbnail, control the limit size and aspect ratio #2922 #2810
- Better than resize transformer, which does not allocate full pixel RAM and faster on CPU. If you've already use transformer to generate thumbnail, you'd better have a try
- Works for both animated images and progressive images, each frame using the thumbnail decoding
- Applies for Vector Format like SVG/PDF as well, see more in [Coder Plugin List](https://github.com/SDWebImage/SDWebImage/wiki/Coder-Plugin-List)
#### Core Image
- Support all transformer method on CIImage based UIImage/NSImage #2918
- For CIImage based UIImage/NSImage, using the CIFilter to take shortcut, which is faster and lazy (rasterize on demand)
#### Cache
- Support to use the creation date and the change date to determine the disk cache expire date compare #2915
### Performances
- Using UIGraphicsImageRenderer on iOS 10+, save memory when image bitmap is RGB(-25%) or Grayscale(-75%) #2907
- Provide the polyfill APIs for firmware iOS 10- and macOS. If you already use `SDGraphicsBeginImageContext` for drawing, you'd better replace that instead.
### Fixes
- Fix Gaussian Blur's bug which take half of the blur radius compared to the standard, should match Core Image's behavior #2927
## [5.4 Patch, on Jan 18th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.4.3)
See [all tickets marked for the 5.4.3 release](https://github.com/SDWebImage/SDWebImage/milestone/61)
### Fixes
- Fix the SDAnimatedImageView's progressive animation bug, which reset the frame index to 0 each time new frames available #2931
## [5.4.2 - 5.4 Patch, on Jan 7th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.4.2)
See [all tickets marked for the 5.4.2 release](https://github.com/SDWebImage/SDWebImage/milestone/58)
### Fixes
- SDAnimatedImage now only keep the animated coder when frame count >=1 , else we will behave like UIImage to save RAM usage #2924
## [5.4.1 - 5.4 Patch, on Dec 27th, 2019](https://github.com/rs/SDWebImage/releases/tag/5.4.1)
See [all tickets marked for the 5.4.1 release](https://github.com/SDWebImage/SDWebImage/milestone/56)
### Fixes
- Fix the issue that "There may be no complete callback when download the picture of the local path" #2917
## [5.4.0 Extended Cache Metadata, on Dec 6th, 2019](https://github.com/rs/SDWebImage/releases/tag/5.4.0)
See [all tickets marked for the 5.4.0 release](https://github.com/SDWebImage/SDWebImage/milestone/51)
### Features
#### Cache
- Allows advanced user to read/write extended metadata associated with image data from disk cache #2898
- This metadata will be processed at the same time when store or query the image. The metadata should conforms to `NSCoding` for archive and unarchive.
#### Manager
- Add `SDWebImageWaitStoreCache`, which wait for all the async disk cache written finished and then callback, useful for advanced user who want to touch the cache right in completion block #2900
### Fixes
- Using one global function to ensure we always sync all the UIImage category associated object status correctly inside our framework #2902
- Fix the thread safe issue with Downloader and DownloaderOperation during cancel #2903
## [5.3 Patch, on Jan 18th, 2020](https://github.com/rs/SDWebImage/releases/tag/5.3.4)
See [all tickets marked for the 5.3.4 release](https://github.com/SDWebImage/SDWebImage/milestone/60)
### Fixes
- Fix the SDAnimatedImageView's progressive animation bug, which reset the frame index to 0 each time new frames available #2931
## [5.3 Patch, on Dec 3rd, 2019](https://github.com/rs/SDWebImage/releases/tag/5.3.3)
See [all tickets marked for the 5.3.3 release](https://github.com/SDWebImage/SDWebImage/milestone/54)
### Fixes
- Fix the crash when using NSCache delegate with SDMemoryCache default implementation on dealloc #2899
## [5.3 Patch, on Nov 22nd, 2019](https://github.com/rs/SDWebImage/releases/tag/5.3.2)
See [all tickets marked for the 5.3.2 release](https://github.com/SDWebImage/SDWebImage/milestone/53)
### Fixes
- Fix animated image playback bugs that cause rendering frame is previous frame index #2895. Thanks @ZXIOU
## [5.3 Patch, on Nov 9th, 2019](https://github.com/rs/SDWebImage/releases/tag/5.3.1)
See [all tickets marked for the 5.3.1 release](https://github.com/SDWebImage/SDWebImage/milestone/52)

View File

@ -24,6 +24,8 @@
[self.imageView sd_setImageWithURL:self.imageURL
placeholderImage:nil
options:SDWebImageProgressiveLoad];
self.imageView.shouldCustomLoopCount = YES;
self.imageView.animationRepeatCount = 0;
}
- (void)viewDidLoad {

View File

@ -74,6 +74,8 @@
@"https://isparta.github.io/compare-webp/image/gif_webp/webp/2.webp",
@"https://nokiatech.github.io/heif/content/images/ski_jump_1440x960.heic",
@"https://nokiatech.github.io/heif/content/image_sequences/starfield_animation.heic",
@"https://s2.ax1x.com/2019/11/01/KHYIgJ.gif",
@"https://raw.githubusercontent.com/icons8/flat-color-icons/master/pdf/stack_of_photos.pdf",
@"https://nr-platform.s3.amazonaws.com/uploads/platform/published_extension/branding_icon/275/AmazonS3.png",
@"http://via.placeholder.com/200x200.jpg",
nil];
@ -112,9 +114,22 @@
}
cell.customTextLabel.text = [NSString stringWithFormat:@"Image #%ld", (long)indexPath.row];
[cell.customImageView sd_setImageWithURL:[NSURL URLWithString:self.objects[indexPath.row]]
placeholderImage:placeholderImage
options:indexPath.row == 0 ? SDWebImageRefreshCached : 0];
__weak SDAnimatedImageView *imageView = cell.customImageView;
[imageView sd_setImageWithURL:[NSURL URLWithString:self.objects[indexPath.row]]
placeholderImage:placeholderImage
options:indexPath.row == 0 ? SDWebImageRefreshCached : 0
context:@{SDWebImageContextImageThumbnailPixelSize : @(CGSizeMake(180, 120))}
progress:nil
completed:^(UIImage * _Nullable image, NSError * _Nullable error, SDImageCacheType cacheType, NSURL * _Nullable imageURL) {
SDWebImageCombinedOperation *operation = [imageView sd_imageLoadOperationForKey:imageView.sd_latestOperationKey];
SDWebImageDownloadToken *token = operation.loaderOperation;
if (@available(iOS 10.0, *)) {
NSURLSessionTaskMetrics *metrics = token.metrics;
if (metrics) {
printf("Metrics: %s download in (%f) seconds\n", [imageURL.absoluteString cStringUsingEncoding:NSUTF8StringEncoding], metrics.taskInterval.duration);
}
}
}];
return cell;
}

View File

@ -30,24 +30,18 @@ let package = Package(
.target(
name: "SDWebImage",
dependencies: [],
path: ".",
sources: ["SDWebImage/Core", "SDWebImage/Private"],
publicHeadersPath: "SDWebImage/Core",
path: "SDWebImage",
sources: ["Core", "Private"],
cSettings: [
.headerSearchPath("SDWebImage/Core"),
.headerSearchPath("SDWebImage/Private")
.headerSearchPath("Core"),
.headerSearchPath("Private")
]
),
.target(
name: "SDWebImageMapKit",
dependencies: ["SDWebImage"],
path: ".",
sources: ["SDWebImage/MapKit"],
publicHeadersPath: "SDWebImage/MapKit",
cSettings: [
.headerSearchPath("SDWebImage/Core"),
.headerSearchPath("SDWebImage/Private")
]
path: "SDWebImageMapKit",
sources: ["MapKit"]
)
]
)

View File

@ -19,8 +19,9 @@ This library provides an async image downloader with cache support. For convenie
- [x] Categories for `UIImageView`, `UIButton`, `MKAnnotationView` adding web image and cache management
- [x] An asynchronous image downloader
- [x] An asynchronous memory + disk image caching with automatic cache expiration handling
- [x] A background image decompression
- [x] Progressive image loading (including animated image, like GIF showing in Web browser)
- [x] A background image decompression to avoid frame rate drop
- [x] [Progressive image loading](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#progressive-animation) (including animated image, like GIF showing in Web browser)
- [x] [Thumbnail image decoding](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#thumbnail-decoding-550) to save CPU && Memory for large images
- [x] [Extendable image coder](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#custom-coder-420) to support massive image format, like WebP
- [x] [Full-stack solution for animated images](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) which keep a balance between CPU && Memory
- [x] [Customizable and composable transformations](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#transformer-50) can be applied to the images right after download
@ -36,11 +37,11 @@ This library provides an async image downloader with cache support. For convenie
## Supported Image Formats
- Image formats supported by UIImage (JPEG, PNG, HEIC, ...), including GIF/APNG/HEIC animation
- 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)
- 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
## Additional modules and Ecosystem
In order to keep SDWebImage focused and limited to the core features, but also allow extensibility and custom behaviors, during the 5.0 refactoring we focused on modularizing the library.
As such, we have moved/built new modules to [SDWebImage org](https://github.com/SDWebImage).
@ -48,29 +49,47 @@ As such, we have moved/built new modules to [SDWebImage org](https://github.com/
#### SwiftUI
[SwiftUI](https://developer.apple.com/xcode/swiftui/) is an innovative UI framework written in Swift to build user interfaces across all Apple platforms.
We support SwiftUI by building with the functions (caching, loading and animation) powered by SDWebImage. You can have a try with [SDWebImageSwiftUI](https://github.com/SDWebImage/SDWebImageSwiftUI)
We support SwiftUI by building a brand new framework called [SDWebImageSwiftUI](https://github.com/SDWebImage/SDWebImageSwiftUI), which is built on top of SDWebImage core functions (caching, loading and animation).
The new framework introduce two View structs `WebImage` and `AnimatedImage` for SwiftUI world, `ImageIndicator` modifier for any View, `ImageManager` observable object for data source. Supports iOS 13+/macOS 10.15+/tvOS 13+/watchOS 6+ and Swift 5.1. Have a nice try and provide feedback!
#### Coders for additional image formats
- [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) - coder for WebP image format. Based on [libwebp](https://chromium.googlesource.com/webm/libwebp)
- [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder) - coder to support HEIF image without Apple's `Image/IO framework`, iOS 8+/macOS 10.10+ support.
- [SDWebImageBPGCoder](https://github.com/SDWebImage/SDWebImageBPGCoder) - coder for BPG format
- [SDWebImageFLIFCoder](https://github.com/SDWebImage/SDWebImageFLIFCoder) - coder for FLIF format
- [SDWebImageAVIFCoder](https://github.com/SDWebImage/SDWebImageAVIFCoder) - coder for AVIF (AV1-based) format
- [SDWebImageWebPCoder](https://github.com/SDWebImage/SDWebImageWebPCoder) - coder for WebP format. Based on [libwebp](https://chromium.googlesource.com/webm/libwebp)
- [SDWebImageHEIFCoder](https://github.com/SDWebImage/SDWebImageHEIFCoder) - coder for HEIF format, iOS 8+/macOS 10.10+ support. Based on [libheif](https://github.com/strukturag/libheif)
- [SDWebImageBPGCoder](https://github.com/SDWebImage/SDWebImageBPGCoder) - coder for BPG format. Based on [libbpg](https://github.com/mirrorer/libbpg)
- [SDWebImageFLIFCoder](https://github.com/SDWebImage/SDWebImageFLIFCoder) - coder for FLIF format. Based on [libflif](https://github.com/FLIF-hub/FLIF)
- [SDWebImageAVIFCoder](https://github.com/SDWebImage/SDWebImageAVIFCoder) - coder for AVIF (AV1-based) format. Based on [libavif](https://github.com/AOMediaCodec/libavif)
- [SDWebImagePDFCoder](https://github.com/SDWebImage/SDWebImagePDFCoder) - coder for PDF vector format. Using built-in frameworks
- [SDWebImageSVGCoder](https://github.com/SDWebImage/SDWebImageSVGCoder) - coder for SVG vector format. Using built-in frameworks
- [SDWebImageLottieCoder](https://github.com/SDWebImage/SDWebImageLottieCoder) - coder for Lottie animation format. Based on [rlottie](https://github.com/Samsung/rlottie)
- and more from community!
#### Loaders
#### Custom Caches
- [SDWebImageYYPlugin](https://github.com/SDWebImage/SDWebImageYYPlugin) - plugin to support caching images with [YYCache](https://github.com/ibireme/YYCache)
- [SDWebImagePINPlugin](https://github.com/SDWebImage/SDWebImagePINPlugin) - plugin to support caching images with [PINCache](https://github.com/pinterest/PINCache)
#### Custom Loaders
- [SDWebImagePhotosPlugin](https://github.com/SDWebImage/SDWebImagePhotosPlugin) - plugin to support loading images from Photos (using `Photos.framework`)
- [SDWebImageLinkPlugin](https://github.com/SDWebImage/SDWebImageLinkPlugin) - plugin to support loading images from rich link url, as well as `LPLinkView` (using `LinkPresentation.framework`)
#### 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
- [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
#### Community driven popular libraries
- [FirebaseUI](https://github.com/firebase/FirebaseUI-iOS) - Firebase Storage binding for query images, based on SDWebImage loader system
- [react-native-fast-image](https://github.com/DylanVann/react-native-fast-image) - React Native fast image component, based on SDWebImage Animated Image solution
- [flutter_image_compress](https://github.com/OpenFlutter/flutter_image_compress) - Flutter compresses image plugin, based on SDWebImage WebP coder plugin
#### Make our lives easier
- [libwebp-Xcode](https://github.com/SDWebImage/libwebp-Xcode) - A wrapper for [libwebp](https://chromium.googlesource.com/webm/libwebp) + an Xcode project.
- [libheif-Xcode](https://github.com/SDWebImage/libheif-Xcode) - A wrapper for [libheif](https://github.com/strukturag/libheif) + an Xcode project.
- and more third-party C/C++ image codec libraries with CocoaPods/Carthage support.
- [libavif-Xcode](https://github.com/SDWebImage/libavif-Xcode) - A wrapper for [libavif](https://github.com/AOMediaCodec/libavif) + an Xcode project.
- and more third-party C/C++ image codec libraries with CocoaPods/Carthage/SwiftPM support.
You can use those directly, or create similar components of your own.
You can use those directly, or create similar components of your own, by using the customizable architecture of SDWebImage.
## Requirements
@ -108,11 +127,13 @@ You can use those directly, or create similar components of your own.
- If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/sdwebimage).
- If you **found a bug**, open an issue.
- If you **have a feature request**, open an issue.
- If you **need IRC channel**, use [Gitter](https://gitter.im/SDWebImage/community).
## Contribution
- If you **want to contribute**, read the [Contributing Guide](https://github.com/SDWebImage/SDWebImage/blob/master/.github/CONTRIBUTING.md)
- For **development contribution guide**, read the [How-To-Contribute](https://github.com/SDWebImage/SDWebImage/wiki/How-to-Contribute)
- For **understanding code architecture**, read the [Code Architecture Analysis](https://github.com/SDWebImage/SDWebImage/wiki/5.6-Code-Architecture-Analysis)
## How To Use
@ -138,7 +159,30 @@ imageView.sd_setImage(with: URL(string: "http://www.domain.com/path/to/image.jpg
## Animated Images (GIF) support
In 5.0, we introduced a brand new mechanism for supporting animated images. This includes animated image loading, rendering, decoding, and also supports customizations (for advanced users).
This animated image solution is available for `iOS`/`tvOS`/`macOS`. The `SDAnimatedImage` is subclass of `UIImage/NSImage`, and `SDAnimatedImageView` is subclass of `UIImageView/NSImageView`, to make them compatible with the common frameworks APIs. See [Animated Image](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for more detailed information.
This animated image solution is available for `iOS`/`tvOS`/`macOS`. The `SDAnimatedImage` is subclass of `UIImage/NSImage`, and `SDAnimatedImageView` is subclass of `UIImageView/NSImageView`, to make them compatible with the common frameworks APIs.
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.
See [Animated Image](https://github.com/SDWebImage/SDWebImage/wiki/Advanced-Usage#animated-image-50) for more detailed information.
* Objective-C
```objective-c
SDAnimatedImageView *imageView = [SDAnimatedImageView new];
SDAnimatedImage *animatedImage = [SDAnimatedImage imageNamed:@"image.gif"];
imageView.image = animatedImage;
```
* Swift
```swift
let imageView = SDAnimatedImageView()
let animatedImage = SDAnimatedImage(named: "image.gif")
imageView.image = animatedImage
```
#### FLAnimatedImage integration has its own dedicated repo
In order to clean up things and make our core project do less things, we decided that the `FLAnimatedImage` integration does not belong here. From 5.0, this will still be available, but under a dedicated repo [SDWebImageFLPlugin](https://github.com/SDWebImage/SDWebImageFLPlugin).
@ -240,6 +284,12 @@ In the source files where you need to use the library, import the umbrella heade
#import <SDWebImage/SDWebImage.h>
```
It's also recommend to use the module import syntax, available for CocoaPods(enable `modular_headers`)/Carthage/SwiftPM.
```objecitivec
@import SDWebImage;
```
### Build Project
At this point your workspace should build without error. If you are having problem, post to the Issue and the
@ -267,6 +317,8 @@ All source code is licensed under the [MIT License](https://github.com/SDWebImag
## Architecture
To learn about SDWebImage's architecture design for contribution, read [The Core of SDWebImage v5.6 Architecture](https://github.com/SDWebImage/SDWebImage/wiki/5.6-Code-Architecture-Analysis). Thanks @looseyi for the post and translation.
#### High Level Diagram
<p align="center" >
<img src="https://raw.githubusercontent.com/SDWebImage/SDWebImage/master/Docs/Diagrams/SDWebImageHighLevelDiagram.jpeg" title="SDWebImage high level diagram">

View File

@ -1,6 +1,6 @@
Pod::Spec.new do |s|
s.name = 'SDWebImage'
s.version = '5.3.1'
s.version = '5.7.3'
s.osx.deployment_target = '10.10'
s.ios.deployment_target = '8.0'
@ -33,7 +33,6 @@ Pod::Spec.new do |s|
s.subspec 'Core' do |core|
core.source_files = 'SDWebImage/Core/*.{h,m}', 'WebImage/SDWebImage.h', 'SDWebImage/Private/*.{h,m}'
core.exclude_files = 'SDWebImage/MapKit/*.{h,m}'
core.private_header_files = 'SDWebImage/Private/*.h'
end
@ -41,7 +40,7 @@ Pod::Spec.new do |s|
mk.osx.deployment_target = '10.10'
mk.ios.deployment_target = '8.0'
mk.tvos.deployment_target = '9.2'
mk.source_files = 'SDWebImage/MapKit/*.{h,m}'
mk.source_files = 'SDWebImageMapKit/MapKit/*.{h,m}'
mk.framework = 'MapKit'
mk.dependency 'SDWebImage/Core'
end

View File

@ -45,9 +45,16 @@
321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */ = {isa = PBXBuildFile; fileRef = 321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */; };
3237F9E820161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
3237F9EB20161AE000A88143 /* NSImage+Compatibility.m in Sources */ = {isa = PBXBuildFile; fileRef = 4397D2F51D0DE2DF00BB2784 /* NSImage+Compatibility.m */; };
3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */; };
3240BB6823968FE7003BA07D /* SDAssociatedObject.h in Headers */ = {isa = PBXBuildFile; fileRef = 3240BB6623968FE6003BA07D /* SDAssociatedObject.h */; settings = {ATTRIBUTES = (Private, ); }; };
3240BB6923968FE7003BA07D /* SDAssociatedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 3240BB6723968FE6003BA07D /* SDAssociatedObject.m */; };
3240BB6A23968FE7003BA07D /* SDAssociatedObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 3240BB6723968FE6003BA07D /* SDAssociatedObject.m */; };
3244062C2296C5F400A36084 /* SDWebImageOptionsProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */; settings = {ATTRIBUTES = (Public, ); }; };
3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */; };
3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = 3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */; };
3246A70323A567AC00FBEA10 /* SDGraphicsImageRenderer.h in Headers */ = {isa = PBXBuildFile; fileRef = 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */; settings = {ATTRIBUTES = (Public, ); }; };
3246A70423A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */; };
3246A70523A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */ = {isa = PBXBuildFile; fileRef = 3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */; };
3248475D201775F600AF9E5A /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32484757201775F600AF9E5A /* SDAnimatedImageView.m */; };
3248475F201775F600AF9E5A /* SDAnimatedImageView.m in Sources */ = {isa = PBXBuildFile; fileRef = 32484757201775F600AF9E5A /* SDAnimatedImageView.m */; };
32484765201775F600AF9E5A /* SDAnimatedImageView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 32484758201775F600AF9E5A /* SDAnimatedImageView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
@ -83,12 +90,18 @@
325C460F223394D8004CAE11 /* SDImageCachesManagerOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */; settings = {ATTRIBUTES = (Private, ); }; };
325C4610223394D8004CAE11 /* SDImageCachesManagerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */; };
325C4611223394D8004CAE11 /* SDImageCachesManagerOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */; };
325C46212233A02E004CAE11 /* UIColor+HexString.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C461E2233A02E004CAE11 /* UIColor+HexString.h */; settings = {ATTRIBUTES = (Private, ); }; };
325C46222233A02E004CAE11 /* UIColor+HexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C461F2233A02E004CAE11 /* UIColor+HexString.m */; };
325C46232233A02E004CAE11 /* UIColor+HexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C461F2233A02E004CAE11 /* UIColor+HexString.m */; };
325C46272233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */; settings = {ATTRIBUTES = (Private, ); }; };
325C46282233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */; };
325C46292233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */; };
325C46212233A02E004CAE11 /* UIColor+SDHexString.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */; settings = {ATTRIBUTES = (Private, ); }; };
325C46222233A02E004CAE11 /* UIColor+SDHexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C461F2233A02E004CAE11 /* UIColor+SDHexString.m */; };
325C46232233A02E004CAE11 /* UIColor+SDHexString.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C461F2233A02E004CAE11 /* UIColor+SDHexString.m */; };
325C46272233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h in Headers */ = {isa = PBXBuildFile; fileRef = 325C46242233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h */; settings = {ATTRIBUTES = (Private, ); }; };
325C46282233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m */; };
325C46292233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m in Sources */ = {isa = PBXBuildFile; fileRef = 325C46252233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m */; };
325F7CC623893B2E00AEDFCC /* SDFileAttributeHelper.h in Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */; settings = {ATTRIBUTES = (Private, ); }; };
325F7CC723893B2E00AEDFCC /* SDFileAttributeHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */; };
325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */; settings = {ATTRIBUTES = (Public, ); }; };
325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */; };
325F7CCC2389463D00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */ = {isa = PBXBuildFile; fileRef = 325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */; };
325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */; };
326E2F2E236F0B23006F847F /* SDAnimatedImagePlayer.h in Headers */ = {isa = PBXBuildFile; fileRef = 326E2F2C236F0B23006F847F /* SDAnimatedImagePlayer.h */; settings = {ATTRIBUTES = (Public, ); }; };
326E2F2F236F0B23006F847F /* SDAnimatedImagePlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */; };
326E2F30236F0B23006F847F /* SDAnimatedImagePlayer.m in Sources */ = {isa = PBXBuildFile; fileRef = 326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */; };
@ -99,6 +112,8 @@
327054D6206CD8B3006EA328 /* SDImageAPNGCoder.h in Headers */ = {isa = PBXBuildFile; fileRef = 327054D2206CD8B3006EA328 /* SDImageAPNGCoder.h */; settings = {ATTRIBUTES = (Public, ); }; };
327054DA206CD8B3006EA328 /* SDImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDImageAPNGCoder.m */; };
327054DC206CD8B3006EA328 /* SDImageAPNGCoder.m in Sources */ = {isa = PBXBuildFile; fileRef = 327054D3206CD8B3006EA328 /* SDImageAPNGCoder.m */; };
3287E6D1244C0C1400007311 /* MKAnnotationView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 3287E6CD244C0C1400007311 /* MKAnnotationView+WebCache.m */; };
3287E6D2244C0C1400007311 /* MKAnnotationView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 3287E6CE244C0C1400007311 /* MKAnnotationView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
328BB69E2081FED200760D6C /* SDWebImageCacheKeyFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 328BB69A2081FED200760D6C /* SDWebImageCacheKeyFilter.h */; settings = {ATTRIBUTES = (Public, ); }; };
328BB6A22081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */; };
328BB6A42081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */; };
@ -111,6 +126,7 @@
328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 328BB6BF2082581100760D6C /* SDMemoryCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
328BB6D32082581100760D6C /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB6C02082581100760D6C /* SDMemoryCache.m */; };
328BB6D52082581100760D6C /* SDMemoryCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 328BB6C02082581100760D6C /* SDMemoryCache.m */; };
328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */ = {isa = PBXBuildFile; fileRef = 3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */; };
3290FA061FA478AF0047D20C /* SDImageFrame.h in Headers */ = {isa = PBXBuildFile; fileRef = 3290FA021FA478AF0047D20C /* SDImageFrame.h */; settings = {ATTRIBUTES = (Public, ); }; };
3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDImageFrame.m */; };
3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */ = {isa = PBXBuildFile; fileRef = 3290FA031FA478AF0047D20C /* SDImageFrame.m */; };
@ -274,8 +290,6 @@
80B6DF822142B44400BCB334 /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */; };
80B6DF832142B44500BCB334 /* NSButton+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 321DB3602011D4D60015D2CB /* NSButton+WebCache.m */; };
80B6DF842142B44600BCB334 /* NSButton+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 321DB35F2011D4D60015D2CB /* NSButton+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
80B6DFA72142B71600BCB334 /* MKAnnotationView+WebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = 32FDE87A2088871B008D7530 /* MKAnnotationView+WebCache.m */; };
80B6DFCD2142B71600BCB334 /* MKAnnotationView+WebCache.h in Headers */ = {isa = PBXBuildFile; fileRef = 32FDE87B2088871B008D7530 /* MKAnnotationView+WebCache.h */; settings = {ATTRIBUTES = (Public, ); }; };
A18A6CC9172DC28500419892 /* UIImage+GIF.m in Sources */ = {isa = PBXBuildFile; fileRef = A18A6CC6172DC28500419892 /* UIImage+GIF.m */; };
AB615306192DA24600A2D8E9 /* UIView+WebCacheOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = AB615302192DA24600A2D8E9 /* UIView+WebCacheOperation.m */; };
ABBE71A818C43B4D00B75E91 /* UIImageView+HighlightedWebCache.m in Sources */ = {isa = PBXBuildFile; fileRef = ABBE71A618C43B4D00B75E91 /* UIImageView+HighlightedWebCache.m */; };
@ -298,6 +312,8 @@
dstPath = include/SDWebImage;
dstSubfolderSpec = 16;
files = (
328E9DE523A61DD30051C893 /* SDGraphicsImageRenderer.h in Copy Headers */,
325F7CCD2389467800AEDFCC /* UIImage+ExtendedCacheData.h in Copy Headers */,
326E2F36236F1E30006F847F /* SDAnimatedImagePlayer.h in Copy Headers */,
3250C9F12355E3DF0093A896 /* SDWebImageDownloaderDecryptor.h in Copy Headers */,
325427662355783C0042BAA4 /* SDWebImageDownloaderResponseModifier.h in Copy Headers */,
@ -379,8 +395,12 @@
321E60A11F38E8F600405457 /* SDImageGIFCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageGIFCoder.m; path = Core/SDImageGIFCoder.m; sourceTree = "<group>"; };
321E60BC1F38E91700405457 /* UIImage+ForceDecode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+ForceDecode.h"; path = "Core/UIImage+ForceDecode.h"; sourceTree = "<group>"; };
321E60BD1F38E91700405457 /* UIImage+ForceDecode.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ForceDecode.m"; path = "Core/UIImage+ForceDecode.m"; sourceTree = "<group>"; };
3240BB6623968FE6003BA07D /* SDAssociatedObject.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDAssociatedObject.h; sourceTree = "<group>"; };
3240BB6723968FE6003BA07D /* SDAssociatedObject.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDAssociatedObject.m; sourceTree = "<group>"; };
324406292296C5F400A36084 /* SDWebImageOptionsProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageOptionsProcessor.h; path = Core/SDWebImageOptionsProcessor.h; sourceTree = "<group>"; };
3244062A2296C5F400A36084 /* SDWebImageOptionsProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageOptionsProcessor.m; path = Core/SDWebImageOptionsProcessor.m; sourceTree = "<group>"; };
3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDGraphicsImageRenderer.h; path = Core/SDGraphicsImageRenderer.h; sourceTree = "<group>"; };
3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDGraphicsImageRenderer.m; path = Core/SDGraphicsImageRenderer.m; sourceTree = "<group>"; };
32484757201775F600AF9E5A /* SDAnimatedImageView.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImageView.m; path = Core/SDAnimatedImageView.m; sourceTree = "<group>"; };
32484758201775F600AF9E5A /* SDAnimatedImageView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "SDAnimatedImageView+WebCache.h"; path = "Core/SDAnimatedImageView+WebCache.h"; sourceTree = "<group>"; };
32484759201775F600AF9E5A /* SDAnimatedImageView.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImageView.h; path = Core/SDAnimatedImageView.h; sourceTree = "<group>"; };
@ -403,16 +423,22 @@
325C460722339426004CAE11 /* SDWeakProxy.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDWeakProxy.m; sourceTree = "<group>"; };
325C460C223394D8004CAE11 /* SDImageCachesManagerOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDImageCachesManagerOperation.h; sourceTree = "<group>"; };
325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDImageCachesManagerOperation.m; sourceTree = "<group>"; };
325C461E2233A02E004CAE11 /* UIColor+HexString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+HexString.h"; sourceTree = "<group>"; };
325C461F2233A02E004CAE11 /* UIColor+HexString.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+HexString.m"; sourceTree = "<group>"; };
325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSBezierPath+RoundedCorners.h"; sourceTree = "<group>"; };
325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSBezierPath+RoundedCorners.m"; sourceTree = "<group>"; };
325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "UIColor+SDHexString.h"; sourceTree = "<group>"; };
325C461F2233A02E004CAE11 /* UIColor+SDHexString.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "UIColor+SDHexString.m"; sourceTree = "<group>"; };
325C46242233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "NSBezierPath+SDRoundedCorners.h"; sourceTree = "<group>"; };
325C46252233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "NSBezierPath+SDRoundedCorners.m"; sourceTree = "<group>"; };
325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDFileAttributeHelper.h; sourceTree = "<group>"; };
325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDFileAttributeHelper.m; sourceTree = "<group>"; };
325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = "UIImage+ExtendedCacheData.h"; path = "Core/UIImage+ExtendedCacheData.h"; sourceTree = "<group>"; };
325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = "UIImage+ExtendedCacheData.m"; path = "Core/UIImage+ExtendedCacheData.m"; sourceTree = "<group>"; };
326E2F2C236F0B23006F847F /* SDAnimatedImagePlayer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDAnimatedImagePlayer.h; path = Core/SDAnimatedImagePlayer.h; sourceTree = "<group>"; };
326E2F2D236F0B23006F847F /* SDAnimatedImagePlayer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDAnimatedImagePlayer.m; path = Core/SDAnimatedImagePlayer.m; sourceTree = "<group>"; };
326E2F31236F1D58006F847F /* SDDeviceHelper.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDDeviceHelper.h; sourceTree = "<group>"; };
326E2F32236F1D58006F847F /* SDDeviceHelper.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDDeviceHelper.m; sourceTree = "<group>"; };
327054D2206CD8B3006EA328 /* SDImageAPNGCoder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDImageAPNGCoder.h; path = Core/SDImageAPNGCoder.h; sourceTree = "<group>"; };
327054D3206CD8B3006EA328 /* SDImageAPNGCoder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDImageAPNGCoder.m; path = Core/SDImageAPNGCoder.m; sourceTree = "<group>"; };
3287E6CD244C0C1400007311 /* MKAnnotationView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MKAnnotationView+WebCache.m"; sourceTree = "<group>"; };
3287E6CE244C0C1400007311 /* MKAnnotationView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MKAnnotationView+WebCache.h"; sourceTree = "<group>"; };
328BB69A2081FED200760D6C /* SDWebImageCacheKeyFilter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageCacheKeyFilter.h; path = Core/SDWebImageCacheKeyFilter.h; sourceTree = "<group>"; };
328BB69B2081FED200760D6C /* SDWebImageCacheKeyFilter.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDWebImageCacheKeyFilter.m; path = Core/SDWebImageCacheKeyFilter.m; sourceTree = "<group>"; };
328BB6A82081FEE500760D6C /* SDWebImageCacheSerializer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDWebImageCacheSerializer.h; path = Core/SDWebImageCacheSerializer.h; sourceTree = "<group>"; };
@ -456,8 +482,6 @@
32F7C06E2030114C00873181 /* SDImageTransformer.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDImageTransformer.m; path = Core/SDImageTransformer.m; sourceTree = "<group>"; };
32F7C07C2030719600873181 /* UIImage+Transform.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIImage+Transform.m"; path = "Core/UIImage+Transform.m"; sourceTree = "<group>"; };
32F7C07D2030719600873181 /* UIImage+Transform.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIImage+Transform.h"; path = "Core/UIImage+Transform.h"; sourceTree = "<group>"; };
32FDE87A2088871B008D7530 /* MKAnnotationView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "MKAnnotationView+WebCache.m"; sourceTree = "<group>"; };
32FDE87B2088871B008D7530 /* MKAnnotationView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "MKAnnotationView+WebCache.h"; sourceTree = "<group>"; };
4369C2751D9807EC007E863A /* UIView+WebCache.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "UIView+WebCache.h"; path = "SDWebImage/Core/UIView+WebCache.h"; sourceTree = "<group>"; };
4369C2761D9807EC007E863A /* UIView+WebCache.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = "UIView+WebCache.m"; path = "SDWebImage/Core/UIView+WebCache.m"; sourceTree = "<group>"; };
4397D2F41D0DE2DF00BB2784 /* NSImage+Compatibility.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = "NSImage+Compatibility.h"; path = "Core/NSImage+Compatibility.h"; sourceTree = "<group>"; };
@ -560,6 +584,8 @@
32CF1C061FA496B000004BD1 /* SDImageCoderHelper.m */,
3257EAF721898AED0097B271 /* SDImageGraphics.h */,
3257EAF821898AED0097B271 /* SDImageGraphics.m */,
3246A70123A567AC00FBEA10 /* SDGraphicsImageRenderer.h */,
3246A70223A567AC00FBEA10 /* SDGraphicsImageRenderer.m */,
);
name = Decoder;
sourceTree = "<group>";
@ -581,6 +607,23 @@
name = AnimatedImage;
sourceTree = "<group>";
};
3287E6C7244C0C1400007311 /* SDWebImageMapKit */ = {
isa = PBXGroup;
children = (
3287E6CC244C0C1400007311 /* MapKit */,
);
path = SDWebImageMapKit;
sourceTree = "<group>";
};
3287E6CC244C0C1400007311 /* MapKit */ = {
isa = PBXGroup;
children = (
3287E6CE244C0C1400007311 /* MKAnnotationView+WebCache.h */,
3287E6CD244C0C1400007311 /* MKAnnotationView+WebCache.m */,
);
path = MapKit;
sourceTree = "<group>";
};
328BB6972081FDAB00760D6C /* Manager */ = {
isa = PBXGroup;
children = (
@ -619,6 +662,8 @@
children = (
32B5CC5E222F89C2005EB74E /* SDAsyncBlockOperation.h */,
32B5CC5F222F89C2005EB74E /* SDAsyncBlockOperation.m */,
3240BB6623968FE6003BA07D /* SDAssociatedObject.h */,
3240BB6723968FE6003BA07D /* SDAssociatedObject.m */,
325C460622339426004CAE11 /* SDWeakProxy.h */,
325C460722339426004CAE11 /* SDWeakProxy.m */,
32E6730F235765B500DB4987 /* SDDisplayLink.h */,
@ -631,10 +676,12 @@
325C460D223394D8004CAE11 /* SDImageCachesManagerOperation.m */,
32C78E39233371AD00C6B7F8 /* SDImageIOAnimatedCoderInternal.h */,
32986560233737C70071958B /* SDImageHEICCoderInternal.h */,
325C461E2233A02E004CAE11 /* UIColor+HexString.h */,
325C461F2233A02E004CAE11 /* UIColor+HexString.m */,
325C46242233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h */,
325C46252233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m */,
325C461E2233A02E004CAE11 /* UIColor+SDHexString.h */,
325C461F2233A02E004CAE11 /* UIColor+SDHexString.m */,
325C46242233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h */,
325C46252233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m */,
325F7CC423893B2E00AEDFCC /* SDFileAttributeHelper.h */,
325F7CC523893B2E00AEDFCC /* SDFileAttributeHelper.m */,
329F123F223FAD3400B309FD /* SDInternalMacros.h */,
329F123E223FAD3400B309FD /* SDInternalMacros.m */,
329F1235223FAA3B00B309FD /* SDmetamacros.h */,
@ -642,15 +689,6 @@
path = Private;
sourceTree = "<group>";
};
32FDE8792088871B008D7530 /* MapKit */ = {
isa = PBXGroup;
children = (
32FDE87B2088871B008D7530 /* MKAnnotationView+WebCache.h */,
32FDE87A2088871B008D7530 /* MKAnnotationView+WebCache.m */,
);
path = MapKit;
sourceTree = "<group>";
};
4369C2851D9811BB007E863A /* WebCache Categories */ = {
isa = PBXGroup;
children = (
@ -692,6 +730,7 @@
children = (
EA9E0C6A2195936400AFB434 /* Configs */,
53922D74148C55820056699D /* SDWebImage */,
3287E6C7244C0C1400007311 /* SDWebImageMapKit */,
4A2CAE001AB4BB5300B6BC39 /* WebImage */,
53922D71148C55820056699D /* Frameworks */,
53922D70148C55820056699D /* Products */,
@ -734,7 +773,6 @@
53922DA9148C562D0056699D /* Categories */,
4369C2851D9811BB007E863A /* WebCache Categories */,
32B5CC5D222F89C2005EB74E /* Private */,
32FDE8792088871B008D7530 /* MapKit */,
);
path = SDWebImage;
sourceTree = "<group>";
@ -744,6 +782,8 @@
children = (
5D5B9140188EE8DD006D06BD /* NSData+ImageContentType.h */,
5D5B9141188EE8DD006D06BD /* NSData+ImageContentType.m */,
325F7CC8238942AB00AEDFCC /* UIImage+ExtendedCacheData.h */,
325F7CC9238942AB00AEDFCC /* UIImage+ExtendedCacheData.m */,
A18A6CC5172DC28500419892 /* UIImage+GIF.h */,
A18A6CC6172DC28500419892 /* UIImage+GIF.m */,
329A18571FFF5DFD008C9A2F /* UIImage+Metadata.h */,
@ -849,7 +889,8 @@
3257EAFA21898AED0097B271 /* SDImageGraphics.h in Headers */,
32D3CDD121DDE87300C4DB49 /* UIImage+MemoryCacheCost.h in Headers */,
328BB6AC2081FEE500760D6C /* SDWebImageCacheSerializer.h in Headers */,
325C46272233A0A8004CAE11 /* NSBezierPath+RoundedCorners.h in Headers */,
325F7CCA238942AB00AEDFCC /* UIImage+ExtendedCacheData.h in Headers */,
325C46272233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.h in Headers */,
321B378F2083290E00C0EA77 /* SDImageLoadersManager.h in Headers */,
329A185B1FFF5DFD008C9A2F /* UIImage+Metadata.h in Headers */,
4369C2791D9807EC007E863A /* UIView+WebCache.h in Headers */,
@ -869,6 +910,7 @@
326E2F2E236F0B23006F847F /* SDAnimatedImagePlayer.h in Headers */,
807A122A1F89636300EC2A9B /* SDImageCodersManager.h in Headers */,
3244062C2296C5F400A36084 /* SDWebImageOptionsProcessor.h in Headers */,
3240BB6823968FE7003BA07D /* SDAssociatedObject.h in Headers */,
4A2CAE211AB4BB7000B6BC39 /* SDWebImageManager.h in Headers */,
4A2CAE1F1AB4BB6C00B6BC39 /* SDImageCache.h in Headers */,
4A2CAE351AB4BB7500B6BC39 /* UIImageView+WebCache.h in Headers */,
@ -880,6 +922,7 @@
4A2CAE1D1AB4BB6800B6BC39 /* SDWebImageDownloaderOperation.h in Headers */,
4A2CAE2B1AB4BB7500B6BC39 /* UIButton+WebCache.h in Headers */,
4A2CAE251AB4BB7000B6BC39 /* SDWebImagePrefetcher.h in Headers */,
3246A70323A567AC00FBEA10 /* SDGraphicsImageRenderer.h in Headers */,
328BB6CF2082581100760D6C /* SDMemoryCache.h in Headers */,
325C460F223394D8004CAE11 /* SDImageCachesManagerOperation.h in Headers */,
321E60881F38E8C800405457 /* SDImageCoder.h in Headers */,
@ -889,7 +932,7 @@
325C460922339426004CAE11 /* SDWeakProxy.h in Headers */,
80B6DF812142B43B00BCB334 /* SDAnimatedImageRep.h in Headers */,
4A2CAE2F1AB4BB7500B6BC39 /* UIImage+MultiFormat.h in Headers */,
325C46212233A02E004CAE11 /* UIColor+HexString.h in Headers */,
325C46212233A02E004CAE11 /* UIColor+SDHexString.h in Headers */,
325312CA200F09910046BF1E /* SDWebImageTransition.h in Headers */,
4A2CAE1A1AB4BB6400B6BC39 /* SDWebImageOperation.h in Headers */,
32484765201775F600AF9E5A /* SDAnimatedImageView+WebCache.h in Headers */,
@ -906,6 +949,7 @@
32C0FDE32013426C001B8F2D /* SDWebImageIndicator.h in Headers */,
32F7C0712030114C00873181 /* SDImageTransformer.h in Headers */,
32E67311235765B500DB4987 /* SDDisplayLink.h in Headers */,
325F7CC623893B2E00AEDFCC /* SDFileAttributeHelper.h in Headers */,
4A2CAE2D1AB4BB7500B6BC39 /* UIImage+GIF.h in Headers */,
4A2CAE291AB4BB7500B6BC39 /* NSData+ImageContentType.h in Headers */,
328BB69E2081FED200760D6C /* SDWebImageCacheKeyFilter.h in Headers */,
@ -916,8 +960,8 @@
isa = PBXHeadersBuildPhase;
buildActionMask = 2147483647;
files = (
3287E6D2244C0C1400007311 /* MKAnnotationView+WebCache.h in Headers */,
806BE07E2142C65200E02143 /* SDWebImageMapKit.h in Headers */,
80B6DFCD2142B71600BCB334 /* MKAnnotationView+WebCache.h in Headers */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -1097,7 +1141,9 @@
files = (
3257EAFD21898AED0097B271 /* SDImageGraphics.m in Sources */,
3290FA0C1FA478AF0047D20C /* SDImageFrame.m in Sources */,
325C46232233A02E004CAE11 /* UIColor+HexString.m in Sources */,
325C46232233A02E004CAE11 /* UIColor+SDHexString.m in Sources */,
325F7CCB238942AB00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */,
3246A70523A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */,
321E60C61F38E91700405457 /* UIImage+ForceDecode.m in Sources */,
3244062E2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */,
3250C9F02355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */,
@ -1105,6 +1151,7 @@
32E67313235765B500DB4987 /* SDDisplayLink.m in Sources */,
4A2CAE2E1AB4BB7500B6BC39 /* UIImage+GIF.m in Sources */,
326E2F35236F1D58006F847F /* SDDeviceHelper.m in Sources */,
3240BB6A23968FE7003BA07D /* SDAssociatedObject.m in Sources */,
80B6DF822142B44400BCB334 /* NSButton+WebCache.m in Sources */,
32D3CDCF21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m in Sources */,
329F1241223FAD3400B309FD /* SDInternalMacros.m in Sources */,
@ -1127,12 +1174,13 @@
321E609C1F38E8ED00405457 /* SDImageIOCoder.m in Sources */,
4A2CAE261AB4BB7000B6BC39 /* SDWebImagePrefetcher.m in Sources */,
328BB6C92082581100760D6C /* SDDiskCache.m in Sources */,
325F7CC723893B2E00AEDFCC /* SDFileAttributeHelper.m in Sources */,
3248475F201775F600AF9E5A /* SDAnimatedImageView.m in Sources */,
32D1222C2080B2EB003685A3 /* SDImageCachesManager.m in Sources */,
32B9B53F206ED4230026769D /* SDWebImageDownloaderConfig.m in Sources */,
43A9186D1D8308FE00B3925F /* SDImageCacheConfig.m in Sources */,
32A09E42233358B700339F9D /* SDImageIOAnimatedCoder.m in Sources */,
325C46292233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */,
325C46292233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m in Sources */,
3248477D201775F600AF9E5A /* SDAnimatedImageView+WebCache.m in Sources */,
321E60AA1F38E8F600405457 /* SDImageGIFCoder.m in Sources */,
321E608E1F38E8C800405457 /* SDImageCoder.m in Sources */,
@ -1167,14 +1215,17 @@
files = (
3257EAFC21898AED0097B271 /* SDImageGraphics.m in Sources */,
3290FA0A1FA478AF0047D20C /* SDImageFrame.m in Sources */,
325C46222233A02E004CAE11 /* UIColor+HexString.m in Sources */,
325C46222233A02E004CAE11 /* UIColor+SDHexString.m in Sources */,
321E60C41F38E91700405457 /* UIImage+ForceDecode.m in Sources */,
3246A70423A567AC00FBEA10 /* SDGraphicsImageRenderer.m in Sources */,
3244062D2296C5F400A36084 /* SDWebImageOptionsProcessor.m in Sources */,
3250C9EF2355D9DA0093A896 /* SDWebImageDownloaderDecryptor.m in Sources */,
3240BB6523968FA1003BA07D /* SDFileAttributeHelper.m in Sources */,
328BB6A22081FED200760D6C /* SDWebImageCacheKeyFilter.m in Sources */,
32E67312235765B500DB4987 /* SDDisplayLink.m in Sources */,
53761309155AD0D5005750A4 /* SDImageCache.m in Sources */,
326E2F34236F1D58006F847F /* SDDeviceHelper.m in Sources */,
3240BB6923968FE7003BA07D /* SDAssociatedObject.m in Sources */,
80B6DF832142B44500BCB334 /* NSButton+WebCache.m in Sources */,
32D3CDCE21DDE87300C4DB49 /* UIImage+MemoryCacheCost.m in Sources */,
329F1240223FAD3400B309FD /* SDInternalMacros.m in Sources */,
@ -1198,11 +1249,12 @@
5376130D155AD0D5005750A4 /* SDWebImagePrefetcher.m in Sources */,
328BB6C72082581100760D6C /* SDDiskCache.m in Sources */,
3248475D201775F600AF9E5A /* SDAnimatedImageView.m in Sources */,
325F7CCC2389463D00AEDFCC /* UIImage+ExtendedCacheData.m in Sources */,
32D1222A2080B2EB003685A3 /* SDImageCachesManager.m in Sources */,
32B9B53D206ED4230026769D /* SDWebImageDownloaderConfig.m in Sources */,
43A9186B1D8308FE00B3925F /* SDImageCacheConfig.m in Sources */,
32A09E41233358B700339F9D /* SDImageIOAnimatedCoder.m in Sources */,
325C46282233A0A8004CAE11 /* NSBezierPath+RoundedCorners.m in Sources */,
325C46282233A0A8004CAE11 /* NSBezierPath+SDRoundedCorners.m in Sources */,
3248477B201775F600AF9E5A /* SDAnimatedImageView+WebCache.m in Sources */,
321E60A81F38E8F600405457 /* SDImageGIFCoder.m in Sources */,
321E608C1F38E8C800405457 /* SDImageCoder.m in Sources */,
@ -1235,7 +1287,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
80B6DFA72142B71600BCB334 /* MKAnnotationView+WebCache.m in Sources */,
3287E6D1244C0C1400007311 /* MKAnnotationView+WebCache.m in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};

View File

@ -23,6 +23,8 @@ static const SDImageFormat SDImageFormatTIFF = 3;
static const SDImageFormat SDImageFormatWebP = 4;
static const SDImageFormat SDImageFormatHEIC = 5;
static const SDImageFormat SDImageFormatHEIF = 6;
static const SDImageFormat SDImageFormatPDF = 7;
static const SDImageFormat SDImageFormatSVG = 8;
/**
NSData category about the image content type and UTI.
@ -43,6 +45,7 @@ static const SDImageFormat SDImageFormatHEIF = 6;
*
* @param format Format as SDImageFormat
* @return The UTType as CFStringRef
* @note For unknown format, `kUTTypeImage` abstract type will return
*/
+ (nonnull CFStringRef)sd_UTTypeFromImageFormat:(SDImageFormat)format CF_RETURNS_NOT_RETAINED NS_SWIFT_NAME(sd_UTType(from:));
@ -51,6 +54,7 @@ static const SDImageFormat SDImageFormatHEIF = 6;
*
* @param uttype The UTType as CFStringRef
* @return The Format as SDImageFormat
* @note For unknown type, `SDImageFormatUndefined` will return
*/
+ (SDImageFormat)sd_imageFormatFromUTType:(nonnull CFStringRef)uttype;

View File

@ -17,6 +17,7 @@
// Currently Image/IO does not support WebP
#define kSDUTTypeWebP ((__bridge CFStringRef)@"public.webp")
#define kSVGTagEnd @"</svg>"
@implementation NSData (ImageContentType)
@ -65,6 +66,24 @@
}
break;
}
case 0x25: {
if (data.length >= 4) {
//%PDF
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(1, 3)] encoding:NSASCIIStringEncoding];
if ([testString isEqualToString:@"PDF"]) {
return SDImageFormatPDF;
}
}
}
case 0x3C: {
if (data.length > 100) {
// Check end with SVG tag
NSString *testString = [[NSString alloc] initWithData:[data subdataWithRange:NSMakeRange(data.length - 100, 100)] encoding:NSASCIIStringEncoding];
if ([testString containsString:kSVGTagEnd]) {
return SDImageFormatSVG;
}
}
}
}
return SDImageFormatUndefined;
}
@ -93,9 +112,15 @@
case SDImageFormatHEIF:
UTType = kSDUTTypeHEIF;
break;
case SDImageFormatPDF:
UTType = kUTTypePDF;
break;
case SDImageFormatSVG:
UTType = kUTTypeScalableVectorGraphics;
break;
default:
// default is kUTTypePNG
UTType = kUTTypePNG;
// default is kUTTypeImage abstract type
UTType = kUTTypeImage;
break;
}
return UTType;
@ -120,6 +145,10 @@
imageFormat = SDImageFormatHEIC;
} else if (CFStringCompare(uttype, kSDUTTypeHEIF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatHEIF;
} else if (CFStringCompare(uttype, kUTTypePDF, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatPDF;
} else if (CFStringCompare(uttype, kUTTypeScalableVectorGraphics, 0) == kCFCompareEqualTo) {
imageFormat = SDImageFormatSVG;
} else {
imageFormat = SDImageFormatUndefined;
}

View File

@ -19,6 +19,10 @@
The underlying Core Graphics image object. This will actually use `CGImageForProposedRect` with the image size.
*/
@property (nonatomic, readonly, nullable) CGImageRef CGImage;
/**
The underlying Core Image data. This will actually use `bestRepresentationForRect` with the image size to find the `NSCIImageRep`.
*/
@property (nonatomic, readonly, nullable) CIImage *CIImage;
/**
The scale factor of the image. This wil actually use `bestRepresentationForRect` with image size and pixel size to calculate the scale factor. If failed, use the default value 1.0. Should be greater than or equal to 1.0.
*/
@ -38,6 +42,16 @@ The underlying Core Graphics image object. This will actually use `CGImageForPro
*/
- (nonnull instancetype)initWithCGImage:(nonnull CGImageRef)cgImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation;
/**
Initializes and returns an image object with the specified Core Image object. The representation is `NSCIImageRep`.
@param ciImage A Core Image image object
@param scale The image scale factor
@param orientation The orientation of the image data
@return The image object
*/
- (nonnull instancetype)initWithCIImage:(nonnull CIImage *)ciImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation;
/**
Returns an image object with the scale factor. The representation is created from the image data.
@note The difference between these this and `initWithData:` is that `initWithData:` will always use `backingScaleFactor` as scale factor.

View File

@ -20,6 +20,15 @@
return cgImage;
}
- (nullable CIImage *)CIImage {
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
if (![imageRep isKindOfClass:NSCIImageRep.class]) {
return nil;
}
return ((NSCIImageRep *)imageRep).CIImage;
}
- (CGFloat)scale {
CGFloat scale = 1;
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
@ -65,6 +74,28 @@
return self;
}
- (instancetype)initWithCIImage:(nonnull CIImage *)ciImage scale:(CGFloat)scale orientation:(CGImagePropertyOrientation)orientation {
NSCIImageRep *imageRep;
if (orientation != kCGImagePropertyOrientationUp) {
CIImage *rotatedCIImage = [ciImage imageByApplyingOrientation:orientation];
imageRep = [[NSCIImageRep alloc] initWithCIImage:rotatedCIImage];
} else {
imageRep = [[NSCIImageRep alloc] initWithCIImage:ciImage];
}
if (scale < 1) {
scale = 1;
}
CGFloat pixelWidth = imageRep.pixelsWide;
CGFloat pixelHeight = imageRep.pixelsHigh;
NSSize size = NSMakeSize(pixelWidth / scale, pixelHeight / scale);
self = [self initWithSize:size];
if (self) {
imageRep.size = size;
[self addRepresentation:imageRep];
}
return self;
}
- (instancetype)initWithData:(nonnull NSData *)data scale:(CGFloat)scale {
NSBitmapImageRep *imageRep = [[NSBitmapImageRep alloc] initWithData:data];
if (!imageRep) {

View File

@ -72,6 +72,7 @@
// This class override these methods from UIImage(NSImage), and it supports NSSecureCoding.
// You should use these methods to create a new animated image. Use other methods just call super instead.
// Pay attention, when the animated image frame count <= 1, all the `SDAnimatedImageProvider` protocol methods will return nil or 0 value, you'd better check the frame count before usage and keep fallback.
+ (nullable instancetype)imageNamed:(nonnull NSString *)name; // Cache in memory, no Asset Catalog support
#if __has_include(<UIKit/UITraitCollection.h>)
+ (nullable instancetype)imageNamed:(nonnull NSString *)name inBundle:(nullable NSBundle *)bundle compatibleWithTraitCollection:(nullable UITraitCollection *)traitCollection; // Cache in memory, no Asset Catalog support

View File

@ -12,6 +12,7 @@
#import "SDImageCodersManager.h"
#import "SDImageFrame.h"
#import "UIImage+MemoryCacheCost.h"
#import "UIImage+Metadata.h"
#import "SDImageAssetManager.h"
#import "objc/runtime.h"
@ -156,7 +157,10 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
self = [super initWithCGImage:image.CGImage scale:MAX(scale, 1) orientation:image.imageOrientation];
#endif
if (self) {
_animatedCoder = animatedCoder;
// Only keep the animated coder if frame count > 1, save RAM usage for non-animated image format (APNG/WebP)
if (animatedCoder.animatedImageFrameCount > 1) {
_animatedCoder = animatedCoder;
}
NSData *data = [animatedCoder animatedImageData];
SDImageFormat format = [NSData sd_imageFormatForImageData:data];
_animatedImageFormat = format;
@ -166,6 +170,9 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
#pragma mark - Preload
- (void)preloadAllFrames {
if (!_animatedCoder) {
return;
}
if (!self.isAllFramesLoaded) {
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray arrayWithCapacity:self.animatedImageFrameCount];
for (size_t i = 0; i < self.animatedImageFrameCount; i++) {
@ -180,6 +187,9 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
- (void)unloadAllFrames {
if (!_animatedCoder) {
return;
}
if (self.isAllFramesLoaded) {
self.loadedAnimatedImageFrames = nil;
self.allFramesLoaded = NO;
@ -190,11 +200,12 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
- (instancetype)initWithCoder:(NSCoder *)aDecoder {
self = [super initWithCoder:aDecoder];
if (self) {
_animatedImageFormat = [aDecoder decodeIntegerForKey:NSStringFromSelector(@selector(animatedImageFormat))];
NSData *animatedImageData = [aDecoder decodeObjectOfClass:[NSData class] forKey:NSStringFromSelector(@selector(animatedImageData))];
CGFloat scale = self.scale;
if (!animatedImageData) {
return self;
}
CGFloat scale = self.scale;
id<SDAnimatedImageCoder> animatedCoder = nil;
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders) {
if ([coder conformsToProtocol:@protocol(SDAnimatedImageCoder)]) {
@ -207,15 +218,16 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
if (!animatedCoder) {
return self;
}
_animatedCoder = animatedCoder;
SDImageFormat format = [NSData sd_imageFormatForImageData:animatedImageData];
_animatedImageFormat = format;
if (animatedCoder.animatedImageFrameCount > 1) {
_animatedCoder = animatedCoder;
}
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)aCoder {
[super encodeWithCoder:aCoder];
[aCoder encodeInteger:self.animatedImageFormat forKey:NSStringFromSelector(@selector(animatedImageFormat))];
NSData *animatedImageData = self.animatedImageData;
if (animatedImageData) {
[aCoder encodeObject:animatedImageData forKey:NSStringFromSelector(@selector(animatedImageData))];
@ -226,7 +238,7 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
return YES;
}
#pragma mark - SDAnimatedImage
#pragma mark - SDAnimatedImageProvider
- (NSData *)animatedImageData {
return [self.animatedCoder animatedImageData];
@ -287,3 +299,31 @@ static CGFloat SDImageScaleFromPath(NSString *string) {
}
@end
@implementation SDAnimatedImage (Metadata)
- (BOOL)sd_isAnimated {
return YES;
}
- (NSUInteger)sd_imageLoopCount {
return self.animatedImageLoopCount;
}
- (void)setSd_imageLoopCount:(NSUInteger)sd_imageLoopCount {
return;
}
- (SDImageFormat)sd_imageFormat {
return self.animatedImageFormat;
}
- (void)setSd_imageFormat:(SDImageFormat)sd_imageFormat {
return;
}
- (BOOL)sd_isVector {
return NO;
}
@end

View File

@ -23,6 +23,7 @@
@property (nonatomic, strong) NSMutableDictionary<NSNumber *, UIImage *> *frameBuffer;
@property (nonatomic, assign) NSTimeInterval currentTime;
@property (nonatomic, assign) BOOL bufferMiss;
@property (nonatomic, assign) BOOL needsDisplayWhenImageBecomesAvailable;
@property (nonatomic, assign) NSUInteger maxBufferCount;
@property (nonatomic, strong) NSOperationQueue *fetchQueue;
@property (nonatomic, strong) dispatch_semaphore_t lock;
@ -165,6 +166,7 @@
self.currentLoopCount = 0;
self.currentTime = 0;
self.bufferMiss = NO;
self.needsDisplayWhenImageBecomesAvailable = NO;
[self handleFrameChange];
}
@ -177,12 +179,12 @@
#pragma mark - Animation Control
- (void)startPlaying {
[self.displayLink start];
// Calculate max buffer size
[self calculateMaxBufferCount];
// Setup frame
if (self.currentFrameIndex == 0 && !self.currentFrame) {
[self setupCurrentFrame];
}
// Calculate max buffer size
[self calculateMaxBufferCount];
}
- (void)stopPlaying {
@ -217,8 +219,6 @@
if (!self.isPlaying) {
return;
}
// Calculate refresh duration
NSTimeInterval duration = self.displayLink.duration;
NSUInteger totalFrameCount = self.totalFrameCount;
if (totalFrameCount <= 1) {
@ -226,8 +226,6 @@
[self stopPlaying];
return;
}
NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
NSTimeInterval playbackRate = self.playbackRate;
if (playbackRate <= 0) {
@ -236,7 +234,46 @@
return;
}
// Check if we have the frame buffer firstly to improve performance
// Calculate refresh duration
NSTimeInterval duration = self.displayLink.duration;
NSUInteger currentFrameIndex = self.currentFrameIndex;
NSUInteger nextFrameIndex = (currentFrameIndex + 1) % totalFrameCount;
// Check if we need to display new frame firstly
BOOL bufferFull = NO;
if (self.needsDisplayWhenImageBecomesAvailable) {
UIImage *currentFrame;
SD_LOCK(self.lock);
currentFrame = self.frameBuffer[@(currentFrameIndex)];
SD_UNLOCK(self.lock);
// Update the current frame
if (currentFrame) {
SD_LOCK(self.lock);
// Remove the frame buffer if need
if (self.frameBuffer.count > self.maxBufferCount) {
self.frameBuffer[@(currentFrameIndex)] = nil;
}
// Check whether we can stop fetch
if (self.frameBuffer.count == totalFrameCount) {
bufferFull = YES;
}
SD_UNLOCK(self.lock);
// Update the current frame immediately
self.currentFrame = currentFrame;
[self handleFrameChange];
self.bufferMiss = NO;
self.needsDisplayWhenImageBecomesAvailable = NO;
}
else {
self.bufferMiss = YES;
}
}
// Check if we have the frame buffer
if (!self.bufferMiss) {
// Then check if timestamp is reached
self.currentTime += duration;
@ -246,6 +283,10 @@
// Current frame timestamp not reached, return
return;
}
// Otherwise, we shoudle be ready to display next frame
self.needsDisplayWhenImageBecomesAvailable = YES;
self.currentFrameIndex = nextFrameIndex;
self.currentTime -= currentDuration;
NSTimeInterval nextDuration = [self.animatedProvider animatedImageDurationAtIndex:nextFrameIndex];
nextDuration = nextDuration / playbackRate;
@ -253,45 +294,19 @@
// Do not skip frame
self.currentTime = nextDuration;
}
}
// Update the current frame
UIImage *currentFrame;
UIImage *fetchFrame;
SD_LOCK(self.lock);
currentFrame = self.frameBuffer[@(currentFrameIndex)];
fetchFrame = currentFrame ? self.frameBuffer[@(nextFrameIndex)] : nil;
SD_UNLOCK(self.lock);
BOOL bufferFull = NO;
if (currentFrame) {
SD_LOCK(self.lock);
// Remove the frame buffer if need
if (self.frameBuffer.count > self.maxBufferCount) {
self.frameBuffer[@(currentFrameIndex)] = nil;
}
// Check whether we can stop fetch
if (self.frameBuffer.count == totalFrameCount) {
bufferFull = YES;
}
SD_UNLOCK(self.lock);
self.currentFrame = currentFrame;
[self handleFrameChange];
self.currentFrameIndex = nextFrameIndex;
self.bufferMiss = NO;
} else {
self.bufferMiss = YES;
}
// Update the loop count when last frame rendered
if (nextFrameIndex == 0 && !self.bufferMiss) {
// Update the loop count
self.currentLoopCount++;
[self handleLoopChnage];
// if reached the max loop count, stop animating, 0 means loop indefinitely
NSUInteger maxLoopCount = self.totalLoopCount;
if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
[self stopPlaying];
return;
// Update the loop count when last frame rendered
if (nextFrameIndex == 0) {
// Update the loop count
self.currentLoopCount++;
[self handleLoopChnage];
// if reached the max loop count, stop animating, 0 means loop indefinitely
NSUInteger maxLoopCount = self.totalLoopCount;
if (maxLoopCount != 0 && (self.currentLoopCount >= maxLoopCount)) {
[self stopPlaying];
return;
}
}
}
@ -301,14 +316,13 @@
}
// Check if we should prefetch next frame or current frame
NSUInteger fetchFrameIndex;
if (self.bufferMiss) {
// When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
fetchFrameIndex = currentFrameIndex;
} else {
// Or, most cases, the decode speed is faster than render speed, we fetch next frame
fetchFrameIndex = nextFrameIndex;
}
// When buffer miss, means the decode speed is slower than render speed, we fetch current miss frame
// Or, most cases, the decode speed is faster than render speed, we fetch next frame
NSUInteger fetchFrameIndex = self.bufferMiss? currentFrameIndex : nextFrameIndex;
UIImage *fetchFrame;
SD_LOCK(self.lock);
fetchFrame = self.bufferMiss? nil : self.frameBuffer[@(nextFrameIndex)];
SD_UNLOCK(self.lock);
if (!fetchFrame && !bufferFull && self.fetchQueue.operationCount == 0) {
// Prefetch next frame in background queue

View File

@ -19,6 +19,7 @@
@interface SDAnimatedImageView () <CALayerDelegate> {
BOOL _initFinished; // Extra flag to mark the `commonInit` is called
NSRunLoopMode _runLoopMode;
NSUInteger _maxBufferSize;
double _playbackRate;
}
@ -153,6 +154,9 @@
// RunLoop Mode
self.player.runLoopMode = self.runLoopMode;
// Max Buffer Size
self.player.maxBufferSize = self.maxBufferSize;
// Play Rate
self.player.playbackRate = self.playbackRate;
@ -166,12 +170,13 @@
};
self.player.animationLoopHandler = ^(NSUInteger loopCount) {
@strongify(self);
self.currentLoopCount = loopCount;
// Progressive image reach the current last frame index. Keep the state and pause animating. Wait for later restart
if (self.isProgressive) {
NSUInteger lastFrameIndex = self.player.totalFrameCount;
NSUInteger lastFrameIndex = self.player.totalFrameCount - 1;
[self.player seekToFrameAtIndex:lastFrameIndex loopCount:0];
[self.player pausePlaying];
} else {
self.currentLoopCount = loopCount;
}
};
@ -206,6 +211,16 @@
return [NSProcessInfo processInfo].activeProcessorCount > 1 ? NSRunLoopCommonModes : NSDefaultRunLoopMode;
}
- (void)setMaxBufferSize:(NSUInteger)maxBufferSize
{
_maxBufferSize = maxBufferSize;
self.player.maxBufferSize = maxBufferSize;
}
- (NSUInteger)maxBufferSize {
return _maxBufferSize; // Defaults to 0
}
- (void)setPlaybackRate:(double)playbackRate
{
_playbackRate = playbackRate;
@ -306,6 +321,19 @@
#pragma mark - UIImageView Method Overrides
#pragma mark Image Data
- (void)setAnimationRepeatCount:(NSInteger)animationRepeatCount
{
#if SD_UIKIT
[super setAnimationRepeatCount:animationRepeatCount];
#else
_animationRepeatCount = animationRepeatCount;
#endif
if (self.shouldCustomLoopCount) {
self.player.totalLoopCount = animationRepeatCount;
}
}
- (void)startAnimating
{
if (self.player) {
@ -442,10 +470,10 @@
// NSImageView use a subview. We need this subview's layer for actual rendering.
// Why using this design may because of properties like `imageAlignment` and `imageScaling`, which it's not available for UIImageView.contentMode (it's impossible to align left and keep aspect ratio at the same time)
- (NSView *)imageView {
NSImageView *imageView = imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageView"));
NSImageView *imageView = objc_getAssociatedObject(self, SD_SEL_SPI(imageView));
if (!imageView) {
// macOS 10.14
imageView = objc_getAssociatedObject(self, NSSelectorFromString(@"_imageSubview"));
imageView = objc_getAssociatedObject(self, SD_SEL_SPI(imageSubview));
}
return imageView;
}

View File

@ -54,6 +54,26 @@
*/
- (void)setData:(nullable NSData *)data forKey:(nonnull NSString *)key;
/**
Returns the extended data associated with a given key.
This method may blocks the calling thread until file read finished.
@param key A string identifying the data. If nil, just return nil.
@return The value associated with key, or nil if no value is associated with key.
*/
- (nullable NSData *)extendedDataForKey:(nonnull NSString *)key;
/**
Set extended data with a given key.
@discussion You can set any extended data to exist cache key. Without override the exist disk file data.
on UNIX, the common way for this is to use the Extended file attributes (xattr)
@param extendedData The extended data (pass nil to remove).
@param key The key with which to associate the value. If nil, this method has no effect.
*/
- (void)setExtendedData:(nullable NSData *)extendedData forKey:(nonnull NSString *)key;
/**
Removes the value of the specified key in the cache.
This method may blocks the calling thread until file delete finished.

View File

@ -8,8 +8,11 @@
#import "SDDiskCache.h"
#import "SDImageCacheConfig.h"
#import "SDFileAttributeHelper.h"
#import <CommonCrypto/CommonDigest.h>
static NSString * const SDDiskCacheExtendedAttributeName = @"com.hackemist.SDDiskCache";
@interface SDDiskCache ()
@property (nonatomic, copy) NSString *diskCachePath;
@ -95,6 +98,31 @@
}
}
- (NSData *)extendedDataForKey:(NSString *)key {
NSParameterAssert(key);
// get cache Path for image key
NSString *cachePathForKey = [self cachePathForKey:key];
NSData *extendedData = [SDFileAttributeHelper extendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
return extendedData;
}
- (void)setExtendedData:(NSData *)extendedData forKey:(NSString *)key {
NSParameterAssert(key);
// get cache Path for image key
NSString *cachePathForKey = [self cachePathForKey:key];
if (!extendedData) {
// Remove
[SDFileAttributeHelper removeExtendedAttribute:SDDiskCacheExtendedAttributeName atPath:cachePathForKey traverseLink:NO error:nil];
} else {
// Override
[SDFileAttributeHelper setExtendedAttribute:SDDiskCacheExtendedAttributeName value:extendedData atPath:cachePathForKey traverseLink:NO overwrite:YES error:nil];
}
}
- (void)removeDataForKey:(NSString *)key {
NSParameterAssert(key);
NSString *filePath = [self cachePathForKey:key];
@ -118,11 +146,15 @@
case SDImageCacheConfigExpireTypeAccessDate:
cacheContentDateKey = NSURLContentAccessDateKey;
break;
case SDImageCacheConfigExpireTypeModificationDate:
cacheContentDateKey = NSURLContentModificationDateKey;
break;
case SDImageCacheConfigExpireTypeCreationDate:
cacheContentDateKey = NSURLCreationDateKey;
break;
case SDImageCacheConfigExpireTypeChangeDate:
cacheContentDateKey = NSURLAttributeModificationDateKey;
break;
default:
break;
}

View File

@ -0,0 +1,73 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
/**
These following class are provided to use `UIGraphicsImageRenderer` with polyfill, which allows write cross-platform(AppKit/UIKit) code and avoid runtime version check.
Compared to `UIGraphicsBeginImageContext`, `UIGraphicsImageRenderer` use dynamic bitmap from your draw code to generate CGContext, not always use ARGB8888, which is more performant on RAM usage.
Which means, if you draw CGImage/CIImage which contains grayscale only, the underlaying bitmap context use grayscale, it's managed by system and not a fixed type. (actually, the `kCGContextTypeAutomatic`)
For usage, See more in Apple's documentation: https://developer.apple.com/documentation/uikit/uigraphicsimagerenderer
For UIKit on iOS/tvOS 10+, these method just use the same `UIGraphicsImageRenderer` API.
For others (macOS/watchOS or iOS/tvOS 10-), these method use the `SDImageGraphics.h` to implements the same behavior (but without dynamic bitmap support)
*/
typedef void (^SDGraphicsImageDrawingActions)(CGContextRef _Nonnull context);
typedef NS_ENUM(NSInteger, SDGraphicsImageRendererFormatRange) {
SDGraphicsImageRendererFormatRangeUnspecified = -1,
SDGraphicsImageRendererFormatRangeAutomatic = 0,
SDGraphicsImageRendererFormatRangeExtended,
SDGraphicsImageRendererFormatRangeStandard
};
/// A set of drawing attributes that represent the configuration of an image renderer context.
@interface SDGraphicsImageRendererFormat : NSObject
/// The display scale of the image renderer context.
/// The default value is equal to the scale of the main screen.
@property (nonatomic) CGFloat scale;
/// A Boolean value indicating whether the underlying Core Graphics context has an alpha channel.
/// The default value is NO.
@property (nonatomic) BOOL opaque;
/// Specifying whether the bitmap context should use extended color.
/// For iOS 12+, the value is from system `preferredRange` property
/// For iOS 10-11, the value is from system `prefersExtendedRange` property
/// For iOS 9-, the value is `.standard`
@property (nonatomic) SDGraphicsImageRendererFormatRange preferredRange;
/// Init the default format. See each properties's default value.
- (nonnull instancetype)init;
/// Returns a new format best suited for the main screens current configuration.
+ (nonnull instancetype)preferredFormat;
@end
/// A graphics renderer for creating Core Graphics-backed images.
@interface SDGraphicsImageRenderer : NSObject
/// Creates an image renderer for drawing images of a given size.
/// @param size The size of images output from the renderer, specified in points.
/// @return An initialized image renderer.
- (nonnull instancetype)initWithSize:(CGSize)size;
/// Creates a new image renderer with a given size and format.
/// @param size The size of images output from the renderer, specified in points.
/// @param format A SDGraphicsImageRendererFormat object that encapsulates the format used to create the renderer context.
/// @return An initialized image renderer.
- (nonnull instancetype)initWithSize:(CGSize)size format:(nonnull SDGraphicsImageRendererFormat *)format;
/// Creates an image by following a set of drawing instructions.
/// @param actions A SDGraphicsImageDrawingActions block that, when invoked by the renderer, executes a set of drawing instructions to create the output image.
/// @note You should not retain or use the context outside the block, it's non-escaping.
/// @return A UIImage object created by the supplied drawing actions.
- (nonnull UIImage *)imageWithActions:(nonnull NS_NOESCAPE SDGraphicsImageDrawingActions)actions;
@end

View File

@ -0,0 +1,244 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDGraphicsImageRenderer.h"
#import "SDImageGraphics.h"
@interface SDGraphicsImageRendererFormat ()
#if SD_UIKIT
@property (nonatomic, strong) UIGraphicsImageRendererFormat *uiformat API_AVAILABLE(ios(10.0), tvos(10.0));
#endif
@end
@implementation SDGraphicsImageRendererFormat
@synthesize scale = _scale;
@synthesize opaque = _opaque;
@synthesize preferredRange = _preferredRange;
#pragma mark - Property
- (CGFloat)scale {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.10, *)) {
return self.uiformat.scale;
} else {
return _scale;
}
#else
return _scale;
#endif
}
- (void)setScale:(CGFloat)scale {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.10, *)) {
self.uiformat.scale = scale;
} else {
_scale = scale;
}
#else
_scale = scale;
#endif
}
- (BOOL)opaque {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.10, *)) {
return self.uiformat.opaque;
} else {
return _opaque;
}
#else
return _opaque;
#endif
}
- (void)setOpaque:(BOOL)opaque {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.10, *)) {
self.uiformat.opaque = opaque;
} else {
_opaque = opaque;
}
#else
_opaque = opaque;
#endif
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
- (SDGraphicsImageRendererFormatRange)preferredRange {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.10, *)) {
if (@available(iOS 12.0, tvOS 12.0, *)) {
return (SDGraphicsImageRendererFormatRange)self.uiformat.preferredRange;
} else {
BOOL prefersExtendedRange = self.uiformat.prefersExtendedRange;
if (prefersExtendedRange) {
return SDGraphicsImageRendererFormatRangeExtended;
} else {
return SDGraphicsImageRendererFormatRangeStandard;
}
}
} else {
return _preferredRange;
}
#else
return _preferredRange;
#endif
}
- (void)setPreferredRange:(SDGraphicsImageRendererFormatRange)preferredRange {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.10, *)) {
if (@available(iOS 12.0, tvOS 12.0, *)) {
self.uiformat.preferredRange = (UIGraphicsImageRendererFormatRange)preferredRange;
} else {
switch (preferredRange) {
case SDGraphicsImageRendererFormatRangeExtended:
self.uiformat.prefersExtendedRange = YES;
break;
case SDGraphicsImageRendererFormatRangeStandard:
self.uiformat.prefersExtendedRange = NO;
default:
// Automatic means default
break;
}
}
} else {
_preferredRange = preferredRange;
}
#else
_preferredRange = preferredRange;
#endif
}
#pragma clang diagnostic pop
- (instancetype)init {
self = [super init];
if (self) {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.10, *)) {
UIGraphicsImageRendererFormat *uiformat = [[UIGraphicsImageRendererFormat alloc] init];
self.uiformat = uiformat;
} else {
#endif
#if SD_WATCH
CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale;
#elif SD_UIKIT
CGFloat screenScale = [UIScreen mainScreen].scale;
#elif SD_MAC
CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor;
#endif
self.scale = screenScale;
self.opaque = NO;
self.preferredRange = SDGraphicsImageRendererFormatRangeStandard;
#if SD_UIKIT
}
#endif
}
return self;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunguarded-availability"
- (instancetype)initForMainScreen {
self = [super init];
if (self) {
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.0, *)) {
UIGraphicsImageRendererFormat *uiformat;
// iOS 11.0.0 GM does have `preferredFormat`, but iOS 11 betas did not (argh!)
if ([UIGraphicsImageRenderer respondsToSelector:@selector(preferredFormat)]) {
uiformat = [UIGraphicsImageRendererFormat preferredFormat];
} else {
uiformat = [UIGraphicsImageRendererFormat defaultFormat];
}
self.uiformat = uiformat;
} else {
#endif
#if SD_WATCH
CGFloat screenScale = [WKInterfaceDevice currentDevice].screenScale;
#elif SD_UIKIT
CGFloat screenScale = [UIScreen mainScreen].scale;
#elif SD_MAC
CGFloat screenScale = [NSScreen mainScreen].backingScaleFactor;
#endif
self.scale = screenScale;
self.opaque = NO;
self.preferredRange = SDGraphicsImageRendererFormatRangeStandard;
#if SD_UIKIT
}
#endif
}
return self;
}
#pragma clang diagnostic pop
+ (instancetype)preferredFormat {
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] initForMainScreen];
return format;
}
@end
@interface SDGraphicsImageRenderer ()
@property (nonatomic, assign) CGSize size;
@property (nonatomic, strong) SDGraphicsImageRendererFormat *format;
#if SD_UIKIT
@property (nonatomic, strong) UIGraphicsImageRenderer *uirenderer API_AVAILABLE(ios(10.0), tvos(10.0));
#endif
@end
@implementation SDGraphicsImageRenderer
- (instancetype)initWithSize:(CGSize)size {
return [self initWithSize:size format:SDGraphicsImageRendererFormat.preferredFormat];
}
- (instancetype)initWithSize:(CGSize)size format:(SDGraphicsImageRendererFormat *)format {
NSParameterAssert(format);
self = [super init];
if (self) {
self.size = size;
self.format = format;
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.0, *)) {
UIGraphicsImageRendererFormat *uiformat = format.uiformat;
self.uirenderer = [[UIGraphicsImageRenderer alloc] initWithSize:size format:uiformat];
}
#endif
}
return self;
}
- (UIImage *)imageWithActions:(NS_NOESCAPE SDGraphicsImageDrawingActions)actions {
NSParameterAssert(actions);
#if SD_UIKIT
if (@available(iOS 10.0, tvOS 10.0, *)) {
UIGraphicsImageDrawingActions uiactions = ^(UIGraphicsImageRendererContext *rendererContext) {
if (actions) {
actions(rendererContext.CGContext);
}
};
return [self.uirenderer imageWithActions:uiactions];
} else {
#endif
SDGraphicsBeginImageContextWithOptions(self.size, self.format.opaque, self.format.scale);
CGContextRef context = SDGraphicsGetCurrentContext();
if (actions) {
actions(context);
}
UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
SDGraphicsEndImageContext();
return image;
#if SD_UIKIT
}
#endif
}
@end

View File

@ -162,6 +162,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES. If NO, the completion block is called synchronously
* @param completionBlock A block executed after the operation is finished
* @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG.
*/
- (void)storeImage:(nullable UIImage *)image
forKey:(nullable NSString *)key
@ -178,6 +179,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
* @param key The unique image cache key, usually it's image absolute URL
* @param toDisk Store the image to disk cache if YES. If NO, the completion block is called synchronously
* @param completionBlock A block executed after the operation is finished
* @note If no image data is provided and encode to disk, we will try to detect the image format (using either `sd_imageFormat` or `SDAnimatedImage` protocol method) and animation status, to choose the best matched format, including GIF, JPEG or PNG.
*/
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)imageData
@ -225,8 +227,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
#pragma mark - Query and Retrieve Ops
/**
* Asynchronously queries the cache with operation and call the completion when done.
* Query the image data for the given key synchronously.
* Synchronously query the image data for the given key in disk cache. You can decode the image data to image after loaded.
*
* @param key The unique key used to store the wanted image
* @return The image data for the given key, or nil if not found.
@ -234,9 +235,18 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key;
/**
* Operation that queries the cache asynchronously and call the completion when done.
* Asynchronously query the image data for the given key in disk cache. You can decode the image data to image after loaded.
*
* @param key The unique key used to store the wanted image
* @param key The unique key used to store the wanted image
* @param completionBlock the block to be executed when the query is done.
* @note the completion block will be always executed on the main queue
*/
- (void)diskImageDataQueryForKey:(nullable NSString *)key completion:(nullable SDImageCacheQueryDataCompletionBlock)completionBlock;
/**
* Asynchronously queries the cache with operation and call the completion when done.
*
* @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
* @param doneBlock The completion block. Will not get called if the operation is cancelled
*
* @return a NSOperation instance containing the cache op
@ -246,7 +256,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
/**
* Asynchronously queries the cache with operation and call the completion when done.
*
* @param key The unique key used to store the wanted image
* @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
* @param options A mask to specify options to use for this cache query
* @param doneBlock The completion block. Will not get called if the operation is cancelled
*
@ -257,7 +267,7 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
/**
* Asynchronously queries the cache with operation and call the completion when done.
*
* @param key The unique key used to store the wanted image
* @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
* @param options A mask to specify options to use for this cache query
* @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
* @param doneBlock The completion block. Will not get called if the operation is cancelled
@ -266,6 +276,19 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/**
* Asynchronously queries the cache with operation and call the completion when done.
*
* @param key The unique key used to store the wanted image. If you want transformed or thumbnail image, calculate the key with `SDTransformedKeyForKey`, `SDThumbnailedKeyForKey`, or generate the cache key from url with `cacheKeyForURL:context:`.
* @param options A mask to specify options to use for this cache query
* @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
* @param queryCacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediatelly.
* @param doneBlock The completion block. Will not get called if the operation is cancelled
*
* @return a NSOperation instance containing the cache op
*/
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock;
/**
* Synchronously query the memory cache.
*
@ -282,6 +305,16 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
*/
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key;
/**
* Synchronously query the disk cache. With the options and context which may effect the image generation. (Such as transformer, animated image, thumbnail, etc)
*
* @param key The unique key used to store the image
* @param options A mask to specify options to use for this cache query
* @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
* @return The image for the given key, or nil if not found.
*/
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;
/**
* Synchronously query the cache (memory and or disk) after checking the memory cache.
*
@ -290,6 +323,16 @@ typedef NS_OPTIONS(NSUInteger, SDImageCacheOptions) {
*/
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key;
/**
* Synchronously query the cache (memory and or disk) after checking the memory cache. With the options and context which may effect the image generation. (Such as transformer, animated image, thumbnail, etc)
*
* @param key The unique key used to store the image
* @param options A mask to specify options to use for this cache query
* @param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
* @return The image for the given key, or nil if not found.
*/
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context;;
#pragma mark - Remove Ops
/**

View File

@ -9,11 +9,11 @@
#import "SDImageCache.h"
#import "NSImage+Compatibility.h"
#import "SDImageCodersManager.h"
#import "SDImageTransformer.h"
#import "SDImageCoderHelper.h"
#import "SDAnimatedImage.h"
#import "UIImage+MemoryCacheCost.h"
#import "UIImage+Metadata.h"
#import "UIImage+ExtendedCacheData.h"
@interface SDImageCache ()
@ -186,17 +186,55 @@
dispatch_async(self.ioQueue, ^{
@autoreleasepool {
NSData *data = imageData;
if (!data && [image conformsToProtocol:@protocol(SDAnimatedImage)]) {
// If image is custom animated image class, prefer its original animated data
data = [((id<SDAnimatedImage>)image) animatedImageData];
}
if (!data && image) {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
SDImageFormat format;
if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
// Check image's associated image format, may return .undefined
SDImageFormat format = image.sd_imageFormat;
if (format == SDImageFormatUndefined) {
// If image is animated, use GIF (APNG may be better, but has bugs before macOS 10.14)
if (image.sd_isAnimated) {
format = SDImageFormatGIF;
} else {
// If we do not have any data to detect image format, check whether it contains alpha channel to use PNG or JPEG format
if ([SDImageCoderHelper CGImageContainsAlpha:image.CGImage]) {
format = SDImageFormatPNG;
} else {
format = SDImageFormatJPEG;
}
}
}
data = [[SDImageCodersManager sharedManager] encodedDataWithImage:image format:format options:nil];
}
[self _storeImageDataToDisk:data forKey:key];
if (image) {
// Check extended data
id extendedObject = image.sd_extendedObject;
if ([extendedObject conformsToProtocol:@protocol(NSCoding)]) {
NSData *extendedData;
if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
NSError *error;
extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject requiringSecureCoding:NO error:&error];
if (error) {
NSLog(@"NSKeyedArchiver archive failed with error: %@", error);
}
} else {
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
extendedData = [NSKeyedArchiver archivedDataWithRootObject:extendedObject];
#pragma clang diagnostic pop
} @catch (NSException *exception) {
NSLog(@"NSKeyedArchiver archive failed with exception: %@", exception);
}
}
if (extendedData) {
[self.diskCache setExtendedData:extendedData forKey:key];
}
}
}
}
if (completionBlock) {
@ -275,6 +313,17 @@
return [self.diskCache containsDataForKey:key];
}
- (void)diskImageDataQueryForKey:(NSString *)key completion:(SDImageCacheQueryDataCompletionBlock)completionBlock {
dispatch_async(self.ioQueue, ^{
NSData *imageData = [self diskImageDataBySearchingAllPathsForKey:key];
if (completionBlock) {
dispatch_async(dispatch_get_main_queue(), ^{
completionBlock(imageData);
});
}
});
}
- (nullable NSData *)diskImageDataForKey:(nullable NSString *)key {
if (!key) {
return nil;
@ -292,7 +341,12 @@
}
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key {
UIImage *diskImage = [self diskImageForKey:key];
return [self imageFromDiskCacheForKey:key options:0 context:nil];
}
- (nullable UIImage *)imageFromDiskCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context {
NSData *data = [self diskImageDataForKey:key];
UIImage *diskImage = [self diskImageForKey:key data:data options:options context:context];
if (diskImage && self.config.shouldCacheImagesInMemory) {
NSUInteger cost = diskImage.sd_memoryCost;
[self.memoryCache setObject:diskImage forKey:key cost:cost];
@ -302,6 +356,10 @@
}
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key {
return [self imageFromCacheForKey:key options:0 context:nil];
}
- (nullable UIImage *)imageFromCacheForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context {
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
if (image) {
@ -309,7 +367,7 @@
}
// Second check the disk cache...
image = [self imageFromDiskCacheForKey:key];
image = [self imageFromDiskCacheForKey:key options:options context:context];
return image;
}
@ -346,6 +404,32 @@
- (nullable UIImage *)diskImageForKey:(nullable NSString *)key data:(nullable NSData *)data options:(SDImageCacheOptions)options context:(SDWebImageContext *)context {
if (data) {
UIImage *image = SDImageCacheDecodeImageData(data, key, [[self class] imageOptionsFromCacheOptions:options], context);
if (image) {
// Check extended data
NSData *extendedData = [self.diskCache extendedDataForKey:key];
if (extendedData) {
id extendedObject;
if (@available(iOS 11, tvOS 11, macOS 10.13, watchOS 4, *)) {
NSError *error;
NSKeyedUnarchiver *unarchiver = [[NSKeyedUnarchiver alloc] initForReadingFromData:extendedData error:&error];
unarchiver.requiresSecureCoding = NO;
extendedObject = [unarchiver decodeTopLevelObjectForKey:NSKeyedArchiveRootObjectKey error:&error];
if (error) {
NSLog(@"NSKeyedUnarchiver unarchive failed with error: %@", error);
}
} else {
@try {
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
extendedObject = [NSKeyedUnarchiver unarchiveObjectWithData:extendedData];
#pragma clang diagnostic pop
} @catch (NSException *exception) {
NSLog(@"NSKeyedUnarchiver unarchive failed with exception: %@", exception);
}
}
image.sd_extendedObject = extendedObject;
}
}
return image;
} else {
return nil;
@ -361,22 +445,29 @@
}
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
return [self queryCacheOperationForKey:key options:options context:context cacheType:SDImageCacheTypeAll done:doneBlock];
}
- (nullable NSOperation *)queryCacheOperationForKey:(nullable NSString *)key options:(SDImageCacheOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType done:(nullable SDImageCacheQueryCompletionBlock)doneBlock {
if (!key) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (transformer) {
// grab the transformed disk image if transformer provided
NSString *transformerKey = [transformer transformerKey];
key = SDTransformedKeyForKey(key, transformerKey);
// Invalid cache type
if (queryCacheType == SDImageCacheTypeNone) {
if (doneBlock) {
doneBlock(nil, nil, SDImageCacheTypeNone);
}
return nil;
}
// First check the in-memory cache...
UIImage *image = [self imageFromMemoryCacheForKey:key];
UIImage *image;
if (queryCacheType != SDImageCacheTypeDisk) {
image = [self imageFromMemoryCacheForKey:key];
}
if (image) {
if (options & SDImageCacheDecodeFirstFrameOnly) {
@ -399,7 +490,7 @@
}
}
BOOL shouldQueryMemoryOnly = (image && !(options & SDImageCacheQueryMemoryData));
BOOL shouldQueryMemoryOnly = (queryCacheType == SDImageCacheTypeMemory) || (image && !(options & SDImageCacheQueryMemoryData));
if (shouldQueryMemoryOnly) {
if (doneBlock) {
doneBlock(image, nil, SDImageCacheTypeMemory);
@ -634,6 +725,10 @@
#pragma mark - SDImageCache
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock];
}
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(nullable SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock {
SDImageCacheOptions cacheOptions = 0;
if (options & SDWebImageQueryMemoryData) cacheOptions |= SDImageCacheQueryMemoryData;
if (options & SDWebImageQueryMemoryDataSync) cacheOptions |= SDImageCacheQueryMemoryDataSync;
@ -644,7 +739,7 @@
if (options & SDWebImagePreloadAllFrames) cacheOptions |= SDImageCachePreloadAllFrames;
if (options & SDWebImageMatchAnimatedImageClass) cacheOptions |= SDImageCacheMatchAnimatedImageClass;
return [self queryCacheOperationForKey:key options:cacheOptions context:context done:completionBlock];
return [self queryCacheOperationForKey:key options:cacheOptions context:context cacheType:cacheType done:completionBlock];
}
- (void)storeImage:(UIImage *)image imageData:(NSData *)imageData forKey:(nullable NSString *)key cacheType:(SDImageCacheType)cacheType completion:(nullable SDWebImageNoParamsBlock)completionBlock {

View File

@ -12,13 +12,21 @@
/// Image Cache Expire Type
typedef NS_ENUM(NSUInteger, SDImageCacheConfigExpireType) {
/**
* When the image is accessed it will update this value
* When the image cache is accessed it will update this value
*/
SDImageCacheConfigExpireTypeAccessDate,
/**
* The image was obtained from the disk cache (Default)
* When the image cache is created or modified it will update this value (Default)
*/
SDImageCacheConfigExpireTypeModificationDate
SDImageCacheConfigExpireTypeModificationDate,
/**
* When the image cache is created it will update this value
*/
SDImageCacheConfigExpireTypeCreationDate,
/**
* When the image cache is created, modified, renamed, file attribute updated (like permission, xattr) it will update this value
*/
SDImageCacheConfigExpireTypeChangeDate,
};
/**

View File

@ -36,6 +36,7 @@ typedef NS_ENUM(NSInteger, SDImageCacheType) {
};
typedef void(^SDImageCacheCheckCompletionBlock)(BOOL isInCache);
typedef void(^SDImageCacheQueryDataCompletionBlock)(NSData * _Nullable data);
typedef void(^SDImageCacheCalculateSizeBlock)(NSUInteger fileCount, NSUInteger totalSize);
typedef NSString * _Nullable (^SDImageCacheAdditionalCachePathBlock)(NSString * _Nonnull key);
typedef void(^SDImageCacheQueryCompletionBlock)(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType);
@ -76,6 +77,23 @@ FOUNDATION_EXPORT UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonn
context:(nullable SDWebImageContext *)context
completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock;
/**
Query the cached image from image cache for given key. The operation can be used to cancel the query.
If image is cached in memory, completion is called synchronously, else aynchronously and depends on the options arg (See `SDWebImageQueryDiskSync`)
@param key The image cache key
@param options A mask to specify options to use for this query
@param context A context contains different options to perform specify changes or processes, see `SDWebImageContextOption`. This hold the extra objects which `options` enum can not hold.
@param cacheType Specify where to query the cache from. By default we use `.all`, which means both memory cache and disk cache. You can choose to query memory only or disk only as well. Pass `.none` is invalid and callback with nil immediatelly.
@param completionBlock The completion block. Will not get called if the operation is cancelled
@return The operation for this query
*/
- (nullable id<SDWebImageOperation>)queryImageForKey:(nullable NSString *)key
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
cacheType:(SDImageCacheType)cacheType
completion:(nullable SDImageCacheQueryCompletionBlock)completionBlock;
/**
Store the image into image cache for the given key. If cache type is memory only, completion is called synchronously, else aynchronously.

View File

@ -18,11 +18,32 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
if (context) {
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
coderOptions = [mutableCoderOptions copy];
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
NSValue *thumbnailSizeValue;
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
if (shouldScaleDown) {
CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
CGFloat dimension = ceil(sqrt(thumbnailPixels));
thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
}
if (context[SDWebImageContextImageThumbnailPixelSize]) {
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
}
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
mutableCoderOptions[SDImageCoderWebImageContext] = context;
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
// Grab the image coder
id<SDImageCoder> imageCoder;
if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) {
imageCoder = context[SDWebImageContextImageCoder];
} else {
imageCoder = [SDImageCodersManager sharedManager];
}
if (!decodeFirstFrame) {
@ -44,7 +65,7 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
}
}
if (!image) {
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions];
image = [imageCoder decodedImageWithData:imageData options:coderOptions];
}
if (image) {
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
@ -56,12 +77,7 @@ UIImage * _Nullable SDImageCacheDecodeImageData(NSData * _Nonnull imageData, NSS
shouldDecode = NO;
}
if (shouldDecode) {
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
if (shouldScaleDown) {
image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
} else {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
image = [SDImageCoderHelper decodedImageWithImage:image];
}
}

View File

@ -85,6 +85,10 @@
#pragma mark - SDImageCache
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock {
return [self queryImageForKey:key options:options context:context cacheType:SDImageCacheTypeAll completion:completionBlock];
}
- (id<SDWebImageOperation>)queryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)cacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock {
if (!key) {
return nil;
}
@ -93,30 +97,30 @@
if (count == 0) {
return nil;
} else if (count == 1) {
return [caches.firstObject queryImageForKey:key options:options context:context completion:completionBlock];
return [caches.firstObject queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
switch (self.queryOperationPolicy) {
case SDImageCachesManagerOperationPolicyHighestOnly: {
id<SDImageCache> cache = caches.lastObject;
return [cache queryImageForKey:key options:options context:context completion:completionBlock];
return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyLowestOnly: {
id<SDImageCache> cache = caches.firstObject;
return [cache queryImageForKey:key options:options context:context completion:completionBlock];
return [cache queryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock];
}
break;
case SDImageCachesManagerOperationPolicyConcurrent: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self concurrentQueryImageForKey:key options:options context:context completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
[self concurrentQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
return operation;
}
break;
case SDImageCachesManagerOperationPolicySerial: {
SDImageCachesManagerOperation *operation = [SDImageCachesManagerOperation new];
[operation beginWithTotalCount:caches.count];
[self serialQueryImageForKey:key options:options context:context completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
[self serialQueryImageForKey:key options:options context:context cacheType:cacheType completion:completionBlock enumerator:caches.reverseObjectEnumerator operation:operation];
return operation;
}
break;
@ -279,11 +283,11 @@
#pragma mark - Concurrent Operation
- (void)concurrentQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
- (void)concurrentQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
for (id<SDImageCache> cache in enumerator) {
[cache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
[cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
if (operation.isCancelled) {
// Cancelled
return;
@ -422,7 +426,7 @@
#pragma mark - Serial Operation
- (void)serialQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
- (void)serialQueryImageForKey:(NSString *)key options:(SDWebImageOptions)options context:(SDWebImageContext *)context cacheType:(SDImageCacheType)queryCacheType completion:(SDImageCacheQueryCompletionBlock)completionBlock enumerator:(NSEnumerator<id<SDImageCache>> *)enumerator operation:(SDImageCachesManagerOperation *)operation {
NSParameterAssert(enumerator);
NSParameterAssert(operation);
id<SDImageCache> cache = enumerator.nextObject;
@ -435,7 +439,7 @@
return;
}
@weakify(self);
[cache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
[cache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable image, NSData * _Nullable data, SDImageCacheType cacheType) {
@strongify(self);
if (operation.isCancelled) {
// Cancelled
@ -455,7 +459,7 @@
return;
}
// Next
[self serialQueryImageForKey:key options:options context:context completion:completionBlock enumerator:enumerator operation:operation];
[self serialQueryImageForKey:key options:options context:context cacheType:queryCacheType completion:completionBlock enumerator:enumerator operation:operation];
}];
}

View File

@ -21,12 +21,30 @@ typedef NSMutableDictionary<SDImageCoderOption, id> SDImageCoderMutableOptions;
@note works for `SDImageCoder`.
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeFirstFrameOnly;
/**
A CGFloat value which is greater than or equal to 1.0. This value specify the image scale factor for decoding. If not provide, use 1.0. (NSNumber)
@note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`.
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeScaleFactor;
/**
A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format).
Defaults to YES.
@note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`.
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodePreserveAspectRatio;
/**
A CGSize value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.preserveAspectRatio`) the value size.
Defaults to CGSizeZero, which means no thumbnail generation at all.
@note Supports for animated image as well.
@note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
@note works for `SDImageCoder`, `SDProgressiveImageCoder`, `SDAnimatedImageCoder`.
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderDecodeThumbnailPixelSize;
// These options are for image encoding
/**
A Boolean value indicating whether to encode the first frame only for animated image during encoding. (NSNumber). If not provide, encode animated image if need.
@ -39,13 +57,43 @@ FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeFirstFrame
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeCompressionQuality;
/**
A UIColor(NSColor) value to used for non-alpha image encoding when the input image has alpha channel, the background color will be used to compose the alpha one. If not provide, use white color.
@note works for `SDImageCoder`
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeBackgroundColor;
/**
A CGSize value indicating the max image resolution in pixels during encoding. For vector image, this also effect the output vector data information about width and height. The encoder will not generate the encoded image larger than this limit. Note it always use the aspect ratio of input image..
Defaults to CGSizeZero, which means no max size limit at all.
@note Supports for animated image as well.
@note The ouput image's width is limited to pixel size's width, the output image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
@note works for `SDImageCoder`
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeMaxPixelSize;
/**
A NSUInteger value specify the max ouput data bytes size after encoding. Some lossy format like JPEG/HEIF supports the hint for codec to automatically reduce the quality and match the file size you want. Note this option will override the `SDImageCoderEncodeCompressionQuality`, because now the quality is decided by the encoder. (NSNumber)
@note This is a hint, no gurantee for output size because of compression algorithm limit. And this options does not works for vector images.
@note works for `SDImageCoder`
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeMaxFileSize;
/**
A Boolean value indicating the encoding format should contains a thumbnail image into the output data. Only some of image format (like JPEG/HEIF/AVIF) support this behavior. The embed thumbnail will be used during next time thumbnail decoding (provided `.thumbnailPixelSize`), which is faster than full image thumbnail decoding. (NSNumber)
Defaults to NO, which does not embed any thumbnail.
@note The thumbnail image's pixel size is not defined, the encoder can choose the proper pixel size which is suitable for encoding quality.
@note works for `SDImageCoder`
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderEncodeEmbedThumbnail;
/**
A SDWebImageContext object which hold the original context options from top-level API. (SDWebImageContext)
This option is ignored for all built-in coders and take no effect.
But this may be useful for some custom coders, because some business logic may dependent on things other than image or image data inforamtion only.
See `SDWebImageContext` for more detailed information.
*/
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext;
FOUNDATION_EXPORT SDImageCoderOption _Nonnull const SDImageCoderWebImageContext API_DEPRECATED("The coder component will be seperated from Core subspec in the future. Update your code to not rely on this context option.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));;
#pragma mark - Coder
/**

View File

@ -10,8 +10,14 @@
SDImageCoderOption const SDImageCoderDecodeFirstFrameOnly = @"decodeFirstFrameOnly";
SDImageCoderOption const SDImageCoderDecodeScaleFactor = @"decodeScaleFactor";
SDImageCoderOption const SDImageCoderDecodePreserveAspectRatio = @"decodePreserveAspectRatio";
SDImageCoderOption const SDImageCoderDecodeThumbnailPixelSize = @"decodeThumbnailPixelSize";
SDImageCoderOption const SDImageCoderEncodeFirstFrameOnly = @"encodeFirstFrameOnly";
SDImageCoderOption const SDImageCoderEncodeCompressionQuality = @"encodeCompressionQuality";
SDImageCoderOption const SDImageCoderEncodeBackgroundColor = @"encodeBackgroundColor";
SDImageCoderOption const SDImageCoderEncodeMaxPixelSize = @"encodeMaxPixelSize";
SDImageCoderOption const SDImageCoderEncodeMaxFileSize = @"encodeMaxFileSize";
SDImageCoderOption const SDImageCoderEncodeEmbedThumbnail = @"encodeEmbedThumbnail";
SDImageCoderOption const SDImageCoderWebImageContext = @"webImageContext";

View File

@ -73,6 +73,16 @@
*/
+ (CGImageRef _Nullable)CGImageCreateDecoded:(_Nonnull CGImageRef)cgImage orientation:(CGImagePropertyOrientation)orientation CF_RETURNS_RETAINED;
/**
Create a scaled CGImage by the provided CGImage and size. This follows The Create Rule and you are response to call release after usage.
It will detect whether the image size matching the scale size, if not, stretch the image to the target size.
@param cgImage The CGImage
@param size The scale size in pixel.
@return A new created scaled image
*/
+ (CGImageRef _Nullable)CGImageCreateScaled:(_Nonnull CGImageRef)cgImage size:(CGSize)size CF_RETURNS_RETAINED;
/**
Return the decoded image by the provided image. This one unlike `CGImageCreateDecoded:`, will not decode the image which contains alpha channel or animated image
@param image The image to be decoded
@ -89,6 +99,12 @@
*/
+ (UIImage * _Nullable)decodedAndScaledDownImageWithImage:(UIImage * _Nullable)image limitBytes:(NSUInteger)bytes;
/**
Control the default limit bytes to scale down larget images.
This value must be larger than or equal to 1MB. Defaults to 60MB on iOS/tvOS, 90MB on macOS, 30MB on watchOS.
*/
@property (class, readwrite) NSUInteger defaultScaleDownLimitBytes;
#if SD_UIKIT || SD_WATCH
/**
Convert an EXIF image orientation to an iOS one.

View File

@ -12,35 +12,35 @@
#import "NSData+ImageContentType.h"
#import "SDAnimatedImageRep.h"
#import "UIImage+ForceDecode.h"
#import "SDAssociatedObject.h"
#import "UIImage+Metadata.h"
#import "SDInternalMacros.h"
#import <Accelerate/Accelerate.h>
static inline size_t SDByteAlign(size_t size, size_t alignment) {
return ((size + (alignment - 1)) / alignment) * alignment;
}
#if SD_UIKIT || SD_WATCH
static const size_t kBytesPerPixel = 4;
static const size_t kBitsPerComponent = 8;
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
/*
* Defines the maximum size in MB of the decoded image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 60.
* Suggested value for iPad2 and iPhone 4: 120.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 30.
*/
static const CGFloat kDestImageSizeMB = 60.f;
/*
* Defines the maximum size in MB of a tile used to decode image when the flag `SDWebImageScaleDownLargeImages` is set
* Suggested value for iPad1 and iPhone 3GS: 20.
* Suggested value for iPad2 and iPhone 4: 40.
* Suggested value for iPhone 3G and iPod 2 and earlier devices: 10.
*/
static const CGFloat kSourceImageTileSizeMB = 20.f;
static const CGFloat kBytesPerMB = 1024.0f * 1024.0f;
static const CGFloat kPixelsPerMB = kBytesPerMB / kBytesPerPixel;
static const CGFloat kDestTotalPixels = kDestImageSizeMB * kPixelsPerMB;
static const CGFloat kTileTotalPixels = kSourceImageTileSizeMB * kPixelsPerMB;
#if SD_MAC
static CGFloat kDestImageLimitBytes = 90.f * kBytesPerMB;
#elif SD_UIKIT
static CGFloat kDestImageLimitBytes = 60.f * kBytesPerMB;
#elif SD_WATCH
static CGFloat kDestImageLimitBytes = 30.f * kBytesPerMB;
#endif
static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to overlap the seems where tiles meet.
#endif
@implementation SDImageCoderHelper
@ -277,10 +277,57 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return newImageRef;
}
+ (CGImageRef)CGImageCreateScaled:(CGImageRef)cgImage size:(CGSize)size {
if (!cgImage) {
return NULL;
}
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
if (width == size.width && height == size.height) {
CGImageRetain(cgImage);
return cgImage;
}
__block vImage_Buffer input_buffer = {}, output_buffer = {};
@onExit {
if (input_buffer.data) free(input_buffer.data);
if (output_buffer.data) free(output_buffer.data);
};
BOOL hasAlpha = [self CGImageContainsAlpha:cgImage];
// iOS display alpha info (BGRA8888/BGRX8888)
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
vImage_CGImageFormat format = (vImage_CGImageFormat) {
.bitsPerComponent = 8,
.bitsPerPixel = 32,
.colorSpace = NULL,
.bitmapInfo = bitmapInfo,
.version = 0,
.decode = NULL,
.renderingIntent = kCGRenderingIntentDefault,
};
vImage_Error a_ret = vImageBuffer_InitWithCGImage(&input_buffer, &format, NULL, cgImage, kvImageNoFlags);
if (a_ret != kvImageNoError) return NULL;
output_buffer.width = MAX(size.width, 0);
output_buffer.height = MAX(size.height, 0);
output_buffer.rowBytes = SDByteAlign(output_buffer.width * 4, 64);
output_buffer.data = malloc(output_buffer.rowBytes * output_buffer.height);
if (!output_buffer.data) return NULL;
vImage_Error ret = vImageScale_ARGB8888(&input_buffer, &output_buffer, NULL, kvImageHighQualityResampling);
if (ret != kvImageNoError) return NULL;
CGImageRef outputImage = vImageCreateCGImageFromBuffer(&output_buffer, &format, NULL, NULL, kvImageNoFlags, &ret);
if (ret != kvImageNoError) {
CGImageRelease(outputImage);
return NULL;
}
return outputImage;
}
+ (UIImage *)decodedImageWithImage:(UIImage *)image {
#if SD_MAC
return image;
#else
if (![self shouldDecodeImage:image]) {
return image;
}
@ -289,18 +336,18 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
if (!imageRef) {
return image;
}
#if SD_MAC
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
UIImage *decodedImage = [[UIImage alloc] initWithCGImage:imageRef scale:image.scale orientation:image.imageOrientation];
CGImageRelease(imageRef);
decodedImage.sd_isDecoded = YES;
decodedImage.sd_imageFormat = image.sd_imageFormat;
return decodedImage;
#endif
CGImageRelease(imageRef);
SDImageCopyAssociatedObject(image, decodedImage);
decodedImage.sd_isDecoded = YES;
return decodedImage;
}
+ (UIImage *)decodedAndScaledDownImageWithImage:(UIImage *)image limitBytes:(NSUInteger)bytes {
#if SD_MAC
return image;
#else
if (![self shouldDecodeImage:image]) {
return image;
}
@ -311,13 +358,11 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
CGFloat destTotalPixels;
CGFloat tileTotalPixels;
if (bytes > 0) {
destTotalPixels = bytes / kBytesPerPixel;
tileTotalPixels = destTotalPixels / 3;
} else {
destTotalPixels = kDestTotalPixels;
tileTotalPixels = kTileTotalPixels;
if (bytes == 0) {
bytes = kDestImageLimitBytes;
}
destTotalPixels = bytes / kBytesPerPixel;
tileTotalPixels = destTotalPixels / 3;
CGContextRef destContext;
// autorelease the bitmap context and all vars to help system to free memory when there are memory warning.
@ -420,16 +465,30 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
if (destImageRef == NULL) {
return image;
}
#if SD_MAC
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:kCGImagePropertyOrientationUp];
#else
UIImage *destImage = [[UIImage alloc] initWithCGImage:destImageRef scale:image.scale orientation:image.imageOrientation];
#endif
CGImageRelease(destImageRef);
if (destImage == nil) {
return image;
}
SDImageCopyAssociatedObject(image, destImage);
destImage.sd_isDecoded = YES;
destImage.sd_imageFormat = image.sd_imageFormat;
return destImage;
}
#endif
}
+ (NSUInteger)defaultScaleDownLimitBytes {
return kDestImageLimitBytes;
}
+ (void)setDefaultScaleDownLimitBytes:(NSUInteger)defaultScaleDownLimitBytes {
if (defaultScaleDownLimitBytes < kBytesPerMB) {
return;
}
kDestImageLimitBytes = defaultScaleDownLimitBytes;
}
#if SD_UIKIT || SD_WATCH
@ -503,18 +562,21 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
#endif
#pragma mark - Helper Fuction
#if SD_UIKIT || SD_WATCH
+ (BOOL)shouldDecodeImage:(nullable UIImage *)image {
// Avoid extra decode
if (image.sd_isDecoded) {
return NO;
}
// Prevent "CGBitmapContextCreateImage: invalid context 0x0" error
if (image == nil) {
return NO;
}
// Avoid extra decode
if (image.sd_isDecoded) {
return NO;
}
// do not decode animated images
if (image.images != nil) {
if (image.sd_isAnimated) {
return NO;
}
// do not decode vector images
if (image.sd_isVector) {
return NO;
}
@ -533,11 +595,10 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return NO;
}
CGFloat destTotalPixels;
if (bytes > 0) {
destTotalPixels = bytes / kBytesPerPixel;
} else {
destTotalPixels = kDestTotalPixels;
if (bytes == 0) {
bytes = kDestImageLimitBytes;
}
destTotalPixels = bytes / kBytesPerPixel;
if (destTotalPixels <= kPixelsPerMB) {
// Too small to scale down
return NO;
@ -551,7 +612,6 @@ static const CGFloat kDestSeemOverlap = 2.0f; // the numbers of pixels to over
return shouldScaleDown;
}
#endif
static inline CGAffineTransform SDCGContextTransformFromOrientation(CGImagePropertyOrientation orientation, CGSize size) {
// Inspiration from @libfeihu

View File

@ -13,6 +13,7 @@
These following graphics context method are provided to easily write cross-platform(AppKit/UIKit) code.
For UIKit, these methods just call the same method in `UIGraphics.h`. See the documentation for usage.
For AppKit, these methods use `NSGraphicsContext` to create image context and match the behavior like UIKit.
@note If you don't care bitmap format (ARGB8888) and just draw image, use `SDGraphicsImageRenderer` instead. It's more performant on RAM usage.`
*/
/// Returns the current graphics context.

View File

@ -12,6 +12,12 @@
#import "NSData+ImageContentType.h"
#import "SDImageCoderHelper.h"
#import "SDAnimatedImageRep.h"
#import "UIImage+ForceDecode.h"
// Specify DPI for vector format in CGImageSource, like PDF
static NSString * kSDCGImageSourceRasterizationDPI = @"kCGImageSourceRasterizationDPI";
// Specify File Size for lossy format encoding, like JPEG
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
@interface SDImageIOCoderFrame : NSObject
@ -32,6 +38,8 @@
NSUInteger _frameCount;
NSArray<SDImageIOCoderFrame *> *_frames;
BOOL _finished;
BOOL _preserveAspectRatio;
CGSize _thumbnailSize;
}
- (void)dealloc
@ -114,8 +122,12 @@
}
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(CGImageSourceRef)source {
NSDictionary *options = @{
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
};
NSTimeInterval frameDuration = 0.1;
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, nil);
CFDictionaryRef cfFrameProperties = CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
if (!cfFrameProperties) {
return frameDuration;
}
@ -145,6 +157,93 @@
return frameDuration;
}
+ (UIImage *)createFrameAtIndex:(NSUInteger)index source:(CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(NSDictionary *)options {
// Some options need to pass to `CGImageSourceCopyPropertiesAtIndex` before `CGImageSourceCreateImageAtIndex`, or ImageIO will ignore them because they parse once :)
// Parse the image properties
NSDictionary *properties = (__bridge_transfer NSDictionary *)CGImageSourceCopyPropertiesAtIndex(source, index, (__bridge CFDictionaryRef)options);
NSUInteger pixelWidth = [properties[(__bridge NSString *)kCGImagePropertyPixelWidth] unsignedIntegerValue];
NSUInteger pixelHeight = [properties[(__bridge NSString *)kCGImagePropertyPixelHeight] unsignedIntegerValue];
CGImagePropertyOrientation exifOrientation = (CGImagePropertyOrientation)[properties[(__bridge NSString *)kCGImagePropertyOrientation] unsignedIntegerValue];
if (!exifOrientation) {
exifOrientation = kCGImagePropertyOrientationUp;
}
CFStringRef uttype = CGImageSourceGetType(source);
// Check vector format
BOOL isVector = NO;
if ([NSData sd_imageFormatFromUTType:uttype] == SDImageFormatPDF) {
isVector = YES;
}
NSMutableDictionary *decodingOptions;
if (options) {
decodingOptions = [NSMutableDictionary dictionaryWithDictionary:options];
} else {
decodingOptions = [NSMutableDictionary dictionary];
}
CGImageRef imageRef;
if (thumbnailSize.width == 0 || thumbnailSize.height == 0 || pixelWidth == 0 || pixelHeight == 0 || (pixelWidth <= thumbnailSize.width && pixelHeight <= thumbnailSize.height)) {
if (isVector) {
if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
// Provide the default pixel count for vector images, simply just use the screen size
#if SD_WATCH
thumbnailSize = WKInterfaceDevice.currentDevice.screenBounds.size;
#elif SD_UIKIT
thumbnailSize = UIScreen.mainScreen.bounds.size;
#elif SD_MAC
thumbnailSize = NSScreen.mainScreen.frame.size;
#endif
}
CGFloat maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
NSUInteger DPIPerPixel = 2;
NSUInteger rasterizationDPI = maxPixelSize * DPIPerPixel;
decodingOptions[kSDCGImageSourceRasterizationDPI] = @(rasterizationDPI);
}
imageRef = CGImageSourceCreateImageAtIndex(source, index, (__bridge CFDictionaryRef)decodingOptions);
} else {
decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailWithTransform] = @(preserveAspectRatio);
CGFloat maxPixelSize;
if (preserveAspectRatio) {
CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat thumbnailRatio = thumbnailSize.width / thumbnailSize.height;
if (pixelRatio > thumbnailRatio) {
maxPixelSize = thumbnailSize.width;
} else {
maxPixelSize = thumbnailSize.height;
}
} else {
maxPixelSize = MAX(thumbnailSize.width, thumbnailSize.height);
}
decodingOptions[(__bridge NSString *)kCGImageSourceThumbnailMaxPixelSize] = @(maxPixelSize);
decodingOptions[(__bridge NSString *)kCGImageSourceCreateThumbnailFromImageIfAbsent] = @(YES);
imageRef = CGImageSourceCreateThumbnailAtIndex(source, index, (__bridge CFDictionaryRef)decodingOptions);
}
if (!imageRef) {
return nil;
}
if (thumbnailSize.width > 0 && thumbnailSize.height > 0) {
if (preserveAspectRatio) {
// kCGImageSourceCreateThumbnailWithTransform will apply EXIF transform as well, we should not apply twice
exifOrientation = kCGImagePropertyOrientationUp;
} else {
// `CGImageSourceCreateThumbnailAtIndex` take only pixel dimension, if not `preserveAspectRatio`, we should manual scale to the target size
CGImageRef scaledImageRef = [SDImageCoderHelper CGImageCreateScaled:imageRef size:thumbnailSize];
CGImageRelease(imageRef);
imageRef = scaledImageRef;
}
}
#if SD_UIKIT || SD_WATCH
UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:exifOrientation];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:imageOrientation];
#else
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:exifOrientation];
#endif
CGImageRelease(imageRef);
return image;
}
#pragma mark - Decode
- (BOOL)canDecodeFromData:(nullable NSData *)data {
return ([NSData sd_imageFormatForImageData:data] == self.class.imageFormat);
@ -160,14 +259,34 @@
scale = MAX([scaleFactor doubleValue], 1);
}
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
imageRep.size = size;
NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
[animatedImage addRepresentation:imageRep];
return animatedImage;
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
BOOL preserveAspectRatio = YES;
NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
if (preserveAspectRatioValue != nil) {
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
#if SD_MAC
// If don't use thumbnail, prefers the built-in generation of frames (GIF/APNG)
// Which decode frames in time and reduce memory usage
if (thumbnailSize.width == 0 || thumbnailSize.height == 0) {
SDAnimatedImageRep *imageRep = [[SDAnimatedImageRep alloc] initWithData:data];
NSSize size = NSMakeSize(imageRep.pixelsWide / scale, imageRep.pixelsHigh / scale);
imageRep.size = size;
NSImage *animatedImage = [[NSImage alloc] initWithSize:size];
[animatedImage addRepresentation:imageRep];
return animatedImage;
}
#endif
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
@ -178,19 +297,17 @@
BOOL decodeFirstFrame = [options[SDImageCoderDecodeFirstFrameOnly] boolValue];
if (decodeFirstFrame || count <= 1) {
animatedImage = [[UIImage alloc] initWithData:data scale:scale];
animatedImage = [self.class createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
} else {
NSMutableArray<SDImageFrame *> *frames = [NSMutableArray array];
for (size_t i = 0; i < count; i++) {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(source, i, NULL);
if (!imageRef) {
UIImage *image = [self.class createFrameAtIndex:i source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
if (!image) {
continue;
}
NSTimeInterval duration = [self.class frameDurationAtIndex:i source:source];
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:scale orientation:UIImageOrientationUp];
CGImageRelease(imageRef);
SDImageFrame *frame = [SDImageFrame frameWithImage:image duration:duration];
[frames addObject:frame];
@ -205,7 +322,6 @@
CFRelease(source);
return animatedImage;
#endif
}
#pragma mark - Progressive Decode
@ -225,6 +341,22 @@
scale = MAX([scaleFactor doubleValue], 1);
}
_scale = scale;
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
_thumbnailSize = thumbnailSize;
BOOL preserveAspectRatio = YES;
NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
if (preserveAspectRatioValue != nil) {
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
_preserveAspectRatio = preserveAspectRatio;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
@ -246,7 +378,11 @@
CGImageSourceUpdateData(_imageSource, (__bridge CFDataRef)data, finished);
if (_width + _height == 0) {
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, NULL);
NSDictionary *options = @{
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
};
CFDictionaryRef properties = CGImageSourceCopyPropertiesAtIndex(_imageSource, 0, (__bridge CFDictionaryRef)options);
if (properties) {
CFTypeRef val = CFDictionaryGetValue(properties, kCGImagePropertyPixelHeight);
if (val) CFNumberGetValue(val, kCFNumberLongType, &_height);
@ -265,20 +401,13 @@
if (_width + _height > 0) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
if (partialImageRef) {
CGFloat scale = _scale;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
#if SD_UIKIT || SD_WATCH
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:UIImageOrientationUp];
#else
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:kCGImagePropertyOrientationUp];
#endif
CGImageRelease(partialImageRef);
CGFloat scale = _scale;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
image = [self.class createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil];
if (image) {
image.sd_imageFormat = self.class.imageFormat;
}
}
@ -295,6 +424,11 @@
if (!image) {
return nil;
}
CGImageRef imageRef = image.CGImage;
if (!imageRef) {
// Earily return, supports CGImage only
return nil;
}
if (format != self.class.imageFormat) {
return nil;
@ -312,29 +446,69 @@
return nil;
}
NSMutableDictionary *properties = [NSMutableDictionary dictionary];
// Encoding Options
double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
if (backgroundColor) {
properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
}
CGSize maxPixelSize = CGSizeZero;
NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
if (maxPixelSizeValue != nil) {
#if SD_MAC
maxPixelSize = maxPixelSizeValue.sizeValue;
#else
maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif
}
NSUInteger pixelWidth = CGImageGetWidth(imageRef);
NSUInteger pixelHeight = CGImageGetHeight(imageRef);
CGFloat finalPixelSize = 0;
if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > 0 && pixelHeight > 0) {
CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
if (pixelRatio > maxPixelSizeRatio) {
finalPixelSize = maxPixelSize.width;
} else {
finalPixelSize = maxPixelSize.height;
}
properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
}
NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
if (maxFileSize > 0) {
properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
// Remove the quality if we have file size limit
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
}
BOOL embedThumbnail = NO;
if (options[SDImageCoderEncodeEmbedThumbnail]) {
embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
}
properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
BOOL encodeFirstFrame = [options[SDImageCoderEncodeFirstFrameOnly] boolValue];
if (encodeFirstFrame || frames.count == 0) {
// for static single images
CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
} else {
// for animated images
NSUInteger loopCount = image.sd_imageLoopCount;
NSDictionary *containerProperties = @{self.class.loopCountProperty : @(loopCount)};
properties[self.class.dictionaryProperty] = containerProperties;
CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)properties);
NSDictionary *containerProperties = @{
self.class.dictionaryProperty: @{self.class.loopCountProperty : @(loopCount)}
};
// container level properties (applies for `CGImageDestinationSetProperties`, not individual frames)
CGImageDestinationSetProperties(imageDestination, (__bridge CFDictionaryRef)containerProperties);
for (size_t i = 0; i < frames.count; i++) {
SDImageFrame *frame = frames[i];
NSTimeInterval frameDuration = frame.duration;
CGImageRef frameImageRef = frame.image.CGImage;
NSDictionary *frameProperties = @{self.class.dictionaryProperty : @{self.class.delayTimeProperty : @(frameDuration)}};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)frameProperties);
properties[self.class.dictionaryProperty] = @{self.class.delayTimeProperty : @(frameDuration)};
CGImageDestinationAddImage(imageDestination, frameImageRef, (__bridge CFDictionaryRef)properties);
}
}
// Finalize the destination.
@ -370,6 +544,22 @@
scale = MAX([scaleFactor doubleValue], 1);
}
_scale = scale;
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
_thumbnailSize = thumbnailSize;
BOOL preserveAspectRatio = YES;
NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
if (preserveAspectRatioValue != nil) {
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
_preserveAspectRatio = preserveAspectRatio;
_imageSource = imageSource;
_imageData = data;
#if SD_UIKIT
@ -421,23 +611,20 @@
}
- (UIImage *)animatedImageFrameAtIndex:(NSUInteger)index {
CGImageRef imageRef = CGImageSourceCreateImageAtIndex(_imageSource, index, NULL);
if (!imageRef) {
if (index >= _frameCount) {
return nil;
}
// Image/IO create CGImage does not decode, so we do this because this is called background queue, this can avoid main queue block when rendering(especially when one more imageViews use the same image instance)
CGImageRef newImageRef = [SDImageCoderHelper CGImageCreateDecoded:imageRef];
if (!newImageRef) {
newImageRef = imageRef;
} else {
CGImageRelease(imageRef);
// Animated Image should not use the CGContext solution to force decode. Prefers to use Image/IO built in method, which is safer and memory friendly, see https://github.com/SDWebImage/SDWebImage/issues/2961
NSDictionary *options = @{
(__bridge NSString *)kCGImageSourceShouldCacheImmediately : @(YES),
(__bridge NSString *)kCGImageSourceShouldCache : @(YES) // Always cache to reduce CPU usage
};
UIImage *image = [self.class createFrameAtIndex:index source:_imageSource scale:_scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:options];
if (!image) {
return nil;
}
#if SD_MAC
UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:kCGImagePropertyOrientationUp];
#else
UIImage *image = [[UIImage alloc] initWithCGImage:newImageRef scale:_scale orientation:UIImageOrientationUp];
#endif
CGImageRelease(newImageRef);
image.sd_imageFormat = self.class.imageFormat;
image.sd_isDecoded = YES;;
return image;
}

View File

@ -12,6 +12,10 @@
#import <ImageIO/ImageIO.h>
#import "UIImage+Metadata.h"
#import "SDImageHEICCoderInternal.h"
#import "SDImageIOAnimatedCoderInternal.h"
// Specify File Size for lossy format encoding, like JPEG
static NSString * kSDCGImageDestinationRequestedFileSize = @"kCGImageDestinationRequestedFileSize";
@implementation SDImageIOCoder {
size_t _width, _height;
@ -19,6 +23,8 @@
CGImageSourceRef _imageSource;
CGFloat _scale;
BOOL _finished;
BOOL _preserveAspectRatio;
CGSize _thumbnailSize;
}
- (void)dealloc {
@ -74,7 +80,33 @@
scale = MAX([scaleFactor doubleValue], 1) ;
}
UIImage *image = [[UIImage alloc] initWithData:data scale:scale];
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
BOOL preserveAspectRatio = YES;
NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
if (preserveAspectRatioValue != nil) {
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
CGImageSourceRef source = CGImageSourceCreateWithData((__bridge CFDataRef)data, NULL);
if (!source) {
return nil;
}
UIImage *image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:source scale:scale preserveAspectRatio:preserveAspectRatio thumbnailSize:thumbnailSize options:nil];
CFRelease(source);
if (!image) {
return nil;
}
image.sd_imageFormat = [NSData sd_imageFormatForImageData:data];
return image;
}
@ -95,6 +127,22 @@
scale = MAX([scaleFactor doubleValue], 1);
}
_scale = scale;
CGSize thumbnailSize = CGSizeZero;
NSValue *thumbnailSizeValue = options[SDImageCoderDecodeThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
}
_thumbnailSize = thumbnailSize;
BOOL preserveAspectRatio = YES;
NSNumber *preserveAspectRatioValue = options[SDImageCoderDecodePreserveAspectRatio];
if (preserveAspectRatioValue != nil) {
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
_preserveAspectRatio = preserveAspectRatio;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(didReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
@ -140,21 +188,13 @@
if (_width + _height > 0) {
// Create the image
CGImageRef partialImageRef = CGImageSourceCreateImageAtIndex(_imageSource, 0, NULL);
if (partialImageRef) {
CGFloat scale = _scale;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
#if SD_UIKIT || SD_WATCH
UIImageOrientation imageOrientation = [SDImageCoderHelper imageOrientationFromEXIFOrientation:_orientation];
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:imageOrientation];
#else
image = [[UIImage alloc] initWithCGImage:partialImageRef scale:scale orientation:_orientation];
#endif
CGImageRelease(partialImageRef);
CGFloat scale = _scale;
NSNumber *scaleFactor = options[SDImageCoderDecodeScaleFactor];
if (scaleFactor != nil) {
scale = MAX([scaleFactor doubleValue], 1);
}
image = [SDImageIOAnimatedCoder createFrameAtIndex:0 source:_imageSource scale:scale preserveAspectRatio:_preserveAspectRatio thumbnailSize:_thumbnailSize options:nil];
if (image) {
CFStringRef uttype = CGImageSourceGetType(_imageSource);
image.sd_imageFormat = [NSData sd_imageFormatFromUTType:uttype];
}
@ -184,9 +224,14 @@
if (!image) {
return nil;
}
CGImageRef imageRef = image.CGImage;
if (!imageRef) {
// Earily return, supports CGImage only
return nil;
}
if (format == SDImageFormatUndefined) {
BOOL hasAlpha = [SDImageCoderHelper CGImageContainsAlpha:image.CGImage];
BOOL hasAlpha = [SDImageCoderHelper CGImageContainsAlpha:imageRef];
if (hasAlpha) {
format = SDImageFormatPNG;
} else {
@ -211,14 +256,52 @@
CGImagePropertyOrientation exifOrientation = kCGImagePropertyOrientationUp;
#endif
properties[(__bridge NSString *)kCGImagePropertyOrientation] = @(exifOrientation);
// Encoding Options
double compressionQuality = 1;
if (options[SDImageCoderEncodeCompressionQuality]) {
compressionQuality = [options[SDImageCoderEncodeCompressionQuality] doubleValue];
}
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = @(compressionQuality);
CGColorRef backgroundColor = [options[SDImageCoderEncodeBackgroundColor] CGColor];
if (backgroundColor) {
properties[(__bridge NSString *)kCGImageDestinationBackgroundColor] = (__bridge id)(backgroundColor);
}
CGSize maxPixelSize = CGSizeZero;
NSValue *maxPixelSizeValue = options[SDImageCoderEncodeMaxPixelSize];
if (maxPixelSizeValue != nil) {
#if SD_MAC
maxPixelSize = maxPixelSizeValue.sizeValue;
#else
maxPixelSize = maxPixelSizeValue.CGSizeValue;
#endif
}
NSUInteger pixelWidth = CGImageGetWidth(imageRef);
NSUInteger pixelHeight = CGImageGetHeight(imageRef);
if (maxPixelSize.width > 0 && maxPixelSize.height > 0 && pixelWidth > 0 && pixelHeight > 0) {
CGFloat pixelRatio = pixelWidth / pixelHeight;
CGFloat maxPixelSizeRatio = maxPixelSize.width / maxPixelSize.height;
CGFloat finalPixelSize;
if (pixelRatio > maxPixelSizeRatio) {
finalPixelSize = maxPixelSize.width;
} else {
finalPixelSize = maxPixelSize.height;
}
properties[(__bridge NSString *)kCGImageDestinationImageMaxPixelSize] = @(finalPixelSize);
}
NSUInteger maxFileSize = [options[SDImageCoderEncodeMaxFileSize] unsignedIntegerValue];
if (maxFileSize > 0) {
properties[kSDCGImageDestinationRequestedFileSize] = @(maxFileSize);
// Remove the quality if we have file size limit
properties[(__bridge NSString *)kCGImageDestinationLossyCompressionQuality] = nil;
}
BOOL embedThumbnail = NO;
if (options[SDImageCoderEncodeEmbedThumbnail]) {
embedThumbnail = [options[SDImageCoderEncodeEmbedThumbnail] boolValue];
}
properties[(__bridge NSString *)kCGImageDestinationEmbedThumbnail] = @(embedThumbnail);
// Add your image to the destination.
CGImageDestinationAddImage(imageDestination, image.CGImage, (__bridge CFDictionaryRef)properties);
CGImageDestinationAddImage(imageDestination, imageRef, (__bridge CFDictionaryRef)properties);
// Finalize the destination.
if (CGImageDestinationFinalize(imageDestination) == NO) {

View File

@ -32,11 +32,32 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
if (context) {
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
coderOptions = [mutableCoderOptions copy];
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
NSValue *thumbnailSizeValue;
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
if (shouldScaleDown) {
CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
CGFloat dimension = ceil(sqrt(thumbnailPixels));
thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
}
if (context[SDWebImageContextImageThumbnailPixelSize]) {
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
}
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
mutableCoderOptions[SDImageCoderWebImageContext] = context;
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
// Grab the image coder
id<SDImageCoder> imageCoder;
if ([context[SDWebImageContextImageCoder] conformsToProtocol:@protocol(SDImageCoder)]) {
imageCoder = context[SDWebImageContextImageCoder];
} else {
imageCoder = [SDImageCodersManager sharedManager];
}
if (!decodeFirstFrame) {
@ -58,7 +79,7 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
}
}
if (!image) {
image = [[SDImageCodersManager sharedManager] decodedImageWithData:imageData options:coderOptions];
image = [imageCoder decodedImageWithData:imageData options:coderOptions];
}
if (image) {
BOOL shouldDecode = !SD_OPTIONS_CONTAINS(options, SDWebImageAvoidDecodeImage);
@ -71,12 +92,7 @@ UIImage * _Nullable SDImageLoaderDecodeImageData(NSData * _Nonnull imageData, NS
}
if (shouldDecode) {
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
if (shouldScaleDown) {
image = [SDImageCoderHelper decodedAndScaledDownImageWithImage:image limitBytes:0];
} else {
image = [SDImageCoderHelper decodedImageWithImage:image];
}
image = [SDImageCoderHelper decodedImageWithImage:image];
}
}
@ -99,21 +115,41 @@ UIImage * _Nullable SDImageLoaderDecodeProgressiveImageData(NSData * _Nonnull im
BOOL decodeFirstFrame = SD_OPTIONS_CONTAINS(options, SDWebImageDecodeFirstFrameOnly);
NSNumber *scaleValue = context[SDWebImageContextImageScaleFactor];
CGFloat scale = scaleValue.doubleValue >= 1 ? scaleValue.doubleValue : SDImageScaleFactorForKey(cacheKey);
SDImageCoderOptions *coderOptions = @{SDImageCoderDecodeFirstFrameOnly : @(decodeFirstFrame), SDImageCoderDecodeScaleFactor : @(scale)};
if (context) {
SDImageCoderMutableOptions *mutableCoderOptions = [coderOptions mutableCopy];
[mutableCoderOptions setValue:context forKey:SDImageCoderWebImageContext];
coderOptions = [mutableCoderOptions copy];
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
NSValue *thumbnailSizeValue;
BOOL shouldScaleDown = SD_OPTIONS_CONTAINS(options, SDWebImageScaleDownLargeImages);
if (shouldScaleDown) {
CGFloat thumbnailPixels = SDImageCoderHelper.defaultScaleDownLimitBytes / 4;
CGFloat dimension = ceil(sqrt(thumbnailPixels));
thumbnailSizeValue = @(CGSizeMake(dimension, dimension));
}
if (context[SDWebImageContextImageThumbnailPixelSize]) {
thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
}
SDImageCoderMutableOptions *mutableCoderOptions = [NSMutableDictionary dictionaryWithCapacity:2];
mutableCoderOptions[SDImageCoderDecodeFirstFrameOnly] = @(decodeFirstFrame);
mutableCoderOptions[SDImageCoderDecodeScaleFactor] = @(scale);
mutableCoderOptions[SDImageCoderDecodePreserveAspectRatio] = preserveAspectRatioValue;
mutableCoderOptions[SDImageCoderDecodeThumbnailPixelSize] = thumbnailSizeValue;
mutableCoderOptions[SDImageCoderWebImageContext] = context;
SDImageCoderOptions *coderOptions = [mutableCoderOptions copy];
// Grab the progressive image coder
id<SDProgressiveImageCoder> progressiveCoder = objc_getAssociatedObject(operation, SDImageLoaderProgressiveCoderKey);
if (!progressiveCoder) {
// We need to create a new instance for progressive decoding to avoid conflicts
for (id<SDImageCoder>coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDProgressiveImageCoder)] &&
[((id<SDProgressiveImageCoder>)coder) canIncrementalDecodeFromData:imageData]) {
progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:coderOptions];
break;
id<SDProgressiveImageCoder> imageCoder = context[SDWebImageContextImageCoder];
// Check the progressive coder if provided
if ([imageCoder conformsToProtocol:@protocol(SDProgressiveImageCoder)]) {
progressiveCoder = [[[imageCoder class] alloc] initIncrementalWithOptions:coderOptions];
} else {
// We need to create a new instance for progressive decoding to avoid conflicts
for (id<SDImageCoder> coder in [SDImageCodersManager sharedManager].coders.reverseObjectEnumerator) {
if ([coder conformsToProtocol:@protocol(SDProgressiveImageCoder)] &&
[((id<SDProgressiveImageCoder>)coder) canIncrementalDecodeFromData:imageData]) {
progressiveCoder = [[[coder class] alloc] initIncrementalWithOptions:coderOptions];
break;
}
}
}
objc_setAssociatedObject(operation, SDImageLoaderProgressiveCoderKey, progressiveCoder, OBJC_ASSOCIATION_RETAIN_NONATOMIC);

View File

@ -18,6 +18,16 @@
*/
FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString * _Nonnull transformerKey);
/**
Return the thumbnailed cache key which applied with specify thumbnailSize and preserveAspectRatio control.
@param key The original cache key
@param thumbnailPixelSize The thumbnail pixel size
@param preserveAspectRatio The preserve aspect ratio option
@return The thumbnailed cache key
@note If you have both transformer and thumbnail applied for image, call `SDThumbnailedKeyForKey` firstly and then with `SDTransformedKeyForKey`.`
*/
FOUNDATION_EXPORT NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullable key, CGSize thumbnailPixelSize, BOOL preserveAspectRatio);
/**
A transformer protocol to transform the image load from cache or from download.
You can provide transformer to cache and manager (Through the `transformer` property or context option `SDWebImageContextImageTransformer`).
@ -38,10 +48,10 @@ FOUNDATION_EXPORT NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullab
Transform the image to another image.
@param image The image to be transformed
@param key The cache key associated to the image
@param key The cache key associated to the image. This arg is a hint for image source, not always useful and should be nullable. In the future we will remove this arg.
@return The transformed image, or nil if transform failed
*/
- (nullable UIImage *)transformedImageWithImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key;
- (nullable UIImage *)transformedImageWithImage:(nonnull UIImage *)image forKey:(nonnull NSString *)key API_DEPRECATED("The key arg will be removed in the future. Update your code and don't rely on that.", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
@end

View File

@ -7,7 +7,7 @@
*/
#import "SDImageTransformer.h"
#import "UIColor+HexString.h"
#import "UIColor+SDHexString.h"
#if SD_UIKIT || SD_MAC
#import <CoreImage/CoreImage.h>
#endif
@ -38,6 +38,11 @@ NSString * _Nullable SDTransformedKeyForKey(NSString * _Nullable key, NSString *
}
}
NSString * _Nullable SDThumbnailedKeyForKey(NSString * _Nullable key, CGSize thumbnailPixelSize, BOOL preserveAspectRatio) {
NSString *thumbnailKey = [NSString stringWithFormat:@"Thumbnail({%f,%f},%d)", thumbnailPixelSize.width, thumbnailPixelSize.height, preserveAspectRatio];
return SDTransformedKeyForKey(key, thumbnailKey);
}
@interface SDImagePipelineTransformer ()
@property (nonatomic, copy, readwrite, nonnull) NSArray<id<SDImageTransformer>> *transformers;

View File

@ -15,6 +15,7 @@
@protocol SDMemoryCache <NSObject>
@required
/**
Create a new memory cache instance with the specify cache config. You can check `maxMemoryCost` and `maxMemoryCount` used for memory cache.
@ -25,7 +26,7 @@
/**
Returns the value associated with a given key.
@param key An object identifying the value. If nil, just return nil.
@return The value associated with key, or nil if no value is associated with key.
*/
@ -33,7 +34,7 @@
/**
Sets the value of the specified key in the cache (0 cost).
@param object The object to be stored in the cache. If nil, it calls `removeObjectForKey:`.
@param key The key with which to associate the value. If nil, this method has no effect.
@discussion Unlike an NSMutableDictionary object, a cache does not copy the key
@ -44,7 +45,7 @@
/**
Sets the value of the specified key in the cache, and associates the key-value
pair with the specified cost.
@param object The object to store in the cache. If nil, it calls `removeObjectForKey`.
@param key The key with which to associate the value. If nil, this method has no effect.
@param cost The cost with which to associate the key-value pair.
@ -55,7 +56,7 @@
/**
Removes the value of the specified key in the cache.
@param key The key identifying the value to be removed. If nil, this method has no effect.
*/
- (void)removeObjectForKey:(nonnull id)key;

View File

@ -30,6 +30,7 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
#if SD_UIKIT
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
#endif
self.delegate = nil;
}
- (instancetype)init {
@ -54,14 +55,14 @@ static void * SDMemoryCacheContext = &SDMemoryCacheContext;
SDImageCacheConfig *config = self.config;
self.totalCostLimit = config.maxMemoryCost;
self.countLimit = config.maxMemoryCount;
[config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCost)) options:0 context:SDMemoryCacheContext];
[config addObserver:self forKeyPath:NSStringFromSelector(@selector(maxMemoryCount)) options:0 context:SDMemoryCacheContext];
#if SD_UIKIT
self.weakCache = [[NSMapTable alloc] initWithKeyOptions:NSPointerFunctionsStrongMemory valueOptions:NSPointerFunctionsWeakMemory capacity:0];
self.weakCacheLock = dispatch_semaphore_create(1);
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(didReceiveMemoryWarning:)
name:UIApplicationDidReceiveMemoryWarningNotification

View File

@ -17,6 +17,10 @@ typedef NSData * _Nullable(^SDWebImageCacheSerializerBlock)(UIImage * _Nonnull i
*/
@protocol SDWebImageCacheSerializer <NSObject>
/// Provide the image data associated to the image and store to disk cache
/// @param image The loaded image
/// @param data The original loaded image data
/// @param imageURL The image URL
- (nullable NSData *)cacheDataWithImage:(nonnull UIImage *)image originalData:(nullable NSData *)data imageURL:(nullable NSURL *)imageURL;
@end

View File

@ -122,9 +122,12 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
SDWebImageAvoidAutoSetImage = 1 << 10,
/**
* By default, images are decoded respecting their original size. On iOS, this flag will scale down the
* images to a size compatible with the constrained memory of devices.
* This flag take no effect if `SDWebImageAvoidDecodeImage` is set. And it will be ignored if `SDWebImageProgressiveLoad` is set.
* By default, images are decoded respecting their original size.
* This flag will scale down the images to a size compatible with the constrained memory of devices.
* To control the limit memory bytes, check `SDImageCoderHelper.defaultScaleDownLimitBytes` (Defaults to 60MB on iOS)
* This will actually translate to use context option `.imageThumbnailPixelSize` from v5.5.0 (Defaults to (3966, 3966) on iOS). Previously does not.
* This flags effect the progressive and animated images as well from v5.5.0. Previously does not.
* @note If you need detail controls, it's better to use context option `imageThumbnailPixelSize` and `imagePreserveAspectRatio` instead.
*/
SDWebImageScaleDownLargeImages = 1 << 11,
@ -184,6 +187,20 @@ typedef NS_OPTIONS(NSUInteger, SDWebImageOptions) {
* Note this options is not compatible with `SDWebImageDecodeFirstFrameOnly`, which always produce a UIImage/NSImage.
*/
SDWebImageMatchAnimatedImageClass = 1 << 21,
/**
* By default, when we load the image from network, the image will be written to the cache (memory and disk, controlled by your `storeCacheType` context option)
* This maybe an asynchronously operation and the final `SDInternalCompletionBlock` callback does not gurantee the disk cache written is finished and may cause logic error. (For example, you modify the disk data just in completion block, however, the disk cache is not ready)
* If you need to process with the disk cache in the completion block, you should use this option to ensure the disk cache already been written when callback.
* Note if you use this when using the custom cache serializer, or using the transformer, we will also wait until the output image data written is finished.
*/
SDWebImageWaitStoreCache = 1 << 22,
/**
* We usually don't apply transform on vector images, because vector images supports dynamically changing to any size, rasterize to a fixed size will loss details. To modify vector images, you can process the vector data at runtime (such as modifying PDF tag / SVG element).
* Use this flag to transform them anyway.
*/
SDWebImageTransformVectorImage = 1 << 23,
};
@ -196,11 +213,31 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextSetIma
/**
A SDWebImageManager instance to control the image download and cache process using in UIImageView+WebCache category and likes. If not provided, use the shared manager (SDWebImageManager *)
@deprecated Deprecated in the future. This context options can be replaced by other context option control like `.imageCache`, `.imageLoader`, `.imageTransofmer` (See below), which already matches all the properties in SDWebImageManager.
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager;
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextCustomManager API_DEPRECATED("Use individual context option like .imageCache, .imageLoader and .imageTransformer instead", macos(10.10, API_TO_BE_DEPRECATED), ios(8.0, API_TO_BE_DEPRECATED), tvos(9.0, API_TO_BE_DEPRECATED), watchos(2.0, API_TO_BE_DEPRECATED));
/**
A id<SDImageTransformer> instance which conforms `SDImageTransformer` protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. (id<SDImageTransformer>)
A id<SDImageCache> instance which conforms to `SDImageCache` protocol. It's used to override the image mananger's cache during the image loading pipeline.
In other word, if you just want to specify a custom cache during image loading, you don't need to re-create a dummy SDWebImageManager instance with the cache. If not provided, use the image manager's cache (id<SDImageCache>)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageCache;
/**
A id<SDImageLoader> instance which conforms to `SDImageLoader` protocol. It's used to override the image mananger's loader during the image loading pipeline.
In other word, if you just want to specify a custom loader during image loading, you don't need to re-create a dummy SDWebImageManager instance with the loader. If not provided, use the image manager's cache (id<SDImageLoader>)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageLoader;
/**
A id<SDImageCoder> instance which conforms to `SDImageCoder` protocol. It's used to override the default image codre for image decoding(including progressive) and encoding during the image loading process.
If you use this context option, we will not always use `SDImageCodersManager.shared` to loop through all registered coders and find the suitable one. Instead, we will arbitrarily use the exact provided coder without extra checking (We may not call `canDecodeFromData:`).
@note This is only useful for cases which you can ensure the loading url matches your coder, or you find it's too hard to write a common coder which can used for generic usage. This will bind the loading url with the coder logic, which is not always a good design, but possible. (id<SDImageCache>)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageCoder;
/**
A id<SDImageTransformer> instance which conforms `SDImageTransformer` protocol. It's used for image transform after the image load finished and store the transformed image to cache. If you provide one, it will ignore the `transformer` in manager and use provided one instead. If you pass NSNull, the transformer feature will be disabled. (id<SDImageTransformer>)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageTransformer;
@ -209,6 +246,25 @@ FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageT
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageScaleFactor;
/**
A Boolean value indicating whether to keep the original aspect ratio when generating thumbnail images (or bitmap images from vector format).
Defaults to YES. (NSNumber)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImagePreserveAspectRatio;
/**
A CGSize raw value indicating whether or not to generate the thumbnail images (or bitmap images from vector format). When this value is provided, the decoder will generate a thumbnail image which pixel size is smaller than or equal to (depends the `.imagePreserveAspectRatio`) the value size.
@note When you pass `.preserveAspectRatio == NO`, the thumbnail image is stretched to match each dimension. When `.preserveAspectRatio == YES`, the thumbnail image's width is limited to pixel size's width, the thumbnail image's height is limited to pixel size's height. For common cases, you can just pass a square size to limit both.
Defaults to CGSizeZero, which means no thumbnail generation at all. (NSValue)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextImageThumbnailPixelSize;
/**
A SDImageCacheType raw value which specify the source of cache to query. Specify `SDImageCacheTypeDisk` to query from disk cache only; `SDImageCacheTypeMemory` to query from memory only. And `SDImageCacheTypeAll` to query from both memory cache and disk cache. Specify `SDImageCacheTypeNone` is invalid and totally ignore the cache query.
If not provide or the value is invalid, we will use `SDImageCacheTypeAll`. (NSNumber)
*/
FOUNDATION_EXPORT SDWebImageContextOption _Nonnull const SDWebImageContextQueryCacheType;
/**
A SDImageCacheType raw value which specify the store cache type when the image has just been downloaded and will be stored to the cache. Specify `SDImageCacheTypeNone` to disable cache storage; `SDImageCacheTypeDisk` to store in disk cache only; `SDImageCacheTypeMemory` to store in memory only. And `SDImageCacheTypeAll` to store in both memory cache and disk cache.
If you use image transformer feature, this actually apply for the transformed image, but not the original image itself. Use `SDWebImageContextOriginalStoreCacheType` if you want to control the original image's store cache type at the same time.

View File

@ -9,6 +9,7 @@
#import "SDWebImageDefine.h"
#import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h"
#import "SDAssociatedObject.h"
#pragma mark - Image scale
@ -110,8 +111,7 @@ inline UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage *
scaledImage = [[UIImage alloc] initWithCGImage:image.CGImage scale:scale orientation:kCGImagePropertyOrientationUp];
#endif
}
scaledImage.sd_isIncremental = image.sd_isIncremental;
scaledImage.sd_imageFormat = image.sd_imageFormat;
SDImageCopyAssociatedObject(image, scaledImage);
return scaledImage;
}
@ -120,8 +120,14 @@ inline UIImage * _Nullable SDScaledImageForScaleFactor(CGFloat scale, UIImage *
SDWebImageContextOption const SDWebImageContextSetImageOperationKey = @"setImageOperationKey";
SDWebImageContextOption const SDWebImageContextCustomManager = @"customManager";
SDWebImageContextOption const SDWebImageContextImageCache = @"imageCache";
SDWebImageContextOption const SDWebImageContextImageLoader = @"imageLoader";
SDWebImageContextOption const SDWebImageContextImageCoder = @"imageCoder";
SDWebImageContextOption const SDWebImageContextImageTransformer = @"imageTransformer";
SDWebImageContextOption const SDWebImageContextImageScaleFactor = @"imageScaleFactor";
SDWebImageContextOption const SDWebImageContextImagePreserveAspectRatio = @"imagePreserveAspectRatio";
SDWebImageContextOption const SDWebImageContextImageThumbnailPixelSize = @"imageThumbnailPixelSize";
SDWebImageContextOption const SDWebImageContextQueryCacheType = @"queryCacheType";
SDWebImageContextOption const SDWebImageContextStoreCacheType = @"storeCacheType";
SDWebImageContextOption const SDWebImageContextOriginalStoreCacheType = @"originalStoreCacheType";
SDWebImageContextOption const SDWebImageContextAnimatedImageClass = @"animatedImageClass";

View File

@ -128,6 +128,11 @@ typedef SDImageLoaderCompletedBlock SDWebImageDownloaderCompletedBlock;
*/
@property (nonatomic, strong, nullable, readonly) NSURLResponse *response;
/**
The download's metrics. This will be nil if download operation does not support metrics.
*/
@property (nonatomic, strong, nullable, readonly) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
@end

View File

@ -24,6 +24,7 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
@property (nonatomic, strong, nullable, readwrite) NSURL *url;
@property (nonatomic, strong, nullable, readwrite) NSURLRequest *request;
@property (nonatomic, strong, nullable, readwrite) NSURLResponse *response;
@property (nonatomic, strong, nullable, readwrite) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
@property (nonatomic, weak, nullable, readwrite) id downloadOperationCancelToken;
@property (nonatomic, weak, nullable) NSOperation<SDWebImageDownloaderOperation> *downloadOperation;
@property (nonatomic, assign, getter=isCancelled) BOOL cancelled;
@ -226,10 +227,11 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
SD_UNLOCK(self.operationsLock);
};
self.URLOperations[url] = operation;
// Add the handlers before submitting to operation queue, avoid the race condition that operation finished before setting handlers.
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
// Add operation to operation queue only after all configuration done according to Apple's doc.
// `addOperation:` does not synchronously execute the `operation.completionBlock` so this will not cause deadlock.
[self.downloadQueue addOperation:operation];
downloadOperationCancelToken = [operation addHandlersForProgress:progressBlock completed:completedBlock];
} else {
// When we reuse the download operation to attach more callbacks, there may be thread safe issue because the getter of callbacks may in another queue (decoding queue or delegate queue)
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
@ -403,7 +405,12 @@ static void * SDWebImageDownloaderContext = &SDWebImageDownloaderContext;
NSOperation<SDWebImageDownloaderOperation> *returnOperation = nil;
for (NSOperation<SDWebImageDownloaderOperation> *operation in self.downloadQueue.operations) {
if ([operation respondsToSelector:@selector(dataTask)]) {
if (operation.dataTask.taskIdentifier == task.taskIdentifier) {
// So we lock the operation here, and in `SDWebImageDownloaderOperation`, we use `@synchonzied (self)`, to ensure the thread safe between these two classes.
NSURLSessionTask *operationTask;
@synchronized (operation) {
operationTask = operation.dataTask;
}
if (operationTask.taskIdentifier == task.taskIdentifier) {
returnOperation = operation;
break;
}
@ -492,30 +499,52 @@ didReceiveResponse:(NSURLResponse *)response
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
// Identify the operation that runs this task and pass it the delegate method
NSOperation<SDWebImageDownloaderOperation> *dataOperation = [self operationWithTask:task];
if ([dataOperation respondsToSelector:@selector(URLSession:task:didFinishCollectingMetrics:)]) {
[dataOperation URLSession:session task:task didFinishCollectingMetrics:metrics];
}
}
@end
@implementation SDWebImageDownloadToken
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self name:SDWebImageDownloadReceiveResponseNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:SDWebImageDownloadStopNotification object:nil];
}
- (instancetype)initWithDownloadOperation:(NSOperation<SDWebImageDownloaderOperation> *)downloadOperation {
self = [super init];
if (self) {
_downloadOperation = downloadOperation;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:downloadOperation];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadDidReceiveResponse:) name:SDWebImageDownloadReceiveResponseNotification object:downloadOperation];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(downloadDidStop:) name:SDWebImageDownloadStopNotification object:downloadOperation];
}
return self;
}
- (void)downloadReceiveResponse:(NSNotification *)notification {
- (void)downloadDidReceiveResponse:(NSNotification *)notification {
NSOperation<SDWebImageDownloaderOperation> *downloadOperation = notification.object;
if (downloadOperation && downloadOperation == self.downloadOperation) {
self.response = downloadOperation.response;
}
}
- (void)downloadDidStop:(NSNotification *)notification {
NSOperation<SDWebImageDownloaderOperation> *downloadOperation = notification.object;
if (downloadOperation && downloadOperation == self.downloadOperation) {
if ([downloadOperation respondsToSelector:@selector(metrics)]) {
if (@available(iOS 10.0, tvOS 10.0, macOS 10.12, watchOS 3.0, *)) {
self.metrics = downloadOperation.metrics;
}
}
}
}
- (void)cancel {
@synchronized (self) {
if (self.isCancelled) {

View File

@ -36,6 +36,7 @@
@optional
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
@property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
@property (strong, nonatomic, nullable) NSURLCredential *credential;
@property (assign, nonatomic) double minimumProgressInterval;
@ -62,6 +63,12 @@
*/
@property (strong, nonatomic, readonly, nullable) NSURLSessionTask *dataTask;
/**
* The collected metrics from `-URLSession:task:didFinishCollectingMetrics:`.
* This can be used to collect the network metrics like download duration, DNS lookup duration, SSL handshake dureation, etc. See Apple's documentation: https://developer.apple.com/documentation/foundation/urlsessiontaskmetrics
*/
@property (strong, nonatomic, readonly, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
/**
* The credential used for authentication challenges in `-URLSession:task:didReceiveChallenge:completionHandler:`.
*

View File

@ -52,7 +52,9 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTask *dataTask;
@property (strong, nonatomic, nonnull) dispatch_queue_t coderQueue; // the queue to do image decoding
@property (strong, nonatomic, readwrite, nullable) NSURLSessionTaskMetrics *metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0));
@property (strong, nonatomic, nonnull) NSOperationQueue *coderQueue; // the serial operation queue to do image decoding
#if SD_UIKIT
@property (assign, nonatomic) UIBackgroundTaskIdentifier backgroundTaskId;
#endif
@ -87,7 +89,8 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
_finished = NO;
_expectedSize = 0;
_unownedSession = session;
_coderQueue = dispatch_queue_create("com.hackemist.SDWebImageDownloaderOperationCoderQueue", DISPATCH_QUEUE_SERIAL);
_coderQueue = [NSOperationQueue new];
_coderQueue.maxConcurrentOperationCount = 1;
#if SD_UIKIT
_backgroundTaskId = UIBackgroundTaskInvalid;
#endif
@ -138,7 +141,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
SDWebImageDownloaderCompletedBlock completedBlock = [token valueForKey:kCompletedCallbackKey];
dispatch_main_async_safe(^{
if (completedBlock) {
completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil], YES);
completedBlock(nil, nil, [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}], YES);
}
});
}
@ -150,7 +153,7 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
if (self.isCancelled) {
self.finished = YES;
// Operation cancelled by user before sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil]];
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user before sending the request"}]];
[self reset];
return;
}
@ -205,8 +208,13 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
if (self.dataTask) {
if (self.options & SDWebImageDownloaderHighPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityHigh;
self.coderQueue.qualityOfService = NSQualityOfServiceUserInteractive;
} else if (self.options & SDWebImageDownloaderLowPriority) {
self.dataTask.priority = NSURLSessionTaskPriorityLow;
self.coderQueue.qualityOfService = NSQualityOfServiceBackground;
} else {
self.dataTask.priority = NSURLSessionTaskPriorityDefault;
self.coderQueue.qualityOfService = NSQualityOfServiceDefault;
}
[self.dataTask resume];
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
@ -243,9 +251,10 @@ typedef NSMutableDictionary<NSString *, id> SDCallbacksDictionary;
// maintain the isFinished and isExecuting flags.
if (self.isExecuting) self.executing = NO;
if (!self.isFinished) self.finished = YES;
} else {
// Operation cancelled by user during sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}]];
}
// Operation cancelled by user before sending the request
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil]];
[self reset];
}
@ -307,7 +316,7 @@ didReceiveResponse:(NSURLResponse *)response
response = [self.responseModifier modifiedResponseWithResponse:response];
if (!response) {
valid = NO;
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadResponse userInfo:nil];
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadResponse userInfo:@{NSLocalizedDescriptionKey : @"Download marked as failed because response is nil"}];
}
}
@ -321,13 +330,13 @@ didReceiveResponse:(NSURLResponse *)response
BOOL statusCodeValid = statusCode >= 200 && statusCode < 400;
if (!statusCodeValid) {
valid = NO;
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadStatusCode userInfo:@{SDWebImageErrorDownloadStatusCodeKey : @(statusCode)}];
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorInvalidDownloadStatusCode userInfo:@{NSLocalizedDescriptionKey : @"Download marked as failed because response status code is not in 200-400", SDWebImageErrorDownloadStatusCodeKey : @(statusCode)}];
}
//'304 Not Modified' is an exceptional one
//URLSession current behavior will return 200 status code when the server respond 304 and URLCache hit. But this is not a standard behavior and we just add a check
if (statusCode == 304 && !self.cachedData) {
valid = NO;
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:nil];
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:@{NSLocalizedDescriptionKey : @"Download response status code is 304 not modified and ignored"}];
}
if (valid) {
@ -381,17 +390,18 @@ didReceiveResponse:(NSURLResponse *)response
// Get the image data
NSData *imageData = [self.imageData copy];
// progressive decode the image in coder queue
dispatch_async(self.coderQueue, ^{
@autoreleasepool {
// keep maxmium one progressive decode process during download
if (self.coderQueue.operationCount == 0) {
// NSOperation have autoreleasepool, don't need to create extra one
[self.coderQueue addOperationWithBlock:^{
UIImage *image = SDImageLoaderDecodeProgressiveImageData(imageData, self.request.URL, finished, self, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
if (image) {
// We do not keep the progressive decoding image even when `finished`=YES. Because they are for view rendering but not take full function from downloader options. And some coders implementation may not keep consistent between progressive decoding and normal decoding.
[self callCompletionBlocksWithImage:image imageData:nil error:nil finished:NO];
}
}
});
}];
}
}
for (SDWebImageDownloaderProgressBlock progressBlock in [self callbacksForKey:kProgressCallbackKey]) {
@ -453,24 +463,23 @@ didReceiveResponse:(NSURLResponse *)response
* then we should check if the cached data is equal to image data
*/
if (self.options & SDWebImageDownloaderIgnoreCachedResponse && [self.cachedData isEqualToData:imageData]) {
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:nil];
self.responseError = [NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCacheNotModified userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image is not modified and ignored"}];
// call completion block with not modified error
[self callCompletionBlocksWithError:self.responseError];
[self done];
} else {
// decode the image in coder queue
dispatch_async(self.coderQueue, ^{
@autoreleasepool {
UIImage *image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
[self done];
// decode the image in coder queue, cancel all previous decoding process
[self.coderQueue cancelAllOperations];
[self.coderQueue addOperationWithBlock:^{
UIImage *image = SDImageLoaderDecodeImageData(imageData, self.request.URL, [[self class] imageOptionsFromDownloaderOptions:self.options], self.context);
CGSize imageSize = image.size;
if (imageSize.width == 0 || imageSize.height == 0) {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Downloaded image has 0 pixels"}]];
} else {
[self callCompletionBlocksWithImage:image imageData:imageData error:nil finished:YES];
}
});
[self done];
}];
}
} else {
[self callCompletionBlocksWithError:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorBadImageData userInfo:@{NSLocalizedDescriptionKey : @"Image data is nil"}]];
@ -512,6 +521,10 @@ didReceiveResponse:(NSURLResponse *)response
}
}
- (void)URLSession:(NSURLSession *)session task:(NSURLSessionTask *)task didFinishCollectingMetrics:(NSURLSessionTaskMetrics *)metrics API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0)) {
self.metrics = metrics;
}
#pragma mark Helper methods
+ (SDWebImageOptions)imageOptionsFromDownloaderOptions:(SDWebImageDownloaderOptions)downloadOptions {
SDWebImageOptions options = 0;

View File

@ -22,5 +22,5 @@ typedef NS_ERROR_ENUM(SDWebImageErrorDomain, SDWebImageError) {
SDWebImageErrorInvalidDownloadOperation = 2000, // The image download operation is invalid, such as nil operation or unexpected error occur when operation initialized
SDWebImageErrorInvalidDownloadStatusCode = 2001, // The image download response a invalid status code. You can check the status code in error's userInfo under `SDWebImageErrorDownloadStatusCodeKey`
SDWebImageErrorCancelled = 2002, // The image loading operation is cancelled before finished, during either async disk cache query, or waiting before actual network request. For actual network request error, check `NSURLErrorDomain` error domain and code.
SDWebImageErrorInvalidDownloadResponse = 2003, // When using response modifier, the modified download response is nil and marked as cancelled.
SDWebImageErrorInvalidDownloadResponse = 2003, // When using response modifier, the modified download response is nil and marked as failed.
};

View File

@ -87,7 +87,7 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager];
[manager loadImageWithURL:imageURL
options:0
progress:nil
completed:^(UIImage *image, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
completed:^(UIImage *image, NSData *data, NSError *error, SDImageCacheType cacheType, BOOL finished, NSURL *imageURL) {
if (image) {
// do something with image
}
@ -262,8 +262,15 @@ SDWebImageManager *manager = [SDWebImageManager sharedManager];
- (void)cancelAll;
/**
* Return the cache key for a given URL
* Return the cache key for a given URL, does not considerate transformer or thumbnail.
* @note This method does not have context option, only use the url and manager level cacheKeyFilter to generate the cache key.
*/
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url;
/**
* Return the cache key for a given URL and context option.
* @note The context option like `.thumbnailPixelSize` and `.imageTransformer` will effect the generated cache key, using this if you have those context associated.
*/
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context;
@end

View File

@ -10,6 +10,7 @@
#import "SDImageCache.h"
#import "SDWebImageDownloader.h"
#import "UIImage+Metadata.h"
#import "SDAssociatedObject.h"
#import "SDWebImageError.h"
#import "SDInternalMacros.h"
@ -94,19 +95,69 @@ static id<SDImageLoader> _defaultImageLoader;
}
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url {
return [self cacheKeyForURL:url cacheKeyFilter:self.cacheKeyFilter];
}
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url cacheKeyFilter:(id<SDWebImageCacheKeyFilter>)cacheKeyFilter {
if (!url) {
return @"";
}
NSString *key;
// Cache Key Filter
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
if (cacheKeyFilter) {
return [cacheKeyFilter cacheKeyForURL:url];
key = [cacheKeyFilter cacheKeyForURL:url];
} else {
return url.absoluteString;
key = url.absoluteString;
}
return key;
}
- (nullable NSString *)cacheKeyForURL:(nullable NSURL *)url context:(nullable SDWebImageContext *)context {
if (!url) {
return @"";
}
NSString *key;
// Cache Key Filter
id<SDWebImageCacheKeyFilter> cacheKeyFilter = self.cacheKeyFilter;
if (context[SDWebImageContextCacheKeyFilter]) {
cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
}
if (cacheKeyFilter) {
key = [cacheKeyFilter cacheKeyForURL:url];
} else {
key = url.absoluteString;
}
// Thumbnail Key Appending
NSValue *thumbnailSizeValue = context[SDWebImageContextImageThumbnailPixelSize];
if (thumbnailSizeValue != nil) {
CGSize thumbnailSize = CGSizeZero;
#if SD_MAC
thumbnailSize = thumbnailSizeValue.sizeValue;
#else
thumbnailSize = thumbnailSizeValue.CGSizeValue;
#endif
BOOL preserveAspectRatio = YES;
NSNumber *preserveAspectRatioValue = context[SDWebImageContextImagePreserveAspectRatio];
if (preserveAspectRatioValue != nil) {
preserveAspectRatio = preserveAspectRatioValue.boolValue;
}
key = SDThumbnailedKeyForKey(key, thumbnailSize, preserveAspectRatio);
}
// Transformer Key Appending
id<SDImageTransformer> transformer = self.transformer;
if (context[SDWebImageContextImageTransformer]) {
transformer = context[SDWebImageContextImageTransformer];
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
transformer = nil;
}
}
if (transformer) {
key = SDTransformedKeyForKey(key, transformer.transformerKey);
}
return key;
}
- (SDWebImageCombinedOperation *)loadImageWithURL:(NSURL *)url options:(SDWebImageOptions)options progress:(SDImageLoaderProgressBlock)progressBlock completed:(SDInternalCompletionBlock)completedBlock {
@ -184,17 +235,28 @@ static id<SDImageLoader> _defaultImageLoader;
context:(nullable SDWebImageContext *)context
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image cache to use
id<SDImageCache> imageCache;
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
// Check whether we should query cache
BOOL shouldQueryCache = !SD_OPTIONS_CONTAINS(options, SDWebImageFromLoaderOnly);
// Get the query cache type
SDImageCacheType queryCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextQueryCacheType]) {
queryCacheType = [context[SDWebImageContextQueryCacheType] integerValue];
}
if (shouldQueryCache) {
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
NSString *key = [self cacheKeyForURL:url context:context];
@weakify(operation);
operation.cacheOperation = [self.imageCache queryImageForKey:key options:options context:context completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
operation.cacheOperation = [imageCache queryImageForKey:key options:options context:context cacheType:queryCacheType completion:^(UIImage * _Nullable cachedImage, NSData * _Nullable cachedData, SDImageCacheType cacheType) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// Image combined operation cancelled by user
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during querying the cache"}] url:url];
[self safelyRemoveOperationFromRunning:operation];
return;
}
@ -217,11 +279,18 @@ static id<SDImageLoader> _defaultImageLoader;
cacheType:(SDImageCacheType)cacheType
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// Grab the image loader to use
id<SDImageLoader> imageLoader;
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader;
}
// Check whether we should download image from network
BOOL shouldDownload = !SD_OPTIONS_CONTAINS(options, SDWebImageFromCacheOnly);
shouldDownload &= (!cachedImage || options & SDWebImageRefreshCached);
shouldDownload &= (![self.delegate respondsToSelector:@selector(imageManager:shouldDownloadImageForURL:)] || [self.delegate imageManager:self shouldDownloadImageForURL:url]);
shouldDownload &= [self.imageLoader canRequestImageForURL:url];
shouldDownload &= [imageLoader canRequestImageForURL:url];
if (shouldDownload) {
if (cachedImage && options & SDWebImageRefreshCached) {
// If image was found in the cache but SDWebImageRefreshCached is provided, notify about the cached image
@ -239,11 +308,11 @@ static id<SDImageLoader> _defaultImageLoader;
}
@weakify(operation);
operation.loaderOperation = [self.imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
operation.loaderOperation = [imageLoader requestImageWithURL:url options:options context:context progress:progressBlock completed:^(UIImage *downloadedImage, NSData *downloadedData, NSError *error, BOOL finished) {
@strongify(operation);
if (!operation || operation.isCancelled) {
// Image combined operation cancelled by user
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:nil] url:url];
[self callCompletionBlockForOperation:operation completion:completedBlock error:[NSError errorWithDomain:SDWebImageErrorDomain code:SDWebImageErrorCancelled userInfo:@{NSLocalizedDescriptionKey : @"Operation cancelled by user during sending the request"}] url:url];
} else if (cachedImage && options & SDWebImageRefreshCached && [error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCacheNotModified) {
// Image refresh hit the NSURLCache cache, do not call the completion block
} else if ([error.domain isEqualToString:SDWebImageErrorDomain] && error.code == SDWebImageErrorCancelled) {
@ -251,7 +320,7 @@ static id<SDImageLoader> _defaultImageLoader;
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
} else if (error) {
[self callCompletionBlockForOperation:operation completion:completedBlock error:error url:url];
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error];
BOOL shouldBlockFailedURL = [self shouldBlockFailedURLWithURL:url error:error options:options context:context];
if (shouldBlockFailedURL) {
SD_LOCK(self.failedURLsLock);
@ -264,7 +333,7 @@ static id<SDImageLoader> _defaultImageLoader;
[self.failedURLs removeObject:url];
SD_UNLOCK(self.failedURLsLock);
}
// Continue store cache process
[self callStoreCacheProcessForOperation:operation url:url options:options context:context downloadedImage:downloadedImage downloadedData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
@ -302,12 +371,20 @@ static id<SDImageLoader> _defaultImageLoader;
if (context[SDWebImageContextOriginalStoreCacheType]) {
originalStoreCacheType = [context[SDWebImageContextOriginalStoreCacheType] integerValue];
}
id<SDWebImageCacheKeyFilter> cacheKeyFilter = context[SDWebImageContextCacheKeyFilter];
NSString *key = [self cacheKeyForURL:url cacheKeyFilter:cacheKeyFilter];
// origin cache key
SDWebImageMutableContext *originContext = [context mutableCopy];
// disable transformer for cache key generation
originContext[SDWebImageContextImageTransformer] = [NSNull null];
NSString *key = [self cacheKeyForURL:url context:originContext];
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
transformer = nil;
}
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
BOOL shouldTransformImage = downloadedImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage)) && transformer;
BOOL shouldTransformImage = downloadedImage && transformer;
shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
shouldTransformImage = shouldTransformImage && (!downloadedImage.sd_isVector || (options & SDWebImageTransformVectorImage));
BOOL shouldCacheOriginal = downloadedImage && finished;
// if available, store original image to cache
@ -318,37 +395,74 @@ static id<SDImageLoader> _defaultImageLoader;
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
NSData *cacheData = [cacheSerializer cacheDataWithImage:downloadedImage originalData:downloadedData imageURL:url];
[self.imageCache storeImage:downloadedImage imageData:cacheData forKey:key cacheType:targetStoreCacheType completion:nil];
[self storeImage:downloadedImage imageData:cacheData forKey:key cacheType:targetStoreCacheType options:options context:context completion:^{
// Continue transform process
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}];
}
});
} else {
[self.imageCache storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:targetStoreCacheType completion:nil];
[self storeImage:downloadedImage imageData:downloadedData forKey:key cacheType:targetStoreCacheType options:options context:context completion:^{
// Continue transform process
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}];
}
} else {
// Continue transform process
[self callTransformProcessForOperation:operation url:url options:options context:context originalImage:downloadedImage originalData:downloadedData finished:finished progress:progressBlock completed:completedBlock];
}
}
// Transform process
- (void)callTransformProcessForOperation:(nonnull SDWebImageCombinedOperation *)operation
url:(nonnull NSURL *)url
options:(SDWebImageOptions)options
context:(SDWebImageContext *)context
originalImage:(nullable UIImage *)originalImage
originalData:(nullable NSData *)originalData
finished:(BOOL)finished
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
// the target image store cache type
SDImageCacheType storeCacheType = SDImageCacheTypeAll;
if (context[SDWebImageContextStoreCacheType]) {
storeCacheType = [context[SDWebImageContextStoreCacheType] integerValue];
}
// transformed cache key
NSString *key = [self cacheKeyForURL:url context:context];
id<SDImageTransformer> transformer = context[SDWebImageContextImageTransformer];
if (![transformer conformsToProtocol:@protocol(SDImageTransformer)]) {
transformer = nil;
}
id<SDWebImageCacheSerializer> cacheSerializer = context[SDWebImageContextCacheSerializer];
BOOL shouldTransformImage = originalImage && transformer;
shouldTransformImage = shouldTransformImage && (!originalImage.sd_isAnimated || (options & SDWebImageTransformAnimatedImage));
shouldTransformImage = shouldTransformImage && (!originalImage.sd_isVector || (options & SDWebImageTransformVectorImage));
// if available, store transformed image to cache
if (shouldTransformImage) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0), ^{
@autoreleasepool {
UIImage *transformedImage = [transformer transformedImageWithImage:downloadedImage forKey:key];
UIImage *transformedImage = [transformer transformedImageWithImage:originalImage forKey:key];
if (transformedImage && finished) {
NSString *transformerKey = [transformer transformerKey];
NSString *cacheKey = SDTransformedKeyForKey(key, transformerKey);
BOOL imageWasTransformed = ![transformedImage isEqual:downloadedImage];
BOOL imageWasTransformed = ![transformedImage isEqual:originalImage];
NSData *cacheData;
// pass nil if the image was transformed, so we can recalculate the data from the image
if (cacheSerializer && (storeCacheType == SDImageCacheTypeDisk || storeCacheType == SDImageCacheTypeAll)) {
cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : downloadedData) imageURL:url];
cacheData = [cacheSerializer cacheDataWithImage:transformedImage originalData:(imageWasTransformed ? nil : originalData) imageURL:url];
} else {
cacheData = (imageWasTransformed ? nil : downloadedData);
cacheData = (imageWasTransformed ? nil : originalData);
}
[self.imageCache storeImage:transformedImage imageData:cacheData forKey:cacheKey cacheType:storeCacheType completion:nil];
[self storeImage:transformedImage imageData:cacheData forKey:key cacheType:storeCacheType options:options context:context completion:^{
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}];
} else {
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
[self callCompletionBlockForOperation:operation completion:completedBlock image:transformedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
});
} else {
[self callCompletionBlockForOperation:operation completion:completedBlock image:downloadedImage data:downloadedData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
[self callCompletionBlockForOperation:operation completion:completedBlock image:originalImage data:originalData error:nil cacheType:SDImageCacheTypeNone finished:finished url:url];
}
}
@ -363,6 +477,35 @@ static id<SDImageLoader> _defaultImageLoader;
SD_UNLOCK(self.runningOperationsLock);
}
- (void)storeImage:(nullable UIImage *)image
imageData:(nullable NSData *)data
forKey:(nullable NSString *)key
cacheType:(SDImageCacheType)cacheType
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context
completion:(nullable SDWebImageNoParamsBlock)completion {
id<SDImageCache> imageCache;
if ([context[SDWebImageContextImageCache] conformsToProtocol:@protocol(SDImageCache)]) {
imageCache = context[SDWebImageContextImageCache];
} else {
imageCache = self.imageCache;
}
BOOL waitStoreCache = SD_OPTIONS_CONTAINS(options, SDWebImageWaitStoreCache);
// Check whether we should wait the store cache finished. If not, callback immediately
[imageCache storeImage:image imageData:data forKey:key cacheType:cacheType completion:^{
if (waitStoreCache) {
if (completion) {
completion();
}
}
}];
if (!waitStoreCache) {
if (completion) {
completion();
}
}
}
- (void)callCompletionBlockForOperation:(nullable SDWebImageCombinedOperation*)operation
completion:(nullable SDInternalCompletionBlock)completionBlock
error:(nullable NSError *)error
@ -386,13 +529,21 @@ static id<SDImageLoader> _defaultImageLoader;
}
- (BOOL)shouldBlockFailedURLWithURL:(nonnull NSURL *)url
error:(nonnull NSError *)error {
error:(nonnull NSError *)error
options:(SDWebImageOptions)options
context:(nullable SDWebImageContext *)context {
id<SDImageLoader> imageLoader;
if ([context[SDWebImageContextImageLoader] conformsToProtocol:@protocol(SDImageLoader)]) {
imageLoader = context[SDWebImageContextImageLoader];
} else {
imageLoader = self.imageLoader;
}
// Check whether we should block failed url
BOOL shouldBlockFailedURL;
if ([self.delegate respondsToSelector:@selector(imageManager:shouldBlockFailedURL:withError:)]) {
shouldBlockFailedURL = [self.delegate imageManager:self shouldBlockFailedURL:url withError:error];
} else {
shouldBlockFailedURL = [self.imageLoader shouldBlockFailedURLWithURL:url error:error];
shouldBlockFailedURL = [imageLoader shouldBlockFailedURLWithURL:url error:error];
}
return shouldBlockFailedURL;

View File

@ -0,0 +1,24 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Fabrice Aneche
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
@interface UIImage (ExtendedCacheData)
/**
Read and Write the extended object and bind it to the image. Which can hold some extra metadata like Image's scale factor, URL rich link, date, etc.
The extended object should conforms to NSCoding, which we use `NSKeyedArchiver` and `NSKeyedUnarchiver` to archive it to data, and write to disk cache.
@note The disk cache preserve both of the data and extended data with the same cache key. For manual query, use the `SDDiskCache` protocol method `extendedDataForKey:` instead.
@note You can specify arbitrary object conforms to NSCoding (NSObject protocol here is used to support object using `NS_ROOT_CLASS`, which is not NSObject subclass). If you load image from disk cache, you should check the extended object class to avoid corrupted data.
@warning This object don't need to implements NSSecureCoding (but it's recommended), because we allows arbitrary class.
*/
@property (nonatomic, strong, nullable) id<NSObject, NSCoding> sd_extendedObject;
@end

View File

@ -0,0 +1,23 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
* (c) Fabrice Aneche
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "UIImage+ExtendedCacheData.h"
#import <objc/runtime.h>
@implementation UIImage (ExtendedCacheData)
- (id<NSObject, NSCoding>)sd_extendedObject {
return objc_getAssociatedObject(self, @selector(sd_extendedObject));
}
- (void)setSd_extendedObject:(id<NSObject, NSCoding>)sd_extendedObject {
objc_setAssociatedObject(self, @selector(sd_extendedObject), sd_extendedObject, OBJC_ASSOCIATION_RETAIN_NONATOMIC);
}
@end

View File

@ -28,12 +28,20 @@
/**
* UIKit:
* Check the `images` array property
* Check the `images` array property.
* AppKit:
* NSImage currently only support animated via GIF imageRep unlike UIImage. It will check the imageRep's frame count.
*/
@property (nonatomic, assign, readonly) BOOL sd_isAnimated;
/**
* UIKit:
* Check the `isSymbolImage` property. Also check the system PDF(iOS 11+) && SVG(iOS 13+) support.
* AppKit:
* NSImage supports PDF && SVG && EPS imageRep, check the imageRep class.
*/
@property (nonatomic, assign, readonly) BOOL sd_isVector;
/**
* The image format represent the original compressed image data format.
* If you don't manually specify a format, this information is retrieve from CGImage using `CGImageGetUTType`, which may return nil for non-CG based image. At this time it will return `SDImageFormatUndefined` as default value.

View File

@ -8,6 +8,7 @@
#import "UIImage+Metadata.h"
#import "NSImage+Compatibility.h"
#import "SDInternalMacros.h"
#import "objc/runtime.h"
@implementation UIImage (Metadata)
@ -32,6 +33,32 @@
return (self.images != nil);
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
- (BOOL)sd_isVector {
if (@available(iOS 13.0, tvOS 13.0, watchOS 6.0, *)) {
// Xcode 11 supports symbol image, keep Xcode 10 compatible currently
SEL SymbolSelector = NSSelectorFromString(@"isSymbolImage");
if ([self respondsToSelector:SymbolSelector] && [self performSelector:SymbolSelector]) {
return YES;
}
// SVG
SEL SVGSelector = SD_SEL_SPI(CGSVGDocument);
if ([self respondsToSelector:SVGSelector] && [self performSelector:SVGSelector]) {
return YES;
}
}
if (@available(iOS 11.0, tvOS 11.0, watchOS 4.0, *)) {
// PDF
SEL PDFSelector = SD_SEL_SPI(CGPDFPage);
if ([self respondsToSelector:PDFSelector] && [self performSelector:PDFSelector]) {
return YES;
}
}
return NO;
}
#pragma clang diagnostic pop
#else
- (NSUInteger)sd_imageLoopCount {
@ -61,7 +88,7 @@
}
- (BOOL)sd_isAnimated {
BOOL isGIF = NO;
BOOL isAnimated = NO;
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
NSBitmapImageRep *bitmapImageRep;
@ -70,9 +97,24 @@
}
if (bitmapImageRep) {
NSUInteger frameCount = [[bitmapImageRep valueForProperty:NSImageFrameCount] unsignedIntegerValue];
isGIF = frameCount > 1 ? YES : NO;
isAnimated = frameCount > 1 ? YES : NO;
}
return isGIF;
return isAnimated;
}
- (BOOL)sd_isVector {
NSRect imageRect = NSMakeRect(0, 0, self.size.width, self.size.height);
NSImageRep *imageRep = [self bestRepresentationForRect:imageRect context:nil hints:nil];
if ([imageRep isKindOfClass:[NSPDFImageRep class]]) {
return YES;
}
if ([imageRep isKindOfClass:[NSEPSImageRep class]]) {
return YES;
}
if ([NSStringFromClass(imageRep.class) hasSuffix:@"NSSVGImageRep"]) {
return YES;
}
return NO;
}
#endif

View File

@ -9,7 +9,8 @@
#import "UIImage+Transform.h"
#import "NSImage+Compatibility.h"
#import "SDImageGraphics.h"
#import "NSBezierPath+RoundedCorners.h"
#import "SDGraphicsImageRenderer.h"
#import "NSBezierPath+SDRoundedCorners.h"
#import <Accelerate/Accelerate.h>
#if SD_UIKIT || SD_MAC
#import <CoreImage/CoreImage.h>
@ -163,13 +164,29 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
return [UIColor colorWithRed:r green:g blue:b alpha:a];
}
#if SD_UIKIT || SD_MAC
// Create-Rule, caller should call CGImageRelease
static inline CGImageRef _Nullable SDCreateCGImageFromCIImage(CIImage * _Nonnull ciImage) {
CGImageRef imageRef = NULL;
if (@available(iOS 10, macOS 10.12, tvOS 10, *)) {
imageRef = ciImage.CGImage;
}
if (!imageRef) {
CIContext *context = [CIContext context];
imageRef = [context createCGImage:ciImage fromRect:ciImage.extent];
} else {
CGImageRetain(imageRef);
}
return imageRef;
}
#endif
@implementation UIImage (Transform)
- (void)sd_drawInRect:(CGRect)rect withScaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips {
- (void)sd_drawInRect:(CGRect)rect context:(CGContextRef)context scaleMode:(SDImageScaleMode)scaleMode clipsToBounds:(BOOL)clips {
CGRect drawRect = SDCGRectFitWithScaleMode(rect, self.size, scaleMode);
if (drawRect.size.width == 0 || drawRect.size.height == 0) return;
if (clips) {
CGContextRef context = SDGraphicsGetCurrentContext();
if (context) {
CGContextSaveGState(context);
CGContextAddRect(context, rect);
@ -184,162 +201,215 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
- (nullable UIImage *)sd_resizedImageWithSize:(CGSize)size scaleMode:(SDImageScaleMode)scaleMode {
if (size.width <= 0 || size.height <= 0) return nil;
SDGraphicsBeginImageContextWithOptions(size, NO, self.scale);
[self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) withScaleMode:scaleMode clipsToBounds:NO];
UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
SDGraphicsEndImageContext();
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
format.scale = self.scale;
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
[self sd_drawInRect:CGRectMake(0, 0, size.width, size.height) context:context scaleMode:scaleMode clipsToBounds:NO];
}];
return image;
}
- (nullable UIImage *)sd_croppedImageWithRect:(CGRect)rect {
if (!self.CGImage) return nil;
rect.origin.x *= self.scale;
rect.origin.y *= self.scale;
rect.size.width *= self.scale;
rect.size.height *= self.scale;
if (rect.size.width <= 0 || rect.size.height <= 0) return nil;
CGImageRef imageRef = CGImageCreateWithImageInRect(self.CGImage, rect);
#if SD_UIKIT || SD_MAC
// CIImage shortcut
if (self.CIImage) {
CGRect croppingRect = CGRectMake(rect.origin.x, self.size.height - CGRectGetMaxY(rect), rect.size.width, rect.size.height);
CIImage *ciImage = [self.CIImage imageByCroppingToRect:croppingRect];
#if SD_UIKIT
UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
#else
UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
return image;
}
#endif
CGImageRef imageRef = self.CGImage;
if (!imageRef) {
return nil;
}
CGImageRef croppedImageRef = CGImageCreateWithImageInRect(imageRef, rect);
if (!croppedImageRef) {
return nil;
}
#if SD_UIKIT || SD_WATCH
UIImage *image = [UIImage imageWithCGImage:imageRef scale:self.scale orientation:self.imageOrientation];
UIImage *image = [UIImage imageWithCGImage:croppedImageRef scale:self.scale orientation:self.imageOrientation];
#else
UIImage *image = [[UIImage alloc] initWithCGImage:imageRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
UIImage *image = [[UIImage alloc] initWithCGImage:croppedImageRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
CGImageRelease(imageRef);
CGImageRelease(croppedImageRef);
return image;
}
- (nullable UIImage *)sd_roundedCornerImageWithRadius:(CGFloat)cornerRadius corners:(SDRectCorner)corners borderWidth:(CGFloat)borderWidth borderColor:(nullable UIColor *)borderColor {
if (!self.CGImage) return nil;
SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
CGContextRef context = SDGraphicsGetCurrentContext();
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2) {
#if SD_UIKIT || SD_WATCH
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
#else
NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius];
#endif
[path closePath];
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
format.scale = self.scale;
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format];
UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
CGRect rect = CGRectMake(0, 0, self.size.width, self.size.height);
CGContextSaveGState(context);
[path addClip];
[self drawInRect:rect];
CGContextRestoreGState(context);
}
if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0;
CGFloat minSize = MIN(self.size.width, self.size.height);
if (borderWidth < minSize / 2) {
#if SD_UIKIT || SD_WATCH
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)];
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadii:CGSizeMake(cornerRadius, cornerRadius)];
#else
NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius];
NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:CGRectInset(rect, borderWidth, borderWidth) byRoundingCorners:corners cornerRadius:cornerRadius];
#endif
[path closePath];
[path closePath];
CGContextSaveGState(context);
[path addClip];
[self drawInRect:rect];
CGContextRestoreGState(context);
}
path.lineWidth = borderWidth;
[borderColor setStroke];
[path stroke];
}
UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
SDGraphicsEndImageContext();
if (borderColor && borderWidth < minSize / 2 && borderWidth > 0) {
CGFloat strokeInset = (floor(borderWidth * self.scale) + 0.5) / self.scale;
CGRect strokeRect = CGRectInset(rect, strokeInset, strokeInset);
CGFloat strokeRadius = cornerRadius > self.scale / 2 ? cornerRadius - self.scale / 2 : 0;
#if SD_UIKIT || SD_WATCH
UIBezierPath *path = [UIBezierPath bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadii:CGSizeMake(strokeRadius, strokeRadius)];
#else
NSBezierPath *path = [NSBezierPath sd_bezierPathWithRoundedRect:strokeRect byRoundingCorners:corners cornerRadius:strokeRadius];
#endif
[path closePath];
path.lineWidth = borderWidth;
[borderColor setStroke];
[path stroke];
}
}];
return image;
}
- (nullable UIImage *)sd_rotatedImageWithAngle:(CGFloat)angle fitSize:(BOOL)fitSize {
if (!self.CGImage) return nil;
size_t width = (size_t)CGImageGetWidth(self.CGImage);
size_t height = (size_t)CGImageGetHeight(self.CGImage);
CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0., 0., width, height),
size_t width = self.size.width;
size_t height = self.size.height;
CGRect newRect = CGRectApplyAffineTransform(CGRectMake(0, 0, width, height),
fitSize ? CGAffineTransformMakeRotation(angle) : CGAffineTransformIdentity);
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL,
(size_t)newRect.size.width,
(size_t)newRect.size.height,
8,
(size_t)newRect.size.width * 4,
colorSpace,
kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
CGContextRotateCTM(context, angle);
CGContextDrawImage(context, CGRectMake(-(width * 0.5), -(height * 0.5), width, height), self.CGImage);
CGImageRef imgRef = CGBitmapContextCreateImage(context);
#if SD_UIKIT || SD_MAC
// CIImage shortcut
if (self.CIImage) {
CIImage *ciImage = self.CIImage;
if (fitSize) {
CGAffineTransform transform = CGAffineTransformMakeRotation(angle);
ciImage = [ciImage imageByApplyingTransform:transform];
} else {
CIFilter *filter = [CIFilter filterWithName:@"CIStraightenFilter"];
[filter setValue:ciImage forKey:kCIInputImageKey];
[filter setValue:@(angle) forKey:kCIInputAngleKey];
ciImage = filter.outputImage;
}
#if SD_UIKIT || SD_WATCH
UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
#else
UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
CGImageRelease(imgRef);
CGContextRelease(context);
return img;
return image;
}
#endif
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
format.scale = self.scale;
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:newRect.size format:format];
UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
CGContextSetShouldAntialias(context, true);
CGContextSetAllowsAntialiasing(context, true);
CGContextSetInterpolationQuality(context, kCGInterpolationHigh);
CGContextTranslateCTM(context, +(newRect.size.width * 0.5), +(newRect.size.height * 0.5));
#if SD_UIKIT || SD_WATCH
// Use UIKit coordinate system counterclockwise ()
CGContextRotateCTM(context, -angle);
#else
CGContextRotateCTM(context, angle);
#endif
[self drawInRect:CGRectMake(-(width * 0.5), -(height * 0.5), width, height)];
}];
return image;
}
- (nullable UIImage *)sd_flippedImageWithHorizontal:(BOOL)horizontal vertical:(BOOL)vertical {
if (!self.CGImage) return nil;
size_t width = (size_t)CGImageGetWidth(self.CGImage);
size_t height = (size_t)CGImageGetHeight(self.CGImage);
size_t bytesPerRow = width * 4;
CGColorSpaceRef colorSpace = CGColorSpaceCreateDeviceRGB();
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, bytesPerRow, colorSpace, kCGBitmapByteOrderDefault | kCGImageAlphaPremultipliedFirst);
CGColorSpaceRelease(colorSpace);
if (!context) return nil;
CGContextDrawImage(context, CGRectMake(0, 0, width, height), self.CGImage);
UInt8 *data = (UInt8 *)CGBitmapContextGetData(context);
if (!data) {
CGContextRelease(context);
return nil;
}
vImage_Buffer src = { data, height, width, bytesPerRow };
vImage_Buffer dest = { data, height, width, bytesPerRow };
if (vertical) {
vImageVerticalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
}
if (horizontal) {
vImageHorizontalReflect_ARGB8888(&src, &dest, kvImageBackgroundColorFill);
}
CGImageRef imgRef = CGBitmapContextCreateImage(context);
CGContextRelease(context);
#if SD_UIKIT || SD_WATCH
UIImage *img = [UIImage imageWithCGImage:imgRef scale:self.scale orientation:self.imageOrientation];
size_t width = self.size.width;
size_t height = self.size.height;
#if SD_UIKIT || SD_MAC
// CIImage shortcut
if (self.CIImage) {
CGAffineTransform transform = CGAffineTransformIdentity;
// Use UIKit coordinate system
if (horizontal) {
CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0);
transform = CGAffineTransformConcat(transform, flipHorizontal);
}
if (vertical) {
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height);
transform = CGAffineTransformConcat(transform, flipVertical);
}
CIImage *ciImage = [self.CIImage imageByApplyingTransform:transform];
#if SD_UIKIT
UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
#else
UIImage *img = [[UIImage alloc] initWithCGImage:imgRef scale:self.scale orientation:kCGImagePropertyOrientationUp];
UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
CGImageRelease(imgRef);
return img;
return image;
}
#endif
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
format.scale = self.scale;
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:self.size format:format];
UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
// Use UIKit coordinate system
if (horizontal) {
CGAffineTransform flipHorizontal = CGAffineTransformMake(-1, 0, 0, 1, width, 0);
CGContextConcatCTM(context, flipHorizontal);
}
if (vertical) {
CGAffineTransform flipVertical = CGAffineTransformMake(1, 0, 0, -1, 0, height);
CGContextConcatCTM(context, flipVertical);
}
[self drawInRect:CGRectMake(0, 0, width, height)];
}];
return image;
}
#pragma mark - Image Blending
- (nullable UIImage *)sd_tintedImageWithColor:(nonnull UIColor *)tintColor {
if (!self.CGImage) return nil;
if (!tintColor.CGColor) return nil;
BOOL hasTint = CGColorGetAlpha(tintColor.CGColor) > __FLT_EPSILON__;
if (!hasTint) {
#if SD_UIKIT || SD_WATCH
return [UIImage imageWithCGImage:self.CGImage scale:self.scale orientation:self.imageOrientation];
#else
return [[UIImage alloc] initWithCGImage:self.CGImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
return self;
}
#if SD_UIKIT || SD_MAC
// CIImage shortcut
if (self.CIImage) {
CIImage *ciImage = self.CIImage;
CIImage *colorImage = [CIImage imageWithColor:[[CIColor alloc] initWithColor:tintColor]];
colorImage = [colorImage imageByCroppingToRect:ciImage.extent];
CIFilter *filter = [CIFilter filterWithName:@"CISourceAtopCompositing"];
[filter setValue:colorImage forKey:kCIInputImageKey];
[filter setValue:ciImage forKey:kCIInputBackgroundImageKey];
ciImage = filter.outputImage;
#if SD_UIKIT
UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
#else
UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
return image;
}
#endif
CGSize size = self.size;
CGRect rect = { CGPointZero, size };
CGFloat scale = self.scale;
@ -347,23 +417,30 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
// blend mode, see https://en.wikipedia.org/wiki/Alpha_compositing
CGBlendMode blendMode = kCGBlendModeSourceAtop;
SDGraphicsBeginImageContextWithOptions(size, NO, scale);
CGContextRef context = SDGraphicsGetCurrentContext();
[self drawInRect:rect];
CGContextSetBlendMode(context, blendMode);
CGContextSetFillColorWithColor(context, tintColor.CGColor);
CGContextFillRect(context, rect);
UIImage *image = SDGraphicsGetImageFromCurrentImageContext();
SDGraphicsEndImageContext();
SDGraphicsImageRendererFormat *format = [[SDGraphicsImageRendererFormat alloc] init];
format.scale = scale;
SDGraphicsImageRenderer *renderer = [[SDGraphicsImageRenderer alloc] initWithSize:size format:format];
UIImage *image = [renderer imageWithActions:^(CGContextRef _Nonnull context) {
[self drawInRect:rect];
CGContextSetBlendMode(context, blendMode);
CGContextSetFillColorWithColor(context, tintColor.CGColor);
CGContextFillRect(context, rect);
}];
return image;
}
- (nullable UIColor *)sd_colorAtPoint:(CGPoint)point {
if (!self) {
return nil;
CGImageRef imageRef = NULL;
// CIImage compatible
#if SD_UIKIT || SD_MAC
if (self.CIImage) {
imageRef = SDCreateCGImageFromCIImage(self.CIImage);
}
#endif
if (!imageRef) {
imageRef = self.CGImage;
CGImageRetain(imageRef);
}
CGImageRef imageRef = self.CGImage;
if (!imageRef) {
return nil;
}
@ -372,42 +449,53 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
CGFloat width = CGImageGetWidth(imageRef);
CGFloat height = CGImageGetHeight(imageRef);
if (point.x < 0 || point.y < 0 || point.x >= width || point.y >= height) {
CGImageRelease(imageRef);
return nil;
}
// Get pixels
CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
if (!provider) {
CGImageRelease(imageRef);
return nil;
}
CFDataRef data = CGDataProviderCopyData(provider);
if (!data) {
CGImageRelease(imageRef);
return nil;
}
// Get pixel at point
size_t bytesPerRow = CGImageGetBytesPerRow(imageRef);
size_t components = CGImageGetBitsPerPixel(imageRef) / CGImageGetBitsPerComponent(imageRef);
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
CFRange range = CFRangeMake(bytesPerRow * point.y + components * point.x, 4);
if (CFDataGetLength(data) < range.location + range.length) {
CFRelease(data);
CGImageRelease(imageRef);
return nil;
}
Pixel_8888 pixel = {0};
CFDataGetBytes(data, range, pixel);
CFRelease(data);
CGImageRelease(imageRef);
// Convert to color
CGBitmapInfo bitmapInfo = CGImageGetBitmapInfo(imageRef);
return SDGetColorFromPixel(pixel, bitmapInfo);
}
- (nullable NSArray<UIColor *> *)sd_colorsWithRect:(CGRect)rect {
if (!self) {
return nil;
CGImageRef imageRef = NULL;
// CIImage compatible
#if SD_UIKIT || SD_MAC
if (self.CIImage) {
imageRef = SDCreateCGImageFromCIImage(self.CIImage);
}
#endif
if (!imageRef) {
imageRef = self.CGImage;
CGImageRetain(imageRef);
}
CGImageRef imageRef = self.CGImage;
if (!imageRef) {
return nil;
}
@ -416,16 +504,19 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
CGFloat width = CGImageGetWidth(imageRef);
CGFloat height = CGImageGetHeight(imageRef);
if (CGRectGetWidth(rect) <= 0 || CGRectGetHeight(rect) <= 0 || CGRectGetMinX(rect) < 0 || CGRectGetMinY(rect) < 0 || CGRectGetMaxX(rect) > width || CGRectGetMaxY(rect) > height) {
CGImageRelease(imageRef);
return nil;
}
// Get pixels
CGDataProviderRef provider = CGImageGetDataProvider(imageRef);
if (!provider) {
CGImageRelease(imageRef);
return nil;
}
CFDataRef data = CGDataProviderCopyData(provider);
if (!data) {
CGImageRelease(imageRef);
return nil;
}
@ -437,6 +528,7 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
size_t end = bytesPerRow * (CGRectGetMaxY(rect) - 1) + components * CGRectGetMaxX(rect);
if (CFDataGetLength(data) < (CFIndex)end) {
CFRelease(data);
CGImageRelease(imageRef);
return nil;
}
@ -460,29 +552,53 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
[colors addObject:color];
}
CFRelease(data);
CGImageRelease(imageRef);
return [colors copy];
}
#pragma mark - Image Effect
// We use vImage to do box convolve for performance and support for watchOS. However, you can just use `CIFilter.CIBoxBlur`. For other blur effect, use any filter in `CICategoryBlur`
// We use vImage to do box convolve for performance and support for watchOS. However, you can just use `CIFilter.CIGaussianBlur`. For other blur effect, use any filter in `CICategoryBlur`
- (nullable UIImage *)sd_blurredImageWithRadius:(CGFloat)blurRadius {
if (self.size.width < 1 || self.size.height < 1) {
return nil;
}
if (!self.CGImage) {
return nil;
}
BOOL hasBlur = blurRadius > __FLT_EPSILON__;
if (!hasBlur) {
return self;
}
CGFloat scale = self.scale;
CGFloat inputRadius = blurRadius * scale;
#if SD_UIKIT || SD_MAC
if (self.CIImage) {
CIFilter *filter = [CIFilter filterWithName:@"CIGaussianBlur"];
[filter setValue:self.CIImage forKey:kCIInputImageKey];
[filter setValue:@(inputRadius) forKey:kCIInputRadiusKey];
CIImage *ciImage = filter.outputImage;
ciImage = [ciImage imageByCroppingToRect:CGRectMake(0, 0, self.size.width, self.size.height)];
#if SD_UIKIT
UIImage *image = [UIImage imageWithCIImage:ciImage scale:self.scale orientation:self.imageOrientation];
#else
UIImage *image = [[UIImage alloc] initWithCIImage:ciImage scale:self.scale orientation:kCGImagePropertyOrientationUp];
#endif
return image;
}
#endif
CGImageRef imageRef = self.CGImage;
//convert to BGRA if it isn't
if (CGImageGetBitsPerPixel(imageRef) != 32 ||
CGImageGetBitsPerComponent(imageRef) != 8 ||
!((CGImageGetBitmapInfo(imageRef) & kCGBitmapAlphaInfoMask))) {
SDGraphicsBeginImageContextWithOptions(self.size, NO, self.scale);
[self drawInRect:CGRectMake(0, 0, self.size.width, self.size.height)];
imageRef = SDGraphicsGetImageFromCurrentImageContext().CGImage;
SDGraphicsEndImageContext();
}
vImage_Buffer effect = {}, scratch = {};
vImage_Buffer *input = NULL, *output = NULL;
@ -490,14 +606,14 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
.bitsPerComponent = 8,
.bitsPerPixel = 32,
.colorSpace = NULL,
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Little, //requests a BGRA buffer.
.bitmapInfo = kCGImageAlphaPremultipliedFirst | kCGBitmapByteOrder32Host, //requests a BGRA buffer.
.version = 0,
.decode = NULL,
.renderingIntent = kCGRenderingIntentDefault
};
vImage_Error err;
err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImagePrintDiagnosticsToConsole);
err = vImageBuffer_InitWithCGImage(&effect, &format, NULL, imageRef, kvImageNoFlags);
if (err != kvImageNoError) {
NSLog(@"UIImage+Transform error: vImageBuffer_InitWithCGImage returned error code %zi for inputImage: %@", err, self);
return nil;
@ -524,9 +640,8 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
//
// ... if d is odd, use three box-blurs of size 'd', centered on the output pixel.
//
CGFloat inputRadius = blurRadius * scale;
if (inputRadius - 2.0 < __FLT_EPSILON__) inputRadius = 2.0;
uint32_t radius = floor((inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5) / 2);
uint32_t radius = floor(inputRadius * 3.0 * sqrt(2 * M_PI) / 4 + 0.5);
radius |= 1; // force radius to be odd so that the three box-blur methodology works.
int iterations;
if (blurRadius * scale < 0.5) iterations = 1;
@ -562,12 +677,19 @@ static inline UIColor * SDGetColorFromPixel(Pixel_8888 pixel, CGBitmapInfo bitma
#if SD_UIKIT || SD_MAC
- (nullable UIImage *)sd_filteredImageWithFilter:(nonnull CIFilter *)filter {
if (!self.CGImage) return nil;
CIContext *context = [CIContext context];
CIImage *inputImage = [CIImage imageWithCGImage:self.CGImage];
CIImage *inputImage;
if (self.CIImage) {
inputImage = self.CIImage;
} else {
CGImageRef imageRef = self.CGImage;
if (!imageRef) {
return nil;
}
inputImage = [CIImage imageWithCGImage:imageRef];
}
if (!inputImage) return nil;
CIContext *context = [CIContext context];
[filter setValue:inputImage forKey:kCIInputImageKey];
CIImage *outputImage = filter.outputImage;
if (!outputImage) return nil;

View File

@ -31,6 +31,13 @@ typedef void(^SDSetImageBlock)(UIImage * _Nullable image, NSData * _Nullable ima
*/
@property (nonatomic, strong, readonly, nullable) NSURL *sd_imageURL;
/**
* Get the current image operation key. Operation key is used to identify the different queries for one view instance (like UIButton).
* See more about this in `SDWebImageContextSetImageOperationKey`.
* @note You can use method `UIView+WebCacheOperation` to invesigate different queries' operation.
*/
@property (nonatomic, strong, readonly, nullable) NSString *sd_latestOperationKey;
/**
* The current image loading progress associated to the view. The unit count is the received size and excepted size of download.
* The `totalUnitCount` and `completedUnitCount` will be reset to 0 after a new image loading start (change from current queue). And they will be set to `SDWebImageProgressUnitCountUnknown` if the progressBlock not been called but the image loading success to mark the progress finished (change from main queue).

View File

@ -52,10 +52,19 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
setImageBlock:(nullable SDSetImageBlock)setImageBlock
progress:(nullable SDImageLoaderProgressBlock)progressBlock
completed:(nullable SDInternalCompletionBlock)completedBlock {
context = [context copy]; // copy to avoid mutable object
if (context) {
// copy to avoid mutable object
context = [context copy];
} else {
context = [NSDictionary dictionary];
}
NSString *validOperationKey = context[SDWebImageContextSetImageOperationKey];
if (!validOperationKey) {
// pass through the operation key to downstream, which can used for tracing operation or image view class
validOperationKey = NSStringFromClass([self class]);
SDWebImageMutableContext *mutableContext = [context mutableCopy];
mutableContext[SDWebImageContextSetImageOperationKey] = validOperationKey;
context = [mutableContext copy];
}
self.sd_latestOperationKey = validOperationKey;
[self sd_cancelImageLoadOperationWithKey:validOperationKey];
@ -80,10 +89,14 @@ const int64_t SDWebImageProgressUnitCountUnknown = 1LL;
[self sd_startImageIndicator];
id<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) {

View File

@ -12,7 +12,7 @@
#import "UIImage+Transform.h"
@interface NSBezierPath (RoundedCorners)
@interface NSBezierPath (SDRoundedCorners)
/**
Convenience way to create a bezier path with the specify rounding corners on macOS. Same as the one on `UIBezierPath`.

View File

@ -6,11 +6,11 @@
* file that was distributed with this source code.
*/
#import "NSBezierPath+RoundedCorners.h"
#import "NSBezierPath+SDRoundedCorners.h"
#if SD_MAC
@implementation NSBezierPath (RoundedCorners)
@implementation NSBezierPath (SDRoundedCorners)
+ (instancetype)sd_bezierPathWithRoundedRect:(NSRect)rect byRoundingCorners:(SDRectCorner)corners cornerRadius:(CGFloat)cornerRadius {
NSBezierPath *path = [NSBezierPath bezierPath];

View File

@ -0,0 +1,14 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDWebImageCompat.h"
/// Copy the associated object from source image to target image. The associated object including all the category read/write properties.
/// @param source source
/// @param target target
FOUNDATION_EXPORT void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable target);

View File

@ -0,0 +1,27 @@
/*
* This file is part of the SDWebImage package.
* (c) Olivier Poitrey <rs@dailymotion.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
#import "SDAssociatedObject.h"
#import "UIImage+Metadata.h"
#import "UIImage+ExtendedCacheData.h"
#import "UIImage+MemoryCacheCost.h"
#import "UIImage+ForceDecode.h"
void SDImageCopyAssociatedObject(UIImage * _Nullable source, UIImage * _Nullable target) {
if (!source || !target) {
return;
}
// Image Metadata
target.sd_isIncremental = source.sd_isIncremental;
target.sd_imageLoopCount = source.sd_imageLoopCount;
target.sd_imageFormat = source.sd_imageFormat;
// Force Decode
target.sd_isDecoded = source.sd_isDecoded;
// Extended Cache Data
target.sd_extendedObject = source.sd_extendedObject;
}

View File

@ -11,6 +11,7 @@
@class SDAsyncBlockOperation;
typedef void (^SDAsyncBlock)(SDAsyncBlockOperation * __nonnull asyncOperation);
/// A async block operation, success after you call `completer` (not like `NSBlockOperation` which is for sync block, success on return)
@interface SDAsyncBlockOperation : NSOperation
- (nonnull instancetype)initWithBlock:(nonnull SDAsyncBlock)block;

View File

@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
/// Device information helper methods
@interface SDDeviceHelper : NSObject
+ (NSUInteger)totalMemory;

View File

@ -9,9 +9,8 @@
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
// Cross-platform display link wrapper. Do not retain the target
// Use `CADisplayLink` on iOS/tvOS, `CVDisplayLink` on macOS, `NSTimer` on watchOS
/// Cross-platform display link wrapper. Do not retain the target
/// Use `CADisplayLink` on iOS/tvOS, `CVDisplayLink` on macOS, `NSTimer` on watchOS
@interface SDDisplayLink : NSObject
@property (readonly, nonatomic, weak, nullable) id target;

View File

@ -0,0 +1,19 @@
//
// This file is from https://gist.github.com/zydeco/6292773
//
// Created by Jesús A. Álvarez on 2008-12-17.
// Copyright 2008-2009 namedfork.net. All rights reserved.
//
#import <Foundation/Foundation.h>
/// File Extended Attribute (xattr) helper methods
@interface SDFileAttributeHelper : NSObject
+ (nullable NSArray<NSString *> *)extendedAttributeNamesAtPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err;
+ (BOOL)hasExtendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err;
+ (nullable NSData *)extendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err;
+ (BOOL)setExtendedAttribute:(nonnull NSString *)name value:(nonnull NSData *)value atPath:(nonnull NSString *)path traverseLink:(BOOL)follow overwrite:(BOOL)overwrite error:(NSError * _Nullable * _Nullable)err;
+ (BOOL)removeExtendedAttribute:(nonnull NSString *)name atPath:(nonnull NSString *)path traverseLink:(BOOL)follow error:(NSError * _Nullable * _Nullable)err;
@end

View File

@ -0,0 +1,127 @@
//
// This file is from https://gist.github.com/zydeco/6292773
//
// Created by Jesús A. Álvarez on 2008-12-17.
// Copyright 2008-2009 namedfork.net. All rights reserved.
//
#import "SDFileAttributeHelper.h"
#import <sys/xattr.h>
@implementation SDFileAttributeHelper
+ (NSArray*)extendedAttributeNamesAtPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err {
int flags = follow? 0 : XATTR_NOFOLLOW;
// get size of name list
ssize_t nameBuffLen = listxattr([path fileSystemRepresentation], NULL, 0, flags);
if (nameBuffLen == -1) {
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
@{
@"error": [NSString stringWithUTF8String:strerror(errno)],
@"function": @"listxattr",
@":path": path,
@":traverseLink": @(follow)
}
];
return nil;
} else if (nameBuffLen == 0) return @[];
// get name list
NSMutableData *nameBuff = [NSMutableData dataWithLength:nameBuffLen];
listxattr([path fileSystemRepresentation], [nameBuff mutableBytes], nameBuffLen, flags);
// convert to array
NSMutableArray * names = [NSMutableArray arrayWithCapacity:5];
char *nextName, *endOfNames = [nameBuff mutableBytes] + nameBuffLen;
for(nextName = [nameBuff mutableBytes]; nextName < endOfNames; nextName += 1+strlen(nextName))
[names addObject:[NSString stringWithUTF8String:nextName]];
return names.copy;
}
+ (BOOL)hasExtendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err {
int flags = follow? 0 : XATTR_NOFOLLOW;
// get size of name list
ssize_t nameBuffLen = listxattr([path fileSystemRepresentation], NULL, 0, flags);
if (nameBuffLen == -1) {
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
@{
@"error": [NSString stringWithUTF8String:strerror(errno)],
@"function": @"listxattr",
@":path": path,
@":traverseLink": @(follow)
}
];
return NO;
} else if (nameBuffLen == 0) return NO;
// get name list
NSMutableData *nameBuff = [NSMutableData dataWithLength:nameBuffLen];
listxattr([path fileSystemRepresentation], [nameBuff mutableBytes], nameBuffLen, flags);
// find our name
char *nextName, *endOfNames = [nameBuff mutableBytes] + nameBuffLen;
for(nextName = [nameBuff mutableBytes]; nextName < endOfNames; nextName += 1+strlen(nextName))
if (strcmp(nextName, [name UTF8String]) == 0) return YES;
return NO;
}
+ (NSData *)extendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err {
int flags = follow? 0 : XATTR_NOFOLLOW;
// get length
ssize_t attrLen = getxattr([path fileSystemRepresentation], [name UTF8String], NULL, 0, 0, flags);
if (attrLen == -1) {
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
@{
@"error": [NSString stringWithUTF8String:strerror(errno)],
@"function": @"getxattr",
@":name": name,
@":path": path,
@":traverseLink": @(follow)
}
];
return nil;
}
// get attribute data
NSMutableData *attrData = [NSMutableData dataWithLength:attrLen];
getxattr([path fileSystemRepresentation], [name UTF8String], [attrData mutableBytes], attrLen, 0, flags);
return attrData;
}
+ (BOOL)setExtendedAttribute:(NSString *)name value:(NSData *)value atPath:(NSString *)path traverseLink:(BOOL)follow overwrite:(BOOL)overwrite error:(NSError **)err {
int flags = (follow? 0 : XATTR_NOFOLLOW) | (overwrite? 0 : XATTR_CREATE);
if (0 == setxattr([path fileSystemRepresentation], [name UTF8String], [value bytes], [value length], 0, flags)) return YES;
// error
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
@{
@"error": [NSString stringWithUTF8String:strerror(errno)],
@"function": @"setxattr",
@":name": name,
@":value.length": @(value.length),
@":path": path,
@":traverseLink": @(follow),
@":overwrite": @(overwrite)
}
];
return NO;
}
+ (BOOL)removeExtendedAttribute:(NSString *)name atPath:(NSString *)path traverseLink:(BOOL)follow error:(NSError **)err {
int flags = (follow? 0 : XATTR_NOFOLLOW);
if (0 == removexattr([path fileSystemRepresentation], [name UTF8String], flags)) return YES;
// error
if (err) *err = [NSError errorWithDomain:NSPOSIXErrorDomain code:errno userInfo:
@{
@"error": [NSString stringWithUTF8String:strerror(errno)],
@"function": @"removexattr",
@":name": name,
@":path": path,
@":traverseLink": @(follow)
}
];
return NO;
}
@end

View File

@ -9,8 +9,8 @@
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
// Apple parse the Asset Catalog compiled file(`Assets.car`) by CoreUI.framework, however it's a private framework and there are no other ways to directly get the data. So we just process the normal bundle files :)
/// A Image-Asset manager to work like UIKit/AppKit's image cache behavior
/// Apple parse the Asset Catalog compiled file(`Assets.car`) by CoreUI.framework, however it's a private framework and there are no other ways to directly get the data. So we just process the normal bundle files :)
@interface SDImageAssetManager : NSObject
@property (nonatomic, strong, nonnull) NSMapTable<NSString *, UIImage *> *imageTable;

View File

@ -9,7 +9,7 @@
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
// This is used for operation management, but not for operation queue execute
/// This is used for operation management, but not for operation queue execute
@interface SDImageCachesManagerOperation : NSOperation
@property (nonatomic, assign, readonly) NSUInteger pendingCount;

View File

@ -13,5 +13,6 @@
+ (NSTimeInterval)frameDurationAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source;
+ (NSUInteger)imageLoopCountWithSource:(nonnull CGImageSourceRef)source;
+ (nullable UIImage *)createFrameAtIndex:(NSUInteger)index source:(nonnull CGImageSourceRef)source scale:(CGFloat)scale preserveAspectRatio:(BOOL)preserveAspectRatio thumbnailSize:(CGSize)thumbnailSize options:(nullable NSDictionary *)options;
@end

View File

@ -21,6 +21,18 @@
#define SD_OPTIONS_CONTAINS(options, value) (((options) & (value)) == (value))
#endif
#ifndef SD_CSTRING
#define SD_CSTRING(str) #str
#endif
#ifndef SD_NSSTRING
#define SD_NSSTRING(str) @(SD_CSTRING(str))
#endif
#ifndef SD_SEL_SPI
#define SD_SEL_SPI(name) NSSelectorFromString([NSString stringWithFormat:@"_%@", SD_NSSTRING(name)])
#endif
#ifndef weakify
#define weakify(...) \
sd_keywordify \

View File

@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>
#import "SDWebImageCompat.h"
/// A weak proxy which forward all the message to the target
@interface SDWeakProxy : NSProxy
@property (nonatomic, weak, readonly, nullable) id target;

View File

@ -8,7 +8,7 @@
#import "SDWebImageCompat.h"
@interface UIColor (HexString)
@interface UIColor (SDHexString)
/**
Convenience way to get hex string from color. The output should always be 32-bit RGBA hex string like `#00000000`.

View File

@ -6,9 +6,9 @@
* file that was distributed with this source code.
*/
#import "UIColor+HexString.h"
#import "UIColor+SDHexString.h"
@implementation UIColor (HexString)
@implementation UIColor (SDHexString)
- (NSString *)sd_hexString {
CGFloat red, green, blue, alpha;

View File

@ -0,0 +1 @@
../../Core/NSButton+WebCache.h

View File

@ -0,0 +1 @@
../../Core/NSData+ImageContentType.h

View File

@ -0,0 +1 @@
../../Core/NSImage+Compatibility.h

View File

@ -0,0 +1 @@
../../Core/SDAnimatedImage.h

View File

@ -0,0 +1 @@
../../Core/SDAnimatedImagePlayer.h

View File

@ -0,0 +1 @@
../../Core/SDAnimatedImageRep.h

View File

@ -0,0 +1 @@
../../Core/SDAnimatedImageView+WebCache.h

View File

@ -0,0 +1 @@
../../Core/SDAnimatedImageView.h

View File

@ -0,0 +1 @@
../../Core/SDDiskCache.h

View File

@ -0,0 +1 @@
../../Core/SDGraphicsImageRenderer.h

View File

@ -0,0 +1 @@
../../Core/SDImageAPNGCoder.h

View File

@ -0,0 +1 @@
../../Core/SDImageCache.h

View File

@ -0,0 +1 @@
../../Core/SDImageCacheConfig.h

View File

@ -0,0 +1 @@
../../Core/SDImageCacheDefine.h

View File

@ -0,0 +1 @@
../../Core/SDImageCachesManager.h

View File

@ -0,0 +1 @@
../../Core/SDImageCoder.h

View File

@ -0,0 +1 @@
../../Core/SDImageCoderHelper.h

View File

@ -0,0 +1 @@
../../Core/SDImageCodersManager.h

View File

@ -0,0 +1 @@
../../Core/SDImageFrame.h

View File

@ -0,0 +1 @@
../../Core/SDImageGIFCoder.h

View File

@ -0,0 +1 @@
../../Core/SDImageGraphics.h

View File

@ -0,0 +1 @@
../../Core/SDImageHEICCoder.h

View File

@ -0,0 +1 @@
../../Core/SDImageIOAnimatedCoder.h

View File

@ -0,0 +1 @@
../../Core/SDImageIOCoder.h

View File

@ -0,0 +1 @@
../../Core/SDImageLoader.h

View File

@ -0,0 +1 @@
../../Core/SDImageLoadersManager.h

View File

@ -0,0 +1 @@
../../Core/SDImageTransformer.h

View File

@ -0,0 +1 @@
../../Core/SDMemoryCache.h

Some files were not shown because too many files have changed in this diff Show More