From 23c19c5f98602a4d69d1667fff99678308b28b5b Mon Sep 17 00:00:00 2001 From: liule Date: Fri, 6 Jan 2023 22:06:24 +0800 Subject: [PATCH 001/173] fix crash when the source image is less than 3 pixels width/height --- core/src/ReadBarcode.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 905dd191cd..5ac61e250e 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -76,7 +76,8 @@ class LumImagePyramid 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) + while (threshold > 0 && std::max(layers.back().width(), layers.back().height()) > threshold && + std::min(layers.back().width(), layers.back().height()) >= N) addLayer(); #if 0 // Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a From f622ef7feb9c54fe68bae40a6d6f01e4edb34263 Mon Sep 17 00:00:00 2001 From: Sergio <3593635+Sergio-@users.noreply.github.com> Date: Wed, 11 Jan 2023 01:19:39 -0300 Subject: [PATCH 002/173] This changes enable the feature of saving images to the phone gallery. --- .../android/app/src/main/AndroidManifest.xml | 1 + .../com/example/zxingcppdemo/MainActivity.kt | 36 ++++++++++++++++--- .../main/res/layout-land/activity_camera.xml | 2 +- .../src/main/res/layout/activity_camera.xml | 2 +- 4 files changed, 35 insertions(+), 6 deletions(-) diff --git a/wrappers/android/app/src/main/AndroidManifest.xml b/wrappers/android/app/src/main/AndroidManifest.xml index d411996a69..4127b366bf 100644 --- a/wrappers/android/app/src/main/AndroidManifest.xml +++ b/wrappers/android/app/src/main/AndroidManifest.xml @@ -6,6 +6,7 @@ + diff --git a/wrappers/android/app/src/main/res/layout/activity_camera.xml b/wrappers/android/app/src/main/res/layout/activity_camera.xml index 66ccae4775..6a351fb312 100644 --- a/wrappers/android/app/src/main/res/layout/activity_camera.xml +++ b/wrappers/android/app/src/main/res/layout/activity_camera.xml @@ -58,7 +58,7 @@ android:background="@drawable/ic_shutter" android:contentDescription="@string/capture_button_alt" android:scaleType="fitCenter" - android:visibility="invisible" + android:visibility="visible" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" /> From 302e8210b865576b35164f48bd57e003f6674dd1 Mon Sep 17 00:00:00 2001 From: FalsinSoft Date: Wed, 11 Jan 2023 20:58:04 +0100 Subject: [PATCH 003/173] Fixed 'invalid_argument' is not a member of 'std' error on Visual Studio --- core/src/Matrix.h | 1 + 1 file changed, 1 insertion(+) diff --git a/core/src/Matrix.h b/core/src/Matrix.h index 8b04bb1af1..0c7c4e88ba 100644 --- a/core/src/Matrix.h +++ b/core/src/Matrix.h @@ -9,6 +9,7 @@ #include "Point.h" #include "ZXAlgorithms.h" +#include #include #include #include From 47f1138dde48e97d08a184f367bb6da27c0f9bc5 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 9 Jan 2023 01:48:06 +0100 Subject: [PATCH 004/173] QRCode: `Version` member function naming simplification --- core/src/qrcode/QRBitMatrixParser.cpp | 17 +++++-------- core/src/qrcode/QREncoder.cpp | 8 +++---- core/src/qrcode/QRVersion.cpp | 13 +++++----- core/src/qrcode/QRVersion.h | 7 +++--- .../qrcode/QRDecodedBitStreamParserTest.cpp | 12 +++++----- test/unit/qrcode/QREncoderTest.cpp | 8 +++---- test/unit/qrcode/QRModeTest.cpp | 24 +++++++++---------- test/unit/qrcode/QRVersionTest.cpp | 18 +++++++------- 8 files changed, 52 insertions(+), 55 deletions(-) diff --git a/core/src/qrcode/QRBitMatrixParser.cpp b/core/src/qrcode/QRBitMatrixParser.cpp index e693857e1f..b970266c30 100644 --- a/core/src/qrcode/QRBitMatrixParser.cpp +++ b/core/src/qrcode/QRBitMatrixParser.cpp @@ -34,15 +34,11 @@ static bool hasValidDimension(const BitMatrix& bitMatrix, bool isMicro) const Version* ReadVersion(const BitMatrix& bitMatrix) { int dimension = bitMatrix.height(); - bool isMicro = dimension < 21; - if (!hasValidDimension(bitMatrix, isMicro)) - return nullptr; - - int provisionalVersion = (dimension - Version::DimensionOffset(isMicro)) / Version::DimensionStep(isMicro); + const Version* version = Version::FromDimension(dimension); - if (provisionalVersion <= 6) - return Version::VersionForNumber(provisionalVersion, isMicro); + if (!version || version->versionNumber() < 7) + return version; for (bool mirror : {false, true}) { // Read top-right/bottom-left version info: 3 wide by 6 tall (depending on mirrored) @@ -51,10 +47,9 @@ const Version* ReadVersion(const BitMatrix& bitMatrix) for (int x = dimension - 9; x >= dimension - 11; --x) AppendBit(versionBits, getBit(bitMatrix, x, y, mirror)); - auto theParsedVersion = Version::DecodeVersionInformation(versionBits); - // TODO: why care for the contents of the version bits if we know the dimension already? - if (theParsedVersion != nullptr && theParsedVersion->dimensionForVersion() == dimension) - return theParsedVersion; + version = Version::DecodeVersionInformation(versionBits); + if (version && version->dimension() == dimension) + return version; } return nullptr; diff --git a/core/src/qrcode/QREncoder.cpp b/core/src/qrcode/QREncoder.cpp index 206e4cd70a..58864791db 100644 --- a/core/src/qrcode/QREncoder.cpp +++ b/core/src/qrcode/QREncoder.cpp @@ -267,7 +267,7 @@ static bool WillFit(int numInputBits, const Version& version, ErrorCorrectionLev static const Version& ChooseVersion(int numInputBits, ErrorCorrectionLevel ecLevel) { for (int versionNum = 1; versionNum <= 40; versionNum++) { - const Version* version = Version::VersionForNumber(versionNum); + const Version* version = Version::FromNumber(versionNum); if (WillFit(numInputBits, *version, ecLevel)) { return *version; } @@ -472,7 +472,7 @@ static const Version& RecommendVersion(ErrorCorrectionLevel ecLevel, CodecMode m // Hard part: need to know version to know how many bits length takes. But need to know how many // bits it takes to know version. First we take a guess at version by assuming version will be // the minimum, 1: - int provisionalBitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *Version::VersionForNumber(1)); + int provisionalBitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *Version::FromNumber(1)); const Version& provisionalVersion = ChooseVersion(provisionalBitsNeeded, ecLevel); // Use that guess to calculate the right version. I am still not sure this works in 100% of cases. @@ -517,7 +517,7 @@ EncodeResult Encode(const std::wstring& content, ErrorCorrectionLevel ecLevel, C const Version* version; if (versionNumber > 0) { - version = Version::VersionForNumber(versionNumber); + version = Version::FromNumber(versionNumber); if (version != nullptr) { int bitsNeeded = CalculateBitsNeeded(mode, headerBits, dataBits, *version); if (!WillFit(bitsNeeded, *version, ecLevel)) { @@ -556,7 +556,7 @@ EncodeResult Encode(const std::wstring& content, ErrorCorrectionLevel ecLevel, C output.version = version; // Choose the mask pattern and set to "qrCode". - int dimension = version->dimensionForVersion(); + int dimension = version->dimension(); TritMatrix matrix(dimension, dimension); output.maskPattern = maskPattern != -1 ? maskPattern : ChooseMaskPattern(finalBits, ecLevel, *version, matrix); diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 537bc91fc7..90c698febe 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -304,7 +304,7 @@ Version::Version(int versionNumber, const std::array& ecBlocks) _totalCodewords = ecBlocks[0].totalDataCodewords(); } -const Version* Version::VersionForNumber(int versionNumber, bool isMicro) +const Version* Version::FromNumber(int versionNumber, bool isMicro) { if (versionNumber < 1 || versionNumber > (isMicro ? 4 : 40)) { //throw std::invalid_argument("Version should be in range [1-40]."); @@ -313,13 +313,14 @@ const Version* Version::VersionForNumber(int versionNumber, bool isMicro) return &(isMicro ? AllMicroVersions() : AllVersions())[versionNumber - 1]; } -const Version* Version::ProvisionalVersionForDimension(int dimension, bool isMicro) +const Version* Version::FromDimension(int dimension) { + bool isMicro = dimension < 21; if (dimension % DimensionStep(isMicro) != 1) { //throw std::invalid_argument("Unexpected dimension"); return nullptr; } - return VersionForNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro); + return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro); } const Version* Version::DecodeVersionInformation(int versionBits) @@ -330,7 +331,7 @@ const Version* Version::DecodeVersionInformation(int versionBits) for (int targetVersion : VERSION_DECODE_INFO) { // Do the version info bits match exactly? done. if (targetVersion == versionBits) { - return VersionForNumber(i + 7); + return FromNumber(i + 7); } // Otherwise see if this is the closest to a real version info bit string // we have seen so far @@ -344,7 +345,7 @@ const Version* Version::DecodeVersionInformation(int versionBits) // We can tolerate up to 3 bits of error since no two version info codewords will // differ in less than 8 bits. if (bestDifference <= 3) { - return VersionForNumber(bestVersion); + return FromNumber(bestVersion); } // If we didn't find a close enough match, fail return nullptr; @@ -355,7 +356,7 @@ const Version* Version::DecodeVersionInformation(int versionBits) */ BitMatrix Version::buildFunctionPattern() const { - int dimension = dimensionForVersion(); + int dimension = this->dimension(); BitMatrix bitMatrix(dimension, dimension); // Top left finder pattern + separator + format diff --git a/core/src/qrcode/QRVersion.h b/core/src/qrcode/QRVersion.h index 024f1a7387..17a1f111e0 100644 --- a/core/src/qrcode/QRVersion.h +++ b/core/src/qrcode/QRVersion.h @@ -33,7 +33,7 @@ class Version int totalCodewords() const { return _totalCodewords; } - int dimensionForVersion() const { return DimensionOfVersion(_versionNumber, _isMicro); } + int dimension() const { return DimensionOfVersion(_versionNumber, _isMicro); } const ECBlocks& ecBlocksForLevel(ErrorCorrectionLevel ecLevel) const { return _ecBlocks[(int)ecLevel]; } @@ -54,11 +54,12 @@ class Version * @param dimension dimension in modules * @return Version for a QR Code of that dimension */ - static const Version* ProvisionalVersionForDimension(int dimension, bool isMicro = false); + static const Version* FromDimension(int dimension); - static const Version* VersionForNumber(int versionNumber, bool isMicro = false); + static const Version* FromNumber(int versionNumber, bool isMicro = false); static const Version* DecodeVersionInformation(int versionBits); + private: int _versionNumber; std::vector _alignmentPatternCenters; diff --git a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp index 0d8c1b0def..66d4f89703 100644 --- a/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp +++ b/test/unit/qrcode/QRDecodedBitStreamParserTest.cpp @@ -31,7 +31,7 @@ TEST(QRDecodedBitStreamParserTest, SimpleByteMode) ba.appendBits(0xF1, 8); ba.appendBits(0xF2, 8); ba.appendBits(0xF3, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\xF1\xF2\xF3", result); } @@ -44,7 +44,7 @@ TEST(QRDecodedBitStreamParserTest, SimpleSJIS) ba.appendBits(0xA2, 8); ba.appendBits(0xA3, 8); ba.appendBits(0xD0, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\uff61\uff62\uff63\uff90", result); } @@ -58,7 +58,7 @@ TEST(QRDecodedBitStreamParserTest, ECI) ba.appendBits(0xA1, 8); ba.appendBits(0xA2, 8); ba.appendBits(0xA3, 8); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\xED\xF3\xFA", result); } @@ -69,7 +69,7 @@ TEST(QRDecodedBitStreamParserTest, Hanzi) ba.appendBits(0x01, 4); // Subset 1 = GB2312 encoding ba.appendBits(0x01, 8); // 1 characters ba.appendBits(0x03C1, 13); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\u963f", result); } @@ -82,13 +82,13 @@ TEST(QRDecodedBitStreamParserTest, HanziLevel1) // A5A2 (U+30A2) => A5A2 - A1A1 = 401, 4*60 + 01 = 0181 ba.appendBits(0x0181, 13); - auto result = DecodeBitStream(ba.toBytes(), *Version::VersionForNumber(1), ErrorCorrectionLevel::Medium).text(); + auto result = DecodeBitStream(ba.toBytes(), *Version::FromNumber(1), ErrorCorrectionLevel::Medium).text(); EXPECT_EQ(L"\u30a2", result); } TEST(QRDecodedBitStreamParserTest, SymbologyIdentifier) { - const Version& version = *Version::VersionForNumber(1); + const Version& version = *Version::FromNumber(1); const ErrorCorrectionLevel ecLevel = ErrorCorrectionLevel::Medium; DecoderResult result; diff --git a/test/unit/qrcode/QREncoderTest.cpp b/test/unit/qrcode/QREncoderTest.cpp index 6a31177836..8e28abc515 100644 --- a/test/unit/qrcode/QREncoderTest.cpp +++ b/test/unit/qrcode/QREncoderTest.cpp @@ -363,22 +363,22 @@ TEST(QREncoderTest, AppendLengthInfo) { BitArray bits; AppendLengthInfo(1, // 1 letter (1/1). - *Version::VersionForNumber(1), CodecMode::NUMERIC, bits); + *Version::FromNumber(1), CodecMode::NUMERIC, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ .X")); // 10 bits. bits = BitArray(); AppendLengthInfo(2, // 2 letters (2/1). - *Version::VersionForNumber(10), CodecMode::ALPHANUMERIC, bits); + *Version::FromNumber(10), CodecMode::ALPHANUMERIC, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ .X.")); // 11 bits. bits = BitArray(); AppendLengthInfo(255, // 255 letter (255/1). - *Version::VersionForNumber(27), CodecMode::BYTE, bits); + *Version::FromNumber(27), CodecMode::BYTE, bits); EXPECT_EQ(ToString(bits), RemoveSpace("........ XXXXXXXX")); // 16 bits. bits = BitArray(); AppendLengthInfo(512, // 512 letters (1024/2). - *Version::VersionForNumber(40), CodecMode::KANJI, bits); + *Version::FromNumber(40), CodecMode::KANJI, bits); EXPECT_EQ(ToString(bits), RemoveSpace("..X..... ....")); // 12 bits. } diff --git a/test/unit/qrcode/QRModeTest.cpp b/test/unit/qrcode/QRModeTest.cpp index 94ab70243f..9a3abb754f 100644 --- a/test/unit/qrcode/QRModeTest.cpp +++ b/test/unit/qrcode/QRModeTest.cpp @@ -25,12 +25,12 @@ TEST(QRModeTest, ForBits) TEST(QRModeTest, CharacterCount) { // Spot check a few values - ASSERT_EQ(10, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(5))); - ASSERT_EQ(12, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(26))); - ASSERT_EQ(14, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(40))); - ASSERT_EQ(9, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::VersionForNumber(6))); - ASSERT_EQ(8, CharacterCountBits(CodecMode::BYTE, *Version::VersionForNumber(7))); - ASSERT_EQ(8, CharacterCountBits(CodecMode::KANJI, *Version::VersionForNumber(8))); + ASSERT_EQ(10, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(5))); + ASSERT_EQ(12, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(26))); + ASSERT_EQ(14, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(40))); + ASSERT_EQ(9, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::FromNumber(6))); + ASSERT_EQ(8, CharacterCountBits(CodecMode::BYTE, *Version::FromNumber(7))); + ASSERT_EQ(8, CharacterCountBits(CodecMode::KANJI, *Version::FromNumber(8))); } TEST(QRModeTest, MicroForBits) @@ -57,10 +57,10 @@ TEST(QRModeTest, MicroForBits) TEST(QRModeTest, MicroCharacterCount) { // Spot check a few values - ASSERT_EQ(3, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(1, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(2, true))); - ASSERT_EQ(6, CharacterCountBits(CodecMode::NUMERIC, *Version::VersionForNumber(4, true))); - ASSERT_EQ(3, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::VersionForNumber(2, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::BYTE, *Version::VersionForNumber(3, true))); - ASSERT_EQ(4, CharacterCountBits(CodecMode::KANJI, *Version::VersionForNumber(4, true))); + ASSERT_EQ(3, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(1, true))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(2, true))); + ASSERT_EQ(6, CharacterCountBits(CodecMode::NUMERIC, *Version::FromNumber(4, true))); + ASSERT_EQ(3, CharacterCountBits(CodecMode::ALPHANUMERIC, *Version::FromNumber(2, true))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::BYTE, *Version::FromNumber(3, true))); + ASSERT_EQ(4, CharacterCountBits(CodecMode::KANJI, *Version::FromNumber(4, true))); } diff --git a/test/unit/qrcode/QRVersionTest.cpp b/test/unit/qrcode/QRVersionTest.cpp index bf4fbe377b..aef3411655 100644 --- a/test/unit/qrcode/QRVersionTest.cpp +++ b/test/unit/qrcode/QRVersionTest.cpp @@ -21,7 +21,7 @@ namespace { if (number > 1 && !version->isMicroQRCode()) { EXPECT_FALSE(version->alignmentPatternCenters().empty()); } - EXPECT_EQ(dimension, version->dimensionForVersion()); + EXPECT_EQ(dimension, version->dimension()); } void DoTestVersion(int expectedVersion, int mask) { @@ -34,11 +34,11 @@ namespace { TEST(QRVersionTest, VersionForNumber) { - auto version = Version::VersionForNumber(0); + auto version = Version::FromNumber(0); EXPECT_EQ(version, nullptr) << "There is version with number 0"; for (int i = 1; i <= 40; i++) { - CheckVersion(Version::VersionForNumber(i), i, 4*i + 17); + CheckVersion(Version::FromNumber(i), i, 4*i + 17); } } @@ -46,7 +46,7 @@ TEST(QRVersionTest, VersionForNumber) TEST(QRVersionTest, GetProvisionalVersionForDimension) { for (int i = 1; i <= 40; i++) { - auto prov = Version::ProvisionalVersionForDimension(4 * i + 17); + auto prov = Version::FromDimension(4 * i + 17); ASSERT_NE(prov, nullptr); EXPECT_EQ(i, prov->versionNumber()); } @@ -65,18 +65,18 @@ TEST(QRVersionTest, DecodeVersionInformation) TEST(QRVersionTest, MicroVersionForNumber) { - auto version = Version::VersionForNumber(0, true); + auto version = Version::FromNumber(0, true); EXPECT_EQ(version, nullptr) << "There is version with number 0"; for (int i = 1; i <= 4; i++) { - CheckVersion(Version::VersionForNumber(i, true), i, 2 * i + 9); + CheckVersion(Version::FromNumber(i, true), i, 2 * i + 9); } } TEST(QRVersionTest, GetProvisionalMicroVersionForDimension) { for (int i = 1; i <= 4; i++) { - auto prov = Version::ProvisionalVersionForDimension(2 * i + 9, true); + auto prov = Version::FromDimension(2 * i + 9); ASSERT_NE(prov, nullptr); EXPECT_EQ(i, prov->versionNumber()); } @@ -90,12 +90,12 @@ TEST(QRVersionTest, FunctionPattern) EXPECT_TRUE(bitMatrix.get(col, row)); }; for (int i = 1; i <= 4; i++) { - const auto version = Version::VersionForNumber(i, true); + const auto version = Version::FromNumber(i, true); const auto functionPattern = version->buildFunctionPattern(); testFinderPatternRegion(functionPattern); // Check timing pattern areas. - const auto dimension = version->dimensionForVersion(); + const auto dimension = version->dimension(); for (int row = dimension; row < functionPattern.height(); row++) EXPECT_TRUE(functionPattern.get(0, row)); for (int col = dimension; col < functionPattern.width(); col++) From 03feb02b5138d23bf559093d4c228038f249092f Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 16 Jan 2023 09:28:05 +0100 Subject: [PATCH 005/173] c++: more #include statement cleanups/fixes --- core/src/BitMatrix.cpp | 1 - core/src/GenericGFPoly.cpp | 1 - test/unit/oned/ODCodaBarWriterTest.cpp | 1 - test/unit/qrcode/QRModeTest.cpp | 1 + test/unit/qrcode/QRWriterTest.cpp | 1 + 5 files changed, 2 insertions(+), 3 deletions(-) diff --git a/core/src/BitMatrix.cpp b/core/src/BitMatrix.cpp index 018bec31cc..d62e182b45 100644 --- a/core/src/BitMatrix.cpp +++ b/core/src/BitMatrix.cpp @@ -6,7 +6,6 @@ #include "BitMatrix.h" -#include "BitArray.h" #include "Pattern.h" #include diff --git a/core/src/GenericGFPoly.cpp b/core/src/GenericGFPoly.cpp index a2634f82d1..1ec9f84fd3 100644 --- a/core/src/GenericGFPoly.cpp +++ b/core/src/GenericGFPoly.cpp @@ -8,7 +8,6 @@ #include "GenericGFPoly.h" #include "GenericGF.h" -#include "ZXConfig.h" #include "ZXAlgorithms.h" #include diff --git a/test/unit/oned/ODCodaBarWriterTest.cpp b/test/unit/oned/ODCodaBarWriterTest.cpp index 9023ffc787..b5d4bcf636 100644 --- a/test/unit/oned/ODCodaBarWriterTest.cpp +++ b/test/unit/oned/ODCodaBarWriterTest.cpp @@ -5,7 +5,6 @@ // SPDX-License-Identifier: Apache-2.0 #include "oned/ODCodabarWriter.h" -#include "BitArray.h" #include "BitMatrixIO.h" #include "DecodeHints.h" #include "Result.h" diff --git a/test/unit/qrcode/QRModeTest.cpp b/test/unit/qrcode/QRModeTest.cpp index 9a3abb754f..fd960b6d46 100644 --- a/test/unit/qrcode/QRModeTest.cpp +++ b/test/unit/qrcode/QRModeTest.cpp @@ -7,6 +7,7 @@ #include "qrcode/QRCodecMode.h" #include "qrcode/QRVersion.h" +#include #include "gtest/gtest.h" using namespace ZXing; diff --git a/test/unit/qrcode/QRWriterTest.cpp b/test/unit/qrcode/QRWriterTest.cpp index f96a24e2f6..bb33cf4d11 100644 --- a/test/unit/qrcode/QRWriterTest.cpp +++ b/test/unit/qrcode/QRWriterTest.cpp @@ -9,6 +9,7 @@ #include "qrcode/QRErrorCorrectionLevel.h" #include "gtest/gtest.h" +#include using namespace ZXing; using namespace ZXing::QRCode; From ca96dab34a293055378fc795fbe27abafa45fb3f Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 16 Jan 2023 09:34:17 +0100 Subject: [PATCH 006/173] python: improve README.md (mention git for source builds) --- wrappers/python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/python/README.md b/wrappers/python/README.md index 015f90eefe..a4079cc84e 100644 --- a/wrappers/python/README.md +++ b/wrappers/python/README.md @@ -14,7 +14,7 @@ or python setup.py install ``` -Note: To install via `setup.py`, you need a suitable [build environment](https://github.com/zxing-cpp/zxing-cpp#build-instructions) including a c++ compiler. +[Note: To install via `setup.py` (or via `pip install` in case there is no pre-build wheel available for your python version), you need a suitable [build environment](https://github.com/zxing-cpp/zxing-cpp#build-instructions) including a c++ compiler and git.] ## Usage From 7e0175bf163ff1088f89d95bed3eb491abffa793 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 16 Jan 2023 09:36:01 +0100 Subject: [PATCH 007/173] PerspectiveTransform: reduce struct size by dropping _isValid member --- core/src/PerspectiveTransform.h | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/core/src/PerspectiveTransform.h b/core/src/PerspectiveTransform.h index c2f5043e18..cc85fcb20c 100644 --- a/core/src/PerspectiveTransform.h +++ b/core/src/PerspectiveTransform.h @@ -20,12 +20,11 @@ namespace ZXing { class PerspectiveTransform { using value_t = PointF::value_t; - value_t a11, a12, a13, a21, a22, a23, a31, a32, a33; - bool _isValid = false; + value_t a11, a12, a13, a21, a22, a23, a31, a32, a33 = NAN; PerspectiveTransform(value_t a11, value_t a21, value_t a31, value_t a12, value_t a22, value_t a32, value_t a13, value_t a23, value_t a33) - : a11(a11), a12(a12), a13(a13), a21(a21), a22(a22), a23(a23), a31(a31), a32(a32), a33(a33), _isValid(true) + : a11(a11), a12(a12), a13(a13), a21(a21), a22(a22), a23(a23), a31(a31), a32(a32), a33(a33) {} PerspectiveTransform inverse() const; @@ -40,7 +39,7 @@ class PerspectiveTransform /// Project from the destination space (grid of modules) into the image space (bit matrix) PointF operator()(PointF p) const; - bool isValid() const { return _isValid; } + bool isValid() const { return !std::isnan(a33); } }; } // ZXing From aa2ea252223f5f7b79bb8fffaa4946f05e7530d5 Mon Sep 17 00:00:00 2001 From: Sergio <3593635+Sergio-@users.noreply.github.com> Date: Tue, 17 Jan 2023 02:13:14 -0300 Subject: [PATCH 008/173] Removed function to save High Resolution Camera Image. Implemented Method to Save Image Buffer as passed to Decoding Algorithm. Added Button to Turn Camera Torch On and Off. --- .../com/example/zxingcppdemo/MainActivity.kt | 124 +++++++++++------- .../main/res/layout-land/activity_camera.xml | 7 +- .../src/main/res/layout/activity_camera.xml | 7 +- 3 files changed, 85 insertions(+), 53 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index 2c55736128..ad98c25adf 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -17,7 +17,6 @@ package com.example.zxingcppdemo import android.Manifest -import android.content.ContentValues import android.content.Context import android.content.pm.PackageManager import android.graphics.* @@ -25,7 +24,7 @@ import android.hardware.camera2.CaptureRequest import android.media.AudioManager import android.media.ToneGenerator import android.os.Bundle -import android.provider.MediaStore +import android.os.Environment import android.view.View import androidx.appcompat.app.AppCompatActivity import androidx.camera.camera2.interop.Camera2CameraControl @@ -34,7 +33,6 @@ import androidx.camera.core.* import androidx.camera.lifecycle.ProcessCameraProvider import androidx.core.app.ActivityCompat import androidx.core.content.ContextCompat -import androidx.core.graphics.toPoint import androidx.core.graphics.toPointF import androidx.lifecycle.LifecycleOwner import com.example.zxingcppdemo.databinding.ActivityCameraBinding @@ -42,19 +40,22 @@ import com.google.zxing.* import com.google.zxing.common.HybridBinarizer import com.zxingcpp.BarcodeReader import com.zxingcpp.BarcodeReader.Format +import java.io.ByteArrayOutputStream +import java.io.File import java.util.concurrent.Executors import kotlin.random.Random class MainActivity : AppCompatActivity() { private lateinit var binding: ActivityCameraBinding - private var imageCapture: ImageCapture? = null + private val executor = Executors.newSingleThreadExecutor() - private val permissions = listOf(Manifest.permission.CAMERA) + private val permissions = listOf(Manifest.permission.CAMERA, Manifest.permission.WRITE_EXTERNAL_STORAGE) private val permissionsRequestCode = Random.nextInt(0, 10000) private val beeper = ToneGenerator(AudioManager.STREAM_NOTIFICATION, 50) private var lastText = String() + private var doSaveImage: Boolean = false override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -64,35 +65,51 @@ class MainActivity : AppCompatActivity() { binding.capture.setOnClickListener { // Disable all camera controls it.isEnabled = false - takePhoto() + doSaveImage = true // Re-enable camera controls it.isEnabled = true } } - private fun takePhoto() { - val imageCapture = imageCapture ?: return - val contentValues = ContentValues().apply { - put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis()) - put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg") - } + private fun ImageProxy.toByteArray(): ByteArray { + //This converts the ImageProxy (from the imageAnalysis Use Case) + //to a ByteArray (compressed as JPEG) for then to be saved for debugging purposes + //This is the closest representation of the image that is passed to the + //decoding algorithm. - val outputOptions = ImageCapture.OutputFileOptions.Builder( - contentResolver, MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues - ).build() + val yBuffer = planes[0].buffer // Y + val vuBuffer = planes[2].buffer // VU - imageCapture.takePicture( - outputOptions, - ContextCompat.getMainExecutor(this), - object : ImageCapture.OnImageSavedCallback { - override fun onError(exc: ImageCaptureException) { - beeper.startTone(ToneGenerator.TONE_CDMA_SOFT_ERROR_LITE) - } - override fun onImageSaved(output: ImageCapture.OutputFileResults) { - beeper.startTone(ToneGenerator.TONE_CDMA_CONFIRM) - } - }) + val ySize = yBuffer.remaining() + val vuSize = vuBuffer.remaining() + + val nv21 = ByteArray(ySize + vuSize) + + yBuffer.get(nv21, 0, ySize) + vuBuffer.get(nv21, ySize, vuSize) + + val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null) + val out = ByteArrayOutputStream() + yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height),100, out) + return out.toByteArray() + } + + private fun saveImage(image: ImageProxy) { + try { + val currentMillis = System.currentTimeMillis().toString() + val filename = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + .toString() + "/" + currentMillis + "_ZXingCpp_bufferImage.jpg" + + File(filename).outputStream().use { out -> + out.write(image.toByteArray()) + out.flush() + out.close() + } + beeper.startTone(ToneGenerator.TONE_CDMA_CONFIRM) //Success Tone + }catch (e: Exception){ + beeper.startTone(ToneGenerator.TONE_CDMA_SOFT_ERROR_LITE) //Fail Tone + } } private fun bindCameraUseCases() = binding.viewFinder.post { @@ -115,8 +132,6 @@ class MainActivity : AppCompatActivity() { .setBackpressureStrategy(ImageAnalysis.STRATEGY_KEEP_ONLY_LATEST) .build() - imageCapture = ImageCapture.Builder().setTargetAspectRatio(AspectRatio.RATIO_16_9).build() - var frameCounter = 0 var lastFpsTimestamp = System.currentTimeMillis() var runtimes: Long = 0 @@ -124,6 +139,28 @@ class MainActivity : AppCompatActivity() { val readerJava = MultiFormatReader() val readerCpp = BarcodeReader() + // Create a new camera selector each time, enforcing lens facing + val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() + + // Camera provider is now guaranteed to be available + val cameraProvider = cameraProviderFuture.get() + + // Apply declared configs to CameraX using the same lifecycle owner + cameraProvider.unbindAll() + val camera = cameraProvider.bindToLifecycle( + this as LifecycleOwner, cameraSelector, preview, imageAnalysis + ) + + // Reduce exposure time to decrease effect of motion blur + val camera2 = Camera2CameraControl.from(camera.cameraControl) + camera2.captureRequestOptions = CaptureRequestOptions.Builder() + .setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, 1600) + .setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, -8) + .build() + + // Use the camera object to link our preview use case with the view + preview.setSurfaceProvider(binding.viewFinder.surfaceProvider) + imageAnalysis.setAnalyzer(executor, ImageAnalysis.Analyzer { image -> // Early exit: image analysis is in paused state if (binding.pause.isChecked) { @@ -131,6 +168,11 @@ class MainActivity : AppCompatActivity() { return@Analyzer } + if (doSaveImage){ + doSaveImage = false + saveImage(image) + } + val cropSize = image.height / 3 * 2 val cropRect = if (binding.crop.isChecked) Rect( @@ -218,31 +260,11 @@ class MainActivity : AppCompatActivity() { runtime2 = 0 } + camera.cameraControl.enableTorch(binding.torch.isChecked) + showResult(resultText, infoText, resultPoints, image) }) - // Create a new camera selector each time, enforcing lens facing - val cameraSelector = CameraSelector.Builder().requireLensFacing(CameraSelector.LENS_FACING_BACK).build() - - // Camera provider is now guaranteed to be available - val cameraProvider = cameraProviderFuture.get() - - // Apply declared configs to CameraX using the same lifecycle owner - cameraProvider.unbindAll() - val camera = cameraProvider.bindToLifecycle( - this as LifecycleOwner, cameraSelector, preview, imageAnalysis, imageCapture - ) - - // Reduce exposure time to decrease effect of motion blur - val camera2 = Camera2CameraControl.from(camera.cameraControl) - camera2.captureRequestOptions = CaptureRequestOptions.Builder() - .setCaptureRequestOption(CaptureRequest.SENSOR_SENSITIVITY, 1600) - .setCaptureRequestOption(CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION, -8) - .build() - - // Use the camera object to link our preview use case with the view - preview.setSurfaceProvider(binding.viewFinder.surfaceProvider) - }, ContextCompat.getMainExecutor(this)) } @@ -290,4 +312,4 @@ class MainActivity : AppCompatActivity() { private fun hasPermissions(context: Context) = permissions.all { ContextCompat.checkSelfPermission(context, it) == PackageManager.PERMISSION_GRANTED } -} +} \ No newline at end of file diff --git a/wrappers/android/app/src/main/res/layout-land/activity_camera.xml b/wrappers/android/app/src/main/res/layout-land/activity_camera.xml index 5aef516645..cf3dca0bde 100644 --- a/wrappers/android/app/src/main/res/layout-land/activity_camera.xml +++ b/wrappers/android/app/src/main/res/layout-land/activity_camera.xml @@ -104,6 +104,11 @@ style="@style/Chip" android:text="crop" /> + + - + \ No newline at end of file diff --git a/wrappers/android/app/src/main/res/layout/activity_camera.xml b/wrappers/android/app/src/main/res/layout/activity_camera.xml index 6a351fb312..4b5d3f4eb4 100644 --- a/wrappers/android/app/src/main/res/layout/activity_camera.xml +++ b/wrappers/android/app/src/main/res/layout/activity_camera.xml @@ -107,6 +107,11 @@ style="@style/ChipR" android:text="crop" /> + + - + \ No newline at end of file From 769ce440685440d39faacba4fe4de09e2eac2b3a Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 18 Jan 2023 00:55:47 +0100 Subject: [PATCH 009/173] zueci: keep source in sync with upstream See https://sourceforge.net/p/libzueci/tickets/4/. --- core/src/libzueci/zueci.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/libzueci/zueci.c b/core/src/libzueci/zueci.c index 156521ef98..26fd7ff9e3 100644 --- a/core/src/libzueci/zueci.c +++ b/core/src/libzueci/zueci.c @@ -1500,7 +1500,7 @@ ZUECI_EXTERN int zueci_eci_to_utf8(const int eci, const unsigned char src[], con zueci_u32 u; int src_incr; unsigned char replacement[5]; - int replacement_len; + int replacement_len = 0; /* g++ complains with "-Wmaybe-uninitialized" if this isn't set */ int ret = 0; if (!zueci_is_valid_eci(eci)) { From bb91b2f7dfe0c073b9027a3003ac17d204298f31 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 18 Jan 2023 02:35:15 +0100 Subject: [PATCH 010/173] android: minor code cleanup and sample app + new capture sound --- .../com/example/zxingcppdemo/MainActivity.kt | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index ad98c25adf..a8347d501a 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -22,6 +22,7 @@ import android.content.pm.PackageManager import android.graphics.* import android.hardware.camera2.CaptureRequest import android.media.AudioManager +import android.media.MediaActionSound import android.media.ToneGenerator import android.os.Bundle import android.os.Environment @@ -72,7 +73,7 @@ class MainActivity : AppCompatActivity() { } - private fun ImageProxy.toByteArray(): ByteArray { + private fun ImageProxy.toJpeg(): ByteArray { //This converts the ImageProxy (from the imageAnalysis Use Case) //to a ByteArray (compressed as JPEG) for then to be saved for debugging purposes //This is the closest representation of the image that is passed to the @@ -91,7 +92,7 @@ class MainActivity : AppCompatActivity() { val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null) val out = ByteArrayOutputStream() - yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height),100, out) + yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 100, out) return out.toByteArray() } @@ -99,15 +100,13 @@ class MainActivity : AppCompatActivity() { try { val currentMillis = System.currentTimeMillis().toString() val filename = Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) - .toString() + "/" + currentMillis + "_ZXingCpp_bufferImage.jpg" + .toString() + "/" + currentMillis + "_ZXingCpp.jpg" File(filename).outputStream().use { out -> - out.write(image.toByteArray()) - out.flush() - out.close() + out.write(image.toJpeg()) } - beeper.startTone(ToneGenerator.TONE_CDMA_CONFIRM) //Success Tone - }catch (e: Exception){ + MediaActionSound().play(MediaActionSound.SHUTTER_CLICK) + } catch (e: Exception) { beeper.startTone(ToneGenerator.TONE_CDMA_SOFT_ERROR_LITE) //Fail Tone } } @@ -168,7 +167,7 @@ class MainActivity : AppCompatActivity() { return@Analyzer } - if (doSaveImage){ + if (doSaveImage) { doSaveImage = false saveImage(image) } From 032dc77ef4405f460387bcfafd19af2ad321f2bf Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 18 Jan 2023 02:37:33 +0100 Subject: [PATCH 011/173] ConcentricFinder: check for out-of-range condition in first stepToEdge call --- core/src/ConcentricFinder.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index c08b570dfd..aaded5b6eb 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -39,7 +39,8 @@ std::optional CenterOfDoubleCross(const BitMatrix& image, PointI center, std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle) { BitMatrixCursorI cur(image, center, {0, 1}); - cur.stepToEdge(nth, range); + if (!cur.stepToEdge(nth, range)) + return {}; cur.turnRight(); // move clock wise and keep edge on the right uint32_t neighbourMask = 0; From e6dfb9165d3989771ca22b522662e15c897850a1 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 18 Jan 2023 02:47:56 +0100 Subject: [PATCH 012/173] Result: tune operator==() (again...) 2 MatrixCodes can be "the same" symbol if they have the same format and are located at the same position, even if one is not valid. This regularly happens when downscaling is enabled and a lower res version of a found QRCode is still detected but has a checksum error. --- core/src/Result.cpp | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/core/src/Result.cpp b/core/src/Result.cpp index af77bad917..7739b3cd09 100644 --- a/core/src/Result.cpp +++ b/core/src/Result.cpp @@ -123,11 +123,17 @@ Result& Result::setDecodeHints(DecodeHints hints) bool Result::operator==(const Result& o) const { - if (format() != o.format() || bytes() != o.bytes() || error() != o.error()) - return false; + // handle case where both are MatrixCodes first + if (!BarcodeFormats(BarcodeFormat::LinearCodes).testFlags(format() | o.format())) { + if (format() != o.format() || (bytes() != o.bytes() && isValid() && o.isValid())) + return false; - if (BarcodeFormats(BarcodeFormat::MatrixCodes).testFlag(format())) + // check for equal position if both are valid with equal bytes or at least one is in error return IsInside(Center(o.position()), position()); + } + + if (format() != o.format() || bytes() != o.bytes() || error() != o.error()) + return false; if (orientation() != o.orientation()) return false; From b35796625466c8510080d37ec553fa2d22630c81 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 18 Jan 2023 11:17:48 +0100 Subject: [PATCH 013/173] QRCode: throw FormatError instead of std::invalid_argument This fixes #490. --- core/src/qrcode/QRCodecMode.cpp | 7 ++++--- core/src/qrcode/QRCodecMode.h | 2 +- test/unit/qrcode/QRModeTest.cpp | 5 +++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/core/src/qrcode/QRCodecMode.cpp b/core/src/qrcode/QRCodecMode.cpp index 9d018d64bd..3d9f435996 100644 --- a/core/src/qrcode/QRCodecMode.cpp +++ b/core/src/qrcode/QRCodecMode.cpp @@ -6,6 +6,7 @@ #include "QRCodecMode.h" +#include "Error.h" #include "QRVersion.h" #include "ZXAlgorithms.h" @@ -14,9 +15,9 @@ namespace ZXing::QRCode { -CodecMode CodecModeForBits(int bits, bool isMirco) +CodecMode CodecModeForBits(int bits, bool isMicro) { - if (!isMirco) { + if (!isMicro) { if ((bits >= 0x00 && bits <= 0x05) || (bits >= 0x07 && bits <= 0x09) || bits == 0x0d) return static_cast(bits); } else { @@ -25,7 +26,7 @@ CodecMode CodecModeForBits(int bits, bool isMirco) return Bits2Mode[bits]; } - throw std::invalid_argument("Invalid mode"); + throw FormatError("Invalid codec mode"); } int CharacterCountBits(CodecMode mode, const Version& version) diff --git a/core/src/qrcode/QRCodecMode.h b/core/src/qrcode/QRCodecMode.h index 54c5dbe622..e5465851b7 100644 --- a/core/src/qrcode/QRCodecMode.h +++ b/core/src/qrcode/QRCodecMode.h @@ -34,7 +34,7 @@ enum class CodecMode * @return Mode encoded by these bits * @throws std::invalid_argument if bits do not correspond to a known mode */ -CodecMode CodecModeForBits(int bits, bool isMirco = false); +CodecMode CodecModeForBits(int bits, bool isMicro = false); /** * @param version version in question diff --git a/test/unit/qrcode/QRModeTest.cpp b/test/unit/qrcode/QRModeTest.cpp index fd960b6d46..4e223f76f6 100644 --- a/test/unit/qrcode/QRModeTest.cpp +++ b/test/unit/qrcode/QRModeTest.cpp @@ -6,6 +6,7 @@ #include "qrcode/QRCodecMode.h" #include "qrcode/QRVersion.h" +#include "Error.h" #include #include "gtest/gtest.h" @@ -20,7 +21,7 @@ TEST(QRModeTest, ForBits) ASSERT_EQ(CodecMode::ALPHANUMERIC, CodecModeForBits(0x02)); ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x04)); ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x08)); - ASSERT_THROW(CodecModeForBits(0x10), std::invalid_argument); + ASSERT_THROW(CodecModeForBits(0x10), Error); } TEST(QRModeTest, CharacterCount) @@ -52,7 +53,7 @@ TEST(QRModeTest, MicroForBits) ASSERT_EQ(CodecMode::BYTE, CodecModeForBits(0x02, true)); ASSERT_EQ(CodecMode::KANJI, CodecModeForBits(0x03, true)); - ASSERT_THROW(CodecModeForBits(0x04, true), std::invalid_argument); + ASSERT_THROW(CodecModeForBits(0x04, true), Error); } TEST(QRModeTest, MicroCharacterCount) From 60bf116069dcd90453f49bbd4b873455d5f93473 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 18 Jan 2023 16:46:03 +0100 Subject: [PATCH 014/173] Create FUNDING.yml --- .github/FUNDING.yml | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .github/FUNDING.yml diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 0000000000..a4b1181a85 --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,2 @@ +#github: axxel +custom: 'www.paypal.com/donate/?hosted_button_id=8LDF4NNQF8Z3L' From e8ae01b830203be8b417ddb60ef67a8eab16f112 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 19 Jan 2023 12:15:48 +0100 Subject: [PATCH 015/173] QRDecoder: fix/update two comments regarding the codec mode See discussion in #490. --- core/src/qrcode/QRCodecMode.h | 2 +- core/src/qrcode/QRDecoder.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/qrcode/QRCodecMode.h b/core/src/qrcode/QRCodecMode.h index e5465851b7..2c4f9d3551 100644 --- a/core/src/qrcode/QRCodecMode.h +++ b/core/src/qrcode/QRCodecMode.h @@ -32,7 +32,7 @@ enum class CodecMode * @param bits variable number of bits encoding a QR Code data mode * @param isMicro is this a MicroQRCode * @return Mode encoded by these bits - * @throws std::invalid_argument if bits do not correspond to a known mode + * @throws FormatError if bits do not correspond to a known mode */ CodecMode CodecModeForBits(int bits, bool isMicro = false); diff --git a/core/src/qrcode/QRDecoder.cpp b/core/src/qrcode/QRDecoder.cpp index 967fdd7688..905f0a5c8d 100644 --- a/core/src/qrcode/QRDecoder.cpp +++ b/core/src/qrcode/QRDecoder.cpp @@ -209,7 +209,7 @@ static ECI ParseECIValue(BitSource& bits) * a terminator code. If true, then the decoding can finish. If false, then the decoding * can read off the next mode code. * - * See ISO 18004:2006, 6.4.1 Table 2 + * See ISO 18004:2015, 7.4.1 Table 2 * * @param bits the stream of bits that might have a terminator code * @param version the QR or micro QR code version From b94c098ec64e2220227431d11d6f604b60b8b31a Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 20 Jan 2023 09:30:03 +0100 Subject: [PATCH 016/173] HybridBinarizer: minor performance tuning in min/max reduction Turns out clang-15 has better optimizations than earlier versions. That slightly changes the optimal min/max calculation. Comopiler Explorer experiments revealed that dropping the `else` allows some vectorization. Interestingly the gains from the related commit https://github.com/zxing-cpp/zxing-cpp/commit/fbabd63fc8676c9ee97b29fc0853f7d6aa2bac28 are basically lost in the sense that clang-15 produces pretty much the same code for all variants now. --- 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 8ccc2aa800..1781a82d94 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -51,7 +51,7 @@ static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, sum += pixel; if (pixel < min) min = pixel; - else if (pixel > max) + if (pixel > max) max = pixel; } // short-circuit min/max tests once dynamic range is met From af1cd1874b19bc2b84bbc172921d06443b090b90 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 20 Jan 2023 09:40:07 +0100 Subject: [PATCH 017/173] Algorithms: introduce UpdateMinMax function This fixes an issue in `IsConvex` that has unknown implications. See TODO. --- core/src/ConcentricFinder.cpp | 8 +++----- core/src/ConcentricFinder.h | 6 ++---- core/src/Quadrilateral.h | 5 +++-- core/src/RegressionLine.h | 7 +++---- core/src/ZXAlgorithms.h | 26 ++++++++++++++++++++++++++ core/src/aztec/AZDetector.cpp | 5 ++--- core/src/datamatrix/DMDetector.cpp | 2 +- core/src/oned/ODRowReader.h | 12 +++++------- core/src/pdf417/PDFScanningDecoder.cpp | 4 ++-- test/unit/ZXAlgorithmsTest.cpp | 21 +++++++++++++++++++++ 10 files changed, 68 insertions(+), 28 deletions(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index aaded5b6eb..b52daed8b4 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -7,6 +7,7 @@ #include "LogMatrix.h" #include "RegressionLine.h" +#include "ZXAlgorithms.h" namespace ZXing { @@ -169,11 +170,8 @@ static bool QuadrilateralIsPlausibleSquare(const QuadrilateralF q, int lineIndex { double m, M; m = M = distance(q[0], q[3]); - for (int i = 1; i < 4; ++i) { - double d = distance(q[i - 1], q[i]); - m = std::min(m, d); - M = std::max(M, d); - } + for (int i = 1; i < 4; ++i) + UpdateMinMax(m, M, distance(q[i - 1], q[i])); return m >= lineIndex * 2 && m > M / 3; } diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index e9be41b5b8..9c2ecd72be 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -86,8 +86,7 @@ std::optional LocateConcentricPattern(const BitMatrix& image, int spread = CheckDirection(cur, d, finderPattern, range, !RELAXED_THRESHOLD); if (!spread) return {}; - minSpread = std::min(spread, minSpread); - maxSpread = std::max(spread, maxSpread); + UpdateMinMax(minSpread, maxSpread, spread); } #if 1 @@ -95,8 +94,7 @@ std::optional LocateConcentricPattern(const BitMatrix& image, int spread = CheckDirection(cur, d, finderPattern, range, false); if (!spread) return {}; - minSpread = std::min(spread, minSpread); - maxSpread = std::max(spread, maxSpread); + UpdateMinMax(minSpread, maxSpread, spread); } #endif diff --git a/core/src/Quadrilateral.h b/core/src/Quadrilateral.h index ce5d47bec5..85069c8235 100644 --- a/core/src/Quadrilateral.h +++ b/core/src/Quadrilateral.h @@ -79,8 +79,9 @@ bool IsConvex(const Quadrilateral& poly) auto d2 = poly[i] - poly[(i + 1) % N]; auto cp = cross(d1, d2); - m = std::min(std::fabs(m), cp); - M = std::max(std::fabs(M), cp); + // TODO: see if the isInside check for all boundary points in GridSampler is still required after fixing the wrong fabs() + // application in the following line + UpdateMinMax(m, M, std::fabs(cp)); if (i == 0) sign = cp > 0; diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index 489e9d503f..e7893f6679 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -6,6 +6,7 @@ #pragma once #include "Point.h" +#include "ZXAlgorithms.h" #include #include @@ -128,10 +129,8 @@ class RegressionLine { PointF min = _points.front(), max = _points.front(); for (auto p : _points) { - min.x = std::min(min.x, p.x); - min.y = std::min(min.y, p.y); - max.x = std::max(max.x, p.x); - max.y = std::max(max.y, p.y); + UpdateMinMax(min.x, max.x, p.x); + UpdateMinMax(min.y, max.y, p.y); } auto diff = max - min; auto len = maxAbsComponent(diff); diff --git a/core/src/ZXAlgorithms.h b/core/src/ZXAlgorithms.h index 50f054c868..3965ab0746 100644 --- a/core/src/ZXAlgorithms.h +++ b/core/src/ZXAlgorithms.h @@ -107,4 +107,30 @@ std::string ToString(T val, int len) return result; } +template +void UpdateMin(T& min, T val) +{ + min = std::min(min, val); +} + +template +void UpdateMax(T& max, T val) +{ + max = std::max(max, val); +} + +template +void UpdateMinMax(T& min, T& max, T val) +{ + min = std::min(min, val); + max = std::max(max, val); + + // Note: the above code is not equivalent to + // if (val < min) min = val; + // else if (val > max) max = val; + // It is basically the same but without the 'else'. For the 'else'-variant to work, + // both min and max have to be initialized with a value that is part of the sequence. + // Also it turns out clang and gcc can vectorize the code above but not the code below. +} + } // ZXing diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 759245e4e9..e3ac7ea49b 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -105,13 +105,12 @@ static int CheckDirection(BitMatrixCursorF& cur, PointF dir, int range, bool upd static std::optional LocateAztecCenter(const BitMatrix& image, PointF center, int spreadH) { auto cur = BitMatrixCursorF(image, center, {}); - int minSpread = spreadH, maxSpread = spreadH; + int minSpread = spreadH, maxSpread = 0; for (auto d : {PointF{0, 1}, {1, 0}, {1, 1}, {1, -1}}) { int spread = CheckDirection(cur, d, spreadH, d.x == 0); if (!spread) return {}; - minSpread = std::min(spread, minSpread); - maxSpread = std::max(spread, maxSpread); + UpdateMinMax(minSpread, maxSpread, spread); } return ConcentricPattern{cur.p, (maxSpread + minSpread) / 2}; diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 381ada961a..5065f7bbc3 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -609,7 +609,7 @@ class EdgeTracer : public BitMatrixCursorF } if (finishLine.isValid()) - maxStepSize = std::min(maxStepSize, static_cast(finishLine.signedDistance(p))); + UpdateMin(maxStepSize, static_cast(finishLine.signedDistance(p))); auto stepResult = traceStep(dEdge, maxStepSize, line.isValid()); diff --git a/core/src/oned/ODRowReader.h b/core/src/oned/ODRowReader.h index 762a0af353..837037528a 100644 --- a/core/src/oned/ODRowReader.h +++ b/core/src/oned/ODRowReader.h @@ -10,6 +10,7 @@ #include "BitArray.h" #include "Pattern.h" #include "Result.h" +#include "ZXAlgorithms.h" #include #include @@ -146,13 +147,10 @@ class RowReader */ static BarAndSpaceI NarrowWideThreshold(const PatternView& view) { - BarAndSpaceI m = {std::numeric_limits::max(), - std::numeric_limits::max()}; - BarAndSpaceI M = {0, 0}; - for (int i = 0; i < view.size(); ++i) { - m[i] = std::min(m[i], view[i]); - M[i] = std::max(M[i], view[i]); - } + BarAndSpaceI m = {view[0], view[1]}; + BarAndSpaceI M = m; + for (int i = 2; i < view.size(); ++i) + UpdateMinMax(m[i], M[i], view[i]); BarAndSpaceI res; for (int i = 0; i < 2; ++i) { diff --git a/core/src/pdf417/PDFScanningDecoder.cpp b/core/src/pdf417/PDFScanningDecoder.cpp index f3840748c7..7d85c77297 100644 --- a/core/src/pdf417/PDFScanningDecoder.cpp +++ b/core/src/pdf417/PDFScanningDecoder.cpp @@ -14,6 +14,7 @@ #include "PDFDetectionResult.h" #include "PDFDecodedBitStreamParser.h" #include "PDFModulusGF.h" +#include "ZXAlgorithms.h" #include "ZXTestSupport.h" #include @@ -729,8 +730,7 @@ ScanningDecoder::Decode(const BitMatrix& image, const Nullable& ima if (codeword != nullptr) { detectionResult.column(barcodeColumn).value().setCodeword(imageRow, codeword); previousStartColumn = startColumn; - minCodewordWidth = std::min(minCodewordWidth, codeword.value().width()); - maxCodewordWidth = std::max(maxCodewordWidth, codeword.value().width()); + UpdateMinMax(minCodewordWidth, maxCodewordWidth, codeword.value().width()); } } } diff --git a/test/unit/ZXAlgorithmsTest.cpp b/test/unit/ZXAlgorithmsTest.cpp index 3531e88b48..7f73d7d7f5 100644 --- a/test/unit/ZXAlgorithmsTest.cpp +++ b/test/unit/ZXAlgorithmsTest.cpp @@ -28,3 +28,24 @@ TEST(ZXAlgorithmsTest, ToString) EXPECT_THROW(ToString(-1, 2), Error); EXPECT_THROW(ToString(111, 2), Error); } + +TEST(ZXAlgorithmsTest, UpdateMinMax) +{ + int m = 10, M = 0; + UpdateMinMax(m, M, 5); + EXPECT_EQ(m, 5); + EXPECT_EQ(M, 5); + + UpdateMinMax(m, M, 2); + EXPECT_EQ(m, 2); + EXPECT_EQ(M, 5); + + m = 1, M = 1; + UpdateMinMax(m, M, 0); + EXPECT_EQ(m, 0); + EXPECT_EQ(M, 1); + + UpdateMinMax(m, M, 2); + EXPECT_EQ(m, 0); + EXPECT_EQ(M, 2); +} From 77af842c1317300d3a7eced2a2684fdeddb093a3 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 20 Jan 2023 09:41:14 +0100 Subject: [PATCH 018/173] editorconfig: add '*.c' files (zueci.c) --- .editorconfig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.editorconfig b/.editorconfig index d3cd97c9dd..c643beb33d 100644 --- a/.editorconfig +++ b/.editorconfig @@ -5,7 +5,7 @@ insert_final_newline = true trim_trailing_whitespace = true charset = utf-8 -[*.{cpp,h,html,py}] +[*.{cpp,c,h,html,py}] indent_style = tab indent_size = 4 From 0c21b33208798ab416a24b4f4a3944d935671d53 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 20 Jan 2023 09:42:06 +0100 Subject: [PATCH 019/173] android: reduce Jpeg compression of debug images from 100 to 90 --- .../app/src/main/java/com/example/zxingcppdemo/MainActivity.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt index a8347d501a..dd0184ad63 100644 --- a/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt +++ b/wrappers/android/app/src/main/java/com/example/zxingcppdemo/MainActivity.kt @@ -72,7 +72,6 @@ class MainActivity : AppCompatActivity() { } } - private fun ImageProxy.toJpeg(): ByteArray { //This converts the ImageProxy (from the imageAnalysis Use Case) //to a ByteArray (compressed as JPEG) for then to be saved for debugging purposes @@ -92,7 +91,7 @@ class MainActivity : AppCompatActivity() { val yuvImage = YuvImage(nv21, ImageFormat.NV21, this.width, this.height, null) val out = ByteArrayOutputStream() - yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 100, out) + yuvImage.compressToJpeg(Rect(0, 0, yuvImage.width, yuvImage.height), 90, out) return out.toByteArray() } From 832b2148ab1711d61ab076989a7161a97f112f3d Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 20 Jan 2023 10:12:42 +0100 Subject: [PATCH 020/173] c++: remove stale #include directives --- core/src/HybridBinarizer.cpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 1781a82d94..fe54291c81 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -7,10 +7,7 @@ #include "HybridBinarizer.h" #include "BitMatrix.h" -#include "BitMatrixIO.h" -#include "ByteArray.h" #include "Matrix.h" -#include "ZXAlgorithms.h" #include #include From 80aac6bebb5da8225abef53426acb788ff3142b0 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 20 Jan 2023 10:13:47 +0100 Subject: [PATCH 021/173] ci: remove obsolete appveyor and travis yml files --- .appveyor.yml | 55 --------------------------------------------------- .travis.yml | 18 ----------------- 2 files changed, 73 deletions(-) delete mode 100644 .appveyor.yml delete mode 100644 .travis.yml diff --git a/.appveyor.yml b/.appveyor.yml deleted file mode 100644 index ef202db43d..0000000000 --- a/.appveyor.yml +++ /dev/null @@ -1,55 +0,0 @@ -version: '{branch}.{build}' -branches: - only: - - /v\d*\.\d*\.\d*/ -skip_non_tags: true -image: Visual Studio 2017 -build_script: -- cmd: >- - set CMAKE_EXE="cmake.exe" - set DESTINATION="wrappers\winrt\UAP\v0.8.0.0\ExtensionSDKs\ZXingWinRT\1.0.0.0" - set BASE_DIR="%CD%" - - cd %DESTINATION% - set DESTINATION=%CD% - - cd %BASE_DIR% - set BUILD_LOC=build_uwp_x86 - md %BUILD_LOC% - cd %BUILD_LOC% - %CMAKE_EXE% -G "Visual Studio 15 2017" -A Win32 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DEXTENSION_SDK_OUTPUT="%DESTINATION%" ..\wrappers\winrt - %CMAKE_EXE% --build . --config Release - - cd %BASE_DIR% - set BUILD_LOC=build_uwp_x64 - md %BUILD_LOC% - cd %BUILD_LOC% - %CMAKE_EXE% -G "Visual Studio 15 2017" -A x64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DEXTENSION_SDK_OUTPUT="%DESTINATION%" ..\wrappers\winrt - %CMAKE_EXE% --build . --config Release - - cd %BASE_DIR% - set BUILD_LOC=build_uwp_arm - md %BUILD_LOC% - cd %BUILD_LOC% - %CMAKE_EXE% -G "Visual Studio 15 2017" -A ARM -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DEXTENSION_SDK_OUTPUT="%DESTINATION%" ..\wrappers\winrt - %CMAKE_EXE% --build . --config Release - - cd %BASE_DIR% - set BUILD_LOC=build_uwp_arm64 - md %BUILD_LOC% - cd %BUILD_LOC% - %CMAKE_EXE% -G "Visual Studio 15 2017" -A ARM64 -DCMAKE_SYSTEM_NAME=WindowsStore -DCMAKE_SYSTEM_VERSION=10.0 -DEXTENSION_SDK_OUTPUT="%DESTINATION%" ..\wrappers\winrt - %CMAKE_EXE% --build . --config Release - - cd %BASE_DIR% - for /f "tokens=1,2,3 delims=. " %%a in ("%APPVEYOR_BUILD_VERSION%") do set major=%%a&set minor=%%b&set patch=%%c&set - nuget pack -Version %major%.%minor%.%patch% wrappers\winrt\nuget\ZXingWinRT.nuspec -artifacts: -- path: '*.nupkg' -deploy: -- provider: NuGet - api_key: - secure: OMvgm0WQ+yV7E+yxtmrOn6c/uO/V1fsbBERdz9aQUicqHGImzWqWoSBAankUtxdEwDZx0XrJ5hlUsIuL5tdQD+C8ZKrI1L3FukbXdbqVdKM= - artifact: /.*\.nupkg/ - on: - APPVEYOR_REPO_TAG: true diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 616d962292..0000000000 --- a/.travis.yml +++ /dev/null @@ -1,18 +0,0 @@ -arch: - - amd64 - - ppc64le -language: cpp -dist: focal -os: - - linux - -script: - - mkdir build && cd build - - cmake -DBUILD_UNIT_TESTS=ON -DBUILD_BLACKBOX_TESTS=ON .. - - make -j10 - - ./test/unit/UnitTest - - ./test/blackbox/ReaderTest ../test/samples - - ./test/blackbox/WriterTest - - ./test/blackbox/ReaderTest *.png - - ./example/ZXingWriter QR_CODE QRCodeTestText test.png - - ./example/ZXingReader test.png From 4fb11b43c36e017b63440cad1c0c8ccc7c532a2b Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 20 Jan 2023 23:51:22 +0100 Subject: [PATCH 022/173] tryDownscale: help compiler with auto vectorization (>4x faster scaling) This reduces the runtime penalty payed for `tryDownscale(true)` on a Google Pixel 3 to 50% or less (depending on other hints). --- core/src/ReadBarcode.cpp | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 5ac61e250e..6585a16520 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -45,9 +45,9 @@ static LumImage ExtractLum(const ImageView& iv, P projection) class LumImagePyramid { - int N = 3; std::vector buffers; + template void addLayer() { auto siv = layers.back(); @@ -66,19 +66,27 @@ class LumImagePyramid } } + void addLayer(int factor) + { + // help the compiler's auto-vectorizer by hard-coding the scale factor + switch (factor) { + case 2: addLayer<2>(); break; + case 3: addLayer<3>(); break; + case 4: addLayer<4>(); break; + default: throw std::invalid_argument("Invalid DecodeHints::downscaleFactor"); break; + } + } + public: std::vector layers; - LumImagePyramid(const ImageView& iv, int threshold, int factor) : N(factor) + LumImagePyramid(const ImageView& iv, int threshold, int factor) { - if (factor < 2) - throw std::invalid_argument("Invalid DecodeHints::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 && - std::min(layers.back().width(), layers.back().height()) >= N) - addLayer(); + std::min(layers.back().width(), layers.back().height()) >= factor) + addLayer(factor); #if 0 // Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a // single symbol. If we start with the higher resolution, we get better (high res) position information. From d9780e5eca49d9358bcd580bee1be256f2e0b994 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 20 Jan 2023 23:52:14 +0100 Subject: [PATCH 023/173] test: fix the expected text content string of one qrcode image --- test/samples/qrcode-2/13.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/samples/qrcode-2/13.txt b/test/samples/qrcode-2/13.txt index 7729d6463d..6c62da9ba6 100644 --- a/test/samples/qrcode-2/13.txt +++ b/test/samples/qrcode-2/13.txt @@ -1 +1 @@ -The 2005 USGS aerial photograph of the Washington Monument is censored. \ No newline at end of file +The 2005 USGS aerial photography of the Washington Monument is censored. \ No newline at end of file From 160d98bdf10f167c223d3da6d0c1e6a867e7f128 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 23 Jan 2023 11:05:52 +0100 Subject: [PATCH 024/173] ZXVersion.h: add `ZXING_VERSION_STR` and print from cli tools `-version` --- core/CMakeLists.txt | 2 +- core/ZXVersion.h.in | 7 +++++++ example/ZXingReader.cpp | 7 ++++++- example/ZXingWriter.cpp | 7 ++++++- 4 files changed, 20 insertions(+), 3 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index afdadbcfe8..cf3e7a4e20 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -439,7 +439,7 @@ add_library (ZXing ) target_include_directories (ZXing - PUBLIC "$" + PUBLIC "$" "$" INTERFACE "$" ) diff --git a/core/ZXVersion.h.in b/core/ZXVersion.h.in index 78fde4106a..41dd401145 100644 --- a/core/ZXVersion.h.in +++ b/core/ZXVersion.h.in @@ -1,5 +1,6 @@ /* * Copyright 2019 Nu-book Inc. +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -9,3 +10,9 @@ #define ZXING_VERSION_MAJOR @PROJECT_VERSION_MAJOR@ #define ZXING_VERSION_MINOR @PROJECT_VERSION_MINOR@ #define ZXING_VERSION_PATCH @PROJECT_VERSION_PATCH@ + +namespace ZXing { + +constexpr const char* ZXING_VERSION_STR = "@PROJECT_VERSION_MAJOR@.@PROJECT_VERSION_MINOR@.@PROJECT_VERSION_PATCH@"; + +} diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 7636c8b765..a7b1e26b6f 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -6,6 +6,7 @@ #include "ReadBarcode.h" #include "GTIN.h" +#include "ZXVersion.h" #include #include @@ -40,7 +41,8 @@ static void PrintUsage(const char* exePath) << " -bytes Write (only) the bytes content of the symbol(s) to stdout\n" << " -pngout \n" << " Write a copy of the input image with barcodes outlined by a green line\n" - << " -help Print usage information and exit\n" + << " -help Print usage information\n" + << " -version Print version information\n" << "\n" << "Supported formats are:\n"; for (auto f : BarcodeFormats::all()) { @@ -100,6 +102,9 @@ static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLi } else if (is("-help") || is("--help")) { PrintUsage(argv[0]); exit(0); + } else if (is("-version") || is("--version")) { + std::cout << "ZXingReader " << ZXING_VERSION_STR << "\n"; + exit(0); } else { filePaths.push_back(argv[i]); } diff --git a/example/ZXingWriter.cpp b/example/ZXingWriter.cpp index 4f5c11531f..b168ed6ce8 100644 --- a/example/ZXingWriter.cpp +++ b/example/ZXingWriter.cpp @@ -8,6 +8,7 @@ #include "BitMatrixIO.h" #include "CharacterSet.h" #include "MultiFormatWriter.h" +#include "ZXVersion.h" #include #include @@ -28,7 +29,8 @@ static void PrintUsage(const char* exePath) << " -margin Margin around barcode\n" << " -encoding Encoding used to encode input text\n" << " -ecc Error correction level, [0-8]\n" - << " -help Print usage information and exit\n" + << " -help Print usage information\n" + << " -version Print version information\n" << "\n" << "Supported formats are:\n"; for (auto f : BarcodeFormatsFromString("Aztec Codabar Code39 Code93 Code128 DataMatrix EAN8 EAN13 ITF PDF417 QRCode UPCA UPCE")) @@ -77,6 +79,9 @@ static bool ParseOptions(int argc, char* argv[], int* width, int* height, int* m } else if (strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) { PrintUsage(argv[0]); exit(0); + } else if (strcmp(argv[i], "-version") || strcmp(argv[i], "--version")) { + std::cout << "ZXingWriter " << ZXING_VERSION_STR << "\n"; + exit(0); } else if (nonOptArgCount == 0) { *format = BarcodeFormatFromString(argv[i]); if (*format == BarcodeFormat::None) { From e13880bb6bb98632208c4820ec6672e7654f142e Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 23 Jan 2023 14:18:52 +0100 Subject: [PATCH 025/173] DMDecoder: code readability improvements --- core/src/datamatrix/DMDataBlock.cpp | 22 ++++++++++------------ core/src/datamatrix/DMDecoder.cpp | 8 +++----- 2 files changed, 13 insertions(+), 17 deletions(-) diff --git a/core/src/datamatrix/DMDataBlock.cpp b/core/src/datamatrix/DMDataBlock.cpp index 710e67d087..e0a1248f4c 100644 --- a/core/src/datamatrix/DMDataBlock.cpp +++ b/core/src/datamatrix/DMDataBlock.cpp @@ -18,7 +18,7 @@ std::vector GetDataBlocks(const ByteArray& rawCodewords, const Versio // First count the total number of data blocks // Now establish DataBlocks of the appropriate size and number of data codewords auto& ecBlocks = version.ecBlocks; - int numResultBlocks = ecBlocks.numBlocks(); + const int numResultBlocks = ecBlocks.numBlocks(); std::vector result; result.reserve(numResultBlocks); for (auto& ecBlock : ecBlocks.blocks) @@ -28,29 +28,27 @@ std::vector GetDataBlocks(const ByteArray& rawCodewords, const Versio // All blocks have the same amount of data, except that the last n // (where n may be 0) have 1 less byte. Figure out where these start. // TODO(bbrown): There is only one case where there is a difference for Data Matrix for size 144 - int longerBlocksTotalCodewords = Size(result[0].codewords); - int longerBlocksNumDataCodewords = longerBlocksTotalCodewords - ecBlocks.codewordsPerBlock; - int shorterBlocksNumDataCodewords = longerBlocksNumDataCodewords - 1; + const int numCodewords = Size(result[0].codewords); + const int numDataCodewords = numCodewords - ecBlocks.codewordsPerBlock; // The last elements of result may be 1 element shorter for 144 matrix // first fill out as many elements as all of them have minus 1 int rawCodewordsOffset = 0; - for (int i = 0; i < shorterBlocksNumDataCodewords; i++) + for (int i = 0; i < numDataCodewords - 1; i++) for (int j = 0; j < numResultBlocks; j++) result[j].codewords[i] = rawCodewords[rawCodewordsOffset++]; // Fill out the last data block in the longer ones - bool specialVersion = version.versionNumber == 24; - int numLongerBlocks = specialVersion ? 8 : numResultBlocks; + const bool size144x144 = version.symbolHeight == 144; + const int numLongerBlocks = size144x144 ? 8 : numResultBlocks; for (int j = 0; j < numLongerBlocks; j++) - result[j].codewords[longerBlocksNumDataCodewords - 1] = rawCodewords[rawCodewordsOffset++]; + result[j].codewords[numDataCodewords - 1] = rawCodewords[rawCodewordsOffset++]; // Now add in error correction blocks - int max = Size(result[0].codewords); - for (int i = longerBlocksNumDataCodewords; i < max; i++) { + for (int i = numDataCodewords; i < numCodewords; i++) { for (int j = 0; j < numResultBlocks; j++) { - int jOffset = specialVersion ? (j + 8) % numResultBlocks : j; - int iOffset = specialVersion && jOffset > 7 ? i - 1 : i; + int jOffset = size144x144 ? (j + 8) % numResultBlocks : j; + int iOffset = size144x144 && jOffset > 7 ? i - 1 : i; result[jOffset].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; } } diff --git a/core/src/datamatrix/DMDecoder.cpp b/core/src/datamatrix/DMDecoder.cpp index c79515e0f5..8a464567f7 100644 --- a/core/src/datamatrix/DMDecoder.cpp +++ b/core/src/datamatrix/DMDecoder.cpp @@ -408,15 +408,13 @@ static DecoderResult DoDecode(const BitMatrix& bits) // Error-correct and copy data blocks together into a stream of bytes const int dataBlocksCount = Size(dataBlocks); for (int j = 0; j < dataBlocksCount; j++) { - auto& dataBlock = dataBlocks[j]; - ByteArray& codewordBytes = dataBlock.codewords; - int numDataCodewords = dataBlock.numDataCodewords; - if (!CorrectErrors(codewordBytes, numDataCodewords)) + auto& [numDataCodewords, codewords] = dataBlocks[j]; + if (!CorrectErrors(codewords, numDataCodewords)) return ChecksumError(); for (int i = 0; i < numDataCodewords; i++) { // De-interlace data blocks. - resultBytes[i * dataBlocksCount + j] = codewordBytes[i]; + resultBytes[i * dataBlocksCount + j] = codewords[i]; } } From 5bd3a9fcfc32256084bcd5f5be5fee24bce04092 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 23 Jan 2023 14:26:36 +0100 Subject: [PATCH 026/173] DMDetector: improve back-projection precision during traceGaps --- core/src/datamatrix/DMDetector.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 5065f7bbc3..20d6250528 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -571,6 +571,10 @@ class EdgeTracer : public BitMatrixCursorF if (std::abs(dot(normalized(d), line.normal())) > 0.7) // thresh is approx. sin(45 deg) return false; + // re-evaluate line with all the points up to here before projecting + if (!line.evaluate(1.5)) + return false; + auto np = line.project(p); // make sure we are making progress even when back-projecting: // consider a 90deg corner, rotated 45deg. we step away perpendicular from the line and get From 99732515d0df29edb55f0300df331b709568f3b7 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 23 Jan 2023 14:53:27 +0100 Subject: [PATCH 027/173] DMDetector: fix detection of pure symbols with mod-size of 1 pixel --- core/src/BitMatrixCursor.h | 4 ++-- test/blackbox/BlackboxTestRunner.cpp | 6 +++--- test/samples/datamatrix-1/mod-size-1.png | Bin 0 -> 119 bytes test/samples/datamatrix-1/mod-size-1.txt | 1 + 4 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 test/samples/datamatrix-1/mod-size-1.png create mode 100644 test/samples/datamatrix-1/mod-size-1.txt diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index 7e1f16c440..1fb057186e 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -155,11 +155,11 @@ class BitMatrixCursor return ret; } - int countEdges(int range = 0) + int countEdges(int range) { int res = 0; - while (int steps = stepToEdge(1, range)) { + while (int steps = range ? stepToEdge(1, range) : 0) { range -= steps; ++res; } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 1402cc7a44..04bebdc6f8 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -351,12 +351,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 21, 21, 270 }, }); - runTests("datamatrix-1", "DataMatrix", 27, { - { 26, 27, 0 }, + runTests("datamatrix-1", "DataMatrix", 28, { + { 26, 28, 0 }, { 0, 26, 90 }, { 0, 26, 180 }, { 0, 26, 270 }, - { 26, 0, pure }, + { 27, 0, pure }, }); runTests("datamatrix-2", "DataMatrix", 13, { diff --git a/test/samples/datamatrix-1/mod-size-1.png b/test/samples/datamatrix-1/mod-size-1.png new file mode 100644 index 0000000000000000000000000000000000000000..91c5565f68f681a72ec62489cb4072003b98635d GIT binary patch literal 119 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=-3?y@QpIZW?*aCb)T>t<74`hZOx?BgObUa-g zLpZJ{CnO|rlqP)eNH}44V8WyW0(TBP*xaBvukm1H Date: Mon, 23 Jan 2023 14:59:40 +0100 Subject: [PATCH 028/173] DMDecoder: support 144x144 symbols in legacy and compliant variants This fixes #259. --- core/src/datamatrix/DMDataBlock.cpp | 5 +++-- core/src/datamatrix/DMDataBlock.h | 3 ++- core/src/datamatrix/DMDecoder.cpp | 15 +++++++++++++-- test/blackbox/BlackboxTestRunner.cpp | 12 ++++++------ test/samples/datamatrix-1/144x144_wrong.png | Bin 0 -> 2834 bytes test/samples/datamatrix-1/144x144_wrong.txt | 5 +++++ 6 files changed, 29 insertions(+), 11 deletions(-) create mode 100644 test/samples/datamatrix-1/144x144_wrong.png create mode 100644 test/samples/datamatrix-1/144x144_wrong.txt diff --git a/core/src/datamatrix/DMDataBlock.cpp b/core/src/datamatrix/DMDataBlock.cpp index e0a1248f4c..ea1c872d7c 100644 --- a/core/src/datamatrix/DMDataBlock.cpp +++ b/core/src/datamatrix/DMDataBlock.cpp @@ -1,6 +1,7 @@ /* * Copyright 2016 Nu-book Inc. * Copyright 2016 ZXing authors +* Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -13,7 +14,7 @@ namespace ZXing::DataMatrix { -std::vector GetDataBlocks(const ByteArray& rawCodewords, const Version& version) +std::vector GetDataBlocks(const ByteArray& rawCodewords, const Version& version, bool fix259) { // First count the total number of data blocks // Now establish DataBlocks of the appropriate size and number of data codewords @@ -47,7 +48,7 @@ std::vector GetDataBlocks(const ByteArray& rawCodewords, const Versio // Now add in error correction blocks for (int i = numDataCodewords; i < numCodewords; i++) { for (int j = 0; j < numResultBlocks; j++) { - int jOffset = size144x144 ? (j + 8) % numResultBlocks : j; + int jOffset = size144x144 && fix259 ? (j + 8) % numResultBlocks : j; int iOffset = size144x144 && jOffset > 7 ? i - 1 : i; result[jOffset].codewords[iOffset] = rawCodewords[rawCodewordsOffset++]; } diff --git a/core/src/datamatrix/DMDataBlock.h b/core/src/datamatrix/DMDataBlock.h index 54dc7fade7..bc2a8b86a2 100644 --- a/core/src/datamatrix/DMDataBlock.h +++ b/core/src/datamatrix/DMDataBlock.h @@ -32,9 +32,10 @@ struct DataBlock * * @param rawCodewords bytes as read directly from the Data Matrix Code * @param version version of the Data Matrix Code + * @param fix259 see https://github.com/zxing-cpp/zxing-cpp/issues/259 * @return DataBlocks containing original bytes, "de-interleaved" from representation in the * Data Matrix Code */ -std::vector GetDataBlocks(const ByteArray& rawCodewords, const Version& version); +std::vector GetDataBlocks(const ByteArray& rawCodewords, const Version& version, bool fix259 = false); } // namespace ZXing::DataMatrix diff --git a/core/src/datamatrix/DMDecoder.cpp b/core/src/datamatrix/DMDecoder.cpp index 8a464567f7..af3b9c5018 100644 --- a/core/src/datamatrix/DMDecoder.cpp +++ b/core/src/datamatrix/DMDecoder.cpp @@ -397,8 +397,10 @@ static DecoderResult DoDecode(const BitMatrix& bits) if (codewords.empty()) return FormatError("Invalid number of code words"); + bool fix259 = false; // see https://github.com/zxing-cpp/zxing-cpp/issues/259 +retry: // Separate into data blocks - std::vector dataBlocks = GetDataBlocks(codewords, *version); + std::vector dataBlocks = GetDataBlocks(codewords, *version, fix259); if (dataBlocks.empty()) return FormatError("Invalid number of data blocks"); @@ -409,14 +411,23 @@ static DecoderResult DoDecode(const BitMatrix& bits) const int dataBlocksCount = Size(dataBlocks); for (int j = 0; j < dataBlocksCount; j++) { auto& [numDataCodewords, codewords] = dataBlocks[j]; - if (!CorrectErrors(codewords, numDataCodewords)) + if (!CorrectErrors(codewords, numDataCodewords)) { + if(version->versionNumber == 24 && !fix259) { + fix259 = true; + goto retry; + } return ChecksumError(); + } for (int i = 0; i < numDataCodewords; i++) { // De-interlace data blocks. resultBytes[i * dataBlocksCount + j] = codewords[i]; } } +#ifdef PRINT_DEBUG + if (fix259) + printf("-> needed retry with fix259 for 144x144 symbol\n"); +#endif // Decode the contents of that stream of bytes return DecodedBitStreamParser::Decode(std::move(resultBytes), version->isDMRE()) diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 04bebdc6f8..e3be449ae8 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -351,12 +351,12 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 21, 21, 270 }, }); - runTests("datamatrix-1", "DataMatrix", 28, { - { 26, 28, 0 }, - { 0, 26, 90 }, - { 0, 26, 180 }, - { 0, 26, 270 }, - { 27, 0, pure }, + runTests("datamatrix-1", "DataMatrix", 29, { + { 27, 29, 0 }, + { 0, 27, 90 }, + { 0, 27, 180 }, + { 0, 27, 270 }, + { 28, 0, pure }, }); runTests("datamatrix-2", "DataMatrix", 13, { diff --git a/test/samples/datamatrix-1/144x144_wrong.png b/test/samples/datamatrix-1/144x144_wrong.png new file mode 100644 index 0000000000000000000000000000000000000000..d298c063889af7265702fdf285665bf4c89cec16 GIT binary patch literal 2834 zcmV+t3+?oYP)>>0GUhpek`KADV zIQg)z%FK{=94&RyBlu|FzTGV3)fgZDsI*(Mc(-eWg+#bZ36>i z5%yyfwnP#>c6a!ySUCMM(5p-GWHM%;Yq@&gVy%Bw6kEzn!fg-xRJWK@bViG%&& z*#)lgEufK#;FWHoefeTHH!A$2Vzy|M#C@Vi2V^CQkEN(b2kjC*Ds@V|Nv*s&X7gD< z^}AK0d6LNfQJFn(5-AuogzXXop0=C1F{uX(U)3P#9d1#rMu?20*)4#)yk1`Mk4oea zA1j6~_iU5ynYeh@iqJe$_^1p^D$BhXemEHg6AJs4WCH16KtV$E?Yi%-R&WB_6O0wXiQiqhcDlE53tv)Ws)Rf|J6Dr^z6)S|9 z&6I!|mZgDAi;%Oewk0imR0eqPW)k#`t+vgC;|!(Y+?zW0ujG7 zh`zW;_^34Vtu8|*Uq!sQ5V;u~cs(zQ*FP$06jmFtcnn)2B51kBP5Kx{_28po_Dz}% zdApCN>E!xyPp)a}#XkS2MAN*zPyjN|xDk!;L`a||+~ z@A*CdsJK|Eb=vsMP2f1J(v?SLRN%3|NA*|xXmo;d(J=44I`_O(ckx|dU-GtpR7}bi zf{*Xyny|DapIhr?Yd#}{k4lrW;;PuUpG)kzLh}}?O{W&Qe^gvhg}mPAW})3CNe@)Z zaa1t_;G;4K=I+V67=3fwscJcL9n!+>;2)J1nV;~Ir8XysTDBZ-ca%-A7JO7Pm=CV? z;Wci1Y)<2j)2yw;$@y2Mn8JMSt12~*L~p2U7eG~nHsPbP)~VR2H#B9gGa07RNS(Dy zVCNqdhcc*6l|A(#6OOH9P1`A5H%<7cG`CWQnUE&wUQCM$?!%gx0v_~_%4D7}&wKPt z)pEX z?xj856UP|9o$#+pWAsToNpXujun9)sfF~u&=?h;~WP`8jdF(Ccbt>lViv&MNit~?( z#}-a~WLhh=Qy%%L9OOfu(|qtzsmZ$ZZELMIx6BIy*zAz$t!4F(%5v>ot?-`gJU(_r zJ$(U;5_f*^RsGff$d#5&s)Id&Wv!Xqr)H(|0{!_%rBtUQJVVJQgsdTKArmpiqyYmT zm57T>w+d}T^}`LA#1;?>Pl(>+*#k4n{yt8r&W!8l!_7d{m0%9Y@E{C+Oj9oCb_OpIytK@sG-MMu*$? zu}YniNSCDIVuR!X34ByiK<{pcy}FS-rv2VsPVU}=SoDudBg5M2u3Cn$&;ToiZ%jzF z;sCxXEez{+^}U=bXr8Uw-pU??VRQ9iG0<=vS$)v9H@ddftvqx?K83-Sh45a5u4Pm?S>>~Nvc=yz3 zx}bkl+8~G$hdjNNPHD5u##nB){)wP_Q%rF(hlmFWt*Is%~VQ4a^xsJ5d3BQ_0uRdAxX;3Y8_!Mw08 zd6uu=T5*nlRH~36>SGYb>2LMCX)V{J{-h_Co!bh-HbqrOX&m}=;QXWFyjEg-Lo9QqW%+PdD1Djru`Ya7WMSQW zxzc7Bwy$D%DF$vFfX=_FP;BOAw^1TBV8rcI%bBuw8Ur7d1t03D=(~m7!b6eCUTCS^ z4m1C#c+$_z#=Q;tZ23XMXlr5E;4;BS^=JF2q$5j-={+qFcNH32ZzJyJP54Kp!-}jz zzGFGmpxA)K^@dwD>4d{nkGUGth$ zTE_$y*)5btqyU@Q9eh-Rs~Hzgbq(=e?e9(Sda*FAA3dZM)15-{OkmDQFiUho*dOtExGSKv8OuET?`s@}V6;U*glYTC)o zch8^LMvkI?RED%HoOFR~)#NtS^^7_NwJp)%tLogiUX6eO#+ngx%TZ$B4Y3jbsN_@t z7}ehs_QaA3UFUf0!Yl9$J}M0t-boA4jT3)u;SVRBO(wcS{i8CQG*j{SOd-coebrm1 kWk^8YGx(~0*vB949}I^^R8M8tXaE2J07*qoM6N<$f=57yk^lez literal 0 HcmV?d00001 diff --git a/test/samples/datamatrix-1/144x144_wrong.txt b/test/samples/datamatrix-1/144x144_wrong.txt new file mode 100644 index 0000000000..102e452641 --- /dev/null +++ b/test/samples/datamatrix-1/144x144_wrong.txt @@ -0,0 +1,5 @@ +Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. Stet clita kasd gubergren, no sea takimata sanctus est Lorem ipsum dolor sit amet. + +Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse molestie consequat, vel illum dolore eu feugiat nulla facilisis at vero eros et accumsan et iusto odio dignissim qui blandit praesent luptatum zzril delenit augue duis dolore te feugait nulla facilisi. Lorem ipsum dolor sit amet, consectetuer adipiscing elit, sed diam nonummy nibh euismod tincidunt ut laoreet dolore magna aliquam erat volutpat. + +Ut wisi enim ad minim veniam, quis nostrud exerci tation ullamcorper suscipit lobortis nisl ut aliquip ex ea commodo consequat. Duis autem vel eum iriure dolor in hendrerit in vulputate velit esse - there is still some space, so I'm filling it with letters. And I don't speak latin.... - there is still some space, so I'm filling it with letters. And I don't speak latin.... - there is still some space, so I'm filling it with letters. And I don't speak latin.... - there is still some space, so I'm filling it with letters. And I don't speak latin.... \ No newline at end of file From cd1063404a288d164944a34acf90f924a4bef32b Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 23 Jan 2023 15:25:43 +0100 Subject: [PATCH 029/173] example: fix build regression in ZXingWriter command line parsing --- example/ZXingWriter.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/example/ZXingWriter.cpp b/example/ZXingWriter.cpp index b168ed6ce8..a0b9389b2e 100644 --- a/example/ZXingWriter.cpp +++ b/example/ZXingWriter.cpp @@ -57,29 +57,30 @@ static bool ParseOptions(int argc, char* argv[], int* width, int* height, int* m { int nonOptArgCount = 0; for (int i = 1; i < argc; ++i) { - if (strcmp(argv[i], "-size") == 0) { + auto is = [&](const char* str) { return strncmp(argv[i], str, strlen(argv[i])) == 0; }; + if (is("-size")) { if (++i == argc) return false; if (!ParseSize(argv[i], width, height)) { std::cerr << "Invalid size specification: " << argv[i] << std::endl; return false; } - } else if (strcmp(argv[i], "-margin") == 0) { + } else if (is("-margin")) { if (++i == argc) return false; *margin = std::stoi(argv[i]); - } else if (strcmp(argv[i], "-ecc") == 0) { + } else if (is("-ecc")) { if (++i == argc) return false; *eccLevel = std::stoi(argv[i]); - } else if (strcmp(argv[i], "-encoding") == 0) { + } else if (is("-encoding")) { if (++i == argc) return false; *encoding = CharacterSetFromString(argv[i]); - } else if (strcmp(argv[i], "-help") == 0 || strcmp(argv[i], "--help") == 0) { + } else if (is("-help") || is("--help")) { PrintUsage(argv[0]); exit(0); - } else if (strcmp(argv[i], "-version") || strcmp(argv[i], "--version")) { + } else if (is("-version") || is("--version")) { std::cout << "ZXingWriter " << ZXING_VERSION_STR << "\n"; exit(0); } else if (nonOptArgCount == 0) { From 9a8575977ca32f3b583154065b28b11842cea58b Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 26 Jan 2023 15:22:51 +0100 Subject: [PATCH 030/173] GlobalHistogramBinarizer: make it up to 16x faster Get rid of bounds checking and help the compiler find a vectorization optimization by using the existing fast implementation from the ThresholdBinarizer. This makes ReadBarcode in special circumstances up to twice as fast. --- core/src/BinaryBitmap.cpp | 30 +++++++++++++++++++++++++++ core/src/BinaryBitmap.h | 2 ++ core/src/GlobalHistogramBinarizer.cpp | 11 ++-------- core/src/HybridBinarizer.cpp | 1 + core/src/ThresholdBinarizer.h | 27 +----------------------- 5 files changed, 36 insertions(+), 35 deletions(-) diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index 2952f2181c..edb997e263 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -17,6 +17,36 @@ struct BinaryBitmap::Cache std::shared_ptr matrix; }; +BitMatrix BinaryBitmap::binarize(const uint8_t threshold) const +{ + BitMatrix res(width(), height()); + + if (_buffer.pixStride() == 1 && _buffer.rowStride() == _buffer.width()) { + // Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2) + auto dst = res.row(0).begin(); + for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst) + *dst = *src <= threshold; + } else { + auto processLine = [&res, threshold](int y, const auto* src, const int stride) { + for (auto& dst : res.row(y)) { + dst = *src <= threshold; + src += stride; + } + }; + for (int y = 0; y < res.height(); ++y) { + auto src = _buffer.data(0, y) + GreenIndex(_buffer.format()); + // Specialize the inner loop for strides 1 and 4 to support auto vectorization + switch (_buffer.pixStride()) { + case 1: processLine(y, src, 1); break; + case 4: processLine(y, src, 4); break; + default: processLine(y, src, _buffer.pixStride()); break; + } + } + } + + return res; +} + BinaryBitmap::BinaryBitmap(const ImageView& buffer) : _cache(new Cache), _buffer(buffer) {} BinaryBitmap::~BinaryBitmap() = default; diff --git a/core/src/BinaryBitmap.h b/core/src/BinaryBitmap.h index 292b866910..435d945150 100644 --- a/core/src/BinaryBitmap.h +++ b/core/src/BinaryBitmap.h @@ -39,6 +39,8 @@ class BinaryBitmap */ virtual std::shared_ptr getBlackMatrix() const = 0; + BitMatrix binarize(const uint8_t threshold) const; + public: BinaryBitmap(const ImageView& buffer); virtual ~BinaryBitmap(); diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index f09d040d22..5669650f75 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -7,7 +7,6 @@ #include "GlobalHistogramBinarizer.h" #include "BitMatrix.h" -#include "ByteArray.h" #include #include @@ -143,15 +142,9 @@ GlobalHistogramBinarizer::getBlackMatrix() const if (blackPoint <= 0) return {}; - // We delay reading the entire image luminance until the black point estimation succeeds. - // Although we end up reading four rows twice, it is consistent with our motto of - // "fail quickly" which is necessary for continuous scanning. - auto matrix = std::make_shared(width(), height()); - for(int y = 0; y < height(); ++y) - for(int x = 0; x < width(); ++x) - matrix->set(x, y, *_buffer.data(x, y) < blackPoint); - return matrix; + + return std::make_shared(binarize(blackPoint)); } } // ZXing diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index fe54291c81..6c401b8c7e 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -103,6 +103,7 @@ static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, 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; } diff --git a/core/src/ThresholdBinarizer.h b/core/src/ThresholdBinarizer.h index 75ad2a898d..f3318cdec6 100644 --- a/core/src/ThresholdBinarizer.h +++ b/core/src/ThresholdBinarizer.h @@ -51,32 +51,7 @@ class ThresholdBinarizer : public BinaryBitmap std::shared_ptr getBlackMatrix() const override { - BitMatrix res(width(), height()); - - if (_buffer.pixStride() == 1 && _buffer.rowStride() == _buffer.width()) { - // Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2) - auto dst = res.row(0).begin(); - for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst) - *dst = *src <= _threshold; - } else { - auto processLine = [this, &res](int y, const auto* src, const int stride) { - for (auto& dst : res.row(y)) { - dst = *src <= _threshold; - src += stride; - } - }; - for (int y = 0; y < res.height(); ++y) { - auto src = _buffer.data(0, y) + GreenIndex(_buffer.format()); - // Specialize the inner loop for strides 1 and 4 to support auto vectorization - switch (_buffer.pixStride()) { - case 1: processLine(y, src, 1); break; - case 4: processLine(y, src, 4); break; - default: processLine(y, src, _buffer.pixStride()); break; - } - } - } - - return std::make_shared(std::move(res)); + return std::make_shared(binarize(_threshold)); } }; From bafd1c65395ec97fb3012cf040f48ee8ba2cd938 Mon Sep 17 00:00:00 2001 From: axxel Date: Sun, 29 Jan 2023 02:12:53 +0100 Subject: [PATCH 031/173] Code128: fix performance regression introduced in last change falsepositives blackbox tests run about 10% faster. --- core/src/oned/ODCode128Reader.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/oned/ODCode128Reader.cpp b/core/src/oned/ODCode128Reader.cpp index e48b3c2e18..9d0927eab6 100644 --- a/core/src/oned/ODCode128Reader.cpp +++ b/core/src/oned/ODCode128Reader.cpp @@ -180,10 +180,10 @@ static auto E2E_PATTERNS = [] { Result Code128Reader::decodePattern(int rowNumber, PatternView& next, std::unique_ptr&) const { int minCharCount = 4; // start + payload + checksum + stop - auto decodePattern = [](const PatternView& view) { + auto decodePattern = [](const PatternView& view, bool start = false) { // This is basically the reference algorithm from the specification int code = IndexOf(E2E_PATTERNS, ToInt(NormalizedE2EPattern(view))); - if (code == -1) // if the reference algo fails, give the original upstream version a try (required to decode a few samples) + if (code == -1 && !start) // if the reference algo fails, give the original upstream version a try (required to decode a few samples) code = DecodeDigit(view, Code128::CODE_PATTERNS, MAX_AVG_VARIANCE, MAX_INDIVIDUAL_VARIANCE); return code; }; @@ -193,7 +193,7 @@ Result Code128Reader::decodePattern(int rowNumber, PatternView& next, std::uniqu return {}; next = next.subView(0, CHAR_LEN); - int startCode = decodePattern(next); + int startCode = decodePattern(next, true); if (!(CODE_START_A <= startCode && startCode <= CODE_START_C)) return {}; From c1e31ffef30865143ec03bf1751574302f1a7b30 Mon Sep 17 00:00:00 2001 From: Qingnan Duan Date: Sun, 29 Jan 2023 15:35:25 +0800 Subject: [PATCH 032/173] Fix out-of-bound access --- core/src/ConcentricFinder.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index e9be41b5b8..775dbb144a 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[N/2 + 1], &pattern[N], pattern[N/2] / 2.f); + auto a = std::accumulate(pattern.begin() + (N/2 + 1), pattern.end(), pattern[N/2] / 2.f); return end - a; } } From 51ef8401a97e914d047b37fa9197710e775ca0af Mon Sep 17 00:00:00 2001 From: Kien Nguyen Date: Thu, 2 Feb 2023 17:59:57 +0700 Subject: [PATCH 033/173] Fix default hints' maxNumberOfSymbols --- wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm index 622b585aa9..9a50235c0d 100644 --- a/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm +++ b/wrappers/ios/Sources/Wrapper/Reader/ZXIDecodeHints.mm @@ -44,6 +44,10 @@ - (instancetype)initWithTryHarder:(BOOL)tryHarder return self; } +-(void)setMaxNumberOfSymbols:(NSInteger)maxNumberOfSymbols { + self.zxingHints.setMaxNumberOfSymbols(maxNumberOfSymbols); +} + -(void)setTryHarder:(BOOL)tryHarder { self.zxingHints.setTryHarder(tryHarder); } @@ -80,6 +84,10 @@ -(void)setDownscalethreshold:(uint16_t)downscaleThreshold { self.zxingHints.setDownscaleThreshold(downscaleThreshold); } +- (NSInteger)maxNumberOfSymbols { + return self.zxingHints.maxNumberOfSymbols(); +} + -(BOOL)tryHarder { return self.zxingHints.tryHarder(); } From e3c0ecbe7f42bb22e1b24ba6fd56c7bd26b89895 Mon Sep 17 00:00:00 2001 From: Kien Nguyen Date: Thu, 2 Feb 2023 18:18:06 +0700 Subject: [PATCH 034/173] Reduce min iOS deployment target to 11.0 --- wrappers/ios/Package.swift | 2 +- wrappers/ios/build-release.sh | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/ios/Package.swift b/wrappers/ios/Package.swift index b0dbe39bb3..7e3352acd7 100644 --- a/wrappers/ios/Package.swift +++ b/wrappers/ios/Package.swift @@ -4,7 +4,7 @@ import PackageDescription let package = Package( name: "ZXingCppWrapper", platforms: [ - .iOS(.v13) + .iOS(.v11) ], products: [ .library( diff --git a/wrappers/ios/build-release.sh b/wrappers/ios/build-release.sh index 9a4256ca09..7c1f4b4d5e 100755 --- a/wrappers/ios/build-release.sh +++ b/wrappers/ios/build-release.sh @@ -6,7 +6,7 @@ echo ========= Create project structure cmake -S../../ -B_builds -GXcode \ -DCMAKE_SYSTEM_NAME=iOS \ "-DCMAKE_OSX_ARCHITECTURES=arm64;x86_64" \ - -DCMAKE_OSX_DEPLOYMENT_TARGET=13.0 \ + -DCMAKE_OSX_DEPLOYMENT_TARGET=11.0 \ -DCMAKE_INSTALL_PREFIX=`pwd`/_install \ -DCMAKE_XCODE_ATTRIBUTE_ONLY_ACTIVE_ARCH=NO \ -DBUILD_UNIT_TESTS=NO \ From eea0a8f986ba9527eb3e543386635d3e5f5df891 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 30 Jan 2023 20:00:52 +0100 Subject: [PATCH 035/173] Aztec: minor performance improvements Moving the break condition in `IsAztectCenterPattern` outside the loop makes the function run about twice as fast. That is counter intuitive and likely caused by enabled vectorizations or simply by reducing the number of conditional jumps that can throw off the branch predictor. Tuned the range limit. --- core/src/aztec/AZDetector.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index e3ac7ea49b..3c798c89d9 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -37,10 +37,8 @@ static bool IsAztectCenterPattern(const PatternView& view) m = v; else if (v > M) M = v; - if (M > m * 4 / 3) - return false; } - return view[-1] >= view[Size(view) / 2] - 2 && view[Size(view)] >= view[Size(view) / 2] - 2; + return M <= m * 4 / 3 && view[-1] >= view[Size(view) / 2] - 2 && view[Size(view)] >= view[Size(view) / 2] - 2; }; // specialized version of FindLeftGuard to find the '1,1,1,1,1,1,1' pattern of a compact Aztec center pattern @@ -57,20 +55,20 @@ static PatternView FindAztecCenterPattern(const PatternView& view) static int CheckDirection(BitMatrixCursorF& cur, PointF dir, int range, bool updatePosition) { - range = range * 2 / 7; // TODO: tune + range *= 2; // tilted symbols may have a larger vertical than horizontal range auto pOri = cur.p; auto cuo = cur; cur.setDirection(dir); cuo.setDirection(-dir); - int centerUp = cur.stepToEdge(1, range); + int centerUp = cur.stepToEdge(1, range / 7); if (!centerUp) return 0; - int centerDown = cuo.stepToEdge(1, range); + int centerDown = cuo.stepToEdge(1, range / 7); if (!centerDown) return 0; int center = centerUp + centerDown - 1; // -1 because the starting pixel is counted twice - if (center > range || center < range / 6) + if (center > range / 7 || center < range / (4 * 7)) return 0; if (updatePosition) @@ -82,7 +80,9 @@ static int CheckDirection(BitMatrixCursorF& cur, PointF dir, int range, bool upd for (auto c : {&cur, &cuo}) { int lastS = center; for (int i = 0; i < 3; ++i) { - int s = c->stepToEdge(1, M); + int s = c->stepToEdge(1, range - spread); + if (s == 0) + return 0; int v = s + lastS; if (m == 0) m = M = v; From 652d2b95b7ba0be16fd9896295b3adb817adc2c0 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 2 Feb 2023 12:43:02 +0100 Subject: [PATCH 036/173] DecodeHints: change interpretation of maxNumberOfSymbols == 0 Explicitly setting `maxNumberOfSymbols` to 0 results in 0 detections which makes no sens in any case. Now we reinterpret 0 as `INT_MAX` which effectively means 'unlimited'. This is somewhat related to #507. --- core/src/ReadBarcode.cpp | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 6585a16520..7a75861f7e 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -11,6 +11,7 @@ #include "MultiFormatReader.h" #include "ThresholdBinarizer.h" +#include #include #include @@ -143,7 +144,7 @@ Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); Results results; - int maxSymbols = hints.maxNumberOfSymbols(); + int maxSymbols = hints.maxNumberOfSymbols() ? hints.maxNumberOfSymbols() : INT_MAX; for (auto&& iv : pyramid.layers) { auto bitmap = CreateBitmap(hints.binarizer(), iv); for (int invert = 0; invert <= static_cast(hints.tryInvert()); ++invert) { From 88f3a18c39fabfd008985b7885cdc8cfe9975330 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 3 Feb 2023 02:24:19 +0100 Subject: [PATCH 037/173] GlobalHistogramBinarizer: micro optimize For non-rotated input, this change makes the `getPatternRow` function about twice as fast. For very select cases (like testing for only one linear barcode) this can reduce the total runtime around 30%. --- core/src/GlobalHistogramBinarizer.cpp | 31 +++++++++++++++++---------- 1 file changed, 20 insertions(+), 11 deletions(-) diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 5669650f75..63da1a8c75 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -79,8 +79,6 @@ bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& if (buffer.width() < 3) return false; // special casing the code below for a width < 3 makes no sense - res.clear(); - const uint8_t* luminances = buffer.data(0, row); const int pixStride = buffer.pixStride(); std::array buckets = {}; @@ -91,31 +89,42 @@ bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& if (blackPoint <= 0) return false; + res.resize(buffer.width() + 2); + std::fill(res.begin(), res.end(), 0); + auto* intPos = res.data(); + auto* lastPos = luminances; bool lastVal = luminances[0] < blackPoint; if (lastVal) - res.push_back(0); // first value is number of white pixels, here 0 + intPos++; // first value is number of white pixels, here 0 auto process = [&](bool val, const uint8_t* p) { - if (val != lastVal) { - res.push_back(narrow_cast((p - lastPos) / pixStride)); - lastVal = val; + bool update = val != lastVal; + *intPos = update * narrow_cast((p - lastPos) / pixStride); + intPos += update; + lastVal = val; + if (update) lastPos = p; - } }; - for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p < e; p += pixStride) - process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); + // the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 2x faster + if (pixStride == 1) + for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p < e; p += pixStride) + process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); + else + for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p < e; p += pixStride) + process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); auto* backPos = buffer.data(buffer.width() - 1, row); bool backVal = *backPos < blackPoint; process(backVal, backPos); - res.push_back(narrow_cast((backPos - lastPos) / pixStride + 1)); + *intPos++ = narrow_cast((backPos - lastPos) / pixStride + 1); if (backVal) - res.push_back(0); // last value is number of white pixels, here 0 + intPos++; + res.resize(intPos - res.data()); assert(res.size() % 2 == 1); return true; From 42d43cbbca6d8304810348f60dd0ebb3ea5dc3ed Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 3 Feb 2023 21:12:33 +0100 Subject: [PATCH 038/173] ConcentricFinder: don't trace the innermost CenterOfRing twice --- core/src/ConcentricFinder.cpp | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index b52daed8b4..c2add97852 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -70,12 +70,12 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra return sum / n; } -std::optional CenterOfRings(const BitMatrix& image, PointI center, int range, int numOfRings) +std::optional CenterOfRings(const BitMatrix& image, PointF center, int range, int numOfRings) { - PointF sum = {}; - int n = 0; - for (int i = 0; i < numOfRings; ++i) { - auto c = CenterOfRing(image, center, range, i + 1); + int n = numOfRings; + PointF sum = numOfRings * center; + for (int i = 1; i < numOfRings; ++i) { + auto c = CenterOfRing(image, PointI(center), range, i + 1); if (!c) return {}; // TODO: decide whether this wheighting depending on distance to the center is worth it @@ -89,10 +89,12 @@ std::optional CenterOfRings(const BitMatrix& image, PointI center, int r std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize) { // make sure we have at least one path of white around the center - if (!CenterOfRing(image, PointI(center), range, 1)) + auto res = CenterOfRing(image, PointI(center), range, 1); + if (!res) return {}; - auto res = CenterOfRings(image, PointI(center), range, finderPatternSize / 2); + center = *res; + res = CenterOfRings(image, center, range, finderPatternSize / 2); if (!res || !image.get(*res)) res = CenterOfDoubleCross(image, PointI(center), range, finderPatternSize / 2 + 1); if (!res || !image.get(*res)) From 8bf088ea18af382195f6273a65f9365b11693b54 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 3 Feb 2023 23:41:44 +0100 Subject: [PATCH 039/173] BitMatrix: consistently use SET_V --- core/src/BinaryBitmap.cpp | 4 ++-- core/src/BitMatrix.h | 9 +++++---- core/src/HybridBinarizer.cpp | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index edb997e263..b9ed76e051 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -25,11 +25,11 @@ BitMatrix BinaryBitmap::binarize(const uint8_t threshold) const // Specialize for a packed buffer with pixStride 1 to support auto vectorization (16x speedup on AVX2) auto dst = res.row(0).begin(); for (auto src = _buffer.data(0, 0), end = _buffer.data(0, height()); src != end; ++src, ++dst) - *dst = *src <= threshold; + *dst = (*src <= threshold) * BitMatrix::SET_V; } else { auto processLine = [&res, threshold](int y, const auto* src, const int stride) { for (auto& dst : res.row(y)) { - dst = *src <= threshold; + dst = (*src <= threshold) * BitMatrix::SET_V; src += stride; } }; diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index 128f3495fc..4acd20902c 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -30,9 +30,6 @@ class BitMatrix int _width = 0; int _height = 0; using data_t = uint8_t; - static constexpr data_t SET_V = 0xff; // allows playing with SIMD binarization - static constexpr data_t UNSET_V = 0; - static_assert(bool(SET_V) && !bool(UNSET_V), "SET_V needs to evaluate to true, UNSET_V to false, see iterator usage"); std::vector _bits; // There is nothing wrong to support this but disable to make it explicit since we may copy something very big here. @@ -55,6 +52,10 @@ class BitMatrix bool getBottomRightOnBit(int &right, int& bottom) const; public: + static constexpr data_t SET_V = 0xff; // allows playing with SIMD binarization + static constexpr data_t UNSET_V = 0; + static_assert(bool(SET_V) && !bool(UNSET_V), "SET_V needs to evaluate to true, UNSET_V to false, see iterator usage"); + BitMatrix() = default; #ifdef __GNUC__ @@ -96,7 +97,7 @@ class BitMatrix void flipAll() { for (auto& i : _bits) - i = !i; + i = !i * SET_V; } /** diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 6c401b8c7e..faa4cd4e50 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -105,7 +105,7 @@ static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, 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; + *dst = (*src <= threshold) * BitMatrix::SET_V; } } From ac0f61d137d44f2c3b4f8776a60af63c0312f881 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 3 Feb 2023 23:42:27 +0100 Subject: [PATCH 040/173] ThresholdBinarizer: use meaningful default threshold value --- core/src/ThresholdBinarizer.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ThresholdBinarizer.h b/core/src/ThresholdBinarizer.h index f3318cdec6..344cffb18b 100644 --- a/core/src/ThresholdBinarizer.h +++ b/core/src/ThresholdBinarizer.h @@ -17,7 +17,7 @@ class ThresholdBinarizer : public BinaryBitmap const uint8_t _threshold = 0; public: - ThresholdBinarizer(const ImageView& buffer, uint8_t threshold = 1) : BinaryBitmap(buffer), _threshold(threshold) {} + ThresholdBinarizer(const ImageView& buffer, uint8_t threshold = 128) : BinaryBitmap(buffer), _threshold(threshold) {} bool getPatternRow(int row, int rotation, PatternRow& res) const override { From fc1c188d3705cd4816164455055fd3a0d93babe4 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 4 Feb 2023 00:43:10 +0100 Subject: [PATCH 041/173] ConcentricFinder: micro-optimize ReadSymmetricPattern Read from the center outward instead of moving to one end and then stepping the same way back again. --- core/src/ConcentricFinder.h | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index 59c9575353..c28545327e 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -35,16 +35,24 @@ static float CenterFromEnd(const std::array& pattern, float end) template std::optional ReadSymmetricPattern(Cursor& cur, int range) { - if (!cur.stepToEdge(std::tuple_size::value / 2 + 1, range)) - return std::nullopt; - + Pattern res = {}; + auto constexpr s_2 = Size(res)/2; + auto cuo = cur; cur.turnBack(); - cur.step(); - auto pattern = cur.template readPattern(range); - if (pattern.back() == 0) - return std::nullopt; - return pattern; + auto next = [&](auto& cur, int i) { + auto v = cur.stepToEdge(1, range); + res[s_2 + i] += v; + return v; + }; + + for (int i = 0; i <= s_2; ++i) { + if (!next(cur, i) || !next(cuo, -i)) + return {}; + } + res[s_2]--; // the starting pixel has been counted twice, fix this + + return res; } template From 983b3ad187cd2a6ee2bc33b9277e61a80df72eb3 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 4 Feb 2023 00:44:46 +0100 Subject: [PATCH 042/173] BinaryBitmap: make it possible to call invert() with an empty matrix cache --- core/src/BinaryBitmap.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index b9ed76e051..c27dc51fc3 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -59,10 +59,8 @@ const BitMatrix* BinaryBitmap::getBitMatrix() const void BinaryBitmap::invert() { - if (_cache->matrix) { - auto matrix = const_cast(_cache->matrix.get()); + if (auto matrix = const_cast(getBitMatrix())) matrix->flipAll(); - } _inverted = true; } From 9c1f50a4a49a3ab9c0d590715c28f01042f80720 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 7 Feb 2023 09:45:24 +0100 Subject: [PATCH 043/173] replace a few more overlooked github.com/nu-book mentions --- wrappers/wasm/demo_reader.html | 2 +- wrappers/wasm/demo_writer.html | 2 +- wrappers/winrt/nuget/ZXingWinRT.nuspec | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index 51461d496c..0f8252bba9 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -143,7 +143,7 @@

Read barcodes

- This is a simple demo of WebAssembly build (using Emcripten) of zxing-cpp + This is a simple demo of WebAssembly build (using Emcripten) of zxing-cpp

diff --git a/wrappers/wasm/demo_writer.html b/wrappers/wasm/demo_writer.html index 9c3aad01c9..8311a0cbc7 100644 --- a/wrappers/wasm/demo_writer.html +++ b/wrappers/wasm/demo_writer.html @@ -67,7 +67,7 @@

Generate barcodes

- This is a simple demo of WebAssembly build (using Emcripten) of zxing-cpp + This is a simple demo of WebAssembly build (using Emcripten) of zxing-cpp

diff --git a/wrappers/winrt/nuget/ZXingWinRT.nuspec b/wrappers/winrt/nuget/ZXingWinRT.nuspec index ebf397c09b..f6b61e8bf6 100644 --- a/wrappers/winrt/nuget/ZXingWinRT.nuspec +++ b/wrappers/winrt/nuget/ZXingWinRT.nuspec @@ -6,8 +6,8 @@ ZXingWinRT Nu-book Inc. Nu-book Inc. - https://github.com/nu-book/zxing-cpp/blob/master/LICENSE - https://github.com/nu-book/zxing-cpp + https://github.com/zxing-cpp/zxing-cpp/blob/master/LICENSE + https://github.com/zxing-cpp/zxing-cpp true C++ port of ZXing barcode scanner library Bug fixes and improvements for many readers and decoders From 8fa4a1bc4e65c6f78aeaf1f9930911e31357453c Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 7 Feb 2023 09:47:14 +0100 Subject: [PATCH 044/173] wasm: fix FileNotFoundError in demo_reader/writer related to favicon.ico --- wrappers/wasm/demo_reader.html | 1 + wrappers/wasm/demo_writer.html | 1 + 2 files changed, 2 insertions(+) diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index 0f8252bba9..a255c0c3dd 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -4,6 +4,7 @@ ZXing in Javascript demo + + + + +

zxing-cpp/wasm live demo

+

+ This is a simple demo of the wasm wrapper of zxing-cpp + scanning for barcodes in a live video stream. +

+ + Camera: + +    + + Format: + +    + + Mode: + +

+ + +

+ +
+ + + + + \ No newline at end of file diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index 26d20201f5..9e9188e778 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -3,7 +3,7 @@ - ZXing in Javascript demo + zxing-cpp/wasm reader demo @@ -142,9 +142,10 @@ -

Read barcodes

+

zxing-cpp/wasm reader demo

- This is a simple demo of WebAssembly build (using Emcripten) of zxing-cpp + This is a simple demo of the wasm wrapper of zxing-cpp + scanning for barcodes in image files.

@@ -157,14 +158,19 @@

Read barcodes

+ + + - + + +
Drag your image here... diff --git a/wrappers/wasm/demo_writer.html b/wrappers/wasm/demo_writer.html index be4ac3cf8b..930318734a 100644 --- a/wrappers/wasm/demo_writer.html +++ b/wrappers/wasm/demo_writer.html @@ -3,7 +3,7 @@ - ZXing in Javascript demo + zxing-cpp/wasm writer demo @@ -66,9 +66,10 @@ -

Generate barcodes

+

zxing-cpp/wasm writer demo

- This is a simple demo of WebAssembly build (using Emcripten) of zxing-cpp + This is a simple demo of the wasm wrapper of zxing-cpp + for generating barcodes.

From 95d2eb10bbc5fb4d5c23aa9b781772d64981e08a Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 10 Feb 2023 01:47:20 +0100 Subject: [PATCH 051/173] wasm: fix build regression when using recent emsdk versions Updated README with new performance measurements. Thanks to @dkonecny-oracle for the patch. --- wrappers/wasm/CMakeLists.txt | 2 +- wrappers/wasm/README.md | 19 +++++++++++-------- 2 files changed, 12 insertions(+), 9 deletions(-) diff --git a/wrappers/wasm/CMakeLists.txt b/wrappers/wasm/CMakeLists.txt index ec383e4016..167c418698 100644 --- a/wrappers/wasm/CMakeLists.txt +++ b/wrappers/wasm/CMakeLists.txt @@ -20,7 +20,7 @@ add_definitions ("-s DISABLE_EXCEPTION_CATCHING=0") add_subdirectory (${CMAKE_CURRENT_SOURCE_DIR}/../../core ${CMAKE_BINARY_DIR}/ZXing) -set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --bind -s ENVIRONMENT=${BUILD_EMSCRIPTEN_ENVIRONMENT} -s DISABLE_EXCEPTION_CATCHING=0 -s FILESYSTEM=0 -s MODULARIZE=1 -s EXPORT_NAME=ZXing -s ALLOW_MEMORY_GROWTH=1") +set (CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} --bind -s ENVIRONMENT=${BUILD_EMSCRIPTEN_ENVIRONMENT} -s DISABLE_EXCEPTION_CATCHING=0 -s FILESYSTEM=0 -s MODULARIZE=1 -s EXPORT_NAME=ZXing -s EXPORTED_FUNCTIONS=\"['_malloc', '_free']\" -s ALLOW_MEMORY_GROWTH=1") if (BUILD_READERS AND BUILD_WRITERS) add_executable (zxing BarcodeReader.cpp BarcodeWriter.cpp) diff --git a/wrappers/wasm/README.md b/wrappers/wasm/README.md index e8a427b8ce..53560f71c3 100644 --- a/wrappers/wasm/README.md +++ b/wrappers/wasm/README.md @@ -12,14 +12,17 @@ You can also download the latest build output from the continuous integration sy ## Performance -It turns out that compiling the library with the `-Os` (`MinSizeRel`) flag causes a really big performance penalty. Here are some measurements from the demo_cam_reader (performed on Chromium 109 running on a Core i9-9980HK): +It turns out that compiling the library with the `-Os` (`MinSizeRel`) flag causes a noticible performance penalty. Here are some measurements from the demo_cam_reader (performed on Chromium 109 running on a Core i9-9980HK): -| | `-Os` | `-Os -flto` | `-O3` | `-O3 -flto` | -|---------|-------|-------------|--------|-------------| -| size | 790kB | 950kb | 940kb | 1000kB | -| runtime | 320ms | 30ms | 8ms | 8ms | +| | `-Os` | `-Os -flto` | `-O3` | `-O3 -flto` | _Build system_ | +|---------|-------|-------------|--------|-------------|-| +| size | 790kB | 950kb | 940kb | 1000kB | _All_ | +| runtime | 320ms | 30ms | 8ms | 8ms | C++17, emsdk 3.1.9 | +| runtime | 13ms | 30ms | 8ms | 8ms | C++17, emsdk 3.1.31 | +| runtime | 46ms | 46ms | 11ms | 11ms | C++20, emsdk 3.1.31 | Conclusions: - * saving 15% of download size for the price of a 40x slowdown seems like a very hard sale... - * link time optimization (`-flto`) is not worth it (in `Release` builds) - \ No newline at end of file + * saving 15% of download size for the price of a 2x-4x slowdown seems like a hard sale (let alone the 40x one)... + * building in C++-20 mode brings position independent DataMatrix detection but costs 35% more time + * link time optimization (`-flto`) is not worth it and potentially even counter productive + From dc9e797c43efe8e46e5ad195f74c3ae30d1f4792 Mon Sep 17 00:00:00 2001 From: "Colin B. Macdonald" Date: Sun, 12 Feb 2023 18:54:44 -0800 Subject: [PATCH 052/173] Correct "Mirco" to "Micro" Closes Issue #512. --- core/src/qrcode/QRFormatInformation.cpp | 2 +- wrappers/python/zxing.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index 87e4be2ffe..574020680f 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -137,7 +137,7 @@ FormatInformation FormatInformation::DecodeQR(uint32_t formatInfoBits1, uint32_t */ FormatInformation FormatInformation::DecodeMQR(uint32_t formatInfoBits) { - // We don't use the additional masking (with 0x4445) to work around potentially non complying MircoQRCode encoders + // We don't use the additional masking (with 0x4445) to work around potentially non complying MicroQRCode encoders auto fi = FindBestFormatInfo(0, FORMAT_INFO_DECODE_LOOKUP_MICRO, {formatInfoBits, MirrorBits(formatInfoBits)}); constexpr uint8_t BITS_TO_VERSION[] = {1, 2, 2, 3, 3, 4, 4, 4}; diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index 14dd5c7e7b..bce2e41963 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -136,7 +136,7 @@ PYBIND11_MODULE(zxingcpp, m) .value("MaxiCode", BarcodeFormat::MaxiCode) .value("PDF417", BarcodeFormat::PDF417) .value("QRCode", BarcodeFormat::QRCode) - .value("MircoQRCode", BarcodeFormat::MicroQRCode) + .value("MicroQRCode", BarcodeFormat::MicroQRCode) .value("DataBar", BarcodeFormat::DataBar) .value("DataBarExpanded", BarcodeFormat::DataBarExpanded) .value("UPCA", BarcodeFormat::UPCA) From 93709608eaaaf3641b42f6cccd0c9bfc9f91c99e Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 14 Feb 2023 00:35:59 +0100 Subject: [PATCH 053/173] link to github sponsors profile page of @axxel --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index a4b1181a85..7be904885c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -#github: axxel +github: axxel custom: 'www.paypal.com/donate/?hosted_button_id=8LDF4NNQF8Z3L' From 258071d694120971ae0a85aa850c63c7fd48f61f Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 14 Feb 2023 15:16:13 +0100 Subject: [PATCH 054/173] ConcentricFinder: properly evaluate range during pattern reading --- core/src/BitMatrixCursor.h | 7 ++++++- core/src/ConcentricFinder.cpp | 7 +++++-- core/src/ConcentricFinder.h | 2 ++ core/src/aztec/AZDetector.cpp | 2 +- core/src/qrcode/QRDetector.cpp | 6 +++--- 5 files changed, 17 insertions(+), 7 deletions(-) diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index 1fb057186e..69f3aa7925 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -171,8 +171,13 @@ class BitMatrixCursor ARRAY readPattern(int range = 0) { ARRAY res; - for (auto& i : res) + for (auto& i : res) { i = stepToEdge(1, range); + if (!i) + return res; + if (range) + range -= i; + } return res; } diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index c2add97852..b13c0eebf5 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -39,8 +39,11 @@ std::optional CenterOfDoubleCross(const BitMatrix& image, PointI center, std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle) { + // range is the approximate width/height of the nth ring, if nth>1 then we can savely reduce the expected radius + int radius = nth > 1 && requireCircle ? range * 2 / 3 : range; + log(center, 3); BitMatrixCursorI cur(image, center, {0, 1}); - if (!cur.stepToEdge(nth, range)) + if (!cur.stepToEdge(nth, radius)) return {}; cur.turnRight(); // move clock wise and keep edge on the right @@ -60,7 +63,7 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra return {}; // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate - if (maxAbsComponent(cur.p - center) > range || center == cur.p || n > 4 * 2 * range) + if (maxAbsComponent(cur.p - center) > radius || center == cur.p || n > 4 * 2 * range) return {}; } while (cur.p != start); diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index c28545327e..49923f4b21 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -43,6 +43,8 @@ std::optional ReadSymmetricPattern(Cursor& cur, int range) auto next = [&](auto& cur, int i) { auto v = cur.stepToEdge(1, range); res[s_2 + i] += v; + if (range) + range -= v; return v; }; diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 3c798c89d9..fc1e2b4b0e 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -124,7 +124,7 @@ static std::vector FindPureFinderPattern(const BitMatrix& ima PointF p(left + width / 2, top + height / 2); constexpr auto PATTERN = FixedPattern<7, 7>{1, 1, 1, 1, 1, 1, 1}; - if (auto pattern = LocateConcentricPattern(image, PATTERN, p, width / 3)) + if (auto pattern = LocateConcentricPattern(image, PATTERN, p, width / 2)) return {*pattern}; else return {}; diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index a4d4351090..a27cc84fd5 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -60,7 +60,7 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t // make sure p is not 'inside' an already found pattern area if (FindIf(res, [p](const auto& old) { return distance(p, old) < old.size / 2; }) == res.end()) { auto pattern = LocateConcentricPattern(image, PATTERN, p, - Reduce(next) * 3 / 2); // 1.5 for very skewed samples + Reduce(next) * 3); // 3 for very skewed samples if (pattern) { log(*pattern, 3); assert(image.get(pattern->x, pattern->y)); @@ -302,7 +302,7 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) // if we did not land on a black pixel or the concentric pattern finder fails, // leave the intersection of the lines as the best guess if (image.get(brCoR)) { - if (auto brCP = LocateConcentricPattern(image, FixedPattern<3, 3>{1, 1, 1}, brCoR, moduleSize * 3)) + if (auto brCP = LocateConcentricPattern(image, FixedPattern<3, 3>{1, 1, 1}, brCoR, moduleSize * 3 * 2)) return sample(*brCP, quad[2] - PointF(3, 3)); } } @@ -345,7 +345,7 @@ DetectorResult DetectPureQR(const BitMatrix& image) Pattern diagonal; // allow corners be moved one pixel inside to accommodate for possible aliasing artifacts for (auto [p, d] : {std::pair(tl, PointI{1, 1}), {tr, {-1, 1}}, {bl, {1, -1}}}) { - diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3); + diagonal = BitMatrixCursorI(image, p, d).readPatternFromBlack(1, width / 3 + 1); if (!IsPattern(diagonal, PATTERN)) return {}; } From 8b880f41c81b7d82a4c16b60089e4069a1022cf1 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 15 Feb 2023 21:57:46 +0100 Subject: [PATCH 055/173] QRCode: major improvement in detection of high version (big) symbols This is achieved by two principal changes: 1. Read the version information before sampling the grid to correct the guessed dimension of the symbol. 2. Search for all the alignment patterns inside the symbol and split the sampling grid into small pieces based on the position of the alignment patterns. This also lays the ground work for improving e.g. the DataMatrix detector in a similar manner. This change fixes #314 and results in almost all `high_version` samples from that set being detected successfully. This work has been generously sponsored by @Sergio-, without which it would not have happened. Thanks a lot. --- core/src/ConcentricFinder.cpp | 5 +- core/src/GridSampler.cpp | 63 +++++--- core/src/GridSampler.h | 17 +++ core/src/Matrix.h | 8 +- core/src/qrcode/QRDetector.cpp | 216 +++++++++++++++++++++++---- core/src/qrcode/QRVersion.cpp | 14 +- core/src/qrcode/QRVersion.h | 2 +- test/blackbox/BlackboxTestRunner.cpp | 18 +-- test/samples/qrcode-2/high-res-1.jpg | Bin 0 -> 44158 bytes test/samples/qrcode-2/high-res-1.txt | 2 + 10 files changed, 273 insertions(+), 72 deletions(-) create mode 100644 test/samples/qrcode-2/high-res-1.jpg create mode 100644 test/samples/qrcode-2/high-res-1.txt diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index b13c0eebf5..3033cd28ea 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -39,8 +39,9 @@ std::optional CenterOfDoubleCross(const BitMatrix& image, PointI center, std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle) { - // range is the approximate width/height of the nth ring, if nth>1 then we can savely reduce the expected radius - int radius = nth > 1 && requireCircle ? range * 2 / 3 : range; + // 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; log(center, 3); BitMatrixCursorI cur(image, center, {0, 1}); if (!cur.stepToEdge(nth, radius)) diff --git a/core/src/GridSampler.cpp b/core/src/GridSampler.cpp index 9bc3490212..845c40ac0f 100644 --- a/core/src/GridSampler.cpp +++ b/core/src/GridSampler.cpp @@ -19,46 +19,61 @@ LogMatrix log; #endif DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix) +{ + return SampleGrid(image, width, height, {ROI{0, width, 0, height, mod2Pix}}); +} + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const ROIs& rois) { #ifdef PRINT_DEBUG LogMatrix log; static int i = 0; LogMatrixWriter lmw(log, image, 5, "grid" + std::to_string(i++) + ".pnm"); #endif - if (width <= 0 || height <= 0 || !mod2Pix.isValid()) + if (width <= 0 || height <= 0) return {}; - // To deal with remaining examples (see #251 and #267) of "numercial instabilities" that have not been - // prevented with the Quadrilateral.h:IsConvex() check, we check for all boundary points of the grid to - // be inside. - auto isInside = [&](PointI p) { return image.isIn(mod2Pix(centered(p))); }; - for (int y = 0; y < height; ++y) - if (!isInside({0, y}) || !isInside({width - 1, y})) - return {}; - for (int x = 1; x < width - 1; ++x) - if (!isInside({x, 0}) || !isInside({x, height - 1})) - return {}; + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) { + // To deal with remaining examples (see #251 and #267) of "numercial instabilities" that have not been + // prevented with the Quadrilateral.h:IsConvex() check, we check for all boundary points of the grid to + // be inside. + auto isInside = [&mod2Pix = mod2Pix, &image](PointI p) { return image.isIn(mod2Pix(centered(p))); }; + for (int y = y0; y < y1; ++y) + if (!isInside({x0, y}) || !isInside({x1 - 1, y})) + return {}; + for (int x = x0; x < x1; ++x) + if (!isInside({x, y0}) || !isInside({x, y1 - 1})) + return {}; + } BitMatrix res(width, height); - for (int y = 0; y < height; ++y) - for (int x = 0; x < width; ++x) { - auto p = mod2Pix(centered(PointI{x, y})); + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) { + for (int y = y0; y < y1; ++y) + for (int x = x0; x < x1; ++x) { + auto p = mod2Pix(centered(PointI{x, y})); #ifdef PRINT_DEBUG - log(p, 3); + log(p, 3); #endif - if (image.get(p)) - res.set(x, y); - } + if (image.get(p)) + res.set(x, y); + } + } #ifdef PRINT_DEBUG printf("width: %d, height: %d\n", width, height); - printf("%s", ToString(res).c_str()); +// printf("%s", ToString(res).c_str()); #endif - auto projectCorner = [&](PointI p) { return PointI(mod2Pix(PointF(p)) + PointF(0.5, 0.5)); }; - return { - std::move(res), - {projectCorner({0, 0}), projectCorner({width, 0}), projectCorner({width, height}), projectCorner({0, height})}}; -} + auto projectCorner = [&](PointI p) { + for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) + if (x0 <= p.x && p.x <= x1 && y0 <= p.y && p.y <= y1) + return PointI(mod2Pix(PointF(p)) + PointF(0.5, 0.5)); + + return PointI(); + }; + + return {std::move(res), + {projectCorner({0, 0}), projectCorner({width, 0}), projectCorner({width, height}), projectCorner({0, height})}}; + } } // ZXing diff --git a/core/src/GridSampler.h b/core/src/GridSampler.h index 8fb4eea6a9..654b866024 100644 --- a/core/src/GridSampler.h +++ b/core/src/GridSampler.h @@ -38,4 +38,21 @@ namespace ZXing { */ DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const PerspectiveTransform& mod2Pix); +template +Quadrilateral Rectangle(int x0, int x1, int y0, int y1, typename PointT::value_t o = 0.5) +{ + return {PointT{x0 + o, y0 + o}, {x1 + o, y0 + o}, {x1 + o, y1 + o}, {x0 + o, y1 + o}}; +} + +class ROI +{ +public: + int x0, x1, y0, y1; + PerspectiveTransform mod2Pix; +}; + +using ROIs = std::vector; + +DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const ROIs& rois); + } // ZXing diff --git a/core/src/Matrix.h b/core/src/Matrix.h index bbbd6e139c..dee6895d46 100644 --- a/core/src/Matrix.h +++ b/core/src/Matrix.h @@ -78,16 +78,16 @@ class Matrix return operator()(x, y); } - void set(int x, int y, value_t value) { - operator()(x, y) = value; + value_t& set(int x, int y, value_t value) { + return operator()(x, y) = value; } const value_t& get(PointI p) const { return operator()(p.x, p.y); } - void set(PointI p, value_t value) { - operator()(p.x, p.y) = value; + value_t& set(PointI p, value_t value) { + return operator()(p.x, p.y) = value; } const value_t* data() const { diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index a27cc84fd5..4e04a40883 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -29,6 +29,10 @@ #include #include +#ifndef PRINT_DEBUG +#define printf(...){} +#endif + namespace ZXing::QRCode { constexpr auto PATTERN = FixedPattern<5, 7>{1, 1, 3, 1, 1}; @@ -127,9 +131,14 @@ FinderPatternSets GenerateFinderPatternSets(FinderPatterns& patterns) auto distAB = std::sqrt(distAB2); auto distBC = std::sqrt(distBC2); + // Make sure distAB and distBC don't differ more than reasonable + // TODO: make sure the constant 2 is not to conservative for reasonably tilted symbols + if (distAB > 2 * distBC || distBC > 2 * distAB) + continue; + // Estimate the module count and ignore this set if it can not result in a valid decoding if (auto moduleCount = (distAB + distBC) / (2 * (a->size + b->size + c->size) / (3 * 7.f)) + 7; - moduleCount < 21 * 0.9 || moduleCount > 177 * 1.05) + moduleCount < 21 * 0.9 || moduleCount > 177 * 1.5) // moduleCount may be overestimated, see above continue; // Make sure the angle between AB and BC does not deviate from 90° by more than 45° @@ -257,6 +266,64 @@ static double EstimateTilt(const FinderPatternSet& fp) return double(max) / min; } +static PerspectiveTransform Mod2Pix(int dimension, PointF brOffset, QuadrilateralF pix) +{ + auto quad = Rectangle(dimension, dimension, 3.5); + quad[2] = quad[2] - brOffset; + return {quad, pix}; +} + +static std::optional LocateAlignmentPattern(const BitMatrix& image, int moduleSize, PointF estimate) +{ + log(estimate, 2); + + for (auto d : {PointF{0, 0}, {0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, +#if 1 + }) { +#else + {0, -2}, {0, 2}, {-2, 0}, {2, 0}, {-1, -2}, {1, -2}, {-1, 2}, {1, 2}, {-2, -1}, {-2, 1}, {2, -1}, {2, 1}}) { +#endif + auto cor = CenterOfRing(image, PointI(estimate + moduleSize * 2.25 * d), moduleSize * 3, 1, false); + + // if we did not land on a black pixel the concentric pattern finder will fail + if (!cor || !image.get(*cor)) + continue; + + if (auto cor1 = CenterOfRing(image, PointI(*cor), moduleSize, 1)) + if (auto cor2 = CenterOfRing(image, PointI(*cor), moduleSize * 3, 2)) + if (distance(*cor1, *cor2) < moduleSize / 2) { + auto res = (*cor1 + *cor2) / 2; + log(res, 3); + return res; + } + } + + return {}; +} + +static const Version* ReadVersion(const BitMatrix& image, int dimension, const PerspectiveTransform& mod2Pix) +{ + int bits[2] = {}; + + 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) { + auto mod = mirror ? PointI{y, x} : PointI{x, y}; + auto pix = mod2Pix(centered(mod)); + if (!image.isIn(pix)) + versionBits = -1; + else + AppendBit(versionBits, image.get(pix)); + log(pix, 3); + } + bits[static_cast(mirror)] = versionBits; + } + + return Version::DecodeVersionInformation(bits[0], bits[1]); +} + DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) { auto top = EstimateDimension(image, fp.tl, fp.tr); @@ -269,13 +336,8 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) int dimension = best.dim; int moduleSize = static_cast(best.ms + 1); - auto quad = Rectangle(dimension, dimension, 3.5); - - auto sample = [&](PointF br, PointF quad2) { - log(br, 3); - quad[2] = quad2; - return SampleGrid(image, dimension, dimension, {quad, {fp.tl, fp.tr, br, fp.bl}}); - }; + auto br = PointF{-1, -1}; + auto brOffset = PointF{3, 3}; // Everything except version 1 (21 modules) has an alignment pattern. Estimate the center of that by intersecting // line extensions of the 1 module wide square around the finder patterns. This could also help with detecting @@ -293,29 +355,131 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) auto brInter = (intersect(bl2, tr2) + intersect(bl3, tr3)) / 2; log(brInter, 3); - // check that the estimated alignment pattern position is inside of the image - if (image.isIn(PointI(brInter), 3 * moduleSize)) { - if (dimension > 21) { - // just in case we landed outside of the central black module of the alignment pattern, use the center - // of the next best circle (either outer or inner edge of the white part of the alignment pattern) - auto brCoR = CenterOfRing(image, PointI(brInter), moduleSize * 4, 1, false).value_or(brInter); - // if we did not land on a black pixel or the concentric pattern finder fails, - // leave the intersection of the lines as the best guess - if (image.get(brCoR)) { - if (auto brCP = LocateConcentricPattern(image, FixedPattern<3, 3>{1, 1, 1}, brCoR, moduleSize * 3 * 2)) - return sample(*brCP, quad[2] - PointF(3, 3)); + if (dimension > 21) + if (auto brCP = LocateAlignmentPattern(image, moduleSize, brInter)) + br = *brCP; + + // if the symbol is tilted or the resolution of the RegressionLines is sufficient, use their intersection + // as the best estimate (see discussion in #199 and test image estimate-tilt.jpg ) + if (!image.isIn(br) && (EstimateTilt(fp) > 1.1 || (bl2.isHighRes() && bl3.isHighRes() && tr2.isHighRes() && tr3.isHighRes()))) + br = brInter; + } + + // otherwise the simple estimation used by upstream is used as a best guess fallback + if (!image.isIn(br)) { + br = fp.tr - fp.tl + fp.bl; + brOffset = PointF(0, 0); + } + + log(br, 3); + auto mod2Pix = Mod2Pix(dimension, brOffset, {fp.tl, fp.tr, br, fp.bl}); + + if( dimension >= Version::DimensionOfVersion(7, false)) { + auto version = ReadVersion(image, dimension, mod2Pix); + + // if the version bits are garbage -> discard the detection + if (!version || std::abs(version->dimension() - dimension) > 8) + return DetectorResult(); + if (version->dimension() != dimension) { + printf("update dimension: %d -> %d\n", dimension, version->dimension()); + dimension = version->dimension(); + mod2Pix = Mod2Pix(dimension, brOffset, {fp.tl, fp.tr, br, fp.bl}); + } +#if 1 + auto& apM = version->alignmentPatternCenters(); // alignment pattern positions in modules + auto apP = Matrix>(Size(apM), Size(apM)); // found/guessed alignment pattern positions in pixels + const int N = Size(apM) - 1; + + // project the alignment pattern at module coordinates x/y to pixel coordinate based on current mod2Pix + auto projectM2P = [&mod2Pix, &apM](int x, int y) { return mod2Pix(centered(PointI(apM[x], apM[y]))); }; + + auto findInnerCornerOfConcentricPattern = [&image, &apP, &projectM2P](int x, int y, const ConcentricPattern& fp) { + auto pc = *apP.set(x, y, projectM2P(x, y)); + if (auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 2)) + for (auto c : *fpQuad) + if (distance(c, pc) < fp.size / 2) + apP.set(x, y, c); + }; + + findInnerCornerOfConcentricPattern(0, 0, fp.tl); + findInnerCornerOfConcentricPattern(0, N, fp.bl); + findInnerCornerOfConcentricPattern(N, 0, fp.tr); + + auto bestGuessAPP = [&](int x, int y){ + if (auto p = apP(x, y)) + return *p; + return projectM2P(x, y); + }; + + for (int y = 0; y <= N; ++y) + for (int x = 0; x <= N; ++x) { + if (apP(x, y)) + continue; + + PointF guessed = + x * y == 0 ? bestGuessAPP(x, y) : bestGuessAPP(x - 1, y) + bestGuessAPP(x, y - 1) - bestGuessAPP(x - 1, y - 1); + if (auto found = LocateAlignmentPattern(image, moduleSize, guessed)) + apP.set(x, y, *found); + } + + // go over the whole set of alignment patters again and try to fill any remaining gap by using available neighbors as guides + for (int y = 0; y <= N; ++y) + for (int x = 0; x <= N; ++x) { + if (apP(x, y)) + continue; + + // find the two closest valid alignment pattern pixel positions both horizontally and vertically + std::vector hori, verti; + for (int i = 2; i < 2 * N && Size(hori) < 2; ++i) { + int xi = x + i / 2 * (i%2 ? 1 : -1); + if (0 <= xi && xi <= N && apP(xi, y)) + hori.push_back(*apP(xi, y)); + } + for (int i = 2; i < 2 * N && Size(verti) < 2; ++i) { + int yi = y + i / 2 * (i%2 ? 1 : -1); + if (0 <= yi && yi <= N && apP(x, yi)) + verti.push_back(*apP(x, yi)); + } + + // if we found 2 each, intersect the two lines that are formed by connecting the point pairs + if (Size(hori) == 2 && Size(verti) == 2) { + auto guessed = intersect(RegressionLine(hori[0], hori[1]), RegressionLine(verti[0], verti[1])); + auto found = LocateAlignmentPattern(image, moduleSize, guessed); + // search again near that intersection and if the search fails, use the intersection + if (!found) printf("location guessed at %dx%d\n", x, y); + apP.set(x, y, found ? *found : guessed); } } - // if the symbol is tilted or the resolution of the RegressionLines is sufficient, use their intersection - // as the best estimate (see discussion in #199 and test image estimate-tilt.jpg ) - if (EstimateTilt(fp) > 1.1 || (bl2.isHighRes() && bl3.isHighRes() && tr2.isHighRes() && tr3.isHighRes())) - return sample(brInter, quad[2] - PointF(3, 3)); - } + if (auto c = apP.get(N, N)) + mod2Pix = Mod2Pix(dimension, PointF(3, 3), {fp.tl, fp.tr, *c, fp.bl}); + + // go over the whole set of alignment patters again and fill any remaining gaps by a projection based on an updated mod2Pix + // projection. This works if the symbol is flat, wich is a reasonable fall-back assumption + for (int y = 0; y <= N; ++y) + for (int x = 0; x <= N; ++x) { + if (apP(x, y)) + continue; + + printf("locate failed at %dx%d\n", x, y); + apP.set(x, y, projectM2P(x, y)); + } + + // assemble a list of region-of-interests based on the found alignment pattern pixel positions + ROIs rois; + for (int y = 0; y < N; ++y) + for (int x = 0; x < N; ++x) { + int x0 = apM[x], x1 = apM[x + 1], y0 = apM[y], y1 = apM[y + 1]; + rois.push_back({x0 - (x == 0) * 6, x1 + (x == N - 1) * 7, y0 - (y == 0) * 6, y1 + (y == N - 1) * 7, + PerspectiveTransform{Rectangle(x0, x1, y0, y1), + {*apP(x, y), *apP(x + 1, y), *apP(x + 1, y + 1), *apP(x, y + 1)}}}); + } + + return SampleGrid(image, dimension, dimension, rois); +#endif } - // otherwise the simple estimation used by upstream is used as a best guess fallback - return sample(fp.tr - fp.tl + fp.bl, quad[2]); + return SampleGrid(image, dimension, dimension, mod2Pix); } /** diff --git a/core/src/qrcode/QRVersion.cpp b/core/src/qrcode/QRVersion.cpp index 90c698febe..2f202b654d 100644 --- a/core/src/qrcode/QRVersion.cpp +++ b/core/src/qrcode/QRVersion.cpp @@ -323,22 +323,24 @@ const Version* Version::FromDimension(int dimension) return FromNumber((dimension - DimensionOffset(isMicro)) / DimensionStep(isMicro), isMicro); } -const Version* Version::DecodeVersionInformation(int versionBits) +const Version* Version::DecodeVersionInformation(int versionBitsA, int versionBitsB) { int bestDifference = std::numeric_limits::max(); int bestVersion = 0; int i = 0; for (int targetVersion : VERSION_DECODE_INFO) { // Do the version info bits match exactly? done. - if (targetVersion == versionBits) { + if (targetVersion == versionBitsA || targetVersion == versionBitsB) { return FromNumber(i + 7); } // Otherwise see if this is the closest to a real version info bit string // we have seen so far - int bitsDifference = BitHacks::CountBitsSet(versionBits ^ targetVersion); - if (bitsDifference < bestDifference) { - bestVersion = i + 7; - bestDifference = bitsDifference; + for (int bits : {versionBitsA, versionBitsB}) { + int bitsDifference = BitHacks::CountBitsSet(bits ^ targetVersion); + if (bitsDifference < bestDifference) { + bestVersion = i + 7; + bestDifference = bitsDifference; + } } ++i; } diff --git a/core/src/qrcode/QRVersion.h b/core/src/qrcode/QRVersion.h index 17a1f111e0..0b03270011 100644 --- a/core/src/qrcode/QRVersion.h +++ b/core/src/qrcode/QRVersion.h @@ -58,7 +58,7 @@ class Version static const Version* FromNumber(int versionNumber, bool isMicro = false); - static const Version* DecodeVersionInformation(int versionBits); + static const Version* DecodeVersionInformation(int versionBitsA, int versionBitsB = 0); private: int _versionNumber; diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index e3be449ae8..d4ef7a0859 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -566,19 +566,19 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 16, 16, 270 }, }); - runTests("qrcode-2", "QRCode", 48, { - { 44, 46, 0 }, - { 44, 46, 90 }, - { 44, 46, 180 }, - { 44, 45, 270 }, + runTests("qrcode-2", "QRCode", 49, { + { 45, 47, 0 }, + { 45, 47, 90 }, + { 45, 47, 180 }, + { 45, 46, 270 }, { 21, 1, pure }, // the misread is the 'outer' symbol in 16.png }); runTests("qrcode-3", "QRCode", 28, { - { 25, 25, 0 }, - { 25, 25, 90 }, - { 25, 25, 180 }, - { 24, 24, 270 }, + { 28, 28, 0 }, + { 28, 28, 90 }, + { 28, 28, 180 }, + { 27, 27, 270 }, }); runTests("qrcode-4", "QRCode", 41, { diff --git a/test/samples/qrcode-2/high-res-1.jpg b/test/samples/qrcode-2/high-res-1.jpg new file mode 100644 index 0000000000000000000000000000000000000000..69efb1f5e85ebd690556321c40d5567d5c804323 GIT binary patch literal 44158 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 contains information about the item to which it is attached. In practice, QR codes often contain data for a locator, identifier, or trac +QR code (abbreviated from Quick Response Code) is the trademark for a type of matrix barcode (or two-dimensional barcode) first designed in 1994 for the automotive industry in Japan.[1] A barcode is a machine-readable optical label that contains information about the item to which it is attached. In practice, QR codes often contain data for a locator, identifier, or tracker that points to a website or application. A QR code uses four standardized encoding modes (numeric, alphanumeric, byte/binary, and kanji) to store data efficiently; extensions may also be used.[2]sdfgksjdflkgjsdkfgiotmbx,cmvbofghjoaasdfaERYYKLLDFGSDFFFFFFFFFFFFFFFFFFFFFFFFDDDDDDDDDSVFB094856JLKSJFGS0DBIUZKL;KSFDF09846JLKSDNFGBLDKSFBHJ0SP98ASDFKthat contains information about the item to which it is attached. In practice, QR codes often contain data for a locator, identifier, or trac \ No newline at end of file From 69b89a3a3d398f8445f14973e11985888c59e596 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 15 Feb 2023 22:23:02 +0100 Subject: [PATCH 056/173] README: added paragraph about sponsorship --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 4c98dc4c70..ab46199544 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,16 @@ ZXing-C++ ("zebra crossing") is an open-source, multi-format linear/matrix barco It was originally ported from the Java [ZXing Library](https://github.com/zxing/zxing) but has been developed further and now includes many improvements in terms of quality and performance. It can both read and write barcodes in a number of formats. +## Sponsors + +You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/axxel). + +Named Sponsors: +* [Useful Sensors Inc](https://github.com/usefulsensors) +* [Sergio Olivo](https://github.com/sergio-) + +Thanks a lot for your contribution! + ## Features * Written in pure C++17 (/C++20), no third-party dependencies (for the library itself) From accced21bae23320aad47b295de1085ab4e561b5 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 15 Feb 2023 23:19:05 +0100 Subject: [PATCH 057/173] cmake: cleanup msvc related build configuration fix #502 --- CMakeLists.txt | 13 +++---------- README.md | 2 +- core/CMakeLists.txt | 4 ++-- wrappers/python/CMakeLists.txt | 2 +- 4 files changed, 7 insertions(+), 14 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index ab3dcafca2..893d77343c 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.14) +cmake_minimum_required(VERSION 3.15) project(ZXing) @@ -17,17 +17,10 @@ else() endif() if (MSVC) - option (LINK_CPP_STATICALLY "MSVC only, link standard library statically (/MT and /MTd)" OFF) - add_definitions (-DUNICODE -D_UNICODE -D_CRT_SECURE_NO_WARNINGS) - set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /MP") - set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /Oi /GS-") - set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /Oi /GS-") + option (LINK_CPP_STATICALLY "MSVC only, link standard library statically (/MT and /MTd)" OFF) if (LINK_CPP_STATICALLY) - set (CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT") - set (CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT") - set (CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd") - set (CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd") + set(CMAKE_MSVC_RUNTIME_LIBRARY "MultiThreaded$<$:Debug>") endif() endif() diff --git a/README.md b/README.md index ab46199544..97ec286a80 100644 --- a/README.md +++ b/README.md @@ -67,7 +67,7 @@ As an example, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). ## Build Instructions These are the generic instructions to build the library on Windows/macOS/Linux. For details on how to build the individual wrappers, follow the links above. -1. Make sure [CMake](https://cmake.org) version 3.14 or newer is installed. +1. Make sure [CMake](https://cmake.org) version 3.15 or newer is installed. 2. Make sure a C++17 compliant compiler is installed (minimum VS 2019 16.8 / gcc 7 / clang 5). 3. See the cmake `BUILD_...` options to enable the testing code, python wrapper, etc. diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index cf3e7a4e20..755cb09771 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required (VERSION 3.14) +cmake_minimum_required(VERSION 3.15) project (ZXing VERSION "2.0.0") set (ZXING_SONAME 3) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 @@ -515,7 +515,7 @@ install ( DESTINATION "${CMAKE_INSTALL_INCLUDEDIR}/ZXing" ) -if(MSVC) +if (MSVC) set_target_properties(ZXing PROPERTIES COMPILE_PDB_NAME ZXing COMPILE_PDB_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 5eb7a618ab..58d4d49673 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.14) +cmake_minimum_required(VERSION 3.15) project(ZXingPython) set (pybind11_git_repo https://github.com/pybind/pybind11.git) From d58df842f9bbb9ffbb997388fe0eda05e225f97b Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 16 Feb 2023 11:22:54 +0100 Subject: [PATCH 058/173] cmake: build with MSVC option /Zc:__cplusplus See https://developercommunity.visualstudio.com/t/-cplusplus-always-199711/1445222#TPIN-N1445753. Fixes #515. --- core/CMakeLists.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 755cb09771..37d9026c83 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -34,6 +34,7 @@ if (MSVC) -D_CRT_SECURE_NO_WARNINGS -D_CRT_NONSTDC_NO_WARNINGS -DNOMINMAX + /Zc:__cplusplus ) else() set (ZXING_CORE_LOCAL_DEFINES ${ZXING_CORE_LOCAL_DEFINES} From dad831d92231a83b7204ee9be6d681bbff069697 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 16 Feb 2023 15:44:47 +0100 Subject: [PATCH 059/173] cmake: allow specifying CMAKE_CXX_STANDARD from the command-line fixes #518. thanks to @enwillyado. --- CMakeLists.txt | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 893d77343c..6352a0bc92 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -35,8 +35,12 @@ if (BUILD_SHARED_LIBS) set (CMAKE_WINDOWS_EXPORT_ALL_SYMBOLS ON) endif() -set (CMAKE_CXX_STANDARD 17) -set (CMAKE_CXX_EXTENSIONS OFF) +if (NOT CMAKE_CXX_STANDARD) + set (CMAKE_CXX_STANDARD 17) +endif() +if (NOT CMAKE_CXX_EXTENSIONS) + set (CMAKE_CXX_EXTENSIONS OFF) +endif() if (NOT (BUILD_READERS OR BUILD_WRITERS)) message(FATAL_ERROR "At least one of BUILD_READERS/BUILD_WRITERS must be enabled.") From 13764ba9bf781a60f0ca86caddc2ee63876e94d2 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 16 Feb 2023 17:20:49 +0100 Subject: [PATCH 060/173] DataBar: change the EstimateLineCount to return 1 iff symbol is stacked This fixes #515 and a bunch of related errors when unifying results that belong to the same symbol. It also improves the position detection for DataBar. --- core/src/oned/ODDataBarCommon.cpp | 12 +++++++++--- test/blackbox/BlackboxTestRunner.cpp | 4 ++-- test/unit/ThresholdBinarizerTest.cpp | 1 + 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/core/src/oned/ODDataBarCommon.cpp b/core/src/oned/ODDataBarCommon.cpp index 3a7f592431..937f350005 100644 --- a/core/src/oned/ODDataBarCommon.cpp +++ b/core/src/oned/ODDataBarCommon.cpp @@ -145,10 +145,16 @@ bool ReadDataCharacterRaw(const PatternView& view, int numModules, bool reversed #endif } +static bool IsStacked(const Pair& first, const Pair& last) +{ + // check if we see two halfes that are far away from each other in y or overlapping in x + return std::abs(first.y - last.y) > (first.xStop - first.xStart) || last.xStart < (first.xStart + first.xStop) / 2; +} + Position EstimatePosition(const Pair& first, const Pair& last) { - if (first.y == last.y) - return Line(first.y, first.xStart, last.xStop); + if (!IsStacked(first, last)) + return Line((first.y + last.y) / 2, first.xStart, last.xStop); else return Position{{first.xStart, first.y}, {first.xStop, first.y}, {last.xStop, last.y}, {last.xStart, last.y}}; } @@ -156,7 +162,7 @@ Position EstimatePosition(const Pair& first, const Pair& last) int EstimateLineCount(const Pair& first, const Pair& last) { // see incrementLineCount() in ODReader.cpp for the -1 here - return std::min(first.count, last.count) * ((first.y == last.y) ? 1 : 2) - 1; + return std::min(first.count, last.count) - 1 + IsStacked(first, last); } } // namespace ZXing::OneD::DataBar diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index d4ef7a0859..c503d38b53 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -549,8 +549,8 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set }); runTests("rssexpandedstacked-1", "DataBarExpanded", 65, { - { 60, 65, 0 }, - { 60, 65, 180 }, + { 55, 65, 0 }, + { 55, 65, 180 }, { 60, 0, pure }, }); diff --git a/test/unit/ThresholdBinarizerTest.cpp b/test/unit/ThresholdBinarizerTest.cpp index 13ff0e5823..d49e51d9cf 100644 --- a/test/unit/ThresholdBinarizerTest.cpp +++ b/test/unit/ThresholdBinarizerTest.cpp @@ -95,6 +95,7 @@ TEST(ThresholdBinarizerTest, PatternRowClear) bits = ParseBitMatrix(bitstream, 53 /*width*/); hints.setFormats(BarcodeFormat::DataBarExpanded); + hints.setMinLineCount(1); OneD::Reader reader(hints); Result result = reader.decode(ThresholdBinarizer(getImageView(buf, bits), 0x7F)); From 8fe95e2f71eeb0d01646a44e1a2f17a7ee853652 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 16 Feb 2023 17:36:11 +0100 Subject: [PATCH 061/173] c++: remove a bunch of unused #include statements --- core/src/BitMatrixIO.cpp | 2 -- core/src/DecoderResult.h | 2 -- core/src/pdf417/PDFDecodedBitStreamParser.cpp | 2 +- core/src/pdf417/PDFReader.cpp | 2 -- test/blackbox/BlackboxTestRunner.cpp | 4 ---- test/unit/CharacterSetECITest.cpp | 1 - test/unit/ContentTest.cpp | 1 - 7 files changed, 1 insertion(+), 13 deletions(-) diff --git a/core/src/BitMatrixIO.cpp b/core/src/BitMatrixIO.cpp index 36d8d83eb4..685e9cf236 100644 --- a/core/src/BitMatrixIO.cpp +++ b/core/src/BitMatrixIO.cpp @@ -6,8 +6,6 @@ #include "BitMatrixIO.h" -#include "BitArray.h" - #include #include diff --git a/core/src/DecoderResult.h b/core/src/DecoderResult.h index 947c959dc7..02b2850841 100644 --- a/core/src/DecoderResult.h +++ b/core/src/DecoderResult.h @@ -6,11 +6,9 @@ #pragma once -#include "ByteArray.h" #include "Content.h" #include "Error.h" #include "StructuredAppend.h" -#include "ZXAlgorithms.h" #include #include diff --git a/core/src/pdf417/PDFDecodedBitStreamParser.cpp b/core/src/pdf417/PDFDecodedBitStreamParser.cpp index 4a2574535c..66b9224779 100644 --- a/core/src/pdf417/PDFDecodedBitStreamParser.cpp +++ b/core/src/pdf417/PDFDecodedBitStreamParser.cpp @@ -6,10 +6,10 @@ #include "PDFDecodedBitStreamParser.h" -#include "ByteArray.h" #include "CharacterSet.h" #include "DecoderResult.h" #include "PDFDecoderResultExtra.h" +#include "ZXAlgorithms.h" #include "ZXBigInteger.h" #include "ZXTestSupport.h" diff --git a/core/src/pdf417/PDFReader.cpp b/core/src/pdf417/PDFReader.cpp index d901ce0631..c33c3698a9 100644 --- a/core/src/pdf417/PDFReader.cpp +++ b/core/src/pdf417/PDFReader.cpp @@ -25,8 +25,6 @@ #include #ifdef PRINT_DEBUG -#include "PDFDecoderResultExtra.h" -#include "PDFDecodedBitStreamParser.h" #include "BitMatrixIO.h" #include #endif diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index c503d38b53..dd9b28b74a 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -6,14 +6,10 @@ #include "BlackboxTestRunner.h" -#include "DecoderResult.h" #include "ImageLoader.h" #include "ReadBarcode.h" -#include "ThresholdBinarizer.h" #include "Utf.h" #include "ZXAlgorithms.h" -#include "pdf417/PDFReader.h" -#include "qrcode/QRReader.h" #include #include diff --git a/test/unit/CharacterSetECITest.cpp b/test/unit/CharacterSetECITest.cpp index e34dd9640f..39888622a4 100644 --- a/test/unit/CharacterSetECITest.cpp +++ b/test/unit/CharacterSetECITest.cpp @@ -7,7 +7,6 @@ #include "ECI.h" #include "gtest/gtest.h" -#include "gmock/gmock.h" using namespace ZXing; using namespace testing; diff --git a/test/unit/ContentTest.cpp b/test/unit/ContentTest.cpp index 982c3d6ffd..1bdb9a8d3b 100644 --- a/test/unit/ContentTest.cpp +++ b/test/unit/ContentTest.cpp @@ -7,7 +7,6 @@ #include "ECI.h" #include "gtest/gtest.h" -#include "gmock/gmock.h" using namespace ZXing; using namespace testing; From 0f368c2f2a153c228ea5a1850863802bd679247c Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 17 Feb 2023 11:12:11 +0100 Subject: [PATCH 062/173] PDF417: add missing check for calculatedNumberOfCodewords plausibility The value was checked in one control path but not the other. This fixes #517. --- core/src/pdf417/PDFScanningDecoder.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/core/src/pdf417/PDFScanningDecoder.cpp b/core/src/pdf417/PDFScanningDecoder.cpp index 7d85c77297..3a612277c2 100644 --- a/core/src/pdf417/PDFScanningDecoder.cpp +++ b/core/src/pdf417/PDFScanningDecoder.cpp @@ -341,13 +341,14 @@ static bool AdjustCodewordCount(const DetectionResult& detectionResult, std::vec { auto numberOfCodewords = barcodeMatrix[0][1].value(); int calculatedNumberOfCodewords = detectionResult.barcodeColumnCount() * detectionResult.barcodeRowCount() - GetNumberOfECCodeWords(detectionResult.barcodeECLevel()); + if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > CodewordDecoder::MAX_CODEWORDS_IN_BARCODE) + calculatedNumberOfCodewords = 0; if (numberOfCodewords.empty()) { - if (calculatedNumberOfCodewords < 1 || calculatedNumberOfCodewords > CodewordDecoder::MAX_CODEWORDS_IN_BARCODE) { + if (!calculatedNumberOfCodewords) return false; - } barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords); } - else if (numberOfCodewords[0] != calculatedNumberOfCodewords) { + else if (calculatedNumberOfCodewords && numberOfCodewords[0] != calculatedNumberOfCodewords) { // The calculated one is more reliable as it is derived from the row indicator columns barcodeMatrix[0][1].setValue(calculatedNumberOfCodewords); } From fb0b0ee7d90caddabf6e12f5eb5059271630a67d Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 20 Feb 2023 00:19:14 +0100 Subject: [PATCH 063/173] Quadrilateral: add Blend() helper function to blend 2 Quadrilaterals --- core/src/ConcentricFinder.cpp | 8 +------- core/src/Quadrilateral.h | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index 3033cd28ea..a9157d9b6f 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -201,13 +201,7 @@ std::optional FindConcentricPatternCorners(const BitMatrix& imag auto& innerCorners = *oInnerCorners; auto& outerCorners = *oOuterCorners; - auto dist2First = [c = innerCorners[0]](auto a, auto b) { return distance(a, c) < distance(b, c); }; - // rotate points such that the the two topLeft points are closest to each other - std::rotate(outerCorners.begin(), std::min_element(outerCorners.begin(), outerCorners.end(), dist2First), outerCorners.end()); - - QuadrilateralF res; - for (int i=0; i<4; ++i) - res[i] = (innerCorners[i] + outerCorners[i]) / 2; + auto res = Blend(innerCorners, outerCorners); for (auto p : innerCorners) log(p, 3); diff --git a/core/src/Quadrilateral.h b/core/src/Quadrilateral.h index 85069c8235..b76f7fa882 100644 --- a/core/src/Quadrilateral.h +++ b/core/src/Quadrilateral.h @@ -140,5 +140,19 @@ bool HaveIntersectingBoundingBoxes(const Quadrilateral& a, const Quadril return !(x || y); } +template +Quadrilateral Blend(const Quadrilateral& a, const Quadrilateral& b) +{ + auto dist2First = [c = a[0]](auto a, auto b) { return distance(a, c) < distance(b, c); }; + // rotate points such that the the two topLeft points are closest to each other + auto offset = std::min_element(b.begin(), b.end(), dist2First) - b.begin(); + + Quadrilateral res; + for (int i = 0; i < 4; ++i) + res[i] = (a[i] + b[(i + offset) % 4]) / 2; + + return res; +} + } // ZXing From 6c194b85a5ddf2a33603a81e600d6ac3daebb50a Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 20 Feb 2023 00:20:05 +0100 Subject: [PATCH 064/173] Aztec: slightly relax the center pattern constraint for low-res input --- core/src/aztec/AZDetector.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index fc1e2b4b0e..f962086741 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -26,7 +26,7 @@ namespace ZXing::Aztec { -static bool IsAztectCenterPattern(const PatternView& view) +static bool IsAztecCenterPattern(const PatternView& view) { // find min/max of all subsequent black/white pairs and check that they 'close together' auto m = view[0] + view[1]; @@ -38,7 +38,7 @@ static bool IsAztectCenterPattern(const PatternView& view) else if (v > M) M = v; } - return M <= m * 4 / 3 && view[-1] >= view[Size(view) / 2] - 2 && view[Size(view)] >= view[Size(view) / 2] - 2; + return M <= m * 4 / 3 + 1 && view[-1] >= view[Size(view) / 2] - 2 && view[Size(view)] >= view[Size(view) / 2] - 2; }; // specialized version of FindLeftGuard to find the '1,1,1,1,1,1,1' pattern of a compact Aztec center pattern @@ -47,7 +47,7 @@ static PatternView FindAztecCenterPattern(const PatternView& view) constexpr int minSize = 8; // Aztec runes auto window = view.subView(0, 7); for (auto end = view.end() - minSize; window.data() < end; window.skipPair()) - if (IsAztectCenterPattern(window)) + if (IsAztecCenterPattern(window)) return window; return {}; @@ -90,7 +90,7 @@ static int CheckDirection(BitMatrixCursorF& cur, PointF dir, int range, bool upd m = v; else if (v > M) M = v; - if (M > m * 4 / 3) + if (M > m * 4 / 3 + 1) return 0; spread += s; lastS = s; From 42a4aa786bbd87e5f9177c0a3f1f126d1ae3d5cc Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 21 Feb 2023 09:53:41 +0100 Subject: [PATCH 065/173] BinaryBitmap: revert performance regression In 983b3ad187cd2a6ee2bc33b9277e61a80df72eb3 I did overlook the fact that it would introduce a mandatory `getBlackMatrix` call even if we were only looking for linear symbologies. --- core/src/BinaryBitmap.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index c27dc51fc3..b9ed76e051 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -59,8 +59,10 @@ const BitMatrix* BinaryBitmap::getBitMatrix() const void BinaryBitmap::invert() { - if (auto matrix = const_cast(getBitMatrix())) + if (_cache->matrix) { + auto matrix = const_cast(_cache->matrix.get()); matrix->flipAll(); + } _inverted = true; } From 9bcfdb3637714af9419bae99dd4c48a12625ab1e Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Feb 2023 13:09:02 +0100 Subject: [PATCH 066/173] Denoise: add experimental `DecodeHints::tryDenoise` This adds a simple morphological closing filter as a means to fix noisy 2D symbols. This has been proven helpful especially in cases of detecting QRCode symbols on low print quality retail point of sale receipts. This is currently experimental code and API and may change/disappear. It is again part of the work funded by @Sergio-. --- core/src/BinaryBitmap.cpp | 30 ++++++++++++++++++++++++++++++ core/src/BinaryBitmap.h | 4 ++++ core/src/DecodeHints.h | 8 +++++++- core/src/ReadBarcode.cpp | 39 +++++++++++++++++++++++++-------------- example/ZXingReader.cpp | 1 + 5 files changed, 67 insertions(+), 15 deletions(-) diff --git a/core/src/BinaryBitmap.cpp b/core/src/BinaryBitmap.cpp index b9ed76e051..4cbe5d5ef2 100644 --- a/core/src/BinaryBitmap.cpp +++ b/core/src/BinaryBitmap.cpp @@ -66,4 +66,34 @@ void BinaryBitmap::invert() _inverted = true; } +template +void SumFilter(const BitMatrix& in, BitMatrix& out, F func) +{ + const auto* in0 = in.row(0).begin(); + const auto* in1 = in.row(1).begin(); + const auto* in2 = in.row(2).begin(); + + for (auto *out1 = out.row(1).begin() + 1, *end = out.row(out.height() - 1).begin() - 1; out1 != end; ++in0, ++in1, ++in2, ++out1) { + int sum = 0; + for (int j = 0; j < 3; ++j) + sum += in0[j] + in1[j] + in2[j]; + + *out1 = func(sum); + } +} + +void BinaryBitmap::close() +{ + if (_cache->matrix) { + auto& matrix = *const_cast(_cache->matrix.get()); + BitMatrix tmp(matrix.width(), matrix.height()); + + // dilate + SumFilter(matrix, tmp, [](int sum) { return (sum > 0 * BitMatrix::SET_V) * BitMatrix::SET_V; }); + // erode + SumFilter(tmp, matrix, [](int sum) { return (sum == 9 * BitMatrix::SET_V) * BitMatrix::SET_V; }); + } + _closed = true; +} + } // ZXing diff --git a/core/src/BinaryBitmap.h b/core/src/BinaryBitmap.h index 435d945150..1a44be09ef 100644 --- a/core/src/BinaryBitmap.h +++ b/core/src/BinaryBitmap.h @@ -28,6 +28,7 @@ class BinaryBitmap struct Cache; std::unique_ptr _cache; bool _inverted = false; + bool _closed = false; protected: const ImageView _buffer; @@ -57,6 +58,9 @@ class BinaryBitmap void invert(); bool inverted() const { return _inverted; } + + void close(); + bool closed() const { return _closed; } }; } // ZXing diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index 97069d6a8c..a296aa261e 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -63,6 +63,7 @@ class DecodeHints Binarizer _binarizer : 2; TextMode _textMode : 3; CharacterSet _characterSet : 6; + bool _tryDenoise : 1; uint8_t _minLineCount = 2; uint8_t _maxNumberOfSymbols = 0xff; @@ -86,7 +87,8 @@ class DecodeHints _eanAddOnSymbol(EanAddOnSymbol::Ignore), _binarizer(Binarizer::LocalAverage), _textMode(TextMode::HRI), - _characterSet(CharacterSet::Unknown) + _characterSet(CharacterSet::Unknown), + _tryDenoise(0) {} #define ZX_PROPERTY(TYPE, GETTER, SETTER) \ @@ -109,6 +111,10 @@ class DecodeHints /// Also try detecting code in downscaled images (depending on image size). ZX_PROPERTY(bool, tryDownscale, setTryDownscale) + /// Also try detecting code after denoising (currently morphological closing filter for 2D symbologies only). + // WARNING: this API is experimental and may change/disappear + ZX_PROPERTY(bool, tryDenoise, setTryDenoise) + /// Binarizer to use internally when using the ReadBarcode function ZX_PROPERTY(Binarizer, binarizer, setBinarizer) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 7a75861f7e..b94109df39 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -141,28 +141,39 @@ Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) if (hints.isPure()) return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; + auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode; + std::unique_ptr closedReader; + if (hints.tryDenoise() && (hints.formats().empty() || hints.formats().testFlags(formatsBenefittingFromClosing))) + closedReader = std::make_unique(DecodeHints(hints).setFormats(hints.formats() & formatsBenefittingFromClosing)); + LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); Results results; int maxSymbols = hints.maxNumberOfSymbols() ? hints.maxNumberOfSymbols() : INT_MAX; for (auto&& iv : pyramid.layers) { auto bitmap = CreateBitmap(hints.binarizer(), iv); - for (int invert = 0; invert <= static_cast(hints.tryInvert()); ++invert) { - if (invert) - bitmap->invert(); - auto rs = reader.readMultiple(*bitmap, maxSymbols); - for (auto& r : rs) { - if (iv.width() != _iv.width()) - r.setPosition(Scale(r.position(), _iv.width() / iv.width())); - if (!Contains(results, r)) { - r.setDecodeHints(hints); - r.setIsInverted(bitmap->inverted()); - results.push_back(std::move(r)); - --maxSymbols; + for (int close = 0; close <= (closedReader ? 1 : 0); ++close) { + if (close) + bitmap->close(); + + // TODO: check if closing after invert would be beneficial + for (int invert = 0; invert <= static_cast(hints.tryInvert() && !close); ++invert) { + if (invert) + bitmap->invert(); + auto rs = (close ? *closedReader : reader).readMultiple(*bitmap, maxSymbols); + for (auto& r : rs) { + if (iv.width() != _iv.width()) + r.setPosition(Scale(r.position(), _iv.width() / iv.width())); + if (!Contains(results, r)) { + r.setDecodeHints(hints); + r.setIsInverted(bitmap->inverted()); + results.push_back(std::move(r)); + --maxSymbols; + } } + if (maxSymbols <= 0) + return results; } - if (maxSymbols <= 0) - return results; } } diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index a7b1e26b6f..f7660e7f03 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -152,6 +152,7 @@ int main(int argc, char* argv[]) bool bytesOnly = false; int ret = 0; + hints.setTryDenoise(true); hints.setTextMode(TextMode::HRI); hints.setEanAddOnSymbol(EanAddOnSymbol::Read); From 1a9b9029a7bba93cd7790b7e63eb5f123b5bbc8c Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Feb 2023 13:11:42 +0100 Subject: [PATCH 067/173] QRCode: fix an off-by-one bounds error limiting the new high-res code --- core/src/qrcode/QRDetector.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 4e04a40883..3709ae0ebc 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -430,12 +430,12 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) // find the two closest valid alignment pattern pixel positions both horizontally and vertically std::vector hori, verti; - for (int i = 2; i < 2 * N && Size(hori) < 2; ++i) { + for (int i = 2; i < 2 * N + 2 && Size(hori) < 2; ++i) { int xi = x + i / 2 * (i%2 ? 1 : -1); if (0 <= xi && xi <= N && apP(xi, y)) hori.push_back(*apP(xi, y)); } - for (int i = 2; i < 2 * N && Size(verti) < 2; ++i) { + for (int i = 2; i < 2 * N + 2 && Size(verti) < 2; ++i) { int yi = y + i / 2 * (i%2 ? 1 : -1); if (0 <= yi && yi <= N && apP(x, yi)) verti.push_back(*apP(x, yi)); @@ -455,7 +455,7 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) mod2Pix = Mod2Pix(dimension, PointF(3, 3), {fp.tl, fp.tr, *c, fp.bl}); // go over the whole set of alignment patters again and fill any remaining gaps by a projection based on an updated mod2Pix - // projection. This works if the symbol is flat, wich is a reasonable fall-back assumption + // projection. This works if the symbol is flat, wich is a reasonable fall-back assumption. for (int y = 0; y <= N; ++y) for (int x = 0; x <= N; ++x) { if (apP(x, y)) From d0a1150213b5026442ae38ad9d38d279959c6f67 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Feb 2023 13:59:33 +0100 Subject: [PATCH 068/173] DecodeHints: fix stack-use-after-scope regression Also make sure this does not happen again. --- core/src/MultiFormatReader.h | 1 + core/src/ReadBarcode.cpp | 7 +++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/core/src/MultiFormatReader.h b/core/src/MultiFormatReader.h index a9022f246c..4d15d86471 100644 --- a/core/src/MultiFormatReader.h +++ b/core/src/MultiFormatReader.h @@ -30,6 +30,7 @@ class MultiFormatReader { public: explicit MultiFormatReader(const DecodeHints& hints); + explicit MultiFormatReader(DecodeHints&& hints) = delete; ~MultiFormatReader(); Result read(const BinaryBitmap& image) const; diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index b94109df39..a0abdc1f10 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -142,9 +142,12 @@ Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode; + DecodeHints closedHints = hints; std::unique_ptr closedReader; - if (hints.tryDenoise() && (hints.formats().empty() || hints.formats().testFlags(formatsBenefittingFromClosing))) - closedReader = std::make_unique(DecodeHints(hints).setFormats(hints.formats() & formatsBenefittingFromClosing)); + if (hints.tryDenoise() && hints.hasFormat(formatsBenefittingFromClosing)) { + closedHints.setFormats((hints.formats().empty() ? BarcodeFormat::Any : hints.formats()) & formatsBenefittingFromClosing); + closedReader = std::make_unique(closedHints); + } LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); From 7572412bafb0d84e59fc175a18b07ef2c4c2714a Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Feb 2023 14:24:03 +0100 Subject: [PATCH 069/173] QRCode: tune module size estimation * check for pattern validity * limit search space based on ConcentricPattern.size * allow for 1 of 2 estimations to fail --- core/src/qrcode/QRDetector.cpp | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 3709ae0ebc..8fa390fa09 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -179,31 +179,26 @@ FinderPatternSets GenerateFinderPatternSets(FinderPatterns& patterns) return res; } -static double EstimateModuleSize(const BitMatrix& image, PointF a, PointF b) +static double EstimateModuleSize(const BitMatrix& image, ConcentricPattern a, ConcentricPattern b) { BitMatrixCursorF cur(image, a, b - a); assert(cur.isBlack()); - if (!cur.stepToEdge(3, static_cast(distance(a, b) / 3), true)) + auto pattern = ReadSymmetricPattern>(cur, a.size * 2); + if (!pattern || !IsPattern(*pattern, PATTERN)) return -1; - assert(cur.isBlack()); - cur.turnBack(); - - - auto pattern = cur.readPattern>(); - - return (2 * Reduce(pattern) - pattern[0] - pattern[4]) / 12.0 * length(cur.d); + return (2 * Reduce(*pattern) - (*pattern)[0] - (*pattern)[4]) / 12.0 * length(cur.d); } struct DimensionEstimate { int dim = 0; double ms = 0; - int err = 0; + int err = 4; }; -static DimensionEstimate EstimateDimension(const BitMatrix& image, PointF a, PointF b) +static DimensionEstimate EstimateDimension(const BitMatrix& image, ConcentricPattern a, ConcentricPattern b) { auto ms_a = EstimateModuleSize(image, a, b); auto ms_b = EstimateModuleSize(image, b, a); @@ -329,10 +324,10 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) auto top = EstimateDimension(image, fp.tl, fp.tr); auto left = EstimateDimension(image, fp.tl, fp.bl); - if (!top.dim || !left.dim) + if (!top.dim && !left.dim) return {}; - auto best = top.err < left.err ? top : left; + auto best = top.err == left.err ? (top.dim > left.dim ? top : left) : (top.err < left.err ? top : left); int dimension = best.dim; int moduleSize = static_cast(best.ms + 1); @@ -515,7 +510,8 @@ DetectorResult DetectPureQR(const BitMatrix& image) } auto fpWidth = Reduce(diagonal); - auto dimension = EstimateDimension(image, tl + fpWidth / 2 * PointF(1, 1), tr + fpWidth / 2 * PointF(-1, 1)).dim; + auto dimension = + EstimateDimension(image, {tl + fpWidth / 2 * PointF(1, 1), fpWidth}, {tr + fpWidth / 2 * PointF(-1, 1), fpWidth}).dim; float moduleSize = float(width) / dimension; if (dimension < MIN_MODULES || dimension > MAX_MODULES || From 12e9343810b64eac00b6b5633c606a5979128dd0 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Feb 2023 14:45:37 +0100 Subject: [PATCH 070/173] ConcentricFinder: code cleanup / new FitSquareToPoints helper function --- core/src/ConcentricFinder.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index a9157d9b6f..d1dfc06504 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -182,31 +182,35 @@ static bool QuadrilateralIsPlausibleSquare(const QuadrilateralF q, int lineIndex return m >= lineIndex * 2 && m > M / 3; } -std::optional FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int lineIndex) +static std::optional FitSquareToPoints(const BitMatrix& image, PointF center, int range, int lineIndex, bool backup) { - auto innerPoints = CollectRingPoints(image, center, range, lineIndex, false); - auto outerPoints = CollectRingPoints(image, center, range, lineIndex + 1, true); - - if (innerPoints.empty() || outerPoints.empty()) + auto points = CollectRingPoints(image, center, range, lineIndex, backup); + if (points.empty()) return {}; - auto oInnerCorners = FitQadrilateralToPoints(center, innerPoints); - if (!oInnerCorners || !QuadrilateralIsPlausibleSquare(*oInnerCorners, lineIndex)) + auto res = FitQadrilateralToPoints(center, points); + if (!res || !QuadrilateralIsPlausibleSquare(*res, lineIndex - backup)) return {}; - auto oOuterCorners = FitQadrilateralToPoints(center, outerPoints); - if (!oOuterCorners || !QuadrilateralIsPlausibleSquare(*oOuterCorners, lineIndex)) + return res; +} + +std::optional FindConcentricPatternCorners(const BitMatrix& image, PointF center, int range, int lineIndex) +{ + auto innerCorners = FitSquareToPoints(image, center, range, lineIndex, false); + if (!innerCorners) return {}; - auto& innerCorners = *oInnerCorners; - auto& outerCorners = *oOuterCorners; + auto outerCorners = FitSquareToPoints(image, center, range, lineIndex + 1, true); + if (!outerCorners) + return {}; - auto res = Blend(innerCorners, outerCorners); + auto res = Blend(*innerCorners, *outerCorners); - for (auto p : innerCorners) + for (auto p : *innerCorners) log(p, 3); - for (auto p : outerCorners) + for (auto p : *outerCorners) log(p, 3); for (auto p : res) From f0b86a33015b855791ac838f87ffed4f481e5070 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Feb 2023 15:18:53 +0100 Subject: [PATCH 071/173] ConcentricFinder: CenterOfRing can now traverse either outer or inner ring --- core/src/ConcentricFinder.cpp | 9 ++++++--- core/src/qrcode/QRDetector.cpp | 2 +- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index d1dfc06504..846c4cccd8 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -42,11 +42,14 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra // 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; + bool inner = nth < 0; + nth = std::abs(nth); log(center, 3); BitMatrixCursorI cur(image, center, {0, 1}); - if (!cur.stepToEdge(nth, radius)) + if (!cur.stepToEdge(nth, radius, inner)) return {}; - cur.turnRight(); // move clock wise and keep edge on the right + cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup + const auto edgeDir = inner ? Direction::LEFT : Direction::RIGHT; uint32_t neighbourMask = 0; auto start = cur.p; @@ -60,7 +63,7 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra // find out if we come full circle around the center. 8 bits have to be set in the end. neighbourMask |= (1 << (4 + dot(bresenhamDirection(cur.p - center), PointI(1, 3)))); - if (!cur.stepAlongEdge(Direction::RIGHT)) + if (!cur.stepAlongEdge(edgeDir)) return {}; // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 8fa390fa09..d95e3c89b4 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -285,7 +285,7 @@ static std::optional LocateAlignmentPattern(const BitMatrix& image, int continue; if (auto cor1 = CenterOfRing(image, PointI(*cor), moduleSize, 1)) - if (auto cor2 = CenterOfRing(image, PointI(*cor), moduleSize * 3, 2)) + if (auto cor2 = CenterOfRing(image, PointI(*cor), moduleSize * 3, -2)) if (distance(*cor1, *cor2) < moduleSize / 2) { auto res = (*cor1 + *cor2) / 2; log(res, 3); From 02c7737aa8f1c29d1f00f9edb32920eac9640883 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Feb 2023 15:22:57 +0100 Subject: [PATCH 072/173] ConcentricFinder: make CollectRingPoints() fail if stepToEdge() fails --- core/src/ConcentricFinder.cpp | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index 846c4cccd8..0743d7f91c 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -114,8 +114,10 @@ std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, Po static std::vector CollectRingPoints(const BitMatrix& image, PointF center, int range, int edgeIndex, bool backup) { PointI centerI(center); + int radius = range; BitMatrixCursorI cur(image, centerI, {0, 1}); - cur.stepToEdge(edgeIndex, range, backup); + if (!cur.stepToEdge(edgeIndex, radius, backup)) + return {}; cur.turnRight(); // move clock wise and keep edge on the right/left depending on backup const auto edgeDir = backup ? Direction::LEFT : Direction::RIGHT; @@ -135,7 +137,7 @@ static std::vector CollectRingPoints(const BitMatrix& image, PointF cent return {}; // use L-inf norm, simply because it is a lot faster than L2-norm and sufficiently accurate - if (maxAbsComponent(cur.p - center) > range || centerI == cur.p || Size(points) > 4 * 2 * range) + if (maxAbsComponent(cur.p - centerI) > radius || centerI == cur.p || Size(points) > 4 * 2 * range) return {}; } while (cur.p != start); From d917ffb64685dfc8d3da146a48760f1646cd48a0 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 27 Feb 2023 15:26:59 +0100 Subject: [PATCH 073/173] Pattern: code cosmetic --- core/src/Pattern.h | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 88c9adacfd..9b6cb64dba 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -156,12 +156,12 @@ struct FixedPattern template using FixedSparcePattern = FixedPattern; -template -float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, +template +float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, float minQuietZone = 0, float moduleSizeRef = 0) { - int width = view.sum(N); - if (SUM > N && width < SUM) + int width = view.sum(LEN); + if (SUM > LEN && width < SUM) return 0; const float moduleSize = (float)width / SUM; @@ -176,7 +176,7 @@ float IsPattern(const PatternView& view, const FixedPattern& patt // TODO: review once we have upsampling in the binarizer in place. const float threshold = moduleSizeRef * (0.5f + RELAXED_THRESHOLD * 0.25f) + 0.5f; - for (int x = 0; x < N; ++x) + for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * moduleSizeRef) > threshold) return 0; From 96a14e4b17a4b0559803200344d535c767ff69cc Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 8 Mar 2023 02:09:14 +0100 Subject: [PATCH 074/173] EAN/UPCA: include AddOn in position calculation This removes a TODO from the code and fixes #527. --- core/src/oned/ODMultiUPCEANReader.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/oned/ODMultiUPCEANReader.cpp b/core/src/oned/ODMultiUPCEANReader.cpp index 8afd1e6ad3..6a7dcb6fdc 100644 --- a/core/src/oned/ODMultiUPCEANReader.cpp +++ b/core/src/oned/ODMultiUPCEANReader.cpp @@ -294,25 +294,25 @@ Result MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::u // i.e. converted to EAN-13 if UPC-A/E, but not doing this here to maintain backward compatibility SymbologyIdentifier symbologyIdentifier = {'E', res.format == BarcodeFormat::EAN8 ? '4' : '0'}; + next = res.end; + auto ext = res.end; PartialResult addOnRes; if (_hints.eanAddOnSymbol() != EanAddOnSymbol::Ignore && ext.skipSymbol() && ext.skipSingle(static_cast(begin.sum() * 3.5)) && (AddOn(addOnRes, ext, 5) || AddOn(addOnRes, ext, 2))) { // ISO/IEC 15420:2009 states that the content for "]E3" should be 15 or 18 digits, i.e. converted to EAN-13 // and extended with no separator, and that the content for "]E4" should be 8 digits, i.e. no add-on - //TODO: extend position in include extension res.txt += " " + addOnRes.txt; + next = addOnRes.end; if (res.format != BarcodeFormat::EAN8) // Keeping EAN-8 with add-on as "]E4" symbologyIdentifier.modifier = '3'; // Combined packet, EAN-13, UPC-A, UPC-E, with add-on } - next = res.end; - if (_hints.eanAddOnSymbol() == EanAddOnSymbol::Require && !addOnRes.isValid()) return {}; - return Result(res.txt, rowNumber, begin.pixelsInFront(), res.end.pixelsTillEnd(), res.format, symbologyIdentifier, error); + return Result(res.txt, rowNumber, begin.pixelsInFront(), next.pixelsTillEnd(), res.format, symbologyIdentifier, error); } } // namespace ZXing::OneD From 9237c8404db84096dea4b9e3960e607cb2517314 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 8 Mar 2023 02:31:59 +0100 Subject: [PATCH 075/173] c++: add missing #include (for std::isdigit and std::tolower) It is effectively included e.g. via `` but not required to by the standard. Thanks to @idscan for making me aware of that. --- core/src/CharacterSet.cpp | 1 + core/src/Content.cpp | 2 ++ core/src/aztec/AZDecoder.cpp | 1 + 3 files changed, 4 insertions(+) diff --git a/core/src/CharacterSet.cpp b/core/src/CharacterSet.cpp index fb84199f9f..8676ad0950 100644 --- a/core/src/CharacterSet.cpp +++ b/core/src/CharacterSet.cpp @@ -9,6 +9,7 @@ #include "ZXAlgorithms.h" #include +#include namespace ZXing { diff --git a/core/src/Content.cpp b/core/src/Content.cpp index 71d87b5c38..4d3c44657c 100644 --- a/core/src/Content.cpp +++ b/core/src/Content.cpp @@ -12,6 +12,8 @@ #include "Utf.h" #include "ZXAlgorithms.h" +#include + namespace ZXing { std::string ToString(ContentType type) diff --git a/core/src/aztec/AZDecoder.cpp b/core/src/aztec/AZDecoder.cpp index 88953f61e4..f87e652c3e 100644 --- a/core/src/aztec/AZDecoder.cpp +++ b/core/src/aztec/AZDecoder.cpp @@ -16,6 +16,7 @@ #include "ReedSolomonDecoder.h" #include "ZXTestSupport.h" +#include #include #include #include From 146a693a904297d7ea7fa40ee56b726a22a78207 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 8 Mar 2023 18:31:23 +0100 Subject: [PATCH 076/173] HybridBinarizer: help the compiler with auto-vectorization Improve performance by sprinkling some `__restrict` keywords to enable auto vectorization. The `ThresholdBlock` function can be twice as fast now. In a QRCode only setting this translates to more than 10% speed increase on an AVX2 system. --- core/src/HybridBinarizer.cpp | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index faa4cd4e50..7e0a1496f7 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -31,7 +31,8 @@ HybridBinarizer::~HybridBinarizer() = default; * 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* luminances, int subWidth, int subHeight, int width, int height, int rowStride) +static Matrix CalculateBlackPoints(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, + int rowStride) { Matrix blackPoints(subWidth, subHeight); @@ -98,7 +99,8 @@ static Matrix CalculateBlackPoints(const uint8_t* luminances, int subWidth, /** * Applies a single threshold to a block of pixels. */ -static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, int threshold, int rowStride, BitMatrix& matrix) +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; @@ -114,7 +116,7 @@ static void ThresholdBlock(const uint8_t* luminances, int xoffset, int yoffset, * 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* luminances, int subWidth, int subHeight, int width, +static std::shared_ptr CalculateMatrix(const uint8_t* __restrict luminances, int subWidth, int subHeight, int width, int height, int rowStride, const Matrix& blackPoints) { auto matrix = std::make_shared(width, height); From 974d0be4697b0b9d50a9b03681b8ac9092cfdce1 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 9 Mar 2023 09:23:11 +0100 Subject: [PATCH 077/173] Denoise: add MicroQRCode to list of benefiting formats --- core/src/ReadBarcode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index a0abdc1f10..e008146f05 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -141,7 +141,7 @@ Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) if (hints.isPure()) return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; - auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode; + auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode; DecodeHints closedHints = hints; std::unique_ptr closedReader; if (hints.tryDenoise() && hints.hasFormat(formatsBenefittingFromClosing)) { From fe52845847a4f88f9a142580a9f7234bc44d139d Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 9 Mar 2023 15:55:49 +0100 Subject: [PATCH 078/173] Pattern: refactoring and cleanup * new Pattern template * some function renaming * switch from BitMatrixCursorF to BitMatrixCursorI in ConcentricFinder --- core/src/BitMatrixCursor.h | 14 +++++------ core/src/ConcentricFinder.h | 44 ++++++++++++++++------------------ core/src/Pattern.h | 8 ++++--- core/src/aztec/AZDetector.cpp | 14 +++++------ core/src/qrcode/QRDetector.cpp | 2 +- 5 files changed, 39 insertions(+), 43 deletions(-) diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index 69f3aa7925..fc6edc7d0e 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -27,6 +27,8 @@ inline Direction opposite(Direction dir) noexcept template class BitMatrixCursor { + using this_t = BitMatrixCursor; + public: const BitMatrix* img; @@ -89,8 +91,8 @@ class BitMatrixCursor Value edgeAtRight() const noexcept { return edgeAt(right()); } Value edgeAt(Direction dir) const noexcept { return edgeAt(direction(dir)); } - void setDirection(PointF dir) { d = bresenhamDirection(dir); } - void setDirection(PointI dir) { d = dir; } + this_t& setDirection(PointF dir) { return d = bresenhamDirection(dir), *this; } + this_t& setDirection(PointI dir) { return d = dir, *this; } bool step(typename POINT::value_t s = 1) { @@ -98,12 +100,8 @@ class BitMatrixCursor return isIn(p); } - BitMatrixCursor movedBy(POINT d) const - { - auto res = *this; - res.p += d; - return res; - } + this_t movedBy(POINT o) const noexcept { return {*img, p + o, d}; } + this_t turnedBack() const noexcept { return {*img, p, back()}; } /** * @brief stepToEdge advances cursor to one step behind the next (or n-th) edge. diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index 49923f4b21..dda2aab5a2 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -32,13 +32,14 @@ static float CenterFromEnd(const std::array& pattern, float end) } } -template -std::optional ReadSymmetricPattern(Cursor& cur, int range) +template +std::optional> ReadSymmetricPattern(Cursor& cur, int range) { - Pattern res = {}; + static_assert(N % 2 == 1); + assert(range > 0); + Pattern res = {}; auto constexpr s_2 = Size(res)/2; - auto cuo = cur; - cur.turnBack(); + auto cuo = cur.turnedBack(); auto next = [&](auto& cur, int i) { auto v = cur.stepToEdge(1, range); @@ -57,23 +58,20 @@ std::optional ReadSymmetricPattern(Cursor& cur, int range) return res; } -template -int CheckDirection(BitMatrixCursorF& cur, PointF dir, FinderPattern finderPattern, int range, bool updatePosition) +template +int CheckSymmetricPattern(BitMatrixCursorI& cur, PATTERN pattern, int range, bool updatePosition) { - using Pattern = std::array; - auto pOri = cur.p; - cur.setDirection(dir); - auto pattern = ReadSymmetricPattern(cur, range); - if (!pattern || !IsPattern(*pattern, finderPattern)) - return 0; + auto view = ReadSymmetricPattern(cur, range); + if (!view || !IsPattern(*view, pattern)) + return cur.p = pOri, 0; if (updatePosition) - cur.step(CenterFromEnd(*pattern, 0.5) - 1); + cur.step(CenterFromEnd(*view, 0.5) - 1); else cur.p = pOri; - return Reduce(*pattern); + return Reduce(*view); } std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle = true); @@ -87,21 +85,21 @@ struct ConcentricPattern : public PointF int size = 0; }; -template -std::optional LocateConcentricPattern(const BitMatrix& image, FINDER_PATTERN finderPattern, PointF center, int range) +template +std::optional LocateConcentricPattern(const BitMatrix& image, PATTERN pattern, PointF center, int range) { - auto cur = BitMatrixCursorF(image, center, {}); + auto cur = BitMatrixCursor(image, PointI(center), {}); int minSpread = image.width(), maxSpread = 0; - for (auto d : {PointF{0, 1}, {1, 0}}) { - int spread = CheckDirection(cur, d, finderPattern, range, !RELAXED_THRESHOLD); + for (auto d : {PointI{0, 1}, {1, 0}}) { + int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range, true); if (!spread) return {}; UpdateMinMax(minSpread, maxSpread, spread); } #if 1 - for (auto d : {PointF{1, 1}, {1, -1}}) { - int spread = CheckDirection(cur, d, finderPattern, range, false); + for (auto d : {PointI{1, 1}, {1, -1}}) { + int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range * 2, false); if (!spread) return {}; UpdateMinMax(minSpread, maxSpread, spread); @@ -111,7 +109,7 @@ std::optional LocateConcentricPattern(const BitMatrix& image, if (maxSpread > 5 * minSpread) return {}; - auto newCenter = FinetuneConcentricPatternCenter(image, cur.p, range, finderPattern.size()); + auto newCenter = FinetuneConcentricPatternCenter(image, PointF(cur.p), range, pattern.size()); if (!newCenter) return {}; diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 9b6cb64dba..3f9d094629 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -19,7 +19,9 @@ namespace ZXing { -using PatternRow = std::vector; +using PatternType = uint16_t; +template using Pattern = std::array; +using PatternRow = std::vector; class PatternView { @@ -43,7 +45,7 @@ class PatternView PatternView(Iterator data, int size, Iterator base, Iterator end) : _data(data), _size(size), _base(base), _end(end) {} template - PatternView(const std::array& row) : _data(row.data()), _size(N) + PatternView(const Pattern& row) : _data(row.data()), _size(N) {} Iterator data() const { return _data; } @@ -134,7 +136,7 @@ struct BarAndSpace bool isValid() const { return bar != T{} && space != T{}; } }; -using BarAndSpaceI = BarAndSpace; +using BarAndSpaceI = BarAndSpace; /** * @brief FixedPattern describes a compile-time constant (start/stop) pattern. diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index f962086741..69f5288f80 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -53,13 +53,11 @@ static PatternView FindAztecCenterPattern(const PatternView& view) return {}; }; -static int CheckDirection(BitMatrixCursorF& cur, PointF dir, int range, bool updatePosition) +static int CheckSymmetricAztecCenterPattern(BitMatrixCursorI& cur, int range, bool updatePosition) { range *= 2; // tilted symbols may have a larger vertical than horizontal range auto pOri = cur.p; - auto cuo = cur; - cur.setDirection(dir); - cuo.setDirection(-dir); + auto cuo = cur.turnedBack(); int centerUp = cur.stepToEdge(1, range / 7); if (!centerUp) @@ -104,16 +102,16 @@ static int CheckDirection(BitMatrixCursorF& cur, PointF dir, int range, bool upd static std::optional LocateAztecCenter(const BitMatrix& image, PointF center, int spreadH) { - auto cur = BitMatrixCursorF(image, center, {}); + auto cur = BitMatrixCursor(image, PointI(center), {}); int minSpread = spreadH, maxSpread = 0; - for (auto d : {PointF{0, 1}, {1, 0}, {1, 1}, {1, -1}}) { - int spread = CheckDirection(cur, d, spreadH, d.x == 0); + for (auto d : {PointI{0, 1}, {1, 0}, {1, 1}, {1, -1}}) { + int spread = CheckSymmetricAztecCenterPattern(cur.setDirection(d), spreadH, d.x == 0); if (!spread) return {}; UpdateMinMax(minSpread, maxSpread, spread); } - return ConcentricPattern{cur.p, (maxSpread + minSpread) / 2}; + return ConcentricPattern{centered(cur.p), (maxSpread + minSpread) / 2}; } static std::vector FindPureFinderPattern(const BitMatrix& image) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index d95e3c89b4..27a7df1048 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -184,7 +184,7 @@ static double EstimateModuleSize(const BitMatrix& image, ConcentricPattern a, Co BitMatrixCursorF cur(image, a, b - a); assert(cur.isBlack()); - auto pattern = ReadSymmetricPattern>(cur, a.size * 2); + auto pattern = ReadSymmetricPattern<5>(cur, a.size * 2); if (!pattern || !IsPattern(*pattern, PATTERN)) return -1; From d172817d0012230cf19b4bcad10e4c65d2aea6ab Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 10 Mar 2023 00:31:26 +0100 Subject: [PATCH 079/173] BitMatrixCursor: improve ConcentricFinder related code (QRCode + Aztec) A new `FastEdgeToEdgeCounter` class used in `CheckSymmetricPattern` use cases about halves the runtime of that function. A QRCode or Aztec only falsepositive use-case has been observed to run around 8% faster. This is basically the open TODO to provide a faster out-of-bounds check in `BitMatrixCursor::stepToEdge()`. --- core/src/BitMatrixCursor.h | 39 +++++++++++++++++++++++++++- core/src/ConcentricFinder.h | 39 ++++++++++++++++++++++------ core/src/aztec/AZDetector.cpp | 35 ++++++++++--------------- test/blackbox/BlackboxTestRunner.cpp | 2 +- 4 files changed, 84 insertions(+), 31 deletions(-) diff --git a/core/src/BitMatrixCursor.h b/core/src/BitMatrixCursor.h index fc6edc7d0e..bdfc523ed6 100644 --- a/core/src/BitMatrixCursor.h +++ b/core/src/BitMatrixCursor.h @@ -8,6 +8,7 @@ #include "BitMatrix.h" #include +#include namespace ZXing { @@ -112,7 +113,6 @@ class BitMatrixCursor */ int stepToEdge(int nth = 1, int range = 0, bool backup = false) { - // TODO: provide an alternative and faster out-of-bounds check than isIn() inside testAt() int steps = 0; auto lv = testAt(p); @@ -191,4 +191,41 @@ class BitMatrixCursor using BitMatrixCursorF = BitMatrixCursor; using BitMatrixCursorI = BitMatrixCursor; +class FastEdgeToEdgeCounter +{ + const uint8_t* p = nullptr; + int stride = 0; + int stepsToBorder = 0; + +public: + FastEdgeToEdgeCounter(const BitMatrixCursorI& cur) + { + stride = cur.d.y * cur.img->width() + cur.d.x; + p = cur.img->row(cur.p.y).begin() + cur.p.x; + + int maxStepsX = cur.d.x ? (cur.d.x > 0 ? cur.img->width() - 1 - cur.p.x : cur.p.x) : INT_MAX; + int maxStepsY = cur.d.y ? (cur.d.y > 0 ? cur.img->height() - 1 - cur.p.y : cur.p.y) : INT_MAX; + stepsToBorder = std::min(maxStepsX, maxStepsY); + } + + int stepToNextEdge(int range) + { + int maxSteps = std::min(stepsToBorder, range); + int steps = 0; + do { + if (++steps > maxSteps) { + if (maxSteps == stepsToBorder) + break; + else + return 0; + } + } while (p[steps * stride] == p[0]); + + p += steps * stride; + stepsToBorder -= steps; + + return steps; + } +}; + } // ZXing diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index dda2aab5a2..ce81cc84d2 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -61,17 +61,40 @@ std::optional> ReadSymmetricPattern(Cursor& cur, int range) template int CheckSymmetricPattern(BitMatrixCursorI& cur, PATTERN pattern, int range, bool updatePosition) { - auto pOri = cur.p; - auto view = ReadSymmetricPattern(cur, range); - if (!view || !IsPattern(*view, pattern)) - return cur.p = pOri, 0; + FastEdgeToEdgeCounter curFwd(cur), curBwd(cur.turnedBack()); + + int centerFwd = curFwd.stepToNextEdge(range); + if (!centerFwd) + return 0; + int centerBwd = curBwd.stepToNextEdge(range); + if (!centerBwd) + return 0; + + assert(range > 0); + Pattern res = {}; + auto constexpr s_2 = Size(res)/2; + res[s_2] = centerFwd + centerBwd - 1; // -1 because the starting pixel is counted twice + range -= res[s_2]; + + auto next = [&](auto& cur, int i) { + auto v = cur.stepToNextEdge(range); + res[s_2 + i] = v; + range -= v; + return v; + }; + + for (int i = 1; i <= s_2; ++i) { + if (!next(curFwd, i) || !next(curBwd, -i)) + return 0; + } + + if (!IsPattern(res, pattern)) + return 0; if (updatePosition) - cur.step(CenterFromEnd(*view, 0.5) - 1); - else - cur.p = pOri; + cur.step(res[s_2] / 2 - (centerBwd - 1)); - return Reduce(*view); + return Reduce(res); } std::optional CenterOfRing(const BitMatrix& image, PointI center, int range, int nth, bool requireCircle = true); diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 69f5288f80..39aeba811e 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -33,10 +33,7 @@ static bool IsAztecCenterPattern(const PatternView& view) auto M = m; for (int i = 1; i < Size(view) - 1; ++i) { int v = view[i] + view[i + 1]; - if (v < m) - m = v; - else if (v > M) - M = v; + UpdateMinMax(m, M, v); } return M <= m * 4 / 3 + 1 && view[-1] >= view[Size(view) / 2] - 2 && view[Size(view)] >= view[Size(view) / 2] - 2; }; @@ -56,38 +53,33 @@ static PatternView FindAztecCenterPattern(const PatternView& view) static int CheckSymmetricAztecCenterPattern(BitMatrixCursorI& cur, int range, bool updatePosition) { range *= 2; // tilted symbols may have a larger vertical than horizontal range - auto pOri = cur.p; - auto cuo = cur.turnedBack(); - int centerUp = cur.stepToEdge(1, range / 7); - if (!centerUp) + FastEdgeToEdgeCounter curFwd(cur), curBwd(cur.turnedBack()); + + int centerFwd = curFwd.stepToNextEdge(range / 7); + if (!centerFwd) return 0; - int centerDown = cuo.stepToEdge(1, range / 7); - if (!centerDown) + int centerBwd = curBwd.stepToNextEdge(range / 7); + if (!centerBwd) return 0; - int center = centerUp + centerDown - 1; // -1 because the starting pixel is counted twice + int center = centerFwd + centerBwd - 1; // -1 because the starting pixel is counted twice if (center > range / 7 || center < range / (4 * 7)) return 0; - if (updatePosition) - pOri = (cur.p + cuo.p) / 2; - int spread = center; int m = 0; int M = 0; - for (auto c : {&cur, &cuo}) { + for (auto c : {&curFwd, &curBwd}) { int lastS = center; for (int i = 0; i < 3; ++i) { - int s = c->stepToEdge(1, range - spread); + int s = c->stepToNextEdge(range - spread); if (s == 0) return 0; int v = s + lastS; if (m == 0) m = M = v; - else if (v < m) - m = v; - else if (v > M) - M = v; + else + UpdateMinMax(m, M, v); if (M > m * 4 / 3 + 1) return 0; spread += s; @@ -95,7 +87,8 @@ static int CheckSymmetricAztecCenterPattern(BitMatrixCursorI& cur, int range, bo } } - cur.p = pOri; + if (updatePosition) + cur.step(centerFwd - centerBwd); return spread; } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index dd9b28b74a..8dac8ca4bd 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -607,7 +607,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 15, 15, 0 }, { 15, 15, 90 }, { 15, 15, 180 }, - { 14, 14, 270 }, + { 15, 15, 270 }, { 9, 0, pure }, }); From 2342f1342b3706fd5ea3194fafc111638bc50b1a Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 10 Mar 2023 00:35:52 +0100 Subject: [PATCH 080/173] Android: updated gradle and bunch of dependencies --- wrappers/android/.idea/gradle.xml | 2 +- wrappers/android/.idea/misc.xml | 2 +- wrappers/android/app/build.gradle | 6 +++--- wrappers/android/build.gradle | 2 +- wrappers/android/gradle.properties | 2 +- wrappers/android/gradle/wrapper/gradle-wrapper.properties | 2 +- wrappers/android/zxingcpp/build.gradle | 2 +- 7 files changed, 9 insertions(+), 9 deletions(-) diff --git a/wrappers/android/.idea/gradle.xml b/wrappers/android/.idea/gradle.xml index 64ed26b8ed..0ff903df8c 100644 --- a/wrappers/android/.idea/gradle.xml +++ b/wrappers/android/.idea/gradle.xml @@ -8,7 +8,7 @@

/// Compute the number of zero bits on the left. /// -inline int NumberOfLeadingZeros(uint32_t x) +template>> +inline int NumberOfLeadingZeros(T x) { - if (x == 0) - return 32; + if constexpr (sizeof(x) <= 4) { + if (x == 0) + return 32; +#ifdef ZX_HAS_GCC_BUILTINS + return __builtin_clz(x); +#elif defined(ZX_HAS_MSC_BUILTINS) + return __lzcnt(x); +#else + int n = 0; + if ((x & 0xFFFF0000) == 0) { n = n + 16; x = x << 16; } + if ((x & 0xFF000000) == 0) { n = n + 8; x = x << 8; } + if ((x & 0xF0000000) == 0) { n = n + 4; x = x << 4; } + if ((x & 0xC0000000) == 0) { n = n + 2; x = x << 2; } + if ((x & 0x80000000) == 0) { n = n + 1; } + return n; +#endif + } else { + if (x == 0) + return 64; #ifdef ZX_HAS_GCC_BUILTINS - return __builtin_clz(x); + return __builtin_clzll(x); +#elif defined(ZX_HAS_MSC_BUILTINS) + return __lzcnt64(x); #else - int n = 0; - if ((x & 0xFFFF0000) == 0) { n = n + 16; x = x << 16; } - if ((x & 0xFF000000) == 0) { n = n + 8; x = x << 8; } - if ((x & 0xF0000000) == 0) { n = n + 4; x = x << 4; } - if ((x & 0xC0000000) == 0) { n = n + 2; x = x << 2; } - if ((x & 0x80000000) == 0) { n = n + 1; } - return n; + int n = NumberOfLeadingZeros(static_cast(x >> 32)); + if (n == 32) + n += NumberOfLeadingZeros(static_cast(x)); + return n; #endif + } } /// /// Compute the number of zero bits on the right. /// -inline int NumberOfTrailingZeros(uint32_t v) +template>> +inline int NumberOfTrailingZeros(T v) { -#ifdef ZX_HAS_GCC_BUILTINS assert(v != 0); - return __builtin_ctz(v); + if constexpr (sizeof(v) <= 4) { +#ifdef ZX_HAS_GCC_BUILTINS + return __builtin_ctz(v); +#elif defined(ZX_HAS_MSC_BUILTINS) + unsigned long where; + if (_BitScanForward(&where, mask)) + return static_cast(where); + return 32; +#else + int c = 32; + v &= -int32_t(v); + if (v) c--; + if (v & 0x0000FFFF) c -= 16; + if (v & 0x00FF00FF) c -= 8; + if (v & 0x0F0F0F0F) c -= 4; + if (v & 0x33333333) c -= 2; + if (v & 0x55555555) c -= 1; + return c; +#endif + } 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, mask)) + return static_cast(where); + #elif defined(_WIN32) + if (_BitScanForward(&where, static_cast(mask))) + return static_cast(where); + if (_BitScanForward(&where, static_cast(mask >> 32))) + return static_cast(where + 32); + #else + #error "Implementation of __builtin_ctzll required" + #endif #else - int c = 32; - v &= -int32_t(v); - if (v) c--; - if (v & 0x0000FFFF) c -= 16; - if (v & 0x00FF00FF) c -= 8; - if (v & 0x0F0F0F0F) c -= 4; - if (v & 0x33333333) c -= 2; - if (v & 0x55555555) c -= 1; - return c; + int n = NumberOfTrailingZeros(static_cast(v)); + if (n == 32) + n += NumberOfTrailingZeros(static_cast(v >> 32)); + return n; #endif + } } inline uint32_t Reverse(uint32_t v) From 19ec690b651dd876e2db2dd319347025dcb49b05 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 10:11:56 +0100 Subject: [PATCH 082/173] Range: add convenience constructors --- core/src/Range.h | 8 ++++++++ test/unit/PatternTest.cpp | 14 ++++++-------- 2 files changed, 14 insertions(+), 8 deletions(-) diff --git a/core/src/Range.h b/core/src/Range.h index d1d94faaaa..02828cfae4 100644 --- a/core/src/Range.h +++ b/core/src/Range.h @@ -28,10 +28,18 @@ struct Range { Iterator _begin, _end; + Range(Iterator b, Iterator e) : _begin(b), _end(e) {} + + template + Range(const C& c) : _begin(std::begin(c)), _end(std::end(c)) {} + Iterator begin() const noexcept { return _begin; } Iterator end() const noexcept { return _end; } explicit operator bool() const { return begin() < end(); } int size() const { return narrow_cast(end() - begin()); } }; +template +Range(const C&) -> Range; + } // namespace ZXing diff --git a/test/unit/PatternTest.cpp b/test/unit/PatternTest.cpp index f3cdc58de0..dde3a58841 100644 --- a/test/unit/PatternTest.cpp +++ b/test/unit/PatternTest.cpp @@ -11,12 +11,13 @@ using namespace ZXing; constexpr int N = 33; +PatternRow pr; + TEST(PatternTest, AllWhite) { for (int s = 1; s <= N; ++s) { std::vector in(s, 0); - PatternRow pr; - GetPatternRow(Range{in.data(), in.data() + in.size()}, pr); + GetPatternRow(Range{in}, pr); EXPECT_EQ(pr.size(), 1); EXPECT_EQ(pr[0], s); @@ -27,8 +28,7 @@ TEST(PatternTest, AllBlack) { for (int s = 1; s <= N; ++s) { std::vector in(s, 0xff); - PatternRow pr; - GetPatternRow(Range{in.data(), in.data() + in.size()}, pr); + GetPatternRow(Range{in}, pr); EXPECT_EQ(pr.size(), 3); EXPECT_EQ(pr[0], 0); @@ -42,8 +42,7 @@ TEST(PatternTest, BlackWhite) for (int s = 1; s <= N; ++s) { std::vector in(N, 0); std::fill_n(in.data(), s, 0xff); - PatternRow pr; - GetPatternRow(Range{in.data(), in.data() + in.size()}, pr); + GetPatternRow(Range{in}, pr); EXPECT_EQ(pr.size(), 3); EXPECT_EQ(pr[0], 0); @@ -57,8 +56,7 @@ TEST(PatternTest, WhiteBlack) for (int s = 0; s < N; ++s) { std::vector in(N, 0xff); std::fill_n(in.data(), s, 0); - PatternRow pr; - GetPatternRow(Range{in.data(), in.data() + in.size()}, pr); + GetPatternRow(Range{in}, pr); EXPECT_EQ(pr.size(), 3); EXPECT_EQ(pr[0], s); From 7821c7db631cf90577950e5a48a55920b13a1f75 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 10:16:01 +0100 Subject: [PATCH 083/173] StrideIterator: enhance API / applicability with std algorithms This especially achieves 2 things: * usable with std::copy() * fix bug when used with negative stride --- core/src/Pattern.h | 3 ++- core/src/Range.h | 16 +++++++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 3f9d094629..b7355858e0 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -316,12 +316,13 @@ void GetPatternRow(Range b_row, PatternRow& p_row) std::fill(p_row.begin(), p_row.end(), 0); auto bitPos = b_row.begin(); + const auto bitPosEnd = b_row.end(); auto intPos = p_row.data(); if (*bitPos) intPos++; // first value is number of white pixels, here 0 - while (++bitPos < b_row.end()) { + while (++bitPos != bitPosEnd) { ++(*intPos); intPos += bitPos[0] != bitPos[-1]; } diff --git a/core/src/Range.h b/core/src/Range.h index 02828cfae4..5040157807 100644 --- a/core/src/Range.h +++ b/core/src/Range.h @@ -7,6 +7,8 @@ #include "ZXAlgorithms.h" +#include + namespace ZXing { template @@ -15,14 +17,26 @@ struct StrideIter Iterator pos; int stride; + using iterator_category = std::random_access_iterator_tag; + using difference_type = typename std::iterator_traits::difference_type; + using value_type = typename std::iterator_traits::value_type; + using pointer = Iterator; + using reference = typename std::iterator_traits::reference; + auto operator*() const { return *pos; } auto operator[](int i) const { return *(pos + i * stride); } StrideIter& operator++() { return pos += stride, *this; } - bool operator<(const StrideIter& rhs) const { return pos < rhs.pos; } + StrideIter operator++(int) { auto temp = *this; ++*this; return temp; } + bool operator!=(const StrideIter& rhs) const { return pos != rhs.pos; } StrideIter operator+(int i) const { return {pos + i * stride, stride}; } + StrideIter operator-(int i) const { return {pos - i * stride, stride}; } int operator-(const StrideIter& rhs) const { return narrow_cast((pos - rhs.pos) / stride); } }; +template +StrideIter(const Iterator&, int) -> StrideIter; + + template struct Range { From 78cc5936e6437c9d9568272552bd1de6eb4b37ba Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 10:52:10 +0100 Subject: [PATCH 084/173] ODReader: save 'od-log.pnm' in PRINT_DEBUG mode --- core/src/oned/ODReader.cpp | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/core/src/oned/ODReader.cpp b/core/src/oned/ODReader.cpp index 52e376562f..51a3ef9898 100644 --- a/core/src/oned/ODReader.cpp +++ b/core/src/oned/ODReader.cpp @@ -22,6 +22,11 @@ #include #include +#ifdef PRINT_DEBUG +#include "BitMatrix.h" +#include "BitMatrixIO.h" +#endif + namespace ZXing { void IncrementLineCount(Result& r) @@ -96,6 +101,10 @@ static Results DoDecode(const std::vector>& readers, PatternRow bars; bars.reserve(128); // e.g. EAN-13 has 59 bars/spaces +#ifdef PRINT_DEBUG + BitMatrix dbg(width, height); +#endif + for (int i = 0; i < maxLines; i++) { // Scanning from the middle out. Determine which row we're looking at next: @@ -121,6 +130,16 @@ static Results DoDecode(const std::vector>& readers, if (!image.getPatternRow(rowNumber, rotate ? 270 : 0, bars)) continue; +#ifdef PRINT_DEBUG + bool val = false; + int x = 0; + for (auto b : bars) { + for(int j = 0; j < b; ++j) + dbg.set(x++, rowNumber, val); + val = !val; + } +#endif + // While we have the image data in a PatternRow, it's fairly cheap to reverse it in place to // handle decoding upside down barcodes. // TODO: the DataBarExpanded (stacked) decoder depends on seeing each line from both directions. This @@ -225,6 +244,10 @@ static Results DoDecode(const std::vector>& readers, it = std::remove_if(res.begin(), res.end(), [](auto&& r) { return r.format() == BarcodeFormat::None; }); res.erase(it, res.end()); +#ifdef PRINT_DEBUG + SaveAsPBM(dbg, rotate ? "od-log-r.pnm" : "od-log.pnm"); +#endif + return res; } From de2de54f8bf17cadc529802c679db4912ee7570b Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 10:56:51 +0100 Subject: [PATCH 085/173] =?UTF-8?q?OD/PDF:=20consistently=20turn=2090?= =?UTF-8?q?=C2=B0=20clockwise=20when=20looking=20for=20'rotated'=20symbols?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This prepares the possibility to use the `BitMatrix` in `HybridBinarizer` instead of falling back to the `GlobalHistogramBinazier::getPatternRow`. --- core/src/BitMatrix.h | 5 ++++- core/src/oned/ODReader.cpp | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index 4acd20902c..7550494729 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -77,7 +77,10 @@ class BitMatrix Range row(int y) { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; } Range row(int y) const { return {_bits.data() + y * _width, _bits.data() + (y + 1) * _width}; } - Range> col(int x) const { return {{_bits.data() + x, _width}, {_bits.data() + x + _height * _width, _width}}; } + Range> col(int x) const + { + return {{_bits.data() + x + (_height - 1) * _width, -_width}, {_bits.data() + x - _width, -_width}}; + } bool get(int x, int y) const { return get(y * _width + x); } void set(int x, int y, bool val = true) { get(y * _width + x) = val * SET_V; } diff --git a/core/src/oned/ODReader.cpp b/core/src/oned/ODReader.cpp index 51a3ef9898..e928d04f77 100644 --- a/core/src/oned/ODReader.cpp +++ b/core/src/oned/ODReader.cpp @@ -127,7 +127,7 @@ static Results DoDecode(const std::vector>& readers, continue; } - if (!image.getPatternRow(rowNumber, rotate ? 270 : 0, bars)) + if (!image.getPatternRow(rowNumber, rotate ? 90 : 0, bars)) continue; #ifdef PRINT_DEBUG @@ -175,7 +175,7 @@ static Results DoDecode(const std::vector>& readers, if (rotate) { auto points = result.position(); for (auto& p : points) { - p = {height - p.y - 1, p.x}; + p = {p.y, width - p.x - 1}; } result.setPosition(std::move(points)); } From 7bcdcf84be0e4fd7260912282fbcf6a44fc158b2 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 11:01:44 +0100 Subject: [PATCH 086/173] [amend last commit]: fix test regression Missed one hunk... --- core/src/GlobalHistogramBinarizer.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 63da1a8c75..2fc336fa0d 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -109,10 +109,10 @@ bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& // the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 2x faster if (pixStride == 1) - for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p < e; p += pixStride) + for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p != e; p += pixStride) process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); else - for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p < e; p += pixStride) + for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p != e; p += pixStride) process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); auto* backPos = buffer.data(buffer.width() - 1, row); From cea834102ff18cc7cc42c0a65d665801d42f92ad Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 11:10:49 +0100 Subject: [PATCH 087/173] windows: fix another build regression (copy'n'paste bug in MSC code) --- core/src/BitHacks.h | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index db29fcf338..44d5474df6 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -74,7 +74,7 @@ inline int NumberOfTrailingZeros(T v) return __builtin_ctz(v); #elif defined(ZX_HAS_MSC_BUILTINS) unsigned long where; - if (_BitScanForward(&where, mask)) + if (_BitScanForward(&where, v)) return static_cast(where); return 32; #else @@ -94,12 +94,12 @@ inline int NumberOfTrailingZeros(T v) #elif defined(ZX_HAS_MSC_BUILTINS) unsigned long where; #if defined(_WIN64) - if (_BitScanForward64(&where, mask)) + if (_BitScanForward64(&where, v)) return static_cast(where); #elif defined(_WIN32) - if (_BitScanForward(&where, static_cast(mask))) + if (_BitScanForward(&where, static_cast(v))) return static_cast(where); - if (_BitScanForward(&where, static_cast(mask >> 32))) + if (_BitScanForward(&where, static_cast(v >> 32))) return static_cast(where + 32); #else #error "Implementation of __builtin_ctzll required" From 5a08f7a9cd0095b7f7e37954e03ad9191e31fa62 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 11:57:25 +0100 Subject: [PATCH 088/173] Performance: increase speed 20-30% (on AVX2 hardware) * separate the RLE compression from the sharpening+thresholding in `GlobalHistogramBinarizer::getPatternRow` * transpose the memory once instead of twice when testing for rotated symbols in `GlobalHistogramBinarizer::getPatternRow` * use generic `GetPatternRow` (RLE) function everywhere * add simd style optimization to RLE compression, shown to help significantly on AVX hardware with large images but is a pessimization on a Google Pixel 3. --- core/src/BitHacks.h | 10 +++ core/src/GlobalHistogramBinarizer.cpp | 106 ++++++++++++++------------ core/src/Pattern.h | 22 ++++++ example/ZXingReader.cpp | 2 +- 4 files changed, 90 insertions(+), 50 deletions(-) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index 44d5474df6..cfde406734 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -185,4 +185,14 @@ void Reverse(std::vector& bits, std::size_t padding) ShiftRight(bits, padding); } +// use to avoid "load of misaligned address" when using a simple type cast +template +T LoadU(const void* ptr) +{ + static_assert(std::is_integral::value, "T must be an integer"); + T res; + memcpy(&res, ptr, sizeof(T)); + return res; +} + } // namespace ZXing::BitHacks diff --git a/core/src/GlobalHistogramBinarizer.cpp b/core/src/GlobalHistogramBinarizer.cpp index 2fc336fa0d..44ca15810c 100644 --- a/core/src/GlobalHistogramBinarizer.cpp +++ b/core/src/GlobalHistogramBinarizer.cpp @@ -7,6 +7,7 @@ #include "GlobalHistogramBinarizer.h" #include "BitMatrix.h" +#include "Pattern.h" #include #include @@ -16,16 +17,45 @@ namespace ZXing { -static const int LUMINANCE_BITS = 5; -static const int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; -static const int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; +static constexpr int LUMINANCE_BITS = 5; +static constexpr int LUMINANCE_SHIFT = 8 - LUMINANCE_BITS; +static constexpr int LUMINANCE_BUCKETS = 1 << LUMINANCE_BITS; + +using Histogram = std::array; GlobalHistogramBinarizer::GlobalHistogramBinarizer(const ImageView& buffer) : BinaryBitmap(buffer) {} GlobalHistogramBinarizer::~GlobalHistogramBinarizer() = default; +using ImageLineView = Range>; + +inline ImageLineView RowView(const ImageView& iv, int row) +{ + return {{iv.data(0, row), iv.pixStride()}, {iv.data(iv.width(), row), iv.pixStride()}}; +} + +static void ThresholdSharpened(const ImageLineView in, int threshold, std::vector& out) +{ + out.resize(in.size()); + auto i = in.begin(); + auto o = out.begin(); + + *o++ = (*i++ <= threshold) * BitMatrix::SET_V; + for (auto end = in.end() - 1; i != end; ++i) + *o++ = ((-i[-1] + (int(i[0]) * 4) - i[1]) / 2 <= threshold) * BitMatrix::SET_V; + *o++ = (*i++ <= threshold) * BitMatrix::SET_V; +} + +static auto GenHistogram(const ImageLineView line) +{ + Histogram res = {}; + for (auto pix : line) + res[pix >> LUMINANCE_SHIFT]++; + return res; +} + // Return -1 on error -static int EstimateBlackPoint(const std::array& buckets) +static int EstimateBlackPoint(const Histogram& buckets) { // Find the tallest peak in the histogram. auto firstPeakPos = std::max_element(buckets.begin(), buckets.end()); @@ -36,7 +66,7 @@ static int EstimateBlackPoint(const std::array& buckets) // Find the second-tallest peak which is somewhat far from the tallest peak. int secondPeak = 0; int secondPeakScore = 0; - for (int x = 0; x < LUMINANCE_BUCKETS; x++) { + for (int x = 0; x < Size(buckets); x++) { int distanceToBiggest = x - firstPeak; // Encourage more distant second peaks by multiplying by square of distance. int score = buckets[x] * distanceToBiggest * distanceToBiggest; @@ -75,57 +105,35 @@ static int EstimateBlackPoint(const std::array& buckets) bool GlobalHistogramBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const { auto buffer = _buffer.rotated(rotation); + auto lineView = RowView(buffer, row); if (buffer.width() < 3) return false; // special casing the code below for a width < 3 makes no sense - const uint8_t* luminances = buffer.data(0, row); - const int pixStride = buffer.pixStride(); - std::array buckets = {}; - for (int x = 0; x < buffer.width(); x++) - buckets[luminances[x * pixStride] >> LUMINANCE_SHIFT]++; +#ifdef __AVX__ + // If we are extracting a column (instead of a row), we run into cache misses on every pixel access both + // during the histogram caluculation and during the sharpen+threshold operation. Additionally, if we + // perform the ThresholdSharpened function on pixStride==1 data, the auto-vectorizer makes that part + // 8x faster on an AVX2 cpu which easily recovers the extra cost that we pay for the copying. + thread_local std::vector line; + if (std::abs(buffer.pixStride()) > 4) { + line.resize(lineView.size()); + std::copy(lineView.begin(), lineView.end(), line.begin()); + lineView = {{line.data(), 1}, {line.data() + line.size(), 1}}; + } +#endif - int blackPoint = EstimateBlackPoint(buckets); - if (blackPoint <= 0) + auto threshold = EstimateBlackPoint(GenHistogram(lineView)) - 1; + if (threshold <= 0) return false; - res.resize(buffer.width() + 2); - std::fill(res.begin(), res.end(), 0); - auto* intPos = res.data(); - - auto* lastPos = luminances; - bool lastVal = luminances[0] < blackPoint; - if (lastVal) - intPos++; // first value is number of white pixels, here 0 - - auto process = [&](bool val, const uint8_t* p) { - bool update = val != lastVal; - *intPos = update * narrow_cast((p - lastPos) / pixStride); - intPos += update; - lastVal = val; - if (update) - lastPos = p; - }; - - // the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 2x faster - if (pixStride == 1) - for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p != e; p += pixStride) - process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); + thread_local std::vector binarized; + // the optimizer can generate a specialized version for pixStride==1 (non-rotated input) that is about 8x faster on AVX2 hardware + if (lineView.begin().stride == 1) + ThresholdSharpened(lineView, threshold, binarized); else - for (auto *p = luminances + pixStride, *e = luminances + (buffer.width() - 1) * pixStride; p != e; p += pixStride) - process((-*(p - pixStride) + (int(*p) * 4) - *(p + pixStride)) / 2 < blackPoint, p); - - auto* backPos = buffer.data(buffer.width() - 1, row); - bool backVal = *backPos < blackPoint; - process(backVal, backPos); - - *intPos++ = narrow_cast((backPos - lastPos) / pixStride + 1); - - if (backVal) - intPos++; - - res.resize(intPos - res.data()); - assert(res.size() % 2 == 1); + ThresholdSharpened(lineView, threshold, binarized); + GetPatternRow(Range(binarized), res); return true; } @@ -136,7 +144,7 @@ GlobalHistogramBinarizer::getBlackMatrix() const { // Quickly calculates the histogram by sampling four rows from the image. This proved to be // more robust on the blackbox tests than sampling a diagonal as we used to do. - std::array localBuckets = {}; + Histogram localBuckets = {}; { for (int y = 1; y < 5; y++) { int row = height() * y / 5; diff --git a/core/src/Pattern.h b/core/src/Pattern.h index b7355858e0..66dd003123 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -5,6 +5,7 @@ #pragma once +#include "BitHacks.h" #include "Range.h" #include "ZXAlgorithms.h" @@ -322,6 +323,27 @@ void GetPatternRow(Range b_row, PatternRow& p_row) if (*bitPos) intPos++; // first value is number of white pixels, here 0 +#if defined(__AVX__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) + // The following code as been observed to cause a speedup of up to 30% on large images on an AVX cpu + // but also to cause a 50% slowdown on an a Google Pixel 3 Android phone. + if constexpr (std::is_pointer_v && sizeof(std::remove_pointer_t) == 1) { + using simd_t = uint64_t; + while (bitPos < bitPosEnd - sizeof(simd_t)) { + auto asSimd0 = BitHacks::LoadU(bitPos); + auto asSimd1 = BitHacks::LoadU(bitPos + 1); + auto z = asSimd0 ^ asSimd1; + if (z) { + int step = BitHacks::NumberOfTrailingZeros(z) / 8 + 1; + (*intPos++) += step; + bitPos += step; + } else { + (*intPos) += sizeof(simd_t); + bitPos += sizeof(simd_t); + } + } + } +#endif + while (++bitPos != bitPosEnd) { ++(*intPos); intPos += bitPos[0] != bitPos[-1]; diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index f7660e7f03..66a22123a3 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -283,7 +283,7 @@ int main(int argc, char* argv[]) if (blockSize < 1000 && duration < std::chrono::milliseconds(100)) blockSize *= 10; } while (duration < std::chrono::seconds(1)); - printf("time: %5.1f ms per frame\n", double(std::chrono::duration_cast(duration).count()) / N); + printf("time: %5.2f ms per frame\n", double(std::chrono::duration_cast(duration).count()) / N); } #endif } From dcfba650a4035f9daf428c268bab0a29cccb0c20 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 13:15:23 +0100 Subject: [PATCH 089/173] HybridBinarizer: alternative "local average" option also for the 1D case Add (a currently disabled) alternative to the GlobalHistogram approach for the linear symbology case. This would e.g. allow for the image in https://github.com/zxing-cpp/zxing-cpp/issues/479#issuecomment-1378270362 to be detected successfully. --- core/src/HybridBinarizer.cpp | 17 +++++++++++++++++ core/src/HybridBinarizer.h | 1 + 2 files changed, 18 insertions(+) diff --git a/core/src/HybridBinarizer.cpp b/core/src/HybridBinarizer.cpp index 7e0a1496f7..4aaec4ae85 100644 --- a/core/src/HybridBinarizer.cpp +++ b/core/src/HybridBinarizer.cpp @@ -26,6 +26,23 @@ HybridBinarizer::HybridBinarizer(const ImageView& iv) : GlobalHistogramBinarizer HybridBinarizer::~HybridBinarizer() = default; +bool HybridBinarizer::getPatternRow(int row, int rotation, PatternRow& res) const +{ +#if 1 + // This is the original "hybrid" behavior: use GlobalHistogram for the 1D case + return GlobalHistogramBinarizer::getPatternRow(row, rotation, res); +#else + // This is an alternative that can be faster in general and perform better in unevenly lit sitations like + // https://github.com/zxing-cpp/zxing-cpp/blob/master/test/samples/ean13-2/21.png. That said, it fairs + // worse in borderline low resolution situations. With the current black box sample set we'd loose 94 + // test cases while gaining 53 others. + auto bits = getBitMatrix(); + if (bits) + GetPatternRow(*bits, row, res, rotation % 180 != 0); + return bits != nullptr; +#endif +} + /** * Calculates a single black point for each block of pixels and saves it away. * See the following thread for a discussion of this algorithm: diff --git a/core/src/HybridBinarizer.h b/core/src/HybridBinarizer.h index 6e0a8b42d0..3175fa63c9 100644 --- a/core/src/HybridBinarizer.h +++ b/core/src/HybridBinarizer.h @@ -33,6 +33,7 @@ class HybridBinarizer : public GlobalHistogramBinarizer explicit HybridBinarizer(const ImageView& iv); ~HybridBinarizer() override; + bool getPatternRow(int row, int rotation, PatternRow &res) const override; std::shared_ptr getBlackMatrix() const override; }; From d98bd6e9d8026a49708b6576227fb7b5b6c41145 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 13 Mar 2023 13:23:47 +0100 Subject: [PATCH 090/173] ReadBarcode: add check for image width/height overflow (65535) --- core/src/ReadBarcode.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index e008146f05..37ff3dfab1 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -9,6 +9,7 @@ #include "GlobalHistogramBinarizer.h" #include "HybridBinarizer.h" #include "MultiFormatReader.h" +#include "Pattern.h" #include "ThresholdBinarizer.h" #include @@ -134,6 +135,9 @@ Result ReadBarcode(const ImageView& _iv, const DecodeHints& hints) Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) { + if (sizeof(PatternType) < 4 && hints.hasFormat(BarcodeFormat::LinearCodes) && (_iv.width() > 0xffff || _iv.height() > 0xffff)) + throw std::invalid_argument("maximum image width/height is 65535"); + LumImage lum; ImageView iv = SetupLumImageView(_iv, lum, hints); MultiFormatReader reader(hints); From 820fda69d7e1843f79b1389aba6e8d292d6a5436 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 14 Mar 2023 13:02:46 +0100 Subject: [PATCH 091/173] PatternRow: don't reallocate local object for every row --- core/src/aztec/AZDetector.cpp | 3 ++- core/src/qrcode/QRDetector.cpp | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index 39aeba811e..da4ba6645c 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -178,9 +178,10 @@ static std::vector FindFinderPatterns(const BitMatrix& image, int skip = tryHarder ? 1 : std::clamp(image.height() / 2 / 100, 1, 5); int margin = tryHarder ? 5 : image.height() / 4; + PatternRow row; + for (int y = margin; y < image.height() - margin; y += skip) { - PatternRow row; GetPatternRow(image, y, row, false); PatternView next = row; next.shift(1); // the center pattern we are looking for starts with white and is 7 wide (compact code) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 27a7df1048..7e9bb66980 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -52,9 +52,9 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t skip = MIN_SKIP; std::vector res; + PatternRow row; for (int y = skip - 1; y < height; y += skip) { - PatternRow row; GetPatternRow(image, y, row, false); PatternView next = row; From d2ed8152aa866714348ffb712672d38b64f030b7 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 15 Mar 2023 14:00:57 +0100 Subject: [PATCH 092/173] performance: enable the simd-style GetPatternRow on all 64bit systems It turned out that my earlier measurements on my phone were wrong/ misleading. It actually behaves similar to my AVX2 system. --- core/src/Pattern.h | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 66dd003123..8b8a651293 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -323,17 +323,20 @@ void GetPatternRow(Range b_row, PatternRow& p_row) if (*bitPos) intPos++; // first value is number of white pixels, here 0 -#if defined(__AVX__) && (__BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) // The following code as been observed to cause a speedup of up to 30% on large images on an AVX cpu - // but also to cause a 50% slowdown on an a Google Pixel 3 Android phone. - if constexpr (std::is_pointer_v && sizeof(std::remove_pointer_t) == 1) { + // and on an a Google Pixel 3 Android phone. Your mileage may vary. + if constexpr (std::is_pointer_v && sizeof(I) == 8 && sizeof(std::remove_pointer_t) == 1) { using simd_t = uint64_t; while (bitPos < bitPosEnd - sizeof(simd_t)) { auto asSimd0 = BitHacks::LoadU(bitPos); auto asSimd1 = BitHacks::LoadU(bitPos + 1); auto z = asSimd0 ^ asSimd1; if (z) { +#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__ int step = BitHacks::NumberOfTrailingZeros(z) / 8 + 1; +#else + int step = BitHacks::NumberOfLeadingZeros(z) / 8 + 1; +#endif (*intPos++) += step; bitPos += step; } else { @@ -342,7 +345,6 @@ void GetPatternRow(Range b_row, PatternRow& p_row) } } } -#endif while (++bitPos != bitPosEnd) { ++(*intPos); From cd7b0fb8b84098d1a12f8aadac48c12faceb60c0 Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Fri, 17 Mar 2023 07:30:06 -0700 Subject: [PATCH 093/173] Upgrades all actions to the latest, resolving Node 12 warnings --- .github/workflows/build-winrt.yml | 18 +++++++++--------- .github/workflows/ci.yml | 22 +++++++++++----------- .github/workflows/codeql-analysis.yml | 2 +- .github/workflows/python-build.yml | 18 +++++++++--------- 4 files changed, 30 insertions(+), 30 deletions(-) diff --git a/.github/workflows/build-winrt.yml b/.github/workflows/build-winrt.yml index 7bdce312aa..d4d8832d0e 100644 --- a/.github/workflows/build-winrt.yml +++ b/.github/workflows/build-winrt.yml @@ -2,7 +2,7 @@ name: build-winrt on: release: types: [published] - + workflow_dispatch: inputs: publish: @@ -20,7 +20,7 @@ jobs: target: [Win32, x64, ARM, ARM64] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Create build environment shell: cmd @@ -39,7 +39,7 @@ jobs: working-directory: ${{runner.workspace}}/build run: cmake --build . -j8 --config Release - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: winrt-${{matrix.target}}-artifacts path: ${{runner.workspace}}/build/dist @@ -49,16 +49,16 @@ jobs: runs-on: windows-latest if: ${{ github.event_name == 'release' || github.event.inputs.publish == 'y' }} steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: winrt-Win32-artifacts - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: winrt-x64-artifacts - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: winrt-ARM-artifacts - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: winrt-ARM64-artifacts @@ -78,8 +78,8 @@ jobs: shell: cmd run: nuget push huycn.zxingcpp.winrt.nupkg -ApiKey ${{ secrets.NUGET_API_KEY }} -Source https://api.nuget.org/v3/index.json - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: nuget-package path: huycn.zxingcpp.winrt.nupkg - + diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index ee91c7886d..c4f0481d0b 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,9 +24,9 @@ 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@v2 + - uses: actions/checkout@v3 - name: Setup Python 3 - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: '3.x' - name: Create Build Environment @@ -79,18 +79,18 @@ jobs: - uses: actions/checkout@v3 with: ref: ${{github.ref}} - + - name: Build the ZXingCpp.xcframework shell: sh working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios run: ./build-release.sh - + - name: Upload .xcframework uses: actions/upload-artifact@v3 with: name: ios-artifacts path: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/ZXingCpp.xcframework - + - name: Build the demo app shell: sh working-directory: ${{runner.workspace}}/${{github.event.repository.name}}/wrappers/ios/demo @@ -99,13 +99,13 @@ jobs: build-android: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Build the lib/app working-directory: wrappers/android run: ./gradlew assembleDebug # build only the debug version of the aar (faster build) - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: android-artifacts path: "wrappers/android/zxingcpp/build/outputs/aar/zxingcpp-debug.aar" @@ -113,7 +113,7 @@ jobs: build-wasm: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: mymindstorm/setup-emsdk@v7 - name: Configure @@ -125,7 +125,7 @@ jobs: # - name: Test # run: node build/EmGlueTests.js - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: name: wasm-artifacts path: "build/zxing*" @@ -138,10 +138,10 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 with: python-version: ${{ matrix.python-version }} diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 3acd79f7dd..0534f25e28 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v2 + uses: actions/checkout@v3 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index b342a99689..1065024a0b 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -3,7 +3,7 @@ name: build-python-dist on: release: types: [published] - + workflow_dispatch: inputs: publish: @@ -25,10 +25,10 @@ jobs: os: [ubuntu-latest, macos-latest, windows-latest] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 - name: Install cibuildwheel run: python -m pip install cibuildwheel==2.11.2 @@ -39,7 +39,7 @@ jobs: CIBW_BUILD: cp39-* cp310-* cp311-* CIBW_SKIP: "*musllinux*" - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: path: ./wheelhouse/*.whl @@ -47,18 +47,18 @@ jobs: name: Build source distribution runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 with: submodules: recursive - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v4 - name: Build sdist working-directory: wrappers/python run: python setup.py sdist - - uses: actions/upload-artifact@v2 + - uses: actions/upload-artifact@v3 with: path: wrappers/python/dist/*.tar.gz @@ -70,12 +70,12 @@ jobs: # if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v') if: github.event_name == 'release' || github.event.inputs.publish == 'y' steps: - - uses: actions/download-artifact@v2 + - uses: actions/download-artifact@v3 with: name: artifact path: dist - - uses: pypa/gh-action-pypi-publish@master + - uses: pypa/gh-action-pypi-publish@release/v1 with: user: __token__ password: ${{ secrets.PYPI_TOKEN }} From b60fe96121228ff5c3616034aa829f46116800b9 Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Tue, 21 Mar 2023 19:36:26 -0700 Subject: [PATCH 094/173] Fixes one missed action update --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c4f0481d0b..f452f5bbc4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -114,7 +114,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: mymindstorm/setup-emsdk@v7 + - uses: mymindstorm/setup-emsdk@v12 - name: Configure run: emcmake cmake -Swrappers/wasm -Bbuild From 93e862b54b16e49a119b057f7654aa5e38c764a7 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 22 Mar 2023 15:41:15 +0100 Subject: [PATCH 095/173] Update codeql action versions --- .github/workflows/codeql-analysis.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index 0534f25e28..3937ed1f5f 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -41,7 +41,7 @@ jobs: # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v1 + uses: github/codeql-action/init@v2 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,7 +52,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v1 + uses: github/codeql-action/autobuild@v2 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -66,4 +66,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v1 + uses: github/codeql-action/analyze@v2 From acce387f23046b3bf3f9f2c6036dbec3e987c89d Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 22 Mar 2023 19:00:13 +0100 Subject: [PATCH 096/173] fix build regression on Windows ARM/ARM64 --- core/src/BitHacks.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index cfde406734..7d95d2cd4e 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -12,7 +12,7 @@ #if defined(__clang__) || defined(__GNUC__) #define ZX_HAS_GCC_BUILTINS -#elif defined(_MSC_VER) +#elif defined(_MSC_VER) && !defined(_M_ARM) && !defined(_M_ARM64) #include #define ZX_HAS_MSC_BUILTINS #endif From 51b9fd7d42301a2cc8b74af8dc4b40c30985baba Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 22 Mar 2023 22:05:26 +0100 Subject: [PATCH 097/173] CI: update cibuildwheel version to 2.12.1 Hopefully this fixes the build regression on macos. --- .github/workflows/python-build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index 1065024a0b..1b64987c4a 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -31,7 +31,7 @@ jobs: uses: actions/setup-python@v4 - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.11.2 + run: python -m pip install cibuildwheel==2.12.1 - name: Build wheels run: python -m cibuildwheel --output-dir wheelhouse wrappers/python From b45817d9bd9f6c38997f2a3a8849dc48e75f970f Mon Sep 17 00:00:00 2001 From: Trenton H <797416+stumpylog@users.noreply.github.com> Date: Thu, 16 Mar 2023 15:18:52 -0700 Subject: [PATCH 098/173] Refactor Python wrapper so the sdist include the core library code, negating the download at build time --- wrappers/python/CMakeLists.txt | 66 +++++++++------------------------- wrappers/python/MANIFEST.in | 6 ++-- wrappers/python/core | 1 + wrappers/python/pyproject.toml | 1 + wrappers/python/zxing.cmake | 1 + 5 files changed, 24 insertions(+), 51 deletions(-) create mode 120000 wrappers/python/core create mode 120000 wrappers/python/zxing.cmake diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 58d4d49673..1f054a77b2 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -1,60 +1,28 @@ cmake_minimum_required(VERSION 3.15) project(ZXingPython) -set (pybind11_git_repo https://github.com/pybind/pybind11.git) -set (pybind11_git_rev v2.10.2) +# Force to build by C++17. The zxing-cpp require C++17 to build +set(CMAKE_CXX_STANDARD 17) +set(CMAKE_CXX_STANDARD_REQUIRED ON) -# check if we are called from the top-level ZXing project -get_directory_property(hasParent PARENT_DIRECTORY) -if (NOT hasParent) - # Force to build by C++17. The zxing-cpp require C++17 to build - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) +option (BUILD_SHARED_LIBS "Link python module to shared lib" OFF) +option (BUILD_WRITERS "Build with writer support (encoders)" ON) +option (BUILD_READERS "Build with reader support (decoders)" ON) - option (BUILD_SHARED_LIBS "Link python module to shared lib" OFF) - option (BUILD_WRITERS "Build with writer support (encoders)" ON) - option (BUILD_READERS "Build with reader support (decoders)" ON) +# build the main library - # build the main library - if (EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/../../core) - # In development mode, when the whole zxing-cpp directory is checked out, build against head code. - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/../../core ZXing EXCLUDE_FROM_ALL) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) + include(${CMAKE_CURRENT_SOURCE_DIR}/../../zxing.cmake) +else() + message(FATAL_ERROR "Unable to locate zxing source code") +endif() - include(${CMAKE_CURRENT_SOURCE_DIR}/../../zxing.cmake) - zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) - else() - # we don't have access to the top-level cmake helpers -> simply fetch it unconditional - include(FetchContent) - FetchContent_Declare (pybind11 - GIT_REPOSITORY ${pybind11_git_repo} - GIT_TAG ${pybind11_git_rev}) - FetchContent_MakeAvailable (pybind11) +find_package(pybind11 CONFIG) - # Building from python source distribution (which does not include the whole repository but only python part) - # so we need to get c++ source git to build the python extension. The python distribution version (given in - # VERSION_INFO), matches the c++ version the distribution must build against ; hence the checkout of - # related tag. - find_package (Git REQUIRED) - execute_process( - COMMAND ${GIT_EXECUTABLE} ls-remote https://github.com/zxing-cpp/zxing-cpp.git tags/* - RESULT_VARIABLE RESULT - OUTPUT_VARIABLE OUTPUT) - string (FIND "${OUTPUT}" "refs/tags/v${VERSION_INFO}\n" FOUND_IDX) - if (FOUND_IDX LESS 0) - # Version not found in tags (suppose we are preparing future version), use master branch - FetchContent_Declare(zxing-cpp - GIT_REPOSITORY https://github.com/zxing-cpp/zxing-cpp.git) - else() - FetchContent_Declare(zxing-cpp - GIT_REPOSITORY https://github.com/zxing-cpp/zxing-cpp.git - GIT_TAG v${VERSION_INFO}) - endif() - if(NOT zxing-cpp_POPULATED) - FetchContent_Populate(zxing-cpp) - add_subdirectory(${zxing-cpp_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) - endif() - endif() -else() +if (NOT pybind11_FOUND) + set (pybind11_git_repo https://github.com/pybind/pybind11.git) + set (pybind11_git_rev v2.10.2) zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) endif() diff --git a/wrappers/python/MANIFEST.in b/wrappers/python/MANIFEST.in index e4f71fae29..b77220579c 100644 --- a/wrappers/python/MANIFEST.in +++ b/wrappers/python/MANIFEST.in @@ -1,2 +1,4 @@ -include CMakeLists.txt -include zxing.cpp \ No newline at end of file +global-include CMakeLists.txt +include zxing.cpp +include zxing.cmake +graft core/ diff --git a/wrappers/python/core b/wrappers/python/core new file mode 120000 index 0000000000..3d25ddeb2c --- /dev/null +++ b/wrappers/python/core @@ -0,0 +1 @@ +../../core \ No newline at end of file diff --git a/wrappers/python/pyproject.toml b/wrappers/python/pyproject.toml index dbeaae43ca..05d9355be2 100644 --- a/wrappers/python/pyproject.toml +++ b/wrappers/python/pyproject.toml @@ -4,5 +4,6 @@ requires = [ "setuptools_scm", "wheel", "cmake>=3.14", + "pybind11[global]>=2.10.1", ] build-backend = "setuptools.build_meta" diff --git a/wrappers/python/zxing.cmake b/wrappers/python/zxing.cmake new file mode 120000 index 0000000000..e0240de99c --- /dev/null +++ b/wrappers/python/zxing.cmake @@ -0,0 +1 @@ +../../zxing.cmake \ No newline at end of file From f0ec4027fe2116eb77a5aa691acf4117b278f944 Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Fri, 17 Mar 2023 07:38:27 -0700 Subject: [PATCH 099/173] Fixes the main CI configuration step --- wrappers/python/CMakeLists.txt | 37 +++++++++++++++++++--------------- wrappers/python/pyproject.toml | 2 +- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 1f054a77b2..fdb6f15d2a 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -1,28 +1,33 @@ cmake_minimum_required(VERSION 3.15) project(ZXingPython) -# Force to build by C++17. The zxing-cpp require C++17 to build -set(CMAKE_CXX_STANDARD 17) -set(CMAKE_CXX_STANDARD_REQUIRED ON) - -option (BUILD_SHARED_LIBS "Link python module to shared lib" OFF) -option (BUILD_WRITERS "Build with writer support (encoders)" ON) -option (BUILD_READERS "Build with reader support (decoders)" ON) - -# build the main library - -if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core) - add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) - include(${CMAKE_CURRENT_SOURCE_DIR}/../../zxing.cmake) +set (pybind11_git_repo https://github.com/pybind/pybind11.git) +set (pybind11_git_rev v2.10.4) + +# check if we are called from the top-level ZXing project +get_directory_property(hasParent PARENT_DIRECTORY) +if (NOT hasParent) + # Force to build by C++17. The zxing-cpp require C++17 to build + set(CMAKE_CXX_STANDARD 17) + set(CMAKE_CXX_STANDARD_REQUIRED ON) + + option (BUILD_SHARED_LIBS "Link python module to shared lib" OFF) + option (BUILD_WRITERS "Build with writer support (encoders)" ON) + option (BUILD_READERS "Build with reader support (decoders)" ON) + if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core) + add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) + include(${CMAKE_CURRENT_SOURCE_DIR}/../../zxing.cmake) + else() + message(FATAL_ERROR "Unable to locate zxing source code") + endif() else() - message(FATAL_ERROR "Unable to locate zxing source code") + zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) endif() +# Check pybind helpers are availible find_package(pybind11 CONFIG) if (NOT pybind11_FOUND) - set (pybind11_git_repo https://github.com/pybind/pybind11.git) - set (pybind11_git_rev v2.10.2) zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) endif() diff --git a/wrappers/python/pyproject.toml b/wrappers/python/pyproject.toml index 05d9355be2..2c0e65ab86 100644 --- a/wrappers/python/pyproject.toml +++ b/wrappers/python/pyproject.toml @@ -4,6 +4,6 @@ requires = [ "setuptools_scm", "wheel", "cmake>=3.14", - "pybind11[global]>=2.10.1", + "pybind11[global]>=2.10.4", ] build-backend = "setuptools.build_meta" From 48ea3829590cb7d2fa904ee69ae96176f64998e4 Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Fri, 17 Mar 2023 07:50:23 -0700 Subject: [PATCH 100/173] Fixes the include to point to the symlink again --- wrappers/python/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index fdb6f15d2a..2155f7c655 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -16,7 +16,7 @@ if (NOT hasParent) option (BUILD_READERS "Build with reader support (decoders)" ON) if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/core) add_subdirectory(${CMAKE_CURRENT_SOURCE_DIR}/core ZXing EXCLUDE_FROM_ALL) - include(${CMAKE_CURRENT_SOURCE_DIR}/../../zxing.cmake) + include(${CMAKE_CURRENT_SOURCE_DIR}/zxing.cmake) else() message(FATAL_ERROR "Unable to locate zxing source code") endif() From 2360f2ff895a7db84ef10020fe0f598846af9366 Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Wed, 22 Mar 2023 15:06:32 -0700 Subject: [PATCH 101/173] Removes extra call to find_package which should be handled by zxing_add_package --- wrappers/python/CMakeLists.txt | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index 2155f7c655..adfd5d8898 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -14,22 +14,16 @@ if (NOT hasParent) option (BUILD_SHARED_LIBS "Link python module to shared lib" OFF) 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) include(${CMAKE_CURRENT_SOURCE_DIR}/zxing.cmake) else() message(FATAL_ERROR "Unable to locate zxing source code") endif() -else() - zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) endif() -# Check pybind helpers are availible -find_package(pybind11 CONFIG) - -if (NOT pybind11_FOUND) - zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) -endif() +zxing_add_package(pybind11 pybind11 ${pybind11_git_repo} ${pybind11_git_rev}) # build the python module pybind11_add_module(zxingcpp zxing.cpp) From 57e25c3aab460c8986c0871390791e77fbe5a16c Mon Sep 17 00:00:00 2001 From: Trenton Holmes <797416+stumpylog@users.noreply.github.com> Date: Wed, 22 Mar 2023 15:14:21 -0700 Subject: [PATCH 102/173] python on macos appears to be Python 2, not 3. Use Python 3 explicitly --- .github/workflows/python-build.yml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index 1b64987c4a..4f931a00ce 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -29,12 +29,14 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 + with: + python-version: '3.9' - name: Install cibuildwheel - run: python -m pip install cibuildwheel==2.12.1 + run: python3 -m pip install cibuildwheel==2.12.1 - name: Build wheels - run: python -m cibuildwheel --output-dir wheelhouse wrappers/python + run: python3 -m cibuildwheel --output-dir wheelhouse wrappers/python env: CIBW_BUILD: cp39-* cp310-* cp311-* CIBW_SKIP: "*musllinux*" @@ -53,10 +55,12 @@ jobs: - name: Set up Python uses: actions/setup-python@v4 + with: + python-version: '3.9' - name: Build sdist working-directory: wrappers/python - run: python setup.py sdist + run: python3 setup.py sdist - uses: actions/upload-artifact@v3 with: From cbe4361e3a8541f4f51cf17b9b001727600e78ef Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 23 Mar 2023 01:03:54 +0100 Subject: [PATCH 103/173] python: update cmake and pybind minimum version There might be some minimum pybind11 version for the wrapper code to compile but it is not 2.10.4 --- wrappers/python/pyproject.toml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/wrappers/python/pyproject.toml b/wrappers/python/pyproject.toml index 2c0e65ab86..16484fe3ee 100644 --- a/wrappers/python/pyproject.toml +++ b/wrappers/python/pyproject.toml @@ -3,7 +3,7 @@ requires = [ "setuptools>=42", "setuptools_scm", "wheel", - "cmake>=3.14", - "pybind11[global]>=2.10.4", + "cmake>=3.15", + "pybind11[global]", ] build-backend = "setuptools.build_meta" From 02370a091aa8e0c0b39839d5064602b0cf45baf7 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 23 Mar 2023 01:33:26 +0100 Subject: [PATCH 104/173] CI: add msvc-analysis workflow --- .github/workflows/msvc-analysis.yml | 68 +++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/msvc-analysis.yml diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml new file mode 100644 index 0000000000..06cb5723e8 --- /dev/null +++ b/.github/workflows/msvc-analysis.yml @@ -0,0 +1,68 @@ +# This workflow uses actions that are not certified by GitHub. +# They are provided by a third-party and are governed by +# separate terms of service, privacy policy, and support +# documentation. +# +# Find more information at: +# https://github.com/microsoft/msvc-code-analysis-action + +name: Microsoft C++ Code Analysis + +on: workflow_dispatch + +#on: +# push: +# branches: [ "master" ] +# pull_request: +# branches: [ "master" ] +# schedule: +# - cron: '20 18 * * 1' + +env: + # Path to the CMake build directory. + build: '${{ github.workspace }}/build' + +permissions: + contents: read + +jobs: + analyze: + permissions: + contents: read # for actions/checkout to fetch code + security-events: write # for github/codeql-action/upload-sarif to upload SARIF results + actions: read # only required for a private repository by github/codeql-action/upload-sarif to get the Action run status + name: Analyze + runs-on: windows-latest + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Configure CMake + run: cmake -B ${{ env.build }} + + # Build is not required unless generated source files are used + # - name: Build CMake + # run: cmake --build ${{ env.build }} + + - name: Initialize MSVC Code Analysis + uses: microsoft/msvc-code-analysis-action@04825f6d9e00f87422d6bf04e1a38b1f3ed60d99 + # Provide a unique ID to access the sarif output path + id: run-analysis + with: + cmakeBuildDirectory: ${{ env.build }} + # Ruleset file that will determine what checks will be run + ruleset: NativeRecommendedRules.ruleset + + # Upload SARIF file to GitHub Code Scanning Alerts + - name: Upload SARIF to GitHub + uses: github/codeql-action/upload-sarif@v2 + with: + sarif_file: ${{ steps.run-analysis.outputs.sarif }} + + # Upload SARIF file as an Artifact to download and view + # - name: Upload SARIF as an Artifact + # uses: actions/upload-artifact@v3 + # with: + # name: sarif-file + # path: ${{ steps.run-analysis.outputs.sarif }} From 14be2ac9e2aec8183caa0f0f66cc5d1b01d406ee Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 23 Mar 2023 12:46:32 +0100 Subject: [PATCH 105/173] c++: trivial warning fixes --- core/src/BitMatrix.cpp | 2 +- core/src/qrcode/QRFormatInformation.cpp | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/core/src/BitMatrix.cpp b/core/src/BitMatrix.cpp index d62e182b45..729a5752b3 100644 --- a/core/src/BitMatrix.cpp +++ b/core/src/BitMatrix.cpp @@ -29,7 +29,7 @@ BitMatrix::setRegion(int left, int top, int width, int height) throw std::invalid_argument("BitMatrix::setRegion(): The region must fit inside the matrix"); } for (int y = top; y < bottom; y++) { - size_t offset = y * _width; + auto offset = y * _width; for (int x = left; x < right; x++) { _bits[offset + x] = SET_V; } diff --git a/core/src/qrcode/QRFormatInformation.cpp b/core/src/qrcode/QRFormatInformation.cpp index 574020680f..9195a7fa65 100644 --- a/core/src/qrcode/QRFormatInformation.cpp +++ b/core/src/qrcode/QRFormatInformation.cpp @@ -7,6 +7,7 @@ #include "QRFormatInformation.h" #include "BitHacks.h" +#include "ZXAlgorithms.h" #include @@ -99,7 +100,7 @@ static FormatInformation FindBestFormatInfo(int mask, const std::array Date: Thu, 23 Mar 2023 13:00:03 +0100 Subject: [PATCH 106/173] CI: update to msvc-code-analysis-action@v0.1.1 hopefully this makes the workflow run at all now. --- .github/workflows/msvc-analysis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml index 06cb5723e8..68ac970da2 100644 --- a/.github/workflows/msvc-analysis.yml +++ b/.github/workflows/msvc-analysis.yml @@ -46,7 +46,7 @@ jobs: # run: cmake --build ${{ env.build }} - name: Initialize MSVC Code Analysis - uses: microsoft/msvc-code-analysis-action@04825f6d9e00f87422d6bf04e1a38b1f3ed60d99 + uses: microsoft/msvc-code-analysis-action@v0.1.1 # Provide a unique ID to access the sarif output path id: run-analysis with: From d58a756fe2bd86cb1d23bff5e2f8276e91e966dc Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 23 Mar 2023 13:10:44 +0100 Subject: [PATCH 107/173] CI: add missing buildConfiguration param One step at a time, it seem... --- .github/workflows/msvc-analysis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml index 68ac970da2..57edb81090 100644 --- a/.github/workflows/msvc-analysis.yml +++ b/.github/workflows/msvc-analysis.yml @@ -39,7 +39,7 @@ jobs: uses: actions/checkout@v3 - name: Configure CMake - run: cmake -B ${{ env.build }} + run: cmake -B ${{ env.build }} -DCMAKE_BUILD_TYPE=${{ env.config }} # Build is not required unless generated source files are used # - name: Build CMake @@ -51,6 +51,7 @@ jobs: id: run-analysis with: cmakeBuildDirectory: ${{ env.build }} + buildConfiguration: ${{ env.config }} # Ruleset file that will determine what checks will be run ruleset: NativeRecommendedRules.ruleset From d728952f5adaa0bb2db45759cbd20337c983e8b5 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 23 Mar 2023 13:35:05 +0100 Subject: [PATCH 108/173] CI: ammend last commit (missing `config:` line) --- .github/workflows/msvc-analysis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml index 57edb81090..e57d785269 100644 --- a/.github/workflows/msvc-analysis.yml +++ b/.github/workflows/msvc-analysis.yml @@ -21,6 +21,7 @@ on: workflow_dispatch env: # Path to the CMake build directory. build: '${{ github.workspace }}/build' + config: 'Debug' permissions: contents: read From 8a60b00c4301c66d81224e353ac8a038ac5e6bf9 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 23 Mar 2023 14:11:02 +0100 Subject: [PATCH 109/173] c++: fix a bunch of random msvc-code-analyzer warnings --- core/src/ReadBarcode.cpp | 2 +- core/src/TextUtfEncoding.cpp | 2 +- core/src/aztec/AZDetector.cpp | 2 +- core/src/pdf417/PDFDetector.h | 2 +- core/src/qrcode/QREncoder.cpp | 2 +- core/src/qrcode/QRReader.cpp | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 37ff3dfab1..4198530adf 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -52,7 +52,7 @@ class LumImagePyramid template void addLayer() { - auto siv = layers.back(); + const auto& siv = layers.back(); buffers.emplace_back(siv.width() / N, siv.height() / N); layers.push_back(buffers.back()); auto& div = buffers.back(); diff --git a/core/src/TextUtfEncoding.cpp b/core/src/TextUtfEncoding.cpp index 7c366371ad..084ec76b40 100644 --- a/core/src/TextUtfEncoding.cpp +++ b/core/src/TextUtfEncoding.cpp @@ -17,7 +17,7 @@ std::string ToUtf8(std::wstring_view str) // Same as `ToUtf8()` above, except if angleEscape set, places non-graphical characters in angle brackets with text name std::string ToUtf8(std::wstring_view str, const bool angleEscape) { - return ZXing::ToUtf8(angleEscape ? EscapeNonGraphical(str) : str); + return angleEscape ? ZXing::ToUtf8(EscapeNonGraphical(str)) : ZXing::ToUtf8(str); } std::wstring FromUtf8(std::string_view utf8) diff --git a/core/src/aztec/AZDetector.cpp b/core/src/aztec/AZDetector.cpp index da4ba6645c..87915060aa 100644 --- a/core/src/aztec/AZDetector.cpp +++ b/core/src/aztec/AZDetector.cpp @@ -337,7 +337,7 @@ DetectorResults Detect(const BitMatrix& image, bool isPure, bool tryHarder, int DetectorResults res; auto fps = isPure ? FindPureFinderPattern(image) : FindFinderPatterns(image, tryHarder); - for (auto fp : fps) { + for (const auto& fp : fps) { auto fpQuad = FindConcentricPatternCorners(image, fp, fp.size, 3); if (!fpQuad) continue; diff --git a/core/src/pdf417/PDFDetector.h b/core/src/pdf417/PDFDetector.h index 1316c0a52c..81d22c34c1 100644 --- a/core/src/pdf417/PDFDetector.h +++ b/core/src/pdf417/PDFDetector.h @@ -35,7 +35,7 @@ class Detector { std::shared_ptr bits; std::list, 8>> points; - int rotation; + int rotation = -1; }; static Result Detect(const BinaryBitmap& image, bool multiple, bool tryRotate); diff --git a/core/src/qrcode/QREncoder.cpp b/core/src/qrcode/QREncoder.cpp index 58864791db..cc3f71d8d3 100644 --- a/core/src/qrcode/QREncoder.cpp +++ b/core/src/qrcode/QREncoder.cpp @@ -256,7 +256,7 @@ static bool WillFit(int numInputBits, const Version& version, ErrorCorrectionLev // numBytes = 196 int numBytes = version.totalCodewords(); // getNumECBytes = 130 - auto ecBlocks = version.ecBlocksForLevel(ecLevel); + auto& ecBlocks = version.ecBlocksForLevel(ecLevel); int numEcBytes = ecBlocks.totalCodewords(); // getNumDataBytes = 196 - 130 = 66 int numDataBytes = numBytes - numEcBytes; diff --git a/core/src/qrcode/QRReader.cpp b/core/src/qrcode/QRReader.cpp index 7d825f7f82..a37ac0634b 100644 --- a/core/src/qrcode/QRReader.cpp +++ b/core/src/qrcode/QRReader.cpp @@ -65,7 +65,7 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const if (_hints.hasFormat(BarcodeFormat::QRCode)) { auto allFPSets = GenerateFinderPatternSets(allFPs); - for (auto& fpSet : allFPSets) { + for (const auto& fpSet : allFPSets) { if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr)) continue; @@ -88,7 +88,7 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const } if (_hints.hasFormat(BarcodeFormat::MicroQRCode) && !(maxSymbols && Size(results) == maxSymbols)) { - for (auto fp : allFPs) { + for (const auto& fp : allFPs) { if (Contains(usedFPs, fp)) continue; From 002690a6032f247db65379dd9f8e91da568f618d Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 23 Mar 2023 17:50:28 +0100 Subject: [PATCH 110/173] CI: suppress noisy "Arithmatic overflow" warnings --- .github/workflows/msvc-analysis.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/msvc-analysis.yml b/.github/workflows/msvc-analysis.yml index e57d785269..269f231f7e 100644 --- a/.github/workflows/msvc-analysis.yml +++ b/.github/workflows/msvc-analysis.yml @@ -55,6 +55,7 @@ jobs: buildConfiguration: ${{ env.config }} # Ruleset file that will determine what checks will be run ruleset: NativeRecommendedRules.ruleset + additionalArgs: /wd26451 # Suppress C26451, apparently bogous in VS2019 # Upload SARIF file to GitHub Code Scanning Alerts - name: Upload SARIF to GitHub From bec4f38e2833fc77e982424dee573b70c4864cc3 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 24 Mar 2023 10:48:06 +0100 Subject: [PATCH 111/173] DMDetector: trivial code cleanup --- core/src/datamatrix/DMDetector.cpp | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 20d6250528..1145357509 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -742,6 +742,10 @@ static DetectorResult Scan(EdgeTracer& startTracer, std::array %.1f, %.1f ^> %.1f, %.1f\n", bl.x, bl.y, tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, tr.x, tr.y); @@ -750,18 +754,13 @@ static DetectorResult Scan(EdgeTracer& startTracer, std::array= 10 && dimT <= 144 && dimR >= 8 && dimR <= 144); - auto movedTowardsBy = [](PointF& a, PointF b1, PointF b2, auto d) { + auto movedTowardsBy = [](PointF a, PointF b1, PointF b2, auto d) { return a + d * normalized(normalized(b1 - a) + normalized(b2 - a)); }; From e95913421b8384c5d428d1aa87d564e20cfc5d3e Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 24 Mar 2023 11:07:50 +0100 Subject: [PATCH 112/173] DMDetector: new meanModSize calculation * potentially combine neighboring gaps instead of discarding them * allows for relaxed regression line evaluation threshold (1.2 vs. 1.0) --- core/src/datamatrix/DMDetector.cpp | 28 +++++++++++++++++++++++----- 1 file changed, 23 insertions(+), 5 deletions(-) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 1145357509..3fdaecfe13 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -426,7 +426,7 @@ class DMRegressionLine : public RegressionLine assert(_points.size() > 3); // re-evaluate and filter out all points too far away. required for the gapSizes calculation. - evaluate(1.0, true); + evaluate(1.2, true); std::vector gapSizes, modSizes; gapSizes.reserve(_points.size()); @@ -452,13 +452,31 @@ class DMRegressionLine : public RegressionLine modSizes.push_back(sumFront + distance(end, project(_points.back()))); modSizes.front() = 0; // the first element is an invalid sumBack value, would be pop_front() if vector supported this auto lineLength = distance(beg, end) - unitPixelDist; - auto meanModSize = average(modSizes, [](double){ return true; }); + auto [iMin, iMax] = std::minmax_element(modSizes.begin() + 1, modSizes.end()); + auto meanModSize = average(modSizes, [](double dist){ return dist > 0; }); #ifdef PRINT_DEBUG printf("unit pixel dist: %.1f\n", unitPixelDist); - printf("lineLength: %.1f, meanModSize: %.1f, gaps: %lu\n", lineLength, meanModSize, modSizes.size()); + printf("lineLength: %.1f, meanModSize: %.1f (min: %.1f, max: %.1f), gaps: %lu\n", lineLength, meanModSize, *iMin, *iMax, + modSizes.size()); + for (auto v : modSizes) + printf("%.1f ", v); + printf("\n"); #endif - for (int i = 0; i < 2; ++i) - meanModSize = average(modSizes, [=](double dist) { return std::abs(dist - meanModSize) < meanModSize / (2 + i); }); + + if (*iMax > 2 * *iMin) { + for (int i = 1; i < Size(modSizes) - 2; ++i) { + if (modSizes[i] > 0 && modSizes[i] + modSizes[i + 2] < meanModSize * 1.4) + modSizes[i] += std::exchange(modSizes[i + 2], 0); + else if (modSizes[i] > meanModSize * 1.6) + modSizes[i] = 0; + } +#ifdef PRINT_DEBUG + for (auto v : modSizes) + printf("%.1f ", v); + printf("\n"); +#endif + meanModSize = average(modSizes, [](double dist) { return dist > 0; }); + } #ifdef PRINT_DEBUG printf("post filter meanModSize: %.1f\n", meanModSize); #endif From 21fbbe616592626fadcba0756c5dd82a0aa118fe Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 24 Mar 2023 11:16:57 +0100 Subject: [PATCH 113/173] DMDetector: printf/PRINT_DEBUG code cosmetic --- core/src/datamatrix/DMDetector.cpp | 34 ++++++++++++++---------------- 1 file changed, 16 insertions(+), 18 deletions(-) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 3fdaecfe13..5ce14fa4ca 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -29,6 +29,16 @@ #include #include +#ifndef PRINT_DEBUG +#define printf(...){} +#define printv(...){} +#else +#define printv(fmt, vec) \ +for (auto v : vec) \ + printf(fmt, v); \ +printf("\n"); +#endif + namespace ZXing::DataMatrix { /** @@ -454,14 +464,11 @@ class DMRegressionLine : public RegressionLine auto lineLength = distance(beg, end) - unitPixelDist; auto [iMin, iMax] = std::minmax_element(modSizes.begin() + 1, modSizes.end()); auto meanModSize = average(modSizes, [](double dist){ return dist > 0; }); -#ifdef PRINT_DEBUG + printf("unit pixel dist: %.1f\n", unitPixelDist); printf("lineLength: %.1f, meanModSize: %.1f (min: %.1f, max: %.1f), gaps: %lu\n", lineLength, meanModSize, *iMin, *iMax, modSizes.size()); - for (auto v : modSizes) - printf("%.1f ", v); - printf("\n"); -#endif + printv("%.1f ", modSizes); if (*iMax > 2 * *iMin) { for (int i = 1; i < Size(modSizes) - 2; ++i) { @@ -470,16 +477,12 @@ class DMRegressionLine : public RegressionLine else if (modSizes[i] > meanModSize * 1.6) modSizes[i] = 0; } -#ifdef PRINT_DEBUG - for (auto v : modSizes) - printf("%.1f ", v); - printf("\n"); -#endif + printv("%.1f ", modSizes); + meanModSize = average(modSizes, [](double dist) { return dist > 0; }); } -#ifdef PRINT_DEBUG printf("post filter meanModSize: %.1f\n", meanModSize); -#endif + return lineLength / meanModSize; } }; @@ -649,9 +652,8 @@ class EdgeTracer : public BitMatrixCursorF corner = p; std::swap(d, dir); traceStep(-1 * dir, 2, false); -#ifdef PRINT_DEBUG printf("turn: %.0f x %.0f -> %.2f, %.2f\n", p.x, p.y, d.x, d.y); -#endif + return isIn(corner) && isIn(p); } }; @@ -737,10 +739,8 @@ static DetectorResult Scan(EdgeTracer& startTracer, std::array %.1f, %.1f (%d : %d : %d : %d)\n", bl.x, bl.y, tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, (int)lenL, (int)lenB, (int)lenT, (int)lenR); -#endif for (auto* l : {&lineL, &lineB, &lineT, &lineR}) l->evaluate(1.0); @@ -764,11 +764,9 @@ static DetectorResult Scan(EdgeTracer& startTracer, std::array %.1f, %.1f ^> %.1f, %.1f\n", bl.x, bl.y, tl.x - bl.x, tl.y - bl.y, br.x - bl.x, br.y - bl.y, tr.x, tr.y); printf("dim: %d x %d\n", dimT, dimR); -#endif // if we have an almost square (invalid rectangular) data matrix dimension, we try to parse it by assuming a // square. we use the dimension that is closer to an integral value. all valid rectangular symbols differ in From 85705c2446c08f031ff30c2ee8c7423f7864edcf Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 24 Mar 2023 15:38:36 +0100 Subject: [PATCH 114/173] DMDetector: reduce overhead of c++-20 builds by around 50% This significantly speeds up the position-independent DataMatrix detection that is enabled in c++-20 build mode. The total falsepositives runtime is reduced by around 15%. --- core/src/datamatrix/DMDetector.cpp | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/core/src/datamatrix/DMDetector.cpp b/core/src/datamatrix/DMDetector.cpp index 5ce14fa4ca..04799791b2 100644 --- a/core/src/datamatrix/DMDetector.cpp +++ b/core/src/datamatrix/DMDetector.cpp @@ -656,17 +656,31 @@ class EdgeTracer : public BitMatrixCursorF return isIn(corner) && isIn(p); } + + bool moveToNextWhiteAfterBlack() + { + assert(std::abs(d.x + d.y) == 1); + + FastEdgeToEdgeCounter e2e(BitMatrixCursorI(*img, PointI(p), PointI(d))); + int steps = e2e.stepToNextEdge(INT_MAX); + if (!steps) + return false; + step(steps); + if(isWhite()) + return true; + + steps = e2e.stepToNextEdge(INT_MAX); + if (!steps) + return false; + return step(steps); + } }; static DetectorResult Scan(EdgeTracer& startTracer, std::array& lines) { - while (startTracer.step()) { + while (startTracer.moveToNextWhiteAfterBlack()) { log(startTracer.p); - // continue until we cross from black into white - if (!startTracer.edgeAtBack().isWhite()) - continue; - PointF tl, bl, br, tr; auto& [lineL, lineB, lineR, lineT] = lines; @@ -822,8 +836,8 @@ static DetectorResults DetectNew(const BitMatrix& image, bool tryHarder, bool tr constexpr int minSymbolSize = 8 * 2; // minimum realistic size in pixel: 8 modules x 2 pixels per module - for (auto dir : {PointF(-1, 0), PointF(1, 0), PointF(0, -1), PointF(0, 1)}) { - auto center = PointF(image.width() / 2, image.height() / 2); + for (auto dir : {PointF{-1, 0}, {1, 0}, {0, -1}, {0, 1}}) { + auto center = PointI(image.width() / 2, image.height() / 2); auto startPos = centered(center - center * dir + minSymbolSize / 2 * dir); history.clear(); From 9980b2da410c4194578d942cd1d6d4d619a05722 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 24 Mar 2023 15:40:59 +0100 Subject: [PATCH 115/173] BitHacks: add c++-20's based implementations --- core/src/BitHacks.h | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index 7d95d2cd4e..4cb378edef 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -10,6 +10,13 @@ #include #include +#if __has_include() +#include +#if __cplusplus > 201703L && defined(__ANDROID__) // NDK 25.1.8937393 has the implementation but fails to advertise it +#define __cpp_lib_bitops 201907L +#endif +#endif + #if defined(__clang__) || defined(__GNUC__) #define ZX_HAS_GCC_BUILTINS #elif defined(_MSC_VER) && !defined(_M_ARM) && !defined(_M_ARM64) @@ -30,6 +37,9 @@ namespace ZXing::BitHacks { template>> inline int NumberOfLeadingZeros(T x) { +#ifdef __cpp_lib_bitops + return std::countl_zero(static_cast>(x)); +#else if constexpr (sizeof(x) <= 4) { if (x == 0) return 32; @@ -60,6 +70,7 @@ inline int NumberOfLeadingZeros(T x) return n; #endif } +#endif } /// @@ -69,6 +80,9 @@ template>> inline int NumberOfTrailingZeros(T v) { assert(v != 0); +#ifdef __cpp_lib_bitops + return std::countr_zero(static_cast>(v)); +#else if constexpr (sizeof(v) <= 4) { #ifdef ZX_HAS_GCC_BUILTINS return __builtin_ctz(v); @@ -111,6 +125,7 @@ inline int NumberOfTrailingZeros(T v) return n; #endif } +#endif } inline uint32_t Reverse(uint32_t v) @@ -133,7 +148,9 @@ inline uint32_t Reverse(uint32_t v) inline int CountBitsSet(uint32_t v) { -#ifdef ZX_HAS_GCC_BUILTINS +#ifdef __cpp_lib_bitops + return std::popcount(v); +#elif defined(ZX_HAS_GCC_BUILTINS) return __builtin_popcount(v); #else v = v - ((v >> 1) & 0x55555555); // reuse input as temporary From 5ff37c2491ce4f29c4ac43e62589a9cd58cd0bad Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 24 Mar 2023 15:42:51 +0100 Subject: [PATCH 116/173] RegressionLine: add (currently unused) pop_front() function --- core/src/RegressionLine.h | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/core/src/RegressionLine.h b/core/src/RegressionLine.h index e7893f6679..015e8c98f3 100644 --- a/core/src/RegressionLine.h +++ b/core/src/RegressionLine.h @@ -95,6 +95,11 @@ class RegressionLine } void pop_back() { _points.pop_back(); } + void pop_front() + { + std::rotate(_points.begin(), _points.begin() + 1, _points.end()); + _points.pop_back(); + } void setDirectionInward(PointF d) { _directionInward = normalized(d); } From 054d8bb09bcc3661a7a6562ab5456041d5eee4e5 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 24 Mar 2023 15:52:18 +0100 Subject: [PATCH 117/173] QRReader: add debugging/logging code --- core/src/qrcode/QRDetector.cpp | 20 +++++++++++++++++++- core/src/qrcode/QRReader.cpp | 22 ++++++++++++++++++++++ 2 files changed, 41 insertions(+), 1 deletion(-) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index 7e9bb66980..b4a217148b 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -52,6 +52,7 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t skip = MIN_SKIP; std::vector res; + [[maybe_unused]] int N = 0; PatternRow row; for (int y = skip - 1; y < height; y += skip) { @@ -63,10 +64,16 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t // make sure p is not 'inside' an already found pattern area if (FindIf(res, [p](const auto& old) { return distance(p, old) < old.size / 2; }) == res.end()) { + log(p); + N++; auto pattern = LocateConcentricPattern(image, PATTERN, p, Reduce(next) * 3); // 3 for very skewed samples if (pattern) { log(*pattern, 3); + log(*pattern + PointF(.2, 0), 3); + log(*pattern - PointF(.2, 0), 3); + log(*pattern + PointF(0, .2), 3); + log(*pattern - PointF(0, .2), 3); assert(image.get(pattern->x, pattern->y)); res.push_back(*pattern); } @@ -78,6 +85,8 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t } } + printf("FPs? : %d\n", N); + return res; } @@ -176,6 +185,9 @@ FinderPatternSets GenerateFinderPatternSets(FinderPatterns& patterns) res.reserve(sets.size()); for (auto& [d, s] : sets) res.push_back(s); + + printf("FPSets: %d\n", Size(res)); + return res; } @@ -270,7 +282,7 @@ static PerspectiveTransform Mod2Pix(int dimension, PointF brOffset, Quadrilatera static std::optional LocateAlignmentPattern(const BitMatrix& image, int moduleSize, PointF estimate) { - log(estimate, 2); + log(estimate, 4); for (auto d : {PointF{0, 0}, {0, -1}, {0, 1}, {-1, 0}, {1, 0}, {-1, -1}, {1, -1}, {1, 1}, {-1, 1}, #if 1 @@ -460,6 +472,12 @@ DetectorResult SampleQR(const BitMatrix& image, const FinderPatternSet& fp) apP.set(x, y, projectM2P(x, y)); } +#ifdef PRINT_DEBUG + for (int y = 0; y <= N; ++y) + for (int x = 0; x <= N; ++x) + log(*apP(x, y), 2); +#endif + // assemble a list of region-of-interests based on the found alignment pattern pixel positions ROIs rois; for (int y = 0; y < N; ++y) diff --git a/core/src/qrcode/QRReader.cpp b/core/src/qrcode/QRReader.cpp index a37ac0634b..ed5e4edbdc 100644 --- a/core/src/qrcode/QRReader.cpp +++ b/core/src/qrcode/QRReader.cpp @@ -48,6 +48,22 @@ Result Reader::decode(const BinaryBitmap& image) const detectorResult.bits().width() < 21 ? BarcodeFormat::MicroQRCode : BarcodeFormat::QRCode); } +void logFPSet(const FinderPatternSet& fps [[maybe_unused]]) +{ +#ifdef PRINT_DEBUG + auto drawLine = [](PointF a, PointF b) { + int steps = maxAbsComponent(b - a); + PointF dir = bresenhamDirection(PointF(b - a)); + for (int i = 0; i < steps; ++i) + log(centered(a + i * dir), 2); + }; + + drawLine(fps.bl, fps.tl); + drawLine(fps.tl, fps.tr); + drawLine(fps.tr, fps.bl); +#endif +} + Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const { auto binImg = image.getBitMatrix(); @@ -60,6 +76,10 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const auto allFPs = FindFinderPatterns(*binImg, _hints.tryHarder()); +#ifdef PRINT_DEBUG + printf("allFPs: %d\n", Size(allFPs)); +#endif + std::vector usedFPs; Results results; @@ -69,6 +89,8 @@ Results Reader::decode(const BinaryBitmap& image, int maxSymbols) const if (Contains(usedFPs, fpSet.bl) || Contains(usedFPs, fpSet.tl) || Contains(usedFPs, fpSet.tr)) continue; + logFPSet(fpSet); + auto detectorResult = SampleQR(*binImg, fpSet); if (detectorResult.isValid()) { auto decoderResult = Decode(detectorResult.bits()); From 113be00734455813fff0abbdc14f0122f5e2f2a5 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 25 Mar 2023 17:46:51 +0100 Subject: [PATCH 118/173] CI: add macos arm target to python-build (#535) --- .github/workflows/python-build.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index 4f931a00ce..cfa5b0965f 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -40,6 +40,7 @@ jobs: env: CIBW_BUILD: cp39-* cp310-* cp311-* CIBW_SKIP: "*musllinux*" + CIBW_ARCHS_MACOS: x86_64 arm64 - uses: actions/upload-artifact@v3 with: From b579057330665726a7793f75d589b9ab19810245 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 25 Mar 2023 18:29:05 +0100 Subject: [PATCH 119/173] c++: fix regression (ill advised prevention of copy) Storing a reference into a vector that is subsequently modified by push_back -> bad idea. --- core/src/ReadBarcode.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 4198530adf..37ff3dfab1 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -52,7 +52,7 @@ class LumImagePyramid template void addLayer() { - const auto& siv = layers.back(); + auto siv = layers.back(); buffers.emplace_back(siv.width() / N, siv.height() / N); layers.push_back(buffers.back()); auto& div = buffers.back(); From f4998ca908185c07a3f66284c3815d08517b5965 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 25 Mar 2023 18:31:19 +0100 Subject: [PATCH 120/173] python: switch to build standalone module with c++20 by default This enables position independent and multi-symbol detection for DataMatrix (if the compiler supports building c++20 code, otherwise cmake will fall back to c++17). --- wrappers/python/CMakeLists.txt | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/wrappers/python/CMakeLists.txt b/wrappers/python/CMakeLists.txt index adfd5d8898..5b2a41581f 100644 --- a/wrappers/python/CMakeLists.txt +++ b/wrappers/python/CMakeLists.txt @@ -7,9 +7,10 @@ set (pybind11_git_rev v2.10.4) # check if we are called from the top-level ZXing project get_directory_property(hasParent PARENT_DIRECTORY) if (NOT hasParent) - # Force to build by C++17. The zxing-cpp require C++17 to build - set(CMAKE_CXX_STANDARD 17) - set(CMAKE_CXX_STANDARD_REQUIRED ON) + # 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_STANDARD_REQUIRED OFF) option (BUILD_SHARED_LIBS "Link python module to shared lib" OFF) option (BUILD_WRITERS "Build with writer support (encoders)" ON) From 5634526c2b7307d78e3ce477e1adfd61c362d9d7 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 28 Mar 2023 09:17:57 +0200 Subject: [PATCH 121/173] CI: build universal2 python module for macos (#539) Turns out that cibuildwheel `CIB_ARCHS_MACOS: arm64` does not work when building modules with cmake based native code. This is the quick/easy way to get something usable on both intel and arm systems. See also https://github.com/nmwsharp/robust-laplacians-py/pull/8/files for reference. --- .github/workflows/python-build.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/python-build.yml b/.github/workflows/python-build.yml index cfa5b0965f..607d6b4ccb 100644 --- a/.github/workflows/python-build.yml +++ b/.github/workflows/python-build.yml @@ -40,7 +40,8 @@ jobs: env: CIBW_BUILD: cp39-* cp310-* cp311-* CIBW_SKIP: "*musllinux*" - CIBW_ARCHS_MACOS: x86_64 arm64 + CIBW_ARCHS_MACOS: universal2 + CIBW_ENVIRONMENT_MACOS: CMAKE_OSX_ARCHITECTURES="arm64;x86_64" - uses: actions/upload-artifact@v3 with: From f7f46ff9ebff9b4bd9b95eec21be1cc724995fb5 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 25 Mar 2023 22:29:03 +0100 Subject: [PATCH 122/173] python: update build/install requirements in README (remove git) #530 took care of that. --- wrappers/python/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/wrappers/python/README.md b/wrappers/python/README.md index a4079cc84e..244a230cbe 100644 --- a/wrappers/python/README.md +++ b/wrappers/python/README.md @@ -14,7 +14,7 @@ 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 python version), you need a suitable [build environment](https://github.com/zxing-cpp/zxing-cpp#build-instructions) including a c++ compiler and git.] +[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.] ## Usage From 138c8972471c8245d01f170b0db9d85ddce89937 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 29 Mar 2023 23:54:13 +0200 Subject: [PATCH 123/173] c++: code cosmetic --- core/src/qrcode/QRDetector.cpp | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index b4a217148b..dbb67b2c11 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -19,8 +19,6 @@ #include "Quadrilateral.h" #include "RegressionLine.h" -#include "BitMatrixIO.h" - #include #include #include @@ -29,7 +27,9 @@ #include #include -#ifndef PRINT_DEBUG +#ifdef PRINT_DEBUG +#include "BitMatrixIO.h" +#else #define printf(...){} #endif From b3b03ed039c17ffcb57a594fd7904d76d5d061d4 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 29 Mar 2023 23:59:57 +0200 Subject: [PATCH 124/173] QRDetector: add specialized FindPattern() helper for better performance This additional fast plausability test for the 1:1:3:1:1 pattern can reduce the false-positive run-time for specific QRCode scenarios by up to 25%. For the average case it is hardly noticeable, though. --- core/src/qrcode/QRDetector.cpp | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index dbb67b2c11..cc97c38e78 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -37,6 +37,16 @@ namespace ZXing::QRCode { constexpr auto PATTERN = FixedPattern<5, 7>{1, 1, 3, 1, 1}; +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.5); + }); +} + std::vector FindFinderPatterns(const BitMatrix& image, bool tryHarder) { constexpr int MIN_SKIP = 3; // 1 pixel/module times 3 modules/center @@ -59,7 +69,7 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t GetPatternRow(image, y, row, false); PatternView next = row; - while (next = FindLeftGuard(next, 0, PATTERN, 0.5), next.isValid()) { + while (next = FindPattern(next), next.isValid()) { PointF p(next.pixelsInFront() + next[0] + next[1] + next[2] / 2.0, y + 0.5); // make sure p is not 'inside' an already found pattern area From cee1d0e0d4424937c368f683dc3ee2478290ad39 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 7 Apr 2023 18:21:23 +0200 Subject: [PATCH 125/173] BitHacks: fix MSVC compiler warnings --- core/src/BitHacks.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index 4cb378edef..d943a1269a 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -10,7 +10,7 @@ #include #include -#if __has_include() +#if __has_include() && __cplusplus > 201703L // MSVC has the header but then warns about including it #include #if __cplusplus > 201703L && defined(__ANDROID__) // NDK 25.1.8937393 has the implementation but fails to advertise it #define __cpp_lib_bitops 201907L @@ -118,6 +118,7 @@ inline int NumberOfTrailingZeros(T v) #else #error "Implementation of __builtin_ctzll required" #endif + return 64; #else int n = NumberOfTrailingZeros(static_cast(v)); if (n == 32) From cf975502708f443216f99692974bc9c80e070dcc Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 17 Apr 2023 16:58:34 +0200 Subject: [PATCH 126/173] QRCode: implement edge2edge finder pattern detection Don't look for a strict 1:1:3:1:1 pattern but rather for a 1:3:1 pattern in black and a 1:1 pattern in white. This helps in detecting symbols that are too "thick" or "thin". Currently the maximal ration between a 1 in black and a 1 in white is 4:1. --- core/src/ConcentricFinder.h | 17 +++++++++----- core/src/Pattern.h | 42 ++++++++++++++++++++++++++++++---- core/src/qrcode/QRDetector.cpp | 7 +++--- 3 files changed, 53 insertions(+), 13 deletions(-) diff --git a/core/src/ConcentricFinder.h b/core/src/ConcentricFinder.h index ce81cc84d2..02c72c171c 100644 --- a/core/src/ConcentricFinder.h +++ b/core/src/ConcentricFinder.h @@ -108,24 +108,29 @@ struct ConcentricPattern : public PointF int size = 0; }; -template +template std::optional LocateConcentricPattern(const BitMatrix& image, PATTERN pattern, PointF center, int range) { auto cur = BitMatrixCursor(image, PointI(center), {}); int minSpread = image.width(), maxSpread = 0; + // TODO: setting maxError to 1 can subtantially help with detecting symbols with low print quality resulting in damaged + // finder patterns, but it sutantially increases the runtime (approx. 20% slower for the falsepositive images). + int maxError = 0; for (auto d : {PointI{0, 1}, {1, 0}}) { - int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range, true); - if (!spread) + int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range, true); + if (spread) + UpdateMinMax(minSpread, maxSpread, spread); + else if (--maxError < 0) return {}; - UpdateMinMax(minSpread, maxSpread, spread); } #if 1 for (auto d : {PointI{1, 1}, {1, -1}}) { int spread = CheckSymmetricPattern(cur.setDirection(d), pattern, range * 2, false); - if (!spread) + if (spread) + UpdateMinMax(minSpread, maxSpread, spread); + else if (--maxError < 0) return {}; - UpdateMinMax(minSpread, maxSpread, spread); } #endif diff --git a/core/src/Pattern.h b/core/src/Pattern.h index 8b8a651293..3d4cbc3ac9 100644 --- a/core/src/Pattern.h +++ b/core/src/Pattern.h @@ -132,13 +132,22 @@ struct BarAndSpace using value_type = T; T bar = {}, space = {}; // even index -> bar, odd index -> space - T& operator[](int i) { return reinterpret_cast(this)[i & 1]; } - T operator[](int i) const { return reinterpret_cast(this)[i & 1]; } + constexpr T& operator[](int i) noexcept { return reinterpret_cast(this)[i & 1]; } + constexpr T operator[](int i) const noexcept { return reinterpret_cast(this)[i & 1]; } bool isValid() const { return bar != T{} && space != T{}; } }; using BarAndSpaceI = BarAndSpace; +template +constexpr auto BarAndSpaceSum(const T* view) noexcept +{ + BarAndSpace res; + for (int i = 0; i < LEN; ++i) + res[i] += view[i]; + return res; +} + /** * @brief FixedPattern describes a compile-time constant (start/stop) pattern. * @@ -154,15 +163,40 @@ 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); } }; template using FixedSparcePattern = FixedPattern; -template +template float IsPattern(const PatternView& view, const FixedPattern& pattern, int spaceInPixel = 0, float minQuietZone = 0, float moduleSizeRef = 0) { + if constexpr (E2E) { + using float_t = double; + + auto widths = BarAndSpaceSum(view.data()); + auto sums = pattern.sums(); + 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 + return 0; + + if (minQuietZone && spaceInPixel < minQuietZone * modSize.space) + return 0; + + 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 moduleSize; + } + int width = view.sum(LEN); if (SUM > LEN && width < SUM) return 0; @@ -177,7 +211,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 + RELAXED_THRESHOLD * 0.25f) + 0.5f; + const float threshold = moduleSizeRef * (0.5f + E2E * 0.25f) + 0.5f; for (int x = 0; x < LEN; ++x) if (std::abs(view[x] - pattern[x] * moduleSizeRef) > threshold) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index cc97c38e78..dfc9d657a9 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -36,6 +36,7 @@ namespace ZXing::QRCode { constexpr auto PATTERN = FixedPattern<5, 7>{1, 1, 3, 1, 1}; +constexpr bool E2E = true; PatternView FindPattern(const PatternView& view) { @@ -43,7 +44,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.5); + return IsPattern(view, PATTERN, spaceInPixel, 0.5); }); } @@ -76,8 +77,8 @@ std::vector FindFinderPatterns(const BitMatrix& image, bool t if (FindIf(res, [p](const auto& old) { return distance(p, old) < old.size / 2; }) == res.end()) { log(p); N++; - auto pattern = LocateConcentricPattern(image, PATTERN, p, - Reduce(next) * 3); // 3 for very skewed samples + auto pattern = LocateConcentricPattern(image, PATTERN, p, + Reduce(next) * 3); // 3 for very skewed samples if (pattern) { log(*pattern, 3); log(*pattern + PointF(.2, 0), 3); From ec44682a20daf0d7e8464b76239f58a1db0f45a6 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 17 Apr 2023 16:59:28 +0200 Subject: [PATCH 127/173] ZXingReader: disable `tryDenoise` in `fast` mode --- example/ZXingReader.cpp | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/example/ZXingReader.cpp b/example/ZXingReader.cpp index 66a22123a3..83115a7bc6 100644 --- a/example/ZXingReader.cpp +++ b/example/ZXingReader.cpp @@ -54,10 +54,13 @@ static void PrintUsage(const char* exePath) static bool ParseOptions(int argc, char* argv[], DecodeHints& hints, bool& oneLine, bool& bytesOnly, std::vector& filePaths, std::string& outPath) { + hints.setTryDenoise(true); + for (int i = 1; i < argc; ++i) { auto is = [&](const char* str) { return strncmp(argv[i], str, strlen(argv[i])) == 0; }; if (is("-fast")) { hints.setTryHarder(false); + hints.setTryDenoise(false); } else if (is("-norotate")) { hints.setTryRotate(false); } else if (is("-noinvert")) { @@ -152,7 +155,6 @@ int main(int argc, char* argv[]) bool bytesOnly = false; int ret = 0; - hints.setTryDenoise(true); hints.setTextMode(TextMode::HRI); hints.setEanAddOnSymbol(EanAddOnSymbol::Read); From f0ff7ff9b56cade89e28309d9171d57e14c84c89 Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 17 Apr 2023 17:01:39 +0200 Subject: [PATCH 128/173] QRReader: cosmetic changes in debugging code --- core/src/qrcode/QRReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/qrcode/QRReader.cpp b/core/src/qrcode/QRReader.cpp index ed5e4edbdc..dd597cb2fc 100644 --- a/core/src/qrcode/QRReader.cpp +++ b/core/src/qrcode/QRReader.cpp @@ -55,7 +55,7 @@ void logFPSet(const FinderPatternSet& fps [[maybe_unused]]) int steps = maxAbsComponent(b - a); PointF dir = bresenhamDirection(PointF(b - a)); for (int i = 0; i < steps; ++i) - log(centered(a + i * dir), 2); + log(a + i * dir, 2); }; drawLine(fps.bl, fps.tl); From f44fb20af08fa087cf6c59bda75102043ceb063b Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 17 Apr 2023 17:25:45 +0200 Subject: [PATCH 129/173] ConcentricFinder: add check for how good points fit on the quadrilateral --- core/src/ConcentricFinder.cpp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index 0743d7f91c..b1ffa2bea5 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -170,6 +170,21 @@ static std::optional FitQadrilateralToPoints(PointF center, std: if (std::any_of(lines.begin(), lines.end(), [](auto line) { return !line.isValid(); })) return {}; + std::array beg = {corners[0] + 1, corners[1] + 1, corners[2] + 1, corners[3] + 1}; + std::array end = {corners[1], corners[2], corners[3], &points.back() + 1}; + + // check if all points belonging to each line segment are sufficiently close to that line + for (int i = 0; i < 4; ++i) + for (const PointF* p = beg[i]; p != end[i]; ++p) { + auto len = std::distance(beg[i], end[i]); + if (len > 3 && lines[i].distance(*p) > std::max(1., std::min(8., len / 8.))) { +#ifdef PRINT_DEBUG + printf("%d: %.2f > %.2f @ %.fx%.f\n", i, lines[i].distance(*p), std::distance(beg[i], end[i]) / 1., p->x, p->y); +#endif + return {}; + } + } + QuadrilateralF res; for (int i = 0; i < 4; ++i) res[i] = intersect(lines[i], lines[(i + 1) % 4]); From faef34728d795847ada6270ea3e63ca1990c6cde Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 17 Apr 2023 17:29:46 +0200 Subject: [PATCH 130/173] ConcentricFinder: improve FinetuneConcentricPatternCenter --- core/src/ConcentricFinder.cpp | 58 +++++++++++++++++++---------------- 1 file changed, 31 insertions(+), 27 deletions(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index b1ffa2bea5..dc4dfe1114 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -79,38 +79,25 @@ std::optional CenterOfRing(const BitMatrix& image, PointI center, int ra std::optional CenterOfRings(const BitMatrix& image, PointF center, int range, int numOfRings) { - int n = numOfRings; - PointF sum = numOfRings * center; - for (int i = 1; i < numOfRings; ++i) { - auto c = CenterOfRing(image, PointI(center), range, i + 1); - if (!c) + int n = 1; + PointF sum = center; + for (int i = 2; i < numOfRings + 1; ++i) { + auto c = CenterOfRing(image, PointI(center), range, i); + if (!c) { + if (n == 1) + return {}; + else + return sum / n; + } else if (distance(*c, center) > range / numOfRings / 2) { return {}; - // TODO: decide whether this wheighting depending on distance to the center is worth it - int weight = numOfRings - i; - sum += weight * *c; - n += weight; + } + + sum += *c; + n++; } return sum / n; } -std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize) -{ - // make sure we have at least one path of white around the center - auto res = CenterOfRing(image, PointI(center), range, 1); - if (!res) - return {}; - - center = *res; - res = CenterOfRings(image, center, range, finderPatternSize / 2); - if (!res || !image.get(*res)) - res = CenterOfDoubleCross(image, PointI(center), range, finderPatternSize / 2 + 1); - if (!res || !image.get(*res)) - res = center; - if (!res || !image.get(*res)) - return {}; - return res; -} - static std::vector CollectRingPoints(const BitMatrix& image, PointF center, int range, int edgeIndex, bool backup) { PointI centerI(center); @@ -239,4 +226,21 @@ std::optional FindConcentricPatternCorners(const BitMatrix& imag return res; } +std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, PointF center, int range, int finderPatternSize) +{ + // make sure we have at least one path of white around the center + if (auto res1 = CenterOfRing(image, PointI(center), range, 1); res1 && image.get(*res1)) { + // and then either at least one more ring around that + if (auto res2 = CenterOfRings(image, *res1, range, finderPatternSize / 2)) + return res2; + // or the center can be approximated by a square + if (FitSquareToPoints(image, *res1, range, 1, false)) + return res1; + // TODO: this is currently only keeping #258 alive, evaluate if still worth it + if (auto res2 = CenterOfDoubleCross(image, PointI(*res1), range, finderPatternSize / 2 + 1)) + return res2; + } + return {}; +} + } // ZXing From 64c6b98ee27dfec8a42c321ffd611bc9fbce4230 Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 20 Apr 2023 16:02:48 +0200 Subject: [PATCH 131/173] python: preload PIL images to properly report corrupt image file errors This should fix #541. --- wrappers/python/test.py | 4 ++-- wrappers/python/zxing.cpp | 47 ++++++++++++++++++++++----------------- 2 files changed, 28 insertions(+), 23 deletions(-) diff --git a/wrappers/python/test.py b/wrappers/python/test.py index ea979f9ee7..5d7f8cf30e 100644 --- a/wrappers/python/test.py +++ b/wrappers/python/test.py @@ -84,13 +84,13 @@ def test_write_read_cycle_pil(self): def test_read_invalid_type(self): self.assertRaisesRegex( - TypeError, "Unsupported type . Expect a PIL Image or numpy array", zxingcpp.read_barcode, "foo" + TypeError, "Could not convert to numpy array.", zxingcpp.read_barcode, "foo" ) def test_read_invalid_numpy_array_channels(self): import numpy as np self.assertRaisesRegex( - TypeError, "Unsupported number of channels for numpy array: 4", zxingcpp.read_barcode, + ValueError, "Unsupported number of channels for numpy array: 4", zxingcpp.read_barcode, np.zeros((100, 100, 4), np.uint8) ) diff --git a/wrappers/python/zxing.cpp b/wrappers/python/zxing.cpp index bce2e41963..cb87be376d 100644 --- a/wrappers/python/zxing.cpp +++ b/wrappers/python/zxing.cpp @@ -50,38 +50,43 @@ auto read_barcodes_impl(py::object _image, const BarcodeFormats& formats, bool t .setEanAddOnSymbol(ean_add_on_symbol); const auto _type = std::string(py::str(py::type::of(_image))); Image image; + ImageFormat imgfmt = ImageFormat::None; try { + if (_type.find("PIL.") != std::string::npos) { + _image.attr("load")(); + const auto mode = _image.attr("mode").cast(); + if (mode == "L") + imgfmt = ImageFormat::Lum; + else if (mode == "RGB") + imgfmt = ImageFormat::RGB; + else if (mode == "RGBA") + imgfmt = ImageFormat::RGBX; + else { + // Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL. + _image = _image.attr("convert")("L"); + imgfmt = ImageFormat::Lum; + } + } image = _image.cast(); - } - catch(...) { - throw py::type_error("Unsupported type " + _type + ". Expect a PIL Image or numpy array"); +#if PYBIND11_VERSION_HEX > 0x02080000 // py::raise_from is available starting from 2.8.0 + } catch (py::error_already_set &e) { + py::raise_from(e, PyExc_TypeError, ("Could not convert " + _type + " to numpy array.").c_str()); + throw py::error_already_set(); +#endif + } catch (...) { + throw py::type_error("Could not convert " + _type + " to numpy array. Expecting a PIL Image or numpy array."); } const auto height = narrow_cast(image.shape(0)); const auto width = narrow_cast(image.shape(1)); - auto channels = image.ndim() == 2 ? 1 : narrow_cast(image.shape(2)); - ImageFormat imgfmt; - if (_type.find("PIL.") != std::string::npos) { - const auto mode = _image.attr("mode").cast(); - if (mode == "L") - imgfmt = ImageFormat::Lum; - else if (mode == "RGB") - imgfmt = ImageFormat::RGB; - else if (mode == "RGBA") - imgfmt = ImageFormat::RGBX; - else { - // Unsupported mode in ImageFormat. Let's do conversion to L mode with PIL - image = _image.attr("convert")("L").cast(); - imgfmt = ImageFormat::Lum; - channels = 1; - } - } else { + const auto channels = image.ndim() == 2 ? 1 : narrow_cast(image.shape(2)); + if (imgfmt == ImageFormat::None) { // Assume grayscale or BGR image depending on channels number if (channels == 1) imgfmt = ImageFormat::Lum; else if (channels == 3) imgfmt = ImageFormat::BGR; else - throw py::type_error("Unsupported number of channels for numpy array: " + std::to_string(channels)); + throw py::value_error("Unsupported number of channels for numpy array: " + std::to_string(channels)); } const auto bytes = image.data(); From 2ba3eaad6e333ec7c5dcbabac80af2cc9060c1b9 Mon Sep 17 00:00:00 2001 From: Marius P Date: Sun, 23 Apr 2023 04:34:18 +0300 Subject: [PATCH 132/173] .gitignore ignore compile_commands.json The file compile_commands.json is generated automatically by kdesrc-build and https://invent.kde.org/frameworks/extra-cmake-modules. Just building zxing-cpp or e.g. kde-pim using kdesrc-build makes the git working directory for zxing-cpp have an unstaged change: added file compile_commands.json. https://community.kde.org/Get_Involved/development/Easy#Just_running_kdesrc-build_should_not_create_%22Unstaged_Changes%22_in_the_local_clone_of_a_KDE_git_repo --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index a4223377f5..9992cb687a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,4 @@ CMakeLists.txt.user *.lib *.d *.a - +compile_commands.json From a31578f78761c15c6a48a959bf58be51454ea7a2 Mon Sep 17 00:00:00 2001 From: th1722 <86101306+th1722@users.noreply.github.com> Date: Tue, 25 Apr 2023 01:21:39 +0900 Subject: [PATCH 133/173] Fix build with GCC 7 (amend: de93ced8) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit (In build message when building with GCC 7) [ 7%] Building CXX object core/CMakeFiles/ZXing.dir/src/GenericGF.cpp.o In file included from /home/th1722/zxing-cpp/core/src/BitMatrix.h:10:0, from /home/th1722/zxing-cpp/core/src/BitMatrix.cpp:7: /home/th1722/zxing-cpp/core/src/Matrix.h:41:48: warning: ‘no_sanitize’ attribute directive ignored [-Wattributes] Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) { ^ In file included from /home/th1722/zxing-cpp/core/src/BitMatrix.cpp:7:0: /home/th1722/zxing-cpp/core/src/BitMatrix.h:64:33: warning: ‘no_sanitize’ attribute directive ignored [-Wattributes] BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V) ^ In file included from /home/th1722/zxing-cpp/core/src/BitMatrix.h:10:0, from /home/th1722/zxing-cpp/core/src/BitMatrixIO.h:11, from /home/th1722/zxing-cpp/core/src/BitMatrixIO.cpp:7: /home/th1722/zxing-cpp/core/src/Matrix.h:41:48: warning: ‘no_sanitize’ attribute directive ignored [-Wattributes] Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) { ^ In file included from /home/th1722/zxing-cpp/core/src/BitMatrixIO.h:11:0, from /home/th1722/zxing-cpp/core/src/BitMatrixIO.cpp:7: /home/th1722/zxing-cpp/core/src/BitMatrix.h:64:33: warning: ‘no_sanitize’ attribute directive ignored [-Wattributes] BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V) ^ In file included from /home/th1722/zxing-cpp/core/src/BitMatrix.h:10:0, from /home/th1722/zxing-cpp/core/src/BitMatrixCursor.h:8, from /home/th1722/zxing-cpp/core/src/ConcentricFinder.h:8, from /home/th1722/zxing-cpp/core/src/ConcentricFinder.cpp:6: /home/th1722/zxing-cpp/core/src/Matrix.h:41:48: warning: ‘no_sanitize’ attribute directive ignored [-Wattributes] Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) { ^ In file included from /home/th1722/zxing-cpp/core/src/BitMatrixCursor.h:8:0, from /home/th1722/zxing-cpp/core/src/ConcentricFinder.h:8, from /home/th1722/zxing-cpp/core/src/ConcentricFinder.cpp:6: /home/th1722/zxing-cpp/core/src/BitMatrix.h:64:33: warning: ‘no_sanitize’ attribute directive ignored [-Wattributes] BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V) This is because 'no_sanitize' is only defined in GCC 8 and later. So avoid using 'no_sanitize' when building with GCC 7. --- core/src/BitMatrix.h | 2 +- core/src/Matrix.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/BitMatrix.h b/core/src/BitMatrix.h index 7550494729..c063d0e0ac 100644 --- a/core/src/BitMatrix.h +++ b/core/src/BitMatrix.h @@ -58,7 +58,7 @@ class BitMatrix BitMatrix() = default; -#ifdef __GNUC__ +#if defined(__llvm__) || (defined(__GNUC__) && (__GNUC__ > 7)) __attribute__((no_sanitize("signed-integer-overflow"))) #endif BitMatrix(int width, int height) : _width(width), _height(height), _bits(width * height, UNSET_V) diff --git a/core/src/Matrix.h b/core/src/Matrix.h index dee6895d46..36d4a0efe2 100644 --- a/core/src/Matrix.h +++ b/core/src/Matrix.h @@ -35,7 +35,7 @@ class Matrix public: Matrix() = default; -#ifdef __GNUC__ +#if defined(__llvm__) || (defined(__GNUC__) && (__GNUC__ > 7)) __attribute__((no_sanitize("signed-integer-overflow"))) #endif Matrix(int width, int height, value_t val = {}) : _width(width), _height(height), _data(_width * _height, val) { From 52a5f2e7501ff9017536958a2056dec4a1a2dceb Mon Sep 17 00:00:00 2001 From: axxel Date: Thu, 27 Apr 2023 10:51:22 +0200 Subject: [PATCH 134/173] android: drop the .idea folder completely from the repo This has come up in #555. A search for best practices convinced me that for our situation, simply not tracking any of the .idea files is currently the best option. --- wrappers/android/.gitignore | 31 ++-- wrappers/android/.idea/.gitignore | 3 - wrappers/android/.idea/.name | 1 - wrappers/android/.idea/codeStyles/Project.xml | 137 ------------------ .../.idea/codeStyles/codeStyleConfig.xml | 5 - wrappers/android/.idea/compiler.xml | 6 - wrappers/android/.idea/gradle.xml | 22 --- wrappers/android/.idea/jarRepositories.xml | 30 ---- wrappers/android/.idea/misc.xml | 9 -- wrappers/android/.idea/vcs.xml | 6 - 10 files changed, 19 insertions(+), 231 deletions(-) delete mode 100644 wrappers/android/.idea/.gitignore delete mode 100644 wrappers/android/.idea/.name delete mode 100644 wrappers/android/.idea/codeStyles/Project.xml delete mode 100644 wrappers/android/.idea/codeStyles/codeStyleConfig.xml delete mode 100644 wrappers/android/.idea/compiler.xml delete mode 100644 wrappers/android/.idea/gradle.xml delete mode 100644 wrappers/android/.idea/jarRepositories.xml delete mode 100644 wrappers/android/.idea/misc.xml delete mode 100644 wrappers/android/.idea/vcs.xml diff --git a/wrappers/android/.gitignore b/wrappers/android/.gitignore index 643d213c62..d699f8732b 100644 --- a/wrappers/android/.gitignore +++ b/wrappers/android/.gitignore @@ -1,15 +1,22 @@ -*.iml +# Gradle files .gradle -/local.properties -/.idea/caches -/.idea/libraries -/.idea/modules.xml -/.idea/workspace.xml -/.idea/navEditor.xml -/.idea/assetWizardSettings.xml -.DS_Store -/build -/captures +.gradle/ +build/ + +# Android Studio +.idea/ +/**/build/ +/**/local.properties +/**/out +/**/production +captures/ +.navigation/ .externalNativeBuild +*.ipr +*~ +*.swp +*.iml + +.DS_Store .cxx -local.properties + diff --git a/wrappers/android/.idea/.gitignore b/wrappers/android/.idea/.gitignore deleted file mode 100644 index eaf91e2ac6..0000000000 --- a/wrappers/android/.idea/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml diff --git a/wrappers/android/.idea/.name b/wrappers/android/.idea/.name deleted file mode 100644 index e0f5cac5d4..0000000000 --- a/wrappers/android/.idea/.name +++ /dev/null @@ -1 +0,0 @@ -ZXingCpp \ No newline at end of file diff --git a/wrappers/android/.idea/codeStyles/Project.xml b/wrappers/android/.idea/codeStyles/Project.xml deleted file mode 100644 index c4a490d90b..0000000000 --- a/wrappers/android/.idea/codeStyles/Project.xml +++ /dev/null @@ -1,137 +0,0 @@ - - - - - - - - - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/codeStyles/codeStyleConfig.xml b/wrappers/android/.idea/codeStyles/codeStyleConfig.xml deleted file mode 100644 index 79ee123c2b..0000000000 --- a/wrappers/android/.idea/codeStyles/codeStyleConfig.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/compiler.xml b/wrappers/android/.idea/compiler.xml deleted file mode 100644 index 7d7ec2eaff..0000000000 --- a/wrappers/android/.idea/compiler.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/gradle.xml b/wrappers/android/.idea/gradle.xml deleted file mode 100644 index 0ff903df8c..0000000000 --- a/wrappers/android/.idea/gradle.xml +++ /dev/null @@ -1,22 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/jarRepositories.xml b/wrappers/android/.idea/jarRepositories.xml deleted file mode 100644 index 52a77b6c6e..0000000000 --- a/wrappers/android/.idea/jarRepositories.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/misc.xml b/wrappers/android/.idea/misc.xml deleted file mode 100644 index 4daf7e2ce7..0000000000 --- a/wrappers/android/.idea/misc.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/wrappers/android/.idea/vcs.xml b/wrappers/android/.idea/vcs.xml deleted file mode 100644 index b2bdec2d71..0000000000 --- a/wrappers/android/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From fc57c6799ee3e6cdad2a69693f0836f3b4181079 Mon Sep 17 00:00:00 2001 From: axxel Date: Fri, 28 Apr 2023 09:35:35 +0200 Subject: [PATCH 135/173] clang-format: don't indent `extern "C" {}` blocks --- .clang-format | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/.clang-format b/.clang-format index 51ba5e80f4..5058084794 100644 --- a/.clang-format +++ b/.clang-format @@ -24,7 +24,14 @@ AllowAllParametersOfDeclarationOnNextLine: true AlwaysBreakAfterDefinitionReturnType: None AlwaysBreakTemplateDeclarations: Yes -BreakBeforeBraces: Mozilla +BreakBeforeBraces: Custom +BraceWrapping: + AfterClass: true + AfterEnum: true + AfterStruct: true + AfterUnion: true + AfterFunction: true + SplitEmptyFunction: false BreakBeforeBinaryOperators: NonAssignment BreakBeforeTernaryOperators: true ConstructorInitializerAllOnOneLineOrOnePerLine: true From 8cafc184803e449adc1aca796a60636cea63b310 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 29 Apr 2023 12:22:11 +0200 Subject: [PATCH 136/173] ZXingWriter: add support to encode binary file into barcode --- example/ZXingWriter.cpp | 29 +++++++++++++++++++++++++---- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/example/ZXingWriter.cpp b/example/ZXingWriter.cpp index a0b9389b2e..bc94f3a839 100644 --- a/example/ZXingWriter.cpp +++ b/example/ZXingWriter.cpp @@ -29,6 +29,7 @@ static void PrintUsage(const char* exePath) << " -margin Margin around barcode\n" << " -encoding Encoding used to encode input text\n" << " -ecc Error correction level, [0-8]\n" + << " -binary Interpret as a file name containing binary data\n" << " -help Print usage information\n" << " -version Print version information\n" << "\n" @@ -53,7 +54,7 @@ static bool ParseSize(std::string str, int* width, int* height) } static bool ParseOptions(int argc, char* argv[], int* width, int* height, int* margin, CharacterSet* encoding, - int* eccLevel, BarcodeFormat* format, std::string* text, std::string* filePath) + int* eccLevel, BarcodeFormat* format, std::string* text, std::string* filePath, bool* inputIsFile) { int nonOptArgCount = 0; for (int i = 1; i < argc; ++i) { @@ -77,6 +78,8 @@ static bool ParseOptions(int argc, char* argv[], int* width, int* height, int* m if (++i == argc) return false; *encoding = CharacterSetFromString(argv[i]); + } else if (is("-binary")) { + *inputIsFile = true; } else if (is("-help") || is("--help")) { PrintUsage(argv[0]); exit(0); @@ -114,23 +117,41 @@ static std::string GetExtension(const std::string& path) return ext; } +static std::string ReadFile(const std::string& fn) +{ + std::ifstream ifs(fn, std::ios::binary); + return ifs ? std::string(std::istreambuf_iterator(ifs), std::istreambuf_iterator()) : std::string(); +}; + int main(int argc, char* argv[]) { int width = 100, height = 100; int margin = 10; int eccLevel = -1; + bool inputIsFile = false; CharacterSet encoding = CharacterSet::Unknown; - std::string text, filePath; + std::string input, filePath; BarcodeFormat format; - if (!ParseOptions(argc, argv, &width, &height, &margin, &encoding, &eccLevel, &format, &text, &filePath)) { + if (!ParseOptions(argc, argv, &width, &height, &margin, &encoding, &eccLevel, &format, &input, &filePath, &inputIsFile)) { PrintUsage(argv[0]); return -1; } try { auto writer = MultiFormatWriter(format).setMargin(margin).setEncoding(encoding).setEccLevel(eccLevel); - auto matrix = writer.encode(text, width, height); + + BitMatrix matrix; + if (inputIsFile) { + auto file = ReadFile(input); + std::wstring bytes; + for (uint8_t c : file) + bytes.push_back(c); + writer.setEncoding(CharacterSet::BINARY); + matrix = writer.encode(bytes, width, height); + } else { + matrix = writer.encode(input, width, height); + } auto bitmap = ToMatrix(matrix); auto ext = GetExtension(filePath); From fac4062c631cd41f3c460b32bf1e93bdc28329ac Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 3 May 2023 18:59:16 +0200 Subject: [PATCH 137/173] README: add named sponsor kurzdigital --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 97ec286a80..dc89da5d3d 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ It was originally ported from the Java [ZXing Library](https://github.com/zxing/ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/axxel). Named Sponsors: +* [KURZ Digital Solutions GmbH & Co. KG](https://github.com/kurzdigital) * [Useful Sensors Inc](https://github.com/usefulsensors) * [Sergio Olivo](https://github.com/sergio-) From 186f9c156332ec6fc165f436f62027d3380e66dd Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 6 May 2023 16:17:11 +0200 Subject: [PATCH 138/173] UPC/EAN: reduce right quiet zone requirement -> fix non-conformant input This should fix #526 and fix #558 and also help with the core question in #509. One can say, I gave in... :-/ --- core/src/oned/ODMultiUPCEANReader.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/oned/ODMultiUPCEANReader.cpp b/core/src/oned/ODMultiUPCEANReader.cpp index 6a7dcb6fdc..0f77dca01e 100644 --- a/core/src/oned/ODMultiUPCEANReader.cpp +++ b/core/src/oned/ODMultiUPCEANReader.cpp @@ -34,7 +34,7 @@ static const int FIRST_DIGIT_ENCODINGS[] = {0x00, 0x0B, 0x0D, 0x0E, 0x13, 0x19, // QZ R: 7 | 7 | 9 | 7 | 5 | 5 constexpr float QUIET_ZONE_LEFT = 6; -constexpr float QUIET_ZONE_RIGHT = 6; +constexpr float QUIET_ZONE_RIGHT = 3; // used to be 6, see #526 and #558 constexpr float QUIET_ZONE_ADDON = 3; // There is a single sample (ean13-1/12.png) that fails to decode with these (new) settings because From eaafe9fc5111f663f4033853677a549956a65fe8 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 6 May 2023 17:47:37 +0200 Subject: [PATCH 139/173] amend last commit: fix test regression and add new test case --- core/src/oned/ODMultiUPCEANReader.cpp | 9 +++++---- test/blackbox/BlackboxTestRunner.cpp | 6 +++--- test/samples/ean13-1/#526.jpg | Bin 0 -> 892 bytes test/samples/ean13-1/#526.txt | 1 + 4 files changed, 9 insertions(+), 7 deletions(-) create mode 100644 test/samples/ean13-1/#526.jpg create mode 100644 test/samples/ean13-1/#526.txt diff --git a/core/src/oned/ODMultiUPCEANReader.cpp b/core/src/oned/ODMultiUPCEANReader.cpp index 0f77dca01e..761bd6658f 100644 --- a/core/src/oned/ODMultiUPCEANReader.cpp +++ b/core/src/oned/ODMultiUPCEANReader.cpp @@ -34,7 +34,8 @@ static const int FIRST_DIGIT_ENCODINGS[] = {0x00, 0x0B, 0x0D, 0x0E, 0x13, 0x19, // QZ R: 7 | 7 | 9 | 7 | 5 | 5 constexpr float QUIET_ZONE_LEFT = 6; -constexpr float QUIET_ZONE_RIGHT = 3; // used to be 6, see #526 and #558 +constexpr float QUIET_ZONE_RIGHT_EAN = 3; // used to be 6, see #526 and #558 +constexpr float QUIET_ZONE_RIGHT_UPC = 6; constexpr float QUIET_ZONE_ADDON = 3; // There is a single sample (ean13-1/12.png) that fails to decode with these (new) settings because @@ -131,7 +132,7 @@ static bool EAN13(PartialResult& res, PatternView begin) auto mid = begin.subView(27, MID_PATTERN.size()); auto end = begin.subView(56, END_PATTERN.size()); - CHECK(end.isValid() && IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT) && IsPattern(mid, MID_PATTERN)); + CHECK(end.isValid() && IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT_EAN) && IsPattern(mid, MID_PATTERN)); auto next = begin.subView(END_PATTERN.size(), CHAR_LEN); res.txt = " "; // make space for lgPattern character @@ -163,7 +164,7 @@ static bool EAN8(PartialResult& res, PatternView begin) auto mid = begin.subView(19, MID_PATTERN.size()); auto end = begin.subView(40, END_PATTERN.size()); - CHECK(end.isValid() && IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT) && IsPattern(mid, MID_PATTERN)); + CHECK(end.isValid() && IsRightGuard(end, END_PATTERN, QUIET_ZONE_RIGHT_EAN) && IsPattern(mid, MID_PATTERN)); // additional plausibility check for the module size: it has to be about the same for both // the guard patterns and the payload/data part. @@ -190,7 +191,7 @@ static bool UPCE(PartialResult& res, PatternView begin) { auto end = begin.subView(27, UPCE_END_PATTERN.size()); - CHECK(end.isValid() && IsRightGuard(end, UPCE_END_PATTERN, QUIET_ZONE_RIGHT)); + CHECK(end.isValid() && IsRightGuard(end, UPCE_END_PATTERN, QUIET_ZONE_RIGHT_UPC)); // additional plausibility check for the module size: it has to be about the same for both // the guard patterns and the payload/data part. This speeds up the falsepositives use case diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 8dac8ca4bd..6266271e32 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -428,9 +428,9 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 7, 0, pure }, }); - runTests("ean13-1", "EAN-13", 31, { - { 24, 29, 0 }, - { 23, 29, 180 }, + runTests("ean13-1", "EAN-13", 32, { + { 26, 30, 0 }, + { 25, 30, 180 }, }); runTests("ean13-2", "EAN-13", 24, { diff --git a/test/samples/ean13-1/#526.jpg b/test/samples/ean13-1/#526.jpg new file mode 100644 index 0000000000000000000000000000000000000000..2f1fa82342a7003ddc8da937fcbdfb833a503b79 GIT binary patch literal 892 zcmV-?1B3kk*#F=F5K2Z#MgRc;000310RRC1+WwA}S+3F)J%EK1e({JV;kgPFHPcZI6$@|H1$Z2mlKK zRRIAJ0RO}Q8~^|V00RL50RR9100000000ID2mk^F0SN^E+5iXv0RR910Gp{QLlr!s z3HRul*TKnBgr(K#>MY>^<8{{a8Q06`D{00IUD2m}fX z00000000000s#XA1_!bQG&gf}2oMoAb+NO;AvIBCGg4GyWpQ$L!^9F3BPAwCRfL6- zqW{_e2mt{A0Y3m4J)HB<>wGqx5CGApmPnF*{k51{(#Y3*xr?a%OlBrWwPd_^s@6 zm^BwfZlh7?Gt6;E=~o*60G(YrL{B0M6q+v>q#%i<`~gp+v7DSjhZ|g-+?Cn2*Izz>P}kjYeKx+dWky1Cc)g_K8~ab?-W zoZ;l@aH*|~yMiDEed5C^6m^8tnB#Gk=6Ec&ALH2MNTe;Qh1jcpAZND}?e&w0t7BMf=Ml2d8}1q8&HvfrhIUH; literal 0 HcmV?d00001 diff --git a/test/samples/ean13-1/#526.txt b/test/samples/ean13-1/#526.txt new file mode 100644 index 0000000000..0101635f8b --- /dev/null +++ b/test/samples/ean13-1/#526.txt @@ -0,0 +1 @@ +8886316200561 \ No newline at end of file From c1edfc873221a82d697a972aa09eeb070b4e3025 Mon Sep 17 00:00:00 2001 From: axxel Date: Sat, 6 May 2023 21:44:48 +0200 Subject: [PATCH 140/173] PDF417: new DecodeCodewords API This cleans up the internal API somewhat, making it obvious that the erasures array is not used anyway. Also preparing the introduction of MicroPDF17 detection. To give a meaningful and consistent `Result::ecLevel` for all PDF417 variants, that string now contains a percentage number of how many code words have been used for error correction. --- core/src/pdf417/PDFDecodedBitStreamParser.cpp | 3 +- core/src/pdf417/PDFDecodedBitStreamParser.h | 2 +- core/src/pdf417/PDFReader.cpp | 12 +------ core/src/pdf417/PDFScanningDecoder.cpp | 33 +++++++++++-------- core/src/pdf417/PDFScanningDecoder.h | 9 +++++ test/unit/pdf417/PDF417DecoderTest.cpp | 4 +-- .../unit/pdf417/PDF417ScanningDecoderTest.cpp | 8 ++--- 7 files changed, 35 insertions(+), 36 deletions(-) diff --git a/core/src/pdf417/PDFDecodedBitStreamParser.cpp b/core/src/pdf417/PDFDecodedBitStreamParser.cpp index 66b9224779..d26be9572b 100644 --- a/core/src/pdf417/PDFDecodedBitStreamParser.cpp +++ b/core/src/pdf417/PDFDecodedBitStreamParser.cpp @@ -672,7 +672,7 @@ int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderRe } DecoderResult -DecodedBitStreamParser::Decode(const std::vector& codewords, int ecLevel) +DecodedBitStreamParser::Decode(const std::vector& codewords) { Content result; result.symbology = { 'L', '2', char(-1) }; @@ -752,7 +752,6 @@ DecodedBitStreamParser::Decode(const std::vector& codewords, int ecLevel) } return DecoderResult(std::move(result)) - .setEcLevel(std::to_string(ecLevel)) .setStructuredAppend(sai) .setReaderInit(readerInit) .setExtra(resultMetadata); diff --git a/core/src/pdf417/PDFDecodedBitStreamParser.h b/core/src/pdf417/PDFDecodedBitStreamParser.h index 103db7c5b0..e788202e75 100644 --- a/core/src/pdf417/PDFDecodedBitStreamParser.h +++ b/core/src/pdf417/PDFDecodedBitStreamParser.h @@ -24,7 +24,7 @@ namespace Pdf417 { class DecodedBitStreamParser { public: - static DecoderResult Decode(const std::vector& codewords, int ecLevel); + static DecoderResult Decode(const std::vector& codewords); }; } // Pdf417 diff --git a/core/src/pdf417/PDFReader.cpp b/core/src/pdf417/PDFReader.cpp index c33c3698a9..dd77c7cc1f 100644 --- a/core/src/pdf417/PDFReader.cpp +++ b/core/src/pdf417/PDFReader.cpp @@ -254,9 +254,6 @@ std::vector ReadCodeWords(BitMatrixCursor topCur, SymbolInfo info) return codeWords; } -// forward declaration from PDFScanningDecoder.cpp -DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const std::vector& erasures); - static Result DecodePure(const BinaryBitmap& image_) { auto pimage = image_.getBitMatrix(); @@ -293,14 +290,7 @@ static Result DecodePure(const BinaryBitmap& image_) auto codeWords = ReadCodeWords(cur, info); - std::vector erasures; - for (int i = 0; i < Size(codeWords); ++i) - if (codeWords[i] == -1) { - codeWords[i] = 0; - erasures.push_back(i); - } - - auto res = DecodeCodewords(codeWords, info.ecLevel, erasures); + auto res = DecodeCodewords(codeWords, NumECCodeWords(info.ecLevel)); return Result(std::move(res), {{left, top}, {right, top}, {right, bottom}, {left, bottom}}, BarcodeFormat::PDF417); } diff --git a/core/src/pdf417/PDFScanningDecoder.cpp b/core/src/pdf417/PDFScanningDecoder.cpp index 3a612277c2..344138d10b 100644 --- a/core/src/pdf417/PDFScanningDecoder.cpp +++ b/core/src/pdf417/PDFScanningDecoder.cpp @@ -457,7 +457,7 @@ static std::vector FindErrorMagnitudes(const ModulusPoly& errorEvaluator, c * @return false if errors cannot be corrected, maybe because of too many errors */ ZXING_EXPORT_TEST_ONLY -bool DecodeErrorCorrection(std::vector& received, int numECCodewords, const std::vector& erasures, int& nbErrors) +bool DecodeErrorCorrection(std::vector& received, int numECCodewords, const std::vector& erasures [[maybe_unused]], int& nbErrors) { const ModulusGF& field = GetModulusGF(); ModulusPoly poly(field, received); @@ -476,23 +476,23 @@ bool DecodeErrorCorrection(std::vector& received, int numECCodewords, const return true; } - ModulusPoly knownErrors = field.one(); - for (int erasure : erasures) { - int b = field.exp(Size(received) - 1 - erasure); - // Add (1 - bx) term: - ModulusPoly term(field, { field.subtract(0, b), 1 }); - knownErrors = knownErrors.multiply(term); - } +// ModulusPoly knownErrors = field.one(); +// for (int erasure : erasures) { +// int b = field.exp(Size(received) - 1 - erasure); +// // Add (1 - bx) term: +// ModulusPoly term(field, { field.subtract(0, b), 1 }); +// knownErrors = knownErrors.multiply(term); +// } ModulusPoly syndrome(field, S); - //syndrome = syndrome.multiply(knownErrors); +// syndrome = syndrome.multiply(knownErrors); ModulusPoly sigma, omega; if (!RunEuclideanAlgorithm(field.buildMonomial(numECCodewords, 1), syndrome, numECCodewords, sigma, omega)) { return false; } - //sigma = sigma.multiply(knownErrors); +// sigma = sigma.multiply(knownErrors); std::vector errorLocations; if (!FindErrorLocations(sigma, errorLocations)) { @@ -566,12 +566,11 @@ static bool VerifyCodewordCount(std::vector& codewords, int numECCodewords) return true; } -DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const std::vector& erasures) +static DecoderResult DecodeCodewords(std::vector& codewords, int numECCodewords, const std::vector& erasures) { if (codewords.empty()) return FormatError(); - int numECCodewords = 1 << (ecLevel + 1); int correctedErrorsCount = 0; if (!CorrectErrors(codewords, erasures, numECCodewords, correctedErrorsCount)) return ChecksumError(); @@ -581,12 +580,18 @@ DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const st // Decode the codewords try { - return DecodedBitStreamParser::Decode(codewords, ecLevel); + return DecodedBitStreamParser::Decode(codewords).setEcLevel(std::to_string(numECCodewords * 100 / Size(codewords)) + "%"); } catch (Error e) { return e; } } +DecoderResult DecodeCodewords(std::vector& codewords, int numECCodeWords) +{ + // erasures array has never been actually used inside the error correction code + return DecodeCodewords(codewords, numECCodeWords, {}); +} + /** * This method deals with the fact, that the decoding process doesn't always yield a single most likely value. The @@ -612,7 +617,7 @@ static DecoderResult CreateDecoderResultFromAmbiguousValues(int ecLevel, std::ve for (size_t i = 0; i < ambiguousIndexCount.size(); i++) { codewords[ambiguousIndexes[i]] = ambiguousIndexValues[i][ambiguousIndexCount[i]]; } - auto result = DecodeCodewords(codewords, ecLevel, erasureArray); + auto result = DecodeCodewords(codewords, NumECCodeWords(ecLevel), erasureArray); if (result.error() != Error::Checksum) { return result; } diff --git a/core/src/pdf417/PDFScanningDecoder.h b/core/src/pdf417/PDFScanningDecoder.h index 8516699bf7..9e3460ef20 100644 --- a/core/src/pdf417/PDFScanningDecoder.h +++ b/core/src/pdf417/PDFScanningDecoder.h @@ -6,6 +6,8 @@ #pragma once +#include + namespace ZXing { class BitMatrix; @@ -27,5 +29,12 @@ class ScanningDecoder int minCodewordWidth, int maxCodewordWidth); }; +inline int NumECCodeWords(int ecLevel) +{ + return 1 << (ecLevel + 1); +} + +DecoderResult DecodeCodewords(std::vector& codewords, int numECCodeWords); + } // Pdf417 } // ZXing diff --git a/test/unit/pdf417/PDF417DecoderTest.cpp b/test/unit/pdf417/PDF417DecoderTest.cpp index 2ec267c14b..7f8da91029 100644 --- a/test/unit/pdf417/PDF417DecoderTest.cpp +++ b/test/unit/pdf417/PDF417DecoderTest.cpp @@ -18,10 +18,10 @@ using namespace ZXing; using namespace ZXing::Pdf417; // Shorthand for Decode() -static DecoderResult parse(const std::vector& codewords, int ecLevel = 0) +static DecoderResult parse(const std::vector& codewords) { try { - return DecodedBitStreamParser::Decode(codewords, ecLevel); + return DecodedBitStreamParser::Decode(codewords); } catch (Error e) { return e; } diff --git a/test/unit/pdf417/PDF417ScanningDecoderTest.cpp b/test/unit/pdf417/PDF417ScanningDecoderTest.cpp index cd7143a42f..f4822d8881 100644 --- a/test/unit/pdf417/PDF417ScanningDecoderTest.cpp +++ b/test/unit/pdf417/PDF417ScanningDecoderTest.cpp @@ -8,18 +8,14 @@ #include "gtest/gtest.h" -namespace ZXing::Pdf417 { - DecoderResult DecodeCodewords(std::vector& codewords, int ecLevel, const std::vector& erasures); -} - using namespace ZXing; using namespace ZXing::Pdf417; // Shorthand for DecodeCodewords() -static DecoderResult decode(std::vector& codewords, int ecLevel = 0) +static DecoderResult decode(std::vector& codewords) { std::vector erasures; - auto result = DecodeCodewords(codewords, ecLevel, erasures); + auto result = DecodeCodewords(codewords, NumECCodeWords(0)); return result; } From f61be84d08ab6d9c5cf1e254bb3898657fc419bd Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 10 May 2023 11:51:45 +0200 Subject: [PATCH 141/173] README: added new sponsor --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index dc89da5d3d..a4397d90cb 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,7 @@ It was originally ported from the Java [ZXing Library](https://github.com/zxing/ You can sponsor this library at [GitHub Sponsors](https://github.com/sponsors/axxel). Named Sponsors: +* [Liftric GmbH](https://github.com/Liftric) * [KURZ Digital Solutions GmbH & Co. KG](https://github.com/kurzdigital) * [Useful Sensors Inc](https://github.com/usefulsensors) * [Sergio Olivo](https://github.com/sergio-) From c230785908488924027f88874f6426855349ac07 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 10 May 2023 16:39:12 +0200 Subject: [PATCH 142/173] PDF417: fix CI regression from DecodeCodewords change The codewords need to be clamped to [0, 928]. Also this revealed that one 'pure' sample is actually not pure at all. --- core/src/pdf417/PDFScanningDecoder.cpp | 3 +++ test/blackbox/BlackboxTestRunner.cpp | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/core/src/pdf417/PDFScanningDecoder.cpp b/core/src/pdf417/PDFScanningDecoder.cpp index 344138d10b..78b3f49021 100644 --- a/core/src/pdf417/PDFScanningDecoder.cpp +++ b/core/src/pdf417/PDFScanningDecoder.cpp @@ -588,6 +588,9 @@ static DecoderResult DecodeCodewords(std::vector& codewords, int numECCodew DecoderResult DecodeCodewords(std::vector& codewords, int numECCodeWords) { + for (auto& cw : codewords) + cw = std::clamp(cw, 0, CodewordDecoder::MAX_CODEWORDS_IN_BARCODE); + // erasures array has never been actually used inside the error correction code return DecodeCodewords(codewords, numECCodeWords, {}); } diff --git a/test/blackbox/BlackboxTestRunner.cpp b/test/blackbox/BlackboxTestRunner.cpp index 6266271e32..ec7acd1dac 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -616,7 +616,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 1, 17, 90 }, { 16, 17, 180 }, { 1, 17, 270 }, - { 17, 0, pure }, + { 16, 0, pure }, }); runTests("pdf417-2", "PDF417", 25, { From deda5eef587a6c2288dad5a69c08240e5224707f Mon Sep 17 00:00:00 2001 From: axxel Date: Mon, 15 May 2023 15:49:30 +0200 Subject: [PATCH 143/173] PDF417: random code cleanup/simplification of the Decode functionality --- core/CMakeLists.txt | 4 +- core/src/pdf417/PDFDecodedBitStreamParser.h | 31 --- ...odedBitStreamParser.cpp => PDFDecoder.cpp} | 221 ++++++++---------- core/src/pdf417/PDFDecoder.h | 20 ++ core/src/pdf417/PDFScanningDecoder.cpp | 8 +- test/unit/pdf417/PDF417DecoderTest.cpp | 40 ++-- 6 files changed, 133 insertions(+), 191 deletions(-) delete mode 100644 core/src/pdf417/PDFDecodedBitStreamParser.h rename core/src/pdf417/{PDFDecodedBitStreamParser.cpp => PDFDecoder.cpp} (78%) create mode 100644 core/src/pdf417/PDFDecoder.h diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 37d9026c83..7d27cfb277 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -331,8 +331,8 @@ if (BUILD_READERS) src/pdf417/PDFCodeword.h src/pdf417/PDFCodewordDecoder.h src/pdf417/PDFCodewordDecoder.cpp - src/pdf417/PDFDecodedBitStreamParser.h - src/pdf417/PDFDecodedBitStreamParser.cpp + src/pdf417/PDFDecoder.h + src/pdf417/PDFDecoder.cpp src/pdf417/PDFDecoderResultExtra.h src/pdf417/PDFDetectionResult.h src/pdf417/PDFDetectionResult.cpp diff --git a/core/src/pdf417/PDFDecodedBitStreamParser.h b/core/src/pdf417/PDFDecodedBitStreamParser.h deleted file mode 100644 index e788202e75..0000000000 --- a/core/src/pdf417/PDFDecodedBitStreamParser.h +++ /dev/null @@ -1,31 +0,0 @@ -/* -* Copyright 2016 Nu-book Inc. -* Copyright 2016 ZXing authors -*/ -// SPDX-License-Identifier: Apache-2.0 - -#pragma once - -#include -#include - -namespace ZXing { - -class DecoderResult; - -namespace Pdf417 { - -/** -*

This class contains the methods for decoding the PDF417 codewords.

-* -* @author SITA Lab (kevin.osullivan@sita.aero) -* @author Guenther Grau -*/ -class DecodedBitStreamParser -{ -public: - static DecoderResult Decode(const std::vector& codewords); -}; - -} // Pdf417 -} // ZXing diff --git a/core/src/pdf417/PDFDecodedBitStreamParser.cpp b/core/src/pdf417/PDFDecoder.cpp similarity index 78% rename from core/src/pdf417/PDFDecodedBitStreamParser.cpp rename to core/src/pdf417/PDFDecoder.cpp index d26be9572b..1c6c89633b 100644 --- a/core/src/pdf417/PDFDecodedBitStreamParser.cpp +++ b/core/src/pdf417/PDFDecoder.cpp @@ -4,7 +4,7 @@ */ // SPDX-License-Identifier: Apache-2.0 -#include "PDFDecodedBitStreamParser.h" +#include "PDFDecoder.h" #include "CharacterSet.h" #include "DecoderResult.h" @@ -15,7 +15,6 @@ #include #include -#include #include #include @@ -31,38 +30,38 @@ enum class Mode PUNCT_SHIFT }; -static const int TEXT_COMPACTION_MODE_LATCH = 900; -static const int BYTE_COMPACTION_MODE_LATCH = 901; -static const int NUMERIC_COMPACTION_MODE_LATCH = 902; +constexpr int TEXT_COMPACTION_MODE_LATCH = 900; +constexpr int BYTE_COMPACTION_MODE_LATCH = 901; +constexpr int NUMERIC_COMPACTION_MODE_LATCH = 902; // 903-912 reserved -static const int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; +constexpr int MODE_SHIFT_TO_BYTE_COMPACTION_MODE = 913; // 914-917 reserved -static const int LINKAGE_OTHER = 918; +constexpr int LINKAGE_OTHER = 918; // 919 reserved -static const int LINKAGE_EANUCC = 920; // GS1 Composite -static const int READER_INIT = 921; // Reader Initialisation/Programming -static const int MACRO_PDF417_TERMINATOR = 922; -static const int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923; -static const int BYTE_COMPACTION_MODE_LATCH_6 = 924; -static const int ECI_USER_DEFINED = 925; // 810900-811799 (1 codeword) -static const int ECI_GENERAL_PURPOSE = 926; // 900-810899 (2 codewords) -static const int ECI_CHARSET = 927; // 0-899 (1 codeword) -static const int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928; - -static const int MAX_NUMERIC_CODEWORDS = 15; - -static const int MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME = 0; -static const int MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT = 1; -static const int MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP = 2; -static const int MACRO_PDF417_OPTIONAL_FIELD_SENDER = 3; -static const int MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE = 4; -static const int MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE = 5; -static const int MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM = 6; +constexpr int LINKAGE_EANUCC = 920; // GS1 Composite +constexpr int READER_INIT = 921; // Reader Initialisation/Programming +constexpr int MACRO_PDF417_TERMINATOR = 922; +constexpr int BEGIN_MACRO_PDF417_OPTIONAL_FIELD = 923; +constexpr int BYTE_COMPACTION_MODE_LATCH_6 = 924; +constexpr int ECI_USER_DEFINED = 925; // 810900-811799 (1 codeword) +constexpr int ECI_GENERAL_PURPOSE = 926; // 900-810899 (2 codewords) +constexpr int ECI_CHARSET = 927; // 0-899 (1 codeword) +constexpr int BEGIN_MACRO_PDF417_CONTROL_BLOCK = 928; + +constexpr int MAX_NUMERIC_CODEWORDS = 15; + +constexpr int MACRO_PDF417_OPTIONAL_FIELD_FILE_NAME = 0; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_SEGMENT_COUNT = 1; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_TIME_STAMP = 2; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_SENDER = 3; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_ADDRESSEE = 4; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_FILE_SIZE = 5; +constexpr int MACRO_PDF417_OPTIONAL_FIELD_CHECKSUM = 6; static const char* PUNCT_CHARS = ";<>@[\\]_`~!\r\t,:\n-.$/\"|*()?{}'"; static const char* MIXED_CHARS = "0123456789&\r\t,:#-.$/+%*=^"; -static const int NUMBER_OF_SEQUENCE_CODEWORDS = 2; +constexpr int NUMBER_OF_SEQUENCE_CODEWORDS = 2; inline bool IsECI(int code) { @@ -449,7 +448,7 @@ Decode the above codewords involves Remove leading 1 => Result is 000213298174000 */ -static std::string DecodeBase900toBase10(const std::vector& codewords, int count) +static std::string DecodeBase900toBase10(const std::vector& codewords, int endIndex, int count) { // Table containing values for the exponent of 900. static const auto EXP900 = []() { @@ -463,7 +462,7 @@ static std::string DecodeBase900toBase10(const std::vector& codewords, int BigInteger result; for (int i = 0; i < count; i++) - result += EXP900[count - i - 1] * codewords[i]; + result += EXP900[count - i - 1] * codewords[endIndex - count + i]; std::string resultString = result.toString(); if (!resultString.empty() && resultString.front() == '1') @@ -486,44 +485,27 @@ static std::string DecodeBase900toBase10(const std::vector& codewords, int static int NumericCompaction(const std::vector& codewords, int codeIndex, Content& result) { int count = 0; - bool end = false; - std::vector numericCodewords(MAX_NUMERIC_CODEWORDS); + while (codeIndex < codewords[0]) { + int code = codewords[codeIndex]; + if (code < TEXT_COMPACTION_MODE_LATCH) { + count++; + codeIndex++; + } + if (count > 0 && (count == MAX_NUMERIC_CODEWORDS || codeIndex == codewords[0] || code >= TEXT_COMPACTION_MODE_LATCH)) { + result += DecodeBase900toBase10(codewords, codeIndex, count); + count = 0; + } - while (codeIndex < codewords[0] && !end) { - int code = codewords[codeIndex++]; if (code >= TEXT_COMPACTION_MODE_LATCH) { if (IsECI(code)) { // As operating in Basic Channel Mode (i.e. not embedding backslashed ECIs and doubling backslashes) // allow ECIs anywhere in Numeric Compaction (i.e. ISO/IEC 15438:2015 5.5.3.4 doesn't apply). - if (count > 0) { - result += DecodeBase900toBase10(numericCodewords, count); - count = 0; - } - codeIndex = ProcessECI(codewords, codeIndex, codewords[0], code, result); - continue; - } - if (!TerminatesCompaction(code)) + codeIndex = ProcessECI(codewords, codeIndex + 1, codewords[0], code, result); + } else if (TerminatesCompaction(code)) { + break; + } else { throw FormatError(); - - codeIndex--; - end = true; - } - if (codeIndex == codewords[0]) { - end = true; - } - if (code < TEXT_COMPACTION_MODE_LATCH) { - numericCodewords[count] = code; - count++; - } - if (count % MAX_NUMERIC_CODEWORDS == 0 || code == NUMERIC_COMPACTION_MODE_LATCH || end) { - // Re-invoking Numeric Compaction mode (by using codeword 902 - // while in Numeric Compaction mode) serves to terminate the - // current Numeric Compaction mode grouping as described in 5.4.4.2, - // and then to start a new one grouping. - if (count > 0) { - result += DecodeBase900toBase10(numericCodewords, count); - count = 0; } } } @@ -568,15 +550,11 @@ static int DecodeMacroOptionalNumericField(const std::vector& codewords, in ZXING_EXPORT_TEST_ONLY int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderResultExtra& resultMetadata) { - // we must have at least two bytes left for the segment index + // we must have at least two codewords left for the segment index if (codeIndex + NUMBER_OF_SEQUENCE_CODEWORDS > codewords[0]) throw FormatError(); - std::vector segmentIndexArray(NUMBER_OF_SEQUENCE_CODEWORDS); - for (int i = 0; i < NUMBER_OF_SEQUENCE_CODEWORDS; i++, codeIndex++) - segmentIndexArray[i] = codewords[codeIndex]; - - std::string strBuf = DecodeBase900toBase10(segmentIndexArray, NUMBER_OF_SEQUENCE_CODEWORDS); + std::string strBuf = DecodeBase900toBase10(codewords, codeIndex += NUMBER_OF_SEQUENCE_CODEWORDS, NUMBER_OF_SEQUENCE_CODEWORDS); resultMetadata.setSegmentIndex(std::stoi(strBuf)); @@ -584,11 +562,10 @@ int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderRe // (See ISO/IEC 15438:2015 Annex H.6) and preserves all info, but some generators (e.g. TEC-IT) write // the fileId using text compaction, so in those cases the fileId will appear mangled. std::ostringstream fileId; - fileId.fill('0'); for (; codeIndex < codewords[0] && codewords[codeIndex] != MACRO_PDF417_TERMINATOR && codewords[codeIndex] != BEGIN_MACRO_PDF417_OPTIONAL_FIELD; codeIndex++) { - fileId << std::setw(3) << codewords[codeIndex]; + fileId << ToString(codewords[codeIndex], 3); } resultMetadata.setFileId(fileId.str()); @@ -664,79 +641,69 @@ int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderRe if (resultMetadata.isLastSegment()) optionalFieldsLength--; // do not include terminator - resultMetadata.setOptionalData(std::vector(codewords.begin() + optionalFieldsStart, - codewords.begin() + optionalFieldsStart + optionalFieldsLength)); + resultMetadata.setOptionalData( + std::vector(codewords.begin() + optionalFieldsStart, codewords.begin() + optionalFieldsStart + optionalFieldsLength)); } return codeIndex; } -DecoderResult -DecodedBitStreamParser::Decode(const std::vector& codewords) +DecoderResult Decode(const std::vector& codewords) { Content result; - result.symbology = { 'L', '2', char(-1) }; + result.symbology = {'L', '2', char(-1)}; bool readerInit = false; auto resultMetadata = std::make_shared(); - int codeIndex = 1; - while (codeIndex < codewords[0]) { - int code = codewords[codeIndex++]; - switch (code) { - case TEXT_COMPACTION_MODE_LATCH: - codeIndex = TextCompaction(codewords, codeIndex, result); - break; - case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: - // This should only be encountered once in this loop, when default Text Compaction mode applies - // (see default case below) - codeIndex = TextCompaction(codewords, codeIndex - 1, result); - break; - case BYTE_COMPACTION_MODE_LATCH: - case BYTE_COMPACTION_MODE_LATCH_6: - codeIndex = ByteCompaction(code, codewords, codeIndex, result); - break; - case NUMERIC_COMPACTION_MODE_LATCH: - codeIndex = NumericCompaction(codewords, codeIndex, result); - break; - case ECI_CHARSET: - case ECI_GENERAL_PURPOSE: - case ECI_USER_DEFINED: - codeIndex = ProcessECI(codewords, codeIndex, codewords[0], code, result); - break; - case BEGIN_MACRO_PDF417_CONTROL_BLOCK: - codeIndex = DecodeMacroBlock(codewords, codeIndex, *resultMetadata); - break; - case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: - case MACRO_PDF417_TERMINATOR: - // Should not see these outside a macro block - throw FormatError(); - break; - case READER_INIT: - if (codeIndex != 2) // Must be first codeword after symbol length (ISO/IEC 15438:2015 5.4.1.4) - throw FormatError(); - else - readerInit = true; - break; - case LINKAGE_EANUCC: - if (codeIndex != 2) // Must be first codeword after symbol length (GS1 Composite ISO/IEC 24723:2010 4.3) + try { + for (int codeIndex = 1; codeIndex < codewords[0];) { + int code = codewords[codeIndex++]; + switch (code) { + case TEXT_COMPACTION_MODE_LATCH: codeIndex = TextCompaction(codewords, codeIndex, result); break; + // This should only be encountered once in this loop, when default Text Compaction mode applies + // (see default case below) + case MODE_SHIFT_TO_BYTE_COMPACTION_MODE: codeIndex = TextCompaction(codewords, codeIndex - 1, result); break; + case BYTE_COMPACTION_MODE_LATCH: + case BYTE_COMPACTION_MODE_LATCH_6: codeIndex = ByteCompaction(code, codewords, codeIndex, result); break; + case NUMERIC_COMPACTION_MODE_LATCH: codeIndex = NumericCompaction(codewords, codeIndex, result); break; + case ECI_CHARSET: + case ECI_GENERAL_PURPOSE: + case ECI_USER_DEFINED: codeIndex = ProcessECI(codewords, codeIndex, codewords[0], code, result); break; + case BEGIN_MACRO_PDF417_CONTROL_BLOCK: codeIndex = DecodeMacroBlock(codewords, codeIndex, *resultMetadata); break; + case BEGIN_MACRO_PDF417_OPTIONAL_FIELD: + case MACRO_PDF417_TERMINATOR: + // Should not see these outside a macro block throw FormatError(); - // TODO: handle else case - break; - case LINKAGE_OTHER: - // Allowed to treat as invalid by ISO/IEC 24723:2010 5.4.1.5 and 5.4.6.1 when in Basic Channel Mode - throw UnsupportedError("LINKAGE_OTHER, see ISO/IEC 24723:2010 5.4.1.5"); - break; - default: - if (code >= TEXT_COMPACTION_MODE_LATCH) { // Reserved codewords (all others in switch) - // Allowed to treat as invalid by ISO/IEC 24723:2010 5.4.6.1 when in Basic Channel Mode - throw UnsupportedError("TEXT_COMPACTION_MODE_LATCH, see ISO/IEC 24723:2010 5.4.6.1"); - } else { - // Default mode is Text Compaction mode Alpha sub-mode (ISO/IEC 15438:2015 5.4.2.1) - codeIndex = TextCompaction(codewords, codeIndex - 1, result); + break; + case READER_INIT: + if (codeIndex != 2) // Must be first codeword after symbol length (ISO/IEC 15438:2015 5.4.1.4) + throw FormatError(); + else + readerInit = true; + break; + case LINKAGE_EANUCC: + if (codeIndex != 2) // Must be first codeword after symbol length (GS1 Composite ISO/IEC 24723:2010 4.3) + throw FormatError(); + // TODO: handle else case + break; + case LINKAGE_OTHER: + // Allowed to treat as invalid by ISO/IEC 24723:2010 5.4.1.5 and 5.4.6.1 when in Basic Channel Mode + throw UnsupportedError("LINKAGE_OTHER, see ISO/IEC 15438:2015 5.4.1.5"); + break; + default: + if (code >= TEXT_COMPACTION_MODE_LATCH) { // Reserved codewords (all others in switch) + // Allowed to treat as invalid by ISO/IEC 24723:2010 5.4.6.1 when in Basic Channel Mode + throw UnsupportedError("Reserved codeword, see ISO/IEC 15438:2015 5.4.6.1"); + } else { + // Default mode is Text Compaction mode Alpha sub-mode (ISO/IEC 15438:2015 5.4.2.1) + codeIndex = TextCompaction(codewords, codeIndex - 1, result); + } + break; } - break; } + } catch (Error e) { + return e; } if (result.empty() && resultMetadata->segmentIndex() == -1) diff --git a/core/src/pdf417/PDFDecoder.h b/core/src/pdf417/PDFDecoder.h new file mode 100644 index 0000000000..d37d4b9f20 --- /dev/null +++ b/core/src/pdf417/PDFDecoder.h @@ -0,0 +1,20 @@ +/* +* Copyright 2016 Nu-book Inc. +* Copyright 2016 ZXing authors +*/ +// SPDX-License-Identifier: Apache-2.0 + +#pragma once + +#include + +namespace ZXing { + +class DecoderResult; + +namespace Pdf417 { + +DecoderResult Decode(const std::vector& codewords); + +} // namespace Pdf417 +} // namespace ZXing diff --git a/core/src/pdf417/PDFScanningDecoder.cpp b/core/src/pdf417/PDFScanningDecoder.cpp index 78b3f49021..27f9a33138 100644 --- a/core/src/pdf417/PDFScanningDecoder.cpp +++ b/core/src/pdf417/PDFScanningDecoder.cpp @@ -12,7 +12,7 @@ #include "PDFBarcodeValue.h" #include "PDFCodewordDecoder.h" #include "PDFDetectionResult.h" -#include "PDFDecodedBitStreamParser.h" +#include "PDFDecoder.h" #include "PDFModulusGF.h" #include "ZXAlgorithms.h" #include "ZXTestSupport.h" @@ -579,11 +579,7 @@ static DecoderResult DecodeCodewords(std::vector& codewords, int numECCodew return FormatError(); // Decode the codewords - try { - return DecodedBitStreamParser::Decode(codewords).setEcLevel(std::to_string(numECCodewords * 100 / Size(codewords)) + "%"); - } catch (Error e) { - return e; - } + return Decode(codewords).setEcLevel(std::to_string(numECCodewords * 100 / Size(codewords)) + "%"); } DecoderResult DecodeCodewords(std::vector& codewords, int numECCodeWords) diff --git a/test/unit/pdf417/PDF417DecoderTest.cpp b/test/unit/pdf417/PDF417DecoderTest.cpp index 7f8da91029..cbee272e11 100644 --- a/test/unit/pdf417/PDF417DecoderTest.cpp +++ b/test/unit/pdf417/PDF417DecoderTest.cpp @@ -5,7 +5,7 @@ // SPDX-License-Identifier: Apache-2.0 #include "DecoderResult.h" -#include "pdf417/PDFDecodedBitStreamParser.h" +#include "pdf417/PDFDecoder.h" #include "pdf417/PDFDecoderResultExtra.h" #include "gtest/gtest.h" @@ -17,16 +17,6 @@ int DecodeMacroBlock(const std::vector& codewords, int codeIndex, DecoderRe using namespace ZXing; using namespace ZXing::Pdf417; -// Shorthand for Decode() -static DecoderResult parse(const std::vector& codewords) -{ - try { - return DecodedBitStreamParser::Decode(codewords); - } catch (Error e) { - return e; - } -} - /** * Tests the first sample given in ISO/IEC 15438:2015(E) - Annex H.4 */ @@ -50,7 +40,7 @@ TEST(PDF417DecoderTest, StandardSample1) EXPECT_EQ(1, optionalData.front()) << "first element of optional array should be the first field identifier"; EXPECT_EQ(67, optionalData.back()) << "last element of optional array should be the last codeword of the last field"; - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(0, result.structuredAppend().index); EXPECT_EQ("017053", result.structuredAppend().id); @@ -80,7 +70,7 @@ TEST(PDF417DecoderTest, StandardSample2) EXPECT_EQ(1, optionalData.front()) << "first element of optional array should be the first field identifier"; EXPECT_EQ(104, optionalData.back()) << "last element of optional array should be the last codeword of the last field"; - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(3, result.structuredAppend().index); EXPECT_EQ("017053", result.structuredAppend().id); @@ -101,7 +91,7 @@ TEST(PDF417DecoderTest, StandardSample3) EXPECT_EQ("100200300", resultMetadata.fileId()); EXPECT_EQ(-1, resultMetadata.segmentCount()); - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(0, result.structuredAppend().index); EXPECT_EQ("100200300", result.structuredAppend().id); @@ -125,7 +115,7 @@ TEST(PDF417DecoderTest, SampleWithFilename) EXPECT_EQ("", resultMetadata.addressee()); EXPECT_EQ("filename.txt", resultMetadata.fileName()); - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(0, result.structuredAppend().index); EXPECT_EQ("000252021086", result.structuredAppend().id); @@ -149,7 +139,7 @@ TEST(PDF417DecoderTest, SampleWithNumericValues) EXPECT_EQ(260013, resultMetadata.checksum()); EXPECT_EQ(-1, resultMetadata.segmentCount()); - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(0, result.structuredAppend().index); EXPECT_EQ("000252021086", result.structuredAppend().id); @@ -168,7 +158,7 @@ TEST(PDF417DecoderTest, SampleWithMacroTerminatorOnly) EXPECT_EQ(true, resultMetadata.isLastSegment()); EXPECT_EQ(-1, resultMetadata.segmentCount()); - auto result = parse(sampleCodes); + auto result = Decode(sampleCodes); EXPECT_EQ(99998, result.structuredAppend().index); EXPECT_EQ("000", result.structuredAppend().id); @@ -178,13 +168,13 @@ TEST(PDF417DecoderTest, SampleWithMacroTerminatorOnly) // Shorthand to decode and return text static std::wstring decode(const std::vector& codewords) { - return parse(codewords).text(); + return Decode(codewords).text(); } // Shorthand to decode and return isValid static bool valid(const std::vector& codewords) { - return parse(codewords).isValid(); + return Decode(codewords).isValid(); } TEST(PDF417DecoderTest, TextCompactionSimple) @@ -500,7 +490,7 @@ TEST(PDF417DecoderTest, ECIMultipleNumeric) TEST(PDF417DecoderTest, ECIInvalid) { EXPECT_EQ(decode({ 4, 927, 901, 0 }), L""); // non-charset ECI > 899 -> empty text result - EXPECT_EQ(parse({4, 927, 901, 0}).content().bytes, ByteArray("AA")); // non-charset ECI > 899 -> ignored in binary result + EXPECT_EQ(Decode({4, 927, 901, 0}).content().bytes, ByteArray("AA")); // non-charset ECI > 899 -> ignored in binary result EXPECT_EQ(decode({ 3, 0, 927 }), L"AA"); // Malformed ECI at end silently ignored } @@ -542,21 +532,21 @@ TEST(PDF417DecoderTest, ECIUserDefined) TEST(PDF417DecoderTest, ReaderInit) { // Null - EXPECT_FALSE(parse({ 2, 0 }).readerInit()); + EXPECT_FALSE(Decode({2, 0}).readerInit()); EXPECT_EQ(decode({ 2, 0 }), L"AA"); // Set - EXPECT_TRUE(parse({ 3, 921, 0 }).readerInit()); + EXPECT_TRUE(Decode({3, 921, 0}).readerInit()); EXPECT_EQ(decode({ 3, 921, 0 }), L"AA"); // Must be first - EXPECT_FALSE(parse({ 3, 0, 921 }).readerInit()); + EXPECT_FALSE(Decode({3, 0, 921}).readerInit()); EXPECT_FALSE(valid({ 3, 0, 921 })); - EXPECT_FALSE(parse({ 4, 901, 65, 921 }).readerInit()); + EXPECT_FALSE(Decode({4, 901, 65, 921}).readerInit()); EXPECT_FALSE(valid({ 4, 901, 65, 921 })); - EXPECT_FALSE(parse({ 4, 901, 921, 65 }).readerInit()); + EXPECT_FALSE(Decode({4, 901, 921, 65}).readerInit()); EXPECT_FALSE(valid({ 4, 901, 921, 65 })); } From f80a9b923a14827cc78bb96703676c14ac61c1ed Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 17 May 2023 11:24:12 +0200 Subject: [PATCH 144/173] GridSampler/PerspectiveTransform: perform bounds check for every grid point This fixes #563 with brute force. --- core/src/GridSampler.cpp | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/core/src/GridSampler.cpp b/core/src/GridSampler.cpp index 845c40ac0f..8b5cf9b336 100644 --- a/core/src/GridSampler.cpp +++ b/core/src/GridSampler.cpp @@ -34,16 +34,10 @@ DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const R return {}; for (auto&& [x0, x1, y0, y1, mod2Pix] : rois) { - // To deal with remaining examples (see #251 and #267) of "numercial instabilities" that have not been - // prevented with the Quadrilateral.h:IsConvex() check, we check for all boundary points of the grid to - // be inside. - auto isInside = [&mod2Pix = mod2Pix, &image](PointI p) { return image.isIn(mod2Pix(centered(p))); }; - for (int y = y0; y < y1; ++y) - if (!isInside({x0, y}) || !isInside({x1 - 1, y})) - return {}; - for (int x = x0; x < x1; ++x) - if (!isInside({x, y0}) || !isInside({x, y1 - 1})) - return {}; + // Precheck the corners of every roi to bail out early if the grid is "obviously" not completely inside the image + auto isInside = [&mod2Pix = mod2Pix, &image](int x, int y) { return image.isIn(mod2Pix(centered(PointI(x, y)))); }; + if (!mod2Pix.isValid() || !isInside(x0, y0) || !isInside(x1 - 1, y0) || !isInside(x1 - 1, y1 - 1) || !isInside(x0, y1 - 1)) + return {}; } BitMatrix res(width, height); @@ -51,6 +45,14 @@ DetectorResult SampleGrid(const BitMatrix& image, int width, int height, const R for (int y = y0; y < y1; ++y) for (int x = x0; x < x1; ++x) { auto p = mod2Pix(centered(PointI{x, y})); + // Due to a "numerical instability" in the PerspectiveTransform generation/application it has been observed + // that even though all boundary grid points get projected inside the image, it can still happen that an + // inner grid points is not. See #563. A true perspective transformation cannot have this property. + // The following check takes 100% care of the issue and turned out to be less of a performance impact than feared. + // TODO: Check some mathematical/numercial property of mod2Pix to determine if it is a perspective transforation. + if (!image.isIn(p)) + return {}; + #ifdef PRINT_DEBUG log(p, 3); #endif From 1169e84911a8fe19a8fe21fe87a96d4afc8bbae9 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 17 May 2023 11:25:53 +0200 Subject: [PATCH 145/173] ConcentricFinder: make sure the fine tuned points are still always black This came up during the debugging of the sample from #563. --- core/src/ConcentricFinder.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/ConcentricFinder.cpp b/core/src/ConcentricFinder.cpp index dc4dfe1114..c21a6455e3 100644 --- a/core/src/ConcentricFinder.cpp +++ b/core/src/ConcentricFinder.cpp @@ -231,13 +231,13 @@ std::optional FinetuneConcentricPatternCenter(const BitMatrix& image, Po // make sure we have at least one path of white around the center if (auto res1 = CenterOfRing(image, PointI(center), range, 1); res1 && image.get(*res1)) { // and then either at least one more ring around that - if (auto res2 = CenterOfRings(image, *res1, range, finderPatternSize / 2)) + if (auto res2 = CenterOfRings(image, *res1, range, finderPatternSize / 2); res2 && image.get(*res2)) return res2; // or the center can be approximated by a square if (FitSquareToPoints(image, *res1, range, 1, false)) return res1; // TODO: this is currently only keeping #258 alive, evaluate if still worth it - if (auto res2 = CenterOfDoubleCross(image, PointI(*res1), range, finderPatternSize / 2 + 1)) + if (auto res2 = CenterOfDoubleCross(image, PointI(*res1), range, finderPatternSize / 2 + 1); res2 && image.get(*res2)) return res2; } return {}; From 33ce0dd7ece806083a631add36b1735a5a600763 Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 17 May 2023 20:17:57 +0200 Subject: [PATCH 146/173] QRCode: reduce minimum required quiet zone from 0.5 to 0.1 modules This fixes #566. --- core/src/qrcode/QRDetector.cpp | 2 +- test/blackbox/BlackboxTestRunner.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/src/qrcode/QRDetector.cpp b/core/src/qrcode/QRDetector.cpp index dfc9d657a9..7b6c16d461 100644 --- a/core/src/qrcode/QRDetector.cpp +++ b/core/src/qrcode/QRDetector.cpp @@ -44,7 +44,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.5); + 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 ec7acd1dac..70005c8e05 100644 --- a/test/blackbox/BlackboxTestRunner.cpp +++ b/test/blackbox/BlackboxTestRunner.cpp @@ -566,7 +566,7 @@ int runBlackBoxTests(const fs::path& testPathPrefix, const std::set { 45, 47, 0 }, { 45, 47, 90 }, { 45, 47, 180 }, - { 45, 46, 270 }, + { 45, 47, 270 }, { 21, 1, pure }, // the misread is the 'outer' symbol in 16.png }); From 6c24d622e6dd9fa6cbb89dabbb72909b53be9d68 Mon Sep 17 00:00:00 2001 From: Adrien Dorsaz Date: Wed, 24 May 2023 12:02:34 +0200 Subject: [PATCH 147/173] wasm wrapper: add function to reader to scan multiple barcode (#567) * wasm wrapper: add function to reader to scan multiple barcode * wasm wrapper: refactor single result function and use vector * wasm wrapper: apply clang-format on BarcodeReader * wrapper wasm: update demo_reader to directly use embind vector methods * wasm wrapper: fix error management for demo_reader * wasm wrapper: fix possible segfault --- wrappers/wasm/BarcodeReader.cpp | 81 ++++++++++++++++++++----------- wrappers/wasm/demo_reader.html | 85 +++++++++++++++++++++++---------- 2 files changed, 113 insertions(+), 53 deletions(-) diff --git a/wrappers/wasm/BarcodeReader.cpp b/wrappers/wasm/BarcodeReader.cpp index 8969c12ea8..8da1b966a5 100644 --- a/wrappers/wasm/BarcodeReader.cpp +++ b/wrappers/wasm/BarcodeReader.cpp @@ -1,14 +1,14 @@ /* -* Copyright 2016 Nu-book Inc. -*/ + * Copyright 2016 Nu-book Inc. + */ // SPDX-License-Identifier: Apache-2.0 #include "ReadBarcode.h" -#include +#include #include #include -#include +#include #define STB_IMAGE_IMPLEMENTATION #include @@ -21,7 +21,8 @@ struct ReadResult ZXing::Position position{}; }; -ReadResult readBarcodeFromImageView(ZXing::ImageView iv, bool tryHarder, const std::string& format) +std::vector readBarcodesFromImageView(ZXing::ImageView iv, bool tryHarder, const std::string& format, + const int maxSymbols = 0xff) { using namespace ZXing; try { @@ -31,22 +32,28 @@ ReadResult readBarcodeFromImageView(ZXing::ImageView iv, bool tryHarder, const s hints.setTryInvert(tryHarder); hints.setTryDownscale(tryHarder); hints.setFormats(BarcodeFormatsFromString(format)); - hints.setMaxNumberOfSymbols(1); + hints.setMaxNumberOfSymbols(maxSymbols); auto results = ReadBarcodes(iv, hints); - if (!results.empty()) { - auto& result = results.front(); - return {ToString(result.format()), result.text(), "", result.position()}; + + std::vector readResults{}; + readResults.reserve(results.size()); + + for (auto& result : results) { + readResults.push_back({ToString(result.format()), result.text(), {}, result.position()}); } + + return readResults; } catch (const std::exception& e) { - return {"", "", e.what()}; + return {{"", "", e.what()}}; } catch (...) { - return {"", "", "Unknown error"}; + return {{"", "", "Unknown error"}}; } return {}; } -ReadResult readBarcodeFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format) +std::vector readBarcodesFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format, + const int maxSymbols) { using namespace ZXing; @@ -55,15 +62,31 @@ ReadResult readBarcodeFromImage(int bufferPtr, int bufferLength, bool tryHarder, stbi_load_from_memory(reinterpret_cast(bufferPtr), bufferLength, &width, &height, &channels, 1), stbi_image_free); if (buffer == nullptr) - return {"", "", "Error loading image"}; + return {{"", "", "Error loading image"}}; - return readBarcodeFromImageView({buffer.get(), width, height, ImageFormat::Lum}, tryHarder, format); + return readBarcodesFromImageView({buffer.get(), width, height, ImageFormat::Lum}, tryHarder, format, maxSymbols); +} + +ReadResult readBarcodeFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format) +{ + using namespace ZXing; + auto results = readBarcodesFromImage(bufferPtr, bufferLength, tryHarder, format, 1); + return results.empty() ? ReadResult() : results.front(); +} + +std::vector readBarcodesFromPixmap(int bufferPtr, int imgWidth, int imgHeight, bool tryHarder, std::string format, + const int maxSymbols) +{ + using namespace ZXing; + return readBarcodesFromImageView({reinterpret_cast(bufferPtr), imgWidth, imgHeight, ImageFormat::RGBX}, tryHarder, + format, maxSymbols); } ReadResult readBarcodeFromPixmap(int bufferPtr, int imgWidth, int imgHeight, bool tryHarder, std::string format) { using namespace ZXing; - return readBarcodeFromImageView({reinterpret_cast(bufferPtr), imgWidth, imgHeight, ImageFormat::RGBX}, tryHarder, format); + auto results = readBarcodesFromPixmap(bufferPtr, imgWidth, imgHeight, tryHarder, format, 1); + return results.empty() ? ReadResult() : results.front(); } EMSCRIPTEN_BINDINGS(BarcodeReader) @@ -71,24 +94,24 @@ EMSCRIPTEN_BINDINGS(BarcodeReader) using namespace emscripten; value_object("ReadResult") - .field("format", &ReadResult::format) - .field("text", &ReadResult::text) - .field("error", &ReadResult::error) - .field("position", &ReadResult::position) - ; + .field("format", &ReadResult::format) + .field("text", &ReadResult::text) + .field("error", &ReadResult::error) + .field("position", &ReadResult::position); - value_object("Point") - .field("x", &ZXing::PointI::x) - .field("y", &ZXing::PointI::y) - ; + value_object("Point").field("x", &ZXing::PointI::x).field("y", &ZXing::PointI::y); value_object("Position") - .field("topLeft", emscripten::index<0>()) - .field("topRight", emscripten::index<1>()) - .field("bottomRight", emscripten::index<2>()) - .field("bottomLeft", emscripten::index<3>()) - ; + .field("topLeft", emscripten::index<0>()) + .field("topRight", emscripten::index<1>()) + .field("bottomRight", emscripten::index<2>()) + .field("bottomLeft", emscripten::index<3>()); + + register_vector("vector"); function("readBarcodeFromImage", &readBarcodeFromImage); function("readBarcodeFromPixmap", &readBarcodeFromPixmap); + + function("readBarcodesFromImage", &readBarcodesFromImage); + function("readBarcodesFromPixmap", &readBarcodesFromPixmap); }; diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index 9e9188e778..46596b8a3d 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -20,48 +20,79 @@ var buffer = zxing._malloc(fileData.length); zxing.HEAPU8.set(fileData, buffer); - var result = zxing.readBarcodeFromImage(buffer, fileData.length, true, format); + var results = zxing.readBarcodesFromImage(buffer, fileData.length, true, format, 0xff); zxing._free(buffer); - showImage(document.getElementById("drop_zone"), fileData, file.type, result.position); - showScanResult(result); + showScanResults(results); + showImageWithResults(document.getElementById("drop_zone"), fileData, file.type, results); } reader.readAsArrayBuffer(file); } -function showImage(container, fileData, fileType, position) { +function showImageWithResults(container, fileData, fileType, results) { fileType = fileType || "image/jpeg"; container.innerHTML = ""; var img = document.createElement("img"); img.addEventListener('load', function() { + const canvas = document.createElement("canvas"); container.style.width = img.width + 'px'; container.style.height = img.height + 'px'; - const canvas = document.createElement("canvas"); canvas.width = img.width; canvas.height = img.height; const ctx = canvas.getContext("2d"); ctx.drawImage(img, 0, 0); - ctx.beginPath(); - ctx.moveTo(position.topLeft.x, position.topLeft.y); - ctx.lineTo(position.topRight.x, position.topRight.y); - ctx.lineTo(position.bottomRight.x, position.bottomRight.y); - ctx.lineTo(position.bottomLeft.x, position.bottomLeft.y); - ctx.closePath(); - ctx.strokeStyle = "red"; - ctx.lineWidth = 5; - ctx.stroke(); + + for (let i = 0; i < results.size(); i += 1) { + const { position } = results.get(i); + // Draw outline square + ctx.beginPath(); + ctx.moveTo(position.topLeft.x, position.topLeft.y); + ctx.lineTo(position.topRight.x, position.topRight.y); + ctx.lineTo(position.bottomRight.x, position.bottomRight.y); + ctx.lineTo(position.bottomLeft.x, position.bottomLeft.y); + ctx.closePath(); + ctx.strokeStyle = "red"; + ctx.lineWidth = 5; + ctx.stroke(); + + // Draw number inside square + const text = { + text: i + 1, + size: Math.abs((position.bottomLeft.y - position.topLeft.y)), + x: (position.topLeft.x + position.topRight.x) / 2.0, + y: (position.topLeft.y + position.bottomLeft.y) / 2.0, + }; + ctx.fillStyle = "white"; + ctx.font = `bold ${text.size}px sans`; + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(text.text, text.x, text.y); + ctx.strokeStyle = "red"; + ctx.lineWidth = 1; + ctx.strokeText(text.text, text.x, text.y); + } + container.appendChild(canvas); }); img.src = "data:" + fileType + ";base64," + base64ArrayBuffer(fileData); } -function showScanResult(result) { - if (result.error) { - document.getElementById("scan_result").innerHTML = 'Error: ' + result.error + ''; - } else if (result.format) { - document.getElementById("scan_result").innerHTML = "Format: " + result.format + "
" + result.text + "
"; +function showScanResults(results) { + const scanResult = document.getElementById("scan_result"); + scanResult.innerHTML = ""; + if (results.size() >= 1 && results.get(0).error) { + scanResult.innerHTML = '
  • Error: ' + results.get(0).error + '
  • '; } else { - document.getElementById("scan_result").innerHTML = "No " + (document.getElementById("scan_format").value || "barcode") + " found"; + if (results.size() === 0) { + scanResult.innerHTML += "
  • No " + (document.getElementById("scan_format").value || "barcode") + " found
  • "; + } + + for (let i = 0; i < results.size(); i += 1) { + const { error, format, text } = results.get(i); + if (!error) { + scanResult.innerHTML += "
  • Format: " + format + "
    " + text + "
  • "; + } + } } } @@ -104,8 +135,12 @@ scanBarcode(input.files[0]); } -function clearScanImage() { - document.getElementById("drop_zone").innerHTML = "Drag your image here..."; +function clearScanResults() { + const dropZone = document.getElementById("drop_zone") + dropZone.innerHTML = "Drag your image here..."; + dropZone.style.width = "220px"; + dropZone.style.height = "150px"; + document.getElementById("scan_result").innerHTML = ""; } - +

    zxing-cpp/wasm reader demo

    This is a simple demo of the wasm wrapper of zxing-cpp @@ -185,7 +99,7 @@

    zxing-cpp/wasm reader demo

    Scan Format: - @@ -205,16 +119,20 @@

    zxing-cpp/wasm reader demo

    - -
    -
    - Drag your image here... -
    - Or -
    - Then see scan result below: -
    -
      + + +    + + Image File: + +

      + + + +

      + +
        +
        diff --git a/wrappers/wasm/demo_writer.html b/wrappers/wasm/demo_writer.html index 930318734a..16b90d411f 100644 --- a/wrappers/wasm/demo_writer.html +++ b/wrappers/wasm/demo_writer.html @@ -1,3 +1,4 @@ + @@ -6,7 +7,6 @@ zxing-cpp/wasm writer demo - From 845df020599f012406eb4c5e3fa6669bf8fa2f7a Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 4 Jul 2023 14:02:41 +0200 Subject: [PATCH 165/173] UPCA: don't return an EAN13 symbol if only UPCA is requested --- core/src/oned/ODMultiUPCEANReader.cpp | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/core/src/oned/ODMultiUPCEANReader.cpp b/core/src/oned/ODMultiUPCEANReader.cpp index 761bd6658f..aa88369c8c 100644 --- a/core/src/oned/ODMultiUPCEANReader.cpp +++ b/core/src/oned/ODMultiUPCEANReader.cpp @@ -290,6 +290,10 @@ Result MultiUPCEANReader::decodePattern(int rowNumber, PatternView& next, std::u res.format = BarcodeFormat::UPCA; } + // if we explicitly requested UPCA but not EAN13, don't return an EAN13 symbol + if (res.format == BarcodeFormat::EAN13 && ! _hints.hasFormat(BarcodeFormat::EAN13)) + return {}; + // Symbology identifier modifiers ISO/IEC 15420:2009 Annex B Table B.1 // ISO/IEC 15420:2009 (& GS1 General Specifications 5.1.3) states that the content for "]E0" should be 13 digits, // i.e. converted to EAN-13 if UPC-A/E, but not doing this here to maintain backward compatibility From b241817c78d6823b294f651034482cc895939bba Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 4 Jul 2023 14:46:47 +0200 Subject: [PATCH 166/173] WASM: more code cleanup and disabling of automatic `returnErrors` --- wrappers/wasm/BarcodeReader.cpp | 35 ++++++++++++--------------------- 1 file changed, 13 insertions(+), 22 deletions(-) diff --git a/wrappers/wasm/BarcodeReader.cpp b/wrappers/wasm/BarcodeReader.cpp index cb66471593..e56762e490 100644 --- a/wrappers/wasm/BarcodeReader.cpp +++ b/wrappers/wasm/BarcodeReader.cpp @@ -1,5 +1,6 @@ /* * Copyright 2016 Nu-book Inc. + * Copyright 2023 Axel Waggershauser */ // SPDX-License-Identifier: Apache-2.0 @@ -13,19 +14,19 @@ #define STB_IMAGE_IMPLEMENTATION #include +using namespace ZXing; + struct ReadResult { std::string format{}; std::string text{}; std::string error{}; - ZXing::Position position{}; - std::string symbologyIdentifier; + Position position{}; + std::string symbologyIdentifier{}; }; -std::vector readBarcodesFromImageView(ZXing::ImageView iv, bool tryHarder, const std::string& format, - const int maxSymbols = 0xff) +std::vector readBarcodes(ImageView iv, bool tryHarder, const std::string& format, int maxSymbols) { - using namespace ZXing; try { DecodeHints hints; hints.setTryHarder(tryHarder); @@ -34,7 +35,7 @@ std::vector readBarcodesFromImageView(ZXing::ImageView iv, bool tryH hints.setTryDownscale(tryHarder); hints.setFormats(BarcodeFormatsFromString(format)); hints.setMaxNumberOfSymbols(maxSymbols); - hints.setReturnErrors(maxSymbols > 1); +// hints.setReturnErrors(maxSymbols > 1); auto results = ReadBarcodes(iv, hints); @@ -54,11 +55,8 @@ std::vector readBarcodesFromImageView(ZXing::ImageView iv, bool tryH return {}; } -std::vector readBarcodesFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format, - const int maxSymbols) +std::vector readBarcodesFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format, int maxSymbols) { - using namespace ZXing; - int width, height, channels; std::unique_ptr buffer( stbi_load_from_memory(reinterpret_cast(bufferPtr), bufferLength, &width, &height, &channels, 1), @@ -66,29 +64,22 @@ std::vector readBarcodesFromImage(int bufferPtr, int bufferLength, b if (buffer == nullptr) return {{"", "", "Error loading image"}}; - return readBarcodesFromImageView({buffer.get(), width, height, ImageFormat::Lum}, tryHarder, format, maxSymbols); + return readBarcodes({buffer.get(), width, height, ImageFormat::Lum}, tryHarder, format, maxSymbols); } ReadResult readBarcodeFromImage(int bufferPtr, int bufferLength, bool tryHarder, std::string format) { - using namespace ZXing; - auto results = readBarcodesFromImage(bufferPtr, bufferLength, tryHarder, format, 1); - return results.empty() ? ReadResult() : results.front(); + return FirstOrDefault(readBarcodesFromImage(bufferPtr, bufferLength, tryHarder, format, 1)); } -std::vector readBarcodesFromPixmap(int bufferPtr, int imgWidth, int imgHeight, bool tryHarder, std::string format, - const int maxSymbols) +std::vector readBarcodesFromPixmap(int bufferPtr, int imgWidth, int imgHeight, bool tryHarder, std::string format, int maxSymbols) { - using namespace ZXing; - return readBarcodesFromImageView({reinterpret_cast(bufferPtr), imgWidth, imgHeight, ImageFormat::RGBX}, tryHarder, - format, maxSymbols); + return readBarcodes({reinterpret_cast(bufferPtr), imgWidth, imgHeight, ImageFormat::RGBX}, tryHarder, format, maxSymbols); } ReadResult readBarcodeFromPixmap(int bufferPtr, int imgWidth, int imgHeight, bool tryHarder, std::string format) { - using namespace ZXing; - auto results = readBarcodesFromPixmap(bufferPtr, imgWidth, imgHeight, tryHarder, format, 1); - return results.empty() ? ReadResult() : results.front(); + return FirstOrDefault(readBarcodesFromPixmap(bufferPtr, imgWidth, imgHeight, tryHarder, format, 1)); } EMSCRIPTEN_BINDINGS(BarcodeReader) From 0f1f3e7d16fcaacfda1844e76841ecf4e430555d Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 4 Jul 2023 14:47:33 +0200 Subject: [PATCH 167/173] WASM: render errors differently in demo_reader.html --- wrappers/wasm/demo_reader.html | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/wrappers/wasm/demo_reader.html b/wrappers/wasm/demo_reader.html index 6793421604..cbcb7cfcca 100644 --- a/wrappers/wasm/demo_reader.html +++ b/wrappers/wasm/demo_reader.html @@ -74,16 +74,12 @@ const resultsDiv = document.getElementById("results"); resultsDiv.innerHTML = ""; if (results.size() === 0) { - resultsDiv.innerHTML += "
      1. No " + (document.getElementById("format").value || "barcode") + " found
      2. "; + resultsDiv.innerHTML += "No " + (document.getElementById("format").value || "barcode") + " found"; } else { for (let i = 0; i < results.size(); i += 1) { const { error, format, text } = results.get(i); - if (!error) { - resultsDiv.innerHTML += "
      3. Format: " + format + "
        " + text + "
      4. "; - } else { - resultsDiv.innerHTML += '
      5. Error: ' + error + '
      6. '; - } + resultsDiv.innerHTML += "
      7. Format: " + format + "
        " + (text || 'Error: ' + error + '') + "
      8. "; } } } From f731468b1f4689698f287d88b3b2607fb4a30645 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 4 Jul 2023 14:50:28 +0200 Subject: [PATCH 168/173] WASM: copy demo_*.html to output folder (prepare automatic gh-pages update) --- wrappers/wasm/CMakeLists.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/wrappers/wasm/CMakeLists.txt b/wrappers/wasm/CMakeLists.txt index 20a046db2d..0bc692da05 100644 --- a/wrappers/wasm/CMakeLists.txt +++ b/wrappers/wasm/CMakeLists.txt @@ -28,11 +28,14 @@ if (BUILD_READERS AND BUILD_WRITERS) endif() if (BUILD_READERS) + configure_file(demo_cam_reader.html demo_cam_reader.html COPYONLY) + configure_file(demo_reader.html demo_reader.html COPYONLY) add_executable (zxing_reader BarcodeReader.cpp) target_link_libraries (zxing_reader ZXing::ZXing stb::stb) endif() if (BUILD_WRITERS) + configure_file(demo_writer.html demo_writer.html COPYONLY) add_executable (zxing_writer BarcodeWriter.cpp ) target_link_libraries (zxing_writer ZXing::ZXing stb::stb) endif() From 817f862c5f77f6a3e8e44537714da160d4ba0b66 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 4 Jul 2023 15:07:54 +0200 Subject: [PATCH 169/173] CI: add `demo*` to wasm-artifacts --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5dee725158..e1e24630a7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -128,7 +128,9 @@ jobs: - uses: actions/upload-artifact@v3 with: name: wasm-artifacts - path: "build/zxing*" + path: | + "build/zxing*" + "build/demo*" build-python: runs-on: ${{ matrix.os }} From 66f4abaf47b27fb6de4becbc92b2f9ff113cc511 Mon Sep 17 00:00:00 2001 From: axxel Date: Tue, 4 Jul 2023 18:15:02 +0200 Subject: [PATCH 170/173] CI: try to fix 'No files were found with the provided path' error --- .github/workflows/ci.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e1e24630a7..c26a4243f4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,8 +129,8 @@ jobs: with: name: wasm-artifacts path: | - "build/zxing*" - "build/demo*" + build/zxing* + build/demo* build-python: runs-on: ${{ matrix.os }} From ba24ee0d3b6dfbaa64720f1980d8d4533c0788fb Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 5 Jul 2023 15:13:30 +0200 Subject: [PATCH 171/173] CI: add gh-pages workflow for automatic github pages creation --- .github/workflows/gh-pages.yml | 56 ++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 .github/workflows/gh-pages.yml diff --git a/.github/workflows/gh-pages.yml b/.github/workflows/gh-pages.yml new file mode 100644 index 0000000000..df51ce0721 --- /dev/null +++ b/.github/workflows/gh-pages.yml @@ -0,0 +1,56 @@ +name: gh-pages + +on: + # Runs on pushes targeting the default branch +# push: +# branches: ["master"] + + # Allows you to run this workflow manually from the Actions tab + workflow_dispatch: + +# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages +permissions: + contents: read + pages: write + id-token: write + +# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. +# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. +concurrency: + group: "pages" + cancel-in-progress: false + +jobs: + # Single deploy job since we're just deploying + deploy: + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup EMSDK + uses: mymindstorm/setup-emsdk@v12 + + - name: Configure + run: emcmake cmake -Swrappers/wasm -Bbuild + + - name: Build + run: cmake --build build -j4 + + - name: Prepare Archive + shell: sh + run: | + mkdir pages + mv build/zxing_* build/*.html pages + + - name: Upload artifact + uses: actions/upload-pages-artifact@v1 + with: + path: 'pages' + + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v2 From 0e1bbeed8f869377d6b4f53f9e52bdcc7045c7ef Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 5 Jul 2023 15:35:17 +0200 Subject: [PATCH 172/173] README: update links to WASM live demos --- README.md | 2 +- wrappers/wasm/README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a4397d90cb..f47b910504 100644 --- a/README.md +++ b/README.md @@ -62,7 +62,7 @@ As an example, have a look at [`ZXingWriter.cpp`](example/ZXingWriter.cpp). ## Web Demos - [Read barcodes](https://zxing-cpp.github.io/zxing-cpp/demo_reader.html) - [Write barcodes](https://zxing-cpp.github.io/zxing-cpp/demo_writer.html) -- [Scan with camera](https://zxing-cpp.github.io/zxing-cpp/zxing_viddemo.html) +- [Read barcodes from camera](https://zxing-cpp.github.io/zxing-cpp/demo_cam_reader.html) [Note: those live demos are not necessarily fully up-to-date at all times.] diff --git a/wrappers/wasm/README.md b/wrappers/wasm/README.md index 53560f71c3..b5dd82d3a8 100644 --- a/wrappers/wasm/README.md +++ b/wrappers/wasm/README.md @@ -8,7 +8,7 @@ 4. To see how to include these into a working HTML page, have a look at the [reader](demo_reader.html), [writer](demo_writer.html) and [cam reader](demo_cam_reader.html) demos. 5. To quickly test your build, copy those demo files into your build directory and run e.g. `emrun --serve_after_close demo_reader.html`. -You can also download the latest build output from the continuous integration system from the [Actions](https://github.com/zxing-cpp/zxing-cpp/actions) tab. Look for 'wasm-artifacts'. Also check out the [live demos](https://zxing-cpp.github.io/zxing-cpp/). +You can also download the latest build output from the continuous integration system from the [Actions](https://github.com/zxing-cpp/zxing-cpp/actions) tab. Look for 'wasm-artifacts'. Also check out the [live demos](https://github.com/zxing-cpp/zxing-cpp#web-demos). ## Performance From 1bb03a85ef9846076fc5068b05646454f7fe6f6f Mon Sep 17 00:00:00 2001 From: axxel Date: Wed, 5 Jul 2023 18:25:57 +0200 Subject: [PATCH 173/173] Release: bump version 2.0.0 -> 2.1.0 --- core/CMakeLists.txt | 2 +- wrappers/python/setup.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/core/CMakeLists.txt b/core/CMakeLists.txt index 79227d8cfb..24cebd93ab 100644 --- a/core/CMakeLists.txt +++ b/core/CMakeLists.txt @@ -1,6 +1,6 @@ cmake_minimum_required(VERSION 3.15) -project (ZXing VERSION "2.0.0") +project (ZXing VERSION "2.1.0") set (ZXING_SONAME 3) # see https://github.com/zxing-cpp/zxing-cpp/issues/333 include(../zxing.cmake) diff --git a/wrappers/python/setup.py b/wrappers/python/setup.py index abb78d31e0..232b5a731f 100644 --- a/wrappers/python/setup.py +++ b/wrappers/python/setup.py @@ -59,7 +59,7 @@ def build_extension(self, ext): # "local_scheme": "no-local-version", # "tag_regex": "v?([0-9]+.[0-9]+.[0-9]+)", # }, - version='2.0.0', + version='2.1.0', description='Python bindings for the zxing-cpp barcode library', long_description=long_description, long_description_content_type="text/markdown", pFad - Phonifier reborn

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

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


        Alternative Proxies:

        Alternative Proxy

        pFad Proxy

        pFad v3 Proxy

        pFad v4 Proxy