Initial commit for first version of project
This commit is contained in:
107
GasTankAR/Views/TankAR/PhotoEncoder.swift
Normal file
107
GasTankAR/Views/TankAR/PhotoEncoder.swift
Normal file
@ -0,0 +1,107 @@
|
||||
//
|
||||
// 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
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user