From a992e1a2044428f092c95fb9c7a2b2b0610b6d18 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 09:00:36 +0100 Subject: [PATCH 01/20] update changelog; bump dependency --- CHANGELOG.md | 45 +++++++++++++++++++++++++++----------------- android/build.gradle | 2 +- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f91bdd079..704e9ff7e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,34 +1,45 @@ ## NEXT -- Fixed an issue which caused the scanWindow to always be present if provided, even when scanWindow is updated to null -- Integrated basic barcode overlay and scanner overlay into the package. -- Made updateScanWindow private, because logic within the MobileScanner widget is needed in order to pass a correct scanWindow. -The scanWindow can be updated by directly changing the scanWindow in the MobileScanner widget. + +**BREAKING CHANGES:** + +* The `updateScanWindow` method is now private. Instead, update the scan window in the `MobileScanner` widget directly. + +Bugs fixed: +* [Apple] Fixed an issue which caused the scanWindow to always be present, even when reset to no value. + +Improvements: +* Added a basic barcode overlay widget, for use with the camera preview. +* Update the bundled MLKit model for Android to version `17.3.0`. ## 7.0.0-beta.3 -Fix build issues on macOS + +* Fixed a build issue on macOS. ## 7.0.0-beta.2 +Bugs fixed: +* [Apple] Fixed an issue with the zoom slider being non-functional. +* [Apple] Fixed an issue where the flash would briefly show when the camera is turned on. +* [Apple] Fixed an issue that prevented the scan window from working. +* [Apple] Fixed an issue that caused the barcode overlay to use the wrong dimensions. + Improvements: * [iOS] Adds support for Swift Package Manager. -* [Apple] Fixes zoom slider -* Fixed torch at start not working -* Fixed scanWindow not being correct -* Fixed barcode overlay not being correct Known issues: -* BoxFit.cover & BoxFit.fitHeight produces wrong width in barcodeOverlay +* BoxFit.cover & BoxFit.fitHeight produce the wrong width in the barcode overlay. ## 7.0.0-beta.1 -This version replaces MLKit on iOS with Apple's Vision API and merges the iOS and MacOS sources. -The requirement for the minimum iOS version has been relaxed back down to iOS 12.0. +Improvements: +* [iOS] Migrate to the Vision API. +* [iOS] Updated the minimum iOS version back down to 12.0. +* [Apple] Merged the iOS and MacOS sources. -There are still some problems with this build. -* Zoom slider not working -* scanWindow not working -* Flash shows briefly when starting scanner. -* Other issues, not fully tested yet. +Known issues: +* [Apple] The zoom slider does not work correctly. +* [Apple] The scan window does not work correctly. +* [Apple] The camera flash briefly shows when the camera is started. ## 6.0.2 diff --git a/android/build.gradle b/android/build.gradle index 6b7c31872..d9332f267 100644 --- a/android/build.gradle +++ b/android/build.gradle @@ -70,7 +70,7 @@ dependencies { implementation 'com.google.android.gms:play-services-mlkit-barcode-scanning:18.3.1' } else { // Bundled model in app - implementation 'com.google.mlkit:barcode-scanning:17.2.0' + implementation 'com.google.mlkit:barcode-scanning:17.3.0' } // org.jetbrains.kotlin:kotlin-bom artifact purpose is to align kotlin stdlib and related code versions. From 86d216d49b5b0bdc50f45391b8497e27111f109c Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 09:53:21 +0100 Subject: [PATCH 02/20] fix odd image size 1 usage; fix wrong value --- CHANGELOG.md | 1 + .../mobile_scanner/MobileScannerPlugin.swift | 43 ++++++++++--------- 2 files changed, 23 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 704e9ff7e..8bc2d185a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ Bugs fixed: * [Apple] Fixed an issue which caused the scanWindow to always be present, even when reset to no value. +* [Apple] Fixed an issue that caused the barcode size to report the wrong height. Improvements: * Added a basic barcode overlay widget, for use with the camera preview. diff --git a/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index 8cf687521..6904f37ef 100644 --- a/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -177,11 +177,11 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, }) DispatchQueue.main.async { + // If the image is nil, use zero as the size. guard let image = cgImage else { - // Image not known, default image size to 1 self?.sink?([ "name": "barcode", - "data": barcodes.map({ $0.toMap(width: 1, height: 1) }), + "data": barcodes.map({ $0.toMap(imageWidth: 0, imageHeight: 0) }), ]) return } @@ -194,10 +194,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, "height": Double(image.height), ] - self?.sink?([ "name": "barcode", - "data": barcodes.map({ $0.toMap(width: image.width, height: image.height) }), + "data": barcodes.map({ $0.toMap(imageWidth: image.width, imageHeight: image.height) }), "image": imageData, ]) } @@ -473,7 +472,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, device.torchMode = .on device.unlockForConfiguration() } catch(_) { - + // Do nothing. } } @@ -526,7 +525,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, /// Set the zoom factor of the camera func setScaleInternal(_ scale: CGFloat) throws { - if (device == nil) { throw MobileScannerError.zoomWhenStopped } @@ -545,10 +543,8 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, actualScale = min(maxZoomFactor, actualScale) // Limit to 1.0 scale - device.videoZoomFactor = actualScale - device.unlockForConfiguration() #endif } catch { @@ -620,7 +616,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, try device.lockForConfiguration() device.torchMode = newTorchMode device.unlockForConfiguration() - } catch(_) {} + } catch(_) { + // Do nothing. + } result(nil) } @@ -690,7 +688,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, result([ "name": "barcode", - "data": barcodes.map({ $0.toMap(width: 1, height: 1) }), + "data": barcodes.map({ $0.toMap(imageWidth: Int(ciImage.extent.width), imageHeight: Int(ciImage.extent.height)) }), ]) }) @@ -803,15 +801,18 @@ extension VNBarcodeObservation { return sqrt(pow(p1.x - p2.x, 2) + pow(p1.y - p2.y, 2)) } - public func toMap(width: Int, height: Int) -> [String: Any?] { - let topLeftX = topLeft.x * CGFloat(width) - let topRightX = topRight.x * CGFloat(width) - let bottomRightX = bottomRight.x * CGFloat(width) - let bottomLeftX = bottomLeft.x * CGFloat(width) - let topLeftY = (1 - topLeft.y) * CGFloat(height) - let topRightY = (1 - topRight.y) * CGFloat(height) - let bottomRightY = (1 - bottomRight.y) * CGFloat(height) - let bottomLeftY = (1 - bottomLeft.y) * CGFloat(height) + /// Map this `VNBarcodeObservation` to a dictionary. + /// + /// The `imageWidth` and `imageHeight` indicate the width and height of the input image that contains this observation. + public func toMap(imageWidth: Int, imageHeight: Int) -> [String: Any?] { + let topLeftX = topLeft.x * CGFloat(imageWidth) + let topRightX = topRight.x * CGFloat(imageWidth) + let bottomRightX = bottomRight.x * CGFloat(imageWidth) + let bottomLeftX = bottomLeft.x * CGFloat(imageWidth) + let topLeftY = (1 - topLeft.y) * CGFloat(imageHeight) + let topRightY = (1 - topRight.y) * CGFloat(imageHeight) + let bottomRightY = (1 - bottomRight.y) * CGFloat(imageHeight) + let bottomLeftY = (1 - bottomLeft.y) * CGFloat(imageHeight) let data = [ "corners": [ ["x": bottomLeftX, "y": bottomLeftY], @@ -823,8 +824,8 @@ extension VNBarcodeObservation { "rawValue": payloadStringValue ?? "", "displayValue": payloadStringValue ?? "", "size": [ - "width": distanceBetween(topLeft, topRight) * CGFloat(width), - "height": distanceBetween(topLeft, bottomLeft) * CGFloat(width), + "width": distanceBetween(topLeft, topRight) * CGFloat(imageWidth), + "height": distanceBetween(topLeft, bottomLeft) * CGFloat(imageHeight), ], ] as [String : Any] return data From 5dbe5fc8f243c0c89ea1315f47ae436683b774d1 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 09:55:42 +0100 Subject: [PATCH 03/20] remove unused subscription --- example/lib/barcode_scanner_controller.dart | 6 ------ 1 file changed, 6 deletions(-) diff --git a/example/lib/barcode_scanner_controller.dart b/example/lib/barcode_scanner_controller.dart index 51643a45b..7f906c6ca 100644 --- a/example/lib/barcode_scanner_controller.dart +++ b/example/lib/barcode_scanner_controller.dart @@ -21,8 +21,6 @@ class _BarcodeScannerWithControllerState torchEnabled: true, ); - StreamSubscription? _subscription; - @override void initState() { super.initState(); @@ -44,8 +42,6 @@ class _BarcodeScannerWithControllerState case AppLifecycleState.resumed: unawaited(controller.start()); case AppLifecycleState.inactive: - unawaited(_subscription?.cancel()); - _subscription = null; unawaited(controller.stop()); } } @@ -96,8 +92,6 @@ class _BarcodeScannerWithControllerState @override Future dispose() async { WidgetsBinding.instance.removeObserver(this); - unawaited(_subscription?.cancel()); - _subscription = null; super.dispose(); await controller.dispose(); } From d328a6cc550c966655969066be7fbc4ec250ac9c Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 10:07:16 +0100 Subject: [PATCH 04/20] add public api docs lint --- analysis_options.yaml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/analysis_options.yaml b/analysis_options.yaml index abd9f6a85..2f4c5c3cc 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -5,4 +5,5 @@ linter: - combinators_ordering - require_trailing_commas - unnecessary_library_directive - - prefer_single_quotes \ No newline at end of file + - prefer_single_quotes + - public_member_api_docs \ No newline at end of file From cc01485665c252aaf3dce4ff6c55f742bd4d2f8a Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 10:08:20 +0100 Subject: [PATCH 05/20] remove deprecated member --- CHANGELOG.md | 1 + lib/src/enums/encryption_type.dart | 5 ----- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8bc2d185a..97dcdcbe5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ **BREAKING CHANGES:** * The `updateScanWindow` method is now private. Instead, update the scan window in the `MobileScanner` widget directly. +* The deprecated `EncryptionType.none` constant has been removed. Use `EncryptionType.unknown` instead. Bugs fixed: * [Apple] Fixed an issue which caused the scanWindow to always be present, even when reset to no value. diff --git a/lib/src/enums/encryption_type.dart b/lib/src/enums/encryption_type.dart index 65624591b..05db6755c 100644 --- a/lib/src/enums/encryption_type.dart +++ b/lib/src/enums/encryption_type.dart @@ -14,11 +14,6 @@ enum EncryptionType { const EncryptionType(this.rawValue); - @Deprecated( - 'EncryptionType.none is deprecated. Use EncryptionType.unknown instead.', - ) - static const EncryptionType none = EncryptionType.unknown; - factory EncryptionType.fromRawValue(int value) { switch (value) { case 0: From 910d280d0464470fc89f517c07df4e1305892223 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 10:13:58 +0100 Subject: [PATCH 06/20] add comment references lint --- analysis_options.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/analysis_options.yaml b/analysis_options.yaml index 2f4c5c3cc..3e6b29b12 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -3,6 +3,7 @@ include: package:lint/analysis_options_package.yaml linter: rules: - combinators_ordering + - comment_references - require_trailing_commas - unnecessary_library_directive - prefer_single_quotes From 6b400b25d6f712550aaede7fa95db796e1282f44 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 10:18:45 +0100 Subject: [PATCH 07/20] fix doc imports --- lib/src/mobile_scanner_controller.dart | 3 +++ lib/src/mobile_scanner_exception.dart | 4 ++++ lib/src/objects/mobile_scanner_state.dart | 3 +++ lib/src/web/zxing/zxing_browser_multi_format_reader.dart | 3 +++ 4 files changed, 13 insertions(+) diff --git a/lib/src/mobile_scanner_controller.dart b/lib/src/mobile_scanner_controller.dart index 8ea6a7b47..b8d5fdc15 100644 --- a/lib/src/mobile_scanner_controller.dart +++ b/lib/src/mobile_scanner_controller.dart @@ -1,3 +1,6 @@ +/// @docImport 'package:mobile_scanner/src/mobile_scanner.dart'; +library; + import 'dart:async'; import 'package:flutter/widgets.dart'; diff --git a/lib/src/mobile_scanner_exception.dart b/lib/src/mobile_scanner_exception.dart index 52fac113f..d1f1e15cc 100644 --- a/lib/src/mobile_scanner_exception.dart +++ b/lib/src/mobile_scanner_exception.dart @@ -1,3 +1,7 @@ +/// @docImport 'package:flutter/services.dart'; +/// @docImport 'package:mobile_scanner/src/mobile_scanner_controller.dart'; +library; + import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; /// This class represents an exception thrown by the [MobileScannerController]. diff --git a/lib/src/objects/mobile_scanner_state.dart b/lib/src/objects/mobile_scanner_state.dart index f097ebb9c..eef6e18e9 100644 --- a/lib/src/objects/mobile_scanner_state.dart +++ b/lib/src/objects/mobile_scanner_state.dart @@ -1,3 +1,6 @@ +/// @docImport 'package:mobile_scanner/src/mobile_scanner_controller.dart'; +library; + import 'dart:ui'; import 'package:mobile_scanner/src/enums/camera_facing.dart'; diff --git a/lib/src/web/zxing/zxing_browser_multi_format_reader.dart b/lib/src/web/zxing/zxing_browser_multi_format_reader.dart index 292cb6bb8..544a125dd 100644 --- a/lib/src/web/zxing/zxing_browser_multi_format_reader.dart +++ b/lib/src/web/zxing/zxing_browser_multi_format_reader.dart @@ -1,3 +1,6 @@ +/// @docImport 'package:mobile_scanner/src/web/zxing/result.dart'; +library; + import 'dart:js_interop'; import 'package:mobile_scanner/src/web/javascript_map.dart'; From 81903e6fa61e9d8f884ac2a46cdd4a9fcb7373cc Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 10:24:03 +0100 Subject: [PATCH 08/20] fix public API docs --- CHANGELOG.md | 1 + lib/src/method_channel/mobile_scanner_method_channel.dart | 1 + lib/src/mobile_scanner_exception.dart | 2 ++ lib/src/mobile_scanner_view_attributes.dart | 1 + lib/src/objects/start_options.dart | 2 ++ lib/src/web/barcode_reader.dart | 3 +++ lib/src/web/javascript_map.dart | 4 ++++ lib/src/web/mobile_scanner_web.dart | 1 + lib/src/web/zxing/zxing_barcode_reader.dart | 1 + 9 files changed, 16 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 97dcdcbe5..cec90f806 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ Bugs fixed: Improvements: * Added a basic barcode overlay widget, for use with the camera preview. * Update the bundled MLKit model for Android to version `17.3.0`. +* Added documentation in places where it was missing. ## 7.0.0-beta.3 diff --git a/lib/src/method_channel/mobile_scanner_method_channel.dart b/lib/src/method_channel/mobile_scanner_method_channel.dart index 793492bf3..2807f54f1 100644 --- a/lib/src/method_channel/mobile_scanner_method_channel.dart +++ b/lib/src/method_channel/mobile_scanner_method_channel.dart @@ -38,6 +38,7 @@ class MethodChannelMobileScanner extends MobileScannerPlatform { Stream>? _eventsStream; + /// Get the event stream of barcode events that come from the [eventChannel]. Stream> get eventsStream { _eventsStream ??= eventChannel.receiveBroadcastStream().cast>(); diff --git a/lib/src/mobile_scanner_exception.dart b/lib/src/mobile_scanner_exception.dart index d1f1e15cc..6d93bdb3d 100644 --- a/lib/src/mobile_scanner_exception.dart +++ b/lib/src/mobile_scanner_exception.dart @@ -6,6 +6,7 @@ import 'package:mobile_scanner/src/enums/mobile_scanner_error_code.dart'; /// This class represents an exception thrown by the [MobileScannerController]. class MobileScannerException implements Exception { + /// Construct a new [MobileScannerException] instance. const MobileScannerException({ required this.errorCode, this.errorDetails, @@ -28,6 +29,7 @@ class MobileScannerException implements Exception { /// The raw error details for a [MobileScannerException]. class MobileScannerErrorDetails { + /// Construct a new [MobileScannerErrorDetails] instance. const MobileScannerErrorDetails({ this.code, this.details, diff --git a/lib/src/mobile_scanner_view_attributes.dart b/lib/src/mobile_scanner_view_attributes.dart index d203fd2ba..d5211be24 100644 --- a/lib/src/mobile_scanner_view_attributes.dart +++ b/lib/src/mobile_scanner_view_attributes.dart @@ -4,6 +4,7 @@ import 'package:mobile_scanner/src/enums/torch_state.dart'; /// This class defines the attributes for the mobile scanner view. class MobileScannerViewAttributes { + /// Construct a new [MobileScannerViewAttributes] instance. const MobileScannerViewAttributes({ required this.currentTorchMode, this.numberOfCameras, diff --git a/lib/src/objects/start_options.dart b/lib/src/objects/start_options.dart index 71f368d69..6a6321d68 100644 --- a/lib/src/objects/start_options.dart +++ b/lib/src/objects/start_options.dart @@ -6,6 +6,7 @@ import 'package:mobile_scanner/src/enums/detection_speed.dart'; /// This class defines the different start options for the mobile scanner. class StartOptions { + /// Construct a new [StartOptions] instance. const StartOptions({ required this.cameraDirection, required this.cameraResolution, @@ -37,6 +38,7 @@ class StartOptions { /// Whether the torch should be turned on when the scanner starts. final bool torchEnabled; + /// Converts this object to a map. Map toMap() { return { if (cameraResolution != null) diff --git a/lib/src/web/barcode_reader.dart b/lib/src/web/barcode_reader.dart index 42fc894ba..ed53e4ae8 100644 --- a/lib/src/web/barcode_reader.dart +++ b/lib/src/web/barcode_reader.dart @@ -11,6 +11,9 @@ import 'package:web/web.dart'; /// This class represents the base interface for a barcode reader implementation. abstract class BarcodeReader { + /// Construct a new [BarcodeReader] instance. + /// + /// This constructor is const, for subclasses. const BarcodeReader(); /// Whether the scanner is currently scanning for barcodes. diff --git a/lib/src/web/javascript_map.dart b/lib/src/web/javascript_map.dart index 5b4457641..40179d521 100644 --- a/lib/src/web/javascript_map.dart +++ b/lib/src/web/javascript_map.dart @@ -11,8 +11,12 @@ import 'dart:js_interop'; @JS('Map') extension type JSMap._(JSObject _) implements JSObject { + /// Construct a new Javascript `Map`. external factory JSMap(); + /// Get the value for the given [key]. external V? get(K key); + + /// Set the [value] for the given [key]. external JSVoid set(K key, V? value); } diff --git a/lib/src/web/mobile_scanner_web.dart b/lib/src/web/mobile_scanner_web.dart index ba6867e34..348e40468 100644 --- a/lib/src/web/mobile_scanner_web.dart +++ b/lib/src/web/mobile_scanner_web.dart @@ -57,6 +57,7 @@ class MobileScannerWeb extends MobileScannerPlatform { /// Get the view type for the platform view factory. String _getViewType(int textureId) => 'mobile-scanner-view-$textureId'; + /// Registers this class as the default instance of [MobileScannerPlatform]. static void registerWith(Registrar registrar) { MobileScannerPlatform.instance = MobileScannerWeb(); } diff --git a/lib/src/web/zxing/zxing_barcode_reader.dart b/lib/src/web/zxing/zxing_barcode_reader.dart index 96c04a70f..5cc71a3a5 100644 --- a/lib/src/web/zxing/zxing_barcode_reader.dart +++ b/lib/src/web/zxing/zxing_barcode_reader.dart @@ -17,6 +17,7 @@ import 'package:web/web.dart' as web; /// A barcode reader implementation that uses the ZXing library. final class ZXingBarcodeReader extends BarcodeReader { + /// Construct a new [ZXingBarcodeReader] instance. ZXingBarcodeReader(); /// ZXing reports an error with this message if the code could not be detected. From 02353f887f4137cf84bed32e920436bff1029660 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 10:44:50 +0100 Subject: [PATCH 09/20] clean up the barcode painter widget --- CHANGELOG.md | 1 + lib/src/overlay/barcode_painter.dart | 51 +++++++++++++++++++++------- pubspec.yaml | 1 + 3 files changed, 40 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cec90f806..2614a8708 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Improvements: * Added a basic barcode overlay widget, for use with the camera preview. * Update the bundled MLKit model for Android to version `17.3.0`. * Added documentation in places where it was missing. +* Added `color` and `style` properties to the `BarcodePainter` widget. ## 7.0.0-beta.3 diff --git a/lib/src/overlay/barcode_painter.dart b/lib/src/overlay/barcode_painter.dart index 9b24c3d5f..53c05b532 100644 --- a/lib/src/overlay/barcode_painter.dart +++ b/lib/src/overlay/barcode_painter.dart @@ -1,23 +1,44 @@ +import 'package:collection/collection.dart'; import 'package:flutter/material.dart'; +/// This class represents a [CustomPainter] that draws the [barcodeCorners] of a single barcode. class BarcodePainter extends CustomPainter { - BarcodePainter({ + /// Construct a new [BarcodePainter] instance. + const BarcodePainter({ required this.barcodeCorners, required this.barcodeSize, required this.boxFit, required this.cameraPreviewSize, + this.color = const Color(0x4DF44336), + this.style = PaintingStyle.fill, }); + /// The corners of the barcode. final List barcodeCorners; + + /// The size of the barcode. final Size barcodeSize; + + /// The [BoxFit] to use when painting the barcode box. final BoxFit boxFit; + + /// The size of the camera preview, + /// relative to which the [barcodeSize] and [barcodeCorners] are positioned. final Size cameraPreviewSize; + /// The color to use when painting the barcode box. + /// + /// Defaults to [Colors.red], with an opacity of 30%. + final Color color; + + /// The style to use when painting the barcode box. + /// + /// Defaults to [PaintingStyle.fill]. + final PaintingStyle style; + @override void paint(Canvas canvas, Size size) { - if (barcodeCorners.isEmpty || - barcodeSize.isEmpty || - cameraPreviewSize.isEmpty) { + if (barcodeCorners.isEmpty || barcodeSize.isEmpty || cameraPreviewSize.isEmpty) { return; } @@ -37,11 +58,8 @@ class BarcodePainter extends CustomPainter { horizontalPadding = 0; } - final double ratioWidth; - final double ratioHeight; - - ratioWidth = cameraPreviewSize.width / adjustedSize.destination.width; - ratioHeight = cameraPreviewSize.height / adjustedSize.destination.height; + final double ratioWidth = cameraPreviewSize.width / adjustedSize.destination.width; + final double ratioHeight = cameraPreviewSize.height / adjustedSize.destination.height; final List adjustedOffset = [ for (final offset in barcodeCorners) @@ -54,14 +72,21 @@ class BarcodePainter extends CustomPainter { final cutoutPath = Path()..addPolygon(adjustedOffset, true); final backgroundPaint = Paint() - ..color = Colors.red.withOpacity(0.3) - ..style = PaintingStyle.fill; + ..color = color + ..style = style; canvas.drawPath(cutoutPath, backgroundPaint); } @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; + bool shouldRepaint(BarcodePainter oldDelegate) { + const ListEquality listEquality = ListEquality(); + + return listEquality.equals(oldDelegate.barcodeCorners, barcodeCorners) || + oldDelegate.barcodeSize != barcodeSize || + oldDelegate.boxFit != boxFit || + oldDelegate.cameraPreviewSize != cameraPreviewSize || + oldDelegate.color != color || + oldDelegate.style != style; } } diff --git a/pubspec.yaml b/pubspec.yaml index 00648e91c..42d9cce97 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ environment: flutter: ">=3.22.0" dependencies: + collection: ^1.18.0 flutter: sdk: flutter flutter_web_plugins: From 4ae3fd88590fb4cef8b1713c9042ebd2ea944712 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 11:15:24 +0100 Subject: [PATCH 10/20] clean up scan window painter --- lib/src/overlay/scan_window_painter.dart | 43 ++++++++++++++++++++++++ lib/src/overlay/scanner_painter.dart | 31 ----------------- 2 files changed, 43 insertions(+), 31 deletions(-) create mode 100644 lib/src/overlay/scan_window_painter.dart delete mode 100644 lib/src/overlay/scanner_painter.dart diff --git a/lib/src/overlay/scan_window_painter.dart b/lib/src/overlay/scan_window_painter.dart new file mode 100644 index 000000000..bc7ab32ff --- /dev/null +++ b/lib/src/overlay/scan_window_painter.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; + +/// This class represents a [CustomPainter] that draws a [scanWindow] rectangle. +class ScanWindowPainter extends CustomPainter { + /// Construct a new [ScanWindowPainter] instance. + const ScanWindowPainter({ + required this.scanWindow, + this.color = const Color(0x80000000), + }); + + /// The color for the scan window box. + final Color color; + + /// The rectangle defining the scan window. + /// + /// Defaults to [Colors.black] with 50% opacity. + final Rect scanWindow; + + @override + void paint(Canvas canvas, Size size) { + if (scanWindow.isEmpty || scanWindow.isInfinite) { + return; + } + + // Define the main overlay path covering the entire screen + final backgroundPath = Path()..addRect(Offset.zero & size); + + // Define the cutout path in the center + final cutoutPath = Path()..addRect(scanWindow); + + // Combine the two paths: overlay minus the cutout area + final overlayWithCutoutPath = Path.combine(PathOperation.difference, backgroundPath, cutoutPath); + + // Paint the overlay with the cutout + final paint = Paint()..color = Colors.black.withOpacity(0.5); + canvas.drawPath(overlayWithCutoutPath, paint); + } + + @override + bool shouldRepaint(ScanWindowPainter oldDelegate) { + return oldDelegate.scanWindow != scanWindow || oldDelegate.color != color; + } +} diff --git a/lib/src/overlay/scanner_painter.dart b/lib/src/overlay/scanner_painter.dart deleted file mode 100644 index b753f4a1d..000000000 --- a/lib/src/overlay/scanner_painter.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/material.dart'; - -class ScannerPainter extends CustomPainter { - ScannerPainter(this.scanWindow); - - final Rect scanWindow; - - @override - void paint(Canvas canvas, Size size) { - // Define the main overlay path covering the entire screen - final backgroundPath = Path() - ..addRect(Rect.fromLTWH(0, 0, size.width, size.height)); - - // Define the cutout path in the center - final cutoutPath = Path()..addRect(scanWindow); - - // Combine the two paths: overlay minus the cutout area - final overlayWithCutoutPath = - Path.combine(PathOperation.difference, backgroundPath, cutoutPath); - - // Paint the overlay with the cutout - final paint = Paint() - ..color = Colors.black.withOpacity(0.5); // Semi-transparent black - canvas.drawPath(overlayWithCutoutPath, paint); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return false; - } -} From 7d3f4c8c243a22a7782a97ae92c46b7f229be976 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 11:40:27 +0100 Subject: [PATCH 11/20] clean up barcode overlay --- lib/src/overlay/barcode_overlay.dart | 58 ++++++++++++++++------------ lib/src/overlay/barcode_painter.dart | 8 +--- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/lib/src/overlay/barcode_overlay.dart b/lib/src/overlay/barcode_overlay.dart index 9f8d4edcd..52c8b1949 100644 --- a/lib/src/overlay/barcode_overlay.dart +++ b/lib/src/overlay/barcode_overlay.dart @@ -1,16 +1,33 @@ import 'package:flutter/material.dart'; import 'package:mobile_scanner/mobile_scanner.dart'; +/// This widget represents an overlay that paints the bounding boxes of detected barcodes. class BarcodeOverlay extends StatelessWidget { + /// Construct a new [BarcodeOverlay] instance. const BarcodeOverlay({ super.key, - required this.controller, required this.boxFit, + required this.controller, + this.color = const Color(0x4DF44336), + this.style = PaintingStyle.fill, }); - final MobileScannerController controller; + /// The [BoxFit] to use when painting the barcode box. final BoxFit boxFit; + /// The controller that provides the barcodes to display. + final MobileScannerController controller; + + /// The color to use when painting the barcode box. + /// + /// Defaults to [Colors.red], with an opacity of 30%. + final Color color; + + /// The style to use when painting the barcode box. + /// + /// Defaults to [PaintingStyle.fill]. + final PaintingStyle style; + @override Widget build(BuildContext context) { return ValueListenableBuilder( @@ -26,32 +43,25 @@ class BarcodeOverlay extends StatelessWidget { builder: (context, snapshot) { final BarcodeCapture? barcodeCapture = snapshot.data; - // No barcode. - if (barcodeCapture == null || barcodeCapture.barcodes.isEmpty) { + // No barcode or preview size. + if (barcodeCapture == null || barcodeCapture.size.isEmpty || barcodeCapture.barcodes.isEmpty) { return const SizedBox(); } - final overlays = []; - - for (final scannedBarcode in barcodeCapture.barcodes) { - // No barcode corners, or size, or no camera preview size. - if (value.size.isEmpty || - scannedBarcode.size.isEmpty || - scannedBarcode.corners.isEmpty) { - continue; - } - - overlays.add( - CustomPaint( - painter: BarcodePainter( - barcodeCorners: scannedBarcode.corners, - barcodeSize: scannedBarcode.size, - boxFit: boxFit, - cameraPreviewSize: barcodeCapture.size, + final overlays = [ + for (final Barcode barcode in barcodeCapture.barcodes) + if (!barcode.size.isEmpty && barcode.corners.isNotEmpty) + CustomPaint( + painter: BarcodePainter( + barcodeCorners: barcode.corners, + barcodeSize: barcode.size, + boxFit: boxFit, + cameraPreviewSize: barcodeCapture.size, + color: color, + style: style, + ), ), - ), - ); - } + ]; return Stack( fit: StackFit.expand, diff --git a/lib/src/overlay/barcode_painter.dart b/lib/src/overlay/barcode_painter.dart index 53c05b532..f37d57246 100644 --- a/lib/src/overlay/barcode_painter.dart +++ b/lib/src/overlay/barcode_painter.dart @@ -9,8 +9,8 @@ class BarcodePainter extends CustomPainter { required this.barcodeSize, required this.boxFit, required this.cameraPreviewSize, - this.color = const Color(0x4DF44336), - this.style = PaintingStyle.fill, + required this.color, + required this.style, }); /// The corners of the barcode. @@ -27,13 +27,9 @@ class BarcodePainter extends CustomPainter { final Size cameraPreviewSize; /// The color to use when painting the barcode box. - /// - /// Defaults to [Colors.red], with an opacity of 30%. final Color color; /// The style to use when painting the barcode box. - /// - /// Defaults to [PaintingStyle.fill]. final PaintingStyle style; @override From 2fc1290d4c981b1dd2aba6f6968b05e818465084 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 11:50:03 +0100 Subject: [PATCH 12/20] clean up the scan window overlay --- CHANGELOG.md | 1 + lib/mobile_scanner.dart | 4 +- lib/src/overlay/scan_window_overlay.dart | 50 ++++++++++++++++++++++++ lib/src/overlay/scan_window_painter.dart | 8 ++-- lib/src/overlay/scanner_overlay.dart | 35 ----------------- 5 files changed, 56 insertions(+), 42 deletions(-) create mode 100644 lib/src/overlay/scan_window_overlay.dart delete mode 100644 lib/src/overlay/scanner_overlay.dart diff --git a/CHANGELOG.md b/CHANGELOG.md index 2614a8708..3501d953f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ Bugs fixed: Improvements: * Added a basic barcode overlay widget, for use with the camera preview. +* Added a basic scan window overlay widget, for use with the camera preview. * Update the bundled MLKit model for Android to version `17.3.0`. * Added documentation in places where it was missing. * Added `color` and `style` properties to the `BarcodePainter` widget. diff --git a/lib/mobile_scanner.dart b/lib/mobile_scanner.dart index 706984ef9..0473e4c45 100644 --- a/lib/mobile_scanner.dart +++ b/lib/mobile_scanner.dart @@ -29,5 +29,5 @@ export 'src/objects/url_bookmark.dart'; export 'src/objects/wifi.dart'; export 'src/overlay/barcode_overlay.dart'; export 'src/overlay/barcode_painter.dart'; -export 'src/overlay/scanner_overlay.dart'; -export 'src/overlay/scanner_painter.dart'; +export 'src/overlay/scan_window_overlay.dart'; +export 'src/overlay/scan_window_painter.dart'; diff --git a/lib/src/overlay/scan_window_overlay.dart b/lib/src/overlay/scan_window_overlay.dart new file mode 100644 index 000000000..12a7fc2c0 --- /dev/null +++ b/lib/src/overlay/scan_window_overlay.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; +import 'package:mobile_scanner/src/overlay/scan_window_painter.dart'; + +/// This widget represents an overlay that paints a scan window cutout. +class ScanWindowOverlay extends StatelessWidget { + /// Construct a new [ScanWindowOverlay] instance. + const ScanWindowOverlay({ + super.key, + required this.controller, + required this.scanWindow, + this.color = const Color(0x80000000), + }); + + /// The color for the scan window box. + /// + /// Defaults to [Colors.black] with 50% opacity. + final Color color; + + /// The controller that manages the camera preview. + final MobileScannerController controller; + + /// The scan window for the overlay. + final Rect scanWindow; + + @override + Widget build(BuildContext context) { + if (scanWindow.isEmpty || scanWindow.isInfinite) { + return const SizedBox(); + } + + return ValueListenableBuilder( + valueListenable: controller, + builder: (context, value, child) { + // Not ready. + if (!value.isInitialized || !value.isRunning || value.error != null || value.size.isEmpty) { + return const SizedBox(); + } + + return CustomPaint( + size: value.size, + painter: ScanWindowPainter( + scanWindow: scanWindow, + color: color, + ), + ); + }, + ); + } +} diff --git a/lib/src/overlay/scan_window_painter.dart b/lib/src/overlay/scan_window_painter.dart index bc7ab32ff..654540dd9 100644 --- a/lib/src/overlay/scan_window_painter.dart +++ b/lib/src/overlay/scan_window_painter.dart @@ -4,16 +4,14 @@ import 'package:flutter/material.dart'; class ScanWindowPainter extends CustomPainter { /// Construct a new [ScanWindowPainter] instance. const ScanWindowPainter({ + required this.color, required this.scanWindow, - this.color = const Color(0x80000000), }); /// The color for the scan window box. final Color color; - /// The rectangle defining the scan window. - /// - /// Defaults to [Colors.black] with 50% opacity. + /// The rectangle that defines the scan window. final Rect scanWindow; @override @@ -32,7 +30,7 @@ class ScanWindowPainter extends CustomPainter { final overlayWithCutoutPath = Path.combine(PathOperation.difference, backgroundPath, cutoutPath); // Paint the overlay with the cutout - final paint = Paint()..color = Colors.black.withOpacity(0.5); + final paint = Paint()..color = color; canvas.drawPath(overlayWithCutoutPath, paint); } diff --git a/lib/src/overlay/scanner_overlay.dart b/lib/src/overlay/scanner_overlay.dart deleted file mode 100644 index 9a725a749..000000000 --- a/lib/src/overlay/scanner_overlay.dart +++ /dev/null @@ -1,35 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:mobile_scanner/src/mobile_scanner_controller.dart'; -import 'package:mobile_scanner/src/overlay/scanner_painter.dart'; - -class ScannerOverlay extends StatelessWidget { - final MobileScannerController controller; - final Rect scanWindow; - - const ScannerOverlay({ - super.key, - required this.controller, - required this.scanWindow, - }); - - @override - Widget build(BuildContext context) { - return ValueListenableBuilder( - valueListenable: controller, - builder: (context, value, child) { - // Not ready. - if (!value.isInitialized || - !value.isRunning || - value.error != null || - value.size.isEmpty) { - return const SizedBox(); - } - - return CustomPaint( - size: value.size, - painter: ScannerPainter(scanWindow), - ); - }, - ); - } -} From 920ebbe3334087291be7763ca42ab72362730754 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 12:15:26 +0100 Subject: [PATCH 13/20] add support for border radius & border styling to scan window painter --- lib/src/overlay/scan_window_painter.dart | 71 +++++++++++++++++++++--- 1 file changed, 63 insertions(+), 8 deletions(-) diff --git a/lib/src/overlay/scan_window_painter.dart b/lib/src/overlay/scan_window_painter.dart index 654540dd9..bbae884e0 100644 --- a/lib/src/overlay/scan_window_painter.dart +++ b/lib/src/overlay/scan_window_painter.dart @@ -4,10 +4,34 @@ import 'package:flutter/material.dart'; class ScanWindowPainter extends CustomPainter { /// Construct a new [ScanWindowPainter] instance. const ScanWindowPainter({ + required this.borderColor, + required this.borderRadius, + required this.borderStrokeCap, + required this.borderStrokeJoin, + required this.borderStyle, + required this.borderWidth, required this.color, required this.scanWindow, }); + /// The color for the scan window border. + final Color borderColor; + + /// The border radius for the scan window and its border. + final BorderRadius borderRadius; + + /// The stroke cap for the border around the scan window. + final StrokeCap borderStrokeCap; + + /// The stroke join for the border around the scan window. + final StrokeJoin borderStrokeJoin; + + /// The style for the border around the scan window. + final PaintingStyle borderStyle; + + /// The width for the border around the scan window. + final double borderWidth; + /// The color for the scan window box. final Color color; @@ -20,22 +44,53 @@ class ScanWindowPainter extends CustomPainter { return; } - // Define the main overlay path covering the entire screen + // Define the main overlay path covering the entire screen. final backgroundPath = Path()..addRect(Offset.zero & size); - // Define the cutout path in the center - final cutoutPath = Path()..addRect(scanWindow); + // The cutout rect depends on the border radius. + final RRect cutoutRect = borderRadius == BorderRadius.zero + ? RRect.fromRectAndCorners(scanWindow) + : RRect.fromRectAndCorners( + scanWindow, + topLeft: borderRadius.topLeft, + topRight: borderRadius.topRight, + bottomLeft: borderRadius.bottomLeft, + bottomRight: borderRadius.bottomRight, + ); + + // The cutout path is always in the center. + final Path cutoutPath = Path()..addRRect(cutoutRect); // Combine the two paths: overlay minus the cutout area - final overlayWithCutoutPath = Path.combine(PathOperation.difference, backgroundPath, cutoutPath); + final Path overlayWithCutoutPath = Path.combine( + PathOperation.difference, + backgroundPath, + cutoutPath, + ); + + final Paint overlayWithCutoutPaint = Paint() + ..color = color + ..style = PaintingStyle.fill + ..blendMode = BlendMode.dstOver; + + final Paint borderPaint = Paint() + ..color = borderColor + ..style = borderStyle + ..strokeWidth = borderWidth + ..strokeCap = borderStrokeCap + ..strokeJoin = borderStrokeJoin; + + // Paint the overlay with the cutout. + canvas.drawPath(overlayWithCutoutPath, overlayWithCutoutPaint); - // Paint the overlay with the cutout - final paint = Paint()..color = color; - canvas.drawPath(overlayWithCutoutPath, paint); + // Then, draw the border around the cutout area. + canvas.drawRRect(cutoutRect, borderPaint); } @override bool shouldRepaint(ScanWindowPainter oldDelegate) { - return oldDelegate.scanWindow != scanWindow || oldDelegate.color != color; + return oldDelegate.scanWindow != scanWindow || + oldDelegate.color != color || + oldDelegate.borderRadius != borderRadius; } } From 30f4b6a14b6dcd3a5d014a8e2b0d38dbe34ecdad Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 12:20:35 +0100 Subject: [PATCH 14/20] add more customization to scanner overlay --- lib/src/overlay/scan_window_overlay.dart | 42 ++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/lib/src/overlay/scan_window_overlay.dart b/lib/src/overlay/scan_window_overlay.dart index 12a7fc2c0..bd6b32521 100644 --- a/lib/src/overlay/scan_window_overlay.dart +++ b/lib/src/overlay/scan_window_overlay.dart @@ -9,9 +9,45 @@ class ScanWindowOverlay extends StatelessWidget { super.key, required this.controller, required this.scanWindow, + this.borderColor = Colors.white, + this.borderRadius = BorderRadius.zero, + this.borderStrokeCap = StrokeCap.butt, + this.borderStrokeJoin = StrokeJoin.miter, + this.borderStyle = PaintingStyle.stroke, + this.borderWidth = 2.0, this.color = const Color(0x80000000), }); + /// The color for the scan window border. + /// + /// Defaults to [Colors.white]. + final Color borderColor; + + /// The border radius for the scan window and its border. + /// + /// Defaults to [BorderRadius.zero]. + final BorderRadius borderRadius; + + /// The stroke cap for the border around the scan window. + /// + /// Defaults to [StrokeCap.butt]. + final StrokeCap borderStrokeCap; + + /// The stroke join for the border around the scan window. + /// + /// Defaults to [StrokeJoin.miter]. + final StrokeJoin borderStrokeJoin; + + /// The style for the border around the scan window. + /// + /// Defaults to [PaintingStyle.stroke]. + final PaintingStyle borderStyle; + + /// The width for the border around the scan window. + /// + /// Defaults to 2.0. + final double borderWidth; + /// The color for the scan window box. /// /// Defaults to [Colors.black] with 50% opacity. @@ -40,6 +76,12 @@ class ScanWindowOverlay extends StatelessWidget { return CustomPaint( size: value.size, painter: ScanWindowPainter( + borderColor: borderColor, + borderRadius: borderRadius, + borderStrokeCap: borderStrokeCap, + borderStrokeJoin: borderStrokeJoin, + borderStyle: borderStyle, + borderWidth: borderWidth, scanWindow: scanWindow, color: color, ), From f3436854f1fb9c2299ed027d60a2384c23bb3cff Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 12:23:15 +0100 Subject: [PATCH 15/20] use the new widget in the sample --- example/lib/barcode_scanner_window.dart | 2 +- example/lib/mobile_scanner_overlay.dart | 72 ++---------------------- lib/src/overlay/barcode_overlay.dart | 4 +- lib/src/overlay/barcode_painter.dart | 10 +++- lib/src/overlay/scan_window_overlay.dart | 5 +- 5 files changed, 20 insertions(+), 73 deletions(-) diff --git a/example/lib/barcode_scanner_window.dart b/example/lib/barcode_scanner_window.dart index 0f9002228..9ff493adb 100644 --- a/example/lib/barcode_scanner_window.dart +++ b/example/lib/barcode_scanner_window.dart @@ -44,7 +44,7 @@ class _BarcodeScannerWithScanWindowState }, ), BarcodeOverlay(controller: controller, boxFit: boxFit), - ScannerOverlay( + ScanWindowOverlay( scanWindow: scanWindow, controller: controller, ), diff --git a/example/lib/mobile_scanner_overlay.dart b/example/lib/mobile_scanner_overlay.dart index ae188cb35..402736785 100644 --- a/example/lib/mobile_scanner_overlay.dart +++ b/example/lib/mobile_scanner_overlay.dart @@ -57,12 +57,14 @@ class _BarcodeScannerWithOverlayState extends State { builder: (context, value, child) { if (!value.isInitialized || !value.isRunning || - value.error != null) { + value.error != null || + scanWindow.isEmpty) { return const SizedBox(); } - return CustomPaint( - painter: ScannerOverlay(scanWindow: scanWindow), + return ScanWindowOverlay( + controller: controller, + scanWindow: scanWindow, ); }, ), @@ -90,67 +92,3 @@ class _BarcodeScannerWithOverlayState extends State { await controller.dispose(); } } - -class ScannerOverlay extends CustomPainter { - const ScannerOverlay({ - required this.scanWindow, - this.borderRadius = 12.0, - }); - - final Rect scanWindow; - final double borderRadius; - - @override - void paint(Canvas canvas, Size size) { - // TODO: use `Offset.zero & size` instead of Rect.largest - // we need to pass the size to the custom paint widget - final backgroundPath = Path()..addRect(Rect.largest); - - final cutoutPath = Path() - ..addRRect( - RRect.fromRectAndCorners( - scanWindow, - topLeft: Radius.circular(borderRadius), - topRight: Radius.circular(borderRadius), - bottomLeft: Radius.circular(borderRadius), - bottomRight: Radius.circular(borderRadius), - ), - ); - - final backgroundPaint = Paint() - ..color = Colors.black.withOpacity(0.5) - ..style = PaintingStyle.fill - ..blendMode = BlendMode.dstOut; - - final backgroundWithCutout = Path.combine( - PathOperation.difference, - backgroundPath, - cutoutPath, - ); - - final borderPaint = Paint() - ..color = Colors.white - ..style = PaintingStyle.stroke - ..strokeWidth = 4.0; - - final borderRect = RRect.fromRectAndCorners( - scanWindow, - topLeft: Radius.circular(borderRadius), - topRight: Radius.circular(borderRadius), - bottomLeft: Radius.circular(borderRadius), - bottomRight: Radius.circular(borderRadius), - ); - - // First, draw the background, - // with a cutout area that is a bit larger than the scan window. - // Finally, draw the scan window itself. - canvas.drawPath(backgroundWithCutout, backgroundPaint); - canvas.drawRRect(borderRect, borderPaint); - } - - @override - bool shouldRepaint(ScannerOverlay oldDelegate) { - return scanWindow != oldDelegate.scanWindow || - borderRadius != oldDelegate.borderRadius; - } -} diff --git a/lib/src/overlay/barcode_overlay.dart b/lib/src/overlay/barcode_overlay.dart index 52c8b1949..dd4e9ba16 100644 --- a/lib/src/overlay/barcode_overlay.dart +++ b/lib/src/overlay/barcode_overlay.dart @@ -44,7 +44,9 @@ class BarcodeOverlay extends StatelessWidget { final BarcodeCapture? barcodeCapture = snapshot.data; // No barcode or preview size. - if (barcodeCapture == null || barcodeCapture.size.isEmpty || barcodeCapture.barcodes.isEmpty) { + if (barcodeCapture == null || + barcodeCapture.size.isEmpty || + barcodeCapture.barcodes.isEmpty) { return const SizedBox(); } diff --git a/lib/src/overlay/barcode_painter.dart b/lib/src/overlay/barcode_painter.dart index f37d57246..58d3636fa 100644 --- a/lib/src/overlay/barcode_painter.dart +++ b/lib/src/overlay/barcode_painter.dart @@ -34,7 +34,9 @@ class BarcodePainter extends CustomPainter { @override void paint(Canvas canvas, Size size) { - if (barcodeCorners.isEmpty || barcodeSize.isEmpty || cameraPreviewSize.isEmpty) { + if (barcodeCorners.isEmpty || + barcodeSize.isEmpty || + cameraPreviewSize.isEmpty) { return; } @@ -54,8 +56,10 @@ class BarcodePainter extends CustomPainter { horizontalPadding = 0; } - final double ratioWidth = cameraPreviewSize.width / adjustedSize.destination.width; - final double ratioHeight = cameraPreviewSize.height / adjustedSize.destination.height; + final double ratioWidth = + cameraPreviewSize.width / adjustedSize.destination.width; + final double ratioHeight = + cameraPreviewSize.height / adjustedSize.destination.height; final List adjustedOffset = [ for (final offset in barcodeCorners) diff --git a/lib/src/overlay/scan_window_overlay.dart b/lib/src/overlay/scan_window_overlay.dart index bd6b32521..13b2de5fb 100644 --- a/lib/src/overlay/scan_window_overlay.dart +++ b/lib/src/overlay/scan_window_overlay.dart @@ -69,7 +69,10 @@ class ScanWindowOverlay extends StatelessWidget { valueListenable: controller, builder: (context, value, child) { // Not ready. - if (!value.isInitialized || !value.isRunning || value.error != null || value.size.isEmpty) { + if (!value.isInitialized || + !value.isRunning || + value.error != null || + value.size.isEmpty) { return const SizedBox(); } From 8529847233099fc644ec238fb70029edd5c16848 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 12:36:37 +0100 Subject: [PATCH 16/20] use range for collection --- pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pubspec.yaml b/pubspec.yaml index 42d9cce97..e7982c2e4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,7 +20,7 @@ environment: flutter: ">=3.22.0" dependencies: - collection: ^1.18.0 + collection: ">=1.15.0" flutter: sdk: flutter flutter_web_plugins: From 2ac67c27ab9a65f4509315e34517e5d42741747d Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Thu, 7 Nov 2024 12:44:11 +0100 Subject: [PATCH 17/20] bump stable for CI --- .github/workflows/flutter.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/flutter.yml b/.github/workflows/flutter.yml index 748295bfb..0331a4375 100644 --- a/.github/workflows/flutter.yml +++ b/.github/workflows/flutter.yml @@ -36,7 +36,7 @@ jobs: - uses: subosito/flutter-action@v2.12.0 with: cache: true - flutter-version: '3.22' + flutter-version: '3.24' channel: 'stable' - name: Version run: flutter doctor -v From d9c2d1e67c9b8d46c1bb0fea2593ffea0feea8db Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Sat, 9 Nov 2024 15:33:53 +0100 Subject: [PATCH 18/20] fix corner point orientation with VNBarcodeObservation --- CHANGELOG.md | 1 + .../Sources/mobile_scanner/MobileScannerPlugin.swift | 3 ++- lib/src/objects/barcode.dart | 12 +++++++++--- lib/src/web/zxing/result.dart | 2 ++ 4 files changed, 14 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3501d953f..8fd449106 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ Bugs fixed: * [Apple] Fixed an issue which caused the scanWindow to always be present, even when reset to no value. * [Apple] Fixed an issue that caused the barcode size to report the wrong height. +* [Apple] Fixed a bug that caused the corner points to not be returned in clockwise orientation. Improvements: * Added a basic barcode overlay widget, for use with the camera preview. diff --git a/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index 6904f37ef..e8d7c0bae 100644 --- a/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -814,11 +814,12 @@ extension VNBarcodeObservation { let bottomRightY = (1 - bottomRight.y) * CGFloat(imageHeight) let bottomLeftY = (1 - bottomLeft.y) * CGFloat(imageHeight) let data = [ + // Clockwise, starting from the top-left corner. "corners": [ - ["x": bottomLeftX, "y": bottomLeftY], ["x": topLeftX, "y": topLeftY], ["x": topRightX, "y": topRightY], ["x": bottomRightX, "y": bottomRightY], + ["x": bottomLeftX, "y": bottomLeftY], ], "format": symbology.toInt ?? -1, "rawValue": payloadStringValue ?? "", diff --git a/lib/src/objects/barcode.dart b/lib/src/objects/barcode.dart index 907756be7..ea9aa03dc 100644 --- a/lib/src/objects/barcode.dart +++ b/lib/src/objects/barcode.dart @@ -102,10 +102,16 @@ class Barcode { /// The contact information that is embedded in the barcode. final ContactInfo? contactInfo; - /// The four corner points of the barcode, - /// in clockwise order, starting with the top-left point. + /// The corner points of the barcode. /// - /// Due to the possible perspective distortions, this is not necessarily a rectangle. + /// On Android, iOS and MacOS, this is a list of four points, + /// in clockwise direction, starting with the top left. + /// + /// On the web, the amount of points and their order + /// is dependent on the type of barcode that was detected. + /// + /// Due to the possible perspective distortions, + /// the points do not necessarily form a rectangle. /// /// This list is empty if the corners can not be determined. final List corners; diff --git a/lib/src/web/zxing/result.dart b/lib/src/web/zxing/result.dart index dd43b09f1..903b6214c 100644 --- a/lib/src/web/zxing/result.dart +++ b/lib/src/web/zxing/result.dart @@ -75,6 +75,8 @@ extension type Result(JSObject _) implements JSObject { /// Convert this result to a [Barcode]. Barcode get toBarcode { + // The order of the points is dependent on the type of barcode. + // Don't do a manual correction here, but leave it up to the reader implementation. final List corners = resultPoints; return Barcode( From 596c0503b26c657eb927d73a7a69ccad28b3c91e Mon Sep 17 00:00:00 2001 From: Julian Steenbakker Date: Wed, 13 Nov 2024 16:44:45 +0100 Subject: [PATCH 19/20] feat: add regionOfInterest --- .../mobile_scanner/MobileScannerPlugin.swift | 85 +++++++++++++++---- lib/src/overlay/scan_window_painter.dart | 2 +- 2 files changed, 68 insertions(+), 19 deletions(-) diff --git a/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index e8d7c0bae..c0d38f0ad 100644 --- a/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -169,10 +169,10 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, 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?.isBarcodeInsideScanWindow(barcodeObservation: barcode, imageSize: CGSize(width: cgImage!.width, height: cgImage!.height)) ?? false) { - return nil - } - +// if self?.scanWindow != nil && cgImage != nil && !(self?.isBarcodeInsideScanWindow(barcodeObservation: barcode, imageSize: CGSize(width: cgImage!.width, height: cgImage!.height)) ?? false) { +// return nil +// } +// return barcode }) @@ -181,7 +181,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, guard let image = cgImage else { self?.sink?([ "name": "barcode", - "data": barcodes.map({ $0.toMap(imageWidth: 0, imageHeight: 0) }), + "data": barcodes.map({ $0.toMap(imageWidth: 0, imageHeight: 0, scanWindow: nil)}), ]) return } @@ -196,7 +196,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, self?.sink?([ "name": "barcode", - "data": barcodes.map({ $0.toMap(imageWidth: image.width, imageHeight: image.height) }), + "data": barcodes.map({ $0.toMap(imageWidth: image.width, imageHeight: image.height, scanWindow: self?.scanWindow) }), "image": imageData, ]) } @@ -206,6 +206,22 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, // Add the symbologies the user wishes to support. barcodeRequest.symbologies = self!.symbologies } + + // Set the region of interest to match scanWindow +// if let scanWindow = self?.scanWindow { +// barcodeRequest.regionOfInterest = scanWindow +// } + // Set the region of interest to match scanWindow + if let scanWindow = self?.scanWindow { + barcodeRequest.regionOfInterest = scanWindow + } + + +// !(self?.isBarcodeInsideScanWindow(barcodeObservation: barcode, imageSize: CGSize(width: cgImage!.width, height: cgImage!.height)) +// if (self?.scanWindow != nil) { +// barcodeRequest.regionOfInterest = self!.scanWindow! +// } + try imageRequestHandler.perform([barcodeRequest]) } catch let error { @@ -688,7 +704,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, result([ "name": "barcode", - "data": barcodes.map({ $0.toMap(imageWidth: Int(ciImage.extent.width), imageHeight: Int(ciImage.extent.height)) }), + "data": barcodes.map({ $0.toMap(imageWidth: Int(ciImage.extent.width), imageHeight: Int(ciImage.extent.height), scanWindow: self.scanWindow) }), ]) }) @@ -804,15 +820,48 @@ extension VNBarcodeObservation { /// Map this `VNBarcodeObservation` to a dictionary. /// /// The `imageWidth` and `imageHeight` indicate the width and height of the input image that contains this observation. - public func toMap(imageWidth: Int, imageHeight: Int) -> [String: Any?] { - let topLeftX = topLeft.x * CGFloat(imageWidth) - let topRightX = topRight.x * CGFloat(imageWidth) - let bottomRightX = bottomRight.x * CGFloat(imageWidth) - let bottomLeftX = bottomLeft.x * CGFloat(imageWidth) - let topLeftY = (1 - topLeft.y) * CGFloat(imageHeight) - let topRightY = (1 - topRight.y) * CGFloat(imageHeight) - let bottomRightY = (1 - bottomRight.y) * CGFloat(imageHeight) - let bottomLeftY = (1 - bottomLeft.y) * CGFloat(imageHeight) + public func toMap(imageWidth: Int, imageHeight: Int, scanWindow: CGRect?) -> [String: Any?] { + + // Calculate adjusted points based on whether scanWindow is set + let adjustedTopLeft: CGPoint + let adjustedTopRight: CGPoint + let adjustedBottomRight: CGPoint + let adjustedBottomLeft: CGPoint + + if let scanWindow = scanWindow { + // When a scanWindow is set, adjust the barcode coordinates to the full image + func adjustPoint(_ point: CGPoint) -> CGPoint { + let x = scanWindow.minX + point.x * scanWindow.width + let y = scanWindow.minY + point.y * scanWindow.height + return CGPoint(x: x, y: y) + } + + adjustedTopLeft = adjustPoint(topLeft) + adjustedTopRight = adjustPoint(topRight) + adjustedBottomRight = adjustPoint(bottomRight) + adjustedBottomLeft = adjustPoint(bottomLeft) + } else { + // If no scanWindow, use original points (already normalized to the full image) + adjustedTopLeft = topLeft + adjustedTopRight = topRight + adjustedBottomRight = bottomRight + adjustedBottomLeft = bottomLeft + } + + // Convert adjusted points from normalized coordinates to image pixel coordinates + let topLeftX = adjustedTopLeft.x * CGFloat(imageWidth) + let topRightX = adjustedTopRight.x * CGFloat(imageWidth) + let bottomRightX = adjustedBottomRight.x * CGFloat(imageWidth) + let bottomLeftX = adjustedBottomLeft.x * CGFloat(imageWidth) + let topLeftY = (1 - adjustedTopLeft.y) * CGFloat(imageHeight) + let topRightY = (1 - adjustedTopRight.y) * CGFloat(imageHeight) + let bottomRightY = (1 - adjustedBottomRight.y) * CGFloat(imageHeight) + let bottomLeftY = (1 - adjustedBottomLeft.y) * CGFloat(imageHeight) + + // Calculate the width and height of the barcode based on adjusted coordinates + let width = distanceBetween(adjustedTopLeft, adjustedTopRight) * CGFloat(imageWidth) + let height = distanceBetween(adjustedTopLeft, adjustedBottomLeft) * CGFloat(imageHeight) + let data = [ // Clockwise, starting from the top-left corner. "corners": [ @@ -825,8 +874,8 @@ extension VNBarcodeObservation { "rawValue": payloadStringValue ?? "", "displayValue": payloadStringValue ?? "", "size": [ - "width": distanceBetween(topLeft, topRight) * CGFloat(imageWidth), - "height": distanceBetween(topLeft, bottomLeft) * CGFloat(imageHeight), + "width": width, + "height": height, ], ] as [String : Any] return data diff --git a/lib/src/overlay/scan_window_painter.dart b/lib/src/overlay/scan_window_painter.dart index bbae884e0..f10ff0d13 100644 --- a/lib/src/overlay/scan_window_painter.dart +++ b/lib/src/overlay/scan_window_painter.dart @@ -71,7 +71,7 @@ class ScanWindowPainter extends CustomPainter { final Paint overlayWithCutoutPaint = Paint() ..color = color ..style = PaintingStyle.fill - ..blendMode = BlendMode.dstOver; + ..blendMode = BlendMode.srcOver; // android final Paint borderPaint = Paint() ..color = borderColor From c1bb161ddeb4069ba667b70c93e2c2f9042a7a06 Mon Sep 17 00:00:00 2001 From: Navaron Bracke Date: Tue, 26 Nov 2024 16:13:57 +0100 Subject: [PATCH 20/20] remove unsued code block --- .../mobile_scanner/MobileScannerPlugin.swift | 36 +------------------ 1 file changed, 1 insertion(+), 35 deletions(-) diff --git a/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift index c0d38f0ad..9b6d89631 100644 --- a/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift +++ b/darwin/mobile_scanner/Sources/mobile_scanner/MobileScannerPlugin.swift @@ -167,12 +167,7 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, 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?.isBarcodeInsideScanWindow(barcodeObservation: barcode, imageSize: CGSize(width: cgImage!.width, height: cgImage!.height)) ?? false) { -// return nil -// } -// + let barcodes: [VNBarcodeObservation] = results.compactMap({ barcode in return barcode }) @@ -208,20 +203,9 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, } // Set the region of interest to match scanWindow -// if let scanWindow = self?.scanWindow { -// barcodeRequest.regionOfInterest = scanWindow -// } - // Set the region of interest to match scanWindow if let scanWindow = self?.scanWindow { barcodeRequest.regionOfInterest = scanWindow } - - -// !(self?.isBarcodeInsideScanWindow(barcodeObservation: barcode, imageSize: CGSize(width: cgImage!.width, height: cgImage!.height)) -// if (self?.scanWindow != nil) { -// barcodeRequest.regionOfInterest = self!.scanWindow! -// } - try imageRequestHandler.perform([barcodeRequest]) } catch let error { @@ -279,24 +263,6 @@ public class MobileScannerPlugin: NSObject, FlutterPlugin, FlutterStreamHandler, result(nil) } - func isBarcodeInsideScanWindow(barcodeObservation: VNBarcodeObservation, imageSize: CGSize) -> Bool { - let boundingBox = barcodeObservation.boundingBox - - // Adjust boundingBox by inverting the y-axis - let adjustedBoundingBox = CGRect( - x: boundingBox.minX, - y: 1.0 - boundingBox.maxY, - width: boundingBox.width, - height: boundingBox.height - ) - - let intersects = scanWindow!.contains(adjustedBoundingBox) - - // Check if the adjusted bounding box intersects with or is within the scan window - return intersects - } - - private func getVideoOrientation() -> AVCaptureVideoOrientation { #if os(iOS) var videoOrientation: AVCaptureVideoOrientation