From aac3af9146cfddf2619839257a2b80b78b9ae2b7 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 22:04:24 +0100 Subject: [PATCH 001/431] Update and rename python-build.yml to publish-python.yml switch from token to trusted publishing authentication and from testpypi to the real thing --- .../{python-build.yml => publish-python.yml} | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) rename .github/workflows/{python-build.yml => publish-python.yml} (88%) diff --git a/.github/workflows/python-build.yml b/.github/workflows/publish-python.yml similarity index 88% rename from .github/workflows/python-build.yml rename to .github/workflows/publish-python.yml index 2c67d28a69..fd937b8ea2 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/publish-python.yml @@ -73,9 +73,14 @@ jobs: path: wrappers/python/dist/*.tar.gz upload-pypi: - name: Deploy + name: Upload to PyPI needs: [build-wheels, build-sdist] runs-on: ubuntu-latest + environment: + name: pypi + url: https://pypi.org/p/zxing-cpp + permissions: + id-token: write # IMPORTANT: this permission is mandatory for trusted publishing # only run if the commit is tagged... # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') if: github.event_name == 'release' || github.event.inputs.publish == 'y' @@ -86,7 +91,5 @@ jobs: path: dist - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.TEST_PYPI_TOKEN }} - repository_url: https://test.pypi.org/legacy/ +# with: +# repository-url: https://test.pypi.org/legacy/ From 7efe62516e81c782ccf8f3c8f71eace0ffe90da3 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 22:16:21 +0100 Subject: [PATCH 002/431] Update publish-python.yml make top-level name consistent with filename --- .github/workflows/publish-python.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-python.yml b/.github/workflows/publish-python.yml index fd937b8ea2..637e91e557 100644 --- a/.github/workflows/publish-python.yml +++ b/.github/workflows/publish-python.yml @@ -1,4 +1,4 @@ -name: build-python-dist +name: publish-python on: release: From cfa0ad019d60c81a9b0ff038386b9153677ed4f5 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 22:20:34 +0100 Subject: [PATCH 003/431] python/README remove build+deploy badge --- wrappers/python/README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/wrappers/python/README.md b/wrappers/python/README.md index 5366af17da..2d7df24bc4 100644 --- a/wrappers/python/README.md +++ b/wrappers/python/README.md @@ -1,6 +1,5 @@ # Python bindings for zxing-cpp -[![Build + Deploy](https://github.com/zxing-cpp/zxing-cpp/actions/workflows/python-build.yml/badge.svg)](https://github.com/zxing-cpp/zxing-cpp/actions/workflows/python-build.yml) [![PyPI](https://img.shields.io/pypi/v/zxing-cpp.svg)](https://pypi.org/project/zxing-cpp/) ## Installation From d940d91b021ea7cc083dca5584b9872b977df9f8 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 8 Dec 2023 22:26:10 +0100 Subject: [PATCH 004/431] publish-android.yml: fix typo in gradle publish command --- .github/workflows/publish-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index 004b41caf9..eb789c3d26 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -28,4 +28,4 @@ jobs: ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} - run: ./gradlew publishRleasePublicationsToSonatypeRepository + run: ./gradlew publishReleasePublicationToSonatypeRepository From d3a8d5f793ed0c32e53283ccf079f390780491a0 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 9 Dec 2023 12:26:23 +0100 Subject: [PATCH 005/431] DecodeHints: provide c++ ABI backward compatibility with 2.1.0 This might end up in a 2.2.1 release. --- core/CMakeLists.txt | 1 + core/src/DecodeHints.cpp | 30 ++++++++++++++++++++++++++++++ core/src/ReaderOptions.h | 2 ++ 3 files changed, 33 insertions(+) create mode 100644 core/src/DecodeHints.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 0fe3bb6abe..f18067e46e 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -105,6 +105,7 @@ if (BUILD_READERS) src/Content.h src/Content.cpp src/DecodeHints.h + src/DecodeHints.cpp src/DecoderResult.h src/DetectorResult.h src/Error.h diff --git a/core/src/DecodeHints.cpp b/core/src/DecodeHints.cpp new file mode 100644 index 0000000000..8dbd5a8685 --- /dev/null +++ b/core/src/DecodeHints.cpp @@ -0,0 +1,30 @@ +/* +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#define HIDE_DECODE_HINTS_ALIAS + +#include "ReadBarcode.h" + +namespace ZXing { + +// Provide a struct that is binary compatible with ReaderOptions and is actually called DecodeHints so that +// the compiler generates a correctly mangled pair of ReadBarcode(s) symbols to keep backward ABI compatibility. + +struct DecodeHints +{ + char data[sizeof(ReaderOptions)]; +}; + +Result ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) +{ + return ReadBarcode(image, reinterpret_cast(hints)); +} + +Results ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) +{ + return ReadBarcodes(image, reinterpret_cast(hints)); +} + +} // ZXing diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index e68d0592d7..3b4cabfa9e 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -172,6 +172,8 @@ class ReaderOptions bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); } }; +#ifndef HIDE_DECODE_HINTS_ALIAS using DecodeHints [[deprecated]] = ReaderOptions; +#endif } // ZXing From 99a83b3a6ac514d7f850dda7fa24cddb5120c7e2 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 11 Dec 2023 00:43:27 +0100 Subject: [PATCH 006/431] release: bump project version to 2.2.1 --- core/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index f18067e46e..e870fb9c66 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project (ZXing VERSION "2.2.0") +project (ZXing VERSION "2.2.1") set (ZXING_SONAME 3) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 include(../zxing.cmake) From befe74461d144b04e75d7ee14dcb666ca48e4ed9 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 11 Dec 2023 08:17:12 +0100 Subject: [PATCH 007/431] Qt: improve example code * fix moc warning * fix timeout logic in QML code * add TextMode, tryInvert and isPure properties --- example/ZXingQt5CamReader.qml | 13 +++++++++---- example/ZXingQt6CamReader.qml | 15 ++++++++++----- example/ZXingQtReader.cpp | 9 +++++---- example/ZXingQtReader.h | 22 +++++++++++++++++++++- 4 files changed, 45 insertions(+), 14 deletions(-) diff --git a/example/ZXingQt5CamReader.qml b/example/ZXingQt5CamReader.qml index 467bb18a63..4b3191f884 100644 --- a/example/ZXingQt5CamReader.qml +++ b/example/ZXingQt5CamReader.qml @@ -22,7 +22,7 @@ Window { Timer { id: resetInfo - interval: 2000 + interval: 1000 } BarcodeReader { @@ -31,7 +31,9 @@ Window { formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) tryRotate: tryRotateSwitch.checked tryHarder: tryHarderSwitch.checked + tryInvert: tryInvertSwitch.checked tryDownscale: tryDownscaleSwitch.checked + textMode: ZXing.HRI // callback with parameter 'result', called for every successfully processed frame // onFoundBarcode: {} @@ -42,11 +44,13 @@ Window { ? [result.position.topLeft, result.position.topRight, result.position.bottomRight, result.position.bottomLeft] : nullPoints - if (result.isValid) + if (result.isValid) { resetInfo.restart() + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.contentTypeName).arg(result.runTime) + } - if (result.isValid || !resetInfo.running) - info.text = qsTr("Format: \t %1 \nText: \t %2 \nError: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.status).arg(result.runTime) + if (!resetInfo.running) + info.text = "No barcode found" // console.log(result) } @@ -138,6 +142,7 @@ Window { Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } + Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index ecb348e410..47b35574fd 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -22,7 +22,7 @@ Window { Timer { id: resetInfo - interval: 2000 + interval: 1000 } BarcodeReader { @@ -32,7 +32,9 @@ Window { formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) tryRotate: tryRotateSwitch.checked tryHarder: tryHarderSwitch.checked + tryInvert: tryInvertSwitch.checked tryDownscale: tryDownscaleSwitch.checked + textMode: ZXing.TextMode.HRI // callback with parameter 'result', called for every successfully processed frame // onFoundBarcode: {} @@ -43,11 +45,13 @@ Window { ? [result.position.topLeft, result.position.topRight, result.position.bottomRight, result.position.bottomLeft] : nullPoints - if (result.isValid) + if (result.isValid) { resetInfo.restart() + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.contentTypeName).arg(result.runTime) + } - if (result.isValid || !resetInfo.running) - info.text = qsTr("Format: \t %1 \nText: \t %2 \nError: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.status).arg(result.runTime) + if (!resetInfo.running) + info.text = "No barcode found" // console.log(result) } @@ -132,7 +136,7 @@ Window { Shape { id: polygon anchors.fill: parent - visible: control.points.length === 4 + visible: points.length === 4 ShapePath { strokeWidth: 3 @@ -175,6 +179,7 @@ Window { Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } + Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } diff --git a/example/ZXingQtReader.cpp b/example/ZXingQtReader.cpp index fe04192160..8b2174f602 100644 --- a/example/ZXingQtReader.cpp +++ b/example/ZXingQtReader.cpp @@ -18,19 +18,20 @@ int main(int argc, char* argv[]) QString filePath = argv[1]; - QImage fileImage = QImage(filePath); + QImage image = QImage(filePath); - if (fileImage.isNull()) { + if (image.isNull()) { qDebug() << "Could not load the filename as an image:" << filePath; return 1; } auto options = ReaderOptions() - .setFormats(BarcodeFormat::Any) + .setFormats(BarcodeFormat::MatrixCodes) .setTryRotate(false) + .setTextMode(TextMode::HRI) .setMaxNumberOfSymbols(10); - auto results = ReadBarcodes(fileImage, options); + auto results = ReadBarcodes(image, options); for (auto& result : results) { qDebug() << "Text: " << result.text(); diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index 1c11c0f50c..0b26625f7a 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -59,9 +59,12 @@ enum class BarcodeFormat enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; +enum class TextMode { Plain, ECI, HRI, Hex, Escaped }; + #else using ZXing::BarcodeFormat; using ZXing::ContentType; +using ZXing::TextMode; #endif using ZXing::ReaderOptions; @@ -70,6 +73,7 @@ using ZXing::BarcodeFormats; Q_ENUM_NS(BarcodeFormat) Q_ENUM_NS(ContentType) +Q_ENUM_NS(TextMode) template QDebug operator<<(QDebug dbg, const T& v) @@ -102,6 +106,7 @@ class Result : private ZXing::Result Q_PROPERTY(QByteArray bytes READ bytes) Q_PROPERTY(bool isValid READ isValid) Q_PROPERTY(ContentType contentType READ contentType) + Q_PROPERTY(QString contentTypeName READ contentTypeName) Q_PROPERTY(Position position READ position) QString _text; @@ -124,6 +129,7 @@ class Result : private ZXing::Result BarcodeFormat format() const { return static_cast(ZXing::Result::format()); } ContentType contentType() const { return static_cast(ZXing::Result::contentType()); } QString formatName() const { return QString::fromStdString(ZXing::ToString(ZXing::Result::format())); } + QString contentTypeName() const { return QString::fromStdString(ZXing::ToString(ZXing::Result::contentType())); } const QString& text() const { return _text; } const QByteArray& bytes() const { return _bytes; } const Position& position() const { return _position; } @@ -347,9 +353,22 @@ class BarcodeReader : public QObject, private ReaderOptions } Q_SIGNAL void formatsChanged(); + Q_PROPERTY(TextMode textMode READ textMode WRITE setTextMode NOTIFY textModeChanged) + TextMode textMode() const noexcept { return static_cast(ReaderOptions::textMode()); } + Q_SLOT void setTextMode(TextMode newVal) + { + if (textMode() != newVal) { + ReaderOptions::setTextMode(static_cast(newVal)); + emit textModeChanged(); + } + } + Q_SIGNAL void textModeChanged(); + ZQ_PROPERTY(bool, tryRotate, setTryRotate) ZQ_PROPERTY(bool, tryHarder, setTryHarder) + ZQ_PROPERTY(bool, tryInvert, setTryInvert) ZQ_PROPERTY(bool, tryDownscale, setTryDownscale) + ZQ_PROPERTY(bool, isPure, setIsPure) public slots: ZXingQt::Result process(const QVideoFrame& image) @@ -389,7 +408,7 @@ public slots: _sink = sink; connect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::process); } - Q_PROPERTY(QVideoSink* videoSink WRITE setVideoSink) + Q_PROPERTY(QVideoSink* videoSink MEMBER _sink WRITE setVideoSink) #endif }; @@ -435,6 +454,7 @@ inline void registerQmlAndMetaTypes() { qRegisterMetaType("BarcodeFormat"); qRegisterMetaType("ContentType"); + qRegisterMetaType("TextMode"); // supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name // but then the qml side complains about "unregistered type" From 377d7bf6800ad08edb16bc44ac68987d5953ab7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoine=20M=C3=A9rino?= Date: Tue, 12 Dec 2023 13:18:48 +0100 Subject: [PATCH 008/431] Add support for DX Film Edge read (#684) * Add support for DX Film Edge read * DXFilmEdge: compress samples images --------- Co-authored-by: axxel --- README.md | 6 +- core/CMakeLists.txt | 2 + core/src/BarcodeFormat.cpp | 1 + core/src/BarcodeFormat.h | 7 +- core/src/oned/ODDXFilmEdgeReader.cpp | 380 ++++++++++++++++++ core/src/oned/ODDXFilmEdgeReader.h | 26 ++ core/src/oned/ODReader.cpp | 3 + test/blackbox/BlackboxTestRunner.cpp | 7 + test/samples/dxfilmedge-1/1.jpg | Bin 0 -> 25235 bytes test/samples/dxfilmedge-1/1.txt | 1 + test/samples/dxfilmedge-1/2.png | Bin 0 -> 16300 bytes test/samples/dxfilmedge-1/2.txt | 1 + test/samples/dxfilmedge-1/3.jpg | Bin 0 -> 1652 bytes test/samples/dxfilmedge-1/3.txt | 1 + .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 1 + .../src/main/java/zxingcpp/BarcodeReader.kt | 2 +- wrappers/c/zxing-c.h | 5 +- wrappers/ios/Sources/Wrapper/ZXIFormat.h | 2 +- .../ios/Sources/Wrapper/ZXIFormatHelper.mm | 4 + wrappers/python/zxing.cpp | 1 + wrappers/winrt/BarcodeReader.cpp | 2 + wrappers/winrt/BarcodeReader.h | 1 + 22 files changed, 443 insertions(+), 10 deletions(-) create mode 100644 core/src/oned/ODDXFilmEdgeReader.cpp create mode 100644 core/src/oned/ODDXFilmEdgeReader.h create mode 100644 test/samples/dxfilmedge-1/1.jpg create mode 100644 test/samples/dxfilmedge-1/1.txt create mode 100644 test/samples/dxfilmedge-1/2.png create mode 100644 test/samples/dxfilmedge-1/2.txt create mode 100644 test/samples/dxfilmedge-1/3.jpg create mode 100644 test/samples/dxfilmedge-1/3.txt diff --git a/README.md b/README.md index 1054cb2fae..826388d960 100644 --- a/README.md +++ b/README.md @@ -40,12 +40,12 @@ Thanks a lot for your contribution! | EAN-8 | Code 128 | rMQR Code | | EAN-13 | Codabar | Aztec | | DataBar | DataBar Expanded | DataMatrix | -| | ITF | PDF417 | -| | | MaxiCode (partial) | +| | DX Film Edge | PDF417 | +| | ITF | MaxiCode (partial) | [Note:] * DataBar used to be called RSS. - * DataBar, MaxiCode, Micro QR Code and rMQR Code are not supported for writing. + * DataBar, DX Film Edge, MaxiCode, Micro QR Code and rMQR Code are not supported for writing. * Building with C++20 (see [CMakeLists.txt](https://github.com/zxing-cpp/zxing-cpp/blob/d4b0f502775857f257d13efd25fb840ece1bca3e/CMakeLists.txt#L45)) changes the behaviour of the library: it then supports multi-symbol and position independent detection for DataMatrix. This comes at a noticable performace cost. C++20 is enabled by default for the Android, iOS, Python and WASM wrappers. ## Getting Started diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index e870fb9c66..4e2135e7d2 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -289,6 +289,8 @@ if (BUILD_READERS) src/oned/ODDataBarExpandedBitDecoder.cpp src/oned/ODDataBarExpandedReader.h src/oned/ODDataBarExpandedReader.cpp + src/oned/ODDXFilmEdgeReader.h + src/oned/ODDXFilmEdgeReader.cpp src/oned/ODITFReader.h src/oned/ODITFReader.cpp src/oned/ODMultiUPCEANReader.h diff --git a/core/src/BarcodeFormat.cpp b/core/src/BarcodeFormat.cpp index 01f4d3860e..b43917764e 100644 --- a/core/src/BarcodeFormat.cpp +++ b/core/src/BarcodeFormat.cpp @@ -32,6 +32,7 @@ static BarcodeFormatName NAMES[] = { {BarcodeFormat::DataBar, "DataBar"}, {BarcodeFormat::DataBarExpanded, "DataBarExpanded"}, {BarcodeFormat::DataMatrix, "DataMatrix"}, + {BarcodeFormat::DXFilmEdge, "DXFilmEdge"}, {BarcodeFormat::EAN8, "EAN-8"}, {BarcodeFormat::EAN13, "EAN-13"}, {BarcodeFormat::ITF, "ITF"}, diff --git a/core/src/BarcodeFormat.h b/core/src/BarcodeFormat.h index 72542ed363..f2d56edad7 100644 --- a/core/src/BarcodeFormat.h +++ b/core/src/BarcodeFormat.h @@ -1,4 +1,4 @@ -/* +/* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors */ @@ -40,12 +40,13 @@ enum class BarcodeFormat UPCE = (1 << 15), ///< UPC-E MicroQRCode = (1 << 16), ///< Micro QR Code RMQRCode = (1 << 17), ///< Rectangular Micro QR Code + DXFilmEdge = (1 << 18), ///< DX Film Edge Barcode - LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | UPCA | UPCE, + LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DXFilmEdge | UPCA | UPCE, MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, Any = LinearCodes | MatrixCodes, - _max = RMQRCode, ///> implementation detail, don't use + _max = DXFilmEdge, ///> implementation detail, don't use }; ZX_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) diff --git a/core/src/oned/ODDXFilmEdgeReader.cpp b/core/src/oned/ODDXFilmEdgeReader.cpp new file mode 100644 index 0000000000..f7647bd05d --- /dev/null +++ b/core/src/oned/ODDXFilmEdgeReader.cpp @@ -0,0 +1,380 @@ +/* + * Copyright 2023 Antoine Mérino + */ +// SPDX-License-Identifier: Apache-2.0 + +#include "DecodeHints.h" +#include "GTIN.h" +#include "ODDXFilmEdgeReader.h" +#include "Result.h" +#include "ZXAlgorithms.h" + +#include +#include +namespace ZXing::OneD { + +// Detection is made from center to bottom. +// We ensure the clock signal is decoded before the data signal to avoid false positives. +// They are two version of a DX Edge codes : without half-frame information and with half-frame information. +// The clock signal is longer if the DX code contains the half-frame information (more recent version) +constexpr int CLOCK_PATTERN_LENGTH_HF = 31; +constexpr int CLOCK_PATTERN_LENGTH_NO_HF = 23; +constexpr int DATA_START_PATTERN_SIZE = 5; +constexpr auto CLOCK_PATTERN_COMMON = FixedPattern<15, 20> {5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1}; +constexpr auto CLOCK_PATTERN_HF = + FixedPattern<25, CLOCK_PATTERN_LENGTH_HF>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}; +constexpr auto CLOCK_PATTERN_NO_HF = FixedPattern<17, CLOCK_PATTERN_LENGTH_NO_HF>{5, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 3}; +constexpr auto DATA_START_PATTERN_ = FixedPattern<5, 5> {1, 1, 1, 1, 1}; +constexpr auto DATA_STOP_PATTERN_ = FixedPattern<3, 3> {1, 1, 1}; + +// Signal data length, without the start and stop patterns +constexpr int DATA_LENGTH_HF = 23; +constexpr int DATA_LENGTH_NO_HF = 15; + + +/** + * @brief Parse a part of a vector of bits (boolean) to a decimal number. + * Eg: {1, 1, 0} -> 6. + * @param begin begin of the vector's part to be parsed + * @param end end of the vector's part to be parsed + * @return The decimal value of the parsed part + */ +int toDecimal(const std::vector::iterator begin, const std::vector::iterator end) +{ + int retval = 0; + auto i = std::distance(begin, end) - 1; + for (std::vector::iterator it = begin; it != end; it++, i--) { + retval += (*it ? (1 << i) : 0); + } + return retval; +} + +// DX Film Edge Clock signal found on 35mm films. +struct Clock { + int rowNumber = 0; + bool containsHFNumber = false; // Clock signal (thus data signal) with half-frame number (longer version) + int xStart = 0; // Beginning of the clock signal on the X-axis, in pixels + int xStop = 0; // End of the clock signal on the X-axis, in pixels + int pixelTolerance = 0; // Pixel tolerance will be set depending of the length of the clock signal (in pixels) + + bool operator<(const int x) const + { + return xStart < x; + } + + bool operator < (const Clock& other) const + { + return xStart < other.xStart; + } + + /* + * @brief Check if this clocks start at about the same x position as another. + * We assume two clock are the same when they start in about the same X position, + * even if they are different clocks (stop at different position or different type). + * Only the more recent clock is kept. + */ + bool xStartInRange(const Clock& other) const + { + auto tolerance = std::max(pixelTolerance, other.pixelTolerance); + return (xStart - tolerance) <= other.xStart && (xStart + tolerance) >= other.xStart; + } + + bool xStartInRange(const int x) const + { + return (xStart - pixelTolerance) <= x && (xStart + pixelTolerance) >= x; + } + + bool xStopInRange(const int x) const + { + return (xStop - pixelTolerance) <= x && (xStop + pixelTolerance) >= x; + } + + /* + * @brief Check the clock's row number is next to the row we want to compare. + * Since we update the clock row number with the latest found signal's row number, + * the signal may be either: + * - below the clock, but not too far + * - slightly above the clock + * @param otherRownumber the other row to check if it's in range or not + * @param totalRows the image total row number (~image height) + */ + bool rowInRange(const int otherRowNumber, const int totalRows) const + { + const auto acceptedRowRange = totalRows / 5 + 1; // Below the clock, not too far + const auto rowMarginTolerance = totalRows / 20 + 1; // If above the clock, it should be really close + auto result = ((otherRowNumber >= rowNumber && otherRowNumber - rowNumber <= acceptedRowRange) + || (rowNumber <= otherRowNumber && otherRowNumber - rowNumber <= rowMarginTolerance)); + return result; + } + +}; + +class ClockSet : public std::set> { +public: + + /* + * @brief Return the clock which starts at the closest X position. + */ + const ClockSet::iterator closestElement(const int x) + { + const auto it = lower_bound(x); + if (it == begin()) + return it; + + const auto prev_it = std::prev(it); + return (it == end() || x - prev_it->xStart <= it->xStart - x) ? prev_it : it; + } + + /** + * @brief Add a new clock to the set. + * If the new clock is close enough to an existing clock in the set, + * the old clock is removed. + */ + void update(const Clock& newClock) + { + auto closestClock = closestElement(newClock.xStart); + if (closestClock != end() && newClock.xStartInRange(*closestClock)) { + erase(closestClock); + insert(newClock); + } else { + insert(newClock); + } + } +}; + + +/* +* @brief To avoid many false positives, +* the clock signal must be found to attempt to decode a data signal. +* We ensure the data signal starts below a clock. +* We accept a tolerance margin, +* ie. the signal may start a few pixels before or after the clock on the X-axis. +*/ +struct DXFEState : public RowReader::DecodingState { + ClockSet allClocks; + int totalRows = 0; +}; + +/** + * @brief Try to find a DX Film Edge clock in the given row. + * @param rowNumber the row number + * @param end end of the vector's part to be parsed + * @return The decimal value of the parsed part + */ +std::optional findClock(int rowNumber, PatternView& view) +{ + // Minimum "allowed "white" zone to the left and the right sides of the clock signal. + constexpr float minClockNoHFQuietZone = 2; + // On HF versions, the decimal number uses to be really close to the clock + constexpr float minClockHFQuietZone = 0.5; + + // Adjust the pixel shift tolerance between the data signal and the clock signal. + // 1 means the signal can be shifted up to one bar to the left or the right. + constexpr float pixelToleranceRatio = 0.5; + + // Before detecting any clock, + // try to detect the common pattern between all types of clocks. + // This avoid doing two detections at each interations instead of one, + // when they is no DX Edge code to detect. + auto commonClockPattern = + FindLeftGuard(view, CLOCK_PATTERN_COMMON.size(), CLOCK_PATTERN_COMMON, std::min(minClockNoHFQuietZone, minClockHFQuietZone)); + if (commonClockPattern.isValid()) { + bool foundClock = false; + bool containsHFNumber = false; + auto clock_pattern = FindLeftGuard(view, CLOCK_PATTERN_HF.size(), CLOCK_PATTERN_HF, minClockHFQuietZone); + if (clock_pattern.isValid()) { + foundClock = true; + containsHFNumber = true; + } else { + clock_pattern = FindLeftGuard(view, CLOCK_PATTERN_NO_HF.size(), CLOCK_PATTERN_NO_HF, minClockNoHFQuietZone); + if (clock_pattern.isValid()) + foundClock = true; + } + if (foundClock) { + Clock clock; + clock.rowNumber = rowNumber; + clock.containsHFNumber = containsHFNumber; + clock.xStart = clock_pattern.pixelsInFront(); + clock.xStop = clock_pattern.pixelsTillEnd(); + clock.pixelTolerance = (clock_pattern.pixelsTillEnd() - clock_pattern.pixelsInFront()) + / (containsHFNumber ? CLOCK_PATTERN_LENGTH_HF : CLOCK_PATTERN_LENGTH_NO_HF) * pixelToleranceRatio; + return clock; + } + } + return std::nullopt; +} + + +Result DXFilmEdgeReader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr& state) const +{ + + // Retrieve the decoding state to check if a clock signal has already been found before. + // We check also if it contains the half-frame number, since it affects the signal structure. + if (!state) { + state.reset(new DXFEState); + // We need the total row number to adjust clock & signal detection sensitivity + static_cast(state.get())->totalRows = 2 * rowNumber; + } + auto& allClocks = static_cast(state.get())->allClocks; + auto& totalRows = static_cast(state.get())->totalRows; + // Minimum "allowed "white" zone to the left and the right sides of the data signal. + // We allow a smaller quiet zone, ie improve detection at risk of getting false positives, + // because the risk is greatly reduced when we check we found the clock before the signal. + constexpr float minDataQuietZone = 0.2; + + // We should find at least one clock before attempting to decode the data signal. + auto clock = findClock(rowNumber, next); + + if (clock) + allClocks.update(clock.value()); + + if (allClocks.empty()) + return {}; + + // Now that we found at least a clock, attempt to decode the data signal. + // Start by finding the data start pattern. + next = FindLeftGuard(next, DATA_START_PATTERN_.size(), DATA_START_PATTERN_, minDataQuietZone); + if (!next.isValid()) + return {}; + + auto xStart = next.pixelsInFront(); + + // The found data signal must be below the clock signal, otherwise we abort the decoding (potential false positive) + auto closestClock = allClocks.closestElement(xStart); + if (!closestClock->xStartInRange(xStart)) + return {}; + + // Avoid decoding a signal found at the top or too far from the clock + // (might happen when stacking two films one of top of the other, or other false positive situations) + if (!closestClock->rowInRange(rowNumber, totalRows)) + return {}; + + // Compute the length of a bar + // It may be greater than 1 depending on what have been found in the raw signal + auto perBarRawWidth = *next.data(); + + // Skip the data start pattern (black, white, black, white, black) + // The first signal bar is always white: this is the + // separation between the start pattern and the product number) + next.shift(DATA_START_PATTERN_SIZE); + + if (!next.isValid()) + return {}; + + std::vector dataSignalBits; + + // They are two possible data signal lengths (with or without half-frame information) + dataSignalBits.reserve(closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF); + + // Populate a vector of booleans to represent the bits. true = black, false = white. + // We start the parsing just after the data start signal. + // The first bit is always a white bar (we include the separator just after the start pattern) + // Eg: {3, 1, 2} -> {0, 0, 0, 1, 0, 0} + int signalLength = 0; + bool currentBarIsBlack = false; // the current bar is white + while (signalLength < (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) { + if (!next.isValid()) + return {}; + + // Zero means we can't conclude on black or white bar. Abort the decoding. + if (*next.data() == 0) + return {}; + + // Adjust the current bar according to the computed ratio above. + // When the raw result is not exact (between two bars), + // we round the bar size to the nearest integer. + auto currentBarWidth = + *next.data() / perBarRawWidth + (*next.data() % perBarRawWidth >= (perBarRawWidth / 2) ? 1 : 0); + + signalLength += currentBarWidth; + + // Extend the bit array according to the current bar length. + // Eg: one white bars -> {0}, three black bars -> {1, 1, 1} + while (currentBarWidth > 0 + && (int)dataSignalBits.size() < (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) { + dataSignalBits.push_back(currentBarIsBlack); + --currentBarWidth; + } + + // Iterate to the next data signal bar (the color is inverted) + currentBarIsBlack = !currentBarIsBlack; + next.shift(1); + } + + // Check the signal length + if (signalLength != (closestClock->containsHFNumber ? DATA_LENGTH_HF : DATA_LENGTH_NO_HF)) + return {}; + + // Check there is the Stop pattern at the end of the data signal + next = next.subView(0, 3); + if (!IsRightGuard(next, DATA_STOP_PATTERN_, minDataQuietZone)) + return {}; + + // Check the data signal has been fully parsed + if (closestClock->containsHFNumber && dataSignalBits.size() < DATA_LENGTH_HF) + return {}; + if (!closestClock->containsHFNumber && dataSignalBits.size() < DATA_LENGTH_NO_HF) + return {}; + + // The following bits are always white (=false), they are separators. + if (dataSignalBits.at(0) || dataSignalBits.at(8)) + return {}; + if (closestClock->containsHFNumber && (dataSignalBits.at(20) || dataSignalBits.at(22))) + return {}; + if (!closestClock->containsHFNumber && (dataSignalBits.at(8) || dataSignalBits.at(14))) + return {}; + + // Check the parity bit + auto signalSum = std::accumulate(dataSignalBits.begin(), dataSignalBits.end()-2, 0); + auto parityBit = *(dataSignalBits.end() - 2); + if (signalSum % 2 != (int)parityBit) + return {}; + + // Compute the DX 1 number (product number) + auto productNumber = toDecimal(dataSignalBits.begin() + 1, dataSignalBits.begin() + 8); + if (!productNumber) + return {}; + + // Compute the DX 2 number (generation number) + auto generationNumber = toDecimal(dataSignalBits.begin() + 9, dataSignalBits.begin() + 13); + + // Generate the textual representation. + // Eg: 115-10/11A means: DX1 = 115, DX2 = 10, Frame number = 11A + std::string txt; + txt.reserve(10); + txt = std::to_string(productNumber) + "-" + std::to_string(generationNumber); + if (closestClock->containsHFNumber) { + auto halfFrameNumber = toDecimal(dataSignalBits.begin() + 13, dataSignalBits.begin() + 20); + txt += "/" + std::to_string(halfFrameNumber / 2); + if (halfFrameNumber % 2) + txt += "A"; + } + + Error error; + + // TODO is it required? + // AFAIK The DX Edge barcode doesn't follow any symbology identifier. + SymbologyIdentifier symbologyIdentifier = {'I', '0'}; // No check character validation ? + + auto xStop = next.pixelsTillEnd(); + + // The found data signal must be below the clock signal, otherwise we abort the decoding (potential false positive) + if (!closestClock->xStopInRange(xStop)) + return {}; + + + // Update the clock X coordinates with the latest corresponding data signal + // This may improve signal detection for next row iterations + if (closestClock->xStop != xStop || closestClock->xStart != xStart) { + Clock clock(*closestClock); + clock.xStart = xStart; + clock.xStop = xStop; + clock.rowNumber = rowNumber; + allClocks.erase(closestClock); + allClocks.insert(clock); + } + closestClock = allClocks.closestElement(xStart); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::DXFilmEdge, symbologyIdentifier, error); +} + +} // namespace ZXing::OneD diff --git a/core/src/oned/ODDXFilmEdgeReader.h b/core/src/oned/ODDXFilmEdgeReader.h new file mode 100644 index 0000000000..357e5e4517 --- /dev/null +++ b/core/src/oned/ODDXFilmEdgeReader.h @@ -0,0 +1,26 @@ +/* + * Copyright 2023 Antoine Mérino + */ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include "ODRowReader.h" + +namespace ZXing::OneD { + +/** + *

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

+ * + *

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

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

zxing-cpp/wasm live demo

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

zxing-cpp/wasm reader demo

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

* -*

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

+*

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

* *

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

From a67b89bf64465c9db8105ed4d861d328ff7c3196 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 11:03:02 +0100 Subject: [PATCH 067/431] rust: fix typos in README --- wrappers/rust/README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index 131f81685b..c97ee2d65e 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -12,8 +12,8 @@ In your Cargo.toml: ```toml [dependencies] -# `bundled` causes cargo to compile and statically link in an up to -# date version of the c++ core library. This is the most convient +# `bundled` causes cargo to compile and statically link an up to +# date version of the c++ core library. This is the most convenient # and safe way to build the library. zxing-cpp = { version = "0.2.1", features = ["bundled", "image"] } ``` @@ -58,5 +58,5 @@ They are: ## Benchmarking To compare the performance of this Rust wrapper project with other availble barcode scanner Rust libraries, -I started the project [zxing-bench](https://github.com/axxel/zxing-bench). The README contains some a few +I started the project [zxing-bench](https://github.com/axxel/zxing-bench). The README contains a few results to get an idea. From 279824455afa18d151429568e6c2cb364e1de267 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 11:10:32 +0100 Subject: [PATCH 068/431] c-API: pass parameter to zxing_PositionToString as value instead of pointer --- core/src/zxing-c.cpp | 4 ++-- core/src/zxing-c.h | 2 +- wrappers/c/zxing-c-test.c | 1 + wrappers/rust/src/bindings.rs | 2 +- wrappers/rust/src/lib.rs | 2 +- 5 files changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index b9340f4fbb..3756928d33 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -179,9 +179,9 @@ char* zxing_ContentTypeToString(zxing_ContentType type) return copy(ToString(static_cast(type))); } -char* zxing_PositionToString(const zxing_Position* position) +char* zxing_PositionToString(zxing_Position position) { - return copy(ToString(transmute_cast(*position))); + return copy(ToString(transmute_cast(position))); } diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index 0ec485bc10..c7b95a53c5 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -181,7 +181,7 @@ typedef struct { zxing_PointI topLeft, topRight, bottomRight, bottomLeft; } zxing_Position; -char* zxing_PositionToString(const zxing_Position* position); +char* zxing_PositionToString(zxing_Position position); bool zxing_Result_isValid(const zxing_Result* result); char* zxing_Result_errorMsg(const zxing_Result* result); diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index acd418f310..2b62511c18 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -82,6 +82,7 @@ int main(int argc, char** argv) printF("Identifier : %s\n", zxing_Result_symbologyIdentifier(result)); printF("EC Level : %s\n", zxing_Result_ecLevel(result)); printF("Error : %s\n", zxing_Result_errorMsg(result)); + printF("Position : %s\n", zxing_PositionToString(zxing_Result_position(result))); printf("Rotation : %d\n", zxing_Result_orientation(result)); if (i < n-1) diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index f118ac03f5..2fad1b4168 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -139,7 +139,7 @@ extern "C" { pub fn zxing_ReaderOptions_getMinLineCount(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; - pub fn zxing_PositionToString(position: *const zxing_Position) -> *mut ::core::ffi::c_char; + pub fn zxing_PositionToString(position: zxing_Position) -> *mut ::core::ffi::c_char; pub fn zxing_Result_isValid(result: *const zxing_Result) -> bool; pub fn zxing_Result_errorMsg(result: *const zxing_Result) -> *mut ::core::ffi::c_char; pub fn zxing_Result_format(result: *const zxing_Result) -> zxing_BarcodeFormat; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 6a43a06c9c..6904d1f0d4 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -295,7 +295,7 @@ impl Display for PointI { impl Display for Position { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", unsafe { - c2r_str(zxing_PositionToString(self as *const Position as *const zxing_Position)) + c2r_str(zxing_PositionToString(*(self as *const Position as *const zxing_Position))) }) } } From ef0a421406a1e791b31d7ea2ee1b065db11f06c7 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 11:14:06 +0100 Subject: [PATCH 069/431] c-API: clang-format fixes --- core/src/zxing-c.h | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index c7b95a53c5..b214738827 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -36,8 +36,7 @@ typedef struct zxing_Results zxing_Results; * ZXing/ImageView.h */ -typedef enum -{ +typedef enum { zxing_ImageFormat_None = 0, zxing_ImageFormat_Lum = 0x01000000, zxing_ImageFormat_RGB = 0x03000102, @@ -52,7 +51,7 @@ zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, int pixStride); void zxing_ImageView_delete(zxing_ImageView* iv); -void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); +void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); void zxing_ImageView_rotate(zxing_ImageView* iv, int degree); /* @@ -173,11 +172,13 @@ typedef enum char* zxing_ContentTypeToString(zxing_ContentType type); -typedef struct { +typedef struct zxing_PointI +{ int x, y; } zxing_PointI; -typedef struct { +typedef struct zxing_Position +{ zxing_PointI topLeft, topRight, bottomRight, bottomLeft; } zxing_Position; From efc06387dac9b148dce7a41c0fc5d6eda74332ec Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 11:15:33 +0100 Subject: [PATCH 070/431] android: deprecate `validateITFCheckSum` reader option --- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 1 - .../android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt | 1 + 2 files changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 0e25ef82e2..1d87d817cb 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -312,7 +312,6 @@ static ReaderOptions CreateReaderOptions(JNIEnv* env, jobject opts) .setMaxNumberOfSymbols(GetIntField(env, cls, opts, "maxNumberOfSymbols")) .setTryCode39ExtendedMode(GetBooleanField(env, cls, opts, "tryCode39ExtendedMode")) .setValidateCode39CheckSum(GetBooleanField(env, cls, opts, "validateCode39CheckSum")) - .setValidateITFCheckSum(GetBooleanField(env, cls, opts, "validateITFCheckSum")) .setReturnCodabarStartEnd(GetBooleanField(env, cls, opts, "returnCodabarStartEnd")) .setReturnErrors(GetBooleanField(env, cls, opts, "returnErrors")) .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, opts, "eanAddOnSymbol", "EanAddOnSymbol"))) diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index b4f7542290..d93f89ece6 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -77,6 +77,7 @@ public class BarcodeReader(public var options: Options = Options()) { var maxNumberOfSymbols: Int = 0xff, var tryCode39ExtendedMode: Boolean = false, var validateCode39CheckSum: Boolean = false, + @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateITFCheckSum: Boolean = false, var returnCodabarStartEnd: Boolean = false, var returnErrors: Boolean = false, From b3fe5744b2a0ac554efa70635a087c5ff3342c42 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 26 Jan 2024 16:27:20 +0100 Subject: [PATCH 071/431] Code39: deprecate `tryCode39ExtendedMode` and `validateCode39CheckSum` The first one is now a true 'try' and enabled by default. The second is always performed and the result reported via the symbologyIdentifier. This detects more samples correctly out of the box. See also https://github.com/zxing-cpp/zxing-cpp/discussions/704 --- core/src/ReaderOptions.h | 10 ++--- core/src/oned/ODCode39Reader.cpp | 39 ++++++++++--------- core/src/oned/ODCode93Reader.cpp | 6 +-- test/blackbox/BlackboxTestRunner.cpp | 2 +- test/unit/oned/ODCode39ExtendedModeTest.cpp | 4 +- test/unit/oned/ODCode39ReaderTest.cpp | 24 +++--------- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 2 - .../src/main/java/zxingcpp/BarcodeReader.kt | 2 + 8 files changed, 40 insertions(+), 49 deletions(-) diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index 73c4d2a9b9..b8c397c622 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -141,13 +141,13 @@ class ReaderOptions /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) - /// If true, the Code-39 reader will try to read extended mode. - ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) + /// Deprecated / does nothing. The Code39 was decoded from full ASCII iff symbologyIdentifier()[2] >= '4' + ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode, [[deprecated]]) - /// Assume Code-39 codes employ a check digit and validate it. - ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum) + /// Deprecated / does nothing. The Code39 symbol has a valid checksum iff symbologyIdentifier()[2] is an odd digit + ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum, [[deprecated]]) - /// This flag is deprecated and does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. + /// Deprecated / does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, [[deprecated]]) /// If true, return the start and end chars in a Codabar barcode instead of stripping them. diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index ee3dc9da32..99b4ecc4d1 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -48,10 +48,9 @@ using CounterContainer = std::array; // each character has 5 bars and 4 spaces constexpr int CHAR_LEN = 9; -/** Decode the extended string in place. Return false if FormatError occurred. +/** Decode the full ASCII string. Return empty string if FormatError occurred. * ctrl is either "$%/+" for code39 or "abcd" for code93. */ -bool -DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]) +std::string DecodeCode39AndCode93FullASCII(std::string encoded, const char ctrl[4]) { auto out = encoded.begin(); for (auto in = encoded.cbegin(); in != encoded.cend(); ++in) { @@ -59,7 +58,7 @@ DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]) if (Contains(ctrl, c)) { char next = *++in; // if in is one short of cend(), then next == 0 if (next < 'A' || next > 'Z') - return false; + return {}; if (c == ctrl[0]) c = next - 64; // $A to $Z map to control codes SH to SB else if (c == ctrl[1]) @@ -72,7 +71,7 @@ DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]) *out++ = c; } encoded.erase(out, encoded.end()); - return true; + return encoded; } Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const @@ -115,24 +114,28 @@ Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique if (Size(txt) < minCharCount - 2 || !next.hasQuietZoneAfter(QUIET_ZONE_SCALE)) return {}; - Error error; - if (_opts.validateCode39CheckSum()) { - auto checkDigit = txt.back(); - txt.pop_back(); - int checksum = TransformReduce(txt, 0, [](char c) { return IndexOf(ALPHABET, c); }); - if (checkDigit != ALPHABET[checksum % 43]) - error = ChecksumError(); - } + auto lastChar = txt.back(); + txt.pop_back(); + int checksum = TransformReduce(txt, 0, [](char c) { return IndexOf(ALPHABET, c); }); + bool hasValidCheckSum = lastChar == ALPHABET[checksum % 43]; + if (!hasValidCheckSum) + txt.push_back(lastChar); + + const char shiftChars[] = "$%/+"; + auto fullASCII = DecodeCode39AndCode93FullASCII(txt, shiftChars); + bool hasFullASCII = !fullASCII.empty() && std::find_first_of(txt.begin(), txt.end(), shiftChars, shiftChars + 4) != txt.end(); + if (hasFullASCII) + txt = fullASCII; - if (!error && _opts.tryCode39ExtendedMode() && !DecodeExtendedCode39AndCode93(txt, "$%/+")) - error = FormatError("Decoding extended Code39/Code93 failed"); + if (hasValidCheckSum) + txt.push_back(lastChar); // Symbology identifier modifiers ISO/IEC 16388:2007 Annex C Table C.1 - constexpr const char symbologyModifiers[4] = { '0', '3' /*checksum*/, '4' /*extended*/, '7' /*checksum,extended*/ }; - SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)_opts.tryCode39ExtendedMode() * 2 + (int)_opts.validateCode39CheckSum()]}; + constexpr const char symbologyModifiers[4] = { '0', '1' /*checksum*/, '4' /*full ASCII*/, '5' /*checksum + full ASCII*/ }; + SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)hasValidCheckSum + 2 * (int)hasFullASCII]}; int xStop = next.pixelsTillEnd(); - return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier, error); + return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODCode93Reader.cpp b/core/src/oned/ODCode93Reader.cpp index cfeaa0db3f..1a2bcc60e4 100644 --- a/core/src/oned/ODCode93Reader.cpp +++ b/core/src/oned/ODCode93Reader.cpp @@ -62,7 +62,7 @@ CheckChecksums(const std::string& result) } // forward declare here. see ODCode39Reader.cpp. Not put in header to not pollute the public facing API -bool DecodeExtendedCode39AndCode93(std::string& encoded, const char ctrl[4]); +std::string DecodeCode39AndCode93FullASCII(std::string encoded, const char ctrl[4]); constexpr int CHAR_LEN = 6; constexpr int CHAR_SUM = 9; @@ -121,8 +121,8 @@ Result Code93Reader::decodePattern(int rowNumber, PatternView& next, std::unique // Remove checksum digits txt.resize(txt.size() - 2); - if (!error && !DecodeExtendedCode39AndCode93(txt, "abcd")) - error = FormatError("Decoding extended Code39/Code93 failed"); + if (!error && (txt = DecodeCode39AndCode93FullASCII(txt, "abcd")).empty()) + error = FormatError("ASCII decoding of Code93 failed"); // Symbology identifier ISO/IEC 15424:2008 4.4.10 no modifiers SymbologyIdentifier symbologyIdentifier = {'G', '0'}; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 6849319a84..27385e34da 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -402,7 +402,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("code39-2", "Code39", 2, { { 2, 2, 0 }, { 2, 2, 180 }, - }, ReaderOptions().setTryCode39ExtendedMode(true)); + }); runTests("code39-3", "Code39", 12, { { 12, 12, 0 }, diff --git a/test/unit/oned/ODCode39ExtendedModeTest.cpp b/test/unit/oned/ODCode39ExtendedModeTest.cpp index 589880a617..e7a3dfbb90 100644 --- a/test/unit/oned/ODCode39ExtendedModeTest.cpp +++ b/test/unit/oned/ODCode39ExtendedModeTest.cpp @@ -17,13 +17,13 @@ using namespace ZXing::OneD; static std::string Decode(std::string_view encoded) { - auto opts = ReaderOptions().setTryCode39ExtendedMode(true); + auto opts = ReaderOptions(); BitArray row = Utility::ParseBitArray(encoded, '1'); Result result = DecodeSingleRow(Code39Reader(opts), row.range()); return result.text(TextMode::Plain); } -TEST(ODCode39ExtendedModeTest, Decode) +TEST(ODCode39FullASCIITest, Decode) { EXPECT_EQ(Decode( "00000100101101101010100100100101100101010110100100100101011010100101101001001001" diff --git a/test/unit/oned/ODCode39ReaderTest.cpp b/test/unit/oned/ODCode39ReaderTest.cpp index a8684a7682..f602a8fc23 100644 --- a/test/unit/oned/ODCode39ReaderTest.cpp +++ b/test/unit/oned/ODCode39ReaderTest.cpp @@ -38,34 +38,22 @@ TEST(ODCode39ReaderTest, SymbologyIdentifier) { // "A" with checksum PatternRow row({ 2, 1, 1, 1, 1, 2, 1, 1, 2, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2 }); - auto result = parse(row, ReaderOptions().setValidateCode39CheckSum(true)); - EXPECT_EQ(result.symbologyIdentifier(), "]A3"); - EXPECT_EQ(result.text(), "A"); - - result = parse(row); - EXPECT_EQ(result.symbologyIdentifier(), "]A0"); + auto result = parse(row); + EXPECT_EQ(result.symbologyIdentifier(), "]A1"); EXPECT_EQ(result.text(), "AA"); } { // Extended "a" PatternRow row({ 1, 2, 1, 1, 1, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2 }); - auto result = parse(row, ReaderOptions().setTryCode39ExtendedMode(true)); + auto result = parse(row); EXPECT_EQ(result.symbologyIdentifier(), "]A4"); EXPECT_EQ(result.text(), "a"); - - result = parse(row); - EXPECT_EQ(result.symbologyIdentifier(), "]A0"); - EXPECT_EQ(result.text(), "+A"); } { // Extended "a" with checksum PatternRow row({ 1, 2, 1, 1, 1, 2, 1, 2, 1, 0, 2, 1, 1, 1, 1, 2, 1, 1, 2, 0, 2, 1, 1, 2, 1, 1, 2, 1, 1 }); - auto result = parse(row, ReaderOptions().setTryCode39ExtendedMode(true).setValidateCode39CheckSum(true)); - EXPECT_EQ(result.symbologyIdentifier(), "]A7"); - EXPECT_EQ(result.text(), "a"); - - result = parse(row); - EXPECT_EQ(result.symbologyIdentifier(), "]A0"); - EXPECT_EQ(result.text(), "+A8"); + auto result = parse(row); + EXPECT_EQ(result.symbologyIdentifier(), "]A5"); + EXPECT_EQ(result.text(), "a8"); } } diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 1d87d817cb..60222bdf05 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -310,8 +310,6 @@ static ReaderOptions CreateReaderOptions(JNIEnv* env, jobject opts) .setDownscaleFactor(GetIntField(env, cls, opts, "downscaleFactor")) .setMinLineCount(GetIntField(env, cls, opts, "minLineCount")) .setMaxNumberOfSymbols(GetIntField(env, cls, opts, "maxNumberOfSymbols")) - .setTryCode39ExtendedMode(GetBooleanField(env, cls, opts, "tryCode39ExtendedMode")) - .setValidateCode39CheckSum(GetBooleanField(env, cls, opts, "validateCode39CheckSum")) .setReturnCodabarStartEnd(GetBooleanField(env, cls, opts, "returnCodabarStartEnd")) .setReturnErrors(GetBooleanField(env, cls, opts, "returnErrors")) .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, opts, "eanAddOnSymbol", "EanAddOnSymbol"))) diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index d93f89ece6..051c9f5474 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -75,7 +75,9 @@ public class BarcodeReader(public var options: Options = Options()) { var downscaleThreshold: Int = 500, var minLineCount: Int = 2, var maxNumberOfSymbols: Int = 0xff, + @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var tryCode39ExtendedMode: Boolean = false, + @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateCode39CheckSum: Boolean = false, @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateITFCheckSum: Boolean = false, From bf40a6662654a3d7293848512fa9468ebdbda1bf Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 02:05:20 +0100 Subject: [PATCH 072/431] rust: make ImageView a value type like shared pointer This allows to pass `ImageView`, `&ImageView` and `&DynamicImage` directly to `read_barcodes`. --- wrappers/rust/examples/demo.rs | 25 +++++++++++++------------ wrappers/rust/src/lib.rs | 29 ++++++++++++++++++----------- 2 files changed, 31 insertions(+), 23 deletions(-) diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index c45f9f5ddd..b0c045861b 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -19,35 +19,36 @@ fn main() -> anyhow::Result<()> { let image = image::open(&cli.filename)?; - #[cfg(feature = "image")] - let iv = ImageView::try_from(&image)?; #[cfg(not(feature = "image"))] let lum_img = image.into_luma8(); #[cfg(not(feature = "image"))] let iv = ImageView::from_slice(&lum_img, lum_img.width(), lum_img.height(), ImageFormat::Lum)?; let formats = barcode_formats_from_string(cli.formats.unwrap_or_default())?; - let opts = ReaderOptions::new() + let opts = ReaderOptions::default() .formats(formats) .try_harder(!cli.fast) .try_invert(!cli.fast) .try_rotate(!cli.fast); - let results = read_barcodes(&iv, &opts)?; + #[cfg(feature = "image")] + let results = read_barcodes(&image, &opts)?; + #[cfg(not(feature = "image"))] + let results = read_barcodes(iv, opts)?; if results.is_empty() { println!("No barcode found."); } else { for result in results { - println!("Text: {}", result.text()); - println!("Bytes: {:?}", result.bytes()); - println!("Format: {}", result.format()); - println!("Content: {}", result.content_type()); + println!("Text: {}", result.text()); + println!("Bytes: {:?}", result.bytes()); + println!("Format: {}", result.format()); + println!("Content: {}", result.content_type()); println!("Identifier: {}", result.symbology_identifier()); - println!("EC Level: {}", result.ec_level()); - println!("Error: {}", result.error_message()); - println!("Orientation: {}", result.orientation()); - println!("Position: {}", result.position()); + println!("EC Level: {}", result.ec_level()); + println!("Error: {}", result.error_message()); + println!("Rotation: {}", result.orientation()); + println!("Position: {}", result.position()); println!(); } } diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 6904d1f0d4..6584bbde6c 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -26,6 +26,7 @@ use std::fmt::{Display, Formatter}; use std::io::ErrorKind; use std::marker::PhantomData; use std::mem::transmute; +use std::rc::Rc; pub type Error = std::io::Error; @@ -107,17 +108,20 @@ impl Display for ContentType { } } -pub struct ImageView<'a>(*mut zxing_ImageView, PhantomData<&'a u8>); +#[derive(Debug, PartialEq)] +struct ImageViewOwner<'a>(*mut zxing_ImageView, PhantomData<&'a u8>); -impl Drop for ImageView<'_> { +impl Drop for ImageViewOwner<'_> { fn drop(&mut self) { unsafe { zxing_ImageView_delete(self.0) } } } +#[derive(Debug, Clone, PartialEq)] +pub struct ImageView<'a>(Rc>); -impl<'a> AsRef> for ImageView<'a> { - fn as_ref(&self) -> &ImageView<'a> { - self +impl<'a> From<&'a ImageView<'a>> for ImageView<'a> { + fn from(img: &'a ImageView) -> Self { + img.clone() } } @@ -143,7 +147,7 @@ impl<'a> ImageView<'a> { row_stride: U, pix_stride: U, ) -> Result { - let res = ImageView( + let res = ImageView(Rc::new(ImageViewOwner( zxing_ImageView_new( ptr, Self::try_into_int(width)?, @@ -153,7 +157,7 @@ impl<'a> ImageView<'a> { Self::try_into_int(pix_stride)?, ), PhantomData, - ); + ))); Ok(res) } @@ -171,12 +175,12 @@ impl<'a> ImageView<'a> { } pub fn cropped(self, left: i32, top: i32, width: i32, height: i32) -> Self { - unsafe { zxing_ImageView_crop(self.0, left, top, width, height) } + unsafe { zxing_ImageView_crop((self.0).0, left, top, width, height) } self } pub fn rotated(self, degree: i32) -> Self { - unsafe { zxing_ImageView_rotate(self.0, degree) } + unsafe { zxing_ImageView_rotate((self.0).0, degree) } self } } @@ -343,9 +347,12 @@ pub fn barcode_formats_from_string(str: impl AsRef) -> Result(image: impl AsRef>, opts: impl AsRef) -> Result, Error> { +pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef) -> Result, Error> { + let iv_: ImageView = image + .try_into() + .map_err(|_| Error::new(ErrorKind::InvalidInput, "Failed to image.try_into::()"))?; unsafe { - let results = zxing_ReadBarcodes(image.as_ref().0, opts.as_ref().0); + let results = zxing_ReadBarcodes((iv_.0).0, opts.as_ref().0); if !results.is_null() { let size = zxing_Results_size(results); let mut vec = Vec::::with_capacity(size as usize); From d636c6d8a054fe5d794e9ed57159a5230ad6ebf5 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 02:22:14 +0100 Subject: [PATCH 073/431] ReaderOptions: "un-deprecate" tryCode39ExtendedMode --- core/src/ReaderOptions.h | 6 +++--- core/src/oned/ODCode39Reader.cpp | 2 +- .../zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index b8c397c622..f7f570f438 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -80,7 +80,7 @@ class ReaderOptions _tryInvert(1), _tryDownscale(1), _isPure(0), - _tryCode39ExtendedMode(0), + _tryCode39ExtendedMode(1), _validateCode39CheckSum(0), _validateITFCheckSum(0), _returnCodabarStartEnd(0), @@ -141,8 +141,8 @@ class ReaderOptions /// The maximum number of symbols (barcodes) to detect / look for in the image with ReadBarcodes ZX_PROPERTY(uint8_t, maxNumberOfSymbols, setMaxNumberOfSymbols) - /// Deprecated / does nothing. The Code39 was decoded from full ASCII iff symbologyIdentifier()[2] >= '4' - ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode, [[deprecated]]) + /// Enable the heuristic to detect and decode "full ASCII"/extended Code39 symbols + ZX_PROPERTY(bool, tryCode39ExtendedMode, setTryCode39ExtendedMode) /// Deprecated / does nothing. The Code39 symbol has a valid checksum iff symbologyIdentifier()[2] is an odd digit ZX_PROPERTY(bool, validateCode39CheckSum, setValidateCode39CheckSum, [[deprecated]]) diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index 99b4ecc4d1..eec62a85ea 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -122,7 +122,7 @@ Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique txt.push_back(lastChar); const char shiftChars[] = "$%/+"; - auto fullASCII = DecodeCode39AndCode93FullASCII(txt, shiftChars); + auto fullASCII = _opts.tryCode39ExtendedMode() ? DecodeCode39AndCode93FullASCII(txt, shiftChars) : ""; bool hasFullASCII = !fullASCII.empty() && std::find_first_of(txt.begin(), txt.end(), shiftChars, shiftChars + 4) != txt.end(); if (hasFullASCII) txt = fullASCII; diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index 051c9f5474..7c743fc312 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -75,8 +75,7 @@ public class BarcodeReader(public var options: Options = Options()) { var downscaleThreshold: Int = 500, var minLineCount: Int = 2, var maxNumberOfSymbols: Int = 0xff, - @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") - var tryCode39ExtendedMode: Boolean = false, + var tryCode39ExtendedMode: Boolean = true, @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateCode39CheckSum: Boolean = false, @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") From c32df728ff2cb04fd6bd8fdea6d06cc77fb11984 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 02:31:28 +0100 Subject: [PATCH 074/431] rust: rename the front end type `ReaderResult` into `Barcode` --- wrappers/rust/README.md | 9 ++++----- wrappers/rust/examples/demo.rs | 26 +++++++++++++------------- wrappers/rust/src/lib.rs | 14 +++++++------- 3 files changed, 24 insertions(+), 25 deletions(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index c97ee2d65e..a6661203bd 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -25,18 +25,17 @@ use zxing_cpp::{ImageView, ReaderOptions, BarcodeFormat, read_barcodes}; fn main() -> anyhow::Result<()> { let image = image::open("some-image-file.jpg")?; - let iv = ImageView::try_from(&image)?; let opts = ReaderOptions::default() .formats(BarcodeFormat::QRCode | BarcodeFormat::LinearCodes) .try_invert(false); - let results = read_barcodes(&iv, &opts)?; + let barcodes = read_barcodes(&image, &opts)?; - if results.is_empty() { + if barcodes.is_empty() { println!("No barcode found."); } else { - for result in results { - println!("{}: {}", result.format(), result.text()); + for barcode in barcodes { + println!("{}: {}", barcode.format(), barcode.text()); } } diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index b0c045861b..1a235c6cbf 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -32,23 +32,23 @@ fn main() -> anyhow::Result<()> { .try_rotate(!cli.fast); #[cfg(feature = "image")] - let results = read_barcodes(&image, &opts)?; + let barcodes = read_barcodes(&image, &opts)?; #[cfg(not(feature = "image"))] - let results = read_barcodes(iv, opts)?; + let barcodes = read_barcodes(iv, opts)?; - if results.is_empty() { + if barcodes.is_empty() { println!("No barcode found."); } else { - for result in results { - println!("Text: {}", result.text()); - println!("Bytes: {:?}", result.bytes()); - println!("Format: {}", result.format()); - println!("Content: {}", result.content_type()); - println!("Identifier: {}", result.symbology_identifier()); - println!("EC Level: {}", result.ec_level()); - println!("Error: {}", result.error_message()); - println!("Rotation: {}", result.orientation()); - println!("Position: {}", result.position()); + for barcode in barcodes { + println!("Text: {}", barcode.text()); + println!("Bytes: {:?}", barcode.bytes()); + println!("Format: {}", barcode.format()); + println!("Content: {}", barcode.content_type()); + println!("Identifier: {}", barcode.symbology_identifier()); + println!("EC Level: {}", barcode.ec_level()); + println!("Error: {}", barcode.error_message()); + println!("Rotation: {}", barcode.orientation()); + println!("Position: {}", barcode.position()); println!(); } } diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 6584bbde6c..b29a1072ae 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -272,9 +272,9 @@ impl ReaderOptions { property!(min_line_count, i32); } -pub struct ReaderResult(*mut zxing_Result); +pub struct Barcode(*mut zxing_Result); -impl Drop for ReaderResult { +impl Drop for Barcode { fn drop(&mut self) { unsafe { zxing_Result_delete(self.0) } } @@ -312,7 +312,7 @@ macro_rules! getter { }; } -impl ReaderResult { +impl Barcode { getter!(is_valid, isValid, transmute, bool); getter!(format, format, (|f| BarcodeFormats::new(f).unwrap().into_iter().last().unwrap()), BarcodeFormat); getter!(content_type, contentType, transmute, ContentType); @@ -347,7 +347,7 @@ pub fn barcode_formats_from_string(str: impl AsRef) -> Result(image: impl TryInto>, opts: impl AsRef) -> Result, Error> { +pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef) -> Result, Error> { let iv_: ImageView = image .try_into() .map_err(|_| Error::new(ErrorKind::InvalidInput, "Failed to image.try_into::()"))?; @@ -355,14 +355,14 @@ pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef::with_capacity(size as usize); + let mut vec = Vec::::with_capacity(size as usize); for i in 0..size { - vec.push(ReaderResult(zxing_Results_move(results, i))); + vec.push(Barcode(zxing_Results_move(results, i))); } zxing_Results_delete(results); Ok(vec) } else { - last_error_or!(Vec::::default()) + last_error_or!(Vec::::default()) } } } From 68c97c744f2fe1ead3677dd106020530d44d7180 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 09:33:45 +0100 Subject: [PATCH 075/431] ReaderOptions: `returnCodabarStartEnd` is deprecated and defaults to true --- core/src/ReaderOptions.h | 8 ++++---- test/samples/codabar-1/01.txt | 2 +- test/samples/codabar-1/02.txt | 2 +- test/samples/codabar-1/03.txt | 2 +- test/samples/codabar-1/04.txt | 2 +- test/samples/codabar-1/09.txt | 2 +- test/samples/codabar-1/10.txt | 2 +- test/samples/codabar-1/11.txt | 2 +- test/samples/codabar-1/12.txt | 2 +- test/samples/codabar-1/13.txt | 2 +- test/samples/codabar-1/14.txt | 2 +- test/samples/codabar-1/15.txt | 2 +- test/samples/codabar-2/01.txt | 2 +- test/samples/codabar-2/02.txt | 2 +- test/samples/codabar-2/03.txt | 2 +- test/unit/oned/ODCodaBarWriterTest.cpp | 2 +- wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp | 2 +- .../zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt | 1 + 18 files changed, 21 insertions(+), 20 deletions(-) diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index f7f570f438..e74db5a33d 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -83,7 +83,7 @@ class ReaderOptions _tryCode39ExtendedMode(1), _validateCode39CheckSum(0), _validateITFCheckSum(0), - _returnCodabarStartEnd(0), + _returnCodabarStartEnd(1), _returnErrors(0), _downscaleFactor(3), _eanAddOnSymbol(EanAddOnSymbol::Ignore), @@ -97,7 +97,7 @@ class ReaderOptions {} #define ZX_PROPERTY(TYPE, GETTER, SETTER, ...) \ - __VA_ARGS__ TYPE GETTER() const noexcept { return _##GETTER; } \ + TYPE GETTER() const noexcept { return _##GETTER; } \ __VA_ARGS__ ReaderOptions& SETTER(TYPE v)& { return (void)(_##GETTER = std::move(v)), *this; } \ __VA_ARGS__ ReaderOptions&& SETTER(TYPE v)&& { return (void)(_##GETTER = std::move(v)), std::move(*this); } @@ -150,8 +150,8 @@ class ReaderOptions /// Deprecated / does nothing. The ITF symbol has a valid checksum iff symbologyIdentifier()[2] == '1'. ZX_PROPERTY(bool, validateITFCheckSum, setValidateITFCheckSum, [[deprecated]]) - /// If true, return the start and end chars in a Codabar barcode instead of stripping them. - ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd) + /// Deprecated / does nothing. Codabar start/stop characters are always returned. + ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd, [[deprecated]]) /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Result::error()) ZX_PROPERTY(bool, returnErrors, setReturnErrors) diff --git a/test/samples/codabar-1/01.txt b/test/samples/codabar-1/01.txt index 6a537b5b36..e0a295b4cd 100644 --- a/test/samples/codabar-1/01.txt +++ b/test/samples/codabar-1/01.txt @@ -1 +1 @@ -1234567890 \ No newline at end of file +A1234567890A \ No newline at end of file diff --git a/test/samples/codabar-1/02.txt b/test/samples/codabar-1/02.txt index 6a537b5b36..e0a295b4cd 100644 --- a/test/samples/codabar-1/02.txt +++ b/test/samples/codabar-1/02.txt @@ -1 +1 @@ -1234567890 \ No newline at end of file +A1234567890A \ No newline at end of file diff --git a/test/samples/codabar-1/03.txt b/test/samples/codabar-1/03.txt index b8ec6e4ecb..bd6d644978 100644 --- a/test/samples/codabar-1/03.txt +++ b/test/samples/codabar-1/03.txt @@ -1 +1 @@ -294/586 \ No newline at end of file +A294/586B \ No newline at end of file diff --git a/test/samples/codabar-1/04.txt b/test/samples/codabar-1/04.txt index cf537db50e..3cca605e35 100644 --- a/test/samples/codabar-1/04.txt +++ b/test/samples/codabar-1/04.txt @@ -1 +1 @@ -123455 \ No newline at end of file +A123455C \ No newline at end of file diff --git a/test/samples/codabar-1/09.txt b/test/samples/codabar-1/09.txt index bd41cba781..8b064ef3eb 100644 --- a/test/samples/codabar-1/09.txt +++ b/test/samples/codabar-1/09.txt @@ -1 +1 @@ -12345 \ No newline at end of file +A12345A \ No newline at end of file diff --git a/test/samples/codabar-1/10.txt b/test/samples/codabar-1/10.txt index 4632e068d5..01ceaa7705 100644 --- a/test/samples/codabar-1/10.txt +++ b/test/samples/codabar-1/10.txt @@ -1 +1 @@ -123456 \ No newline at end of file +A123456A \ No newline at end of file diff --git a/test/samples/codabar-1/11.txt b/test/samples/codabar-1/11.txt index 675525a78d..3b0f5365b5 100644 --- a/test/samples/codabar-1/11.txt +++ b/test/samples/codabar-1/11.txt @@ -1 +1 @@ -3419500 \ No newline at end of file +A3419500A \ No newline at end of file diff --git a/test/samples/codabar-1/12.txt b/test/samples/codabar-1/12.txt index 8782ad7969..7a517b46dd 100644 --- a/test/samples/codabar-1/12.txt +++ b/test/samples/codabar-1/12.txt @@ -1 +1 @@ -31117013206375 \ No newline at end of file +A31117013206375B \ No newline at end of file diff --git a/test/samples/codabar-1/13.txt b/test/samples/codabar-1/13.txt index bd41cba781..872b3c4b62 100644 --- a/test/samples/codabar-1/13.txt +++ b/test/samples/codabar-1/13.txt @@ -1 +1 @@ -12345 \ No newline at end of file +A12345B \ No newline at end of file diff --git a/test/samples/codabar-1/14.txt b/test/samples/codabar-1/14.txt index 8782ad7969..8e8259ef91 100644 --- a/test/samples/codabar-1/14.txt +++ b/test/samples/codabar-1/14.txt @@ -1 +1 @@ -31117013206375 \ No newline at end of file +A31117013206375A \ No newline at end of file diff --git a/test/samples/codabar-1/15.txt b/test/samples/codabar-1/15.txt index 4d38188ff3..745114b8d1 100644 --- a/test/samples/codabar-1/15.txt +++ b/test/samples/codabar-1/15.txt @@ -1 +1 @@ -123456789012 \ No newline at end of file +A123456789012A \ No newline at end of file diff --git a/test/samples/codabar-2/01.txt b/test/samples/codabar-2/01.txt index a9dbc2e2b4..df315db396 100644 --- a/test/samples/codabar-2/01.txt +++ b/test/samples/codabar-2/01.txt @@ -1 +1 @@ -80125178+ \ No newline at end of file +A80125178+B \ No newline at end of file diff --git a/test/samples/codabar-2/02.txt b/test/samples/codabar-2/02.txt index a9dbc2e2b4..df315db396 100644 --- a/test/samples/codabar-2/02.txt +++ b/test/samples/codabar-2/02.txt @@ -1 +1 @@ -80125178+ \ No newline at end of file +A80125178+B \ No newline at end of file diff --git a/test/samples/codabar-2/03.txt b/test/samples/codabar-2/03.txt index f19eb7e7bc..cb586b0492 100644 --- a/test/samples/codabar-2/03.txt +++ b/test/samples/codabar-2/03.txt @@ -1 +1 @@ -0944416895273 \ No newline at end of file +A0944416895273A \ No newline at end of file diff --git a/test/unit/oned/ODCodaBarWriterTest.cpp b/test/unit/oned/ODCodaBarWriterTest.cpp index c49ca3d880..35b1f87ece 100644 --- a/test/unit/oned/ODCodaBarWriterTest.cpp +++ b/test/unit/oned/ODCodaBarWriterTest.cpp @@ -50,7 +50,7 @@ TEST(ODCodaBarWriterTest, FullCircle) { std::string text = "A0123456789-$:/.+A"; auto matrix = CodabarWriter().encode(text, 0, 0); - auto opts = ReaderOptions().setReturnCodabarStartEnd(true); + auto opts = ReaderOptions(); Result res = OneD::DecodeSingleRow(CodabarReader(opts), matrix.row(0)); EXPECT_EQ(text, res.text()); diff --git a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp index 60222bdf05..a87db81e9c 100644 --- a/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp +++ b/wrappers/android/zxingcpp/src/main/cpp/ZXingCpp.cpp @@ -310,7 +310,7 @@ static ReaderOptions CreateReaderOptions(JNIEnv* env, jobject opts) .setDownscaleFactor(GetIntField(env, cls, opts, "downscaleFactor")) .setMinLineCount(GetIntField(env, cls, opts, "minLineCount")) .setMaxNumberOfSymbols(GetIntField(env, cls, opts, "maxNumberOfSymbols")) - .setReturnCodabarStartEnd(GetBooleanField(env, cls, opts, "returnCodabarStartEnd")) + .setTryCode39ExtendedMode(GetBooleanField(env, cls, opts, "tryCode39ExtendedMode")) .setReturnErrors(GetBooleanField(env, cls, opts, "returnErrors")) .setEanAddOnSymbol(EanAddOnSymbolFromString(GetEnumField(env, cls, opts, "eanAddOnSymbol", "EanAddOnSymbol"))) .setTextMode(TextModeFromString(GetEnumField(env, cls, opts, "textMode", "TextMode"))) diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index 7c743fc312..748c00e834 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -80,6 +80,7 @@ public class BarcodeReader(public var options: Options = Options()) { var validateCode39CheckSum: Boolean = false, @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var validateITFCheckSum: Boolean = false, + @Deprecated("See https://github.com/zxing-cpp/zxing-cpp/discussions/704") var returnCodabarStartEnd: Boolean = false, var returnErrors: Boolean = false, var eanAddOnSymbol: EanAddOnSymbol = EanAddOnSymbol.IGNORE, From 2f3c72cccea22a60d41b31c185aba1ea5425d6bb Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 09:47:41 +0100 Subject: [PATCH 076/431] ReaderOptions: re-enable validateCode39CheckSum and validateITFCheckSum Those two properties are still deprecated but I realized that keeping the API for ABI compatibility reasons but removing the one line that actually does the work, is bad judgment with respect to the user base. Sorry, @gitlost ;). --- core/src/oned/ODCode39Reader.cpp | 4 +++- core/src/oned/ODITFReader.cpp | 4 +++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index eec62a85ea..e102fee353 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -130,12 +130,14 @@ Result Code39Reader::decodePattern(int rowNumber, PatternView& next, std::unique if (hasValidCheckSum) txt.push_back(lastChar); + Error error = _opts.validateCode39CheckSum() && !hasValidCheckSum ? ChecksumError() : Error(); + // Symbology identifier modifiers ISO/IEC 16388:2007 Annex C Table C.1 constexpr const char symbologyModifiers[4] = { '0', '1' /*checksum*/, '4' /*full ASCII*/, '5' /*checksum + full ASCII*/ }; SymbologyIdentifier symbologyIdentifier = {'A', symbologyModifiers[(int)hasValidCheckSum + 2 * (int)hasFullASCII]}; int xStop = next.pixelsTillEnd(); - return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier); + return Result(std::move(txt), rowNumber, xStart, xStop, BarcodeFormat::Code39, symbologyIdentifier, error); } } // namespace ZXing::OneD diff --git a/core/src/oned/ODITFReader.cpp b/core/src/oned/ODITFReader.cpp index c9af66503a..b6d75aa7b4 100644 --- a/core/src/oned/ODITFReader.cpp +++ b/core/src/oned/ODITFReader.cpp @@ -65,12 +65,14 @@ Result ITFReader::decodePattern(int rowNumber, PatternView& next, std::unique_pt if (!IsRightGuard(next, STOP_PATTERN_1, minQuietZone) && !IsRightGuard(next, STOP_PATTERN_2, minQuietZone)) return {}; + Error error = _opts.validateITFCheckSum() && !GTIN::IsCheckDigitValid(txt) ? ChecksumError() : Error(); + // Symbology identifier ISO/IEC 16390:2007 Annex C Table C.1 // See also GS1 General Specifications 5.1.2 Figure 5.1.2-2 SymbologyIdentifier symbologyIdentifier = {'I', GTIN::IsCheckDigitValid(txt) ? '1' : '0'}; int xStop = next.pixelsTillEnd(); - return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::ITF, symbologyIdentifier); + return Result(txt, rowNumber, xStart, xStop, BarcodeFormat::ITF, symbologyIdentifier, error); } } // namespace ZXing::OneD From 0ce62fcce7e1cb4f12a2eae322a861d3f01cc0c5 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 09:59:01 +0100 Subject: [PATCH 077/431] rust: bump version to 0.2.2 --- wrappers/rust/Cargo.toml | 6 +++--- wrappers/rust/README.md | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index cf6fcfca3b..9e403551f6 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zxing-cpp" -version = "0.2.1" +version = "0.2.2" edition = "2021" # authors = ["Axel Waggershauser "] license = "Apache-2.0" @@ -22,13 +22,13 @@ bundled = [] [dependencies] paste = "1.0.14" flagset = "0.4.4" -image = {version = "0.24.7", optional = true} +image = {version = "0.24.8", optional = true} [dev-dependencies] cfg-if = "1.0.0" anyhow = "1.0.79" clap = {version = "4.4.13", features = ["derive"]} -image = {version = "0.24.7"} +image = {version = "0.24.8"} [build-dependencies] cmake = {version = "0.1.50"} diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index a6661203bd..3ef352c3c4 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -15,7 +15,7 @@ In your Cargo.toml: # `bundled` causes cargo to compile and statically link an up to # date version of the c++ core library. This is the most convenient # and safe way to build the library. -zxing-cpp = { version = "0.2.1", features = ["bundled", "image"] } +zxing-cpp = { version = "0.2.2", features = ["bundled", "image"] } ``` Simple example usage: From c366d39ad47ba42fa7c3502702ba1476bc90c320 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 15:26:36 +0100 Subject: [PATCH 078/431] c-API: rename `Result` -> `Barcode` For a detailed reasoning and discussion, see https://github.com/zxing-cpp/zxing-cpp/discussions/705 --- core/src/zxing-c.cpp | 46 +++++++++++++++---------------- core/src/zxing-c.h | 52 +++++++++++++++++------------------ wrappers/c/README.md | 16 +++++------ wrappers/c/zxing-c-test.c | 34 +++++++++++------------ wrappers/rust/src/bindings.rs | 48 ++++++++++++++++---------------- wrappers/rust/src/lib.rs | 16 +++++------ 6 files changed, 106 insertions(+), 106 deletions(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index 3756928d33..acd28ca75b 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -185,28 +185,28 @@ char* zxing_PositionToString(zxing_Position position) } -bool zxing_Result_isValid(const zxing_Result* result) +bool zxing_Barcode_isValid(const zxing_Barcode* barcode) { - return result != NULL && result->isValid(); + return barcode != NULL && barcode->isValid(); } -char* zxing_Result_errorMsg(const zxing_Result* result) +char* zxing_Barcode_errorMsg(const zxing_Barcode* barcode) { - return copy(ToString(result->error())); + return copy(ToString(barcode->error())); } -uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len) +uint8_t* zxing_Barcode_bytes(const zxing_Barcode* barcode, int* len) { - return copy(result->bytes(), len); + return copy(barcode->bytes(), len); } -uint8_t* zxing_Result_bytesECI(const zxing_Result* result, int* len) +uint8_t* zxing_Barcode_bytesECI(const zxing_Barcode* barcode, int* len) { - return copy(result->bytesECI(), len); + return copy(barcode->bytesECI(), len); } #define ZX_GETTER(TYPE, GETTER, TRANS) \ - TYPE zxing_Result_##GETTER(const zxing_Result* result) { return static_cast(TRANS(result->GETTER())); } + TYPE zxing_Barcode_##GETTER(const zxing_Barcode* barcode) { return static_cast(TRANS(barcode->GETTER())); } ZX_GETTER(zxing_BarcodeFormat, format,) ZX_GETTER(zxing_ContentType, contentType,) @@ -226,46 +226,46 @@ ZX_GETTER(int, lineCount,) * ZXing/ReadBarcode.h */ -zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { auto res = ReadBarcodesAndSetLastError(iv, opts, 1); return !res.empty() ? new Result(std::move(res.front())) : NULL; } -zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { auto res = ReadBarcodesAndSetLastError(iv, opts, 0); return !res.empty() ? new Results(std::move(res)) : NULL; } -void zxing_Result_delete(zxing_Result* result) +void zxing_Barcode_delete(zxing_Barcode* barcode) { - delete result; + delete barcode; } -void zxing_Results_delete(zxing_Results* results) +void zxing_Barcodes_delete(zxing_Barcodes* barcodes) { - delete results; + delete barcodes; } -int zxing_Results_size(const zxing_Results* results) +int zxing_Barcodes_size(const zxing_Barcodes* barcodes) { - return results ? Size(*results) : 0; + return barcodes ? Size(*barcodes) : 0; } -const zxing_Result* zxing_Results_at(const zxing_Results* results, int i) +const zxing_Barcode* zxing_Barcodes_at(const zxing_Barcodes* barcodes, int i) { - if (!results || i < 0 || i >= Size(*results)) + if (!barcodes || i < 0 || i >= Size(*barcodes)) return NULL; - return &(*results)[i]; + return &(*barcodes)[i]; } -zxing_Result* zxing_Results_move(zxing_Results* results, int i) +zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i) { - if (!results || i < 0 || i >= Size(*results)) + if (!barcodes || i < 0 || i >= Size(*barcodes)) return NULL; - return new Result(std::move((*results)[i])); + return new Result(std::move((*barcodes)[i])); } char* zxing_LastErrorMsg() diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index b214738827..93904b0979 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -18,8 +18,8 @@ typedef ZXing::ImageView zxing_ImageView; typedef ZXing::ReaderOptions zxing_ReaderOptions; -typedef ZXing::Result zxing_Result; -typedef ZXing::Results zxing_Results; +typedef ZXing::Result zxing_Barcode; +typedef ZXing::Results zxing_Barcodes; extern "C" { @@ -27,8 +27,8 @@ extern "C" typedef struct zxing_ImageView zxing_ImageView; typedef struct zxing_ReaderOptions zxing_ReaderOptions; -typedef struct zxing_Result zxing_Result; -typedef struct zxing_Results zxing_Results; +typedef struct zxing_Barcode zxing_Barcode; +typedef struct zxing_Barcodes zxing_Barcodes; #endif @@ -184,36 +184,36 @@ typedef struct zxing_Position char* zxing_PositionToString(zxing_Position position); -bool zxing_Result_isValid(const zxing_Result* result); -char* zxing_Result_errorMsg(const zxing_Result* result); -zxing_BarcodeFormat zxing_Result_format(const zxing_Result* result); -zxing_ContentType zxing_Result_contentType(const zxing_Result* result); -uint8_t* zxing_Result_bytes(const zxing_Result* result, int* len); -uint8_t* zxing_Result_bytesECI(const zxing_Result* result, int* len); -char* zxing_Result_text(const zxing_Result* result); -char* zxing_Result_ecLevel(const zxing_Result* result); -char* zxing_Result_symbologyIdentifier(const zxing_Result* result); -zxing_Position zxing_Result_position(const zxing_Result* result); -int zxing_Result_orientation(const zxing_Result* result); -bool zxing_Result_hasECI(const zxing_Result* result); -bool zxing_Result_isInverted(const zxing_Result* result); -bool zxing_Result_isMirrored(const zxing_Result* result); -int zxing_Result_lineCount(const zxing_Result* result); +bool zxing_Barcode_isValid(const zxing_Barcode* barcode); +char* zxing_Barcode_errorMsg(const zxing_Barcode* barcode); +zxing_BarcodeFormat zxing_Barcode_format(const zxing_Barcode* barcode); +zxing_ContentType zxing_Barcode_contentType(const zxing_Barcode* barcode); +uint8_t* zxing_Barcode_bytes(const zxing_Barcode* barcode, int* len); +uint8_t* zxing_Barcode_bytesECI(const zxing_Barcode* barcode, int* len); +char* zxing_Barcode_text(const zxing_Barcode* barcode); +char* zxing_Barcode_ecLevel(const zxing_Barcode* barcode); +char* zxing_Barcode_symbologyIdentifier(const zxing_Barcode* barcode); +zxing_Position zxing_Barcode_position(const zxing_Barcode* barcode); +int zxing_Barcode_orientation(const zxing_Barcode* barcode); +bool zxing_Barcode_hasECI(const zxing_Barcode* barcode); +bool zxing_Barcode_isInverted(const zxing_Barcode* barcode); +bool zxing_Barcode_isMirrored(const zxing_Barcode* barcode); +int zxing_Barcode_lineCount(const zxing_Barcode* barcode); /* * ZXing/ReadBarcode.h */ /** Note: opts is optional, i.e. it can be NULL, which will imply default settings. */ -zxing_Result* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); -zxing_Results* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); +zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); +zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); -void zxing_Result_delete(zxing_Result* result); -void zxing_Results_delete(zxing_Results* results); +void zxing_Barcode_delete(zxing_Barcode* barcode); +void zxing_Barcodes_delete(zxing_Barcodes* barcodes); -int zxing_Results_size(const zxing_Results* results); -const zxing_Result* zxing_Results_at(const zxing_Results* results, int i); -zxing_Result* zxing_Results_move(zxing_Results* results, int i); +int zxing_Barcodes_size(const zxing_Barcodes* barcodes); +const zxing_Barcode* zxing_Barcodes_at(const zxing_Barcodes* barcodes, int i); +zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i); char* zxing_LastErrorMsg(); diff --git a/wrappers/c/README.md b/wrappers/c/README.md index a9130186e5..0263df550d 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -26,18 +26,21 @@ int main(int argc, char** argv) zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); /* set ReaderOptions properties, if requried */ - zxing_Result* result = zxing_ReadBarcode(iv, opts); + zxing_Barcode* barcode = zxing_ReadBarcode(iv, opts); - if (result) { - const char* format = zxing_BarcodeFormatToString(zxing_Result_format(result)); + zxing_ImageView_delete(iv); + zxing_ReaderOptions_delete(opts); + + if (barcode) { + const char* format = zxing_BarcodeFormatToString(zxing_Barcode_format(barcode)); printf("Format : %s\n", format); zxing_free(format); - const char* text = zxing_Result_text(result); + const char* text = zxing_Barcode_text(barcode); printf("Text : %s\n", text); zxing_free(text); - zxing_Result_delete(result); + zxing_Barcode_delete(barcode); } else { const char* error = zxing_LastErrorMsg(); if (error) { @@ -48,9 +51,6 @@ int main(int argc, char** argv) zxing_free(error); } - zxing_ImageView_delete(iv); - zxing_ReaderOptions_delete(opts); - return 0; } ``` diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index 2b62511c18..63855b497b 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -70,26 +70,30 @@ int main(int argc, char** argv) zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); - zxing_Results* results = zxing_ReadBarcodes(iv, opts); + zxing_Barcodes* barcodes = zxing_ReadBarcodes(iv, opts); - if (results) { - for (int i = 0, n = zxing_Results_size(results); i < n; ++i) { - const zxing_Result* result = zxing_Results_at(results, i); + zxing_ImageView_delete(iv); + zxing_ReaderOptions_delete(opts); + stbi_image_free(data); + + if (barcodes) { + for (int i = 0, n = zxing_Barcodes_size(barcodes); i < n; ++i) { + const zxing_Barcode* barcode = zxing_Barcodes_at(barcodes, i); - printF("Text : %s\n", zxing_Result_text(result)); - printF("Format : %s\n", zxing_BarcodeFormatToString(zxing_Result_format(result))); - printF("Content : %s\n", zxing_ContentTypeToString(zxing_Result_contentType(result))); - printF("Identifier : %s\n", zxing_Result_symbologyIdentifier(result)); - printF("EC Level : %s\n", zxing_Result_ecLevel(result)); - printF("Error : %s\n", zxing_Result_errorMsg(result)); - printF("Position : %s\n", zxing_PositionToString(zxing_Result_position(result))); - printf("Rotation : %d\n", zxing_Result_orientation(result)); + printF("Text : %s\n", zxing_Barcode_text(barcode)); + printF("Format : %s\n", zxing_BarcodeFormatToString(zxing_Barcode_format(barcode))); + printF("Content : %s\n", zxing_ContentTypeToString(zxing_Barcode_contentType(barcode))); + printF("Identifier : %s\n", zxing_Barcode_symbologyIdentifier(barcode)); + printF("EC Level : %s\n", zxing_Barcode_ecLevel(barcode)); + printF("Error : %s\n", zxing_Barcode_errorMsg(barcode)); + printF("Position : %s\n", zxing_PositionToString(zxing_Barcode_position(barcode))); + printf("Rotation : %d\n", zxing_Barcode_orientation(barcode)); if (i < n-1) printf("\n"); } - zxing_Results_delete(results); + zxing_Barcodes_delete(barcodes); } else { const char* error = zxing_LastErrorMsg(); if (error) { @@ -100,9 +104,5 @@ int main(int argc, char** argv) } } - zxing_ImageView_delete(iv); - zxing_ReaderOptions_delete(opts); - stbi_image_free(data); - return ret; } diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 2fad1b4168..5b5b0c2e4a 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -12,12 +12,12 @@ pub struct zxing_ReaderOptions { } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_Result { +pub struct zxing_Barcode { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_Results { +pub struct zxing_Barcodes { _unused: [u8; 0], } pub const zxing_ImageFormat_None: zxing_ImageFormat = 0; @@ -140,28 +140,28 @@ extern "C" { pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; pub fn zxing_PositionToString(position: zxing_Position) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_isValid(result: *const zxing_Result) -> bool; - pub fn zxing_Result_errorMsg(result: *const zxing_Result) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_format(result: *const zxing_Result) -> zxing_BarcodeFormat; - pub fn zxing_Result_contentType(result: *const zxing_Result) -> zxing_ContentType; - pub fn zxing_Result_bytes(result: *const zxing_Result, len: *mut ::core::ffi::c_int) -> *mut u8; - pub fn zxing_Result_bytesECI(result: *const zxing_Result, len: *mut ::core::ffi::c_int) -> *mut u8; - pub fn zxing_Result_text(result: *const zxing_Result) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_ecLevel(result: *const zxing_Result) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_symbologyIdentifier(result: *const zxing_Result) -> *mut ::core::ffi::c_char; - pub fn zxing_Result_position(result: *const zxing_Result) -> zxing_Position; - pub fn zxing_Result_orientation(result: *const zxing_Result) -> ::core::ffi::c_int; - pub fn zxing_Result_hasECI(result: *const zxing_Result) -> bool; - pub fn zxing_Result_isInverted(result: *const zxing_Result) -> bool; - pub fn zxing_Result_isMirrored(result: *const zxing_Result) -> bool; - pub fn zxing_Result_lineCount(result: *const zxing_Result) -> ::core::ffi::c_int; - pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Result; - pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Results; - pub fn zxing_Result_delete(result: *mut zxing_Result); - pub fn zxing_Results_delete(results: *mut zxing_Results); - pub fn zxing_Results_size(results: *const zxing_Results) -> ::core::ffi::c_int; - pub fn zxing_Results_at(results: *const zxing_Results, i: ::core::ffi::c_int) -> *const zxing_Result; - pub fn zxing_Results_move(results: *mut zxing_Results, i: ::core::ffi::c_int) -> *mut zxing_Result; + pub fn zxing_Barcode_isValid(barcode: *const zxing_Barcode) -> bool; + pub fn zxing_Barcode_errorMsg(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; + pub fn zxing_Barcode_format(barcode: *const zxing_Barcode) -> zxing_BarcodeFormat; + pub fn zxing_Barcode_contentType(barcode: *const zxing_Barcode) -> zxing_ContentType; + pub fn zxing_Barcode_bytes(barcode: *const zxing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn zxing_Barcode_bytesECI(barcode: *const zxing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn zxing_Barcode_text(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; + pub fn zxing_Barcode_ecLevel(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; + pub fn zxing_Barcode_symbologyIdentifier(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; + pub fn zxing_Barcode_position(barcode: *const zxing_Barcode) -> zxing_Position; + pub fn zxing_Barcode_orientation(barcode: *const zxing_Barcode) -> ::core::ffi::c_int; + pub fn zxing_Barcode_hasECI(barcode: *const zxing_Barcode) -> bool; + pub fn zxing_Barcode_isInverted(barcode: *const zxing_Barcode) -> bool; + pub fn zxing_Barcode_isMirrored(barcode: *const zxing_Barcode) -> bool; + pub fn zxing_Barcode_lineCount(barcode: *const zxing_Barcode) -> ::core::ffi::c_int; + pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Barcode; + pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Barcodes; + pub fn zxing_Barcode_delete(barcode: *mut zxing_Barcode); + pub fn zxing_Barcodes_delete(barcodes: *mut zxing_Barcodes); + pub fn zxing_Barcodes_size(barcodes: *const zxing_Barcodes) -> ::core::ffi::c_int; + pub fn zxing_Barcodes_at(barcodes: *const zxing_Barcodes, i: ::core::ffi::c_int) -> *const zxing_Barcode; + pub fn zxing_Barcodes_move(barcodes: *mut zxing_Barcodes, i: ::core::ffi::c_int) -> *mut zxing_Barcode; pub fn zxing_LastErrorMsg() -> *mut ::core::ffi::c_char; pub fn zxing_free(ptr: *mut ::core::ffi::c_void); } diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index b29a1072ae..b98d4bb28b 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -272,11 +272,11 @@ impl ReaderOptions { property!(min_line_count, i32); } -pub struct Barcode(*mut zxing_Result); +pub struct Barcode(*mut zxing_Barcode); impl Drop for Barcode { fn drop(&mut self) { - unsafe { zxing_Result_delete(self.0) } + unsafe { zxing_Barcode_delete(self.0) } } } @@ -307,7 +307,7 @@ impl Display for Position { macro_rules! getter { ($r_name:ident, $c_name:ident, $conv:expr, $type:ty) => { pub fn $r_name(&self) -> $type { - paste! { unsafe { $conv([](self.0)) } } + paste! { unsafe { $conv([](self.0)) } } } }; } @@ -329,11 +329,11 @@ impl Barcode { pub fn bytes(&self) -> Vec { let mut len: c_int = 0; - unsafe { c2r_vec(zxing_Result_bytes(self.0, &mut len), len) } + unsafe { c2r_vec(zxing_Barcode_bytes(self.0, &mut len), len) } } pub fn bytes_eci(&self) -> Vec { let mut len: c_int = 0; - unsafe { c2r_vec(zxing_Result_bytesECI(self.0, &mut len), len) } + unsafe { c2r_vec(zxing_Barcode_bytesECI(self.0, &mut len), len) } } } @@ -354,12 +354,12 @@ pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef::with_capacity(size as usize); for i in 0..size { - vec.push(Barcode(zxing_Results_move(results, i))); + vec.push(Barcode(zxing_Barcodes_move(results, i))); } - zxing_Results_delete(results); + zxing_Barcodes_delete(results); Ok(vec) } else { last_error_or!(Vec::::default()) From 04b8f83419871c96beafd75032f2b534394f39b6 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 15:55:07 +0100 Subject: [PATCH 079/431] dotnet: initial .NET wrapper implementation --- .editorconfig | 2 +- wrappers/dotnet/.gitignore | 56 +++ wrappers/dotnet/README.md | 64 ++++ wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 333 ++++++++++++++++++ wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 11 + wrappers/dotnet/ZXingCppDemo/Program.cs | 34 ++ .../dotnet/ZXingCppDemo/ZXingCppDemo.csproj | 15 + wrappers/dotnet/dotnet.sln | 28 ++ 8 files changed, 542 insertions(+), 1 deletion(-) create mode 100644 wrappers/dotnet/.gitignore create mode 100644 wrappers/dotnet/README.md create mode 100644 wrappers/dotnet/ZXingCpp/ZXingCpp.cs create mode 100644 wrappers/dotnet/ZXingCpp/ZXingCpp.csproj create mode 100644 wrappers/dotnet/ZXingCppDemo/Program.cs create mode 100644 wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj create mode 100644 wrappers/dotnet/dotnet.sln diff --git a/.editorconfig b/.editorconfig index 50d16bc2ce..4538c85cd7 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,c,h,html,py,kt}] +[*.{cpp,c,h,html,py,kt,cs}] indent_style = tab indent_size = 4 max_line_length = 135 diff --git a/wrappers/dotnet/.gitignore b/wrappers/dotnet/.gitignore new file mode 100644 index 0000000000..75028b44d1 --- /dev/null +++ b/wrappers/dotnet/.gitignore @@ -0,0 +1,56 @@ +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. +## +## Get latest from `dotnet new gitignore` + +# dotenv files +.env + +# User-specific files +*.rsuser +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Mono auto generated files +mono_crash.* + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +[Ww][Ii][Nn]32/ +[Aa][Rr][Mm]/ +[Aa][Rr][Mm]64/ +bld/ +[Bb]in/ +[Oo]bj/ +[Ll]og/ +[Ll]ogs/ + +# Visual Studio 2015/2017 cache/options directory +.vs/ + +# Visual Studio 2017 auto generated files +Generated\ Files/ + +# NuGet Packages +*.nupkg +# NuGet Symbol Packages +*.snupkg +# The packages folder can be ignored because of Package Restore +**/[Pp]ackages/* +# except build/, which is used as an MSBuild target. +!**/[Pp]ackages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/[Pp]ackages/repositories.config +# NuGet v3's project.json files produces more ignorable files +*.nuget.props +*.nuget.targets diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md new file mode 100644 index 0000000000..5d33207de6 --- /dev/null +++ b/wrappers/dotnet/README.md @@ -0,0 +1,64 @@ +# ZXingCpp + +ZXingCpp is a .NET wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). + +It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. +It was originally ported from the Java ZXing Library but has been developed further and now includes +many improvements in terms of runtime and detection performance. + +## Usage + +There is going to be a NuGet package available that includes the native c++ dll for easy installation +at a later point. + +Simple example usage: + +```cs +using System.Collections.Generic; +using ImageMagick; +using ZXingCpp; + +public static class ImageMagickBarcodeReader +{ + public static List Read(MagickImage img, ReaderOptions? opts = null) + { + var bytes = img.ToByteArray(MagickFormat.Gray); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); +} + +public class Program +{ + public static void Main(string[] args) + { + var img = new MagickImage(args[0]); + + var reader = new BarcodeReader() { + Formats = BarcodeReader.FormatsFromString(args[1]), + TryInvert = false, + }; + + foreach (var b in reader.Read(img)) + Console.WriteLine($"{b.Format} : {b.Text}"); + } +} +``` + +To run the `ZXingCppDemo` sample program, it is important that the dotnet runtime finds the native +`ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this + +```sh +LD_LIBRARY_PATH= dotnet run --project ZXingCppDemo -- ../../test/samples/multi-1/1.png +``` + +Note: This should currently be considered a pre-release. The API may change slightly to be even more +"managed" depending on community feedback. + +## Benchmarking + +To compare the performance of this .NET wrapper project with other available barcode scanner .NET libraries, +I started the project [zxing-bench](https://github.com/axxel/zxing-bench). The README contains a few +results to get an idea. diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs new file mode 100644 index 0000000000..1a665d85f6 --- /dev/null +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -0,0 +1,333 @@ +namespace ZXingCpp { + +using System; +using System.Runtime.InteropServices; +using System.Collections.Generic; + +using static Dll; +using BarcodeFormat = BarcodeFormats; + +internal class Dll +{ + private const string DllName = "ZXing"; + + [DllImport(DllName)] public static extern IntPtr zxing_ReaderOptions_new(); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_delete(IntPtr opts); + + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryHarder(IntPtr opts, bool tryHarder); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryHarder(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryRotate(IntPtr opts, bool tryRotate); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryRotate(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryInvert(IntPtr opts, bool tryInvert); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryInvert(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryDownscale(IntPtr opts, bool tryDownscale); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryDownscale(IntPtr opts); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getIsPure(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setIsPure(IntPtr opts, bool isPure); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setReturnErrors(IntPtr opts, bool returnErrors); + [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getReturnErrors(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setFormats(IntPtr opts, BarcodeFormats formats); + [DllImport(DllName)] public static extern BarcodeFormats zxing_ReaderOptions_getFormats(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setBinarizer(IntPtr opts, Binarizer binarizer); + [DllImport(DllName)] public static extern Binarizer zxing_ReaderOptions_getBinarizer(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setEanAddOnSymbol(IntPtr opts, EanAddOnSymbol eanAddOnSymbol); + [DllImport(DllName)] public static extern EanAddOnSymbol zxing_ReaderOptions_getEanAddOnSymbol(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTextMode(IntPtr opts, TextMode textMode); + [DllImport(DllName)] public static extern TextMode zxing_ReaderOptions_getTextMode(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setMinLineCount(IntPtr opts, int n); + [DllImport(DllName)] public static extern int zxing_ReaderOptions_getMinLineCount(IntPtr opts); + [DllImport(DllName)] public static extern void zxing_ReaderOptions_setMaxNumberOfSymbols(IntPtr opts, int n); + [DllImport(DllName)] public static extern int zxing_ReaderOptions_getMaxNumberOfSymbols(IntPtr opts); + + [DllImport(DllName)] public static extern IntPtr zxing_PositionToString(Position position); + [DllImport(DllName)] public static extern BarcodeFormats zxing_BarcodeFormatsFromString(string str); + + [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new(byte[] data, int width, int height, ImageFormat format, int rowStride, int pixStride); + [DllImport(DllName)] public static extern void zxing_ImageView_delete(IntPtr iv); + + [DllImport(DllName)] public static extern IntPtr zxing_ReadBarcodes(IntPtr iv, IntPtr opts); + [DllImport(DllName)] public static extern void zxing_Barcode_delete(IntPtr result); + [DllImport(DllName)] public static extern void zxing_Barcodes_delete(IntPtr results); + [DllImport(DllName)] public static extern int zxing_Barcodes_size(IntPtr results); + [DllImport(DllName)] public static extern IntPtr zxing_Barcodes_move(IntPtr results, int i); + + [DllImport(DllName)] public static extern bool zxing_Barcode_isValid(IntPtr result); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_errorMsg(IntPtr result); + [DllImport(DllName)] public static extern BarcodeFormat zxing_Barcode_format(IntPtr result); + [DllImport(DllName)] public static extern ContentType zxing_Barcode_contentType(IntPtr result); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_bytes(IntPtr result, out int len); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_bytesECI(IntPtr result, out int len); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_text(IntPtr result); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_ecLevel(IntPtr result); + [DllImport(DllName)] public static extern IntPtr zxing_Barcode_symbologyIdentifier(IntPtr result); + [DllImport(DllName)] public static extern Position zxing_Barcode_position(IntPtr result); + [DllImport(DllName)] public static extern int zxing_Barcode_orientation(IntPtr result); + [DllImport(DllName)] public static extern bool zxing_Barcode_hasECI(IntPtr result); + [DllImport(DllName)] public static extern bool zxing_Barcode_isInverted(IntPtr result); + [DllImport(DllName)] public static extern bool zxing_Barcode_isMirrored(IntPtr result); + [DllImport(DllName)] public static extern int zxing_Barcode_lineCount(IntPtr result); + + [DllImport(DllName)] public static extern void zxing_free(IntPtr opts); + [DllImport(DllName)] public static extern IntPtr zxing_LastErrorMsg(); + + + public static string MarshalAsString(IntPtr ptr) + { + if (ptr == IntPtr.Zero) + throw new Exception("ZXing C-API returned a NULL char*."); + + string res = Marshal.PtrToStringUTF8(ptr) ?? ""; + zxing_free(ptr); + return res; + } + + public delegate IntPtr RetBytesFunc(IntPtr ptr, out int len); + + public static byte[] MarshalAsBytes(RetBytesFunc func, IntPtr d) + { + IntPtr ptr = func(d, out int len); + if (ptr == IntPtr.Zero) + throw new Exception("ZXing C-API returned a NULL byte*."); + + byte[] res = new byte[len]; + Marshal.Copy(ptr, res, 0, len); + zxing_free(ptr); + return res; + } +} + +[Flags] +public enum BarcodeFormats +{ + None = 0, ///< Used as a return value if no valid barcode has been detected + Aztec = (1 << 0), ///< Aztec + Codabar = (1 << 1), ///< Codabar + Code39 = (1 << 2), ///< Code39 + Code93 = (1 << 3), ///< Code93 + Code128 = (1 << 4), ///< Code128 + DataBar = (1 << 5), ///< GS1 DataBar, formerly known as RSS 14 + DataBarExpanded = (1 << 6), ///< GS1 DataBar Expanded, formerly known as RSS EXPANDED + DataMatrix = (1 << 7), ///< DataMatrix + EAN8 = (1 << 8), ///< EAN-8 + EAN13 = (1 << 9), ///< EAN-13 + ITF = (1 << 10), ///< ITF (Interleaved Two of Five) + MaxiCode = (1 << 11), ///< MaxiCode + PDF417 = (1 << 12), ///< PDF417 + QRCode = (1 << 13), ///< QR Code + UPCA = (1 << 14), ///< UPC-A + UPCE = (1 << 15), ///< UPC-E + MicroQRCode = (1 << 16), ///< Micro QR Code + RMQRCode = (1 << 17), ///< Rectangular Micro QR Code + DXFilmEdge = (1 << 18), ///< DX Film Edge Barcode + + LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DXFilmEdge | UPCA | UPCE, + MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, + Any = LinearCodes | MatrixCodes, + + Invalid = -1, +}; + + +public enum Binarizer +{ + LocalAverage, ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer) + GlobalHistogram, ///< T = valley between the 2 largest peaks in the histogram (per line in linear case) + FixedThreshold, ///< T = 127 + BoolCast, ///< T = 0, fastest possible +}; + +public enum EanAddOnSymbol +{ + Ignore, ///< Ignore any Add-On symbol during read/scan + Read, ///< Read EAN-2/EAN-5 Add-On symbol if found + Require, ///< Require EAN-2/EAN-5 Add-On symbol to be present +}; + +public enum TextMode +{ + Plain, ///< bytes() transcoded to unicode based on ECI info or guessed charset (the default mode prior to 2.0) + ECI, ///< standard content following the ECI protocol with every character set ECI segment transcoded to unicode + HRI, ///< Human Readable Interpretation (dependent on the ContentType) + Hex, ///< bytes() transcoded to ASCII string of HEX values + Escaped, ///< Use the EscapeNonGraphical() function (e.g. ASCII 29 will be transcoded to "") +}; + +public enum ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; + +public enum ImageFormat { + None = 0, + Lum = 0x01000000, + RGB = 0x03000102, + BGR = 0x03020100, + RGBX = 0x04000102, + XRGB = 0x04010203, + BGRX = 0x04020100, + XBGR = 0x04030201, +}; + +public struct PointI +{ + public int x, y; +}; + +public struct Position +{ + public PointI topLeft, topRight, bottomRight, bottomLeft; + + public override string ToString() => Dll.MarshalAsString(Dll.zxing_PositionToString(this)); +}; + +public class ImageView +{ + internal IntPtr _d; + + public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride, int pixStride) + { + _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); + if (_d == IntPtr.Zero) + throw new Exception("Failed to create ImageView."); + } + + ~ImageView() => zxing_ImageView_delete(_d); +} + +public class ReaderOptions +{ + internal IntPtr _d; + + public ReaderOptions() + { + _d = zxing_ReaderOptions_new(); + if (_d == IntPtr.Zero) + throw new Exception("Failed to create ReaderOptions."); + } + + ~ReaderOptions() => zxing_ReaderOptions_delete(_d); + + public bool TryHarder + { + get => zxing_ReaderOptions_getTryHarder(_d); + set => zxing_ReaderOptions_setTryHarder(_d, value); + } + + public bool TryRotate + { + get => zxing_ReaderOptions_getTryRotate(_d); + set => zxing_ReaderOptions_setTryRotate(_d, value); + } + + public bool TryInvert + { + get => zxing_ReaderOptions_getTryInvert(_d); + set => zxing_ReaderOptions_setTryInvert(_d, value); + } + + public bool TryDownscale + { + get => zxing_ReaderOptions_getTryDownscale(_d); + set => zxing_ReaderOptions_setTryDownscale(_d, value); + } + + public bool IsPure + { + get => zxing_ReaderOptions_getIsPure(_d); + set => zxing_ReaderOptions_setIsPure(_d, value); + } + + public bool ReturnErrors + { + get => zxing_ReaderOptions_getReturnErrors(_d); + set => zxing_ReaderOptions_setReturnErrors(_d, value); + } + + public BarcodeFormats Formats + { + get => zxing_ReaderOptions_getFormats(_d); + set => zxing_ReaderOptions_setFormats(_d, value); + } + + public Binarizer Binarizer + { + get => zxing_ReaderOptions_getBinarizer(_d); + set => zxing_ReaderOptions_setBinarizer(_d, value); + } + + public EanAddOnSymbol EanAddOnSymbol + { + get => zxing_ReaderOptions_getEanAddOnSymbol(_d); + set => zxing_ReaderOptions_setEanAddOnSymbol(_d, value); + } + + public TextMode TextMode + { + get => zxing_ReaderOptions_getTextMode(_d); + set => zxing_ReaderOptions_setTextMode(_d, value); + } + + public int MinLineCount + { + get => zxing_ReaderOptions_getMinLineCount(_d); + set => zxing_ReaderOptions_setMinLineCount(_d, value); + } + + public int MaxNumberOfSymbols + { + get => zxing_ReaderOptions_getMaxNumberOfSymbols(_d); + set => zxing_ReaderOptions_setMaxNumberOfSymbols(_d, value); + } + +} + +public class Barcode +{ + internal IntPtr _d; + + internal Barcode(IntPtr d) => _d = d; + ~Barcode() => zxing_Barcode_delete(_d); + + public bool IsValid => zxing_Barcode_isValid(_d); + public BarcodeFormat Format => zxing_Barcode_format(_d); + public ContentType ContentType => zxing_Barcode_contentType(_d); + public string Text => MarshalAsString(zxing_Barcode_text(_d)); + public byte[] Bytes => MarshalAsBytes(zxing_Barcode_bytes, _d); + public byte[] BytesECI => MarshalAsBytes(zxing_Barcode_bytesECI, _d); + public string ECLevel => MarshalAsString(zxing_Barcode_ecLevel(_d)); + public string SymbologyIdentifier => MarshalAsString(zxing_Barcode_symbologyIdentifier(_d)); + public string ErrorMsg => MarshalAsString(zxing_Barcode_errorMsg(_d)); + public Position Position => zxing_Barcode_position(_d); + public int Orientation => zxing_Barcode_orientation(_d); + public bool HasECI => zxing_Barcode_hasECI(_d); + public bool IsInverted => zxing_Barcode_isInverted(_d); + public bool IsMirrored => zxing_Barcode_isMirrored(_d); + public int LineCount => zxing_Barcode_lineCount(_d); +} + +public class BarcodeReader : ReaderOptions +{ + public static BarcodeFormats FormatsFromString(string str) + { + var fmts = zxing_BarcodeFormatsFromString(str); + if (fmts == BarcodeFormats.Invalid) + throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + return fmts; + } + + public static List Read(ImageView iv, ReaderOptions? opts = null) + { + var ptr = zxing_ReadBarcodes(iv._d, opts?._d ?? IntPtr.Zero); + if (ptr == IntPtr.Zero) + throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + + var size = zxing_Barcodes_size(ptr); + var res = new List(size); + for (int i = 0; i < size; ++i) + res.Add(new Barcode(zxing_Barcodes_move(ptr, i))); + zxing_Barcodes_delete(ptr); + + return res; + } + + public List Read(ImageView iv) => Read(iv, this); +} + +} \ No newline at end of file diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj new file mode 100644 index 0000000000..93c3508db5 --- /dev/null +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -0,0 +1,11 @@ + + + + netstandard2.1 + + enable + + + + + diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs new file mode 100644 index 0000000000..6cd8c00cef --- /dev/null +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -0,0 +1,34 @@ +using System.Collections.Generic; +using ImageMagick; +using ZXingCpp; + +public static class ImageMagickBarcodeReader +{ + public static List Read(MagickImage img, ReaderOptions? opts = null) + { + var bytes = img.ToByteArray(MagickFormat.Gray); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); +} + +public class Program +{ + public static void Main(string[] args) + { + var img = new MagickImage(args[0]); + Console.WriteLine(img); + + var reader = new BarcodeReader() { + TryInvert = false, + }; + + if (args.Length >= 2) + reader.Formats = BarcodeReader.FormatsFromString(args[1]); + + foreach (var b in reader.Read(img)) + Console.WriteLine($"{b.Format} ({b.ContentType}): {b.Text} / [{string.Join(", ", b.Bytes)}]"); + } +} diff --git a/wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj b/wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj new file mode 100644 index 0000000000..28f5299710 --- /dev/null +++ b/wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj @@ -0,0 +1,15 @@ + + + + + + + + + Exe + net8.0 + enable + enable + + + diff --git a/wrappers/dotnet/dotnet.sln b/wrappers/dotnet/dotnet.sln new file mode 100644 index 0000000000..bc94b4088f --- /dev/null +++ b/wrappers/dotnet/dotnet.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31903.59 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp", "ZXingCpp\ZXingCpp.csproj", "{64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCppDemo", "ZXingCppDemo\ZXingCppDemo.csproj", "{05C626FE-A5CE-4263-9419-35E44F14DB93}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}.Release|Any CPU.Build.0 = Release|Any CPU + {05C626FE-A5CE-4263-9419-35E44F14DB93}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {05C626FE-A5CE-4263-9419-35E44F14DB93}.Debug|Any CPU.Build.0 = Debug|Any CPU + {05C626FE-A5CE-4263-9419-35E44F14DB93}.Release|Any CPU.ActiveCfg = Release|Any CPU + {05C626FE-A5CE-4263-9419-35E44F14DB93}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection +EndGlobal From 9fffdeee7b3db2a2559758891eed2a2f5ac76c51 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 18:51:04 +0100 Subject: [PATCH 080/431] dotnet: add metatdata for NuGet package publishing See https://www.nuget.org/packages/ZXingCpp --- wrappers/dotnet/README.md | 44 ++++++++++++------------ wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 20 ++++++++++- 2 files changed, 41 insertions(+), 23 deletions(-) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index 5d33207de6..a185c2054e 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -8,8 +8,8 @@ many improvements in terms of runtime and detection performance. ## Usage -There is going to be a NuGet package available that includes the native c++ dll for easy installation -at a later point. +There is a NuGet package available: https://www.nuget.org/packages/ZXingCpp. It does currently not yet +contain the native binary dll file. That needs to be copied/build separately at the moment. Simple example usage: @@ -20,30 +20,30 @@ using ZXingCpp; public static class ImageMagickBarcodeReader { - public static List Read(MagickImage img, ReaderOptions? opts = null) - { - var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); - return BarcodeReader.Read(iv, opts); - } - - public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); + public static List Read(MagickImage img, ReaderOptions? opts = null) + { + var bytes = img.ToByteArray(MagickFormat.Gray); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); } public class Program { - public static void Main(string[] args) - { - var img = new MagickImage(args[0]); - - var reader = new BarcodeReader() { - Formats = BarcodeReader.FormatsFromString(args[1]), - TryInvert = false, - }; - - foreach (var b in reader.Read(img)) - Console.WriteLine($"{b.Format} : {b.Text}"); - } + public static void Main(string[] args) + { + var img = new MagickImage(args[0]); + + var reader = new BarcodeReader() { + Formats = BarcodeReader.FormatsFromString(args[1]), + TryInvert = false, + }; + + foreach (var b in reader.Read(img)) + Console.WriteLine($"{b.Format} : {b.Text}"); + } } ``` diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj index 93c3508db5..c106284325 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -4,8 +4,26 @@ netstandard2.1 enable + + ZXingCpp + 0.1.0-alpha + Axel Waggershauser + zxing-cpp + + ZXingCpp is a .NET wrapper for the C++ library zxing-cpp. + It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. + It was originally ported from the Java ZXing Library but has been developed further and now includes + many improvements in terms of runtime and detection performance. + https://github.com/zxing-cpp/zxing-cpp + README.md + Barcode;QRCode + Apache-2.0 + + + - + + From 9e3ec7b9e3b3aae5ba29acc3138eebab1dfc525b Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 18:51:53 +0100 Subject: [PATCH 081/431] dotnet: add license header comments --- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 7 ++++++- wrappers/dotnet/ZXingCppDemo/Program.cs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 1a665d85f6..08c89daa56 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -1,4 +1,9 @@ -namespace ZXingCpp { +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +namespace ZXingCpp { using System; using System.Runtime.InteropServices; diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs index 6cd8c00cef..cf8aec38bf 100644 --- a/wrappers/dotnet/ZXingCppDemo/Program.cs +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -1,4 +1,9 @@ -using System.Collections.Generic; +/* +* Copyright 2024 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +using System.Collections.Generic; using ImageMagick; using ZXingCpp; From 32b3b4c47d9a587334409e0b9548771d7249c760 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 23:35:04 +0100 Subject: [PATCH 082/431] c-API: zxing_ReadBarcodes returns empty list instead of NULL This actually fixes a bug in the .NET wrapper that I made because I must have thought NULL means there was error otherwise I get a list that might be empty. That was not the case but now it is. --- core/src/zxing-c.cpp | 14 ++++++++------ wrappers/c/README.md | 29 ++++++++++++++--------------- wrappers/c/zxing-c-test.c | 14 +++++++------- wrappers/rust/src/lib.rs | 1 + 4 files changed, 30 insertions(+), 28 deletions(-) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index acd28ca75b..d5e4c059a3 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -12,6 +12,7 @@ #include #include #include +#include #include using namespace ZXing; @@ -47,14 +48,15 @@ static uint8_t* copy(const ByteArray& ba, int* len) return ret; } -static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts, int maxSymbols) +static std::tuple ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts, + int maxSymbols) { try { if (iv) { auto o = opts ? *opts : ReaderOptions{}; if (maxSymbols) o.setMaxNumberOfSymbols(maxSymbols); - return ReadBarcodes(*iv, o); + return {ReadBarcodes(*iv, o), true}; } else lastErrorMsg = "ImageView param is NULL"; } catch (std::exception& e) { @@ -63,7 +65,7 @@ static Results ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxin lastErrorMsg = "Unknown error"; } - return {}; + return {Results{}, false}; } extern "C" { @@ -228,14 +230,14 @@ ZX_GETTER(int, lineCount,) zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodesAndSetLastError(iv, opts, 1); + auto [res, ok] = ReadBarcodesAndSetLastError(iv, opts, 1); return !res.empty() ? new Result(std::move(res.front())) : NULL; } zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) { - auto res = ReadBarcodesAndSetLastError(iv, opts, 0); - return !res.empty() ? new Results(std::move(res)) : NULL; + auto [res, ok] = ReadBarcodesAndSetLastError(iv, opts, 0); + return !res.empty() || ok ? new Results(std::move(res)) : NULL; } void zxing_Barcode_delete(zxing_Barcode* barcode) diff --git a/wrappers/c/README.md b/wrappers/c/README.md index 0263df550d..62b920ae97 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -26,28 +26,27 @@ int main(int argc, char** argv) zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); /* set ReaderOptions properties, if requried */ - zxing_Barcode* barcode = zxing_ReadBarcode(iv, opts); + zxing_Barcodes* barcodes = zxing_ReadBarcodes(iv, opts); zxing_ImageView_delete(iv); zxing_ReaderOptions_delete(opts); - if (barcode) { - const char* format = zxing_BarcodeFormatToString(zxing_Barcode_format(barcode)); - printf("Format : %s\n", format); - zxing_free(format); + if (barcodes) { + for (int i = 0, n = zxing_Barcodes_size(barcodes); i < n; ++i) { + const zxing_Barcode* barcode = zxing_Barcodes_at(barcodes, i); - const char* text = zxing_Barcode_text(barcode); - printf("Text : %s\n", text); - zxing_free(text); + char* format = zxing_BarcodeFormatToString(zxing_Barcode_format(barcode)); + printf("Format : %s\n", format); + zxing_free(format); - zxing_Barcode_delete(barcode); - } else { - const char* error = zxing_LastErrorMsg(); - if (error) { - printf("%s\n", error); - } else { - printf("No barcode found\n"); + char* text = zxing_Barcode_text(barcode); + printf("Text : %s\n", text); + zxing_free(text); } + zxing_Barcodes_delete(barcodes); + } else { + char* error = zxing_LastErrorMsg(); + fprintf(stderr, "%s\n", error); zxing_free(error); } diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c index 63855b497b..e5bef602c0 100644 --- a/wrappers/c/zxing-c-test.c +++ b/wrappers/c/zxing-c-test.c @@ -93,15 +93,15 @@ int main(int argc, char** argv) printf("\n"); } + if (zxing_Barcodes_size(barcodes) == 0) + printf("No barcode found\n"); + zxing_Barcodes_delete(barcodes); } else { - const char* error = zxing_LastErrorMsg(); - if (error) { - fprintf(stderr, "%s\n", error); - ret = 2; - } else { - printf("No barcode found\n"); - } + char* error = zxing_LastErrorMsg(); + fprintf(stderr, "%s\n", error); + zxing_free(error); + ret = 2; } return ret; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index b98d4bb28b..c40a05b82b 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -362,6 +362,7 @@ pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef::default()) } } From 9a27c69035820de43980f355893d3f1b17178a9d Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 30 Jan 2024 23:35:19 +0100 Subject: [PATCH 083/431] dotnet: code cosmetic --- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 08c89daa56..e9b60485ae 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -179,7 +179,7 @@ public struct Position { public PointI topLeft, topRight, bottomRight, bottomLeft; - public override string ToString() => Dll.MarshalAsString(Dll.zxing_PositionToString(this)); + public override string ToString() => MarshalAsString(zxing_PositionToString(this)); }; public class ImageView From eba82a8ffadeb9beb0dbcbbb55fbe40a7cc33ab2 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 03:23:15 +0100 Subject: [PATCH 084/431] dotnet: fix issue with loading binary png files via ImageMagick --- wrappers/dotnet/README.md | 2 ++ wrappers/dotnet/ZXingCppDemo/Program.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index a185c2054e..889f1772b9 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -22,6 +22,8 @@ public static class ImageMagickBarcodeReader { public static List Read(MagickImage img, ReaderOptions? opts = null) { + if (img.DetermineBitDepth() < 8) + img.SetBitDepth(8); var bytes = img.ToByteArray(MagickFormat.Gray); var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); return BarcodeReader.Read(iv, opts); diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs index cf8aec38bf..2da0631ffb 100644 --- a/wrappers/dotnet/ZXingCppDemo/Program.cs +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -11,6 +11,8 @@ public static class ImageMagickBarcodeReader { public static List Read(MagickImage img, ReaderOptions? opts = null) { + if (img.DetermineBitDepth() < 8) + img.SetBitDepth(8); var bytes = img.ToByteArray(MagickFormat.Gray); var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); return BarcodeReader.Read(iv, opts); From ea25c3d54274c48fba2e5a4efab53cac180f07b8 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 08:26:31 +0100 Subject: [PATCH 085/431] README: add link to new .NET wrapper --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 3417999786..774752014f 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Thanks a lot for your contribution! * [Android](wrappers/android/README.md) * [C](wrappers/c/README.md) * [iOS](wrappers/ios/README.md) + * [.NET](wrappers/dotnet/README.md) * [Python](wrappers/python/README.md) * [Rust](wrappers/rust/README.md) * [WebAssembly](wrappers/wasm/README.md) From 6861b8229bbfd1088496fcd3edf0222039d4c452 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 10:50:28 +0100 Subject: [PATCH 086/431] dotnet: default `rowStride` and `pixStride` parameters for `ImageView` --- wrappers/dotnet/README.md | 2 +- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 2 +- wrappers/dotnet/ZXingCppDemo/Program.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index 889f1772b9..e4f3693833 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -25,7 +25,7 @@ public static class ImageMagickBarcodeReader if (img.DetermineBitDepth() < 8) img.SetBitDepth(8); var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); return BarcodeReader.Read(iv, opts); } diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index e9b60485ae..697b94de47 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -186,7 +186,7 @@ public class ImageView { internal IntPtr _d; - public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride, int pixStride) + public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) { _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); if (_d == IntPtr.Zero) diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs index 2da0631ffb..9b978b0d76 100644 --- a/wrappers/dotnet/ZXingCppDemo/Program.cs +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -14,7 +14,7 @@ public static List Read(MagickImage img, ReaderOptions? opts = null) if (img.DetermineBitDepth() < 8) img.SetBitDepth(8); var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum, 0, 0); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); return BarcodeReader.Read(iv, opts); } From 38f701e07b66c378e02aa581335817f61c624148 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 10:51:14 +0100 Subject: [PATCH 087/431] dotnet: remove publicly visible BarcodeFormats.Invalid enum value --- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 697b94de47..d127775b73 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -128,8 +128,6 @@ public enum BarcodeFormats LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DXFilmEdge | UPCA | UPCE, MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode, Any = LinearCodes | MatrixCodes, - - Invalid = -1, }; @@ -312,7 +310,7 @@ public class BarcodeReader : ReaderOptions public static BarcodeFormats FormatsFromString(string str) { var fmts = zxing_BarcodeFormatsFromString(str); - if (fmts == BarcodeFormats.Invalid) + if ((int)fmts == -1) // see zxing_BarcodeFormat_Invalid throw new Exception(MarshalAsString(zxing_LastErrorMsg())); return fmts; } From aec1dc37572770988689f5fdbe8b1e5155b6eb15 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 12:14:21 +0100 Subject: [PATCH 088/431] ImageView: introduce bounds checks in constructor * throw on ImageView(nullptr, ...) * new default constructor * new bounds checked constructor: ImageView(data, size, width, ...) * add zxing_ImageView_new_checked to c-API * use new_checked version in Rust and .NET wrappers --- core/src/ImageView.h | 32 +++++++++++++- core/src/ReadBarcode.cpp | 4 +- core/src/zxing-c.cpp | 19 ++++++++- core/src/zxing-c.h | 2 + test/blackbox/ImageLoader.cpp | 2 +- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 6 +-- wrappers/rust/src/bindings.rs | 9 ++++ wrappers/rust/src/lib.rs | 64 +++++++++++++++++----------- 8 files changed, 104 insertions(+), 34 deletions(-) diff --git a/core/src/ImageView.h b/core/src/ImageView.h index cf9bd6d3f8..a7b6f74add 100644 --- a/core/src/ImageView.h +++ b/core/src/ImageView.h @@ -7,6 +7,8 @@ #include #include +#include +#include namespace ZXing { @@ -42,10 +44,14 @@ class ImageView { protected: const uint8_t* _data = nullptr; - ImageFormat _format; + ImageFormat _format = ImageFormat::None; int _width = 0, _height = 0, _pixStride = 0, _rowStride = 0; public: + /** ImageView default constructor creates a 'null' image view + */ + ImageView() = default; + /** * ImageView constructor * @@ -63,7 +69,29 @@ class ImageView _height(height), _pixStride(pixStride ? pixStride : PixStride(format)), _rowStride(rowStride ? rowStride : width * _pixStride) - {} + { + // TODO: [[deprecated]] this check is to prevent exising code from suddenly throwing, remove in 3.0 + if (_data == nullptr && _width == 0 && _height == 0 && rowStride == 0 && pixStride == 0) { + fprintf(stderr, "zxing-cpp deprecation warning: ImageView(nullptr, ...) will throw in the future, use ImageView()\n"); + return; + } + + if (_data == nullptr) + throw std::invalid_argument("Can not construct an ImageView from a NULL pointer"); + + if (_width <= 0 || _height <= 0) + throw std::invalid_argument("Neither width nor height of ImageView can be less or equal to 0"); + } + + /** + * ImageView constructor with bounds checking + */ + ImageView(const uint8_t* data, int size, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) + : ImageView(data, width, height, format, rowStride, pixStride) + { + if (_rowStride < 0 || _pixStride < 0 || size < _height * _rowStride) + throw std::invalid_argument("ImageView parameters are inconsistent (out of bounds)"); + } int width() const { return _width; } int height() const { return _height; } diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 4a166bb9c0..5c371adf1e 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -26,7 +26,7 @@ class LumImage : public ImageView {} public: - LumImage() : ImageView(nullptr, 0, 0, ImageFormat::Lum) {} + LumImage() = default; LumImage(int w, int h) : LumImage(std::make_unique(w * h), w, h) {} uint8_t* data() { return _memory.get(); } @@ -135,7 +135,7 @@ Result ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) { - if (sizeof(PatternType) < 4 && opts.hasFormat(BarcodeFormat::LinearCodes) && (_iv.width() > 0xffff || _iv.height() > 0xffff)) + if (sizeof(PatternType) < 4 && (_iv.width() > 0xffff || _iv.height() > 0xffff)) throw std::invalid_argument("Maximum image width/height is 65535"); if (!_iv.data(0, 0) || _iv.width() * _iv.height() == 0) diff --git a/core/src/zxing-c.cpp b/core/src/zxing-c.cpp index d5e4c059a3..f141130b6d 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/zxing-c.cpp @@ -77,7 +77,24 @@ zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, int pixStride) { ImageFormat cppformat = static_cast(format); - return new ImageView(data, width, height, cppformat, rowStride, pixStride); + try { + return new ImageView(data, width, height, cppformat, rowStride, pixStride); + } catch (std::exception& e) { + lastErrorMsg = e.what(); + } + return NULL; +} + +zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, zxing_ImageFormat format, + int rowStride, int pixStride) +{ + ImageFormat cppformat = static_cast(format); + try { + return new ImageView(data, size, width, height, cppformat, rowStride, pixStride); + } catch (std::exception& e) { + lastErrorMsg = e.what(); + } + return NULL; } void zxing_ImageView_delete(zxing_ImageView* iv) diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h index 93904b0979..75207aa679 100644 --- a/core/src/zxing-c.h +++ b/core/src/zxing-c.h @@ -49,6 +49,8 @@ typedef enum { zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, int pixStride); +zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, zxing_ImageFormat format, + int rowStride, int pixStride); void zxing_ImageView_delete(zxing_ImageView* iv); void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); diff --git a/test/blackbox/ImageLoader.cpp b/test/blackbox/ImageLoader.cpp index 1f1f073421..c3f7ad5165 100644 --- a/test/blackbox/ImageLoader.cpp +++ b/test/blackbox/ImageLoader.cpp @@ -23,7 +23,7 @@ class STBImage : public ImageView std::unique_ptr _memory; public: - STBImage() : ImageView(nullptr, 0, 0, ImageFormat::None), _memory(nullptr, stbi_image_free) {} + STBImage() : ImageView(), _memory(nullptr, stbi_image_free) {} void load(const fs::path& imgPath) { diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index d127775b73..6a9f788d0e 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -47,7 +47,7 @@ internal class Dll [DllImport(DllName)] public static extern IntPtr zxing_PositionToString(Position position); [DllImport(DllName)] public static extern BarcodeFormats zxing_BarcodeFormatsFromString(string str); - [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new(byte[] data, int width, int height, ImageFormat format, int rowStride, int pixStride); + [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new_checked(byte[] data, int size, int width, int height, ImageFormat format, int rowStride, int pixStride); [DllImport(DllName)] public static extern void zxing_ImageView_delete(IntPtr iv); [DllImport(DllName)] public static extern IntPtr zxing_ReadBarcodes(IntPtr iv, IntPtr opts); @@ -186,9 +186,9 @@ public class ImageView public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) { - _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); + _d = zxing_ImageView_new_checked(data, data.Length, width, height, format, rowStride, pixStride); if (_d == IntPtr.Zero) - throw new Exception("Failed to create ImageView."); + throw new Exception(MarshalAsString(zxing_LastErrorMsg())); } ~ImageView() => zxing_ImageView_delete(_d); diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 5b5b0c2e4a..52e659b622 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -100,6 +100,15 @@ extern "C" { rowStride: ::core::ffi::c_int, pixStride: ::core::ffi::c_int, ) -> *mut zxing_ImageView; + pub fn zxing_ImageView_new_checked( + data: *const u8, + size: ::core::ffi::c_int, + width: ::core::ffi::c_int, + height: ::core::ffi::c_int, + format: zxing_ImageFormat, + rowStride: ::core::ffi::c_int, + pixStride: ::core::ffi::c_int, + ) -> *mut zxing_ImageView; pub fn zxing_ImageView_delete(iv: *mut zxing_ImageView); pub fn zxing_ImageView_crop( iv: *mut zxing_ImageView, diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index c40a05b82b..5046a53332 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -48,6 +48,13 @@ fn c2r_vec(buf: *mut u8, len: c_int) -> Vec { res } +fn last_error() -> Error { + match unsafe { zxing_LastErrorMsg().as_mut() } { + None => panic!("Internal error: zxing_LastErrorMsg() returned NULL"), + Some(error) => Error::new(ErrorKind::InvalidInput, c2r_str(error)), + } +} + macro_rules! last_error_or { ($expr:expr) => { match unsafe { zxing_LastErrorMsg().as_mut() } { @@ -137,8 +144,9 @@ impl<'a> ImageView<'a> { /// # Safety /// /// The memory gets accessed inside the c++ library at random places between - /// `ptr` and `ptr + height * row_stride + width * pix_stride. Note that both - /// the stride values could be negative, e.g. if the image view is rotated. + /// `ptr` and `ptr + height * row_stride` or `ptr + width * pix_stride`. + /// Note that both the stride values could be negative, e.g. if the image + /// view is rotated. pub unsafe fn from_ptr, U: TryInto>( ptr: *const u8, width: T, @@ -147,30 +155,37 @@ impl<'a> ImageView<'a> { row_stride: U, pix_stride: U, ) -> Result { - let res = ImageView(Rc::new(ImageViewOwner( - zxing_ImageView_new( - ptr, - Self::try_into_int(width)?, - Self::try_into_int(height)?, - format as zxing_ImageFormat, - Self::try_into_int(row_stride)?, - Self::try_into_int(pix_stride)?, - ), - PhantomData, - ))); - Ok(res) + let iv = zxing_ImageView_new( + ptr, + Self::try_into_int(width)?, + Self::try_into_int(height)?, + format as zxing_ImageFormat, + Self::try_into_int(row_stride)?, + Self::try_into_int(pix_stride)?, + ); + if iv.is_null() { + Err(last_error()) + } else { + Ok(ImageView(Rc::new(ImageViewOwner(iv, PhantomData)))) + } } pub fn from_slice + Clone>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { - let pix_size = match format { - ImageFormat::Lum => 1, - ImageFormat::RGB => 3, - ImageFormat::RGBX => 4, - }; - if Self::try_into_int(data.len())? < Self::try_into_int(width.clone())? * Self::try_into_int(height.clone())? * pix_size { - Err(Error::new(ErrorKind::InvalidInput, "data.len() < width * height * pix_size")) - } else { - unsafe { Self::from_ptr(data.as_ptr(), width, height, format, 0, 0) } + unsafe { + let iv = zxing_ImageView_new_checked( + data.as_ptr(), + data.len() as c_int, + Self::try_into_int(width)?, + Self::try_into_int(height)?, + format as zxing_ImageFormat, + 0, + 0, + ); + if iv.is_null() { + Err(last_error()) + } else { + Ok(ImageView(Rc::new(ImageViewOwner(iv, PhantomData)))) + } } } @@ -362,8 +377,7 @@ pub fn read_barcodes<'a>(image: impl TryInto>, opts: impl AsRef::default()) + Err(last_error()) } } } From e1dead79d57a4ab1b2c467edd8eee289fbd9f1a8 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 31 Jan 2024 12:23:13 +0100 Subject: [PATCH 089/431] dotnet: add minimal unit testing code --- wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs | 56 +++++++++++++++++++ .../ZXingCpp.Tests/ZXingCpp.Tests.csproj | 29 ++++++++++ wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 6 +- wrappers/dotnet/ZXingCppDemo/Program.cs | 2 +- wrappers/dotnet/dotnet.sln | 6 ++ 5 files changed, 95 insertions(+), 4 deletions(-) create mode 100644 wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs create mode 100644 wrappers/dotnet/ZXingCpp.Tests/ZXingCpp.Tests.csproj diff --git a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs new file mode 100644 index 0000000000..4cfd1eaf2d --- /dev/null +++ b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs @@ -0,0 +1,56 @@ +using Xunit; +using System.Text; +using ZXingCpp; + +namespace ZXingCpp.Tests; + +public class UnitTest1 +{ + [Fact] + public void ValidBarcodeFormatsParsing() + { + Assert.Equal(BarcodeFormats.QRCode, BarcodeReader.FormatsFromString("qrcode")); + Assert.Equal(BarcodeFormats.LinearCodes, BarcodeReader.FormatsFromString("linear_codes")); + Assert.Equal(BarcodeFormats.None, BarcodeReader.FormatsFromString("")); + } + + [Fact] + public void InvalidBarcodeFormatsParsing() + { + Assert.Throws(() => BarcodeReader.FormatsFromString("nope")); + } + + [Fact] + public void InvalidImageView() + { + Assert.Throws(() => new ImageView(new byte[0], 1, 1, ImageFormat.Lum)); + Assert.Throws(() => new ImageView(new byte[1], 1, 1, ImageFormat.Lum, 2)); + } + + [Fact] + public void Read() + { + var data = new List(); + foreach (var v in "0000101000101101011110111101011011101010100111011100101000100101110010100000") + data.Add((byte)(v == '0' ? 255 : 0)); + + var iv = new ImageView(data.ToArray(), data.Count, 1, ImageFormat.Lum); + var br = new BarcodeReader() { + Binarizer = Binarizer.BoolCast, + }; + var res = br.Read(iv); + + var expected = "96385074"; + + Assert.Single(res); + Assert.True(res[0].IsValid); + Assert.Equal(BarcodeFormats.EAN8, res[0].Format); + Assert.Equal(expected, res[0].Text); + Assert.Equal(Encoding.ASCII.GetBytes(expected), res[0].Bytes); + Assert.False(res[0].HasECI); + Assert.Equal(ContentType.Text, res[0].ContentType); + Assert.Equal(0, res[0].Orientation); + Assert.Equal(new PointI() { X = 4, Y = 0 }, res[0].Position.TopLeft); + Assert.Equal(1, res[0].LineCount); + } +} \ No newline at end of file diff --git a/wrappers/dotnet/ZXingCpp.Tests/ZXingCpp.Tests.csproj b/wrappers/dotnet/ZXingCpp.Tests/ZXingCpp.Tests.csproj new file mode 100644 index 0000000000..94b01c23c4 --- /dev/null +++ b/wrappers/dotnet/ZXingCpp.Tests/ZXingCpp.Tests.csproj @@ -0,0 +1,29 @@ + + + + net8.0 + enable + enable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 6a9f788d0e..6da5275451 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2024 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -170,12 +170,12 @@ public enum ImageFormat { public struct PointI { - public int x, y; + public int X, Y; }; public struct Position { - public PointI topLeft, topRight, bottomRight, bottomLeft; + public PointI TopLeft, TopRight, BottomRight, BottomLeft; public override string ToString() => MarshalAsString(zxing_PositionToString(this)); }; diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCppDemo/Program.cs index 9b978b0d76..c67b55dce2 100644 --- a/wrappers/dotnet/ZXingCppDemo/Program.cs +++ b/wrappers/dotnet/ZXingCppDemo/Program.cs @@ -1,4 +1,4 @@ -/* +/* * Copyright 2024 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 diff --git a/wrappers/dotnet/dotnet.sln b/wrappers/dotnet/dotnet.sln index bc94b4088f..a1927f302c 100644 --- a/wrappers/dotnet/dotnet.sln +++ b/wrappers/dotnet/dotnet.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp", "ZXingCpp\ZXingC EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCppDemo", "ZXingCppDemo\ZXingCppDemo.csproj", "{05C626FE-A5CE-4263-9419-35E44F14DB93}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp.Tests", "ZXingCpp.Tests\ZXingCpp.Tests.csproj", "{9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -24,5 +26,9 @@ Global {05C626FE-A5CE-4263-9419-35E44F14DB93}.Debug|Any CPU.Build.0 = Debug|Any CPU {05C626FE-A5CE-4263-9419-35E44F14DB93}.Release|Any CPU.ActiveCfg = Release|Any CPU {05C626FE-A5CE-4263-9419-35E44F14DB93}.Release|Any CPU.Build.0 = Release|Any CPU + {9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}.Debug|Any CPU.Build.0 = Debug|Any CPU + {9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}.Release|Any CPU.ActiveCfg = Release|Any CPU + {9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal From 15a4ccc3ef692d0d0e641722cddc7bf001b3f66c Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 1 Feb 2024 23:13:30 +0100 Subject: [PATCH 090/431] dotnet: rename ZXingCppDemo -> ZXingCpp.Demo --- wrappers/dotnet/README.md | 4 ++-- wrappers/dotnet/{ZXingCppDemo => ZXingCpp.Demo}/Program.cs | 0 .../ZXingCpp.Demo.csproj} | 0 wrappers/dotnet/dotnet.sln | 2 +- 4 files changed, 3 insertions(+), 3 deletions(-) rename wrappers/dotnet/{ZXingCppDemo => ZXingCpp.Demo}/Program.cs (100%) rename wrappers/dotnet/{ZXingCppDemo/ZXingCppDemo.csproj => ZXingCpp.Demo/ZXingCpp.Demo.csproj} (100%) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index e4f3693833..b70883d96a 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -49,11 +49,11 @@ public class Program } ``` -To run the `ZXingCppDemo` sample program, it is important that the dotnet runtime finds the native +To run the `ZXingCpp.Demo` sample program, it is important that the dotnet runtime finds the native `ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this ```sh -LD_LIBRARY_PATH= dotnet run --project ZXingCppDemo -- ../../test/samples/multi-1/1.png +LD_LIBRARY_PATH= dotnet run --project ZXingCpp.Demo -- ../../test/samples/multi-1/1.png ``` Note: This should currently be considered a pre-release. The API may change slightly to be even more diff --git a/wrappers/dotnet/ZXingCppDemo/Program.cs b/wrappers/dotnet/ZXingCpp.Demo/Program.cs similarity index 100% rename from wrappers/dotnet/ZXingCppDemo/Program.cs rename to wrappers/dotnet/ZXingCpp.Demo/Program.cs diff --git a/wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj similarity index 100% rename from wrappers/dotnet/ZXingCppDemo/ZXingCppDemo.csproj rename to wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj diff --git a/wrappers/dotnet/dotnet.sln b/wrappers/dotnet/dotnet.sln index a1927f302c..dbccc51a00 100644 --- a/wrappers/dotnet/dotnet.sln +++ b/wrappers/dotnet/dotnet.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 17.0.31903.59 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp", "ZXingCpp\ZXingCpp.csproj", "{64E71DE2-C1C6-4FCF-B8A6-FFC6D1605ECD}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCppDemo", "ZXingCppDemo\ZXingCppDemo.csproj", "{05C626FE-A5CE-4263-9419-35E44F14DB93}" +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp.Demo", "ZXingCpp.Demo\ZXingCpp.Demo.csproj", "{05C626FE-A5CE-4263-9419-35E44F14DB93}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ZXingCpp.Tests", "ZXingCpp.Tests\ZXingCpp.Tests.csproj", "{9D57E7B5-A24F-4F1D-8AF0-CED8FAE54B06}" EndProject From 7593619f4c89561527912101ec88755f5b602ee9 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 1 Feb 2024 23:14:59 +0100 Subject: [PATCH 091/431] dotnet: whitespace fixes in UnitTest1.cs --- wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs | 60 ++++++++++----------- 1 file changed, 30 insertions(+), 30 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs index 4cfd1eaf2d..0275290c94 100644 --- a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs +++ b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs @@ -6,38 +6,38 @@ namespace ZXingCpp.Tests; public class UnitTest1 { - [Fact] - public void ValidBarcodeFormatsParsing() - { - Assert.Equal(BarcodeFormats.QRCode, BarcodeReader.FormatsFromString("qrcode")); - Assert.Equal(BarcodeFormats.LinearCodes, BarcodeReader.FormatsFromString("linear_codes")); - Assert.Equal(BarcodeFormats.None, BarcodeReader.FormatsFromString("")); - } - - [Fact] - public void InvalidBarcodeFormatsParsing() - { - Assert.Throws(() => BarcodeReader.FormatsFromString("nope")); - } - - [Fact] - public void InvalidImageView() - { - Assert.Throws(() => new ImageView(new byte[0], 1, 1, ImageFormat.Lum)); - Assert.Throws(() => new ImageView(new byte[1], 1, 1, ImageFormat.Lum, 2)); - } - - [Fact] - public void Read() - { - var data = new List(); + [Fact] + public void ValidBarcodeFormatsParsing() + { + Assert.Equal(BarcodeFormats.QRCode, BarcodeReader.FormatsFromString("qrcode")); + Assert.Equal(BarcodeFormats.LinearCodes, BarcodeReader.FormatsFromString("linear_codes")); + Assert.Equal(BarcodeFormats.None, BarcodeReader.FormatsFromString("")); + } + + [Fact] + public void InvalidBarcodeFormatsParsing() + { + Assert.Throws(() => BarcodeReader.FormatsFromString("nope")); + } + + [Fact] + public void InvalidImageView() + { + Assert.Throws(() => new ImageView(new byte[0], 1, 1, ImageFormat.Lum)); + Assert.Throws(() => new ImageView(new byte[1], 1, 1, ImageFormat.Lum, 2)); + } + + [Fact] + public void Read() + { + var data = new List(); foreach (var v in "0000101000101101011110111101011011101010100111011100101000100101110010100000") - data.Add((byte)(v == '0' ? 255 : 0)); + data.Add((byte)(v == '0' ? 255 : 0)); - var iv = new ImageView(data.ToArray(), data.Count, 1, ImageFormat.Lum); + var iv = new ImageView(data.ToArray(), data.Count, 1, ImageFormat.Lum); var br = new BarcodeReader() { - Binarizer = Binarizer.BoolCast, - }; + Binarizer = Binarizer.BoolCast, + }; var res = br.Read(iv); var expected = "96385074"; @@ -52,5 +52,5 @@ public void Read() Assert.Equal(0, res[0].Orientation); Assert.Equal(new PointI() { X = 4, Y = 0 }, res[0].Position.TopLeft); Assert.Equal(1, res[0].LineCount); - } + } } \ No newline at end of file From 7823c9ef4464157d2c46812eef0ba582acbb67fb Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 1 Feb 2024 23:15:34 +0100 Subject: [PATCH 092/431] dotnet: bump version to 0.1.1-alpha --- wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj | 11 ++++++----- wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj index 28f5299710..704202a992 100644 --- a/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj +++ b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj @@ -1,15 +1,16 @@  - - - - - Exe net8.0 enable enable + false + + + + + diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj index c106284325..c5659c054f 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -6,7 +6,7 @@ enable ZXingCpp - 0.1.0-alpha + 0.1.1-alpha Axel Waggershauser zxing-cpp From c58920f78e0cc39181083ef2a65242b719fadd04 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 1 Feb 2024 23:17:19 +0100 Subject: [PATCH 093/431] rust: code cosmetic (drop outdated `+ Clone` constraint) --- wrappers/rust/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 5046a53332..0adea923a1 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -170,7 +170,7 @@ impl<'a> ImageView<'a> { } } - pub fn from_slice + Clone>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { + pub fn from_slice>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { unsafe { let iv = zxing_ImageView_new_checked( data.as_ptr(), From 4d73b1971be34549001e076c4c08da64e362fb7e Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 00:23:56 +0100 Subject: [PATCH 094/431] dotnet: add ImageView(IntPtr, ...) to support SkiaSharp.SKBitmap input Add SKBitmapBarcodeReader extension class to Demo Program.cs. --- wrappers/dotnet/README.md | 2 +- wrappers/dotnet/ZXingCpp.Demo/Program.cs | 32 ++++++++++++++++++- .../dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj | 6 +++- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 8 +++++ 4 files changed, 45 insertions(+), 3 deletions(-) diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index b70883d96a..4d0a6f4bc4 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -18,7 +18,7 @@ using System.Collections.Generic; using ImageMagick; using ZXingCpp; -public static class ImageMagickBarcodeReader +public static class MagickImageBarcodeReader { public static List Read(MagickImage img, ReaderOptions? opts = null) { diff --git a/wrappers/dotnet/ZXingCpp.Demo/Program.cs b/wrappers/dotnet/ZXingCpp.Demo/Program.cs index c67b55dce2..74323a5b11 100644 --- a/wrappers/dotnet/ZXingCpp.Demo/Program.cs +++ b/wrappers/dotnet/ZXingCpp.Demo/Program.cs @@ -5,9 +5,10 @@ using System.Collections.Generic; using ImageMagick; +using SkiaSharp; using ZXingCpp; -public static class ImageMagickBarcodeReader +public static class MagickImageBarcodeReader { public static List Read(MagickImage img, ReaderOptions? opts = null) { @@ -21,11 +22,40 @@ public static List Read(MagickImage img, ReaderOptions? opts = null) public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); } +public static class SkBitmapBarcodeReader +{ + public static List Read(SKBitmap img, ReaderOptions? opts = null) + { + var format = img.Info.ColorType switch + { + SKColorType.Gray8 => ImageFormat.Lum, + SKColorType.Rgba8888 => ImageFormat.RGBX, + SKColorType.Bgra8888 => ImageFormat.BGRX, + _ => ImageFormat.None, + }; + if (format == ImageFormat.None) + { + if (!img.CanCopyTo(SKColorType.Gray8)) + throw new Exception("Incompatible SKColorType"); + img = img.Copy(SKColorType.Gray8); + format = ImageFormat.Lum; + } + var iv = new ImageView(img.GetPixels(), img.Info.Width, img.Info.Height, format); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, SKBitmap img) => Read(img, reader); +} + public class Program { public static void Main(string[] args) { +#if false var img = new MagickImage(args[0]); +#else + var img = SKBitmap.Decode(args[0]); +#endif Console.WriteLine(img); var reader = new BarcodeReader() { diff --git a/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj index 704202a992..f6cbee8124 100644 --- a/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj +++ b/wrappers/dotnet/ZXingCpp.Demo/ZXingCpp.Demo.csproj @@ -9,8 +9,12 @@ - + + + + + diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 6da5275451..61545ceef3 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -47,6 +47,7 @@ internal class Dll [DllImport(DllName)] public static extern IntPtr zxing_PositionToString(Position position); [DllImport(DllName)] public static extern BarcodeFormats zxing_BarcodeFormatsFromString(string str); + [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new(IntPtr data, int width, int height, ImageFormat format, int rowStride, int pixStride); [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new_checked(byte[] data, int size, int width, int height, ImageFormat format, int rowStride, int pixStride); [DllImport(DllName)] public static extern void zxing_ImageView_delete(IntPtr iv); @@ -191,6 +192,13 @@ public ImageView(byte[] data, int width, int height, ImageFormat format, int row throw new Exception(MarshalAsString(zxing_LastErrorMsg())); } + public ImageView(IntPtr data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) + { + _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); + if (_d == IntPtr.Zero) + throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + } + ~ImageView() => zxing_ImageView_delete(_d); } From c580d1e0c0c53a58cea1789b5a9dd8e78b5a53e1 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 01:32:51 +0100 Subject: [PATCH 095/431] ZXingReader: add `-binarizer ` command line option --- example/ZXingReader.cpp | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 3e22294bc0..a8d9595d44 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -35,6 +35,8 @@ static void PrintUsage(const char* exePath) << " Only detect given format(s) (faster)\n" << " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n" << " -errors Include results with errors (like checksum error)\n" + << " -binarizer \n" + << " Binarizer to be used for gray to binary conversion\n" << " -mode \n" << " Text mode used to render the raw byte content into text\n" << " -1 Print only file name, content/error on one line per file/barcode (implies '-mode Escaped')\n" @@ -85,6 +87,17 @@ static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& o std::cerr << e.what() << "\n"; return false; } + } else if (is("-binarizer")) { + if (++i == argc) + return false; + else if (is("local")) + options.setBinarizer(Binarizer::LocalAverage); + else if (is("global")) + options.setBinarizer(Binarizer::GlobalHistogram); + else if (is("fixed")) + options.setBinarizer(Binarizer::FixedThreshold); + else + return false; } else if (is("-mode")) { if (++i == argc) return false; From e6fb84be4def35f58954e348e028eefa225a66c5 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 10:08:42 +0100 Subject: [PATCH 096/431] dotnet: split README into repo and NuGet versions --- wrappers/dotnet/README.md | 51 ++----------------- wrappers/dotnet/ZXingCpp/README.md | 64 ++++++++++++++++++++++++ wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 4 +- 3 files changed, 70 insertions(+), 49 deletions(-) create mode 100644 wrappers/dotnet/ZXingCpp/README.md diff --git a/wrappers/dotnet/README.md b/wrappers/dotnet/README.md index 4d0a6f4bc4..8ad1242dde 100644 --- a/wrappers/dotnet/README.md +++ b/wrappers/dotnet/README.md @@ -1,64 +1,21 @@ # ZXingCpp -ZXingCpp is a .NET wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). - -It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. -It was originally ported from the Java ZXing Library but has been developed further and now includes -many improvements in terms of runtime and detection performance. - -## Usage +ZXingCpp is a .NET wrapper for [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). There is a NuGet package available: https://www.nuget.org/packages/ZXingCpp. It does currently not yet contain the native binary dll file. That needs to be copied/build separately at the moment. -Simple example usage: - -```cs -using System.Collections.Generic; -using ImageMagick; -using ZXingCpp; - -public static class MagickImageBarcodeReader -{ - public static List Read(MagickImage img, ReaderOptions? opts = null) - { - if (img.DetermineBitDepth() < 8) - img.SetBitDepth(8); - var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); - return BarcodeReader.Read(iv, opts); - } - - public static List Read(this BarcodeReader reader, MagickImage img) => Read(img, reader); -} - -public class Program -{ - public static void Main(string[] args) - { - var img = new MagickImage(args[0]); +## Usage - var reader = new BarcodeReader() { - Formats = BarcodeReader.FormatsFromString(args[1]), - TryInvert = false, - }; - - foreach (var b in reader.Read(img)) - Console.WriteLine($"{b.Format} : {b.Text}"); - } -} -``` +See either the [ZXingCpp/README.md](ZXingCpp/README.md) or the [ZXingCpp.Demo](ZXingCpp.Demo) project. To run the `ZXingCpp.Demo` sample program, it is important that the dotnet runtime finds the native `ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this ```sh -LD_LIBRARY_PATH= dotnet run --project ZXingCpp.Demo -- ../../test/samples/multi-1/1.png +LD_LIBRARY_PATH= dotnet run --project ZXingCpp.Demo -- ../../test/samples/multi-1/1.png ``` -Note: This should currently be considered a pre-release. The API may change slightly to be even more -"managed" depending on community feedback. - ## Benchmarking To compare the performance of this .NET wrapper project with other available barcode scanner .NET libraries, diff --git a/wrappers/dotnet/ZXingCpp/README.md b/wrappers/dotnet/ZXingCpp/README.md new file mode 100644 index 0000000000..b9188a11b7 --- /dev/null +++ b/wrappers/dotnet/ZXingCpp/README.md @@ -0,0 +1,64 @@ +# ZXingCpp + +ZXingCpp is a .NET wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). + +It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. +It was originally ported from the Java ZXing Library but has been developed further and now includes +many improvements in terms of runtime and detection performance. + +## Usage + +```cs +using System.Collections.Generic; +using ImageMagick; +using ZXingCpp; + +// BarcodeReader extension class to support direct reading from MagickImage +public static class MagickImageBarcodeReader +{ + public static List Read(MagickImage img, ReaderOptions? opts = null) + { + if (img.DetermineBitDepth() < 8) + img.SetBitDepth(8); + var bytes = img.ToByteArray(MagickFormat.Gray); + var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); + return BarcodeReader.Read(iv, opts); + } + + public static List Read(this BarcodeReader reader, MagickImage img) + => Read(img, reader); +} + +public class Program +{ + public static void Main(string[] args) + { + var img = new MagickImage(args[0]); + + var reader = new BarcodeReader() { + Formats = BarcodeReader.FormatsFromString(args[1]), + TryInvert = false, + // see the ReaderOptions implementation for more available options + }; + + foreach (var b in reader.Read(img)) + Console.WriteLine($"{b.Format} : {b.Text}"); + } +} +``` + +To run the code above, it is important that the dotnet runtime finds the native +`ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this + +```sh +LD_LIBRARY_PATH= dotnet run -- +``` + +Note: This is an alpha release, meaning the API may still change slightly to feel even more +"managed" depending on community feedback. + +## Benchmarking + +To compare the performance of this .NET wrapper project with other available barcode scanner .NET libraries, +I started the project [zxing-bench](https://github.com/axxel/zxing-bench). +The [README](https://github.com/axxel/zxing-bench/blob/main/dotnet/README.md) contains a few results to get an idea. diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj index c5659c054f..6070b37b24 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -6,7 +6,7 @@ enable ZXingCpp - 0.1.1-alpha + 0.1.2-alpha Axel Waggershauser zxing-cpp @@ -21,7 +21,7 @@ - + From 66b47cc6996e7d01015653556e2b0e657da5cbf8 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 14:37:49 +0100 Subject: [PATCH 097/431] dotnet: improve README (simpler example, why ZXingCpp section) --- wrappers/dotnet/ZXingCpp/README.md | 71 ++++++++++++++++++------------ 1 file changed, 43 insertions(+), 28 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp/README.md b/wrappers/dotnet/ZXingCpp/README.md index b9188a11b7..f3a6795795 100644 --- a/wrappers/dotnet/ZXingCpp/README.md +++ b/wrappers/dotnet/ZXingCpp/README.md @@ -6,42 +6,27 @@ It is an open-source, multi-format linear/matrix barcode image processing librar It was originally ported from the Java ZXing Library but has been developed further and now includes many improvements in terms of runtime and detection performance. + ## Usage ```cs -using System.Collections.Generic; -using ImageMagick; +using SkiaSharp; using ZXingCpp; -// BarcodeReader extension class to support direct reading from MagickImage -public static class MagickImageBarcodeReader -{ - public static List Read(MagickImage img, ReaderOptions? opts = null) - { - if (img.DetermineBitDepth() < 8) - img.SetBitDepth(8); - var bytes = img.ToByteArray(MagickFormat.Gray); - var iv = new ImageView(bytes, img.Width, img.Height, ImageFormat.Lum); - return BarcodeReader.Read(iv, opts); - } - - public static List Read(this BarcodeReader reader, MagickImage img) - => Read(img, reader); -} - public class Program { public static void Main(string[] args) { - var img = new MagickImage(args[0]); + var img = SKBitmap.Decode(args[0]).Copy(SKColorType.Gray8); + var iv = new ImageView(img.GetPixels(), img.Info.Width, img.Info.Height, ImageFormat.Lum); var reader = new BarcodeReader() { - Formats = BarcodeReader.FormatsFromString(args[1]), + Formats = args.Length > 1 ? BarcodeReader.FormatsFromString(args[1]) : BarcodeFormats.Any, TryInvert = false, // see the ReaderOptions implementation for more available options }; - foreach (var b in reader.Read(img)) + foreach (var b in reader.Read(iv)) Console.WriteLine($"{b.Format} : {b.Text}"); } } @@ -51,14 +36,44 @@ To run the code above, it is important that the dotnet runtime finds the native `ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this ```sh -LD_LIBRARY_PATH= dotnet run -- +LD_LIBRARY_PATH= dotnet run -- [barcode-format-list] ``` -Note: This is an alpha release, meaning the API may still change slightly to feel even more -"managed" depending on community feedback. +Note: This is an alpha release, meaning the API may still change slightly to potentially feel even +more like a native C# library depending on community feedback. + + +## Why ZXingCpp? + +There are a number of areas where ZXingCpp shines compared to other popular .NET barcode scanner libraries. +The following comparison is with respect to the open source [ZXing.Net](https://www.nuget.org/packages/ZXing.Net) +and the commercial [Dynamsoft](https://www.nuget.org/packages/Dynamsoft.DotNet.Barcode) projects. + +### Performance + +To compare the performance of ZXingCpp with the other two libraries, I started the project +[zxing-bench](https://github.com/axxel/zxing-bench). +The [README](https://github.com/axxel/zxing-bench/blob/main/dotnet/README.md) contains a few details but to get +an idea: ZXingCpp is on average 2x-10x faster than Dynamsoft and 10x-50x faster than ZXing.Net. + +### Detection rate + +The benchmarking tool also showed that ZXingCpp has a superior detection rate compared to ZXing.Net while it is +sometimes better sometimes worse than the commercial Dynamsoft package, depending on the sample type and the +library configuration. The latter definitively supports more barcode formats compared to the two ZXing decendents. + +### Ease of use + +The sample program above shows the simplicitly of the API. The others are similar but seem a bit more +complicated with regards to setting parameters. + +### Standards support + +ZXingCpp has full support for binary data and ECI handling and provides a standards conforming `bytesECI()` +data that can be used to simulate a hardware/handheld barcode scanner. This seems not the case for ZXing.Net +and is unclear for Dynamsoft. -## Benchmarking +### License / costs -To compare the performance of this .NET wrapper project with other available barcode scanner .NET libraries, -I started the project [zxing-bench](https://github.com/axxel/zxing-bench). -The [README](https://github.com/axxel/zxing-bench/blob/main/dotnet/README.md) contains a few results to get an idea. +ZXingCpp has the liberal Apache-2.0 license and is free to use in commercial applications. That said, +I accept [donations](https://github.com/sponsors/axxel) and might be available for commercial consulting ;). From a12262c35673e0b5532ead40431790eb9f57b0af Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 2 Feb 2024 19:14:40 +0100 Subject: [PATCH 098/431] ci: major cleanup of shell/working-directory and env var usage * BUILD_SHARED_LIBS=ON (also on windows) * add Install and upload step for native builds --- .github/workflows/ci.yml | 79 ++++++++++++++++++---------------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index edd2a464b0..40db6ba466 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,36 +23,41 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v3 + - name: Setup Python 3 uses: actions/setup-python@v4 with: python-version: '3.x' - - name: Create Build Environment - # Some projects don't allow in-source building, so create a separate build directory - # We'll use this as our working directory for all subsequent commands - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - working-directory: ${{runner.workspace}}/build - # Use a bash shell so we can use the same syntax for environment variable - # access regardless of the host operating system - shell: bash - run: cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=$BUILD_TYPE -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=ON -DBUILD_C_API=ON + + - name: Configure + run: > + cmake -S . -B build -DCMAKE_BUILD_TYPE=${{env.BUILD_TYPE}} -DBUILD_SHARED_LIBS=ON + -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=ON -DBUILD_C_API=ON - name: Build - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute the build. You can specify a specific target with "--target " - run: cmake --build . -j8 --config $BUILD_TYPE + run: cmake --build build -j8 --config ${{env.BUILD_TYPE}} + + # - name: Set PATH for Tests + # shell: bash # to make the $GITHUB_PATH update work + # if: runner.os == 'Windows' + # run: | + # echo "${GITHUB_WORKSPACE}/build/core/${BUILD_TYPE}" >> $GITHUB_PATH + # echo "${GITHUB_WORKSPACE}/build/lib/${BUILD_TYPE}" >> $GITHUB_PATH - name: Test - working-directory: ${{runner.workspace}}/build - shell: bash - # Execute tests defined by the CMake configuration. - # See https://cmake.org/cmake/help/latest/manual/ctest.1.html for more detail - run: ctest -V -C $BUILD_TYPE + if: runner.os != 'Windows' # need to disable ctest on Windows when build as shared library for now + run: ctest --test-dir build -V -C ${{env.BUILD_TYPE}} + + - name: Install + run: | + cmake -E make_directory install + cmake --install build --config ${{env.BUILD_TYPE}} --prefix ${{github.workspace}}/install + + - uses: actions/upload-artifact@v4 + with: + name: ${{matrix.os}}-artifacts + path: install build-ubuntu-sanitize: runs-on: ubuntu-latest @@ -60,22 +65,19 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{github.ref}} - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - name: Configure run: > - cmake -S $GITHUB_WORKSPACE -B ${{runner.workspace}}/build -DCMAKE_BUILD_TYPE=RelWithDebInfo + cmake -S . -B build -DCMAKE_BUILD_TYPE=RelWithDebInfo -DBUILD_BLACKBOX_TESTS=ON -DBUILD_UNIT_TESTS=ON -DBUILD_PYTHON_MODULE=OFF -DBUILD_C_API=OFF -DCMAKE_CXX_COMPILER=clang++ -DCMAKE_CXX_FLAGS="-march=native -fsanitize=address,undefined -fno-optimize-sibling-calls -fsanitize-address-use-after-scope -fno-omit-frame-pointer" - name: Build - run: cmake --build ${{runner.workspace}}/build -j8 + run: cmake --build build -j8 - name: Test - working-directory: ${{runner.workspace}}/build - run: ctest -V + run: ctest -V --test-dir build build-ios: runs-on: macos-latest @@ -85,18 +87,13 @@ jobs: ref: ${{github.ref}} - name: Build the swift package - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}} run: swift build - name: Build the demo app - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/demo + working-directory: wrappers/ios/demo run: xcodebuild build -scheme demo -sdk "iphonesimulator" - name: Validate the Pod - shell: sh - working-directory: ${{runner.workspace}}/${{github.event.repository.name}} run: pod lib lint --allow-warnings build-android: @@ -202,22 +199,18 @@ jobs: steps: - uses: actions/checkout@v3 - - name: Create Build Environment - run: cmake -E make_directory ${{runner.workspace}}/build - - - name: Configure CMake - shell: cmd + - name: Configure + shell: cmd # powershell messes up the arguments containing a '.' ?!? run: > - cmake -S ${{github.workspace}}/wrappers/winrt -B ${{runner.workspace}}/build -A ARM64 + cmake -S wrappers/winrt -B build -A ARM64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DCMAKE_BUILD_TYPE=Release - -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF + -DBUILD_WINRT_LIB=ON -DBUILD_EXAMPLES=OFF -DBUILD_BLACKBOX_TESTS=OFF -DBUILD_C_API=OFF -DEXTENSION_SDK_OUTPUT=dist/UAP/v0.8.0.0/ExtensionSDKs/ZXingWinRT/1.0.0.0 - name: Build - shell: cmd - run: cmake --build ${{runner.workspace}}/build -j8 --config Release + run: cmake --build build -j8 --config Release - uses: actions/upload-artifact@v3 with: name: winrt-ARM64-artifacts - path: ${{runner.workspace}}/build/dist + path: build/dist From 604c4d3206a76bd0c4d0608ed902e0323072e0aa Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 3 Feb 2024 02:34:45 +0100 Subject: [PATCH 099/431] dotnet: add x64 runtime assets to NuGet package and bump version --- wrappers/dotnet/.gitignore | 3 +++ wrappers/dotnet/ZXingCpp/README.md | 14 +++++++++----- wrappers/dotnet/ZXingCpp/ZXingCpp.csproj | 7 +++++-- 3 files changed, 17 insertions(+), 7 deletions(-) diff --git a/wrappers/dotnet/.gitignore b/wrappers/dotnet/.gitignore index 75028b44d1..0069337825 100644 --- a/wrappers/dotnet/.gitignore +++ b/wrappers/dotnet/.gitignore @@ -3,6 +3,9 @@ ## ## Get latest from `dotnet new gitignore` +# hand selected runtime/dll files +runtimes + # dotenv files .env diff --git a/wrappers/dotnet/ZXingCpp/README.md b/wrappers/dotnet/ZXingCpp/README.md index f3a6795795..2a12b75c2d 100644 --- a/wrappers/dotnet/ZXingCpp/README.md +++ b/wrappers/dotnet/ZXingCpp/README.md @@ -3,7 +3,7 @@ ZXingCpp is a .NET wrapper for the C++ library [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp). It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. -It was originally ported from the Java ZXing Library but has been developed further and now includes +It was originally ported from the Java ZXing library but has been developed further and now includes many improvements in terms of runtime and detection performance. @@ -32,13 +32,17 @@ public class Program } ``` -To run the code above, it is important that the dotnet runtime finds the native -`ZXing[.dll|.so|.dylib]` in your path. E.g. on Linux a complete command line would look like this - +Executing this sample code from the command line would look like this: ```sh -LD_LIBRARY_PATH= dotnet run -- [barcode-format-list] +dotnet run -- [barcode-format-list] ``` +The NuGet package includes the runtime/native c++ libraries for the x64 architecture on +Windows, Linux and macOS. If something is not working out of the box or you need arm64 support +then you need to build the `[lib]ZXing[.dll|.so|.dylib]` file yourself and make sure the .NET +runtime find it (see e.g. the environment variables `LD_LIBRARY_PATH` on Linux or `PATH` on +Windows). + Note: This is an alpha release, meaning the API may still change slightly to potentially feel even more like a native C# library depending on community feedback. diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj index 6070b37b24..f55757cf37 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.csproj @@ -6,13 +6,13 @@ enable ZXingCpp - 0.1.2-alpha + 0.1.3-alpha Axel Waggershauser zxing-cpp ZXingCpp is a .NET wrapper for the C++ library zxing-cpp. It is an open-source, multi-format linear/matrix barcode image processing library implemented in C++. - It was originally ported from the Java ZXing Library but has been developed further and now includes + It was originally ported from the Java ZXing library but has been developed further and now includes many improvements in terms of runtime and detection performance. https://github.com/zxing-cpp/zxing-cpp README.md @@ -22,6 +22,9 @@ + + + From a49f2e084ef82a4652c2c6b58afc9ac913a88b4d Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 4 Feb 2024 16:23:06 +0100 Subject: [PATCH 100/431] c++: remove unused #include statements (and one dead file) --- core/CMakeLists.txt | 1 - core/src/BitMatrix.cpp | 1 - core/src/GlobalHistogramBinarizer.cpp | 2 -- core/src/HybridBinarizer.cpp | 3 +-- core/src/Result.cpp | 1 - core/src/aztec/AZDecoder.cpp | 1 - core/src/aztec/AZDetector.cpp | 1 - core/src/aztec/AZReader.cpp | 1 - core/src/datamatrix/DMDecoder.cpp | 1 - core/src/datamatrix/DMDetector.cpp | 1 - core/src/maxicode/MCReader.cpp | 1 - core/src/oned/ODCode128Reader.cpp | 2 -- core/src/oned/ODCode39Reader.cpp | 1 - core/src/oned/ODDataBarCommon.cpp | 1 + core/src/oned/ODDataBarExpandedBitDecoder.cpp | 2 -- core/src/oned/ODITFReader.cpp | 2 -- core/src/oned/ODRowReader.cpp | 17 ----------------- core/src/pdf417/PDFReader.cpp | 2 +- core/src/qrcode/QRCodecMode.cpp | 1 - core/src/qrcode/QRFormatInformation.cpp | 2 -- test/blackbox/TestReaderMain.cpp | 1 - 21 files changed, 3 insertions(+), 42 deletions(-) delete mode 100644 core/src/oned/ODRowReader.cpp diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 77cc1f9763..d63d9ecc63 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -300,7 +300,6 @@ if (BUILD_READERS) src/oned/ODReader.h src/oned/ODReader.cpp src/oned/ODRowReader.h - src/oned/ODRowReader.cpp ) endif() if (BUILD_WRITERS) diff --git a/core/src/BitMatrix.cpp b/core/src/BitMatrix.cpp index 729a5752b3..f03cde7471 100644 --- a/core/src/BitMatrix.cpp +++ b/core/src/BitMatrix.cpp @@ -173,4 +173,3 @@ BitMatrix Deflate(const BitMatrix& input, int width, int height, float top, floa } } // ZXing - diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 44ca15810c..351118492c 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -11,8 +11,6 @@ #include #include -#include -#include #include namespace ZXing { diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index a74fbd2872..68506fc335 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -9,10 +9,9 @@ #include "BitMatrix.h" #include "Matrix.h" +#include #include #include -#include -#include namespace ZXing { diff --git a/core/src/Result.cpp b/core/src/Result.cpp index 67b38e27fa..c637811a25 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -7,7 +7,6 @@ #include "Result.h" #include "DecoderResult.h" -#include "TextDecoder.h" #include "ZXAlgorithms.h" #include diff --git a/core/src/aztec/AZDecoder.cpp b/core/src/aztec/AZDecoder.cpp index 7ab6f6a84f..c33324afcf 100644 --- a/core/src/aztec/AZDecoder.cpp +++ b/core/src/aztec/AZDecoder.cpp @@ -10,7 +10,6 @@ #include "AZDetectorResult.h" #include "BitArray.h" #include "BitMatrix.h" -#include "CharacterSet.h" #include "DecoderResult.h" #include "GenericGF.h" #include "ReedSolomonDecoder.h" diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 500c181c5a..bd528d2b64 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -20,7 +20,6 @@ #include "ZXAlgorithms.h" #include -#include #include #include diff --git a/core/src/aztec/AZReader.cpp b/core/src/aztec/AZReader.cpp index b9b36a7ba6..1d70b19b3c 100644 --- a/core/src/aztec/AZReader.cpp +++ b/core/src/aztec/AZReader.cpp @@ -15,7 +15,6 @@ #include "DecoderResult.h" #include "Result.h" -#include #include namespace ZXing::Aztec { diff --git a/core/src/datamatrix/DMDecoder.cpp b/core/src/datamatrix/DMDecoder.cpp index af3b9c5018..34a01d39db 100644 --- a/core/src/datamatrix/DMDecoder.cpp +++ b/core/src/datamatrix/DMDecoder.cpp @@ -8,7 +8,6 @@ #include "BitMatrix.h" #include "BitSource.h" -#include "CharacterSet.h" #include "DMBitLayout.h" #include "DMDataBlock.h" #include "DMVersion.h" diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index ce8d15fde6..2d8773b505 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -25,7 +25,6 @@ #include #include #include -#include #include #include diff --git a/core/src/maxicode/MCReader.cpp b/core/src/maxicode/MCReader.cpp index 66f25aa3f8..8a70952fae 100644 --- a/core/src/maxicode/MCReader.cpp +++ b/core/src/maxicode/MCReader.cpp @@ -8,7 +8,6 @@ #include "BinaryBitmap.h" #include "BitMatrix.h" -#include "ReaderOptions.h" #include "DecoderResult.h" #include "MCBitMatrixParser.h" #include "MCDecoder.h" diff --git a/core/src/oned/ODCode128Reader.cpp b/core/src/oned/ODCode128Reader.cpp index 9d0927eab6..d40b53d9b3 100644 --- a/core/src/oned/ODCode128Reader.cpp +++ b/core/src/oned/ODCode128Reader.cpp @@ -13,9 +13,7 @@ #include #include #include -#include #include -#include #include namespace ZXing::OneD { diff --git a/core/src/oned/ODCode39Reader.cpp b/core/src/oned/ODCode39Reader.cpp index e102fee353..d829ac3e97 100644 --- a/core/src/oned/ODCode39Reader.cpp +++ b/core/src/oned/ODCode39Reader.cpp @@ -7,7 +7,6 @@ #include "ODCode39Reader.h" #include "ReaderOptions.h" -#include "DecoderResult.h" #include "Result.h" #include "ZXAlgorithms.h" diff --git a/core/src/oned/ODDataBarCommon.cpp b/core/src/oned/ODDataBarCommon.cpp index 937f350005..2b85607428 100644 --- a/core/src/oned/ODDataBarCommon.cpp +++ b/core/src/oned/ODDataBarCommon.cpp @@ -4,6 +4,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "ODDataBarCommon.h" + #include namespace ZXing::OneD::DataBar { diff --git a/core/src/oned/ODDataBarExpandedBitDecoder.cpp b/core/src/oned/ODDataBarExpandedBitDecoder.cpp index aa0484769e..165cd32f93 100644 --- a/core/src/oned/ODDataBarExpandedBitDecoder.cpp +++ b/core/src/oned/ODDataBarExpandedBitDecoder.cpp @@ -11,8 +11,6 @@ #include "Error.h" #include "GTIN.h" -#include - namespace ZXing::OneD::DataBar { constexpr char GS = 29; // FNC1 diff --git a/core/src/oned/ODITFReader.cpp b/core/src/oned/ODITFReader.cpp index b6d75aa7b4..6e699e882d 100644 --- a/core/src/oned/ODITFReader.cpp +++ b/core/src/oned/ODITFReader.cpp @@ -11,8 +11,6 @@ #include "Result.h" #include "ZXAlgorithms.h" -#include - namespace ZXing::OneD { constexpr auto START_PATTERN_ = FixedPattern<4, 4>{1, 1, 1, 1}; diff --git a/core/src/oned/ODRowReader.cpp b/core/src/oned/ODRowReader.cpp deleted file mode 100644 index c8454dbf4b..0000000000 --- a/core/src/oned/ODRowReader.cpp +++ /dev/null @@ -1,17 +0,0 @@ -/* -* Copyright 2020 Axel Waggershauser -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "ODRowReader.h" - -#include "BitArray.h" -#include "Result.h" - -#include - -namespace ZXing::OneD { - - - -} // namespace ZXing::OneD diff --git a/core/src/pdf417/PDFReader.cpp b/core/src/pdf417/PDFReader.cpp index 27d943c5e0..2282e3be32 100644 --- a/core/src/pdf417/PDFReader.cpp +++ b/core/src/pdf417/PDFReader.cpp @@ -26,7 +26,7 @@ #ifdef PRINT_DEBUG #include "BitMatrixIO.h" -#include +#include #endif namespace ZXing { diff --git a/core/src/qrcode/QRCodecMode.cpp b/core/src/qrcode/QRCodecMode.cpp index 5020118a87..b0e546a013 100644 --- a/core/src/qrcode/QRCodecMode.cpp +++ b/core/src/qrcode/QRCodecMode.cpp @@ -12,7 +12,6 @@ #include "ZXAlgorithms.h" #include -#include namespace ZXing::QRCode { diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index 85c88d766b..9eec6b81de 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -11,8 +11,6 @@ #include "BitHacks.h" #include "ZXAlgorithms.h" -#include - namespace ZXing::QRCode { static uint32_t MirrorBits(uint32_t bits) diff --git a/test/blackbox/TestReaderMain.cpp b/test/blackbox/TestReaderMain.cpp index 5a69c6143c..b129810ac2 100644 --- a/test/blackbox/TestReaderMain.cpp +++ b/test/blackbox/TestReaderMain.cpp @@ -8,7 +8,6 @@ #include "ImageLoader.h" #include "ReadBarcode.h" #include "ZXAlgorithms.h" -#include "ZXFilesystem.h" #include #include From 8083f99da4d36fdf36bc45351512cd068ca137f0 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:33:55 +0100 Subject: [PATCH 101/431] GlobalHistogramBinarizer: comment on failed performance tuning experiment --- core/src/GlobalHistogramBinarizer.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 351118492c..5e0e245b50 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -46,6 +46,8 @@ static void ThresholdSharpened(const ImageLineView in, int threshold, std::vecto static auto GenHistogram(const ImageLineView line) { + // This code causes about 20% of the total runtime on an AVX2 system for a EAN13 search on Lum input data. + // Trying to increase the performance by performing 2 or 4 "parallel" histograms helped nothing. Histogram res = {}; for (auto pix : line) res[pix >> LUMINANCE_SHIFT]++; From 85d8acb5c86ff8f7f403b85d7897d16047ac4b4a Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:34:47 +0100 Subject: [PATCH 102/431] GridSampler: add disabled experiment for 3x3 sampling instead of 1x1 --- core/src/GridSampler.cpp | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/core/src/GridSampler.cpp b/core/src/GridSampler.cpp index 8b5cf9b336..626ce025e3 100644 --- a/core/src/GridSampler.cpp +++ b/core/src/GridSampler.cpp @@ -56,7 +56,15 @@ DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const R #ifdef PRINT_DEBUG log(p, 3); #endif +#if 0 + int sum = 0; + for (int dy = -1; dy <= 1; ++dy) + for (int dx = -1; dx <= 1; ++dx) + sum += image.get(p + PointF(dx, dy)); + if (sum >= 5) +#else if (image.get(p)) +#endif res.set(x, y); } } From 676f2ffca08f7efccd60abf90eec6034c1ed9852 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:36:32 +0100 Subject: [PATCH 103/431] RegressionLine: fflush(stdout) debugging printf --- core/src/RegressionLine.h | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index c536a8e475..6de1d4863f 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -123,6 +123,7 @@ class RegressionLine break; #ifdef PRINT_DEBUG printf("removed %zu points -> %zu remaining\n", old_points_size - points.size(), points.size()); + fflush(stdout); #endif ret = evaluate(points); } From c969dacdc9d458d8d28adee87bdabba9eba8742d Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:47:45 +0100 Subject: [PATCH 104/431] ConcentricFinder: add disabled code discarding non-circle-like loops --- core/src/ConcentricFinder.cpp | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index c21a6455e3..e21f65e664 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -39,6 +39,24 @@ std::optional CenterOfDoubleCross(const BitMatrix& image, PointI center, std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle) { +#if 0 + if (requireCircle) { + // alternative implementation with the aim of discarding closed loops that are not all circle like (M > 5*m) + auto points = CollectRingPoints(image, center, range, std::abs(nth), nth < 0); + if (points.empty()) + return {}; + auto res = Reduce(points, PointF{}, std::plus{}) / Size(points); + + double m = range, M = 0; + for (auto p : points) + UpdateMinMax(m, M, maxAbsComponent(p - res)); + + if (M > 5 * m) + return {}; + + return res; + } +#endif // range is the approximate width/height of the nth ring, if nth>1 then it would be plausible to limit the search radius // to approximately range / 2 * sqrt(2) == range * 0.75 but it turned out to be too limiting with realworld/noisy data. int radius = range; From acf6966bdf0cb98068265aa102de335f174b0ce2 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 5 Feb 2024 01:50:56 +0100 Subject: [PATCH 105/431] Pattern: add disabled alternative NormalizedPattern implementation --- core/src/Pattern.h | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 3a587010e2..e7eb2baad1 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -299,6 +299,7 @@ template std::array NormalizedPattern(const PatternView& view) { float moduleSize = static_cast(view.sum(LEN)) / SUM; +#if 1 int err = SUM; std::array is; std::array rs; @@ -318,7 +319,25 @@ std::array NormalizedPattern(const PatternView& view) is[mi] += err; rs[mi] -= err; } +#else + std::array is, e2e; + int min_v = view[0], min_i = 0; + + for (int i = 1; i < LEN; i++) { + float v = (view[i - 1] + view[i]) / moduleSize; + e2e[i] = int(v + .5f); + if (view[i] < min_v) { + min_v = view[i]; + min_i = i; + } + } + is[min_i] = 1; + for (int i = min_i + 1; i < LEN; ++i) + is[i] = e2e[i] - is[i - 1]; + for (int i = min_i - 1; i >= 0; --i) + is[i] = e2e[i + 1] - is[i + 1]; +#endif return is; } From 6b9f16a79d1965197fb9da4a733090c1efc6c346 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 6 Feb 2024 14:57:49 +0100 Subject: [PATCH 106/431] HybridBinarizer: re-implement with symmetric threshold interpolation This carefully crafted auto-vectorizable alternative to the original implementation is practically as fast but fixes the general issue with inverted symbols and the assumption in the old implementation that the background is bright by default. This is a direct fix for #709. It looses 8 of the current black box tests but gains 18 new ones, plus those like in #709. I left the old implementation in the code easy before/after comparisons in case new issues should be reported. --- core/src/HybridBinarizer.cpp | 179 +++++++++++++++++++++++---- core/src/Matrix.h | 8 ++ test/blackbox/BlackboxTestRunner.cpp | 22 ++-- test/samples/qrcode-2/#709.jpg | Bin 0 -> 12918 bytes test/samples/qrcode-2/#709.txt | 1 + 5 files changed, 178 insertions(+), 32 deletions(-) create mode 100644 test/samples/qrcode-2/#709.jpg create mode 100644 test/samples/qrcode-2/#709.txt diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 68506fc335..bf6baf027b 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -11,14 +11,17 @@ #include #include +#include #include +#define USE_NEW_ALGORITHM + namespace ZXing { // This class uses 5x5 blocks to compute local luminance, where each block is 8x8 pixels. // So this is the smallest dimension in each axis we can accept. static constexpr int BLOCK_SIZE = 8; -static constexpr int MINIMUM_DIMENSION = BLOCK_SIZE * 5; +static constexpr int WINDOW_SIZE = BLOCK_SIZE * (1 + 2 * 2); static constexpr int MIN_DYNAMIC_RANGE = 24; HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer(iv) {} @@ -42,15 +45,34 @@ bool HybridBinarizer::getPatternRow(int row, int rotation, PatternRow& res) cons #endif } +using T_t = uint8_t; + +/** +* Applies a single threshold to a block of pixels. +*/ +static void ThresholdBlock(const uint8_t* __restrict luminances, int xoffset, int yoffset, int threshold, int rowStride, + BitMatrix& matrix) +{ + for (int y = yoffset; y < yoffset + BLOCK_SIZE; ++y) { + auto* src = luminances + y * rowStride + xoffset; + auto* const dstBegin = matrix.row(y).begin() + xoffset; + // TODO: fix pixelStride > 1 case + for (auto* dst = dstBegin; dst < dstBegin + BLOCK_SIZE; ++dst, ++src) + *dst = (*src <= threshold) * BitMatrix::SET_V; + } +} + +#ifndef USE_NEW_ALGORITHM + /** * Calculates a single black point for each block of pixels and saves it away. * See the following thread for a discussion of this algorithm: * http://groups.google.com/group/zxing/browse_thread/thread/d06efa2c35a7ddc0 */ -static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, +static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, int rowStride) { - Matrix blackPoints(subWidth, subHeight); + Matrix blackPoints(subWidth, subHeight); for (int y = 0; y < subHeight; y++) { int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); @@ -111,32 +133,21 @@ static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, in return blackPoints; } - -/** -* Applies a single threshold to a block of pixels. -*/ -static void ThresholdBlock(const uint8_t* __restrict luminances, int xoffset, int yoffset, int threshold, int rowStride, - BitMatrix& matrix) -{ - for (int y = yoffset; y < yoffset + BLOCK_SIZE; ++y) { - auto* src = luminances + y * rowStride + xoffset; - auto* const dstBegin = matrix.row(y).begin() + xoffset; - // TODO: fix pixelStride > 1 case - for (auto* dst = dstBegin; dst < dstBegin + BLOCK_SIZE; ++dst, ++src) - *dst = (*src <= threshold) * BitMatrix::SET_V; - } -} - /** * For each block in the image, calculate the average black point using a 5x5 grid * of the blocks around it. Also handles the corner cases (fractional blocks are computed based * on the last pixels in the row/column which are also used in the previous block). */ static std::shared_ptr CalculateMatrix(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, - int height, int rowStride, const Matrix& blackPoints) + int height, int rowStride, const Matrix& blackPoints) { auto matrix = std::make_shared(width, height); +#ifndef NDEBUG + Matrix out(width, height); + Matrix out2(width, height); +#endif + for (int y = 0; y < subHeight; y++) { int yoffset = std::min(y * BLOCK_SIZE, height - BLOCK_SIZE); for (int x = 0; x < subWidth; x++) { @@ -151,15 +162,140 @@ static std::shared_ptr CalculateMatrix(const uint8_t* __restrict lumi } int average = sum / 25; ThresholdBlock(luminances, xoffset, yoffset, average, rowStride, *matrix); + +#ifndef NDEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) { + out.set(xoffset + xx, yoffset + yy, blackPoints(x, y)); + out2.set(xoffset + xx, yoffset + yy, average); + } +#endif + } + } + +#ifndef NDEBUG + std::ofstream file("thresholds.pnm"); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); + std::ofstream file2("thresholds_avg.pnm"); + file2 << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file2.write(reinterpret_cast(out2.data()), out2.size()); +#endif + + return matrix; +} + +#else + +// Subdivide the image in blocks of BLOCK_SIZE and calculate one treshold value per block as +// (max - min > MIN_DYNAMIC_RANGE) ? (max + min) / 2 : 0 +static Matrix BlockThresholds(const ImageView iv) +{ + int subWidth = (iv.width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS) + int subHeight = (iv.height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS) + + Matrix thresholds(subWidth, subHeight); + + for (int y = 0; y < subHeight; y++) { + int y0 = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE); + for (int x = 0; x < subWidth; x++) { + int x0 = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE); + uint8_t min = 255; + uint8_t max = 0; + for (int yy = 0; yy < BLOCK_SIZE; yy++) { + auto line = iv.data(x0, y0 + yy); + for (int xx = 0; xx < BLOCK_SIZE; xx++) + UpdateMinMax(min, max, line[xx]); + } + + thresholds(x, y) = (max - min > MIN_DYNAMIC_RANGE) ? (int(max) + min) / 2 : 0; } } + return thresholds; +} + +// Apply gaussian-like smoothing filter over all non-zero thresholds and fill any remainig gaps with nearest neighbor +static Matrix SmoothThresholds(Matrix&& in) +{ + Matrix out(in.width(), in.height()); + + constexpr int R = WINDOW_SIZE / BLOCK_SIZE / 2; + for (int y = 0; y < in.height(); y++) { + for (int x = 0; x < in.width(); x++) { + int left = std::clamp(x, R, in.width() - R - 1); + int top = std::clamp(y, R, in.height() - R - 1); + + int sum = in(x, y) * 2; + int n = (sum > 0) * 2; + auto add = [&](int x, int y) { + int t = in(x, y); + sum += t; + n += t > 0; + }; + + for (int dy = -R; dy <= R; ++dy) + for (int dx = -R; dx <= R; ++dx) + add(left + dx, top + dy); + + out(x, y) = n > 0 ? sum / n : 0; + } + } + + // flood fill any remaing gaps of (very large) no-contrast regions + auto last = out.begin() - 1; + for (auto* i = out.begin(); i != out.end(); ++i) { + if (*i) { + if (last != i - 1) + std::fill(last + 1, i, *i); + last = i; + } + } + std::fill(last + 1, out.end(), *last); + + return out; +} + +static std::shared_ptr ThresholdImage(const ImageView iv, const Matrix& thresholds) +{ + auto matrix = std::make_shared(iv.width(), iv.height()); + +#ifndef NDEBUG + Matrix out(iv.width(), iv.height()); +#endif + + for (int y = 0; y < thresholds.height(); y++) { + int yoffset = std::min(y * BLOCK_SIZE, iv.height() - BLOCK_SIZE); + for (int x = 0; x < thresholds.width(); x++) { + int xoffset = std::min(x * BLOCK_SIZE, iv.width() - BLOCK_SIZE); + ThresholdBlock(iv.data(0, 0), xoffset, yoffset, thresholds(x, y), iv.rowStride(), *matrix); + +#ifndef NDEBUG + for (int yy = 0; yy < 8; ++yy) + for (int xx = 0; xx < 8; ++xx) + out.set(xoffset + xx, yoffset + yy, thresholds(x, y)); +#endif + } + } + +#ifndef NDEBUG + std::ofstream file("thresholds_new.pnm"); + file << "P5\n" << out.width() << ' ' << out.height() << "\n255\n"; + file.write(reinterpret_cast(out.data()), out.size()); +#endif + return matrix; } +#endif + std::shared_ptr HybridBinarizer::getBlackMatrix() const { - if (width() >= MINIMUM_DIMENSION && height() >= MINIMUM_DIMENSION) { + if (width() >= WINDOW_SIZE && height() >= WINDOW_SIZE) { +#ifdef USE_NEW_ALGORITHM + auto thrs = SmoothThresholds(BlockThresholds(_buffer)); + return ThresholdImage(_buffer, thrs); +#else const uint8_t* luminances = _buffer.data(0, 0); int subWidth = (width() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(width/BS) int subHeight = (height() + BLOCK_SIZE - 1) / BLOCK_SIZE; // ceil(height/BS) @@ -167,6 +303,7 @@ std::shared_ptr HybridBinarizer::getBlackMatrix() const CalculateBlackPoints(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride()); return CalculateMatrix(luminances, subWidth, subHeight, width(), height(), _buffer.rowStride(), blackPoints); +#endif } else { // If the image is too small, fall back to the global histogram approach. return GlobalHistogramBinarizer::getBlackMatrix(); diff --git a/core/src/Matrix.h b/core/src/Matrix.h index 8e6cbbdc90..b6f8bc2150 100644 --- a/core/src/Matrix.h +++ b/core/src/Matrix.h @@ -102,6 +102,14 @@ class Matrix return _data.data() + _width * _height; } + value_t* begin() { + return _data.data(); + } + + value_t* end() { + return _data.data() + _width * _height; + } + void clear(value_t value = {}) { std::fill(_data.begin(), _data.end(), value); } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 27385e34da..c75fdc63af 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -569,11 +569,11 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 16, 16, 270 }, }); - runTests("qrcode-2", "QRCode", 50, { - { 46, 48, 0 }, - { 46, 48, 90 }, - { 46, 48, 180 }, - { 46, 48, 270 }, + runTests("qrcode-2", "QRCode", 51, { + { 45, 48, 0 }, + { 45, 48, 90 }, + { 45, 48, 180 }, + { 45, 48, 270 }, { 22, 1, pure }, // the misread is the 'outer' symbol in 16.png }); @@ -581,14 +581,14 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 28, 28, 0 }, { 28, 28, 90 }, { 28, 28, 180 }, - { 27, 27, 270 }, + { 28, 28, 270 }, }); runTests("qrcode-4", "QRCode", 41, { - { 29, 29, 0 }, - { 29, 29, 90 }, - { 29, 29, 180 }, - { 29, 29, 270 }, + { 31, 31, 0 }, + { 31, 31, 90 }, + { 31, 31, 180 }, + { 31, 31, 270 }, }); runTests("qrcode-5", "QRCode", 16, { @@ -612,7 +612,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("microqrcode-1", "MicroQRCode", 16, { { 15, 15, 0 }, - { 15, 15, 90 }, + { 14, 14, 90 }, { 15, 15, 180 }, { 15, 15, 270 }, { 9, 0, pure }, diff --git a/test/samples/qrcode-2/#709.jpg b/test/samples/qrcode-2/#709.jpg new file mode 100644 index 0000000000000000000000000000000000000000..1814946889095419450852397ca0057dc4c3bdac GIT binary patch literal 12918 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 literal 0 HcmV?d00001 diff --git a/test/samples/qrcode-2/#709.txt b/test/samples/qrcode-2/#709.txt new file mode 100644 index 0000000000..064afa604a --- /dev/null +++ b/test/samples/qrcode-2/#709.txt @@ -0,0 +1 @@ +https://u.wechat.com/ELaSWShWyTFQvXJkp9vCgFA \ No newline at end of file From 89cac928c62c3fe833504dcef2e7f1af53b0786b Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 6 Feb 2024 15:00:26 +0100 Subject: [PATCH 107/431] ReadBarcode: add check for downscaleFactor --- core/src/ReadBarcode.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 5c371adf1e..ab8108c1ba 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -84,6 +84,9 @@ class LumImagePyramid LumImagePyramid(const ImageView& iv, int threshold, int factor) { + if (factor < 2) + throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor"); + layers.push_back(iv); // TODO: if only matrix codes were considered, then using std::min would be sufficient (see #425) while (threshold > 0 && std::max(layers.back().width(), layers.back().height()) > threshold && From f14aaca4f83215be51a1799a4a6d00b8dc7281a8 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 6 Feb 2024 17:38:04 +0100 Subject: [PATCH 108/431] HybridBinarizer: fix buffer overflow regression --- core/src/HybridBinarizer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index bf6baf027b..0214ce1782 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -251,7 +251,7 @@ static Matrix SmoothThresholds(Matrix&& in) last = i; } } - std::fill(last + 1, out.end(), *last); + std::fill(last + 1, out.end(), *(std::max(last, out.begin()))); return out; } From e107516f8accea34dc3e4574b88186f19b06417c Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 6 Feb 2024 18:35:48 +0100 Subject: [PATCH 109/431] tests: disable a spurious/platform dependent black box test result This is at least annoying and likely related to the recent HybridBinarizer reimplementation. Non-determinism via auto-vectorizer. sigh... --- test/blackbox/BlackboxTestRunner.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index c75fdc63af..f20421eb5f 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -613,7 +613,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("microqrcode-1", "MicroQRCode", 16, { { 15, 15, 0 }, { 14, 14, 90 }, - { 15, 15, 180 }, + { 14, 14, 180 }, // ughs: 1 result is platform/compiler dependent (e.g. -march=core2 vs. haswell) { 15, 15, 270 }, { 9, 0, pure }, }); From e01f2d4f8f5550f63f25e236be7058327895ecc7 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 7 Feb 2024 00:28:17 +0100 Subject: [PATCH 110/431] ZXingReader: add `-single` option to `setMaxNumberOfSymbols(1)` --- example/ZXingReader.cpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index a8d9595d44..724c3881ae 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -33,6 +33,7 @@ static void PrintUsage(const char* exePath) << " -noscale Don't try downscaled images during detection (faster)\n" << " -format \n" << " Only detect given format(s) (faster)\n" + << " -single Stop after the first barcode is detected (faster)\n" << " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n" << " -errors Include results with errors (like checksum error)\n" << " -binarizer \n" @@ -73,6 +74,8 @@ static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& o options.setTryInvert(false); } else if (is("-noscale")) { options.setTryDownscale(false); + } else if (is("-single")) { + options.setMaxNumberOfSymbols(1); } else if (is("-ispure")) { options.setIsPure(true); options.setBinarizer(Binarizer::FixedThreshold); From fb617df67f497e5da13c8cad7e5d5e71bcb36a3f Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 7 Feb 2024 01:00:11 +0100 Subject: [PATCH 111/431] cmake: drop the `-ffloat-store` handling I can't reproduce the issues that this setting was fixing when originally introduced. And here comes the kicker: removing it make the g++ Release build on my Core i9 machine 20% faster!?! (Insert eye popping emoji here.) The decode time for the full ReaderTest goes down from 5.2s to 4.2s. While the clang++ build takes 4.7s either way (does not support that setting at all). --- core/CMakeLists.txt | 7 ------- 1 file changed, 7 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index d63d9ecc63..bc74850f2c 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -460,13 +460,6 @@ target_compile_options (ZXing include (CheckCXXCompilerFlag) -CHECK_CXX_COMPILER_FLAG ("-ffloat-store" COMPILER_NEEDS_FLOAT_STORE) -if (COMPILER_NEEDS_FLOAT_STORE) - target_compile_options(ZXing PRIVATE - -ffloat-store # same floating point precision in all optimization levels - ) -endif() - target_compile_features(ZXing PUBLIC cxx_std_17) target_link_libraries (ZXing PRIVATE Threads::Threads) From 47df03d5312f61730c5c863b9df1f876485302cf Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 7 Feb 2024 22:45:04 +0100 Subject: [PATCH 112/431] Pattern: switch from float to double precision This looses 2 but gains 8 successful black box tests. --- core/src/Pattern.h | 59 +++++++++++++--------------- core/src/qrcode/QRDetector.cpp | 4 +- test/blackbox/BlackboxTestRunner.cpp | 20 +++++----- 3 files changed, 40 insertions(+), 43 deletions(-) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index e7eb2baad1..06bc4e7b9e 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -138,7 +138,7 @@ struct BarAndSpace using BarAndSpaceI = BarAndSpace; -template +template constexpr auto BarAndSpaceSum(const T* view) noexcept { BarAndSpace res; @@ -162,22 +162,20 @@ struct FixedPattern constexpr value_type operator[](int i) const noexcept { return _data[i]; } constexpr const value_type* data() const noexcept { return _data; } constexpr int size() const noexcept { return N; } - constexpr BarAndSpace sums() const noexcept { return BarAndSpaceSum(_data); } + constexpr BarAndSpace sums() const noexcept { return BarAndSpaceSum(_data); } }; template using FixedSparcePattern = FixedPattern; template -float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, - float minQuietZone = 0, float moduleSizeRef = 0) +double IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, + double minQuietZone = 0, double moduleSizeRef = 0) { if constexpr (E2E) { - using float_t = double; - - auto widths = BarAndSpaceSum(view.data()); + auto widths = BarAndSpaceSum(view.data()); auto sums = pattern.sums(); - BarAndSpace modSize = {widths[0] / sums[0], widths[1] / sums[1]}; + BarAndSpace modSize = {widths[0] / sums[0], widths[1] / sums[1]}; auto [m, M] = std::minmax(modSize[0], modSize[1]); if (M > 4 * m) // make sure module sizes of bars and spaces are not too far away from each other @@ -186,21 +184,20 @@ float IsPattern(const PatternView& view, const FixedPattern& pa if (minQuietZone && spaceInPixel < minQuietZone * modSize.space) return 0; - const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] / (2 + (LEN < 6)) + .5}; + const BarAndSpace thr = {modSize[0] * .75 + .5, modSize[1] / (2 + (LEN < 6)) + .5}; for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * modSize[x]) > thr[x]) return 0; - const float_t moduleSize = (modSize[0] + modSize[1]) / 2; - return static_cast(moduleSize); + return (modSize[0] + modSize[1]) / 2; } - int width = view.sum(LEN); + double width = view.sum(LEN); if (SUM > LEN && width < SUM) return 0; - const float moduleSize = (float)width / SUM; + const auto moduleSize = width / SUM; if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) return 0; @@ -210,7 +207,7 @@ float IsPattern(const PatternView& view, const FixedPattern& pa // the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes. // TODO: review once we have upsampling in the binarizer in place. - const float threshold = moduleSizeRef * (0.5f + E2E * 0.25f) + 0.5f; + const auto threshold = moduleSizeRef * (0.5 + E2E * 0.25) + 0.5; for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * moduleSizeRef) > threshold) @@ -220,15 +217,15 @@ float IsPattern(const PatternView& view, const FixedPattern& pa } template -float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, - float minQuietZone = 0, float moduleSizeRef = 0) +double IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, + double minQuietZone = 0, double moduleSizeRef = 0) { // pattern contains the indices with the bars/spaces that need to be equally wide - int width = 0; + double width = 0; for (int x = 0; x < SUM; ++x) width += view[pattern[x]]; - const float moduleSize = (float)width / SUM; + const auto moduleSize = width / SUM; if (minQuietZone && spaceInPixel < minQuietZone * moduleSize - 1) return 0; @@ -238,7 +235,7 @@ float IsPattern(const PatternView& view, const FixedPattern& patte // the offset of 0.5 is to make the code less sensitive to quantization errors for small (near 1) module sizes. // TODO: review once we have upsampling in the binarizer in place. - const float threshold = moduleSizeRef * (0.5f + RELAXED_THRESHOLD * 0.25f) + 0.5f; + const auto threshold = moduleSizeRef * (0.5 + RELAXED_THRESHOLD * 0.25) + 0.5; for (int x = 0; x < SUM; ++x) if (std::abs(view[pattern[x]] - moduleSizeRef) > threshold) @@ -248,8 +245,8 @@ float IsPattern(const PatternView& view, const FixedPattern& patte } template -bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, float minQuietZone, - float moduleSizeRef = 0.f) +bool IsRightGuard(const PatternView& view, const FixedPattern& pattern, double minQuietZone, + double moduleSizeRef = 0) { int spaceInPixel = view.isAtLastBar() ? std::numeric_limits::max() : *view.end(); return IsPattern(view, pattern, spaceInPixel, minQuietZone, moduleSizeRef) != 0; @@ -273,7 +270,7 @@ PatternView FindLeftGuard(const PatternView& view, int minSize, Pred isGuard) template PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPattern& pattern, - float minQuietZone) + double minQuietZone) { return FindLeftGuard(view, std::max(minSize, LEN), [&pattern, minQuietZone](const PatternView& window, int spaceInPixel) { @@ -284,12 +281,12 @@ PatternView FindLeftGuard(const PatternView& view, int minSize, const FixedPatte template std::array NormalizedE2EPattern(const PatternView& view) { - float moduleSize = static_cast(view.sum(LEN)) / SUM; + double moduleSize = static_cast(view.sum(LEN)) / SUM; std::array e2e; for (int i = 0; i < LEN - 2; i++) { - float v = (view[i] + view[i + 1]) / moduleSize; - e2e[i] = int(v + .5f); + double v = (view[i] + view[i + 1]) / moduleSize; + e2e[i] = int(v + .5); } return e2e; @@ -298,14 +295,14 @@ std::array NormalizedE2EPattern(const PatternView& view) template std::array NormalizedPattern(const PatternView& view) { - float moduleSize = static_cast(view.sum(LEN)) / SUM; + double moduleSize = static_cast(view.sum(LEN)) / SUM; #if 1 int err = SUM; std::array is; - std::array rs; + std::array rs; for (int i = 0; i < LEN; i++) { - float v = view[i] / moduleSize; - is[i] = int(v + .5f); + double v = view[i] / moduleSize; + is[i] = int(v + .5); rs[i] = v - is[i]; err -= is[i]; } @@ -324,8 +321,8 @@ std::array NormalizedPattern(const PatternView& view) int min_v = view[0], min_i = 0; for (int i = 1; i < LEN; i++) { - float v = (view[i - 1] + view[i]) / moduleSize; - e2e[i] = int(v + .5f); + double v = (view[i - 1] + view[i]) / moduleSize; + e2e[i] = int(v + .5); if (view[i] < min_v) { min_v = view[i]; min_i = i; diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index faa3ba61d7..37476843bc 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -44,8 +44,8 @@ PatternView FindPattern(const PatternView& view) return FindLeftGuard(view, PATTERN.size(), [](const PatternView& view, int spaceInPixel) { // perform a fast plausability test for 1:1:3:1:1 pattern if (view[2] < 2 * std::max(view[0], view[4]) || view[2] < std::max(view[1], view[3])) - return 0.f; - return IsPattern(view, PATTERN, spaceInPixel, 0.1f); // the requires 4, here we accept almost 0 + return 0.; + return IsPattern(view, PATTERN, spaceInPixel, 0.1); // the requires 4, here we accept almost 0 }); } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index f20421eb5f..b676138e8b 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -452,7 +452,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set runTests("ean13-4", "EAN-13", 22, { { 6, 13, 0 }, - { 8, 13, 180 }, + { 7, 13, 180 }, }); runTests("ean13-extension-1", "EAN-13", 5, { @@ -479,27 +479,27 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("upca-1", "UPC-A", 12, { - { 9, 12, 0 }, + { 10, 12, 0 }, { 11, 12, 180 }, }); runTests("upca-2", "UPC-A", 36, { { 17, 22, 0 }, - { 16, 22, 180 }, + { 17, 22, 180 }, }); runTests("upca-3", "UPC-A", 21, { - { 7, 10, 0 }, - { 8, 10, 180 }, + { 7, 11, 0 }, + { 8, 11, 180 }, }); runTests("upca-4", "UPC-A", 19, { - { 8, 12, 0 }, - { 9, 12, 180 }, + { 8, 12, 0, 1, 0 }, + { 9, 12, 0, 1, 180 }, }); runTests("upca-5", "UPC-A", 32, { - { 17, 20, 0 }, + { 18, 20, 0 }, { 18, 20, 180 }, }); @@ -515,8 +515,8 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("upce-2", "UPC-E", 28, { - { 17, 22, 0, 1, 0 }, - { 20, 22, 1, 1, 180 }, + { 18, 22, 0, 1, 0 }, + { 19, 22, 1, 1, 180 }, }); runTests("upce-3", "UPC-E", 11, { From 19470ad786d534ba78025ae88b82013820721c26 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 8 Feb 2024 00:48:47 +0100 Subject: [PATCH 113/431] ReadBarcode: help gcc's auto-vectorizer with the RGB->Lum conversion This makes the `RGBToLum` loop 2-3 times faster with gcc but does currently not help llvm. No idea what msvc is able to make of this. For very specific use cases, like `ZXingReader samples/falsepositives-1/09.png -s -format qrcode -nos -bin global` this can make a substantial difference (.43ms instead of .76ms). --- core/src/ReadBarcode.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index ab8108c1ba..7525a35768 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -107,7 +107,14 @@ ImageView SetupLumImageView(ImageView iv, LumImage& lum, const ReaderOptions& op throw std::invalid_argument("Invalid image format"); if (opts.binarizer() == Binarizer::GlobalHistogram || opts.binarizer() == Binarizer::LocalAverage) { - if (iv.format() != ImageFormat::Lum) { + // manually spell out the 3 most common pixel formats to get at least gcc to vectorize the code + if (iv.format() == ImageFormat::RGB && iv.pixStride() == 3) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); }); + } else if (iv.format() == ImageFormat::RGBX && iv.pixStride() == 4) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); }); + } else if (iv.format() == ImageFormat::BGR && iv.pixStride() == 3) { + lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[2], src[1], src[0]); }); + } else if (iv.format() != ImageFormat::Lum) { lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())]( const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); }); } else if (iv.pixStride() != 1) { From 50d9d66c3158f2faa47d179d06eb22606111b8fa Mon Sep 17 00:00:00 2001 From: ISNing Date: Thu, 8 Feb 2024 15:29:17 +0800 Subject: [PATCH 114/431] publish-android.yml: fix typo of ci name --- .github/workflows/publish-android.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/publish-android.yml b/.github/workflows/publish-android.yml index eb789c3d26..f586a58aae 100644 --- a/.github/workflows/publish-android.yml +++ b/.github/workflows/publish-android.yml @@ -1,4 +1,4 @@ -name: publish-adroid +name: publish-android on: # release: From 6ab17fdce1b8f0e605c1eb0266da63eca261529a Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 8 Feb 2024 22:06:09 +0100 Subject: [PATCH 115/431] ODCode128Writer: make sure a std::exception::what() string is valid UTF8 This should fix the root cause of the iOS crash discussed in the #717. --- core/src/oned/ODCode128Writer.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/oned/ODCode128Writer.cpp b/core/src/oned/ODCode128Writer.cpp index 8cc9b41d4a..1f7b583194 100644 --- a/core/src/oned/ODCode128Writer.cpp +++ b/core/src/oned/ODCode128Writer.cpp @@ -153,7 +153,7 @@ Code128Writer::encode(const std::wstring& contents, int width, int height) const default: if (c > 127) { // support for FNC4 isn't implemented, no full Latin-1 character set available at the moment - throw std::invalid_argument(std::string("Bad character in input: ") + static_cast(c)); + throw std::invalid_argument("Bad character in input: " + ToUtf8(contents.substr(i, 1))); } } } From 0eaeb1d8ef37b0d0e5a78ef2c5c530a331380c1e Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 8 Feb 2024 22:09:45 +0100 Subject: [PATCH 116/431] ios: add ASCII fallback if SetNSError encounters invalid UTF8 error string This adds a layer of security to prevent nil crashes in case there are more cases with invalid UTF8 error messages. This supersedes #717. --- wrappers/ios/Sources/Wrapper/ZXIErrors.mm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm index 6b88bcb1de..bfc5f0da8c 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm +++ b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm @@ -12,10 +12,9 @@ void SetNSError(NSError *__autoreleasing _Nullable* error, } NSString *errorDescription = @"Unknown C++ error"; if (message && strlen(message) > 0) { - try { - errorDescription = [NSString stringWithUTF8String: message]; - } catch (NSException *exception) { - errorDescription = @"Unknown ObjC error"; + errorDescription = [NSString stringWithUTF8String: message]; + if (errorDescription == nil) { + [NSString stringWithCString: message encoding: NSASCIIStringEncoding]; } } NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: errorDescription }; From b80310ff78f7dd87dc80011793d62432c5fa1e53 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 8 Feb 2024 22:47:41 +0100 Subject: [PATCH 117/431] ios: make my last commit actually have any effect at all As @markusfisch was so kind to point out: actually assigning the new NSString to somewhere might be of use. --- wrappers/ios/Sources/Wrapper/ZXIErrors.mm | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm index bfc5f0da8c..6ebfa20397 100644 --- a/wrappers/ios/Sources/Wrapper/ZXIErrors.mm +++ b/wrappers/ios/Sources/Wrapper/ZXIErrors.mm @@ -14,7 +14,8 @@ void SetNSError(NSError *__autoreleasing _Nullable* error, if (message && strlen(message) > 0) { errorDescription = [NSString stringWithUTF8String: message]; if (errorDescription == nil) { - [NSString stringWithCString: message encoding: NSASCIIStringEncoding]; + errorDescription = [NSString stringWithCString: message + encoding: NSASCIIStringEncoding]; } } NSDictionary *userInfo = @{ NSLocalizedDescriptionKey: errorDescription }; From 44c2122da05c33789ac557df0e7d9ed29e4c93bc Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 14:29:50 +0100 Subject: [PATCH 118/431] rust: remove clap from dependencies list for demo This fixes a build issue because the latest clap crate requires a more modern rustc than Ubuntu currently provides. --- wrappers/rust/Cargo.toml | 1 - wrappers/rust/examples/demo.rs | 23 ++++++++--------------- 2 files changed, 8 insertions(+), 16 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 9e403551f6..0e42f223ee 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -27,7 +27,6 @@ image = {version = "0.24.8", optional = true} [dev-dependencies] cfg-if = "1.0.0" anyhow = "1.0.79" -clap = {version = "4.4.13", features = ["derive"]} image = {version = "0.24.8"} [build-dependencies] diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index 1a235c6cbf..2fcad932ac 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -3,33 +3,26 @@ */ // SPDX-License-Identifier: Apache-2.0 -use clap::Parser; -use std::path::PathBuf; use zxing_cpp::*; -#[derive(Parser)] -struct Cli { - filename: PathBuf, - formats: Option, - fast: bool, -} - fn main() -> anyhow::Result<()> { - let cli = Cli::parse(); + let filename = std::env::args().nth(1).expect("no image file name provided"); + let formats = std::env::args().nth(2); + let fast = std::env::args().nth(3).is_some(); - let image = image::open(&cli.filename)?; + let image = image::open(&filename)?; #[cfg(not(feature = "image"))] let lum_img = image.into_luma8(); #[cfg(not(feature = "image"))] let iv = ImageView::from_slice(&lum_img, lum_img.width(), lum_img.height(), ImageFormat::Lum)?; - let formats = barcode_formats_from_string(cli.formats.unwrap_or_default())?; + let formats = barcode_formats_from_string(formats.unwrap_or_default())?; let opts = ReaderOptions::default() .formats(formats) - .try_harder(!cli.fast) - .try_invert(!cli.fast) - .try_rotate(!cli.fast); + .try_harder(!fast) + .try_invert(!fast) + .try_rotate(!fast); #[cfg(feature = "image")] let barcodes = read_barcodes(&image, &opts)?; From c0298930cc818bb2ae3e33ca1b522b5ad136e07d Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 15:09:59 +0100 Subject: [PATCH 119/431] rust: replace io.Error with custom Error type based on thiserror --- wrappers/rust/Cargo.toml | 7 +++--- wrappers/rust/src/lib.rs | 52 ++++++++++++++++++++++++++++++---------- 2 files changed, 44 insertions(+), 15 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 0e42f223ee..7f1bbd8d36 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -20,13 +20,14 @@ image = ["dep:image"] bundled = [] [dependencies] -paste = "1.0.14" +thiserror = "1.0" +paste = "1.0" flagset = "0.4.4" image = {version = "0.24.8", optional = true} [dev-dependencies] -cfg-if = "1.0.0" -anyhow = "1.0.79" +cfg-if = "1.0" +anyhow = "1.0" image = {version = "0.24.8"} [build-dependencies] diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 0adea923a1..9df557eff9 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -21,14 +21,40 @@ use bindings::*; use flagset::{flags, FlagSet}; use paste::paste; -use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString}; +use std::ffi::{c_char, c_int, c_uint, c_void, CStr, CString, NulError}; use std::fmt::{Display, Formatter}; -use std::io::ErrorKind; use std::marker::PhantomData; use std::mem::transmute; use std::rc::Rc; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum Error { + #[error("{0}")] + InvalidInput(String), + + #[error("NulError from CString::new")] + NulError(#[from] NulError), + // + // #[error("data store disconnected")] + // IOError(#[from] std::io::Error), + // #[error("the data for key `{0}` is not available")] + // Redaction(String), + // #[error("invalid header (expected {expected:?}, found {found:?})")] + // InvalidHeader { + // expected: String, + // found: String, + // }, + // #[error("unknown data store error")] + // Unknown, +} -pub type Error = std::io::Error; +// see https://github.com/dtolnay/thiserror/issues/62 +impl From for Error { + fn from(_: std::convert::Infallible) -> Self { + unreachable!() + } +} fn c2r_str(str: *mut c_char) -> String { let mut res = String::new(); @@ -51,7 +77,7 @@ fn c2r_vec(buf: *mut u8, len: c_int) -> Vec { fn last_error() -> Error { match unsafe { zxing_LastErrorMsg().as_mut() } { None => panic!("Internal error: zxing_LastErrorMsg() returned NULL"), - Some(error) => Error::new(ErrorKind::InvalidInput, c2r_str(error)), + Some(error) => Error::InvalidInput(c2r_str(error)), } } @@ -59,7 +85,7 @@ macro_rules! last_error_or { ($expr:expr) => { match unsafe { zxing_LastErrorMsg().as_mut() } { None => Ok($expr), - Some(error) => Err(Error::new(ErrorKind::InvalidInput, c2r_str(error))), + Some(error) => Err(Error::InvalidInput(c2r_str(error))), } }; } @@ -134,8 +160,7 @@ impl<'a> From<&'a ImageView<'a>> for ImageView<'a> { impl<'a> ImageView<'a> { fn try_into_int>(val: T) -> Result { - val.try_into() - .map_err(|_| Error::new(ErrorKind::InvalidInput, "Could not convert Integer into c_int.")) + val.try_into().map_err(|_| Error::InvalidInput("Could not convert Integer into c_int.".to_string())) } /// Constructs an ImageView from a raw pointer and the width/height (in pixels) @@ -223,7 +248,7 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { }; match format { Some(format) => Ok(ImageView::from_slice(img.as_bytes(), img.width(), img.height(), format)?), - None => Err(Error::new(ErrorKind::InvalidInput, "Invalid image format (must be either luma8|rgb8|rgba8)")), + None => Err(Error::InvalidInput("Invalid image format (must be either luma8|rgb8|rgba8)".to_string())), } } } @@ -362,10 +387,13 @@ pub fn barcode_formats_from_string(str: impl AsRef) -> Result(image: impl TryInto>, opts: impl AsRef) -> Result, Error> { - let iv_: ImageView = image - .try_into() - .map_err(|_| Error::new(ErrorKind::InvalidInput, "Failed to image.try_into::()"))?; +pub fn read_barcodes<'a, IV, RO>(image: IV, opts: RO) -> Result, Error> +where + IV: TryInto>, + IV::Error: Into, + RO: AsRef, +{ + let iv_: ImageView = image.try_into().map_err(Into::into)?; unsafe { let results = zxing_ReadBarcodes((iv_.0).0, opts.as_ref().0); if !results.is_null() { From a9c81732a1a90f37f322166524f5c555dc2c881c Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 23:07:47 +0100 Subject: [PATCH 120/431] rust: minor README improvement --- wrappers/rust/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index 3ef352c3c4..65c28dcc18 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -52,7 +52,7 @@ zxing-cpp provides features that are behind [Cargo features](https://doc.rust-la They are: * `bundled` uses a bundled version of the [zxing-cpp](https://github.com/zxing-cpp/zxing-cpp) c++ library. -* [`image`](https://crates.io/crates/image) allows convenient `ImageView` construction from `GreyImage` and `DynamicImage`. +* [`image`](https://crates.io/crates/image) allows convenient/implicit `ImageView` construction from `GreyImage` and `DynamicImage`. ## Benchmarking From b1d46f972deec1f2f88f7f94c17d2fd264be2712 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 23:48:31 +0100 Subject: [PATCH 121/431] c-API: replace `zxing_` prefix with `ZXing_` Apart from the prefix, the rest of the naming is strictly following the C++ symbol names using _ to replace wither :: or .. So in that sense the prefix should be `ZXing_` to replace the `ZXing` c++ namespace. `ZXing::ReaderOptions::tryHarder` becomes `ZXing_ReaderOptions_tryHarder`. For more info see also https://github.com/zxing-cpp/zxing-cpp/discussions/720 --- core/CMakeLists.txt | 8 +- core/src/{zxing-c.cpp => ZXingC.cpp} | 84 ++++----- core/src/ZXingC.h | 228 +++++++++++++++++++++++ core/src/zxing-c.h | 228 ----------------------- wrappers/c/CMakeLists.txt | 6 +- wrappers/c/README.md | 37 ++-- wrappers/c/ZXingCTest.c | 108 +++++++++++ wrappers/c/zxing-c-test.c | 108 ----------- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 236 ++++++++++++------------ wrappers/rust/build.rs | 2 +- wrappers/rust/src/bindings.rs | 264 +++++++++++++-------------- wrappers/rust/src/lib.rs | 70 +++---- 12 files changed, 690 insertions(+), 689 deletions(-) rename core/src/{zxing-c.cpp => ZXingC.cpp} (63%) create mode 100644 core/src/ZXingC.h delete mode 100644 core/src/zxing-c.h create mode 100644 wrappers/c/ZXingCTest.c delete mode 100644 wrappers/c/zxing-c-test.c diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index bc74850f2c..2afedb078a 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -137,8 +137,8 @@ if (BUILD_READERS) src/ThresholdBinarizer.h src/WhiteRectDetector.h src/WhiteRectDetector.cpp - $<$:src/zxing-c.h> - $<$:src/zxing-c.cpp> + $<$:src/ZXingC.h> + $<$:src/ZXingC.cpp> ) endif() if (BUILD_WRITERS) @@ -177,7 +177,7 @@ if (BUILD_READERS) src/ReaderOptions.h src/Result.h src/StructuredAppend.h - $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/zxing-c.h> + $<$:${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingC.h> ) endif() if (BUILD_WRITERS) @@ -481,7 +481,7 @@ endif() set_target_properties(ZXing PROPERTIES PUBLIC_HEADER "${PUBLIC_HEADERS}") set(PRECOMPILE_HEADERS ${PUBLIC_HEADERS}) -list(REMOVE_ITEM PRECOMPILE_HEADERS "$<$:${CMAKE_CURRENT_SOURCE_DIR}/src/zxing-c.h>") +list(REMOVE_ITEM PRECOMPILE_HEADERS "$<$:${CMAKE_CURRENT_SOURCE_DIR}/src/ZXingC.h>") target_precompile_headers(ZXing PRIVATE ${PRECOMPILE_HEADERS}) set_source_files_properties(src/libzueci/zueci.c PROPERTIES SKIP_PRECOMPILE_HEADERS ON) set_source_files_properties(src/DecodeHints.cpp PROPERTIES SKIP_PRECOMPILE_HEADERS ON) diff --git a/core/src/zxing-c.cpp b/core/src/ZXingC.cpp similarity index 63% rename from core/src/zxing-c.cpp rename to core/src/ZXingC.cpp index f141130b6d..05579f29f0 100644 --- a/core/src/zxing-c.cpp +++ b/core/src/ZXingC.cpp @@ -4,7 +4,7 @@ */ // SPDX-License-Identifier: Apache-2.0 -#include "zxing-c.h" +#include "ZXingC.h" #include "ReadBarcode.h" @@ -48,7 +48,7 @@ static uint8_t* copy(const ByteArray& ba, int* len) return ret; } -static std::tuple ReadBarcodesAndSetLastError(const zxing_ImageView* iv, const zxing_ReaderOptions* opts, +static std::tuple ReadBarcodesAndSetLastError(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts, int maxSymbols) { try { @@ -73,7 +73,7 @@ extern "C" { * ZXing/ImageView.h */ -zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, +ZXing_ImageView* ZXing_ImageView_new(const uint8_t* data, int width, int height, ZXing_ImageFormat format, int rowStride, int pixStride) { ImageFormat cppformat = static_cast(format); @@ -85,7 +85,7 @@ zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, return NULL; } -zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, zxing_ImageFormat format, +ZXing_ImageView* ZXing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, ZXing_ImageFormat format, int rowStride, int pixStride) { ImageFormat cppformat = static_cast(format); @@ -97,17 +97,17 @@ zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int return NULL; } -void zxing_ImageView_delete(zxing_ImageView* iv) +void ZXing_ImageView_delete(ZXing_ImageView* iv) { delete iv; } -void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height) +void ZXing_ImageView_crop(ZXing_ImageView* iv, int left, int top, int width, int height) { *iv = iv->cropped(left, top, width, height); } -void zxing_ImageView_rotate(zxing_ImageView* iv, int degree) +void ZXing_ImageView_rotate(ZXing_ImageView* iv, int degree) { *iv = iv->rotated(degree); } @@ -116,29 +116,29 @@ void zxing_ImageView_rotate(zxing_ImageView* iv, int degree) * ZXing/BarcodeFormat.h */ -zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str) +ZXing_BarcodeFormats ZXing_BarcodeFormatsFromString(const char* str) { if (!str) return {}; try { auto format = BarcodeFormatsFromString(str); - return static_cast(transmute_cast(format)); + return static_cast(transmute_cast(format)); } catch (std::exception& e) { lastErrorMsg = e.what(); } catch (...) { lastErrorMsg = "Unknown error"; } - return zxing_BarcodeFormat_Invalid; + return ZXing_BarcodeFormat_Invalid; } -zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str) +ZXing_BarcodeFormat ZXing_BarcodeFormatFromString(const char* str) { - zxing_BarcodeFormat res = zxing_BarcodeFormatsFromString(str); - return BitHacks::CountBitsSet(res) == 1 ? res : zxing_BarcodeFormat_Invalid; + ZXing_BarcodeFormat res = ZXing_BarcodeFormatsFromString(str); + return BitHacks::CountBitsSet(res) == 1 ? res : ZXing_BarcodeFormat_Invalid; } -char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) +char* ZXing_BarcodeFormatToString(ZXing_BarcodeFormat format) { return copy(ToString(static_cast(format))); } @@ -147,19 +147,19 @@ char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format) * ZXing/ReaderOptions.h */ -zxing_ReaderOptions* zxing_ReaderOptions_new() +ZXing_ReaderOptions* ZXing_ReaderOptions_new() { return new ReaderOptions(); } -void zxing_ReaderOptions_delete(zxing_ReaderOptions* opts) +void ZXing_ReaderOptions_delete(ZXing_ReaderOptions* opts) { delete opts; } #define ZX_PROPERTY(TYPE, GETTER, SETTER) \ - TYPE zxing_ReaderOptions_get##SETTER(const zxing_ReaderOptions* opts) { return opts->GETTER(); } \ - void zxing_ReaderOptions_set##SETTER(zxing_ReaderOptions* opts, TYPE val) { opts->set##SETTER(val); } + TYPE ZXing_ReaderOptions_get##SETTER(const ZXing_ReaderOptions* opts) { return opts->GETTER(); } \ + void ZXing_ReaderOptions_set##SETTER(ZXing_ReaderOptions* opts, TYPE val) { opts->set##SETTER(val); } ZX_PROPERTY(bool, tryHarder, TryHarder) ZX_PROPERTY(bool, tryRotate, TryRotate) @@ -170,20 +170,20 @@ ZX_PROPERTY(bool, returnErrors, ReturnErrors) ZX_PROPERTY(int, minLineCount, MinLineCount) ZX_PROPERTY(int, maxNumberOfSymbols, MaxNumberOfSymbols) -void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeFormats formats) +void ZXing_ReaderOptions_setFormats(ZXing_ReaderOptions* opts, ZXing_BarcodeFormats formats) { opts->setFormats(static_cast(formats)); } -zxing_BarcodeFormats zxing_ReaderOptions_getFormats(const zxing_ReaderOptions* opts) +ZXing_BarcodeFormats ZXing_ReaderOptions_getFormats(const ZXing_ReaderOptions* opts) { auto v = opts->formats(); - return transmute_cast(v); + return transmute_cast(v); } #define ZX_ENUM_PROPERTY(TYPE, GETTER, SETTER) \ - zxing_##TYPE zxing_ReaderOptions_get##SETTER(const zxing_ReaderOptions* opts) { return static_cast(opts->GETTER()); } \ - void zxing_ReaderOptions_set##SETTER(zxing_ReaderOptions* opts, zxing_##TYPE val) { opts->set##SETTER(static_cast(val)); } + ZXing_##TYPE ZXing_ReaderOptions_get##SETTER(const ZXing_ReaderOptions* opts) { return static_cast(opts->GETTER()); } \ + void ZXing_ReaderOptions_set##SETTER(ZXing_ReaderOptions* opts, ZXing_##TYPE val) { opts->set##SETTER(static_cast(val)); } ZX_ENUM_PROPERTY(Binarizer, binarizer, Binarizer) ZX_ENUM_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, EanAddOnSymbol) @@ -193,46 +193,46 @@ ZX_ENUM_PROPERTY(TextMode, textMode, TextMode) * ZXing/Result.h */ -char* zxing_ContentTypeToString(zxing_ContentType type) +char* ZXing_ContentTypeToString(ZXing_ContentType type) { return copy(ToString(static_cast(type))); } -char* zxing_PositionToString(zxing_Position position) +char* ZXing_PositionToString(ZXing_Position position) { return copy(ToString(transmute_cast(position))); } -bool zxing_Barcode_isValid(const zxing_Barcode* barcode) +bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode) { return barcode != NULL && barcode->isValid(); } -char* zxing_Barcode_errorMsg(const zxing_Barcode* barcode) +char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode) { return copy(ToString(barcode->error())); } -uint8_t* zxing_Barcode_bytes(const zxing_Barcode* barcode, int* len) +uint8_t* ZXing_Barcode_bytes(const ZXing_Barcode* barcode, int* len) { return copy(barcode->bytes(), len); } -uint8_t* zxing_Barcode_bytesECI(const zxing_Barcode* barcode, int* len) +uint8_t* ZXing_Barcode_bytesECI(const ZXing_Barcode* barcode, int* len) { return copy(barcode->bytesECI(), len); } #define ZX_GETTER(TYPE, GETTER, TRANS) \ - TYPE zxing_Barcode_##GETTER(const zxing_Barcode* barcode) { return static_cast(TRANS(barcode->GETTER())); } + TYPE ZXing_Barcode_##GETTER(const ZXing_Barcode* barcode) { return static_cast(TRANS(barcode->GETTER())); } -ZX_GETTER(zxing_BarcodeFormat, format,) -ZX_GETTER(zxing_ContentType, contentType,) +ZX_GETTER(ZXing_BarcodeFormat, format,) +ZX_GETTER(ZXing_ContentType, contentType,) ZX_GETTER(char*, text, copy) ZX_GETTER(char*, ecLevel, copy) ZX_GETTER(char*, symbologyIdentifier, copy) -ZX_GETTER(zxing_Position, position, transmute_cast) +ZX_GETTER(ZXing_Position, position, transmute_cast) ZX_GETTER(int, orientation,) ZX_GETTER(bool, hasECI,) @@ -245,41 +245,41 @@ ZX_GETTER(int, lineCount,) * ZXing/ReadBarcode.h */ -zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +ZXing_Barcode* ZXing_ReadBarcode(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts) { auto [res, ok] = ReadBarcodesAndSetLastError(iv, opts, 1); return !res.empty() ? new Result(std::move(res.front())) : NULL; } -zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts) +ZXing_Barcodes* ZXing_ReadBarcodes(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts) { auto [res, ok] = ReadBarcodesAndSetLastError(iv, opts, 0); return !res.empty() || ok ? new Results(std::move(res)) : NULL; } -void zxing_Barcode_delete(zxing_Barcode* barcode) +void ZXing_Barcode_delete(ZXing_Barcode* barcode) { delete barcode; } -void zxing_Barcodes_delete(zxing_Barcodes* barcodes) +void ZXing_Barcodes_delete(ZXing_Barcodes* barcodes) { delete barcodes; } -int zxing_Barcodes_size(const zxing_Barcodes* barcodes) +int ZXing_Barcodes_size(const ZXing_Barcodes* barcodes) { return barcodes ? Size(*barcodes) : 0; } -const zxing_Barcode* zxing_Barcodes_at(const zxing_Barcodes* barcodes, int i) +const ZXing_Barcode* ZXing_Barcodes_at(const ZXing_Barcodes* barcodes, int i) { if (!barcodes || i < 0 || i >= Size(*barcodes)) return NULL; return &(*barcodes)[i]; } -zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i) +ZXing_Barcode* ZXing_Barcodes_move(ZXing_Barcodes* barcodes, int i) { if (!barcodes || i < 0 || i >= Size(*barcodes)) return NULL; @@ -287,7 +287,7 @@ zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i) return new Result(std::move((*barcodes)[i])); } -char* zxing_LastErrorMsg() +char* ZXing_LastErrorMsg() { if (lastErrorMsg.empty()) return NULL; @@ -295,7 +295,7 @@ char* zxing_LastErrorMsg() return copy(std::exchange(lastErrorMsg, {})); } -void zxing_free(void* ptr) +void ZXing_free(void* ptr) { free(ptr); } diff --git a/core/src/ZXingC.h b/core/src/ZXingC.h new file mode 100644 index 0000000000..63d249ed6c --- /dev/null +++ b/core/src/ZXingC.h @@ -0,0 +1,228 @@ +/* +* Copyright 2023 siiky +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#ifndef _ZXING_C_H +#define _ZXING_C_H + +#include +#include + +#ifdef __cplusplus + +#include "ReaderOptions.h" +#include "ImageView.h" +#include "Result.h" + +typedef ZXing::ImageView ZXing_ImageView; +typedef ZXing::ReaderOptions ZXing_ReaderOptions; +typedef ZXing::Result ZXing_Barcode; +typedef ZXing::Results ZXing_Barcodes; + +extern "C" +{ +#else + +typedef struct ZXing_ImageView ZXing_ImageView; +typedef struct ZXing_ReaderOptions ZXing_ReaderOptions; +typedef struct ZXing_Barcode ZXing_Barcode; +typedef struct ZXing_Barcodes ZXing_Barcodes; + +#endif + +/* + * ZXing/ImageView.h + */ + +typedef enum { + ZXing_ImageFormat_None = 0, + ZXing_ImageFormat_Lum = 0x01000000, + ZXing_ImageFormat_RGB = 0x03000102, + ZXing_ImageFormat_BGR = 0x03020100, + ZXing_ImageFormat_RGBX = 0x04000102, + ZXing_ImageFormat_XRGB = 0x04010203, + ZXing_ImageFormat_BGRX = 0x04020100, + ZXing_ImageFormat_XBGR = 0x04030201, +} ZXing_ImageFormat; + +ZXing_ImageView* ZXing_ImageView_new(const uint8_t* data, int width, int height, ZXing_ImageFormat format, int rowStride, + int pixStride); +ZXing_ImageView* ZXing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, ZXing_ImageFormat format, + int rowStride, int pixStride); +void ZXing_ImageView_delete(ZXing_ImageView* iv); + +void ZXing_ImageView_crop(ZXing_ImageView* iv, int left, int top, int width, int height); +void ZXing_ImageView_rotate(ZXing_ImageView* iv, int degree); + +/* + * ZXing/BarcodeFormat.h + */ + +typedef enum +{ + ZXing_BarcodeFormat_None = 0, + ZXing_BarcodeFormat_Aztec = (1 << 0), + ZXing_BarcodeFormat_Codabar = (1 << 1), + ZXing_BarcodeFormat_Code39 = (1 << 2), + ZXing_BarcodeFormat_Code93 = (1 << 3), + ZXing_BarcodeFormat_Code128 = (1 << 4), + ZXing_BarcodeFormat_DataBar = (1 << 5), + ZXing_BarcodeFormat_DataBarExpanded = (1 << 6), + ZXing_BarcodeFormat_DataMatrix = (1 << 7), + ZXing_BarcodeFormat_EAN8 = (1 << 8), + ZXing_BarcodeFormat_EAN13 = (1 << 9), + ZXing_BarcodeFormat_ITF = (1 << 10), + ZXing_BarcodeFormat_MaxiCode = (1 << 11), + ZXing_BarcodeFormat_PDF417 = (1 << 12), + ZXing_BarcodeFormat_QRCode = (1 << 13), + ZXing_BarcodeFormat_UPCA = (1 << 14), + ZXing_BarcodeFormat_UPCE = (1 << 15), + ZXing_BarcodeFormat_MicroQRCode = (1 << 16), + ZXing_BarcodeFormat_RMQRCode = (1 << 17), + ZXing_BarcodeFormat_DXFilmEdge = (1 << 18), + + ZXing_BarcodeFormat_LinearCodes = ZXing_BarcodeFormat_Codabar | ZXing_BarcodeFormat_Code39 | ZXing_BarcodeFormat_Code93 + | ZXing_BarcodeFormat_Code128 | ZXing_BarcodeFormat_EAN8 | ZXing_BarcodeFormat_EAN13 + | ZXing_BarcodeFormat_ITF | ZXing_BarcodeFormat_DataBar | ZXing_BarcodeFormat_DataBarExpanded + | ZXing_BarcodeFormat_DXFilmEdge | ZXing_BarcodeFormat_UPCA | ZXing_BarcodeFormat_UPCE, + ZXing_BarcodeFormat_MatrixCodes = ZXing_BarcodeFormat_Aztec | ZXing_BarcodeFormat_DataMatrix | ZXing_BarcodeFormat_MaxiCode + | ZXing_BarcodeFormat_PDF417 | ZXing_BarcodeFormat_QRCode | ZXing_BarcodeFormat_MicroQRCode + | ZXing_BarcodeFormat_RMQRCode, + ZXing_BarcodeFormat_Any = ZXing_BarcodeFormat_LinearCodes | ZXing_BarcodeFormat_MatrixCodes, + + ZXing_BarcodeFormat_Invalid = 0xFFFFFFFFu /* return value when BarcodeFormatsFromString() throws */ +} ZXing_BarcodeFormat; + +typedef ZXing_BarcodeFormat ZXing_BarcodeFormats; + +ZXing_BarcodeFormats ZXing_BarcodeFormatsFromString(const char* str); +ZXing_BarcodeFormat ZXing_BarcodeFormatFromString(const char* str); +char* ZXing_BarcodeFormatToString(ZXing_BarcodeFormat format); + +/* + * ZXing/ReaderOptions.h + */ + +typedef enum +{ + ZXing_Binarizer_LocalAverage, + ZXing_Binarizer_GlobalHistogram, + ZXing_Binarizer_FixedThreshold, + ZXing_Binarizer_BoolCast, +} ZXing_Binarizer; + +typedef enum +{ + ZXing_EanAddOnSymbol_Ignore, + ZXing_EanAddOnSymbol_Read, + ZXing_EanAddOnSymbol_Require, +} ZXing_EanAddOnSymbol; + +typedef enum +{ + ZXing_TextMode_Plain, + ZXing_TextMode_ECI, + ZXing_TextMode_HRI, + ZXing_TextMode_Hex, + ZXing_TextMode_Escaped, +} ZXing_TextMode; + +ZXing_ReaderOptions* ZXing_ReaderOptions_new(); +void ZXing_ReaderOptions_delete(ZXing_ReaderOptions* opts); + +void ZXing_ReaderOptions_setTryHarder(ZXing_ReaderOptions* opts, bool tryHarder); +void ZXing_ReaderOptions_setTryRotate(ZXing_ReaderOptions* opts, bool tryRotate); +void ZXing_ReaderOptions_setTryInvert(ZXing_ReaderOptions* opts, bool tryInvert); +void ZXing_ReaderOptions_setTryDownscale(ZXing_ReaderOptions* opts, bool tryDownscale); +void ZXing_ReaderOptions_setIsPure(ZXing_ReaderOptions* opts, bool isPure); +void ZXing_ReaderOptions_setReturnErrors(ZXing_ReaderOptions* opts, bool returnErrors); +void ZXing_ReaderOptions_setFormats(ZXing_ReaderOptions* opts, ZXing_BarcodeFormats formats); +void ZXing_ReaderOptions_setBinarizer(ZXing_ReaderOptions* opts, ZXing_Binarizer binarizer); +void ZXing_ReaderOptions_setEanAddOnSymbol(ZXing_ReaderOptions* opts, ZXing_EanAddOnSymbol eanAddOnSymbol); +void ZXing_ReaderOptions_setTextMode(ZXing_ReaderOptions* opts, ZXing_TextMode textMode); +void ZXing_ReaderOptions_setMinLineCount(ZXing_ReaderOptions* opts, int n); +void ZXing_ReaderOptions_setMaxNumberOfSymbols(ZXing_ReaderOptions* opts, int n); + +bool ZXing_ReaderOptions_getTryHarder(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getTryRotate(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getTryInvert(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getTryDownscale(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getIsPure(const ZXing_ReaderOptions* opts); +bool ZXing_ReaderOptions_getReturnErrors(const ZXing_ReaderOptions* opts); +ZXing_BarcodeFormats ZXing_ReaderOptions_getFormats(const ZXing_ReaderOptions* opts); +ZXing_Binarizer ZXing_ReaderOptions_getBinarizer(const ZXing_ReaderOptions* opts); +ZXing_EanAddOnSymbol ZXing_ReaderOptions_getEanAddOnSymbol(const ZXing_ReaderOptions* opts); +ZXing_TextMode ZXing_ReaderOptions_getTextMode(const ZXing_ReaderOptions* opts); +int ZXing_ReaderOptions_getMinLineCount(const ZXing_ReaderOptions* opts); +int ZXing_ReaderOptions_getMaxNumberOfSymbols(const ZXing_ReaderOptions* opts); + +/* + * ZXing/Result.h + */ + +typedef enum +{ + ZXing_ContentType_Text, + ZXing_ContentType_Binary, + ZXing_ContentType_Mixed, + ZXing_ContentType_GS1, + ZXing_ContentType_ISO15434, + ZXing_ContentType_UnknownECI +} ZXing_ContentType; + +char* ZXing_ContentTypeToString(ZXing_ContentType type); + +typedef struct ZXing_PointI +{ + int x, y; +} ZXing_PointI; + +typedef struct ZXing_Position +{ + ZXing_PointI topLeft, topRight, bottomRight, bottomLeft; +} ZXing_Position; + +char* ZXing_PositionToString(ZXing_Position position); + +bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode); +char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode); +ZXing_BarcodeFormat ZXing_Barcode_format(const ZXing_Barcode* barcode); +ZXing_ContentType ZXing_Barcode_contentType(const ZXing_Barcode* barcode); +uint8_t* ZXing_Barcode_bytes(const ZXing_Barcode* barcode, int* len); +uint8_t* ZXing_Barcode_bytesECI(const ZXing_Barcode* barcode, int* len); +char* ZXing_Barcode_text(const ZXing_Barcode* barcode); +char* ZXing_Barcode_ecLevel(const ZXing_Barcode* barcode); +char* ZXing_Barcode_symbologyIdentifier(const ZXing_Barcode* barcode); +ZXing_Position ZXing_Barcode_position(const ZXing_Barcode* barcode); +int ZXing_Barcode_orientation(const ZXing_Barcode* barcode); +bool ZXing_Barcode_hasECI(const ZXing_Barcode* barcode); +bool ZXing_Barcode_isInverted(const ZXing_Barcode* barcode); +bool ZXing_Barcode_isMirrored(const ZXing_Barcode* barcode); +int ZXing_Barcode_lineCount(const ZXing_Barcode* barcode); + +/* + * ZXing/ReadBarcode.h + */ + +/** Note: opts is optional, i.e. it can be NULL, which will imply default settings. */ +ZXing_Barcode* ZXing_ReadBarcode(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts); +ZXing_Barcodes* ZXing_ReadBarcodes(const ZXing_ImageView* iv, const ZXing_ReaderOptions* opts); + +void ZXing_Barcode_delete(ZXing_Barcode* barcode); +void ZXing_Barcodes_delete(ZXing_Barcodes* barcodes); + +int ZXing_Barcodes_size(const ZXing_Barcodes* barcodes); +const ZXing_Barcode* ZXing_Barcodes_at(const ZXing_Barcodes* barcodes, int i); +ZXing_Barcode* ZXing_Barcodes_move(ZXing_Barcodes* barcodes, int i); + +char* ZXing_LastErrorMsg(); + +void ZXing_free(void* ptr); + +#ifdef __cplusplus +} +#endif + +#endif /* _ZXING_C_H */ diff --git a/core/src/zxing-c.h b/core/src/zxing-c.h deleted file mode 100644 index 75207aa679..0000000000 --- a/core/src/zxing-c.h +++ /dev/null @@ -1,228 +0,0 @@ -/* -* Copyright 2023 siiky -* Copyright 2023 Axel Waggershauser -*/ -// SPDX-License-Identifier: Apache-2.0 - -#ifndef _ZXING_C_H -#define _ZXING_C_H - -#include -#include - -#ifdef __cplusplus - -#include "ReaderOptions.h" -#include "ImageView.h" -#include "Result.h" - -typedef ZXing::ImageView zxing_ImageView; -typedef ZXing::ReaderOptions zxing_ReaderOptions; -typedef ZXing::Result zxing_Barcode; -typedef ZXing::Results zxing_Barcodes; - -extern "C" -{ -#else - -typedef struct zxing_ImageView zxing_ImageView; -typedef struct zxing_ReaderOptions zxing_ReaderOptions; -typedef struct zxing_Barcode zxing_Barcode; -typedef struct zxing_Barcodes zxing_Barcodes; - -#endif - -/* - * ZXing/ImageView.h - */ - -typedef enum { - zxing_ImageFormat_None = 0, - zxing_ImageFormat_Lum = 0x01000000, - zxing_ImageFormat_RGB = 0x03000102, - zxing_ImageFormat_BGR = 0x03020100, - zxing_ImageFormat_RGBX = 0x04000102, - zxing_ImageFormat_XRGB = 0x04010203, - zxing_ImageFormat_BGRX = 0x04020100, - zxing_ImageFormat_XBGR = 0x04030201, -} zxing_ImageFormat; - -zxing_ImageView* zxing_ImageView_new(const uint8_t* data, int width, int height, zxing_ImageFormat format, int rowStride, - int pixStride); -zxing_ImageView* zxing_ImageView_new_checked(const uint8_t* data, int size, int width, int height, zxing_ImageFormat format, - int rowStride, int pixStride); -void zxing_ImageView_delete(zxing_ImageView* iv); - -void zxing_ImageView_crop(zxing_ImageView* iv, int left, int top, int width, int height); -void zxing_ImageView_rotate(zxing_ImageView* iv, int degree); - -/* - * ZXing/BarcodeFormat.h - */ - -typedef enum -{ - zxing_BarcodeFormat_None = 0, - zxing_BarcodeFormat_Aztec = (1 << 0), - zxing_BarcodeFormat_Codabar = (1 << 1), - zxing_BarcodeFormat_Code39 = (1 << 2), - zxing_BarcodeFormat_Code93 = (1 << 3), - zxing_BarcodeFormat_Code128 = (1 << 4), - zxing_BarcodeFormat_DataBar = (1 << 5), - zxing_BarcodeFormat_DataBarExpanded = (1 << 6), - zxing_BarcodeFormat_DataMatrix = (1 << 7), - zxing_BarcodeFormat_EAN8 = (1 << 8), - zxing_BarcodeFormat_EAN13 = (1 << 9), - zxing_BarcodeFormat_ITF = (1 << 10), - zxing_BarcodeFormat_MaxiCode = (1 << 11), - zxing_BarcodeFormat_PDF417 = (1 << 12), - zxing_BarcodeFormat_QRCode = (1 << 13), - zxing_BarcodeFormat_UPCA = (1 << 14), - zxing_BarcodeFormat_UPCE = (1 << 15), - zxing_BarcodeFormat_MicroQRCode = (1 << 16), - zxing_BarcodeFormat_RMQRCode = (1 << 17), - zxing_BarcodeFormat_DXFilmEdge = (1 << 18), - - zxing_BarcodeFormat_LinearCodes = zxing_BarcodeFormat_Codabar | zxing_BarcodeFormat_Code39 | zxing_BarcodeFormat_Code93 - | zxing_BarcodeFormat_Code128 | zxing_BarcodeFormat_EAN8 | zxing_BarcodeFormat_EAN13 - | zxing_BarcodeFormat_ITF | zxing_BarcodeFormat_DataBar | zxing_BarcodeFormat_DataBarExpanded - | zxing_BarcodeFormat_DXFilmEdge | zxing_BarcodeFormat_UPCA | zxing_BarcodeFormat_UPCE, - zxing_BarcodeFormat_MatrixCodes = zxing_BarcodeFormat_Aztec | zxing_BarcodeFormat_DataMatrix | zxing_BarcodeFormat_MaxiCode - | zxing_BarcodeFormat_PDF417 | zxing_BarcodeFormat_QRCode | zxing_BarcodeFormat_MicroQRCode - | zxing_BarcodeFormat_RMQRCode, - zxing_BarcodeFormat_Any = zxing_BarcodeFormat_LinearCodes | zxing_BarcodeFormat_MatrixCodes, - - zxing_BarcodeFormat_Invalid = 0xFFFFFFFFu /* return value when BarcodeFormatsFromString() throws */ -} zxing_BarcodeFormat; - -typedef zxing_BarcodeFormat zxing_BarcodeFormats; - -zxing_BarcodeFormats zxing_BarcodeFormatsFromString(const char* str); -zxing_BarcodeFormat zxing_BarcodeFormatFromString(const char* str); -char* zxing_BarcodeFormatToString(zxing_BarcodeFormat format); - -/* - * ZXing/ReaderOptions.h - */ - -typedef enum -{ - zxing_Binarizer_LocalAverage, - zxing_Binarizer_GlobalHistogram, - zxing_Binarizer_FixedThreshold, - zxing_Binarizer_BoolCast, -} zxing_Binarizer; - -typedef enum -{ - zxing_EanAddOnSymbol_Ignore, - zxing_EanAddOnSymbol_Read, - zxing_EanAddOnSymbol_Require, -} zxing_EanAddOnSymbol; - -typedef enum -{ - zxing_TextMode_Plain, - zxing_TextMode_ECI, - zxing_TextMode_HRI, - zxing_TextMode_Hex, - zxing_TextMode_Escaped, -} zxing_TextMode; - -zxing_ReaderOptions* zxing_ReaderOptions_new(); -void zxing_ReaderOptions_delete(zxing_ReaderOptions* opts); - -void zxing_ReaderOptions_setTryHarder(zxing_ReaderOptions* opts, bool tryHarder); -void zxing_ReaderOptions_setTryRotate(zxing_ReaderOptions* opts, bool tryRotate); -void zxing_ReaderOptions_setTryInvert(zxing_ReaderOptions* opts, bool tryInvert); -void zxing_ReaderOptions_setTryDownscale(zxing_ReaderOptions* opts, bool tryDownscale); -void zxing_ReaderOptions_setIsPure(zxing_ReaderOptions* opts, bool isPure); -void zxing_ReaderOptions_setReturnErrors(zxing_ReaderOptions* opts, bool returnErrors); -void zxing_ReaderOptions_setFormats(zxing_ReaderOptions* opts, zxing_BarcodeFormats formats); -void zxing_ReaderOptions_setBinarizer(zxing_ReaderOptions* opts, zxing_Binarizer binarizer); -void zxing_ReaderOptions_setEanAddOnSymbol(zxing_ReaderOptions* opts, zxing_EanAddOnSymbol eanAddOnSymbol); -void zxing_ReaderOptions_setTextMode(zxing_ReaderOptions* opts, zxing_TextMode textMode); -void zxing_ReaderOptions_setMinLineCount(zxing_ReaderOptions* opts, int n); -void zxing_ReaderOptions_setMaxNumberOfSymbols(zxing_ReaderOptions* opts, int n); - -bool zxing_ReaderOptions_getTryHarder(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getTryRotate(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getTryInvert(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getTryDownscale(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getIsPure(const zxing_ReaderOptions* opts); -bool zxing_ReaderOptions_getReturnErrors(const zxing_ReaderOptions* opts); -zxing_BarcodeFormats zxing_ReaderOptions_getFormats(const zxing_ReaderOptions* opts); -zxing_Binarizer zxing_ReaderOptions_getBinarizer(const zxing_ReaderOptions* opts); -zxing_EanAddOnSymbol zxing_ReaderOptions_getEanAddOnSymbol(const zxing_ReaderOptions* opts); -zxing_TextMode zxing_ReaderOptions_getTextMode(const zxing_ReaderOptions* opts); -int zxing_ReaderOptions_getMinLineCount(const zxing_ReaderOptions* opts); -int zxing_ReaderOptions_getMaxNumberOfSymbols(const zxing_ReaderOptions* opts); - -/* - * ZXing/Result.h - */ - -typedef enum -{ - zxing_ContentType_Text, - zxing_ContentType_Binary, - zxing_ContentType_Mixed, - zxing_ContentType_GS1, - zxing_ContentType_ISO15434, - zxing_ContentType_UnknownECI -} zxing_ContentType; - -char* zxing_ContentTypeToString(zxing_ContentType type); - -typedef struct zxing_PointI -{ - int x, y; -} zxing_PointI; - -typedef struct zxing_Position -{ - zxing_PointI topLeft, topRight, bottomRight, bottomLeft; -} zxing_Position; - -char* zxing_PositionToString(zxing_Position position); - -bool zxing_Barcode_isValid(const zxing_Barcode* barcode); -char* zxing_Barcode_errorMsg(const zxing_Barcode* barcode); -zxing_BarcodeFormat zxing_Barcode_format(const zxing_Barcode* barcode); -zxing_ContentType zxing_Barcode_contentType(const zxing_Barcode* barcode); -uint8_t* zxing_Barcode_bytes(const zxing_Barcode* barcode, int* len); -uint8_t* zxing_Barcode_bytesECI(const zxing_Barcode* barcode, int* len); -char* zxing_Barcode_text(const zxing_Barcode* barcode); -char* zxing_Barcode_ecLevel(const zxing_Barcode* barcode); -char* zxing_Barcode_symbologyIdentifier(const zxing_Barcode* barcode); -zxing_Position zxing_Barcode_position(const zxing_Barcode* barcode); -int zxing_Barcode_orientation(const zxing_Barcode* barcode); -bool zxing_Barcode_hasECI(const zxing_Barcode* barcode); -bool zxing_Barcode_isInverted(const zxing_Barcode* barcode); -bool zxing_Barcode_isMirrored(const zxing_Barcode* barcode); -int zxing_Barcode_lineCount(const zxing_Barcode* barcode); - -/* - * ZXing/ReadBarcode.h - */ - -/** Note: opts is optional, i.e. it can be NULL, which will imply default settings. */ -zxing_Barcode* zxing_ReadBarcode(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); -zxing_Barcodes* zxing_ReadBarcodes(const zxing_ImageView* iv, const zxing_ReaderOptions* opts); - -void zxing_Barcode_delete(zxing_Barcode* barcode); -void zxing_Barcodes_delete(zxing_Barcodes* barcodes); - -int zxing_Barcodes_size(const zxing_Barcodes* barcodes); -const zxing_Barcode* zxing_Barcodes_at(const zxing_Barcodes* barcodes, int i); -zxing_Barcode* zxing_Barcodes_move(zxing_Barcodes* barcodes, int i); - -char* zxing_LastErrorMsg(); - -void zxing_free(void* ptr); - -#ifdef __cplusplus -} -#endif - -#endif /* _ZXING_C_H */ diff --git a/wrappers/c/CMakeLists.txt b/wrappers/c/CMakeLists.txt index 0de0d12036..9754a9f70b 100644 --- a/wrappers/c/CMakeLists.txt +++ b/wrappers/c/CMakeLists.txt @@ -1,7 +1,7 @@ zxing_add_package_stb() if (BUILD_READERS) - add_executable (zxing-c-test zxing-c-test.c) - target_link_libraries (zxing-c-test ZXing::ZXing stb::stb) - add_test(NAME zxing-c-test COMMAND zxing-c-test ${CMAKE_SOURCE_DIR}/test/samples/qrcode-1/1.png) + add_executable (ZXingCTest ZXingCTest.c) + target_link_libraries (ZXingCTest ZXing::ZXing stb::stb) + add_test(NAME ZXingCTest COMMAND ZXingCTest ${CMAKE_SOURCE_DIR}/test/samples/qrcode-1/1.png) endif() diff --git a/wrappers/c/README.md b/wrappers/c/README.md index 62b920ae97..aa0d8c1a94 100644 --- a/wrappers/c/README.md +++ b/wrappers/c/README.md @@ -6,48 +6,49 @@ This is a preview/proposal for a C-API to zxing-cpp. If you have any comments or It is currently included in the default build to be trivially accessible for everyone. -Probably the easiest way to play with the C-API is to just modify the [zxing-c-test.c](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/zxing-c-test.c) file. +Probably the easiest way to play with the C-API is to just modify the [ZXingCTest.c](https://github.com/zxing-cpp/zxing-cpp/blob/master/wrappers/c/ZXingCTest.c) file. ## Usage The following is close to the most trivial use case scenario that is supported. ```c -#include "ZXing/zxing-c.h" +#include "ZXing/ZXingC.h" int main(int argc, char** argv) { int width, height; unsigned char* data; - /* load your image data from somewhere. ImageFormat_Lum assumes grey scale image data. */ + /* load your image data from somewhere. ZXing_ImageFormat_Lum assumes grey scale image data. */ - zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); + ZXing_ImageView* iv = ZXing_ImageView_new(data, width, height, ZXing_ImageFormat_Lum, 0, 0); - zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); - /* set ReaderOptions properties, if requried */ + ZXing_ReaderOptions* opts = ZXing_ReaderOptions_new(); + /* set ReaderOptions properties, if requried, e.g. */ + ZXing_ReaderOptions_setFormats(ZXing_BarcodeFormat_QRCode | ZXing_BarcodeFromat_EAN13); - zxing_Barcodes* barcodes = zxing_ReadBarcodes(iv, opts); + ZXing_Barcodes* barcodes = ZXing_ReadBarcodes(iv, opts); - zxing_ImageView_delete(iv); - zxing_ReaderOptions_delete(opts); + ZXing_ImageView_delete(iv); + ZXing_ReaderOptions_delete(opts); if (barcodes) { - for (int i = 0, n = zxing_Barcodes_size(barcodes); i < n; ++i) { - const zxing_Barcode* barcode = zxing_Barcodes_at(barcodes, i); + for (int i = 0, n = ZXing_Barcodes_size(barcodes); i < n; ++i) { + const ZXing_Barcode* barcode = ZXing_Barcodes_at(barcodes, i); - char* format = zxing_BarcodeFormatToString(zxing_Barcode_format(barcode)); + char* format = ZXing_BarcodeFormatToString(ZXing_Barcode_format(barcode)); printf("Format : %s\n", format); - zxing_free(format); + ZXing_free(format); - char* text = zxing_Barcode_text(barcode); + char* text = ZXing_Barcode_text(barcode); printf("Text : %s\n", text); - zxing_free(text); + ZXing_free(text); } - zxing_Barcodes_delete(barcodes); + ZXing_Barcodes_delete(barcodes); } else { - char* error = zxing_LastErrorMsg(); + char* error = ZXing_LastErrorMsg(); fprintf(stderr, "%s\n", error); - zxing_free(error); + ZXing_free(error); } return 0; diff --git a/wrappers/c/ZXingCTest.c b/wrappers/c/ZXingCTest.c new file mode 100644 index 0000000000..fd1b73def3 --- /dev/null +++ b/wrappers/c/ZXingCTest.c @@ -0,0 +1,108 @@ +/* +* Copyright 2023 siiky +* Copyright 2023 Axel Waggershauser +*/ +// SPDX-License-Identifier: Apache-2.0 + +#include "ZXingC.h" + +#include +#include + +#define STB_IMAGE_IMPLEMENTATION +#define STBI_NO_LINEAR // prevent dependency on -lm +#define STBI_NO_HDR +#include + +int usage(char* pname) +{ + fprintf(stderr, "Usage: %s FILE [FORMATS]\n", pname); + return 1; +} + +bool parse_args(int argc, char** argv, char** filename, ZXing_BarcodeFormats* formats) +{ + if (argc < 2) + return false; + *filename = argv[1]; + if (argc >= 3) { + *formats = ZXing_BarcodeFormatsFromString(argv[2]); + if (*formats == ZXing_BarcodeFormat_Invalid) { + fprintf(stderr, "%s\n", ZXing_LastErrorMsg()); + return false; + } + } + return true; +} + +void printF(const char* fmt, char* text) +{ + if (!text) + return; + if (*text) + printf(fmt, text); + ZXing_free(text); +} + +int main(int argc, char** argv) +{ + int ret = 0; + char* filename = NULL; + ZXing_BarcodeFormats formats = ZXing_BarcodeFormat_None; + + if (!parse_args(argc, argv, &filename, &formats)) + return usage(argv[0]); + + int width = 0; + int height = 0; + int channels = 0; + stbi_uc* data = stbi_load(filename, &width, &height, &channels, STBI_grey); + if (!data) { + fprintf(stderr, "Could not read image '%s'\n", filename); + return 2; + } + + ZXing_ReaderOptions* opts = ZXing_ReaderOptions_new(); + ZXing_ReaderOptions_setTextMode(opts, ZXing_TextMode_HRI); + ZXing_ReaderOptions_setEanAddOnSymbol(opts, ZXing_EanAddOnSymbol_Ignore); + ZXing_ReaderOptions_setFormats(opts, formats); + ZXing_ReaderOptions_setReturnErrors(opts, true); + + ZXing_ImageView* iv = ZXing_ImageView_new(data, width, height, ZXing_ImageFormat_Lum, 0, 0); + + ZXing_Barcodes* barcodes = ZXing_ReadBarcodes(iv, opts); + + ZXing_ImageView_delete(iv); + ZXing_ReaderOptions_delete(opts); + stbi_image_free(data); + + if (barcodes) { + for (int i = 0, n = ZXing_Barcodes_size(barcodes); i < n; ++i) { + const ZXing_Barcode* barcode = ZXing_Barcodes_at(barcodes, i); + + printF("Text : %s\n", ZXing_Barcode_text(barcode)); + printF("Format : %s\n", ZXing_BarcodeFormatToString(ZXing_Barcode_format(barcode))); + printF("Content : %s\n", ZXing_ContentTypeToString(ZXing_Barcode_contentType(barcode))); + printF("Identifier : %s\n", ZXing_Barcode_symbologyIdentifier(barcode)); + printF("EC Level : %s\n", ZXing_Barcode_ecLevel(barcode)); + printF("Error : %s\n", ZXing_Barcode_errorMsg(barcode)); + printF("Position : %s\n", ZXing_PositionToString(ZXing_Barcode_position(barcode))); + printf("Rotation : %d\n", ZXing_Barcode_orientation(barcode)); + + if (i < n-1) + printf("\n"); + } + + if (ZXing_Barcodes_size(barcodes) == 0) + printf("No barcode found\n"); + + ZXing_Barcodes_delete(barcodes); + } else { + char* error = ZXing_LastErrorMsg(); + fprintf(stderr, "%s\n", error); + ZXing_free(error); + ret = 2; + } + + return ret; +} diff --git a/wrappers/c/zxing-c-test.c b/wrappers/c/zxing-c-test.c deleted file mode 100644 index e5bef602c0..0000000000 --- a/wrappers/c/zxing-c-test.c +++ /dev/null @@ -1,108 +0,0 @@ -/* -* Copyright 2023 siiky -* Copyright 2023 Axel Waggershauser -*/ -// SPDX-License-Identifier: Apache-2.0 - -#include "zxing-c.h" - -#include -#include - -#define STB_IMAGE_IMPLEMENTATION -#define STBI_NO_LINEAR // prevent dependency on -lm -#define STBI_NO_HDR -#include - -int usage(char* pname) -{ - fprintf(stderr, "Usage: %s FILE [FORMATS]\n", pname); - return 1; -} - -bool parse_args(int argc, char** argv, char** filename, zxing_BarcodeFormats* formats) -{ - if (argc < 2) - return false; - *filename = argv[1]; - if (argc >= 3) { - *formats = zxing_BarcodeFormatsFromString(argv[2]); - if (*formats == zxing_BarcodeFormat_Invalid) { - fprintf(stderr, "%s\n", zxing_LastErrorMsg()); - return false; - } - } - return true; -} - -void printF(const char* fmt, char* text) -{ - if (!text) - return; - if (*text) - printf(fmt, text); - zxing_free(text); -} - -int main(int argc, char** argv) -{ - int ret = 0; - char* filename = NULL; - zxing_BarcodeFormats formats = zxing_BarcodeFormat_None; - - if (!parse_args(argc, argv, &filename, &formats)) - return usage(argv[0]); - - int width = 0; - int height = 0; - int channels = 0; - stbi_uc* data = stbi_load(filename, &width, &height, &channels, STBI_grey); - if (!data) { - fprintf(stderr, "Could not read image '%s'\n", filename); - return 2; - } - - zxing_ReaderOptions* opts = zxing_ReaderOptions_new(); - zxing_ReaderOptions_setTextMode(opts, zxing_TextMode_HRI); - zxing_ReaderOptions_setEanAddOnSymbol(opts, zxing_EanAddOnSymbol_Ignore); - zxing_ReaderOptions_setFormats(opts, formats); - zxing_ReaderOptions_setReturnErrors(opts, true); - - zxing_ImageView* iv = zxing_ImageView_new(data, width, height, zxing_ImageFormat_Lum, 0, 0); - - zxing_Barcodes* barcodes = zxing_ReadBarcodes(iv, opts); - - zxing_ImageView_delete(iv); - zxing_ReaderOptions_delete(opts); - stbi_image_free(data); - - if (barcodes) { - for (int i = 0, n = zxing_Barcodes_size(barcodes); i < n; ++i) { - const zxing_Barcode* barcode = zxing_Barcodes_at(barcodes, i); - - printF("Text : %s\n", zxing_Barcode_text(barcode)); - printF("Format : %s\n", zxing_BarcodeFormatToString(zxing_Barcode_format(barcode))); - printF("Content : %s\n", zxing_ContentTypeToString(zxing_Barcode_contentType(barcode))); - printF("Identifier : %s\n", zxing_Barcode_symbologyIdentifier(barcode)); - printF("EC Level : %s\n", zxing_Barcode_ecLevel(barcode)); - printF("Error : %s\n", zxing_Barcode_errorMsg(barcode)); - printF("Position : %s\n", zxing_PositionToString(zxing_Barcode_position(barcode))); - printf("Rotation : %d\n", zxing_Barcode_orientation(barcode)); - - if (i < n-1) - printf("\n"); - } - - if (zxing_Barcodes_size(barcodes) == 0) - printf("No barcode found\n"); - - zxing_Barcodes_delete(barcodes); - } else { - char* error = zxing_LastErrorMsg(); - fprintf(stderr, "%s\n", error); - zxing_free(error); - ret = 2; - } - - return ret; -} diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 61545ceef3..a02081174c 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -16,65 +16,65 @@ internal class Dll { private const string DllName = "ZXing"; - [DllImport(DllName)] public static extern IntPtr zxing_ReaderOptions_new(); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_delete(IntPtr opts); - - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryHarder(IntPtr opts, bool tryHarder); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryHarder(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryRotate(IntPtr opts, bool tryRotate); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryRotate(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryInvert(IntPtr opts, bool tryInvert); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryInvert(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTryDownscale(IntPtr opts, bool tryDownscale); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getTryDownscale(IntPtr opts); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getIsPure(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setIsPure(IntPtr opts, bool isPure); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setReturnErrors(IntPtr opts, bool returnErrors); - [DllImport(DllName)] public static extern bool zxing_ReaderOptions_getReturnErrors(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setFormats(IntPtr opts, BarcodeFormats formats); - [DllImport(DllName)] public static extern BarcodeFormats zxing_ReaderOptions_getFormats(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setBinarizer(IntPtr opts, Binarizer binarizer); - [DllImport(DllName)] public static extern Binarizer zxing_ReaderOptions_getBinarizer(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setEanAddOnSymbol(IntPtr opts, EanAddOnSymbol eanAddOnSymbol); - [DllImport(DllName)] public static extern EanAddOnSymbol zxing_ReaderOptions_getEanAddOnSymbol(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setTextMode(IntPtr opts, TextMode textMode); - [DllImport(DllName)] public static extern TextMode zxing_ReaderOptions_getTextMode(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setMinLineCount(IntPtr opts, int n); - [DllImport(DllName)] public static extern int zxing_ReaderOptions_getMinLineCount(IntPtr opts); - [DllImport(DllName)] public static extern void zxing_ReaderOptions_setMaxNumberOfSymbols(IntPtr opts, int n); - [DllImport(DllName)] public static extern int zxing_ReaderOptions_getMaxNumberOfSymbols(IntPtr opts); - - [DllImport(DllName)] public static extern IntPtr zxing_PositionToString(Position position); - [DllImport(DllName)] public static extern BarcodeFormats zxing_BarcodeFormatsFromString(string str); - - [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new(IntPtr data, int width, int height, ImageFormat format, int rowStride, int pixStride); - [DllImport(DllName)] public static extern IntPtr zxing_ImageView_new_checked(byte[] data, int size, int width, int height, ImageFormat format, int rowStride, int pixStride); - [DllImport(DllName)] public static extern void zxing_ImageView_delete(IntPtr iv); - - [DllImport(DllName)] public static extern IntPtr zxing_ReadBarcodes(IntPtr iv, IntPtr opts); - [DllImport(DllName)] public static extern void zxing_Barcode_delete(IntPtr result); - [DllImport(DllName)] public static extern void zxing_Barcodes_delete(IntPtr results); - [DllImport(DllName)] public static extern int zxing_Barcodes_size(IntPtr results); - [DllImport(DllName)] public static extern IntPtr zxing_Barcodes_move(IntPtr results, int i); - - [DllImport(DllName)] public static extern bool zxing_Barcode_isValid(IntPtr result); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_errorMsg(IntPtr result); - [DllImport(DllName)] public static extern BarcodeFormat zxing_Barcode_format(IntPtr result); - [DllImport(DllName)] public static extern ContentType zxing_Barcode_contentType(IntPtr result); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_bytes(IntPtr result, out int len); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_bytesECI(IntPtr result, out int len); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_text(IntPtr result); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_ecLevel(IntPtr result); - [DllImport(DllName)] public static extern IntPtr zxing_Barcode_symbologyIdentifier(IntPtr result); - [DllImport(DllName)] public static extern Position zxing_Barcode_position(IntPtr result); - [DllImport(DllName)] public static extern int zxing_Barcode_orientation(IntPtr result); - [DllImport(DllName)] public static extern bool zxing_Barcode_hasECI(IntPtr result); - [DllImport(DllName)] public static extern bool zxing_Barcode_isInverted(IntPtr result); - [DllImport(DllName)] public static extern bool zxing_Barcode_isMirrored(IntPtr result); - [DllImport(DllName)] public static extern int zxing_Barcode_lineCount(IntPtr result); - - [DllImport(DllName)] public static extern void zxing_free(IntPtr opts); - [DllImport(DllName)] public static extern IntPtr zxing_LastErrorMsg(); + [DllImport(DllName)] public static extern IntPtr ZXing_ReaderOptions_new(); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_delete(IntPtr opts); + + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTryHarder(IntPtr opts, bool tryHarder); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getTryHarder(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTryRotate(IntPtr opts, bool tryRotate); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getTryRotate(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTryInvert(IntPtr opts, bool tryInvert); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getTryInvert(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTryDownscale(IntPtr opts, bool tryDownscale); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getTryDownscale(IntPtr opts); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getIsPure(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setIsPure(IntPtr opts, bool isPure); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setReturnErrors(IntPtr opts, bool returnErrors); + [DllImport(DllName)] public static extern bool ZXing_ReaderOptions_getReturnErrors(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setFormats(IntPtr opts, BarcodeFormats formats); + [DllImport(DllName)] public static extern BarcodeFormats ZXing_ReaderOptions_getFormats(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setBinarizer(IntPtr opts, Binarizer binarizer); + [DllImport(DllName)] public static extern Binarizer ZXing_ReaderOptions_getBinarizer(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setEanAddOnSymbol(IntPtr opts, EanAddOnSymbol eanAddOnSymbol); + [DllImport(DllName)] public static extern EanAddOnSymbol ZXing_ReaderOptions_getEanAddOnSymbol(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setTextMode(IntPtr opts, TextMode textMode); + [DllImport(DllName)] public static extern TextMode ZXing_ReaderOptions_getTextMode(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setMinLineCount(IntPtr opts, int n); + [DllImport(DllName)] public static extern int ZXing_ReaderOptions_getMinLineCount(IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_ReaderOptions_setMaxNumberOfSymbols(IntPtr opts, int n); + [DllImport(DllName)] public static extern int ZXing_ReaderOptions_getMaxNumberOfSymbols(IntPtr opts); + + [DllImport(DllName)] public static extern IntPtr ZXing_PositionToString(Position position); + [DllImport(DllName)] public static extern BarcodeFormats ZXing_BarcodeFormatsFromString(string str); + + [DllImport(DllName)] public static extern IntPtr ZXing_ImageView_new(IntPtr data, int width, int height, ImageFormat format, int rowStride, int pixStride); + [DllImport(DllName)] public static extern IntPtr ZXing_ImageView_new_checked(byte[] data, int size, int width, int height, ImageFormat format, int rowStride, int pixStride); + [DllImport(DllName)] public static extern void ZXing_ImageView_delete(IntPtr iv); + + [DllImport(DllName)] public static extern IntPtr ZXing_ReadBarcodes(IntPtr iv, IntPtr opts); + [DllImport(DllName)] public static extern void ZXing_Barcode_delete(IntPtr result); + [DllImport(DllName)] public static extern void ZXing_Barcodes_delete(IntPtr results); + [DllImport(DllName)] public static extern int ZXing_Barcodes_size(IntPtr results); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcodes_move(IntPtr results, int i); + + [DllImport(DllName)] public static extern bool ZXing_Barcode_isValid(IntPtr result); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_errorMsg(IntPtr result); + [DllImport(DllName)] public static extern BarcodeFormat ZXing_Barcode_format(IntPtr result); + [DllImport(DllName)] public static extern ContentType ZXing_Barcode_contentType(IntPtr result); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytes(IntPtr result, out int len); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytesECI(IntPtr result, out int len); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_text(IntPtr result); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_ecLevel(IntPtr result); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_symbologyIdentifier(IntPtr result); + [DllImport(DllName)] public static extern Position ZXing_Barcode_position(IntPtr result); + [DllImport(DllName)] public static extern int ZXing_Barcode_orientation(IntPtr result); + [DllImport(DllName)] public static extern bool ZXing_Barcode_hasECI(IntPtr result); + [DllImport(DllName)] public static extern bool ZXing_Barcode_isInverted(IntPtr result); + [DllImport(DllName)] public static extern bool ZXing_Barcode_isMirrored(IntPtr result); + [DllImport(DllName)] public static extern int ZXing_Barcode_lineCount(IntPtr result); + + [DllImport(DllName)] public static extern void ZXing_free(IntPtr opts); + [DllImport(DllName)] public static extern IntPtr ZXing_LastErrorMsg(); public static string MarshalAsString(IntPtr ptr) @@ -83,7 +83,7 @@ public static string MarshalAsString(IntPtr ptr) throw new Exception("ZXing C-API returned a NULL char*."); string res = Marshal.PtrToStringUTF8(ptr) ?? ""; - zxing_free(ptr); + ZXing_free(ptr); return res; } @@ -97,7 +97,7 @@ public static byte[] MarshalAsBytes(RetBytesFunc func, IntPtr d) byte[] res = new byte[len]; Marshal.Copy(ptr, res, 0, len); - zxing_free(ptr); + ZXing_free(ptr); return res; } } @@ -178,7 +178,7 @@ public struct Position { public PointI TopLeft, TopRight, BottomRight, BottomLeft; - public override string ToString() => MarshalAsString(zxing_PositionToString(this)); + public override string ToString() => MarshalAsString(ZXing_PositionToString(this)); }; public class ImageView @@ -187,19 +187,19 @@ public class ImageView public ImageView(byte[] data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) { - _d = zxing_ImageView_new_checked(data, data.Length, width, height, format, rowStride, pixStride); + _d = ZXing_ImageView_new_checked(data, data.Length, width, height, format, rowStride, pixStride); if (_d == IntPtr.Zero) - throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + throw new Exception(MarshalAsString(ZXing_LastErrorMsg())); } public ImageView(IntPtr data, int width, int height, ImageFormat format, int rowStride = 0, int pixStride = 0) { - _d = zxing_ImageView_new(data, width, height, format, rowStride, pixStride); + _d = ZXing_ImageView_new(data, width, height, format, rowStride, pixStride); if (_d == IntPtr.Zero) - throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + throw new Exception(MarshalAsString(ZXing_LastErrorMsg())); } - ~ImageView() => zxing_ImageView_delete(_d); + ~ImageView() => ZXing_ImageView_delete(_d); } public class ReaderOptions @@ -208,83 +208,83 @@ public class ReaderOptions public ReaderOptions() { - _d = zxing_ReaderOptions_new(); + _d = ZXing_ReaderOptions_new(); if (_d == IntPtr.Zero) throw new Exception("Failed to create ReaderOptions."); } - ~ReaderOptions() => zxing_ReaderOptions_delete(_d); + ~ReaderOptions() => ZXing_ReaderOptions_delete(_d); public bool TryHarder { - get => zxing_ReaderOptions_getTryHarder(_d); - set => zxing_ReaderOptions_setTryHarder(_d, value); + get => ZXing_ReaderOptions_getTryHarder(_d); + set => ZXing_ReaderOptions_setTryHarder(_d, value); } public bool TryRotate { - get => zxing_ReaderOptions_getTryRotate(_d); - set => zxing_ReaderOptions_setTryRotate(_d, value); + get => ZXing_ReaderOptions_getTryRotate(_d); + set => ZXing_ReaderOptions_setTryRotate(_d, value); } public bool TryInvert { - get => zxing_ReaderOptions_getTryInvert(_d); - set => zxing_ReaderOptions_setTryInvert(_d, value); + get => ZXing_ReaderOptions_getTryInvert(_d); + set => ZXing_ReaderOptions_setTryInvert(_d, value); } public bool TryDownscale { - get => zxing_ReaderOptions_getTryDownscale(_d); - set => zxing_ReaderOptions_setTryDownscale(_d, value); + get => ZXing_ReaderOptions_getTryDownscale(_d); + set => ZXing_ReaderOptions_setTryDownscale(_d, value); } public bool IsPure { - get => zxing_ReaderOptions_getIsPure(_d); - set => zxing_ReaderOptions_setIsPure(_d, value); + get => ZXing_ReaderOptions_getIsPure(_d); + set => ZXing_ReaderOptions_setIsPure(_d, value); } public bool ReturnErrors { - get => zxing_ReaderOptions_getReturnErrors(_d); - set => zxing_ReaderOptions_setReturnErrors(_d, value); + get => ZXing_ReaderOptions_getReturnErrors(_d); + set => ZXing_ReaderOptions_setReturnErrors(_d, value); } public BarcodeFormats Formats { - get => zxing_ReaderOptions_getFormats(_d); - set => zxing_ReaderOptions_setFormats(_d, value); + get => ZXing_ReaderOptions_getFormats(_d); + set => ZXing_ReaderOptions_setFormats(_d, value); } public Binarizer Binarizer { - get => zxing_ReaderOptions_getBinarizer(_d); - set => zxing_ReaderOptions_setBinarizer(_d, value); + get => ZXing_ReaderOptions_getBinarizer(_d); + set => ZXing_ReaderOptions_setBinarizer(_d, value); } public EanAddOnSymbol EanAddOnSymbol { - get => zxing_ReaderOptions_getEanAddOnSymbol(_d); - set => zxing_ReaderOptions_setEanAddOnSymbol(_d, value); + get => ZXing_ReaderOptions_getEanAddOnSymbol(_d); + set => ZXing_ReaderOptions_setEanAddOnSymbol(_d, value); } public TextMode TextMode { - get => zxing_ReaderOptions_getTextMode(_d); - set => zxing_ReaderOptions_setTextMode(_d, value); + get => ZXing_ReaderOptions_getTextMode(_d); + set => ZXing_ReaderOptions_setTextMode(_d, value); } public int MinLineCount { - get => zxing_ReaderOptions_getMinLineCount(_d); - set => zxing_ReaderOptions_setMinLineCount(_d, value); + get => ZXing_ReaderOptions_getMinLineCount(_d); + set => ZXing_ReaderOptions_setMinLineCount(_d, value); } public int MaxNumberOfSymbols { - get => zxing_ReaderOptions_getMaxNumberOfSymbols(_d); - set => zxing_ReaderOptions_setMaxNumberOfSymbols(_d, value); + get => ZXing_ReaderOptions_getMaxNumberOfSymbols(_d); + set => ZXing_ReaderOptions_setMaxNumberOfSymbols(_d, value); } } @@ -294,46 +294,46 @@ public class Barcode internal IntPtr _d; internal Barcode(IntPtr d) => _d = d; - ~Barcode() => zxing_Barcode_delete(_d); - - public bool IsValid => zxing_Barcode_isValid(_d); - public BarcodeFormat Format => zxing_Barcode_format(_d); - public ContentType ContentType => zxing_Barcode_contentType(_d); - public string Text => MarshalAsString(zxing_Barcode_text(_d)); - public byte[] Bytes => MarshalAsBytes(zxing_Barcode_bytes, _d); - public byte[] BytesECI => MarshalAsBytes(zxing_Barcode_bytesECI, _d); - public string ECLevel => MarshalAsString(zxing_Barcode_ecLevel(_d)); - public string SymbologyIdentifier => MarshalAsString(zxing_Barcode_symbologyIdentifier(_d)); - public string ErrorMsg => MarshalAsString(zxing_Barcode_errorMsg(_d)); - public Position Position => zxing_Barcode_position(_d); - public int Orientation => zxing_Barcode_orientation(_d); - public bool HasECI => zxing_Barcode_hasECI(_d); - public bool IsInverted => zxing_Barcode_isInverted(_d); - public bool IsMirrored => zxing_Barcode_isMirrored(_d); - public int LineCount => zxing_Barcode_lineCount(_d); + ~Barcode() => ZXing_Barcode_delete(_d); + + public bool IsValid => ZXing_Barcode_isValid(_d); + public BarcodeFormat Format => ZXing_Barcode_format(_d); + public ContentType ContentType => ZXing_Barcode_contentType(_d); + public string Text => MarshalAsString(ZXing_Barcode_text(_d)); + public byte[] Bytes => MarshalAsBytes(ZXing_Barcode_bytes, _d); + public byte[] BytesECI => MarshalAsBytes(ZXing_Barcode_bytesECI, _d); + public string ECLevel => MarshalAsString(ZXing_Barcode_ecLevel(_d)); + public string SymbologyIdentifier => MarshalAsString(ZXing_Barcode_symbologyIdentifier(_d)); + public string ErrorMsg => MarshalAsString(ZXing_Barcode_errorMsg(_d)); + public Position Position => ZXing_Barcode_position(_d); + public int Orientation => ZXing_Barcode_orientation(_d); + public bool HasECI => ZXing_Barcode_hasECI(_d); + public bool IsInverted => ZXing_Barcode_isInverted(_d); + public bool IsMirrored => ZXing_Barcode_isMirrored(_d); + public int LineCount => ZXing_Barcode_lineCount(_d); } public class BarcodeReader : ReaderOptions { public static BarcodeFormats FormatsFromString(string str) { - var fmts = zxing_BarcodeFormatsFromString(str); - if ((int)fmts == -1) // see zxing_BarcodeFormat_Invalid - throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + var fmts = ZXing_BarcodeFormatsFromString(str); + if ((int)fmts == -1) // see ZXing_BarcodeFormat_Invalid + throw new Exception(MarshalAsString(ZXing_LastErrorMsg())); return fmts; } public static List Read(ImageView iv, ReaderOptions? opts = null) { - var ptr = zxing_ReadBarcodes(iv._d, opts?._d ?? IntPtr.Zero); + var ptr = ZXing_ReadBarcodes(iv._d, opts?._d ?? IntPtr.Zero); if (ptr == IntPtr.Zero) - throw new Exception(MarshalAsString(zxing_LastErrorMsg())); + throw new Exception(MarshalAsString(ZXing_LastErrorMsg())); - var size = zxing_Barcodes_size(ptr); + var size = ZXing_Barcodes_size(ptr); var res = new List(size); for (int i = 0; i < size; ++i) - res.Add(new Barcode(zxing_Barcodes_move(ptr, i))); - zxing_Barcodes_delete(ptr); + res.Add(new Barcode(ZXing_Barcodes_move(ptr, i))); + ZXing_Barcodes_delete(ptr); return res; } diff --git a/wrappers/rust/build.rs b/wrappers/rust/build.rs index 6d754a58d6..8f43f3a1be 100644 --- a/wrappers/rust/build.rs +++ b/wrappers/rust/build.rs @@ -29,7 +29,7 @@ fn main() -> miette::Result<()> { } // manual bindings.rs generation: - // bindgen core/src/zxing-c.h -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --no-doc-comments --no-layout-tests --with-derive-partialeq --allowlist-item "zxing.*" + // bindgen core/src/ZXingC.h -o src/bindings.rs --no-prepend-enum-name --merge-extern-blocks --use-core --no-doc-comments --no-layout-tests --with-derive-partialeq --allowlist-item "ZXing.*" Ok(()) } diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 52e659b622..00a465a6c8 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -2,175 +2,175 @@ #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_ImageView { +pub struct ZXing_ImageView { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_ReaderOptions { +pub struct ZXing_ReaderOptions { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_Barcode { +pub struct ZXing_Barcode { _unused: [u8; 0], } #[repr(C)] #[derive(Debug, Copy, Clone)] -pub struct zxing_Barcodes { +pub struct ZXing_Barcodes { _unused: [u8; 0], } -pub const zxing_ImageFormat_None: zxing_ImageFormat = 0; -pub const zxing_ImageFormat_Lum: zxing_ImageFormat = 16777216; -pub const zxing_ImageFormat_RGB: zxing_ImageFormat = 50331906; -pub const zxing_ImageFormat_BGR: zxing_ImageFormat = 50462976; -pub const zxing_ImageFormat_RGBX: zxing_ImageFormat = 67109122; -pub const zxing_ImageFormat_XRGB: zxing_ImageFormat = 67174915; -pub const zxing_ImageFormat_BGRX: zxing_ImageFormat = 67240192; -pub const zxing_ImageFormat_XBGR: zxing_ImageFormat = 67305985; -pub type zxing_ImageFormat = ::core::ffi::c_uint; -pub const zxing_BarcodeFormat_None: zxing_BarcodeFormat = 0; -pub const zxing_BarcodeFormat_Aztec: zxing_BarcodeFormat = 1; -pub const zxing_BarcodeFormat_Codabar: zxing_BarcodeFormat = 2; -pub const zxing_BarcodeFormat_Code39: zxing_BarcodeFormat = 4; -pub const zxing_BarcodeFormat_Code93: zxing_BarcodeFormat = 8; -pub const zxing_BarcodeFormat_Code128: zxing_BarcodeFormat = 16; -pub const zxing_BarcodeFormat_DataBar: zxing_BarcodeFormat = 32; -pub const zxing_BarcodeFormat_DataBarExpanded: zxing_BarcodeFormat = 64; -pub const zxing_BarcodeFormat_DataMatrix: zxing_BarcodeFormat = 128; -pub const zxing_BarcodeFormat_EAN8: zxing_BarcodeFormat = 256; -pub const zxing_BarcodeFormat_EAN13: zxing_BarcodeFormat = 512; -pub const zxing_BarcodeFormat_ITF: zxing_BarcodeFormat = 1024; -pub const zxing_BarcodeFormat_MaxiCode: zxing_BarcodeFormat = 2048; -pub const zxing_BarcodeFormat_PDF417: zxing_BarcodeFormat = 4096; -pub const zxing_BarcodeFormat_QRCode: zxing_BarcodeFormat = 8192; -pub const zxing_BarcodeFormat_UPCA: zxing_BarcodeFormat = 16384; -pub const zxing_BarcodeFormat_UPCE: zxing_BarcodeFormat = 32768; -pub const zxing_BarcodeFormat_MicroQRCode: zxing_BarcodeFormat = 65536; -pub const zxing_BarcodeFormat_RMQRCode: zxing_BarcodeFormat = 131072; -pub const zxing_BarcodeFormat_DXFilmEdge: zxing_BarcodeFormat = 262144; -pub const zxing_BarcodeFormat_LinearCodes: zxing_BarcodeFormat = 313214; -pub const zxing_BarcodeFormat_MatrixCodes: zxing_BarcodeFormat = 211073; -pub const zxing_BarcodeFormat_Any: zxing_BarcodeFormat = 524287; -pub const zxing_BarcodeFormat_Invalid: zxing_BarcodeFormat = 4294967295; -pub type zxing_BarcodeFormat = ::core::ffi::c_uint; -pub use self::zxing_BarcodeFormat as zxing_BarcodeFormats; -pub const zxing_Binarizer_LocalAverage: zxing_Binarizer = 0; -pub const zxing_Binarizer_GlobalHistogram: zxing_Binarizer = 1; -pub const zxing_Binarizer_FixedThreshold: zxing_Binarizer = 2; -pub const zxing_Binarizer_BoolCast: zxing_Binarizer = 3; -pub type zxing_Binarizer = ::core::ffi::c_uint; -pub const zxing_EanAddOnSymbol_Ignore: zxing_EanAddOnSymbol = 0; -pub const zxing_EanAddOnSymbol_Read: zxing_EanAddOnSymbol = 1; -pub const zxing_EanAddOnSymbol_Require: zxing_EanAddOnSymbol = 2; -pub type zxing_EanAddOnSymbol = ::core::ffi::c_uint; -pub const zxing_TextMode_Plain: zxing_TextMode = 0; -pub const zxing_TextMode_ECI: zxing_TextMode = 1; -pub const zxing_TextMode_HRI: zxing_TextMode = 2; -pub const zxing_TextMode_Hex: zxing_TextMode = 3; -pub const zxing_TextMode_Escaped: zxing_TextMode = 4; -pub type zxing_TextMode = ::core::ffi::c_uint; -pub const zxing_ContentType_Text: zxing_ContentType = 0; -pub const zxing_ContentType_Binary: zxing_ContentType = 1; -pub const zxing_ContentType_Mixed: zxing_ContentType = 2; -pub const zxing_ContentType_GS1: zxing_ContentType = 3; -pub const zxing_ContentType_ISO15434: zxing_ContentType = 4; -pub const zxing_ContentType_UnknownECI: zxing_ContentType = 5; -pub type zxing_ContentType = ::core::ffi::c_uint; +pub const ZXing_ImageFormat_None: ZXing_ImageFormat = 0; +pub const ZXing_ImageFormat_Lum: ZXing_ImageFormat = 16777216; +pub const ZXing_ImageFormat_RGB: ZXing_ImageFormat = 50331906; +pub const ZXing_ImageFormat_BGR: ZXing_ImageFormat = 50462976; +pub const ZXing_ImageFormat_RGBX: ZXing_ImageFormat = 67109122; +pub const ZXing_ImageFormat_XRGB: ZXing_ImageFormat = 67174915; +pub const ZXing_ImageFormat_BGRX: ZXing_ImageFormat = 67240192; +pub const ZXing_ImageFormat_XBGR: ZXing_ImageFormat = 67305985; +pub type ZXing_ImageFormat = ::core::ffi::c_uint; +pub const ZXing_BarcodeFormat_None: ZXing_BarcodeFormat = 0; +pub const ZXing_BarcodeFormat_Aztec: ZXing_BarcodeFormat = 1; +pub const ZXing_BarcodeFormat_Codabar: ZXing_BarcodeFormat = 2; +pub const ZXing_BarcodeFormat_Code39: ZXing_BarcodeFormat = 4; +pub const ZXing_BarcodeFormat_Code93: ZXing_BarcodeFormat = 8; +pub const ZXing_BarcodeFormat_Code128: ZXing_BarcodeFormat = 16; +pub const ZXing_BarcodeFormat_DataBar: ZXing_BarcodeFormat = 32; +pub const ZXing_BarcodeFormat_DataBarExpanded: ZXing_BarcodeFormat = 64; +pub const ZXing_BarcodeFormat_DataMatrix: ZXing_BarcodeFormat = 128; +pub const ZXing_BarcodeFormat_EAN8: ZXing_BarcodeFormat = 256; +pub const ZXing_BarcodeFormat_EAN13: ZXing_BarcodeFormat = 512; +pub const ZXing_BarcodeFormat_ITF: ZXing_BarcodeFormat = 1024; +pub const ZXing_BarcodeFormat_MaxiCode: ZXing_BarcodeFormat = 2048; +pub const ZXing_BarcodeFormat_PDF417: ZXing_BarcodeFormat = 4096; +pub const ZXing_BarcodeFormat_QRCode: ZXing_BarcodeFormat = 8192; +pub const ZXing_BarcodeFormat_UPCA: ZXing_BarcodeFormat = 16384; +pub const ZXing_BarcodeFormat_UPCE: ZXing_BarcodeFormat = 32768; +pub const ZXing_BarcodeFormat_MicroQRCode: ZXing_BarcodeFormat = 65536; +pub const ZXing_BarcodeFormat_RMQRCode: ZXing_BarcodeFormat = 131072; +pub const ZXing_BarcodeFormat_DXFilmEdge: ZXing_BarcodeFormat = 262144; +pub const ZXing_BarcodeFormat_LinearCodes: ZXing_BarcodeFormat = 313214; +pub const ZXing_BarcodeFormat_MatrixCodes: ZXing_BarcodeFormat = 211073; +pub const ZXing_BarcodeFormat_Any: ZXing_BarcodeFormat = 524287; +pub const ZXing_BarcodeFormat_Invalid: ZXing_BarcodeFormat = 4294967295; +pub type ZXing_BarcodeFormat = ::core::ffi::c_uint; +pub use self::ZXing_BarcodeFormat as ZXing_BarcodeFormats; +pub const ZXing_Binarizer_LocalAverage: ZXing_Binarizer = 0; +pub const ZXing_Binarizer_GlobalHistogram: ZXing_Binarizer = 1; +pub const ZXing_Binarizer_FixedThreshold: ZXing_Binarizer = 2; +pub const ZXing_Binarizer_BoolCast: ZXing_Binarizer = 3; +pub type ZXing_Binarizer = ::core::ffi::c_uint; +pub const ZXing_EanAddOnSymbol_Ignore: ZXing_EanAddOnSymbol = 0; +pub const ZXing_EanAddOnSymbol_Read: ZXing_EanAddOnSymbol = 1; +pub const ZXing_EanAddOnSymbol_Require: ZXing_EanAddOnSymbol = 2; +pub type ZXing_EanAddOnSymbol = ::core::ffi::c_uint; +pub const ZXing_TextMode_Plain: ZXing_TextMode = 0; +pub const ZXing_TextMode_ECI: ZXing_TextMode = 1; +pub const ZXing_TextMode_HRI: ZXing_TextMode = 2; +pub const ZXing_TextMode_Hex: ZXing_TextMode = 3; +pub const ZXing_TextMode_Escaped: ZXing_TextMode = 4; +pub type ZXing_TextMode = ::core::ffi::c_uint; +pub const ZXing_ContentType_Text: ZXing_ContentType = 0; +pub const ZXing_ContentType_Binary: ZXing_ContentType = 1; +pub const ZXing_ContentType_Mixed: ZXing_ContentType = 2; +pub const ZXing_ContentType_GS1: ZXing_ContentType = 3; +pub const ZXing_ContentType_ISO15434: ZXing_ContentType = 4; +pub const ZXing_ContentType_UnknownECI: ZXing_ContentType = 5; +pub type ZXing_ContentType = ::core::ffi::c_uint; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] -pub struct zxing_PointI { +pub struct ZXing_PointI { pub x: ::core::ffi::c_int, pub y: ::core::ffi::c_int, } #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] -pub struct zxing_Position { - pub topLeft: zxing_PointI, - pub topRight: zxing_PointI, - pub bottomRight: zxing_PointI, - pub bottomLeft: zxing_PointI, +pub struct ZXing_Position { + pub topLeft: ZXing_PointI, + pub topRight: ZXing_PointI, + pub bottomRight: ZXing_PointI, + pub bottomLeft: ZXing_PointI, } extern "C" { - pub fn zxing_ImageView_new( + pub fn ZXing_ImageView_new( data: *const u8, width: ::core::ffi::c_int, height: ::core::ffi::c_int, - format: zxing_ImageFormat, + format: ZXing_ImageFormat, rowStride: ::core::ffi::c_int, pixStride: ::core::ffi::c_int, - ) -> *mut zxing_ImageView; - pub fn zxing_ImageView_new_checked( + ) -> *mut ZXing_ImageView; + pub fn ZXing_ImageView_new_checked( data: *const u8, size: ::core::ffi::c_int, width: ::core::ffi::c_int, height: ::core::ffi::c_int, - format: zxing_ImageFormat, + format: ZXing_ImageFormat, rowStride: ::core::ffi::c_int, pixStride: ::core::ffi::c_int, - ) -> *mut zxing_ImageView; - pub fn zxing_ImageView_delete(iv: *mut zxing_ImageView); - pub fn zxing_ImageView_crop( - iv: *mut zxing_ImageView, + ) -> *mut ZXing_ImageView; + pub fn ZXing_ImageView_delete(iv: *mut ZXing_ImageView); + pub fn ZXing_ImageView_crop( + iv: *mut ZXing_ImageView, left: ::core::ffi::c_int, top: ::core::ffi::c_int, width: ::core::ffi::c_int, height: ::core::ffi::c_int, ); - pub fn zxing_ImageView_rotate(iv: *mut zxing_ImageView, degree: ::core::ffi::c_int); - pub fn zxing_BarcodeFormatsFromString(str_: *const ::core::ffi::c_char) -> zxing_BarcodeFormats; - pub fn zxing_BarcodeFormatFromString(str_: *const ::core::ffi::c_char) -> zxing_BarcodeFormat; - pub fn zxing_BarcodeFormatToString(format: zxing_BarcodeFormat) -> *mut ::core::ffi::c_char; - pub fn zxing_ReaderOptions_new() -> *mut zxing_ReaderOptions; - pub fn zxing_ReaderOptions_delete(opts: *mut zxing_ReaderOptions); - pub fn zxing_ReaderOptions_setTryHarder(opts: *mut zxing_ReaderOptions, tryHarder: bool); - pub fn zxing_ReaderOptions_setTryRotate(opts: *mut zxing_ReaderOptions, tryRotate: bool); - pub fn zxing_ReaderOptions_setTryInvert(opts: *mut zxing_ReaderOptions, tryInvert: bool); - pub fn zxing_ReaderOptions_setTryDownscale(opts: *mut zxing_ReaderOptions, tryDownscale: bool); - pub fn zxing_ReaderOptions_setIsPure(opts: *mut zxing_ReaderOptions, isPure: bool); - pub fn zxing_ReaderOptions_setReturnErrors(opts: *mut zxing_ReaderOptions, returnErrors: bool); - pub fn zxing_ReaderOptions_setFormats(opts: *mut zxing_ReaderOptions, formats: zxing_BarcodeFormats); - pub fn zxing_ReaderOptions_setBinarizer(opts: *mut zxing_ReaderOptions, binarizer: zxing_Binarizer); - pub fn zxing_ReaderOptions_setEanAddOnSymbol(opts: *mut zxing_ReaderOptions, eanAddOnSymbol: zxing_EanAddOnSymbol); - pub fn zxing_ReaderOptions_setTextMode(opts: *mut zxing_ReaderOptions, textMode: zxing_TextMode); - pub fn zxing_ReaderOptions_setMinLineCount(opts: *mut zxing_ReaderOptions, n: ::core::ffi::c_int); - pub fn zxing_ReaderOptions_setMaxNumberOfSymbols(opts: *mut zxing_ReaderOptions, n: ::core::ffi::c_int); - pub fn zxing_ReaderOptions_getTryHarder(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getTryRotate(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getTryInvert(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getTryDownscale(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getIsPure(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getReturnErrors(opts: *const zxing_ReaderOptions) -> bool; - pub fn zxing_ReaderOptions_getFormats(opts: *const zxing_ReaderOptions) -> zxing_BarcodeFormats; - pub fn zxing_ReaderOptions_getBinarizer(opts: *const zxing_ReaderOptions) -> zxing_Binarizer; - pub fn zxing_ReaderOptions_getEanAddOnSymbol(opts: *const zxing_ReaderOptions) -> zxing_EanAddOnSymbol; - pub fn zxing_ReaderOptions_getTextMode(opts: *const zxing_ReaderOptions) -> zxing_TextMode; - pub fn zxing_ReaderOptions_getMinLineCount(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; - pub fn zxing_ReaderOptions_getMaxNumberOfSymbols(opts: *const zxing_ReaderOptions) -> ::core::ffi::c_int; - pub fn zxing_ContentTypeToString(type_: zxing_ContentType) -> *mut ::core::ffi::c_char; - pub fn zxing_PositionToString(position: zxing_Position) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_isValid(barcode: *const zxing_Barcode) -> bool; - pub fn zxing_Barcode_errorMsg(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_format(barcode: *const zxing_Barcode) -> zxing_BarcodeFormat; - pub fn zxing_Barcode_contentType(barcode: *const zxing_Barcode) -> zxing_ContentType; - pub fn zxing_Barcode_bytes(barcode: *const zxing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; - pub fn zxing_Barcode_bytesECI(barcode: *const zxing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; - pub fn zxing_Barcode_text(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_ecLevel(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_symbologyIdentifier(barcode: *const zxing_Barcode) -> *mut ::core::ffi::c_char; - pub fn zxing_Barcode_position(barcode: *const zxing_Barcode) -> zxing_Position; - pub fn zxing_Barcode_orientation(barcode: *const zxing_Barcode) -> ::core::ffi::c_int; - pub fn zxing_Barcode_hasECI(barcode: *const zxing_Barcode) -> bool; - pub fn zxing_Barcode_isInverted(barcode: *const zxing_Barcode) -> bool; - pub fn zxing_Barcode_isMirrored(barcode: *const zxing_Barcode) -> bool; - pub fn zxing_Barcode_lineCount(barcode: *const zxing_Barcode) -> ::core::ffi::c_int; - pub fn zxing_ReadBarcode(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Barcode; - pub fn zxing_ReadBarcodes(iv: *const zxing_ImageView, opts: *const zxing_ReaderOptions) -> *mut zxing_Barcodes; - pub fn zxing_Barcode_delete(barcode: *mut zxing_Barcode); - pub fn zxing_Barcodes_delete(barcodes: *mut zxing_Barcodes); - pub fn zxing_Barcodes_size(barcodes: *const zxing_Barcodes) -> ::core::ffi::c_int; - pub fn zxing_Barcodes_at(barcodes: *const zxing_Barcodes, i: ::core::ffi::c_int) -> *const zxing_Barcode; - pub fn zxing_Barcodes_move(barcodes: *mut zxing_Barcodes, i: ::core::ffi::c_int) -> *mut zxing_Barcode; - pub fn zxing_LastErrorMsg() -> *mut ::core::ffi::c_char; - pub fn zxing_free(ptr: *mut ::core::ffi::c_void); + pub fn ZXing_ImageView_rotate(iv: *mut ZXing_ImageView, degree: ::core::ffi::c_int); + pub fn ZXing_BarcodeFormatsFromString(str_: *const ::core::ffi::c_char) -> ZXing_BarcodeFormats; + pub fn ZXing_BarcodeFormatFromString(str_: *const ::core::ffi::c_char) -> ZXing_BarcodeFormat; + pub fn ZXing_BarcodeFormatToString(format: ZXing_BarcodeFormat) -> *mut ::core::ffi::c_char; + pub fn ZXing_ReaderOptions_new() -> *mut ZXing_ReaderOptions; + pub fn ZXing_ReaderOptions_delete(opts: *mut ZXing_ReaderOptions); + pub fn ZXing_ReaderOptions_setTryHarder(opts: *mut ZXing_ReaderOptions, tryHarder: bool); + pub fn ZXing_ReaderOptions_setTryRotate(opts: *mut ZXing_ReaderOptions, tryRotate: bool); + pub fn ZXing_ReaderOptions_setTryInvert(opts: *mut ZXing_ReaderOptions, tryInvert: bool); + pub fn ZXing_ReaderOptions_setTryDownscale(opts: *mut ZXing_ReaderOptions, tryDownscale: bool); + pub fn ZXing_ReaderOptions_setIsPure(opts: *mut ZXing_ReaderOptions, isPure: bool); + pub fn ZXing_ReaderOptions_setReturnErrors(opts: *mut ZXing_ReaderOptions, returnErrors: bool); + pub fn ZXing_ReaderOptions_setFormats(opts: *mut ZXing_ReaderOptions, formats: ZXing_BarcodeFormats); + pub fn ZXing_ReaderOptions_setBinarizer(opts: *mut ZXing_ReaderOptions, binarizer: ZXing_Binarizer); + pub fn ZXing_ReaderOptions_setEanAddOnSymbol(opts: *mut ZXing_ReaderOptions, eanAddOnSymbol: ZXing_EanAddOnSymbol); + pub fn ZXing_ReaderOptions_setTextMode(opts: *mut ZXing_ReaderOptions, textMode: ZXing_TextMode); + pub fn ZXing_ReaderOptions_setMinLineCount(opts: *mut ZXing_ReaderOptions, n: ::core::ffi::c_int); + pub fn ZXing_ReaderOptions_setMaxNumberOfSymbols(opts: *mut ZXing_ReaderOptions, n: ::core::ffi::c_int); + pub fn ZXing_ReaderOptions_getTryHarder(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getTryRotate(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getTryInvert(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getTryDownscale(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getIsPure(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getReturnErrors(opts: *const ZXing_ReaderOptions) -> bool; + pub fn ZXing_ReaderOptions_getFormats(opts: *const ZXing_ReaderOptions) -> ZXing_BarcodeFormats; + pub fn ZXing_ReaderOptions_getBinarizer(opts: *const ZXing_ReaderOptions) -> ZXing_Binarizer; + pub fn ZXing_ReaderOptions_getEanAddOnSymbol(opts: *const ZXing_ReaderOptions) -> ZXing_EanAddOnSymbol; + pub fn ZXing_ReaderOptions_getTextMode(opts: *const ZXing_ReaderOptions) -> ZXing_TextMode; + pub fn ZXing_ReaderOptions_getMinLineCount(opts: *const ZXing_ReaderOptions) -> ::core::ffi::c_int; + pub fn ZXing_ReaderOptions_getMaxNumberOfSymbols(opts: *const ZXing_ReaderOptions) -> ::core::ffi::c_int; + pub fn ZXing_ContentTypeToString(type_: ZXing_ContentType) -> *mut ::core::ffi::c_char; + pub fn ZXing_PositionToString(position: ZXing_Position) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_isValid(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_errorMsg(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_format(barcode: *const ZXing_Barcode) -> ZXing_BarcodeFormat; + pub fn ZXing_Barcode_contentType(barcode: *const ZXing_Barcode) -> ZXing_ContentType; + pub fn ZXing_Barcode_bytes(barcode: *const ZXing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn ZXing_Barcode_bytesECI(barcode: *const ZXing_Barcode, len: *mut ::core::ffi::c_int) -> *mut u8; + pub fn ZXing_Barcode_text(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_ecLevel(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_symbologyIdentifier(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; + pub fn ZXing_Barcode_position(barcode: *const ZXing_Barcode) -> ZXing_Position; + pub fn ZXing_Barcode_orientation(barcode: *const ZXing_Barcode) -> ::core::ffi::c_int; + pub fn ZXing_Barcode_hasECI(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_isInverted(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_isMirrored(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_lineCount(barcode: *const ZXing_Barcode) -> ::core::ffi::c_int; + pub fn ZXing_ReadBarcode(iv: *const ZXing_ImageView, opts: *const ZXing_ReaderOptions) -> *mut ZXing_Barcode; + pub fn ZXing_ReadBarcodes(iv: *const ZXing_ImageView, opts: *const ZXing_ReaderOptions) -> *mut ZXing_Barcodes; + pub fn ZXing_Barcode_delete(barcode: *mut ZXing_Barcode); + pub fn ZXing_Barcodes_delete(barcodes: *mut ZXing_Barcodes); + pub fn ZXing_Barcodes_size(barcodes: *const ZXing_Barcodes) -> ::core::ffi::c_int; + pub fn ZXing_Barcodes_at(barcodes: *const ZXing_Barcodes, i: ::core::ffi::c_int) -> *const ZXing_Barcode; + pub fn ZXing_Barcodes_move(barcodes: *mut ZXing_Barcodes, i: ::core::ffi::c_int) -> *mut ZXing_Barcode; + pub fn ZXing_LastErrorMsg() -> *mut ::core::ffi::c_char; + pub fn ZXing_free(ptr: *mut ::core::ffi::c_void); } diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 9df557eff9..95af608812 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -60,7 +60,7 @@ fn c2r_str(str: *mut c_char) -> String { let mut res = String::new(); if !str.is_null() { unsafe { res = CStr::from_ptr(str).to_string_lossy().to_string() }; - unsafe { zxing_free(str as *mut c_void) }; + unsafe { ZXing_free(str as *mut c_void) }; } res } @@ -69,21 +69,21 @@ fn c2r_vec(buf: *mut u8, len: c_int) -> Vec { let mut res = Vec::::new(); if !buf.is_null() && len > 0 { unsafe { res = std::slice::from_raw_parts(buf, len as usize).to_vec() }; - unsafe { zxing_free(buf as *mut c_void) }; + unsafe { ZXing_free(buf as *mut c_void) }; } res } fn last_error() -> Error { - match unsafe { zxing_LastErrorMsg().as_mut() } { - None => panic!("Internal error: zxing_LastErrorMsg() returned NULL"), + match unsafe { ZXing_LastErrorMsg().as_mut() } { + None => panic!("Internal error: ZXing_LastErrorMsg() returned NULL"), Some(error) => Error::InvalidInput(c2r_str(error)), } } macro_rules! last_error_or { ($expr:expr) => { - match unsafe { zxing_LastErrorMsg().as_mut() } { + match unsafe { ZXing_LastErrorMsg().as_mut() } { None => Ok($expr), Some(error) => Err(Error::InvalidInput(c2r_str(error))), } @@ -95,7 +95,7 @@ macro_rules! make_zxing_enum { #[repr(u32)] #[derive(Debug, Copy, Clone, PartialEq)] pub enum $name { - $($field = paste! { [] },)* + $($field = paste! { [] },)* } } } @@ -105,7 +105,7 @@ macro_rules! make_zxing_flags { flags! { #[repr(u32)] pub enum $name: c_uint { - $($field = paste! { [] },)* + $($field = paste! { [] },)* } } } @@ -131,22 +131,22 @@ pub type BarcodeFormats = FlagSet; impl Display for BarcodeFormat { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", unsafe { c2r_str(zxing_BarcodeFormatToString(BarcodeFormats::from(*self).bits())) }) + write!(f, "{}", unsafe { c2r_str(ZXing_BarcodeFormatToString(BarcodeFormats::from(*self).bits())) }) } } impl Display for ContentType { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", unsafe { c2r_str(zxing_ContentTypeToString(transmute(*self))) }) + write!(f, "{}", unsafe { c2r_str(ZXing_ContentTypeToString(transmute(*self))) }) } } #[derive(Debug, PartialEq)] -struct ImageViewOwner<'a>(*mut zxing_ImageView, PhantomData<&'a u8>); +struct ImageViewOwner<'a>(*mut ZXing_ImageView, PhantomData<&'a u8>); impl Drop for ImageViewOwner<'_> { fn drop(&mut self) { - unsafe { zxing_ImageView_delete(self.0) } + unsafe { ZXing_ImageView_delete(self.0) } } } #[derive(Debug, Clone, PartialEq)] @@ -180,11 +180,11 @@ impl<'a> ImageView<'a> { row_stride: U, pix_stride: U, ) -> Result { - let iv = zxing_ImageView_new( + let iv = ZXing_ImageView_new( ptr, Self::try_into_int(width)?, Self::try_into_int(height)?, - format as zxing_ImageFormat, + format as ZXing_ImageFormat, Self::try_into_int(row_stride)?, Self::try_into_int(pix_stride)?, ); @@ -197,12 +197,12 @@ impl<'a> ImageView<'a> { pub fn from_slice>(data: &'a [u8], width: T, height: T, format: ImageFormat) -> Result { unsafe { - let iv = zxing_ImageView_new_checked( + let iv = ZXing_ImageView_new_checked( data.as_ptr(), data.len() as c_int, Self::try_into_int(width)?, Self::try_into_int(height)?, - format as zxing_ImageFormat, + format as ZXing_ImageFormat, 0, 0, ); @@ -215,12 +215,12 @@ impl<'a> ImageView<'a> { } pub fn cropped(self, left: i32, top: i32, width: i32, height: i32) -> Self { - unsafe { zxing_ImageView_crop((self.0).0, left, top, width, height) } + unsafe { ZXing_ImageView_crop((self.0).0, left, top, width, height) } self } pub fn rotated(self, degree: i32) -> Self { - unsafe { zxing_ImageView_rotate((self.0).0, degree) } + unsafe { ZXing_ImageView_rotate((self.0).0, degree) } self } } @@ -253,11 +253,11 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { } } -pub struct ReaderOptions(*mut zxing_ReaderOptions); +pub struct ReaderOptions(*mut ZXing_ReaderOptions); impl Drop for ReaderOptions { fn drop(&mut self) { - unsafe { zxing_ReaderOptions_delete(self.0) } + unsafe { ZXing_ReaderOptions_delete(self.0) } } } @@ -276,18 +276,18 @@ impl AsRef for ReaderOptions { macro_rules! property { ($name:ident, $type:ty) => { pub fn $name(self, v: impl Into<$type>) -> Self { - paste! { unsafe { [](self.0, transmute(v.into())) } }; + paste! { unsafe { [](self.0, transmute(v.into())) } }; self } paste! { pub fn [](&mut self, v : impl Into<$type>) -> &mut Self { - unsafe { [](self.0, transmute(v.into())) }; + unsafe { [](self.0, transmute(v.into())) }; self } pub fn [](&self) -> $type { - unsafe { transmute([](self.0)) } + unsafe { transmute([](self.0)) } } } }; @@ -295,7 +295,7 @@ macro_rules! property { impl ReaderOptions { pub fn new() -> Self { - unsafe { ReaderOptions(zxing_ReaderOptions_new()) } + unsafe { ReaderOptions(ZXing_ReaderOptions_new()) } } property!(try_harder, bool); @@ -312,15 +312,15 @@ impl ReaderOptions { property!(min_line_count, i32); } -pub struct Barcode(*mut zxing_Barcode); +pub struct Barcode(*mut ZXing_Barcode); impl Drop for Barcode { fn drop(&mut self) { - unsafe { zxing_Barcode_delete(self.0) } + unsafe { ZXing_Barcode_delete(self.0) } } } -pub type PointI = zxing_PointI; +pub type PointI = ZXing_PointI; #[repr(C)] #[derive(Debug, Copy, Clone)] pub struct Position { @@ -339,7 +339,7 @@ impl Display for PointI { impl Display for Position { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", unsafe { - c2r_str(zxing_PositionToString(*(self as *const Position as *const zxing_Position))) + c2r_str(ZXing_PositionToString(*(self as *const Position as *const ZXing_Position))) }) } } @@ -347,7 +347,7 @@ impl Display for Position { macro_rules! getter { ($r_name:ident, $c_name:ident, $conv:expr, $type:ty) => { pub fn $r_name(&self) -> $type { - paste! { unsafe { $conv([](self.0)) } } + paste! { unsafe { $conv([](self.0)) } } } }; } @@ -369,17 +369,17 @@ impl Barcode { pub fn bytes(&self) -> Vec { let mut len: c_int = 0; - unsafe { c2r_vec(zxing_Barcode_bytes(self.0, &mut len), len) } + unsafe { c2r_vec(ZXing_Barcode_bytes(self.0, &mut len), len) } } pub fn bytes_eci(&self) -> Vec { let mut len: c_int = 0; - unsafe { c2r_vec(zxing_Barcode_bytesECI(self.0, &mut len), len) } + unsafe { c2r_vec(ZXing_Barcode_bytesECI(self.0, &mut len), len) } } } pub fn barcode_formats_from_string(str: impl AsRef) -> Result { let cstr = CString::new(str.as_ref())?; - let res = unsafe { BarcodeFormats::new_unchecked(zxing_BarcodeFormatsFromString(cstr.as_ptr())) }; + let res = unsafe { BarcodeFormats::new_unchecked(ZXing_BarcodeFormatsFromString(cstr.as_ptr())) }; match res.bits() { u32::MAX => last_error_or!(BarcodeFormats::default()), 0 => Ok(BarcodeFormats::full()), @@ -395,14 +395,14 @@ where { let iv_: ImageView = image.try_into().map_err(Into::into)?; unsafe { - let results = zxing_ReadBarcodes((iv_.0).0, opts.as_ref().0); + let results = ZXing_ReadBarcodes((iv_.0).0, opts.as_ref().0); if !results.is_null() { - let size = zxing_Barcodes_size(results); + let size = ZXing_Barcodes_size(results); let mut vec = Vec::::with_capacity(size as usize); for i in 0..size { - vec.push(Barcode(zxing_Barcodes_move(results, i))); + vec.push(Barcode(ZXing_Barcodes_move(results, i))); } - zxing_Barcodes_delete(results); + ZXing_Barcodes_delete(results); Ok(vec) } else { Err(last_error()) From 4c5c0732c10f96b7ca5948684d26bcd6a0a7be91 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 13 Feb 2024 23:55:24 +0100 Subject: [PATCH 122/431] rust: add `.try_downscale(!fast)` just to make `cargo fmt --check` happy --- wrappers/rust/examples/demo.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index 2fcad932ac..87d51f43eb 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -22,7 +22,8 @@ fn main() -> anyhow::Result<()> { .formats(formats) .try_harder(!fast) .try_invert(!fast) - .try_rotate(!fast); + .try_rotate(!fast) + .try_downscale(!fast); #[cfg(feature = "image")] let barcodes = read_barcodes(&image, &opts)?; From 8590eebdfe3137ad9af12e4a6827b8d57a462579 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 14 Feb 2024 22:11:01 +0100 Subject: [PATCH 123/431] android: upgrade GradlePlugin and a few dependencies --- wrappers/android/gradle/libs.versions.toml | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/wrappers/android/gradle/libs.versions.toml b/wrappers/android/gradle/libs.versions.toml index 2f72e440dc..6ee60f25c6 100644 --- a/wrappers/android/gradle/libs.versions.toml +++ b/wrappers/android/gradle/libs.versions.toml @@ -1,20 +1,17 @@ [versions] -androidCoreDesugaring = "2.0.3" -androidGradlePlugin = "8.1.4" +androidGradlePlugin = "8.2.2" androidCompileSdk = "34" androidMinSdk = "21" androidTargetSdk = "33" -androidx-activity = "1.8.1" androidx-appcompat = "1.6.1" -androidx-camera = "1.3.0" +androidx-camera = "1.3.1" androidx-core = "1.12.0" androidx-constraintLayout = "2.1.4" android-material = "1.11.0-rc01" kotlin = "1.9.10" -zxing-core = "3.5.2" +zxing-core = "3.5.3" [libraries] -androidx-activityktx = { module = "androidx.activity:activity-ktx", version.ref = "androidx-activity" } androidx-appcompat = { module = "androidx.appcompat:appcompat", version.ref = "androidx-appcompat" } androidx-camera-core = { module = "androidx.camera:camera-core", version.ref = "androidx-camera" } androidx-camera-camera2 = { module = "androidx.camera:camera-camera2", version.ref = "androidx-camera" } From c4392ebe14099e70896a1b883bbad2e589f0b6ea Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 14 Feb 2024 22:12:20 +0100 Subject: [PATCH 124/431] android: fix build warning by adding @OptIn(Experimental...) in demo app --- .../android/app/src/main/java/zxingcpp/app/MainActivity.kt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 2184b1be0d..db92429a4a 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -31,9 +31,11 @@ import android.os.Build import android.os.Bundle import android.os.Environment import android.view.View +import androidx.annotation.OptIn import androidx.appcompat.app.AppCompatActivity import androidx.camera.camera2.interop.Camera2CameraControl import androidx.camera.camera2.interop.CaptureRequestOptions +import androidx.camera.camera2.interop.ExperimentalCamera2Interop import androidx.camera.core.AspectRatio import androidx.camera.core.CameraSelector import androidx.camera.core.ImageAnalysis @@ -129,6 +131,7 @@ class MainActivity : AppCompatActivity() { } } + @OptIn(ExperimentalCamera2Interop::class) private fun bindCameraUseCases() = binding.viewFinder.post { val cameraProviderFuture = ProcessCameraProvider.getInstance(this) From 14c625df184179033580797f50009dcba873bc5c Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 14 Feb 2024 22:17:49 +0100 Subject: [PATCH 125/431] android: set ALSO_INVERTED hint in Java code path of demo app --- wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index db92429a4a..56605a151b 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -219,6 +219,8 @@ class MainActivity : AppCompatActivity() { hints[DecodeHintType.POSSIBLE_FORMATS] = arrayListOf(BarcodeFormat.QR_CODE) if (binding.tryHarder.isChecked) hints[DecodeHintType.TRY_HARDER] = true + if (binding.tryInvert.isChecked) + hints[DecodeHintType.ALSO_INVERTED] = true resultText = try { val bitmap = BinaryBitmap( From 6763109e6936b896275f8eca1915c2dd07b050b8 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 14 Feb 2024 22:56:24 +0100 Subject: [PATCH 126/431] android: "-DANDROID_ARM_NEON=ON", "-DBUILD_WRITERS=OFF" The ARM_NEON=ON setting does not seem to have an effect. --- wrappers/android/zxingcpp/build.gradle.kts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/android/zxingcpp/build.gradle.kts b/wrappers/android/zxingcpp/build.gradle.kts index 7f1e43c99c..013279032c 100644 --- a/wrappers/android/zxingcpp/build.gradle.kts +++ b/wrappers/android/zxingcpp/build.gradle.kts @@ -25,7 +25,7 @@ android { } externalNativeBuild { cmake { - arguments("-DCMAKE_BUILD_TYPE=RelWithDebInfo") + arguments("-DCMAKE_BUILD_TYPE=RelWithDebInfo", "-DANDROID_ARM_NEON=ON", "-DBUILD_WRITERS=OFF") } } From 0af083de582d0108a9d9c9c8db070d97cc937ec2 Mon Sep 17 00:00:00 2001 From: Markus Fisch Date: Thu, 15 Feb 2024 10:09:41 +0100 Subject: [PATCH 127/431] ios: remove initWithFormats initializer Since it requires specifying all parameters and that isn't usually very useful. It's better to always use `init` and the corresponding `setter` functions. --- .../Sources/Wrapper/Reader/ZXIReaderOptions.h | 18 -------- .../Wrapper/Reader/ZXIReaderOptions.mm | 41 ------------------- 2 files changed, 59 deletions(-) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h index d8221a585f..82d94e84a1 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.h @@ -48,24 +48,6 @@ typedef NS_ENUM(NSInteger, ZXITextMode) { @property(nonatomic) ZXIEanAddOnSymbol eanAddOnSymbol; @property(nonatomic) ZXITextMode textMode; -- (instancetype)initWithFormats:(NSArray*)formats - tryHarder:(BOOL)tryHarder - tryRotate:(BOOL)tryRotate - tryInvert:(BOOL)tryInvert - tryDownscale:(BOOL)tryDownscale - isPure:(BOOL)isPure - binarizer:(ZXIBinarizer)binarizer - downscaleFactor:(NSInteger)downscaleFactor - downscaleThreshold:(NSInteger)downscaleThreshold - minLineCount:(NSInteger)minLineCount - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols - tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode - validateCode39CheckSum:(BOOL)validateCode39CheckSum - validateITFCheckSum:(BOOL)validateITFCheckSum - returnCodabarStartEnd:(BOOL)returnCodabarStartEnd - returnErrors:(BOOL)returnErrors - eanAddOnSymbol:(ZXIEanAddOnSymbol)eanAddOnSymbol - textMode:(ZXITextMode)textMode; @end NS_ASSUME_NONNULL_END diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm index 724fc600bf..1eca538a54 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIReaderOptions.mm @@ -17,47 +17,6 @@ -(instancetype)init { return self; } -- (instancetype)initWithFormats:(NSArray*)formats - tryHarder:(BOOL)tryHarder - tryRotate:(BOOL)tryRotate - tryInvert:(BOOL)tryInvert - tryDownscale:(BOOL)tryDownscale - isPure:(BOOL)isPure - binarizer:(ZXIBinarizer)binarizer - downscaleFactor:(NSInteger)downscaleFactor - downscaleThreshold:(NSInteger)downscaleThreshold - minLineCount:(NSInteger)minLineCount - maxNumberOfSymbols:(NSInteger)maxNumberOfSymbols - tryCode39ExtendedMode:(BOOL)tryCode39ExtendedMode - validateCode39CheckSum:(BOOL)validateCode39CheckSum - validateITFCheckSum:(BOOL)validateITFCheckSum - returnCodabarStartEnd:(BOOL)returnCodabarStartEnd - returnErrors:(BOOL)returnErrors - eanAddOnSymbol:(ZXIEanAddOnSymbol)eanAddOnSymbol - textMode:(ZXITextMode)textMode { - self = [super init]; - self.cppOpts = ZXing::ReaderOptions(); - self.formats = formats; - self.tryHarder = tryHarder; - self.tryRotate = tryRotate; - self.tryInvert = tryInvert; - self.tryDownscale = tryDownscale; - self.isPure = isPure; - self.binarizer = binarizer; - self.downscaleFactor = downscaleFactor; - self.downscaleThreshold = downscaleThreshold; - self.minLineCount = minLineCount; - self.maxNumberOfSymbols = maxNumberOfSymbols; - self.tryCode39ExtendedMode = tryCode39ExtendedMode; - self.validateCode39CheckSum = validateCode39CheckSum; - self.validateITFCheckSum = validateITFCheckSum; - self.returnCodabarStartEnd = returnCodabarStartEnd; - self.returnErrors = returnErrors; - self.eanAddOnSymbol = eanAddOnSymbol; - self.textMode = textMode; - return self; -} - -(BOOL)tryHarder { return self.cppOpts.tryHarder(); } From c661a3f22ee038394c1f9debff0a2d329be4ea05 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 16 Feb 2024 00:36:43 +0100 Subject: [PATCH 128/431] BitHacks: fix random QRCode content on pre-Haswell Windows machines (#727) Turns out `__lzcnt()` can function as a random number generator on old Windows boxes. Nice. This fixes #727 and supersedes #728, as it takes care to prevent even more wrong results by calling it for non 32 bit values. A C++20 build would have fixed this as well. Thanks to @Abooow786 for the effort of debugging this. --- core/src/BitHacks.h | 27 ++++++++------------------- 1 file changed, 8 insertions(+), 19 deletions(-) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index eefede8781..c8979289c0 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -42,12 +42,16 @@ inline int NumberOfLeadingZeros(T x) return std::countl_zero(static_cast>(x)); #else if constexpr (sizeof(x) <= 4) { + static_assert(sizeof(x) == 4, "NumberOfLeadingZeros not implemented for 8 and 16 bit ints."); if (x == 0) return 32; #ifdef ZX_HAS_GCC_BUILTINS return __builtin_clz(x); #elif defined(ZX_HAS_MSC_BUILTINS) - return __lzcnt(x); + unsigned long where; + if (_BitScanReverse(&where, x)) + return 31 - static_cast(where); + return 32; #else int n = 0; if ((x & 0xFFFF0000) == 0) { n = n + 16; x = x << 16; } @@ -62,9 +66,7 @@ inline int NumberOfLeadingZeros(T x) return 64; #ifdef ZX_HAS_GCC_BUILTINS return __builtin_clzll(x); -#elif defined(ZX_HAS_MSC_BUILTINS) - return __lzcnt64(x); -#else +#else // including ZX_HAS_MSC_BUILTINS int n = NumberOfLeadingZeros(static_cast(x >> 32)); if (n == 32) n += NumberOfLeadingZeros(static_cast(x)); @@ -85,6 +87,7 @@ inline int NumberOfTrailingZeros(T v) return std::countr_zero(static_cast>(v)); #else if constexpr (sizeof(v) <= 4) { + static_assert(sizeof(v) == 4, "NumberOfTrailingZeros not implemented for 8 and 16 bit ints."); #ifdef ZX_HAS_GCC_BUILTINS return __builtin_ctz(v); #elif defined(ZX_HAS_MSC_BUILTINS) @@ -106,21 +109,7 @@ inline int NumberOfTrailingZeros(T v) } else { #ifdef ZX_HAS_GCC_BUILTINS return __builtin_ctzll(v); -#elif defined(ZX_HAS_MSC_BUILTINS) - unsigned long where; - #if defined(_WIN64) - if (_BitScanForward64(&where, v)) - return static_cast(where); - #elif defined(_WIN32) - if (_BitScanForward(&where, static_cast(v))) - return static_cast(where); - if (_BitScanForward(&where, static_cast(v >> 32))) - return static_cast(where + 32); - #else - #error "Implementation of __builtin_ctzll required" - #endif - return 64; -#else +#else // including ZX_HAS_MSC_BUILTINS int n = NumberOfTrailingZeros(static_cast(v)); if (n == 32) n += NumberOfTrailingZeros(static_cast(v >> 32)); From d33f5052ba949453c9c86f442e935f7ce1cf07e5 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 16 Feb 2024 00:38:24 +0100 Subject: [PATCH 129/431] ImageView: Add ImageFormat::LumX for 2-byte grey + alpha input --- core/src/ImageView.h | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/ImageView.h b/core/src/ImageView.h index a7b6f74add..a1b32f8854 100644 --- a/core/src/ImageView.h +++ b/core/src/ImageView.h @@ -16,6 +16,7 @@ enum class ImageFormat : uint32_t { None = 0, Lum = 0x01000000, + LumX = 0x02000000, RGB = 0x03000102, BGR = 0x03020100, RGBX = 0x04000102, From 295653c987b471f4883f34580020c3a4f4f3ab3c Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 16 Feb 2024 01:15:07 +0100 Subject: [PATCH 130/431] ZXingReader: add hidden cli options -rotate and -channels for debugging Use the new option to read the image files without forcing stb_image to return rgb data also in the black box testing related ImageLoader. --- example/ZXingReader.cpp | 28 ++++++++++++++++++++-------- test/blackbox/ImageLoader.cpp | 26 +++++++++++++++----------- 2 files changed, 35 insertions(+), 19 deletions(-) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 724c3881ae..5a566dcf6e 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -31,7 +31,7 @@ static void PrintUsage(const char* exePath) << " -norotate Don't try rotated image during detection (faster)\n" << " -noinvert Don't search for inverted codes during detection (faster)\n" << " -noscale Don't try downscaled images during detection (faster)\n" - << " -format \n" + << " -formats \n" << " Only detect given format(s) (faster)\n" << " -single Stop after the first barcode is detected (faster)\n" << " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n" @@ -54,8 +54,8 @@ static void PrintUsage(const char* exePath) std::cout << "Formats can be lowercase, with or without '-', separated by ',' and/or '|'\n"; } -static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& oneLine, bool& bytesOnly, - std::vector& filePaths, std::string& outPath) +static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& oneLine, bool& bytesOnly, int& forceChannels, + int& rotate, std::vector& filePaths, std::string& outPath) { #ifdef ZXING_BUILD_EXPERIMENTAL_API options.setTryDenoise(true); @@ -122,6 +122,14 @@ static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, bool& o if (++i == argc) return false; outPath = argv[i]; + } else if (is("-channels")) { + if (++i == argc) + return false; + forceChannels = atoi(argv[i]); + } else if (is("-rotate")) { + if (++i == argc) + return false; + rotate = atoi(argv[i]); } else if (is("-help") || is("--help")) { PrintUsage(argv[0]); exit(0); @@ -166,29 +174,33 @@ int main(int argc, char* argv[]) std::string outPath; bool oneLine = false; bool bytesOnly = false; + int forceChannels = 0; + int rotate = 0; int ret = 0; options.setTextMode(TextMode::HRI); options.setEanAddOnSymbol(EanAddOnSymbol::Read); - if (!ParseOptions(argc, argv, options, oneLine, bytesOnly, filePaths, outPath)) { + if (!ParseOptions(argc, argv, options, oneLine, bytesOnly, forceChannels, rotate, filePaths, outPath)) { PrintUsage(argv[0]); return -1; } - std::cout.setf(std::ios::boolalpha); for (const auto& filePath : filePaths) { int width, height, channels; - std::unique_ptr buffer(stbi_load(filePath.c_str(), &width, &height, &channels, 3), stbi_image_free); + std::unique_ptr buffer(stbi_load(filePath.c_str(), &width, &height, &channels, forceChannels), + stbi_image_free); if (buffer == nullptr) { std::cerr << "Failed to read image: " << filePath << " (" << stbi_failure_reason() << ")" << "\n"; return -1; } + channels = forceChannels ? forceChannels : channels; - ImageView image{buffer.get(), width, height, ImageFormat::RGB}; - auto results = ReadBarcodes(image, options); + auto ImageFormatFromChannels = std::array{ImageFormat::None, ImageFormat::Lum, ImageFormat::LumX, ImageFormat::RGB, ImageFormat::RGBX}; + ImageView image{buffer.get(), width, height, ImageFormatFromChannels.at(channels)}; + auto results = ReadBarcodes(image.rotated(rotate), options); // if we did not find anything, insert a dummy to produce some output for each file if (results.empty()) diff --git a/test/blackbox/ImageLoader.cpp b/test/blackbox/ImageLoader.cpp index c3f7ad5165..bef256a6f5 100644 --- a/test/blackbox/ImageLoader.cpp +++ b/test/blackbox/ImageLoader.cpp @@ -9,6 +9,7 @@ #include "BinaryBitmap.h" #include "ImageView.h" +#include #include #include #include @@ -27,18 +28,21 @@ class STBImage : public ImageView void load(const fs::path& imgPath) { - int width, height, colors; - _memory.reset(stbi_load(imgPath.string().c_str(), &width, &height, &colors, 3)); + int width, height, channels; + _memory.reset(stbi_load(imgPath.string().c_str(), &width, &height, &channels, 0)); if (_memory == nullptr) - throw std::runtime_error("Failed to read image"); -#if 1 - auto* img = _memory.get(); - for (int i = 0; i < width * height; ++i ) - img[i] = RGBToLum(img[3 * i + 0], img[3 * i + 1], img[3 * i + 2]); - ImageView::operator=({_memory.get(), width, height, ImageFormat::Lum}); -#else - ImageView::operator=({_memory.get(), width, height, ImageFormat::RGB}); -#endif + throw std::runtime_error("Failed to read image: " + imgPath.string() + " (" + stbi_failure_reason() + ")"); + + auto ImageFormatFromChannels = std::array{ImageFormat::None, ImageFormat::Lum, ImageFormat::LumX, ImageFormat::RGB, ImageFormat::RGBX}; + ImageView::operator=({_memory.get(), width, height, ImageFormatFromChannels.at(channels)}); + + // preconvert from RGB -> Lum to do this only once instead of for each rotation + if (_format == ImageFormat::RGB) { + auto* img = _memory.get(); + for (int i = 0; i < width * height; ++i) + img[i] = RGBToLum(img[3 * i + 0], img[3 * i + 1], img[3 * i + 2]); + ImageView::operator=({_memory.get(), width, height, ImageFormat::Lum}); + } } operator bool() const { return _data; } From fd367dbfe78a7515244bbcd88d11cd964f2ff3fe Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 17 Feb 2024 00:31:09 +0100 Subject: [PATCH 131/431] c-API: add NULL termination to uint8_t* return values (bytes) * There was an extra byte allocated but not set to 0. * Don't crash if len pointer argument is NULL. * Print `bytesECI()` in testing code. * Add comment about ZXing_LastErrorMsg() return values. --- core/src/ZXingC.cpp | 21 +++++++++------------ core/src/ZXingC.h | 1 + wrappers/c/ZXingCTest.c | 1 + 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/core/src/ZXingC.cpp b/core/src/ZXingC.cpp index 05579f29f0..2986362495 100644 --- a/core/src/ZXingC.cpp +++ b/core/src/ZXingC.cpp @@ -25,26 +25,23 @@ template R transmute_cast(const T& v) return *(const R*)(&v); } -static char* copy(std::string_view sv) +template +P copy(const C& c) { - auto ret = (char*)malloc(sv.size() + 1); + auto ret = (P)malloc(c.size() + 1); if (ret) { - strncpy(ret, sv.data(), sv.size()); - ret[sv.size()] = '\0'; + memcpy(ret, c.data(), c.size()); + ret[c.size()] = 0; } return ret; } static uint8_t* copy(const ByteArray& ba, int* len) { - *len = Size(ba); - - auto ret = (uint8_t*)malloc(*len + 1); - if (ret) - memcpy(ret, ba.data(), *len); - else - *len = 0; - + // for convencience and as a safety measure, we NULL terminate even byte arrays + auto ret = copy(ba); + if (len) + *len = ret ? Size(ba) : 0; return ret; } diff --git a/core/src/ZXingC.h b/core/src/ZXingC.h index 63d249ed6c..921ef85554 100644 --- a/core/src/ZXingC.h +++ b/core/src/ZXingC.h @@ -217,6 +217,7 @@ int ZXing_Barcodes_size(const ZXing_Barcodes* barcodes); const ZXing_Barcode* ZXing_Barcodes_at(const ZXing_Barcodes* barcodes, int i); ZXing_Barcode* ZXing_Barcodes_move(ZXing_Barcodes* barcodes, int i); +/* ZXing_LastErrorMsg() returns NULL in case there is no last error and a copy of the string otherwise. */ char* ZXing_LastErrorMsg(); void ZXing_free(void* ptr); diff --git a/wrappers/c/ZXingCTest.c b/wrappers/c/ZXingCTest.c index fd1b73def3..fbac6edd1d 100644 --- a/wrappers/c/ZXingCTest.c +++ b/wrappers/c/ZXingCTest.c @@ -81,6 +81,7 @@ int main(int argc, char** argv) const ZXing_Barcode* barcode = ZXing_Barcodes_at(barcodes, i); printF("Text : %s\n", ZXing_Barcode_text(barcode)); + printF("BytesECI : %s\n", (char*)ZXing_Barcode_bytesECI(barcode, NULL)); printF("Format : %s\n", ZXing_BarcodeFormatToString(ZXing_Barcode_format(barcode))); printF("Content : %s\n", ZXing_ContentTypeToString(ZXing_Barcode_contentType(barcode))); printF("Identifier : %s\n", ZXing_Barcode_symbologyIdentifier(barcode)); From 36ba3198a5bf3bc7c90528cd9e8d170019b64966 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 18 Feb 2024 21:15:24 +0100 Subject: [PATCH 132/431] android: replace license header info with SPDX-License-Identifier --- .../app/src/main/java/zxingcpp/app/MainActivity.kt | 13 +------------ .../src/main/java/zxingcpp/app/PreviewOverlay.kt | 13 +------------ .../src/main/java/zxingcpp/BarcodeReader.kt | 13 +------------ 3 files changed, 3 insertions(+), 36 deletions(-) diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt index 56605a151b..18b196155d 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/MainActivity.kt @@ -1,18 +1,7 @@ /* * Copyright 2021 Axel Waggershauser -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. */ +// SPDX-License-Identifier: Apache-2.0 package zxingcpp.app diff --git a/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt b/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt index 8a79cb117f..90f4e7caa9 100644 --- a/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt +++ b/wrappers/android/app/src/main/java/zxingcpp/app/PreviewOverlay.kt @@ -1,19 +1,8 @@ /* * Copyright 2022 Axel Waggershauser * Copyright 2022 Markus Fisch -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. */ +// SPDX-License-Identifier: Apache-2.0 package com.example.zxingcppdemo diff --git a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt index 748c00e834..b7b9d1f36a 100644 --- a/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt +++ b/wrappers/android/zxingcpp/src/main/java/zxingcpp/BarcodeReader.kt @@ -1,18 +1,7 @@ /* * Copyright 2021 Axel Waggershauser -* -* Licensed under the Apache License, Version 2.0 (the "License"); -* you may not use this file except in compliance with the License. -* You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, -* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -* See the License for the specific language governing permissions and -* limitations under the License. */ +// SPDX-License-Identifier: Apache-2.0 package zxingcpp From 6e80aacddeb7916b984c9a007e0d4d86d1cac8bf Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 19 Feb 2024 00:21:00 +0100 Subject: [PATCH 133/431] rust: implement new `BarcodeReader` API Replace `ReaderOptions` + `read_barcodes()` with `BarcodeReader` and `BarcodeReader.read()`. For the motivation behind this change, see https://github.com/zxing-cpp/zxing-cpp/discussions/696#discussioncomment-8459148 Also replace `barcode_formats_from_string` with more idiomatic `BarcodeFormats::from_str`. --- wrappers/rust/Cargo.toml | 2 +- wrappers/rust/README.md | 8 +- wrappers/rust/examples/demo.rs | 8 +- wrappers/rust/src/lib.rs | 171 ++++++++++++++++----------------- wrappers/rust/src/tests.rs | 19 ++-- 5 files changed, 103 insertions(+), 105 deletions(-) diff --git a/wrappers/rust/Cargo.toml b/wrappers/rust/Cargo.toml index 7f1bbd8d36..27961cfb5d 100644 --- a/wrappers/rust/Cargo.toml +++ b/wrappers/rust/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "zxing-cpp" -version = "0.2.2" +version = "0.3.0" edition = "2021" # authors = ["Axel Waggershauser "] license = "Apache-2.0" diff --git a/wrappers/rust/README.md b/wrappers/rust/README.md index 65c28dcc18..bda4f075cd 100644 --- a/wrappers/rust/README.md +++ b/wrappers/rust/README.md @@ -15,21 +15,21 @@ In your Cargo.toml: # `bundled` causes cargo to compile and statically link an up to # date version of the c++ core library. This is the most convenient # and safe way to build the library. -zxing-cpp = { version = "0.2.2", features = ["bundled", "image"] } +zxing-cpp = { version = "0.3.0", features = ["bundled", "image"] } ``` Simple example usage: ```rust -use zxing_cpp::{ImageView, ReaderOptions, BarcodeFormat, read_barcodes}; +use zxing_cpp::{BarcodeFormat, BarcodeReader, ImageView}; fn main() -> anyhow::Result<()> { let image = image::open("some-image-file.jpg")?; - let opts = ReaderOptions::default() + let reader = BarcodeReader::new() .formats(BarcodeFormat::QRCode | BarcodeFormat::LinearCodes) .try_invert(false); - let barcodes = read_barcodes(&image, &opts)?; + let barcodes = reader.read(&image)?; if barcodes.is_empty() { println!("No barcode found."); diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index 87d51f43eb..cee5d48509 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -17,8 +17,8 @@ fn main() -> anyhow::Result<()> { #[cfg(not(feature = "image"))] let iv = ImageView::from_slice(&lum_img, lum_img.width(), lum_img.height(), ImageFormat::Lum)?; - let formats = barcode_formats_from_string(formats.unwrap_or_default())?; - let opts = ReaderOptions::default() + let formats = BarcodeFormats::from_str(formats.unwrap_or_default())?; + let reader = BarcodeReader::new() .formats(formats) .try_harder(!fast) .try_invert(!fast) @@ -26,9 +26,9 @@ fn main() -> anyhow::Result<()> { .try_downscale(!fast); #[cfg(feature = "image")] - let barcodes = read_barcodes(&image, &opts)?; + let barcodes = reader.read(&image)?; #[cfg(not(feature = "image"))] - let barcodes = read_barcodes(iv, opts)?; + let barcodes = reader.read(iv)?; if barcodes.is_empty() { println!("No barcode found."); diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 95af608812..142fdea573 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -127,8 +127,6 @@ make_zxing_flags!(BarcodeFormat { MaxiCode, PDF417, QRCode, UPCA, UPCE, MicroQRCode, RMQRCode, DXFilmEdge, LinearCodes, MatrixCodes, Any }); -pub type BarcodeFormats = FlagSet; - impl Display for BarcodeFormat { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { write!(f, "{}", unsafe { c2r_str(ZXing_BarcodeFormatToString(BarcodeFormats::from(*self).bits())) }) @@ -141,6 +139,24 @@ impl Display for ContentType { } } +pub type BarcodeFormats = FlagSet; + +pub trait FromStr: Sized { + fn from_str(str: impl AsRef) -> Result; +} + +impl FromStr for BarcodeFormats { + fn from_str(str: impl AsRef) -> Result { + let cstr = CString::new(str.as_ref())?; + let res = unsafe { BarcodeFormats::new_unchecked(ZXing_BarcodeFormatsFromString(cstr.as_ptr())) }; + match res.bits() { + u32::MAX => last_error_or!(BarcodeFormats::default()), + 0 => Ok(BarcodeFormats::full()), + _ => Ok(res), + } + } +} + #[derive(Debug, PartialEq)] struct ImageViewOwner<'a>(*mut ZXing_ImageView, PhantomData<&'a u8>); @@ -253,65 +269,6 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { } } -pub struct ReaderOptions(*mut ZXing_ReaderOptions); - -impl Drop for ReaderOptions { - fn drop(&mut self) { - unsafe { ZXing_ReaderOptions_delete(self.0) } - } -} - -impl Default for ReaderOptions { - fn default() -> Self { - Self::new() - } -} - -impl AsRef for ReaderOptions { - fn as_ref(&self) -> &ReaderOptions { - self - } -} - -macro_rules! property { - ($name:ident, $type:ty) => { - pub fn $name(self, v: impl Into<$type>) -> Self { - paste! { unsafe { [](self.0, transmute(v.into())) } }; - self - } - - paste! { - pub fn [](&mut self, v : impl Into<$type>) -> &mut Self { - unsafe { [](self.0, transmute(v.into())) }; - self - } - - pub fn [](&self) -> $type { - unsafe { transmute([](self.0)) } - } - } - }; -} - -impl ReaderOptions { - pub fn new() -> Self { - unsafe { ReaderOptions(ZXing_ReaderOptions_new()) } - } - - property!(try_harder, bool); - property!(try_rotate, bool); - property!(try_invert, bool); - property!(try_downscale, bool); - property!(is_pure, bool); - property!(return_errors, bool); - property!(formats, BarcodeFormats); - property!(text_mode, TextMode); - property!(binarizer, Binarizer); - property!(ean_add_on_symbol, EanAddOnSymbol); - property!(max_number_of_symbols, i32); - property!(min_line_count, i32); -} - pub struct Barcode(*mut ZXing_Barcode); impl Drop for Barcode { @@ -377,35 +334,77 @@ impl Barcode { } } -pub fn barcode_formats_from_string(str: impl AsRef) -> Result { - let cstr = CString::new(str.as_ref())?; - let res = unsafe { BarcodeFormats::new_unchecked(ZXing_BarcodeFormatsFromString(cstr.as_ptr())) }; - match res.bits() { - u32::MAX => last_error_or!(BarcodeFormats::default()), - 0 => Ok(BarcodeFormats::full()), - _ => Ok(res), +pub struct BarcodeReader(*mut ZXing_ReaderOptions); + +impl Drop for BarcodeReader { + fn drop(&mut self) { + unsafe { ZXing_ReaderOptions_delete(self.0) } + } +} + +impl Default for BarcodeReader { + fn default() -> Self { + Self::new() } } -pub fn read_barcodes<'a, IV, RO>(image: IV, opts: RO) -> Result, Error> -where - IV: TryInto>, - IV::Error: Into, - RO: AsRef, -{ - let iv_: ImageView = image.try_into().map_err(Into::into)?; - unsafe { - let results = ZXing_ReadBarcodes((iv_.0).0, opts.as_ref().0); - if !results.is_null() { - let size = ZXing_Barcodes_size(results); - let mut vec = Vec::::with_capacity(size as usize); - for i in 0..size { - vec.push(Barcode(ZXing_Barcodes_move(results, i))); +macro_rules! property { + ($name:ident, $type:ty) => { + pub fn $name(self, v: impl Into<$type>) -> Self { + paste! { unsafe { [](self.0, transmute(v.into())) } }; + self + } + + paste! { + pub fn [](&mut self, v : impl Into<$type>) -> &mut Self { + unsafe { [](self.0, transmute(v.into())) }; + self + } + + pub fn [](&self) -> $type { + unsafe { transmute([](self.0)) } + } + } + }; +} + +impl BarcodeReader { + pub fn new() -> Self { + unsafe { BarcodeReader(ZXing_ReaderOptions_new()) } + } + + property!(try_harder, bool); + property!(try_rotate, bool); + property!(try_invert, bool); + property!(try_downscale, bool); + property!(is_pure, bool); + property!(return_errors, bool); + property!(formats, BarcodeFormats); + property!(text_mode, TextMode); + property!(binarizer, Binarizer); + property!(ean_add_on_symbol, EanAddOnSymbol); + property!(max_number_of_symbols, i32); + property!(min_line_count, i32); + + pub fn read<'a, IV>(&self, image: IV) -> Result, Error> + where + IV: TryInto>, + IV::Error: Into, + { + let iv_: ImageView = image.try_into().map_err(Into::into)?; + unsafe { + let results = ZXing_ReadBarcodes((iv_.0).0, self.0); + if !results.is_null() { + let size = ZXing_Barcodes_size(results); + let mut vec = Vec::::with_capacity(size as usize); + for i in 0..size { + vec.push(Barcode(ZXing_Barcodes_move(results, i))); + } + ZXing_Barcodes_delete(results); + Ok(vec) + } else { + Err(last_error()) } - ZXing_Barcodes_delete(results); - Ok(vec) - } else { - Err(last_error()) } } } diff --git a/wrappers/rust/src/tests.rs b/wrappers/rust/src/tests.rs index 952e4e3141..465dfe65d0 100644 --- a/wrappers/rust/src/tests.rs +++ b/wrappers/rust/src/tests.rs @@ -8,14 +8,14 @@ mod tests { use crate::*; #[test] - fn barcode_formats_from_string_valid() { - let formats = barcode_formats_from_string("qrcode,linearcodes").unwrap(); + fn barcode_formats_from_str_valid() { + let formats = BarcodeFormats::from_str("qrcode,linearcodes").unwrap(); assert_eq!(formats, BarcodeFormat::QRCode | BarcodeFormat::LinearCodes); } #[test] - fn reader_options() { - let mut o1 = ReaderOptions::default(); + fn barcode_reader_new() { + let mut o1 = BarcodeReader::new(); assert_eq!(o1.get_formats(), BarcodeFormat::None); assert_eq!(o1.get_try_harder(), true); o1.set_formats(BarcodeFormat::EAN8); @@ -23,7 +23,7 @@ mod tests { o1.set_try_harder(false); assert_eq!(o1.get_try_harder(), false); - o1 = ReaderOptions::default().is_pure(true).text_mode(TextMode::Hex); + o1 = BarcodeReader::new().is_pure(true).text_mode(TextMode::Hex); assert_eq!(o1.get_formats(), BarcodeFormat::None); assert_eq!(o1.get_try_harder(), true); assert_eq!(o1.get_is_pure(), true); @@ -32,19 +32,18 @@ mod tests { #[test] #[should_panic] - fn barcode_formats_from_string_invalid() { - let _ = barcode_formats_from_string("qrcoder").unwrap(); + fn barcode_formats_from_str_invalid() { + let _ = BarcodeFormats::from_str("qrcoder").unwrap(); } #[test] - fn read_barcodes_pure() { + fn read_pure() { let mut data = Vec::::new(); for v in "0000101000101101011110111101011011101010100111011100101000100101110010100000".chars() { data.push(if v == '0' { 255 } else { 0 }); } let iv = ImageView::from_slice(&data, data.len(), 1, ImageFormat::Lum).unwrap(); - let op = ReaderOptions::default().binarizer(Binarizer::BoolCast); - let res = read_barcodes(&iv, &op).unwrap(); + let res = BarcodeReader::new().binarizer(Binarizer::BoolCast).read(&iv).unwrap(); let expected = "96385074"; From cfb35cd62ff5ecdf9f58d412a5a3ca71c98d49eb Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 19 Feb 2024 10:57:26 +0100 Subject: [PATCH 134/431] python: change indentation in setup.py from space to tab (fix #731) --- wrappers/python/setup.py | 114 +++++++++++++++++++-------------------- 1 file changed, 57 insertions(+), 57 deletions(-) diff --git a/wrappers/python/setup.py b/wrappers/python/setup.py index f3c3b38c7d..16c223b7ac 100644 --- a/wrappers/python/setup.py +++ b/wrappers/python/setup.py @@ -9,74 +9,74 @@ # Adapted from here: https://github.com/pybind/cmake_example/blob/master/setup.py class CMakeExtension(Extension): - def __init__(self, name, sourcedir=''): - Extension.__init__(self, name, sources=[]) - self.sourcedir = os.path.abspath(sourcedir) + def __init__(self, name, sourcedir=''): + Extension.__init__(self, name, sources=[]) + self.sourcedir = os.path.abspath(sourcedir) class CMakeBuild(build_ext): - def build_extension(self, ext): - extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) - cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, - '-DPYTHON_EXECUTABLE=' + sys.executable, - '-DVERSION_INFO=' + self.distribution.get_version()] + def build_extension(self, ext): + extdir = os.path.abspath(os.path.dirname(self.get_ext_fullpath(ext.name))) + cmake_args = ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY=' + extdir, + '-DPYTHON_EXECUTABLE=' + sys.executable, + '-DVERSION_INFO=' + self.distribution.get_version()] - cfg = 'Debug' if self.debug else 'Release' - build_args = ['--config', cfg, - '-j', '8'] + cfg = 'Debug' if self.debug else 'Release' + build_args = ['--config', cfg, + '-j', '8'] - if platform.system() == "Windows": - cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] - if sys.maxsize > 2**32: - cmake_args += ['-A', 'x64'] - else: - cmake_args += ['-A', 'Win32'] - build_args += ['--', '/m'] - else: - cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] + if platform.system() == "Windows": + cmake_args += ['-DCMAKE_LIBRARY_OUTPUT_DIRECTORY_{}={}'.format(cfg.upper(), extdir)] + if sys.maxsize > 2**32: + cmake_args += ['-A', 'x64'] + else: + cmake_args += ['-A', 'Win32'] + build_args += ['--', '/m'] + else: + cmake_args += ['-DCMAKE_BUILD_TYPE=' + cfg] - if not os.path.exists(self.build_temp): - os.makedirs(self.build_temp) + if not os.path.exists(self.build_temp): + os.makedirs(self.build_temp) - subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp) - subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) + subprocess.check_call(['cmake', ext.sourcedir] + cmake_args, cwd=self.build_temp) + subprocess.check_call(['cmake', '--build', '.'] + build_args, cwd=self.build_temp) with open("README.md", "r", encoding="utf-8") as fh: - long_description = fh.read() + long_description = fh.read() setup( - name='zxing-cpp', - # setuptools_scm cannot be used because of the structure of the project until the following issues are solved: - # https://github.com/pypa/setuptools_scm/issues/357 - # https://github.com/pypa/pip/issues/7549 - # Because pip works on a copy of current directory in a temporary directory, the temporary directory does not hold - # the .git directory of the repo, so that setuptools_scm cannot guess the current version. - # use_scm_version={ - # "root": "../..", - # "version_scheme": "guess-next-dev", - # "local_scheme": "no-local-version", - # "tag_regex": "v?([0-9]+.[0-9]+.[0-9]+)", - # }, - version='2.2.0', - description='Python bindings for the zxing-cpp barcode library', - long_description=long_description, - long_description_content_type="text/markdown", - author='ZXing-C++ Community', - author_email='zxingcpp@gmail.com', - url='https://github.com/zxing-cpp/zxing-cpp', - license='Apache License 2.0', - keywords=['barcode'], - classifiers=[ - "Development Status :: 4 - Beta", - "Programming Language :: Python :: 3", - "License :: OSI Approved :: Apache Software License", - "Operating System :: OS Independent", - "Topic :: Multimedia :: Graphics", - ], - python_requires=">=3.6", - ext_modules=[CMakeExtension('zxingcpp')], - cmdclass=dict(build_ext=CMakeBuild), - zip_safe=False, + name='zxing-cpp', + # setuptools_scm cannot be used because of the structure of the project until the following issues are solved: + # https://github.com/pypa/setuptools_scm/issues/357 + # https://github.com/pypa/pip/issues/7549 + # Because pip works on a copy of current directory in a temporary directory, the temporary directory does not hold + # the .git directory of the repo, so that setuptools_scm cannot guess the current version. + # use_scm_version={ + # "root": "../..", + # "version_scheme": "guess-next-dev", + # "local_scheme": "no-local-version", + # "tag_regex": "v?([0-9]+.[0-9]+.[0-9]+)", + # }, + version='2.2.0', + description='Python bindings for the zxing-cpp barcode library', + long_description=long_description, + long_description_content_type="text/markdown", + author='ZXing-C++ Community', + author_email='zxingcpp@gmail.com', + url='https://github.com/zxing-cpp/zxing-cpp', + license='Apache License 2.0', + keywords=['barcode'], + classifiers=[ + "Development Status :: 4 - Beta", + "Programming Language :: Python :: 3", + "License :: OSI Approved :: Apache Software License", + "Operating System :: OS Independent", + "Topic :: Multimedia :: Graphics", + ], + python_requires=">=3.6", + ext_modules=[CMakeExtension('zxingcpp')], + cmdclass=dict(build_ext=CMakeBuild), + zip_safe=False, ) From 74319f24c725d78c397c5ff2a7ba5a0925822b6f Mon Sep 17 00:00:00 2001 From: ISNing <33572057+ISNing@users.noreply.github.com> Date: Mon, 19 Feb 2024 18:07:26 +0800 Subject: [PATCH 135/431] Add Kotlin/Native Wrapper (#719) This adds a Kotlin/Native wrapper based on the C-API. --- .github/workflows/ci.yml | 39 +++ .github/workflows/publish-kn.yml | 54 ++++ wrappers/kn/.gitignore | 44 ++++ wrappers/kn/README.md | 56 +++++ wrappers/kn/build.gradle.kts | 213 ++++++++++++++++ wrappers/kn/gradle.properties | 2 + wrappers/kn/gradle/libs.versions.toml | 9 + wrappers/kn/gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 60756 bytes .../gradle/wrapper/gradle-wrapper.properties | 5 + wrappers/kn/gradlew | 234 ++++++++++++++++++ wrappers/kn/gradlew.bat | 89 +++++++ wrappers/kn/settings.gradle.kts | 20 ++ .../src/nativeMain/kotlin/zxingcpp/Barcode.kt | 142 +++++++++++ .../kotlin/zxingcpp/BarcodeFormat.kt | 47 ++++ .../kotlin/zxingcpp/BarcodeReader.kt | 121 +++++++++ .../nativeMain/kotlin/zxingcpp/ImageView.kt | 58 +++++ wrappers/kn/src/nativeTest/kotlin/Test.kt | 41 +++ 17 files changed, 1174 insertions(+) create mode 100644 .github/workflows/publish-kn.yml create mode 100644 wrappers/kn/.gitignore create mode 100644 wrappers/kn/README.md create mode 100644 wrappers/kn/build.gradle.kts create mode 100644 wrappers/kn/gradle.properties create mode 100644 wrappers/kn/gradle/libs.versions.toml create mode 100644 wrappers/kn/gradle/wrapper/gradle-wrapper.jar create mode 100644 wrappers/kn/gradle/wrapper/gradle-wrapper.properties create mode 100755 wrappers/kn/gradlew create mode 100644 wrappers/kn/gradlew.bat create mode 100644 wrappers/kn/settings.gradle.kts create mode 100644 wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt create mode 100644 wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeFormat.kt create mode 100644 wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeReader.kt create mode 100644 wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt create mode 100644 wrappers/kn/src/nativeTest/kotlin/Test.kt diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 40db6ba466..373055a741 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,6 +114,45 @@ jobs: name: android-artifacts path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-debug.aar" + build-kn: + runs-on: ubuntu-latest + defaults: + run: + working-directory: ./wrappers/kn + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout toolchain initializer repository + uses: actions/checkout@v4 + with: + repository: ISNing/kn-toolchain-initializer + path: wrappers/kn/.kn-toolchain-initializer + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Initialize Kotlin/Native toolchain + working-directory: wrappers/kn/.kn-toolchain-initializer + run: ./gradlew build -DkotlinVersion=1.9.22 + + - name: Export Toolchain properties + run: | + echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-linux-x86_64-1.9.22" > local.properties + + - name: Run test for linuxX64 target + run: | + ./gradlew linuxX64Test + build-python: runs-on: ${{ matrix.os }} strategy: diff --git a/.github/workflows/publish-kn.yml b/.github/workflows/publish-kn.yml new file mode 100644 index 0000000000..aa925f9282 --- /dev/null +++ b/.github/workflows/publish-kn.yml @@ -0,0 +1,54 @@ +name: publish-kn + +on: + workflow_dispatch: + +jobs: + publish: + name: Library Publish + + runs-on: macos-latest + + defaults: + run: + working-directory: ./wrappers/kn + + steps: + - name: Checkout repository + uses: actions/checkout@v4 + + - name: Checkout toolchain initializer repository + uses: actions/checkout@v4 + with: + repository: ISNing/kn-toolchain-initializer + path: wrappers/kn/.kn-toolchain-initializer + + - name: Set up JDK + uses: actions/setup-java@v4 + with: + java-version: 17 + distribution: temurin + + - name: Setup Gradle + uses: gradle/actions/setup-gradle@v3 + + - name: Validate Gradle Wrapper + uses: gradle/wrapper-validation-action@v2 + + - name: Initialize Kotlin/Native toolchain + working-directory: wrappers/kn/.kn-toolchain-initializer + run: ./gradlew build -DkotlinVersion=1.9.22 + + - name: Export Toolchain properties + run: | + echo -e "konan.dir=$HOME/.konan/kotlin-native-prebuilt-macos-x86_64-1.9.22" > local.properties + + - name: Publish Library + env: + ORG_GRADLE_PROJECT_signingKey: ${{ secrets.OSSRH_GPG_SECRET_KEY }} + ORG_GRADLE_PROJECT_signingPassword: ${{ secrets.OSSRH_GPG_SECRET_KEY_PASSWORD }} + ORG_GRADLE_PROJECT_ossrhUsername: ${{ secrets.OSSRH_USERNAME }} + ORG_GRADLE_PROJECT_ossrhPassword: ${{ secrets.OSSRH_PASSWORD }} + run: | + export PATH="$PATH:$(brew --prefix llvm@15)/bin" + ./gradlew publishAllPublicationsToSonatypeRepository diff --git a/wrappers/kn/.gitignore b/wrappers/kn/.gitignore new file mode 100644 index 0000000000..cc4fca248e --- /dev/null +++ b/wrappers/kn/.gitignore @@ -0,0 +1,44 @@ +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### IntelliJ IDEA ### +.idea/modules.xml +.idea/jarRepositories.xml +.idea/compiler.xml +.idea/libraries/ +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### Eclipse ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ + +### Mac OS ### +.DS_Store + +local.properties diff --git a/wrappers/kn/README.md b/wrappers/kn/README.md new file mode 100644 index 0000000000..6c41d5a2f3 --- /dev/null +++ b/wrappers/kn/README.md @@ -0,0 +1,56 @@ +# ZXing-C++ Kotlin/Native Library + +## Install + +The easiest way to use the library is to fetch if from _mavenCentral_. Simply add + +```gradle +implementation("io.github.zxing-cpp:kotlin-native:2.2.1") +``` + +to your `build.gradle.kts` file in the `dependencies` section of `nativeMain` source set. + +## Use + +A trivial use case looks like this: + +```kotlin +import zxingcpp.BarcodeFormat +import zxingcpp.BarcodeReader +import zxingcpp.ImageFormat +import zxingcpp.ImageView + +val data: ByteArray = ... // the image data +val width: Int = ... // the image width +val height: Int = ... // the image height +val format = ImageFormat.Lum // ImageFormat.Lum assumes grey scale image data + +val image = ImageView(data, width, height, format) +val barcodeReader = BarcodeReader().apply { + formats = setOf(BarcodeFormat.EAN13, BarcodeFormat.QRCode) + tryHarder = true + maxNumberOfSymbols = 3 + // more options, see documentation +} + +barcodeReader.read(image).joinToString("\n") { barcode: Barcode -> + "${barcode.format} (${barcode.contentType}): ${barcode.text}" +} +``` + +Here you have to load your image into memory by yourself and pass the decoded data to the constructor of `ImageView`. + +## Build locally + +1. Install JDK, CMake and Android NDK(With `$ANDROID_NDK` correctly configured) and ensure their + executable binaries appear in `$PATH`. +2. Prepare kotlin/native toolchain (You can easily do this by cloning + [K/N Toolchain Initializer](https://github.com/ISNing/kn-toolchain-initializer) and executing `gradle build` + so that kotlin will download toolchains needed into user's home dir.). +3. Ensure there's `run_konan` available in `$PATH` or specify path of kotlin-native toolchain in `local.properties` + like `konan.dir=/home/user/.konan/kotlin-native-prebuilt-linux-x86_64-1.9.22`. +4. Ensure there's `llvm-ar` available in `$PATH` for mingwX64 target building. + +And then you can build the project from the command line: + + $ ./gradlew :assemble diff --git a/wrappers/kn/build.gradle.kts b/wrappers/kn/build.gradle.kts new file mode 100644 index 0000000000..a3865412fe --- /dev/null +++ b/wrappers/kn/build.gradle.kts @@ -0,0 +1,213 @@ +import io.github.isning.gradle.plugins.cmake.params.CustomCMakeParams +import io.github.isning.gradle.plugins.cmake.params.entries.CustomCMakeCacheEntries +import io.github.isning.gradle.plugins.cmake.params.entries.asCMakeParams +import io.github.isning.gradle.plugins.cmake.params.entries.platform.ModifiablePlatformEntriesImpl +import io.github.isning.gradle.plugins.cmake.params.entries.plus +import io.github.isning.gradle.plugins.cmake.params.plus +import io.github.isning.gradle.plugins.kn.krossCompile.invoke +import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinNativeTarget +import java.util.* + +plugins { + alias(libs.plugins.kotlinMultiplatform) + alias(libs.plugins.krossCompile) + `maven-publish` + signing +} + +group = "io.github.zxing-cpp" +version = "2.2.1" + +Properties().apply { + rootProject.file("local.properties").takeIf { it.exists() && it.isFile }?.let { load(it.reader()) } +}.onEach { (key, value) -> + if (key is String) ext[key] = value +} + +val hostOs = System.getProperty("os.name") + +repositories { + mavenCentral() + google() +} + +kotlin { + val androidTargets = { + listOf( + androidNativeArm32(), + androidNativeArm64(), + androidNativeX86(), + androidNativeX64(), + ) + } + val appleTargets = { + listOf( + iosX64(), + iosArm64(), + iosSimulatorArm64(), + macosX64(), + macosArm64(), + watchosX64(), + watchosArm32(), + watchosArm64(), + watchosSimulatorArm64(), + tvosX64(), + tvosArm64(), + tvosSimulatorArm64(), + ) + } + val linuxTargets = { + listOf( + linuxX64(), + linuxArm64(), + ) + } + // TODO: Linking failed, keep up with https://youtrack.jetbrains.com/issue/KT-65671 +// val windowsTargets = { +// listOf( +// mingwX64(), +// ) +// } + val enabledTargetList = mutableListOf() + enabledTargetList.addAll(androidTargets()) + enabledTargetList.addAll(linuxTargets()) + // TODO: Linking failed, keep up with https://youtrack.jetbrains.com/issue/KT-65671 +// enabledTargetList.addAll(windowsTargets()) + + if (hostOs == "Mac OS X") enabledTargetList.addAll(appleTargets()) +} + +krossCompile { + libraries { + val zxingCpp by creating { + sourceDir = file("../../core").absolutePath + outputPath = "" + libraryArtifactNames = listOf("libZXing.a") + + cinterop { + packageName = "zxingcpp.cinterop" + headers = listOf("$sourceDir/src/ZXingC.h") + } + cmake.apply { + val buildPath = project.layout.buildDirectory.dir("cmake").get().asFile.absolutePath + + "/{projectName}/{targetName}" + configParams { + buildDir = buildPath + } + configParams += (ModifiablePlatformEntriesImpl().apply { + buildType = "Release" + buildSharedLibs = false + } + CustomCMakeCacheEntries( + mapOf( + "BUILD_C_API" to "ON", + "BUILD_WRITERS" to "OFF", + ) + )).asCMakeParams + buildParams { + buildDir = buildPath + config = "Release" + } + buildParams += CustomCMakeParams(listOf("-j16")) + } + + androidNativeX64.konan() + androidNativeX86.konan() + androidNativeArm32.konan() + androidNativeArm64.konan() + + // TODO: Find a way to build linux targets with cxx20. Detail: https://github.com/zxing-cpp/zxing-cpp/pull/719#discussion_r1485701269 + linuxX64.konan { + cmake { + configParams += CustomCMakeCacheEntries( + mapOf( + "CMAKE_CXX_STANDARD" to "17", + ) + ).asCMakeParams + } + } + linuxArm64.konan { + cmake { + configParams += CustomCMakeCacheEntries( + mapOf( + "CMAKE_CXX_STANDARD" to "17", + ) + ).asCMakeParams + } + } + // TODO: Linking failed, keep up with https://youtrack.jetbrains.com/issue/KT-65671 +// mingwX64.konan() + + if (hostOs == "Mac OS X") { + iosX64.konan() + iosArm64.konan() + iosSimulatorArm64.konan() + macosX64.konan() + macosArm64.konan() + watchosX64.konan() + watchosArm32.konan() + watchosArm64.konan() + watchosSimulatorArm64.konan() + tvosX64.konan() + tvosArm64.konan() + tvosSimulatorArm64.konan() + } + } + } +} + +publishing { + publications.withType().all { + artifactId = artifactId.replace(project.name, "kotlin-native") + groupId = project.group.toString() + version = project.version.toString() + + pom { + name = "zxing-cpp" + description = "Wrapper for zxing-cpp barcode image processing library" + url = "https://github.com/zxing-cpp/zxing-cpp" + licenses { + license { + name = "The Apache License, Version 2.0" + url = "http://www.apache.org/licenses/LICENSE-2.0.txt" + } + } + developers { + developer { + id = "zxing-cpp" + name = "zxing-cpp community" + email = "zxingcpp@gmail.com" + } + } + scm { + connection = "scm:git:git://github.com/zxing-cpp/zxing-cpp.git" + developerConnection = "scm:git:git://github.com/zxing-cpp/zxing-cpp.git" + url = "https://github.com/zxing-cpp/zxing-cpp" + } + } + } + repositories { + maven { + name = "sonatype" + + val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/" + val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/" + setUrl(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl) + + credentials { + val ossrhUsername: String? by project + val ossrhPassword: String? by project + username = ossrhUsername + password = ossrhPassword + } + } + } +} + +signing { + val signingKey: String? by project + val signingPassword: String? by project + if (signingKey != null && signingPassword != null) { + useInMemoryPgpKeys(signingKey, signingPassword) + sign(publishing.publications) + } +} diff --git a/wrappers/kn/gradle.properties b/wrappers/kn/gradle.properties new file mode 100644 index 0000000000..8aa580cdc6 --- /dev/null +++ b/wrappers/kn/gradle.properties @@ -0,0 +1,2 @@ +kotlin.code.style=official +kotlin.mpp.enableCInteropCommonization=true \ No newline at end of file diff --git a/wrappers/kn/gradle/libs.versions.toml b/wrappers/kn/gradle/libs.versions.toml new file mode 100644 index 0000000000..96d7471f4d --- /dev/null +++ b/wrappers/kn/gradle/libs.versions.toml @@ -0,0 +1,9 @@ +[versions] +kotlin = "1.9.22" +krossCompile = "0.1.6" + +[libraries] + +[plugins] +kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" } +krossCompile = { id = "io.github.isning.gradle.plugins.kn.krossCompile", version.ref = "krossCompile" } diff --git a/wrappers/kn/gradle/wrapper/gradle-wrapper.jar b/wrappers/kn/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..249e5832f090a2944b7473328c07c9755baa3196 GIT binary patch literal 60756 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 '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +APP_NAME="Gradle" +APP_BASE_NAME=${0##*/} + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/wrappers/kn/gradlew.bat b/wrappers/kn/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/wrappers/kn/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/wrappers/kn/settings.gradle.kts b/wrappers/kn/settings.gradle.kts new file mode 100644 index 0000000000..b6a233e3a2 --- /dev/null +++ b/wrappers/kn/settings.gradle.kts @@ -0,0 +1,20 @@ +pluginManagement { + repositories { + mavenLocal() + gradlePluginPortal() + google() + mavenCentral() + } +} +plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "0.5.0" +} + +dependencyResolutionManagement { + repositories { + google() + mavenCentral() + } +} + +rootProject.name = "zxing-cpp" diff --git a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt new file mode 100644 index 0000000000..26f86fc788 --- /dev/null +++ b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/Barcode.kt @@ -0,0 +1,142 @@ +/* +* Copyright 2024 ISNing +*/ +// SPDX-License-Identifier: Apache-2.0 + +package zxingcpp + +import cnames.structs.ZXing_Barcode +import cnames.structs.ZXing_Barcodes +import kotlinx.cinterop.* +import zxingcpp.cinterop.* +import zxingcpp.cinterop.ZXing_ContentType.* +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.ref.createCleaner + +@OptIn(ExperimentalForeignApi::class) +enum class ContentType(internal val cValue: ZXing_ContentType) { + Text(ZXing_ContentType_Text), + Binary(ZXing_ContentType_Binary), + Mixed(ZXing_ContentType_Mixed), + GS1(ZXing_ContentType_GS1), + ISO15434(ZXing_ContentType_ISO15434), + UnknownECI(ZXing_ContentType_UnknownECI); +} + +@OptIn(ExperimentalForeignApi::class) +fun ZXing_ContentType.toKObject(): ContentType { + return ContentType.entries.first { it.cValue == this } +} + +data class PointI( + val x: Int, + val y: Int +) + +@OptIn(ExperimentalForeignApi::class) +fun ZXing_PointI.toKObject(): PointI = PointI(x, y) + +data class Position( + val topLeft: PointI, + val topRight: PointI, + val bottomRight: PointI, + val bottomLeft: PointI, +) + +@OptIn(ExperimentalForeignApi::class) +fun ZXing_Position.toKObject(): Position = Position( + topLeft.toKObject(), + topRight.toKObject(), + bottomRight.toKObject(), + bottomLeft.toKObject(), +) + +@OptIn(ExperimentalForeignApi::class) +class Barcode(val cValue: CValuesRef) { + val isValid: Boolean + get() = ZXing_Barcode_isValid(cValue) + val errorMsg: String? by lazy { + ZXing_Barcode_errorMsg(cValue)?.toKStringNullPtrHandledAndFree() + } + val format: BarcodeFormat by lazy { + ZXing_Barcode_format(cValue).parseIntoBarcodeFormat().first { it != BarcodeFormat.None } + } + val contentType: ContentType by lazy { + ZXing_Barcode_contentType(cValue).toKObject() + } + + val bytes: ByteArray? by lazy { + memScoped { + val len = alloc() + (ZXing_Barcode_bytes(cValue, len.ptr)?.run { + readBytes(len.value).also { ZXing_free(this) } + } ?: throw OutOfMemoryError()).takeUnless { it.isEmpty() } + } + } + + val bytesECI: ByteArray? by lazy { + memScoped { + val len = alloc() + (ZXing_Barcode_bytesECI(cValue, len.ptr)?.run { + readBytes(len.value).also { ZXing_free(this) } + } ?: throw OutOfMemoryError()).takeUnless { it.isEmpty() } + } + } + + val text: String? by lazy { + ZXing_Barcode_text(cValue)?.toKStringNullPtrHandledAndFree() + } + val ecLevel: String? by lazy { + ZXing_Barcode_ecLevel(cValue)?.toKStringNullPtrHandledAndFree() + } + val symbologyIdentifier: String? by lazy { + ZXing_Barcode_symbologyIdentifier(cValue)?.toKStringNullPtrHandledAndFree() + } + val position: Position by lazy { + ZXing_Barcode_position(cValue).useContents { toKObject() } + } + val orientation: Int + get() = ZXing_Barcode_orientation(cValue) + val hasECI: Boolean + get() = ZXing_Barcode_hasECI(cValue) + val isInverted: Boolean + get() = ZXing_Barcode_isInverted(cValue) + val isMirrored: Boolean + get() = ZXing_Barcode_isMirrored(cValue) + val lineCount: Int + get() = ZXing_Barcode_lineCount(cValue) + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val cleaner = createCleaner(cValue) { ZXing_Barcode_delete(it) } + + override fun toString(): String { + return "Barcode(" + + "cValue=$cValue, " + + "bytes=${bytes?.contentToString()}, " + + "bytesECI=${bytesECI?.contentToString()}, " + + "lineCount=$lineCount, " + + "isMirrored=$isMirrored, " + + "isInverted=$isInverted, " + + "hasECI=$hasECI, " + + "orientation=$orientation, " + + "position=$position, " + + "symbologyIdentifier=$symbologyIdentifier, " + + "ecLevel=$ecLevel, " + + "text=$text, " + + "contentType=$contentType, " + + "format=$format, " + + "errorMsg=$errorMsg, " + + "isValid=$isValid" + + ")" + } +} + +@OptIn(ExperimentalForeignApi::class) +fun CValuesRef.toKObject(): Barcode = Barcode(this) + +@OptIn(ExperimentalForeignApi::class) +fun CValuesRef.toKObject(): List = mutableListOf().apply { + for (i in 0.. = + BarcodeFormat.entries.filter { this.or(it.rawValue) == this }.toSet() + +@OptIn(ExperimentalForeignApi::class) +fun Iterable.toValue(): ZXing_BarcodeFormat = + this.map { it.rawValue }.reduce { acc, format -> acc.or(format) } diff --git a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeReader.kt b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeReader.kt new file mode 100644 index 0000000000..317cfbc727 --- /dev/null +++ b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/BarcodeReader.kt @@ -0,0 +1,121 @@ +/* +* Copyright 2024 ISNing +*/ +// SPDX-License-Identifier: Apache-2.0 + +package zxingcpp + +import cnames.structs.ZXing_ReaderOptions +import kotlinx.cinterop.* +import zxingcpp.cinterop.* +import zxingcpp.cinterop.ZXing_Binarizer.* +import zxingcpp.cinterop.ZXing_EanAddOnSymbol.* +import zxingcpp.cinterop.ZXing_TextMode.* +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.ref.createCleaner + + +@OptIn(ExperimentalForeignApi::class) +internal fun CPointer?.toKStringNullPtrHandledAndFree(): String? = (this ?: throw OutOfMemoryError()).run { + toKString().also { ZXing_free(this) }.ifEmpty { null } +} + + +@OptIn(ExperimentalForeignApi::class) +class BarcodeReader : ReaderOptions() { + @Throws(BarcodeReadingException::class) + fun read(imageView: ImageView): List = + ZXing_ReadBarcodes(imageView.cValue, cValue)?.let { cValues -> cValues.toKObject().also { ZXing_Barcodes_delete(cValues) } } + ?: throw BarcodeReadingException(ZXing_LastErrorMsg()?.toKStringNullPtrHandledAndFree()) +} + +class BarcodeReadingException(message: String?) : Exception("Failed to read barcodes: $message") + + +@OptIn(ExperimentalForeignApi::class) +open class ReaderOptions { + var tryHarder: Boolean + get() = ZXing_ReaderOptions_getTryHarder(cValue) + set(value) = ZXing_ReaderOptions_setTryHarder(cValue, value) + var tryRotate: Boolean + get() = ZXing_ReaderOptions_getTryRotate(cValue) + set(value) = ZXing_ReaderOptions_setTryRotate(cValue, value) + var tryDownscale: Boolean + get() = ZXing_ReaderOptions_getTryDownscale(cValue) + set(value) = ZXing_ReaderOptions_setTryDownscale(cValue, value) + var tryInvert: Boolean + get() = ZXing_ReaderOptions_getTryInvert(cValue) + set(value) = ZXing_ReaderOptions_setTryInvert(cValue, value) + var isPure: Boolean + get() = ZXing_ReaderOptions_getIsPure(cValue) + set(value) = ZXing_ReaderOptions_setIsPure(cValue, value) + var returnErrors: Boolean + get() = ZXing_ReaderOptions_getReturnErrors(cValue) + set(value) = ZXing_ReaderOptions_setReturnErrors(cValue, value) + var binarizer: Binarizer + get() = Binarizer.fromCValue(ZXing_ReaderOptions_getBinarizer(cValue)) + set(value) = ZXing_ReaderOptions_setBinarizer(cValue, value.cValue) + var formats: Set + get() = ZXing_ReaderOptions_getFormats(cValue).parseIntoBarcodeFormat() + set(value) = ZXing_ReaderOptions_setFormats(cValue, value.toValue()) + var eanAddOnSymbol: EanAddOnSymbol + get() = EanAddOnSymbol.fromCValue(ZXing_ReaderOptions_getEanAddOnSymbol(cValue)) + set(value) = ZXing_ReaderOptions_setEanAddOnSymbol(cValue, value.cValue) + var textMode: TextMode + get() = TextMode.fromCValue(ZXing_ReaderOptions_getTextMode(cValue)) + set(value) = ZXing_ReaderOptions_setTextMode(cValue, value.cValue) + var minLineCount: Int + get() = ZXing_ReaderOptions_getMinLineCount(cValue) + set(value) = ZXing_ReaderOptions_setMinLineCount(cValue, value) + var maxNumberOfSymbols: Int + get() = ZXing_ReaderOptions_getMaxNumberOfSymbols(cValue) + set(value) = ZXing_ReaderOptions_setMaxNumberOfSymbols(cValue, value) + + val cValue: CValuesRef? = ZXing_ReaderOptions_new() + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val cleaner = createCleaner(cValue) { ZXing_ReaderOptions_delete(it) } +} + +@OptIn(ExperimentalForeignApi::class) +enum class Binarizer(internal val cValue: ZXing_Binarizer) { + LocalAverage(ZXing_Binarizer_LocalAverage), + GlobalHistogram(ZXing_Binarizer_GlobalHistogram), + FixedThreshold(ZXing_Binarizer_FixedThreshold), + BoolCast(ZXing_Binarizer_BoolCast); + + companion object { + fun fromCValue(cValue: ZXing_Binarizer): Binarizer { + return entries.first { it.cValue == cValue } + } + } +} + +@OptIn(ExperimentalForeignApi::class) +enum class EanAddOnSymbol(internal val cValue: ZXing_EanAddOnSymbol) { + Ignore(ZXing_EanAddOnSymbol_Ignore), + Read(ZXing_EanAddOnSymbol_Read), + Require(ZXing_EanAddOnSymbol_Require); + + companion object { + fun fromCValue(cValue: ZXing_EanAddOnSymbol): EanAddOnSymbol { + return entries.first { it.cValue == cValue } + } + } +} + +@OptIn(ExperimentalForeignApi::class) +enum class TextMode(internal val cValue: ZXing_TextMode) { + Plain(ZXing_TextMode_Plain), + ECI(ZXing_TextMode_ECI), + HRI(ZXing_TextMode_HRI), + Hex(ZXing_TextMode_Hex), + Escaped(ZXing_TextMode_Escaped); + + companion object { + fun fromCValue(cValue: ZXing_TextMode): TextMode { + return entries.first { it.cValue == cValue } + } + } +} diff --git a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt new file mode 100644 index 0000000000..69308906bb --- /dev/null +++ b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt @@ -0,0 +1,58 @@ +/* +* Copyright 2024 ISNing +*/ +// SPDX-License-Identifier: Apache-2.0 + +package zxingcpp + +import cnames.structs.ZXing_ImageView +import kotlinx.cinterop.* +import zxingcpp.cinterop.* +import kotlin.experimental.ExperimentalNativeApi +import kotlin.native.ref.createCleaner + +@OptIn(ExperimentalForeignApi::class) +class ImageView( + val data: ByteArray, + val width: Int, + val height: Int, + val format: ImageFormat, + val rowStride: Int = 0, + val pixStride: Int = 0, +) { + private val pinnedData = data.pin() + val cValue: CPointer? = + ZXing_ImageView_new_checked( + pinnedData.addressOf(0).reinterpret(), + data.size, + width, + height, + format.cValue, + rowStride, + pixStride + ) + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val cValueCleaner = createCleaner(cValue) { ZXing_ImageView_delete(it) } + + @Suppress("unused") + @OptIn(ExperimentalNativeApi::class) + private val pinnedDataCleaner = createCleaner(pinnedData) { it.unpin() } +} + +@OptIn(ExperimentalForeignApi::class) +enum class ImageFormat(internal val cValue: ZXing_ImageFormat) { + None(ZXing_ImageFormat_None), + Lum(ZXing_ImageFormat_Lum), + RGB(ZXing_ImageFormat_RGB), + BGR(ZXing_ImageFormat_BGR), + RGBX(ZXing_ImageFormat_RGBX), + XRGB(ZXing_ImageFormat_XRGB), + BGRX(ZXing_ImageFormat_BGRX), + XBGR(ZXing_ImageFormat_XBGR) +} + +@OptIn(ExperimentalForeignApi::class) +fun ZXing_ImageFormat.parseIntoImageFormat(): ImageFormat? = + ImageFormat.entries.firstOrNull { it.cValue == this } diff --git a/wrappers/kn/src/nativeTest/kotlin/Test.kt b/wrappers/kn/src/nativeTest/kotlin/Test.kt new file mode 100644 index 0000000000..6ff753cfb1 --- /dev/null +++ b/wrappers/kn/src/nativeTest/kotlin/Test.kt @@ -0,0 +1,41 @@ +/* +* Copyright 2024 ISNing +*/ +// SPDX-License-Identifier: Apache-2.0 + +import zxingcpp.* +import kotlin.experimental.ExperimentalNativeApi +import kotlin.test.Test +import kotlin.test.assertContentEquals +import kotlin.test.assertEquals +import kotlin.test.assertNotNull + +class BarcodeReaderTest { + + @Test + @OptIn(ExperimentalNativeApi::class) + fun `read barcode`() { + val data = "0000101000101101011110111101011011101010100111011100101000100101110010100000".map { + if (it == '0') 255.toByte() else 0.toByte() + } + + val iv = ImageView(data.toByteArray(), data.size, 1, ImageFormat.Lum) + val br = BarcodeReader().apply { + binarizer = Binarizer.BoolCast + } + val res = br.read(iv).firstOrNull() + + val expected = "96385074" + + assertNotNull(res) + assert(res.isValid) + assertEquals(res.format, BarcodeFormat.EAN8) + assertEquals(res.text, expected) + assertContentEquals(res.bytes, expected.encodeToByteArray()) + assert(!res.hasECI) + assertEquals(res.contentType, ContentType.Text) + assertEquals(res.orientation, 0) + assertEquals(res.position.topLeft, PointI(4, 0)) + assertEquals(res.lineCount, 1) + } +} From 1bda584b7bf7595351424174bbd6a60b0d78c503 Mon Sep 17 00:00:00 2001 From: Axel Gembe Date: Sat, 17 Feb 2024 13:42:54 +0700 Subject: [PATCH 136/431] cmake: Make build reproducible across different build directories Without this, the usage of the __FILE__ macro leaves the build directory in the binary. When building the Python extension with build isolation enabled this would lead to random paths in the binary. --- core/CMakeLists.txt | 13 +++++++++++-- wrappers/python/CMakeLists.txt | 12 ++++++++++-- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 2afedb078a..938eb5941b 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -44,6 +44,17 @@ else() -Wall -Wextra -Wno-missing-braces -Werror=undef -Werror=return-type) endif() +include (CheckCXXCompilerFlag) + +# This is needed for reproducible builds across different build directories. +# Without this, the usage of the __FILE__ macro leaves the build directory in +# the binary. When building the Python extension with build isolation enabled +# this would lead to random paths in the binary. +set(FILE_PREFIX_ARG "-ffile-prefix-map=${CMAKE_CURRENT_SOURCE_DIR}=") +check_cxx_compiler_flag("${FILE_PREFIX_ARG}" HAS_FILE_PREFIX_ARG) +if(HAS_FILE_PREFIX_ARG) + set(ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} "${FILE_PREFIX_ARG}") +endif() ################# Source files @@ -458,8 +469,6 @@ target_compile_options (ZXing PRIVATE ${ZXING_CORE_LOCAL_DEFINES} ) -include (CheckCXXCompilerFlag) - target_compile_features(ZXing PUBLIC cxx_std_17) target_link_libraries (ZXing PRIVATE Threads::Threads) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 9610420485..1cb3fd749b 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -17,8 +17,16 @@ if (NOT hasParent) option (BUILD_WRITERS "Build with writer support (encoders)" ON) option (BUILD_READERS "Build with reader support (decoders)" ON) set(BUILD_DEPENDENCIES "AUTO") - if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) + + set(CORE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/core) + if(IS_SYMLINK ${CORE_PATH}) + # This is needed because otherwise GCC resolves the symlink which causes paths to randomly + # be prefixed by /core or by /wrappers/python/core depending on include order. + set(CORE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/../../core) + endif() + + if(EXISTS ${CORE_PATH}) + add_subdirectory(${CORE_PATH} ZXing EXCLUDE_FROM_ALL) include(${CMAKE_CURRENT_SOURCE_DIR}/zxing.cmake) else() message(FATAL_ERROR "Unable to locate zxing source code") From f220ee8fe5b6f09b99818350398717c37c9d373a Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 19 Feb 2024 18:23:28 +0100 Subject: [PATCH 137/431] qt: Result -> Barcode renaming and new `failedRead()` signal --- example/ZXingQt5CamReader.qml | 29 +++++++++--------- example/ZXingQt6CamReader.qml | 29 +++++++++--------- example/ZXingQtReader.cpp | 14 ++++----- example/ZXingQtReader.h | 55 +++++++++++++++++++---------------- 4 files changed, 63 insertions(+), 64 deletions(-) diff --git a/example/ZXingQt5CamReader.qml b/example/ZXingQt5CamReader.qml index 4b3191f884..12e0ab624b 100644 --- a/example/ZXingQt5CamReader.qml +++ b/example/ZXingQt5CamReader.qml @@ -35,24 +35,21 @@ Window { tryDownscale: tryDownscaleSwitch.checked textMode: ZXing.HRI - // callback with parameter 'result', called for every successfully processed frame - // onFoundBarcode: {} - - // callback with parameter 'result', called for every processed frame - onNewResult: { - points = result.isValid - ? [result.position.topLeft, result.position.topRight, result.position.bottomRight, result.position.bottomLeft] - : nullPoints - - if (result.isValid) { - resetInfo.restart() - info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.contentTypeName).arg(result.runTime) - } + // callback with parameter 'barcode', called for every successfully processed frame + onFoundBarcode: (barcode)=> { + points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) - if (!resetInfo.running) - info.text = "No barcode found" + resetInfo.restart() +// console.log(barcode) + } -// console.log(result) + // called for every processed frame where no barcode was detected + onFailedRead: ()=> { + points = nullPoints + + if (!resetInfo.running) + info.text = "No barcode found (in %1 ms)".arg(runTime) } } diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index 47b35574fd..4100aa7b47 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -36,24 +36,21 @@ Window { tryDownscale: tryDownscaleSwitch.checked textMode: ZXing.TextMode.HRI - // callback with parameter 'result', called for every successfully processed frame - // onFoundBarcode: {} - - // callback with parameter 'result', called for every processed frame - onNewResult: (result)=> { - points = result.isValid - ? [result.position.topLeft, result.position.topRight, result.position.bottomRight, result.position.bottomLeft] - : nullPoints - - if (result.isValid) { - resetInfo.restart() - info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(result.formatName).arg(result.text).arg(result.contentTypeName).arg(result.runTime) - } + // callback with parameter 'barcode', called for every successfully processed frame + onFoundBarcode: (barcode)=> { + points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) - if (!resetInfo.running) - info.text = "No barcode found" + resetInfo.restart() +// console.log(barcode) + } -// console.log(result) + // called for every processed frame where no barcode was detected + onFailedRead: ()=> { + points = nullPoints + + if (!resetInfo.running) + info.text = "No barcode found (in %1 ms)".arg(runTime) } } diff --git a/example/ZXingQtReader.cpp b/example/ZXingQtReader.cpp index 8b2174f602..2aba7dfddc 100644 --- a/example/ZXingQtReader.cpp +++ b/example/ZXingQtReader.cpp @@ -27,18 +27,18 @@ int main(int argc, char* argv[]) auto options = ReaderOptions() .setFormats(BarcodeFormat::MatrixCodes) - .setTryRotate(false) + .setTryInvert(false) .setTextMode(TextMode::HRI) .setMaxNumberOfSymbols(10); - auto results = ReadBarcodes(image, options); + auto barcodes = ReadBarcodes(image, options); - for (auto& result : results) { - qDebug() << "Text: " << result.text(); - qDebug() << "Format: " << result.format(); - qDebug() << "Content:" << result.contentType(); + for (auto& barcode : barcodes) { + qDebug() << "Text: " << barcode.text(); + qDebug() << "Format: " << barcode.format(); + qDebug() << "Content:" << barcode.contentType(); qDebug() << ""; } - return results.isEmpty() ? 1 : 0; + return barcodes.isEmpty() ? 1 : 0; } diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index aede6fc027..d720983fd5 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -97,7 +97,7 @@ class Position : public ZXing::Quadrilateral using Base::Base; }; -class Result : private ZXing::Result +class Barcode : private ZXing::Result { Q_GADGET @@ -115,9 +115,9 @@ class Result : private ZXing::Result Position _position; public: - Result() = default; // required for qmetatype machinery + Barcode() = default; // required for qmetatype machinery - explicit Result(ZXing::Result&& r) : ZXing::Result(std::move(r)) { + explicit Barcode(ZXing::Result&& r) : ZXing::Result(std::move(r)) { _text = QString::fromStdString(ZXing::Result::text()); _bytes = QByteArray(reinterpret_cast(ZXing::Result::bytes().data()), Size(ZXing::Result::bytes())); auto& pos = ZXing::Result::position(); @@ -134,21 +134,21 @@ class Result : private ZXing::Result const QString& text() const { return _text; } const QByteArray& bytes() const { return _bytes; } const Position& position() const { return _position; } - - // For debugging/development - int runTime = 0; - Q_PROPERTY(int runTime MEMBER runTime) }; -inline QList QListResults(ZXing::Results&& zxres) +inline QList ZXBarcodesToQBarcodes(ZXing::Results&& zxres) { - QList res; + QList res; for (auto&& r : zxres) - res.push_back(Result(std::move(r))); +#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) + res.push_back(Barcode(std::move(r))); +#else + res.emplace_back(std::move(r)); +#endif return res; } -inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts = {}) +inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts = {}) { using namespace ZXing; @@ -170,21 +170,21 @@ inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts = }; auto exec = [&](const QImage& img) { - return QListResults(ZXing::ReadBarcodes( + return ZXBarcodesToQBarcodes(ZXing::ReadBarcodes( {img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast(img.bytesPerLine())}, opts)); }; return ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img); } -inline Result ReadBarcode(const QImage& img, const ReaderOptions& opts = {}) +inline Barcode ReadBarcode(const QImage& img, const ReaderOptions& opts = {}) { auto res = ReadBarcodes(img, ReaderOptions(opts).setMaxNumberOfSymbols(1)); - return !res.isEmpty() ? res.takeFirst() : Result(); + return !res.isEmpty() ? res.takeFirst() : Barcode(); } #ifdef QT_MULTIMEDIA_LIB -inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& opts = {}) +inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& opts = {}) { using namespace ZXing; @@ -280,7 +280,7 @@ inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& } QScopeGuard unmap([&] { img.unmap(); }); - return QListResults(ZXing::ReadBarcodes( + return ZXBarcodesToQBarcodes(ZXing::ReadBarcodes( {img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, opts)); } else { @@ -300,10 +300,10 @@ inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& } } -inline Result ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = {}) +inline Barcode ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = {}) { auto res = ReadBarcodes(frame, ReaderOptions(opts).setMaxNumberOfSymbols(1)); - return !res.isEmpty() ? res.takeFirst() : Result(); + return !res.isEmpty() ? res.takeFirst() : Barcode(); } #define ZQ_PROPERTY(Type, name, setter) \ @@ -371,25 +371,30 @@ class BarcodeReader : public QObject, private ReaderOptions ZQ_PROPERTY(bool, tryDownscale, setTryDownscale) ZQ_PROPERTY(bool, isPure, setIsPure) + // For debugging/development + int runTime = 0; + Q_PROPERTY(int runTime MEMBER runTime) + public slots: - ZXingQt::Result process(const QVideoFrame& image) + ZXingQt::Barcode process(const QVideoFrame& image) { QElapsedTimer t; t.start(); auto res = ReadBarcode(image, *this); - res.runTime = t.elapsed(); + runTime = t.elapsed(); - emit newResult(res); if (res.isValid()) emit foundBarcode(res); + else + emit failedRead(); return res; } signals: - void newResult(ZXingQt::Result result); - void foundBarcode(ZXingQt::Result result); + void failedRead(); + void foundBarcode(ZXingQt::Barcode barcode); #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) public: @@ -443,7 +448,7 @@ inline QVideoFilterRunnable* BarcodeReader::createFilterRunnable() Q_DECLARE_METATYPE(ZXingQt::Position) -Q_DECLARE_METATYPE(ZXingQt::Result) +Q_DECLARE_METATYPE(ZXingQt::Barcode) #ifdef QT_QML_LIB @@ -460,7 +465,7 @@ inline void registerQmlAndMetaTypes() // supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name // but then the qml side complains about "unregistered type" qRegisterMetaType("Position"); - qRegisterMetaType("Result"); + qRegisterMetaType("Barcode"); qmlRegisterUncreatableMetaObject( ZXingQt::staticMetaObject, "ZXing", 1, 0, "ZXing", "Access to enums & flags only"); From de348ffea91b1ff759ece5d9d86b6c2aaf09551d Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 19 Feb 2024 18:28:51 +0100 Subject: [PATCH 138/431] qt: replace space with tab indentation in qml files --- .editorconfig | 2 +- example/ZXingQt5CamReader.qml | 268 +++++++++++++------------- example/ZXingQt6CamReader.qml | 342 +++++++++++++++++----------------- 3 files changed, 306 insertions(+), 306 deletions(-) diff --git a/.editorconfig b/.editorconfig index 4538c85cd7..f8db50e18e 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,c,h,html,py,kt,cs}] +[*.{cpp,c,h,html,py,kt,cs,qlm}] indent_style = tab indent_size = 4 max_line_length = 135 diff --git a/example/ZXingQt5CamReader.qml b/example/ZXingQt5CamReader.qml index 12e0ab624b..0704c7b3a7 100644 --- a/example/ZXingQt5CamReader.qml +++ b/example/ZXingQt5CamReader.qml @@ -12,138 +12,138 @@ import QtMultimedia 5.12 import ZXing 1.0 Window { - visible: true - width: 640 - height: 480 - title: Qt.application.name - - property var nullPoints: [Qt.point(0,0), Qt.point(0,0), Qt.point(0,0), Qt.point(0,0)] - property var points: nullPoints - - Timer { - id: resetInfo - interval: 1000 - } - - BarcodeReader { - id: barcodeReader - - formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) - tryRotate: tryRotateSwitch.checked - tryHarder: tryHarderSwitch.checked - tryInvert: tryInvertSwitch.checked - tryDownscale: tryDownscaleSwitch.checked - textMode: ZXing.HRI - - // callback with parameter 'barcode', called for every successfully processed frame - onFoundBarcode: (barcode)=> { - points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] - info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) - - resetInfo.restart() -// console.log(barcode) - } - - // called for every processed frame where no barcode was detected - onFailedRead: ()=> { - points = nullPoints - - if (!resetInfo.running) - info.text = "No barcode found (in %1 ms)".arg(runTime) - } - } - - Camera { - id: camera - - captureMode: Camera.CaptureViewfinder - deviceId: QtMultimedia.availableCameras[camerasComboBox.currentIndex] ? QtMultimedia.availableCameras[camerasComboBox.currentIndex].deviceId : "" - - onDeviceIdChanged: { - focus.focusMode = CameraFocus.FocusContinuous - focus.focusPointMode = CameraFocus.FocusPointAuto - } - - onError: console.log("camera error:" + errorString) - } - - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: false - visible: QtMultimedia.availableCameras.length > 1 - Label { - text: qsTr("Camera: ") - Layout.fillWidth: false - } - ComboBox { - id: camerasComboBox - Layout.fillWidth: true - model: QtMultimedia.availableCameras - textRole: "displayName" - currentIndex: 0 - } - } - - VideoOutput { - id: videoOutput - Layout.fillHeight: true - Layout.fillWidth: true - filters: [barcodeReader] - source: camera - autoOrientation: true - - Shape { - id: polygon - anchors.fill: parent - visible: points.length == 4 - ShapePath { - strokeWidth: 3 - strokeColor: "red" - strokeStyle: ShapePath.SolidLine - fillColor: "transparent" - //TODO: really? I don't know qml... - startX: videoOutput.mapPointToItem(points[3]).x - startY: videoOutput.mapPointToItem(points[3]).y - PathLine { - x: videoOutput.mapPointToItem(points[0]).x - y: videoOutput.mapPointToItem(points[0]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[1]).x - y: videoOutput.mapPointToItem(points[1]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[2]).x - y: videoOutput.mapPointToItem(points[2]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[3]).x - y: videoOutput.mapPointToItem(points[3]).y - } - } - } - - Label { - id: info - color: "white" - padding: 10 - background: Rectangle { color: "#80808080" } - } - - ColumnLayout { - anchors.right: parent.right - anchors.bottom: parent.bottom - - Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } - Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } - Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } - Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } - Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } - Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } - } - } - } + visible: true + width: 640 + height: 480 + title: Qt.application.name + + property var nullPoints: [Qt.point(0,0), Qt.point(0,0), Qt.point(0,0), Qt.point(0,0)] + property var points: nullPoints + + Timer { + id: resetInfo + interval: 1000 + } + + BarcodeReader { + id: barcodeReader + + formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) + tryRotate: tryRotateSwitch.checked + tryHarder: tryHarderSwitch.checked + tryInvert: tryInvertSwitch.checked + tryDownscale: tryDownscaleSwitch.checked + textMode: ZXing.HRI + + // callback with parameter 'barcode', called for every successfully processed frame + onFoundBarcode: (barcode)=> { + points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) + + resetInfo.restart() +// console.log(barcode) + } + + // called for every processed frame where no barcode was detected + onFailedRead: ()=> { + points = nullPoints + + if (!resetInfo.running) + info.text = "No barcode found (in %1 ms)".arg(runTime) + } + } + + Camera { + id: camera + + captureMode: Camera.CaptureViewfinder + deviceId: QtMultimedia.availableCameras[camerasComboBox.currentIndex] ? QtMultimedia.availableCameras[camerasComboBox.currentIndex].deviceId : "" + + onDeviceIdChanged: { + focus.focusMode = CameraFocus.FocusContinuous + focus.focusPointMode = CameraFocus.FocusPointAuto + } + + onError: console.log("camera error:" + errorString) + } + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: false + visible: QtMultimedia.availableCameras.length > 1 + Label { + text: qsTr("Camera: ") + Layout.fillWidth: false + } + ComboBox { + id: camerasComboBox + Layout.fillWidth: true + model: QtMultimedia.availableCameras + textRole: "displayName" + currentIndex: 0 + } + } + + VideoOutput { + id: videoOutput + Layout.fillHeight: true + Layout.fillWidth: true + filters: [barcodeReader] + source: camera + autoOrientation: true + + Shape { + id: polygon + anchors.fill: parent + visible: points.length == 4 + ShapePath { + strokeWidth: 3 + strokeColor: "red" + strokeStyle: ShapePath.SolidLine + fillColor: "transparent" + //TODO: really? I don't know qml... + startX: videoOutput.mapPointToItem(points[3]).x + startY: videoOutput.mapPointToItem(points[3]).y + PathLine { + x: videoOutput.mapPointToItem(points[0]).x + y: videoOutput.mapPointToItem(points[0]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[1]).x + y: videoOutput.mapPointToItem(points[1]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[2]).x + y: videoOutput.mapPointToItem(points[2]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[3]).x + y: videoOutput.mapPointToItem(points[3]).y + } + } + } + + Label { + id: info + color: "white" + padding: 10 + background: Rectangle { color: "#80808080" } + } + + ColumnLayout { + anchors.right: parent.right + anchors.bottom: parent.bottom + + Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } + Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } + Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } + Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } + Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } + Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } + } + } + } } diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index 4100aa7b47..7eb85445f4 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -12,175 +12,175 @@ import QtMultimedia import ZXing Window { - visible: true - width: 640 - height: 480 - title: Qt.application.name - - property var nullPoints: [Qt.point(0,0), Qt.point(0,0), Qt.point(0,0), Qt.point(0,0)] - property var points: nullPoints - - Timer { - id: resetInfo - interval: 1000 - } - - BarcodeReader { - id: barcodeReader - videoSink: videoOutput.videoSink - - formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) - tryRotate: tryRotateSwitch.checked - tryHarder: tryHarderSwitch.checked - tryInvert: tryInvertSwitch.checked - tryDownscale: tryDownscaleSwitch.checked - textMode: ZXing.TextMode.HRI - - // callback with parameter 'barcode', called for every successfully processed frame - onFoundBarcode: (barcode)=> { - points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] - info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) - - resetInfo.restart() -// console.log(barcode) - } - - // called for every processed frame where no barcode was detected - onFailedRead: ()=> { - points = nullPoints - - if (!resetInfo.running) - info.text = "No barcode found (in %1 ms)".arg(runTime) - } - } - - MediaDevices { - id: devices - } - - Camera { - id: camera - cameraDevice: devices.videoInputs[camerasComboBox.currentIndex] ? devices.videoInputs[camerasComboBox.currentIndex] : devices.defaultVideoInput - focusMode: Camera.FocusModeAutoNear - onErrorOccurred: console.log("camera error:" + errorString) - active: true - } - - CaptureSession { - id: captureSession - camera: camera - videoOutput: videoOutput - } - - ColumnLayout { - anchors.fill: parent - - RowLayout { - Layout.fillWidth: true - Layout.fillHeight: false - visible: devices.videoInputs.length > 1 - Label { - text: qsTr("Camera: ") - Layout.fillWidth: false - } - ComboBox { - id: camerasComboBox - Layout.fillWidth: true - model: devices.videoInputs - textRole: "description" - currentIndex: 0 - } - } - - VideoOutput { - id: videoOutput - Layout.fillHeight: true - Layout.fillWidth: true - - function mapPointToItem(point) - { - if (videoOutput.sourceRect.width === 0 || videoOutput.sourceRect.height === 0) - return Qt.point(0, 0); - - let dx = point.x; - let dy = point.y; - - if ((videoOutput.orientation % 180) == 0) - { - dx = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; - dy = dy * videoOutput.contentRect.height / videoOutput.sourceRect.height; - } - else - { - dx = dx * videoOutput.contentRect.height / videoOutput.sourceRect.height; - dy = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; - } - - switch ((videoOutput.orientation + 360) % 360) - { - case 0: - default: - return Qt.point(videoOutput.contentRect.x + dx, videoOutput.contentRect.y + dy); - case 90: - return Qt.point(videoOutput.contentRect.x + dy, videoOutput.contentRect.y + videoOutput.contentRect.height - dx); - case 180: - return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dx, videoOutput.contentRect.y + videoOutput.contentRect.height -dy); - case 270: - return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dy, videoOutput.contentRect.y + dx); - } - } - - Shape { - id: polygon - anchors.fill: parent - visible: points.length === 4 - - ShapePath { - strokeWidth: 3 - strokeColor: "red" - strokeStyle: ShapePath.SolidLine - fillColor: "transparent" - //TODO: really? I don't know qml... - startX: videoOutput.mapPointToItem(points[3]).x - startY: videoOutput.mapPointToItem(points[3]).y - - PathLine { - x: videoOutput.mapPointToItem(points[0]).x - y: videoOutput.mapPointToItem(points[0]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[1]).x - y: videoOutput.mapPointToItem(points[1]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[2]).x - y: videoOutput.mapPointToItem(points[2]).y - } - PathLine { - x: videoOutput.mapPointToItem(points[3]).x - y: videoOutput.mapPointToItem(points[3]).y - } - } - } - - Label { - id: info - color: "white" - padding: 10 - background: Rectangle { color: "#80808080" } - } - - ColumnLayout { - anchors.right: parent.right - anchors.bottom: parent.bottom - - Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } - Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } - Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } - Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } - Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } - Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } - } - } - } + visible: true + width: 640 + height: 480 + title: Qt.application.name + + property var nullPoints: [Qt.point(0,0), Qt.point(0,0), Qt.point(0,0), Qt.point(0,0)] + property var points: nullPoints + + Timer { + id: resetInfo + interval: 1000 + } + + BarcodeReader { + id: barcodeReader + videoSink: videoOutput.videoSink + + formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) + tryRotate: tryRotateSwitch.checked + tryHarder: tryHarderSwitch.checked + tryInvert: tryInvertSwitch.checked + tryDownscale: tryDownscaleSwitch.checked + textMode: ZXing.TextMode.HRI + + // callback with parameter 'barcode', called for every successfully processed frame + onFoundBarcode: (barcode)=> { + points = [barcode.position.topLeft, barcode.position.topRight, barcode.position.bottomRight, barcode.position.bottomLeft] + info.text = qsTr("Format: \t %1 \nText: \t %2 \nType: \t %3 \nTime: \t %4 ms").arg(barcode.formatName).arg(barcode.text).arg(barcode.contentTypeName).arg(runTime) + + resetInfo.restart() +// console.log(barcode) + } + + // called for every processed frame where no barcode was detected + onFailedRead: ()=> { + points = nullPoints + + if (!resetInfo.running) + info.text = "No barcode found (in %1 ms)".arg(runTime) + } + } + + MediaDevices { + id: devices + } + + Camera { + id: camera + cameraDevice: devices.videoInputs[camerasComboBox.currentIndex] ? devices.videoInputs[camerasComboBox.currentIndex] : devices.defaultVideoInput + focusMode: Camera.FocusModeAutoNear + onErrorOccurred: console.log("camera error:" + errorString) + active: true + } + + CaptureSession { + id: captureSession + camera: camera + videoOutput: videoOutput + } + + ColumnLayout { + anchors.fill: parent + + RowLayout { + Layout.fillWidth: true + Layout.fillHeight: false + visible: devices.videoInputs.length > 1 + Label { + text: qsTr("Camera: ") + Layout.fillWidth: false + } + ComboBox { + id: camerasComboBox + Layout.fillWidth: true + model: devices.videoInputs + textRole: "description" + currentIndex: 0 + } + } + + VideoOutput { + id: videoOutput + Layout.fillHeight: true + Layout.fillWidth: true + + function mapPointToItem(point) + { + if (videoOutput.sourceRect.width === 0 || videoOutput.sourceRect.height === 0) + return Qt.point(0, 0); + + let dx = point.x; + let dy = point.y; + + if ((videoOutput.orientation % 180) == 0) + { + dx = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; + dy = dy * videoOutput.contentRect.height / videoOutput.sourceRect.height; + } + else + { + dx = dx * videoOutput.contentRect.height / videoOutput.sourceRect.height; + dy = dx * videoOutput.contentRect.width / videoOutput.sourceRect.width; + } + + switch ((videoOutput.orientation + 360) % 360) + { + case 0: + default: + return Qt.point(videoOutput.contentRect.x + dx, videoOutput.contentRect.y + dy); + case 90: + return Qt.point(videoOutput.contentRect.x + dy, videoOutput.contentRect.y + videoOutput.contentRect.height - dx); + case 180: + return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dx, videoOutput.contentRect.y + videoOutput.contentRect.height -dy); + case 270: + return Qt.point(videoOutput.contentRect.x + videoOutput.contentRect.width - dy, videoOutput.contentRect.y + dx); + } + } + + Shape { + id: polygon + anchors.fill: parent + visible: points.length === 4 + + ShapePath { + strokeWidth: 3 + strokeColor: "red" + strokeStyle: ShapePath.SolidLine + fillColor: "transparent" + //TODO: really? I don't know qml... + startX: videoOutput.mapPointToItem(points[3]).x + startY: videoOutput.mapPointToItem(points[3]).y + + PathLine { + x: videoOutput.mapPointToItem(points[0]).x + y: videoOutput.mapPointToItem(points[0]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[1]).x + y: videoOutput.mapPointToItem(points[1]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[2]).x + y: videoOutput.mapPointToItem(points[2]).y + } + PathLine { + x: videoOutput.mapPointToItem(points[3]).x + y: videoOutput.mapPointToItem(points[3]).y + } + } + } + + Label { + id: info + color: "white" + padding: 10 + background: Rectangle { color: "#80808080" } + } + + ColumnLayout { + anchors.right: parent.right + anchors.bottom: parent.bottom + + Switch {id: tryRotateSwitch; text: qsTr("Try Rotate"); checked: true } + Switch {id: tryHarderSwitch; text: qsTr("Try Harder"); checked: true } + Switch {id: tryInvertSwitch; text: qsTr("Try Invert"); checked: true } + Switch {id: tryDownscaleSwitch; text: qsTr("Try Downscale"); checked: true } + Switch {id: linearSwitch; text: qsTr("Linear Codes"); checked: true } + Switch {id: matrixSwitch; text: qsTr("Matrix Codes"); checked: true } + } + } + } } From 2091f001b0598f8339465e4384bd11efb8da8034 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 02:01:35 +0100 Subject: [PATCH 139/431] c-API: add ZXing_ErrorType enum and ZXing_Barcode_errorType() getter Also add this to dotnet and rust wrapper. --- core/src/ZXingC.cpp | 5 ++++ core/src/ZXingC.h | 9 +++++++ wrappers/dotnet/ZXingCpp.Demo/Program.cs | 3 ++- wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs | 2 ++ wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 4 +++ wrappers/rust/examples/demo.rs | 5 ++-- wrappers/rust/src/bindings.rs | 6 +++++ wrappers/rust/src/lib.rs | 29 ++++++++++++++++++++- wrappers/rust/src/tests.rs | 2 ++ 9 files changed, 61 insertions(+), 4 deletions(-) diff --git a/core/src/ZXingC.cpp b/core/src/ZXingC.cpp index 2986362495..edb3aa6324 100644 --- a/core/src/ZXingC.cpp +++ b/core/src/ZXingC.cpp @@ -206,6 +206,11 @@ bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode) return barcode != NULL && barcode->isValid(); } +ZXing_ErrorType ZXing_Barcode_errorType(const ZXing_Barcode* barcode) +{ + return static_cast(barcode->error().type()); +} + char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode) { return copy(ToString(barcode->error())); diff --git a/core/src/ZXingC.h b/core/src/ZXingC.h index 921ef85554..934d8b4696 100644 --- a/core/src/ZXingC.h +++ b/core/src/ZXingC.h @@ -172,6 +172,14 @@ typedef enum ZXing_ContentType_UnknownECI } ZXing_ContentType; +typedef enum +{ + ZXing_ErrorType_None, + ZXing_ErrorType_Format, + ZXing_ErrorType_Checksum, + ZXing_ErrorType_Unsupported +} ZXing_ErrorType; + char* ZXing_ContentTypeToString(ZXing_ContentType type); typedef struct ZXing_PointI @@ -187,6 +195,7 @@ typedef struct ZXing_Position char* ZXing_PositionToString(ZXing_Position position); bool ZXing_Barcode_isValid(const ZXing_Barcode* barcode); +ZXing_ErrorType ZXing_Barcode_errorType(const ZXing_Barcode* barcode); char* ZXing_Barcode_errorMsg(const ZXing_Barcode* barcode); ZXing_BarcodeFormat ZXing_Barcode_format(const ZXing_Barcode* barcode); ZXing_ContentType ZXing_Barcode_contentType(const ZXing_Barcode* barcode); diff --git a/wrappers/dotnet/ZXingCpp.Demo/Program.cs b/wrappers/dotnet/ZXingCpp.Demo/Program.cs index 74323a5b11..627a0d64b7 100644 --- a/wrappers/dotnet/ZXingCpp.Demo/Program.cs +++ b/wrappers/dotnet/ZXingCpp.Demo/Program.cs @@ -60,12 +60,13 @@ public static void Main(string[] args) var reader = new BarcodeReader() { TryInvert = false, + ReturnErrors = true, }; if (args.Length >= 2) reader.Formats = BarcodeReader.FormatsFromString(args[1]); foreach (var b in reader.Read(img)) - Console.WriteLine($"{b.Format} ({b.ContentType}): {b.Text} / [{string.Join(", ", b.Bytes)}]"); + Console.WriteLine($"{b.Format} ({b.ContentType}): {b.Text} / [{string.Join(", ", b.Bytes)}] {b.ErrorMsg}"); } } diff --git a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs index 0275290c94..25fbc7769a 100644 --- a/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs +++ b/wrappers/dotnet/ZXingCpp.Tests/UnitTest1.cs @@ -52,5 +52,7 @@ public void Read() Assert.Equal(0, res[0].Orientation); Assert.Equal(new PointI() { X = 4, Y = 0 }, res[0].Position.TopLeft); Assert.Equal(1, res[0].LineCount); + Assert.Equal(ErrorType.None, res[0].ErrorType); + Assert.Equal("", res[0].ErrorMsg); } } \ No newline at end of file diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index a02081174c..032a15b7a8 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -59,6 +59,7 @@ internal class Dll [DllImport(DllName)] public static extern bool ZXing_Barcode_isValid(IntPtr result); [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_errorMsg(IntPtr result); + [DllImport(DllName)] public static extern ErrorType ZXing_Barcode_errorType(IntPtr result); [DllImport(DllName)] public static extern BarcodeFormat ZXing_Barcode_format(IntPtr result); [DllImport(DllName)] public static extern ContentType ZXing_Barcode_contentType(IntPtr result); [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytes(IntPtr result, out int len); @@ -158,6 +159,8 @@ public enum TextMode public enum ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; +public enum ErrorType { None, Format, Checksum, Unsupported }; + public enum ImageFormat { None = 0, Lum = 0x01000000, @@ -305,6 +308,7 @@ public class Barcode public string ECLevel => MarshalAsString(ZXing_Barcode_ecLevel(_d)); public string SymbologyIdentifier => MarshalAsString(ZXing_Barcode_symbologyIdentifier(_d)); public string ErrorMsg => MarshalAsString(ZXing_Barcode_errorMsg(_d)); + public ErrorType ErrorType => ZXing_Barcode_errorType(_d); public Position Position => ZXing_Barcode_position(_d); public int Orientation => ZXing_Barcode_orientation(_d); public bool HasECI => ZXing_Barcode_hasECI(_d); diff --git a/wrappers/rust/examples/demo.rs b/wrappers/rust/examples/demo.rs index cee5d48509..f5380e06e4 100644 --- a/wrappers/rust/examples/demo.rs +++ b/wrappers/rust/examples/demo.rs @@ -23,7 +23,8 @@ fn main() -> anyhow::Result<()> { .try_harder(!fast) .try_invert(!fast) .try_rotate(!fast) - .try_downscale(!fast); + .try_downscale(!fast) + .return_errors(true); #[cfg(feature = "image")] let barcodes = reader.read(&image)?; @@ -40,7 +41,7 @@ fn main() -> anyhow::Result<()> { println!("Content: {}", barcode.content_type()); println!("Identifier: {}", barcode.symbology_identifier()); println!("EC Level: {}", barcode.ec_level()); - println!("Error: {}", barcode.error_message()); + println!("Error: {}", barcode.error()); println!("Rotation: {}", barcode.orientation()); println!("Position: {}", barcode.position()); println!(); diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 00a465a6c8..40f9a1be01 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -77,6 +77,11 @@ pub const ZXing_ContentType_GS1: ZXing_ContentType = 3; pub const ZXing_ContentType_ISO15434: ZXing_ContentType = 4; pub const ZXing_ContentType_UnknownECI: ZXing_ContentType = 5; pub type ZXing_ContentType = ::core::ffi::c_uint; +pub const ZXing_ErrorType_None: ZXing_ErrorType = 0; +pub const ZXing_ErrorType_Format: ZXing_ErrorType = 1; +pub const ZXing_ErrorType_Checksum: ZXing_ErrorType = 2; +pub const ZXing_ErrorType_Unsupported: ZXing_ErrorType = 3; +pub type ZXing_ErrorType = ::core::ffi::c_uint; #[repr(C)] #[derive(Debug, Copy, Clone, PartialEq)] pub struct ZXing_PointI { @@ -150,6 +155,7 @@ extern "C" { pub fn ZXing_ContentTypeToString(type_: ZXing_ContentType) -> *mut ::core::ffi::c_char; pub fn ZXing_PositionToString(position: ZXing_Position) -> *mut ::core::ffi::c_char; pub fn ZXing_Barcode_isValid(barcode: *const ZXing_Barcode) -> bool; + pub fn ZXing_Barcode_errorType(barcode: *const ZXing_Barcode) -> ZXing_ErrorType; pub fn ZXing_Barcode_errorMsg(barcode: *const ZXing_Barcode) -> *mut ::core::ffi::c_char; pub fn ZXing_Barcode_format(barcode: *const ZXing_Barcode) -> ZXing_BarcodeFormat; pub fn ZXing_Barcode_contentType(barcode: *const ZXing_Barcode) -> ZXing_ContentType; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 142fdea573..19001ed02d 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -277,6 +277,21 @@ impl Drop for Barcode { } } +#[derive(Error, Debug, PartialEq)] +pub enum BarcodeError { + #[error("")] + None(), + + #[error("{0}")] + Checksum(String), + + #[error("{0}")] + Format(String), + + #[error("{0}")] + Unsupported(String), +} + pub type PointI = ZXing_PointI; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -315,7 +330,6 @@ impl Barcode { getter!(content_type, contentType, transmute, ContentType); getter!(text, text, c2r_str, String); getter!(ec_level, ecLevel, c2r_str, String); - getter!(error_message, errorMsg, c2r_str, String); getter!(symbology_identifier, symbologyIdentifier, c2r_str, String); getter!(position, position, transmute, Position); getter!(orientation, orientation, transmute, i32); @@ -332,6 +346,19 @@ impl Barcode { let mut len: c_int = 0; unsafe { c2r_vec(ZXing_Barcode_bytesECI(self.0, &mut len), len) } } + + pub fn error(&self) -> BarcodeError { + let error_type = unsafe { ZXing_Barcode_errorType(self.0) }; + let error_msg = unsafe { c2r_str(ZXing_Barcode_errorMsg(self.0)) }; + #[allow(non_upper_case_globals)] + match error_type { + ZXing_ErrorType_None => BarcodeError::None(), + ZXing_ErrorType_Format => BarcodeError::Format(error_msg), + ZXing_ErrorType_Checksum => BarcodeError::Checksum(error_msg), + ZXing_ErrorType_Unsupported => BarcodeError::Unsupported(error_msg), + _ => panic!("Internal error: invalid ZXing_ErrorType"), + } + } } pub struct BarcodeReader(*mut ZXing_ReaderOptions); diff --git a/wrappers/rust/src/tests.rs b/wrappers/rust/src/tests.rs index 465dfe65d0..45254ccba7 100644 --- a/wrappers/rust/src/tests.rs +++ b/wrappers/rust/src/tests.rs @@ -57,5 +57,7 @@ mod tests { assert_eq!(res[0].orientation(), 0); assert_eq!(res[0].position().top_left, PointI { x: 4, y: 0 }); assert_eq!(res[0].line_count(), 1); + assert!(matches!(res[0].error(), BarcodeError::None())); + assert_eq!(res[0].error().to_string(), ""); } } From 050a2564378cebf5dc15c511026c5457d73e7ccf Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 02:06:22 +0100 Subject: [PATCH 140/431] README: add Kotlin/Native wrapper info --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 774752014f..f9609da66b 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,7 @@ Thanks a lot for your contribution! * [Android](wrappers/android/README.md) * [C](wrappers/c/README.md) * [iOS](wrappers/ios/README.md) + * [Kotlin/Native](wrappers/kn/README.md) * [.NET](wrappers/dotnet/README.md) * [Python](wrappers/python/README.md) * [Rust](wrappers/rust/README.md) From 4fb4fddc26047130fa5c3fae952207b5be161421 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 02:10:41 +0100 Subject: [PATCH 141/431] dotnet: code cosmetic (change parameter name from result to barcode) --- wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 42 ++++++++++++++-------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 032a15b7a8..428d16e8cc 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -52,27 +52,27 @@ internal class Dll [DllImport(DllName)] public static extern void ZXing_ImageView_delete(IntPtr iv); [DllImport(DllName)] public static extern IntPtr ZXing_ReadBarcodes(IntPtr iv, IntPtr opts); - [DllImport(DllName)] public static extern void ZXing_Barcode_delete(IntPtr result); - [DllImport(DllName)] public static extern void ZXing_Barcodes_delete(IntPtr results); - [DllImport(DllName)] public static extern int ZXing_Barcodes_size(IntPtr results); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcodes_move(IntPtr results, int i); - - [DllImport(DllName)] public static extern bool ZXing_Barcode_isValid(IntPtr result); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_errorMsg(IntPtr result); - [DllImport(DllName)] public static extern ErrorType ZXing_Barcode_errorType(IntPtr result); - [DllImport(DllName)] public static extern BarcodeFormat ZXing_Barcode_format(IntPtr result); - [DllImport(DllName)] public static extern ContentType ZXing_Barcode_contentType(IntPtr result); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytes(IntPtr result, out int len); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytesECI(IntPtr result, out int len); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_text(IntPtr result); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_ecLevel(IntPtr result); - [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_symbologyIdentifier(IntPtr result); - [DllImport(DllName)] public static extern Position ZXing_Barcode_position(IntPtr result); - [DllImport(DllName)] public static extern int ZXing_Barcode_orientation(IntPtr result); - [DllImport(DllName)] public static extern bool ZXing_Barcode_hasECI(IntPtr result); - [DllImport(DllName)] public static extern bool ZXing_Barcode_isInverted(IntPtr result); - [DllImport(DllName)] public static extern bool ZXing_Barcode_isMirrored(IntPtr result); - [DllImport(DllName)] public static extern int ZXing_Barcode_lineCount(IntPtr result); + [DllImport(DllName)] public static extern void ZXing_Barcode_delete(IntPtr barcode); + [DllImport(DllName)] public static extern void ZXing_Barcodes_delete(IntPtr barcodes); + [DllImport(DllName)] public static extern int ZXing_Barcodes_size(IntPtr barcodes); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcodes_move(IntPtr barcodes, int i); + + [DllImport(DllName)] public static extern bool ZXing_Barcode_isValid(IntPtr barcode); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_errorMsg(IntPtr barcode); + [DllImport(DllName)] public static extern ErrorType ZXing_Barcode_errorType(IntPtr barcode); + [DllImport(DllName)] public static extern BarcodeFormat ZXing_Barcode_format(IntPtr barcode); + [DllImport(DllName)] public static extern ContentType ZXing_Barcode_contentType(IntPtr barcode); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytes(IntPtr barcode, out int len); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_bytesECI(IntPtr barcode, out int len); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_text(IntPtr barcode); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_ecLevel(IntPtr barcode); + [DllImport(DllName)] public static extern IntPtr ZXing_Barcode_symbologyIdentifier(IntPtr barcode); + [DllImport(DllName)] public static extern Position ZXing_Barcode_position(IntPtr barcode); + [DllImport(DllName)] public static extern int ZXing_Barcode_orientation(IntPtr barcode); + [DllImport(DllName)] public static extern bool ZXing_Barcode_hasECI(IntPtr barcode); + [DllImport(DllName)] public static extern bool ZXing_Barcode_isInverted(IntPtr barcode); + [DllImport(DllName)] public static extern bool ZXing_Barcode_isMirrored(IntPtr barcode); + [DllImport(DllName)] public static extern int ZXing_Barcode_lineCount(IntPtr barcode); [DllImport(DllName)] public static extern void ZXing_free(IntPtr opts); [DllImport(DllName)] public static extern IntPtr ZXing_LastErrorMsg(); From 6ad1b9309186e1c5d07aabd9a23cc4e385c57349 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 09:00:49 +0100 Subject: [PATCH 142/431] c-API: add ImageFormat::LumX enum value (also in .NET, Rust and K/N API) --- core/src/ZXingC.h | 1 + wrappers/dotnet/ZXingCpp/ZXingCpp.cs | 1 + wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt | 1 + wrappers/rust/src/bindings.rs | 1 + wrappers/rust/src/lib.rs | 5 +++-- 5 files changed, 7 insertions(+), 2 deletions(-) diff --git a/core/src/ZXingC.h b/core/src/ZXingC.h index 934d8b4696..5b77e89fc0 100644 --- a/core/src/ZXingC.h +++ b/core/src/ZXingC.h @@ -39,6 +39,7 @@ typedef struct ZXing_Barcodes ZXing_Barcodes; typedef enum { ZXing_ImageFormat_None = 0, ZXing_ImageFormat_Lum = 0x01000000, + ZXing_ImageFormat_LumX = 0x02000000, ZXing_ImageFormat_RGB = 0x03000102, ZXing_ImageFormat_BGR = 0x03020100, ZXing_ImageFormat_RGBX = 0x04000102, diff --git a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs index 428d16e8cc..430de67d6c 100644 --- a/wrappers/dotnet/ZXingCpp/ZXingCpp.cs +++ b/wrappers/dotnet/ZXingCpp/ZXingCpp.cs @@ -164,6 +164,7 @@ public enum ErrorType { None, Format, Checksum, Unsupported }; public enum ImageFormat { None = 0, Lum = 0x01000000, + LumX = 0x02000000, RGB = 0x03000102, BGR = 0x03020100, RGBX = 0x04000102, diff --git a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt index 69308906bb..f58ecc5e76 100644 --- a/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt +++ b/wrappers/kn/src/nativeMain/kotlin/zxingcpp/ImageView.kt @@ -45,6 +45,7 @@ class ImageView( enum class ImageFormat(internal val cValue: ZXing_ImageFormat) { None(ZXing_ImageFormat_None), Lum(ZXing_ImageFormat_Lum), + LumX(ZXing_ImageFormat_LumX), RGB(ZXing_ImageFormat_RGB), BGR(ZXing_ImageFormat_BGR), RGBX(ZXing_ImageFormat_RGBX), diff --git a/wrappers/rust/src/bindings.rs b/wrappers/rust/src/bindings.rs index 40f9a1be01..d208e41048 100644 --- a/wrappers/rust/src/bindings.rs +++ b/wrappers/rust/src/bindings.rs @@ -22,6 +22,7 @@ pub struct ZXing_Barcodes { } pub const ZXing_ImageFormat_None: ZXing_ImageFormat = 0; pub const ZXing_ImageFormat_Lum: ZXing_ImageFormat = 16777216; +pub const ZXing_ImageFormat_LumX: ZXing_ImageFormat = 33554432; pub const ZXing_ImageFormat_RGB: ZXing_ImageFormat = 50331906; pub const ZXing_ImageFormat_BGR: ZXing_ImageFormat = 50462976; pub const ZXing_ImageFormat_RGBX: ZXing_ImageFormat = 67109122; diff --git a/wrappers/rust/src/lib.rs b/wrappers/rust/src/lib.rs index 19001ed02d..ea5441f5b7 100644 --- a/wrappers/rust/src/lib.rs +++ b/wrappers/rust/src/lib.rs @@ -111,7 +111,7 @@ macro_rules! make_zxing_flags { } } #[rustfmt::skip] // workaround for broken #[rustfmt::skip::macros(make_zxing_enum)] -make_zxing_enum!(ImageFormat { Lum, RGB, RGBX }); +make_zxing_enum!(ImageFormat { Lum, LumX, RGB, BGR, RGBX, XRGB, BGRX, XBGR }); #[rustfmt::skip] make_zxing_enum!(ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }); #[rustfmt::skip] @@ -258,13 +258,14 @@ impl<'a> TryFrom<&'a image::DynamicImage> for ImageView<'a> { fn try_from(img: &'a image::DynamicImage) -> Result { let format = match img { image::DynamicImage::ImageLuma8(_) => Some(ImageFormat::Lum), + image::DynamicImage::ImageLumaA8(_) => Some(ImageFormat::LumX), image::DynamicImage::ImageRgb8(_) => Some(ImageFormat::RGB), image::DynamicImage::ImageRgba8(_) => Some(ImageFormat::RGBX), _ => None, }; match format { Some(format) => Ok(ImageView::from_slice(img.as_bytes(), img.width(), img.height(), format)?), - None => Err(Error::InvalidInput("Invalid image format (must be either luma8|rgb8|rgba8)".to_string())), + None => Err(Error::InvalidInput("Invalid image format (must be either luma8|lumaA8|rgb8|rgba8)".to_string())), } } } From bdf97f4c0717cd40ba063652a87b54ee5247ba67 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 20 Feb 2024 12:35:00 +0100 Subject: [PATCH 143/431] Result: Introduce new name `Barcode` (and `Barcodes`) via `using` alias The class itself is still called Result to not break the ABI. But the term `ZXing::Result` is now deprecated. For more background information and reasoning for this change, see https://github.com/zxing-cpp/zxing-cpp/discussions/705 The Android, iOS and Python wrappers still use the term Result a.t.m. --- core/src/DecodeHints.cpp | 4 +- core/src/GTIN.cpp | 7 +- core/src/GTIN.h | 12 +-- core/src/MultiFormatReader.cpp | 15 ++- core/src/MultiFormatReader.h | 5 +- core/src/ReadBarcode.cpp | 14 +-- core/src/ReadBarcode.h | 8 +- core/src/Reader.h | 6 +- core/src/ReaderOptions.h | 4 +- core/src/Result.cpp | 38 ++++---- core/src/Result.h | 22 +++-- core/src/ZXAlgorithms.h | 4 +- core/src/ZXingC.cpp | 14 +-- core/src/ZXingC.h | 4 +- core/src/aztec/AZReader.cpp | 15 ++- core/src/aztec/AZReader.h | 4 +- core/src/datamatrix/DMReader.cpp | 14 +-- core/src/datamatrix/DMReader.h | 4 +- core/src/maxicode/MCReader.cpp | 5 +- core/src/maxicode/MCReader.h | 2 +- core/src/oned/ODCodabarReader.cpp | 5 +- core/src/oned/ODCodabarReader.h | 2 +- core/src/oned/ODCode128Reader.cpp | 6 +- core/src/oned/ODCode128Reader.h | 2 +- core/src/oned/ODCode39Reader.cpp | 4 +- core/src/oned/ODCode39Reader.h | 2 +- core/src/oned/ODCode93Reader.cpp | 4 +- core/src/oned/ODCode93Reader.h | 2 +- core/src/oned/ODDXFilmEdgeReader.cpp | 4 +- core/src/oned/ODDXFilmEdgeReader.h | 2 +- core/src/oned/ODDataBarExpandedReader.cpp | 3 +- core/src/oned/ODDataBarExpandedReader.h | 2 +- core/src/oned/ODDataBarReader.cpp | 5 +- core/src/oned/ODDataBarReader.h | 2 +- core/src/oned/ODITFReader.cpp | 4 +- core/src/oned/ODITFReader.h | 2 +- core/src/oned/ODMultiUPCEANReader.cpp | 4 +- core/src/oned/ODMultiUPCEANReader.h | 2 +- core/src/oned/ODReader.cpp | 21 ++-- core/src/oned/ODReader.h | 4 +- core/src/oned/ODRowReader.h | 4 +- core/src/pdf417/PDFReader.cpp | 20 ++-- core/src/pdf417/PDFReader.h | 4 +- core/src/qrcode/QRReader.cpp | 31 +++--- core/src/qrcode/QRReader.h | 4 +- example/ZXingOpenCV.cpp | 6 +- example/ZXingOpenCV.h | 8 +- example/ZXingQtReader.h | 22 ++--- example/ZXingReader.cpp | 96 +++++++++---------- test/blackbox/BlackboxTestRunner.cpp | 68 ++++++------- test/blackbox/TestReaderMain.cpp | 10 +- test/unit/ThresholdBinarizerTest.cpp | 6 +- test/unit/oned/ODCodaBarWriterTest.cpp | 2 +- test/unit/oned/ODCode128ReaderTest.cpp | 2 +- test/unit/oned/ODCode128WriterTest.cpp | 2 +- test/unit/oned/ODCode39ExtendedModeTest.cpp | 2 +- test/unit/oned/ODCode39ReaderTest.cpp | 2 +- test/unit/oned/ODDataBarReaderTest.cpp | 4 +- .../zxingcpp/src/main/cpp/ZXingCpp.cpp | 10 +- wrappers/python/zxing.cpp | 38 ++++---- wrappers/wasm/BarcodeReader.cpp | 18 ++-- wrappers/winrt/BarcodeReader.cpp | 6 +- 62 files changed, 319 insertions(+), 329 deletions(-) diff --git a/core/src/DecodeHints.cpp b/core/src/DecodeHints.cpp index 8dbd5a8685..9a54d8d2fb 100644 --- a/core/src/DecodeHints.cpp +++ b/core/src/DecodeHints.cpp @@ -17,12 +17,12 @@ struct DecodeHints char data[sizeof(ReaderOptions)]; }; -Result ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) +Barcode ReadBarcode(const ImageView& image, const DecodeHints& hints = {}) { return ReadBarcode(image, reinterpret_cast(hints)); } -Results ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) +Barcodes ReadBarcodes(const ImageView& image, const DecodeHints& hints = {}) { return ReadBarcodes(image, reinterpret_cast(hints)); } diff --git a/core/src/GTIN.cpp b/core/src/GTIN.cpp index 256855a030..778618ced2 100644 --- a/core/src/GTIN.cpp +++ b/core/src/GTIN.cpp @@ -199,12 +199,11 @@ std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat return it != std::end(COUNTRIES) && prefix >= it->first && prefix <= it->last ? it->id : std::string(); } -std::string EanAddOn(const Result& result) +std::string EanAddOn(const Barcode& barcode) { - if (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE | BarcodeFormat::EAN8) - .testFlag(result.format())) + if (!(BarcodeFormat::EAN13 | BarcodeFormat::UPCA | BarcodeFormat::UPCE | BarcodeFormat::EAN8).testFlag(barcode.format())) return {}; - auto txt = result.bytes().asString(); + auto txt = barcode.bytes().asString(); auto pos = txt.find(' '); return pos != std::string::npos ? std::string(txt.substr(pos + 1)) : std::string(); } diff --git a/core/src/GTIN.h b/core/src/GTIN.h index d56b604e5f..b907f42e92 100644 --- a/core/src/GTIN.h +++ b/core/src/GTIN.h @@ -7,16 +7,13 @@ #pragma once +#include "Result.h" #include "BarcodeFormat.h" #include "ZXAlgorithms.h" #include -namespace ZXing { - -class Result; - -namespace GTIN { +namespace ZXing::GTIN { template T ComputeCheckDigit(const std::basic_string& digits, bool skipTail = false) @@ -47,10 +44,9 @@ bool IsCheckDigitValid(const std::basic_string& s) */ std::string LookupCountryIdentifier(const std::string& GTIN, const BarcodeFormat format = BarcodeFormat::None); -std::string EanAddOn(const Result& result); +std::string EanAddOn(const Barcode& barcode); std::string IssueNr(const std::string& ean2AddOn); std::string Price(const std::string& ean5AddOn); -} // namespace GTIN -} // namespace ZXing +} // namespace ZXing::GTIN diff --git a/core/src/MultiFormatReader.cpp b/core/src/MultiFormatReader.cpp index 27e7f31311..d652e71627 100644 --- a/core/src/MultiFormatReader.cpp +++ b/core/src/MultiFormatReader.cpp @@ -46,21 +46,20 @@ MultiFormatReader::MultiFormatReader(const ReaderOptions& opts) : _opts(opts) MultiFormatReader::~MultiFormatReader() = default; -Result -MultiFormatReader::read(const BinaryBitmap& image) const +Barcode MultiFormatReader::read(const BinaryBitmap& image) const { - Result r; + Barcode r; for (const auto& reader : _readers) { r = reader->decode(image); if (r.isValid()) return r; } - return _opts.returnErrors() ? r : Result(); + return _opts.returnErrors() ? r : Barcode(); } -Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbols) const +Barcodes MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbols) const { - std::vector res; + Barcodes res; for (const auto& reader : _readers) { if (image.inverted() && !reader->supportsInversion) @@ -77,8 +76,8 @@ Results MultiFormatReader::readMultiple(const BinaryBitmap& image, int maxSymbol break; } - // sort results based on their position on the image - std::sort(res.begin(), res.end(), [](const Result& l, const Result& r) { + // sort barcodes based on their position on the image + std::sort(res.begin(), res.end(), [](const Barcode& l, const Barcode& r) { auto lp = l.position().topLeft(); auto rp = r.position().topLeft(); return lp.y < rp.y || (lp.y == rp.y && lp.x < rp.x); diff --git a/core/src/MultiFormatReader.h b/core/src/MultiFormatReader.h index fe7749b525..1b865d4f50 100644 --- a/core/src/MultiFormatReader.h +++ b/core/src/MultiFormatReader.h @@ -13,7 +13,6 @@ namespace ZXing { -class Result; class Reader; class BinaryBitmap; class ReaderOptions; @@ -25,10 +24,10 @@ class MultiFormatReader explicit MultiFormatReader(ReaderOptions&& opts) = delete; ~MultiFormatReader(); - Result read(const BinaryBitmap& image) const; + Barcode read(const BinaryBitmap& image) const; // WARNING: this API is experimental and may change/disappear - Results readMultiple(const BinaryBitmap& image, int maxSymbols = 0xFF) const; + Barcodes readMultiple(const BinaryBitmap& image, int maxSymbols = 0xFF) const; private: std::vector> _readers; diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 7525a35768..eeed964a2d 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -138,12 +138,12 @@ std::unique_ptr CreateBitmap(ZXing::Binarizer binarizer, const Ima return {}; // silence gcc warning } -Result ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) +Barcode ReadBarcode(const ImageView& _iv, const ReaderOptions& opts) { return FirstOrDefault(ReadBarcodes(_iv, ReaderOptions(opts).setMaxNumberOfSymbols(1))); } -Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) +Barcodes ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) { if (sizeof(PatternType) < 4 && (_iv.width() > 0xffff || _iv.height() > 0xffff)) throw std::invalid_argument("Maximum image width/height is 65535"); @@ -169,7 +169,7 @@ Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) #endif LumImagePyramid pyramid(iv, opts.downscaleThreshold() * opts.tryDownscale(), opts.downscaleFactor()); - Results results; + Barcodes res; int maxSymbols = opts.maxNumberOfSymbols() ? opts.maxNumberOfSymbols() : INT_MAX; for (auto&& iv : pyramid.layers) { auto bitmap = CreateBitmap(opts.binarizer(), iv); @@ -185,20 +185,20 @@ Results ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts) for (auto& r : rs) { if (iv.width() != _iv.width()) r.setPosition(Scale(r.position(), _iv.width() / iv.width())); - if (!Contains(results, r)) { + if (!Contains(res, r)) { r.setReaderOptions(opts); r.setIsInverted(bitmap->inverted()); - results.push_back(std::move(r)); + res.push_back(std::move(r)); --maxSymbols; } } if (maxSymbols <= 0) - return results; + return res; } } } - return results; + return res; } } // ZXing diff --git a/core/src/ReadBarcode.h b/core/src/ReadBarcode.h index 343ac8487b..a2b6608ebc 100644 --- a/core/src/ReadBarcode.h +++ b/core/src/ReadBarcode.h @@ -16,18 +16,18 @@ namespace ZXing { * * @param image view of the image data including layout and format * @param options optional ReaderOptions to parameterize / speed up detection - * @return #Result structure + * @return #Barcode structure */ -Result ReadBarcode(const ImageView& image, const ReaderOptions& options = {}); +Barcode ReadBarcode(const ImageView& image, const ReaderOptions& options = {}); /** * Read barcodes from an ImageView * * @param image view of the image data including layout and format * @param options optional ReaderOptions to parameterize / speed up detection - * @return #Results list of results found, may be empty + * @return #Barcodes list of barcodes found, may be empty */ -Results ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); +Barcodes ReadBarcodes(const ImageView& image, const ReaderOptions& options = {}); } // ZXing diff --git a/core/src/Reader.h b/core/src/Reader.h index 7ece21b56d..ab91b868dc 100644 --- a/core/src/Reader.h +++ b/core/src/Reader.h @@ -26,12 +26,12 @@ class Reader explicit Reader(ReaderOptions&& opts) = delete; virtual ~Reader() = default; - virtual Result decode(const BinaryBitmap& image) const = 0; + virtual Barcode decode(const BinaryBitmap& image) const = 0; // WARNING: this API is experimental and may change/disappear - virtual Results decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { + virtual Barcodes decode(const BinaryBitmap& image, [[maybe_unused]] int maxSymbols) const { auto res = decode(image); - return res.isValid() || (_opts.returnErrors() && res.format() != BarcodeFormat::None) ? Results{std::move(res)} : Results{}; + return res.isValid() || (_opts.returnErrors() && res.format() != BarcodeFormat::None) ? Barcodes{std::move(res)} : Barcodes{}; } }; diff --git a/core/src/ReaderOptions.h b/core/src/ReaderOptions.h index e74db5a33d..6df6e800b2 100644 --- a/core/src/ReaderOptions.h +++ b/core/src/ReaderOptions.h @@ -153,13 +153,13 @@ class ReaderOptions /// Deprecated / does nothing. Codabar start/stop characters are always returned. ZX_PROPERTY(bool, returnCodabarStartEnd, setReturnCodabarStartEnd, [[deprecated]]) - /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Result::error()) + /// If true, return the barcodes with errors as well (e.g. checksum errors, see @Barcode::error()) ZX_PROPERTY(bool, returnErrors, setReturnErrors) /// Specify whether to ignore, read or require EAN-2/5 add-on symbols while scanning EAN/UPC codes ZX_PROPERTY(EanAddOnSymbol, eanAddOnSymbol, setEanAddOnSymbol) - /// Specifies the TextMode that controls the return of the Result::text() function + /// Specifies the TextMode that controls the return of the Barcode::text() function ZX_PROPERTY(TextMode, textMode, setTextMode) /// Specifies fallback character set to use instead of auto-detecting it (when applicable) diff --git a/core/src/Result.cpp b/core/src/Result.cpp index c637811a25..15c33ee997 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -157,45 +157,45 @@ bool Result::operator==(const Result& o) const return std::min(dTop, dBot) < length / 2 && dLength < length / 5; } -Result MergeStructuredAppendSequence(const Results& results) +Barcode MergeStructuredAppendSequence(const Barcodes& barcodes) { - if (results.empty()) + if (barcodes.empty()) return {}; - std::list allResults(results.begin(), results.end()); - allResults.sort([](const Result& r1, const Result& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); + std::list allBarcodes(barcodes.begin(), barcodes.end()); + allBarcodes.sort([](const Barcode& r1, const Barcode& r2) { return r1.sequenceIndex() < r2.sequenceIndex(); }); - Result res = allResults.front(); - for (auto i = std::next(allResults.begin()); i != allResults.end(); ++i) + Barcode res = allBarcodes.front(); + for (auto i = std::next(allBarcodes.begin()); i != allBarcodes.end(); ++i) res._content.append(i->_content); res._position = {}; res._sai.index = -1; - if (allResults.back().sequenceSize() != Size(allResults) || - !std::all_of(allResults.begin(), allResults.end(), - [&](Result& it) { return it.sequenceId() == allResults.front().sequenceId(); })) + if (allBarcodes.back().sequenceSize() != Size(allBarcodes) || + !std::all_of(allBarcodes.begin(), allBarcodes.end(), + [&](Barcode& it) { return it.sequenceId() == allBarcodes.front().sequenceId(); })) res._error = FormatError("sequenceIDs not matching during structured append sequence merging"); return res; } -Results MergeStructuredAppendSequences(const Results& results) +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes) { - std::map sas; - for (auto& res : results) { - if (res.isPartOfSequence()) - sas[res.sequenceId()].push_back(res); + std::map sas; + for (auto& barcode : barcodes) { + if (barcode.isPartOfSequence()) + sas[barcode.sequenceId()].push_back(barcode); } - Results saiResults; + Barcodes res; for (auto& [id, seq] : sas) { - auto res = MergeStructuredAppendSequence(seq); - if (res.isValid()) - saiResults.push_back(std::move(res)); + auto barcode = MergeStructuredAppendSequence(seq); + if (barcode.isValid()) + res.push_back(std::move(barcode)); } - return saiResults; + return res; } } // ZXing diff --git a/core/src/Result.h b/core/src/Result.h index 7d46238551..1f87a08634 100644 --- a/core/src/Result.h +++ b/core/src/Result.h @@ -22,20 +22,24 @@ namespace ZXing { class DecoderResult; class ImageView; +class Result; // TODO: 3.0 replace deprected symbol name using Position = QuadrilateralI; +using Barcode = Result; +using Barcodes = std::vector; +using Results = std::vector; /** - * @brief The Result class encapsulates the result of decoding a barcode within an image. + * @brief The Barcode class encapsulates the result of decoding a barcode within an image. */ class Result { void setIsInverted(bool v) { _isInverted = v; } Result& setReaderOptions(const ReaderOptions& opts); - friend Result MergeStructuredAppendSequence(const std::vector& results); - friend std::vector ReadBarcodes(const ImageView&, const ReaderOptions&); - friend void IncrementLineCount(Result&); + friend Barcode MergeStructuredAppendSequence(const Barcodes&); + friend Barcodes ReadBarcodes(const ImageView&, const ReaderOptions&); + friend void IncrementLineCount(Barcode&); public: Result() = default; @@ -168,16 +172,14 @@ class Result bool _readerInit = false; }; -using Results = std::vector; - /** - * @brief Merge a list of Results from one Structured Append sequence to a single result + * @brief Merge a list of Barcodes from one Structured Append sequence to a single barcode */ -Result MergeStructuredAppendSequence(const Results& results); +Barcode MergeStructuredAppendSequence(const Barcodes& results); /** - * @brief Automatically merge all Structured Append sequences found in the given results + * @brief Automatically merge all Structured Append sequences found in the given list of barcodes */ -Results MergeStructuredAppendSequences(const Results& results); +Barcodes MergeStructuredAppendSequences(const Barcodes& barcodes); } // ZXing diff --git a/core/src/ZXAlgorithms.h b/core/src/ZXAlgorithms.h index e6f1b99368..66898363eb 100644 --- a/core/src/ZXAlgorithms.h +++ b/core/src/ZXAlgorithms.h @@ -47,9 +47,9 @@ inline bool Contains(const char* str, char c) { } template