Skip to content

Commit

Permalink
Add ThumbnailImageDataProvider
Browse files Browse the repository at this point in the history
  • Loading branch information
onevcat committed Dec 11, 2024
1 parent 623e159 commit 6e279da
Showing 1 changed file with 81 additions and 0 deletions.
81 changes: 81 additions & 0 deletions Sources/General/ImageSource/ImageDataProvider.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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<Data, any Error>) -> 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))
}
}
}
}

0 comments on commit 6e279da

Please sign in to comment.