Navaron Bracke
Committed by GitHub

Merge pull request #725 from ryanduffyne/mac_os_fixes

fix: Mac os fixes
@@ -384,6 +384,7 @@ class MobileScannerController { @@ -384,6 +384,7 @@ class MobileScannerController {
384 barcodes: [ 384 barcodes: [
385 Barcode( 385 Barcode(
386 rawValue: (data as Map)['payload'] as String?, 386 rawValue: (data as Map)['payload'] as String?,
  387 + format: toFormat(data['symbology'] as int),
387 ), 388 ),
388 ], 389 ],
389 ), 390 ),
@@ -2,6 +2,7 @@ import AVFoundation @@ -2,6 +2,7 @@ import AVFoundation
2 import FlutterMacOS 2 import FlutterMacOS
3 import Vision 3 import Vision
4 import AppKit 4 import AppKit
  5 +import VideoToolbox
5 6
6 public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate { 7 public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, FlutterTexture, AVCaptureVideoDataOutputSampleBufferDelegate {
7 8
@@ -17,8 +18,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -17,8 +18,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
17 var captureSession: AVCaptureSession! 18 var captureSession: AVCaptureSession!
18 19
19 // The selected camera 20 // The selected camera
20 - var device: AVCaptureDevice!  
21 - 21 + weak var device: AVCaptureDevice!
  22 +
22 // Image to be sent to the texture 23 // Image to be sent to the texture
23 var latestBuffer: CVImageBuffer! 24 var latestBuffer: CVImageBuffer!
24 25
@@ -28,6 +29,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -28,6 +29,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
28 var detectionSpeed: DetectionSpeed = DetectionSpeed.noDuplicates 29 var detectionSpeed: DetectionSpeed = DetectionSpeed.noDuplicates
29 30
30 var timeoutSeconds: Double = 0 31 var timeoutSeconds: Double = 0
  32 +
  33 + var symbologies:[VNBarcodeSymbology] = []
31 34
32 35
33 // var analyzeMode: Int = 0 36 // var analyzeMode: Int = 0
@@ -93,7 +96,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -93,7 +96,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
93 } 96 }
94 97
95 var nextScanTime = 0.0 98 var nextScanTime = 0.0
96 - var imagesCurrentlyBeingProcessed = 0 99 + var imagesCurrentlyBeingProcessed = false
97 100
98 // Gets called when a new image is added to the buffer 101 // Gets called when a new image is added to the buffer
99 public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { 102 public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) {
@@ -109,45 +112,53 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -109,45 +112,53 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
109 registry.textureFrameAvailable(textureId) 112 registry.textureFrameAvailable(textureId)
110 113
111 let currentTime = Date().timeIntervalSince1970 114 let currentTime = Date().timeIntervalSince1970
112 - let eligibleForScan = currentTime > nextScanTime && imagesCurrentlyBeingProcessed == 0; 115 + let eligibleForScan = currentTime > nextScanTime && !imagesCurrentlyBeingProcessed
113 if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && eligibleForScan || detectionSpeed == DetectionSpeed.unrestricted) { 116 if ((detectionSpeed == DetectionSpeed.normal || detectionSpeed == DetectionSpeed.noDuplicates) && eligibleForScan || detectionSpeed == DetectionSpeed.unrestricted) {
114 nextScanTime = currentTime + timeoutSeconds 117 nextScanTime = currentTime + timeoutSeconds
115 - imagesCurrentlyBeingProcessed += 1  
116 - let imageRequestHandler = VNImageRequestHandler(  
117 - cvPixelBuffer: latestBuffer,  
118 - orientation: .right)  
119 - 118 + imagesCurrentlyBeingProcessed = true
  119 + DispatchQueue.global(qos: .userInitiated).async { [weak self] in
  120 + if(self!.latestBuffer == nil){
  121 + return
  122 + }
  123 + var cgImage: CGImage?
  124 + VTCreateCGImageFromCVPixelBuffer(self!.latestBuffer, options: nil, imageOut: &cgImage)
  125 + let imageRequestHandler = VNImageRequestHandler(cgImage: cgImage!)
120 do { 126 do {
121 - try imageRequestHandler.perform([VNDetectBarcodesRequest { [self] (request, error) in  
122 - imagesCurrentlyBeingProcessed -= 1  
123 - if error == nil {  
124 - if let results = request.results as? [VNBarcodeObservation] { 127 + let barcodeRequest:VNDetectBarcodesRequest = VNDetectBarcodesRequest(completionHandler: { [weak self] (request, error) in
  128 + self?.imagesCurrentlyBeingProcessed = false
  129 + if error == nil {
  130 + if let results = request.results as? [VNBarcodeObservation] {
125 for barcode in results { 131 for barcode in results {
126 - if self.scanWindow != nil {  
127 - let match = self.isbarCodeInScanWindow(self.scanWindow!, barcode, self.latestBuffer) 132 + if self?.scanWindow != nil && cgImage != nil {
  133 + let match = self?.isBarCodeInScanWindow(self!.scanWindow!, barcode, cgImage!) ?? false
128 if (!match) { 134 if (!match) {
129 continue 135 continue
130 } 136 }
131 } 137 }
132 138
133 - let barcodeType = String(barcode.symbology.rawValue).replacingOccurrences(of: "VNBarcodeSymbology", with: "")  
134 - let event: [String: Any?] = ["name": "barcodeMac", "data" : ["payload": barcode.payloadStringValue, "symbology": barcodeType]]  
135 - self.sink?(event)  
136 -  
137 - // if barcodeType == "QR" {  
138 - // let image = CIImage(image: source)  
139 - // image?.cropping(to: barcode.boundingBox)  
140 - // self.qrCodeDescriptor(qrCode: barcode, qrCodeImage: image!)  
141 - // }  
142 - }  
143 - }  
144 - } else {  
145 - print(error!.localizedDescription)  
146 - }  
147 - }])  
148 - } catch {  
149 - print(error) 139 + DispatchQueue.main.async {
  140 + self?.sink?(["name": "barcodeMac", "data" : ["payload": barcode.payloadStringValue, "symbology": barcode.symbology.toInt as Any?]] as [String : Any])
  141 + }
  142 +// if barcodeType == "QR" {
  143 +// let image = CIImage(image: source)
  144 +// image?.cropping(to: barcode.boundingBox)
  145 +// self.qrCodeDescriptor(qrCode: barcode, qrCodeImage: image!)
  146 +// }
  147 + }
  148 + }
  149 + } else {
  150 + self?.sink?(FlutterError(code: "MobileScanner", message: error?.localizedDescription, details: nil))
  151 + }
  152 + })
  153 + if(self?.symbologies.isEmpty == false){
  154 + // add the symbologies the user wishes to support
  155 + barcodeRequest.symbologies = self!.symbologies
  156 + }
  157 + try imageRequestHandler.perform([barcodeRequest])
  158 + } catch let e {
  159 + self?.sink?(FlutterError(code: "MobileScanner", message: e.localizedDescription, details: nil))
150 } 160 }
  161 + }
151 } 162 }
152 } 163 }
153 164
@@ -180,10 +191,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -180,10 +191,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
180 let scanWindowData: Array? = argReader.floatArray(key: "rect") 191 let scanWindowData: Array? = argReader.floatArray(key: "rect")
181 192
182 if (scanWindowData == nil) { 193 if (scanWindowData == nil) {
183 - return 194 + return
184 } 195 }
185 196
186 - let minX = scanWindowData![0] 197 + let minX = scanWindowData![0]
187 let minY = scanWindowData![1] 198 let minY = scanWindowData![1]
188 199
189 let width = scanWindowData![2] - minX 200 let width = scanWindowData![2] - minX
@@ -191,9 +202,23 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -191,9 +202,23 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
191 202
192 scanWindow = CGRect(x: minX, y: minY, width: width, height: height) 203 scanWindow = CGRect(x: minX, y: minY, width: width, height: height)
193 } 204 }
  205 +
  206 + func isBarCodeInScanWindow(_ scanWindow: CGRect, _ barcode: VNBarcodeObservation, _ inputImage: CGImage) -> Bool {
194 207
195 - func isbarCodeInScanWindow(_ scanWindow: CGRect, _ barcode: VNBarcodeObservation, _ inputImage: CVImageBuffer) -> Bool {  
196 - let size = CVImageBufferGetEncodedSize(inputImage) 208 + let imageWidth = CGFloat(inputImage.width);
  209 + let imageHeight = CGFloat(inputImage.height);
  210 +
  211 + let minX = scanWindow.minX * imageWidth
  212 + let minY = scanWindow.minY * imageHeight
  213 + let width = scanWindow.width * imageWidth
  214 + let height = scanWindow.height * imageHeight
  215 +
  216 + let scaledScanWindow = CGRect(x: minX, y: minY, width: width, height: height)
  217 + return scaledScanWindow.contains(barcode.boundingBox)
  218 + }
  219 +
  220 + func isBarCodeInScanWindow(_ scanWindow: CGRect, _ barcode: VNBarcodeObservation, _ inputImage: CVImageBuffer) -> Bool {
  221 + let size = CVImageBufferGetEncodedSize(inputImage)
197 222
198 let imageWidth = size.width; 223 let imageWidth = size.width;
199 let imageHeight = size.height; 224 let imageHeight = size.height;
@@ -210,23 +235,24 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -210,23 +235,24 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
210 func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { 235 func start(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) {
211 if (device != nil) { 236 if (device != nil) {
212 result(FlutterError(code: "MobileScanner", 237 result(FlutterError(code: "MobileScanner",
213 - message: "Called start() while already started!",  
214 - details: nil)) 238 + message: "Called start() while already started!",
  239 + details: nil))
215 return 240 return
216 } 241 }
217 - 242 +
218 textureId = registry.register(self) 243 textureId = registry.register(self)
219 captureSession = AVCaptureSession() 244 captureSession = AVCaptureSession()
220 - 245 +
221 let argReader = MapArgumentReader(call.arguments as? [String: Any]) 246 let argReader = MapArgumentReader(call.arguments as? [String: Any])
222 -  
223 -// let ratio: Int = argReader.int(key: "ratio")  
224 - let torch: Bool = argReader.bool(key: "torch") ?? false  
225 - let facing: Int = argReader.int(key: "facing") ?? 1  
226 - let speed: Int = (call.arguments as! Dictionary<String, Any?>)["speed"] as? Int ?? 0  
227 - let timeoutMs: Int = (call.arguments as! Dictionary<String, Any?>)["timeout"] as? Int ?? 0  
228 247
229 - timeoutSeconds = Double(timeoutMs) * 1000.0 248 + // let ratio: Int = argReader.int(key: "ratio")
  249 + let torch:Bool = argReader.bool(key: "torch") ?? false
  250 + let facing:Int = argReader.int(key: "facing") ?? 1
  251 + let speed:Int = argReader.int(key: "speed") ?? 0
  252 + let timeoutMs:Int = argReader.int(key: "timeout") ?? 0
  253 + symbologies = argReader.toSymbology()
  254 +
  255 + timeoutSeconds = Double(timeoutMs) / 1000.0
230 detectionSpeed = DetectionSpeed(rawValue: speed)! 256 detectionSpeed = DetectionSpeed(rawValue: speed)!
231 257
232 // Set the camera to use 258 // Set the camera to use
@@ -241,8 +267,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -241,8 +267,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
241 267
242 if (device == nil) { 268 if (device == nil) {
243 result(FlutterError(code: "MobileScanner", 269 result(FlutterError(code: "MobileScanner",
244 - message: "No camera found or failed to open camera!",  
245 - details: nil)) 270 + message: "No camera found or failed to open camera!",
  271 + details: nil))
246 return 272 return
247 } 273 }
248 274
@@ -250,10 +276,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -250,10 +276,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
250 if (device.hasTorch) { 276 if (device.hasTorch) {
251 do { 277 do {
252 try device.lockForConfiguration() 278 try device.lockForConfiguration()
253 - device.torchMode = torch ? .on : .off  
254 - device.unlockForConfiguration()  
255 - } catch {  
256 - result(FlutterError(code: error.localizedDescription, message: nil, details: nil)) 279 + device.torchMode = torch ? .on : .off
  280 + device.unlockForConfiguration()
  281 + } catch {
  282 + result(FlutterError(code: error.localizedDescription, message: nil, details: nil))
257 } 283 }
258 } 284 }
259 285
@@ -277,7 +303,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -277,7 +303,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
277 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main) 303 videoOutput.setSampleBufferDelegate(self, queue: DispatchQueue.main)
278 captureSession.addOutput(videoOutput) 304 captureSession.addOutput(videoOutput)
279 for connection in videoOutput.connections { 305 for connection in videoOutput.connections {
280 -// connection.videoOrientation = .portrait 306 + // connection.videoOrientation = .portrait
281 if position == .front && connection.isVideoMirroringSupported { 307 if position == .front && connection.isVideoMirroringSupported {
282 connection.isVideoMirrored = true 308 connection.isVideoMirrored = true
283 } 309 }
@@ -351,30 +377,117 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, @@ -351,30 +377,117 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler,
351 377
352 class MapArgumentReader { 378 class MapArgumentReader {
353 379
354 - let args: [String: Any]?  
355 -  
356 - init(_ args: [String: Any]?) {  
357 - self.args = args  
358 - }  
359 -  
360 - func string(key: String) -> String? {  
361 - return args?[key] as? String  
362 - }  
363 -  
364 - func int(key: String) -> Int? {  
365 - return (args?[key] as? NSNumber)?.intValue  
366 - }  
367 - 380 + let args: [String: Any]?
  381 +
  382 + init(_ args: [String: Any]?) {
  383 + self.args = args
  384 + }
  385 +
  386 + func string(key: String) -> String? {
  387 + return args?[key] as? String
  388 + }
  389 +
  390 + func int(key: String) -> Int? {
  391 + return (args?[key] as? NSNumber)?.intValue
  392 + }
  393 +
368 func bool(key: String) -> Bool? { 394 func bool(key: String) -> Bool? {
369 - return (args?[key] as? NSNumber)?.boolValue 395 + return (args?[key] as? NSNumber)?.boolValue
  396 + }
  397 +
  398 + func stringArray(key: String) -> [String]? {
  399 + return args?[key] as? [String]
  400 + }
  401 +
  402 + func toSymbology() -> [VNBarcodeSymbology] {
  403 + guard let syms:[Int] = args?["formats"] as? [Int] else {
  404 + return []
  405 + }
  406 + if(syms.contains(0)){
  407 + return []
  408 + }
  409 + var barcodeFormats:[VNBarcodeSymbology] = []
  410 + syms.forEach { id in
  411 + if let bc:VNBarcodeSymbology = VNBarcodeSymbology.fromInt(id) {
  412 + barcodeFormats.append(bc)
  413 + }
  414 + }
  415 + return barcodeFormats
370 } 416 }
371 417
372 - func stringArray(key: String) -> [String]? {  
373 - return args?[key] as? [String]  
374 - } 418 + func floatArray(key: String) -> [CGFloat]? {
  419 + return args?[key] as? [CGFloat]
  420 + }
  421 +
  422 +}
375 423
376 - func floatArray(key: String) -> [CGFloat]? {  
377 - return args?[key] as? [CGFloat]  
378 - } 424 +extension VNBarcodeSymbology {
379 425
  426 + static func fromInt(_ mapValue:Int) -> VNBarcodeSymbology? {
  427 + if #available(macOS 12.0, *) {
  428 + if(mapValue == 8){
  429 + return VNBarcodeSymbology.codabar
  430 + }
  431 + }
  432 + switch(mapValue){
  433 + case 1:
  434 + return VNBarcodeSymbology.code128
  435 + case 2:
  436 + return VNBarcodeSymbology.code39
  437 + case 4:
  438 + return VNBarcodeSymbology.code93
  439 + case 16:
  440 + return VNBarcodeSymbology.dataMatrix
  441 + case 32:
  442 + return VNBarcodeSymbology.ean13
  443 + case 64:
  444 + return VNBarcodeSymbology.ean8
  445 + case 128:
  446 + return VNBarcodeSymbology.itf14
  447 + case 256:
  448 + return VNBarcodeSymbology.qr
  449 + case 1024:
  450 + return VNBarcodeSymbology.upce
  451 + case 2048:
  452 + return VNBarcodeSymbology.pdf417
  453 + case 4096:
  454 + return VNBarcodeSymbology.aztec
  455 + default:
  456 + return nil
  457 + }
  458 + }
  459 +
  460 + var toInt:Int? {
  461 + if #available(macOS 12.0, *) {
  462 + if(self == VNBarcodeSymbology.codabar){
  463 + return 8
  464 + }
  465 + }
  466 + switch(self){
  467 + case VNBarcodeSymbology.code128:
  468 + return 1
  469 + case VNBarcodeSymbology.code39:
  470 + return 2
  471 + case VNBarcodeSymbology.code93:
  472 + return 4
  473 + case VNBarcodeSymbology.dataMatrix:
  474 + return 16
  475 + case VNBarcodeSymbology.ean13:
  476 + return 32
  477 + case VNBarcodeSymbology.ean8:
  478 + return 64
  479 + case VNBarcodeSymbology.itf14:
  480 + return 128
  481 + case VNBarcodeSymbology.qr:
  482 + return 256
  483 + case VNBarcodeSymbology.upce:
  484 + return 1024
  485 + case VNBarcodeSymbology.pdf417:
  486 + return 2048
  487 + case VNBarcodeSymbology.aztec:
  488 + return 4096
  489 + default:
  490 + return -1;
  491 + }
  492 + }
380 } 493 }