From 5ed7f071a01e896f953906034b96166b2d7ac3b3 Mon Sep 17 00:00:00 2001 From: Khoren Markosyan Date: Mon, 9 Jan 2023 15:56:03 +0400 Subject: [PATCH] added isInverted, isMirrored and duration to Code, image now resized before decoding --- example/lib/main.dart | 59 ++++++------ ios/Classes/src/common.cpp | 41 -------- ios/Classes/src/common.h | 26 ----- ios/Classes/src/native_zxing.cpp | 146 ----------------------------- ios/Classes/src/native_zxing.h | 119 ----------------------- lib/generated_bindings.dart | 31 ++++-- lib/src/logic/barcode_reader.dart | 18 ++-- lib/src/logic/barcodes_reader.dart | 15 ++- lib/src/logic/zxing.dart | 1 + lib/src/models/code.dart | 12 ++- lib/src/models/params.dart | 4 + lib/src/ui/reader_widget.dart | 4 +- lib/src/utils/extentions.dart | 10 +- lib/src/utils/image_converter.dart | 13 +++ src/native_zxing.cpp | 52 +++++----- src/native_zxing.h | 11 ++- 16 files changed, 143 insertions(+), 419 deletions(-) delete mode 100644 ios/Classes/src/common.cpp delete mode 100644 ios/Classes/src/common.h delete mode 100644 ios/Classes/src/native_zxing.cpp delete mode 100644 ios/Classes/src/native_zxing.h diff --git a/example/lib/main.dart b/example/lib/main.dart index e808d44..8bae4e1 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -4,7 +4,7 @@ import 'package:flutter_zxing/flutter_zxing.dart'; import 'package:image_picker/image_picker.dart'; void main() { - zx.setLogEnabled(!kDebugMode); + zx.setLogEnabled(kDebugMode); runApp(const MyApp()); } @@ -66,15 +66,9 @@ class _DemoPageState extends State { const Center( child: Text('Camera not supported on this platform'), ) - else if (result != null) + else if (result != null && result?.isValid == true) ScanResultWidget( - result: result?.text, - format: result?.format?.name, - onScanAgain: () => setState(() => result = null), - ) - else if (result != null) - ScanResultWidget( - result: result?.text, + result: result, onScanAgain: () => setState(() => result = null), ) else @@ -82,7 +76,7 @@ class _DemoPageState extends State { children: [ ReaderWidget( onScan: _onScanSuccess, - onScanFailure: () => _onScanFailure(null), + onScanFailure: _onScanFailure, tryInverted: true, ), ScanFromGalleryWidget( @@ -93,6 +87,8 @@ class _DemoPageState extends State { DebugInfoWidget( successScans: successScans, failedScans: failedScans, + error: result?.error, + duration: result?.duration ?? 0, onReset: _onReset, ), ], @@ -125,19 +121,20 @@ class _DemoPageState extends State { ); } - _onScanSuccess(value) { + _onScanSuccess(Code? code) { setState(() { successScans++; - result = value; + result = code; }); } - _onScanFailure(String? error) { + _onScanFailure(Code? code) { setState(() { failedScans++; + result = code; }); - if (error != null) { - _showMessage(context, error); + if (code?.error?.isNotEmpty == true) { + _showMessage(context, 'Error: ${code?.error}'); } } @@ -160,12 +157,10 @@ class ScanResultWidget extends StatelessWidget { const ScanResultWidget({ Key? key, this.result, - this.format, this.onScanAgain, }) : super(key: key); - final String? result; - final String? format; + final Code? result; final Function()? onScanAgain; @override @@ -177,15 +172,20 @@ class ScanResultWidget extends StatelessWidget { mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - format ?? '', + result?.format?.name ?? '', style: Theme.of(context).textTheme.headline5, ), const SizedBox(height: 20), Text( - result ?? '', + result?.text ?? '', style: Theme.of(context).textTheme.headline6, ), - const SizedBox(height: 30), + const SizedBox(height: 20), + Text( + 'Inverted: ${result?.isInverted}\t\tMirrored: ${result?.isMirrored}', + style: Theme.of(context).textTheme.bodyText2, + ), + const SizedBox(height: 40), ElevatedButton( onPressed: onScanAgain, child: const Text('Scan Again'), @@ -204,8 +204,8 @@ class ScanFromGalleryWidget extends StatelessWidget { this.onScanFailure, }) : super(key: key); - final Function(Code?)? onScan; - final Function(String)? onScanFailure; + final Function(Code)? onScan; + final Function(Code?)? onScanFailure; @override Widget build(BuildContext context) { @@ -230,7 +230,8 @@ class ScanFromGalleryWidget extends StatelessWidget { if (result != null && result.isValid) { onScan?.call(result); } else { - onScanFailure?.call('Failed to read barcode from image'); + result?.error = 'No barcode found'; + onScanFailure?.call(result); } } } @@ -241,18 +242,22 @@ class DebugInfoWidget extends StatelessWidget { Key? key, required this.successScans, required this.failedScans, + this.error, + this.duration = 0, this.onReset, }) : super(key: key); final int successScans; final int failedScans; + final String? error; + final int duration; final Function()? onReset; @override Widget build(BuildContext context) { return Align( - alignment: Alignment.topCenter, + alignment: Alignment.topLeft, child: Padding( padding: const EdgeInsets.all(8.0), child: ClipRRect( @@ -264,8 +269,8 @@ class DebugInfoWidget extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ Text( - 'Success: $successScans\nFailed: $failedScans', - style: Theme.of(context).textTheme.headline6, + 'Success: $successScans\nFailed: $failedScans\nDuration: $duration ms', + style: Theme.of(context).textTheme.bodySmall, ), TextButton( onPressed: onReset, diff --git a/ios/Classes/src/common.cpp b/ios/Classes/src/common.cpp deleted file mode 100644 index 4fffec9..0000000 --- a/ios/Classes/src/common.cpp +++ /dev/null @@ -1,41 +0,0 @@ -#include "common.h" -#include -#include -#include - -using namespace std; - -bool isLogEnabled; - -void setLoggingEnabled(bool enabled) -{ - isLogEnabled = enabled; -} - -long long int get_now() -{ - return chrono::duration_cast( - chrono::system_clock::now().time_since_epoch()) - .count(); -} - -void platform_log(const char *fmt, ...) -{ - if (isLogEnabled) - { - va_list args; - va_start(args, fmt); -#ifdef __ANDROID__ - __android_log_vprint(ANDROID_LOG_VERBOSE, "ndk", fmt, args); -#elif defined(IS_WIN32) - char *buf = new char[4096]; - std::fill_n(buf, 4096, '\0'); - _vsprintf_p(buf, 4096, fmt, args); - OutputDebugStringA(buf); - delete[] buf; -#else - vprintf(fmt, args); -#endif - va_end(args); - } -} \ No newline at end of file diff --git a/ios/Classes/src/common.h b/ios/Classes/src/common.h deleted file mode 100644 index a4cb7b8..0000000 --- a/ios/Classes/src/common.h +++ /dev/null @@ -1,26 +0,0 @@ - -#if defined(WIN32) || defined(_WIN32) || defined(__WIN32) -#define IS_WIN32 -#endif - -#ifdef __ANDROID__ -#include -#endif - -#ifdef IS_WIN32 -#include -#endif - -#if defined(__GNUC__) -// Attributes to prevent 'unused' function from being removed and to make it visible -#define FUNCTION_ATTRIBUTE __attribute__((visibility("default"))) __attribute__((used)) -#elif defined(_MSC_VER) -// Marking a function for export -#define FUNCTION_ATTRIBUTE __declspec(dllexport) -#endif - -long long int get_now(); - -void platform_log(const char *fmt, ...); - -void setLoggingEnabled(bool enabled); \ No newline at end of file diff --git a/ios/Classes/src/native_zxing.cpp b/ios/Classes/src/native_zxing.cpp deleted file mode 100644 index 03fea67..0000000 --- a/ios/Classes/src/native_zxing.cpp +++ /dev/null @@ -1,146 +0,0 @@ -#include "common.h" -#include "ReadBarcode.h" -#include "MultiFormatWriter.h" -#include "TextUtfEncoding.h" -#include "BitMatrix.h" -#include "native_zxing.h" - -#include -#include -#include - -using namespace ZXing; -using namespace std; - -extern "C" -{ - FUNCTION_ATTRIBUTE - void setLogEnabled(int enable) - { - setLoggingEnabled(enable); - } - - FUNCTION_ATTRIBUTE - char const *version() - { - return "1.4.0"; - } - - FUNCTION_ATTRIBUTE - struct CodeResult readBarcode(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate) - { - long long start = get_now(); - - long length = width * height; - auto *data = new uint8_t[length]; - memcpy(data, bytes, length); - - ImageView image{data, width, height, ImageFormat::Lum}; - if (cropWidth > 0 && cropHeight > 0 && cropWidth < width && cropHeight < height) - { - image = image.cropped(width / 2 - cropWidth / 2, height / 2 - cropHeight / 2, cropWidth, cropHeight); - } - DecodeHints hints = DecodeHints().setTryHarder(tryHarder).setTryRotate(tryRotate).setFormats(BarcodeFormat(format)); - Result result = ReadBarcode(image, hints); - - struct CodeResult code = {false, nullptr}; - if (result.isValid()) - { - resultToCodeResult(&code, result); - } - - delete[] data; - delete[] bytes; - - int evalInMillis = static_cast(get_now() - start); - platform_log("Read Barcode in: %d ms\n", evalInMillis); - return code; - } - - FUNCTION_ATTRIBUTE - struct CodeResults readBarcodes(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate) - { - long long start = get_now(); - - long length = width * height; - auto *data = new uint8_t[length]; - memcpy(data, bytes, length); - - ImageView image{data, width, height, ImageFormat::Lum}; - if (cropWidth > 0 && cropHeight > 0 && cropWidth < width && cropHeight < height) - { - image = image.cropped(width / 2 - cropWidth / 2, height / 2 - cropHeight / 2, cropWidth, cropHeight); - } - DecodeHints hints = DecodeHints().setTryHarder(tryHarder).setTryRotate(tryRotate).setFormats(BarcodeFormat(format)); - Results results = ReadBarcodes(image, hints); - - auto *codes = new struct CodeResult[results.size()]; - int i = 0; - for (auto &result : results) - { - struct CodeResult code = {false, nullptr}; - if (result.isValid()) - { - resultToCodeResult(&code, result); - codes[i] = code; - i++; - } - } - - delete[] data; - delete[] bytes; - - int evalInMillis = static_cast(get_now() - start); - platform_log("Read Barcode in: %d ms\n", evalInMillis); - return {i, codes}; - } - - FUNCTION_ATTRIBUTE - struct EncodeResult encodeBarcode(char *contents, int width, int height, int format, int margin, int eccLevel) - { - long long start = get_now(); - - struct EncodeResult result = {0, contents, format, nullptr, 0, nullptr}; - try - { - auto writer = MultiFormatWriter(BarcodeFormat(format)).setMargin(margin).setEccLevel(eccLevel).setEncoding(CharacterSet::UTF8); - auto bitMatrix = writer.encode(TextUtfEncoding::FromUtf8(string(contents)), width, height); - result.data = ToMatrix(bitMatrix).data(); - result.length = bitMatrix.width() * bitMatrix.height(); - result.isValid = true; - } - catch (const exception &e) - { - platform_log("Can't encode text: %s\nError: %s\n", contents, e.what()); - result.error = new char[strlen(e.what()) + 1]; - strcpy(result.error, e.what()); - } - - int evalInMillis = static_cast(get_now() - start); - platform_log("Encode Barcode in: %d ms\n", evalInMillis); - return result; - } - - FUNCTION_ATTRIBUTE - void resultToCodeResult(struct CodeResult *code, Result result) - { - code->isValid = result.isValid(); - - code->format = static_cast(result.format()); - - code->bytes = result.bytes().data(); - code->length = result.bytes().size(); - - string text = result.text(); - code->text = new char[text.length() + 1]; - strcpy(code->text, text.c_str()); - - auto p = result.position(); - auto tl = p.topLeft(); - auto tr = p.topRight(); - auto bl = p.bottomLeft(); - auto br = p.bottomRight(); - code->pos = new Pos{tl.x, tl.y, tr.x, tr.y, bl.x, bl.y, br.x, br.y}; - platform_log("Result: %s\n", code->text); - } -} diff --git a/ios/Classes/src/native_zxing.h b/ios/Classes/src/native_zxing.h deleted file mode 100644 index 815753e..0000000 --- a/ios/Classes/src/native_zxing.h +++ /dev/null @@ -1,119 +0,0 @@ -#ifdef __cplusplus -extern "C" -{ -#endif - - /** - * @brief Pos is a position of a barcode in a image. - * - */ - struct Pos - { - 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 - int topRightY; ///< y coordinate of top right corner of barcode - int bottomLeftX; ///< x coordinate of bottom left corner of barcode - int bottomLeftY; ///< y coordinate of bottom left corner of barcode - int bottomRightX; ///< x coordinate of bottom right corner of barcode - int bottomRightY; ///< y coordinate of bottom right corner of barcode - }; - - /** - * @brief The CodeResult class encapsulates the result of decoding a barcode within an image. - */ - struct CodeResult - { - int isValid; ///< Whether the barcode was successfully decoded - char *text; ///< The decoded text - const unsigned char *bytes; ///< The bytes is the raw / standard content without any modifications like character set conversions - int length; ///< The length of the bytes - int format; ///< The format of the barcode - struct Pos *pos; ///< The position of the barcode within the image - }; - - /** - * @brief The CodeResults class encapsulates the result of decoding multiple barcodes within an image. - */ - struct CodeResults - { - int count; ///< The number of barcodes detected - struct CodeResult *results; ///< The results of the barcode decoding - }; - - /** - * @brief EncodeResult encapsulates the result of encoding a barcode. - * - */ - struct EncodeResult - { - int isValid; ///< Whether the barcode was successfully encoded - char *text; ///< The encoded text - int format; ///< The format of the barcode - const signed char *data; ///< The encoded data - int length; ///< The length of the encoded data - char *error; ///< The error message - }; - - /** - * @brief Enables or disables the logging of the library. - * @param enable Whether to enable or disable the logging. - * - * @param enabled - */ - void setLogEnabled(int enable); - - /** - * Returns the version of the zxing-cpp library. - * - * @return The version of the zxing-cpp library. - */ - char const *version(); - - /** - * @brief Read barcode from image bytes. - * @param bytes Image bytes. - * @param format Specify a set of BarcodeFormats that should be searched for. - * @param width Image width in pixels. - * @param height Image height in pixels. - * @param cropWidth Crop width. - * @param cropHeight Crop height. - * @param tryHarder Spend more time to try to find a barcode; optimize for accuracy, not speed. - * @param tryRotate Also try detecting code in 90, 180 and 270 degree rotated images. - * @return The barcode result. - */ - struct CodeResult readBarcode(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate); - - /** - * @brief Read barcodes from image bytes. - * @param bytes Image bytes. - * @param format Specify a set of BarcodeFormats that should be searched for. - * @param width Image width in pixels. - * @param height Image height in pixels. - * @param cropWidth Crop width. - * @param cropHeight Crop height. - * @param tryHarder Spend more time to try to find a barcode, optimize for accuracy, not speed. - * @param tryRotate Also try detecting code in 90, 180 and 270 degree rotated images. - * @return The barcode results. - */ - struct CodeResults readBarcodes(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate); - - /** - * @brief Encode a string into a barcode - * @param contents The string to encode - * @param width The width of the barcode in pixels. - * @param height The height of the barcode in pixels. - * @param format The format of the barcode - * @param margin The margin of the barcode - * @param eccLevel The error correction level of the barcode. Used for Aztec, PDF417, and QRCode only, [0-8]. - * @return The barcode data - */ - 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); - -#ifdef __cplusplus -} -#endif \ No newline at end of file diff --git a/lib/generated_bindings.dart b/lib/generated_bindings.dart index 83abb25..19bceb6 100644 --- a/lib/generated_bindings.dart +++ b/lib/generated_bindings.dart @@ -74,6 +74,7 @@ class GeneratedBindings { int cropHeight, int tryHarder, int tryRotate, + int tryInvert, ) { return _readBarcode( bytes, @@ -84,16 +85,17 @@ class GeneratedBindings { cropHeight, tryHarder, tryRotate, + tryInvert, ); } late final _readBarcodePtr = _lookup< ffi.NativeFunction< CodeResult Function(ffi.Pointer, ffi.Int, ffi.Int, ffi.Int, - ffi.Int, ffi.Int, ffi.Int, ffi.Int)>>('readBarcode'); + ffi.Int, ffi.Int, ffi.Int, ffi.Int, ffi.Int)>>('readBarcode'); late final _readBarcode = _readBarcodePtr.asFunction< CodeResult Function( - ffi.Pointer, int, int, int, int, int, int, int)>(); + ffi.Pointer, int, int, int, int, int, int, int, int)>(); /// @brief Read barcodes from image bytes. /// @param bytes Image bytes. @@ -114,6 +116,7 @@ class GeneratedBindings { int cropHeight, int tryHarder, int tryRotate, + int tryInvert, ) { return _readBarcodes( bytes, @@ -124,16 +127,17 @@ class GeneratedBindings { cropHeight, tryHarder, tryRotate, + tryInvert, ); } late final _readBarcodesPtr = _lookup< ffi.NativeFunction< CodeResults Function(ffi.Pointer, ffi.Int, ffi.Int, ffi.Int, - ffi.Int, ffi.Int, ffi.Int, ffi.Int)>>('readBarcodes'); + ffi.Int, ffi.Int, ffi.Int, ffi.Int, ffi.Int)>>('readBarcodes'); late final _readBarcodes = _readBarcodesPtr.asFunction< CodeResults Function( - ffi.Pointer, int, int, int, int, int, int, int)>(); + ffi.Pointer, int, int, int, int, int, int, int, int)>(); /// @brief Encode a string into a barcode /// @param contents The string to encode @@ -206,12 +210,15 @@ class Pos extends ffi.Struct { /// @brief The CodeResult class encapsulates the result of decoding a barcode within an image. class CodeResult extends ffi.Struct { + /// < The decoded text + external ffi.Pointer text; + /// < Whether the barcode was successfully decoded @ffi.Int() external int isValid; - /// < The decoded text - external ffi.Pointer text; + /// < The error message + external ffi.Pointer error; /// < The bytes is the raw / standard content without any modifications like character set conversions external ffi.Pointer bytes; @@ -226,6 +233,18 @@ class CodeResult extends ffi.Struct { /// < The position of the barcode within the image external ffi.Pointer pos; + + /// < Whether the barcode was inverted + @ffi.Int() + external int isInverted; + + /// < Whether the barcode was mirrored + @ffi.Int() + external int isMirrored; + + /// < The duration of the decoding in milliseconds + @ffi.Int() + external int duration; } /// @brief The CodeResults class encapsulates the result of decoding multiple barcodes within an image. diff --git a/lib/src/logic/barcode_reader.dart b/lib/src/logic/barcode_reader.dart index 735e4fd..00211a0 100644 --- a/lib/src/logic/barcode_reader.dart +++ b/lib/src/logic/barcode_reader.dart @@ -16,10 +16,11 @@ Future zxingReadBarcodeImagePath( DecodeParams? params, }) async { final Uint8List imageBytes = await path.readAsBytes(); - final imglib.Image? image = imglib.decodeImage(imageBytes); + imglib.Image? image = imglib.decodeImage(imageBytes); if (image == null) { return null; } + image = resizeToMaxSize(image, params?.maxSize); return zxingReadBarcode( image.getBytes(format: imglib.Format.luminance), width: image.width, @@ -35,10 +36,11 @@ Future zxingReadBarcodeImageUrl( }) async { final Uint8List imageBytes = (await NetworkAssetBundle(Uri.parse(url)).load(url)).buffer.asUint8List(); - final imglib.Image? image = imglib.decodeImage(imageBytes); + imglib.Image? image = imglib.decodeImage(imageBytes); if (image == null) { return null; } + image = resizeToMaxSize(image, params?.maxSize); return zxingReadBarcode( image.getBytes(format: imglib.Format.luminance), width: image.width, @@ -53,15 +55,8 @@ Code zxingReadBarcode( required int width, required int height, DecodeParams? params, -}) { - Code result = _readBarcode(bytes, width, height, params); - if (!result.isValid && params != null && params.tryInverted == true) { - // try to invert the image and read again - final Uint8List invertedBytes = invertImage(bytes); - result = _readBarcode(invertedBytes, width, height, params); - } - return result; -} +}) => + _readBarcode(bytes, width, height, params); Code _readBarcode( Uint8List bytes, @@ -79,5 +74,6 @@ Code _readBarcode( params?.cropHeight ?? 0, params?.tryHarder ?? false ? 1 : 0, params?.tryRotate ?? true ? 1 : 0, + params?.tryInverted ?? false ? 1 : 0, ) .toCode(); diff --git a/lib/src/logic/barcodes_reader.dart b/lib/src/logic/barcodes_reader.dart index 05f499c..5cac114 100644 --- a/lib/src/logic/barcodes_reader.dart +++ b/lib/src/logic/barcodes_reader.dart @@ -16,10 +16,11 @@ Future> zxingReadBarcodesImagePath( DecodeParams? params, }) async { final Uint8List imageBytes = await path.readAsBytes(); - final imglib.Image? image = imglib.decodeImage(imageBytes); + imglib.Image? image = imglib.decodeImage(imageBytes); if (image == null) { return []; } + image = resizeToMaxSize(image, params?.maxSize); return zxingReadBarcodes( image.getBytes(format: imglib.Format.luminance), width: image.width, @@ -35,10 +36,11 @@ Future> zxingReadBarcodesImageUrl( }) async { final Uint8List imageBytes = (await NetworkAssetBundle(Uri.parse(url)).load(url)).buffer.asUint8List(); - final imglib.Image? image = imglib.decodeImage(imageBytes); + imglib.Image? image = imglib.decodeImage(imageBytes); if (image == null) { return []; } + image = resizeToMaxSize(image, params?.maxSize); return zxingReadBarcodes( image.getBytes(format: imglib.Format.luminance), width: image.width, @@ -54,13 +56,7 @@ List zxingReadBarcodes( required int height, DecodeParams? params, }) { - List results = _readBarcodes(bytes, width, height, params); - if (results.isEmpty && params != null && params.tryInverted == true) { - // try to invert the image and read again - final Uint8List invertedBytes = invertImage(bytes); - results = _readBarcodes(invertedBytes, width, height, params); - } - return results; + return _readBarcodes(bytes, width, height, params); } List _readBarcodes( @@ -78,6 +74,7 @@ List _readBarcodes( params?.cropHeight ?? 0, params?.tryHarder ?? false ? 1 : 0, params?.tryRotate ?? true ? 1 : 0, + params?.tryInverted ?? false ? 1 : 0, ); final List results = []; for (int i = 0; i < result.count; i++) { diff --git a/lib/src/logic/zxing.dart b/lib/src/logic/zxing.dart index 9e96ffb..cde4427 100644 --- a/lib/src/logic/zxing.dart +++ b/lib/src/logic/zxing.dart @@ -12,6 +12,7 @@ import 'package:image/image.dart' as imglib; import '../../generated_bindings.dart'; import '../models/models.dart'; import '../utils/extentions.dart'; +// import '../utils/image_converter.dart'; import '../utils/image_converter.dart'; import '../utils/isolate_utils.dart'; diff --git a/lib/src/models/code.dart b/lib/src/models/code.dart index a597b05..6cb8011 100644 --- a/lib/src/models/code.dart +++ b/lib/src/models/code.dart @@ -5,16 +5,24 @@ import 'position.dart'; // Represents a barcode code class Code { Code( - this.isValid, this.text, + this.isValid, + this.error, this.rawBytes, this.format, this.position, + this.isInverted, + this.isMirrored, + this.duration, ); - bool isValid; // Whether the code is valid String? text; // The text of the code + bool isValid; // Whether the code is valid + String? error; // The error of the code Uint8List? rawBytes; // The raw bytes of the code int? format; // The format of the code Position? position; // The position of the code + bool isInverted; // Whether the code is inverted + bool isMirrored; // Whether the code is mirrored + int duration; // The duration of the decoding in milliseconds } diff --git a/lib/src/models/params.dart b/lib/src/models/params.dart index 2908f72..5616db4 100644 --- a/lib/src/models/params.dart +++ b/lib/src/models/params.dart @@ -11,6 +11,7 @@ class DecodeParams { this.tryHarder = false, this.tryRotate = true, this.tryInverted = false, + this.maxSize = 512, }); // Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. @@ -30,6 +31,9 @@ class DecodeParams { // Try to detect inverted code bool tryInverted; + + // Resize the image to a smaller size before scanning to improve performance + int maxSize; } // Represents the parameters for encoding a barcode diff --git a/lib/src/ui/reader_widget.dart b/lib/src/ui/reader_widget.dart index 9234544..07cc81d 100644 --- a/lib/src/ui/reader_widget.dart +++ b/lib/src/ui/reader_widget.dart @@ -33,7 +33,7 @@ class ReaderWidget extends StatefulWidget { final Function(Code) onScan; /// Called when a code is not detected - final Function()? onScanFailure; + final Function(Code)? onScanFailure; /// Called when the camera controller is created final Function(CameraController?)? onControllerCreated; @@ -217,7 +217,7 @@ class _ReaderWidgetState extends State setState(() {}); await Future.delayed(widget.scanDelaySuccess); } else { - widget.onScanFailure?.call(); + 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 fbc9f0f..930168d 100644 --- a/lib/src/utils/extentions.dart +++ b/lib/src/utils/extentions.dart @@ -6,15 +6,21 @@ import 'package:ffi/ffi.dart'; import '../../zxing_mobile.dart'; extension CodeExt on CodeResult { - Code toCode() => Code( - isValid == 1, + 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, ); + } } extension EncodeExt on EncodeResult { diff --git a/lib/src/utils/image_converter.dart b/lib/src/utils/image_converter.dart index 96617b6..1a6e2bf 100644 --- a/lib/src/utils/image_converter.dart +++ b/lib/src/utils/image_converter.dart @@ -1,3 +1,4 @@ +import 'dart:math'; import 'dart:typed_data'; import 'package:camera/camera.dart'; @@ -65,3 +66,15 @@ Uint8List invertImage(Uint8List bytes) { } return invertedBytes; } + +imglib.Image resizeToMaxSize(imglib.Image image, int? maxSize) { + if (maxSize == null) { + return image; + } + if (image.width > maxSize || image.height > maxSize) { + final double scaleFactor = maxSize / max(image.width, image.height); + image = + imglib.copyResize(image, width: (image.width * scaleFactor).toInt()); + } + return image; +} diff --git a/src/native_zxing.cpp b/src/native_zxing.cpp index 3217cc2..f813565 100644 --- a/src/native_zxing.cpp +++ b/src/native_zxing.cpp @@ -27,7 +27,7 @@ extern "C" } FUNCTION_ATTRIBUTE - struct CodeResult readBarcode(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate) + struct CodeResult readBarcode(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate, int tryInvert) { long long start = get_now(); @@ -40,25 +40,23 @@ extern "C" { image = image.cropped(width / 2 - cropWidth / 2, height / 2 - cropHeight / 2, cropWidth, cropHeight); } - DecodeHints hints = DecodeHints().setTryHarder(tryHarder).setTryRotate(tryRotate).setFormats(BarcodeFormat(format)); + DecodeHints hints = DecodeHints().setTryHarder(tryHarder).setTryRotate(tryRotate).setFormats(BarcodeFormat(format)).setTryInvert(tryInvert); Result result = ReadBarcode(image, hints); - struct CodeResult code = {false, nullptr}; - if (result.isValid()) - { - resultToCodeResult(&code, result); - } + struct CodeResult code; + resultToCodeResult(&code, result); delete[] data; delete[] bytes; int evalInMillis = static_cast(get_now() - start); - platform_log("Read Barcode in: %d ms\n", evalInMillis); + code.duration = evalInMillis; + platform_log("Read Barcode in: %d ms\n", code.duration); return code; } FUNCTION_ATTRIBUTE - struct CodeResults readBarcodes(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate) + struct CodeResults readBarcodes(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate, int tryInvert) { long long start = get_now(); @@ -71,27 +69,27 @@ extern "C" { image = image.cropped(width / 2 - cropWidth / 2, height / 2 - cropHeight / 2, cropWidth, cropHeight); } - DecodeHints hints = DecodeHints().setTryHarder(tryHarder).setTryRotate(tryRotate).setFormats(BarcodeFormat(format)); + DecodeHints hints = DecodeHints().setTryHarder(tryHarder).setTryRotate(tryRotate).setFormats(BarcodeFormat(format)).setTryInvert(tryInvert); Results results = ReadBarcodes(image, hints); + int evalInMillis = static_cast(get_now() - start); + platform_log("Read Barcode in: %d ms\n", evalInMillis); + auto *codes = new struct CodeResult[results.size()]; int i = 0; for (auto &result : results) { - struct CodeResult code = {false, nullptr}; - if (result.isValid()) - { - resultToCodeResult(&code, result); - codes[i] = code; - i++; - } + struct CodeResult code; + resultToCodeResult(&code, result); + code.duration = evalInMillis; + codes[i] = code; + i++; } delete[] data; delete[] bytes; - int evalInMillis = static_cast(get_now() - start); - platform_log("Read Barcode in: %d ms\n", evalInMillis); + return {i, codes}; } @@ -124,23 +122,29 @@ extern "C" FUNCTION_ATTRIBUTE void resultToCodeResult(struct CodeResult *code, Result result) { + string text = result.text(); + code->text = new char[text.length() + 1]; + strcpy(code->text, text.c_str()); + code->isValid = result.isValid(); + string error = result.error().msg(); + code->error = new char[error.length() + 1]; + strcpy(code->error, error.c_str()); + code->format = static_cast(result.format()); code->bytes = result.bytes().data(); code->length = result.bytes().size(); - string text = result.text(); - code->text = new char[text.length() + 1]; - strcpy(code->text, text.c_str()); - auto p = result.position(); auto tl = p.topLeft(); auto tr = p.topRight(); auto bl = p.bottomLeft(); auto br = p.bottomRight(); code->pos = new Pos{tl.x, tl.y, tr.x, tr.y, bl.x, bl.y, br.x, br.y}; - platform_log("Result: %s\n", code->text); + + code->isInverted = result.isInverted(); + code->isMirrored = result.isMirrored(); } } diff --git a/src/native_zxing.h b/src/native_zxing.h index 815753e..df0bffa 100644 --- a/src/native_zxing.h +++ b/src/native_zxing.h @@ -24,12 +24,16 @@ extern "C" */ struct CodeResult { - int isValid; ///< Whether the barcode was successfully decoded char *text; ///< The decoded text + int isValid; ///< Whether the barcode was successfully decoded + char *error; ///< The error message const unsigned char *bytes; ///< The bytes is the raw / standard content without any modifications like character set conversions int length; ///< The length of the bytes int format; ///< The format of the barcode struct Pos *pos; ///< The position of the barcode within the image + int isInverted; ///< Whether the barcode was inverted + int isMirrored; ///< Whether the barcode was mirrored + int duration; ///< The duration of the decoding in milliseconds }; /** @@ -82,7 +86,7 @@ extern "C" * @param tryRotate Also try detecting code in 90, 180 and 270 degree rotated images. * @return The barcode result. */ - struct CodeResult readBarcode(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate); + struct CodeResult readBarcode(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate, int tryInvert); /** * @brief Read barcodes from image bytes. @@ -96,7 +100,7 @@ extern "C" * @param tryRotate Also try detecting code in 90, 180 and 270 degree rotated images. * @return The barcode results. */ - struct CodeResults readBarcodes(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate); + struct CodeResults readBarcodes(char *bytes, int format, int width, int height, int cropWidth, int cropHeight, int tryHarder, int tryRotate, int tryInvert); /** * @brief Encode a string into a barcode @@ -111,7 +115,6 @@ 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); #ifdef __cplusplus