diff --git a/CHANGELOG.md b/CHANGELOG.md index 93e936447..ff9d4a1f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +## 5.2.2 + +Improvements: +* [MacOS] Adds Swift Package Manager support. +* [MacOS] Adds support for `returnImage`. +* Added a new `size` property to `Barcode`, that denotes the bounding box of the barcode. + +Bugs fixed: +* Fixed some documentation errors for the `size` and `image` of `BarcodeCapture`. +* [iOS] Fixed a bug with `returnImage`. +* [Android/iOS] Adjusted the raw barcode scan value to pass the raw event data, like on MacOS. + ## 5.2.1 * Updates the `package:web` dependency to use a version range. @@ -44,7 +56,7 @@ Improvements: This major release contains all the changes from the 5.0.0 beta releases, along with the following changes: Improvements: -- [Android] Remove the Kotlin Standard Library from the dependencies, as it is automatically included in Kotlin 1.4+ +* [Android] Remove the Kotlin Standard Library from the dependencies, as it is automatically included in Kotlin 1.4+ ## 5.0.0-beta.3 **BREAKING CHANGES:** diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt index b51260bd0..1f6897421 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt @@ -50,9 +50,11 @@ class MobileScannerHandler( barcodeHandler.publishEvent(mapOf( "name" to "barcode", "data" to barcodes, - "image" to image, - "width" to width!!.toDouble(), - "height" to height!!.toDouble() + "image" to mapOf( + "bytes" to image, + "width" to width?.toDouble(), + "height" to height?.toDouble(), + ) )) } else { barcodeHandler.publishEvent(mapOf( diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerUtilities.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerUtilities.kt index f08e93127..1247404fd 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerUtilities.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerUtilities.kt @@ -28,12 +28,22 @@ fun Image.toByteArray(): ByteArray { val Barcode.data: Map get() = mapOf( - "corners" to cornerPoints?.map { corner -> corner.data }, "format" to format, - "rawBytes" to rawBytes, "rawValue" to rawValue, "type" to valueType, - "calendarEvent" to calendarEvent?.data, "contactInfo" to contactInfo?.data, - "driverLicense" to driverLicense?.data, "email" to email?.data, - "geoPoint" to geoPoint?.data, "phone" to phone?.data, "sms" to sms?.data, - "url" to url?.data, "wifi" to wifi?.data, "displayValue" to displayValue + "calendarEvent" to calendarEvent?.data, + "contactInfo" to contactInfo?.data, + "corners" to cornerPoints?.map { corner -> corner.data }, + "displayValue" to displayValue, + "driverLicense" to driverLicense?.data, + "email" to email?.data, + "format" to format, + "geoPoint" to geoPoint?.data, + "phone" to phone?.data, + "rawBytes" to rawBytes, + "rawValue" to rawValue, + "size" to boundingBox?.size, + "sms" to sms?.data, + "type" to valueType, + "url" to url?.data, + "wifi" to wifi?.data, ) private val Point.data: Map @@ -92,4 +102,14 @@ private val Barcode.UrlBookmark.data: Map get() = mapOf("title" to title, "url" to url) private val Barcode.WiFi.data: Map - get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid) \ No newline at end of file + get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid) + +private val Rect.size: Map + get() { + // Rect.isValid can't be accessed for some reason, so just do the check manually. + if (left <= right && top <= bottom) { + return mapOf("width" to width().toDouble(), "height" to height().toDouble()) + } + + return emptyMap() + } \ No newline at end of file diff --git a/example/ios/Runner/AppDelegate.swift b/example/ios/Runner/AppDelegate.swift index 70693e4a8..b63630348 100644 --- a/example/ios/Runner/AppDelegate.swift +++ b/example/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import UIKit import Flutter -@UIApplicationMain +@main @objc class AppDelegate: FlutterAppDelegate { override func application( _ application: UIApplication, diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef6437..8e02df288 100644 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/ios/Classes/MobileScanner.swift b/ios/Classes/MobileScanner.swift index e9c066f25..e05c338d9 100644 --- a/ios/Classes/MobileScanner.swift +++ b/ios/Classes/MobileScanner.swift @@ -25,9 +25,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega /// Barcode scanner for results var scanner = BarcodeScanner.barcodeScanner() - /// Return image buffer with the Barcode event - var returnImage: Bool = false - /// Default position of camera var videoPosition: AVCaptureDevice.Position = AVCaptureDevice.Position.back @@ -127,7 +124,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega /// Gets called when a new image is added to the buffer public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { - print("Failed to get image buffer from sample buffer.") return } latestBuffer = imageBuffer @@ -160,7 +156,9 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega if (error == nil && barcodesString != nil && newScannedBarcodes != nil && barcodesString!.elementsEqual(newScannedBarcodes!)) { return - } else if (newScannedBarcodes?.isEmpty == false) { + } + + if (newScannedBarcodes?.isEmpty == false) { barcodesString = newScannedBarcodes } } @@ -171,7 +169,7 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega } /// Start scanning for barcodes - func start(barcodeScannerOptions: BarcodeScannerOptions?, returnImage: Bool, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws { + func start(barcodeScannerOptions: BarcodeScannerOptions?, cameraPosition: AVCaptureDevice.Position, torch: Bool, detectionSpeed: DetectionSpeed, completion: @escaping (MobileScannerStartParameters) -> ()) throws { self.detectionSpeed = detectionSpeed if (device != nil || captureSession != nil) { throw MobileScannerError.alreadyStarted @@ -446,17 +444,6 @@ public class MobileScanner: NSObject, AVCaptureVideoDataOutputSampleBufferDelega var barcodesString: Array? - // /// Convert image buffer to jpeg - // private func ciImageToJpeg(ciImage: CIImage) -> Data { - // - // // let ciImage = CIImage(cvPixelBuffer: latestBuffer) - // let context:CIContext = CIContext.init(options: nil) - // let cgImage:CGImage = context.createCGImage(ciImage, from: ciImage.extent)! - // let uiImage:UIImage = UIImage(cgImage: cgImage, scale: 1, orientation: UIImage.Orientation.up) - // - // return uiImage.jpegData(compressionQuality: 0.8)! - // } - /// Rotates images accordingly func imageOrientation( deviceOrientation: UIDeviceOrientation, diff --git a/ios/Classes/MobileScannerPlugin.swift b/ios/Classes/MobileScannerPlugin.swift index eba359a36..0ee886f51 100644 --- a/ios/Classes/MobileScannerPlugin.swift +++ b/ios/Classes/MobileScannerPlugin.swift @@ -11,6 +11,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { /// The handler sends all information via an event channel back to Flutter private let barcodeHandler: BarcodeHandler + + /// Whether to return the input image with the barcode event. + /// This is static to avoid accessing `self` in the callback in the constructor. + private static var returnImage: Bool = false /// The points for the scan window. static var scanWindow: [CGFloat]? @@ -37,24 +41,48 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { init(barcodeHandler: BarcodeHandler, registry: FlutterTextureRegistry) { self.mobileScanner = MobileScanner(registry: registry, mobileScannerCallback: { barcodes, error, image in - if barcodes != nil { - let barcodesMap: [Any?] = barcodes!.compactMap { barcode in - if (MobileScannerPlugin.scanWindow != nil) { - if (MobileScannerPlugin.isBarcodeInScanWindow(barcode: barcode, imageSize: image.size)) { - return barcode.data - } else { - return nil - } - } else { - return barcode.data - } + if error != nil { + barcodeHandler.publishEvent(["name": "error", "data": error!.localizedDescription]) + return + } + + if barcodes == nil { + return + } + + let barcodesMap: [Any?] = barcodes!.compactMap { barcode in + if (MobileScannerPlugin.scanWindow == nil) { + return barcode.data } - if (!barcodesMap.isEmpty) { - barcodeHandler.publishEvent(["name": "barcode", "data": barcodesMap, "image": FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 0.8)!), "width": image.size.width, "height": image.size.height]) + + if (MobileScannerPlugin.isBarcodeInScanWindow(barcode: barcode, imageSize: image.size)) { + return barcode.data } - } else if (error != nil){ - barcodeHandler.publishEvent(["name": "error", "data": error!.localizedDescription]) + + return nil } + + if (barcodesMap.isEmpty) { + return + } + + if (!MobileScannerPlugin.returnImage) { + barcodeHandler.publishEvent([ + "name": "barcode", + "data": barcodesMap, + ]) + return + } + + barcodeHandler.publishEvent([ + "name": "barcode", + "data": barcodesMap, + "image": [ + "bytes": FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 0.8)!), + "width": image.size.width, + "height": image.size.height, + ], + ]) }, torchModeChangeCallback: { torchState in barcodeHandler.publishEvent(["name": "torchState", "data": torchState]) }, zoomScaleChangeCallback: { zoomScale in @@ -104,6 +132,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { let speed: Int = (call.arguments as! Dictionary)["speed"] as? Int ?? 0 let timeoutMs: Int = (call.arguments as! Dictionary)["timeout"] as? Int ?? 0 self.mobileScanner.timeoutSeconds = Double(timeoutMs) / Double(1000) + MobileScannerPlugin.returnImage = returnImage let formatList = formats.map { format in return BarcodeFormat(rawValue: format)} var barcodeOptions: BarcodeScannerOptions? = nil @@ -120,7 +149,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { let detectionSpeed: DetectionSpeed = DetectionSpeed(rawValue: speed)! do { - try mobileScanner.start(barcodeScannerOptions: barcodeOptions, returnImage: returnImage, cameraPosition: position, torch: torch, detectionSpeed: detectionSpeed) { parameters in + try mobileScanner.start(barcodeScannerOptions: barcodeOptions, cameraPosition: position, torch: torch, detectionSpeed: detectionSpeed) { parameters in DispatchQueue.main.async { result([ "textureId": parameters.textureId, @@ -257,12 +286,14 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { DispatchQueue.main.async { result(nil) } - } else { - let barcodesMap: [Any?] = barcodes!.compactMap { barcode in barcode.data } - DispatchQueue.main.async { - result(["name": "barcode", "data": barcodesMap]) - } + return + } + + let barcodesMap: [Any?] = barcodes!.compactMap { barcode in barcode.data } + + DispatchQueue.main.async { + result(["name": "barcode", "data": barcodesMap]) } }) } diff --git a/ios/Classes/MobileScannerUtilities.swift b/ios/Classes/MobileScannerUtilities.swift index 8bac36bc0..9a18bdbe5 100644 --- a/ios/Classes/MobileScannerUtilities.swift +++ b/ios/Classes/MobileScannerUtilities.swift @@ -29,8 +29,27 @@ extension UIDeviceOrientation { extension Barcode { var data: [String: Any?] { - let corners = cornerPoints?.map({$0.cgPointValue.data}) - return ["corners": corners, "format": format.rawValue, "rawBytes": rawData, "rawValue": rawValue, "type": valueType.rawValue, "calendarEvent": calendarEvent?.data, "contactInfo": contactInfo?.data, "driverLicense": driverLicense?.data, "email": email?.data, "geoPoint": geoPoint?.data, "phone": phone?.data, "sms": sms?.data, "url": url?.data, "wifi": wifi?.data, "displayValue": displayValue] + return [ + "calendarEvent": calendarEvent?.data, + "contactInfo": contactInfo?.data, + "corners": cornerPoints?.map({$0.cgPointValue.data}), + "displayValue": displayValue, + "driverLicense": driverLicense?.data, + "email": email?.data, + "format": format.rawValue, + "geoPoint": geoPoint?.data, + "phone": phone?.data, + "rawBytes": rawData, + "rawValue": rawValue, + "size": frame.isNull ? nil : [ + "width": frame.width, + "height": frame.height, + ], + "sms": sms?.data, + "type": valueType.rawValue, + "url": url?.data, + "wifi": wifi?.data, + ] } } diff --git a/ios/mobile_scanner.podspec b/ios/mobile_scanner.podspec index aa52fb624..61dd8e6b1 100644 --- a/ios/mobile_scanner.podspec +++ b/ios/mobile_scanner.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'mobile_scanner' - s.version = '5.2.1' + s.version = '5.2.2' s.summary = 'An universal scanner for Flutter based on MLKit.' s.description = <<-DESC An universal scanner for Flutter based on MLKit. diff --git a/lib/src/method_channel/mobile_scanner_method_channel.dart b/lib/src/method_channel/mobile_scanner_method_channel.dart index 1cc937cfd..891057b11 100644 --- a/lib/src/method_channel/mobile_scanner_method_channel.dart +++ b/lib/src/method_channel/mobile_scanner_method_channel.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:flutter/widgets.dart'; -import 'package:mobile_scanner/src/enums/barcode_format.dart'; import 'package:mobile_scanner/src/enums/mobile_scanner_authorization_state.dart'; import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; import 'package:mobile_scanner/src/enums/torch_state.dart'; @@ -54,31 +53,19 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { final List> barcodes = data.cast>(); - if (defaultTargetPlatform == TargetPlatform.macOS) { - return BarcodeCapture( - raw: event, - barcodes: barcodes - .map( - (barcode) => Barcode( - rawValue: barcode['payload'] as String?, - format: BarcodeFormat.fromRawValue( - barcode['symbology'] as int? ?? -1, - ), - ), - ) - .toList(), - ); - } - if (defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS) { - final double? width = event['width'] as double?; - final double? height = event['height'] as double?; + defaultTargetPlatform == TargetPlatform.iOS || + defaultTargetPlatform == TargetPlatform.macOS) { + final Map? imageData = + event['image'] as Map?; + final Uint8List? image = imageData?['bytes'] as Uint8List?; + final double? width = imageData?['width'] as double?; + final double? height = imageData?['height'] as double?; return BarcodeCapture( - raw: data, + raw: event, barcodes: barcodes.map(Barcode.fromNative).toList(), - image: event['image'] as Uint8List?, + image: image, size: width == null || height == null ? Size.zero : Size(width, height), ); } @@ -154,8 +141,8 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { @override Future analyzeImage(String path) async { - final Map? result = - await methodChannel.invokeMapMethod( + final Map? result = + await methodChannel.invokeMapMethod( 'analyzeImage', path, ); diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index dc7f5d1ff..39c11c61e 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -80,7 +80,7 @@ class MobileScannerController extends ValueNotifier { /// /// If this is false, [BarcodeCapture.image] will always be null. /// - /// Defaults to false, and is only supported on iOS and Android. + /// Defaults to false, and is only supported on iOS, MacOS and Android. final bool returnImage; /// Whether the flashlight should be turned on when the camera is started. diff --git a/lib/src/objects/barcode.dart b/lib/src/objects/barcode.dart index fba2fe665..c03fd9b9b 100644 --- a/lib/src/objects/barcode.dart +++ b/lib/src/objects/barcode.dart @@ -28,6 +28,7 @@ class Barcode { this.phone, this.rawBytes, this.rawValue, + this.size = Size.zero, this.sms, this.type = BarcodeType.unknown, this.url, @@ -38,9 +39,9 @@ class Barcode { factory Barcode.fromNative(Map data) { final Map? calendarEvent = data['calendarEvent'] as Map?; - final List? corners = data['corners'] as List?; final Map? contactInfo = data['contactInfo'] as Map?; + final List? corners = data['corners'] as List?; final Map? driverLicense = data['driverLicense'] as Map?; final Map? email = @@ -50,9 +51,13 @@ class Barcode { final Map? phone = data['phone'] as Map?; final Map? sms = data['sms'] as Map?; + final Map? size = data['size'] as Map?; final Map? url = data['url'] as Map?; final Map? wifi = data['wifi'] as Map?; + final double? barcodeWidth = size?['width'] as double?; + final double? barcodeHeight = size?['height'] as double?; + return Barcode( calendarEvent: calendarEvent == null ? null @@ -81,6 +86,9 @@ class Barcode { phone: phone == null ? null : Phone.fromNative(phone), rawBytes: data['rawBytes'] as Uint8List?, rawValue: data['rawValue'] as String?, + size: barcodeWidth == null || barcodeHeight == null + ? Size.zero + : Size(barcodeWidth, barcodeHeight), sms: sms == null ? null : SMS.fromNative(sms), type: BarcodeType.fromRawValue(data['type'] as int? ?? 0), url: url == null ? null : UrlBookmark.fromNative(url), @@ -144,6 +152,11 @@ class Barcode { /// This is null if the raw value is not available. final String? rawValue; + /// The size of the barcode bounding box. + /// + /// If the bounding box is unavailable, this will be [Size.zero]. + final Size size; + /// The SMS message that is embedded in the barcode. final SMS? sms; diff --git a/lib/src/objects/barcode_capture.dart b/lib/src/objects/barcode_capture.dart index 9ae7a8820..173a555d2 100644 --- a/lib/src/objects/barcode_capture.dart +++ b/lib/src/objects/barcode_capture.dart @@ -1,3 +1,6 @@ +/// @docImport 'package:mobile_scanner/src/mobile_scanner_controller.dart'; +library; + import 'dart:typed_data'; import 'dart:ui'; @@ -16,15 +19,21 @@ class BarcodeCapture { /// The list of scanned barcodes. final List barcodes; - /// The bytes of the image that is embedded in the barcode. + /// The input image of the barcode capture. + /// + /// This is the image that was used to detect the available [barcodes], + /// not the image from a specific barcode. /// - /// This null if [MobileScannerController.returnImage] is false, - /// or if there is no available image. + /// This is always null if [MobileScannerController.returnImage] is false. final Uint8List? image; - /// The raw data of the scanned barcode. + /// The raw data of the barcode scan. + /// + /// This is the data that was used to detect the available [barcodes], the input [image] and the [size]. final Object? raw; - /// The size of the scanned barcode. + /// The size of the input [image]. + /// + /// If [image] is null, this will be [Size.zero]. final Size size; } diff --git a/macos/mobile_scanner.podspec b/macos/mobile_scanner.podspec index bc4c60a1b..2e5e5122b 100644 --- a/macos/mobile_scanner.podspec +++ b/macos/mobile_scanner.podspec @@ -4,7 +4,7 @@ # Pod::Spec.new do |s| s.name = 'mobile_scanner' - s.version = '5.2.1' + s.version = '5.2.2' s.summary = 'An universal scanner for Flutter based on MLKit.' s.description = <<-DESC An universal scanner for Flutter based on MLKit. diff --git a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index 9ba4f606f..e5e82f071 100644 --- a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -25,6 +25,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, // optional window to limit scan search var scanWindow: CGRect? + + /// Whether to return the input image with the barcode event. + /// This is static to avoid accessing `self` in the `VNDetectBarcodesRequest` callback. + private static var returnImage: Bool = false var detectionSpeed: DetectionSpeed = DetectionSpeed.noDuplicates @@ -32,8 +36,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, var symbologies:[VNBarcodeSymbology] = [] - // var analyzeMode: Int = 0 - var analyzing: Bool = false var position = AVCaptureDevice.Position.back public static func register(with registrar: FlutterPluginRegistrar) { @@ -65,8 +67,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, setScale(call, result) case "resetScale": resetScale(call, result) - // case "analyze": - // switchAnalyzeMode(call, result) case "stop": stop(result) case "updateScanWindow": @@ -101,12 +101,11 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, // Gets called when a new image is added to the buffer public func captureOutput(_ output: AVCaptureOutput, didOutput sampleBuffer: CMSampleBuffer, from connection: AVCaptureConnection) { - // Ignore invalid textureId + // Ignore invalid texture id. if textureId == nil { return } guard let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer) else { - print("Failed to get image buffer from sample buffer.") return } latestBuffer = imageBuffer @@ -118,7 +117,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, nextScanTime = currentTime + timeoutSeconds imagesCurrentlyBeingProcessed = true DispatchQueue.global(qos: .userInitiated).async { [weak self] in - if(self!.latestBuffer == nil){ + if self!.latestBuffer == nil { return } var cgImage: CGImage? @@ -127,44 +126,57 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, do { let barcodeRequest:VNDetectBarcodesRequest = VNDetectBarcodesRequest(completionHandler: { [weak self] (request, error) in self?.imagesCurrentlyBeingProcessed = false - if error == nil { - if let results = request.results as? [VNBarcodeObservation] { - for barcode in results { - if self?.scanWindow != nil && cgImage != nil { - let match = self?.isBarCodeInScanWindow(self!.scanWindow!, barcode, cgImage!) ?? false - if (!match) { - continue - } - } - - DispatchQueue.main.async { - self?.sink?([ - "name": "barcode", - "data": [ - [ - "payload": barcode.payloadStringValue ?? "", - "symbology": barcode.symbology.toInt ?? -1, - ], - ], - ]) - } - // if barcodeType == "QR" { - // let image = CIImage(image: source) - // image?.cropping(to: barcode.boundingBox) - // self.qrCodeDescriptor(qrCode: barcode, qrCodeImage: image!) - // } - } - } - } else { + + if error != nil { DispatchQueue.main.async { self?.sink?(FlutterError(code: "MobileScanner", message: error?.localizedDescription, details: nil)) } + return + } + + guard let results: [VNBarcodeObservation] = request.results as? [VNBarcodeObservation] else { + return + } + + if results.isEmpty { + return + } + + let barcodes: [VNBarcodeObservation] = results.compactMap({ barcode in + // If there is a scan window, check if the barcode is within said scan window. + if self?.scanWindow != nil && cgImage != nil && !(self?.isBarCodeInScanWindow(self!.scanWindow!, barcode, cgImage!) ?? false) { + return nil + } + + return barcode + }) + + DispatchQueue.main.async { + if (!MobileScannerPlugin.returnImage) { + self?.sink?([ + "name": "barcode", + "data": barcodes.map({ $0.toMap() }), + ]) + return + } + + self?.sink?([ + "name": "barcode", + "data": barcodes.map({ $0.toMap() }), + "image": cgImage == nil ? nil : [ + "bytes": FlutterStandardTypedData(bytes: cgImage!.jpegData(compressionQuality: 0.8)!), + "width": Double(cgImage!.width), + "height": Double(cgImage!.height), + ], + ]) } }) - if(self?.symbologies.isEmpty == false){ - // add the symbologies the user wishes to support + + if self?.symbologies.isEmpty == false { + // Add the symbologies the user wishes to support. barcodeRequest.symbologies = self!.symbologies } + try imageRequestHandler.perform([barcodeRequest]) } catch let e { DispatchQueue.main.async { @@ -265,6 +277,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, let speed:Int = argReader.int(key: "speed") ?? 0 let timeoutMs:Int = argReader.int(key: "timeout") ?? 0 symbologies = argReader.toSymbology() + MobileScannerPlugin.returnImage = argReader.bool(key: "returnImage") ?? false timeoutSeconds = Double(timeoutMs) / 1000.0 detectionSpeed = DetectionSpeed(rawValue: speed)! @@ -415,11 +428,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, result(nil) } - // func switchAnalyzeMode(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) { - // analyzeMode = call.arguments as! Int - // result(nil) - // } - func stop(_ result: FlutterResult) { if (device == nil || captureSession == nil) { result(nil) @@ -436,7 +444,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, device.removeObserver(self, forKeyPath: #keyPath(AVCaptureDevice.torchMode)) registry.unregisterTexture(textureId) - // analyzeMode = 0 latestBuffer = nil captureSession = nil device = nil @@ -504,6 +511,45 @@ class MapArgumentReader { } +extension CGImage { + public func jpegData(compressionQuality: CGFloat) -> Data? { + let mutableData = CFDataCreateMutable(nil, 0) + + let formatHint: CFString + + if #available(macOS 11.0, *) { + formatHint = UTType.jpeg.identifier as CFString + } else { + formatHint = kUTTypeJPEG + } + + guard let destination = CGImageDestinationCreateWithData(mutableData!, formatHint, 1, nil) else { + return nil + } + + let options: NSDictionary = [ + kCGImageDestinationLossyCompressionQuality: compressionQuality, + ] + + CGImageDestinationAddImage(destination, self, options) + + if !CGImageDestinationFinalize(destination) { + return nil + } + + return mutableData as Data? + } +} + +extension VNBarcodeObservation { + public func toMap() -> [String: Any?] { + return [ + "rawValue": self.payloadStringValue ?? "", + "format": self.symbology.toInt ?? -1, + ] + } +} + extension VNBarcodeSymbology { static func fromInt(_ mapValue:Int) -> VNBarcodeSymbology? { if #available(macOS 12.0, *) { diff --git a/pubspec.yaml b/pubspec.yaml index 71d2a6007..4997e7909 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: mobile_scanner description: A universal barcode and QR code scanner for Flutter based on MLKit. Uses CameraX on Android, AVFoundation on iOS and Apple Vision & AVFoundation on macOS. -version: 5.2.1 +version: 5.2.2 repository: https://github.com/juliansteenbakker/mobile_scanner screenshots: