Browse Source

working on multi scan improvements

pull/76/head
Khoren Markosyan 2 years ago
parent
commit
d150fc6a12
  1. 4
      .pubignore
  2. 2
      README.md
  3. 108
      example/lib/main.dart
  4. 6
      lib/flutter_zxing.dart
  5. 8
      lib/generated_bindings.dart
  6. 6
      lib/src/logic/camera_stream.dart
  7. 4
      lib/src/models/params.dart
  8. 5
      lib/src/models/position.dart
  9. 49
      lib/src/ui/reader_widget.dart
  10. 26
      lib/src/utils/extentions.dart
  11. 18
      lib/src/utils/isolate_utils.dart
  12. 11
      lib/zxing_mobile.dart
  13. 7
      lib/zxing_web.dart
  14. 7
      src/native_zxing.cpp
  15. 4
      src/native_zxing.h
  16. 2
      zxscanner/pubspec.lock
  17. 2
      zxscanner/pubspec.yaml

4
.pubignore

@ -28,8 +28,10 @@
.packages .packages
build/ build/
# Ignore Example and ZXScanner folders # Ignore ZXScanner folders
zxscanner/ zxscanner/
# Ignore ZXing-CPP unneeded files
src/zxing/* src/zxing/*
!src/zxing/CMakeLists.txt
!src/zxing/core/ !src/zxing/core/

2
README.md

@ -25,7 +25,7 @@ Flutter ZXing is a Flutter plugin for scanning and generating QR codes using the
## Features ## Features
- Scan QR codes and barcodes from the camera stream (on mobile platforms only), image file or URL - 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 - Generate QR codes with customizable content and size
- Return the position points of the scanned barcode - 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 - Customizable scanner frame size and color, and ability to enable or disable features like torch and pinch to zoom

108
example/lib/main.dart

@ -1,10 +1,12 @@
import 'dart:ui';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_zxing/flutter_zxing.dart'; import 'package:flutter_zxing/flutter_zxing.dart';
import 'package:image_picker/image_picker.dart'; import 'package:image_picker/image_picker.dart';
void main() { void main() {
zx.setLogEnabled(kDebugMode); zx.setLogEnabled(false);
runApp(const MyApp()); runApp(const MyApp());
} }
@ -36,6 +38,7 @@ class _DemoPageState extends State<DemoPage> {
Uint8List? createdCodeBytes; Uint8List? createdCodeBytes;
Code? result; Code? result;
List<Code> multiResult = [];
bool showDebugInfo = true; bool showDebugInfo = true;
int successScans = 0; int successScans = 0;
@ -77,8 +80,26 @@ class _DemoPageState extends State<DemoPage> {
ReaderWidget( ReaderWidget(
onScan: _onScanSuccess, onScan: _onScanSuccess,
onScanFailure: _onScanFailure, 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( ScanFromGalleryWidget(
onScan: _onScanSuccess, onScan: _onScanSuccess,
onScanFailure: _onScanFailure, onScanFailure: _onScanFailure,
@ -138,6 +159,23 @@ class _DemoPageState extends State<DemoPage> {
} }
} }
_onMultiScanSuccess(List<Code> codes) {
setState(() {
successScans++;
multiResult = codes;
});
}
_onMultiScanFailure(List<Code> codes) {
setState(() {
failedScans++;
multiResult = codes;
});
if (result?.error?.isNotEmpty == true) {
_showMessage(context, 'Error: ${codes.first.error}');
}
}
_showMessage(BuildContext context, String message) { _showMessage(BuildContext context, String message) {
ScaffoldMessenger.of(context).hideCurrentSnackBar(); ScaffoldMessenger.of(context).hideCurrentSnackBar();
ScaffoldMessenger.of(context).showSnackBar( ScaffoldMessenger.of(context).showSnackBar(
@ -286,9 +324,7 @@ class DebugInfoWidget extends StatelessWidget {
} }
class UnsupportedPlatformWidget extends StatelessWidget { class UnsupportedPlatformWidget extends StatelessWidget {
const UnsupportedPlatformWidget({ const UnsupportedPlatformWidget({Key? key}) : super(key: key);
Key? key,
}) : super(key: key);
@override @override
Widget build(BuildContext context) { 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<Code> 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<Code> 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<Offset> 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<Offset> 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()),
];
}
}

6
lib/flutter_zxing.dart

@ -38,6 +38,12 @@ abstract class Zxing {
DecodeParams? params, DecodeParams? params,
}); });
/// Reads barcodes from the camera
Future<List<Code>> processCameraImageMulti(
CameraImage image, {
DecodeParams? params,
});
/// Reads barcode from String image path /// Reads barcode from String image path
Future<Code?> readBarcodeImagePathString( Future<Code?> readBarcodeImagePathString(
String path, { String path, {

8
lib/generated_bindings.dart

@ -175,6 +175,14 @@ class GeneratedBindings {
/// @brief Pos is a position of a barcode in a image. /// @brief Pos is a position of a barcode in a image.
class Pos extends ffi.Struct { 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 /// < x coordinate of top left corner of barcode
@ffi.Int() @ffi.Int()
external int topLeftX; external int topLeftX;

6
lib/src/logic/camera_stream.dart

@ -11,17 +11,17 @@ Future<void> zxingStartCameraProcessing() async {
/// Stops reading barcode from the camera /// Stops reading barcode from the camera
void zxingStopCameraProcessing() => isolateUtils?.stopReadingBarcode(); void zxingStopCameraProcessing() => isolateUtils?.stopReadingBarcode();
Future<Code> zxingProcessCameraImage( Future<dynamic> zxingProcessCameraImage(
CameraImage image, { CameraImage image, {
DecodeParams? params, DecodeParams? params,
}) async { }) async {
final IsolateData isolateData = IsolateData(image, params ?? DecodeParams()); final IsolateData isolateData = IsolateData(image, params ?? DecodeParams());
final Code result = await _inference(isolateData); final dynamic result = await _inference(isolateData);
return result; return result;
} }
/// Runs inference in another isolate /// Runs inference in another isolate
Future<Code> _inference(IsolateData isolateData) async { Future<dynamic> _inference(IsolateData isolateData) async {
final ReceivePort responsePort = ReceivePort(); final ReceivePort responsePort = ReceivePort();
isolateUtils?.sendPort isolateUtils?.sendPort
?.send(isolateData..responsePort = responsePort.sendPort); ?.send(isolateData..responsePort = responsePort.sendPort);

4
lib/src/models/params.dart

@ -12,6 +12,7 @@ class DecodeParams {
this.tryRotate = true, this.tryRotate = true,
this.tryInverted = false, this.tryInverted = false,
this.maxSize = 512, this.maxSize = 512,
this.isMultiScan = false,
}); });
// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. // 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 // Resize the image to a smaller size before scanning to improve performance
int maxSize; int maxSize;
// Whether to scan multiple barcodes
bool isMultiScan;
} }
// Represents the parameters for encoding a barcode // Represents the parameters for encoding a barcode

5
lib/src/models/position.dart

@ -1,6 +1,8 @@
/// Represents the position of a barcode in an image. /// Represents the position of a barcode in an image.
class Position { class Position {
Position( Position(
this.imageWidth,
this.imageHeight,
this.topLeftX, this.topLeftX,
this.topLeftY, this.topLeftY,
this.topRightX, this.topRightX,
@ -11,6 +13,9 @@ class Position {
this.bottomRightY, 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 topLeftX; // x coordinate of top left corner of barcode
int topLeftY; // y 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 int topRightX; // x coordinate of top right corner of barcode

49
lib/src/ui/reader_widget.dart

@ -11,9 +11,12 @@ import '../../flutter_zxing.dart';
class ReaderWidget extends StatefulWidget { class ReaderWidget extends StatefulWidget {
const ReaderWidget({ const ReaderWidget({
super.key, super.key,
required this.onScan, this.onScan,
this.onScanFailure, this.onScanFailure,
this.onMultiScan,
this.onMultiScanFailure,
this.onControllerCreated, this.onControllerCreated,
this.isMultiScan = false,
this.codeFormat = Format.any, this.codeFormat = Format.any,
this.tryHarder = false, this.tryHarder = false,
this.tryInverted = false, this.tryInverted = false,
@ -30,14 +33,23 @@ class ReaderWidget extends StatefulWidget {
}); });
/// Called when a code is detected /// Called when a code is detected
final Function(Code) onScan; final Function(Code)? onScan;
/// Called when a code is not detected /// Called when a code is not detected
final Function(Code)? onScanFailure; final Function(Code)? onScanFailure;
/// Called when a code is detected
final Function(List<Code>)? onMultiScan;
/// Called when a code is not detected
final Function(List<Code>)? onMultiScanFailure;
/// Called when the camera controller is created /// Called when the camera controller is created
final Function(CameraController?)? onControllerCreated; final Function(CameraController?)? onControllerCreated;
/// Allow multiple scans
final bool isMultiScan;
/// Code format to scan /// Code format to scan
final int codeFormat; final int codeFormat;
@ -207,17 +219,32 @@ class _ReaderWidgetState extends State<ReaderWidget>
cropHeight: cropSize, cropHeight: cropSize,
tryHarder: widget.tryHarder, tryHarder: widget.tryHarder,
tryInverted: widget.tryInverted, tryInverted: widget.tryInverted,
isMultiScan: widget.isMultiScan,
); );
final Code result = await zx.processCameraImage( if (widget.isMultiScan) {
image, final List<Code> results = await zx.processCameraImageMulti(
params: params, image,
); params: params,
if (result.isValid) { );
widget.onScan(result); if (results.isNotEmpty) {
setState(() {}); widget.onMultiScan?.call(results);
await Future<void>.delayed(widget.scanDelaySuccess); await Future<void>.delayed(widget.scanDelaySuccess);
setState(() {});
} else {
widget.onMultiScanFailure?.call(results);
}
} else { } else {
widget.onScanFailure?.call(result); final Code result = await zx.processCameraImage(
image,
params: params,
);
if (result.isValid) {
widget.onScan?.call(result);
setState(() {});
await Future<void>.delayed(widget.scanDelaySuccess);
} else {
widget.onScanFailure?.call(result);
}
} }
} on FileSystemException catch (e) { } on FileSystemException catch (e) {
debugPrint(e.message); debugPrint(e.message);

26
lib/src/utils/extentions.dart

@ -8,18 +8,18 @@ import '../../zxing_mobile.dart';
extension CodeExt on CodeResult { extension CodeExt on CodeResult {
Code toCode() { Code toCode() {
return Code( return Code(
text == nullptr ? null : text.cast<Utf8>().toDartString(), text == nullptr ? null : text.cast<Utf8>().toDartString(),
isValid == 1, isValid == 1,
error == nullptr ? null : error.cast<Utf8>().toDartString(), error == nullptr ? null : error.cast<Utf8>().toDartString(),
bytes == nullptr bytes == nullptr
? null ? null
: Uint8List.fromList(bytes.cast<Int8>().asTypedList(length)), : Uint8List.fromList(bytes.cast<Int8>().asTypedList(length)),
format, format,
pos == nullptr ? null : pos.ref.toPosition(), pos == nullptr ? null : pos.ref.toPosition(),
isInverted == 1, isInverted == 1,
isMirrored == 1, isMirrored == 1,
duration, duration,
); );
} }
} }
@ -38,6 +38,8 @@ extension EncodeExt on EncodeResult {
extension PoeExt on Pos { extension PoeExt on Pos {
Position toPosition() => Position( Position toPosition() => Position(
imageWidth,
imageHeight,
topLeftX, topLeftX,
topLeftY, topLeftY,
topRightX, topRightX,

18
lib/src/utils/isolate_utils.dart

@ -57,14 +57,18 @@ class IsolateUtils {
try { try {
final CameraImage image = isolateData.cameraImage; final CameraImage image = isolateData.cameraImage;
final Uint8List bytes = await convertImage(image); 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( dynamic result;
bytes, if (params.isMultiScan) {
width: image.width, result = zxingReadBarcodes(bytes,
height: image.height, width: width, height: height, params: params);
params: isolateData.params, } else {
); result = zxingReadBarcode(bytes,
width: width, height: height, params: params);
}
isolateData.responsePort?.send(result); isolateData.responsePort?.send(result);
} catch (e) { } catch (e) {
isolateData.responsePort?.send(e); isolateData.responsePort?.send(e);

11
lib/zxing_mobile.dart

@ -42,8 +42,15 @@ class ZxingMobile implements Zxing {
Future<Code> processCameraImage( Future<Code> processCameraImage(
CameraImage image, { CameraImage image, {
DecodeParams? params, DecodeParams? params,
}) => }) async =>
zxingProcessCameraImage(image, params: params); await zxingProcessCameraImage(image, params: params) as Code;
@override
Future<List<Code>> processCameraImageMulti(
CameraImage image, {
DecodeParams? params,
}) async =>
await zxingProcessCameraImage(image, params: params) as List<Code>;
@override @override
Future<Code?> readBarcodeImagePathString( Future<Code?> readBarcodeImagePathString(

7
lib/zxing_web.dart

@ -38,6 +38,13 @@ class ZxingWeb implements Zxing {
}) => }) =>
throw UnimplementedError(); throw UnimplementedError();
@override
Future<List<Code>> processCameraImageMulti(
CameraImage image, {
DecodeParams? params,
}) =>
throw UnimplementedError();
@override @override
Future<Code?> readBarcodeImagePathString( Future<Code?> readBarcodeImagePathString(
String path, { String path, {

7
src/native_zxing.cpp

@ -51,6 +51,8 @@ extern "C"
int evalInMillis = static_cast<int>(get_now() - start); int evalInMillis = static_cast<int>(get_now() - start);
code.duration = evalInMillis; code.duration = evalInMillis;
code.pos->imageWidth = width;
code.pos->imageHeight = height;
platform_log("Read Barcode in: %d ms\n", code.duration); platform_log("Read Barcode in: %d ms\n", code.duration);
return code; return code;
} }
@ -82,14 +84,13 @@ extern "C"
struct CodeResult code; struct CodeResult code;
resultToCodeResult(&code, result); resultToCodeResult(&code, result);
code.duration = evalInMillis; code.duration = evalInMillis;
code.pos->imageWidth = width;
code.pos->imageHeight = height;
codes[i] = code; codes[i] = code;
i++; i++;
} }
delete[] data; delete[] data;
delete[] bytes; delete[] bytes;
return {i, codes}; return {i, codes};
} }

4
src/native_zxing.h

@ -9,6 +9,8 @@ extern "C"
*/ */
struct Pos 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 topLeftX; ///< x coordinate of top left corner of barcode
int topLeftY; ///< y 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 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); struct EncodeResult encodeBarcode(char *contents, int width, int height, int format, int margin, int eccLevel);
// Private functions // Private functions
void resultToCodeResult(struct CodeResult *code, ZXing::Result result); // void resultToCodeResult(struct CodeResult *code, ZXing::Result result);
#ifdef __cplusplus #ifdef __cplusplus
} }

2
zxscanner/pubspec.lock

@ -321,7 +321,7 @@ packages:
name: flutter_zxing name: flutter_zxing
url: "https://pub.dartlang.org" url: "https://pub.dartlang.org"
source: hosted source: hosted
version: "0.10.0" version: "1.0.0-beta.1"
font_awesome_flutter: font_awesome_flutter:
dependency: "direct main" dependency: "direct main"
description: description:

2
zxscanner/pubspec.yaml

@ -17,7 +17,7 @@ dependencies:
sdk: flutter sdk: flutter
flutter_markdown: ^0.6.13 flutter_markdown: ^0.6.13
flutter_mobx: ^2.0.6 flutter_mobx: ^2.0.6
flutter_zxing: ^0.10.0 flutter_zxing: 1.0.0-beta.1
font_awesome_flutter: ^10.3.0 font_awesome_flutter: ^10.3.0
hive: ^2.2.3 hive: ^2.2.3
hive_flutter: ^1.1.0 hive_flutter: ^1.1.0

Loading…
Cancel
Save