Files
to-live-photo/Sources/LivePhotoCore/AIEnhancer/AIEnhancer.swift
empty 5aba93e967 feat: M2-M4 完成,添加 AI 增强、设计系统、App Store 准备
新增功能:
- AI 超分辨率模块 (Real-ESRGAN Core ML)
- Soft UI 设计系统 (DesignSystem.swift)
- 设置页、隐私政策页、引导页
- 最近作品管理器

App Store 准备:
- 完善截图 (iPhone 6.7"/6.5", iPad 12.9")
- App Store 元数据文档
- 修复应用图标 alpha 通道
- 更新显示名称为 Live Photo Studio

工程配置:
- 配置 Git LFS 跟踪 mlmodel 文件
- 添加 Claude skill 开发指南
- 更新 .gitignore 规则

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-16 10:24:31 +08:00

208 lines
5.9 KiB
Swift

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