Mercurial > hgrepos > Python2 > PyMuPDF
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 } |
