-
-
Notifications
You must be signed in to change notification settings - Fork 607
fix: Fix discrepancy between barcode bounding box and barcode capture size #1169
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
c179310
c83dd61
28be45d
0856a59
fa763a2
48d83fe
fa23d7d
f772f29
2e9e246
e503f0a
2f7de30
02e0102
cc23a80
cfc3643
9209f59
3bb18bd
1d1b0f9
3450814
6dea0d3
75418f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -50,9 +50,11 @@ class MobileScannerHandler( | |
barcodeHandler.publishEvent(mapOf( | ||
"name" to "barcode", | ||
"data" to barcodes, | ||
"image" to image, | ||
"width" to width!!.toDouble(), | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Since the I also added safe access to the width / height. The method channel implementation handles null width/height by falling back to |
||
"height" to height!!.toDouble() | ||
"image" to mapOf( | ||
"bytes" to image, | ||
"width" to width?.toDouble(), | ||
"height" to height?.toDouble(), | ||
) | ||
)) | ||
} else { | ||
barcodeHandler.publishEvent(mapOf( | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -28,12 +28,22 @@ fun Image.toByteArray(): ByteArray { | |
|
||
val Barcode.data: Map<String, Any?> | ||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I sorted these properties alphabetically, to make it easier to follow. The line |
||
"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<String, Double> | ||
|
@@ -92,4 +102,14 @@ private val Barcode.UrlBookmark.data: Map<String, Any?> | |
get() = mapOf("title" to title, "url" to url) | ||
|
||
private val Barcode.WiFi.data: Map<String, Any?> | ||
get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid) | ||
get() = mapOf("encryptionType" to encryptionType, "password" to password, "ssid" to ssid) | ||
|
||
private val Rect.size: Map<String, Any?> | ||
get() { | ||
// Rect.isValid can't be accessed for some reason, so just do the check manually. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. When inspecting the source code of |
||
if (left <= right && top <= bottom) { | ||
return mapOf("width" to width().toDouble(), "height" to height().toDouble()) | ||
} | ||
|
||
return emptyMap() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import UIKit | ||
import Flutter | ||
|
||
@UIApplicationMain | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. UIApplicationMain is deprecated. This was updated by the Flutter tool project migrators. |
||
@main | ||
@objc class AppDelegate: FlutterAppDelegate { | ||
override func application( | ||
_ application: UIApplication, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was moved |
||
|
||
/// 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.") | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This print doesn't add any value |
||
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) { | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Early return instead of else |
||
|
||
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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This parameter is no longer needed |
||
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<String?>? | ||
|
||
// /// Convert image buffer to jpeg | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unused |
||
// 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, | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rewriting to use early returns |
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is new. Previously we would always send the image data, which is incorrect. |
||
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<String, Any?>)["speed"] as? Int ?? 0 | ||
let timeoutMs: Int = (call.arguments as! Dictionary<String, Any?>)["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 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Early return |
||
} | ||
|
||
let barcodesMap: [Any?] = barcodes!.compactMap { barcode in barcode.data } | ||
|
||
DispatchQueue.main.async { | ||
result(["name": "barcode", "data": barcodesMap]) | ||
} | ||
}) | ||
} | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Like on Android, I sorted these on alphabetical keys, and added the |
||
"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, | ||
] | ||
} | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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<Map<Object?, Object?>> barcodes = | ||
data.cast<Map<Object?, Object?>>(); | ||
|
||
if (defaultTargetPlatform == TargetPlatform.macOS) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated the MacOS implementation to use the same keys / datatypes as iOS/Android. So this can be removed. |
||
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) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MacOS is added here, as the format has been updated to match the other platforms. |
||
final Map<Object?, Object?>? imageData = | ||
event['image'] as Map<Object?, Object?>?; | ||
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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. MacOS used the Unlikely that a lot of people need the raw data anyway. |
||
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<BarcodeCapture?> analyzeImage(String path) async { | ||
final Map<String, Object?>? result = | ||
await methodChannel.invokeMapMethod<String, Object?>( | ||
final Map<Object?, Object?>? result = | ||
await methodChannel.invokeMapMethod<Object?, Object?>( | ||
'analyzeImage', | ||
path, | ||
); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This was added by a previous PR, but wasn't yet in the changelog.