From 8202a4e13a65de3a2b459d121af7b02cc7d2e9b5 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 6 Jul 2023 12:42:22 +0200 Subject: [PATCH 001/587] EXPERIMENTAL_API: actually make the new build option work I noticed one day after the 2.1 release... sigh. --- CMakeLists.txt | 4 ++++ core/CMakeLists.txt | 1 - core/src/DecodeHints.h | 6 +++--- example/ZXingReader.cpp | 4 ++-- 4 files changed, 9 insertions(+), 6 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 87785add27..7c6cc67d41 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -54,6 +54,10 @@ if (BUILD_UNIT_TESTS AND (NOT BUILD_WRITERS OR NOT BUILD_READERS)) set (BUILD_READERS ON) endif() +if (BUILD_EXPERIMENTAL_API) + add_definitions (-DZXING_BUILD_EXPERIMENTAL_API) +endif() + set(BUILD_DEPENDENCIES_LIST AUTO GITHUB LOCAL) set_property(CACHE BUILD_DEPENDENCIES PROPERTY STRINGS ${BUILD_DEPENDENCIES_LIST}) if(NOT BUILD_DEPENDENCIES IN_LIST BUILD_DEPENDENCIES_LIST) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 24cebd93ab..a06cd6e355 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -27,7 +27,6 @@ set (ZXING_CORE_LOCAL_DEFINES $<$:-DZXING_BUILD_READERS> $<$:-DZXING_BUILD_WRITERS> $<$:-DZXING_BUILD_FOR_TEST> - $<$:-DZXING_BUILD_EXPERIMENTAL_API> ) if (MSVC) set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index facee2d7ef..2aa254963a 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -63,7 +63,7 @@ class DecodeHints Binarizer _binarizer : 2; TextMode _textMode : 3; CharacterSet _characterSet : 6; -#ifdef BUILD_EXPERIMENTAL_API +#ifdef ZXING_BUILD_EXPERIMENTAL_API bool _tryDenoise : 1; #endif @@ -90,7 +90,7 @@ class DecodeHints _binarizer(Binarizer::LocalAverage), _textMode(TextMode::HRI), _characterSet(CharacterSet::Unknown) -#ifdef BUILD_EXPERIMENTAL_API +#ifdef ZXING_BUILD_EXPERIMENTAL_API , _tryDenoise(0) #endif @@ -116,7 +116,7 @@ class DecodeHints /// Also try detecting code in downscaled images (depending on image size). ZX_PROPERTY(bool, tryDownscale, setTryDownscale) -#ifdef BUILD_EXPERIMENTAL_API +#ifdef ZXING_BUILD_EXPERIMENTAL_API /// Also try detecting code after denoising (currently morphological closing filter for 2D symbologies only). ZX_PROPERTY(bool, tryDenoise, setTryDenoise) #endif diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index b857b2505d..b2376e6cb6 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -54,7 +54,7 @@ static void PrintUsage(const char* exePath) static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLine, bool& bytesOnly, std::vector& filePaths, std::string& outPath) { -#ifdef BUILD_EXPERIMENTAL_API +#ifdef ZXING_BUILD_EXPERIMENTAL_API hints.setTryDenoise(true); #endif @@ -62,7 +62,7 @@ static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLi auto is = [&](const char* str) { return strncmp(argv[i], str, strlen(argv[i])) == 0; }; if (is("-fast")) { hints.setTryHarder(false); -#ifdef BUILD_EXPERIMENTAL_API +#ifdef ZXING_BUILD_EXPERIMENTAL_API hints.setTryDenoise(false); #endif } else if (is("-norotate")) { From f9a65efac0fc685c1e26e1f6c2dfad4a28f27e00 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 7 Jul 2023 11:41:43 +0200 Subject: [PATCH 002/587] README: update link to repology.org --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index f47b910504..3811192da0 100644 --- a/README.md +++ b/README.md @@ -83,4 +83,4 @@ cmake --build zxing-cpp.release -j8 [vcpkg](https://github.com/Microsoft/vcpkg/tree/master/ports/nu-book-zxing-cpp), [conan](https://github.com/conan-io/conan-center-index/tree/master/recipes/zxing-cpp), [mingw](https://github.com/msys2/MINGW-packages/tree/master/mingw-w64-zxing-cpp) and a bunch of -[linux distributions](https://repology.org/project/zxing-cpp-nu-book/versions).] +[linux distributions](https://repology.org/project/zxing-cpp/versions).] From 8a0c458dc2284dcaa1a1393510fdf5e8f7735041 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 11 Jul 2023 14:37:46 +0200 Subject: [PATCH 003/587] WASM: return the `bytes` member and fix issue with '<', '>', etc. chars This is supposed to implement #584 in a 'proper' way (still not what I hoped it would be, see comment about `typed_memory_view`). --- wrappers/wasm/BarcodeReader.cpp | 19 ++++++++++++++----- wrappers/wasm/demo_cam_reader.html | 6 +++++- wrappers/wasm/demo_reader.html | 23 +++++++++++++++++++++-- 3 files changed, 40 insertions(+), 8 deletions(-) diff --git a/wrappers/wasm/BarcodeReader.cpp b/wrappers/wasm/BarcodeReader.cpp index e56762e490..d749aec4cf 100644 --- a/wrappers/wasm/BarcodeReader.cpp +++ b/wrappers/wasm/BarcodeReader.cpp @@ -20,9 +20,16 @@ struct ReadResult { std::string format{}; std::string text{}; + std::string bytes{}; std::string error{}; Position position{}; std::string symbologyIdentifier{}; + +// The following seemed like the way to go, because the bytes member on the JS side would then automatically be a Uint8Array +// but unfortunatelly, I don't understand something about the memory management, because the resulting array could contain 8 bytes of garbage. +// ByteArray bytes; +// emscripten::val get_bytes() const { return emscripten::val(emscripten::typed_memory_view(bytes.size(), bytes.data())); } +// void set_bytes(emscripten::val) {} // dummy setter }; std::vector readBarcodes(ImageView iv, bool tryHarder, const std::string& format, int maxSymbols) @@ -42,15 +49,16 @@ std::vector readBarcodes(ImageView iv, bool tryHarder, const std::st std::vector readResults{}; readResults.reserve(results.size()); - for (auto& result : results) { - readResults.push_back({ToString(result.format()), result.text(), ToString(result.error()), result.position(), result.symbologyIdentifier()}); + for (auto&& result : results) { + readResults.push_back({ToString(result.format()), result.text(), std::string(result.bytes().asString()), + ToString(result.error()), result.position(), result.symbologyIdentifier()}); } return readResults; } catch (const std::exception& e) { - return {{"", "", e.what()}}; + return {{"", "", {}, e.what()}}; } catch (...) { - return {{"", "", "Unknown error"}}; + return {{"", "", {}, "Unknown error"}}; } return {}; } @@ -62,7 +70,7 @@ std::vector readBarcodesFromImage(int bufferPtr, int bufferLength, b stbi_load_from_memory(reinterpret_cast(bufferPtr), bufferLength, &width, &height, &channels, 1), stbi_image_free); if (buffer == nullptr) - return {{"", "", "Error loading image"}}; + return {{"", "", {}, "Error loading image"}}; return readBarcodes({buffer.get(), width, height, ImageFormat::Lum}, tryHarder, format, maxSymbols); } @@ -89,6 +97,7 @@ EMSCRIPTEN_BINDINGS(BarcodeReader) value_object("ReadResult") .field("format", &ReadResult::format) .field("text", &ReadResult::text) + .field("bytes", &ReadResult::bytes) .field("error", &ReadResult::error) .field("position", &ReadResult::position) .field("symbologyIdentifier", &ReadResult::symbologyIdentifier); diff --git a/wrappers/wasm/demo_cam_reader.html b/wrappers/wasm/demo_cam_reader.html index 1791d8cf66..9037e9ee65 100644 --- a/wrappers/wasm/demo_cam_reader.html +++ b/wrappers/wasm/demo_cam_reader.html @@ -111,12 +111,16 @@

zxing-cpp/wasm live demo

} } + function escapeTags(htmlStr) { + return htmlStr.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); + } + const processFrame = function () { ctx.drawImage(video, 0, 0, canvas.width, canvas.height); const code = readBarcodeFromCanvas(canvas, format.value, mode.value === 'true'); if (code.format) { - resultElement.innerText = code.format + ": " + code.text; + resultElement.innerText = code.format + ": " + escapeTags(code.text); drawResult(code) } else { resultElement.innerText = "No barcode found"; diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index cbcb7cfcca..99829f0a23 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -70,6 +70,22 @@ img.src = URL.createObjectURL(file) } +function escapeTags(htmlStr) { + return htmlStr.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); +} + +function str2u8a(str) { + var bufView = new Uint8Array(new ArrayBuffer(str.length)); + for (var i = 0; i < bufView.length; i++) { + bufView[i] = str.charCodeAt(i); + } + return bufView; +} + +function u8a2hex(bytes) { + return bytes.reduce((a, b) => a + b.toString(16).padStart(2, '0') + ' ', ''); +} + function showResults(results) { const resultsDiv = document.getElementById("results"); resultsDiv.innerHTML = ""; @@ -78,8 +94,11 @@ } else { for (let i = 0; i < results.size(); i += 1) { - const { error, format, text } = results.get(i); - resultsDiv.innerHTML += "
  • Format: " + format + "
    " + (text || 'Error: ' + error + '') + "
  • "; + const { format, text, bytes, error } = results.get(i); + resultsDiv.innerHTML += "
  • Format: " + format + "" + + "
    " + (escapeTags(text) || 'Error: ' + error + '') + "
    " + + "
    " + u8a2hex(str2u8a(bytes)) + "
    " + + "
  • "; } } } From a11b6764d787c101a7cf17283b5781347a4c558f Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 11 Jul 2023 14:41:38 +0200 Subject: [PATCH 004/587] c++: fix typo in comment --- core/src/oned/ODMultiUPCEANReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/oned/ODMultiUPCEANReader.cpp b/core/src/oned/ODMultiUPCEANReader.cpp index aa88369c8c..07af59dfa2 100644 --- a/core/src/oned/ODMultiUPCEANReader.cpp +++ b/core/src/oned/ODMultiUPCEANReader.cpp @@ -70,7 +70,7 @@ static bool DecodeDigit(const PatternView& view, std::string& txt, int* lgPatter // clang-format off /* pattern now contains the central 5 bits of the L/G/R code - * L/G-codes always wart with 1 and end with 0, R-codes are simply + * L/G-codes always start with 1 and end with 0, R-codes are simply * inverted L-codes. L-Code G-Code R-Code From e097a3c1fd2ab15817972d5b90285d61b54951c5 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 13 Jul 2023 14:52:51 +0200 Subject: [PATCH 005/587] Qt: add trivial writer example --- example/CMakeLists.txt | 21 ++++++++++++------- example/ZXingQtWriter.cpp | 44 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 7 deletions(-) create mode 100644 example/ZXingQtWriter.cpp diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index fbceb837da..81df390bdc 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -22,15 +22,15 @@ if (BUILD_READERS) install(TARGETS ZXingReader DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() -if (BUILD_READERS) - find_package(Qt5 COMPONENTS Gui Multimedia Quick QUIET) - if (NOT (Qt5_FOUND OR Qt6_FOUND)) - message("INFO: Qt (Gui/Multimedia/Quick) not found, skipping Qt examples") - endif() +find_package(Qt5 COMPONENTS Gui Multimedia Quick QUIET) +if (NOT (Qt5_FOUND OR Qt6_FOUND)) + message("INFO: Qt (Gui/Multimedia/Quick) not found, skipping Qt examples") +endif() - set(CMAKE_AUTOMOC ON) - set(CMAKE_AUTORCC ON) +set(CMAKE_AUTOMOC ON) +set(CMAKE_AUTORCC ON) +if (BUILD_READERS) if (TARGET Qt::Gui) add_executable (ZXingQtReader ZXingQtReader.cpp ZXingQtReader.h) target_link_libraries(ZXingQtReader ZXing::ZXing Qt::Gui) @@ -50,3 +50,10 @@ if (BUILD_READERS) message("INFO: OpenCV not found, skipping ZXingOpenCV example") endif() endif() + +if (BUILD_WRITERS) + if (TARGET Qt::Gui) + add_executable (ZXingQtWriter ZXingQtWriter.cpp) + target_link_libraries(ZXingQtWriter ZXing::ZXing Qt::Gui) + endif() +endif() diff --git a/example/ZXingQtWriter.cpp b/example/ZXingQtWriter.cpp new file mode 100644 index 0000000000..047f182a7d --- /dev/null +++ b/example/ZXingQtWriter.cpp @@ -0,0 +1,44 @@ +/* + * Copyright 2023 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#include "BarcodeFormat.h" +#include "BitMatrix.h" +#include "MultiFormatWriter.h" + +#include +#include + +namespace ZXingQt { + +QImage WriteBarcode(QStringView text, ZXing::BarcodeFormat format) +{ + using namespace ZXing; + + auto writer = MultiFormatWriter(format); + auto matrix = writer.encode(text.toString().toStdString(), 0, 0); + auto bitmap = ToMatrix(matrix); + + return QImage(bitmap.data(), bitmap.width(), bitmap.height(), bitmap.width(), QImage::Format::Format_Grayscale8).copy(); +} + +} // namespace ZXingQt + +int main(int argc, char* argv[]) +{ + if (argc != 4) { + qDebug() << "usage: ZXingQtWriter "; + return 1; + } + + auto format = ZXing::BarcodeFormatFromString(argv[1]); + auto text = QString(argv[2]); + auto filename = QString(argv[3]); + + auto result = ZXingQt::WriteBarcode(text, format); + + result.save(filename); + + return 0; +} From f3387c3b832640fa43070c17f013918477b5b935 Mon Sep 17 00:00:00 2001 From: Ze-Zheng Wu Date: Fri, 14 Jul 2023 20:44:35 +0800 Subject: [PATCH 006/587] WASM: bytes in `ReadResult` (#588) safely return bytes as `Uint8Array` in `ReadResult` and remove str2u8a workaround. --- wrappers/wasm/BarcodeReader.cpp | 22 +++++++++++++--------- wrappers/wasm/demo_reader.html | 10 +--------- 2 files changed, 14 insertions(+), 18 deletions(-) diff --git a/wrappers/wasm/BarcodeReader.cpp b/wrappers/wasm/BarcodeReader.cpp index d749aec4cf..2f216d5f3f 100644 --- a/wrappers/wasm/BarcodeReader.cpp +++ b/wrappers/wasm/BarcodeReader.cpp @@ -7,6 +7,7 @@ #include "ReadBarcode.h" #include +#include #include #include #include @@ -20,16 +21,10 @@ struct ReadResult { std::string format{}; std::string text{}; - std::string bytes{}; + emscripten::val bytes; std::string error{}; Position position{}; std::string symbologyIdentifier{}; - -// The following seemed like the way to go, because the bytes member on the JS side would then automatically be a Uint8Array -// but unfortunatelly, I don't understand something about the memory management, because the resulting array could contain 8 bytes of garbage. -// ByteArray bytes; -// emscripten::val get_bytes() const { return emscripten::val(emscripten::typed_memory_view(bytes.size(), bytes.data())); } -// void set_bytes(emscripten::val) {} // dummy setter }; std::vector readBarcodes(ImageView iv, bool tryHarder, const std::string& format, int maxSymbols) @@ -49,9 +44,18 @@ std::vector readBarcodes(ImageView iv, bool tryHarder, const std::st std::vector readResults{}; readResults.reserve(results.size()); + thread_local const emscripten::val Uint8Array = emscripten::val::global("Uint8Array"); + for (auto&& result : results) { - readResults.push_back({ToString(result.format()), result.text(), std::string(result.bytes().asString()), - ToString(result.error()), result.position(), result.symbologyIdentifier()}); + const ByteArray& bytes = result.bytes(); + readResults.push_back({ + ToString(result.format()), + result.text(), + Uint8Array.new_(emscripten::typed_memory_view(bytes.size(), bytes.data())), + ToString(result.error()), + result.position(), + result.symbologyIdentifier() + }); } return readResults; diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index 99829f0a23..86b6af1358 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -74,14 +74,6 @@ return htmlStr.replace(/&/g, "&").replace(//g, ">").replace(/"/g, """).replace(/'/g, "'"); } -function str2u8a(str) { - var bufView = new Uint8Array(new ArrayBuffer(str.length)); - for (var i = 0; i < bufView.length; i++) { - bufView[i] = str.charCodeAt(i); - } - return bufView; -} - function u8a2hex(bytes) { return bytes.reduce((a, b) => a + b.toString(16).padStart(2, '0') + ' ', ''); } @@ -97,7 +89,7 @@ const { format, text, bytes, error } = results.get(i); resultsDiv.innerHTML += "
  • Format: " + format + "" + "
    " + (escapeTags(text) || 'Error: ' + error + '') + "
    " - + "
    " + u8a2hex(str2u8a(bytes)) + "
    " + + "
    " + u8a2hex(bytes) + "
    " + "
  • "; } } From 679b6f60257a33a8aa9b54285eab2b52548345a6 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 14 Jul 2023 20:00:32 +0200 Subject: [PATCH 007/587] EXPERIMENTAL_API: actually make the new build option work (again...) I overlooked another hunk during the commit 8202a4e13a65de3a2b459d121af7b02cc7d2e9b5. double sigh... --- core/src/ReadBarcode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index f814add08b..16fd25b264 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -146,7 +146,7 @@ Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; std::unique_ptr closedReader; -#ifdef BUILD_EXPERIMENTAL_API +#ifdef ZXING_BUILD_EXPERIMENTAL_API auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode; if (hints.tryDenoise() && hints.hasFormat(formatsBenefittingFromClosing)) { DecodeHints closedHints = hints; From a392ec6bbc4859ff7bdbe10277a5f86f02120ba7 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 21 Jul 2023 11:22:51 +0200 Subject: [PATCH 008/587] BitMatrixCursor: initialize std::array with 0s (fix #591) --- core/src/BitMatrixCursor.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index bdfc523ed6..25cc95cb13 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -168,7 +168,7 @@ class BitMatrixCursor template ARRAY readPattern(int range = 0) { - ARRAY res; + ARRAY res = {}; for (auto& i : res) { i = stepToEdge(1, range); if (!i) From 99175c58ba15d1ce566c94784d261f53b419a435 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 21 Jul 2023 11:26:34 +0200 Subject: [PATCH 009/587] DMDetector: add TODO comment --- core/src/datamatrix/DMDetector.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index b263b26adf..f1b9943d56 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -878,7 +878,7 @@ static DetectorResults DetectNew(const BitMatrix& image, bool tryHarder, bool tr /** * This method detects a code in a "pure" image -- that is, pure monochrome image -* which contains only an unrotated, unskewed, image of a code, with some white border +* which contains only an unrotated, unskewed, image of a code, with some optional white border * around it. This is a specialized method that works exceptionally fast in this special * case. */ @@ -928,6 +928,7 @@ DetectorResults Detect(const BitMatrix& image, bool tryHarder, bool tryRotate, b co_yield std::move(r); } if (!found && tryHarder) { + //TODO: implement a tryRotate version of DetectPure, see #590. if (auto r = DetectPure(image); r.isValid()) co_yield std::move(r); else if(auto r = DetectOld(image); r.isValid()) From b2402778072d0ad4a0e2f87968c7da085a17dc12 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 24 Jul 2023 19:44:52 +0200 Subject: [PATCH 010/587] C-API: fix whitespace indentation of `extern "C"` block --- wrappers/c/zxing-c.cpp | 352 ++++++++++++++++++++--------------------- wrappers/c/zxing-c.h | 288 ++++++++++++++++----------------- 2 files changed, 320 insertions(+), 320 deletions(-) diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index f40916027a..befb8b777c 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -20,222 +20,222 @@ char* copy(std::string_view sv) return ret; } -extern "C" +extern "C" { +/* + * ZXing/ImageView.h + */ + +zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, + int pixStride) { - /* - * ZXing/ImageView.h - */ + ImageFormat cppformat = static_cast(format); + return new ImageView(data, width, height, cppformat, rowStride, pixStride); +} - zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, - int pixStride) - { - ImageFormat cppformat = static_cast(format); - return new ImageView(data, width, height, cppformat, rowStride, pixStride); - } +void zxing_ImageView_delete(zxing_ImageView* iv) +{ + delete iv; +} - void zxing_ImageView_delete(zxing_ImageView* iv) - { - delete iv; - } +/* + * ZXing/BarcodeFormat.h + */ - /* - * ZXing/BarcodeFormat.h - */ - - zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str) - { - if (!str) - return {}; - try { - auto format = BarcodeFormatsFromString(str); - return static_cast(*reinterpret_cast(&format)); - } catch (...) { - return zxing_BarcodeFormat_Invalid; - } +zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str) +{ + if (!str) + return {}; + try { + auto format = BarcodeFormatsFromString(str); + return static_cast(*reinterpret_cast(&format)); + } catch (...) { + return zxing_BarcodeFormat_Invalid; } +} - zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str) - { - zxing_BarcodeFormat res = zxing_BarcodeFormatsFromString(str); - return BitHacks::CountBitsSet(res) == 1 ? res : zxing_BarcodeFormat_Invalid; - } +zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str) +{ + zxing_BarcodeFormat res = zxing_BarcodeFormatsFromString(str); + return BitHacks::CountBitsSet(res) == 1 ? res : zxing_BarcodeFormat_Invalid; +} - char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) - { - return copy(ToString(static_cast(format))); - } +char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) +{ + return copy(ToString(static_cast(format))); +} - /* - * ZXing/DecodeHints.h - */ +/* + * ZXing/DecodeHints.h + */ - zxing_DecodeHints* zxing_DecodeHints_new() - { - return new DecodeHints(); - } +zxing_DecodeHints* zxing_DecodeHints_new() +{ + return new DecodeHints(); +} - void zxing_DecodeHints_delete(zxing_DecodeHints* hints) - { - delete hints; - } +void zxing_DecodeHints_delete(zxing_DecodeHints* hints) +{ + delete hints; +} - void zxing_DecodeHints_setTryHarder(zxing_DecodeHints* hints, bool tryHarder) - { - hints->setTryHarder(tryHarder); - } +void zxing_DecodeHints_setTryHarder(zxing_DecodeHints* hints, bool tryHarder) +{ + hints->setTryHarder(tryHarder); +} - void zxing_DecodeHints_setTryRotate(zxing_DecodeHints* hints, bool tryRotate) - { - hints->setTryRotate(tryRotate); - } +void zxing_DecodeHints_setTryRotate(zxing_DecodeHints* hints, bool tryRotate) +{ + hints->setTryRotate(tryRotate); +} - void zxing_DecodeHints_setTryInvert(zxing_DecodeHints* hints, bool tryInvert) - { - hints->setTryInvert(tryInvert); - } +void zxing_DecodeHints_setTryInvert(zxing_DecodeHints* hints, bool tryInvert) +{ + hints->setTryInvert(tryInvert); +} - void zxing_DecodeHints_setTryDownscale(zxing_DecodeHints* hints, bool tryDownscale) - { - hints->setTryDownscale(tryDownscale); - } +void zxing_DecodeHints_setTryDownscale(zxing_DecodeHints* hints, bool tryDownscale) +{ + hints->setTryDownscale(tryDownscale); +} - void zxing_DecodeHints_setIsPure(zxing_DecodeHints* hints, bool isPure) - { - hints->setIsPure(isPure); - } +void zxing_DecodeHints_setIsPure(zxing_DecodeHints* hints, bool isPure) +{ + hints->setIsPure(isPure); +} - void zxing_DecodeHints_setReturnErrors(zxing_DecodeHints* hints, bool returnErrors) - { - hints->setReturnErrors(returnErrors); - } +void zxing_DecodeHints_setReturnErrors(zxing_DecodeHints* hints, bool returnErrors) +{ + hints->setReturnErrors(returnErrors); +} - void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats formats) - { - hints->setFormats(static_cast(formats)); - } +void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats formats) +{ + hints->setFormats(static_cast(formats)); +} - void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer) - { - hints->setBinarizer(static_cast(binarizer)); - } +void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer) +{ + hints->setBinarizer(static_cast(binarizer)); +} - void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol) - { - hints->setEanAddOnSymbol(static_cast(eanAddOnSymbol)); - } +void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol) +{ + hints->setEanAddOnSymbol(static_cast(eanAddOnSymbol)); +} - void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode) - { - hints->setTextMode(static_cast(textMode)); - } +void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode) +{ + hints->setTextMode(static_cast(textMode)); +} - /* - * ZXing/Result.h - */ +/* + * ZXing/Result.h + */ - char* zxing_ContentTypeToString(zxing_ContentType type) - { - return copy(ToString(static_cast(type))); - } +char* zxing_ContentTypeToString(zxing_ContentType type) +{ + return copy(ToString(static_cast(type))); +} - bool zxing_Result_isValid(const zxing_Result* result) - { - return result != NULL && result->isValid(); - } +bool zxing_Result_isValid(const zxing_Result* result) +{ + return result != NULL && result->isValid(); +} - char* zxing_Result_errorMsg(const zxing_Result* result) - { - return copy(ToString(result->error())); - } +char* zxing_Result_errorMsg(const zxing_Result* result) +{ + return copy(ToString(result->error())); +} - zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result) - { - return static_cast(result->format()); - } +zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result) +{ + return static_cast(result->format()); +} - zxing_ContentType zxing_Result_contentType(const zxing_Result* result) - { - return static_cast(result->contentType()); - } +zxing_ContentType zxing_Result_contentType(const zxing_Result* result) +{ + return static_cast(result->contentType()); +} - uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len) - { - *len = Size(result->bytes()); +uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len) +{ + *len = Size(result->bytes()); - auto ret = (uint8_t*)malloc(*len + 1); - if (ret) - memcpy(ret, result->bytes().data(), *len); - else - *len = 0; + auto ret = (uint8_t*)malloc(*len + 1); + if (ret) + memcpy(ret, result->bytes().data(), *len); + else + *len = 0; - return ret; - } + return ret; +} - char* zxing_Result_text(const zxing_Result* result) - { - return copy(result->text()); - } +char* zxing_Result_text(const zxing_Result* result) +{ + return copy(result->text()); +} - char* zxing_Result_ecLevel(const zxing_Result* result) - { - return copy(result->ecLevel()); - } +char* zxing_Result_ecLevel(const zxing_Result* result) +{ + return copy(result->ecLevel()); +} - char* zxing_Result_symbologyIdentifier(const zxing_Result* result) - { - return copy(result->symbologyIdentifier()); - } +char* zxing_Result_symbologyIdentifier(const zxing_Result* result) +{ + return copy(result->symbologyIdentifier()); +} - int zxing_Result_orientation(const zxing_Result* result) - { - return result->orientation(); - } +int zxing_Result_orientation(const zxing_Result* result) +{ + return result->orientation(); +} - bool zxing_Result_isInverted(const zxing_Result* result) - { - return result->isInverted(); - } +bool zxing_Result_isInverted(const zxing_Result* result) +{ + return result->isInverted(); +} - bool zxing_Result_isMirrored(const zxing_Result* result) - { - return result->isMirrored(); - } +bool zxing_Result_isMirrored(const zxing_Result* result) +{ + return result->isMirrored(); +} - /* - * ZXing/ReadBarcode.h - */ +/* + * ZXing/ReadBarcode.h + */ - zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_DecodeHints* hints) - { - auto res = ReadBarcode(*iv, *hints); - return res.format() != BarcodeFormat::None ? new Result(std::move(res)) : NULL; - } +zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_DecodeHints* hints) +{ + auto res = ReadBarcode(*iv, *hints); + return res.format() != BarcodeFormat::None ? new Result(std::move(res)) : NULL; +} - zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_DecodeHints* hints) - { - auto res = ReadBarcodes(*iv, *hints); - return !res.empty() ? new Results(std::move(res)) : NULL; - } +zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_DecodeHints* hints) +{ + auto res = ReadBarcodes(*iv, *hints); + return !res.empty() ? new Results(std::move(res)) : NULL; +} - void zxing_Result_delete(zxing_Result* result) - { - delete result; - } +void zxing_Result_delete(zxing_Result* result) +{ + delete result; +} - void zxing_Results_delete(zxing_Results* results) - { - delete results; - } +void zxing_Results_delete(zxing_Results* results) +{ + delete results; +} - int zxing_Results_size(const zxing_Results* results) - { - return results ? Size(*results) : 0; - } +int zxing_Results_size(const zxing_Results* results) +{ + return results ? Size(*results) : 0; +} - const zxing_Result* zxing_Results_at(const zxing_Results* results, int i) - { - if (!results || i < 0 || i >= Size(*results)) - return NULL; - return &(*results)[i]; - } +const zxing_Result* zxing_Results_at(const zxing_Results* results, int i) +{ + if (!results || i < 0 || i >= Size(*results)) + return NULL; + return &(*results)[i]; } + +} // extern "C" diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index daded89240..b66889516d 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -32,150 +32,150 @@ typedef struct zxing_Results zxing_Results; #endif - /* - * ZXing/ImageView.h - */ - - typedef enum - { - zxing_ImageFormat_None = 0, - zxing_ImageFormat_Lum = 0x01000000, - zxing_ImageFormat_RGB = 0x03000102, - zxing_ImageFormat_BGR = 0x03020100, - zxing_ImageFormat_RGBX = 0x04000102, - zxing_ImageFormat_XRGB = 0x04010203, - zxing_ImageFormat_BGRX = 0x04020100, - zxing_ImageFormat_XBGR = 0x04030201, - } zxing_ImageFormat; - - zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, - int pixStride); - void zxing_ImageView_delete(zxing_ImageView* iv); - - /* - * ZXing/BarcodeFormat.h - */ - - typedef enum - { - zxing_BarcodeFormat_None = 0, - zxing_BarcodeFormat_Aztec = (1 << 0), - zxing_BarcodeFormat_Codabar = (1 << 1), - zxing_BarcodeFormat_Code39 = (1 << 2), - zxing_BarcodeFormat_Code93 = (1 << 3), - zxing_BarcodeFormat_Code128 = (1 << 4), - zxing_BarcodeFormat_DataBar = (1 << 5), - zxing_BarcodeFormat_DataBarExpanded = (1 << 6), - zxing_BarcodeFormat_DataMatrix = (1 << 7), - zxing_BarcodeFormat_EAN8 = (1 << 8), - zxing_BarcodeFormat_EAN13 = (1 << 9), - zxing_BarcodeFormat_ITF = (1 << 10), - zxing_BarcodeFormat_MaxiCode = (1 << 11), - zxing_BarcodeFormat_PDF417 = (1 << 12), - zxing_BarcodeFormat_QRCode = (1 << 13), - zxing_BarcodeFormat_UPCA = (1 << 14), - zxing_BarcodeFormat_UPCE = (1 << 15), - zxing_BarcodeFormat_MicroQRCode = (1 << 16), - - zxing_BarcodeFormat_LinearCodes = zxing_BarcodeFormat_Codabar | zxing_BarcodeFormat_Code39 | zxing_BarcodeFormat_Code93 - | zxing_BarcodeFormat_Code128 | zxing_BarcodeFormat_EAN8 | zxing_BarcodeFormat_EAN13 - | zxing_BarcodeFormat_ITF | zxing_BarcodeFormat_DataBar | zxing_BarcodeFormat_DataBarExpanded - | zxing_BarcodeFormat_UPCA | zxing_BarcodeFormat_UPCE, - zxing_BarcodeFormat_MatrixCodes = zxing_BarcodeFormat_Aztec | zxing_BarcodeFormat_DataMatrix | zxing_BarcodeFormat_MaxiCode - | zxing_BarcodeFormat_PDF417 | zxing_BarcodeFormat_QRCode | zxing_BarcodeFormat_MicroQRCode, - zxing_BarcodeFormat_Any = zxing_BarcodeFormat_LinearCodes | zxing_BarcodeFormat_MatrixCodes, - - zxing_BarcodeFormat_Invalid = -1 /* return value when BarcodeFormatsFromString() throws */ - } zxing_BarcodeFormat; - - typedef zxing_BarcodeFormat zxing_BarcodeFormats; - - zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str); - zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str); - char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format); - - /* - * ZXing/DecodeHints.h - */ - - typedef enum - { - zxing_Binarizer_LocalAverage, - zxing_Binarizer_GlobalHistogram, - zxing_Binarizer_FixedThreshold, - zxing_Binarizer_BoolCast, - } zxing_Binarizer; - - typedef enum - { - zxing_EanAddOnSymbol_Ignore, - zxing_EanAddOnSymbol_Read, - zxing_EanAddOnSymbol_Require, - } zxing_EanAddOnSymbol; - - typedef enum - { - zxing_TextMode_Plain, - zxing_TextMode_ECI, - zxing_TextMode_HRI, - zxing_TextMode_Hex, - zxing_TextMode_Escaped, - } zxing_TextMode; - - zxing_DecodeHints* zxing_DecodeHints_new(); - void zxing_DecodeHints_delete(zxing_DecodeHints* hints); - - void zxing_DecodeHints_setTryHarder(zxing_DecodeHints* hints, bool tryHarder); - void zxing_DecodeHints_setTryRotate(zxing_DecodeHints* hints, bool tryRotate); - void zxing_DecodeHints_setTryInvert(zxing_DecodeHints* hints, bool tryInvert); - void zxing_DecodeHints_setTryDownscale(zxing_DecodeHints* hints, bool tryDownscale); - void zxing_DecodeHints_setIsPure(zxing_DecodeHints* hints, bool isPure); - void zxing_DecodeHints_setReturnErrors(zxing_DecodeHints* hints, bool returnErrors); - void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats formats); - void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer); - void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol); - void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode); - - /* - * ZXing/Result.h - */ - - typedef enum - { - zxing_ContentType_Text, - zxing_ContentType_Binary, - zxing_ContentType_Mixed, - zxing_ContentType_GS1, - zxing_ContentType_ISO15434, - zxing_ContentType_UnknownECI - } zxing_ContentType; - - char* zxing_ContentTypeToString(zxing_ContentType type); - - bool zxing_Result_isValid(const zxing_Result* result); - char* zxing_Result_errorMsg(const zxing_Result* result); - zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result); - zxing_ContentType zxing_Result_contentType(const zxing_Result* result); - uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len); - char* zxing_Result_text(const zxing_Result* result); - char* zxing_Result_ecLevel(const zxing_Result* result); - char* zxing_Result_symbologyIdentifier(const zxing_Result* result); - int zxing_Result_orientation(const zxing_Result* result); - bool zxing_Result_isInverted(const zxing_Result* result); - bool zxing_Result_isMirrored(const zxing_Result* result); - - /* - * ZXing/ReadBarcode.h - */ - - zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_DecodeHints* hints); - zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_DecodeHints* hints); - - void zxing_Result_delete(zxing_Result* result); - void zxing_Results_delete(zxing_Results* results); - - int zxing_Results_size(const zxing_Results* results); - const zxing_Result* zxing_Results_at(const zxing_Results* results, int i); +/* + * ZXing/ImageView.h + */ + +typedef enum +{ + zxing_ImageFormat_None = 0, + zxing_ImageFormat_Lum = 0x01000000, + zxing_ImageFormat_RGB = 0x03000102, + zxing_ImageFormat_BGR = 0x03020100, + zxing_ImageFormat_RGBX = 0x04000102, + zxing_ImageFormat_XRGB = 0x04010203, + zxing_ImageFormat_BGRX = 0x04020100, + zxing_ImageFormat_XBGR = 0x04030201, +} zxing_ImageFormat; + +zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, + int pixStride); +void zxing_ImageView_delete(zxing_ImageView* iv); + +/* + * ZXing/BarcodeFormat.h + */ + +typedef enum +{ + zxing_BarcodeFormat_None = 0, + zxing_BarcodeFormat_Aztec = (1 << 0), + zxing_BarcodeFormat_Codabar = (1 << 1), + zxing_BarcodeFormat_Code39 = (1 << 2), + zxing_BarcodeFormat_Code93 = (1 << 3), + zxing_BarcodeFormat_Code128 = (1 << 4), + zxing_BarcodeFormat_DataBar = (1 << 5), + zxing_BarcodeFormat_DataBarExpanded = (1 << 6), + zxing_BarcodeFormat_DataMatrix = (1 << 7), + zxing_BarcodeFormat_EAN8 = (1 << 8), + zxing_BarcodeFormat_EAN13 = (1 << 9), + zxing_BarcodeFormat_ITF = (1 << 10), + zxing_BarcodeFormat_MaxiCode = (1 << 11), + zxing_BarcodeFormat_PDF417 = (1 << 12), + zxing_BarcodeFormat_QRCode = (1 << 13), + zxing_BarcodeFormat_UPCA = (1 << 14), + zxing_BarcodeFormat_UPCE = (1 << 15), + zxing_BarcodeFormat_MicroQRCode = (1 << 16), + + zxing_BarcodeFormat_LinearCodes = zxing_BarcodeFormat_Codabar | zxing_BarcodeFormat_Code39 | zxing_BarcodeFormat_Code93 + | zxing_BarcodeFormat_Code128 | zxing_BarcodeFormat_EAN8 | zxing_BarcodeFormat_EAN13 + | zxing_BarcodeFormat_ITF | zxing_BarcodeFormat_DataBar | zxing_BarcodeFormat_DataBarExpanded + | zxing_BarcodeFormat_UPCA | zxing_BarcodeFormat_UPCE, + zxing_BarcodeFormat_MatrixCodes = zxing_BarcodeFormat_Aztec | zxing_BarcodeFormat_DataMatrix | zxing_BarcodeFormat_MaxiCode + | zxing_BarcodeFormat_PDF417 | zxing_BarcodeFormat_QRCode | zxing_BarcodeFormat_MicroQRCode, + zxing_BarcodeFormat_Any = zxing_BarcodeFormat_LinearCodes | zxing_BarcodeFormat_MatrixCodes, + + zxing_BarcodeFormat_Invalid = -1 /* return value when BarcodeFormatsFromString() throws */ +} zxing_BarcodeFormat; + +typedef zxing_BarcodeFormat zxing_BarcodeFormats; + +zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str); +zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str); +char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format); + +/* + * ZXing/DecodeHints.h + */ + +typedef enum +{ + zxing_Binarizer_LocalAverage, + zxing_Binarizer_GlobalHistogram, + zxing_Binarizer_FixedThreshold, + zxing_Binarizer_BoolCast, +} zxing_Binarizer; + +typedef enum +{ + zxing_EanAddOnSymbol_Ignore, + zxing_EanAddOnSymbol_Read, + zxing_EanAddOnSymbol_Require, +} zxing_EanAddOnSymbol; + +typedef enum +{ + zxing_TextMode_Plain, + zxing_TextMode_ECI, + zxing_TextMode_HRI, + zxing_TextMode_Hex, + zxing_TextMode_Escaped, +} zxing_TextMode; + +zxing_DecodeHints* zxing_DecodeHints_new(); +void zxing_DecodeHints_delete(zxing_DecodeHints* hints); + +void zxing_DecodeHints_setTryHarder(zxing_DecodeHints* hints, bool tryHarder); +void zxing_DecodeHints_setTryRotate(zxing_DecodeHints* hints, bool tryRotate); +void zxing_DecodeHints_setTryInvert(zxing_DecodeHints* hints, bool tryInvert); +void zxing_DecodeHints_setTryDownscale(zxing_DecodeHints* hints, bool tryDownscale); +void zxing_DecodeHints_setIsPure(zxing_DecodeHints* hints, bool isPure); +void zxing_DecodeHints_setReturnErrors(zxing_DecodeHints* hints, bool returnErrors); +void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats formats); +void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer); +void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol); +void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode); + +/* + * ZXing/Result.h + */ + +typedef enum +{ + zxing_ContentType_Text, + zxing_ContentType_Binary, + zxing_ContentType_Mixed, + zxing_ContentType_GS1, + zxing_ContentType_ISO15434, + zxing_ContentType_UnknownECI +} zxing_ContentType; + +char* zxing_ContentTypeToString(zxing_ContentType type); + +bool zxing_Result_isValid(const zxing_Result* result); +char* zxing_Result_errorMsg(const zxing_Result* result); +zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result); +zxing_ContentType zxing_Result_contentType(const zxing_Result* result); +uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len); +char* zxing_Result_text(const zxing_Result* result); +char* zxing_Result_ecLevel(const zxing_Result* result); +char* zxing_Result_symbologyIdentifier(const zxing_Result* result); +int zxing_Result_orientation(const zxing_Result* result); +bool zxing_Result_isInverted(const zxing_Result* result); +bool zxing_Result_isMirrored(const zxing_Result* result); + +/* + * ZXing/ReadBarcode.h + */ + +zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_DecodeHints* hints); +zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_DecodeHints* hints); + +void zxing_Result_delete(zxing_Result* result); +void zxing_Results_delete(zxing_Results* results); + +int zxing_Results_size(const zxing_Results* results); +const zxing_Result* zxing_Results_at(const zxing_Results* results, int i); #ifdef __cplusplus } From 63dda1c2aa43923825f337b47fe98c84b9c39a6a Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 25 Jul 2023 01:20:13 +0200 Subject: [PATCH 011/587] C-API: add README.md --- README.md | 1 + wrappers/c/README.md | 47 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 wrappers/c/README.md diff --git a/README.md b/README.md index 3811192da0..605465dee4 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Thanks a lot for your contribution! * Thread safe * Wrappers/Bindings for: * [Android](wrappers/android/README.md) + * [C](wrappers/c/README.md) * [iOS](wrappers/ios/README.md) * [Python](wrappers/python/README.md) * [WebAssembly](wrappers/wasm/README.md) diff --git a/wrappers/c/README.md b/wrappers/c/README.md new file mode 100644 index 0000000000..399eae8d0d --- /dev/null +++ b/wrappers/c/README.md @@ -0,0 +1,47 @@ +# C bindings for zxing-cpp + +This is a preview/proposal for a C-API to zxing-cpp. If this turns out to be useful and practical, it will most likely be merged into the library itself so that it will be trivially accessible for everyone. If you have any comments or feedback, please have a look at https://github.com/zxing-cpp/zxing-cpp/discussions/583. + +## Installation + +Probably the easiest way to play with the C-API is to either just modify the [zxing-c-test.c](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c-test.c) file or copy the files [zxing-c.h](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c.h) and [zxing-c.cpp](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c.cpp) into your own project and link it to the standard zxing-cpp library. + +## Usage + +The following is close to the most trivial use case scenario that is supported. + +```c +#include "zxing-c.h" + +int main(int argc, char** argv) +{ + int width, height; + unsigned char* data; + /* load your image data from somewhere. ImageFormat_Lum assumes grey scale image data. */ + + zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); + + zxing_DecodeHints* hints = zxing_DecodeHints_new(); + /* set DecodeHints properties, if requried */ + + zxing_Result* result = zxing_ReadBarcode(iv, hints); + + if (result) { + printf("Format : %s\n", zxing_BarcodeFormatToString(zxing_Result_format(result))); + + const char* text = zxing_Result_text(result); + printf("Text : %s\n", text); + free(text); + + zxing_Result_delete(result); + } else { + printf("No barcode found\n"); + } + + zxing_ImageView_delete(iv); + zxing_DecodeHints_delete(hints); + + return 0; +} +``` + From 6bd32f4785f3de4899b2d7dd938e6a8f1d46fb14 Mon Sep 17 00:00:00 2001 From: Leonnardo Verol <6124410+LeonnardoVerol@users.noreply.github.com> Date: Sun, 30 Jul 2023 18:01:05 -0300 Subject: [PATCH 012/587] Fix QT 6 QML Crash & Enable QML Shape (#597) * Change ComboBox textRole from "displayName" to "description" * Create mapPointToItem Function --------- Co-authored-by: axxel --- example/ZXingQt6CamReader.qml | 97 ++++++++++++++++++++++++----------- 1 file changed, 66 insertions(+), 31 deletions(-) diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index ceaf431617..ecb348e410 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -86,7 +86,7 @@ Window { id: camerasComboBox Layout.fillWidth: true model: devices.videoInputs - textRole: "displayName" + textRole: "description" currentIndex: 0 } } @@ -96,36 +96,71 @@ Window { Layout.fillHeight: true Layout.fillWidth: true -// Shape { -// id: polygon -// anchors.fill: parent -// visible: points.length == 4 -// ShapePath { -// strokeWidth: 3 -// strokeColor: "red" -// strokeStyle: ShapePath.SolidLine -// fillColor: "transparent" -// //TODO: really? I don't know qml... -// startX: videoOutput.mapPointToItem(points[3]).x -// startY: videoOutput.mapPointToItem(points[3]).y -// PathLine { -// x: videoOutput.mapPointToItem(points[0]).x -// y: videoOutput.mapPointToItem(points[0]).y -// } -// PathLine { -// x: videoOutput.mapPointToItem(points[1]).x -// y: videoOutput.mapPointToItem(points[1]).y -// } -// PathLine { -// x: videoOutput.mapPointToItem(points[2]).x -// y: videoOutput.mapPointToItem(points[2]).y -// } -// PathLine { -// x: videoOutput.mapPointToItem(points[3]).x -// y: videoOutput.mapPointToItem(points[3]).y -// } -// } -// } + function mapPointToItem(point) + { + if (videoOutput.sourceRect.width === 0 || videoOutput.sourceRect.height === 0) + return Qt.point(0, 0); + + let dx = point.x; + let dy = point.y; + + if ((videoOutput.orientation % 180) == 0) + { + dx = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; + dy = dy * videoOutput.contentRect.height / videoOutput.sourceRect.height; + } + else + { + dx = dx * videoOutput.contentRect.height / videoOutput.sourceRect.height; + dy = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; + } + + switch ((videoOutput.orientation + 360) % 360) + { + case 0: + default: + return Qt.point(videoOutput.contentRect.x + dx, videoOutput.contentRect.y + dy); + case 90: + return Qt.point(videoOutput.contentRect.x + dy, videoOutput.contentRect.y + videoOutput.contentRect.height - dx); + case 180: + return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dx, videoOutput.contentRect.y + videoOutput.contentRect.height -dy); + case 270: + return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dy, videoOutput.contentRect.y + dx); + } + } + + Shape { + id: polygon + anchors.fill: parent + visible: control.points.length === 4 + + ShapePath { + strokeWidth: 3 + strokeColor: "red" + strokeStyle: ShapePath.SolidLine + fillColor: "transparent" + //TODO: really? I don't know qml... + startX: videoOutput.mapPointToItem(points[3]).x + startY: videoOutput.mapPointToItem(points[3]).y + + PathLine { + x: videoOutput.mapPointToItem(points[0]).x + y: videoOutput.mapPointToItem(points[0]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[1]).x + y: videoOutput.mapPointToItem(points[1]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[2]).x + y: videoOutput.mapPointToItem(points[2]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[3]).x + y: videoOutput.mapPointToItem(points[3]).y + } + } + } Label { id: info From 0d5093941b46837789c1287c16248c25ca51ebbc Mon Sep 17 00:00:00 2001 From: Marco Ribeiro Date: Wed, 17 May 2023 20:48:20 -0300 Subject: [PATCH 013/587] Expose Result::ecLevel method as python property --- wrappers/python/zxing.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index 79db3c480d..3e35d74dba 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -227,6 +227,9 @@ PYBIND11_MODULE(zxingcpp, m) .def_property_readonly("symbology_identifier", &Result::symbologyIdentifier, ":return: decoded symbology idendifier\n" ":rtype: str") + .def_property_readonly("ec_level", &Result::ecLevel, + ":return: error correction level of the symbol (empty string if not applicable)\n" + ":rtype: str") .def_property_readonly("content_type", &Result::contentType, ":return: content type of symbol\n" ":rtype: zxing.ContentType") From 82d38d504a2c552ad97a29396e496cc1797da4ae Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Thu, 7 Sep 2023 17:19:49 +0700 Subject: [PATCH 014/587] Python: Fix wrapper build on MSVC not having __cplusplus for BitHacks The `BitHacks.h` header needs the `__cplusplus` define to determine whether to include ``. The MSVC compiler only defines this when it is passed the `/Zc:__cplusplus` flag. The main library was already setting this flag, but it not set when building the Python wrapper, causing compilation to fail because `countr_zero` and `popcount` are being used without `` being included. Fixes #612 --- core/CMakeLists.txt | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a06cd6e355..37480e2f55 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -22,6 +22,11 @@ if (WINRT) -DWINRT ) endif() +if (MSVC) + set (ZXING_CORE_DEFINES ${ZXING_CORE_DEFINES} + /Zc:__cplusplus + ) +endif() set (ZXING_CORE_LOCAL_DEFINES $<$:-DZXING_BUILD_READERS> @@ -34,7 +39,6 @@ if (MSVC) -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS -DNOMINMAX - /Zc:__cplusplus ) else() set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} From 0e42395e92a9f07ca93bbf061aa4fb7a6af253f9 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Sep 2023 23:35:26 +0200 Subject: [PATCH 015/587] python: increase consistency and readability of demo code Thanks to @PanderMusubi and his work on #487. --- wrappers/python/README.md | 12 +++++++----- wrappers/python/demo_reader.py | 7 +++++-- 2 files changed, 12 insertions(+), 7 deletions(-) diff --git a/wrappers/python/README.md b/wrappers/python/README.md index 52b23f9b14..5366af17da 100644 --- a/wrappers/python/README.md +++ b/wrappers/python/README.md @@ -19,14 +19,16 @@ python setup.py install ## Usage ```python -import cv2 -import zxingcpp +import cv2, zxingcpp -img = cv2.imread('myimage.png') +img = cv2.imread('test.png') results = zxingcpp.read_barcodes(img) for result in results: - print("Found barcode:\n Text: '{}'\n Format: {}\n Position: {}" - .format(result.text, result.format, result.position)) + print('Found barcode:' + f'\n Text: "{result.text}"' + f'\n Format: {result.format}' + f'\n Content: {result.content_type}' + f'\n Position: {result.position}') if len(results) == 0: print("Could not find any barcode.") ``` diff --git a/wrappers/python/demo_reader.py b/wrappers/python/demo_reader.py index c2a3ad9468..60a0a11fbb 100644 --- a/wrappers/python/demo_reader.py +++ b/wrappers/python/demo_reader.py @@ -4,7 +4,10 @@ img = Image.open(sys.argv[1]) results = zxingcpp.read_barcodes(img) for result in results: - print("Found barcode:\n Text: '{}'\n Format: {}\n Content: {}\n Position: {}" - .format(result.text, result.format, result.content_type, result.position)) + print('Found barcode:' + f'\n Text: "{result.text}"' + f'\n Format: {result.format}' + f'\n Content: {result.content_type}' + f'\n Position: {result.position}') if len(results) == 0: print("Could not find any barcode.") From 6672f64b00f89ba0d37b04e95ca1feeafa9c33d4 Mon Sep 17 00:00:00 2001 From: billmccartney Date: Mon, 11 Sep 2023 16:52:21 -0400 Subject: [PATCH 016/587] Python Multithreading Support via GIL Enabled parallel Python support by disabling the GIL during ReadBarcodes. This allows multiple threads to decode different images at the same time in Python. --- wrappers/python/zxing.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index 3e35d74dba..ab615477f1 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -90,6 +90,8 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t } const auto bytes = image.data(); + // Disables the GIL during zxing processing (restored automatically upon completion) + py::gil_scoped_release release; return ReadBarcodes({bytes, width, height, imgfmt, width * channels, channels}, hints); } From b71358af120d3cb6608bffe9e7e2421d7b0757e2 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Thu, 14 Sep 2023 18:11:05 +0900 Subject: [PATCH 017/587] Python: Remove numpy requirement, use buffer protocol instead (#615) Partly taken from https://github.com/zxing-cpp/zxing-cpp/issues/283#issuecomment-1517021218 This removes the requirement of `read_barcode` / `write_barcode` to have Numpy installed and uses [buffer protocol](https://pybind11.readthedocs.io/en/stable/advanced/pycpp/numpy.html#buffer-protocol) instead. Numpy arrays can already be converted to a buffer, for PIL images we get the `__array_interface__` and use its values to create a memory view with the right shape. The `write_barcode` function now returns a buffer instead of a Numpy array. This buffer also supports `__array_interface__` for easy conversion to PIL images or Numpy arrays. * For PIL: `img = Image.fromarray(result, "L")` * For Numpy: `img = np.array(result)` fixes #283 --------- Co-authored-by: axxel --- wrappers/python/setup.py | 1 - wrappers/python/test.py | 46 ++++++++++-- wrappers/python/zxing.cpp | 142 +++++++++++++++++++++++++++----------- 3 files changed, 144 insertions(+), 45 deletions(-) diff --git a/wrappers/python/setup.py b/wrappers/python/setup.py index 232b5a731f..370b2e522c 100644 --- a/wrappers/python/setup.py +++ b/wrappers/python/setup.py @@ -76,7 +76,6 @@ def build_extension(self, ext): "Topic :: Multimedia :: Graphics", ], python_requires=">=3.6", - install_requires=["numpy"], ext_modules=[CMakeExtension('zxingcpp')], cmdclass=dict(build_ext=CMakeBuild), zip_safe=False, diff --git a/wrappers/python/test.py b/wrappers/python/test.py index 5d7f8cf30e..266b1f57fe 100644 --- a/wrappers/python/test.py +++ b/wrappers/python/test.py @@ -1,5 +1,6 @@ import importlib.util import unittest +import math import zxingcpp @@ -16,7 +17,6 @@ def test_format(self): self.assertEqual(zxingcpp.barcode_formats_from_str('ITF, qrcode'), BF.ITF | BF.QRCode) -@unittest.skipIf(not has_numpy, "need numpy for read/write tests") class TestReadWrite(unittest.TestCase): def check_res(self, res, format, text): @@ -60,7 +60,19 @@ def test_write_read_multi_cycle(self): res = zxingcpp.read_barcodes(img)[0] self.check_res(res, format, text) - def test_failed_read(self): + @staticmethod + def zeroes(shape): + return memoryview(b"0" * math.prod(shape)).cast("B", shape=shape) + + def test_failed_read_buffer(self): + res = zxingcpp.read_barcode( + self.zeroes((100, 100)), formats=BF.EAN8 | BF.Aztec, binarizer=zxingcpp.Binarizer.BoolCast + ) + + self.assertEqual(res, None) + + @unittest.skipIf(not has_numpy, "need numpy for read/write tests") + def test_failed_read_numpy(self): import numpy as np res = zxingcpp.read_barcode( np.zeros((100, 100), np.uint8), formats=BF.EAN8 | BF.Aztec, binarizer=zxingcpp.Binarizer.BoolCast @@ -68,6 +80,23 @@ def test_failed_read(self): self.assertEqual(res, None) + def test_write_read_cycle_buffer(self): + format = BF.QRCode + text = "I have the best words." + img = zxingcpp.write_barcode(format, text) + + self.check_res(zxingcpp.read_barcode(img), format, text) + + @unittest.skipIf(not has_numpy, "need numpy for read/write tests") + def test_write_read_cycle_numpy(self): + import numpy as np + format = BF.QRCode + text = "I have the best words." + img = zxingcpp.write_barcode(format, text) + img = np.array(img) + + self.check_res(zxingcpp.read_barcode(img), format, text) + @unittest.skipIf(not has_pil, "need PIL for read/write tests") def test_write_read_cycle_pil(self): from PIL import Image @@ -84,13 +113,20 @@ def test_write_read_cycle_pil(self): def test_read_invalid_type(self): self.assertRaisesRegex( - TypeError, "Could not convert to numpy array.", zxingcpp.read_barcode, "foo" + TypeError, "Invalid input: does not support the buffer protocol.", zxingcpp.read_barcode, "foo" + ) + + def test_read_invalid_numpy_array_channels_buffer(self): + self.assertRaisesRegex( + ValueError, "Unsupported number of channels for buffer: 4", zxingcpp.read_barcode, + self.zeroes((100, 100, 4)) ) - def test_read_invalid_numpy_array_channels(self): + @unittest.skipIf(not has_numpy, "need numpy for read/write tests") + def test_read_invalid_numpy_array_channels_numpy(self): import numpy as np self.assertRaisesRegex( - ValueError, "Unsupported number of channels for numpy array: 4", zxingcpp.read_barcode, + ValueError, "Unsupported number of channels for buffer: 4", zxingcpp.read_barcode, np.zeros((100, 100, 4), np.uint8) ) diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index ab615477f1..e109141bf1 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -13,20 +13,20 @@ // Writer #include "BitMatrix.h" +#include "Matrix.h" #include "MultiFormatWriter.h" -#include #include #include #include +#include #include +#include #include using namespace ZXing; namespace py = pybind11; - -// Numpy array wrapper class for images (either BGR or GRAYSCALE) -using Image = py::array_t; +using namespace pybind11::literals; // to bring in the `_a` literal std::ostream& operator<<(std::ostream& os, const Position& points) { for (const auto& p : points) @@ -49,36 +49,84 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t .setMaxNumberOfSymbols(max_number_of_symbols) .setEanAddOnSymbol(ean_add_on_symbol); const auto _type = std::string(py::str(py::type::of(_image))); - Image image; + py::buffer buffer; ImageFormat imgfmt = ImageFormat::None; try { - if (_type.find("PIL.") != std::string::npos) { - _image.attr("load")(); - const auto mode = _image.attr("mode").cast(); - if (mode == "L") - imgfmt = ImageFormat::Lum; - else if (mode == "RGB") - imgfmt = ImageFormat::RGB; - else if (mode == "RGBA") - imgfmt = ImageFormat::RGBX; - else { - // Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL. - _image = _image.attr("convert")("L"); - imgfmt = ImageFormat::Lum; + if (py::hasattr(_image, "__array_interface__")) { + if (_type.find("PIL.") != std::string::npos) { + _image.attr("load")(); + const auto mode = _image.attr("mode").cast(); + if (mode == "L") + imgfmt = ImageFormat::Lum; + else if (mode == "RGB") + imgfmt = ImageFormat::RGB; + else if (mode == "RGBA") + imgfmt = ImageFormat::RGBX; + else { + // Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL. + _image = _image.attr("convert")("L"); + imgfmt = ImageFormat::Lum; + } + } + + auto ai = _image.attr("__array_interface__").cast(); + auto ashape = ai["shape"].cast(); + + if (ai.contains("data")) { + auto adata = ai["data"]; + + if (py::isinstance(adata)) { + auto data_ptr = adata.cast()[0].cast(); + auto data_len = Reduce(ashape.cast>(), 1, std::multiplies{}); + buffer = py::memoryview::from_memory(reinterpret_cast(data_ptr), data_len, true); + } else if (py::isinstance(adata)) { + // Numpy and our own __array_interface__ passes data as a buffer/bytes object + buffer = adata.cast(); + } else { + throw py::type_error("No way to get data from __array_interface__"); + } + } else { + buffer = _image.cast(); + } + + py::tuple bshape; + if (py::hasattr(buffer, "shape")) { + bshape = buffer.attr("shape").cast(); + } + + // We need to check if the shape is equal because memoryviews can only be cast from 1D + // to ND and in reverse, not from ND to ND. If the shape is already correct, as with our + // return value from write_barcode, we don't need to cast. There are libraries (PIL for + // example) that pass 1D data here, in that case we need to cast because the later code + // expects a buffer in the correct shape. + if (!ashape.equal(bshape)) { + auto bufferview = py::memoryview(buffer); + buffer = bufferview.attr("cast")("B", ashape).cast(); } + } else { + buffer = _image.cast(); } - image = _image.cast(); #if PYBIND11_VERSION_HEX > 0x02080000 // py::raise_from is available starting from 2.8.0 } catch (py::error_already_set &e) { - py::raise_from(e, PyExc_TypeError, ("Could not convert " + _type + " to numpy array of dtype 'uint8'.").c_str()); + py::raise_from(e, PyExc_TypeError, ("Invalid input: " + _type + " does not support the buffer protocol.").c_str()); throw py::error_already_set(); #endif } catch (...) { - throw py::type_error("Could not convert " + _type + " to numpy array. Expecting a PIL Image or numpy array."); + throw py::type_error("Invalid input: " + _type + " does not support the buffer protocol."); } - const auto height = narrow_cast(image.shape(0)); - const auto width = narrow_cast(image.shape(1)); - const auto channels = image.ndim() == 2 ? 1 : narrow_cast(image.shape(2)); + + /* Request a buffer descriptor from Python */ + py::buffer_info info = buffer.request(); + + if (info.format != py::format_descriptor::format()) + throw py::type_error("Incompatible buffer format: expected a uint8_t array."); + + if (info.ndim != 2 && info.ndim != 3) + throw py::type_error("Incompatible buffer dimension (needs to be 2 or 3)."); + + const auto height = narrow_cast(info.shape[0]); + const auto width = narrow_cast(info.shape[1]); + const auto channels = info.ndim == 2 ? 1 : narrow_cast(info.shape[2]); if (imgfmt == ImageFormat::None) { // Assume grayscale or BGR image depending on channels number if (channels == 1) @@ -86,10 +134,10 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t else if (channels == 3) imgfmt = ImageFormat::BGR; else - throw py::value_error("Unsupported number of channels for numpy array: " + std::to_string(channels)); + throw py::value_error("Unsupported number of channels for buffer: " + std::to_string(channels)); } - const auto bytes = image.data(); + const auto bytes = static_cast(info.ptr); // Disables the GIL during zxing processing (restored automatically upon completion) py::gil_scoped_release release; return ReadBarcodes({bytes, width, height, imgfmt, width * channels, channels}, hints); @@ -108,17 +156,11 @@ Results read_barcodes(py::object _image, const BarcodeFormats& formats, bool try return read_barcodes_impl(_image, formats, try_rotate, try_downscale, text_mode, binarizer, is_pure, ean_add_on_symbol); } -Image write_barcode(BarcodeFormat format, std::string text, int width, int height, int quiet_zone, int ec_level) +Matrix write_barcode(BarcodeFormat format, std::string text, int width, int height, int quiet_zone, int ec_level) { auto writer = MultiFormatWriter(format).setEncoding(CharacterSet::UTF8).setMargin(quiet_zone).setEccLevel(ec_level); auto bitmap = writer.encode(text, width, height); - - auto result = Image({bitmap.height(), bitmap.width()}); - auto r = result.mutable_unchecked<2>(); - for (py::ssize_t y = 0; y < r.shape(0); y++) - for (py::ssize_t x = 0; x < r.shape(1); x++) - r(y, x) = bitmap.get(narrow_cast(x), narrow_cast(y)) ? 0 : 255; - return result; + return ToMatrix(bitmap); } @@ -265,8 +307,9 @@ PYBIND11_MODULE(zxingcpp, m) py::arg("is_pure") = false, py::arg("ean_add_on_symbol") = EanAddOnSymbol::Ignore, "Read (decode) a barcode from a numpy BGR or grayscale image array or from a PIL image.\n\n" - ":type image: numpy.ndarray|PIL.Image.Image\n" + ":type image: buffer|numpy.ndarray|PIL.Image.Image\n" ":param image: The image object to decode. The image can be either:\n" + " - a buffer with the correct shape, use .cast on memory view to convert\n" " - a numpy array containing image either in grayscale (1 byte per pixel) or BGR mode (3 bytes per pixel)\n" " - a PIL Image\n" ":type formats: zxing.BarcodeFormat|zxing.BarcodeFormats\n" @@ -302,8 +345,9 @@ PYBIND11_MODULE(zxingcpp, m) py::arg("is_pure") = false, py::arg("ean_add_on_symbol") = EanAddOnSymbol::Ignore, "Read (decode) multiple barcodes from a numpy BGR or grayscale image array or from a PIL image.\n\n" - ":type image: numpy.ndarray|PIL.Image.Image\n" + ":type image: buffer|numpy.ndarray|PIL.Image.Image\n" ":param image: The image object to decode. The image can be either:\n" + " - a buffer with the correct shape, use .cast on memory view to convert\n" " - a numpy array containing image either in grayscale (1 byte per pixel) or BGR mode (3 bytes per pixel)\n" " - a PIL Image\n" ":type formats: zxing.BarcodeFormat|zxing.BarcodeFormats\n" @@ -329,6 +373,26 @@ PYBIND11_MODULE(zxingcpp, m) ":rtype: zxing.Result\n" ":return: a list of zxing results containing decoded symbols, the list is empty if none is found" ); + + py::class_>(m, "Bitmap", py::buffer_protocol()) + .def_property_readonly( + "__array_interface__", + [](const Matrix& m) { + return py::dict("version"_a = 3, "data"_a = m, "shape"_a = py::make_tuple(m.height(), m.width()), "typestr"_a = "|u1"); + }) + .def_property_readonly("shape", [](const Matrix& m) { return py::make_tuple(m.height(), m.width()); }) + .def_buffer([](const Matrix& m) -> py::buffer_info { + return { + const_cast(m.data()), // Pointer to buffer + sizeof(uint8_t), // Size of one scalar + py::format_descriptor::format(), // Python struct-style format descriptor + 2, // Number of dimensions + {m.height(), m.width()}, // Buffer dimensions + {sizeof(uint8_t) * m.width(), sizeof(uint8_t)}, // Strides (in bytes) for each index + true // read-only + }; + }); + m.def("write_barcode", &write_barcode, py::arg("format"), py::arg("text"), @@ -336,7 +400,7 @@ PYBIND11_MODULE(zxingcpp, m) py::arg("height") = 0, py::arg("quiet_zone") = -1, py::arg("ec_level") = -1, - "Write (encode) a text into a barcode and return numpy (grayscale) image array\n\n" + "Write (encode) a text into a barcode and return 8-bit grayscale bitmap buffer\n\n" ":type format: zxing.BarcodeFormat\n" ":param format: format of the barcode to create\n" ":type text: str\n" @@ -351,7 +415,7 @@ PYBIND11_MODULE(zxingcpp, m) ":param quiet_zone: minimum size (in pixels) of the quiet zone around barcode. If undefined (or set to -1), \n" " the minimum quiet zone of respective barcode is used." ":type ec_level: int\n" - ":param ec_level: error correction level of the barcode\n" - " (Used for Aztec, PDF417, and QRCode only)." + ":param ec_level: error correction level of the barcode (Used for Aztec, PDF417, and QRCode only).\n" + ":rtype: zxing.Bitmap\n" ); } From 73761c0ccabd6d5a940617eb5a0fee9e69670bfe Mon Sep 17 00:00:00 2001 From: Greg Wittel Date: Thu, 14 Sep 2023 09:01:43 -0700 Subject: [PATCH 018/587] Expose return_errors parameter to Python wrapper APIs. By default errors will not be returned. When requested, results with errors will only be returned when possible barcodes were found. --- wrappers/python/zxing.cpp | 48 +++++++++++++++++++++++++++++++-------- 1 file changed, 39 insertions(+), 9 deletions(-) diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index e109141bf1..38b1ee7a66 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -37,7 +37,8 @@ std::ostream& operator<<(std::ostream& os, const Position& points) { } auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool try_rotate, bool try_downscale, TextMode text_mode, - Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol, uint8_t max_number_of_symbols = 0xff) + Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol, bool return_errors, + uint8_t max_number_of_symbols = 0xff) { const auto hints = DecodeHints() .setFormats(formats) @@ -47,7 +48,8 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t .setBinarizer(binarizer) .setIsPure(is_pure) .setMaxNumberOfSymbols(max_number_of_symbols) - .setEanAddOnSymbol(ean_add_on_symbol); + .setEanAddOnSymbol(ean_add_on_symbol) + .setReturnErrors(return_errors); const auto _type = std::string(py::str(py::type::of(_image))); py::buffer buffer; ImageFormat imgfmt = ImageFormat::None; @@ -144,16 +146,19 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t } std::optional read_barcode(py::object _image, const BarcodeFormats& formats, bool try_rotate, bool try_downscale, - TextMode text_mode, Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol) + TextMode text_mode, Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol, + bool return_errors) { - auto res = read_barcodes_impl(_image, formats, try_rotate, try_downscale, text_mode, binarizer, is_pure, ean_add_on_symbol, 1); + auto res = read_barcodes_impl(_image, formats, try_rotate, try_downscale, text_mode, binarizer, is_pure, ean_add_on_symbol, + return_errors, 1); return res.empty() ? std::nullopt : std::optional(res.front()); } Results read_barcodes(py::object _image, const BarcodeFormats& formats, bool try_rotate, bool try_downscale, - TextMode text_mode, Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol) + TextMode text_mode, Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol, bool return_errors) { - return read_barcodes_impl(_image, formats, try_rotate, try_downscale, text_mode, binarizer, is_pure, ean_add_on_symbol); + return read_barcodes_impl(_image, formats, try_rotate, try_downscale, text_mode, binarizer, is_pure, ean_add_on_symbol, + return_errors); } Matrix write_barcode(BarcodeFormat format, std::string text, int width, int height, int quiet_zone, int ec_level) @@ -255,6 +260,20 @@ PYBIND11_MODULE(zxingcpp, m) oss << pos; return oss.str(); }); + py::enum_(m, "ErrorType", "") + .value("None", Error::Type::None, "No error") + .value("Format", Error::Type::Format, "Data format error") + .value("Checksum", Error::Type::Checksum, "Checksum error") + .value("Unsupported", Error::Type::Unsupported, "Unsupported content error") + .export_values(); + py::class_(m, "Error", "Barcode reading error") + .def_property_readonly("type", &Error::type, + ":return: Error type\n" + ":rtype: zxing.ErrorType") + .def_property_readonly("message", &Error::msg, + ":return: Error message\n" + ":rtype: str") + .def("__str__", [](Error e) { return ToString(e); }); py::class_(m, "Result", "Result of barcode reading") .def_property_readonly("valid", &Result::isValid, ":return: whether or not result is valid (i.e. a symbol was found)\n" @@ -282,7 +301,11 @@ PYBIND11_MODULE(zxingcpp, m) ":rtype: zxing.Position") .def_property_readonly("orientation", &Result::orientation, ":return: orientation (in degree) of the decoded symbol\n" - ":rtype: int"); + ":rtype: int") + .def_property_readonly( + "error", [](const Result& res) { return res.error() ? std::optional(res.error()) : std::nullopt; }, + ":return: Error code or None\n" + ":rtype: zxing.Error"); m.def("barcode_format_from_str", &BarcodeFormatFromString, py::arg("str"), "Convert string to BarcodeFormat\n\n" @@ -306,6 +329,7 @@ PYBIND11_MODULE(zxingcpp, m) py::arg("binarizer") = Binarizer::LocalAverage, py::arg("is_pure") = false, py::arg("ean_add_on_symbol") = EanAddOnSymbol::Ignore, + py::arg("return_errors") = false, "Read (decode) a barcode from a numpy BGR or grayscale image array or from a PIL image.\n\n" ":type image: buffer|numpy.ndarray|PIL.Image.Image\n" ":param image: The image object to decode. The image can be either:\n" @@ -332,6 +356,9 @@ PYBIND11_MODULE(zxingcpp, m) ":type ean_add_on_symbol: zxing.EanAddOnSymbol\n" ":param ean_add_on_symbol: Specify whether to Ignore, Read or Require EAN-2/5 add-on symbols while scanning \n" " EAN/UPC codes. Default is ``Ignore``.\n" + ":type return_errors: bool\n" + ":param return_errors: Set to True to return the barcodes with errors as well (e.g. checksum errors); see ``Result.error``.\n" + " Default is False." ":rtype: zxing.Result\n" ":return: a zxing result containing decoded symbol if found, None otherwise" ); @@ -344,6 +371,7 @@ PYBIND11_MODULE(zxingcpp, m) py::arg("binarizer") = Binarizer::LocalAverage, py::arg("is_pure") = false, py::arg("ean_add_on_symbol") = EanAddOnSymbol::Ignore, + py::arg("return_errors") = false, "Read (decode) multiple barcodes from a numpy BGR or grayscale image array or from a PIL image.\n\n" ":type image: buffer|numpy.ndarray|PIL.Image.Image\n" ":param image: The image object to decode. The image can be either:\n" @@ -370,10 +398,12 @@ PYBIND11_MODULE(zxingcpp, m) ":type ean_add_on_symbol: zxing.EanAddOnSymbol\n" ":param ean_add_on_symbol: Specify whether to Ignore, Read or Require EAN-2/5 add-on symbols while scanning \n" " EAN/UPC codes. Default is ``Ignore``.\n" + ":type return_errors: bool\n" + ":param return_errors: Set to True to return the barcodes with errors as well (e.g. checksum errors); see ``Result.error``.\n" + " Default is False.\n" ":rtype: zxing.Result\n" ":return: a list of zxing results containing decoded symbols, the list is empty if none is found" - ); - + ); py::class_>(m, "Bitmap", py::buffer_protocol()) .def_property_readonly( "__array_interface__", From 18a722a443855063a00b27d03d33794fa573a61f Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 15 Sep 2023 10:04:22 +0200 Subject: [PATCH 019/587] README: add `--config Release` to cmake build instructions Multi-Config build tools like Visual Studio need this to build a Release. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 605465dee4..4432c839cb 100644 --- a/README.md +++ b/README.md @@ -77,7 +77,7 @@ These are the generic instructions to build the library on Windows/macOS/Linux. ``` git clone https://github.com/zxing-cpp/zxing-cpp.git --single-branch --depth 1 cmake -S zxing-cpp -B zxing-cpp.release -DCMAKE_BUILD_TYPE=Release -cmake --build zxing-cpp.release -j8 +cmake --build zxing-cpp.release -j8 --config Release ``` [Note: binary packages are available for/as From 9497e1e1b440976967c8ea6639ac56905e95a9c2 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 20 Sep 2023 09:25:20 +0200 Subject: [PATCH 020/587] DM: don't yield (return) empty DetectorResult in c++20 version of `Detect` --- core/src/datamatrix/DMDetector.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index f1b9943d56..6d2a62d43e 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -920,7 +920,8 @@ DetectorResults Detect(const BitMatrix& image, bool tryHarder, bool tryRotate, b { #ifdef __cpp_impl_coroutine if (isPure) { - co_yield DetectPure(image); + if (auto r = DetectPure(image); r.isValid()) + co_yield std::move(r); } else { bool found = false; for (auto&& r : DetectNew(image, tryHarder, tryRotate)) { From 94f838edcf870c267eadb7e27b09aa42a400dc5f Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 20 Sep 2023 09:27:14 +0200 Subject: [PATCH 021/587] EXPERIMENTAL_API: fix stack-use-after-scope --- core/src/ReadBarcode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 16fd25b264..383a5bc948 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -148,8 +148,8 @@ Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) std::unique_ptr closedReader; #ifdef ZXING_BUILD_EXPERIMENTAL_API auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode; + DecodeHints closedHints = hints; if (hints.tryDenoise() && hints.hasFormat(formatsBenefittingFromClosing)) { - DecodeHints closedHints = hints; closedHints.setFormats((hints.formats().empty() ? BarcodeFormat::Any : hints.formats()) & formatsBenefittingFromClosing); closedReader = std::make_unique(closedHints); } From 4df255635bfd9a878ec1f33e9cd6179d872dc796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Karmaz=C3=ADn?= Date: Thu, 21 Sep 2023 15:13:32 +0200 Subject: [PATCH 022/587] Android wrapper: Modernize build files This commit updates the Android build files. No source files were changed. Notable changes: - Convert build scripts to Kotlin (build.gradle -> build.gradle.kts) I tried to introduce this change incrementally to keep the build file history tracked by Git, but was unable to do so. - Kotlin 1.9.10 - Gradle 8.3, including the Gradle wrapper - Use Gradle Version Catalog - Android Gradle Plugin 8.1.1 --- .github/workflows/ci.yml | 4 + wrappers/android/app/build.gradle | 59 ---- wrappers/android/app/build.gradle.kts | 50 +++ wrappers/android/app/proguard-rules.pro | 4 +- wrappers/android/build.gradle | 26 -- wrappers/android/build.gradle.kts | 11 + wrappers/android/gradle.properties | 7 +- wrappers/android/gradle/libs.versions.toml | 32 ++ .../android/gradle/wrapper/gradle-wrapper.jar | Bin 54329 -> 63721 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 +- wrappers/android/gradlew | 301 +++++++++++------- wrappers/android/gradlew.bat | 56 ++-- wrappers/android/settings.gradle | 3 - wrappers/android/settings.gradle.kts | 19 ++ wrappers/android/zxingcpp/build.gradle | 53 --- wrappers/android/zxingcpp/build.gradle.kts | 49 +++ .../zxingcpp/src/main/AndroidManifest.xml | 4 +- 17 files changed, 396 insertions(+), 287 deletions(-) delete mode 100644 wrappers/android/app/build.gradle create mode 100644 wrappers/android/app/build.gradle.kts delete mode 100644 wrappers/android/build.gradle create mode 100644 wrappers/android/build.gradle.kts create mode 100644 wrappers/android/gradle/libs.versions.toml delete mode 100644 wrappers/android/settings.gradle create mode 100644 wrappers/android/settings.gradle.kts delete mode 100644 wrappers/android/zxingcpp/build.gradle create mode 100644 wrappers/android/zxingcpp/build.gradle.kts diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c26a4243f4..2fab2b5970 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -100,6 +100,10 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' - name: Build the lib/app working-directory: wrappers/android diff --git a/wrappers/android/app/build.gradle b/wrappers/android/app/build.gradle deleted file mode 100644 index 14437f1648..0000000000 --- a/wrappers/android/app/build.gradle +++ /dev/null @@ -1,59 +0,0 @@ -plugins { - id 'com.android.application' - id 'kotlin-android' -} - -android { - compileSdkVersion 33 - buildToolsVersion "32.0.0" - - defaultConfig { - applicationId 'com.example.zxingdemo' - minSdkVersion 26 - targetSdkVersion 33 - versionCode 1 - versionName "1.0" - } - - buildFeatures { - viewBinding true - } - buildTypes { - release { - minifyEnabled false - proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' - } - } - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - lint { - disable 'UnsafeExperimentalUsageError' - } - namespace 'com.example.zxingcppdemo' -} - -dependencies { - - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.core:core-ktx:1.9.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.8.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' - - // CameraX - def camerax_version = "1.2.1" - implementation "androidx.camera:camera-core:${camerax_version}" - implementation "androidx.camera:camera-camera2:${camerax_version}" - implementation "androidx.camera:camera-lifecycle:${camerax_version}" - implementation 'androidx.camera:camera-view:1.0.0-alpha32' - - // Java 'upstream' version of zxing (to compare performance) - implementation 'com.google.zxing:core:3.5.0' - - implementation project(':zxingcpp') -} diff --git a/wrappers/android/app/build.gradle.kts b/wrappers/android/app/build.gradle.kts new file mode 100644 index 0000000000..aede6c5e91 --- /dev/null +++ b/wrappers/android/app/build.gradle.kts @@ -0,0 +1,50 @@ +plugins { + alias(libs.plugins.android.application) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.example.zxingcppdemo" + defaultConfig { + applicationId = "com.example.zxingdemo" + compileSdk = libs.versions.androidCompileSdk.get().toInt() + minSdk = 26 // for the adaptive icons. TODO: remove adaptive icons and lower to API 21 + targetSdk = libs.versions.androidTargetSdk.get().toInt() + versionCode = 1 + versionName = "1.0" + } + buildFeatures { + viewBinding = true + } + buildTypes { + release { + isMinifyEnabled = false + proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro") + } + } + compileOptions { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = "1.8" + } + lint { + disable.add("UnsafeExperimentalUsageError") + } +} + +dependencies { + implementation(project(":zxingcpp")) + + implementation(libs.androidx.appcompat) + implementation(libs.androidx.constraintlayout) + implementation(libs.androidx.core) + implementation(libs.androidx.camera.camera2) + implementation(libs.androidx.camera.lifecycle) + implementation(libs.androidx.camera.view) + implementation(libs.google.material) + + // Java "upstream" version of zxing (to compare performance) + implementation(libs.google.zxing) +} diff --git a/wrappers/android/app/proguard-rules.pro b/wrappers/android/app/proguard-rules.pro index 64b4a059a2..49dab8dee1 100644 --- a/wrappers/android/app/proguard-rules.pro +++ b/wrappers/android/app/proguard-rules.pro @@ -1,6 +1,6 @@ # Add project specific ProGuard rules here. # You can control the set of applied configuration files using the -# proguardFiles setting in build.gradle. +# proguardFiles setting in build.gradle.kts. # # For more details, see # http://developer.android.com/guide/developing/tools/proguard.html @@ -18,4 +18,4 @@ # If you keep the line number information, uncomment this to # hide the original source file name. -#-renamesourcefileattribute SourceFile \ No newline at end of file +#-renamesourcefileattribute SourceFile diff --git a/wrappers/android/build.gradle b/wrappers/android/build.gradle deleted file mode 100644 index b46a4947a6..0000000000 --- a/wrappers/android/build.gradle +++ /dev/null @@ -1,26 +0,0 @@ -// Top-level build file where you can add configuration options common to all sub-projects/modules. -buildscript { - ext.kotlin_version = "1.7.20" - repositories { - google() - mavenCentral() - } - dependencies { - classpath 'com.android.tools.build:gradle:7.4.2' - classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - - // NOTE: Do not place your application dependencies here; they belong - // in the individual module build.gradle files - } -} - -allprojects { - repositories { - google() - mavenCentral() - } -} - -task clean(type: Delete) { - delete rootProject.buildDir -} \ No newline at end of file diff --git a/wrappers/android/build.gradle.kts b/wrappers/android/build.gradle.kts new file mode 100644 index 0000000000..140c5d81f9 --- /dev/null +++ b/wrappers/android/build.gradle.kts @@ -0,0 +1,11 @@ +plugins { + base + alias(libs.plugins.android.application) apply false + alias(libs.plugins.android.library) apply false + alias(libs.plugins.kotlin.android) apply false +} + +tasks.named("clean") { + val buildDirs = allprojects.map { it.layout.buildDirectory.asFile } + delete(buildDirs) +} diff --git a/wrappers/android/gradle.properties b/wrappers/android/gradle.properties index 95845120f6..cb35e9e670 100644 --- a/wrappers/android/gradle.properties +++ b/wrappers/android/gradle.properties @@ -4,19 +4,20 @@ # any settings specified in this file. # For more details on how to configure your build environment visit # http://www.gradle.org/docs/current/userguide/build_environment.html + # Specifies the JVM arguments used for the daemon process. # The setting is particularly useful for tweaking memory settings. org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 + # When configured, Gradle will run in incubating parallel mode. # This option should only be used with decoupled projects. More details, visit # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects # org.gradle.parallel=true + # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app"s APK # https://developer.android.com/topic/libraries/support-library/androidx-rn android.useAndroidX=true -# Automatically convert third-party libraries to use AndroidX -android.enableJetifier=false + # Kotlin code style for this project: "official" or "obsolete": kotlin.code.style=official -# org.gradle.warning.mode=all \ No newline at end of file diff --git a/wrappers/android/gradle/libs.versions.toml b/wrappers/android/gradle/libs.versions.toml new file mode 100644 index 0000000000..6d017cf5c2 --- /dev/null +++ b/wrappers/android/gradle/libs.versions.toml @@ -0,0 +1,32 @@ +[versions] +androidCoreDesugaring = "2.0.3" +androidGradlePlugin = "8.1.1" +androidCompileSdk = "34" +androidMinSdk = "21" +androidTargetSdk = "33" +androidx-activity = "1.7.2" +androidx-appcompat = "1.6.1" +androidx-camera = "1.3.0-rc01" +androidx-core = "1.12.0" +constraintLayout = "2.1.4" +google-zxing = "3.5.0" +kotlin = "1.9.10" +materialDesign = "1.11.0-alpha02" + +[libraries] +androidx-activityktx = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" } +androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } +androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" } +androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" } +androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" } +androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" } +androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintLayout" } +androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } +google-material = { module = "com.google.android.material:material", version.ref = "materialDesign" } +google-zxing = { module = "com.google.zxing:core", version.ref = "google-zxing" } + +[plugins] +android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } +android-library = { id = "com.android.library", version.ref = "androidGradlePlugin" } +kotlin-android = { id = "org.jetbrains.kotlin.android", version.ref = "kotlin" } + diff --git a/wrappers/android/gradle/wrapper/gradle-wrapper.jar b/wrappers/android/gradle/wrapper/gradle-wrapper.jar index f6b961fd5a86aa5fbfe90f707c3138408be7c718..7f93135c49b765f8051ef9d0a6055ff8e46073d8 100644 GIT binary patch literal 63721 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鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc literal 54329 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

    <5G2M1WZP5 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 F{|7XC?P&l2 diff --git a/wrappers/android/gradle/wrapper/gradle-wrapper.properties b/wrappers/android/gradle/wrapper/gradle-wrapper.properties index cfb22fcb3f..ac72c34e8a 100644 --- a/wrappers/android/gradle/wrapper/gradle-wrapper.properties +++ b/wrappers/android/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,7 @@ -#Tue Apr 13 21:15:17 CEST 2021 distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.3-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.5-bin.zip diff --git a/wrappers/android/gradlew b/wrappers/android/gradlew index cccdd3d517..0adc8e1a53 100755 --- a/wrappers/android/gradlew +++ b/wrappers/android/gradlew @@ -1,78 +1,127 @@ -#!/usr/bin/env sh +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null - -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS="" +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + # Determine the Java command to use to start the JVM. if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -81,92 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) -# For Cygwin, switch paths to Windows format before running java -if $cygwin ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=$((i+1)) + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - (0) set -- ;; - (1) set -- "$args0" ;; - (2) set -- "$args0" "$args1" ;; - (3) set -- "$args0" "$args1" "$args2" ;; - (4) set -- "$args0" "$args1" "$args2" "$args3" ;; - (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=$(save "$@") - -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" -# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong -if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then - cd "$(dirname "$0")" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" fi +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + exec "$JAVACMD" "$@" diff --git a/wrappers/android/gradlew.bat b/wrappers/android/gradlew.bat index e95643d6a2..6689b85bee 100644 --- a/wrappers/android/gradlew.bat +++ b/wrappers/android/gradlew.bat @@ -1,4 +1,20 @@ -@if "%DEBUG%" == "" @echo off +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -9,19 +25,23 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS= +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" @rem Find java.exe if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto init +if %ERRORLEVEL% equ 0 goto execute echo. echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. @@ -35,7 +55,7 @@ goto fail set JAVA_HOME=%JAVA_HOME:"=% set JAVA_EXE=%JAVA_HOME%/bin/java.exe -if exist "%JAVA_EXE%" goto init +if exist "%JAVA_EXE%" goto execute echo. echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% @@ -45,38 +65,26 @@ echo location of your Java installation. goto fail -:init -@rem Get command-line arguments, handling Windows variants - -if not "%OS%" == "Windows_NT" goto win9xME_args - -:win9xME_args -@rem Slurp the command line arguments. -set CMD_LINE_ARGS= -set _SKIP=2 - -:win9xME_args_slurp -if "x%~1" == "x" goto execute - -set CMD_LINE_ARGS=%* - :execute @rem Setup the command line set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + @rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal diff --git a/wrappers/android/settings.gradle b/wrappers/android/settings.gradle deleted file mode 100644 index c068e8ec94..0000000000 --- a/wrappers/android/settings.gradle +++ /dev/null @@ -1,3 +0,0 @@ -include ':zxingcpp' -include ':app' -rootProject.name = "ZXingCpp" diff --git a/wrappers/android/settings.gradle.kts b/wrappers/android/settings.gradle.kts new file mode 100644 index 0000000000..d56ae2f4bc --- /dev/null +++ b/wrappers/android/settings.gradle.kts @@ -0,0 +1,19 @@ +pluginManagement { + repositories { + gradlePluginPortal() + google() + } +} + +dependencyResolutionManagement { + repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS) + repositories { + mavenCentral() + google() + } +} + +include(":app") +include(":zxingcpp") + +rootProject.name = "ZXingCpp" diff --git a/wrappers/android/zxingcpp/build.gradle b/wrappers/android/zxingcpp/build.gradle deleted file mode 100644 index 2b3a4ed505..0000000000 --- a/wrappers/android/zxingcpp/build.gradle +++ /dev/null @@ -1,53 +0,0 @@ -plugins { - id 'com.android.library' - id 'kotlin-android' -} - -android { - compileSdkVersion 33 - buildToolsVersion "32.0.0" - // ndk version 25 is known to support c++20 (see #386) - // ndkVersion '25.1.8937393' - - defaultConfig { - minSdkVersion 21 - targetSdkVersion 33 - - ndk { - // speed up build: compile only arm versions - abiFilters 'armeabi-v7a', 'arm64-v8a' - } - externalNativeBuild { - cmake { - arguments "-DCMAKE_BUILD_TYPE=RelWithDebInfo" - } - } - } - - compileOptions { - sourceCompatibility JavaVersion.VERSION_1_8 - targetCompatibility JavaVersion.VERSION_1_8 - } - kotlinOptions { - jvmTarget = '1.8' - } - externalNativeBuild { - cmake { - path file('src/main/cpp/CMakeLists.txt') - } - } - lint { - disable 'UnsafeExperimentalUsageError' - } - namespace 'com.zxingcpp' -} - -dependencies { - - implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" - implementation 'androidx.core:core-ktx:1.9.0' - - def camerax_version = "1.2.1" - implementation "androidx.camera:camera-core:${camerax_version}" - -} diff --git a/wrappers/android/zxingcpp/build.gradle.kts b/wrappers/android/zxingcpp/build.gradle.kts new file mode 100644 index 0000000000..477c26f9cd --- /dev/null +++ b/wrappers/android/zxingcpp/build.gradle.kts @@ -0,0 +1,49 @@ +plugins { + alias(libs.plugins.android.library) + alias(libs.plugins.kotlin.android) +} + +android { + namespace = "com.zxingcpp" + // ndk version 25 is known to support c++20 (see #386) + // ndkVersion = "25.1.8937393" + + defaultConfig { + compileSdk = libs.versions.androidCompileSdk.get().toInt() + minSdk = libs.versions.androidMinSdk.get().toInt() + + ndk { + // speed up build: compile only arm versions + + //noinspection ChromeOsAbiSupport + abiFilters += "armeabi-v7a" + + //noinspection ChromeOsAbiSupport + abiFilters += "arm64-v8a" + } + externalNativeBuild { + cmake { + arguments("-DCMAKE_BUILD_TYPE=RelWithDebInfo") + } + } + } + compileOptions { + sourceCompatibility(JavaVersion.VERSION_1_8) + targetCompatibility(JavaVersion.VERSION_1_8) + } + kotlinOptions { + jvmTarget = "1.8" + } + externalNativeBuild { + cmake { + path(file("src/main/cpp/CMakeLists.txt")) + } + } + lint { + disable.add("UnsafeExperimentalUsageError") + } +} + +dependencies { + implementation(libs.androidx.camera.core) +} diff --git a/wrappers/android/zxingcpp/src/main/AndroidManifest.xml b/wrappers/android/zxingcpp/src/main/AndroidManifest.xml index 8bdb7e14b3..8072ee00db 100644 --- a/wrappers/android/zxingcpp/src/main/AndroidManifest.xml +++ b/wrappers/android/zxingcpp/src/main/AndroidManifest.xml @@ -1,4 +1,2 @@ - - - + From 2d314dc19683ed1d930bd875787e76a1adf51eac Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 22 Sep 2023 23:47:25 +0200 Subject: [PATCH 023/587] android: remove 2 unnecessary .gitignore files --- wrappers/android/app/.gitignore | 1 - wrappers/android/zxingcpp/.gitignore | 1 - 2 files changed, 2 deletions(-) delete mode 100644 wrappers/android/app/.gitignore delete mode 100644 wrappers/android/zxingcpp/.gitignore diff --git a/wrappers/android/app/.gitignore b/wrappers/android/app/.gitignore deleted file mode 100644 index 42afabfd2a..0000000000 --- a/wrappers/android/app/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build \ No newline at end of file diff --git a/wrappers/android/zxingcpp/.gitignore b/wrappers/android/zxingcpp/.gitignore deleted file mode 100644 index 796b96d1c4..0000000000 --- a/wrappers/android/zxingcpp/.gitignore +++ /dev/null @@ -1 +0,0 @@ -/build From 2e5ffaa93c6c3cc2d18de764360174e767f1a771 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Karmaz=C3=ADn?= Date: Thu, 21 Sep 2023 15:09:34 +0200 Subject: [PATCH 024/587] Android wrapper: Enable Kotlin explicit API mode --- wrappers/android/zxingcpp/build.gradle.kts | 4 ++++ .../main/java/com/zxingcpp/BarcodeReader.kt | 21 ++++++++++--------- 2 files changed, 15 insertions(+), 10 deletions(-) diff --git a/wrappers/android/zxingcpp/build.gradle.kts b/wrappers/android/zxingcpp/build.gradle.kts index 477c26f9cd..f1fd062a33 100644 --- a/wrappers/android/zxingcpp/build.gradle.kts +++ b/wrappers/android/zxingcpp/build.gradle.kts @@ -44,6 +44,10 @@ android { } } +kotlin { + explicitApi() +} + dependencies { implementation(libs.androidx.camera.core) } diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt index b241cef0f2..2d7247222f 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt @@ -24,19 +24,20 @@ import androidx.camera.core.ImageProxy import java.lang.RuntimeException import java.nio.ByteBuffer -class BarcodeReader { +public class BarcodeReader { // Enumerates barcode formats known to this package. // Note that this has to be kept synchronized with native (C++/JNI) side. - enum class Format { + public enum class Format { NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E } - enum class ContentType { + + public enum class ContentType { TEXT, BINARY, MIXED, GS1, ISO15434, UNKNOWN_ECI } - data class Options( + public data class Options( val formats: Set = setOf(), val tryHarder: Boolean = false, val tryRotate: Boolean = false, @@ -44,7 +45,7 @@ class BarcodeReader { val tryDownscale: Boolean = false ) - data class Position( + public data class Position( val topLeft: Point, val topRight: Point, val bottomLeft: Point, @@ -52,7 +53,7 @@ class BarcodeReader { val orientation: Double ) - data class Result( + public data class Result( val format: Format = Format.NONE, val bytes: ByteArray? = null, val text: String? = null, @@ -64,9 +65,9 @@ class BarcodeReader { val symbologyIdentifier: String? = null ) - var options : Options = Options() + public var options : Options = Options() - fun read(image: ImageProxy): Result? { + public fun read(image: ImageProxy): Result? { val supportedYUVFormats = arrayOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) if (image.format !in supportedYUVFormats) { error("invalid image format") @@ -97,11 +98,11 @@ class BarcodeReader { } } - fun read(bitmap: Bitmap, cropRect: Rect = Rect(), rotation: Int = 0): Result? { + public fun read(bitmap: Bitmap, cropRect: Rect = Rect(), rotation: Int = 0): Result? { return read(bitmap, options, cropRect, rotation) } - fun read(bitmap: Bitmap, options: Options, cropRect: Rect = Rect(), rotation: Int = 0): Result? { + public fun read(bitmap: Bitmap, options: Options, cropRect: Rect = Rect(), rotation: Int = 0): Result? { var result = Result() val status = with(options) { readBitmap( From 829a54578783748070ea7135075cd1b00797aeb4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Karmaz=C3=ADn?= Date: Thu, 21 Sep 2023 15:13:32 +0200 Subject: [PATCH 025/587] Android BarcodeReader: Move init block to the top of the class see https://kotlinlang.org/docs/coding-conventions.html#source-file-organization --- .../zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt index 2d7247222f..6cc4915b2a 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt @@ -25,6 +25,9 @@ import java.lang.RuntimeException import java.nio.ByteBuffer public class BarcodeReader { + init { + System.loadLibrary("zxing_android") + } // Enumerates barcode formats known to this package. // Note that this has to be kept synchronized with native (C++/JNI) side. @@ -129,8 +132,4 @@ public class BarcodeReader { formats: String, tryHarder: Boolean, tryRotate: Boolean, tryInvert: Boolean, tryDownscale: Boolean, result: Result, ): String? - - init { - System.loadLibrary("zxing_android") - } } From 5dd9c8a69c10e11730c342c3d67b55e350974505 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ond=C5=99ej=20Karmaz=C3=ADn?= Date: Thu, 21 Sep 2023 15:26:54 +0200 Subject: [PATCH 026/587] Android BarcodeReader: Correctly handle supported image formats on APIs < 23 --- .../src/main/java/com/zxingcpp/BarcodeReader.kt | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt index 6cc4915b2a..360cafcc61 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt @@ -20,11 +20,19 @@ import android.graphics.Bitmap import android.graphics.ImageFormat import android.graphics.Point import android.graphics.Rect +import android.os.Build import androidx.camera.core.ImageProxy import java.lang.RuntimeException import java.nio.ByteBuffer public class BarcodeReader { + private val supportedYUVFormats: List = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) + } else { + listOf(ImageFormat.YUV_420_888) + } + init { System.loadLibrary("zxing_android") } @@ -71,9 +79,8 @@ public class BarcodeReader { public var options : Options = Options() public fun read(image: ImageProxy): Result? { - val supportedYUVFormats = arrayOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) - if (image.format !in supportedYUVFormats) { - error("invalid image format") + check(image.format in supportedYUVFormats) { + "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" } var result = Result() From b23085c42bf7c01dc0339f0f97a460427195d26c Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Thu, 5 Oct 2023 15:51:50 +0200 Subject: [PATCH 027/587] Build wheels for Python 3.12. --- .github/workflows/python-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index 607d6b4ccb..fe1dd3e914 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -33,12 +33,12 @@ jobs: python-version: '3.9' - name: Install cibuildwheel - run: python3 -m pip install cibuildwheel==2.12.1 + run: python3 -m pip install cibuildwheel==2.16.2 - name: Build wheels run: python3 -m cibuildwheel --output-dir wheelhouse wrappers/python env: - CIBW_BUILD: cp39-* cp310-* cp311-* + CIBW_BUILD: cp39-* cp310-* cp311-* cp312-* CIBW_SKIP: "*musllinux*" CIBW_ARCHS_MACOS: universal2 CIBW_ENVIRONMENT_MACOS: CMAKE_OSX_ARCHITECTURES="arm64;x86_64" From a561ccd49af4dcb96f5b784c62a89ac94db9b2f0 Mon Sep 17 00:00:00 2001 From: Alexander Manzer Date: Thu, 5 Oct 2023 15:52:56 +0200 Subject: [PATCH 028/587] Add required framework versions to MACOSX builds With Xcode15 it is required, that frameworks must have in their Info.plist two (previously not needed) values: CFBundleShortVersionString and CFBundleVersion. If they are missing, a build will be successful, but no upload to the appstore is possible, it will fail with this error codes: Asset validation failed (90057) Asset validation failed (90056) see https://stackoverflow.com/a/52298841 and https://cmake.org/cmake/help/v3.0/prop_tgt/MACOSX_FRAMEWORK_INFO_PLIST.html --- core/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 37480e2f55..a637d11b9b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -491,6 +491,8 @@ if (APPLE AND BUILD_APPLE_FRAMEWORK) XCODE_ATTRIBUTE_MODULEMAP_FILE "wrappers/ios/Sources/Wrapper/module.modulemap" XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES MACOSX_FRAMEWORK_IDENTIFIER "com.zxing-cpp.ios" + MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${PROJECT_VERSION} + MACOSX_FRAMEWORK_BUNDLE_VERSION ${PROJECT_VERSION} CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO #MACOSX_FRAMEWORK_INFO_PLIST Info.plist PUBLIC_HEADER "${PUBLIC_HEADERS}" From 5b505fcd8b799c80c7cfb3be949992976c5d6909 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 6 Oct 2023 20:10:35 +0200 Subject: [PATCH 029/587] Build wheels for all supported Python versions. Use Python 3.12 to build the source package and run tests. --- .github/workflows/ci.yml | 2 +- .github/workflows/python-build.yml | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2fab2b5970..e93bd06827 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -140,7 +140,7 @@ jobs: runs-on: ${{ matrix.os }} strategy: matrix: - python-version: ['3.9', '3.10', '3.11'] + python-version: ['3.12'] os: [ubuntu-latest, macos-latest, windows-latest] steps: diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index fe1dd3e914..dcfce55336 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -30,7 +30,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.12' - name: Install cibuildwheel run: python3 -m pip install cibuildwheel==2.16.2 @@ -38,7 +38,7 @@ jobs: - name: Build wheels run: python3 -m cibuildwheel --output-dir wheelhouse wrappers/python env: - CIBW_BUILD: cp39-* cp310-* cp311-* cp312-* + CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-* CIBW_SKIP: "*musllinux*" CIBW_ARCHS_MACOS: universal2 CIBW_ENVIRONMENT_MACOS: CMAKE_OSX_ARCHITECTURES="arm64;x86_64" @@ -58,7 +58,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 with: - python-version: '3.9' + python-version: '3.12' - name: Build sdist working-directory: wrappers/python From 931987892768f034eb2bde9db898e2a19e696bc9 Mon Sep 17 00:00:00 2001 From: Dain Nilsson Date: Fri, 6 Oct 2023 20:58:45 +0200 Subject: [PATCH 030/587] Add setuptools. --- .github/workflows/ci.yml | 2 +- .github/workflows/python-build.yml | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e93bd06827..004277a1cd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -154,7 +154,7 @@ jobs: - name: Install dependencies working-directory: wrappers/python run: | - python -m pip install --upgrade pip + python -m pip install --upgrade pip setuptools python -m pip install numpy pillow - name: Build module diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index dcfce55336..61e9c2d7c1 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -60,6 +60,10 @@ jobs: with: python-version: '3.12' + - name: Install dependencies + working-directory: wrappers/python + run: python -m pip install --upgrade pip setuptools + - name: Build sdist working-directory: wrappers/python run: python3 setup.py sdist From 80bf5b58397ec56d2fb2e7af896c298ab8d949b3 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 9 Oct 2023 18:43:16 +0200 Subject: [PATCH 031/587] HRI: added one missing GS1 aiInfo (4309) Based on https://github.com/zxing/zxing/pull/1681/files --- core/src/HRI.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/HRI.cpp b/core/src/HRI.cpp index d3a2687a4a..dc16e291b4 100644 --- a/core/src/HRI.cpp +++ b/core/src/HRI.cpp @@ -177,6 +177,7 @@ static const AiInfo aiInfos[] = { { "4306", -70 }, { "4307", 2 }, { "4308", -30 }, + { "4309", 20 }, { "4310", -35 }, { "4311", -35 }, { "4312", -70 }, From a7fb90c98955ebf6a78d80a9aa24579eff2a7981 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 12 Oct 2023 16:41:46 +0200 Subject: [PATCH 032/587] DataMatrix: use charset for encoding (#628) Support non-ISO8859_1 encodings for DataMatrix generation. This fixes #592. --- core/src/MultiFormatWriter.cpp | 8 +++++++- core/src/datamatrix/DMHighLevelEncoder.cpp | 10 +++++++--- core/src/datamatrix/DMHighLevelEncoder.h | 4 +++- core/src/datamatrix/DMWriter.cpp | 6 ++++-- core/src/datamatrix/DMWriter.h | 7 +++++++ test/unit/datamatrix/DMHighLevelEncodeTest.cpp | 3 ++- 6 files changed, 30 insertions(+), 8 deletions(-) diff --git a/core/src/MultiFormatWriter.cpp b/core/src/MultiFormatWriter.cpp index b278d848dd..e8d48471cc 100644 --- a/core/src/MultiFormatWriter.cpp +++ b/core/src/MultiFormatWriter.cpp @@ -49,9 +49,15 @@ MultiFormatWriter::encode(const std::wstring& contents, int width, int height) c return exec0(std::move(writer)); }; + auto exec2 = [&](auto&& writer) { + if (_encoding != CharacterSet::Unknown) + writer.setEncoding(_encoding); + return exec0(std::move(writer)); + }; + switch (_format) { case BarcodeFormat::Aztec: return exec1(Aztec::Writer(), AztecEccLevel); - case BarcodeFormat::DataMatrix: return exec0(DataMatrix::Writer()); + case BarcodeFormat::DataMatrix: return exec2(DataMatrix::Writer()); case BarcodeFormat::PDF417: return exec1(Pdf417::Writer(), Pdf417EccLevel); case BarcodeFormat::QRCode: return exec1(QRCode::Writer(), QRCodeEccLevel); case BarcodeFormat::Codabar: return exec0(OneD::CodabarWriter()); diff --git a/core/src/datamatrix/DMHighLevelEncoder.cpp b/core/src/datamatrix/DMHighLevelEncoder.cpp index 9741de5473..db0859347f 100644 --- a/core/src/datamatrix/DMHighLevelEncoder.cpp +++ b/core/src/datamatrix/DMHighLevelEncoder.cpp @@ -857,7 +857,7 @@ static bool EndsWith(const std::wstring& s, const std::wstring& ss) ByteArray Encode(const std::wstring& msg) { - return Encode(msg, SymbolShape::NONE, -1, -1, -1, -1); + return Encode(msg, CharacterSet::ISO8859_1, SymbolShape::NONE, -1, -1, -1, -1); } /** @@ -871,7 +871,7 @@ ByteArray Encode(const std::wstring& msg) * @param maxSize the maximum symbol size constraint or null for no constraint * @return the encoded message (the char values range from 0 to 255) */ -ByteArray Encode(const std::wstring& msg, SymbolShape shape, int minWidth, int minHeight, int maxWidth, int maxHeight) +ByteArray Encode(const std::wstring& msg, CharacterSet charset, SymbolShape shape, int minWidth, int minHeight, int maxWidth, int maxHeight) { //the codewords 0..255 are encoded as Unicode characters //Encoder[] encoders = { @@ -879,7 +879,11 @@ ByteArray Encode(const std::wstring& msg, SymbolShape shape, int minWidth, int m // new X12Encoder(), new EdifactEncoder(), new Base256Encoder() //}; - EncoderContext context(TextEncoder::FromUnicode(msg, CharacterSet::ISO8859_1)); + if (charset == CharacterSet::Unknown) { + charset = CharacterSet::ISO8859_1; + } + + EncoderContext context(TextEncoder::FromUnicode(msg, charset)); context.setSymbolShape(shape); context.setSizeConstraints(minWidth, minHeight, maxWidth, maxHeight); diff --git a/core/src/datamatrix/DMHighLevelEncoder.h b/core/src/datamatrix/DMHighLevelEncoder.h index 1a17587300..8f1e589707 100644 --- a/core/src/datamatrix/DMHighLevelEncoder.h +++ b/core/src/datamatrix/DMHighLevelEncoder.h @@ -6,6 +6,8 @@ #pragma once +#include "CharacterSet.h" + #include namespace ZXing { @@ -21,7 +23,7 @@ enum class SymbolShape; * annex S. */ ByteArray Encode(const std::wstring& msg); -ByteArray Encode(const std::wstring& msg, SymbolShape shape, int minWidth, int minHeight, int maxWidth, int maxHeight); +ByteArray Encode(const std::wstring& msg, CharacterSet encoding, SymbolShape shape, int minWidth, int minHeight, int maxWidth, int maxHeight); } // DataMatrix } // ZXing diff --git a/core/src/datamatrix/DMWriter.cpp b/core/src/datamatrix/DMWriter.cpp index e3ab758de3..a6723843c3 100644 --- a/core/src/datamatrix/DMWriter.cpp +++ b/core/src/datamatrix/DMWriter.cpp @@ -8,6 +8,7 @@ #include "BitMatrix.h" #include "ByteArray.h" +#include "CharacterSet.h" #include "DMBitLayout.h" #include "DMECEncoder.h" #include "DMHighLevelEncoder.h" @@ -75,7 +76,8 @@ static BitMatrix EncodeLowLevel(const BitMatrix& placement, const SymbolInfo& sy } Writer::Writer() : - _shapeHint(SymbolShape::NONE) + _shapeHint(SymbolShape::NONE), + _encoding(CharacterSet::Unknown) { } @@ -91,7 +93,7 @@ Writer::encode(const std::wstring& contents, int width, int height) const } //1. step: Data encodation - auto encoded = Encode(contents, _shapeHint, _minWidth, _minHeight, _maxWidth, _maxHeight); + auto encoded = Encode(contents, _encoding, _shapeHint, _minWidth, _minHeight, _maxWidth, _maxHeight); const SymbolInfo* symbolInfo = SymbolInfo::Lookup(Size(encoded), _shapeHint, _minWidth, _minHeight, _maxWidth, _maxHeight); if (symbolInfo == nullptr) { throw std::invalid_argument("Can't find a symbol arrangement that matches the message. Data codewords: " + std::to_string(encoded.size())); diff --git a/core/src/datamatrix/DMWriter.h b/core/src/datamatrix/DMWriter.h index c0e3a43ee4..896f0036e8 100644 --- a/core/src/datamatrix/DMWriter.h +++ b/core/src/datamatrix/DMWriter.h @@ -6,6 +6,7 @@ #pragma once +#include "CharacterSet.h" #include "DMSymbolShape.h" #include @@ -43,12 +44,18 @@ class Writer return *this; } + Writer& setEncoding(CharacterSet encoding) { + _encoding = encoding; + return *this; + } + BitMatrix encode(const std::wstring& contents, int width, int height) const; BitMatrix encode(const std::string& contents, int width, int height) const; private: SymbolShape _shapeHint; int _quietZone = 1, _minWidth = -1, _minHeight = -1, _maxWidth = -1, _maxHeight = -1; + CharacterSet _encoding; }; } // DataMatrix diff --git a/test/unit/datamatrix/DMHighLevelEncodeTest.cpp b/test/unit/datamatrix/DMHighLevelEncodeTest.cpp index cf03ef4e62..fd436b8e8b 100644 --- a/test/unit/datamatrix/DMHighLevelEncodeTest.cpp +++ b/test/unit/datamatrix/DMHighLevelEncodeTest.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "ByteArray.h" +#include "CharacterSet.h" #include "ZXAlgorithms.h" #include "datamatrix/DMHighLevelEncoder.h" #include "datamatrix/DMSymbolInfo.h" @@ -346,7 +347,7 @@ TEST(DMHighLevelEncodeTest, EncodingWithStartAsX12AndLatchToEDIFACTInTheMiddle) TEST(DMHighLevelEncodeTest, EDIFACTWithEODBug) { std::string visualized = Visualize( - DataMatrix::Encode(L"abc<->ABCDE", DataMatrix::SymbolShape::SQUARE, -1, -1, -1, -1)); + DataMatrix::Encode(L"abc<->ABCDE", CharacterSet::ISO8859_1, DataMatrix::SymbolShape::SQUARE, -1, -1, -1, -1)); // switch to EDIFACT on '<', uses 10 code words + 2 padding. Buggy code introduced invalid 254 after the 5 EXPECT_EQ(visualized, "98 99 100 240 242 223 129 8 49 5 129 147"); } From 5e8c82d58f6b1b981de743ded8f9f7be9d1d41bb Mon Sep 17 00:00:00 2001 From: Ben John Date: Wed, 18 Oct 2023 02:08:02 +0200 Subject: [PATCH 033/587] iOS Wrapper improvements (#630) * refactor(wrappers/ios): enable adding ios dependency in a clean way - move `Package.swift` to root - get rid of `unsafeFlags` - adjust readme and gitignore files * feat(wrappers/ios/demo): adapt xcodeproj for streamlined deps usage - add `ZXingCppWrapper` as swift package dependency - embed `ZXingCppWrapper` framework in project - lower minimum deployment target to `iOS 13.0` - start stream on background thread - add more camera variants to enable macro mode on newer devices * refactor(core): remove apple framework references from cmake file * fix(wrappers/ios): change c++ language standard to 20 * chore(workflows/ci): replace old script invocation with swift pm build command * fix(wrappers/ios): omit the type (static vs dynamic) for the library in swift pm Co-authored-by: Alexander Manzer --- .github/workflows/ci.yml | 12 ++--- .gitignore | 2 + wrappers/ios/Package.swift => Package.swift | 20 +++++---- core/CMakeLists.txt | 19 -------- wrappers/ios/.gitignore | 4 -- wrappers/ios/Info.plist | Bin 723 -> 0 bytes wrappers/ios/README.md | 13 +----- .../Wrapper/Reader/ZXIBarcodeReader.mm | 6 +-- .../Sources/Wrapper/Reader/ZXIDecodeHints.mm | 2 +- .../Wrapper/Reader/ZXIPosition+Helper.h | 2 +- .../Wrapper/Writer/ZXIBarcodeWriter.mm | 4 +- .../ios/Sources/Wrapper/ZXIFormatHelper.h | 2 +- wrappers/ios/build-release.sh | 37 --------------- wrappers/ios/demo/README.md | 2 - .../ios/demo/demo.xcodeproj/project.pbxproj | 42 +++++++++++++----- wrappers/ios/demo/demo/ViewController.swift | 15 +++++-- 16 files changed, 68 insertions(+), 114 deletions(-) rename wrappers/ios/Package.swift => Package.swift (53%) delete mode 100644 wrappers/ios/Info.plist delete mode 100755 wrappers/ios/build-release.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 004277a1cd..0ca0361399 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,16 +80,10 @@ jobs: with: ref: ${{github.ref}} - - name: Build the ZXingCpp.xcframework + - name: Build the swift package shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios - run: ./build-release.sh - - - name: Upload .xcframework - uses: actions/upload-artifact@v3 - with: - name: ios-artifacts - path: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/ZXingCpp.xcframework + working-directory: ${{runner.workspace}}/${{github.event.repository.name}} + run: swift build - name: Build the demo app shell: sh diff --git a/.gitignore b/.gitignore index 9992cb687a..57cb900099 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ CMakeLists.txt.user *.d *.a compile_commands.json +.swiftpm +.build diff --git a/wrappers/ios/Package.swift b/Package.swift similarity index 53% rename from wrappers/ios/Package.swift rename to Package.swift index 7e3352acd7..36ce23b707 100644 --- a/wrappers/ios/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.3 +// swift-tools-version:5.7.1 import PackageDescription let package = Package( @@ -9,23 +9,25 @@ let package = Package( products: [ .library( name: "ZXingCppWrapper", - type: .static, targets: ["ZXingCppWrapper"]) ], targets: [ - .binaryTarget( + .target( name: "ZXingCpp", - path: "ZXingCpp.xcframework" + path: "core/src", + publicHeadersPath: "." ), .target( name: "ZXingCppWrapper", dependencies: ["ZXingCpp"], - path: "Sources/Wrapper", + path: "wrappers/ios/Sources/Wrapper", publicHeadersPath: ".", - cxxSettings: [ - .unsafeFlags(["-stdlib=libc++"]), - .unsafeFlags(["-std=gnu++17"]) + linkerSettings: [ + .linkedFramework("CoreGraphics"), + .linkedFramework("CoreImage"), + .linkedFramework("CoreVideo") ] ) - ] + ], + cxxLanguageStandard: CXXLanguageStandard.gnucxx20 ) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index a637d11b9b..ab8f8ab8cc 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -482,25 +482,6 @@ endif() set_target_properties(ZXing PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") -if (APPLE AND BUILD_APPLE_FRAMEWORK) - set_target_properties(ZXing PROPERTIES - FRAMEWORK TRUE - FRAMEWORK_VERSION "C" - XCODE_ATTRIBUTE_DEFINES_MODULE YES - XCODE_ATTRIBUTE_BUILD_LIBRARY_FOR_DISTRIBUTION YES - XCODE_ATTRIBUTE_MODULEMAP_FILE "wrappers/ios/Sources/Wrapper/module.modulemap" - XCODE_ATTRIBUTE_CLANG_ENABLE_OBJC_ARC YES - MACOSX_FRAMEWORK_IDENTIFIER "com.zxing-cpp.ios" - MACOSX_FRAMEWORK_SHORT_VERSION_STRING ${PROJECT_VERSION} - MACOSX_FRAMEWORK_BUNDLE_VERSION ${PROJECT_VERSION} - CMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH NO - #MACOSX_FRAMEWORK_INFO_PLIST Info.plist - PUBLIC_HEADER "${PUBLIC_HEADERS}" - XCODE_ATTRIBUTE_CODE_SIGN_IDENTITY "iPhone Developer" - XCODE_ATTRIBUTE_ENABLE_BITCODE "NO" - ) -endif() - include (GNUInstallDirs) set(ZX_INSTALL_TARGETS ZXing) diff --git a/wrappers/ios/.gitignore b/wrappers/ios/.gitignore index be5558caa1..9bce6af399 100644 --- a/wrappers/ios/.gitignore +++ b/wrappers/ios/.gitignore @@ -1,5 +1 @@ -.swiftpm -_builds -.build xcuserdata -ZXingCpp.xcframework diff --git a/wrappers/ios/Info.plist b/wrappers/ios/Info.plist deleted file mode 100644 index 0433f478b49fa21f43f38dbba7b6bc0a62542df1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 723 zcmYjOyKWOf6x}<#5+2DWPJo02On3x{Sb4pPrO+VRYa=VNW4!Cwi9%N6-LXB&?1Ozo zc7B0^p3YxDO+|&M_y8(IK|_ORD4=0>V-u#i_ndpqoqK269-~2+$$Sy#0G&B|?)-&` z$%~gRUzwT~XJ%7#^H;B3Us$}cbn{mF_DcHB-Fx>}9~xpRA5msiu-T&y+13(dhzo^M zK5{HZRB}X^>)FH!>!eFv#}H@7wPK%`QHa}&{7058E~Onxd_!CwE6YyDRjCd-9coARwl*>!G|K$ts7XrLrfgt{OO0}!yo{(%tdl|kd$>&*4Jip4Sy|0xn?Yf-1mM>1VoI_~rQvFUjdbptIYW%N>|xurklXSm#=wVoUQ+N2?_#A?1cBNES& zwF8N^VcDj;mWOn4uVpiE~Q7?A6fi7=I>-%FA16@`WE>nuKtkh*?_&|i=qXzcv z+`2F!C_-5v!m;pLcq@DmJ`2BK3Kn1uo #import "ZXIPosition.h" -#import "ZXing/Result.h" +#import "Result.h" NS_ASSUME_NONNULL_BEGIN diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm index 430fed9995..2a10257904 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm @@ -4,8 +4,8 @@ #import #import "ZXIBarcodeWriter.h" -#import "ZXing/MultiFormatWriter.h" -#import "ZXing/BitMatrix.h" +#import "MultiFormatWriter.h" +#import "BitMatrix.h" #import "ZXIFormatHelper.h" #import "ZXIErrors.h" #import diff --git a/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.h b/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.h index 29f90a03bf..257314506c 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.h +++ b/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.h @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #import -#import "ZXing/BarcodeFormat.h" +#import "BarcodeFormat.h" #import "ZXIFormat.h" NS_ASSUME_NONNULL_BEGIN diff --git a/wrappers/ios/build-release.sh b/wrappers/ios/build-release.sh deleted file mode 100755 index 7c1f4b4d5e..0000000000 --- a/wrappers/ios/build-release.sh +++ /dev/null @@ -1,37 +0,0 @@ -echo ========= Remove previous builds -rm -rf _builds -rm -rf ZXingCpp.xcframework - -echo ========= Create project structure -cmake -S../../ -B_builds -GXcode \ - -DCMAKE_SYSTEM_NAME=iOS \ - "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ - -DCMAKE_INSTALL_PREFIX=`pwd`/_install \ - -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \ - -DBUILD_UNIT_TESTS=NO \ - -DBUILD_BLACKBOX_TESTS=NO \ - -DBUILD_EXAMPLES=NO \ - -DBUILD_APPLE_FRAMEWORK=YES - -echo ========= Build the sdk for simulators -xcodebuild -project _builds/ZXing.xcodeproj build \ - -target ZXing \ - -parallelizeTargets \ - -configuration Release \ - -hideShellScriptEnvironment \ - -sdk iphonesimulator - -echo ========= Build the sdk for iOS -xcodebuild -project _builds/ZXing.xcodeproj build \ - -target ZXing \ - -parallelizeTargets \ - -configuration Release \ - -hideShellScriptEnvironment \ - -sdk iphoneos - -echo ========= Create the xcframework -xcodebuild -create-xcframework \ - -framework ./_builds/core/Release-iphonesimulator/ZXing.framework \ - -framework ./_builds/core/Release-iphoneos/ZXing.framework \ - -output ZXingCpp.xcframework diff --git a/wrappers/ios/demo/README.md b/wrappers/ios/demo/README.md index 6d8a8ed41e..c8b7ccf3a2 100644 --- a/wrappers/ios/demo/README.md +++ b/wrappers/ios/demo/README.md @@ -1,5 +1,3 @@ # ZXingWrapper Demo Project This demo-project sets up a basic `AVCaptureSession` and uses ZXing on the incoming frames. -You have to build the `ZXing.xcframework` before, by performing the `build-release.sh` script -in the parent-directory. diff --git a/wrappers/ios/demo/demo.xcodeproj/project.pbxproj b/wrappers/ios/demo/demo.xcodeproj/project.pbxproj index 204cd12fe1..99c06f5311 100644 --- a/wrappers/ios/demo/demo.xcodeproj/project.pbxproj +++ b/wrappers/ios/demo/demo.xcodeproj/project.pbxproj @@ -13,10 +13,23 @@ 388BF030283CC49D005CE271 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 388BF02E283CC49D005CE271 /* Main.storyboard */; }; 388BF032283CC49E005CE271 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 388BF031283CC49E005CE271 /* Assets.xcassets */; }; 388BF035283CC49E005CE271 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 388BF033283CC49E005CE271 /* LaunchScreen.storyboard */; }; - 9507445028609C0500E02D06 /* ZXingCppWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = 9507444F28609C0500E02D06 /* ZXingCppWrapper */; }; + 5EFA4B742ADF0F35000132A0 /* ZXingCppWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = 5EFA4B732ADF0F35000132A0 /* ZXingCppWrapper */; }; 950744522860A3A300E02D06 /* WriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950744512860A3A300E02D06 /* WriteViewController.swift */; }; /* End PBXBuildFile section */ +/* Begin PBXCopyFilesBuildPhase section */ + 52A975482ADAD7DE002D6BD8 /* Embed Frameworks */ = { + isa = PBXCopyFilesBuildPhase; + buildActionMask = 2147483647; + dstPath = ""; + dstSubfolderSpec = 10; + files = ( + ); + name = "Embed Frameworks"; + runOnlyForDeploymentPostprocessing = 0; + }; +/* End PBXCopyFilesBuildPhase section */ + /* Begin PBXFileReference section */ 388BF025283CC49D005CE271 /* demo.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = demo.app; sourceTree = BUILT_PRODUCTS_DIR; }; 388BF028283CC49D005CE271 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; @@ -27,8 +40,8 @@ 388BF034283CC49E005CE271 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 388BF036283CC49E005CE271 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 388BF043283CE0AC005CE271 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = ""; }; + 5EFA4B712ADF0F16000132A0 /* zxing-cpp */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = "zxing-cpp"; path = ../../..; sourceTree = ""; }; 950744512860A3A300E02D06 /* WriteViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WriteViewController.swift; sourceTree = ""; }; - 9550105328609B7900ED103F /* ios */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = ios; path = ..; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -36,7 +49,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 9507445028609C0500E02D06 /* ZXingCppWrapper in Frameworks */, + 5EFA4B742ADF0F35000132A0 /* ZXingCppWrapper in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -46,11 +59,11 @@ 388BF01C283CC49D005CE271 = { isa = PBXGroup; children = ( + 5EFA4B702ADF0F16000132A0 /* Packages */, 388BF043283CE0AC005CE271 /* README.md */, - 388BF03E283CD6C5005CE271 /* Packages */, 388BF027283CC49D005CE271 /* demo */, 388BF026283CC49D005CE271 /* Products */, - 388BF040283CD908005CE271 /* Frameworks */, + 5EFA4B722ADF0F35000132A0 /* Frameworks */, ); sourceTree = ""; }; @@ -77,15 +90,15 @@ path = demo; sourceTree = ""; }; - 388BF03E283CD6C5005CE271 /* Packages */ = { + 5EFA4B702ADF0F16000132A0 /* Packages */ = { isa = PBXGroup; children = ( - 9550105328609B7900ED103F /* ios */, + 5EFA4B712ADF0F16000132A0 /* zxing-cpp */, ); name = Packages; sourceTree = ""; }; - 388BF040283CD908005CE271 /* Frameworks */ = { + 5EFA4B722ADF0F35000132A0 /* Frameworks */ = { isa = PBXGroup; children = ( ); @@ -102,6 +115,7 @@ 388BF021283CC49D005CE271 /* Sources */, 388BF022283CC49D005CE271 /* Frameworks */, 388BF023283CC49D005CE271 /* Resources */, + 52A975482ADAD7DE002D6BD8 /* Embed Frameworks */, ); buildRules = ( ); @@ -109,7 +123,7 @@ ); name = demo; packageProductDependencies = ( - 9507444F28609C0500E02D06 /* ZXingCppWrapper */, + 5EFA4B732ADF0F35000132A0 /* ZXingCppWrapper */, ); productName = demo; productReference = 388BF025283CC49D005CE271 /* demo.app */; @@ -139,6 +153,8 @@ Base, ); mainGroup = 388BF01C283CC49D005CE271; + packageReferences = ( + ); productRefGroup = 388BF026283CC49D005CE271 /* Products */; projectDirPath = ""; projectRoot = ""; @@ -314,6 +330,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -326,6 +343,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -333,6 +351,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.zxing-cpp.ios.demo-${SAMPLE_CODE_DISAMBIGUATOR}"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SAMPLE_CODE_DISAMBIGUATOR = "${DEVELOPMENT_TEAM}"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -345,6 +364,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = ""; @@ -357,6 +377,7 @@ INFOPLIST_KEY_UISupportedInterfaceOrientations = UIInterfaceOrientationPortrait; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + IPHONEOS_DEPLOYMENT_TARGET = 13.0; LD_RUNPATH_SEARCH_PATHS = ( "$(inherited)", "@executable_path/Frameworks", @@ -364,6 +385,7 @@ MARKETING_VERSION = 1.0; PRODUCT_BUNDLE_IDENTIFIER = "com.zxing-cpp.ios.demo-${SAMPLE_CODE_DISAMBIGUATOR}"; PRODUCT_NAME = "$(TARGET_NAME)"; + PROVISIONING_PROFILE_SPECIFIER = ""; SAMPLE_CODE_DISAMBIGUATOR = "${DEVELOPMENT_TEAM}"; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; @@ -395,7 +417,7 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - 9507444F28609C0500E02D06 /* ZXingCppWrapper */ = { + 5EFA4B732ADF0F35000132A0 /* ZXingCppWrapper */ = { isa = XCSwiftPackageProductDependency; productName = ZXingCppWrapper; }; diff --git a/wrappers/ios/demo/demo/ViewController.swift b/wrappers/ios/demo/demo/ViewController.swift index 4b4dd1e245..baea20b463 100644 --- a/wrappers/ios/demo/demo/ViewController.swift +++ b/wrappers/ios/demo/demo/ViewController.swift @@ -25,8 +25,13 @@ class ViewController: UIViewController { // setup camera session self.requestAccess { let discoverySession = AVCaptureDevice.DiscoverySession( - deviceTypes: [.builtInWideAngleCamera], - mediaType: AVMediaType.video, + deviceTypes: [ + .builtInTripleCamera, + .builtInDualWideCamera, + .builtInDualCamera, + .builtInWideAngleCamera + ], + mediaType: .video, position: .back) let device = discoverySession.devices.first! @@ -35,11 +40,13 @@ class ViewController: UIViewController { self.captureSession.addInput(cameraInput) let videoDataOutput = AVCaptureVideoDataOutput() videoDataOutput.setSampleBufferDelegate(self, queue: self.queue) - videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: Int(kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange)] + videoDataOutput.videoSettings = [kCVPixelBufferPixelFormatTypeKey as String: kCVPixelFormatType_420YpCbCr8BiPlanarVideoRange] videoDataOutput.alwaysDiscardsLateVideoFrames = true self.captureSession.addOutput(videoDataOutput) self.captureSession.commitConfiguration() - self.captureSession.startRunning() + DispatchQueue.global(qos: .background).async { + self.captureSession.startRunning() + } } } } From 143b288a345f575927fe349d008327c91fd173d4 Mon Sep 17 00:00:00 2001 From: Alexander Manzer Date: Thu, 19 Oct 2023 08:03:21 +0200 Subject: [PATCH 034/587] Rename ZXingCppWrapper to ZXingCpp --- Package.swift | 12 ++++++------ wrappers/ios/Sources/Wrapper/module.modulemap | 2 +- wrappers/ios/demo/demo.xcodeproj/project.pbxproj | 10 +++++----- wrappers/ios/demo/demo/ViewController.swift | 2 +- wrappers/ios/demo/demo/WriteViewController.swift | 2 +- 5 files changed, 14 insertions(+), 14 deletions(-) diff --git a/Package.swift b/Package.swift index 36ce23b707..27e70a9bc2 100644 --- a/Package.swift +++ b/Package.swift @@ -2,24 +2,24 @@ import PackageDescription let package = Package( - name: "ZXingCppWrapper", + name: "ZXingCpp", platforms: [ .iOS(.v11) ], products: [ .library( - name: "ZXingCppWrapper", - targets: ["ZXingCppWrapper"]) + name: "ZXingCpp", + targets: ["ZXingCpp"]) ], targets: [ .target( - name: "ZXingCpp", + name: "ZXingCppCore", path: "core/src", publicHeadersPath: "." ), .target( - name: "ZXingCppWrapper", - dependencies: ["ZXingCpp"], + name: "ZXingCpp", + dependencies: ["ZXingCppCore"], path: "wrappers/ios/Sources/Wrapper", publicHeadersPath: ".", linkerSettings: [ diff --git a/wrappers/ios/Sources/Wrapper/module.modulemap b/wrappers/ios/Sources/Wrapper/module.modulemap index f613294fc1..0d334fc7d8 100644 --- a/wrappers/ios/Sources/Wrapper/module.modulemap +++ b/wrappers/ios/Sources/Wrapper/module.modulemap @@ -1,4 +1,4 @@ -module ZXingCppWrapper { +module ZXingCpp { umbrella header "UmbrellaHeader.h" export * module * { export * } diff --git a/wrappers/ios/demo/demo.xcodeproj/project.pbxproj b/wrappers/ios/demo/demo.xcodeproj/project.pbxproj index 99c06f5311..e7cbad577b 100644 --- a/wrappers/ios/demo/demo.xcodeproj/project.pbxproj +++ b/wrappers/ios/demo/demo.xcodeproj/project.pbxproj @@ -13,7 +13,7 @@ 388BF030283CC49D005CE271 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 388BF02E283CC49D005CE271 /* Main.storyboard */; }; 388BF032283CC49E005CE271 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 388BF031283CC49E005CE271 /* Assets.xcassets */; }; 388BF035283CC49E005CE271 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 388BF033283CC49E005CE271 /* LaunchScreen.storyboard */; }; - 5EFA4B742ADF0F35000132A0 /* ZXingCppWrapper in Frameworks */ = {isa = PBXBuildFile; productRef = 5EFA4B732ADF0F35000132A0 /* ZXingCppWrapper */; }; + 5EFA4B742ADF0F35000132A0 /* ZXingCpp in Frameworks */ = {isa = PBXBuildFile; productRef = 5EFA4B732ADF0F35000132A0 /* ZXingCpp */; }; 950744522860A3A300E02D06 /* WriteViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 950744512860A3A300E02D06 /* WriteViewController.swift */; }; /* End PBXBuildFile section */ @@ -49,7 +49,7 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5EFA4B742ADF0F35000132A0 /* ZXingCppWrapper in Frameworks */, + 5EFA4B742ADF0F35000132A0 /* ZXingCpp in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -123,7 +123,7 @@ ); name = demo; packageProductDependencies = ( - 5EFA4B732ADF0F35000132A0 /* ZXingCppWrapper */, + 5EFA4B732ADF0F35000132A0 /* ZXingCpp */, ); productName = demo; productReference = 388BF025283CC49D005CE271 /* demo.app */; @@ -417,9 +417,9 @@ /* End XCConfigurationList section */ /* Begin XCSwiftPackageProductDependency section */ - 5EFA4B732ADF0F35000132A0 /* ZXingCppWrapper */ = { + 5EFA4B732ADF0F35000132A0 /* ZXingCpp */ = { isa = XCSwiftPackageProductDependency; - productName = ZXingCppWrapper; + productName = ZXingCpp; }; /* End XCSwiftPackageProductDependency section */ }; diff --git a/wrappers/ios/demo/demo/ViewController.swift b/wrappers/ios/demo/demo/ViewController.swift index baea20b463..964ca6010d 100644 --- a/wrappers/ios/demo/demo/ViewController.swift +++ b/wrappers/ios/demo/demo/ViewController.swift @@ -6,7 +6,7 @@ import UIKit import AVFoundation -import ZXingCppWrapper +import ZXingCpp class ViewController: UIViewController { let captureSession = AVCaptureSession() diff --git a/wrappers/ios/demo/demo/WriteViewController.swift b/wrappers/ios/demo/demo/WriteViewController.swift index 8f1e1f28e8..e77ebffee9 100644 --- a/wrappers/ios/demo/demo/WriteViewController.swift +++ b/wrappers/ios/demo/demo/WriteViewController.swift @@ -6,7 +6,7 @@ // SPDX-License-Identifier: Apache-2.0 import UIKit -import ZXingCppWrapper +import ZXingCpp class WriteViewController: UIViewController { @IBOutlet fileprivate var imageView: UIImageView! From 8a67baea3491eab0f5e2bfc5f13de54983757730 Mon Sep 17 00:00:00 2001 From: Alexander Manzer Date: Thu, 19 Oct 2023 08:10:13 +0200 Subject: [PATCH 035/587] Update Readme due to renamed package --- wrappers/ios/demo/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/ios/demo/README.md b/wrappers/ios/demo/README.md index c8b7ccf3a2..61f28ef695 100644 --- a/wrappers/ios/demo/README.md +++ b/wrappers/ios/demo/README.md @@ -1,3 +1,3 @@ -# ZXingWrapper Demo Project +# ZXingCpp Demo Project This demo-project sets up a basic `AVCaptureSession` and uses ZXing on the incoming frames. From bf06236cdec02ae3a817417f2bb86d0f830b98cc Mon Sep 17 00:00:00 2001 From: Alex Manzer Date: Thu, 19 Oct 2023 10:24:24 +0200 Subject: [PATCH 036/587] iOS wrapper: Add functionality of encoding binary data into Barcodes (#635) * Rename methods to writeText and writeBytes --------- Co-authored-by: Alexander Manzer --- .../Sources/Wrapper/Writer/ZXIBarcodeWriter.h | 17 ++- .../Wrapper/Writer/ZXIBarcodeWriter.mm | 131 ++++++++++-------- .../ios/demo/demo/WriteViewController.swift | 2 +- 3 files changed, 90 insertions(+), 60 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h index 664ff3dd84..f9efc0ce87 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h @@ -9,11 +9,18 @@ NS_ASSUME_NONNULL_BEGIN @interface ZXIBarcodeWriter : NSObject --(nullable CGImageRef)write:(NSString *)contents - width:(int)width - height:(int)height - format:(ZXIFormat)format - error:(NSError **)error; +-(nullable CGImageRef)writeText:(NSString *)contents + width:(int)width + height:(int)height + format:(ZXIFormat)format + error:(NSError *__autoreleasing _Nullable *)error; + +-(nullable CGImageRef)writeBytes:(NSData *)data + width:(int)width + height:(int)height + format:(ZXIFormat)format + error:(NSError *__autoreleasing _Nullable *)error; + @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm index 2a10257904..53a2cee6b9 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm @@ -6,6 +6,7 @@ #import "ZXIBarcodeWriter.h" #import "MultiFormatWriter.h" #import "BitMatrix.h" +#import "BitMatrixIO.h" #import "ZXIFormatHelper.h" #import "ZXIErrors.h" #import @@ -18,69 +19,58 @@ sizeof(wchar_t)); } -#ifdef DEBUG -std::string ToString(const BitMatrix& matrix, char one, char zero, bool addSpace, bool printAsCString) -{ - std::string result; - result.reserve((addSpace ? 2 : 1) * (matrix.width() * matrix.height()) + matrix.height()); - for (int y = 0; y < matrix.height(); ++y) { - for (int x = 0; x < matrix.width(); ++x) { - result += matrix.get(x, y) ? one : zero; - if (addSpace) - result += ' '; - } - if (printAsCString) - result += "\\n\""; - result += '\n'; +std::wstring NSDataToStringW(NSData *data) { + std::wstring s; + const unsigned char *bytes = (const unsigned char *) [data bytes]; + size_t len = [data length]; + for (int i = 0; i < len; ++i) { + s.push_back(bytes[i]); } - return result; + return s; } -#endif @implementation ZXIBarcodeWriter --(CGImageRef)write:(NSString *)contents - width:(int)width - height:(int)height - format:(ZXIFormat)format - error:(NSError *__autoreleasing _Nullable *)error { +-(CGImageRef)writeBytes:(NSData *)data + width:(int)width + height:(int)height + format:(ZXIFormat)format + error:(NSError *__autoreleasing _Nullable *)error { + return [self encode: NSDataToStringW(data) + width: width + height: height + format: format + encoding: CharacterSet::BINARY + error: error]; +} + +-(CGImageRef)writeText:(NSString *)contents + width:(int)width + height:(int)height + format:(ZXIFormat)format + error:(NSError *__autoreleasing _Nullable *)error { + return [self encode: NSStringToStringW(contents) + width: width + height: height + format: format + encoding: CharacterSet::UTF8 + error: error]; +} + +-(CGImageRef)encode:(std::wstring)content + width:(int)width + height:(int)height + format:(ZXIFormat)format + encoding:(CharacterSet)encoding + error:(NSError *__autoreleasing _Nullable *)error { MultiFormatWriter writer { BarcodeFormatFromZXIFormat(format) }; + writer.setEncoding(encoding); // Catch exception for invalid formats try { - BitMatrix result = writer.encode(NSStringToStringW(contents), width, height); - int realWidth = result.width(); - int realHeight = result.height(); - -#ifdef DEBUG -// std::cout << ToString(result, 'X', ' ', false, false); -#endif - - NSMutableData *resultAsNSData = [[NSMutableData alloc] initWithLength:realWidth * realHeight]; - size_t index = 0; - uint8_t *bytes = (uint8_t*)resultAsNSData.mutableBytes; - for (int y = 0; y < realHeight; ++y) { - for (int x = 0; x < realWidth; ++x) { - bytes[index] = result.get(x, y) ? 0 : 255; - ++index; - } - } - - CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray); - - CGImageRef cgimage = CGImageCreate(realWidth, - realHeight, - 8, - 8, - realWidth, - colorSpace, - kCGBitmapByteOrderDefault, - CGDataProviderCreateWithCFData((CFDataRef)resultAsNSData), - NULL, - YES, - kCGRenderingIntentDefault); - return cgimage; + BitMatrix bitMatrix = writer.encode(content, width, height); + return [self inflate:&bitMatrix]; } catch(std::exception &e) { - if(error != nil) { + if (error != nil) { NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: [[NSString alloc] initWithUTF8String:e.what()] }; @@ -90,4 +80,37 @@ -(CGImageRef)write:(NSString *)contents } } +-(CGImageRef)inflate:(BitMatrix *)bitMatrix { + int realWidth = bitMatrix->width(); + int realHeight = bitMatrix->height(); + +#ifdef DEBUG + std::cout << ToString(*bitMatrix, 'X', ' ', false, false); +#endif + + NSMutableData *resultAsNSData = [[NSMutableData alloc] initWithLength:realWidth * realHeight]; + size_t index = 0; + uint8_t *bytes = (uint8_t*)resultAsNSData.mutableBytes; + for (int y = 0; y < realHeight; ++y) { + for (int x = 0; x < realWidth; ++x) { + bytes[index] = bitMatrix->get(x, y) ? 0 : 255; + ++index; + } + } + + CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray); + + return CGImageCreate(realWidth, + realHeight, + 8, + 8, + realWidth, + colorSpace, + kCGBitmapByteOrderDefault, + CGDataProviderCreateWithCFData((CFDataRef)resultAsNSData), + NULL, + YES, + kCGRenderingIntentDefault); +} + @end diff --git a/wrappers/ios/demo/demo/WriteViewController.swift b/wrappers/ios/demo/demo/WriteViewController.swift index 8f1e1f28e8..975699d248 100644 --- a/wrappers/ios/demo/demo/WriteViewController.swift +++ b/wrappers/ios/demo/demo/WriteViewController.swift @@ -15,7 +15,7 @@ class WriteViewController: UIViewController { @IBAction func textFieldChanged(_ sender: UITextField) { guard let text = sender.text, - let image = try? ZXIBarcodeWriter().write(text, width: 200, height: 200, format: .QR_CODE) + let image = try? ZXIBarcodeWriter().writeText(text, width: 200, height: 200, format: .QR_CODE) else { return } From 893b8c25dd7a8cb1e71a30a67db94cec7e9891e4 Mon Sep 17 00:00:00 2001 From: Ben John Date: Thu, 19 Oct 2023 12:03:38 +0200 Subject: [PATCH 037/587] iOS: Enable CocoaPods by providing a podspec (#637) Fixes #623. --- .github/workflows/ci.yml | 5 +++++ zxing-cpp.podspec | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 zxing-cpp.podspec diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0ca0361399..d476f125a2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -90,6 +90,11 @@ jobs: working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/demo run: xcodebuild build -scheme demo -sdk "iphonesimulator" + - name: Validate the Pod + shell: sh + working-directory: ${{runner.workspace}}/${{github.event.repository.name}} + run: pod lib lint --allow-warnings + build-android: runs-on: ubuntu-latest steps: diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec new file mode 100644 index 0000000000..467616643e --- /dev/null +++ b/zxing-cpp.podspec @@ -0,0 +1,29 @@ +Pod::Spec.new do |s| + s.name = 'zxing-cpp' + s.version = '2.1.0' + s.summary = 'C++ port of ZXing' + s.homepage = 'https://github.com/zxing-cpp/zxing-cpp' + s.author = 'axxel' + s.license = { + :type => 'Apache License 2.0', + :file => 'LICENSE' + } + s.source = { + :git => 'https://github.com/zxing-cpp/zxing-cpp.git', + :tag => "v#{s.version}" + } + s.module_name = 'ZXingCpp' + s.platform = :ios, '11.0' + s.frameworks = 'CoreGraphics', 'CoreImage', 'CoreVideo' + s.library = ['c++'] + s.pod_target_xcconfig = { + 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20' + } + s.source_files = 'core/src/**/*.{h,c,cpp}', + 'wrappers/ios/Sources/Wrapper/**/*.{h,m,mm}' + s.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIDecodeHints}.h', + 'wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h', + 'wrappers/ios/Sources/Wrapper/{ZXIErrors,ZXIFormat}.h' + s.private_header_files = 'core/src/**/*.h' + s.exclude_files = 'wrappers/ios/Sources/Wrapper/UmbrellaHeader.h' +end From 3ed557df7d27a1370d5915d95ab3abb32e5d6450 Mon Sep 17 00:00:00 2001 From: Alexander Manzer Date: Thu, 19 Oct 2023 12:56:22 +0200 Subject: [PATCH 038/587] Improve naming of writeBytes and writeText methods With swift3 there was a new mechanism, called objectice-c-name-translation. The goal is, to use objC methods in a more "Swifty" way. The both functions `writeText()` and `writeBytes` have now be renamed to `writeString` and `writeData` to get the automatic name translation and to use the both methods simply with `write()`. Swift picks the right method depending on the parameter. --- .../Sources/Wrapper/Writer/ZXIBarcodeWriter.h | 14 ++++++------- .../Wrapper/Writer/ZXIBarcodeWriter.mm | 20 +++++++++---------- .../ios/demo/demo/WriteViewController.swift | 2 +- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h index f9efc0ce87..142f695c18 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h @@ -9,18 +9,18 @@ NS_ASSUME_NONNULL_BEGIN @interface ZXIBarcodeWriter : NSObject --(nullable CGImageRef)writeText:(NSString *)contents +-(nullable CGImageRef)writeString:(NSString *)contents + width:(int)width + height:(int)height + format:(ZXIFormat)format + error:(NSError *__autoreleasing _Nullable *)error; + +-(nullable CGImageRef)writeData:(NSData *)data width:(int)width height:(int)height format:(ZXIFormat)format error:(NSError *__autoreleasing _Nullable *)error; --(nullable CGImageRef)writeBytes:(NSData *)data - width:(int)width - height:(int)height - format:(ZXIFormat)format - error:(NSError *__autoreleasing _Nullable *)error; - @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm index 53a2cee6b9..879168aea3 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm @@ -31,11 +31,11 @@ @implementation ZXIBarcodeWriter --(CGImageRef)writeBytes:(NSData *)data - width:(int)width - height:(int)height - format:(ZXIFormat)format - error:(NSError *__autoreleasing _Nullable *)error { +-(CGImageRef)writeData:(NSData *)data + width:(int)width + height:(int)height + format:(ZXIFormat)format + error:(NSError *__autoreleasing _Nullable *)error { return [self encode: NSDataToStringW(data) width: width height: height @@ -44,11 +44,11 @@ -(CGImageRef)writeBytes:(NSData *)data error: error]; } --(CGImageRef)writeText:(NSString *)contents - width:(int)width - height:(int)height - format:(ZXIFormat)format - error:(NSError *__autoreleasing _Nullable *)error { +-(CGImageRef)writeString:(NSString *)contents + width:(int)width + height:(int)height + format:(ZXIFormat)format + error:(NSError *__autoreleasing _Nullable *)error { return [self encode: NSStringToStringW(contents) width: width height: height diff --git a/wrappers/ios/demo/demo/WriteViewController.swift b/wrappers/ios/demo/demo/WriteViewController.swift index 77678c822e..e77ebffee9 100644 --- a/wrappers/ios/demo/demo/WriteViewController.swift +++ b/wrappers/ios/demo/demo/WriteViewController.swift @@ -15,7 +15,7 @@ class WriteViewController: UIViewController { @IBAction func textFieldChanged(_ sender: UITextField) { guard let text = sender.text, - let image = try? ZXIBarcodeWriter().writeText(text, width: 200, height: 200, format: .QR_CODE) + let image = try? ZXIBarcodeWriter().write(text, width: 200, height: 200, format: .QR_CODE) else { return } From 9d40fbdac9bdc37ce58ab803f20699636fd8c60b Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 19 Oct 2023 21:38:50 +0200 Subject: [PATCH 039/587] DataMatrix: tune line tracing and pure vs non-pure detection order This fixes the issue discussed in https://github.com/markusfisch/zxing-cpp/commit/850243cb3b5572478b39e52f35cc6d0bd0d0de5f#r130276661 --- core/src/datamatrix/DMDetector.cpp | 46 +++++++++++++--------------- test/blackbox/BlackboxTestRunner.cpp | 2 +- 2 files changed, 22 insertions(+), 26 deletions(-) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 6d2a62d43e..0dcde39a9b 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -552,21 +552,24 @@ class EdgeTracer : public BitMatrixCursorF return true; } + bool updateDirectionFromLine(RegressionLine& line) + { + return line.evaluate(1.5) && updateDirectionFromOrigin(p - line.project(p) + line.points().front()); + } + bool traceLine(PointF dEdge, RegressionLine& line) { line.setDirectionInward(dEdge); do { log(p); line.add(p); - if (line.points().size() % 50 == 10) { - if (!line.evaluate()) - return false; - if (!updateDirectionFromOrigin(p - line.project(p) + line.points().front())) - return false; - } + if (line.points().size() % 50 == 10 && !updateDirectionFromLine(line)) + return false; auto stepResult = traceStep(dEdge, 1, line.isValid()); - if (stepResult != StepResult::FOUND) + if (stepResult != StepResult::FOUND) { + updateDirectionFromLine(line); return stepResult == StepResult::OPEN_END && line.points().size() > 1; + } } while (true); } @@ -618,9 +621,7 @@ class EdgeTracer : public BitMatrixCursorF if (stepLengthInMainDir > 1 || maxAbsComponent(curStep) >= 2) { ++gaps; if (gaps >= 2 || line.points().size() > 5) { - if (!line.evaluate(1.5)) - return false; - if (!updateDirectionFromOrigin(p - line.project(p) + line.points().front())) + if (!updateDirectionFromLine(line)) return false; // check if the first half of the top-line trace is complete. // the minimum code size is 10x10 -> every code has at least 4 gaps @@ -919,31 +920,26 @@ static DetectorResult DetectPure(const BitMatrix& image) DetectorResults Detect(const BitMatrix& image, bool tryHarder, bool tryRotate, bool isPure) { #ifdef __cpp_impl_coroutine - if (isPure) { - if (auto r = DetectPure(image); r.isValid()) - co_yield std::move(r); - } else { + // First try the very fast DetectPure() path. Also because DetectNew() generally fails with pure module size 1 symbols + // TODO: implement a tryRotate version of DetectPure, see #590. + if (auto r = DetectPure(image); r.isValid()) + co_yield std::move(r); + else if (!isPure) { // If r.isValid() then there is no point in looking for more (no-pure) symbols bool found = false; for (auto&& r : DetectNew(image, tryHarder, tryRotate)) { found = true; co_yield std::move(r); } if (!found && tryHarder) { - //TODO: implement a tryRotate version of DetectPure, see #590. - if (auto r = DetectPure(image); r.isValid()) - co_yield std::move(r); - else if(auto r = DetectOld(image); r.isValid()) + if (auto r = DetectOld(image); r.isValid()) co_yield std::move(r); } } #else - if (isPure) - return DetectPure(image); - - auto result = DetectNew(image, tryHarder, tryRotate); - if (!result.isValid() && tryHarder) - result = DetectPure(image); - if (!result.isValid() && tryHarder) + auto result = DetectPure(image); + if (!result.isValid() && !isPure) + result = DetectNew(image, tryHarder, tryRotate); + if (!result.isValid() && tryHarder && !isPure) result = DetectOld(image); return result; #endif diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index af2e247d80..7b03109021 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -348,7 +348,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("datamatrix-1", "DataMatrix", 29, { - { 27, 29, 0 }, + { 29, 29, 0 }, { 0, 27, 90 }, { 0, 27, 180 }, { 0, 27, 270 }, From a99d1655bbe7cca6068212bb10b05beb0d270d51 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 19 Oct 2023 21:41:42 +0200 Subject: [PATCH 040/587] ZXingReader: improve error message on failed image loading --- example/ZXingReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index b2376e6cb6..47a9c0782c 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -174,7 +174,7 @@ int main(int argc, char* argv[]) int width, height, channels; std::unique_ptr buffer(stbi_load(filePath.c_str(), &width, &height, &channels, 3), stbi_image_free); if (buffer == nullptr) { - std::cerr << "Failed to read image: " << filePath << "\n"; + std::cerr << "Failed to read image: " << filePath << " (" << stbi_failure_reason() << ")" << "\n"; return -1; } From 9a310457552002b70d6f47f64060d081b8fd5e7d Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 19 Oct 2023 21:43:04 +0200 Subject: [PATCH 041/587] examples: switch to Qt6 as the default --- example/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/example/CMakeLists.txt b/example/CMakeLists.txt index 81df390bdc..9a492ca5eb 100644 --- a/example/CMakeLists.txt +++ b/example/CMakeLists.txt @@ -22,7 +22,7 @@ if (BUILD_READERS) install(TARGETS ZXingReader DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() -find_package(Qt5 COMPONENTS Gui Multimedia Quick QUIET) +find_package(Qt6 COMPONENTS Gui Multimedia Quick QUIET) if (NOT (Qt5_FOUND OR Qt6_FOUND)) message("INFO: Qt (Gui/Multimedia/Quick) not found, skipping Qt examples") endif() From 1f6f691966d2b4e88b0f4dc52683154298bc0bd7 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 19 Oct 2023 21:44:05 +0200 Subject: [PATCH 042/587] HybridBinarizer: code cosmetic --- core/src/HybridBinarizer.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 4aaec4ae85..a74fbd2872 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -18,9 +18,9 @@ namespace ZXing { // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. // So this is the smallest dimension in each axis we can accept. -static const int BLOCK_SIZE = 8; -static const int MINIMUM_DIMENSION = BLOCK_SIZE * 5; -static const int MIN_DYNAMIC_RANGE = 24; +static constexpr int BLOCK_SIZE = 8; +static constexpr int MINIMUM_DIMENSION = BLOCK_SIZE * 5; +static constexpr int MIN_DYNAMIC_RANGE = 24; HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer(iv) {} From 6624175d4d6e95022f4e6d9666dbe13835440de8 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 19 Oct 2023 22:38:53 +0200 Subject: [PATCH 043/587] Android: switch to C++20 as the default for improved DataMatrix detection --- wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt b/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt index d078a4eaef..d3c0d32c28 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt +++ b/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt @@ -1,8 +1,8 @@ cmake_minimum_required(VERSION 3.14) project(ZXingAndroid) -# Force to build by C++17. The zxing-cpp require C++17 to build -set(CMAKE_CXX_STANDARD 17) +# The zxing-cpp requires at least C++17 to build +set(CMAKE_CXX_STANDARD 20) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(BUILD_READERS ON) From 69173797312e737e7dca04002d09940cf565dd84 Mon Sep 17 00:00:00 2001 From: Ben John Date: Thu, 19 Oct 2023 23:11:08 +0200 Subject: [PATCH 044/587] feat(wrappers/ios): split Podspec into subspecs --- zxing-cpp.podspec | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec index 467616643e..be57307827 100644 --- a/zxing-cpp.podspec +++ b/zxing-cpp.podspec @@ -14,16 +14,23 @@ Pod::Spec.new do |s| } s.module_name = 'ZXingCpp' s.platform = :ios, '11.0' - s.frameworks = 'CoreGraphics', 'CoreImage', 'CoreVideo' s.library = ['c++'] s.pod_target_xcconfig = { 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20' } - s.source_files = 'core/src/**/*.{h,c,cpp}', - 'wrappers/ios/Sources/Wrapper/**/*.{h,m,mm}' - s.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIDecodeHints}.h', - 'wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h', - 'wrappers/ios/Sources/Wrapper/{ZXIErrors,ZXIFormat}.h' - s.private_header_files = 'core/src/**/*.h' - s.exclude_files = 'wrappers/ios/Sources/Wrapper/UmbrellaHeader.h' + + s.subspec 'Core' do |ss| + ss.source_files = 'core/src/**/*.{h,c,cpp}' + ss.private_header_files = 'core/src/**/*.h' + end + + s.subspec 'Wrapper' do |ss| + ss.dependency 'zxing-cpp/Core' + ss.frameworks = 'CoreGraphics', 'CoreImage', 'CoreVideo' + ss.source_files = 'wrappers/ios/Sources/Wrapper/**/*.{h,m,mm}' + ss.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIDecodeHints}.h', + 'wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h', + 'wrappers/ios/Sources/Wrapper/{ZXIErrors,ZXIFormat}.h' + ss.exclude_files = 'wrappers/ios/Sources/Wrapper/UmbrellaHeader.h' + end end From ad63f53efff3fc781548eca8e83d327686b46725 Mon Sep 17 00:00:00 2001 From: Ben John Date: Fri, 20 Oct 2023 09:37:19 +0200 Subject: [PATCH 045/587] fix(wrappers/ios): define default subspec --- zxing-cpp.podspec | 2 ++ 1 file changed, 2 insertions(+) diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec index be57307827..7367f8e03e 100644 --- a/zxing-cpp.podspec +++ b/zxing-cpp.podspec @@ -19,6 +19,8 @@ Pod::Spec.new do |s| 'CLANG_CXX_LANGUAGE_STANDARD' => 'c++20' } + s.default_subspec = 'Wrapper' + s.subspec 'Core' do |ss| ss.source_files = 'core/src/**/*.{h,c,cpp}' ss.private_header_files = 'core/src/**/*.h' From 5deb5ba7f59e879a25544da239ad2525959b87ff Mon Sep 17 00:00:00 2001 From: Ben John Date: Fri, 20 Oct 2023 21:44:15 +0200 Subject: [PATCH 046/587] doc(wrappers/ios): add basic usage section in iOS readme --- wrappers/ios/README.md | 26 +++++++++++++++++++++++++- zxing-cpp.podspec | 1 + 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/wrappers/ios/README.md b/wrappers/ios/README.md index 1af3f4c041..d6f0158142 100644 --- a/wrappers/ios/README.md +++ b/wrappers/ios/README.md @@ -1,3 +1,27 @@ # ZXingCpp iOS Framework -Add the iOS wrapper either as a local Swift Package by adding it as a dependency to your app or add the repository, the Package.swift file is automatically picked. +## Usage + +For general usage, please compare the source code provided in the demo project. + +### Swift PM + +As this repository provides a `Package.swift` on root level, you can add `zxing-cpp` including wrapper code by adding a Package Dependency in Xcode. + +An alternative way is to check this repository out and add it as a local Swift Package by adding it as dependency to your app. + +### CocoaPods + +As an alternative way, you can also rely on [CocoaPods](https://cocoapods.org/pods/zxing-cpp). Just add the Pod as dependency, for instance: + +``` +pod 'zxing-cpp' +``` + +If you just need the core without the wrapper code, you can also rely on: + +``` +pod 'zxing-cpp/Core' +``` + +The module to be imported is named `ZXingCpp`. diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec index 7367f8e03e..df4c7b773d 100644 --- a/zxing-cpp.podspec +++ b/zxing-cpp.podspec @@ -4,6 +4,7 @@ Pod::Spec.new do |s| s.summary = 'C++ port of ZXing' s.homepage = 'https://github.com/zxing-cpp/zxing-cpp' s.author = 'axxel' + s.readme = 'https://raw.githubusercontent.com/zxing-cpp/zxing-cpp/master/wrappers/ios/README.md' s.license = { :type => 'Apache License 2.0', :file => 'LICENSE' From ebd7db8dbeff4767a7b1a13ba2ec60d66aa9fd31 Mon Sep 17 00:00:00 2001 From: sayzzy-nt <136289338+sayzzy-nt@users.noreply.github.com> Date: Sat, 21 Oct 2023 08:11:59 +0900 Subject: [PATCH 047/587] Support QR Code Model1 (#633) This adds basic support to read/decode QR Code Model 1 symbols. It can be considered to fix #614, although axxel believes that it only works for version 1-4. For higher versions there is still work to be done regarding the ecBlock interlacing (or the lack thereof). --------- Co-authored-by: axxel --- core/src/qrcode/QRBitMatrixParser.cpp | 69 ++++++++++- core/src/qrcode/QRDecoder.cpp | 15 ++- core/src/qrcode/QRFormatInformation.cpp | 19 ++- core/src/qrcode/QRFormatInformation.h | 1 + core/src/qrcode/QRVersion.cpp | 115 +++++++++++++++++-- core/src/qrcode/QRVersion.h | 7 +- test/blackbox/BlackboxTestRunner.cpp | 12 +- test/samples/qrcode-2/qr-model-1.png | Bin 0 -> 230 bytes test/samples/qrcode-2/qr-model-1.txt | 1 + test/unit/qrcode/QRFormatInformationTest.cpp | 6 +- 10 files changed, 215 insertions(+), 30 deletions(-) create mode 100644 test/samples/qrcode-2/qr-model-1.png create mode 100644 test/samples/qrcode-2/qr-model-1.txt diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index b970266c30..c554242226 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -135,6 +135,66 @@ static ByteArray ReadQRCodewords(const BitMatrix& bitMatrix, const Version& vers return result; } +static ByteArray ReadQRCodewordsModel1(const BitMatrix& bitMatrix, const Version& version, const FormatInformation& formatInfo) +{ + + ByteArray result; + result.reserve(version.totalCodewords()); + int dimension = bitMatrix.height(); + int columns = dimension / 4 + 1 + 2; + for (int j = 0; j < columns; j++) { + if (j <= 1) { // vertical symbols on the right side + int rows = (dimension - 8) / 4; + for (int i = 0; i < rows; i++) { + if (j == 0 && i % 2 == 0 && i > 0 && i < rows - 1) // extension + continue; + int x = (dimension - 1) - (j * 2); + int y = (dimension - 1) - (i * 4); + uint8_t currentByte = 0; + for (int b = 0; b < 8; b++) { + AppendBit(currentByte, GetDataMaskBit(formatInfo.dataMask, x - b % 2, y - (b / 2)) + != getBit(bitMatrix, x - b % 2, y - (b / 2), formatInfo.isMirrored)); + } + result.push_back(currentByte); + } + } else if (columns - j <= 4) { // vertical symbols on the left side + int rows = (dimension - 16) / 4; + for (int i = 0; i < rows; i++) { + int x = (columns - j - 1) * 2 + 1 + (columns - j == 4 ? 1 : 0); // timing + int y = (dimension - 1) - 8 - (i * 4); + uint8_t currentByte = 0; + for (int b = 0; b < 8; b++) { + AppendBit(currentByte, GetDataMaskBit(formatInfo.dataMask, x - b % 2, y - (b / 2)) + != getBit(bitMatrix, x - b % 2, y - (b / 2), formatInfo.isMirrored)); + } + result.push_back(currentByte); + } + } else { // horizontal symbols + int rows = dimension / 2; + for (int i = 0; i < rows; i++) { + if (j == 2 && i >= rows - 4) // alignment & finder + continue; + if (i == 0 && j % 2 == 1 && j + 1 != columns - 4) // extension + continue; + int x = (dimension - 1) - (2 * 2) - (j - 2) * 4; + int y = (dimension - 1) - (i * 2) - (i >= rows - 3 ? 1 : 0); // timing + uint8_t currentByte = 0; + for (int b = 0; b < 8; b++) { + AppendBit(currentByte, GetDataMaskBit(formatInfo.dataMask, x - b % 4, y - (b / 4)) + != getBit(bitMatrix, x - b % 4, y - (b / 4), formatInfo.isMirrored)); + } + result.push_back(currentByte); + } + } + } + + result[0] &= 0xf; // ignore corner + if (Size(result) != version.totalCodewords()) + return {}; + + return result; +} + static ByteArray ReadMQRCodewords(const BitMatrix& bitMatrix, const QRCode::Version& version, const FormatInformation& formatInfo) { BitMatrix functionPattern = version.buildFunctionPattern(); @@ -185,9 +245,12 @@ ByteArray ReadCodewords(const BitMatrix& bitMatrix, const Version& version, cons { if (!hasValidDimension(bitMatrix, version.isMicroQRCode())) return {}; - - return version.isMicroQRCode() ? ReadMQRCodewords(bitMatrix, version, formatInfo) - : ReadQRCodewords(bitMatrix, version, formatInfo); + if (version.isMicroQRCode()) + return ReadMQRCodewords(bitMatrix, version, formatInfo); + else if (formatInfo.isModel1) + return ReadQRCodewordsModel1(bitMatrix, version, formatInfo); + else + return ReadQRCodewords(bitMatrix, version, formatInfo); } } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index 905f0a5c8d..2190618ca5 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -237,6 +237,9 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo StructuredAppendInfo structuredAppend; const int modeBitLength = CodecModeBitsLength(version); + if (version.isQRCodeModel1()) + bits.readBits(4); // Model 1 is leading with 4 0-bits -> drop them + try { while(!IsEndOfStream(bits, version)) { @@ -316,14 +319,16 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo DecoderResult Decode(const BitMatrix& bits) { - const Version* pversion = ReadVersion(bits); + bool isMicroQRCode = bits.height() < 21; + auto formatInfo = ReadFormatInformation(bits, isMicroQRCode); + if (!formatInfo.isValid()) + return FormatError("Invalid format information"); + + const Version* pversion = formatInfo.isModel1 ? Version::FromDimension(bits.height(), true) : ReadVersion(bits); if (!pversion) return FormatError("Invalid version"); - const Version& version = *pversion; - auto formatInfo = ReadFormatInformation(bits, version.isMicroQRCode()); - if (!formatInfo.isValid()) - return FormatError("Invalid format information"); + const Version& version = *pversion; // Read codewords ByteArray codewords = ReadCodewords(bits, version, formatInfo); diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index 9195a7fa65..c5502d1553 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -15,6 +15,8 @@ namespace ZXing::QRCode { static const int FORMAT_INFO_MASK_QR = 0x5412; +static const int FORMAT_INFO_MASK_QR_MODEL1 = 0x2825; + /** * See ISO 18004:2006, Annex C, Table C.1 */ @@ -93,13 +95,12 @@ static uint32_t MirrorBits(uint32_t bits) return BitHacks::Reverse(bits) >> 17; } -static FormatInformation FindBestFormatInfo(int mask, const std::array, 32> lookup, +static FormatInformation FindBestFormatInfo(const std::vector& masks, const std::array, 32> lookup, const std::vector& bits) { FormatInformation fi; - // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking. - for (auto mask : {0, mask}) + for (auto mask : masks) for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) for (const auto& [pattern, index] : lookup) { // Find the int in lookup with fewest bits differing @@ -122,8 +123,16 @@ FormatInformation FormatInformation::DecodeQR(uint32_t formatInfoBits1, uint32_t // maks out the 'Dark Module' for mirrored and non-mirrored case (see Figure 25 in ISO/IEC 18004:2015) uint32_t mirroredFormatInfoBits2 = MirrorBits(((formatInfoBits2 >> 1) & 0b111111110000000) | (formatInfoBits2 & 0b1111111)); formatInfoBits2 = ((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111); - auto fi = FindBestFormatInfo(FORMAT_INFO_MASK_QR, FORMAT_INFO_DECODE_LOOKUP, + // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking. + auto fi = FindBestFormatInfo({0, FORMAT_INFO_MASK_QR}, FORMAT_INFO_DECODE_LOOKUP, {formatInfoBits1, formatInfoBits2, MirrorBits(formatInfoBits1), mirroredFormatInfoBits2}); + auto fi_model1 = FindBestFormatInfo({FORMAT_INFO_MASK_QR ^ FORMAT_INFO_MASK_QR_MODEL1}, FORMAT_INFO_DECODE_LOOKUP, + {formatInfoBits1, formatInfoBits2, MirrorBits(formatInfoBits1), mirroredFormatInfoBits2}); + + if (fi_model1.hammingDistance < fi.hammingDistance) { + fi_model1.isModel1 = true; + fi = fi_model1; + } // Use bits 3/4 for error correction, and 0-2 for mask. fi.ecLevel = ECLevelFromBits((fi.index >> 3) & 0x03); @@ -139,7 +148,7 @@ FormatInformation FormatInformation::DecodeQR(uint32_t formatInfoBits1, uint32_t FormatInformation FormatInformation::DecodeMQR(uint32_t formatInfoBits) { // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders - auto fi = FindBestFormatInfo(0, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); + auto fi = FindBestFormatInfo({0}, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); constexpr uint8_t BITS_TO_VERSION[] = {1, 2, 2, 3, 3, 4, 4, 4}; diff --git a/core/src/qrcode/QRFormatInformation.h b/core/src/qrcode/QRFormatInformation.h index f5ebc096df..ec2d1ce322 100644 --- a/core/src/qrcode/QRFormatInformation.h +++ b/core/src/qrcode/QRFormatInformation.h @@ -18,6 +18,7 @@ class FormatInformation uint8_t index = 255; uint8_t hammingDistance = 255; bool isMirrored = false; + bool isModel1 = false; uint8_t dataMask = 0; uint8_t microVersion = 0; uint8_t bitsIndex = 255; diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 2f202b654d..83971c85ac 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -292,35 +292,136 @@ const Version* Version::AllMicroVersions() return allVersions; } +const Version* Version::AllModel1Versions() +{ + /** + * See ISO 18004:2000 M.4.2 Table M.2 + * See ISO 18004:2000 M.5 Table M.4 + */ + static const Version allVersions[] = { + {1, { + 7 , 1, 19, 0, 0, + 10, 1, 16, 0, 0, + 13, 1, 13, 0, 0, + 17, 1, 9 , 0, 0 + }}, + {2, { + 10, 1, 36, 0, 0, + 16, 1, 30, 0, 0, + 22, 1, 24, 0, 0, + 30, 1, 16, 0, 0, + }}, + {3, { + 15, 1, 57, 0, 0, + 28, 1, 44, 0, 0, + 36, 1, 36, 0, 0, + 48, 1, 24, 0, 0, + }}, + {4, { + 20, 1, 80, 0, 0, + 40, 1, 60, 0, 0, + 50, 1, 50, 0, 0, + 66, 1, 34, 0, 0, + }}, + {5, { + 26, 1, 108, 0, 0, + 52, 1, 82 , 0, 0, + 66, 1, 68 , 0, 0, + 88, 2, 46 , 0, 0, + }}, + {6, { + 34 , 1, 136, 0, 0, + 63 , 2, 106, 0, 0, + 84 , 2, 86 , 0, 0, + 112, 2, 58 , 0, 0, + }}, + {7, { + 42 , 1, 170, 0, 0, + 80 , 2, 132, 0, 0, + 104, 2, 108, 0, 0, + 138, 3, 72 , 0, 0, + }}, + {8, { + 48 , 2, 208, 0, 0, + 96 , 2, 160, 0, 0, + 128, 2, 128, 0, 0, + 168, 3, 87 , 0, 0, + }}, + {9, { + 60 , 2, 246, 0, 0, + 120, 2, 186, 0, 0, + 150, 3, 156, 0, 0, + 204, 3, 102, 0, 0, + }}, + {10, { + 68 , 2, 290, 0, 0, + 136, 2, 222, 0, 0, + 174, 3, 183, 0, 0, + 232, 4, 124, 0, 0, + }}, + {11, { + 80 , 2, 336, 0, 0, + 160, 4, 256, 0, 0, + 208, 4, 208, 0, 0, + 270, 5, 145, 0, 0, + }}, + {12, { + 92 , 2, 384, 0, 0, + 184, 4, 292, 0, 0, + 232, 4, 244, 0, 0, + 310, 5, 165, 0, 0, + }}, + {13, { + 108, 3, 432, 0, 0, + 208, 4, 332, 0, 0, + 264, 4, 276, 0, 0, + 348, 6, 192, 0, 0, + }}, + {14, { + 120, 3, 489, 0, 0, + 240, 4, 368, 0, 0, + 300, 5, 310, 0, 0, + 396, 6, 210, 0, 0, + }}, + }; + return allVersions; +} + +static inline bool isMicro(const std::array& ecBlocks) +{ + return ecBlocks[0].codewordsPerBlock < 7 || ecBlocks[0].codewordsPerBlock == 8; +} + Version::Version(int versionNumber, std::initializer_list alignmentPatternCenters, const std::array& ecBlocks) - : _versionNumber(versionNumber), _alignmentPatternCenters(alignmentPatternCenters), _ecBlocks(ecBlocks), _isMicro(false) + : _versionNumber(versionNumber), _alignmentPatternCenters(alignmentPatternCenters), _ecBlocks(ecBlocks), _isMicro(false), _isModel1(false) { _totalCodewords = ecBlocks[0].totalDataCodewords(); } Version::Version(int versionNumber, const std::array& ecBlocks) - : _versionNumber(versionNumber), _ecBlocks(ecBlocks), _isMicro(true) + : _versionNumber(versionNumber), _ecBlocks(ecBlocks), _isMicro(isMicro(ecBlocks)), _isModel1(!isMicro(ecBlocks)) { _totalCodewords = ecBlocks[0].totalDataCodewords(); } -const Version* Version::FromNumber(int versionNumber, bool isMicro) +const Version* Version::FromNumber(int versionNumber, bool isMicro, bool isModel1) { - if (versionNumber < 1 || versionNumber > (isMicro ? 4 : 40)) { + if (versionNumber < 1 || versionNumber > (isMicro ? 4 : (isModel1 ? 14 : 40))) { //throw std::invalid_argument("Version should be in range [1-40]."); return nullptr; } - return &(isMicro ? AllMicroVersions() : AllVersions())[versionNumber - 1]; + + return &(isMicro ? AllMicroVersions() : (isModel1 ? AllModel1Versions() : AllVersions()))[versionNumber - 1]; } -const Version* Version::FromDimension(int dimension) +const Version* Version::FromDimension(int dimension, bool isModel1) { bool isMicro = dimension < 21; if (dimension % DimensionStep(isMicro) != 1) { //throw std::invalid_argument("Unexpected dimension"); return nullptr; } - return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro); + return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro, isModel1); } const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBitsB) diff --git a/core/src/qrcode/QRVersion.h b/core/src/qrcode/QRVersion.h index 0b03270011..eca41d87e3 100644 --- a/core/src/qrcode/QRVersion.h +++ b/core/src/qrcode/QRVersion.h @@ -40,6 +40,7 @@ class Version BitMatrix buildFunctionPattern() const; bool isMicroQRCode() const { return _isMicro; } + bool isQRCodeModel1() const { return _isModel1; } static constexpr int DimensionStep(bool isMicro) { return std::array{4, 2}[isMicro]; } static constexpr int DimensionOffset(bool isMicro) { return std::array{17, 9}[isMicro]; } @@ -54,9 +55,9 @@ class Version * @param dimension dimension in modules * @return Version for a QR Code of that dimension */ - static const Version* FromDimension(int dimension); + static const Version* FromDimension(int dimension, bool isModel1 = false); - static const Version* FromNumber(int versionNumber, bool isMicro = false); + static const Version* FromNumber(int versionNumber, bool isMicro = false, bool isModel1 = false); static const Version* DecodeVersionInformation(int versionBitsA, int versionBitsB = 0); @@ -66,11 +67,13 @@ class Version std::array _ecBlocks; int _totalCodewords; bool _isMicro; + bool _isModel1; Version(int versionNumber, std::initializer_list alignmentPatternCenters, const std::array &ecBlocks); Version(int versionNumber, const std::array& ecBlocks); static const Version* AllVersions(); static const Version* AllMicroVersions(); + static const Version* AllModel1Versions(); }; } // QRCode diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 7b03109021..f28f775fcd 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -562,12 +562,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 16, 16, 270 }, }); - runTests("qrcode-2", "QRCode", 49, { - { 45, 47, 0 }, - { 45, 47, 90 }, - { 45, 47, 180 }, - { 45, 47, 270 }, - { 21, 1, pure }, // the misread is the 'outer' symbol in 16.png + runTests("qrcode-2", "QRCode", 50, { + { 46, 48, 0 }, + { 46, 48, 90 }, + { 46, 48, 180 }, + { 46, 48, 270 }, + { 22, 1, pure }, // the misread is the 'outer' symbol in 16.png }); runTests("qrcode-3", "QRCode", 28, { diff --git a/test/samples/qrcode-2/qr-model-1.png b/test/samples/qrcode-2/qr-model-1.png new file mode 100644 index 0000000000000000000000000000000000000000..4b199902d0bae9a8fd98dbe698d570069d80ac40 GIT binary patch literal 230 zcmeAS@N?(olHy`uVBq!ia0vp^P9V(43?y&PnzR~7u?6^qxB}__|Nk$&IsYz@HQUq0 zF+}71+DnF9%my5+0S8SdtY(Q}jH(NKJ#hus_iDb=GVF(QR)qtdDm~>V#Of0& Date: Sat, 21 Oct 2023 10:48:32 +0200 Subject: [PATCH 048/587] ios/README.md: wording cosmetic --- wrappers/ios/README.md | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/wrappers/ios/README.md b/wrappers/ios/README.md index d6f0158142..c6b6bfd1ba 100644 --- a/wrappers/ios/README.md +++ b/wrappers/ios/README.md @@ -1,27 +1,25 @@ # ZXingCpp iOS Framework -## Usage - -For general usage, please compare the source code provided in the demo project. +## Installation -### Swift PM +### SwiftPM -As this repository provides a `Package.swift` on root level, you can add `zxing-cpp` including wrapper code by adding a Package Dependency in Xcode. +As this repository provides a `Package.swift` on the root level, so you can add `zxing-cpp` including wrapper code by adding a Package Dependency in Xcode. -An alternative way is to check this repository out and add it as a local Swift Package by adding it as dependency to your app. +An alternative way is to check this repository out and add it as a local Swift Package by adding it as a dependency to your app. ### CocoaPods -As an alternative way, you can also rely on [CocoaPods](https://cocoapods.org/pods/zxing-cpp). Just add the Pod as dependency, for instance: +You can also use [CocoaPods](https://cocoapods.org/pods/zxing-cpp). Just add the Pod as a dependency: ``` pod 'zxing-cpp' ``` - -If you just need the core without the wrapper code, you can also rely on: - +The module to be imported is named `ZXingCpp`. If you just need the core without the wrapper code, you can use: ``` pod 'zxing-cpp/Core' ``` -The module to be imported is named `ZXingCpp`. +## Usage + +For general usage of the ObjectiveC/Swift wrapper, please have a look at the source code provided in the [demo project](demo). From 37b79cf4b3570fb4607485948099bfb2799f9333 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 21 Oct 2023 11:19:50 +0200 Subject: [PATCH 049/587] README: add trivial example and update c++20 info --- README.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4432c839cb..d293580518 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ZXing-C++ ("zebra crossing") is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. -It was originally ported from the Java [ZXing Library](https://github.com/zxing/zxing) but has been developed further and now includes many improvements in terms of quality and performance. It can both read and write barcodes in a number of formats. +It was originally ported from the Java [ZXing Library](https://github.com/zxing/zxing) but has been developed further and now includes many improvements in terms of runtime and detection performance. It can both read and write barcodes in a number of formats. ## Sponsors @@ -45,21 +45,44 @@ Thanks a lot for your contribution! [Note:] * DataBar used to be called RSS. * DataBar, MaxiCode and Micro QR Code are not supported for writing. - * Building with C++20 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behaviour of the library: it then supports multi-symbol and position independent detection for DataMatrix. This comes at a noticable performace cost. To enable this in the Android library, one needs to use at least NDK [version 25](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/wrappers/android/zxingcpp/build.gradle#L9). + * Building with C++20 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behaviour of the library: it then supports multi-symbol and position independent detection for DataMatrix. This comes at a noticable performace cost. C++20 is enabled by default for the Android, iOS, Python and WASM wrappers. ## Getting Started ### To read barcodes: -As an example, have a look at [`ZXingReader.cpp`](example/ZXingReader.cpp). 1. Load your image into memory (3rd-party library required). 2. Call `ReadBarcodes()` from [`ReadBarcode.h`](core/src/ReadBarcode.h), the simplest API to get a list of `Result` objects. +A very simple example looks like this: +```c++ +#include "ZXing/ReadBarcode.h" +#include + +int main(int argc, char** argv) +{ + int width, height; + unsigned char* data; + // load your image data from somewhere. ImageFormat::Lum assumes grey scale image data. + + auto image = ZXing::ImageView(data, width, height, ZXing::ImageFormat::Lum); + auto hints = ZXing::DecodeHints().setFormats(ZXing::BarcodeFormat::Any); + auto results = ZXing::ReadBarcodes(image, hints); + + for (const auto& r : results) + std::cout << ZXing::ToString(r.format()) << ": " << r.text() << "\n"; + + return 0; +} +``` +To see the full capability of the API, have a look at [`ZXingReader.cpp`](example/ZXingReader.cpp). + ### To write barcodes: -As an example, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). 1. Create a [`MultiFormatWriter`](core/src/MultiFormatWriter.h) instance with the format you want to generate. Set encoding and margins if needed. 2. Call `encode()` with text content and the image size. This returns a [`BitMatrix`](core/src/BitMatrix.h) which is a binary image of the barcode where `true` == visual black and `false` == visual white. 3. Convert the bit matrix to your native image format. See also the `ToMatrix(BitMatrix&)` helper function. +As an example, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). + ## Web Demos - [Read barcodes](https://zxing-cpp.github.io/zxing-cpp/demo_reader.html) - [Write barcodes](https://zxing-cpp.github.io/zxing-cpp/demo_writer.html) From 338b3062d9d569e8148304d397bd6ca5cb890a67 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 21 Oct 2023 11:41:56 +0200 Subject: [PATCH 050/587] QRDecoder: fix symbologyIdentifier for Model 1 symbols --- core/src/qrcode/QRDecoder.cpp | 4 +++- test/samples/qrcode-2/qr-model-1.result.txt | 2 ++ 2 files changed, 5 insertions(+), 1 deletion(-) create mode 100644 test/samples/qrcode-2/qr-model-1.result.txt diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index 2190618ca5..f3993fba58 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -233,7 +233,7 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo BitSource bits(bytes); Content result; Error error; - result.symbology = {'Q', '1', 1}; + result.symbology = {'Q', version.isQRCodeModel1() ? '0' : '1', 1}; StructuredAppendInfo structuredAppend; const int modeBitLength = CodecModeBitsLength(version); @@ -277,6 +277,8 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo structuredAppend.id = std::to_string(bits.readBits(8)); break; case CodecMode::ECI: + if (version.isQRCodeModel1()) + throw FormatError("QRCode Model 1 does not support ECI"); // Count doesn't apply to ECI result.switchEncoding(ParseECIValue(bits)); break; diff --git a/test/samples/qrcode-2/qr-model-1.result.txt b/test/samples/qrcode-2/qr-model-1.result.txt new file mode 100644 index 0000000000..7f3634787a --- /dev/null +++ b/test/samples/qrcode-2/qr-model-1.result.txt @@ -0,0 +1,2 @@ +symbologyIdentifier=]Q0 +ecLevel=M From b8c3d1a150d0290e29200a3ba839d7c076b77bf7 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 22 Oct 2023 00:21:52 +0200 Subject: [PATCH 051/587] QRCode: restructure FormatInfo and Version parsing (post Model1 cleanup) * introduce QRCode::Type enum to distinguish Model1, Model2 and Micro * first parse FormatInfo then Version because latter depends on former * cleanup FormatInfo search regarding mask application * replace `FromNumber` with explicit Model1/Model2/Micro getters --- core/src/qrcode/QRBitMatrixParser.cpp | 45 +++---- core/src/qrcode/QRBitMatrixParser.h | 6 +- core/src/qrcode/QRCodecMode.cpp | 6 +- core/src/qrcode/QRDecoder.cpp | 16 ++- core/src/qrcode/QREncoder.cpp | 6 +- core/src/qrcode/QRErrorCorrectionLevel.h | 7 + core/src/qrcode/QRFormatInformation.cpp | 123 ++++-------------- core/src/qrcode/QRFormatInformation.h | 23 +++- core/src/qrcode/QRVersion.cpp | 74 ++++++----- core/src/qrcode/QRVersion.h | 35 +++-- test/unit/qrcode/QRBitMatrixParserTest.cpp | 8 +- .../qrcode/QRDecodedBitStreamParserTest.cpp | 14 +- test/unit/qrcode/QREncoderTest.cpp | 8 +- test/unit/qrcode/QRFormatInformationTest.cpp | 2 +- test/unit/qrcode/QRModeTest.cpp | 24 ++-- test/unit/qrcode/QRVersionTest.cpp | 20 ++- 16 files changed, 177 insertions(+), 240 deletions(-) diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index c554242226..abf265241b 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -22,24 +22,24 @@ static bool getBit(const BitMatrix& bitMatrix, int x, int y, bool mirrored = fal return mirrored ? bitMatrix.get(y, x) : bitMatrix.get(x, y); } -static bool hasValidDimension(const BitMatrix& bitMatrix, bool isMicro) +const Version* ReadVersion(const BitMatrix& bitMatrix, Type type) { - int dimension = bitMatrix.height(); - if (isMicro) - return dimension >= 11 && dimension <= 17 && (dimension % 2) == 1; - else - return dimension >= 21 && dimension <= 177 && (dimension % 4) == 1; -} + assert(Version::HasValidSize(bitMatrix)); -const Version* ReadVersion(const BitMatrix& bitMatrix) -{ - int dimension = bitMatrix.height(); + int number = Version::Number(bitMatrix); - const Version* version = Version::FromDimension(dimension); + switch (type) { + case Type::Micro: return Version::Micro(number); + case Type::Model1: return Version::Model1(number); + case Type::Model2: break; + } + const Version* version = Version::Model2(number); if (!version || version->versionNumber() < 7) return version; + int dimension = bitMatrix.height(); + for (bool mirror : {false, true}) { // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) int versionBits = 0; @@ -55,12 +55,9 @@ const Version* ReadVersion(const BitMatrix& bitMatrix) return nullptr; } -FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix, bool isMicro) +FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix) { - if (!hasValidDimension(bitMatrix, isMicro)) - return {}; - - if (isMicro) { + if (Version::HasMicroSize(bitMatrix)) { // Read top-left format info bits int formatInfoBits = 0; for (int x = 1; x < 9; x++) @@ -137,7 +134,6 @@ static ByteArray ReadQRCodewords(const BitMatrix& bitMatrix, const Version& vers static ByteArray ReadQRCodewordsModel1(const BitMatrix& bitMatrix, const Version& version, const FormatInformation& formatInfo) { - ByteArray result; result.reserve(version.totalCodewords()); int dimension = bitMatrix.height(); @@ -243,14 +239,13 @@ static ByteArray ReadMQRCodewords(const BitMatrix& bitMatrix, const QRCode::Vers ByteArray ReadCodewords(const BitMatrix& bitMatrix, const Version& version, const FormatInformation& formatInfo) { - if (!hasValidDimension(bitMatrix, version.isMicroQRCode())) - return {}; - if (version.isMicroQRCode()) - return ReadMQRCodewords(bitMatrix, version, formatInfo); - else if (formatInfo.isModel1) - return ReadQRCodewordsModel1(bitMatrix, version, formatInfo); - else - return ReadQRCodewords(bitMatrix, version, formatInfo); + switch (version.type()) { + case Type::Micro: return ReadMQRCodewords(bitMatrix, version, formatInfo); + case Type::Model1: return ReadQRCodewordsModel1(bitMatrix, version, formatInfo); + case Type::Model2: return ReadQRCodewords(bitMatrix, version, formatInfo); + } + + return {}; } } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRBitMatrixParser.h b/core/src/qrcode/QRBitMatrixParser.h index 0f070c8403..e695472b84 100644 --- a/core/src/qrcode/QRBitMatrixParser.h +++ b/core/src/qrcode/QRBitMatrixParser.h @@ -6,6 +6,8 @@ #pragma once +#include "QRErrorCorrectionLevel.h" + namespace ZXing { class BitMatrix; @@ -20,14 +22,14 @@ class FormatInformation; * @brief Reads version information from the QR Code. * @return {@link Version} encapsulating the QR Code's version, nullptr if neither location can be parsed */ -const Version* ReadVersion(const BitMatrix& bitMatrix); +const Version* ReadVersion(const BitMatrix& bitMatrix, Type type); /** * @brief Reads format information from one of its two locations within the QR Code. * @return {@link FormatInformation} encapsulating the QR Code's format info, result is invalid if both format * information locations cannot be parsed as the valid encoding of format information */ -FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix, bool isMicro); +FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix); /** * @brief Reads the codewords from the BitMatrix. diff --git a/core/src/qrcode/QRCodecMode.cpp b/core/src/qrcode/QRCodecMode.cpp index 3d9f435996..c494151fca 100644 --- a/core/src/qrcode/QRCodecMode.cpp +++ b/core/src/qrcode/QRCodecMode.cpp @@ -32,7 +32,7 @@ CodecMode CodecModeForBits(int bits, bool isMicro) int CharacterCountBits(CodecMode mode, const Version& version) { int number = version.versionNumber(); - if (version.isMicroQRCode()) { + if (version.isMicro()) { switch (mode) { case CodecMode::NUMERIC: return std::array{3, 4, 5, 6}[number - 1]; case CodecMode::ALPHANUMERIC: return std::array{3, 4, 5}[number - 2]; @@ -63,12 +63,12 @@ int CharacterCountBits(CodecMode mode, const Version& version) int CodecModeBitsLength(const Version& version) { - return version.isMicroQRCode() ? version.versionNumber() - 1 : 4; + return version.isMicro() ? version.versionNumber() - 1 : 4; } int TerminatorBitsLength(const Version& version) { - return version.isMicroQRCode() ? version.versionNumber() * 2 + 1 : 4; + return version.isMicro() ? version.versionNumber() * 2 + 1 : 4; } } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index f3993fba58..dfdcbf1477 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -233,11 +233,11 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo BitSource bits(bytes); Content result; Error error; - result.symbology = {'Q', version.isQRCodeModel1() ? '0' : '1', 1}; + result.symbology = {'Q', version.isModel1() ? '0' : '1', 1}; StructuredAppendInfo structuredAppend; const int modeBitLength = CodecModeBitsLength(version); - if (version.isQRCodeModel1()) + if (version.isModel1()) bits.readBits(4); // Model 1 is leading with 4 0-bits -> drop them try @@ -247,7 +247,7 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo if (modeBitLength == 0) mode = CodecMode::NUMERIC; // MicroQRCode version 1 is always NUMERIC and modeBitLength is 0 else - mode = CodecModeForBits(bits.readBits(modeBitLength), version.isMicroQRCode()); + mode = CodecModeForBits(bits.readBits(modeBitLength), version.isMicro()); switch (mode) { case CodecMode::FNC1_FIRST_POSITION: @@ -277,7 +277,7 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo structuredAppend.id = std::to_string(bits.readBits(8)); break; case CodecMode::ECI: - if (version.isQRCodeModel1()) + if (version.isModel1()) throw FormatError("QRCode Model 1 does not support ECI"); // Count doesn't apply to ECI result.switchEncoding(ParseECIValue(bits)); @@ -321,12 +321,14 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo DecoderResult Decode(const BitMatrix& bits) { - bool isMicroQRCode = bits.height() < 21; - auto formatInfo = ReadFormatInformation(bits, isMicroQRCode); + if (!Version::HasValidSize(bits)) + return FormatError("Invalid symbol size"); + + auto formatInfo = ReadFormatInformation(bits); if (!formatInfo.isValid()) return FormatError("Invalid format information"); - const Version* pversion = formatInfo.isModel1 ? Version::FromDimension(bits.height(), true) : ReadVersion(bits); + const Version* pversion = ReadVersion(bits, formatInfo.type()); if (!pversion) return FormatError("Invalid version"); diff --git a/core/src/qrcode/QREncoder.cpp b/core/src/qrcode/QREncoder.cpp index cc3f71d8d3..89d7df1398 100644 --- a/core/src/qrcode/QREncoder.cpp +++ b/core/src/qrcode/QREncoder.cpp @@ -267,7 +267,7 @@ static bool WillFit(int numInputBits, const Version& version, ErrorCorrectionLev static const Version& ChooseVersion(int numInputBits, ErrorCorrectionLevel ecLevel) { for (int versionNum = 1; versionNum <= 40; versionNum++) { - const Version* version = Version::FromNumber(versionNum); + const Version* version = Version::Model2(versionNum); if (WillFit(numInputBits, *version, ecLevel)) { return *version; } @@ -472,7 +472,7 @@ static const Version& RecommendVersion(ErrorCorrectionLevel ecLevel, CodecMode m // Hard part: need to know version to know how many bits length takes. But need to know how many // bits it takes to know version. First we take a guess at version by assuming version will be // the minimum, 1: - int provisionalBitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *Version::FromNumber(1)); + int provisionalBitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *Version::Model2(1)); const Version& provisionalVersion = ChooseVersion(provisionalBitsNeeded, ecLevel); // Use that guess to calculate the right version. I am still not sure this works in 100% of cases. @@ -517,7 +517,7 @@ EncodeResult Encode(const std::wstring& content, ErrorCorrectionLevel ecLevel, C const Version* version; if (versionNumber > 0) { - version = Version::FromNumber(versionNumber); + version = Version::Model2(versionNumber); if (version != nullptr) { int bitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *version); if (!WillFit(bitsNeeded, *version, ecLevel)) { diff --git a/core/src/qrcode/QRErrorCorrectionLevel.h b/core/src/qrcode/QRErrorCorrectionLevel.h index 38bd9ac307..82708f8d9c 100644 --- a/core/src/qrcode/QRErrorCorrectionLevel.h +++ b/core/src/qrcode/QRErrorCorrectionLevel.h @@ -28,4 +28,11 @@ ErrorCorrectionLevel ECLevelFromString(const char* str); ErrorCorrectionLevel ECLevelFromBits(int bits, const bool isMicro = false); int BitsFromECLevel(ErrorCorrectionLevel l); +enum class Type +{ + Model1, + Model2, + Micro, +}; + } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index c5502d1553..ac811d72a2 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -1,6 +1,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -13,99 +14,31 @@ namespace ZXing::QRCode { -static const int FORMAT_INFO_MASK_QR = 0x5412; - -static const int FORMAT_INFO_MASK_QR_MODEL1 = 0x2825; - -/** -* See ISO 18004:2006, Annex C, Table C.1 -*/ -static const std::array, 32> FORMAT_INFO_DECODE_LOOKUP = {{ - {0x5412, 0x00}, - {0x5125, 0x01}, - {0x5E7C, 0x02}, - {0x5B4B, 0x03}, - {0x45F9, 0x04}, - {0x40CE, 0x05}, - {0x4F97, 0x06}, - {0x4AA0, 0x07}, - {0x77C4, 0x08}, - {0x72F3, 0x09}, - {0x7DAA, 0x0A}, - {0x789D, 0x0B}, - {0x662F, 0x0C}, - {0x6318, 0x0D}, - {0x6C41, 0x0E}, - {0x6976, 0x0F}, - {0x1689, 0x10}, - {0x13BE, 0x11}, - {0x1CE7, 0x12}, - {0x19D0, 0x13}, - {0x0762, 0x14}, - {0x0255, 0x15}, - {0x0D0C, 0x16}, - {0x083B, 0x17}, - {0x355F, 0x18}, - {0x3068, 0x19}, - {0x3F31, 0x1A}, - {0x3A06, 0x1B}, - {0x24B4, 0x1C}, - {0x2183, 0x1D}, - {0x2EDA, 0x1E}, - {0x2BED, 0x1F}, -}}; - -static const std::array, 32> FORMAT_INFO_DECODE_LOOKUP_MICRO = {{ - {0x4445, 0x00}, - {0x4172, 0x01}, - {0x4E2B, 0x02}, - {0x4B1C, 0x03}, - {0x55AE, 0x04}, - {0x5099, 0x05}, - {0x5FC0, 0x06}, - {0x5AF7, 0x07}, - {0x6793, 0x08}, - {0x62A4, 0x09}, - {0x6DFD, 0x0A}, - {0x68CA, 0x0B}, - {0x7678, 0x0C}, - {0x734F, 0x0D}, - {0x7C16, 0x0E}, - {0x7921, 0x0F}, - {0x06DE, 0x10}, - {0x03E9, 0x11}, - {0x0CB0, 0x12}, - {0x0987, 0x13}, - {0x1735, 0x14}, - {0x1202, 0x15}, - {0x1D5B, 0x16}, - {0x186C, 0x17}, - {0x2508, 0x18}, - {0x203F, 0x19}, - {0x2F66, 0x1A}, - {0x2A51, 0x1B}, - {0x34E3, 0x1C}, - {0x31D4, 0x1D}, - {0x3E8D, 0x1E}, - {0x3BBA, 0x1F}, -}}; - static uint32_t MirrorBits(uint32_t bits) { return BitHacks::Reverse(bits) >> 17; } -static FormatInformation FindBestFormatInfo(const std::vector& masks, const std::array, 32> lookup, - const std::vector& bits) +static FormatInformation FindBestFormatInfo(const std::vector& masks, const std::vector& bits) { + // See ISO 18004:2015, Annex C, Table C.1 + constexpr uint32_t MODEL2_MASKED_PATTERNS[] = { + 0x5412, 0x5125, 0x5E7C, 0x5B4B, 0x45F9, 0x40CE, 0x4F97, 0x4AA0, 0x77C4, 0x72F3, 0x7DAA, 0x789D, 0x662F, 0x6318, 0x6C41, 0x6976, + 0x1689, 0x13BE, 0x1CE7, 0x19D0, 0x0762, 0x0255, 0x0D0C, 0x083B, 0x355F, 0x3068, 0x3F31, 0x3A06, 0x24B4, 0x2183, 0x2EDA, 0x2BED, + }; + FormatInformation fi; for (auto mask : masks) for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) - for (const auto& [pattern, index] : lookup) { - // Find the int in lookup with fewest bits differing - if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); hammingDist < fi.hammingDistance) { - fi.index = index; + for (uint32_t pattern : MODEL2_MASKED_PATTERNS) { + // 'unmask' the pattern first to get the original 5-data bits + 10-ec bits back + pattern ^= FORMAT_INFO_MASK_MODEL2; + // Find the pattern with fewest bits differing + if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); + hammingDist < fi.hammingDistance) { + fi.mask = mask; // store the used mask to discriminate between types/models + fi.data = pattern >> 10; // drop the 10 BCH error correction bits fi.hammingDistance = hammingDist; fi.bitsIndex = bitsIndex; } @@ -123,20 +56,13 @@ FormatInformation FormatInformation::DecodeQR(uint32_t formatInfoBits1, uint32_t // maks out the 'Dark Module' for mirrored and non-mirrored case (see Figure 25 in ISO/IEC 18004:2015) uint32_t mirroredFormatInfoBits2 = MirrorBits(((formatInfoBits2 >> 1) & 0b111111110000000) | (formatInfoBits2 & 0b1111111)); formatInfoBits2 = ((formatInfoBits2 >> 1) & 0b111111100000000) | (formatInfoBits2 & 0b11111111); - // Some QR codes apparently do not apply the XOR mask. Try without and with additional masking. - auto fi = FindBestFormatInfo({0, FORMAT_INFO_MASK_QR}, FORMAT_INFO_DECODE_LOOKUP, + // Some (Model2) QR codes apparently do not apply the XOR mask. Try with (standard) and without (quirk) masking. + auto fi = FindBestFormatInfo({FORMAT_INFO_MASK_MODEL2, 0, FORMAT_INFO_MASK_MODEL1}, {formatInfoBits1, formatInfoBits2, MirrorBits(formatInfoBits1), mirroredFormatInfoBits2}); - auto fi_model1 = FindBestFormatInfo({FORMAT_INFO_MASK_QR ^ FORMAT_INFO_MASK_QR_MODEL1}, FORMAT_INFO_DECODE_LOOKUP, - {formatInfoBits1, formatInfoBits2, MirrorBits(formatInfoBits1), mirroredFormatInfoBits2}); - - if (fi_model1.hammingDistance < fi.hammingDistance) { - fi_model1.isModel1 = true; - fi = fi_model1; - } // Use bits 3/4 for error correction, and 0-2 for mask. - fi.ecLevel = ECLevelFromBits((fi.index >> 3) & 0x03); - fi.dataMask = static_cast(fi.index & 0x07); + fi.ecLevel = ECLevelFromBits((fi.data >> 3) & 0x03); + fi.dataMask = static_cast(fi.data & 0x07); fi.isMirrored = fi.bitsIndex > 1; return fi; @@ -147,15 +73,14 @@ FormatInformation FormatInformation::DecodeQR(uint32_t formatInfoBits1, uint32_t */ FormatInformation FormatInformation::DecodeMQR(uint32_t formatInfoBits) { - // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders - auto fi = FindBestFormatInfo({0}, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); + auto fi = FindBestFormatInfo({FORMAT_INFO_MASK_MICRO}, {formatInfoBits, MirrorBits(formatInfoBits)}); constexpr uint8_t BITS_TO_VERSION[] = {1, 2, 2, 3, 3, 4, 4, 4}; // Bits 2/3/4 contain both error correction level and version, 0/1 contain mask. - fi.ecLevel = ECLevelFromBits((fi.index >> 2) & 0x07, true); - fi.dataMask = static_cast(fi.index & 0x03); - fi.microVersion = BITS_TO_VERSION[(fi.index >> 2) & 0x07]; + fi.ecLevel = ECLevelFromBits((fi.data >> 2) & 0x07, true); + fi.dataMask = static_cast(fi.data & 0x03); + fi.microVersion = BITS_TO_VERSION[(fi.data >> 2) & 0x07]; fi.isMirrored = fi.bitsIndex == 1; return fi; diff --git a/core/src/qrcode/QRFormatInformation.h b/core/src/qrcode/QRFormatInformation.h index ec2d1ce322..78564e6954 100644 --- a/core/src/qrcode/QRFormatInformation.h +++ b/core/src/qrcode/QRFormatInformation.h @@ -1,6 +1,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -12,16 +13,21 @@ namespace ZXing::QRCode { +static constexpr uint32_t FORMAT_INFO_MASK_MODEL2 = 0x5412; +static constexpr uint32_t FORMAT_INFO_MASK_MODEL1 = 0x2825; +static constexpr uint32_t FORMAT_INFO_MASK_MICRO = 0x4445; + class FormatInformation { public: - uint8_t index = 255; + uint32_t mask = 0; + uint8_t data = 255; uint8_t hammingDistance = 255; + uint8_t bitsIndex = 255; + bool isMirrored = false; - bool isModel1 = false; uint8_t dataMask = 0; uint8_t microVersion = 0; - uint8_t bitsIndex = 255; ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Invalid; FormatInformation() = default; @@ -32,9 +38,18 @@ class FormatInformation // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match bool isValid() const { return hammingDistance <= 3; } + Type type() const + { + switch (mask) { + case FORMAT_INFO_MASK_MODEL1: return Type::Model1; + case FORMAT_INFO_MASK_MICRO: return Type::Micro; + default: return Type::Model2; + } + } + bool operator==(const FormatInformation& other) const { - return dataMask == other.dataMask && ecLevel == other.ecLevel; + return dataMask == other.dataMask && ecLevel == other.ecLevel && type() == other.type(); } }; diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 83971c85ac..5bc53f6207 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -28,8 +28,7 @@ namespace ZXing::QRCode { 0x2542E, 0x26A64, 0x27541, 0x28C69 }; -const Version * -Version::AllVersions() +const Version* Version::Model2(int number) { /** * See ISO 18004:2006 6.5.1 Table 9 @@ -276,10 +275,13 @@ Version::AllVersions() 30, 20, 15, 61, 16 }}, }; - return allVersions; + + if (number < 1 || number > 40) + return nullptr; + return allVersions + number - 1; } -const Version* Version::AllMicroVersions() +const Version* Version::Micro(int number) { /** * See ISO 18004:2006 6.5.1 Table 9 @@ -289,10 +291,13 @@ const Version* Version::AllMicroVersions() {2, {5, 1, 5, 0, 0, 6, 1, 4, 0, 0}}, {3, {6, 1, 11, 0, 0, 8, 1, 9, 0, 0}}, {4, {8, 1, 16, 0, 0, 10, 1, 14, 0, 0, 14, 1, 10, 0, 0}}}; - return allVersions; + + if (number < 1 || number > 4) + return nullptr; + return allVersions + number - 1; } -const Version* Version::AllModel1Versions() +const Version* Version::Model1(int number) { /** * See ISO 18004:2000 M.4.2 Table M.2 @@ -384,44 +389,45 @@ const Version* Version::AllModel1Versions() 396, 6, 210, 0, 0, }}, }; - return allVersions; -} -static inline bool isMicro(const std::array& ecBlocks) -{ - return ecBlocks[0].codewordsPerBlock < 7 || ecBlocks[0].codewordsPerBlock == 8; + if (number < 1 || number > 14) + return nullptr; + return allVersions + number - 1; } Version::Version(int versionNumber, std::initializer_list alignmentPatternCenters, const std::array& ecBlocks) - : _versionNumber(versionNumber), _alignmentPatternCenters(alignmentPatternCenters), _ecBlocks(ecBlocks), _isMicro(false), _isModel1(false) + : _versionNumber(versionNumber), _alignmentPatternCenters(alignmentPatternCenters), _ecBlocks(ecBlocks), _type(Type::Model2) { _totalCodewords = ecBlocks[0].totalDataCodewords(); } Version::Version(int versionNumber, const std::array& ecBlocks) - : _versionNumber(versionNumber), _ecBlocks(ecBlocks), _isMicro(isMicro(ecBlocks)), _isModel1(!isMicro(ecBlocks)) + : _versionNumber(versionNumber), + _ecBlocks(ecBlocks), + _type(ecBlocks[0].codewordsPerBlock < 7 || ecBlocks[0].codewordsPerBlock == 8 ? Type::Micro : Type::Model1) { _totalCodewords = ecBlocks[0].totalDataCodewords(); } -const Version* Version::FromNumber(int versionNumber, bool isMicro, bool isModel1) +bool Version::HasMicroSize(const BitMatrix& bitMatrix) { - if (versionNumber < 1 || versionNumber > (isMicro ? 4 : (isModel1 ? 14 : 40))) { - //throw std::invalid_argument("Version should be in range [1-40]."); - return nullptr; - } + int size = bitMatrix.height(); + return size >= 11 && size <= 17 && (size % 2) == 1; +} - return &(isMicro ? AllMicroVersions() : (isModel1 ? AllModel1Versions() : AllVersions()))[versionNumber - 1]; +bool Version::HasValidSize(const BitMatrix& bitMatrix) +{ + int size = bitMatrix.height(); + return HasMicroSize(bitMatrix) || (size >= 21 && size <= 177 && (size % 4) == 1); } -const Version* Version::FromDimension(int dimension, bool isModel1) +int Version::Number(const BitMatrix& bitMatrix) { - bool isMicro = dimension < 21; - if (dimension % DimensionStep(isMicro) != 1) { - //throw std::invalid_argument("Unexpected dimension"); - return nullptr; - } - return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro, isModel1); + if (!HasValidSize(bitMatrix)) + return 0; + + bool isMicro = HasMicroSize(bitMatrix); + return (bitMatrix.height() - DimensionOffset(isMicro)) / DimensionStep(isMicro); } const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBitsB) @@ -430,12 +436,6 @@ const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBi int bestVersion = 0; int i = 0; for (int targetVersion : VERSION_DECODE_INFO) { - // Do the version info bits match exactly? done. - if (targetVersion == versionBitsA || targetVersion == versionBitsB) { - return FromNumber(i + 7); - } - // Otherwise see if this is the closest to a real version info bit string - // we have seen so far for (int bits : {versionBitsA, versionBitsB}) { int bitsDifference = BitHacks::CountBitsSet(bits ^ targetVersion); if (bitsDifference < bestDifference) { @@ -443,13 +443,15 @@ const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBi bestDifference = bitsDifference; } } + if (bestDifference == 0) + break; ++i; } // We can tolerate up to 3 bits of error since no two version info codewords will // differ in less than 8 bits. - if (bestDifference <= 3) { - return FromNumber(bestVersion); - } + if (bestDifference <= 3) + return Model2(bestVersion); + // If we didn't find a close enough match, fail return nullptr; } @@ -465,7 +467,7 @@ BitMatrix Version::buildFunctionPattern() const // Top left finder pattern + separator + format bitMatrix.setRegion(0, 0, 9, 9); - if (!_isMicro) { + if (!isMicro()) { // Top right finder pattern + separator + format bitMatrix.setRegion(dimension - 8, 0, 8, 9); // Bottom left finder pattern + separator + format diff --git a/core/src/qrcode/QRVersion.h b/core/src/qrcode/QRVersion.h index eca41d87e3..e73091d75b 100644 --- a/core/src/qrcode/QRVersion.h +++ b/core/src/qrcode/QRVersion.h @@ -1,6 +1,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -21,27 +22,27 @@ namespace QRCode { /** * See ISO 18004:2006 Annex D -* -* @author Sean Owen */ class Version { public: + Type type() const { return _type; } + bool isMicro() const { return type() == Type::Micro; } + bool isModel1() const { return type() == Type::Model1; } + bool isModel2() const { return type() == Type::Model2; } + int versionNumber() const { return _versionNumber; } const std::vector& alignmentPatternCenters() const { return _alignmentPatternCenters; } int totalCodewords() const { return _totalCodewords; } - int dimension() const { return DimensionOfVersion(_versionNumber, _isMicro); } + int dimension() const { return DimensionOfVersion(_versionNumber, isMicro()); } const ECBlocks& ecBlocksForLevel(ErrorCorrectionLevel ecLevel) const { return _ecBlocks[(int)ecLevel]; } BitMatrix buildFunctionPattern() const; - bool isMicroQRCode() const { return _isMicro; } - bool isQRCodeModel1() const { return _isModel1; } - static constexpr int DimensionStep(bool isMicro) { return std::array{4, 2}[isMicro]; } static constexpr int DimensionOffset(bool isMicro) { return std::array{17, 9}[isMicro]; } static constexpr int DimensionOfVersion(int version, bool isMicro) @@ -49,31 +50,25 @@ class Version return DimensionOffset(isMicro) + DimensionStep(isMicro) * version; } - /** - *

    Deduces version information purely from micro QR or QR Code dimensions.

    - * - * @param dimension dimension in modules - * @return Version for a QR Code of that dimension - */ - static const Version* FromDimension(int dimension, bool isModel1 = false); - - static const Version* FromNumber(int versionNumber, bool isMicro = false, bool isModel1 = false); + static bool HasMicroSize(const BitMatrix& bitMatrix); + static bool HasValidSize(const BitMatrix& bitMatrix); + static int Number(const BitMatrix& bitMatrix); static const Version* DecodeVersionInformation(int versionBitsA, int versionBitsB = 0); + static const Version* Model1(int number); + static const Version* Model2(int number); + static const Version* Micro(int number); + private: int _versionNumber; std::vector _alignmentPatternCenters; std::array _ecBlocks; int _totalCodewords; - bool _isMicro; - bool _isModel1; + Type _type; Version(int versionNumber, std::initializer_list alignmentPatternCenters, const std::array &ecBlocks); Version(int versionNumber, const std::array& ecBlocks); - static const Version* AllVersions(); - static const Version* AllMicroVersions(); - static const Version* AllModel1Versions(); }; } // QRCode diff --git a/test/unit/qrcode/QRBitMatrixParserTest.cpp b/test/unit/qrcode/QRBitMatrixParserTest.cpp index f8c5a93a0d..4b438a304d 100644 --- a/test/unit/qrcode/QRBitMatrixParserTest.cpp +++ b/test/unit/qrcode/QRBitMatrixParserTest.cpp @@ -35,9 +35,9 @@ TEST(QRBitMatrixParserTest, MQRCodeM3L) "XXX XX X X XXXX\n", 88, false); - const auto version = ReadVersion(bitMatrix); + const auto format = ReadFormatInformation(bitMatrix); + const auto version = ReadVersion(bitMatrix, format.type()); EXPECT_EQ(3, version->versionNumber()); - const auto format = ReadFormatInformation(bitMatrix, true); const auto codewords = ReadCodewords(bitMatrix, *version, format); EXPECT_EQ(17, codewords.size()); EXPECT_EQ(0x0, codewords[10]); @@ -63,9 +63,9 @@ TEST(QRBitMatrixParserTest, MQRCodeM3M) "X X XXXX XXX\n", 88, false); - const auto version = ReadVersion(bitMatrix); + const auto format = ReadFormatInformation(bitMatrix); + const auto version = ReadVersion(bitMatrix, format.type()); EXPECT_EQ(3, version->versionNumber()); - const auto format = ReadFormatInformation(bitMatrix, true); const auto codewords = ReadCodewords(bitMatrix, *version, format); EXPECT_EQ(17, codewords.size()); EXPECT_EQ(0x0, codewords[8]); diff --git a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp index b979e73855..26c71beaf3 100644 --- a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp +++ b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp @@ -7,8 +7,6 @@ #include "BitArray.h" #include "ByteArray.h" #include "DecoderResult.h" -#include "qrcode/QRDataMask.h" -#include "qrcode/QRDecoder.h" #include "qrcode/QRErrorCorrectionLevel.h" #include "qrcode/QRVersion.h" @@ -31,7 +29,7 @@ TEST(QRDecodedBitStreamParserTest, SimpleByteMode) ba.appendBits(0xF1, 8); ba.appendBits(0xF2, 8); ba.appendBits(0xF3, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::Model2(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\xF1\xF2\xF3", result); } @@ -44,7 +42,7 @@ TEST(QRDecodedBitStreamParserTest, SimpleSJIS) ba.appendBits(0xA2, 8); ba.appendBits(0xA3, 8); ba.appendBits(0xD0, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::Model2(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\uff61\uff62\uff63\uff90", result); } @@ -58,7 +56,7 @@ TEST(QRDecodedBitStreamParserTest, ECI) ba.appendBits(0xA1, 8); ba.appendBits(0xA2, 8); ba.appendBits(0xA3, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::Model2(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\xED\xF3\xFA", result); } @@ -69,7 +67,7 @@ TEST(QRDecodedBitStreamParserTest, Hanzi) ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding ba.appendBits(0x01, 8); // 1 characters ba.appendBits(0x03C1, 13); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::Model2(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\u963f", result); } @@ -82,13 +80,13 @@ TEST(QRDecodedBitStreamParserTest, HanziLevel1) // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 ba.appendBits(0x0181, 13); - auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::Model2(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\u30a2", result); } TEST(QRDecodedBitStreamParserTest, SymbologyIdentifier) { - const Version& version = *Version::FromNumber(1); + const Version& version = *Version::Model2(1); const ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Medium; DecoderResult result; diff --git a/test/unit/qrcode/QREncoderTest.cpp b/test/unit/qrcode/QREncoderTest.cpp index 8e28abc515..d438a6b3b4 100644 --- a/test/unit/qrcode/QREncoderTest.cpp +++ b/test/unit/qrcode/QREncoderTest.cpp @@ -363,22 +363,22 @@ TEST(QREncoderTest, AppendLengthInfo) { BitArray bits; AppendLengthInfo(1, // 1 letter (1/1). - *Version::FromNumber(1), CodecMode::NUMERIC, bits); + *Version::Model2(1), CodecMode::NUMERIC, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ .X")); // 10 bits. bits = BitArray(); AppendLengthInfo(2, // 2 letters (2/1). - *Version::FromNumber(10), CodecMode::ALPHANUMERIC, bits); + *Version::Model2(10), CodecMode::ALPHANUMERIC, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ .X.")); // 11 bits. bits = BitArray(); AppendLengthInfo(255, // 255 letter (255/1). - *Version::FromNumber(27), CodecMode::BYTE, bits); + *Version::Model2(27), CodecMode::BYTE, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ XXXXXXXX")); // 16 bits. bits = BitArray(); AppendLengthInfo(512, // 512 letters (1024/2). - *Version::FromNumber(40), CodecMode::KANJI, bits); + *Version::Model2(40), CodecMode::KANJI, bits); EXPECT_EQ(ToString(bits), RemoveSpace("..X..... ....")); // 12 bits. } diff --git a/test/unit/qrcode/QRFormatInformationTest.cpp b/test/unit/qrcode/QRFormatInformationTest.cpp index fb933e0ab5..90a948eb00 100644 --- a/test/unit/qrcode/QRFormatInformationTest.cpp +++ b/test/unit/qrcode/QRFormatInformationTest.cpp @@ -44,7 +44,7 @@ TEST(QRFormatInformationTest, DecodeWithBitDifference) EXPECT_EQ(expected, FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO ^ 0x07, MASKED_TEST_FORMAT_INFO2 ^ 0x07)); auto unexpected = FormatInformation::DecodeQR(MASKED_TEST_FORMAT_INFO ^ 0x0F, MASKED_TEST_FORMAT_INFO2 ^ 0x0F); EXPECT_FALSE(expected == unexpected); - EXPECT_FALSE(unexpected.isValid() && !unexpected.isModel1); + EXPECT_FALSE(unexpected.isValid() && unexpected.type() == Type::Model2); } TEST(QRFormatInformationTest, DecodeWithMisread) diff --git a/test/unit/qrcode/QRModeTest.cpp b/test/unit/qrcode/QRModeTest.cpp index 28411f1cf5..84f3f00a39 100644 --- a/test/unit/qrcode/QRModeTest.cpp +++ b/test/unit/qrcode/QRModeTest.cpp @@ -27,12 +27,12 @@ TEST(QRModeTest, ForBits) TEST(QRModeTest, CharacterCount) { // Spot check a few values - ASSERT_EQ(10, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(5))); - ASSERT_EQ(12, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(26))); - ASSERT_EQ(14, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(40))); - ASSERT_EQ(9, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::FromNumber(6))); - ASSERT_EQ(8, CharacterCountBits(CodecMode::BYTE, *Version::FromNumber(7))); - ASSERT_EQ(8, CharacterCountBits(CodecMode::KANJI, *Version::FromNumber(8))); + ASSERT_EQ(10, CharacterCountBits(CodecMode::NUMERIC, *Version::Model2(5))); + ASSERT_EQ(12, CharacterCountBits(CodecMode::NUMERIC, *Version::Model2(26))); + ASSERT_EQ(14, CharacterCountBits(CodecMode::NUMERIC, *Version::Model2(40))); + ASSERT_EQ(9, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::Model2(6))); + ASSERT_EQ(8, CharacterCountBits(CodecMode::BYTE, *Version::Model2(7))); + ASSERT_EQ(8, CharacterCountBits(CodecMode::KANJI, *Version::Model2(8))); } TEST(QRModeTest, MicroForBits) @@ -59,10 +59,10 @@ TEST(QRModeTest, MicroForBits) TEST(QRModeTest, MicroCharacterCount) { // Spot check a few values - ASSERT_EQ(3, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(1, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(2, true))); - ASSERT_EQ(6, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(4, true))); - ASSERT_EQ(3, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::FromNumber(2, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::BYTE, *Version::FromNumber(3, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::KANJI, *Version::FromNumber(4, true))); + ASSERT_EQ(3, CharacterCountBits(CodecMode::NUMERIC, *Version::Micro(1))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::NUMERIC, *Version::Micro(2))); + ASSERT_EQ(6, CharacterCountBits(CodecMode::NUMERIC, *Version::Micro(4))); + ASSERT_EQ(3, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::Micro(2))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::BYTE, *Version::Micro(3))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::KANJI, *Version::Micro(4))); } diff --git a/test/unit/qrcode/QRVersionTest.cpp b/test/unit/qrcode/QRVersionTest.cpp index ff2bea8b4b..ba3c6fbffd 100644 --- a/test/unit/qrcode/QRVersionTest.cpp +++ b/test/unit/qrcode/QRVersionTest.cpp @@ -18,7 +18,7 @@ namespace { void CheckVersion(const Version* version, int number, int dimension) { ASSERT_NE(version, nullptr); EXPECT_EQ(number, version->versionNumber()); - if (number > 1 && !version->isMicroQRCode()) { + if (number > 1 && version->isModel2()) { EXPECT_FALSE(version->alignmentPatternCenters().empty()); } EXPECT_EQ(dimension, version->dimension()); @@ -34,11 +34,11 @@ namespace { TEST(QRVersionTest, VersionForNumber) { - auto version = Version::FromNumber(0); + auto version = Version::Model2(0); EXPECT_EQ(version, nullptr) << "There is version with number 0"; for (int i = 1; i <= 40; i++) { - CheckVersion(Version::FromNumber(i), i, 4*i + 17); + CheckVersion(Version::Model2(i), i, 4*i + 17); } } @@ -46,9 +46,7 @@ TEST(QRVersionTest, VersionForNumber) TEST(QRVersionTest, GetProvisionalVersionForDimension) { for (int i = 1; i <= 40; i++) { - auto prov = Version::FromDimension(4 * i + 17); - ASSERT_NE(prov, nullptr); - EXPECT_EQ(i, prov->versionNumber()); + EXPECT_EQ(i, Version::Number(BitMatrix(4 * i + 17))); } } @@ -65,20 +63,18 @@ TEST(QRVersionTest, DecodeVersionInformation) TEST(QRVersionTest, MicroVersionForNumber) { - auto version = Version::FromNumber(0, true); + auto version = Version::Micro(0); EXPECT_EQ(version, nullptr) << "There is version with number 0"; for (int i = 1; i <= 4; i++) { - CheckVersion(Version::FromNumber(i, true), i, 2 * i + 9); + CheckVersion(Version::Micro(i), i, 2 * i + 9); } } TEST(QRVersionTest, GetProvisionalMicroVersionForDimension) { for (int i = 1; i <= 4; i++) { - auto prov = Version::FromDimension(2 * i + 9); - ASSERT_NE(prov, nullptr); - EXPECT_EQ(i, prov->versionNumber()); + EXPECT_EQ(i, Version::Number(BitMatrix(2 * i + 9))); } } @@ -90,7 +86,7 @@ TEST(QRVersionTest, FunctionPattern) EXPECT_TRUE(bitMatrix.get(col, row)); }; for (int i = 1; i <= 4; i++) { - const auto version = Version::FromNumber(i, true); + const auto version = Version::Micro(i); const auto functionPattern = version->buildFunctionPattern(); testFinderPatternRegion(functionPattern); From f50cd5cc0377186dd25fa9e1c51cc3b2870cdda1 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 26 Oct 2023 23:43:11 +0200 Subject: [PATCH 052/587] ios: specify EC level/margin for generation (#644) * ios: specify EC level/margin for generation * ios: add ZXIEncodeHints to bundle options --- wrappers/ios/Sources/Wrapper/UmbrellaHeader.h | 1 + .../Sources/Wrapper/Writer/ZXIBarcodeWriter.h | 33 +++++++++--- .../Wrapper/Writer/ZXIBarcodeWriter.mm | 35 ++++++------ .../Sources/Wrapper/Writer/ZXIEncodeHints.h | 26 +++++++++ .../Sources/Wrapper/Writer/ZXIEncodeHints.mm | 53 +++++++++++++++++++ .../ios/demo/demo/WriteViewController.swift | 3 +- zxing-cpp.podspec | 2 +- 7 files changed, 129 insertions(+), 24 deletions(-) create mode 100644 wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h create mode 100644 wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm diff --git a/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h b/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h index 951357f673..d76dc767c4 100644 --- a/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h +++ b/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h @@ -10,6 +10,7 @@ #import "Reader/ZXIPosition.h" #import "Reader/ZXIPoint.h" #import "Reader/ZXIDecodeHints.h" +#import "Writer/ZXIEncodeHints.h" #import "Writer/ZXIBarcodeWriter.h" #import "ZXIErrors.h" #import "ZXIFormat.h" diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h index 142f695c18..c173c61c47 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h @@ -3,22 +3,41 @@ // SPDX-License-Identifier: Apache-2.0 #import -#import "ZXIFormat.h" +#import "ZXIEncodeHints.h" NS_ASSUME_NONNULL_BEGIN +const int AZTEC_ERROR_CORRECTION_0 = 0; +const int AZTEC_ERROR_CORRECTION_12 = 1; +const int AZTEC_ERROR_CORRECTION_25 = 2; +const int AZTEC_ERROR_CORRECTION_37 = 3; +const int AZTEC_ERROR_CORRECTION_50 = 4; +const int AZTEC_ERROR_CORRECTION_62 = 5; +const int AZTEC_ERROR_CORRECTION_75 = 6; +const int AZTEC_ERROR_CORRECTION_87 = 7; +const int AZTEC_ERROR_CORRECTION_100 = 8; +const int QR_ERROR_CORRECTION_LOW = 2; +const int QR_ERROR_CORRECTION_MEDIUM = 4; +const int QR_ERROR_CORRECTION_QUARTILE = 6; +const int QR_ERROR_CORRECTION_HIGH = 8; +const int PDF417_ERROR_CORRECTION_0 = 0; +const int PDF417_ERROR_CORRECTION_1 = 1; +const int PDF417_ERROR_CORRECTION_2 = 2; +const int PDF417_ERROR_CORRECTION_3 = 3; +const int PDF417_ERROR_CORRECTION_4 = 4; +const int PDF417_ERROR_CORRECTION_5 = 5; +const int PDF417_ERROR_CORRECTION_6 = 6; +const int PDF417_ERROR_CORRECTION_7 = 7; +const int PDF417_ERROR_CORRECTION_8 = 8; + @interface ZXIBarcodeWriter : NSObject -(nullable CGImageRef)writeString:(NSString *)contents - width:(int)width - height:(int)height - format:(ZXIFormat)format + hints:(ZXIEncodeHints *)hints error:(NSError *__autoreleasing _Nullable *)error; -(nullable CGImageRef)writeData:(NSData *)data - width:(int)width - height:(int)height - format:(ZXIFormat)format + hints:(ZXIEncodeHints *)hints error:(NSError *__autoreleasing _Nullable *)error; @end diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm index 879168aea3..c8594b9a58 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm @@ -4,6 +4,7 @@ #import #import "ZXIBarcodeWriter.h" +#import "ZXIEncodeHints.h" #import "MultiFormatWriter.h" #import "BitMatrix.h" #import "BitMatrixIO.h" @@ -23,7 +24,7 @@ std::wstring s; const unsigned char *bytes = (const unsigned char *) [data bytes]; size_t len = [data length]; - for (int i = 0; i < len; ++i) { + for (int i = 0; i < len; ++i) { s.push_back(bytes[i]); } return s; @@ -32,39 +33,43 @@ @implementation ZXIBarcodeWriter -(CGImageRef)writeData:(NSData *)data - width:(int)width - height:(int)height - format:(ZXIFormat)format + hints:(ZXIEncodeHints *)hints error:(NSError *__autoreleasing _Nullable *)error { return [self encode: NSDataToStringW(data) - width: width - height: height - format: format encoding: CharacterSet::BINARY + format: hints.format + width: hints.width + height: hints.height + margin: hints.margin + ecLevel: hints.ecLevel error: error]; } -(CGImageRef)writeString:(NSString *)contents - width:(int)width - height:(int)height - format:(ZXIFormat)format + hints:(ZXIEncodeHints *)hints error:(NSError *__autoreleasing _Nullable *)error { return [self encode: NSStringToStringW(contents) - width: width - height: height - format: format encoding: CharacterSet::UTF8 + format: hints.format + width: hints.width + height: hints.height + margin: hints.margin + ecLevel: hints.ecLevel error: error]; } -(CGImageRef)encode:(std::wstring)content + encoding:(CharacterSet)encoding + format:(ZXIFormat)format width:(int)width height:(int)height - format:(ZXIFormat)format - encoding:(CharacterSet)encoding + margin:(int)margin + ecLevel:(int)ecLevel error:(NSError *__autoreleasing _Nullable *)error { MultiFormatWriter writer { BarcodeFormatFromZXIFormat(format) }; writer.setEncoding(encoding); + writer.setMargin(margin); + writer.setEccLevel(ecLevel); // Catch exception for invalid formats try { BitMatrix bitMatrix = writer.encode(content, width, height); diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h new file mode 100644 index 0000000000..7646d7a32c --- /dev/null +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h @@ -0,0 +1,26 @@ +// Copyright 2023 KURZ Digital Solutions GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +#import +#import "ZXIFormat.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface ZXIEncodeHints : NSObject +@property(nonatomic) ZXIFormat format; +@property(nonatomic) int width; +@property(nonatomic) int height; +@property(nonatomic) int ecLevel; +@property(nonatomic) int margin; + +- (instancetype)initWithFormat:(ZXIFormat)format; + +- (instancetype)initWithFormat:(ZXIFormat)format + width:(int)width + height:(int)height + ecLevel:(int)ecLevel + margin:(int)margin; +@end + +NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm new file mode 100644 index 0000000000..f463f02966 --- /dev/null +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm @@ -0,0 +1,53 @@ +// Copyright 2023 KURZ Digital Solutions GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +#import "ZXIEncodeHints.h" + +@implementation ZXIEncodeHints + +- (instancetype)initWithFormat:(ZXIFormat)format { + self = [super init]; + self.format = format; + self.width = 0; + self.height = 0; + self.ecLevel = -1; + self.margin = -1; + return self; +} + +- (instancetype)initWithFormat:(ZXIFormat)format + width:(int)width + height:(int)height + ecLevel:(int)ecLevel + margin:(int)margin { + self = [super init]; + self.format = format; + self.width = width; + self.height = height; + self.ecLevel = ecLevel; + self.margin = margin; + return self; +} + +-(void)setFormat:(ZXIFormat)format { + self.format = format; +} + +-(void)setWidth:(int)width { + self.width = width; +} + +-(void)setHeight:(int)height { + self.height = height; +} + +-(void)setEcLevel:(int)ecLevel { + self.ecLevel = ecLevel; +} + +-(void)setMargin:(int)margin { + self.margin = margin; +} + +@end diff --git a/wrappers/ios/demo/demo/WriteViewController.swift b/wrappers/ios/demo/demo/WriteViewController.swift index e77ebffee9..f6576c35b1 100644 --- a/wrappers/ios/demo/demo/WriteViewController.swift +++ b/wrappers/ios/demo/demo/WriteViewController.swift @@ -14,8 +14,9 @@ class WriteViewController: UIViewController { // MARK: - Actions @IBAction func textFieldChanged(_ sender: UITextField) { + let hints = ZXIEncodeHints(format: .QR_CODE, width: 200, height: 200, ecLevel: QR_ERROR_CORRECTION_LOW, margin: -1) guard let text = sender.text, - let image = try? ZXIBarcodeWriter().write(text, width: 200, height: 200, format: .QR_CODE) + let image = try? ZXIBarcodeWriter().write(text, hints: hints) else { return } diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec index df4c7b773d..23da89b8b8 100644 --- a/zxing-cpp.podspec +++ b/zxing-cpp.podspec @@ -32,7 +32,7 @@ Pod::Spec.new do |s| ss.frameworks = 'CoreGraphics', 'CoreImage', 'CoreVideo' ss.source_files = 'wrappers/ios/Sources/Wrapper/**/*.{h,m,mm}' ss.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIDecodeHints}.h', - 'wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h', + 'wrappers/ios/Sources/Wrapper/Writer/{ZXIBarcodeWriter,ZXIEncodeHints}.h', 'wrappers/ios/Sources/Wrapper/{ZXIErrors,ZXIFormat}.h' ss.exclude_files = 'wrappers/ios/Sources/Wrapper/UmbrellaHeader.h' end From 266be7c7dc313e54540233c949b2176e3e03b365 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 27 Oct 2023 09:49:40 +0200 Subject: [PATCH 053/587] ios: correct spelling of setters in ZXIDecodeHints --- .../Sources/Wrapper/Reader/ZXIDecodeHints.mm | 34 +++++++++---------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm index 92383d36e4..54772f5c0c 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm @@ -33,8 +33,8 @@ - (instancetype)initWithTryHarder:(BOOL)tryHarder self.tryHarder = tryHarder; self.tryRotate = tryRotate; self.tryDownscale = tryDownscale; - self.maxNumberOfSymbols = maxNumberOfSymbols; self.tryInvert = tryInvert; + self.maxNumberOfSymbols = maxNumberOfSymbols; self.tryCode39ExtendedMode = tryCode39ExtendedMode; self.validateCode39CheckSum = validateCode39CheckSum; self.validateITFCheckSum = validateITFCheckSum; @@ -44,50 +44,46 @@ - (instancetype)initWithTryHarder:(BOOL)tryHarder return self; } --(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { - self.zxingHints.setMaxNumberOfSymbols(maxNumberOfSymbols); -} - -(void)setTryHarder:(BOOL)tryHarder { self.zxingHints.setTryHarder(tryHarder); } --(void)setTryrotate:(BOOL)tryRotate { +-(void)setTryRotate:(BOOL)tryRotate { self.zxingHints.setTryRotate(tryRotate); } --(void)setTrydownscale:(BOOL)tryDownscale { +-(void)setTryDownscale:(BOOL)tryDownscale { self.zxingHints.setTryDownscale(tryDownscale); } --(void)setTryinvert:(BOOL)tryInvert { +-(void)setTryInvert:(BOOL)tryInvert { self.zxingHints.setTryInvert(tryInvert); } --(void)setTrycode39Extendedmode:(BOOL)tryCode39ExtendedMode { +-(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { + self.zxingHints.setMaxNumberOfSymbols(maxNumberOfSymbols); +} + +-(void)setTryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode { self.zxingHints.setTryCode39ExtendedMode(tryCode39ExtendedMode); } --(void)setValidatecode39Checksum:(BOOL)validateCode39CheckSum { +-(void)setValidateCode39CheckSum:(BOOL)validateCode39CheckSum { self.zxingHints.setValidateCode39CheckSum(validateCode39CheckSum); } --(void)setValidateitfchecksum:(BOOL)validateITFCheckSum { +-(void)setValidateITFCheckSum:(BOOL)validateITFCheckSum { self.zxingHints.setValidateITFCheckSum(validateITFCheckSum); } --(void)setDownscalefactor:(uint8_t)downscaleFactor { +-(void)setDownscaleFactor:(uint8_t)downscaleFactor { self.zxingHints.setDownscaleFactor(downscaleFactor); } --(void)setDownscalethreshold:(uint16_t)downscaleThreshold { +-(void)setDownscaleThreshold:(uint16_t)downscaleThreshold { self.zxingHints.setDownscaleThreshold(downscaleThreshold); } -- (NSInteger)maxNumberOfSymbols { - return self.zxingHints.maxNumberOfSymbols(); -} - -(BOOL)tryHarder { return self.zxingHints.tryHarder(); } @@ -104,6 +100,10 @@ -(BOOL)tryInvert { return self.zxingHints.tryInvert(); } +- (NSInteger)maxNumberOfSymbols { + return self.zxingHints.maxNumberOfSymbols(); +} + -(BOOL)tryCode39ExtendedMode { return self.zxingHints.tryCode39ExtendedMode(); } From d3d9ca9a56a75ba7a4c5aec3ab21e26fb6bdbddf Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 27 Oct 2023 09:53:10 +0200 Subject: [PATCH 054/587] ios: remove unnecessary setter functions These setters are already defined by `@property`. --- .../Sources/Wrapper/Writer/ZXIEncodeHints.mm | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm index f463f02966..0cbc4dcbea 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm @@ -30,24 +30,4 @@ - (instancetype)initWithFormat:(ZXIFormat)format return self; } --(void)setFormat:(ZXIFormat)format { - self.format = format; -} - --(void)setWidth:(int)width { - self.width = width; -} - --(void)setHeight:(int)height { - self.height = height; -} - --(void)setEcLevel:(int)ecLevel { - self.ecLevel = ecLevel; -} - --(void)setMargin:(int)margin { - self.margin = margin; -} - @end From f0211a481d7f0a32593d75ca8895d8ec9f91e62d Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 27 Oct 2023 15:54:50 +0200 Subject: [PATCH 055/587] DMDetector: improve performance This fixes a performance regression introduced in 9d40fbdac9bdc37ce58ab803f20699636fd8c60b --- core/src/RegressionLine.h | 3 ++- core/src/datamatrix/DMDetector.cpp | 14 +++++++++----- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index eb7ecd1d4f..401eb38d81 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -79,6 +79,7 @@ class RegressionLine auto signedDistance(PointF p) const { return dot(normal(), p) - c; } template auto distance(PointT p) const { return std::abs(signedDistance(PointF(p))); } PointF project(PointF p) const { return p - signedDistance(p) * normal(); } + PointF centroid() const { return std::accumulate(_points.begin(), _points.end(), PointF()) / _points.size(); } void reset() { @@ -119,7 +120,7 @@ class RegressionLine if (old_points_size == points.size()) break; #ifdef PRINT_DEBUG - printf("removed %zu points\n", old_points_size - points.size()); + printf("removed %zu points -> %zu remaining\n", old_points_size - points.size(), points.size()); #endif ret = evaluate(points); } diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 0dcde39a9b..ad9d846f5d 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -557,19 +557,23 @@ class EdgeTracer : public BitMatrixCursorF return line.evaluate(1.5) && updateDirectionFromOrigin(p - line.project(p) + line.points().front()); } + bool updateDirectionFromLineCentroid(RegressionLine& line) + { + // Basically a faster, less accurate version of the above without the line evaluation + return updateDirectionFromOrigin(line.centroid()); + } + bool traceLine(PointF dEdge, RegressionLine& line) { line.setDirectionInward(dEdge); do { log(p); line.add(p); - if (line.points().size() % 50 == 10 && !updateDirectionFromLine(line)) + if (line.points().size() % 50 == 10 && !updateDirectionFromLineCentroid(line)) return false; auto stepResult = traceStep(dEdge, 1, line.isValid()); - if (stepResult != StepResult::FOUND) { - updateDirectionFromLine(line); - return stepResult == StepResult::OPEN_END && line.points().size() > 1; - } + if (stepResult != StepResult::FOUND) + return stepResult == StepResult::OPEN_END && line.points().size() > 1 && updateDirectionFromLineCentroid(line); } while (true); } From 77307448c13eacfd8b2770afb151e63cdb73d44d Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 27 Oct 2023 22:38:31 +0200 Subject: [PATCH 056/587] DataMatrix: fix assert in RegressionLine::evaluate (fix #593) --- core/src/RegressionLine.h | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index 401eb38d81..dac602ee9f 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -117,6 +117,9 @@ class RegressionLine return sd > maxSignedDist || sd < -2 * maxSignedDist; }); points.erase(end, points.end()); + // if we threw away too many points, something is off with the line to begin with + if (points.size() < old_points_size / 2 || points.size() < 2) + return false; if (old_points_size == points.size()) break; #ifdef PRINT_DEBUG From 1352b3eab03a1946fee12d18b2ac4d64daa65597 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 30 Oct 2023 09:33:32 +0100 Subject: [PATCH 057/587] HRI: update AIs to latest gs1-syntax-dictionary.txt Snatched from https://github.com/gitlost/zxing-cpp/commit/8d5c96b9b7cf56688723cf7aaafc58553d345eac. Thanks to @gitlost! --------- Co-authored-by: gitlost --- core/src/HRI.cpp | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/core/src/HRI.cpp b/core/src/HRI.cpp index dc16e291b4..9a72dc6b31 100644 --- a/core/src/HRI.cpp +++ b/core/src/HRI.cpp @@ -30,9 +30,9 @@ struct AiInfo } }; -// GS1 General Specifications Release 22.0 (Jan 22, 2022) +// https://github.com/gs1/gs1-syntax-dictionary 2023-09-22 static const AiInfo aiInfos[] = { -// TWO_DIGIT_DATA_LENGTH +//TWO_DIGIT_DATA_LENGTH { "00", 18 }, { "01", 14 }, { "02", 14 }, @@ -52,7 +52,6 @@ static const AiInfo aiInfos[] = { { "30", -8 }, { "37", -8 }, - //internal company codes { "90", -30 }, { "91", -90 }, { "92", -90 }, @@ -195,6 +194,10 @@ static const AiInfo aiInfos[] = { { "4324", 10 }, { "4325", 10 }, { "4326", 6 }, + { "4330", -7 }, + { "4331", -7 }, + { "4332", -7 }, + { "4333", -7 }, { "7001", 13 }, { "7002", -30 }, @@ -206,12 +209,15 @@ static const AiInfo aiInfos[] = { { "7008", -3 }, { "7009", -10 }, { "7010", -2 }, + { "7011", -10 }, { "7020", -20 }, { "7021", -20 }, { "7022", -20 }, { "7023", -30 }, { "7040", 4 }, { "7240", -20 }, + { "7241", 2 }, + { "7242", -25 }, { "8001", 14 }, { "8002", -20 }, @@ -231,6 +237,7 @@ static const AiInfo aiInfos[] = { { "8019", -10 }, { "8020", -25 }, { "8026", 18 }, + { "8030", -90 }, { "8110", -70 }, { "8111", 4 }, { "8112", -70 }, From 7834f83ddc59102efd9d2081eb4d4a685349ffbe Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 30 Oct 2023 09:36:35 +0100 Subject: [PATCH 058/587] DMDetector: use forced inline to reduce cost in c++20 build Force traceSteps() to inline to allow the compiler optimize for the maxStepSize==1 case in traceLine() this can result in a 10% speedup of the falsepositive use case when build with c++20. --- core/src/datamatrix/DMDetector.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index ad9d846f5d..e76f02c579 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -493,6 +493,13 @@ class EdgeTracer : public BitMatrixCursorF { enum class StepResult { FOUND, OPEN_END, CLOSED_END }; + // force this function inline to allow the compiler optimize for the maxStepSize==1 case in traceLine() + // this can result in a 10% speedup of the falsepositive use case when build with c++20 +#if defined(__clang__) || defined(__GNUC__) + inline __attribute__((always_inline)) +#elif defined(_MSC_VER) + __forceinline +#endif StepResult traceStep(PointF dEdge, int maxStepSize, bool goodDirection) { dEdge = mainDirection(dEdge); From 5dbb69d063158203332fb71964d69e484a1ca528 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 30 Oct 2023 09:51:37 +0100 Subject: [PATCH 059/587] WASM: link to external wrapper zxing-wasm Thanks to @Sec-ant for the contribution. --- wrappers/wasm/README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/wrappers/wasm/README.md b/wrappers/wasm/README.md index b5dd82d3a8..c0e7c3794f 100644 --- a/wrappers/wasm/README.md +++ b/wrappers/wasm/README.md @@ -1,4 +1,4 @@ -# WebAssembly/WASM Wrapper/Library +# WebAssembly/WASM Wrapper ## Build @@ -10,6 +10,10 @@ You can also download the latest build output from the continuous integration system from the [Actions](https://github.com/zxing-cpp/zxing-cpp/actions) tab. Look for 'wasm-artifacts'. Also check out the [live demos](https://github.com/zxing-cpp/zxing-cpp#web-demos). +## Alternative Wrapper Project + +There is an alternative (external) wrapper project called [zxing-wasm](https://github.com/Sec-ant/zxing-wasm). It is written in TypeScript, has a more feature complete interface closer to the C++ API, spares you from dealing with WASM intricacies and is provided as a fully fledged ES module on [npmjs](https://www.npmjs.com/package/zxing-wasm). + ## Performance It turns out that compiling the library with the `-Os` (`MinSizeRel`) flag causes a noticible performance penalty. Here are some measurements from the demo_cam_reader (performed on Chromium 109 running on a Core i9-9980HK): From cb21a92fb37cc2396ff1ba8506d18277f603f876 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Mon, 30 Oct 2023 12:02:17 +0100 Subject: [PATCH 060/587] ios: fix declaring error correction constants Separate declaration and initialization into header and implemenation to avoid duplicated symbols when the header file is included multiple times. --- .../Sources/Wrapper/Writer/ZXIBarcodeWriter.h | 23 ------------------- .../Sources/Wrapper/Writer/ZXIEncodeHints.h | 23 +++++++++++++++++++ .../Sources/Wrapper/Writer/ZXIEncodeHints.mm | 23 +++++++++++++++++++ 3 files changed, 46 insertions(+), 23 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h index c173c61c47..8d737e5264 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h @@ -7,29 +7,6 @@ NS_ASSUME_NONNULL_BEGIN -const int AZTEC_ERROR_CORRECTION_0 = 0; -const int AZTEC_ERROR_CORRECTION_12 = 1; -const int AZTEC_ERROR_CORRECTION_25 = 2; -const int AZTEC_ERROR_CORRECTION_37 = 3; -const int AZTEC_ERROR_CORRECTION_50 = 4; -const int AZTEC_ERROR_CORRECTION_62 = 5; -const int AZTEC_ERROR_CORRECTION_75 = 6; -const int AZTEC_ERROR_CORRECTION_87 = 7; -const int AZTEC_ERROR_CORRECTION_100 = 8; -const int QR_ERROR_CORRECTION_LOW = 2; -const int QR_ERROR_CORRECTION_MEDIUM = 4; -const int QR_ERROR_CORRECTION_QUARTILE = 6; -const int QR_ERROR_CORRECTION_HIGH = 8; -const int PDF417_ERROR_CORRECTION_0 = 0; -const int PDF417_ERROR_CORRECTION_1 = 1; -const int PDF417_ERROR_CORRECTION_2 = 2; -const int PDF417_ERROR_CORRECTION_3 = 3; -const int PDF417_ERROR_CORRECTION_4 = 4; -const int PDF417_ERROR_CORRECTION_5 = 5; -const int PDF417_ERROR_CORRECTION_6 = 6; -const int PDF417_ERROR_CORRECTION_7 = 7; -const int PDF417_ERROR_CORRECTION_8 = 8; - @interface ZXIBarcodeWriter : NSObject -(nullable CGImageRef)writeString:(NSString *)contents diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h index 7646d7a32c..eed784dc78 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h @@ -7,6 +7,29 @@ NS_ASSUME_NONNULL_BEGIN +extern const int AZTEC_ERROR_CORRECTION_0; +extern const int AZTEC_ERROR_CORRECTION_12; +extern const int AZTEC_ERROR_CORRECTION_25; +extern const int AZTEC_ERROR_CORRECTION_37; +extern const int AZTEC_ERROR_CORRECTION_50; +extern const int AZTEC_ERROR_CORRECTION_62; +extern const int AZTEC_ERROR_CORRECTION_75; +extern const int AZTEC_ERROR_CORRECTION_87; +extern const int AZTEC_ERROR_CORRECTION_100; +extern const int QR_ERROR_CORRECTION_LOW; +extern const int QR_ERROR_CORRECTION_MEDIUM; +extern const int QR_ERROR_CORRECTION_QUARTILE; +extern const int QR_ERROR_CORRECTION_HIGH; +extern const int PDF417_ERROR_CORRECTION_0; +extern const int PDF417_ERROR_CORRECTION_1; +extern const int PDF417_ERROR_CORRECTION_2; +extern const int PDF417_ERROR_CORRECTION_3; +extern const int PDF417_ERROR_CORRECTION_4; +extern const int PDF417_ERROR_CORRECTION_5; +extern const int PDF417_ERROR_CORRECTION_6; +extern const int PDF417_ERROR_CORRECTION_7; +extern const int PDF417_ERROR_CORRECTION_8; + @interface ZXIEncodeHints : NSObject @property(nonatomic) ZXIFormat format; @property(nonatomic) int width; diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm index 0cbc4dcbea..d53f2986d5 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm @@ -4,6 +4,29 @@ #import "ZXIEncodeHints.h" +const int AZTEC_ERROR_CORRECTION_0 = 0; +const int AZTEC_ERROR_CORRECTION_12 = 1; +const int AZTEC_ERROR_CORRECTION_25 = 2; +const int AZTEC_ERROR_CORRECTION_37 = 3; +const int AZTEC_ERROR_CORRECTION_50 = 4; +const int AZTEC_ERROR_CORRECTION_62 = 5; +const int AZTEC_ERROR_CORRECTION_75 = 6; +const int AZTEC_ERROR_CORRECTION_87 = 7; +const int AZTEC_ERROR_CORRECTION_100 = 8; +const int QR_ERROR_CORRECTION_LOW = 2; +const int QR_ERROR_CORRECTION_MEDIUM = 4; +const int QR_ERROR_CORRECTION_QUARTILE = 6; +const int QR_ERROR_CORRECTION_HIGH = 8; +const int PDF417_ERROR_CORRECTION_0 = 0; +const int PDF417_ERROR_CORRECTION_1 = 1; +const int PDF417_ERROR_CORRECTION_2 = 2; +const int PDF417_ERROR_CORRECTION_3 = 3; +const int PDF417_ERROR_CORRECTION_4 = 4; +const int PDF417_ERROR_CORRECTION_5 = 5; +const int PDF417_ERROR_CORRECTION_6 = 6; +const int PDF417_ERROR_CORRECTION_7 = 7; +const int PDF417_ERROR_CORRECTION_8 = 8; + @implementation ZXIEncodeHints - (instancetype)initWithFormat:(ZXIFormat)format { From 2f4ad8fa3b49dddd0886217eb2d3fd59e7a36446 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Tue, 31 Oct 2023 11:47:51 +0100 Subject: [PATCH 061/587] ios: expose all native result items in wrapper (#649) So ecLevel and all the other fields can be accessed from iOS apps too. --- .../Wrapper/Reader/ZXIBarcodeReader.mm | 35 +++++++++++++++---- wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.h | 22 ++++++++++++ .../ios/Sources/Wrapper/Reader/ZXIGTIN.mm | 20 +++++++++++ .../ios/Sources/Wrapper/Reader/ZXIResult.h | 21 ++++++++++- .../ios/Sources/Wrapper/Reader/ZXIResult.mm | 20 ++++++++++- wrappers/ios/Sources/Wrapper/UmbrellaHeader.h | 1 + zxing-cpp.podspec | 2 +- 7 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.h create mode 100644 wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.mm diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm index 1e32566378..c13b150e8c 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm @@ -6,11 +6,27 @@ #import "ReadBarcode.h" #import "ImageView.h" #import "Result.h" +#import "GTIN.h" #import "ZXIFormatHelper.h" #import "ZXIPosition+Helper.h" using namespace ZXing; +NSString *stringToNSString(const std::string &text) { + return [[NSString alloc]initWithBytes:text.data() length:text.size() encoding:NSUTF8StringEncoding]; +} + +ZXIGTIN *getGTIN(const Result &result) { + auto country = GTIN::LookupCountryIdentifier(result.text(TextMode::Plain), result.format()); + auto addOn = GTIN::EanAddOn(result); + return country.empty() + ? nullptr + : [[ZXIGTIN alloc]initWithCountry:stringToNSString(country) + addOn:stringToNSString(addOn) + price:stringToNSString(GTIN::Price(addOn)) + issueNumber:stringToNSString(GTIN::IssueNr(addOn))]; +} + @interface ZXIBarcodeReader() @property (nonatomic, strong) CIContext* ciContext; @end @@ -115,16 +131,21 @@ + (DecodeHints)DecodeHintsFromZXIOptions:(ZXIDecodeHints*)hints { NSMutableArray* zxiResults = [NSMutableArray array]; for (auto result: results) { - auto resultText = result.text(); - NSString *text = [[NSString alloc]initWithBytes:resultText.data() length:resultText.size() encoding:NSUTF8StringEncoding]; - - NSData *bytes = [[NSData alloc] initWithBytes:result.bytes().data() length:result.bytes().size()]; [zxiResults addObject: - [[ZXIResult alloc] init:text + [[ZXIResult alloc] init:stringToNSString(result.text()) format:ZXIFormatFromBarcodeFormat(result.format()) - bytes:bytes + bytes:[[NSData alloc] initWithBytes:result.bytes().data() length:result.bytes().size()] position:[[ZXIPosition alloc]initWithPosition: result.position()] - ]]; + orientation:result.orientation() + ecLevel:stringToNSString(result.ecLevel()) + symbologyIdentifier:stringToNSString(result.symbologyIdentifier()) + sequenceSize:result.sequenceSize() + sequenceIndex:result.sequenceIndex() + sequenceId:stringToNSString(result.sequenceId()) + readerInit:result.readerInit() + lineCount:result.lineCount() + gtin:getGTIN(result)] + ]; } return zxiResults; } diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.h new file mode 100644 index 0000000000..0992d845ff --- /dev/null +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.h @@ -0,0 +1,22 @@ +// Copyright 2022 KURZ Digital Solutions GmbH +// +// SPDX-License-Identifier: Apache-2.0 + + +#import + +NS_ASSUME_NONNULL_BEGIN + +@interface ZXIGTIN : NSObject +@property(nonatomic, nonnull)NSString *country; +@property(nonatomic, nonnull)NSString *addOn; +@property(nonatomic, nonnull)NSString *price; +@property(nonatomic, nonnull)NSString *issueNumber; + +- (instancetype)initWithCountry:(NSString *)country + addOn:(NSString *)addOn + price:(NSString *)price + issueNumber:(NSString *)issueNumber; +@end + +NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.mm new file mode 100644 index 0000000000..c0d801d0b6 --- /dev/null +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIGTIN.mm @@ -0,0 +1,20 @@ +// Copyright 2022 KURZ Digital Solutions GmbH +// +// SPDX-License-Identifier: Apache-2.0 + + +#import "ZXIGTIN.h" + +@implementation ZXIGTIN +- (instancetype)initWithCountry:(NSString *)country + addOn:(NSString *)addOn + price:(NSString *)price + issueNumber:(NSString *)issueNumber { + self = [super init]; + self.country = country; + self.addOn = addOn; + self.price = price; + self.issueNumber = issueNumber; + return self; +} +@end diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIResult.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIResult.h index 83d8a4cffc..9c4c4e8a36 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIResult.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIResult.h @@ -5,6 +5,7 @@ #import #import "ZXIFormat.h" #import "ZXIPosition.h" +#import "ZXIGTIN.h" NS_ASSUME_NONNULL_BEGIN @@ -13,11 +14,29 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic, strong) NSData *bytes; @property(nonatomic, strong) ZXIPosition *position; @property(nonatomic) ZXIFormat format; +@property(nonatomic) NSInteger orientation; +@property(nonatomic, strong) NSString *ecLevel; +@property(nonatomic, strong) NSString *symbologyIdentifier; +@property(nonatomic) NSInteger sequenceSize; +@property(nonatomic) NSInteger sequenceIndex; +@property(nonatomic, strong) NSString *sequenceId; +@property(nonatomic) BOOL readerInit; +@property(nonatomic) NSInteger lineCount; +@property(nonatomic, strong) ZXIGTIN *gtin; - (instancetype)init:(NSString *)text format:(ZXIFormat)format bytes:(NSData *)bytes - position:(ZXIPosition *)position; + position:(ZXIPosition *)position + orientation:(NSInteger)orientation + ecLevel:(NSString *)ecLevel + symbologyIdentifier:(NSString *)symbologyIdentifier + sequenceSize:(NSInteger)sequenceSize + sequenceIndex:(NSInteger)sequenceIndex + sequenceId:(NSString *)sequenceId + readerInit:(BOOL)readerInit + lineCount:(NSInteger)lineCount + gtin:(ZXIGTIN *)gtin; @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIResult.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIResult.mm index b84267d2a0..6a80a77d28 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIResult.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIResult.mm @@ -8,12 +8,30 @@ @implementation ZXIResult - (instancetype)init:(NSString *)text format:(ZXIFormat)format bytes:(NSData *)bytes - position:(ZXIPosition *)position { + position:(ZXIPosition *)position + orientation:(NSInteger)orientation + ecLevel:(NSString *)ecLevel + symbologyIdentifier:(NSString *)symbologyIdentifier + sequenceSize:(NSInteger)sequenceSize + sequenceIndex:(NSInteger)sequenceIndex + sequenceId:(NSString *)sequenceId + readerInit:(BOOL)readerInit + lineCount:(NSInteger)lineCount + gtin:(ZXIGTIN *)gtin { self = [super init]; self.text = text; self.format = format; self.bytes = bytes; self.position = position; + self.orientation = orientation; + self.ecLevel = ecLevel; + self.symbologyIdentifier = symbologyIdentifier; + self.sequenceSize = sequenceSize; + self.sequenceIndex = sequenceIndex; + self.sequenceId = sequenceId; + self.readerInit = readerInit; + self.lineCount = lineCount; + self.gtin = gtin; return self; } @end diff --git a/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h b/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h index d76dc767c4..b14dc9b5df 100644 --- a/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h +++ b/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h @@ -9,6 +9,7 @@ #import "Reader/ZXIResult.h" #import "Reader/ZXIPosition.h" #import "Reader/ZXIPoint.h" +#import "Reader/ZXIGTIN.h" #import "Reader/ZXIDecodeHints.h" #import "Writer/ZXIEncodeHints.h" #import "Writer/ZXIBarcodeWriter.h" diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec index 23da89b8b8..02997d19c0 100644 --- a/zxing-cpp.podspec +++ b/zxing-cpp.podspec @@ -31,7 +31,7 @@ Pod::Spec.new do |s| ss.dependency 'zxing-cpp/Core' ss.frameworks = 'CoreGraphics', 'CoreImage', 'CoreVideo' ss.source_files = 'wrappers/ios/Sources/Wrapper/**/*.{h,m,mm}' - ss.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIDecodeHints}.h', + ss.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIGTIN,ZXIDecodeHints}.h', 'wrappers/ios/Sources/Wrapper/Writer/{ZXIBarcodeWriter,ZXIEncodeHints}.h', 'wrappers/ios/Sources/Wrapper/{ZXIErrors,ZXIFormat}.h' ss.exclude_files = 'wrappers/ios/Sources/Wrapper/UmbrellaHeader.h' From cdb921ea3220f654fd920012eff183678e42c71c Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Tue, 31 Oct 2023 15:34:01 +0100 Subject: [PATCH 062/587] ios: clean up ZXIDecodeHints And match argument order with native DecodeHints. --- .../Sources/Wrapper/Reader/ZXIDecodeHints.h | 7 ++-- .../Sources/Wrapper/Reader/ZXIDecodeHints.mm | 32 +++++++++---------- 2 files changed, 19 insertions(+), 20 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.h index 5edd6625e5..8000c73f58 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.h @@ -9,28 +9,27 @@ NS_ASSUME_NONNULL_BEGIN @interface ZXIDecodeHints : NSObject @property(nonatomic) BOOL tryHarder; @property(nonatomic) BOOL tryRotate; -@property(nonatomic) BOOL tryDownscale; @property(nonatomic) BOOL tryInvert; +@property(nonatomic) BOOL tryDownscale; @property(nonatomic) BOOL tryCode39ExtendedMode; @property(nonatomic) BOOL validateCode39CheckSum; @property(nonatomic) BOOL validateITFCheckSum; @property(nonatomic) uint8_t downscaleFactor; @property(nonatomic) uint16_t downscaleThreshold; - @property(nonatomic) NSInteger maxNumberOfSymbols; /// An array of ZXIFormat @property(nonatomic, strong) NSArray *formats; - (instancetype)initWithTryHarder:(BOOL)tryHarder tryRotate:(BOOL)tryRotate - tryDownscale:(BOOL)tryDownscale - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols tryInvert:(BOOL)tryInvert + tryDownscale:(BOOL)tryDownscale tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode validateCode39CheckSum:(BOOL)validateCode39CheckSum validateITFCheckSum:(BOOL)validateITFCheckSum downscaleFactor:(uint8_t)downscaleFactor downscaleThreshold:(uint16_t)downscaleThreshold + maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols formats:(NSArray*)formats; @end diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm index 54772f5c0c..fd7c55a8ed 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm @@ -19,27 +19,27 @@ -(instancetype)init { - (instancetype)initWithTryHarder:(BOOL)tryHarder tryRotate:(BOOL)tryRotate - tryDownscale:(BOOL)tryDownscale - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols tryInvert:(BOOL)tryInvert + tryDownscale:(BOOL)tryDownscale tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode validateCode39CheckSum:(BOOL)validateCode39CheckSum validateITFCheckSum:(BOOL)validateITFCheckSum downscaleFactor:(uint8_t)downscaleFactor downscaleThreshold:(uint16_t)downscaleThreshold + maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols formats:(NSArray*)formats { self = [super init]; self.zxingHints = ZXing::DecodeHints(); self.tryHarder = tryHarder; self.tryRotate = tryRotate; - self.tryDownscale = tryDownscale; self.tryInvert = tryInvert; - self.maxNumberOfSymbols = maxNumberOfSymbols; + self.tryDownscale = tryDownscale; self.tryCode39ExtendedMode = tryCode39ExtendedMode; self.validateCode39CheckSum = validateCode39CheckSum; self.validateITFCheckSum = validateITFCheckSum; self.downscaleFactor = downscaleFactor; self.downscaleThreshold = downscaleThreshold; + self.maxNumberOfSymbols = maxNumberOfSymbols; self.formats = formats; return self; } @@ -52,16 +52,12 @@ -(void)setTryRotate:(BOOL)tryRotate { self.zxingHints.setTryRotate(tryRotate); } --(void)setTryDownscale:(BOOL)tryDownscale { - self.zxingHints.setTryDownscale(tryDownscale); -} - -(void)setTryInvert:(BOOL)tryInvert { self.zxingHints.setTryInvert(tryInvert); } --(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { - self.zxingHints.setMaxNumberOfSymbols(maxNumberOfSymbols); +-(void)setTryDownscale:(BOOL)tryDownscale { + self.zxingHints.setTryDownscale(tryDownscale); } -(void)setTryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode { @@ -84,6 +80,10 @@ -(void)setDownscaleThreshold:(uint16_t)downscaleThreshold { self.zxingHints.setDownscaleThreshold(downscaleThreshold); } +-(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { + self.zxingHints.setMaxNumberOfSymbols(maxNumberOfSymbols); +} + -(BOOL)tryHarder { return self.zxingHints.tryHarder(); } @@ -92,16 +92,12 @@ -(BOOL)tryRotate { return self.zxingHints.tryRotate(); } --(BOOL)tryDownscale { - return self.zxingHints.tryDownscale(); -} - -(BOOL)tryInvert { return self.zxingHints.tryInvert(); } -- (NSInteger)maxNumberOfSymbols { - return self.zxingHints.maxNumberOfSymbols(); +-(BOOL)tryDownscale { + return self.zxingHints.tryDownscale(); } -(BOOL)tryCode39ExtendedMode { @@ -124,4 +120,8 @@ -(uint16_t)downscaleThreshold { return self.zxingHints.downscaleThreshold(); } +- (NSInteger)maxNumberOfSymbols { + return self.zxingHints.maxNumberOfSymbols(); +} + @end From 8e05581350ad048590097f483c7a849a1ba9dc57 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 31 Oct 2023 21:55:23 +0100 Subject: [PATCH 063/587] android: don't close() the ImageProxy inside read() anymore This requires a change of the client code. This supersedes #647. See there for a discussion. --- .../app/src/main/java/com/example/zxingcppdemo/MainActivity.kt | 2 +- .../zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index dd0184ad63..111f065069 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -223,7 +223,7 @@ class MainActivity : AppCompatActivity() { ) resultText = try { - val result = readerCpp.read(image) + val result = image.use{ readerCpp.read(it) } runtime2 += result?.time?.toInt() ?: 0 resultPoints = result?.position?.let { listOf( diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt index 360cafcc61..0846a6b4b6 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt @@ -84,7 +84,7 @@ public class BarcodeReader { } var result = Result() - val status = image.use { + val status = image.let { readYBuffer( it.planes[0].buffer, it.planes[0].rowStride, From e0e9eeffbd6fbe0ccab95c4f98544aa8acd1961d Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Tue, 31 Oct 2023 23:47:36 +0100 Subject: [PATCH 064/587] Ignore swp files (#460) Ignore Vim's swap files and JetBrains .idea directory --------- Co-authored-by: axxel --- .gitignore | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 57cb900099..53a44bff88 100644 --- a/.gitignore +++ b/.gitignore @@ -1,9 +1,19 @@ -CMakeLists.txt.user *.o *.so *.lib *.d *.a compile_commands.json + +# QtCreator +CMakeLists.txt.user + +# Vim +*.swp + +# XCode/Swift .swiftpm .build + +# JetBrain (AndroidStudio, clion) +.idea From 2d0e2713b73d6e67129cc5ccfcdf33f558ce9f90 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Wed, 8 Nov 2023 15:27:36 +0100 Subject: [PATCH 065/587] android: remove WRITE_EXTERNAL_STORAGE for R+ Requesting WRITE_EXTERNAL_STORAGE on Android R (SDK 30) *always* fails and so the sample app is always immediately closed. This permission has no effect on R+. See: https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE To save anything in external storage, this sample would have to be migrated to Scoped Storage. --- .../java/com/example/zxingcppdemo/MainActivity.kt | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 111f065069..57c1f67303 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -24,6 +24,7 @@ import android.hardware.camera2.CaptureRequest import android.media.AudioManager import android.media.MediaActionSound import android.media.ToneGenerator +import android.os.Build import android.os.Bundle import android.os.Environment import android.view.View @@ -51,13 +52,21 @@ class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityCameraBinding private val executor = Executors.newSingleThreadExecutor() - private val permissions = listOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) + private val permissions = mutableListOf(Manifest.permission.CAMERA) private val permissionsRequestCode = Random.nextInt(0, 10000) private val beeper = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 50) private var lastText = String() private var doSaveImage: Boolean = false + init { + // On R or higher, this permission has no effect. See: + // https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityCameraBinding.inflate(layoutInflater) From b15819d65b55698b5b69b9297c39316244fdd869 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Wed, 8 Nov 2023 15:31:56 +0100 Subject: [PATCH 066/587] android: use a literal for the perm request code The request code is an "*application specific* request code to match with a result reported to onRequestPermissionsResult". There's no point in using random numbers here. --- .../app/src/main/java/com/example/zxingcppdemo/MainActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 57c1f67303..d36e8b5ef7 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -45,7 +45,6 @@ import com.zxingcpp.BarcodeReader.Format import java.io.ByteArrayOutputStream import java.io.File import java.util.concurrent.Executors -import kotlin.random.Random class MainActivity : AppCompatActivity() { @@ -53,7 +52,7 @@ class MainActivity : AppCompatActivity() { private val executor = Executors.newSingleThreadExecutor() private val permissions = mutableListOf(Manifest.permission.CAMERA) - private val permissionsRequestCode = Random.nextInt(0, 10000) + private val permissionsRequestCode = 1 private val beeper = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 50) private var lastText = String() From b4a97a0682087a834b110f960ec7059cf9666e9c Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Wed, 8 Nov 2023 11:27:45 +0100 Subject: [PATCH 067/587] android: return list of results And allocate the Result object(s) on the C++ side, and only if something was found. --- .../com/example/zxingcppdemo/MainActivity.kt | 31 ++-- .../zxingcpp/src/main/cpp/BarcodeReader.cpp | 135 +++++++++++------- .../main/java/com/zxingcpp/BarcodeReader.kt | 60 +++----- 3 files changed, 123 insertions(+), 103 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index d36e8b5ef7..01e0dcf0d4 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -231,21 +231,22 @@ class MainActivity : AppCompatActivity() { ) resultText = try { - val result = image.use{ readerCpp.read(it) } - runtime2 += result?.time?.toInt() ?: 0 - resultPoints = result?.position?.let { - listOf( - it.topLeft, - it.topRight, - it.bottomRight, - it.bottomLeft - ).map { p -> - p.toPointF() + val results = image.use { readerCpp.read(it) } + results?.first()?.let { + runtime2 += it.time?.toInt() ?: 0 + resultPoints = it.position?.let { + listOf( + it.topLeft, + it.topRight, + it.bottomRight, + it.bottomLeft + ).map { p -> + p.toPointF() + } } - } - (result?.let { "${it.format} (${it.contentType}): " + - "${if (it.contentType != BarcodeReader.ContentType.BINARY) it.text else it.bytes!!.joinToString(separator = "") { v -> "%02x".format(v) }}" } - ?: "") + "${it.format} (${it.contentType}): " + + "${if (it.contentType != BarcodeReader.ContentType.BINARY) it.text else it.bytes!!.joinToString(separator = "") { v -> "%02x".format(v) }}" + } ?: "" } catch (e: Throwable) { e.message ?: "Error" } @@ -318,4 +319,4 @@ class MainActivity : AppCompatActivity() { private fun hasPermissions(context: Context) = permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED } -} \ No newline at end of file +} diff --git a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp b/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp index c7b116dea3..05a8351e85 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp @@ -62,13 +62,6 @@ static jstring ThrowJavaException(JNIEnv* env, const char* message) return nullptr; } -static jobject CreateContentType(JNIEnv* env, ContentType contentType) -{ - jclass cls = env->FindClass("com/zxingcpp/BarcodeReader$ContentType"); - jfieldID fidCT = env->GetStaticFieldID(cls , JavaContentTypeName(contentType), "Lcom/zxingcpp/BarcodeReader$ContentType;"); - return env->GetStaticObjectField(cls, fidCT); -} - static jobject CreateAndroidPoint(JNIEnv* env, const PointT& point) { jclass cls = env->FindClass("android/graphics/Point"); @@ -95,8 +88,73 @@ static jobject CreatePosition(JNIEnv* env, const Position& position) position.orientation()); } -jstring Read(JNIEnv *env, ImageView image, jstring formats, jboolean tryHarder, jboolean tryRotate, - jboolean tryInvert, jboolean tryDownscale, jobject result) +static jobject CreateContentType(JNIEnv* env, ContentType contentType) +{ + jclass cls = env->FindClass("com/zxingcpp/BarcodeReader$ContentType"); + jfieldID fidCT = env->GetStaticFieldID(cls, + JavaContentTypeName(contentType), + "Lcom/zxingcpp/BarcodeReader$ContentType;"); + return env->GetStaticObjectField(cls, fidCT); +} + +static jbyteArray CreateByteArray(JNIEnv* env, const void* data, + unsigned int length) +{ + auto size = static_cast(length); + jbyteArray byteArray = env->NewByteArray(size); + env->SetByteArrayRegion( + byteArray, 0, size, reinterpret_cast(data)); + return byteArray; +} + +static jbyteArray CreateByteArray(JNIEnv* env, + const std::vector& byteArray) +{ + return CreateByteArray(env, + reinterpret_cast(byteArray.data()), + byteArray.size()); +} + +static jobject CreateFormat(JNIEnv* env, BarcodeFormat format) +{ + jclass cls = env->FindClass("com/zxingcpp/BarcodeReader$Format"); + jfieldID fidCT = env->GetStaticFieldID(cls, + JavaBarcodeFormatName(format), + "Lcom/zxingcpp/BarcodeReader$Format;"); + return env->GetStaticObjectField(cls, fidCT); +} + +static jobject CreateResult(JNIEnv* env, const Result& result, + const jstring& timeString) +{ + jclass cls = env->FindClass( + "com/zxingcpp/BarcodeReader$Result"); + auto constructor = env->GetMethodID( + cls, "", + "(Lcom/zxingcpp/BarcodeReader$Format;" + "[B" + "Ljava/lang/String;" + "Ljava/lang/String;" + "Lcom/zxingcpp/BarcodeReader$ContentType;" + "Lcom/zxingcpp/BarcodeReader$Position;" + "I" + "Ljava/lang/String;" + "Ljava/lang/String;)V"); + return env->NewObject( + cls, constructor, + CreateFormat(env, result.format()), + CreateByteArray(env, result.bytes()), + C2JString(env, result.text()), + timeString, + CreateContentType(env, result.contentType()), + CreatePosition(env, result.position()), + result.orientation(), + C2JString(env, result.ecLevel()), + C2JString(env, result.symbologyIdentifier())); +} + +static jobject Read(JNIEnv *env, ImageView image, jstring formats, jboolean tryHarder, jboolean tryRotate, + jboolean tryInvert, jboolean tryDownscale) { try { auto hints = DecodeHints() @@ -111,41 +169,22 @@ jstring Read(JNIEnv *env, ImageView image, jstring formats, jboolean tryHarder, auto results = ReadBarcodes(image, hints); auto duration = std::chrono::high_resolution_clock::now() - startTime; // LOGD("time: %4d ms\n", (int)std::chrono::duration_cast(duration).count()); - - jclass clResult = env->GetObjectClass(result); - - jfieldID fidTime = env->GetFieldID(clResult, "time", "Ljava/lang/String;"); auto time = std::to_wstring(std::chrono::duration_cast(duration).count()); - env->SetObjectField(result, fidTime, C2JString(env, time)); + auto timeString = C2JString(env, time); if (!results.empty()) { - auto& res = results.front(); - jbyteArray jByteArray = env->NewByteArray(res.bytes().size()); - env->SetByteArrayRegion(jByteArray, 0, res.bytes().size(), (jbyte*)res.bytes().data()); - jfieldID fidBytes = env->GetFieldID(clResult, "bytes", "[B"); - env->SetObjectField(result, fidBytes, jByteArray); - - jfieldID fidText = env->GetFieldID(clResult, "text", "Ljava/lang/String;"); - env->SetObjectField(result, fidText, C2JString(env, res.text())); - - jfieldID fidContentType = env->GetFieldID(clResult , "contentType", "Lcom/zxingcpp/BarcodeReader$ContentType;"); - env->SetObjectField(result, fidContentType, CreateContentType(env, res.contentType())); - - jfieldID fidPosition = env->GetFieldID(clResult, "position", "Lcom/zxingcpp/BarcodeReader$Position;"); - env->SetObjectField(result, fidPosition, CreatePosition(env, res.position())); - - jfieldID fidOrientation = env->GetFieldID(clResult, "orientation", "I"); - env->SetIntField(result, fidOrientation, res.orientation()); - - jfieldID fidEcLevel = env->GetFieldID(clResult, "ecLevel", "Ljava/lang/String;"); - env->SetObjectField(result, fidEcLevel, C2JString(env, res.ecLevel())); - - jfieldID fidSymbologyIdentifier = env->GetFieldID(clResult, "symbologyIdentifier", "Ljava/lang/String;"); - env->SetObjectField(result, fidSymbologyIdentifier, C2JString(env, res.symbologyIdentifier())); - - return C2JString(env, JavaBarcodeFormatName(res.format())); - } else - return C2JString(env, "NotFound"); + // Only allocate when something is found. + auto cls = env->FindClass("java/util/ArrayList"); + auto list = env->NewObject(cls, + env->GetMethodID(cls, "", "()V")); + auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z"); + for (const auto& result: results) { + env->CallBooleanMethod(list, add, CreateResult(env, result, timeString)); + } + return list; + } else { + return nullptr; + } } catch (const std::exception& e) { return ThrowJavaException(env, e.what()); } catch (...) { @@ -153,12 +192,11 @@ jstring Read(JNIEnv *env, ImageView image, jstring formats, jboolean tryHarder, } } -extern "C" JNIEXPORT jstring JNICALL +extern "C" JNIEXPORT jobject JNICALL Java_com_zxingcpp_BarcodeReader_readYBuffer( JNIEnv *env, jobject thiz, jobject yBuffer, jint rowStride, jint left, jint top, jint width, jint height, jint rotation, - jstring formats, jboolean tryHarder, jboolean tryRotate, jboolean tryInvert, jboolean tryDownscale, - jobject result) + jstring formats, jboolean tryHarder, jboolean tryRotate, jboolean tryInvert, jboolean tryDownscale) { const uint8_t* pixels = static_cast(env->GetDirectBufferAddress(yBuffer)); @@ -166,7 +204,7 @@ Java_com_zxingcpp_BarcodeReader_readYBuffer( ImageView{pixels + top * rowStride + left, width, height, ImageFormat::Lum, rowStride} .rotated(rotation); - return Read(env, image, formats, tryHarder, tryRotate, tryInvert, tryDownscale, result); + return Read(env, image, formats, tryHarder, tryRotate, tryInvert, tryDownscale); } struct LockedPixels @@ -188,12 +226,11 @@ struct LockedPixels } }; -extern "C" JNIEXPORT jstring JNICALL +extern "C" JNIEXPORT jobject JNICALL Java_com_zxingcpp_BarcodeReader_readBitmap( JNIEnv* env, jobject thiz, jobject bitmap, jint left, jint top, jint width, jint height, jint rotation, - jstring formats, jboolean tryHarder, jboolean tryRotate, jboolean tryInvert, jboolean tryDownscale, - jobject result) + jstring formats, jboolean tryHarder, jboolean tryRotate, jboolean tryInvert, jboolean tryDownscale) { AndroidBitmapInfo bmInfo; AndroidBitmap_getInfo(env, bitmap, &bmInfo); @@ -214,5 +251,5 @@ Java_com_zxingcpp_BarcodeReader_readBitmap( .cropped(left, top, width, height) .rotated(rotation); - return Read(env, image, formats, tryHarder, tryRotate, tryInvert, tryDownscale, result); + return Read(env, image, formats, tryHarder, tryRotate, tryInvert, tryDownscale); } diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt index 0846a6b4b6..e31fa0c6c4 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt @@ -78,65 +78,47 @@ public class BarcodeReader { public var options : Options = Options() - public fun read(image: ImageProxy): Result? { + public fun read(image: ImageProxy): List? { check(image.format in supportedYUVFormats) { "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" } - var result = Result() - val status = image.let { - readYBuffer( - it.planes[0].buffer, - it.planes[0].rowStride, - it.cropRect.left, - it.cropRect.top, - it.cropRect.width(), - it.cropRect.height(), - it.imageInfo.rotationDegrees, - options.formats.joinToString(), - options.tryHarder, - options.tryRotate, - options.tryInvert, - options.tryDownscale, - result - ) - } - return try { - result.copy(format = Format.valueOf(status!!)) - } catch (e: Throwable) { - if (status == "NotFound") null else throw RuntimeException(status!!) - } + return readYBuffer( + image.planes[0].buffer, + image.planes[0].rowStride, + image.cropRect.left, + image.cropRect.top, + image.cropRect.width(), + image.cropRect.height(), + image.imageInfo.rotationDegrees, + options.formats.joinToString(), + options.tryHarder, + options.tryRotate, + options.tryInvert, + options.tryDownscale, + ) } - public fun read(bitmap: Bitmap, cropRect: Rect = Rect(), rotation: Int = 0): Result? { + public fun read(bitmap: Bitmap, cropRect: Rect = Rect(), rotation: Int = 0): List? { return read(bitmap, options, cropRect, rotation) } - public fun read(bitmap: Bitmap, options: Options, cropRect: Rect = Rect(), rotation: Int = 0): Result? { - var result = Result() - val status = with(options) { + public fun read(bitmap: Bitmap, options: Options, cropRect: Rect = Rect(), rotation: Int = 0): List? { + return with(options) { readBitmap( bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, - formats.joinToString(), tryHarder, tryRotate, tryInvert, tryDownscale, result + formats.joinToString(), tryHarder, tryRotate, tryInvert, tryDownscale, ) } - return try { - result.copy(format = Format.valueOf(status!!)) - } catch (e: Throwable) { - if (status == "NotFound") null else throw RuntimeException(status!!) - } } - // setting the format enum from inside the JNI code is a hassle -> use returned String instead private external fun readYBuffer( yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, formats: String, tryHarder: Boolean, tryRotate: Boolean, tryInvert: Boolean, tryDownscale: Boolean, - result: Result, - ): String? + ): List? private external fun readBitmap( bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, formats: String, tryHarder: Boolean, tryRotate: Boolean, tryInvert: Boolean, tryDownscale: Boolean, - result: Result, - ): String? + ): List? } From 479913cc8f17ac26bead522b4f281f3826952001 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Wed, 8 Nov 2023 12:19:20 +0100 Subject: [PATCH 068/587] android: extend options with decode hints --- .../zxingcpp/src/main/cpp/BarcodeReader.cpp | 131 ++++++++++++++++-- .../main/java/com/zxingcpp/BarcodeReader.kt | 49 +++++-- 2 files changed, 151 insertions(+), 29 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp b/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp index 05a8351e85..bcfe71003c 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp @@ -53,6 +53,51 @@ static const char* JavaContentTypeName(ContentType contentType) } } +static EanAddOnSymbol EanAddOnSymbolFromString(const std::string& name) +{ + if (name == "IGNORE") { + return EanAddOnSymbol::Ignore; + } else if (name == "READ") { + return EanAddOnSymbol::Read; + } else if (name == "REQUIRE") { + return EanAddOnSymbol::Require; + } else { + throw std::invalid_argument("Invalid eanAddOnSymbol name"); + } +} + +static Binarizer BinarizerFromString(const std::string& name) +{ + if (name == "LOCAL_AVERAGE") { + return Binarizer::LocalAverage; + } else if (name == "GLOBAL_HISTOGRAM") { + return Binarizer::GlobalHistogram; + } else if (name == "FIXED_THRESHOLD") { + return Binarizer::FixedThreshold; + } else if (name == "BOOL_CAST") { + return Binarizer::BoolCast; + } else { + throw std::invalid_argument("Invalid binarizer name"); + } +} + +static TextMode TextModeFromString(const std::string& name) +{ + if (name == "PLAIN") { + return TextMode::Plain; + } else if (name == "ECI") { + return TextMode::ECI; + } else if (name == "HRI") { + return TextMode::HRI; + } else if (name == "HEX") { + return TextMode::Hex; + } else if (name == "ESCAPED") { + return TextMode::Escaped; + } else { + throw std::invalid_argument("Invalid textMode name"); + } +} + static jstring ThrowJavaException(JNIEnv* env, const char* message) { // if (env->ExceptionCheck()) @@ -153,18 +198,9 @@ static jobject CreateResult(JNIEnv* env, const Result& result, C2JString(env, result.symbologyIdentifier())); } -static jobject Read(JNIEnv *env, ImageView image, jstring formats, jboolean tryHarder, jboolean tryRotate, - jboolean tryInvert, jboolean tryDownscale) +static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) { try { - auto hints = DecodeHints() - .setFormats(BarcodeFormatsFromString(J2CString(env, formats))) - .setTryHarder(tryHarder) - .setTryRotate(tryRotate) - .setTryInvert(tryInvert) - .setTryDownscale(tryDownscale) - .setMaxNumberOfSymbols(1); - auto startTime = std::chrono::high_resolution_clock::now(); auto results = ReadBarcodes(image, hints); auto duration = std::chrono::high_resolution_clock::now() - startTime; @@ -192,11 +228,78 @@ static jobject Read(JNIEnv *env, ImageView image, jstring formats, jboolean tryH } } +static bool GetBooleanField(JNIEnv* env, jclass cls, jobject hints, + const char* name) +{ + return env->GetBooleanField(hints, env->GetFieldID(cls, name, "Z")); +} + +static int GetIntField(JNIEnv* env, jclass cls, jobject hints, + const char* name) +{ + return env->GetIntField(hints, env->GetFieldID(cls, name, "I")); +} + +static std::string GetEnumField(JNIEnv* env, jclass hintClass, jobject hints, + const char* enumClass, const char* name) +{ + jclass cls = env->FindClass(enumClass); + jstring s = (jstring) env->CallObjectMethod( + env->GetObjectField(hints, env->GetFieldID(hintClass, name, + ("L" + std::string(enumClass) + ";").c_str())), + env->GetMethodID(cls, "name", "()Ljava/lang/String;")); + return J2CString(env, s); +} + +static std::string JoinFormats(JNIEnv* env, jclass hintClass, jobject hints) +{ + const char* setClass = "java/util/Set"; + jclass cls = env->FindClass(setClass); + jstring jStr = (jstring) env->CallObjectMethod( + env->GetObjectField(hints, env->GetFieldID(hintClass, "formats", + ("L" + std::string(setClass) + ";").c_str())), + env->GetMethodID(cls, "toString", "()Ljava/lang/String;")); + std::string s = J2CString(env, jStr); + s.erase(0, s.find_first_not_of("[")); + s.erase(s.find_last_not_of("]") + 1); + return s; +} + +static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) +{ + jclass cls = env->GetObjectClass(hints); + return DecodeHints() + .setFormats(BarcodeFormatsFromString(JoinFormats(env, cls, hints))) + .setTryHarder(GetBooleanField(env, cls, hints, "tryHarder")) + .setTryRotate(GetBooleanField(env, cls, hints, "tryRotate")) + .setTryInvert(GetBooleanField(env, cls, hints, "tryInvert")) + .setTryDownscale(GetBooleanField(env, cls, hints, "tryDownscale")) + .setIsPure(GetBooleanField(env, cls, hints, "isPure")) + .setTryCode39ExtendedMode(GetBooleanField(env, cls, hints, "tryCode39ExtendedMode")) + .setValidateCode39CheckSum(GetBooleanField(env, cls, hints, "validateCode39CheckSum")) + .setValidateITFCheckSum(GetBooleanField(env, cls, hints, "validateITFCheckSum")) + .setReturnCodabarStartEnd(GetBooleanField(env, cls, hints, "returnCodabarStartEnd")) + .setReturnErrors(GetBooleanField(env, cls, hints, "returnErrors")) + .setDownscaleFactor(GetIntField(env, cls, hints, "downscaleFactor")) + .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, hints, + "com/zxingcpp/BarcodeReader$EanAddOnSymbol", + "eanAddOnSymbol"))) + .setBinarizer(BinarizerFromString(GetEnumField(env, cls, hints, + "com/zxingcpp/BarcodeReader$Binarizer", + "binarizer"))) + .setTextMode(TextModeFromString(GetEnumField(env, cls, hints, + "com/zxingcpp/BarcodeReader$TextMode", + "textMode"))) + .setMinLineCount(GetIntField(env, cls, hints, "minLineCount")) + .setMaxNumberOfSymbols(GetIntField(env, cls, hints, "maxNumberOfSymbols")) + .setDownscaleThreshold(GetIntField(env, cls, hints, "downscaleThreshold")); +} + extern "C" JNIEXPORT jobject JNICALL Java_com_zxingcpp_BarcodeReader_readYBuffer( JNIEnv *env, jobject thiz, jobject yBuffer, jint rowStride, jint left, jint top, jint width, jint height, jint rotation, - jstring formats, jboolean tryHarder, jboolean tryRotate, jboolean tryInvert, jboolean tryDownscale) + jobject hints) { const uint8_t* pixels = static_cast(env->GetDirectBufferAddress(yBuffer)); @@ -204,7 +307,7 @@ Java_com_zxingcpp_BarcodeReader_readYBuffer( ImageView{pixels + top * rowStride + left, width, height, ImageFormat::Lum, rowStride} .rotated(rotation); - return Read(env, image, formats, tryHarder, tryRotate, tryInvert, tryDownscale); + return Read(env, image, CreateDecodeHints(env, hints)); } struct LockedPixels @@ -230,7 +333,7 @@ extern "C" JNIEXPORT jobject JNICALL Java_com_zxingcpp_BarcodeReader_readBitmap( JNIEnv* env, jobject thiz, jobject bitmap, jint left, jint top, jint width, jint height, jint rotation, - jstring formats, jboolean tryHarder, jboolean tryRotate, jboolean tryInvert, jboolean tryDownscale) + jobject hints) { AndroidBitmapInfo bmInfo; AndroidBitmap_getInfo(env, bitmap, &bmInfo); @@ -251,5 +354,5 @@ Java_com_zxingcpp_BarcodeReader_readBitmap( .cropped(left, top, width, height) .rotated(rotation); - return Read(env, image, formats, tryHarder, tryRotate, tryInvert, tryDownscale); + return Read(env, image, CreateDecodeHints(env, hints)); } diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt index e31fa0c6c4..4871a59ec4 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt @@ -48,12 +48,37 @@ public class BarcodeReader { TEXT, BINARY, MIXED, GS1, ISO15434, UNKNOWN_ECI } + public enum class Binarizer { + LOCAL_AVERAGE, GLOBAL_HISTOGRAM, FIXED_THRESHOLD, BOOL_CAST + } + + public enum class EanAddOnSymbol { + IGNORE, READ, REQUIRE + } + + public enum class TextMode { + PLAIN, ECI, HRI, HEX, ESCAPED + } + public data class Options( val formats: Set = setOf(), val tryHarder: Boolean = false, val tryRotate: Boolean = false, val tryInvert: Boolean = false, - val tryDownscale: Boolean = false + val tryDownscale: Boolean = false, + val isPure: Boolean = false, + val tryCode39ExtendedMode: Boolean = false, + val validateCode39CheckSum: Boolean = false, + val validateITFCheckSum: Boolean = false, + val returnCodabarStartEnd: Boolean = false, + val returnErrors: Boolean = false, + val downscaleFactor: Int = 3, + val eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, + val binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, + val textMode: TextMode = TextMode.HRI, + val minLineCount: Int = 2, + val maxNumberOfSymbols: Int = 0xff, + val downscaleThreshold: Int = 500 ) public data class Position( @@ -76,7 +101,7 @@ public class BarcodeReader { val symbologyIdentifier: String? = null ) - public var options : Options = Options() + public var options: Options = Options() public fun read(image: ImageProxy): List? { check(image.format in supportedYUVFormats) { @@ -91,11 +116,7 @@ public class BarcodeReader { image.cropRect.width(), image.cropRect.height(), image.imageInfo.rotationDegrees, - options.formats.joinToString(), - options.tryHarder, - options.tryRotate, - options.tryInvert, - options.tryDownscale, + options ) } @@ -104,21 +125,19 @@ public class BarcodeReader { } public fun read(bitmap: Bitmap, options: Options, cropRect: Rect = Rect(), rotation: Int = 0): List? { - return with(options) { - readBitmap( - bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, - formats.joinToString(), tryHarder, tryRotate, tryInvert, tryDownscale, - ) - } + return readBitmap( + bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, + options + ) } private external fun readYBuffer( yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, - formats: String, tryHarder: Boolean, tryRotate: Boolean, tryInvert: Boolean, tryDownscale: Boolean, + options: Options ): List? private external fun readBitmap( bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, - formats: String, tryHarder: Boolean, tryRotate: Boolean, tryInvert: Boolean, tryDownscale: Boolean, + options: Options ): List? } From f59bad78f438e5e9fb8dd90aee52deaff1791f15 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Wed, 8 Nov 2023 17:50:57 +0100 Subject: [PATCH 069/587] android: extend Result with missing members Except `version` and `GTIN`, as requested. `version` is about to be deprecated. --- .../zxingcpp/src/main/cpp/BarcodeReader.cpp | 14 +++++++++-- .../main/java/com/zxingcpp/BarcodeReader.kt | 23 +++++++++++-------- 2 files changed, 26 insertions(+), 11 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp b/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp index bcfe71003c..15c211b256 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp @@ -184,7 +184,12 @@ static jobject CreateResult(JNIEnv* env, const Result& result, "Lcom/zxingcpp/BarcodeReader$Position;" "I" "Ljava/lang/String;" - "Ljava/lang/String;)V"); + "Ljava/lang/String;" + "I" + "I" + "Ljava/lang/String;" + "Z" + "I)V"); return env->NewObject( cls, constructor, CreateFormat(env, result.format()), @@ -195,7 +200,12 @@ static jobject CreateResult(JNIEnv* env, const Result& result, CreatePosition(env, result.position()), result.orientation(), C2JString(env, result.ecLevel()), - C2JString(env, result.symbologyIdentifier())); + C2JString(env, result.symbologyIdentifier()), + result.sequenceSize(), + result.sequenceIndex(), + C2JString(env, result.sequenceId()), + result.readerInit(), + result.lineCount()); } static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt index 4871a59ec4..89318aca01 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt @@ -90,15 +90,20 @@ public class BarcodeReader { ) public data class Result( - val format: Format = Format.NONE, - val bytes: ByteArray? = null, - val text: String? = null, - val time: String? = null, // for development/debug purposes only - val contentType: ContentType = ContentType.TEXT, - val position: Position? = null, - val orientation: Int = 0, - val ecLevel: String? = null, - val symbologyIdentifier: String? = null + val format: Format, + val bytes: ByteArray?, + val text: String?, + val time: String?, // for development/debug purposes only + val contentType: ContentType, + val position: Position?, + val orientation: Int, + val ecLevel: String?, + val symbologyIdentifier: String?, + val sequenceSize: Int, + val sequenceIndex: Int, + val sequenceId: String, + val readerInit: Boolean, + val lineCount: Int ) public var options: Options = Options() From b897a45bdd577dff3c1dbd631c00abf3b771748d Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 9 Nov 2023 09:03:23 +0100 Subject: [PATCH 070/587] android: fix indent and stray space character --- wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp | 4 ++-- wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp b/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp index 15c211b256..1bddbec9cf 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp @@ -361,8 +361,8 @@ Java_com_zxingcpp_BarcodeReader_readBitmap( return ThrowJavaException(env, "Failed to lock/Read AndroidBitmap data"); auto image = ImageView{pixels, (int)bmInfo.width, (int)bmInfo.height, fmt, (int)bmInfo.stride} - .cropped(left, top, width, height) - .rotated(rotation); + .cropped(left, top, width, height) + .rotated(rotation); return Read(env, image, CreateDecodeHints(env, hints)); } diff --git a/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp b/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp index f797bda380..a6e2aade67 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp @@ -28,7 +28,7 @@ static void Utf32toUtf16(const uint32_t* utf32, size_t length, std::vector Date: Thu, 9 Nov 2023 10:57:53 +0100 Subject: [PATCH 071/587] android: publish library in local repository MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Now you can run this to publish the the library in your local Maven repository: $ ./gradlew publishToMavenLocal Then you can include the library in any project (on your machine) by adding this to `dependencies` in `app/build.gradle`: implementation 'com.zxingcpp:zxingcpp:2.1.0' Of course, for this to work you need to add `mavelLocal()` to your `repositories` in your top level `build.gradle`: repositories { mavenLocal() … } To publish the library in a public remote Maven repository, we need to add the corresponding credentials. --- wrappers/android/zxingcpp/build.gradle.kts | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/wrappers/android/zxingcpp/build.gradle.kts b/wrappers/android/zxingcpp/build.gradle.kts index f1fd062a33..7cd939673c 100644 --- a/wrappers/android/zxingcpp/build.gradle.kts +++ b/wrappers/android/zxingcpp/build.gradle.kts @@ -1,6 +1,7 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) + `maven-publish` } android { @@ -51,3 +52,17 @@ kotlin { dependencies { implementation(libs.androidx.camera.core) } + +publishing { + publications { + register("release") { + groupId = "com.zxingcpp" + artifactId = "zxingcpp" + version = "2.1.0" + + afterEvaluate { + from(components["release"]) + } + } + } +} From 23ce4aba2923c657ba78851b6ca5b2ea0145b6d8 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 9 Nov 2023 15:30:57 +0100 Subject: [PATCH 072/587] android: refactor BarcodeReader to ZXingCpp Because the term "ZXingCpp" is more clear and there will probably be write support some day, too. --- .../com/example/zxingcppdemo/MainActivity.kt | 10 +++---- .../zxingcpp/src/main/cpp/CMakeLists.txt | 2 +- .../cpp/{BarcodeReader.cpp => ZXingCpp.cpp} | 28 +++++++++---------- .../{BarcodeReader.kt => ZXingCpp.kt} | 2 +- 4 files changed, 21 insertions(+), 21 deletions(-) rename wrappers/android/zxingcpp/src/main/cpp/{BarcodeReader.cpp => ZXingCpp.cpp} (94%) rename wrappers/android/zxingcpp/src/main/java/com/zxingcpp/{BarcodeReader.kt => ZXingCpp.kt} (96%) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 01e0dcf0d4..57526dbee8 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -40,8 +40,8 @@ import androidx.lifecycle.LifecycleOwner import com.example.zxingcppdemo.databinding.ActivityCameraBinding import com.google.zxing.* import com.google.zxing.common.HybridBinarizer -import com.zxingcpp.BarcodeReader -import com.zxingcpp.BarcodeReader.Format +import com.zxingcpp.ZXingCpp +import com.zxingcpp.ZXingCpp.Format import java.io.ByteArrayOutputStream import java.io.File import java.util.concurrent.Executors @@ -143,7 +143,7 @@ class MainActivity : AppCompatActivity() { var runtimes: Long = 0 var runtime2: Long = 0 val readerJava = MultiFormatReader() - val readerCpp = BarcodeReader() + val readerCpp = ZXingCpp() // Create a new camera selector each time, enforcing lens facing val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() @@ -222,7 +222,7 @@ class MainActivity : AppCompatActivity() { if (e.toString() != "com.google.zxing.NotFoundException") e.toString() else "" } } else { - readerCpp.options = BarcodeReader.Options( + readerCpp.options = ZXingCpp.Options( formats = if (binding.qrcode.isChecked) setOf(Format.QR_CODE) else setOf(), tryHarder = binding.tryHarder.isChecked, tryRotate = binding.tryRotate.isChecked, @@ -245,7 +245,7 @@ class MainActivity : AppCompatActivity() { } } "${it.format} (${it.contentType}): " + - "${if (it.contentType != BarcodeReader.ContentType.BINARY) it.text else it.bytes!!.joinToString(separator = "") { v -> "%02x".format(v) }}" + "${if (it.contentType != ZXingCpp.ContentType.BINARY) it.text else it.bytes!!.joinToString(separator = "") { v -> "%02x".format(v) }}" } ?: "" } catch (e: Throwable) { e.message ?: "Error" diff --git a/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt b/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt index d3c0d32c28..1f45f4a225 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt +++ b/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt @@ -10,7 +10,7 @@ set(BUILD_WRITERS OFF) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../core ZXing EXCLUDE_FROM_ALL) -add_library(zxing_android SHARED BarcodeReader.cpp JNIUtils.cpp) +add_library(zxing_android SHARED ZXingCpp.cpp JNIUtils.cpp) target_link_libraries(zxing_android PRIVATE ZXing::ZXing log jnigraphics) diff --git a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp similarity index 94% rename from wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp rename to wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 1bddbec9cf..7443d175d2 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/BarcodeReader.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -116,7 +116,7 @@ static jobject CreateAndroidPoint(JNIEnv* env, const PointT& point) static jobject CreatePosition(JNIEnv* env, const Position& position) { - jclass cls = env->FindClass("com/zxingcpp/BarcodeReader$Position"); + jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Position"); auto constructor = env->GetMethodID( cls, "", "(Landroid/graphics/Point;" @@ -135,10 +135,10 @@ static jobject CreatePosition(JNIEnv* env, const Position& position) static jobject CreateContentType(JNIEnv* env, ContentType contentType) { - jclass cls = env->FindClass("com/zxingcpp/BarcodeReader$ContentType"); + jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$ContentType"); jfieldID fidCT = env->GetStaticFieldID(cls, JavaContentTypeName(contentType), - "Lcom/zxingcpp/BarcodeReader$ContentType;"); + "Lcom/zxingcpp/ZXingCpp$ContentType;"); return env->GetStaticObjectField(cls, fidCT); } @@ -162,10 +162,10 @@ static jbyteArray CreateByteArray(JNIEnv* env, static jobject CreateFormat(JNIEnv* env, BarcodeFormat format) { - jclass cls = env->FindClass("com/zxingcpp/BarcodeReader$Format"); + jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Format"); jfieldID fidCT = env->GetStaticFieldID(cls, JavaBarcodeFormatName(format), - "Lcom/zxingcpp/BarcodeReader$Format;"); + "Lcom/zxingcpp/ZXingCpp$Format;"); return env->GetStaticObjectField(cls, fidCT); } @@ -173,15 +173,15 @@ static jobject CreateResult(JNIEnv* env, const Result& result, const jstring& timeString) { jclass cls = env->FindClass( - "com/zxingcpp/BarcodeReader$Result"); + "com/zxingcpp/ZXingCpp$Result"); auto constructor = env->GetMethodID( cls, "", - "(Lcom/zxingcpp/BarcodeReader$Format;" + "(Lcom/zxingcpp/ZXingCpp$Format;" "[B" "Ljava/lang/String;" "Ljava/lang/String;" - "Lcom/zxingcpp/BarcodeReader$ContentType;" - "Lcom/zxingcpp/BarcodeReader$Position;" + "Lcom/zxingcpp/ZXingCpp$ContentType;" + "Lcom/zxingcpp/ZXingCpp$Position;" "I" "Ljava/lang/String;" "Ljava/lang/String;" @@ -292,13 +292,13 @@ static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) .setReturnErrors(GetBooleanField(env, cls, hints, "returnErrors")) .setDownscaleFactor(GetIntField(env, cls, hints, "downscaleFactor")) .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, hints, - "com/zxingcpp/BarcodeReader$EanAddOnSymbol", + "com/zxingcpp/ZXingCpp$EanAddOnSymbol", "eanAddOnSymbol"))) .setBinarizer(BinarizerFromString(GetEnumField(env, cls, hints, - "com/zxingcpp/BarcodeReader$Binarizer", + "com/zxingcpp/ZXingCpp$Binarizer", "binarizer"))) .setTextMode(TextModeFromString(GetEnumField(env, cls, hints, - "com/zxingcpp/BarcodeReader$TextMode", + "com/zxingcpp/ZXingCpp$TextMode", "textMode"))) .setMinLineCount(GetIntField(env, cls, hints, "minLineCount")) .setMaxNumberOfSymbols(GetIntField(env, cls, hints, "maxNumberOfSymbols")) @@ -306,7 +306,7 @@ static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) } extern "C" JNIEXPORT jobject JNICALL -Java_com_zxingcpp_BarcodeReader_readYBuffer( +Java_com_zxingcpp_ZXingCpp_readYBuffer( JNIEnv *env, jobject thiz, jobject yBuffer, jint rowStride, jint left, jint top, jint width, jint height, jint rotation, jobject hints) @@ -340,7 +340,7 @@ struct LockedPixels }; extern "C" JNIEXPORT jobject JNICALL -Java_com_zxingcpp_BarcodeReader_readBitmap( +Java_com_zxingcpp_ZXingCpp_readBitmap( JNIEnv* env, jobject thiz, jobject bitmap, jint left, jint top, jint width, jint height, jint rotation, jobject hints) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt similarity index 96% rename from wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt rename to wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 89318aca01..fa96b6ec4a 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -25,7 +25,7 @@ import androidx.camera.core.ImageProxy import java.lang.RuntimeException import java.nio.ByteBuffer -public class BarcodeReader { +public class ZXingCpp { private val supportedYUVFormats: List = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) From e739f81bb7e04b5f07ae43aa3f74b4c61a917812 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 9 Nov 2023 15:35:02 +0100 Subject: [PATCH 073/587] android: make `options` an argument to read() Instead of an object field. This makes the API a little bit more clear about side effects and also a bit more consistent. --- .../main/java/com/example/zxingcppdemo/MainActivity.kt | 4 ++-- .../zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt | 8 +------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 57526dbee8..28e1ab639e 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -222,7 +222,7 @@ class MainActivity : AppCompatActivity() { if (e.toString() != "com.google.zxing.NotFoundException") e.toString() else "" } } else { - readerCpp.options = ZXingCpp.Options( + val options = ZXingCpp.Options( formats = if (binding.qrcode.isChecked) setOf(Format.QR_CODE) else setOf(), tryHarder = binding.tryHarder.isChecked, tryRotate = binding.tryRotate.isChecked, @@ -231,7 +231,7 @@ class MainActivity : AppCompatActivity() { ) resultText = try { - val results = image.use { readerCpp.read(it) } + val results = image.use { readerCpp.read(it, options) } results?.first()?.let { runtime2 += it.time?.toInt() ?: 0 resultPoints = it.position?.let { diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index fa96b6ec4a..221f3167e2 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -106,9 +106,7 @@ public class ZXingCpp { val lineCount: Int ) - public var options: Options = Options() - - public fun read(image: ImageProxy): List? { + public fun read(image: ImageProxy, options: Options): List? { check(image.format in supportedYUVFormats) { "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" } @@ -125,10 +123,6 @@ public class ZXingCpp { ) } - public fun read(bitmap: Bitmap, cropRect: Rect = Rect(), rotation: Int = 0): List? { - return read(bitmap, options, cropRect, rotation) - } - public fun read(bitmap: Bitmap, options: Options, cropRect: Rect = Rect(), rotation: Int = 0): List? { return readBitmap( bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, From fbabc0c685630e06e55c9abc075bf0d61d3ba1ef Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 9 Nov 2023 15:38:15 +0100 Subject: [PATCH 074/587] android: make ZXingCpp a singleton Because ZXingCpp doesn't have any fields anymore. This way, ZXingCpp is more clear to the user about possible side effects. --- .../app/src/main/java/com/example/zxingcppdemo/MainActivity.kt | 3 +-- .../android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 28e1ab639e..c333b0e8ad 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -143,7 +143,6 @@ class MainActivity : AppCompatActivity() { var runtimes: Long = 0 var runtime2: Long = 0 val readerJava = MultiFormatReader() - val readerCpp = ZXingCpp() // Create a new camera selector each time, enforcing lens facing val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() @@ -231,7 +230,7 @@ class MainActivity : AppCompatActivity() { ) resultText = try { - val results = image.use { readerCpp.read(it, options) } + val results = image.use { ZXingCpp.read(it, options) } results?.first()?.let { runtime2 += it.time?.toInt() ?: 0 resultPoints = it.position?.let { diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 221f3167e2..f36939f35e 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -25,7 +25,7 @@ import androidx.camera.core.ImageProxy import java.lang.RuntimeException import java.nio.ByteBuffer -public class ZXingCpp { +public object ZXingCpp { private val supportedYUVFormats: List = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) From b330f3077801f9c6ff8aa3ff930420dabe44fa5a Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 9 Nov 2023 15:43:36 +0100 Subject: [PATCH 075/587] android: refactor Options to DecodeHints As we use the term "DecodeHints" in other wrappers and because "Options" is a very general term. "DecodeHints" is more precise, and it's immediately clear for what these options are. --- .../java/com/example/zxingcppdemo/MainActivity.kt | 2 +- .../src/main/java/com/zxingcpp/ZXingCpp.kt | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index c333b0e8ad..6d36490ef5 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -221,7 +221,7 @@ class MainActivity : AppCompatActivity() { if (e.toString() != "com.google.zxing.NotFoundException") e.toString() else "" } } else { - val options = ZXingCpp.Options( + val options = ZXingCpp.DecodeHints( formats = if (binding.qrcode.isChecked) setOf(Format.QR_CODE) else setOf(), tryHarder = binding.tryHarder.isChecked, tryRotate = binding.tryRotate.isChecked, diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index f36939f35e..409af615fe 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -60,7 +60,7 @@ public object ZXingCpp { PLAIN, ECI, HRI, HEX, ESCAPED } - public data class Options( + public data class DecodeHints( val formats: Set = setOf(), val tryHarder: Boolean = false, val tryRotate: Boolean = false, @@ -106,7 +106,7 @@ public object ZXingCpp { val lineCount: Int ) - public fun read(image: ImageProxy, options: Options): List? { + public fun read(image: ImageProxy, decodeHints: DecodeHints): List? { check(image.format in supportedYUVFormats) { "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" } @@ -119,24 +119,24 @@ public object ZXingCpp { image.cropRect.width(), image.cropRect.height(), image.imageInfo.rotationDegrees, - options + decodeHints ) } - public fun read(bitmap: Bitmap, options: Options, cropRect: Rect = Rect(), rotation: Int = 0): List? { + public fun read(bitmap: Bitmap, decodeHints: DecodeHints, cropRect: Rect = Rect(), rotation: Int = 0): List? { return readBitmap( bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, - options + decodeHints ) } private external fun readYBuffer( yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, - options: Options + decodeHints: DecodeHints ): List? private external fun readBitmap( bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, - options: Options + decodeHints: DecodeHints ): List? } From 8c17cb373f147d0760136a879d52ff11d7379676 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 9 Nov 2023 16:19:38 +0100 Subject: [PATCH 076/587] android: show multiple results in sample app --- .../com/example/zxingcppdemo/MainActivity.kt | 45 ++++++++++++------- .../example/zxingcppdemo/PreviewOverlay.kt | 14 +++--- 2 files changed, 38 insertions(+), 21 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 6d36490ef5..20d7fc6041 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -190,7 +190,7 @@ class MainActivity : AppCompatActivity() { val startTime = System.currentTimeMillis() var resultText: String - var resultPoints: List? = null + val resultPoints = mutableListOf>() if (binding.java.isChecked) { val yPlane = image.planes[0] @@ -230,21 +230,34 @@ class MainActivity : AppCompatActivity() { ) resultText = try { - val results = image.use { ZXingCpp.read(it, options) } - results?.first()?.let { - runtime2 += it.time?.toInt() ?: 0 - resultPoints = it.position?.let { - listOf( - it.topLeft, - it.topRight, - it.bottomRight, - it.bottomLeft - ).map { p -> - p.toPointF() - } + image.use { + ZXingCpp.read(it, options) + }?.apply { + runtime2 += this[0].time?.toInt() ?: 0 + }?.joinToString("\n") { result -> + result.position?.let { + resultPoints.add( + listOf( + it.topLeft, + it.topRight, + it.bottomRight, + it.bottomLeft + ).map { p -> + p.toPointF() + } + ) } - "${it.format} (${it.contentType}): " + - "${if (it.contentType != ZXingCpp.ContentType.BINARY) it.text else it.bytes!!.joinToString(separator = "") { v -> "%02x".format(v) }}" + "${result.format} (${result.contentType}): ${ + if (result.contentType != ZXingCpp.ContentType.BINARY) { + result.text + } else { + result.bytes!!.joinToString(separator = "") { v -> + "%02x".format( + v + ) + } + } + }" } ?: "" } catch (e: Throwable) { e.message ?: "Error" @@ -274,7 +287,7 @@ class MainActivity : AppCompatActivity() { }, ContextCompat.getMainExecutor(this)) } - private fun showResult(resultText: String, fpsText: String?, points: List?, image: ImageProxy) = + private fun showResult(resultText: String, fpsText: String?, points: List>, image: ImageProxy) = binding.viewFinder.post { // Update the text and UI binding.result.text = resultText diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt index 7c2db15c12..677f43eefb 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt @@ -44,7 +44,7 @@ class PreviewOverlay constructor(context: Context, attributeSet: AttributeSet?) private var s: Float = 0f private var o: Float = 0f - fun update(viewFinder: View, image: ImageProxy, points: List?) { + fun update(viewFinder: View, image: ImageProxy, points: List>) { cropRect = image.cropRect rotation = image.imageInfo.rotationDegrees s = min(viewFinder.width, viewFinder.height).toFloat() / image.height @@ -52,10 +52,14 @@ class PreviewOverlay constructor(context: Context, attributeSet: AttributeSet?) path.apply { rewind() - if (!points.isNullOrEmpty()) { - moveTo(points.last().x, points.last().y) - for (p in points) - lineTo(p.x, p.y) + points.forEach { + if (!it.isEmpty()) { + val last = it.last() + moveTo(last.x, last.y) + for (p in it) { + lineTo(p.x, p.y) + } + } } } invalidate() From dfa585fc245bada8bbed09149459e9bed0e2e868 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 10 Nov 2023 13:36:21 +0100 Subject: [PATCH 077/587] android: reformat sample app With Android Studio for the latest style changes. --- .../com/example/zxingcppdemo/MainActivity.kt | 61 +++++++++++++++---- .../example/zxingcppdemo/PreviewOverlay.kt | 6 +- 2 files changed, 53 insertions(+), 14 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 20d7fc6041..f3114cda1b 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -19,7 +19,10 @@ package com.example.zxingcppdemo import android.Manifest import android.content.Context import android.content.pm.PackageManager -import android.graphics.* +import android.graphics.ImageFormat +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.YuvImage import android.hardware.camera2.CaptureRequest import android.media.AudioManager import android.media.MediaActionSound @@ -31,14 +34,22 @@ import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.camera.camera2.interop.Camera2CameraControl import androidx.camera.camera2.interop.CaptureRequestOptions -import androidx.camera.core.* +import androidx.camera.core.AspectRatio +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.graphics.toPointF import androidx.lifecycle.LifecycleOwner import com.example.zxingcppdemo.databinding.ActivityCameraBinding -import com.google.zxing.* +import com.google.zxing.BarcodeFormat +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.MultiFormatReader +import com.google.zxing.PlanarYUVLuminanceSource import com.google.zxing.common.HybridBinarizer import com.zxingcpp.ZXingCpp import com.zxingcpp.ZXingCpp.Format @@ -106,8 +117,9 @@ class MainActivity : AppCompatActivity() { private fun saveImage(image: ImageProxy) { try { val currentMillis = System.currentTimeMillis().toString() - val filename = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - .toString() + "/" + currentMillis + "_ZXingCpp.jpg" + val filename = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .toString() + "/" + currentMillis + "_ZXingCpp.jpg" File(filename).outputStream().use { out -> out.write(image.toJpeg()) @@ -145,7 +157,8 @@ class MainActivity : AppCompatActivity() { val readerJava = MultiFormatReader() // Create a new camera selector each time, enforcing lens facing - val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() + val cameraSelector = + CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() // Camera provider is now guaranteed to be available val cameraProvider = cameraProviderFuture.get() @@ -181,8 +194,10 @@ class MainActivity : AppCompatActivity() { val cropSize = image.height / 3 * 2 val cropRect = if (binding.crop.isChecked) Rect( - (image.width - cropSize) / 2, (image.height - cropSize) / 2, - (image.width - cropSize) / 2 + cropSize, (image.height - cropSize) / 2 + cropSize + (image.width - cropSize) / 2, + (image.height - cropSize) / 2, + (image.width - cropSize) / 2 + cropSize, + (image.height - cropSize) / 2 + cropSize ) else Rect(0, 0, image.width, image.height) @@ -209,8 +224,13 @@ class MainActivity : AppCompatActivity() { val bitmap = BinaryBitmap( HybridBinarizer( PlanarYUVLuminanceSource( - data, yStride, image.height, - cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), + data, + yStride, + image.height, + cropRect.left, + cropRect.top, + cropRect.width(), + cropRect.height(), false ) ) @@ -272,7 +292,13 @@ class MainActivity : AppCompatActivity() { val fps = 1000 * frameCounter.toDouble() / (now - lastFpsTimestamp) infoText = "Time: %2d/%2d ms, FPS: %.02f, (%dx%d)" - .format(runtimes / frameCounter, runtime2 / frameCounter, fps, image.width, image.height) + .format( + runtimes / frameCounter, + runtime2 / frameCounter, + fps, + image.width, + image.height + ) lastFpsTimestamp = now frameCounter = 0 runtimes = 0 @@ -287,7 +313,12 @@ class MainActivity : AppCompatActivity() { }, ContextCompat.getMainExecutor(this)) } - private fun showResult(resultText: String, fpsText: String?, points: List>, image: ImageProxy) = + private fun showResult( + resultText: String, + fpsText: String?, + points: List>, + image: ImageProxy + ) = binding.viewFinder.post { // Update the text and UI binding.result.text = resultText @@ -309,7 +340,11 @@ class MainActivity : AppCompatActivity() { // Request permissions each time the app resumes, since they can be revoked at any time if (!hasPermissions(this)) { - ActivityCompat.requestPermissions(this, permissions.toTypedArray(), permissionsRequestCode) + ActivityCompat.requestPermissions( + this, + permissions.toTypedArray(), + permissionsRequestCode + ) } else { bindCameraUseCases() } diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt index 677f43eefb..293ed37fed 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt @@ -18,7 +18,11 @@ package com.example.zxingcppdemo import android.content.Context -import android.graphics.* +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PointF +import android.graphics.Rect import android.util.AttributeSet import android.view.View import androidx.camera.core.ImageProxy From cf596b066c9c356f745c2be809ce5454b1b39d2e Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 10 Nov 2023 13:37:16 +0100 Subject: [PATCH 078/587] android: reformat library source To use tabs for indentation like the sample app does. There's no point in using a different indentation here. --- .../src/main/java/com/zxingcpp/ZXingCpp.kt | 238 +++++++++--------- 1 file changed, 124 insertions(+), 114 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 409af615fe..a3cc607ad4 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -22,121 +22,131 @@ import android.graphics.Point import android.graphics.Rect import android.os.Build import androidx.camera.core.ImageProxy -import java.lang.RuntimeException import java.nio.ByteBuffer public object ZXingCpp { - private val supportedYUVFormats: List = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) - } else { - listOf(ImageFormat.YUV_420_888) - } - - init { - System.loadLibrary("zxing_android") - } - - // Enumerates barcode formats known to this package. - // Note that this has to be kept synchronized with native (C++/JNI) side. - public enum class Format { - NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, - DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E - } - - public enum class ContentType { - TEXT, BINARY, MIXED, GS1, ISO15434, UNKNOWN_ECI - } - - public enum class Binarizer { - LOCAL_AVERAGE, GLOBAL_HISTOGRAM, FIXED_THRESHOLD, BOOL_CAST - } - - public enum class EanAddOnSymbol { - IGNORE, READ, REQUIRE - } - - public enum class TextMode { - PLAIN, ECI, HRI, HEX, ESCAPED - } - - public data class DecodeHints( - val formats: Set = setOf(), - val tryHarder: Boolean = false, - val tryRotate: Boolean = false, - val tryInvert: Boolean = false, - val tryDownscale: Boolean = false, - val isPure: Boolean = false, - val tryCode39ExtendedMode: Boolean = false, - val validateCode39CheckSum: Boolean = false, - val validateITFCheckSum: Boolean = false, - val returnCodabarStartEnd: Boolean = false, - val returnErrors: Boolean = false, - val downscaleFactor: Int = 3, - val eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, - val binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, - val textMode: TextMode = TextMode.HRI, - val minLineCount: Int = 2, - val maxNumberOfSymbols: Int = 0xff, - val downscaleThreshold: Int = 500 - ) - - public data class Position( - val topLeft: Point, - val topRight: Point, - val bottomLeft: Point, - val bottomRight: Point, - val orientation: Double - ) - - public data class Result( - val format: Format, - val bytes: ByteArray?, - val text: String?, - val time: String?, // for development/debug purposes only - val contentType: ContentType, - val position: Position?, - val orientation: Int, - val ecLevel: String?, - val symbologyIdentifier: String?, - val sequenceSize: Int, - val sequenceIndex: Int, - val sequenceId: String, - val readerInit: Boolean, - val lineCount: Int - ) - - public fun read(image: ImageProxy, decodeHints: DecodeHints): List? { - check(image.format in supportedYUVFormats) { - "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" - } - - return readYBuffer( - image.planes[0].buffer, - image.planes[0].rowStride, - image.cropRect.left, - image.cropRect.top, - image.cropRect.width(), - image.cropRect.height(), - image.imageInfo.rotationDegrees, - decodeHints - ) - } - - public fun read(bitmap: Bitmap, decodeHints: DecodeHints, cropRect: Rect = Rect(), rotation: Int = 0): List? { - return readBitmap( - bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, - decodeHints - ) - } - - private external fun readYBuffer( - yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, - decodeHints: DecodeHints - ): List? - - private external fun readBitmap( - bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, - decodeHints: DecodeHints - ): List? + private val supportedYUVFormats: List = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) + } else { + listOf(ImageFormat.YUV_420_888) + } + + init { + System.loadLibrary("zxing_android") + } + + // Enumerates barcode formats known to this package. + // Note that this has to be kept synchronized with native (C++/JNI) side. + public enum class Format { + NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, + DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E + } + + public enum class ContentType { + TEXT, BINARY, MIXED, GS1, ISO15434, UNKNOWN_ECI + } + + public enum class Binarizer { + LOCAL_AVERAGE, GLOBAL_HISTOGRAM, FIXED_THRESHOLD, BOOL_CAST + } + + public enum class EanAddOnSymbol { + IGNORE, READ, REQUIRE + } + + public enum class TextMode { + PLAIN, ECI, HRI, HEX, ESCAPED + } + + public data class DecodeHints( + val formats: Set = setOf(), + val tryHarder: Boolean = false, + val tryRotate: Boolean = false, + val tryInvert: Boolean = false, + val tryDownscale: Boolean = false, + val isPure: Boolean = false, + val tryCode39ExtendedMode: Boolean = false, + val validateCode39CheckSum: Boolean = false, + val validateITFCheckSum: Boolean = false, + val returnCodabarStartEnd: Boolean = false, + val returnErrors: Boolean = false, + val downscaleFactor: Int = 3, + val eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, + val binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, + val textMode: TextMode = TextMode.HRI, + val minLineCount: Int = 2, + val maxNumberOfSymbols: Int = 0xff, + val downscaleThreshold: Int = 500 + ) + + public data class Position( + val topLeft: Point, + val topRight: Point, + val bottomLeft: Point, + val bottomRight: Point, + val orientation: Double + ) + + public data class Result( + val format: Format, + val bytes: ByteArray?, + val text: String?, + val time: String?, // for development/debug purposes only + val contentType: ContentType, + val position: Position?, + val orientation: Int, + val ecLevel: String?, + val symbologyIdentifier: String?, + val sequenceSize: Int, + val sequenceIndex: Int, + val sequenceId: String, + val readerInit: Boolean, + val lineCount: Int + ) + + public fun read(image: ImageProxy, decodeHints: DecodeHints): List? { + check(image.format in supportedYUVFormats) { + "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" + } + + return readYBuffer( + image.planes[0].buffer, + image.planes[0].rowStride, + image.cropRect.left, + image.cropRect.top, + image.cropRect.width(), + image.cropRect.height(), + image.imageInfo.rotationDegrees, + decodeHints + ) + } + + public fun read( + bitmap: Bitmap, + decodeHints: DecodeHints, + cropRect: Rect = Rect(), + rotation: Int = 0 + ): List? { + return readBitmap( + bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, + decodeHints + ) + } + + private external fun readYBuffer( + yBuffer: ByteBuffer, + rowStride: Int, + left: Int, + top: Int, + width: Int, + height: Int, + rotation: Int, + decodeHints: DecodeHints + ): List? + + private external fun readBitmap( + bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, + decodeHints: DecodeHints + ): List? } From 44d898f0a7f67d6626c9323be7f39b775e28b41a Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 10 Nov 2023 13:39:25 +0100 Subject: [PATCH 079/587] android: convert source files from dos to unix Replace CR/LF by just LF, to match the rest of this project. --- .../com/example/zxingcppdemo/MainActivity.kt | 738 +++++++++--------- .../example/zxingcppdemo/PreviewOverlay.kt | 188 ++--- .../src/main/java/com/zxingcpp/ZXingCpp.kt | 304 ++++---- 3 files changed, 615 insertions(+), 615 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index f3114cda1b..19ef6382c0 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -1,369 +1,369 @@ -/* -* Copyright 2021 Axel Waggershauser -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.example.zxingcppdemo - -import android.Manifest -import android.content.Context -import android.content.pm.PackageManager -import android.graphics.ImageFormat -import android.graphics.PointF -import android.graphics.Rect -import android.graphics.YuvImage -import android.hardware.camera2.CaptureRequest -import android.media.AudioManager -import android.media.MediaActionSound -import android.media.ToneGenerator -import android.os.Build -import android.os.Bundle -import android.os.Environment -import android.view.View -import androidx.appcompat.app.AppCompatActivity -import androidx.camera.camera2.interop.Camera2CameraControl -import androidx.camera.camera2.interop.CaptureRequestOptions -import androidx.camera.core.AspectRatio -import androidx.camera.core.CameraSelector -import androidx.camera.core.ImageAnalysis -import androidx.camera.core.ImageProxy -import androidx.camera.core.Preview -import androidx.camera.lifecycle.ProcessCameraProvider -import androidx.core.app.ActivityCompat -import androidx.core.content.ContextCompat -import androidx.core.graphics.toPointF -import androidx.lifecycle.LifecycleOwner -import com.example.zxingcppdemo.databinding.ActivityCameraBinding -import com.google.zxing.BarcodeFormat -import com.google.zxing.BinaryBitmap -import com.google.zxing.DecodeHintType -import com.google.zxing.MultiFormatReader -import com.google.zxing.PlanarYUVLuminanceSource -import com.google.zxing.common.HybridBinarizer -import com.zxingcpp.ZXingCpp -import com.zxingcpp.ZXingCpp.Format -import java.io.ByteArrayOutputStream -import java.io.File -import java.util.concurrent.Executors - - -class MainActivity : AppCompatActivity() { - private lateinit var binding: ActivityCameraBinding - - private val executor = Executors.newSingleThreadExecutor() - private val permissions = mutableListOf(Manifest.permission.CAMERA) - private val permissionsRequestCode = 1 - - private val beeper = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 50) - private var lastText = String() - private var doSaveImage: Boolean = false - - init { - // On R or higher, this permission has no effect. See: - // https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { - permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) - } - } - - override fun onCreate(savedInstanceState: Bundle?) { - super.onCreate(savedInstanceState) - binding = ActivityCameraBinding.inflate(layoutInflater) - setContentView(binding.root) - - binding.capture.setOnClickListener { - // Disable all camera controls - it.isEnabled = false - doSaveImage = true - // Re-enable camera controls - it.isEnabled = true - } - } - - private fun ImageProxy.toJpeg(): ByteArray { - //This converts the ImageProxy (from the imageAnalysis Use Case) - //to a ByteArray (compressed as JPEG) for then to be saved for debugging purposes - //This is the closest representation of the image that is passed to the - //decoding algorithm. - - val yBuffer = planes[0].buffer // Y - val vuBuffer = planes[2].buffer // VU - - val ySize = yBuffer.remaining() - val vuSize = vuBuffer.remaining() - - val nv21 = ByteArray(ySize + vuSize) - - yBuffer.get(nv21, 0, ySize) - vuBuffer.get(nv21, ySize, vuSize) - - val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null) - val out = ByteArrayOutputStream() - yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 90, out) - return out.toByteArray() - } - - private fun saveImage(image: ImageProxy) { - try { - val currentMillis = System.currentTimeMillis().toString() - val filename = - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - .toString() + "/" + currentMillis + "_ZXingCpp.jpg" - - File(filename).outputStream().use { out -> - out.write(image.toJpeg()) - } - MediaActionSound().play(MediaActionSound.SHUTTER_CLICK) - } catch (e: Exception) { - beeper.startTone(ToneGenerator.TONE_CDMA_SOFT_ERROR_LITE) //Fail Tone - } - } - - private fun bindCameraUseCases() = binding.viewFinder.post { - - val cameraProviderFuture = ProcessCameraProvider.getInstance(this) - cameraProviderFuture.addListener({ - -// val size = Size(1600, 1200) - - // Set up the view finder use case to display camera preview - val preview = Preview.Builder() - .setTargetAspectRatio(AspectRatio.RATIO_16_9) -// .setTargetResolution(size) - .build() - - // Set up the image analysis use case which will process frames in real time - val imageAnalysis = ImageAnalysis.Builder() - .setTargetAspectRatio(AspectRatio.RATIO_16_9) // -> 1280x720 -// .setTargetResolution(size) - .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) - .build() - - var frameCounter = 0 - var lastFpsTimestamp = System.currentTimeMillis() - var runtimes: Long = 0 - var runtime2: Long = 0 - val readerJava = MultiFormatReader() - - // Create a new camera selector each time, enforcing lens facing - val cameraSelector = - CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() - - // Camera provider is now guaranteed to be available - val cameraProvider = cameraProviderFuture.get() - - // Apply declared configs to CameraX using the same lifecycle owner - cameraProvider.unbindAll() - val camera = cameraProvider.bindToLifecycle( - this as LifecycleOwner, cameraSelector, preview, imageAnalysis - ) - - // Reduce exposure time to decrease effect of motion blur - val camera2 = Camera2CameraControl.from(camera.cameraControl) - camera2.captureRequestOptions = CaptureRequestOptions.Builder() - .setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, 1600) - .setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, -8) - .build() - - // Use the camera object to link our preview use case with the view - preview.setSurfaceProvider(binding.viewFinder.surfaceProvider) - - imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { image -> - // Early exit: image analysis is in paused state - if (binding.pause.isChecked) { - image.close() - return@Analyzer - } - - if (doSaveImage) { - doSaveImage = false - saveImage(image) - } - - val cropSize = image.height / 3 * 2 - val cropRect = if (binding.crop.isChecked) - Rect( - (image.width - cropSize) / 2, - (image.height - cropSize) / 2, - (image.width - cropSize) / 2 + cropSize, - (image.height - cropSize) / 2 + cropSize - ) - else - Rect(0, 0, image.width, image.height) - image.setCropRect(cropRect) - - val startTime = System.currentTimeMillis() - var resultText: String - val resultPoints = mutableListOf>() - - if (binding.java.isChecked) { - val yPlane = image.planes[0] - val yBuffer = yPlane.buffer - val yStride = yPlane.rowStride - val data = ByteArray(yBuffer.remaining()) - yBuffer.get(data, 0, data.size) - image.close() - val hints = mutableMapOf() - if (binding.qrcode.isChecked) - hints[DecodeHintType.POSSIBLE_FORMATS] = arrayListOf(BarcodeFormat.QR_CODE) - if (binding.tryHarder.isChecked) - hints[DecodeHintType.TRY_HARDER] = true - - resultText = try { - val bitmap = BinaryBitmap( - HybridBinarizer( - PlanarYUVLuminanceSource( - data, - yStride, - image.height, - cropRect.left, - cropRect.top, - cropRect.width(), - cropRect.height(), - false - ) - ) - ) - val result = readerJava.decode(bitmap, hints) - result?.let { "${it.barcodeFormat}: ${it.text}" } ?: "" - } catch (e: Throwable) { - if (e.toString() != "com.google.zxing.NotFoundException") e.toString() else "" - } - } else { - val options = ZXingCpp.DecodeHints( - formats = if (binding.qrcode.isChecked) setOf(Format.QR_CODE) else setOf(), - tryHarder = binding.tryHarder.isChecked, - tryRotate = binding.tryRotate.isChecked, - tryInvert = binding.tryInvert.isChecked, - tryDownscale = binding.tryDownscale.isChecked - ) - - resultText = try { - image.use { - ZXingCpp.read(it, options) - }?.apply { - runtime2 += this[0].time?.toInt() ?: 0 - }?.joinToString("\n") { result -> - result.position?.let { - resultPoints.add( - listOf( - it.topLeft, - it.topRight, - it.bottomRight, - it.bottomLeft - ).map { p -> - p.toPointF() - } - ) - } - "${result.format} (${result.contentType}): ${ - if (result.contentType != ZXingCpp.ContentType.BINARY) { - result.text - } else { - result.bytes!!.joinToString(separator = "") { v -> - "%02x".format( - v - ) - } - } - }" - } ?: "" - } catch (e: Throwable) { - e.message ?: "Error" - } - } - - runtimes += System.currentTimeMillis() - startTime - - var infoText: String? = null - if (++frameCounter == 15) { - val now = System.currentTimeMillis() - val fps = 1000 * frameCounter.toDouble() / (now - lastFpsTimestamp) - - infoText = "Time: %2d/%2d ms, FPS: %.02f, (%dx%d)" - .format( - runtimes / frameCounter, - runtime2 / frameCounter, - fps, - image.width, - image.height - ) - lastFpsTimestamp = now - frameCounter = 0 - runtimes = 0 - runtime2 = 0 - } - - camera.cameraControl.enableTorch(binding.torch.isChecked) - - showResult(resultText, infoText, resultPoints, image) - }) - - }, ContextCompat.getMainExecutor(this)) - } - - private fun showResult( - resultText: String, - fpsText: String?, - points: List>, - image: ImageProxy - ) = - binding.viewFinder.post { - // Update the text and UI - binding.result.text = resultText - binding.result.visibility = View.VISIBLE - - binding.overlay.update(binding.viewFinder, image, points) - - if (fpsText != null) - binding.fps.text = fpsText - - if (resultText.isNotEmpty() && lastText != resultText) { - lastText = resultText - beeper.startTone(ToneGenerator.TONE_PROP_BEEP) - } - } - - override fun onResume() { - super.onResume() - - // Request permissions each time the app resumes, since they can be revoked at any time - if (!hasPermissions(this)) { - ActivityCompat.requestPermissions( - this, - permissions.toTypedArray(), - permissionsRequestCode - ) - } else { - bindCameraUseCases() - } - } - - override fun onRequestPermissionsResult( - requestCode: Int, - permissions: Array, - grantResults: IntArray, - ) { - super.onRequestPermissionsResult(requestCode, permissions, grantResults) - if (requestCode == permissionsRequestCode && hasPermissions(this)) { - bindCameraUseCases() - } else { - finish() // If we don't have the required permissions, we can't run - } - } - - private fun hasPermissions(context: Context) = permissions.all { - ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED - } -} +/* +* Copyright 2021 Axel Waggershauser +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.example.zxingcppdemo + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.graphics.ImageFormat +import android.graphics.PointF +import android.graphics.Rect +import android.graphics.YuvImage +import android.hardware.camera2.CaptureRequest +import android.media.AudioManager +import android.media.MediaActionSound +import android.media.ToneGenerator +import android.os.Build +import android.os.Bundle +import android.os.Environment +import android.view.View +import androidx.appcompat.app.AppCompatActivity +import androidx.camera.camera2.interop.Camera2CameraControl +import androidx.camera.camera2.interop.CaptureRequestOptions +import androidx.camera.core.AspectRatio +import androidx.camera.core.CameraSelector +import androidx.camera.core.ImageAnalysis +import androidx.camera.core.ImageProxy +import androidx.camera.core.Preview +import androidx.camera.lifecycle.ProcessCameraProvider +import androidx.core.app.ActivityCompat +import androidx.core.content.ContextCompat +import androidx.core.graphics.toPointF +import androidx.lifecycle.LifecycleOwner +import com.example.zxingcppdemo.databinding.ActivityCameraBinding +import com.google.zxing.BarcodeFormat +import com.google.zxing.BinaryBitmap +import com.google.zxing.DecodeHintType +import com.google.zxing.MultiFormatReader +import com.google.zxing.PlanarYUVLuminanceSource +import com.google.zxing.common.HybridBinarizer +import com.zxingcpp.ZXingCpp +import com.zxingcpp.ZXingCpp.Format +import java.io.ByteArrayOutputStream +import java.io.File +import java.util.concurrent.Executors + + +class MainActivity : AppCompatActivity() { + private lateinit var binding: ActivityCameraBinding + + private val executor = Executors.newSingleThreadExecutor() + private val permissions = mutableListOf(Manifest.permission.CAMERA) + private val permissionsRequestCode = 1 + + private val beeper = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 50) + private var lastText = String() + private var doSaveImage: Boolean = false + + init { + // On R or higher, this permission has no effect. See: + // https://developer.android.com/reference/android/Manifest.permission#WRITE_EXTERNAL_STORAGE + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R) { + permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE) + } + } + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + binding = ActivityCameraBinding.inflate(layoutInflater) + setContentView(binding.root) + + binding.capture.setOnClickListener { + // Disable all camera controls + it.isEnabled = false + doSaveImage = true + // Re-enable camera controls + it.isEnabled = true + } + } + + private fun ImageProxy.toJpeg(): ByteArray { + //This converts the ImageProxy (from the imageAnalysis Use Case) + //to a ByteArray (compressed as JPEG) for then to be saved for debugging purposes + //This is the closest representation of the image that is passed to the + //decoding algorithm. + + val yBuffer = planes[0].buffer // Y + val vuBuffer = planes[2].buffer // VU + + val ySize = yBuffer.remaining() + val vuSize = vuBuffer.remaining() + + val nv21 = ByteArray(ySize + vuSize) + + yBuffer.get(nv21, 0, ySize) + vuBuffer.get(nv21, ySize, vuSize) + + val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null) + val out = ByteArrayOutputStream() + yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 90, out) + return out.toByteArray() + } + + private fun saveImage(image: ImageProxy) { + try { + val currentMillis = System.currentTimeMillis().toString() + val filename = + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .toString() + "/" + currentMillis + "_ZXingCpp.jpg" + + File(filename).outputStream().use { out -> + out.write(image.toJpeg()) + } + MediaActionSound().play(MediaActionSound.SHUTTER_CLICK) + } catch (e: Exception) { + beeper.startTone(ToneGenerator.TONE_CDMA_SOFT_ERROR_LITE) //Fail Tone + } + } + + private fun bindCameraUseCases() = binding.viewFinder.post { + + val cameraProviderFuture = ProcessCameraProvider.getInstance(this) + cameraProviderFuture.addListener({ + +// val size = Size(1600, 1200) + + // Set up the view finder use case to display camera preview + val preview = Preview.Builder() + .setTargetAspectRatio(AspectRatio.RATIO_16_9) +// .setTargetResolution(size) + .build() + + // Set up the image analysis use case which will process frames in real time + val imageAnalysis = ImageAnalysis.Builder() + .setTargetAspectRatio(AspectRatio.RATIO_16_9) // -> 1280x720 +// .setTargetResolution(size) + .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) + .build() + + var frameCounter = 0 + var lastFpsTimestamp = System.currentTimeMillis() + var runtimes: Long = 0 + var runtime2: Long = 0 + val readerJava = MultiFormatReader() + + // Create a new camera selector each time, enforcing lens facing + val cameraSelector = + CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() + + // Camera provider is now guaranteed to be available + val cameraProvider = cameraProviderFuture.get() + + // Apply declared configs to CameraX using the same lifecycle owner + cameraProvider.unbindAll() + val camera = cameraProvider.bindToLifecycle( + this as LifecycleOwner, cameraSelector, preview, imageAnalysis + ) + + // Reduce exposure time to decrease effect of motion blur + val camera2 = Camera2CameraControl.from(camera.cameraControl) + camera2.captureRequestOptions = CaptureRequestOptions.Builder() + .setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, 1600) + .setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, -8) + .build() + + // Use the camera object to link our preview use case with the view + preview.setSurfaceProvider(binding.viewFinder.surfaceProvider) + + imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { image -> + // Early exit: image analysis is in paused state + if (binding.pause.isChecked) { + image.close() + return@Analyzer + } + + if (doSaveImage) { + doSaveImage = false + saveImage(image) + } + + val cropSize = image.height / 3 * 2 + val cropRect = if (binding.crop.isChecked) + Rect( + (image.width - cropSize) / 2, + (image.height - cropSize) / 2, + (image.width - cropSize) / 2 + cropSize, + (image.height - cropSize) / 2 + cropSize + ) + else + Rect(0, 0, image.width, image.height) + image.setCropRect(cropRect) + + val startTime = System.currentTimeMillis() + var resultText: String + val resultPoints = mutableListOf>() + + if (binding.java.isChecked) { + val yPlane = image.planes[0] + val yBuffer = yPlane.buffer + val yStride = yPlane.rowStride + val data = ByteArray(yBuffer.remaining()) + yBuffer.get(data, 0, data.size) + image.close() + val hints = mutableMapOf() + if (binding.qrcode.isChecked) + hints[DecodeHintType.POSSIBLE_FORMATS] = arrayListOf(BarcodeFormat.QR_CODE) + if (binding.tryHarder.isChecked) + hints[DecodeHintType.TRY_HARDER] = true + + resultText = try { + val bitmap = BinaryBitmap( + HybridBinarizer( + PlanarYUVLuminanceSource( + data, + yStride, + image.height, + cropRect.left, + cropRect.top, + cropRect.width(), + cropRect.height(), + false + ) + ) + ) + val result = readerJava.decode(bitmap, hints) + result?.let { "${it.barcodeFormat}: ${it.text}" } ?: "" + } catch (e: Throwable) { + if (e.toString() != "com.google.zxing.NotFoundException") e.toString() else "" + } + } else { + val options = ZXingCpp.DecodeHints( + formats = if (binding.qrcode.isChecked) setOf(Format.QR_CODE) else setOf(), + tryHarder = binding.tryHarder.isChecked, + tryRotate = binding.tryRotate.isChecked, + tryInvert = binding.tryInvert.isChecked, + tryDownscale = binding.tryDownscale.isChecked + ) + + resultText = try { + image.use { + ZXingCpp.read(it, options) + }?.apply { + runtime2 += this[0].time?.toInt() ?: 0 + }?.joinToString("\n") { result -> + result.position?.let { + resultPoints.add( + listOf( + it.topLeft, + it.topRight, + it.bottomRight, + it.bottomLeft + ).map { p -> + p.toPointF() + } + ) + } + "${result.format} (${result.contentType}): ${ + if (result.contentType != ZXingCpp.ContentType.BINARY) { + result.text + } else { + result.bytes!!.joinToString(separator = "") { v -> + "%02x".format( + v + ) + } + } + }" + } ?: "" + } catch (e: Throwable) { + e.message ?: "Error" + } + } + + runtimes += System.currentTimeMillis() - startTime + + var infoText: String? = null + if (++frameCounter == 15) { + val now = System.currentTimeMillis() + val fps = 1000 * frameCounter.toDouble() / (now - lastFpsTimestamp) + + infoText = "Time: %2d/%2d ms, FPS: %.02f, (%dx%d)" + .format( + runtimes / frameCounter, + runtime2 / frameCounter, + fps, + image.width, + image.height + ) + lastFpsTimestamp = now + frameCounter = 0 + runtimes = 0 + runtime2 = 0 + } + + camera.cameraControl.enableTorch(binding.torch.isChecked) + + showResult(resultText, infoText, resultPoints, image) + }) + + }, ContextCompat.getMainExecutor(this)) + } + + private fun showResult( + resultText: String, + fpsText: String?, + points: List>, + image: ImageProxy + ) = + binding.viewFinder.post { + // Update the text and UI + binding.result.text = resultText + binding.result.visibility = View.VISIBLE + + binding.overlay.update(binding.viewFinder, image, points) + + if (fpsText != null) + binding.fps.text = fpsText + + if (resultText.isNotEmpty() && lastText != resultText) { + lastText = resultText + beeper.startTone(ToneGenerator.TONE_PROP_BEEP) + } + } + + override fun onResume() { + super.onResume() + + // Request permissions each time the app resumes, since they can be revoked at any time + if (!hasPermissions(this)) { + ActivityCompat.requestPermissions( + this, + permissions.toTypedArray(), + permissionsRequestCode + ) + } else { + bindCameraUseCases() + } + } + + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray, + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (requestCode == permissionsRequestCode && hasPermissions(this)) { + bindCameraUseCases() + } else { + finish() // If we don't have the required permissions, we can't run + } + } + + private fun hasPermissions(context: Context) = permissions.all { + ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED + } +} diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt index 293ed37fed..8a79cb117f 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt @@ -1,94 +1,94 @@ -/* -* Copyright 2022 Axel Waggershauser -* Copyright 2022 Markus Fisch -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.example.zxingcppdemo - -import android.content.Context -import android.graphics.Canvas -import android.graphics.Paint -import android.graphics.Path -import android.graphics.PointF -import android.graphics.Rect -import android.util.AttributeSet -import android.view.View -import androidx.camera.core.ImageProxy -import kotlin.math.max -import kotlin.math.min - -class PreviewOverlay constructor(context: Context, attributeSet: AttributeSet?) : - View(context, attributeSet) { - - private val paintPath = Paint(Paint.ANTI_ALIAS_FLAG).apply { - style = Paint.Style.STROKE - color = 0xff00ff00.toInt() - strokeWidth = 2 * context.resources.displayMetrics.density - } - private val paintRect = Paint().apply { - style = Paint.Style.STROKE - color = 0x80ffffff.toInt() - strokeWidth = 3 * context.resources.displayMetrics.density - } - private val path = Path() - private var cropRect = Rect() - private var rotation = 0 - private var s: Float = 0f - private var o: Float = 0f - - fun update(viewFinder: View, image: ImageProxy, points: List>) { - cropRect = image.cropRect - rotation = image.imageInfo.rotationDegrees - s = min(viewFinder.width, viewFinder.height).toFloat() / image.height - o = (max(viewFinder.width, viewFinder.height) - (image.width * s).toInt()).toFloat() / 2 - - path.apply { - rewind() - points.forEach { - if (!it.isEmpty()) { - val last = it.last() - moveTo(last.x, last.y) - for (p in it) { - lineTo(p.x, p.y) - } - } - } - } - invalidate() - } - - override fun onDraw(canvas: Canvas) { - canvas.apply { - // draw the cropRect, which is relative to the original image orientation - save() - if (rotation == 90) { - translate(width.toFloat(), 0f) - rotate(rotation.toFloat()) - } - translate(o, 0f) - scale(s, s) - drawRect(cropRect, paintRect) - restore() - - // draw the path, which is relative to the (centered) rotated cropRect - when (rotation) { - 0, 180 -> translate(o + cropRect.left * s, cropRect.top * s) - 90 -> translate(cropRect.top * s, o + cropRect.left * s) - } - scale(s, s) - drawPath(path, paintPath) - } - } -} +/* +* Copyright 2022 Axel Waggershauser +* Copyright 2022 Markus Fisch +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.example.zxingcppdemo + +import android.content.Context +import android.graphics.Canvas +import android.graphics.Paint +import android.graphics.Path +import android.graphics.PointF +import android.graphics.Rect +import android.util.AttributeSet +import android.view.View +import androidx.camera.core.ImageProxy +import kotlin.math.max +import kotlin.math.min + +class PreviewOverlay constructor(context: Context, attributeSet: AttributeSet?) : + View(context, attributeSet) { + + private val paintPath = Paint(Paint.ANTI_ALIAS_FLAG).apply { + style = Paint.Style.STROKE + color = 0xff00ff00.toInt() + strokeWidth = 2 * context.resources.displayMetrics.density + } + private val paintRect = Paint().apply { + style = Paint.Style.STROKE + color = 0x80ffffff.toInt() + strokeWidth = 3 * context.resources.displayMetrics.density + } + private val path = Path() + private var cropRect = Rect() + private var rotation = 0 + private var s: Float = 0f + private var o: Float = 0f + + fun update(viewFinder: View, image: ImageProxy, points: List>) { + cropRect = image.cropRect + rotation = image.imageInfo.rotationDegrees + s = min(viewFinder.width, viewFinder.height).toFloat() / image.height + o = (max(viewFinder.width, viewFinder.height) - (image.width * s).toInt()).toFloat() / 2 + + path.apply { + rewind() + points.forEach { + if (!it.isEmpty()) { + val last = it.last() + moveTo(last.x, last.y) + for (p in it) { + lineTo(p.x, p.y) + } + } + } + } + invalidate() + } + + override fun onDraw(canvas: Canvas) { + canvas.apply { + // draw the cropRect, which is relative to the original image orientation + save() + if (rotation == 90) { + translate(width.toFloat(), 0f) + rotate(rotation.toFloat()) + } + translate(o, 0f) + scale(s, s) + drawRect(cropRect, paintRect) + restore() + + // draw the path, which is relative to the (centered) rotated cropRect + when (rotation) { + 0, 180 -> translate(o + cropRect.left * s, cropRect.top * s) + 90 -> translate(cropRect.top * s, o + cropRect.left * s) + } + scale(s, s) + drawPath(path, paintPath) + } + } +} diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index a3cc607ad4..884450aa87 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -1,152 +1,152 @@ -/* -* Copyright 2021 Axel Waggershauser -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. -*/ - -package com.zxingcpp - -import android.graphics.Bitmap -import android.graphics.ImageFormat -import android.graphics.Point -import android.graphics.Rect -import android.os.Build -import androidx.camera.core.ImageProxy -import java.nio.ByteBuffer - -public object ZXingCpp { - private val supportedYUVFormats: List = - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) - } else { - listOf(ImageFormat.YUV_420_888) - } - - init { - System.loadLibrary("zxing_android") - } - - // Enumerates barcode formats known to this package. - // Note that this has to be kept synchronized with native (C++/JNI) side. - public enum class Format { - NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, - DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E - } - - public enum class ContentType { - TEXT, BINARY, MIXED, GS1, ISO15434, UNKNOWN_ECI - } - - public enum class Binarizer { - LOCAL_AVERAGE, GLOBAL_HISTOGRAM, FIXED_THRESHOLD, BOOL_CAST - } - - public enum class EanAddOnSymbol { - IGNORE, READ, REQUIRE - } - - public enum class TextMode { - PLAIN, ECI, HRI, HEX, ESCAPED - } - - public data class DecodeHints( - val formats: Set = setOf(), - val tryHarder: Boolean = false, - val tryRotate: Boolean = false, - val tryInvert: Boolean = false, - val tryDownscale: Boolean = false, - val isPure: Boolean = false, - val tryCode39ExtendedMode: Boolean = false, - val validateCode39CheckSum: Boolean = false, - val validateITFCheckSum: Boolean = false, - val returnCodabarStartEnd: Boolean = false, - val returnErrors: Boolean = false, - val downscaleFactor: Int = 3, - val eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, - val binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, - val textMode: TextMode = TextMode.HRI, - val minLineCount: Int = 2, - val maxNumberOfSymbols: Int = 0xff, - val downscaleThreshold: Int = 500 - ) - - public data class Position( - val topLeft: Point, - val topRight: Point, - val bottomLeft: Point, - val bottomRight: Point, - val orientation: Double - ) - - public data class Result( - val format: Format, - val bytes: ByteArray?, - val text: String?, - val time: String?, // for development/debug purposes only - val contentType: ContentType, - val position: Position?, - val orientation: Int, - val ecLevel: String?, - val symbologyIdentifier: String?, - val sequenceSize: Int, - val sequenceIndex: Int, - val sequenceId: String, - val readerInit: Boolean, - val lineCount: Int - ) - - public fun read(image: ImageProxy, decodeHints: DecodeHints): List? { - check(image.format in supportedYUVFormats) { - "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" - } - - return readYBuffer( - image.planes[0].buffer, - image.planes[0].rowStride, - image.cropRect.left, - image.cropRect.top, - image.cropRect.width(), - image.cropRect.height(), - image.imageInfo.rotationDegrees, - decodeHints - ) - } - - public fun read( - bitmap: Bitmap, - decodeHints: DecodeHints, - cropRect: Rect = Rect(), - rotation: Int = 0 - ): List? { - return readBitmap( - bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, - decodeHints - ) - } - - private external fun readYBuffer( - yBuffer: ByteBuffer, - rowStride: Int, - left: Int, - top: Int, - width: Int, - height: Int, - rotation: Int, - decodeHints: DecodeHints - ): List? - - private external fun readBitmap( - bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, - decodeHints: DecodeHints - ): List? -} +/* +* Copyright 2021 Axel Waggershauser +* +* Licensed under the Apache License, Version 2.0 (the "License"); +* you may not use this file except in compliance with the License. +* You may obtain a copy of the License at +* +* http://www.apache.org/licenses/LICENSE-2.0 +* +* Unless required by applicable law or agreed to in writing, software +* distributed under the License is distributed on an "AS IS" BASIS, +* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +* See the License for the specific language governing permissions and +* limitations under the License. +*/ + +package com.zxingcpp + +import android.graphics.Bitmap +import android.graphics.ImageFormat +import android.graphics.Point +import android.graphics.Rect +import android.os.Build +import androidx.camera.core.ImageProxy +import java.nio.ByteBuffer + +public object ZXingCpp { + private val supportedYUVFormats: List = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) + } else { + listOf(ImageFormat.YUV_420_888) + } + + init { + System.loadLibrary("zxing_android") + } + + // Enumerates barcode formats known to this package. + // Note that this has to be kept synchronized with native (C++/JNI) side. + public enum class Format { + NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, + DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E + } + + public enum class ContentType { + TEXT, BINARY, MIXED, GS1, ISO15434, UNKNOWN_ECI + } + + public enum class Binarizer { + LOCAL_AVERAGE, GLOBAL_HISTOGRAM, FIXED_THRESHOLD, BOOL_CAST + } + + public enum class EanAddOnSymbol { + IGNORE, READ, REQUIRE + } + + public enum class TextMode { + PLAIN, ECI, HRI, HEX, ESCAPED + } + + public data class DecodeHints( + val formats: Set = setOf(), + val tryHarder: Boolean = false, + val tryRotate: Boolean = false, + val tryInvert: Boolean = false, + val tryDownscale: Boolean = false, + val isPure: Boolean = false, + val tryCode39ExtendedMode: Boolean = false, + val validateCode39CheckSum: Boolean = false, + val validateITFCheckSum: Boolean = false, + val returnCodabarStartEnd: Boolean = false, + val returnErrors: Boolean = false, + val downscaleFactor: Int = 3, + val eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, + val binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, + val textMode: TextMode = TextMode.HRI, + val minLineCount: Int = 2, + val maxNumberOfSymbols: Int = 0xff, + val downscaleThreshold: Int = 500 + ) + + public data class Position( + val topLeft: Point, + val topRight: Point, + val bottomLeft: Point, + val bottomRight: Point, + val orientation: Double + ) + + public data class Result( + val format: Format, + val bytes: ByteArray?, + val text: String?, + val time: String?, // for development/debug purposes only + val contentType: ContentType, + val position: Position?, + val orientation: Int, + val ecLevel: String?, + val symbologyIdentifier: String?, + val sequenceSize: Int, + val sequenceIndex: Int, + val sequenceId: String, + val readerInit: Boolean, + val lineCount: Int + ) + + public fun read(image: ImageProxy, decodeHints: DecodeHints): List? { + check(image.format in supportedYUVFormats) { + "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" + } + + return readYBuffer( + image.planes[0].buffer, + image.planes[0].rowStride, + image.cropRect.left, + image.cropRect.top, + image.cropRect.width(), + image.cropRect.height(), + image.imageInfo.rotationDegrees, + decodeHints + ) + } + + public fun read( + bitmap: Bitmap, + decodeHints: DecodeHints, + cropRect: Rect = Rect(), + rotation: Int = 0 + ): List? { + return readBitmap( + bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, + decodeHints + ) + } + + private external fun readYBuffer( + yBuffer: ByteBuffer, + rowStride: Int, + left: Int, + top: Int, + width: Int, + height: Int, + rotation: Int, + decodeHints: DecodeHints + ): List? + + private external fun readBitmap( + bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, + decodeHints: DecodeHints + ): List? +} From 98c4b00e4e5cfa552aee7dc32fb87c7ae95cb4ef Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 10 Nov 2023 13:45:42 +0100 Subject: [PATCH 080/587] android: match comment style Add a space between slash and the first word like it's the case with every other comment around here. --- .../main/java/com/example/zxingcppdemo/MainActivity.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 19ef6382c0..6c14792fd1 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -92,10 +92,10 @@ class MainActivity : AppCompatActivity() { } private fun ImageProxy.toJpeg(): ByteArray { - //This converts the ImageProxy (from the imageAnalysis Use Case) - //to a ByteArray (compressed as JPEG) for then to be saved for debugging purposes - //This is the closest representation of the image that is passed to the - //decoding algorithm. + // This converts the ImageProxy (from the imageAnalysis Use Case) + // to a ByteArray (compressed as JPEG) for then to be saved for debugging purposes + // This is the closest representation of the image that is passed to the + // decoding algorithm. val yBuffer = planes[0].buffer // Y val vuBuffer = planes[2].buffer // VU From 2d08929c920446e80cfcae32ff966f972267d254 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 10 Nov 2023 13:47:45 +0100 Subject: [PATCH 081/587] android: remove commented out code from sample Focus on the essentials. --- .../src/main/java/com/example/zxingcppdemo/MainActivity.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 6c14792fd1..0553a5cbc0 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -135,18 +135,14 @@ class MainActivity : AppCompatActivity() { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) cameraProviderFuture.addListener({ -// val size = Size(1600, 1200) - // Set up the view finder use case to display camera preview val preview = Preview.Builder() .setTargetAspectRatio(AspectRatio.RATIO_16_9) -// .setTargetResolution(size) .build() // Set up the image analysis use case which will process frames in real time val imageAnalysis = ImageAnalysis.Builder() .setTargetAspectRatio(AspectRatio.RATIO_16_9) // -> 1280x720 -// .setTargetResolution(size) .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() From 1234ced4e4feaed6713ffe2c40f04c34a59b5918 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 10 Nov 2023 16:17:09 +0100 Subject: [PATCH 082/587] android: group beeper with other immutables If that blank line between the immutable and mutable fields is supposed to mean anything, `beeper` belongs to the first group as it is immutable. --- .../app/src/main/java/com/example/zxingcppdemo/MainActivity.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 0553a5cbc0..b5dbba1b46 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -64,8 +64,8 @@ class MainActivity : AppCompatActivity() { private val executor = Executors.newSingleThreadExecutor() private val permissions = mutableListOf(Manifest.permission.CAMERA) private val permissionsRequestCode = 1 - private val beeper = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 50) + private var lastText = String() private var doSaveImage: Boolean = false From 3451f7bf6a03a076a1d9798d09b27f585eaa5c85 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 10 Nov 2023 16:21:19 +0100 Subject: [PATCH 083/587] android: make properties of DecodeHints mutable So DecodeHints can be reused between scans and does not need to be allocated again. This is of advantage when using `read()` with a video stream because it avoids allocation in the hot path. --- .../com/example/zxingcppdemo/MainActivity.kt | 15 ++++---- .../src/main/java/com/zxingcpp/ZXingCpp.kt | 36 +++++++++---------- 2 files changed, 26 insertions(+), 25 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index b5dbba1b46..fd816adf25 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -65,6 +65,7 @@ class MainActivity : AppCompatActivity() { private val permissions = mutableListOf(Manifest.permission.CAMERA) private val permissionsRequestCode = 1 private val beeper = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 50) + private val decodeHints = ZXingCpp.DecodeHints() private var lastText = String() private var doSaveImage: Boolean = false @@ -237,17 +238,17 @@ class MainActivity : AppCompatActivity() { if (e.toString() != "com.google.zxing.NotFoundException") e.toString() else "" } } else { - val options = ZXingCpp.DecodeHints( - formats = if (binding.qrcode.isChecked) setOf(Format.QR_CODE) else setOf(), - tryHarder = binding.tryHarder.isChecked, - tryRotate = binding.tryRotate.isChecked, - tryInvert = binding.tryInvert.isChecked, + decodeHints.apply { + formats = if (binding.qrcode.isChecked) setOf(Format.QR_CODE) else setOf() + tryHarder = binding.tryHarder.isChecked + tryRotate = binding.tryRotate.isChecked + tryInvert = binding.tryInvert.isChecked tryDownscale = binding.tryDownscale.isChecked - ) + } resultText = try { image.use { - ZXingCpp.read(it, options) + ZXingCpp.read(it, decodeHints) }?.apply { runtime2 += this[0].time?.toInt() ?: 0 }?.joinToString("\n") { result -> diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 884450aa87..bcdf31e7d5 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -60,24 +60,24 @@ public object ZXingCpp { } public data class DecodeHints( - val formats: Set = setOf(), - val tryHarder: Boolean = false, - val tryRotate: Boolean = false, - val tryInvert: Boolean = false, - val tryDownscale: Boolean = false, - val isPure: Boolean = false, - val tryCode39ExtendedMode: Boolean = false, - val validateCode39CheckSum: Boolean = false, - val validateITFCheckSum: Boolean = false, - val returnCodabarStartEnd: Boolean = false, - val returnErrors: Boolean = false, - val downscaleFactor: Int = 3, - val eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, - val binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, - val textMode: TextMode = TextMode.HRI, - val minLineCount: Int = 2, - val maxNumberOfSymbols: Int = 0xff, - val downscaleThreshold: Int = 500 + var formats: Set = setOf(), + var tryHarder: Boolean = false, + var tryRotate: Boolean = false, + var tryInvert: Boolean = false, + var tryDownscale: Boolean = false, + var isPure: Boolean = false, + var tryCode39ExtendedMode: Boolean = false, + var validateCode39CheckSum: Boolean = false, + var validateITFCheckSum: Boolean = false, + var returnCodabarStartEnd: Boolean = false, + var returnErrors: Boolean = false, + var downscaleFactor: Int = 3, + var eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, + var binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, + var textMode: TextMode = TextMode.HRI, + var minLineCount: Int = 2, + var maxNumberOfSymbols: Int = 0xff, + var downscaleThreshold: Int = 500 ) public data class Position( From 17353dce018fa79c3a9392336224cd44efd48546 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 10 Nov 2023 16:30:41 +0100 Subject: [PATCH 084/587] android: remove double code when creating enums Can be generalized by using an additional function. --- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 33 +++++++++++-------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 7443d175d2..659542ee55 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -133,15 +133,6 @@ static jobject CreatePosition(JNIEnv* env, const Position& position) position.orientation()); } -static jobject CreateContentType(JNIEnv* env, ContentType contentType) -{ - jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$ContentType"); - jfieldID fidCT = env->GetStaticFieldID(cls, - JavaContentTypeName(contentType), - "Lcom/zxingcpp/ZXingCpp$ContentType;"); - return env->GetStaticObjectField(cls, fidCT); -} - static jbyteArray CreateByteArray(JNIEnv* env, const void* data, unsigned int length) { @@ -160,15 +151,29 @@ static jbyteArray CreateByteArray(JNIEnv* env, byteArray.size()); } -static jobject CreateFormat(JNIEnv* env, BarcodeFormat format) +static jobject CreateEnum(JNIEnv* env, const char* enumClass, + const char* value) { - jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Format"); - jfieldID fidCT = env->GetStaticFieldID(cls, - JavaBarcodeFormatName(format), - "Lcom/zxingcpp/ZXingCpp$Format;"); + jclass cls = env->FindClass(enumClass); + jfieldID fidCT = env->GetStaticFieldID(cls, value, + ("L" + std::string(enumClass) + ";").c_str()); return env->GetStaticObjectField(cls, fidCT); } +static jobject CreateContentType(JNIEnv* env, ContentType contentType) +{ + return CreateEnum(env, + "com/zxingcpp/ZXingCpp$ContentType", + JavaContentTypeName(contentType)); +} + +static jobject CreateFormat(JNIEnv* env, BarcodeFormat format) +{ + return CreateEnum(env, + "com/zxingcpp/ZXingCpp$Format", + JavaBarcodeFormatName(format)); +} + static jobject CreateResult(JNIEnv* env, const Result& result, const jstring& timeString) { From 7fd2b74cdeb4af815ef57612430812250c140115 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Sat, 11 Nov 2023 18:06:13 +0100 Subject: [PATCH 085/587] android: add consumerProguardFiles To automatically add the necessary proguard configuration to the consuming app of this library. This way, there is no manual proguard configuration required for the users of this library. Note that this is only true if the library is included as an AAR (or via maven), but not in a multi-module build (like it's the case with the sample app), because `consumerProguardFiles` is not used in multi-module builds. --- wrappers/android/zxingcpp/build.gradle.kts | 2 ++ wrappers/android/zxingcpp/consumer-rules.pro | 1 + 2 files changed, 3 insertions(+) create mode 100644 wrappers/android/zxingcpp/consumer-rules.pro diff --git a/wrappers/android/zxingcpp/build.gradle.kts b/wrappers/android/zxingcpp/build.gradle.kts index f1fd062a33..807253a3f6 100644 --- a/wrappers/android/zxingcpp/build.gradle.kts +++ b/wrappers/android/zxingcpp/build.gradle.kts @@ -26,6 +26,8 @@ android { arguments("-DCMAKE_BUILD_TYPE=RelWithDebInfo") } } + + consumerProguardFiles("consumer-rules.pro") } compileOptions { sourceCompatibility(JavaVersion.VERSION_1_8) diff --git a/wrappers/android/zxingcpp/consumer-rules.pro b/wrappers/android/zxingcpp/consumer-rules.pro new file mode 100644 index 0000000000..9958e802ed --- /dev/null +++ b/wrappers/android/zxingcpp/consumer-rules.pro @@ -0,0 +1 @@ +-keep class com.zxingcpp.** { *; } From 64deacd9ff33ab94392ee2181f027951a098dbb7 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Mon, 13 Nov 2023 13:11:48 +0100 Subject: [PATCH 086/587] android: add error message to Result object Since there is `returnErrors` in `DecodeHints`, the `Result` should contain a corresponding `error` member. --- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 6 ++++-- .../android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt | 3 ++- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 659542ee55..be0d363080 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -194,7 +194,8 @@ static jobject CreateResult(JNIEnv* env, const Result& result, "I" "Ljava/lang/String;" "Z" - "I)V"); + "I" + "Ljava/lang/String;)V"); return env->NewObject( cls, constructor, CreateFormat(env, result.format()), @@ -210,7 +211,8 @@ static jobject CreateResult(JNIEnv* env, const Result& result, result.sequenceIndex(), C2JString(env, result.sequenceId()), result.readerInit(), - result.lineCount()); + result.lineCount(), + C2JString(env, result.error().msg())); } static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index bcdf31e7d5..0329305517 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -102,7 +102,8 @@ public object ZXingCpp { val sequenceIndex: Int, val sequenceId: String, val readerInit: Boolean, - val lineCount: Int + val lineCount: Int, + val error: String ) public fun read(image: ImageProxy, decodeHints: DecodeHints): List? { From 8f41d024b5df8eb4cfc43dc6c992ea85c7abcad2 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Mon, 13 Nov 2023 13:13:04 +0100 Subject: [PATCH 087/587] android: make time and position non-null in Result These members can never be null. --- .../android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 0329305517..3b170ad636 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -92,9 +92,9 @@ public object ZXingCpp { val format: Format, val bytes: ByteArray?, val text: String?, - val time: String?, // for development/debug purposes only + val time: String, // for development/debug purposes only val contentType: ContentType, - val position: Position?, + val position: Position, val orientation: Int, val ecLevel: String?, val symbologyIdentifier: String?, From 22f23a91cfa58dddaf898d4950cf316dde241b04 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Mon, 13 Nov 2023 16:10:40 +0100 Subject: [PATCH 088/587] android: remove unnecessary safe calls from sample `time` and `position` can never be null now. --- .../src/main/java/com/example/zxingcppdemo/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index fd816adf25..7c6102c119 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -250,9 +250,9 @@ class MainActivity : AppCompatActivity() { image.use { ZXingCpp.read(it, decodeHints) }?.apply { - runtime2 += this[0].time?.toInt() ?: 0 + runtime2 += this[0].time.toInt() }?.joinToString("\n") { result -> - result.position?.let { + result.position.let { resultPoints.add( listOf( it.topLeft, From 5679aa6b5b2b4a064adaf51da8c59f6be121c224 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Mon, 13 Nov 2023 16:12:38 +0100 Subject: [PATCH 089/587] android: add an Error data class To also know the error type on the JVM. --- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 35 +++++++++++++++++-- .../src/main/java/com/zxingcpp/ZXingCpp.kt | 11 +++++- 2 files changed, 43 insertions(+), 3 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index be0d363080..41dadc9bc9 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -53,6 +53,17 @@ static const char* JavaContentTypeName(ContentType contentType) } } +static const char* JavaErrorTypeName(Error::Type errorType) +{ + // These have to be the names of the enum constants in the kotlin code. + switch (errorType) { + case Error::Type::Format: return "FORMAT"; + case Error::Type::Checksum: return "CHECKSUM"; + case Error::Type::Unsupported: return "UNSUPPORTED"; + default: throw std::invalid_argument("Invalid errorType"); + } +} + static EanAddOnSymbol EanAddOnSymbolFromString(const std::string& name) { if (name == "IGNORE") { @@ -160,6 +171,26 @@ static jobject CreateEnum(JNIEnv* env, const char* enumClass, return env->GetStaticObjectField(cls, fidCT); } +static jobject CreateErrorType(JNIEnv* env, Error::Type errorType) +{ + return CreateEnum(env, + "com/zxingcpp/ZXingCpp$ErrorType", + JavaErrorTypeName(errorType)); +} + +static jobject CreateError(JNIEnv* env, const Error& error) +{ + jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Error"); + auto constructor = env->GetMethodID( + cls, "", + "(Lcom/zxingcpp/ZXingCpp$ErrorType;" + "Ljava/lang/String;)V"); + return env->NewObject( + cls, constructor, + CreateErrorType(env, error.type()), + C2JString(env, error.msg())); +} + static jobject CreateContentType(JNIEnv* env, ContentType contentType) { return CreateEnum(env, @@ -195,7 +226,7 @@ static jobject CreateResult(JNIEnv* env, const Result& result, "Ljava/lang/String;" "Z" "I" - "Ljava/lang/String;)V"); + "Lcom/zxingcpp/ZXingCpp$Error;)V"); return env->NewObject( cls, constructor, CreateFormat(env, result.format()), @@ -212,7 +243,7 @@ static jobject CreateResult(JNIEnv* env, const Result& result, C2JString(env, result.sequenceId()), result.readerInit(), result.lineCount(), - C2JString(env, result.error().msg())); + result.error() ? CreateError(env, result.error()) : nullptr); } static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 3b170ad636..4816c0a0bf 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -59,6 +59,10 @@ public object ZXingCpp { PLAIN, ECI, HRI, HEX, ESCAPED } + public enum class ErrorType { + FORMAT, CHECKSUM, UNSUPPORTED + } + public data class DecodeHints( var formats: Set = setOf(), var tryHarder: Boolean = false, @@ -80,6 +84,11 @@ public object ZXingCpp { var downscaleThreshold: Int = 500 ) + public data class Error( + val type: ErrorType, + val message: String + ) + public data class Position( val topLeft: Point, val topRight: Point, @@ -103,7 +112,7 @@ public object ZXingCpp { val sequenceId: String, val readerInit: Boolean, val lineCount: Int, - val error: String + val error: Error? ) public fun read(image: ImageProxy, decodeHints: DecodeHints): List? { From 133051471706db09ad60ed2a9314d9b2a0caae5d Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Mon, 13 Nov 2023 16:30:19 +0100 Subject: [PATCH 090/587] android: return null for nullable types in Result If the result is not valid. Also make `sequenceId` nullable, too. --- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 11 ++++++----- .../zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 41dadc9bc9..00c115bf09 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -227,20 +227,21 @@ static jobject CreateResult(JNIEnv* env, const Result& result, "Z" "I" "Lcom/zxingcpp/ZXingCpp$Error;)V"); + bool valid = result.isValid(); return env->NewObject( cls, constructor, CreateFormat(env, result.format()), - CreateByteArray(env, result.bytes()), - C2JString(env, result.text()), + valid ? CreateByteArray(env, result.bytes()) : nullptr, + valid ? C2JString(env, result.text()) : nullptr, timeString, CreateContentType(env, result.contentType()), CreatePosition(env, result.position()), result.orientation(), - C2JString(env, result.ecLevel()), - C2JString(env, result.symbologyIdentifier()), + valid ? C2JString(env, result.ecLevel()) : nullptr, + valid ? C2JString(env, result.symbologyIdentifier()) : nullptr, result.sequenceSize(), result.sequenceIndex(), - C2JString(env, result.sequenceId()), + valid ? C2JString(env, result.sequenceId()) : nullptr, result.readerInit(), result.lineCount(), result.error() ? CreateError(env, result.error()) : nullptr); diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 4816c0a0bf..0a9d50d1ff 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -109,7 +109,7 @@ public object ZXingCpp { val symbologyIdentifier: String?, val sequenceSize: Int, val sequenceIndex: Int, - val sequenceId: String, + val sequenceId: String?, val readerInit: Boolean, val lineCount: Int, val error: Error? From cf3cb80ca98598587e41a1f20fee5fc288df2277 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Mon, 13 Nov 2023 16:38:01 +0100 Subject: [PATCH 091/587] android: always return a List of Results Instead of returning null when no barcodes could be found. While this change makes it easier to deal with a reading result, it has the downside of needlessly allocating an empty ArrayList. --- .../java/com/example/zxingcppdemo/MainActivity.kt | 4 ++-- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 11 ++++------- .../zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt | 8 ++++---- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 7c6102c119..a16ba36a95 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -249,9 +249,9 @@ class MainActivity : AppCompatActivity() { resultText = try { image.use { ZXingCpp.read(it, decodeHints) - }?.apply { + }.apply { runtime2 += this[0].time.toInt() - }?.joinToString("\n") { result -> + }.joinToString("\n") { result -> result.position.let { resultPoints.add( listOf( diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 00c115bf09..555b21fa92 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -257,19 +257,16 @@ static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) auto time = std::to_wstring(std::chrono::duration_cast(duration).count()); auto timeString = C2JString(env, time); + auto cls = env->FindClass("java/util/ArrayList"); + auto list = env->NewObject(cls, + env->GetMethodID(cls, "", "()V")); if (!results.empty()) { - // Only allocate when something is found. - auto cls = env->FindClass("java/util/ArrayList"); - auto list = env->NewObject(cls, - env->GetMethodID(cls, "", "()V")); auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z"); for (const auto& result: results) { env->CallBooleanMethod(list, add, CreateResult(env, result, timeString)); } - return list; - } else { - return nullptr; } + return list; } catch (const std::exception& e) { return ThrowJavaException(env, e.what()); } catch (...) { diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 0a9d50d1ff..1b4b537b85 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -115,7 +115,7 @@ public object ZXingCpp { val error: Error? ) - public fun read(image: ImageProxy, decodeHints: DecodeHints): List? { + public fun read(image: ImageProxy, decodeHints: DecodeHints): List { check(image.format in supportedYUVFormats) { "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" } @@ -137,7 +137,7 @@ public object ZXingCpp { decodeHints: DecodeHints, cropRect: Rect = Rect(), rotation: Int = 0 - ): List? { + ): List { return readBitmap( bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, decodeHints @@ -153,10 +153,10 @@ public object ZXingCpp { height: Int, rotation: Int, decodeHints: DecodeHints - ): List? + ): List private external fun readBitmap( bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, decodeHints: DecodeHints - ): List? + ): List } From 66d9be2651e0973c1cba77525c2e9bb49625200c Mon Sep 17 00:00:00 2001 From: Alexander Manzer Date: Tue, 14 Nov 2023 10:32:05 +0100 Subject: [PATCH 092/587] ios: use a default error text instead of crashing Because `e.what()` returns `nil` sometimes, what is causing a crash when initializing the dictionary. --- wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm index c8594b9a58..433994fb59 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm @@ -76,10 +76,10 @@ -(CGImageRef)encode:(std::wstring)content return [self inflate:&bitMatrix]; } catch(std::exception &e) { if (error != nil) { - NSDictionary *userInfo = @{ - NSLocalizedDescriptionKey: [[NSString alloc] initWithUTF8String:e.what()] - }; - *error = [[NSError alloc] initWithDomain:ZXIErrorDomain code:ZXIWriterError userInfo:userInfo]; + const char *errorCString = e.what(); + NSString *errorDescription = errorCString ? [NSString stringWithUTF8String:errorCString] : @"Unknown error"; + NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: errorDescription }; + *error = [NSError errorWithDomain:ZXIErrorDomain code:ZXIWriterError userInfo:userInfo]; } return nil; } From 2ef3267f156cd97718364b78b065e4d81d18f3e6 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 17 Nov 2023 17:22:46 +0100 Subject: [PATCH 093/587] ios: improve exception/error handling Make `read()` throw so C++ exceptions can be caught in Swift. So an app can't crash silently anymore when a C++ exception is thrown while trying to read a barcode. This also adds a function to properly set a NSError from a C++ exception to avoid code duplication in ZXIBarcodeReader/Writer. --- .../Sources/Wrapper/Reader/ZXIBarcodeReader.h | 16 +++-- .../Wrapper/Reader/ZXIBarcodeReader.mm | 65 +++++++++++-------- .../Wrapper/Writer/ZXIBarcodeWriter.mm | 7 +- wrappers/ios/Sources/Wrapper/ZXIErrors.h | 6 ++ wrappers/ios/Sources/Wrapper/ZXIErrors.mm | 25 +++++++ wrappers/ios/demo/demo/ViewController.swift | 2 +- 6 files changed, 82 insertions(+), 39 deletions(-) create mode 100644 wrappers/ios/Sources/Wrapper/ZXIErrors.mm diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h index f2cca0ba21..f72e6691ed 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h @@ -2,6 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 +#import #import #import #import "ZXIResult.h" @@ -12,10 +13,17 @@ NS_ASSUME_NONNULL_BEGIN @interface ZXIBarcodeReader : NSObject @property(nonatomic, strong) ZXIDecodeHints *hints; -- (instancetype)initWithHints:(ZXIDecodeHints*)options; -- (NSArray *)readCIImage:(nonnull CIImage *)image; -- (NSArray *)readCGImage:(nonnull CGImageRef)image; -- (NSArray *)readCVPixelBuffer:(nonnull CVPixelBufferRef)pixelBuffer; +-(instancetype)initWithHints:(ZXIDecodeHints*)options; + +-(nullable NSArray *)readCIImage:(nonnull CIImage *)image + error:(NSError *__autoreleasing _Nullable *)error; + +-(nullable NSArray *)readCGImage:(nonnull CGImageRef)image + error:(NSError *__autoreleasing _Nullable *)error; + +-(nullable NSArray *)readCVPixelBuffer:(nonnull CVPixelBufferRef)pixelBuffer + error:(NSError *__autoreleasing _Nullable *)error; + @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm index c13b150e8c..1783305c4d 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm @@ -9,6 +9,7 @@ #import "GTIN.h" #import "ZXIFormatHelper.h" #import "ZXIPosition+Helper.h" +#import "ZXIErrors.h" using namespace ZXing; @@ -44,7 +45,8 @@ - (instancetype)initWithHints:(ZXIDecodeHints*)hints{ return self; } -- (NSArray *)readCVPixelBuffer:(nonnull CVPixelBufferRef)pixelBuffer { +- (NSArray *)readCVPixelBuffer:(nonnull CVPixelBufferRef)pixelBuffer + error:(NSError *__autoreleasing _Nullable *)error { OSType pixelFormat = CVPixelBufferGetPixelFormatType(pixelBuffer); // We tried to work with all luminance based formats listed in kCVPixelFormatType @@ -64,24 +66,26 @@ - (instancetype)initWithHints:(ZXIDecodeHints*)hints{ ImageFormat::Lum, static_cast(bytesPerRow), 0); - NSArray* results = [self readImageView:imageView]; + NSArray* results = [self readImageView:imageView error:error]; CVPixelBufferUnlockBaseAddress(pixelBuffer, kCVPixelBufferLock_ReadOnly); return results; } // If given pixel format is not a supported type with a luminance channel we just use the // default method - return [self readCIImage:[[CIImage alloc] initWithCVImageBuffer:pixelBuffer]]; + return [self readCIImage:[[CIImage alloc] initWithCVImageBuffer:pixelBuffer] error:error]; } -- (NSArray *)readCIImage:(nonnull CIImage *)image { +- (NSArray *)readCIImage:(nonnull CIImage *)image + error:(NSError *__autoreleasing _Nullable *)error { CGImageRef cgImage = [self.ciContext createCGImage:image fromRect:image.extent]; - auto results = [self readCGImage:cgImage]; + auto results = [self readCGImage:cgImage error:error]; CGImageRelease(cgImage); return results; } -- (NSArray *)readCGImage: (nonnull CGImageRef)image { +- (NSArray *)readCGImage:(nonnull CGImageRef)image + error:(NSError *__autoreleasing _Nullable *)error { CGColorSpaceRef colorSpace = CGColorSpaceCreateWithName(kCGColorSpaceGenericGray); CGFloat cols = CGImageGetWidth(image); CGFloat rows = CGImageGetHeight(image); @@ -104,7 +108,7 @@ - (instancetype)initWithHints:(ZXIDecodeHints*)hints{ static_cast(cols), static_cast(rows), ImageFormat::Lum); - return [self readImageView:imageView]; + return [self readImageView:imageView error:error]; } + (DecodeHints)DecodeHintsFromZXIOptions:(ZXIDecodeHints*)hints { @@ -126,28 +130,33 @@ + (DecodeHints)DecodeHintsFromZXIOptions:(ZXIDecodeHints*)hints { return resultingHints; } -- (NSArray *)readImageView: (ImageView)imageView { - Results results = ReadBarcodes(imageView, [ZXIBarcodeReader DecodeHintsFromZXIOptions:self.hints]); - - NSMutableArray* zxiResults = [NSMutableArray array]; - for (auto result: results) { - [zxiResults addObject: - [[ZXIResult alloc] init:stringToNSString(result.text()) - format:ZXIFormatFromBarcodeFormat(result.format()) - bytes:[[NSData alloc] initWithBytes:result.bytes().data() length:result.bytes().size()] - position:[[ZXIPosition alloc]initWithPosition: result.position()] - orientation:result.orientation() - ecLevel:stringToNSString(result.ecLevel()) - symbologyIdentifier:stringToNSString(result.symbologyIdentifier()) - sequenceSize:result.sequenceSize() - sequenceIndex:result.sequenceIndex() - sequenceId:stringToNSString(result.sequenceId()) - readerInit:result.readerInit() - lineCount:result.lineCount() - gtin:getGTIN(result)] - ]; +- (NSArray *)readImageView:(ImageView)imageView + error:(NSError *__autoreleasing _Nullable *)error { + try { + Results results = ReadBarcodes(imageView, [ZXIBarcodeReader DecodeHintsFromZXIOptions:self.hints]); + NSMutableArray* zxiResults = [NSMutableArray array]; + for (auto result: results) { + [zxiResults addObject: + [[ZXIResult alloc] init:stringToNSString(result.text()) + format:ZXIFormatFromBarcodeFormat(result.format()) + bytes:[[NSData alloc] initWithBytes:result.bytes().data() length:result.bytes().size()] + position:[[ZXIPosition alloc]initWithPosition: result.position()] + orientation:result.orientation() + ecLevel:stringToNSString(result.ecLevel()) + symbologyIdentifier:stringToNSString(result.symbologyIdentifier()) + sequenceSize:result.sequenceSize() + sequenceIndex:result.sequenceIndex() + sequenceId:stringToNSString(result.sequenceId()) + readerInit:result.readerInit() + lineCount:result.lineCount() + gtin:getGTIN(result)] + ]; + } + return zxiResults; + } catch(std::exception &e) { + SetNSError(error, ZXIReaderError, e.what()); + return nil; } - return zxiResults; } @end diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm index 433994fb59..dfaf706ccc 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm @@ -75,12 +75,7 @@ -(CGImageRef)encode:(std::wstring)content BitMatrix bitMatrix = writer.encode(content, width, height); return [self inflate:&bitMatrix]; } catch(std::exception &e) { - if (error != nil) { - const char *errorCString = e.what(); - NSString *errorDescription = errorCString ? [NSString stringWithUTF8String:errorCString] : @"Unknown error"; - NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: errorDescription }; - *error = [NSError errorWithDomain:ZXIErrorDomain code:ZXIWriterError userInfo:userInfo]; - } + SetNSError(error, ZXIWriterError, e.what()); return nil; } } diff --git a/wrappers/ios/Sources/Wrapper/ZXIErrors.h b/wrappers/ios/Sources/Wrapper/ZXIErrors.h index 7f1ebb6e26..583df50771 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIErrors.h +++ b/wrappers/ios/Sources/Wrapper/ZXIErrors.h @@ -8,8 +8,14 @@ NS_ASSUME_NONNULL_BEGIN #define ZXIErrorDomain @"ZXIErrorDomain" +typedef NS_ENUM(NSInteger, ZXIBarcodeReaderError) { + ZXIReaderError, +}; + typedef NS_ENUM(NSInteger, ZXIBarcodeWriterError) { ZXIWriterError, }; +void SetNSError(NSError *__autoreleasing _Nullable* error, NSInteger code, const char* message); + NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm new file mode 100644 index 0000000000..6b88bcb1de --- /dev/null +++ b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm @@ -0,0 +1,25 @@ +// Copyright 2023 KURZ Digital Solutions GmbH +// +// SPDX-License-Identifier: Apache-2.0 + +#import "ZXIErrors.h" + +void SetNSError(NSError *__autoreleasing _Nullable* error, + NSInteger code, + const char* message) { + if (error == nil) { + return; + } + NSString *errorDescription = @"Unknown C++ error"; + if (message && strlen(message) > 0) { + try { + errorDescription = [NSString stringWithUTF8String: message]; + } catch (NSException *exception) { + errorDescription = @"Unknown ObjC error"; + } + } + NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: errorDescription }; + *error = [NSError errorWithDomain:ZXIErrorDomain + code:code + userInfo:userInfo]; +} diff --git a/wrappers/ios/demo/demo/ViewController.swift b/wrappers/ios/demo/demo/ViewController.swift index 964ca6010d..30d437eb31 100644 --- a/wrappers/ios/demo/demo/ViewController.swift +++ b/wrappers/ios/demo/demo/ViewController.swift @@ -70,7 +70,7 @@ extension ViewController: AVCaptureVideoDataOutputSampleBufferDelegate { return } let imageBuffer = CMSampleBufferGetImageBuffer(sampleBuffer)! - if let result = reader.read(imageBuffer).first { + if let result = try? reader.read(imageBuffer).first { print("Found barcode of format", result.format.rawValue, "with text", result.text) } self.zxingLock.signal() From 53c73e12f50ab1b8abd6b00f6addd6eff4809450 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Fri, 17 Nov 2023 17:30:24 +0100 Subject: [PATCH 094/587] ios: catch exceptions from invalid GTIN data Because invalid GTIN data can lead to exceptions (e.g. from std::stoi), in which case we don't want to discard the whole result. --- .../Wrapper/Reader/ZXIBarcodeReader.mm | 22 ++++++++++++------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm index 1783305c4d..9a964be3df 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm @@ -18,14 +18,20 @@ } ZXIGTIN *getGTIN(const Result &result) { - auto country = GTIN::LookupCountryIdentifier(result.text(TextMode::Plain), result.format()); - auto addOn = GTIN::EanAddOn(result); - return country.empty() - ? nullptr - : [[ZXIGTIN alloc]initWithCountry:stringToNSString(country) - addOn:stringToNSString(addOn) - price:stringToNSString(GTIN::Price(addOn)) - issueNumber:stringToNSString(GTIN::IssueNr(addOn))]; + try { + auto country = GTIN::LookupCountryIdentifier(result.text(TextMode::Plain), result.format()); + auto addOn = GTIN::EanAddOn(result); + return country.empty() + ? nullptr + : [[ZXIGTIN alloc]initWithCountry:stringToNSString(country) + addOn:stringToNSString(addOn) + price:stringToNSString(GTIN::Price(addOn)) + issueNumber:stringToNSString(GTIN::IssueNr(addOn))]; + } catch (std::exception e) { + // Because invalid GTIN data can lead to exceptions, in which case + // we don't want to discard the whole result. + return nullptr; + } } @interface ZXIBarcodeReader() From 891b6dfd1fc167dbdd7c8c6ff796346b3d860cdb Mon Sep 17 00:00:00 2001 From: siiky Date: Fri, 17 Nov 2023 18:22:26 +0000 Subject: [PATCH 095/587] Add `DecodeHints.setMaxNumberOfSymbols()` to C API --- wrappers/c/zxing-c.cpp | 5 +++++ wrappers/c/zxing-c.h | 1 + 2 files changed, 6 insertions(+) diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index befb8b777c..a7e3d60e5b 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -128,6 +128,11 @@ void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode text hints->setTextMode(static_cast(textMode)); } +void zxing_DecodeHints_setMaxNumberOfSymbols(zxing_DecodeHints* hints, int n) +{ + hints->setMaxNumberOfSymbols(n); +} + /* * ZXing/Result.h */ diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index b66889516d..2a5ebcb1da 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -135,6 +135,7 @@ void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer); void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol); void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode); +void zxing_DecodeHints_setMaxNumberOfSymbols(zxing_DecodeHints* hints, int n); /* * ZXing/Result.h From b53b63f3e1163567f28565c3931c32da42beed9d Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 16 Nov 2023 02:15:49 +0100 Subject: [PATCH 096/587] android: fix index error message if no result is found --- .../src/main/java/com/example/zxingcppdemo/MainActivity.kt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index a16ba36a95..6a693fbf52 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -250,7 +250,9 @@ class MainActivity : AppCompatActivity() { image.use { ZXingCpp.read(it, decodeHints) }.apply { - runtime2 += this[0].time.toInt() + runtime2 += if (this.isNotEmpty()) { + this[0].time.toInt() + } else 0 }.joinToString("\n") { result -> result.position.let { resultPoints.add( From dd745f86c53ed158537af72814c2d1814c7cc8e8 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 16 Nov 2023 02:17:24 +0100 Subject: [PATCH 097/587] android: minor clang-tidy performance fix with find_first_not_of usage --- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 555b21fa92..ae972e0310 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -306,8 +306,8 @@ static std::string JoinFormats(JNIEnv* env, jclass hintClass, jobject hints) ("L" + std::string(setClass) + ";").c_str())), env->GetMethodID(cls, "toString", "()Ljava/lang/String;")); std::string s = J2CString(env, jStr); - s.erase(0, s.find_first_not_of("[")); - s.erase(s.find_last_not_of("]") + 1); + s.erase(0, s.find_first_not_of('[')); + s.erase(s.find_last_not_of(']') + 1); return s; } From d05a1b2d8169981b0e9eb722cdbb85915fdfbfc0 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 20 Nov 2023 08:13:47 +0100 Subject: [PATCH 098/587] android: harmonize line length with rest of code base --- .editorconfig | 3 +- .../com/example/zxingcppdemo/MainActivity.kt | 32 +++----- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 75 +++++++------------ .../src/main/java/com/zxingcpp/ZXingCpp.kt | 24 ++---- 4 files changed, 42 insertions(+), 92 deletions(-) diff --git a/.editorconfig b/.editorconfig index c643beb33d..50d16bc2ce 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,9 +5,10 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,c,h,html,py}] +[*.{cpp,c,h,html,py,kt}] indent_style = tab indent_size = 4 +max_line_length = 135 [CMakeLists.txt] indent_style = space diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 6a693fbf52..8d7664694d 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -255,26 +255,17 @@ class MainActivity : AppCompatActivity() { } else 0 }.joinToString("\n") { result -> result.position.let { - resultPoints.add( - listOf( - it.topLeft, - it.topRight, - it.bottomRight, - it.bottomLeft - ).map { p -> - p.toPointF() - } - ) + resultPoints.add(listOf( + it.topLeft, it.topRight, it.bottomRight, it.bottomLeft + ).map { p -> + p.toPointF() + }) } "${result.format} (${result.contentType}): ${ if (result.contentType != ZXingCpp.ContentType.BINARY) { result.text } else { - result.bytes!!.joinToString(separator = "") { v -> - "%02x".format( - v - ) - } + result.bytes!!.joinToString(separator = "") { v -> "%02x".format(v) } } }" } ?: "" @@ -290,14 +281,9 @@ class MainActivity : AppCompatActivity() { val now = System.currentTimeMillis() val fps = 1000 * frameCounter.toDouble() / (now - lastFpsTimestamp) - infoText = "Time: %2d/%2d ms, FPS: %.02f, (%dx%d)" - .format( - runtimes / frameCounter, - runtime2 / frameCounter, - fps, - image.width, - image.height - ) + infoText = "Time: %2d/%2d ms, FPS: %.02f, (%dx%d)".format( + runtimes / frameCounter, runtime2 / frameCounter, fps, image.width, image.height + ) lastFpsTimestamp = now frameCounter = 0 runtimes = 0 diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index ae972e0310..e7de0e94af 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -12,6 +12,7 @@ #include using namespace ZXing; +using namespace std::string_literals; static const char* JavaBarcodeFormatName(BarcodeFormat format) { @@ -144,72 +145,52 @@ static jobject CreatePosition(JNIEnv* env, const Position& position) position.orientation()); } -static jbyteArray CreateByteArray(JNIEnv* env, const void* data, - unsigned int length) +static jbyteArray CreateByteArray(JNIEnv* env, const void* data, unsigned int length) { auto size = static_cast(length); jbyteArray byteArray = env->NewByteArray(size); - env->SetByteArrayRegion( - byteArray, 0, size, reinterpret_cast(data)); + env->SetByteArrayRegion(byteArray, 0, size, reinterpret_cast(data)); return byteArray; } -static jbyteArray CreateByteArray(JNIEnv* env, - const std::vector& byteArray) +static jbyteArray CreateByteArray(JNIEnv* env, const std::vector& byteArray) { - return CreateByteArray(env, - reinterpret_cast(byteArray.data()), - byteArray.size()); + return CreateByteArray(env, reinterpret_cast(byteArray.data()), byteArray.size()); } -static jobject CreateEnum(JNIEnv* env, const char* enumClass, - const char* value) +static jobject CreateEnum(JNIEnv* env, const char* enumClass, const char* value) { jclass cls = env->FindClass(enumClass); - jfieldID fidCT = env->GetStaticFieldID(cls, value, - ("L" + std::string(enumClass) + ";").c_str()); + jfieldID fidCT = env->GetStaticFieldID(cls, value, ("L"s + enumClass + ";").c_str()); return env->GetStaticObjectField(cls, fidCT); } static jobject CreateErrorType(JNIEnv* env, Error::Type errorType) { - return CreateEnum(env, - "com/zxingcpp/ZXingCpp$ErrorType", - JavaErrorTypeName(errorType)); + return CreateEnum(env, "com/zxingcpp/ZXingCpp$ErrorType", JavaErrorTypeName(errorType)); } static jobject CreateError(JNIEnv* env, const Error& error) { jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Error"); - auto constructor = env->GetMethodID( - cls, "", - "(Lcom/zxingcpp/ZXingCpp$ErrorType;" - "Ljava/lang/String;)V"); - return env->NewObject( - cls, constructor, - CreateErrorType(env, error.type()), - C2JString(env, error.msg())); + auto constructor = env->GetMethodID(cls, "", "(Lcom/zxingcpp/ZXingCpp$ErrorType;" "Ljava/lang/String;)V"); + return env->NewObject(cls, constructor, CreateErrorType(env, error.type()), C2JString(env, error.msg())); } static jobject CreateContentType(JNIEnv* env, ContentType contentType) { - return CreateEnum(env, - "com/zxingcpp/ZXingCpp$ContentType", - JavaContentTypeName(contentType)); + return CreateEnum(env, "com/zxingcpp/ZXingCpp$ContentType", JavaContentTypeName(contentType)); } static jobject CreateFormat(JNIEnv* env, BarcodeFormat format) { - return CreateEnum(env, - "com/zxingcpp/ZXingCpp$Format", - JavaBarcodeFormatName(format)); + return CreateEnum(env, "com/zxingcpp/ZXingCpp$Format", JavaBarcodeFormatName(format)); } static jobject CreateResult(JNIEnv* env, const Result& result, const jstring& timeString) { - jclass cls = env->FindClass( - "com/zxingcpp/ZXingCpp$Result"); + jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Result"); auto constructor = env->GetMethodID( cls, "", "(Lcom/zxingcpp/ZXingCpp$Format;" @@ -258,8 +239,7 @@ static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) auto timeString = C2JString(env, time); auto cls = env->FindClass("java/util/ArrayList"); - auto list = env->NewObject(cls, - env->GetMethodID(cls, "", "()V")); + auto list = env->NewObject(cls, env->GetMethodID(cls, "", "()V")); if (!results.empty()) { auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z"); for (const auto& result: results) { @@ -274,14 +254,12 @@ static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) } } -static bool GetBooleanField(JNIEnv* env, jclass cls, jobject hints, - const char* name) +static bool GetBooleanField(JNIEnv* env, jclass cls, jobject hints, const char* name) { return env->GetBooleanField(hints, env->GetFieldID(cls, name, "Z")); } -static int GetIntField(JNIEnv* env, jclass cls, jobject hints, - const char* name) +static int GetIntField(JNIEnv* env, jclass cls, jobject hints, const char* name) { return env->GetIntField(hints, env->GetFieldID(cls, name, "I")); } @@ -299,12 +277,10 @@ static std::string GetEnumField(JNIEnv* env, jclass hintClass, jobject hints, static std::string JoinFormats(JNIEnv* env, jclass hintClass, jobject hints) { - const char* setClass = "java/util/Set"; - jclass cls = env->FindClass(setClass); + jclass cls = env->FindClass("java/util/Set"); jstring jStr = (jstring) env->CallObjectMethod( - env->GetObjectField(hints, env->GetFieldID(hintClass, "formats", - ("L" + std::string(setClass) + ";").c_str())), - env->GetMethodID(cls, "toString", "()Ljava/lang/String;")); + env->GetObjectField(hints, env->GetFieldID(hintClass, "formats","Ljava/util/Set;")), + env->GetMethodID(cls, "toString", "()Ljava/lang/String;")); std::string s = J2CString(env, jStr); s.erase(0, s.find_first_not_of('[')); s.erase(s.find_last_not_of(']') + 1); @@ -344,8 +320,7 @@ static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) extern "C" JNIEXPORT jobject JNICALL Java_com_zxingcpp_ZXingCpp_readYBuffer( JNIEnv *env, jobject thiz, jobject yBuffer, jint rowStride, - jint left, jint top, jint width, jint height, jint rotation, - jobject hints) + jint left, jint top, jint width, jint height, jint rotation, jobject hints) { const uint8_t* pixels = static_cast(env->GetDirectBufferAddress(yBuffer)); @@ -378,8 +353,7 @@ struct LockedPixels extern "C" JNIEXPORT jobject JNICALL Java_com_zxingcpp_ZXingCpp_readBitmap( JNIEnv* env, jobject thiz, jobject bitmap, - jint left, jint top, jint width, jint height, jint rotation, - jobject hints) + jint left, jint top, jint width, jint height, jint rotation, jobject hints) { AndroidBitmapInfo bmInfo; AndroidBitmap_getInfo(env, bitmap, &bmInfo); @@ -396,9 +370,10 @@ Java_com_zxingcpp_ZXingCpp_readBitmap( if (!pixels) return ThrowJavaException(env, "Failed to lock/Read AndroidBitmap data"); - auto image = ImageView{pixels, (int)bmInfo.width, (int)bmInfo.height, fmt, (int)bmInfo.stride} - .cropped(left, top, width, height) - .rotated(rotation); + auto image = + ImageView{pixels, (int)bmInfo.width, (int)bmInfo.height, fmt, (int)bmInfo.stride} + .cropped(left, top, width, height) + .rotated(rotation); return Read(env, image, CreateDecodeHints(env, hints)); } diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 1b4b537b85..f6b7c8505b 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -115,7 +115,7 @@ public object ZXingCpp { val error: Error? ) - public fun read(image: ImageProxy, decodeHints: DecodeHints): List { + public fun read(image: ImageProxy, hints: DecodeHints): List { check(image.format in supportedYUVFormats) { "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" } @@ -128,35 +128,23 @@ public object ZXingCpp { image.cropRect.width(), image.cropRect.height(), image.imageInfo.rotationDegrees, - decodeHints + hints ) } public fun read( - bitmap: Bitmap, - decodeHints: DecodeHints, - cropRect: Rect = Rect(), - rotation: Int = 0 + bitmap: Bitmap, hints: DecodeHints, cropRect: Rect = Rect(), rotation: Int = 0 ): List { return readBitmap( - bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, - decodeHints + bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, hints ) } private external fun readYBuffer( - yBuffer: ByteBuffer, - rowStride: Int, - left: Int, - top: Int, - width: Int, - height: Int, - rotation: Int, - decodeHints: DecodeHints + yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, hints: DecodeHints ): List private external fun readBitmap( - bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, - decodeHints: DecodeHints + bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, hints: DecodeHints ): List } From 28ebb45815fde4b5b9d47d7d6b9f88bdfe218102 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 20 Nov 2023 08:17:09 +0100 Subject: [PATCH 099/587] android: switch debugging time info from string to int (ms) --- .../com/example/zxingcppdemo/MainActivity.kt | 4 +--- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 20 +++++++++---------- .../src/main/java/com/zxingcpp/ZXingCpp.kt | 4 ++-- 3 files changed, 12 insertions(+), 16 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 8d7664694d..efe6a7e7a2 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -250,9 +250,7 @@ class MainActivity : AppCompatActivity() { image.use { ZXingCpp.read(it, decodeHints) }.apply { - runtime2 += if (this.isNotEmpty()) { - this[0].time.toInt() - } else 0 + runtime2 += if (this.isNotEmpty()) this[0].time else 0 }.joinToString("\n") { result -> result.position.let { resultPoints.add(listOf( diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index e7de0e94af..817a48f312 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -187,8 +187,7 @@ static jobject CreateFormat(JNIEnv* env, BarcodeFormat format) return CreateEnum(env, "com/zxingcpp/ZXingCpp$Format", JavaBarcodeFormatName(format)); } -static jobject CreateResult(JNIEnv* env, const Result& result, - const jstring& timeString) +static jobject CreateResult(JNIEnv* env, const Result& result, int time) { jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Result"); auto constructor = env->GetMethodID( @@ -196,7 +195,6 @@ static jobject CreateResult(JNIEnv* env, const Result& result, "(Lcom/zxingcpp/ZXingCpp$Format;" "[B" "Ljava/lang/String;" - "Ljava/lang/String;" "Lcom/zxingcpp/ZXingCpp$ContentType;" "Lcom/zxingcpp/ZXingCpp$Position;" "I" @@ -207,14 +205,14 @@ static jobject CreateResult(JNIEnv* env, const Result& result, "Ljava/lang/String;" "Z" "I" - "Lcom/zxingcpp/ZXingCpp$Error;)V"); + "Lcom/zxingcpp/ZXingCpp$Error;" + "I)V"); bool valid = result.isValid(); return env->NewObject( cls, constructor, CreateFormat(env, result.format()), valid ? CreateByteArray(env, result.bytes()) : nullptr, valid ? C2JString(env, result.text()) : nullptr, - timeString, CreateContentType(env, result.contentType()), CreatePosition(env, result.position()), result.orientation(), @@ -225,7 +223,9 @@ static jobject CreateResult(JNIEnv* env, const Result& result, valid ? C2JString(env, result.sequenceId()) : nullptr, result.readerInit(), result.lineCount(), - result.error() ? CreateError(env, result.error()) : nullptr); + result.error() ? CreateError(env, result.error()) : nullptr, + time + ); } static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) @@ -235,16 +235,14 @@ static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) auto results = ReadBarcodes(image, hints); auto duration = std::chrono::high_resolution_clock::now() - startTime; // LOGD("time: %4d ms\n", (int)std::chrono::duration_cast(duration).count()); - auto time = std::to_wstring(std::chrono::duration_cast(duration).count()); - auto timeString = C2JString(env, time); + auto time = std::chrono::duration_cast(duration).count(); auto cls = env->FindClass("java/util/ArrayList"); auto list = env->NewObject(cls, env->GetMethodID(cls, "", "()V")); if (!results.empty()) { auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z"); - for (const auto& result: results) { - env->CallBooleanMethod(list, add, CreateResult(env, result, timeString)); - } + for (const auto& result: results) + env->CallBooleanMethod(list, add, CreateResult(env, result, time)); } return list; } catch (const std::exception& e) { diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index f6b7c8505b..7ea2a9662c 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -101,7 +101,6 @@ public object ZXingCpp { val format: Format, val bytes: ByteArray?, val text: String?, - val time: String, // for development/debug purposes only val contentType: ContentType, val position: Position, val orientation: Int, @@ -112,7 +111,8 @@ public object ZXingCpp { val sequenceId: String?, val readerInit: Boolean, val lineCount: Int, - val error: Error? + val error: Error?, + val time: Int // for development/debug purposes only ) public fun read(image: ImageProxy, hints: DecodeHints): List { From 0d8e2cf6ee2e58f6767a5f045bfdd6d881b3ef08 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 20 Nov 2023 08:45:37 +0100 Subject: [PATCH 100/587] android: enum creation and extration code simplification --- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 74 +++++++------------ 1 file changed, 25 insertions(+), 49 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 817a48f312..f889e41a8d 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -145,46 +145,28 @@ static jobject CreatePosition(JNIEnv* env, const Position& position) position.orientation()); } -static jbyteArray CreateByteArray(JNIEnv* env, const void* data, unsigned int length) -{ - auto size = static_cast(length); - jbyteArray byteArray = env->NewByteArray(size); - env->SetByteArrayRegion(byteArray, 0, size, reinterpret_cast(data)); - return byteArray; -} - static jbyteArray CreateByteArray(JNIEnv* env, const std::vector& byteArray) { - return CreateByteArray(env, reinterpret_cast(byteArray.data()), byteArray.size()); + auto size = static_cast(byteArray.size()); + jbyteArray res = env->NewByteArray(size); + env->SetByteArrayRegion(res, 0, size, reinterpret_cast(byteArray.data())); + return res; } -static jobject CreateEnum(JNIEnv* env, const char* enumClass, const char* value) +static jobject CreateEnum(JNIEnv* env, const char* value, const char* type) { - jclass cls = env->FindClass(enumClass); - jfieldID fidCT = env->GetStaticFieldID(cls, value, ("L"s + enumClass + ";").c_str()); + auto className = "com/zxingcpp/ZXingCpp$"s + type; + jclass cls = env->FindClass(className.c_str()); + jfieldID fidCT = env->GetStaticFieldID(cls, value, ("L" + className + ";").c_str()); return env->GetStaticObjectField(cls, fidCT); } -static jobject CreateErrorType(JNIEnv* env, Error::Type errorType) -{ - return CreateEnum(env, "com/zxingcpp/ZXingCpp$ErrorType", JavaErrorTypeName(errorType)); -} - static jobject CreateError(JNIEnv* env, const Error& error) { jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Error"); auto constructor = env->GetMethodID(cls, "", "(Lcom/zxingcpp/ZXingCpp$ErrorType;" "Ljava/lang/String;)V"); - return env->NewObject(cls, constructor, CreateErrorType(env, error.type()), C2JString(env, error.msg())); -} - -static jobject CreateContentType(JNIEnv* env, ContentType contentType) -{ - return CreateEnum(env, "com/zxingcpp/ZXingCpp$ContentType", JavaContentTypeName(contentType)); -} - -static jobject CreateFormat(JNIEnv* env, BarcodeFormat format) -{ - return CreateEnum(env, "com/zxingcpp/ZXingCpp$Format", JavaBarcodeFormatName(format)); + return env->NewObject(cls, constructor, CreateEnum(env, JavaErrorTypeName(error.type()), "ErrorType"), + C2JString(env, error.msg())); } static jobject CreateResult(JNIEnv* env, const Result& result, int time) @@ -210,10 +192,10 @@ static jobject CreateResult(JNIEnv* env, const Result& result, int time) bool valid = result.isValid(); return env->NewObject( cls, constructor, - CreateFormat(env, result.format()), + CreateEnum(env, JavaBarcodeFormatName(result.format()), "Format"), valid ? CreateByteArray(env, result.bytes()) : nullptr, valid ? C2JString(env, result.text()) : nullptr, - CreateContentType(env, result.contentType()), + CreateEnum(env, JavaContentTypeName(result.contentType()), "ContentType"), CreatePosition(env, result.position()), result.orientation(), valid ? C2JString(env, result.ecLevel()) : nullptr, @@ -262,14 +244,13 @@ static int GetIntField(JNIEnv* env, jclass cls, jobject hints, const char* name) return env->GetIntField(hints, env->GetFieldID(cls, name, "I")); } -static std::string GetEnumField(JNIEnv* env, jclass hintClass, jobject hints, - const char* enumClass, const char* name) +static std::string GetEnumField(JNIEnv* env, jclass hintClass, jobject hints, const char* name, const char* type) { - jclass cls = env->FindClass(enumClass); + auto className = "com/zxingcpp/ZXingCpp$"s + type; + jclass cls = env->FindClass(className.c_str()); jstring s = (jstring) env->CallObjectMethod( - env->GetObjectField(hints, env->GetFieldID(hintClass, name, - ("L" + std::string(enumClass) + ";").c_str())), - env->GetMethodID(cls, "name", "()Ljava/lang/String;")); + env->GetObjectField(hints, env->GetFieldID(hintClass, name, ("L"s + className + ";").c_str())), + env->GetMethodID(cls, "name", "()Ljava/lang/String;")); return J2CString(env, s); } @@ -295,24 +276,19 @@ static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) .setTryInvert(GetBooleanField(env, cls, hints, "tryInvert")) .setTryDownscale(GetBooleanField(env, cls, hints, "tryDownscale")) .setIsPure(GetBooleanField(env, cls, hints, "isPure")) + .setBinarizer(BinarizerFromString(GetEnumField(env, cls, hints, "binarizer", "Binarizer"))) + .setDownscaleThreshold(GetIntField(env, cls, hints, "downscaleThreshold")) + .setDownscaleFactor(GetIntField(env, cls, hints, "downscaleFactor")) + .setMinLineCount(GetIntField(env, cls, hints, "minLineCount")) + .setMaxNumberOfSymbols(GetIntField(env, cls, hints, "maxNumberOfSymbols")) .setTryCode39ExtendedMode(GetBooleanField(env, cls, hints, "tryCode39ExtendedMode")) .setValidateCode39CheckSum(GetBooleanField(env, cls, hints, "validateCode39CheckSum")) .setValidateITFCheckSum(GetBooleanField(env, cls, hints, "validateITFCheckSum")) .setReturnCodabarStartEnd(GetBooleanField(env, cls, hints, "returnCodabarStartEnd")) .setReturnErrors(GetBooleanField(env, cls, hints, "returnErrors")) - .setDownscaleFactor(GetIntField(env, cls, hints, "downscaleFactor")) - .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, hints, - "com/zxingcpp/ZXingCpp$EanAddOnSymbol", - "eanAddOnSymbol"))) - .setBinarizer(BinarizerFromString(GetEnumField(env, cls, hints, - "com/zxingcpp/ZXingCpp$Binarizer", - "binarizer"))) - .setTextMode(TextModeFromString(GetEnumField(env, cls, hints, - "com/zxingcpp/ZXingCpp$TextMode", - "textMode"))) - .setMinLineCount(GetIntField(env, cls, hints, "minLineCount")) - .setMaxNumberOfSymbols(GetIntField(env, cls, hints, "maxNumberOfSymbols")) - .setDownscaleThreshold(GetIntField(env, cls, hints, "downscaleThreshold")); + .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, hints, "eanAddOnSymbol", "EanAddOnSymbol"))) + .setTextMode(TextModeFromString(GetEnumField(env, cls, hints, "textMode", "TextMode"))) + ; } extern "C" JNIEXPORT jobject JNICALL From 8d095190a5972e8a2518cbded5f9be5d4fabfe29 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 20 Nov 2023 08:50:15 +0100 Subject: [PATCH 101/587] android: sync member order of c++ and Kotlin DecodeHints structs --- .../zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index 7ea2a9662c..dcb2b24d14 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -70,18 +70,18 @@ public object ZXingCpp { var tryInvert: Boolean = false, var tryDownscale: Boolean = false, var isPure: Boolean = false, + var binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, + var downscaleFactor: Int = 3, + var downscaleThreshold: Int = 500, + var minLineCount: Int = 2, + var maxNumberOfSymbols: Int = 0xff, var tryCode39ExtendedMode: Boolean = false, var validateCode39CheckSum: Boolean = false, var validateITFCheckSum: Boolean = false, var returnCodabarStartEnd: Boolean = false, var returnErrors: Boolean = false, - var downscaleFactor: Int = 3, var eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, - var binarizer: Binarizer = Binarizer.LOCAL_AVERAGE, var textMode: TextMode = TextMode.HRI, - var minLineCount: Int = 2, - var maxNumberOfSymbols: Int = 0xff, - var downscaleThreshold: Int = 500 ) public data class Error( From dc7a81f7f3caf0310735d4b6d0e17e7f941a4b57 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 20 Nov 2023 09:44:25 +0100 Subject: [PATCH 102/587] android: Kotlin code cosmetic --- .../src/main/java/com/example/zxingcppdemo/MainActivity.kt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index efe6a7e7a2..999798af71 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -250,7 +250,7 @@ class MainActivity : AppCompatActivity() { image.use { ZXingCpp.read(it, decodeHints) }.apply { - runtime2 += if (this.isNotEmpty()) this[0].time else 0 + runtime2 += firstOrNull()?.time ?: 0 }.joinToString("\n") { result -> result.position.let { resultPoints.add(listOf( @@ -266,7 +266,7 @@ class MainActivity : AppCompatActivity() { result.bytes!!.joinToString(separator = "") { v -> "%02x".format(v) } } }" - } ?: "" + } } catch (e: Throwable) { e.message ?: "Error" } From 3270b3a19d5960288760fdf997e798cf1fc3119b Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 22 Nov 2023 00:34:10 +0100 Subject: [PATCH 103/587] android: avoid using `Set.toString` in DecodeHints::formats extraction This superseeds #666. --- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 25 +++++++++++-------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index f889e41a8d..db68dc7471 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -254,23 +254,28 @@ static std::string GetEnumField(JNIEnv* env, jclass hintClass, jobject hints, co return J2CString(env, s); } -static std::string JoinFormats(JNIEnv* env, jclass hintClass, jobject hints) +static BarcodeFormats GetFormats(JNIEnv* env, jclass hintClass, jobject hints) { - jclass cls = env->FindClass("java/util/Set"); - jstring jStr = (jstring) env->CallObjectMethod( - env->GetObjectField(hints, env->GetFieldID(hintClass, "formats","Ljava/util/Set;")), - env->GetMethodID(cls, "toString", "()Ljava/lang/String;")); - std::string s = J2CString(env, jStr); - s.erase(0, s.find_first_not_of('[')); - s.erase(s.find_last_not_of(']') + 1); - return s; + auto objArray = static_cast(env->CallObjectMethod( + env->GetObjectField(hints, env->GetFieldID(hintClass, "formats", "Ljava/util/Set;")), + env->GetMethodID(env->FindClass("java/util/Set"), "toArray", "()[Ljava/lang/Object;"))); + if (!objArray) + return {}; + + auto midName = env->GetMethodID(env->FindClass("com/zxingcpp/ZXingCpp$Format"), "name", "()Ljava/lang/String;"); + BarcodeFormats ret; + for (int i = 0, size = env->GetArrayLength(objArray); i < size; ++i) { + auto objName = static_cast(env->CallObjectMethod(env->GetObjectArrayElement(objArray, i), midName)); + ret |= BarcodeFormatFromString(J2CString(env, objName)); + } + return ret; } static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) { jclass cls = env->GetObjectClass(hints); return DecodeHints() - .setFormats(BarcodeFormatsFromString(JoinFormats(env, cls, hints))) + .setFormats(GetFormats(env, cls, hints)) .setTryHarder(GetBooleanField(env, cls, hints, "tryHarder")) .setTryRotate(GetBooleanField(env, cls, hints, "tryRotate")) .setTryInvert(GetBooleanField(env, cls, hints, "tryInvert")) From 9ad369d6d59523156141cd886f1dacda4f74132c Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 22 Nov 2023 00:40:38 +0100 Subject: [PATCH 104/587] android: condense/cleanup CreatePosition helper --- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 30 +++++++++---------- .../src/main/java/com/zxingcpp/ZXingCpp.kt | 2 +- 2 files changed, 15 insertions(+), 17 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index db68dc7471..e6027172d0 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -119,30 +119,28 @@ static jstring ThrowJavaException(JNIEnv* env, const char* message) return nullptr; } -static jobject CreateAndroidPoint(JNIEnv* env, const PointT& point) -{ - jclass cls = env->FindClass("android/graphics/Point"); - auto constructor = env->GetMethodID(cls, "", "(II)V"); - return env->NewObject(cls, constructor, point.x, point.y); -} - static jobject CreatePosition(JNIEnv* env, const Position& position) { - jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Position"); - auto constructor = env->GetMethodID( - cls, "", + jclass clsPosition = env->FindClass("com/zxingcpp/ZXingCpp$Position"); + jclass clsPoint = env->FindClass("android/graphics/Point"); + auto midPointInit= env->GetMethodID(clsPoint, "", "(II)V"); + auto NewPoint = [&](const PointI& point) { + return env->NewObject(clsPoint, midPointInit, point.x, point.y); + }; + auto midPositionInit= env->GetMethodID( + clsPosition, "", "(Landroid/graphics/Point;" "Landroid/graphics/Point;" "Landroid/graphics/Point;" "Landroid/graphics/Point;" "D)V"); return env->NewObject( - cls, constructor, - CreateAndroidPoint(env, position.topLeft()), - CreateAndroidPoint(env, position.topRight()), - CreateAndroidPoint(env, position.bottomLeft()), - CreateAndroidPoint(env, position.bottomRight()), - position.orientation()); + clsPosition, midPositionInit, + NewPoint(position[0]), + NewPoint(position[1]), + NewPoint(position[2]), + NewPoint(position[3]), + position.orientation()); } static jbyteArray CreateByteArray(JNIEnv* env, const std::vector& byteArray) diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt index dcb2b24d14..b0a2389c7e 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt @@ -92,8 +92,8 @@ public object ZXingCpp { public data class Position( val topLeft: Point, val topRight: Point, - val bottomLeft: Point, val bottomRight: Point, + val bottomLeft: Point, val orientation: Double ) From 7a0de5b0ee8942f7c45410c91996cbab2397938b Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 22 Nov 2023 01:24:37 +0100 Subject: [PATCH 105/587] android: JNI helper function and variable naming cosmetic --- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 66 +++++++++---------- 1 file changed, 31 insertions(+), 35 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index e6027172d0..b560de592c 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -114,20 +114,20 @@ static jstring ThrowJavaException(JNIEnv* env, const char* message) { // if (env->ExceptionCheck()) // return 0; - jclass jcls = env->FindClass("java/lang/RuntimeException"); - env->ThrowNew(jcls, message); + jclass cls = env->FindClass("java/lang/RuntimeException"); + env->ThrowNew(cls, message); return nullptr; } -static jobject CreatePosition(JNIEnv* env, const Position& position) +static jobject NewPosition(JNIEnv* env, const Position& position) { jclass clsPosition = env->FindClass("com/zxingcpp/ZXingCpp$Position"); jclass clsPoint = env->FindClass("android/graphics/Point"); - auto midPointInit= env->GetMethodID(clsPoint, "", "(II)V"); + jmethodID midPointInit= env->GetMethodID(clsPoint, "", "(II)V"); auto NewPoint = [&](const PointI& point) { return env->NewObject(clsPoint, midPointInit, point.x, point.y); }; - auto midPositionInit= env->GetMethodID( + jmethodID midPositionInit= env->GetMethodID( clsPosition, "", "(Landroid/graphics/Point;" "Landroid/graphics/Point;" @@ -143,7 +143,7 @@ static jobject CreatePosition(JNIEnv* env, const Position& position) position.orientation()); } -static jbyteArray CreateByteArray(JNIEnv* env, const std::vector& byteArray) +static jbyteArray NewByteArray(JNIEnv* env, const std::vector& byteArray) { auto size = static_cast(byteArray.size()); jbyteArray res = env->NewByteArray(size); @@ -151,7 +151,7 @@ static jbyteArray CreateByteArray(JNIEnv* env, const std::vector& byteA return res; } -static jobject CreateEnum(JNIEnv* env, const char* value, const char* type) +static jobject NewEnum(JNIEnv* env, const char* value, const char* type) { auto className = "com/zxingcpp/ZXingCpp$"s + type; jclass cls = env->FindClass(className.c_str()); @@ -159,18 +159,17 @@ static jobject CreateEnum(JNIEnv* env, const char* value, const char* type) return env->GetStaticObjectField(cls, fidCT); } -static jobject CreateError(JNIEnv* env, const Error& error) +static jobject NewError(JNIEnv* env, const Error& error) { jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Error"); - auto constructor = env->GetMethodID(cls, "", "(Lcom/zxingcpp/ZXingCpp$ErrorType;" "Ljava/lang/String;)V"); - return env->NewObject(cls, constructor, CreateEnum(env, JavaErrorTypeName(error.type()), "ErrorType"), - C2JString(env, error.msg())); + jmethodID midInit = env->GetMethodID(cls, "", "(Lcom/zxingcpp/ZXingCpp$ErrorType;" "Ljava/lang/String;)V"); + return env->NewObject(cls, midInit, NewEnum(env, JavaErrorTypeName(error.type()), "ErrorType"), C2JString(env, error.msg())); } -static jobject CreateResult(JNIEnv* env, const Result& result, int time) +static jobject NewResult(JNIEnv* env, const Result& result, int time) { jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Result"); - auto constructor = env->GetMethodID( + jmethodID midInit = env->GetMethodID( cls, "", "(Lcom/zxingcpp/ZXingCpp$Format;" "[B" @@ -188,13 +187,12 @@ static jobject CreateResult(JNIEnv* env, const Result& result, int time) "Lcom/zxingcpp/ZXingCpp$Error;" "I)V"); bool valid = result.isValid(); - return env->NewObject( - cls, constructor, - CreateEnum(env, JavaBarcodeFormatName(result.format()), "Format"), - valid ? CreateByteArray(env, result.bytes()) : nullptr, + return env->NewObject(cls, midInit, + NewEnum(env, JavaBarcodeFormatName(result.format()), "Format"), + valid ? NewByteArray(env, result.bytes()) : nullptr, valid ? C2JString(env, result.text()) : nullptr, - CreateEnum(env, JavaContentTypeName(result.contentType()), "ContentType"), - CreatePosition(env, result.position()), + NewEnum(env, JavaContentTypeName(result.contentType()), "ContentType"), + NewPosition(env, result.position()), result.orientation(), valid ? C2JString(env, result.ecLevel()) : nullptr, valid ? C2JString(env, result.symbologyIdentifier()) : nullptr, @@ -203,7 +201,7 @@ static jobject CreateResult(JNIEnv* env, const Result& result, int time) valid ? C2JString(env, result.sequenceId()) : nullptr, result.readerInit(), result.lineCount(), - result.error() ? CreateError(env, result.error()) : nullptr, + result.error() ? NewError(env, result.error()) : nullptr, time ); } @@ -217,14 +215,14 @@ static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) // LOGD("time: %4d ms\n", (int)std::chrono::duration_cast(duration).count()); auto time = std::chrono::duration_cast(duration).count(); - auto cls = env->FindClass("java/util/ArrayList"); - auto list = env->NewObject(cls, env->GetMethodID(cls, "", "()V")); + jclass clsList = env->FindClass("java/util/ArrayList"); + jobject objList = env->NewObject(clsList, env->GetMethodID(clsList, "", "()V")); if (!results.empty()) { - auto add = env->GetMethodID(cls, "add", "(Ljava/lang/Object;)Z"); + jmethodID midAdd = env->GetMethodID(clsList, "add", "(Ljava/lang/Object;)Z"); for (const auto& result: results) - env->CallBooleanMethod(list, add, CreateResult(env, result, time)); + env->CallBooleanMethod(objList, midAdd, NewResult(env, result, time)); } - return list; + return objList; } catch (const std::exception& e) { return ThrowJavaException(env, e.what()); } catch (...) { @@ -242,25 +240,23 @@ static int GetIntField(JNIEnv* env, jclass cls, jobject hints, const char* name) return env->GetIntField(hints, env->GetFieldID(cls, name, "I")); } -static std::string GetEnumField(JNIEnv* env, jclass hintClass, jobject hints, const char* name, const char* type) +static std::string GetEnumField(JNIEnv* env, jclass cls, jobject hints, const char* name, const char* type) { auto className = "com/zxingcpp/ZXingCpp$"s + type; - jclass cls = env->FindClass(className.c_str()); - jstring s = (jstring) env->CallObjectMethod( - env->GetObjectField(hints, env->GetFieldID(hintClass, name, ("L"s + className + ";").c_str())), - env->GetMethodID(cls, "name", "()Ljava/lang/String;")); - return J2CString(env, s); + jmethodID midName = env->GetMethodID(env->FindClass(className.c_str()), "name", "()Ljava/lang/String;"); + jobject objField = env->GetObjectField(hints, env->GetFieldID(cls, name, ("L"s + className + ";").c_str())); + return J2CString(env, static_cast(env->CallObjectMethod(objField, midName))); } static BarcodeFormats GetFormats(JNIEnv* env, jclass hintClass, jobject hints) { - auto objArray = static_cast(env->CallObjectMethod( - env->GetObjectField(hints, env->GetFieldID(hintClass, "formats", "Ljava/util/Set;")), - env->GetMethodID(env->FindClass("java/util/Set"), "toArray", "()[Ljava/lang/Object;"))); + jobject objField = env->GetObjectField(hints, env->GetFieldID(hintClass, "formats", "Ljava/util/Set;")); + jmethodID midToArray = env->GetMethodID(env->FindClass("java/util/Set"), "toArray", "()[Ljava/lang/Object;"); + auto objArray = static_cast(env->CallObjectMethod(objField, midToArray)); if (!objArray) return {}; - auto midName = env->GetMethodID(env->FindClass("com/zxingcpp/ZXingCpp$Format"), "name", "()Ljava/lang/String;"); + jmethodID midName = env->GetMethodID(env->FindClass("com/zxingcpp/ZXingCpp$Format"), "name", "()Ljava/lang/String;"); BarcodeFormats ret; for (int i = 0, size = env->GetArrayLength(objArray); i < size; ++i) { auto objName = static_cast(env->CallObjectMethod(env->GetObjectArrayElement(objArray, i), midName)); From 4cd3caf608a09d90688e13147a8bda45b0be281e Mon Sep 17 00:00:00 2001 From: gitlost Date: Thu, 23 Nov 2023 11:12:18 +0000 Subject: [PATCH 106/587] aztec: on decoding check for padding bits after B/S --- core/src/aztec/AZDecoder.cpp | 2 ++ test/blackbox/BlackboxTestRunner.cpp | 12 ++++++------ test/samples/aztec-1/padding_bs.png | Bin 0 -> 1221 bytes test/samples/aztec-1/padding_bs.txt | 1 + 4 files changed, 9 insertions(+), 6 deletions(-) create mode 100644 test/samples/aztec-1/padding_bs.png create mode 100644 test/samples/aztec-1/padding_bs.txt diff --git a/core/src/aztec/AZDecoder.cpp b/core/src/aztec/AZDecoder.cpp index f87e652c3e..1d1b8566c1 100644 --- a/core/src/aztec/AZDecoder.cpp +++ b/core/src/aztec/AZDecoder.cpp @@ -258,6 +258,8 @@ static void DecodeContent(const BitArray& bits, Content& res) while (remBits.size() >= (shiftTable == Table::DIGIT ? 4 : 5)) { // see ISO/IEC 24778:2008 7.3.1.2 regarding padding bits if (shiftTable == Table::BINARY) { + if (remBits.size() <= 5) // padding bits + break; int length = remBits.readBits(5); if (length == 0) length = remBits.readBits(11) + 31; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index f28f775fcd..1934d41bf2 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -332,12 +332,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set auto startTime = std::chrono::steady_clock::now(); // clang-format off - runTests("aztec-1", "Aztec", 26, { - { 25, 26, 0 }, - { 25, 26, 90 }, - { 25, 26, 180 }, - { 25, 26, 270 }, - { 24, 0, pure }, + runTests("aztec-1", "Aztec", 27, { + { 26, 27, 0 }, + { 26, 27, 90 }, + { 26, 27, 180 }, + { 26, 27, 270 }, + { 25, 0, pure }, }); runTests("aztec-2", "Aztec", 22, { diff --git a/test/samples/aztec-1/padding_bs.png b/test/samples/aztec-1/padding_bs.png new file mode 100644 index 0000000000000000000000000000000000000000..17d38a214e377b1c1524f1f70014e464bf8302bb GIT binary patch literal 1221 zcmV;$1UmbPP)TNP6pp|3e-{!OF&KKFl)zuX0}@%b{mFb7=};vQ>ng|^vvOb=k*0V_*^V- zy~4aqM4kw%QCO<29CwBrTr}F=^y9YE$#e(cHY!JkNLa#By-qio=zj@ zZRQnD=8fCT`JrEzt&zK%AOEP!=Ez}<^9mneTE316HfQVsZFAq$Ugr57iMQE z(qbCyYeoX8F~piMg*>R;I52>C7o%Rq_j$?1yeySrxX%;?4?S=B-2 z-W`*2!&DkDkG{81E2hyyYEnH7`ZsF~2pA&O+OrR8i&2sYTvdyPQU6XvXk0V*+=BXG zt%Z=u3rDqDyp=)b;8crg&6F}DJ*j_FR!D@wESnk)dPz>^1xSjkOKpQ*Qi8UWlTl$K z) Date: Fri, 24 Nov 2023 10:22:37 +0000 Subject: [PATCH 107/587] aztec: re [7a0de5b0] padding bits - can have 11 (check 5 -> 6) --- core/src/aztec/AZDecoder.cpp | 2 +- test/samples/aztec-1/padding_bs.png | Bin 1221 -> 1058 bytes test/samples/aztec-1/padding_bs.txt | Bin 1992 -> 996 bytes 3 files changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/aztec/AZDecoder.cpp b/core/src/aztec/AZDecoder.cpp index 1d1b8566c1..788f49cd1b 100644 --- a/core/src/aztec/AZDecoder.cpp +++ b/core/src/aztec/AZDecoder.cpp @@ -258,7 +258,7 @@ static void DecodeContent(const BitArray& bits, Content& res) while (remBits.size() >= (shiftTable == Table::DIGIT ? 4 : 5)) { // see ISO/IEC 24778:2008 7.3.1.2 regarding padding bits if (shiftTable == Table::BINARY) { - if (remBits.size() <= 5) // padding bits + if (remBits.size() <= 6) // padding bits break; int length = remBits.readBits(5); if (length == 0) diff --git a/test/samples/aztec-1/padding_bs.png b/test/samples/aztec-1/padding_bs.png index 17d38a214e377b1c1524f1f70014e464bf8302bb..925ad93df1c5e5f69acb8007627942c3667eeed2 100644 GIT binary patch delta 1019 zcmV!|r2eJyxAp~y7Mvbz_OkmPg-F6<`hQd(AVJNj{m!5J^~ z`x2Y>JqNqUR?S;qzwY`*5py!*`l7Gxu|M~)Y&d5G_wJoFyE*w*#Q*@HH(Q|$DjvpZ zXKd{;8)VL4t$&V0V7yf^ATTtr5K|etL`n~Jj~1k?iEB~L^1XV$!*Ae6`tTFrY2Hl-=FStin+#&71kQ@JY*M-*_lfxJAX+#^qj3E04%+OM7+)V{`~hj zghM1$q)R{RYwU##T&k5$+3BpWRkMZ)Ez4mI+Vvczo0Ees3ST)9lo$`$tBt_!21?;x zVkcQbDpF=NJ;T>*hinZ?+W{EQteIB>;mJR0et=jG9RRn{LphwBuy``QBi!To4U7EZoU*J z@|&#h&sjnb`1oP{`9L5K`1su~hZ#zlPG0WAW%^-$4;jJbK(mL2ANEwlQP5}QEY(Jb z`HiXA)Bs#$IG;W{c$8UHg> zByFe;sjySei3(j&6lrB|_7>~=-)9GEt-227Y0=RR^SfJLt2-+t5gzunwQBI_BA#G< z!hczoIEl+rUtr1Vhy8qNK`j>08U*byOAr@HkP}-^lSB5HW31_sEsgT@nMpczAaeDr zwcu2@&mriDh}Jz0^V^m)Xw4{@EbEYMFks+nST3~e&~w1%m^$Y`>d_AGwGk1c)>zfR z$2|1Bx8=c9Et$|yJ-@N9_h#?k_Z#+(=XC$wTRZD(|9cR?lfAXGEP+e9pRn;DOW+dw z>3*ir8+lH*bnkn9N%u{*?0SBUePa(<5MJH$>-l~AoJKbL+J!TV`)shCid|#hKBqwt p#7pNy8*I6+d&BcrW7F&E{u`HZvw(!C9{vCT002ovPDHLkV1j@~2%7)^ delta 1184 zcmV;R1Yi522*nAIHGc$pNklGxsBT z`#E{re&$YRYJW%h9_DAR?=Fqdu%lylF|tG4r+v@dQ&&pGO078&B_TB0HDl)PCbfsi z%{0d5z2eSVJJeMC3Ep0{JiVV@J-@mHj0;RxFmJnHH*-=#(%=ROTE!R#^9JMCM3R@x zg^_m7_dW9z7(%CtIX07kW7=-1D~bpA6jB~!t|+Db1WZ1bHzmtWa!m-WI}9>cj}e4flb2rW zpf-C^Xr@A;^^o`0c^4rxvYv{QyIUl$d2qf%O^1xSjk zOKpQ*Qi8UWlTl$K(n1f_Kzf5-cl!6ovz|+R55J}r!tYaG zXPoM;v!0LINkhhNEmcPYA7oxt3f549Vx&)r@AmKJ1`Hvio8j&{zh|x#UC>KmEM=H^ z2oSudtjK0o2K`$JNQ_-uNmbLJf5+&MEriOBwbGz}ix#hB4C2l-TIZkdf5{x5a)1B* zedjHodg@o_J%e8IeP=y~_wcXJ)^3LO-*#5s^fBH4<9;vkv$X9S=3-C8{M+-)n{|Ns zS$W$r=CYUhdD?V@dG|+We)(=|O188=? Date: Mon, 27 Nov 2023 00:45:25 +0100 Subject: [PATCH 108/587] python: add support for sliced numpy arrays (fix #672) This also redoes the PIL related buffer dimension fixes. --- wrappers/python/test.py | 15 +++++++++- wrappers/python/zxing.cpp | 61 +++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 33 deletions(-) diff --git a/wrappers/python/test.py b/wrappers/python/test.py index 266b1f57fe..a086b24413 100644 --- a/wrappers/python/test.py +++ b/wrappers/python/test.py @@ -6,6 +6,7 @@ has_numpy = importlib.util.find_spec('numpy') is not None has_pil = importlib.util.find_spec('PIL') is not None +has_cv2 = importlib.util.find_spec('cv2') is not None BF = zxingcpp.BarcodeFormat @@ -92,10 +93,11 @@ def test_write_read_cycle_numpy(self): import numpy as np format = BF.QRCode text = "I have the best words." - img = zxingcpp.write_barcode(format, text) + img = zxingcpp.write_barcode(format, text, quiet_zone=10) img = np.array(img) self.check_res(zxingcpp.read_barcode(img), format, text) + self.check_res(zxingcpp.read_barcode(img[5:40,5:40]), format, text) @unittest.skipIf(not has_pil, "need PIL for read/write tests") def test_write_read_cycle_pil(self): @@ -111,6 +113,17 @@ def test_write_read_cycle_pil(self): self.check_res(zxingcpp.read_barcode(img.convert("1")), format, text) self.check_res(zxingcpp.read_barcode(img.convert("CMYK")), format, text) + @unittest.skipIf(not has_cv2, "need cv2 for read/write tests") + def test_write_read_cycle_cv2(self): + import cv2, numpy + format = BF.QRCode + text = "I have the best words." + img = zxingcpp.write_barcode(format, text, quiet_zone=10) + img = cv2.cvtColor(numpy.array(img), cv2.COLOR_GRAY2BGR ) + + self.check_res(zxingcpp.read_barcode(img), format, text) + self.check_res(zxingcpp.read_barcode(img[5:40,5:40,:]), format, text) + def test_read_invalid_type(self): self.assertRaisesRegex( TypeError, "Invalid input: does not support the buffer protocol.", zxingcpp.read_barcode, "foo" diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index 38b1ee7a66..04a864915f 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -51,7 +51,7 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t .setEanAddOnSymbol(ean_add_on_symbol) .setReturnErrors(return_errors); const auto _type = std::string(py::str(py::type::of(_image))); - py::buffer buffer; + py::buffer_info info; ImageFormat imgfmt = ImageFormat::None; try { if (py::hasattr(_image, "__array_interface__")) { @@ -72,41 +72,39 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t } auto ai = _image.attr("__array_interface__").cast(); - auto ashape = ai["shape"].cast(); + auto shape = ai["shape"].cast>(); + auto typestr = ai["typestr"].cast(); + + if (typestr != "|u1") + throw py::type_error("Incompatible __array_interface__ data type (" + typestr + "): expected a uint8_t array (|u1)."); if (ai.contains("data")) { auto adata = ai["data"]; - if (py::isinstance(adata)) { - auto data_ptr = adata.cast()[0].cast(); - auto data_len = Reduce(ashape.cast>(), 1, std::multiplies{}); - buffer = py::memoryview::from_memory(reinterpret_cast(data_ptr), data_len, true); - } else if (py::isinstance(adata)) { - // Numpy and our own __array_interface__ passes data as a buffer/bytes object - buffer = adata.cast(); + if (py::isinstance(adata)) { + // PIL and our own __array_interface__ passes data as a buffer/bytes object + info = adata.cast().request(); + // PIL's bytes object has wrong dim/shape/strides info + if (info.ndim != Size(shape)) { + info.ndim = Size(shape); + info.shape = shape; + info.strides = py::detail::c_strides(shape, 1); + } + } else if (py::isinstance(adata)) { + // numpy data is passed as a tuple + auto strides = py::detail::c_strides(shape, 1); + if (ai.contains("strides") && !ai["strides"].is_none()) + strides = ai["strides"].cast>(); + auto data_ptr = reinterpret_cast(adata.cast()[0].cast()); + info = py::buffer_info(data_ptr, 1, "B", Size(shape), shape, strides); } else { throw py::type_error("No way to get data from __array_interface__"); } } else { - buffer = _image.cast(); - } - - py::tuple bshape; - if (py::hasattr(buffer, "shape")) { - bshape = buffer.attr("shape").cast(); - } - - // We need to check if the shape is equal because memoryviews can only be cast from 1D - // to ND and in reverse, not from ND to ND. If the shape is already correct, as with our - // return value from write_barcode, we don't need to cast. There are libraries (PIL for - // example) that pass 1D data here, in that case we need to cast because the later code - // expects a buffer in the correct shape. - if (!ashape.equal(bshape)) { - auto bufferview = py::memoryview(buffer); - buffer = bufferview.attr("cast")("B", ashape).cast(); + info = _image.cast().request(); } } else { - buffer = _image.cast(); + info = _image.cast().request(); } #if PYBIND11_VERSION_HEX > 0x02080000 // py::raise_from is available starting from 2.8.0 } catch (py::error_already_set &e) { @@ -117,18 +115,17 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t throw py::type_error("Invalid input: " + _type + " does not support the buffer protocol."); } - /* Request a buffer descriptor from Python */ - py::buffer_info info = buffer.request(); - if (info.format != py::format_descriptor::format()) - throw py::type_error("Incompatible buffer format: expected a uint8_t array."); + throw py::type_error("Incompatible buffer format '" + info.format + "': expected a uint8_t array."); if (info.ndim != 2 && info.ndim != 3) - throw py::type_error("Incompatible buffer dimension (needs to be 2 or 3)."); + throw py::type_error("Incompatible buffer dimension " + std::to_string(info.ndim) + " (needs to be 2 or 3)."); const auto height = narrow_cast(info.shape[0]); const auto width = narrow_cast(info.shape[1]); const auto channels = info.ndim == 2 ? 1 : narrow_cast(info.shape[2]); + const auto rowStride = narrow_cast(info.strides[0]); + const auto pixStride = narrow_cast(info.strides[1]); if (imgfmt == ImageFormat::None) { // Assume grayscale or BGR image depending on channels number if (channels == 1) @@ -142,7 +139,7 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t const auto bytes = static_cast(info.ptr); // Disables the GIL during zxing processing (restored automatically upon completion) py::gil_scoped_release release; - return ReadBarcodes({bytes, width, height, imgfmt, width * channels, channels}, hints); + return ReadBarcodes({bytes, width, height, imgfmt, rowStride, pixStride}, hints); } std::optional read_barcode(py::object _image, const BarcodeFormats& formats, bool try_rotate, bool try_downscale, From d2fdb565e42cf6d982de376ae8e93dd1a70e237d Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Nov 2023 01:03:16 +0100 Subject: [PATCH 109/587] python: trying to fix build regression on MSVC --- wrappers/python/zxing.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index 04a864915f..3cc540829a 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -72,7 +72,7 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t } auto ai = _image.attr("__array_interface__").cast(); - auto shape = ai["shape"].cast>(); + auto shape = ai["shape"].cast>(); auto typestr = ai["typestr"].cast(); if (typestr != "|u1") @@ -94,7 +94,7 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t // numpy data is passed as a tuple auto strides = py::detail::c_strides(shape, 1); if (ai.contains("strides") && !ai["strides"].is_none()) - strides = ai["strides"].cast>(); + strides = ai["strides"].cast>(); auto data_ptr = reinterpret_cast(adata.cast()[0].cast()); info = py::buffer_info(data_ptr, 1, "B", Size(shape), shape, strides); } else { From 295b193b0105e68bb24747aefbff2653df892b4c Mon Sep 17 00:00:00 2001 From: Michael Weghorn Date: Mon, 27 Nov 2023 14:28:34 +0100 Subject: [PATCH 110/587] android: Fix build with NDK 26 While the workarounds added in commit df0b9213017a136bf7253ea1d4aba5677c52d45c Author: axxel Date: Thu Dec 15 20:43:48 2022 +0100 android: work around limitations of c++-20 support in NDK may be necessary for NDK 25, they are no longer for NDK 26, and even break the build with NDK 26: C/C++: .../zxing-cpp/core/src/Generator.h:103:7: error: reference to 'default_sentinel_t' is ambiguous C/C++: std::default_sentinel_t end() { return {}; } C/C++: ^ C/C++: .../Android/Sdk/ndk/26.1.10909125/toolchains/llvm/prebuilt/linux-x86_64/sysroot/usr/include/c++/v1/__iterator/default_sentinel.h:23:8: note: candidate found by name lookup is 'std::__ndk1::default_sentinel_t' C/C++: struct default_sentinel_t { }; C/C++: ^ C/C++: .../zxing-cpp/core/src/Generator.h:15:9: note: candidate found by name lookup is 'std::default_sentinel_t' C/C++: struct default_sentinel_t {}; C/C++: ^ C/C++: 2 errors generated. Restrict the workaround to NDK version < 26 to fix this. Fixes: #673 --- core/src/Generator.h | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/Generator.h b/core/src/Generator.h index 7a1fd179f2..a5083e9d15 100644 --- a/core/src/Generator.h +++ b/core/src/Generator.h @@ -5,8 +5,12 @@ #pragma once -#ifdef __cpp_impl_coroutine #ifdef __ANDROID__ +#include +#endif + +#ifdef __cpp_impl_coroutine +#if defined __ANDROID__ && __NDK_MAJOR__ < 26 // NDK 25.1.8937393 can compile this code with c++20 but needs a few tweaks: #include namespace std { @@ -25,7 +29,7 @@ namespace std { // this code is based on https://en.cppreference.com/w/cpp/coroutine/coroutine_handle#Example // but modified trying to prevent accidental copying of generated objects -#ifdef __ANDROID__ +#if defined __ANDROID__ && __NDK_MAJOR__ < 26 template #else template From 025cb0c8b21d8a1d5380941ea1754d30c9bb18eb Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Nov 2023 15:28:25 +0100 Subject: [PATCH 111/587] android demo: add "single" symbol detection switch (vs. multi-symbol) --- .../src/main/java/com/example/zxingcppdemo/MainActivity.kt | 1 + .../app/src/main/res/layout-land/activity_camera.xml | 7 ++++++- .../android/app/src/main/res/layout/activity_camera.xml | 7 ++++++- 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 999798af71..773683ba99 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -244,6 +244,7 @@ class MainActivity : AppCompatActivity() { tryRotate = binding.tryRotate.isChecked tryInvert = binding.tryInvert.isChecked tryDownscale = binding.tryDownscale.isChecked + maxNumberOfSymbols = if (binding.single.isChecked) 1 else 255 } resultText = try { diff --git a/wrappers/android/app/src/main/res/layout-land/activity_camera.xml b/wrappers/android/app/src/main/res/layout-land/activity_camera.xml index cf3dca0bde..75efb08ad2 100644 --- a/wrappers/android/app/src/main/res/layout-land/activity_camera.xml +++ b/wrappers/android/app/src/main/res/layout-land/activity_camera.xml @@ -104,6 +104,11 @@ style="@style/Chip" android:text="crop" /> + + - \ No newline at end of file + diff --git a/wrappers/android/app/src/main/res/layout/activity_camera.xml b/wrappers/android/app/src/main/res/layout/activity_camera.xml index 4b5d3f4eb4..d5d3a0e590 100644 --- a/wrappers/android/app/src/main/res/layout/activity_camera.xml +++ b/wrappers/android/app/src/main/res/layout/activity_camera.xml @@ -107,6 +107,11 @@ style="@style/ChipR" android:text="crop" /> + + - \ No newline at end of file + From 10cea641fa753dc7abe0a8b6fd48079742e4334d Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Nov 2023 23:11:22 +0100 Subject: [PATCH 112/587] CodeQL: add kotlin, python and swift language checking --- .github/workflows/codeql-analysis.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3937ed1f5f..943cedd161 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -25,12 +25,12 @@ on: workflow_dispatch jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} strategy: fail-fast: false matrix: - language: [ 'cpp' ] + language: [ 'c-cpp', 'java-kotlin', 'python', 'swift' ] # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed @@ -67,3 +67,5 @@ jobs: - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" From a3cfc157bad5c83319ede7cfc32a43a72d3692b5 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Nov 2023 23:27:01 +0100 Subject: [PATCH 113/587] CodeQL: disable kotlin and swift because autobuild fails for them --- .github/workflows/codeql-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 943cedd161..96f35f4209 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -30,7 +30,7 @@ jobs: strategy: fail-fast: false matrix: - language: [ 'c-cpp', 'java-kotlin', 'python', 'swift' ] + language: [ 'c-cpp', 'python' ] # , 'java-kotlin', 'swift' currently fail with autobuild # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] # Learn more: # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed From a4c59409f7d0dff714d3ef7590bca4a317970bd7 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Nov 2023 23:47:40 +0100 Subject: [PATCH 114/587] msvc-analysis: disable broken additionalArgs parameter --- .github/workflows/msvc-analysis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml index 269f231f7e..4c9342c845 100644 --- a/.github/workflows/msvc-analysis.yml +++ b/.github/workflows/msvc-analysis.yml @@ -55,7 +55,8 @@ jobs: buildConfiguration: ${{ env.config }} # Ruleset file that will determine what checks will be run ruleset: NativeRecommendedRules.ruleset - additionalArgs: /wd26451 # Suppress C26451, apparently bogous in VS2019 + # additionalArgs: /wd26451 # Suppress C26451, apparently bogous in VS2019 + # still fails, see https://github.com/microsoft/msvc-code-analysis-action/issues/31 # Upload SARIF file to GitHub Code Scanning Alerts - name: Upload SARIF to GitHub From e50d50298a9a62c46148e473b12bad18343cf5ff Mon Sep 17 00:00:00 2001 From: gitlost Date: Wed, 29 Nov 2023 11:40:17 +0000 Subject: [PATCH 115/587] qrdecoder: fix `DecodeAlphanumericSegment()` percent/FNC1 loop --- core/src/qrcode/QRDecoder.cpp | 10 +++++----- test/unit/qrcode/QRDecodedBitStreamParserTest.cpp | 12 ++++++++++++ 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index dfdcbf1477..f3dceca94e 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -147,14 +147,14 @@ static void DecodeAlphanumericSegment(BitSource& bits, int count, Content& resul // See section 6.4.8.1, 6.4.8.2 if (result.symbology.aiFlag != AIFlag::None) { // We need to massage the result a bit if in an FNC1 mode: - for (size_t i = 0; i < buffer.length(); i++) { - if (buffer[i] == '%') { - if (i < buffer.length() - 1 && buffer[i + 1] == '%') { + for (auto i = buffer.begin(); i != buffer.end(); i++) { + if (*i == '%') { + if (i + 1 != buffer.end() && *(i + 1) == '%') { // %% is rendered as % - buffer.erase(i + 1); + i = buffer.erase(i); } else { // In alpha mode, % should be converted to FNC1 separator 0x1D - buffer[i] = static_cast(0x1D); + *i = static_cast(0x1D); } } } diff --git a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp index 26c71beaf3..3521c91bc2 100644 --- a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp +++ b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp @@ -130,3 +130,15 @@ TEST(QRDecodedBitStreamParserTest, SymbologyIdentifier) result = DecodeBitStream({0x9A, 0x42, 0x00, 0x96, 0x00}, version, ecLevel); EXPECT_FALSE(result.isValid()); } + +TEST(QRDecodedBitStreamParserTest, GS1PercentGS) +{ + const Version& version = *Version::Model2(1); + const ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Quality; + DecoderResult result; + + // GS1 "FNC1(1st) A(11) 9112%%%2012 (9112%2012)" + result = DecodeBitStream({0x52, 0x05, 0x99, 0x60, 0x5F, 0xB5, 0x35, 0x80, 0x01, 0x08, 0x00, 0xEC, 0x11}, version, ecLevel); + EXPECT_EQ(result.content().text(TextMode::Plain), "9112%\x1D" "2012"); + EXPECT_EQ(result.content().text(TextMode::HRI), "(91)12%(20)12"); +} From 4a6b532f0ff544a96dd98f5616b885a7f601a3dc Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 1 Dec 2023 09:52:19 +0100 Subject: [PATCH 116/587] android: naming cleanup groupId = io.github.zxing-cpp artifact = android namespace/package = zxingcpp client side usage: * implementation("io.github.zxing-cpp:android:2.1.0") * import zxingcpp.ZXingCpp See https://github.com/zxing-cpp/zxing-cpp/discussions/636#discussioncomment-7711650 --- wrappers/android/app/build.gradle.kts | 4 +- .../android/app/src/main/AndroidManifest.xml | 4 +- .../app}/MainActivity.kt | 8 +-- .../app}/PreviewOverlay.kt | 0 .../app/src/main/res/values/values.xml | 2 +- wrappers/android/settings.gradle.kts | 2 +- wrappers/android/zxingcpp/build.gradle.kts | 71 +++++++++++++++++-- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 28 ++++---- .../main/java/{com => }/zxingcpp/ZXingCpp.kt | 2 +- 9 files changed, 90 insertions(+), 31 deletions(-) rename wrappers/android/app/src/main/java/{com/example/zxingcppdemo => zxingcpp/app}/MainActivity.kt (98%) rename wrappers/android/app/src/main/java/{com/example/zxingcppdemo => zxingcpp/app}/PreviewOverlay.kt (100%) rename wrappers/android/zxingcpp/src/main/java/{com => }/zxingcpp/ZXingCpp.kt (99%) diff --git a/wrappers/android/app/build.gradle.kts b/wrappers/android/app/build.gradle.kts index aede6c5e91..ef30f033c2 100644 --- a/wrappers/android/app/build.gradle.kts +++ b/wrappers/android/app/build.gradle.kts @@ -4,9 +4,9 @@ plugins { } android { - namespace = "com.example.zxingcppdemo" + namespace = "zxingcpp.app" defaultConfig { - applicationId = "com.example.zxingdemo" + applicationId = "io.github.zxingcpp.app" compileSdk = libs.versions.androidCompileSdk.get().toInt() minSdk = 26 // for the adaptive icons. TODO: remove adaptive icons and lower to API 21 targetSdk = libs.versions.androidTargetSdk.get().toInt() diff --git a/wrappers/android/app/src/main/AndroidManifest.xml b/wrappers/android/app/src/main/AndroidManifest.xml index 4127b366bf..1317424c9f 100644 --- a/wrappers/android/app/src/main/AndroidManifest.xml +++ b/wrappers/android/app/src/main/AndroidManifest.xml @@ -16,7 +16,7 @@ android:supportsRtl="true" android:theme="@style/AppTheme"> @@ -25,4 +25,4 @@ - \ No newline at end of file + diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt similarity index 98% rename from wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt rename to wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 773683ba99..36e233a829 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.example.zxingcppdemo +package zxingcpp.app import android.Manifest import android.content.Context @@ -44,18 +44,18 @@ import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat import androidx.core.graphics.toPointF import androidx.lifecycle.LifecycleOwner -import com.example.zxingcppdemo.databinding.ActivityCameraBinding import com.google.zxing.BarcodeFormat import com.google.zxing.BinaryBitmap import com.google.zxing.DecodeHintType import com.google.zxing.MultiFormatReader import com.google.zxing.PlanarYUVLuminanceSource import com.google.zxing.common.HybridBinarizer -import com.zxingcpp.ZXingCpp -import com.zxingcpp.ZXingCpp.Format import java.io.ByteArrayOutputStream import java.io.File import java.util.concurrent.Executors +import zxingcpp.app.databinding.ActivityCameraBinding +import zxingcpp.ZXingCpp +import zxingcpp.ZXingCpp.Format class MainActivity : AppCompatActivity() { diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt b/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt similarity index 100% rename from wrappers/android/app/src/main/java/com/example/zxingcppdemo/PreviewOverlay.kt rename to wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt diff --git a/wrappers/android/app/src/main/res/values/values.xml b/wrappers/android/app/src/main/res/values/values.xml index 027c699408..8d7301984f 100644 --- a/wrappers/android/app/src/main/res/values/values.xml +++ b/wrappers/android/app/src/main/res/values/values.xml @@ -22,7 +22,7 @@ - ZXingCpp-Demo + zxing-cpp Capture UNKNOWN diff --git a/wrappers/android/settings.gradle.kts b/wrappers/android/settings.gradle.kts index d56ae2f4bc..7cd059c9b2 100644 --- a/wrappers/android/settings.gradle.kts +++ b/wrappers/android/settings.gradle.kts @@ -16,4 +16,4 @@ dependencyResolutionManagement { include(":app") include(":zxingcpp") -rootProject.name = "ZXingCpp" +rootProject.name = "zxing-cpp" diff --git a/wrappers/android/zxingcpp/build.gradle.kts b/wrappers/android/zxingcpp/build.gradle.kts index 35e7e16028..9a6fe39117 100644 --- a/wrappers/android/zxingcpp/build.gradle.kts +++ b/wrappers/android/zxingcpp/build.gradle.kts @@ -1,11 +1,12 @@ plugins { alias(libs.plugins.android.library) alias(libs.plugins.kotlin.android) - `maven-publish` + id("maven-publish") + id("signing") } android { - namespace = "com.zxingcpp" + namespace = "zxingcpp" // ndk version 25 is known to support c++20 (see #386) // ndkVersion = "25.1.8937393" @@ -55,16 +56,72 @@ dependencies { implementation(libs.androidx.camera.core) } +group = "io.github.zxing-cpp" +version = "2.1.0-SNAPSHOT" + +val javadocJar by tasks.registering(Jar::class) { + archiveClassifier.set("javadoc") +} + publishing { publications { register("release") { - groupId = "com.zxingcpp" - artifactId = "zxingcpp" - version = "2.1.0" + artifactId = "android" + groupId = project.group.toString() + version = project.version.toString() + + afterEvaluate { + from(components["release"]) + } + + artifact(javadocJar.get()) - afterEvaluate { - from(components["release"]) + pom { + name = "zxing-cpp" + description = "Wrapper for zxing-cpp barcode image processing library" + url = "https://github.com/zxing-cpp/zxing-cpp" + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = "zxing-cpp" + name = "zxing-cpp community" + email = "zxingcpp@gmail.com" + } + } + scm { + connection = "scm:git:git://github.com/zxing-cpp/zxing-cpp.git" + developerConnection = "scm:git:git://github.com/zxing-cpp/zxing-cpp.git" + url = "https://github.com/zxing-cpp/zxing-cpp" } + } + } + } + repositories { + maven { + name = "sonatype" + + val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" + setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) + + credentials { + val ossrhUsername: String? by project + val ossrhPassword: String? by project + username = ossrhUsername + password = ossrhPassword + } } } } + +signing { + val signingKey: String? by project + val signingPassword: String? by project + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications) +} diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index b560de592c..5e590cf608 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -14,6 +14,8 @@ using namespace ZXing; using namespace std::string_literals; +#define PACKAGE "zxingcpp/ZXingCpp$" + static const char* JavaBarcodeFormatName(BarcodeFormat format) { // These have to be the names of the enum constants in the kotlin code. @@ -121,7 +123,7 @@ static jstring ThrowJavaException(JNIEnv* env, const char* message) static jobject NewPosition(JNIEnv* env, const Position& position) { - jclass clsPosition = env->FindClass("com/zxingcpp/ZXingCpp$Position"); + jclass clsPosition = env->FindClass(PACKAGE "Position"); jclass clsPoint = env->FindClass("android/graphics/Point"); jmethodID midPointInit= env->GetMethodID(clsPoint, "", "(II)V"); auto NewPoint = [&](const PointI& point) { @@ -153,7 +155,7 @@ static jbyteArray NewByteArray(JNIEnv* env, const std::vector& byteArra static jobject NewEnum(JNIEnv* env, const char* value, const char* type) { - auto className = "com/zxingcpp/ZXingCpp$"s + type; + auto className = PACKAGE ""s + type; jclass cls = env->FindClass(className.c_str()); jfieldID fidCT = env->GetStaticFieldID(cls, value, ("L" + className + ";").c_str()); return env->GetStaticObjectField(cls, fidCT); @@ -161,21 +163,21 @@ static jobject NewEnum(JNIEnv* env, const char* value, const char* type) static jobject NewError(JNIEnv* env, const Error& error) { - jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Error"); - jmethodID midInit = env->GetMethodID(cls, "", "(Lcom/zxingcpp/ZXingCpp$ErrorType;" "Ljava/lang/String;)V"); + jclass cls = env->FindClass(PACKAGE "Error"); + jmethodID midInit = env->GetMethodID(cls, "", "(L" PACKAGE "ErrorType;" "Ljava/lang/String;)V"); return env->NewObject(cls, midInit, NewEnum(env, JavaErrorTypeName(error.type()), "ErrorType"), C2JString(env, error.msg())); } static jobject NewResult(JNIEnv* env, const Result& result, int time) { - jclass cls = env->FindClass("com/zxingcpp/ZXingCpp$Result"); + jclass cls = env->FindClass(PACKAGE "Result"); jmethodID midInit = env->GetMethodID( cls, "", - "(Lcom/zxingcpp/ZXingCpp$Format;" + "(L" PACKAGE "Format;" "[B" "Ljava/lang/String;" - "Lcom/zxingcpp/ZXingCpp$ContentType;" - "Lcom/zxingcpp/ZXingCpp$Position;" + "L" PACKAGE "ContentType;" + "L" PACKAGE "Position;" "I" "Ljava/lang/String;" "Ljava/lang/String;" @@ -184,7 +186,7 @@ static jobject NewResult(JNIEnv* env, const Result& result, int time) "Ljava/lang/String;" "Z" "I" - "Lcom/zxingcpp/ZXingCpp$Error;" + "L" PACKAGE "Error;" "I)V"); bool valid = result.isValid(); return env->NewObject(cls, midInit, @@ -242,7 +244,7 @@ static int GetIntField(JNIEnv* env, jclass cls, jobject hints, const char* name) static std::string GetEnumField(JNIEnv* env, jclass cls, jobject hints, const char* name, const char* type) { - auto className = "com/zxingcpp/ZXingCpp$"s + type; + auto className = PACKAGE ""s + type; jmethodID midName = env->GetMethodID(env->FindClass(className.c_str()), "name", "()Ljava/lang/String;"); jobject objField = env->GetObjectField(hints, env->GetFieldID(cls, name, ("L"s + className + ";").c_str())); return J2CString(env, static_cast(env->CallObjectMethod(objField, midName))); @@ -256,7 +258,7 @@ static BarcodeFormats GetFormats(JNIEnv* env, jclass hintClass, jobject hints) if (!objArray) return {}; - jmethodID midName = env->GetMethodID(env->FindClass("com/zxingcpp/ZXingCpp$Format"), "name", "()Ljava/lang/String;"); + jmethodID midName = env->GetMethodID(env->FindClass(PACKAGE "Format"), "name", "()Ljava/lang/String;"); BarcodeFormats ret; for (int i = 0, size = env->GetArrayLength(objArray); i < size; ++i) { auto objName = static_cast(env->CallObjectMethod(env->GetObjectArrayElement(objArray, i), midName)); @@ -291,7 +293,7 @@ static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) } extern "C" JNIEXPORT jobject JNICALL -Java_com_zxingcpp_ZXingCpp_readYBuffer( +Java_zxingcpp_ZXingCpp_readYBuffer( JNIEnv *env, jobject thiz, jobject yBuffer, jint rowStride, jint left, jint top, jint width, jint height, jint rotation, jobject hints) { @@ -324,7 +326,7 @@ struct LockedPixels }; extern "C" JNIEXPORT jobject JNICALL -Java_com_zxingcpp_ZXingCpp_readBitmap( +Java_zxingcpp_ZXingCpp_readBitmap( JNIEnv* env, jobject thiz, jobject bitmap, jint left, jint top, jint width, jint height, jint rotation, jobject hints) { diff --git a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt similarity index 99% rename from wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt rename to wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt index b0a2389c7e..2affa8b98c 100644 --- a/wrappers/android/zxingcpp/src/main/java/com/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt @@ -14,7 +14,7 @@ * limitations under the License. */ -package com.zxingcpp +package zxingcpp import android.graphics.Bitmap import android.graphics.ImageFormat From 22b64ffd398e841aac7f84506c6fd39692ba40de Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 1 Dec 2023 10:11:11 +0100 Subject: [PATCH 117/587] Create publish-android.yml --- .github/workflows/publish-android.yml | 32 +++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 .github/workflows/publish-android.yml diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml new file mode 100644 index 0000000000..912602bd47 --- /dev/null +++ b/.github/workflows/publish-android.yml @@ -0,0 +1,32 @@ +name: publish-adroid + +on: +# release: +# types: [published] + + workflow_dispatch: + inputs: + publish: + description: 'Publish package (y/n)' + default: 'n' + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '17' +# - name: Grant Permission to Execute +# run: chmod +x gradlew +# - name: New version +# run: ./gradlew versionDisplay + - name: Publish Library + env: + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} + ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} + run: ./gradlew publishAllPublicationsToSonatypeRepository From 26f3a3d8581d76562c6a94fd0456b03e7484926a Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 1 Dec 2023 10:16:37 +0100 Subject: [PATCH 118/587] publish-android.yml: add missing working directory --- .github/workflows/publish-android.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index 912602bd47..7f50bff7a3 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -20,10 +20,12 @@ jobs: distribution: 'temurin' java-version: '17' # - name: Grant Permission to Execute -# run: chmod +x gradlew +# run: chmod +x wrappers/android/gradlew # - name: New version +# working-directory: wrappers/android # run: ./gradlew versionDisplay - name: Publish Library + working-directory: wrappers/android env: ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }} ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} From 5505eeb24cbdaeeb7aeea51ce3207bee61fba59b Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 1 Dec 2023 15:50:49 +0100 Subject: [PATCH 119/587] publish-android.yml: cosmetic changes --- .github/workflows/publish-android.yml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index 7f50bff7a3..004b41caf9 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -19,9 +19,6 @@ jobs: with: distribution: 'temurin' java-version: '17' -# - name: Grant Permission to Execute -# run: chmod +x wrappers/android/gradlew -# - name: New version # working-directory: wrappers/android # run: ./gradlew versionDisplay - name: Publish Library @@ -31,4 +28,4 @@ jobs: ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} - run: ./gradlew publishAllPublicationsToSonatypeRepository + run: ./gradlew publishRleasePublicationsToSonatypeRepository From 50aefbbaf0932ee061bb7f844567f1cce3a22794 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 2 Dec 2023 22:19:29 +0100 Subject: [PATCH 120/587] android: simply `C2JString` and drop JNIUtils.* --- .../zxingcpp/src/main/cpp/CMakeLists.txt | 6 +- .../zxingcpp/src/main/cpp/JNIUtils.cpp | 73 ------------------- .../android/zxingcpp/src/main/cpp/JNIUtils.h | 22 ------ .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 28 ++++++- .../src/main/java/zxingcpp/ZXingCpp.kt | 2 +- 5 files changed, 31 insertions(+), 100 deletions(-) delete mode 100644 wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp delete mode 100644 wrappers/android/zxingcpp/src/main/cpp/JNIUtils.h diff --git a/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt b/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt index 1f45f4a225..f8b65ae02b 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt +++ b/wrappers/android/zxingcpp/src/main/cpp/CMakeLists.txt @@ -1,5 +1,5 @@ cmake_minimum_required(VERSION 3.14) -project(ZXingAndroid) +project(ZXingCppAndroid) # The zxing-cpp requires at least C++17 to build set(CMAKE_CXX_STANDARD 20) @@ -10,7 +10,7 @@ set(BUILD_WRITERS OFF) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../../../../../core ZXing EXCLUDE_FROM_ALL) -add_library(zxing_android SHARED ZXingCpp.cpp JNIUtils.cpp) +add_library(zxingcpp_android SHARED ZXingCpp.cpp) -target_link_libraries(zxing_android PRIVATE ZXing::ZXing log jnigraphics) +target_link_libraries(zxingcpp_android PRIVATE ZXing::ZXing log jnigraphics) diff --git a/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp b/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp deleted file mode 100644 index a6e2aade67..0000000000 --- a/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.cpp +++ /dev/null @@ -1,73 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -*/ -// SPDX-License-Identifier: Apache-2.0 -#include "JNIUtils.h" - -#include "Utf.h" - -#include -#include - -static bool RequiresSurrogates(uint32_t ucs4) -{ - return ucs4 >= 0x10000; -} - -static uint16_t HighSurrogate(uint32_t ucs4) -{ - return uint16_t((ucs4 >> 10) + 0xd7c0); -} - -static uint16_t LowSurrogate(uint32_t ucs4) -{ - return uint16_t(ucs4 % 0x400 + 0xdc00); -} - -static void Utf32toUtf16(const uint32_t* utf32, size_t length, std::vector& result) -{ - result.clear(); - result.reserve(length); - for (size_t i = 0; i < length; ++i) { - uint32_t c = utf32[i]; - if (RequiresSurrogates(c)) { - result.push_back(HighSurrogate(c)); - result.push_back(LowSurrogate(c)); - } else { - result.push_back(c); - } - } -} - -jstring C2JString(JNIEnv* env, const std::wstring& str) -{ - if (env->ExceptionCheck()) - return 0; - - if constexpr (sizeof(wchar_t) == 2) { - return env->NewString((const jchar*)str.data(), str.size()); - } else { - std::vector buffer; - Utf32toUtf16((const uint32_t*)str.data(), str.size(), buffer); - return env->NewString((const jchar*)buffer.data(), buffer.size()); - } -} - -jstring C2JString(JNIEnv* env, const std::string& str) -{ - return C2JString(env, ZXing::FromUtf8(str)); -} - -std::string J2CString(JNIEnv* env, jstring str) -{ - // Buffer size must be in bytes. - const jsize size = env->GetStringUTFLength(str); - std::string res(size, 0); - - // Translates 'len' number of Unicode characters into modified - // UTF-8 encoding and place the result in the given buffer. - const jsize len = env->GetStringLength(str); - env->GetStringUTFRegion(str, 0, len, res.data()); - - return res; -} diff --git a/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.h b/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.h deleted file mode 100644 index ed1391e022..0000000000 --- a/wrappers/android/zxingcpp/src/main/cpp/JNIUtils.h +++ /dev/null @@ -1,22 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -*/ -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include -#include - -#include - -#define ZX_LOG_TAG "ZXing" - -#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, ZX_LOG_TAG, __VA_ARGS__) -#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, ZX_LOG_TAG, __VA_ARGS__) -#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, ZX_LOG_TAG, __VA_ARGS__) -#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, ZX_LOG_TAG, __VA_ARGS__) - -jstring C2JString(JNIEnv* env, const std::wstring& str); -jstring C2JString(JNIEnv* env, const std::string& str); -std::string J2CString(JNIEnv* env, jstring str); diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 5e590cf608..63218bcdcd 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -3,19 +3,26 @@ */ // SPDX-License-Identifier: Apache-2.0 -#include "JNIUtils.h" #include "ReadBarcode.h" #include +#include #include #include #include +#include using namespace ZXing; using namespace std::string_literals; #define PACKAGE "zxingcpp/ZXingCpp$" +#define ZX_LOG_TAG "zxingcpp" +#define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, ZX_LOG_TAG, __VA_ARGS__) +#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, ZX_LOG_TAG, __VA_ARGS__) +#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, ZX_LOG_TAG, __VA_ARGS__) +#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, ZX_LOG_TAG, __VA_ARGS__) + static const char* JavaBarcodeFormatName(BarcodeFormat format) { // These have to be the names of the enum constants in the kotlin code. @@ -121,6 +128,25 @@ static jstring ThrowJavaException(JNIEnv* env, const char* message) return nullptr; } +jstring C2JString(JNIEnv* env, const std::string& str) +{ + return env->NewStringUTF(str.c_str()); +} + +std::string J2CString(JNIEnv* env, jstring str) +{ + // Buffer size must be in bytes. + const jsize size = env->GetStringUTFLength(str); + std::string res(size, 0); + + // Translates 'len' number of Unicode characters into modified + // UTF-8 encoding and place the result in the given buffer. + const jsize len = env->GetStringLength(str); + env->GetStringUTFRegion(str, 0, len, res.data()); + + return res; +} + static jobject NewPosition(JNIEnv* env, const Position& position) { jclass clsPosition = env->FindClass(PACKAGE "Position"); diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt index 2affa8b98c..60179b543b 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt @@ -33,7 +33,7 @@ public object ZXingCpp { } init { - System.loadLibrary("zxing_android") + System.loadLibrary("zxingcpp_android") } // Enumerates barcode formats known to this package. From 06082d5a54211a39740b3d10e9a74853dac5df4a Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 2 Dec 2023 22:37:17 +0100 Subject: [PATCH 121/587] android: further align Kotlin API with C++ API When `BarcodeReader` was renamed to `ZXingCpp` it would have been appropriate to also rename `Format` to `BarcodeFormat` and `read` to `readBarcodes`. Doing that now to be even more consistent with the C++ API. Also making the `DecodeHints` argument to `readBarcodes` optional (just like in the C++ API). --- .../app/src/main/java/zxingcpp/app/MainActivity.kt | 7 +++---- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 12 ++++++------ .../zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt | 12 ++++++------ 3 files changed, 15 insertions(+), 16 deletions(-) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 36e233a829..7e49ae83fd 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -55,8 +55,7 @@ import java.io.File import java.util.concurrent.Executors import zxingcpp.app.databinding.ActivityCameraBinding import zxingcpp.ZXingCpp -import zxingcpp.ZXingCpp.Format - +import zxingcpp.ZXingCpp.BarcodeFormat.* class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityCameraBinding @@ -239,7 +238,7 @@ class MainActivity : AppCompatActivity() { } } else { decodeHints.apply { - formats = if (binding.qrcode.isChecked) setOf(Format.QR_CODE) else setOf() + formats = if (binding.qrcode.isChecked) setOf(QR_CODE) else setOf() tryHarder = binding.tryHarder.isChecked tryRotate = binding.tryRotate.isChecked tryInvert = binding.tryInvert.isChecked @@ -249,7 +248,7 @@ class MainActivity : AppCompatActivity() { resultText = try { image.use { - ZXingCpp.read(it, decodeHints) + ZXingCpp.readBarcodes(it, decodeHints) }.apply { runtime2 += firstOrNull()?.time ?: 0 }.joinToString("\n") { result -> diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 63218bcdcd..74885a667c 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -45,7 +45,7 @@ static const char* JavaBarcodeFormatName(BarcodeFormat format) case BarcodeFormat::DataBarExpanded: return "DATA_BAR_EXPANDED"; case BarcodeFormat::UPCA: return "UPC_A"; case BarcodeFormat::UPCE: return "UPC_E"; - default: throw std::invalid_argument("Invalid format"); + default: throw std::invalid_argument("Invalid BarcodeFormat"); } } @@ -199,7 +199,7 @@ static jobject NewResult(JNIEnv* env, const Result& result, int time) jclass cls = env->FindClass(PACKAGE "Result"); jmethodID midInit = env->GetMethodID( cls, "", - "(L" PACKAGE "Format;" + "(L" PACKAGE "BarcodeFormat;" "[B" "Ljava/lang/String;" "L" PACKAGE "ContentType;" @@ -216,7 +216,7 @@ static jobject NewResult(JNIEnv* env, const Result& result, int time) "I)V"); bool valid = result.isValid(); return env->NewObject(cls, midInit, - NewEnum(env, JavaBarcodeFormatName(result.format()), "Format"), + NewEnum(env, JavaBarcodeFormatName(result.format()), "BarcodeFormat"), valid ? NewByteArray(env, result.bytes()) : nullptr, valid ? C2JString(env, result.text()) : nullptr, NewEnum(env, JavaContentTypeName(result.contentType()), "ContentType"), @@ -284,7 +284,7 @@ static BarcodeFormats GetFormats(JNIEnv* env, jclass hintClass, jobject hints) if (!objArray) return {}; - jmethodID midName = env->GetMethodID(env->FindClass(PACKAGE "Format"), "name", "()Ljava/lang/String;"); + jmethodID midName = env->GetMethodID(env->FindClass(PACKAGE "BarcodeFormat"), "name", "()Ljava/lang/String;"); BarcodeFormats ret; for (int i = 0, size = env->GetArrayLength(objArray); i < size; ++i) { auto objName = static_cast(env->CallObjectMethod(env->GetObjectArrayElement(objArray, i), midName)); @@ -363,13 +363,13 @@ Java_zxingcpp_ZXingCpp_readBitmap( switch (bmInfo.format) { case ANDROID_BITMAP_FORMAT_A_8: fmt = ImageFormat::Lum; break; case ANDROID_BITMAP_FORMAT_RGBA_8888: fmt = ImageFormat::RGBX; break; - default: return ThrowJavaException(env, "Unsupported format"); + default: return ThrowJavaException(env, "Unsupported image format in AndroidBitmap"); } auto pixels = LockedPixels(env, bitmap); if (!pixels) - return ThrowJavaException(env, "Failed to lock/Read AndroidBitmap data"); + return ThrowJavaException(env, "Failed to lock/read AndroidBitmap data"); auto image = ImageView{pixels, (int)bmInfo.width, (int)bmInfo.height, fmt, (int)bmInfo.stride} diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt index 60179b543b..43e2e87d91 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt @@ -38,7 +38,7 @@ public object ZXingCpp { // Enumerates barcode formats known to this package. // Note that this has to be kept synchronized with native (C++/JNI) side. - public enum class Format { + public enum class BarcodeFormat { NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E } @@ -64,7 +64,7 @@ public object ZXingCpp { } public data class DecodeHints( - var formats: Set = setOf(), + var formats: Set = setOf(), var tryHarder: Boolean = false, var tryRotate: Boolean = false, var tryInvert: Boolean = false, @@ -98,7 +98,7 @@ public object ZXingCpp { ) public data class Result( - val format: Format, + val format: BarcodeFormat, val bytes: ByteArray?, val text: String?, val contentType: ContentType, @@ -115,7 +115,7 @@ public object ZXingCpp { val time: Int // for development/debug purposes only ) - public fun read(image: ImageProxy, hints: DecodeHints): List { + public fun readBarcodes(image: ImageProxy, hints: DecodeHints = DecodeHints()): List { check(image.format in supportedYUVFormats) { "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" } @@ -132,8 +132,8 @@ public object ZXingCpp { ) } - public fun read( - bitmap: Bitmap, hints: DecodeHints, cropRect: Rect = Rect(), rotation: Int = 0 + public fun readBarcodes( + bitmap: Bitmap, hints: DecodeHints = DecodeHints(), cropRect: Rect = Rect(), rotation: Int = 0 ): List { return readBitmap( bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, hints From 2a9b97ea9e9cfd5a753167d9141a578978712a69 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 01:00:21 +0100 Subject: [PATCH 122/587] android: revert back to "reader object" API See https://github.com/zxing-cpp/zxing-cpp/discussions/678 --- .../main/java/zxingcpp/app/MainActivity.kt | 13 +++++----- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 20 ++++++++-------- .../{ZXingCpp.kt => BarcodeReader.kt} | 24 +++++++++---------- 3 files changed, 29 insertions(+), 28 deletions(-) rename wrappers/android/zxingcpp/src/main/java/zxingcpp/{ZXingCpp.kt => BarcodeReader.kt} (88%) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 7e49ae83fd..41f3cb27d1 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -54,8 +54,8 @@ import java.io.ByteArrayOutputStream import java.io.File import java.util.concurrent.Executors import zxingcpp.app.databinding.ActivityCameraBinding -import zxingcpp.ZXingCpp -import zxingcpp.ZXingCpp.BarcodeFormat.* +import zxingcpp.BarcodeReader +import zxingcpp.BarcodeReader.Format.* class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityCameraBinding @@ -64,7 +64,6 @@ class MainActivity : AppCompatActivity() { private val permissions = mutableListOf(Manifest.permission.CAMERA) private val permissionsRequestCode = 1 private val beeper = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 50) - private val decodeHints = ZXingCpp.DecodeHints() private var lastText = String() private var doSaveImage: Boolean = false @@ -151,6 +150,8 @@ class MainActivity : AppCompatActivity() { var runtimes: Long = 0 var runtime2: Long = 0 val readerJava = MultiFormatReader() + val readerCpp = BarcodeReader() + // Create a new camera selector each time, enforcing lens facing val cameraSelector = @@ -237,7 +238,7 @@ class MainActivity : AppCompatActivity() { if (e.toString() != "com.google.zxing.NotFoundException") e.toString() else "" } } else { - decodeHints.apply { + readerCpp.options.apply { formats = if (binding.qrcode.isChecked) setOf(QR_CODE) else setOf() tryHarder = binding.tryHarder.isChecked tryRotate = binding.tryRotate.isChecked @@ -248,7 +249,7 @@ class MainActivity : AppCompatActivity() { resultText = try { image.use { - ZXingCpp.readBarcodes(it, decodeHints) + readerCpp.read(it) }.apply { runtime2 += firstOrNull()?.time ?: 0 }.joinToString("\n") { result -> @@ -260,7 +261,7 @@ class MainActivity : AppCompatActivity() { }) } "${result.format} (${result.contentType}): ${ - if (result.contentType != ZXingCpp.ContentType.BINARY) { + if (result.contentType != BarcodeReader.ContentType.BINARY) { result.text } else { result.bytes!!.joinToString(separator = "") { v -> "%02x".format(v) } diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 74885a667c..fd715fe1ae 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -15,7 +15,7 @@ using namespace ZXing; using namespace std::string_literals; -#define PACKAGE "zxingcpp/ZXingCpp$" +#define PACKAGE "zxingcpp/BarcodeReader$" #define ZX_LOG_TAG "zxingcpp" #define LOGV(...) __android_log_print(ANDROID_LOG_VERBOSE, ZX_LOG_TAG, __VA_ARGS__) @@ -199,7 +199,7 @@ static jobject NewResult(JNIEnv* env, const Result& result, int time) jclass cls = env->FindClass(PACKAGE "Result"); jmethodID midInit = env->GetMethodID( cls, "", - "(L" PACKAGE "BarcodeFormat;" + "(L" PACKAGE "Format;" "[B" "Ljava/lang/String;" "L" PACKAGE "ContentType;" @@ -216,7 +216,7 @@ static jobject NewResult(JNIEnv* env, const Result& result, int time) "I)V"); bool valid = result.isValid(); return env->NewObject(cls, midInit, - NewEnum(env, JavaBarcodeFormatName(result.format()), "BarcodeFormat"), + NewEnum(env, JavaBarcodeFormatName(result.format()), "Format"), valid ? NewByteArray(env, result.bytes()) : nullptr, valid ? C2JString(env, result.text()) : nullptr, NewEnum(env, JavaContentTypeName(result.contentType()), "ContentType"), @@ -284,7 +284,7 @@ static BarcodeFormats GetFormats(JNIEnv* env, jclass hintClass, jobject hints) if (!objArray) return {}; - jmethodID midName = env->GetMethodID(env->FindClass(PACKAGE "BarcodeFormat"), "name", "()Ljava/lang/String;"); + jmethodID midName = env->GetMethodID(env->FindClass(PACKAGE "Format"), "name", "()Ljava/lang/String;"); BarcodeFormats ret; for (int i = 0, size = env->GetArrayLength(objArray); i < size; ++i) { auto objName = static_cast(env->CallObjectMethod(env->GetObjectArrayElement(objArray, i), midName)); @@ -319,9 +319,9 @@ static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) } extern "C" JNIEXPORT jobject JNICALL -Java_zxingcpp_ZXingCpp_readYBuffer( +Java_zxingcpp_BarcodeReader_readYBuffer( JNIEnv *env, jobject thiz, jobject yBuffer, jint rowStride, - jint left, jint top, jint width, jint height, jint rotation, jobject hints) + jint left, jint top, jint width, jint height, jint rotation, jobject options) { const uint8_t* pixels = static_cast(env->GetDirectBufferAddress(yBuffer)); @@ -329,7 +329,7 @@ Java_zxingcpp_ZXingCpp_readYBuffer( ImageView{pixels + top * rowStride + left, width, height, ImageFormat::Lum, rowStride} .rotated(rotation); - return Read(env, image, CreateDecodeHints(env, hints)); + return Read(env, image, CreateDecodeHints(env, options)); } struct LockedPixels @@ -352,9 +352,9 @@ struct LockedPixels }; extern "C" JNIEXPORT jobject JNICALL -Java_zxingcpp_ZXingCpp_readBitmap( +Java_zxingcpp_BarcodeReader_readBitmap( JNIEnv* env, jobject thiz, jobject bitmap, - jint left, jint top, jint width, jint height, jint rotation, jobject hints) + jint left, jint top, jint width, jint height, jint rotation, jobject options) { AndroidBitmapInfo bmInfo; AndroidBitmap_getInfo(env, bitmap, &bmInfo); @@ -376,5 +376,5 @@ Java_zxingcpp_ZXingCpp_readBitmap( .cropped(left, top, width, height) .rotated(rotation); - return Read(env, image, CreateDecodeHints(env, hints)); + return Read(env, image, CreateDecodeHints(env, options)); } diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt similarity index 88% rename from wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt rename to wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index 43e2e87d91..5891991e93 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/ZXingCpp.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -24,7 +24,7 @@ import android.os.Build import androidx.camera.core.ImageProxy import java.nio.ByteBuffer -public object ZXingCpp { +public class BarcodeReader(public var options: Options = Options()) { private val supportedYUVFormats: List = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { listOf(ImageFormat.YUV_420_888, ImageFormat.YUV_422_888, ImageFormat.YUV_444_888) @@ -38,7 +38,7 @@ public object ZXingCpp { // Enumerates barcode formats known to this package. // Note that this has to be kept synchronized with native (C++/JNI) side. - public enum class BarcodeFormat { + public enum class Format { NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E } @@ -63,8 +63,8 @@ public object ZXingCpp { FORMAT, CHECKSUM, UNSUPPORTED } - public data class DecodeHints( - var formats: Set = setOf(), + public data class Options( + var formats: Set = setOf(), var tryHarder: Boolean = false, var tryRotate: Boolean = false, var tryInvert: Boolean = false, @@ -98,7 +98,7 @@ public object ZXingCpp { ) public data class Result( - val format: BarcodeFormat, + val format: Format, val bytes: ByteArray?, val text: String?, val contentType: ContentType, @@ -115,7 +115,7 @@ public object ZXingCpp { val time: Int // for development/debug purposes only ) - public fun readBarcodes(image: ImageProxy, hints: DecodeHints = DecodeHints()): List { + public fun read(image: ImageProxy): List { check(image.format in supportedYUVFormats) { "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" } @@ -128,23 +128,23 @@ public object ZXingCpp { image.cropRect.width(), image.cropRect.height(), image.imageInfo.rotationDegrees, - hints + options ) } - public fun readBarcodes( - bitmap: Bitmap, hints: DecodeHints = DecodeHints(), cropRect: Rect = Rect(), rotation: Int = 0 + public fun read( + bitmap: Bitmap, cropRect: Rect = Rect(), rotation: Int = 0 ): List { return readBitmap( - bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, hints + bitmap, cropRect.left, cropRect.top, cropRect.width(), cropRect.height(), rotation, options ) } private external fun readYBuffer( - yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, hints: DecodeHints + yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, hints: Options ): List private external fun readBitmap( - bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, hints: DecodeHints + bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, hints: Options ): List } From 52c772bedc3750f1933e4d8c8030c0ca11bc6a56 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 01:06:34 +0100 Subject: [PATCH 123/587] android: use hash helper to support switching on strings --- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 65 +++++++++---------- 1 file changed, 32 insertions(+), 33 deletions(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index fd715fe1ae..31df3df090 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -11,6 +11,7 @@ #include #include #include +#include using namespace ZXing; using namespace std::string_literals; @@ -74,48 +75,46 @@ static const char* JavaErrorTypeName(Error::Type errorType) } } -static EanAddOnSymbol EanAddOnSymbolFromString(const std::string& name) +inline constexpr auto hash(std::string_view sv) { - if (name == "IGNORE") { - return EanAddOnSymbol::Ignore; - } else if (name == "READ") { - return EanAddOnSymbol::Read; - } else if (name == "REQUIRE") { - return EanAddOnSymbol::Require; - } else { - throw std::invalid_argument("Invalid eanAddOnSymbol name"); + unsigned int hash = 5381; + for (unsigned char c : sv) + hash = ((hash << 5) + hash) ^ c; + return hash; +} + +inline constexpr auto operator "" _h(const char* str, size_t len){ return hash({str, len}); } + +static EanAddOnSymbol EanAddOnSymbolFromString(std::string_view name) +{ + switch (hash(name)) { + case "IGNORE"_h : return EanAddOnSymbol::Ignore; + case "READ"_h : return EanAddOnSymbol::Read; + case "REQUIRE"_h : return EanAddOnSymbol::Require; + default: throw std::invalid_argument("Invalid eanAddOnSymbol name"); } } -static Binarizer BinarizerFromString(const std::string& name) +static Binarizer BinarizerFromString(std::string_view name) { - if (name == "LOCAL_AVERAGE") { - return Binarizer::LocalAverage; - } else if (name == "GLOBAL_HISTOGRAM") { - return Binarizer::GlobalHistogram; - } else if (name == "FIXED_THRESHOLD") { - return Binarizer::FixedThreshold; - } else if (name == "BOOL_CAST") { - return Binarizer::BoolCast; - } else { - throw std::invalid_argument("Invalid binarizer name"); + switch (hash(name)) { + case "LOCAL_AVERAGE"_h : return Binarizer::LocalAverage; + case "GLOBAL_HISTOGRAM"_h : return Binarizer::GlobalHistogram; + case "FIXED_THRESHOLD"_h : return Binarizer::FixedThreshold; + case "BOOL_CAST"_h : return Binarizer::BoolCast; + default: throw std::invalid_argument("Invalid binarizer name"); } } -static TextMode TextModeFromString(const std::string& name) +static TextMode TextModeFromString(std::string_view name) { - if (name == "PLAIN") { - return TextMode::Plain; - } else if (name == "ECI") { - return TextMode::ECI; - } else if (name == "HRI") { - return TextMode::HRI; - } else if (name == "HEX") { - return TextMode::Hex; - } else if (name == "ESCAPED") { - return TextMode::Escaped; - } else { - throw std::invalid_argument("Invalid textMode name"); + switch (hash(name)) { + case "PLAIN"_h : return TextMode::Plain; + case "ECI"_h : return TextMode::ECI; + case "HRI"_h : return TextMode::HRI; + case "HEX"_h : return TextMode::Hex; + case "ESCAPED"_h : return TextMode::Escaped; + default: throw std::invalid_argument("Invalid textMode name"); } } From 1803fd79cbd3144400a83f4780c932187d6ef970 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 01:31:17 +0100 Subject: [PATCH 124/587] android: switch from 'single' to 'multiSymbol' option in GUI --- .../app/src/main/java/zxingcpp/app/MainActivity.kt | 2 +- .../app/src/main/res/layout-land/activity_camera.xml | 8 ++++---- .../android/app/src/main/res/layout/activity_camera.xml | 8 ++++---- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 41f3cb27d1..2f337d59c8 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -244,7 +244,7 @@ class MainActivity : AppCompatActivity() { tryRotate = binding.tryRotate.isChecked tryInvert = binding.tryInvert.isChecked tryDownscale = binding.tryDownscale.isChecked - maxNumberOfSymbols = if (binding.single.isChecked) 1 else 255 + maxNumberOfSymbols = if (binding.multiSymbol.isChecked) 255 else 1 } resultText = try { diff --git a/wrappers/android/app/src/main/res/layout-land/activity_camera.xml b/wrappers/android/app/src/main/res/layout-land/activity_camera.xml index 75efb08ad2..50f1cb7c31 100644 --- a/wrappers/android/app/src/main/res/layout-land/activity_camera.xml +++ b/wrappers/android/app/src/main/res/layout-land/activity_camera.xml @@ -100,14 +100,14 @@ android:text="tryDownscale" /> + android:text="multiSymbol" /> + android:text="crop" /> + android:text="multiSymbol" /> + android:text="crop" /> Date: Mon, 4 Dec 2023 01:33:27 +0100 Subject: [PATCH 125/587] android: update README with install and usage examples --- wrappers/android/README.md | 31 +++++++++++++++++++++++++++---- 1 file changed, 27 insertions(+), 4 deletions(-) diff --git a/wrappers/android/README.md b/wrappers/android/README.md index 448f53e871..c324580e1d 100644 --- a/wrappers/android/README.md +++ b/wrappers/android/README.md @@ -1,10 +1,32 @@ # ZXing-C++ Android Library -To use the Android (wrapper) library in other apps, it is easiest -to build the library project and include the resulting AAR (Android -Archive) file in your app. +## Install -## Build +The easiest way to use the library is to fetch if from _mavenCentral_. Simply add +```gradle +implementation("io.github.zxing-cpp:android:2.2.0") +``` +to your `build.gradle.kts` file in the `dependencies` section. + +## Use + +A trivial use case looks like this (in Kotlin): + +```kotlin +import zxingcpp.BarcodeReader + +var barcodeReader = BarcodeReader() + +fun process(image: ImageProxy) { + image.use { + barcodeReader.read(it) + }.joinToString("\n") { result -> + "${result.format} (${result.contentType}): ${result.text}" + } +} +``` + +## Build locally 1. Install AndroidStudio including NDK and CMake (see 'SDK Tools'). 2. Open the project in folder containing this README. @@ -16,3 +38,4 @@ To build the AAR (Android Archive) from the command line: Then copy `zxingcpp/build/outputs/aar/zxingcpp-release.aar` into `app/libs` of your app. + From f1004c1d3ceb0d5b8b891c8643ad314ecca35451 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 12:35:51 +0100 Subject: [PATCH 126/587] DecodeHints: remove empty .cpp file --- core/CMakeLists.txt | 1 - core/src/DecodeHints.cpp | 11 ----------- 2 files changed, 12 deletions(-) delete mode 100644 core/src/DecodeHints.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index ab8f8ab8cc..d1e45438ce 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -105,7 +105,6 @@ if (BUILD_READERS) src/Content.h src/Content.cpp src/DecodeHints.h - src/DecodeHints.cpp src/DecoderResult.h src/DetectorResult.h src/Error.h diff --git a/core/src/DecodeHints.cpp b/core/src/DecodeHints.cpp deleted file mode 100644 index 72ded6073c..0000000000 --- a/core/src/DecodeHints.cpp +++ /dev/null @@ -1,11 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "DecodeHints.h" - -namespace ZXing { - -} // ZXing From b0d8cc4858da26cda16fd000bd64c57b186b8a91 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 12:36:36 +0100 Subject: [PATCH 127/587] QREncoder: fix weird "return throw" statement --- core/src/qrcode/QREncoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/qrcode/QREncoder.cpp b/core/src/qrcode/QREncoder.cpp index 89d7df1398..bcf14855fd 100644 --- a/core/src/qrcode/QREncoder.cpp +++ b/core/src/qrcode/QREncoder.cpp @@ -136,7 +136,7 @@ void AppendLengthInfo(int numLetters, const Version& version, CodecMode mode, Bi { int numBits = CharacterCountBits(mode, version); if (numLetters >= (1 << numBits)) { - return throw std::invalid_argument(std::to_string(numLetters) + " is bigger than " + std::to_string((1 << numBits) - 1)); + throw std::invalid_argument(std::to_string(numLetters) + " is bigger than " + std::to_string((1 << numBits) - 1)); } bits.appendBits(numLetters, numBits); } From 50ca4f91a85cda41020555e8978333fff327ef3c Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 15:51:38 +0100 Subject: [PATCH 128/587] BitMatrix: new ToString() implementation with fancy unicode blocks MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This is how it would look like in a commit message: █▄▀ █ ▀ █▄▀ █▄▀▄▀▄ █ ▄▄█ █ █ ▄█▄▄ █ ▄ ▄██ █ █ █ ██ █▄▄▄ ▀ █▄▀▀▄██████ ████▀▄▄▀ ▀██████ █▄██▀ █▀▀ ▀▀█▄███▄ █▄█▄▀▄▀▀ ▀▄▄█ ▄ █▄ █ ▀▄▄ ▀▄ ▄█▀█ █▄ ██▄█████▄█▄▄▄▄█▄█▄ This is inspired/stolen from @markusfisch android wrapper code. Thanks ;). --- core/src/BitMatrixIO.cpp | 18 ++++++++++++++++++ core/src/BitMatrixIO.h | 3 ++- test/unit/qrcode/QREncoderTest.cpp | 14 +++++++------- 3 files changed, 27 insertions(+), 8 deletions(-) diff --git a/core/src/BitMatrixIO.cpp b/core/src/BitMatrixIO.cpp index 685e9cf236..08678084af 100644 --- a/core/src/BitMatrixIO.cpp +++ b/core/src/BitMatrixIO.cpp @@ -6,6 +6,7 @@ #include "BitMatrixIO.h" +#include #include #include @@ -30,6 +31,23 @@ std::string ToString(const BitMatrix& matrix, char one, char zero, bool addSpace return result; } +std::string ToString(const BitMatrix& matrix, bool inverted) +{ + constexpr auto map = std::array{" ", "▀", "▄", "█"}; + std::string res; + + for (int y = 0; y < matrix.height(); y += 2) { + for (int x = 0; x < matrix.width(); ++x) { + int tp = matrix.get(x, y) ^ inverted; + int bt = (matrix.height() == 1 && tp) || (y + 1 < matrix.height() && (matrix.get(x, y + 1) ^ inverted)); + res += map[tp | (bt << 1)]; + } + res.push_back('\n'); + } + + return res; +} + std::string ToSVG(const BitMatrix& matrix) { // see https://stackoverflow.com/questions/10789059/create-qr-code-in-vector-image/60638350#60638350 diff --git a/core/src/BitMatrixIO.h b/core/src/BitMatrixIO.h index 022b48ec35..d4b695c7d5 100644 --- a/core/src/BitMatrixIO.h +++ b/core/src/BitMatrixIO.h @@ -12,7 +12,8 @@ namespace ZXing { -std::string ToString(const BitMatrix& matrix, char one = 'X', char zero = ' ', bool addSpace = true, bool printAsCString = false); +std::string ToString(const BitMatrix& matrix, bool inverted = false); +std::string ToString(const BitMatrix& matrix, char one, char zero = ' ', bool addSpace = true, bool printAsCString = false); std::string ToSVG(const BitMatrix& matrix); BitMatrix ParseBitMatrix(const std::string& str, char one = 'X', bool expectSpace = true); void SaveAsPBM(const BitMatrix& matrix, const std::string filename, int quietZone = 0); diff --git a/test/unit/qrcode/QREncoderTest.cpp b/test/unit/qrcode/QREncoderTest.cpp index d438a6b3b4..2f640d4674 100644 --- a/test/unit/qrcode/QREncoderTest.cpp +++ b/test/unit/qrcode/QREncoderTest.cpp @@ -118,7 +118,7 @@ TEST(QREncoderTest, Encode) ASSERT_NE(qrCode.version, nullptr); EXPECT_EQ(qrCode.version->versionNumber(), 1); EXPECT_EQ(qrCode.maskPattern, 4); - EXPECT_EQ(ToString(qrCode.matrix), + EXPECT_EQ(ToString(qrCode.matrix, 'X', ' ', true), "X X X X X X X X X X X X X X X X \n" "X X X X X X X \n" "X X X X X X X X X X \n" @@ -164,7 +164,7 @@ TEST(QREncoderTest, SimpleUTF8ECI) ASSERT_NE(qrCode.version, nullptr); EXPECT_EQ(qrCode.version->versionNumber(), 1); EXPECT_EQ(qrCode.maskPattern, 6); - EXPECT_EQ(ToString(qrCode.matrix), // break the line comment + EXPECT_EQ(ToString(qrCode.matrix, 'X', ' ', true), "X X X X X X X X X X X X X X X X \n" "X X X X X X \n" "X X X X X X X X X X X X X \n" @@ -196,7 +196,7 @@ TEST(QREncoderTest, SimpleBINARYECI) ASSERT_NE(qrCode.version, nullptr); EXPECT_EQ(qrCode.version->versionNumber(), 1); EXPECT_EQ(qrCode.maskPattern, 6); - EXPECT_EQ(ToString(qrCode.matrix), + EXPECT_EQ(ToString(qrCode.matrix, 'X', ' ', true), "X X X X X X X X X X X X X X X X X X \n" "X X X X X \n" "X X X X X X X X X X X X X \n" @@ -228,7 +228,7 @@ TEST(QREncoderTest, EncodeKanjiMode) ASSERT_NE(qrCode.version, nullptr); EXPECT_EQ(qrCode.version->versionNumber(), 1); EXPECT_EQ(qrCode.maskPattern, 0); - EXPECT_EQ(ToString(qrCode.matrix), + EXPECT_EQ(ToString(qrCode.matrix, 'X', ' ', true), "X X X X X X X X X X X X X X X X \n" "X X X X X X \n" "X X X X X X X X X X X X X X \n" @@ -260,7 +260,7 @@ TEST(QREncoderTest, EncodeShiftjisNumeric) ASSERT_NE(qrCode.version, nullptr); EXPECT_EQ(qrCode.version->versionNumber(), 1); EXPECT_EQ(qrCode.maskPattern, 2); - EXPECT_EQ(ToString(qrCode.matrix), + EXPECT_EQ(ToString(qrCode.matrix, 'X', ' ', true), "X X X X X X X X X X X X X X X X X \n" "X X X X X X \n" "X X X X X X X X X X X \n" @@ -292,7 +292,7 @@ TEST(QREncoderTest, EncodeGS1) ASSERT_NE(qrCode.version, nullptr); EXPECT_EQ(qrCode.version->versionNumber(), 2); EXPECT_EQ(qrCode.maskPattern, 4); - EXPECT_EQ(ToString(qrCode.matrix), + EXPECT_EQ(ToString(qrCode.matrix, 'X', ' ', true), "X X X X X X X X X X X X X X X X X X X X \n" "X X X X X X X X \n" "X X X X X X X X X X X X X X \n" @@ -328,7 +328,7 @@ TEST(QREncoderTest, EncodeGS1ModeHeaderWithECI) ASSERT_NE(qrCode.version, nullptr); EXPECT_EQ(qrCode.version->versionNumber(), 1); EXPECT_EQ(qrCode.maskPattern, 5); - EXPECT_EQ(ToString(qrCode.matrix), + EXPECT_EQ(ToString(qrCode.matrix, 'X', ' ', true), "X X X X X X X X X X X X X X X X X \n" "X X X X X X \n" "X X X X X X X X X X X X X \n" From 6fb451b2657bad409cfd67efb69b2e4ab6a53c4e Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 18:20:52 +0100 Subject: [PATCH 129/587] python-build.yml: upload to test.pypi.org --- .github/workflows/python-build.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index 61e9c2d7c1..2c67d28a69 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -88,5 +88,5 @@ jobs: - uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ - password: ${{ secrets.PYPI_TOKEN }} - # To test: repository_url: https://test.pypi.org/legacy/ + password: ${{ secrets.TEST_PYPI_TOKEN }} + repository_url: https://test.pypi.org/legacy/ From 8931dc0853bbda4de5be1e60b2d7a616f07c87d8 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 23:04:40 +0100 Subject: [PATCH 130/587] c++: fix a few random -Wcomma warnings --- core/src/DecodeHints.h | 8 ++++---- core/src/Utf.cpp | 6 ++++-- test/unit/ZXAlgorithmsTest.cpp | 2 +- 3 files changed, 9 insertions(+), 7 deletions(-) diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index 2aa254963a..2bdaba3a30 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -98,8 +98,8 @@ class DecodeHints #define ZX_PROPERTY(TYPE, GETTER, SETTER) \ TYPE GETTER() const noexcept { return _##GETTER; } \ - DecodeHints& SETTER(TYPE v)& { return _##GETTER = std::move(v), *this; } \ - DecodeHints&& SETTER(TYPE v)&& { return _##GETTER = std::move(v), std::move(*this); } + DecodeHints& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ + DecodeHints&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } /// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. ZX_PROPERTY(BarcodeFormats, formats, setFormats) @@ -164,8 +164,8 @@ class DecodeHints /// Specifies fallback character set to use instead of auto-detecting it (when applicable) ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet) - DecodeHints& setCharacterSet(std::string_view v)& { return _characterSet = CharacterSetFromString(v), *this; } - DecodeHints&& setCharacterSet(std::string_view v) && { return _characterSet = CharacterSetFromString(v), std::move(*this); } + DecodeHints& setCharacterSet(std::string_view v)& { return (void)(_characterSet = CharacterSetFromString(v)), *this; } + DecodeHints&& setCharacterSet(std::string_view v) && { return (void)(_characterSet = CharacterSetFromString(v)), std::move(*this); } #undef ZX_PROPERTY diff --git a/core/src/Utf.cpp b/core/src/Utf.cpp index ff112e65c8..d15be33765 100644 --- a/core/src/Utf.cpp +++ b/core/src/Utf.cpp @@ -247,8 +247,10 @@ std::wstring EscapeNonGraphical(std::wstring_view str) ws << "<" << ascii_nongraphs[wc == 127 ? 32 : wc] << ">"; else if (wc < 128) // ASCII ws << wc; - else if (IsUtf16SurrogatePair(str)) - ws.write(str.data(), 2), str.remove_prefix(1); + else if (IsUtf16SurrogatePair(str)) { + ws.write(str.data(), 2); + str.remove_prefix(1); + } // Exclude unpaired surrogates and NO-BREAK spaces NBSP and NUMSP else if ((wc < 0xd800 || wc >= 0xe000) && (iswgraph(wc) && wc != 0xA0 && wc != 0x2007 && wc != 0x2000 && wc != 0xfffd)) ws << wc; diff --git a/test/unit/ZXAlgorithmsTest.cpp b/test/unit/ZXAlgorithmsTest.cpp index 7f73d7d7f5..968419d9f1 100644 --- a/test/unit/ZXAlgorithmsTest.cpp +++ b/test/unit/ZXAlgorithmsTest.cpp @@ -40,7 +40,7 @@ TEST(ZXAlgorithmsTest, UpdateMinMax) EXPECT_EQ(m, 2); EXPECT_EQ(M, 5); - m = 1, M = 1; + m = M = 1; UpdateMinMax(m, M, 0); EXPECT_EQ(m, 0); EXPECT_EQ(M, 1); From 953e676c54064811caa9a6a125144be4cf67c9f7 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 4 Dec 2023 23:25:54 +0100 Subject: [PATCH 131/587] c++: fix -Wdocumentation warnings --- core/src/BitMatrix.h | 6 +++--- core/src/Pattern.h | 6 +++--- core/src/TextDecoder.cpp | 1 - core/src/WhiteRectDetector.cpp | 11 ++++++----- core/src/aztec/AZDetector.h | 5 ----- core/src/datamatrix/DMDetector.cpp | 2 -- core/src/datamatrix/DMECEncoder.h | 3 +-- core/src/pdf417/PDFDecoder.cpp | 3 --- core/src/pdf417/PDFEncoder.cpp | 4 +--- core/src/pdf417/PDFEncoder.h | 2 +- core/src/pdf417/PDFHighLevelEncoder.cpp | 13 ++++++------- test/unit/datamatrix/DMHighLevelEncodeTest.cpp | 2 +- 12 files changed, 22 insertions(+), 36 deletions(-) diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index c063d0e0ac..6d7dd54c9e 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -153,7 +153,7 @@ void GetPatternRow(const BitMatrix& matrix, int r, std::vector& pr, bo /** * @brief Inflate scales a BitMatrix up and adds a quiet Zone plus padding - * @param matrix input to be expanded + * @param input matrix to be expanded * @param width new width in bits (pixel) * @param height new height in bits (pixel) * @param quietZone size of quiet zone to add in modules @@ -163,7 +163,7 @@ BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone); /** * @brief Deflate (crop + subsample) a bit matrix - * @param matrix + * @param input matrix to be shrinked * @param width new width * @param height new height * @param top cropping starts at top row @@ -171,7 +171,7 @@ BitMatrix Inflate(BitMatrix&& input, int width, int height, int quietZone); * @param subSampling typically the module size * @return deflated input */ -BitMatrix Deflate(const BitMatrix& matrix, int width, int height, float top, float left, float subSampling); +BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, float left, float subSampling); template BitMatrix ToBitMatrix(const Matrix& in, T trueValue = {true}) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 3d4cbc3ac9..f4d377fc4c 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -151,9 +151,9 @@ constexpr auto BarAndSpaceSum(const T* view) noexcept /** * @brief FixedPattern describes a compile-time constant (start/stop) pattern. * - * @param N number of bars/spaces - * @param SUM sum over all N elements (size of pattern in modules) - * @param IS_SPARCE whether or not the pattern contains '0's denoting 'wide' bars/spaces + * N = number of bars/spaces + * SUM = sum over all N elements (size of pattern in modules) + * IS_SPARCE = whether or not the pattern contains '0's denoting 'wide' bars/spaces */ template struct FixedPattern diff --git a/core/src/TextDecoder.cpp b/core/src/TextDecoder.cpp index bca4617616..216c6c9994 100644 --- a/core/src/TextDecoder.cpp +++ b/core/src/TextDecoder.cpp @@ -53,7 +53,6 @@ void TextDecoder::Append(std::wstring& str, const uint8_t* bytes, size_t length, /** * @param bytes bytes encoding a string, whose encoding should be guessed -* @param hints decode hints if applicable * @return name of guessed encoding; at the moment will only guess one of: * {@link #SHIFT_JIS}, {@link #UTF8}, {@link #ISO88591}, or the platform * default encoding if none of these can possibly be correct diff --git a/core/src/WhiteRectDetector.cpp b/core/src/WhiteRectDetector.cpp index 87cfca924e..9644a271a9 100644 --- a/core/src/WhiteRectDetector.cpp +++ b/core/src/WhiteRectDetector.cpp @@ -76,15 +76,16 @@ static bool GetBlackPointOnSegment(const BitMatrix& image, int aX, int aY, int b /** * recenters the points of a constant distance towards the center * +* p0 to p3 describing the corners of the rectangular +* region. The first and last points are opposed on the diagonal, as +* are the second and third. The first point will be the topmost +* point and the last, the bottommost. The second point will be +* leftmost and the third, the rightmost +* * @param y bottom most point * @param z left most point * @param x right most point * @param t top most point -* @return {@link ResultPoint}[] describing the corners of the rectangular -* region. The first and last points are opposed on the diagonal, as -* are the second and third. The first point will be the topmost -* point and the last, the bottommost. The second point will be -* leftmost and the third, the rightmost */ static void CenterEdges(const ResultPoint& y, const ResultPoint& z, const ResultPoint& x, const ResultPoint& t, int width, ResultPoint& p0, ResultPoint& p1, ResultPoint& p2, ResultPoint& p3) { diff --git a/core/src/aztec/AZDetector.h b/core/src/aztec/AZDetector.h index 364f1f6303..009f3409a6 100644 --- a/core/src/aztec/AZDetector.h +++ b/core/src/aztec/AZDetector.h @@ -16,11 +16,6 @@ namespace Aztec { class DetectorResult; -/** - * Detects an Aztec Code in an image. - * - * @param isMirror if true, image is a mirror-image of original - */ DetectorResult Detect(const BitMatrix& image, bool isPure, bool tryHarder = true); using DetectorResults = std::vector; diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index e76f02c579..ce8d15fde6 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -213,8 +213,6 @@ static float CrossProductZ(const ResultPoint& a, const ResultPoint& b, const Res /** * Orders an array of three ResultPoints in an order [A,B,C] such that AB is less than AC * and BC is less than AC, and the angle between BC and BA is less than 180 degrees. -* -* @param patterns array of three {@code ResultPoint} to order */ static void OrderByBestPatterns(const ResultPoint*& p0, const ResultPoint*& p1, const ResultPoint*& p2) { diff --git a/core/src/datamatrix/DMECEncoder.h b/core/src/datamatrix/DMECEncoder.h index aea7d3bce4..d0cafee2a1 100644 --- a/core/src/datamatrix/DMECEncoder.h +++ b/core/src/datamatrix/DMECEncoder.h @@ -17,9 +17,8 @@ class SymbolInfo; /** * Creates and interleaves the ECC200 error correction for an encoded message. * - * @param codewords the codewords + * @param codewords the codewords (with interleaved error correction after function return) * @param symbolInfo information about the symbol to be encoded - * @return the codewords with interleaved error correction. */ void EncodeECC200(ByteArray& codewords, const SymbolInfo& symbolInfo); diff --git a/core/src/pdf417/PDFDecoder.cpp b/core/src/pdf417/PDFDecoder.cpp index 1c6c89633b..2a1ff73802 100644 --- a/core/src/pdf417/PDFDecoder.cpp +++ b/core/src/pdf417/PDFDecoder.cpp @@ -367,7 +367,6 @@ static int ProcessByteECIs(const std::vector& codewords, int codeIndex, Con * This includes all ASCII characters value 0 to 127 inclusive and provides for international * character set support. * -* @param status Set on format error. * @param mode The byte compaction mode i.e. 901 or 924 * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. @@ -475,11 +474,9 @@ static std::string DecodeBase900toBase10(const std::vector& codewords, int /** * Numeric Compaction mode (see 5.4.4) permits efficient encoding of numeric data strings. * -* @param status Set on format error. * @param codewords The array of codewords (data + error) * @param codeIndex The current index into the codeword array. * @param result The decoded data is appended to the result. -* @param encoding Currently active character encoding. * @return The next index into the codeword array. */ static int NumericCompaction(const std::vector& codewords, int codeIndex, Content& result) diff --git a/core/src/pdf417/PDFEncoder.cpp b/core/src/pdf417/PDFEncoder.cpp index 2e33035925..a5b884e498 100644 --- a/core/src/pdf417/PDFEncoder.cpp +++ b/core/src/pdf417/PDFEncoder.cpp @@ -305,9 +305,8 @@ static int GetErrorCorrectionCodewordCount(int errorCorrectionLevel) /** * Generates the error correction codewords according to 4.10 in ISO/IEC 15438:2001(E). * -* @param dataCodewords the data codewords +* @param dataCodewords the data codewords (including error correction after return) * @param errorCorrectionLevel the error correction level (0-8) -* @return the String representing the error correction codewords */ static void GenerateErrorCorrection(std::vector& dataCodewords, int errorCorrectionLevel) { @@ -446,7 +445,6 @@ static BarcodeMatrix EncodeLowLevel(const std::vector& fullCodewords, int c * * @param sourceCodeWords number of code words * @param errorCorrectionCodeWords number of error correction code words -* @return dimension object containing cols as width and rows as height */ static void DetermineDimensions(int minCols, int maxCols, int minRows, int maxRows, int sourceCodeWords, int errorCorrectionCodeWords, int& outCols, int& outRows) { diff --git a/core/src/pdf417/PDFEncoder.h b/core/src/pdf417/PDFEncoder.h index 3f086dfd0d..47b53d28fa 100644 --- a/core/src/pdf417/PDFEncoder.h +++ b/core/src/pdf417/PDFEncoder.h @@ -50,7 +50,7 @@ class BarcodeRow * This function scales the row * * @param scale How much you want the image to be scaled, must be greater than or equal to 1. - * @return the scaled row + * @param output the scaled row */ void getScaledRow(int scale, std::vector& output) const { output.resize(_row.size() * scale); diff --git a/core/src/pdf417/PDFHighLevelEncoder.cpp b/core/src/pdf417/PDFHighLevelEncoder.cpp index 07f08b4421..600f5c741a 100644 --- a/core/src/pdf417/PDFHighLevelEncoder.cpp +++ b/core/src/pdf417/PDFHighLevelEncoder.cpp @@ -205,11 +205,11 @@ static bool IsText(int ch) * Encode parts of the message using Text Compaction as described in ISO/IEC 15438:2001(E), * chapter 4.4.2. * -* @param msg the message -* @param startpos the start position within the message -* @param count the number of characters to encode -* @param sb receives the encoded codewords -* @param initialSubmode should normally be SUBMODE_ALPHA +* @param msg the message +* @param startpos the start position within the message +* @param count the number of characters to encode +* @param submode should normally be SUBMODE_ALPHA +* @param output receives the encoded codewords * @return the text submode in which this method ends */ static int EncodeText(const std::wstring& msg, int startpos, int count, int submode, std::vector& output) @@ -318,7 +318,7 @@ static int EncodeText(const std::wstring& msg, int startpos, int count, int subm * @param startpos the start position within the message * @param count the number of bytes to encode * @param startmode the mode from which this method starts -* @param sb receives the encoded codewords +* @param output receives the encoded codewords */ static void EncodeBinary(const std::string& bytes, int startpos, int count, int startmode, std::vector& output) { @@ -456,7 +456,6 @@ static int DetermineConsecutiveTextCount(const std::wstring& msg, int startpos) * * @param msg the message * @param startpos the start position within the message -* @param encoding the charset used to convert the message to a byte array * @return the requested character count */ static int DetermineConsecutiveBinaryCount(const std::wstring& msg, int startpos) diff --git a/test/unit/datamatrix/DMHighLevelEncodeTest.cpp b/test/unit/datamatrix/DMHighLevelEncodeTest.cpp index fd436b8e8b..4a77810ba7 100644 --- a/test/unit/datamatrix/DMHighLevelEncodeTest.cpp +++ b/test/unit/datamatrix/DMHighLevelEncodeTest.cpp @@ -236,7 +236,7 @@ TEST(DMHighLevelEncodeTest, EDIFACTEncodation) // Checking temporary unlatch from EDIFACT visualized = Encode(L".XXX.XXX.XXX.XXX.XXX.XXX.\xFCXX.XXX.XXX.XXX.XXX.XXX.XXX"); EXPECT_EQ(visualized, "240 185 134 24 185 134 24 185 134 24 185 134 24 185 134 24 185 134 24" - " 124 47 235 125 240" //<-- this is the temporary unlatch + " 124 47 235 125 240" // <- this is the temporary unlatch " 97 139 152 97 139 152 97 139 152 97 139 152 97 139 152 97 139 152 89 89"); } From e587383783203ebe8e5683996a9ce991d7c6e540 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 5 Dec 2023 00:22:22 +0100 Subject: [PATCH 132/587] fuzz: fix/update fuzzer code --- test/fuzz/fuzzDBEDecoder.cpp | 7 ++++++- test/fuzz/fuzzODDecoders.cpp | 2 +- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/test/fuzz/fuzzDBEDecoder.cpp b/test/fuzz/fuzzDBEDecoder.cpp index 6270fff552..239aab2484 100644 --- a/test/fuzz/fuzzDBEDecoder.cpp +++ b/test/fuzz/fuzzDBEDecoder.cpp @@ -7,6 +7,7 @@ #include #include "BitArray.h" +#include "Error.h" #include "oned/ODDataBarExpandedBitDecoder.h" using namespace ZXing; @@ -20,7 +21,11 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) for (size_t i = 0; i < size; ++i) bits.appendBits(data[i], 8); - OneD::DataBar::DecodeExpandedBits(bits); + try { + OneD::DataBar::DecodeExpandedBits(bits); + } catch (std::out_of_range) { + } catch (Error) { + } return 0; } diff --git a/test/fuzz/fuzzODDecoders.cpp b/test/fuzz/fuzzODDecoders.cpp index a3d44ec84d..ea5cd04bb8 100644 --- a/test/fuzz/fuzzODDecoders.cpp +++ b/test/fuzz/fuzzODDecoders.cpp @@ -24,7 +24,7 @@ static std::vector> readers; bool init() { - DecodeHints hints; + static DecodeHints hints; readers.emplace_back(new MultiUPCEANReader(hints)); readers.emplace_back(new Code39Reader(hints)); readers.emplace_back(new Code93Reader(hints)); From 238c3a7dfd8f116adee69dc6ad8bd04b4799aef8 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 5 Dec 2023 00:23:16 +0100 Subject: [PATCH 133/587] BitArray: fix wrong bounds check condition (fuzz) --- core/src/BitArray.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/BitArray.h b/core/src/BitArray.h index 8e814b5a19..a260b3a3b8 100644 --- a/core/src/BitArray.h +++ b/core/src/BitArray.h @@ -158,7 +158,7 @@ class BitArrayView BitArrayView& skipBits(int n) { - if (n > bits.size()) + if (cur + n > bits.end()) throw std::out_of_range("BitArrayView::skipBits() out of range."); cur += n; return *this; @@ -167,7 +167,7 @@ class BitArrayView int peakBits(int n) const { assert(n <= 32); - if (n > bits.size()) + if (cur + n > bits.end()) throw std::out_of_range("BitArrayView::peakBits() out of range."); int res = 0; for (auto i = cur; n > 0; --n, i++) From 976b2ad8a82d91c73ec8cca9ebc942ddcb58fc46 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 5 Dec 2023 01:09:15 +0100 Subject: [PATCH 134/587] fuzz: add fuzzer for QRDecoder and AZDecoder --- test/fuzz/CMakeLists.txt | 4 +++- test/fuzz/fuzzAZDecoder.cpp | 32 ++++++++++++++++++++++++++++++++ test/fuzz/fuzzQRDecoder.cpp | 35 +++++++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 test/fuzz/fuzzAZDecoder.cpp create mode 100644 test/fuzz/fuzzQRDecoder.cpp diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 8d4e7c3af1..468364fd27 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -5,7 +5,7 @@ set (CMAKE_CXX_COMPILER /usr/bin/clang++) project (ZXingFuzz) -set (CMAKE_CXX_STANDARD 17) +set (CMAKE_CXX_STANDARD 20) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -g -march=native -fsanitize=address,fuzzer") set (BUILD_WRITERS ON) @@ -15,10 +15,12 @@ add_definitions (-DZXING_BUILD_FOR_TEST) add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/../../core ${CMAKE_BINARY_DIR}/ZXing) set (TESTS + AZDecoder DBEDecoder DMDecoder DMEncoder ODDecoders + QRDecoder ) foreach (test ${TESTS}) diff --git a/test/fuzz/fuzzAZDecoder.cpp b/test/fuzz/fuzzAZDecoder.cpp new file mode 100644 index 0000000000..1deac2991c --- /dev/null +++ b/test/fuzz/fuzzAZDecoder.cpp @@ -0,0 +1,32 @@ +/* + * Copyright 2021 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include + +#include "BitArray.h" +#include "DecoderResult.h" + +using namespace ZXing; + +namespace ZXing::Aztec { +DecoderResult Decode(const BitArray& bits); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if (size < 2) + return 0; + + BitArray bits; + for (size_t i = 1; i < size - 1; ++i) + bits.appendBits(data[i], 8); + + bits.appendBits(data[size - 1], (data[0] & 0x7) + 1); + + Aztec::Decode(bits); + + return 0; +} diff --git a/test/fuzz/fuzzQRDecoder.cpp b/test/fuzz/fuzzQRDecoder.cpp new file mode 100644 index 0000000000..d1a931598c --- /dev/null +++ b/test/fuzz/fuzzQRDecoder.cpp @@ -0,0 +1,35 @@ +/* + * Copyright 2023 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#include +#include +#include + +#include "ByteArray.h" +#include "DecoderResult.h" +#include "qrcode/QRErrorCorrectionLevel.h" +#include "qrcode/QRVersion.h" + +using namespace ZXing; + +namespace ZXing::QRCode { +DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if (size < 3) + return 0; + + auto version = QRCode::Version::Model2(std::clamp(data[0] & 0x3F, 1, 40)); + auto ecLevel = QRCode::ECLevelFromBits(data[0] >> 6); + + ByteArray ba; + ba.insert(ba.begin(), data, data + size); + + QRCode::DecodeBitStream(std::move(ba), *version, ecLevel); + + return 0; +} From c2d0db780e2b63d5b32c465f9d4c9fc168aba37a Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 5 Dec 2023 01:09:55 +0100 Subject: [PATCH 135/587] AZDecoder: fix segfault (fuzz) --- core/src/aztec/AZDecoder.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/aztec/AZDecoder.cpp b/core/src/aztec/AZDecoder.cpp index 788f49cd1b..7ab6f6a84f 100644 --- a/core/src/aztec/AZDecoder.cpp +++ b/core/src/aztec/AZDecoder.cpp @@ -323,7 +323,7 @@ DecoderResult Decode(const BitArray& bits) // As converting character set ECIs ourselves and ignoring/skipping non-character ECIs, not using // modifiers that indicate ECI protocol (ISO/IEC 24778:2008 Annex F Table F.1) - if (res.bytes[0] == 29) { + if (res.bytes.size() > 1 && res.bytes[0] == 29) { res.symbology.modifier = '1'; // GS1 res.symbology.aiFlag = AIFlag::GS1; res.erase(0, 1); // Remove FNC1 From 5b2219e0e3e75f924cfa49e0550fb931ed00a325 Mon Sep 17 00:00:00 2001 From: adrienbusin Date: Tue, 5 Dec 2023 11:34:27 +0100 Subject: [PATCH 136/587] fix: wrong path for consumer-rules --- wrappers/android/zxingcpp/consumer-rules.pro | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/android/zxingcpp/consumer-rules.pro b/wrappers/android/zxingcpp/consumer-rules.pro index 9958e802ed..0f792e00c7 100644 --- a/wrappers/android/zxingcpp/consumer-rules.pro +++ b/wrappers/android/zxingcpp/consumer-rules.pro @@ -1 +1 @@ --keep class com.zxingcpp.** { *; } +-keep class zxingcpp.** { *; } From 0d64ac1a42fc921f5c71a473aacacf96f7f6820e Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 5 Dec 2023 16:26:14 +0100 Subject: [PATCH 137/587] QRCode: fix Version info for Model1 versions 5 and up Disclaimer: this likely still does not work but at least it does not crash anymore during fuzzing. I have the suspicion that the procssing of the interleaved error codewords needs to be different for Model 1 but I can't find or create a single sample of higher version Model 1 symbols. --- core/src/qrcode/QRVersion.cpp | 82 +++++++++++++++++------------------ 1 file changed, 41 insertions(+), 41 deletions(-) diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 5bc53f6207..36a5ee6c7a 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -309,84 +309,84 @@ const Version* Version::Model1(int number) 10, 1, 16, 0, 0, 13, 1, 13, 0, 0, 17, 1, 9 , 0, 0 - }}, + }}, {2, { 10, 1, 36, 0, 0, 16, 1, 30, 0, 0, 22, 1, 24, 0, 0, 30, 1, 16, 0, 0, - }}, + }}, {3, { 15, 1, 57, 0, 0, 28, 1, 44, 0, 0, 36, 1, 36, 0, 0, 48, 1, 24, 0, 0, - }}, + }}, {4, { 20, 1, 80, 0, 0, 40, 1, 60, 0, 0, 50, 1, 50, 0, 0, 66, 1, 34, 0, 0, - }}, + }}, {5, { 26, 1, 108, 0, 0, 52, 1, 82 , 0, 0, 66, 1, 68 , 0, 0, - 88, 2, 46 , 0, 0, + 44, 2, 23 , 0, 0, }}, {6, { - 34 , 1, 136, 0, 0, - 63 , 2, 106, 0, 0, - 84 , 2, 86 , 0, 0, - 112, 2, 58 , 0, 0, + 34, 1, 136, 0, 0, + 32, 2, 53 , 0, 0, + 42, 2, 43 , 0, 0, + 56, 2, 29 , 0, 0, }}, {7, { - 42 , 1, 170, 0, 0, - 80 , 2, 132, 0, 0, - 104, 2, 108, 0, 0, - 138, 3, 72 , 0, 0, + 42, 1, 170, 0, 0, + 40, 2, 66 , 0, 0, + 52, 2, 54 , 0, 0, + 46, 3, 24 , 0, 0, }}, {8, { - 48 , 2, 208, 0, 0, - 96 , 2, 160, 0, 0, - 128, 2, 128, 0, 0, - 168, 3, 87 , 0, 0, + 24, 2, 104, 0, 0, + 48, 2, 80 , 0, 0, + 64, 2, 64 , 0, 0, + 56, 3, 29 , 0, 0, }}, {9, { - 60 , 2, 246, 0, 0, - 120, 2, 186, 0, 0, - 150, 3, 156, 0, 0, - 204, 3, 102, 0, 0, + 30, 2, 123, 0, 0, + 60, 2, 93 , 0, 0, + 50, 3, 52 , 0, 0, + 68, 3, 34 , 0, 0, }}, {10, { - 68 , 2, 290, 0, 0, - 136, 2, 222, 0, 0, - 174, 3, 183, 0, 0, - 232, 4, 124, 0, 0, + 34, 2, 145, 0, 0, + 68, 2, 111, 0, 0, + 58, 3, 61 , 0, 0, + 58, 4, 31 , 0, 0, }}, {11, { - 80 , 2, 336, 0, 0, - 160, 4, 256, 0, 0, - 208, 4, 208, 0, 0, - 270, 5, 145, 0, 0, + 40, 2, 168, 0, 0, + 40, 4, 64 , 0, 0, + 52, 4, 52 , 0, 0, + 54, 5, 29 , 0, 0, }}, {12, { - 92 , 2, 384, 0, 0, - 184, 4, 292, 0, 0, - 232, 4, 244, 0, 0, - 310, 5, 165, 0, 0, + 46, 2, 192, 0, 0, + 46, 4, 73 , 0, 0, + 58, 4, 61 , 0, 0, + 62, 5, 33 , 0, 0, }}, {13, { - 108, 3, 432, 0, 0, - 208, 4, 332, 0, 0, - 264, 4, 276, 0, 0, - 348, 6, 192, 0, 0, + 36, 3, 144, 0, 0, + 52, 4, 83 , 0, 0, + 66, 4, 69 , 0, 0, + 58, 6, 32 , 0, 0, }}, {14, { - 120, 3, 489, 0, 0, - 240, 4, 368, 0, 0, - 300, 5, 310, 0, 0, - 396, 6, 210, 0, 0, + 40, 3, 163, 0, 0, + 60, 4, 92 , 0, 0, + 60, 5, 62 , 0, 0, + 66, 6, 35 , 0, 0, }}, }; From 0bd9b92e05ac813098ee53dc38a70fed0bdc3a63 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 5 Dec 2023 22:03:57 +0100 Subject: [PATCH 138/587] fuzz: reorganize and extend fuzzing code --- test/fuzz/CMakeLists.txt | 8 +- test/fuzz/fuzzAZDecoder.cpp | 32 ------- test/fuzz/fuzzDMDecoder.cpp | 31 ------- test/fuzz/fuzzDecodeMatrix.cpp | 87 +++++++++++++++++++ test/fuzz/fuzzQRDecoder.cpp | 35 -------- ...{fuzzODDecoders.cpp => fuzzReadLinear.cpp} | 1 + test/fuzz/fuzzReadMatrix.cpp | 63 ++++++++++++++ 7 files changed, 155 insertions(+), 102 deletions(-) delete mode 100644 test/fuzz/fuzzAZDecoder.cpp delete mode 100644 test/fuzz/fuzzDMDecoder.cpp create mode 100644 test/fuzz/fuzzDecodeMatrix.cpp delete mode 100644 test/fuzz/fuzzQRDecoder.cpp rename test/fuzz/{fuzzODDecoders.cpp => fuzzReadLinear.cpp} (98%) create mode 100644 test/fuzz/fuzzReadMatrix.cpp diff --git a/test/fuzz/CMakeLists.txt b/test/fuzz/CMakeLists.txt index 468364fd27..a5202f19a5 100644 --- a/test/fuzz/CMakeLists.txt +++ b/test/fuzz/CMakeLists.txt @@ -15,12 +15,11 @@ add_definitions (-DZXING_BUILD_FOR_TEST) add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/../../core ${CMAKE_BINARY_DIR}/ZXing) set (TESTS - AZDecoder DBEDecoder - DMDecoder DMEncoder - ODDecoders - QRDecoder + ReadLinear + ReadMatrix + DecodeMatrix ) foreach (test ${TESTS}) @@ -28,3 +27,4 @@ foreach (test ${TESTS}) add_executable (${name} "${name}.cpp") target_link_libraries (${name} ZXing::ZXing) endforeach() + diff --git a/test/fuzz/fuzzAZDecoder.cpp b/test/fuzz/fuzzAZDecoder.cpp deleted file mode 100644 index 1deac2991c..0000000000 --- a/test/fuzz/fuzzAZDecoder.cpp +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2021 Axel Waggershauser - */ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include - -#include "BitArray.h" -#include "DecoderResult.h" - -using namespace ZXing; - -namespace ZXing::Aztec { -DecoderResult Decode(const BitArray& bits); -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) -{ - if (size < 2) - return 0; - - BitArray bits; - for (size_t i = 1; i < size - 1; ++i) - bits.appendBits(data[i], 8); - - bits.appendBits(data[size - 1], (data[0] & 0x7) + 1); - - Aztec::Decode(bits); - - return 0; -} diff --git a/test/fuzz/fuzzDMDecoder.cpp b/test/fuzz/fuzzDMDecoder.cpp deleted file mode 100644 index 6b4500d8c7..0000000000 --- a/test/fuzz/fuzzDMDecoder.cpp +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright 2021 Axel Waggershauser - */ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include - -#include "ByteArray.h" -#include "DecoderResult.h" - -using namespace ZXing; - -namespace ZXing::DataMatrix::DecodedBitStreamParser { -DecoderResult Decode(ByteArray&& bytes, const bool isDMRE); -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) -{ - if (size < 2) - return 0; - - ByteArray ba; - ba.insert(ba.begin(), data, data + size); - try { - DataMatrix::DecodedBitStreamParser::Decode(std::move(ba), false); - } catch (...) { - } - - return 0; -} diff --git a/test/fuzz/fuzzDecodeMatrix.cpp b/test/fuzz/fuzzDecodeMatrix.cpp new file mode 100644 index 0000000000..88ec435eae --- /dev/null +++ b/test/fuzz/fuzzDecodeMatrix.cpp @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#include "BitArray.h" +#include "ByteArray.h" +#include "DecoderResult.h" +#include "qrcode/QRErrorCorrectionLevel.h" +#include "qrcode/QRVersion.h" +#include "pdf417/PDFDecoder.h" + +#include +#include +#include + +using namespace ZXing; + +namespace ZXing::Aztec { +DecoderResult Decode(const BitArray& bits); +} + +namespace ZXing::DataMatrix::DecodedBitStreamParser { +DecoderResult Decode(ByteArray&& bytes, const bool isDMRE); +} + +namespace ZXing::QRCode { +DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel); +} + +void az(const uint8_t* data, size_t size) +{ + BitArray bits; + for (size_t i = 1; i < size - 1; ++i) + bits.appendBits(data[i], 8); + + bits.appendBits(data[size - 1], (data[0] & 0x7) + 1); + + Aztec::Decode(bits); +} + +void dm(const uint8_t* data, size_t size) +{ + ByteArray ba; + ba.insert(ba.begin(), data, data + size); + try { + DataMatrix::DecodedBitStreamParser::Decode(std::move(ba), false); + } catch (...) { + } +} + +void qr(const uint8_t* data, size_t size) +{ + auto version = QRCode::Version::Model2(std::clamp(data[0] & 0x3F, 1, 40)); + auto ecLevel = QRCode::ECLevelFromBits(data[0] >> 6); + + ByteArray ba; + ba.insert(ba.begin(), data, data + size); + + QRCode::DecodeBitStream(std::move(ba), *version, ecLevel); +} + +void pd(const uint8_t* data, size_t size) +{ + auto codewords = std::vector(size / 2); + auto u16 = reinterpret_cast(data); + + for (int i = 0; i < Size(codewords); ++i) + codewords[i] = u16[i] % 929; + + codewords[0] = Size(codewords); + + Pdf417::Decode(codewords); +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if (size < 3) + return 0; + + az(data, size); + dm(data, size); + qr(data, size); + pd(data, size); + + return 0; +} diff --git a/test/fuzz/fuzzQRDecoder.cpp b/test/fuzz/fuzzQRDecoder.cpp deleted file mode 100644 index d1a931598c..0000000000 --- a/test/fuzz/fuzzQRDecoder.cpp +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2023 Axel Waggershauser - */ -// SPDX-License-Identifier: Apache-2.0 - -#include -#include -#include - -#include "ByteArray.h" -#include "DecoderResult.h" -#include "qrcode/QRErrorCorrectionLevel.h" -#include "qrcode/QRVersion.h" - -using namespace ZXing; - -namespace ZXing::QRCode { -DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCorrectionLevel ecLevel); -} - -extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) -{ - if (size < 3) - return 0; - - auto version = QRCode::Version::Model2(std::clamp(data[0] & 0x3F, 1, 40)); - auto ecLevel = QRCode::ECLevelFromBits(data[0] >> 6); - - ByteArray ba; - ba.insert(ba.begin(), data, data + size); - - QRCode::DecodeBitStream(std::move(ba), *version, ecLevel); - - return 0; -} diff --git a/test/fuzz/fuzzODDecoders.cpp b/test/fuzz/fuzzReadLinear.cpp similarity index 98% rename from test/fuzz/fuzzODDecoders.cpp rename to test/fuzz/fuzzReadLinear.cpp index ea5cd04bb8..86027c4524 100644 --- a/test/fuzz/fuzzODDecoders.cpp +++ b/test/fuzz/fuzzReadLinear.cpp @@ -25,6 +25,7 @@ static std::vector> readers; bool init() { static DecodeHints hints; + hints.setReturnErrors(true); readers.emplace_back(new MultiUPCEANReader(hints)); readers.emplace_back(new Code39Reader(hints)); readers.emplace_back(new Code93Reader(hints)); diff --git a/test/fuzz/fuzzReadMatrix.cpp b/test/fuzz/fuzzReadMatrix.cpp new file mode 100644 index 0000000000..0db6a261ef --- /dev/null +++ b/test/fuzz/fuzzReadMatrix.cpp @@ -0,0 +1,63 @@ +/* + * Copyright 2023 Axel Waggershauser + */ +// SPDX-License-Identifier: Apache-2.0 + +#include "ReadBarcode.h" + +#include +#include +#include +#include + +using namespace ZXing; + +uint64_t Expand(uint8_t b) +{ + uint64_t shift = 0x0000040810204081ul; // bits set: 0, 7, 14, 21, 28, 35, 42 + uint64_t mask = 0x0001010101010101ul; // bits set: 0, 8, 16, 24, 32, 40, 48 + return ((uint64_t)(b & 127) * shift & mask) | (uint64_t)(b & 128) << 49; +} + +extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) +{ + if (size < 3) + return 0; + + static auto options = DecodeHints() + .setFormats(BarcodeFormat::MatrixCodes) + .setBinarizer(Binarizer::BoolCast) + .setReturnErrors(true) + .setTryInvert(false) + .setTryRotate(false); + + int ratio = data[0] + 1; + int nBits = (size - 1) * 8; + int width = std::clamp(nBits * ratio / 256, 1, nBits); + int height = std::clamp(nBits / width, 1, nBits); + + assert(width * height <= nBits); + + ByteArray buffer(nBits); + for (size_t i = 1; i < size; ++i) + *reinterpret_cast(&buffer[(i - 1) * 8]) = Expand(data[i]); + +#ifdef PRINT_DEBUG + printf("s: %zu, r: %d, n: %d -> %d x %d\n", size, ratio, nBits, width, height); +#endif + + auto image = ImageView(buffer.data(), width, height, ImageFormat::Lum); + auto res = ReadBarcodes(image, options); + +#ifdef PRINT_DEBUG + for (const auto& r : res) + printf("%s: %s / %s\n", ToString(r.format()).c_str(), r.text().c_str(), ToString(r.error()).c_str()); +#endif + + static int detectedSybols = 0; + detectedSybols += Size(res); + if (!res.empty() && detectedSybols % 100 == 0) + printf("detected barcode symbols: %d\n", detectedSybols); + + return 0; +} From 5ca3a7e24e85808f65eb10fd07c0f8afda383714 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 5 Dec 2023 22:05:05 +0100 Subject: [PATCH 139/587] PDFDecoder: catch std::exception (from invalid intput to stoi) (fuzz) --- core/src/pdf417/PDFDecoder.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/pdf417/PDFDecoder.cpp b/core/src/pdf417/PDFDecoder.cpp index 2a1ff73802..ce8d13711a 100644 --- a/core/src/pdf417/PDFDecoder.cpp +++ b/core/src/pdf417/PDFDecoder.cpp @@ -699,6 +699,8 @@ DecoderResult Decode(const std::vector& codewords) break; } } + } catch (std::exception& e) { + return FormatError(e.what()); } catch (Error e) { return e; } From 44f90f33e3862e81c0729b243e03eb340ed507aa Mon Sep 17 00:00:00 2001 From: gitlost Date: Tue, 5 Dec 2023 21:40:48 +0000 Subject: [PATCH 140/587] Result: allow this or other to have `lineCount` 1 and swop calcs accordingly; "rssexpanded-2/4_03.png" causes `assert(lineCount() == 1)` to trigger; also makes equals symmetrical --- core/src/Result.cpp | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/core/src/Result.cpp b/core/src/Result.cpp index 837a561a43..6fcf80e1d2 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -141,16 +141,19 @@ bool Result::operator==(const Result& o) const if (lineCount() > 1 && o.lineCount() > 1) return HaveIntersectingBoundingBoxes(o.position(), position()); - // the following code is only meant for this->lineCount == 1 - assert(lineCount() == 1); + // the following code is only meant for this or other lineCount == 1 + assert(lineCount() == 1 || o.lineCount() == 1); + + const auto& r1 = lineCount() == 1 ? *this : o; + const auto& r2 = lineCount() == 1 ? o : *this; // if one line is less than half the length of the other away from the // latter, we consider it to belong to the same symbol. additionally, both need to have // roughly the same length (see #367) - auto dTop = maxAbsComponent(o.position().topLeft() - position().topLeft()); - auto dBot = maxAbsComponent(o.position().bottomLeft() - position().topLeft()); - auto length = maxAbsComponent(position().topLeft() - position().bottomRight()); - auto dLength = std::abs(length - maxAbsComponent(o.position().topLeft() - o.position().bottomRight())); + auto dTop = maxAbsComponent(r2.position().topLeft() - r1.position().topLeft()); + auto dBot = maxAbsComponent(r2.position().bottomLeft() - r1.position().topLeft()); + auto length = maxAbsComponent(r1.position().topLeft() - r1.position().bottomRight()); + auto dLength = std::abs(length - maxAbsComponent(r2.position().topLeft() - r2.position().bottomRight())); return std::min(dTop, dBot) < length / 2 && dLength < length / 5; } From 2495d45f15127940e8296bbf91e78289b5f699ee Mon Sep 17 00:00:00 2001 From: gitlost Date: Tue, 5 Dec 2023 21:42:57 +0000 Subject: [PATCH 141/587] Add support for Rectangular Micro QR Code (rMQR), pure only --- core/src/BarcodeFormat.cpp | 1 + core/src/BarcodeFormat.h | 5 +- core/src/MultiFormatReader.cpp | 2 +- core/src/qrcode/QRBitMatrixParser.cpp | 58 ++++ core/src/qrcode/QRCodecMode.cpp | 35 ++- core/src/qrcode/QRCodecMode.h | 5 +- core/src/qrcode/QRDecoder.cpp | 2 +- core/src/qrcode/QRDetector.cpp | 131 ++++++++ core/src/qrcode/QRDetector.h | 2 + core/src/qrcode/QRErrorCorrectionLevel.h | 1 + core/src/qrcode/QRFormatInformation.cpp | 73 +++++ core/src/qrcode/QRFormatInformation.h | 7 +- core/src/qrcode/QRReader.cpp | 23 ++ core/src/qrcode/QRVersion.cpp | 295 +++++++++++++++++- core/src/qrcode/QRVersion.h | 5 + test/blackbox/BlackboxTestRunner.cpp | 8 + test/samples/rmqrcode-1/R7x43-H.png | Bin 0 -> 186 bytes test/samples/rmqrcode-1/R7x43-H.result.txt | 1 + test/samples/rmqrcode-1/R7x43-H.txt | 1 + test/samples/rmqrcode-1/R7x43-H_inverted.png | Bin 0 -> 152 bytes .../rmqrcode-1/R7x43-H_inverted.result.txt | 1 + test/samples/rmqrcode-1/R7x43-H_inverted.txt | 1 + test/unit/CMakeLists.txt | 6 + test/unit/qrcode/QRFormatInformationTest.cpp | 55 ++++ test/unit/qrcode/QRModeTest.cpp | 60 ++-- test/unit/qrcode/QRVersionTest.cpp | 109 +++++++ test/unit/qrcode/RMQRDecoderTest.cpp | 206 ++++++++++++ 27 files changed, 1058 insertions(+), 35 deletions(-) create mode 100644 test/samples/rmqrcode-1/R7x43-H.png create mode 100644 test/samples/rmqrcode-1/R7x43-H.result.txt create mode 100644 test/samples/rmqrcode-1/R7x43-H.txt create mode 100644 test/samples/rmqrcode-1/R7x43-H_inverted.png create mode 100644 test/samples/rmqrcode-1/R7x43-H_inverted.result.txt create mode 100644 test/samples/rmqrcode-1/R7x43-H_inverted.txt create mode 100644 test/unit/qrcode/RMQRDecoderTest.cpp diff --git a/core/src/BarcodeFormat.cpp b/core/src/BarcodeFormat.cpp index 58db3decce..29a87a4c1e 100644 --- a/core/src/BarcodeFormat.cpp +++ b/core/src/BarcodeFormat.cpp @@ -39,6 +39,7 @@ static BarcodeFormatName NAMES[] = { {BarcodeFormat::MicroQRCode, "MicroQRCode"}, {BarcodeFormat::PDF417, "PDF417"}, {BarcodeFormat::QRCode, "QRCode"}, + {BarcodeFormat::rMQR, "rMQR"}, {BarcodeFormat::UPCA, "UPC-A"}, {BarcodeFormat::UPCE, "UPC-E"}, {BarcodeFormat::LinearCodes, "Linear-Codes"}, diff --git a/core/src/BarcodeFormat.h b/core/src/BarcodeFormat.h index 3d3a4c326e..2036b19d47 100644 --- a/core/src/BarcodeFormat.h +++ b/core/src/BarcodeFormat.h @@ -39,12 +39,13 @@ enum class BarcodeFormat UPCA = (1 << 14), ///< UPC-A UPCE = (1 << 15), ///< UPC-E MicroQRCode = (1 << 16), ///< Micro QR Code + rMQR = (1 << 17), ///< Rectangular Micro QR Code LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE, - MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode, + MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | rMQR, Any = LinearCodes | MatrixCodes, - _max = MicroQRCode, ///> implementation detail, don't use + _max = rMQR, ///> implementation detail, don't use }; ZX_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) diff --git a/core/src/MultiFormatReader.cpp b/core/src/MultiFormatReader.cpp index 58a78cc0ca..cd09eb13dc 100644 --- a/core/src/MultiFormatReader.cpp +++ b/core/src/MultiFormatReader.cpp @@ -28,7 +28,7 @@ MultiFormatReader::MultiFormatReader(const DecodeHints& hints) : _hints(hints) if (formats.testFlags(BarcodeFormat::LinearCodes) && !hints.tryHarder()) _readers.emplace_back(new OneD::Reader(hints)); - if (formats.testFlags(BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode)) + if (formats.testFlags(BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode | BarcodeFormat::rMQR)) _readers.emplace_back(new QRCode::Reader(hints, true)); if (formats.testFlag(BarcodeFormat::DataMatrix)) _readers.emplace_back(new DataMatrix::Reader(hints, true)); diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index abf265241b..781f224031 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -30,6 +30,7 @@ const Version* ReadVersion(const BitMatrix& bitMatrix, Type type) switch (type) { case Type::Micro: return Version::Micro(number); + case Type::rMQR: return Version::rMQR(number); case Type::Model1: return Version::Model1(number); case Type::Model2: break; } @@ -67,6 +68,25 @@ FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix) return FormatInformation::DecodeMQR(formatInfoBits); } + if (Version::HasRMQRSize(bitMatrix)) { + // Read top-left format info bits + uint32_t formatInfoBits1 = 0; + for (int y = 3; y >= 1; y--) + AppendBit(formatInfoBits1, getBit(bitMatrix, 11, y)); + for (int x = 10; x >= 8; x--) + for (int y = 5; y >= 1; y--) + AppendBit(formatInfoBits1, getBit(bitMatrix, x, y)); + // Read bottom-right format info bits + uint32_t formatInfoBits2 = 0; + const int width = bitMatrix.width(); + const int height = bitMatrix.height(); + for (int x = 3; x <= 5; x++) + AppendBit(formatInfoBits2, getBit(bitMatrix, width - x, height - 6)); + for (int x = 6; x <= 8; x++) + for (int y = 2; y <= 6; y++) + AppendBit(formatInfoBits2, getBit(bitMatrix, width - x, height - y)); + return FormatInformation::DecodeRMQR(formatInfoBits1, formatInfoBits2); + } // Read top-left format info bits int formatInfoBits1 = 0; @@ -237,10 +257,48 @@ static ByteArray ReadMQRCodewords(const BitMatrix& bitMatrix, const QRCode::Vers return result; } +static ByteArray ReadRMQRCodewords(const BitMatrix& bitMatrix, const Version& version, const FormatInformation& formatInfo) +{ + BitMatrix functionPattern = version.buildFunctionPattern(); + + ByteArray result; + result.reserve(version.totalCodewords()); + uint8_t currentByte = 0; + bool readingUp = true; + int bitsRead = 0; + const int width = bitMatrix.width(); + const int height = bitMatrix.height(); + // Read columns in pairs, from right to left + for (int x = width - 1 - 1; x > 0; x -= 2) { // Skip right edge alignment + // Read alternatingly from bottom to top then top to bottom + for (int row = 0; row < height; row++) { + int y = readingUp ? height - 1 - row : row; + for (int col = 0; col < 2; col++) { + int xx = x - col; + // Ignore bits covered by the function pattern + if (!functionPattern.get(xx, y)) { + // Read a bit + AppendBit(currentByte, + GetDataMaskBit(formatInfo.dataMask, xx, y) != getBit(bitMatrix, xx, y, formatInfo.isMirrored)); + // If we've made a whole byte, save it off + if (++bitsRead % 8 == 0) + result.push_back(std::exchange(currentByte, 0)); + } + } + } + readingUp = !readingUp; // switch directions + } + if (Size(result) != version.totalCodewords()) + return {}; + + return result; +} + ByteArray ReadCodewords(const BitMatrix& bitMatrix, const Version& version, const FormatInformation& formatInfo) { switch (version.type()) { case Type::Micro: return ReadMQRCodewords(bitMatrix, version, formatInfo); + case Type::rMQR: return ReadRMQRCodewords(bitMatrix, version, formatInfo); case Type::Model1: return ReadQRCodewordsModel1(bitMatrix, version, formatInfo); case Type::Model2: return ReadQRCodewords(bitMatrix, version, formatInfo); } diff --git a/core/src/qrcode/QRCodecMode.cpp b/core/src/qrcode/QRCodecMode.cpp index c494151fca..8e94a57505 100644 --- a/core/src/qrcode/QRCodecMode.cpp +++ b/core/src/qrcode/QRCodecMode.cpp @@ -15,15 +15,22 @@ namespace ZXing::QRCode { -CodecMode CodecModeForBits(int bits, bool isMicro) +CodecMode CodecModeForBits(int bits, Type type) { - if (!isMicro) { - if ((bits >= 0x00 && bits <= 0x05) || (bits >= 0x07 && bits <= 0x09) || bits == 0x0d) - return static_cast(bits); - } else { + if (type == Type::Micro) { constexpr CodecMode Bits2Mode[4] = {CodecMode::NUMERIC, CodecMode::ALPHANUMERIC, CodecMode::BYTE, CodecMode::KANJI}; if (bits < Size(Bits2Mode)) return Bits2Mode[bits]; + } else if (type == Type::rMQR) { + constexpr CodecMode Bits2Mode[8] = { + CodecMode::TERMINATOR, CodecMode::NUMERIC, CodecMode::ALPHANUMERIC, CodecMode::BYTE, + CodecMode::KANJI, CodecMode::FNC1_FIRST_POSITION, CodecMode::FNC1_SECOND_POSITION, CodecMode::ECI + }; + if (bits < Size(Bits2Mode)) + return Bits2Mode[bits]; + } else { + if ((bits >= 0x00 && bits <= 0x05) || (bits >= 0x07 && bits <= 0x09) || bits == 0x0d) + return static_cast(bits); } throw FormatError("Invalid codec mode"); @@ -42,6 +49,20 @@ int CharacterCountBits(CodecMode mode, const Version& version) default: return 0; } } + if (version.isRMQR()) { + // See ISO/IEC 23941:2022 7.4.1, Table 3 - Number of bits of character count indicator + constexpr char numeric[32] = {4, 5, 6, 7, 7, 5, 6, 7, 7, 8, 4, 6, 7, 7, 8, 8, 5, 6, 7, 7, 8, 8, 7, 7, 8, 8, 9, 7, 8, 8, 8, 9}; + constexpr char alphanum[32] = {3, 5, 5, 6, 6, 5, 5, 6, 6, 7, 4, 5, 6, 6, 7, 7, 5, 6, 6, 7, 7, 8, 6, 7, 7, 7, 8, 6, 7, 7, 8, 8}; + constexpr char byte[32] = {3, 4, 5, 5, 6, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 4, 5, 6, 6, 7, 7, 6, 6, 7, 7, 7, 6, 6, 7, 7, 8}; + constexpr char kanji[32] = {2, 3, 4, 5, 5, 3, 4, 5, 5, 6, 2, 4, 5, 5, 6, 6, 3, 5, 5, 6, 6, 7, 5, 5, 6, 6, 7, 5, 6, 6, 6, 7}; + switch (mode) { + case CodecMode::NUMERIC: return numeric[number - 1]; + case CodecMode::ALPHANUMERIC: return alphanum[number - 1]; + case CodecMode::BYTE: return byte[number - 1]; + case CodecMode::KANJI: return kanji[number - 1]; + default: return 0; + } + } int i; if (number <= 9) @@ -63,12 +84,12 @@ int CharacterCountBits(CodecMode mode, const Version& version) int CodecModeBitsLength(const Version& version) { - return version.isMicro() ? version.versionNumber() - 1 : 4; + return version.isMicro() ? version.versionNumber() - 1 : 4 - version.isRMQR(); } int TerminatorBitsLength(const Version& version) { - return version.isMicro() ? version.versionNumber() * 2 + 1 : 4; + return version.isMicro() ? version.versionNumber() * 2 + 1 : 4 - version.isRMQR(); } } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRCodecMode.h b/core/src/qrcode/QRCodecMode.h index 2c4f9d3551..eff1e18b42 100644 --- a/core/src/qrcode/QRCodecMode.h +++ b/core/src/qrcode/QRCodecMode.h @@ -8,6 +8,7 @@ namespace ZXing::QRCode { +enum class Type; class Version; /** @@ -30,11 +31,11 @@ enum class CodecMode /** * @param bits variable number of bits encoding a QR Code data mode - * @param isMicro is this a MicroQRCode + * @param type type of QR Code * @return Mode encoded by these bits * @throws FormatError if bits do not correspond to a known mode */ -CodecMode CodecModeForBits(int bits, bool isMicro = false); +CodecMode CodecModeForBits(int bits, Type type); /** * @param version version in question diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index f3dceca94e..44ce4d41b5 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -247,7 +247,7 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo if (modeBitLength == 0) mode = CodecMode::NUMERIC; // MicroQRCode version 1 is always NUMERIC and modeBitLength is 0 else - mode = CodecModeForBits(bits.readBits(modeBitLength), version.isMicro()); + mode = CodecModeForBits(bits.readBits(modeBitLength), version.type()); switch (mode) { case CodecMode::FNC1_FIRST_POSITION: diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 7b6c16d461..a4fbc947dc 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -36,6 +36,8 @@ namespace ZXing::QRCode { constexpr auto PATTERN = FixedPattern<5, 7>{1, 1, 3, 1, 1}; +constexpr auto SUBPATTERN_RMQR = FixedPattern<5, 5>{1, 1, 1, 1, 1}; +constexpr auto CORNER_EDGE_RMQR = FixedPattern<2, 4>{3, 1}; constexpr bool E2E = true; PatternView FindPattern(const PatternView& view) @@ -601,6 +603,84 @@ DetectorResult DetectPureMQR(const BitMatrix& image) {{left, top}, {right, top}, {right, bottom}, {left, bottom}}}; } +DetectorResult DetectPureRMQR(const BitMatrix& image) +{ + using Pattern = std::array; + using SubPattern = std::array; + using CornerEdgePattern = std::array; + +#ifdef PRINT_DEBUG + SaveAsPBM(image, "weg.pbm"); +#endif + + constexpr int MIN_MODULES = 7; + constexpr int MIN_MODULES_W = 27; + constexpr int MIN_MODULES_H = 7; + constexpr int MAX_MODULES_W = 139; + constexpr int MAX_MODULES_H = 17; + + int left, top, width, height; + if (!image.findBoundingBox(left, top, width, height, MIN_MODULES)) + return {}; + int right = left + width - 1; + int bottom = top + height - 1; + + PointI tl{left, top}, tr{right, top}, br{right, bottom}, bl{left, bottom}; + + // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts + auto diagonal = BitMatrixCursorI(image, tl, {1, 1}).readPatternFromBlack(1); + if (!IsPattern(diagonal, PATTERN)) + return {}; + + // Finder sub pattern + auto subdiagonal = BitMatrixCursorI(image, br, {-1, -1}).readPatternFromBlack(1); + if (Size(subdiagonal) == 5 && subdiagonal[4] > subdiagonal[3]) // Sub pattern has no separator so can run off along the diagonal + subdiagonal[4] = subdiagonal[3]; // Hack it back to previous + if (!IsPattern(subdiagonal, SUBPATTERN_RMQR)) + return {}; + + // Horizontal corner finder patterns (for vertical ones see below) + for (auto [p, d] : {std::pair(tr, PointI{-1, 0}), {bl, {1, 0}}}) { + auto corner = BitMatrixCursorI(image, p, d).readPatternFromBlack(1); + if (!IsPattern(corner, CORNER_EDGE_RMQR)) + return {}; + } + + auto fpWidth = Reduce(diagonal); + float moduleSize = float(fpWidth) / 7; + int dimW = narrow_cast(std::lround(width / moduleSize)); + int dimH = narrow_cast(std::lround(height / moduleSize)); + + if (dimW == dimH || !(dimW & 1) || !(dimH & 1) || + dimW < MIN_MODULES_W || dimW > MAX_MODULES_W || dimH < MIN_MODULES_H || dimH > MAX_MODULES_H || + !image.isIn(PointF{left + moduleSize / 2 + (dimW - 1) * moduleSize, + top + moduleSize / 2 + (dimH - 1) * moduleSize})) + return {}; + + // Vertical corner finder patterns + if (dimH > 7) { // None for R7 + auto corner = BitMatrixCursorI(image, tr, {0, 1}).readPatternFromBlack(1); + if (!IsPattern(corner, CORNER_EDGE_RMQR)) + return {}; + if (dimH > 9) { // No bottom left for R9 + corner = BitMatrixCursorI(image, bl, {0, -1}).readPatternFromBlack(1); + if (!IsPattern(corner, CORNER_EDGE_RMQR)) + return {}; + } + } + +#ifdef PRINT_DEBUG + LogMatrix log; + LogMatrixWriter lmw(log, image, 5, "grid2.pnm"); + for (int y = 0; y < dimH; y++) + for (int x = 0; x < dimW; x++) + log(PointF(left + (x + .5f) * moduleSize, top + (y + .5f) * moduleSize)); +#endif + + // Now just read off the bits (this is a crop + subsample) + return {Deflate(image, dimW, dimH, top + moduleSize / 2, left + moduleSize / 2, moduleSize), {tl, tr, br, bl}}; +} + DetectorResult SampleMQR(const BitMatrix& image, const ConcentricPattern& fp) { auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 2); @@ -662,4 +742,55 @@ DetectorResult SampleMQR(const BitMatrix& image, const ConcentricPattern& fp) return SampleGrid(image, dim, dim, bestPT); } +DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp) +{ + // TODO proper + auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 2); + if (!fpQuad) + return {}; + + auto srcQuad = Rectangle(7, 7, 0.5); + + static const PointI FORMAT_INFO_EDGE_COORDS[] = {{8, 0}, {9, 0}, {10, 0}, {11, 0}}; + static const PointI FORMAT_INFO_COORDS[] = { + { 8, 1}, { 8, 2}, { 8, 3}, { 8, 4}, { 8, 5}, + { 9, 1}, { 9, 2}, { 9, 3}, { 9, 4}, { 9, 5}, + {10, 1}, {10, 2}, {10, 3}, {10, 4}, {10, 5}, + {11, 1}, {11, 2}, {11, 3}, + }; + + FormatInformation bestFI; + PerspectiveTransform bestPT; + + for (int i = 0; i < 4; ++i) { + auto mod2Pix = PerspectiveTransform(srcQuad, RotatedCorners(*fpQuad, i)); + + auto check = [&](int i, bool on) { + auto p = mod2Pix(centered(FORMAT_INFO_EDGE_COORDS[i])); + return image.isIn(p) && image.get(p) == on; + }; + + // check that we see top edge timing pattern modules + if (!check(0, true) || !check(1, false) || !check(2, true) || !check(3, false)) + continue; + + uint32_t formatInfoBits = 0; + for (int i = 0; i < Size(FORMAT_INFO_COORDS); ++i) + AppendBit(formatInfoBits, image.get(mod2Pix(centered(FORMAT_INFO_COORDS[i])))); + + auto fi = FormatInformation::DecodeRMQR(formatInfoBits, 0 /*formatInfoBits2*/); + if (fi.hammingDistance < bestFI.hammingDistance) { + bestFI = fi; + bestPT = mod2Pix; + } + } + + if (!bestFI.isValid()) + return {}; + + const PointI dim = Version::DimensionOfVersionRMQR(bestFI.rMQRVersion + 1); + + return SampleGrid(image, dim.x, dim.y, bestPT); +} + } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRDetector.h b/core/src/qrcode/QRDetector.h index 4173d59a7a..be767e6d30 100644 --- a/core/src/qrcode/QRDetector.h +++ b/core/src/qrcode/QRDetector.h @@ -32,9 +32,11 @@ FinderPatternSets GenerateFinderPatternSets(FinderPatterns& patterns); DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp); DetectorResult SampleMQR(const BitMatrix& image, const ConcentricPattern& fp); +DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp); DetectorResult DetectPureQR(const BitMatrix& image); DetectorResult DetectPureMQR(const BitMatrix& image); +DetectorResult DetectPureRMQR(const BitMatrix& image); } // QRCode } // ZXing diff --git a/core/src/qrcode/QRErrorCorrectionLevel.h b/core/src/qrcode/QRErrorCorrectionLevel.h index 82708f8d9c..d87da0978a 100644 --- a/core/src/qrcode/QRErrorCorrectionLevel.h +++ b/core/src/qrcode/QRErrorCorrectionLevel.h @@ -33,6 +33,7 @@ enum class Type Model1, Model2, Micro, + rMQR, }; } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index ac811d72a2..c2f2c57d0e 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -47,6 +47,56 @@ static FormatInformation FindBestFormatInfo(const std::vector& masks, return fi; } +static FormatInformation FindBestFormatInfoRMQR(const std::vector& bits, const std::vector& subbits) +{ + // See ISO/IEC 23941:2022, Annex C, Table C.1 - Valid format information sequences + constexpr uint32_t MASKED_PATTERNS[64] = { // Finder pattern side + 0x1FAB2, 0x1E597, 0x1DBDD, 0x1C4F8, 0x1B86C, 0x1A749, 0x19903, 0x18626, + 0x17F0E, 0x1602B, 0x15E61, 0x14144, 0x13DD0, 0x122F5, 0x11CBF, 0x1039A, + 0x0F1CA, 0x0EEEF, 0x0D0A5, 0x0CF80, 0x0B314, 0x0AC31, 0x0927B, 0x08D5E, + 0x07476, 0x06B53, 0x05519, 0x04A3C, 0x036A8, 0x0298D, 0x017C7, 0x008E2, + 0x3F367, 0x3EC42, 0x3D208, 0x3CD2D, 0x3B1B9, 0x3AE9C, 0x390D6, 0x38FF3, + 0x376DB, 0x369FE, 0x357B4, 0x34891, 0x33405, 0x32B20, 0x3156A, 0x30A4F, + 0x2F81F, 0x2E73A, 0x2D970, 0x2C655, 0x2BAC1, 0x2A5E4, 0x29BAE, 0x2848B, + 0x27DA3, 0x26286, 0x25CCC, 0x243E9, 0x23F7D, 0x22058, 0x21E12, 0x20137, + }; + constexpr uint32_t MASKED_PATTERNS_SUB[64] = { // Finder sub pattern side + 0x20A7B, 0x2155E, 0x22B14, 0x23431, 0x248A5, 0x25780, 0x269CA, 0x276EF, + 0x28FC7, 0x290E2, 0x2AEA8, 0x2B18D, 0x2CD19, 0x2D23C, 0x2EC76, 0x2F353, + 0x30103, 0x31E26, 0x3206C, 0x33F49, 0x343DD, 0x35CF8, 0x362B2, 0x37D97, + 0x384BF, 0x39B9A, 0x3A5D0, 0x3BAF5, 0x3C661, 0x3D944, 0x3E70E, 0x3F82B, + 0x003AE, 0x01C8B, 0x022C1, 0x03DE4, 0x04170, 0x05E55, 0x0601F, 0x07F3A, + 0x08612, 0x09937, 0x0A77D, 0x0B858, 0x0C4CC, 0x0DBE9, 0x0E5A3, 0x0FA86, + 0x108D6, 0x117F3, 0x129B9, 0x1369C, 0x14A08, 0x1552D, 0x16B67, 0x17442, + 0x18D6A, 0x1924F, 0x1AC05, 0x1B320, 0x1CFB4, 0x1D091, 0x1EEDB, 0x1F1FE, + }; + + FormatInformation fi; + + auto best = [&fi](const std::vector& bits, const uint32_t (&patterns)[64], uint32_t mask) + { + for (int bitsIndex = 0; bitsIndex < Size(bits); ++bitsIndex) + for (uint32_t pattern : patterns) { + // 'unmask' the pattern first to get the original 6-data bits + 12-ec bits back + pattern ^= mask; + // Find the pattern with fewest bits differing + if (int hammingDist = BitHacks::CountBitsSet((bits[bitsIndex] ^ mask) ^ pattern); + hammingDist < fi.hammingDistance) { + fi.mask = mask; // store the used mask to discriminate between types/models + fi.data = pattern >> 12; // drop the 12 BCH error correction bits + fi.hammingDistance = hammingDist; + fi.bitsIndex = bitsIndex; + } + } + }; + + best(bits, MASKED_PATTERNS, FORMAT_INFO_MASK_RMQR); + if (Size(subbits)) // TODO probably remove if `sampleRMQR()` done properly + best(subbits, MASKED_PATTERNS_SUB, FORMAT_INFO_MASK_RMQR_SUB); + + return fi; +} + /** * @param formatInfoBits1 format info indicator, with mask still applied * @param formatInfoBits2 second copy of same info; both are checked at the same time to establish best match @@ -86,4 +136,27 @@ FormatInformation FormatInformation::DecodeMQR(uint32_t formatInfoBits) return fi; } +/** +* @param formatInfoBits1 format info indicator, with mask still applied +* @param formatInfoBits2 second copy of same info; both are checked at the same time to establish best match +*/ +FormatInformation FormatInformation::DecodeRMQR(uint32_t formatInfoBits1, uint32_t formatInfoBits2) +{ + FormatInformation fi; + auto mirror18Bits = [](uint32_t bits) { return BitHacks::Reverse(bits) >> 14; }; + if (formatInfoBits2) + fi = FindBestFormatInfoRMQR({formatInfoBits1, mirror18Bits(formatInfoBits1)}, + {formatInfoBits2, mirror18Bits(formatInfoBits2)}); + else // TODO probably remove if `sampleRMQR()` done properly + fi = FindBestFormatInfoRMQR({formatInfoBits1, mirror18Bits(formatInfoBits1)}, {}); + + // Bit 6 is error correction (M/H), and bits 0-5 version. + fi.ecLevel = ECLevelFromBits(((fi.data >> 5) & 1) << 1); // Shift to match QRCode M/H + fi.dataMask = 4; // ((y / 2) + (x / 3)) % 2 == 0 + fi.rMQRVersion = fi.data & 0x1F; + fi.isMirrored = fi.bitsIndex > 1; + + return fi; +} + } // namespace ZXing::QRCode diff --git a/core/src/qrcode/QRFormatInformation.h b/core/src/qrcode/QRFormatInformation.h index 78564e6954..302472cc10 100644 --- a/core/src/qrcode/QRFormatInformation.h +++ b/core/src/qrcode/QRFormatInformation.h @@ -16,6 +16,8 @@ namespace ZXing::QRCode { static constexpr uint32_t FORMAT_INFO_MASK_MODEL2 = 0x5412; static constexpr uint32_t FORMAT_INFO_MASK_MODEL1 = 0x2825; static constexpr uint32_t FORMAT_INFO_MASK_MICRO = 0x4445; +static constexpr uint32_t FORMAT_INFO_MASK_RMQR = 0x1FAB2; // Finder pattern side +static constexpr uint32_t FORMAT_INFO_MASK_RMQR_SUB = 0x20A7B; // Finder sub pattern side class FormatInformation { @@ -28,14 +30,16 @@ class FormatInformation bool isMirrored = false; uint8_t dataMask = 0; uint8_t microVersion = 0; + uint8_t rMQRVersion = 0; ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Invalid; FormatInformation() = default; static FormatInformation DecodeQR(uint32_t formatInfoBits1, uint32_t formatInfoBits2); static FormatInformation DecodeMQR(uint32_t formatInfoBits); + static FormatInformation DecodeRMQR(uint32_t formatInfoBits1, uint32_t formatInfoBits2); - // Hamming distance of the 32 masked codes is 7, by construction, so <= 3 bits differing means we found a match + // Hamming distance of the 32 masked codes is 7 (64 and 8 for rMQR), by construction, so <= 3 bits differing means we found a match bool isValid() const { return hammingDistance <= 3; } Type type() const @@ -43,6 +47,7 @@ class FormatInformation switch (mask) { case FORMAT_INFO_MASK_MODEL1: return Type::Model1; case FORMAT_INFO_MASK_MICRO: return Type::Micro; + case FORMAT_INFO_MASK_RMQR: case FORMAT_INFO_MASK_RMQR_SUB: return Type::rMQR; default: return Type::Model2; } } diff --git a/core/src/qrcode/QRReader.cpp b/core/src/qrcode/QRReader.cpp index dd597cb2fc..1ca6bc362a 100644 --- a/core/src/qrcode/QRReader.cpp +++ b/core/src/qrcode/QRReader.cpp @@ -37,6 +37,8 @@ Result Reader::decode(const BinaryBitmap& image) const detectorResult = DetectPureQR(*binImg); if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !detectorResult.isValid()) detectorResult = DetectPureMQR(*binImg); + if (_hints.hasFormat(BarcodeFormat::rMQR) && !detectorResult.isValid()) + detectorResult = DetectPureRMQR(*binImg); if (!detectorResult.isValid()) return {}; @@ -45,6 +47,7 @@ Result Reader::decode(const BinaryBitmap& image) const auto position = detectorResult.position(); return Result(std::move(decoderResult), std::move(position), + detectorResult.bits().width() != detectorResult.bits().height() ? BarcodeFormat::rMQR : detectorResult.bits().width() < 21 ? BarcodeFormat::MicroQRCode : BarcodeFormat::QRCode); } @@ -128,6 +131,26 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const } } + if (_hints.hasFormat(BarcodeFormat::rMQR) && !(maxSymbols && Size(results) == maxSymbols)) { + // TODO proper + for (const auto& fp : allFPs) { + if (Contains(usedFPs, fp)) + continue; + + auto detectorResult = SampleRMQR(*binImg, fp); + if (detectorResult.isValid()) { + auto decoderResult = Decode(detectorResult.bits()); + auto position = detectorResult.position(); + if (decoderResult.isValid(_hints.returnErrors())) { + results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::rMQR); + if (maxSymbols && Size(results) == maxSymbols) + break; + } + + } + } + } + return results; } diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 36a5ee6c7a..96d68124a4 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -297,6 +297,243 @@ const Version* Version::Micro(int number) return allVersions + number - 1; } +const Version* Version::rMQR(int number) +{ + /** + * See ISO/IEC 23941:2022 Annex D, Table D.1 - Column coordinates of centre module of alignment patterns + * See ISO/IEC 23941:2022 7.5.1, Table 8 - Error correction characteristics for rMQR + */ + static const Version allVersions[] = { + // Version number, alignment pattern centres, `ECBlocks` + { 1, {21}, { // R7x43 + // 4 `ECBlocks`, one for each `ecLevel` - rMQR only uses M & H but using 2 dummies to keep `ecLevel` index same as QR Code + // Each begins with no. of error correction codewords divided by no. of error correction blocks, followed by 2 `ECBlock`s + // Each `ECBlock` begins with no. of error correction blocks followed by no. of data codewords per block + 0, 0, 0, 0, 0, // L (dummy) - also used to differentiate rMQR from Model2 in `Version::Version()` + 7, 1, 6, 0, 0, // M + 0, 0, 0, 0, 0, // Q (dummy) + 10, 1, 3, 0, 0, // H + }}, + { 2, {19, 39}, { // R7x59 + 0, 0, 0, 0, 0, + 9, 1, 12, 0, 0, + 0, 0, 0, 0, 0, + 14, 1, 7, 0, 0, + }}, + { 3, {25, 51}, { // R7x77 + 0, 0, 0, 0, 0, + 12, 1, 20, 0, 0, + 0, 0, 0, 0, 0, + 22, 1, 10, 0, 0, + }}, + { 4, {23, 49, 75}, { // R7x99 + 0, 0, 0, 0, 0, + 16, 1, 28, 0, 0, + 0, 0, 0, 0, 0, + 30, 1, 14, 0, 0, + }}, + { 5, {27, 55, 83, 111}, { // R7x139 + 0, 0, 0, 0, 0, + 24, 1, 44, 0, 0, + 0, 0, 0, 0, 0, + 22, 2, 12, 0, 0, + }}, + { 6, {21}, { // R9x43 + 0, 0, 0, 0, 0, + 9, 1, 12, 0, 0, + 0, 0, 0, 0, 0, + 14, 1, 7, 0, 0, + }}, + { 7, {19, 39}, { // R9x59 + 0, 0, 0, 0, 0, + 12, 1, 21, 0, 0, + 0, 0, 0, 0, 0, + 22, 1, 11, 0, 0, + }}, + { 8, {25, 51}, { // R9x77 + 0, 0, 0, 0, 0, + 18, 1, 31, 0, 0, + 0, 0, 0, 0, 0, + 16, 1, 8, 1, 9, + }}, + { 9, {23, 49, 75}, { // R9x99 + 0, 0, 0, 0, 0, + 24, 1, 42, 0, 0, + 0, 0, 0, 0, 0, + 22, 2, 11, 0, 0, + }}, + {10, {27, 55, 83, 111}, { // R9x139 + 0, 0, 0, 0, 0, + 18, 1, 31, 1, 32, + 0, 0, 0, 0, 0, + 22, 3, 11, 0, 0, + }}, + {11, {}, { // R11x27 + 0, 0, 0, 0, 0, + 8, 1, 7, 0, 0, + 0, 0, 0, 0, 0, + 10, 1, 5, 0, 0, + }}, + {12, {21}, { // R11x43 + 0, 0, 0, 0, 0, + 12, 1, 19, 0, 0, + 0, 0, 0, 0, 0, + 20, 1, 11, 0, 0, + }}, + {13, {19, 39}, { // R11x59 + 0, 0, 0, 0, 0, + 16, 1, 31, 0, 0, + 0, 0, 0, 0, 0, + 16, 1, 7, 1, 8, + }}, + {14, {25, 51}, { // R11x77 + 0, 0, 0, 0, 0, + 24, 1, 43, 0, 0, + 0, 0, 0, 0, 0, + 22, 1, 11, 1, 12, + }}, + {15, {23, 49, 75}, { // R11x99 + 0, 0, 0, 0, 0, + 16, 1, 28, 1, 29, + 0, 0, 0, 0, 0, + 30, 1, 14, 1, 15, + }}, + {16, {27, 55, 83, 111}, { // R11x139 + 0, 0, 0, 0, 0, + 24, 2, 42, 0, 0, + 0, 0, 0, 0, 0, + 30, 3, 14, 0, 0, + }}, + {17, {}, { // R13x27 + 0, 0, 0, 0, 0, + 9, 1, 12, 0, 0, + 0, 0, 0, 0, 0, + 14, 1, 7, 0, 0, + }}, + {18, {21}, { // R13x43 + 0, 0, 0, 0, 0, + 14, 1, 27, 0, 0, + 0, 0, 0, 0, 0, + 28, 1, 13, 0, 0, + }}, + {19, {19, 39}, { // R13x59 + 0, 0, 0, 0, 0, + 22, 1, 38, 0, 0, + 0, 0, 0, 0, 0, + 20, 2, 10, 0, 0, + }}, + {20, {25, 51}, { // R13x77 + 0, 0, 0, 0, 0, + 16, 1, 26, 1, 27, + 0, 0, 0, 0, 0, + 28, 1, 14, 1, 15, + }}, + {21, {23, 49, 75}, { // R13x99 + 0, 0, 0, 0, 0, + 20, 1, 36, 1, 37, + 0, 0, 0, 0, 0, + 26, 1, 11, 2, 12, + }}, + {22, {27, 55, 83, 111}, { // R13x139 + 0, 0, 0, 0, 0, + 20, 2, 35, 1, 36, + 0, 0, 0, 0, 0, + 28, 2, 13, 2, 14, + }}, + {23, {21}, { // R15x43 + 0, 0, 0, 0, 0, + 18, 1, 33, 0, 0, + 0, 0, 0, 0, 0, + 18, 1, 7, 1, 8, + }}, + {24, {19, 39}, { // R15x59 + 0, 0, 0, 0, 0, + 26, 1, 48, 0, 0, + 0, 0, 0, 0, 0, + 24, 2, 13, 0, 0, + }}, + {25, {25, 51}, { // R15x77 + 0, 0, 0, 0, 0, + 18, 1, 33, 1, 34, + 0, 0, 0, 0, 0, + 24, 2, 10, 1, 11, + }}, + {26, {23, 49, 75}, { // R15x99 + 0, 0, 0, 0, 0, + 24, 2, 44, 0, 0, + 0, 0, 0, 0, 0, + 22, 4, 12, 0, 0, + }}, + {27, {27, 55, 83, 111}, { // R15x139 + 0, 0, 0, 0, 0, + 24, 2, 42, 1, 43, + 0, 0, 0, 0, 0, + 26, 1, 13, 4, 14, + }}, + {28, {21}, { // R17x43 + 0, 0, 0, 0, 0, + 22, 1, 39, 0, 0, + 0, 0, 0, 0, 0, + 20, 1, 10, 1, 11, + }}, + {29, {19, 39}, { // R17x59 + 0, 0, 0, 0, 0, + 16, 2, 28, 0, 0, + 0, 0, 0, 0, 0, + 30, 2, 14, 0, 0, + }}, + {30, {25, 51}, { // R17x77 + 0, 0, 0, 0, 0, + 22, 2, 39, 0, 0, + 0, 0, 0, 0, 0, + 28, 1, 12, 2, 13, + }}, + {31, {23, 49, 75}, { // R17x99 + 0, 0, 0, 0, 0, + 20, 2, 33, 1, 34, + 0, 0, 0, 0, 0, + 26, 4, 14, 0, 0, + }}, + {32, {27, 55, 83, 111}, { // R17x139 + 0, 0, 0, 0, 0, + 20, 4, 38, 0, 0, + 0, 0, 0, 0, 0, + 26, 2, 12, 4, 13, + }}, + }; + + if (number < 1 || number > Size(allVersions)) + return nullptr; + return allVersions + number - 1; +} + +static const PointI dimsVersionRMQR[32] { + {43, 7}, {59, 7}, {77, 7}, {99, 7}, {139, 7}, + {43, 9}, {59, 9}, {77, 9}, {99, 9}, {139, 9}, + {27, 11}, {43, 11}, {59, 11}, {77, 11}, {99, 11}, {139, 11}, + {27, 13}, {43, 13}, {59, 13}, {77, 13}, {99, 13}, {139, 13}, + {43, 15}, {59, 15}, {77, 15}, {99, 15}, {139, 15}, + {43, 17}, {59, 17}, {77, 17}, {99, 17}, {139, 17}, +}; + +static int getVersionRMQR(const BitMatrix& bitMatrix) +{ + const int width = bitMatrix.width(); + const int height = bitMatrix.height(); + if (width != height && (width & 1) && (height & 1) && width >= 27 && width <= 139 && height >= 7 && height <= 17) + for (int i = 0; i < Size(dimsVersionRMQR); i++) + if (width == dimsVersionRMQR[i].x && height == dimsVersionRMQR[i].y) + return i; + return -1; +} + +PointI Version::DimensionOfVersionRMQR(int versionNumber) +{ + if (versionNumber < 1 || versionNumber > Size(dimsVersionRMQR)) + return {0, 0}; + return dimsVersionRMQR[versionNumber - 1]; +} + const Version* Version::Model1(int number) { /** @@ -396,9 +633,10 @@ const Version* Version::Model1(int number) } Version::Version(int versionNumber, std::initializer_list alignmentPatternCenters, const std::array& ecBlocks) - : _versionNumber(versionNumber), _alignmentPatternCenters(alignmentPatternCenters), _ecBlocks(ecBlocks), _type(Type::Model2) + : _versionNumber(versionNumber), _alignmentPatternCenters(alignmentPatternCenters), _ecBlocks(ecBlocks), + _type(ecBlocks[0].codewordsPerBlock == 0 ? Type::rMQR : Type::Model2) { - _totalCodewords = ecBlocks[0].totalDataCodewords(); + _totalCodewords = ecBlocks[1].totalDataCodewords(); // Use 1 (M) as 0 dummy for rMQR } Version::Version(int versionNumber, const std::array& ecBlocks) @@ -412,17 +650,27 @@ Version::Version(int versionNumber, const std::array& ecBlocks) bool Version::HasMicroSize(const BitMatrix& bitMatrix) { int size = bitMatrix.height(); - return size >= 11 && size <= 17 && (size % 2) == 1; + return size == bitMatrix.width() && size >= 11 && size <= 17 && (size % 2) == 1; +} + +bool Version::HasRMQRSize(const BitMatrix& bitMatrix) +{ + return getVersionRMQR(bitMatrix) != -1; } bool Version::HasValidSize(const BitMatrix& bitMatrix) { int size = bitMatrix.height(); + if (bitMatrix.width() != size) + return HasRMQRSize(bitMatrix); return HasMicroSize(bitMatrix) || (size >= 21 && size <= 177 && (size % 4) == 1); } int Version::Number(const BitMatrix& bitMatrix) { + if (bitMatrix.width() != bitMatrix.height()) + return getVersionRMQR(bitMatrix) + 1; + if (!HasValidSize(bitMatrix)) return 0; @@ -461,6 +709,47 @@ const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBi */ BitMatrix Version::buildFunctionPattern() const { + if (isRMQR()) { + PointI dimension = Version::DimensionOfVersionRMQR(_versionNumber); + BitMatrix bitMatrix(dimension.x, dimension.y); + + // Set edge timing patterns + bitMatrix.setRegion(0, 0, dimension.x, 1); // Top + bitMatrix.setRegion(0, dimension.y - 1, dimension.x, 1); // Bottom + bitMatrix.setRegion(0, 1, 1, dimension.y - 2); // Left + bitMatrix.setRegion(dimension.x - 1, 1, 1, dimension.y - 2); // Right + + // Set vertical timing and alignment patterns + size_t max = _alignmentPatternCenters.size(); // Same as vertical timing column + for (size_t x = 0; x < max; ++x) { + int cx = _alignmentPatternCenters[x]; + bitMatrix.setRegion(cx - 1, 1, 3, 2); // Top alignment pattern + bitMatrix.setRegion(cx - 1, dimension.y - 3, 3, 2); // Bottom alignment pattern + bitMatrix.setRegion(cx, 3, 1, dimension.y - 6); // Vertical timing pattern + } + + // Top left finder pattern + separator + bitMatrix.setRegion(1, 1, 8 - 1, 8 - 1 - (dimension.y == 7)); // R7 finder bottom flush with edge + // Top left format + bitMatrix.setRegion(8, 1, 3, 5); + bitMatrix.setRegion(11, 1, 1, 3); + + // Bottom right finder subpattern + bitMatrix.setRegion(dimension.x - 5, dimension.y - 5, 5 - 1, 5 - 1); + // Bottom right format + bitMatrix.setRegion(dimension.x - 8, dimension.y - 6, 3, 5); + bitMatrix.setRegion(dimension.x - 5, dimension.y - 6, 3, 1); + + // Top right corner finder + bitMatrix.set(dimension.x - 2, 1); + if (dimension.y > 9) { + // Bottom left corner finder + bitMatrix.set(1, dimension.y - 2); + } + + return bitMatrix; + } + int dimension = this->dimension(); BitMatrix bitMatrix(dimension, dimension); diff --git a/core/src/qrcode/QRVersion.h b/core/src/qrcode/QRVersion.h index e73091d75b..cbc8bb1fcb 100644 --- a/core/src/qrcode/QRVersion.h +++ b/core/src/qrcode/QRVersion.h @@ -7,6 +7,7 @@ #pragma once +#include "Point.h" #include "QRECB.h" #include "QRErrorCorrectionLevel.h" @@ -28,6 +29,7 @@ class Version public: Type type() const { return _type; } bool isMicro() const { return type() == Type::Micro; } + bool isRMQR() const { return type() == Type::rMQR; } bool isModel1() const { return type() == Type::Model1; } bool isModel2() const { return type() == Type::Model2; } @@ -49,8 +51,10 @@ class Version { return DimensionOffset(isMicro) + DimensionStep(isMicro) * version; } + static PointI DimensionOfVersionRMQR(int versionNumber); static bool HasMicroSize(const BitMatrix& bitMatrix); + static bool HasRMQRSize(const BitMatrix& bitMatrix); static bool HasValidSize(const BitMatrix& bitMatrix); static int Number(const BitMatrix& bitMatrix); @@ -59,6 +63,7 @@ class Version static const Version* Model1(int number); static const Version* Model2(int number); static const Version* Micro(int number); + static const Version* rMQR(int number); private: int _versionNumber; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 1934d41bf2..82eaa05ded 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -611,6 +611,14 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 9, 0, pure }, }); + runTests("rmqrcode-1", "rMQR", 2, { + { 1, 2, 0 }, + { 1, 2, 90 }, + { 1, 2, 180 }, + { 1, 2, 270 }, + { 1, 1, pure }, + }); + runTests("pdf417-1", "PDF417", 17, { { 16, 17, 0 }, { 1, 17, 90 }, diff --git a/test/samples/rmqrcode-1/R7x43-H.png b/test/samples/rmqrcode-1/R7x43-H.png new file mode 100644 index 0000000000000000000000000000000000000000..d62eb47c6e911eea478fcd4862accd088fb8660e GIT binary patch literal 186 zcmeAS@N?(olHy`uVBq!ia0vp^5kM@!$P6S`1Zb53DYgKg5ZC|z{{xw!hc4FvDb50q z$YKTtZeb8+WSBKa0w@^e>Eal|aXt3jLeT>XJg$LRo18^gyZ$}Xu+X)%O)HFfvSgI- zt11rv*q=@_tau#Q8s40lvg_n#bG7t~DTQ+?cjs)>@b+GR>5;`NiLDxQW+uIq%4fCw e&!2QISYF*QczN8m#nC`p7(8A5T-G@yGywp#cRbes literal 0 HcmV?d00001 diff --git a/test/samples/rmqrcode-1/R7x43-H.result.txt b/test/samples/rmqrcode-1/R7x43-H.result.txt new file mode 100644 index 0000000000..f29c889326 --- /dev/null +++ b/test/samples/rmqrcode-1/R7x43-H.result.txt @@ -0,0 +1 @@ +symbologyIdentifier=]Q1 diff --git a/test/samples/rmqrcode-1/R7x43-H.txt b/test/samples/rmqrcode-1/R7x43-H.txt new file mode 100644 index 0000000000..8b22b22d43 --- /dev/null +++ b/test/samples/rmqrcode-1/R7x43-H.txt @@ -0,0 +1 @@ +,, \ No newline at end of file diff --git a/test/samples/rmqrcode-1/R7x43-H_inverted.png b/test/samples/rmqrcode-1/R7x43-H_inverted.png new file mode 100644 index 0000000000000000000000000000000000000000..138a06562d78574cda2c96553df80df78ca5228f GIT binary patch literal 152 zcmeAS@N?(olHy`uVBq!ia0vp^VL;5s$P6U=T`To~6kC8#h%1o(|NsBeoAd7iS-zew zjv*Y^W6y2mJYc}%>ew9Z*!GbjD)zy*#xTwue;1S-EGvJyRY-wh>)pg`t^0zB?UPm4 zFZ=hRNJug}ar)jpr>tf8KHGg{Ec;=:/utf-8>") +endif() + # Our executable add_executable (UnitTest BarcodeFormatTest.cpp @@ -62,6 +67,7 @@ add_executable (UnitTest qrcode/QRModeTest.cpp qrcode/QRVersionTest.cpp qrcode/QRWriterTest.cpp + qrcode/RMQRDecoderTest.cpp pdf417/PDF417DecoderTest.cpp pdf417/PDF417ErrorCorrectionTest.cpp pdf417/PDF417HighLevelEncoderTest.cpp diff --git a/test/unit/qrcode/QRFormatInformationTest.cpp b/test/unit/qrcode/QRFormatInformationTest.cpp index 90a948eb00..0e8488bd9c 100644 --- a/test/unit/qrcode/QRFormatInformationTest.cpp +++ b/test/unit/qrcode/QRFormatInformationTest.cpp @@ -15,6 +15,8 @@ static const int MASKED_TEST_FORMAT_INFO = 0x2BED; static const int MASKED_TEST_FORMAT_INFO2 = ((0x2BED << 1) & 0b1111111000000000) | 0b100000000 | (0x2BED & 0b11111111); // insert the 'Dark Module' static const int UNMASKED_TEST_FORMAT_INFO = MASKED_TEST_FORMAT_INFO ^ 0x5412; static const int MICRO_MASKED_TEST_FORMAT_INFO = 0x3BBA; +static const int RMQR_MASKED_TEST_FORMAT_INFO = 0x20137; +static const int RMQR_MASKED_TEST_FORMAT_INFO_SUB = 0x1F1FE; static void DoFormatInformationTest(const int formatInfo, const uint8_t expectedMask, const ErrorCorrectionLevel& expectedECL) { @@ -24,6 +26,18 @@ static void DoFormatInformationTest(const int formatInfo, const uint8_t expected EXPECT_EQ(expectedECL, parsedFormat.ecLevel); } +// Helper for rMQR to unset `numBits` number of bits +static uint32_t RMQRUnsetBits(uint32_t formatInfoBits, int numBits) +{ + for (int i = 0; i < 18 && numBits; i++) { + if (formatInfoBits & (1 << i)) { + formatInfoBits ^= 1 << i; + numBits--; + } + } + return formatInfoBits; +} + TEST(QRFormatInformationTest, Decode) { // Normal case @@ -89,3 +103,44 @@ TEST(QRFormatInformationTest, DecodeMicroWithBitDifference) // EXPECT_NE(expected.errorCorrectionLevel(), // FormatInformation::DecodeFormatInformation(MICRO_MASKED_TEST_FORMAT_INFO ^ 0x3f).errorCorrectionLevel()); } + +TEST(QRFormatInformationTest, DecodeRMQR) +{ + // Normal case + FormatInformation expected = FormatInformation::DecodeRMQR(RMQR_MASKED_TEST_FORMAT_INFO, RMQR_MASKED_TEST_FORMAT_INFO_SUB); + EXPECT_TRUE(expected.isValid()); + EXPECT_EQ(4, expected.dataMask); + EXPECT_EQ(ErrorCorrectionLevel::High, expected.ecLevel); + EXPECT_EQ(FORMAT_INFO_MASK_RMQR, expected.mask); + // Not catered for: where the code forgot the mask! +} + +TEST(QRFormatInformationTest, DecodeRMQRWithBitDifference) +{ + FormatInformation expected = FormatInformation::DecodeRMQR(RMQR_MASKED_TEST_FORMAT_INFO, RMQR_MASKED_TEST_FORMAT_INFO_SUB); + EXPECT_EQ(expected.ecLevel, ErrorCorrectionLevel::High); + // 1,2,3,4,5 bits difference + EXPECT_EQ(expected, FormatInformation::DecodeRMQR(RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 1), RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 1))); + EXPECT_EQ(expected, FormatInformation::DecodeRMQR(RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 2), RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 2))); + EXPECT_EQ(expected, FormatInformation::DecodeRMQR(RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 3), RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 3))); + EXPECT_EQ(expected, FormatInformation::DecodeRMQR(RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 4), RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 4))); + auto unexpected = FormatInformation::DecodeRMQR(RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 5), RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 5)); + EXPECT_FALSE(expected == unexpected); + EXPECT_FALSE(unexpected.isValid()); + EXPECT_TRUE(unexpected.type() == Type::rMQR); // Note `mask` (used to determine type) set regardless +} + +TEST(QRFormatInformationTest, DecodeRMQRWithMisread) +{ + FormatInformation expected = FormatInformation::DecodeRMQR(RMQR_MASKED_TEST_FORMAT_INFO, RMQR_MASKED_TEST_FORMAT_INFO_SUB); + { + auto actual = FormatInformation::DecodeRMQR(RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 2), RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 4)); + EXPECT_EQ(expected, actual); + EXPECT_EQ(actual.mask, FORMAT_INFO_MASK_RMQR); + } + { + auto actual = FormatInformation::DecodeRMQR(RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO, 5), RMQRUnsetBits(RMQR_MASKED_TEST_FORMAT_INFO_SUB, 4)); + EXPECT_EQ(expected, actual); + EXPECT_EQ(actual.mask, FORMAT_INFO_MASK_RMQR_SUB); + } +} diff --git a/test/unit/qrcode/QRModeTest.cpp b/test/unit/qrcode/QRModeTest.cpp index 84f3f00a39..9b83b15e68 100644 --- a/test/unit/qrcode/QRModeTest.cpp +++ b/test/unit/qrcode/QRModeTest.cpp @@ -16,12 +16,12 @@ using namespace ZXing::QRCode; TEST(QRModeTest, ForBits) { - ASSERT_EQ(CodecMode::TERMINATOR, CodecModeForBits(0x00)); - ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x01)); - ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x02)); - ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x04)); - ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x08)); - ASSERT_THROW(CodecModeForBits(0x10), Error); + ASSERT_EQ(CodecMode::TERMINATOR, CodecModeForBits(0x00, Type::Model2)); + ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x01, Type::Model2)); + ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x02, Type::Model2)); + ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x04, Type::Model2)); + ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x08, Type::Model2)); + ASSERT_THROW(CodecModeForBits(0x10, Type::Model2), Error); } TEST(QRModeTest, CharacterCount) @@ -38,22 +38,22 @@ TEST(QRModeTest, CharacterCount) TEST(QRModeTest, MicroForBits) { // M1 - ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x00, true)); + ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x00, Type::Micro)); // M2 - ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x00, true)); - ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x01, true)); + ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x00, Type::Micro)); + ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x01, Type::Micro)); // M3 - ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x00, true)); - ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x01, true)); - ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x02, true)); - ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x03, true)); + ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x00, Type::Micro)); + ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x01, Type::Micro)); + ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x02, Type::Micro)); + ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x03, Type::Micro)); // M4 - ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x00, true)); - ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x01, true)); - ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x02, true)); - ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x03, true)); + ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x00, Type::Micro)); + ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x01, Type::Micro)); + ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x02, Type::Micro)); + ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x03, Type::Micro)); - ASSERT_THROW(CodecModeForBits(0x04, true), Error); + ASSERT_THROW(CodecModeForBits(0x04, Type::Micro), Error); } TEST(QRModeTest, MicroCharacterCount) @@ -66,3 +66,27 @@ TEST(QRModeTest, MicroCharacterCount) ASSERT_EQ(4, CharacterCountBits(CodecMode::BYTE, *Version::Micro(3))); ASSERT_EQ(4, CharacterCountBits(CodecMode::KANJI, *Version::Micro(4))); } + +TEST(QRModeTest, RMQRForBits) +{ + ASSERT_EQ(CodecMode::TERMINATOR, CodecModeForBits(0x00, Type::rMQR)); + ASSERT_EQ(CodecMode::NUMERIC, CodecModeForBits(0x01, Type::rMQR)); + ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x02, Type::rMQR)); + ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x03, Type::rMQR)); + ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x04, Type::rMQR)); + ASSERT_EQ(CodecMode::FNC1_FIRST_POSITION, CodecModeForBits(0x05, Type::rMQR)); + ASSERT_EQ(CodecMode::FNC1_SECOND_POSITION, CodecModeForBits(0x06, Type::rMQR)); + ASSERT_EQ(CodecMode::ECI, CodecModeForBits(0x07, Type::rMQR)); + ASSERT_THROW(CodecModeForBits(0x08, Type::rMQR), Error); +} + +TEST(QRModeTest, RMQRCharacterCount) +{ + // Spot check a few values + ASSERT_EQ(7, CharacterCountBits(CodecMode::NUMERIC, *Version::rMQR(5))); + ASSERT_EQ(8, CharacterCountBits(CodecMode::NUMERIC, *Version::rMQR(26))); + ASSERT_EQ(9, CharacterCountBits(CodecMode::NUMERIC, *Version::rMQR(32))); + ASSERT_EQ(5, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::rMQR(6))); + ASSERT_EQ(5, CharacterCountBits(CodecMode::BYTE, *Version::rMQR(7))); + ASSERT_EQ(5, CharacterCountBits(CodecMode::KANJI, *Version::rMQR(8))); +} diff --git a/test/unit/qrcode/QRVersionTest.cpp b/test/unit/qrcode/QRVersionTest.cpp index ba3c6fbffd..04face2d10 100644 --- a/test/unit/qrcode/QRVersionTest.cpp +++ b/test/unit/qrcode/QRVersionTest.cpp @@ -7,6 +7,7 @@ #include "qrcode/QRVersion.h" #include "BitMatrix.h" +#include "BitMatrixIO.h" #include "gtest/gtest.h" @@ -98,3 +99,111 @@ TEST(QRVersionTest, FunctionPattern) EXPECT_TRUE(functionPattern.get(col, 0)); } } + +namespace { + + void CheckRMQRVersion(const Version* version, int number) { + ASSERT_NE(version, nullptr); + EXPECT_EQ(number, version->versionNumber()); + EXPECT_EQ(Version::DimensionOfVersionRMQR(number).x == 27, version->alignmentPatternCenters().empty()); + } + +} + +TEST(QRVersionTest, RMQRVersionForNumber) +{ + auto version = Version::rMQR(0); + EXPECT_EQ(version, nullptr) << "There is version with number 0"; + + for (int i = 1; i <= 32; i++) { + CheckRMQRVersion(Version::rMQR(i), i); + } +} + +TEST(QRVersionTest, RMQRFunctionPattern) +{ + { + const auto expected = ParseBitMatrix( + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" + "XXXXXXXXXXXX XXX XXXXXXXX\n" + "XXXXXXXXXXXX XXX XXXXXXXX\n" + "XXXXXXXXXXXX X XXXXXXXX\n" + "XXXXXXXXXXX XXX XXXXXXXX\n" + "XXXXXXXXXXX XXX XXXXXXXX\n" + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n", + 'X', false); + const auto version = Version::rMQR(1); // R7x43 + const auto functionPattern = version->buildFunctionPattern(); + EXPECT_EQ(expected, functionPattern); + } + { + const auto expected = ParseBitMatrix( + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" + "XXXXXXXXXXXX XXX XX\n" + "XXXXXXXXXXXX XXX X\n" + "XXXXXXXXXXXX X XXXXXX X\n" + "XXXXXXXXXXX X XXXXXXXX\n" + "XXXXXXXXXXX X XXXXXXXX\n" + "XXXXXXXX XXX XXXXXXXX\n" + "XXXXXXXX XXX XXXXXXXX\n" + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n", + 'X', false); + const auto version = Version::rMQR(6); // R9x43 + const auto functionPattern = version->buildFunctionPattern(); + EXPECT_EQ(expected, functionPattern); + } + { + const auto expected = ParseBitMatrix( + "XXXXXXXXXXXXXXXXXXXXXXXXXXX\n" + "XXXXXXXXXXXX XX\n" + "XXXXXXXXXXXX X\n" + "XXXXXXXXXXXX X\n" + "XXXXXXXXXXX X\n" + "XXXXXXXXXXX XXXXXX X\n" + "XXXXXXXX XXXXXXXX\n" + "XXXXXXXX XXXXXXXX\n" + "X XXXXXXXX\n" + "XX XXXXXXXX\n" + "XXXXXXXXXXXXXXXXXXXXXXXXXXX\n", + 'X', false); + const auto version = Version::rMQR(11); // R11x27 + const auto functionPattern = version->buildFunctionPattern(); + EXPECT_EQ(expected, functionPattern); + } + { + const auto expected = ParseBitMatrix( + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" + "XXXXXXXXXXXX XXX XX\n" + "XXXXXXXXXXXX XXX X\n" + "XXXXXXXXXXXX X X\n" + "XXXXXXXXXXX X X\n" + "XXXXXXXXXXX X XXXXXX X\n" + "XXXXXXXX X XXXXXXXX\n" + "XXXXXXXX X XXXXXXXX\n" + "X XXX XXXXXXXX\n" + "XX XXX XXXXXXXX\n" + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n", + 'X', false); + const auto version = Version::rMQR(12); // R11x43 + const auto functionPattern = version->buildFunctionPattern(); + EXPECT_EQ(expected, functionPattern); + } + { + const auto expected = ParseBitMatrix( + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n" + "XXXXXXXXXXXX XXX XXX XX\n" + "XXXXXXXXXXXX XXX XXX X\n" + "XXXXXXXXXXXX X X X\n" + "XXXXXXXXXXX X X X\n" + "XXXXXXXXXXX X X XXXXXX X\n" + "XXXXXXXX X X XXXXXXXX\n" + "XXXXXXXX X X XXXXXXXX\n" + "X XXX XXX XXXXXXXX\n" + "XX XXX XXX XXXXXXXX\n" + "XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\n", + 'X', false); + const auto version = Version::rMQR(13); // R11x59 + const auto functionPattern = version->buildFunctionPattern(); + EXPECT_EQ(expected, functionPattern); + } +} diff --git a/test/unit/qrcode/RMQRDecoderTest.cpp b/test/unit/qrcode/RMQRDecoderTest.cpp new file mode 100644 index 0000000000..46c20664f0 --- /dev/null +++ b/test/unit/qrcode/RMQRDecoderTest.cpp @@ -0,0 +1,206 @@ +/* + * Copyright 2023 gitlost +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "qrcode/QRDecoder.h" + +#include "BitMatrix.h" +#include "BitMatrixIO.h" +#include "DecoderResult.h" +#include "ECI.h" + +#include "gtest/gtest.h" + +using namespace ZXing; +using namespace ZXing::QRCode; + +TEST(RMQRDecoderTest, RMQRCodeR7x43M) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X X XXX X X X X X X X X XXX\n" + "X X X XXX XXXXX XXX X X XX X X\n" + "X XXX X X XXX X X X XXXX XXXX X X XXXXXXXX\n" + "X XXX X XX XXXXX XXXXXX X X X X\n" + "X XXX X XX XXX XXXXXXX X X XX X X X\n" + "X X XXXXX XXX XXX XXXXX XXXXXX X X\n" + "XXXXXXX X X X X X X XXX X X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(result.content().text(TextMode::Plain), "ABCDEFG"); +} + +TEST(RMQRDecoderTest, RMQRCodeR7x43MError6Bits) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X X XXX X X X X X X X X XXX\n" + "X X X XXX XXXXX XXX X X XX X X\n" + "X XXX X X XXX X X XXXX XXXX XX X XXXXXXXX\n" // 2 + "X XXX X XX XXXXX X XXXXXX X X X X\n" // 3 + "X XXX X XX XXX XXXXXXX X X XXX X X X\n" // 5 + "X X XXXXX XXX XXX XXXX X XXXXXX X X\n" // 6 + "XXXXXXX X X X X X X XXX X X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_EQ(Error::Checksum, result.error()); + EXPECT_TRUE(result.text().empty()); + EXPECT_TRUE(result.content().text(TextMode::Plain).empty()); +} + +TEST(RMQRDecoderTest, RMQRCodeR7x139H) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X XXX\n" + "X X XX XXX X X X X X XX XX X X X XXX XX XXXX XXX XX XX XX X XX X X X XXX X XX XX XX X X XX X XX XXXX X X X\n" + "X XXX X X XXXXX X XXXXX X X XXX XX X XXX X XX XXX XX X XXX X X XXXX X XXXXXXX X XX XXX X X X XXX X XXXXX\n" + "X XXX X XXXX X XX X X XX XX X XX XX X XXX XX X XX X XX X X XX X X XXX X X X X X X X XX X XX XX X X X\n" + "X XXX X XXXX XXXXX X X XXXXXX XX X XXXX X XXXX X XXX XXXX X XXXXXXX XXX XXXXXX X X XX X XXX X XXXXXXXXX X XXXX X X X X X\n" + "X X X XX XX X X XX X X X XXXX X X X XX X XXX X X X X X XXX XX XXX X X XX XXXX XX X X X X XXXXX XXX XX X XX X\n" + "XXXXXXX X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X X X XXX X X X X X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(result.content().text(TextMode::Plain), "1234567890,ABCDEFGHIJKLMOPQRSTUVW"); +} + +TEST(RMQRDecoderTest, RMQRCodeR9x59H) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X XXX X X X X X X X X XXX X X X X X X X XXX\n" + "X X X XXXXX XXX X X XXXXXXXX X X X X XXXX X X\n" + "X XXX X XX XXX X XXX XXXX X XXXXXXX X XXXXX X X\n" + "X XXX X XXXX X XX X XX XXXX XX XX X X X XXX X \n" + "X XXX X X X XX XXXXXX X X XX X XX X X XXXX XXXXX\n" + "X X X X X X XXX X X X XX X XXXX XX X X X X\n" + "XXXXXXX XXXXX XXXXXX X XX XXX X XXXX X X X XX X X\n" + " XXX XXXX XX XXX X XXXXXXX X XX XXX XX XX X\n" + "XXX X X X X X X X XXX X X X X X X X X XXX X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(result.content().text(TextMode::Plain), "ABCDEFGHIJKLMN"); +} + +TEST(RMQRDecoderTest, RMQRCodeR9x77M) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X X XXX\n" + "X X XXX XX XXX XXX XXXX XXX XX X XXXXXXXXX X XXX XXXX X XXXX XX XXX X\n" + "X XXX X X X X XXX X XXXX XX XX X XX XX XXX XXXX X X XX X X XX X\n" + "X XXX X X X XXXXXX X XX XXXX X XXX X XX X XX XX XX X XXX X X XXX XX \n" + "X XXX X XXXX X X XXXX XXXX XX XXX X XX XXXXXX X X XXX XX XXXXX\n" + "X X X X XX XXX X X XX X X XX XXX X X X X X XX XXXXX X\n" + "XXXXXXX X XX XX X XXXX X X X X X XX XXX X XX X XXX XX X X\n" + " X XXXXX XX X XXXXXX XX XXXXX X XX XX XXXXX XXX X\n" + "XXX X X X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(result.content().text(TextMode::Plain), "__ABCDEFGH__1234567890___ABCDEFGHIJK"); +} + +TEST(RMQRDecoderTest, RMQRCodeR11x27H) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X X X X XXX\n" + "X X XX X X X X\n" + "X XXX X X XX X X XX\n" + "X XXX X XXXX XX X XXXXXX \n" + "X XXX X X X XX XX XXX X\n" + "X X XXX X XX XXXX X \n" + "XXXXXXX X XX X XXXXX\n" + " X X X X X\n" + "XXXX X X X XX XXXXXX X X\n" + "X XX XXXXXX XXX XXXX X X\n" + "XXX X X X X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(result.content().text(TextMode::Plain), "ABCDEF"); +} + +TEST(RMQRDecoderTest, RMQRCodeR13x27M_ECI) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X X X X XXX\n" + "X X XX XX XXX XX X\n" + "X XXX X XX X XX XX XXX X\n" + "X XXX X XX X XX X X XX \n" + "X XXX X XXXXXXX X X XX\n" + "X X XX X XXX XX XX \n" + "XXXXXXX X X X X XXX\n" + " XXX XX X XX XXX \n" + "XXX XX XX X X XX XX XXXXX\n" + " XXX X X X X X X\n" + "X XX X X XX X XX X X X X\n" + "X X X X X X X X X\n" + "XXX X X X X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(result.content().text(TextMode::Plain), "AB貫12345AB"); + EXPECT_TRUE(result.content().hasECI); + EXPECT_EQ(result.content().encodings[0].eci, ECI::Shift_JIS); + EXPECT_EQ(result.content().symbology.toString(), "]Q1"); +} + +TEST(RMQRDecoderTest, RMQRCodeR15x59H_GS1) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X XXX X X X X X X X X XXX X X X X X X X XXX\n" + "X X XXX XXX X XXXXX XX XXX X X X X X X XXX X\n" + "X XXX X XXX XX X XXX XXX X X XXX XXXXX XX XXX XX\n" + "X XXX X X X XX X X XXX X X X XXXXX XX XXX \n" + "X XXX X XX XXX XX X X X XX XX XX XXX XXXX X XXXX\n" + "X X X X X X X XXX XXX XXXX X XXX XX X X \n" + "XXXXXXX X XXX XXXX X XX XXXX X X XX XXX XXXXX X\n" + " X XXX X XXXXX X XX XXXX XX X \n" + "XX XX X X X XXXXX XX X X XX XX X XX X X XX X\n" + " XX XX X XXXXXX XXX XX X X XX XXX X X XXX \n" + "X X XX XXXXXXXXXX XX X X XX XX XX X XXXX XX XXXXXX\n" + " XX X XX X XXX X X X XXX X XXX X X XXX XXXX X\n" + "XXXX X X XX XXX X X X XX XXXXX XX X XX XXX X X\n" + "X X X XX XXX XXXXXXX XXX X XXX XX X X X XX X\n" + "XXX X X X X X X X XXX X X X X X X X X XXX X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_TRUE(result.content().type() == ContentType::GS1); + EXPECT_EQ(result.content().text(TextMode::HRI), "(01)09524000059109(21)12345678p901(10)1234567p(17)231120"); +} + +TEST(RMQRDecoderTest, RMQRCodeR17x99H) +{ + const auto bitMatrix = ParseBitMatrix( + "XXXXXXX X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X XXX\n" + "X X X XXXXX XXX X X X XX X X XX XXXXX X XX X XX XXX X X XX X X XXX X X XX X X X\n" + "X XXX X X X XXX XXX X XXX XXX X X XX XXXX X X X X XXX XXXXX X X XX X XX X X\n" + "X XXX X XX X XX X X XX X XXXX X XXXXX X X XX X XXX XX X X X X XXXXXX X \n" + "X XXX X X XX X X X X X X X X XXX XX XXXXXX X X XXX X XXXXXX X X X X X X X XX X\n" + "X X XX X X XXXXX XX X XXX X XX X X XXX X XXX XXX X XXXX XX X X X XX XXXX \n" + "XXXXXXX X XX X XX X X XXX XX X XXXX X X XXX X X XX X XXXX XX X X X XX X XXXX\n" + " XX XX XX XX X XX X X X XXX XX X X XXX XXXX XX X X X X XX XX XXX \n" + "XX X XXX X X XXXX XXX XXXXX XXX XXX X X X X X XXX X XX XX X X X X XX X XXX\n" + " X XXXXX X X XXXXX X XX X XX XXXX X X XXXXX X XX X XX X XX X XX XX \n" + "X XX XX X XX XXX XX XXXXXX X XXXXX XX XXXX X X X X XXXX XX X X XXXXXX XX X X\n" + " XXX XX XXX XX XX X X X XX X X X X XX XXX XXXX X XX XXX X X X XXXX XXXXX X XXX \n" + "X X XX X XX XX XX X X XX X X X XX XXXXXXXX X XX XX X X X X X XX X X XXXXXXXXXXX\n" + " X X X XX X X X XX XXXX X XXX X XX X X X X X XXX XXXXX XX X X X XXXXX X X X\n" + "XXXX XX XX X XXXX XXXX X XX X XX XX XX XXXX XXX X X XX XX X XXXX X XXX XX X XX X X\n" + "X XXX XX XXX X X X XXX X XXX X XXXX XX X X XXXXX X XX X X X X X X X X XXXX XXXX X\n" + "XXX X X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X X X X XXX X X X X X X X X XXXXX\n", + 'X', false); + + const auto result = Decode(bitMatrix); + EXPECT_TRUE(result.isValid()); + EXPECT_EQ(result.content().text(TextMode::Plain), "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890________________________"); +} From c24662d3db7d0e7c2c1ffa5610456eee8ba30535 Mon Sep 17 00:00:00 2001 From: gitlost Date: Tue, 5 Dec 2023 23:15:36 +0000 Subject: [PATCH 142/587] ODCode128WriterTest: change "\xf1" etc to use "\u00f1" etc so that test passes on Windows (due to change to default UTF-8 in "test/unit/CMakeLists.txt" apparently); only needed to change the 3 tests with two instances in the one string but changed the others for consistency) --- test/unit/oned/ODCode128WriterTest.cpp | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/test/unit/oned/ODCode128WriterTest.cpp b/test/unit/oned/ODCode128WriterTest.cpp index de4c0fe912..d89ac42ea6 100644 --- a/test/unit/oned/ODCode128WriterTest.cpp +++ b/test/unit/oned/ODCode128WriterTest.cpp @@ -43,7 +43,7 @@ static ZXing::Result Decode(const BitMatrix &matrix) TEST(ODCode128Writer, EncodeWithFunc1) { - auto toEncode = L"\xf1""123"; + auto toEncode = L"\u00f1123"; // "12" "3" check digit 92 auto expected = QUIET_SPACE + START_CODE_C + FNC1 + "10110011100" + SWITCH_CODE_B + "11001011100" + "10101111000" + STOP + QUIET_SPACE; @@ -53,7 +53,7 @@ TEST(ODCode128Writer, EncodeWithFunc1) TEST(ODCode128Writer, EncodeWithFunc2) { - auto toEncode = L"\xf2""123"; + auto toEncode = L"\u00f2123"; // "1" "2" "3" check digit 56 auto expected = QUIET_SPACE + START_CODE_B + FNC2 + "10011100110" + "11001110010" + "11001011100" + "11100010110" + STOP + QUIET_SPACE; @@ -63,7 +63,7 @@ TEST(ODCode128Writer, EncodeWithFunc2) TEST(ODCode128Writer, EncodeWithFunc3) { - auto toEncode = L"\xf3""123"; + auto toEncode = L"\u00f3123"; // "1" "2" "3" check digit 51 auto expected = QUIET_SPACE + START_CODE_B + FNC3 + "10011100110" + "11001110010" + "11001011100" + "11101000110" + STOP + QUIET_SPACE; @@ -73,7 +73,7 @@ TEST(ODCode128Writer, EncodeWithFunc3) TEST(ODCode128Writer, EncodeWithFunc4) { - auto toEncode = L"\xf4""123"; + auto toEncode = L"\u00f4123"; // "1" "2" "3" check digit 59 auto expected = QUIET_SPACE + START_CODE_B + FNC4B + "10011100110" + "11001110010" + "11001011100" + "11100011010" + STOP + QUIET_SPACE; @@ -83,7 +83,7 @@ TEST(ODCode128Writer, EncodeWithFunc4) TEST(ODCode128Writer, EncodeWithFncsAndNumberInCodesetA) { - auto toEncode = L"\n" "\xf1" "\xf4" "1" "\n"; + auto toEncode = L"\n\u00f1\u00f41\n"; auto expected = QUIET_SPACE + START_CODE_A + LF + FNC1 + FNC4A + "10011100110" + LF + "10101111000" + STOP + QUIET_SPACE; auto actual = LineMatrixToString(Code128Writer().encode(toEncode, 0, 0)); EXPECT_EQ(actual, expected); @@ -91,7 +91,7 @@ TEST(ODCode128Writer, EncodeWithFncsAndNumberInCodesetA) TEST(ODCode128Writer, RoundtripGS1) { - auto toEncode = L"\xf1" "10958" "\xf1" "17160526"; + auto toEncode = L"\u00f110958\u00f117160526"; auto decResult = Decode(Code128Writer().encode(toEncode, 0, 0)); EXPECT_EQ(decResult.text(TextMode::HRI), "(10)958(17)160526"); @@ -100,7 +100,7 @@ TEST(ODCode128Writer, RoundtripGS1) TEST(ODCode128Writer, RoundtripFNC1) { - auto toEncode = L"1\xf1" "0958" "\xf1" "17160526"; + auto toEncode = L"1\u00f10958\u00f117160526"; auto encResult = Code128Writer().encode(toEncode, 0, 0); auto decResult = Decode(encResult); From 2cd0e80f36806e58f5335350ea173497898de43f Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 6 Dec 2023 09:13:42 +0100 Subject: [PATCH 143/587] RMQRCode: add new format to all wrappers and rename from rMQR --- core/src/BarcodeFormat.cpp | 2 +- core/src/BarcodeFormat.h | 8 ++++---- core/src/MultiFormatReader.cpp | 2 +- core/src/qrcode/QRReader.cpp | 8 ++++---- example/ZXingQtReader.h | 3 ++- test/blackbox/BlackboxTestRunner.cpp | 2 +- .../app/src/main/java/zxingcpp/app/MainActivity.kt | 2 +- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 1 + .../zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt | 2 +- wrappers/c/zxing-c.h | 4 +++- wrappers/ios/Sources/Wrapper/ZXIFormat.h | 2 +- wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm | 4 ++++ wrappers/python/zxing.cpp | 1 + wrappers/wasm/demo_cam_reader.html | 1 + wrappers/wasm/demo_reader.html | 1 + wrappers/winrt/BarcodeReader.cpp | 4 ++++ wrappers/winrt/BarcodeReader.h | 1 + 17 files changed, 32 insertions(+), 16 deletions(-) diff --git a/core/src/BarcodeFormat.cpp b/core/src/BarcodeFormat.cpp index 29a87a4c1e..01f4d3860e 100644 --- a/core/src/BarcodeFormat.cpp +++ b/core/src/BarcodeFormat.cpp @@ -39,7 +39,7 @@ static BarcodeFormatName NAMES[] = { {BarcodeFormat::MicroQRCode, "MicroQRCode"}, {BarcodeFormat::PDF417, "PDF417"}, {BarcodeFormat::QRCode, "QRCode"}, - {BarcodeFormat::rMQR, "rMQR"}, + {BarcodeFormat::RMQRCode, "rMQRCode"}, {BarcodeFormat::UPCA, "UPC-A"}, {BarcodeFormat::UPCE, "UPC-E"}, {BarcodeFormat::LinearCodes, "Linear-Codes"}, diff --git a/core/src/BarcodeFormat.h b/core/src/BarcodeFormat.h index 2036b19d47..72542ed363 100644 --- a/core/src/BarcodeFormat.h +++ b/core/src/BarcodeFormat.h @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors */ @@ -39,13 +39,13 @@ enum class BarcodeFormat UPCA = (1 << 14), ///< UPC-A UPCE = (1 << 15), ///< UPC-E MicroQRCode = (1 << 16), ///< Micro QR Code - rMQR = (1 << 17), ///< Rectangular Micro QR Code + RMQRCode = (1 << 17), ///< Rectangular Micro QR Code LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE, - MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | rMQR, + MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, Any = LinearCodes | MatrixCodes, - _max = rMQR, ///> implementation detail, don't use + _max = RMQRCode, ///> implementation detail, don't use }; ZX_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) diff --git a/core/src/MultiFormatReader.cpp b/core/src/MultiFormatReader.cpp index cd09eb13dc..f8ee215547 100644 --- a/core/src/MultiFormatReader.cpp +++ b/core/src/MultiFormatReader.cpp @@ -28,7 +28,7 @@ MultiFormatReader::MultiFormatReader(const DecodeHints& hints) : _hints(hints) if (formats.testFlags(BarcodeFormat::LinearCodes) && !hints.tryHarder()) _readers.emplace_back(new OneD::Reader(hints)); - if (formats.testFlags(BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode | BarcodeFormat::rMQR)) + if (formats.testFlags(BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode | BarcodeFormat::RMQRCode)) _readers.emplace_back(new QRCode::Reader(hints, true)); if (formats.testFlag(BarcodeFormat::DataMatrix)) _readers.emplace_back(new DataMatrix::Reader(hints, true)); diff --git a/core/src/qrcode/QRReader.cpp b/core/src/qrcode/QRReader.cpp index 1ca6bc362a..8acbef58fe 100644 --- a/core/src/qrcode/QRReader.cpp +++ b/core/src/qrcode/QRReader.cpp @@ -37,7 +37,7 @@ Result Reader::decode(const BinaryBitmap& image) const detectorResult = DetectPureQR(*binImg); if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !detectorResult.isValid()) detectorResult = DetectPureMQR(*binImg); - if (_hints.hasFormat(BarcodeFormat::rMQR) && !detectorResult.isValid()) + if (_hints.hasFormat(BarcodeFormat::RMQRCode) && !detectorResult.isValid()) detectorResult = DetectPureRMQR(*binImg); if (!detectorResult.isValid()) @@ -47,7 +47,7 @@ Result Reader::decode(const BinaryBitmap& image) const auto position = detectorResult.position(); return Result(std::move(decoderResult), std::move(position), - detectorResult.bits().width() != detectorResult.bits().height() ? BarcodeFormat::rMQR : + detectorResult.bits().width() != detectorResult.bits().height() ? BarcodeFormat::RMQRCode : detectorResult.bits().width() < 21 ? BarcodeFormat::MicroQRCode : BarcodeFormat::QRCode); } @@ -131,7 +131,7 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const } } - if (_hints.hasFormat(BarcodeFormat::rMQR) && !(maxSymbols && Size(results) == maxSymbols)) { + if (_hints.hasFormat(BarcodeFormat::RMQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { // TODO proper for (const auto& fp : allFPs) { if (Contains(usedFPs, fp)) @@ -142,7 +142,7 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const auto decoderResult = Decode(detectorResult.bits()); auto position = detectorResult.position(); if (decoderResult.isValid(_hints.returnErrors())) { - results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::rMQR); + results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::RMQRCode); if (maxSymbols && Size(results) == maxSymbols) break; } diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index 9fec57c900..b9380f3930 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -51,9 +51,10 @@ enum class BarcodeFormat UPCA = (1 << 14), ///< UPC-A UPCE = (1 << 15), ///< UPC-E MicroQRCode = (1 << 16), ///< Micro QR Code + RMQRCode = (1 << 17), ///< Rectangular Micro QR Code LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE, - MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode, + MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, }; enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 82eaa05ded..33770e510e 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -611,7 +611,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 9, 0, pure }, }); - runTests("rmqrcode-1", "rMQR", 2, { + runTests("rmqrcode-1", "rMQRCode", 2, { { 1, 2, 0 }, { 1, 2, 90 }, { 1, 2, 180 }, diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 2f337d59c8..2ee4f30b7b 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -239,7 +239,7 @@ class MainActivity : AppCompatActivity() { } } else { readerCpp.options.apply { - formats = if (binding.qrcode.isChecked) setOf(QR_CODE) else setOf() + formats = if (binding.qrcode.isChecked) setOf(QR_CODE, MICRO_QR_CODE, RMQR_CODE) else setOf() tryHarder = binding.tryHarder.isChecked tryRotate = binding.tryRotate.isChecked tryInvert = binding.tryInvert.isChecked diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 31df3df090..7fef5d3955 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -42,6 +42,7 @@ static const char* JavaBarcodeFormatName(BarcodeFormat format) case BarcodeFormat::PDF417: return "PDF_417"; case BarcodeFormat::QRCode: return "QR_CODE"; case BarcodeFormat::MicroQRCode: return "MICRO_QR_CODE"; + case BarcodeFormat::RMQRCode: return "RMQR_CODE"; case BarcodeFormat::DataBar: return "DATA_BAR"; case BarcodeFormat::DataBarExpanded: return "DATA_BAR_EXPANDED"; case BarcodeFormat::UPCA: return "UPC_A"; diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index 5891991e93..fdd24d83bd 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -40,7 +40,7 @@ public class BarcodeReader(public var options: Options = Options()) { // Note that this has to be kept synchronized with native (C++/JNI) side. public enum class Format { NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, - DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E + DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, RMQR_CODE, UPC_A, UPC_E } public enum class ContentType { diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index 2a5ebcb1da..fe4be794eb 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -76,13 +76,15 @@ typedef enum zxing_BarcodeFormat_UPCA = (1 << 14), zxing_BarcodeFormat_UPCE = (1 << 15), zxing_BarcodeFormat_MicroQRCode = (1 << 16), + zxing_BarcodeFormat_RMQRCode = (1 << 17), zxing_BarcodeFormat_LinearCodes = zxing_BarcodeFormat_Codabar | zxing_BarcodeFormat_Code39 | zxing_BarcodeFormat_Code93 | zxing_BarcodeFormat_Code128 | zxing_BarcodeFormat_EAN8 | zxing_BarcodeFormat_EAN13 | zxing_BarcodeFormat_ITF | zxing_BarcodeFormat_DataBar | zxing_BarcodeFormat_DataBarExpanded | zxing_BarcodeFormat_UPCA | zxing_BarcodeFormat_UPCE, zxing_BarcodeFormat_MatrixCodes = zxing_BarcodeFormat_Aztec | zxing_BarcodeFormat_DataMatrix | zxing_BarcodeFormat_MaxiCode - | zxing_BarcodeFormat_PDF417 | zxing_BarcodeFormat_QRCode | zxing_BarcodeFormat_MicroQRCode, + | zxing_BarcodeFormat_PDF417 | zxing_BarcodeFormat_QRCode | zxing_BarcodeFormat_MicroQRCode + | zxing_BarcodeFormat_RMQRCode, zxing_BarcodeFormat_Any = zxing_BarcodeFormat_LinearCodes | zxing_BarcodeFormat_MatrixCodes, zxing_BarcodeFormat_Invalid = -1 /* return value when BarcodeFormatsFromString() throws */ diff --git a/wrappers/ios/Sources/Wrapper/ZXIFormat.h b/wrappers/ios/Sources/Wrapper/ZXIFormat.h index 157ac227cf..0ba9bd8f6d 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIFormat.h +++ b/wrappers/ios/Sources/Wrapper/ZXIFormat.h @@ -8,7 +8,7 @@ typedef NS_ENUM(NSInteger, ZXIFormat) { NONE, AZTEC, CODABAR, CODE_39, CODE_93, CODE_128, DATA_BAR, DATA_BAR_EXPANDED, - DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, UPC_A, UPC_E, + DATA_MATRIX, EAN_8, EAN_13, ITF, MAXICODE, PDF_417, QR_CODE, MICRO_QR_CODE, RMQR_CODE, UPC_A, UPC_E, LINEAR_CODES, MATRIX_CODES, ANY }; diff --git a/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm b/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm index 457c6e551f..487933f4e4 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm +++ b/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm @@ -46,6 +46,8 @@ return ZXing::BarcodeFormat::Aztec; case ZXIFormat::MICRO_QR_CODE: return ZXing::BarcodeFormat::MicroQRCode; + case ZXIFormat::RMQR_CODE: + return ZXing::BarcodeFormat::RMQRCode; case ZXIFormat::NONE: return ZXing::BarcodeFormat::None; } @@ -95,6 +97,8 @@ ZXIFormat ZXIFormatFromBarcodeFormat(ZXing::BarcodeFormat format) { return ZXIFormat::MATRIX_CODES; case ZXing::BarcodeFormat::MicroQRCode: return ZXIFormat::MICRO_QR_CODE; + case ZXing::BarcodeFormat::RMQRCode: + return ZXIFormat::RMQR_CODE; case ZXing::BarcodeFormat::Any: return ZXIFormat::ANY; } diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index 3cc540829a..5006a68374 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -188,6 +188,7 @@ PYBIND11_MODULE(zxingcpp, m) .value("PDF417", BarcodeFormat::PDF417) .value("QRCode", BarcodeFormat::QRCode) .value("MicroQRCode", BarcodeFormat::MicroQRCode) + .value("RMQRCode", BarcodeFormat::RMQRCode) .value("DataBar", BarcodeFormat::DataBar) .value("DataBarExpanded", BarcodeFormat::DataBarExpanded) .value("UPCA", BarcodeFormat::UPCA) diff --git a/wrappers/wasm/demo_cam_reader.html b/wrappers/wasm/demo_cam_reader.html index 9037e9ee65..1040a142df 100644 --- a/wrappers/wasm/demo_cam_reader.html +++ b/wrappers/wasm/demo_cam_reader.html @@ -38,6 +38,7 @@

    zxing-cpp/wasm live demo

    + diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index 86b6af1358..fadc566a3c 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -122,6 +122,7 @@

    zxing-cpp/wasm reader demo

    + diff --git a/wrappers/winrt/BarcodeReader.cpp b/wrappers/winrt/BarcodeReader.cpp index 1fcd7a751c..6963d8cac1 100644 --- a/wrappers/winrt/BarcodeReader.cpp +++ b/wrappers/winrt/BarcodeReader.cpp @@ -92,6 +92,8 @@ BarcodeFormat BarcodeReader::ConvertRuntimeToNative(BarcodeType type) return BarcodeFormat::QRCode; case BarcodeType::MICRO_QR_CODE: return BarcodeFormat::MicroQRCode; + case BarcodeType::RMQR_CODE: + return BarcodeFormat::RMQRCode; case BarcodeType::RSS_14: return BarcodeFormat::DataBar; case BarcodeType::RSS_EXPANDED: @@ -135,6 +137,8 @@ BarcodeType BarcodeReader::ConvertNativeToRuntime(BarcodeFormat format) return BarcodeType::QR_CODE; case BarcodeFormat::MicroQRCode: return BarcodeType::MICRO_QR_CODE; + case BarcodeFormat::RMQRCode: + return BarcodeType::RMQR_CODE; case BarcodeFormat::DataBar: return BarcodeType::RSS_14; case BarcodeFormat::DataBarExpanded: diff --git a/wrappers/winrt/BarcodeReader.h b/wrappers/winrt/BarcodeReader.h index e68383e0e7..591fa10d31 100644 --- a/wrappers/winrt/BarcodeReader.h +++ b/wrappers/winrt/BarcodeReader.h @@ -25,6 +25,7 @@ public enum class BarcodeType : int { PDF_417, QR_CODE, MICRO_QR_CODE, + RMQR_CODE RSS_14, RSS_EXPANDED, UPC_A, From a2a2fcad02b1845d854c8d2c5000c1f174b603ce Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 6 Dec 2023 09:19:55 +0100 Subject: [PATCH 144/587] README: mention new rMQR Code support --- README.md | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index d293580518..717a727a0a 100644 --- a/README.md +++ b/README.md @@ -37,14 +37,15 @@ Thanks a lot for your contribution! |----------------|-------------------|--------------------| | UPC-A | Code 39 | QR Code | | UPC-E | Code 93 | Micro QR Code | -| EAN-8 | Code 128 | Aztec | -| EAN-13 | Codabar | DataMatrix | -| DataBar | DataBar Expanded | PDF417 | -| | ITF | MaxiCode (partial) | +| EAN-8 | Code 128 | rMQR Code | +| EAN-13 | Codabar | Aztec | +| DataBar | DataBar Expanded | DataMatrix | +| | ITF | PDF417 | +| | | MaxiCode (partial) | [Note:] * DataBar used to be called RSS. - * DataBar, MaxiCode and Micro QR Code are not supported for writing. + * DataBar, MaxiCode, Micro QR Code and rMQR Code are not supported for writing. * Building with C++20 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behaviour of the library: it then supports multi-symbol and position independent detection for DataMatrix. This comes at a noticable performace cost. C++20 is enabled by default for the Android, iOS, Python and WASM wrappers. ## Getting Started From 7ac51306890718d5ef45358d4a53e3f75d5fe08a Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 6 Dec 2023 09:31:02 +0100 Subject: [PATCH 145/587] RMQRCode: add a few copyright lines for new code from gitlost --- core/src/qrcode/QRBitMatrixParser.cpp | 1 + core/src/qrcode/QRCodecMode.cpp | 1 + core/src/qrcode/QRDetector.cpp | 1 + core/src/qrcode/QRFormatInformation.cpp | 1 + core/src/qrcode/QRVersion.cpp | 2 ++ 5 files changed, 6 insertions(+) diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index 781f224031..a8cbda7efb 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -1,6 +1,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2023 gitlost */ // SPDX-License-Identifier: Apache-2.0 diff --git a/core/src/qrcode/QRCodecMode.cpp b/core/src/qrcode/QRCodecMode.cpp index 8e94a57505..5020118a87 100644 --- a/core/src/qrcode/QRCodecMode.cpp +++ b/core/src/qrcode/QRCodecMode.cpp @@ -1,6 +1,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2023 gitlost */ // SPDX-License-Identifier: Apache-2.0 diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index a4fbc947dc..8f4812634a 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -2,6 +2,7 @@ * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors * Copyright 2020 Axel Waggershauser +* Copyright 2023 gitlost */ // SPDX-License-Identifier: Apache-2.0 diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index c2f2c57d0e..d2116afb03 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -2,6 +2,7 @@ * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors * Copyright 2023 Axel Waggershauser +* Copyright 2023 gitlost */ // SPDX-License-Identifier: Apache-2.0 diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 96d68124a4..1e1fc4eed6 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -1,6 +1,8 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2023 Axel Waggershauser +* Copyright 2023 gitlost */ // SPDX-License-Identifier: Apache-2.0 From f27106c78c78287bf74d22958c83a09da6986e92 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 7 Dec 2023 09:44:12 +0100 Subject: [PATCH 146/587] QRCode: restructure Format and Version code after rRMQ addition --- core/src/qrcode/QRBitMatrixParser.cpp | 6 +- core/src/qrcode/QRDetector.cpp | 81 +++++++++--------------- core/src/qrcode/QRFormatInformation.cpp | 10 ++- core/src/qrcode/QRFormatInformation.h | 4 +- core/src/qrcode/QRVersion.cpp | 84 ++++++------------------- core/src/qrcode/QRVersion.h | 65 ++++++++++++++++--- test/unit/qrcode/QRVersionTest.cpp | 2 +- 7 files changed, 116 insertions(+), 136 deletions(-) diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index a8cbda7efb..1aea0d9dfa 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -59,7 +59,7 @@ const Version* ReadVersion(const BitMatrix& bitMatrix, Type type) FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix) { - if (Version::HasMicroSize(bitMatrix)) { + if (Version::HasValidSize(bitMatrix, Type::Micro)) { // Read top-left format info bits int formatInfoBits = 0; for (int x = 1; x < 9; x++) @@ -69,7 +69,7 @@ FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix) return FormatInformation::DecodeMQR(formatInfoBits); } - if (Version::HasRMQRSize(bitMatrix)) { + if (Version::HasValidSize(bitMatrix, Type::rMQR)) { // Read top-left format info bits uint32_t formatInfoBits1 = 0; for (int y = 3; y >= 1; y--) @@ -77,6 +77,7 @@ FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix) for (int x = 10; x >= 8; x--) for (int y = 5; y >= 1; y--) AppendBit(formatInfoBits1, getBit(bitMatrix, x, y)); + // Read bottom-right format info bits uint32_t formatInfoBits2 = 0; const int width = bitMatrix.width(); @@ -86,6 +87,7 @@ FormatInformation ReadFormatInformation(const BitMatrix& bitMatrix) for (int x = 6; x <= 8; x++) for (int y = 2; y <= 6; y++) AppendBit(formatInfoBits2, getBit(bitMatrix, width - x, height - y)); + return FormatInformation::DecodeRMQR(formatInfoBits1, formatInfoBits2); } diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 8f4812634a..3e29456602 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -37,8 +37,6 @@ namespace ZXing::QRCode { constexpr auto PATTERN = FixedPattern<5, 7>{1, 1, 3, 1, 1}; -constexpr auto SUBPATTERN_RMQR = FixedPattern<5, 5>{1, 1, 1, 1, 1}; -constexpr auto CORNER_EDGE_RMQR = FixedPattern<2, 4>{3, 1}; constexpr bool E2E = true; PatternView FindPattern(const PatternView& view) @@ -395,7 +393,7 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) log(br, 3); auto mod2Pix = Mod2Pix(dimension, brOffset, {fp.tl, fp.tr, br, fp.bl}); - if( dimension >= Version::DimensionOfVersion(7, false)) { + if( dimension >= Version::SymbolSize(7, Type::Model2).x) { auto version = ReadVersion(image, dimension, mod2Pix); // if the version bits are garbage -> discard the detection @@ -523,8 +521,7 @@ DetectorResult DetectPureQR(const BitMatrix& image) SaveAsPBM(image, "weg.pbm"); #endif - constexpr int MIN_MODULES = Version::DimensionOfVersion(1, false); - constexpr int MAX_MODULES = Version::DimensionOfVersion(40, false); + constexpr int MIN_MODULES = Version::SymbolSize(1, Type::Model2).x; int left, top, width, height; if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) || std::abs(width - height) > 1) @@ -546,7 +543,7 @@ DetectorResult DetectPureQR(const BitMatrix& image) EstimateDimension(image, {tl + fpWidth / 2 * PointF(1, 1), fpWidth}, {tr + fpWidth / 2 * PointF(-1, 1), fpWidth}).dim; float moduleSize = float(width) / dimension; - if (dimension < MIN_MODULES || dimension > MAX_MODULES || + if (!Version::IsValidSize({dimension, dimension}, Type::Model2) || !image.isIn(PointF{left + moduleSize / 2 + (dimension - 1) * moduleSize, top + moduleSize / 2 + (dimension - 1) * moduleSize})) return {}; @@ -568,8 +565,7 @@ DetectorResult DetectPureMQR(const BitMatrix& image) { using Pattern = std::array; - constexpr int MIN_MODULES = Version::DimensionOfVersion(1, true); - constexpr int MAX_MODULES = Version::DimensionOfVersion(4, true); + constexpr int MIN_MODULES = Version::SymbolSize(1, Type::Micro).x; int left, top, width, height; if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) || std::abs(width - height) > 1) @@ -586,7 +582,7 @@ DetectorResult DetectPureMQR(const BitMatrix& image) float moduleSize = float(fpWidth) / 7; int dimension = narrow_cast(std::lround(width / moduleSize)); - if (dimension < MIN_MODULES || dimension > MAX_MODULES || + if (!Version::IsValidSize({dimension, dimension}, Type::Micro) || !image.isIn(PointF{left + moduleSize / 2 + (dimension - 1) * moduleSize, top + moduleSize / 2 + (dimension - 1) * moduleSize})) return {}; @@ -606,22 +602,21 @@ DetectorResult DetectPureMQR(const BitMatrix& image) DetectorResult DetectPureRMQR(const BitMatrix& image) { + constexpr auto SUBPATTERN = FixedPattern<4, 4>{1, 1, 1, 1}; + constexpr auto TIMINGPATTERN = FixedPattern<10, 10>{1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; + using Pattern = std::array; - using SubPattern = std::array; - using CornerEdgePattern = std::array; + using SubPattern = std::array; + using TimingPattern = std::array; #ifdef PRINT_DEBUG SaveAsPBM(image, "weg.pbm"); #endif - constexpr int MIN_MODULES = 7; - constexpr int MIN_MODULES_W = 27; - constexpr int MIN_MODULES_H = 7; - constexpr int MAX_MODULES_W = 139; - constexpr int MAX_MODULES_H = 17; + constexpr int MIN_MODULES = Version::SymbolSize(1, Type::rMQR).y; int left, top, width, height; - if (!image.findBoundingBox(left, top, width, height, MIN_MODULES)) + if (!image.findBoundingBox(left, top, width, height, MIN_MODULES) || height >= width) return {}; int right = left + width - 1; int bottom = top + height - 1; @@ -633,41 +628,27 @@ DetectorResult DetectPureRMQR(const BitMatrix& image) if (!IsPattern(diagonal, PATTERN)) return {}; - // Finder sub pattern - auto subdiagonal = BitMatrixCursorI(image, br, {-1, -1}).readPatternFromBlack(1); - if (Size(subdiagonal) == 5 && subdiagonal[4] > subdiagonal[3]) // Sub pattern has no separator so can run off along the diagonal - subdiagonal[4] = subdiagonal[3]; // Hack it back to previous - if (!IsPattern(subdiagonal, SUBPATTERN_RMQR)) - return {}; - - // Horizontal corner finder patterns (for vertical ones see below) - for (auto [p, d] : {std::pair(tr, PointI{-1, 0}), {bl, {1, 0}}}) { - auto corner = BitMatrixCursorI(image, p, d).readPatternFromBlack(1); - if (!IsPattern(corner, CORNER_EDGE_RMQR)) - return {}; - } - auto fpWidth = Reduce(diagonal); float moduleSize = float(fpWidth) / 7; int dimW = narrow_cast(std::lround(width / moduleSize)); int dimH = narrow_cast(std::lround(height / moduleSize)); - if (dimW == dimH || !(dimW & 1) || !(dimH & 1) || - dimW < MIN_MODULES_W || dimW > MAX_MODULES_W || dimH < MIN_MODULES_H || dimH > MAX_MODULES_H || - !image.isIn(PointF{left + moduleSize / 2 + (dimW - 1) * moduleSize, - top + moduleSize / 2 + (dimH - 1) * moduleSize})) + if (!Version::IsValidSize(PointI{dimW, dimH}, Type::rMQR)) + return {}; + + // Finder sub pattern + auto subdiagonal = BitMatrixCursorI(image, br, {-1, -1}).readPatternFromBlack(1); + if (!IsPattern(subdiagonal, SUBPATTERN)) return {}; - // Vertical corner finder patterns - if (dimH > 7) { // None for R7 - auto corner = BitMatrixCursorI(image, tr, {0, 1}).readPatternFromBlack(1); - if (!IsPattern(corner, CORNER_EDGE_RMQR)) + // Horizontal timing patterns + for (auto [p, d] : {std::pair(tr, PointI{-1, 0}), {bl, {1, 0}}, {tl, {1, 0}}, {br, {-1, 0}}}) { + auto cur = BitMatrixCursorI(image, p, d); + // skip corner / finder / sub pattern edge + cur.stepToEdge(2 + cur.isWhite()); + auto timing = cur.readPattern(); + if (!IsPattern(timing, TIMINGPATTERN)) return {}; - if (dimH > 9) { // No bottom left for R9 - corner = BitMatrixCursorI(image, bl, {0, -1}).readPatternFromBlack(1); - if (!IsPattern(corner, CORNER_EDGE_RMQR)) - return {}; - } } #ifdef PRINT_DEBUG @@ -727,7 +708,7 @@ DetectorResult SampleMQR(const BitMatrix& image, const ConcentricPattern& fp) if (!bestFI.isValid()) return {}; - const int dim = Version::DimensionOfVersion(bestFI.microVersion, true); + const int dim = Version::SymbolSize(bestFI.microVersion, Type::Micro).x; // check that we are in fact not looking at a corner of a non-micro QRCode symbol // we accept at most 1/3rd black pixels in the quite zone (in a QRCode symbol we expect about 1/2). @@ -754,10 +735,10 @@ DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp) static const PointI FORMAT_INFO_EDGE_COORDS[] = {{8, 0}, {9, 0}, {10, 0}, {11, 0}}; static const PointI FORMAT_INFO_COORDS[] = { - { 8, 1}, { 8, 2}, { 8, 3}, { 8, 4}, { 8, 5}, - { 9, 1}, { 9, 2}, { 9, 3}, { 9, 4}, { 9, 5}, - {10, 1}, {10, 2}, {10, 3}, {10, 4}, {10, 5}, - {11, 1}, {11, 2}, {11, 3}, + {11, 3}, {11, 2}, {11, 1}, + {10, 5}, {10, 4}, {10, 3}, {10, 2}, {10, 1}, + { 9, 5}, { 9, 4}, { 9, 3}, { 9, 2}, { 9, 1}, + { 8, 5}, { 8, 4}, { 8, 3}, { 8, 2}, { 8, 1}, }; FormatInformation bestFI; @@ -789,7 +770,7 @@ DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp) if (!bestFI.isValid()) return {}; - const PointI dim = Version::DimensionOfVersionRMQR(bestFI.rMQRVersion + 1); + const PointI dim = Version::SymbolSize(bestFI.microVersion, Type::rMQR); return SampleGrid(image, dim.x, dim.y, bestPT); } diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index d2116afb03..85c88d766b 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -144,18 +144,16 @@ FormatInformation FormatInformation::DecodeMQR(uint32_t formatInfoBits) FormatInformation FormatInformation::DecodeRMQR(uint32_t formatInfoBits1, uint32_t formatInfoBits2) { FormatInformation fi; - auto mirror18Bits = [](uint32_t bits) { return BitHacks::Reverse(bits) >> 14; }; if (formatInfoBits2) - fi = FindBestFormatInfoRMQR({formatInfoBits1, mirror18Bits(formatInfoBits1)}, - {formatInfoBits2, mirror18Bits(formatInfoBits2)}); + fi = FindBestFormatInfoRMQR({formatInfoBits1}, {formatInfoBits2}); else // TODO probably remove if `sampleRMQR()` done properly - fi = FindBestFormatInfoRMQR({formatInfoBits1, mirror18Bits(formatInfoBits1)}, {}); + fi = FindBestFormatInfoRMQR({formatInfoBits1}, {}); // Bit 6 is error correction (M/H), and bits 0-5 version. fi.ecLevel = ECLevelFromBits(((fi.data >> 5) & 1) << 1); // Shift to match QRCode M/H fi.dataMask = 4; // ((y / 2) + (x / 3)) % 2 == 0 - fi.rMQRVersion = fi.data & 0x1F; - fi.isMirrored = fi.bitsIndex > 1; + fi.microVersion = (fi.data & 0x1F) + 1; + fi.isMirrored = false; // TODO: implement mirrored format bit reading return fi; } diff --git a/core/src/qrcode/QRFormatInformation.h b/core/src/qrcode/QRFormatInformation.h index 302472cc10..c73d75663a 100644 --- a/core/src/qrcode/QRFormatInformation.h +++ b/core/src/qrcode/QRFormatInformation.h @@ -30,7 +30,6 @@ class FormatInformation bool isMirrored = false; uint8_t dataMask = 0; uint8_t microVersion = 0; - uint8_t rMQRVersion = 0; ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Invalid; FormatInformation() = default; @@ -47,7 +46,8 @@ class FormatInformation switch (mask) { case FORMAT_INFO_MASK_MODEL1: return Type::Model1; case FORMAT_INFO_MASK_MICRO: return Type::Micro; - case FORMAT_INFO_MASK_RMQR: case FORMAT_INFO_MASK_RMQR_SUB: return Type::rMQR; + case FORMAT_INFO_MASK_RMQR: [[fallthrough]]; + case FORMAT_INFO_MASK_RMQR_SUB: return Type::rMQR; default: return Type::Model2; } } diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 1e1fc4eed6..75d89a3bad 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -509,33 +509,6 @@ const Version* Version::rMQR(int number) return allVersions + number - 1; } -static const PointI dimsVersionRMQR[32] { - {43, 7}, {59, 7}, {77, 7}, {99, 7}, {139, 7}, - {43, 9}, {59, 9}, {77, 9}, {99, 9}, {139, 9}, - {27, 11}, {43, 11}, {59, 11}, {77, 11}, {99, 11}, {139, 11}, - {27, 13}, {43, 13}, {59, 13}, {77, 13}, {99, 13}, {139, 13}, - {43, 15}, {59, 15}, {77, 15}, {99, 15}, {139, 15}, - {43, 17}, {59, 17}, {77, 17}, {99, 17}, {139, 17}, -}; - -static int getVersionRMQR(const BitMatrix& bitMatrix) -{ - const int width = bitMatrix.width(); - const int height = bitMatrix.height(); - if (width != height && (width & 1) && (height & 1) && width >= 27 && width <= 139 && height >= 7 && height <= 17) - for (int i = 0; i < Size(dimsVersionRMQR); i++) - if (width == dimsVersionRMQR[i].x && height == dimsVersionRMQR[i].y) - return i; - return -1; -} - -PointI Version::DimensionOfVersionRMQR(int versionNumber) -{ - if (versionNumber < 1 || versionNumber > Size(dimsVersionRMQR)) - return {0, 0}; - return dimsVersionRMQR[versionNumber - 1]; -} - const Version* Version::Model1(int number) { /** @@ -649,35 +622,14 @@ Version::Version(int versionNumber, const std::array& ecBlocks) _totalCodewords = ecBlocks[0].totalDataCodewords(); } -bool Version::HasMicroSize(const BitMatrix& bitMatrix) -{ - int size = bitMatrix.height(); - return size == bitMatrix.width() && size >= 11 && size <= 17 && (size % 2) == 1; -} - -bool Version::HasRMQRSize(const BitMatrix& bitMatrix) +bool Version::HasValidSize(const BitMatrix& bitMatrix, Type type) { - return getVersionRMQR(bitMatrix) != -1; -} - -bool Version::HasValidSize(const BitMatrix& bitMatrix) -{ - int size = bitMatrix.height(); - if (bitMatrix.width() != size) - return HasRMQRSize(bitMatrix); - return HasMicroSize(bitMatrix) || (size >= 21 && size <= 177 && (size % 4) == 1); + return IsValidSize(PointI{bitMatrix.width(), bitMatrix.height()}, type); } int Version::Number(const BitMatrix& bitMatrix) { - if (bitMatrix.width() != bitMatrix.height()) - return getVersionRMQR(bitMatrix) + 1; - - if (!HasValidSize(bitMatrix)) - return 0; - - bool isMicro = HasMicroSize(bitMatrix); - return (bitMatrix.height() - DimensionOffset(isMicro)) / DimensionStep(isMicro); + return Number(PointI{bitMatrix.width(), bitMatrix.height()}); } const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBitsB) @@ -712,41 +664,41 @@ const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBi BitMatrix Version::buildFunctionPattern() const { if (isRMQR()) { - PointI dimension = Version::DimensionOfVersionRMQR(_versionNumber); - BitMatrix bitMatrix(dimension.x, dimension.y); + PointI size = Version::SymbolSize(versionNumber(), Type::rMQR); + BitMatrix bitMatrix(size.x, size.y); // Set edge timing patterns - bitMatrix.setRegion(0, 0, dimension.x, 1); // Top - bitMatrix.setRegion(0, dimension.y - 1, dimension.x, 1); // Bottom - bitMatrix.setRegion(0, 1, 1, dimension.y - 2); // Left - bitMatrix.setRegion(dimension.x - 1, 1, 1, dimension.y - 2); // Right + bitMatrix.setRegion(0, 0, size.x, 1); // Top + bitMatrix.setRegion(0, size.y - 1, size.x, 1); // Bottom + bitMatrix.setRegion(0, 1, 1, size.y - 2); // Left + bitMatrix.setRegion(size.x - 1, 1, 1, size.y - 2); // Right // Set vertical timing and alignment patterns size_t max = _alignmentPatternCenters.size(); // Same as vertical timing column for (size_t x = 0; x < max; ++x) { int cx = _alignmentPatternCenters[x]; bitMatrix.setRegion(cx - 1, 1, 3, 2); // Top alignment pattern - bitMatrix.setRegion(cx - 1, dimension.y - 3, 3, 2); // Bottom alignment pattern - bitMatrix.setRegion(cx, 3, 1, dimension.y - 6); // Vertical timing pattern + bitMatrix.setRegion(cx - 1, size.y - 3, 3, 2); // Bottom alignment pattern + bitMatrix.setRegion(cx, 3, 1, size.y - 6); // Vertical timing pattern } // Top left finder pattern + separator - bitMatrix.setRegion(1, 1, 8 - 1, 8 - 1 - (dimension.y == 7)); // R7 finder bottom flush with edge + bitMatrix.setRegion(1, 1, 8 - 1, 8 - 1 - (size.y == 7)); // R7 finder bottom flush with edge // Top left format bitMatrix.setRegion(8, 1, 3, 5); bitMatrix.setRegion(11, 1, 1, 3); // Bottom right finder subpattern - bitMatrix.setRegion(dimension.x - 5, dimension.y - 5, 5 - 1, 5 - 1); + bitMatrix.setRegion(size.x - 5, size.y - 5, 5 - 1, 5 - 1); // Bottom right format - bitMatrix.setRegion(dimension.x - 8, dimension.y - 6, 3, 5); - bitMatrix.setRegion(dimension.x - 5, dimension.y - 6, 3, 1); + bitMatrix.setRegion(size.x - 8, size.y - 6, 3, 5); + bitMatrix.setRegion(size.x - 5, size.y - 6, 3, 1); // Top right corner finder - bitMatrix.set(dimension.x - 2, 1); - if (dimension.y > 9) { + bitMatrix.set(size.x - 2, 1); + if (size.y > 9) { // Bottom left corner finder - bitMatrix.set(1, dimension.y - 2); + bitMatrix.set(1, size.y - 2); } return bitMatrix; diff --git a/core/src/qrcode/QRVersion.h b/core/src/qrcode/QRVersion.h index cbc8bb1fcb..f2229465c8 100644 --- a/core/src/qrcode/QRVersion.h +++ b/core/src/qrcode/QRVersion.h @@ -10,6 +10,7 @@ #include "Point.h" #include "QRECB.h" #include "QRErrorCorrectionLevel.h" +#include "ZXAlgorithms.h" #include #include @@ -21,6 +22,17 @@ class BitMatrix; namespace QRCode { +// clang-format off +constexpr std::array RMQR_SIZES { + PointI{43, 7}, {59, 7}, {77, 7}, {99, 7}, {139, 7}, + {43, 9}, {59, 9}, {77, 9}, {99, 9}, {139, 9}, + {27, 11}, {43, 11}, {59, 11}, {77, 11}, {99, 11}, {139, 11}, + {27, 13}, {43, 13}, {59, 13}, {77, 13}, {99, 13}, {139, 13}, + {43, 15}, {59, 15}, {77, 15}, {99, 15}, {139, 15}, + {43, 17}, {59, 17}, {77, 17}, {99, 17}, {139, 17}, +}; +// clang-format on + /** * See ISO 18004:2006 Annex D */ @@ -39,23 +51,58 @@ class Version int totalCodewords() const { return _totalCodewords; } - int dimension() const { return DimensionOfVersion(_versionNumber, isMicro()); } + int dimension() const { return SymbolSize(versionNumber(), isMicro() ? Type::Micro : Type::Model2).x; } const ECBlocks& ecBlocksForLevel(ErrorCorrectionLevel ecLevel) const { return _ecBlocks[(int)ecLevel]; } BitMatrix buildFunctionPattern() const; - static constexpr int DimensionStep(bool isMicro) { return std::array{4, 2}[isMicro]; } - static constexpr int DimensionOffset(bool isMicro) { return std::array{17, 9}[isMicro]; } - static constexpr int DimensionOfVersion(int version, bool isMicro) + static constexpr PointI SymbolSize(int version, Type type) + { + auto square = [](int s) { return PointI(s, s); }; + auto valid = [](int v, int max) { return v >= 1 && v <= max; }; + + switch (type) { + case Type::Model1: return valid(version, 32) ? square(17 + 4 * version) : PointI{}; + case Type::Model2: return valid(version, 40) ? square(17 + 4 * version) : PointI{}; + case Type::Micro: return valid(version, 4) ? square(9 + 2 * version) : PointI{}; + case Type::rMQR: return valid(version, 32) ? RMQR_SIZES[version - 1] : PointI{}; + } + + return {}; // silence warning + } + + static constexpr bool IsValidSize(PointI size, Type type) + { + switch (type) { + case Type::Model1: return size.x == size.y && size.x >= 21 && size.x <= 145 && (size.x % 4 == 1); + case Type::Model2: return size.x == size.y && size.x >= 21 && size.x <= 177 && (size.x % 4 == 1); + case Type::Micro: return size.x == size.y && size.x >= 11 && size.x <= 17 && (size.x % 2 == 1); + case Type::rMQR: + return size.x != size.y && size.x & 1 && size.y & 1 && size.x >= 27 && size.x <= 139 && size.y >= 7 && size.y <= 17 + && IndexOf(RMQR_SIZES, size) != -1; + } + return {}; // silence warning + } + static bool HasValidSize(const BitMatrix& bitMatrix, Type type); + + static bool HasValidSize(const BitMatrix& matrix) + { + return HasValidSize(matrix, Type::Model1) || HasValidSize(matrix, Type::Model2) || HasValidSize(matrix, Type::Micro) + || HasValidSize(matrix, Type::rMQR); + } + + static constexpr int Number(PointI size) { - return DimensionOffset(isMicro) + DimensionStep(isMicro) * version; + if (size.x != size.y) + return IndexOf(RMQR_SIZES, size) + 1; + if (IsValidSize(size, Type::Model2)) + return (size.x - 17) / 4; + if (IsValidSize(size, Type::Micro)) + return (size.x - 9) / 2; + return 0; } - static PointI DimensionOfVersionRMQR(int versionNumber); - static bool HasMicroSize(const BitMatrix& bitMatrix); - static bool HasRMQRSize(const BitMatrix& bitMatrix); - static bool HasValidSize(const BitMatrix& bitMatrix); static int Number(const BitMatrix& bitMatrix); static const Version* DecodeVersionInformation(int versionBitsA, int versionBitsB = 0); diff --git a/test/unit/qrcode/QRVersionTest.cpp b/test/unit/qrcode/QRVersionTest.cpp index 04face2d10..8b7851ec0d 100644 --- a/test/unit/qrcode/QRVersionTest.cpp +++ b/test/unit/qrcode/QRVersionTest.cpp @@ -105,7 +105,7 @@ namespace { void CheckRMQRVersion(const Version* version, int number) { ASSERT_NE(version, nullptr); EXPECT_EQ(number, version->versionNumber()); - EXPECT_EQ(Version::DimensionOfVersionRMQR(number).x == 27, version->alignmentPatternCenters().empty()); + EXPECT_EQ(Version::SymbolSize(number, Type::rMQR).x == 27, version->alignmentPatternCenters().empty()); } } From c581d8b0bbbe5862f72eda07ecb50fed5cc6bb83 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 7 Dec 2023 10:56:16 +0100 Subject: [PATCH 147/587] rMQR: improve pure detection of large symbols Better moduleSize estimation via larger sample size. --- core/src/qrcode/QRDetector.cpp | 18 ++++++++++-------- test/blackbox/BlackboxTestRunner.cpp | 4 ++-- test/samples/rmqrcode-1/R17x139.png | Bin 0 -> 1113 bytes test/samples/rmqrcode-1/R17x139.txt | 1 + 4 files changed, 13 insertions(+), 10 deletions(-) create mode 100644 test/samples/rmqrcode-1/R17x139.png create mode 100644 test/samples/rmqrcode-1/R17x139.txt diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 3e29456602..eefd32f856 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -628,19 +628,13 @@ DetectorResult DetectPureRMQR(const BitMatrix& image) if (!IsPattern(diagonal, PATTERN)) return {}; - auto fpWidth = Reduce(diagonal); - float moduleSize = float(fpWidth) / 7; - int dimW = narrow_cast(std::lround(width / moduleSize)); - int dimH = narrow_cast(std::lround(height / moduleSize)); - - if (!Version::IsValidSize(PointI{dimW, dimH}, Type::rMQR)) - return {}; - // Finder sub pattern auto subdiagonal = BitMatrixCursorI(image, br, {-1, -1}).readPatternFromBlack(1); if (!IsPattern(subdiagonal, SUBPATTERN)) return {}; + float moduleSize = Reduce(diagonal) + Reduce(subdiagonal); + // Horizontal timing patterns for (auto [p, d] : {std::pair(tr, PointI{-1, 0}), {bl, {1, 0}}, {tl, {1, 0}}, {br, {-1, 0}}}) { auto cur = BitMatrixCursorI(image, p, d); @@ -649,8 +643,16 @@ DetectorResult DetectPureRMQR(const BitMatrix& image) auto timing = cur.readPattern(); if (!IsPattern(timing, TIMINGPATTERN)) return {}; + moduleSize += Reduce(timing); } + moduleSize /= 7 + 4 + 4 * 10; // fp + sub + 4 x timing + int dimW = narrow_cast(std::lround(width / moduleSize)); + int dimH = narrow_cast(std::lround(height / moduleSize)); + + if (!Version::IsValidSize(PointI{dimW, dimH}, Type::rMQR)) + return {}; + #ifdef PRINT_DEBUG LogMatrix log; LogMatrixWriter lmw(log, image, 5, "grid2.pnm"); diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 33770e510e..0ca23c1e76 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -611,12 +611,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 9, 0, pure }, }); - runTests("rmqrcode-1", "rMQRCode", 2, { + runTests("rmqrcode-1", "rMQRCode", 3, { { 1, 2, 0 }, { 1, 2, 90 }, { 1, 2, 180 }, { 1, 2, 270 }, - { 1, 1, pure }, + { 2, 2, pure }, }); runTests("pdf417-1", "PDF417", 17, { diff --git a/test/samples/rmqrcode-1/R17x139.png b/test/samples/rmqrcode-1/R17x139.png new file mode 100644 index 0000000000000000000000000000000000000000..19d17e352d281b395125499f8cf3b857e8154553 GIT binary patch literal 1113 zcmV-f1g86mP)HzuAx7|-I<6Y&ur@hU;`m@gM=?fE=T^>i}EXsxg7 zx<+o>w(lvWob$uOB6&S+99tTDTF=`yraLtYWrY&SNXk{HDAb;^P;;YhX)T1hr^Wc% z)c67_1GDyU#g$>NQ%VFgmX_mHjA0xHCeje|ym(bwUeoJTjbVOhc_d@O@P4QOSXZFx zIQ&VNj01WNDt=18p{3Cn0LS8WZ~++hG|E4X2h5tqs&J#0V_uF69LsrPH6%@6TNz2# zCrXpvMGD2eo(wy+@USXw+uNqJx3E^eG-ApIpvSTXr~rw927o?*$G+4HBRVk8j{wxC zgeS_yVux){Q!?3PO|Q9nsVBy%8k$VqDBXVnnSex-p=G0}U>)n*rXXSEdI3!CW$7Ul z8U^IT$l@&2r(LKFKwpm;UsI}JBGbv_44voxHO4UQtS5kZ(Ui{Mq*+uTNt#e<6^iM5 z)a9kr_#!HQ<5yjNOzC6R(F+xB+Zp;$0WM5ZPXhwl&(5n@rZ8ekq{*0PExn@HX>XP> z$aug)9xV;Pol-wdoqiQHJ*O6-TpD4%pw?K<)AkLDy-?StRsfh zyMU$#6w45XeP=aU(!6vAi-GBFQ~H}!`3XuwlSLI5!~zYqCa(uv48XM46Oh@Y#b0`7 z;GU4O$&Ei2sk+-L-UG#qxl_TldRD`1*dUVLwiWKR@yJ-3HD6#cY-vg$a|V`U@h(`m zZRtHwChPVYswi%0#7T5$uf3DGVyMqL0~|~9dpmr(k{Js|ya!68>&i9uJ8?4AkXrM; zoyj^RW&@eFCF}444YWhAE@09rm3i|4?NU6@}5fD5J zb)awOp$>hLA@rfY+|Do=3;W&KMOHkc?EjNmI>+LQ3!{w4iKbDt!7*=Q_&)cW-J(gfqtoR)IkOJP@-2Kr~7pOmOwaQxiS*>J$Oqdct zL)|EET`U;tbem+P+a4BfW5tL>1FhL7nebjH=6U`Xhfe0%p%ZzhUT_rlT2xDV@v$M9 fJ`P|%;#J}=iPu^Vu(t@k00000NkvXXu0mjfK}-rG literal 0 HcmV?d00001 diff --git a/test/samples/rmqrcode-1/R17x139.txt b/test/samples/rmqrcode-1/R17x139.txt new file mode 100644 index 0000000000..ea78e300a4 --- /dev/null +++ b/test/samples/rmqrcode-1/R17x139.txt @@ -0,0 +1 @@ +3.1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679821480865132823066470938446095505822317253594081 \ No newline at end of file From 677fc21c7d6f9e43e6e1852ab801d16e27d4c563 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 7 Dec 2023 13:52:46 +0100 Subject: [PATCH 148/587] rMQR: improve detection rate by using finder sub pattern This can be considered a WIP. --- core/src/qrcode/QRDetector.cpp | 42 +++++++++++++++++++++++++++- test/blackbox/BlackboxTestRunner.cpp | 12 ++++---- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index eefd32f856..7912962dee 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -728,7 +728,6 @@ DetectorResult SampleMQR(const BitMatrix& image, const ConcentricPattern& fp) DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp) { - // TODO proper auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 2); if (!fpQuad) return {}; @@ -774,6 +773,47 @@ DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp) const PointI dim = Version::SymbolSize(bestFI.microVersion, Type::rMQR); + // TODO: this is a WIP + auto intersectQuads = [](QuadrilateralF& a, QuadrilateralF& b) { + auto tl = Center(a); + auto br = Center(b); + // rotate points such that topLeft of a is furthest away from b and topLeft of b is closest to a + auto dist2B = [c = br](auto a, auto b) { return distance(a, c) < distance(b, c); }; + auto offsetA = std::max_element(a.begin(), a.end(), dist2B) - a.begin(); + auto dist2A = [c = tl](auto a, auto b) { return distance(a, c) < distance(b, c); }; + auto offsetB = std::min_element(b.begin(), b.end(), dist2A) - b.begin(); + + a = RotatedCorners(a, offsetA); + b = RotatedCorners(b, offsetB); + + auto tr = (intersect(RegressionLine(a[0], a[1]), RegressionLine(b[1], b[2])) + + intersect(RegressionLine(a[3], a[2]), RegressionLine(b[0], b[3]))) + / 2; + auto bl = (intersect(RegressionLine(a[0], a[3]), RegressionLine(b[2], b[3])) + + intersect(RegressionLine(a[1], a[2]), RegressionLine(b[0], b[1]))) + / 2; + + log(tr, 2); + log(bl, 2); + + return QuadrilateralF{tl, tr, br, bl}; + }; + + if (auto found = LocateAlignmentPattern(image, fp.size / 7, bestPT(dim - PointF(3, 3)))) { + log(*found, 2); + if (auto spQuad = FindConcentricPatternCorners(image, *found, fp.size / 2, 1)) { + auto dest = intersectQuads(*fpQuad, *spQuad); + if (dim.y <= 9) { + bestPT = PerspectiveTransform({{6.5, 0.5}, {dim.x - 1.5, dim.y - 3.5}, {dim.x - 1.5, dim.y - 1.5}, {6.5, 6.5}}, + {fpQuad->topRight(), spQuad->topRight(), spQuad->bottomRight(), fpQuad->bottomRight()}); + } else { + dest[0] = fp; + dest[2] = *found; + bestPT = PerspectiveTransform({{3.5, 3.5}, {dim.x - 2.5, 3.5}, {dim.x - 2.5, dim.y - 2.5}, {3.5, dim.y - 2.5}}, dest); + } + } + } + return SampleGrid(image, dim.x, dim.y, bestPT); } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 0ca23c1e76..1617f14ede 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -228,7 +228,9 @@ static void doRunTests(const fs::path& directory, std::string_view format, int t if (tc.name.empty()) break; auto startTime = std::chrono::steady_clock::now(); - hints.setTryDownscale(false); + hints.setTryDownscale(tc.name == "slow_"); + hints.setDownscaleFactor(2); + hints.setDownscaleThreshold(180); hints.setTryHarder(tc.name == "slow"); hints.setTryRotate(tc.name == "slow"); hints.setTryInvert(tc.name == "slow"); @@ -612,10 +614,10 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("rmqrcode-1", "rMQRCode", 3, { - { 1, 2, 0 }, - { 1, 2, 90 }, - { 1, 2, 180 }, - { 1, 2, 270 }, + { 2, 3, 0 }, + { 2, 3, 90 }, + { 2, 3, 180 }, + { 2, 3, 270 }, { 2, 2, pure }, }); From 55573f771096e5022a2ef188e14d9cbd911e985a Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 7 Dec 2023 21:16:08 +0100 Subject: [PATCH 149/587] android: update a few dependencies and make library names more consistent --- wrappers/android/app/build.gradle.kts | 4 ++-- wrappers/android/gradle/libs.versions.toml | 18 +++++++++--------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/wrappers/android/app/build.gradle.kts b/wrappers/android/app/build.gradle.kts index ef30f033c2..496145bf74 100644 --- a/wrappers/android/app/build.gradle.kts +++ b/wrappers/android/app/build.gradle.kts @@ -43,8 +43,8 @@ dependencies { implementation(libs.androidx.camera.camera2) implementation(libs.androidx.camera.lifecycle) implementation(libs.androidx.camera.view) - implementation(libs.google.material) + implementation(libs.android.material) // Java "upstream" version of zxing (to compare performance) - implementation(libs.google.zxing) + implementation(libs.zxing.core) } diff --git a/wrappers/android/gradle/libs.versions.toml b/wrappers/android/gradle/libs.versions.toml index 6d017cf5c2..2f72e440dc 100644 --- a/wrappers/android/gradle/libs.versions.toml +++ b/wrappers/android/gradle/libs.versions.toml @@ -1,17 +1,17 @@ [versions] androidCoreDesugaring = "2.0.3" -androidGradlePlugin = "8.1.1" +androidGradlePlugin = "8.1.4" androidCompileSdk = "34" androidMinSdk = "21" androidTargetSdk = "33" -androidx-activity = "1.7.2" +androidx-activity = "1.8.1" androidx-appcompat = "1.6.1" -androidx-camera = "1.3.0-rc01" +androidx-camera = "1.3.0" androidx-core = "1.12.0" -constraintLayout = "2.1.4" -google-zxing = "3.5.0" +androidx-constraintLayout = "2.1.4" +android-material = "1.11.0-rc01" kotlin = "1.9.10" -materialDesign = "1.11.0-alpha02" +zxing-core = "3.5.2" [libraries] androidx-activityktx = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" } @@ -20,10 +20,10 @@ androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = " androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" } androidx-camera-lifecycle = { module = "androidx.camera:camera-lifecycle", version.ref = "androidx-camera" } androidx-camera-view = { module = "androidx.camera:camera-view", version.ref = "androidx-camera" } -androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "constraintLayout" } +androidx-constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version.ref = "androidx-constraintLayout" } androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" } -google-material = { module = "com.google.android.material:material", version.ref = "materialDesign" } -google-zxing = { module = "com.google.zxing:core", version.ref = "google-zxing" } +android-material = { module = "com.google.android.material:material", version.ref = "android-material" } +zxing-core = { module = "com.google.zxing:core", version.ref = "zxing-core" } [plugins] android-application = { id = "com.android.application", version.ref = "androidGradlePlugin" } From 45e2070955dd60baeffb58be38803e9cfd7d5d63 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 7 Dec 2023 21:34:28 +0100 Subject: [PATCH 150/587] android: replace `Result.time` with `BarcodeReader.lastReadTime` Does not clutter Result struct and also works when no barcode was found. --- .../src/main/java/zxingcpp/app/MainActivity.kt | 3 +-- .../android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 17 +++++++++-------- .../src/main/java/zxingcpp/BarcodeReader.kt | 3 ++- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 2ee4f30b7b..2184b1be0d 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -250,8 +250,6 @@ class MainActivity : AppCompatActivity() { resultText = try { image.use { readerCpp.read(it) - }.apply { - runtime2 += firstOrNull()?.time ?: 0 }.joinToString("\n") { result -> result.position.let { resultPoints.add(listOf( @@ -274,6 +272,7 @@ class MainActivity : AppCompatActivity() { } runtimes += System.currentTimeMillis() - startTime + runtime2 += readerCpp.lastReadTime var infoText: String? = null if (++frameCounter == 15) { diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 7fef5d3955..e9f7bd0e75 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -194,7 +194,7 @@ static jobject NewError(JNIEnv* env, const Error& error) return env->NewObject(cls, midInit, NewEnum(env, JavaErrorTypeName(error.type()), "ErrorType"), C2JString(env, error.msg())); } -static jobject NewResult(JNIEnv* env, const Result& result, int time) +static jobject NewResult(JNIEnv* env, const Result& result) { jclass cls = env->FindClass(PACKAGE "Result"); jmethodID midInit = env->GetMethodID( @@ -213,7 +213,7 @@ static jobject NewResult(JNIEnv* env, const Result& result, int time) "Z" "I" "L" PACKAGE "Error;" - "I)V"); + ")V"); bool valid = result.isValid(); return env->NewObject(cls, midInit, NewEnum(env, JavaBarcodeFormatName(result.format()), "Format"), @@ -229,12 +229,11 @@ static jobject NewResult(JNIEnv* env, const Result& result, int time) valid ? C2JString(env, result.sequenceId()) : nullptr, result.readerInit(), result.lineCount(), - result.error() ? NewError(env, result.error()) : nullptr, - time + result.error() ? NewError(env, result.error()) : nullptr ); } -static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) +static jobject Read(JNIEnv *env, jobject thiz, ImageView image, const DecodeHints& hints) { try { auto startTime = std::chrono::high_resolution_clock::now(); @@ -243,12 +242,14 @@ static jobject Read(JNIEnv *env, ImageView image, const DecodeHints& hints) // LOGD("time: %4d ms\n", (int)std::chrono::duration_cast(duration).count()); auto time = std::chrono::duration_cast(duration).count(); + env->SetIntField(thiz, env->GetFieldID(env->GetObjectClass(thiz), "lastReadTime", "I"), time); + jclass clsList = env->FindClass("java/util/ArrayList"); jobject objList = env->NewObject(clsList, env->GetMethodID(clsList, "", "()V")); if (!results.empty()) { jmethodID midAdd = env->GetMethodID(clsList, "add", "(Ljava/lang/Object;)Z"); for (const auto& result: results) - env->CallBooleanMethod(objList, midAdd, NewResult(env, result, time)); + env->CallBooleanMethod(objList, midAdd, NewResult(env, result)); } return objList; } catch (const std::exception& e) { @@ -329,7 +330,7 @@ Java_zxingcpp_BarcodeReader_readYBuffer( ImageView{pixels + top * rowStride + left, width, height, ImageFormat::Lum, rowStride} .rotated(rotation); - return Read(env, image, CreateDecodeHints(env, options)); + return Read(env, thiz, image, CreateDecodeHints(env, options)); } struct LockedPixels @@ -376,5 +377,5 @@ Java_zxingcpp_BarcodeReader_readBitmap( .cropped(left, top, width, height) .rotated(rotation); - return Read(env, image, CreateDecodeHints(env, options)); + return Read(env, thiz, image, CreateDecodeHints(env, options)); } diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index fdd24d83bd..b994d9c342 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -112,9 +112,10 @@ public class BarcodeReader(public var options: Options = Options()) { val readerInit: Boolean, val lineCount: Int, val error: Error?, - val time: Int // for development/debug purposes only ) + public val lastReadTime : Int = 0 // runtime of last read call in ms (for debugging purposes only) + public fun read(image: ImageProxy): List { check(image.format in supportedYUVFormats) { "Invalid image format: ${image.format}. Must be one of: $supportedYUVFormats" From a500449894265f2155301abc363a65164cfbde9b Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 7 Dec 2023 21:59:53 +0100 Subject: [PATCH 151/587] ios: rename DecodeHints -> ReaderOptions See https://github.com/zxing-cpp/zxing-cpp/discussions/678 --- .../Sources/Wrapper/Reader/ZXIBarcodeReader.h | 6 ++-- .../Wrapper/Reader/ZXIBarcodeReader.mm | 29 +++++++++---------- .../{ZXIDecodeHints.h => ZXIReaderOptions.h} | 12 ++++---- ...{ZXIDecodeHints.mm => ZXIReaderOptions.mm} | 12 ++++---- wrappers/ios/Sources/Wrapper/UmbrellaHeader.h | 2 +- zxing-cpp.podspec | 2 +- 6 files changed, 31 insertions(+), 32 deletions(-) rename wrappers/ios/Sources/Wrapper/Reader/{ZXIDecodeHints.h => ZXIReaderOptions.h} (84%) rename wrappers/ios/Sources/Wrapper/Reader/{ZXIDecodeHints.mm => ZXIReaderOptions.mm} (92%) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h index f72e6691ed..153abcdebb 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h @@ -6,14 +6,14 @@ #import #import #import "ZXIResult.h" -#import "ZXIDecodeHints.h" +#import "ZXIReaderOptions.h" NS_ASSUME_NONNULL_BEGIN @interface ZXIBarcodeReader : NSObject -@property(nonatomic, strong) ZXIDecodeHints *hints; +@property(nonatomic, strong) ZXIReaderOptions *options; --(instancetype)initWithHints:(ZXIDecodeHints*)options; +-(instancetype)initWithOptions:(ZXIReaderOptions*)options; -(nullable NSArray *)readCIImage:(nonnull CIImage *)image error:(NSError *__autoreleasing _Nullable *)error; diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm index 9a964be3df..e381e36ae5 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm @@ -41,13 +41,13 @@ @interface ZXIBarcodeReader() @implementation ZXIBarcodeReader - (instancetype)init { - return [self initWithHints: [[ZXIDecodeHints alloc] init]]; + return [self initWithOptions: [[ZXIReaderOptions alloc] init]]; } -- (instancetype)initWithHints:(ZXIDecodeHints*)hints{ +- (instancetype)initWithOptions:(ZXIReaderOptions*)options{ self = [super init]; self.ciContext = [[CIContext alloc] initWithOptions:@{kCIContextWorkingColorSpace: [NSNull new]}]; - self.hints = hints; + self.options = options; return self; } @@ -117,29 +117,28 @@ - (instancetype)initWithHints:(ZXIDecodeHints*)hints{ return [self readImageView:imageView error:error]; } -+ (DecodeHints)DecodeHintsFromZXIOptions:(ZXIDecodeHints*)hints { ++ (DecodeHints)DecodeHintsFromZXIReaderOptions:(ZXIReaderOptions*)options { BarcodeFormats formats; - for(NSNumber* flag in hints.formats) { + for(NSNumber* flag in options.formats) { formats.setFlag(BarcodeFormatFromZXIFormat((ZXIFormat)flag.integerValue)); } DecodeHints resultingHints = DecodeHints() - .setTryRotate(hints.tryRotate) - .setTryHarder(hints.tryHarder) - .setTryInvert(hints.tryInvert) - .setTryDownscale(hints.tryDownscale) - .setTryCode39ExtendedMode(hints.tryCode39ExtendedMode) - .setValidateCode39CheckSum(hints.validateCode39CheckSum) - .setValidateITFCheckSum(hints.validateITFCheckSum) - .setFormats(formats) - .setMaxNumberOfSymbols(hints.maxNumberOfSymbols); + .setTryRotate(options.tryRotate) + .setTryHarder(options.tryHarder) + .setTryInvert(options.tryInvert) + .setTryDownscale(options.tryDownscale) + .setTryCode39ExtendedMode(options.tryCode39ExtendedMode) + .setValidateCode39CheckSum(options.validateCode39CheckSum) + .setValidateITFCheckSum(options.validateITFCheckSum) + .setMaxNumberOfSymbols(options.maxNumberOfSymbols); return resultingHints; } - (NSArray *)readImageView:(ImageView)imageView error:(NSError *__autoreleasing _Nullable *)error { try { - Results results = ReadBarcodes(imageView, [ZXIBarcodeReader DecodeHintsFromZXIOptions:self.hints]); + Results results = ReadBarcodes(imageView, [ZXIBarcodeReader DecodeHintsFromZXIReaderOptions:self.options]); NSMutableArray* zxiResults = [NSMutableArray array]; for (auto result: results) { [zxiResults addObject: diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h similarity index 84% rename from wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.h rename to wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h index 8000c73f58..cae964c2ff 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h @@ -6,7 +6,9 @@ NS_ASSUME_NONNULL_BEGIN -@interface ZXIDecodeHints : NSObject +@interface ZXIReaderOptions : NSObject +/// An array of ZXIFormat +@property(nonatomic, strong) NSArray *formats; @property(nonatomic) BOOL tryHarder; @property(nonatomic) BOOL tryRotate; @property(nonatomic) BOOL tryInvert; @@ -17,10 +19,9 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) uint8_t downscaleFactor; @property(nonatomic) uint16_t downscaleThreshold; @property(nonatomic) NSInteger maxNumberOfSymbols; -/// An array of ZXIFormat -@property(nonatomic, strong) NSArray *formats; -- (instancetype)initWithTryHarder:(BOOL)tryHarder +- (instancetype)initWithFormats:(NSArray*)formats + tryHarder:(BOOL)tryHarder tryRotate:(BOOL)tryRotate tryInvert:(BOOL)tryInvert tryDownscale:(BOOL)tryDownscale @@ -29,8 +30,7 @@ NS_ASSUME_NONNULL_BEGIN validateITFCheckSum:(BOOL)validateITFCheckSum downscaleFactor:(uint8_t)downscaleFactor downscaleThreshold:(uint16_t)downscaleThreshold - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols - formats:(NSArray*)formats; + maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols; @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm similarity index 92% rename from wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm rename to wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm index fd7c55a8ed..801fe6ed7d 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm @@ -2,14 +2,14 @@ // // SPDX-License-Identifier: Apache-2.0 -#import "ZXIDecodeHints.h" +#import "ZXIReaderOptions.h" #import "DecodeHints.h" -@interface ZXIDecodeHints() +@interface ZXIReaderOptions() @property(nonatomic) ZXing::DecodeHints zxingHints; @end -@implementation ZXIDecodeHints +@implementation ZXIReaderOptions -(instancetype)init { self = [super init]; @@ -17,7 +17,8 @@ -(instancetype)init { return self; } -- (instancetype)initWithTryHarder:(BOOL)tryHarder +- (instancetype)initWithFormats:(NSArray*)formats + tryHarder:(BOOL)tryHarder tryRotate:(BOOL)tryRotate tryInvert:(BOOL)tryInvert tryDownscale:(BOOL)tryDownscale @@ -26,8 +27,7 @@ - (instancetype)initWithTryHarder:(BOOL)tryHarder validateITFCheckSum:(BOOL)validateITFCheckSum downscaleFactor:(uint8_t)downscaleFactor downscaleThreshold:(uint16_t)downscaleThreshold - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols - formats:(NSArray*)formats { + maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { self = [super init]; self.zxingHints = ZXing::DecodeHints(); self.tryHarder = tryHarder; diff --git a/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h b/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h index b14dc9b5df..187df227d8 100644 --- a/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h +++ b/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h @@ -10,7 +10,7 @@ #import "Reader/ZXIPosition.h" #import "Reader/ZXIPoint.h" #import "Reader/ZXIGTIN.h" -#import "Reader/ZXIDecodeHints.h" +#import "Reader/ZXIReaderOptions.h" #import "Writer/ZXIEncodeHints.h" #import "Writer/ZXIBarcodeWriter.h" #import "ZXIErrors.h" diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec index 02997d19c0..c1bf74ad3e 100644 --- a/zxing-cpp.podspec +++ b/zxing-cpp.podspec @@ -31,7 +31,7 @@ Pod::Spec.new do |s| ss.dependency 'zxing-cpp/Core' ss.frameworks = 'CoreGraphics', 'CoreImage', 'CoreVideo' ss.source_files = 'wrappers/ios/Sources/Wrapper/**/*.{h,m,mm}' - ss.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIGTIN,ZXIDecodeHints}.h', + ss.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIGTIN,ZXIReaderOptions}.h', 'wrappers/ios/Sources/Wrapper/Writer/{ZXIBarcodeWriter,ZXIEncodeHints}.h', 'wrappers/ios/Sources/Wrapper/{ZXIErrors,ZXIFormat}.h' ss.exclude_files = 'wrappers/ios/Sources/Wrapper/UmbrellaHeader.h' From cc05f373ff9d523f7d2378be856d083bee9e0a34 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 7 Dec 2023 22:33:09 +0100 Subject: [PATCH 152/587] ios: rename ZXIEncodeHints to ZXIWriterOptions Also make the Writer interface consistent with the Reader by making options a property of the writer instead. --- wrappers/ios/Sources/Wrapper/UmbrellaHeader.h | 2 +- .../Sources/Wrapper/Writer/ZXIBarcodeWriter.h | 7 ++-- .../Wrapper/Writer/ZXIBarcodeWriter.mm | 34 ++++++++++++------- .../{ZXIEncodeHints.h => ZXIWriterOptions.h} | 2 +- ...{ZXIEncodeHints.mm => ZXIWriterOptions.mm} | 4 +-- .../ios/demo/demo/WriteViewController.swift | 4 +-- zxing-cpp.podspec | 2 +- 7 files changed, 32 insertions(+), 23 deletions(-) rename wrappers/ios/Sources/Wrapper/Writer/{ZXIEncodeHints.h => ZXIWriterOptions.h} (97%) rename wrappers/ios/Sources/Wrapper/Writer/{ZXIEncodeHints.mm => ZXIWriterOptions.mm} (96%) diff --git a/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h b/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h index 187df227d8..7306d381dc 100644 --- a/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h +++ b/wrappers/ios/Sources/Wrapper/UmbrellaHeader.h @@ -11,7 +11,7 @@ #import "Reader/ZXIPoint.h" #import "Reader/ZXIGTIN.h" #import "Reader/ZXIReaderOptions.h" -#import "Writer/ZXIEncodeHints.h" +#import "Writer/ZXIWriterOptions.h" #import "Writer/ZXIBarcodeWriter.h" #import "ZXIErrors.h" #import "ZXIFormat.h" diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h index 8d737e5264..41b6515438 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.h @@ -3,18 +3,19 @@ // SPDX-License-Identifier: Apache-2.0 #import -#import "ZXIEncodeHints.h" +#import "ZXIWriterOptions.h" NS_ASSUME_NONNULL_BEGIN @interface ZXIBarcodeWriter : NSObject +@property(nonatomic, strong) ZXIWriterOptions *options; + +-(instancetype)initWithOptions:(ZXIWriterOptions*)options; -(nullable CGImageRef)writeString:(NSString *)contents - hints:(ZXIEncodeHints *)hints error:(NSError *__autoreleasing _Nullable *)error; -(nullable CGImageRef)writeData:(NSData *)data - hints:(ZXIEncodeHints *)hints error:(NSError *__autoreleasing _Nullable *)error; @end diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm index dfaf706ccc..eafd885310 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIBarcodeWriter.mm @@ -4,7 +4,7 @@ #import #import "ZXIBarcodeWriter.h" -#import "ZXIEncodeHints.h" +#import "ZXIWriterOptions.h" #import "MultiFormatWriter.h" #import "BitMatrix.h" #import "BitMatrixIO.h" @@ -32,29 +32,37 @@ @implementation ZXIBarcodeWriter +- (instancetype)init { + return [self initWithOptions: [[ZXIWriterOptions alloc] init]]; +} + +- (instancetype)initWithOptions:(ZXIWriterOptions*)options{ + self = [super init]; + self.options = options; + return self; +} + -(CGImageRef)writeData:(NSData *)data - hints:(ZXIEncodeHints *)hints error:(NSError *__autoreleasing _Nullable *)error { return [self encode: NSDataToStringW(data) encoding: CharacterSet::BINARY - format: hints.format - width: hints.width - height: hints.height - margin: hints.margin - ecLevel: hints.ecLevel + format: self.options.format + width: self.options.width + height: self.options.height + margin: self.options.margin + ecLevel: self.options.ecLevel error: error]; } -(CGImageRef)writeString:(NSString *)contents - hints:(ZXIEncodeHints *)hints error:(NSError *__autoreleasing _Nullable *)error { return [self encode: NSStringToStringW(contents) encoding: CharacterSet::UTF8 - format: hints.format - width: hints.width - height: hints.height - margin: hints.margin - ecLevel: hints.ecLevel + format: self.options.format + width: self.options.width + height: self.options.height + margin: self.options.margin + ecLevel: self.options.ecLevel error: error]; } diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h b/wrappers/ios/Sources/Wrapper/Writer/ZXIWriterOptions.h similarity index 97% rename from wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h rename to wrappers/ios/Sources/Wrapper/Writer/ZXIWriterOptions.h index eed784dc78..a68386d919 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.h +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIWriterOptions.h @@ -30,7 +30,7 @@ extern const int PDF417_ERROR_CORRECTION_6; extern const int PDF417_ERROR_CORRECTION_7; extern const int PDF417_ERROR_CORRECTION_8; -@interface ZXIEncodeHints : NSObject +@interface ZXIWriterOptions : NSObject @property(nonatomic) ZXIFormat format; @property(nonatomic) int width; @property(nonatomic) int height; diff --git a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm b/wrappers/ios/Sources/Wrapper/Writer/ZXIWriterOptions.mm similarity index 96% rename from wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm rename to wrappers/ios/Sources/Wrapper/Writer/ZXIWriterOptions.mm index d53f2986d5..651bd135e6 100644 --- a/wrappers/ios/Sources/Wrapper/Writer/ZXIEncodeHints.mm +++ b/wrappers/ios/Sources/Wrapper/Writer/ZXIWriterOptions.mm @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -#import "ZXIEncodeHints.h" +#import "ZXIWriterOptions.h" const int AZTEC_ERROR_CORRECTION_0 = 0; const int AZTEC_ERROR_CORRECTION_12 = 1; @@ -27,7 +27,7 @@ const int PDF417_ERROR_CORRECTION_7 = 7; const int PDF417_ERROR_CORRECTION_8 = 8; -@implementation ZXIEncodeHints +@implementation ZXIWriterOptions - (instancetype)initWithFormat:(ZXIFormat)format { self = [super init]; diff --git a/wrappers/ios/demo/demo/WriteViewController.swift b/wrappers/ios/demo/demo/WriteViewController.swift index f6576c35b1..9a4e21df7c 100644 --- a/wrappers/ios/demo/demo/WriteViewController.swift +++ b/wrappers/ios/demo/demo/WriteViewController.swift @@ -14,9 +14,9 @@ class WriteViewController: UIViewController { // MARK: - Actions @IBAction func textFieldChanged(_ sender: UITextField) { - let hints = ZXIEncodeHints(format: .QR_CODE, width: 200, height: 200, ecLevel: QR_ERROR_CORRECTION_LOW, margin: -1) + let options = ZXIWriterOptions(format: .QR_CODE, width: 200, height: 200, ecLevel: QR_ERROR_CORRECTION_LOW, margin: -1) guard let text = sender.text, - let image = try? ZXIBarcodeWriter().write(text, hints: hints) + let image = try? ZXIBarcodeWriter(options: options).write(text) else { return } diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec index c1bf74ad3e..041934c779 100644 --- a/zxing-cpp.podspec +++ b/zxing-cpp.podspec @@ -32,7 +32,7 @@ Pod::Spec.new do |s| ss.frameworks = 'CoreGraphics', 'CoreImage', 'CoreVideo' ss.source_files = 'wrappers/ios/Sources/Wrapper/**/*.{h,m,mm}' ss.public_header_files = 'wrappers/ios/Sources/Wrapper/Reader/{ZXIBarcodeReader,ZXIResult,ZXIPosition,ZXIPoint,ZXIGTIN,ZXIReaderOptions}.h', - 'wrappers/ios/Sources/Wrapper/Writer/{ZXIBarcodeWriter,ZXIEncodeHints}.h', + 'wrappers/ios/Sources/Wrapper/Writer/{ZXIBarcodeWriter,ZXIWriterOptions}.h', 'wrappers/ios/Sources/Wrapper/{ZXIErrors,ZXIFormat}.h' ss.exclude_files = 'wrappers/ios/Sources/Wrapper/UmbrellaHeader.h' end From 920e1b5ad7718b40aaa308264ef093ed3d50a74b Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 13:32:54 +0100 Subject: [PATCH 153/587] DecodeHints: introduce new name `ReaderOptions` for the public API A recent discussion revealed that `DecodeHints` is a bad or at least outdated name. It came from the obsolete `MultiFormatReader::decode`. Both "Decode" and "Hint" are sub optimal: * most properties actually relate to the Detect part * most of them don't hint at something but actually change something The old name is still used internally. It will be deprecated and then at a later time be completely removed. --- README.md | 4 +- core/src/DecodeHints.h | 2 + core/src/ReadBarcode.cpp | 40 +++++------ core/src/ReadBarcode.h | 12 ++-- example/ZXingOpenCV.h | 4 +- example/ZXingQtReader.cpp | 4 +- example/ZXingQtReader.h | 34 +++++----- example/ZXingReader.cpp | 44 ++++++------- test/blackbox/TestReaderMain.cpp | 6 +- test/fuzz/fuzzReadLinear.cpp | 20 +++--- test/fuzz/fuzzReadMatrix.cpp | 14 ++-- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 66 +++++++++---------- .../src/main/java/zxingcpp/BarcodeReader.kt | 4 +- wrappers/c/README.md | 8 +-- wrappers/c/zxing-c-test.c | 14 ++-- wrappers/c/zxing-c.cpp | 60 ++++++++--------- wrappers/c/zxing-c.h | 36 +++++----- .../Wrapper/Reader/ZXIBarcodeReader.mm | 8 +-- .../Wrapper/Reader/ZXIReaderOptions.mm | 46 ++++++------- wrappers/python/zxing.cpp | 4 +- wrappers/wasm/BarcodeReader.cpp | 20 +++--- wrappers/winrt/BarcodeReader.cpp | 12 ++-- wrappers/winrt/BarcodeReader.h | 4 +- 23 files changed, 234 insertions(+), 232 deletions(-) diff --git a/README.md b/README.md index 717a727a0a..1054cb2fae 100644 --- a/README.md +++ b/README.md @@ -66,8 +66,8 @@ int main(int argc, char** argv) // load your image data from somewhere. ImageFormat::Lum assumes grey scale image data. auto image = ZXing::ImageView(data, width, height, ZXing::ImageFormat::Lum); - auto hints = ZXing::DecodeHints().setFormats(ZXing::BarcodeFormat::Any); - auto results = ZXing::ReadBarcodes(image, hints); + auto options = ZXing::ReaderOptions().setFormats(ZXing::BarcodeFormat::Any); + auto results = ZXing::ReadBarcodes(image, options); for (const auto& r : results) std::cout << ZXing::ToString(r.format()) << ": " << r.text() << "\n"; diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index 2bdaba3a30..53d9adb384 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -172,4 +172,6 @@ class DecodeHints bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); } }; +using ReaderOptions = DecodeHints; + } // ZXing diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 383a5bc948..4cc94e01a7 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -75,7 +75,7 @@ class LumImagePyramid case 2: addLayer<2>(); break; case 3: addLayer<3>(); break; case 4: addLayer<4>(); break; - default: throw std::invalid_argument("Invalid DecodeHints::downscaleFactor"); break; + default: throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor"); break; } } @@ -98,12 +98,12 @@ class LumImagePyramid } }; -ImageView SetupLumImageView(ImageView iv, LumImage& lum, const DecodeHints& hints) +ImageView SetupLumImageView(ImageView iv, LumImage& lum, const ReaderOptions& opts) { if (iv.format() == ImageFormat::None) throw std::invalid_argument("Invalid image format"); - if (hints.binarizer() == Binarizer::GlobalHistogram || hints.binarizer() == Binarizer::LocalAverage) { + if (opts.binarizer() == Binarizer::GlobalHistogram || opts.binarizer() == Binarizer::LocalAverage) { if (iv.format() != ImageFormat::Lum) { lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())]( const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); }); @@ -128,44 +128,44 @@ std::unique_ptr CreateBitmap(ZXing::Binarizer binarizer, const Ima return {}; // silence gcc warning } -Result ReadBarcode(const ImageView& _iv, const DecodeHints& hints) +Result ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) { - return FirstOrDefault(ReadBarcodes(_iv, DecodeHints(hints).setMaxNumberOfSymbols(1))); + return FirstOrDefault(ReadBarcodes(_iv, ReaderOptions(opts).setMaxNumberOfSymbols(1))); } -Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) +Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) { - if (sizeof(PatternType) < 4 && hints.hasFormat(BarcodeFormat::LinearCodes) && (_iv.width() > 0xffff || _iv.height() > 0xffff)) + if (sizeof(PatternType) < 4 && opts.hasFormat(BarcodeFormat::LinearCodes) && (_iv.width() > 0xffff || _iv.height() > 0xffff)) throw std::invalid_argument("maximum image width/height is 65535"); LumImage lum; - ImageView iv = SetupLumImageView(_iv, lum, hints); - MultiFormatReader reader(hints); + ImageView iv = SetupLumImageView(_iv, lum, opts); + MultiFormatReader reader(opts); - if (hints.isPure()) - return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; + if (opts.isPure()) + return {reader.read(*CreateBitmap(opts.binarizer(), iv))}; std::unique_ptr closedReader; #ifdef ZXING_BUILD_EXPERIMENTAL_API auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode; - DecodeHints closedHints = hints; - if (hints.tryDenoise() && hints.hasFormat(formatsBenefittingFromClosing)) { - closedHints.setFormats((hints.formats().empty() ? BarcodeFormat::Any : hints.formats()) & formatsBenefittingFromClosing); - closedReader = std::make_unique(closedHints); + ReaderOptions closedOptions = opts; + if (opts.tryDenoise() && opts.hasFormat(formatsBenefittingFromClosing)) { + closedOptions.setFormats((opts.formats().empty() ? BarcodeFormat::Any : opts.formats()) & formatsBenefittingFromClosing); + closedReader = std::make_unique(closedOptions); } #endif - LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); + LumImagePyramid pyramid(iv, opts.downscaleThreshold() * opts.tryDownscale(), opts.downscaleFactor()); Results results; - int maxSymbols = hints.maxNumberOfSymbols() ? hints.maxNumberOfSymbols() : INT_MAX; + int maxSymbols = opts.maxNumberOfSymbols() ? opts.maxNumberOfSymbols() : INT_MAX; for (auto&& iv : pyramid.layers) { - auto bitmap = CreateBitmap(hints.binarizer(), iv); + auto bitmap = CreateBitmap(opts.binarizer(), iv); for (int close = 0; close <= (closedReader ? 1 : 0); ++close) { if (close) bitmap->close(); // TODO: check if closing after invert would be beneficial - for (int invert = 0; invert <= static_cast(hints.tryInvert() && !close); ++invert) { + for (int invert = 0; invert <= static_cast(opts.tryInvert() && !close); ++invert) { if (invert) bitmap->invert(); auto rs = (close ? *closedReader : reader).readMultiple(*bitmap, maxSymbols); @@ -173,7 +173,7 @@ Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) if (iv.width() != _iv.width()) r.setPosition(Scale(r.position(), _iv.width() / iv.width())); if (!Contains(results, r)) { - r.setDecodeHints(hints); + r.setDecodeHints(opts); r.setIsInverted(bitmap->inverted()); results.push_back(std::move(r)); --maxSymbols; diff --git a/core/src/ReadBarcode.h b/core/src/ReadBarcode.h index 5b73d16913..9769b11e59 100644 --- a/core/src/ReadBarcode.h +++ b/core/src/ReadBarcode.h @@ -14,20 +14,20 @@ namespace ZXing { /** * Read barcode from an ImageView * - * @param buffer view of the image data including layout and format - * @param hints optional DecodeHints to parameterize / speed up decoding + * @param image view of the image data including layout and format + * @param options optional ReaderOptions to parameterize / speed up detection * @return #Result structure */ -Result ReadBarcode(const ImageView& buffer, const DecodeHints& hints = {}); +Result ReadBarcode(const ImageView& image, const ReaderOptions& options = {}); /** * Read barcodes from an ImageView * - * @param buffer view of the image data including layout and format - * @param hints optional DecodeHints to parameterize / speed up decoding + * @param image view of the image data including layout and format + * @param options optional ReaderOptions to parameterize / speed up detection * @return #Results list of results found, may be empty */ -Results ReadBarcodes(const ImageView& buffer, const DecodeHints& hints = {}); +Results ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); } // ZXing diff --git a/example/ZXingOpenCV.h b/example/ZXingOpenCV.h index c81d0e598c..bd125c2aa2 100644 --- a/example/ZXingOpenCV.h +++ b/example/ZXingOpenCV.h @@ -26,9 +26,9 @@ inline ZXing::ImageView ImageViewFromMat(const cv::Mat& image) return {image.data, image.cols, image.rows, fmt}; } -inline ZXing::Results ReadBarcodes(const cv::Mat& image, const ZXing::DecodeHints& hints = {}) +inline ZXing::Results ReadBarcodes(const cv::Mat& image, const ZXing::ReaderOptions& options = {}) { - return ZXing::ReadBarcodes(ImageViewFromMat(image), hints); + return ZXing::ReadBarcodes(ImageViewFromMat(image), options); } inline void DrawResult(cv::Mat& img, ZXing::Result res) diff --git a/example/ZXingQtReader.cpp b/example/ZXingQtReader.cpp index 757cabd87c..fe04192160 100644 --- a/example/ZXingQtReader.cpp +++ b/example/ZXingQtReader.cpp @@ -25,12 +25,12 @@ int main(int argc, char* argv[]) return 1; } - auto hints = DecodeHints() + auto options = ReaderOptions() .setFormats(BarcodeFormat::Any) .setTryRotate(false) .setMaxNumberOfSymbols(10); - auto results = ReadBarcodes(fileImage, hints); + auto results = ReadBarcodes(fileImage, options); for (auto& result : results) { qDebug() << "Text: " << result.text(); diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index b9380f3930..1c11c0f50c 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -64,7 +64,7 @@ using ZXing::BarcodeFormat; using ZXing::ContentType; #endif -using ZXing::DecodeHints; +using ZXing::ReaderOptions; using ZXing::Binarizer; using ZXing::BarcodeFormats; @@ -141,7 +141,7 @@ inline QList QListResults(ZXing::Results&& zxres) return res; } -inline QList ReadBarcodes(const QImage& img, const DecodeHints& hints = {}) +inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts = {}) { using namespace ZXing; @@ -164,20 +164,20 @@ inline QList ReadBarcodes(const QImage& img, const DecodeHints& hints = auto exec = [&](const QImage& img) { return QListResults(ZXing::ReadBarcodes( - {img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast(img.bytesPerLine())}, hints)); + {img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast(img.bytesPerLine())}, opts)); }; return ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img); } -inline Result ReadBarcode(const QImage& img, const DecodeHints& hints = {}) +inline Result ReadBarcode(const QImage& img, const ReaderOptions& opts = {}) { - auto res = ReadBarcodes(img, DecodeHints(hints).setMaxNumberOfSymbols(1)); + auto res = ReadBarcodes(img, ReaderOptions(opts).setMaxNumberOfSymbols(1)); return !res.isEmpty() ? res.takeFirst() : Result(); } #ifdef QT_MULTIMEDIA_LIB -inline QList ReadBarcodes(const QVideoFrame& frame, const DecodeHints& hints = {}) +inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& opts = {}) { using namespace ZXing; @@ -274,7 +274,7 @@ inline QList ReadBarcodes(const QVideoFrame& frame, const DecodeHints& h QScopeGuard unmap([&] { img.unmap(); }); return QListResults(ZXing::ReadBarcodes( - {img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, hints)); + {img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, opts)); } else { #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) @@ -287,26 +287,26 @@ inline QList ReadBarcodes(const QVideoFrame& frame, const DecodeHints& h auto qimg = frame.toImage(); #endif if (qimg.format() != QImage::Format_Invalid) - return ReadBarcodes(qimg, hints); + return ReadBarcodes(qimg, opts); qWarning() << "failed to convert QVideoFrame to QImage"; return {}; } } -inline Result ReadBarcode(const QVideoFrame& frame, const DecodeHints& hints = {}) +inline Result ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = {}) { - auto res = ReadBarcodes(frame, DecodeHints(hints).setMaxNumberOfSymbols(1)); + auto res = ReadBarcodes(frame, ReaderOptions(opts).setMaxNumberOfSymbols(1)); return !res.isEmpty() ? res.takeFirst() : Result(); } #define ZQ_PROPERTY(Type, name, setter) \ public: \ Q_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \ - Type name() const noexcept { return DecodeHints::name(); } \ + Type name() const noexcept { return ReaderOptions::name(); } \ Q_SLOT void setter(const Type& newVal) \ { \ if (name() != newVal) { \ - DecodeHints::setter(newVal); \ + ReaderOptions::setter(newVal); \ emit name##Changed(); \ } \ } \ @@ -314,9 +314,9 @@ public: \ #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) -class BarcodeReader : public QAbstractVideoFilter, private DecodeHints +class BarcodeReader : public QAbstractVideoFilter, private ReaderOptions #else -class BarcodeReader : public QObject, private DecodeHints +class BarcodeReader : public QObject, private ReaderOptions #endif { Q_OBJECT @@ -334,15 +334,15 @@ class BarcodeReader : public QObject, private DecodeHints Q_PROPERTY(int formats READ formats WRITE setFormats NOTIFY formatsChanged) int formats() const noexcept { - auto fmts = DecodeHints::formats(); + auto fmts = ReaderOptions::formats(); return *reinterpret_cast(&fmts); } Q_SLOT void setFormats(int newVal) { if (formats() != newVal) { - DecodeHints::setFormats(static_cast(newVal)); + ReaderOptions::setFormats(static_cast(newVal)); emit formatsChanged(); - qDebug() << DecodeHints::formats(); + qDebug() << ReaderOptions::formats(); } } Q_SIGNAL void formatsChanged(); diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 47a9c0782c..74682a9d2a 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -51,36 +51,36 @@ static void PrintUsage(const char* exePath) std::cout << "Formats can be lowercase, with or without '-', separated by ',' and/or '|'\n"; } -static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLine, bool& bytesOnly, +static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& oneLine, bool& bytesOnly, std::vector& filePaths, std::string& outPath) { #ifdef ZXING_BUILD_EXPERIMENTAL_API - hints.setTryDenoise(true); + options.setTryDenoise(true); #endif for (int i = 1; i < argc; ++i) { auto is = [&](const char* str) { return strncmp(argv[i], str, strlen(argv[i])) == 0; }; if (is("-fast")) { - hints.setTryHarder(false); + options.setTryHarder(false); #ifdef ZXING_BUILD_EXPERIMENTAL_API - hints.setTryDenoise(false); + options.setTryDenoise(false); #endif } else if (is("-norotate")) { - hints.setTryRotate(false); + options.setTryRotate(false); } else if (is("-noinvert")) { - hints.setTryInvert(false); + options.setTryInvert(false); } else if (is("-noscale")) { - hints.setTryDownscale(false); + options.setTryDownscale(false); } else if (is("-ispure")) { - hints.setIsPure(true); - hints.setBinarizer(Binarizer::FixedThreshold); + options.setIsPure(true); + options.setBinarizer(Binarizer::FixedThreshold); } else if (is("-errors")) { - hints.setReturnErrors(true); + options.setReturnErrors(true); } else if (is("-format")) { if (++i == argc) return false; try { - hints.setFormats(BarcodeFormatsFromString(argv[i])); + options.setFormats(BarcodeFormatsFromString(argv[i])); } catch (const std::exception& e) { std::cerr << e.what() << "\n"; return false; @@ -89,13 +89,13 @@ static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLi if (++i == argc) return false; else if (is("plain")) - hints.setTextMode(TextMode::Plain); + options.setTextMode(TextMode::Plain); else if (is("eci")) - hints.setTextMode(TextMode::ECI); + options.setTextMode(TextMode::ECI); else if (is("hri")) - hints.setTextMode(TextMode::HRI); + options.setTextMode(TextMode::HRI); else if (is("escaped")) - hints.setTextMode(TextMode::Escaped); + options.setTextMode(TextMode::Escaped); else return false; } else if (is("-1")) { @@ -151,7 +151,7 @@ void drawRect(const ImageView& image, const Position& pos, bool error) int main(int argc, char* argv[]) { - DecodeHints hints; + ReaderOptions options; std::vector filePaths; Results allResults; std::string outPath; @@ -159,10 +159,10 @@ int main(int argc, char* argv[]) bool bytesOnly = false; int ret = 0; - hints.setTextMode(TextMode::HRI); - hints.setEanAddOnSymbol(EanAddOnSymbol::Read); + options.setTextMode(TextMode::HRI); + options.setEanAddOnSymbol(EanAddOnSymbol::Read); - if (!ParseOptions(argc, argv, hints, oneLine, bytesOnly, filePaths, outPath)) { + if (!ParseOptions(argc, argv, options, oneLine, bytesOnly, filePaths, outPath)) { PrintUsage(argv[0]); return -1; } @@ -179,7 +179,7 @@ int main(int argc, char* argv[]) } ImageView image{buffer.get(), width, height, ImageFormat::RGB}; - auto results = ReadBarcodes(image, hints); + auto results = ReadBarcodes(image, options); // if we did not find anything, insert a dummy to produce some output for each file if (results.empty()) @@ -229,7 +229,7 @@ int main(int argc, char* argv[]) } std::cout << "Text: \"" << result.text() << "\"\n" - << "Bytes: " << ToHex(hints.textMode() == TextMode::ECI ? result.bytesECI() : result.bytes()) << "\n" + << "Bytes: " << ToHex(options.textMode() == TextMode::ECI ? result.bytesECI() : result.bytes()) << "\n" << "Format: " << ToString(result.format()) << "\n" << "Identifier: " << result.symbologyIdentifier() << "\n" << "Content: " << ToString(result.contentType()) << "\n" @@ -283,7 +283,7 @@ int main(int argc, char* argv[]) int blockSize = 1; do { for (int i = 0; i < blockSize; ++i) - ReadBarcodes(image, hints); + ReadBarcodes(image, options); N += blockSize; duration = std::chrono::high_resolution_clock::now() - startTime; if (blockSize < 1000 && duration < std::chrono::milliseconds(100)) diff --git a/test/blackbox/TestReaderMain.cpp b/test/blackbox/TestReaderMain.cpp index 31f9063092..5a69c6143c 100644 --- a/test/blackbox/TestReaderMain.cpp +++ b/test/blackbox/TestReaderMain.cpp @@ -35,13 +35,13 @@ int main(int argc, char** argv) fs::path pathPrefix = argv[1]; if (Contains({".png", ".jpg", ".pgm", ".gif"}, pathPrefix.extension())) { - auto hints = DecodeHints().setTryHarder(!getEnv("FAST", false)).setTryRotate(true).setIsPure(getEnv("IS_PURE")); + auto opts = ReaderOptions().setTryHarder(!getEnv("FAST", false)).setTryRotate(true).setIsPure(getEnv("IS_PURE")); if (getenv("FORMATS")) - hints.setFormats(BarcodeFormatsFromString(getenv("FORMATS"))); + opts.setFormats(BarcodeFormatsFromString(getenv("FORMATS"))); int rotation = getEnv("ROTATION"); for (int i = 1; i < argc; ++i) { - Result result = ReadBarcode(ImageLoader::load(argv[i]).rotated(rotation), hints); + Result result = ReadBarcode(ImageLoader::load(argv[i]).rotated(rotation), opts); std::cout << argv[i] << ": "; if (result.isValid()) std::cout << ToString(result.format()) << ": " << result.text() << "\n"; diff --git a/test/fuzz/fuzzReadLinear.cpp b/test/fuzz/fuzzReadLinear.cpp index 86027c4524..03d529ecb6 100644 --- a/test/fuzz/fuzzReadLinear.cpp +++ b/test/fuzz/fuzzReadLinear.cpp @@ -24,16 +24,16 @@ static std::vector> readers; bool init() { - static DecodeHints hints; - hints.setReturnErrors(true); - readers.emplace_back(new MultiUPCEANReader(hints)); - readers.emplace_back(new Code39Reader(hints)); - readers.emplace_back(new Code93Reader(hints)); - readers.emplace_back(new Code128Reader(hints)); - readers.emplace_back(new ITFReader(hints)); - readers.emplace_back(new CodabarReader(hints)); - readers.emplace_back(new DataBarReader(hints)); - readers.emplace_back(new DataBarExpandedReader(hints)); + static ReaderOptions opts; + opts.setReturnErrors(true); + readers.emplace_back(new MultiUPCEANReader(opts)); + readers.emplace_back(new Code39Reader(opts)); + readers.emplace_back(new Code93Reader(opts)); + readers.emplace_back(new Code128Reader(opts)); + readers.emplace_back(new ITFReader(opts)); + readers.emplace_back(new CodabarReader(opts)); + readers.emplace_back(new DataBarReader(opts)); + readers.emplace_back(new DataBarExpandedReader(opts)); return true; } diff --git a/test/fuzz/fuzzReadMatrix.cpp b/test/fuzz/fuzzReadMatrix.cpp index 0db6a261ef..dd01b40171 100644 --- a/test/fuzz/fuzzReadMatrix.cpp +++ b/test/fuzz/fuzzReadMatrix.cpp @@ -24,12 +24,12 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) if (size < 3) return 0; - static auto options = DecodeHints() - .setFormats(BarcodeFormat::MatrixCodes) - .setBinarizer(Binarizer::BoolCast) - .setReturnErrors(true) - .setTryInvert(false) - .setTryRotate(false); + static auto opts = ReaderOptions() + .setFormats(BarcodeFormat::MatrixCodes) + .setBinarizer(Binarizer::BoolCast) + .setReturnErrors(true) + .setTryInvert(false) + .setTryRotate(false); int ratio = data[0] + 1; int nBits = (size - 1) * 8; @@ -47,7 +47,7 @@ extern "C" int LLVMFuzzerTestOneInput(const uint8_t* data, size_t size) #endif auto image = ImageView(buffer.data(), width, height, ImageFormat::Lum); - auto res = ReadBarcodes(image, options); + auto res = ReadBarcodes(image, opts); #ifdef PRINT_DEBUG for (const auto& r : res) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index e9f7bd0e75..e8154aaa3d 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -233,11 +233,11 @@ static jobject NewResult(JNIEnv* env, const Result& result) ); } -static jobject Read(JNIEnv *env, jobject thiz, ImageView image, const DecodeHints& hints) +static jobject Read(JNIEnv *env, jobject thiz, ImageView image, const ReaderOptions& opts) { try { auto startTime = std::chrono::high_resolution_clock::now(); - auto results = ReadBarcodes(image, hints); + auto results = ReadBarcodes(image, opts); auto duration = std::chrono::high_resolution_clock::now() - startTime; // LOGD("time: %4d ms\n", (int)std::chrono::duration_cast(duration).count()); auto time = std::chrono::duration_cast(duration).count(); @@ -259,27 +259,27 @@ static jobject Read(JNIEnv *env, jobject thiz, ImageView image, const DecodeHint } } -static bool GetBooleanField(JNIEnv* env, jclass cls, jobject hints, const char* name) +static bool GetBooleanField(JNIEnv* env, jclass cls, jobject opts, const char* name) { - return env->GetBooleanField(hints, env->GetFieldID(cls, name, "Z")); + return env->GetBooleanField(opts, env->GetFieldID(cls, name, "Z")); } -static int GetIntField(JNIEnv* env, jclass cls, jobject hints, const char* name) +static int GetIntField(JNIEnv* env, jclass cls, jobject opts, const char* name) { - return env->GetIntField(hints, env->GetFieldID(cls, name, "I")); + return env->GetIntField(opts, env->GetFieldID(cls, name, "I")); } -static std::string GetEnumField(JNIEnv* env, jclass cls, jobject hints, const char* name, const char* type) +static std::string GetEnumField(JNIEnv* env, jclass cls, jobject opts, const char* name, const char* type) { auto className = PACKAGE ""s + type; jmethodID midName = env->GetMethodID(env->FindClass(className.c_str()), "name", "()Ljava/lang/String;"); - jobject objField = env->GetObjectField(hints, env->GetFieldID(cls, name, ("L"s + className + ";").c_str())); + jobject objField = env->GetObjectField(opts, env->GetFieldID(cls, name, ("L"s + className + ";").c_str())); return J2CString(env, static_cast(env->CallObjectMethod(objField, midName))); } -static BarcodeFormats GetFormats(JNIEnv* env, jclass hintClass, jobject hints) +static BarcodeFormats GetFormats(JNIEnv* env, jclass clsOptions, jobject opts) { - jobject objField = env->GetObjectField(hints, env->GetFieldID(hintClass, "formats", "Ljava/util/Set;")); + jobject objField = env->GetObjectField(opts, env->GetFieldID(clsOptions, "formats", "Ljava/util/Set;")); jmethodID midToArray = env->GetMethodID(env->FindClass("java/util/Set"), "toArray", "()[Ljava/lang/Object;"); auto objArray = static_cast(env->CallObjectMethod(objField, midToArray)); if (!objArray) @@ -294,28 +294,28 @@ static BarcodeFormats GetFormats(JNIEnv* env, jclass hintClass, jobject hints) return ret; } -static DecodeHints CreateDecodeHints(JNIEnv* env, jobject hints) +static ReaderOptions CreateReaderOptions(JNIEnv* env, jobject opts) { - jclass cls = env->GetObjectClass(hints); - return DecodeHints() - .setFormats(GetFormats(env, cls, hints)) - .setTryHarder(GetBooleanField(env, cls, hints, "tryHarder")) - .setTryRotate(GetBooleanField(env, cls, hints, "tryRotate")) - .setTryInvert(GetBooleanField(env, cls, hints, "tryInvert")) - .setTryDownscale(GetBooleanField(env, cls, hints, "tryDownscale")) - .setIsPure(GetBooleanField(env, cls, hints, "isPure")) - .setBinarizer(BinarizerFromString(GetEnumField(env, cls, hints, "binarizer", "Binarizer"))) - .setDownscaleThreshold(GetIntField(env, cls, hints, "downscaleThreshold")) - .setDownscaleFactor(GetIntField(env, cls, hints, "downscaleFactor")) - .setMinLineCount(GetIntField(env, cls, hints, "minLineCount")) - .setMaxNumberOfSymbols(GetIntField(env, cls, hints, "maxNumberOfSymbols")) - .setTryCode39ExtendedMode(GetBooleanField(env, cls, hints, "tryCode39ExtendedMode")) - .setValidateCode39CheckSum(GetBooleanField(env, cls, hints, "validateCode39CheckSum")) - .setValidateITFCheckSum(GetBooleanField(env, cls, hints, "validateITFCheckSum")) - .setReturnCodabarStartEnd(GetBooleanField(env, cls, hints, "returnCodabarStartEnd")) - .setReturnErrors(GetBooleanField(env, cls, hints, "returnErrors")) - .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, hints, "eanAddOnSymbol", "EanAddOnSymbol"))) - .setTextMode(TextModeFromString(GetEnumField(env, cls, hints, "textMode", "TextMode"))) + jclass cls = env->GetObjectClass(opts); + return ReaderOptions() + .setFormats(GetFormats(env, cls, opts)) + .setTryHarder(GetBooleanField(env, cls, opts, "tryHarder")) + .setTryRotate(GetBooleanField(env, cls, opts, "tryRotate")) + .setTryInvert(GetBooleanField(env, cls, opts, "tryInvert")) + .setTryDownscale(GetBooleanField(env, cls, opts, "tryDownscale")) + .setIsPure(GetBooleanField(env, cls, opts, "isPure")) + .setBinarizer(BinarizerFromString(GetEnumField(env, cls, opts, "binarizer", "Binarizer"))) + .setDownscaleThreshold(GetIntField(env, cls, opts, "downscaleThreshold")) + .setDownscaleFactor(GetIntField(env, cls, opts, "downscaleFactor")) + .setMinLineCount(GetIntField(env, cls, opts, "minLineCount")) + .setMaxNumberOfSymbols(GetIntField(env, cls, opts, "maxNumberOfSymbols")) + .setTryCode39ExtendedMode(GetBooleanField(env, cls, opts, "tryCode39ExtendedMode")) + .setValidateCode39CheckSum(GetBooleanField(env, cls, opts, "validateCode39CheckSum")) + .setValidateITFCheckSum(GetBooleanField(env, cls, opts, "validateITFCheckSum")) + .setReturnCodabarStartEnd(GetBooleanField(env, cls, opts, "returnCodabarStartEnd")) + .setReturnErrors(GetBooleanField(env, cls, opts, "returnErrors")) + .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, opts, "eanAddOnSymbol", "EanAddOnSymbol"))) + .setTextMode(TextModeFromString(GetEnumField(env, cls, opts, "textMode", "TextMode"))) ; } @@ -330,7 +330,7 @@ Java_zxingcpp_BarcodeReader_readYBuffer( ImageView{pixels + top * rowStride + left, width, height, ImageFormat::Lum, rowStride} .rotated(rotation); - return Read(env, thiz, image, CreateDecodeHints(env, options)); + return Read(env, thiz, image, CreateReaderOptions(env, options)); } struct LockedPixels @@ -377,5 +377,5 @@ Java_zxingcpp_BarcodeReader_readBitmap( .cropped(left, top, width, height) .rotated(rotation); - return Read(env, thiz, image, CreateDecodeHints(env, options)); + return Read(env, thiz, image, CreateReaderOptions(env, options)); } diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index b994d9c342..2d330baa7b 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -142,10 +142,10 @@ public class BarcodeReader(public var options: Options = Options()) { } private external fun readYBuffer( - yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, hints: Options + yBuffer: ByteBuffer, rowStride: Int, left: Int, top: Int, width: Int, height: Int, rotation: Int, options: Options ): List private external fun readBitmap( - bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, hints: Options + bitmap: Bitmap, left: Int, top: Int, width: Int, height: Int, rotation: Int, options: Options ): List } diff --git a/wrappers/c/README.md b/wrappers/c/README.md index 399eae8d0d..550565ae71 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -21,10 +21,10 @@ int main(int argc, char** argv) zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); - zxing_DecodeHints* hints = zxing_DecodeHints_new(); - /* set DecodeHints properties, if requried */ + zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); + /* set ReaderOptions properties, if requried */ - zxing_Result* result = zxing_ReadBarcode(iv, hints); + zxing_Result* result = zxing_ReadBarcode(iv, opts); if (result) { printf("Format : %s\n", zxing_BarcodeFormatToString(zxing_Result_format(result))); @@ -39,7 +39,7 @@ int main(int argc, char** argv) } zxing_ImageView_delete(iv); - zxing_DecodeHints_delete(hints); + zxing_ReaderOptions_delete(opts); return 0; } diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index 24d4457d66..0497a1cadd 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -54,15 +54,15 @@ int main(int argc, char** argv) if (!data) return 2; - zxing_DecodeHints* hints = zxing_DecodeHints_new(); - zxing_DecodeHints_setTextMode(hints, zxing_TextMode_HRI); - zxing_DecodeHints_setEanAddOnSymbol(hints, zxing_EanAddOnSymbol_Ignore); - zxing_DecodeHints_setFormats(hints, formats); - zxing_DecodeHints_setReturnErrors(hints, true); + zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); + zxing_ReaderOptions_setTextMode(opts, zxing_TextMode_HRI); + zxing_ReaderOptions_setEanAddOnSymbol(opts, zxing_EanAddOnSymbol_Ignore); + zxing_ReaderOptions_setFormats(opts, formats); + zxing_ReaderOptions_setReturnErrors(opts, true); zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); - zxing_Results* results = zxing_ReadBarcodes(iv, hints); + zxing_Results* results = zxing_ReadBarcodes(iv, opts); if (results) { for (int i = 0, n = zxing_Results_size(results); i < n; ++i) { @@ -86,7 +86,7 @@ int main(int argc, char** argv) } zxing_ImageView_delete(iv); - zxing_DecodeHints_delete(hints); + zxing_ReaderOptions_delete(opts); stbi_image_free(data); return 0; diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index a7e3d60e5b..601ef324a6 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -68,69 +68,69 @@ char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) * ZXing/DecodeHints.h */ -zxing_DecodeHints* zxing_DecodeHints_new() +zxing_ReaderOptions* zxing_ReaderOptions_new() { - return new DecodeHints(); + return new ReaderOptions(); } -void zxing_DecodeHints_delete(zxing_DecodeHints* hints) +void zxing_ReaderOptions_delete(zxing_ReaderOptions* opts) { - delete hints; + delete opts; } -void zxing_DecodeHints_setTryHarder(zxing_DecodeHints* hints, bool tryHarder) +void zxing_ReaderOptions_setTryHarder(zxing_ReaderOptions* opts, bool tryHarder) { - hints->setTryHarder(tryHarder); + opts->setTryHarder(tryHarder); } -void zxing_DecodeHints_setTryRotate(zxing_DecodeHints* hints, bool tryRotate) +void zxing_ReaderOptions_setTryRotate(zxing_ReaderOptions* opts, bool tryRotate) { - hints->setTryRotate(tryRotate); + opts->setTryRotate(tryRotate); } -void zxing_DecodeHints_setTryInvert(zxing_DecodeHints* hints, bool tryInvert) +void zxing_ReaderOptions_setTryInvert(zxing_ReaderOptions* opts, bool tryInvert) { - hints->setTryInvert(tryInvert); + opts->setTryInvert(tryInvert); } -void zxing_DecodeHints_setTryDownscale(zxing_DecodeHints* hints, bool tryDownscale) +void zxing_ReaderOptions_setTryDownscale(zxing_ReaderOptions* opts, bool tryDownscale) { - hints->setTryDownscale(tryDownscale); + opts->setTryDownscale(tryDownscale); } -void zxing_DecodeHints_setIsPure(zxing_DecodeHints* hints, bool isPure) +void zxing_ReaderOptions_setIsPure(zxing_ReaderOptions* opts, bool isPure) { - hints->setIsPure(isPure); + opts->setIsPure(isPure); } -void zxing_DecodeHints_setReturnErrors(zxing_DecodeHints* hints, bool returnErrors) +void zxing_ReaderOptions_setReturnErrors(zxing_ReaderOptions* opts, bool returnErrors) { - hints->setReturnErrors(returnErrors); + opts->setReturnErrors(returnErrors); } -void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats formats) +void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeFormats formats) { - hints->setFormats(static_cast(formats)); + opts->setFormats(static_cast(formats)); } -void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer) +void zxing_ReaderOptions_setBinarizer(zxing_ReaderOptions* opts, zxing_Binarizer binarizer) { - hints->setBinarizer(static_cast(binarizer)); + opts->setBinarizer(static_cast(binarizer)); } -void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol) +void zxing_ReaderOptions_setEanAddOnSymbol(zxing_ReaderOptions* opts, zxing_EanAddOnSymbol eanAddOnSymbol) { - hints->setEanAddOnSymbol(static_cast(eanAddOnSymbol)); + opts->setEanAddOnSymbol(static_cast(eanAddOnSymbol)); } -void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode) +void zxing_ReaderOptions_setTextMode(zxing_ReaderOptions* opts, zxing_TextMode textMode) { - hints->setTextMode(static_cast(textMode)); + opts->setTextMode(static_cast(textMode)); } -void zxing_DecodeHints_setMaxNumberOfSymbols(zxing_DecodeHints* hints, int n) +void zxing_ReaderOptions_setMaxNumberOfSymbols(zxing_ReaderOptions* opts, int n) { - hints->setMaxNumberOfSymbols(n); + opts->setMaxNumberOfSymbols(n); } /* @@ -209,15 +209,15 @@ bool zxing_Result_isMirrored(const zxing_Result* result) * ZXing/ReadBarcode.h */ -zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_DecodeHints* hints) +zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcode(*iv, *hints); + auto res = ReadBarcode(*iv, *opts); return res.format() != BarcodeFormat::None ? new Result(std::move(res)) : NULL; } -zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_DecodeHints* hints) +zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodes(*iv, *hints); + auto res = ReadBarcodes(*iv, *opts); return !res.empty() ? new Results(std::move(res)) : NULL; } diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index fe4be794eb..4a5dacca75 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -17,7 +17,7 @@ #include "Result.h" typedef ZXing::ImageView zxing_ImageView; -typedef ZXing::DecodeHints zxing_DecodeHints; +typedef ZXing::ReaderOptions zxing_ReaderOptions; typedef ZXing::Result zxing_Result; typedef ZXing::Results zxing_Results; @@ -26,7 +26,7 @@ extern "C" #else typedef struct zxing_ImageView zxing_ImageView; -typedef struct zxing_DecodeHints zxing_DecodeHints; +typedef struct zxing_ReaderOptions zxing_ReaderOptions; typedef struct zxing_Result zxing_Result; typedef struct zxing_Results zxing_Results; @@ -124,20 +124,20 @@ typedef enum zxing_TextMode_Escaped, } zxing_TextMode; -zxing_DecodeHints* zxing_DecodeHints_new(); -void zxing_DecodeHints_delete(zxing_DecodeHints* hints); - -void zxing_DecodeHints_setTryHarder(zxing_DecodeHints* hints, bool tryHarder); -void zxing_DecodeHints_setTryRotate(zxing_DecodeHints* hints, bool tryRotate); -void zxing_DecodeHints_setTryInvert(zxing_DecodeHints* hints, bool tryInvert); -void zxing_DecodeHints_setTryDownscale(zxing_DecodeHints* hints, bool tryDownscale); -void zxing_DecodeHints_setIsPure(zxing_DecodeHints* hints, bool isPure); -void zxing_DecodeHints_setReturnErrors(zxing_DecodeHints* hints, bool returnErrors); -void zxing_DecodeHints_setFormats(zxing_DecodeHints* hints, zxing_BarcodeFormats formats); -void zxing_DecodeHints_setBinarizer(zxing_DecodeHints* hints, zxing_Binarizer binarizer); -void zxing_DecodeHints_setEanAddOnSymbol(zxing_DecodeHints* hints, zxing_EanAddOnSymbol eanAddOnSymbol); -void zxing_DecodeHints_setTextMode(zxing_DecodeHints* hints, zxing_TextMode textMode); -void zxing_DecodeHints_setMaxNumberOfSymbols(zxing_DecodeHints* hints, int n); +zxing_ReaderOptions* zxing_ReaderOptions_new(); +void zxing_ReaderOptions_delete(zxing_ReaderOptions* opts); + +void zxing_ReaderOptions_setTryHarder(zxing_ReaderOptions* opts, bool tryHarder); +void zxing_ReaderOptions_setTryRotate(zxing_ReaderOptions* opts, bool tryRotate); +void zxing_ReaderOptions_setTryInvert(zxing_ReaderOptions* opts, bool tryInvert); +void zxing_ReaderOptions_setTryDownscale(zxing_ReaderOptions* opts, bool tryDownscale); +void zxing_ReaderOptions_setIsPure(zxing_ReaderOptions* opts, bool isPure); +void zxing_ReaderOptions_setReturnErrors(zxing_ReaderOptions* opts, bool returnErrors); +void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeFormats formats); +void zxing_ReaderOptions_setBinarizer(zxing_ReaderOptions* opts, zxing_Binarizer binarizer); +void zxing_ReaderOptions_setEanAddOnSymbol(zxing_ReaderOptions* opts, zxing_EanAddOnSymbol eanAddOnSymbol); +void zxing_ReaderOptions_setTextMode(zxing_ReaderOptions* opts, zxing_TextMode textMode); +void zxing_ReaderOptions_setMaxNumberOfSymbols(zxing_ReaderOptions* opts, int n); /* * ZXing/Result.h @@ -171,8 +171,8 @@ bool zxing_Result_isMirrored(const zxing_Result* result); * ZXing/ReadBarcode.h */ -zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_DecodeHints* hints); -zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_DecodeHints* hints); +zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); +zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); void zxing_Result_delete(zxing_Result* result); void zxing_Results_delete(zxing_Results* results); diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm index e381e36ae5..4b21c81bf3 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm @@ -117,12 +117,12 @@ - (instancetype)initWithOptions:(ZXIReaderOptions*)options{ return [self readImageView:imageView error:error]; } -+ (DecodeHints)DecodeHintsFromZXIReaderOptions:(ZXIReaderOptions*)options { ++ (ReaderOptions)ReaderOptionsFromZXIReaderOptions:(ZXIReaderOptions*)options { BarcodeFormats formats; for(NSNumber* flag in options.formats) { formats.setFlag(BarcodeFormatFromZXIFormat((ZXIFormat)flag.integerValue)); } - DecodeHints resultingHints = DecodeHints() + ReaderOptions cppOpts = ReaderOptions() .setFormats(formats) .setTryRotate(options.tryRotate) .setTryHarder(options.tryHarder) @@ -132,13 +132,13 @@ + (DecodeHints)DecodeHintsFromZXIReaderOptions:(ZXIReaderOptions*)options { .setValidateCode39CheckSum(options.validateCode39CheckSum) .setValidateITFCheckSum(options.validateITFCheckSum) .setMaxNumberOfSymbols(options.maxNumberOfSymbols); - return resultingHints; + return cppOpts; } - (NSArray *)readImageView:(ImageView)imageView error:(NSError *__autoreleasing _Nullable *)error { try { - Results results = ReadBarcodes(imageView, [ZXIBarcodeReader DecodeHintsFromZXIReaderOptions:self.options]); + Results results = ReadBarcodes(imageView, [ZXIBarcodeReader ReaderOptionsFromZXIReaderOptions:self.options]); NSMutableArray* zxiResults = [NSMutableArray array]; for (auto result: results) { [zxiResults addObject: diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm index 801fe6ed7d..973eeccb7b 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm @@ -6,14 +6,14 @@ #import "DecodeHints.h" @interface ZXIReaderOptions() -@property(nonatomic) ZXing::DecodeHints zxingHints; +@property(nonatomic) ZXing::ReaderOptions cppOpts; @end @implementation ZXIReaderOptions -(instancetype)init { self = [super init]; - self.zxingHints = ZXing::DecodeHints(); + self.cppOpts = ZXing::ReaderOptions(); return self; } @@ -29,7 +29,7 @@ - (instancetype)initWithFormats:(NSArray*)formats downscaleThreshold:(uint16_t)downscaleThreshold maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { self = [super init]; - self.zxingHints = ZXing::DecodeHints(); + self.cppOpts = ZXing::ReaderOptions(); self.tryHarder = tryHarder; self.tryRotate = tryRotate; self.tryInvert = tryInvert; @@ -45,83 +45,83 @@ - (instancetype)initWithFormats:(NSArray*)formats } -(void)setTryHarder:(BOOL)tryHarder { - self.zxingHints.setTryHarder(tryHarder); + self.cppOpts.setTryHarder(tryHarder); } -(void)setTryRotate:(BOOL)tryRotate { - self.zxingHints.setTryRotate(tryRotate); + self.cppOpts.setTryRotate(tryRotate); } -(void)setTryInvert:(BOOL)tryInvert { - self.zxingHints.setTryInvert(tryInvert); + self.cppOpts.setTryInvert(tryInvert); } -(void)setTryDownscale:(BOOL)tryDownscale { - self.zxingHints.setTryDownscale(tryDownscale); + self.cppOpts.setTryDownscale(tryDownscale); } -(void)setTryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode { - self.zxingHints.setTryCode39ExtendedMode(tryCode39ExtendedMode); + self.cppOpts.setTryCode39ExtendedMode(tryCode39ExtendedMode); } -(void)setValidateCode39CheckSum:(BOOL)validateCode39CheckSum { - self.zxingHints.setValidateCode39CheckSum(validateCode39CheckSum); + self.cppOpts.setValidateCode39CheckSum(validateCode39CheckSum); } -(void)setValidateITFCheckSum:(BOOL)validateITFCheckSum { - self.zxingHints.setValidateITFCheckSum(validateITFCheckSum); + self.cppOpts.setValidateITFCheckSum(validateITFCheckSum); } -(void)setDownscaleFactor:(uint8_t)downscaleFactor { - self.zxingHints.setDownscaleFactor(downscaleFactor); + self.cppOpts.setDownscaleFactor(downscaleFactor); } -(void)setDownscaleThreshold:(uint16_t)downscaleThreshold { - self.zxingHints.setDownscaleThreshold(downscaleThreshold); + self.cppOpts.setDownscaleThreshold(downscaleThreshold); } -(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { - self.zxingHints.setMaxNumberOfSymbols(maxNumberOfSymbols); + self.cppOpts.setMaxNumberOfSymbols(maxNumberOfSymbols); } -(BOOL)tryHarder { - return self.zxingHints.tryHarder(); + return self.cppOpts.tryHarder(); } -(BOOL)tryRotate { - return self.zxingHints.tryRotate(); + return self.cppOpts.tryRotate(); } -(BOOL)tryInvert { - return self.zxingHints.tryInvert(); + return self.cppOpts.tryInvert(); } -(BOOL)tryDownscale { - return self.zxingHints.tryDownscale(); + return self.cppOpts.tryDownscale(); } -(BOOL)tryCode39ExtendedMode { - return self.zxingHints.tryCode39ExtendedMode(); + return self.cppOpts.tryCode39ExtendedMode(); } -(BOOL)validateCode39CheckSum { - return self.zxingHints.validateCode39CheckSum(); + return self.cppOpts.validateCode39CheckSum(); } -(BOOL)validateITFCheckSum { - return self.zxingHints.validateITFCheckSum(); + return self.cppOpts.validateITFCheckSum(); } -(uint8_t)downscaleFactor { - return self.zxingHints.downscaleFactor(); + return self.cppOpts.downscaleFactor(); } -(uint16_t)downscaleThreshold { - return self.zxingHints.downscaleThreshold(); + return self.cppOpts.downscaleThreshold(); } - (NSInteger)maxNumberOfSymbols { - return self.zxingHints.maxNumberOfSymbols(); + return self.cppOpts.maxNumberOfSymbols(); } @end diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index 5006a68374..a83e1b36ea 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -40,7 +40,7 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t Binarizer binarizer, bool is_pure, EanAddOnSymbol ean_add_on_symbol, bool return_errors, uint8_t max_number_of_symbols = 0xff) { - const auto hints = DecodeHints() + const auto opts = ReaderOptions() .setFormats(formats) .setTryRotate(try_rotate) .setTryDownscale(try_downscale) @@ -139,7 +139,7 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t const auto bytes = static_cast(info.ptr); // Disables the GIL during zxing processing (restored automatically upon completion) py::gil_scoped_release release; - return ReadBarcodes({bytes, width, height, imgfmt, rowStride, pixStride}, hints); + return ReadBarcodes({bytes, width, height, imgfmt, rowStride, pixStride}, opts); } std::optional read_barcode(py::object _image, const BarcodeFormats& formats, bool try_rotate, bool try_downscale, diff --git a/wrappers/wasm/BarcodeReader.cpp b/wrappers/wasm/BarcodeReader.cpp index 2f216d5f3f..a023368850 100644 --- a/wrappers/wasm/BarcodeReader.cpp +++ b/wrappers/wasm/BarcodeReader.cpp @@ -30,16 +30,16 @@ struct ReadResult std::vector readBarcodes(ImageView iv, bool tryHarder, const std::string& format, int maxSymbols) { try { - DecodeHints hints; - hints.setTryHarder(tryHarder); - hints.setTryRotate(tryHarder); - hints.setTryInvert(tryHarder); - hints.setTryDownscale(tryHarder); - hints.setFormats(BarcodeFormatsFromString(format)); - hints.setMaxNumberOfSymbols(maxSymbols); -// hints.setReturnErrors(maxSymbols > 1); - - auto results = ReadBarcodes(iv, hints); + ReaderOptions opts; + opts.setTryHarder(tryHarder); + opts.setTryRotate(tryHarder); + opts.setTryInvert(tryHarder); + opts.setTryDownscale(tryHarder); + opts.setFormats(BarcodeFormatsFromString(format)); + opts.setMaxNumberOfSymbols(maxSymbols); +// opts.setReturnErrors(maxSymbols > 1); + + auto results = ReadBarcodes(iv, opts); std::vector readResults{}; readResults.reserve(results.size()); diff --git a/wrappers/winrt/BarcodeReader.cpp b/wrappers/winrt/BarcodeReader.cpp index 6963d8cac1..9657d494d0 100644 --- a/wrappers/winrt/BarcodeReader.cpp +++ b/wrappers/winrt/BarcodeReader.cpp @@ -45,17 +45,17 @@ BarcodeReader::BarcodeReader(bool tryHarder) void BarcodeReader::init(bool tryHarder, bool tryRotate, const Platform::Array^ types) { - m_hints.reset(new DecodeHints()); - m_hints->setTryHarder(tryHarder); - m_hints->setTryRotate(tryRotate); - m_hints->setTryInvert(tryHarder); + m_opts.reset(new ReaderOptions()); + m_opts->setTryHarder(tryHarder); + m_opts->setTryRotate(tryRotate); + m_opts->setTryInvert(tryHarder); if (types != nullptr && types->Length > 0) { BarcodeFormats barcodeFormats; for (BarcodeType type : types) { barcodeFormats |= BarcodeReader::ConvertRuntimeToNative(type); } - m_hints->setFormats(barcodeFormats); + m_opts->setFormats(barcodeFormats); } } @@ -189,7 +189,7 @@ BarcodeReader::Read(SoftwareBitmap^ bitmap, int cropWidth, int cropHeight) auto img = ImageView(inBytes, bitmap->PixelWidth, bitmap->PixelHeight, fmt, inBuffer->GetPlaneDescription(0).Stride) .cropped(cropLeft, cropTop, cropWidth, cropHeight); - auto result = ReadBarcode(img, *m_hints); + auto result = ReadBarcode(img, *m_opts); if (result.isValid()) { return ref new ReadResult(ToPlatformString(ZXing::ToString(result.format())), ToPlatformString(result.text()), ConvertNativeToRuntime(result.format())); } diff --git a/wrappers/winrt/BarcodeReader.h b/wrappers/winrt/BarcodeReader.h index 591fa10d31..49ffdc02c1 100644 --- a/wrappers/winrt/BarcodeReader.h +++ b/wrappers/winrt/BarcodeReader.h @@ -32,7 +32,7 @@ public enum class BarcodeType : int { UPC_E }; -class DecodeHints; +class ReaderOptions; ref class ReadResult; public ref class BarcodeReader sealed @@ -52,7 +52,7 @@ public ref class BarcodeReader sealed static BarcodeFormat ConvertRuntimeToNative(BarcodeType type); static BarcodeType ConvertNativeToRuntime(BarcodeFormat format); - std::unique_ptr m_hints; + std::unique_ptr m_opts; }; } // ZXing From 8ac818e36b001ebef5aea7eaac57806b0c9869ae Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 14:49:04 +0100 Subject: [PATCH 154/587] WinRT: fix compile regressions --- wrappers/winrt/BarcodeReader.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/winrt/BarcodeReader.h b/wrappers/winrt/BarcodeReader.h index 49ffdc02c1..68ad884150 100644 --- a/wrappers/winrt/BarcodeReader.h +++ b/wrappers/winrt/BarcodeReader.h @@ -6,6 +6,7 @@ #pragma once #include "BarcodeFormat.h" +#include "DecodeHints.h" #include @@ -25,14 +26,13 @@ public enum class BarcodeType : int { PDF_417, QR_CODE, MICRO_QR_CODE, - RMQR_CODE + RMQR_CODE, RSS_14, RSS_EXPANDED, UPC_A, UPC_E }; -class ReaderOptions; ref class ReadResult; public ref class BarcodeReader sealed From 319c5457fa8c003e140963326532b9d9b975a42d Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 17:09:03 +0100 Subject: [PATCH 155/587] ReaderOptions: complete the renaming from DecodeHints --- core/CMakeLists.txt | 2 + core/src/Content.h | 2 +- core/src/DecodeHints.h | 173 +---------------- core/src/MultiFormatReader.cpp | 28 +-- core/src/MultiFormatReader.h | 18 +- core/src/ReadBarcode.cpp | 4 +- core/src/ReadBarcode.h | 2 +- core/src/Reader.h | 12 +- core/src/ReaderOptions.h | 177 ++++++++++++++++++ core/src/Result.cpp | 10 +- core/src/Result.h | 12 +- core/src/aztec/AZReader.cpp | 12 +- core/src/datamatrix/DMReader.cpp | 10 +- core/src/maxicode/MCReader.cpp | 2 +- core/src/oned/ODCodabarReader.cpp | 4 +- core/src/oned/ODCode39Reader.cpp | 10 +- core/src/oned/ODITFReader.cpp | 8 +- core/src/oned/ODMultiUPCEANReader.cpp | 20 +- core/src/oned/ODReader.cpp | 40 ++-- core/src/oned/ODReader.h | 4 +- core/src/oned/ODRowReader.h | 8 +- core/src/pdf417/PDFReader.cpp | 10 +- core/src/qrcode/QRReader.cpp | 32 ++-- test/blackbox/BlackboxTestRunner.cpp | 34 ++-- test/fuzz/fuzzReadLinear.cpp | 2 +- test/unit/ThresholdBinarizerTest.cpp | 8 +- test/unit/oned/ODCodaBarWriterTest.cpp | 6 +- test/unit/oned/ODCode128ReaderTest.cpp | 6 +- test/unit/oned/ODCode128WriterTest.cpp | 6 +- test/unit/oned/ODCode39ExtendedModeTest.cpp | 6 +- test/unit/oned/ODCode39ReaderTest.cpp | 12 +- test/unit/oned/ODCode93ReaderTest.cpp | 6 +- test/unit/oned/ODDataBarReaderTest.cpp | 6 +- wrappers/c/zxing-c.cpp | 2 +- wrappers/c/zxing-c.h | 4 +- .../Wrapper/Reader/ZXIReaderOptions.mm | 2 +- wrappers/winrt/BarcodeReader.cpp | 2 +- wrappers/winrt/BarcodeReader.h | 2 +- 38 files changed, 354 insertions(+), 350 deletions(-) create mode 100644 core/src/ReaderOptions.h diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d1e45438ce..589c37a86b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -122,6 +122,7 @@ if (BUILD_READERS) src/PerspectiveTransform.h src/PerspectiveTransform.cpp src/Reader.h + src/ReaderOptions.h src/ReadBarcode.h src/ReadBarcode.cpp src/ReedSolomonDecoder.h @@ -171,6 +172,7 @@ if (BUILD_READERS) src/Point.h src/Quadrilateral.h src/ReadBarcode.h + src/ReaderOptions.h src/Result.h src/StructuredAppend.h ) diff --git a/core/src/Content.h b/core/src/Content.h index 501f89d5b7..99e5a01e71 100644 --- a/core/src/Content.h +++ b/core/src/Content.h @@ -7,7 +7,7 @@ #include "ByteArray.h" #include "CharacterSet.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include #include diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index 53d9adb384..f3276baa0a 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -1,177 +1,10 @@ /* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -* Copyright 2020 Axel Waggershauser +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 #pragma once -#include "BarcodeFormat.h" -#include "CharacterSet.h" +#include "ReaderOptions.h" -#include -#include - -namespace ZXing { - -/** - * @brief The Binarizer enum - * - * Specify which algorithm to use for the grayscale to binary transformation. - * The difference is how to get to a threshold value T which results in a bit - * value R = L <= T. - */ -enum class Binarizer : unsigned char // needs to be unsigned for the bitfield below to work, uint8_t fails as well -{ - LocalAverage, ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer) - GlobalHistogram, ///< T = valley between the 2 largest peaks in the histogram (per line in linear case) - FixedThreshold, ///< T = 127 - BoolCast, ///< T = 0, fastest possible -}; - -enum class EanAddOnSymbol : unsigned char // see above -{ - Ignore, ///< Ignore any Add-On symbol during read/scan - Read, ///< Read EAN-2/EAN-5 Add-On symbol if found - Require, ///< Require EAN-2/EAN-5 Add-On symbol to be present -}; - -enum class TextMode : unsigned char // see above -{ - Plain, ///< bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0) - ECI, ///< standard content following the ECI protocol with every character set ECI segment transcoded to unicode - HRI, ///< Human Readable Interpretation (dependent on the ContentType) - Hex, ///< bytes() transcoded to ASCII string of HEX values - Escaped, ///< Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to "") -}; - -class DecodeHints -{ - bool _tryHarder : 1; - bool _tryRotate : 1; - bool _tryInvert : 1; - bool _tryDownscale : 1; - bool _isPure : 1; - bool _tryCode39ExtendedMode : 1; - bool _validateCode39CheckSum : 1; - bool _validateITFCheckSum : 1; - bool _returnCodabarStartEnd : 1; - bool _returnErrors : 1; - uint8_t _downscaleFactor : 3; - EanAddOnSymbol _eanAddOnSymbol : 2; - Binarizer _binarizer : 2; - TextMode _textMode : 3; - CharacterSet _characterSet : 6; -#ifdef ZXING_BUILD_EXPERIMENTAL_API - bool _tryDenoise : 1; -#endif - - uint8_t _minLineCount = 2; - uint8_t _maxNumberOfSymbols = 0xff; - uint16_t _downscaleThreshold = 500; - BarcodeFormats _formats = BarcodeFormat::None; - -public: - // bitfields don't get default initialized to 0 before c++20 - DecodeHints() - : _tryHarder(1), - _tryRotate(1), - _tryInvert(1), - _tryDownscale(1), - _isPure(0), - _tryCode39ExtendedMode(0), - _validateCode39CheckSum(0), - _validateITFCheckSum(0), - _returnCodabarStartEnd(0), - _returnErrors(0), - _downscaleFactor(3), - _eanAddOnSymbol(EanAddOnSymbol::Ignore), - _binarizer(Binarizer::LocalAverage), - _textMode(TextMode::HRI), - _characterSet(CharacterSet::Unknown) -#ifdef ZXING_BUILD_EXPERIMENTAL_API - , - _tryDenoise(0) -#endif - {} - -#define ZX_PROPERTY(TYPE, GETTER, SETTER) \ - TYPE GETTER() const noexcept { return _##GETTER; } \ - DecodeHints& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ - DecodeHints&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } - - /// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. - ZX_PROPERTY(BarcodeFormats, formats, setFormats) - - /// Spend more time to try to find a barcode; optimize for accuracy, not speed. - ZX_PROPERTY(bool, tryHarder, setTryHarder) - - /// Also try detecting code in 90, 180 and 270 degree rotated images. - ZX_PROPERTY(bool, tryRotate, setTryRotate) - - /// Also try detecting inverted ("reversed reflectance") codes if the format allows for those. - ZX_PROPERTY(bool, tryInvert, setTryInvert) - - /// Also try detecting code in downscaled images (depending on image size). - ZX_PROPERTY(bool, tryDownscale, setTryDownscale) - -#ifdef ZXING_BUILD_EXPERIMENTAL_API - /// Also try detecting code after denoising (currently morphological closing filter for 2D symbologies only). - ZX_PROPERTY(bool, tryDenoise, setTryDenoise) -#endif - - /// Binarizer to use internally when using the ReadBarcode function - ZX_PROPERTY(Binarizer, binarizer, setBinarizer) - - /// Set to true if the input contains nothing but a single perfectly aligned barcode (generated image) - ZX_PROPERTY(bool, isPure, setIsPure) - - /// Image size ( min(width, height) ) threshold at which to start downscaled scanning - // WARNING: this API is experimental and may change/disappear - ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold) - - /// Scale factor used during downscaling, meaningful values are 2, 3 and 4 - // WARNING: this API is experimental and may change/disappear - ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor) - - /// The number of scan lines in a linear barcode that have to be equal to accept the result, default is 2 - ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount) - - /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes - ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) - - /// If true, the Code-39 reader will try to read extended mode. - ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) - - /// Assume Code-39 codes employ a check digit and validate it. - ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum) - - /// Assume ITF codes employ a GS1 check digit and validate it. - ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum) - - /// If true, return the start and end chars in a Codabar barcode instead of stripping them. - ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd) - - /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Result::error()) - ZX_PROPERTY(bool, returnErrors, setReturnErrors) - - /// Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes - ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) - - /// Specifies the TextMode that controls the return of the Result::text() function - ZX_PROPERTY(TextMode, textMode, setTextMode) - - /// Specifies fallback character set to use instead of auto-detecting it (when applicable) - ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet) - DecodeHints& setCharacterSet(std::string_view v)& { return (void)(_characterSet = CharacterSetFromString(v)), *this; } - DecodeHints&& setCharacterSet(std::string_view v) && { return (void)(_characterSet = CharacterSetFromString(v)), std::move(*this); } - -#undef ZX_PROPERTY - - bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); } -}; - -using ReaderOptions = DecodeHints; - -} // ZXing +// TODO: remove this backward compatibility header once the deprecated name DecodeHints has been removed (3.0) diff --git a/core/src/MultiFormatReader.cpp b/core/src/MultiFormatReader.cpp index f8ee215547..27e7f31311 100644 --- a/core/src/MultiFormatReader.cpp +++ b/core/src/MultiFormatReader.cpp @@ -8,7 +8,7 @@ #include "BarcodeFormat.h" #include "BinaryBitmap.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "aztec/AZReader.h" #include "datamatrix/DMReader.h" #include "maxicode/MCReader.h" @@ -20,28 +20,28 @@ namespace ZXing { -MultiFormatReader::MultiFormatReader(const DecodeHints& hints) : _hints(hints) +MultiFormatReader::MultiFormatReader(const ReaderOptions& opts) : _opts(opts) { - auto formats = hints.formats().empty() ? BarcodeFormat::Any : hints.formats(); + auto formats = opts.formats().empty() ? BarcodeFormat::Any : opts.formats(); // Put linear readers upfront in "normal" mode - if (formats.testFlags(BarcodeFormat::LinearCodes) && !hints.tryHarder()) - _readers.emplace_back(new OneD::Reader(hints)); + if (formats.testFlags(BarcodeFormat::LinearCodes) && !opts.tryHarder()) + _readers.emplace_back(new OneD::Reader(opts)); if (formats.testFlags(BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode | BarcodeFormat::RMQRCode)) - _readers.emplace_back(new QRCode::Reader(hints, true)); + _readers.emplace_back(new QRCode::Reader(opts, true)); if (formats.testFlag(BarcodeFormat::DataMatrix)) - _readers.emplace_back(new DataMatrix::Reader(hints, true)); + _readers.emplace_back(new DataMatrix::Reader(opts, true)); if (formats.testFlag(BarcodeFormat::Aztec)) - _readers.emplace_back(new Aztec::Reader(hints, true)); + _readers.emplace_back(new Aztec::Reader(opts, true)); if (formats.testFlag(BarcodeFormat::PDF417)) - _readers.emplace_back(new Pdf417::Reader(hints)); + _readers.emplace_back(new Pdf417::Reader(opts)); if (formats.testFlag(BarcodeFormat::MaxiCode)) - _readers.emplace_back(new MaxiCode::Reader(hints)); + _readers.emplace_back(new MaxiCode::Reader(opts)); // At end in "try harder" mode - if (formats.testFlags(BarcodeFormat::LinearCodes) && hints.tryHarder()) - _readers.emplace_back(new OneD::Reader(hints)); + if (formats.testFlags(BarcodeFormat::LinearCodes) && opts.tryHarder()) + _readers.emplace_back(new OneD::Reader(opts)); } MultiFormatReader::~MultiFormatReader() = default; @@ -55,7 +55,7 @@ MultiFormatReader::read(const BinaryBitmap& image) const if (r.isValid()) return r; } - return _hints.returnErrors() ? r : Result(); + return _opts.returnErrors() ? r : Result(); } Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbols) const @@ -66,7 +66,7 @@ Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbol if (image.inverted() && !reader->supportsInversion) continue; auto r = reader->decode(image, maxSymbols); - if (!_hints.returnErrors()) { + if (!_opts.returnErrors()) { //TODO: C++20 res.erase_if() auto it = std::remove_if(res.begin(), res.end(), [](auto&& r) { return !r.isValid(); }); res.erase(it, res.end()); diff --git a/core/src/MultiFormatReader.h b/core/src/MultiFormatReader.h index 4765b1574a..fe7749b525 100644 --- a/core/src/MultiFormatReader.h +++ b/core/src/MultiFormatReader.h @@ -16,21 +16,13 @@ namespace ZXing { class Result; class Reader; class BinaryBitmap; -class DecodeHints; - -/** -* MultiFormatReader is a convenience class and the main entry point into the library for most uses. -* By default it attempts to decode all barcode formats that the library supports. Optionally, you -* can provide a hints object to request different behavior, for example only decoding QR codes. -* -* @author Sean Owen -* @author dswitkin@google.com (Daniel Switkin) -*/ +class ReaderOptions; + class MultiFormatReader { public: - explicit MultiFormatReader(const DecodeHints& hints); - explicit MultiFormatReader(DecodeHints&& hints) = delete; + explicit MultiFormatReader(const ReaderOptions& opts); + explicit MultiFormatReader(ReaderOptions&& opts) = delete; ~MultiFormatReader(); Result read(const BinaryBitmap& image) const; @@ -40,7 +32,7 @@ class MultiFormatReader private: std::vector> _readers; - const DecodeHints& _hints; + const ReaderOptions& _opts; }; } // ZXing diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 4cc94e01a7..43269e0c6d 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -5,7 +5,7 @@ #include "ReadBarcode.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "GlobalHistogramBinarizer.h" #include "HybridBinarizer.h" #include "MultiFormatReader.h" @@ -173,7 +173,7 @@ Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) if (iv.width() != _iv.width()) r.setPosition(Scale(r.position(), _iv.width() / iv.width())); if (!Contains(results, r)) { - r.setDecodeHints(opts); + r.setReaderOptions(opts); r.setIsInverted(bitmap->inverted()); results.push_back(std::move(r)); --maxSymbols; diff --git a/core/src/ReadBarcode.h b/core/src/ReadBarcode.h index 9769b11e59..343ac8487b 100644 --- a/core/src/ReadBarcode.h +++ b/core/src/ReadBarcode.h @@ -5,7 +5,7 @@ #pragma once -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "ImageView.h" #include "Result.h" diff --git a/core/src/Reader.h b/core/src/Reader.h index bc2d69cdc2..7ece21b56d 100644 --- a/core/src/Reader.h +++ b/core/src/Reader.h @@ -6,24 +6,24 @@ #pragma once -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" namespace ZXing { class BinaryBitmap; -class DecodeHints; +class ReaderOptions; class Reader { protected: - const DecodeHints& _hints; + const ReaderOptions& _opts; public: const bool supportsInversion; - explicit Reader(const DecodeHints& hints, bool supportsInversion = false) : _hints(hints), supportsInversion(supportsInversion) {} - explicit Reader(DecodeHints&& hints) = delete; + explicit Reader(const ReaderOptions& opts, bool supportsInversion = false) : _opts(opts), supportsInversion(supportsInversion) {} + explicit Reader(ReaderOptions&& opts) = delete; virtual ~Reader() = default; virtual Result decode(const BinaryBitmap& image) const = 0; @@ -31,7 +31,7 @@ class Reader // WARNING: this API is experimental and may change/disappear virtual Results decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { auto res = decode(image); - return res.isValid() || (_hints.returnErrors() && res.format() != BarcodeFormat::None) ? Results{std::move(res)} : Results{}; + return res.isValid() || (_opts.returnErrors() && res.format() != BarcodeFormat::None) ? Results{std::move(res)} : Results{}; } }; diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h new file mode 100644 index 0000000000..e68d0592d7 --- /dev/null +++ b/core/src/ReaderOptions.h @@ -0,0 +1,177 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +* Copyright 2020 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "BarcodeFormat.h" +#include "CharacterSet.h" + +#include +#include + +namespace ZXing { + +/** + * @brief The Binarizer enum + * + * Specify which algorithm to use for the grayscale to binary transformation. + * The difference is how to get to a threshold value T which results in a bit + * value R = L <= T. + */ +enum class Binarizer : unsigned char // needs to be unsigned for the bitfield below to work, uint8_t fails as well +{ + LocalAverage, ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer) + GlobalHistogram, ///< T = valley between the 2 largest peaks in the histogram (per line in linear case) + FixedThreshold, ///< T = 127 + BoolCast, ///< T = 0, fastest possible +}; + +enum class EanAddOnSymbol : unsigned char // see above +{ + Ignore, ///< Ignore any Add-On symbol during read/scan + Read, ///< Read EAN-2/EAN-5 Add-On symbol if found + Require, ///< Require EAN-2/EAN-5 Add-On symbol to be present +}; + +enum class TextMode : unsigned char // see above +{ + Plain, ///< bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0) + ECI, ///< standard content following the ECI protocol with every character set ECI segment transcoded to unicode + HRI, ///< Human Readable Interpretation (dependent on the ContentType) + Hex, ///< bytes() transcoded to ASCII string of HEX values + Escaped, ///< Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to "") +}; + +class ReaderOptions +{ + bool _tryHarder : 1; + bool _tryRotate : 1; + bool _tryInvert : 1; + bool _tryDownscale : 1; + bool _isPure : 1; + bool _tryCode39ExtendedMode : 1; + bool _validateCode39CheckSum : 1; + bool _validateITFCheckSum : 1; + bool _returnCodabarStartEnd : 1; + bool _returnErrors : 1; + uint8_t _downscaleFactor : 3; + EanAddOnSymbol _eanAddOnSymbol : 2; + Binarizer _binarizer : 2; + TextMode _textMode : 3; + CharacterSet _characterSet : 6; +#ifdef ZXING_BUILD_EXPERIMENTAL_API + bool _tryDenoise : 1; +#endif + + uint8_t _minLineCount = 2; + uint8_t _maxNumberOfSymbols = 0xff; + uint16_t _downscaleThreshold = 500; + BarcodeFormats _formats = BarcodeFormat::None; + +public: + // bitfields don't get default initialized to 0 before c++20 + ReaderOptions() + : _tryHarder(1), + _tryRotate(1), + _tryInvert(1), + _tryDownscale(1), + _isPure(0), + _tryCode39ExtendedMode(0), + _validateCode39CheckSum(0), + _validateITFCheckSum(0), + _returnCodabarStartEnd(0), + _returnErrors(0), + _downscaleFactor(3), + _eanAddOnSymbol(EanAddOnSymbol::Ignore), + _binarizer(Binarizer::LocalAverage), + _textMode(TextMode::HRI), + _characterSet(CharacterSet::Unknown) +#ifdef ZXING_BUILD_EXPERIMENTAL_API + , + _tryDenoise(0) +#endif + {} + +#define ZX_PROPERTY(TYPE, GETTER, SETTER) \ + TYPE GETTER() const noexcept { return _##GETTER; } \ + ReaderOptions& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ + ReaderOptions&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } + + /// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. + ZX_PROPERTY(BarcodeFormats, formats, setFormats) + + /// Spend more time to try to find a barcode; optimize for accuracy, not speed. + ZX_PROPERTY(bool, tryHarder, setTryHarder) + + /// Also try detecting code in 90, 180 and 270 degree rotated images. + ZX_PROPERTY(bool, tryRotate, setTryRotate) + + /// Also try detecting inverted ("reversed reflectance") codes if the format allows for those. + ZX_PROPERTY(bool, tryInvert, setTryInvert) + + /// Also try detecting code in downscaled images (depending on image size). + ZX_PROPERTY(bool, tryDownscale, setTryDownscale) + +#ifdef ZXING_BUILD_EXPERIMENTAL_API + /// Also try detecting code after denoising (currently morphological closing filter for 2D symbologies only). + ZX_PROPERTY(bool, tryDenoise, setTryDenoise) +#endif + + /// Binarizer to use internally when using the ReadBarcode function + ZX_PROPERTY(Binarizer, binarizer, setBinarizer) + + /// Set to true if the input contains nothing but a single perfectly aligned barcode (generated image) + ZX_PROPERTY(bool, isPure, setIsPure) + + /// Image size ( min(width, height) ) threshold at which to start downscaled scanning + // WARNING: this API is experimental and may change/disappear + ZX_PROPERTY(uint16_t, downscaleThreshold, setDownscaleThreshold) + + /// Scale factor used during downscaling, meaningful values are 2, 3 and 4 + // WARNING: this API is experimental and may change/disappear + ZX_PROPERTY(uint8_t, downscaleFactor, setDownscaleFactor) + + /// The number of scan lines in a linear barcode that have to be equal to accept the result, default is 2 + ZX_PROPERTY(uint8_t, minLineCount, setMinLineCount) + + /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes + ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) + + /// If true, the Code-39 reader will try to read extended mode. + ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) + + /// Assume Code-39 codes employ a check digit and validate it. + ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum) + + /// Assume ITF codes employ a GS1 check digit and validate it. + ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum) + + /// If true, return the start and end chars in a Codabar barcode instead of stripping them. + ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd) + + /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Result::error()) + ZX_PROPERTY(bool, returnErrors, setReturnErrors) + + /// Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes + ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) + + /// Specifies the TextMode that controls the return of the Result::text() function + ZX_PROPERTY(TextMode, textMode, setTextMode) + + /// Specifies fallback character set to use instead of auto-detecting it (when applicable) + ZX_PROPERTY(CharacterSet, characterSet, setCharacterSet) + ReaderOptions& setCharacterSet(std::string_view v)& { return (void)(_characterSet = CharacterSetFromString(v)), *this; } + ReaderOptions&& setCharacterSet(std::string_view v) && { return (void)(_characterSet = CharacterSetFromString(v)), std::move(*this); } + +#undef ZX_PROPERTY + + bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); } +}; + +using DecodeHints [[deprecated]] = ReaderOptions; + +} // ZXing diff --git a/core/src/Result.cpp b/core/src/Result.cpp index 6fcf80e1d2..427ed84e51 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -64,7 +64,7 @@ std::string Result::text(TextMode mode) const std::string Result::text() const { - return text(_decodeHints.textMode()); + return text(_readerOpts.textMode()); } std::string Result::ecLevel() const @@ -113,11 +113,11 @@ std::string Result::version() const return _version; } -Result& Result::setDecodeHints(DecodeHints hints) +Result& Result::setReaderOptions(const ReaderOptions& opts) { - if (hints.characterSet() != CharacterSet::Unknown) - _content.defaultCharset = hints.characterSet(); - _decodeHints = hints; + if (opts.characterSet() != CharacterSet::Unknown) + _content.defaultCharset = opts.characterSet(); + _readerOpts = opts; return *this; } diff --git a/core/src/Result.h b/core/src/Result.h index 9defbc5033..30a180f48e 100644 --- a/core/src/Result.h +++ b/core/src/Result.h @@ -10,7 +10,7 @@ #include "BarcodeFormat.h" #include "ByteArray.h" #include "Content.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Error.h" #include "Quadrilateral.h" #include "StructuredAppend.h" @@ -31,10 +31,10 @@ using Position = QuadrilateralI; class Result { void setIsInverted(bool v) { _isInverted = v; } - Result& setDecodeHints(DecodeHints hints); + Result& setReaderOptions(const ReaderOptions& opts); friend Result MergeStructuredAppendSequence(const std::vector& results); - friend std::vector ReadBarcodes(const ImageView&, const DecodeHints&); + friend std::vector ReadBarcodes(const ImageView&, const ReaderOptions&); friend void IncrementLineCount(Result&); public: @@ -68,7 +68,7 @@ class Result std::string text(TextMode mode) const; /** - * @brief text returns the bytes() content rendered to unicode/utf8 text accoring to the TextMode set in the DecodingHints + * @brief text returns the bytes() content rendered to unicode/utf8 text accoring to the TextMode set in the ReaderOptions */ std::string text() const; @@ -101,7 +101,7 @@ class Result bool isMirrored() const { return _isMirrored; } /** - * @brief isInverted is the symbol inverted / has reveresed reflectance (see DecodeHints::tryInvert) + * @brief isInverted is the symbol inverted / has reveresed reflectance (see ReaderOptions::tryInvert) */ bool isInverted() const { return _isInverted; } @@ -157,7 +157,7 @@ class Result Content _content; Error _error; Position _position; - DecodeHints _decodeHints; + ReaderOptions _readerOpts; StructuredAppendInfo _sai; BarcodeFormat _format = BarcodeFormat::None; char _ecLevel[4] = {}; diff --git a/core/src/aztec/AZReader.cpp b/core/src/aztec/AZReader.cpp index 2464925414..b9b36a7ba6 100644 --- a/core/src/aztec/AZReader.cpp +++ b/core/src/aztec/AZReader.cpp @@ -11,7 +11,7 @@ #include "AZDetector.h" #include "AZDetectorResult.h" #include "BinaryBitmap.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "DecoderResult.h" #include "Result.h" @@ -26,8 +26,8 @@ Reader::decode(const BinaryBitmap& image) const auto binImg = image.getBitMatrix(); if (binImg == nullptr) return {}; - - DetectorResult detectorResult = Detect(*binImg, _hints.isPure(), _hints.tryHarder()); + + DetectorResult detectorResult = Detect(*binImg, _opts.isPure(), _opts.tryHarder()); if (!detectorResult.isValid()) return {}; @@ -44,14 +44,14 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const auto binImg = image.getBitMatrix(); if (binImg == nullptr) return {}; - - auto detRess = Detect(*binImg, _hints.isPure(), _hints.tryHarder(), maxSymbols); + + auto detRess = Detect(*binImg, _opts.isPure(), _opts.tryHarder(), maxSymbols); Results results; for (auto&& detRes : detRess) { auto decRes = Decode(detRes).setReaderInit(detRes.readerInit()).setIsMirrored(detRes.isMirrored()).setVersionNumber(detRes.nbLayers()); - if (decRes.isValid(_hints.returnErrors())) { + if (decRes.isValid(_opts.returnErrors())) { results.emplace_back(std::move(decRes), std::move(detRes).position(), BarcodeFormat::Aztec); if (maxSymbols > 0 && Size(results) >= maxSymbols) break; diff --git a/core/src/datamatrix/DMReader.cpp b/core/src/datamatrix/DMReader.cpp index 0754a8411f..313f39993e 100644 --- a/core/src/datamatrix/DMReader.cpp +++ b/core/src/datamatrix/DMReader.cpp @@ -9,7 +9,7 @@ #include "BinaryBitmap.h" #include "DMDecoder.h" #include "DMDetector.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "DecoderResult.h" #include "DetectorResult.h" #include "Result.h" @@ -26,8 +26,8 @@ Result Reader::decode(const BinaryBitmap& image) const auto binImg = image.getBitMatrix(); if (binImg == nullptr) return {}; - - auto detectorResult = Detect(*binImg, _hints.tryHarder(), _hints.tryRotate(), _hints.isPure()); + + auto detectorResult = Detect(*binImg, _opts.tryHarder(), _opts.tryRotate(), _opts.isPure()); if (!detectorResult.isValid()) return {}; @@ -43,9 +43,9 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const return {}; Results results; - for (auto&& detRes : Detect(*binImg, _hints.tryHarder(), _hints.tryRotate(), _hints.isPure())) { + for (auto&& detRes : Detect(*binImg, _opts.tryHarder(), _opts.tryRotate(), _opts.isPure())) { auto decRes = Decode(detRes.bits()); - if (decRes.isValid(_hints.returnErrors())) { + if (decRes.isValid(_opts.returnErrors())) { results.emplace_back(std::move(decRes), std::move(detRes).position(), BarcodeFormat::DataMatrix); if (maxSymbols > 0 && Size(results) >= maxSymbols) break; diff --git a/core/src/maxicode/MCReader.cpp b/core/src/maxicode/MCReader.cpp index 6a41fc9b42..66f25aa3f8 100644 --- a/core/src/maxicode/MCReader.cpp +++ b/core/src/maxicode/MCReader.cpp @@ -8,7 +8,7 @@ #include "BinaryBitmap.h" #include "BitMatrix.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "DecoderResult.h" #include "MCBitMatrixParser.h" #include "MCDecoder.h" diff --git a/core/src/oned/ODCodabarReader.cpp b/core/src/oned/ODCodabarReader.cpp index f72b6ad44c..1d6121bc4c 100644 --- a/core/src/oned/ODCodabarReader.cpp +++ b/core/src/oned/ODCodabarReader.cpp @@ -7,7 +7,7 @@ #include "ODCodabarReader.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" #include "ZXAlgorithms.h" @@ -84,7 +84,7 @@ CodabarReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { // minimal number of characters that must be present (including start, stop and checksum characters) - int minCharCount = _hints.validateCode39CheckSum() ? 4 : 3; + int minCharCount = _opts.validateCode39CheckSum() ? 4 : 3; auto isStartOrStopSymbol = [](char c) { return c == '*'; }; // provide the indices with the narrow bars/spaces which have to be equally wide @@ -116,7 +116,7 @@ Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique return {}; Error error; - if (_hints.validateCode39CheckSum()) { + if (_opts.validateCode39CheckSum()) { auto checkDigit = txt.back(); txt.pop_back(); int checksum = TransformReduce(txt, 0, [](char c) { return IndexOf(ALPHABET, c); }); @@ -124,12 +124,12 @@ Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique error = ChecksumError(); } - if (!error && _hints.tryCode39ExtendedMode() && !DecodeExtendedCode39AndCode93(txt, "$%/+")) + if (!error && _opts.tryCode39ExtendedMode() && !DecodeExtendedCode39AndCode93(txt, "$%/+")) error = FormatError("Decoding extended Code39/Code93 failed"); // Symbology identifier modifiers ISO/IEC 16388:2007 Annex C Table C.1 constexpr const char symbologyModifiers[4] = { '0', '3' /*checksum*/, '4' /*extended*/, '7' /*checksum,extended*/ }; - SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)_hints.tryCode39ExtendedMode() * 2 + (int)_hints.validateCode39CheckSum()]}; + SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)_opts.tryCode39ExtendedMode() * 2 + (int)_opts.validateCode39CheckSum()]}; int xStop = next.pixelsTillEnd(); return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier, error); diff --git a/core/src/oned/ODITFReader.cpp b/core/src/oned/ODITFReader.cpp index eec3cf3799..439a1933dd 100644 --- a/core/src/oned/ODITFReader.cpp +++ b/core/src/oned/ODITFReader.cpp @@ -6,7 +6,7 @@ #include "ODITFReader.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "GTIN.h" #include "Result.h" #include "ZXAlgorithms.h" @@ -66,14 +66,14 @@ Result ITFReader::decodePattern(int rowNumber, PatternView& next, std::unique_pt return {}; Error error; - if (_hints.validateITFCheckSum() && !GTIN::IsCheckDigitValid(txt)) + if (_opts.validateITFCheckSum() && !GTIN::IsCheckDigitValid(txt)) error = ChecksumError(); // Symbology identifier ISO/IEC 16390:2007 Annex C Table C.1 // See also GS1 General Specifications 5.1.3 Figure 5.1.3-2 SymbologyIdentifier symbologyIdentifier = {'I', '0'}; // No check character validation - - if (_hints.validateITFCheckSum() || (txt.size() == 14 && GTIN::IsCheckDigitValid(txt))) // If no hint test if valid ITF-14 + + if (_opts.validateITFCheckSum() || (txt.size() == 14 && GTIN::IsCheckDigitValid(txt))) // If no hint test if valid ITF-14 symbologyIdentifier.modifier = '1'; // Modulo 10 symbol check character validated and transmitted int xStop = next.pixelsTillEnd(); diff --git a/core/src/oned/ODMultiUPCEANReader.cpp b/core/src/oned/ODMultiUPCEANReader.cpp index 07af59dfa2..738268e01d 100644 --- a/core/src/oned/ODMultiUPCEANReader.cpp +++ b/core/src/oned/ODMultiUPCEANReader.cpp @@ -9,7 +9,7 @@ #include "BarcodeFormat.h" #include "BitArray.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "GTIN.h" #include "ODUPCEANCommon.h" #include "Result.h" @@ -272,10 +272,10 @@ Result MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::u PartialResult res; auto begin = next; - - if (!(((_hints.hasFormat(BarcodeFormat::EAN13 | BarcodeFormat::UPCA)) && EAN13(res, begin)) || - (_hints.hasFormat(BarcodeFormat::EAN8) && EAN8(res, begin)) || - (_hints.hasFormat(BarcodeFormat::UPCE) && UPCE(res, begin)))) + + if (!(((_opts.hasFormat(BarcodeFormat::EAN13 | BarcodeFormat::UPCA)) && EAN13(res, begin)) || + (_opts.hasFormat(BarcodeFormat::EAN8) && EAN8(res, begin)) || + (_opts.hasFormat(BarcodeFormat::UPCE) && UPCE(res, begin)))) return {}; Error error; @@ -285,13 +285,13 @@ Result MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::u // If UPC-A was a requested format and we detected a EAN-13 code with a leading '0', then we drop the '0' and call it // a UPC-A code. // TODO: this is questionable - if (_hints.hasFormat(BarcodeFormat::UPCA) && res.format == BarcodeFormat::EAN13 && res.txt.front() == '0') { + if (_opts.hasFormat(BarcodeFormat::UPCA) && res.format == BarcodeFormat::EAN13 && res.txt.front() == '0') { res.txt = res.txt.substr(1); res.format = BarcodeFormat::UPCA; } // if we explicitly requested UPCA but not EAN13, don't return an EAN13 symbol - if (res.format == BarcodeFormat::EAN13 && ! _hints.hasFormat(BarcodeFormat::EAN13)) + if (res.format == BarcodeFormat::EAN13 && ! _opts.hasFormat(BarcodeFormat::EAN13)) return {}; // Symbology identifier modifiers ISO/IEC 15420:2009 Annex B Table B.1 @@ -303,7 +303,7 @@ Result MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::u auto ext = res.end; PartialResult addOnRes; - if (_hints.eanAddOnSymbol() != EanAddOnSymbol::Ignore && ext.skipSymbol() && ext.skipSingle(static_cast(begin.sum() * 3.5)) + if (_opts.eanAddOnSymbol() != EanAddOnSymbol::Ignore && ext.skipSymbol() && ext.skipSingle(static_cast(begin.sum() * 3.5)) && (AddOn(addOnRes, ext, 5) || AddOn(addOnRes, ext, 2))) { // ISO/IEC 15420:2009 states that the content for "]E3" should be 15 or 18 digits, i.e. converted to EAN-13 // and extended with no separator, and that the content for "]E4" should be 8 digits, i.e. no add-on @@ -313,8 +313,8 @@ Result MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::u if (res.format != BarcodeFormat::EAN8) // Keeping EAN-8 with add-on as "]E4" symbologyIdentifier.modifier = '3'; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on } - - if (_hints.eanAddOnSymbol() == EanAddOnSymbol::Require && !addOnRes.isValid()) + + if (_opts.eanAddOnSymbol() == EanAddOnSymbol::Require && !addOnRes.isValid()) return {}; return Result(res.txt, rowNumber, begin.pixelsInFront(), next.pixelsTillEnd(), res.format, symbologyIdentifier, error); diff --git a/core/src/oned/ODReader.cpp b/core/src/oned/ODReader.cpp index e928d04f77..07f6a62533 100644 --- a/core/src/oned/ODReader.cpp +++ b/core/src/oned/ODReader.cpp @@ -8,7 +8,7 @@ #include "ODReader.h" #include "BinaryBitmap.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "ODCodabarReader.h" #include "ODCode128Reader.h" #include "ODCode39Reader.h" @@ -38,29 +38,29 @@ void IncrementLineCount(Result& r) namespace ZXing::OneD { -Reader::Reader(const DecodeHints& hints) : ZXing::Reader(hints) +Reader::Reader(const ReaderOptions& opts) : ZXing::Reader(opts) { _readers.reserve(8); - auto formats = hints.formats().empty() ? BarcodeFormat::Any : hints.formats(); + auto formats = opts.formats().empty() ? BarcodeFormat::Any : opts.formats(); if (formats.testFlags(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::EAN8 | BarcodeFormat::UPCE)) - _readers.emplace_back(new MultiUPCEANReader(hints)); + _readers.emplace_back(new MultiUPCEANReader(opts)); if (formats.testFlag(BarcodeFormat::Code39)) - _readers.emplace_back(new Code39Reader(hints)); + _readers.emplace_back(new Code39Reader(opts)); if (formats.testFlag(BarcodeFormat::Code93)) - _readers.emplace_back(new Code93Reader(hints)); + _readers.emplace_back(new Code93Reader(opts)); if (formats.testFlag(BarcodeFormat::Code128)) - _readers.emplace_back(new Code128Reader(hints)); + _readers.emplace_back(new Code128Reader(opts)); if (formats.testFlag(BarcodeFormat::ITF)) - _readers.emplace_back(new ITFReader(hints)); + _readers.emplace_back(new ITFReader(opts)); if (formats.testFlag(BarcodeFormat::Codabar)) - _readers.emplace_back(new CodabarReader(hints)); + _readers.emplace_back(new CodabarReader(opts)); if (formats.testFlags(BarcodeFormat::DataBar)) - _readers.emplace_back(new DataBarReader(hints)); + _readers.emplace_back(new DataBarReader(opts)); if (formats.testFlags(BarcodeFormat::DataBarExpanded)) - _readers.emplace_back(new DataBarExpandedReader(hints)); + _readers.emplace_back(new DataBarExpandedReader(opts)); } Reader::~Reader() = default; @@ -255,21 +255,21 @@ Result Reader::decode(const BinaryBitmap& image) const { auto result = - DoDecode(_readers, image, _hints.tryHarder(), false, _hints.isPure(), 1, _hints.minLineCount(), _hints.returnErrors()); - - if (result.empty() && _hints.tryRotate()) - result = DoDecode(_readers, image, _hints.tryHarder(), true, _hints.isPure(), 1, _hints.minLineCount(), _hints.returnErrors()); + DoDecode(_readers, image, _opts.tryHarder(), false, _opts.isPure(), 1, _opts.minLineCount(), _opts.returnErrors()); + + if (result.empty() && _opts.tryRotate()) + result = DoDecode(_readers, image, _opts.tryHarder(), true, _opts.isPure(), 1, _opts.minLineCount(), _opts.returnErrors()); return FirstOrDefault(std::move(result)); } Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const { - auto resH = DoDecode(_readers, image, _hints.tryHarder(), false, _hints.isPure(), maxSymbols, _hints.minLineCount(), - _hints.returnErrors()); - if ((!maxSymbols || Size(resH) < maxSymbols) && _hints.tryRotate()) { - auto resV = DoDecode(_readers, image, _hints.tryHarder(), true, _hints.isPure(), maxSymbols - Size(resH), - _hints.minLineCount(), _hints.returnErrors()); + auto resH = DoDecode(_readers, image, _opts.tryHarder(), false, _opts.isPure(), maxSymbols, _opts.minLineCount(), + _opts.returnErrors()); + if ((!maxSymbols || Size(resH) < maxSymbols) && _opts.tryRotate()) { + auto resV = DoDecode(_readers, image, _opts.tryHarder(), true, _opts.isPure(), maxSymbols - Size(resH), + _opts.minLineCount(), _opts.returnErrors()); resH.insert(resH.end(), resV.begin(), resV.end()); } return resH; diff --git a/core/src/oned/ODReader.h b/core/src/oned/ODReader.h index ab2b5866a8..647452ddcf 100644 --- a/core/src/oned/ODReader.h +++ b/core/src/oned/ODReader.h @@ -13,7 +13,7 @@ namespace ZXing { -class DecodeHints; +class ReaderOptions; namespace OneD { @@ -22,7 +22,7 @@ class RowReader; class Reader : public ZXing::Reader { public: - explicit Reader(const DecodeHints& hints); + explicit Reader(const ReaderOptions& opts); ~Reader() override; Result decode(const BinaryBitmap& image) const override; diff --git a/core/src/oned/ODRowReader.h b/core/src/oned/ODRowReader.h index 837037528a..7e3e2e932a 100644 --- a/core/src/oned/ODRowReader.h +++ b/core/src/oned/ODRowReader.h @@ -40,7 +40,7 @@ RSSExp.: v?-74d/?-41c namespace ZXing { -class DecodeHints; +class ReaderOptions; namespace OneD { @@ -51,11 +51,11 @@ namespace OneD { class RowReader { protected: - const DecodeHints& _hints; + const ReaderOptions& _opts; public: - explicit RowReader(const DecodeHints& hints) : _hints(hints) {} - explicit RowReader(DecodeHints&& hints) = delete; + explicit RowReader(const ReaderOptions& opts) : _opts(opts) {} + explicit RowReader(ReaderOptions&&) = delete; struct DecodingState { diff --git a/core/src/pdf417/PDFReader.cpp b/core/src/pdf417/PDFReader.cpp index dd77c7cc1f..27d943c5e0 100644 --- a/core/src/pdf417/PDFReader.cpp +++ b/core/src/pdf417/PDFReader.cpp @@ -9,7 +9,7 @@ #include "PDFDetector.h" #include "PDFScanningDecoder.h" #include "PDFCodewordDecoder.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "DecoderResult.h" #include "Result.h" @@ -298,20 +298,20 @@ static Result DecodePure(const BinaryBitmap& image_) Result Reader::decode(const BinaryBitmap& image) const { - if (_hints.isPure()) { + if (_opts.isPure()) { auto res = DecodePure(image); if (res.error() != Error::Checksum) return res; // This falls through and tries the non-pure code path if we have a checksum error. This approach is // currently the best option to deal with 'aliased' input like e.g. 03-aliased.png } - - return FirstOrDefault(DoDecode(image, false, _hints.tryRotate(), _hints.returnErrors())); + + return FirstOrDefault(DoDecode(image, false, _opts.tryRotate(), _opts.returnErrors())); } Results Reader::decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { - return DoDecode(image, true, _hints.tryRotate(), _hints.returnErrors()); + return DoDecode(image, true, _opts.tryRotate(), _opts.returnErrors()); } } // Pdf417 diff --git a/core/src/qrcode/QRReader.cpp b/core/src/qrcode/QRReader.cpp index 8acbef58fe..e3bbda0307 100644 --- a/core/src/qrcode/QRReader.cpp +++ b/core/src/qrcode/QRReader.cpp @@ -9,7 +9,7 @@ #include "BinaryBitmap.h" #include "ConcentricFinder.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "DecoderResult.h" #include "DetectorResult.h" #include "LogMatrix.h" @@ -24,7 +24,7 @@ namespace ZXing::QRCode { Result Reader::decode(const BinaryBitmap& image) const { #if 1 - if (!_hints.isPure()) + if (!_opts.isPure()) return FirstOrDefault(decode(image, 1)); #endif @@ -33,11 +33,11 @@ Result Reader::decode(const BinaryBitmap& image) const return {}; DetectorResult detectorResult; - if (_hints.hasFormat(BarcodeFormat::QRCode)) + if (_opts.hasFormat(BarcodeFormat::QRCode)) detectorResult = DetectPureQR(*binImg); - if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !detectorResult.isValid()) + if (_opts.hasFormat(BarcodeFormat::MicroQRCode) && !detectorResult.isValid()) detectorResult = DetectPureMQR(*binImg); - if (_hints.hasFormat(BarcodeFormat::RMQRCode) && !detectorResult.isValid()) + if (_opts.hasFormat(BarcodeFormat::RMQRCode) && !detectorResult.isValid()) detectorResult = DetectPureRMQR(*binImg); if (!detectorResult.isValid()) @@ -76,8 +76,8 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const #ifdef PRINT_DEBUG LogMatrixWriter lmw(log, *binImg, 5, "qr-log.pnm"); #endif - - auto allFPs = FindFinderPatterns(*binImg, _hints.tryHarder()); + + auto allFPs = FindFinderPatterns(*binImg, _opts.tryHarder()); #ifdef PRINT_DEBUG printf("allFPs: %d\n", Size(allFPs)); @@ -85,8 +85,8 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const std::vector usedFPs; Results results; - - if (_hints.hasFormat(BarcodeFormat::QRCode)) { + + if (_opts.hasFormat(BarcodeFormat::QRCode)) { auto allFPSets = GenerateFinderPatternSets(allFPs); for (const auto& fpSet : allFPSets) { if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr)) @@ -103,7 +103,7 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const usedFPs.push_back(fpSet.tl); usedFPs.push_back(fpSet.tr); } - if (decoderResult.isValid(_hints.returnErrors())) { + if (decoderResult.isValid(_opts.returnErrors())) { results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::QRCode); if (maxSymbols && Size(results) == maxSymbols) break; @@ -111,8 +111,8 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const } } } - - if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { + + if (_opts.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { for (const auto& fp : allFPs) { if (Contains(usedFPs, fp)) continue; @@ -121,7 +121,7 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const if (detectorResult.isValid()) { auto decoderResult = Decode(detectorResult.bits()); auto position = detectorResult.position(); - if (decoderResult.isValid(_hints.returnErrors())) { + if (decoderResult.isValid(_opts.returnErrors())) { results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::MicroQRCode); if (maxSymbols && Size(results) == maxSymbols) break; @@ -130,8 +130,8 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const } } } - - if (_hints.hasFormat(BarcodeFormat::RMQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { + + if (_opts.hasFormat(BarcodeFormat::RMQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { // TODO proper for (const auto& fp : allFPs) { if (Contains(usedFPs, fp)) @@ -141,7 +141,7 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const if (detectorResult.isValid()) { auto decoderResult = Decode(detectorResult.bits()); auto position = detectorResult.position(); - if (decoderResult.isValid(_hints.returnErrors())) { + if (decoderResult.isValid(_opts.returnErrors())) { results.emplace_back(std::move(decoderResult), std::move(position), BarcodeFormat::RMQRCode); if (maxSymbols && Size(results) == maxSymbols) break; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 1617f14ede..7dd5234333 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -212,7 +212,7 @@ static std::vector getImagesInDirectory(const fs::path& directory) } static void doRunTests(const fs::path& directory, std::string_view format, int totalTests, const std::vector& tests, - DecodeHints hints) + ReaderOptions opts) { auto imgPaths = getImagesInDirectory(directory); auto folderName = directory.stem(); @@ -228,17 +228,17 @@ static void doRunTests(const fs::path& directory, std::string_view format, int t if (tc.name.empty()) break; auto startTime = std::chrono::steady_clock::now(); - hints.setTryDownscale(tc.name == "slow_"); - hints.setDownscaleFactor(2); - hints.setDownscaleThreshold(180); - hints.setTryHarder(tc.name == "slow"); - hints.setTryRotate(tc.name == "slow"); - hints.setTryInvert(tc.name == "slow"); - hints.setIsPure(tc.name == "pure"); - if (hints.isPure()) - hints.setBinarizer(Binarizer::FixedThreshold); + opts.setTryDownscale(tc.name == "slow_"); + opts.setDownscaleFactor(2); + opts.setDownscaleThreshold(180); + opts.setTryHarder(tc.name == "slow"); + opts.setTryRotate(tc.name == "slow"); + opts.setTryInvert(tc.name == "slow"); + opts.setIsPure(tc.name == "pure"); + if (opts.isPure()) + opts.setBinarizer(Binarizer::FixedThreshold); for (const auto& imgPath : imgPaths) { - auto result = ReadBarcode(ImageLoader::load(imgPath).rotated(test.rotation), hints); + auto result = ReadBarcode(ImageLoader::load(imgPath).rotated(test.rotation), opts); if (result.isValid()) { auto error = checkResult(imgPath, format, result); if (!error.empty()) @@ -262,7 +262,7 @@ static Result readMultiple(const std::vector& imgPaths, std::string_vi Results allResults; for (const auto& imgPath : imgPaths) { auto results = ReadBarcodes(ImageLoader::load(imgPath), - DecodeHints().setFormats(BarcodeFormatFromString(format)).setTryDownscale(false)); + ReaderOptions().setFormats(BarcodeFormatFromString(format)).setTryDownscale(false)); allResults.insert(allResults.end(), results.begin(), results.end()); } @@ -318,9 +318,9 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }; auto runTests = [&](std::string_view directory, std::string_view format, int total, - const std::vector& tests, const DecodeHints& hints = DecodeHints()) { + const std::vector& tests, const ReaderOptions& opts = ReaderOptions()) { if (hasTest(directory)) - doRunTests(testPathPrefix / directory, format, total, tests, hints); + doRunTests(testPathPrefix / directory, format, total, tests, opts); }; auto runStructuredAppendTest = [&](std::string_view directory, std::string_view format, int total, @@ -397,7 +397,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("code39-2", "Code39", 2, { { 2, 2, 0 }, { 2, 2, 180 }, - }, DecodeHints().setTryCode39ExtendedMode(true)); + }, ReaderOptions().setTryCode39ExtendedMode(true)); runTests("code39-3", "Code39", 12, { { 12, 12, 0 }, @@ -453,7 +453,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("ean13-extension-1", "EAN-13", 5, { { 3, 5, 0 }, { 3, 5, 180 }, - }, DecodeHints().setEanAddOnSymbol(EanAddOnSymbol::Require)); + }, ReaderOptions().setEanAddOnSymbol(EanAddOnSymbol::Require)); runTests("itf-1", "ITF", 11, { { 10, 11, 0 }, @@ -501,7 +501,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("upca-extension-1", "UPC-A", 6, { { 4, 4, 0 }, { 3, 4, 180 }, - }, DecodeHints().setEanAddOnSymbol(EanAddOnSymbol::Require)); + }, ReaderOptions().setEanAddOnSymbol(EanAddOnSymbol::Require)); runTests("upce-1", "UPC-E", 3, { { 3, 3, 0 }, diff --git a/test/fuzz/fuzzReadLinear.cpp b/test/fuzz/fuzzReadLinear.cpp index 03d529ecb6..6f05c9d1c8 100644 --- a/test/fuzz/fuzzReadLinear.cpp +++ b/test/fuzz/fuzzReadLinear.cpp @@ -14,7 +14,7 @@ #include "oned/ODDataBarExpandedReader.h" #include "oned/ODITFReader.h" #include "oned/ODCodabarReader.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" using namespace ZXing; diff --git a/test/unit/ThresholdBinarizerTest.cpp b/test/unit/ThresholdBinarizerTest.cpp index d49e51d9cf..0755696cf2 100644 --- a/test/unit/ThresholdBinarizerTest.cpp +++ b/test/unit/ThresholdBinarizerTest.cpp @@ -44,7 +44,7 @@ TEST(ThresholdBinarizerTest, PatternRowClear) { std::string bitstream; BitMatrix bits; - DecodeHints hints; + ReaderOptions opts; std::vector buf; // Test that ThresholdBinarizer::getPatternRow() clears row first (same as GlobalHistogramBinarizer) @@ -94,9 +94,9 @@ TEST(ThresholdBinarizerTest, PatternRowClear) "01000111000101111010011000000000101011110100111000010"; bits = ParseBitMatrix(bitstream, 53 /*width*/); - hints.setFormats(BarcodeFormat::DataBarExpanded); - hints.setMinLineCount(1); - OneD::Reader reader(hints); + opts.setFormats(BarcodeFormat::DataBarExpanded); + opts.setMinLineCount(1); + OneD::Reader reader(opts); Result result = reader.decode(ThresholdBinarizer(getImageView(buf, bits), 0x7F)); EXPECT_TRUE(result.isValid()); diff --git a/test/unit/oned/ODCodaBarWriterTest.cpp b/test/unit/oned/ODCodaBarWriterTest.cpp index 471ac52473..c49ca3d880 100644 --- a/test/unit/oned/ODCodaBarWriterTest.cpp +++ b/test/unit/oned/ODCodaBarWriterTest.cpp @@ -6,7 +6,7 @@ #include "oned/ODCodabarWriter.h" #include "BitMatrixIO.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" #include "oned/ODCodabarReader.h" @@ -50,9 +50,9 @@ TEST(ODCodaBarWriterTest, FullCircle) { std::string text = "A0123456789-$:/.+A"; auto matrix = CodabarWriter().encode(text, 0, 0); - auto hints = DecodeHints().setReturnCodabarStartEnd(true); + auto opts = ReaderOptions().setReturnCodabarStartEnd(true); - Result res = OneD::DecodeSingleRow(CodabarReader(hints), matrix.row(0)); + Result res = OneD::DecodeSingleRow(CodabarReader(opts), matrix.row(0)); EXPECT_EQ(text, res.text()); } diff --git a/test/unit/oned/ODCode128ReaderTest.cpp b/test/unit/oned/ODCode128ReaderTest.cpp index b1b122eaab..1c68d94357 100644 --- a/test/unit/oned/ODCode128ReaderTest.cpp +++ b/test/unit/oned/ODCode128ReaderTest.cpp @@ -5,7 +5,7 @@ #include "oned/ODCode128Reader.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" #include "gtest/gtest.h" @@ -26,8 +26,8 @@ static Result parse(const int startPattern, PatternRow row) row.insert(row.end(), { 2, 3, 3, 1, 1, 1, 2, 0 }); // Stop pattern std::unique_ptr state; - DecodeHints hints; - Code128Reader reader(hints); + ReaderOptions opts; + Code128Reader reader(opts); PatternView next(row); return reader.decodePattern(0, next, state); } diff --git a/test/unit/oned/ODCode128WriterTest.cpp b/test/unit/oned/ODCode128WriterTest.cpp index d89ac42ea6..7c7291b3bc 100644 --- a/test/unit/oned/ODCode128WriterTest.cpp +++ b/test/unit/oned/ODCode128WriterTest.cpp @@ -6,7 +6,7 @@ #include "oned/ODCode128Writer.h" #include "BitMatrixIO.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" #include "oned/ODCode128Reader.h" @@ -37,8 +37,8 @@ static std::string LineMatrixToString(const BitMatrix& matrix) static ZXing::Result Decode(const BitMatrix &matrix) { - DecodeHints hints; - return DecodeSingleRow(Code128Reader(hints), matrix.row(0)); + ReaderOptions opts; + return DecodeSingleRow(Code128Reader(opts), matrix.row(0)); } TEST(ODCode128Writer, EncodeWithFunc1) diff --git a/test/unit/oned/ODCode39ExtendedModeTest.cpp b/test/unit/oned/ODCode39ExtendedModeTest.cpp index b7af0c6918..589880a617 100644 --- a/test/unit/oned/ODCode39ExtendedModeTest.cpp +++ b/test/unit/oned/ODCode39ExtendedModeTest.cpp @@ -6,7 +6,7 @@ #include "BitArray.h" #include "BitArrayUtility.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" #include "oned/ODCode39Reader.h" @@ -17,9 +17,9 @@ using namespace ZXing::OneD; static std::string Decode(std::string_view encoded) { - auto hints = DecodeHints().setTryCode39ExtendedMode(true); + auto opts = ReaderOptions().setTryCode39ExtendedMode(true); BitArray row = Utility::ParseBitArray(encoded, '1'); - Result result = DecodeSingleRow(Code39Reader(hints), row.range()); + Result result = DecodeSingleRow(Code39Reader(opts), row.range()); return result.text(TextMode::Plain); } diff --git a/test/unit/oned/ODCode39ReaderTest.cpp b/test/unit/oned/ODCode39ReaderTest.cpp index e33f587a0c..a8684a7682 100644 --- a/test/unit/oned/ODCode39ReaderTest.cpp +++ b/test/unit/oned/ODCode39ReaderTest.cpp @@ -5,7 +5,7 @@ #include "oned/ODCode39Reader.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" #include "gtest/gtest.h" @@ -14,9 +14,9 @@ using namespace ZXing; using namespace ZXing::OneD; // Helper to call decodePattern() -static Result parse(PatternRow row, DecodeHints hints = {}) +static Result parse(PatternRow row, ReaderOptions opts = {}) { - Code39Reader reader(hints); + Code39Reader reader(opts); row.insert(row.begin(), { 0, 1, 2, 1, 1, 2, 1, 2, 1, 1, 0 }); row.insert(row.end(), { 0, 1, 2, 1, 1, 2, 1, 2, 1, 1, 0 }); @@ -38,7 +38,7 @@ TEST(ODCode39ReaderTest, SymbologyIdentifier) { // "A" with checksum PatternRow row({ 2, 1, 1, 1, 1, 2, 1, 1, 2, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2 }); - auto result = parse(row, DecodeHints().setValidateCode39CheckSum(true)); + auto result = parse(row, ReaderOptions().setValidateCode39CheckSum(true)); EXPECT_EQ(result.symbologyIdentifier(), "]A3"); EXPECT_EQ(result.text(), "A"); @@ -49,7 +49,7 @@ TEST(ODCode39ReaderTest, SymbologyIdentifier) { // Extended "a" PatternRow row({ 1, 2, 1, 1, 1, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2 }); - auto result = parse(row, DecodeHints().setTryCode39ExtendedMode(true)); + auto result = parse(row, ReaderOptions().setTryCode39ExtendedMode(true)); EXPECT_EQ(result.symbologyIdentifier(), "]A4"); EXPECT_EQ(result.text(), "a"); @@ -60,7 +60,7 @@ TEST(ODCode39ReaderTest, SymbologyIdentifier) { // Extended "a" with checksum PatternRow row({ 1, 2, 1, 1, 1, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2, 0, 2, 1, 1, 2, 1, 1, 2, 1, 1 }); - auto result = parse(row, DecodeHints().setTryCode39ExtendedMode(true).setValidateCode39CheckSum(true)); + auto result = parse(row, ReaderOptions().setTryCode39ExtendedMode(true).setValidateCode39CheckSum(true)); EXPECT_EQ(result.symbologyIdentifier(), "]A7"); EXPECT_EQ(result.text(), "a"); diff --git a/test/unit/oned/ODCode93ReaderTest.cpp b/test/unit/oned/ODCode93ReaderTest.cpp index 98b49de46e..58c09ea035 100644 --- a/test/unit/oned/ODCode93ReaderTest.cpp +++ b/test/unit/oned/ODCode93ReaderTest.cpp @@ -7,7 +7,7 @@ #include "oned/ODCode93Reader.h" #include "BitArray.h" #include "BitArrayUtility.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" #include "gtest/gtest.h" @@ -17,9 +17,9 @@ using namespace ZXing::OneD; static std::string Decode(std::string_view input) { - DecodeHints hints; + ReaderOptions opts; auto row = Utility::ParseBitArray(input, '1'); - auto result = DecodeSingleRow(Code93Reader(hints), row.range()); + auto result = DecodeSingleRow(Code93Reader(opts), row.range()); return result.text(TextMode::Plain); } diff --git a/test/unit/oned/ODDataBarReaderTest.cpp b/test/unit/oned/ODDataBarReaderTest.cpp index 70cbfacbb8..cab6b36876 100644 --- a/test/unit/oned/ODDataBarReaderTest.cpp +++ b/test/unit/oned/ODDataBarReaderTest.cpp @@ -5,7 +5,7 @@ #include "oned/ODDataBarReader.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "Result.h" #include "gtest/gtest.h" @@ -14,9 +14,9 @@ using namespace ZXing; using namespace ZXing::OneD; // Helper to call decodePattern() -static Result parse(PatternRow row, DecodeHints hints = {}) +static Result parse(PatternRow row, ReaderOptions opts = {}) { - DataBarReader reader(hints); + DataBarReader reader(opts); row.insert(row.begin(), { 1, 1 }); // Left guard row.insert(row.end(), { 1, 1 }); // Right guard diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index 601ef324a6..00c09963fe 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -65,7 +65,7 @@ char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) } /* - * ZXing/DecodeHints.h + * ZXing/ReaderOptions.h */ zxing_ReaderOptions* zxing_ReaderOptions_new() diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index 4a5dacca75..636dd433b4 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -12,7 +12,7 @@ #ifdef __cplusplus -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "ImageView.h" #include "Result.h" @@ -97,7 +97,7 @@ zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str); char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format); /* - * ZXing/DecodeHints.h + * ZXing/ReaderOptions.h */ typedef enum diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm index 973eeccb7b..7cfc8f7901 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm @@ -3,7 +3,7 @@ // SPDX-License-Identifier: Apache-2.0 #import "ZXIReaderOptions.h" -#import "DecodeHints.h" +#import "ReaderOptions.h" @interface ZXIReaderOptions() @property(nonatomic) ZXing::ReaderOptions cppOpts; diff --git a/wrappers/winrt/BarcodeReader.cpp b/wrappers/winrt/BarcodeReader.cpp index 9657d494d0..b3186024c1 100644 --- a/wrappers/winrt/BarcodeReader.cpp +++ b/wrappers/winrt/BarcodeReader.cpp @@ -11,7 +11,7 @@ #include "BarcodeReader.h" #include "BarcodeFormat.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include "ReadBarcode.h" #include "ReadResult.h" #include "Utf.h" diff --git a/wrappers/winrt/BarcodeReader.h b/wrappers/winrt/BarcodeReader.h index 68ad884150..16e8352130 100644 --- a/wrappers/winrt/BarcodeReader.h +++ b/wrappers/winrt/BarcodeReader.h @@ -6,7 +6,7 @@ #pragma once #include "BarcodeFormat.h" -#include "DecodeHints.h" +#include "ReaderOptions.h" #include From d3286829685548c4a464e5f7236073c8190c4b79 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 20:00:32 +0100 Subject: [PATCH 156/587] release: update version info to 2.2.0 --- core/CMakeLists.txt | 2 +- wrappers/android/zxingcpp/build.gradle.kts | 2 +- wrappers/python/setup.py | 2 +- zxing-cpp.podspec | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 589c37a86b..0fe3bb6abe 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project (ZXing VERSION "2.1.0") +project (ZXing VERSION "2.2.0") set (ZXING_SONAME 3) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 include(../zxing.cmake) diff --git a/wrappers/android/zxingcpp/build.gradle.kts b/wrappers/android/zxingcpp/build.gradle.kts index 9a6fe39117..7f1e43c99c 100644 --- a/wrappers/android/zxingcpp/build.gradle.kts +++ b/wrappers/android/zxingcpp/build.gradle.kts @@ -57,7 +57,7 @@ dependencies { } group = "io.github.zxing-cpp" -version = "2.1.0-SNAPSHOT" +version = "2.2.0" val javadocJar by tasks.registering(Jar::class) { archiveClassifier.set("javadoc") diff --git a/wrappers/python/setup.py b/wrappers/python/setup.py index 370b2e522c..f3c3b38c7d 100644 --- a/wrappers/python/setup.py +++ b/wrappers/python/setup.py @@ -59,7 +59,7 @@ def build_extension(self, ext): # "local_scheme": "no-local-version", # "tag_regex": "v?([0-9]+.[0-9]+.[0-9]+)", # }, - version='2.1.0', + version='2.2.0', description='Python bindings for the zxing-cpp barcode library', long_description=long_description, long_description_content_type="text/markdown", diff --git a/zxing-cpp.podspec b/zxing-cpp.podspec index 041934c779..4db8ff0c6e 100644 --- a/zxing-cpp.podspec +++ b/zxing-cpp.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = 'zxing-cpp' - s.version = '2.1.0' + s.version = '2.2.0' s.summary = 'C++ port of ZXing' s.homepage = 'https://github.com/zxing-cpp/zxing-cpp' s.author = 'axxel' From aac3af9146cfddf2619839257a2b80b78b9ae2b7 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 22:04:24 +0100 Subject: [PATCH 157/587] Update and rename python-build.yml to publish-python.yml switch from token to trusted publishing authentication and from testpypi to the real thing --- .../{python-build.yml => publish-python.yml} | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) rename .github/workflows/{python-build.yml => publish-python.yml} (88%) diff --git a/.github/workflows/python-build.yml b/.github/workflows/publish-python.yml similarity index 88% rename from .github/workflows/python-build.yml rename to .github/workflows/publish-python.yml index 2c67d28a69..fd937b8ea2 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/publish-python.yml @@ -73,9 +73,14 @@ jobs: path: wrappers/python/dist/*.tar.gz upload-pypi: - name: Deploy + name: Upload to PyPI needs: [build-wheels, build-sdist] runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/zxing-cpp + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing # only run if the commit is tagged... # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') if: github.event_name == 'release' || github.event.inputs.publish == 'y' @@ -86,7 +91,5 @@ jobs: path: dist - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.TEST_PYPI_TOKEN }} - repository_url: https://test.pypi.org/legacy/ +# with: +# repository-url: https://test.pypi.org/legacy/ From 7efe62516e81c782ccf8f3c8f71eace0ffe90da3 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 22:16:21 +0100 Subject: [PATCH 158/587] Update publish-python.yml make top-level name consistent with filename --- .github/workflows/publish-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index fd937b8ea2..637e91e557 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -1,4 +1,4 @@ -name: build-python-dist +name: publish-python on: release: From cfa0ad019d60c81a9b0ff038386b9153677ed4f5 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 22:20:34 +0100 Subject: [PATCH 159/587] python/README remove build+deploy badge --- wrappers/python/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/wrappers/python/README.md b/wrappers/python/README.md index 5366af17da..2d7df24bc4 100644 --- a/wrappers/python/README.md +++ b/wrappers/python/README.md @@ -1,6 +1,5 @@ # Python bindings for zxing-cpp -[![Build + Deploy](https://github.com/zxing-cpp/zxing-cpp/actions/workflows/python-build.yml/badge.svg)](https://github.com/zxing-cpp/zxing-cpp/actions/workflows/python-build.yml) [![PyPI](https://img.shields.io/pypi/v/zxing-cpp.svg)](https://pypi.org/project/zxing-cpp/) ## Installation From d940d91b021ea7cc083dca5584b9872b977df9f8 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 22:26:10 +0100 Subject: [PATCH 160/587] publish-android.yml: fix typo in gradle publish command --- .github/workflows/publish-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index 004b41caf9..eb789c3d26 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -28,4 +28,4 @@ jobs: ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} - run: ./gradlew publishRleasePublicationsToSonatypeRepository + run: ./gradlew publishReleasePublicationToSonatypeRepository From d3a8d5f793ed0c32e53283ccf079f390780491a0 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 9 Dec 2023 12:26:23 +0100 Subject: [PATCH 161/587] DecodeHints: provide c++ ABI backward compatibility with 2.1.0 This might end up in a 2.2.1 release. --- core/CMakeLists.txt | 1 + core/src/DecodeHints.cpp | 30 ++++++++++++++++++++++++++++++ core/src/ReaderOptions.h | 2 ++ 3 files changed, 33 insertions(+) create mode 100644 core/src/DecodeHints.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 0fe3bb6abe..f18067e46e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -105,6 +105,7 @@ if (BUILD_READERS) src/Content.h src/Content.cpp src/DecodeHints.h + src/DecodeHints.cpp src/DecoderResult.h src/DetectorResult.h src/Error.h diff --git a/core/src/DecodeHints.cpp b/core/src/DecodeHints.cpp new file mode 100644 index 0000000000..8dbd5a8685 --- /dev/null +++ b/core/src/DecodeHints.cpp @@ -0,0 +1,30 @@ +/* +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#define HIDE_DECODE_HINTS_ALIAS + +#include "ReadBarcode.h" + +namespace ZXing { + +// Provide a struct that is binary compatible with ReaderOptions and is actually called DecodeHints so that +// the compiler generates a correctly mangled pair of ReadBarcode(s) symbols to keep backward ABI compatibility. + +struct DecodeHints +{ + char data[sizeof(ReaderOptions)]; +}; + +Result ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) +{ + return ReadBarcode(image, reinterpret_cast(hints)); +} + +Results ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) +{ + return ReadBarcodes(image, reinterpret_cast(hints)); +} + +} // ZXing diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index e68d0592d7..3b4cabfa9e 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -172,6 +172,8 @@ class ReaderOptions bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); } }; +#ifndef HIDE_DECODE_HINTS_ALIAS using DecodeHints [[deprecated]] = ReaderOptions; +#endif } // ZXing From 99a83b3a6ac514d7f850dda7fa24cddb5120c7e2 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 11 Dec 2023 00:43:27 +0100 Subject: [PATCH 162/587] release: bump project version to 2.2.1 --- core/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index f18067e46e..e870fb9c66 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project (ZXing VERSION "2.2.0") +project (ZXing VERSION "2.2.1") set (ZXING_SONAME 3) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 include(../zxing.cmake) From befe74461d144b04e75d7ee14dcb666ca48e4ed9 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 11 Dec 2023 08:17:12 +0100 Subject: [PATCH 163/587] Qt: improve example code * fix moc warning * fix timeout logic in QML code * add TextMode, tryInvert and isPure properties --- example/ZXingQt5CamReader.qml | 13 +++++++++---- example/ZXingQt6CamReader.qml | 15 ++++++++++----- example/ZXingQtReader.cpp | 9 +++++---- example/ZXingQtReader.h | 22 +++++++++++++++++++++- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/example/ZXingQt5CamReader.qml b/example/ZXingQt5CamReader.qml index 467bb18a63..4b3191f884 100644 --- a/example/ZXingQt5CamReader.qml +++ b/example/ZXingQt5CamReader.qml @@ -22,7 +22,7 @@ Window { Timer { id: resetInfo - interval: 2000 + interval: 1000 } BarcodeReader { @@ -31,7 +31,9 @@ Window { formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) tryRotate: tryRotateSwitch.checked tryHarder: tryHarderSwitch.checked + tryInvert: tryInvertSwitch.checked tryDownscale: tryDownscaleSwitch.checked + textMode: ZXing.HRI // callback with parameter 'result', called for every successfully processed frame // onFoundBarcode: {} @@ -42,11 +44,13 @@ Window { ? [result.position.topLeft, result.position.topRight, result.position.bottomRight, result.position.bottomLeft] : nullPoints - if (result.isValid) + if (result.isValid) { resetInfo.restart() + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.contentTypeName).arg(result.runTime) + } - if (result.isValid || !resetInfo.running) - info.text = qsTr("Format: \t %1 \nText: \t %2 \nError: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.status).arg(result.runTime) + if (!resetInfo.running) + info.text = "No barcode found" // console.log(result) } @@ -138,6 +142,7 @@ Window { Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } + Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index ecb348e410..47b35574fd 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -22,7 +22,7 @@ Window { Timer { id: resetInfo - interval: 2000 + interval: 1000 } BarcodeReader { @@ -32,7 +32,9 @@ Window { formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) tryRotate: tryRotateSwitch.checked tryHarder: tryHarderSwitch.checked + tryInvert: tryInvertSwitch.checked tryDownscale: tryDownscaleSwitch.checked + textMode: ZXing.TextMode.HRI // callback with parameter 'result', called for every successfully processed frame // onFoundBarcode: {} @@ -43,11 +45,13 @@ Window { ? [result.position.topLeft, result.position.topRight, result.position.bottomRight, result.position.bottomLeft] : nullPoints - if (result.isValid) + if (result.isValid) { resetInfo.restart() + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.contentTypeName).arg(result.runTime) + } - if (result.isValid || !resetInfo.running) - info.text = qsTr("Format: \t %1 \nText: \t %2 \nError: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.status).arg(result.runTime) + if (!resetInfo.running) + info.text = "No barcode found" // console.log(result) } @@ -132,7 +136,7 @@ Window { Shape { id: polygon anchors.fill: parent - visible: control.points.length === 4 + visible: points.length === 4 ShapePath { strokeWidth: 3 @@ -175,6 +179,7 @@ Window { Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } + Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } diff --git a/example/ZXingQtReader.cpp b/example/ZXingQtReader.cpp index fe04192160..8b2174f602 100644 --- a/example/ZXingQtReader.cpp +++ b/example/ZXingQtReader.cpp @@ -18,19 +18,20 @@ int main(int argc, char* argv[]) QString filePath = argv[1]; - QImage fileImage = QImage(filePath); + QImage image = QImage(filePath); - if (fileImage.isNull()) { + if (image.isNull()) { qDebug() << "Could not load the filename as an image:" << filePath; return 1; } auto options = ReaderOptions() - .setFormats(BarcodeFormat::Any) + .setFormats(BarcodeFormat::MatrixCodes) .setTryRotate(false) + .setTextMode(TextMode::HRI) .setMaxNumberOfSymbols(10); - auto results = ReadBarcodes(fileImage, options); + auto results = ReadBarcodes(image, options); for (auto& result : results) { qDebug() << "Text: " << result.text(); diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index 1c11c0f50c..0b26625f7a 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -59,9 +59,12 @@ enum class BarcodeFormat enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; +enum class TextMode { Plain, ECI, HRI, Hex, Escaped }; + #else using ZXing::BarcodeFormat; using ZXing::ContentType; +using ZXing::TextMode; #endif using ZXing::ReaderOptions; @@ -70,6 +73,7 @@ using ZXing::BarcodeFormats; Q_ENUM_NS(BarcodeFormat) Q_ENUM_NS(ContentType) +Q_ENUM_NS(TextMode) template QDebug operator<<(QDebug dbg, const T& v) @@ -102,6 +106,7 @@ class Result : private ZXing::Result Q_PROPERTY(QByteArray bytes READ bytes) Q_PROPERTY(bool isValid READ isValid) Q_PROPERTY(ContentType contentType READ contentType) + Q_PROPERTY(QString contentTypeName READ contentTypeName) Q_PROPERTY(Position position READ position) QString _text; @@ -124,6 +129,7 @@ class Result : private ZXing::Result BarcodeFormat format() const { return static_cast(ZXing::Result::format()); } ContentType contentType() const { return static_cast(ZXing::Result::contentType()); } QString formatName() const { return QString::fromStdString(ZXing::ToString(ZXing::Result::format())); } + QString contentTypeName() const { return QString::fromStdString(ZXing::ToString(ZXing::Result::contentType())); } const QString& text() const { return _text; } const QByteArray& bytes() const { return _bytes; } const Position& position() const { return _position; } @@ -347,9 +353,22 @@ class BarcodeReader : public QObject, private ReaderOptions } Q_SIGNAL void formatsChanged(); + Q_PROPERTY(TextMode textMode READ textMode WRITE setTextMode NOTIFY textModeChanged) + TextMode textMode() const noexcept { return static_cast(ReaderOptions::textMode()); } + Q_SLOT void setTextMode(TextMode newVal) + { + if (textMode() != newVal) { + ReaderOptions::setTextMode(static_cast(newVal)); + emit textModeChanged(); + } + } + Q_SIGNAL void textModeChanged(); + ZQ_PROPERTY(bool, tryRotate, setTryRotate) ZQ_PROPERTY(bool, tryHarder, setTryHarder) + ZQ_PROPERTY(bool, tryInvert, setTryInvert) ZQ_PROPERTY(bool, tryDownscale, setTryDownscale) + ZQ_PROPERTY(bool, isPure, setIsPure) public slots: ZXingQt::Result process(const QVideoFrame& image) @@ -389,7 +408,7 @@ public slots: _sink = sink; connect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::process); } - Q_PROPERTY(QVideoSink* videoSink WRITE setVideoSink) + Q_PROPERTY(QVideoSink* videoSink MEMBER _sink WRITE setVideoSink) #endif }; @@ -435,6 +454,7 @@ inline void registerQmlAndMetaTypes() { qRegisterMetaType("BarcodeFormat"); qRegisterMetaType("ContentType"); + qRegisterMetaType("TextMode"); // supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name // but then the qml side complains about "unregistered type" From 377d7bf6800ad08edb16bc44ac68987d5953ab7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20M=C3=A9rino?= Date: Tue, 12 Dec 2023 13:18:48 +0100 Subject: [PATCH 164/587] Add support for DX Film Edge read (#684) * Add support for DX Film Edge read * DXFilmEdge: compress samples images --------- Co-authored-by: axxel --- README.md | 6 +- core/CMakeLists.txt | 2 + core/src/BarcodeFormat.cpp | 1 + core/src/BarcodeFormat.h | 7 +- core/src/oned/ODDXFilmEdgeReader.cpp | 380 ++++++++++++++++++ core/src/oned/ODDXFilmEdgeReader.h | 26 ++ core/src/oned/ODReader.cpp | 3 + test/blackbox/BlackboxTestRunner.cpp | 7 + test/samples/dxfilmedge-1/1.jpg | Bin 0 -> 25235 bytes test/samples/dxfilmedge-1/1.txt | 1 + test/samples/dxfilmedge-1/2.png | Bin 0 -> 16300 bytes test/samples/dxfilmedge-1/2.txt | 1 + test/samples/dxfilmedge-1/3.jpg | Bin 0 -> 1652 bytes test/samples/dxfilmedge-1/3.txt | 1 + .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 1 + .../src/main/java/zxingcpp/BarcodeReader.kt | 2 +- wrappers/c/zxing-c.h | 5 +- wrappers/ios/Sources/Wrapper/ZXIFormat.h | 2 +- .../ios/Sources/Wrapper/ZXIFormatHelper.mm | 4 + wrappers/python/zxing.cpp | 1 + wrappers/winrt/BarcodeReader.cpp | 2 + wrappers/winrt/BarcodeReader.h | 1 + 22 files changed, 443 insertions(+), 10 deletions(-) create mode 100644 core/src/oned/ODDXFilmEdgeReader.cpp create mode 100644 core/src/oned/ODDXFilmEdgeReader.h create mode 100644 test/samples/dxfilmedge-1/1.jpg create mode 100644 test/samples/dxfilmedge-1/1.txt create mode 100644 test/samples/dxfilmedge-1/2.png create mode 100644 test/samples/dxfilmedge-1/2.txt create mode 100644 test/samples/dxfilmedge-1/3.jpg create mode 100644 test/samples/dxfilmedge-1/3.txt diff --git a/README.md b/README.md index 1054cb2fae..826388d960 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ Thanks a lot for your contribution! | EAN-8 | Code 128 | rMQR Code | | EAN-13 | Codabar | Aztec | | DataBar | DataBar Expanded | DataMatrix | -| | ITF | PDF417 | -| | | MaxiCode (partial) | +| | DX Film Edge | PDF417 | +| | ITF | MaxiCode (partial) | [Note:] * DataBar used to be called RSS. - * DataBar, MaxiCode, Micro QR Code and rMQR Code are not supported for writing. + * DataBar, DX Film Edge, MaxiCode, Micro QR Code and rMQR Code are not supported for writing. * Building with C++20 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behaviour of the library: it then supports multi-symbol and position independent detection for DataMatrix. This comes at a noticable performace cost. C++20 is enabled by default for the Android, iOS, Python and WASM wrappers. ## Getting Started diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index e870fb9c66..4e2135e7d2 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -289,6 +289,8 @@ if (BUILD_READERS) src/oned/ODDataBarExpandedBitDecoder.cpp src/oned/ODDataBarExpandedReader.h src/oned/ODDataBarExpandedReader.cpp + src/oned/ODDXFilmEdgeReader.h + src/oned/ODDXFilmEdgeReader.cpp src/oned/ODITFReader.h src/oned/ODITFReader.cpp src/oned/ODMultiUPCEANReader.h diff --git a/core/src/BarcodeFormat.cpp b/core/src/BarcodeFormat.cpp index 01f4d3860e..b43917764e 100644 --- a/core/src/BarcodeFormat.cpp +++ b/core/src/BarcodeFormat.cpp @@ -32,6 +32,7 @@ static BarcodeFormatName NAMES[] = { {BarcodeFormat::DataBar, "DataBar"}, {BarcodeFormat::DataBarExpanded, "DataBarExpanded"}, {BarcodeFormat::DataMatrix, "DataMatrix"}, + {BarcodeFormat::DXFilmEdge, "DXFilmEdge"}, {BarcodeFormat::EAN8, "EAN-8"}, {BarcodeFormat::EAN13, "EAN-13"}, {BarcodeFormat::ITF, "ITF"}, diff --git a/core/src/BarcodeFormat.h b/core/src/BarcodeFormat.h index 72542ed363..f2d56edad7 100644 --- a/core/src/BarcodeFormat.h +++ b/core/src/BarcodeFormat.h @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors */ @@ -40,12 +40,13 @@ enum class BarcodeFormat UPCE = (1 << 15), ///< UPC-E MicroQRCode = (1 << 16), ///< Micro QR Code RMQRCode = (1 << 17), ///< Rectangular Micro QR Code + DXFilmEdge = (1 << 18), ///< DX Film Edge Barcode - LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE, + LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DXFilmEdge | UPCA | UPCE, MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, Any = LinearCodes | MatrixCodes, - _max = RMQRCode, ///> implementation detail, don't use + _max = DXFilmEdge, ///> implementation detail, don't use }; ZX_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) diff --git a/core/src/oned/ODDXFilmEdgeReader.cpp b/core/src/oned/ODDXFilmEdgeReader.cpp new file mode 100644 index 0000000000..f7647bd05d --- /dev/null +++ b/core/src/oned/ODDXFilmEdgeReader.cpp @@ -0,0 +1,380 @@ +/* + * Copyright 2023 Antoine Mérino + */ +// SPDX-License-Identifier: Apache-2.0 + +#include "DecodeHints.h" +#include "GTIN.h" +#include "ODDXFilmEdgeReader.h" +#include "Result.h" +#include "ZXAlgorithms.h" + +#include +#include +namespace ZXing::OneD { + +// Detection is made from center to bottom. +// We ensure the clock signal is decoded before the data signal to avoid false positives. +// They are two version of a DX Edge codes : without half-frame information and with half-frame information. +// The clock signal is longer if the DX code contains the half-frame information (more recent version) +constexpr int CLOCK_PATTERN_LENGTH_HF = 31; +constexpr int CLOCK_PATTERN_LENGTH_NO_HF = 23; +constexpr int DATA_START_PATTERN_SIZE = 5; +constexpr auto CLOCK_PATTERN_COMMON = FixedPattern<15, 20> {5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; +constexpr auto CLOCK_PATTERN_HF = + FixedPattern<25, CLOCK_PATTERN_LENGTH_HF>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}; +constexpr auto CLOCK_PATTERN_NO_HF = FixedPattern<17, CLOCK_PATTERN_LENGTH_NO_HF>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}; +constexpr auto DATA_START_PATTERN_ = FixedPattern<5, 5> {1, 1, 1, 1, 1}; +constexpr auto DATA_STOP_PATTERN_ = FixedPattern<3, 3> {1, 1, 1}; + +// Signal data length, without the start and stop patterns +constexpr int DATA_LENGTH_HF = 23; +constexpr int DATA_LENGTH_NO_HF = 15; + + +/** + * @brief Parse a part of a vector of bits (boolean) to a decimal number. + * Eg: {1, 1, 0} -> 6. + * @param begin begin of the vector's part to be parsed + * @param end end of the vector's part to be parsed + * @return The decimal value of the parsed part + */ +int toDecimal(const std::vector::iterator begin, const std::vector::iterator end) +{ + int retval = 0; + auto i = std::distance(begin, end) - 1; + for (std::vector::iterator it = begin; it != end; it++, i--) { + retval += (*it ? (1 << i) : 0); + } + return retval; +} + +// DX Film Edge Clock signal found on 35mm films. +struct Clock { + int rowNumber = 0; + bool containsHFNumber = false; // Clock signal (thus data signal) with half-frame number (longer version) + int xStart = 0; // Beginning of the clock signal on the X-axis, in pixels + int xStop = 0; // End of the clock signal on the X-axis, in pixels + int pixelTolerance = 0; // Pixel tolerance will be set depending of the length of the clock signal (in pixels) + + bool operator<(const int x) const + { + return xStart < x; + } + + bool operator < (const Clock& other) const + { + return xStart < other.xStart; + } + + /* + * @brief Check if this clocks start at about the same x position as another. + * We assume two clock are the same when they start in about the same X position, + * even if they are different clocks (stop at different position or different type). + * Only the more recent clock is kept. + */ + bool xStartInRange(const Clock& other) const + { + auto tolerance = std::max(pixelTolerance, other.pixelTolerance); + return (xStart - tolerance) <= other.xStart && (xStart + tolerance) >= other.xStart; + } + + bool xStartInRange(const int x) const + { + return (xStart - pixelTolerance) <= x && (xStart + pixelTolerance) >= x; + } + + bool xStopInRange(const int x) const + { + return (xStop - pixelTolerance) <= x && (xStop + pixelTolerance) >= x; + } + + /* + * @brief Check the clock's row number is next to the row we want to compare. + * Since we update the clock row number with the latest found signal's row number, + * the signal may be either: + * - below the clock, but not too far + * - slightly above the clock + * @param otherRownumber the other row to check if it's in range or not + * @param totalRows the image total row number (~image height) + */ + bool rowInRange(const int otherRowNumber, const int totalRows) const + { + const auto acceptedRowRange = totalRows / 5 + 1; // Below the clock, not too far + const auto rowMarginTolerance = totalRows / 20 + 1; // If above the clock, it should be really close + auto result = ((otherRowNumber >= rowNumber && otherRowNumber - rowNumber <= acceptedRowRange) + || (rowNumber <= otherRowNumber && otherRowNumber - rowNumber <= rowMarginTolerance)); + return result; + } + +}; + +class ClockSet : public std::set> { +public: + + /* + * @brief Return the clock which starts at the closest X position. + */ + const ClockSet::iterator closestElement(const int x) + { + const auto it = lower_bound(x); + if (it == begin()) + return it; + + const auto prev_it = std::prev(it); + return (it == end() || x - prev_it->xStart <= it->xStart - x) ? prev_it : it; + } + + /** + * @brief Add a new clock to the set. + * If the new clock is close enough to an existing clock in the set, + * the old clock is removed. + */ + void update(const Clock& newClock) + { + auto closestClock = closestElement(newClock.xStart); + if (closestClock != end() && newClock.xStartInRange(*closestClock)) { + erase(closestClock); + insert(newClock); + } else { + insert(newClock); + } + } +}; + + +/* +* @brief To avoid many false positives, +* the clock signal must be found to attempt to decode a data signal. +* We ensure the data signal starts below a clock. +* We accept a tolerance margin, +* ie. the signal may start a few pixels before or after the clock on the X-axis. +*/ +struct DXFEState : public RowReader::DecodingState { + ClockSet allClocks; + int totalRows = 0; +}; + +/** + * @brief Try to find a DX Film Edge clock in the given row. + * @param rowNumber the row number + * @param end end of the vector's part to be parsed + * @return The decimal value of the parsed part + */ +std::optional findClock(int rowNumber, PatternView& view) +{ + // Minimum "allowed "white" zone to the left and the right sides of the clock signal. + constexpr float minClockNoHFQuietZone = 2; + // On HF versions, the decimal number uses to be really close to the clock + constexpr float minClockHFQuietZone = 0.5; + + // Adjust the pixel shift tolerance between the data signal and the clock signal. + // 1 means the signal can be shifted up to one bar to the left or the right. + constexpr float pixelToleranceRatio = 0.5; + + // Before detecting any clock, + // try to detect the common pattern between all types of clocks. + // This avoid doing two detections at each interations instead of one, + // when they is no DX Edge code to detect. + auto commonClockPattern = + FindLeftGuard(view, CLOCK_PATTERN_COMMON.size(), CLOCK_PATTERN_COMMON, std::min(minClockNoHFQuietZone, minClockHFQuietZone)); + if (commonClockPattern.isValid()) { + bool foundClock = false; + bool containsHFNumber = false; + auto clock_pattern = FindLeftGuard(view, CLOCK_PATTERN_HF.size(), CLOCK_PATTERN_HF, minClockHFQuietZone); + if (clock_pattern.isValid()) { + foundClock = true; + containsHFNumber = true; + } else { + clock_pattern = FindLeftGuard(view, CLOCK_PATTERN_NO_HF.size(), CLOCK_PATTERN_NO_HF, minClockNoHFQuietZone); + if (clock_pattern.isValid()) + foundClock = true; + } + if (foundClock) { + Clock clock; + clock.rowNumber = rowNumber; + clock.containsHFNumber = containsHFNumber; + clock.xStart = clock_pattern.pixelsInFront(); + clock.xStop = clock_pattern.pixelsTillEnd(); + clock.pixelTolerance = (clock_pattern.pixelsTillEnd() - clock_pattern.pixelsInFront()) + / (containsHFNumber ? CLOCK_PATTERN_LENGTH_HF : CLOCK_PATTERN_LENGTH_NO_HF) * pixelToleranceRatio; + return clock; + } + } + return std::nullopt; +} + + +Result DXFilmEdgeReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const +{ + + // Retrieve the decoding state to check if a clock signal has already been found before. + // We check also if it contains the half-frame number, since it affects the signal structure. + if (!state) { + state.reset(new DXFEState); + // We need the total row number to adjust clock & signal detection sensitivity + static_cast(state.get())->totalRows = 2 * rowNumber; + } + auto& allClocks = static_cast(state.get())->allClocks; + auto& totalRows = static_cast(state.get())->totalRows; + // Minimum "allowed "white" zone to the left and the right sides of the data signal. + // We allow a smaller quiet zone, ie improve detection at risk of getting false positives, + // because the risk is greatly reduced when we check we found the clock before the signal. + constexpr float minDataQuietZone = 0.2; + + // We should find at least one clock before attempting to decode the data signal. + auto clock = findClock(rowNumber, next); + + if (clock) + allClocks.update(clock.value()); + + if (allClocks.empty()) + return {}; + + // Now that we found at least a clock, attempt to decode the data signal. + // Start by finding the data start pattern. + next = FindLeftGuard(next, DATA_START_PATTERN_.size(), DATA_START_PATTERN_, minDataQuietZone); + if (!next.isValid()) + return {}; + + auto xStart = next.pixelsInFront(); + + // The found data signal must be below the clock signal, otherwise we abort the decoding (potential false positive) + auto closestClock = allClocks.closestElement(xStart); + if (!closestClock->xStartInRange(xStart)) + return {}; + + // Avoid decoding a signal found at the top or too far from the clock + // (might happen when stacking two films one of top of the other, or other false positive situations) + if (!closestClock->rowInRange(rowNumber, totalRows)) + return {}; + + // Compute the length of a bar + // It may be greater than 1 depending on what have been found in the raw signal + auto perBarRawWidth = *next.data(); + + // Skip the data start pattern (black, white, black, white, black) + // The first signal bar is always white: this is the + // separation between the start pattern and the product number) + next.shift(DATA_START_PATTERN_SIZE); + + if (!next.isValid()) + return {}; + + std::vector dataSignalBits; + + // They are two possible data signal lengths (with or without half-frame information) + dataSignalBits.reserve(closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF); + + // Populate a vector of booleans to represent the bits. true = black, false = white. + // We start the parsing just after the data start signal. + // The first bit is always a white bar (we include the separator just after the start pattern) + // Eg: {3, 1, 2} -> {0, 0, 0, 1, 0, 0} + int signalLength = 0; + bool currentBarIsBlack = false; // the current bar is white + while (signalLength < (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) { + if (!next.isValid()) + return {}; + + // Zero means we can't conclude on black or white bar. Abort the decoding. + if (*next.data() == 0) + return {}; + + // Adjust the current bar according to the computed ratio above. + // When the raw result is not exact (between two bars), + // we round the bar size to the nearest integer. + auto currentBarWidth = + *next.data() / perBarRawWidth + (*next.data() % perBarRawWidth >= (perBarRawWidth / 2) ? 1 : 0); + + signalLength += currentBarWidth; + + // Extend the bit array according to the current bar length. + // Eg: one white bars -> {0}, three black bars -> {1, 1, 1} + while (currentBarWidth > 0 + && (int)dataSignalBits.size() < (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) { + dataSignalBits.push_back(currentBarIsBlack); + --currentBarWidth; + } + + // Iterate to the next data signal bar (the color is inverted) + currentBarIsBlack = !currentBarIsBlack; + next.shift(1); + } + + // Check the signal length + if (signalLength != (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) + return {}; + + // Check there is the Stop pattern at the end of the data signal + next = next.subView(0, 3); + if (!IsRightGuard(next, DATA_STOP_PATTERN_, minDataQuietZone)) + return {}; + + // Check the data signal has been fully parsed + if (closestClock->containsHFNumber && dataSignalBits.size() < DATA_LENGTH_HF) + return {}; + if (!closestClock->containsHFNumber && dataSignalBits.size() < DATA_LENGTH_NO_HF) + return {}; + + // The following bits are always white (=false), they are separators. + if (dataSignalBits.at(0) || dataSignalBits.at(8)) + return {}; + if (closestClock->containsHFNumber && (dataSignalBits.at(20) || dataSignalBits.at(22))) + return {}; + if (!closestClock->containsHFNumber && (dataSignalBits.at(8) || dataSignalBits.at(14))) + return {}; + + // Check the parity bit + auto signalSum = std::accumulate(dataSignalBits.begin(), dataSignalBits.end()-2, 0); + auto parityBit = *(dataSignalBits.end() - 2); + if (signalSum % 2 != (int)parityBit) + return {}; + + // Compute the DX 1 number (product number) + auto productNumber = toDecimal(dataSignalBits.begin() + 1, dataSignalBits.begin() + 8); + if (!productNumber) + return {}; + + // Compute the DX 2 number (generation number) + auto generationNumber = toDecimal(dataSignalBits.begin() + 9, dataSignalBits.begin() + 13); + + // Generate the textual representation. + // Eg: 115-10/11A means: DX1 = 115, DX2 = 10, Frame number = 11A + std::string txt; + txt.reserve(10); + txt = std::to_string(productNumber) + "-" + std::to_string(generationNumber); + if (closestClock->containsHFNumber) { + auto halfFrameNumber = toDecimal(dataSignalBits.begin() + 13, dataSignalBits.begin() + 20); + txt += "/" + std::to_string(halfFrameNumber / 2); + if (halfFrameNumber % 2) + txt += "A"; + } + + Error error; + + // TODO is it required? + // AFAIK The DX Edge barcode doesn't follow any symbology identifier. + SymbologyIdentifier symbologyIdentifier = {'I', '0'}; // No check character validation ? + + auto xStop = next.pixelsTillEnd(); + + // The found data signal must be below the clock signal, otherwise we abort the decoding (potential false positive) + if (!closestClock->xStopInRange(xStop)) + return {}; + + + // Update the clock X coordinates with the latest corresponding data signal + // This may improve signal detection for next row iterations + if (closestClock->xStop != xStop || closestClock->xStart != xStart) { + Clock clock(*closestClock); + clock.xStart = xStart; + clock.xStop = xStop; + clock.rowNumber = rowNumber; + allClocks.erase(closestClock); + allClocks.insert(clock); + } + closestClock = allClocks.closestElement(xStart); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::DXFilmEdge, symbologyIdentifier, error); +} + +} // namespace ZXing::OneD diff --git a/core/src/oned/ODDXFilmEdgeReader.h b/core/src/oned/ODDXFilmEdgeReader.h new file mode 100644 index 0000000000..357e5e4517 --- /dev/null +++ b/core/src/oned/ODDXFilmEdgeReader.h @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Antoine Mérino + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ODRowReader.h" + +namespace ZXing::OneD { + +/** + *

    Implements decoding of the DX Edge Film code format, a type or barcode found on 35mm films.

    + * + *

    See https://en.wikipedia.org/wiki/DX_encoding

    + */ + +class DXFilmEdgeReader : public RowReader +{ +public: + using RowReader::RowReader; + + Result decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const override; +}; + +} // namespace ZXing::OneD diff --git a/core/src/oned/ODReader.cpp b/core/src/oned/ODReader.cpp index 07f6a62533..0caf3805d4 100644 --- a/core/src/oned/ODReader.cpp +++ b/core/src/oned/ODReader.cpp @@ -15,6 +15,7 @@ #include "ODCode93Reader.h" #include "ODDataBarExpandedReader.h" #include "ODDataBarReader.h" +#include "ODDXFilmEdgeReader.h" #include "ODITFReader.h" #include "ODMultiUPCEANReader.h" #include "Result.h" @@ -61,6 +62,8 @@ Reader::Reader(const ReaderOptions& opts) : ZXing::Reader(opts) _readers.emplace_back(new DataBarReader(opts)); if (formats.testFlags(BarcodeFormat::DataBarExpanded)) _readers.emplace_back(new DataBarExpandedReader(opts)); + if (formats.testFlag(BarcodeFormat::DXFilmEdge)) + _readers.emplace_back(new DXFilmEdgeReader(opts)); } Reader::~Reader() = default; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 7dd5234333..1fecb431e9 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -333,6 +333,13 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { auto startTime = std::chrono::steady_clock::now(); + runTests("dxfilmedge-1", "DXFilmEdge", 3, { + { 1, 2, 0 }, + { 1, 2, 0 }, + { 1, 2, 0 }, + }); + + // clang-format off runTests("aztec-1", "Aztec", 27, { { 26, 27, 0 }, diff --git a/test/samples/dxfilmedge-1/1.jpg b/test/samples/dxfilmedge-1/1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..9f342117169c4b693bff2d6744e6139835fd2c3d GIT binary patch literal 25235 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 OLf<{4@HnJ69Q{9+5I#=; literal 0 HcmV?d00001 diff --git a/test/samples/dxfilmedge-1/1.txt b/test/samples/dxfilmedge-1/1.txt new file mode 100644 index 0000000000..49f698075c --- /dev/null +++ b/test/samples/dxfilmedge-1/1.txt @@ -0,0 +1 @@ +36-2/21 \ No newline at end of file diff --git a/test/samples/dxfilmedge-1/2.png b/test/samples/dxfilmedge-1/2.png new file mode 100644 index 0000000000000000000000000000000000000000..665f080a3788f00ab91dbd03237d9b9680534fe0 GIT binary patch literal 16300 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 iFuD1RTy!_yTmFBuYL+5CfUGnC00005;zb5 literal 0 HcmV?d00001 diff --git a/test/samples/dxfilmedge-1/2.txt b/test/samples/dxfilmedge-1/2.txt new file mode 100644 index 0000000000..a2b143407c --- /dev/null +++ b/test/samples/dxfilmedge-1/2.txt @@ -0,0 +1 @@ +80-11/23 \ No newline at end of file diff --git a/test/samples/dxfilmedge-1/3.jpg b/test/samples/dxfilmedge-1/3.jpg new file mode 100644 index 0000000000000000000000000000000000000000..ce106b7804c215430fe9b9ee4e37a3b73e379eeb GIT binary patch literal 1652 zcmV-)28;Rs*#F=F5K2Z#MgRc;000310RRC1+WwA}S+3F)J%EK1e({JV;kgPFHPcZI6$@|H1$Z2mnU_ z>Hz@}0RO}Q8~^|T1pxs80000000000000011O)~M0|E&D+5iXv0RR910NMq_St5bC zx;^s48~OGfcTS+s4Grx)eC*muuU}j~hjfe20OY)#Gm+kU1AGC=qwt|+Mf$TP?k8iE zl4To@baw@BqG)z2uR@)X-h_mu1^%rpMO!Gk6&lYC(fxMiL(r&gfga!eBo$I#4?&A; z#cnm*Eos)_H-aU`Rda|g7DIf#(4Pi<1+^W0G z{<4o!V8_8qNUDR@-}<3Bjk|}zZ~@d+ApGKY{9iM?{1{JgV@rwH0V?BsqBkn%g5n)J z$oE3~NA)PCWXum?%SxW*O-}O33CI~#wa`^(lR3v)s;i2bpvZBPg$2r*cX1fw#wwA` zbP*Puw6G9$XHQ8~Z~LW#}gBX=8T7;RAYF`uM-6<``^f&GOtsu#-kHb$2R9huk9S|1NG zsNwatorTg<&4Iaak4X=&r+qyH4}>@nWO2ihIXOD5oys3dy%K8P6@z1CK4D*uV~^IY zN2W*od_3O^sLgDg7ly+P8R6lgTr3vFR2%R(*=(*OM8* zm?m)Zg3s!>`?2eu{22cLV&CfJ&yEa#v2XQq;XaYA#)^7dW1S?DG`Ez&0jk#inh7Xt zz8;9};Ss1SaOiOQLcI6MKZRoalJ5%r*~RKsuK3NxR`*$bDTnWk>4WfDxmO%jOh@i2 z%iUTP@4Pp1u=djKSJvi~AE+x>>5Nsq&Tm0c_M>=E_AK11c2&>n_-%(3k*5ApyIu73 zTzQ2ACTUq1J4sSl>Pz~uVq+n z7)BMKbhhp)cm&YBnZ`n8f>yjcly1Y67(`*JuFpJ^Nbv9jii)y3dwBT7E%Sw-u8QsZ-nPDjCOSG3KgiC8ls}n8^yBnp%f(W9!CMMY^V*K0Cn1RRP6Z zJJWiGyYc)6Z&tCI*p*Lqm(o!mfB95{6!DLzRqwqIPOCWSV|T#rrFHMGRxZk2>ijCwELV@{h&(;@>Wxdhx%3DCXMQsBBCSaoB2lVcka$H=B-WX zj8%sek{_h5x3qbvJ0@;fSFN>U(y*+&moZ307|9N>*-or%Dk$*9y+TWda Date: Tue, 12 Dec 2023 13:28:40 +0100 Subject: [PATCH 165/587] DXFilmEdge: fix runtime performance issue and simplify code The overall performance cost of this addition is now in the order of 1%. Also: * detects one extra blackbox sample * add a few missing mentionings of the new barcode format --- core/src/Result.cpp | 2 +- core/src/oned/ODDXFilmEdgeReader.cpp | 417 ++++++------------ example/ZXingQtReader.h | 3 +- test/blackbox/BlackboxTestRunner.cpp | 12 +- test/fuzz/fuzzReadLinear.cpp | 2 + .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 2 +- wrappers/c/zxing-c.h | 4 +- .../ios/Sources/Wrapper/ZXIFormatHelper.mm | 4 +- wrappers/python/zxing.cpp | 2 +- wrappers/wasm/demo_cam_reader.html | 1 + wrappers/wasm/demo_reader.html | 1 + wrappers/winrt/BarcodeReader.cpp | 6 +- 12 files changed, 160 insertions(+), 296 deletions(-) diff --git a/core/src/Result.cpp b/core/src/Result.cpp index 427ed84e51..3e4532c3cf 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -44,7 +44,7 @@ Result::Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat bool Result::isValid() const { - return format() != BarcodeFormat::None && _content.symbology.code != 0 && !error(); + return format() != BarcodeFormat::None && !error(); } const ByteArray& Result::bytes() const diff --git a/core/src/oned/ODDXFilmEdgeReader.cpp b/core/src/oned/ODDXFilmEdgeReader.cpp index f7647bd05d..3686d13aef 100644 --- a/core/src/oned/ODDXFilmEdgeReader.cpp +++ b/core/src/oned/ODDXFilmEdgeReader.cpp @@ -1,380 +1,239 @@ /* * Copyright 2023 Antoine Mérino + * Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 -#include "DecodeHints.h" -#include "GTIN.h" #include "ODDXFilmEdgeReader.h" + #include "Result.h" -#include "ZXAlgorithms.h" #include -#include +#include + namespace ZXing::OneD { -// Detection is made from center to bottom. -// We ensure the clock signal is decoded before the data signal to avoid false positives. -// They are two version of a DX Edge codes : without half-frame information and with half-frame information. -// The clock signal is longer if the DX code contains the half-frame information (more recent version) -constexpr int CLOCK_PATTERN_LENGTH_HF = 31; -constexpr int CLOCK_PATTERN_LENGTH_NO_HF = 23; -constexpr int DATA_START_PATTERN_SIZE = 5; -constexpr auto CLOCK_PATTERN_COMMON = FixedPattern<15, 20> {5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; -constexpr auto CLOCK_PATTERN_HF = - FixedPattern<25, CLOCK_PATTERN_LENGTH_HF>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}; -constexpr auto CLOCK_PATTERN_NO_HF = FixedPattern<17, CLOCK_PATTERN_LENGTH_NO_HF>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}; -constexpr auto DATA_START_PATTERN_ = FixedPattern<5, 5> {1, 1, 1, 1, 1}; -constexpr auto DATA_STOP_PATTERN_ = FixedPattern<3, 3> {1, 1, 1}; - -// Signal data length, without the start and stop patterns -constexpr int DATA_LENGTH_HF = 23; -constexpr int DATA_LENGTH_NO_HF = 15; - - -/** - * @brief Parse a part of a vector of bits (boolean) to a decimal number. - * Eg: {1, 1, 0} -> 6. - * @param begin begin of the vector's part to be parsed - * @param end end of the vector's part to be parsed - * @return The decimal value of the parsed part - */ -int toDecimal(const std::vector::iterator begin, const std::vector::iterator end) +namespace { + +// Detection is made from center outward. +// We ensure the clock track is decoded before the data tack to avoid false positives. +// They are two version of a DX Edge codes : with and without frame number. +// The clock track is longer if the DX code contains the frame number (more recent version) +constexpr int CLOCK_LENGTH_FN = 31; +constexpr int CLOCK_LENGTH_NO_FN = 23; + +// data track length, without the start and stop patterns +constexpr int DATA_LENGTH_FN = 23; +constexpr int DATA_LENGTH_NO_FN = 15; + +constexpr auto CLOCK_PATTERN_FN = + FixedPattern<25, CLOCK_LENGTH_FN>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}; +constexpr auto CLOCK_PATTERN_NO_FN = FixedPattern<17, CLOCK_LENGTH_NO_FN>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}; +constexpr auto DATA_START_PATTERN = FixedPattern<5, 5>{1, 1, 1, 1, 1}; +constexpr auto DATA_STOP_PATTERN = FixedPattern<3, 3>{1, 1, 1}; + +template +bool IsPattern(PatternView& view, const FixedPattern& pattern, float minQuietZone) { - int retval = 0; - auto i = std::distance(begin, end) - 1; - for (std::vector::iterator it = begin; it != end; it++, i--) { - retval += (*it ? (1 << i) : 0); - } - return retval; + view = view.subView(0, N); + return view.isValid() && IsPattern(view, pattern, view.isAtFirstBar() ? std::numeric_limits::max() : view[-1], minQuietZone); +} + +bool DistIsBelowThreshold(PointI a, PointI b, PointI threshold) +{ + return std::abs(a.x - b.x) < threshold.x && std::abs(a.y - b.y) < threshold.y; } // DX Film Edge Clock signal found on 35mm films. -struct Clock { +struct Clock +{ + bool hasFrameNr = false; // Clock tack (thus data trac) with frame number (longer version) int rowNumber = 0; - bool containsHFNumber = false; // Clock signal (thus data signal) with half-frame number (longer version) - int xStart = 0; // Beginning of the clock signal on the X-axis, in pixels - int xStop = 0; // End of the clock signal on the X-axis, in pixels - int pixelTolerance = 0; // Pixel tolerance will be set depending of the length of the clock signal (in pixels) + int xStart = 0; // Beginning of the clock track on the X-axis, in pixels + int xStop = 0; // End of the clock tack on the X-axis, in pixels - bool operator<(const int x) const - { - return xStart < x; - } + int dataLength() const { return hasFrameNr ? DATA_LENGTH_FN : DATA_LENGTH_NO_FN; } - bool operator < (const Clock& other) const - { - return xStart < other.xStart; - } + float moduleSize() const { return float(xStop - xStart) / (hasFrameNr ? CLOCK_LENGTH_FN : CLOCK_LENGTH_NO_FN); } - /* - * @brief Check if this clocks start at about the same x position as another. - * We assume two clock are the same when they start in about the same X position, - * even if they are different clocks (stop at different position or different type). - * Only the more recent clock is kept. - */ - bool xStartInRange(const Clock& other) const + bool isCloseToStart(int x, int y) const { - auto tolerance = std::max(pixelTolerance, other.pixelTolerance); - return (xStart - tolerance) <= other.xStart && (xStart + tolerance) >= other.xStart; + auto ms = moduleSize(); + return DistIsBelowThreshold({x, y}, {xStart, rowNumber}, {int(0.5 * ms), int(4 * ms)}); } - bool xStartInRange(const int x) const + bool isCloseToStop(int x, int y) const { - return (xStart - pixelTolerance) <= x && (xStart + pixelTolerance) >= x; + auto ms = moduleSize(); + return DistIsBelowThreshold({x, y}, {xStop, rowNumber}, {int(0.5 * ms), int(4 * ms)}); } - - bool xStopInRange(const int x) const - { - return (xStop - pixelTolerance) <= x && (xStop + pixelTolerance) >= x; - } - - /* - * @brief Check the clock's row number is next to the row we want to compare. - * Since we update the clock row number with the latest found signal's row number, - * the signal may be either: - * - below the clock, but not too far - * - slightly above the clock - * @param otherRownumber the other row to check if it's in range or not - * @param totalRows the image total row number (~image height) - */ - bool rowInRange(const int otherRowNumber, const int totalRows) const - { - const auto acceptedRowRange = totalRows / 5 + 1; // Below the clock, not too far - const auto rowMarginTolerance = totalRows / 20 + 1; // If above the clock, it should be really close - auto result = ((otherRowNumber >= rowNumber && otherRowNumber - rowNumber <= acceptedRowRange) - || (rowNumber <= otherRowNumber && otherRowNumber - rowNumber <= rowMarginTolerance)); - return result; - } - }; -class ClockSet : public std::set> { -public: +struct DXFEState : public RowReader::DecodingState +{ + int centerRow = 0; + std::vector clocks; - /* - * @brief Return the clock which starts at the closest X position. - */ - const ClockSet::iterator closestElement(const int x) + // see if we a clock that starts near {x, y} + Clock* findClock(int x, int y) { - const auto it = lower_bound(x); - if (it == begin()) - return it; - - const auto prev_it = std::prev(it); - return (it == end() || x - prev_it->xStart <= it->xStart - x) ? prev_it : it; + auto i = FindIf(clocks, [start = PointI{x, y}](auto& v) { return v.isCloseToStart(start.x, start.y); }); + return i != clocks.end() ? &(*i) : nullptr; } - /** - * @brief Add a new clock to the set. - * If the new clock is close enough to an existing clock in the set, - * the old clock is removed. - */ - void update(const Clock& newClock) + // add/update clock + void addClock(const Clock& clock) { - auto closestClock = closestElement(newClock.xStart); - if (closestClock != end() && newClock.xStartInRange(*closestClock)) { - erase(closestClock); - insert(newClock); - } else { - insert(newClock); - } + if (Clock* i = findClock(clock.xStart, clock.rowNumber)) + *i = clock; + else + clocks.push_back(clock); } }; +std::optional CheckForClock(int rowNumber, PatternView& view) +{ + Clock clock; -/* -* @brief To avoid many false positives, -* the clock signal must be found to attempt to decode a data signal. -* We ensure the data signal starts below a clock. -* We accept a tolerance margin, -* ie. the signal may start a few pixels before or after the clock on the X-axis. -*/ -struct DXFEState : public RowReader::DecodingState { - ClockSet allClocks; - int totalRows = 0; -}; + if (IsPattern(view, CLOCK_PATTERN_FN, 0.5)) // On FN versions, the decimal number can be really close to the clock + clock.hasFrameNr = true; + else if (IsPattern(view, CLOCK_PATTERN_NO_FN, 2.0)) + clock.hasFrameNr = false; + else + return {}; -/** - * @brief Try to find a DX Film Edge clock in the given row. - * @param rowNumber the row number - * @param end end of the vector's part to be parsed - * @return The decimal value of the parsed part - */ -std::optional findClock(int rowNumber, PatternView& view) -{ - // Minimum "allowed "white" zone to the left and the right sides of the clock signal. - constexpr float minClockNoHFQuietZone = 2; - // On HF versions, the decimal number uses to be really close to the clock - constexpr float minClockHFQuietZone = 0.5; - - // Adjust the pixel shift tolerance between the data signal and the clock signal. - // 1 means the signal can be shifted up to one bar to the left or the right. - constexpr float pixelToleranceRatio = 0.5; - - // Before detecting any clock, - // try to detect the common pattern between all types of clocks. - // This avoid doing two detections at each interations instead of one, - // when they is no DX Edge code to detect. - auto commonClockPattern = - FindLeftGuard(view, CLOCK_PATTERN_COMMON.size(), CLOCK_PATTERN_COMMON, std::min(minClockNoHFQuietZone, minClockHFQuietZone)); - if (commonClockPattern.isValid()) { - bool foundClock = false; - bool containsHFNumber = false; - auto clock_pattern = FindLeftGuard(view, CLOCK_PATTERN_HF.size(), CLOCK_PATTERN_HF, minClockHFQuietZone); - if (clock_pattern.isValid()) { - foundClock = true; - containsHFNumber = true; - } else { - clock_pattern = FindLeftGuard(view, CLOCK_PATTERN_NO_HF.size(), CLOCK_PATTERN_NO_HF, minClockNoHFQuietZone); - if (clock_pattern.isValid()) - foundClock = true; - } - if (foundClock) { - Clock clock; - clock.rowNumber = rowNumber; - clock.containsHFNumber = containsHFNumber; - clock.xStart = clock_pattern.pixelsInFront(); - clock.xStop = clock_pattern.pixelsTillEnd(); - clock.pixelTolerance = (clock_pattern.pixelsTillEnd() - clock_pattern.pixelsInFront()) - / (containsHFNumber ? CLOCK_PATTERN_LENGTH_HF : CLOCK_PATTERN_LENGTH_NO_HF) * pixelToleranceRatio; - return clock; - } - } - return std::nullopt; + clock.rowNumber = rowNumber; + clock.xStart = view.pixelsInFront(); + clock.xStop = view.pixelsTillEnd(); + + return clock; } +} // namespace Result DXFilmEdgeReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const { - - // Retrieve the decoding state to check if a clock signal has already been found before. - // We check also if it contains the half-frame number, since it affects the signal structure. if (!state) { state.reset(new DXFEState); - // We need the total row number to adjust clock & signal detection sensitivity - static_cast(state.get())->totalRows = 2 * rowNumber; + static_cast(state.get())->centerRow = rowNumber; } - auto& allClocks = static_cast(state.get())->allClocks; - auto& totalRows = static_cast(state.get())->totalRows; - // Minimum "allowed "white" zone to the left and the right sides of the data signal. - // We allow a smaller quiet zone, ie improve detection at risk of getting false positives, - // because the risk is greatly reduced when we check we found the clock before the signal. - constexpr float minDataQuietZone = 0.2; - - // We should find at least one clock before attempting to decode the data signal. - auto clock = findClock(rowNumber, next); - if (clock) - allClocks.update(clock.value()); + auto dxState = static_cast(state.get()); - if (allClocks.empty()) + // Only consider rows below the center row of the image + if (!_opts.tryRotate() && rowNumber < dxState->centerRow) return {}; - // Now that we found at least a clock, attempt to decode the data signal. - // Start by finding the data start pattern. - next = FindLeftGuard(next, DATA_START_PATTERN_.size(), DATA_START_PATTERN_, minDataQuietZone); + // Look for a pattern that is part of both the clock as well as the data pattern (starting on a space) + constexpr auto Is4x1 = [](const PatternView& view, int spaceInPixel) { + // find min/max of 4 consecutive bars/spaces and make sure they are + auto [m, M] = std::minmax({view[0], view[1], view[2], view[3]}); + return M <= m * 4 / 3 + 1 && spaceInPixel > m / 2; + }; + + // 8 is the minimum size of the data pattern if all data bits are 0 + next = FindLeftGuard<4>(next.subView(1), 8, Is4x1); if (!next.isValid()) return {}; - auto xStart = next.pixelsInFront(); + // Reverse the shift from bar to space above + next.shift(-1); + next.extend(); - // The found data signal must be below the clock signal, otherwise we abort the decoding (potential false positive) - auto closestClock = allClocks.closestElement(xStart); - if (!closestClock->xStartInRange(xStart)) + // Check if the 4x1 pattern is part of clock track + if (auto clock = CheckForClock(rowNumber, next)) { + dxState->addClock(*clock); + next.skipSymbol(); return {}; + } - // Avoid decoding a signal found at the top or too far from the clock - // (might happen when stacking two films one of top of the other, or other false positive situations) - if (!closestClock->rowInRange(rowNumber, totalRows)) + if (dxState->clocks.empty()) return {}; - // Compute the length of a bar - // It may be greater than 1 depending on what have been found in the raw signal - auto perBarRawWidth = *next.data(); + // Now that we found at least one clock, attempt to decode the data track. + constexpr float minDataQuietZone = 0.5; - // Skip the data start pattern (black, white, black, white, black) - // The first signal bar is always white: this is the - // separation between the start pattern and the product number) - next.shift(DATA_START_PATTERN_SIZE); - - if (!next.isValid()) + if (!IsPattern(next, DATA_START_PATTERN, minDataQuietZone)) return {}; - std::vector dataSignalBits; + auto xStart = next.pixelsInFront(); + + // Only considert data tracks that are next to a clock track + auto clock = dxState->findClock(xStart, rowNumber); + if (!clock) + return {}; - // They are two possible data signal lengths (with or without half-frame information) - dataSignalBits.reserve(closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF); + // Skip the data start pattern (black, white, black, white, black) + // The first signal bar is always white: this is the + // separation between the start pattern and the product number) + next.skipSymbol(); // Populate a vector of booleans to represent the bits. true = black, false = white. // We start the parsing just after the data start signal. // The first bit is always a white bar (we include the separator just after the start pattern) // Eg: {3, 1, 2} -> {0, 0, 0, 1, 0, 0} - int signalLength = 0; - bool currentBarIsBlack = false; // the current bar is white - while (signalLength < (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) { - if (!next.isValid()) - return {}; - - // Zero means we can't conclude on black or white bar. Abort the decoding. - if (*next.data() == 0) - return {}; - - // Adjust the current bar according to the computed ratio above. - // When the raw result is not exact (between two bars), - // we round the bar size to the nearest integer. - auto currentBarWidth = - *next.data() / perBarRawWidth + (*next.data() % perBarRawWidth >= (perBarRawWidth / 2) ? 1 : 0); - - signalLength += currentBarWidth; - - // Extend the bit array according to the current bar length. - // Eg: one white bars -> {0}, three black bars -> {1, 1, 1} - while (currentBarWidth > 0 - && (int)dataSignalBits.size() < (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) { - dataSignalBits.push_back(currentBarIsBlack); - --currentBarWidth; - } - - // Iterate to the next data signal bar (the color is inverted) - currentBarIsBlack = !currentBarIsBlack; + BitArray dataBits; + while (next.isValid(1) && dataBits.size() < clock->dataLength()) { + + int modules = next[0] / clock->moduleSize() + 0.5; + // even index means we are at a bar, otherwise at a space + dataBits.appendBits(next.index() % 2 == 0 ? 0xFFFFFFFF : 0x0, modules); + next.shift(1); } - // Check the signal length - if (signalLength != (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) - return {}; - - // Check there is the Stop pattern at the end of the data signal - next = next.subView(0, 3); - if (!IsRightGuard(next, DATA_STOP_PATTERN_, minDataQuietZone)) + // Check the data track length + if (dataBits.size() != clock->dataLength()) return {}; - // Check the data signal has been fully parsed - if (closestClock->containsHFNumber && dataSignalBits.size() < DATA_LENGTH_HF) - return {}; - if (!closestClock->containsHFNumber && dataSignalBits.size() < DATA_LENGTH_NO_HF) + next = next.subView(0, DATA_STOP_PATTERN.size()); + + // Check there is the Stop pattern at the end of the data track + if (!next.isValid() || !IsRightGuard(next, DATA_STOP_PATTERN, minDataQuietZone)) return {}; // The following bits are always white (=false), they are separators. - if (dataSignalBits.at(0) || dataSignalBits.at(8)) - return {}; - if (closestClock->containsHFNumber && (dataSignalBits.at(20) || dataSignalBits.at(22))) - return {}; - if (!closestClock->containsHFNumber && (dataSignalBits.at(8) || dataSignalBits.at(14))) + if (dataBits.get(0) || dataBits.get(8) || (clock->hasFrameNr ? (dataBits.get(20) || dataBits.get(22)) : dataBits.get(14))) return {}; // Check the parity bit - auto signalSum = std::accumulate(dataSignalBits.begin(), dataSignalBits.end()-2, 0); - auto parityBit = *(dataSignalBits.end() - 2); + auto signalSum = std::accumulate(dataBits.begin(), dataBits.end() - 2, 0); + auto parityBit = *(dataBits.end() - 2); if (signalSum % 2 != (int)parityBit) return {}; // Compute the DX 1 number (product number) - auto productNumber = toDecimal(dataSignalBits.begin() + 1, dataSignalBits.begin() + 8); + auto productNumber = ToInt(dataBits, 1, 7); if (!productNumber) return {}; // Compute the DX 2 number (generation number) - auto generationNumber = toDecimal(dataSignalBits.begin() + 9, dataSignalBits.begin() + 13); + auto generationNumber = ToInt(dataBits, 9, 4); // Generate the textual representation. // Eg: 115-10/11A means: DX1 = 115, DX2 = 10, Frame number = 11A std::string txt; txt.reserve(10); txt = std::to_string(productNumber) + "-" + std::to_string(generationNumber); - if (closestClock->containsHFNumber) { - auto halfFrameNumber = toDecimal(dataSignalBits.begin() + 13, dataSignalBits.begin() + 20); - txt += "/" + std::to_string(halfFrameNumber / 2); - if (halfFrameNumber % 2) + if (clock->hasFrameNr) { + auto frameNr = ToInt(dataBits, 13, 6); + txt += "/" + std::to_string(frameNr); + if (dataBits.get(19)) txt += "A"; } - Error error; - - // TODO is it required? - // AFAIK The DX Edge barcode doesn't follow any symbology identifier. - SymbologyIdentifier symbologyIdentifier = {'I', '0'}; // No check character validation ? - auto xStop = next.pixelsTillEnd(); - // The found data signal must be below the clock signal, otherwise we abort the decoding (potential false positive) - if (!closestClock->xStopInRange(xStop)) + // The found data track must end near the clock track + if (!clock->isCloseToStop(xStop, rowNumber)) return {}; - - // Update the clock X coordinates with the latest corresponding data signal + // Update the clock coordinates with the latest corresponding data track // This may improve signal detection for next row iterations - if (closestClock->xStop != xStop || closestClock->xStart != xStart) { - Clock clock(*closestClock); - clock.xStart = xStart; - clock.xStop = xStop; - clock.rowNumber = rowNumber; - allClocks.erase(closestClock); - allClocks.insert(clock); - } - closestClock = allClocks.closestElement(xStart); - return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::DXFilmEdge, symbologyIdentifier, error); + clock->xStart = xStart; + clock->xStop = xStop; + + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::DXFilmEdge, {}); } } // namespace ZXing::OneD diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index 0b26625f7a..aede6fc027 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -52,8 +52,9 @@ enum class BarcodeFormat UPCE = (1 << 15), ///< UPC-E MicroQRCode = (1 << 16), ///< Micro QR Code RMQRCode = (1 << 17), ///< Rectangular Micro QR Code + DXFilmEdge = (1 << 18), ///< DX Film Edge Barcode - LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE, + LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DXFilmEdge | UPCA | UPCE, MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, }; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 1fecb431e9..fe7914d3a2 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -333,13 +333,6 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { auto startTime = std::chrono::steady_clock::now(); - runTests("dxfilmedge-1", "DXFilmEdge", 3, { - { 1, 2, 0 }, - { 1, 2, 0 }, - { 1, 2, 0 }, - }); - - // clang-format off runTests("aztec-1", "Aztec", 27, { { 26, 27, 0 }, @@ -386,6 +379,11 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 19, 0, pure }, }); + runTests("dxfilmedge-1", "DXFilmEdge", 3, { + { 1, 3, 0 }, + { 0, 3, 180 }, + }); + runTests("codabar-1", "Codabar", 11, { { 11, 11, 0 }, { 11, 11, 180 }, diff --git a/test/fuzz/fuzzReadLinear.cpp b/test/fuzz/fuzzReadLinear.cpp index 6f05c9d1c8..c9208f9724 100644 --- a/test/fuzz/fuzzReadLinear.cpp +++ b/test/fuzz/fuzzReadLinear.cpp @@ -12,6 +12,7 @@ #include "oned/ODCode128Reader.h" #include "oned/ODDataBarReader.h" #include "oned/ODDataBarExpandedReader.h" +#include "oned/ODDXFilmEdgeReader.h" #include "oned/ODITFReader.h" #include "oned/ODCodabarReader.h" #include "ReaderOptions.h" @@ -34,6 +35,7 @@ bool init() readers.emplace_back(new CodabarReader(opts)); readers.emplace_back(new DataBarReader(opts)); readers.emplace_back(new DataBarExpandedReader(opts)); + readers.emplace_back(new DXFilmEdgeReader(opts)); return true; } diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 36545e262a..0e25ef82e2 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -44,8 +44,8 @@ static const char* JavaBarcodeFormatName(BarcodeFormat format) case BarcodeFormat::MicroQRCode: return "MICRO_QR_CODE"; case BarcodeFormat::RMQRCode: return "RMQR_CODE"; case BarcodeFormat::DataBar: return "DATA_BAR"; - case BarcodeFormat::DXFilmEdge: return "DX_FILM_EDGE"; case BarcodeFormat::DataBarExpanded: return "DATA_BAR_EXPANDED"; + case BarcodeFormat::DXFilmEdge: return "DX_FILM_EDGE"; case BarcodeFormat::UPCA: return "UPC_A"; case BarcodeFormat::UPCE: return "UPC_E"; default: throw std::invalid_argument("Invalid BarcodeFormat"); diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index 58a370dda7..cf19c8ed3f 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -81,8 +81,8 @@ typedef enum zxing_BarcodeFormat_LinearCodes = zxing_BarcodeFormat_Codabar | zxing_BarcodeFormat_Code39 | zxing_BarcodeFormat_Code93 | zxing_BarcodeFormat_Code128 | zxing_BarcodeFormat_EAN8 | zxing_BarcodeFormat_EAN13 - | zxing_BarcodeFormat_ITF | zxing_BarcodeFormat_DataBar | zxing_BarcodeFormat_DataBarExpanded | zxing_BarcodeFormat_UPCA | zxing_BarcodeFormat_UPCE - | zxing_BarcodeFormat_DXFilmEdge, + | zxing_BarcodeFormat_ITF | zxing_BarcodeFormat_DataBar | zxing_BarcodeFormat_DataBarExpanded + | zxing_BarcodeFormat_DXFilmEdge | zxing_BarcodeFormat_UPCA | zxing_BarcodeFormat_UPCE, zxing_BarcodeFormat_MatrixCodes = zxing_BarcodeFormat_Aztec | zxing_BarcodeFormat_DataMatrix | zxing_BarcodeFormat_MaxiCode | zxing_BarcodeFormat_PDF417 | zxing_BarcodeFormat_QRCode | zxing_BarcodeFormat_MicroQRCode | zxing_BarcodeFormat_RMQRCode, diff --git a/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm b/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm index ef6bf9160b..64ebfd7a97 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm +++ b/wrappers/ios/Sources/Wrapper/ZXIFormatHelper.mm @@ -73,12 +73,12 @@ ZXIFormat ZXIFormatFromBarcodeFormat(ZXing::BarcodeFormat format) { return ZXIFormat::CODE_128; case ZXing::BarcodeFormat::DataBar: return ZXIFormat::DATA_BAR; - case ZXing::BarcodeFormat::DXFilmEdge: - return ZXIFormat::DX_FILM_EDGE; case ZXing::BarcodeFormat::DataBarExpanded: return ZXIFormat::DATA_BAR_EXPANDED; case ZXing::BarcodeFormat::DataMatrix: return ZXIFormat::DATA_MATRIX; + case ZXing::BarcodeFormat::DXFilmEdge: + return ZXIFormat::DX_FILM_EDGE; case ZXing::BarcodeFormat::EAN8: return ZXIFormat::EAN_8; case ZXing::BarcodeFormat::EAN13: diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index b6dd88078b..e9f4bb36cf 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -181,7 +181,6 @@ PYBIND11_MODULE(zxingcpp, m) .value("Code93", BarcodeFormat::Code93) .value("Code128", BarcodeFormat::Code128) .value("DataMatrix", BarcodeFormat::DataMatrix) - .value("DXFilmEdge", BarcodeFormat::DXFilmEdge) .value("EAN8", BarcodeFormat::EAN8) .value("EAN13", BarcodeFormat::EAN13) .value("ITF", BarcodeFormat::ITF) @@ -192,6 +191,7 @@ PYBIND11_MODULE(zxingcpp, m) .value("RMQRCode", BarcodeFormat::RMQRCode) .value("DataBar", BarcodeFormat::DataBar) .value("DataBarExpanded", BarcodeFormat::DataBarExpanded) + .value("DXFilmEdge", BarcodeFormat::DXFilmEdge) .value("UPCA", BarcodeFormat::UPCA) .value("UPCE", BarcodeFormat::UPCE) // use upper case 'NONE' because 'None' is a reserved identifier in python diff --git a/wrappers/wasm/demo_cam_reader.html b/wrappers/wasm/demo_cam_reader.html index 1040a142df..7a09af0325 100644 --- a/wrappers/wasm/demo_cam_reader.html +++ b/wrappers/wasm/demo_cam_reader.html @@ -32,6 +32,7 @@

    zxing-cpp/wasm live demo

    + diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index fadc566a3c..6d3ba0f61f 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -116,6 +116,7 @@

    zxing-cpp/wasm reader demo

    + diff --git a/wrappers/winrt/BarcodeReader.cpp b/wrappers/winrt/BarcodeReader.cpp index 300ed69ac3..7d8e86ae47 100644 --- a/wrappers/winrt/BarcodeReader.cpp +++ b/wrappers/winrt/BarcodeReader.cpp @@ -78,8 +78,6 @@ BarcodeFormat BarcodeReader::ConvertRuntimeToNative(BarcodeType type) return BarcodeFormat::Code93; case BarcodeType::DATA_MATRIX: return BarcodeFormat::DataMatrix; - case BarcodeType::DX_FILM_EDGE: - return BarcodeFormat::DXFilmEdge; case BarcodeType::EAN_13: return BarcodeFormat::EAN13; case BarcodeType::EAN_8: @@ -100,6 +98,8 @@ BarcodeFormat BarcodeReader::ConvertRuntimeToNative(BarcodeType type) return BarcodeFormat::DataBar; case BarcodeType::RSS_EXPANDED: return BarcodeFormat::DataBarExpanded; + case BarcodeType::DX_FILM_EDGE: + return BarcodeFormat::DXFilmEdge; case BarcodeType::UPC_A: return BarcodeFormat::UPCA; case BarcodeType::UPC_E: @@ -145,6 +145,8 @@ BarcodeType BarcodeReader::ConvertNativeToRuntime(BarcodeFormat format) return BarcodeType::RSS_14; case BarcodeFormat::DataBarExpanded: return BarcodeType::RSS_EXPANDED; + case BarcodeFormat::DXFilmEdge: + return BarcodeType::DX_FILM_EDGE; case BarcodeFormat::UPCA: return BarcodeType::UPC_A; case BarcodeFormat::UPCE: From fd456f4bd373e07f88e8102bb1e1f50aacf7c279 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 13 Dec 2023 08:26:04 +0100 Subject: [PATCH 166/587] DecodeHints: limit the 2.2.1 c++ ABI fix to shared library builds This should fix the problem with building libreoffice with enabled precompiled headers. See #685. --- core/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 4e2135e7d2..12cadb3e82 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -105,7 +105,7 @@ if (BUILD_READERS) src/Content.h src/Content.cpp src/DecodeHints.h - src/DecodeHints.cpp + $<$:src/DecodeHints.cpp> # [[deprecated]] src/DecoderResult.h src/DetectorResult.h src/Error.h From ce602b468109576ce4e976a576105eb0201dab03 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 13 Dec 2023 08:28:46 +0100 Subject: [PATCH 167/587] cmake: enable pre-compiled headers This slightly improves compilation time. But the main reason for inclusion is to detect issues like #685 in the future. --- core/CMakeLists.txt | 4 ++++ core/src/BitHacks.h | 1 + test/unit/CMakeLists.txt | 2 ++ 3 files changed, 7 insertions(+) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 12cadb3e82..e9e324cea3 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -486,6 +486,10 @@ endif() set_target_properties(ZXing PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") +target_precompile_headers(ZXing PRIVATE ${PUBLIC_HEADERS}) +set_source_files_properties(src/libzueci/zueci.c PROPERTIES SKIP_PRECOMPILE_HEADERS ON) +set_source_files_properties(src/DecodeHints.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON) + include (GNUInstallDirs) set(ZX_INSTALL_TARGETS ZXing) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index d943a1269a..eefede8781 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -8,6 +8,7 @@ #include #include +#include #include #if __has_include() && __cplusplus > 201703L // MSVC has the header but then warns about including it diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 02d78d0f2c..00100cfde1 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -79,4 +79,6 @@ target_include_directories (UnitTest PRIVATE .) target_link_libraries (UnitTest ZXing::ZXing GTest::gtest_main GTest::gmock) +#target_precompile_headers(UnitTest PRIVATE ${CMAKE_SOURCE_DIR}/core/src/ReadBarcode.h) + add_test(NAME UnitTest COMMAND UnitTest) From d8a0a285344402be4b33b1ab054798041c761d13 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 13 Dec 2023 08:57:34 +0100 Subject: [PATCH 168/587] ci: add WinRT target to standard ci build script --- .github/workflows/ci.yml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d476f125a2..e9073c6c6a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -163,3 +163,25 @@ jobs: - name: Test module working-directory: wrappers/python run: python -m unittest -v + + build-winrt: + runs-on: windows-latest + + steps: + - uses: actions/checkout@v3 + + - name: Create Build Environment + run: cmake -E make_directory ${{runner.workspace}}/build + + - name: Configure CMake + shell: cmd + run: cmake -S ${{github.workspace}}/wrappers/winrt -B ${{runner.workspace}}/build -A ARM64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 + + - name: Build + shell: cmd + run: cmake --build ${{runner.workspace}}/build -j8 --config Release + + - uses: actions/upload-artifact@v3 + with: + name: winrt-ARM64-artifacts + path: ${{runner.workspace}}/build/dist From 66afdf1455b4353ef4c3fcd21cf5f20e9530a06d Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 13 Dec 2023 09:14:12 +0100 Subject: [PATCH 169/587] ci: rename build-winrt to publish-winrt for consistency --- .github/workflows/{build-winrt.yml => publish-winrt.yml} | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) rename .github/workflows/{build-winrt.yml => publish-winrt.yml} (99%) diff --git a/.github/workflows/build-winrt.yml b/.github/workflows/publish-winrt.yml similarity index 99% rename from .github/workflows/build-winrt.yml rename to .github/workflows/publish-winrt.yml index d4d8832d0e..9b53608942 100644 --- a/.github/workflows/build-winrt.yml +++ b/.github/workflows/publish-winrt.yml @@ -1,4 +1,4 @@ -name: build-winrt +name: publish-winrt on: release: types: [published] From be5a82d05ffec55bca50d164bf405d4ccd42ecca Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 13 Dec 2023 09:15:32 +0100 Subject: [PATCH 170/587] c++: fix MSVC compiler warnings --- core/src/Pattern.h | 2 +- core/src/oned/ODDXFilmEdgeReader.cpp | 2 +- core/src/qrcode/QRDecoder.cpp | 2 +- core/src/qrcode/QRDetector.cpp | 6 +++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index f4d377fc4c..2f38b99125 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -194,7 +194,7 @@ float IsPattern(const PatternView& view, const FixedPattern& pa return 0; const float_t moduleSize = (modSize[0] + modSize[1]) / 2; - return moduleSize; + return static_cast(moduleSize); } int width = view.sum(LEN); diff --git a/core/src/oned/ODDXFilmEdgeReader.cpp b/core/src/oned/ODDXFilmEdgeReader.cpp index 3686d13aef..9601828c44 100644 --- a/core/src/oned/ODDXFilmEdgeReader.cpp +++ b/core/src/oned/ODDXFilmEdgeReader.cpp @@ -175,7 +175,7 @@ Result DXFilmEdgeReader::decodePattern(int rowNumber, PatternView& next, std::un BitArray dataBits; while (next.isValid(1) && dataBits.size() < clock->dataLength()) { - int modules = next[0] / clock->moduleSize() + 0.5; + int modules = int(next[0] / clock->moduleSize() + 0.5); // even index means we are at a bar, otherwise at a space dataBits.appendBits(next.index() % 2 == 0 ? 0xFFFFFFFF : 0x0, modules); diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index 44ce4d41b5..f6ef382de5 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -306,7 +306,7 @@ DecoderResult DecodeBitStream(ByteArray&& bytes, const Version& version, ErrorCo } } } - } catch (std::out_of_range& e) { // see BitSource::readBits + } catch (std::out_of_range&) { // see BitSource::readBits error = FormatError("Truncated bit stream"); } catch (Error e) { error = std::move(e); diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 7912962dee..326251072d 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -45,7 +45,7 @@ PatternView FindPattern(const PatternView& view) // perform a fast plausability test for 1:1:3:1:1 pattern if (view[2] < 2 * std::max(view[0], view[4]) || view[2] < std::max(view[1], view[3])) return 0.f; - return IsPattern(view, PATTERN, spaceInPixel, 0.1); // the requires 4, here we accept almost 0 + return IsPattern(view, PATTERN, spaceInPixel, 0.1f); // the requires 4, here we accept almost 0 }); } @@ -779,9 +779,9 @@ DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp) auto br = Center(b); // rotate points such that topLeft of a is furthest away from b and topLeft of b is closest to a auto dist2B = [c = br](auto a, auto b) { return distance(a, c) < distance(b, c); }; - auto offsetA = std::max_element(a.begin(), a.end(), dist2B) - a.begin(); + auto offsetA = narrow_cast(std::max_element(a.begin(), a.end(), dist2B) - a.begin()); auto dist2A = [c = tl](auto a, auto b) { return distance(a, c) < distance(b, c); }; - auto offsetB = std::min_element(b.begin(), b.end(), dist2A) - b.begin(); + auto offsetB = narrow_cast(std::min_element(b.begin(), b.end(), dist2A) - b.begin()); a = RotatedCorners(a, offsetA); b = RotatedCorners(b, offsetB); From 5214892ccd68264f9bc3a81c02ec0f7d54719077 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 13 Dec 2023 11:28:15 +0100 Subject: [PATCH 171/587] DXEdgeFilm: code and comment cosmetic --- core/src/oned/ODDXFilmEdgeReader.cpp | 48 ++++++++++------------------ 1 file changed, 17 insertions(+), 31 deletions(-) diff --git a/core/src/oned/ODDXFilmEdgeReader.cpp b/core/src/oned/ODDXFilmEdgeReader.cpp index 9601828c44..4793f906ea 100644 --- a/core/src/oned/ODDXFilmEdgeReader.cpp +++ b/core/src/oned/ODDXFilmEdgeReader.cpp @@ -16,7 +16,7 @@ namespace ZXing::OneD { namespace { // Detection is made from center outward. -// We ensure the clock track is decoded before the data tack to avoid false positives. +// We ensure the clock track is decoded before the data track to avoid false positives. // They are two version of a DX Edge codes : with and without frame number. // The clock track is longer if the DX code contains the frame number (more recent version) constexpr int CLOCK_LENGTH_FN = 31; @@ -44,29 +44,22 @@ bool DistIsBelowThreshold(PointI a, PointI b, PointI threshold) return std::abs(a.x - b.x) < threshold.x && std::abs(a.y - b.y) < threshold.y; } -// DX Film Edge Clock signal found on 35mm films. +// DX Film Edge clock track found on 35mm films. struct Clock { - bool hasFrameNr = false; // Clock tack (thus data trac) with frame number (longer version) + bool hasFrameNr = false; // Clock track (thus data track) with frame number (longer version) int rowNumber = 0; int xStart = 0; // Beginning of the clock track on the X-axis, in pixels - int xStop = 0; // End of the clock tack on the X-axis, in pixels + int xStop = 0; // End of the clock track on the X-axis, in pixels int dataLength() const { return hasFrameNr ? DATA_LENGTH_FN : DATA_LENGTH_NO_FN; } float moduleSize() const { return float(xStop - xStart) / (hasFrameNr ? CLOCK_LENGTH_FN : CLOCK_LENGTH_NO_FN); } - bool isCloseToStart(int x, int y) const - { - auto ms = moduleSize(); - return DistIsBelowThreshold({x, y}, {xStart, rowNumber}, {int(0.5 * ms), int(4 * ms)}); - } + bool isCloseTo(PointI p, int x) const { return DistIsBelowThreshold(p, {x, rowNumber}, PointI(moduleSize() * PointF{0.5, 4})); } - bool isCloseToStop(int x, int y) const - { - auto ms = moduleSize(); - return DistIsBelowThreshold({x, y}, {xStop, rowNumber}, {int(0.5 * ms), int(4 * ms)}); - } + bool isCloseToStart(int x, int y) const { return isCloseTo({x, y}, xStart); } + bool isCloseToStop(int x, int y) const { return isCloseTo({x, y}, xStop); } }; struct DXFEState : public RowReader::DecodingState @@ -124,33 +117,29 @@ Result DXFilmEdgeReader::decodePattern(int rowNumber, PatternView& next, std::un if (!_opts.tryRotate() && rowNumber < dxState->centerRow) return {}; - // Look for a pattern that is part of both the clock as well as the data pattern (starting on a space) + // Look for a pattern that is part of both the clock as well as the data track (ommitting the first bar) constexpr auto Is4x1 = [](const PatternView& view, int spaceInPixel) { - // find min/max of 4 consecutive bars/spaces and make sure they are - auto [m, M] = std::minmax({view[0], view[1], view[2], view[3]}); + // find min/max of 4 consecutive bars/spaces and make sure they are close together + auto [m, M] = std::minmax({view[1], view[2], view[3], view[4]}); return M <= m * 4 / 3 + 1 && spaceInPixel > m / 2; }; - // 8 is the minimum size of the data pattern if all data bits are 0 - next = FindLeftGuard<4>(next.subView(1), 8, Is4x1); + // 12 is the minimum size of the data track (at least one product class bit + one parity bit) + next = FindLeftGuard<4>(next, 10, Is4x1); if (!next.isValid()) return {}; - // Reverse the shift from bar to space above - next.shift(-1); - next.extend(); - - // Check if the 4x1 pattern is part of clock track + // Check if the 4x1 pattern is part of a clock track if (auto clock = CheckForClock(rowNumber, next)) { dxState->addClock(*clock); next.skipSymbol(); return {}; } + // Without at least one clock track, we stop here if (dxState->clocks.empty()) return {}; - // Now that we found at least one clock, attempt to decode the data track. constexpr float minDataQuietZone = 0.5; if (!IsPattern(next, DATA_START_PATTERN, minDataQuietZone)) @@ -158,20 +147,17 @@ Result DXFilmEdgeReader::decodePattern(int rowNumber, PatternView& next, std::un auto xStart = next.pixelsInFront(); - // Only considert data tracks that are next to a clock track + // Only consider data tracks that are next to a clock track auto clock = dxState->findClock(xStart, rowNumber); if (!clock) return {}; // Skip the data start pattern (black, white, black, white, black) // The first signal bar is always white: this is the - // separation between the start pattern and the product number) + // separation between the start pattern and the product number next.skipSymbol(); - // Populate a vector of booleans to represent the bits. true = black, false = white. - // We start the parsing just after the data start signal. - // The first bit is always a white bar (we include the separator just after the start pattern) - // Eg: {3, 1, 2} -> {0, 0, 0, 1, 0, 0} + // Read the data bits BitArray dataBits; while (next.isValid(1) && dataBits.size() < clock->dataLength()) { From b315e37563c8c0b80bacc0daa1f9c067a2216548 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 15 Dec 2023 10:36:30 +0100 Subject: [PATCH 172/587] cmake: fix warning when building python wrapper with cmake 3.27 --- wrappers/python/CMakeLists.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 5b2a41581f..a073282349 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -24,6 +24,7 @@ if (NOT hasParent) endif() endif() +find_package (Python COMPONENTS Interpreter Development) # see https://github.com/pybind/pybind11/issues/4785 zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) # build the python module @@ -40,3 +41,4 @@ install(TARGETS zxingcpp RUNTIME DESTINATION "${CMAKE_INSTALL_BINDIR}" LIBRARY DESTINATION "${CMAKE_INSTALL_LIBDIR}" ARCHIVE DESTINATION "${CMAKE_INSTALL_LIBDIR}") + From 9df92f43f8c1d86b698dd247b5f0e210d96a83fb Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 15 Dec 2023 10:37:54 +0100 Subject: [PATCH 173/587] cmake: update git fetched pybind11 and gtest packages --- test/unit/CMakeLists.txt | 2 +- wrappers/python/CMakeLists.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/test/unit/CMakeLists.txt b/test/unit/CMakeLists.txt index 00100cfde1..b9ab8de452 100644 --- a/test/unit/CMakeLists.txt +++ b/test/unit/CMakeLists.txt @@ -1,4 +1,4 @@ -zxing_add_package(GTest googletest https://github.com/google/googletest.git release-1.11.0) +zxing_add_package(GTest googletest https://github.com/google/googletest.git v1.14.0) if (GTest_POPULATED) # don't install gtest stuff on "make install" diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index a073282349..1a601c4f0a 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required(VERSION 3.15) project(ZXingPython) set (pybind11_git_repo https://github.com/pybind/pybind11.git) -set (pybind11_git_rev v2.10.4) +set (pybind11_git_rev v2.11.1) # check if we are called from the top-level ZXing project get_directory_property(hasParent PARENT_DIRECTORY) From ac88bce74311fdbf376faf0bc88e90d0990d048a Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 15 Dec 2023 10:40:05 +0100 Subject: [PATCH 174/587] QRCode: skip extra version check The version bits have already been parsed during detection. If they would have been wrong then, we would not have ended up here. If we did, there is no point in reading them again. This fixes #688. --- core/src/qrcode/QRBitMatrixParser.cpp | 20 +------------------- 1 file changed, 1 insertion(+), 19 deletions(-) diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index 1aea0d9dfa..3cf4ee11dd 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -33,25 +33,7 @@ const Version* ReadVersion(const BitMatrix& bitMatrix, Type type) case Type::Micro: return Version::Micro(number); case Type::rMQR: return Version::rMQR(number); case Type::Model1: return Version::Model1(number); - case Type::Model2: break; - } - - const Version* version = Version::Model2(number); - if (!version || version->versionNumber() < 7) - return version; - - int dimension = bitMatrix.height(); - - for (bool mirror : {false, true}) { - // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) - int versionBits = 0; - for (int y = 5; y >= 0; --y) - for (int x = dimension - 9; x >= dimension - 11; --x) - AppendBit(versionBits, getBit(bitMatrix, x, y, mirror)); - - version = Version::DecodeVersionInformation(versionBits); - if (version && version->dimension() == dimension) - return version; + case Type::Model2: return Version::Model2(number); } return nullptr; From a91d3065cc4af1ac335acf3ec1a3956ef82a4930 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 15 Dec 2023 11:32:50 +0100 Subject: [PATCH 175/587] cmake: fix python test regression --- wrappers/python/CMakeLists.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 1a601c4f0a..8c1be1872e 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -24,7 +24,7 @@ if (NOT hasParent) endif() endif() -find_package (Python COMPONENTS Interpreter Development) # see https://github.com/pybind/pybind11/issues/4785 +find_package(Python3 COMPONENTS Interpreter Development REQUIRED) # see https://github.com/pybind/pybind11/issues/4785 zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) # build the python module @@ -32,7 +32,7 @@ pybind11_add_module(zxingcpp zxing.cpp) target_link_libraries(zxingcpp PRIVATE ZXing::ZXing) if (BUILD_READERS AND BUILD_WRITERS) - add_test(NAME PythonTest COMMAND ${PYTHON_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test.py -v) + add_test(NAME PythonTest COMMAND ${Python3_EXECUTABLE} ${CMAKE_CURRENT_SOURCE_DIR}/test.py -v) set_property(TEST PythonTest PROPERTY ENVIRONMENT PYTHONPATH=$) endif() From 957e4c14d819fd8244b6c8891ab7157d2b14d7ef Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 19 Dec 2023 18:52:13 +0100 Subject: [PATCH 176/587] Result: fix `isValid` regression (fix #691) I decided to be pragmatic and accept only symbols with non-empty content. The spec might theoretically allow for empty symbols but supporting this is not worth it without a clear use case. --- core/src/DecoderResult.h | 2 +- core/src/Result.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/DecoderResult.h b/core/src/DecoderResult.h index 02b2850841..d09a917f41 100644 --- a/core/src/DecoderResult.h +++ b/core/src/DecoderResult.h @@ -43,7 +43,7 @@ class DecoderResult bool isValid(bool includeErrors = false) const { - return includeErrors || (_content.symbology.code != 0 && !_error); + return (!_content.bytes.empty() && !_error) || (includeErrors && !!_error); } const Content& content() const & { return _content; } diff --git a/core/src/Result.cpp b/core/src/Result.cpp index 3e4532c3cf..67b38e27fa 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -44,7 +44,7 @@ Result::Result(DecoderResult&& decodeResult, Position&& position, BarcodeFormat bool Result::isValid() const { - return format() != BarcodeFormat::None && !error(); + return format() != BarcodeFormat::None && !_content.bytes.empty() && !error(); } const ByteArray& Result::bytes() const From e607eb72b70e3c774821213184e1f546964db593 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 19 Dec 2023 19:00:32 +0100 Subject: [PATCH 177/587] README: add note about minimum c++ version (fix #689) --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 826388d960..bfbcc397b1 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,8 @@ int main(int argc, char** argv) ``` To see the full capability of the API, have a look at [`ZXingReader.cpp`](example/ZXingReader.cpp). +[Note: At least C++17 is reqired on the client side to use the API.] + ### To write barcodes: 1. Create a [`MultiFormatWriter`](core/src/MultiFormatWriter.h) instance with the format you want to generate. Set encoding and margins if needed. 2. Call `encode()` with text content and the image size. This returns a [`BitMatrix`](core/src/BitMatrix.h) which is a binary image of the barcode where `true` == visual black and `false` == visual white. From 565ec6b6d5ff3408f2e2d7b6657bb57b47e67d86 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Tue, 19 Dec 2023 16:18:00 +0100 Subject: [PATCH 178/587] ios: reformat arguments for alignment Just like you do in Objective C. --- .../Sources/Wrapper/Reader/ZXIBarcodeReader.h | 6 +++--- .../Wrapper/Reader/ZXIBarcodeReader.mm | 3 +-- .../Sources/Wrapper/Reader/ZXIReaderOptions.h | 20 +++++++++---------- .../Wrapper/Reader/ZXIReaderOptions.mm | 20 +++++++++---------- 4 files changed, 24 insertions(+), 25 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h index 153abcdebb..144842c56a 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.h @@ -16,13 +16,13 @@ NS_ASSUME_NONNULL_BEGIN -(instancetype)initWithOptions:(ZXIReaderOptions*)options; -(nullable NSArray *)readCIImage:(nonnull CIImage *)image - error:(NSError *__autoreleasing _Nullable *)error; + error:(NSError *__autoreleasing _Nullable *)error; -(nullable NSArray *)readCGImage:(nonnull CGImageRef)image - error:(NSError *__autoreleasing _Nullable *)error; + error:(NSError *__autoreleasing _Nullable *)error; -(nullable NSArray *)readCVPixelBuffer:(nonnull CVPixelBufferRef)pixelBuffer - error:(NSError *__autoreleasing _Nullable *)error; + error:(NSError *__autoreleasing _Nullable *)error; @end diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm index 4b21c81bf3..60b7019f81 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIBarcodeReader.mm @@ -98,8 +98,7 @@ - (instancetype)initWithOptions:(ZXIReaderOptions*)options{ NSMutableData *data = [NSMutableData dataWithLength: cols * rows]; - CGContextRef contextRef = CGBitmapContextCreate( - data.mutableBytes,// Pointer to backing data + CGContextRef contextRef = CGBitmapContextCreate(data.mutableBytes,// Pointer to backing data cols, // Width of bitmap rows, // Height of bitmap 8, // Bits per component diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h index cae964c2ff..38cbb26017 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h @@ -21,16 +21,16 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) NSInteger maxNumberOfSymbols; - (instancetype)initWithFormats:(NSArray*)formats - tryHarder:(BOOL)tryHarder - tryRotate:(BOOL)tryRotate - tryInvert:(BOOL)tryInvert - tryDownscale:(BOOL)tryDownscale - tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode - validateCode39CheckSum:(BOOL)validateCode39CheckSum - validateITFCheckSum:(BOOL)validateITFCheckSum - downscaleFactor:(uint8_t)downscaleFactor - downscaleThreshold:(uint16_t)downscaleThreshold - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols; + tryHarder:(BOOL)tryHarder + tryRotate:(BOOL)tryRotate + tryInvert:(BOOL)tryInvert + tryDownscale:(BOOL)tryDownscale + tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode + validateCode39CheckSum:(BOOL)validateCode39CheckSum + validateITFCheckSum:(BOOL)validateITFCheckSum + downscaleFactor:(uint8_t)downscaleFactor + downscaleThreshold:(uint16_t)downscaleThreshold + maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols; @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm index 7cfc8f7901..a9681d0bee 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm @@ -18,16 +18,16 @@ -(instancetype)init { } - (instancetype)initWithFormats:(NSArray*)formats - tryHarder:(BOOL)tryHarder - tryRotate:(BOOL)tryRotate - tryInvert:(BOOL)tryInvert - tryDownscale:(BOOL)tryDownscale - tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode - validateCode39CheckSum:(BOOL)validateCode39CheckSum - validateITFCheckSum:(BOOL)validateITFCheckSum - downscaleFactor:(uint8_t)downscaleFactor - downscaleThreshold:(uint16_t)downscaleThreshold - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { + tryHarder:(BOOL)tryHarder + tryRotate:(BOOL)tryRotate + tryInvert:(BOOL)tryInvert + tryDownscale:(BOOL)tryDownscale + tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode + validateCode39CheckSum:(BOOL)validateCode39CheckSum + validateITFCheckSum:(BOOL)validateITFCheckSum + downscaleFactor:(uint8_t)downscaleFactor + downscaleThreshold:(uint16_t)downscaleThreshold + maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { self = [super init]; self.cppOpts = ZXing::ReaderOptions(); self.tryHarder = tryHarder; From 9ee33a895fcde9349fd89eea0541e07f64d4c4ca Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Wed, 20 Dec 2023 12:58:52 +0100 Subject: [PATCH 179/587] ios: complete reader options Include all native options in `ZXIReaderOptions` to expose all functionality to the iOS wrapper as well. Also match the ordering of the options to correspond with the native `ResultOptions` (and the Android wrapper). --- .../Sources/Wrapper/Reader/ZXIReaderOptions.h | 47 +++- .../Wrapper/Reader/ZXIReaderOptions.mm | 206 +++++++++++++++--- 2 files changed, 215 insertions(+), 38 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h index 38cbb26017..d8221a585f 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h @@ -6,6 +6,27 @@ NS_ASSUME_NONNULL_BEGIN +typedef NS_ENUM(NSInteger, ZXIBinarizer) { + ZXIBinarizerLocalAverage, + ZXIBinarizerGlobalHistogram, + ZXIBinarizerFixedThreshold, + ZXIBinarizerBoolCast +}; + +typedef NS_ENUM(NSInteger, ZXIEanAddOnSymbol) { + ZXIEanAddOnSymbolIgnore, + ZXIEanAddOnSymbolRead, + ZXIEanAddOnSymbolRequire +}; + +typedef NS_ENUM(NSInteger, ZXITextMode) { + ZXITextModePlain, + ZXITextModeECI, + ZXITextModeHRI, + ZXITextModeHex, + ZXITextModeEscaped +}; + @interface ZXIReaderOptions : NSObject /// An array of ZXIFormat @property(nonatomic, strong) NSArray *formats; @@ -13,24 +34,38 @@ NS_ASSUME_NONNULL_BEGIN @property(nonatomic) BOOL tryRotate; @property(nonatomic) BOOL tryInvert; @property(nonatomic) BOOL tryDownscale; +@property(nonatomic) BOOL isPure; +@property(nonatomic) ZXIBinarizer binarizer; +@property(nonatomic) NSInteger downscaleFactor; +@property(nonatomic) NSInteger downscaleThreshold; +@property(nonatomic) NSInteger minLineCount; +@property(nonatomic) NSInteger maxNumberOfSymbols; @property(nonatomic) BOOL tryCode39ExtendedMode; @property(nonatomic) BOOL validateCode39CheckSum; @property(nonatomic) BOOL validateITFCheckSum; -@property(nonatomic) uint8_t downscaleFactor; -@property(nonatomic) uint16_t downscaleThreshold; -@property(nonatomic) NSInteger maxNumberOfSymbols; +@property(nonatomic) BOOL returnCodabarStartEnd; +@property(nonatomic) BOOL returnErrors; +@property(nonatomic) ZXIEanAddOnSymbol eanAddOnSymbol; +@property(nonatomic) ZXITextMode textMode; - (instancetype)initWithFormats:(NSArray*)formats tryHarder:(BOOL)tryHarder tryRotate:(BOOL)tryRotate tryInvert:(BOOL)tryInvert tryDownscale:(BOOL)tryDownscale + isPure:(BOOL)isPure + binarizer:(ZXIBinarizer)binarizer + downscaleFactor:(NSInteger)downscaleFactor + downscaleThreshold:(NSInteger)downscaleThreshold + minLineCount:(NSInteger)minLineCount + maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode validateCode39CheckSum:(BOOL)validateCode39CheckSum validateITFCheckSum:(BOOL)validateITFCheckSum - downscaleFactor:(uint8_t)downscaleFactor - downscaleThreshold:(uint16_t)downscaleThreshold - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols; + returnCodabarStartEnd:(BOOL)returnCodabarStartEnd + returnErrors:(BOOL)returnErrors + eanAddOnSymbol:(ZXIEanAddOnSymbol)eanAddOnSymbol + textMode:(ZXITextMode)textMode; @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm index a9681d0bee..724fc600bf 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm @@ -22,106 +22,248 @@ - (instancetype)initWithFormats:(NSArray*)formats tryRotate:(BOOL)tryRotate tryInvert:(BOOL)tryInvert tryDownscale:(BOOL)tryDownscale + isPure:(BOOL)isPure + binarizer:(ZXIBinarizer)binarizer + downscaleFactor:(NSInteger)downscaleFactor + downscaleThreshold:(NSInteger)downscaleThreshold + minLineCount:(NSInteger)minLineCount + maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode validateCode39CheckSum:(BOOL)validateCode39CheckSum validateITFCheckSum:(BOOL)validateITFCheckSum - downscaleFactor:(uint8_t)downscaleFactor - downscaleThreshold:(uint16_t)downscaleThreshold - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { + returnCodabarStartEnd:(BOOL)returnCodabarStartEnd + returnErrors:(BOOL)returnErrors + eanAddOnSymbol:(ZXIEanAddOnSymbol)eanAddOnSymbol + textMode:(ZXITextMode)textMode { self = [super init]; self.cppOpts = ZXing::ReaderOptions(); + self.formats = formats; self.tryHarder = tryHarder; self.tryRotate = tryRotate; self.tryInvert = tryInvert; self.tryDownscale = tryDownscale; - self.tryCode39ExtendedMode = tryCode39ExtendedMode; - self.validateCode39CheckSum = validateCode39CheckSum; - self.validateITFCheckSum = validateITFCheckSum; + self.isPure = isPure; + self.binarizer = binarizer; self.downscaleFactor = downscaleFactor; self.downscaleThreshold = downscaleThreshold; + self.minLineCount = minLineCount; self.maxNumberOfSymbols = maxNumberOfSymbols; - self.formats = formats; + self.tryCode39ExtendedMode = tryCode39ExtendedMode; + self.validateCode39CheckSum = validateCode39CheckSum; + self.validateITFCheckSum = validateITFCheckSum; + self.returnCodabarStartEnd = returnCodabarStartEnd; + self.returnErrors = returnErrors; + self.eanAddOnSymbol = eanAddOnSymbol; + self.textMode = textMode; return self; } +-(BOOL)tryHarder { + return self.cppOpts.tryHarder(); +} + -(void)setTryHarder:(BOOL)tryHarder { self.cppOpts.setTryHarder(tryHarder); } +-(BOOL)tryRotate { + return self.cppOpts.tryRotate(); +} + -(void)setTryRotate:(BOOL)tryRotate { self.cppOpts.setTryRotate(tryRotate); } +-(BOOL)tryInvert { + return self.cppOpts.tryInvert(); +} + -(void)setTryInvert:(BOOL)tryInvert { self.cppOpts.setTryInvert(tryInvert); } +-(BOOL)tryDownscale { + return self.cppOpts.tryDownscale(); +} + -(void)setTryDownscale:(BOOL)tryDownscale { self.cppOpts.setTryDownscale(tryDownscale); } --(void)setTryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode { - self.cppOpts.setTryCode39ExtendedMode(tryCode39ExtendedMode); +-(BOOL)isPure { + return self.cppOpts.isPure(); } --(void)setValidateCode39CheckSum:(BOOL)validateCode39CheckSum { - self.cppOpts.setValidateCode39CheckSum(validateCode39CheckSum); +-(void)setIsPure:(BOOL)isPure { + self.cppOpts.setIsPure(isPure); } --(void)setValidateITFCheckSum:(BOOL)validateITFCheckSum { - self.cppOpts.setValidateITFCheckSum(validateITFCheckSum); +-(ZXIBinarizer)binarizer { + switch (self.cppOpts.binarizer()) { + default: + case ZXing::Binarizer::LocalAverage: + return ZXIBinarizer::ZXIBinarizerLocalAverage; + case ZXing::Binarizer::GlobalHistogram: + return ZXIBinarizer::ZXIBinarizerGlobalHistogram; + case ZXing::Binarizer::FixedThreshold: + return ZXIBinarizer::ZXIBinarizerFixedThreshold; + case ZXing::Binarizer::BoolCast: + return ZXIBinarizer::ZXIBinarizerBoolCast; + } +} + +ZXing::Binarizer toNativeBinarizer(ZXIBinarizer binarizer) { + switch (binarizer) { + default: + case ZXIBinarizerLocalAverage: + return ZXing::Binarizer::LocalAverage; + case ZXIBinarizerGlobalHistogram: + return ZXing::Binarizer::GlobalHistogram; + case ZXIBinarizerFixedThreshold: + return ZXing::Binarizer::FixedThreshold; + case ZXIBinarizerBoolCast: + return ZXing::Binarizer::BoolCast; + } } --(void)setDownscaleFactor:(uint8_t)downscaleFactor { +-(void)setBinarizer:(ZXIBinarizer)binarizer { + self.cppOpts.setBinarizer(toNativeBinarizer(binarizer)); +} + +-(NSInteger)downscaleFactor { + return self.cppOpts.downscaleFactor(); +} + +-(void)setDownscaleFactor:(NSInteger)downscaleFactor { self.cppOpts.setDownscaleFactor(downscaleFactor); } --(void)setDownscaleThreshold:(uint16_t)downscaleThreshold { - self.cppOpts.setDownscaleThreshold(downscaleThreshold); +-(NSInteger)downscaleThreshold { + return self.cppOpts.downscaleThreshold(); } --(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { - self.cppOpts.setMaxNumberOfSymbols(maxNumberOfSymbols); +-(void)setDownscaleThreshold:(NSInteger)downscaleThreshold { + self.cppOpts.setDownscaleThreshold(downscaleThreshold); } --(BOOL)tryHarder { - return self.cppOpts.tryHarder(); +-(NSInteger)minLineCount { + return self.cppOpts.minLineCount(); } --(BOOL)tryRotate { - return self.cppOpts.tryRotate(); +-(void)setMinLineCount:(NSInteger)minLineCount { + self.cppOpts.setMinLineCount(minLineCount); } --(BOOL)tryInvert { - return self.cppOpts.tryInvert(); +- (NSInteger)maxNumberOfSymbols { + return self.cppOpts.maxNumberOfSymbols(); } --(BOOL)tryDownscale { - return self.cppOpts.tryDownscale(); +-(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { + self.cppOpts.setMaxNumberOfSymbols(maxNumberOfSymbols); } -(BOOL)tryCode39ExtendedMode { return self.cppOpts.tryCode39ExtendedMode(); } +-(void)setTryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode { + self.cppOpts.setTryCode39ExtendedMode(tryCode39ExtendedMode); +} + -(BOOL)validateCode39CheckSum { return self.cppOpts.validateCode39CheckSum(); } +-(void)setValidateCode39CheckSum:(BOOL)validateCode39CheckSum { + self.cppOpts.setValidateCode39CheckSum(validateCode39CheckSum); +} + -(BOOL)validateITFCheckSum { return self.cppOpts.validateITFCheckSum(); } --(uint8_t)downscaleFactor { - return self.cppOpts.downscaleFactor(); +-(void)setValidateITFCheckSum:(BOOL)validateITFCheckSum { + self.cppOpts.setValidateITFCheckSum(validateITFCheckSum); } --(uint16_t)downscaleThreshold { - return self.cppOpts.downscaleThreshold(); +-(BOOL)returnCodabarStartEnd { + return self.cppOpts.returnCodabarStartEnd(); } -- (NSInteger)maxNumberOfSymbols { - return self.cppOpts.maxNumberOfSymbols(); +-(void)setReturnCodabarStartEnd:(BOOL)returnCodabarStartEnd { + self.cppOpts.setReturnCodabarStartEnd(returnCodabarStartEnd); +} + +-(BOOL)returnErrors { + return self.cppOpts.returnErrors(); +} + +-(void)setReturnErrors:(BOOL)returnErrors { + self.cppOpts.setReturnErrors(returnErrors); +} + +-(ZXIEanAddOnSymbol)eanAddOnSymbol { + switch (self.cppOpts.eanAddOnSymbol()) { + default: + case ZXing::EanAddOnSymbol::Ignore: + return ZXIEanAddOnSymbol::ZXIEanAddOnSymbolIgnore; + case ZXing::EanAddOnSymbol::Read: + return ZXIEanAddOnSymbol::ZXIEanAddOnSymbolRead; + case ZXing::EanAddOnSymbol::Require: + return ZXIEanAddOnSymbol::ZXIEanAddOnSymbolRequire; + } +} + +ZXing::EanAddOnSymbol toNativeEanAddOnSymbol(ZXIEanAddOnSymbol eanAddOnSymbol) { + switch (eanAddOnSymbol) { + default: + case ZXIEanAddOnSymbolIgnore: + return ZXing::EanAddOnSymbol::Ignore; + case ZXIEanAddOnSymbolRead: + return ZXing::EanAddOnSymbol::Read; + case ZXIEanAddOnSymbolRequire: + return ZXing::EanAddOnSymbol::Require; + } +} + +-(void)setEanAddOnSymbol:(ZXIEanAddOnSymbol)eanAddOnSymbol { + self.cppOpts.setEanAddOnSymbol(toNativeEanAddOnSymbol(eanAddOnSymbol)); +} + +-(ZXITextMode)textMode { + switch (self.cppOpts.textMode()) { + default: + case ZXing::TextMode::Plain: + return ZXITextMode::ZXITextModePlain; + case ZXing::TextMode::ECI: + return ZXITextMode::ZXITextModeECI; + case ZXing::TextMode::HRI: + return ZXITextMode::ZXITextModeHRI; + case ZXing::TextMode::Hex: + return ZXITextMode::ZXITextModeHex; + case ZXing::TextMode::Escaped: + return ZXITextMode::ZXITextModeEscaped; + } +} + +ZXing::TextMode toNativeTextMode(ZXITextMode mode) { + switch (mode) { + default: + case ZXITextModePlain: + return ZXing::TextMode::Plain; + case ZXITextModeECI: + return ZXing::TextMode::ECI; + case ZXITextModeHRI: + return ZXing::TextMode::HRI; + case ZXITextModeHex: + return ZXing::TextMode::Hex; + case ZXITextModeEscaped: + return ZXing::TextMode::Escaped; + } +} + +-(void)setTextMode:(ZXITextMode)textMode { + self.cppOpts.setTextMode(toNativeTextMode(textMode)); } @end From ac2935a939e3d1ff611f0e8f81cc00b19b8e19df Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 23 Dec 2023 13:35:38 +0100 Subject: [PATCH 180/587] Reduce: make consistent use of Reduce() vs. std::accumulate() Thought about introducing std::reduce as a faster replacement for std::accumulate. std::reduce() is not required to be a strict left-fold operation, meaning the order of the op-application is not sequenced left-to-right. This sounded like an optimization opportunity but it turns out that for this use case it actually does not make a difference (falsepositives runtime). And when tested with a large std::vector and proper auto-vectorization (e.g. clang++ -O2), it turns out that std::accumulate() can be twice as fast as std::reduce. There are 3 uses of std::accumulate (instead of Reduce) left in the code base where a strict left-fold operation is required. --- core/src/ConcentricFinder.h | 2 +- core/src/GenericGFPoly.cpp | 14 +++++--------- core/src/Pattern.h | 6 +++--- core/src/RegressionLine.h | 5 ++--- core/src/ZXAlgorithms.h | 12 +++++++++++- core/src/aztec/AZDetector.cpp | 2 +- core/src/oned/ODDXFilmEdgeReader.cpp | 2 +- core/src/oned/ODRowReader.h | 4 ++-- core/src/pdf417/PDFModulusPoly.cpp | 21 +++++++-------------- core/src/qrcode/QRDecoder.cpp | 2 +- core/src/qrcode/QRDetector.cpp | 2 +- 11 files changed, 35 insertions(+), 37 deletions(-) diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index 02c72c171c..68f29b6d45 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -27,7 +27,7 @@ static float CenterFromEnd(const std::array& pattern, float end) float b = (pattern[2] + pattern[1] + pattern[0]) / 2.f; return end - (2 * a + b) / 3; } else { // aztec - auto a = std::accumulate(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.f); + auto a = Reduce(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.f); return end - a; } } diff --git a/core/src/GenericGFPoly.cpp b/core/src/GenericGFPoly.cpp index 1ec9f84fd3..a624e487e2 100644 --- a/core/src/GenericGFPoly.cpp +++ b/core/src/GenericGFPoly.cpp @@ -19,18 +19,14 @@ namespace ZXing { int GenericGFPoly::evaluateAt(int a) const { - if (a == 0) - // Just return the x^0 coefficient + if (a == 0) // return the x^0 coefficient return constant(); - if (a == 1) - // Just the sum of the coefficients - return Reduce(_coefficients, 0, [](auto a, auto b) { return a ^ b; }); + if (a == 1) // return the sum of the coefficients + return Reduce(_coefficients, 0, [](auto s, auto c) { return s ^ c; }); - int result = _coefficients[0]; - for (size_t i = 1; i < _coefficients.size(); ++i) - result = _field->multiply(a, result) ^ _coefficients[i]; - return result; + return std::accumulate(_coefficients.begin(), _coefficients.end(), 0, + [this, a](auto s, auto c) { return _field->multiply(a, s) ^ c; }); } GenericGFPoly& GenericGFPoly::addOrSubtract(GenericGFPoly& other) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 2f38b99125..579354e387 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -59,13 +59,13 @@ class PatternView return _data[i]; } - int sum(int n = 0) const { return std::accumulate(_data, _data + (n == 0 ? _size : n), 0); } + int sum(int n = 0) const { return Reduce(_data, _data + (n == 0 ? _size : n)); } int size() const { return _size; } // index is the number of bars and spaces from the first bar to the current position int index() const { return narrow_cast(_data - _base) - 1; } - int pixelsInFront() const { return std::accumulate(_base, _data, 0); } - int pixelsTillEnd() const { return std::accumulate(_base, _data + _size, 0) - 1; } + int pixelsInFront() const { return Reduce(_base, _data); } + int pixelsTillEnd() const { return Reduce(_base, _data + _size) - 1; } bool isAtFirstBar() const { return _data == _base + 1; } bool isAtLastBar() const { return _data + _size == _end - 1; } bool isValid(int n) const { return _data && _data >= _base && _data + n <= _end; } diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index dac602ee9f..c536a8e475 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -10,7 +10,6 @@ #include #include -#include #include #ifdef PRINT_DEBUG @@ -30,7 +29,7 @@ class RegressionLine template bool evaluate(const PointT* begin, const PointT* end) { - auto mean = std::accumulate(begin, end, PointF()) / std::distance(begin, end); + auto mean = Reduce(begin, end, PointF()) / std::distance(begin, end); PointF::value_t sumXX = 0, sumYY = 0, sumXY = 0; for (auto p = begin; p != end; ++p) { auto d = *p - mean; @@ -79,7 +78,7 @@ class RegressionLine auto signedDistance(PointF p) const { return dot(normal(), p) - c; } template auto distance(PointT p) const { return std::abs(signedDistance(PointF(p))); } PointF project(PointF p) const { return p - signedDistance(p) * normal(); } - PointF centroid() const { return std::accumulate(_points.begin(), _points.end(), PointF()) / _points.size(); } + PointF centroid() const { return Reduce(_points) / _points.size(); } void reset() { diff --git a/core/src/ZXAlgorithms.h b/core/src/ZXAlgorithms.h index 3965ab0746..e6f1b99368 100644 --- a/core/src/ZXAlgorithms.h +++ b/core/src/ZXAlgorithms.h @@ -52,9 +52,19 @@ auto FirstOrDefault(C&& results) return results.empty() ? typename C::value_type() : std::move(results.front()); } +template ::value_type, typename Op = std::plus> +Value Reduce(Iterator b, Iterator e, Value v = Value{}, Op op = {}) { + // std::reduce() first sounded like a better implementation because it is not implemented as a strict left-fold + // operation, meaning the order of the op-application is not specified. This sounded like an optimization opportunity + // but it turns out that for this use case it actually does not make a difference (falsepositives runtime). And + // when tested with a large std::vector and proper autovectorization (e.g. clang++ -O2) it turns out that + // std::accumulate can be twice as fast as std::reduce. + return std::accumulate(b, e, v, op); +} + template > Value Reduce(const Container& c, Value v = Value{}, Op op = {}) { - return std::accumulate(std::begin(c), std::end(c), v, op); + return Reduce(std::begin(c), std::end(c), v, op); } // see C++20 ssize diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 87915060aa..500c181c5a 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -210,7 +210,7 @@ static std::vector FindFinderPatterns(const BitMatrix& image, ++N; log(p, 1); - auto pattern = LocateAztecCenter(image, p, Reduce(next)); + auto pattern = LocateAztecCenter(image, p, next.sum()); if (pattern) { log(*pattern, 3); assert(image.get(*pattern)); diff --git a/core/src/oned/ODDXFilmEdgeReader.cpp b/core/src/oned/ODDXFilmEdgeReader.cpp index 4793f906ea..879899ee0b 100644 --- a/core/src/oned/ODDXFilmEdgeReader.cpp +++ b/core/src/oned/ODDXFilmEdgeReader.cpp @@ -183,7 +183,7 @@ Result DXFilmEdgeReader::decodePattern(int rowNumber, PatternView& next, std::un return {}; // Check the parity bit - auto signalSum = std::accumulate(dataBits.begin(), dataBits.end() - 2, 0); + auto signalSum = Reduce(dataBits.begin(), dataBits.end() - 2, 0); auto parityBit = *(dataBits.end() - 2); if (signalSum % 2 != (int)parityBit) return {}; diff --git a/core/src/oned/ODRowReader.h b/core/src/oned/ODRowReader.h index 7e3e2e932a..d1185929b1 100644 --- a/core/src/oned/ODRowReader.h +++ b/core/src/oned/ODRowReader.h @@ -79,8 +79,8 @@ class RowReader template static float PatternMatchVariance(const CP* counters, const PP* pattern, size_t length, float maxIndividualVariance) { - int total = std::accumulate(counters, counters+length, 0); - int patternLength = std::accumulate(pattern, pattern+length, 0); + int total = Reduce(counters, counters + length, 0); + int patternLength = Reduce(pattern, pattern + length, 0); if (total < patternLength) { // If we don't even have one pixel per unit of bar width, assume this is too small // to reliably match, so fail: diff --git a/core/src/pdf417/PDFModulusPoly.cpp b/core/src/pdf417/PDFModulusPoly.cpp index 9883775f77..bcd2a6b894 100644 --- a/core/src/pdf417/PDFModulusPoly.cpp +++ b/core/src/pdf417/PDFModulusPoly.cpp @@ -42,21 +42,14 @@ ModulusPoly::ModulusPoly(const ModulusGF& field, const std::vector& coeffic int ModulusPoly::evaluateAt(int a) const { - if (a == 0) { - // Just return the x^0 coefficient + if (a == 0) // return the x^0 coefficient return coefficient(0); - } - size_t size = _coefficients.size(); - if (a == 1) { - // Just the sum of the coefficients - const auto op = [this](auto result, const auto coefficient){ return _field->add(result, coefficient);}; - return std::accumulate(std::begin(_coefficients), std::end(_coefficients), int{}, op); - } - int result = _coefficients[0]; - for (size_t i = 1; i < size; i++) { - result = _field->add(_field->multiply(a, result), _coefficients[i]); - } - return result; + + if (a == 1) // return the sum of the coefficients + return Reduce(_coefficients, 0, [this](auto res, auto coef) { return _field->add(res, coef); }); + + return std::accumulate(_coefficients.begin(), _coefficients.end(), 0, + [this, a](auto res, auto coef) { return _field->add(_field->multiply(a, res), coef); }); } ModulusPoly diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index f6ef382de5..c8f8d75d18 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -346,7 +346,7 @@ DecoderResult Decode(const BitMatrix& bits) // Count total number of data bytes const auto op = [](auto totalBytes, const auto& dataBlock){ return totalBytes + dataBlock.numDataCodewords();}; - const auto totalBytes = std::accumulate(std::begin(dataBlocks), std::end(dataBlocks), int{}, op); + const auto totalBytes = Reduce(dataBlocks, int{}, op); ByteArray resultBytes(totalBytes); auto resultIterator = resultBytes.begin(); diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 326251072d..d98e200045 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -79,7 +79,7 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t log(p); N++; auto pattern = LocateConcentricPattern(image, PATTERN, p, - Reduce(next) * 3); // 3 for very skewed samples + next.sum() * 3); // 3 for very skewed samples if (pattern) { log(*pattern, 3); log(*pattern + PointF(.2, 0), 3); From 17eff515637880a933727b5787ded0b979717760 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 26 Dec 2023 16:30:30 +0100 Subject: [PATCH 181/587] python: add support to write `bytes` as binary data Adding support to write both `str` and `bytes` via `write_barcode`. If passed a `bytes` buffer, it will be encoded as binary data. This can be considered a breaking change, because passing a `bytes` buffer that happened to contain a valid utf8 string was converted to a standard text due to the automatic conversion support of pybind11. This fixes #694. --- wrappers/python/test.py | 13 ++++++++++++- wrappers/python/zxing.cpp | 18 +++++++++++++----- 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/wrappers/python/test.py b/wrappers/python/test.py index a086b24413..284d8d3869 100644 --- a/wrappers/python/test.py +++ b/wrappers/python/test.py @@ -9,7 +9,7 @@ has_cv2 = importlib.util.find_spec('cv2') is not None BF = zxingcpp.BarcodeFormat - +CT = zxingcpp.ContentType class TestFormat(unittest.TestCase): @@ -26,6 +26,7 @@ def check_res(self, res, format, text): self.assertEqual(res.text, text) self.assertEqual(res.bytes, bytes(text, 'utf-8')) self.assertEqual(res.orientation, 0) + self.assertEqual(res.content_type, CT.Text) def test_write_read_cycle(self): format = BF.QRCode @@ -61,6 +62,16 @@ def test_write_read_multi_cycle(self): res = zxingcpp.read_barcodes(img)[0] self.check_res(res, format, text) + def test_write_read_bytes_cycle(self): + format = BF.QRCode + text = b"\1\2\3\4" + img = zxingcpp.write_barcode(format, text) + + res = zxingcpp.read_barcode(img) + self.assertTrue(res.valid) + self.assertEqual(res.bytes, text) + self.assertEqual(res.content_type, CT.Binary) + @staticmethod def zeroes(shape): return memoryview(b"0" * math.prod(shape)).cast("B", shape=shape) diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index e9f4bb36cf..f2d2936610 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -158,10 +158,18 @@ Results read_barcodes(py::object _image, const BarcodeFormats& formats, bool try return_errors); } -Matrix write_barcode(BarcodeFormat format, std::string text, int width, int height, int quiet_zone, int ec_level) +Matrix write_barcode(BarcodeFormat format, py::object content, int width, int height, int quiet_zone, int ec_level) { - auto writer = MultiFormatWriter(format).setEncoding(CharacterSet::UTF8).setMargin(quiet_zone).setEccLevel(ec_level); - auto bitmap = writer.encode(text, width, height); + CharacterSet encoding; + if (py::isinstance(content)) + encoding = CharacterSet::UTF8; + else if (py::isinstance(content)) + encoding = CharacterSet::BINARY; + else + throw py::type_error("Invalid input: only 'str' and 'bytes' supported."); + + auto writer = MultiFormatWriter(format).setEncoding(encoding).setMargin(quiet_zone).setEccLevel(ec_level); + auto bitmap = writer.encode(py::cast(content), width, height); return ToMatrix(bitmap); } @@ -432,8 +440,8 @@ PYBIND11_MODULE(zxingcpp, m) "Write (encode) a text into a barcode and return 8-bit grayscale bitmap buffer\n\n" ":type format: zxing.BarcodeFormat\n" ":param format: format of the barcode to create\n" - ":type text: str\n" - ":param text: the text of barcode\n" + ":type text: str|bytes\n" + ":param text: the text/content of the barcode. A str is encoded as utf8 text and bytes as binary data\n" ":type width: int\n" ":param width: width (in pixels) of the barcode to create. If undefined (or set to 0), barcode will be\n" " created with the minimum possible width\n" From f631c2a47864a4cbeffefaa518f7f0643d1f5b9c Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 28 Dec 2023 13:10:25 +0100 Subject: [PATCH 182/587] c-API: support passing NULL as opts parameter to zxing_ReadBardcode --- wrappers/c/zxing-c.cpp | 4 ++-- wrappers/c/zxing-c.h | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index 00c09963fe..af444edc8f 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -211,13 +211,13 @@ bool zxing_Result_isMirrored(const zxing_Result* result) zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcode(*iv, *opts); + auto res = ReadBarcode(*iv, opts ? *opts : ReaderOptions{}); return res.format() != BarcodeFormat::None ? new Result(std::move(res)) : NULL; } zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodes(*iv, *opts); + auto res = ReadBarcodes(*iv, opts ? *opts : ReaderOptions{}); return !res.empty() ? new Results(std::move(res)) : NULL; } diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index cf19c8ed3f..38289da106 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -172,6 +172,7 @@ bool zxing_Result_isMirrored(const zxing_Result* result); * ZXing/ReadBarcode.h */ +/** Note: opts is optional, i.e. it can be NULL, which will imply default settings. */ zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); From 82be565c80495dd8c5c186a26eaeb95e3b2a0513 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 29 Dec 2023 01:57:25 +0100 Subject: [PATCH 183/587] Error: add check for empty ImageView in ReadBardcodes Also: make error messages consistently start with capital letter. --- core/src/BitMatrix.h | 2 +- core/src/Matrix.h | 2 +- core/src/ReadBarcode.cpp | 5 ++++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index 6d7dd54c9e..fe8ff20cfb 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -64,7 +64,7 @@ class BitMatrix BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V) { if (width != 0 && Size(_bits) / width != height) - throw std::invalid_argument("invalid size: width * height is too big"); + throw std::invalid_argument("Invalid size: width * height is too big"); } explicit BitMatrix(int dimension) : BitMatrix(dimension, dimension) {} // Construct a square matrix. diff --git a/core/src/Matrix.h b/core/src/Matrix.h index 36d4a0efe2..8e6cbbdc90 100644 --- a/core/src/Matrix.h +++ b/core/src/Matrix.h @@ -40,7 +40,7 @@ class Matrix #endif Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) { if (width != 0 && Size(_data) / width != height) - throw std::invalid_argument("invalid size: width * height is too big"); + throw std::invalid_argument("Invalid size: width * height is too big"); } Matrix(Matrix&&) noexcept = default; diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 43269e0c6d..9941bdde0a 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -136,7 +136,10 @@ Result ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) { if (sizeof(PatternType) < 4 && opts.hasFormat(BarcodeFormat::LinearCodes) && (_iv.width() > 0xffff || _iv.height() > 0xffff)) - throw std::invalid_argument("maximum image width/height is 65535"); + throw std::invalid_argument("Maximum image width/height is 65535"); + + if (!_iv.data(0, 0) || _iv.width() * _iv.height() == 0) + throw std::invalid_argument("ImageView is null/empty"); LumImage lum; ImageView iv = SetupLumImageView(_iv, lum, opts); From 83593fcf157c1d334d3af5ae1aaf6df3e47515f5 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 29 Dec 2023 02:13:08 +0100 Subject: [PATCH 184/587] c-API: add `zxing_LastErrorMsg()` function `zxing_ReadBarcodes()` may fail when called with null/empty `ImageView`. `zxing_LastErrorMsg()` may be used to determine the case cause of a failed call. --- wrappers/c/README.md | 8 +++++++- wrappers/c/zxing-c-test.c | 20 ++++++++++++++++---- wrappers/c/zxing-c.cpp | 40 ++++++++++++++++++++++++++++++++++----- wrappers/c/zxing-c.h | 2 ++ 4 files changed, 60 insertions(+), 10 deletions(-) diff --git a/wrappers/c/README.md b/wrappers/c/README.md index 550565ae71..4b0c3ab013 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -35,7 +35,13 @@ int main(int argc, char** argv) zxing_Result_delete(result); } else { - printf("No barcode found\n"); + const char* error = zxing_LastErrorMsg(); + if (error) { + printf("%s\n", error); + free(error); + } else { + printf("No barcode found\n"); + } } zxing_ImageView_delete(iv); diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index 0497a1cadd..09f7c742c4 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -6,6 +6,9 @@ #include "zxing-c.h" +#include +#include + #define STB_IMAGE_IMPLEMENTATION #include @@ -23,7 +26,7 @@ bool parse_args(int argc, char** argv, char** filename, zxing_BarcodeFormats* fo if (argc >= 3) { *formats = zxing_BarcodeFormatsFromString(argv[2]); if (*formats == zxing_BarcodeFormat_Invalid) { - fprintf(stderr, "Invalid barcode formats string '%s'\n", argv[2]); + fprintf(stderr, "%s\n", zxing_LastErrorMsg()); return false; } } @@ -41,6 +44,7 @@ void printF(const char* fmt, char* text) int main(int argc, char** argv) { + int ret = 0; char* filename = NULL; zxing_BarcodeFormats formats = zxing_BarcodeFormat_None; @@ -51,8 +55,10 @@ int main(int argc, char** argv) int height = 0; int channels = 0; stbi_uc* data = stbi_load(filename, &width, &height, &channels, STBI_grey); - if (!data) + if (!data) { + fprintf(stderr, "Could not read image '%s'\n", filename); return 2; + } zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); zxing_ReaderOptions_setTextMode(opts, zxing_TextMode_HRI); @@ -82,12 +88,18 @@ int main(int argc, char** argv) zxing_Results_delete(results); } else { - printf("No barcode found\n"); + const char* error = zxing_LastErrorMsg(); + if (error) { + fprintf(stderr, "%s\n", error); + ret = 2; + } else { + printf("No barcode found\n"); + } } zxing_ImageView_delete(iv); zxing_ReaderOptions_delete(opts); stbi_image_free(data); - return 0; + return ret; } diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index af444edc8f..5ebba9004a 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -10,7 +10,9 @@ using namespace ZXing; -char* copy(std::string_view sv) +static thread_local std::string lastErrorMsg; + +static char* copy(std::string_view sv) { auto ret = (char*)malloc(sv.size() + 1); if (ret) { @@ -48,9 +50,13 @@ zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str) try { auto format = BarcodeFormatsFromString(str); return static_cast(*reinterpret_cast(&format)); + } catch (std::exception& e) { + lastErrorMsg = e.what(); } catch (...) { - return zxing_BarcodeFormat_Invalid; + lastErrorMsg = "Unknown error."; } + + return zxing_BarcodeFormat_Invalid; } zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str) @@ -209,15 +215,31 @@ bool zxing_Result_isMirrored(const zxing_Result* result) * ZXing/ReadBarcode.h */ +static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +{ + try { + if (iv) + return ReadBarcodes(*iv, opts ? *opts : ReaderOptions{}); + else + lastErrorMsg = "ImageView param is NULL."; + } catch (std::exception& e) { + lastErrorMsg = e.what(); + } catch (...) { + lastErrorMsg = "Unknown error."; + } + + return {}; +} + zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcode(*iv, opts ? *opts : ReaderOptions{}); - return res.format() != BarcodeFormat::None ? new Result(std::move(res)) : NULL; + auto res = ReadBarcodesAndSetLastError(iv, opts); + return !res.empty() ? new Result(std::move(res.front())) : NULL; } zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodes(*iv, opts ? *opts : ReaderOptions{}); + auto res = ReadBarcodesAndSetLastError(iv, opts); return !res.empty() ? new Results(std::move(res)) : NULL; } @@ -243,4 +265,12 @@ const zxing_Result* zxing_Results_at(const zxing_Results* results, int i) return &(*results)[i]; } +char* zxing_LastErrorMsg() +{ + if (lastErrorMsg.empty()) + return NULL; + + return copy(std::exchange(lastErrorMsg, {})); +} + } // extern "C" diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index 38289da106..f19ac8f0e6 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -182,6 +182,8 @@ void zxing_Results_delete(zxing_Results* results); int zxing_Results_size(const zxing_Results* results); const zxing_Result* zxing_Results_at(const zxing_Results* results, int i); +char* zxing_LastErrorMsg(); + #ifdef __cplusplus } #endif From 9288ccaa0efb556b27892c1da1a0bd51e5387f06 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 29 Dec 2023 12:41:36 +0100 Subject: [PATCH 185/587] c-API: fix build regression on MSVC --- wrappers/c/zxing-c.cpp | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index 5ebba9004a..7fe646da37 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -22,6 +22,22 @@ static char* copy(std::string_view sv) return ret; } +static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +{ + try { + if (iv) + return ReadBarcodes(*iv, opts ? *opts : ReaderOptions{}); + else + lastErrorMsg = "ImageView param is NULL"; + } catch (std::exception& e) { + lastErrorMsg = e.what(); + } catch (...) { + lastErrorMsg = "Unknown error"; + } + + return {}; +} + extern "C" { /* * ZXing/ImageView.h @@ -53,7 +69,7 @@ zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str) } catch (std::exception& e) { lastErrorMsg = e.what(); } catch (...) { - lastErrorMsg = "Unknown error."; + lastErrorMsg = "Unknown error"; } return zxing_BarcodeFormat_Invalid; @@ -215,22 +231,6 @@ bool zxing_Result_isMirrored(const zxing_Result* result) * ZXing/ReadBarcode.h */ -static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) -{ - try { - if (iv) - return ReadBarcodes(*iv, opts ? *opts : ReaderOptions{}); - else - lastErrorMsg = "ImageView param is NULL."; - } catch (std::exception& e) { - lastErrorMsg = e.what(); - } catch (...) { - lastErrorMsg = "Unknown error."; - } - - return {}; -} - zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { auto res = ReadBarcodesAndSetLastError(iv, opts); From a568644c1178c2afa6d64d0cfad7dff69b545863 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 29 Dec 2023 12:48:06 +0100 Subject: [PATCH 186/587] c-API: fix performace regression (missing `setMaxNumberOfSymbols()`) --- wrappers/c/zxing-c.cpp | 21 +++++++++++++++------ 1 file changed, 15 insertions(+), 6 deletions(-) diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index 7fe646da37..c7446308fe 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -8,6 +8,12 @@ #include "ReadBarcode.h" +#include +#include +#include +#include +#include + using namespace ZXing; static thread_local std::string lastErrorMsg; @@ -22,12 +28,15 @@ static char* copy(std::string_view sv) return ret; } -static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts, int maxSymbols) { try { - if (iv) - return ReadBarcodes(*iv, opts ? *opts : ReaderOptions{}); - else + if (iv) { + auto o = opts ? *opts : ReaderOptions{}; + if (maxSymbols) + o.setMaxNumberOfSymbols(maxSymbols); + return ReadBarcodes(*iv, o); + } else lastErrorMsg = "ImageView param is NULL"; } catch (std::exception& e) { lastErrorMsg = e.what(); @@ -233,13 +242,13 @@ bool zxing_Result_isMirrored(const zxing_Result* result) zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodesAndSetLastError(iv, opts); + auto res = ReadBarcodesAndSetLastError(iv, opts, 1); return !res.empty() ? new Result(std::move(res.front())) : NULL; } zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodesAndSetLastError(iv, opts); + auto res = ReadBarcodesAndSetLastError(iv, opts, 0); return !res.empty() ? new Results(std::move(res)) : NULL; } From 0dbcb6d97debeb7ae5bd180dcd83fecca0132808 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 31 Dec 2023 01:44:06 +0100 Subject: [PATCH 187/587] c++: fix clazy warnings about using static non-POD types --- core/src/datamatrix/DMHighLevelEncoder.cpp | 14 +++++++------- core/src/pdf417/PDFDetector.cpp | 11 ++++++----- 2 files changed, 13 insertions(+), 12 deletions(-) diff --git a/core/src/datamatrix/DMHighLevelEncoder.cpp b/core/src/datamatrix/DMHighLevelEncoder.cpp index db0859347f..5e98282630 100644 --- a/core/src/datamatrix/DMHighLevelEncoder.cpp +++ b/core/src/datamatrix/DMHighLevelEncoder.cpp @@ -19,7 +19,6 @@ #include #include #include -#include #include #include @@ -32,10 +31,6 @@ static const uint8_t MACRO_06 = 237; static const uint8_t C40_UNLATCH = 254; static const uint8_t X12_UNLATCH = 254; -static const std::wstring MACRO_05_HEADER = L"[)>\x1E""05\x1D"; -static const std::wstring MACRO_06_HEADER = L"[)>\x1E""06\x1D"; -static const std::wstring MACRO_TRAILER = L"\x1E\x04"; - enum { ASCII_ENCODATION, @@ -845,12 +840,13 @@ namespace Base256Encoder { } // Base256Encoder -static bool StartsWith(const std::wstring& s, const std::wstring& ss) +//TODO: c++20 +static bool StartsWith(std::wstring_view s, std::wstring_view ss) { return s.length() > ss.length() && s.compare(0, ss.length(), ss) == 0; } -static bool EndsWith(const std::wstring& s, const std::wstring& ss) +static bool EndsWith(std::wstring_view s, std::wstring_view ss) { return s.length() > ss.length() && s.compare(s.length() - ss.length(), ss.length(), ss) == 0; } @@ -887,6 +883,10 @@ ByteArray Encode(const std::wstring& msg, CharacterSet charset, SymbolShape shap context.setSymbolShape(shape); context.setSizeConstraints(minWidth, minHeight, maxWidth, maxHeight); + constexpr std::wstring_view MACRO_05_HEADER = L"[)>\x1E""05\x1D"; + constexpr std::wstring_view MACRO_06_HEADER = L"[)>\x1E""06\x1D"; + constexpr std::wstring_view MACRO_TRAILER = L"\x1E\x04"; + if (StartsWith(msg, MACRO_05_HEADER) && EndsWith(msg, MACRO_TRAILER)) { context.addCodeword(MACRO_05); context.setSkipAtEnd(2); diff --git a/core/src/pdf417/PDFDetector.cpp b/core/src/pdf417/PDFDetector.cpp index e09edf479f..8926823db9 100644 --- a/core/src/pdf417/PDFDetector.cpp +++ b/core/src/pdf417/PDFDetector.cpp @@ -25,11 +25,6 @@ static const int INDEXES_STOP_PATTERN[] = { 6, 2, 7, 3 }; static const float MAX_AVG_VARIANCE = 0.42f; static const float MAX_INDIVIDUAL_VARIANCE = 0.8f; -// B S B S B S B S Bar/Space pattern -// 11111111 0 1 0 1 0 1 000 -static const std::vector START_PATTERN = { 8, 1, 1, 1, 1, 1, 1, 3 }; -// 1111111 0 1 000 1 0 1 00 1 -static const std::vector STOP_PATTERN = { 7, 1, 1, 3, 1, 1, 1, 2, 1 }; static const int MAX_PIXEL_DRIFT = 3; static const int MAX_PATTERN_DRIFT = 5; // if we set the value too low, then we don't detect the correct height of the bar if the start patterns are damaged. @@ -226,6 +221,12 @@ CopyToResult(std::array, 8>& result, const std::array, 8> FindVertices(const BitMatrix& matrix, int startRow, int startColumn) { + // B S B S B S B S Bar/Space pattern + // 11111111 0 1 0 1 0 1 000 + static const std::vector START_PATTERN = { 8, 1, 1, 1, 1, 1, 1, 3 }; + // 1111111 0 1 000 1 0 1 00 1 + static const std::vector STOP_PATTERN = { 7, 1, 1, 3, 1, 1, 1, 2, 1 }; + int width = matrix.width(); int height = matrix.height(); From 1ff0e5a00e4a4fd8ede7a59e31ee40de34011cd9 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 5 Jan 2024 12:19:20 +0100 Subject: [PATCH 188/587] ci: switch manylinux image in cibuildwheel to support c++20 This is supposed to help with fixing the missing multi-symbol DataMatrix support in pre-build Linux PyPi packages (see #695). --- .github/workflows/publish-python.yml | 2 ++ wrappers/python/CMakeLists.txt | 5 +++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index 637e91e557..3d59ccb49b 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -40,6 +40,8 @@ jobs: env: CIBW_BUILD: cp38-* cp39-* cp310-* cp311-* cp312-* CIBW_SKIP: "*musllinux*" + # the default maylinux2014 image does not contain a c++20 compiler, see https://github.com/pypa/manylinux + CIBW_MANYLINUX_X86_64_IMAGE: quay.io/pypa/manylinux_2_28_x86_64 CIBW_ARCHS_MACOS: universal2 CIBW_ENVIRONMENT_MACOS: CMAKE_OSX_ARCHITECTURES="arm64;x86_64" diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 8c1be1872e..9610420485 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -9,7 +9,8 @@ get_directory_property(hasParent PARENT_DIRECTORY) if (NOT hasParent) # Build with C++20 by default (which enables position independent DataMatrix detection). set(CMAKE_CXX_STANDARD 20) - # Allow the fallback to earlier versions of the compiler does not support it. + set(CMAKE_CXX_EXTENSIONS OFF) + # Allow the fallback to earlier versions if the compiler does not support it. set(CMAKE_CXX_STANDARD_REQUIRED OFF) option (BUILD_SHARED_LIBS "Link python module to shared lib" OFF) @@ -24,7 +25,7 @@ if (NOT hasParent) endif() endif() -find_package(Python3 COMPONENTS Interpreter Development REQUIRED) # see https://github.com/pybind/pybind11/issues/4785 +find_package(Python3 COMPONENTS Interpreter Development.Module REQUIRED) # see https://github.com/pybind/pybind11/issues/4785 zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) # build the python module From 5d623be0422d4e146e63f9bf0d25754c9797fb06 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 5 Jan 2024 12:57:37 +0100 Subject: [PATCH 189/587] Result: add TODO comment about structure packing --- core/src/Result.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/Result.h b/core/src/Result.h index 30a180f48e..7d46238551 100644 --- a/core/src/Result.h +++ b/core/src/Result.h @@ -157,7 +157,7 @@ class Result Content _content; Error _error; Position _position; - ReaderOptions _readerOpts; + ReaderOptions _readerOpts; // TODO: 3.0 switch order to prevent 4 padding bytes StructuredAppendInfo _sai; BarcodeFormat _format = BarcodeFormat::None; char _ecLevel[4] = {}; From c97fc94e0e129feb0c3596371f86d725540322d7 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 5 Jan 2024 18:37:13 +0100 Subject: [PATCH 190/587] python: add README note about installing from source for DataMatrix support See also here: https://github.com/zxing-cpp/zxing-cpp/issues/695#issuecomment-1878324515 --- wrappers/python/README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/wrappers/python/README.md b/wrappers/python/README.md index 2d7df24bc4..c3de4ef9fa 100644 --- a/wrappers/python/README.md +++ b/wrappers/python/README.md @@ -2,6 +2,7 @@ [![PyPI](https://img.shields.io/pypi/v/zxing-cpp.svg)](https://pypi.org/project/zxing-cpp/) + ## Installation ```bash @@ -13,7 +14,14 @@ or python setup.py install ``` -[Note: To install via `setup.py` (or via `pip install` in case there is no pre-build wheel available for your platfor or python version), you need a suitable [build environment](https://github.com/zxing-cpp/zxing-cpp#build-instructions) including a c++ compiler.] +**Note**: To enable position independent and multi-symbol DataMatrix detection, the library needs to be compiled with a c++20 compiler. Unfortunatelly some build environments used by `cibuildwheel` to generate the binary wheels that are published on [pypi.org](https://pypi.org/project/zxing-cpp/) don't include a c++20 compiler. Best chance to enable proper DataMatrix support in that case is by installing from source: + +```bash +pip install zxing-cpp --no-binary zxing-cpp +``` + +In that case or if there is no pre-build wheel available for your platform or python version or if you use `setup.py` directly, a suitable [build environment](https://github.com/zxing-cpp/zxing-cpp#build-instructions) including a c++ compiler is required. + ## Usage From d6177733db1e5af6b010de08079476dc045af76f Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 11 Jan 2024 23:15:42 +0100 Subject: [PATCH 191/587] ImageView: add overflow handling for left/top cropping params --- core/src/ImageView.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/ImageView.h b/core/src/ImageView.h index bee631b67a..cf9bd6d3f8 100644 --- a/core/src/ImageView.h +++ b/core/src/ImageView.h @@ -75,8 +75,8 @@ class ImageView ImageView cropped(int left, int top, int width, int height) const { - left = std::max(0, left); - top = std::max(0, top); + left = std::clamp(left, 0, _width - 1); + top = std::clamp(top, 0, _height - 1); width = width <= 0 ? (_width - left) : std::min(_width - left, width); height = height <= 0 ? (_height - top) : std::min(_height - top, height); return {data(left, top), width, height, _format, _rowStride, _pixStride}; From ee037eead730ae033c791182ba92ea4e4c0fba6f Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 11 Jan 2024 23:22:47 +0100 Subject: [PATCH 192/587] c-API: add a bunch of missing functions * ImageView::crop/rotate * ReaderOptions::get.* * zxing_free(void*) to free the `char*` memory * zxing_Results_move() to move/extract one Result from the vector Decided to add the 'get' prefix to the getters for symmetry (helps with the rust wrapper). --- wrappers/c/zxing-c-test.c | 2 +- wrappers/c/zxing-c.cpp | 85 +++++++++++++++++++-------------------- wrappers/c/zxing-c.h | 20 ++++++++- 3 files changed, 62 insertions(+), 45 deletions(-) diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index 09f7c742c4..46f65d171b 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -39,7 +39,7 @@ void printF(const char* fmt, char* text) return; if (*text) printf(fmt, text); - free(text); + zxing_free(text); } int main(int argc, char** argv) diff --git a/wrappers/c/zxing-c.cpp b/wrappers/c/zxing-c.cpp index c7446308fe..ef64690546 100644 --- a/wrappers/c/zxing-c.cpp +++ b/wrappers/c/zxing-c.cpp @@ -64,6 +64,16 @@ void zxing_ImageView_delete(zxing_ImageView* iv) delete iv; } +void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height) +{ + *iv = iv->cropped(left, top, width, height); +} + +void zxing_ImageView_rotate(zxing_ImageView* iv, int degree) +{ + *iv = iv->rotated(degree); +} + /* * ZXing/BarcodeFormat.h */ @@ -109,60 +119,36 @@ void zxing_ReaderOptions_delete(zxing_ReaderOptions* opts) delete opts; } -void zxing_ReaderOptions_setTryHarder(zxing_ReaderOptions* opts, bool tryHarder) -{ - opts->setTryHarder(tryHarder); -} - -void zxing_ReaderOptions_setTryRotate(zxing_ReaderOptions* opts, bool tryRotate) -{ - opts->setTryRotate(tryRotate); -} - -void zxing_ReaderOptions_setTryInvert(zxing_ReaderOptions* opts, bool tryInvert) -{ - opts->setTryInvert(tryInvert); -} - -void zxing_ReaderOptions_setTryDownscale(zxing_ReaderOptions* opts, bool tryDownscale) -{ - opts->setTryDownscale(tryDownscale); -} - -void zxing_ReaderOptions_setIsPure(zxing_ReaderOptions* opts, bool isPure) -{ - opts->setIsPure(isPure); -} +#define ZX_PROPERTY(TYPE, GETTER, SETTER) \ + TYPE zxing_ReaderOptions_get##SETTER(const zxing_ReaderOptions* opts) { return opts->GETTER(); } \ + void zxing_ReaderOptions_set##SETTER(zxing_ReaderOptions* opts, TYPE val) { opts->set##SETTER(val); } -void zxing_ReaderOptions_setReturnErrors(zxing_ReaderOptions* opts, bool returnErrors) -{ - opts->setReturnErrors(returnErrors); -} +ZX_PROPERTY(bool, tryHarder, TryHarder) +ZX_PROPERTY(bool, tryRotate, TryRotate) +ZX_PROPERTY(bool, tryInvert, TryInvert) +ZX_PROPERTY(bool, tryDownscale, TryDownscale) +ZX_PROPERTY(bool, isPure, IsPure) +ZX_PROPERTY(bool, returnErrors, ReturnErrors) +ZX_PROPERTY(int, maxNumberOfSymbols, MaxNumberOfSymbols) void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeFormats formats) { opts->setFormats(static_cast(formats)); } -void zxing_ReaderOptions_setBinarizer(zxing_ReaderOptions* opts, zxing_Binarizer binarizer) -{ - opts->setBinarizer(static_cast(binarizer)); -} - -void zxing_ReaderOptions_setEanAddOnSymbol(zxing_ReaderOptions* opts, zxing_EanAddOnSymbol eanAddOnSymbol) +zxing_BarcodeFormats zxing_ReaderOptions_formats(const zxing_ReaderOptions* opts) { - opts->setEanAddOnSymbol(static_cast(eanAddOnSymbol)); + auto v = opts->formats(); + return *reinterpret_cast(&v); } -void zxing_ReaderOptions_setTextMode(zxing_ReaderOptions* opts, zxing_TextMode textMode) -{ - opts->setTextMode(static_cast(textMode)); -} +#define ZX_ENUM_PROPERTY(TYPE, GETTER, SETTER) \ + zxing_##TYPE zxing_ReaderOptions_get##SETTER(const zxing_ReaderOptions* opts) { return static_cast(opts->GETTER()); } \ + void zxing_ReaderOptions_set##SETTER(zxing_ReaderOptions* opts, zxing_##TYPE val) { opts->set##SETTER(static_cast(val)); } -void zxing_ReaderOptions_setMaxNumberOfSymbols(zxing_ReaderOptions* opts, int n) -{ - opts->setMaxNumberOfSymbols(n); -} +ZX_ENUM_PROPERTY(Binarizer, binarizer, Binarizer) +ZX_ENUM_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, EanAddOnSymbol) +ZX_ENUM_PROPERTY(TextMode, textMode, TextMode) /* * ZXing/Result.h @@ -274,6 +260,14 @@ const zxing_Result* zxing_Results_at(const zxing_Results* results, int i) return &(*results)[i]; } +zxing_Result* zxing_Results_move(zxing_Results* results, int i) +{ + if (!results || i < 0 || i >= Size(*results)) + return NULL; + + return new Result(std::move((*results)[i])); +} + char* zxing_LastErrorMsg() { if (lastErrorMsg.empty()) @@ -282,4 +276,9 @@ char* zxing_LastErrorMsg() return copy(std::exchange(lastErrorMsg, {})); } +void zxing_free(void* ptr) +{ + free(ptr); +} + } // extern "C" diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index f19ac8f0e6..4f0835f032 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -52,6 +52,9 @@ zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, int pixStride); void zxing_ImageView_delete(zxing_ImageView* iv); +void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); +void zxing_ImageView_rotate(zxing_ImageView* iv, int degree); + /* * ZXing/BarcodeFormat.h */ @@ -88,7 +91,7 @@ typedef enum | zxing_BarcodeFormat_RMQRCode, zxing_BarcodeFormat_Any = zxing_BarcodeFormat_LinearCodes | zxing_BarcodeFormat_MatrixCodes, - zxing_BarcodeFormat_Invalid = -1 /* return value when BarcodeFormatsFromString() throws */ + zxing_BarcodeFormat_Invalid = -1u /* return value when BarcodeFormatsFromString() throws */ } zxing_BarcodeFormat; typedef zxing_BarcodeFormat zxing_BarcodeFormats; @@ -140,6 +143,18 @@ void zxing_ReaderOptions_setEanAddOnSymbol(zxing_ReaderOptions* opts, zxing_EanA void zxing_ReaderOptions_setTextMode(zxing_ReaderOptions* opts, zxing_TextMode textMode); void zxing_ReaderOptions_setMaxNumberOfSymbols(zxing_ReaderOptions* opts, int n); +bool zxing_ReaderOptions_getTryHarder(const zxing_ReaderOptions* opts); +bool zxing_ReaderOptions_getTryRotate(const zxing_ReaderOptions* opts); +bool zxing_ReaderOptions_getTryInvert(const zxing_ReaderOptions* opts); +bool zxing_ReaderOptions_getTryDownscale(const zxing_ReaderOptions* opts); +bool zxing_ReaderOptions_getIsPure(const zxing_ReaderOptions* opts); +bool zxing_ReaderOptions_getReturnErrors(const zxing_ReaderOptions* opts); +zxing_BarcodeFormats zxing_ReaderOptions_getFormats(const zxing_ReaderOptions* opts); +zxing_Binarizer zxing_ReaderOptions_getBinarizer(const zxing_ReaderOptions* opts); +zxing_EanAddOnSymbol zxing_ReaderOptions_getEanAddOnSymbol(const zxing_ReaderOptions* opts); +zxing_TextMode zxing_ReaderOptions_getTextMode(const zxing_ReaderOptions* opts); +int zxing_ReaderOptions_getMaxNumberOfSymbols(const zxing_ReaderOptions* opts); + /* * ZXing/Result.h */ @@ -181,9 +196,12 @@ void zxing_Results_delete(zxing_Results* results); int zxing_Results_size(const zxing_Results* results); const zxing_Result* zxing_Results_at(const zxing_Results* results, int i); +zxing_Result* zxing_Results_move(zxing_Results* results, int i); char* zxing_LastErrorMsg(); +void zxing_free(void* ptr); + #ifdef __cplusplus } #endif From 9366dc1ccda909187627af8986accf3d08da8b58 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 12 Jan 2024 02:04:12 +0100 Subject: [PATCH 193/587] c-API: add to standard build (will likely be included in next release) This is mainly done now to facilitate the rust wrapper based on bindgen. --- core/CMakeLists.txt | 3 +++ wrappers/c/CMakeLists.txt | 4 ++-- wrappers/c/README.md | 16 ++++++++++------ 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index e9e324cea3..258bd2895e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -138,6 +138,8 @@ if (BUILD_READERS) src/ThresholdBinarizer.h src/WhiteRectDetector.h src/WhiteRectDetector.cpp + ../wrappers/c/zxing-c.h + ../wrappers/c/zxing-c.cpp ) endif() if (BUILD_WRITERS) @@ -163,6 +165,7 @@ set (PUBLIC_HEADERS src/TextUtfEncoding.h # [[deprecated]] src/ZXAlgorithms.h src/ZXConfig.h + ../wrappers/c/zxing-c.h ) if (BUILD_READERS) set (PUBLIC_HEADERS ${PUBLIC_HEADERS} diff --git a/wrappers/c/CMakeLists.txt b/wrappers/c/CMakeLists.txt index 38a817e79e..bf6d247c67 100644 --- a/wrappers/c/CMakeLists.txt +++ b/wrappers/c/CMakeLists.txt @@ -1,7 +1,7 @@ zxing_add_package_stb() if (BUILD_READERS) - add_executable (zxing-c-test zxing-c.cpp zxing-c-test.c) - target_link_libraries (zxing-c-test ZXing::ZXing stb::stb) + add_executable (zxing-c-test zxing-c-test.c) + target_link_libraries (zxing-c-test ZXing::ZXing stb::stb m) add_test(NAME zxing-c-test COMMAND zxing-c-test ${CMAKE_SOURCE_DIR}/test/samples/qrcode-1/1.png) endif() diff --git a/wrappers/c/README.md b/wrappers/c/README.md index 4b0c3ab013..a9130186e5 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -1,17 +1,19 @@ # C bindings for zxing-cpp -This is a preview/proposal for a C-API to zxing-cpp. If this turns out to be useful and practical, it will most likely be merged into the library itself so that it will be trivially accessible for everyone. If you have any comments or feedback, please have a look at https://github.com/zxing-cpp/zxing-cpp/discussions/583. +This is a preview/proposal for a C-API to zxing-cpp. If you have any comments or feedback, please have a look at https://github.com/zxing-cpp/zxing-cpp/discussions/583. ## Installation -Probably the easiest way to play with the C-API is to either just modify the [zxing-c-test.c](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c-test.c) file or copy the files [zxing-c.h](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c.h) and [zxing-c.cpp](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c.cpp) into your own project and link it to the standard zxing-cpp library. +It is currently included in the default build to be trivially accessible for everyone. + +Probably the easiest way to play with the C-API is to just modify the [zxing-c-test.c](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c-test.c) file. ## Usage The following is close to the most trivial use case scenario that is supported. ```c -#include "zxing-c.h" +#include "ZXing/zxing-c.h" int main(int argc, char** argv) { @@ -27,21 +29,23 @@ int main(int argc, char** argv) zxing_Result* result = zxing_ReadBarcode(iv, opts); if (result) { - printf("Format : %s\n", zxing_BarcodeFormatToString(zxing_Result_format(result))); + const char* format = zxing_BarcodeFormatToString(zxing_Result_format(result)); + printf("Format : %s\n", format); + zxing_free(format); const char* text = zxing_Result_text(result); printf("Text : %s\n", text); - free(text); + zxing_free(text); zxing_Result_delete(result); } else { const char* error = zxing_LastErrorMsg(); if (error) { printf("%s\n", error); - free(error); } else { printf("No barcode found\n"); } + zxing_free(error); } zxing_ImageView_delete(iv); From b8cfdad87252e41bcee64db2ac38be782ecf3b41 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 12 Jan 2024 03:20:52 +0100 Subject: [PATCH 194/587] c-API: fix a whole bunch of build regressions * disable c-api for ubsan build to omit linking error in zxing-c-test * don't include zxing-c.* in build with BUILD_C_API=OFF * fix winrt msvc complaint about -1u * make stbi_image not include math.h -> no more -lm dependency --- .github/workflows/ci.yml | 12 ++++++++++-- core/CMakeLists.txt | 6 +++--- wrappers/c/CMakeLists.txt | 2 +- wrappers/c/zxing-c-test.c | 2 ++ wrappers/c/zxing-c.h | 2 +- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e9073c6c6a..f3f5c19176 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -64,7 +64,11 @@ jobs: run: cmake -E make_directory ${{runner.workspace}}/build - name: Configure - run: cmake -S $GITHUB_WORKSPACE -B ${{runner.workspace}}/build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=OFF -DBUILD_C_API=ON -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-march=native -fsanitize=address,undefined -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer" + run: > + cmake -S $GITHUB_WORKSPACE -B ${{runner.workspace}}/build -DCMAKE_BUILD_TYPE=RelWithDebInfo + -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=OFF -DBUILD_C_API=OFF + -DCMAKE_CXX_COMPILER=clang++ + -DCMAKE_CXX_FLAGS="-march=native -fsanitize=address,undefined -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer" - name: Build run: cmake --build ${{runner.workspace}}/build -j8 @@ -175,7 +179,11 @@ jobs: - name: Configure CMake shell: cmd - run: cmake -S ${{github.workspace}}/wrappers/winrt -B ${{runner.workspace}}/build -A ARM64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 + run: > + cmake -S ${{github.workspace}}/wrappers/winrt -B ${{runner.workspace}}/build -A ARM64 + -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release + -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF + -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 - name: Build shell: cmd diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 258bd2895e..59aec0ca78 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -138,8 +138,8 @@ if (BUILD_READERS) src/ThresholdBinarizer.h src/WhiteRectDetector.h src/WhiteRectDetector.cpp - ../wrappers/c/zxing-c.h - ../wrappers/c/zxing-c.cpp + $<$:${CMAKE_SOURCE_DIR}/wrappers/c/zxing-c.h> + $<$:${CMAKE_SOURCE_DIR}/wrappers/c/zxing-c.cpp> ) endif() if (BUILD_WRITERS) @@ -165,7 +165,6 @@ set (PUBLIC_HEADERS src/TextUtfEncoding.h # [[deprecated]] src/ZXAlgorithms.h src/ZXConfig.h - ../wrappers/c/zxing-c.h ) if (BUILD_READERS) set (PUBLIC_HEADERS ${PUBLIC_HEADERS} @@ -179,6 +178,7 @@ if (BUILD_READERS) src/ReaderOptions.h src/Result.h src/StructuredAppend.h + $<$:${CMAKE_SOURCE_DIR}/wrappers/c/zxing-c.h> ) endif() if (BUILD_WRITERS) diff --git a/wrappers/c/CMakeLists.txt b/wrappers/c/CMakeLists.txt index bf6d247c67..0de0d12036 100644 --- a/wrappers/c/CMakeLists.txt +++ b/wrappers/c/CMakeLists.txt @@ -2,6 +2,6 @@ zxing_add_package_stb() if (BUILD_READERS) add_executable (zxing-c-test zxing-c-test.c) - target_link_libraries (zxing-c-test ZXing::ZXing stb::stb m) + target_link_libraries (zxing-c-test ZXing::ZXing stb::stb) add_test(NAME zxing-c-test COMMAND zxing-c-test ${CMAKE_SOURCE_DIR}/test/samples/qrcode-1/1.png) endif() diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index 46f65d171b..acd418f310 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -10,6 +10,8 @@ #include #define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_LINEAR // prevent dependency on -lm +#define STBI_NO_HDR #include int usage(char* pname) diff --git a/wrappers/c/zxing-c.h b/wrappers/c/zxing-c.h index 4f0835f032..69b9cd627c 100644 --- a/wrappers/c/zxing-c.h +++ b/wrappers/c/zxing-c.h @@ -91,7 +91,7 @@ typedef enum | zxing_BarcodeFormat_RMQRCode, zxing_BarcodeFormat_Any = zxing_BarcodeFormat_LinearCodes | zxing_BarcodeFormat_MatrixCodes, - zxing_BarcodeFormat_Invalid = -1u /* return value when BarcodeFormatsFromString() throws */ + zxing_BarcodeFormat_Invalid = 0xFFFFFFFFu /* return value when BarcodeFormatsFromString() throws */ } zxing_BarcodeFormat; typedef zxing_BarcodeFormat zxing_BarcodeFormats; From d1c34452b0a0a9a4936666e969c88f774f399c78 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 12 Jan 2024 13:54:52 +0100 Subject: [PATCH 195/587] QRCode: fix crash reported in #700 --- core/src/qrcode/QRDetector.cpp | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index d98e200045..faa3ba61d7 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -683,6 +683,7 @@ DetectorResult SampleMQR(const BitMatrix& image, const ConcentricPattern& fp) FormatInformation bestFI; PerspectiveTransform bestPT; + BitMatrixCursorF cur(image, {}, {}); for (int i = 0; i < 4; ++i) { auto mod2Pix = PerspectiveTransform(srcQuad, RotatedCorners(*fpQuad, i)); @@ -698,7 +699,7 @@ DetectorResult SampleMQR(const BitMatrix& image, const ConcentricPattern& fp) int formatInfoBits = 0; for (int i = 1; i <= 15; ++i) - AppendBit(formatInfoBits, image.get(mod2Pix(centered(FORMAT_INFO_COORDS[i])))); + AppendBit(formatInfoBits, cur.blackAt(mod2Pix(centered(FORMAT_INFO_COORDS[i])))); auto fi = FormatInformation::DecodeMQR(formatInfoBits); if (fi.hammingDistance < bestFI.hammingDistance) { @@ -718,7 +719,7 @@ DetectorResult SampleMQR(const BitMatrix& image, const ConcentricPattern& fp) for (int i = 0; i < dim; ++i) { auto px = bestPT(centered(PointI{i, dim})); auto py = bestPT(centered(PointI{dim, i})); - blackPixels += (image.isIn(px) && image.get(px)) + (image.isIn(py) && image.get(py)); + blackPixels += cur.blackAt(px) && cur.blackAt(py); } if (blackPixels > 2 * dim / 3) return {}; @@ -744,13 +745,13 @@ DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp) FormatInformation bestFI; PerspectiveTransform bestPT; + BitMatrixCursorF cur(image, {}, {}); for (int i = 0; i < 4; ++i) { auto mod2Pix = PerspectiveTransform(srcQuad, RotatedCorners(*fpQuad, i)); auto check = [&](int i, bool on) { - auto p = mod2Pix(centered(FORMAT_INFO_EDGE_COORDS[i])); - return image.isIn(p) && image.get(p) == on; + return cur.testAt(mod2Pix(centered(FORMAT_INFO_EDGE_COORDS[i]))) == BitMatrixCursorF::Value(on); }; // check that we see top edge timing pattern modules @@ -759,7 +760,7 @@ DetectorResult SampleRMQR(const BitMatrix& image, const ConcentricPattern& fp) uint32_t formatInfoBits = 0; for (int i = 0; i < Size(FORMAT_INFO_COORDS); ++i) - AppendBit(formatInfoBits, image.get(mod2Pix(centered(FORMAT_INFO_COORDS[i])))); + AppendBit(formatInfoBits, cur.blackAt(mod2Pix(centered(FORMAT_INFO_COORDS[i])))); auto fi = FormatInformation::DecodeRMQR(formatInfoBits, 0 /*formatInfoBits2*/); if (fi.hammingDistance < bestFI.hammingDistance) { From 9b0cdd428f89ef84d06b43f24e21e7459d4a2bd7 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 12 Jan 2024 09:43:50 +0100 Subject: [PATCH 196/587] rust: initial rust-wrapper implementation based on the c-API (bindgen) --- wrappers/rust/.gitignore | 4 + wrappers/rust/Cargo.toml | 18 +++ wrappers/rust/build.rs | 13 ++ wrappers/rust/examples/demo.rs | 44 ++++++ wrappers/rust/rustfmt.toml | 15 ++ wrappers/rust/src/bindings.rs | 147 +++++++++++++++++++ wrappers/rust/src/lib.rs | 254 +++++++++++++++++++++++++++++++++ 7 files changed, 495 insertions(+) create mode 100644 wrappers/rust/.gitignore create mode 100644 wrappers/rust/Cargo.toml create mode 100644 wrappers/rust/build.rs create mode 100644 wrappers/rust/examples/demo.rs create mode 100644 wrappers/rust/rustfmt.toml create mode 100644 wrappers/rust/src/bindings.rs create mode 100644 wrappers/rust/src/lib.rs diff --git a/wrappers/rust/.gitignore b/wrappers/rust/.gitignore new file mode 100644 index 0000000000..7268cb7913 --- /dev/null +++ b/wrappers/rust/.gitignore @@ -0,0 +1,4 @@ +target +.idea +.vscode +Cargo.lock diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml new file mode 100644 index 0000000000..e9351a02fb --- /dev/null +++ b/wrappers/rust/Cargo.toml @@ -0,0 +1,18 @@ +[package] +name = "zxing-cpp" +version = "0.1.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +paste = "1.0.14" +flagset = "0.4.4" +image = "0.24.7" + +[dev-dependencies] +anyhow = "1.0.79" +clap = {version = "4.4.13", features = ["derive"]} + +[build-dependencies] +miette = {version = "5.10.0", features = ["fancy"]} diff --git a/wrappers/rust/build.rs b/wrappers/rust/build.rs new file mode 100644 index 0000000000..38916afa47 --- /dev/null +++ b/wrappers/rust/build.rs @@ -0,0 +1,13 @@ +use std::env; + +fn main() -> miette::Result<()> { + if let Ok(lib_dir) = env::var("ZXING_CPP_LIB_DIR") { + println!("cargo:rustc-link-search=native={}", lib_dir); + println!("cargo:rustc-link-lib=dylib=ZXing"); + } + + // manual bindings.rs generation: + // bindgen -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --allowlist-type "zxing.*" --allowlist-function "zxing.*" + + Ok(()) +} diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs new file mode 100644 index 0000000000..780d2f1613 --- /dev/null +++ b/wrappers/rust/examples/demo.rs @@ -0,0 +1,44 @@ +use clap::Parser; +use image::io::Reader; +use std::path::PathBuf; +use zxing_cpp::*; + +#[derive(Parser)] +struct Cli { + filename: PathBuf, + formats: Option, + fast: bool, +} + +fn main() -> anyhow::Result<()> { + let cli = Cli::parse(); + + let formats = barcode_formats_from_string(cli.formats.unwrap_or_default())?; + let opts = ReaderOptions::new() + .formats(formats) + .try_harder(!cli.fast) + .try_invert(!cli.fast) + .try_rotate(!cli.fast); + let image = Reader::open(&cli.filename)?.decode()?.into_luma8(); + let iv = ImageView::from(&image); + + let results = read_barcodes(&iv, &opts)?; + + if results.is_empty() { + println!("No barcode found."); + } else { + for result in results { + println!("Text: {}", result.text()); + println!("Bytes: {:?}", result.bytes()); + println!("Format: {}", result.format()); + println!("Content: {}", result.content_type()); + println!("Identifier: {}", result.symbology_identifier()); + println!("EC Level: {}", result.ec_level()); + println!("Error: {}", result.error_message()); + println!("Orientation: {}", result.orientation()); + println!(); + } + } + + Ok(()) +} diff --git a/wrappers/rust/rustfmt.toml b/wrappers/rust/rustfmt.toml new file mode 100644 index 0000000000..c0781a332f --- /dev/null +++ b/wrappers/rust/rustfmt.toml @@ -0,0 +1,15 @@ +max_width = 135 +hard_tabs = true +tab_spaces = 4 +newline_style = "Auto" +use_small_heuristics = "Default" +fn_call_width = 100 +attr_fn_like_width = 70 +struct_lit_width = 18 +struct_variant_width = 35 +array_width = 100 +chain_width = 100 +single_line_if_else_max_width = 70 +short_array_element_width_threshold = 10 +fn_params_layout = "Tall" +edition = "2021" diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs new file mode 100644 index 0000000000..122d2584d2 --- /dev/null +++ b/wrappers/rust/src/bindings.rs @@ -0,0 +1,147 @@ +/* automatically generated by rust-bindgen 0.69.2 */ + +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct zxing_ImageView { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct zxing_ReaderOptions { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct zxing_Result { + _unused: [u8; 0], +} +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct zxing_Results { + _unused: [u8; 0], +} +pub const zxing_ImageFormat_None: zxing_ImageFormat = 0; +pub const zxing_ImageFormat_Lum: zxing_ImageFormat = 16777216; +pub const zxing_ImageFormat_RGB: zxing_ImageFormat = 50331906; +pub const zxing_ImageFormat_BGR: zxing_ImageFormat = 50462976; +pub const zxing_ImageFormat_RGBX: zxing_ImageFormat = 67109122; +pub const zxing_ImageFormat_XRGB: zxing_ImageFormat = 67174915; +pub const zxing_ImageFormat_BGRX: zxing_ImageFormat = 67240192; +pub const zxing_ImageFormat_XBGR: zxing_ImageFormat = 67305985; +pub type zxing_ImageFormat = ::core::ffi::c_uint; +pub const zxing_BarcodeFormat_None: zxing_BarcodeFormat = 0; +pub const zxing_BarcodeFormat_Aztec: zxing_BarcodeFormat = 1; +pub const zxing_BarcodeFormat_Codabar: zxing_BarcodeFormat = 2; +pub const zxing_BarcodeFormat_Code39: zxing_BarcodeFormat = 4; +pub const zxing_BarcodeFormat_Code93: zxing_BarcodeFormat = 8; +pub const zxing_BarcodeFormat_Code128: zxing_BarcodeFormat = 16; +pub const zxing_BarcodeFormat_DataBar: zxing_BarcodeFormat = 32; +pub const zxing_BarcodeFormat_DataBarExpanded: zxing_BarcodeFormat = 64; +pub const zxing_BarcodeFormat_DataMatrix: zxing_BarcodeFormat = 128; +pub const zxing_BarcodeFormat_EAN8: zxing_BarcodeFormat = 256; +pub const zxing_BarcodeFormat_EAN13: zxing_BarcodeFormat = 512; +pub const zxing_BarcodeFormat_ITF: zxing_BarcodeFormat = 1024; +pub const zxing_BarcodeFormat_MaxiCode: zxing_BarcodeFormat = 2048; +pub const zxing_BarcodeFormat_PDF417: zxing_BarcodeFormat = 4096; +pub const zxing_BarcodeFormat_QRCode: zxing_BarcodeFormat = 8192; +pub const zxing_BarcodeFormat_UPCA: zxing_BarcodeFormat = 16384; +pub const zxing_BarcodeFormat_UPCE: zxing_BarcodeFormat = 32768; +pub const zxing_BarcodeFormat_MicroQRCode: zxing_BarcodeFormat = 65536; +pub const zxing_BarcodeFormat_RMQRCode: zxing_BarcodeFormat = 131072; +pub const zxing_BarcodeFormat_DXFilmEdge: zxing_BarcodeFormat = 262144; +pub const zxing_BarcodeFormat_LinearCodes: zxing_BarcodeFormat = 313214; +pub const zxing_BarcodeFormat_MatrixCodes: zxing_BarcodeFormat = 211073; +pub const zxing_BarcodeFormat_Any: zxing_BarcodeFormat = 524287; +pub const zxing_BarcodeFormat_Invalid: zxing_BarcodeFormat = 4294967295; +pub type zxing_BarcodeFormat = ::core::ffi::c_uint; +pub use self::zxing_BarcodeFormat as zxing_BarcodeFormats; +pub const zxing_Binarizer_LocalAverage: zxing_Binarizer = 0; +pub const zxing_Binarizer_GlobalHistogram: zxing_Binarizer = 1; +pub const zxing_Binarizer_FixedThreshold: zxing_Binarizer = 2; +pub const zxing_Binarizer_BoolCast: zxing_Binarizer = 3; +pub type zxing_Binarizer = ::core::ffi::c_uint; +pub const zxing_EanAddOnSymbol_Ignore: zxing_EanAddOnSymbol = 0; +pub const zxing_EanAddOnSymbol_Read: zxing_EanAddOnSymbol = 1; +pub const zxing_EanAddOnSymbol_Require: zxing_EanAddOnSymbol = 2; +pub type zxing_EanAddOnSymbol = ::core::ffi::c_uint; +pub const zxing_TextMode_Plain: zxing_TextMode = 0; +pub const zxing_TextMode_ECI: zxing_TextMode = 1; +pub const zxing_TextMode_HRI: zxing_TextMode = 2; +pub const zxing_TextMode_Hex: zxing_TextMode = 3; +pub const zxing_TextMode_Escaped: zxing_TextMode = 4; +pub type zxing_TextMode = ::core::ffi::c_uint; +pub const zxing_ContentType_Text: zxing_ContentType = 0; +pub const zxing_ContentType_Binary: zxing_ContentType = 1; +pub const zxing_ContentType_Mixed: zxing_ContentType = 2; +pub const zxing_ContentType_GS1: zxing_ContentType = 3; +pub const zxing_ContentType_ISO15434: zxing_ContentType = 4; +pub const zxing_ContentType_UnknownECI: zxing_ContentType = 5; +pub type zxing_ContentType = ::core::ffi::c_uint; +extern "C" { + pub fn zxing_ImageView_new( + data: *const u8, + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + format: zxing_ImageFormat, + rowStride: ::core::ffi::c_int, + pixStride: ::core::ffi::c_int, + ) -> *mut zxing_ImageView; + pub fn zxing_ImageView_delete(iv: *mut zxing_ImageView); + pub fn zxing_ImageView_crop( + iv: *mut zxing_ImageView, + left: ::core::ffi::c_int, + top: ::core::ffi::c_int, + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + ); + pub fn zxing_ImageView_rotate(iv: *mut zxing_ImageView, degree: ::core::ffi::c_int); + pub fn zxing_BarcodeFormatsFromString(str_: *const ::core::ffi::c_char) -> zxing_BarcodeFormats; + pub fn zxing_BarcodeFormatFromString(str_: *const ::core::ffi::c_char) -> zxing_BarcodeFormat; + pub fn zxing_BarcodeFormatToString(format: zxing_BarcodeFormat) -> *mut ::core::ffi::c_char; + pub fn zxing_ReaderOptions_new() -> *mut zxing_ReaderOptions; + pub fn zxing_ReaderOptions_delete(opts: *mut zxing_ReaderOptions); + pub fn zxing_ReaderOptions_setTryHarder(opts: *mut zxing_ReaderOptions, tryHarder: bool); + pub fn zxing_ReaderOptions_setTryRotate(opts: *mut zxing_ReaderOptions, tryRotate: bool); + pub fn zxing_ReaderOptions_setTryInvert(opts: *mut zxing_ReaderOptions, tryInvert: bool); + pub fn zxing_ReaderOptions_setTryDownscale(opts: *mut zxing_ReaderOptions, tryDownscale: bool); + pub fn zxing_ReaderOptions_setIsPure(opts: *mut zxing_ReaderOptions, isPure: bool); + pub fn zxing_ReaderOptions_setReturnErrors(opts: *mut zxing_ReaderOptions, returnErrors: bool); + pub fn zxing_ReaderOptions_setFormats(opts: *mut zxing_ReaderOptions, formats: zxing_BarcodeFormats); + pub fn zxing_ReaderOptions_setBinarizer(opts: *mut zxing_ReaderOptions, binarizer: zxing_Binarizer); + pub fn zxing_ReaderOptions_setEanAddOnSymbol(opts: *mut zxing_ReaderOptions, eanAddOnSymbol: zxing_EanAddOnSymbol); + pub fn zxing_ReaderOptions_setTextMode(opts: *mut zxing_ReaderOptions, textMode: zxing_TextMode); + pub fn zxing_ReaderOptions_setMaxNumberOfSymbols(opts: *mut zxing_ReaderOptions, n: ::core::ffi::c_int); + pub fn zxing_ReaderOptions_getTryHarder(opts: *const zxing_ReaderOptions) -> bool; + pub fn zxing_ReaderOptions_getTryRotate(opts: *const zxing_ReaderOptions) -> bool; + pub fn zxing_ReaderOptions_getTryInvert(opts: *const zxing_ReaderOptions) -> bool; + pub fn zxing_ReaderOptions_getTryDownscale(opts: *const zxing_ReaderOptions) -> bool; + pub fn zxing_ReaderOptions_getIsPure(opts: *const zxing_ReaderOptions) -> bool; + pub fn zxing_ReaderOptions_getReturnErrors(opts: *const zxing_ReaderOptions) -> bool; + pub fn zxing_ReaderOptions_getFormats(opts: *const zxing_ReaderOptions) -> zxing_BarcodeFormats; + pub fn zxing_ReaderOptions_getBinarizer(opts: *const zxing_ReaderOptions) -> zxing_Binarizer; + pub fn zxing_ReaderOptions_getEanAddOnSymbol(opts: *const zxing_ReaderOptions) -> zxing_EanAddOnSymbol; + pub fn zxing_ReaderOptions_getTextMode(opts: *const zxing_ReaderOptions) -> zxing_TextMode; + pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; + pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; + pub fn zxing_Result_isValid(result: *const zxing_Result) -> bool; + pub fn zxing_Result_errorMsg(result: *const zxing_Result) -> *mut ::core::ffi::c_char; + pub fn zxing_Result_format(result: *const zxing_Result) -> zxing_BarcodeFormat; + pub fn zxing_Result_contentType(result: *const zxing_Result) -> zxing_ContentType; + pub fn zxing_Result_bytes(result: *const zxing_Result, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn zxing_Result_text(result: *const zxing_Result) -> *mut ::core::ffi::c_char; + pub fn zxing_Result_ecLevel(result: *const zxing_Result) -> *mut ::core::ffi::c_char; + pub fn zxing_Result_symbologyIdentifier(result: *const zxing_Result) -> *mut ::core::ffi::c_char; + pub fn zxing_Result_orientation(result: *const zxing_Result) -> ::core::ffi::c_int; + pub fn zxing_Result_isInverted(result: *const zxing_Result) -> bool; + pub fn zxing_Result_isMirrored(result: *const zxing_Result) -> bool; + #[doc = " Note: opts is optional, i.e. it can be NULL, which will imply default settings."] + pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Result; + pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Results; + pub fn zxing_Result_delete(result: *mut zxing_Result); + pub fn zxing_Results_delete(results: *mut zxing_Results); + pub fn zxing_Results_size(results: *const zxing_Results) -> ::core::ffi::c_int; + pub fn zxing_Results_at(results: *const zxing_Results, i: ::core::ffi::c_int) -> *const zxing_Result; + pub fn zxing_Results_move(results: *mut zxing_Results, i: ::core::ffi::c_int) -> *mut zxing_Result; + pub fn zxing_LastErrorMsg() -> *mut ::core::ffi::c_char; + pub fn zxing_free(ptr: *mut ::core::ffi::c_void); +} diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs new file mode 100644 index 0000000000..a2f9920820 --- /dev/null +++ b/wrappers/rust/src/lib.rs @@ -0,0 +1,254 @@ +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(unused_unsafe)] + +include!("bindings.rs"); + +use flagset::{flags, FlagSet}; +use image; +use paste::paste; +use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString}; +use std::fmt::{Display, Formatter}; +use std::io::{Error, ErrorKind}; +use std::marker::PhantomData; +use std::mem::transmute; + +fn c2r_str(str: *mut c_char) -> String { + let mut res = String::new(); + if !str.is_null() { + unsafe { res = CStr::from_ptr(str).to_string_lossy().to_string() }; + unsafe { zxing_free(str as *mut c_void) }; + } + res +} + +fn c2r_vec(buf: *mut u8, len: c_int) -> Vec { + let mut res = Vec::::new(); + if !buf.is_null() && len > 0 { + unsafe { res = std::slice::from_raw_parts(buf, len as usize).to_vec() }; + unsafe { zxing_free(buf as *mut c_void) }; + } + res +} + +macro_rules! last_error_or { + ($expr:expr) => { + match unsafe { zxing_LastErrorMsg().as_mut() } { + None => Ok($expr), + Some(error) => Err(Error::new(ErrorKind::InvalidInput, c2r_str(error))), + } + }; +} + +macro_rules! make_zxing_enum { + ($name:ident { $($field:ident),* }) => { + #[repr(u32)] + #[derive(Debug, Copy, Clone)] + pub enum $name { + $($field = paste! { [] },)* + } + } +} + +macro_rules! make_zxing_flags { + ($name:ident { $($field:ident),* }) => { + flags! { + #[repr(u32)] + pub enum $name: c_uint { + $($field = paste! { [] },)* + } + } + } +} +#[rustfmt::skip] // workaround for broken #[rustfmt::skip::macros(make_zxing_enum)] +make_zxing_enum!(ImageFormat { Lum, RGB, RGBX }); +#[rustfmt::skip] +make_zxing_enum!(ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }); +#[rustfmt::skip] +make_zxing_enum!(Binarizer { LocalAverage, GlobalHistogram, FixedThreshold, BoolCast }); +#[rustfmt::skip] +make_zxing_enum!(TextMode { Plain, ECI, HRI, Hex, Escaped }); +#[rustfmt::skip] +make_zxing_enum!(EanAddOnSymbol { Ignore, Read, Require }); + +#[rustfmt::skip] +make_zxing_flags!(BarcodeFormat { + None, Aztec, Codabar, Code39, Code93, Code128, DataBar, DataBarExpanded, DataMatrix, EAN8, EAN13, ITF, + MaxiCode, PDF417, QRCode, UPCA, UPCE, MicroQRCode, RMQRCode, DXFilmEdge, LinearCodes, MatrixCodes, Any +}); + +type BarcodeFormats = FlagSet; + +impl Display for BarcodeFormat { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", unsafe { c2r_str(zxing_BarcodeFormatToString(BarcodeFormats::from(*self).bits())) }) + } +} + +impl Display for ContentType { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", unsafe { c2r_str(zxing_ContentTypeToString(transmute(*self))) }) + } +} + +pub struct ImageView<'a>(*mut zxing_ImageView, PhantomData<&'a u8>); + +impl Drop for ImageView<'_> { + fn drop(&mut self) { + unsafe { zxing_ImageView_delete(self.0) } + } +} + +impl<'a> AsRef> for ImageView<'a> { + fn as_ref(&self) -> &ImageView<'a> { + &self + } +} + +impl<'a> ImageView<'a> { + pub fn new(data: &'a [u8], width: u32, height: u32, format: ImageFormat, row_stride: u32, pix_stride: u32) -> Self { + unsafe { + ImageView( + zxing_ImageView_new( + data.as_ptr(), + width as c_int, + height as c_int, + format as zxing_ImageFormat, + row_stride as c_int, + pix_stride as c_int, + ), + PhantomData, + ) + } + } + + pub fn cropped(self, left: i32, top: i32, width: i32, height: i32) -> Self { + unsafe { zxing_ImageView_crop(self.0, left, top, width, height) } + self + } + + pub fn rotated(self, degree: i32) -> Self { + unsafe { zxing_ImageView_rotate(self.0, degree) } + self + } +} + +impl<'a> From<&'a image::GrayImage> for ImageView<'a> { + fn from(img: &'a image::GrayImage) -> Self { + ImageView::new(img.as_ref(), img.width(), img.height(), ImageFormat::Lum, 0, 0) + } +} + +pub struct ReaderOptions(*mut zxing_ReaderOptions); + +impl Drop for ReaderOptions { + fn drop(&mut self) { + unsafe { zxing_ReaderOptions_delete(self.0) } + } +} + +impl AsRef for ReaderOptions { + fn as_ref(&self) -> &ReaderOptions { + &self + } +} + +macro_rules! property { + ($name:ident, $type:ty) => { + pub fn $name(self, v: impl Into<$type>) -> Self { + paste! { unsafe { [](self.0, transmute(v.into())) } }; + self + } + + paste! { + pub fn [](&mut self, v : impl Into<$type>) -> &mut Self { + unsafe { [](self.0, transmute(v.into())) }; + self + } + + pub fn [](&self) -> $type { + unsafe { transmute([](self.0)) } + } + } + }; +} + +impl ReaderOptions { + pub fn new() -> Self { + unsafe { ReaderOptions(zxing_ReaderOptions_new()) } + } + + property!(try_harder, bool); + property!(try_rotate, bool); + property!(try_invert, bool); + property!(try_downscale, bool); + property!(is_pure, bool); + property!(return_errors, bool); + property!(formats, BarcodeFormats); + property!(text_mode, TextMode); + property!(binarizer, Binarizer); + property!(ean_add_on_symbol, EanAddOnSymbol); + property!(max_number_of_symbols, i32); +} + + +pub struct ReaderResult(*mut zxing_Result); + +impl Drop for ReaderResult { + fn drop(&mut self) { + unsafe { zxing_Result_delete(self.0) } + } +} + +macro_rules! getter { + ($r_name:ident, $c_name:ident, $conv:expr, $type:ty) => { + pub fn $r_name(&self) -> $type { + paste! { unsafe { $conv([](self.0)) } } + } + }; +} + +impl ReaderResult { + getter!(is_valid, isValid, transmute, bool); + getter!(format, format, (|f| BarcodeFormats::new(f).unwrap().into_iter().last().unwrap()), BarcodeFormat); + getter!(content_type, contentType, transmute, ContentType); + getter!(text, text, c2r_str, String); + getter!(ec_level, ecLevel, c2r_str, String); + getter!(error_message, errorMsg, c2r_str, String); + getter!(symbology_identifier, symbologyIdentifier, c2r_str, String); + getter!(orientation, orientation, transmute, i32); + getter!(is_inverted, isInverted, transmute, bool); + getter!(is_mirrored, isMirrored, transmute, bool); + + pub fn bytes(&self) -> Vec { + let mut len: c_int = 0; + unsafe { c2r_vec(zxing_Result_bytes(self.0, &mut len), len) } + } +} + +pub fn barcode_formats_from_string(str: impl AsRef) -> Result { + let cstr = CString::new(str.as_ref())?; + let res = unsafe { BarcodeFormats::new_unchecked(zxing_BarcodeFormatsFromString(cstr.as_ptr())) }; + match res.bits() { + u32::MAX => last_error_or!(BarcodeFormats::default()), + 0 => Ok(BarcodeFormats::full()), + _ => Ok(res), + } +} + +pub fn read_barcodes<'a>(image: impl AsRef>, opts: impl AsRef) -> Result, Error> { + unsafe { + let results = zxing_ReadBarcodes(image.as_ref().0, opts.as_ref().0); + if !results.is_null() { + let size = zxing_Results_size(results); + let mut vec = Vec::::with_capacity(size as usize); + for i in 0..size { + vec.push(ReaderResult(zxing_Results_move(results, i))); + } + zxing_Results_delete(results); + Ok(vec) + } else { + last_error_or!(Vec::::default()) + } + } +} From 7e3f5be7c664fa546ef62b147364b1d88469a224 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 19 Jan 2024 19:16:42 +0100 Subject: [PATCH 197/587] c-API: move implementation and header files into core/src This helps in building the C_API for the rust wrapper in a sub-directory. Also remove the outdated inclusion of ../zxing.cmake --- core/CMakeLists.txt | 7 +++---- {wrappers/c => core/src}/zxing-c.cpp | 0 {wrappers/c => core/src}/zxing-c.h | 0 3 files changed, 3 insertions(+), 4 deletions(-) rename {wrappers/c => core/src}/zxing-c.cpp (100%) rename {wrappers/c => core/src}/zxing-c.h (100%) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 59aec0ca78..eef81c2eb5 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -3,7 +3,6 @@ cmake_minimum_required(VERSION 3.15) project (ZXing VERSION "2.2.1") set (ZXING_SONAME 3) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 -include(../zxing.cmake) if (BUILD_SHARED_LIBS) set(CMAKE_POSITION_INDEPENDENT_CODE ON) endif() @@ -138,8 +137,8 @@ if (BUILD_READERS) src/ThresholdBinarizer.h src/WhiteRectDetector.h src/WhiteRectDetector.cpp - $<$:${CMAKE_SOURCE_DIR}/wrappers/c/zxing-c.h> - $<$:${CMAKE_SOURCE_DIR}/wrappers/c/zxing-c.cpp> + $<$:src/zxing-c.h> + $<$:src/zxing-c.cpp> ) endif() if (BUILD_WRITERS) @@ -178,7 +177,7 @@ if (BUILD_READERS) src/ReaderOptions.h src/Result.h src/StructuredAppend.h - $<$:${CMAKE_SOURCE_DIR}/wrappers/c/zxing-c.h> + $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/zxing-c.h> ) endif() if (BUILD_WRITERS) diff --git a/wrappers/c/zxing-c.cpp b/core/src/zxing-c.cpp similarity index 100% rename from wrappers/c/zxing-c.cpp rename to core/src/zxing-c.cpp diff --git a/wrappers/c/zxing-c.h b/core/src/zxing-c.h similarity index 100% rename from wrappers/c/zxing-c.h rename to core/src/zxing-c.h From f4f388cde8900e5d7800151571df14d963dc7d76 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 20 Jan 2024 10:54:37 +0100 Subject: [PATCH 198/587] Rust: prepare crates.io publication / add "bundled" and "image" features --- wrappers/rust/Cargo.toml | 16 ++++++++++- wrappers/rust/README.md | 50 ++++++++++++++++++++++++++++++++++ wrappers/rust/build.rs | 24 +++++++++++++++- wrappers/rust/core | 1 + wrappers/rust/examples/demo.rs | 10 +++++-- wrappers/rust/src/lib.rs | 12 +++++--- 6 files changed, 104 insertions(+), 9 deletions(-) create mode 100644 wrappers/rust/README.md create mode 120000 wrappers/rust/core diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index e9351a02fb..4442463e5e 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -2,17 +2,31 @@ name = "zxing-cpp" version = "0.1.0" edition = "2021" +license = "Apache-2.0" +description = "A rust wrapper for the zxing-cpp barcode library." +repository = "https://github.com/zxing-cpp/zxing-cpp/" +readme = "README.md" +keywords = ["zxing", "barcode", "barcode_reader"] +categories = ["api-bindings"] # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +[features] +default = [] +image = ["dep:image"] +bundled = [] + [dependencies] paste = "1.0.14" flagset = "0.4.4" -image = "0.24.7" +image = {version = "0.24.7", optional = true} [dev-dependencies] +cfg-if = "1.0.0" anyhow = "1.0.79" clap = {version = "4.4.13", features = ["derive"]} +image = {version = "0.24.7"} [build-dependencies] +cmake = {version = "0.1.50"} miette = {version = "5.10.0", features = ["fancy"]} diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md new file mode 100644 index 0000000000..f2a55e5cef --- /dev/null +++ b/wrappers/rust/README.md @@ -0,0 +1,50 @@ +# zxing-cpp + +zxing-cpp is a Rust wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). + +It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. It was originally ported from the Java ZXing Library but has been developed further and now includes many improvements in terms of runtime and detection performance. + +## Usage + +In your Cargo.toml: + +```toml +[dependencies] +# `bundled` causes cargo to compile and statically link in an up to date +# version of the c++ core library. This is the most convient and save +# way to build the library. +rusqlite = { version = "0.1.0", features = ["bundled", "image"] } +``` + +Simple example usage: + +```rust +use zxing_cpp::{ImageView, ReaderOptions, BarcodeFormat, read_barcodes}; + +fn main() -> anyhow::Result<()> { + let image = image::open("some-image-file.jpg")?.into_luma8(); + let iv = ImageView::from(&image); + let opts = ReaderOptions::new() + .formats(BarcodeFormat::QRCode | BarcodeFormat::LinearCodes) + .try_invert(false); + + let results = read_barcodes(&iv, &opts)?; + + if results.is_empty() { + println!("No barcode found."); + } else { + for result in results { + println!("{}: {}", result.format(), result.text()); + } + } + + Ok(()) +} +``` + +## Optional Features + +zxing-cpp provides several features that are behind [Cargo features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are: + +* `bundled` uses a bundled version of the [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) c++ library. +* [`image`](https://crates.io/crates/image) allows convenient `ImageView::from(&GreyImage)` conversion. diff --git a/wrappers/rust/build.rs b/wrappers/rust/build.rs index 38916afa47..ba457e5da3 100644 --- a/wrappers/rust/build.rs +++ b/wrappers/rust/build.rs @@ -1,9 +1,31 @@ use std::env; fn main() -> miette::Result<()> { - if let Ok(lib_dir) = env::var("ZXING_CPP_LIB_DIR") { + if cfg!(feature = "bundled") { + // Builds the project in the directory located in `core`, installing it into $OUT_DIR + let mut dst = cmake::Config::new("core") + .define("BUILD_SHARED_LIBS", "OFF") + .define("BUILD_READERS", "ON") + .define("BUILD_WRITERS", "OFF") + .define("BUILD_C_API", "ON") + .define("CMAKE_CXX_STANDARD", "20") + .build(); + dst.push("lib"); + println!("cargo:rustc-link-search=native={}", dst.display()); + println!("cargo:rustc-link-lib=static=ZXing"); + + if let Ok(target) = env::var("TARGET") { + if target.contains("apple") { + println!("cargo:rustc-link-lib=dylib=c++"); + } else if target.contains("linux") { + println!("cargo:rustc-link-lib=dylib=stdc++"); + } + } + } else if let Ok(lib_dir) = env::var("ZXING_CPP_LIB_DIR") { println!("cargo:rustc-link-search=native={}", lib_dir); println!("cargo:rustc-link-lib=dylib=ZXing"); + } else { + panic!("ZXing library not found. Use feature 'bundled' or set environment variabale ZXING_CPP_LIB_DIR.") } // manual bindings.rs generation: diff --git a/wrappers/rust/core b/wrappers/rust/core new file mode 120000 index 0000000000..58377d59e0 --- /dev/null +++ b/wrappers/rust/core @@ -0,0 +1 @@ +../../core/ \ No newline at end of file diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index 780d2f1613..f5aaa49960 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -1,5 +1,4 @@ use clap::Parser; -use image::io::Reader; use std::path::PathBuf; use zxing_cpp::*; @@ -13,14 +12,19 @@ struct Cli { fn main() -> anyhow::Result<()> { let cli = Cli::parse(); + let image = image::open(&cli.filename)?.into_luma8(); + + #[cfg(feature = "image")] + let iv = ImageView::from(&image); + #[cfg(not(feature = "image"))] + let iv = ImageView::new(image.as_ref(), image.width(), image.height(), ImageFormat::Lum, 0, 0); + let formats = barcode_formats_from_string(cli.formats.unwrap_or_default())?; let opts = ReaderOptions::new() .formats(formats) .try_harder(!cli.fast) .try_invert(!cli.fast) .try_rotate(!cli.fast); - let image = Reader::open(&cli.filename)?.decode()?.into_luma8(); - let iv = ImageView::from(&image); let results = read_barcodes(&iv, &opts)?; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index a2f9920820..e8092379ac 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -5,14 +5,15 @@ include!("bindings.rs"); use flagset::{flags, FlagSet}; -use image; use paste::paste; use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString}; use std::fmt::{Display, Formatter}; -use std::io::{Error, ErrorKind}; +use std::io::ErrorKind; use std::marker::PhantomData; use std::mem::transmute; +pub type Error = std::io::Error; + fn c2r_str(str: *mut c_char) -> String { let mut res = String::new(); if !str.is_null() { @@ -77,7 +78,7 @@ make_zxing_flags!(BarcodeFormat { MaxiCode, PDF417, QRCode, UPCA, UPCE, MicroQRCode, RMQRCode, DXFilmEdge, LinearCodes, MatrixCodes, Any }); -type BarcodeFormats = FlagSet; +pub type BarcodeFormats = FlagSet; impl Display for BarcodeFormat { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { @@ -133,6 +134,10 @@ impl<'a> ImageView<'a> { } } +#[cfg(feature = "image")] +use image; + +#[cfg(feature = "image")] impl<'a> From<&'a image::GrayImage> for ImageView<'a> { fn from(img: &'a image::GrayImage) -> Self { ImageView::new(img.as_ref(), img.width(), img.height(), ImageFormat::Lum, 0, 0) @@ -191,7 +196,6 @@ impl ReaderOptions { property!(max_number_of_symbols, i32); } - pub struct ReaderResult(*mut zxing_Result); impl Drop for ReaderResult { From 9e0347689c4d4d50f33dbbd7e8c8544773ee79da Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 20 Jan 2024 16:07:41 +0100 Subject: [PATCH 199/587] rust: fix/silence clippy warnings and allow to run it in the first place --- wrappers/rust/build.rs | 2 +- wrappers/rust/src/lib.rs | 12 ++++++++++-- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/wrappers/rust/build.rs b/wrappers/rust/build.rs index ba457e5da3..6e6ba34f35 100644 --- a/wrappers/rust/build.rs +++ b/wrappers/rust/build.rs @@ -25,7 +25,7 @@ fn main() -> miette::Result<()> { println!("cargo:rustc-link-search=native={}", lib_dir); println!("cargo:rustc-link-lib=dylib=ZXing"); } else { - panic!("ZXing library not found. Use feature 'bundled' or set environment variabale ZXING_CPP_LIB_DIR.") +// panic!("ZXing library not found. Use feature 'bundled' or set environment variabale ZXING_CPP_LIB_DIR.") } // manual bindings.rs generation: diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index e8092379ac..30c19f50b7 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -1,6 +1,8 @@ #![allow(non_camel_case_types)] #![allow(non_upper_case_globals)] #![allow(unused_unsafe)] +#![allow(clippy::useless_transmute)] +#![allow(clippy::redundant_closure_call)] include!("bindings.rs"); @@ -102,7 +104,7 @@ impl Drop for ImageView<'_> { impl<'a> AsRef> for ImageView<'a> { fn as_ref(&self) -> &ImageView<'a> { - &self + self } } @@ -152,9 +154,15 @@ impl Drop for ReaderOptions { } } +impl Default for ReaderOptions { + fn default() -> Self { + Self::new() + } +} + impl AsRef for ReaderOptions { fn as_ref(&self) -> &ReaderOptions { - &self + self } } From 49f5451b3322d3c766099be75c9c47ecd3e700e5 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 20 Jan 2024 16:27:26 +0100 Subject: [PATCH 200/587] ci: add build-rust job (plus build fixes) * fix trivial rustfmt comment complaint * fix typo in c-API function name --- .github/workflows/ci.yml | 68 +++++++++++++++++++++++++++------------- core/src/zxing-c.cpp | 2 +- wrappers/rust/build.rs | 2 +- 3 files changed, 48 insertions(+), 24 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f3f5c19176..c2b3b5311a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -117,28 +117,6 @@ jobs: name: android-artifacts path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-debug.aar" - build-wasm: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: mymindstorm/setup-emsdk@v12 - - - name: Configure - run: emcmake cmake -Swrappers/wasm -Bbuild - - - name: Build - run: cmake --build build -j4 - -# - name: Test -# run: node build/EmGlueTests.js - - - uses: actions/upload-artifact@v3 - with: - name: wasm-artifacts - path: | - build/zxing* - build/demo* - build-python: runs-on: ${{ matrix.os }} strategy: @@ -168,6 +146,52 @@ jobs: working-directory: wrappers/python run: python -m unittest -v + build-rust: + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + + steps: + - uses: actions/checkout@v3 + + - name: Lint + working-directory: wrappers/rust + run: | + cargo fmt --check + cargo clippy -- -Dwarnings + + - name: Build + working-directory: wrappers/rust + run: cargo build --release --verbose --features bundled,image --examples + + - name: Package + working-directory: wrappers/rust + # --allow-dirty is required on the windows build (but not the ubuntu build?!) + run: cargo package --verbose --allow-dirty --features bundled,image + + build-wasm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: mymindstorm/setup-emsdk@v12 + + - name: Configure + run: emcmake cmake -Swrappers/wasm -Bbuild + + - name: Build + run: cmake --build build -j4 + +# - name: Test +# run: node build/EmGlueTests.js + + - uses: actions/upload-artifact@v3 + with: + name: wasm-artifacts + path: | + build/zxing* + build/demo* + build-winrt: runs-on: windows-latest diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index ef64690546..bd3d12a7fa 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -136,7 +136,7 @@ void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeForm opts->setFormats(static_cast(formats)); } -zxing_BarcodeFormats zxing_ReaderOptions_formats(const zxing_ReaderOptions* opts) +zxing_BarcodeFormats zxing_ReaderOptions_getFormats(const zxing_ReaderOptions* opts) { auto v = opts->formats(); return *reinterpret_cast(&v); diff --git a/wrappers/rust/build.rs b/wrappers/rust/build.rs index 6e6ba34f35..6ce1b521a9 100644 --- a/wrappers/rust/build.rs +++ b/wrappers/rust/build.rs @@ -25,7 +25,7 @@ fn main() -> miette::Result<()> { println!("cargo:rustc-link-search=native={}", lib_dir); println!("cargo:rustc-link-lib=dylib=ZXing"); } else { -// panic!("ZXing library not found. Use feature 'bundled' or set environment variabale ZXING_CPP_LIB_DIR.") + // panic!("ZXing library not found. Use feature 'bundled' or set environment variabale ZXING_CPP_LIB_DIR.") } // manual bindings.rs generation: From 72b5fde82c605397f3f9a687af5042b336ed8fef Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 03:12:28 +0100 Subject: [PATCH 201/587] README: add link to new Rust wrapper --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index bfbcc397b1..3417999786 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,7 @@ Thanks a lot for your contribution! * [C](wrappers/c/README.md) * [iOS](wrappers/ios/README.md) * [Python](wrappers/python/README.md) + * [Rust](wrappers/rust/README.md) * [WebAssembly](wrappers/wasm/README.md) * [WinRT](wrappers/winrt/README.md) * [Flutter](https://pub.dev/packages/flutter_zxing) (external project) From 0fd4d44a25949b7cd847fdbb61e6c86d0e453874 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 03:14:57 +0100 Subject: [PATCH 202/587] rust/README: fix copy'n'paste bug --- wrappers/rust/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index f2a55e5cef..b3ac9f8683 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -13,7 +13,7 @@ In your Cargo.toml: # `bundled` causes cargo to compile and statically link in an up to date # version of the c++ core library. This is the most convient and save # way to build the library. -rusqlite = { version = "0.1.0", features = ["bundled", "image"] } +zxing-cpp = { version = "0.1.0", features = ["bundled", "image"] } ``` Simple example usage: From f6a91c96d50eb49abcbbfb1289b9a8404ccfc5c3 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 11:21:32 +0100 Subject: [PATCH 203/587] rust: metadata fine tuning excluding writer/encoder related files as they are currently not used (see `BUILD_WRITERS=OFF` in `build.rs. --- wrappers/rust/Cargo.toml | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 4442463e5e..1e6affe72f 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -2,14 +2,17 @@ name = "zxing-cpp" version = "0.1.0" edition = "2021" +# authors = ["Axel Waggershauser "] license = "Apache-2.0" description = "A rust wrapper for the zxing-cpp barcode library." repository = "https://github.com/zxing-cpp/zxing-cpp/" readme = "README.md" -keywords = ["zxing", "barcode", "barcode_reader"] -categories = ["api-bindings"] - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html +keywords = ["zxing", "barcode", "barcode_reader", "ffi"] +categories = ["api-bindings", "computer-vision"] +exclude = [ + "core/**/*Write*", + "core/**/*Encode*", +] [features] default = [] From e452b46c1481cb9797be912c334bff417ec5b44b Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 11:59:18 +0100 Subject: [PATCH 204/587] rust: add hint about API stability to README --- wrappers/rust/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index b3ac9f8683..e88eb5329f 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -42,6 +42,8 @@ fn main() -> anyhow::Result<()> { } ``` +Note: This should currently be considered a pre-release. The API may change slightly to be even more "rusty" depending on community feedback. + ## Optional Features zxing-cpp provides several features that are behind [Cargo features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are: From 6952b36958a7c6ec6eb36e88f65d12227ff4cb83 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 15:33:37 +0100 Subject: [PATCH 205/587] rust: keep the low level bindings.rs symbols from the public API --- wrappers/rust/build.rs | 2 +- wrappers/rust/src/bindings.rs | 1 - wrappers/rust/src/lib.rs | 11 ++++++++--- 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/wrappers/rust/build.rs b/wrappers/rust/build.rs index 6ce1b521a9..67940e3682 100644 --- a/wrappers/rust/build.rs +++ b/wrappers/rust/build.rs @@ -29,7 +29,7 @@ fn main() -> miette::Result<()> { } // manual bindings.rs generation: - // bindgen -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --allowlist-type "zxing.*" --allowlist-function "zxing.*" + // bindgen -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --no-doc-comments --allowlist-type "zxing.*" --allowlist-function "zxing.*" Ok(()) } diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 122d2584d2..f9f76ce3e4 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -134,7 +134,6 @@ extern "C" { pub fn zxing_Result_orientation(result: *const zxing_Result) -> ::core::ffi::c_int; pub fn zxing_Result_isInverted(result: *const zxing_Result) -> bool; pub fn zxing_Result_isMirrored(result: *const zxing_Result) -> bool; - #[doc = " Note: opts is optional, i.e. it can be NULL, which will imply default settings."] pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Result; pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Results; pub fn zxing_Result_delete(result: *mut zxing_Result); diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 30c19f50b7..06fcf38f39 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -1,10 +1,15 @@ -#![allow(non_camel_case_types)] -#![allow(non_upper_case_globals)] #![allow(unused_unsafe)] #![allow(clippy::useless_transmute)] #![allow(clippy::redundant_closure_call)] -include!("bindings.rs"); +#[allow(dead_code)] +#[allow(non_camel_case_types)] +#[allow(non_upper_case_globals)] +mod bindings { + include!("bindings.rs"); +} + +use bindings::*; use flagset::{flags, FlagSet}; use paste::paste; From f989f110af7d12990be1a11e957a72c88ca9ea7f Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 18:27:55 +0100 Subject: [PATCH 206/587] rust: add TryFrom<&image::DynamicImage> for ImageView --- wrappers/rust/examples/demo.rs | 8 +++++--- wrappers/rust/src/lib.rs | 18 ++++++++++++++++++ 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index f5aaa49960..c264aa7344 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -12,12 +12,14 @@ struct Cli { fn main() -> anyhow::Result<()> { let cli = Cli::parse(); - let image = image::open(&cli.filename)?.into_luma8(); + let image = image::open(&cli.filename)?; #[cfg(feature = "image")] - let iv = ImageView::from(&image); + let iv = ImageView::try_from(&image)?; #[cfg(not(feature = "image"))] - let iv = ImageView::new(image.as_ref(), image.width(), image.height(), ImageFormat::Lum, 0, 0); + let lum_img = image.into_luma8(); + #[cfg(not(feature = "image"))] + let iv = ImageView::new(lum_img.as_ref(), lum_img.width(), lum_img.height(), ImageFormat::Lum, 0, 0); let formats = barcode_formats_from_string(cli.formats.unwrap_or_default())?; let opts = ReaderOptions::new() diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 06fcf38f39..a24dbe814c 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -151,6 +151,24 @@ impl<'a> From<&'a image::GrayImage> for ImageView<'a> { } } +#[cfg(feature = "image")] +impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { + type Error = Error; + + fn try_from(img: &'a image::DynamicImage) -> Result { + let format = match img { + image::DynamicImage::ImageLuma8(_) => Some(ImageFormat::Lum), + image::DynamicImage::ImageRgb8(_) => Some(ImageFormat::RGB), + image::DynamicImage::ImageRgba8(_) => Some(ImageFormat::RGBX), + _ => None, + }; + match format { + Some(format) => Ok(ImageView::new(img.as_bytes(), img.width(), img.height(), format, 0, 0)), + None => Err(Error::new(ErrorKind::InvalidInput, "Invalid image format (must be either luma8|rgb8|rgba8)")), + } + } +} + pub struct ReaderOptions(*mut zxing_ReaderOptions); impl Drop for ReaderOptions { From b2a56f6cb4f8355db24dbb1fc927b7eb7a0f4279 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 23:00:13 +0100 Subject: [PATCH 207/587] BinaryBitmap: fix segfault in experimental closing operator --- core/src/BinaryBitmap.cpp | 2 ++ core/src/ReadBarcode.cpp | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index 4cbe5d5ef2..8b4914e9bf 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -69,6 +69,8 @@ void BinaryBitmap::invert() template void SumFilter(const BitMatrix& in, BitMatrix& out, F func) { + assert(in.height() >= 3); + const auto* in0 = in.row(0).begin(); const auto* in1 = in.row(1).begin(); const auto* in2 = in.row(2).begin(); diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 9941bdde0a..4a166bb9c0 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -152,7 +152,7 @@ Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) #ifdef ZXING_BUILD_EXPERIMENTAL_API auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode; ReaderOptions closedOptions = opts; - if (opts.tryDenoise() && opts.hasFormat(formatsBenefittingFromClosing)) { + if (opts.tryDenoise() && opts.hasFormat(formatsBenefittingFromClosing) && _iv.height() >= 3) { closedOptions.setFormats((opts.formats().empty() ? BarcodeFormat::Any : opts.formats()) & formatsBenefittingFromClosing); closedReader = std::make_unique(closedOptions); } From fa4f4b17260fc919e07793374af749221d11c906 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 23:01:20 +0100 Subject: [PATCH 208/587] ODReader: adjust minLineCount when image.height() is smaller --- core/src/oned/ODReader.cpp | 2 ++ test/blackbox/BlackboxTestRunner.cpp | 8 ++++---- test/samples/ean8-1/single-line.png | Bin 0 -> 95 bytes test/samples/ean8-1/single-line.txt | 1 + 4 files changed, 7 insertions(+), 4 deletions(-) create mode 100644 test/samples/ean8-1/single-line.png create mode 100644 test/samples/ean8-1/single-line.txt diff --git a/core/src/oned/ODReader.cpp b/core/src/oned/ODReader.cpp index 0caf3805d4..90713b3765 100644 --- a/core/src/oned/ODReader.cpp +++ b/core/src/oned/ODReader.cpp @@ -99,6 +99,8 @@ static Results DoDecode(const std::vector>& readers, if (isPure) minLineCount = 1; + else + minLineCount = std::min(minLineCount, height); std::vector checkRows; PatternRow bars; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index fe7914d3a2..6849319a84 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -429,10 +429,10 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 2, 2, 180 }, }); - runTests("ean8-1", "EAN-8", 8, { - { 8, 8, 0 }, - { 8, 8, 180 }, - { 7, 0, pure }, + runTests("ean8-1", "EAN-8", 9, { + { 9, 9, 0 }, + { 9, 9, 180 }, + { 8, 0, pure }, }); runTests("ean13-1", "EAN-13", 32, { diff --git a/test/samples/ean8-1/single-line.png b/test/samples/ean8-1/single-line.png new file mode 100644 index 0000000000000000000000000000000000000000..275c5bcbd0e155835aa832eec8605737313e75dc GIT binary patch literal 95 zcmeAS@N?(olHy`uVBq!ia0vp^u0YJl0VEjK>f7`KDK$?Q#}JO|t|uMY7#w(54le!w s|A|t2ODf;uU!Ee{Lv(iw-#1$mBl|^AmdKI;Vst05BpOJpcdz literal 0 HcmV?d00001 diff --git a/test/samples/ean8-1/single-line.txt b/test/samples/ean8-1/single-line.txt new file mode 100644 index 0000000000..c1c1178167 --- /dev/null +++ b/test/samples/ean8-1/single-line.txt @@ -0,0 +1 @@ +12345670 \ No newline at end of file From 714a254e554389d69b48d340a15e849b659805e4 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 21 Jan 2024 23:04:22 +0100 Subject: [PATCH 209/587] rust: add copyright and SPDX header comments --- wrappers/rust/examples/demo.rs | 5 +++++ wrappers/rust/src/lib.rs | 5 +++++ 2 files changed, 10 insertions(+) diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index c264aa7344..4a9f18987a 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -1,3 +1,8 @@ +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + use clap::Parser; use std::path::PathBuf; use zxing_cpp::*; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index a24dbe814c..4eb1e7906d 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -1,3 +1,8 @@ +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + #![allow(unused_unsafe)] #![allow(clippy::useless_transmute)] #![allow(clippy::redundant_closure_call)] From b986cce3f61fe98280c22238b938a021e70d6ece Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 02:10:40 +0100 Subject: [PATCH 210/587] rust: redo `ImageView` constructors * replace `fn new(...)` with `unsafe fn from_ptr(...)`: This is now unsafe because we deal with a raw pointer and unchecked width/height/stride information. * add `fn from_slice(...)` that does some basic parameter checking and is therefore safe. Both versions accept any `TryInto` number but can therefore fail. --- wrappers/rust/README.md | 4 +-- wrappers/rust/examples/demo.rs | 2 +- wrappers/rust/src/lib.rs | 62 ++++++++++++++++++++++++++-------- 3 files changed, 50 insertions(+), 18 deletions(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index e88eb5329f..d8d937adbb 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -22,8 +22,8 @@ Simple example usage: use zxing_cpp::{ImageView, ReaderOptions, BarcodeFormat, read_barcodes}; fn main() -> anyhow::Result<()> { - let image = image::open("some-image-file.jpg")?.into_luma8(); - let iv = ImageView::from(&image); + let image = image::open("some-image-file.jpg")?; + let iv = ImageView::try_from(&image)?; let opts = ReaderOptions::new() .formats(BarcodeFormat::QRCode | BarcodeFormat::LinearCodes) .try_invert(false); diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index 4a9f18987a..aaa4ca898d 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -24,7 +24,7 @@ fn main() -> anyhow::Result<()> { #[cfg(not(feature = "image"))] let lum_img = image.into_luma8(); #[cfg(not(feature = "image"))] - let iv = ImageView::new(lum_img.as_ref(), lum_img.width(), lum_img.height(), ImageFormat::Lum, 0, 0); + let iv = ImageView::from_slice(&lum_img, lum_img.width(), lum_img.height(), ImageFormat::Lum)?; let formats = barcode_formats_from_string(cli.formats.unwrap_or_default())?; let opts = ReaderOptions::new() diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 4eb1e7906d..9814994362 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -119,19 +119,51 @@ impl<'a> AsRef> for ImageView<'a> { } impl<'a> ImageView<'a> { - pub fn new(data: &'a [u8], width: u32, height: u32, format: ImageFormat, row_stride: u32, pix_stride: u32) -> Self { - unsafe { - ImageView( - zxing_ImageView_new( - data.as_ptr(), - width as c_int, - height as c_int, - format as zxing_ImageFormat, - row_stride as c_int, - pix_stride as c_int, - ), - PhantomData, - ) + fn try_into_int>(val: T) -> Result { + val.try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "Could not convert Integer into c_int.")) + } + + /// Constructs an ImageView from a raw pointer and the width/height (in pixels) + /// and row_stride/pix_stride (in bytes). + /// + /// # Safety + /// + /// The memory gets accessed inside the c++ library at random places between + /// `ptr` and `ptr + height * row_stride + width * pix_stride. Note that both + /// the stride values could be negative, e.g. if the image view is rotated. + pub unsafe fn from_ptr, U: TryInto>( + ptr: *const u8, + width: T, + height: T, + format: ImageFormat, + row_stride: U, + pix_stride: U, + ) -> Result { + let res = ImageView( + zxing_ImageView_new( + ptr, + Self::try_into_int(width)?, + Self::try_into_int(height)?, + format as zxing_ImageFormat, + Self::try_into_int(row_stride)?, + Self::try_into_int(pix_stride)?, + ), + PhantomData, + ); + Ok(res) + } + + pub fn from_slice + Clone>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { + let pix_size = match format { + ImageFormat::Lum => 1, + ImageFormat::RGB => 3, + ImageFormat::RGBX => 4, + }; + if Self::try_into_int(data.len())? < Self::try_into_int(width.clone())? * Self::try_into_int(height.clone())? * pix_size { + Err(Error::new(ErrorKind::InvalidInput, "data.len() < width * height * pix_size")) + } else { + unsafe { Self::from_ptr(data.as_ptr(), width, height, format, 0, 0) } } } @@ -152,7 +184,7 @@ use image; #[cfg(feature = "image")] impl<'a> From<&'a image::GrayImage> for ImageView<'a> { fn from(img: &'a image::GrayImage) -> Self { - ImageView::new(img.as_ref(), img.width(), img.height(), ImageFormat::Lum, 0, 0) + ImageView::from_slice(img.as_ref(), img.width(), img.height(), ImageFormat::Lum).unwrap() } } @@ -168,7 +200,7 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { _ => None, }; match format { - Some(format) => Ok(ImageView::new(img.as_bytes(), img.width(), img.height(), format, 0, 0)), + Some(format) => Ok(ImageView::from_slice(img.as_bytes(), img.width(), img.height(), format)?), None => Err(Error::new(ErrorKind::InvalidInput, "Invalid image format (must be either luma8|rgb8|rgba8)")), } } From f6818705c33dbebb53427703bc0559d5586935ea Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 02:17:10 +0100 Subject: [PATCH 211/587] rust: add basic testing and hook up in ci --- .github/workflows/ci.yml | 4 +++ wrappers/rust/src/lib.rs | 4 ++- wrappers/rust/src/tests.rs | 58 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 65 insertions(+), 1 deletion(-) create mode 100644 wrappers/rust/src/tests.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c2b3b5311a..edd2a464b0 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -165,6 +165,10 @@ jobs: working-directory: wrappers/rust run: cargo build --release --verbose --features bundled,image --examples + - name: Test + working-directory: wrappers/rust + run: cargo test --release --features bundled,image + - name: Package working-directory: wrappers/rust # --allow-dirty is required on the windows build (but not the ubuntu build?!) diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 9814994362..af605dbdb1 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -7,6 +7,8 @@ #![allow(clippy::useless_transmute)] #![allow(clippy::redundant_closure_call)] +mod tests; + #[allow(dead_code)] #[allow(non_camel_case_types)] #[allow(non_upper_case_globals)] @@ -56,7 +58,7 @@ macro_rules! last_error_or { macro_rules! make_zxing_enum { ($name:ident { $($field:ident),* }) => { #[repr(u32)] - #[derive(Debug, Copy, Clone)] + #[derive(Debug, Copy, Clone, PartialEq)] pub enum $name { $($field = paste! { [] },)* } diff --git a/wrappers/rust/src/tests.rs b/wrappers/rust/src/tests.rs new file mode 100644 index 0000000000..f7b01b478d --- /dev/null +++ b/wrappers/rust/src/tests.rs @@ -0,0 +1,58 @@ +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#[cfg(test)] +mod tests { + use crate::*; + + #[test] + fn barcode_formats_from_string_valid() { + let formats = barcode_formats_from_string("qrcode,linearcodes").unwrap(); + assert_eq!(formats, BarcodeFormat::QRCode | BarcodeFormat::LinearCodes); + } + + #[test] + fn reader_options() { + let mut o1 = ReaderOptions::default(); + assert_eq!(o1.get_formats(), BarcodeFormat::None); + assert_eq!(o1.get_try_harder(), true); + o1.set_formats(BarcodeFormat::EAN8); + assert_eq!(o1.get_formats(), BarcodeFormat::EAN8); + o1.set_try_harder(false); + assert_eq!(o1.get_try_harder(), false); + + o1 = ReaderOptions::default().is_pure(true).text_mode(TextMode::Hex); + assert_eq!(o1.get_formats(), BarcodeFormat::None); + assert_eq!(o1.get_try_harder(), true); + assert_eq!(o1.get_is_pure(), true); + assert_eq!(o1.get_text_mode(), TextMode::Hex); + } + + #[test] + #[should_panic] + fn barcode_formats_from_string_invalid() { + let _ = barcode_formats_from_string("qrcoder").unwrap(); + } + + #[test] + fn read_barcodes_pure() { + let mut data = Vec::::new(); + for v in "0000101000101101011110111101011011101010100111011100101000100101110010100000".chars() { + data.push(if v == '0' { 255 } else { 0 }); + } + let iv = ImageView::from_slice(&data, data.len(), 1, ImageFormat::Lum).unwrap(); + let op = ReaderOptions::default().binarizer(Binarizer::BoolCast); + let res = read_barcodes(&iv, &op).unwrap(); + + let expected = "96385074"; + + assert_eq!(res.len(), 1); + assert_eq!(res[0].format(), BarcodeFormat::EAN8); + assert_eq!(res[0].text(), expected); + assert_eq!(res[0].bytes(), expected.as_bytes()); + assert_eq!(res[0].content_type(), ContentType::Text); + assert_eq!(res[0].orientation(), 0); + } +} From 5a9200f3f3f7bc1782e14407c18e93a1344f0dcc Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 02:24:27 +0100 Subject: [PATCH 212/587] rust: bump version to 0.2.0 for new upload to crates.io --- wrappers/rust/Cargo.toml | 2 +- wrappers/rust/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 1e6affe72f..f42aeec466 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zxing-cpp" -version = "0.1.0" +version = "0.2.0" edition = "2021" # authors = ["Axel Waggershauser "] license = "Apache-2.0" diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index d8d937adbb..ff29cd4ebf 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -13,7 +13,7 @@ In your Cargo.toml: # `bundled` causes cargo to compile and statically link in an up to date # version of the c++ core library. This is the most convient and save # way to build the library. -zxing-cpp = { version = "0.1.0", features = ["bundled", "image"] } +zxing-cpp = { version = "0.2.0", features = ["bundled", "image"] } ``` Simple example usage: From 6b92deaf74e2a124192c13efeb7f890949e22e8e Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 10:20:27 +0100 Subject: [PATCH 213/587] Quadrilateral: implement ToString() function --- core/src/Quadrilateral.h | 10 ++++++++++ example/ZXingReader.cpp | 9 +-------- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/core/src/Quadrilateral.h b/core/src/Quadrilateral.h index 3e339bb61c..caa1fe375a 100644 --- a/core/src/Quadrilateral.h +++ b/core/src/Quadrilateral.h @@ -10,6 +10,7 @@ #include #include +#include namespace ZXing { @@ -163,5 +164,14 @@ Quadrilateral Blend(const Quadrilateral& a, const Quadrilateral< return res; } +template +std::string ToString(const Quadrilateral>& points) +{ + std::string res; + for (const auto& p : points) + res += std::to_string(p.x) + "x" + std::to_string(p.y) + (&p == &points.back() ? "" : " "); + return res; +} + } // ZXing diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 74682a9d2a..3e22294bc0 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -120,13 +120,6 @@ static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& o return !filePaths.empty(); } -std::ostream& operator<<(std::ostream& os, const Position& points) -{ - for (const auto& p : points) - os << p.x << "x" << p.y << " "; - return os; -} - void drawLine(const ImageView& iv, PointI a, PointI b, bool error) { int steps = maxAbsComponent(b - a); @@ -234,7 +227,7 @@ int main(int argc, char* argv[]) << "Identifier: " << result.symbologyIdentifier() << "\n" << "Content: " << ToString(result.contentType()) << "\n" << "HasECI: " << result.hasECI() << "\n" - << "Position: " << result.position() << "\n" + << "Position: " << ToString(result.position()) << "\n" << "Rotation: " << result.orientation() << " deg\n" << "IsMirrored: " << result.isMirrored() << "\n" << "IsInverted: " << result.isInverted() << "\n"; From f917ea8b3a26b3dd19f30519143e4cda763e6e28 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 10:21:34 +0100 Subject: [PATCH 214/587] c-API: fix aliasing compiler warning (hopefully) --- core/src/zxing-c.cpp | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index bd3d12a7fa..4675af837e 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -18,6 +18,12 @@ using namespace ZXing; static thread_local std::string lastErrorMsg; +template R transmute_cast(const T& v) +{ + static_assert(sizeof(T) == sizeof(R)); + return *(const R*)(&v); +} + static char* copy(std::string_view sv) { auto ret = (char*)malloc(sv.size() + 1); @@ -84,7 +90,7 @@ zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str) return {}; try { auto format = BarcodeFormatsFromString(str); - return static_cast(*reinterpret_cast(&format)); + return static_cast(transmute_cast(format)); } catch (std::exception& e) { lastErrorMsg = e.what(); } catch (...) { @@ -139,7 +145,7 @@ void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeForm zxing_BarcodeFormats zxing_ReaderOptions_getFormats(const zxing_ReaderOptions* opts) { auto v = opts->formats(); - return *reinterpret_cast(&v); + return transmute_cast(v); } #define ZX_ENUM_PROPERTY(TYPE, GETTER, SETTER) \ From 926cb94afc12c6f48012fb44bacdd1e03777d56b Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 10:25:25 +0100 Subject: [PATCH 215/587] rust/c: add accessor to Result::position --- core/src/zxing-c.cpp | 10 ++++++++++ core/src/zxing-c.h | 11 +++++++++++ wrappers/rust/build.rs | 2 +- wrappers/rust/examples/demo.rs | 1 + wrappers/rust/src/bindings.rs | 16 ++++++++++++++++ wrappers/rust/src/lib.rs | 26 ++++++++++++++++++++++++++ wrappers/rust/src/tests.rs | 1 + 7 files changed, 66 insertions(+), 1 deletion(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index 4675af837e..328d064fcd 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -165,6 +165,11 @@ char* zxing_ContentTypeToString(zxing_ContentType type) return copy(ToString(static_cast(type))); } +char* zxing_PositionToString(const zxing_Position* position) +{ + return copy(ToString(transmute_cast(*position))); +} + bool zxing_Result_isValid(const zxing_Result* result) { return result != NULL && result->isValid(); @@ -213,6 +218,11 @@ char* zxing_Result_symbologyIdentifier(const zxing_Result* result) return copy(result->symbologyIdentifier()); } +zxing_Position zxing_Result_position(const zxing_Result* result) +{ + return transmute_cast(result->position()); +} + int zxing_Result_orientation(const zxing_Result* result) { return result->orientation(); diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index 69b9cd627c..3d38474dce 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -171,6 +171,16 @@ typedef enum char* zxing_ContentTypeToString(zxing_ContentType type); +typedef struct { + int x, y; +} zxing_PointI; + +typedef struct { + zxing_PointI topLeft, topRight, bottomRight, bottomLeft; +} zxing_Position; + +char* zxing_PositionToString(const zxing_Position* position); + bool zxing_Result_isValid(const zxing_Result* result); char* zxing_Result_errorMsg(const zxing_Result* result); zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result); @@ -179,6 +189,7 @@ uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len); char* zxing_Result_text(const zxing_Result* result); char* zxing_Result_ecLevel(const zxing_Result* result); char* zxing_Result_symbologyIdentifier(const zxing_Result* result); +zxing_Position zxing_Result_position(const zxing_Result* result); int zxing_Result_orientation(const zxing_Result* result); bool zxing_Result_isInverted(const zxing_Result* result); bool zxing_Result_isMirrored(const zxing_Result* result); diff --git a/wrappers/rust/build.rs b/wrappers/rust/build.rs index 67940e3682..6d754a58d6 100644 --- a/wrappers/rust/build.rs +++ b/wrappers/rust/build.rs @@ -29,7 +29,7 @@ fn main() -> miette::Result<()> { } // manual bindings.rs generation: - // bindgen -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --no-doc-comments --allowlist-type "zxing.*" --allowlist-function "zxing.*" + // bindgen core/src/zxing-c.h -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --no-doc-comments --no-layout-tests --with-derive-partialeq --allowlist-item "zxing.*" Ok(()) } diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index aaa4ca898d..c45f9f5ddd 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -47,6 +47,7 @@ fn main() -> anyhow::Result<()> { println!("EC Level: {}", result.ec_level()); println!("Error: {}", result.error_message()); println!("Orientation: {}", result.orientation()); + println!("Position: {}", result.position()); println!(); } } diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index f9f76ce3e4..5403f81e30 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -77,6 +77,20 @@ pub const zxing_ContentType_GS1: zxing_ContentType = 3; pub const zxing_ContentType_ISO15434: zxing_ContentType = 4; pub const zxing_ContentType_UnknownECI: zxing_ContentType = 5; pub type zxing_ContentType = ::core::ffi::c_uint; +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct zxing_PointI { + pub x: ::core::ffi::c_int, + pub y: ::core::ffi::c_int, +} +#[repr(C)] +#[derive(Debug, Copy, Clone, PartialEq)] +pub struct zxing_Position { + pub topLeft: zxing_PointI, + pub topRight: zxing_PointI, + pub bottomRight: zxing_PointI, + pub bottomLeft: zxing_PointI, +} extern "C" { pub fn zxing_ImageView_new( data: *const u8, @@ -123,6 +137,7 @@ extern "C" { pub fn zxing_ReaderOptions_getTextMode(opts: *const zxing_ReaderOptions) -> zxing_TextMode; pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; + pub fn zxing_PositionToString(position: *const zxing_Position) -> *mut ::core::ffi::c_char; pub fn zxing_Result_isValid(result: *const zxing_Result) -> bool; pub fn zxing_Result_errorMsg(result: *const zxing_Result) -> *mut ::core::ffi::c_char; pub fn zxing_Result_format(result: *const zxing_Result) -> zxing_BarcodeFormat; @@ -131,6 +146,7 @@ extern "C" { pub fn zxing_Result_text(result: *const zxing_Result) -> *mut ::core::ffi::c_char; pub fn zxing_Result_ecLevel(result: *const zxing_Result) -> *mut ::core::ffi::c_char; pub fn zxing_Result_symbologyIdentifier(result: *const zxing_Result) -> *mut ::core::ffi::c_char; + pub fn zxing_Result_position(result: *const zxing_Result) -> zxing_Position; pub fn zxing_Result_orientation(result: *const zxing_Result) -> ::core::ffi::c_int; pub fn zxing_Result_isInverted(result: *const zxing_Result) -> bool; pub fn zxing_Result_isMirrored(result: *const zxing_Result) -> bool; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index af605dbdb1..24c1ed0a91 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -11,6 +11,7 @@ mod tests; #[allow(dead_code)] #[allow(non_camel_case_types)] +#[allow(non_snake_case)] #[allow(non_upper_case_globals)] mod bindings { include!("bindings.rs"); @@ -274,6 +275,30 @@ impl Drop for ReaderResult { } } +pub type PointI = zxing_PointI; +#[repr(C)] +#[derive(Debug, Copy, Clone)] +pub struct Position { + pub top_left: PointI, + pub top_right: PointI, + pub bottom_right: PointI, + pub bottom_left: PointI, +} + +impl Display for PointI { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}x{}", self.x, self.y) + } +} + +impl Display for Position { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", unsafe { + c2r_str(zxing_PositionToString(self as *const Position as *const zxing_Position)) + }) + } +} + macro_rules! getter { ($r_name:ident, $c_name:ident, $conv:expr, $type:ty) => { pub fn $r_name(&self) -> $type { @@ -290,6 +315,7 @@ impl ReaderResult { getter!(ec_level, ecLevel, c2r_str, String); getter!(error_message, errorMsg, c2r_str, String); getter!(symbology_identifier, symbologyIdentifier, c2r_str, String); + getter!(position, position, transmute, Position); getter!(orientation, orientation, transmute, i32); getter!(is_inverted, isInverted, transmute, bool); getter!(is_mirrored, isMirrored, transmute, bool); diff --git a/wrappers/rust/src/tests.rs b/wrappers/rust/src/tests.rs index f7b01b478d..b88c90d358 100644 --- a/wrappers/rust/src/tests.rs +++ b/wrappers/rust/src/tests.rs @@ -54,5 +54,6 @@ mod tests { assert_eq!(res[0].bytes(), expected.as_bytes()); assert_eq!(res[0].content_type(), ContentType::Text); assert_eq!(res[0].orientation(), 0); + assert_eq!(res[0].position().top_left, PointI { x: 4, y: 0 }); } } From 2ee0ef8736e071e933da521203eda2807d232265 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 10:33:39 +0100 Subject: [PATCH 216/587] c++: remove unused #include statements --- core/src/BitArray.h | 2 -- core/src/BitMatrix.h | 2 -- core/src/BitMatrixCursor.h | 1 - core/src/Pattern.h | 1 - core/src/Point.h | 1 - 5 files changed, 7 deletions(-) diff --git a/core/src/BitArray.h b/core/src/BitArray.h index a260b3a3b8..c9e38c87eb 100644 --- a/core/src/BitArray.h +++ b/core/src/BitArray.h @@ -13,10 +13,8 @@ #include #include #include -#include #include #include -#include #include namespace ZXing { diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index fe8ff20cfb..821a035e79 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -11,10 +11,8 @@ #include "Point.h" #include "Range.h" -#include #include #include -#include #include namespace ZXing { diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index 25cc95cb13..a7fae30dc9 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -7,7 +7,6 @@ #include "BitMatrix.h" -#include #include namespace ZXing { diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 579354e387..3a587010e2 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -15,7 +15,6 @@ #include #include #include -#include #include namespace ZXing { diff --git a/core/src/Point.h b/core/src/Point.h index 19e1366099..e1146f8056 100644 --- a/core/src/Point.h +++ b/core/src/Point.h @@ -6,7 +6,6 @@ #pragma once #include -#include #include namespace ZXing { From d2f330a9d25c1b9d0ad78d97e07d49a7d5ef06ec Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 11:43:12 +0100 Subject: [PATCH 217/587] rust/c: add some missing ReaderOptions and Result properties Those are: hasECI, bytesECI, minLineCount, lineCount --- core/src/zxing-c.cpp | 78 ++++++++++++++--------------------- core/src/zxing-c.h | 5 +++ wrappers/rust/src/bindings.rs | 5 +++ wrappers/rust/src/lib.rs | 7 ++++ wrappers/rust/src/tests.rs | 3 ++ 5 files changed, 51 insertions(+), 47 deletions(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index 328d064fcd..b9340f4fbb 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -34,6 +34,19 @@ static char* copy(std::string_view sv) return ret; } +static uint8_t* copy(const ByteArray& ba, int* len) +{ + *len = Size(ba); + + auto ret = (uint8_t*)malloc(*len + 1); + if (ret) + memcpy(ret, ba.data(), *len); + else + *len = 0; + + return ret; +} + static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts, int maxSymbols) { try { @@ -135,6 +148,7 @@ ZX_PROPERTY(bool, tryInvert, TryInvert) ZX_PROPERTY(bool, tryDownscale, TryDownscale) ZX_PROPERTY(bool, isPure, IsPure) ZX_PROPERTY(bool, returnErrors, ReturnErrors) +ZX_PROPERTY(int, minLineCount, MinLineCount) ZX_PROPERTY(int, maxNumberOfSymbols, MaxNumberOfSymbols) void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeFormats formats) @@ -170,6 +184,7 @@ char* zxing_PositionToString(const zxing_Position* position) return copy(ToString(transmute_cast(*position))); } + bool zxing_Result_isValid(const zxing_Result* result) { return result != NULL && result->isValid(); @@ -180,63 +195,32 @@ char* zxing_Result_errorMsg(const zxing_Result* result) return copy(ToString(result->error())); } -zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result) -{ - return static_cast(result->format()); -} - -zxing_ContentType zxing_Result_contentType(const zxing_Result* result) -{ - return static_cast(result->contentType()); -} - uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len) { - *len = Size(result->bytes()); - - auto ret = (uint8_t*)malloc(*len + 1); - if (ret) - memcpy(ret, result->bytes().data(), *len); - else - *len = 0; - - return ret; -} - -char* zxing_Result_text(const zxing_Result* result) -{ - return copy(result->text()); + return copy(result->bytes(), len); } -char* zxing_Result_ecLevel(const zxing_Result* result) +uint8_t* zxing_Result_bytesECI(const zxing_Result* result, int* len) { - return copy(result->ecLevel()); + return copy(result->bytesECI(), len); } -char* zxing_Result_symbologyIdentifier(const zxing_Result* result) -{ - return copy(result->symbologyIdentifier()); -} +#define ZX_GETTER(TYPE, GETTER, TRANS) \ + TYPE zxing_Result_##GETTER(const zxing_Result* result) { return static_cast(TRANS(result->GETTER())); } -zxing_Position zxing_Result_position(const zxing_Result* result) -{ - return transmute_cast(result->position()); -} +ZX_GETTER(zxing_BarcodeFormat, format,) +ZX_GETTER(zxing_ContentType, contentType,) +ZX_GETTER(char*, text, copy) +ZX_GETTER(char*, ecLevel, copy) +ZX_GETTER(char*, symbologyIdentifier, copy) +ZX_GETTER(zxing_Position, position, transmute_cast) -int zxing_Result_orientation(const zxing_Result* result) -{ - return result->orientation(); -} +ZX_GETTER(int, orientation,) +ZX_GETTER(bool, hasECI,) +ZX_GETTER(bool, isInverted,) +ZX_GETTER(bool, isMirrored,) +ZX_GETTER(int, lineCount,) -bool zxing_Result_isInverted(const zxing_Result* result) -{ - return result->isInverted(); -} - -bool zxing_Result_isMirrored(const zxing_Result* result) -{ - return result->isMirrored(); -} /* * ZXing/ReadBarcode.h diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index 3d38474dce..0ec485bc10 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -141,6 +141,7 @@ void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeForm void zxing_ReaderOptions_setBinarizer(zxing_ReaderOptions* opts, zxing_Binarizer binarizer); void zxing_ReaderOptions_setEanAddOnSymbol(zxing_ReaderOptions* opts, zxing_EanAddOnSymbol eanAddOnSymbol); void zxing_ReaderOptions_setTextMode(zxing_ReaderOptions* opts, zxing_TextMode textMode); +void zxing_ReaderOptions_setMinLineCount(zxing_ReaderOptions* opts, int n); void zxing_ReaderOptions_setMaxNumberOfSymbols(zxing_ReaderOptions* opts, int n); bool zxing_ReaderOptions_getTryHarder(const zxing_ReaderOptions* opts); @@ -153,6 +154,7 @@ zxing_BarcodeFormats zxing_ReaderOptions_getFormats(const zxing_ReaderOptions* o zxing_Binarizer zxing_ReaderOptions_getBinarizer(const zxing_ReaderOptions* opts); zxing_EanAddOnSymbol zxing_ReaderOptions_getEanAddOnSymbol(const zxing_ReaderOptions* opts); zxing_TextMode zxing_ReaderOptions_getTextMode(const zxing_ReaderOptions* opts); +int zxing_ReaderOptions_getMinLineCount(const zxing_ReaderOptions* opts); int zxing_ReaderOptions_getMaxNumberOfSymbols(const zxing_ReaderOptions* opts); /* @@ -186,13 +188,16 @@ char* zxing_Result_errorMsg(const zxing_Result* result); zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result); zxing_ContentType zxing_Result_contentType(const zxing_Result* result); uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len); +uint8_t* zxing_Result_bytesECI(const zxing_Result* result, int* len); char* zxing_Result_text(const zxing_Result* result); char* zxing_Result_ecLevel(const zxing_Result* result); char* zxing_Result_symbologyIdentifier(const zxing_Result* result); zxing_Position zxing_Result_position(const zxing_Result* result); int zxing_Result_orientation(const zxing_Result* result); +bool zxing_Result_hasECI(const zxing_Result* result); bool zxing_Result_isInverted(const zxing_Result* result); bool zxing_Result_isMirrored(const zxing_Result* result); +int zxing_Result_lineCount(const zxing_Result* result); /* * ZXing/ReadBarcode.h diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 5403f81e30..f118ac03f5 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -124,6 +124,7 @@ extern "C" { pub fn zxing_ReaderOptions_setBinarizer(opts: *mut zxing_ReaderOptions, binarizer: zxing_Binarizer); pub fn zxing_ReaderOptions_setEanAddOnSymbol(opts: *mut zxing_ReaderOptions, eanAddOnSymbol: zxing_EanAddOnSymbol); pub fn zxing_ReaderOptions_setTextMode(opts: *mut zxing_ReaderOptions, textMode: zxing_TextMode); + pub fn zxing_ReaderOptions_setMinLineCount(opts: *mut zxing_ReaderOptions, n: ::core::ffi::c_int); pub fn zxing_ReaderOptions_setMaxNumberOfSymbols(opts: *mut zxing_ReaderOptions, n: ::core::ffi::c_int); pub fn zxing_ReaderOptions_getTryHarder(opts: *const zxing_ReaderOptions) -> bool; pub fn zxing_ReaderOptions_getTryRotate(opts: *const zxing_ReaderOptions) -> bool; @@ -135,6 +136,7 @@ extern "C" { pub fn zxing_ReaderOptions_getBinarizer(opts: *const zxing_ReaderOptions) -> zxing_Binarizer; pub fn zxing_ReaderOptions_getEanAddOnSymbol(opts: *const zxing_ReaderOptions) -> zxing_EanAddOnSymbol; pub fn zxing_ReaderOptions_getTextMode(opts: *const zxing_ReaderOptions) -> zxing_TextMode; + pub fn zxing_ReaderOptions_getMinLineCount(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; pub fn zxing_PositionToString(position: *const zxing_Position) -> *mut ::core::ffi::c_char; @@ -143,13 +145,16 @@ extern "C" { pub fn zxing_Result_format(result: *const zxing_Result) -> zxing_BarcodeFormat; pub fn zxing_Result_contentType(result: *const zxing_Result) -> zxing_ContentType; pub fn zxing_Result_bytes(result: *const zxing_Result, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn zxing_Result_bytesECI(result: *const zxing_Result, len: *mut ::core::ffi::c_int) -> *mut u8; pub fn zxing_Result_text(result: *const zxing_Result) -> *mut ::core::ffi::c_char; pub fn zxing_Result_ecLevel(result: *const zxing_Result) -> *mut ::core::ffi::c_char; pub fn zxing_Result_symbologyIdentifier(result: *const zxing_Result) -> *mut ::core::ffi::c_char; pub fn zxing_Result_position(result: *const zxing_Result) -> zxing_Position; pub fn zxing_Result_orientation(result: *const zxing_Result) -> ::core::ffi::c_int; + pub fn zxing_Result_hasECI(result: *const zxing_Result) -> bool; pub fn zxing_Result_isInverted(result: *const zxing_Result) -> bool; pub fn zxing_Result_isMirrored(result: *const zxing_Result) -> bool; + pub fn zxing_Result_lineCount(result: *const zxing_Result) -> ::core::ffi::c_int; pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Result; pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Results; pub fn zxing_Result_delete(result: *mut zxing_Result); diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 24c1ed0a91..6a43a06c9c 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -265,6 +265,7 @@ impl ReaderOptions { property!(binarizer, Binarizer); property!(ean_add_on_symbol, EanAddOnSymbol); property!(max_number_of_symbols, i32); + property!(min_line_count, i32); } pub struct ReaderResult(*mut zxing_Result); @@ -317,13 +318,19 @@ impl ReaderResult { getter!(symbology_identifier, symbologyIdentifier, c2r_str, String); getter!(position, position, transmute, Position); getter!(orientation, orientation, transmute, i32); + getter!(has_eci, hasECI, transmute, bool); getter!(is_inverted, isInverted, transmute, bool); getter!(is_mirrored, isMirrored, transmute, bool); + getter!(line_count, lineCount, transmute, i32); pub fn bytes(&self) -> Vec { let mut len: c_int = 0; unsafe { c2r_vec(zxing_Result_bytes(self.0, &mut len), len) } } + pub fn bytes_eci(&self) -> Vec { + let mut len: c_int = 0; + unsafe { c2r_vec(zxing_Result_bytesECI(self.0, &mut len), len) } + } } pub fn barcode_formats_from_string(str: impl AsRef) -> Result { diff --git a/wrappers/rust/src/tests.rs b/wrappers/rust/src/tests.rs index b88c90d358..952e4e3141 100644 --- a/wrappers/rust/src/tests.rs +++ b/wrappers/rust/src/tests.rs @@ -49,11 +49,14 @@ mod tests { let expected = "96385074"; assert_eq!(res.len(), 1); + assert_eq!(res[0].is_valid(), true); assert_eq!(res[0].format(), BarcodeFormat::EAN8); assert_eq!(res[0].text(), expected); assert_eq!(res[0].bytes(), expected.as_bytes()); + assert_eq!(res[0].has_eci(), false); assert_eq!(res[0].content_type(), ContentType::Text); assert_eq!(res[0].orientation(), 0); assert_eq!(res[0].position().top_left, PointI { x: 4, y: 0 }); + assert_eq!(res[0].line_count(), 1); } } From 24bee7fb4975898759ab0df3f111894e17cfb7e7 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 11:45:06 +0100 Subject: [PATCH 218/587] rust: new release --- wrappers/rust/Cargo.toml | 2 +- wrappers/rust/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index f42aeec466..cf6fcfca3b 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zxing-cpp" -version = "0.2.0" +version = "0.2.1" edition = "2021" # authors = ["Axel Waggershauser "] license = "Apache-2.0" diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index ff29cd4ebf..8a5773a787 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -13,7 +13,7 @@ In your Cargo.toml: # `bundled` causes cargo to compile and statically link in an up to date # version of the c++ core library. This is the most convient and save # way to build the library. -zxing-cpp = { version = "0.2.0", features = ["bundled", "image"] } +zxing-cpp = { version = "0.2.1", features = ["bundled", "image"] } ``` Simple example usage: From 052d3087c7ce46206e7e5b7992d867d2e44e9940 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 22 Jan 2024 11:48:22 +0100 Subject: [PATCH 219/587] rust: fine-tune README.md --- wrappers/rust/README.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index 8a5773a787..d22a072518 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -10,9 +10,9 @@ In your Cargo.toml: ```toml [dependencies] -# `bundled` causes cargo to compile and statically link in an up to date -# version of the c++ core library. This is the most convient and save -# way to build the library. +# `bundled` causes cargo to compile and statically link in an up to +# date version of the c++ core library. This is the most convient +# and safe way to build the library. zxing-cpp = { version = "0.2.1", features = ["bundled", "image"] } ``` @@ -24,7 +24,7 @@ use zxing_cpp::{ImageView, ReaderOptions, BarcodeFormat, read_barcodes}; fn main() -> anyhow::Result<()> { let image = image::open("some-image-file.jpg")?; let iv = ImageView::try_from(&image)?; - let opts = ReaderOptions::new() + let opts = ReaderOptions::default() .formats(BarcodeFormat::QRCode | BarcodeFormat::LinearCodes) .try_invert(false); @@ -46,7 +46,7 @@ Note: This should currently be considered a pre-release. The API may change slig ## Optional Features -zxing-cpp provides several features that are behind [Cargo features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are: +zxing-cpp provides features that are behind [Cargo features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are: * `bundled` uses a bundled version of the [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) c++ library. * [`image`](https://crates.io/crates/image) allows convenient `ImageView::from(&GreyImage)` conversion. From fb8d447c52aa583d6a499012792a9be93330749e Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 23 Jan 2024 01:49:36 +0100 Subject: [PATCH 220/587] rust: update README, add Benchmarking section --- wrappers/rust/README.md | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index d22a072518..131f81685b 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -2,7 +2,9 @@ zxing-cpp is a Rust wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). -It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. It was originally ported from the Java ZXing Library but has been developed further and now includes many improvements in terms of runtime and detection performance. +It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. +It was originally ported from the Java ZXing Library but has been developed further and now includes +many improvements in terms of runtime and detection performance. ## Usage @@ -42,11 +44,19 @@ fn main() -> anyhow::Result<()> { } ``` -Note: This should currently be considered a pre-release. The API may change slightly to be even more "rusty" depending on community feedback. +Note: This should currently be considered a pre-release. The API may change slightly to be even more +"rusty" depending on community feedback. ## Optional Features -zxing-cpp provides features that are behind [Cargo features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). They are: +zxing-cpp provides features that are behind [Cargo features](https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section). +They are: * `bundled` uses a bundled version of the [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) c++ library. -* [`image`](https://crates.io/crates/image) allows convenient `ImageView::from(&GreyImage)` conversion. +* [`image`](https://crates.io/crates/image) allows convenient `ImageView` construction from `GreyImage` and `DynamicImage`. + +## Benchmarking + +To compare the performance of this Rust wrapper project with other availble barcode scanner Rust libraries, +I started the project [zxing-bench](https://github.com/axxel/zxing-bench). The README contains some a few +results to get an idea. From b152afd750e0c4b2b1b712bfcba6d0abb6a4c81d Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 25 Jan 2024 09:00:40 +0100 Subject: [PATCH 221/587] cmake: exclude `zxing-c.h` from precompiled headers --- core/CMakeLists.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index eef81c2eb5..77cc1f9763 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -488,7 +488,9 @@ endif() set_target_properties(ZXing PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") -target_precompile_headers(ZXing PRIVATE ${PUBLIC_HEADERS}) +set(PRECOMPILE_HEADERS ${PUBLIC_HEADERS}) +list(REMOVE_ITEM PRECOMPILE_HEADERS "$<$:${CMAKE_CURRENT_SOURCE_DIR}/src/zxing-c.h>") +target_precompile_headers(ZXing PRIVATE ${PRECOMPILE_HEADERS}) set_source_files_properties(src/libzueci/zueci.c PROPERTIES SKIP_PRECOMPILE_HEADERS ON) set_source_files_properties(src/DecodeHints.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON) From fc8f32d7db00060b3aab24338c08ab792cef0bfd Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 10:36:00 +0100 Subject: [PATCH 222/587] ReaderOptions: deprecate `validateITFCheckSum` property According to the specification (and now the implementation) the ITF symbol has a valid checksum if and only if `symbologyIdentifier()[2] == '1'`. See also https://github.com/zxing-cpp/zxing-cpp/discussions/704. --- core/src/ReaderOptions.h | 12 ++++++------ core/src/oned/ODITFReader.cpp | 13 +++---------- core/src/oned/ODITFReader.h | 2 +- 3 files changed, 10 insertions(+), 17 deletions(-) diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index 3b4cabfa9e..73c4d2a9b9 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -96,10 +96,10 @@ class ReaderOptions #endif {} -#define ZX_PROPERTY(TYPE, GETTER, SETTER) \ - TYPE GETTER() const noexcept { return _##GETTER; } \ - ReaderOptions& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ - ReaderOptions&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } +#define ZX_PROPERTY(TYPE, GETTER, SETTER, ...) \ + __VA_ARGS__ TYPE GETTER() const noexcept { return _##GETTER; } \ + __VA_ARGS__ ReaderOptions& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ + __VA_ARGS__ ReaderOptions&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } /// Specify a set of BarcodeFormats that should be searched for, the default is all supported formats. ZX_PROPERTY(BarcodeFormats, formats, setFormats) @@ -147,8 +147,8 @@ class ReaderOptions /// Assume Code-39 codes employ a check digit and validate it. ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum) - /// Assume ITF codes employ a GS1 check digit and validate it. - ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum) + /// This flag is deprecated and does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. + ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, [[deprecated]]) /// If true, return the start and end chars in a Codabar barcode instead of stripping them. ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd) diff --git a/core/src/oned/ODITFReader.cpp b/core/src/oned/ODITFReader.cpp index 439a1933dd..c9af66503a 100644 --- a/core/src/oned/ODITFReader.cpp +++ b/core/src/oned/ODITFReader.cpp @@ -65,19 +65,12 @@ Result ITFReader::decodePattern(int rowNumber, PatternView& next, std::unique_pt if (!IsRightGuard(next, STOP_PATTERN_1, minQuietZone) && !IsRightGuard(next, STOP_PATTERN_2, minQuietZone)) return {}; - Error error; - if (_opts.validateITFCheckSum() && !GTIN::IsCheckDigitValid(txt)) - error = ChecksumError(); - // Symbology identifier ISO/IEC 16390:2007 Annex C Table C.1 - // See also GS1 General Specifications 5.1.3 Figure 5.1.3-2 - SymbologyIdentifier symbologyIdentifier = {'I', '0'}; // No check character validation + // See also GS1 General Specifications 5.1.2 Figure 5.1.2-2 + SymbologyIdentifier symbologyIdentifier = {'I', GTIN::IsCheckDigitValid(txt) ? '1' : '0'}; - if (_opts.validateITFCheckSum() || (txt.size() == 14 && GTIN::IsCheckDigitValid(txt))) // If no hint test if valid ITF-14 - symbologyIdentifier.modifier = '1'; // Modulo 10 symbol check character validated and transmitted - int xStop = next.pixelsTillEnd(); - return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::ITF, symbologyIdentifier, error); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::ITF, symbologyIdentifier); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODITFReader.h b/core/src/oned/ODITFReader.h index e71d0ea25e..55159d7d5a 100644 --- a/core/src/oned/ODITFReader.h +++ b/core/src/oned/ODITFReader.h @@ -17,7 +17,7 @@ namespace ZXing::OneD { * At the moment it reads length >= 6. Not all lengths are scanned, especially shorter ones, to avoid false positives. * This in turn is due to a lack of required checksum function.

    * -*

    The checksum is optional and is only checked if the validateITFCheckSum hint is given.

    +*

    According to the specification, the modifier (3rd char) of the symbologyIdentifier is '1' iff the symbol has a valid checksum

    * *

    http://en.wikipedia.org/wiki/Interleaved_2_of_5 * is a great reference for Interleaved 2 of 5 information.

    From a67b89bf64465c9db8105ed4d861d328ff7c3196 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 11:03:02 +0100 Subject: [PATCH 223/587] rust: fix typos in README --- wrappers/rust/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index 131f81685b..c97ee2d65e 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -12,8 +12,8 @@ In your Cargo.toml: ```toml [dependencies] -# `bundled` causes cargo to compile and statically link in an up to -# date version of the c++ core library. This is the most convient +# `bundled` causes cargo to compile and statically link an up to +# date version of the c++ core library. This is the most convenient # and safe way to build the library. zxing-cpp = { version = "0.2.1", features = ["bundled", "image"] } ``` @@ -58,5 +58,5 @@ They are: ## Benchmarking To compare the performance of this Rust wrapper project with other availble barcode scanner Rust libraries, -I started the project [zxing-bench](https://github.com/axxel/zxing-bench). The README contains some a few +I started the project [zxing-bench](https://github.com/axxel/zxing-bench). The README contains a few results to get an idea. From 279824455afa18d151429568e6c2cb364e1de267 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 11:10:32 +0100 Subject: [PATCH 224/587] c-API: pass parameter to zxing_PositionToString as value instead of pointer --- core/src/zxing-c.cpp | 4 ++-- core/src/zxing-c.h | 2 +- wrappers/c/zxing-c-test.c | 1 + wrappers/rust/src/bindings.rs | 2 +- wrappers/rust/src/lib.rs | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index b9340f4fbb..3756928d33 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -179,9 +179,9 @@ char* zxing_ContentTypeToString(zxing_ContentType type) return copy(ToString(static_cast(type))); } -char* zxing_PositionToString(const zxing_Position* position) +char* zxing_PositionToString(zxing_Position position) { - return copy(ToString(transmute_cast(*position))); + return copy(ToString(transmute_cast(position))); } diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index 0ec485bc10..c7b95a53c5 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -181,7 +181,7 @@ typedef struct { zxing_PointI topLeft, topRight, bottomRight, bottomLeft; } zxing_Position; -char* zxing_PositionToString(const zxing_Position* position); +char* zxing_PositionToString(zxing_Position position); bool zxing_Result_isValid(const zxing_Result* result); char* zxing_Result_errorMsg(const zxing_Result* result); diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index acd418f310..2b62511c18 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -82,6 +82,7 @@ int main(int argc, char** argv) printF("Identifier : %s\n", zxing_Result_symbologyIdentifier(result)); printF("EC Level : %s\n", zxing_Result_ecLevel(result)); printF("Error : %s\n", zxing_Result_errorMsg(result)); + printF("Position : %s\n", zxing_PositionToString(zxing_Result_position(result))); printf("Rotation : %d\n", zxing_Result_orientation(result)); if (i < n-1) diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index f118ac03f5..2fad1b4168 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -139,7 +139,7 @@ extern "C" { pub fn zxing_ReaderOptions_getMinLineCount(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; - pub fn zxing_PositionToString(position: *const zxing_Position) -> *mut ::core::ffi::c_char; + pub fn zxing_PositionToString(position: zxing_Position) -> *mut ::core::ffi::c_char; pub fn zxing_Result_isValid(result: *const zxing_Result) -> bool; pub fn zxing_Result_errorMsg(result: *const zxing_Result) -> *mut ::core::ffi::c_char; pub fn zxing_Result_format(result: *const zxing_Result) -> zxing_BarcodeFormat; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 6a43a06c9c..6904d1f0d4 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -295,7 +295,7 @@ impl Display for PointI { impl Display for Position { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", unsafe { - c2r_str(zxing_PositionToString(self as *const Position as *const zxing_Position)) + c2r_str(zxing_PositionToString(*(self as *const Position as *const zxing_Position))) }) } } From ef0a421406a1e791b31d7ea2ee1b065db11f06c7 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 11:14:06 +0100 Subject: [PATCH 225/587] c-API: clang-format fixes --- core/src/zxing-c.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index c7b95a53c5..b214738827 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -36,8 +36,7 @@ typedef struct zxing_Results zxing_Results; * ZXing/ImageView.h */ -typedef enum -{ +typedef enum { zxing_ImageFormat_None = 0, zxing_ImageFormat_Lum = 0x01000000, zxing_ImageFormat_RGB = 0x03000102, @@ -52,7 +51,7 @@ zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, int pixStride); void zxing_ImageView_delete(zxing_ImageView* iv); -void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); +void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); void zxing_ImageView_rotate(zxing_ImageView* iv, int degree); /* @@ -173,11 +172,13 @@ typedef enum char* zxing_ContentTypeToString(zxing_ContentType type); -typedef struct { +typedef struct zxing_PointI +{ int x, y; } zxing_PointI; -typedef struct { +typedef struct zxing_Position +{ zxing_PointI topLeft, topRight, bottomRight, bottomLeft; } zxing_Position; From efc06387dac9b148dce7a41c0fc5d6eda74332ec Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 11:15:33 +0100 Subject: [PATCH 226/587] android: deprecate `validateITFCheckSum` reader option --- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 1 - .../android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 0e25ef82e2..1d87d817cb 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -312,7 +312,6 @@ static ReaderOptions CreateReaderOptions(JNIEnv* env, jobject opts) .setMaxNumberOfSymbols(GetIntField(env, cls, opts, "maxNumberOfSymbols")) .setTryCode39ExtendedMode(GetBooleanField(env, cls, opts, "tryCode39ExtendedMode")) .setValidateCode39CheckSum(GetBooleanField(env, cls, opts, "validateCode39CheckSum")) - .setValidateITFCheckSum(GetBooleanField(env, cls, opts, "validateITFCheckSum")) .setReturnCodabarStartEnd(GetBooleanField(env, cls, opts, "returnCodabarStartEnd")) .setReturnErrors(GetBooleanField(env, cls, opts, "returnErrors")) .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, opts, "eanAddOnSymbol", "EanAddOnSymbol"))) diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index b4f7542290..d93f89ece6 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -77,6 +77,7 @@ public class BarcodeReader(public var options: Options = Options()) { var maxNumberOfSymbols: Int = 0xff, var tryCode39ExtendedMode: Boolean = false, var validateCode39CheckSum: Boolean = false, + @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateITFCheckSum: Boolean = false, var returnCodabarStartEnd: Boolean = false, var returnErrors: Boolean = false, From b3fe5744b2a0ac554efa70635a087c5ff3342c42 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 16:27:20 +0100 Subject: [PATCH 227/587] Code39: deprecate `tryCode39ExtendedMode` and `validateCode39CheckSum` The first one is now a true 'try' and enabled by default. The second is always performed and the result reported via the symbologyIdentifier. This detects more samples correctly out of the box. See also https://github.com/zxing-cpp/zxing-cpp/discussions/704 --- core/src/ReaderOptions.h | 10 ++--- core/src/oned/ODCode39Reader.cpp | 39 ++++++++++--------- core/src/oned/ODCode93Reader.cpp | 6 +-- test/blackbox/BlackboxTestRunner.cpp | 2 +- test/unit/oned/ODCode39ExtendedModeTest.cpp | 4 +- test/unit/oned/ODCode39ReaderTest.cpp | 24 +++--------- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 2 - .../src/main/java/zxingcpp/BarcodeReader.kt | 2 + 8 files changed, 40 insertions(+), 49 deletions(-) diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index 73c4d2a9b9..b8c397c622 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -141,13 +141,13 @@ class ReaderOptions /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) - /// If true, the Code-39 reader will try to read extended mode. - ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) + /// Deprecated / does nothing. The Code39 was decoded from full ASCII iff symbologyIdentifier()[2] >= '4' + ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode, [[deprecated]]) - /// Assume Code-39 codes employ a check digit and validate it. - ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum) + /// Deprecated / does nothing. The Code39 symbol has a valid checksum iff symbologyIdentifier()[2] is an odd digit + ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum, [[deprecated]]) - /// This flag is deprecated and does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. + /// Deprecated / does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, [[deprecated]]) /// If true, return the start and end chars in a Codabar barcode instead of stripping them. diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index ee3dc9da32..99b4ecc4d1 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -48,10 +48,9 @@ using CounterContainer = std::array; // each character has 5 bars and 4 spaces constexpr int CHAR_LEN = 9; -/** Decode the extended string in place. Return false if FormatError occurred. +/** Decode the full ASCII string. Return empty string if FormatError occurred. * ctrl is either "$%/+" for code39 or "abcd" for code93. */ -bool -DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]) +std::string DecodeCode39AndCode93FullASCII(std::string encoded, const char ctrl[4]) { auto out = encoded.begin(); for (auto in = encoded.cbegin(); in != encoded.cend(); ++in) { @@ -59,7 +58,7 @@ DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]) if (Contains(ctrl, c)) { char next = *++in; // if in is one short of cend(), then next == 0 if (next < 'A' || next > 'Z') - return false; + return {}; if (c == ctrl[0]) c = next - 64; // $A to $Z map to control codes SH to SB else if (c == ctrl[1]) @@ -72,7 +71,7 @@ DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]) *out++ = c; } encoded.erase(out, encoded.end()); - return true; + return encoded; } Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const @@ -115,24 +114,28 @@ Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique if (Size(txt) < minCharCount - 2 || !next.hasQuietZoneAfter(QUIET_ZONE_SCALE)) return {}; - Error error; - if (_opts.validateCode39CheckSum()) { - auto checkDigit = txt.back(); - txt.pop_back(); - int checksum = TransformReduce(txt, 0, [](char c) { return IndexOf(ALPHABET, c); }); - if (checkDigit != ALPHABET[checksum % 43]) - error = ChecksumError(); - } + auto lastChar = txt.back(); + txt.pop_back(); + int checksum = TransformReduce(txt, 0, [](char c) { return IndexOf(ALPHABET, c); }); + bool hasValidCheckSum = lastChar == ALPHABET[checksum % 43]; + if (!hasValidCheckSum) + txt.push_back(lastChar); + + const char shiftChars[] = "$%/+"; + auto fullASCII = DecodeCode39AndCode93FullASCII(txt, shiftChars); + bool hasFullASCII = !fullASCII.empty() && std::find_first_of(txt.begin(), txt.end(), shiftChars, shiftChars + 4) != txt.end(); + if (hasFullASCII) + txt = fullASCII; - if (!error && _opts.tryCode39ExtendedMode() && !DecodeExtendedCode39AndCode93(txt, "$%/+")) - error = FormatError("Decoding extended Code39/Code93 failed"); + if (hasValidCheckSum) + txt.push_back(lastChar); // Symbology identifier modifiers ISO/IEC 16388:2007 Annex C Table C.1 - constexpr const char symbologyModifiers[4] = { '0', '3' /*checksum*/, '4' /*extended*/, '7' /*checksum,extended*/ }; - SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)_opts.tryCode39ExtendedMode() * 2 + (int)_opts.validateCode39CheckSum()]}; + constexpr const char symbologyModifiers[4] = { '0', '1' /*checksum*/, '4' /*full ASCII*/, '5' /*checksum + full ASCII*/ }; + SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)hasValidCheckSum + 2 * (int)hasFullASCII]}; int xStop = next.pixelsTillEnd(); - return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier, error); + return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODCode93Reader.cpp b/core/src/oned/ODCode93Reader.cpp index cfeaa0db3f..1a2bcc60e4 100644 --- a/core/src/oned/ODCode93Reader.cpp +++ b/core/src/oned/ODCode93Reader.cpp @@ -62,7 +62,7 @@ CheckChecksums(const std::string& result) } // forward declare here. see ODCode39Reader.cpp. Not put in header to not pollute the public facing API -bool DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]); +std::string DecodeCode39AndCode93FullASCII(std::string encoded, const char ctrl[4]); constexpr int CHAR_LEN = 6; constexpr int CHAR_SUM = 9; @@ -121,8 +121,8 @@ Result Code93Reader::decodePattern(int rowNumber, PatternView& next, std::unique // Remove checksum digits txt.resize(txt.size() - 2); - if (!error && !DecodeExtendedCode39AndCode93(txt, "abcd")) - error = FormatError("Decoding extended Code39/Code93 failed"); + if (!error && (txt = DecodeCode39AndCode93FullASCII(txt, "abcd")).empty()) + error = FormatError("ASCII decoding of Code93 failed"); // Symbology identifier ISO/IEC 15424:2008 4.4.10 no modifiers SymbologyIdentifier symbologyIdentifier = {'G', '0'}; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 6849319a84..27385e34da 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -402,7 +402,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("code39-2", "Code39", 2, { { 2, 2, 0 }, { 2, 2, 180 }, - }, ReaderOptions().setTryCode39ExtendedMode(true)); + }); runTests("code39-3", "Code39", 12, { { 12, 12, 0 }, diff --git a/test/unit/oned/ODCode39ExtendedModeTest.cpp b/test/unit/oned/ODCode39ExtendedModeTest.cpp index 589880a617..e7a3dfbb90 100644 --- a/test/unit/oned/ODCode39ExtendedModeTest.cpp +++ b/test/unit/oned/ODCode39ExtendedModeTest.cpp @@ -17,13 +17,13 @@ using namespace ZXing::OneD; static std::string Decode(std::string_view encoded) { - auto opts = ReaderOptions().setTryCode39ExtendedMode(true); + auto opts = ReaderOptions(); BitArray row = Utility::ParseBitArray(encoded, '1'); Result result = DecodeSingleRow(Code39Reader(opts), row.range()); return result.text(TextMode::Plain); } -TEST(ODCode39ExtendedModeTest, Decode) +TEST(ODCode39FullASCIITest, Decode) { EXPECT_EQ(Decode( "00000100101101101010100100100101100101010110100100100101011010100101101001001001" diff --git a/test/unit/oned/ODCode39ReaderTest.cpp b/test/unit/oned/ODCode39ReaderTest.cpp index a8684a7682..f602a8fc23 100644 --- a/test/unit/oned/ODCode39ReaderTest.cpp +++ b/test/unit/oned/ODCode39ReaderTest.cpp @@ -38,34 +38,22 @@ TEST(ODCode39ReaderTest, SymbologyIdentifier) { // "A" with checksum PatternRow row({ 2, 1, 1, 1, 1, 2, 1, 1, 2, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2 }); - auto result = parse(row, ReaderOptions().setValidateCode39CheckSum(true)); - EXPECT_EQ(result.symbologyIdentifier(), "]A3"); - EXPECT_EQ(result.text(), "A"); - - result = parse(row); - EXPECT_EQ(result.symbologyIdentifier(), "]A0"); + auto result = parse(row); + EXPECT_EQ(result.symbologyIdentifier(), "]A1"); EXPECT_EQ(result.text(), "AA"); } { // Extended "a" PatternRow row({ 1, 2, 1, 1, 1, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2 }); - auto result = parse(row, ReaderOptions().setTryCode39ExtendedMode(true)); + auto result = parse(row); EXPECT_EQ(result.symbologyIdentifier(), "]A4"); EXPECT_EQ(result.text(), "a"); - - result = parse(row); - EXPECT_EQ(result.symbologyIdentifier(), "]A0"); - EXPECT_EQ(result.text(), "+A"); } { // Extended "a" with checksum PatternRow row({ 1, 2, 1, 1, 1, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2, 0, 2, 1, 1, 2, 1, 1, 2, 1, 1 }); - auto result = parse(row, ReaderOptions().setTryCode39ExtendedMode(true).setValidateCode39CheckSum(true)); - EXPECT_EQ(result.symbologyIdentifier(), "]A7"); - EXPECT_EQ(result.text(), "a"); - - result = parse(row); - EXPECT_EQ(result.symbologyIdentifier(), "]A0"); - EXPECT_EQ(result.text(), "+A8"); + auto result = parse(row); + EXPECT_EQ(result.symbologyIdentifier(), "]A5"); + EXPECT_EQ(result.text(), "a8"); } } diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 1d87d817cb..60222bdf05 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -310,8 +310,6 @@ static ReaderOptions CreateReaderOptions(JNIEnv* env, jobject opts) .setDownscaleFactor(GetIntField(env, cls, opts, "downscaleFactor")) .setMinLineCount(GetIntField(env, cls, opts, "minLineCount")) .setMaxNumberOfSymbols(GetIntField(env, cls, opts, "maxNumberOfSymbols")) - .setTryCode39ExtendedMode(GetBooleanField(env, cls, opts, "tryCode39ExtendedMode")) - .setValidateCode39CheckSum(GetBooleanField(env, cls, opts, "validateCode39CheckSum")) .setReturnCodabarStartEnd(GetBooleanField(env, cls, opts, "returnCodabarStartEnd")) .setReturnErrors(GetBooleanField(env, cls, opts, "returnErrors")) .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, opts, "eanAddOnSymbol", "EanAddOnSymbol"))) diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index d93f89ece6..051c9f5474 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -75,7 +75,9 @@ public class BarcodeReader(public var options: Options = Options()) { var downscaleThreshold: Int = 500, var minLineCount: Int = 2, var maxNumberOfSymbols: Int = 0xff, + @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var tryCode39ExtendedMode: Boolean = false, + @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateCode39CheckSum: Boolean = false, @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateITFCheckSum: Boolean = false, From bf40a6662654a3d7293848512fa9468ebdbda1bf Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 02:05:20 +0100 Subject: [PATCH 228/587] rust: make ImageView a value type like shared pointer This allows to pass `ImageView`, `&ImageView` and `&DynamicImage` directly to `read_barcodes`. --- wrappers/rust/examples/demo.rs | 25 +++++++++++++------------ wrappers/rust/src/lib.rs | 29 ++++++++++++++++++----------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index c45f9f5ddd..b0c045861b 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -19,35 +19,36 @@ fn main() -> anyhow::Result<()> { let image = image::open(&cli.filename)?; - #[cfg(feature = "image")] - let iv = ImageView::try_from(&image)?; #[cfg(not(feature = "image"))] let lum_img = image.into_luma8(); #[cfg(not(feature = "image"))] let iv = ImageView::from_slice(&lum_img, lum_img.width(), lum_img.height(), ImageFormat::Lum)?; let formats = barcode_formats_from_string(cli.formats.unwrap_or_default())?; - let opts = ReaderOptions::new() + let opts = ReaderOptions::default() .formats(formats) .try_harder(!cli.fast) .try_invert(!cli.fast) .try_rotate(!cli.fast); - let results = read_barcodes(&iv, &opts)?; + #[cfg(feature = "image")] + let results = read_barcodes(&image, &opts)?; + #[cfg(not(feature = "image"))] + let results = read_barcodes(iv, opts)?; if results.is_empty() { println!("No barcode found."); } else { for result in results { - println!("Text: {}", result.text()); - println!("Bytes: {:?}", result.bytes()); - println!("Format: {}", result.format()); - println!("Content: {}", result.content_type()); + println!("Text: {}", result.text()); + println!("Bytes: {:?}", result.bytes()); + println!("Format: {}", result.format()); + println!("Content: {}", result.content_type()); println!("Identifier: {}", result.symbology_identifier()); - println!("EC Level: {}", result.ec_level()); - println!("Error: {}", result.error_message()); - println!("Orientation: {}", result.orientation()); - println!("Position: {}", result.position()); + println!("EC Level: {}", result.ec_level()); + println!("Error: {}", result.error_message()); + println!("Rotation: {}", result.orientation()); + println!("Position: {}", result.position()); println!(); } } diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 6904d1f0d4..6584bbde6c 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -26,6 +26,7 @@ use std::fmt::{Display, Formatter}; use std::io::ErrorKind; use std::marker::PhantomData; use std::mem::transmute; +use std::rc::Rc; pub type Error = std::io::Error; @@ -107,17 +108,20 @@ impl Display for ContentType { } } -pub struct ImageView<'a>(*mut zxing_ImageView, PhantomData<&'a u8>); +#[derive(Debug, PartialEq)] +struct ImageViewOwner<'a>(*mut zxing_ImageView, PhantomData<&'a u8>); -impl Drop for ImageView<'_> { +impl Drop for ImageViewOwner<'_> { fn drop(&mut self) { unsafe { zxing_ImageView_delete(self.0) } } } +#[derive(Debug, Clone, PartialEq)] +pub struct ImageView<'a>(Rc>); -impl<'a> AsRef> for ImageView<'a> { - fn as_ref(&self) -> &ImageView<'a> { - self +impl<'a> From<&'a ImageView<'a>> for ImageView<'a> { + fn from(img: &'a ImageView) -> Self { + img.clone() } } @@ -143,7 +147,7 @@ impl<'a> ImageView<'a> { row_stride: U, pix_stride: U, ) -> Result { - let res = ImageView( + let res = ImageView(Rc::new(ImageViewOwner( zxing_ImageView_new( ptr, Self::try_into_int(width)?, @@ -153,7 +157,7 @@ impl<'a> ImageView<'a> { Self::try_into_int(pix_stride)?, ), PhantomData, - ); + ))); Ok(res) } @@ -171,12 +175,12 @@ impl<'a> ImageView<'a> { } pub fn cropped(self, left: i32, top: i32, width: i32, height: i32) -> Self { - unsafe { zxing_ImageView_crop(self.0, left, top, width, height) } + unsafe { zxing_ImageView_crop((self.0).0, left, top, width, height) } self } pub fn rotated(self, degree: i32) -> Self { - unsafe { zxing_ImageView_rotate(self.0, degree) } + unsafe { zxing_ImageView_rotate((self.0).0, degree) } self } } @@ -343,9 +347,12 @@ pub fn barcode_formats_from_string(str: impl AsRef) -> Result(image: impl AsRef>, opts: impl AsRef) -> Result, Error> { +pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef) -> Result, Error> { + let iv_: ImageView = image + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "Failed to image.try_into::()"))?; unsafe { - let results = zxing_ReadBarcodes(image.as_ref().0, opts.as_ref().0); + let results = zxing_ReadBarcodes((iv_.0).0, opts.as_ref().0); if !results.is_null() { let size = zxing_Results_size(results); let mut vec = Vec::::with_capacity(size as usize); From d636c6d8a054fe5d794e9ed57159a5230ad6ebf5 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 02:22:14 +0100 Subject: [PATCH 229/587] ReaderOptions: "un-deprecate" tryCode39ExtendedMode --- core/src/ReaderOptions.h | 6 +++--- core/src/oned/ODCode39Reader.cpp | 2 +- .../zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index b8c397c622..f7f570f438 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -80,7 +80,7 @@ class ReaderOptions _tryInvert(1), _tryDownscale(1), _isPure(0), - _tryCode39ExtendedMode(0), + _tryCode39ExtendedMode(1), _validateCode39CheckSum(0), _validateITFCheckSum(0), _returnCodabarStartEnd(0), @@ -141,8 +141,8 @@ class ReaderOptions /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) - /// Deprecated / does nothing. The Code39 was decoded from full ASCII iff symbologyIdentifier()[2] >= '4' - ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode, [[deprecated]]) + /// Enable the heuristic to detect and decode "full ASCII"/extended Code39 symbols + ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) /// Deprecated / does nothing. The Code39 symbol has a valid checksum iff symbologyIdentifier()[2] is an odd digit ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum, [[deprecated]]) diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index 99b4ecc4d1..eec62a85ea 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -122,7 +122,7 @@ Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique txt.push_back(lastChar); const char shiftChars[] = "$%/+"; - auto fullASCII = DecodeCode39AndCode93FullASCII(txt, shiftChars); + auto fullASCII = _opts.tryCode39ExtendedMode() ? DecodeCode39AndCode93FullASCII(txt, shiftChars) : ""; bool hasFullASCII = !fullASCII.empty() && std::find_first_of(txt.begin(), txt.end(), shiftChars, shiftChars + 4) != txt.end(); if (hasFullASCII) txt = fullASCII; diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index 051c9f5474..7c743fc312 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -75,8 +75,7 @@ public class BarcodeReader(public var options: Options = Options()) { var downscaleThreshold: Int = 500, var minLineCount: Int = 2, var maxNumberOfSymbols: Int = 0xff, - @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") - var tryCode39ExtendedMode: Boolean = false, + var tryCode39ExtendedMode: Boolean = true, @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateCode39CheckSum: Boolean = false, @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") From c32df728ff2cb04fd6bd8fdea6d06cc77fb11984 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 02:31:28 +0100 Subject: [PATCH 230/587] rust: rename the front end type `ReaderResult` into `Barcode` --- wrappers/rust/README.md | 9 ++++----- wrappers/rust/examples/demo.rs | 26 +++++++++++++------------- wrappers/rust/src/lib.rs | 14 +++++++------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index c97ee2d65e..a6661203bd 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -25,18 +25,17 @@ use zxing_cpp::{ImageView, ReaderOptions, BarcodeFormat, read_barcodes}; fn main() -> anyhow::Result<()> { let image = image::open("some-image-file.jpg")?; - let iv = ImageView::try_from(&image)?; let opts = ReaderOptions::default() .formats(BarcodeFormat::QRCode | BarcodeFormat::LinearCodes) .try_invert(false); - let results = read_barcodes(&iv, &opts)?; + let barcodes = read_barcodes(&image, &opts)?; - if results.is_empty() { + if barcodes.is_empty() { println!("No barcode found."); } else { - for result in results { - println!("{}: {}", result.format(), result.text()); + for barcode in barcodes { + println!("{}: {}", barcode.format(), barcode.text()); } } diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index b0c045861b..1a235c6cbf 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -32,23 +32,23 @@ fn main() -> anyhow::Result<()> { .try_rotate(!cli.fast); #[cfg(feature = "image")] - let results = read_barcodes(&image, &opts)?; + let barcodes = read_barcodes(&image, &opts)?; #[cfg(not(feature = "image"))] - let results = read_barcodes(iv, opts)?; + let barcodes = read_barcodes(iv, opts)?; - if results.is_empty() { + if barcodes.is_empty() { println!("No barcode found."); } else { - for result in results { - println!("Text: {}", result.text()); - println!("Bytes: {:?}", result.bytes()); - println!("Format: {}", result.format()); - println!("Content: {}", result.content_type()); - println!("Identifier: {}", result.symbology_identifier()); - println!("EC Level: {}", result.ec_level()); - println!("Error: {}", result.error_message()); - println!("Rotation: {}", result.orientation()); - println!("Position: {}", result.position()); + for barcode in barcodes { + println!("Text: {}", barcode.text()); + println!("Bytes: {:?}", barcode.bytes()); + println!("Format: {}", barcode.format()); + println!("Content: {}", barcode.content_type()); + println!("Identifier: {}", barcode.symbology_identifier()); + println!("EC Level: {}", barcode.ec_level()); + println!("Error: {}", barcode.error_message()); + println!("Rotation: {}", barcode.orientation()); + println!("Position: {}", barcode.position()); println!(); } } diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 6584bbde6c..b29a1072ae 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -272,9 +272,9 @@ impl ReaderOptions { property!(min_line_count, i32); } -pub struct ReaderResult(*mut zxing_Result); +pub struct Barcode(*mut zxing_Result); -impl Drop for ReaderResult { +impl Drop for Barcode { fn drop(&mut self) { unsafe { zxing_Result_delete(self.0) } } @@ -312,7 +312,7 @@ macro_rules! getter { }; } -impl ReaderResult { +impl Barcode { getter!(is_valid, isValid, transmute, bool); getter!(format, format, (|f| BarcodeFormats::new(f).unwrap().into_iter().last().unwrap()), BarcodeFormat); getter!(content_type, contentType, transmute, ContentType); @@ -347,7 +347,7 @@ pub fn barcode_formats_from_string(str: impl AsRef) -> Result(image: impl TryInto>, opts: impl AsRef) -> Result, Error> { +pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef) -> Result, Error> { let iv_: ImageView = image .try_into() .map_err(|_| Error::new(ErrorKind::InvalidInput, "Failed to image.try_into::()"))?; @@ -355,14 +355,14 @@ pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef::with_capacity(size as usize); + let mut vec = Vec::::with_capacity(size as usize); for i in 0..size { - vec.push(ReaderResult(zxing_Results_move(results, i))); + vec.push(Barcode(zxing_Results_move(results, i))); } zxing_Results_delete(results); Ok(vec) } else { - last_error_or!(Vec::::default()) + last_error_or!(Vec::::default()) } } } From 68c97c744f2fe1ead3677dd106020530d44d7180 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 09:33:45 +0100 Subject: [PATCH 231/587] ReaderOptions: `returnCodabarStartEnd` is deprecated and defaults to true --- core/src/ReaderOptions.h | 8 ++++---- test/samples/codabar-1/01.txt | 2 +- test/samples/codabar-1/02.txt | 2 +- test/samples/codabar-1/03.txt | 2 +- test/samples/codabar-1/04.txt | 2 +- test/samples/codabar-1/09.txt | 2 +- test/samples/codabar-1/10.txt | 2 +- test/samples/codabar-1/11.txt | 2 +- test/samples/codabar-1/12.txt | 2 +- test/samples/codabar-1/13.txt | 2 +- test/samples/codabar-1/14.txt | 2 +- test/samples/codabar-1/15.txt | 2 +- test/samples/codabar-2/01.txt | 2 +- test/samples/codabar-2/02.txt | 2 +- test/samples/codabar-2/03.txt | 2 +- test/unit/oned/ODCodaBarWriterTest.cpp | 2 +- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 2 +- .../zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt | 1 + 18 files changed, 21 insertions(+), 20 deletions(-) diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index f7f570f438..e74db5a33d 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -83,7 +83,7 @@ class ReaderOptions _tryCode39ExtendedMode(1), _validateCode39CheckSum(0), _validateITFCheckSum(0), - _returnCodabarStartEnd(0), + _returnCodabarStartEnd(1), _returnErrors(0), _downscaleFactor(3), _eanAddOnSymbol(EanAddOnSymbol::Ignore), @@ -97,7 +97,7 @@ class ReaderOptions {} #define ZX_PROPERTY(TYPE, GETTER, SETTER, ...) \ - __VA_ARGS__ TYPE GETTER() const noexcept { return _##GETTER; } \ + TYPE GETTER() const noexcept { return _##GETTER; } \ __VA_ARGS__ ReaderOptions& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ __VA_ARGS__ ReaderOptions&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } @@ -150,8 +150,8 @@ class ReaderOptions /// Deprecated / does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, [[deprecated]]) - /// If true, return the start and end chars in a Codabar barcode instead of stripping them. - ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd) + /// Deprecated / does nothing. Codabar start/stop characters are always returned. + ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd, [[deprecated]]) /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Result::error()) ZX_PROPERTY(bool, returnErrors, setReturnErrors) diff --git a/test/samples/codabar-1/01.txt b/test/samples/codabar-1/01.txt index 6a537b5b36..e0a295b4cd 100644 --- a/test/samples/codabar-1/01.txt +++ b/test/samples/codabar-1/01.txt @@ -1 +1 @@ -1234567890 \ No newline at end of file +A1234567890A \ No newline at end of file diff --git a/test/samples/codabar-1/02.txt b/test/samples/codabar-1/02.txt index 6a537b5b36..e0a295b4cd 100644 --- a/test/samples/codabar-1/02.txt +++ b/test/samples/codabar-1/02.txt @@ -1 +1 @@ -1234567890 \ No newline at end of file +A1234567890A \ No newline at end of file diff --git a/test/samples/codabar-1/03.txt b/test/samples/codabar-1/03.txt index b8ec6e4ecb..bd6d644978 100644 --- a/test/samples/codabar-1/03.txt +++ b/test/samples/codabar-1/03.txt @@ -1 +1 @@ -294/586 \ No newline at end of file +A294/586B \ No newline at end of file diff --git a/test/samples/codabar-1/04.txt b/test/samples/codabar-1/04.txt index cf537db50e..3cca605e35 100644 --- a/test/samples/codabar-1/04.txt +++ b/test/samples/codabar-1/04.txt @@ -1 +1 @@ -123455 \ No newline at end of file +A123455C \ No newline at end of file diff --git a/test/samples/codabar-1/09.txt b/test/samples/codabar-1/09.txt index bd41cba781..8b064ef3eb 100644 --- a/test/samples/codabar-1/09.txt +++ b/test/samples/codabar-1/09.txt @@ -1 +1 @@ -12345 \ No newline at end of file +A12345A \ No newline at end of file diff --git a/test/samples/codabar-1/10.txt b/test/samples/codabar-1/10.txt index 4632e068d5..01ceaa7705 100644 --- a/test/samples/codabar-1/10.txt +++ b/test/samples/codabar-1/10.txt @@ -1 +1 @@ -123456 \ No newline at end of file +A123456A \ No newline at end of file diff --git a/test/samples/codabar-1/11.txt b/test/samples/codabar-1/11.txt index 675525a78d..3b0f5365b5 100644 --- a/test/samples/codabar-1/11.txt +++ b/test/samples/codabar-1/11.txt @@ -1 +1 @@ -3419500 \ No newline at end of file +A3419500A \ No newline at end of file diff --git a/test/samples/codabar-1/12.txt b/test/samples/codabar-1/12.txt index 8782ad7969..7a517b46dd 100644 --- a/test/samples/codabar-1/12.txt +++ b/test/samples/codabar-1/12.txt @@ -1 +1 @@ -31117013206375 \ No newline at end of file +A31117013206375B \ No newline at end of file diff --git a/test/samples/codabar-1/13.txt b/test/samples/codabar-1/13.txt index bd41cba781..872b3c4b62 100644 --- a/test/samples/codabar-1/13.txt +++ b/test/samples/codabar-1/13.txt @@ -1 +1 @@ -12345 \ No newline at end of file +A12345B \ No newline at end of file diff --git a/test/samples/codabar-1/14.txt b/test/samples/codabar-1/14.txt index 8782ad7969..8e8259ef91 100644 --- a/test/samples/codabar-1/14.txt +++ b/test/samples/codabar-1/14.txt @@ -1 +1 @@ -31117013206375 \ No newline at end of file +A31117013206375A \ No newline at end of file diff --git a/test/samples/codabar-1/15.txt b/test/samples/codabar-1/15.txt index 4d38188ff3..745114b8d1 100644 --- a/test/samples/codabar-1/15.txt +++ b/test/samples/codabar-1/15.txt @@ -1 +1 @@ -123456789012 \ No newline at end of file +A123456789012A \ No newline at end of file diff --git a/test/samples/codabar-2/01.txt b/test/samples/codabar-2/01.txt index a9dbc2e2b4..df315db396 100644 --- a/test/samples/codabar-2/01.txt +++ b/test/samples/codabar-2/01.txt @@ -1 +1 @@ -80125178+ \ No newline at end of file +A80125178+B \ No newline at end of file diff --git a/test/samples/codabar-2/02.txt b/test/samples/codabar-2/02.txt index a9dbc2e2b4..df315db396 100644 --- a/test/samples/codabar-2/02.txt +++ b/test/samples/codabar-2/02.txt @@ -1 +1 @@ -80125178+ \ No newline at end of file +A80125178+B \ No newline at end of file diff --git a/test/samples/codabar-2/03.txt b/test/samples/codabar-2/03.txt index f19eb7e7bc..cb586b0492 100644 --- a/test/samples/codabar-2/03.txt +++ b/test/samples/codabar-2/03.txt @@ -1 +1 @@ -0944416895273 \ No newline at end of file +A0944416895273A \ No newline at end of file diff --git a/test/unit/oned/ODCodaBarWriterTest.cpp b/test/unit/oned/ODCodaBarWriterTest.cpp index c49ca3d880..35b1f87ece 100644 --- a/test/unit/oned/ODCodaBarWriterTest.cpp +++ b/test/unit/oned/ODCodaBarWriterTest.cpp @@ -50,7 +50,7 @@ TEST(ODCodaBarWriterTest, FullCircle) { std::string text = "A0123456789-$:/.+A"; auto matrix = CodabarWriter().encode(text, 0, 0); - auto opts = ReaderOptions().setReturnCodabarStartEnd(true); + auto opts = ReaderOptions(); Result res = OneD::DecodeSingleRow(CodabarReader(opts), matrix.row(0)); EXPECT_EQ(text, res.text()); diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 60222bdf05..a87db81e9c 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -310,7 +310,7 @@ static ReaderOptions CreateReaderOptions(JNIEnv* env, jobject opts) .setDownscaleFactor(GetIntField(env, cls, opts, "downscaleFactor")) .setMinLineCount(GetIntField(env, cls, opts, "minLineCount")) .setMaxNumberOfSymbols(GetIntField(env, cls, opts, "maxNumberOfSymbols")) - .setReturnCodabarStartEnd(GetBooleanField(env, cls, opts, "returnCodabarStartEnd")) + .setTryCode39ExtendedMode(GetBooleanField(env, cls, opts, "tryCode39ExtendedMode")) .setReturnErrors(GetBooleanField(env, cls, opts, "returnErrors")) .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, opts, "eanAddOnSymbol", "EanAddOnSymbol"))) .setTextMode(TextModeFromString(GetEnumField(env, cls, opts, "textMode", "TextMode"))) diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index 7c743fc312..748c00e834 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -80,6 +80,7 @@ public class BarcodeReader(public var options: Options = Options()) { var validateCode39CheckSum: Boolean = false, @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateITFCheckSum: Boolean = false, + @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var returnCodabarStartEnd: Boolean = false, var returnErrors: Boolean = false, var eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, From 2f3c72cccea22a60d41b31c185aba1ea5425d6bb Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 09:47:41 +0100 Subject: [PATCH 232/587] ReaderOptions: re-enable validateCode39CheckSum and validateITFCheckSum Those two properties are still deprecated but I realized that keeping the API for ABI compatibility reasons but removing the one line that actually does the work, is bad judgment with respect to the user base. Sorry, @gitlost ;). --- core/src/oned/ODCode39Reader.cpp | 4 +++- core/src/oned/ODITFReader.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index eec62a85ea..e102fee353 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -130,12 +130,14 @@ Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique if (hasValidCheckSum) txt.push_back(lastChar); + Error error = _opts.validateCode39CheckSum() && !hasValidCheckSum ? ChecksumError() : Error(); + // Symbology identifier modifiers ISO/IEC 16388:2007 Annex C Table C.1 constexpr const char symbologyModifiers[4] = { '0', '1' /*checksum*/, '4' /*full ASCII*/, '5' /*checksum + full ASCII*/ }; SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)hasValidCheckSum + 2 * (int)hasFullASCII]}; int xStop = next.pixelsTillEnd(); - return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier); + return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier, error); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODITFReader.cpp b/core/src/oned/ODITFReader.cpp index c9af66503a..b6d75aa7b4 100644 --- a/core/src/oned/ODITFReader.cpp +++ b/core/src/oned/ODITFReader.cpp @@ -65,12 +65,14 @@ Result ITFReader::decodePattern(int rowNumber, PatternView& next, std::unique_pt if (!IsRightGuard(next, STOP_PATTERN_1, minQuietZone) && !IsRightGuard(next, STOP_PATTERN_2, minQuietZone)) return {}; + Error error = _opts.validateITFCheckSum() && !GTIN::IsCheckDigitValid(txt) ? ChecksumError() : Error(); + // Symbology identifier ISO/IEC 16390:2007 Annex C Table C.1 // See also GS1 General Specifications 5.1.2 Figure 5.1.2-2 SymbologyIdentifier symbologyIdentifier = {'I', GTIN::IsCheckDigitValid(txt) ? '1' : '0'}; int xStop = next.pixelsTillEnd(); - return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::ITF, symbologyIdentifier); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::ITF, symbologyIdentifier, error); } } // namespace ZXing::OneD From 0ce62fcce7e1cb4f12a2eae322a861d3f01cc0c5 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 09:59:01 +0100 Subject: [PATCH 233/587] rust: bump version to 0.2.2 --- wrappers/rust/Cargo.toml | 6 +++--- wrappers/rust/README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index cf6fcfca3b..9e403551f6 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zxing-cpp" -version = "0.2.1" +version = "0.2.2" edition = "2021" # authors = ["Axel Waggershauser "] license = "Apache-2.0" @@ -22,13 +22,13 @@ bundled = [] [dependencies] paste = "1.0.14" flagset = "0.4.4" -image = {version = "0.24.7", optional = true} +image = {version = "0.24.8", optional = true} [dev-dependencies] cfg-if = "1.0.0" anyhow = "1.0.79" clap = {version = "4.4.13", features = ["derive"]} -image = {version = "0.24.7"} +image = {version = "0.24.8"} [build-dependencies] cmake = {version = "0.1.50"} diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index a6661203bd..3ef352c3c4 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -15,7 +15,7 @@ In your Cargo.toml: # `bundled` causes cargo to compile and statically link an up to # date version of the c++ core library. This is the most convenient # and safe way to build the library. -zxing-cpp = { version = "0.2.1", features = ["bundled", "image"] } +zxing-cpp = { version = "0.2.2", features = ["bundled", "image"] } ``` Simple example usage: From c366d39ad47ba42fa7c3502702ba1476bc90c320 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 15:26:36 +0100 Subject: [PATCH 234/587] c-API: rename `Result` -> `Barcode` For a detailed reasoning and discussion, see https://github.com/zxing-cpp/zxing-cpp/discussions/705 --- core/src/zxing-c.cpp | 46 +++++++++++++++---------------- core/src/zxing-c.h | 52 +++++++++++++++++------------------ wrappers/c/README.md | 16 +++++------ wrappers/c/zxing-c-test.c | 34 +++++++++++------------ wrappers/rust/src/bindings.rs | 48 ++++++++++++++++---------------- wrappers/rust/src/lib.rs | 16 +++++------ 6 files changed, 106 insertions(+), 106 deletions(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index 3756928d33..acd28ca75b 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -185,28 +185,28 @@ char* zxing_PositionToString(zxing_Position position) } -bool zxing_Result_isValid(const zxing_Result* result) +bool zxing_Barcode_isValid(const zxing_Barcode* barcode) { - return result != NULL && result->isValid(); + return barcode != NULL && barcode->isValid(); } -char* zxing_Result_errorMsg(const zxing_Result* result) +char* zxing_Barcode_errorMsg(const zxing_Barcode* barcode) { - return copy(ToString(result->error())); + return copy(ToString(barcode->error())); } -uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len) +uint8_t* zxing_Barcode_bytes(const zxing_Barcode* barcode, int* len) { - return copy(result->bytes(), len); + return copy(barcode->bytes(), len); } -uint8_t* zxing_Result_bytesECI(const zxing_Result* result, int* len) +uint8_t* zxing_Barcode_bytesECI(const zxing_Barcode* barcode, int* len) { - return copy(result->bytesECI(), len); + return copy(barcode->bytesECI(), len); } #define ZX_GETTER(TYPE, GETTER, TRANS) \ - TYPE zxing_Result_##GETTER(const zxing_Result* result) { return static_cast(TRANS(result->GETTER())); } + TYPE zxing_Barcode_##GETTER(const zxing_Barcode* barcode) { return static_cast(TRANS(barcode->GETTER())); } ZX_GETTER(zxing_BarcodeFormat, format,) ZX_GETTER(zxing_ContentType, contentType,) @@ -226,46 +226,46 @@ ZX_GETTER(int, lineCount,) * ZXing/ReadBarcode.h */ -zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { auto res = ReadBarcodesAndSetLastError(iv, opts, 1); return !res.empty() ? new Result(std::move(res.front())) : NULL; } -zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { auto res = ReadBarcodesAndSetLastError(iv, opts, 0); return !res.empty() ? new Results(std::move(res)) : NULL; } -void zxing_Result_delete(zxing_Result* result) +void zxing_Barcode_delete(zxing_Barcode* barcode) { - delete result; + delete barcode; } -void zxing_Results_delete(zxing_Results* results) +void zxing_Barcodes_delete(zxing_Barcodes* barcodes) { - delete results; + delete barcodes; } -int zxing_Results_size(const zxing_Results* results) +int zxing_Barcodes_size(const zxing_Barcodes* barcodes) { - return results ? Size(*results) : 0; + return barcodes ? Size(*barcodes) : 0; } -const zxing_Result* zxing_Results_at(const zxing_Results* results, int i) +const zxing_Barcode* zxing_Barcodes_at(const zxing_Barcodes* barcodes, int i) { - if (!results || i < 0 || i >= Size(*results)) + if (!barcodes || i < 0 || i >= Size(*barcodes)) return NULL; - return &(*results)[i]; + return &(*barcodes)[i]; } -zxing_Result* zxing_Results_move(zxing_Results* results, int i) +zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i) { - if (!results || i < 0 || i >= Size(*results)) + if (!barcodes || i < 0 || i >= Size(*barcodes)) return NULL; - return new Result(std::move((*results)[i])); + return new Result(std::move((*barcodes)[i])); } char* zxing_LastErrorMsg() diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index b214738827..93904b0979 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -18,8 +18,8 @@ typedef ZXing::ImageView zxing_ImageView; typedef ZXing::ReaderOptions zxing_ReaderOptions; -typedef ZXing::Result zxing_Result; -typedef ZXing::Results zxing_Results; +typedef ZXing::Result zxing_Barcode; +typedef ZXing::Results zxing_Barcodes; extern "C" { @@ -27,8 +27,8 @@ extern "C" typedef struct zxing_ImageView zxing_ImageView; typedef struct zxing_ReaderOptions zxing_ReaderOptions; -typedef struct zxing_Result zxing_Result; -typedef struct zxing_Results zxing_Results; +typedef struct zxing_Barcode zxing_Barcode; +typedef struct zxing_Barcodes zxing_Barcodes; #endif @@ -184,36 +184,36 @@ typedef struct zxing_Position char* zxing_PositionToString(zxing_Position position); -bool zxing_Result_isValid(const zxing_Result* result); -char* zxing_Result_errorMsg(const zxing_Result* result); -zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result); -zxing_ContentType zxing_Result_contentType(const zxing_Result* result); -uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len); -uint8_t* zxing_Result_bytesECI(const zxing_Result* result, int* len); -char* zxing_Result_text(const zxing_Result* result); -char* zxing_Result_ecLevel(const zxing_Result* result); -char* zxing_Result_symbologyIdentifier(const zxing_Result* result); -zxing_Position zxing_Result_position(const zxing_Result* result); -int zxing_Result_orientation(const zxing_Result* result); -bool zxing_Result_hasECI(const zxing_Result* result); -bool zxing_Result_isInverted(const zxing_Result* result); -bool zxing_Result_isMirrored(const zxing_Result* result); -int zxing_Result_lineCount(const zxing_Result* result); +bool zxing_Barcode_isValid(const zxing_Barcode* barcode); +char* zxing_Barcode_errorMsg(const zxing_Barcode* barcode); +zxing_BarcodeFormat zxing_Barcode_format(const zxing_Barcode* barcode); +zxing_ContentType zxing_Barcode_contentType(const zxing_Barcode* barcode); +uint8_t* zxing_Barcode_bytes(const zxing_Barcode* barcode, int* len); +uint8_t* zxing_Barcode_bytesECI(const zxing_Barcode* barcode, int* len); +char* zxing_Barcode_text(const zxing_Barcode* barcode); +char* zxing_Barcode_ecLevel(const zxing_Barcode* barcode); +char* zxing_Barcode_symbologyIdentifier(const zxing_Barcode* barcode); +zxing_Position zxing_Barcode_position(const zxing_Barcode* barcode); +int zxing_Barcode_orientation(const zxing_Barcode* barcode); +bool zxing_Barcode_hasECI(const zxing_Barcode* barcode); +bool zxing_Barcode_isInverted(const zxing_Barcode* barcode); +bool zxing_Barcode_isMirrored(const zxing_Barcode* barcode); +int zxing_Barcode_lineCount(const zxing_Barcode* barcode); /* * ZXing/ReadBarcode.h */ /** Note: opts is optional, i.e. it can be NULL, which will imply default settings. */ -zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); -zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); +zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); +zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); -void zxing_Result_delete(zxing_Result* result); -void zxing_Results_delete(zxing_Results* results); +void zxing_Barcode_delete(zxing_Barcode* barcode); +void zxing_Barcodes_delete(zxing_Barcodes* barcodes); -int zxing_Results_size(const zxing_Results* results); -const zxing_Result* zxing_Results_at(const zxing_Results* results, int i); -zxing_Result* zxing_Results_move(zxing_Results* results, int i); +int zxing_Barcodes_size(const zxing_Barcodes* barcodes); +const zxing_Barcode* zxing_Barcodes_at(const zxing_Barcodes* barcodes, int i); +zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i); char* zxing_LastErrorMsg(); diff --git a/wrappers/c/README.md b/wrappers/c/README.md index a9130186e5..0263df550d 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -26,18 +26,21 @@ int main(int argc, char** argv) zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); /* set ReaderOptions properties, if requried */ - zxing_Result* result = zxing_ReadBarcode(iv, opts); + zxing_Barcode* barcode = zxing_ReadBarcode(iv, opts); - if (result) { - const char* format = zxing_BarcodeFormatToString(zxing_Result_format(result)); + zxing_ImageView_delete(iv); + zxing_ReaderOptions_delete(opts); + + if (barcode) { + const char* format = zxing_BarcodeFormatToString(zxing_Barcode_format(barcode)); printf("Format : %s\n", format); zxing_free(format); - const char* text = zxing_Result_text(result); + const char* text = zxing_Barcode_text(barcode); printf("Text : %s\n", text); zxing_free(text); - zxing_Result_delete(result); + zxing_Barcode_delete(barcode); } else { const char* error = zxing_LastErrorMsg(); if (error) { @@ -48,9 +51,6 @@ int main(int argc, char** argv) zxing_free(error); } - zxing_ImageView_delete(iv); - zxing_ReaderOptions_delete(opts); - return 0; } ``` diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index 2b62511c18..63855b497b 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -70,26 +70,30 @@ int main(int argc, char** argv) zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); - zxing_Results* results = zxing_ReadBarcodes(iv, opts); + zxing_Barcodes* barcodes = zxing_ReadBarcodes(iv, opts); - if (results) { - for (int i = 0, n = zxing_Results_size(results); i < n; ++i) { - const zxing_Result* result = zxing_Results_at(results, i); + zxing_ImageView_delete(iv); + zxing_ReaderOptions_delete(opts); + stbi_image_free(data); + + if (barcodes) { + for (int i = 0, n = zxing_Barcodes_size(barcodes); i < n; ++i) { + const zxing_Barcode* barcode = zxing_Barcodes_at(barcodes, i); - printF("Text : %s\n", zxing_Result_text(result)); - printF("Format : %s\n", zxing_BarcodeFormatToString(zxing_Result_format(result))); - printF("Content : %s\n", zxing_ContentTypeToString(zxing_Result_contentType(result))); - printF("Identifier : %s\n", zxing_Result_symbologyIdentifier(result)); - printF("EC Level : %s\n", zxing_Result_ecLevel(result)); - printF("Error : %s\n", zxing_Result_errorMsg(result)); - printF("Position : %s\n", zxing_PositionToString(zxing_Result_position(result))); - printf("Rotation : %d\n", zxing_Result_orientation(result)); + printF("Text : %s\n", zxing_Barcode_text(barcode)); + printF("Format : %s\n", zxing_BarcodeFormatToString(zxing_Barcode_format(barcode))); + printF("Content : %s\n", zxing_ContentTypeToString(zxing_Barcode_contentType(barcode))); + printF("Identifier : %s\n", zxing_Barcode_symbologyIdentifier(barcode)); + printF("EC Level : %s\n", zxing_Barcode_ecLevel(barcode)); + printF("Error : %s\n", zxing_Barcode_errorMsg(barcode)); + printF("Position : %s\n", zxing_PositionToString(zxing_Barcode_position(barcode))); + printf("Rotation : %d\n", zxing_Barcode_orientation(barcode)); if (i < n-1) printf("\n"); } - zxing_Results_delete(results); + zxing_Barcodes_delete(barcodes); } else { const char* error = zxing_LastErrorMsg(); if (error) { @@ -100,9 +104,5 @@ int main(int argc, char** argv) } } - zxing_ImageView_delete(iv); - zxing_ReaderOptions_delete(opts); - stbi_image_free(data); - return ret; } diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 2fad1b4168..5b5b0c2e4a 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -12,12 +12,12 @@ pub struct zxing_ReaderOptions { } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_Result { +pub struct zxing_Barcode { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_Results { +pub struct zxing_Barcodes { _unused: [u8; 0], } pub const zxing_ImageFormat_None: zxing_ImageFormat = 0; @@ -140,28 +140,28 @@ extern "C" { pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; pub fn zxing_PositionToString(position: zxing_Position) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_isValid(result: *const zxing_Result) -> bool; - pub fn zxing_Result_errorMsg(result: *const zxing_Result) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_format(result: *const zxing_Result) -> zxing_BarcodeFormat; - pub fn zxing_Result_contentType(result: *const zxing_Result) -> zxing_ContentType; - pub fn zxing_Result_bytes(result: *const zxing_Result, len: *mut ::core::ffi::c_int) -> *mut u8; - pub fn zxing_Result_bytesECI(result: *const zxing_Result, len: *mut ::core::ffi::c_int) -> *mut u8; - pub fn zxing_Result_text(result: *const zxing_Result) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_ecLevel(result: *const zxing_Result) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_symbologyIdentifier(result: *const zxing_Result) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_position(result: *const zxing_Result) -> zxing_Position; - pub fn zxing_Result_orientation(result: *const zxing_Result) -> ::core::ffi::c_int; - pub fn zxing_Result_hasECI(result: *const zxing_Result) -> bool; - pub fn zxing_Result_isInverted(result: *const zxing_Result) -> bool; - pub fn zxing_Result_isMirrored(result: *const zxing_Result) -> bool; - pub fn zxing_Result_lineCount(result: *const zxing_Result) -> ::core::ffi::c_int; - pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Result; - pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Results; - pub fn zxing_Result_delete(result: *mut zxing_Result); - pub fn zxing_Results_delete(results: *mut zxing_Results); - pub fn zxing_Results_size(results: *const zxing_Results) -> ::core::ffi::c_int; - pub fn zxing_Results_at(results: *const zxing_Results, i: ::core::ffi::c_int) -> *const zxing_Result; - pub fn zxing_Results_move(results: *mut zxing_Results, i: ::core::ffi::c_int) -> *mut zxing_Result; + pub fn zxing_Barcode_isValid(barcode: *const zxing_Barcode) -> bool; + pub fn zxing_Barcode_errorMsg(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; + pub fn zxing_Barcode_format(barcode: *const zxing_Barcode) -> zxing_BarcodeFormat; + pub fn zxing_Barcode_contentType(barcode: *const zxing_Barcode) -> zxing_ContentType; + pub fn zxing_Barcode_bytes(barcode: *const zxing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn zxing_Barcode_bytesECI(barcode: *const zxing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn zxing_Barcode_text(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; + pub fn zxing_Barcode_ecLevel(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; + pub fn zxing_Barcode_symbologyIdentifier(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; + pub fn zxing_Barcode_position(barcode: *const zxing_Barcode) -> zxing_Position; + pub fn zxing_Barcode_orientation(barcode: *const zxing_Barcode) -> ::core::ffi::c_int; + pub fn zxing_Barcode_hasECI(barcode: *const zxing_Barcode) -> bool; + pub fn zxing_Barcode_isInverted(barcode: *const zxing_Barcode) -> bool; + pub fn zxing_Barcode_isMirrored(barcode: *const zxing_Barcode) -> bool; + pub fn zxing_Barcode_lineCount(barcode: *const zxing_Barcode) -> ::core::ffi::c_int; + pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Barcode; + pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Barcodes; + pub fn zxing_Barcode_delete(barcode: *mut zxing_Barcode); + pub fn zxing_Barcodes_delete(barcodes: *mut zxing_Barcodes); + pub fn zxing_Barcodes_size(barcodes: *const zxing_Barcodes) -> ::core::ffi::c_int; + pub fn zxing_Barcodes_at(barcodes: *const zxing_Barcodes, i: ::core::ffi::c_int) -> *const zxing_Barcode; + pub fn zxing_Barcodes_move(barcodes: *mut zxing_Barcodes, i: ::core::ffi::c_int) -> *mut zxing_Barcode; pub fn zxing_LastErrorMsg() -> *mut ::core::ffi::c_char; pub fn zxing_free(ptr: *mut ::core::ffi::c_void); } diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index b29a1072ae..b98d4bb28b 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -272,11 +272,11 @@ impl ReaderOptions { property!(min_line_count, i32); } -pub struct Barcode(*mut zxing_Result); +pub struct Barcode(*mut zxing_Barcode); impl Drop for Barcode { fn drop(&mut self) { - unsafe { zxing_Result_delete(self.0) } + unsafe { zxing_Barcode_delete(self.0) } } } @@ -307,7 +307,7 @@ impl Display for Position { macro_rules! getter { ($r_name:ident, $c_name:ident, $conv:expr, $type:ty) => { pub fn $r_name(&self) -> $type { - paste! { unsafe { $conv([](self.0)) } } + paste! { unsafe { $conv([](self.0)) } } } }; } @@ -329,11 +329,11 @@ impl Barcode { pub fn bytes(&self) -> Vec { let mut len: c_int = 0; - unsafe { c2r_vec(zxing_Result_bytes(self.0, &mut len), len) } + unsafe { c2r_vec(zxing_Barcode_bytes(self.0, &mut len), len) } } pub fn bytes_eci(&self) -> Vec { let mut len: c_int = 0; - unsafe { c2r_vec(zxing_Result_bytesECI(self.0, &mut len), len) } + unsafe { c2r_vec(zxing_Barcode_bytesECI(self.0, &mut len), len) } } } @@ -354,12 +354,12 @@ pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef::with_capacity(size as usize); for i in 0..size { - vec.push(Barcode(zxing_Results_move(results, i))); + vec.push(Barcode(zxing_Barcodes_move(results, i))); } - zxing_Results_delete(results); + zxing_Barcodes_delete(results); Ok(vec) } else { last_error_or!(Vec::::default()) From 04b8f83419871c96beafd75032f2b534394f39b6 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 15:55:07 +0100 Subject: [PATCH 235/587] dotnet: initial .NET wrapper implementation --- .editorconfig | 2 +- wrappers/dotnet/.gitignore | 56 +++ wrappers/dotnet/README.md | 64 ++++ wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 333 ++++++++++++++++++ wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 11 + wrappers/dotnet/ZXingCppDemo/Program.cs | 34 ++ .../dotnet/ZXingCppDemo/ZXingCppDemo.csproj | 15 + wrappers/dotnet/dotnet.sln | 28 ++ 8 files changed, 542 insertions(+), 1 deletion(-) create mode 100644 wrappers/dotnet/.gitignore create mode 100644 wrappers/dotnet/README.md create mode 100644 wrappers/dotnet/ZXingCpp/ZXingCpp.cs create mode 100644 wrappers/dotnet/ZXingCpp/ZXingCpp.csproj create mode 100644 wrappers/dotnet/ZXingCppDemo/Program.cs create mode 100644 wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj create mode 100644 wrappers/dotnet/dotnet.sln diff --git a/.editorconfig b/.editorconfig index 50d16bc2ce..4538c85cd7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,c,h,html,py,kt}] +[*.{cpp,c,h,html,py,kt,cs}] indent_style = tab indent_size = 4 max_line_length = 135 diff --git a/wrappers/dotnet/.gitignore b/wrappers/dotnet/.gitignore new file mode 100644 index 0000000000..75028b44d1 --- /dev/null +++ b/wrappers/dotnet/.gitignore @@ -0,0 +1,56 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md new file mode 100644 index 0000000000..5d33207de6 --- /dev/null +++ b/wrappers/dotnet/README.md @@ -0,0 +1,64 @@ +# ZXingCpp + +ZXingCpp is a .NET wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). + +It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. +It was originally ported from the Java ZXing Library but has been developed further and now includes +many improvements in terms of runtime and detection performance. + +## Usage + +There is going to be a NuGet package available that includes the native c++ dll for easy installation +at a later point. + +Simple example usage: + +```cs +using System.Collections.Generic; +using ImageMagick; +using ZXingCpp; + +public static class ImageMagickBarcodeReader +{ + public static List Read(MagickImage img, ReaderOptions? opts = null) + { + var bytes = img.ToByteArray(MagickFormat.Gray); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); +} + +public class Program +{ + public static void Main(string[] args) + { + var img = new MagickImage(args[0]); + + var reader = new BarcodeReader() { + Formats = BarcodeReader.FormatsFromString(args[1]), + TryInvert = false, + }; + + foreach (var b in reader.Read(img)) + Console.WriteLine($"{b.Format} : {b.Text}"); + } +} +``` + +To run the `ZXingCppDemo` sample program, it is important that the dotnet runtime finds the native +`ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this + +```sh +LD_LIBRARY_PATH= dotnet run --project ZXingCppDemo -- ../../test/samples/multi-1/1.png +``` + +Note: This should currently be considered a pre-release. The API may change slightly to be even more +"managed" depending on community feedback. + +## Benchmarking + +To compare the performance of this .NET wrapper project with other available barcode scanner .NET libraries, +I started the project [zxing-bench](https://github.com/axxel/zxing-bench). The README contains a few +results to get an idea. diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs new file mode 100644 index 0000000000..1a665d85f6 --- /dev/null +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -0,0 +1,333 @@ +namespace ZXingCpp { + +using System; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +using static Dll; +using BarcodeFormat = BarcodeFormats; + +internal class Dll +{ + private const string DllName = "ZXing"; + + [DllImport(DllName)] public static extern IntPtr zxing_ReaderOptions_new(); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_delete(IntPtr opts); + + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryHarder(IntPtr opts, bool tryHarder); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryHarder(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryRotate(IntPtr opts, bool tryRotate); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryRotate(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryInvert(IntPtr opts, bool tryInvert); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryInvert(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryDownscale(IntPtr opts, bool tryDownscale); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryDownscale(IntPtr opts); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getIsPure(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setIsPure(IntPtr opts, bool isPure); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setReturnErrors(IntPtr opts, bool returnErrors); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getReturnErrors(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setFormats(IntPtr opts, BarcodeFormats formats); + [DllImport(DllName)] public static extern BarcodeFormats zxing_ReaderOptions_getFormats(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setBinarizer(IntPtr opts, Binarizer binarizer); + [DllImport(DllName)] public static extern Binarizer zxing_ReaderOptions_getBinarizer(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setEanAddOnSymbol(IntPtr opts, EanAddOnSymbol eanAddOnSymbol); + [DllImport(DllName)] public static extern EanAddOnSymbol zxing_ReaderOptions_getEanAddOnSymbol(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTextMode(IntPtr opts, TextMode textMode); + [DllImport(DllName)] public static extern TextMode zxing_ReaderOptions_getTextMode(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setMinLineCount(IntPtr opts, int n); + [DllImport(DllName)] public static extern int zxing_ReaderOptions_getMinLineCount(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setMaxNumberOfSymbols(IntPtr opts, int n); + [DllImport(DllName)] public static extern int zxing_ReaderOptions_getMaxNumberOfSymbols(IntPtr opts); + + [DllImport(DllName)] public static extern IntPtr zxing_PositionToString(Position position); + [DllImport(DllName)] public static extern BarcodeFormats zxing_BarcodeFormatsFromString(string str); + + [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new(byte[] data, int width, int height, ImageFormat format, int rowStride, int pixStride); + [DllImport(DllName)] public static extern void zxing_ImageView_delete(IntPtr iv); + + [DllImport(DllName)] public static extern IntPtr zxing_ReadBarcodes(IntPtr iv, IntPtr opts); + [DllImport(DllName)] public static extern void zxing_Barcode_delete(IntPtr result); + [DllImport(DllName)] public static extern void zxing_Barcodes_delete(IntPtr results); + [DllImport(DllName)] public static extern int zxing_Barcodes_size(IntPtr results); + [DllImport(DllName)] public static extern IntPtr zxing_Barcodes_move(IntPtr results, int i); + + [DllImport(DllName)] public static extern bool zxing_Barcode_isValid(IntPtr result); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_errorMsg(IntPtr result); + [DllImport(DllName)] public static extern BarcodeFormat zxing_Barcode_format(IntPtr result); + [DllImport(DllName)] public static extern ContentType zxing_Barcode_contentType(IntPtr result); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_bytes(IntPtr result, out int len); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_bytesECI(IntPtr result, out int len); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_text(IntPtr result); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_ecLevel(IntPtr result); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_symbologyIdentifier(IntPtr result); + [DllImport(DllName)] public static extern Position zxing_Barcode_position(IntPtr result); + [DllImport(DllName)] public static extern int zxing_Barcode_orientation(IntPtr result); + [DllImport(DllName)] public static extern bool zxing_Barcode_hasECI(IntPtr result); + [DllImport(DllName)] public static extern bool zxing_Barcode_isInverted(IntPtr result); + [DllImport(DllName)] public static extern bool zxing_Barcode_isMirrored(IntPtr result); + [DllImport(DllName)] public static extern int zxing_Barcode_lineCount(IntPtr result); + + [DllImport(DllName)] public static extern void zxing_free(IntPtr opts); + [DllImport(DllName)] public static extern IntPtr zxing_LastErrorMsg(); + + + public static string MarshalAsString(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + throw new Exception("ZXing C-API returned a NULL char*."); + + string res = Marshal.PtrToStringUTF8(ptr) ?? ""; + zxing_free(ptr); + return res; + } + + public delegate IntPtr RetBytesFunc(IntPtr ptr, out int len); + + public static byte[] MarshalAsBytes(RetBytesFunc func, IntPtr d) + { + IntPtr ptr = func(d, out int len); + if (ptr == IntPtr.Zero) + throw new Exception("ZXing C-API returned a NULL byte*."); + + byte[] res = new byte[len]; + Marshal.Copy(ptr, res, 0, len); + zxing_free(ptr); + return res; + } +} + +[Flags] +public enum BarcodeFormats +{ + None = 0, ///< Used as a return value if no valid barcode has been detected + Aztec = (1 << 0), ///< Aztec + Codabar = (1 << 1), ///< Codabar + Code39 = (1 << 2), ///< Code39 + Code93 = (1 << 3), ///< Code93 + Code128 = (1 << 4), ///< Code128 + DataBar = (1 << 5), ///< GS1 DataBar, formerly known as RSS 14 + DataBarExpanded = (1 << 6), ///< GS1 DataBar Expanded, formerly known as RSS EXPANDED + DataMatrix = (1 << 7), ///< DataMatrix + EAN8 = (1 << 8), ///< EAN-8 + EAN13 = (1 << 9), ///< EAN-13 + ITF = (1 << 10), ///< ITF (Interleaved Two of Five) + MaxiCode = (1 << 11), ///< MaxiCode + PDF417 = (1 << 12), ///< PDF417 + QRCode = (1 << 13), ///< QR Code + UPCA = (1 << 14), ///< UPC-A + UPCE = (1 << 15), ///< UPC-E + MicroQRCode = (1 << 16), ///< Micro QR Code + RMQRCode = (1 << 17), ///< Rectangular Micro QR Code + DXFilmEdge = (1 << 18), ///< DX Film Edge Barcode + + LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DXFilmEdge | UPCA | UPCE, + MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, + Any = LinearCodes | MatrixCodes, + + Invalid = -1, +}; + + +public enum Binarizer +{ + LocalAverage, ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer) + GlobalHistogram, ///< T = valley between the 2 largest peaks in the histogram (per line in linear case) + FixedThreshold, ///< T = 127 + BoolCast, ///< T = 0, fastest possible +}; + +public enum EanAddOnSymbol +{ + Ignore, ///< Ignore any Add-On symbol during read/scan + Read, ///< Read EAN-2/EAN-5 Add-On symbol if found + Require, ///< Require EAN-2/EAN-5 Add-On symbol to be present +}; + +public enum TextMode +{ + Plain, ///< bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0) + ECI, ///< standard content following the ECI protocol with every character set ECI segment transcoded to unicode + HRI, ///< Human Readable Interpretation (dependent on the ContentType) + Hex, ///< bytes() transcoded to ASCII string of HEX values + Escaped, ///< Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to "") +}; + +public enum ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; + +public enum ImageFormat { + None = 0, + Lum = 0x01000000, + RGB = 0x03000102, + BGR = 0x03020100, + RGBX = 0x04000102, + XRGB = 0x04010203, + BGRX = 0x04020100, + XBGR = 0x04030201, +}; + +public struct PointI +{ + public int x, y; +}; + +public struct Position +{ + public PointI topLeft, topRight, bottomRight, bottomLeft; + + public override string ToString() => Dll.MarshalAsString(Dll.zxing_PositionToString(this)); +}; + +public class ImageView +{ + internal IntPtr _d; + + public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride, int pixStride) + { + _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); + if (_d == IntPtr.Zero) + throw new Exception("Failed to create ImageView."); + } + + ~ImageView() => zxing_ImageView_delete(_d); +} + +public class ReaderOptions +{ + internal IntPtr _d; + + public ReaderOptions() + { + _d = zxing_ReaderOptions_new(); + if (_d == IntPtr.Zero) + throw new Exception("Failed to create ReaderOptions."); + } + + ~ReaderOptions() => zxing_ReaderOptions_delete(_d); + + public bool TryHarder + { + get => zxing_ReaderOptions_getTryHarder(_d); + set => zxing_ReaderOptions_setTryHarder(_d, value); + } + + public bool TryRotate + { + get => zxing_ReaderOptions_getTryRotate(_d); + set => zxing_ReaderOptions_setTryRotate(_d, value); + } + + public bool TryInvert + { + get => zxing_ReaderOptions_getTryInvert(_d); + set => zxing_ReaderOptions_setTryInvert(_d, value); + } + + public bool TryDownscale + { + get => zxing_ReaderOptions_getTryDownscale(_d); + set => zxing_ReaderOptions_setTryDownscale(_d, value); + } + + public bool IsPure + { + get => zxing_ReaderOptions_getIsPure(_d); + set => zxing_ReaderOptions_setIsPure(_d, value); + } + + public bool ReturnErrors + { + get => zxing_ReaderOptions_getReturnErrors(_d); + set => zxing_ReaderOptions_setReturnErrors(_d, value); + } + + public BarcodeFormats Formats + { + get => zxing_ReaderOptions_getFormats(_d); + set => zxing_ReaderOptions_setFormats(_d, value); + } + + public Binarizer Binarizer + { + get => zxing_ReaderOptions_getBinarizer(_d); + set => zxing_ReaderOptions_setBinarizer(_d, value); + } + + public EanAddOnSymbol EanAddOnSymbol + { + get => zxing_ReaderOptions_getEanAddOnSymbol(_d); + set => zxing_ReaderOptions_setEanAddOnSymbol(_d, value); + } + + public TextMode TextMode + { + get => zxing_ReaderOptions_getTextMode(_d); + set => zxing_ReaderOptions_setTextMode(_d, value); + } + + public int MinLineCount + { + get => zxing_ReaderOptions_getMinLineCount(_d); + set => zxing_ReaderOptions_setMinLineCount(_d, value); + } + + public int MaxNumberOfSymbols + { + get => zxing_ReaderOptions_getMaxNumberOfSymbols(_d); + set => zxing_ReaderOptions_setMaxNumberOfSymbols(_d, value); + } + +} + +public class Barcode +{ + internal IntPtr _d; + + internal Barcode(IntPtr d) => _d = d; + ~Barcode() => zxing_Barcode_delete(_d); + + public bool IsValid => zxing_Barcode_isValid(_d); + public BarcodeFormat Format => zxing_Barcode_format(_d); + public ContentType ContentType => zxing_Barcode_contentType(_d); + public string Text => MarshalAsString(zxing_Barcode_text(_d)); + public byte[] Bytes => MarshalAsBytes(zxing_Barcode_bytes, _d); + public byte[] BytesECI => MarshalAsBytes(zxing_Barcode_bytesECI, _d); + public string ECLevel => MarshalAsString(zxing_Barcode_ecLevel(_d)); + public string SymbologyIdentifier => MarshalAsString(zxing_Barcode_symbologyIdentifier(_d)); + public string ErrorMsg => MarshalAsString(zxing_Barcode_errorMsg(_d)); + public Position Position => zxing_Barcode_position(_d); + public int Orientation => zxing_Barcode_orientation(_d); + public bool HasECI => zxing_Barcode_hasECI(_d); + public bool IsInverted => zxing_Barcode_isInverted(_d); + public bool IsMirrored => zxing_Barcode_isMirrored(_d); + public int LineCount => zxing_Barcode_lineCount(_d); +} + +public class BarcodeReader : ReaderOptions +{ + public static BarcodeFormats FormatsFromString(string str) + { + var fmts = zxing_BarcodeFormatsFromString(str); + if (fmts == BarcodeFormats.Invalid) + throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + return fmts; + } + + public static List Read(ImageView iv, ReaderOptions? opts = null) + { + var ptr = zxing_ReadBarcodes(iv._d, opts?._d ?? IntPtr.Zero); + if (ptr == IntPtr.Zero) + throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + + var size = zxing_Barcodes_size(ptr); + var res = new List(size); + for (int i = 0; i < size; ++i) + res.Add(new Barcode(zxing_Barcodes_move(ptr, i))); + zxing_Barcodes_delete(ptr); + + return res; + } + + public List Read(ImageView iv) => Read(iv, this); +} + +} \ No newline at end of file diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj new file mode 100644 index 0000000000..93c3508db5 --- /dev/null +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.1 + + enable + + + + + diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs new file mode 100644 index 0000000000..6cd8c00cef --- /dev/null +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using ImageMagick; +using ZXingCpp; + +public static class ImageMagickBarcodeReader +{ + public static List Read(MagickImage img, ReaderOptions? opts = null) + { + var bytes = img.ToByteArray(MagickFormat.Gray); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); +} + +public class Program +{ + public static void Main(string[] args) + { + var img = new MagickImage(args[0]); + Console.WriteLine(img); + + var reader = new BarcodeReader() { + TryInvert = false, + }; + + if (args.Length >= 2) + reader.Formats = BarcodeReader.FormatsFromString(args[1]); + + foreach (var b in reader.Read(img)) + Console.WriteLine($"{b.Format} ({b.ContentType}): {b.Text} / [{string.Join(", ", b.Bytes)}]"); + } +} diff --git a/wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj b/wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj new file mode 100644 index 0000000000..28f5299710 --- /dev/null +++ b/wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj @@ -0,0 +1,15 @@ + + + + + + + + + Exe + net8.0 + enable + enable + + + diff --git a/wrappers/dotnet/dotnet.sln b/wrappers/dotnet/dotnet.sln new file mode 100644 index 0000000000..bc94b4088f --- /dev/null +++ b/wrappers/dotnet/dotnet.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp", "ZXingCpp\ZXingCpp.csproj", "{64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCppDemo", "ZXingCppDemo\ZXingCppDemo.csproj", "{05C626FE-A5CE-4263-9419-35E44F14DB93}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}.Release|Any CPU.Build.0 = Release|Any CPU + {05C626FE-A5CE-4263-9419-35E44F14DB93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05C626FE-A5CE-4263-9419-35E44F14DB93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05C626FE-A5CE-4263-9419-35E44F14DB93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05C626FE-A5CE-4263-9419-35E44F14DB93}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 9fffdeee7b3db2a2559758891eed2a2f5ac76c51 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 18:51:04 +0100 Subject: [PATCH 236/587] dotnet: add metatdata for NuGet package publishing See https://www.nuget.org/packages/ZXingCpp --- wrappers/dotnet/README.md | 44 ++++++++++++------------ wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 20 ++++++++++- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index 5d33207de6..a185c2054e 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -8,8 +8,8 @@ many improvements in terms of runtime and detection performance. ## Usage -There is going to be a NuGet package available that includes the native c++ dll for easy installation -at a later point. +There is a NuGet package available: https://www.nuget.org/packages/ZXingCpp. It does currently not yet +contain the native binary dll file. That needs to be copied/build separately at the moment. Simple example usage: @@ -20,30 +20,30 @@ using ZXingCpp; public static class ImageMagickBarcodeReader { - public static List Read(MagickImage img, ReaderOptions? opts = null) - { - var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); - return BarcodeReader.Read(iv, opts); - } - - public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); + public static List Read(MagickImage img, ReaderOptions? opts = null) + { + var bytes = img.ToByteArray(MagickFormat.Gray); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); } public class Program { - public static void Main(string[] args) - { - var img = new MagickImage(args[0]); - - var reader = new BarcodeReader() { - Formats = BarcodeReader.FormatsFromString(args[1]), - TryInvert = false, - }; - - foreach (var b in reader.Read(img)) - Console.WriteLine($"{b.Format} : {b.Text}"); - } + public static void Main(string[] args) + { + var img = new MagickImage(args[0]); + + var reader = new BarcodeReader() { + Formats = BarcodeReader.FormatsFromString(args[1]), + TryInvert = false, + }; + + foreach (var b in reader.Read(img)) + Console.WriteLine($"{b.Format} : {b.Text}"); + } } ``` diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj index 93c3508db5..c106284325 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -4,8 +4,26 @@ netstandard2.1 enable + + ZXingCpp + 0.1.0-alpha + Axel Waggershauser + zxing-cpp + + ZXingCpp is a .NET wrapper for the C++ library zxing-cpp. + It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. + It was originally ported from the Java ZXing Library but has been developed further and now includes + many improvements in terms of runtime and detection performance. + https://github.com/zxing-cpp/zxing-cpp + README.md + Barcode;QRCode + Apache-2.0 + + + - + + From 9e3ec7b9e3b3aae5ba29acc3138eebab1dfc525b Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 18:51:53 +0100 Subject: [PATCH 237/587] dotnet: add license header comments --- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 7 ++++++- wrappers/dotnet/ZXingCppDemo/Program.cs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 1a665d85f6..08c89daa56 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -1,4 +1,9 @@ -namespace ZXingCpp { +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +namespace ZXingCpp { using System; using System.Runtime.InteropServices; diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs index 6cd8c00cef..cf8aec38bf 100644 --- a/wrappers/dotnet/ZXingCppDemo/Program.cs +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -1,4 +1,9 @@ -using System.Collections.Generic; +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; using ImageMagick; using ZXingCpp; From 32b3b4c47d9a587334409e0b9548771d7249c760 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 23:35:04 +0100 Subject: [PATCH 238/587] c-API: zxing_ReadBarcodes returns empty list instead of NULL This actually fixes a bug in the .NET wrapper that I made because I must have thought NULL means there was error otherwise I get a list that might be empty. That was not the case but now it is. --- core/src/zxing-c.cpp | 14 ++++++++------ wrappers/c/README.md | 29 ++++++++++++++--------------- wrappers/c/zxing-c-test.c | 14 +++++++------- wrappers/rust/src/lib.rs | 1 + 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index acd28ca75b..d5e4c059a3 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include using namespace ZXing; @@ -47,14 +48,15 @@ static uint8_t* copy(const ByteArray& ba, int* len) return ret; } -static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts, int maxSymbols) +static std::tuple ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts, + int maxSymbols) { try { if (iv) { auto o = opts ? *opts : ReaderOptions{}; if (maxSymbols) o.setMaxNumberOfSymbols(maxSymbols); - return ReadBarcodes(*iv, o); + return {ReadBarcodes(*iv, o), true}; } else lastErrorMsg = "ImageView param is NULL"; } catch (std::exception& e) { @@ -63,7 +65,7 @@ static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxin lastErrorMsg = "Unknown error"; } - return {}; + return {Results{}, false}; } extern "C" { @@ -228,14 +230,14 @@ ZX_GETTER(int, lineCount,) zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodesAndSetLastError(iv, opts, 1); + auto [res, ok] = ReadBarcodesAndSetLastError(iv, opts, 1); return !res.empty() ? new Result(std::move(res.front())) : NULL; } zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodesAndSetLastError(iv, opts, 0); - return !res.empty() ? new Results(std::move(res)) : NULL; + auto [res, ok] = ReadBarcodesAndSetLastError(iv, opts, 0); + return !res.empty() || ok ? new Results(std::move(res)) : NULL; } void zxing_Barcode_delete(zxing_Barcode* barcode) diff --git a/wrappers/c/README.md b/wrappers/c/README.md index 0263df550d..62b920ae97 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -26,28 +26,27 @@ int main(int argc, char** argv) zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); /* set ReaderOptions properties, if requried */ - zxing_Barcode* barcode = zxing_ReadBarcode(iv, opts); + zxing_Barcodes* barcodes = zxing_ReadBarcodes(iv, opts); zxing_ImageView_delete(iv); zxing_ReaderOptions_delete(opts); - if (barcode) { - const char* format = zxing_BarcodeFormatToString(zxing_Barcode_format(barcode)); - printf("Format : %s\n", format); - zxing_free(format); + if (barcodes) { + for (int i = 0, n = zxing_Barcodes_size(barcodes); i < n; ++i) { + const zxing_Barcode* barcode = zxing_Barcodes_at(barcodes, i); - const char* text = zxing_Barcode_text(barcode); - printf("Text : %s\n", text); - zxing_free(text); + char* format = zxing_BarcodeFormatToString(zxing_Barcode_format(barcode)); + printf("Format : %s\n", format); + zxing_free(format); - zxing_Barcode_delete(barcode); - } else { - const char* error = zxing_LastErrorMsg(); - if (error) { - printf("%s\n", error); - } else { - printf("No barcode found\n"); + char* text = zxing_Barcode_text(barcode); + printf("Text : %s\n", text); + zxing_free(text); } + zxing_Barcodes_delete(barcodes); + } else { + char* error = zxing_LastErrorMsg(); + fprintf(stderr, "%s\n", error); zxing_free(error); } diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index 63855b497b..e5bef602c0 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -93,15 +93,15 @@ int main(int argc, char** argv) printf("\n"); } + if (zxing_Barcodes_size(barcodes) == 0) + printf("No barcode found\n"); + zxing_Barcodes_delete(barcodes); } else { - const char* error = zxing_LastErrorMsg(); - if (error) { - fprintf(stderr, "%s\n", error); - ret = 2; - } else { - printf("No barcode found\n"); - } + char* error = zxing_LastErrorMsg(); + fprintf(stderr, "%s\n", error); + zxing_free(error); + ret = 2; } return ret; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index b98d4bb28b..c40a05b82b 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -362,6 +362,7 @@ pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef::default()) } } From 9a27c69035820de43980f355893d3f1b17178a9d Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 23:35:19 +0100 Subject: [PATCH 239/587] dotnet: code cosmetic --- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 08c89daa56..e9b60485ae 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -179,7 +179,7 @@ public struct Position { public PointI topLeft, topRight, bottomRight, bottomLeft; - public override string ToString() => Dll.MarshalAsString(Dll.zxing_PositionToString(this)); + public override string ToString() => MarshalAsString(zxing_PositionToString(this)); }; public class ImageView From eba82a8ffadeb9beb0dbcbbb55fbe40a7cc33ab2 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 03:23:15 +0100 Subject: [PATCH 240/587] dotnet: fix issue with loading binary png files via ImageMagick --- wrappers/dotnet/README.md | 2 ++ wrappers/dotnet/ZXingCppDemo/Program.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index a185c2054e..889f1772b9 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -22,6 +22,8 @@ public static class ImageMagickBarcodeReader { public static List Read(MagickImage img, ReaderOptions? opts = null) { + if (img.DetermineBitDepth() < 8) + img.SetBitDepth(8); var bytes = img.ToByteArray(MagickFormat.Gray); var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); return BarcodeReader.Read(iv, opts); diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs index cf8aec38bf..2da0631ffb 100644 --- a/wrappers/dotnet/ZXingCppDemo/Program.cs +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -11,6 +11,8 @@ public static class ImageMagickBarcodeReader { public static List Read(MagickImage img, ReaderOptions? opts = null) { + if (img.DetermineBitDepth() < 8) + img.SetBitDepth(8); var bytes = img.ToByteArray(MagickFormat.Gray); var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); return BarcodeReader.Read(iv, opts); From ea25c3d54274c48fba2e5a4efab53cac180f07b8 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 08:26:31 +0100 Subject: [PATCH 241/587] README: add link to new .NET wrapper --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3417999786..774752014f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Thanks a lot for your contribution! * [Android](wrappers/android/README.md) * [C](wrappers/c/README.md) * [iOS](wrappers/ios/README.md) + * [.NET](wrappers/dotnet/README.md) * [Python](wrappers/python/README.md) * [Rust](wrappers/rust/README.md) * [WebAssembly](wrappers/wasm/README.md) From 6861b8229bbfd1088496fcd3edf0222039d4c452 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 10:50:28 +0100 Subject: [PATCH 242/587] dotnet: default `rowStride` and `pixStride` parameters for `ImageView` --- wrappers/dotnet/README.md | 2 +- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 2 +- wrappers/dotnet/ZXingCppDemo/Program.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index 889f1772b9..e4f3693833 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -25,7 +25,7 @@ public static class ImageMagickBarcodeReader if (img.DetermineBitDepth() < 8) img.SetBitDepth(8); var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); return BarcodeReader.Read(iv, opts); } diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index e9b60485ae..697b94de47 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -186,7 +186,7 @@ public class ImageView { internal IntPtr _d; - public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride, int pixStride) + public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) { _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); if (_d == IntPtr.Zero) diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs index 2da0631ffb..9b978b0d76 100644 --- a/wrappers/dotnet/ZXingCppDemo/Program.cs +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -14,7 +14,7 @@ public static List Read(MagickImage img, ReaderOptions? opts = null) if (img.DetermineBitDepth() < 8) img.SetBitDepth(8); var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); return BarcodeReader.Read(iv, opts); } From 38f701e07b66c378e02aa581335817f61c624148 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 10:51:14 +0100 Subject: [PATCH 243/587] dotnet: remove publicly visible BarcodeFormats.Invalid enum value --- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 697b94de47..d127775b73 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -128,8 +128,6 @@ public enum BarcodeFormats LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DXFilmEdge | UPCA | UPCE, MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, Any = LinearCodes | MatrixCodes, - - Invalid = -1, }; @@ -312,7 +310,7 @@ public class BarcodeReader : ReaderOptions public static BarcodeFormats FormatsFromString(string str) { var fmts = zxing_BarcodeFormatsFromString(str); - if (fmts == BarcodeFormats.Invalid) + if ((int)fmts == -1) // see zxing_BarcodeFormat_Invalid throw new Exception(MarshalAsString(zxing_LastErrorMsg())); return fmts; } From aec1dc37572770988689f5fdbe8b1e5155b6eb15 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 12:14:21 +0100 Subject: [PATCH 244/587] ImageView: introduce bounds checks in constructor * throw on ImageView(nullptr, ...) * new default constructor * new bounds checked constructor: ImageView(data, size, width, ...) * add zxing_ImageView_new_checked to c-API * use new_checked version in Rust and .NET wrappers --- core/src/ImageView.h | 32 +++++++++++++- core/src/ReadBarcode.cpp | 4 +- core/src/zxing-c.cpp | 19 ++++++++- core/src/zxing-c.h | 2 + test/blackbox/ImageLoader.cpp | 2 +- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 6 +-- wrappers/rust/src/bindings.rs | 9 ++++ wrappers/rust/src/lib.rs | 64 +++++++++++++++++----------- 8 files changed, 104 insertions(+), 34 deletions(-) diff --git a/core/src/ImageView.h b/core/src/ImageView.h index cf9bd6d3f8..a7b6f74add 100644 --- a/core/src/ImageView.h +++ b/core/src/ImageView.h @@ -7,6 +7,8 @@ #include #include +#include +#include namespace ZXing { @@ -42,10 +44,14 @@ class ImageView { protected: const uint8_t* _data = nullptr; - ImageFormat _format; + ImageFormat _format = ImageFormat::None; int _width = 0, _height = 0, _pixStride = 0, _rowStride = 0; public: + /** ImageView default constructor creates a 'null' image view + */ + ImageView() = default; + /** * ImageView constructor * @@ -63,7 +69,29 @@ class ImageView _height(height), _pixStride(pixStride ? pixStride : PixStride(format)), _rowStride(rowStride ? rowStride : width * _pixStride) - {} + { + // TODO: [[deprecated]] this check is to prevent exising code from suddenly throwing, remove in 3.0 + if (_data == nullptr && _width == 0 && _height == 0 && rowStride == 0 && pixStride == 0) { + fprintf(stderr, "zxing-cpp deprecation warning: ImageView(nullptr, ...) will throw in the future, use ImageView()\n"); + return; + } + + if (_data == nullptr) + throw std::invalid_argument("Can not construct an ImageView from a NULL pointer"); + + if (_width <= 0 || _height <= 0) + throw std::invalid_argument("Neither width nor height of ImageView can be less or equal to 0"); + } + + /** + * ImageView constructor with bounds checking + */ + ImageView(const uint8_t* data, int size, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) + : ImageView(data, width, height, format, rowStride, pixStride) + { + if (_rowStride < 0 || _pixStride < 0 || size < _height * _rowStride) + throw std::invalid_argument("ImageView parameters are inconsistent (out of bounds)"); + } int width() const { return _width; } int height() const { return _height; } diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 4a166bb9c0..5c371adf1e 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -26,7 +26,7 @@ class LumImage : public ImageView {} public: - LumImage() : ImageView(nullptr, 0, 0, ImageFormat::Lum) {} + LumImage() = default; LumImage(int w, int h) : LumImage(std::make_unique(w * h), w, h) {} uint8_t* data() { return _memory.get(); } @@ -135,7 +135,7 @@ Result ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) { - if (sizeof(PatternType) < 4 && opts.hasFormat(BarcodeFormat::LinearCodes) && (_iv.width() > 0xffff || _iv.height() > 0xffff)) + if (sizeof(PatternType) < 4 && (_iv.width() > 0xffff || _iv.height() > 0xffff)) throw std::invalid_argument("Maximum image width/height is 65535"); if (!_iv.data(0, 0) || _iv.width() * _iv.height() == 0) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index d5e4c059a3..f141130b6d 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -77,7 +77,24 @@ zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, int pixStride) { ImageFormat cppformat = static_cast(format); - return new ImageView(data, width, height, cppformat, rowStride, pixStride); + try { + return new ImageView(data, width, height, cppformat, rowStride, pixStride); + } catch (std::exception& e) { + lastErrorMsg = e.what(); + } + return NULL; +} + +zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, zxing_ImageFormat format, + int rowStride, int pixStride) +{ + ImageFormat cppformat = static_cast(format); + try { + return new ImageView(data, size, width, height, cppformat, rowStride, pixStride); + } catch (std::exception& e) { + lastErrorMsg = e.what(); + } + return NULL; } void zxing_ImageView_delete(zxing_ImageView* iv) diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index 93904b0979..75207aa679 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -49,6 +49,8 @@ typedef enum { zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, int pixStride); +zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, zxing_ImageFormat format, + int rowStride, int pixStride); void zxing_ImageView_delete(zxing_ImageView* iv); void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); diff --git a/test/blackbox/ImageLoader.cpp b/test/blackbox/ImageLoader.cpp index 1f1f073421..c3f7ad5165 100644 --- a/test/blackbox/ImageLoader.cpp +++ b/test/blackbox/ImageLoader.cpp @@ -23,7 +23,7 @@ class STBImage : public ImageView std::unique_ptr _memory; public: - STBImage() : ImageView(nullptr, 0, 0, ImageFormat::None), _memory(nullptr, stbi_image_free) {} + STBImage() : ImageView(), _memory(nullptr, stbi_image_free) {} void load(const fs::path& imgPath) { diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index d127775b73..6a9f788d0e 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -47,7 +47,7 @@ internal class Dll [DllImport(DllName)] public static extern IntPtr zxing_PositionToString(Position position); [DllImport(DllName)] public static extern BarcodeFormats zxing_BarcodeFormatsFromString(string str); - [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new(byte[] data, int width, int height, ImageFormat format, int rowStride, int pixStride); + [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new_checked(byte[] data, int size, int width, int height, ImageFormat format, int rowStride, int pixStride); [DllImport(DllName)] public static extern void zxing_ImageView_delete(IntPtr iv); [DllImport(DllName)] public static extern IntPtr zxing_ReadBarcodes(IntPtr iv, IntPtr opts); @@ -186,9 +186,9 @@ public class ImageView public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) { - _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); + _d = zxing_ImageView_new_checked(data, data.Length, width, height, format, rowStride, pixStride); if (_d == IntPtr.Zero) - throw new Exception("Failed to create ImageView."); + throw new Exception(MarshalAsString(zxing_LastErrorMsg())); } ~ImageView() => zxing_ImageView_delete(_d); diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 5b5b0c2e4a..52e659b622 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -100,6 +100,15 @@ extern "C" { rowStride: ::core::ffi::c_int, pixStride: ::core::ffi::c_int, ) -> *mut zxing_ImageView; + pub fn zxing_ImageView_new_checked( + data: *const u8, + size: ::core::ffi::c_int, + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + format: zxing_ImageFormat, + rowStride: ::core::ffi::c_int, + pixStride: ::core::ffi::c_int, + ) -> *mut zxing_ImageView; pub fn zxing_ImageView_delete(iv: *mut zxing_ImageView); pub fn zxing_ImageView_crop( iv: *mut zxing_ImageView, diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index c40a05b82b..5046a53332 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -48,6 +48,13 @@ fn c2r_vec(buf: *mut u8, len: c_int) -> Vec { res } +fn last_error() -> Error { + match unsafe { zxing_LastErrorMsg().as_mut() } { + None => panic!("Internal error: zxing_LastErrorMsg() returned NULL"), + Some(error) => Error::new(ErrorKind::InvalidInput, c2r_str(error)), + } +} + macro_rules! last_error_or { ($expr:expr) => { match unsafe { zxing_LastErrorMsg().as_mut() } { @@ -137,8 +144,9 @@ impl<'a> ImageView<'a> { /// # Safety /// /// The memory gets accessed inside the c++ library at random places between - /// `ptr` and `ptr + height * row_stride + width * pix_stride. Note that both - /// the stride values could be negative, e.g. if the image view is rotated. + /// `ptr` and `ptr + height * row_stride` or `ptr + width * pix_stride`. + /// Note that both the stride values could be negative, e.g. if the image + /// view is rotated. pub unsafe fn from_ptr, U: TryInto>( ptr: *const u8, width: T, @@ -147,30 +155,37 @@ impl<'a> ImageView<'a> { row_stride: U, pix_stride: U, ) -> Result { - let res = ImageView(Rc::new(ImageViewOwner( - zxing_ImageView_new( - ptr, - Self::try_into_int(width)?, - Self::try_into_int(height)?, - format as zxing_ImageFormat, - Self::try_into_int(row_stride)?, - Self::try_into_int(pix_stride)?, - ), - PhantomData, - ))); - Ok(res) + let iv = zxing_ImageView_new( + ptr, + Self::try_into_int(width)?, + Self::try_into_int(height)?, + format as zxing_ImageFormat, + Self::try_into_int(row_stride)?, + Self::try_into_int(pix_stride)?, + ); + if iv.is_null() { + Err(last_error()) + } else { + Ok(ImageView(Rc::new(ImageViewOwner(iv, PhantomData)))) + } } pub fn from_slice + Clone>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { - let pix_size = match format { - ImageFormat::Lum => 1, - ImageFormat::RGB => 3, - ImageFormat::RGBX => 4, - }; - if Self::try_into_int(data.len())? < Self::try_into_int(width.clone())? * Self::try_into_int(height.clone())? * pix_size { - Err(Error::new(ErrorKind::InvalidInput, "data.len() < width * height * pix_size")) - } else { - unsafe { Self::from_ptr(data.as_ptr(), width, height, format, 0, 0) } + unsafe { + let iv = zxing_ImageView_new_checked( + data.as_ptr(), + data.len() as c_int, + Self::try_into_int(width)?, + Self::try_into_int(height)?, + format as zxing_ImageFormat, + 0, + 0, + ); + if iv.is_null() { + Err(last_error()) + } else { + Ok(ImageView(Rc::new(ImageViewOwner(iv, PhantomData)))) + } } } @@ -362,8 +377,7 @@ pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef::default()) + Err(last_error()) } } } From e1dead79d57a4ab1b2c467edd8eee289fbd9f1a8 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 12:23:13 +0100 Subject: [PATCH 245/587] dotnet: add minimal unit testing code --- wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs | 56 +++++++++++++++++++ .../ZXingCpp.Tests/ZXingCpp.Tests.csproj | 29 ++++++++++ wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 6 +- wrappers/dotnet/ZXingCppDemo/Program.cs | 2 +- wrappers/dotnet/dotnet.sln | 6 ++ 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs create mode 100644 wrappers/dotnet/ZXingCpp.Tests/ZXingCpp.Tests.csproj diff --git a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs new file mode 100644 index 0000000000..4cfd1eaf2d --- /dev/null +++ b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs @@ -0,0 +1,56 @@ +using Xunit; +using System.Text; +using ZXingCpp; + +namespace ZXingCpp.Tests; + +public class UnitTest1 +{ + [Fact] + public void ValidBarcodeFormatsParsing() + { + Assert.Equal(BarcodeFormats.QRCode, BarcodeReader.FormatsFromString("qrcode")); + Assert.Equal(BarcodeFormats.LinearCodes, BarcodeReader.FormatsFromString("linear_codes")); + Assert.Equal(BarcodeFormats.None, BarcodeReader.FormatsFromString("")); + } + + [Fact] + public void InvalidBarcodeFormatsParsing() + { + Assert.Throws(() => BarcodeReader.FormatsFromString("nope")); + } + + [Fact] + public void InvalidImageView() + { + Assert.Throws(() => new ImageView(new byte[0], 1, 1, ImageFormat.Lum)); + Assert.Throws(() => new ImageView(new byte[1], 1, 1, ImageFormat.Lum, 2)); + } + + [Fact] + public void Read() + { + var data = new List(); + foreach (var v in "0000101000101101011110111101011011101010100111011100101000100101110010100000") + data.Add((byte)(v == '0' ? 255 : 0)); + + var iv = new ImageView(data.ToArray(), data.Count, 1, ImageFormat.Lum); + var br = new BarcodeReader() { + Binarizer = Binarizer.BoolCast, + }; + var res = br.Read(iv); + + var expected = "96385074"; + + Assert.Single(res); + Assert.True(res[0].IsValid); + Assert.Equal(BarcodeFormats.EAN8, res[0].Format); + Assert.Equal(expected, res[0].Text); + Assert.Equal(Encoding.ASCII.GetBytes(expected), res[0].Bytes); + Assert.False(res[0].HasECI); + Assert.Equal(ContentType.Text, res[0].ContentType); + Assert.Equal(0, res[0].Orientation); + Assert.Equal(new PointI() { X = 4, Y = 0 }, res[0].Position.TopLeft); + Assert.Equal(1, res[0].LineCount); + } +} \ No newline at end of file diff --git a/wrappers/dotnet/ZXingCpp.Tests/ZXingCpp.Tests.csproj b/wrappers/dotnet/ZXingCpp.Tests/ZXingCpp.Tests.csproj new file mode 100644 index 0000000000..94b01c23c4 --- /dev/null +++ b/wrappers/dotnet/ZXingCpp.Tests/ZXingCpp.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 6a9f788d0e..6da5275451 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2024 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -170,12 +170,12 @@ public enum ImageFormat { public struct PointI { - public int x, y; + public int X, Y; }; public struct Position { - public PointI topLeft, topRight, bottomRight, bottomLeft; + public PointI TopLeft, TopRight, BottomRight, BottomLeft; public override string ToString() => MarshalAsString(zxing_PositionToString(this)); }; diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs index 9b978b0d76..c67b55dce2 100644 --- a/wrappers/dotnet/ZXingCppDemo/Program.cs +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2024 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 diff --git a/wrappers/dotnet/dotnet.sln b/wrappers/dotnet/dotnet.sln index bc94b4088f..a1927f302c 100644 --- a/wrappers/dotnet/dotnet.sln +++ b/wrappers/dotnet/dotnet.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp", "ZXingCpp\ZXingC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCppDemo", "ZXingCppDemo\ZXingCppDemo.csproj", "{05C626FE-A5CE-4263-9419-35E44F14DB93}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp.Tests", "ZXingCpp.Tests\ZXingCpp.Tests.csproj", "{9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {05C626FE-A5CE-4263-9419-35E44F14DB93}.Debug|Any CPU.Build.0 = Debug|Any CPU {05C626FE-A5CE-4263-9419-35E44F14DB93}.Release|Any CPU.ActiveCfg = Release|Any CPU {05C626FE-A5CE-4263-9419-35E44F14DB93}.Release|Any CPU.Build.0 = Release|Any CPU + {9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 15a4ccc3ef692d0d0e641722cddc7bf001b3f66c Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 1 Feb 2024 23:13:30 +0100 Subject: [PATCH 246/587] dotnet: rename ZXingCppDemo -> ZXingCpp.Demo --- wrappers/dotnet/README.md | 4 ++-- wrappers/dotnet/{ZXingCppDemo => ZXingCpp.Demo}/Program.cs | 0 .../ZXingCpp.Demo.csproj} | 0 wrappers/dotnet/dotnet.sln | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename wrappers/dotnet/{ZXingCppDemo => ZXingCpp.Demo}/Program.cs (100%) rename wrappers/dotnet/{ZXingCppDemo/ZXingCppDemo.csproj => ZXingCpp.Demo/ZXingCpp.Demo.csproj} (100%) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index e4f3693833..b70883d96a 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -49,11 +49,11 @@ public class Program } ``` -To run the `ZXingCppDemo` sample program, it is important that the dotnet runtime finds the native +To run the `ZXingCpp.Demo` sample program, it is important that the dotnet runtime finds the native `ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this ```sh -LD_LIBRARY_PATH= dotnet run --project ZXingCppDemo -- ../../test/samples/multi-1/1.png +LD_LIBRARY_PATH= dotnet run --project ZXingCpp.Demo -- ../../test/samples/multi-1/1.png ``` Note: This should currently be considered a pre-release. The API may change slightly to be even more diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCpp.Demo/Program.cs similarity index 100% rename from wrappers/dotnet/ZXingCppDemo/Program.cs rename to wrappers/dotnet/ZXingCpp.Demo/Program.cs diff --git a/wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj similarity index 100% rename from wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj rename to wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj diff --git a/wrappers/dotnet/dotnet.sln b/wrappers/dotnet/dotnet.sln index a1927f302c..dbccc51a00 100644 --- a/wrappers/dotnet/dotnet.sln +++ b/wrappers/dotnet/dotnet.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp", "ZXingCpp\ZXingCpp.csproj", "{64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCppDemo", "ZXingCppDemo\ZXingCppDemo.csproj", "{05C626FE-A5CE-4263-9419-35E44F14DB93}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp.Demo", "ZXingCpp.Demo\ZXingCpp.Demo.csproj", "{05C626FE-A5CE-4263-9419-35E44F14DB93}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp.Tests", "ZXingCpp.Tests\ZXingCpp.Tests.csproj", "{9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}" EndProject From 7593619f4c89561527912101ec88755f5b602ee9 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 1 Feb 2024 23:14:59 +0100 Subject: [PATCH 247/587] dotnet: whitespace fixes in UnitTest1.cs --- wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs | 60 ++++++++++----------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs index 4cfd1eaf2d..0275290c94 100644 --- a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs +++ b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs @@ -6,38 +6,38 @@ namespace ZXingCpp.Tests; public class UnitTest1 { - [Fact] - public void ValidBarcodeFormatsParsing() - { - Assert.Equal(BarcodeFormats.QRCode, BarcodeReader.FormatsFromString("qrcode")); - Assert.Equal(BarcodeFormats.LinearCodes, BarcodeReader.FormatsFromString("linear_codes")); - Assert.Equal(BarcodeFormats.None, BarcodeReader.FormatsFromString("")); - } - - [Fact] - public void InvalidBarcodeFormatsParsing() - { - Assert.Throws(() => BarcodeReader.FormatsFromString("nope")); - } - - [Fact] - public void InvalidImageView() - { - Assert.Throws(() => new ImageView(new byte[0], 1, 1, ImageFormat.Lum)); - Assert.Throws(() => new ImageView(new byte[1], 1, 1, ImageFormat.Lum, 2)); - } - - [Fact] - public void Read() - { - var data = new List(); + [Fact] + public void ValidBarcodeFormatsParsing() + { + Assert.Equal(BarcodeFormats.QRCode, BarcodeReader.FormatsFromString("qrcode")); + Assert.Equal(BarcodeFormats.LinearCodes, BarcodeReader.FormatsFromString("linear_codes")); + Assert.Equal(BarcodeFormats.None, BarcodeReader.FormatsFromString("")); + } + + [Fact] + public void InvalidBarcodeFormatsParsing() + { + Assert.Throws(() => BarcodeReader.FormatsFromString("nope")); + } + + [Fact] + public void InvalidImageView() + { + Assert.Throws(() => new ImageView(new byte[0], 1, 1, ImageFormat.Lum)); + Assert.Throws(() => new ImageView(new byte[1], 1, 1, ImageFormat.Lum, 2)); + } + + [Fact] + public void Read() + { + var data = new List(); foreach (var v in "0000101000101101011110111101011011101010100111011100101000100101110010100000") - data.Add((byte)(v == '0' ? 255 : 0)); + data.Add((byte)(v == '0' ? 255 : 0)); - var iv = new ImageView(data.ToArray(), data.Count, 1, ImageFormat.Lum); + var iv = new ImageView(data.ToArray(), data.Count, 1, ImageFormat.Lum); var br = new BarcodeReader() { - Binarizer = Binarizer.BoolCast, - }; + Binarizer = Binarizer.BoolCast, + }; var res = br.Read(iv); var expected = "96385074"; @@ -52,5 +52,5 @@ public void Read() Assert.Equal(0, res[0].Orientation); Assert.Equal(new PointI() { X = 4, Y = 0 }, res[0].Position.TopLeft); Assert.Equal(1, res[0].LineCount); - } + } } \ No newline at end of file From 7823c9ef4464157d2c46812eef0ba582acbb67fb Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 1 Feb 2024 23:15:34 +0100 Subject: [PATCH 248/587] dotnet: bump version to 0.1.1-alpha --- wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj | 11 ++++++----- wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj index 28f5299710..704202a992 100644 --- a/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj +++ b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj @@ -1,15 +1,16 @@  - - - - - Exe net8.0 enable enable + false + + + + + diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj index c106284325..c5659c054f 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -6,7 +6,7 @@ enable ZXingCpp - 0.1.0-alpha + 0.1.1-alpha Axel Waggershauser zxing-cpp From c58920f78e0cc39181083ef2a65242b719fadd04 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 1 Feb 2024 23:17:19 +0100 Subject: [PATCH 249/587] rust: code cosmetic (drop outdated `+ Clone` constraint) --- wrappers/rust/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 5046a53332..0adea923a1 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -170,7 +170,7 @@ impl<'a> ImageView<'a> { } } - pub fn from_slice + Clone>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { + pub fn from_slice>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { unsafe { let iv = zxing_ImageView_new_checked( data.as_ptr(), From 4d73b1971be34549001e076c4c08da64e362fb7e Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 00:23:56 +0100 Subject: [PATCH 250/587] dotnet: add ImageView(IntPtr, ...) to support SkiaSharp.SKBitmap input Add SKBitmapBarcodeReader extension class to Demo Program.cs. --- wrappers/dotnet/README.md | 2 +- wrappers/dotnet/ZXingCpp.Demo/Program.cs | 32 ++++++++++++++++++- .../dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj | 6 +++- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 8 +++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index b70883d96a..4d0a6f4bc4 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -18,7 +18,7 @@ using System.Collections.Generic; using ImageMagick; using ZXingCpp; -public static class ImageMagickBarcodeReader +public static class MagickImageBarcodeReader { public static List Read(MagickImage img, ReaderOptions? opts = null) { diff --git a/wrappers/dotnet/ZXingCpp.Demo/Program.cs b/wrappers/dotnet/ZXingCpp.Demo/Program.cs index c67b55dce2..74323a5b11 100644 --- a/wrappers/dotnet/ZXingCpp.Demo/Program.cs +++ b/wrappers/dotnet/ZXingCpp.Demo/Program.cs @@ -5,9 +5,10 @@ using System.Collections.Generic; using ImageMagick; +using SkiaSharp; using ZXingCpp; -public static class ImageMagickBarcodeReader +public static class MagickImageBarcodeReader { public static List Read(MagickImage img, ReaderOptions? opts = null) { @@ -21,11 +22,40 @@ public static List Read(MagickImage img, ReaderOptions? opts = null) public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); } +public static class SkBitmapBarcodeReader +{ + public static List Read(SKBitmap img, ReaderOptions? opts = null) + { + var format = img.Info.ColorType switch + { + SKColorType.Gray8 => ImageFormat.Lum, + SKColorType.Rgba8888 => ImageFormat.RGBX, + SKColorType.Bgra8888 => ImageFormat.BGRX, + _ => ImageFormat.None, + }; + if (format == ImageFormat.None) + { + if (!img.CanCopyTo(SKColorType.Gray8)) + throw new Exception("Incompatible SKColorType"); + img = img.Copy(SKColorType.Gray8); + format = ImageFormat.Lum; + } + var iv = new ImageView(img.GetPixels(), img.Info.Width, img.Info.Height, format); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, SKBitmap img) => Read(img, reader); +} + public class Program { public static void Main(string[] args) { +#if false var img = new MagickImage(args[0]); +#else + var img = SKBitmap.Decode(args[0]); +#endif Console.WriteLine(img); var reader = new BarcodeReader() { diff --git a/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj index 704202a992..f6cbee8124 100644 --- a/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj +++ b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj @@ -9,8 +9,12 @@ - + + + + + diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 6da5275451..61545ceef3 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -47,6 +47,7 @@ internal class Dll [DllImport(DllName)] public static extern IntPtr zxing_PositionToString(Position position); [DllImport(DllName)] public static extern BarcodeFormats zxing_BarcodeFormatsFromString(string str); + [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new(IntPtr data, int width, int height, ImageFormat format, int rowStride, int pixStride); [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new_checked(byte[] data, int size, int width, int height, ImageFormat format, int rowStride, int pixStride); [DllImport(DllName)] public static extern void zxing_ImageView_delete(IntPtr iv); @@ -191,6 +192,13 @@ public ImageView(byte[] data, int width, int height, ImageFormat format, int row throw new Exception(MarshalAsString(zxing_LastErrorMsg())); } + public ImageView(IntPtr data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) + { + _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); + if (_d == IntPtr.Zero) + throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + } + ~ImageView() => zxing_ImageView_delete(_d); } From c580d1e0c0c53a58cea1789b5a9dd8e78b5a53e1 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 01:32:51 +0100 Subject: [PATCH 251/587] ZXingReader: add `-binarizer ` command line option --- example/ZXingReader.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 3e22294bc0..a8d9595d44 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -35,6 +35,8 @@ static void PrintUsage(const char* exePath) << " Only detect given format(s) (faster)\n" << " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n" << " -errors Include results with errors (like checksum error)\n" + << " -binarizer \n" + << " Binarizer to be used for gray to binary conversion\n" << " -mode \n" << " Text mode used to render the raw byte content into text\n" << " -1 Print only file name, content/error on one line per file/barcode (implies '-mode Escaped')\n" @@ -85,6 +87,17 @@ static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& o std::cerr << e.what() << "\n"; return false; } + } else if (is("-binarizer")) { + if (++i == argc) + return false; + else if (is("local")) + options.setBinarizer(Binarizer::LocalAverage); + else if (is("global")) + options.setBinarizer(Binarizer::GlobalHistogram); + else if (is("fixed")) + options.setBinarizer(Binarizer::FixedThreshold); + else + return false; } else if (is("-mode")) { if (++i == argc) return false; From e6fb84be4def35f58954e348e028eefa225a66c5 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 10:08:42 +0100 Subject: [PATCH 252/587] dotnet: split README into repo and NuGet versions --- wrappers/dotnet/README.md | 51 ++----------------- wrappers/dotnet/ZXingCpp/README.md | 64 ++++++++++++++++++++++++ wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 4 +- 3 files changed, 70 insertions(+), 49 deletions(-) create mode 100644 wrappers/dotnet/ZXingCpp/README.md diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index 4d0a6f4bc4..8ad1242dde 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -1,64 +1,21 @@ # ZXingCpp -ZXingCpp is a .NET wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). - -It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. -It was originally ported from the Java ZXing Library but has been developed further and now includes -many improvements in terms of runtime and detection performance. - -## Usage +ZXingCpp is a .NET wrapper for [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). There is a NuGet package available: https://www.nuget.org/packages/ZXingCpp. It does currently not yet contain the native binary dll file. That needs to be copied/build separately at the moment. -Simple example usage: - -```cs -using System.Collections.Generic; -using ImageMagick; -using ZXingCpp; - -public static class MagickImageBarcodeReader -{ - public static List Read(MagickImage img, ReaderOptions? opts = null) - { - if (img.DetermineBitDepth() < 8) - img.SetBitDepth(8); - var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); - return BarcodeReader.Read(iv, opts); - } - - public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); -} - -public class Program -{ - public static void Main(string[] args) - { - var img = new MagickImage(args[0]); +## Usage - var reader = new BarcodeReader() { - Formats = BarcodeReader.FormatsFromString(args[1]), - TryInvert = false, - }; - - foreach (var b in reader.Read(img)) - Console.WriteLine($"{b.Format} : {b.Text}"); - } -} -``` +See either the [ZXingCpp/README.md](ZXingCpp/README.md) or the [ZXingCpp.Demo](ZXingCpp.Demo) project. To run the `ZXingCpp.Demo` sample program, it is important that the dotnet runtime finds the native `ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this ```sh -LD_LIBRARY_PATH= dotnet run --project ZXingCpp.Demo -- ../../test/samples/multi-1/1.png +LD_LIBRARY_PATH= dotnet run --project ZXingCpp.Demo -- ../../test/samples/multi-1/1.png ``` -Note: This should currently be considered a pre-release. The API may change slightly to be even more -"managed" depending on community feedback. - ## Benchmarking To compare the performance of this .NET wrapper project with other available barcode scanner .NET libraries, diff --git a/wrappers/dotnet/ZXingCpp/README.md b/wrappers/dotnet/ZXingCpp/README.md new file mode 100644 index 0000000000..b9188a11b7 --- /dev/null +++ b/wrappers/dotnet/ZXingCpp/README.md @@ -0,0 +1,64 @@ +# ZXingCpp + +ZXingCpp is a .NET wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). + +It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. +It was originally ported from the Java ZXing Library but has been developed further and now includes +many improvements in terms of runtime and detection performance. + +## Usage + +```cs +using System.Collections.Generic; +using ImageMagick; +using ZXingCpp; + +// BarcodeReader extension class to support direct reading from MagickImage +public static class MagickImageBarcodeReader +{ + public static List Read(MagickImage img, ReaderOptions? opts = null) + { + if (img.DetermineBitDepth() < 8) + img.SetBitDepth(8); + var bytes = img.ToByteArray(MagickFormat.Gray); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, MagickImage img) + => Read(img, reader); +} + +public class Program +{ + public static void Main(string[] args) + { + var img = new MagickImage(args[0]); + + var reader = new BarcodeReader() { + Formats = BarcodeReader.FormatsFromString(args[1]), + TryInvert = false, + // see the ReaderOptions implementation for more available options + }; + + foreach (var b in reader.Read(img)) + Console.WriteLine($"{b.Format} : {b.Text}"); + } +} +``` + +To run the code above, it is important that the dotnet runtime finds the native +`ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this + +```sh +LD_LIBRARY_PATH= dotnet run -- +``` + +Note: This is an alpha release, meaning the API may still change slightly to feel even more +"managed" depending on community feedback. + +## Benchmarking + +To compare the performance of this .NET wrapper project with other available barcode scanner .NET libraries, +I started the project [zxing-bench](https://github.com/axxel/zxing-bench). +The [README](https://github.com/axxel/zxing-bench/blob/main/dotnet/README.md) contains a few results to get an idea. diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj index c5659c054f..6070b37b24 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -6,7 +6,7 @@ enable ZXingCpp - 0.1.1-alpha + 0.1.2-alpha Axel Waggershauser zxing-cpp @@ -21,7 +21,7 @@ - + From 66b47cc6996e7d01015653556e2b0e657da5cbf8 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 14:37:49 +0100 Subject: [PATCH 253/587] dotnet: improve README (simpler example, why ZXingCpp section) --- wrappers/dotnet/ZXingCpp/README.md | 71 ++++++++++++++++++------------ 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp/README.md b/wrappers/dotnet/ZXingCpp/README.md index b9188a11b7..f3a6795795 100644 --- a/wrappers/dotnet/ZXingCpp/README.md +++ b/wrappers/dotnet/ZXingCpp/README.md @@ -6,42 +6,27 @@ It is an open-source, multi-format linear/matrix barcode image processing librar It was originally ported from the Java ZXing Library but has been developed further and now includes many improvements in terms of runtime and detection performance. + ## Usage ```cs -using System.Collections.Generic; -using ImageMagick; +using SkiaSharp; using ZXingCpp; -// BarcodeReader extension class to support direct reading from MagickImage -public static class MagickImageBarcodeReader -{ - public static List Read(MagickImage img, ReaderOptions? opts = null) - { - if (img.DetermineBitDepth() < 8) - img.SetBitDepth(8); - var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); - return BarcodeReader.Read(iv, opts); - } - - public static List Read(this BarcodeReader reader, MagickImage img) - => Read(img, reader); -} - public class Program { public static void Main(string[] args) { - var img = new MagickImage(args[0]); + var img = SKBitmap.Decode(args[0]).Copy(SKColorType.Gray8); + var iv = new ImageView(img.GetPixels(), img.Info.Width, img.Info.Height, ImageFormat.Lum); var reader = new BarcodeReader() { - Formats = BarcodeReader.FormatsFromString(args[1]), + Formats = args.Length > 1 ? BarcodeReader.FormatsFromString(args[1]) : BarcodeFormats.Any, TryInvert = false, // see the ReaderOptions implementation for more available options }; - foreach (var b in reader.Read(img)) + foreach (var b in reader.Read(iv)) Console.WriteLine($"{b.Format} : {b.Text}"); } } @@ -51,14 +36,44 @@ To run the code above, it is important that the dotnet runtime finds the native `ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this ```sh -LD_LIBRARY_PATH= dotnet run -- +LD_LIBRARY_PATH= dotnet run -- [barcode-format-list] ``` -Note: This is an alpha release, meaning the API may still change slightly to feel even more -"managed" depending on community feedback. +Note: This is an alpha release, meaning the API may still change slightly to potentially feel even +more like a native C# library depending on community feedback. + + +## Why ZXingCpp? + +There are a number of areas where ZXingCpp shines compared to other popular .NET barcode scanner libraries. +The following comparison is with respect to the open source [ZXing.Net](https://www.nuget.org/packages/ZXing.Net) +and the commercial [Dynamsoft](https://www.nuget.org/packages/Dynamsoft.DotNet.Barcode) projects. + +### Performance + +To compare the performance of ZXingCpp with the other two libraries, I started the project +[zxing-bench](https://github.com/axxel/zxing-bench). +The [README](https://github.com/axxel/zxing-bench/blob/main/dotnet/README.md) contains a few details but to get +an idea: ZXingCpp is on average 2x-10x faster than Dynamsoft and 10x-50x faster than ZXing.Net. + +### Detection rate + +The benchmarking tool also showed that ZXingCpp has a superior detection rate compared to ZXing.Net while it is +sometimes better sometimes worse than the commercial Dynamsoft package, depending on the sample type and the +library configuration. The latter definitively supports more barcode formats compared to the two ZXing decendents. + +### Ease of use + +The sample program above shows the simplicitly of the API. The others are similar but seem a bit more +complicated with regards to setting parameters. + +### Standards support + +ZXingCpp has full support for binary data and ECI handling and provides a standards conforming `bytesECI()` +data that can be used to simulate a hardware/handheld barcode scanner. This seems not the case for ZXing.Net +and is unclear for Dynamsoft. -## Benchmarking +### License / costs -To compare the performance of this .NET wrapper project with other available barcode scanner .NET libraries, -I started the project [zxing-bench](https://github.com/axxel/zxing-bench). -The [README](https://github.com/axxel/zxing-bench/blob/main/dotnet/README.md) contains a few results to get an idea. +ZXingCpp has the liberal Apache-2.0 license and is free to use in commercial applications. That said, +I accept [donations](https://github.com/sponsors/axxel) and might be available for commercial consulting ;). From a12262c35673e0b5532ead40431790eb9f57b0af Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 19:14:40 +0100 Subject: [PATCH 254/587] ci: major cleanup of shell/working-directory and env var usage * BUILD_SHARED_LIBS=ON (also on windows) * add Install and upload step for native builds --- .github/workflows/ci.yml | 79 ++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edd2a464b0..40db6ba466 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,36 +23,41 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 + - name: Setup Python 3 uses: actions/setup-python@v4 with: python-version: '3.x' - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - working-directory: ${{runner.workspace}}/build - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=ON -DBUILD_C_API=ON + + - name: Configure + run: > + cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON + -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=ON -DBUILD_C_API=ON - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . -j8 --config $BUILD_TYPE + run: cmake --build build -j8 --config ${{env.BUILD_TYPE}} + + # - name: Set PATH for Tests + # shell: bash # to make the $GITHUB_PATH update work + # if: runner.os == 'Windows' + # run: | + # echo "${GITHUB_WORKSPACE}/build/core/${BUILD_TYPE}" >> $GITHUB_PATH + # echo "${GITHUB_WORKSPACE}/build/lib/${BUILD_TYPE}" >> $GITHUB_PATH - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -V -C $BUILD_TYPE + if: runner.os != 'Windows' # need to disable ctest on Windows when build as shared library for now + run: ctest --test-dir build -V -C ${{env.BUILD_TYPE}} + + - name: Install + run: | + cmake -E make_directory install + cmake --install build --config ${{env.BUILD_TYPE}} --prefix ${{github.workspace}}/install + + - uses: actions/upload-artifact@v4 + with: + name: ${{matrix.os}}-artifacts + path: install build-ubuntu-sanitize: runs-on: ubuntu-latest @@ -60,22 +65,19 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{github.ref}} - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - name: Configure run: > - cmake -S $GITHUB_WORKSPACE -B ${{runner.workspace}}/build -DCMAKE_BUILD_TYPE=RelWithDebInfo + cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=OFF -DBUILD_C_API=OFF -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-march=native -fsanitize=address,undefined -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer" - name: Build - run: cmake --build ${{runner.workspace}}/build -j8 + run: cmake --build build -j8 - name: Test - working-directory: ${{runner.workspace}}/build - run: ctest -V + run: ctest -V --test-dir build build-ios: runs-on: macos-latest @@ -85,18 +87,13 @@ jobs: ref: ${{github.ref}} - name: Build the swift package - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}} run: swift build - name: Build the demo app - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/demo + working-directory: wrappers/ios/demo run: xcodebuild build -scheme demo -sdk "iphonesimulator" - name: Validate the Pod - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}} run: pod lib lint --allow-warnings build-android: @@ -202,22 +199,18 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - shell: cmd + - name: Configure + shell: cmd # powershell messes up the arguments containing a '.' ?!? run: > - cmake -S ${{github.workspace}}/wrappers/winrt -B ${{runner.workspace}}/build -A ARM64 + cmake -S wrappers/winrt -B build -A ARM64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release - -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF + -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DBUILD_C_API=OFF -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 - name: Build - shell: cmd - run: cmake --build ${{runner.workspace}}/build -j8 --config Release + run: cmake --build build -j8 --config Release - uses: actions/upload-artifact@v3 with: name: winrt-ARM64-artifacts - path: ${{runner.workspace}}/build/dist + path: build/dist From 604c4d3206a76bd0c4d0608ed902e0323072e0aa Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 3 Feb 2024 02:34:45 +0100 Subject: [PATCH 255/587] dotnet: add x64 runtime assets to NuGet package and bump version --- wrappers/dotnet/.gitignore | 3 +++ wrappers/dotnet/ZXingCpp/README.md | 14 +++++++++----- wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 7 +++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/wrappers/dotnet/.gitignore b/wrappers/dotnet/.gitignore index 75028b44d1..0069337825 100644 --- a/wrappers/dotnet/.gitignore +++ b/wrappers/dotnet/.gitignore @@ -3,6 +3,9 @@ ## ## Get latest from `dotnet new gitignore` +# hand selected runtime/dll files +runtimes + # dotenv files .env diff --git a/wrappers/dotnet/ZXingCpp/README.md b/wrappers/dotnet/ZXingCpp/README.md index f3a6795795..2a12b75c2d 100644 --- a/wrappers/dotnet/ZXingCpp/README.md +++ b/wrappers/dotnet/ZXingCpp/README.md @@ -3,7 +3,7 @@ ZXingCpp is a .NET wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. -It was originally ported from the Java ZXing Library but has been developed further and now includes +It was originally ported from the Java ZXing library but has been developed further and now includes many improvements in terms of runtime and detection performance. @@ -32,13 +32,17 @@ public class Program } ``` -To run the code above, it is important that the dotnet runtime finds the native -`ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this - +Executing this sample code from the command line would look like this: ```sh -LD_LIBRARY_PATH= dotnet run -- [barcode-format-list] +dotnet run -- [barcode-format-list] ``` +The NuGet package includes the runtime/native c++ libraries for the x64 architecture on +Windows, Linux and macOS. If something is not working out of the box or you need arm64 support +then you need to build the `[lib]ZXing[.dll|.so|.dylib]` file yourself and make sure the .NET +runtime find it (see e.g. the environment variables `LD_LIBRARY_PATH` on Linux or `PATH` on +Windows). + Note: This is an alpha release, meaning the API may still change slightly to potentially feel even more like a native C# library depending on community feedback. diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj index 6070b37b24..f55757cf37 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -6,13 +6,13 @@ enable ZXingCpp - 0.1.2-alpha + 0.1.3-alpha Axel Waggershauser zxing-cpp ZXingCpp is a .NET wrapper for the C++ library zxing-cpp. It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. - It was originally ported from the Java ZXing Library but has been developed further and now includes + It was originally ported from the Java ZXing library but has been developed further and now includes many improvements in terms of runtime and detection performance. https://github.com/zxing-cpp/zxing-cpp README.md @@ -22,6 +22,9 @@ + + + From a49f2e084ef82a4652c2c6b58afc9ac913a88b4d Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 4 Feb 2024 16:23:06 +0100 Subject: [PATCH 256/587] c++: remove unused #include statements (and one dead file) --- core/CMakeLists.txt | 1 - core/src/BitMatrix.cpp | 1 - core/src/GlobalHistogramBinarizer.cpp | 2 -- core/src/HybridBinarizer.cpp | 3 +-- core/src/Result.cpp | 1 - core/src/aztec/AZDecoder.cpp | 1 - core/src/aztec/AZDetector.cpp | 1 - core/src/aztec/AZReader.cpp | 1 - core/src/datamatrix/DMDecoder.cpp | 1 - core/src/datamatrix/DMDetector.cpp | 1 - core/src/maxicode/MCReader.cpp | 1 - core/src/oned/ODCode128Reader.cpp | 2 -- core/src/oned/ODCode39Reader.cpp | 1 - core/src/oned/ODDataBarCommon.cpp | 1 + core/src/oned/ODDataBarExpandedBitDecoder.cpp | 2 -- core/src/oned/ODITFReader.cpp | 2 -- core/src/oned/ODRowReader.cpp | 17 ----------------- core/src/pdf417/PDFReader.cpp | 2 +- core/src/qrcode/QRCodecMode.cpp | 1 - core/src/qrcode/QRFormatInformation.cpp | 2 -- test/blackbox/TestReaderMain.cpp | 1 - 21 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 core/src/oned/ODRowReader.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 77cc1f9763..d63d9ecc63 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -300,7 +300,6 @@ if (BUILD_READERS) src/oned/ODReader.h src/oned/ODReader.cpp src/oned/ODRowReader.h - src/oned/ODRowReader.cpp ) endif() if (BUILD_WRITERS) diff --git a/core/src/BitMatrix.cpp b/core/src/BitMatrix.cpp index 729a5752b3..f03cde7471 100644 --- a/core/src/BitMatrix.cpp +++ b/core/src/BitMatrix.cpp @@ -173,4 +173,3 @@ BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, floa } } // ZXing - diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 44ca15810c..351118492c 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -11,8 +11,6 @@ #include #include -#include -#include #include namespace ZXing { diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index a74fbd2872..68506fc335 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -9,10 +9,9 @@ #include "BitMatrix.h" #include "Matrix.h" +#include #include #include -#include -#include namespace ZXing { diff --git a/core/src/Result.cpp b/core/src/Result.cpp index 67b38e27fa..c637811a25 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -7,7 +7,6 @@ #include "Result.h" #include "DecoderResult.h" -#include "TextDecoder.h" #include "ZXAlgorithms.h" #include diff --git a/core/src/aztec/AZDecoder.cpp b/core/src/aztec/AZDecoder.cpp index 7ab6f6a84f..c33324afcf 100644 --- a/core/src/aztec/AZDecoder.cpp +++ b/core/src/aztec/AZDecoder.cpp @@ -10,7 +10,6 @@ #include "AZDetectorResult.h" #include "BitArray.h" #include "BitMatrix.h" -#include "CharacterSet.h" #include "DecoderResult.h" #include "GenericGF.h" #include "ReedSolomonDecoder.h" diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 500c181c5a..bd528d2b64 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -20,7 +20,6 @@ #include "ZXAlgorithms.h" #include -#include #include #include diff --git a/core/src/aztec/AZReader.cpp b/core/src/aztec/AZReader.cpp index b9b36a7ba6..1d70b19b3c 100644 --- a/core/src/aztec/AZReader.cpp +++ b/core/src/aztec/AZReader.cpp @@ -15,7 +15,6 @@ #include "DecoderResult.h" #include "Result.h" -#include #include namespace ZXing::Aztec { diff --git a/core/src/datamatrix/DMDecoder.cpp b/core/src/datamatrix/DMDecoder.cpp index af3b9c5018..34a01d39db 100644 --- a/core/src/datamatrix/DMDecoder.cpp +++ b/core/src/datamatrix/DMDecoder.cpp @@ -8,7 +8,6 @@ #include "BitMatrix.h" #include "BitSource.h" -#include "CharacterSet.h" #include "DMBitLayout.h" #include "DMDataBlock.h" #include "DMVersion.h" diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index ce8d15fde6..2d8773b505 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include diff --git a/core/src/maxicode/MCReader.cpp b/core/src/maxicode/MCReader.cpp index 66f25aa3f8..8a70952fae 100644 --- a/core/src/maxicode/MCReader.cpp +++ b/core/src/maxicode/MCReader.cpp @@ -8,7 +8,6 @@ #include "BinaryBitmap.h" #include "BitMatrix.h" -#include "ReaderOptions.h" #include "DecoderResult.h" #include "MCBitMatrixParser.h" #include "MCDecoder.h" diff --git a/core/src/oned/ODCode128Reader.cpp b/core/src/oned/ODCode128Reader.cpp index 9d0927eab6..d40b53d9b3 100644 --- a/core/src/oned/ODCode128Reader.cpp +++ b/core/src/oned/ODCode128Reader.cpp @@ -13,9 +13,7 @@ #include #include #include -#include #include -#include #include namespace ZXing::OneD { diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index e102fee353..d829ac3e97 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -7,7 +7,6 @@ #include "ODCode39Reader.h" #include "ReaderOptions.h" -#include "DecoderResult.h" #include "Result.h" #include "ZXAlgorithms.h" diff --git a/core/src/oned/ODDataBarCommon.cpp b/core/src/oned/ODDataBarCommon.cpp index 937f350005..2b85607428 100644 --- a/core/src/oned/ODDataBarCommon.cpp +++ b/core/src/oned/ODDataBarCommon.cpp @@ -4,6 +4,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "ODDataBarCommon.h" + #include namespace ZXing::OneD::DataBar { diff --git a/core/src/oned/ODDataBarExpandedBitDecoder.cpp b/core/src/oned/ODDataBarExpandedBitDecoder.cpp index aa0484769e..165cd32f93 100644 --- a/core/src/oned/ODDataBarExpandedBitDecoder.cpp +++ b/core/src/oned/ODDataBarExpandedBitDecoder.cpp @@ -11,8 +11,6 @@ #include "Error.h" #include "GTIN.h" -#include - namespace ZXing::OneD::DataBar { constexpr char GS = 29; // FNC1 diff --git a/core/src/oned/ODITFReader.cpp b/core/src/oned/ODITFReader.cpp index b6d75aa7b4..6e699e882d 100644 --- a/core/src/oned/ODITFReader.cpp +++ b/core/src/oned/ODITFReader.cpp @@ -11,8 +11,6 @@ #include "Result.h" #include "ZXAlgorithms.h" -#include - namespace ZXing::OneD { constexpr auto START_PATTERN_ = FixedPattern<4, 4>{1, 1, 1, 1}; diff --git a/core/src/oned/ODRowReader.cpp b/core/src/oned/ODRowReader.cpp deleted file mode 100644 index c8454dbf4b..0000000000 --- a/core/src/oned/ODRowReader.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/* -* Copyright 2020 Axel Waggershauser -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "ODRowReader.h" - -#include "BitArray.h" -#include "Result.h" - -#include - -namespace ZXing::OneD { - - - -} // namespace ZXing::OneD diff --git a/core/src/pdf417/PDFReader.cpp b/core/src/pdf417/PDFReader.cpp index 27d943c5e0..2282e3be32 100644 --- a/core/src/pdf417/PDFReader.cpp +++ b/core/src/pdf417/PDFReader.cpp @@ -26,7 +26,7 @@ #ifdef PRINT_DEBUG #include "BitMatrixIO.h" -#include +#include #endif namespace ZXing { diff --git a/core/src/qrcode/QRCodecMode.cpp b/core/src/qrcode/QRCodecMode.cpp index 5020118a87..b0e546a013 100644 --- a/core/src/qrcode/QRCodecMode.cpp +++ b/core/src/qrcode/QRCodecMode.cpp @@ -12,7 +12,6 @@ #include "ZXAlgorithms.h" #include -#include namespace ZXing::QRCode { diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index 85c88d766b..9eec6b81de 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -11,8 +11,6 @@ #include "BitHacks.h" #include "ZXAlgorithms.h" -#include - namespace ZXing::QRCode { static uint32_t MirrorBits(uint32_t bits) diff --git a/test/blackbox/TestReaderMain.cpp b/test/blackbox/TestReaderMain.cpp index 5a69c6143c..b129810ac2 100644 --- a/test/blackbox/TestReaderMain.cpp +++ b/test/blackbox/TestReaderMain.cpp @@ -8,7 +8,6 @@ #include "ImageLoader.h" #include "ReadBarcode.h" #include "ZXAlgorithms.h" -#include "ZXFilesystem.h" #include #include From 8083f99da4d36fdf36bc45351512cd068ca137f0 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:33:55 +0100 Subject: [PATCH 257/587] GlobalHistogramBinarizer: comment on failed performance tuning experiment --- core/src/GlobalHistogramBinarizer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 351118492c..5e0e245b50 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -46,6 +46,8 @@ static void ThresholdSharpened(const ImageLineView in, int threshold, std::vecto static auto GenHistogram(const ImageLineView line) { + // This code causes about 20% of the total runtime on an AVX2 system for a EAN13 search on Lum input data. + // Trying to increase the performance by performing 2 or 4 "parallel" histograms helped nothing. Histogram res = {}; for (auto pix : line) res[pix >> LUMINANCE_SHIFT]++; From 85d8acb5c86ff8f7f403b85d7897d16047ac4b4a Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:34:47 +0100 Subject: [PATCH 258/587] GridSampler: add disabled experiment for 3x3 sampling instead of 1x1 --- core/src/GridSampler.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/GridSampler.cpp b/core/src/GridSampler.cpp index 8b5cf9b336..626ce025e3 100644 --- a/core/src/GridSampler.cpp +++ b/core/src/GridSampler.cpp @@ -56,7 +56,15 @@ DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const R #ifdef PRINT_DEBUG log(p, 3); #endif +#if 0 + int sum = 0; + for (int dy = -1; dy <= 1; ++dy) + for (int dx = -1; dx <= 1; ++dx) + sum += image.get(p + PointF(dx, dy)); + if (sum >= 5) +#else if (image.get(p)) +#endif res.set(x, y); } } From 676f2ffca08f7efccd60abf90eec6034c1ed9852 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:36:32 +0100 Subject: [PATCH 259/587] RegressionLine: fflush(stdout) debugging printf --- core/src/RegressionLine.h | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index c536a8e475..6de1d4863f 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -123,6 +123,7 @@ class RegressionLine break; #ifdef PRINT_DEBUG printf("removed %zu points -> %zu remaining\n", old_points_size - points.size(), points.size()); + fflush(stdout); #endif ret = evaluate(points); } From c969dacdc9d458d8d28adee87bdabba9eba8742d Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:47:45 +0100 Subject: [PATCH 260/587] ConcentricFinder: add disabled code discarding non-circle-like loops --- core/src/ConcentricFinder.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index c21a6455e3..e21f65e664 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -39,6 +39,24 @@ std::optional CenterOfDoubleCross(const BitMatrix& image, PointI center, std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle) { +#if 0 + if (requireCircle) { + // alternative implementation with the aim of discarding closed loops that are not all circle like (M > 5*m) + auto points = CollectRingPoints(image, center, range, std::abs(nth), nth < 0); + if (points.empty()) + return {}; + auto res = Reduce(points, PointF{}, std::plus{}) / Size(points); + + double m = range, M = 0; + for (auto p : points) + UpdateMinMax(m, M, maxAbsComponent(p - res)); + + if (M > 5 * m) + return {}; + + return res; + } +#endif // range is the approximate width/height of the nth ring, if nth>1 then it would be plausible to limit the search radius // to approximately range / 2 * sqrt(2) == range * 0.75 but it turned out to be too limiting with realworld/noisy data. int radius = range; From acf6966bdf0cb98068265aa102de335f174b0ce2 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:50:56 +0100 Subject: [PATCH 261/587] Pattern: add disabled alternative NormalizedPattern implementation --- core/src/Pattern.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 3a587010e2..e7eb2baad1 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -299,6 +299,7 @@ template std::array NormalizedPattern(const PatternView& view) { float moduleSize = static_cast(view.sum(LEN)) / SUM; +#if 1 int err = SUM; std::array is; std::array rs; @@ -318,7 +319,25 @@ std::array NormalizedPattern(const PatternView& view) is[mi] += err; rs[mi] -= err; } +#else + std::array is, e2e; + int min_v = view[0], min_i = 0; + + for (int i = 1; i < LEN; i++) { + float v = (view[i - 1] + view[i]) / moduleSize; + e2e[i] = int(v + .5f); + if (view[i] < min_v) { + min_v = view[i]; + min_i = i; + } + } + is[min_i] = 1; + for (int i = min_i + 1; i < LEN; ++i) + is[i] = e2e[i] - is[i - 1]; + for (int i = min_i - 1; i >= 0; --i) + is[i] = e2e[i + 1] - is[i + 1]; +#endif return is; } From 6b9f16a79d1965197fb9da4a733090c1efc6c346 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 6 Feb 2024 14:57:49 +0100 Subject: [PATCH 262/587] HybridBinarizer: re-implement with symmetric threshold interpolation This carefully crafted auto-vectorizable alternative to the original implementation is practically as fast but fixes the general issue with inverted symbols and the assumption in the old implementation that the background is bright by default. This is a direct fix for #709. It looses 8 of the current black box tests but gains 18 new ones, plus those like in #709. I left the old implementation in the code easy before/after comparisons in case new issues should be reported. --- core/src/HybridBinarizer.cpp | 179 +++++++++++++++++++++++---- core/src/Matrix.h | 8 ++ test/blackbox/BlackboxTestRunner.cpp | 22 ++-- test/samples/qrcode-2/#709.jpg | Bin 0 -> 12918 bytes test/samples/qrcode-2/#709.txt | 1 + 5 files changed, 178 insertions(+), 32 deletions(-) create mode 100644 test/samples/qrcode-2/#709.jpg create mode 100644 test/samples/qrcode-2/#709.txt diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 68506fc335..bf6baf027b 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -11,14 +11,17 @@ #include #include +#include #include +#define USE_NEW_ALGORITHM + namespace ZXing { // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. // So this is the smallest dimension in each axis we can accept. static constexpr int BLOCK_SIZE = 8; -static constexpr int MINIMUM_DIMENSION = BLOCK_SIZE * 5; +static constexpr int WINDOW_SIZE = BLOCK_SIZE * (1 + 2 * 2); static constexpr int MIN_DYNAMIC_RANGE = 24; HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer(iv) {} @@ -42,15 +45,34 @@ bool HybridBinarizer::getPatternRow(int row, int rotation, PatternRow& res) cons #endif } +using T_t = uint8_t; + +/** +* Applies a single threshold to a block of pixels. +*/ +static void ThresholdBlock(const uint8_t* __restrict luminances, int xoffset, int yoffset, int threshold, int rowStride, + BitMatrix& matrix) +{ + for (int y = yoffset; y < yoffset + BLOCK_SIZE; ++y) { + auto* src = luminances + y * rowStride + xoffset; + auto* const dstBegin = matrix.row(y).begin() + xoffset; + // TODO: fix pixelStride > 1 case + for (auto* dst = dstBegin; dst < dstBegin + BLOCK_SIZE; ++dst, ++src) + *dst = (*src <= threshold) * BitMatrix::SET_V; + } +} + +#ifndef USE_NEW_ALGORITHM + /** * Calculates a single black point for each block of pixels and saves it away. * See the following thread for a discussion of this algorithm: * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 */ -static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, +static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, int rowStride) { - Matrix blackPoints(subWidth, subHeight); + Matrix blackPoints(subWidth, subHeight); for (int y = 0; y < subHeight; y++) { int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); @@ -111,32 +133,21 @@ static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, in return blackPoints; } - -/** -* Applies a single threshold to a block of pixels. -*/ -static void ThresholdBlock(const uint8_t* __restrict luminances, int xoffset, int yoffset, int threshold, int rowStride, - BitMatrix& matrix) -{ - for (int y = yoffset; y < yoffset + BLOCK_SIZE; ++y) { - auto* src = luminances + y * rowStride + xoffset; - auto* const dstBegin = matrix.row(y).begin() + xoffset; - // TODO: fix pixelStride > 1 case - for (auto* dst = dstBegin; dst < dstBegin + BLOCK_SIZE; ++dst, ++src) - *dst = (*src <= threshold) * BitMatrix::SET_V; - } -} - /** * For each block in the image, calculate the average black point using a 5x5 grid * of the blocks around it. Also handles the corner cases (fractional blocks are computed based * on the last pixels in the row/column which are also used in the previous block). */ static std::shared_ptr CalculateMatrix(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, - int height, int rowStride, const Matrix& blackPoints) + int height, int rowStride, const Matrix& blackPoints) { auto matrix = std::make_shared(width, height); +#ifndef NDEBUG + Matrix out(width, height); + Matrix out2(width, height); +#endif + for (int y = 0; y < subHeight; y++) { int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); for (int x = 0; x < subWidth; x++) { @@ -151,15 +162,140 @@ static std::shared_ptr CalculateMatrix(const uint8_t* __restrict lumi } int average = sum / 25; ThresholdBlock(luminances, xoffset, yoffset, average, rowStride, *matrix); + +#ifndef NDEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) { + out.set(xoffset + xx, yoffset + yy, blackPoints(x, y)); + out2.set(xoffset + xx, yoffset + yy, average); + } +#endif + } + } + +#ifndef NDEBUG + std::ofstream file("thresholds.pnm"); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); + std::ofstream file2("thresholds_avg.pnm"); + file2 << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file2.write(reinterpret_cast(out2.data()), out2.size()); +#endif + + return matrix; +} + +#else + +// Subdivide the image in blocks of BLOCK_SIZE and calculate one treshold value per block as +// (max - min > MIN_DYNAMIC_RANGE) ? (max + min) / 2 : 0 +static Matrix BlockThresholds(const ImageView iv) +{ + int subWidth = (iv.width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS) + int subHeight = (iv.height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS) + + Matrix thresholds(subWidth, subHeight); + + for (int y = 0; y < subHeight; y++) { + int y0 = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE); + for (int x = 0; x < subWidth; x++) { + int x0 = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE); + uint8_t min = 255; + uint8_t max = 0; + for (int yy = 0; yy < BLOCK_SIZE; yy++) { + auto line = iv.data(x0, y0 + yy); + for (int xx = 0; xx < BLOCK_SIZE; xx++) + UpdateMinMax(min, max, line[xx]); + } + + thresholds(x, y) = (max - min > MIN_DYNAMIC_RANGE) ? (int(max) + min) / 2 : 0; } } + return thresholds; +} + +// Apply gaussian-like smoothing filter over all non-zero thresholds and fill any remainig gaps with nearest neighbor +static Matrix SmoothThresholds(Matrix&& in) +{ + Matrix out(in.width(), in.height()); + + constexpr int R = WINDOW_SIZE / BLOCK_SIZE / 2; + for (int y = 0; y < in.height(); y++) { + for (int x = 0; x < in.width(); x++) { + int left = std::clamp(x, R, in.width() - R - 1); + int top = std::clamp(y, R, in.height() - R - 1); + + int sum = in(x, y) * 2; + int n = (sum > 0) * 2; + auto add = [&](int x, int y) { + int t = in(x, y); + sum += t; + n += t > 0; + }; + + for (int dy = -R; dy <= R; ++dy) + for (int dx = -R; dx <= R; ++dx) + add(left + dx, top + dy); + + out(x, y) = n > 0 ? sum / n : 0; + } + } + + // flood fill any remaing gaps of (very large) no-contrast regions + auto last = out.begin() - 1; + for (auto* i = out.begin(); i != out.end(); ++i) { + if (*i) { + if (last != i - 1) + std::fill(last + 1, i, *i); + last = i; + } + } + std::fill(last + 1, out.end(), *last); + + return out; +} + +static std::shared_ptr ThresholdImage(const ImageView iv, const Matrix& thresholds) +{ + auto matrix = std::make_shared(iv.width(), iv.height()); + +#ifndef NDEBUG + Matrix out(iv.width(), iv.height()); +#endif + + for (int y = 0; y < thresholds.height(); y++) { + int yoffset = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE); + for (int x = 0; x < thresholds.width(); x++) { + int xoffset = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE); + ThresholdBlock(iv.data(0, 0), xoffset, yoffset, thresholds(x, y), iv.rowStride(), *matrix); + +#ifndef NDEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) + out.set(xoffset + xx, yoffset + yy, thresholds(x, y)); +#endif + } + } + +#ifndef NDEBUG + std::ofstream file("thresholds_new.pnm"); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); +#endif + return matrix; } +#endif + std::shared_ptr HybridBinarizer::getBlackMatrix() const { - if (width() >= MINIMUM_DIMENSION && height() >= MINIMUM_DIMENSION) { + if (width() >= WINDOW_SIZE && height() >= WINDOW_SIZE) { +#ifdef USE_NEW_ALGORITHM + auto thrs = SmoothThresholds(BlockThresholds(_buffer)); + return ThresholdImage(_buffer, thrs); +#else const uint8_t* luminances = _buffer.data(0, 0); int subWidth = (width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS) int subHeight = (height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS) @@ -167,6 +303,7 @@ std::shared_ptr HybridBinarizer::getBlackMatrix() const CalculateBlackPoints(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride()); return CalculateMatrix(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride(), blackPoints); +#endif } else { // If the image is too small, fall back to the global histogram approach. return GlobalHistogramBinarizer::getBlackMatrix(); diff --git a/core/src/Matrix.h b/core/src/Matrix.h index 8e6cbbdc90..b6f8bc2150 100644 --- a/core/src/Matrix.h +++ b/core/src/Matrix.h @@ -102,6 +102,14 @@ class Matrix return _data.data() + _width * _height; } + value_t* begin() { + return _data.data(); + } + + value_t* end() { + return _data.data() + _width * _height; + } + void clear(value_t value = {}) { std::fill(_data.begin(), _data.end(), value); } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 27385e34da..c75fdc63af 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -569,11 +569,11 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 16, 16, 270 }, }); - runTests("qrcode-2", "QRCode", 50, { - { 46, 48, 0 }, - { 46, 48, 90 }, - { 46, 48, 180 }, - { 46, 48, 270 }, + runTests("qrcode-2", "QRCode", 51, { + { 45, 48, 0 }, + { 45, 48, 90 }, + { 45, 48, 180 }, + { 45, 48, 270 }, { 22, 1, pure }, // the misread is the 'outer' symbol in 16.png }); @@ -581,14 +581,14 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 28, 28, 0 }, { 28, 28, 90 }, { 28, 28, 180 }, - { 27, 27, 270 }, + { 28, 28, 270 }, }); runTests("qrcode-4", "QRCode", 41, { - { 29, 29, 0 }, - { 29, 29, 90 }, - { 29, 29, 180 }, - { 29, 29, 270 }, + { 31, 31, 0 }, + { 31, 31, 90 }, + { 31, 31, 180 }, + { 31, 31, 270 }, }); runTests("qrcode-5", "QRCode", 16, { @@ -612,7 +612,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("microqrcode-1", "MicroQRCode", 16, { { 15, 15, 0 }, - { 15, 15, 90 }, + { 14, 14, 90 }, { 15, 15, 180 }, { 15, 15, 270 }, { 9, 0, pure }, diff --git a/test/samples/qrcode-2/#709.jpg b/test/samples/qrcode-2/#709.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1814946889095419450852397ca0057dc4c3bdac GIT binary patch literal 12918 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 literal 0 HcmV?d00001 diff --git a/test/samples/qrcode-2/#709.txt b/test/samples/qrcode-2/#709.txt new file mode 100644 index 0000000000..064afa604a --- /dev/null +++ b/test/samples/qrcode-2/#709.txt @@ -0,0 +1 @@ +https://u.wechat.com/ELaSWShWyTFQvXJkp9vCgFA \ No newline at end of file From 89cac928c62c3fe833504dcef2e7f1af53b0786b Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 6 Feb 2024 15:00:26 +0100 Subject: [PATCH 263/587] ReadBarcode: add check for downscaleFactor --- core/src/ReadBarcode.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 5c371adf1e..ab8108c1ba 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -84,6 +84,9 @@ class LumImagePyramid LumImagePyramid(const ImageView& iv, int threshold, int factor) { + if (factor < 2) + throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor"); + layers.push_back(iv); // TODO: if only matrix codes were considered, then using std::min would be sufficient (see #425) while (threshold > 0 && std::max(layers.back().width(), layers.back().height()) > threshold && From f14aaca4f83215be51a1799a4a6d00b8dc7281a8 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 6 Feb 2024 17:38:04 +0100 Subject: [PATCH 264/587] HybridBinarizer: fix buffer overflow regression --- core/src/HybridBinarizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index bf6baf027b..0214ce1782 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -251,7 +251,7 @@ static Matrix SmoothThresholds(Matrix&& in) last = i; } } - std::fill(last + 1, out.end(), *last); + std::fill(last + 1, out.end(), *(std::max(last, out.begin()))); return out; } From e107516f8accea34dc3e4574b88186f19b06417c Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 6 Feb 2024 18:35:48 +0100 Subject: [PATCH 265/587] tests: disable a spurious/platform dependent black box test result This is at least annoying and likely related to the recent HybridBinarizer reimplementation. Non-determinism via auto-vectorizer. sigh... --- test/blackbox/BlackboxTestRunner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index c75fdc63af..f20421eb5f 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -613,7 +613,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("microqrcode-1", "MicroQRCode", 16, { { 15, 15, 0 }, { 14, 14, 90 }, - { 15, 15, 180 }, + { 14, 14, 180 }, // ughs: 1 result is platform/compiler dependent (e.g. -march=core2 vs. haswell) { 15, 15, 270 }, { 9, 0, pure }, }); From e01f2d4f8f5550f63f25e236be7058327895ecc7 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 7 Feb 2024 00:28:17 +0100 Subject: [PATCH 266/587] ZXingReader: add `-single` option to `setMaxNumberOfSymbols(1)` --- example/ZXingReader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index a8d9595d44..724c3881ae 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -33,6 +33,7 @@ static void PrintUsage(const char* exePath) << " -noscale Don't try downscaled images during detection (faster)\n" << " -format \n" << " Only detect given format(s) (faster)\n" + << " -single Stop after the first barcode is detected (faster)\n" << " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n" << " -errors Include results with errors (like checksum error)\n" << " -binarizer \n" @@ -73,6 +74,8 @@ static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& o options.setTryInvert(false); } else if (is("-noscale")) { options.setTryDownscale(false); + } else if (is("-single")) { + options.setMaxNumberOfSymbols(1); } else if (is("-ispure")) { options.setIsPure(true); options.setBinarizer(Binarizer::FixedThreshold); From fb617df67f497e5da13c8cad7e5d5e71bcb36a3f Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 7 Feb 2024 01:00:11 +0100 Subject: [PATCH 267/587] cmake: drop the `-ffloat-store` handling I can't reproduce the issues that this setting was fixing when originally introduced. And here comes the kicker: removing it make the g++ Release build on my Core i9 machine 20% faster!?! (Insert eye popping emoji here.) The decode time for the full ReaderTest goes down from 5.2s to 4.2s. While the clang++ build takes 4.7s either way (does not support that setting at all). --- core/CMakeLists.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d63d9ecc63..bc74850f2c 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -460,13 +460,6 @@ target_compile_options (ZXing include (CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG ("-ffloat-store" COMPILER_NEEDS_FLOAT_STORE) -if (COMPILER_NEEDS_FLOAT_STORE) - target_compile_options(ZXing PRIVATE - -ffloat-store # same floating point precision in all optimization levels - ) -endif() - target_compile_features(ZXing PUBLIC cxx_std_17) target_link_libraries (ZXing PRIVATE Threads::Threads) From 47df03d5312f61730c5c863b9df1f876485302cf Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 7 Feb 2024 22:45:04 +0100 Subject: [PATCH 268/587] Pattern: switch from float to double precision This looses 2 but gains 8 successful black box tests. --- core/src/Pattern.h | 59 +++++++++++++--------------- core/src/qrcode/QRDetector.cpp | 4 +- test/blackbox/BlackboxTestRunner.cpp | 20 +++++----- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index e7eb2baad1..06bc4e7b9e 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -138,7 +138,7 @@ struct BarAndSpace using BarAndSpaceI = BarAndSpace; -template +template constexpr auto BarAndSpaceSum(const T* view) noexcept { BarAndSpace res; @@ -162,22 +162,20 @@ struct FixedPattern constexpr value_type operator[](int i) const noexcept { return _data[i]; } constexpr const value_type* data() const noexcept { return _data; } constexpr int size() const noexcept { return N; } - constexpr BarAndSpace sums() const noexcept { return BarAndSpaceSum(_data); } + constexpr BarAndSpace sums() const noexcept { return BarAndSpaceSum(_data); } }; template using FixedSparcePattern = FixedPattern; template -float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, - float minQuietZone = 0, float moduleSizeRef = 0) +double IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, + double minQuietZone = 0, double moduleSizeRef = 0) { if constexpr (E2E) { - using float_t = double; - - auto widths = BarAndSpaceSum(view.data()); + auto widths = BarAndSpaceSum(view.data()); auto sums = pattern.sums(); - BarAndSpace modSize = {widths[0] / sums[0], widths[1] / sums[1]}; + BarAndSpace modSize = {widths[0] / sums[0], widths[1] / sums[1]}; auto [m, M] = std::minmax(modSize[0], modSize[1]); if (M > 4 * m) // make sure module sizes of bars and spaces are not too far away from each other @@ -186,21 +184,20 @@ float IsPattern(const PatternView& view, const FixedPattern& pa if (minQuietZone && spaceInPixel < minQuietZone * modSize.space) return 0; - const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] / (2 + (LEN < 6)) + .5}; + const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] / (2 + (LEN < 6)) + .5}; for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * modSize[x]) > thr[x]) return 0; - const float_t moduleSize = (modSize[0] + modSize[1]) / 2; - return static_cast(moduleSize); + return (modSize[0] + modSize[1]) / 2; } - int width = view.sum(LEN); + double width = view.sum(LEN); if (SUM > LEN && width < SUM) return 0; - const float moduleSize = (float)width / SUM; + const auto moduleSize = width / SUM; if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) return 0; @@ -210,7 +207,7 @@ float IsPattern(const PatternView& view, const FixedPattern& pa // the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes. // TODO: review once we have upsampling in the binarizer in place. - const float threshold = moduleSizeRef * (0.5f + E2E * 0.25f) + 0.5f; + const auto threshold = moduleSizeRef * (0.5 + E2E * 0.25) + 0.5; for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * moduleSizeRef) > threshold) @@ -220,15 +217,15 @@ float IsPattern(const PatternView& view, const FixedPattern& pa } template -float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, - float minQuietZone = 0, float moduleSizeRef = 0) +double IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, + double minQuietZone = 0, double moduleSizeRef = 0) { // pattern contains the indices with the bars/spaces that need to be equally wide - int width = 0; + double width = 0; for (int x = 0; x < SUM; ++x) width += view[pattern[x]]; - const float moduleSize = (float)width / SUM; + const auto moduleSize = width / SUM; if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) return 0; @@ -238,7 +235,7 @@ float IsPattern(const PatternView& view, const FixedPattern& patte // the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes. // TODO: review once we have upsampling in the binarizer in place. - const float threshold = moduleSizeRef * (0.5f + RELAXED_THRESHOLD * 0.25f) + 0.5f; + const auto threshold = moduleSizeRef * (0.5 + RELAXED_THRESHOLD * 0.25) + 0.5; for (int x = 0; x < SUM; ++x) if (std::abs(view[pattern[x]] - moduleSizeRef) > threshold) @@ -248,8 +245,8 @@ float IsPattern(const PatternView& view, const FixedPattern& patte } template -bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, float minQuietZone, - float moduleSizeRef = 0.f) +bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, double minQuietZone, + double moduleSizeRef = 0) { int spaceInPixel = view.isAtLastBar() ? std::numeric_limits::max() : *view.end(); return IsPattern(view, pattern, spaceInPixel, minQuietZone, moduleSizeRef) != 0; @@ -273,7 +270,7 @@ PatternView FindLeftGuard(const PatternView& view, int minSize, Pred isGuard) template PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPattern& pattern, - float minQuietZone) + double minQuietZone) { return FindLeftGuard(view, std::max(minSize, LEN), [&pattern, minQuietZone](const PatternView& window, int spaceInPixel) { @@ -284,12 +281,12 @@ PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPatte template std::array NormalizedE2EPattern(const PatternView& view) { - float moduleSize = static_cast(view.sum(LEN)) / SUM; + double moduleSize = static_cast(view.sum(LEN)) / SUM; std::array e2e; for (int i = 0; i < LEN - 2; i++) { - float v = (view[i] + view[i + 1]) / moduleSize; - e2e[i] = int(v + .5f); + double v = (view[i] + view[i + 1]) / moduleSize; + e2e[i] = int(v + .5); } return e2e; @@ -298,14 +295,14 @@ std::array NormalizedE2EPattern(const PatternView& view) template std::array NormalizedPattern(const PatternView& view) { - float moduleSize = static_cast(view.sum(LEN)) / SUM; + double moduleSize = static_cast(view.sum(LEN)) / SUM; #if 1 int err = SUM; std::array is; - std::array rs; + std::array rs; for (int i = 0; i < LEN; i++) { - float v = view[i] / moduleSize; - is[i] = int(v + .5f); + double v = view[i] / moduleSize; + is[i] = int(v + .5); rs[i] = v - is[i]; err -= is[i]; } @@ -324,8 +321,8 @@ std::array NormalizedPattern(const PatternView& view) int min_v = view[0], min_i = 0; for (int i = 1; i < LEN; i++) { - float v = (view[i - 1] + view[i]) / moduleSize; - e2e[i] = int(v + .5f); + double v = (view[i - 1] + view[i]) / moduleSize; + e2e[i] = int(v + .5); if (view[i] < min_v) { min_v = view[i]; min_i = i; diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index faa3ba61d7..37476843bc 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -44,8 +44,8 @@ PatternView FindPattern(const PatternView& view) return FindLeftGuard(view, PATTERN.size(), [](const PatternView& view, int spaceInPixel) { // perform a fast plausability test for 1:1:3:1:1 pattern if (view[2] < 2 * std::max(view[0], view[4]) || view[2] < std::max(view[1], view[3])) - return 0.f; - return IsPattern(view, PATTERN, spaceInPixel, 0.1f); // the requires 4, here we accept almost 0 + return 0.; + return IsPattern(view, PATTERN, spaceInPixel, 0.1); // the requires 4, here we accept almost 0 }); } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index f20421eb5f..b676138e8b 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -452,7 +452,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("ean13-4", "EAN-13", 22, { { 6, 13, 0 }, - { 8, 13, 180 }, + { 7, 13, 180 }, }); runTests("ean13-extension-1", "EAN-13", 5, { @@ -479,27 +479,27 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("upca-1", "UPC-A", 12, { - { 9, 12, 0 }, + { 10, 12, 0 }, { 11, 12, 180 }, }); runTests("upca-2", "UPC-A", 36, { { 17, 22, 0 }, - { 16, 22, 180 }, + { 17, 22, 180 }, }); runTests("upca-3", "UPC-A", 21, { - { 7, 10, 0 }, - { 8, 10, 180 }, + { 7, 11, 0 }, + { 8, 11, 180 }, }); runTests("upca-4", "UPC-A", 19, { - { 8, 12, 0 }, - { 9, 12, 180 }, + { 8, 12, 0, 1, 0 }, + { 9, 12, 0, 1, 180 }, }); runTests("upca-5", "UPC-A", 32, { - { 17, 20, 0 }, + { 18, 20, 0 }, { 18, 20, 180 }, }); @@ -515,8 +515,8 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("upce-2", "UPC-E", 28, { - { 17, 22, 0, 1, 0 }, - { 20, 22, 1, 1, 180 }, + { 18, 22, 0, 1, 0 }, + { 19, 22, 1, 1, 180 }, }); runTests("upce-3", "UPC-E", 11, { From 19470ad786d534ba78025ae88b82013820721c26 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 8 Feb 2024 00:48:47 +0100 Subject: [PATCH 269/587] ReadBarcode: help gcc's auto-vectorizer with the RGB->Lum conversion This makes the `RGBToLum` loop 2-3 times faster with gcc but does currently not help llvm. No idea what msvc is able to make of this. For very specific use cases, like `ZXingReader samples/falsepositives-1/09.png -s -format qrcode -nos -bin global` this can make a substantial difference (.43ms instead of .76ms). --- core/src/ReadBarcode.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index ab8108c1ba..7525a35768 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -107,7 +107,14 @@ ImageView SetupLumImageView(ImageView iv, LumImage& lum, const ReaderOptions& op throw std::invalid_argument("Invalid image format"); if (opts.binarizer() == Binarizer::GlobalHistogram || opts.binarizer() == Binarizer::LocalAverage) { - if (iv.format() != ImageFormat::Lum) { + // manually spell out the 3 most common pixel formats to get at least gcc to vectorize the code + if (iv.format() == ImageFormat::RGB && iv.pixStride() == 3) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); }); + } else if (iv.format() == ImageFormat::RGBX && iv.pixStride() == 4) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); }); + } else if (iv.format() == ImageFormat::BGR && iv.pixStride() == 3) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[2], src[1], src[0]); }); + } else if (iv.format() != ImageFormat::Lum) { lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())]( const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); }); } else if (iv.pixStride() != 1) { From 50d9d66c3158f2faa47d179d06eb22606111b8fa Mon Sep 17 00:00:00 2001 From: ISNing Date: Thu, 8 Feb 2024 15:29:17 +0800 Subject: [PATCH 270/587] publish-android.yml: fix typo of ci name --- .github/workflows/publish-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index eb789c3d26..f586a58aae 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -1,4 +1,4 @@ -name: publish-adroid +name: publish-android on: # release: From 6ab17fdce1b8f0e605c1eb0266da63eca261529a Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 8 Feb 2024 22:06:09 +0100 Subject: [PATCH 271/587] ODCode128Writer: make sure a std::exception::what() string is valid UTF8 This should fix the root cause of the iOS crash discussed in the #717. --- core/src/oned/ODCode128Writer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/oned/ODCode128Writer.cpp b/core/src/oned/ODCode128Writer.cpp index 8cc9b41d4a..1f7b583194 100644 --- a/core/src/oned/ODCode128Writer.cpp +++ b/core/src/oned/ODCode128Writer.cpp @@ -153,7 +153,7 @@ Code128Writer::encode(const std::wstring& contents, int width, int height) const default: if (c > 127) { // support for FNC4 isn't implemented, no full Latin-1 character set available at the moment - throw std::invalid_argument(std::string("Bad character in input: ") + static_cast(c)); + throw std::invalid_argument("Bad character in input: " + ToUtf8(contents.substr(i, 1))); } } } From 0eaeb1d8ef37b0d0e5a78ef2c5c530a331380c1e Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 8 Feb 2024 22:09:45 +0100 Subject: [PATCH 272/587] ios: add ASCII fallback if SetNSError encounters invalid UTF8 error string This adds a layer of security to prevent nil crashes in case there are more cases with invalid UTF8 error messages. This supersedes #717. --- wrappers/ios/Sources/Wrapper/ZXIErrors.mm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm index 6b88bcb1de..bfc5f0da8c 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm +++ b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm @@ -12,10 +12,9 @@ void SetNSError(NSError *__autoreleasing _Nullable* error, } NSString *errorDescription = @"Unknown C++ error"; if (message && strlen(message) > 0) { - try { - errorDescription = [NSString stringWithUTF8String: message]; - } catch (NSException *exception) { - errorDescription = @"Unknown ObjC error"; + errorDescription = [NSString stringWithUTF8String: message]; + if (errorDescription == nil) { + [NSString stringWithCString: message encoding: NSASCIIStringEncoding]; } } NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: errorDescription }; From b80310ff78f7dd87dc80011793d62432c5fa1e53 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 8 Feb 2024 22:47:41 +0100 Subject: [PATCH 273/587] ios: make my last commit actually have any effect at all As @markusfisch was so kind to point out: actually assigning the new NSString to somewhere might be of use. --- wrappers/ios/Sources/Wrapper/ZXIErrors.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm index bfc5f0da8c..6ebfa20397 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm +++ b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm @@ -14,7 +14,8 @@ void SetNSError(NSError *__autoreleasing _Nullable* error, if (message && strlen(message) > 0) { errorDescription = [NSString stringWithUTF8String: message]; if (errorDescription == nil) { - [NSString stringWithCString: message encoding: NSASCIIStringEncoding]; + errorDescription = [NSString stringWithCString: message + encoding: NSASCIIStringEncoding]; } } NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: errorDescription }; From 44c2122da05c33789ac557df0e7d9ed29e4c93bc Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 14:29:50 +0100 Subject: [PATCH 274/587] rust: remove clap from dependencies list for demo This fixes a build issue because the latest clap crate requires a more modern rustc than Ubuntu currently provides. --- wrappers/rust/Cargo.toml | 1 - wrappers/rust/examples/demo.rs | 23 ++++++++--------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 9e403551f6..0e42f223ee 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -27,7 +27,6 @@ image = {version = "0.24.8", optional = true} [dev-dependencies] cfg-if = "1.0.0" anyhow = "1.0.79" -clap = {version = "4.4.13", features = ["derive"]} image = {version = "0.24.8"} [build-dependencies] diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index 1a235c6cbf..2fcad932ac 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -3,33 +3,26 @@ */ // SPDX-License-Identifier: Apache-2.0 -use clap::Parser; -use std::path::PathBuf; use zxing_cpp::*; -#[derive(Parser)] -struct Cli { - filename: PathBuf, - formats: Option, - fast: bool, -} - fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); + let filename = std::env::args().nth(1).expect("no image file name provided"); + let formats = std::env::args().nth(2); + let fast = std::env::args().nth(3).is_some(); - let image = image::open(&cli.filename)?; + let image = image::open(&filename)?; #[cfg(not(feature = "image"))] let lum_img = image.into_luma8(); #[cfg(not(feature = "image"))] let iv = ImageView::from_slice(&lum_img, lum_img.width(), lum_img.height(), ImageFormat::Lum)?; - let formats = barcode_formats_from_string(cli.formats.unwrap_or_default())?; + let formats = barcode_formats_from_string(formats.unwrap_or_default())?; let opts = ReaderOptions::default() .formats(formats) - .try_harder(!cli.fast) - .try_invert(!cli.fast) - .try_rotate(!cli.fast); + .try_harder(!fast) + .try_invert(!fast) + .try_rotate(!fast); #[cfg(feature = "image")] let barcodes = read_barcodes(&image, &opts)?; From c0298930cc818bb2ae3e33ca1b522b5ad136e07d Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 15:09:59 +0100 Subject: [PATCH 275/587] rust: replace io.Error with custom Error type based on thiserror --- wrappers/rust/Cargo.toml | 7 +++--- wrappers/rust/src/lib.rs | 52 ++++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 0e42f223ee..7f1bbd8d36 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -20,13 +20,14 @@ image = ["dep:image"] bundled = [] [dependencies] -paste = "1.0.14" +thiserror = "1.0" +paste = "1.0" flagset = "0.4.4" image = {version = "0.24.8", optional = true} [dev-dependencies] -cfg-if = "1.0.0" -anyhow = "1.0.79" +cfg-if = "1.0" +anyhow = "1.0" image = {version = "0.24.8"} [build-dependencies] diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 0adea923a1..9df557eff9 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -21,14 +21,40 @@ use bindings::*; use flagset::{flags, FlagSet}; use paste::paste; -use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString}; +use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString, NulError}; use std::fmt::{Display, Formatter}; -use std::io::ErrorKind; use std::marker::PhantomData; use std::mem::transmute; use std::rc::Rc; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + InvalidInput(String), + + #[error("NulError from CString::new")] + NulError(#[from] NulError), + // + // #[error("data store disconnected")] + // IOError(#[from] std::io::Error), + // #[error("the data for key `{0}` is not available")] + // Redaction(String), + // #[error("invalid header (expected {expected:?}, found {found:?})")] + // InvalidHeader { + // expected: String, + // found: String, + // }, + // #[error("unknown data store error")] + // Unknown, +} -pub type Error = std::io::Error; +// see https://github.com/dtolnay/thiserror/issues/62 +impl From for Error { + fn from(_: std::convert::Infallible) -> Self { + unreachable!() + } +} fn c2r_str(str: *mut c_char) -> String { let mut res = String::new(); @@ -51,7 +77,7 @@ fn c2r_vec(buf: *mut u8, len: c_int) -> Vec { fn last_error() -> Error { match unsafe { zxing_LastErrorMsg().as_mut() } { None => panic!("Internal error: zxing_LastErrorMsg() returned NULL"), - Some(error) => Error::new(ErrorKind::InvalidInput, c2r_str(error)), + Some(error) => Error::InvalidInput(c2r_str(error)), } } @@ -59,7 +85,7 @@ macro_rules! last_error_or { ($expr:expr) => { match unsafe { zxing_LastErrorMsg().as_mut() } { None => Ok($expr), - Some(error) => Err(Error::new(ErrorKind::InvalidInput, c2r_str(error))), + Some(error) => Err(Error::InvalidInput(c2r_str(error))), } }; } @@ -134,8 +160,7 @@ impl<'a> From<&'a ImageView<'a>> for ImageView<'a> { impl<'a> ImageView<'a> { fn try_into_int>(val: T) -> Result { - val.try_into() - .map_err(|_| Error::new(ErrorKind::InvalidInput, "Could not convert Integer into c_int.")) + val.try_into().map_err(|_| Error::InvalidInput("Could not convert Integer into c_int.".to_string())) } /// Constructs an ImageView from a raw pointer and the width/height (in pixels) @@ -223,7 +248,7 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { }; match format { Some(format) => Ok(ImageView::from_slice(img.as_bytes(), img.width(), img.height(), format)?), - None => Err(Error::new(ErrorKind::InvalidInput, "Invalid image format (must be either luma8|rgb8|rgba8)")), + None => Err(Error::InvalidInput("Invalid image format (must be either luma8|rgb8|rgba8)".to_string())), } } } @@ -362,10 +387,13 @@ pub fn barcode_formats_from_string(str: impl AsRef) -> Result(image: impl TryInto>, opts: impl AsRef) -> Result, Error> { - let iv_: ImageView = image - .try_into() - .map_err(|_| Error::new(ErrorKind::InvalidInput, "Failed to image.try_into::()"))?; +pub fn read_barcodes<'a, IV, RO>(image: IV, opts: RO) -> Result, Error> +where + IV: TryInto>, + IV::Error: Into, + RO: AsRef, +{ + let iv_: ImageView = image.try_into().map_err(Into::into)?; unsafe { let results = zxing_ReadBarcodes((iv_.0).0, opts.as_ref().0); if !results.is_null() { From a9c81732a1a90f37f322166524f5c555dc2c881c Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 23:07:47 +0100 Subject: [PATCH 276/587] rust: minor README improvement --- wrappers/rust/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index 3ef352c3c4..65c28dcc18 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -52,7 +52,7 @@ zxing-cpp provides features that are behind [Cargo features](https://doc.rust-la They are: * `bundled` uses a bundled version of the [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) c++ library. -* [`image`](https://crates.io/crates/image) allows convenient `ImageView` construction from `GreyImage` and `DynamicImage`. +* [`image`](https://crates.io/crates/image) allows convenient/implicit `ImageView` construction from `GreyImage` and `DynamicImage`. ## Benchmarking From b1d46f972deec1f2f88f7f94c17d2fd264be2712 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 23:48:31 +0100 Subject: [PATCH 277/587] c-API: replace `zxing_` prefix with `ZXing_` Apart from the prefix, the rest of the naming is strictly following the C++ symbol names using _ to replace wither :: or .. So in that sense the prefix should be `ZXing_` to replace the `ZXing` c++ namespace. `ZXing::ReaderOptions::tryHarder` becomes `ZXing_ReaderOptions_tryHarder`. For more info see also https://github.com/zxing-cpp/zxing-cpp/discussions/720 --- core/CMakeLists.txt | 8 +- core/src/{zxing-c.cpp => ZXingC.cpp} | 84 ++++----- core/src/ZXingC.h | 228 +++++++++++++++++++++++ core/src/zxing-c.h | 228 ----------------------- wrappers/c/CMakeLists.txt | 6 +- wrappers/c/README.md | 37 ++-- wrappers/c/ZXingCTest.c | 108 +++++++++++ wrappers/c/zxing-c-test.c | 108 ----------- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 236 ++++++++++++------------ wrappers/rust/build.rs | 2 +- wrappers/rust/src/bindings.rs | 264 +++++++++++++-------------- wrappers/rust/src/lib.rs | 70 +++---- 12 files changed, 690 insertions(+), 689 deletions(-) rename core/src/{zxing-c.cpp => ZXingC.cpp} (63%) create mode 100644 core/src/ZXingC.h delete mode 100644 core/src/zxing-c.h create mode 100644 wrappers/c/ZXingCTest.c delete mode 100644 wrappers/c/zxing-c-test.c diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index bc74850f2c..2afedb078a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -137,8 +137,8 @@ if (BUILD_READERS) src/ThresholdBinarizer.h src/WhiteRectDetector.h src/WhiteRectDetector.cpp - $<$:src/zxing-c.h> - $<$:src/zxing-c.cpp> + $<$:src/ZXingC.h> + $<$:src/ZXingC.cpp> ) endif() if (BUILD_WRITERS) @@ -177,7 +177,7 @@ if (BUILD_READERS) src/ReaderOptions.h src/Result.h src/StructuredAppend.h - $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/zxing-c.h> + $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingC.h> ) endif() if (BUILD_WRITERS) @@ -481,7 +481,7 @@ endif() set_target_properties(ZXing PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") set(PRECOMPILE_HEADERS ${PUBLIC_HEADERS}) -list(REMOVE_ITEM PRECOMPILE_HEADERS "$<$:${CMAKE_CURRENT_SOURCE_DIR}/src/zxing-c.h>") +list(REMOVE_ITEM PRECOMPILE_HEADERS "$<$:${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingC.h>") target_precompile_headers(ZXing PRIVATE ${PRECOMPILE_HEADERS}) set_source_files_properties(src/libzueci/zueci.c PROPERTIES SKIP_PRECOMPILE_HEADERS ON) set_source_files_properties(src/DecodeHints.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON) diff --git a/core/src/zxing-c.cpp b/core/src/ZXingC.cpp similarity index 63% rename from core/src/zxing-c.cpp rename to core/src/ZXingC.cpp index f141130b6d..05579f29f0 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/ZXingC.cpp @@ -4,7 +4,7 @@ */ // SPDX-License-Identifier: Apache-2.0 -#include "zxing-c.h" +#include "ZXingC.h" #include "ReadBarcode.h" @@ -48,7 +48,7 @@ static uint8_t* copy(const ByteArray& ba, int* len) return ret; } -static std::tuple ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts, +static std::tuple ReadBarcodesAndSetLastError(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts, int maxSymbols) { try { @@ -73,7 +73,7 @@ extern "C" { * ZXing/ImageView.h */ -zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, +ZXing_ImageView* ZXing_ImageView_new(const uint8_t* data, int width, int height, ZXing_ImageFormat format, int rowStride, int pixStride) { ImageFormat cppformat = static_cast(format); @@ -85,7 +85,7 @@ zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, return NULL; } -zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, zxing_ImageFormat format, +ZXing_ImageView* ZXing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, ZXing_ImageFormat format, int rowStride, int pixStride) { ImageFormat cppformat = static_cast(format); @@ -97,17 +97,17 @@ zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int return NULL; } -void zxing_ImageView_delete(zxing_ImageView* iv) +void ZXing_ImageView_delete(ZXing_ImageView* iv) { delete iv; } -void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height) +void ZXing_ImageView_crop(ZXing_ImageView* iv, int left, int top, int width, int height) { *iv = iv->cropped(left, top, width, height); } -void zxing_ImageView_rotate(zxing_ImageView* iv, int degree) +void ZXing_ImageView_rotate(ZXing_ImageView* iv, int degree) { *iv = iv->rotated(degree); } @@ -116,29 +116,29 @@ void zxing_ImageView_rotate(zxing_ImageView* iv, int degree) * ZXing/BarcodeFormat.h */ -zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str) +ZXing_BarcodeFormats ZXing_BarcodeFormatsFromString(const char* str) { if (!str) return {}; try { auto format = BarcodeFormatsFromString(str); - return static_cast(transmute_cast(format)); + return static_cast(transmute_cast(format)); } catch (std::exception& e) { lastErrorMsg = e.what(); } catch (...) { lastErrorMsg = "Unknown error"; } - return zxing_BarcodeFormat_Invalid; + return ZXing_BarcodeFormat_Invalid; } -zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str) +ZXing_BarcodeFormat ZXing_BarcodeFormatFromString(const char* str) { - zxing_BarcodeFormat res = zxing_BarcodeFormatsFromString(str); - return BitHacks::CountBitsSet(res) == 1 ? res : zxing_BarcodeFormat_Invalid; + ZXing_BarcodeFormat res = ZXing_BarcodeFormatsFromString(str); + return BitHacks::CountBitsSet(res) == 1 ? res : ZXing_BarcodeFormat_Invalid; } -char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) +char* ZXing_BarcodeFormatToString(ZXing_BarcodeFormat format) { return copy(ToString(static_cast(format))); } @@ -147,19 +147,19 @@ char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) * ZXing/ReaderOptions.h */ -zxing_ReaderOptions* zxing_ReaderOptions_new() +ZXing_ReaderOptions* ZXing_ReaderOptions_new() { return new ReaderOptions(); } -void zxing_ReaderOptions_delete(zxing_ReaderOptions* opts) +void ZXing_ReaderOptions_delete(ZXing_ReaderOptions* opts) { delete opts; } #define ZX_PROPERTY(TYPE, GETTER, SETTER) \ - TYPE zxing_ReaderOptions_get##SETTER(const zxing_ReaderOptions* opts) { return opts->GETTER(); } \ - void zxing_ReaderOptions_set##SETTER(zxing_ReaderOptions* opts, TYPE val) { opts->set##SETTER(val); } + TYPE ZXing_ReaderOptions_get##SETTER(const ZXing_ReaderOptions* opts) { return opts->GETTER(); } \ + void ZXing_ReaderOptions_set##SETTER(ZXing_ReaderOptions* opts, TYPE val) { opts->set##SETTER(val); } ZX_PROPERTY(bool, tryHarder, TryHarder) ZX_PROPERTY(bool, tryRotate, TryRotate) @@ -170,20 +170,20 @@ ZX_PROPERTY(bool, returnErrors, ReturnErrors) ZX_PROPERTY(int, minLineCount, MinLineCount) ZX_PROPERTY(int, maxNumberOfSymbols, MaxNumberOfSymbols) -void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeFormats formats) +void ZXing_ReaderOptions_setFormats(ZXing_ReaderOptions* opts, ZXing_BarcodeFormats formats) { opts->setFormats(static_cast(formats)); } -zxing_BarcodeFormats zxing_ReaderOptions_getFormats(const zxing_ReaderOptions* opts) +ZXing_BarcodeFormats ZXing_ReaderOptions_getFormats(const ZXing_ReaderOptions* opts) { auto v = opts->formats(); - return transmute_cast(v); + return transmute_cast(v); } #define ZX_ENUM_PROPERTY(TYPE, GETTER, SETTER) \ - zxing_##TYPE zxing_ReaderOptions_get##SETTER(const zxing_ReaderOptions* opts) { return static_cast(opts->GETTER()); } \ - void zxing_ReaderOptions_set##SETTER(zxing_ReaderOptions* opts, zxing_##TYPE val) { opts->set##SETTER(static_cast(val)); } + ZXing_##TYPE ZXing_ReaderOptions_get##SETTER(const ZXing_ReaderOptions* opts) { return static_cast(opts->GETTER()); } \ + void ZXing_ReaderOptions_set##SETTER(ZXing_ReaderOptions* opts, ZXing_##TYPE val) { opts->set##SETTER(static_cast(val)); } ZX_ENUM_PROPERTY(Binarizer, binarizer, Binarizer) ZX_ENUM_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, EanAddOnSymbol) @@ -193,46 +193,46 @@ ZX_ENUM_PROPERTY(TextMode, textMode, TextMode) * ZXing/Result.h */ -char* zxing_ContentTypeToString(zxing_ContentType type) +char* ZXing_ContentTypeToString(ZXing_ContentType type) { return copy(ToString(static_cast(type))); } -char* zxing_PositionToString(zxing_Position position) +char* ZXing_PositionToString(ZXing_Position position) { return copy(ToString(transmute_cast(position))); } -bool zxing_Barcode_isValid(const zxing_Barcode* barcode) +bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode) { return barcode != NULL && barcode->isValid(); } -char* zxing_Barcode_errorMsg(const zxing_Barcode* barcode) +char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode) { return copy(ToString(barcode->error())); } -uint8_t* zxing_Barcode_bytes(const zxing_Barcode* barcode, int* len) +uint8_t* ZXing_Barcode_bytes(const ZXing_Barcode* barcode, int* len) { return copy(barcode->bytes(), len); } -uint8_t* zxing_Barcode_bytesECI(const zxing_Barcode* barcode, int* len) +uint8_t* ZXing_Barcode_bytesECI(const ZXing_Barcode* barcode, int* len) { return copy(barcode->bytesECI(), len); } #define ZX_GETTER(TYPE, GETTER, TRANS) \ - TYPE zxing_Barcode_##GETTER(const zxing_Barcode* barcode) { return static_cast(TRANS(barcode->GETTER())); } + TYPE ZXing_Barcode_##GETTER(const ZXing_Barcode* barcode) { return static_cast(TRANS(barcode->GETTER())); } -ZX_GETTER(zxing_BarcodeFormat, format,) -ZX_GETTER(zxing_ContentType, contentType,) +ZX_GETTER(ZXing_BarcodeFormat, format,) +ZX_GETTER(ZXing_ContentType, contentType,) ZX_GETTER(char*, text, copy) ZX_GETTER(char*, ecLevel, copy) ZX_GETTER(char*, symbologyIdentifier, copy) -ZX_GETTER(zxing_Position, position, transmute_cast) +ZX_GETTER(ZXing_Position, position, transmute_cast) ZX_GETTER(int, orientation,) ZX_GETTER(bool, hasECI,) @@ -245,41 +245,41 @@ ZX_GETTER(int, lineCount,) * ZXing/ReadBarcode.h */ -zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +ZXing_Barcode* ZXing_ReadBarcode(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts) { auto [res, ok] = ReadBarcodesAndSetLastError(iv, opts, 1); return !res.empty() ? new Result(std::move(res.front())) : NULL; } -zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +ZXing_Barcodes* ZXing_ReadBarcodes(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts) { auto [res, ok] = ReadBarcodesAndSetLastError(iv, opts, 0); return !res.empty() || ok ? new Results(std::move(res)) : NULL; } -void zxing_Barcode_delete(zxing_Barcode* barcode) +void ZXing_Barcode_delete(ZXing_Barcode* barcode) { delete barcode; } -void zxing_Barcodes_delete(zxing_Barcodes* barcodes) +void ZXing_Barcodes_delete(ZXing_Barcodes* barcodes) { delete barcodes; } -int zxing_Barcodes_size(const zxing_Barcodes* barcodes) +int ZXing_Barcodes_size(const ZXing_Barcodes* barcodes) { return barcodes ? Size(*barcodes) : 0; } -const zxing_Barcode* zxing_Barcodes_at(const zxing_Barcodes* barcodes, int i) +const ZXing_Barcode* ZXing_Barcodes_at(const ZXing_Barcodes* barcodes, int i) { if (!barcodes || i < 0 || i >= Size(*barcodes)) return NULL; return &(*barcodes)[i]; } -zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i) +ZXing_Barcode* ZXing_Barcodes_move(ZXing_Barcodes* barcodes, int i) { if (!barcodes || i < 0 || i >= Size(*barcodes)) return NULL; @@ -287,7 +287,7 @@ zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i) return new Result(std::move((*barcodes)[i])); } -char* zxing_LastErrorMsg() +char* ZXing_LastErrorMsg() { if (lastErrorMsg.empty()) return NULL; @@ -295,7 +295,7 @@ char* zxing_LastErrorMsg() return copy(std::exchange(lastErrorMsg, {})); } -void zxing_free(void* ptr) +void ZXing_free(void* ptr) { free(ptr); } diff --git a/core/src/ZXingC.h b/core/src/ZXingC.h new file mode 100644 index 0000000000..63d249ed6c --- /dev/null +++ b/core/src/ZXingC.h @@ -0,0 +1,228 @@ +/* +* Copyright 2023 siiky +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#ifndef _ZXING_C_H +#define _ZXING_C_H + +#include +#include + +#ifdef __cplusplus + +#include "ReaderOptions.h" +#include "ImageView.h" +#include "Result.h" + +typedef ZXing::ImageView ZXing_ImageView; +typedef ZXing::ReaderOptions ZXing_ReaderOptions; +typedef ZXing::Result ZXing_Barcode; +typedef ZXing::Results ZXing_Barcodes; + +extern "C" +{ +#else + +typedef struct ZXing_ImageView ZXing_ImageView; +typedef struct ZXing_ReaderOptions ZXing_ReaderOptions; +typedef struct ZXing_Barcode ZXing_Barcode; +typedef struct ZXing_Barcodes ZXing_Barcodes; + +#endif + +/* + * ZXing/ImageView.h + */ + +typedef enum { + ZXing_ImageFormat_None = 0, + ZXing_ImageFormat_Lum = 0x01000000, + ZXing_ImageFormat_RGB = 0x03000102, + ZXing_ImageFormat_BGR = 0x03020100, + ZXing_ImageFormat_RGBX = 0x04000102, + ZXing_ImageFormat_XRGB = 0x04010203, + ZXing_ImageFormat_BGRX = 0x04020100, + ZXing_ImageFormat_XBGR = 0x04030201, +} ZXing_ImageFormat; + +ZXing_ImageView* ZXing_ImageView_new(const uint8_t* data, int width, int height, ZXing_ImageFormat format, int rowStride, + int pixStride); +ZXing_ImageView* ZXing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, ZXing_ImageFormat format, + int rowStride, int pixStride); +void ZXing_ImageView_delete(ZXing_ImageView* iv); + +void ZXing_ImageView_crop(ZXing_ImageView* iv, int left, int top, int width, int height); +void ZXing_ImageView_rotate(ZXing_ImageView* iv, int degree); + +/* + * ZXing/BarcodeFormat.h + */ + +typedef enum +{ + ZXing_BarcodeFormat_None = 0, + ZXing_BarcodeFormat_Aztec = (1 << 0), + ZXing_BarcodeFormat_Codabar = (1 << 1), + ZXing_BarcodeFormat_Code39 = (1 << 2), + ZXing_BarcodeFormat_Code93 = (1 << 3), + ZXing_BarcodeFormat_Code128 = (1 << 4), + ZXing_BarcodeFormat_DataBar = (1 << 5), + ZXing_BarcodeFormat_DataBarExpanded = (1 << 6), + ZXing_BarcodeFormat_DataMatrix = (1 << 7), + ZXing_BarcodeFormat_EAN8 = (1 << 8), + ZXing_BarcodeFormat_EAN13 = (1 << 9), + ZXing_BarcodeFormat_ITF = (1 << 10), + ZXing_BarcodeFormat_MaxiCode = (1 << 11), + ZXing_BarcodeFormat_PDF417 = (1 << 12), + ZXing_BarcodeFormat_QRCode = (1 << 13), + ZXing_BarcodeFormat_UPCA = (1 << 14), + ZXing_BarcodeFormat_UPCE = (1 << 15), + ZXing_BarcodeFormat_MicroQRCode = (1 << 16), + ZXing_BarcodeFormat_RMQRCode = (1 << 17), + ZXing_BarcodeFormat_DXFilmEdge = (1 << 18), + + ZXing_BarcodeFormat_LinearCodes = ZXing_BarcodeFormat_Codabar | ZXing_BarcodeFormat_Code39 | ZXing_BarcodeFormat_Code93 + | ZXing_BarcodeFormat_Code128 | ZXing_BarcodeFormat_EAN8 | ZXing_BarcodeFormat_EAN13 + | ZXing_BarcodeFormat_ITF | ZXing_BarcodeFormat_DataBar | ZXing_BarcodeFormat_DataBarExpanded + | ZXing_BarcodeFormat_DXFilmEdge | ZXing_BarcodeFormat_UPCA | ZXing_BarcodeFormat_UPCE, + ZXing_BarcodeFormat_MatrixCodes = ZXing_BarcodeFormat_Aztec | ZXing_BarcodeFormat_DataMatrix | ZXing_BarcodeFormat_MaxiCode + | ZXing_BarcodeFormat_PDF417 | ZXing_BarcodeFormat_QRCode | ZXing_BarcodeFormat_MicroQRCode + | ZXing_BarcodeFormat_RMQRCode, + ZXing_BarcodeFormat_Any = ZXing_BarcodeFormat_LinearCodes | ZXing_BarcodeFormat_MatrixCodes, + + ZXing_BarcodeFormat_Invalid = 0xFFFFFFFFu /* return value when BarcodeFormatsFromString() throws */ +} ZXing_BarcodeFormat; + +typedef ZXing_BarcodeFormat ZXing_BarcodeFormats; + +ZXing_BarcodeFormats ZXing_BarcodeFormatsFromString(const char* str); +ZXing_BarcodeFormat ZXing_BarcodeFormatFromString(const char* str); +char* ZXing_BarcodeFormatToString(ZXing_BarcodeFormat format); + +/* + * ZXing/ReaderOptions.h + */ + +typedef enum +{ + ZXing_Binarizer_LocalAverage, + ZXing_Binarizer_GlobalHistogram, + ZXing_Binarizer_FixedThreshold, + ZXing_Binarizer_BoolCast, +} ZXing_Binarizer; + +typedef enum +{ + ZXing_EanAddOnSymbol_Ignore, + ZXing_EanAddOnSymbol_Read, + ZXing_EanAddOnSymbol_Require, +} ZXing_EanAddOnSymbol; + +typedef enum +{ + ZXing_TextMode_Plain, + ZXing_TextMode_ECI, + ZXing_TextMode_HRI, + ZXing_TextMode_Hex, + ZXing_TextMode_Escaped, +} ZXing_TextMode; + +ZXing_ReaderOptions* ZXing_ReaderOptions_new(); +void ZXing_ReaderOptions_delete(ZXing_ReaderOptions* opts); + +void ZXing_ReaderOptions_setTryHarder(ZXing_ReaderOptions* opts, bool tryHarder); +void ZXing_ReaderOptions_setTryRotate(ZXing_ReaderOptions* opts, bool tryRotate); +void ZXing_ReaderOptions_setTryInvert(ZXing_ReaderOptions* opts, bool tryInvert); +void ZXing_ReaderOptions_setTryDownscale(ZXing_ReaderOptions* opts, bool tryDownscale); +void ZXing_ReaderOptions_setIsPure(ZXing_ReaderOptions* opts, bool isPure); +void ZXing_ReaderOptions_setReturnErrors(ZXing_ReaderOptions* opts, bool returnErrors); +void ZXing_ReaderOptions_setFormats(ZXing_ReaderOptions* opts, ZXing_BarcodeFormats formats); +void ZXing_ReaderOptions_setBinarizer(ZXing_ReaderOptions* opts, ZXing_Binarizer binarizer); +void ZXing_ReaderOptions_setEanAddOnSymbol(ZXing_ReaderOptions* opts, ZXing_EanAddOnSymbol eanAddOnSymbol); +void ZXing_ReaderOptions_setTextMode(ZXing_ReaderOptions* opts, ZXing_TextMode textMode); +void ZXing_ReaderOptions_setMinLineCount(ZXing_ReaderOptions* opts, int n); +void ZXing_ReaderOptions_setMaxNumberOfSymbols(ZXing_ReaderOptions* opts, int n); + +bool ZXing_ReaderOptions_getTryHarder(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getTryRotate(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getTryInvert(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getTryDownscale(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getIsPure(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getReturnErrors(const ZXing_ReaderOptions* opts); +ZXing_BarcodeFormats ZXing_ReaderOptions_getFormats(const ZXing_ReaderOptions* opts); +ZXing_Binarizer ZXing_ReaderOptions_getBinarizer(const ZXing_ReaderOptions* opts); +ZXing_EanAddOnSymbol ZXing_ReaderOptions_getEanAddOnSymbol(const ZXing_ReaderOptions* opts); +ZXing_TextMode ZXing_ReaderOptions_getTextMode(const ZXing_ReaderOptions* opts); +int ZXing_ReaderOptions_getMinLineCount(const ZXing_ReaderOptions* opts); +int ZXing_ReaderOptions_getMaxNumberOfSymbols(const ZXing_ReaderOptions* opts); + +/* + * ZXing/Result.h + */ + +typedef enum +{ + ZXing_ContentType_Text, + ZXing_ContentType_Binary, + ZXing_ContentType_Mixed, + ZXing_ContentType_GS1, + ZXing_ContentType_ISO15434, + ZXing_ContentType_UnknownECI +} ZXing_ContentType; + +char* ZXing_ContentTypeToString(ZXing_ContentType type); + +typedef struct ZXing_PointI +{ + int x, y; +} ZXing_PointI; + +typedef struct ZXing_Position +{ + ZXing_PointI topLeft, topRight, bottomRight, bottomLeft; +} ZXing_Position; + +char* ZXing_PositionToString(ZXing_Position position); + +bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode); +char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode); +ZXing_BarcodeFormat ZXing_Barcode_format(const ZXing_Barcode* barcode); +ZXing_ContentType ZXing_Barcode_contentType(const ZXing_Barcode* barcode); +uint8_t* ZXing_Barcode_bytes(const ZXing_Barcode* barcode, int* len); +uint8_t* ZXing_Barcode_bytesECI(const ZXing_Barcode* barcode, int* len); +char* ZXing_Barcode_text(const ZXing_Barcode* barcode); +char* ZXing_Barcode_ecLevel(const ZXing_Barcode* barcode); +char* ZXing_Barcode_symbologyIdentifier(const ZXing_Barcode* barcode); +ZXing_Position ZXing_Barcode_position(const ZXing_Barcode* barcode); +int ZXing_Barcode_orientation(const ZXing_Barcode* barcode); +bool ZXing_Barcode_hasECI(const ZXing_Barcode* barcode); +bool ZXing_Barcode_isInverted(const ZXing_Barcode* barcode); +bool ZXing_Barcode_isMirrored(const ZXing_Barcode* barcode); +int ZXing_Barcode_lineCount(const ZXing_Barcode* barcode); + +/* + * ZXing/ReadBarcode.h + */ + +/** Note: opts is optional, i.e. it can be NULL, which will imply default settings. */ +ZXing_Barcode* ZXing_ReadBarcode(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts); +ZXing_Barcodes* ZXing_ReadBarcodes(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts); + +void ZXing_Barcode_delete(ZXing_Barcode* barcode); +void ZXing_Barcodes_delete(ZXing_Barcodes* barcodes); + +int ZXing_Barcodes_size(const ZXing_Barcodes* barcodes); +const ZXing_Barcode* ZXing_Barcodes_at(const ZXing_Barcodes* barcodes, int i); +ZXing_Barcode* ZXing_Barcodes_move(ZXing_Barcodes* barcodes, int i); + +char* ZXing_LastErrorMsg(); + +void ZXing_free(void* ptr); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZXING_C_H */ diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h deleted file mode 100644 index 75207aa679..0000000000 --- a/core/src/zxing-c.h +++ /dev/null @@ -1,228 +0,0 @@ -/* -* Copyright 2023 siiky -* Copyright 2023 Axel Waggershauser -*/ -// SPDX-License-Identifier: Apache-2.0 - -#ifndef _ZXING_C_H -#define _ZXING_C_H - -#include -#include - -#ifdef __cplusplus - -#include "ReaderOptions.h" -#include "ImageView.h" -#include "Result.h" - -typedef ZXing::ImageView zxing_ImageView; -typedef ZXing::ReaderOptions zxing_ReaderOptions; -typedef ZXing::Result zxing_Barcode; -typedef ZXing::Results zxing_Barcodes; - -extern "C" -{ -#else - -typedef struct zxing_ImageView zxing_ImageView; -typedef struct zxing_ReaderOptions zxing_ReaderOptions; -typedef struct zxing_Barcode zxing_Barcode; -typedef struct zxing_Barcodes zxing_Barcodes; - -#endif - -/* - * ZXing/ImageView.h - */ - -typedef enum { - zxing_ImageFormat_None = 0, - zxing_ImageFormat_Lum = 0x01000000, - zxing_ImageFormat_RGB = 0x03000102, - zxing_ImageFormat_BGR = 0x03020100, - zxing_ImageFormat_RGBX = 0x04000102, - zxing_ImageFormat_XRGB = 0x04010203, - zxing_ImageFormat_BGRX = 0x04020100, - zxing_ImageFormat_XBGR = 0x04030201, -} zxing_ImageFormat; - -zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, - int pixStride); -zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, zxing_ImageFormat format, - int rowStride, int pixStride); -void zxing_ImageView_delete(zxing_ImageView* iv); - -void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); -void zxing_ImageView_rotate(zxing_ImageView* iv, int degree); - -/* - * ZXing/BarcodeFormat.h - */ - -typedef enum -{ - zxing_BarcodeFormat_None = 0, - zxing_BarcodeFormat_Aztec = (1 << 0), - zxing_BarcodeFormat_Codabar = (1 << 1), - zxing_BarcodeFormat_Code39 = (1 << 2), - zxing_BarcodeFormat_Code93 = (1 << 3), - zxing_BarcodeFormat_Code128 = (1 << 4), - zxing_BarcodeFormat_DataBar = (1 << 5), - zxing_BarcodeFormat_DataBarExpanded = (1 << 6), - zxing_BarcodeFormat_DataMatrix = (1 << 7), - zxing_BarcodeFormat_EAN8 = (1 << 8), - zxing_BarcodeFormat_EAN13 = (1 << 9), - zxing_BarcodeFormat_ITF = (1 << 10), - zxing_BarcodeFormat_MaxiCode = (1 << 11), - zxing_BarcodeFormat_PDF417 = (1 << 12), - zxing_BarcodeFormat_QRCode = (1 << 13), - zxing_BarcodeFormat_UPCA = (1 << 14), - zxing_BarcodeFormat_UPCE = (1 << 15), - zxing_BarcodeFormat_MicroQRCode = (1 << 16), - zxing_BarcodeFormat_RMQRCode = (1 << 17), - zxing_BarcodeFormat_DXFilmEdge = (1 << 18), - - zxing_BarcodeFormat_LinearCodes = zxing_BarcodeFormat_Codabar | zxing_BarcodeFormat_Code39 | zxing_BarcodeFormat_Code93 - | zxing_BarcodeFormat_Code128 | zxing_BarcodeFormat_EAN8 | zxing_BarcodeFormat_EAN13 - | zxing_BarcodeFormat_ITF | zxing_BarcodeFormat_DataBar | zxing_BarcodeFormat_DataBarExpanded - | zxing_BarcodeFormat_DXFilmEdge | zxing_BarcodeFormat_UPCA | zxing_BarcodeFormat_UPCE, - zxing_BarcodeFormat_MatrixCodes = zxing_BarcodeFormat_Aztec | zxing_BarcodeFormat_DataMatrix | zxing_BarcodeFormat_MaxiCode - | zxing_BarcodeFormat_PDF417 | zxing_BarcodeFormat_QRCode | zxing_BarcodeFormat_MicroQRCode - | zxing_BarcodeFormat_RMQRCode, - zxing_BarcodeFormat_Any = zxing_BarcodeFormat_LinearCodes | zxing_BarcodeFormat_MatrixCodes, - - zxing_BarcodeFormat_Invalid = 0xFFFFFFFFu /* return value when BarcodeFormatsFromString() throws */ -} zxing_BarcodeFormat; - -typedef zxing_BarcodeFormat zxing_BarcodeFormats; - -zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str); -zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str); -char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format); - -/* - * ZXing/ReaderOptions.h - */ - -typedef enum -{ - zxing_Binarizer_LocalAverage, - zxing_Binarizer_GlobalHistogram, - zxing_Binarizer_FixedThreshold, - zxing_Binarizer_BoolCast, -} zxing_Binarizer; - -typedef enum -{ - zxing_EanAddOnSymbol_Ignore, - zxing_EanAddOnSymbol_Read, - zxing_EanAddOnSymbol_Require, -} zxing_EanAddOnSymbol; - -typedef enum -{ - zxing_TextMode_Plain, - zxing_TextMode_ECI, - zxing_TextMode_HRI, - zxing_TextMode_Hex, - zxing_TextMode_Escaped, -} zxing_TextMode; - -zxing_ReaderOptions* zxing_ReaderOptions_new(); -void zxing_ReaderOptions_delete(zxing_ReaderOptions* opts); - -void zxing_ReaderOptions_setTryHarder(zxing_ReaderOptions* opts, bool tryHarder); -void zxing_ReaderOptions_setTryRotate(zxing_ReaderOptions* opts, bool tryRotate); -void zxing_ReaderOptions_setTryInvert(zxing_ReaderOptions* opts, bool tryInvert); -void zxing_ReaderOptions_setTryDownscale(zxing_ReaderOptions* opts, bool tryDownscale); -void zxing_ReaderOptions_setIsPure(zxing_ReaderOptions* opts, bool isPure); -void zxing_ReaderOptions_setReturnErrors(zxing_ReaderOptions* opts, bool returnErrors); -void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeFormats formats); -void zxing_ReaderOptions_setBinarizer(zxing_ReaderOptions* opts, zxing_Binarizer binarizer); -void zxing_ReaderOptions_setEanAddOnSymbol(zxing_ReaderOptions* opts, zxing_EanAddOnSymbol eanAddOnSymbol); -void zxing_ReaderOptions_setTextMode(zxing_ReaderOptions* opts, zxing_TextMode textMode); -void zxing_ReaderOptions_setMinLineCount(zxing_ReaderOptions* opts, int n); -void zxing_ReaderOptions_setMaxNumberOfSymbols(zxing_ReaderOptions* opts, int n); - -bool zxing_ReaderOptions_getTryHarder(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getTryRotate(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getTryInvert(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getTryDownscale(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getIsPure(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getReturnErrors(const zxing_ReaderOptions* opts); -zxing_BarcodeFormats zxing_ReaderOptions_getFormats(const zxing_ReaderOptions* opts); -zxing_Binarizer zxing_ReaderOptions_getBinarizer(const zxing_ReaderOptions* opts); -zxing_EanAddOnSymbol zxing_ReaderOptions_getEanAddOnSymbol(const zxing_ReaderOptions* opts); -zxing_TextMode zxing_ReaderOptions_getTextMode(const zxing_ReaderOptions* opts); -int zxing_ReaderOptions_getMinLineCount(const zxing_ReaderOptions* opts); -int zxing_ReaderOptions_getMaxNumberOfSymbols(const zxing_ReaderOptions* opts); - -/* - * ZXing/Result.h - */ - -typedef enum -{ - zxing_ContentType_Text, - zxing_ContentType_Binary, - zxing_ContentType_Mixed, - zxing_ContentType_GS1, - zxing_ContentType_ISO15434, - zxing_ContentType_UnknownECI -} zxing_ContentType; - -char* zxing_ContentTypeToString(zxing_ContentType type); - -typedef struct zxing_PointI -{ - int x, y; -} zxing_PointI; - -typedef struct zxing_Position -{ - zxing_PointI topLeft, topRight, bottomRight, bottomLeft; -} zxing_Position; - -char* zxing_PositionToString(zxing_Position position); - -bool zxing_Barcode_isValid(const zxing_Barcode* barcode); -char* zxing_Barcode_errorMsg(const zxing_Barcode* barcode); -zxing_BarcodeFormat zxing_Barcode_format(const zxing_Barcode* barcode); -zxing_ContentType zxing_Barcode_contentType(const zxing_Barcode* barcode); -uint8_t* zxing_Barcode_bytes(const zxing_Barcode* barcode, int* len); -uint8_t* zxing_Barcode_bytesECI(const zxing_Barcode* barcode, int* len); -char* zxing_Barcode_text(const zxing_Barcode* barcode); -char* zxing_Barcode_ecLevel(const zxing_Barcode* barcode); -char* zxing_Barcode_symbologyIdentifier(const zxing_Barcode* barcode); -zxing_Position zxing_Barcode_position(const zxing_Barcode* barcode); -int zxing_Barcode_orientation(const zxing_Barcode* barcode); -bool zxing_Barcode_hasECI(const zxing_Barcode* barcode); -bool zxing_Barcode_isInverted(const zxing_Barcode* barcode); -bool zxing_Barcode_isMirrored(const zxing_Barcode* barcode); -int zxing_Barcode_lineCount(const zxing_Barcode* barcode); - -/* - * ZXing/ReadBarcode.h - */ - -/** Note: opts is optional, i.e. it can be NULL, which will imply default settings. */ -zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); -zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); - -void zxing_Barcode_delete(zxing_Barcode* barcode); -void zxing_Barcodes_delete(zxing_Barcodes* barcodes); - -int zxing_Barcodes_size(const zxing_Barcodes* barcodes); -const zxing_Barcode* zxing_Barcodes_at(const zxing_Barcodes* barcodes, int i); -zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i); - -char* zxing_LastErrorMsg(); - -void zxing_free(void* ptr); - -#ifdef __cplusplus -} -#endif - -#endif /* _ZXING_C_H */ diff --git a/wrappers/c/CMakeLists.txt b/wrappers/c/CMakeLists.txt index 0de0d12036..9754a9f70b 100644 --- a/wrappers/c/CMakeLists.txt +++ b/wrappers/c/CMakeLists.txt @@ -1,7 +1,7 @@ zxing_add_package_stb() if (BUILD_READERS) - add_executable (zxing-c-test zxing-c-test.c) - target_link_libraries (zxing-c-test ZXing::ZXing stb::stb) - add_test(NAME zxing-c-test COMMAND zxing-c-test ${CMAKE_SOURCE_DIR}/test/samples/qrcode-1/1.png) + add_executable (ZXingCTest ZXingCTest.c) + target_link_libraries (ZXingCTest ZXing::ZXing stb::stb) + add_test(NAME ZXingCTest COMMAND ZXingCTest ${CMAKE_SOURCE_DIR}/test/samples/qrcode-1/1.png) endif() diff --git a/wrappers/c/README.md b/wrappers/c/README.md index 62b920ae97..aa0d8c1a94 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -6,48 +6,49 @@ This is a preview/proposal for a C-API to zxing-cpp. If you have any comments or It is currently included in the default build to be trivially accessible for everyone. -Probably the easiest way to play with the C-API is to just modify the [zxing-c-test.c](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c-test.c) file. +Probably the easiest way to play with the C-API is to just modify the [ZXingCTest.c](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/ZXingCTest.c) file. ## Usage The following is close to the most trivial use case scenario that is supported. ```c -#include "ZXing/zxing-c.h" +#include "ZXing/ZXingC.h" int main(int argc, char** argv) { int width, height; unsigned char* data; - /* load your image data from somewhere. ImageFormat_Lum assumes grey scale image data. */ + /* load your image data from somewhere. ZXing_ImageFormat_Lum assumes grey scale image data. */ - zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); + ZXing_ImageView* iv = ZXing_ImageView_new(data, width, height, ZXing_ImageFormat_Lum, 0, 0); - zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); - /* set ReaderOptions properties, if requried */ + ZXing_ReaderOptions* opts = ZXing_ReaderOptions_new(); + /* set ReaderOptions properties, if requried, e.g. */ + ZXing_ReaderOptions_setFormats(ZXing_BarcodeFormat_QRCode | ZXing_BarcodeFromat_EAN13); - zxing_Barcodes* barcodes = zxing_ReadBarcodes(iv, opts); + ZXing_Barcodes* barcodes = ZXing_ReadBarcodes(iv, opts); - zxing_ImageView_delete(iv); - zxing_ReaderOptions_delete(opts); + ZXing_ImageView_delete(iv); + ZXing_ReaderOptions_delete(opts); if (barcodes) { - for (int i = 0, n = zxing_Barcodes_size(barcodes); i < n; ++i) { - const zxing_Barcode* barcode = zxing_Barcodes_at(barcodes, i); + for (int i = 0, n = ZXing_Barcodes_size(barcodes); i < n; ++i) { + const ZXing_Barcode* barcode = ZXing_Barcodes_at(barcodes, i); - char* format = zxing_BarcodeFormatToString(zxing_Barcode_format(barcode)); + char* format = ZXing_BarcodeFormatToString(ZXing_Barcode_format(barcode)); printf("Format : %s\n", format); - zxing_free(format); + ZXing_free(format); - char* text = zxing_Barcode_text(barcode); + char* text = ZXing_Barcode_text(barcode); printf("Text : %s\n", text); - zxing_free(text); + ZXing_free(text); } - zxing_Barcodes_delete(barcodes); + ZXing_Barcodes_delete(barcodes); } else { - char* error = zxing_LastErrorMsg(); + char* error = ZXing_LastErrorMsg(); fprintf(stderr, "%s\n", error); - zxing_free(error); + ZXing_free(error); } return 0; diff --git a/wrappers/c/ZXingCTest.c b/wrappers/c/ZXingCTest.c new file mode 100644 index 0000000000..fd1b73def3 --- /dev/null +++ b/wrappers/c/ZXingCTest.c @@ -0,0 +1,108 @@ +/* +* Copyright 2023 siiky +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "ZXingC.h" + +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_LINEAR // prevent dependency on -lm +#define STBI_NO_HDR +#include + +int usage(char* pname) +{ + fprintf(stderr, "Usage: %s FILE [FORMATS]\n", pname); + return 1; +} + +bool parse_args(int argc, char** argv, char** filename, ZXing_BarcodeFormats* formats) +{ + if (argc < 2) + return false; + *filename = argv[1]; + if (argc >= 3) { + *formats = ZXing_BarcodeFormatsFromString(argv[2]); + if (*formats == ZXing_BarcodeFormat_Invalid) { + fprintf(stderr, "%s\n", ZXing_LastErrorMsg()); + return false; + } + } + return true; +} + +void printF(const char* fmt, char* text) +{ + if (!text) + return; + if (*text) + printf(fmt, text); + ZXing_free(text); +} + +int main(int argc, char** argv) +{ + int ret = 0; + char* filename = NULL; + ZXing_BarcodeFormats formats = ZXing_BarcodeFormat_None; + + if (!parse_args(argc, argv, &filename, &formats)) + return usage(argv[0]); + + int width = 0; + int height = 0; + int channels = 0; + stbi_uc* data = stbi_load(filename, &width, &height, &channels, STBI_grey); + if (!data) { + fprintf(stderr, "Could not read image '%s'\n", filename); + return 2; + } + + ZXing_ReaderOptions* opts = ZXing_ReaderOptions_new(); + ZXing_ReaderOptions_setTextMode(opts, ZXing_TextMode_HRI); + ZXing_ReaderOptions_setEanAddOnSymbol(opts, ZXing_EanAddOnSymbol_Ignore); + ZXing_ReaderOptions_setFormats(opts, formats); + ZXing_ReaderOptions_setReturnErrors(opts, true); + + ZXing_ImageView* iv = ZXing_ImageView_new(data, width, height, ZXing_ImageFormat_Lum, 0, 0); + + ZXing_Barcodes* barcodes = ZXing_ReadBarcodes(iv, opts); + + ZXing_ImageView_delete(iv); + ZXing_ReaderOptions_delete(opts); + stbi_image_free(data); + + if (barcodes) { + for (int i = 0, n = ZXing_Barcodes_size(barcodes); i < n; ++i) { + const ZXing_Barcode* barcode = ZXing_Barcodes_at(barcodes, i); + + printF("Text : %s\n", ZXing_Barcode_text(barcode)); + printF("Format : %s\n", ZXing_BarcodeFormatToString(ZXing_Barcode_format(barcode))); + printF("Content : %s\n", ZXing_ContentTypeToString(ZXing_Barcode_contentType(barcode))); + printF("Identifier : %s\n", ZXing_Barcode_symbologyIdentifier(barcode)); + printF("EC Level : %s\n", ZXing_Barcode_ecLevel(barcode)); + printF("Error : %s\n", ZXing_Barcode_errorMsg(barcode)); + printF("Position : %s\n", ZXing_PositionToString(ZXing_Barcode_position(barcode))); + printf("Rotation : %d\n", ZXing_Barcode_orientation(barcode)); + + if (i < n-1) + printf("\n"); + } + + if (ZXing_Barcodes_size(barcodes) == 0) + printf("No barcode found\n"); + + ZXing_Barcodes_delete(barcodes); + } else { + char* error = ZXing_LastErrorMsg(); + fprintf(stderr, "%s\n", error); + ZXing_free(error); + ret = 2; + } + + return ret; +} diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c deleted file mode 100644 index e5bef602c0..0000000000 --- a/wrappers/c/zxing-c-test.c +++ /dev/null @@ -1,108 +0,0 @@ -/* -* Copyright 2023 siiky -* Copyright 2023 Axel Waggershauser -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "zxing-c.h" - -#include -#include - -#define STB_IMAGE_IMPLEMENTATION -#define STBI_NO_LINEAR // prevent dependency on -lm -#define STBI_NO_HDR -#include - -int usage(char* pname) -{ - fprintf(stderr, "Usage: %s FILE [FORMATS]\n", pname); - return 1; -} - -bool parse_args(int argc, char** argv, char** filename, zxing_BarcodeFormats* formats) -{ - if (argc < 2) - return false; - *filename = argv[1]; - if (argc >= 3) { - *formats = zxing_BarcodeFormatsFromString(argv[2]); - if (*formats == zxing_BarcodeFormat_Invalid) { - fprintf(stderr, "%s\n", zxing_LastErrorMsg()); - return false; - } - } - return true; -} - -void printF(const char* fmt, char* text) -{ - if (!text) - return; - if (*text) - printf(fmt, text); - zxing_free(text); -} - -int main(int argc, char** argv) -{ - int ret = 0; - char* filename = NULL; - zxing_BarcodeFormats formats = zxing_BarcodeFormat_None; - - if (!parse_args(argc, argv, &filename, &formats)) - return usage(argv[0]); - - int width = 0; - int height = 0; - int channels = 0; - stbi_uc* data = stbi_load(filename, &width, &height, &channels, STBI_grey); - if (!data) { - fprintf(stderr, "Could not read image '%s'\n", filename); - return 2; - } - - zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); - zxing_ReaderOptions_setTextMode(opts, zxing_TextMode_HRI); - zxing_ReaderOptions_setEanAddOnSymbol(opts, zxing_EanAddOnSymbol_Ignore); - zxing_ReaderOptions_setFormats(opts, formats); - zxing_ReaderOptions_setReturnErrors(opts, true); - - zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); - - zxing_Barcodes* barcodes = zxing_ReadBarcodes(iv, opts); - - zxing_ImageView_delete(iv); - zxing_ReaderOptions_delete(opts); - stbi_image_free(data); - - if (barcodes) { - for (int i = 0, n = zxing_Barcodes_size(barcodes); i < n; ++i) { - const zxing_Barcode* barcode = zxing_Barcodes_at(barcodes, i); - - printF("Text : %s\n", zxing_Barcode_text(barcode)); - printF("Format : %s\n", zxing_BarcodeFormatToString(zxing_Barcode_format(barcode))); - printF("Content : %s\n", zxing_ContentTypeToString(zxing_Barcode_contentType(barcode))); - printF("Identifier : %s\n", zxing_Barcode_symbologyIdentifier(barcode)); - printF("EC Level : %s\n", zxing_Barcode_ecLevel(barcode)); - printF("Error : %s\n", zxing_Barcode_errorMsg(barcode)); - printF("Position : %s\n", zxing_PositionToString(zxing_Barcode_position(barcode))); - printf("Rotation : %d\n", zxing_Barcode_orientation(barcode)); - - if (i < n-1) - printf("\n"); - } - - if (zxing_Barcodes_size(barcodes) == 0) - printf("No barcode found\n"); - - zxing_Barcodes_delete(barcodes); - } else { - char* error = zxing_LastErrorMsg(); - fprintf(stderr, "%s\n", error); - zxing_free(error); - ret = 2; - } - - return ret; -} diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 61545ceef3..a02081174c 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -16,65 +16,65 @@ internal class Dll { private const string DllName = "ZXing"; - [DllImport(DllName)] public static extern IntPtr zxing_ReaderOptions_new(); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_delete(IntPtr opts); - - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryHarder(IntPtr opts, bool tryHarder); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryHarder(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryRotate(IntPtr opts, bool tryRotate); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryRotate(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryInvert(IntPtr opts, bool tryInvert); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryInvert(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryDownscale(IntPtr opts, bool tryDownscale); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryDownscale(IntPtr opts); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getIsPure(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setIsPure(IntPtr opts, bool isPure); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setReturnErrors(IntPtr opts, bool returnErrors); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getReturnErrors(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setFormats(IntPtr opts, BarcodeFormats formats); - [DllImport(DllName)] public static extern BarcodeFormats zxing_ReaderOptions_getFormats(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setBinarizer(IntPtr opts, Binarizer binarizer); - [DllImport(DllName)] public static extern Binarizer zxing_ReaderOptions_getBinarizer(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setEanAddOnSymbol(IntPtr opts, EanAddOnSymbol eanAddOnSymbol); - [DllImport(DllName)] public static extern EanAddOnSymbol zxing_ReaderOptions_getEanAddOnSymbol(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTextMode(IntPtr opts, TextMode textMode); - [DllImport(DllName)] public static extern TextMode zxing_ReaderOptions_getTextMode(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setMinLineCount(IntPtr opts, int n); - [DllImport(DllName)] public static extern int zxing_ReaderOptions_getMinLineCount(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setMaxNumberOfSymbols(IntPtr opts, int n); - [DllImport(DllName)] public static extern int zxing_ReaderOptions_getMaxNumberOfSymbols(IntPtr opts); - - [DllImport(DllName)] public static extern IntPtr zxing_PositionToString(Position position); - [DllImport(DllName)] public static extern BarcodeFormats zxing_BarcodeFormatsFromString(string str); - - [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new(IntPtr data, int width, int height, ImageFormat format, int rowStride, int pixStride); - [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new_checked(byte[] data, int size, int width, int height, ImageFormat format, int rowStride, int pixStride); - [DllImport(DllName)] public static extern void zxing_ImageView_delete(IntPtr iv); - - [DllImport(DllName)] public static extern IntPtr zxing_ReadBarcodes(IntPtr iv, IntPtr opts); - [DllImport(DllName)] public static extern void zxing_Barcode_delete(IntPtr result); - [DllImport(DllName)] public static extern void zxing_Barcodes_delete(IntPtr results); - [DllImport(DllName)] public static extern int zxing_Barcodes_size(IntPtr results); - [DllImport(DllName)] public static extern IntPtr zxing_Barcodes_move(IntPtr results, int i); - - [DllImport(DllName)] public static extern bool zxing_Barcode_isValid(IntPtr result); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_errorMsg(IntPtr result); - [DllImport(DllName)] public static extern BarcodeFormat zxing_Barcode_format(IntPtr result); - [DllImport(DllName)] public static extern ContentType zxing_Barcode_contentType(IntPtr result); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_bytes(IntPtr result, out int len); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_bytesECI(IntPtr result, out int len); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_text(IntPtr result); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_ecLevel(IntPtr result); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_symbologyIdentifier(IntPtr result); - [DllImport(DllName)] public static extern Position zxing_Barcode_position(IntPtr result); - [DllImport(DllName)] public static extern int zxing_Barcode_orientation(IntPtr result); - [DllImport(DllName)] public static extern bool zxing_Barcode_hasECI(IntPtr result); - [DllImport(DllName)] public static extern bool zxing_Barcode_isInverted(IntPtr result); - [DllImport(DllName)] public static extern bool zxing_Barcode_isMirrored(IntPtr result); - [DllImport(DllName)] public static extern int zxing_Barcode_lineCount(IntPtr result); - - [DllImport(DllName)] public static extern void zxing_free(IntPtr opts); - [DllImport(DllName)] public static extern IntPtr zxing_LastErrorMsg(); + [DllImport(DllName)] public static extern IntPtr ZXing_ReaderOptions_new(); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_delete(IntPtr opts); + + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTryHarder(IntPtr opts, bool tryHarder); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getTryHarder(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTryRotate(IntPtr opts, bool tryRotate); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getTryRotate(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTryInvert(IntPtr opts, bool tryInvert); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getTryInvert(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTryDownscale(IntPtr opts, bool tryDownscale); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getTryDownscale(IntPtr opts); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getIsPure(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setIsPure(IntPtr opts, bool isPure); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setReturnErrors(IntPtr opts, bool returnErrors); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getReturnErrors(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setFormats(IntPtr opts, BarcodeFormats formats); + [DllImport(DllName)] public static extern BarcodeFormats ZXing_ReaderOptions_getFormats(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setBinarizer(IntPtr opts, Binarizer binarizer); + [DllImport(DllName)] public static extern Binarizer ZXing_ReaderOptions_getBinarizer(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setEanAddOnSymbol(IntPtr opts, EanAddOnSymbol eanAddOnSymbol); + [DllImport(DllName)] public static extern EanAddOnSymbol ZXing_ReaderOptions_getEanAddOnSymbol(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTextMode(IntPtr opts, TextMode textMode); + [DllImport(DllName)] public static extern TextMode ZXing_ReaderOptions_getTextMode(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setMinLineCount(IntPtr opts, int n); + [DllImport(DllName)] public static extern int ZXing_ReaderOptions_getMinLineCount(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setMaxNumberOfSymbols(IntPtr opts, int n); + [DllImport(DllName)] public static extern int ZXing_ReaderOptions_getMaxNumberOfSymbols(IntPtr opts); + + [DllImport(DllName)] public static extern IntPtr ZXing_PositionToString(Position position); + [DllImport(DllName)] public static extern BarcodeFormats ZXing_BarcodeFormatsFromString(string str); + + [DllImport(DllName)] public static extern IntPtr ZXing_ImageView_new(IntPtr data, int width, int height, ImageFormat format, int rowStride, int pixStride); + [DllImport(DllName)] public static extern IntPtr ZXing_ImageView_new_checked(byte[] data, int size, int width, int height, ImageFormat format, int rowStride, int pixStride); + [DllImport(DllName)] public static extern void ZXing_ImageView_delete(IntPtr iv); + + [DllImport(DllName)] public static extern IntPtr ZXing_ReadBarcodes(IntPtr iv, IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_Barcode_delete(IntPtr result); + [DllImport(DllName)] public static extern void ZXing_Barcodes_delete(IntPtr results); + [DllImport(DllName)] public static extern int ZXing_Barcodes_size(IntPtr results); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcodes_move(IntPtr results, int i); + + [DllImport(DllName)] public static extern bool ZXing_Barcode_isValid(IntPtr result); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_errorMsg(IntPtr result); + [DllImport(DllName)] public static extern BarcodeFormat ZXing_Barcode_format(IntPtr result); + [DllImport(DllName)] public static extern ContentType ZXing_Barcode_contentType(IntPtr result); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytes(IntPtr result, out int len); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytesECI(IntPtr result, out int len); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_text(IntPtr result); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_ecLevel(IntPtr result); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_symbologyIdentifier(IntPtr result); + [DllImport(DllName)] public static extern Position ZXing_Barcode_position(IntPtr result); + [DllImport(DllName)] public static extern int ZXing_Barcode_orientation(IntPtr result); + [DllImport(DllName)] public static extern bool ZXing_Barcode_hasECI(IntPtr result); + [DllImport(DllName)] public static extern bool ZXing_Barcode_isInverted(IntPtr result); + [DllImport(DllName)] public static extern bool ZXing_Barcode_isMirrored(IntPtr result); + [DllImport(DllName)] public static extern int ZXing_Barcode_lineCount(IntPtr result); + + [DllImport(DllName)] public static extern void ZXing_free(IntPtr opts); + [DllImport(DllName)] public static extern IntPtr ZXing_LastErrorMsg(); public static string MarshalAsString(IntPtr ptr) @@ -83,7 +83,7 @@ public static string MarshalAsString(IntPtr ptr) throw new Exception("ZXing C-API returned a NULL char*."); string res = Marshal.PtrToStringUTF8(ptr) ?? ""; - zxing_free(ptr); + ZXing_free(ptr); return res; } @@ -97,7 +97,7 @@ public static byte[] MarshalAsBytes(RetBytesFunc func, IntPtr d) byte[] res = new byte[len]; Marshal.Copy(ptr, res, 0, len); - zxing_free(ptr); + ZXing_free(ptr); return res; } } @@ -178,7 +178,7 @@ public struct Position { public PointI TopLeft, TopRight, BottomRight, BottomLeft; - public override string ToString() => MarshalAsString(zxing_PositionToString(this)); + public override string ToString() => MarshalAsString(ZXing_PositionToString(this)); }; public class ImageView @@ -187,19 +187,19 @@ public class ImageView public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) { - _d = zxing_ImageView_new_checked(data, data.Length, width, height, format, rowStride, pixStride); + _d = ZXing_ImageView_new_checked(data, data.Length, width, height, format, rowStride, pixStride); if (_d == IntPtr.Zero) - throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + throw new Exception(MarshalAsString(ZXing_LastErrorMsg())); } public ImageView(IntPtr data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) { - _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); + _d = ZXing_ImageView_new(data, width, height, format, rowStride, pixStride); if (_d == IntPtr.Zero) - throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + throw new Exception(MarshalAsString(ZXing_LastErrorMsg())); } - ~ImageView() => zxing_ImageView_delete(_d); + ~ImageView() => ZXing_ImageView_delete(_d); } public class ReaderOptions @@ -208,83 +208,83 @@ public class ReaderOptions public ReaderOptions() { - _d = zxing_ReaderOptions_new(); + _d = ZXing_ReaderOptions_new(); if (_d == IntPtr.Zero) throw new Exception("Failed to create ReaderOptions."); } - ~ReaderOptions() => zxing_ReaderOptions_delete(_d); + ~ReaderOptions() => ZXing_ReaderOptions_delete(_d); public bool TryHarder { - get => zxing_ReaderOptions_getTryHarder(_d); - set => zxing_ReaderOptions_setTryHarder(_d, value); + get => ZXing_ReaderOptions_getTryHarder(_d); + set => ZXing_ReaderOptions_setTryHarder(_d, value); } public bool TryRotate { - get => zxing_ReaderOptions_getTryRotate(_d); - set => zxing_ReaderOptions_setTryRotate(_d, value); + get => ZXing_ReaderOptions_getTryRotate(_d); + set => ZXing_ReaderOptions_setTryRotate(_d, value); } public bool TryInvert { - get => zxing_ReaderOptions_getTryInvert(_d); - set => zxing_ReaderOptions_setTryInvert(_d, value); + get => ZXing_ReaderOptions_getTryInvert(_d); + set => ZXing_ReaderOptions_setTryInvert(_d, value); } public bool TryDownscale { - get => zxing_ReaderOptions_getTryDownscale(_d); - set => zxing_ReaderOptions_setTryDownscale(_d, value); + get => ZXing_ReaderOptions_getTryDownscale(_d); + set => ZXing_ReaderOptions_setTryDownscale(_d, value); } public bool IsPure { - get => zxing_ReaderOptions_getIsPure(_d); - set => zxing_ReaderOptions_setIsPure(_d, value); + get => ZXing_ReaderOptions_getIsPure(_d); + set => ZXing_ReaderOptions_setIsPure(_d, value); } public bool ReturnErrors { - get => zxing_ReaderOptions_getReturnErrors(_d); - set => zxing_ReaderOptions_setReturnErrors(_d, value); + get => ZXing_ReaderOptions_getReturnErrors(_d); + set => ZXing_ReaderOptions_setReturnErrors(_d, value); } public BarcodeFormats Formats { - get => zxing_ReaderOptions_getFormats(_d); - set => zxing_ReaderOptions_setFormats(_d, value); + get => ZXing_ReaderOptions_getFormats(_d); + set => ZXing_ReaderOptions_setFormats(_d, value); } public Binarizer Binarizer { - get => zxing_ReaderOptions_getBinarizer(_d); - set => zxing_ReaderOptions_setBinarizer(_d, value); + get => ZXing_ReaderOptions_getBinarizer(_d); + set => ZXing_ReaderOptions_setBinarizer(_d, value); } public EanAddOnSymbol EanAddOnSymbol { - get => zxing_ReaderOptions_getEanAddOnSymbol(_d); - set => zxing_ReaderOptions_setEanAddOnSymbol(_d, value); + get => ZXing_ReaderOptions_getEanAddOnSymbol(_d); + set => ZXing_ReaderOptions_setEanAddOnSymbol(_d, value); } public TextMode TextMode { - get => zxing_ReaderOptions_getTextMode(_d); - set => zxing_ReaderOptions_setTextMode(_d, value); + get => ZXing_ReaderOptions_getTextMode(_d); + set => ZXing_ReaderOptions_setTextMode(_d, value); } public int MinLineCount { - get => zxing_ReaderOptions_getMinLineCount(_d); - set => zxing_ReaderOptions_setMinLineCount(_d, value); + get => ZXing_ReaderOptions_getMinLineCount(_d); + set => ZXing_ReaderOptions_setMinLineCount(_d, value); } public int MaxNumberOfSymbols { - get => zxing_ReaderOptions_getMaxNumberOfSymbols(_d); - set => zxing_ReaderOptions_setMaxNumberOfSymbols(_d, value); + get => ZXing_ReaderOptions_getMaxNumberOfSymbols(_d); + set => ZXing_ReaderOptions_setMaxNumberOfSymbols(_d, value); } } @@ -294,46 +294,46 @@ public class Barcode internal IntPtr _d; internal Barcode(IntPtr d) => _d = d; - ~Barcode() => zxing_Barcode_delete(_d); - - public bool IsValid => zxing_Barcode_isValid(_d); - public BarcodeFormat Format => zxing_Barcode_format(_d); - public ContentType ContentType => zxing_Barcode_contentType(_d); - public string Text => MarshalAsString(zxing_Barcode_text(_d)); - public byte[] Bytes => MarshalAsBytes(zxing_Barcode_bytes, _d); - public byte[] BytesECI => MarshalAsBytes(zxing_Barcode_bytesECI, _d); - public string ECLevel => MarshalAsString(zxing_Barcode_ecLevel(_d)); - public string SymbologyIdentifier => MarshalAsString(zxing_Barcode_symbologyIdentifier(_d)); - public string ErrorMsg => MarshalAsString(zxing_Barcode_errorMsg(_d)); - public Position Position => zxing_Barcode_position(_d); - public int Orientation => zxing_Barcode_orientation(_d); - public bool HasECI => zxing_Barcode_hasECI(_d); - public bool IsInverted => zxing_Barcode_isInverted(_d); - public bool IsMirrored => zxing_Barcode_isMirrored(_d); - public int LineCount => zxing_Barcode_lineCount(_d); + ~Barcode() => ZXing_Barcode_delete(_d); + + public bool IsValid => ZXing_Barcode_isValid(_d); + public BarcodeFormat Format => ZXing_Barcode_format(_d); + public ContentType ContentType => ZXing_Barcode_contentType(_d); + public string Text => MarshalAsString(ZXing_Barcode_text(_d)); + public byte[] Bytes => MarshalAsBytes(ZXing_Barcode_bytes, _d); + public byte[] BytesECI => MarshalAsBytes(ZXing_Barcode_bytesECI, _d); + public string ECLevel => MarshalAsString(ZXing_Barcode_ecLevel(_d)); + public string SymbologyIdentifier => MarshalAsString(ZXing_Barcode_symbologyIdentifier(_d)); + public string ErrorMsg => MarshalAsString(ZXing_Barcode_errorMsg(_d)); + public Position Position => ZXing_Barcode_position(_d); + public int Orientation => ZXing_Barcode_orientation(_d); + public bool HasECI => ZXing_Barcode_hasECI(_d); + public bool IsInverted => ZXing_Barcode_isInverted(_d); + public bool IsMirrored => ZXing_Barcode_isMirrored(_d); + public int LineCount => ZXing_Barcode_lineCount(_d); } public class BarcodeReader : ReaderOptions { public static BarcodeFormats FormatsFromString(string str) { - var fmts = zxing_BarcodeFormatsFromString(str); - if ((int)fmts == -1) // see zxing_BarcodeFormat_Invalid - throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + var fmts = ZXing_BarcodeFormatsFromString(str); + if ((int)fmts == -1) // see ZXing_BarcodeFormat_Invalid + throw new Exception(MarshalAsString(ZXing_LastErrorMsg())); return fmts; } public static List Read(ImageView iv, ReaderOptions? opts = null) { - var ptr = zxing_ReadBarcodes(iv._d, opts?._d ?? IntPtr.Zero); + var ptr = ZXing_ReadBarcodes(iv._d, opts?._d ?? IntPtr.Zero); if (ptr == IntPtr.Zero) - throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + throw new Exception(MarshalAsString(ZXing_LastErrorMsg())); - var size = zxing_Barcodes_size(ptr); + var size = ZXing_Barcodes_size(ptr); var res = new List(size); for (int i = 0; i < size; ++i) - res.Add(new Barcode(zxing_Barcodes_move(ptr, i))); - zxing_Barcodes_delete(ptr); + res.Add(new Barcode(ZXing_Barcodes_move(ptr, i))); + ZXing_Barcodes_delete(ptr); return res; } diff --git a/wrappers/rust/build.rs b/wrappers/rust/build.rs index 6d754a58d6..8f43f3a1be 100644 --- a/wrappers/rust/build.rs +++ b/wrappers/rust/build.rs @@ -29,7 +29,7 @@ fn main() -> miette::Result<()> { } // manual bindings.rs generation: - // bindgen core/src/zxing-c.h -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --no-doc-comments --no-layout-tests --with-derive-partialeq --allowlist-item "zxing.*" + // bindgen core/src/ZXingC.h -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --no-doc-comments --no-layout-tests --with-derive-partialeq --allowlist-item "ZXing.*" Ok(()) } diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 52e659b622..00a465a6c8 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -2,175 +2,175 @@ #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_ImageView { +pub struct ZXing_ImageView { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_ReaderOptions { +pub struct ZXing_ReaderOptions { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_Barcode { +pub struct ZXing_Barcode { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_Barcodes { +pub struct ZXing_Barcodes { _unused: [u8; 0], } -pub const zxing_ImageFormat_None: zxing_ImageFormat = 0; -pub const zxing_ImageFormat_Lum: zxing_ImageFormat = 16777216; -pub const zxing_ImageFormat_RGB: zxing_ImageFormat = 50331906; -pub const zxing_ImageFormat_BGR: zxing_ImageFormat = 50462976; -pub const zxing_ImageFormat_RGBX: zxing_ImageFormat = 67109122; -pub const zxing_ImageFormat_XRGB: zxing_ImageFormat = 67174915; -pub const zxing_ImageFormat_BGRX: zxing_ImageFormat = 67240192; -pub const zxing_ImageFormat_XBGR: zxing_ImageFormat = 67305985; -pub type zxing_ImageFormat = ::core::ffi::c_uint; -pub const zxing_BarcodeFormat_None: zxing_BarcodeFormat = 0; -pub const zxing_BarcodeFormat_Aztec: zxing_BarcodeFormat = 1; -pub const zxing_BarcodeFormat_Codabar: zxing_BarcodeFormat = 2; -pub const zxing_BarcodeFormat_Code39: zxing_BarcodeFormat = 4; -pub const zxing_BarcodeFormat_Code93: zxing_BarcodeFormat = 8; -pub const zxing_BarcodeFormat_Code128: zxing_BarcodeFormat = 16; -pub const zxing_BarcodeFormat_DataBar: zxing_BarcodeFormat = 32; -pub const zxing_BarcodeFormat_DataBarExpanded: zxing_BarcodeFormat = 64; -pub const zxing_BarcodeFormat_DataMatrix: zxing_BarcodeFormat = 128; -pub const zxing_BarcodeFormat_EAN8: zxing_BarcodeFormat = 256; -pub const zxing_BarcodeFormat_EAN13: zxing_BarcodeFormat = 512; -pub const zxing_BarcodeFormat_ITF: zxing_BarcodeFormat = 1024; -pub const zxing_BarcodeFormat_MaxiCode: zxing_BarcodeFormat = 2048; -pub const zxing_BarcodeFormat_PDF417: zxing_BarcodeFormat = 4096; -pub const zxing_BarcodeFormat_QRCode: zxing_BarcodeFormat = 8192; -pub const zxing_BarcodeFormat_UPCA: zxing_BarcodeFormat = 16384; -pub const zxing_BarcodeFormat_UPCE: zxing_BarcodeFormat = 32768; -pub const zxing_BarcodeFormat_MicroQRCode: zxing_BarcodeFormat = 65536; -pub const zxing_BarcodeFormat_RMQRCode: zxing_BarcodeFormat = 131072; -pub const zxing_BarcodeFormat_DXFilmEdge: zxing_BarcodeFormat = 262144; -pub const zxing_BarcodeFormat_LinearCodes: zxing_BarcodeFormat = 313214; -pub const zxing_BarcodeFormat_MatrixCodes: zxing_BarcodeFormat = 211073; -pub const zxing_BarcodeFormat_Any: zxing_BarcodeFormat = 524287; -pub const zxing_BarcodeFormat_Invalid: zxing_BarcodeFormat = 4294967295; -pub type zxing_BarcodeFormat = ::core::ffi::c_uint; -pub use self::zxing_BarcodeFormat as zxing_BarcodeFormats; -pub const zxing_Binarizer_LocalAverage: zxing_Binarizer = 0; -pub const zxing_Binarizer_GlobalHistogram: zxing_Binarizer = 1; -pub const zxing_Binarizer_FixedThreshold: zxing_Binarizer = 2; -pub const zxing_Binarizer_BoolCast: zxing_Binarizer = 3; -pub type zxing_Binarizer = ::core::ffi::c_uint; -pub const zxing_EanAddOnSymbol_Ignore: zxing_EanAddOnSymbol = 0; -pub const zxing_EanAddOnSymbol_Read: zxing_EanAddOnSymbol = 1; -pub const zxing_EanAddOnSymbol_Require: zxing_EanAddOnSymbol = 2; -pub type zxing_EanAddOnSymbol = ::core::ffi::c_uint; -pub const zxing_TextMode_Plain: zxing_TextMode = 0; -pub const zxing_TextMode_ECI: zxing_TextMode = 1; -pub const zxing_TextMode_HRI: zxing_TextMode = 2; -pub const zxing_TextMode_Hex: zxing_TextMode = 3; -pub const zxing_TextMode_Escaped: zxing_TextMode = 4; -pub type zxing_TextMode = ::core::ffi::c_uint; -pub const zxing_ContentType_Text: zxing_ContentType = 0; -pub const zxing_ContentType_Binary: zxing_ContentType = 1; -pub const zxing_ContentType_Mixed: zxing_ContentType = 2; -pub const zxing_ContentType_GS1: zxing_ContentType = 3; -pub const zxing_ContentType_ISO15434: zxing_ContentType = 4; -pub const zxing_ContentType_UnknownECI: zxing_ContentType = 5; -pub type zxing_ContentType = ::core::ffi::c_uint; +pub const ZXing_ImageFormat_None: ZXing_ImageFormat = 0; +pub const ZXing_ImageFormat_Lum: ZXing_ImageFormat = 16777216; +pub const ZXing_ImageFormat_RGB: ZXing_ImageFormat = 50331906; +pub const ZXing_ImageFormat_BGR: ZXing_ImageFormat = 50462976; +pub const ZXing_ImageFormat_RGBX: ZXing_ImageFormat = 67109122; +pub const ZXing_ImageFormat_XRGB: ZXing_ImageFormat = 67174915; +pub const ZXing_ImageFormat_BGRX: ZXing_ImageFormat = 67240192; +pub const ZXing_ImageFormat_XBGR: ZXing_ImageFormat = 67305985; +pub type ZXing_ImageFormat = ::core::ffi::c_uint; +pub const ZXing_BarcodeFormat_None: ZXing_BarcodeFormat = 0; +pub const ZXing_BarcodeFormat_Aztec: ZXing_BarcodeFormat = 1; +pub const ZXing_BarcodeFormat_Codabar: ZXing_BarcodeFormat = 2; +pub const ZXing_BarcodeFormat_Code39: ZXing_BarcodeFormat = 4; +pub const ZXing_BarcodeFormat_Code93: ZXing_BarcodeFormat = 8; +pub const ZXing_BarcodeFormat_Code128: ZXing_BarcodeFormat = 16; +pub const ZXing_BarcodeFormat_DataBar: ZXing_BarcodeFormat = 32; +pub const ZXing_BarcodeFormat_DataBarExpanded: ZXing_BarcodeFormat = 64; +pub const ZXing_BarcodeFormat_DataMatrix: ZXing_BarcodeFormat = 128; +pub const ZXing_BarcodeFormat_EAN8: ZXing_BarcodeFormat = 256; +pub const ZXing_BarcodeFormat_EAN13: ZXing_BarcodeFormat = 512; +pub const ZXing_BarcodeFormat_ITF: ZXing_BarcodeFormat = 1024; +pub const ZXing_BarcodeFormat_MaxiCode: ZXing_BarcodeFormat = 2048; +pub const ZXing_BarcodeFormat_PDF417: ZXing_BarcodeFormat = 4096; +pub const ZXing_BarcodeFormat_QRCode: ZXing_BarcodeFormat = 8192; +pub const ZXing_BarcodeFormat_UPCA: ZXing_BarcodeFormat = 16384; +pub const ZXing_BarcodeFormat_UPCE: ZXing_BarcodeFormat = 32768; +pub const ZXing_BarcodeFormat_MicroQRCode: ZXing_BarcodeFormat = 65536; +pub const ZXing_BarcodeFormat_RMQRCode: ZXing_BarcodeFormat = 131072; +pub const ZXing_BarcodeFormat_DXFilmEdge: ZXing_BarcodeFormat = 262144; +pub const ZXing_BarcodeFormat_LinearCodes: ZXing_BarcodeFormat = 313214; +pub const ZXing_BarcodeFormat_MatrixCodes: ZXing_BarcodeFormat = 211073; +pub const ZXing_BarcodeFormat_Any: ZXing_BarcodeFormat = 524287; +pub const ZXing_BarcodeFormat_Invalid: ZXing_BarcodeFormat = 4294967295; +pub type ZXing_BarcodeFormat = ::core::ffi::c_uint; +pub use self::ZXing_BarcodeFormat as ZXing_BarcodeFormats; +pub const ZXing_Binarizer_LocalAverage: ZXing_Binarizer = 0; +pub const ZXing_Binarizer_GlobalHistogram: ZXing_Binarizer = 1; +pub const ZXing_Binarizer_FixedThreshold: ZXing_Binarizer = 2; +pub const ZXing_Binarizer_BoolCast: ZXing_Binarizer = 3; +pub type ZXing_Binarizer = ::core::ffi::c_uint; +pub const ZXing_EanAddOnSymbol_Ignore: ZXing_EanAddOnSymbol = 0; +pub const ZXing_EanAddOnSymbol_Read: ZXing_EanAddOnSymbol = 1; +pub const ZXing_EanAddOnSymbol_Require: ZXing_EanAddOnSymbol = 2; +pub type ZXing_EanAddOnSymbol = ::core::ffi::c_uint; +pub const ZXing_TextMode_Plain: ZXing_TextMode = 0; +pub const ZXing_TextMode_ECI: ZXing_TextMode = 1; +pub const ZXing_TextMode_HRI: ZXing_TextMode = 2; +pub const ZXing_TextMode_Hex: ZXing_TextMode = 3; +pub const ZXing_TextMode_Escaped: ZXing_TextMode = 4; +pub type ZXing_TextMode = ::core::ffi::c_uint; +pub const ZXing_ContentType_Text: ZXing_ContentType = 0; +pub const ZXing_ContentType_Binary: ZXing_ContentType = 1; +pub const ZXing_ContentType_Mixed: ZXing_ContentType = 2; +pub const ZXing_ContentType_GS1: ZXing_ContentType = 3; +pub const ZXing_ContentType_ISO15434: ZXing_ContentType = 4; +pub const ZXing_ContentType_UnknownECI: ZXing_ContentType = 5; +pub type ZXing_ContentType = ::core::ffi::c_uint; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] -pub struct zxing_PointI { +pub struct ZXing_PointI { pub x: ::core::ffi::c_int, pub y: ::core::ffi::c_int, } #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] -pub struct zxing_Position { - pub topLeft: zxing_PointI, - pub topRight: zxing_PointI, - pub bottomRight: zxing_PointI, - pub bottomLeft: zxing_PointI, +pub struct ZXing_Position { + pub topLeft: ZXing_PointI, + pub topRight: ZXing_PointI, + pub bottomRight: ZXing_PointI, + pub bottomLeft: ZXing_PointI, } extern "C" { - pub fn zxing_ImageView_new( + pub fn ZXing_ImageView_new( data: *const u8, width: ::core::ffi::c_int, height: ::core::ffi::c_int, - format: zxing_ImageFormat, + format: ZXing_ImageFormat, rowStride: ::core::ffi::c_int, pixStride: ::core::ffi::c_int, - ) -> *mut zxing_ImageView; - pub fn zxing_ImageView_new_checked( + ) -> *mut ZXing_ImageView; + pub fn ZXing_ImageView_new_checked( data: *const u8, size: ::core::ffi::c_int, width: ::core::ffi::c_int, height: ::core::ffi::c_int, - format: zxing_ImageFormat, + format: ZXing_ImageFormat, rowStride: ::core::ffi::c_int, pixStride: ::core::ffi::c_int, - ) -> *mut zxing_ImageView; - pub fn zxing_ImageView_delete(iv: *mut zxing_ImageView); - pub fn zxing_ImageView_crop( - iv: *mut zxing_ImageView, + ) -> *mut ZXing_ImageView; + pub fn ZXing_ImageView_delete(iv: *mut ZXing_ImageView); + pub fn ZXing_ImageView_crop( + iv: *mut ZXing_ImageView, left: ::core::ffi::c_int, top: ::core::ffi::c_int, width: ::core::ffi::c_int, height: ::core::ffi::c_int, ); - pub fn zxing_ImageView_rotate(iv: *mut zxing_ImageView, degree: ::core::ffi::c_int); - pub fn zxing_BarcodeFormatsFromString(str_: *const ::core::ffi::c_char) -> zxing_BarcodeFormats; - pub fn zxing_BarcodeFormatFromString(str_: *const ::core::ffi::c_char) -> zxing_BarcodeFormat; - pub fn zxing_BarcodeFormatToString(format: zxing_BarcodeFormat) -> *mut ::core::ffi::c_char; - pub fn zxing_ReaderOptions_new() -> *mut zxing_ReaderOptions; - pub fn zxing_ReaderOptions_delete(opts: *mut zxing_ReaderOptions); - pub fn zxing_ReaderOptions_setTryHarder(opts: *mut zxing_ReaderOptions, tryHarder: bool); - pub fn zxing_ReaderOptions_setTryRotate(opts: *mut zxing_ReaderOptions, tryRotate: bool); - pub fn zxing_ReaderOptions_setTryInvert(opts: *mut zxing_ReaderOptions, tryInvert: bool); - pub fn zxing_ReaderOptions_setTryDownscale(opts: *mut zxing_ReaderOptions, tryDownscale: bool); - pub fn zxing_ReaderOptions_setIsPure(opts: *mut zxing_ReaderOptions, isPure: bool); - pub fn zxing_ReaderOptions_setReturnErrors(opts: *mut zxing_ReaderOptions, returnErrors: bool); - pub fn zxing_ReaderOptions_setFormats(opts: *mut zxing_ReaderOptions, formats: zxing_BarcodeFormats); - pub fn zxing_ReaderOptions_setBinarizer(opts: *mut zxing_ReaderOptions, binarizer: zxing_Binarizer); - pub fn zxing_ReaderOptions_setEanAddOnSymbol(opts: *mut zxing_ReaderOptions, eanAddOnSymbol: zxing_EanAddOnSymbol); - pub fn zxing_ReaderOptions_setTextMode(opts: *mut zxing_ReaderOptions, textMode: zxing_TextMode); - pub fn zxing_ReaderOptions_setMinLineCount(opts: *mut zxing_ReaderOptions, n: ::core::ffi::c_int); - pub fn zxing_ReaderOptions_setMaxNumberOfSymbols(opts: *mut zxing_ReaderOptions, n: ::core::ffi::c_int); - pub fn zxing_ReaderOptions_getTryHarder(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getTryRotate(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getTryInvert(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getTryDownscale(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getIsPure(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getReturnErrors(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getFormats(opts: *const zxing_ReaderOptions) -> zxing_BarcodeFormats; - pub fn zxing_ReaderOptions_getBinarizer(opts: *const zxing_ReaderOptions) -> zxing_Binarizer; - pub fn zxing_ReaderOptions_getEanAddOnSymbol(opts: *const zxing_ReaderOptions) -> zxing_EanAddOnSymbol; - pub fn zxing_ReaderOptions_getTextMode(opts: *const zxing_ReaderOptions) -> zxing_TextMode; - pub fn zxing_ReaderOptions_getMinLineCount(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; - pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; - pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; - pub fn zxing_PositionToString(position: zxing_Position) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_isValid(barcode: *const zxing_Barcode) -> bool; - pub fn zxing_Barcode_errorMsg(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_format(barcode: *const zxing_Barcode) -> zxing_BarcodeFormat; - pub fn zxing_Barcode_contentType(barcode: *const zxing_Barcode) -> zxing_ContentType; - pub fn zxing_Barcode_bytes(barcode: *const zxing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; - pub fn zxing_Barcode_bytesECI(barcode: *const zxing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; - pub fn zxing_Barcode_text(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_ecLevel(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_symbologyIdentifier(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_position(barcode: *const zxing_Barcode) -> zxing_Position; - pub fn zxing_Barcode_orientation(barcode: *const zxing_Barcode) -> ::core::ffi::c_int; - pub fn zxing_Barcode_hasECI(barcode: *const zxing_Barcode) -> bool; - pub fn zxing_Barcode_isInverted(barcode: *const zxing_Barcode) -> bool; - pub fn zxing_Barcode_isMirrored(barcode: *const zxing_Barcode) -> bool; - pub fn zxing_Barcode_lineCount(barcode: *const zxing_Barcode) -> ::core::ffi::c_int; - pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Barcode; - pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Barcodes; - pub fn zxing_Barcode_delete(barcode: *mut zxing_Barcode); - pub fn zxing_Barcodes_delete(barcodes: *mut zxing_Barcodes); - pub fn zxing_Barcodes_size(barcodes: *const zxing_Barcodes) -> ::core::ffi::c_int; - pub fn zxing_Barcodes_at(barcodes: *const zxing_Barcodes, i: ::core::ffi::c_int) -> *const zxing_Barcode; - pub fn zxing_Barcodes_move(barcodes: *mut zxing_Barcodes, i: ::core::ffi::c_int) -> *mut zxing_Barcode; - pub fn zxing_LastErrorMsg() -> *mut ::core::ffi::c_char; - pub fn zxing_free(ptr: *mut ::core::ffi::c_void); + pub fn ZXing_ImageView_rotate(iv: *mut ZXing_ImageView, degree: ::core::ffi::c_int); + pub fn ZXing_BarcodeFormatsFromString(str_: *const ::core::ffi::c_char) -> ZXing_BarcodeFormats; + pub fn ZXing_BarcodeFormatFromString(str_: *const ::core::ffi::c_char) -> ZXing_BarcodeFormat; + pub fn ZXing_BarcodeFormatToString(format: ZXing_BarcodeFormat) -> *mut ::core::ffi::c_char; + pub fn ZXing_ReaderOptions_new() -> *mut ZXing_ReaderOptions; + pub fn ZXing_ReaderOptions_delete(opts: *mut ZXing_ReaderOptions); + pub fn ZXing_ReaderOptions_setTryHarder(opts: *mut ZXing_ReaderOptions, tryHarder: bool); + pub fn ZXing_ReaderOptions_setTryRotate(opts: *mut ZXing_ReaderOptions, tryRotate: bool); + pub fn ZXing_ReaderOptions_setTryInvert(opts: *mut ZXing_ReaderOptions, tryInvert: bool); + pub fn ZXing_ReaderOptions_setTryDownscale(opts: *mut ZXing_ReaderOptions, tryDownscale: bool); + pub fn ZXing_ReaderOptions_setIsPure(opts: *mut ZXing_ReaderOptions, isPure: bool); + pub fn ZXing_ReaderOptions_setReturnErrors(opts: *mut ZXing_ReaderOptions, returnErrors: bool); + pub fn ZXing_ReaderOptions_setFormats(opts: *mut ZXing_ReaderOptions, formats: ZXing_BarcodeFormats); + pub fn ZXing_ReaderOptions_setBinarizer(opts: *mut ZXing_ReaderOptions, binarizer: ZXing_Binarizer); + pub fn ZXing_ReaderOptions_setEanAddOnSymbol(opts: *mut ZXing_ReaderOptions, eanAddOnSymbol: ZXing_EanAddOnSymbol); + pub fn ZXing_ReaderOptions_setTextMode(opts: *mut ZXing_ReaderOptions, textMode: ZXing_TextMode); + pub fn ZXing_ReaderOptions_setMinLineCount(opts: *mut ZXing_ReaderOptions, n: ::core::ffi::c_int); + pub fn ZXing_ReaderOptions_setMaxNumberOfSymbols(opts: *mut ZXing_ReaderOptions, n: ::core::ffi::c_int); + pub fn ZXing_ReaderOptions_getTryHarder(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getTryRotate(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getTryInvert(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getTryDownscale(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getIsPure(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getReturnErrors(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getFormats(opts: *const ZXing_ReaderOptions) -> ZXing_BarcodeFormats; + pub fn ZXing_ReaderOptions_getBinarizer(opts: *const ZXing_ReaderOptions) -> ZXing_Binarizer; + pub fn ZXing_ReaderOptions_getEanAddOnSymbol(opts: *const ZXing_ReaderOptions) -> ZXing_EanAddOnSymbol; + pub fn ZXing_ReaderOptions_getTextMode(opts: *const ZXing_ReaderOptions) -> ZXing_TextMode; + pub fn ZXing_ReaderOptions_getMinLineCount(opts: *const ZXing_ReaderOptions) -> ::core::ffi::c_int; + pub fn ZXing_ReaderOptions_getMaxNumberOfSymbols(opts: *const ZXing_ReaderOptions) -> ::core::ffi::c_int; + pub fn ZXing_ContentTypeToString(type_: ZXing_ContentType) -> *mut ::core::ffi::c_char; + pub fn ZXing_PositionToString(position: ZXing_Position) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_isValid(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_errorMsg(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_format(barcode: *const ZXing_Barcode) -> ZXing_BarcodeFormat; + pub fn ZXing_Barcode_contentType(barcode: *const ZXing_Barcode) -> ZXing_ContentType; + pub fn ZXing_Barcode_bytes(barcode: *const ZXing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn ZXing_Barcode_bytesECI(barcode: *const ZXing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn ZXing_Barcode_text(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_ecLevel(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_symbologyIdentifier(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_position(barcode: *const ZXing_Barcode) -> ZXing_Position; + pub fn ZXing_Barcode_orientation(barcode: *const ZXing_Barcode) -> ::core::ffi::c_int; + pub fn ZXing_Barcode_hasECI(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_isInverted(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_isMirrored(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_lineCount(barcode: *const ZXing_Barcode) -> ::core::ffi::c_int; + pub fn ZXing_ReadBarcode(iv: *const ZXing_ImageView, opts: *const ZXing_ReaderOptions) -> *mut ZXing_Barcode; + pub fn ZXing_ReadBarcodes(iv: *const ZXing_ImageView, opts: *const ZXing_ReaderOptions) -> *mut ZXing_Barcodes; + pub fn ZXing_Barcode_delete(barcode: *mut ZXing_Barcode); + pub fn ZXing_Barcodes_delete(barcodes: *mut ZXing_Barcodes); + pub fn ZXing_Barcodes_size(barcodes: *const ZXing_Barcodes) -> ::core::ffi::c_int; + pub fn ZXing_Barcodes_at(barcodes: *const ZXing_Barcodes, i: ::core::ffi::c_int) -> *const ZXing_Barcode; + pub fn ZXing_Barcodes_move(barcodes: *mut ZXing_Barcodes, i: ::core::ffi::c_int) -> *mut ZXing_Barcode; + pub fn ZXing_LastErrorMsg() -> *mut ::core::ffi::c_char; + pub fn ZXing_free(ptr: *mut ::core::ffi::c_void); } diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 9df557eff9..95af608812 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -60,7 +60,7 @@ fn c2r_str(str: *mut c_char) -> String { let mut res = String::new(); if !str.is_null() { unsafe { res = CStr::from_ptr(str).to_string_lossy().to_string() }; - unsafe { zxing_free(str as *mut c_void) }; + unsafe { ZXing_free(str as *mut c_void) }; } res } @@ -69,21 +69,21 @@ fn c2r_vec(buf: *mut u8, len: c_int) -> Vec { let mut res = Vec::::new(); if !buf.is_null() && len > 0 { unsafe { res = std::slice::from_raw_parts(buf, len as usize).to_vec() }; - unsafe { zxing_free(buf as *mut c_void) }; + unsafe { ZXing_free(buf as *mut c_void) }; } res } fn last_error() -> Error { - match unsafe { zxing_LastErrorMsg().as_mut() } { - None => panic!("Internal error: zxing_LastErrorMsg() returned NULL"), + match unsafe { ZXing_LastErrorMsg().as_mut() } { + None => panic!("Internal error: ZXing_LastErrorMsg() returned NULL"), Some(error) => Error::InvalidInput(c2r_str(error)), } } macro_rules! last_error_or { ($expr:expr) => { - match unsafe { zxing_LastErrorMsg().as_mut() } { + match unsafe { ZXing_LastErrorMsg().as_mut() } { None => Ok($expr), Some(error) => Err(Error::InvalidInput(c2r_str(error))), } @@ -95,7 +95,7 @@ macro_rules! make_zxing_enum { #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq)] pub enum $name { - $($field = paste! { [] },)* + $($field = paste! { [] },)* } } } @@ -105,7 +105,7 @@ macro_rules! make_zxing_flags { flags! { #[repr(u32)] pub enum $name: c_uint { - $($field = paste! { [] },)* + $($field = paste! { [] },)* } } } @@ -131,22 +131,22 @@ pub type BarcodeFormats = FlagSet; impl Display for BarcodeFormat { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", unsafe { c2r_str(zxing_BarcodeFormatToString(BarcodeFormats::from(*self).bits())) }) + write!(f, "{}", unsafe { c2r_str(ZXing_BarcodeFormatToString(BarcodeFormats::from(*self).bits())) }) } } impl Display for ContentType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", unsafe { c2r_str(zxing_ContentTypeToString(transmute(*self))) }) + write!(f, "{}", unsafe { c2r_str(ZXing_ContentTypeToString(transmute(*self))) }) } } #[derive(Debug, PartialEq)] -struct ImageViewOwner<'a>(*mut zxing_ImageView, PhantomData<&'a u8>); +struct ImageViewOwner<'a>(*mut ZXing_ImageView, PhantomData<&'a u8>); impl Drop for ImageViewOwner<'_> { fn drop(&mut self) { - unsafe { zxing_ImageView_delete(self.0) } + unsafe { ZXing_ImageView_delete(self.0) } } } #[derive(Debug, Clone, PartialEq)] @@ -180,11 +180,11 @@ impl<'a> ImageView<'a> { row_stride: U, pix_stride: U, ) -> Result { - let iv = zxing_ImageView_new( + let iv = ZXing_ImageView_new( ptr, Self::try_into_int(width)?, Self::try_into_int(height)?, - format as zxing_ImageFormat, + format as ZXing_ImageFormat, Self::try_into_int(row_stride)?, Self::try_into_int(pix_stride)?, ); @@ -197,12 +197,12 @@ impl<'a> ImageView<'a> { pub fn from_slice>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { unsafe { - let iv = zxing_ImageView_new_checked( + let iv = ZXing_ImageView_new_checked( data.as_ptr(), data.len() as c_int, Self::try_into_int(width)?, Self::try_into_int(height)?, - format as zxing_ImageFormat, + format as ZXing_ImageFormat, 0, 0, ); @@ -215,12 +215,12 @@ impl<'a> ImageView<'a> { } pub fn cropped(self, left: i32, top: i32, width: i32, height: i32) -> Self { - unsafe { zxing_ImageView_crop((self.0).0, left, top, width, height) } + unsafe { ZXing_ImageView_crop((self.0).0, left, top, width, height) } self } pub fn rotated(self, degree: i32) -> Self { - unsafe { zxing_ImageView_rotate((self.0).0, degree) } + unsafe { ZXing_ImageView_rotate((self.0).0, degree) } self } } @@ -253,11 +253,11 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { } } -pub struct ReaderOptions(*mut zxing_ReaderOptions); +pub struct ReaderOptions(*mut ZXing_ReaderOptions); impl Drop for ReaderOptions { fn drop(&mut self) { - unsafe { zxing_ReaderOptions_delete(self.0) } + unsafe { ZXing_ReaderOptions_delete(self.0) } } } @@ -276,18 +276,18 @@ impl AsRef for ReaderOptions { macro_rules! property { ($name:ident, $type:ty) => { pub fn $name(self, v: impl Into<$type>) -> Self { - paste! { unsafe { [](self.0, transmute(v.into())) } }; + paste! { unsafe { [](self.0, transmute(v.into())) } }; self } paste! { pub fn [](&mut self, v : impl Into<$type>) -> &mut Self { - unsafe { [](self.0, transmute(v.into())) }; + unsafe { [](self.0, transmute(v.into())) }; self } pub fn [](&self) -> $type { - unsafe { transmute([](self.0)) } + unsafe { transmute([](self.0)) } } } }; @@ -295,7 +295,7 @@ macro_rules! property { impl ReaderOptions { pub fn new() -> Self { - unsafe { ReaderOptions(zxing_ReaderOptions_new()) } + unsafe { ReaderOptions(ZXing_ReaderOptions_new()) } } property!(try_harder, bool); @@ -312,15 +312,15 @@ impl ReaderOptions { property!(min_line_count, i32); } -pub struct Barcode(*mut zxing_Barcode); +pub struct Barcode(*mut ZXing_Barcode); impl Drop for Barcode { fn drop(&mut self) { - unsafe { zxing_Barcode_delete(self.0) } + unsafe { ZXing_Barcode_delete(self.0) } } } -pub type PointI = zxing_PointI; +pub type PointI = ZXing_PointI; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct Position { @@ -339,7 +339,7 @@ impl Display for PointI { impl Display for Position { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", unsafe { - c2r_str(zxing_PositionToString(*(self as *const Position as *const zxing_Position))) + c2r_str(ZXing_PositionToString(*(self as *const Position as *const ZXing_Position))) }) } } @@ -347,7 +347,7 @@ impl Display for Position { macro_rules! getter { ($r_name:ident, $c_name:ident, $conv:expr, $type:ty) => { pub fn $r_name(&self) -> $type { - paste! { unsafe { $conv([](self.0)) } } + paste! { unsafe { $conv([](self.0)) } } } }; } @@ -369,17 +369,17 @@ impl Barcode { pub fn bytes(&self) -> Vec { let mut len: c_int = 0; - unsafe { c2r_vec(zxing_Barcode_bytes(self.0, &mut len), len) } + unsafe { c2r_vec(ZXing_Barcode_bytes(self.0, &mut len), len) } } pub fn bytes_eci(&self) -> Vec { let mut len: c_int = 0; - unsafe { c2r_vec(zxing_Barcode_bytesECI(self.0, &mut len), len) } + unsafe { c2r_vec(ZXing_Barcode_bytesECI(self.0, &mut len), len) } } } pub fn barcode_formats_from_string(str: impl AsRef) -> Result { let cstr = CString::new(str.as_ref())?; - let res = unsafe { BarcodeFormats::new_unchecked(zxing_BarcodeFormatsFromString(cstr.as_ptr())) }; + let res = unsafe { BarcodeFormats::new_unchecked(ZXing_BarcodeFormatsFromString(cstr.as_ptr())) }; match res.bits() { u32::MAX => last_error_or!(BarcodeFormats::default()), 0 => Ok(BarcodeFormats::full()), @@ -395,14 +395,14 @@ where { let iv_: ImageView = image.try_into().map_err(Into::into)?; unsafe { - let results = zxing_ReadBarcodes((iv_.0).0, opts.as_ref().0); + let results = ZXing_ReadBarcodes((iv_.0).0, opts.as_ref().0); if !results.is_null() { - let size = zxing_Barcodes_size(results); + let size = ZXing_Barcodes_size(results); let mut vec = Vec::::with_capacity(size as usize); for i in 0..size { - vec.push(Barcode(zxing_Barcodes_move(results, i))); + vec.push(Barcode(ZXing_Barcodes_move(results, i))); } - zxing_Barcodes_delete(results); + ZXing_Barcodes_delete(results); Ok(vec) } else { Err(last_error()) From 4c5c0732c10f96b7ca5948684d26bcd6a0a7be91 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 23:55:24 +0100 Subject: [PATCH 278/587] rust: add `.try_downscale(!fast)` just to make `cargo fmt --check` happy --- wrappers/rust/examples/demo.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index 2fcad932ac..87d51f43eb 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -22,7 +22,8 @@ fn main() -> anyhow::Result<()> { .formats(formats) .try_harder(!fast) .try_invert(!fast) - .try_rotate(!fast); + .try_rotate(!fast) + .try_downscale(!fast); #[cfg(feature = "image")] let barcodes = read_barcodes(&image, &opts)?; From 8590eebdfe3137ad9af12e4a6827b8d57a462579 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 14 Feb 2024 22:11:01 +0100 Subject: [PATCH 279/587] android: upgrade GradlePlugin and a few dependencies --- wrappers/android/gradle/libs.versions.toml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/wrappers/android/gradle/libs.versions.toml b/wrappers/android/gradle/libs.versions.toml index 2f72e440dc..6ee60f25c6 100644 --- a/wrappers/android/gradle/libs.versions.toml +++ b/wrappers/android/gradle/libs.versions.toml @@ -1,20 +1,17 @@ [versions] -androidCoreDesugaring = "2.0.3" -androidGradlePlugin = "8.1.4" +androidGradlePlugin = "8.2.2" androidCompileSdk = "34" androidMinSdk = "21" androidTargetSdk = "33" -androidx-activity = "1.8.1" androidx-appcompat = "1.6.1" -androidx-camera = "1.3.0" +androidx-camera = "1.3.1" androidx-core = "1.12.0" androidx-constraintLayout = "2.1.4" android-material = "1.11.0-rc01" kotlin = "1.9.10" -zxing-core = "3.5.2" +zxing-core = "3.5.3" [libraries] -androidx-activityktx = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" } From c4392ebe14099e70896a1b883bbad2e589f0b6ea Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 14 Feb 2024 22:12:20 +0100 Subject: [PATCH 280/587] android: fix build warning by adding @OptIn(Experimental...) in demo app --- .../android/app/src/main/java/zxingcpp/app/MainActivity.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 2184b1be0d..db92429a4a 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -31,9 +31,11 @@ import android.os.Build import android.os.Bundle import android.os.Environment import android.view.View +import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity import androidx.camera.camera2.interop.Camera2CameraControl import androidx.camera.camera2.interop.CaptureRequestOptions +import androidx.camera.camera2.interop.ExperimentalCamera2Interop import androidx.camera.core.AspectRatio import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis @@ -129,6 +131,7 @@ class MainActivity : AppCompatActivity() { } } + @OptIn(ExperimentalCamera2Interop::class) private fun bindCameraUseCases() = binding.viewFinder.post { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) From 14c625df184179033580797f50009dcba873bc5c Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 14 Feb 2024 22:17:49 +0100 Subject: [PATCH 281/587] android: set ALSO_INVERTED hint in Java code path of demo app --- wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index db92429a4a..56605a151b 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -219,6 +219,8 @@ class MainActivity : AppCompatActivity() { hints[DecodeHintType.POSSIBLE_FORMATS] = arrayListOf(BarcodeFormat.QR_CODE) if (binding.tryHarder.isChecked) hints[DecodeHintType.TRY_HARDER] = true + if (binding.tryInvert.isChecked) + hints[DecodeHintType.ALSO_INVERTED] = true resultText = try { val bitmap = BinaryBitmap( From 6763109e6936b896275f8eca1915c2dd07b050b8 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 14 Feb 2024 22:56:24 +0100 Subject: [PATCH 282/587] android: "-DANDROID_ARM_NEON=ON", "-DBUILD_WRITERS=OFF" The ARM_NEON=ON setting does not seem to have an effect. --- wrappers/android/zxingcpp/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/android/zxingcpp/build.gradle.kts b/wrappers/android/zxingcpp/build.gradle.kts index 7f1e43c99c..013279032c 100644 --- a/wrappers/android/zxingcpp/build.gradle.kts +++ b/wrappers/android/zxingcpp/build.gradle.kts @@ -25,7 +25,7 @@ android { } externalNativeBuild { cmake { - arguments("-DCMAKE_BUILD_TYPE=RelWithDebInfo") + arguments("-DCMAKE_BUILD_TYPE=RelWithDebInfo", "-DANDROID_ARM_NEON=ON", "-DBUILD_WRITERS=OFF") } } From 0af083de582d0108a9d9c9c8db070d97cc937ec2 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 15 Feb 2024 10:09:41 +0100 Subject: [PATCH 283/587] ios: remove initWithFormats initializer Since it requires specifying all parameters and that isn't usually very useful. It's better to always use `init` and the corresponding `setter` functions. --- .../Sources/Wrapper/Reader/ZXIReaderOptions.h | 18 -------- .../Wrapper/Reader/ZXIReaderOptions.mm | 41 ------------------- 2 files changed, 59 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h index d8221a585f..82d94e84a1 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h @@ -48,24 +48,6 @@ typedef NS_ENUM(NSInteger, ZXITextMode) { @property(nonatomic) ZXIEanAddOnSymbol eanAddOnSymbol; @property(nonatomic) ZXITextMode textMode; -- (instancetype)initWithFormats:(NSArray*)formats - tryHarder:(BOOL)tryHarder - tryRotate:(BOOL)tryRotate - tryInvert:(BOOL)tryInvert - tryDownscale:(BOOL)tryDownscale - isPure:(BOOL)isPure - binarizer:(ZXIBinarizer)binarizer - downscaleFactor:(NSInteger)downscaleFactor - downscaleThreshold:(NSInteger)downscaleThreshold - minLineCount:(NSInteger)minLineCount - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols - tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode - validateCode39CheckSum:(BOOL)validateCode39CheckSum - validateITFCheckSum:(BOOL)validateITFCheckSum - returnCodabarStartEnd:(BOOL)returnCodabarStartEnd - returnErrors:(BOOL)returnErrors - eanAddOnSymbol:(ZXIEanAddOnSymbol)eanAddOnSymbol - textMode:(ZXITextMode)textMode; @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm index 724fc600bf..1eca538a54 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm @@ -17,47 +17,6 @@ -(instancetype)init { return self; } -- (instancetype)initWithFormats:(NSArray*)formats - tryHarder:(BOOL)tryHarder - tryRotate:(BOOL)tryRotate - tryInvert:(BOOL)tryInvert - tryDownscale:(BOOL)tryDownscale - isPure:(BOOL)isPure - binarizer:(ZXIBinarizer)binarizer - downscaleFactor:(NSInteger)downscaleFactor - downscaleThreshold:(NSInteger)downscaleThreshold - minLineCount:(NSInteger)minLineCount - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols - tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode - validateCode39CheckSum:(BOOL)validateCode39CheckSum - validateITFCheckSum:(BOOL)validateITFCheckSum - returnCodabarStartEnd:(BOOL)returnCodabarStartEnd - returnErrors:(BOOL)returnErrors - eanAddOnSymbol:(ZXIEanAddOnSymbol)eanAddOnSymbol - textMode:(ZXITextMode)textMode { - self = [super init]; - self.cppOpts = ZXing::ReaderOptions(); - self.formats = formats; - self.tryHarder = tryHarder; - self.tryRotate = tryRotate; - self.tryInvert = tryInvert; - self.tryDownscale = tryDownscale; - self.isPure = isPure; - self.binarizer = binarizer; - self.downscaleFactor = downscaleFactor; - self.downscaleThreshold = downscaleThreshold; - self.minLineCount = minLineCount; - self.maxNumberOfSymbols = maxNumberOfSymbols; - self.tryCode39ExtendedMode = tryCode39ExtendedMode; - self.validateCode39CheckSum = validateCode39CheckSum; - self.validateITFCheckSum = validateITFCheckSum; - self.returnCodabarStartEnd = returnCodabarStartEnd; - self.returnErrors = returnErrors; - self.eanAddOnSymbol = eanAddOnSymbol; - self.textMode = textMode; - return self; -} - -(BOOL)tryHarder { return self.cppOpts.tryHarder(); } From c661a3f22ee038394c1f9debff0a2d329be4ea05 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 16 Feb 2024 00:36:43 +0100 Subject: [PATCH 284/587] BitHacks: fix random QRCode content on pre-Haswell Windows machines (#727) Turns out `__lzcnt()` can function as a random number generator on old Windows boxes. Nice. This fixes #727 and supersedes #728, as it takes care to prevent even more wrong results by calling it for non 32 bit values. A C++20 build would have fixed this as well. Thanks to @Abooow786 for the effort of debugging this. --- core/src/BitHacks.h | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index eefede8781..c8979289c0 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -42,12 +42,16 @@ inline int NumberOfLeadingZeros(T x) return std::countl_zero(static_cast>(x)); #else if constexpr (sizeof(x) <= 4) { + static_assert(sizeof(x) == 4, "NumberOfLeadingZeros not implemented for 8 and 16 bit ints."); if (x == 0) return 32; #ifdef ZX_HAS_GCC_BUILTINS return __builtin_clz(x); #elif defined(ZX_HAS_MSC_BUILTINS) - return __lzcnt(x); + unsigned long where; + if (_BitScanReverse(&where, x)) + return 31 - static_cast(where); + return 32; #else int n = 0; if ((x & 0xFFFF0000) == 0) { n = n + 16; x = x << 16; } @@ -62,9 +66,7 @@ inline int NumberOfLeadingZeros(T x) return 64; #ifdef ZX_HAS_GCC_BUILTINS return __builtin_clzll(x); -#elif defined(ZX_HAS_MSC_BUILTINS) - return __lzcnt64(x); -#else +#else // including ZX_HAS_MSC_BUILTINS int n = NumberOfLeadingZeros(static_cast(x >> 32)); if (n == 32) n += NumberOfLeadingZeros(static_cast(x)); @@ -85,6 +87,7 @@ inline int NumberOfTrailingZeros(T v) return std::countr_zero(static_cast>(v)); #else if constexpr (sizeof(v) <= 4) { + static_assert(sizeof(v) == 4, "NumberOfTrailingZeros not implemented for 8 and 16 bit ints."); #ifdef ZX_HAS_GCC_BUILTINS return __builtin_ctz(v); #elif defined(ZX_HAS_MSC_BUILTINS) @@ -106,21 +109,7 @@ inline int NumberOfTrailingZeros(T v) } else { #ifdef ZX_HAS_GCC_BUILTINS return __builtin_ctzll(v); -#elif defined(ZX_HAS_MSC_BUILTINS) - unsigned long where; - #if defined(_WIN64) - if (_BitScanForward64(&where, v)) - return static_cast(where); - #elif defined(_WIN32) - if (_BitScanForward(&where, static_cast(v))) - return static_cast(where); - if (_BitScanForward(&where, static_cast(v >> 32))) - return static_cast(where + 32); - #else - #error "Implementation of __builtin_ctzll required" - #endif - return 64; -#else +#else // including ZX_HAS_MSC_BUILTINS int n = NumberOfTrailingZeros(static_cast(v)); if (n == 32) n += NumberOfTrailingZeros(static_cast(v >> 32)); From d33f5052ba949453c9c86f442e935f7ce1cf07e5 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 16 Feb 2024 00:38:24 +0100 Subject: [PATCH 285/587] ImageView: Add ImageFormat::LumX for 2-byte grey + alpha input --- core/src/ImageView.h | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/ImageView.h b/core/src/ImageView.h index a7b6f74add..a1b32f8854 100644 --- a/core/src/ImageView.h +++ b/core/src/ImageView.h @@ -16,6 +16,7 @@ enum class ImageFormat : uint32_t { None = 0, Lum = 0x01000000, + LumX = 0x02000000, RGB = 0x03000102, BGR = 0x03020100, RGBX = 0x04000102, From 295653c987b471f4883f34580020c3a4f4f3ab3c Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 16 Feb 2024 01:15:07 +0100 Subject: [PATCH 286/587] ZXingReader: add hidden cli options -rotate and -channels for debugging Use the new option to read the image files without forcing stb_image to return rgb data also in the black box testing related ImageLoader. --- example/ZXingReader.cpp | 28 ++++++++++++++++++++-------- test/blackbox/ImageLoader.cpp | 26 +++++++++++++++----------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 724c3881ae..5a566dcf6e 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -31,7 +31,7 @@ static void PrintUsage(const char* exePath) << " -norotate Don't try rotated image during detection (faster)\n" << " -noinvert Don't search for inverted codes during detection (faster)\n" << " -noscale Don't try downscaled images during detection (faster)\n" - << " -format \n" + << " -formats \n" << " Only detect given format(s) (faster)\n" << " -single Stop after the first barcode is detected (faster)\n" << " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n" @@ -54,8 +54,8 @@ static void PrintUsage(const char* exePath) std::cout << "Formats can be lowercase, with or without '-', separated by ',' and/or '|'\n"; } -static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& oneLine, bool& bytesOnly, - std::vector& filePaths, std::string& outPath) +static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& oneLine, bool& bytesOnly, int& forceChannels, + int& rotate, std::vector& filePaths, std::string& outPath) { #ifdef ZXING_BUILD_EXPERIMENTAL_API options.setTryDenoise(true); @@ -122,6 +122,14 @@ static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& o if (++i == argc) return false; outPath = argv[i]; + } else if (is("-channels")) { + if (++i == argc) + return false; + forceChannels = atoi(argv[i]); + } else if (is("-rotate")) { + if (++i == argc) + return false; + rotate = atoi(argv[i]); } else if (is("-help") || is("--help")) { PrintUsage(argv[0]); exit(0); @@ -166,29 +174,33 @@ int main(int argc, char* argv[]) std::string outPath; bool oneLine = false; bool bytesOnly = false; + int forceChannels = 0; + int rotate = 0; int ret = 0; options.setTextMode(TextMode::HRI); options.setEanAddOnSymbol(EanAddOnSymbol::Read); - if (!ParseOptions(argc, argv, options, oneLine, bytesOnly, filePaths, outPath)) { + if (!ParseOptions(argc, argv, options, oneLine, bytesOnly, forceChannels, rotate, filePaths, outPath)) { PrintUsage(argv[0]); return -1; } - std::cout.setf(std::ios::boolalpha); for (const auto& filePath : filePaths) { int width, height, channels; - std::unique_ptr buffer(stbi_load(filePath.c_str(), &width, &height, &channels, 3), stbi_image_free); + std::unique_ptr buffer(stbi_load(filePath.c_str(), &width, &height, &channels, forceChannels), + stbi_image_free); if (buffer == nullptr) { std::cerr << "Failed to read image: " << filePath << " (" << stbi_failure_reason() << ")" << "\n"; return -1; } + channels = forceChannels ? forceChannels : channels; - ImageView image{buffer.get(), width, height, ImageFormat::RGB}; - auto results = ReadBarcodes(image, options); + auto ImageFormatFromChannels = std::array{ImageFormat::None, ImageFormat::Lum, ImageFormat::LumX, ImageFormat::RGB, ImageFormat::RGBX}; + ImageView image{buffer.get(), width, height, ImageFormatFromChannels.at(channels)}; + auto results = ReadBarcodes(image.rotated(rotate), options); // if we did not find anything, insert a dummy to produce some output for each file if (results.empty()) diff --git a/test/blackbox/ImageLoader.cpp b/test/blackbox/ImageLoader.cpp index c3f7ad5165..bef256a6f5 100644 --- a/test/blackbox/ImageLoader.cpp +++ b/test/blackbox/ImageLoader.cpp @@ -9,6 +9,7 @@ #include "BinaryBitmap.h" #include "ImageView.h" +#include #include #include #include @@ -27,18 +28,21 @@ class STBImage : public ImageView void load(const fs::path& imgPath) { - int width, height, colors; - _memory.reset(stbi_load(imgPath.string().c_str(), &width, &height, &colors, 3)); + int width, height, channels; + _memory.reset(stbi_load(imgPath.string().c_str(), &width, &height, &channels, 0)); if (_memory == nullptr) - throw std::runtime_error("Failed to read image"); -#if 1 - auto* img = _memory.get(); - for (int i = 0; i < width * height; ++i ) - img[i] = RGBToLum(img[3 * i + 0], img[3 * i + 1], img[3 * i + 2]); - ImageView::operator=({_memory.get(), width, height, ImageFormat::Lum}); -#else - ImageView::operator=({_memory.get(), width, height, ImageFormat::RGB}); -#endif + throw std::runtime_error("Failed to read image: " + imgPath.string() + " (" + stbi_failure_reason() + ")"); + + auto ImageFormatFromChannels = std::array{ImageFormat::None, ImageFormat::Lum, ImageFormat::LumX, ImageFormat::RGB, ImageFormat::RGBX}; + ImageView::operator=({_memory.get(), width, height, ImageFormatFromChannels.at(channels)}); + + // preconvert from RGB -> Lum to do this only once instead of for each rotation + if (_format == ImageFormat::RGB) { + auto* img = _memory.get(); + for (int i = 0; i < width * height; ++i) + img[i] = RGBToLum(img[3 * i + 0], img[3 * i + 1], img[3 * i + 2]); + ImageView::operator=({_memory.get(), width, height, ImageFormat::Lum}); + } } operator bool() const { return _data; } From fd367dbfe78a7515244bbcd88d11cd964f2ff3fe Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 17 Feb 2024 00:31:09 +0100 Subject: [PATCH 287/587] c-API: add NULL termination to uint8_t* return values (bytes) * There was an extra byte allocated but not set to 0. * Don't crash if len pointer argument is NULL. * Print `bytesECI()` in testing code. * Add comment about ZXing_LastErrorMsg() return values. --- core/src/ZXingC.cpp | 21 +++++++++------------ core/src/ZXingC.h | 1 + wrappers/c/ZXingCTest.c | 1 + 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/core/src/ZXingC.cpp b/core/src/ZXingC.cpp index 05579f29f0..2986362495 100644 --- a/core/src/ZXingC.cpp +++ b/core/src/ZXingC.cpp @@ -25,26 +25,23 @@ template R transmute_cast(const T& v) return *(const R*)(&v); } -static char* copy(std::string_view sv) +template +P copy(const C& c) { - auto ret = (char*)malloc(sv.size() + 1); + auto ret = (P)malloc(c.size() + 1); if (ret) { - strncpy(ret, sv.data(), sv.size()); - ret[sv.size()] = '\0'; + memcpy(ret, c.data(), c.size()); + ret[c.size()] = 0; } return ret; } static uint8_t* copy(const ByteArray& ba, int* len) { - *len = Size(ba); - - auto ret = (uint8_t*)malloc(*len + 1); - if (ret) - memcpy(ret, ba.data(), *len); - else - *len = 0; - + // for convencience and as a safety measure, we NULL terminate even byte arrays + auto ret = copy(ba); + if (len) + *len = ret ? Size(ba) : 0; return ret; } diff --git a/core/src/ZXingC.h b/core/src/ZXingC.h index 63d249ed6c..921ef85554 100644 --- a/core/src/ZXingC.h +++ b/core/src/ZXingC.h @@ -217,6 +217,7 @@ int ZXing_Barcodes_size(const ZXing_Barcodes* barcodes); const ZXing_Barcode* ZXing_Barcodes_at(const ZXing_Barcodes* barcodes, int i); ZXing_Barcode* ZXing_Barcodes_move(ZXing_Barcodes* barcodes, int i); +/* ZXing_LastErrorMsg() returns NULL in case there is no last error and a copy of the string otherwise. */ char* ZXing_LastErrorMsg(); void ZXing_free(void* ptr); diff --git a/wrappers/c/ZXingCTest.c b/wrappers/c/ZXingCTest.c index fd1b73def3..fbac6edd1d 100644 --- a/wrappers/c/ZXingCTest.c +++ b/wrappers/c/ZXingCTest.c @@ -81,6 +81,7 @@ int main(int argc, char** argv) const ZXing_Barcode* barcode = ZXing_Barcodes_at(barcodes, i); printF("Text : %s\n", ZXing_Barcode_text(barcode)); + printF("BytesECI : %s\n", (char*)ZXing_Barcode_bytesECI(barcode, NULL)); printF("Format : %s\n", ZXing_BarcodeFormatToString(ZXing_Barcode_format(barcode))); printF("Content : %s\n", ZXing_ContentTypeToString(ZXing_Barcode_contentType(barcode))); printF("Identifier : %s\n", ZXing_Barcode_symbologyIdentifier(barcode)); From 36ba3198a5bf3bc7c90528cd9e8d170019b64966 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 18 Feb 2024 21:15:24 +0100 Subject: [PATCH 288/587] android: replace license header info with SPDX-License-Identifier --- .../app/src/main/java/zxingcpp/app/MainActivity.kt | 13 +------------ .../src/main/java/zxingcpp/app/PreviewOverlay.kt | 13 +------------ .../src/main/java/zxingcpp/BarcodeReader.kt | 13 +------------ 3 files changed, 3 insertions(+), 36 deletions(-) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 56605a151b..18b196155d 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -1,18 +1,7 @@ /* * Copyright 2021 Axel Waggershauser -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. */ +// SPDX-License-Identifier: Apache-2.0 package zxingcpp.app diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt b/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt index 8a79cb117f..90f4e7caa9 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt @@ -1,19 +1,8 @@ /* * Copyright 2022 Axel Waggershauser * Copyright 2022 Markus Fisch -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. */ +// SPDX-License-Identifier: Apache-2.0 package com.example.zxingcppdemo diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index 748c00e834..b7b9d1f36a 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -1,18 +1,7 @@ /* * Copyright 2021 Axel Waggershauser -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. */ +// SPDX-License-Identifier: Apache-2.0 package zxingcpp From 6e80aacddeb7916b984c9a007e0d4d86d1cac8bf Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 19 Feb 2024 00:21:00 +0100 Subject: [PATCH 289/587] rust: implement new `BarcodeReader` API Replace `ReaderOptions` + `read_barcodes()` with `BarcodeReader` and `BarcodeReader.read()`. For the motivation behind this change, see https://github.com/zxing-cpp/zxing-cpp/discussions/696#discussioncomment-8459148 Also replace `barcode_formats_from_string` with more idiomatic `BarcodeFormats::from_str`. --- wrappers/rust/Cargo.toml | 2 +- wrappers/rust/README.md | 8 +- wrappers/rust/examples/demo.rs | 8 +- wrappers/rust/src/lib.rs | 171 ++++++++++++++++----------------- wrappers/rust/src/tests.rs | 19 ++-- 5 files changed, 103 insertions(+), 105 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 7f1bbd8d36..27961cfb5d 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zxing-cpp" -version = "0.2.2" +version = "0.3.0" edition = "2021" # authors = ["Axel Waggershauser "] license = "Apache-2.0" diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index 65c28dcc18..bda4f075cd 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -15,21 +15,21 @@ In your Cargo.toml: # `bundled` causes cargo to compile and statically link an up to # date version of the c++ core library. This is the most convenient # and safe way to build the library. -zxing-cpp = { version = "0.2.2", features = ["bundled", "image"] } +zxing-cpp = { version = "0.3.0", features = ["bundled", "image"] } ``` Simple example usage: ```rust -use zxing_cpp::{ImageView, ReaderOptions, BarcodeFormat, read_barcodes}; +use zxing_cpp::{BarcodeFormat, BarcodeReader, ImageView}; fn main() -> anyhow::Result<()> { let image = image::open("some-image-file.jpg")?; - let opts = ReaderOptions::default() + let reader = BarcodeReader::new() .formats(BarcodeFormat::QRCode | BarcodeFormat::LinearCodes) .try_invert(false); - let barcodes = read_barcodes(&image, &opts)?; + let barcodes = reader.read(&image)?; if barcodes.is_empty() { println!("No barcode found."); diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index 87d51f43eb..cee5d48509 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -17,8 +17,8 @@ fn main() -> anyhow::Result<()> { #[cfg(not(feature = "image"))] let iv = ImageView::from_slice(&lum_img, lum_img.width(), lum_img.height(), ImageFormat::Lum)?; - let formats = barcode_formats_from_string(formats.unwrap_or_default())?; - let opts = ReaderOptions::default() + let formats = BarcodeFormats::from_str(formats.unwrap_or_default())?; + let reader = BarcodeReader::new() .formats(formats) .try_harder(!fast) .try_invert(!fast) @@ -26,9 +26,9 @@ fn main() -> anyhow::Result<()> { .try_downscale(!fast); #[cfg(feature = "image")] - let barcodes = read_barcodes(&image, &opts)?; + let barcodes = reader.read(&image)?; #[cfg(not(feature = "image"))] - let barcodes = read_barcodes(iv, opts)?; + let barcodes = reader.read(iv)?; if barcodes.is_empty() { println!("No barcode found."); diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 95af608812..142fdea573 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -127,8 +127,6 @@ make_zxing_flags!(BarcodeFormat { MaxiCode, PDF417, QRCode, UPCA, UPCE, MicroQRCode, RMQRCode, DXFilmEdge, LinearCodes, MatrixCodes, Any }); -pub type BarcodeFormats = FlagSet; - impl Display for BarcodeFormat { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", unsafe { c2r_str(ZXing_BarcodeFormatToString(BarcodeFormats::from(*self).bits())) }) @@ -141,6 +139,24 @@ impl Display for ContentType { } } +pub type BarcodeFormats = FlagSet; + +pub trait FromStr: Sized { + fn from_str(str: impl AsRef) -> Result; +} + +impl FromStr for BarcodeFormats { + fn from_str(str: impl AsRef) -> Result { + let cstr = CString::new(str.as_ref())?; + let res = unsafe { BarcodeFormats::new_unchecked(ZXing_BarcodeFormatsFromString(cstr.as_ptr())) }; + match res.bits() { + u32::MAX => last_error_or!(BarcodeFormats::default()), + 0 => Ok(BarcodeFormats::full()), + _ => Ok(res), + } + } +} + #[derive(Debug, PartialEq)] struct ImageViewOwner<'a>(*mut ZXing_ImageView, PhantomData<&'a u8>); @@ -253,65 +269,6 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { } } -pub struct ReaderOptions(*mut ZXing_ReaderOptions); - -impl Drop for ReaderOptions { - fn drop(&mut self) { - unsafe { ZXing_ReaderOptions_delete(self.0) } - } -} - -impl Default for ReaderOptions { - fn default() -> Self { - Self::new() - } -} - -impl AsRef for ReaderOptions { - fn as_ref(&self) -> &ReaderOptions { - self - } -} - -macro_rules! property { - ($name:ident, $type:ty) => { - pub fn $name(self, v: impl Into<$type>) -> Self { - paste! { unsafe { [](self.0, transmute(v.into())) } }; - self - } - - paste! { - pub fn [](&mut self, v : impl Into<$type>) -> &mut Self { - unsafe { [](self.0, transmute(v.into())) }; - self - } - - pub fn [](&self) -> $type { - unsafe { transmute([](self.0)) } - } - } - }; -} - -impl ReaderOptions { - pub fn new() -> Self { - unsafe { ReaderOptions(ZXing_ReaderOptions_new()) } - } - - property!(try_harder, bool); - property!(try_rotate, bool); - property!(try_invert, bool); - property!(try_downscale, bool); - property!(is_pure, bool); - property!(return_errors, bool); - property!(formats, BarcodeFormats); - property!(text_mode, TextMode); - property!(binarizer, Binarizer); - property!(ean_add_on_symbol, EanAddOnSymbol); - property!(max_number_of_symbols, i32); - property!(min_line_count, i32); -} - pub struct Barcode(*mut ZXing_Barcode); impl Drop for Barcode { @@ -377,35 +334,77 @@ impl Barcode { } } -pub fn barcode_formats_from_string(str: impl AsRef) -> Result { - let cstr = CString::new(str.as_ref())?; - let res = unsafe { BarcodeFormats::new_unchecked(ZXing_BarcodeFormatsFromString(cstr.as_ptr())) }; - match res.bits() { - u32::MAX => last_error_or!(BarcodeFormats::default()), - 0 => Ok(BarcodeFormats::full()), - _ => Ok(res), +pub struct BarcodeReader(*mut ZXing_ReaderOptions); + +impl Drop for BarcodeReader { + fn drop(&mut self) { + unsafe { ZXing_ReaderOptions_delete(self.0) } + } +} + +impl Default for BarcodeReader { + fn default() -> Self { + Self::new() } } -pub fn read_barcodes<'a, IV, RO>(image: IV, opts: RO) -> Result, Error> -where - IV: TryInto>, - IV::Error: Into, - RO: AsRef, -{ - let iv_: ImageView = image.try_into().map_err(Into::into)?; - unsafe { - let results = ZXing_ReadBarcodes((iv_.0).0, opts.as_ref().0); - if !results.is_null() { - let size = ZXing_Barcodes_size(results); - let mut vec = Vec::::with_capacity(size as usize); - for i in 0..size { - vec.push(Barcode(ZXing_Barcodes_move(results, i))); +macro_rules! property { + ($name:ident, $type:ty) => { + pub fn $name(self, v: impl Into<$type>) -> Self { + paste! { unsafe { [](self.0, transmute(v.into())) } }; + self + } + + paste! { + pub fn [](&mut self, v : impl Into<$type>) -> &mut Self { + unsafe { [](self.0, transmute(v.into())) }; + self + } + + pub fn [](&self) -> $type { + unsafe { transmute([](self.0)) } + } + } + }; +} + +impl BarcodeReader { + pub fn new() -> Self { + unsafe { BarcodeReader(ZXing_ReaderOptions_new()) } + } + + property!(try_harder, bool); + property!(try_rotate, bool); + property!(try_invert, bool); + property!(try_downscale, bool); + property!(is_pure, bool); + property!(return_errors, bool); + property!(formats, BarcodeFormats); + property!(text_mode, TextMode); + property!(binarizer, Binarizer); + property!(ean_add_on_symbol, EanAddOnSymbol); + property!(max_number_of_symbols, i32); + property!(min_line_count, i32); + + pub fn read<'a, IV>(&self, image: IV) -> Result, Error> + where + IV: TryInto>, + IV::Error: Into, + { + let iv_: ImageView = image.try_into().map_err(Into::into)?; + unsafe { + let results = ZXing_ReadBarcodes((iv_.0).0, self.0); + if !results.is_null() { + let size = ZXing_Barcodes_size(results); + let mut vec = Vec::::with_capacity(size as usize); + for i in 0..size { + vec.push(Barcode(ZXing_Barcodes_move(results, i))); + } + ZXing_Barcodes_delete(results); + Ok(vec) + } else { + Err(last_error()) } - ZXing_Barcodes_delete(results); - Ok(vec) - } else { - Err(last_error()) } } } diff --git a/wrappers/rust/src/tests.rs b/wrappers/rust/src/tests.rs index 952e4e3141..465dfe65d0 100644 --- a/wrappers/rust/src/tests.rs +++ b/wrappers/rust/src/tests.rs @@ -8,14 +8,14 @@ mod tests { use crate::*; #[test] - fn barcode_formats_from_string_valid() { - let formats = barcode_formats_from_string("qrcode,linearcodes").unwrap(); + fn barcode_formats_from_str_valid() { + let formats = BarcodeFormats::from_str("qrcode,linearcodes").unwrap(); assert_eq!(formats, BarcodeFormat::QRCode | BarcodeFormat::LinearCodes); } #[test] - fn reader_options() { - let mut o1 = ReaderOptions::default(); + fn barcode_reader_new() { + let mut o1 = BarcodeReader::new(); assert_eq!(o1.get_formats(), BarcodeFormat::None); assert_eq!(o1.get_try_harder(), true); o1.set_formats(BarcodeFormat::EAN8); @@ -23,7 +23,7 @@ mod tests { o1.set_try_harder(false); assert_eq!(o1.get_try_harder(), false); - o1 = ReaderOptions::default().is_pure(true).text_mode(TextMode::Hex); + o1 = BarcodeReader::new().is_pure(true).text_mode(TextMode::Hex); assert_eq!(o1.get_formats(), BarcodeFormat::None); assert_eq!(o1.get_try_harder(), true); assert_eq!(o1.get_is_pure(), true); @@ -32,19 +32,18 @@ mod tests { #[test] #[should_panic] - fn barcode_formats_from_string_invalid() { - let _ = barcode_formats_from_string("qrcoder").unwrap(); + fn barcode_formats_from_str_invalid() { + let _ = BarcodeFormats::from_str("qrcoder").unwrap(); } #[test] - fn read_barcodes_pure() { + fn read_pure() { let mut data = Vec::::new(); for v in "0000101000101101011110111101011011101010100111011100101000100101110010100000".chars() { data.push(if v == '0' { 255 } else { 0 }); } let iv = ImageView::from_slice(&data, data.len(), 1, ImageFormat::Lum).unwrap(); - let op = ReaderOptions::default().binarizer(Binarizer::BoolCast); - let res = read_barcodes(&iv, &op).unwrap(); + let res = BarcodeReader::new().binarizer(Binarizer::BoolCast).read(&iv).unwrap(); let expected = "96385074"; From cfb35cd62ff5ecdf9f58d412a5a3ca71c98d49eb Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 19 Feb 2024 10:57:26 +0100 Subject: [PATCH 290/587] python: change indentation in setup.py from space to tab (fix #731) --- wrappers/python/setup.py | 114 +++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/wrappers/python/setup.py b/wrappers/python/setup.py index f3c3b38c7d..16c223b7ac 100644 --- a/wrappers/python/setup.py +++ b/wrappers/python/setup.py @@ -9,74 +9,74 @@ # Adapted from here: https://github.com/pybind/cmake_example/blob/master/setup.py class CMakeExtension(Extension): - def __init__(self, name, sourcedir=''): - Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) + def __init__(self, name, sourcedir=''): + Extension.__init__(self, name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) class CMakeBuild(build_ext): - def build_extension(self, ext): - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable, - '-DVERSION_INFO=' + self.distribution.get_version()] + def build_extension(self, ext): + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, + '-DPYTHON_EXECUTABLE=' + sys.executable, + '-DVERSION_INFO=' + self.distribution.get_version()] - cfg = 'Debug' if self.debug else 'Release' - build_args = ['--config', cfg, - '-j', '8'] + cfg = 'Debug' if self.debug else 'Release' + build_args = ['--config', cfg, + '-j', '8'] - if platform.system() == "Windows": - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] - if sys.maxsize > 2**32: - cmake_args += ['-A', 'x64'] - else: - cmake_args += ['-A', 'Win32'] - build_args += ['--', '/m'] - else: - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] + if platform.system() == "Windows": + cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] + if sys.maxsize > 2**32: + cmake_args += ['-A', 'x64'] + else: + cmake_args += ['-A', 'Win32'] + build_args += ['--', '/m'] + else: + cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) - subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp) - subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) + subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp) + subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() + long_description = fh.read() setup( - name='zxing-cpp', - # setuptools_scm cannot be used because of the structure of the project until the following issues are solved: - # https://github.com/pypa/setuptools_scm/issues/357 - # https://github.com/pypa/pip/issues/7549 - # Because pip works on a copy of current directory in a temporary directory, the temporary directory does not hold - # the .git directory of the repo, so that setuptools_scm cannot guess the current version. - # use_scm_version={ - # "root": "../..", - # "version_scheme": "guess-next-dev", - # "local_scheme": "no-local-version", - # "tag_regex": "v?([0-9]+.[0-9]+.[0-9]+)", - # }, - version='2.2.0', - description='Python bindings for the zxing-cpp barcode library', - long_description=long_description, - long_description_content_type="text/markdown", - author='ZXing-C++ Community', - author_email='zxingcpp@gmail.com', - url='https://github.com/zxing-cpp/zxing-cpp', - license='Apache License 2.0', - keywords=['barcode'], - classifiers=[ - "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Topic :: Multimedia :: Graphics", - ], - python_requires=">=3.6", - ext_modules=[CMakeExtension('zxingcpp')], - cmdclass=dict(build_ext=CMakeBuild), - zip_safe=False, + name='zxing-cpp', + # setuptools_scm cannot be used because of the structure of the project until the following issues are solved: + # https://github.com/pypa/setuptools_scm/issues/357 + # https://github.com/pypa/pip/issues/7549 + # Because pip works on a copy of current directory in a temporary directory, the temporary directory does not hold + # the .git directory of the repo, so that setuptools_scm cannot guess the current version. + # use_scm_version={ + # "root": "../..", + # "version_scheme": "guess-next-dev", + # "local_scheme": "no-local-version", + # "tag_regex": "v?([0-9]+.[0-9]+.[0-9]+)", + # }, + version='2.2.0', + description='Python bindings for the zxing-cpp barcode library', + long_description=long_description, + long_description_content_type="text/markdown", + author='ZXing-C++ Community', + author_email='zxingcpp@gmail.com', + url='https://github.com/zxing-cpp/zxing-cpp', + license='Apache License 2.0', + keywords=['barcode'], + classifiers=[ + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Topic :: Multimedia :: Graphics", + ], + python_requires=">=3.6", + ext_modules=[CMakeExtension('zxingcpp')], + cmdclass=dict(build_ext=CMakeBuild), + zip_safe=False, ) From 74319f24c725d78c397c5ff2a7ba5a0925822b6f Mon Sep 17 00:00:00 2001 From: ISNing <33572057+ISNing@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:07:26 +0800 Subject: [PATCH 291/587] Add Kotlin/Native Wrapper (#719) This adds a Kotlin/Native wrapper based on the C-API. --- .github/workflows/ci.yml | 39 +++ .github/workflows/publish-kn.yml | 54 ++++ wrappers/kn/.gitignore | 44 ++++ wrappers/kn/README.md | 56 +++++ wrappers/kn/build.gradle.kts | 213 ++++++++++++++++ wrappers/kn/gradle.properties | 2 + wrappers/kn/gradle/libs.versions.toml | 9 + wrappers/kn/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + wrappers/kn/gradlew | 234 ++++++++++++++++++ wrappers/kn/gradlew.bat | 89 +++++++ wrappers/kn/settings.gradle.kts | 20 ++ .../src/nativeMain/kotlin/zxingcpp/Barcode.kt | 142 +++++++++++ .../kotlin/zxingcpp/BarcodeFormat.kt | 47 ++++ .../kotlin/zxingcpp/BarcodeReader.kt | 121 +++++++++ .../nativeMain/kotlin/zxingcpp/ImageView.kt | 58 +++++ wrappers/kn/src/nativeTest/kotlin/Test.kt | 41 +++ 17 files changed, 1174 insertions(+) create mode 100644 .github/workflows/publish-kn.yml create mode 100644 wrappers/kn/.gitignore create mode 100644 wrappers/kn/README.md create mode 100644 wrappers/kn/build.gradle.kts create mode 100644 wrappers/kn/gradle.properties create mode 100644 wrappers/kn/gradle/libs.versions.toml create mode 100644 wrappers/kn/gradle/wrapper/gradle-wrapper.jar create mode 100644 wrappers/kn/gradle/wrapper/gradle-wrapper.properties create mode 100755 wrappers/kn/gradlew create mode 100644 wrappers/kn/gradlew.bat create mode 100644 wrappers/kn/settings.gradle.kts create mode 100644 wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt create mode 100644 wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeFormat.kt create mode 100644 wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeReader.kt create mode 100644 wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt create mode 100644 wrappers/kn/src/nativeTest/kotlin/Test.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40db6ba466..373055a741 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,45 @@ jobs: name: android-artifacts path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-debug.aar" + build-kn: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./wrappers/kn + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout toolchain initializer repository + uses: actions/checkout@v4 + with: + repository: ISNing/kn-toolchain-initializer + path: wrappers/kn/.kn-toolchain-initializer + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Initialize Kotlin/Native toolchain + working-directory: wrappers/kn/.kn-toolchain-initializer + run: ./gradlew build -DkotlinVersion=1.9.22 + + - name: Export Toolchain properties + run: | + echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-linux-x86_64-1.9.22" > local.properties + + - name: Run test for linuxX64 target + run: | + ./gradlew linuxX64Test + build-python: runs-on: ${{ matrix.os }} strategy: diff --git a/.github/workflows/publish-kn.yml b/.github/workflows/publish-kn.yml new file mode 100644 index 0000000000..aa925f9282 --- /dev/null +++ b/.github/workflows/publish-kn.yml @@ -0,0 +1,54 @@ +name: publish-kn + +on: + workflow_dispatch: + +jobs: + publish: + name: Library Publish + + runs-on: macos-latest + + defaults: + run: + working-directory: ./wrappers/kn + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout toolchain initializer repository + uses: actions/checkout@v4 + with: + repository: ISNing/kn-toolchain-initializer + path: wrappers/kn/.kn-toolchain-initializer + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Initialize Kotlin/Native toolchain + working-directory: wrappers/kn/.kn-toolchain-initializer + run: ./gradlew build -DkotlinVersion=1.9.22 + + - name: Export Toolchain properties + run: | + echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-macos-x86_64-1.9.22" > local.properties + + - name: Publish Library + env: + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} + ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} + run: | + export PATH="$PATH:$(brew --prefix llvm@15)/bin" + ./gradlew publishAllPublicationsToSonatypeRepository diff --git a/wrappers/kn/.gitignore b/wrappers/kn/.gitignore new file mode 100644 index 0000000000..cc4fca248e --- /dev/null +++ b/wrappers/kn/.gitignore @@ -0,0 +1,44 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +local.properties diff --git a/wrappers/kn/README.md b/wrappers/kn/README.md new file mode 100644 index 0000000000..6c41d5a2f3 --- /dev/null +++ b/wrappers/kn/README.md @@ -0,0 +1,56 @@ +# ZXing-C++ Kotlin/Native Library + +## Install + +The easiest way to use the library is to fetch if from _mavenCentral_. Simply add + +```gradle +implementation("io.github.zxing-cpp:kotlin-native:2.2.1") +``` + +to your `build.gradle.kts` file in the `dependencies` section of `nativeMain` source set. + +## Use + +A trivial use case looks like this: + +```kotlin +import zxingcpp.BarcodeFormat +import zxingcpp.BarcodeReader +import zxingcpp.ImageFormat +import zxingcpp.ImageView + +val data: ByteArray = ... // the image data +val width: Int = ... // the image width +val height: Int = ... // the image height +val format = ImageFormat.Lum // ImageFormat.Lum assumes grey scale image data + +val image = ImageView(data, width, height, format) +val barcodeReader = BarcodeReader().apply { + formats = setOf(BarcodeFormat.EAN13, BarcodeFormat.QRCode) + tryHarder = true + maxNumberOfSymbols = 3 + // more options, see documentation +} + +barcodeReader.read(image).joinToString("\n") { barcode: Barcode -> + "${barcode.format} (${barcode.contentType}): ${barcode.text}" +} +``` + +Here you have to load your image into memory by yourself and pass the decoded data to the constructor of `ImageView`. + +## Build locally + +1. Install JDK, CMake and Android NDK(With `$ANDROID_NDK` correctly configured) and ensure their + executable binaries appear in `$PATH`. +2. Prepare kotlin/native toolchain (You can easily do this by cloning + [K/N Toolchain Initializer](https://github.com/ISNing/kn-toolchain-initializer) and executing `gradle build` + so that kotlin will download toolchains needed into user's home dir.). +3. Ensure there's `run_konan` available in `$PATH` or specify path of kotlin-native toolchain in `local.properties` + like `konan.dir=/home/user/.konan/kotlin-native-prebuilt-linux-x86_64-1.9.22`. +4. Ensure there's `llvm-ar` available in `$PATH` for mingwX64 target building. + +And then you can build the project from the command line: + + $ ./gradlew :assemble diff --git a/wrappers/kn/build.gradle.kts b/wrappers/kn/build.gradle.kts new file mode 100644 index 0000000000..a3865412fe --- /dev/null +++ b/wrappers/kn/build.gradle.kts @@ -0,0 +1,213 @@ +import io.github.isning.gradle.plugins.cmake.params.CustomCMakeParams +import io.github.isning.gradle.plugins.cmake.params.entries.CustomCMakeCacheEntries +import io.github.isning.gradle.plugins.cmake.params.entries.asCMakeParams +import io.github.isning.gradle.plugins.cmake.params.entries.platform.ModifiablePlatformEntriesImpl +import io.github.isning.gradle.plugins.cmake.params.entries.plus +import io.github.isning.gradle.plugins.cmake.params.plus +import io.github.isning.gradle.plugins.kn.krossCompile.invoke +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import java.util.* + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.krossCompile) + `maven-publish` + signing +} + +group = "io.github.zxing-cpp" +version = "2.2.1" + +Properties().apply { + rootProject.file("local.properties").takeIf { it.exists() && it.isFile }?.let { load(it.reader()) } +}.onEach { (key, value) -> + if (key is String) ext[key] = value +} + +val hostOs = System.getProperty("os.name") + +repositories { + mavenCentral() + google() +} + +kotlin { + val androidTargets = { + listOf( + androidNativeArm32(), + androidNativeArm64(), + androidNativeX86(), + androidNativeX64(), + ) + } + val appleTargets = { + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + macosX64(), + macosArm64(), + watchosX64(), + watchosArm32(), + watchosArm64(), + watchosSimulatorArm64(), + tvosX64(), + tvosArm64(), + tvosSimulatorArm64(), + ) + } + val linuxTargets = { + listOf( + linuxX64(), + linuxArm64(), + ) + } + // TODO: Linking failed, keep up with https://youtrack.jetbrains.com/issue/KT-65671 +// val windowsTargets = { +// listOf( +// mingwX64(), +// ) +// } + val enabledTargetList = mutableListOf() + enabledTargetList.addAll(androidTargets()) + enabledTargetList.addAll(linuxTargets()) + // TODO: Linking failed, keep up with https://youtrack.jetbrains.com/issue/KT-65671 +// enabledTargetList.addAll(windowsTargets()) + + if (hostOs == "Mac OS X") enabledTargetList.addAll(appleTargets()) +} + +krossCompile { + libraries { + val zxingCpp by creating { + sourceDir = file("../../core").absolutePath + outputPath = "" + libraryArtifactNames = listOf("libZXing.a") + + cinterop { + packageName = "zxingcpp.cinterop" + headers = listOf("$sourceDir/src/ZXingC.h") + } + cmake.apply { + val buildPath = project.layout.buildDirectory.dir("cmake").get().asFile.absolutePath + + "/{projectName}/{targetName}" + configParams { + buildDir = buildPath + } + configParams += (ModifiablePlatformEntriesImpl().apply { + buildType = "Release" + buildSharedLibs = false + } + CustomCMakeCacheEntries( + mapOf( + "BUILD_C_API" to "ON", + "BUILD_WRITERS" to "OFF", + ) + )).asCMakeParams + buildParams { + buildDir = buildPath + config = "Release" + } + buildParams += CustomCMakeParams(listOf("-j16")) + } + + androidNativeX64.konan() + androidNativeX86.konan() + androidNativeArm32.konan() + androidNativeArm64.konan() + + // TODO: Find a way to build linux targets with cxx20. Detail: https://github.com/zxing-cpp/zxing-cpp/pull/719#discussion_r1485701269 + linuxX64.konan { + cmake { + configParams += CustomCMakeCacheEntries( + mapOf( + "CMAKE_CXX_STANDARD" to "17", + ) + ).asCMakeParams + } + } + linuxArm64.konan { + cmake { + configParams += CustomCMakeCacheEntries( + mapOf( + "CMAKE_CXX_STANDARD" to "17", + ) + ).asCMakeParams + } + } + // TODO: Linking failed, keep up with https://youtrack.jetbrains.com/issue/KT-65671 +// mingwX64.konan() + + if (hostOs == "Mac OS X") { + iosX64.konan() + iosArm64.konan() + iosSimulatorArm64.konan() + macosX64.konan() + macosArm64.konan() + watchosX64.konan() + watchosArm32.konan() + watchosArm64.konan() + watchosSimulatorArm64.konan() + tvosX64.konan() + tvosArm64.konan() + tvosSimulatorArm64.konan() + } + } + } +} + +publishing { + publications.withType().all { + artifactId = artifactId.replace(project.name, "kotlin-native") + groupId = project.group.toString() + version = project.version.toString() + + pom { + name = "zxing-cpp" + description = "Wrapper for zxing-cpp barcode image processing library" + url = "https://github.com/zxing-cpp/zxing-cpp" + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = "zxing-cpp" + name = "zxing-cpp community" + email = "zxingcpp@gmail.com" + } + } + scm { + connection = "scm:git:git://github.com/zxing-cpp/zxing-cpp.git" + developerConnection = "scm:git:git://github.com/zxing-cpp/zxing-cpp.git" + url = "https://github.com/zxing-cpp/zxing-cpp" + } + } + } + repositories { + maven { + name = "sonatype" + + val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" + setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) + + credentials { + val ossrhUsername: String? by project + val ossrhPassword: String? by project + username = ossrhUsername + password = ossrhPassword + } + } + } +} + +signing { + val signingKey: String? by project + val signingPassword: String? by project + if (signingKey != null && signingPassword != null) { + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications) + } +} diff --git a/wrappers/kn/gradle.properties b/wrappers/kn/gradle.properties new file mode 100644 index 0000000000..8aa580cdc6 --- /dev/null +++ b/wrappers/kn/gradle.properties @@ -0,0 +1,2 @@ +kotlin.code.style=official +kotlin.mpp.enableCInteropCommonization=true \ No newline at end of file diff --git a/wrappers/kn/gradle/libs.versions.toml b/wrappers/kn/gradle/libs.versions.toml new file mode 100644 index 0000000000..96d7471f4d --- /dev/null +++ b/wrappers/kn/gradle/libs.versions.toml @@ -0,0 +1,9 @@ +[versions] +kotlin = "1.9.22" +krossCompile = "0.1.6" + +[libraries] + +[plugins] +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +krossCompile = { id = "io.github.isning.gradle.plugins.kn.krossCompile", version.ref = "krossCompile" } diff --git a/wrappers/kn/gradle/wrapper/gradle-wrapper.jar b/wrappers/kn/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/wrappers/kn/gradlew.bat b/wrappers/kn/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/wrappers/kn/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/wrappers/kn/settings.gradle.kts b/wrappers/kn/settings.gradle.kts new file mode 100644 index 0000000000..b6a233e3a2 --- /dev/null +++ b/wrappers/kn/settings.gradle.kts @@ -0,0 +1,20 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "zxing-cpp" diff --git a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt new file mode 100644 index 0000000000..26f86fc788 --- /dev/null +++ b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt @@ -0,0 +1,142 @@ +/* +* Copyright 2024 ISNing +*/ +// SPDX-License-Identifier: Apache-2.0 + +package zxingcpp + +import cnames.structs.ZXing_Barcode +import cnames.structs.ZXing_Barcodes +import kotlinx.cinterop.* +import zxingcpp.cinterop.* +import zxingcpp.cinterop.ZXing_ContentType.* +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.ref.createCleaner + +@OptIn(ExperimentalForeignApi::class) +enum class ContentType(internal val cValue: ZXing_ContentType) { + Text(ZXing_ContentType_Text), + Binary(ZXing_ContentType_Binary), + Mixed(ZXing_ContentType_Mixed), + GS1(ZXing_ContentType_GS1), + ISO15434(ZXing_ContentType_ISO15434), + UnknownECI(ZXing_ContentType_UnknownECI); +} + +@OptIn(ExperimentalForeignApi::class) +fun ZXing_ContentType.toKObject(): ContentType { + return ContentType.entries.first { it.cValue == this } +} + +data class PointI( + val x: Int, + val y: Int +) + +@OptIn(ExperimentalForeignApi::class) +fun ZXing_PointI.toKObject(): PointI = PointI(x, y) + +data class Position( + val topLeft: PointI, + val topRight: PointI, + val bottomRight: PointI, + val bottomLeft: PointI, +) + +@OptIn(ExperimentalForeignApi::class) +fun ZXing_Position.toKObject(): Position = Position( + topLeft.toKObject(), + topRight.toKObject(), + bottomRight.toKObject(), + bottomLeft.toKObject(), +) + +@OptIn(ExperimentalForeignApi::class) +class Barcode(val cValue: CValuesRef) { + val isValid: Boolean + get() = ZXing_Barcode_isValid(cValue) + val errorMsg: String? by lazy { + ZXing_Barcode_errorMsg(cValue)?.toKStringNullPtrHandledAndFree() + } + val format: BarcodeFormat by lazy { + ZXing_Barcode_format(cValue).parseIntoBarcodeFormat().first { it != BarcodeFormat.None } + } + val contentType: ContentType by lazy { + ZXing_Barcode_contentType(cValue).toKObject() + } + + val bytes: ByteArray? by lazy { + memScoped { + val len = alloc() + (ZXing_Barcode_bytes(cValue, len.ptr)?.run { + readBytes(len.value).also { ZXing_free(this) } + } ?: throw OutOfMemoryError()).takeUnless { it.isEmpty() } + } + } + + val bytesECI: ByteArray? by lazy { + memScoped { + val len = alloc() + (ZXing_Barcode_bytesECI(cValue, len.ptr)?.run { + readBytes(len.value).also { ZXing_free(this) } + } ?: throw OutOfMemoryError()).takeUnless { it.isEmpty() } + } + } + + val text: String? by lazy { + ZXing_Barcode_text(cValue)?.toKStringNullPtrHandledAndFree() + } + val ecLevel: String? by lazy { + ZXing_Barcode_ecLevel(cValue)?.toKStringNullPtrHandledAndFree() + } + val symbologyIdentifier: String? by lazy { + ZXing_Barcode_symbologyIdentifier(cValue)?.toKStringNullPtrHandledAndFree() + } + val position: Position by lazy { + ZXing_Barcode_position(cValue).useContents { toKObject() } + } + val orientation: Int + get() = ZXing_Barcode_orientation(cValue) + val hasECI: Boolean + get() = ZXing_Barcode_hasECI(cValue) + val isInverted: Boolean + get() = ZXing_Barcode_isInverted(cValue) + val isMirrored: Boolean + get() = ZXing_Barcode_isMirrored(cValue) + val lineCount: Int + get() = ZXing_Barcode_lineCount(cValue) + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val cleaner = createCleaner(cValue) { ZXing_Barcode_delete(it) } + + override fun toString(): String { + return "Barcode(" + + "cValue=$cValue, " + + "bytes=${bytes?.contentToString()}, " + + "bytesECI=${bytesECI?.contentToString()}, " + + "lineCount=$lineCount, " + + "isMirrored=$isMirrored, " + + "isInverted=$isInverted, " + + "hasECI=$hasECI, " + + "orientation=$orientation, " + + "position=$position, " + + "symbologyIdentifier=$symbologyIdentifier, " + + "ecLevel=$ecLevel, " + + "text=$text, " + + "contentType=$contentType, " + + "format=$format, " + + "errorMsg=$errorMsg, " + + "isValid=$isValid" + + ")" + } +} + +@OptIn(ExperimentalForeignApi::class) +fun CValuesRef.toKObject(): Barcode = Barcode(this) + +@OptIn(ExperimentalForeignApi::class) +fun CValuesRef.toKObject(): List = mutableListOf().apply { + for (i in 0.. = + BarcodeFormat.entries.filter { this.or(it.rawValue) == this }.toSet() + +@OptIn(ExperimentalForeignApi::class) +fun Iterable.toValue(): ZXing_BarcodeFormat = + this.map { it.rawValue }.reduce { acc, format -> acc.or(format) } diff --git a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeReader.kt b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeReader.kt new file mode 100644 index 0000000000..317cfbc727 --- /dev/null +++ b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeReader.kt @@ -0,0 +1,121 @@ +/* +* Copyright 2024 ISNing +*/ +// SPDX-License-Identifier: Apache-2.0 + +package zxingcpp + +import cnames.structs.ZXing_ReaderOptions +import kotlinx.cinterop.* +import zxingcpp.cinterop.* +import zxingcpp.cinterop.ZXing_Binarizer.* +import zxingcpp.cinterop.ZXing_EanAddOnSymbol.* +import zxingcpp.cinterop.ZXing_TextMode.* +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.ref.createCleaner + + +@OptIn(ExperimentalForeignApi::class) +internal fun CPointer?.toKStringNullPtrHandledAndFree(): String? = (this ?: throw OutOfMemoryError()).run { + toKString().also { ZXing_free(this) }.ifEmpty { null } +} + + +@OptIn(ExperimentalForeignApi::class) +class BarcodeReader : ReaderOptions() { + @Throws(BarcodeReadingException::class) + fun read(imageView: ImageView): List = + ZXing_ReadBarcodes(imageView.cValue, cValue)?.let { cValues -> cValues.toKObject().also { ZXing_Barcodes_delete(cValues) } } + ?: throw BarcodeReadingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree()) +} + +class BarcodeReadingException(message: String?) : Exception("Failed to read barcodes: $message") + + +@OptIn(ExperimentalForeignApi::class) +open class ReaderOptions { + var tryHarder: Boolean + get() = ZXing_ReaderOptions_getTryHarder(cValue) + set(value) = ZXing_ReaderOptions_setTryHarder(cValue, value) + var tryRotate: Boolean + get() = ZXing_ReaderOptions_getTryRotate(cValue) + set(value) = ZXing_ReaderOptions_setTryRotate(cValue, value) + var tryDownscale: Boolean + get() = ZXing_ReaderOptions_getTryDownscale(cValue) + set(value) = ZXing_ReaderOptions_setTryDownscale(cValue, value) + var tryInvert: Boolean + get() = ZXing_ReaderOptions_getTryInvert(cValue) + set(value) = ZXing_ReaderOptions_setTryInvert(cValue, value) + var isPure: Boolean + get() = ZXing_ReaderOptions_getIsPure(cValue) + set(value) = ZXing_ReaderOptions_setIsPure(cValue, value) + var returnErrors: Boolean + get() = ZXing_ReaderOptions_getReturnErrors(cValue) + set(value) = ZXing_ReaderOptions_setReturnErrors(cValue, value) + var binarizer: Binarizer + get() = Binarizer.fromCValue(ZXing_ReaderOptions_getBinarizer(cValue)) + set(value) = ZXing_ReaderOptions_setBinarizer(cValue, value.cValue) + var formats: Set + get() = ZXing_ReaderOptions_getFormats(cValue).parseIntoBarcodeFormat() + set(value) = ZXing_ReaderOptions_setFormats(cValue, value.toValue()) + var eanAddOnSymbol: EanAddOnSymbol + get() = EanAddOnSymbol.fromCValue(ZXing_ReaderOptions_getEanAddOnSymbol(cValue)) + set(value) = ZXing_ReaderOptions_setEanAddOnSymbol(cValue, value.cValue) + var textMode: TextMode + get() = TextMode.fromCValue(ZXing_ReaderOptions_getTextMode(cValue)) + set(value) = ZXing_ReaderOptions_setTextMode(cValue, value.cValue) + var minLineCount: Int + get() = ZXing_ReaderOptions_getMinLineCount(cValue) + set(value) = ZXing_ReaderOptions_setMinLineCount(cValue, value) + var maxNumberOfSymbols: Int + get() = ZXing_ReaderOptions_getMaxNumberOfSymbols(cValue) + set(value) = ZXing_ReaderOptions_setMaxNumberOfSymbols(cValue, value) + + val cValue: CValuesRef? = ZXing_ReaderOptions_new() + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val cleaner = createCleaner(cValue) { ZXing_ReaderOptions_delete(it) } +} + +@OptIn(ExperimentalForeignApi::class) +enum class Binarizer(internal val cValue: ZXing_Binarizer) { + LocalAverage(ZXing_Binarizer_LocalAverage), + GlobalHistogram(ZXing_Binarizer_GlobalHistogram), + FixedThreshold(ZXing_Binarizer_FixedThreshold), + BoolCast(ZXing_Binarizer_BoolCast); + + companion object { + fun fromCValue(cValue: ZXing_Binarizer): Binarizer { + return entries.first { it.cValue == cValue } + } + } +} + +@OptIn(ExperimentalForeignApi::class) +enum class EanAddOnSymbol(internal val cValue: ZXing_EanAddOnSymbol) { + Ignore(ZXing_EanAddOnSymbol_Ignore), + Read(ZXing_EanAddOnSymbol_Read), + Require(ZXing_EanAddOnSymbol_Require); + + companion object { + fun fromCValue(cValue: ZXing_EanAddOnSymbol): EanAddOnSymbol { + return entries.first { it.cValue == cValue } + } + } +} + +@OptIn(ExperimentalForeignApi::class) +enum class TextMode(internal val cValue: ZXing_TextMode) { + Plain(ZXing_TextMode_Plain), + ECI(ZXing_TextMode_ECI), + HRI(ZXing_TextMode_HRI), + Hex(ZXing_TextMode_Hex), + Escaped(ZXing_TextMode_Escaped); + + companion object { + fun fromCValue(cValue: ZXing_TextMode): TextMode { + return entries.first { it.cValue == cValue } + } + } +} diff --git a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt new file mode 100644 index 0000000000..69308906bb --- /dev/null +++ b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt @@ -0,0 +1,58 @@ +/* +* Copyright 2024 ISNing +*/ +// SPDX-License-Identifier: Apache-2.0 + +package zxingcpp + +import cnames.structs.ZXing_ImageView +import kotlinx.cinterop.* +import zxingcpp.cinterop.* +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.ref.createCleaner + +@OptIn(ExperimentalForeignApi::class) +class ImageView( + val data: ByteArray, + val width: Int, + val height: Int, + val format: ImageFormat, + val rowStride: Int = 0, + val pixStride: Int = 0, +) { + private val pinnedData = data.pin() + val cValue: CPointer? = + ZXing_ImageView_new_checked( + pinnedData.addressOf(0).reinterpret(), + data.size, + width, + height, + format.cValue, + rowStride, + pixStride + ) + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val cValueCleaner = createCleaner(cValue) { ZXing_ImageView_delete(it) } + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val pinnedDataCleaner = createCleaner(pinnedData) { it.unpin() } +} + +@OptIn(ExperimentalForeignApi::class) +enum class ImageFormat(internal val cValue: ZXing_ImageFormat) { + None(ZXing_ImageFormat_None), + Lum(ZXing_ImageFormat_Lum), + RGB(ZXing_ImageFormat_RGB), + BGR(ZXing_ImageFormat_BGR), + RGBX(ZXing_ImageFormat_RGBX), + XRGB(ZXing_ImageFormat_XRGB), + BGRX(ZXing_ImageFormat_BGRX), + XBGR(ZXing_ImageFormat_XBGR) +} + +@OptIn(ExperimentalForeignApi::class) +fun ZXing_ImageFormat.parseIntoImageFormat(): ImageFormat? = + ImageFormat.entries.firstOrNull { it.cValue == this } diff --git a/wrappers/kn/src/nativeTest/kotlin/Test.kt b/wrappers/kn/src/nativeTest/kotlin/Test.kt new file mode 100644 index 0000000000..6ff753cfb1 --- /dev/null +++ b/wrappers/kn/src/nativeTest/kotlin/Test.kt @@ -0,0 +1,41 @@ +/* +* Copyright 2024 ISNing +*/ +// SPDX-License-Identifier: Apache-2.0 + +import zxingcpp.* +import kotlin.experimental.ExperimentalNativeApi +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class BarcodeReaderTest { + + @Test + @OptIn(ExperimentalNativeApi::class) + fun `read barcode`() { + val data = "0000101000101101011110111101011011101010100111011100101000100101110010100000".map { + if (it == '0') 255.toByte() else 0.toByte() + } + + val iv = ImageView(data.toByteArray(), data.size, 1, ImageFormat.Lum) + val br = BarcodeReader().apply { + binarizer = Binarizer.BoolCast + } + val res = br.read(iv).firstOrNull() + + val expected = "96385074" + + assertNotNull(res) + assert(res.isValid) + assertEquals(res.format, BarcodeFormat.EAN8) + assertEquals(res.text, expected) + assertContentEquals(res.bytes, expected.encodeToByteArray()) + assert(!res.hasECI) + assertEquals(res.contentType, ContentType.Text) + assertEquals(res.orientation, 0) + assertEquals(res.position.topLeft, PointI(4, 0)) + assertEquals(res.lineCount, 1) + } +} From 1bda584b7bf7595351424174bbd6a60b0d78c503 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sat, 17 Feb 2024 13:42:54 +0700 Subject: [PATCH 292/587] cmake: Make build reproducible across different build directories Without this, the usage of the __FILE__ macro leaves the build directory in the binary. When building the Python extension with build isolation enabled this would lead to random paths in the binary. --- core/CMakeLists.txt | 13 +++++++++++-- wrappers/python/CMakeLists.txt | 12 ++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 2afedb078a..938eb5941b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -44,6 +44,17 @@ else() -Wall -Wextra -Wno-missing-braces -Werror=undef -Werror=return-type) endif() +include (CheckCXXCompilerFlag) + +# This is needed for reproducible builds across different build directories. +# Without this, the usage of the __FILE__ macro leaves the build directory in +# the binary. When building the Python extension with build isolation enabled +# this would lead to random paths in the binary. +set(FILE_PREFIX_ARG "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=") +check_cxx_compiler_flag("${FILE_PREFIX_ARG}" HAS_FILE_PREFIX_ARG) +if(HAS_FILE_PREFIX_ARG) + set(ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} "${FILE_PREFIX_ARG}") +endif() ################# Source files @@ -458,8 +469,6 @@ target_compile_options (ZXing PRIVATE ${ZXING_CORE_LOCAL_DEFINES} ) -include (CheckCXXCompilerFlag) - target_compile_features(ZXing PUBLIC cxx_std_17) target_link_libraries (ZXing PRIVATE Threads::Threads) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 9610420485..1cb3fd749b 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -17,8 +17,16 @@ if (NOT hasParent) option (BUILD_WRITERS "Build with writer support (encoders)" ON) option (BUILD_READERS "Build with reader support (decoders)" ON) set(BUILD_DEPENDENCIES "AUTO") - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) + + set(CORE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/core) + if(IS_SYMLINK ${CORE_PATH}) + # This is needed because otherwise GCC resolves the symlink which causes paths to randomly + # be prefixed by /core or by /wrappers/python/core depending on include order. + set(CORE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../core) + endif() + + if(EXISTS ${CORE_PATH}) + add_subdirectory(${CORE_PATH} ZXing EXCLUDE_FROM_ALL) include(${CMAKE_CURRENT_SOURCE_DIR}/zxing.cmake) else() message(FATAL_ERROR "Unable to locate zxing source code") From f220ee8fe5b6f09b99818350398717c37c9d373a Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 19 Feb 2024 18:23:28 +0100 Subject: [PATCH 293/587] qt: Result -> Barcode renaming and new `failedRead()` signal --- example/ZXingQt5CamReader.qml | 29 +++++++++--------- example/ZXingQt6CamReader.qml | 29 +++++++++--------- example/ZXingQtReader.cpp | 14 ++++----- example/ZXingQtReader.h | 55 +++++++++++++++++++---------------- 4 files changed, 63 insertions(+), 64 deletions(-) diff --git a/example/ZXingQt5CamReader.qml b/example/ZXingQt5CamReader.qml index 4b3191f884..12e0ab624b 100644 --- a/example/ZXingQt5CamReader.qml +++ b/example/ZXingQt5CamReader.qml @@ -35,24 +35,21 @@ Window { tryDownscale: tryDownscaleSwitch.checked textMode: ZXing.HRI - // callback with parameter 'result', called for every successfully processed frame - // onFoundBarcode: {} - - // callback with parameter 'result', called for every processed frame - onNewResult: { - points = result.isValid - ? [result.position.topLeft, result.position.topRight, result.position.bottomRight, result.position.bottomLeft] - : nullPoints - - if (result.isValid) { - resetInfo.restart() - info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.contentTypeName).arg(result.runTime) - } + // callback with parameter 'barcode', called for every successfully processed frame + onFoundBarcode: (barcode)=> { + points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) - if (!resetInfo.running) - info.text = "No barcode found" + resetInfo.restart() +// console.log(barcode) + } -// console.log(result) + // called for every processed frame where no barcode was detected + onFailedRead: ()=> { + points = nullPoints + + if (!resetInfo.running) + info.text = "No barcode found (in %1 ms)".arg(runTime) } } diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index 47b35574fd..4100aa7b47 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -36,24 +36,21 @@ Window { tryDownscale: tryDownscaleSwitch.checked textMode: ZXing.TextMode.HRI - // callback with parameter 'result', called for every successfully processed frame - // onFoundBarcode: {} - - // callback with parameter 'result', called for every processed frame - onNewResult: (result)=> { - points = result.isValid - ? [result.position.topLeft, result.position.topRight, result.position.bottomRight, result.position.bottomLeft] - : nullPoints - - if (result.isValid) { - resetInfo.restart() - info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.contentTypeName).arg(result.runTime) - } + // callback with parameter 'barcode', called for every successfully processed frame + onFoundBarcode: (barcode)=> { + points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) - if (!resetInfo.running) - info.text = "No barcode found" + resetInfo.restart() +// console.log(barcode) + } -// console.log(result) + // called for every processed frame where no barcode was detected + onFailedRead: ()=> { + points = nullPoints + + if (!resetInfo.running) + info.text = "No barcode found (in %1 ms)".arg(runTime) } } diff --git a/example/ZXingQtReader.cpp b/example/ZXingQtReader.cpp index 8b2174f602..2aba7dfddc 100644 --- a/example/ZXingQtReader.cpp +++ b/example/ZXingQtReader.cpp @@ -27,18 +27,18 @@ int main(int argc, char* argv[]) auto options = ReaderOptions() .setFormats(BarcodeFormat::MatrixCodes) - .setTryRotate(false) + .setTryInvert(false) .setTextMode(TextMode::HRI) .setMaxNumberOfSymbols(10); - auto results = ReadBarcodes(image, options); + auto barcodes = ReadBarcodes(image, options); - for (auto& result : results) { - qDebug() << "Text: " << result.text(); - qDebug() << "Format: " << result.format(); - qDebug() << "Content:" << result.contentType(); + for (auto& barcode : barcodes) { + qDebug() << "Text: " << barcode.text(); + qDebug() << "Format: " << barcode.format(); + qDebug() << "Content:" << barcode.contentType(); qDebug() << ""; } - return results.isEmpty() ? 1 : 0; + return barcodes.isEmpty() ? 1 : 0; } diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index aede6fc027..d720983fd5 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -97,7 +97,7 @@ class Position : public ZXing::Quadrilateral using Base::Base; }; -class Result : private ZXing::Result +class Barcode : private ZXing::Result { Q_GADGET @@ -115,9 +115,9 @@ class Result : private ZXing::Result Position _position; public: - Result() = default; // required for qmetatype machinery + Barcode() = default; // required for qmetatype machinery - explicit Result(ZXing::Result&& r) : ZXing::Result(std::move(r)) { + explicit Barcode(ZXing::Result&& r) : ZXing::Result(std::move(r)) { _text = QString::fromStdString(ZXing::Result::text()); _bytes = QByteArray(reinterpret_cast(ZXing::Result::bytes().data()), Size(ZXing::Result::bytes())); auto& pos = ZXing::Result::position(); @@ -134,21 +134,21 @@ class Result : private ZXing::Result const QString& text() const { return _text; } const QByteArray& bytes() const { return _bytes; } const Position& position() const { return _position; } - - // For debugging/development - int runTime = 0; - Q_PROPERTY(int runTime MEMBER runTime) }; -inline QList QListResults(ZXing::Results&& zxres) +inline QList ZXBarcodesToQBarcodes(ZXing::Results&& zxres) { - QList res; + QList res; for (auto&& r : zxres) - res.push_back(Result(std::move(r))); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + res.push_back(Barcode(std::move(r))); +#else + res.emplace_back(std::move(r)); +#endif return res; } -inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts = {}) +inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts = {}) { using namespace ZXing; @@ -170,21 +170,21 @@ inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts = }; auto exec = [&](const QImage& img) { - return QListResults(ZXing::ReadBarcodes( + return ZXBarcodesToQBarcodes(ZXing::ReadBarcodes( {img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast(img.bytesPerLine())}, opts)); }; return ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img); } -inline Result ReadBarcode(const QImage& img, const ReaderOptions& opts = {}) +inline Barcode ReadBarcode(const QImage& img, const ReaderOptions& opts = {}) { auto res = ReadBarcodes(img, ReaderOptions(opts).setMaxNumberOfSymbols(1)); - return !res.isEmpty() ? res.takeFirst() : Result(); + return !res.isEmpty() ? res.takeFirst() : Barcode(); } #ifdef QT_MULTIMEDIA_LIB -inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& opts = {}) +inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& opts = {}) { using namespace ZXing; @@ -280,7 +280,7 @@ inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& } QScopeGuard unmap([&] { img.unmap(); }); - return QListResults(ZXing::ReadBarcodes( + return ZXBarcodesToQBarcodes(ZXing::ReadBarcodes( {img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, opts)); } else { @@ -300,10 +300,10 @@ inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& } } -inline Result ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = {}) +inline Barcode ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = {}) { auto res = ReadBarcodes(frame, ReaderOptions(opts).setMaxNumberOfSymbols(1)); - return !res.isEmpty() ? res.takeFirst() : Result(); + return !res.isEmpty() ? res.takeFirst() : Barcode(); } #define ZQ_PROPERTY(Type, name, setter) \ @@ -371,25 +371,30 @@ class BarcodeReader : public QObject, private ReaderOptions ZQ_PROPERTY(bool, tryDownscale, setTryDownscale) ZQ_PROPERTY(bool, isPure, setIsPure) + // For debugging/development + int runTime = 0; + Q_PROPERTY(int runTime MEMBER runTime) + public slots: - ZXingQt::Result process(const QVideoFrame& image) + ZXingQt::Barcode process(const QVideoFrame& image) { QElapsedTimer t; t.start(); auto res = ReadBarcode(image, *this); - res.runTime = t.elapsed(); + runTime = t.elapsed(); - emit newResult(res); if (res.isValid()) emit foundBarcode(res); + else + emit failedRead(); return res; } signals: - void newResult(ZXingQt::Result result); - void foundBarcode(ZXingQt::Result result); + void failedRead(); + void foundBarcode(ZXingQt::Barcode barcode); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) public: @@ -443,7 +448,7 @@ inline QVideoFilterRunnable* BarcodeReader::createFilterRunnable() Q_DECLARE_METATYPE(ZXingQt::Position) -Q_DECLARE_METATYPE(ZXingQt::Result) +Q_DECLARE_METATYPE(ZXingQt::Barcode) #ifdef QT_QML_LIB @@ -460,7 +465,7 @@ inline void registerQmlAndMetaTypes() // supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name // but then the qml side complains about "unregistered type" qRegisterMetaType("Position"); - qRegisterMetaType("Result"); + qRegisterMetaType("Barcode"); qmlRegisterUncreatableMetaObject( ZXingQt::staticMetaObject, "ZXing", 1, 0, "ZXing", "Access to enums & flags only"); From de348ffea91b1ff759ece5d9d86b6c2aaf09551d Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 19 Feb 2024 18:28:51 +0100 Subject: [PATCH 294/587] qt: replace space with tab indentation in qml files --- .editorconfig | 2 +- example/ZXingQt5CamReader.qml | 268 +++++++++++++------------- example/ZXingQt6CamReader.qml | 342 +++++++++++++++++----------------- 3 files changed, 306 insertions(+), 306 deletions(-) diff --git a/.editorconfig b/.editorconfig index 4538c85cd7..f8db50e18e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,c,h,html,py,kt,cs}] +[*.{cpp,c,h,html,py,kt,cs,qlm}] indent_style = tab indent_size = 4 max_line_length = 135 diff --git a/example/ZXingQt5CamReader.qml b/example/ZXingQt5CamReader.qml index 12e0ab624b..0704c7b3a7 100644 --- a/example/ZXingQt5CamReader.qml +++ b/example/ZXingQt5CamReader.qml @@ -12,138 +12,138 @@ import QtMultimedia 5.12 import ZXing 1.0 Window { - visible: true - width: 640 - height: 480 - title: Qt.application.name - - property var nullPoints: [Qt.point(0,0), Qt.point(0,0), Qt.point(0,0), Qt.point(0,0)] - property var points: nullPoints - - Timer { - id: resetInfo - interval: 1000 - } - - BarcodeReader { - id: barcodeReader - - formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) - tryRotate: tryRotateSwitch.checked - tryHarder: tryHarderSwitch.checked - tryInvert: tryInvertSwitch.checked - tryDownscale: tryDownscaleSwitch.checked - textMode: ZXing.HRI - - // callback with parameter 'barcode', called for every successfully processed frame - onFoundBarcode: (barcode)=> { - points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] - info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) - - resetInfo.restart() -// console.log(barcode) - } - - // called for every processed frame where no barcode was detected - onFailedRead: ()=> { - points = nullPoints - - if (!resetInfo.running) - info.text = "No barcode found (in %1 ms)".arg(runTime) - } - } - - Camera { - id: camera - - captureMode: Camera.CaptureViewfinder - deviceId: QtMultimedia.availableCameras[camerasComboBox.currentIndex] ? QtMultimedia.availableCameras[camerasComboBox.currentIndex].deviceId : "" - - onDeviceIdChanged: { - focus.focusMode = CameraFocus.FocusContinuous - focus.focusPointMode = CameraFocus.FocusPointAuto - } - - onError: console.log("camera error:" + errorString) - } - - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: false - visible: QtMultimedia.availableCameras.length > 1 - Label { - text: qsTr("Camera: ") - Layout.fillWidth: false - } - ComboBox { - id: camerasComboBox - Layout.fillWidth: true - model: QtMultimedia.availableCameras - textRole: "displayName" - currentIndex: 0 - } - } - - VideoOutput { - id: videoOutput - Layout.fillHeight: true - Layout.fillWidth: true - filters: [barcodeReader] - source: camera - autoOrientation: true - - Shape { - id: polygon - anchors.fill: parent - visible: points.length == 4 - ShapePath { - strokeWidth: 3 - strokeColor: "red" - strokeStyle: ShapePath.SolidLine - fillColor: "transparent" - //TODO: really? I don't know qml... - startX: videoOutput.mapPointToItem(points[3]).x - startY: videoOutput.mapPointToItem(points[3]).y - PathLine { - x: videoOutput.mapPointToItem(points[0]).x - y: videoOutput.mapPointToItem(points[0]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[1]).x - y: videoOutput.mapPointToItem(points[1]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[2]).x - y: videoOutput.mapPointToItem(points[2]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[3]).x - y: videoOutput.mapPointToItem(points[3]).y - } - } - } - - Label { - id: info - color: "white" - padding: 10 - background: Rectangle { color: "#80808080" } - } - - ColumnLayout { - anchors.right: parent.right - anchors.bottom: parent.bottom - - Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } - Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } - Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } - Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } - Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } - Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } - } - } - } + visible: true + width: 640 + height: 480 + title: Qt.application.name + + property var nullPoints: [Qt.point(0,0), Qt.point(0,0), Qt.point(0,0), Qt.point(0,0)] + property var points: nullPoints + + Timer { + id: resetInfo + interval: 1000 + } + + BarcodeReader { + id: barcodeReader + + formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) + tryRotate: tryRotateSwitch.checked + tryHarder: tryHarderSwitch.checked + tryInvert: tryInvertSwitch.checked + tryDownscale: tryDownscaleSwitch.checked + textMode: ZXing.HRI + + // callback with parameter 'barcode', called for every successfully processed frame + onFoundBarcode: (barcode)=> { + points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) + + resetInfo.restart() +// console.log(barcode) + } + + // called for every processed frame where no barcode was detected + onFailedRead: ()=> { + points = nullPoints + + if (!resetInfo.running) + info.text = "No barcode found (in %1 ms)".arg(runTime) + } + } + + Camera { + id: camera + + captureMode: Camera.CaptureViewfinder + deviceId: QtMultimedia.availableCameras[camerasComboBox.currentIndex] ? QtMultimedia.availableCameras[camerasComboBox.currentIndex].deviceId : "" + + onDeviceIdChanged: { + focus.focusMode = CameraFocus.FocusContinuous + focus.focusPointMode = CameraFocus.FocusPointAuto + } + + onError: console.log("camera error:" + errorString) + } + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: false + visible: QtMultimedia.availableCameras.length > 1 + Label { + text: qsTr("Camera: ") + Layout.fillWidth: false + } + ComboBox { + id: camerasComboBox + Layout.fillWidth: true + model: QtMultimedia.availableCameras + textRole: "displayName" + currentIndex: 0 + } + } + + VideoOutput { + id: videoOutput + Layout.fillHeight: true + Layout.fillWidth: true + filters: [barcodeReader] + source: camera + autoOrientation: true + + Shape { + id: polygon + anchors.fill: parent + visible: points.length == 4 + ShapePath { + strokeWidth: 3 + strokeColor: "red" + strokeStyle: ShapePath.SolidLine + fillColor: "transparent" + //TODO: really? I don't know qml... + startX: videoOutput.mapPointToItem(points[3]).x + startY: videoOutput.mapPointToItem(points[3]).y + PathLine { + x: videoOutput.mapPointToItem(points[0]).x + y: videoOutput.mapPointToItem(points[0]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[1]).x + y: videoOutput.mapPointToItem(points[1]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[2]).x + y: videoOutput.mapPointToItem(points[2]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[3]).x + y: videoOutput.mapPointToItem(points[3]).y + } + } + } + + Label { + id: info + color: "white" + padding: 10 + background: Rectangle { color: "#80808080" } + } + + ColumnLayout { + anchors.right: parent.right + anchors.bottom: parent.bottom + + Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } + Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } + Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } + Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } + Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } + Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } + } + } + } } diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index 4100aa7b47..7eb85445f4 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -12,175 +12,175 @@ import QtMultimedia import ZXing Window { - visible: true - width: 640 - height: 480 - title: Qt.application.name - - property var nullPoints: [Qt.point(0,0), Qt.point(0,0), Qt.point(0,0), Qt.point(0,0)] - property var points: nullPoints - - Timer { - id: resetInfo - interval: 1000 - } - - BarcodeReader { - id: barcodeReader - videoSink: videoOutput.videoSink - - formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) - tryRotate: tryRotateSwitch.checked - tryHarder: tryHarderSwitch.checked - tryInvert: tryInvertSwitch.checked - tryDownscale: tryDownscaleSwitch.checked - textMode: ZXing.TextMode.HRI - - // callback with parameter 'barcode', called for every successfully processed frame - onFoundBarcode: (barcode)=> { - points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] - info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) - - resetInfo.restart() -// console.log(barcode) - } - - // called for every processed frame where no barcode was detected - onFailedRead: ()=> { - points = nullPoints - - if (!resetInfo.running) - info.text = "No barcode found (in %1 ms)".arg(runTime) - } - } - - MediaDevices { - id: devices - } - - Camera { - id: camera - cameraDevice: devices.videoInputs[camerasComboBox.currentIndex] ? devices.videoInputs[camerasComboBox.currentIndex] : devices.defaultVideoInput - focusMode: Camera.FocusModeAutoNear - onErrorOccurred: console.log("camera error:" + errorString) - active: true - } - - CaptureSession { - id: captureSession - camera: camera - videoOutput: videoOutput - } - - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: false - visible: devices.videoInputs.length > 1 - Label { - text: qsTr("Camera: ") - Layout.fillWidth: false - } - ComboBox { - id: camerasComboBox - Layout.fillWidth: true - model: devices.videoInputs - textRole: "description" - currentIndex: 0 - } - } - - VideoOutput { - id: videoOutput - Layout.fillHeight: true - Layout.fillWidth: true - - function mapPointToItem(point) - { - if (videoOutput.sourceRect.width === 0 || videoOutput.sourceRect.height === 0) - return Qt.point(0, 0); - - let dx = point.x; - let dy = point.y; - - if ((videoOutput.orientation % 180) == 0) - { - dx = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; - dy = dy * videoOutput.contentRect.height / videoOutput.sourceRect.height; - } - else - { - dx = dx * videoOutput.contentRect.height / videoOutput.sourceRect.height; - dy = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; - } - - switch ((videoOutput.orientation + 360) % 360) - { - case 0: - default: - return Qt.point(videoOutput.contentRect.x + dx, videoOutput.contentRect.y + dy); - case 90: - return Qt.point(videoOutput.contentRect.x + dy, videoOutput.contentRect.y + videoOutput.contentRect.height - dx); - case 180: - return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dx, videoOutput.contentRect.y + videoOutput.contentRect.height -dy); - case 270: - return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dy, videoOutput.contentRect.y + dx); - } - } - - Shape { - id: polygon - anchors.fill: parent - visible: points.length === 4 - - ShapePath { - strokeWidth: 3 - strokeColor: "red" - strokeStyle: ShapePath.SolidLine - fillColor: "transparent" - //TODO: really? I don't know qml... - startX: videoOutput.mapPointToItem(points[3]).x - startY: videoOutput.mapPointToItem(points[3]).y - - PathLine { - x: videoOutput.mapPointToItem(points[0]).x - y: videoOutput.mapPointToItem(points[0]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[1]).x - y: videoOutput.mapPointToItem(points[1]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[2]).x - y: videoOutput.mapPointToItem(points[2]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[3]).x - y: videoOutput.mapPointToItem(points[3]).y - } - } - } - - Label { - id: info - color: "white" - padding: 10 - background: Rectangle { color: "#80808080" } - } - - ColumnLayout { - anchors.right: parent.right - anchors.bottom: parent.bottom - - Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } - Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } - Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } - Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } - Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } - Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } - } - } - } + visible: true + width: 640 + height: 480 + title: Qt.application.name + + property var nullPoints: [Qt.point(0,0), Qt.point(0,0), Qt.point(0,0), Qt.point(0,0)] + property var points: nullPoints + + Timer { + id: resetInfo + interval: 1000 + } + + BarcodeReader { + id: barcodeReader + videoSink: videoOutput.videoSink + + formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) + tryRotate: tryRotateSwitch.checked + tryHarder: tryHarderSwitch.checked + tryInvert: tryInvertSwitch.checked + tryDownscale: tryDownscaleSwitch.checked + textMode: ZXing.TextMode.HRI + + // callback with parameter 'barcode', called for every successfully processed frame + onFoundBarcode: (barcode)=> { + points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) + + resetInfo.restart() +// console.log(barcode) + } + + // called for every processed frame where no barcode was detected + onFailedRead: ()=> { + points = nullPoints + + if (!resetInfo.running) + info.text = "No barcode found (in %1 ms)".arg(runTime) + } + } + + MediaDevices { + id: devices + } + + Camera { + id: camera + cameraDevice: devices.videoInputs[camerasComboBox.currentIndex] ? devices.videoInputs[camerasComboBox.currentIndex] : devices.defaultVideoInput + focusMode: Camera.FocusModeAutoNear + onErrorOccurred: console.log("camera error:" + errorString) + active: true + } + + CaptureSession { + id: captureSession + camera: camera + videoOutput: videoOutput + } + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: false + visible: devices.videoInputs.length > 1 + Label { + text: qsTr("Camera: ") + Layout.fillWidth: false + } + ComboBox { + id: camerasComboBox + Layout.fillWidth: true + model: devices.videoInputs + textRole: "description" + currentIndex: 0 + } + } + + VideoOutput { + id: videoOutput + Layout.fillHeight: true + Layout.fillWidth: true + + function mapPointToItem(point) + { + if (videoOutput.sourceRect.width === 0 || videoOutput.sourceRect.height === 0) + return Qt.point(0, 0); + + let dx = point.x; + let dy = point.y; + + if ((videoOutput.orientation % 180) == 0) + { + dx = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; + dy = dy * videoOutput.contentRect.height / videoOutput.sourceRect.height; + } + else + { + dx = dx * videoOutput.contentRect.height / videoOutput.sourceRect.height; + dy = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; + } + + switch ((videoOutput.orientation + 360) % 360) + { + case 0: + default: + return Qt.point(videoOutput.contentRect.x + dx, videoOutput.contentRect.y + dy); + case 90: + return Qt.point(videoOutput.contentRect.x + dy, videoOutput.contentRect.y + videoOutput.contentRect.height - dx); + case 180: + return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dx, videoOutput.contentRect.y + videoOutput.contentRect.height -dy); + case 270: + return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dy, videoOutput.contentRect.y + dx); + } + } + + Shape { + id: polygon + anchors.fill: parent + visible: points.length === 4 + + ShapePath { + strokeWidth: 3 + strokeColor: "red" + strokeStyle: ShapePath.SolidLine + fillColor: "transparent" + //TODO: really? I don't know qml... + startX: videoOutput.mapPointToItem(points[3]).x + startY: videoOutput.mapPointToItem(points[3]).y + + PathLine { + x: videoOutput.mapPointToItem(points[0]).x + y: videoOutput.mapPointToItem(points[0]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[1]).x + y: videoOutput.mapPointToItem(points[1]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[2]).x + y: videoOutput.mapPointToItem(points[2]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[3]).x + y: videoOutput.mapPointToItem(points[3]).y + } + } + } + + Label { + id: info + color: "white" + padding: 10 + background: Rectangle { color: "#80808080" } + } + + ColumnLayout { + anchors.right: parent.right + anchors.bottom: parent.bottom + + Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } + Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } + Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } + Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } + Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } + Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } + } + } + } } From 2091f001b0598f8339465e4384bd11efb8da8034 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 02:01:35 +0100 Subject: [PATCH 295/587] c-API: add ZXing_ErrorType enum and ZXing_Barcode_errorType() getter Also add this to dotnet and rust wrapper. --- core/src/ZXingC.cpp | 5 ++++ core/src/ZXingC.h | 9 +++++++ wrappers/dotnet/ZXingCpp.Demo/Program.cs | 3 ++- wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs | 2 ++ wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 4 +++ wrappers/rust/examples/demo.rs | 5 ++-- wrappers/rust/src/bindings.rs | 6 +++++ wrappers/rust/src/lib.rs | 29 ++++++++++++++++++++- wrappers/rust/src/tests.rs | 2 ++ 9 files changed, 61 insertions(+), 4 deletions(-) diff --git a/core/src/ZXingC.cpp b/core/src/ZXingC.cpp index 2986362495..edb3aa6324 100644 --- a/core/src/ZXingC.cpp +++ b/core/src/ZXingC.cpp @@ -206,6 +206,11 @@ bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode) return barcode != NULL && barcode->isValid(); } +ZXing_ErrorType ZXing_Barcode_errorType(const ZXing_Barcode* barcode) +{ + return static_cast(barcode->error().type()); +} + char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode) { return copy(ToString(barcode->error())); diff --git a/core/src/ZXingC.h b/core/src/ZXingC.h index 921ef85554..934d8b4696 100644 --- a/core/src/ZXingC.h +++ b/core/src/ZXingC.h @@ -172,6 +172,14 @@ typedef enum ZXing_ContentType_UnknownECI } ZXing_ContentType; +typedef enum +{ + ZXing_ErrorType_None, + ZXing_ErrorType_Format, + ZXing_ErrorType_Checksum, + ZXing_ErrorType_Unsupported +} ZXing_ErrorType; + char* ZXing_ContentTypeToString(ZXing_ContentType type); typedef struct ZXing_PointI @@ -187,6 +195,7 @@ typedef struct ZXing_Position char* ZXing_PositionToString(ZXing_Position position); bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode); +ZXing_ErrorType ZXing_Barcode_errorType(const ZXing_Barcode* barcode); char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode); ZXing_BarcodeFormat ZXing_Barcode_format(const ZXing_Barcode* barcode); ZXing_ContentType ZXing_Barcode_contentType(const ZXing_Barcode* barcode); diff --git a/wrappers/dotnet/ZXingCpp.Demo/Program.cs b/wrappers/dotnet/ZXingCpp.Demo/Program.cs index 74323a5b11..627a0d64b7 100644 --- a/wrappers/dotnet/ZXingCpp.Demo/Program.cs +++ b/wrappers/dotnet/ZXingCpp.Demo/Program.cs @@ -60,12 +60,13 @@ public static void Main(string[] args) var reader = new BarcodeReader() { TryInvert = false, + ReturnErrors = true, }; if (args.Length >= 2) reader.Formats = BarcodeReader.FormatsFromString(args[1]); foreach (var b in reader.Read(img)) - Console.WriteLine($"{b.Format} ({b.ContentType}): {b.Text} / [{string.Join(", ", b.Bytes)}]"); + Console.WriteLine($"{b.Format} ({b.ContentType}): {b.Text} / [{string.Join(", ", b.Bytes)}] {b.ErrorMsg}"); } } diff --git a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs index 0275290c94..25fbc7769a 100644 --- a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs +++ b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs @@ -52,5 +52,7 @@ public void Read() Assert.Equal(0, res[0].Orientation); Assert.Equal(new PointI() { X = 4, Y = 0 }, res[0].Position.TopLeft); Assert.Equal(1, res[0].LineCount); + Assert.Equal(ErrorType.None, res[0].ErrorType); + Assert.Equal("", res[0].ErrorMsg); } } \ No newline at end of file diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index a02081174c..032a15b7a8 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -59,6 +59,7 @@ internal class Dll [DllImport(DllName)] public static extern bool ZXing_Barcode_isValid(IntPtr result); [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_errorMsg(IntPtr result); + [DllImport(DllName)] public static extern ErrorType ZXing_Barcode_errorType(IntPtr result); [DllImport(DllName)] public static extern BarcodeFormat ZXing_Barcode_format(IntPtr result); [DllImport(DllName)] public static extern ContentType ZXing_Barcode_contentType(IntPtr result); [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytes(IntPtr result, out int len); @@ -158,6 +159,8 @@ public enum TextMode public enum ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; +public enum ErrorType { None, Format, Checksum, Unsupported }; + public enum ImageFormat { None = 0, Lum = 0x01000000, @@ -305,6 +308,7 @@ public class Barcode public string ECLevel => MarshalAsString(ZXing_Barcode_ecLevel(_d)); public string SymbologyIdentifier => MarshalAsString(ZXing_Barcode_symbologyIdentifier(_d)); public string ErrorMsg => MarshalAsString(ZXing_Barcode_errorMsg(_d)); + public ErrorType ErrorType => ZXing_Barcode_errorType(_d); public Position Position => ZXing_Barcode_position(_d); public int Orientation => ZXing_Barcode_orientation(_d); public bool HasECI => ZXing_Barcode_hasECI(_d); diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index cee5d48509..f5380e06e4 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -23,7 +23,8 @@ fn main() -> anyhow::Result<()> { .try_harder(!fast) .try_invert(!fast) .try_rotate(!fast) - .try_downscale(!fast); + .try_downscale(!fast) + .return_errors(true); #[cfg(feature = "image")] let barcodes = reader.read(&image)?; @@ -40,7 +41,7 @@ fn main() -> anyhow::Result<()> { println!("Content: {}", barcode.content_type()); println!("Identifier: {}", barcode.symbology_identifier()); println!("EC Level: {}", barcode.ec_level()); - println!("Error: {}", barcode.error_message()); + println!("Error: {}", barcode.error()); println!("Rotation: {}", barcode.orientation()); println!("Position: {}", barcode.position()); println!(); diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 00a465a6c8..40f9a1be01 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -77,6 +77,11 @@ pub const ZXing_ContentType_GS1: ZXing_ContentType = 3; pub const ZXing_ContentType_ISO15434: ZXing_ContentType = 4; pub const ZXing_ContentType_UnknownECI: ZXing_ContentType = 5; pub type ZXing_ContentType = ::core::ffi::c_uint; +pub const ZXing_ErrorType_None: ZXing_ErrorType = 0; +pub const ZXing_ErrorType_Format: ZXing_ErrorType = 1; +pub const ZXing_ErrorType_Checksum: ZXing_ErrorType = 2; +pub const ZXing_ErrorType_Unsupported: ZXing_ErrorType = 3; +pub type ZXing_ErrorType = ::core::ffi::c_uint; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] pub struct ZXing_PointI { @@ -150,6 +155,7 @@ extern "C" { pub fn ZXing_ContentTypeToString(type_: ZXing_ContentType) -> *mut ::core::ffi::c_char; pub fn ZXing_PositionToString(position: ZXing_Position) -> *mut ::core::ffi::c_char; pub fn ZXing_Barcode_isValid(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_errorType(barcode: *const ZXing_Barcode) -> ZXing_ErrorType; pub fn ZXing_Barcode_errorMsg(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; pub fn ZXing_Barcode_format(barcode: *const ZXing_Barcode) -> ZXing_BarcodeFormat; pub fn ZXing_Barcode_contentType(barcode: *const ZXing_Barcode) -> ZXing_ContentType; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 142fdea573..19001ed02d 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -277,6 +277,21 @@ impl Drop for Barcode { } } +#[derive(Error, Debug, PartialEq)] +pub enum BarcodeError { + #[error("")] + None(), + + #[error("{0}")] + Checksum(String), + + #[error("{0}")] + Format(String), + + #[error("{0}")] + Unsupported(String), +} + pub type PointI = ZXing_PointI; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -315,7 +330,6 @@ impl Barcode { getter!(content_type, contentType, transmute, ContentType); getter!(text, text, c2r_str, String); getter!(ec_level, ecLevel, c2r_str, String); - getter!(error_message, errorMsg, c2r_str, String); getter!(symbology_identifier, symbologyIdentifier, c2r_str, String); getter!(position, position, transmute, Position); getter!(orientation, orientation, transmute, i32); @@ -332,6 +346,19 @@ impl Barcode { let mut len: c_int = 0; unsafe { c2r_vec(ZXing_Barcode_bytesECI(self.0, &mut len), len) } } + + pub fn error(&self) -> BarcodeError { + let error_type = unsafe { ZXing_Barcode_errorType(self.0) }; + let error_msg = unsafe { c2r_str(ZXing_Barcode_errorMsg(self.0)) }; + #[allow(non_upper_case_globals)] + match error_type { + ZXing_ErrorType_None => BarcodeError::None(), + ZXing_ErrorType_Format => BarcodeError::Format(error_msg), + ZXing_ErrorType_Checksum => BarcodeError::Checksum(error_msg), + ZXing_ErrorType_Unsupported => BarcodeError::Unsupported(error_msg), + _ => panic!("Internal error: invalid ZXing_ErrorType"), + } + } } pub struct BarcodeReader(*mut ZXing_ReaderOptions); diff --git a/wrappers/rust/src/tests.rs b/wrappers/rust/src/tests.rs index 465dfe65d0..45254ccba7 100644 --- a/wrappers/rust/src/tests.rs +++ b/wrappers/rust/src/tests.rs @@ -57,5 +57,7 @@ mod tests { assert_eq!(res[0].orientation(), 0); assert_eq!(res[0].position().top_left, PointI { x: 4, y: 0 }); assert_eq!(res[0].line_count(), 1); + assert!(matches!(res[0].error(), BarcodeError::None())); + assert_eq!(res[0].error().to_string(), ""); } } From 050a2564378cebf5dc15c511026c5457d73e7ccf Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 02:06:22 +0100 Subject: [PATCH 296/587] README: add Kotlin/Native wrapper info --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 774752014f..f9609da66b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Thanks a lot for your contribution! * [Android](wrappers/android/README.md) * [C](wrappers/c/README.md) * [iOS](wrappers/ios/README.md) + * [Kotlin/Native](wrappers/kn/README.md) * [.NET](wrappers/dotnet/README.md) * [Python](wrappers/python/README.md) * [Rust](wrappers/rust/README.md) From 4fb4fddc26047130fa5c3fae952207b5be161421 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 02:10:41 +0100 Subject: [PATCH 297/587] dotnet: code cosmetic (change parameter name from result to barcode) --- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 42 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 032a15b7a8..428d16e8cc 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -52,27 +52,27 @@ internal class Dll [DllImport(DllName)] public static extern void ZXing_ImageView_delete(IntPtr iv); [DllImport(DllName)] public static extern IntPtr ZXing_ReadBarcodes(IntPtr iv, IntPtr opts); - [DllImport(DllName)] public static extern void ZXing_Barcode_delete(IntPtr result); - [DllImport(DllName)] public static extern void ZXing_Barcodes_delete(IntPtr results); - [DllImport(DllName)] public static extern int ZXing_Barcodes_size(IntPtr results); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcodes_move(IntPtr results, int i); - - [DllImport(DllName)] public static extern bool ZXing_Barcode_isValid(IntPtr result); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_errorMsg(IntPtr result); - [DllImport(DllName)] public static extern ErrorType ZXing_Barcode_errorType(IntPtr result); - [DllImport(DllName)] public static extern BarcodeFormat ZXing_Barcode_format(IntPtr result); - [DllImport(DllName)] public static extern ContentType ZXing_Barcode_contentType(IntPtr result); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytes(IntPtr result, out int len); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytesECI(IntPtr result, out int len); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_text(IntPtr result); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_ecLevel(IntPtr result); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_symbologyIdentifier(IntPtr result); - [DllImport(DllName)] public static extern Position ZXing_Barcode_position(IntPtr result); - [DllImport(DllName)] public static extern int ZXing_Barcode_orientation(IntPtr result); - [DllImport(DllName)] public static extern bool ZXing_Barcode_hasECI(IntPtr result); - [DllImport(DllName)] public static extern bool ZXing_Barcode_isInverted(IntPtr result); - [DllImport(DllName)] public static extern bool ZXing_Barcode_isMirrored(IntPtr result); - [DllImport(DllName)] public static extern int ZXing_Barcode_lineCount(IntPtr result); + [DllImport(DllName)] public static extern void ZXing_Barcode_delete(IntPtr barcode); + [DllImport(DllName)] public static extern void ZXing_Barcodes_delete(IntPtr barcodes); + [DllImport(DllName)] public static extern int ZXing_Barcodes_size(IntPtr barcodes); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcodes_move(IntPtr barcodes, int i); + + [DllImport(DllName)] public static extern bool ZXing_Barcode_isValid(IntPtr barcode); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_errorMsg(IntPtr barcode); + [DllImport(DllName)] public static extern ErrorType ZXing_Barcode_errorType(IntPtr barcode); + [DllImport(DllName)] public static extern BarcodeFormat ZXing_Barcode_format(IntPtr barcode); + [DllImport(DllName)] public static extern ContentType ZXing_Barcode_contentType(IntPtr barcode); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytes(IntPtr barcode, out int len); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytesECI(IntPtr barcode, out int len); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_text(IntPtr barcode); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_ecLevel(IntPtr barcode); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_symbologyIdentifier(IntPtr barcode); + [DllImport(DllName)] public static extern Position ZXing_Barcode_position(IntPtr barcode); + [DllImport(DllName)] public static extern int ZXing_Barcode_orientation(IntPtr barcode); + [DllImport(DllName)] public static extern bool ZXing_Barcode_hasECI(IntPtr barcode); + [DllImport(DllName)] public static extern bool ZXing_Barcode_isInverted(IntPtr barcode); + [DllImport(DllName)] public static extern bool ZXing_Barcode_isMirrored(IntPtr barcode); + [DllImport(DllName)] public static extern int ZXing_Barcode_lineCount(IntPtr barcode); [DllImport(DllName)] public static extern void ZXing_free(IntPtr opts); [DllImport(DllName)] public static extern IntPtr ZXing_LastErrorMsg(); From 6ad1b9309186e1c5d07aabd9a23cc4e385c57349 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 09:00:49 +0100 Subject: [PATCH 298/587] c-API: add ImageFormat::LumX enum value (also in .NET, Rust and K/N API) --- core/src/ZXingC.h | 1 + wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 1 + wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt | 1 + wrappers/rust/src/bindings.rs | 1 + wrappers/rust/src/lib.rs | 5 +++-- 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/ZXingC.h b/core/src/ZXingC.h index 934d8b4696..5b77e89fc0 100644 --- a/core/src/ZXingC.h +++ b/core/src/ZXingC.h @@ -39,6 +39,7 @@ typedef struct ZXing_Barcodes ZXing_Barcodes; typedef enum { ZXing_ImageFormat_None = 0, ZXing_ImageFormat_Lum = 0x01000000, + ZXing_ImageFormat_LumX = 0x02000000, ZXing_ImageFormat_RGB = 0x03000102, ZXing_ImageFormat_BGR = 0x03020100, ZXing_ImageFormat_RGBX = 0x04000102, diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 428d16e8cc..430de67d6c 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -164,6 +164,7 @@ public enum ErrorType { None, Format, Checksum, Unsupported }; public enum ImageFormat { None = 0, Lum = 0x01000000, + LumX = 0x02000000, RGB = 0x03000102, BGR = 0x03020100, RGBX = 0x04000102, diff --git a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt index 69308906bb..f58ecc5e76 100644 --- a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt +++ b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt @@ -45,6 +45,7 @@ class ImageView( enum class ImageFormat(internal val cValue: ZXing_ImageFormat) { None(ZXing_ImageFormat_None), Lum(ZXing_ImageFormat_Lum), + LumX(ZXing_ImageFormat_LumX), RGB(ZXing_ImageFormat_RGB), BGR(ZXing_ImageFormat_BGR), RGBX(ZXing_ImageFormat_RGBX), diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 40f9a1be01..d208e41048 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -22,6 +22,7 @@ pub struct ZXing_Barcodes { } pub const ZXing_ImageFormat_None: ZXing_ImageFormat = 0; pub const ZXing_ImageFormat_Lum: ZXing_ImageFormat = 16777216; +pub const ZXing_ImageFormat_LumX: ZXing_ImageFormat = 33554432; pub const ZXing_ImageFormat_RGB: ZXing_ImageFormat = 50331906; pub const ZXing_ImageFormat_BGR: ZXing_ImageFormat = 50462976; pub const ZXing_ImageFormat_RGBX: ZXing_ImageFormat = 67109122; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 19001ed02d..ea5441f5b7 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -111,7 +111,7 @@ macro_rules! make_zxing_flags { } } #[rustfmt::skip] // workaround for broken #[rustfmt::skip::macros(make_zxing_enum)] -make_zxing_enum!(ImageFormat { Lum, RGB, RGBX }); +make_zxing_enum!(ImageFormat { Lum, LumX, RGB, BGR, RGBX, XRGB, BGRX, XBGR }); #[rustfmt::skip] make_zxing_enum!(ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }); #[rustfmt::skip] @@ -258,13 +258,14 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { fn try_from(img: &'a image::DynamicImage) -> Result { let format = match img { image::DynamicImage::ImageLuma8(_) => Some(ImageFormat::Lum), + image::DynamicImage::ImageLumaA8(_) => Some(ImageFormat::LumX), image::DynamicImage::ImageRgb8(_) => Some(ImageFormat::RGB), image::DynamicImage::ImageRgba8(_) => Some(ImageFormat::RGBX), _ => None, }; match format { Some(format) => Ok(ImageView::from_slice(img.as_bytes(), img.width(), img.height(), format)?), - None => Err(Error::InvalidInput("Invalid image format (must be either luma8|rgb8|rgba8)".to_string())), + None => Err(Error::InvalidInput("Invalid image format (must be either luma8|lumaA8|rgb8|rgba8)".to_string())), } } } From bdf97f4c0717cd40ba063652a87b54ee5247ba67 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 12:35:00 +0100 Subject: [PATCH 299/587] Result: Introduce new name `Barcode` (and `Barcodes`) via `using` alias The class itself is still called Result to not break the ABI. But the term `ZXing::Result` is now deprecated. For more background information and reasoning for this change, see https://github.com/zxing-cpp/zxing-cpp/discussions/705 The Android, iOS and Python wrappers still use the term Result a.t.m. --- core/src/DecodeHints.cpp | 4 +- core/src/GTIN.cpp | 7 +- core/src/GTIN.h | 12 +-- core/src/MultiFormatReader.cpp | 15 ++- core/src/MultiFormatReader.h | 5 +- core/src/ReadBarcode.cpp | 14 +-- core/src/ReadBarcode.h | 8 +- core/src/Reader.h | 6 +- core/src/ReaderOptions.h | 4 +- core/src/Result.cpp | 38 ++++---- core/src/Result.h | 22 +++-- core/src/ZXAlgorithms.h | 4 +- core/src/ZXingC.cpp | 14 +-- core/src/ZXingC.h | 4 +- core/src/aztec/AZReader.cpp | 15 ++- core/src/aztec/AZReader.h | 4 +- core/src/datamatrix/DMReader.cpp | 14 +-- core/src/datamatrix/DMReader.h | 4 +- core/src/maxicode/MCReader.cpp | 5 +- core/src/maxicode/MCReader.h | 2 +- core/src/oned/ODCodabarReader.cpp | 5 +- core/src/oned/ODCodabarReader.h | 2 +- core/src/oned/ODCode128Reader.cpp | 6 +- core/src/oned/ODCode128Reader.h | 2 +- core/src/oned/ODCode39Reader.cpp | 4 +- core/src/oned/ODCode39Reader.h | 2 +- core/src/oned/ODCode93Reader.cpp | 4 +- core/src/oned/ODCode93Reader.h | 2 +- core/src/oned/ODDXFilmEdgeReader.cpp | 4 +- core/src/oned/ODDXFilmEdgeReader.h | 2 +- core/src/oned/ODDataBarExpandedReader.cpp | 3 +- core/src/oned/ODDataBarExpandedReader.h | 2 +- core/src/oned/ODDataBarReader.cpp | 5 +- core/src/oned/ODDataBarReader.h | 2 +- core/src/oned/ODITFReader.cpp | 4 +- core/src/oned/ODITFReader.h | 2 +- core/src/oned/ODMultiUPCEANReader.cpp | 4 +- core/src/oned/ODMultiUPCEANReader.h | 2 +- core/src/oned/ODReader.cpp | 21 ++-- core/src/oned/ODReader.h | 4 +- core/src/oned/ODRowReader.h | 4 +- core/src/pdf417/PDFReader.cpp | 20 ++-- core/src/pdf417/PDFReader.h | 4 +- core/src/qrcode/QRReader.cpp | 31 +++--- core/src/qrcode/QRReader.h | 4 +- example/ZXingOpenCV.cpp | 6 +- example/ZXingOpenCV.h | 8 +- example/ZXingQtReader.h | 22 ++--- example/ZXingReader.cpp | 96 +++++++++---------- test/blackbox/BlackboxTestRunner.cpp | 68 ++++++------- test/blackbox/TestReaderMain.cpp | 10 +- test/unit/ThresholdBinarizerTest.cpp | 6 +- test/unit/oned/ODCodaBarWriterTest.cpp | 2 +- test/unit/oned/ODCode128ReaderTest.cpp | 2 +- test/unit/oned/ODCode128WriterTest.cpp | 2 +- test/unit/oned/ODCode39ExtendedModeTest.cpp | 2 +- test/unit/oned/ODCode39ReaderTest.cpp | 2 +- test/unit/oned/ODDataBarReaderTest.cpp | 4 +- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 10 +- wrappers/python/zxing.cpp | 38 ++++---- wrappers/wasm/BarcodeReader.cpp | 18 ++-- wrappers/winrt/BarcodeReader.cpp | 6 +- 62 files changed, 319 insertions(+), 329 deletions(-) diff --git a/core/src/DecodeHints.cpp b/core/src/DecodeHints.cpp index 8dbd5a8685..9a54d8d2fb 100644 --- a/core/src/DecodeHints.cpp +++ b/core/src/DecodeHints.cpp @@ -17,12 +17,12 @@ struct DecodeHints char data[sizeof(ReaderOptions)]; }; -Result ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) +Barcode ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) { return ReadBarcode(image, reinterpret_cast(hints)); } -Results ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) +Barcodes ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) { return ReadBarcodes(image, reinterpret_cast(hints)); } diff --git a/core/src/GTIN.cpp b/core/src/GTIN.cpp index 256855a030..778618ced2 100644 --- a/core/src/GTIN.cpp +++ b/core/src/GTIN.cpp @@ -199,12 +199,11 @@ std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat return it != std::end(COUNTRIES) && prefix >= it->first && prefix <= it->last ? it->id : std::string(); } -std::string EanAddOn(const Result& result) +std::string EanAddOn(const Barcode& barcode) { - if (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE | BarcodeFormat::EAN8) - .testFlag(result.format())) + if (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE | BarcodeFormat::EAN8).testFlag(barcode.format())) return {}; - auto txt = result.bytes().asString(); + auto txt = barcode.bytes().asString(); auto pos = txt.find(' '); return pos != std::string::npos ? std::string(txt.substr(pos + 1)) : std::string(); } diff --git a/core/src/GTIN.h b/core/src/GTIN.h index d56b604e5f..b907f42e92 100644 --- a/core/src/GTIN.h +++ b/core/src/GTIN.h @@ -7,16 +7,13 @@ #pragma once +#include "Result.h" #include "BarcodeFormat.h" #include "ZXAlgorithms.h" #include -namespace ZXing { - -class Result; - -namespace GTIN { +namespace ZXing::GTIN { template T ComputeCheckDigit(const std::basic_string& digits, bool skipTail = false) @@ -47,10 +44,9 @@ bool IsCheckDigitValid(const std::basic_string& s) */ std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat format = BarcodeFormat::None); -std::string EanAddOn(const Result& result); +std::string EanAddOn(const Barcode& barcode); std::string IssueNr(const std::string& ean2AddOn); std::string Price(const std::string& ean5AddOn); -} // namespace GTIN -} // namespace ZXing +} // namespace ZXing::GTIN diff --git a/core/src/MultiFormatReader.cpp b/core/src/MultiFormatReader.cpp index 27e7f31311..d652e71627 100644 --- a/core/src/MultiFormatReader.cpp +++ b/core/src/MultiFormatReader.cpp @@ -46,21 +46,20 @@ MultiFormatReader::MultiFormatReader(const ReaderOptions& opts) : _opts(opts) MultiFormatReader::~MultiFormatReader() = default; -Result -MultiFormatReader::read(const BinaryBitmap& image) const +Barcode MultiFormatReader::read(const BinaryBitmap& image) const { - Result r; + Barcode r; for (const auto& reader : _readers) { r = reader->decode(image); if (r.isValid()) return r; } - return _opts.returnErrors() ? r : Result(); + return _opts.returnErrors() ? r : Barcode(); } -Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbols) const +Barcodes MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbols) const { - std::vector res; + Barcodes res; for (const auto& reader : _readers) { if (image.inverted() && !reader->supportsInversion) @@ -77,8 +76,8 @@ Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbol break; } - // sort results based on their position on the image - std::sort(res.begin(), res.end(), [](const Result& l, const Result& r) { + // sort barcodes based on their position on the image + std::sort(res.begin(), res.end(), [](const Barcode& l, const Barcode& r) { auto lp = l.position().topLeft(); auto rp = r.position().topLeft(); return lp.y < rp.y || (lp.y == rp.y && lp.x < rp.x); diff --git a/core/src/MultiFormatReader.h b/core/src/MultiFormatReader.h index fe7749b525..1b865d4f50 100644 --- a/core/src/MultiFormatReader.h +++ b/core/src/MultiFormatReader.h @@ -13,7 +13,6 @@ namespace ZXing { -class Result; class Reader; class BinaryBitmap; class ReaderOptions; @@ -25,10 +24,10 @@ class MultiFormatReader explicit MultiFormatReader(ReaderOptions&& opts) = delete; ~MultiFormatReader(); - Result read(const BinaryBitmap& image) const; + Barcode read(const BinaryBitmap& image) const; // WARNING: this API is experimental and may change/disappear - Results readMultiple(const BinaryBitmap& image, int maxSymbols = 0xFF) const; + Barcodes readMultiple(const BinaryBitmap& image, int maxSymbols = 0xFF) const; private: std::vector> _readers; diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 7525a35768..eeed964a2d 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -138,12 +138,12 @@ std::unique_ptr CreateBitmap(ZXing::Binarizer binarizer, const Ima return {}; // silence gcc warning } -Result ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) +Barcode ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) { return FirstOrDefault(ReadBarcodes(_iv, ReaderOptions(opts).setMaxNumberOfSymbols(1))); } -Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) +Barcodes ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) { if (sizeof(PatternType) < 4 && (_iv.width() > 0xffff || _iv.height() > 0xffff)) throw std::invalid_argument("Maximum image width/height is 65535"); @@ -169,7 +169,7 @@ Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) #endif LumImagePyramid pyramid(iv, opts.downscaleThreshold() * opts.tryDownscale(), opts.downscaleFactor()); - Results results; + Barcodes res; int maxSymbols = opts.maxNumberOfSymbols() ? opts.maxNumberOfSymbols() : INT_MAX; for (auto&& iv : pyramid.layers) { auto bitmap = CreateBitmap(opts.binarizer(), iv); @@ -185,20 +185,20 @@ Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) for (auto& r : rs) { if (iv.width() != _iv.width()) r.setPosition(Scale(r.position(), _iv.width() / iv.width())); - if (!Contains(results, r)) { + if (!Contains(res, r)) { r.setReaderOptions(opts); r.setIsInverted(bitmap->inverted()); - results.push_back(std::move(r)); + res.push_back(std::move(r)); --maxSymbols; } } if (maxSymbols <= 0) - return results; + return res; } } } - return results; + return res; } } // ZXing diff --git a/core/src/ReadBarcode.h b/core/src/ReadBarcode.h index 343ac8487b..a2b6608ebc 100644 --- a/core/src/ReadBarcode.h +++ b/core/src/ReadBarcode.h @@ -16,18 +16,18 @@ namespace ZXing { * * @param image view of the image data including layout and format * @param options optional ReaderOptions to parameterize / speed up detection - * @return #Result structure + * @return #Barcode structure */ -Result ReadBarcode(const ImageView& image, const ReaderOptions& options = {}); +Barcode ReadBarcode(const ImageView& image, const ReaderOptions& options = {}); /** * Read barcodes from an ImageView * * @param image view of the image data including layout and format * @param options optional ReaderOptions to parameterize / speed up detection - * @return #Results list of results found, may be empty + * @return #Barcodes list of barcodes found, may be empty */ -Results ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); +Barcodes ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); } // ZXing diff --git a/core/src/Reader.h b/core/src/Reader.h index 7ece21b56d..ab91b868dc 100644 --- a/core/src/Reader.h +++ b/core/src/Reader.h @@ -26,12 +26,12 @@ class Reader explicit Reader(ReaderOptions&& opts) = delete; virtual ~Reader() = default; - virtual Result decode(const BinaryBitmap& image) const = 0; + virtual Barcode decode(const BinaryBitmap& image) const = 0; // WARNING: this API is experimental and may change/disappear - virtual Results decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { + virtual Barcodes decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { auto res = decode(image); - return res.isValid() || (_opts.returnErrors() && res.format() != BarcodeFormat::None) ? Results{std::move(res)} : Results{}; + return res.isValid() || (_opts.returnErrors() && res.format() != BarcodeFormat::None) ? Barcodes{std::move(res)} : Barcodes{}; } }; diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index e74db5a33d..6df6e800b2 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -153,13 +153,13 @@ class ReaderOptions /// Deprecated / does nothing. Codabar start/stop characters are always returned. ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd, [[deprecated]]) - /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Result::error()) + /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Barcode::error()) ZX_PROPERTY(bool, returnErrors, setReturnErrors) /// Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) - /// Specifies the TextMode that controls the return of the Result::text() function + /// Specifies the TextMode that controls the return of the Barcode::text() function ZX_PROPERTY(TextMode, textMode, setTextMode) /// Specifies fallback character set to use instead of auto-detecting it (when applicable) diff --git a/core/src/Result.cpp b/core/src/Result.cpp index c637811a25..15c33ee997 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -157,45 +157,45 @@ bool Result::operator==(const Result& o) const return std::min(dTop, dBot) < length / 2 && dLength < length / 5; } -Result MergeStructuredAppendSequence(const Results& results) +Barcode MergeStructuredAppendSequence(const Barcodes& barcodes) { - if (results.empty()) + if (barcodes.empty()) return {}; - std::list allResults(results.begin(), results.end()); - allResults.sort([](const Result& r1, const Result& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); + std::list allBarcodes(barcodes.begin(), barcodes.end()); + allBarcodes.sort([](const Barcode& r1, const Barcode& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); - Result res = allResults.front(); - for (auto i = std::next(allResults.begin()); i != allResults.end(); ++i) + Barcode res = allBarcodes.front(); + for (auto i = std::next(allBarcodes.begin()); i != allBarcodes.end(); ++i) res._content.append(i->_content); res._position = {}; res._sai.index = -1; - if (allResults.back().sequenceSize() != Size(allResults) || - !std::all_of(allResults.begin(), allResults.end(), - [&](Result& it) { return it.sequenceId() == allResults.front().sequenceId(); })) + if (allBarcodes.back().sequenceSize() != Size(allBarcodes) || + !std::all_of(allBarcodes.begin(), allBarcodes.end(), + [&](Barcode& it) { return it.sequenceId() == allBarcodes.front().sequenceId(); })) res._error = FormatError("sequenceIDs not matching during structured append sequence merging"); return res; } -Results MergeStructuredAppendSequences(const Results& results) +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes) { - std::map sas; - for (auto& res : results) { - if (res.isPartOfSequence()) - sas[res.sequenceId()].push_back(res); + std::map sas; + for (auto& barcode : barcodes) { + if (barcode.isPartOfSequence()) + sas[barcode.sequenceId()].push_back(barcode); } - Results saiResults; + Barcodes res; for (auto& [id, seq] : sas) { - auto res = MergeStructuredAppendSequence(seq); - if (res.isValid()) - saiResults.push_back(std::move(res)); + auto barcode = MergeStructuredAppendSequence(seq); + if (barcode.isValid()) + res.push_back(std::move(barcode)); } - return saiResults; + return res; } } // ZXing diff --git a/core/src/Result.h b/core/src/Result.h index 7d46238551..1f87a08634 100644 --- a/core/src/Result.h +++ b/core/src/Result.h @@ -22,20 +22,24 @@ namespace ZXing { class DecoderResult; class ImageView; +class Result; // TODO: 3.0 replace deprected symbol name using Position = QuadrilateralI; +using Barcode = Result; +using Barcodes = std::vector; +using Results = std::vector; /** - * @brief The Result class encapsulates the result of decoding a barcode within an image. + * @brief The Barcode class encapsulates the result of decoding a barcode within an image. */ class Result { void setIsInverted(bool v) { _isInverted = v; } Result& setReaderOptions(const ReaderOptions& opts); - friend Result MergeStructuredAppendSequence(const std::vector& results); - friend std::vector ReadBarcodes(const ImageView&, const ReaderOptions&); - friend void IncrementLineCount(Result&); + friend Barcode MergeStructuredAppendSequence(const Barcodes&); + friend Barcodes ReadBarcodes(const ImageView&, const ReaderOptions&); + friend void IncrementLineCount(Barcode&); public: Result() = default; @@ -168,16 +172,14 @@ class Result bool _readerInit = false; }; -using Results = std::vector; - /** - * @brief Merge a list of Results from one Structured Append sequence to a single result + * @brief Merge a list of Barcodes from one Structured Append sequence to a single barcode */ -Result MergeStructuredAppendSequence(const Results& results); +Barcode MergeStructuredAppendSequence(const Barcodes& results); /** - * @brief Automatically merge all Structured Append sequences found in the given results + * @brief Automatically merge all Structured Append sequences found in the given list of barcodes */ -Results MergeStructuredAppendSequences(const Results& results); +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes); } // ZXing diff --git a/core/src/ZXAlgorithms.h b/core/src/ZXAlgorithms.h index e6f1b99368..66898363eb 100644 --- a/core/src/ZXAlgorithms.h +++ b/core/src/ZXAlgorithms.h @@ -47,9 +47,9 @@ inline bool Contains(const char* str, char c) { } template