// // PhotoEncoder.swift // GasTankAR // // Created by Dennis Nemec on 28.08.26. // import ImageIO import UniformTypeIdentifiers import CoreLocation import UIKit /// Kodiert einen Snapshot als JPEG und bettet dabei EXIF-Aufnahmezeit sowie – falls vorhanden – /// GPS-Metadaten direkt in die Bilddatei ein (ImageIO). So tragen auch exportierte Bilder ihren /// Aufnahmeort mit sich. enum PhotoEncoder { /// Erzeugt JPEG-Daten mit eingebetteten Metadaten. Fällt bei Fehlern auf `UIImage.jpegData` zurück. static func jpegData( from image: UIImage, coordinate: CLLocationCoordinate2D?, date: Date, compressionQuality: CGFloat = 0.9 ) -> Data? { guard let cgImage = image.cgImage else { return image.jpegData(compressionQuality: compressionQuality) } let output = NSMutableData() guard let destination = CGImageDestinationCreateWithData( output, UTType.jpeg.identifier as CFString, 1, nil ) else { return image.jpegData(compressionQuality: compressionQuality) } var properties: [CFString: Any] = [ kCGImageDestinationLossyCompressionQuality: compressionQuality, kCGImagePropertyOrientation: CGImagePropertyOrientation(image.imageOrientation).rawValue, kCGImagePropertyExifDictionary: [ kCGImagePropertyExifDateTimeOriginal: Self.exifDateFormatter.string(from: date), kCGImagePropertyExifDateTimeDigitized: Self.exifDateFormatter.string(from: date), ], ] if let coordinate { properties[kCGImagePropertyGPSDictionary] = gpsProperties(for: coordinate, date: date) } CGImageDestinationAddImage(destination, cgImage, properties as CFDictionary) guard CGImageDestinationFinalize(destination) else { return image.jpegData(compressionQuality: compressionQuality) } return output as Data } private static func gpsProperties(for coordinate: CLLocationCoordinate2D, date: Date) -> [CFString: Any] { [ kCGImagePropertyGPSLatitude: abs(coordinate.latitude), kCGImagePropertyGPSLatitudeRef: coordinate.latitude >= 0 ? "N" : "S", kCGImagePropertyGPSLongitude: abs(coordinate.longitude), kCGImagePropertyGPSLongitudeRef: coordinate.longitude >= 0 ? "E" : "W", kCGImagePropertyGPSDateStamp: Self.gpsDateFormatter.string(from: date), kCGImagePropertyGPSTimeStamp: Self.gpsTimeFormatter.string(from: date), ] } /// EXIF-Zeitstempel in lokaler Zeit („yyyy:MM:dd HH:mm:ss"). private static let exifDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.dateFormat = "yyyy:MM:dd HH:mm:ss" return formatter }() /// GPS-Datum in UTC („yyyy:MM:dd"). private static let gpsDateFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(identifier: "UTC") formatter.dateFormat = "yyyy:MM:dd" return formatter }() /// GPS-Uhrzeit in UTC („HH:mm:ss"). private static let gpsTimeFormatter: DateFormatter = { let formatter = DateFormatter() formatter.locale = Locale(identifier: "en_US_POSIX") formatter.timeZone = TimeZone(identifier: "UTC") formatter.dateFormat = "HH:mm:ss" return formatter }() } private extension CGImagePropertyOrientation { init(_ orientation: UIImage.Orientation) { switch orientation { case .up: self = .up case .down: self = .down case .left: self = .left case .right: self = .right case .upMirrored: self = .upMirrored case .downMirrored: self = .downMirrored case .leftMirrored: self = .leftMirrored case .rightMirrored: self = .rightMirrored @unknown default: self = .up } } }