Files
immich/mobile/ios/Runner/Images/ThumbnailsImpl.swift
T
mertalev ddd65dea58 thumbhash improvements
thumbhash render box

refactor

wip

rebase
2025-08-11 01:08:57 -04:00

125 lines
4.0 KiB
Swift

import CryptoKit
import Flutter
import MobileCoreServices
import Photos
// https://stackoverflow.com/a/55839062
extension UIImage {
func toData(options: NSDictionary?, type: ImageType) -> Data? {
guard cgImage != nil else { return nil }
return toData(options: options, type: type.value)
}
// about properties: https://developer.apple.com/documentation/imageio/1464962-cgimagedestinationaddimage
func toData(options: NSDictionary?, type: CFString) -> Data? {
guard let cgImage = cgImage else { return nil }
return autoreleasepool { () -> Data? in
let data = NSMutableData()
guard
let imageDestination = CGImageDestinationCreateWithData(data as CFMutableData, type, 1, nil)
else { return nil }
CGImageDestinationAddImage(imageDestination, cgImage, options)
CGImageDestinationFinalize(imageDestination)
return data as Data
}
}
enum ImageType {
case image // abstract image data
case jpeg // JPEG image
case jpeg2000 // JPEG-2000 image
case tiff // TIFF image
case pict // Quickdraw PICT format
case gif // GIF image
case png // PNG image
case quickTimeImage // QuickTime image format (OSType 'qtif')
case appleICNS // Apple icon data
case bmp // Windows bitmap
case ico // Windows icon data
case rawImage // base type for raw image data (.raw)
case scalableVectorGraphics // SVG image
case livePhoto // Live Photo
var value: CFString {
switch self {
case .image: return kUTTypeImage
case .jpeg: return kUTTypeJPEG
case .jpeg2000: return kUTTypeJPEG2000
case .tiff: return kUTTypeTIFF
case .pict: return kUTTypePICT
case .gif: return kUTTypeGIF
case .png: return kUTTypePNG
case .quickTimeImage: return kUTTypeQuickTimeImage
case .appleICNS: return kUTTypeAppleICNS
case .bmp: return kUTTypeBMP
case .ico: return kUTTypeICO
case .rawImage: return kUTTypeRawImage
case .scalableVectorGraphics: return kUTTypeScalableVectorGraphics
case .livePhoto: return kUTTypeLivePhoto
}
}
}
}
class ThumbnailApiImpl: ThumbnailApi {
private static let cacheManager = PHImageManager.default()
private static let fetchOptions = {
let fetchOptions = PHFetchOptions()
fetchOptions.fetchLimit = 1
return fetchOptions
}()
private static let requestOptions = {
let requestOptions = PHImageRequestOptions()
requestOptions.isNetworkAccessAllowed = true
requestOptions.deliveryMode = .highQualityFormat
requestOptions.resizeMode = .exact
requestOptions.isSynchronous = true
requestOptions.version = .current
return requestOptions
}()
private static let processingQueue = DispatchQueue(
label: "thumbnail.processing", qos: .userInteractive, attributes: .concurrent)
func getThumbnail(
assetId: String,
width: Int64,
height: Int64,
completion: @escaping (Result<FlutterStandardTypedData, Error>) -> Void
) {
Self.processingQueue.async {
do {
let asset = try self.getAsset(assetId: assetId)
Self.cacheManager.requestImage(
for: asset,
targetSize: CGSize(width: Double(width), height: Double(height)),
contentMode: .aspectFill,
options: Self.requestOptions,
resultHandler: { (image, info) -> Void in
guard let data = image?.toData(options: nil, type: .bmp) else { return }
completion(.success(FlutterStandardTypedData(bytes: data)))
}
)
} catch {
completion(
.failure(PigeonError(code: "", message: "Could not get asset data", details: nil)))
}
}
}
private func getAsset(assetId: String) throws -> PHAsset {
guard
let asset = PHAsset.fetchAssets(withLocalIdentifiers: [assetId], options: Self.fetchOptions)
.firstObject
else {
throw PigeonError(code: "", message: "Could not fetch asset", details: nil)
}
return asset
}
// func cancel(requestId: Int32) {
// Self.cacheManager.cancelImageRequest(requestId as PHImageRequestID)
// }
}