diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 96cb64a..56de916 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -4472,6 +4472,7 @@ dependencies = [ "objc2-app-kit", "objc2-core-foundation", "objc2-core-graphics", + "objc2-core-image", "objc2-core-ml", "objc2-foundation", "objc2-image-io", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 12c9fbf..e99a649 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -38,6 +38,7 @@ core-foundation = "0.10" security-framework = "3" objc2 = "0.6" objc2-core-foundation = "0.3" +objc2-core-image = { version = "0.3", features = ["CIContext"] } objc2-core-ml = { version = "0.3", features = ["MLCPUComputeDevice", "MLComputeDeviceProtocol"] } objc2-image-io = { version = "0.3", features = ["CGImageSource"] } objc2-app-kit = { version = "0.3", features = [ @@ -60,6 +61,7 @@ objc2-foundation = { version = "0.3", features = [ "NSDictionary", "NSError", "NSObject", + "NSValue", ] } objc2-vision = { version = "0.3", features = [ "objc2-core-graphics", diff --git a/src-tauri/src/ocr/tests/diagnose-vision.swift b/src-tauri/src/ocr/tests/diagnose-vision.swift index 1ed2048..cf6e5db 100644 --- a/src-tauri/src/ocr/tests/diagnose-vision.swift +++ b/src-tauri/src/ocr/tests/diagnose-vision.swift @@ -3,6 +3,7 @@ import Foundation import ImageIO import Vision import CoreML +import CoreImage let file = URL(fileURLWithPath: CommandLine.arguments[1]) let data = try Data(contentsOf: file) @@ -51,15 +52,20 @@ let profiles: [(String, (VNRecognizeTextRequest) throws -> Void)] = [ }), ] for (name, configure) in profiles { + for software in [false, true] { do { let request = VNRecognizeTextRequest() try configure(request) - let handler = VNImageRequestHandler(cgImage: image, options: [:]) + let options: [VNImageOption: Any] = software + ? [.ciContext: CIContext(options: [.useSoftwareRenderer: true])] + : [:] + let handler = VNImageRequestHandler(cgImage: image, options: options) try handler.perform([request]) let text = request.results?.compactMap { $0.topCandidates(1).first?.string }.joined(separator: "\n") ?? "" - print("\(name): OK \(text)") + print("\(name) software=\(software): OK \(text)") } catch { let error = error as NSError - print("\(name): ERROR domain=\(error.domain) code=\(error.code) details=\(error.userInfo)") + print("\(name) software=\(software): ERROR domain=\(error.domain) code=\(error.code) details=\(error.userInfo)") + } } } diff --git a/src-tauri/src/ocr/vision.rs b/src-tauri/src/ocr/vision.rs index 7809938..1c63224 100644 --- a/src-tauri/src/ocr/vision.rs +++ b/src-tauri/src/ocr/vision.rs @@ -6,11 +6,15 @@ #![cfg(target_os = "macos")] use objc2::rc::{autoreleasepool, Retained}; -use objc2::{AnyThread, ClassType}; +use objc2::{AnyObject, AnyThread, ClassType}; use objc2_core_graphics::CGImage; +use objc2_core_image::{kCIContextUseSoftwareRenderer, CIContext}; use objc2_core_ml::MLCPUComputeDevice; -use objc2_foundation::{NSArray, NSObjectProtocol, NSString}; -use objc2_vision::{VNImageRequestHandler, VNRecognizeTextRequest, VNRequestTextRecognitionLevel}; +use objc2_foundation::{NSArray, NSDictionary, NSNumber, NSObjectProtocol, NSString}; +use objc2_vision::{ + VNImageOptionCIContext, VNImageRequestHandler, VNRecognizeTextRequest, + VNRequestTextRecognitionLevel, +}; /// 对指定图片文件路径执行 OCR,返回识别文本(按行拼接)。 pub fn recognize_file(path: &str) -> Result { @@ -46,11 +50,25 @@ pub(super) fn recognize_once(image: &CGImage, compatibility: bool) -> Result = VNRecognizeTextRequest::new(); configure_request(&request, compatibility)?; + let options = if compatibility || objc2::available!(macos = 27.0) { + let software = NSNumber::numberWithBool(true); + let values: [&AnyObject; 1] = [&software]; + let context_options = + NSDictionary::from_slices(&[kCIContextUseSoftwareRenderer], &values); + let context = CIContext::contextWithOptions(Some(&context_options)); + let values: [&AnyObject; 1] = [&context]; + // CPU 模型选择不控制图像缩放器。显式软件 CIContext 避开 + // 新系统后台/虚拟环境中不可用的硬件图像处理路径。 + NSDictionary::from_slices(&[VNImageOptionCIContext], &values) + } else { + NSDictionary::new() + }; + // 直接提供已规范化的 CGImage,避免 Vision 再走压缩数据解码路径。 let handler = VNImageRequestHandler::initWithCGImage_options( VNImageRequestHandler::alloc(), image, - &objc2_foundation::NSDictionary::new(), + &options, ); let requests = NSArray::from_retained_slice(&[Retained::into_super(Retained::into_super(