How to Build a QR Code Scanner for Windows and Android with Qt QML

Xiao Ling
7 min readNov 8, 2021

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 and vcomp110.dll to libs/windows.
  • Copy DynamsoftCommon.h and DynamsoftBarcodeReader.h to include.

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:

  1. Include DynamsoftBarcodeReader.h and DynamsoftCommon.h.
#include "include/DynamsoftCommon.h"
#include "include/DynamsoftBarcodeReader.h"

2. 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, "LICENSE-KEY");
}
~QRCodeFilterRunnable() {DBR_DestroyInstance(reader);}
QVideoFrame run(QVideoFrame *input, const QVideoSurfaceFormat &surfaceFormat, RunFlags flags) override;

private:
QRCodeFilter *m_filter;
void *reader;
};

3. Get the video frame buffer in QRCodeFilterRunnable::run() and call DBR_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 is QVideoFrame::Format_YUYV, which is not supported by Dynamsoft Barcode Reader. Therefore, a conversion is needed. We extract Y 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();

4. 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);

5. 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:

Source Code

https://github.com/yushulx/Qt-QML-QR-code-scanner

Originally published at https://www.dynamsoft.com on November 8, 2021.

--

--

Xiao Ling

Manager of Dynamsoft Open Source Projects | Tech Lover