KMGOCROperation.swift 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  1. //
  2. // KMGOCROperation.swift
  3. // PDF Master
  4. //
  5. // Created by liujiajie on 2023/11/15.
  6. //
  7. import Foundation
  8. @objc(KMGOCROperationDelegate)
  9. protocol KMGOCROperationDelegate: AnyObject {
  10. @objc optional func GOCROperationCancel(_ op: KMGOCROperation, atIndex: Int)
  11. @objc optional func GOCROperationStart(_ op: KMGOCROperation, atIndex: Int)
  12. @objc optional func GOCROperationFinish(_ op: KMGOCROperation, atIndex: Int, results: Array<Any>)
  13. @objc optional func GOCROperationFail(_ op: KMGOCROperation, atIndex: Int, err: Error?)
  14. }
  15. let KMGOC_API_URL = "https://vision.googleapis.com/v1/images:annotate"
  16. #if VERSION_DMG
  17. let KMGOC_API_KEY = "AIzaSyBhSRohpngAu8pSgFDXPytslNDHgGm7uDs"
  18. #else
  19. let KMGOC_API_KEY = "AIzaSyCJuqJ9YvtkFKMl1mW3Yq-av3mmI9ScbRY"
  20. #endif
  21. @objcMembers class KMGOCROperation: Operation{
  22. @objc var operationDelegate: KMGOCROperationDelegate?
  23. var selectedLanguages: Array<Any>?
  24. override var isExecuting: Bool{
  25. set{
  26. self.willChangeValue(forKey: "isExecuting")
  27. self.isExecuting = newValue
  28. didChangeValue(forKey: "isExecuting")
  29. }
  30. get{
  31. return super.isExecuting
  32. }
  33. }
  34. override var isFinished: Bool{
  35. set{
  36. self.willChangeValue(forKey: "isFinished")
  37. self.isFinished = newValue
  38. didChangeValue(forKey: "isFinished")
  39. }
  40. get{
  41. return super.isFinished
  42. }
  43. }
  44. override var isCancelled: Bool{
  45. set{
  46. self.willChangeValue(forKey: "isCancelled")
  47. self.isCancelled = newValue
  48. didChangeValue(forKey: "isCancelled")
  49. }
  50. get{
  51. return super.isCancelled
  52. }
  53. }
  54. var fileName: String = ""
  55. var orcImage: NSImage?
  56. var task: URLSessionDataTask?
  57. var imageIndex: Int = 0
  58. init(recognitionImg:NSImage, imgIndex:Int) {
  59. super.init()
  60. self.imageIndex = imgIndex
  61. self.fileName = fileNameWithDate()
  62. self.orcImage = recognitionImg
  63. self.queuePriority = .normal
  64. self.name = self.fileName
  65. self.isExecuting = false
  66. self.isFinished = false
  67. }
  68. func fileNameWithDate() -> String {
  69. let formatter = DateFormatter()
  70. formatter.dateFormat = "YYYY-MM-dd-hh-mm-ss-SSS"
  71. let dateString = formatter.string(from: Date())
  72. let fileName = "\(dateString) \(imageIndex)"
  73. return fileName
  74. }
  75. override func start() {
  76. if p_checkCancelled() { return }
  77. isExecuting = true
  78. Thread.detachNewThreadSelector(#selector(main), toTarget: self, with: nil)
  79. }
  80. @objc override func main() {
  81. do {
  82. if p_checkCancelled() { return }
  83. recognitionImage(orcImage ?? NSImage())
  84. while isExecuting {
  85. if p_checkCancelled() {
  86. return
  87. }
  88. }
  89. } catch let e {
  90. Swift.debugPrint("Exception %@", e)
  91. }
  92. }
  93. override func cancel() {
  94. super.cancel()
  95. if task != nil {
  96. task?.cancel()
  97. task = nil
  98. }
  99. self.operationDelegate?.GOCROperationCancel?(self, atIndex: self.imageIndex)
  100. if isExecuting {
  101. isExecuting = false
  102. isFinished = true
  103. } else {
  104. isFinished = false
  105. }
  106. isCancelled = true
  107. }
  108. func p_done() {
  109. isExecuting = false
  110. isFinished = true
  111. }
  112. func p_checkCancelled() -> Bool {
  113. if isCancelled {
  114. isFinished = true
  115. return true
  116. }
  117. return false
  118. }
  119. func recognitionImage(_ image: NSImage) {
  120. self.operationDelegate?.GOCROperationStart?(self, atIndex: self.imageIndex)
  121. let binaryImageData = base64EncodeImage(image)
  122. if binaryImageData == nil {
  123. self.operationDelegate?.GOCROperationFail?(self, atIndex: self.imageIndex, err: nil)
  124. return
  125. }
  126. let urlString = "\(KMGOC_API_URL)?key=\(KMGOC_API_KEY)"
  127. var request = URLRequest(url: URL(string: urlString)!)
  128. request.httpMethod = "POST"
  129. request.addValue("application/json", forHTTPHeaderField: "Content-Type")
  130. let imageDictionary = ["content": binaryImageData]
  131. let featuresArray = [["type": "TEXT_DETECTION", "maxResults": 10]]
  132. var paramsDictionary = ["requests": [["image": imageDictionary, "features": featuresArray]]] as [String: Any]
  133. if selectedLanguages != nil && selectedLanguages?.count ?? 0 > 0 {
  134. let imageContextDictionary = ["languageHints": selectedLanguages]
  135. paramsDictionary = ["requests": [["image": imageDictionary, "features": featuresArray, "imageContext": imageContextDictionary]]] as [String: Any]
  136. }
  137. let requestData = try? JSONSerialization.data(withJSONObject: paramsDictionary, options: [])
  138. request.httpBody = requestData
  139. let URLSession = URLSession.shared
  140. task = URLSession.dataTask(with: request) { (data, response, error) in
  141. if (error as NSError?)?.code == NSURLErrorCancelled {
  142. return
  143. }
  144. var results: [Any]? = nil
  145. if error == nil {
  146. let dictionary = try? JSONSerialization.jsonObject(with: data!, options: []) as? [String: Any]
  147. results = self.responseDataResults(dictionary! as NSDictionary)
  148. }
  149. DispatchQueue.main.async {
  150. if error != nil || results == nil {
  151. self.operationDelegate?.GOCROperationFail?(self, atIndex: self.imageIndex, err: error)
  152. } else {
  153. self.operationDelegate?.GOCROperationFinish?(self, atIndex: self.imageIndex, results: results ?? [])
  154. }
  155. }
  156. if error != nil || results == nil {
  157. self.cancel()
  158. } else {
  159. self.p_done()
  160. }
  161. }
  162. task?.resume()
  163. }
  164. func base64EncodeImage(_ image: NSImage) -> String? {
  165. guard let data = image.tiffRepresentation else { return nil }
  166. let imageRep = NSBitmapImageRep(data: data)!
  167. imageRep.size = image.size
  168. let imageData = imageRep.representation(using: .png, properties: [:])
  169. // Resize the image if it exceeds the 4MB API limit
  170. if imageData?.count ?? 0 > 4194304 {
  171. let compressedData = compressImageData(imageData!, toMaxFileSize: 4194304)
  172. if let data = compressedData {
  173. return data.base64EncodedString(options: .endLineWithCarriageReturn)
  174. }
  175. }
  176. if let data = imageData {
  177. if #available(macOS 10.9, *) {
  178. return data.base64EncodedString(options: .endLineWithCarriageReturn)
  179. } else {
  180. return data.base64EncodedString(options: [])
  181. }
  182. }
  183. return nil
  184. }
  185. func compressImageData(_ imageData: Data, toMaxFileSize maxFileSize: Int) -> Data? {
  186. var compression: CGFloat = 0.9
  187. let maxCompression: CGFloat = 0.1
  188. var compressImageData = imageData
  189. while compressImageData.count > maxFileSize && compression > maxCompression {
  190. compression -= 0.1
  191. let imageRep = NSBitmapImageRep(data: compressImageData)!
  192. compressImageData = imageRep.representation(using: .jpeg, properties: [NSBitmapImageRep.PropertyKey.compressionFactor: NSNumber(value: Float(compression))])!
  193. }
  194. return compressImageData
  195. }
  196. func responseDataResults(_ dictionary: NSDictionary) -> [Any]? {
  197. guard let responses = dictionary["responses"] as? [Any],
  198. let responseData = responses.first as? NSDictionary,
  199. let errorObj = dictionary["error"] as? NSDictionary else { return nil }
  200. var results: [Any]? = nil
  201. if let textAnnotations = responseData["textAnnotations"] as? [Any] {
  202. results = [Any]()
  203. for annotation in textAnnotations {
  204. var textBounds = CGRect.zero
  205. let dic = (annotation as? NSDictionary)?["boundingPoly"]
  206. if let vertices = (dic as? NSDictionary)?["vertices"] as? [Any] {
  207. var minX: CGFloat = 0, minY: CGFloat = 0, maxX: CGFloat = 0, maxY: CGFloat = 0
  208. for i in 0..<vertices.count {
  209. if let vertex = vertices[i] as? NSDictionary,
  210. let x = vertex["x"] as? CGFloat,
  211. let y = vertex["y"] as? CGFloat {
  212. minX = i == 0 ? x : min(x, minX)
  213. minY = i == 0 ? y : min(y, minY)
  214. maxX = i == 0 ? x : max(x, maxX)
  215. maxY = i == 0 ? y : max(y, maxY)
  216. }
  217. }
  218. textBounds = CGRect(x: minX, y: minY, width: maxX-minX, height: maxY-minY)
  219. }
  220. let result = KMGOCRResult()
  221. result.text = (annotation as? NSDictionary)?["description"] as? String ?? ""
  222. result.locale = (annotation as? NSDictionary)?["locale"] as? String ?? ""
  223. result.textBounds = textBounds
  224. results?.append(result)
  225. }
  226. }
  227. return results
  228. }
  229. }