diff --git a/core/src/BitHacks.h b/core/src/BitHacks.h index f21a15460e..4545a24439 100644 --- a/core/src/BitHacks.h +++ b/core/src/BitHacks.h @@ -97,6 +97,12 @@ inline int HighestBitSet(uint32_t v) return 31 - NumberOfLeadingZeros(v); } +template +inline Int FilterLowestBit(Int v) +{ + return v & ~(v - 1); +} + // shift a whole array of bits by offset bits to the right (thinking of the array as a contiguous stream of bits // starting with the LSB of the first int and ending with the MSB of the last int, this is actually a left shift) template diff --git a/core/src/DecodeHints.cpp b/core/src/DecodeHints.cpp index 72ded6073c..31054c1a7b 100644 --- a/core/src/DecodeHints.cpp +++ b/core/src/DecodeHints.cpp @@ -7,5 +7,18 @@ #include "DecodeHints.h" namespace ZXing { + BinarizerV2 UpgradeBinarizer(Binarizer binarizer){ + switch(binarizer){ + case Binarizer::BoolCast: + return BinarizerV2::BoolCast; + case Binarizer::FixedThreshold: + return BinarizerV2::FixedThreshold; + case Binarizer::GlobalHistogram: + return BinarizerV2::GlobalHistogram; + case Binarizer::LocalAverage: + return BinarizerV2::LocalAverage; + } + return BinarizerV2::None; + } } // ZXing diff --git a/core/src/DecodeHints.h b/core/src/DecodeHints.h index 97069d6a8c..49d8d738ae 100644 --- a/core/src/DecodeHints.h +++ b/core/src/DecodeHints.h @@ -9,7 +9,9 @@ #include "BarcodeFormat.h" #include "CharacterSet.h" +#include "Flags.h" +#include #include #include @@ -30,6 +32,19 @@ enum class Binarizer : unsigned char // needs to be unsigned for the bitfield be BoolCast, ///< T = 0, fastest possible }; +enum class BinarizerV2 : uint32_t +{ + None = 0, + LocalAverage = (1 << 0), ///< T = average of neighboring pixels for matrix and GlobalHistogram for linear (HybridBinarizer) + GlobalHistogram = (1 << 1), ///< T = valley between the 2 largest peaks in the histogram (per line in linear case) + FixedThreshold = (1 << 2), ///< T = 127 + BoolCast = (1 << 3), ///< T = 0, fastest possible +}; + +BinarizerV2 UpgradeBinarizer(Binarizer binarizer); + +ZX_DECLARE_FLAGS(BinarizerSequence, BinarizerV2) + enum class EanAddOnSymbol : unsigned char // see above { Ignore, ///< Ignore any Add-On symbol during read/scan @@ -68,6 +83,7 @@ class DecodeHints uint8_t _maxNumberOfSymbols = 0xff; uint16_t _downscaleThreshold = 500; BarcodeFormats _formats = BarcodeFormat::None; + BinarizerSequence _binarizerSequence = BinarizerV2::None; public: // bitfields don't get default initialized to 0 before c++20 @@ -155,6 +171,8 @@ class DecodeHints DecodeHints& setCharacterSet(std::string_view v)& { return _characterSet = CharacterSetFromString(v), *this; } DecodeHints&& setCharacterSet(std::string_view v) && { return _characterSet = CharacterSetFromString(v), std::move(*this); } + ZX_PROPERTY(BinarizerSequence, binarizerSequence, setBinarizerSequence) + #undef ZX_PROPERTY bool hasFormat(BarcodeFormats f) const noexcept { return _formats.testFlags(f) || _formats.empty(); } diff --git a/core/src/Flags.h b/core/src/Flags.h index c5f2e2a8ab..f183e70233 100644 --- a/core/src/Flags.h +++ b/core/src/Flags.h @@ -100,6 +100,12 @@ class Flags constexpr static Flags all() noexcept { return ~(unsigned(~0) << highestBitSet(Int(Enum::_max))); } + Enum unpackNext() { + Enum res = static_cast(BitHacks::FilterLowestBit(i)); + setFlag(res, false); + return res; + } + private: // constexpr static inline Int // initializer_list_helper(typename std::initializer_list::const_iterator it, diff --git a/core/src/ReadBarcode.cpp b/core/src/ReadBarcode.cpp index 5ac61e250e..4de229bd19 100644 --- a/core/src/ReadBarcode.cpp +++ b/core/src/ReadBarcode.cpp @@ -88,12 +88,12 @@ class LumImagePyramid } }; -ImageView SetupLumImageView(ImageView iv, LumImage& lum, const DecodeHints& hints) +ImageView SetupLumImageView(ImageView iv, LumImage& lum, BinarizerV2 binarizer) { if (iv.format() == ImageFormat::None) throw std::invalid_argument("Invalid image format"); - if (hints.binarizer() == Binarizer::GlobalHistogram || hints.binarizer() == Binarizer::LocalAverage) { + if (binarizer == BinarizerV2::GlobalHistogram || binarizer == BinarizerV2::LocalAverage) { if (iv.format() != ImageFormat::Lum) { lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())]( const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); }); @@ -107,13 +107,13 @@ ImageView SetupLumImageView(ImageView iv, LumImage& lum, const DecodeHints& hint return iv; } -std::unique_ptr CreateBitmap(ZXing::Binarizer binarizer, const ImageView& iv) +std::unique_ptr CreateBitmap(ZXing::BinarizerV2 binarizer, const ImageView& iv) { switch (binarizer) { - case Binarizer::BoolCast: return std::make_unique(iv, 0); - case Binarizer::FixedThreshold: return std::make_unique(iv, 127); - case Binarizer::GlobalHistogram: return std::make_unique(iv); - case Binarizer::LocalAverage: return std::make_unique(iv); + case BinarizerV2::BoolCast: return std::make_unique(iv, 0); + case BinarizerV2::FixedThreshold: return std::make_unique(iv, 127); + case BinarizerV2::GlobalHistogram: return std::make_unique(iv); + case BinarizerV2::LocalAverage: return std::make_unique(iv); } return {}; // silence gcc warning } @@ -126,34 +126,51 @@ Result ReadBarcode(const ImageView& _iv, const DecodeHints& hints) Results ReadBarcodes(const ImageView& _iv, const DecodeHints& hints) { LumImage lum; - ImageView iv = SetupLumImageView(_iv, lum, hints); - MultiFormatReader reader(hints); - if (hints.isPure()) - return {reader.read(*CreateBitmap(hints.binarizer(), iv))}; + auto binarizers = hints.binarizerSequence(); + if(binarizers.empty()){ + // Try use the legacy entry + binarizers.setFlag(UpgradeBinarizer(hints.binarizer())); + } - LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); + if (hints.isPure()){ + // No support for multiple binarizers in pure mode. + BinarizerV2 firstBinarizer = binarizers.unpackNext(); + ImageView iv = SetupLumImageView(_iv, lum, firstBinarizer); + MultiFormatReader reader(hints); + return {reader.read(*CreateBitmap(firstBinarizer, iv))}; + } Results results; int maxSymbols = hints.maxNumberOfSymbols(); - 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; + BinarizerV2 currentBinarizer = BinarizerV2::None; + while((currentBinarizer = binarizers.unpackNext()) != BinarizerV2::None){ + ImageView iv = SetupLumImageView(_iv, lum, currentBinarizer); + + // TODO: If GlobalHistogram and LocalAverage are both specified, only read 1D barcodes once. + MultiFormatReader reader(hints); + + LumImagePyramid pyramid(iv, hints.downscaleThreshold() * hints.tryDownscale(), hints.downscaleFactor()); + + for (auto&& iv : pyramid.layers) { + auto bitmap = CreateBitmap(currentBinarizer, 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; + } } + if (maxSymbols <= 0) + return results; } - if (maxSymbols <= 0) - return results; } }