comparison mupdf-source/thirdparty/zxing-cpp/example/ZXingReader.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 2016 Nu-book Inc.
3 * Copyright 2019 Axel Waggershauser
4 */
5 // SPDX-License-Identifier: Apache-2.0
6
7 #include "GTIN.h"
8 #include "ReadBarcode.h"
9 #include "Version.h"
10
11 #ifdef ZXING_EXPERIMENTAL_API
12 #include "WriteBarcode.h"
13 #endif
14
15 #include <cctype>
16 #include <chrono>
17 #include <cstring>
18 #include <iostream>
19 #include <iterator>
20 #include <memory>
21 #include <string>
22 #include <vector>
23
24 #define STB_IMAGE_IMPLEMENTATION
25 #include <stb_image.h>
26 #define STB_IMAGE_WRITE_IMPLEMENTATION
27 #include <stb_image_write.h>
28
29 using namespace ZXing;
30
31 struct CLI
32 {
33 std::vector<std::string> filePaths;
34 std::string outPath;
35 int forceChannels = 0;
36 int rotate = 0;
37 bool oneLine = false;
38 bool bytesOnly = false;
39 bool showSymbol = false;
40 };
41
42 static void PrintUsage(const char* exePath)
43 {
44 std::cout << "Usage: " << exePath << " [options] <image file>...\n"
45 << " -fast Skip some lines/pixels during detection (faster)\n"
46 << " -norotate Don't try rotated image during detection (faster)\n"
47 << " -noinvert Don't search for inverted codes during detection (faster)\n"
48 << " -noscale Don't try downscaled images during detection (faster)\n"
49 << " -formats <FORMAT[,...]>\n"
50 << " Only detect given format(s) (faster)\n"
51 << " -single Stop after the first barcode is detected (faster)\n"
52 << " -ispure Assume the image contains only a 'pure'/perfect code (faster)\n"
53 << " -errors Include barcodes with errors (like checksum error)\n"
54 << " -binarizer <local|global|fixed>\n"
55 << " Binarizer to be used for gray to binary conversion\n"
56 << " -mode <plain|eci|hri|escaped>\n"
57 << " Text mode used to render the raw byte content into text\n"
58 << " -1 Print only file name, content/error on one line per file/barcode (implies '-mode Escaped')\n"
59 #ifdef ZXING_EXPERIMENTAL_API
60 << " -symbol Print the detected symbol (if available)\n"
61 #endif
62 << " -bytes Write (only) the bytes content of the symbol(s) to stdout\n"
63 << " -pngout <file name>\n"
64 << " Write a copy of the input image with barcodes outlined by a green line\n"
65 << " -help Print usage information\n"
66 << " -version Print version information\n"
67 << "\n"
68 << "Supported formats are:\n";
69 for (auto f : BarcodeFormats::all()) {
70 std::cout << " " << ToString(f) << "\n";
71 }
72 std::cout << "Formats can be lowercase, with or without '-', separated by ',' and/or '|'\n";
73 }
74
75 static bool ParseOptions(int argc, char* argv[], ReaderOptions& options, CLI& cli)
76 {
77 #ifdef ZXING_EXPERIMENTAL_API
78 options.setTryDenoise(true);
79 #endif
80
81 for (int i = 1; i < argc; ++i) {
82 auto is = [&](const char* str) { return strlen(argv[i]) > 1 && strncmp(argv[i], str, strlen(argv[i])) == 0; };
83 if (is("-fast")) {
84 options.setTryHarder(false);
85 #ifdef ZXING_EXPERIMENTAL_API
86 options.setTryDenoise(false);
87 #endif
88 } else if (is("-norotate")) {
89 options.setTryRotate(false);
90 } else if (is("-noinvert")) {
91 options.setTryInvert(false);
92 } else if (is("-noscale")) {
93 options.setTryDownscale(false);
94 } else if (is("-single")) {
95 options.setMaxNumberOfSymbols(1);
96 } else if (is("-ispure")) {
97 options.setIsPure(true);
98 options.setBinarizer(Binarizer::FixedThreshold);
99 } else if (is("-errors")) {
100 options.setReturnErrors(true);
101 } else if (is("-formats")) {
102 if (++i == argc)
103 return false;
104 try {
105 options.setFormats(BarcodeFormatsFromString(argv[i]));
106 } catch (const std::exception& e) {
107 std::cerr << e.what() << "\n";
108 return false;
109 }
110 } else if (is("-binarizer")) {
111 if (++i == argc)
112 return false;
113 else if (is("local"))
114 options.setBinarizer(Binarizer::LocalAverage);
115 else if (is("global"))
116 options.setBinarizer(Binarizer::GlobalHistogram);
117 else if (is("fixed"))
118 options.setBinarizer(Binarizer::FixedThreshold);
119 else
120 return false;
121 } else if (is("-mode")) {
122 if (++i == argc)
123 return false;
124 else if (is("plain"))
125 options.setTextMode(TextMode::Plain);
126 else if (is("eci"))
127 options.setTextMode(TextMode::ECI);
128 else if (is("hri"))
129 options.setTextMode(TextMode::HRI);
130 else if (is("escaped"))
131 options.setTextMode(TextMode::Escaped);
132 else
133 return false;
134 } else if (is("-1")) {
135 cli.oneLine = true;
136 } else if (is("-bytes")) {
137 cli.bytesOnly = true;
138 } else if (is("-symbol")) {
139 cli.showSymbol = true;
140 } else if (is("-pngout")) {
141 if (++i == argc)
142 return false;
143 cli.outPath = argv[i];
144 } else if (is("-channels")) {
145 if (++i == argc)
146 return false;
147 cli.forceChannels = atoi(argv[i]);
148 } else if (is("-rotate")) {
149 if (++i == argc)
150 return false;
151 cli.rotate = atoi(argv[i]);
152 } else if (is("-help") || is("--help")) {
153 PrintUsage(argv[0]);
154 exit(0);
155 } else if (is("-version") || is("--version")) {
156 std::cout << "ZXingReader " << ZXING_VERSION_STR << "\n";
157 exit(0);
158 } else {
159 cli.filePaths.push_back(argv[i]);
160 }
161 }
162
163 return !cli.filePaths.empty();
164 }
165
166 void drawLine(const ImageView& iv, PointI a, PointI b, bool error)
167 {
168 int steps = maxAbsComponent(b - a);
169 PointF dir = bresenhamDirection(PointF(b - a));
170 int R = RedIndex(iv.format()), G = GreenIndex(iv.format()), B = BlueIndex(iv.format());
171 for (int i = 0; i < steps; ++i) {
172 auto p = PointI(centered(a + i * dir));
173 auto* dst = const_cast<uint8_t*>(iv.data(p.x, p.y));
174 if (dst < iv.data(0, 0) || dst > iv.data(iv.width() - 1, iv.height() - 1))
175 continue;
176 dst[R] = error ? 0xff : 0;
177 dst[G] = error ? 0 : 0xff;
178 dst[B] = 0;
179 }
180 }
181
182 void drawRect(const ImageView& image, const Position& pos, bool error)
183 {
184 for (int i = 0; i < 4; ++i)
185 drawLine(image, pos[i], pos[(i + 1) % 4], error);
186 }
187
188 int main(int argc, char* argv[])
189 {
190 ReaderOptions options;
191 CLI cli;
192 Barcodes allBarcodes;
193 int ret = 0;
194
195 options.setTextMode(TextMode::HRI);
196 options.setEanAddOnSymbol(EanAddOnSymbol::Read);
197
198 if (!ParseOptions(argc, argv, options, cli)) {
199 PrintUsage(argv[0]);
200 return -1;
201 }
202
203 std::cout.setf(std::ios::boolalpha);
204
205 if (!cli.outPath.empty())
206 cli.forceChannels = 3; // the drawing code only works for RGB data
207
208 for (const auto& filePath : cli.filePaths) {
209 int width, height, channels;
210 std::unique_ptr<stbi_uc, void (*)(void*)> buffer(
211 filePath == "-" ? stbi_load_from_file(stdin, &width, &height, &channels, cli.forceChannels)
212 : stbi_load(filePath.c_str(), &width, &height, &channels, cli.forceChannels),
213 stbi_image_free);
214 if (buffer == nullptr) {
215 std::cerr << "Failed to read image: " << filePath << " (" << stbi_failure_reason() << ")" << "\n";
216 return -1;
217 }
218 channels = cli.forceChannels ? cli.forceChannels : channels;
219
220 auto ImageFormatFromChannels = std::array{ImageFormat::None, ImageFormat::Lum, ImageFormat::LumA, ImageFormat::RGB, ImageFormat::RGBA};
221 ImageView image{buffer.get(), width, height, ImageFormatFromChannels.at(channels)};
222 auto barcodes = ReadBarcodes(image.rotated(cli.rotate), options);
223
224 // if we did not find anything, insert a dummy to produce some output for each file
225 if (barcodes.empty())
226 barcodes.emplace_back();
227
228 allBarcodes.insert(allBarcodes.end(), barcodes.begin(), barcodes.end());
229 if (filePath == cli.filePaths.back()) {
230 auto merged = MergeStructuredAppendSequences(allBarcodes);
231 // report all merged sequences as part of the last file to make the logic not overly complicated here
232 barcodes.insert(barcodes.end(), std::make_move_iterator(merged.begin()), std::make_move_iterator(merged.end()));
233 }
234
235 for (auto&& barcode : barcodes) {
236
237 if (!cli.outPath.empty())
238 drawRect(image, barcode.position(), bool(barcode.error()));
239
240 ret |= static_cast<int>(barcode.error().type());
241
242 if (cli.bytesOnly) {
243 std::cout.write(reinterpret_cast<const char*>(barcode.bytes().data()), barcode.bytes().size());
244 continue;
245 }
246
247 if (cli.oneLine) {
248 std::cout << filePath << " " << ToString(barcode.format());
249 if (barcode.isValid())
250 std::cout << " \"" << barcode.text(TextMode::Escaped) << "\"";
251 else if (barcode.error())
252 std::cout << " " << ToString(barcode.error());
253 std::cout << "\n";
254 continue;
255 }
256
257 if (cli.filePaths.size() > 1 || barcodes.size() > 1) {
258 static bool firstFile = true;
259 if (!firstFile)
260 std::cout << "\n";
261 if (cli.filePaths.size() > 1)
262 std::cout << "File: " << filePath << "\n";
263 firstFile = false;
264 }
265
266 if (barcode.format() == BarcodeFormat::None) {
267 std::cout << "No barcode found\n";
268 continue;
269 }
270
271 std::cout << "Text: \"" << barcode.text() << "\"\n"
272 << "Bytes: " << ToHex(options.textMode() == TextMode::ECI ? barcode.bytesECI() : barcode.bytes()) << "\n"
273 << "Format: " << ToString(barcode.format()) << "\n"
274 << "Identifier: " << barcode.symbologyIdentifier() << "\n"
275 << "Content: " << ToString(barcode.contentType()) << "\n"
276 << "HasECI: " << barcode.hasECI() << "\n"
277 << "Position: " << ToString(barcode.position()) << "\n"
278 << "Rotation: " << barcode.orientation() << " deg\n"
279 << "IsMirrored: " << barcode.isMirrored() << "\n"
280 << "IsInverted: " << barcode.isInverted() << "\n";
281
282 auto printOptional = [](const char* key, const std::string& v) {
283 if (!v.empty())
284 std::cout << key << v << "\n";
285 };
286
287 printOptional("EC Level: ", barcode.ecLevel());
288 printOptional("Version: ", barcode.version());
289 printOptional("Error: ", ToString(barcode.error()));
290
291 if (barcode.lineCount())
292 std::cout << "Lines: " << barcode.lineCount() << "\n";
293
294 if ((BarcodeFormat::EAN13 | BarcodeFormat::EAN8 | BarcodeFormat::UPCA | BarcodeFormat::UPCE)
295 .testFlag(barcode.format())) {
296 printOptional("Country: ", GTIN::LookupCountryIdentifier(barcode.text(), barcode.format()));
297 printOptional("Add-On: ", GTIN::EanAddOn(barcode));
298 printOptional("Price: ", GTIN::Price(GTIN::EanAddOn(barcode)));
299 printOptional("Issue #: ", GTIN::IssueNr(GTIN::EanAddOn(barcode)));
300 } else if (barcode.format() == BarcodeFormat::ITF && Size(barcode.bytes()) == 14) {
301 printOptional("Country: ", GTIN::LookupCountryIdentifier(barcode.text(), barcode.format()));
302 }
303
304 if (barcode.isPartOfSequence())
305 std::cout << "Structured Append: symbol " << barcode.sequenceIndex() + 1 << " of "
306 << barcode.sequenceSize() << " (parity/id: '" << barcode.sequenceId() << "')\n";
307 else if (barcode.sequenceSize() > 0)
308 std::cout << "Structured Append: merged result from " << barcode.sequenceSize() << " symbols (parity/id: '"
309 << barcode.sequenceId() << "')\n";
310
311 if (barcode.readerInit())
312 std::cout << "Reader Initialisation/Programming\n";
313
314 #ifdef ZXING_EXPERIMENTAL_API
315 if (cli.showSymbol && barcode.symbol().data())
316 std::cout << "Symbol:\n" << WriteBarcodeToUtf8(barcode);
317 #endif
318 }
319
320 if (Size(cli.filePaths) == 1 && !cli.outPath.empty())
321 stbi_write_png(cli.outPath.c_str(), image.width(), image.height(), 3, image.data(), image.rowStride());
322
323 #ifdef NDEBUG
324 if (getenv("MEASURE_PERF")) {
325 auto startTime = std::chrono::high_resolution_clock::now();
326 auto duration = startTime - startTime;
327 int N = 0;
328 int blockSize = 1;
329 do {
330 for (int i = 0; i < blockSize; ++i)
331 ReadBarcodes(image, options);
332 N += blockSize;
333 duration = std::chrono::high_resolution_clock::now() - startTime;
334 if (blockSize < 1000 && duration < std::chrono::milliseconds(100))
335 blockSize *= 10;
336 } while (duration < std::chrono::seconds(1));
337 printf("time: %5.2f ms per frame\n", double(std::chrono::duration_cast<std::chrono::milliseconds>(duration).count()) / N);
338 }
339 #endif
340 }
341
342 return ret;
343 }