Files
Gas-Tank-AR-App/GasTankAR/Views/TankAR/PhotoEncoder.swift
2026-09-02 20:54:36 +02:00

108 lines
4.0 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// 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
}
}
}