Files
Schnappix/node_modules/expo-image-manipulator/ios/ImageManipulatorUtils.swift
T

129 lines
4.1 KiB
Swift

import SDWebImageWebPCoder
import Photos
import ExpoModulesCore
internal typealias SaveImageResult = (url: URL, data: Data)
/**
Loads the image from given URL.
*/
internal func loadImage(atUrl url: URL, appContext: AppContext) async throws -> UIImage {
if url.scheme == "data" {
guard let data = try? Data(contentsOf: url), let image = UIImage(data: data) else {
throw CorruptedImageDataException()
}
return image
}
if url.scheme == "ph" || url.scheme == "assets-library" {
return try await loadImageFromPhotoLibrary(url: url)
}
guard let imageLoader = appContext.imageLoader else {
throw ImageLoaderNotFoundException()
}
guard FileSystemUtilities.permissions(appContext, for: url).contains(.read) && FileManager.default.isReadableFile(atPath: url.path) else {
throw FileSystemReadPermissionException(url.absoluteString)
}
do {
if let result = try await imageLoader.loadImage(for: url) {
return result
}
} catch {
throw ImageLoadingFailedException((error as NSError).debugDescription)
}
// TODO: throw something better
throw ImageLoadingFailedException("")
}
/**
Loads the image from user's photo library.
*/
internal func loadImageFromPhotoLibrary(url: URL) async throws -> UIImage {
guard let asset = retrieveAsset(from: url) else {
throw ImageNotFoundException()
}
let size = CGSize(width: asset.pixelWidth, height: asset.pixelHeight)
let options = PHImageRequestOptions()
options.resizeMode = .exact
options.isNetworkAccessAllowed = true
options.isSynchronous = true
options.deliveryMode = .highQualityFormat
return try await withCheckedThrowingContinuation { continuation in
PHImageManager.default().requestImage(for: asset, targetSize: size, contentMode: .aspectFit, options: options) { image, _ in
if let image {
continuation.resume(returning: image)
} else {
continuation.resume(throwing: ImageNotFoundException())
}
}
}
}
/**
Returns pixel data representation of the image.
*/
func imageData(from image: UIImage, format: ImageFormat, compression: Double) -> Data? {
switch format {
case .jpeg, .jpg:
return image.jpegData(compressionQuality: compression)
case .png:
return image.pngData()
case .webp:
return SDImageWebPCoder.shared.encodedData(with: image, format: .webP, options: [.encodeCompressionQuality: compression])
}
}
/**
Checks if we are dealing with a ph asset URL and uses the correct method to fetch it.
*/
func retrieveAsset(from url: URL) -> PHAsset? {
if url.scheme == "ph" {
let identifier = String(url.absoluteString.dropFirst(5)) // removes ph://
return PHAsset.fetchAssets(withLocalIdentifiers: [identifier], options: nil).firstObject
}
return PHAsset.fetchAssets(withALAssetURLs: [url], options: nil).firstObject
}
/**
Helper function for drawing the image in graphics context.
Throws appropriate exceptions when the context is missing or the image couldn't be rendered.
*/
internal func drawInNewContext(size: CGSize, drawing: (UIGraphicsImageRendererContext) -> Void) -> UIImage {
let format = UIGraphicsImageRendererFormat()
format.scale = 1
let renderer = UIGraphicsImageRenderer(size: size, format: format)
return renderer.image { context in
drawing(context)
}
}
/**
Saves the image as a file.
*/
internal func saveImage(_ image: UIImage, options: ManipulateOptions, appContext: AppContext) throws -> SaveImageResult {
guard let cachesDirectory = appContext.config.cacheDirectory else {
throw FileSystemNotFoundException()
}
let directory = URL(fileURLWithPath: cachesDirectory.path).appendingPathComponent("ImageManipulator")
let filename = UUID().uuidString.appending(options.format.fileExtension)
let fileUrl = directory.appendingPathComponent(filename)
FileSystemUtilities.ensureDirExists(at: directory)
guard let data = imageData(from: image, format: options.format, compression: options.compress) else {
throw CorruptedImageDataException()
}
do {
try data.write(to: fileUrl, options: .atomic)
} catch let error {
throw ImageWriteFailedException(error.localizedDescription)
}
return (url: fileUrl, data: data)
}