How to Build a Qt Barcode Scanner with MSVC and Dynamsoft C++ Barcode SDK on Windows


Qt is a cross-platform C++ framework for developing applications with graphical user interfaces (GUIs). In this tutorial, you’ll learn how to create a robust, production-ready barcode scanner application using Qt 6, Microsoft Visual C++ (MSVC), and the Dynamsoft Barcode Reader C++ SDK on Windows. This tutorial covers everything from environment setup to implementing advanced features like real-time camera scanning and visual barcode overlays.



Demo: Qt Barcode Scanner for Windows



Prerequisites



What You’ll Build

By the end of this tutorial, you’ll create a barcode scanner application with these features:

  • Dual-mode scanning: Image file scanning and real-time camera scanning
  • Visual feedback: Live barcode detection overlays with bounding boxes
  • Multiple format support: Support for 1D and 2D barcodes (QR codes, Code 128, DataMatrix, etc.)
  • Drag-and-drop functionality: Easy image loading via drag-and-drop

Qt barcode scanner for Windows



Step 1: Environment Setup



1.1 Install Qt 6 with MSVC

Download and install Qt 6 from the official Qt installer:

  1. Run the Qt Online Installer.
  2. Select Qt 6.7.2 or later.
  3. Choose MSVC 2019/2022 64-bit component.
  4. Install to C:\Qt\6.7.2\msvc2022_64

    # Set Qt environment variable
    set Qt6_DIR=C:\Qt\6.7.2\msvc2022_64\lib\cmake\Qt6
    



1.2 Install OpenCV

Download OpenCV 4.8.0 from opencv.org:

  1. Download the Windows release
  2. Extract to C:\opencv
  3. Set environment variable:

    set OpenCV_DIR=C:\opencv\build
    



Step 2: Project Structure and CMake Configuration



2.1 Create Project Directory

mkdir qt-barcode-scanner
cd qt-barcode-scanner
Enter fullscreen mode

Exit fullscreen mode



2.2 CMakeLists.txt Configuration

Create a CMakeLists.txt file that handles all dependencies:

cmake_minimum_required(VERSION 3.16)
project(QtBarcodeScanner VERSION 1.0 LANGUAGES CXX)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

# Find Qt6 components
find_package(Qt6 REQUIRED COMPONENTS Core Widgets Multimedia MultimediaWidgets)

# Enable Qt MOC, UIC, and RCC
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTORCC ON)

# OpenCV for camera support - always enabled
find_package(OpenCV REQUIRED)
if(OpenCV_FOUND)
    message(STATUS "OpenCV found: ${OpenCV_VERSION}")
endif()

# Source files
set(SOURCES
    main.cpp
    mainwindow.cpp
    mainwindow.h
    mainwindow.ui
    barcodeworker.cpp
    barcodeworker.h
    opencvcamera.cpp
    opencvcamera.h
)

# Create executable
add_executable(QtBarcodeScanner ${SOURCES})

# Link Qt libraries
target_link_libraries(QtBarcodeScanner
    Qt6::Core
    Qt6::Widgets
    Qt6::Multimedia
    Qt6::MultimediaWidgets
)

# Link OpenCV
if(OpenCV_FOUND)
    target_link_libraries(QtBarcodeScanner ${OpenCV_LIBS})
    target_include_directories(QtBarcodeScanner PRIVATE ${OpenCV_INCLUDE_DIRS})
endif()

# Dynamsoft SDK configuration
set(DCV_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../dcv")
target_include_directories(QtBarcodeScanner PRIVATE "${DCV_ROOT}/include")

if(WIN32)
    target_link_libraries(QtBarcodeScanner
        "${DCV_ROOT}/lib/win/DynamsoftCaptureVisionRouter.lib"
        "${DCV_ROOT}/lib/win/DynamsoftCore.lib"
        "${DCV_ROOT}/lib/win/DynamsoftUtility.lib"
        "${DCV_ROOT}/lib/win/DynamsoftLicense.lib"
    )
endif()

# Post-build DLL and resource copying
if(WIN32)
    # Copy Qt platform plugins
    add_custom_command(TARGET QtBarcodeScanner POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E make_directory 
        "$/platforms"
        COMMAND ${CMAKE_COMMAND} -E copy_directory 
        "${Qt6_DIR}/../../../plugins/platforms"
        "$/platforms"
    )

    # Copy complete DLL directory
    add_custom_command(TARGET QtBarcodeScanner POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_directory 
        "${DCV_ROOT}/lib/win"
        "$"
    )
endif()
Enter fullscreen mode

Exit fullscreen mode

Key Configuration Points:

  • Automatic MOC/UIC/RCC: Essential for Qt meta-object compilation
  • Complete DLL copying: Ensures all Dynamsoft libraries are available
  • Plugin directory copying: Required for Qt platform plugins
  • Conditional compilation: Enables camera features based on available components



Step 3: Core Application Structure



3.1 Main Window Header (mainwindow.h)

#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include 
#include 
#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 

#include "opencvcamera.h"
#include 

#include "barcodeworker.h"

QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();

protected:
    void dragEnterEvent(QDragEnterEvent *event) override;
    void dropEvent(QDropEvent *event) override;
    void resizeEvent(QResizeEvent *event) override;
    void changeEvent(QEvent *event) override;

private slots:
    void openImageFile();
    void startCamera();
    void stopCamera();
    void clearResults();
    void about();
    void setLicense();
    void loadTemplate();

    void onBarcodeResults(const QList<BarcodeResult> &results);
    void onImageTabSelected();
    void onCameraTabSelected();
    void onLicenseStatusChanged(const QString &status, bool isValid);

private:
    void setupConnections();
    void loadImageFile(const QString &filePath);
    void updateImageDisplay(const QPixmap &pixmap, const QList<BarcodeResult> &results = QList<BarcodeResult>());
    void updateResultsDisplay(const QList<BarcodeResult> &results);
    void updateCameraDisplay();
    void updateLicenseStatus(const QString &status, bool isValid);

    bool tryStartOpenCVCamera();

    Ui::MainWindow *ui;

    std::unique_ptr<QCamera> camera;
    std::unique_ptr<QMediaCaptureSession> captureSession;
    std::unique_ptr<QVideoWidget> videoWidget;

    OpenCVCamera *openCVCamera;
    QLabel *cameraLabel;
    bool useOpenCVCamera;
    QTimer *resizeTimer;
    bool cameraUpdatesPaused;
#endif

    QThread *workerThread;
    BarcodeWorker *barcodeWorker;

    QPixmap currentPixmap;
    QList<BarcodeResult> currentImageResults;
    QPixmap currentCameraFrame;
    QList<BarcodeResult> currentCameraResults;
    QString licenseKey;
    QString templateContent;
    QString lastImageDirectory;

    QLabel *licenseStatusLabel;
};

#endif // MAINWINDOW_H
Enter fullscreen mode

Exit fullscreen mode



3.2 Barcode Worker Implementation

The BarcodeWorker class handles barcode detection in a separate thread to prevent UI blocking:

// barcodeworker.h
#ifndef BARCODEWORKER_H
#define BARCODEWORKER_H

#include 
#include 
#include 
#include 

#include "DynamsoftCaptureVisionRouter.h"
#include "DynamsoftUtility.h"

using namespace dynamsoft::license;
using namespace dynamsoft::cvr;
using namespace dynamsoft::dbr;
using namespace dynamsoft::utility;
using namespace dynamsoft::basic_structures;

struct BarcodeResult
{
    QString format;
    QString text;
    QList<QPoint> points;
};

class BarcodeWorker : public QObject, public CCapturedResultReceiver
{
    Q_OBJECT

public:
    explicit BarcodeWorker(QObject *parent = nullptr);
    ~BarcodeWorker();

    virtual void OnDecodedBarcodesReceived(CDecodedBarcodesResult *pResult) override;

public slots:
    void initialize();
    void processImage(const QImage &image);
    void processFrame(const QImage &frame);
    void setLicense(const QString &license);

signals:
    void resultsReady(const QList<BarcodeResult> &results);
    void licenseStatusChanged(const QString &status, bool isValid);

private:
    QList<BarcodeResult> convertResults(CDecodedBarcodesResult *pResult);

    CCaptureVisionRouter *m_router;
    QString m_licenseKey;
    bool m_initialized;
};

#endif // BARCODEWORKER_H
Enter fullscreen mode

Exit fullscreen mode

Key Implementation Details:

  • Thread Safety: All barcode processing happens in a worker thread
  • Dynamsoft Integration: Implements CCapturedResultReceiver for result callbacks
  • Signal-Slot Communication: Uses Qt signals for thread-safe communication
  • License Management: Handles Dynamsoft license initialization and validation



Step 4: Implementing the Barcode Worker



4.1 Worker Initialization and License Setup

// barcodeworker.cpp
void BarcodeWorker::initialize()
{
    try
    {
        char errorMsgBuffer[512];
        int ret = CLicenseManager::InitLicense(m_licenseKey.toUtf8().constData(), errorMsgBuffer, 512);
        if (ret != EC_OK)
        {
            QString errorMsg = QString::fromUtf8(errorMsgBuffer);
            emit licenseStatusChanged(QString("License: Failed (%1)").arg(errorMsg), false);
        }
        else
        {
            emit licenseStatusChanged("License: Valid", true);
        }

        m_router = new CCaptureVisionRouter();
        if (!m_router)
        {
            return;
        }

        m_router->AddResultReceiver(this);

        m_initialized = true;
    }
    catch (const std::exception &e)
    {
        m_initialized = false;
    }
}
Enter fullscreen mode

Exit fullscreen mode



4.2 Image Processing Implementation

void BarcodeWorker::processImage(const QImage &image)
{
    if (!m_initialized || !m_router || image.isNull())
    {
        if (!m_initialized)
        {
            initialize();
            if (!m_initialized)
            {
                emit resultsReady(QList<BarcodeResult>());
                return;
            }
        }

        if (image.isNull())
        {
            emit resultsReady(QList<BarcodeResult>());
            return;
        }
    }

    try
    {
        // Convert QImage to Dynamsoft-compatible format
        QImage rgbImage = image.convertToFormat(QImage::Format_RGB888);

        CImageData imageData(
            rgbImage.sizeInBytes(),
            rgbImage.bits(),
            rgbImage.width(),
            rgbImage.height(),
            rgbImage.bytesPerLine(),
            IPF_RGB_888
        );

        CCapturedResult *result = m_router->Capture(&imageData, CPresetTemplate::PT_READ_BARCODES);

        if (result)
        {
            if (result->GetErrorCode() != EC_OK)
            {
                emit resultsReady(QList<BarcodeResult>());
            }
            else
            {
                CDecodedBarcodesResult *barcodeResult = result->GetDecodedBarcodesResult();
                if (barcodeResult)
                {
                    QList<BarcodeResult> results = convertResults(barcodeResult);
                    emit resultsReady(results);
                }
                else
                {
                    emit resultsReady(QList<BarcodeResult>());
                }
            }
            result->Release();
        }
        else
        {
            emit resultsReady(QList<BarcodeResult>());
        }
    }
    catch (const std::exception &e)
    {
        emit resultsReady(QList<BarcodeResult>());
    }
}
Enter fullscreen mode

Exit fullscreen mode

Critical Points in Image Processing:

  • Format Conversion: Dynamsoft requires RGB888 format for optimal performance
  • Preset Templates: Using PT_READ_BARCODES for optimal detection settings



4.3 Result Conversion and Coordinate Extraction

QList<BarcodeResult> BarcodeWorker::convertResults(CDecodedBarcodesResult *pResult)
{
    QList<BarcodeResult> results;

    if (!pResult || pResult->GetErrorCode() != EC_OK)
    {
        return results;
    }

    int count = pResult->GetItemsCount();

    for (int i = 0; i < count; i++)
    {
        const CBarcodeResultItem *barcodeItem = pResult->GetItem(i);
        if (!barcodeItem)
            continue;

        BarcodeResult result;
        result.format = QString::fromUtf8(barcodeItem->GetFormatString());
        result.text = QString::fromUtf8(barcodeItem->GetText());

        CQuadrilateral location = barcodeItem->GetLocation();
        CPoint points[4];
        location.points[0] = points[0];
        location.points[1] = points[1]; 
        location.points[2] = points[2];
        location.points[3] = points[3];

        for (int j = 0; j < 4; j++)
        {
            result.points.append(QPoint(points[j].coordinate[0], points[j].coordinate[1]));
        }

        results.append(result);
    }

    return results;
}
Enter fullscreen mode

Exit fullscreen mode



Step 5: Implementing Visual Overlay System



5.1 Image Display with Barcode Overlay

void MainWindow::updateImageDisplay(const QPixmap &pixmap, const QList<BarcodeResult> &results)
{
    QPixmap displayPixmap = pixmap;

    if (!results.isEmpty())
    {
        QPainter painter(&displayPixmap);
        painter.setPen(QPen(Qt::green, 3));
        painter.setFont(QFont("Arial", 12, QFont::Bold));

        for (const auto &result : results)
        {
            if (result.points.size() >= 4)
            {
                // Draw bounding polygon
                QPolygon polygon;
                for (const auto &point : result.points)
                {
                    polygon << point;
                }
                painter.drawPolygon(polygon);

                if (!result.points.isEmpty())
                {
                    int textWidth = painter.fontMetrics().horizontalAdvance(result.text);
                    QRect textRect(result.points[0].x(), result.points[0].y() - 20, 
                                  textWidth + 10, 20);

                    painter.fillRect(textRect, QColor(0, 255, 0, 180));
                    painter.setPen(Qt::black);
                    painter.drawText(result.points[0].x() + 5, result.points[0].y() - 5, result.text);
                    painter.setPen(QPen(Qt::green, 3));
                }
            }
        }
    }

    ui->imageLabel->setPixmap(displayPixmap.scaled(ui->imageLabel->size(), 
                                                  Qt::KeepAspectRatio, 
                                                  Qt::SmoothTransformation));
}
Enter fullscreen mode

Exit fullscreen mode



5.2 Real-time Camera Overlay

connect(openCVCamera, QOverload<const QPixmap &>::of(&OpenCVCamera::frameReady), 
        this, [this](const QPixmap &pixmap)
{
    if (useOpenCVCamera && cameraLabel && cameraLabel->isVisible() && !cameraUpdatesPaused) {
        currentCameraFrame = pixmap;
        QSize labelSize = cameraLabel->size();

        if (labelSize.width() > 10 && labelSize.height() > 10) {
            if (!currentCameraResults.isEmpty()) {
                // Create overlay on current frame
                QPixmap overlayPixmap = pixmap;
                QPainter painter(&overlayPixmap);
                painter.setRenderHint(QPainter::Antialiasing);

                // Draw real-time barcode detection boxes
                for (const auto &result : currentCameraResults) {
                    if (!result.points.isEmpty() && result.points.size() >= 4) {
                        QPen pen(Qt::green, 3);
                        painter.setPen(pen);

                        // Draw detection polygon
                        QPolygonF polygon;
                        for (const auto &point : result.points) {
                            polygon << point;
                        }
                        painter.drawPolygon(polygon);

                        // Draw format label
                        if (!result.format.isEmpty()) {
                            painter.setPen(QPen(Qt::yellow, 2));
                            QFont font = painter.font();
                            font.setPointSize(12);
                            font.setBold(true);
                            painter.setFont(font);

                            QPointF textPos = result.points.first();
                            textPos.setY(textPos.y() - 10);
                            painter.drawText(textPos, result.format);
                        }
                    }
                }
                painter.end();

                QPixmap scaledPixmap = overlayPixmap.scaled(labelSize, 
                                                          Qt::KeepAspectRatio, 
                                                          Qt::SmoothTransformation);
                cameraLabel->setPixmap(scaledPixmap);
            } else {
                // No overlay needed
                QPixmap scaledPixmap = pixmap.scaled(labelSize, 
                                                   Qt::KeepAspectRatio, 
                                                   Qt::SmoothTransformation);
                cameraLabel->setPixmap(scaledPixmap);
            }
        }

        // Process frame for barcode detection
        if (barcodeWorker) {
            QImage image = pixmap.toImage();
            barcodeWorker->processImage(image);
        }
    }
});
Enter fullscreen mode

Exit fullscreen mode



Step 6: OpenCV Camera Implementation

The application uses OpenCV exclusively for camera access, providing better reliability and cross-platform compatibility than Qt6 Multimedia.



6.1 Camera Initialization and Setup

void MainWindow::startCamera()
{
    if (openCVCamera && openCVCamera->start(0))
    {
        // Show OpenCV camera display
        if (cameraLabel)
        {
            cameraLabel->show();
        }

        ui->startCameraButton->setEnabled(false);
        ui->stopCameraButton->setEnabled(true);

        ui->resultsTextEdit->append("OpenCV camera started successfully!");
    }
    else
    {
        QStringList availableCameras = openCVCamera ? openCVCamera->availableCameras() : QStringList();
        if (availableCameras.isEmpty())
        {
            ui->resultsTextEdit->append("No cameras detected by OpenCV");
        }
        else
        {
            ui->resultsTextEdit->append(QString("Available cameras: %1, but failed to start")
                                       .arg(availableCameras.join(", ")));
        }
    }
}
Enter fullscreen mode

Exit fullscreen mode



6.2 OpenCV Camera Class Implementation

// opencvcamera.h
class OpenCVCamera : public QObject
{
    Q_OBJECT

public:
    explicit OpenCVCamera(QObject *parent = nullptr);
    ~OpenCVCamera();

    bool start(int cameraIndex = 0);
    void stop();
    bool isActive() const { return active; }
    bool isAvailable() const;
    QStringList availableCameras() const;

signals:
    void frameReady(const QPixmap &frame);

private slots:
    void captureFrame();

private:
    cv::VideoCapture capture;
    QTimer *captureTimer;
    bool active;
    int currentCameraIndex;
};
Enter fullscreen mode

Exit fullscreen mode



6.3 Real-time Frame Processing

void OpenCVCamera::captureFrame()
{
    if (!active || !capture.isOpened())
        return;

    cv::Mat frame;
    if (capture.read(frame) && !frame.empty())
    {
        // Convert BGR to RGB for Qt
        cv::Mat rgbFrame;
        cv::cvtColor(frame, rgbFrame, cv::COLOR_BGR2RGB);

        // Convert to QImage then QPixmap
        QImage qImage(rgbFrame.data, rgbFrame.cols, rgbFrame.rows, 
                     rgbFrame.step, QImage::Format_RGB888);
        QPixmap pixmap = QPixmap::fromImage(qImage);

        emit frameReady(pixmap);
    }
}
Enter fullscreen mode

Exit fullscreen mode



Step 7: Building and Deployment

Create build.bat for easy building:

@echo off
echo Building Qt Barcode Scanner in Release mode...

REM Check for Qt6_DIR environment variable
if not defined Qt6_DIR (
    echo Qt6_DIR environment variable not set. Searching for Qt installations...

    REM Search common Qt installation paths
    for %%d in (
        "C:\Qt\6.7.2\msvc2022_64\lib\cmake\Qt6"
        "C:\Qt\6.7.2\msvc2019_64\lib\cmake\Qt6"
        "C:\Qt\6.6.0\msvc2022_64\lib\cmake\Qt6"
    ) do (
        if exist %%d (
            set "Qt6_DIR=%%~d"
            echo Found Qt at: %%~d
            goto :found_qt
        )
    )

    echo Qt6 installation not found. Please install Qt6 or set Qt6_DIR manually.
    pause
    exit /b 1
)

:found_qt
echo Using Qt6_DIR: %Qt6_DIR%
set "CMAKE_PREFIX_PATH=%Qt6_DIR%\..\..\.."

echo Configuring CMake...
if not exist build mkdir build
cd build

cmake .. -DQt6_DIR="%Qt6_DIR%" -DOpenCV_DIR="%OpenCV_DIR%" -G "Visual Studio 17 2022"
if errorlevel 1 (
    echo CMake configuration failed
    pause
    exit /b 1
)

echo Building project...
cmake --build . --config Release
if errorlevel 1 (
    echo Build failed
    pause
    exit /b 1
)

echo Build completed successfully
echo Executable location: %CD%\bin\QtBarcodeScanner.exe

echo To run the application:
echo   cd %CD%\bin
echo   QtBarcodeScanner.exe

pause
Enter fullscreen mode

Exit fullscreen mode



Source Code

https://github.com/yushulx/cmake-cpp-barcode-qrcode-mrz/tree/main/examples/qt_barcode_scanner



Source link

Leave a Reply

Your email address will not be published. Required fields are marked *