From 6e279da13e4a180b2cf051f2c6ff908fbf60c580 Mon Sep 17 00:00:00 2001 From: onevcat Date: Thu, 12 Dec 2024 00:56:07 +0900 Subject: [PATCH] Add ThumbnailImageDataProvider --- .../ImageSource/ImageDataProvider.swift | 81 +++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/Sources/General/ImageSource/ImageDataProvider.swift b/Sources/General/ImageSource/ImageDataProvider.swift index 8ca1e67cd..0065a9932 100644 --- a/Sources/General/ImageSource/ImageDataProvider.swift +++ b/Sources/General/ImageSource/ImageDataProvider.swift @@ -25,6 +25,7 @@ // THE SOFTWARE. import Foundation +import ImageIO /// Represents a data provider to provide image data to Kingfisher when setting with /// ``Source/provider(_:)`` source. Compared to ``Source/network(_:)`` member, it gives a chance @@ -190,3 +191,83 @@ public struct RawImageDataProvider: ImageDataProvider { handler(.success(data)) } } + +/// A data provider that creates a thumbnail from a URL using Core Graphics. +public struct ThumbnailImageDataProvider: ImageDataProvider { + + public enum ThumbnailImageDataProviderError: Error { + case invalidImageSource + case invalidThumbnail + case writeDataError + case finalizeDataError + } + + /// The URL from which to load the image + public let url: URL + + /// The maximum size of the thumbnail in pixels + public var maxPixelSize: CGFloat + + /// Whether to always create a thumbnail even if the image is smaller than maxPixelSize + public var alwaysCreateThumbnail: Bool + + /// The cache key for this provider + public var cacheKey: String + + /// Creates a new thumbnail data provider + /// - Parameters: + /// - url: The URL from which to load the image + /// - maxPixelSize: The maximum size of the thumbnail in pixels + /// - alwaysCreateThumbnail: Whether to always create a thumbnail even if the image is smaller than maxPixelSize + public init( + url: URL, + maxPixelSize: CGFloat, + alwaysCreateThumbnail: Bool = true, + cacheKey: String? = nil + ) { + self.url = url + self.maxPixelSize = maxPixelSize + self.alwaysCreateThumbnail = alwaysCreateThumbnail + self.cacheKey = cacheKey ?? "\(url.absoluteString)_thumb_\(maxPixelSize)_\(alwaysCreateThumbnail)" + } + + public func data(handler: @escaping @Sendable (Result) -> Void) { + DispatchQueue.global(qos: .userInitiated).async { + do { + guard let url = URL(string: url.absoluteString) else { + throw KingfisherError.imageSettingError(reason: .emptySource) + + } + + guard let imageSource = CGImageSourceCreateWithURL(url as CFURL, nil) else { + throw ThumbnailImageDataProviderError.invalidImageSource + } + + let options = [ + kCGImageSourceThumbnailMaxPixelSize: maxPixelSize, + kCGImageSourceCreateThumbnailFromImageAlways: alwaysCreateThumbnail + ] + + guard let thumbnailRef = CGImageSourceCreateThumbnailAtIndex(imageSource, 0, options as CFDictionary) else { + throw ThumbnailImageDataProviderError.invalidThumbnail + } + + let data = NSMutableData() + guard let destination = CGImageDestinationCreateWithData( + data, CGImageSourceGetType(imageSource)!, 1, nil + ) else { + throw ThumbnailImageDataProviderError.writeDataError + } + + CGImageDestinationAddImage(destination, thumbnailRef, nil) + if CGImageDestinationFinalize(destination) { + handler(.success(data as Data)) + } else { + throw ThumbnailImageDataProviderError.finalizeDataError + } + } catch { + handler(.failure(error)) + } + } + } +}