ZXingReader.cpp 11 KB

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