How to Build a QR Code Scanner for Windows and Android with Qt QML
Tags: android, qrcode, scanner, qt
Published: 2021-11-09 07:40:54

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](https://www.dynamsoft.com/barcode-reader/overview/) as the barcode SDK for the project.

## Prerequisites
- [Qt Online Installer](https://www.qt.io/download-qt-installer-oss)
- [Dynamsoft Barcode Reader Trial License](https://www.dynamsoft.com/customer/license/trialLicense/?product=dbr)


## 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](https://www.dynamsoft.com/codepool/img/2021/11/qt-creator-android.jpg)

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](https://www.dynamsoft.com/codepool/img/2024/04/qt6-cmake-config.png)

3. Download the [Dynamsoft Barcode Reader SDKs](https://www.dynamsoft.com/barcode-reader/downloads/). 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
    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](https://www.dynamsoft.com/codepool/img/2024/04/qt-creator-camera-samples.jpg)

Based on the `camera` example, we can create a camera view in `main.qml` as follows:

```qml
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:

```cpp
#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](https://doc.qt.io/qt-6/qvideosink.html#details) class delivers video frames to the application developer via the [videoFrameChanged()](https://doc.qt.io/qt-6/qvideosink.html#videoFrameChanged) signal. We can create a custom Qt object, `FrameProcessor`, to manage the video frames:

**FrameProcessor.h**

```cpp
#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**

```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:

```qml
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:

```cpp
#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`:

```cpp
#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](https://www.dynamsoft.com/customer/license/trialLicense/?product=dbr) for Dynamsoft Barcode Reader and create an instance of `ImageProcessor`. Then, we move the `ImageProcessor` to a new thread.

```cpp
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:

```cpp
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:

```cpp
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**

![QR code scanner for Windows with Qt QML](https://www.dynamsoft.com/codepool/img/2024/04/qt6-windows-qrcode-scanner.jpg)


**Android**

![QR code scanner for Android with Qt QML](https://www.dynamsoft.com/codepool/img/2024/04/qt6-android-qrcode-scanner.jpg)

## Source Code

[https://github.com/yushulx/Qt-QML-QR-code-scanner](https://github.com/yushulx/Qt-QML-QR-code-scanner)