How to Build a QR Code Scanner for Windows and Android with Qt QML
If you want to create a QR code scanner for both Windows and Android from a single codebase, what is the best way to do it? If you have read my previous article, Flutter could be your answer. However, it takes time to develop Flutter plugins. The cons of Flutter are that the framework for Windows is still under development, and you need to use three programming languages, Dart, C/C++, and Java/Kotlin, to build a cross-platform app. In this article, I will recommend Qt. With a few lines of C/C++ and QML code, you can quickly build a QR code scanner for Windows and Android in less than 30 minutes.
Requirements
Setting Up Qt Environment for Windows and Android
Open Qt Creator and go to Tools > Options > Devices > Android.
You need to specify the paths of Java SDK, Android SDK and Android NDK.
When creating a new Qt project, you need to select Android Qt 5.12.11 Clang arm64-v8a for Android and Desktop Qt 5.12.11 MinGW 64-bit for Windows. Why not Qt 6? Because the Qt Multimedia module which provides camera API is not in Qt 6 yet.
Building QR Code Scanner for Windows Using Qt QML and C++
We create a simple camera view in main.qml
:
import QtQuick 2.0
import QtMultimedia 5.4
Item {
width: 1280
height: 720
Camera {
id: camera
}
VideoOutput {
id: viewfinder
width: parent.width
height: parent.height
source: camera
}
}
Click Debug
to select the kit for desktop and then run the application.
Without any C/C++ code, a camera view application is done.
To recognize QR code, we need to obtain the video frame by adding a filter. The Qt-5.12.11/multimedia/video/qmlvideofilter_opencl
sample demonstrates the details of how to add the filter.
Adding a filter to get the video frame
In main.cpp
, we create a QRCodeFilter
class, a QRCodeFilterRunnable
class and a QRCodeFilterResult
class. We get the video frame via QVideoFrame QRCodeFilterRunnable::run()
.
class QRCodeFilter : public QAbstractVideoFilter
{
Q_OBJECT
public:
QVideoFilterRunnable *createFilterRunnable() override;
signals:
void finished(QObject *result);
private:
friend class QRCodeFilterRunnable;
};
class QRCodeFilterRunnable : public QVideoFilterRunnable
{
public:
QRCodeFilterRunnable(QRCodeFilter *filter) : m_filter(filter)
{
}
~QRCodeFilterRunnable() {}
QVideoFrame run(QVideoFrame *input, const QVideoSurfaceFormat &surfaceFormat, RunFlags flags) override;
private:
QRCodeFilter *m_filter;
};
class QRCodeFilterResult : public QObject
{
Q_OBJECT
Q_PROPERTY(QString text READ text)
public:
QString text() const { return m_text; }
private:
QString m_text;
friend class QRCodeFilterRunnable;
};
QVideoFilterRunnable *QRCodeFilter::createFilterRunnable()
{
return new QRCodeFilterRunnable(this);
}
QVideoFrame QRCodeFilterRunnable::run(QVideoFrame *input, const QVideoSurfaceFormat &surfaceFormat, RunFlags flags)
{
Q_UNUSED(surfaceFormat);
Q_UNUSED(flags);
QRCodeFilterResult *result = new QRCodeFilterResult;
result->m_text = "QVideoFrame";
emit m_filter->finished(result);
return *input;
}
To make the filter work, we register the filter before setting the QML source file:
int main(int argc, char* argv[])
{
QGuiApplication app(argc,argv);
qmlRegisterType<QRCodeFilter>("com.dynamsoft.barcode", 1, 0, "QRCodeFilter");
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
QObject::connect(view.engine(), &QQmlEngine::quit,
qApp, &QGuiApplication::quit);
view.setSource(QUrl("qrc:///main.qml"));
view.resize(1280, 720);
view.show();
return app.exec();
}
Now, the filter is accessible in the QML source file:
import QtQuick 2.0
import QtMultimedia 5.4
import com.dynamsoft.barcode 1.0
Item {
width: 1280
height: 720
Camera {
id: camera
}
VideoOutput {
id: viewfinder
width: parent.width
height: parent.height
source: camera
}
}
After importing the module, we can use the filter as follows:
import QtQuick 2.0
import QtMultimedia 5.4
import com.dynamsoft.barcode 1.0
Item {
width: 1280
height: 720
Camera {
id: camera
captureMode: Camera.CaptureVideo
focus {
focusMode: Camera.FocusContinuous
focusPointMode: Camera.FocusPointCenter
}
objectName: "qrcamera"
}
VideoOutput {
id: viewfinder
width: parent.width
height: parent.height
source: camera
filters: [ qrcodefilter ]
}
QRCodeFilter {
id: qrcodefilter
onFinished: {
info.text = result.text;
}
}
Column {
x: 10
y: 10
Text {
font.pointSize: 24
color: "green"
text: "Qt Demo: QR Code Scanner"
}
Text {
id: info
font.pointSize: 12
color: "green"
text: info.text
}
}
}
The text is the placeholder for displaying QR code information on the screen. The next step is to add the C++ barcode SDK and implement QR code recognition.
Integrating C++ barcode SDK into Qt project
We create directories: libs/windows
and include
.
- Copy
DynamsoftBarcodeReaderx64.dll
andvcomp110.dll
tolibs/windows
. - Copy
DynamsoftCommon.h
andDynamsoftBarcodeReader.h
toinclude
.
Then configure the header files and libraries in qrcodescanner.pro
:
TEMPLATE=app
TARGET=qrcodescanner
QT += quick qml multimedia
SOURCES += main.cpp
RESOURCES += qrcodescanner.qrc
HEADERS = include/DynamsoftCommon.h \
include/DynamsoftBarcodeReader.h
target.path = $$PWD
INSTALLS += target
win32: LIBS += -L$$PWD/libs/windows -lDynamsoftBarcodeReaderx64
Here are the steps for reading QR code from video frame in main.cpp
:
-
Include
DynamsoftBarcodeReader.h
andDynamsoftCommon.h
.#include "include/DynamsoftCommon.h" #include "include/DynamsoftBarcodeReader.h"
-
Get a valid license and initialize the barcode reader in
QRCodeFilterRunnable
:class QRCodeFilterRunnable : public QVideoFilterRunnable { public: QRCodeFilterRunnable(QRCodeFilter *filter) : m_filter(filter) { reader = DBR_CreateInstance(); char errorMessage[256]; PublicRuntimeSettings settings; DBR_GetRuntimeSettings(reader, &settings); settings.barcodeFormatIds = BF_QR_CODE; DBR_UpdateRuntimeSettings(reader, &settings, errorMessage, 256); DBR_InitLicense(reader, "DLS2eyJoYW5kc2hha2VDb2RlIjoiMjAwMDAxLTE2NDk4Mjk3OTI2MzUiLCJvcmdhbml6YXRpb25JRCI6IjIwMDAwMSIsInNlc3Npb25QYXNzd29yZCI6IndTcGR6Vm05WDJrcEQ5YUoifQ=="); } ~QRCodeFilterRunnable() {DBR_DestroyInstance(reader);} QVideoFrame run(QVideoFrame *input, const QVideoSurfaceFormat &surfaceFormat, RunFlags flags) override; private: QRCodeFilter *m_filter; void *reader; };
-
Get the video frame buffer in
QRCodeFilterRunnable::run()
and callDBR_DecodeBuffer()
:QVideoFrame QRCodeFilterRunnable::run(QVideoFrame *input, const QVideoSurfaceFormat &surfaceFormat, RunFlags flags) { Q_UNUSED(surfaceFormat); Q_UNUSED(flags); QRCodeFilterResult *result = new QRCodeFilterResult; input->map(QAbstractVideoBuffer::ReadOnly); int ret = DBR_DecodeBuffer(reader, input->bits(), input->width(), input->height(), input->bytesPerLine(), <PixelFormat>, ""); input->unmap(); }
What is the pixel format we should use here? It depends on what the pixel format of video frame is. We can get the pixel format by calling
input->pixelFormat()
. In my environment, the pixel format of the input video frame isQVideoFrame::Format_YUYV
, which is not supported by Dynamsoft Barcode Reader. Therefore, a conversion is needed. We extractY
to construct a grayscale image buffer:input->map(QAbstractVideoBuffer::ReadOnly); int width = input->width(); int height = input->height(); int total = width * height; unsigned char* origin = input->bits(); unsigned char* grayscale = new unsigned char[total]; for (int i = 0; i < total; i++) { grayscale[i] = origin[i * 2]; } int ret = DBR_DecodeBuffer(reader, grayscale, width, height, width, IPF_GRAYSCALED, ""); delete[] grayscale; input->unmap();
-
As decoding succeeds, get the result and send it to the filter:
QString out = ""; TextResultArray *handler = NULL; DBR_GetAllTextResults(reader, &handler); TextResult **results = handler->results; int count = handler->resultsCount; for (int index = 0; index < count; index++) { out += "Index: " + QString::number(index) + ", Elapsed time: " + QString::number(start.msecsTo(end)) + "ms\n"; out += "Barcode format: " + QLatin1String(results[index]->barcodeFormatString) + "\n"; out += "Barcode value: " + QLatin1String(results[index]->barcodeText) + "\n"; } DBR_FreeTextResults(&handler); result->m_text = out; emit m_filter->finished(result);
-
Build and run the simple QR code scanner for Windows:
Making the Codebase Work for Android with a Bit of Change
When thinking about integrating an Android barcode SDK, you may come up with how to link *.aar
package and how to call Java API firstly. It is true that Dynamsoft provides a DynamsoftBarcodeReaderAndroid.aar
package for Android programming. Nevertheless, there is a trick to use the mobile barcode SDK without linking the aar file in build.gradle
.
We unzip the DynamsoftBarcodeReaderAndroid.aar
package to extract libDynamsoftBarcodeReaderAndroid.so
. Then copy it to libs/android
folder.
Link the *.so
file in qrcodescanner.pro
and use ANDROID_EXTRA_LIBS
to copy the *.so file
to the final apk file:
unix: LIBS += -L$$PWD/libs/android -lDynamsoftBarcodeReaderAndroid
contains(ANDROID_TARGET_ARCH,arm64-v8a) {
ANDROID_EXTRA_LIBS = $$PWD/libs/android/libDynamsoftBarcodeReaderAndroid.so
}
Yes. We can invoke native API directly without any JNI calls. But we still need some extra work, such as configuring camera focus mode which does not work on Windows:
import QtQuick 2.0
import QtMultimedia 5.4
import com.dynamsoft.barcode 1.0
Item {
width: 1280
height: 720
Camera {
id: camera
captureMode: Camera.CaptureVideo
focus {
focusMode: Camera.FocusContinuous
focusPointMode: Camera.FocusPointCenter
}
objectName: "qrcamera"
}
}
It is known that the default pixel format of camera frame on Android is NV21. However, when debugging the Qt code, I found it is Format_BGR32
. Although I have tried to set the pixel format before opening the camera, it doesn’t seem to work:
QObject *qmlCamera = view.findChild<QObject*>("qrcamera");
QCamera *camera = qvariant_cast<QCamera *>(qmlCamera->property("mediaObject"));
QCameraViewfinderSettings viewfinderSettings = camera->viewfinderSettings();
viewfinderSettings.setResolution(640, 480);
viewfinderSettings.setMinimumFrameRate(15.0);
viewfinderSettings.setMaximumFrameRate(30.0);
viewfinderSettings.setPixelFormat(QVideoFrame::Format_NV21); // cannot work
camera->setViewfinderSettings(viewfinderSettings);
Afterwards, the corresponding decoding code is changed to:
DBR_DecodeBuffer(reader, input->bits(), width, height, input->bytesPerLine(), IPF_ABGR_8888, "");
We can use the predefined macros to make the code compatible with Windows and Android:
#ifdef Q_OS_ANDROID
int ret = DBR_DecodeBuffer(reader, input->bits(), width, height, input->bytesPerLine(), IPF_ABGR_8888, "");
#else
unsigned char* origin = input->bits();
unsigned char* grayscale = new unsigned char[total];
for (int i = 0; i < total; i++)
{
grayscale[i] = origin[i * 2];
}
int ret = DBR_DecodeBuffer(reader, grayscale, width, height, width, IPF_GRAYSCALED, "");
delete[] grayscale;
#endif
Finally, we can build and run the QR code scanner for Android: