diff --git a/.pubignore b/.pubignore index 3b45e37..2c5c965 100644 --- a/.pubignore +++ b/.pubignore @@ -28,8 +28,10 @@ .packages build/ -# Ignore Example and ZXScanner folders +# Ignore ZXScanner folders zxscanner/ +# Ignore ZXing-CPP unneeded files src/zxing/* +!src/zxing/CMakeLists.txt !src/zxing/core/ diff --git a/README.md b/README.md index 6f1068e..342de44 100644 --- a/README.md +++ b/README.md @@ -25,7 +25,7 @@ Flutter ZXing is a Flutter plugin for scanning and generating QR codes using the ## Features - Scan QR codes and barcodes from the camera stream (on mobile platforms only), image file or URL -- Scan multiple barcodes at once from the camera stream (on mobile platforms only), image file or URL +- Scan multiple barcodes at once from the camera stream (on mobile platforms only), image file or URL - Generate QR codes with customizable content and size - Return the position points of the scanned barcode - Customizable scanner frame size and color, and ability to enable or disable features like torch and pinch to zoom diff --git a/example/lib/main.dart b/example/lib/main.dart index 8bae4e1..38b2efc 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,10 +1,12 @@ +import 'dart:ui'; + import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_zxing/flutter_zxing.dart'; import 'package:image_picker/image_picker.dart'; void main() { - zx.setLogEnabled(kDebugMode); + zx.setLogEnabled(false); runApp(const MyApp()); } @@ -36,6 +38,7 @@ class _DemoPageState extends State { Uint8List? createdCodeBytes; Code? result; + List multiResult = []; bool showDebugInfo = true; int successScans = 0; @@ -77,8 +80,26 @@ class _DemoPageState extends State { ReaderWidget( onScan: _onScanSuccess, onScanFailure: _onScanFailure, - tryInverted: true, + onMultiScan: _onMultiScanSuccess, + onMultiScanFailure: _onMultiScanFailure, + isMultiScan: true, + // showScannerOverlay: false, + // scanDelay: const Duration(milliseconds: 0), + // tryInverted: true, ), + // show multi results as rectangles + // if (multiResult.isNotEmpty) + // Positioned.fill( + // child: CustomPaint( + // painter: MultiScanPainter( + // codes: multiResult, + // size: Size( + // MediaQuery.of(context).size.width, + // MediaQuery.of(context).size.height, + // ), + // ), + // ), + // ), ScanFromGalleryWidget( onScan: _onScanSuccess, onScanFailure: _onScanFailure, @@ -138,6 +159,23 @@ class _DemoPageState extends State { } } + _onMultiScanSuccess(List codes) { + setState(() { + successScans++; + multiResult = codes; + }); + } + + _onMultiScanFailure(List codes) { + setState(() { + failedScans++; + multiResult = codes; + }); + if (result?.error?.isNotEmpty == true) { + _showMessage(context, 'Error: ${codes.first.error}'); + } + } + _showMessage(BuildContext context, String message) { ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).showSnackBar( @@ -286,9 +324,7 @@ class DebugInfoWidget extends StatelessWidget { } class UnsupportedPlatformWidget extends StatelessWidget { - const UnsupportedPlatformWidget({ - Key? key, - }) : super(key: key); + const UnsupportedPlatformWidget({Key? key}) : super(key: key); @override Widget build(BuildContext context) { @@ -300,3 +336,65 @@ class UnsupportedPlatformWidget extends StatelessWidget { ); } } + +class MultiResultWidget extends StatelessWidget { + const MultiResultWidget({ + super.key, + this.results = const [], + }); + + final List results; + + @override + Widget build(BuildContext context) { + return Container(); + } +} + +class MultiScanPainter extends CustomPainter { + MultiScanPainter({ + required this.codes, + required this.size, + // required this.scale, + // required this.offset, + }); + + final List codes; + final Size size; + final double scale = 1; + final Offset offset = Offset.zero; + + @override + void paint(Canvas canvas, Size size) { + final Paint paint = Paint() + ..color = Colors.green + ..strokeWidth = 2 + ..style = PaintingStyle.stroke; + + for (final Code code in codes) { + final position = code.position; + if (position == null) { + continue; + } + // position to points + final List points = positionToPoints(position); + // debugPrint('w: ${position.imageWidth} h: ${position.imageHeight}'); + // print(points); + canvas.drawPoints(PointMode.polygon, points, paint); + } + } + + @override + bool shouldRepaint(MultiScanPainter oldDelegate) { + return true; + } + + List positionToPoints(Position pos) { + return [ + Offset(pos.topLeftX.toDouble(), pos.topLeftY.toDouble()), + Offset(pos.topRightX.toDouble(), pos.topRightY.toDouble()), + Offset(pos.bottomRightX.toDouble(), pos.bottomRightY.toDouble()), + Offset(pos.bottomLeftX.toDouble(), pos.bottomLeftY.toDouble()), + ]; + } +} diff --git a/lib/flutter_zxing.dart b/lib/flutter_zxing.dart index 03de081..2178fe4 100644 --- a/lib/flutter_zxing.dart +++ b/lib/flutter_zxing.dart @@ -38,6 +38,12 @@ abstract class Zxing { DecodeParams? params, }); +/// Reads barcodes from the camera + Future> processCameraImageMulti( + CameraImage image, { + DecodeParams? params, + }); + /// Reads barcode from String image path Future readBarcodeImagePathString( String path, { diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart index 19bceb6..6fd4a3a 100644 --- a/lib/generated_bindings.dart +++ b/lib/generated_bindings.dart @@ -175,6 +175,14 @@ class GeneratedBindings { /// @brief Pos is a position of a barcode in a image. class Pos extends ffi.Struct { + /// < The width of the image + @ffi.Int() + external int imageWidth; + + /// < The height of the image + @ffi.Int() + external int imageHeight; + /// < x coordinate of top left corner of barcode @ffi.Int() external int topLeftX; diff --git a/lib/src/logic/camera_stream.dart b/lib/src/logic/camera_stream.dart index 5e4c3bb..e83418a 100644 --- a/lib/src/logic/camera_stream.dart +++ b/lib/src/logic/camera_stream.dart @@ -11,17 +11,17 @@ Future zxingStartCameraProcessing() async { /// Stops reading barcode from the camera void zxingStopCameraProcessing() => isolateUtils?.stopReadingBarcode(); -Future zxingProcessCameraImage( +Future zxingProcessCameraImage( CameraImage image, { DecodeParams? params, }) async { final IsolateData isolateData = IsolateData(image, params ?? DecodeParams()); - final Code result = await _inference(isolateData); + final dynamic result = await _inference(isolateData); return result; } /// Runs inference in another isolate -Future _inference(IsolateData isolateData) async { +Future _inference(IsolateData isolateData) async { final ReceivePort responsePort = ReceivePort(); isolateUtils?.sendPort ?.send(isolateData..responsePort = responsePort.sendPort); diff --git a/lib/src/models/params.dart b/lib/src/models/params.dart index 5616db4..1450084 100644 --- a/lib/src/models/params.dart +++ b/lib/src/models/params.dart @@ -12,6 +12,7 @@ class DecodeParams { this.tryRotate = true, this.tryInverted = false, this.maxSize = 512, + this.isMultiScan = false, }); // Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. @@ -34,6 +35,9 @@ class DecodeParams { // Resize the image to a smaller size before scanning to improve performance int maxSize; + + // Whether to scan multiple barcodes + bool isMultiScan; } // Represents the parameters for encoding a barcode diff --git a/lib/src/models/position.dart b/lib/src/models/position.dart index 7062935..b18afe0 100644 --- a/lib/src/models/position.dart +++ b/lib/src/models/position.dart @@ -1,6 +1,8 @@ /// Represents the position of a barcode in an image. class Position { Position( + this.imageWidth, + this.imageHeight, this.topLeftX, this.topLeftY, this.topRightX, @@ -11,6 +13,9 @@ class Position { this.bottomRightY, ); + int imageWidth; // width of the image + int imageHeight; // height of the image + int topLeftX; // x coordinate of top left corner of barcode int topLeftY; // y coordinate of top left corner of barcode int topRightX; // x coordinate of top right corner of barcode diff --git a/lib/src/ui/reader_widget.dart b/lib/src/ui/reader_widget.dart index 07cc81d..6fb4a01 100644 --- a/lib/src/ui/reader_widget.dart +++ b/lib/src/ui/reader_widget.dart @@ -11,9 +11,12 @@ import '../../flutter_zxing.dart'; class ReaderWidget extends StatefulWidget { const ReaderWidget({ super.key, - required this.onScan, + this.onScan, this.onScanFailure, + this.onMultiScan, + this.onMultiScanFailure, this.onControllerCreated, + this.isMultiScan = false, this.codeFormat = Format.any, this.tryHarder = false, this.tryInverted = false, @@ -30,14 +33,23 @@ class ReaderWidget extends StatefulWidget { }); /// Called when a code is detected - final Function(Code) onScan; + final Function(Code)? onScan; /// Called when a code is not detected final Function(Code)? onScanFailure; + /// Called when a code is detected + final Function(List)? onMultiScan; + + /// Called when a code is not detected + final Function(List)? onMultiScanFailure; + /// Called when the camera controller is created final Function(CameraController?)? onControllerCreated; + /// Allow multiple scans + final bool isMultiScan; + /// Code format to scan final int codeFormat; @@ -207,17 +219,32 @@ class _ReaderWidgetState extends State cropHeight: cropSize, tryHarder: widget.tryHarder, tryInverted: widget.tryInverted, + isMultiScan: widget.isMultiScan, ); - final Code result = await zx.processCameraImage( - image, - params: params, - ); - if (result.isValid) { - widget.onScan(result); - setState(() {}); - await Future.delayed(widget.scanDelaySuccess); + if (widget.isMultiScan) { + final List results = await zx.processCameraImageMulti( + image, + params: params, + ); + if (results.isNotEmpty) { + widget.onMultiScan?.call(results); + await Future.delayed(widget.scanDelaySuccess); + setState(() {}); + } else { + widget.onMultiScanFailure?.call(results); + } } else { - widget.onScanFailure?.call(result); + final Code result = await zx.processCameraImage( + image, + params: params, + ); + if (result.isValid) { + widget.onScan?.call(result); + setState(() {}); + await Future.delayed(widget.scanDelaySuccess); + } else { + widget.onScanFailure?.call(result); + } } } on FileSystemException catch (e) { debugPrint(e.message); diff --git a/lib/src/utils/extentions.dart b/lib/src/utils/extentions.dart index 930168d..09865dc 100644 --- a/lib/src/utils/extentions.dart +++ b/lib/src/utils/extentions.dart @@ -8,18 +8,18 @@ import '../../zxing_mobile.dart'; extension CodeExt on CodeResult { Code toCode() { return Code( - text == nullptr ? null : text.cast().toDartString(), - isValid == 1, - error == nullptr ? null : error.cast().toDartString(), - bytes == nullptr - ? null - : Uint8List.fromList(bytes.cast().asTypedList(length)), - format, - pos == nullptr ? null : pos.ref.toPosition(), - isInverted == 1, - isMirrored == 1, - duration, - ); + text == nullptr ? null : text.cast().toDartString(), + isValid == 1, + error == nullptr ? null : error.cast().toDartString(), + bytes == nullptr + ? null + : Uint8List.fromList(bytes.cast().asTypedList(length)), + format, + pos == nullptr ? null : pos.ref.toPosition(), + isInverted == 1, + isMirrored == 1, + duration, + ); } } @@ -38,6 +38,8 @@ extension EncodeExt on EncodeResult { extension PoeExt on Pos { Position toPosition() => Position( + imageWidth, + imageHeight, topLeftX, topLeftY, topRightX, diff --git a/lib/src/utils/isolate_utils.dart b/lib/src/utils/isolate_utils.dart index 3063bad..d7d56ca 100644 --- a/lib/src/utils/isolate_utils.dart +++ b/lib/src/utils/isolate_utils.dart @@ -57,14 +57,18 @@ class IsolateUtils { try { final CameraImage image = isolateData.cameraImage; final Uint8List bytes = await convertImage(image); + final int width = image.width; + final int height = image.height; + final DecodeParams params = isolateData.params; - final Code result = zxingReadBarcode( - bytes, - width: image.width, - height: image.height, - params: isolateData.params, - ); - + dynamic result; + if (params.isMultiScan) { + result = zxingReadBarcodes(bytes, + width: width, height: height, params: params); + } else { + result = zxingReadBarcode(bytes, + width: width, height: height, params: params); + } isolateData.responsePort?.send(result); } catch (e) { isolateData.responsePort?.send(e); diff --git a/lib/zxing_mobile.dart b/lib/zxing_mobile.dart index 6d7677f..69fcdbb 100644 --- a/lib/zxing_mobile.dart +++ b/lib/zxing_mobile.dart @@ -42,8 +42,15 @@ class ZxingMobile implements Zxing { Future processCameraImage( CameraImage image, { DecodeParams? params, - }) => - zxingProcessCameraImage(image, params: params); + }) async => + await zxingProcessCameraImage(image, params: params) as Code; + + @override + Future> processCameraImageMulti( + CameraImage image, { + DecodeParams? params, + }) async => + await zxingProcessCameraImage(image, params: params) as List; @override Future readBarcodeImagePathString( diff --git a/lib/zxing_web.dart b/lib/zxing_web.dart index aa79843..edace0f 100644 --- a/lib/zxing_web.dart +++ b/lib/zxing_web.dart @@ -38,6 +38,13 @@ class ZxingWeb implements Zxing { }) => throw UnimplementedError(); + @override + Future> processCameraImageMulti( + CameraImage image, { + DecodeParams? params, + }) => + throw UnimplementedError(); + @override Future readBarcodeImagePathString( String path, { diff --git a/src/native_zxing.cpp b/src/native_zxing.cpp index f813565..50d9e48 100644 --- a/src/native_zxing.cpp +++ b/src/native_zxing.cpp @@ -51,6 +51,8 @@ extern "C" int evalInMillis = static_cast(get_now() - start); code.duration = evalInMillis; + code.pos->imageWidth = width; + code.pos->imageHeight = height; platform_log("Read Barcode in: %d ms\n", code.duration); return code; } @@ -82,14 +84,13 @@ extern "C" struct CodeResult code; resultToCodeResult(&code, result); code.duration = evalInMillis; + code.pos->imageWidth = width; + code.pos->imageHeight = height; codes[i] = code; i++; } - delete[] data; delete[] bytes; - - return {i, codes}; } diff --git a/src/native_zxing.h b/src/native_zxing.h index df0bffa..db5a508 100644 --- a/src/native_zxing.h +++ b/src/native_zxing.h @@ -9,6 +9,8 @@ extern "C" */ struct Pos { + int imageWidth; ///< The width of the image + int imageHeight; ///< The height of the image int topLeftX; ///< x coordinate of top left corner of barcode int topLeftY; ///< y coordinate of top left corner of barcode int topRightX; ///< x coordinate of top right corner of barcode @@ -115,7 +117,7 @@ extern "C" struct EncodeResult encodeBarcode(char *contents, int width, int height, int format, int margin, int eccLevel); // Private functions - void resultToCodeResult(struct CodeResult *code, ZXing::Result result); + // void resultToCodeResult(struct CodeResult *code, ZXing::Result result); #ifdef __cplusplus } diff --git a/zxscanner/pubspec.lock b/zxscanner/pubspec.lock index 4092141..9badee0 100644 --- a/zxscanner/pubspec.lock +++ b/zxscanner/pubspec.lock @@ -321,7 +321,7 @@ packages: name: flutter_zxing url: "https://pub.dartlang.org" source: hosted - version: "0.10.0" + version: "1.0.0-beta.1" font_awesome_flutter: dependency: "direct main" description: diff --git a/zxscanner/pubspec.yaml b/zxscanner/pubspec.yaml index d483f1d..112d7ef 100644 --- a/zxscanner/pubspec.yaml +++ b/zxscanner/pubspec.yaml @@ -17,7 +17,7 @@ dependencies: sdk: flutter flutter_markdown: ^0.6.13 flutter_mobx: ^2.0.6 - flutter_zxing: ^0.10.0 + flutter_zxing: 1.0.0-beta.1 font_awesome_flutter: ^10.3.0 hive: ^2.2.3 hive_flutter: ^1.1.0