This snapshot establishes the camera-to-result recognition flow and related tests while checking in the project skill/docs assets required for the configured local tooling.
100 lines
3.2 KiB
Swift
100 lines
3.2 KiB
Swift
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
|
|
}
|
|
}
|