scanner: replace zbar with zxing

This commit is contained in:
tobtoht 2023-10-10 14:21:11 +02:00
parent 0ef54aa4c8
commit 8a634dae13
No known key found for this signature in database
GPG key ID: E45B10DD027D2472
22 changed files with 231 additions and 722 deletions

View file

@ -92,11 +92,9 @@ if(Polyseed_SUBMODULE)
add_subdirectory(src/third-party/polyseed EXCLUDE_FROM_ALL)
endif()
# ZBAR
# ZXing
if(WITH_SCANNER)
find_package(ZBAR REQUIRED)
message(STATUS "libzbar: include dir at ${ZBAR_INCLUDE_DIR}")
message(STATUS "libzbar: libraries at ${ZBAR_LIBRARIES}")
find_package(ZXing REQUIRED)
endif()
# libzip

View file

@ -1,21 +0,0 @@
find_package(PkgConfig)
if(PkgConfig_FOUND)
pkg_check_modules(PC_ZBAR QUIET zbar)
if(PC_ZBAR_FOUND)
set(ZBAR_DEFINITIONS ${PC_ZBAR_CFLAGS_OTHER})
find_library(ZBAR_LIBRARIES NAMES zbar HINTS ${PC_ZBAR_LIBDIR} ${PC_ZBAR_LIBRARY_DIRS})
find_path(ZBAR_INCLUDE_DIR Decoder.h HINTS ${PC_ZBAR_INCLUDEDIR} ${PC_ZBAR_INCLUDE_DIRS})
endif()
endif()
if(NOT ZBAR_INCLUDE_DIR)
find_path(ZBAR_H_PATH zbar.h)
if(ZBAR_H_PATH)
set(ZBAR_INCLUDE_DIR "${ZBAR_H_PATH}")
endif()
endif()
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ZBAR DEFAULT_MSG ZBAR_LIBRARIES ZBAR_INCLUDE_DIR)
message(STATUS "Found zbar libraries ${ZBAR_LIBRARIES}")

55
cmake/FindZXing.cmake Normal file
View file

@ -0,0 +1,55 @@
############################################################################
# FindZxing.txt
# Copyright (C) 2018 Belledonne Communications, Grenoble France
#
############################################################################
#
# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
#
############################################################################
#
# - Find the zxing include file and library
#
# ZXING_FOUND - system has zxing
# ZXING_INCLUDE_DIRS - the zxing include directory
# ZXING_LIBRARIES - The libraries needed to use zxing
find_path(ZXING_INCLUDE_DIRS
NAMES
ZXing/BarcodeFormat.h
ZXing/BitHacks.h
ZXing/ByteArray.h
ZXing/CharacterSet.h
ZXing/Flags.h
ZXing/GTIN.h
ZXing/TextUtfEncoding.h
ZXing/ZXAlgorithms.h
ZXing/ZXConfig.h
PATH_SUFFIXES include
)
find_library(ZXING_LIBRARIES
NAMES ZXing libZXing
PATH_SUFFIXES Frameworks bin lib lib64
)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(ZXing
DEFAULT_MSG
ZXING_INCLUDE_DIRS ZXING_LIBRARIES
)
mark_as_advanced(ZXING_INCLUDE_DIRS ZXING_LIBRARIES)

View file

@ -1,4 +1,4 @@
packages := boost openssl libiconv unbound qrencode zbar sodium polyseed hidapi protobuf libusb zlib libgpg-error libgcrypt expat libzip bc-ur quirc
packages := boost openssl libiconv unbound qrencode sodium polyseed hidapi protobuf libusb zlib libgpg-error libgcrypt expat libzip bc-ur zxing-cpp
native_packages := native_qt native_protobuf
linux_packages := eudev libfuse libsquashfuse zstd appimage_runtime

View file

@ -1,22 +0,0 @@
package=quirc
$(package)_version=1.2
$(package)_download_path=https://github.com/dlbeer/quirc/archive/refs/tags/
$(package)_file_name=v1.2.tar.gz
$(package)_sha256_hash=73c12ea33d337ec38fb81218c7674f57dba7ec0570bddd5c7f7a977c0deb64c5
$(package)_patches += CMakeLists.txt
define $(package)_preprocess_cmds
cp $($(package)_patch_dir)/CMakeLists.txt CMakeLists.txt
endef
define $(package)_config_cmds
$($(package)_cmake) -DCMAKE_INSTALL_PREFIX=$(host_prefix) .
endef
define $(package)_build_cmds
$(MAKE)
endef
define $(package)_stage_cmds
$(MAKE) DESTDIR=$($(package)_staging_dir) install
endef

View file

@ -1,32 +0,0 @@
package=zbar
$(package)_version=0.23.90
$(package)_download_path=https://github.com/mchehab/zbar/archive/refs/tags/
$(package)_download_file=$($(package)_version).tar.gz
$(package)_file_name=$(package)-$($(package)_version).tar.gz
$(package)_sha256_hash=25fdd6726d5c4c6f95c95d37591bfbb2dde63d13d0b10cb1350923ea8b11963b
$(package)_dependencies=libiconv
define $(package)_set_vars
$(package)_cflags+=-fPIE
$(package)_cxxflags+=-fPIE
endef
define $(package)_preprocess_cmds
autoreconf -vfi
endef
define $(package)_set_vars
$(package)_config_opts=--prefix=$(host_prefix) --disable-shared --without-imagemagick --disable-video --without-xv --with-gtk=no --with-python=no --enable-doc=no --host=$(host)
endef
define $(package)_config_cmds
$($(package)_autoconf) $($(package)_config_opts)
endef
define $(package)_build_cmds
$(MAKE) $($(package)_build_opts)
endef
define $(package)_stage_cmds
$(MAKE) DESTDIR=$($(package)_staging_dir) install
endef

View file

@ -0,0 +1,22 @@
package=zxing-cpp
$(package)_version=2.1.0
$(package)_download_path=https://github.com/$(package)/$(package)/archive/refs/tags
$(package)_download_file=v$($(package)_version).tar.gz
$(package)_file_name=$(package)-$($(package)_version).tar.gz
$(package)_sha256_hash=6d54e403592ec7a143791c6526c1baafddf4c0897bb49b1af72b70a0f0c4a3fe
define $(package)_set_vars
$(package)_config_opts=-DBUILD_WRITERS=OFF -DBUILD_EXAMPLES=OFF -DBUILD_SHARED_LIBS=OFF
endef
define $(package)_config_cmds
$($(package)_cmake) .
endef
define $(package)_build_cmds
$(MAKE)
endef
define $(package)_stage_cmds
$(MAKE) DESTDIR=$($(package)_staging_dir) install
endef

View file

@ -1,18 +0,0 @@
cmake_minimum_required(VERSION 3.5)
project(quirc)
add_library(quirc STATIC
lib/decode.c
lib/identify.c
lib/quirc.c
lib/version_db.c
)
target_include_directories(quirc PUBLIC lib)
install(TARGETS quirc)
install(FILES
lib/quirc.h
DESTINATION include/quirc)

View file

@ -85,18 +85,12 @@ if (WITH_SCANNER)
"qrcode/utils/*.cpp")
endif()
if (WITH_SCANNER AND NOT Qt6_FOUND)
if (WITH_SCANNER AND Qt6_FOUND)
file(GLOB SCANNER_FILES
"qrcode/scanner/*.h"
"qrcode/scanner/*.cpp")
endif()
if (WITH_SCANNER AND Qt6_FOUND)
file(GLOB SCANNER_FILES
"qrcode/scanner_qt6/*.h"
"qrcode/scanner_qt6/*.cpp")
endif()
list(APPEND SOURCE_FILES
${UPDATER_FILES}
${QRCODE_UTILS_FILES}
@ -161,9 +155,9 @@ target_include_directories(feather PUBLIC
if(WITH_SCANNER)
target_include_directories(feather PUBLIC
${ZBAR_INCLUDE_DIR}
${QtMultimedia_INCLUDE_DIRS}
${QtMultimediaWidgets_INCLUDE_DIRS}
${ZXING_INCLUDE_DIRS}
)
endif()
@ -191,7 +185,7 @@ if(XMRIG)
target_compile_definitions(feather PRIVATE HAS_XMRIG=1)
endif()
if(WITH_SCANNER)
if(WITH_SCANNER AND Qt6_FOUND)
target_compile_definitions(feather PRIVATE WITH_SCANNER=1)
endif()
@ -281,9 +275,9 @@ endif()
if (WITH_SCANNER)
target_link_libraries(feather
${ZBAR_LIBRARIES}
Qt::Multimedia
Qt::MultimediaWidgets
${ZXING_LIBRARIES}
)
endif()

View file

@ -13,11 +13,8 @@
#include "Icons.h"
#include "libwalletqt/WalletManager.h"
#if defined(WITH_SCANNER) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
#if defined(WITH_SCANNER)
#include "qrcode/scanner/QrCodeScanDialog.h"
#include <QtMultimedia/QCameraInfo>
#elif defined(WITH_SCANNER)
#include "qrcode/scanner_qt6//QrCodeScanDialog.h"
#include <QMediaDevices>
#endif

View file

@ -4,112 +4,94 @@
#include "QrCodeScanDialog.h"
#include "ui_QrCodeScanDialog.h"
#include <QCamera>
#include <QMediaDevices>
#include <QCameraDevice>
#include <QMessageBox>
#include <QtMultimedia/QCamera>
#include <QtMultimedia/QCameraInfo>
#include <QImageCapture>
#include <QVideoFrame>
#include "Utils.h"
QrCodeScanDialog::QrCodeScanDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::QrCodeScanDialog)
: QDialog(parent)
, ui(new Ui::QrCodeScanDialog)
, m_sink(new QVideoSink(this))
{
ui->setupUi(this);
this->setWindowTitle("Scan QR Code");
this->setWindowTitle("Scan QR code");
QPixmap pixmap = QPixmap(":/assets/images/warning.png");
ui->icon_warning->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
m_cameras = QCameraInfo::availableCameras();
for (const auto &camera : m_cameras) {
#ifdef Q_OS_WIN
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
for (const auto &camera : cameras) {
ui->combo_camera->addItem(camera.description());
#else
ui->combo_camera->addItem(camera.deviceName());
#endif
}
connect(ui->combo_camera, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &QrCodeScanDialog::onCameraSwitched);
connect(ui->viewfinder->videoSink(), &QVideoSink::videoFrameChanged, this, &QrCodeScanDialog::handleFrameCaptured);
this->onCameraSwitched(0);
m_thread = new QrScanThread(this);
m_thread->start();
connect(m_thread, &QrScanThread::decoded, this, &QrCodeScanDialog::onDecoded);
connect(m_thread, &QrScanThread::notifyError, this, &QrCodeScanDialog::notifyError);
connect(&m_imageTimer, &QTimer::timeout, this, &QrCodeScanDialog::takeImage);
m_imageTimer.start(500);
}
void QrCodeScanDialog::onCameraSwitched(int index) {
if (index >= m_cameras.size()) {
return;
}
m_camera.reset(new QCamera(m_cameras.at(index)));
auto captureMode = QCamera::CaptureStillImage;
if (m_camera->isCaptureModeSupported(captureMode)) {
m_camera->setCaptureMode(captureMode);
}
connect(m_camera.data(), QOverload<QCamera::Error>::of(&QCamera::error), this, &QrCodeScanDialog::displayCameraError);
connect(m_camera.data(), &QCamera::statusChanged, [this](QCamera::Status status){
bool unloaded = (status == QCamera::Status::UnloadedStatus);
ui->frame_unavailable->setVisible(unloaded);
});
m_imageCapture.reset(new QCameraImageCapture(m_camera.data()));
if (!m_imageCapture->isCaptureDestinationSupported(QCameraImageCapture::CaptureToBuffer)) {
qDebug() << "Capture to buffer is NOT supported";
}
m_imageCapture->setCaptureDestination(QCameraImageCapture::CaptureToBuffer);
connect(m_imageCapture.data(), &QCameraImageCapture::imageAvailable, this, &QrCodeScanDialog::processAvailableImage);
connect(m_imageCapture.data(), QOverload<int, QCameraImageCapture::Error, const QString &>::of(&QCameraImageCapture::error),
this, &QrCodeScanDialog::displayCaptureError);
m_camera->setViewfinder(ui->viewfinder);
m_camera->start();
}
void QrCodeScanDialog::displayCaptureError(int id, const QCameraImageCapture::Error error, const QString &errorString)
{
Q_UNUSED(id);
Q_UNUSED(error);
QMessageBox::warning(this, "Image Capture Error", errorString);
}
void QrCodeScanDialog::displayCameraError()
{
QMessageBox::warning(this, "Camera Error", m_camera->errorString());
}
void QrCodeScanDialog::processAvailableImage(int id, const QVideoFrame &frame) {
Q_UNUSED(id);
QImage img = frame.image();
img.convertTo(QImage::Format_RGB32);
void QrCodeScanDialog::handleFrameCaptured(const QVideoFrame &frame) {
QImage img = this->videoFrameToImage(frame);
m_thread->addImage(img);
}
void QrCodeScanDialog::takeImage()
QImage QrCodeScanDialog::videoFrameToImage(const QVideoFrame &videoFrame)
{
if (m_imageCapture->isReadyForCapture()) {
m_imageCapture->capture();
auto handleType = videoFrame.handleType();
if (handleType == QVideoFrame::NoHandle) {
QImage image = videoFrame.toImage();
if (image.isNull()) {
return {};
}
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
return image.copy();
}
return {};
}
void QrCodeScanDialog::onDecoded(int type, const QString &data) {
void QrCodeScanDialog::onCameraSwitched(int index) {
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
if (index >= cameras.size()) {
return;
}
m_camera.reset(new QCamera(cameras.at(index)));
m_captureSession.setCamera(m_camera.data());
m_captureSession.setVideoOutput(ui->viewfinder);
connect(m_camera.data(), &QCamera::activeChanged, [this](bool active){
ui->frame_unavailable->setVisible(!active);
});
m_camera->start();
}
void QrCodeScanDialog::onDecoded(const QString &data) {
decodedString = data;
this->accept();
}
void QrCodeScanDialog::notifyError(const QString &msg) {
qDebug() << "QrScanner error: " << msg;
}
QrCodeScanDialog::~QrCodeScanDialog()
{
m_thread->stop();

View file

@ -6,9 +6,10 @@
#include <QDialog>
#include <QCamera>
#include <QCameraImageCapture>
#include <QScopedPointer>
#include <QMediaCaptureSession>
#include <QTimer>
#include <QVideoFrame>
#include <QVideoSink>
#include "QrScanThread.h"
@ -28,23 +29,19 @@ public:
private slots:
void onCameraSwitched(int index);
void onDecoded(int type, const QString &data);
void notifyError(const QString &msg);
void onDecoded(const QString &data);
private:
void processAvailableImage(int id, const QVideoFrame &frame);
void displayCaptureError(int, QCameraImageCapture::Error, const QString &errorString);
void displayCameraError();
void takeImage();
QImage videoFrameToImage(const QVideoFrame &videoFrame);
void handleFrameCaptured(const QVideoFrame &videoFrame);
QScopedPointer<Ui::QrCodeScanDialog> ui;
QScopedPointer<QCamera> m_camera;
QScopedPointer<QCameraImageCapture> m_imageCapture;
QrScanThread *m_thread;
QTimer m_imageTimer;
QList<QCameraInfo> m_cameras;
QScopedPointer<QCamera> m_camera;
QMediaCaptureSession m_captureSession;
QVideoSink m_sink;
};
#endif //FEATHER_QRCODESCANDIALOG_H

View file

@ -6,8 +6,8 @@
<rect>
<x>0</x>
<y>0</y>
<width>400</width>
<height>300</height>
<width>490</width>
<height>422</height>
</rect>
</property>
<property name="windowTitle">
@ -15,7 +15,7 @@
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QCameraViewfinder" name="viewfinder" native="true">
<widget class="QVideoWidget" name="viewfinder" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
@ -32,7 +32,7 @@
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="icon_warning">
<property name="sizePolicy">
@ -56,14 +56,14 @@
</property>
<property name="sizeHint" stdset="0">
<size>
<width>10</width>
<width>55</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Lost connection to camera. Please restart scan dialog.</string>
</property>
@ -99,9 +99,9 @@
</widget>
<customwidgets>
<customwidget>
<class>QCameraViewfinder</class>
<class>QVideoWidget</class>
<extends>QWidget</extends>
<header>qcameraviewfinder.h</header>
<header>qvideowidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>

View file

@ -2,70 +2,27 @@
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "QrScanThread.h"
#include <QtGlobal>
#include <QDebug>
#include <ZXing/ReadBarcode.h>
QrScanThread::QrScanThread(QObject *parent)
: QThread(parent)
, m_running(true)
{
m_scanner.set_handler(*this);
}
void QrScanThread::image_callback(zbar::Image &image)
{
qDebug() << "image_callback : Found Code ! " ;
for (zbar::Image::SymbolIterator sym = image.symbol_begin(); sym != image.symbol_end(); ++sym) {
if (!sym->get_count()) {
QString data = QString::fromStdString(sym->get_data());
emit decoded(sym->get_type(), data);
}
}
}
void QrScanThread::processZImage(zbar::Image &image)
{
m_scanner.recycle_image(image);
zbar::Image tmp = image.convert(zbar_fourcc('Y', '8', '0', '0'));
m_scanner.scan(tmp);
image.set_symbols(tmp.get_symbols());
}
bool QrScanThread::zimageFromQImage(const QImage &qimg, zbar::Image &dst)
{
switch (qimg.format()) {
case QImage::Format_RGB32 :
case QImage::Format_ARGB32 :
case QImage::Format_ARGB32_Premultiplied :
break;
default :
qDebug() << "Format: " << qimg.format();
emit notifyError(QString("Invalid QImage Format !"));
return false;
}
unsigned int bpl( qimg.bytesPerLine() ), width( bpl / 4), height( qimg.height());
dst.set_size(width, height);
dst.set_format("BGR4");
unsigned long datalen = qimg.sizeInBytes();
dst.set_data(qimg.bits(), datalen);
if((width * 4 != bpl) || (width * height * 4 > datalen)){
emit notifyError(QString("QImage to Zbar::Image failed !"));
return false;
}
return true;
}
void QrScanThread::processQImage(const QImage &qimg)
{
try {
m_image = QSharedPointer<zbar::Image>(new zbar::Image());
if (!zimageFromQImage(qimg, *m_image))
return;
processZImage(*m_image);
}
catch(std::exception &e) {
qDebug() << "ERROR: " << e.what();
emit notifyError(e.what());
const auto hints = ZXing::DecodeHints()
.setFormats(ZXing::BarcodeFormat::QRCode | ZXing::BarcodeFormat::DataMatrix)
.setTryHarder(true)
.setBinarizer(ZXing::Binarizer::FixedThreshold);
const auto result = QrCodeUtils::ReadBarcode(qimg, hints);
if (result.isValid()) {
emit decoded(result.text());
}
}

View file

@ -1,39 +1,36 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef _QRSCANTHREAD_H_
#define _QRSCANTHREAD_H_
#ifndef QRSCANTHREAD_H_
#define QRSCANTHREAD_H_
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QEvent>
#include <QCamera>
#include <zbar.h>
class QrScanThread : public QThread, public zbar::Image::Handler
#include <ZXing/ReadBarcode.h>
#include "qrcode/utils/QrCodeUtils.h"
class QrScanThread : public QThread
{
Q_OBJECT
public:
QrScanThread(QObject *parent = nullptr);
explicit QrScanThread(QObject *parent = nullptr);
void addImage(const QImage &img);
virtual void stop();
signals:
void decoded(int type, const QString &data);
void notifyError(const QString &error, bool warning = false);
void decoded(const QString &data);
protected:
virtual void run();
void run() override;
void processQImage(const QImage &);
void processZImage(zbar::Image &image);
virtual void image_callback(zbar::Image &image);
bool zimageFromQImage(const QImage&, zbar::Image &);
private:
zbar::ImageScanner m_scanner;
QSharedPointer<zbar::Image> m_image;
bool m_running;
QMutex m_mutex;
QWaitCondition m_waitCondition;

View file

@ -1,107 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "QrCodeScanDialog.h"
#include "ui_QrCodeScanDialog.h"
#include <QCamera>
#include <QMediaDevices>
#include <QCameraDevice>
#include <QMessageBox>
#include <QImageCapture>
#include <QVideoFrame>
#include "Utils.h"
QrCodeScanDialog::QrCodeScanDialog(QWidget *parent)
: QDialog(parent)
, ui(new Ui::QrCodeScanDialog)
{
ui->setupUi(this);
this->setWindowTitle("Scan QR code");
QPixmap pixmap = QPixmap(":/assets/images/warning.png");
ui->icon_warning->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation));
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
for (const auto &camera : cameras) {
ui->combo_camera->addItem(camera.description());
}
connect(ui->combo_camera, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &QrCodeScanDialog::onCameraSwitched);
this->onCameraSwitched(0);
m_thread = new QrScanThread(this);
m_thread->start();
connect(m_thread, &QrScanThread::decoded, this, &QrCodeScanDialog::onDecoded);
connect(m_thread, &QrScanThread::notifyError, this, &QrCodeScanDialog::notifyError);
connect(&m_imageTimer, &QTimer::timeout, this, &QrCodeScanDialog::takeImage);
m_imageTimer.start(500);
}
void QrCodeScanDialog::onCameraSwitched(int index) {
const QList<QCameraDevice> cameras = QMediaDevices::videoInputs();
if (index >= cameras.size()) {
return;
}
m_camera.reset(new QCamera(cameras.at(index)));
m_captureSession.setCamera(m_camera.data());
m_captureSession.setVideoOutput(ui->viewfinder);
m_imageCapture = new QImageCapture;
m_captureSession.setImageCapture(m_imageCapture);
connect(m_imageCapture, &QImageCapture::imageCaptured, this, &QrCodeScanDialog::processCapturedImage);
connect(m_camera.data(), &QCamera::errorOccurred, this, &QrCodeScanDialog::displayCameraError);
connect(m_camera.data(), &QCamera::activeChanged, [this](bool active){
ui->frame_unavailable->setVisible(!active);
});
m_camera->start();
}
void QrCodeScanDialog::processCapturedImage(int requestId, const QImage& img) {
Q_UNUSED(requestId);
QImage image{img};
image.convertTo(QImage::Format_RGB32);
m_thread->addImage(image);
}
void QrCodeScanDialog::takeImage()
{
if (m_imageCapture->isReadyForCapture()) {
m_imageCapture->capture();
}
}
void QrCodeScanDialog::onDecoded(int type, const QString &data) {
decodedString = data;
this->accept();
}
void QrCodeScanDialog::displayCameraError()
{
if (m_camera->error() != QCamera::NoError) {
Utils::showError(this, "Camera error", m_camera->errorString());
}
}
void QrCodeScanDialog::notifyError(const QString &msg) {
qDebug() << "QrScanner error: " << msg;
}
QrCodeScanDialog::~QrCodeScanDialog()
{
m_thread->stop();
m_thread->quit();
if (!m_thread->wait(5000))
{
m_thread->terminate();
m_thread->wait();
}
}

View file

@ -1,49 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef FEATHER_QRCODESCANDIALOG_H
#define FEATHER_QRCODESCANDIALOG_H
#include <QDialog>
#include <QCamera>
#include <QScopedPointer>
#include <QMediaCaptureSession>
#include <QTimer>
#include "QrScanThread.h"
namespace Ui {
class QrCodeScanDialog;
}
class QrCodeScanDialog : public QDialog
{
Q_OBJECT
public:
explicit QrCodeScanDialog(QWidget *parent);
~QrCodeScanDialog() override;
QString decodedString = "";
private slots:
void onCameraSwitched(int index);
void onDecoded(int type, const QString &data);
void notifyError(const QString &msg);
private:
void processCapturedImage(int requestId, const QImage& img);
void displayCameraError();
void takeImage();
QScopedPointer<Ui::QrCodeScanDialog> ui;
QrScanThread *m_thread;
QImageCapture *m_imageCapture;
QTimer m_imageTimer;
QScopedPointer<QCamera> m_camera;
QMediaCaptureSession m_captureSession;
};
#endif //FEATHER_QRCODESCANDIALOG_H

View file

@ -1,110 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>QrCodeScanDialog</class>
<widget class="QDialog" name="QrCodeScanDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>490</width>
<height>422</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QVideoWidget" name="viewfinder" native="true">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Expanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QFrame" name="frame_unavailable">
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QLabel" name="icon_warning">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>icon</string>
</property>
</widget>
</item>
<item>
<spacer name="horizontalSpacer">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="sizeType">
<enum>QSizePolicy::Preferred</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>55</width>
<height>0</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QLabel" name="label_2">
<property name="text">
<string>Lost connection to camera. Please restart scan dialog.</string>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_3">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="text">
<string>Camera:</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="combo_camera"/>
</item>
</layout>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>QVideoWidget</class>
<extends>QWidget</extends>
<header>qvideowidget.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<resources/>
<connections/>
</ui>

View file

@ -1,96 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "QrScanThread.h"
#include <QtGlobal>
#include <QDebug>
QrScanThread::QrScanThread(QObject *parent)
: QThread(parent)
, m_running(true)
{
m_scanner.set_handler(*this);
}
void QrScanThread::image_callback(zbar::Image &image)
{
qDebug() << "image_callback : Found Code ! " ;
for (zbar::Image::SymbolIterator sym = image.symbol_begin(); sym != image.symbol_end(); ++sym) {
if (!sym->get_count()) {
QString data = QString::fromStdString(sym->get_data());
emit decoded(sym->get_type(), data);
}
}
}
void QrScanThread::processZImage(zbar::Image &image)
{
m_scanner.recycle_image(image);
zbar::Image tmp = image.convert(zbar_fourcc('Y', '8', '0', '0'));
m_scanner.scan(tmp);
image.set_symbols(tmp.get_symbols());
}
bool QrScanThread::zimageFromQImage(const QImage &qimg, zbar::Image &dst)
{
switch (qimg.format()) {
case QImage::Format_RGB32 :
case QImage::Format_ARGB32 :
case QImage::Format_ARGB32_Premultiplied :
break;
default :
qDebug() << "Format: " << qimg.format();
emit notifyError(QString("Invalid QImage Format !"));
return false;
}
unsigned int bpl( qimg.bytesPerLine() ), width( bpl / 4), height( qimg.height());
dst.set_size(width, height);
dst.set_format("BGR4");
unsigned long datalen = qimg.sizeInBytes();
dst.set_data(qimg.bits(), datalen);
if((width * 4 != bpl) || (width * height * 4 > datalen)){
emit notifyError(QString("QImage to Zbar::Image failed !"));
return false;
}
return true;
}
void QrScanThread::processQImage(const QImage &qimg)
{
try {
m_image = QSharedPointer<zbar::Image>(new zbar::Image());
if (!zimageFromQImage(qimg, *m_image))
return;
processZImage(*m_image);
}
catch(std::exception &e) {
qDebug() << "ERROR: " << e.what();
emit notifyError(e.what());
}
}
void QrScanThread::stop()
{
m_running = false;
m_waitCondition.wakeOne();
}
void QrScanThread::addImage(const QImage &img)
{
QMutexLocker locker(&m_mutex);
m_queue.append(img);
m_waitCondition.wakeOne();
}
void QrScanThread::run()
{
while (m_running) {
QMutexLocker locker(&m_mutex);
while (m_queue.isEmpty() && m_running) {
m_waitCondition.wait(&m_mutex);
}
if (!m_queue.isEmpty()) {
processQImage(m_queue.takeFirst());
}
}
}

View file

@ -1,42 +0,0 @@
// SPDX-License-Identifier: BSD-3-Clause
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#ifndef _QRSCANTHREAD_H_
#define _QRSCANTHREAD_H_
#include <QThread>
#include <QMutex>
#include <QWaitCondition>
#include <QEvent>
#include <QCamera>
#include <zbar.h>
class QrScanThread : public QThread, public zbar::Image::Handler
{
Q_OBJECT
public:
QrScanThread(QObject *parent = nullptr);
void addImage(const QImage &img);
virtual void stop();
signals:
void decoded(int type, const QString &data);
void notifyError(const QString &error, bool warning = false);
protected:
virtual void run();
void processQImage(const QImage &);
void processZImage(zbar::Image &image);
virtual void image_callback(zbar::Image &image);
bool zimageFromQImage(const QImage&, zbar::Image &);
private:
zbar::ImageScanner m_scanner;
QSharedPointer<zbar::Image> m_image;
bool m_running;
QMutex m_mutex;
QWaitCondition m_waitCondition;
QList<QImage> m_queue;
};
#endif

View file

@ -2,52 +2,46 @@
// SPDX-FileCopyrightText: 2020-2023 The Monero Project
#include "QrCodeUtils.h"
#include <QDebug>
bool QrCodeUtils::zimageFromQImage(const QImage &qImg, zbar::Image &dst) {
qDebug() << qImg.format();
switch (qImg.format()) {
case QImage::Format_RGB32 :
case QImage::Format_ARGB32 :
case QImage::Format_ARGB32_Premultiplied :
break;
default :
return false;
}
Result QrCodeUtils::ReadBarcode(const QImage& img, const ZXing::DecodeHints& hints)
{
auto ImgFmtFromQImg = [](const QImage& img){
switch (img.format()) {
case QImage::Format_ARGB32:
case QImage::Format_RGB32:
#if Q_BYTE_ORDER == Q_LITTLE_ENDIAN
return ZXing::ImageFormat::BGRX;
unsigned int bpl(qImg.bytesPerLine());
unsigned int width(bpl / 4);
unsigned int height(qImg.height());
#else
return ZXing::ImageFormat::XRGB;
dst.set_size(width, height);
dst.set_format("BGR4");
unsigned long datalen = qImg.sizeInBytes();
dst.set_data(qImg.bits(), datalen);
if ((width * 4 != bpl) || (width * height * 4 > datalen)) {
return false;
}
return true;
#endif
case QImage::Format_RGB888: return ZXing::ImageFormat::RGB;
case QImage::Format_RGBX8888:
case QImage::Format_RGBA8888: return ZXing::ImageFormat::RGBX;
case QImage::Format_Grayscale8: return ZXing::ImageFormat::Lum;
default: return ZXing::ImageFormat::None;
}
};
auto exec = [&](const QImage& img){
return Result(ZXing::ReadBarcode({ img.bits(), img.width(), img.height(), ImgFmtFromQImg(img) }, hints));
};
return ImgFmtFromQImg(img) == ZXing::ImageFormat::None ? exec(img.convertToFormat(QImage::Format_RGBX8888)) : exec(img);
}
QString QrCodeUtils::scanImage(const QImage &img) {
zbar::ImageScanner scanner;
scanner.set_config(zbar::ZBAR_NONE, zbar::ZBAR_CFG_ENABLE, 1);
const auto hints = ZXing::DecodeHints()
.setFormats(ZXing::BarcodeFormat::QRCode | ZXing::BarcodeFormat::DataMatrix)
.setTryHarder(true)
.setBinarizer(ZXing::Binarizer::FixedThreshold);
zbar::Image zImg;
int r = zimageFromQImage(img, zImg);
if (!r) {
qWarning() << "Unable to convert QImage into zbar::Image";
return "";
}
const auto result = ReadBarcode(img, hints);
zbar::Image scanImg = zImg.convert(zbar_fourcc('Y', '8', '0', '0'));
scanner.scan(scanImg);
QString result;
for (zbar::Image::SymbolIterator sym = scanImg.symbol_begin(); sym != scanImg.symbol_end(); ++sym) {
if (!sym->get_count()) {
result = QString::fromStdString(sym->get_data());
}
}
return result;
return result.text();
}

View file

@ -5,13 +5,26 @@
#define FEATHER_QRCODEUTILS_H
#include <QImage>
#include <zbar.h>
#include <ZXing/ReadBarcode.h>
class Result : private ZXing::Result
{
public:
explicit Result(ZXing::Result&& r) :
m_result(std::move(r)){ }
inline QString text() const { return QString::fromStdString(m_result.text()); }
bool isValid() const { return m_result.isValid(); }
private:
ZXing::Result m_result;
};
class QrCodeUtils {
public:
static bool zimageFromQImage(const QImage &qImg, zbar::Image &dst);
static QString scanImage(const QImage &img);
static Result ReadBarcode(const QImage& img, const ZXing::DecodeHints& hints = { });
};
#endif //FEATHER_QRCODEUTILS_H