import CoreImage import UIKit struct PreparedImage { let normalizedImage: UIImage let analysisCGImage: CGImage let uploadJPEGData: Data let thumbnailJPEGData: Data? } enum ImagePreprocessorError: LocalizedError { case unableToCreateImage var errorDescription: String? { switch self { case .unableToCreateImage: return "The selected image could not be prepared for OCR." } } } struct ImagePreprocessor { private let ciContext = CIContext() func prepare(_ image: UIImage) throws -> PreparedImage { let upright = normalized(image) let resized = resizedImage(from: upright, maxDimension: 2_048) let enhanced = enhancedImage(from: resized) ?? resized guard let cgImage = makeCGImage(from: enhanced) else { throw ImagePreprocessorError.unableToCreateImage } return PreparedImage( normalizedImage: enhanced, analysisCGImage: cgImage, uploadJPEGData: enhanced.jpegData(compressionQuality: 0.82) ?? Data(), thumbnailJPEGData: resizedImage(from: enhanced, maxDimension: 240).jpegData(compressionQuality: 0.65) ) } private func normalized(_ image: UIImage) -> UIImage { guard image.imageOrientation != .up else { return image } let renderer = UIGraphicsImageRenderer(size: image.size) return renderer.image { _ in image.draw(in: CGRect(origin: .zero, size: image.size)) } } private func resizedImage(from image: UIImage, maxDimension: CGFloat) -> UIImage { let largestDimension = max(image.size.width, image.size.height) guard largestDimension > maxDimension else { return image } let scale = maxDimension / largestDimension let targetSize = CGSize(width: image.size.width * scale, height: image.size.height * scale) let renderer = UIGraphicsImageRenderer(size: targetSize) return renderer.image { _ in image.draw(in: CGRect(origin: .zero, size: targetSize)) } } private func enhancedImage(from image: UIImage) -> UIImage? { guard let ciImage = CIImage(image: image) else { return nil } let adjusted = ciImage .applyingFilter("CIColorControls", parameters: [ kCIInputContrastKey: 1.08, kCIInputSaturationKey: 0.96, kCIInputBrightnessKey: 0.01, ]) .applyingFilter("CISharpenLuminance", parameters: [ kCIInputSharpnessKey: 0.35, ]) guard let cgImage = ciContext.createCGImage(adjusted, from: adjusted.extent) else { return nil } return UIImage(cgImage: cgImage) } private func makeCGImage(from image: UIImage) -> CGImage? { if let cgImage = image.cgImage { return cgImage } if let ciImage = image.ciImage { return ciContext.createCGImage(ciImage, from: ciImage.extent) } let renderer = UIGraphicsImageRenderer(size: image.size) let rendered = renderer.image { _ in image.draw(in: CGRect(origin: .zero, size: image.size)) } return rendered.cgImage } }