damus

nostr ios client
git clone git://jb55.com/damus
Log | Files | Refs | README | LICENSE

ImageProcessing.swift (5288B)


      1 //
      2 //  ImageProcessing.swift
      3 //  damus
      4 //
      5 //  Created by KernelKind on 2/27/24.
      6 //
      7 
      8 import UIKit
      9 
     10 /// Removes GPS data from image at url and writes changes to new file
     11 func processImage(url: URL) -> URL? {
     12     let fileExtension = url.pathExtension
     13     guard let imageData = try? Data(contentsOf: url) else {
     14         print("Failed to load image data from URL.")
     15         return nil
     16     }
     17     
     18     guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
     19     
     20     return processImage(source: source, fileExtension: fileExtension)
     21 }
     22 
     23 /// Removes GPS data from image and writes changes to new file
     24 func processImage(image: UIImage) -> URL? {
     25     let fixedImage = image.fixOrientation()
     26     guard let imageData = fixedImage.jpegData(compressionQuality: 1.0) else { return nil }
     27     guard let source = CGImageSourceCreateWithData(imageData as CFData, nil) else { return nil }
     28 
     29     return processImage(source: source, fileExtension: "jpeg")
     30 }
     31 
     32 fileprivate func processImage(source: CGImageSource, fileExtension: String) -> URL? {
     33     let destinationURL = generateUniqueTemporaryMediaURL(fileExtension: fileExtension)
     34     
     35     guard let destination = removeGPSDataFromImage(source: source, url: destinationURL) else { return nil }
     36     
     37     if !CGImageDestinationFinalize(destination) { return nil }
     38     
     39     return destinationURL
     40 }
     41 
     42 /// TODO: strip GPS data from video
     43 func processVideo(videoURL: URL) -> URL? {
     44     saveVideoToTemporaryFolder(videoURL: videoURL)
     45 }
     46 
     47 fileprivate func saveVideoToTemporaryFolder(videoURL: URL) -> URL? {
     48     let destinationURL = generateUniqueTemporaryMediaURL(fileExtension: videoURL.pathExtension)
     49     
     50     do {
     51         try FileManager.default.copyItem(at: videoURL, to: destinationURL)
     52         return destinationURL
     53     } catch {
     54         print("Error copying file: \(error.localizedDescription)")
     55         return nil
     56     }
     57 }
     58 
     59 /// Generate a temporary URL with a unique filename
     60 func generateUniqueTemporaryMediaURL(fileExtension: String) -> URL {
     61     let temporaryDirectoryURL = URL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)
     62     let uniqueMediaName = "\(UUID().uuidString).\(fileExtension)"
     63     let temporaryMediaURL = temporaryDirectoryURL.appendingPathComponent(uniqueMediaName)
     64 
     65     return temporaryMediaURL
     66 }
     67 
     68 /**
     69  Take the PreUploadedMedia payload, process it, if necessary, and convert it into a URL
     70  which is ready to be uploaded to the upload service.
     71  
     72  URLs containing media that hasn't been processed were generated from the system and were granted
     73  access as a security scoped resource. The data will need to be processed to strip GPS data
     74  and saved to a new location which isn't security scoped.
     75  */
     76 func generateMediaUpload(_ media: PreUploadedMedia?) -> MediaUpload? {
     77     guard let media else { return nil }
     78     
     79     switch media {
     80     case .uiimage(let image):
     81             guard let url = processImage(image: image) else { return nil }
     82             return .image(url)
     83     case .unprocessed_image(let url):
     84         guard let newUrl = processImage(url: url) else { return nil }
     85         url.stopAccessingSecurityScopedResource()
     86         return .image(newUrl)
     87     case .processed_image(let url):
     88         return .image(url)
     89     case .processed_video(let url):
     90         return .video(url)
     91     case .unprocessed_video(let url):
     92         guard let newUrl = processVideo(videoURL: url) else { return nil }
     93         url.stopAccessingSecurityScopedResource()
     94         return .video(newUrl)
     95     }
     96 }
     97 
     98 extension UIImage {
     99     func fixOrientation() -> UIImage {
    100         guard imageOrientation != .up else { return self }
    101         
    102         UIGraphicsBeginImageContextWithOptions(size, false, scale)
    103         draw(in: CGRect(origin: .zero, size: size))
    104         let normalizedImage = UIGraphicsGetImageFromCurrentImageContext()
    105         UIGraphicsEndImageContext()
    106         
    107         return normalizedImage ?? self
    108     }
    109 }
    110 
    111 func canGetSourceTypeFromUrl(url: URL) -> Bool {
    112     guard let source = CGImageSourceCreateWithURL(url as CFURL, nil) else {
    113         print("Failed to create image source.")
    114         return false
    115     }
    116     return CGImageSourceGetType(source) != nil
    117 }
    118 
    119 func removeGPSDataFromImageAndWrite(fromImageURL imageURL: URL) -> Bool {
    120     guard let source = CGImageSourceCreateWithURL(imageURL as CFURL, nil) else {
    121         print("Failed to create image source.")
    122         return false
    123     }
    124 
    125     guard let destination = removeGPSDataFromImage(source: source, url: imageURL) else { return false }
    126     
    127     return CGImageDestinationFinalize(destination)
    128 }
    129 
    130 fileprivate func removeGPSDataFromImage(source: CGImageSource, url: URL) -> CGImageDestination? {
    131     let totalCount = CGImageSourceGetCount(source)
    132 
    133     guard totalCount > 0 else {
    134         print("No images found.")
    135         return nil
    136     }
    137 
    138     guard let type = CGImageSourceGetType(source),
    139           let destination = CGImageDestinationCreateWithURL(url as CFURL, type, totalCount, nil) else {
    140         print("Failed to create image destination.")
    141         return nil
    142     }
    143     
    144     let removeGPSProperties: CFDictionary = [kCGImageMetadataShouldExcludeGPS: kCFBooleanTrue] as CFDictionary
    145     
    146     for i in 0..<totalCount {
    147         CGImageDestinationAddImageFromSource(destination, source, i, removeGPSProperties)
    148     }
    149     
    150     return destination
    151 }