|
25 | 25 | // THE SOFTWARE.
|
26 | 26 |
|
27 | 27 | import Foundation
|
| 28 | +import ImageIO |
28 | 29 |
|
29 | 30 | /// Represents a data provider to provide image data to Kingfisher when setting with
|
30 | 31 | /// ``Source/provider(_:)`` source. Compared to ``Source/network(_:)`` member, it gives a chance
|
@@ -190,3 +191,83 @@ public struct RawImageDataProvider: ImageDataProvider {
|
190 | 191 | handler(.success(data))
|
191 | 192 | }
|
192 | 193 | }
|
| 194 | + |
| 195 | +/// A data provider that creates a thumbnail from a URL using Core Graphics. |
| 196 | +public struct ThumbnailImageDataProvider: ImageDataProvider { |
| 197 | + |
| 198 | + public enum ThumbnailImageDataProviderError: Error { |
| 199 | + case invalidImageSource |
| 200 | + case invalidThumbnail |
| 201 | + case writeDataError |
| 202 | + case finalizeDataError |
| 203 | + } |
| 204 | + |
| 205 | + /// The URL from which to load the image |
| 206 | + public let url: URL |
| 207 | + |
| 208 | + /// The maximum size of the thumbnail in pixels |
| 209 | + public var maxPixelSize: CGFloat |
| 210 | + |
| 211 | + /// Whether to always create a thumbnail even if the image is smaller than maxPixelSize |
| 212 | + public var alwaysCreateThumbnail: Bool |
| 213 | + |
| 214 | + /// The cache key for this provider |
| 215 | + public var cacheKey: String |
| 216 | + |
| 217 | + /// Creates a new thumbnail data provider |
| 218 | + /// - Parameters: |
| 219 | + /// - url: The URL from which to load the image |
| 220 | + /// - maxPixelSize: The maximum size of the thumbnail in pixels |
| 221 | + /// - alwaysCreateThumbnail: Whether to always create a thumbnail even if the image is smaller than maxPixelSize |
| 222 | + public init( |
| 223 | + url: URL, |
| 224 | + maxPixelSize: CGFloat, |
| 225 | + alwaysCreateThumbnail: Bool = true, |
| 226 | + cacheKey: String? = nil |
| 227 | + ) { |
| 228 | + self.url = url |
| 229 | + self.maxPixelSize = maxPixelSize |
| 230 | + self.alwaysCreateThumbnail = alwaysCreateThumbnail |
| 231 | + self.cacheKey = cacheKey ?? "\(url.absoluteString)_thumb_\(maxPixelSize)_\(alwaysCreateThumbnail)" |
| 232 | + } |
| 233 | + |
| 234 | + public func data(handler: @escaping @Sendable (Result<Data, any Error>) -> Void) { |
| 235 | + DispatchQueue.global(qos: .userInitiated).async { |
| 236 | + do { |
| 237 | + guard let url = URL(string: url.absoluteString) else { |
| 238 | + throw KingfisherError.imageSettingError(reason: .emptySource) |
| 239 | + |
| 240 | + } |
| 241 | + |
| 242 | + guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { |
| 243 | + throw ThumbnailImageDataProviderError.invalidImageSource |
| 244 | + } |
| 245 | + |
| 246 | + let options = [ |
| 247 | + kCGImageSourceThumbnailMaxPixelSize: maxPixelSize, |
| 248 | + kCGImageSourceCreateThumbnailFromImageAlways: alwaysCreateThumbnail |
| 249 | + ] |
| 250 | + |
| 251 | + guard let thumbnailRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else { |
| 252 | + throw ThumbnailImageDataProviderError.invalidThumbnail |
| 253 | + } |
| 254 | + |
| 255 | + let data = NSMutableData() |
| 256 | + guard let destination = CGImageDestinationCreateWithData( |
| 257 | + data, CGImageSourceGetType(imageSource)!, 1, nil |
| 258 | + ) else { |
| 259 | + throw ThumbnailImageDataProviderError.writeDataError |
| 260 | + } |
| 261 | + |
| 262 | + CGImageDestinationAddImage(destination, thumbnailRef, nil) |
| 263 | + if CGImageDestinationFinalize(destination) { |
| 264 | + handler(.success(data as Data)) |
| 265 | + } else { |
| 266 | + throw ThumbnailImageDataProviderError.finalizeDataError |
| 267 | + } |
| 268 | + } catch { |
| 269 | + handler(.failure(error)) |
| 270 | + } |
| 271 | + } |
| 272 | + } |
| 273 | +} |
0 commit comments