ZXingQtReader.h 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478
  1. /*
  2. * Copyright 2020 Axel Waggershauser
  3. */
  4. // SPDX-License-Identifier: Apache-2.0
  5. #pragma once
  6. #include "ReadBarcode.h"
  7. #include <QImage>
  8. #include <QDebug>
  9. #include <QMetaType>
  10. #include <QScopeGuard>
  11. #ifdef QT_MULTIMEDIA_LIB
  12. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  13. #include <QAbstractVideoFilter>
  14. #else
  15. #include <QVideoFrame>
  16. #include <QVideoSink>
  17. #endif
  18. #include <QElapsedTimer>
  19. #endif
  20. // This is some sample code to start a discussion about how a minimal and header-only Qt wrapper/helper could look like.
  21. namespace ZXingQt {
  22. Q_NAMESPACE
  23. //TODO: find a better way to export these enums to QML than to duplicate their definition
  24. // #ifdef Q_MOC_RUN produces meta information in the moc output but it does end up working in qml
  25. #ifdef QT_QML_LIB
  26. enum class BarcodeFormat
  27. {
  28. None = 0, ///< Used as a return value if no valid barcode has been detected
  29. Aztec = (1 << 0), ///< Aztec
  30. Codabar = (1 << 1), ///< Codabar
  31. Code39 = (1 << 2), ///< Code39
  32. Code93 = (1 << 3), ///< Code93
  33. Code128 = (1 << 4), ///< Code128
  34. DataBar = (1 << 5), ///< GS1 DataBar, formerly known as RSS 14
  35. DataBarExpanded = (1 << 6), ///< GS1 DataBar Expanded, formerly known as RSS EXPANDED
  36. DataMatrix = (1 << 7), ///< DataMatrix
  37. EAN8 = (1 << 8), ///< EAN-8
  38. EAN13 = (1 << 9), ///< EAN-13
  39. ITF = (1 << 10), ///< ITF (Interleaved Two of Five)
  40. MaxiCode = (1 << 11), ///< MaxiCode
  41. PDF417 = (1 << 12), ///< PDF417 or
  42. QRCode = (1 << 13), ///< QR Code
  43. UPCA = (1 << 14), ///< UPC-A
  44. UPCE = (1 << 15), ///< UPC-E
  45. MicroQRCode = (1 << 16), ///< Micro QR Code
  46. RMQRCode = (1 << 17), ///< Rectangular Micro QR Code
  47. DXFilmEdge = (1 << 18), ///< DX Film Edge Barcode
  48. DataBarLimited = (1 << 19), ///< GS1 DataBar Limited
  49. LinearCodes = Codabar | Code39 | Code93 | Code128 | EAN8 | EAN13 | ITF | DataBar | DataBarExpanded | DataBarLimited | DXFilmEdge | UPCA | UPCE,
  50. MatrixCodes = Aztec | DataMatrix | MaxiCode | PDF417 | QRCode | MicroQRCode | RMQRCode,
  51. };
  52. enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI };
  53. enum class TextMode { Plain, ECI, HRI, Hex, Escaped };
  54. #else
  55. using ZXing::BarcodeFormat;
  56. using ZXing::ContentType;
  57. using ZXing::TextMode;
  58. #endif
  59. using ZXing::ReaderOptions;
  60. using ZXing::Binarizer;
  61. using ZXing::BarcodeFormats;
  62. Q_ENUM_NS(BarcodeFormat)
  63. Q_ENUM_NS(ContentType)
  64. Q_ENUM_NS(TextMode)
  65. template<typename T, typename = decltype(ZXing::ToString(T()))>
  66. QDebug operator<<(QDebug dbg, const T& v)
  67. {
  68. return dbg.noquote() << QString::fromStdString(ToString(v));
  69. }
  70. class Position : public ZXing::Quadrilateral<QPoint>
  71. {
  72. Q_GADGET
  73. Q_PROPERTY(QPoint topLeft READ topLeft)
  74. Q_PROPERTY(QPoint topRight READ topRight)
  75. Q_PROPERTY(QPoint bottomRight READ bottomRight)
  76. Q_PROPERTY(QPoint bottomLeft READ bottomLeft)
  77. using Base = ZXing::Quadrilateral<QPoint>;
  78. public:
  79. using Base::Base;
  80. };
  81. class Barcode : private ZXing::Barcode
  82. {
  83. Q_GADGET
  84. Q_PROPERTY(BarcodeFormat format READ format)
  85. Q_PROPERTY(QString formatName READ formatName)
  86. Q_PROPERTY(QString text READ text)
  87. Q_PROPERTY(QByteArray bytes READ bytes)
  88. Q_PROPERTY(bool isValid READ isValid)
  89. Q_PROPERTY(ContentType contentType READ contentType)
  90. Q_PROPERTY(QString contentTypeName READ contentTypeName)
  91. Q_PROPERTY(Position position READ position)
  92. QString _text;
  93. QByteArray _bytes;
  94. Position _position;
  95. public:
  96. Barcode() = default; // required for qmetatype machinery
  97. explicit Barcode(ZXing::Barcode&& r) : ZXing::Barcode(std::move(r)) {
  98. _text = QString::fromStdString(ZXing::Barcode::text());
  99. _bytes = QByteArray(reinterpret_cast<const char*>(ZXing::Barcode::bytes().data()), Size(ZXing::Barcode::bytes()));
  100. auto& pos = ZXing::Barcode::position();
  101. auto qp = [&pos](int i) { return QPoint(pos[i].x, pos[i].y); };
  102. _position = {qp(0), qp(1), qp(2), qp(3)};
  103. }
  104. using ZXing::Barcode::isValid;
  105. BarcodeFormat format() const { return static_cast<BarcodeFormat>(ZXing::Barcode::format()); }
  106. ContentType contentType() const { return static_cast<ContentType>(ZXing::Barcode::contentType()); }
  107. QString formatName() const { return QString::fromStdString(ZXing::ToString(ZXing::Barcode::format())); }
  108. QString contentTypeName() const { return QString::fromStdString(ZXing::ToString(ZXing::Barcode::contentType())); }
  109. const QString& text() const { return _text; }
  110. const QByteArray& bytes() const { return _bytes; }
  111. const Position& position() const { return _position; }
  112. };
  113. inline QList<Barcode> ZXBarcodesToQBarcodes(ZXing::Barcodes&& zxres)
  114. {
  115. QList<Barcode> res;
  116. for (auto&& r : zxres)
  117. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  118. res.push_back(Barcode(std::move(r)));
  119. #else
  120. res.emplace_back(std::move(r));
  121. #endif
  122. return res;
  123. }
  124. inline QList<Barcode> ReadBarcodes(const QImage& img, const ReaderOptions& opts = {})
  125. {
  126. using namespace ZXing;
  127. auto ImgFmtFromQImg = [](const QImage& img) {
  128. switch (img.format()) {
  129. case QImage::Format_ARGB32:
  130. case QImage::Format_RGB32:
  131. #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
  132. return ImageFormat::BGRA;
  133. #else
  134. return ImageFormat::ARGB;
  135. #endif
  136. case QImage::Format_RGB888: return ImageFormat::RGB;
  137. case QImage::Format_RGBX8888:
  138. case QImage::Format_RGBA8888: return ImageFormat::RGBA;
  139. case QImage::Format_Grayscale8: return ImageFormat::Lum;
  140. default: return ImageFormat::None;
  141. }
  142. };
  143. auto exec = [&](const QImage& img) {
  144. return ZXBarcodesToQBarcodes(ZXing::ReadBarcodes(
  145. {img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast<int>(img.bytesPerLine())}, opts));
  146. };
  147. return ImgFmtFromQImg(img) == ImageFormat::None ? exec(img.convertToFormat(QImage::Format_Grayscale8)) : exec(img);
  148. }
  149. inline Barcode ReadBarcode(const QImage& img, const ReaderOptions& opts = {})
  150. {
  151. auto res = ReadBarcodes(img, ReaderOptions(opts).setMaxNumberOfSymbols(1));
  152. return !res.isEmpty() ? res.takeFirst() : Barcode();
  153. }
  154. #ifdef QT_MULTIMEDIA_LIB
  155. inline QList<Barcode> ReadBarcodes(const QVideoFrame& frame, const ReaderOptions& opts = {})
  156. {
  157. using namespace ZXing;
  158. ImageFormat fmt = ImageFormat::None;
  159. int pixStride = 0;
  160. int pixOffset = 0;
  161. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  162. #define FORMAT(F5, F6) QVideoFrame::Format_##F5
  163. #define FIRST_PLANE
  164. #else
  165. #define FORMAT(F5, F6) QVideoFrameFormat::Format_##F6
  166. #define FIRST_PLANE 0
  167. #endif
  168. switch (frame.pixelFormat()) {
  169. case FORMAT(ARGB32, ARGB8888):
  170. case FORMAT(ARGB32_Premultiplied, ARGB8888_Premultiplied):
  171. case FORMAT(RGB32, RGBX8888):
  172. #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
  173. fmt = ImageFormat::BGRA;
  174. #else
  175. fmt = ImageFormat::ARGB;
  176. #endif
  177. break;
  178. case FORMAT(BGRA32, BGRA8888):
  179. case FORMAT(BGRA32_Premultiplied, BGRA8888_Premultiplied):
  180. case FORMAT(BGR32, BGRX8888):
  181. #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
  182. fmt = ImageFormat::RGBA;
  183. #else
  184. fmt = ImageFormat::ABGR;
  185. #endif
  186. break;
  187. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  188. case QVideoFrame::Format_RGB24: fmt = ImageFormat::RGB; break;
  189. case QVideoFrame::Format_BGR24: fmt = ImageFormat::BGR; break;
  190. case QVideoFrame::Format_YUV444: fmt = ImageFormat::Lum, pixStride = 3; break;
  191. #else
  192. case QVideoFrameFormat::Format_P010:
  193. case QVideoFrameFormat::Format_P016: fmt = ImageFormat::Lum, pixStride = 1; break;
  194. #endif
  195. case FORMAT(AYUV444, AYUV):
  196. case FORMAT(AYUV444_Premultiplied, AYUV_Premultiplied):
  197. #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
  198. fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 3;
  199. #else
  200. fmt = ImageFormat::Lum, pixStride = 4, pixOffset = 2;
  201. #endif
  202. break;
  203. case FORMAT(YUV420P, YUV420P):
  204. case FORMAT(NV12, NV12):
  205. case FORMAT(NV21, NV21):
  206. case FORMAT(IMC1, IMC1):
  207. case FORMAT(IMC2, IMC2):
  208. case FORMAT(IMC3, IMC3):
  209. case FORMAT(IMC4, IMC4):
  210. case FORMAT(YV12, YV12): fmt = ImageFormat::Lum; break;
  211. case FORMAT(UYVY, UYVY): fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
  212. case FORMAT(YUYV, YUYV): fmt = ImageFormat::Lum, pixStride = 2; break;
  213. case FORMAT(Y8, Y8): fmt = ImageFormat::Lum; break;
  214. case FORMAT(Y16, Y16): fmt = ImageFormat::Lum, pixStride = 2, pixOffset = 1; break;
  215. #if (QT_VERSION >= QT_VERSION_CHECK(5, 13, 0))
  216. case FORMAT(ABGR32, ABGR8888):
  217. #if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
  218. fmt = ImageFormat::RGBA;
  219. #else
  220. fmt = ImageFormat::ABGR;
  221. #endif
  222. break;
  223. #endif
  224. #if (QT_VERSION >= QT_VERSION_CHECK(5, 14, 0))
  225. case FORMAT(YUV422P, YUV422P): fmt = ImageFormat::Lum; break;
  226. #endif
  227. default: break;
  228. }
  229. if (fmt != ImageFormat::None) {
  230. auto img = frame; // shallow copy just get access to non-const map() function
  231. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  232. if (!img.isValid() || !img.map(QAbstractVideoBuffer::ReadOnly)){
  233. #else
  234. if (!img.isValid() || !img.map(QVideoFrame::ReadOnly)){
  235. #endif
  236. qWarning() << "invalid QVideoFrame: could not map memory";
  237. return {};
  238. }
  239. QScopeGuard unmap([&] { img.unmap(); });
  240. return ZXBarcodesToQBarcodes(ZXing::ReadBarcodes(
  241. {img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, opts));
  242. }
  243. else {
  244. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  245. if (QVideoFrame::imageFormatFromPixelFormat(frame.pixelFormat()) != QImage::Format_Invalid) {
  246. qWarning() << "unsupported QVideoFrame::pixelFormat";
  247. return {};
  248. }
  249. auto qimg = frame.image();
  250. #else
  251. auto qimg = frame.toImage();
  252. #endif
  253. if (qimg.format() != QImage::Format_Invalid)
  254. return ReadBarcodes(qimg, opts);
  255. qWarning() << "failed to convert QVideoFrame to QImage";
  256. return {};
  257. }
  258. }
  259. inline Barcode ReadBarcode(const QVideoFrame& frame, const ReaderOptions& opts = {})
  260. {
  261. auto res = ReadBarcodes(frame, ReaderOptions(opts).setMaxNumberOfSymbols(1));
  262. return !res.isEmpty() ? res.takeFirst() : Barcode();
  263. }
  264. #define ZQ_PROPERTY(Type, name, setter) \
  265. public: \
  266. Q_PROPERTY(Type name READ name WRITE setter NOTIFY name##Changed) \
  267. Type name() const noexcept { return ReaderOptions::name(); } \
  268. Q_SLOT void setter(const Type& newVal) \
  269. { \
  270. if (name() != newVal) { \
  271. ReaderOptions::setter(newVal); \
  272. emit name##Changed(); \
  273. } \
  274. } \
  275. Q_SIGNAL void name##Changed();
  276. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  277. class BarcodeReader : public QAbstractVideoFilter, private ReaderOptions
  278. #else
  279. class BarcodeReader : public QObject, private ReaderOptions
  280. #endif
  281. {
  282. Q_OBJECT
  283. public:
  284. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  285. BarcodeReader(QObject* parent = nullptr) : QAbstractVideoFilter(parent) {}
  286. #else
  287. BarcodeReader(QObject* parent = nullptr) : QObject(parent) {}
  288. #endif
  289. // TODO: find out how to properly expose QFlags to QML
  290. // simply using ZQ_PROPERTY(BarcodeFormats, formats, setFormats)
  291. // results in the runtime error "can't assign int to formats"
  292. Q_PROPERTY(int formats READ formats WRITE setFormats NOTIFY formatsChanged)
  293. int formats() const noexcept
  294. {
  295. auto fmts = ReaderOptions::formats();
  296. return *reinterpret_cast<int*>(&fmts);
  297. }
  298. Q_SLOT void setFormats(int newVal)
  299. {
  300. if (formats() != newVal) {
  301. ReaderOptions::setFormats(static_cast<ZXing::BarcodeFormat>(newVal));
  302. emit formatsChanged();
  303. qDebug() << ReaderOptions::formats();
  304. }
  305. }
  306. Q_SIGNAL void formatsChanged();
  307. Q_PROPERTY(TextMode textMode READ textMode WRITE setTextMode NOTIFY textModeChanged)
  308. TextMode textMode() const noexcept { return static_cast<TextMode>(ReaderOptions::textMode()); }
  309. Q_SLOT void setTextMode(TextMode newVal)
  310. {
  311. if (textMode() != newVal) {
  312. ReaderOptions::setTextMode(static_cast<ZXing::TextMode>(newVal));
  313. emit textModeChanged();
  314. }
  315. }
  316. Q_SIGNAL void textModeChanged();
  317. ZQ_PROPERTY(bool, tryRotate, setTryRotate)
  318. ZQ_PROPERTY(bool, tryHarder, setTryHarder)
  319. ZQ_PROPERTY(bool, tryInvert, setTryInvert)
  320. ZQ_PROPERTY(bool, tryDownscale, setTryDownscale)
  321. ZQ_PROPERTY(bool, isPure, setIsPure)
  322. // For debugging/development
  323. int runTime = 0;
  324. Q_PROPERTY(int runTime MEMBER runTime)
  325. public slots:
  326. ZXingQt::Barcode process(const QVideoFrame& image)
  327. {
  328. QElapsedTimer t;
  329. t.start();
  330. auto res = ReadBarcode(image, *this);
  331. runTime = t.elapsed();
  332. if (res.isValid())
  333. emit foundBarcode(res);
  334. else
  335. emit failedRead();
  336. return res;
  337. }
  338. signals:
  339. void failedRead();
  340. void foundBarcode(ZXingQt::Barcode barcode);
  341. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  342. public:
  343. QVideoFilterRunnable *createFilterRunnable() override;
  344. #else
  345. private:
  346. QVideoSink *_sink = nullptr;
  347. public:
  348. void setVideoSink(QVideoSink* sink) {
  349. if (_sink == sink)
  350. return;
  351. if (_sink)
  352. disconnect(_sink, nullptr, this, nullptr);
  353. _sink = sink;
  354. connect(_sink, &QVideoSink::videoFrameChanged, this, &BarcodeReader::process);
  355. }
  356. Q_PROPERTY(QVideoSink* videoSink MEMBER _sink WRITE setVideoSink)
  357. #endif
  358. };
  359. #undef ZX_PROPERTY
  360. #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
  361. class VideoFilterRunnable : public QVideoFilterRunnable
  362. {
  363. BarcodeReader* _filter = nullptr;
  364. public:
  365. explicit VideoFilterRunnable(BarcodeReader* filter) : _filter(filter) {}
  366. QVideoFrame run(QVideoFrame* input, const QVideoSurfaceFormat& /*surfaceFormat*/, RunFlags /*flags*/) override
  367. {
  368. _filter->process(*input);
  369. return *input;
  370. }
  371. };
  372. inline QVideoFilterRunnable* BarcodeReader::createFilterRunnable()
  373. {
  374. return new VideoFilterRunnable(this);
  375. }
  376. #endif
  377. #endif // QT_MULTIMEDIA_LIB
  378. } // namespace ZXingQt
  379. Q_DECLARE_METATYPE(ZXingQt::Position)
  380. Q_DECLARE_METATYPE(ZXingQt::Barcode)
  381. #ifdef QT_QML_LIB
  382. #include <QQmlEngine>
  383. namespace ZXingQt {
  384. inline void registerQmlAndMetaTypes()
  385. {
  386. qRegisterMetaType<ZXingQt::BarcodeFormat>("BarcodeFormat");
  387. qRegisterMetaType<ZXingQt::ContentType>("ContentType");
  388. qRegisterMetaType<ZXingQt::TextMode>("TextMode");
  389. // supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name
  390. // but then the qml side complains about "unregistered type"
  391. qRegisterMetaType<ZXingQt::Position>("Position");
  392. qRegisterMetaType<ZXingQt::Barcode>("Barcode");
  393. qmlRegisterUncreatableMetaObject(
  394. ZXingQt::staticMetaObject, "ZXing", 1, 0, "ZXing", "Access to enums & flags only");
  395. qmlRegisterType<ZXingQt::BarcodeReader>("ZXing", 1, 0, "BarcodeReader");
  396. }
  397. } // namespace ZXingQt
  398. #endif // QT_QML_LIB