Merge pull request #212 from ftylitak/qt6_2_multimedia_support

Qt 6.2 multimedia support
This commit is contained in:
Nikos Ftylitakis 2021-12-26 11:09:46 +02:00 committed by GitHub
commit 6ea2b31e26
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 472 additions and 88 deletions

View File

@ -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)
<a name="howTo"></a>
# 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).
<a name="howToDecoding"></a>
@ -159,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
@ -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"
@ -250,7 +254,7 @@ QZXing::registerQMLImageProvider(engine);
Default settings:
```qml
import QZXing 3.2
import QZXing 3.3
TextField {
id: inputField
@ -265,18 +269,18 @@ 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.
```qml
import QZXing 3.2
import QZXing 3.3
TextField {
id: inputField
@ -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).
<a name="contact"></a>
# Contact

View File

@ -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

View File

@ -1,5 +1,5 @@
import QtQuick 2.0
import QZXing 3.2
import QZXing 3.3
Rectangle {
width: 360

View File

@ -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
lessThan(QT_VERSION, 6.2) {
HEADERS += \
native.h
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
SOURCES += \
native.cpp
ANDROID_PACKAGE_SOURCE_DIR = $$PWD/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
}
}
else:ios {
QMAKE_INFO_PLIST=Info.plist
}

View File

@ -1,10 +1,20 @@
#include "application.h"
#include <QDebug>
#if QT_VERSION < 0x060000
#include "native.h"
#endif
#if defined(Q_OS_ANDROID)
#if QT_VERSION < 0x060100
#include <QAndroidJniObject>
#include <QtAndroid>
#else
#include <QJniObject>
#endif
#endif // Q_OS_ANDROID
Application::Application()
@ -18,12 +28,18 @@ Application::Application()
connect(this, &Application::onPermissionsDenied,
this, &Application::initializeQML);
NativeHelpers::registerApplicationInstance(this);
#if QT_VERSION < 0x060000
NativeHelpers::registerApplicationInstance(this);
#endif
}
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()
@ -32,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<void>("org/ftylitak/qzxing/Utilities",
"requestQZXingPermissions",
"(Landroid/app/Activity;)V",
QtAndroid::androidActivity().object());
#else
emit onPermissionsGranted();
#endif
qDebug() << "Permissions granted";
#else
emit onPermissionsGranted();

View File

@ -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
{

View File

@ -0,0 +1,166 @@
import QtQuick 2.5
import QtQuick.Window 2.0
import QtQuick.Controls 2.0
import QtQuick.Layouts 1.1
import QtMultimedia
import QZXing 3.3
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
}
Component.onCompleted: { camera.active = false; camera.active = true; }
}
QZXingFilter
{
id: zxingFilter
videoSink: videoOutput.videoSink
orientation: videoOutput.orientation
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
}
}

View File

@ -1,5 +1,6 @@
<RCC>
<qresource prefix="/">
<file>main.qml</file>
<file>main_qt6_2.qml</file>
</qresource>
</RCC>

View File

@ -1,4 +1,4 @@
cmake_minimum_required(VERSION 3.2)
cmake_minimum_required(VERSION 3.3)
project(QZXing)
find_package(Qt5 COMPONENTS Core REQUIRED)

View File

@ -409,11 +409,20 @@ qzxing_multimedia {
DEFINES += QZXING_MULTIMEDIA
PRL_EXPORT_DEFINES += QZXING_MULTIMEDIA
HEADERS += \
$$PWD/QZXingFilter.h
lessThan(QT_VERSION, 6.2) {
HEADERS += \
$$PWD/QZXingFilter.h
SOURCES += \
$$PWD/QZXingFilter.cpp
}
greaterThan(QT_VERSION, 6.1) {
QT += concurrent
HEADERS += \
$$PWD/QZXingFilterVideoSink.h
SOURCES += \
$$PWD/QZXingFilter.cpp
$$PWD/QZXingFilterVideoSink.cpp
}
}
qzxing_qml {

View File

@ -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
@ -98,10 +102,10 @@ QZXing::QZXing(QZXing::DecoderFormat decodeHints, QObject *parent) : QObject(par
#if QT_VERSION >= 0x040700
void QZXing::registerQMLTypes()
{
qmlRegisterType<QZXing>("QZXing", 3, 2, "QZXing");
qmlRegisterType<QZXing>("QZXing", 3, 3, "QZXing");
#ifdef QZXING_MULTIMEDIA
qmlRegisterType<QZXingFilter>("QZXing", 3, 2, "QZXingFilter");
qmlRegisterType<QZXingFilter>("QZXing", 3, 3, "QZXingFilter");
#endif //QZXING_MULTIMEDIA
}
@ -419,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 "";
}
@ -551,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 "";
}

View File

@ -25,7 +25,7 @@ CONFIG += \
#qzxing_qml \
#qzxing_multimedia \
VERSION = 3.2
VERSION = 3.3
TARGET = QZXing
TEMPLATE = lib

View File

@ -0,0 +1,114 @@
#include "QZXingFilterVideoSink.h"
#include <QDebug>
#include <QtConcurrent/QtConcurrent>
#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::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<QVideoSink*>(videoSink);
connect(m_videoSink, &QVideoSink::videoFrameChanged, this, &QZXingFilter::processFrame);
}
void QZXingFilter::processFrame(const QVideoFrame &frame) {
#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
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
}

View File

@ -0,0 +1,69 @@
/*
* 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 <QObject>
#include <QDebug>
#include <QFuture>
#include "QZXing.h"
#include <QtMultimedia/QVideoSink>
#include <QtMultimedia/QVideoFrame>
/// 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)
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<void> 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