108 lines
4.0 KiB
Swift
108 lines
4.0 KiB
Swift
//
|
||
// 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
|
||
}
|
||
}
|
||
}
|