Browse Source

added isInverted, isMirrored and duration to Code,

image now resized before decoding
pull/76/head
Khoren Markosyan 2 years ago
parent
commit
5ed7f071a0
  1. 59
      example/lib/main.dart
  2. 41
      ios/Classes/src/common.cpp
  3. 26
      ios/Classes/src/common.h
  4. 146
      ios/Classes/src/native_zxing.cpp
  5. 119
      ios/Classes/src/native_zxing.h
  6. 31
      lib/generated_bindings.dart
  7. 18
      lib/src/logic/barcode_reader.dart
  8. 15
      lib/src/logic/barcodes_reader.dart
  9. 1
      lib/src/logic/zxing.dart
  10. 12
      lib/src/models/code.dart
  11. 4
      lib/src/models/params.dart
  12. 4
      lib/src/ui/reader_widget.dart
  13. 10
      lib/src/utils/extentions.dart
  14. 13
      lib/src/utils/image_converter.dart
  15. 52
      src/native_zxing.cpp
  16. 11
      src/native_zxing.h

59
example/lib/main.dart

@ -4,7 +4,7 @@ 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(kDebugMode);
runApp(const MyApp()); runApp(const MyApp());
} }
@ -66,15 +66,9 @@ class _DemoPageState extends State<DemoPage> {
const Center( const Center(
child: Text('Camera not supported on this platform'), child: Text('Camera not supported on this platform'),
) )
else if (result != null) else if (result != null && result?.isValid == true)
ScanResultWidget( ScanResultWidget(
result: result?.text, result: result,
format: result?.format?.name,
onScanAgain: () => setState(() => result = null),
)
else if (result != null)
ScanResultWidget(
result: result?.text,
onScanAgain: () => setState(() => result = null), onScanAgain: () => setState(() => result = null),
) )
else else
@ -82,7 +76,7 @@ class _DemoPageState extends State<DemoPage> {
children: [ children: [
ReaderWidget( ReaderWidget(
onScan: _onScanSuccess, onScan: _onScanSuccess,
onScanFailure: () => _onScanFailure(null), onScanFailure: _onScanFailure,
tryInverted: true, tryInverted: true,
), ),
ScanFromGalleryWidget( ScanFromGalleryWidget(
@ -93,6 +87,8 @@ class _DemoPageState extends State<DemoPage> {
DebugInfoWidget( DebugInfoWidget(
successScans: successScans, successScans: successScans,
failedScans: failedScans, failedScans: failedScans,
error: result?.error,
duration: result?.duration ?? 0,
onReset: _onReset, onReset: _onReset,
), ),
], ],
@ -125,19 +121,20 @@ class _DemoPageState extends State<DemoPage> {
); );
} }
_onScanSuccess(value) { _onScanSuccess(Code? code) {
setState(() { setState(() {
successScans++; successScans++;
result = value; result = code;
}); });
} }
_onScanFailure(String? error) { _onScanFailure(Code? code) {
setState(() { setState(() {
failedScans++; failedScans++;
result = code;
}); });
if (error != null) { if (code?.error?.isNotEmpty == true) {
_showMessage(context, error); _showMessage(context, 'Error: ${code?.error}');
} }
} }
@ -160,12 +157,10 @@ class ScanResultWidget extends StatelessWidget {
const ScanResultWidget({ const ScanResultWidget({
Key? key, Key? key,
this.result, this.result,
this.format,
this.onScanAgain, this.onScanAgain,
}) : super(key: key); }) : super(key: key);
final String? result; final Code? result;
final String? format;
final Function()? onScanAgain; final Function()? onScanAgain;
@override @override
@ -177,15 +172,20 @@ class ScanResultWidget extends StatelessWidget {
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: [ children: [
Text( Text(
format ?? '', result?.format?.name ?? '',
style: Theme.of(context).textTheme.headline5, style: Theme.of(context).textTheme.headline5,
), ),
const SizedBox(height: 20), const SizedBox(height: 20),
Text( Text(
result ?? '', result?.text ?? '',
style: Theme.of(context).textTheme.headline6, 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( ElevatedButton(
onPressed: onScanAgain, onPressed: onScanAgain,
child: const Text('Scan Again'), child: const Text('Scan Again'),
@ -204,8 +204,8 @@ class ScanFromGalleryWidget extends StatelessWidget {
this.onScanFailure, this.onScanFailure,
}) : super(key: key); }) : super(key: key);
final Function(Code?)? onScan; final Function(Code)? onScan;
final Function(String)? onScanFailure; final Function(Code?)? onScanFailure;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
@ -230,7 +230,8 @@ class ScanFromGalleryWidget extends StatelessWidget {
if (result != null && result.isValid) { if (result != null && result.isValid) {
onScan?.call(result); onScan?.call(result);
} else { } 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, Key? key,
required this.successScans, required this.successScans,
required this.failedScans, required this.failedScans,
this.error,
this.duration = 0,
this.onReset, this.onReset,
}) : super(key: key); }) : super(key: key);
final int successScans; final int successScans;
final int failedScans; final int failedScans;
final String? error;
final int duration;
final Function()? onReset; final Function()? onReset;
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return Align( return Align(
alignment: Alignment.topCenter, alignment: Alignment.topLeft,
child: Padding( child: Padding(
padding: const EdgeInsets.all(8.0), padding: const EdgeInsets.all(8.0),
child: ClipRRect( child: ClipRRect(
@ -264,8 +269,8 @@ class DebugInfoWidget extends StatelessWidget {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
Text( Text(
'Success: $successScans\nFailed: $failedScans', 'Success: $successScans\nFailed: $failedScans\nDuration: $duration ms',
style: Theme.of(context).textTheme.headline6, style: Theme.of(context).textTheme.bodySmall,
), ),
TextButton( TextButton(
onPressed: onReset, onPressed: onReset,

41
ios/Classes/src/common.cpp

@ -1,41 +0,0 @@
#include "common.h"
#include <chrono>
#include <stdio.h>
#include <stdarg.h>
using namespace std;
bool isLogEnabled;
void setLoggingEnabled(bool enabled)
{
isLogEnabled = enabled;
}
long long int get_now()
{
return chrono::duration_cast<std::chrono::milliseconds>(
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);
}
}

26
ios/Classes/src/common.h

@ -1,26 +0,0 @@
#if defined(WIN32) || defined(_WIN32) || defined(__WIN32)
#define IS_WIN32
#endif
#ifdef __ANDROID__
#include <android/log.h>
#endif
#ifdef IS_WIN32
#include <windows.h>
#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);

146
ios/Classes/src/native_zxing.cpp

@ -1,146 +0,0 @@
#include "common.h"
#include "ReadBarcode.h"
#include "MultiFormatWriter.h"
#include "TextUtfEncoding.h"
#include "BitMatrix.h"
#include "native_zxing.h"
#include <locale>
#include <codecvt>
#include <stdarg.h>
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<int>(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<int>(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<int8_t>(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<int>(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<int>(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);
}
}

119
ios/Classes/src/native_zxing.h

@ -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

31
lib/generated_bindings.dart

@ -74,6 +74,7 @@ class GeneratedBindings {
int cropHeight, int cropHeight,
int tryHarder, int tryHarder,
int tryRotate, int tryRotate,
int tryInvert,
) { ) {
return _readBarcode( return _readBarcode(
bytes, bytes,
@ -84,16 +85,17 @@ class GeneratedBindings {
cropHeight, cropHeight,
tryHarder, tryHarder,
tryRotate, tryRotate,
tryInvert,
); );
} }
late final _readBarcodePtr = _lookup< late final _readBarcodePtr = _lookup<
ffi.NativeFunction< ffi.NativeFunction<
CodeResult Function(ffi.Pointer<ffi.Char>, ffi.Int, ffi.Int, ffi.Int, CodeResult Function(ffi.Pointer<ffi.Char>, 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< late final _readBarcode = _readBarcodePtr.asFunction<
CodeResult Function( CodeResult Function(
ffi.Pointer<ffi.Char>, int, int, int, int, int, int, int)>(); ffi.Pointer<ffi.Char>, int, int, int, int, int, int, int, int)>();
/// @brief Read barcodes from image bytes. /// @brief Read barcodes from image bytes.
/// @param bytes Image bytes. /// @param bytes Image bytes.
@ -114,6 +116,7 @@ class GeneratedBindings {
int cropHeight, int cropHeight,
int tryHarder, int tryHarder,
int tryRotate, int tryRotate,
int tryInvert,
) { ) {
return _readBarcodes( return _readBarcodes(
bytes, bytes,
@ -124,16 +127,17 @@ class GeneratedBindings {
cropHeight, cropHeight,
tryHarder, tryHarder,
tryRotate, tryRotate,
tryInvert,
); );
} }
late final _readBarcodesPtr = _lookup< late final _readBarcodesPtr = _lookup<
ffi.NativeFunction< ffi.NativeFunction<
CodeResults Function(ffi.Pointer<ffi.Char>, ffi.Int, ffi.Int, ffi.Int, CodeResults Function(ffi.Pointer<ffi.Char>, 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< late final _readBarcodes = _readBarcodesPtr.asFunction<
CodeResults Function( CodeResults Function(
ffi.Pointer<ffi.Char>, int, int, int, int, int, int, int)>(); ffi.Pointer<ffi.Char>, int, int, int, int, int, int, int, int)>();
/// @brief Encode a string into a barcode /// @brief Encode a string into a barcode
/// @param contents The string to encode /// @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. /// @brief The CodeResult class encapsulates the result of decoding a barcode within an image.
class CodeResult extends ffi.Struct { class CodeResult extends ffi.Struct {
/// < The decoded text
external ffi.Pointer<ffi.Char> text;
/// < Whether the barcode was successfully decoded /// < Whether the barcode was successfully decoded
@ffi.Int() @ffi.Int()
external int isValid; external int isValid;
/// < The decoded text /// < The error message
external ffi.Pointer<ffi.Char> text; external ffi.Pointer<ffi.Char> error;
/// < The bytes is the raw / standard content without any modifications like character set conversions /// < The bytes is the raw / standard content without any modifications like character set conversions
external ffi.Pointer<ffi.UnsignedChar> bytes; external ffi.Pointer<ffi.UnsignedChar> bytes;
@ -226,6 +233,18 @@ class CodeResult extends ffi.Struct {
/// < The position of the barcode within the image /// < The position of the barcode within the image
external ffi.Pointer<Pos> pos; external ffi.Pointer<Pos> 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. /// @brief The CodeResults class encapsulates the result of decoding multiple barcodes within an image.

18
lib/src/logic/barcode_reader.dart

@ -16,10 +16,11 @@ Future<Code?> zxingReadBarcodeImagePath(
DecodeParams? params, DecodeParams? params,
}) async { }) async {
final Uint8List imageBytes = await path.readAsBytes(); final Uint8List imageBytes = await path.readAsBytes();
final imglib.Image? image = imglib.decodeImage(imageBytes); imglib.Image? image = imglib.decodeImage(imageBytes);
if (image == null) { if (image == null) {
return null; return null;
} }
image = resizeToMaxSize(image, params?.maxSize);
return zxingReadBarcode( return zxingReadBarcode(
image.getBytes(format: imglib.Format.luminance), image.getBytes(format: imglib.Format.luminance),
width: image.width, width: image.width,
@ -35,10 +36,11 @@ Future<Code?> zxingReadBarcodeImageUrl(
}) async { }) async {
final Uint8List imageBytes = final Uint8List imageBytes =
(await NetworkAssetBundle(Uri.parse(url)).load(url)).buffer.asUint8List(); (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) { if (image == null) {
return null; return null;
} }
image = resizeToMaxSize(image, params?.maxSize);
return zxingReadBarcode( return zxingReadBarcode(
image.getBytes(format: imglib.Format.luminance), image.getBytes(format: imglib.Format.luminance),
width: image.width, width: image.width,
@ -53,15 +55,8 @@ Code zxingReadBarcode(
required int width, required int width,
required int height, required int height,
DecodeParams? params, DecodeParams? params,
}) { }) =>
Code result = _readBarcode(bytes, width, height, params); _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;
}
Code _readBarcode( Code _readBarcode(
Uint8List bytes, Uint8List bytes,
@ -79,5 +74,6 @@ Code _readBarcode(
params?.cropHeight ?? 0, params?.cropHeight ?? 0,
params?.tryHarder ?? false ? 1 : 0, params?.tryHarder ?? false ? 1 : 0,
params?.tryRotate ?? true ? 1 : 0, params?.tryRotate ?? true ? 1 : 0,
params?.tryInverted ?? false ? 1 : 0,
) )
.toCode(); .toCode();

15
lib/src/logic/barcodes_reader.dart

@ -16,10 +16,11 @@ Future<List<Code>> zxingReadBarcodesImagePath(
DecodeParams? params, DecodeParams? params,
}) async { }) async {
final Uint8List imageBytes = await path.readAsBytes(); final Uint8List imageBytes = await path.readAsBytes();
final imglib.Image? image = imglib.decodeImage(imageBytes); imglib.Image? image = imglib.decodeImage(imageBytes);
if (image == null) { if (image == null) {
return <Code>[]; return <Code>[];
} }
image = resizeToMaxSize(image, params?.maxSize);
return zxingReadBarcodes( return zxingReadBarcodes(
image.getBytes(format: imglib.Format.luminance), image.getBytes(format: imglib.Format.luminance),
width: image.width, width: image.width,
@ -35,10 +36,11 @@ Future<List<Code>> zxingReadBarcodesImageUrl(
}) async { }) async {
final Uint8List imageBytes = final Uint8List imageBytes =
(await NetworkAssetBundle(Uri.parse(url)).load(url)).buffer.asUint8List(); (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) { if (image == null) {
return <Code>[]; return <Code>[];
} }
image = resizeToMaxSize(image, params?.maxSize);
return zxingReadBarcodes( return zxingReadBarcodes(
image.getBytes(format: imglib.Format.luminance), image.getBytes(format: imglib.Format.luminance),
width: image.width, width: image.width,
@ -54,13 +56,7 @@ List<Code> zxingReadBarcodes(
required int height, required int height,
DecodeParams? params, DecodeParams? params,
}) { }) {
List<Code> results = _readBarcodes(bytes, width, height, params); return _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;
} }
List<Code> _readBarcodes( List<Code> _readBarcodes(
@ -78,6 +74,7 @@ List<Code> _readBarcodes(
params?.cropHeight ?? 0, params?.cropHeight ?? 0,
params?.tryHarder ?? false ? 1 : 0, params?.tryHarder ?? false ? 1 : 0,
params?.tryRotate ?? true ? 1 : 0, params?.tryRotate ?? true ? 1 : 0,
params?.tryInverted ?? false ? 1 : 0,
); );
final List<Code> results = <Code>[]; final List<Code> results = <Code>[];
for (int i = 0; i < result.count; i++) { for (int i = 0; i < result.count; i++) {

1
lib/src/logic/zxing.dart

@ -12,6 +12,7 @@ import 'package:image/image.dart' as imglib;
import '../../generated_bindings.dart'; import '../../generated_bindings.dart';
import '../models/models.dart'; import '../models/models.dart';
import '../utils/extentions.dart'; import '../utils/extentions.dart';
// import '../utils/image_converter.dart';
import '../utils/image_converter.dart'; import '../utils/image_converter.dart';
import '../utils/isolate_utils.dart'; import '../utils/isolate_utils.dart';

12
lib/src/models/code.dart

@ -5,16 +5,24 @@ import 'position.dart';
// Represents a barcode code // Represents a barcode code
class Code { class Code {
Code( Code(
this.isValid,
this.text, this.text,
this.isValid,
this.error,
this.rawBytes, this.rawBytes,
this.format, this.format,
this.position, this.position,
this.isInverted,
this.isMirrored,
this.duration,
); );
bool isValid; // Whether the code is valid
String? text; // The text of the code 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 Uint8List? rawBytes; // The raw bytes of the code
int? format; // The format of the code int? format; // The format of the code
Position? position; // The position 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
} }

4
lib/src/models/params.dart

@ -11,6 +11,7 @@ class DecodeParams {
this.tryHarder = false, this.tryHarder = false,
this.tryRotate = true, this.tryRotate = true,
this.tryInverted = false, this.tryInverted = false,
this.maxSize = 512,
}); });
// 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.
@ -30,6 +31,9 @@ class DecodeParams {
// Try to detect inverted code // Try to detect inverted code
bool tryInverted; bool tryInverted;
// Resize the image to a smaller size before scanning to improve performance
int maxSize;
} }
// Represents the parameters for encoding a barcode // Represents the parameters for encoding a barcode

4
lib/src/ui/reader_widget.dart

@ -33,7 +33,7 @@ class ReaderWidget extends StatefulWidget {
final Function(Code) onScan; final Function(Code) onScan;
/// Called when a code is not detected /// Called when a code is not detected
final Function()? onScanFailure; final Function(Code)? onScanFailure;
/// Called when the camera controller is created /// Called when the camera controller is created
final Function(CameraController?)? onControllerCreated; final Function(CameraController?)? onControllerCreated;
@ -217,7 +217,7 @@ class _ReaderWidgetState extends State<ReaderWidget>
setState(() {}); setState(() {});
await Future<void>.delayed(widget.scanDelaySuccess); await Future<void>.delayed(widget.scanDelaySuccess);
} else { } else {
widget.onScanFailure?.call(); widget.onScanFailure?.call(result);
} }
} on FileSystemException catch (e) { } on FileSystemException catch (e) {
debugPrint(e.message); debugPrint(e.message);

10
lib/src/utils/extentions.dart

@ -6,15 +6,21 @@ import 'package:ffi/ffi.dart';
import '../../zxing_mobile.dart'; import '../../zxing_mobile.dart';
extension CodeExt on CodeResult { extension CodeExt on CodeResult {
Code toCode() => Code( Code toCode() {
isValid == 1, return Code(
text == nullptr ? null : text.cast<Utf8>().toDartString(), text == nullptr ? null : text.cast<Utf8>().toDartString(),
isValid == 1,
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,
isMirrored == 1,
duration,
); );
}
} }
extension EncodeExt on EncodeResult { extension EncodeExt on EncodeResult {

13
lib/src/utils/image_converter.dart

@ -1,3 +1,4 @@
import 'dart:math';
import 'dart:typed_data'; import 'dart:typed_data';
import 'package:camera/camera.dart'; import 'package:camera/camera.dart';
@ -65,3 +66,15 @@ Uint8List invertImage(Uint8List bytes) {
} }
return invertedBytes; 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;
}

52
src/native_zxing.cpp

@ -27,7 +27,7 @@ extern "C"
} }
FUNCTION_ATTRIBUTE 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(); long long start = get_now();
@ -40,25 +40,23 @@ extern "C"
{ {
image = image.cropped(width / 2 - cropWidth / 2, height / 2 - cropHeight / 2, cropWidth, cropHeight); 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); Result result = ReadBarcode(image, hints);
struct CodeResult code = {false, nullptr}; struct CodeResult code;
if (result.isValid()) resultToCodeResult(&code, result);
{
resultToCodeResult(&code, result);
}
delete[] data; delete[] data;
delete[] bytes; delete[] bytes;
int evalInMillis = static_cast<int>(get_now() - start); int evalInMillis = static_cast<int>(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; return code;
} }
FUNCTION_ATTRIBUTE 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(); long long start = get_now();
@ -71,27 +69,27 @@ extern "C"
{ {
image = image.cropped(width / 2 - cropWidth / 2, height / 2 - cropHeight / 2, cropWidth, cropHeight); 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); Results results = ReadBarcodes(image, hints);
int evalInMillis = static_cast<int>(get_now() - start);
platform_log("Read Barcode in: %d ms\n", evalInMillis);
auto *codes = new struct CodeResult[results.size()]; auto *codes = new struct CodeResult[results.size()];
int i = 0; int i = 0;
for (auto &result : results) for (auto &result : results)
{ {
struct CodeResult code = {false, nullptr}; struct CodeResult code;
if (result.isValid()) resultToCodeResult(&code, result);
{ code.duration = evalInMillis;
resultToCodeResult(&code, result); codes[i] = code;
codes[i] = code; i++;
i++;
}
} }
delete[] data; delete[] data;
delete[] bytes; delete[] bytes;
int evalInMillis = static_cast<int>(get_now() - start);
platform_log("Read Barcode in: %d ms\n", evalInMillis);
return {i, codes}; return {i, codes};
} }
@ -124,23 +122,29 @@ extern "C"
FUNCTION_ATTRIBUTE FUNCTION_ATTRIBUTE
void resultToCodeResult(struct CodeResult *code, Result result) 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(); 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<int>(result.format()); code->format = static_cast<int>(result.format());
code->bytes = result.bytes().data(); code->bytes = result.bytes().data();
code->length = result.bytes().size(); 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 p = result.position();
auto tl = p.topLeft(); auto tl = p.topLeft();
auto tr = p.topRight(); auto tr = p.topRight();
auto bl = p.bottomLeft(); auto bl = p.bottomLeft();
auto br = p.bottomRight(); auto br = p.bottomRight();
code->pos = new Pos{tl.x, tl.y, tr.x, tr.y, bl.x, bl.y, br.x, br.y}; 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();
} }
} }

11
src/native_zxing.h

@ -24,12 +24,16 @@ extern "C"
*/ */
struct CodeResult struct CodeResult
{ {
int isValid; ///< Whether the barcode was successfully decoded
char *text; ///< The decoded text 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 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 length; ///< The length of the bytes
int format; ///< The format of the barcode int format; ///< The format of the barcode
struct Pos *pos; ///< The position of the barcode within the image 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. * @param tryRotate Also try detecting code in 90, 180 and 270 degree rotated images.
* @return The barcode result. * @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. * @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. * @param tryRotate Also try detecting code in 90, 180 and 270 degree rotated images.
* @return The barcode results. * @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 * @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); 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

Loading…
Cancel
Save