From 986211227372a419d9fe1681a965c7334133a8b3 Mon Sep 17 00:00:00 2001 From: m7913d Date: Mon, 5 May 2025 01:44:36 +0200 Subject: [PATCH 1/2] Improvement to Qt/QML interface ZXingQt6CamReader.qml: Starting from Qt 6.6, camera permission should be requested manually. Only start the camera after the permission is granted by the user, otherwise starting the camera will fail and starting will not be retried after the permission is granted. ZXingQtReader.h: always use BarcodeFormat/ContentType/TextMode enum from main ZXing namespace to allow calling `ZXing::ReaderOptions().setFormats(ZXing::BarcodeFormat::MatrixCodes)` even if compiled with QML support (especially useful in case QML / QWidgets are mixed). Moved redefinition of those enum for QML to separate namespace. BarcodeReader: expose flags as QFlags to qml registerQmlAndMetaTypes: use qmlRegisterType to register the metatype to be able to store the position / barcode in a QML property, i.e. `property barcode myBarcode: ...` registerQmlAndMetaTypes: expose ZXing as ZXingQt to QML, as the Qt interface of ZXing is also exposed using the ZXingQt namespace in C++. Moved helper functions to Detail namespace ZXingQtInitializer: automatically register qml types, similar to how Qt does it internally. --- core/src/Flags.h | 1 + example/ZXingQt6CamReader.qml | 20 ++++++-- example/ZXingQtCamReader.cpp | 2 - example/ZXingQtReader.h | 90 ++++++++++++++++++++++------------- 4 files changed, 74 insertions(+), 39 deletions(-) diff --git a/core/src/Flags.h b/core/src/Flags.h index c5f2e2a8ab..b159e9b838 100644 --- a/core/src/Flags.h +++ b/core/src/Flags.h @@ -67,6 +67,7 @@ class Flags int count() const noexcept { return BitHacks::CountBitsSet(i); } constexpr inline bool operator==(Flags other) const noexcept { return i == other.i; } + constexpr inline bool operator!=(Flags other) const noexcept { return i != other.i; } inline Flags& operator&=(Flags mask) noexcept { return i &= mask.i, *this; } inline Flags& operator&=(Enum mask) noexcept { return i &= Int(mask), *this; } diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index 7eb85445f4..2981b48b01 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -9,7 +9,8 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Shapes import QtMultimedia -import ZXing +import ZXingQt +import QtCore // required for CameraPermission (Qt 6.6 and above) Window { visible: true @@ -29,12 +30,12 @@ Window { id: barcodeReader videoSink: videoOutput.videoSink - formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) + formats: (linearSwitch.checked ? (ZXingQt.LinearCodes) : ZXingQt.None) | (matrixSwitch.checked ? (ZXingQt.MatrixCodes) : ZXingQt.None) tryRotate: tryRotateSwitch.checked tryHarder: tryHarderSwitch.checked tryInvert: tryInvertSwitch.checked tryDownscale: tryDownscaleSwitch.checked - textMode: ZXing.TextMode.HRI + textMode: ZXingQt.TextMode.HRI // callback with parameter 'barcode', called for every successfully processed frame onFoundBarcode: (barcode)=> { @@ -58,12 +59,23 @@ Window { id: devices } + // Starting from Qt 6.6, camera permission should be requested manually. For older Qt versions, CameraPermission object can be simply removed as Qt automatically requests the permission. + CameraPermission { + id: cameraPermission + Component.onCompleted: { + if (status !== Qt.PermissionStatus.Granted) + request(); + } + } + // End of Qt 6.6+ section + Camera { id: camera cameraDevice: devices.videoInputs[camerasComboBox.currentIndex] ? devices.videoInputs[camerasComboBox.currentIndex] : devices.defaultVideoInput focusMode: Camera.FocusModeAutoNear onErrorOccurred: console.log("camera error:" + errorString) - active: true + active: cameraPermission.status === Qt.PermissionStatus.Granted // for Qt 6.6 and above + // active: true // pre Qt 6.6 } CaptureSession { diff --git a/example/ZXingQtCamReader.cpp b/example/ZXingQtCamReader.cpp index 940e474e4a..faec694da5 100644 --- a/example/ZXingQtCamReader.cpp +++ b/example/ZXingQtCamReader.cpp @@ -14,8 +14,6 @@ int main(int argc, char *argv[]) QCoreApplication::setAttribute(Qt::AA_EnableHighDpiScaling); #endif - ZXingQt::registerQmlAndMetaTypes(); - QGuiApplication app(argc, argv); app.setApplicationName("ZXingQtCamReader"); QQmlApplicationEngine engine; diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index b32e37fe43..e2f9d1238d 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -27,11 +27,12 @@ namespace ZXingQt { -Q_NAMESPACE //TODO: find a better way to export these enums to QML than to duplicate their definition // #ifdef Q_MOC_RUN produces meta information in the moc output but it does end up working in qml #ifdef QT_QML_LIB +namespace QML { +Q_NAMESPACE enum class BarcodeFormat { None = 0, ///< Used as a return value if no valid barcode has been detected @@ -64,20 +65,24 @@ enum class ContentType { Text, Binary, Mixed, GS1, ISO15434, UnknownECI }; enum class TextMode { Plain, ECI, HRI, Hex, Escaped }; -#else +Q_DECLARE_FLAGS(BarcodeFormats, BarcodeFormat) +Q_DECLARE_OPERATORS_FOR_FLAGS(BarcodeFormats) +Q_FLAG_NS(BarcodeFormats) +Q_ENUM_NS(BarcodeFormat) + +Q_ENUM_NS(ContentType) +Q_ENUM_NS(TextMode) +} // namespace QML +#endif + using ZXing::BarcodeFormat; using ZXing::ContentType; using ZXing::TextMode; -#endif using ZXing::ReaderOptions; using ZXing::Binarizer; using ZXing::BarcodeFormats; -Q_ENUM_NS(BarcodeFormat) -Q_ENUM_NS(ContentType) -Q_ENUM_NS(TextMode) - template QDebug operator<<(QDebug dbg, const T& v) { @@ -129,8 +134,8 @@ class Barcode : private ZXing::Barcode using ZXing::Barcode::isValid; - BarcodeFormat format() const { return static_cast(ZXing::Barcode::format()); } - ContentType contentType() const { return static_cast(ZXing::Barcode::contentType()); } + BarcodeFormat format() const { return ZXing::Barcode::format(); } + ContentType contentType() const { return ZXing::Barcode::contentType(); } QString formatName() const { return QString::fromStdString(ZXing::ToString(ZXing::Barcode::format())); } QString contentTypeName() const { return QString::fromStdString(ZXing::ToString(ZXing::Barcode::contentType())); } const QString& text() const { return _text; } @@ -138,6 +143,7 @@ class Barcode : private ZXing::Barcode const Position& position() const { return _position; } }; +namespace Detail { inline QList ZXBarcodesToQBarcodes(ZXing::Barcodes&& zxres) { QList res; @@ -149,6 +155,7 @@ inline QList ZXBarcodesToQBarcodes(ZXing::Barcodes&& zxres) #endif return res; } +} // namespace Detail inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts = {}) { @@ -172,7 +179,7 @@ inline QList ReadBarcodes(const QImage& img, const ReaderOptions& opts }; auto exec = [&](const QImage& img) { - return ZXBarcodesToQBarcodes(ZXing::ReadBarcodes( + return Detail::ZXBarcodesToQBarcodes(ZXing::ReadBarcodes( {img.bits(), img.width(), img.height(), ImgFmtFromQImg(img), static_cast(img.bytesPerLine())}, opts)); }; @@ -282,7 +289,7 @@ inline QList ReadBarcodes(const QVideoFrame& frame, const ReaderOptions } QScopeGuard unmap([&] { img.unmap(); }); - return ZXBarcodesToQBarcodes(ZXing::ReadBarcodes( + return Detail::ZXBarcodesToQBarcodes(ZXing::ReadBarcodes( {img.bits(FIRST_PLANE) + pixOffset, img.width(), img.height(), fmt, img.bytesPerLine(FIRST_PLANE), pixStride}, opts)); } else { @@ -330,6 +337,9 @@ class BarcodeReader : public QObject, private ReaderOptions { Q_OBJECT + Q_PROPERTY(QML::BarcodeFormats formats READ formatsQML WRITE setFormatsQML NOTIFY formatsChanged) + Q_PROPERTY(QML::TextMode textMode READ textModeQML WRITE setTextModeQML NOTIFY textModeChanged) + public: #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) BarcodeReader(QObject* parent = nullptr) : QAbstractVideoFilter(parent) {} @@ -346,35 +356,23 @@ class BarcodeReader : public QObject, private ReaderOptions #endif - // TODO: find out how to properly expose QFlags to QML - // simply using ZQ_PROPERTY(BarcodeFormats, formats, setFormats) - // results in the runtime error "can't assign int to formats" - Q_PROPERTY(int formats READ formats WRITE setFormats NOTIFY formatsChanged) - int formats() const noexcept - { - auto fmts = ReaderOptions::formats(); - return *reinterpret_cast(&fmts); - } - Q_SLOT void setFormats(int newVal) + BarcodeFormats formats() const noexcept { return ReaderOptions::formats(); } + void setFormats(BarcodeFormats newVal) { if (formats() != newVal) { - ReaderOptions::setFormats(static_cast(newVal)); + ReaderOptions::setFormats(newVal); emit formatsChanged(); - qDebug() << ReaderOptions::formats(); } } - Q_SIGNAL void formatsChanged(); - Q_PROPERTY(TextMode textMode READ textMode WRITE setTextMode NOTIFY textModeChanged) - TextMode textMode() const noexcept { return static_cast(ReaderOptions::textMode()); } - Q_SLOT void setTextMode(TextMode newVal) + TextMode textMode() const noexcept { return ReaderOptions::textMode(); } + void setTextMode(TextMode newVal) { if (textMode() != newVal) { - ReaderOptions::setTextMode(static_cast(newVal)); + ReaderOptions::setTextMode(newVal); emit textModeChanged(); } } - Q_SIGNAL void textModeChanged(); ZQ_PROPERTY(bool, tryRotate, setTryRotate) ZQ_PROPERTY(bool, tryHarder, setTryHarder) @@ -408,6 +406,20 @@ public slots: void failedRead(); void foundBarcode(ZXingQt::Barcode barcode); + void formatsChanged(); + void textModeChanged(); + +private: + QML::BarcodeFormats formatsQML() const noexcept + { + auto fmts = formats(); + return QML::BarcodeFormats(*reinterpret_cast(&fmts)); + } + void setFormatsQML(QML::BarcodeFormats newVal) { setFormats(static_cast(newVal.operator int())); } + + QML::TextMode textModeQML() const noexcept { return static_cast(textMode()); } + void setTextModeQML(QML::TextMode newVal) { setTextMode(static_cast(newVal)); } + #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) public: QVideoFilterRunnable *createFilterRunnable() override; @@ -489,6 +501,8 @@ Q_DECLARE_METATYPE(ZXingQt::Barcode) namespace ZXingQt { +namespace Detail { + inline void registerQmlAndMetaTypes() { qRegisterMetaType("BarcodeFormat"); @@ -497,14 +511,24 @@ inline void registerQmlAndMetaTypes() // supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name // but then the qml side complains about "unregistered type" - qRegisterMetaType("Position"); - qRegisterMetaType("Barcode"); + qmlRegisterType("ZXingQt", 1, 0, "position"); + qmlRegisterType("ZXingQt", 1, 0, "barcode"); qmlRegisterUncreatableMetaObject( - ZXingQt::staticMetaObject, "ZXing", 1, 0, "ZXing", "Access to enums & flags only"); - qmlRegisterType("ZXing", 1, 0, "BarcodeReader"); + ZXingQt::QML::staticMetaObject, "ZXingQt", 1, 0, "ZXingQt", "Access to enums & flags only"); +#ifdef QT_MULTIMEDIA_LIB + qmlRegisterType("ZXingQt", 1, 0, "BarcodeReader"); +#endif // QT_MULTIMEDIA_LIB } +struct ZXingQtInitializer +{ + ZXingQtInitializer() {registerQmlAndMetaTypes();} +} inline zxingQtInitializer; + +} // namespace Detail + + } // namespace ZXingQt #endif // QT_QML_LIB From 39200a0e68f12409687d63c2be7e5e9878ba2cbe Mon Sep 17 00:00:00 2001 From: m7913d Date: Tue, 13 May 2025 23:37:40 +0200 Subject: [PATCH 2/2] Changes based on review. --- example/ZXingQt6CamReader.qml | 6 +++--- example/ZXingQtReader.h | 13 +++++++------ 2 files changed, 10 insertions(+), 9 deletions(-) diff --git a/example/ZXingQt6CamReader.qml b/example/ZXingQt6CamReader.qml index 2981b48b01..84e27d813c 100644 --- a/example/ZXingQt6CamReader.qml +++ b/example/ZXingQt6CamReader.qml @@ -9,7 +9,7 @@ import QtQuick.Controls import QtQuick.Layouts import QtQuick.Shapes import QtMultimedia -import ZXingQt +import ZXing import QtCore // required for CameraPermission (Qt 6.6 and above) Window { @@ -30,12 +30,12 @@ Window { id: barcodeReader videoSink: videoOutput.videoSink - formats: (linearSwitch.checked ? (ZXingQt.LinearCodes) : ZXingQt.None) | (matrixSwitch.checked ? (ZXingQt.MatrixCodes) : ZXingQt.None) + formats: (linearSwitch.checked ? (ZXing.LinearCodes) : ZXing.None) | (matrixSwitch.checked ? (ZXing.MatrixCodes) : ZXing.None) tryRotate: tryRotateSwitch.checked tryHarder: tryHarderSwitch.checked tryInvert: tryInvertSwitch.checked tryDownscale: tryDownscaleSwitch.checked - textMode: ZXingQt.TextMode.HRI + textMode: ZXing.TextMode.HRI // callback with parameter 'barcode', called for every successfully processed frame onFoundBarcode: (barcode)=> { diff --git a/example/ZXingQtReader.h b/example/ZXingQtReader.h index e2f9d1238d..8f0d1486ee 100644 --- a/example/ZXingQtReader.h +++ b/example/ZXingQtReader.h @@ -11,6 +11,7 @@ #include #include #include +#include #ifdef QT_MULTIMEDIA_LIB #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) @@ -357,7 +358,7 @@ class BarcodeReader : public QObject, private ReaderOptions #endif BarcodeFormats formats() const noexcept { return ReaderOptions::formats(); } - void setFormats(BarcodeFormats newVal) + Q_SLOT void setFormats(BarcodeFormats newVal) { if (formats() != newVal) { ReaderOptions::setFormats(newVal); @@ -366,7 +367,7 @@ class BarcodeReader : public QObject, private ReaderOptions } TextMode textMode() const noexcept { return ReaderOptions::textMode(); } - void setTextMode(TextMode newVal) + Q_SLOT void setTextMode(TextMode newVal) { if (textMode() != newVal) { ReaderOptions::setTextMode(newVal); @@ -511,13 +512,13 @@ inline void registerQmlAndMetaTypes() // supposedly the Q_DECLARE_METATYPE should be used with the overload without a custom name // but then the qml side complains about "unregistered type" - qmlRegisterType("ZXingQt", 1, 0, "position"); - qmlRegisterType("ZXingQt", 1, 0, "barcode"); + qmlRegisterType("ZXing", 1, 0, "position"); + qmlRegisterType("ZXing", 1, 0, "barcode"); qmlRegisterUncreatableMetaObject( - ZXingQt::QML::staticMetaObject, "ZXingQt", 1, 0, "ZXingQt", "Access to enums & flags only"); + ZXingQt::QML::staticMetaObject, "ZXing", 1, 0, "ZXing", "Access to enums & flags only"); #ifdef QT_MULTIMEDIA_LIB - qmlRegisterType("ZXingQt", 1, 0, "BarcodeReader"); + qmlRegisterType("ZXing", 1, 0, "BarcodeReader"); #endif // QT_MULTIMEDIA_LIB }