From 7d536b0c89f266adf3db317143409c4c82178f99 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 5 Oct 2024 10:51:26 +0200 Subject: [PATCH 1/7] provide image width/height on Android even when returnImage is false --- .../mobile_scanner/MobileScanner.kt | 6 ++++- .../mobile_scanner/MobileScannerHandler.kt | 27 ++++++++----------- 2 files changed, 16 insertions(+), 17 deletions(-) diff --git a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt index 47f7b5779..f56be75a9 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScanner.kt @@ -120,7 +120,11 @@ class MobileScanner( } if (!returnImage) { - mobileScannerCallback(barcodeMap, null, null, null) + mobileScannerCallback( + barcodeMap, + null, + mediaImage.width, + mediaImage.height) return@addOnSuccessListener } 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 4ef12d644..8b67192f5 100644 --- a/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt +++ b/android/src/main/kotlin/dev/steenbakker/mobile_scanner/MobileScannerHandler.kt @@ -47,22 +47,17 @@ class MobileScannerHandler( private var analyzerResult: MethodChannel.Result? = null private val callback: MobileScannerCallback = { barcodes: List>, image: ByteArray?, width: Int?, height: Int? -> - if (image != null) { - barcodeHandler.publishEvent(mapOf( - "name" to "barcode", - "data" to barcodes, - "image" to mapOf( - "bytes" to image, - "width" to width?.toDouble(), - "height" to height?.toDouble(), - ) - )) - } else { - barcodeHandler.publishEvent(mapOf( - "name" to "barcode", - "data" to barcodes - )) - } + barcodeHandler.publishEvent(mapOf( + "name" to "barcode", + "data" to barcodes, + // The image dimensions are always provided. + // The image bytes are only non-null when `returnImage` is true. + "image" to mapOf( + "bytes" to image, + "width" to width?.toDouble(), + "height" to height?.toDouble(), + ) + )) } private val errorCallback: MobileScannerErrorCallback = {error: String -> From 75208e479949f34de2dec49df423d45716f7130b Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 5 Oct 2024 11:23:09 +0200 Subject: [PATCH 2/7] provide image width/height on iOS/MacOS even when returnImage is false --- ios/Classes/MobileScannerPlugin.swift | 20 ++++++++----------- .../mobile_scanner/MobileScannerPlugin.swift | 18 ++++++++++------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/ios/Classes/MobileScannerPlugin.swift b/ios/Classes/MobileScannerPlugin.swift index 1db5e4988..ff09ff77f 100644 --- a/ios/Classes/MobileScannerPlugin.swift +++ b/ios/Classes/MobileScannerPlugin.swift @@ -69,22 +69,18 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin { return } - if (!MobileScannerPlugin.returnImage) { - barcodeHandler.publishEvent([ - "name": "barcode", - "data": barcodesMap, - ]) - return - } + // The image dimensions are always provided. + // The image bytes are only non-null when `returnImage` is true. + let imageData: [String: Any?] = [ + "bytes": MobileScannerPlugin.returnImage ? FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 0.8)!) : nil, + "width": image.size.width, + "height": image.size.height, + ] barcodeHandler.publishEvent([ "name": "barcode", "data": barcodesMap, - "image": [ - "bytes": FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 0.8)!), - "width": image.size.width, - "height": image.size.height, - ], + "image": imageData, ]) }, torchModeChangeCallback: { torchState in barcodeHandler.publishEvent(["name": "torchState", "data": torchState]) diff --git a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index ef389b8a9..8261d367c 100644 --- a/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/macos/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -156,22 +156,26 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, }) DispatchQueue.main.async { - if (!MobileScannerPlugin.returnImage) { + guard let image = cgImage else { self?.sink?([ "name": "barcode", "data": barcodes.map({ $0.toMap() }), ]) return } - + + // The image dimensions are always provided. + // The image bytes are only non-null when `returnImage` is true. + let imageData: [String: Any?] = [ + "bytes": MobileScannerPlugin.returnImage ? FlutterStandardTypedData(bytes: image.jpegData(compressionQuality: 0.8)!) : nil, + "width": Double(image.width), + "height": Double(image.height), + ] + 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), - ], + "image": imageData, ]) } }) From 816c0594efe4e17c5f7c5b8ce0d1c5bf88d41538 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 5 Oct 2024 11:37:39 +0200 Subject: [PATCH 3/7] provide the barcode capture size on the web --- lib/src/web/zxing/zxing_barcode_reader.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/src/web/zxing/zxing_barcode_reader.dart b/lib/src/web/zxing/zxing_barcode_reader.dart index 407f972cb..96c04a70f 100644 --- a/lib/src/web/zxing/zxing_barcode_reader.dart +++ b/lib/src/web/zxing/zxing_barcode_reader.dart @@ -121,6 +121,7 @@ final class ZXingBarcodeReader extends BarcodeReader { controller.add( BarcodeCapture( barcodes: [result.toBarcode], + size: videoSize, ), ); } From 73827aea99862a09d45a098be46300eb613b215d Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 5 Oct 2024 11:38:16 +0200 Subject: [PATCH 4/7] fix error in docs --- lib/src/mobile_scanner_controller.dart | 3 +-- lib/src/objects/barcode_capture.dart | 4 +--- 2 files changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index cabfef726..e4edd86b4 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -75,8 +75,7 @@ class MobileScannerController extends ValueNotifier { /// If this is empty, all supported formats are detected. final List formats; - /// Whether scanned barcodes should contain the image - /// that is embedded into the barcode. + /// Whether the [BarcodeCapture.image] bytes should be provided. /// /// If this is false, [BarcodeCapture.image] will always be null. /// diff --git a/lib/src/objects/barcode_capture.dart b/lib/src/objects/barcode_capture.dart index 173a555d2..e2500b42d 100644 --- a/lib/src/objects/barcode_capture.dart +++ b/lib/src/objects/barcode_capture.dart @@ -32,8 +32,6 @@ class BarcodeCapture { /// 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 input [image]. - /// - /// If [image] is null, this will be [Size.zero]. + /// The size of the camera input [image]. final Size size; } From 1e7154cf4190f24a5b6d2f72ec6d3f2cab4a1504 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 5 Oct 2024 11:43:01 +0200 Subject: [PATCH 5/7] update changelog --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 479efd560..733bc4279 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,11 +5,14 @@ Improvements: * [MacOS] Added support for `analyzeImage`. * [MacOS] Added a Privacy Manifest. * [web] Added the size information to barcode results. +* [web] Added the video output size information to barcode capture. * Added support for barcode formats to image analysis. * Updated the scanner to report any scanning errors that were encountered during processing. * Introduced a new getter `hasCameraPermission` for the `MobileScannerState`. * Fixed a bug in the lifecycle handling sample. Now instead of checking `isInitialized`, the sample recommends using `hasCameraPermission`, which also guards against camera permission errors. +* Updated the behavior of `returnImage` to only determine if the camera output bytes should be sent. +* Updated the behavior of `BarcodeCapture.size` to always be provided when available, regardless of `returnImage`. Bugs fixed: * Fixed a bug that would cause the scanner to emit an error when it was already started. Now it ignores any calls to start while it is starting. From 47e39d0f8d4f9ddddf1f0de422fe753f9d4643e3 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 5 Oct 2024 12:14:01 +0200 Subject: [PATCH 6/7] update comment --- lib/src/web/mobile_scanner_web.dart | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index afaf21cd1..176625967 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -30,8 +30,7 @@ class MobileScannerWeb extends MobileScannerPlatform { BarcodeReader? _barcodeReader; /// The stream controller for the barcode stream. - final StreamController _barcodesController = - StreamController.broadcast(); + final StreamController _barcodesController = StreamController.broadcast(); /// The subscription for the barcode stream. StreamSubscription? _barcodesSubscription; @@ -45,8 +44,7 @@ class MobileScannerWeb extends MobileScannerPlatform { /// because that is the only property for video tracks that can be observed. /// /// See https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#instance_properties_of_video_tracks - final StreamController _settingsController = - StreamController.broadcast(); + final StreamController _settingsController = StreamController.broadcast(); /// The texture ID for the camera view. int _textureId = 1; @@ -65,12 +63,10 @@ class MobileScannerWeb extends MobileScannerPlatform { Stream get barcodesStream => _barcodesController.stream; @override - Stream get torchStateStream => - _settingsController.stream.map((_) => TorchState.unavailable); + Stream get torchStateStream => _settingsController.stream.map((_) => TorchState.unavailable); @override - Stream get zoomScaleStateStream => - _settingsController.stream.map((_) => 1.0); + Stream get zoomScaleStateStream => _settingsController.stream.map((_) => 1.0); /// Create the [HTMLVideoElement] along with its parent container [HTMLDivElement]. HTMLVideoElement _createVideoElement(int textureId) { @@ -143,6 +139,7 @@ class MobileScannerWeb extends MobileScannerPlatform { final JSArray? facingModes = capabilities.facingModeNullable; // TODO: this is an empty array on MacOS Chrome, where there is no facing mode, but one, user facing camera. + // We might be able to add a workaround, using the label of the video track. // Facing mode is not supported by this track, do nothing. if (facingModes == null || facingModes.toDart.isEmpty) { return; @@ -166,14 +163,12 @@ class MobileScannerWeb extends MobileScannerPlatform { throw const MobileScannerException( errorCode: MobileScannerErrorCode.unsupported, errorDetails: MobileScannerErrorDetails( - message: - 'This browser does not support displaying video from the camera.', + message: 'This browser does not support displaying video from the camera.', ), ); } - final MediaTrackSupportedConstraints capabilities = - window.navigator.mediaDevices.getSupportedConstraints(); + final MediaTrackSupportedConstraints capabilities = window.navigator.mediaDevices.getSupportedConstraints(); final MediaStreamConstraints constraints; @@ -194,8 +189,7 @@ class MobileScannerWeb extends MobileScannerPlatform { try { // Retrieving the media devices requests the camera permission. - final MediaStream videoStream = - await window.navigator.mediaDevices.getUserMedia(constraints).toDart; + final MediaStream videoStream = await window.navigator.mediaDevices.getUserMedia(constraints).toDart; return videoStream; } on DOMException catch (error, stackTrace) { @@ -204,8 +198,7 @@ class MobileScannerWeb extends MobileScannerPlatform { MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError; // Handle both unsupported and permission errors from the web. - if (errorMessage.contains('NotFoundError') || - errorMessage.contains('NotSupportedError')) { + if (errorMessage.contains('NotFoundError') || errorMessage.contains('NotSupportedError')) { errorCode = MobileScannerErrorCode.unsupported; } else if (errorMessage.contains('NotAllowedError')) { errorCode = MobileScannerErrorCode.permissionDenied; From b0d58a73e267b3e264a14b60eb0aa151bc71a069 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 5 Oct 2024 12:22:06 +0200 Subject: [PATCH 7/7] format --- lib/src/web/mobile_scanner_web.dart | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index 176625967..ba6867e34 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -30,7 +30,8 @@ class MobileScannerWeb extends MobileScannerPlatform { BarcodeReader? _barcodeReader; /// The stream controller for the barcode stream. - final StreamController _barcodesController = StreamController.broadcast(); + final StreamController _barcodesController = + StreamController.broadcast(); /// The subscription for the barcode stream. StreamSubscription? _barcodesSubscription; @@ -44,7 +45,8 @@ class MobileScannerWeb extends MobileScannerPlatform { /// because that is the only property for video tracks that can be observed. /// /// See https://developer.mozilla.org/en-US/docs/Web/API/MediaTrackConstraints#instance_properties_of_video_tracks - final StreamController _settingsController = StreamController.broadcast(); + final StreamController _settingsController = + StreamController.broadcast(); /// The texture ID for the camera view. int _textureId = 1; @@ -63,10 +65,12 @@ class MobileScannerWeb extends MobileScannerPlatform { Stream get barcodesStream => _barcodesController.stream; @override - Stream get torchStateStream => _settingsController.stream.map((_) => TorchState.unavailable); + Stream get torchStateStream => + _settingsController.stream.map((_) => TorchState.unavailable); @override - Stream get zoomScaleStateStream => _settingsController.stream.map((_) => 1.0); + Stream get zoomScaleStateStream => + _settingsController.stream.map((_) => 1.0); /// Create the [HTMLVideoElement] along with its parent container [HTMLDivElement]. HTMLVideoElement _createVideoElement(int textureId) { @@ -163,12 +167,14 @@ class MobileScannerWeb extends MobileScannerPlatform { throw const MobileScannerException( errorCode: MobileScannerErrorCode.unsupported, errorDetails: MobileScannerErrorDetails( - message: 'This browser does not support displaying video from the camera.', + message: + 'This browser does not support displaying video from the camera.', ), ); } - final MediaTrackSupportedConstraints capabilities = window.navigator.mediaDevices.getSupportedConstraints(); + final MediaTrackSupportedConstraints capabilities = + window.navigator.mediaDevices.getSupportedConstraints(); final MediaStreamConstraints constraints; @@ -189,7 +195,8 @@ class MobileScannerWeb extends MobileScannerPlatform { try { // Retrieving the media devices requests the camera permission. - final MediaStream videoStream = await window.navigator.mediaDevices.getUserMedia(constraints).toDart; + final MediaStream videoStream = + await window.navigator.mediaDevices.getUserMedia(constraints).toDart; return videoStream; } on DOMException catch (error, stackTrace) { @@ -198,7 +205,8 @@ class MobileScannerWeb extends MobileScannerPlatform { MobileScannerErrorCode errorCode = MobileScannerErrorCode.genericError; // Handle both unsupported and permission errors from the web. - if (errorMessage.contains('NotFoundError') || errorMessage.contains('NotSupportedError')) { + if (errorMessage.contains('NotFoundError') || + errorMessage.contains('NotSupportedError')) { errorCode = MobileScannerErrorCode.unsupported; } else if (errorMessage.contains('NotAllowedError')) { errorCode = MobileScannerErrorCode.permissionDenied;