Qt is renowned for its cross-platform capabilities, enabling developers to craft applications for Windows, macOS, Linux, iOS, and Android. In this article, we will construct a QR code scanner for Windows and Android utilizing Qt6. The sample code will be presented in C++ and QML, employing the Dynamsoft Barcode Reader as the barcode SDK for the project.
Prerequisites
Setting Up the Qt6 Environment for Windows and Android
-
Open Qt Creator and navigate to Tools > Options > Devices > Android. Here, you need to specify the paths for the Java SDK, Android SDK, and Android NDK.
-
Create a new Qt6 project using the
Qt Quick Application
template. Name the projectqt6project
. Ensure the project is configured with kits for Windows and Android. Download the Dynamsoft Barcode Reader SDKs. For Windows, the SDK includes header files and DLLs. For Android, it provides a
DynamsoftBarcodeReader.aar
file. You can extract the.aar
file to obtain the shared librarylibDynamsoftBarcodeReaderAndroid.so
. The header files are common to both platforms. Copy the header files and all shared libraries from the Dynamsoft Barcode Reader SDK to your project.-
Configure the
CMakeLists.txt
file for both Windows and Android platforms.
cmake_minimum_required(VERSION 3.16) project(qt6project VERSION 0.1 LANGUAGES CXX) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(TARGET_NAME appqt6project) find_package(Qt6 6.5 REQUIRED COMPONENTS Quick Multimedia) qt_standard_project_setup(REQUIRES 6.5) qt_add_executable(appqt6project main.cpp ) qt_add_qml_module(appqt6project URI qt6project VERSION 1.0 QML_FILES Main.qml SOURCES include/DynamsoftBarcodeReader.h include/DynamsoftCommon.h ) set_target_properties(appqt6project PROPERTIES WIN32_EXECUTABLE TRUE ) include_directories("${PROJECT_SOURCE_DIR}/include/") target_link_libraries(${TARGET_NAME} PRIVATE Qt6::Quick Qt::Multimedia) # Platform-specific configurations if(WIN32) if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU") target_link_libraries(${TARGET_NAME} PRIVATE "${PROJECT_SOURCE_DIR}/libs/windows/DynamsoftBarcodeReaderx64.dll") else() # Assuming MSVC or other non-GNU compilers target_link_libraries(${TARGET_NAME} PRIVATE "DBRx64") endif() # Copy DLLs to build directory after build add_custom_command(TARGET ${TARGET_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory "${PROJECT_SOURCE_DIR}/libs/windows/" $<TARGET_FILE_DIR:${TARGET_NAME}>) elseif(ANDROID) add_library(native-lib SHARED IMPORTED) set_target_properties(native-lib PROPERTIES IMPORTED_LOCATION "${PROJECT_SOURCE_DIR}/libs/android/libDynamsoftBarcodeReaderAndroid.so") target_link_libraries(${TARGET_NAME} PRIVATE native-lib) endif() include(GNUInstallDirs) install(TARGETS appqt6project BUNDLE DESTINATION . LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} )
Building a Multiple Barcode and QR Code Scanner for Windows and Android Using Qt QML and C++
To implement the barcode and QR code scanner, we need to complete the following tasks:
- Capture camera frames for image processing in Qt6.
- Offload heavy image processing tasks to a separate thread.
How to Capture Camera Frames for Image Processing in Qt6
Qt Creator offers several camera examples that developers can use to learn how to access a camera in Qt6. We can start with the camera
example.
Based on the camera
example, we can create a camera view in main.qml
as follows:
import QtQuick
import QtMultimedia
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
CaptureSession {
id: captureSession
camera: Camera {
id: camera
}
videoOutput: viewfinder
}
VideoOutput {
id: viewfinder
visible: true
anchors.fill: parent
}
Component.onCompleted: camera.start()
}
For Android, the camera access requires permission requests. In the main.cpp
file, add the following code:
#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQuickView>
#if QT_CONFIG(permissions)
#include <QPermission>
#endif
int main(int argc, char *argv[])
{
QGuiApplication app(argc, argv);
QQmlApplicationEngine engine;
QQuickView view;
view.setResizeMode(QQuickView::SizeRootObjectToView);
auto setupView = [&engine, &app]() {
QObject::connect(
&engine,
&QQmlApplicationEngine::objectCreationFailed,
&app,
[]() { QCoreApplication::exit(-1); },
Qt::QueuedConnection);
engine.loadFromModule("qt6project", "Main");
};
#if QT_CONFIG(permissions)
QCameraPermission cameraPermission;
qApp->requestPermission(cameraPermission, [&setupView](const QPermission &permission) {
if (permission.status() != Qt::PermissionStatus::Granted)
qWarning("Camera permission is not granted!");
setupView();
});
#else
setupView();
#endif
return app.exec();
}
The simple camera view application is now complete. The next step involves capturing video frames. The VideoOutput
component features a read-only property named videoSink
, which is a QVideoSink
object. The QVideoSink class delivers video frames to the application developer via the videoFrameChanged() signal. We can create a custom Qt object, FrameProcessor
, to manage the video frames:
FrameProcessor.h
#include <QObject>
#include <QVideoSink>
#include <QVideoFrame>
class FrameProcessor : public QObject
{
Q_OBJECT
Q_PROPERTY(QVideoSink *videoSink READ videoSink WRITE setVideoSink NOTIFY videoSinkChanged)
pulic:
QVideoSink *videoSink() const;
void setVideoSink(QVideoSink *sink);
signals:
void videoSinkChanged();
private slots:
void processFrame(const QVideoFrame &frame);
private:
QVideoSink *m_videoSink;
}
FrameProcessor.cpp
#include "FrameProcessor.h"
QVideoSink *FrameProcessor::videoSink() const
{
return m_videoSink;
}
void FrameProcessor::setVideoSink(QVideoSink *sink)
{
if (m_videoSink != sink)
{
m_videoSink = sink;
connect(m_videoSink, &QVideoSink::videoFrameChanged, this, &FrameProcessor::processFrame);
}
}
void FrameProcessor::processFrame(const QVideoFrame &frame)
{
// image processing
}
Once FrameProcessor
is made accessible to QML, the setVideoSink
method can be invoked implicitly through property binding. Subsequently, the connect
function is utilized to link the videoFrameChanged
signal with the processFrame
slot. To facilitate video frame reception, modify the main.qml
file as follows:
import QtQuick
import QtMultimedia
Window {
width: 640
height: 480
visible: true
title: qsTr("Hello World")
FrameProcessor {
id: frameProcessor
videoSink: viewfinder.videoSink
}
CaptureSession {
id: captureSession
camera: Camera {
id: camera
}
videoOutput: viewfinder
}
VideoOutput {
id: viewfinder
visible: true
anchors.fill: parent
}
Component.onCompleted: camera.start()
}
The application should now return each video frame through the processFrame
slot. However, if you do CPU-intensive image processing work in the slot, the application may freeze. To avoid this, we need to move the image processing to a separate thread.
How to Decode Barcodes and QR Codes from Camera Frames on QThread
We define a new class, ImageProcessor
, for handling image processing:
#ifndef IMAGEPROCESSOR_H
#define IMAGEPROCESSOR_H
#include <QObject>
class ImageProcessor : public QObject {
Q_OBJECT
public:
explicit ImageProcessor(QObject *parent = nullptr);
~ImageProcessor();
public slots:
void processImage(const QImage &image);
signals:
void imageProcessed(const QString &out);
private:
void *reader;
};
#endif // IMAGEPROCESSOR_H
The reader
is a pointer to a Dynamsoft Barcode Reader object. The processImage
slot decodes barcodes and QR codes from the image. The imageProcessed
signal is emitted upon completion of the decoding process.
Here is the implementation of ImageProcessor
:
#include "ImageProcessor.h"
#include "DynamsoftBarcodeReader.h"
#include <QImage>
ImageProcessor::ImageProcessor(QObject *parent) : QObject(parent)
{
reader = DBR_CreateInstance();
char errorMessage[256];
PublicRuntimeSettings settings;
DBR_GetRuntimeSettings(reader, &settings);
DBR_UpdateRuntimeSettings(reader, &settings, errorMessage, 256);
}
ImageProcessor::~ImageProcessor()
{
if (reader) DBR_DestroyInstance(reader);
}
void ImageProcessor::processImage(const QImage &image)
{
if (!reader) return;
QString out = "";
if (!image.isNull())
{
int width = image.width();
int height = image.height();
int bytesPerLine = image.bytesPerLine();
const uchar *pixelData = image.constBits();
int ret = 0;
if (image.format() == QImage::Format_RGBA8888_Premultiplied || image.format() == QImage::Format_RGBA8888)
{
ret = DBR_DecodeBuffer(reader, pixelData, width, height, bytesPerLine, IPF_ABGR_8888, "");
}
}
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) + "\n";
out += "Barcode format: " + QLatin1String(results[index]->barcodeFormatString) + "\n";
out += "Barcode value: " + QLatin1String(results[index]->barcodeText) + "\n";
out += "----------------------------------------------------------------------------------------\n";
}
DBR_FreeTextResults(&handler);
emit imageProcessed(out);
}
In frameProcessor
, we set the license key for Dynamsoft Barcode Reader and create an instance of ImageProcessor
. Then, we move the ImageProcessor
to a new thread.
FrameProcessor::FrameProcessor(QObject *parent) : QObject(parent)
{
m_isAvailable = true;
char errorMsgBuffer[512];
// Click https://www.dynamsoft.com/customer/license/trialLicense/?product=dbr to get a trial license.
DBR_InitLicense("LICENSE-KEY", errorMsgBuffer, 512);
printf("DBR_InitLicense: %s\n", errorMsgBuffer);
const char *version = DBR_GetVersion();
m_displayString = QString(version);
ImageProcessor *processor = new ImageProcessor();
QThread *workerThread = new QThread();
processor->moveToThread(workerThread);
connect(workerThread, &QThread::finished, processor, &QObject::deleteLater);
connect(this, &FrameProcessor::newFrameAvailable, processor, &ImageProcessor::processImage);
connect(processor, &ImageProcessor::imageProcessed, this, &FrameProcessor::onImageProcessed);
workerThread->start();
}
The camera frame needs to be converted from QVideoFrame
to QImage
before being processed by ImageProcessor
. The processFrame
slot is modified as follows:
void FrameProcessor::processFrame(const QVideoFrame &frame)
{
if (!frame.isValid() || !m_isAvailable)
return;
m_isAvailable = false;
QImage image = frame.toImage();
emit newFrameAvailable(image);
}
Once the image processing is complete, the imageProcessed
signal is emitted. The onImageProcessed
slot is subsequently called to display the barcode and QR code information:
void FrameProcessor::onImageProcessed(const QString &out) {
emit barcodeDetected(out);
m_isAvailable = true;
}
Running the Qt6 Barcode and QR Code Scanner on Windows and Android
Windows
Android