Browse Source

【2025】【OCR】内存不释放问题修复

lizhe 1 month ago
parent
commit
43f575ee51

+ 1 - 1
PDF Office/PDF Master/Class/OC/OCR/KMGOCRManager.h

@@ -42,7 +42,7 @@ extern NSString * KMGOCRLanguageStringKey;
 
 @interface KMGOCRManager : NSObject
 
-@property (nonatomic,assign) id<KMGOCRManagerDelegate> delegate;
+@property (nonatomic, weak) id<KMGOCRManagerDelegate> delegate;
 
 @property (nonatomic,readonly) NSMutableArray *images;
 

+ 32 - 23
PDF Office/PDF Master/Class/OC/OCR/KMGOCRManager.m

@@ -315,6 +315,7 @@ static inline NSFont * FontWithSize(NSString *strChar, CGSize size) {
         NSData *imageData = [NSData dataWithContentsOfFile:path];
         CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
         CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
+        CFRelease(imageSource); // 添加这行
         CGContextSaveGState(pdfContext);
         CGContextDrawImage(pdfContext, pageRect, imageRef);
         CGContextRestoreGState(pdfContext);
@@ -407,6 +408,7 @@ static inline NSFont * FontWithSize(NSString *strChar, CGSize size) {
         NSData *imageData = image.TIFFRepresentation;
         CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
         CGImageRef imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
+        CFRelease(imageSource); // 添加这行
         CGContextSaveGState(pdfContext);
         CGContextDrawImage(pdfContext, pageRect, imageRef);
         CGContextRestoreGState(pdfContext);
@@ -554,11 +556,16 @@ static inline NSFont * FontWithSize(NSString *strChar, CGSize size) {
 
 - (void)recognitionAppleImage:(NSImage *)image {
     dispatch_async(dispatch_get_global_queue(0, 0), ^{
-    __block typeof(self) blockSelf = self;
-        _appleRequest = [[VNRecognizeTextRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
+        if(self->_appleRequest) {
+            [self->_appleRequest cancel];
+            self->_appleRequest = nil;
+        }
+        __weak typeof(self) weakSelf = self;
+        self->_appleRequest = [[VNRecognizeTextRequest alloc] initWithCompletionHandler:^(VNRequest * _Nonnull request, NSError * _Nullable error) {
+            __strong typeof(weakSelf) strongSelf = weakSelf;
             NSArray *results = nil;
             if (request.results.count > 0) {
-                results = [blockSelf responseDataRequest:request dictionary:nil imageSize:image.size];
+                results = [strongSelf responseDataRequest:request dictionary:nil imageSize:image.size];
             }
 
             NSMutableArray *resultArray = [[NSMutableArray alloc] init];
@@ -574,21 +581,21 @@ static inline NSFont * FontWithSize(NSString *strChar, CGSize size) {
             
             dispatch_async(dispatch_get_main_queue(), ^{
                 if (error || !results) {
-                    if (self.delegate && [blockSelf.delegate respondsToSelector:@selector(GOCRManager:didFailureOCRImageAtIndex:error:)]) {
-                        [blockSelf.delegate GOCRManager:blockSelf didFailureOCRImageAtIndex:blockSelf.finishIndex error:error];
+                    if (strongSelf.delegate && [strongSelf.delegate respondsToSelector:@selector(GOCRManager:didFailureOCRImageAtIndex:error:)]) {
+                        [strongSelf.delegate GOCRManager:strongSelf didFailureOCRImageAtIndex:strongSelf.finishIndex error:error];
                     }
                 } else {
-                    if (self.delegate && [blockSelf.delegate respondsToSelector:@selector(GOCRManager:didFinishOCRImageAtIndex:results:)]) {
-                        [blockSelf.delegate GOCRManager:blockSelf didFinishOCRImageAtIndex:blockSelf.finishIndex results:results];
+                    if (strongSelf.delegate && [strongSelf.delegate respondsToSelector:@selector(GOCRManager:didFinishOCRImageAtIndex:results:)]) {
+                        [strongSelf.delegate GOCRManager:strongSelf didFinishOCRImageAtIndex:strongSelf.finishIndex results:results];
                     }
                 }
-                [blockSelf recognitionAppleImageAtIndex:blockSelf.finishIndex+1];
+                [strongSelf recognitionAppleImageAtIndex:strongSelf.finishIndex+1];
             });
         }];
         self->_appleRequest.usesCPUOnly = YES;
         self->_appleRequest.recognitionLevel = self.appleRecognitionMode;
         if (self.languages.count > 0) {
-            NSMutableArray *array = [[NSMutableArray alloc] initWithArray:blockSelf.languages];
+            NSMutableArray *array = [[NSMutableArray alloc] initWithArray:self.languages];
             if ([self.languages containsObject:@"zh-Hant"]) {
                 [array removeObject:@"zh-Hant"];
                 [array insertObject:@"zh-Hant" atIndex:0];
@@ -603,10 +610,12 @@ static inline NSFont * FontWithSize(NSString *strChar, CGSize size) {
             self->_appleRequest.recognitionLanguages = @[@"zh-Hans",@"zh-Hant"];
         }
         NSError *error = nil;
-        VNImageRequestHandler *handle = [[VNImageRequestHandler alloc] initWithCGImage:[self nsImageToCGImageRef:image] options:@{}];
+        CGImageRef imageRef = [self nsImageToCGImageRef:image];
+        VNImageRequestHandler *handle = [[VNImageRequestHandler alloc] initWithCGImage:imageRef options:@{}];
         if (self->_appleRequest){
             [handle performRequests:@[self->_appleRequest] error:&error];
         }
+        CGImageRelease(imageRef);
     });
 }
 
@@ -840,19 +849,19 @@ static inline NSFont * FontWithSize(NSString *strChar, CGSize size) {
     return results;
 }
 
-- (CGImageRef)nsImageToCGImageRef:(NSImage*)image;
-{
-    NSData * imageData = [image TIFFRepresentation];
-    CGImageRef imageRef;
-    if(imageData)
-    {
-        CGImageSourceRef imageSource =
-                  CGImageSourceCreateWithData(
-                            (CFDataRef)imageData,  NULL);
-        imageRef = CGImageSourceCreateImageAtIndex(
-                               imageSource, 0, NULL);
-    }
-    return imageRef;
+- (CGImageRef)nsImageToCGImageRef:(NSImage*)image {
+    NSData *imageData = [image TIFFRepresentation];
+    CGImageRef imageRef = NULL;
+    
+    if (imageData) {
+        CGImageSourceRef imageSource = CGImageSourceCreateWithData((CFDataRef)imageData, NULL);
+        if (imageSource) {
+            imageRef = CGImageSourceCreateImageAtIndex(imageSource, 0, NULL);
+            CFRelease(imageSource); // 释放 imageSource
+        }
+    }
+    
+    return imageRef; // 调用者负责释放
 }
 
 @end

+ 79 - 21
PDF Office/PDF Master/Class/PDFTools/OCRNew/Model/KMGOCROperation.swift

@@ -170,41 +170,99 @@ let KMGOC_API_KEY = "AIzaSyCJuqJ9YvtkFKMl1mW3Yq-av3mmI9ScbRY"
         }
         task?.resume()
     }
+    
     func base64EncodeImage(_ image: NSImage) -> String? {
+        // 获取 TIFF 数据
         guard let data = image.tiffRepresentation else { return nil }
-        let imageRep = NSBitmapImageRep(data: data)!
+        
+        // 创建位图表示
+        guard let imageRep = NSBitmapImageRep(data: data) else { return nil }
         imageRep.size = image.size
-        let imageData = imageRep.representation(using: .png, properties: [:])
-        // Resize the image if it exceeds the 4MB API limit
-        if imageData?.count ?? 0 > 4194304 {
-            let compressedData = compressImageData(imageData!, toMaxFileSize: 4194304)
-            if let data = compressedData {
-                return data.base64EncodedString(options: .endLineWithCarriageReturn)
-            }
-        }
         
-        if let data = imageData {
-            if #available(macOS 10.9, *) {
-                return data.base64EncodedString(options: .endLineWithCarriageReturn)
-            } else {
-                return data.base64EncodedString(options: [])
+        // 转换为 PNG 数据
+        guard let imageData = imageRep.representation(using: .png, properties: [:]) else { return nil }
+        
+        // 如果数据大小超过限制,尝试压缩
+        let finalData: Data
+        if imageData.count > 4194304 {
+            guard let compressedData = compressImageData(imageData, toMaxFileSize: 4194304) else {
+                return nil // 压缩失败时返回 nil
             }
+            finalData = compressedData
+        } else {
+            finalData = imageData
         }
         
-        return nil
+        // 返回 Base64 编码字符串
+        return finalData.base64EncodedString(options: .lineLength64Characters)
     }
+
     func compressImageData(_ imageData: Data, toMaxFileSize maxFileSize: Int) -> Data? {
+        // 初始压缩比
         var compression: CGFloat = 0.9
         let maxCompression: CGFloat = 0.1
-        var compressImageData = imageData
-        while compressImageData.count > maxFileSize && compression > maxCompression {
+        
+        // 循环压缩
+        var currentData = imageData
+        while currentData.count > maxFileSize && compression > maxCompression {
             compression -= 0.1
-            let imageRep = NSBitmapImageRep(data: compressImageData)!
-            compressImageData = imageRep.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: NSNumber(value: Float(compression))])!
+            
+            // 创建位图表示
+            guard let imageRep = NSBitmapImageRep(data: currentData) else {
+                return nil // 解码失败时返回 nil
+            }
+            
+            // 尝试压缩为 JPEG 数据
+            guard let compressedData = imageRep.representation(
+                using: .jpeg,
+                properties: [.compressionFactor: NSNumber(value: Float(compression))]
+            ) else {
+                return nil // 压缩失败时返回 nil
+            }
+            
+            currentData = compressedData
         }
-        return compressImageData
+        
+        return currentData.count <= maxFileSize ? currentData : nil
     }
-    func responseDataResults(_ dictionary: NSDictionary) -> [Any]? { 
+//
+//    func base64EncodeImage(_ image: NSImage) -> String? {
+//        guard let data = image.tiffRepresentation else { return nil }
+//        let imageRep = NSBitmapImageRep(data: data)!
+//        imageRep.size = image.size
+//        let imageData = imageRep.representation(using: .png, properties: [:])
+//        // Resize the image if it exceeds the 4MB API limit
+//        if imageData?.count ?? 0 > 4194304 {
+//            let compressedData = compressImageData(imageData!, toMaxFileSize: 4194304)
+//            if let data = compressedData {
+//                return data.base64EncodedString(options: .endLineWithCarriageReturn)
+//            }
+//        }
+//        
+//        if let data = imageData {
+//            if #available(macOS 10.9, *) {
+//                return data.base64EncodedString(options: .endLineWithCarriageReturn)
+//            } else {
+//                return data.base64EncodedString(options: [])
+//            }
+//        }
+//        
+//        return nil
+//    }
+//    
+//    func compressImageData(_ imageData: Data, toMaxFileSize maxFileSize: Int) -> Data? {
+//        var compression: CGFloat = 0.9
+//        let maxCompression: CGFloat = 0.1
+//        var compressImageData = imageData
+//        while compressImageData.count > maxFileSize && compression > maxCompression {
+//            compression -= 0.1
+//            let imageRep = NSBitmapImageRep(data: compressImageData)!
+//            compressImageData = imageRep.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: NSNumber(value: Float(compression))])!
+//        }
+//        return compressImageData
+//    }
+    
+    func responseDataResults(_ dictionary: NSDictionary) -> [Any]? {
         let responses = dictionary["responses"] as? [Any]
         if responses == nil {
             return nil

+ 10 - 24
PDF Office/PDF Master/KMClass/KMPDFViewController/RightSideController/Views/OCR/Tool/Manager/KMOCRManager.swift

@@ -30,7 +30,7 @@ class KMOCRManager: NSObject {
     //Tool OCR
     private var OCRManger: KMGOCRManager?
     private var ocrDictionary:[NSNumber: Any] = [:]
-    private var pageImages: [NSImage] = []
+//    private var pageImages: [NSImage] = []
     
     private var OCRComplete: KMOCRManagerOCRComplete?
     private var progress: KMOCRManagerOCRProgress?
@@ -146,7 +146,7 @@ extension KMOCRManager: KMGOCRManagerDelegate {
             }
         }
         
-        self.pageImages = selctPageImages
+//        self.pageImages = selctPageImages
         
         if (selctPageImages.count == 0) {
             fail()
@@ -200,8 +200,6 @@ extension KMOCRManager: KMGOCRManagerDelegate {
     
     //MARK: - KMGOCRManagerDelegate
     func gocrManagerDidFinishOCR(_ manager: KMGOCRManager!) {
-//        self.batchesOCR()
-//        KMGOCRManager.default().createPDFFile(savePDFPath, imagePaths: self.pageImages, results: resultArrays, scale: KMImageScale)
         var resultArrays: Array<Any> = []
         var ocrIndexArrays: Array<Any> = []
         let sortedKeys = self.ocrDictionary.keys.sorted(by: { $0.compare(($1)) == .orderedAscending })
@@ -214,20 +212,26 @@ extension KMOCRManager: KMGOCRManagerDelegate {
         if model.showType == .page {
             if model.saveAsPDF {
                 if model.saveType == .PDF {
-                    KMGOCRManager.default().createPDFFile(manager.saveFilePath, images: self.pageImages, results: resultArrays, scale: maxImageScale)
+                    let images: [NSImage] = manager.images as? [NSImage] ?? []
+                    KMGOCRManager.default().createPDFFile(manager.saveFilePath, images: images, results: resultArrays, scale: maxImageScale)
+                    NSWorkspace.shared.activateFileViewerSelecting([URL(fileURLWithPath: manager.saveFilePath ?? "")])
+                    self.OCRComplete?(nil, nil, nil)
                 } else {
                     self.saveTXT()
                 }
             } else {
                 let foler = self.fetchOutputFolderPath()
                 let outputPath = foler + "/temp_OCR.pdf";
-                KMGOCRManager.default().createPDFFile(outputPath, images: self.pageImages, results: resultArrays, scale: maxImageScale)
+                let images: [NSImage] = manager.images as? [NSImage] ?? []
+                KMGOCRManager.default().createPDFFile(outputPath, images: images, results: resultArrays, scale: maxImageScale)
                 
                 guard let document = self.document else {
                     self.fail()
                     return }
                 self.insertPDF(currentDocument: document, pageIndexs: self.pageIndexs, outputPath: outputPath)
             }
+            manager.cancelRecognition()
+            self.OCRManger = nil
         } else if model.showType == .area {
             let string = self.fetchTXT(ocrDictionary: ocrDictionary)
             self.OCRComplete?(nil, string, nil)
@@ -240,24 +244,6 @@ extension KMOCRManager: KMGOCRManagerDelegate {
         workspace.activateFileViewerSelecting([url])
     }
     
-//    KMGOCRManager.default().createPDFFile(savePDFPath, imagePaths: imagePath, results: resultArrays, scale: KMImageScale)
-//    if saveAccessCtr.openAutomaticButton.state == .on {
-//        self.cancelButtonAction("")
-//        NSDocumentController.shared.openDocument(withContentsOf: URL(fileURLWithPath: savePDFPath), display: true, completionHandler: {document,documentWasAlreadyOpen,error in
-//            
-//        })
-//    } else {
-//        self.viewFileAtFinder(savePDFPath)
-//    }
-//}
-//}
-//}
-//
-//func viewFileAtFinder(_ fileName: String) {
-//let workspace = NSWorkspace.shared
-//let url = URL(fileURLWithPath: fileName)
-//workspace.activateFileViewerSelecting([url])
-//}
     func gocrManager(_ manager: KMGOCRManager!, didCancelOCRImageAt index: Int) {
         
     }

+ 1 - 1
PDF Office/PDF Master/KMClass/KMPDFViewController/RightSideController/Views/OCR/Tool/View/Page/KMOCRPageView.swift

@@ -277,7 +277,7 @@ extension KMOCRPageView {
     
     func saveAsPDFAction(_ sender: ComponentCheckBox) {
         model.saveAsPDF = (sender.properties.checkboxType == .normal) ? false : true
-//        self.reloadData()
+        self.reloadData()
         
         guard let callBack = changeAction else { return }