From 8202a4e13a65de3a2b459d121af7b02cc7d2e9b5 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 6 Jul 2023 12:42:22 +0200 Subject: [PATCH 001/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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 zcmb5Wb9gP!wgnp7wrv|bwr$&XvSZt}Z6`anZSUAlc9NHKf9JdJ;NJVr`=eI(_pMp0 zy1VAAG3FfAOI`{X1O)&90s;U4K;XLp008~hCjbEC_fbYfS%6kTR+JtXK>nW$ZR+`W ze|#J8f4A@M|F5BpfUJb5h>|j$jOe}0oE!`Zf6fM>CR?!y@zU(cL8NsKk`a z6tx5mAkdjD;J=LcJ;;Aw8p!v#ouk>mUDZF@ zK>yvw%+bKu+T{Nk@LZ;zkYy0HBKw06_IWcMHo*0HKpTsEFZhn5qCHH9j z)|XpN&{`!0a>Vl+PmdQc)Yg4A(AG-z!+@Q#eHr&g<9D?7E)_aEB?s_rx>UE9TUq|? z;(ggJt>9l?C|zoO@5)tu?EV0x_7T17q4fF-q3{yZ^ipUbKcRZ4Qftd!xO(#UGhb2y>?*@{xq%`(-`2T^vc=#< zx!+@4pRdk&*1ht2OWk^Z5IAQ0YTAXLkL{(D*$gENaD)7A%^XXrCchN&z2x+*>o2FwPFjWpeaL=!tzv#JOW#( z$B)Nel<+$bkH1KZv3&-}=SiG~w2sbDbAWarg%5>YbC|}*d9hBjBkR(@tyM0T)FO$# zPtRXukGPnOd)~z=?avu+4Co@wF}1T)-uh5jI<1$HLtyDrVak{gw`mcH@Q-@wg{v^c zRzu}hMKFHV<8w}o*yg6p@Sq%=gkd~;`_VGTS?L@yVu`xuGy+dH6YOwcP6ZE`_0rK% zAx5!FjDuss`FQ3eF|mhrWkjux(Pny^k$u_)dyCSEbAsecHsq#8B3n3kDU(zW5yE|( zgc>sFQywFj5}U*qtF9Y(bi*;>B7WJykcAXF86@)z|0-Vm@jt!EPoLA6>r)?@DIobIZ5Sx zsc@OC{b|3%vaMbyeM|O^UxEYlEMHK4r)V-{r)_yz`w1*xV0|lh-LQOP`OP`Pk1aW( z8DSlGN>Ts|n*xj+%If~+E_BxK)~5T#w6Q1WEKt{!Xtbd`J;`2a>8boRo;7u2M&iOop4qcy<)z023=oghSFV zST;?S;ye+dRQe>ygiJ6HCv4;~3DHtJ({fWeE~$H@mKn@Oh6Z(_sO>01JwH5oA4nvK zr5Sr^g+LC zLt(i&ecdmqsIJGNOSUyUpglvhhrY8lGkzO=0USEKNL%8zHshS>Qziu|`eyWP^5xL4 zRP122_dCJl>hZc~?58w~>`P_s18VoU|7(|Eit0-lZRgLTZKNq5{k zE?V=`7=R&ro(X%LTS*f+#H-mGo_j3dm@F_krAYegDLk6UV{`UKE;{YSsn$ z(yz{v1@p|p!0>g04!eRSrSVb>MQYPr8_MA|MpoGzqyd*$@4j|)cD_%^Hrd>SorF>@ zBX+V<@vEB5PRLGR(uP9&U&5=(HVc?6B58NJT_igiAH*q~Wb`dDZpJSKfy5#Aag4IX zj~uv74EQ_Q_1qaXWI!7Vf@ZrdUhZFE;L&P_Xr8l@GMkhc#=plV0+g(ki>+7fO%?Jb zl+bTy7q{w^pTb{>(Xf2q1BVdq?#f=!geqssXp z4pMu*q;iiHmA*IjOj4`4S&|8@gSw*^{|PT}Aw~}ZXU`6=vZB=GGeMm}V6W46|pU&58~P+?LUs%n@J}CSrICkeng6YJ^M? zS(W?K4nOtoBe4tvBXs@@`i?4G$S2W&;$z8VBSM;Mn9 zxcaEiQ9=vS|bIJ>*tf9AH~m&U%2+Dim<)E=}KORp+cZ^!@wI`h1NVBXu{@%hB2Cq(dXx_aQ9x3mr*fwL5!ZryQqi|KFJuzvP zK1)nrKZ7U+B{1ZmJub?4)Ln^J6k!i0t~VO#=q1{?T)%OV?MN}k5M{}vjyZu#M0_*u z8jwZKJ#Df~1jcLXZL7bnCEhB6IzQZ-GcoQJ!16I*39iazoVGugcKA{lhiHg4Ta2fD zk1Utyc5%QzZ$s3;p0N+N8VX{sd!~l*Ta3|t>lhI&G`sr6L~G5Lul`>m z{!^INm?J|&7X=;{XveF!(b*=?9NAp4y&r&N3(GKcW4rS(Ejk|Lzs1PrxPI_owB-`H zg3(Rruh^&)`TKA6+_!n>RdI6pw>Vt1_j&+bKIaMTYLiqhZ#y_=J8`TK{Jd<7l9&sY z^^`hmi7^14s16B6)1O;vJWOF$=$B5ONW;;2&|pUvJlmeUS&F;DbSHCrEb0QBDR|my zIs+pE0Y^`qJTyH-_mP=)Y+u^LHcuZhsM3+P||?+W#V!_6E-8boP#R-*na4!o-Q1 zVthtYhK{mDhF(&7Okzo9dTi03X(AE{8cH$JIg%MEQca`S zy@8{Fjft~~BdzWC(di#X{ny;!yYGK9b@=b|zcKZ{vv4D8i+`ilOPl;PJl{!&5-0!w z^fOl#|}vVg%=n)@_e1BrP)`A zKPgs`O0EO}Y2KWLuo`iGaKu1k#YR6BMySxQf2V++Wo{6EHmK>A~Q5o73yM z-RbxC7Qdh0Cz!nG+7BRZE>~FLI-?&W_rJUl-8FDIaXoNBL)@1hwKa^wOr1($*5h~T zF;%f^%<$p8Y_yu(JEg=c_O!aZ#)Gjh$n(hfJAp$C2he555W5zdrBqjFmo|VY+el;o z=*D_w|GXG|p0**hQ7~9-n|y5k%B}TAF0iarDM!q-jYbR^us(>&y;n^2l0C%@2B}KM zyeRT9)oMt97Agvc4sEKUEy%MpXr2vz*lb zh*L}}iG>-pqDRw7ud{=FvTD?}xjD)w{`KzjNom-$jS^;iw0+7nXSnt1R@G|VqoRhE%12nm+PH?9`(4rM0kfrZzIK9JU=^$YNyLvAIoxl#Q)xxDz!^0@zZ zSCs$nfcxK_vRYM34O<1}QHZ|hp4`ioX3x8(UV(FU$J@o%tw3t4k1QPmlEpZa2IujG&(roX_q*%e`Hq|);0;@k z0z=fZiFckp#JzW0p+2A+D$PC~IsakhJJkG(c;CqAgFfU0Z`u$PzG~-9I1oPHrCw&)@s^Dc~^)#HPW0Ra}J^=|h7Fs*<8|b13ZzG6MP*Q1dkoZ6&A^!}|hbjM{2HpqlSXv_UUg1U4gn z3Q)2VjU^ti1myodv+tjhSZp%D978m~p& z43uZUrraHs80Mq&vcetqfQpQP?m!CFj)44t8Z}k`E798wxg&~aCm+DBoI+nKq}&j^ zlPY3W$)K;KtEajks1`G?-@me7C>{PiiBu+41#yU_c(dITaqE?IQ(DBu+c^Ux!>pCj zLC|HJGU*v+!it1(;3e`6igkH(VA)-S+k(*yqxMgUah3$@C zz`7hEM47xr>j8^g`%*f=6S5n>z%Bt_Fg{Tvmr+MIsCx=0gsu_sF`q2hlkEmisz#Fy zj_0;zUWr;Gz}$BS%Y`meb(=$d%@Crs(OoJ|}m#<7=-A~PQbyN$x%2iXP2@e*nO0b7AwfH8cCUa*Wfu@b)D_>I*%uE4O3 z(lfnB`-Xf*LfC)E}e?%X2kK7DItK6Tf<+M^mX0Ijf_!IP>7c8IZX%8_#0060P{QMuV^B9i<^E`_Qf0pv9(P%_s8D`qvDE9LK9u-jB}J2S`(mCO&XHTS04Z5Ez*vl^T%!^$~EH8M-UdwhegL>3IQ*)(MtuH2Xt1p!fS4o~*rR?WLxlA!sjc2(O znjJn~wQ!Fp9s2e^IWP1C<4%sFF}T4omr}7+4asciyo3DntTgWIzhQpQirM$9{EbQd z3jz9vS@{aOqTQHI|l#aUV@2Q^Wko4T0T04Me4!2nsdrA8QY1%fnAYb~d2GDz@lAtfcHq(P7 zaMBAGo}+NcE-K*@9y;Vt3*(aCaMKXBB*BJcD_Qnxpt75r?GeAQ}*|>pYJE=uZb73 zC>sv)18)q#EGrTG6io*}JLuB_jP3AU1Uiu$D7r|2_zlIGb9 zjhst#ni)Y`$)!fc#reM*$~iaYoz~_Cy7J3ZTiPm)E?%`fbk`3Tu-F#`{i!l5pNEn5 zO-Tw-=TojYhzT{J=?SZj=Z8#|eoF>434b-DXiUsignxXNaR3 zm_}4iWU$gt2Mw5NvZ5(VpF`?X*f2UZDs1TEa1oZCif?Jdgr{>O~7}-$|BZ7I(IKW`{f;@|IZFX*R8&iT= zoWstN8&R;}@2Ka%d3vrLtR|O??ben;k8QbS-WB0VgiCz;<$pBmIZdN!aalyCSEm)crpS9dcD^Y@XT1a3+zpi-`D}e#HV<} z$Y(G&o~PvL-xSVD5D?JqF3?B9rxGWeb=oEGJ3vRp5xfBPlngh1O$yI95EL+T8{GC@ z98i1H9KhZGFl|;`)_=QpM6H?eDPpw~^(aFQWwyXZ8_EEE4#@QeT_URray*mEOGsGc z6|sdXtq!hVZo=d#+9^@lm&L5|q&-GDCyUx#YQiccq;spOBe3V+VKdjJA=IL=Zn%P} zNk=_8u}VhzFf{UYZV0`lUwcD&)9AFx0@Fc6LD9A6Rd1=ga>Mi0)_QxM2ddCVRmZ0d z+J=uXc(?5JLX3=)e)Jm$HS2yF`44IKhwRnm2*669_J=2LlwuF5$1tAo@ROSU@-y+;Foy2IEl2^V1N;fk~YR z?&EP8#t&m0B=?aJeuz~lHjAzRBX>&x=A;gIvb>MD{XEV zV%l-+9N-)i;YH%nKP?>f`=?#`>B(`*t`aiPLoQM(a6(qs4p5KFjDBN?8JGrf3z8>= zi7sD)c)Nm~x{e<^jy4nTx${P~cwz_*a>%0_;ULou3kHCAD7EYkw@l$8TN#LO9jC( z1BeFW`k+bu5e8Ns^a8dPcjEVHM;r6UX+cN=Uy7HU)j-myRU0wHd$A1fNI~`4;I~`zC)3ul#8#^rXVSO*m}Ag>c%_;nj=Nv$rCZ z*~L@C@OZg%Q^m)lc-kcX&a*a5`y&DaRxh6O*dfhLfF+fU5wKs(1v*!TkZidw*)YBP za@r`3+^IHRFeO%!ai%rxy;R;;V^Fr=OJlpBX;(b*3+SIw}7= zIq$*Thr(Zft-RlY)D3e8V;BmD&HOfX+E$H#Y@B3?UL5L~_fA-@*IB-!gItK7PIgG9 zgWuGZK_nuZjHVT_Fv(XxtU%)58;W39vzTI2n&)&4Dmq7&JX6G>XFaAR{7_3QB6zsT z?$L8c*WdN~nZGiscY%5KljQARN;`w$gho=p006z;n(qIQ*Zu<``TMO3n0{ARL@gYh zoRwS*|Niw~cR!?hE{m*y@F`1)vx-JRfqET=dJ5_(076st(=lFfjtKHoYg`k3oNmo_ zNbQEw8&sO5jAYmkD|Zaz_yUb0rC})U!rCHOl}JhbYIDLzLvrZVw0~JO`d*6f;X&?V=#T@ND*cv^I;`sFeq4 z##H5;gpZTb^0Hz@3C*~u0AqqNZ-r%rN3KD~%Gw`0XsIq$(^MEb<~H(2*5G^<2(*aI z%7}WB+TRlMIrEK#s0 z93xn*Ohb=kWFc)BNHG4I(~RPn-R8#0lqyBBz5OM6o5|>x9LK@%HaM}}Y5goCQRt2C z{j*2TtT4ne!Z}vh89mjwiSXG=%DURar~=kGNNaO_+Nkb+tRi~Rkf!7a$*QlavziD( z83s4GmQ^Wf*0Bd04f#0HX@ua_d8 z23~z*53ePD6@xwZ(vdl0DLc=>cPIOPOdca&MyR^jhhKrdQO?_jJh`xV3GKz&2lvP8 zEOwW6L*ufvK;TN{=S&R@pzV^U=QNk^Ec}5H z+2~JvEVA{`uMAr)?Kf|aW>33`)UL@bnfIUQc~L;TsTQ6>r-<^rB8uoNOJ>HWgqMI8 zSW}pZmp_;z_2O5_RD|fGyTxaxk53Hg_3Khc<8AUzV|ZeK{fp|Ne933=1&_^Dbv5^u zB9n=*)k*tjHDRJ@$bp9mrh}qFn*s}npMl5BMDC%Hs0M0g-hW~P*3CNG06G!MOPEQ_ zi}Qs-6M8aMt;sL$vlmVBR^+Ry<64jrm1EI1%#j?c?4b*7>)a{aDw#TfTYKq+SjEFA z(aJ&z_0?0JB83D-i3Vh+o|XV4UP+YJ$9Boid2^M2en@APw&wx7vU~t$r2V`F|7Qfo z>WKgI@eNBZ-+Og<{u2ZiG%>YvH2L3fNpV9J;WLJoBZda)01Rn;o@){01{7E#ke(7U zHK>S#qZ(N=aoae*4X!0A{)nu0R_sKpi1{)u>GVjC+b5Jyl6#AoQ-1_3UDovNSo`T> z?c-@7XX*2GMy?k?{g)7?Sv;SJkmxYPJPs!&QqB12ejq`Lee^-cDveVWL^CTUldb(G zjDGe(O4P=S{4fF=#~oAu>LG>wrU^z_?3yt24FOx>}{^lCGh8?vtvY$^hbZ)9I0E3r3NOlb9I?F-Yc=r$*~l`4N^xzlV~N zl~#oc>U)Yjl0BxV>O*Kr@lKT{Z09OXt2GlvE38nfs+DD7exl|&vT;)>VFXJVZp9Np zDK}aO;R3~ag$X*|hRVY3OPax|PG`@_ESc8E!mHRByJbZQRS38V2F__7MW~sgh!a>98Q2%lUNFO=^xU52|?D=IK#QjwBky-C>zOWlsiiM&1n z;!&1((Xn1$9K}xabq~222gYvx3hnZPg}VMF_GV~5ocE=-v>V=T&RsLBo&`)DOyIj* zLV{h)JU_y*7SdRtDajP_Y+rBkNN*1_TXiKwHH2&p51d(#zv~s#HwbNy?<+(=9WBvo zw2hkk2Dj%kTFhY+$T+W-b7@qD!bkfN#Z2ng@Pd=i3-i?xYfs5Z*1hO?kd7Sp^9`;Y zM2jeGg<-nJD1er@Pc_cSY7wo5dzQX44=%6rn}P_SRbpzsA{6B+!$3B0#;}qwO37G^ zL(V_5JK`XT?OHVk|{_$vQ|oNEpab*BO4F zUTNQ7RUhnRsU`TK#~`)$icsvKh~(pl=3p6m98@k3P#~upd=k*u20SNcb{l^1rUa)>qO997)pYRWMncC8A&&MHlbW?7i^7M`+B$hH~Y|J zd>FYOGQ;j>Zc2e7R{KK7)0>>nn_jYJy&o@sK!4G>-rLKM8Hv)f;hi1D2fAc$+six2 zyVZ@wZ6x|fJ!4KrpCJY=!Mq0;)X)OoS~{Lkh6u8J`eK%u0WtKh6B>GW_)PVc zl}-k`p09qwGtZ@VbYJC!>29V?Dr>>vk?)o(x?!z*9DJ||9qG-&G~#kXxbw{KKYy}J zQKa-dPt~M~E}V?PhW0R26xdA%1T*%ra6SguGu50YHngOTIv)@N|YttEXo#OZfgtP7;H?EeZZxo<}3YlYxtBq znJ!WFR^tmGf0Py}N?kZ(#=VtpC@%xJkDmfcCoBTxq zr_|5gP?u1@vJZbxPZ|G0AW4=tpb84gM2DpJU||(b8kMOV1S3|(yuwZJ&rIiFW(U;5 zUtAW`O6F6Zy+eZ1EDuP~AAHlSY-+A_eI5Gx)%*uro5tljy}kCZU*_d7)oJ>oQSZ3* zneTn`{gnNC&uJd)0aMBzAg021?YJ~b(fmkwZAd696a=0NzBAqBN54KuNDwa*no(^O z6p05bioXUR^uXjpTol*ppHp%1v9e)vkoUAUJyBx3lw0UO39b0?^{}yb!$yca(@DUn zCquRF?t=Zb9`Ed3AI6|L{eX~ijVH`VzSMheKoP7LSSf4g>md>`yi!TkoG5P>Ofp+n z(v~rW+(5L96L{vBb^g51B=(o)?%%xhvT*A5btOpw(TKh^g^4c zw>0%X!_0`{iN%RbVk+A^f{w-4-SSf*fu@FhruNL##F~sF24O~u zyYF<3el2b$$wZ_|uW#@Ak+VAGk#e|kS8nL1g>2B-SNMjMp^8;-FfeofY2fphFHO!{ z*!o4oTb{4e;S<|JEs<1_hPsmAlVNk?_5-Fp5KKU&d#FiNW~Y+pVFk@Cua1I{T+1|+ zHx6rFMor)7L)krbilqsWwy@T+g3DiH5MyVf8Wy}XbEaoFIDr~y;@r&I>FMW{ z?Q+(IgyebZ)-i4jNoXQhq4Muy9Fv+OxU;9_Jmn+<`mEC#%2Q_2bpcgzcinygNI!&^ z=V$)o2&Yz04~+&pPWWn`rrWxJ&}8khR)6B(--!9Q zubo}h+1T)>a@c)H^i``@<^j?|r4*{;tQf78(xn0g39IoZw0(CwY1f<%F>kEaJ zp9u|IeMY5mRdAlw*+gSN^5$Q)ShM<~E=(c8QM+T-Qk)FyKz#Sw0EJ*edYcuOtO#~Cx^(M7w5 z3)rl#L)rF|(Vun2LkFr!rg8Q@=r>9p>(t3Gf_auiJ2Xx9HmxYTa|=MH_SUlYL`mz9 zTTS$`%;D-|Jt}AP1&k7PcnfFNTH0A-*FmxstjBDiZX?}%u%Yq94$fUT&z6od+(Uk> zuqsld#G(b$G8tus=M!N#oPd|PVFX)?M?tCD0tS%2IGTfh}3YA3f&UM)W$_GNV8 zQo+a(ml2Km4o6O%gKTCSDNq+#zCTIQ1*`TIJh~k6Gp;htHBFnne))rlFdGqwC6dx2+La1&Mnko*352k0y z+tQcwndQlX`nc6nb$A9?<-o|r*%aWXV#=6PQic0Ok_D;q>wbv&j7cKc!w4~KF#-{6 z(S%6Za)WpGIWf7jZ3svNG5OLs0>vCL9{V7cgO%zevIVMH{WgP*^D9ws&OqA{yr|m| zKD4*07dGXshJHd#e%x%J+qmS^lS|0Bp?{drv;{@{l9ArPO&?Q5=?OO9=}h$oVe#3b z3Yofj&Cb}WC$PxmRRS)H%&$1-)z7jELS}!u!zQ?A^Y{Tv4QVt*vd@uj-^t2fYRzQj zfxGR>-q|o$3sGn^#VzZ!QQx?h9`njeJry}@x?|k0-GTTA4y3t2E`3DZ!A~D?GiJup z)8%PK2^9OVRlP(24P^4_<|D=H^7}WlWu#LgsdHzB%cPy|f8dD3|A^mh4WXxhLTVu_ z@abE{6Saz|Y{rXYPd4$tfPYo}ef(oQWZ=4Bct-=_9`#Qgp4ma$n$`tOwq#&E18$B; z@Bp)bn3&rEi0>fWWZ@7k5WazfoX`SCO4jQWwVuo+$PmSZn^Hz?O(-tW@*DGxuf)V1 zO_xm&;NVCaHD4dqt(-MlszI3F-p?0!-e$fbiCeuaw66h^TTDLWuaV<@C-`=Xe5WL) zwooG7h>4&*)p3pKMS3O!4>-4jQUN}iAMQ)2*70?hP~)TzzR?-f@?Aqy$$1Iy8VGG$ zMM?8;j!pUX7QQD$gRc_#+=raAS577ga-w?jd`vCiN5lu)dEUkkUPl9!?{$IJNxQys z*E4e$eF&n&+AMRQR2gcaFEjAy*r)G!s(P6D&TfoApMFC_*Ftx0|D0@E-=B7tezU@d zZ{hGiN;YLIoSeRS;9o%dEua4b%4R3;$SugDjP$x;Z!M!@QibuSBb)HY!3zJ7M;^jw zlx6AD50FD&p3JyP*>o+t9YWW8(7P2t!VQQ21pHJOcG_SXQD;(5aX#M6x##5H_Re>6lPyDCjxr*R(+HE%c&QN+b^tbT zXBJk?p)zhJj#I?&Y2n&~XiytG9!1ox;bw5Rbj~)7c(MFBb4>IiRATdhg zmiEFlj@S_hwYYI(ki{}&<;_7(Z0Qkfq>am z&LtL=2qc7rWguk3BtE4zL41@#S;NN*-jWw|7Kx7H7~_%7fPt;TIX}Ubo>;Rmj94V> zNB1=;-9AR7s`Pxn}t_6^3ahlq53e&!Lh85uG zec0vJY_6e`tg7LgfrJ3k!DjR)Bi#L@DHIrZ`sK=<5O0Ip!fxGf*OgGSpP@Hbbe&$9 z;ZI}8lEoC2_7;%L2=w?tb%1oL0V+=Z`7b=P&lNGY;yVBazXRYu;+cQDKvm*7NCxu&i;zub zAJh#11%?w>E2rf2e~C4+rAb-&$^vsdACs7 z@|Ra!OfVM(ke{vyiqh7puf&Yp6cd6{DptUteYfIRWG3pI+5< zBVBI_xkBAc<(pcb$!Y%dTW(b;B;2pOI-(QCsLv@U-D1XJ z(Gk8Q3l7Ws46Aktuj>|s{$6zA&xCPuXL-kB`CgYMs}4IeyG*P51IDwW?8UNQd+$i~ zlxOPtSi5L|gJcF@DwmJA5Ju8HEJ>o{{upwIpb!f{2(vLNBw`7xMbvcw<^{Fj@E~1( z?w`iIMieunS#>nXlmUcSMU+D3rX28f?s7z;X=se6bo8;5vM|O^(D6{A9*ChnGH!RG zP##3>LDC3jZPE4PH32AxrqPk|yIIrq~`aL-=}`okhNu9aT%q z1b)7iJ)CN=V#Ly84N_r7U^SH2FGdE5FpTO2 z630TF$P>GNMu8`rOytb(lB2};`;P4YNwW1<5d3Q~AX#P0aX}R2b2)`rgkp#zTxcGj zAV^cvFbhP|JgWrq_e`~exr~sIR$6p5V?o4Wym3kQ3HA+;Pr$bQ0(PmADVO%MKL!^q z?zAM8j1l4jrq|5X+V!8S*2Wl@=7*pPgciTVK6kS1Ge zMsd_u6DFK$jTnvVtE;qa+8(1sGBu~n&F%dh(&c(Zs4Fc#A=gG^^%^AyH}1^?|8quj zl@Z47h$){PlELJgYZCIHHL= z{U8O>Tw4x3<1{?$8>k-P<}1y9DmAZP_;(3Y*{Sk^H^A=_iSJ@+s5ktgwTXz_2$~W9>VVZsfwCm@s0sQ zeB50_yu@uS+e7QoPvdCwDz{prjo(AFwR%C?z`EL{1`|coJHQTk^nX=tvs1<0arUOJ z!^`*x&&BvTYmemyZ)2p~{%eYX=JVR?DYr(rNgqRMA5E1PR1Iw=prk=L2ldy3r3Vg@27IZx43+ywyzr-X*p*d@tZV+!U#~$-q=8c zgdSuh#r?b4GhEGNai)ayHQpk>5(%j5c@C1K3(W1pb~HeHpaqijJZa-e6vq_8t-^M^ zBJxq|MqZc?pjXPIH}70a5vt!IUh;l}<>VX<-Qcv^u@5(@@M2CHSe_hD$VG-eiV^V( zj7*9T0?di?P$FaD6oo?)<)QT>Npf6Og!GO^GmPV(Km0!=+dE&bk#SNI+C9RGQ|{~O*VC+tXK3!n`5 zHfl6>lwf_aEVV3`0T!aHNZLsj$paS$=LL(?b!Czaa5bbSuZ6#$_@LK<(7yrrl+80| z{tOFd=|ta2Z`^ssozD9BINn45NxUeCQis?-BKmU*Kt=FY-NJ+)8S1ecuFtN-M?&42 zl2$G>u!iNhAk*HoJ^4v^9#ORYp5t^wDj6|lx~5w45#E5wVqI1JQ~9l?nPp1YINf++ zMAdSif~_ETv@Er(EFBI^@L4BULFW>)NI+ejHFP*T}UhWNN`I)RRS8za? z*@`1>9ZB}An%aT5K=_2iQmfE;GcBVHLF!$`I99o5GO`O%O_zLr9AG18>&^HkG(;=V z%}c!OBQ~?MX(9h~tajX{=x)+!cbM7$YzTlmsPOdp2L-?GoW`@{lY9U3f;OUo*BwRB z8A+nv(br0-SH#VxGy#ZrgnGD(=@;HME;yd46EgWJ`EL%oXc&lFpc@Y}^>G(W>h_v_ zlN!`idhX+OjL+~T?19sroAFVGfa5tX-D49w$1g2g_-T|EpHL6}K_aX4$K=LTvwtlF zL*z}j{f+Uoe7{-px3_5iKPA<_7W=>Izkk)!l9ez2w%vi(?Y;i8AxRNLSOGDzNoqoI zP!1uAl}r=_871(G?y`i&)-7{u=%nxk7CZ_Qh#!|ITec zwQn`33GTUM`;D2POWnkqngqJhJRlM>CTONzTG}>^Q0wUunQyn|TAiHzyX2_%ATx%P z%7gW)%4rA9^)M<_%k@`Y?RbC<29sWU&5;@|9thf2#zf8z12$hRcZ!CSb>kUp=4N#y zl3hE#y6>kkA8VY2`W`g5Ip?2qC_BY$>R`iGQLhz2-S>x(RuWv)SPaGdl^)gGw7tjR zH@;jwk!jIaCgSg_*9iF|a);sRUTq30(8I(obh^|}S~}P4U^BIGYqcz;MPpC~Y@k_m zaw4WG1_vz2GdCAX!$_a%GHK**@IrHSkGoN>)e}>yzUTm52on`hYot7cB=oA-h1u|R ztH$11t?54Qg2L+i33FPFKKRm1aOjKST{l1*(nps`>sv%VqeVMWjl5+Gh+9);hIP8? zA@$?}Sc z3qIRpba+y5yf{R6G(u8Z^vkg0Fu&D-7?1s=QZU`Ub{-!Y`I?AGf1VNuc^L3v>)>i# z{DV9W$)>34wnzAXUiV^ZpYKw>UElrN_5Xj6{r_3| z$X5PK`e5$7>~9Dj7gK5ash(dvs`vwfk}&RD`>04;j62zoXESkFBklYaKm5seyiX(P zqQ-;XxlV*yg?Dhlx%xt!b0N3GHp@(p$A;8|%# zZ5m2KL|{on4nr>2_s9Yh=r5ScQ0;aMF)G$-9-Ca6%wA`Pa)i?NGFA|#Yi?{X-4ZO_ z^}%7%vkzvUHa$-^Y#aA+aiR5sa%S|Ebyn`EV<3Pc?ax_f>@sBZF1S;7y$CXd5t5=WGsTKBk8$OfH4v|0?0I=Yp}7c=WBSCg!{0n)XmiU;lfx)**zZaYqmDJelxk$)nZyx5`x$6R|fz(;u zEje5Dtm|a%zK!!tk3{i9$I2b{vXNFy%Bf{50X!x{98+BsDr_u9i>G5%*sqEX|06J0 z^IY{UcEbj6LDwuMh7cH`H@9sVt1l1#8kEQ(LyT@&+K}(ReE`ux8gb0r6L_#bDUo^P z3Ka2lRo52Hdtl_%+pwVs14=q`{d^L58PsU@AMf(hENumaxM{7iAT5sYmWh@hQCO^ zK&}ijo=`VqZ#a3vE?`7QW0ZREL17ZvDfdqKGD?0D4fg{7v%|Yj&_jcKJAB)>=*RS* zto8p6@k%;&^ZF>hvXm&$PCuEp{uqw3VPG$9VMdW5$w-fy2CNNT>E;>ejBgy-m_6`& z97L1p{%srn@O_JQgFpa_#f(_)eb#YS>o>q3(*uB;uZb605(iqM$=NK{nHY=+X2*G) zO3-_Xh%aG}fHWe*==58zBwp%&`mge<8uq8;xIxOd=P%9EK!34^E9sk|(Zq1QSz-JVeP12Fp)-`F|KY$LPwUE?rku zY@OJ)Z9A!ojfzfeyJ9;zv2EM7ZQB)AR5xGa-tMn^bl)FmoIiVyJ@!~@%{}qXXD&Ns zPnfe5U+&ohKefILu_1mPfLGuapX@btta5C#gPB2cjk5m4T}Nfi+Vfka!Yd(L?-c~5 z#ZK4VeQEXNPc4r$K00Fg>g#_W!YZ)cJ?JTS<&68_$#cZT-ME`}tcwqg3#``3M3UPvn+pi}(VNNx6y zFIMVb6OwYU(2`at$gHba*qrMVUl8xk5z-z~fb@Q3Y_+aXuEKH}L+>eW__!IAd@V}L zkw#s%H0v2k5-=vh$^vPCuAi22Luu3uKTf6fPo?*nvj$9(u)4$6tvF-%IM+3pt*cgs z_?wW}J7VAA{_~!?))?s6{M=KPpVhg4fNuU*|3THp@_(q!b*hdl{fjRVFWtu^1dV(f z6iOux9hi&+UK=|%M*~|aqFK{Urfl!TA}UWY#`w(0P!KMe1Si{8|o))Gy6d7;!JQYhgMYmXl?3FfOM2nQGN@~Ap6(G z3+d_5y@=nkpKAhRqf{qQ~k7Z$v&l&@m7Ppt#FSNzKPZM z8LhihcE6i=<(#87E|Wr~HKvVWhkll4iSK$^mUHaxgy8*K$_Zj;zJ`L$naPj+^3zTi z-3NTaaKnD5FPY-~?Tq6QHnmDDRxu0mh0D|zD~Y=vv_qig5r-cIbCpxlju&8Sya)@{ zsmv6XUSi)@(?PvItkiZEeN*)AE~I_?#+Ja-r8$(XiXei2d@Hi7Rx8+rZZb?ZLa{;@*EHeRQ-YDadz~M*YCM4&F-r;E#M+@CSJMJ0oU|PQ^ z=E!HBJDMQ2TN*Y(Ag(ynAL8%^v;=~q?s4plA_hig&5Z0x_^Oab!T)@6kRN$)qEJ6E zNuQjg|G7iwU(N8pI@_6==0CL;lRh1dQF#wePhmu@hADFd3B5KIH#dx(2A zp~K&;Xw}F_N6CU~0)QpQk7s$a+LcTOj1%=WXI(U=Dv!6 z{#<#-)2+gCyyv=Jw?Ab#PVkxPDeH|sAxyG`|Ys}A$PW4TdBv%zDz z^?lwrxWR<%Vzc8Sgt|?FL6ej_*e&rhqJZ3Y>k=X(^dytycR;XDU16}Pc9Vn0>_@H+ zQ;a`GSMEG64=JRAOg%~L)x*w{2re6DVprNp+FcNra4VdNjiaF0M^*>CdPkt(m150rCue?FVdL0nFL$V%5y6N z%eLr5%YN7D06k5ji5*p4v$UMM)G??Q%RB27IvH7vYr_^3>1D-M66#MN8tWGw>WED} z5AhlsanO=STFYFs)Il_0i)l)f<8qn|$DW7ZXhf5xI;m+7M5-%P63XFQrG9>DMqHc} zsgNU9nR`b}E^mL5=@7<1_R~j@q_2U^3h|+`7YH-?C=vme1C3m`Fe0HC>pjt6f_XMh zy~-i-8R46QNYneL4t@)<0VU7({aUO?aH`z4V2+kxgH5pYD5)wCh75JqQY)jIPN=U6 z+qi8cGiOtXG2tXm;_CfpH9ESCz#i5B(42}rBJJF$jh<1sbpj^8&L;gzGHb8M{of+} zzF^8VgML2O9nxBW7AvdEt90vp+#kZxWf@A)o9f9}vKJy9NDBjBW zSt=Hcs=YWCwnfY1UYx*+msp{g!w0HC<_SM!VL1(I2PE?CS}r(eh?{I)mQixmo5^p# zV?2R!R@3GV6hwTCrfHiK#3Orj>I!GS2kYhk1S;aFBD_}u2v;0HYFq}Iz1Z(I4oca4 zxquja8$+8JW_EagDHf$a1OTk5S97umGSDaj)gH=fLs9>_=XvVj^Xj9a#gLdk=&3tl zfmK9MNnIX9v{?%xdw7568 zNrZ|roYs(vC4pHB5RJ8>)^*OuyNC>x7ad)tB_}3SgQ96+-JT^Qi<`xi=)_=$Skwv~ zdqeT9Pa`LYvCAn&rMa2aCDV(TMI#PA5g#RtV|CWpgDYRA^|55LLN^uNh*gOU>Z=a06qJ;$C9z8;n-Pq=qZnc1zUwJ@t)L;&NN+E5m zRkQ(SeM8=l-aoAKGKD>!@?mWTW&~)uF2PYUJ;tB^my`r9n|Ly~0c%diYzqs9W#FTjy?h&X3TnH zXqA{QI82sdjPO->f=^K^f>N`+B`q9&rN0bOXO79S&a9XX8zund(kW7O76f4dcWhIu zER`XSMSFbSL>b;Rp#`CuGJ&p$s~G|76){d?xSA5wVg##_O0DrmyEYppyBr%fyWbbv zp`K84JwRNP$d-pJ!Qk|(RMr?*!wi1if-9G#0p>>1QXKXWFy)eB3ai)l3601q8!9JC zvU#ZWWDNKq9g6fYs?JQ)Q4C_cgTy3FhgKb8s&m)DdmL5zhNK#8wWg!J*7G7Qhe9VU zha?^AQTDpYcuN!B+#1dE*X{<#!M%zfUQbj=zLE{dW0XeQ7-oIsGY6RbkP2re@Q{}r_$iiH0xU%iN*ST`A)-EH6eaZB$GA#v)cLi z*MpA(3bYk$oBDKAzu^kJoSUsDd|856DApz={3u8sbQV@JnRkp2nC|)m;#T=DvIL-O zI4vh;g7824l}*`_p@MT4+d`JZ2%6NQh=N9bmgJ#q!hK@_<`HQq3}Z8Ij>3%~<*= zcv=!oT#5xmeGI92lqm9sGVE%#X$ls;St|F#u!?5Y7syhx6q#MVRa&lBmmn%$C0QzU z);*ldgwwCmzM3uglr}!Z2G+?& zf%Dpo&mD%2ZcNFiN-Z0f;c_Q;A%f@>26f?{d1kxIJD}LxsQkB47SAdwinfMILZdN3 zfj^HmTzS3Ku5BxY>ANutS8WPQ-G>v4^_Qndy==P3pDm+Xc?>rUHl-4+^%Sp5atOja z2oP}ftw-rqnb}+khR3CrRg^ibi6?QYk1*i^;kQGirQ=uB9Sd1NTfT-Rbv;hqnY4neE5H1YUrjS2m+2&@uXiAo- zrKUX|Ohg7(6F(AoP~tj;NZlV#xsfo-5reuQHB$&EIAhyZk;bL;k9ouDmJNBAun;H& zn;Of1z_Qj`x&M;5X;{s~iGzBQTY^kv-k{ksbE*Dl%Qf%N@hQCfY~iUw!=F-*$cpf2 z3wix|aLBV0b;W@z^%7S{>9Z^T^fLOI68_;l@+Qzaxo`nAI8emTV@rRhEKZ z?*z_{oGdI~R*#<2{bkz$G~^Qef}$*4OYTgtL$e9q!FY7EqxJ2`zk6SQc}M(k(_MaV zSLJnTXw&@djco1~a(vhBl^&w=$fa9{Sru>7g8SHahv$&Bl(D@(Zwxo_3r=;VH|uc5 zi1Ny)J!<(KN-EcQ(xlw%PNwK8U>4$9nVOhj(y0l9X^vP1TA>r_7WtSExIOsz`nDOP zs}d>Vxb2Vo2e5x8p(n~Y5ggAyvib>d)6?)|E@{FIz?G3PVGLf7-;BxaP;c?7ddH$z zA+{~k^V=bZuXafOv!RPsE1GrR3J2TH9uB=Z67gok+u`V#}BR86hB1xl}H4v`F+mRfr zYhortD%@IGfh!JB(NUNSDh+qDz?4ztEgCz&bIG-Wg7w-ua4ChgQR_c+z8dT3<1?uX z*G(DKy_LTl*Ea!%v!RhpCXW1WJO6F`bgS-SB;Xw9#! z<*K}=#wVu9$`Yo|e!z-CPYH!nj7s9dEPr-E`DXUBu0n!xX~&|%#G=BeM?X@shQQMf zMvr2!y7p_gD5-!Lnm|a@z8Of^EKboZsTMk%5VsJEm>VsJ4W7Kv{<|#4f-qDE$D-W>gWT%z-!qXnDHhOvLk=?^a1*|0j z{pW{M0{#1VcR5;F!!fIlLVNh_Gj zbnW(_j?0c2q$EHIi@fSMR{OUKBcLr{Y&$hrM8XhPByyZaXy|dd&{hYQRJ9@Fn%h3p7*VQolBIV@Eq`=y%5BU~3RPa^$a?ixp^cCg z+}Q*X+CW9~TL29@OOng(#OAOd!)e$d%sr}^KBJ-?-X&|4HTmtemxmp?cT3uA?md4% zT8yZ0U;6Rg6JHy3fJae{6TMGS?ZUX6+gGTT{Q{)SI85$5FD{g-eR%O0KMpWPY`4@O zx!hen1*8^E(*}{m^V_?}(b5k3hYo=T+$&M32+B`}81~KKZhY;2H{7O-M@vbCzuX0n zW-&HXeyr1%I3$@ns-V1~Lb@wIpkmx|8I~ob1Of7i6BTNysEwI}=!nU%q7(V_^+d*G z7G;07m(CRTJup!`cdYi93r^+LY+`M*>aMuHJm(A8_O8C#A*$!Xvddgpjx5)?_EB*q zgE8o5O>e~9IiSC@WtZpF{4Bj2J5eZ>uUzY%TgWF7wdDE!fSQIAWCP)V{;HsU3ap?4 znRsiiDbtN7i9hapO;(|Ew>Ip2TZSvK9Z^N21%J?OiA_&eP1{(Pu_=%JjKy|HOardq ze?zK^K zA%sjF64*Wufad%H<) z^|t>e*h+Z1#l=5wHexzt9HNDNXgM=-OPWKd^5p!~%SIl>Fo&7BvNpbf8{NXmH)o{r zO=aBJ;meX1^{O%q;kqdw*5k!Y7%t_30 zy{nGRVc&5qt?dBwLs+^Sfp;f`YVMSB#C>z^a9@fpZ!xb|b-JEz1LBX7ci)V@W+kvQ89KWA0T~Lj$aCcfW#nD5bt&Y_< z-q{4ZXDqVg?|0o)j1%l0^_it0WF*LCn-+)c!2y5yS7aZIN$>0LqNnkujV*YVes(v$ zY@_-!Q;!ZyJ}Bg|G-~w@or&u0RO?vlt5*9~yeoPV_UWrO2J54b4#{D(D>jF(R88u2 zo#B^@iF_%S>{iXSol8jpmsZuJ?+;epg>k=$d`?GSegAVp3n$`GVDvK${N*#L_1`44 z{w0fL{2%)0|E+qgZtjX}itZz^KJt4Y;*8uSK}Ft38+3>j|K(PxIXXR-t4VopXo#9# zt|F{LWr-?34y`$nLBVV_*UEgA6AUI65dYIbqpNq9cl&uLJ0~L}<=ESlOm?Y-S@L*d z<7vt}`)TW#f%Rp$Q}6@3=j$7Tze@_uZO@aMn<|si{?S}~maII`VTjs&?}jQ4_cut9$)PEqMukwoXobzaKx^MV z2fQwl+;LSZ$qy%Tys0oo^K=jOw$!YwCv^ei4NBVauL)tN%=wz9M{uf{IB(BxK|lT*pFkmNK_1tV`nb%jH=a0~VNq2RCKY(rG7jz!-D^k)Ec)yS%17pE#o6&eY+ z^qN(hQT$}5F(=4lgNQhlxj?nB4N6ntUY6(?+R#B?W3hY_a*)hnr4PA|vJ<6p`K3Z5Hy z{{8(|ux~NLUW=!?9Qe&WXMTAkQnLXg(g=I@(VG3{HE13OaUT|DljyWXPs2FE@?`iU z4GQlM&Q=T<4&v@Fe<+TuXiZQT3G~vZ&^POfmI1K2h6t4eD}Gk5XFGpbj1n_g*{qmD6Xy z`6Vv|lLZtLmrnv*{Q%xxtcWVj3K4M%$bdBk_a&ar{{GWyu#ljM;dII;*jP;QH z#+^o-A4np{@|Mz+LphTD0`FTyxYq#wY)*&Ls5o{0z9yg2K+K7ZN>j1>N&;r+Z`vI| zDzG1LJZ+sE?m?>x{5LJx^)g&pGEpY=fQ-4}{x=ru;}FL$inHemOg%|R*ZXPodU}Kh zFEd5#+8rGq$Y<_?k-}r5zgQ3jRV=ooHiF|@z_#D4pKVEmn5CGV(9VKCyG|sT9nc=U zEoT67R`C->KY8Wp-fEcjjFm^;Cg(ls|*ABVHq8clBE(;~K^b+S>6uj70g? z&{XQ5U&!Z$SO7zfP+y^8XBbiu*Cv-yJG|l-oe*!s5$@Lh_KpxYL2sx`B|V=dETN>5K+C+CU~a_3cI8{vbu$TNVdGf15*>D zz@f{zIlorkY>TRh7mKuAlN9A0>N>SV`X)+bEHms=mfYTMWt_AJtz_h+JMmrgH?mZt zm=lfdF`t^J*XLg7v+iS)XZROygK=CS@CvUaJo&w2W!Wb@aa?~Drtf`JV^cCMjngVZ zv&xaIBEo8EYWuML+vxCpjjY^s1-ahXJzAV6hTw%ZIy!FjI}aJ+{rE&u#>rs)vzuxz z+$5z=7W?zH2>Eb32dvgHYZtCAf!=OLY-pb4>Ae79rd68E2LkVPj-|jFeyqtBCCwiW zkB@kO_(3wFq)7qwV}bA=zD!*@UhT`geq}ITo%@O(Z5Y80nEX~;0-8kO{oB6|(4fQh z);73T!>3@{ZobPwRv*W?7m0Ml9GmJBCJd&6E?hdj9lV= z4flNfsc(J*DyPv?RCOx!MSvk(M952PJ-G|JeVxWVjN~SNS6n-_Ge3Q;TGE;EQvZg86%wZ`MB zSMQua(i*R8a75!6$QRO^(o7sGoomb+Y{OMy;m~Oa`;P9Yqo>?bJAhqXxLr7_3g_n>f#UVtxG!^F#1+y@os6x(sg z^28bsQ@8rw%Gxk-stAEPRbv^}5sLe=VMbkc@Jjimqjvmd!3E7+QnL>|(^3!R} zD-l1l7*Amu@j+PWLGHXXaFG0Ct2Q=}5YNUxEQHCAU7gA$sSC<5OGylNnQUa>>l%sM zyu}z6i&({U@x^hln**o6r2s-(C-L50tQvz|zHTqW!ir?w&V23tuYEDJVV#5pE|OJu z7^R!A$iM$YCe?8n67l*J-okwfZ+ZTkGvZ)tVPfR;|3gyFjF)8V zyXXN=!*bpyRg9#~Bg1+UDYCt0 ztp4&?t1X0q>uz;ann$OrZs{5*r`(oNvw=$7O#rD|Wuv*wIi)4b zGtq4%BX+kkagv3F9Id6~-c+1&?zny%w5j&nk9SQfo0k4LhdSU_kWGW7axkfpgR`8* z!?UTG*Zi_baA1^0eda8S|@&F z{)Rad0kiLjB|=}XFJhD(S3ssKlveFFmkN{Vl^_nb!o5M!RC=m)V&v2%e?ZoRC@h3> zJ(?pvToFd`*Zc@HFPL#=otWKwtuuQ_dT-Hr{S%pQX<6dqVJ8;f(o)4~VM_kEQkMR+ zs1SCVi~k>M`u1u2xc}>#D!V&6nOOh-E$O&SzYrjJdZpaDv1!R-QGA141WjQe2s0J~ zQ;AXG)F+K#K8_5HVqRoRM%^EduqOnS(j2)|ctA6Q^=|s_WJYU;Z%5bHp08HPL`YF2 zR)Ad1z{zh`=sDs^&V}J z%$Z$!jd7BY5AkT?j`eqMs%!Gm@T8)4w3GYEX~IwgE~`d|@T{WYHkudy(47brgHXx& zBL1yFG6!!!VOSmDxBpefy2{L_u5yTwja&HA!mYA#wg#bc-m%~8aRR|~AvMnind@zs zy>wkShe5&*un^zvSOdlVu%kHsEo>@puMQ`b1}(|)l~E{5)f7gC=E$fP(FC2=F<^|A zxeIm?{EE!3sO!Gr7e{w)Dx(uU#3WrFZ>ibmKSQ1tY?*-Nh1TDHLe+k*;{Rp!Bmd_m zb#^kh`Y*8l|9Cz2e{;RL%_lg{#^Ar+NH|3z*Zye>!alpt{z;4dFAw^^H!6ING*EFc z_yqhr8d!;%nHX9AKhFQZBGrSzfzYCi%C!(Q5*~hX>)0N`vbhZ@N|i;_972WSx*>LH z87?en(;2_`{_JHF`Sv6Wlps;dCcj+8IJ8ca6`DsOQCMb3n# z3)_w%FuJ3>fjeOOtWyq)ag|PmgQbC-s}KRHG~enBcIwqIiGW8R8jFeBNY9|YswRY5 zjGUxdGgUD26wOpwM#8a!Nuqg68*dG@VM~SbOroL_On0N6QdT9?)NeB3@0FCC?Z|E0 z6TPZj(AsPtwCw>*{eDEE}Gby>0q{*lI+g2e&(YQrsY&uGM{O~}(oM@YWmb*F zA0^rr5~UD^qmNljq$F#ARXRZ1igP`MQx4aS6*MS;Ot(1L5jF2NJ;de!NujUYg$dr# z=TEL_zTj2@>ZZN(NYCeVX2==~=aT)R30gETO{G&GM4XN<+!&W&(WcDP%oL8PyIVUC zs5AvMgh6qr-2?^unB@mXK*Dbil^y-GTC+>&N5HkzXtozVf93m~xOUHn8`HpX=$_v2 z61H;Z1qK9o;>->tb8y%#4H)765W4E>TQ1o0PFj)uTOPEvv&}%(_mG0ISmyhnQV33Z$#&yd{ zc{>8V8XK$3u8}04CmAQ#I@XvtmB*s4t8va?-IY4@CN>;)mLb_4!&P3XSw4pA_NzDb zORn!blT-aHk1%Jpi>T~oGLuh{DB)JIGZ9KOsciWs2N7mM1JWM+lna4vkDL?Q)z_Ct z`!mi0jtr+4*L&N7jk&LodVO#6?_qRGVaucqVB8*us6i3BTa^^EI0x%EREQSXV@f!lak6Wf1cNZ8>*artIJ(ADO*=<-an`3zB4d*oO*8D1K!f z*A@P1bZCNtU=p!742MrAj%&5v%Xp_dSX@4YCw%F|%Dk=u|1BOmo)HsVz)nD5USa zR~??e61sO(;PR)iaxK{M%QM_rIua9C^4ppVS$qCT9j2%?*em?`4Z;4@>I(c%M&#cH z>4}*;ej<4cKkbCAjjDsyKS8rIm90O)Jjgyxj5^venBx&7B!xLmzxW3jhj7sR(^3Fz z84EY|p1NauwXUr;FfZjdaAfh%ivyp+^!jBjJuAaKa!yCq=?T_)R!>16?{~p)FQ3LDoMyG%hL#pR!f@P%*;#90rs_y z@9}@r1BmM-SJ#DeuqCQk=J?ixDSwL*wh|G#us;dd{H}3*-Y7Tv5m=bQJMcH+_S`zVtf;!0kt*(zwJ zs+kedTm!A}cMiM!qv(c$o5K%}Yd0|nOd0iLjus&;s0Acvoi-PFrWm?+q9f^FslxGi z6ywB`QpL$rJzWDg(4)C4+!2cLE}UPCTBLa*_=c#*$b2PWrRN46$y~yST3a2$7hEH= zNjux+wna^AzQ=KEa_5#9Ph=G1{S0#hh1L3hQ`@HrVnCx{!fw_a0N5xV(iPdKZ-HOM za)LdgK}1ww*C_>V7hbQnTzjURJL`S%`6nTHcgS+dB6b_;PY1FsrdE8(2K6FN>37!62j_cBlui{jO^$dPkGHV>pXvW0EiOA zqW`YaSUBWg_v^Y5tPJfWLcLpsA8T zG)!x>pKMpt!lv3&KV!-um= zKCir6`bEL_LCFx4Z5bAFXW$g3Cq`?Q%)3q0r852XI*Der*JNuKUZ`C{cCuu8R8nkt z%pnF>R$uY8L+D!V{s^9>IC+bmt<05h**>49R*#vpM*4i0qRB2uPbg8{{s#9yC;Z18 zD7|4m<9qneQ84uX|J&f-g8a|nFKFt34@Bt{CU`v(SYbbn95Q67*)_Esl_;v291s=9 z+#2F2apZU4Tq=x+?V}CjwD(P=U~d<=mfEFuyPB`Ey82V9G#Sk8H_Ob_RnP3s?)S_3 zr%}Pb?;lt_)Nf>@zX~D~TBr;-LS<1I##8z`;0ZCvI_QbXNh8Iv)$LS=*gHr;}dgb=w5$3k2la1keIm|=7<-JD>)U%=Avl0Vj@+&vxn zt-)`vJxJr88D&!}2^{GPXc^nmRf#}nb$4MMkBA21GzB`-Or`-3lq^O^svO7Vs~FdM zv`NvzyG+0T!P8l_&8gH|pzE{N(gv_tgDU7SWeiI-iHC#0Ai%Ixn4&nt{5y3(GQs)i z&uA;~_0shP$0Wh0VooIeyC|lak__#KVJfxa7*mYmZ22@(<^W}FdKjd*U1CqSjNKW% z*z$5$=t^+;Ui=MoDW~A7;)Mj%ibX1_p4gu>RC}Z_pl`U*{_z@+HN?AF{_W z?M_X@o%w8fgFIJ$fIzBeK=v#*`mtY$HC3tqw7q^GCT!P$I%=2N4FY7j9nG8aIm$c9 zeKTxVKN!UJ{#W)zxW|Q^K!3s;(*7Gbn;e@pQBCDS(I|Y0euK#dSQ_W^)sv5pa%<^o zyu}3d?Lx`)3-n5Sy9r#`I{+t6x%I%G(iewGbvor&I^{lhu-!#}*Q3^itvY(^UWXgvthH52zLy&T+B)Pw;5>4D6>74 zO_EBS)>l!zLTVkX@NDqyN2cXTwsUVao7$HcqV2%t$YzdAC&T)dwzExa3*kt9d(}al zA~M}=%2NVNUjZiO7c>04YH)sRelXJYpWSn^aC$|Ji|E13a^-v2MB!Nc*b+=KY7MCm zqIteKfNkONq}uM;PB?vvgQvfKLPMB8u5+Am=d#>g+o&Ysb>dX9EC8q?D$pJH!MTAqa=DS5$cb+;hEvjwVfF{4;M{5U&^_+r zvZdu_rildI!*|*A$TzJ&apQWV@p{!W`=?t(o0{?9y&vM)V)ycGSlI3`;ps(vf2PUq zX745#`cmT*ra7XECC0gKkpu2eyhFEUb?;4@X7weEnLjXj_F~?OzL1U1L0|s6M+kIhmi%`n5vvDALMagi4`wMc=JV{XiO+^ z?s9i7;GgrRW{Mx)d7rj)?(;|b-`iBNPqdwtt%32se@?w4<^KU&585_kZ=`Wy^oLu9 z?DQAh5z%q;UkP48jgMFHTf#mj?#z|=w= z(q6~17Vn}P)J3M?O)x))%a5+>TFW3No~TgP;f}K$#icBh;rSS+R|}l鯊%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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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/156] 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' pFad - Phonifier reborn

    Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

    Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


    Alternative Proxies:

    Alternative Proxy

    pFad Proxy

    pFad v3 Proxy

    pFad v4 Proxy