// // ImageFormatConverter.swift // LivePhotoCore // // Utilities for converting between CGImage and CVPixelBuffer formats. // import Accelerate import CoreGraphics import CoreVideo import Foundation import VideoToolbox /// Utilities for image format conversion enum ImageFormatConverter { /// Convert CGImage to CVPixelBuffer for Core ML input /// - Parameters: /// - image: Input CGImage /// - pixelFormat: Output pixel format (default BGRA) /// - Returns: CVPixelBuffer ready for Core ML static func cgImageToPixelBuffer( _ image: CGImage, pixelFormat: OSType = kCVPixelFormatType_32BGRA ) throws -> CVPixelBuffer { let width = image.width let height = image.height // Create pixel buffer var pixelBuffer: CVPixelBuffer? let attrs: [CFString: Any] = [ kCVPixelBufferCGImageCompatibilityKey: true, kCVPixelBufferCGBitmapContextCompatibilityKey: true, kCVPixelBufferMetalCompatibilityKey: true, ] let status = CVPixelBufferCreate( kCFAllocatorDefault, width, height, pixelFormat, attrs as CFDictionary, &pixelBuffer ) guard status == kCVReturnSuccess, let buffer = pixelBuffer else { throw AIEnhanceError.inputImageInvalid } // Lock buffer for writing CVPixelBufferLockBaseAddress(buffer, []) defer { CVPixelBufferUnlockBaseAddress(buffer, []) } guard let baseAddress = CVPixelBufferGetBaseAddress(buffer) else { throw AIEnhanceError.inputImageInvalid } let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer) let colorSpace = CGColorSpaceCreateDeviceRGB() // Create bitmap context to draw into pixel buffer guard let context = CGContext( data: baseAddress, width: width, height: height, bitsPerComponent: 8, bytesPerRow: bytesPerRow, space: colorSpace, bitmapInfo: CGImageAlphaInfo.premultipliedFirst.rawValue | CGBitmapInfo.byteOrder32Little.rawValue ) else { throw AIEnhanceError.inputImageInvalid } // Draw image into context (this converts the format) context.draw(image, in: CGRect(x: 0, y: 0, width: width, height: height)) return buffer } /// Convert CVPixelBuffer to CGImage /// - Parameter pixelBuffer: Input pixel buffer /// - Returns: CGImage representation static func pixelBufferToCGImage(_ pixelBuffer: CVPixelBuffer) throws -> CGImage { var cgImage: CGImage? VTCreateCGImageFromCVPixelBuffer(pixelBuffer, options: nil, imageOut: &cgImage) guard let image = cgImage else { throw AIEnhanceError.inferenceError("Failed to create CGImage from pixel buffer") } return image } /// Extract raw RGBA pixel data from CVPixelBuffer /// - Parameter pixelBuffer: Input pixel buffer /// - Returns: Array of RGBA bytes static func pixelBufferToRGBAData(_ pixelBuffer: CVPixelBuffer) throws -> [UInt8] { CVPixelBufferLockBaseAddress(pixelBuffer, .readOnly) defer { CVPixelBufferUnlockBaseAddress(pixelBuffer, .readOnly) } let width = CVPixelBufferGetWidth(pixelBuffer) let height = CVPixelBufferGetHeight(pixelBuffer) let bytesPerRow = CVPixelBufferGetBytesPerRow(pixelBuffer) guard let baseAddress = CVPixelBufferGetBaseAddress(pixelBuffer) else { throw AIEnhanceError.inferenceError("Cannot access pixel buffer data") } let pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer) // Handle BGRA format (most common from Core ML) if pixelFormat == kCVPixelFormatType_32BGRA { return convertBGRAToRGBA( baseAddress: baseAddress, width: width, height: height, bytesPerRow: bytesPerRow ) } // Handle RGBA format if pixelFormat == kCVPixelFormatType_32RGBA { var result = [UInt8](repeating: 0, count: width * height * 4) for y in 0.. CVPixelBuffer { var pixelBuffer: CVPixelBuffer? let attrs: [CFString: Any] = [ kCVPixelBufferCGImageCompatibilityKey: true, kCVPixelBufferCGBitmapContextCompatibilityKey: true, ] let status = CVPixelBufferCreate( kCFAllocatorDefault, width, height, kCVPixelFormatType_32BGRA, attrs as CFDictionary, &pixelBuffer ) guard status == kCVReturnSuccess, let buffer = pixelBuffer else { throw AIEnhanceError.inputImageInvalid } CVPixelBufferLockBaseAddress(buffer, []) defer { CVPixelBufferUnlockBaseAddress(buffer, []) } guard let baseAddress = CVPixelBufferGetBaseAddress(buffer) else { throw AIEnhanceError.inputImageInvalid } let bytesPerRow = CVPixelBufferGetBytesPerRow(buffer) // Convert RGBA to BGRA while copying for y in 0.. BGRA swap dstRow[dstIdx + 0] = rgbaData[srcIdx + 2] // B dstRow[dstIdx + 1] = rgbaData[srcIdx + 1] // G dstRow[dstIdx + 2] = rgbaData[srcIdx + 0] // R dstRow[dstIdx + 3] = rgbaData[srcIdx + 3] // A } } return buffer } // MARK: - Private Helpers private static func convertBGRAToRGBA( baseAddress: UnsafeMutableRawPointer, width: Int, height: Int, bytesPerRow: Int ) -> [UInt8] { var result = [UInt8](repeating: 0, count: width * height * 4) let dstBytesPerRow = width * 4 var srcBuffer = vImage_Buffer( data: baseAddress, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow ) result.withUnsafeMutableBufferPointer { dstPtr in var dstBuffer = vImage_Buffer( data: dstPtr.baseAddress!, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: dstBytesPerRow ) // BGRA → RGBA: 通道重排 [2,1,0,3] let permuteMap: [UInt8] = [2, 1, 0, 3] vImagePermuteChannels_ARGB8888(&srcBuffer, &dstBuffer, permuteMap, vImage_Flags(kvImageNoFlags)) } return result } private static func convertARGBToRGBA( baseAddress: UnsafeMutableRawPointer, width: Int, height: Int, bytesPerRow: Int ) -> [UInt8] { var result = [UInt8](repeating: 0, count: width * height * 4) let dstBytesPerRow = width * 4 var srcBuffer = vImage_Buffer( data: baseAddress, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: bytesPerRow ) result.withUnsafeMutableBufferPointer { dstPtr in var dstBuffer = vImage_Buffer( data: dstPtr.baseAddress!, height: vImagePixelCount(height), width: vImagePixelCount(width), rowBytes: dstBytesPerRow ) // ARGB → RGBA: 通道重排 [1,2,3,0] let permuteMap: [UInt8] = [1, 2, 3, 0] vImagePermuteChannels_ARGB8888(&srcBuffer, &dstBuffer, permuteMap, vImage_Flags(kvImageNoFlags)) } return result } }