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