// // AIEnhancer.swift // LivePhotoCore // // AI super-resolution enhancement using Real-ESRGAN Core ML model. // import CoreGraphics import CoreML import Foundation import os // MARK: - Configuration /// AI enhancement configuration public struct AIEnhanceConfig: Codable, Sendable, Hashable { /// Enable AI super-resolution public var enabled: Bool public init(enabled: Bool = false) { self.enabled = enabled } /// Disabled configuration public static let disabled = AIEnhanceConfig(enabled: false) /// Standard configuration public static let standard = AIEnhanceConfig(enabled: true) } // MARK: - Result /// AI enhancement result public struct AIEnhanceResult: Sendable { /// Enhanced image public let enhancedImage: CGImage /// Original image size public let originalSize: CGSize /// Enhanced image size public let enhancedSize: CGSize /// Processing time in milliseconds public let processingTimeMs: Double } // MARK: - Errors /// AI enhancement error types public enum AIEnhanceError: Error, Sendable, LocalizedError { case modelNotFound case modelLoadFailed(String) case inputImageInvalid case inferenceError(String) case memoryPressure case cancelled case deviceNotSupported public var errorDescription: String? { switch self { case .modelNotFound: return "AI model file not found in bundle" case let .modelLoadFailed(reason): return "Failed to load AI model: \(reason)" case .inputImageInvalid: return "Input image is invalid or cannot be processed" case let .inferenceError(reason): return "AI inference failed: \(reason)" case .memoryPressure: return "Not enough memory for AI processing" case .cancelled: return "AI enhancement was cancelled" case .deviceNotSupported: return "Device does not support AI enhancement" } } } // MARK: - Progress /// Progress callback for AI enhancement /// - Parameter progress: Value from 0.0 to 1.0 public typealias AIEnhanceProgress = @Sendable (Double) -> Void // MARK: - Main Actor /// AI enhancement actor for super-resolution processing public actor AIEnhancer { private let config: AIEnhanceConfig private var processor: RealESRGANProcessor? private let logger = Logger(subsystem: "LivePhotoCore", category: "AIEnhancer") /// Scale factor (4 for Real-ESRGAN x4plus) public static let scaleFactor: Int = 4 /// Initialize with configuration public init(config: AIEnhanceConfig = .standard) { self.config = config } // MARK: - Device Capability /// Check if AI enhancement is available on this device public static func isAvailable() -> Bool { // Require iOS 17+ guard #available(iOS 17.0, *) else { return false } // Check device memory (require at least 4GB) let totalMemory = ProcessInfo.processInfo.physicalMemory let memoryGB = Double(totalMemory) / (1024 * 1024 * 1024) guard memoryGB >= 4.0 else { return false } // Neural Engine is available on A12+ (iPhone XS and later) // iOS 17 requirement ensures A12+ is present return true } // MARK: - Model Management /// Preload the model (call during app launch or settings change) public func preloadModel() async throws { guard AIEnhancer.isAvailable() else { throw AIEnhanceError.deviceNotSupported } guard processor == nil else { logger.debug("Model already loaded") return } logger.info("Preloading Real-ESRGAN model...") processor = RealESRGANProcessor() try await processor?.loadModel() logger.info("Model preloaded successfully") } /// Release model from memory public func unloadModel() async { await processor?.unloadModel() processor = nil logger.info("Model unloaded") } // MARK: - Enhancement /// Enhance a single image with AI super-resolution /// - Parameters: /// - image: Input CGImage to enhance /// - progress: Optional progress callback (0.0 to 1.0) /// - Returns: Enhanced result with metadata public func enhance( image: CGImage, progress: AIEnhanceProgress? = nil ) async throws -> AIEnhanceResult { guard config.enabled else { throw AIEnhanceError.inputImageInvalid } guard AIEnhancer.isAvailable() else { throw AIEnhanceError.deviceNotSupported } let startTime = CFAbsoluteTimeGetCurrent() let originalSize = CGSize(width: image.width, height: image.height) logger.info("Starting AI enhancement: \(image.width)x\(image.height)") // Ensure model is loaded if processor == nil { try await preloadModel() } guard let processor = processor else { throw AIEnhanceError.modelNotFound } // Process image (no tiling - model has fixed 1280x1280 input) let wholeImageProcessor = WholeImageProcessor() let enhancedImage = try await wholeImageProcessor.processImage( image, processor: processor, progress: progress ) let processingTime = (CFAbsoluteTimeGetCurrent() - startTime) * 1000 let enhancedSize = CGSize(width: enhancedImage.width, height: enhancedImage.height) logger.info( "AI enhancement complete: \(Int(originalSize.width))x\(Int(originalSize.height)) -> \(Int(enhancedSize.width))x\(Int(enhancedSize.height)) in \(Int(processingTime))ms" ) return AIEnhanceResult( enhancedImage: enhancedImage, originalSize: originalSize, enhancedSize: enhancedSize, processingTimeMs: processingTime ) } }