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

Xiao Ling - Nov 9 '21 - - Dev Community

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

  1. 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.

    QR code scanner for Windows with Qt QML

  2. Create a new Qt6 project using the Qt Quick Application template. Name the project qt6project. Ensure the project is configured with kits for Windows and Android.

    QR build tool

  3. 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 library libDynamsoftBarcodeReaderAndroid.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.

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

  1. Capture camera frames for image processing in Qt6.
  2. 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.

Qt camera samples

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()
}
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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()
}
Enter fullscreen mode Exit fullscreen mode

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

Enter fullscreen mode Exit fullscreen mode

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);
}

Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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;
}
Enter fullscreen mode Exit fullscreen mode

Running the Qt6 Barcode and QR Code Scanner on Windows and Android

Windows

QR code scanner for Windows with Qt QML

Android

QR code scanner for Android with Qt QML

Source Code

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

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Terabox Video Player