Skip to content

Commit 62f7a1d

Browse files
authored
Merge pull request onevcat#2349 from onevcat/feature/thumbnail-provider
Provide a `ThumbnailImageDataProvider` to create thumbnail image without dealing with explicitly downloading image data
2 parents fa3609d + 6e279da commit 62f7a1d

File tree

1 file changed

+81
-0
lines changed

1 file changed

+81
-0
lines changed

Sources/General/ImageSource/ImageDataProvider.swift

+81
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
// THE SOFTWARE.
2626

2727
import Foundation
28+
import ImageIO
2829

2930
/// Represents a data provider to provide image data to Kingfisher when setting with
3031
/// ``Source/provider(_:)`` source. Compared to ``Source/network(_:)`` member, it gives a chance
@@ -190,3 +191,83 @@ public struct RawImageDataProvider: ImageDataProvider {
190191
handler(.success(data))
191192
}
192193
}
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

Comments
 (0)