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