comparison mupdf-source/thirdparty/zxing-cpp/core/src/ReadBarcode.cpp @ 2:b50eed0cc0ef upstream

ADD: MuPDF v1.26.7: the MuPDF source as downloaded by a default build of PyMuPDF 1.26.4. The directory name has changed: no version number in the expanded directory now.
author Franz Glasner <fzglas.hg@dom66.de>
date Mon, 15 Sep 2025 11:43:07 +0200
parents
children
comparison
equal deleted inserted replaced
1:1d09e1dec1d9 2:b50eed0cc0ef
1 /*
2 * Copyright 2019 Axel Waggershauser
3 */
4 // SPDX-License-Identifier: Apache-2.0
5
6 #include "ReadBarcode.h"
7
8 #if !defined(ZXING_READERS) && !defined(ZXING_WRITERS)
9 #include "Version.h"
10 #endif
11
12 #ifdef ZXING_READERS
13 #include "GlobalHistogramBinarizer.h"
14 #include "HybridBinarizer.h"
15 #include "MultiFormatReader.h"
16 #include "Pattern.h"
17 #include "ThresholdBinarizer.h"
18 #endif
19
20 #include <climits>
21 #include <memory>
22 #include <stdexcept>
23
24 namespace ZXing {
25
26 #ifdef ZXING_READERS
27
28 class LumImage : public Image
29 {
30 public:
31 using Image::Image;
32
33 uint8_t* data() { return const_cast<uint8_t*>(Image::data()); }
34 };
35
36 template<typename P>
37 static LumImage ExtractLum(const ImageView& iv, P projection)
38 {
39 LumImage res(iv.width(), iv.height());
40
41 auto* dst = res.data();
42 for(int y = 0; y < iv.height(); ++y)
43 for(int x = 0, w = iv.width(); x < w; ++x)
44 *dst++ = projection(iv.data(x, y));
45
46 return res;
47 }
48
49 class LumImagePyramid
50 {
51 std::vector<LumImage> buffers;
52
53 template<int N>
54 void addLayer()
55 {
56 auto siv = layers.back();
57 buffers.emplace_back(siv.width() / N, siv.height() / N);
58 layers.push_back(buffers.back());
59 auto& div = buffers.back();
60 auto* d = div.data();
61
62 for (int dy = 0; dy < div.height(); ++dy)
63 for (int dx = 0; dx < div.width(); ++dx) {
64 int sum = (N * N) / 2;
65 for (int ty = 0; ty < N; ++ty)
66 for (int tx = 0; tx < N; ++tx)
67 sum += *siv.data(dx * N + tx, dy * N + ty);
68 *d++ = sum / (N * N);
69 }
70 }
71
72 void addLayer(int factor)
73 {
74 // help the compiler's auto-vectorizer by hard-coding the scale factor
75 switch (factor) {
76 case 2: addLayer<2>(); break;
77 case 3: addLayer<3>(); break;
78 case 4: addLayer<4>(); break;
79 default: throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor"); break;
80 }
81 }
82
83 public:
84 std::vector<ImageView> layers;
85
86 LumImagePyramid(const ImageView& iv, int threshold, int factor)
87 {
88 if (factor < 2)
89 throw std::invalid_argument("Invalid ReaderOptions::downscaleFactor");
90
91 layers.push_back(iv);
92 // TODO: if only matrix codes were considered, then using std::min would be sufficient (see #425)
93 while (threshold > 0 && std::max(layers.back().width(), layers.back().height()) > threshold &&
94 std::min(layers.back().width(), layers.back().height()) >= factor)
95 addLayer(factor);
96 #if 0
97 // Reversing the layers means we'd start with the smallest. that can make sense if we are only looking for a
98 // single symbol. If we start with the higher resolution, we get better (high res) position information.
99 // TODO: see if masking out higher res layers based on found symbols in lower res helps overall performance.
100 std::reverse(layers.begin(), layers.end());
101 #endif
102 }
103 };
104
105 ImageView SetupLumImageView(ImageView iv, LumImage& lum, const ReaderOptions& opts)
106 {
107 if (iv.format() == ImageFormat::None)
108 throw std::invalid_argument("Invalid image format");
109
110 if (opts.binarizer() == Binarizer::GlobalHistogram || opts.binarizer() == Binarizer::LocalAverage) {
111 // manually spell out the 3 most common pixel formats to get at least gcc to vectorize the code
112 if (iv.format() == ImageFormat::RGB && iv.pixStride() == 3) {
113 lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); });
114 } else if (iv.format() == ImageFormat::RGBA && iv.pixStride() == 4) {
115 lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[0], src[1], src[2]); });
116 } else if (iv.format() == ImageFormat::BGR && iv.pixStride() == 3) {
117 lum = ExtractLum(iv, [](const uint8_t* src) { return RGBToLum(src[2], src[1], src[0]); });
118 } else if (iv.format() != ImageFormat::Lum) {
119 lum = ExtractLum(iv, [r = RedIndex(iv.format()), g = GreenIndex(iv.format()), b = BlueIndex(iv.format())](
120 const uint8_t* src) { return RGBToLum(src[r], src[g], src[b]); });
121 } else if (iv.pixStride() != 1) {
122 // GlobalHistogram and LocalAverage need dense line memory layout
123 lum = ExtractLum(iv, [](const uint8_t* src) { return *src; });
124 }
125 if (lum.data())
126 return lum;
127 }
128 return iv;
129 }
130
131 std::unique_ptr<BinaryBitmap> CreateBitmap(ZXing::Binarizer binarizer, const ImageView& iv)
132 {
133 switch (binarizer) {
134 case Binarizer::BoolCast: return std::make_unique<ThresholdBinarizer>(iv, 0);
135 case Binarizer::FixedThreshold: return std::make_unique<ThresholdBinarizer>(iv, 127);
136 case Binarizer::GlobalHistogram: return std::make_unique<GlobalHistogramBinarizer>(iv);
137 case Binarizer::LocalAverage: return std::make_unique<HybridBinarizer>(iv);
138 }
139 return {}; // silence gcc warning
140 }
141
142 Barcode ReadBarcode(const ImageView& _iv, const ReaderOptions& opts)
143 {
144 return FirstOrDefault(ReadBarcodes(_iv, ReaderOptions(opts).setMaxNumberOfSymbols(1)));
145 }
146
147 Barcodes ReadBarcodes(const ImageView& _iv, const ReaderOptions& opts)
148 {
149 if (sizeof(PatternType) < 4 && (_iv.width() > 0xffff || _iv.height() > 0xffff))
150 throw std::invalid_argument("Maximum image width/height is 65535");
151
152 if (!_iv.data() || _iv.width() * _iv.height() == 0)
153 throw std::invalid_argument("ImageView is null/empty");
154
155 LumImage lum;
156 ImageView iv = SetupLumImageView(_iv, lum, opts);
157 MultiFormatReader reader(opts);
158
159 if (opts.isPure())
160 return {reader.read(*CreateBitmap(opts.binarizer(), iv)).setReaderOptions(opts)};
161
162 std::unique_ptr<MultiFormatReader> closedReader;
163 #ifdef ZXING_EXPERIMENTAL_API
164 auto formatsBenefittingFromClosing = BarcodeFormat::Aztec | BarcodeFormat::DataMatrix | BarcodeFormat::QRCode | BarcodeFormat::MicroQRCode;
165 ReaderOptions closedOptions = opts;
166 if (opts.tryDenoise() && opts.hasFormat(formatsBenefittingFromClosing) && _iv.height() >= 3) {
167 closedOptions.setFormats((opts.formats().empty() ? BarcodeFormat::Any : opts.formats()) & formatsBenefittingFromClosing);
168 closedReader = std::make_unique<MultiFormatReader>(closedOptions);
169 }
170 #endif
171 LumImagePyramid pyramid(iv, opts.downscaleThreshold() * opts.tryDownscale(), opts.downscaleFactor());
172
173 Barcodes res;
174 int maxSymbols = opts.maxNumberOfSymbols() ? opts.maxNumberOfSymbols() : INT_MAX;
175 for (auto&& iv : pyramid.layers) {
176 auto bitmap = CreateBitmap(opts.binarizer(), iv);
177 for (int close = 0; close <= (closedReader ? 1 : 0); ++close) {
178 if (close)
179 bitmap->close();
180
181 // TODO: check if closing after invert would be beneficial
182 for (int invert = 0; invert <= static_cast<int>(opts.tryInvert() && !close); ++invert) {
183 if (invert)
184 bitmap->invert();
185 auto rs = (close ? *closedReader : reader).readMultiple(*bitmap, maxSymbols);
186 for (auto& r : rs) {
187 if (iv.width() != _iv.width())
188 r.setPosition(Scale(r.position(), _iv.width() / iv.width()));
189 if (!Contains(res, r)) {
190 r.setReaderOptions(opts);
191 r.setIsInverted(bitmap->inverted());
192 res.push_back(std::move(r));
193 --maxSymbols;
194 }
195 }
196 if (maxSymbols <= 0)
197 return res;
198 }
199 }
200 }
201
202 return res;
203 }
204
205 #else // ZXING_READERS
206
207 Barcode ReadBarcode(const ImageView&, const ReaderOptions&)
208 {
209 throw std::runtime_error("This build of zxing-cpp does not support reading barcodes.");
210 }
211
212 Barcodes ReadBarcodes(const ImageView&, const ReaderOptions&)
213 {
214 throw std::runtime_error("This build of zxing-cpp does not support reading barcodes.");
215 }
216
217 #endif // ZXING_READERS
218
219 } // ZXing