From efbc26f3eac16552a7e632a4379dccb6d54133b4 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Wed, 3 Nov 2021 11:26:14 +0200 Subject: [PATCH 01/14] Added implementation of QZXingFilter compatible with Qt 6.2 #199 --- src/QZXing-components.pri | 15 +++++-- src/QZXing.cpp | 6 ++- src/QZXingFilterVideoSink.cpp | 74 +++++++++++++++++++++++++++++++++++ src/QZXingFilterVideoSink.h | 64 ++++++++++++++++++++++++++++++ 4 files changed, 155 insertions(+), 4 deletions(-) create mode 100644 src/QZXingFilterVideoSink.cpp create mode 100644 src/QZXingFilterVideoSink.h diff --git a/src/QZXing-components.pri b/src/QZXing-components.pri index a7fec1af..b32b5121 100644 --- a/src/QZXing-components.pri +++ b/src/QZXing-components.pri @@ -409,11 +409,20 @@ qzxing_multimedia { DEFINES += QZXING_MULTIMEDIA PRL_EXPORT_DEFINES += QZXING_MULTIMEDIA + lessThan(QT_VERSION, 6.2) { + HEADERS += \ + $$PWD/QZXingFilter.h + + SOURCES += \ + $$PWD/QZXingFilter.cpp + } + greaterThan(QT_VERSION, 6.1) { + QT += concurrent HEADERS += \ - $$PWD/QZXingFilter.h - + $$PWD/QZXingFilterVideoSink.h SOURCES += \ - $$PWD/QZXingFilter.cpp + $$PWD/QZXingFilterVideoSink.cpp + } } qzxing_qml { diff --git a/src/QZXing.cpp b/src/QZXing.cpp index 092f884c..c404d665 100644 --- a/src/QZXing.cpp +++ b/src/QZXing.cpp @@ -27,7 +27,11 @@ #endif // ENABLE_ENCODER_QR_CODE #ifdef QZXING_MULTIMEDIA -#include "QZXingFilter.h" +#if QT_VERSION >= 0x060200 + #include "QZXingFilterVideoSink.h" +#else + #include "QZXingFilter.h" +#endif //QT_VERSION #endif //QZXING_MULTIMEDIA #ifdef QZXING_QML diff --git a/src/QZXingFilterVideoSink.cpp b/src/QZXingFilterVideoSink.cpp new file mode 100644 index 00000000..279f8066 --- /dev/null +++ b/src/QZXingFilterVideoSink.cpp @@ -0,0 +1,74 @@ +#include "QZXingFilterVideoSink.h" +#include +#include +#include "QZXingImageProvider.h" + +QZXingFilter::QZXingFilter(QObject *parent) + : QObject(parent) + , decoder(QZXing::DecoderFormat_QR_CODE) + , decoding(false) +{ + /// Connecting signals to handlers that will send signals to QML + connect(&decoder, &QZXing::decodingStarted, + this, &QZXingFilter::handleDecodingStarted); + connect(&decoder, &QZXing::decodingFinished, + this, &QZXingFilter::handleDecodingFinished); +} + +QZXingFilter::~QZXingFilter() +{ + if(!processThread.isFinished()) { + processThread.cancel(); + processThread.waitForFinished(); + } +} + +void QZXingFilter::handleDecodingStarted() +{ + decoding = true; + emit decodingStarted(); + emit isDecodingChanged(); +} + +void QZXingFilter::handleDecodingFinished(bool succeeded) +{ + decoding = false; + emit decodingFinished(succeeded, decoder.getProcessTimeOfLastDecoding()); + emit isDecodingChanged(); +} + +void QZXingFilter::setVideoSink(QObject *videoSink){ + m_videoSink = qobject_cast(videoSink); + + connect(m_videoSink, &QVideoSink::videoFrameChanged, this, &QZXingFilter::processFrame); +} + +void QZXingFilter::processFrame(const QVideoFrame &frame) { + if(isDecoding() || !processThread.isFinished()) return; + + decoding = true; + QImage image = frame.toImage(); + processThread = QtConcurrent::run([=](){ + if(image.isNull()) + { + qDebug() << "QZXingFilter error: Cant create image file to process."; + decoding = false; + return; + } + + QImage frameToProcess(image); + const QRect& rect = captureRect.toRect(); + + if (captureRect.isValid() && frameToProcess.size() != rect.size()) { + frameToProcess = image.copy(rect); + } + + static int i=0; + qDebug() << "image.size()" << frameToProcess.size(); + qDebug() << "image.format()" << frameToProcess.format(); + const QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/qrtest/test_" + QString::number(i++ % 100) + ".png"; + qDebug() << "saving image" << i << "at:" << path << frameToProcess.save(path); + + decoder.decodeImage(frameToProcess, frameToProcess.width(), frameToProcess.height()); + }); +} diff --git a/src/QZXingFilterVideoSink.h b/src/QZXingFilterVideoSink.h new file mode 100644 index 00000000..60b921b2 --- /dev/null +++ b/src/QZXingFilterVideoSink.h @@ -0,0 +1,64 @@ +/* + * Copyright 2017 QZXing authors + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef QZXingFilter_H +#define QZXingFilter_H + +#include +#include +#include +#include "QZXing.h" +#include +#include + +/// Video filter is the filter that has to be registered in C++, instantiated and attached in QML +class QZXingFilter : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool decoding READ isDecoding NOTIFY isDecodingChanged) + Q_PROPERTY(QZXing* decoder READ getDecoder) + Q_PROPERTY(QRectF captureRect MEMBER captureRect NOTIFY captureRectChanged) + Q_PROPERTY(QObject* videoSink WRITE setVideoSink) + + signals: + void isDecodingChanged(); + void decodingFinished(bool succeeded, int decodeTime); + void decodingStarted(); + void captureRectChanged(); + + private slots: + void handleDecodingStarted(); + void handleDecodingFinished(bool succeeded); + void processFrame(const QVideoFrame &frame); + + private: /// Attributes + QZXing decoder; + bool decoding; + QRectF captureRect; + + QVideoSink *m_videoSink; + QFuture processThread; + + public: /// Methods + explicit QZXingFilter(QObject *parent = 0); + void setVideoSink(QObject *videoSink); + virtual ~QZXingFilter(); + + bool isDecoding() {return decoding; } + QZXing* getDecoder() { return &decoder; } +}; + +#endif // QZXingFilter_H From ea066e1cb2fe2c6d07491a68824c0924e7d83198 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Wed, 3 Nov 2021 11:28:28 +0200 Subject: [PATCH 02/14] Updated QZXingLive example: use of QZXingFilter to comply with Qt 6.2 #199 --- examples/QZXingLive/application.cpp | 4 + examples/QZXingLive/main_qt6_2.qml | 163 ++++++++++++++++++++++++++++ examples/QZXingLive/qml.qrc | 1 + 3 files changed, 168 insertions(+) create mode 100644 examples/QZXingLive/main_qt6_2.qml diff --git a/examples/QZXingLive/application.cpp b/examples/QZXingLive/application.cpp index ee711948..3a5d9d9f 100644 --- a/examples/QZXingLive/application.cpp +++ b/examples/QZXingLive/application.cpp @@ -23,7 +23,11 @@ Application::Application() void Application::initializeQML() { +#if QT_VERSION < 0x060200 engine.load(QUrl(QStringLiteral("qrc:/main.qml"))); +#else + engine.load(QUrl(QStringLiteral("qrc:/main_qt6_2.qml"))); +#endif // QT_VERSION < 0x060200 } void Application::checkPermissions() diff --git a/examples/QZXingLive/main_qt6_2.qml b/examples/QZXingLive/main_qt6_2.qml new file mode 100644 index 00000000..20a8339d --- /dev/null +++ b/examples/QZXingLive/main_qt6_2.qml @@ -0,0 +1,163 @@ +import QtQuick 2.5 +import QtQuick.Window 2.0 +import QtQuick.Controls 2.0 +import QtQuick.Layouts 1.1 +import QtMultimedia + +import QZXing 3.2 + +ApplicationWindow +{ + id: window + visible: true + width: 640 + height: 480 + title: "Qt QZXing Filter Test" + + property int detectedTags: 0 + property string lastTag: "" + + Rectangle + { + id: bgRect + color: "white" + anchors.fill: videoOutput + } + + Text + { + id: text1 + wrapMode: Text.Wrap + font.pixelSize: 20 + anchors.top: parent.top + anchors.left: parent.left + z: 50 + text: "Tags detected: " + detectedTags + } + Text + { + id: fps + font.pixelSize: 20 + anchors.top: parent.top + anchors.right: parent.right + z: 50 + text: (1000 / zxingFilter.timePerFrameDecode).toFixed(0) + "fps" + } + + Camera + { + id:camera + active: true + focusMode: Camera.FocusModeAutoNear + } + + CaptureSession { + camera: camera + videoOutput: videoOutput + } + + VideoOutput + { + id: videoOutput + anchors.top: text1.bottom + anchors.bottom: text2.top + anchors.left: parent.left + anchors.right: parent.right + fillMode: VideoOutput.Stretch + + property double captureRectStartFactorX: 0.25 + property double captureRectStartFactorY: 0.25 + property double captureRectFactorWidth: 0.5 + property double captureRectFactorHeight: 0.5 + + MouseArea { + anchors.fill: parent + onClicked: { + camera.customFocusPoint = Qt.point(mouseX / width, mouseY / height); + camera.focusMode = Camera.FocusModeManual; + } + } + + Rectangle { + id: captureZone + color: "red" + opacity: 0.2 + width: parent.width * parent.captureRectFactorWidth + height: parent.height * parent.captureRectFactorHeight + x: parent.width * parent.captureRectStartFactorX + y: parent.height * parent.captureRectStartFactorY + } + } + + QZXingFilter + { + id: zxingFilter + videoSink: videoOutput.videoSink + + captureRect: { + videoOutput.sourceRect; + return Qt.rect(videoOutput.sourceRect.width * videoOutput.captureRectStartFactorX, + videoOutput.sourceRect.height * videoOutput.captureRectStartFactorY, + videoOutput.sourceRect.width * videoOutput.captureRectFactorWidth, + videoOutput.sourceRect.height * videoOutput.captureRectFactorHeight) + } + + decoder { + enabledDecoders: QZXing.DecoderFormat_EAN_13 | QZXing.DecoderFormat_CODE_39 | QZXing.DecoderFormat_QR_CODE + + onTagFound: { + console.log(tag + " | " + decoder.foundedFormat() + " | " + decoder.charSet()); + + window.detectedTags++; + window.lastTag = tag; + } + + tryHarder: false + } + + onDecodingStarted: + { +// console.log("started"); + } + + property int framesDecoded: 0 + property real timePerFrameDecode: 0 + + onDecodingFinished: + { + timePerFrameDecode = (decodeTime + framesDecoded * timePerFrameDecode) / (framesDecoded + 1); + framesDecoded++; + if(succeeded) + console.log("frame finished: " + succeeded, decodeTime, timePerFrameDecode, framesDecoded); + } + } + + Text + { + id: text2 + wrapMode: Text.Wrap + font.pixelSize: 20 + anchors.bottom: parent.bottom + anchors.left: parent.left + z: 50 + text: "Last tag: " + lastTag + } + Switch { + text: "Autofocus" + checked: camera.focusMode === Camera.FocusModeAutoNear + anchors { + right: parent.right + bottom: parent.bottom + } + onCheckedChanged: { + if (checked) { + camera.focusMode = Camera.FocusModeAutoNear + } else { + camera.focusMode = Camera.FocusModeManual + camera.customFocusPoint = Qt.point(0.5, 0.5) + } + } + font.family: Qt.platform.os === 'android' ? 'Droid Sans Mono' : 'Monospace' + font.pixelSize: Screen.pixelDensity * 5 + } +} diff --git a/examples/QZXingLive/qml.qrc b/examples/QZXingLive/qml.qrc index 5f6483ac..cf9b40eb 100644 --- a/examples/QZXingLive/qml.qrc +++ b/examples/QZXingLive/qml.qrc @@ -1,5 +1,6 @@ main.qml + main_qt6_2.qml From 12b6f8f100b84292a0843e194304d17e851ededf Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Wed, 3 Nov 2021 11:33:27 +0200 Subject: [PATCH 03/14] Applied fix suggested by @kiibimees when QZXingFilter is used on Android --- src/QZXingFilterVideoSink.cpp | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/QZXingFilterVideoSink.cpp b/src/QZXingFilterVideoSink.cpp index 279f8066..7e39c8c0 100644 --- a/src/QZXingFilterVideoSink.cpp +++ b/src/QZXingFilterVideoSink.cpp @@ -47,7 +47,16 @@ void QZXingFilter::processFrame(const QVideoFrame &frame) { if(isDecoding() || !processThread.isFinished()) return; decoding = true; - QImage image = frame.toImage(); + +#ifdef Q_OS_ANDROID + m_videoSink->setRhi(nullptr); // https://bugreports.qt.io/browse/QTBUG-97789 + QVideoFrame f(frame); + f.map(QVideoFrame::ReadOnly); +#else + const QVideoFrame &f = frame; +#endif // Q_OS_ANDROID + + QImage image = f.toImage(); processThread = QtConcurrent::run([=](){ if(image.isNull()) { From 87765b165fc2e739754c3460cf9e1ccdfd06e3b5 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Wed, 3 Nov 2021 11:38:02 +0200 Subject: [PATCH 04/14] Added frame unmap for Android when the image has already been extracted --- src/QZXingFilterVideoSink.cpp | 22 +++++++++++++--------- 1 file changed, 13 insertions(+), 9 deletions(-) diff --git a/src/QZXingFilterVideoSink.cpp b/src/QZXingFilterVideoSink.cpp index 7e39c8c0..86296592 100644 --- a/src/QZXingFilterVideoSink.cpp +++ b/src/QZXingFilterVideoSink.cpp @@ -49,14 +49,18 @@ void QZXingFilter::processFrame(const QVideoFrame &frame) { decoding = true; #ifdef Q_OS_ANDROID - m_videoSink->setRhi(nullptr); // https://bugreports.qt.io/browse/QTBUG-97789 - QVideoFrame f(frame); - f.map(QVideoFrame::ReadOnly); + m_videoSink->setRhi(nullptr); // https://bugreports.qt.io/browse/QTBUG-97789 + QVideoFrame f(frame); + f.map(QVideoFrame::ReadOnly); #else - const QVideoFrame &f = frame; + const QVideoFrame &f = frame; #endif // Q_OS_ANDROID QImage image = f.toImage(); + +#ifdef Q_OS_ANDROID + f.unmap(); +#endif processThread = QtConcurrent::run([=](){ if(image.isNull()) { @@ -72,11 +76,11 @@ void QZXingFilter::processFrame(const QVideoFrame &frame) { frameToProcess = image.copy(rect); } - static int i=0; - qDebug() << "image.size()" << frameToProcess.size(); - qDebug() << "image.format()" << frameToProcess.format(); - const QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/qrtest/test_" + QString::number(i++ % 100) + ".png"; - qDebug() << "saving image" << i << "at:" << path << frameToProcess.save(path); +// static int i=0; +// qDebug() << "image.size()" << frameToProcess.size(); +// qDebug() << "image.format()" << frameToProcess.format(); +// const QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/qrtest/test_" + QString::number(i++ % 100) + ".png"; +// qDebug() << "saving image" << i << "at:" << path << frameToProcess.save(path); decoder.decodeImage(frameToProcess, frameToProcess.width(), frameToProcess.height()); }); From 9628ef5cbecff63e70f2cb3ed48999b8da96ae23 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Wed, 3 Nov 2021 11:50:48 +0200 Subject: [PATCH 05/14] Updated Readme with Qt 6.2 changes for QZXingFilter --- README.md | 91 +++++++++++++++++++++++++++---------------------------- 1 file changed, 44 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index b2c1135b..f4235e8c 100644 --- a/README.md +++ b/README.md @@ -4,41 +4,41 @@ Qt/QML wrapper library for the [ZXing](https://github.com/zxing/zxing) barcode i Supports barcode decoding for the following types: -- UPC-A -- UPC-E -- EAN-8 -- EAN-13 -- ITF -- Code 39 -- Code 93 -- Code 128 (GS1) -- Codabar -- QR Code -- Data Matrix -- Aztec (beta) -- PDF 417 +- UPC-A +- UPC-E +- EAN-8 +- EAN-13 +- ITF +- Code 39 +- Code 93 +- Code 128 (GS1) +- Codabar +- QR Code +- Data Matrix +- Aztec (beta) +- PDF 417 Supports barcode encoding for the following types: -- QR Code +- QR Code # Table of contents 1. [How to include](#howToInclude) - 1. [Embed the source code](#embedInSourceCode) - 1. [Compile the project as an external library](#externalLibrary) - 1. [Control dependencies](#controlDependencies) - 1. [QZXing (core)](#controlDependenciesCore) - 1. [QZXing (core + QML)](#controlDependenciesCoreQML) - 1. [QZXing + QZXingFilter](#controlDependenciesCoreQMLQZXingFilter) + 1. [Embed the source code](#embedInSourceCode) + 1. [Compile the project as an external library](#externalLibrary) + 1. [Control dependencies](#controlDependencies) + 1. [QZXing (core)](#controlDependenciesCore) + 1. [QZXing (core + QML)](#controlDependenciesCoreQML) + 1. [QZXing + QZXingFilter](#controlDependenciesCoreQMLQZXingFilter) 1. [How to use](#howTo) - 1. [Decoding operation](#howToDecoding) - 1. [C++/Qt](#howToDecodingCPP) - 1. [Qt Quick](#howToDecodingQtQuick) - 1. [Encoding operation](#howToEncoding) - 1. [C++/Qt](#howToEncodingCPP) - 1. [Qt Quick](#howToEncodingQtQuick) - 1. [Encoded text format Information](#howToEncodingFormatExamples) + 1. [Decoding operation](#howToDecoding) + 1. [C++/Qt](#howToDecodingCPP) + 1. [Qt Quick](#howToDecodingQtQuick) + 1. [Encoding operation](#howToEncoding) + 1. [C++/Qt](#howToEncodingCPP) + 1. [Qt Quick](#howToEncodingQtQuick) + 1. [Encoded text format Information](#howToEncodingFormatExamples) 1. [Unit test dependency](#unitTestDependency) 1. [Qt 6 limitations](#qt6limitations) 1. [Contact](#contact) @@ -98,18 +98,22 @@ CONFIG += qzxing_qml ### QZXing + QZXingFilter -QZXing includes QZXingFilter, a QAbstractVideoFilter implementation to provide a mean of providing live feed to the decoding library. It automatically includes QML implementation as well. -This option requires "multimedia" Qt module this is why it is considered as a separate configuration. It can be used by adding the folloing line to the .pro file of a project: +QZXing includes QZXingFilter, an implementation to provide live feed to the decoding library. It automatically includes QML implementation as well. +This option requires "multimedia" Qt module this is why it is considered as a separate configuration. It can be used by adding the following line to the .pro file of a project: ```qmake CONFIG += qzxing_multimedia ``` +For examples on how to use QZXingFilter, it is advised to see [QZXingLive](https://github.com/ftylitak/qzxing/tree/master/examples/QZXingLive) example project. For Qt 5.x versions check [main.qml](https://github.com/ftylitak/qzxing/tree/master/examples/QZXingLive/main.qml) file, whereas for Qt 6.2 (or newer) check [main_qt6_2.qml](https://github.com/ftylitak/qzxing/tree/master/examples/QZXingLive/main_qt6_2.qml). + +(Pending task: a wiki page should be written to better explain the usage of the QZXingFilter component) + # How to use -Follows simple code snippets that brefly show the use of the library. For more details advise the examples included in the repository and the [wiki](https://github.com/ftylitak/qzxing/wiki). +Follows simple code snippets that briefly show the use of the library. For more details advise the examples included in the repository and the [wiki](https://github.com/ftylitak/qzxing/wiki). @@ -210,9 +214,9 @@ The encoding function has been written as static as it does not have any depende Use the encoding function with its default settings: -- Format: QR Code -- Size: 240x240 -- Error Correction Level: Low (L) +- Format: QR Code +- Size: 240x240 +- Error Correction Level: Low (L) ```cpp #include "QZXing.h" @@ -265,13 +269,13 @@ Image{ Or use the encoding function with the optional custom settings that are passed like URL query parameters: -| attribute name | value | description | -| --------------- | ----------- | --------------------------------------------------------- | -| border | true, false | image has border (white 1px) | -| correctionLevel | L, M, Q, H | the error correction level | -| format | qrcode | the encode formatter. Currently only QR Code. | -| transparent | true, false | whether the black pixels are transparent | -| explicitSize | int | if provided, it will be the size of the Qr rectangle | +| attribute name | value | description | +| --------------- | ----------- | ---------------------------------------------------- | +| border | true, false | image has border (white 1px) | +| correctionLevel | L, M, Q, H | the error correction level | +| format | qrcode | the encode formatter. Currently only QR Code. | +| transparent | true, false | whether the black pixels are transparent | +| explicitSize | int | if provided, it will be the size of the Qr rectangle | the size of the image can be adjusted by using the Image.sourceWidth and Image.sourceHeight properties of Image QML element. @@ -324,13 +328,6 @@ After testing, it seems that QTextCodec, if used through core5compat in Qt 6, it To avoid the dependency of an extra module (that also does not work as supposed to), QTextCodec has been replaced by [QStringDecoder](https://doc.qt.io/qt-6/qstringdecoder.html) only when building for Qt 6. If QZXing if build for Qt 5, QTextCodec is used as it was. -## Multimedia (Video / Camera) - -Qt Multimedia modules that includes the Camera item for QML and Video related operations for frame manipulation and live decoding are not supported in Qt 6 for the moment. -To my knowledge, there is no specific replacement for this absent modules and I hope they get re-supported for Qt 6. - -Thus, if building for Qt 5, everything works fine. If trying to used **qzxing_multimedia** configuration in your **pro** file, the project will fail (example: [QZXingLive](https://github.com/ftylitak/qzxing/tree/master/examples/QZXingLive) project). - # Contact From 638e68de6c39fd9be8ab682d0b5b9e83e73b7de5 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Wed, 3 Nov 2021 11:53:46 +0200 Subject: [PATCH 06/14] Updated library version from 3.2 to 3.3 --- .../qml/QZXingDragNDropTest/main_QtQuick1.qml | 2 +- .../qml/QZXingDragNDropTest/main_QtQuick2.qml | 2 +- examples/QZXingLive/main.qml | 2 +- examples/QZXingLive/main_qt6_2.qml | 2 +- src/CMakeLists.txt | 2 +- src/QZXing.pro | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/examples/QZXingDragNDropTest/qml/QZXingDragNDropTest/main_QtQuick1.qml b/examples/QZXingDragNDropTest/qml/QZXingDragNDropTest/main_QtQuick1.qml index e26958c6..d77cd43e 100644 --- a/examples/QZXingDragNDropTest/qml/QZXingDragNDropTest/main_QtQuick1.qml +++ b/examples/QZXingDragNDropTest/qml/QZXingDragNDropTest/main_QtQuick1.qml @@ -1,7 +1,7 @@ // import QtQuick 1.0 // to target S60 5th Edition or Maemo 5 import QtQuick 1.1 import DropArea 1.0 -import QZXing 3.2 +import QZXing 3.3 Rectangle { width: 360 diff --git a/examples/QZXingDragNDropTest/qml/QZXingDragNDropTest/main_QtQuick2.qml b/examples/QZXingDragNDropTest/qml/QZXingDragNDropTest/main_QtQuick2.qml index e0bfd67d..575c67fa 100644 --- a/examples/QZXingDragNDropTest/qml/QZXingDragNDropTest/main_QtQuick2.qml +++ b/examples/QZXingDragNDropTest/qml/QZXingDragNDropTest/main_QtQuick2.qml @@ -1,5 +1,5 @@ import QtQuick 2.0 -import QZXing 3.2 +import QZXing 3.3 Rectangle { width: 360 diff --git a/examples/QZXingLive/main.qml b/examples/QZXingLive/main.qml index 142fcbe1..99e00f33 100644 --- a/examples/QZXingLive/main.qml +++ b/examples/QZXingLive/main.qml @@ -4,7 +4,7 @@ import QtQuick.Controls 2.0 import QtQuick.Layouts 1.1 import QtMultimedia 5.5 -import QZXing 3.2 +import QZXing 3.3 ApplicationWindow { diff --git a/examples/QZXingLive/main_qt6_2.qml b/examples/QZXingLive/main_qt6_2.qml index 20a8339d..39d0bf6b 100644 --- a/examples/QZXingLive/main_qt6_2.qml +++ b/examples/QZXingLive/main_qt6_2.qml @@ -4,7 +4,7 @@ import QtQuick.Controls 2.0 import QtQuick.Layouts 1.1 import QtMultimedia -import QZXing 3.2 +import QZXing 3.3 ApplicationWindow { diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index c77f38d3..427ae197 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,4 +1,4 @@ -cmake_minimum_required(VERSION 3.2) +cmake_minimum_required(VERSION 3.3) project(QZXing) find_package(Qt5 COMPONENTS Core REQUIRED) diff --git a/src/QZXing.pro b/src/QZXing.pro index b1301646..4029e2b7 100644 --- a/src/QZXing.pro +++ b/src/QZXing.pro @@ -25,7 +25,7 @@ CONFIG += \ #qzxing_qml \ #qzxing_multimedia \ -VERSION = 3.2 +VERSION = 3.3 TARGET = QZXing TEMPLATE = lib From ea3983ffe852a64c243bd12700479137e1fc9c53 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Thu, 2 Dec 2021 19:17:02 +0200 Subject: [PATCH 07/14] fix application crash for Android #199 --- src/QZXingFilterVideoSink.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/QZXingFilterVideoSink.cpp b/src/QZXingFilterVideoSink.cpp index 86296592..e0536c6f 100644 --- a/src/QZXingFilterVideoSink.cpp +++ b/src/QZXingFilterVideoSink.cpp @@ -61,7 +61,10 @@ void QZXingFilter::processFrame(const QVideoFrame &frame) { #ifdef Q_OS_ANDROID f.unmap(); #endif + +#ifndef Q_OS_ANDROID processThread = QtConcurrent::run([=](){ +#endif if(image.isNull()) { qDebug() << "QZXingFilter error: Cant create image file to process."; @@ -83,5 +86,8 @@ void QZXingFilter::processFrame(const QVideoFrame &frame) { // qDebug() << "saving image" << i << "at:" << path << frameToProcess.save(path); decoder.decodeImage(frameToProcess, frameToProcess.width(), frameToProcess.height()); +#ifndef Q_OS_ANDROID }); +#endif + } From 1e4369fce7bb5298ee8d6fa543b9e25095ef20f8 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Thu, 2 Dec 2021 19:17:42 +0200 Subject: [PATCH 08/14] fix processing time per frame calculation and QML component version --- src/QZXing.cpp | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/QZXing.cpp b/src/QZXing.cpp index c404d665..69da3b25 100644 --- a/src/QZXing.cpp +++ b/src/QZXing.cpp @@ -102,10 +102,10 @@ QZXing::QZXing(QZXing::DecoderFormat decodeHints, QObject *parent) : QObject(par #if QT_VERSION >= 0x040700 void QZXing::registerQMLTypes() { - qmlRegisterType("QZXing", 3, 2, "QZXing"); + qmlRegisterType("QZXing", 3, 3, "QZXing"); #ifdef QZXING_MULTIMEDIA - qmlRegisterType("QZXing", 3, 2, "QZXingFilter"); + qmlRegisterType("QZXing", 3, 3, "QZXingFilter"); #endif //QZXING_MULTIMEDIA } @@ -423,8 +423,8 @@ QString QZXing::decodeImage(const QImage &image, int maxWidth, int maxHeight, bo if(image.isNull()) { - emit decodingFinished(false); processingTime = t.elapsed(); + emit decodingFinished(false); //qDebug() << "End decoding 1"; return ""; } @@ -555,15 +555,14 @@ QString QZXing::decodeImage(const QImage &image, int maxWidth, int maxHeight, bo emit tagFoundAdvanced(string, decodedFormat, charSet_, rect); }catch(zxing::Exception &/*e*/){} } + processingTime = t.elapsed(); emit decodingFinished(true); - //qDebug() << "End decoding 2"; return string; } + processingTime = t.elapsed(); emit error(errorMessage); emit decodingFinished(false); - processingTime = t.elapsed(); - //qDebug() << "End decoding 3"; return ""; } From c48a7a9006d4ab11f8af27484d38eb330c97d230 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Thu, 2 Dec 2021 19:19:25 +0200 Subject: [PATCH 09/14] fix camera activation for Android with @deletexl suggestion #199 --- examples/QZXingLive/main_qt6_2.qml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/QZXingLive/main_qt6_2.qml b/examples/QZXingLive/main_qt6_2.qml index 39d0bf6b..b8877f9e 100644 --- a/examples/QZXingLive/main_qt6_2.qml +++ b/examples/QZXingLive/main_qt6_2.qml @@ -63,7 +63,7 @@ ApplicationWindow anchors.bottom: text2.top anchors.left: parent.left anchors.right: parent.right - fillMode: VideoOutput.Stretch + // fillMode: VideoOutput.Stretch property double captureRectStartFactorX: 0.25 property double captureRectStartFactorY: 0.25 @@ -87,6 +87,8 @@ ApplicationWindow x: parent.width * parent.captureRectStartFactorX y: parent.height * parent.captureRectStartFactorY } + + Component.onCompleted: { camera.active = false; camera.active = true; } } QZXingFilter From 1f819ed0f0f9b31219c665df4d6de8099e72b814 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Tue, 7 Dec 2021 11:34:34 +0200 Subject: [PATCH 10/14] fix of VideoSink use for Android: map/unmap every frame. @kiibimees #199 --- src/QZXingFilterVideoSink.cpp | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/QZXingFilterVideoSink.cpp b/src/QZXingFilterVideoSink.cpp index e0536c6f..49f1f6ed 100644 --- a/src/QZXingFilterVideoSink.cpp +++ b/src/QZXingFilterVideoSink.cpp @@ -44,10 +44,6 @@ void QZXingFilter::setVideoSink(QObject *videoSink){ } void QZXingFilter::processFrame(const QVideoFrame &frame) { - if(isDecoding() || !processThread.isFinished()) return; - - decoding = true; - #ifdef Q_OS_ANDROID m_videoSink->setRhi(nullptr); // https://bugreports.qt.io/browse/QTBUG-97789 QVideoFrame f(frame); @@ -56,15 +52,17 @@ void QZXingFilter::processFrame(const QVideoFrame &frame) { const QVideoFrame &f = frame; #endif // Q_OS_ANDROID + if(isDecoding() || !processThread.isFinished()) return; + + decoding = true; + QImage image = f.toImage(); #ifdef Q_OS_ANDROID f.unmap(); #endif -#ifndef Q_OS_ANDROID processThread = QtConcurrent::run([=](){ -#endif if(image.isNull()) { qDebug() << "QZXingFilter error: Cant create image file to process."; @@ -86,8 +84,5 @@ void QZXingFilter::processFrame(const QVideoFrame &frame) { // qDebug() << "saving image" << i << "at:" << path << frameToProcess.save(path); decoder.decodeImage(frameToProcess, frameToProcess.width(), frameToProcess.height()); -#ifndef Q_OS_ANDROID }); -#endif - } From 7d3b430b9ee53343f43181ed82f7198dc1884a42 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Tue, 7 Dec 2021 23:34:44 +0200 Subject: [PATCH 11/14] Fix unmap in case of early return of frame processing Added support to image rotate based on the orientation of the frame #199 --- src/QZXingFilterVideoSink.cpp | 84 +++++++++++++++++++++++------------ src/QZXingFilterVideoSink.h | 5 +++ 2 files changed, 60 insertions(+), 29 deletions(-) diff --git a/src/QZXingFilterVideoSink.cpp b/src/QZXingFilterVideoSink.cpp index 49f1f6ed..1de7aaa4 100644 --- a/src/QZXingFilterVideoSink.cpp +++ b/src/QZXingFilterVideoSink.cpp @@ -37,6 +37,21 @@ void QZXingFilter::handleDecodingFinished(bool succeeded) emit isDecodingChanged(); } +void QZXingFilter::setOrientation(int orientation) +{ + if (orientation_ == orientation) { + return; + } + + orientation_ = orientation; + emit orientationChanged(orientation_); +} + +int QZXingFilter::orientation() const +{ + return orientation_; +} + void QZXingFilter::setVideoSink(QObject *videoSink){ m_videoSink = qobject_cast(videoSink); @@ -52,37 +67,48 @@ void QZXingFilter::processFrame(const QVideoFrame &frame) { const QVideoFrame &f = frame; #endif // Q_OS_ANDROID - if(isDecoding() || !processThread.isFinished()) return; - - decoding = true; - - QImage image = f.toImage(); + if(!isDecoding() && processThread.isFinished()){ + decoding = true; + + QImage image = f.toImage(); + processThread = QtConcurrent::run([=](){ + if(image.isNull()) + { + qDebug() << "QZXingFilter error: Cant create image file to process."; + decoding = false; + return; + } + + QImage frameToProcess(image); + const QRect& rect = captureRect.toRect(); + + if (captureRect.isValid() && frameToProcess.size() != rect.size()) { + frameToProcess = image.copy(rect); + } + + if (!orientation_) { + decoder.decodeImage(frameToProcess); + } else { + QTransform transformation; + transformation.translate(frameToProcess.rect().center().x(), frameToProcess.rect().center().y()); + transformation.rotate(-orientation_); + + QImage translatedImage = frameToProcess.transformed(transformation); + + decoder.decodeImage(translatedImage); + } + + // static int i=0; + // qDebug() << "image.size()" << frameToProcess.size(); + // qDebug() << "image.format()" << frameToProcess.format(); + // const QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/qrtest/test_" + QString::number(i++ % 100) + ".png"; + // qDebug() << "saving image" << i << "at:" << path << frameToProcess.save(path); + + decoder.decodeImage(frameToProcess, frameToProcess.width(), frameToProcess.height()); + }); + } #ifdef Q_OS_ANDROID f.unmap(); #endif - - processThread = QtConcurrent::run([=](){ - if(image.isNull()) - { - qDebug() << "QZXingFilter error: Cant create image file to process."; - decoding = false; - return; - } - - QImage frameToProcess(image); - const QRect& rect = captureRect.toRect(); - - if (captureRect.isValid() && frameToProcess.size() != rect.size()) { - frameToProcess = image.copy(rect); - } - -// static int i=0; -// qDebug() << "image.size()" << frameToProcess.size(); -// qDebug() << "image.format()" << frameToProcess.format(); -// const QString path = QStandardPaths::writableLocation(QStandardPaths::PicturesLocation) + "/qrtest/test_" + QString::number(i++ % 100) + ".png"; -// qDebug() << "saving image" << i << "at:" << path << frameToProcess.save(path); - - decoder.decodeImage(frameToProcess, frameToProcess.width(), frameToProcess.height()); - }); } diff --git a/src/QZXingFilterVideoSink.h b/src/QZXingFilterVideoSink.h index 60b921b2..76d4d556 100644 --- a/src/QZXingFilterVideoSink.h +++ b/src/QZXingFilterVideoSink.h @@ -32,22 +32,27 @@ class QZXingFilter : public QObject Q_PROPERTY(QZXing* decoder READ getDecoder) Q_PROPERTY(QRectF captureRect MEMBER captureRect NOTIFY captureRectChanged) Q_PROPERTY(QObject* videoSink WRITE setVideoSink) + Q_PROPERTY(int orientation READ orientation WRITE setOrientation NOTIFY orientationChanged) signals: void isDecodingChanged(); void decodingFinished(bool succeeded, int decodeTime); void decodingStarted(); void captureRectChanged(); + void orientationChanged(int orientation); private slots: void handleDecodingStarted(); void handleDecodingFinished(bool succeeded); void processFrame(const QVideoFrame &frame); + void setOrientation(int orientation); + int orientation() const; private: /// Attributes QZXing decoder; bool decoding; QRectF captureRect; + int orientation_; QVideoSink *m_videoSink; QFuture processThread; From 30fa997992680d1fc62ed98583b01a9ae81855b6 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Sun, 26 Dec 2021 10:45:13 +0200 Subject: [PATCH 12/14] exclude custom android code if Qt 6 and greater --- examples/QZXingLive/QZXingLive.pro | 53 ++++++++++++++++------------- examples/QZXingLive/application.cpp | 18 +++++++++- 2 files changed, 46 insertions(+), 25 deletions(-) diff --git a/examples/QZXingLive/QZXingLive.pro b/examples/QZXingLive/QZXingLive.pro index 3e1f7903..77534b27 100644 --- a/examples/QZXingLive/QZXingLive.pro +++ b/examples/QZXingLive/QZXingLive.pro @@ -15,12 +15,10 @@ CONFIG(debug, debug|release) { } HEADERS += \ - application.h \ - native.h + application.h SOURCES += main.cpp \ - application.cpp \ - native.cpp + application.cpp RESOURCES += qml.qrc @@ -33,28 +31,35 @@ include(../../src/QZXing-components.pri) include(deployment.pri) android { - QT += androidextras - - DISTFILES += \ - android/AndroidManifest.xml \ - android/gradle/wrapper/gradle-wrapper.jar \ - android/gradlew \ - android/res/values/libs.xml \ - android/build.gradle \ - android/gradle/wrapper/gradle-wrapper.properties \ - android/gradlew.bat \ - android/AndroidManifest.xml \ - android/gradle/wrapper/gradle-wrapper.jar \ - android/gradlew \ - android/res/values/libs.xml \ - android/build.gradle \ - android/gradle/wrapper/gradle-wrapper.properties \ - android/gradlew.bat - - ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + lessThan(QT_VERSION, 6.2) { + HEADERS += \ + native.h + + SOURCES += \ + native.cpp + + QT += androidextras + + DISTFILES += \ + android/AndroidManifest.xml \ + android/gradle/wrapper/gradle-wrapper.jar \ + android/gradlew \ + android/res/values/libs.xml \ + android/build.gradle \ + android/gradle/wrapper/gradle-wrapper.properties \ + android/gradlew.bat \ + android/AndroidManifest.xml \ + android/gradle/wrapper/gradle-wrapper.jar \ + android/gradlew \ + android/res/values/libs.xml \ + android/build.gradle \ + android/gradle/wrapper/gradle-wrapper.properties \ + android/gradlew.bat + + ANDROID_PACKAGE_SOURCE_DIR = $$PWD/android + } } else:ios { QMAKE_INFO_PLIST=Info.plist } - diff --git a/examples/QZXingLive/application.cpp b/examples/QZXingLive/application.cpp index 3a5d9d9f..899b5200 100644 --- a/examples/QZXingLive/application.cpp +++ b/examples/QZXingLive/application.cpp @@ -1,10 +1,20 @@ #include "application.h" #include + +#if QT_VERSION < 0x060000 #include "native.h" +#endif + #if defined(Q_OS_ANDROID) + +#if QT_VERSION < 0x060100 #include #include +#else + #include +#endif + #endif // Q_OS_ANDROID Application::Application() @@ -18,7 +28,9 @@ Application::Application() connect(this, &Application::onPermissionsDenied, this, &Application::initializeQML); - NativeHelpers::registerApplicationInstance(this); +#if QT_VERSION < 0x060000 + NativeHelpers::registerApplicationInstance(this); +#endif } void Application::initializeQML() @@ -36,10 +48,14 @@ void Application::checkPermissions() //intentionally called in the C++ thread since it is blocking and will continue after the check qDebug() << "About to request permissions"; + #if QT_VERSION < 0x060000 QAndroidJniObject::callStaticMethod("org/ftylitak/qzxing/Utilities", "requestQZXingPermissions", "(Landroid/app/Activity;)V", QtAndroid::androidActivity().object()); + #else + emit onPermissionsGranted(); + #endif qDebug() << "Permissions granted"; #else emit onPermissionsGranted(); From 9f8a13569cbd2f84af0eb3cf22753efe328654a8 Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Sun, 26 Dec 2021 10:45:46 +0200 Subject: [PATCH 13/14] enable the orientation binding --- examples/QZXingLive/main_qt6_2.qml | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/QZXingLive/main_qt6_2.qml b/examples/QZXingLive/main_qt6_2.qml index b8877f9e..74c06645 100644 --- a/examples/QZXingLive/main_qt6_2.qml +++ b/examples/QZXingLive/main_qt6_2.qml @@ -95,6 +95,7 @@ ApplicationWindow { id: zxingFilter videoSink: videoOutput.videoSink + orientation: videoOutput.orientation captureRect: { videoOutput.sourceRect; From ebb3e5ea0f89cc34de7fa54b7a3aef77ff3b955b Mon Sep 17 00:00:00 2001 From: Nikolaos Ftylitakis Date: Sun, 26 Dec 2021 10:47:14 +0200 Subject: [PATCH 14/14] increase version number to v3.3 --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index f4235e8c..99e09ace 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ int main() The in the QML file ```qml -import QZXing 3.2 +import QZXing 3.3 function decode(preview) { imageToDecode.source = preview @@ -254,7 +254,7 @@ QZXing::registerQMLImageProvider(engine); Default settings: ```qml -import QZXing 3.2 +import QZXing 3.3 TextField { id: inputField @@ -280,7 +280,7 @@ Or use the encoding function with the optional custom settings that are passed l the size of the image can be adjusted by using the Image.sourceWidth and Image.sourceHeight properties of Image QML element. ```qml -import QZXing 3.2 +import QZXing 3.3 TextField { id: inputField