129 lines
4.1 KiB
Swift
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)
|
|
}
|