From: tobtoht Date: Tue, 27 Dec 2022 15:52:06 +0000 (+0100) Subject: Enable webcam QR scanner X-Git-Url: https://git.nutra.tk/v2?a=commitdiff_plain;h=23d4b0cd1c33d5de72d64b10d974075ca2309fa7;p=gamesguru%2Ffeather.git Enable webcam QR scanner --- diff --git a/CMakeLists.txt b/CMakeLists.txt index 4df48ee9..729d81b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -25,9 +25,7 @@ option(CHECK_UPDATES "Enable checking for application updates" OFF) option(PLATFORM_INSTALLER "Built-in updater fetches installer (windows-only)" OFF) option(USE_DEVICE_TREZOR "Trezor support compilation" ON) option(DONATE_BEG "Prompt donation window every once in a while" ON) - - -option(WITH_SCANNER "Enable webcam QR scanner" OFF) +option(WITH_SCANNER "Enable webcam QR scanner" ON) list(INSERT CMAKE_MODULE_PATH 0 "${CMAKE_SOURCE_DIR}/cmake") include(CheckCCompilerFlag) diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index fb21e401..dac430dc 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -69,8 +69,8 @@ file(GLOB SOURCE_FILES "polyseed/*.h" "polyseed/*.cpp" "polyseed/*.c" - "qrcode_scanner/QrCodeUtils.cpp" - "qrcode_scanner/QrCodeUtils.h" + "qrcode_utils/QrCodeUtils.cpp" + "qrcode_utils/QrCodeUtils.h" "monero_seed/argon2/blake2/*.c" "monero_seed/argon2/*.c" "monero_seed/*.cpp" @@ -175,7 +175,7 @@ if(XMRIG) target_compile_definitions(feather PRIVATE HAS_XMRIG=1) endif() -if(WITH_SCANNER AND NOT Qt6_FOUND) +if(WITH_SCANNER) target_compile_definitions(feather PRIVATE WITH_SCANNER=1) endif() diff --git a/src/SendWidget.cpp b/src/SendWidget.cpp index a8d1082d..570ad8c4 100644 --- a/src/SendWidget.cpp +++ b/src/SendWidget.cpp @@ -14,6 +14,9 @@ #if defined(WITH_SCANNER) && QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #include "qrcode_scanner/QrCodeScanDialog.h" #include +#elif defined(WITH_SCANNER) +#include "qrcode_scanner_qt6/QrCodeScanDialog.h" +#include #endif SendWidget::SendWidget(QSharedPointer ctx, QWidget *parent) @@ -123,6 +126,17 @@ void SendWidget::scanClicked() { dialog->exec(); ui->lineAddress->setText(dialog->decodedString); dialog->deleteLater(); +#elif defined(WITH_SCANNER) + auto cameras = QMediaDevices::videoInputs(); + if (cameras.empty()) { + QMessageBox::warning(this, "QR code scanner", "No available cameras found."); + return; + } + + auto dialog = new QrCodeScanDialog(this); + dialog->exec(); + ui->lineAddress->setText(dialog->decodedString); + dialog->deleteLater(); #else QMessageBox::warning(this, "QR scanner", "Feather was built without webcam QR scanner support."); #endif diff --git a/src/qrcode_scanner_qt6/QrCodeScanDialog.cpp b/src/qrcode_scanner_qt6/QrCodeScanDialog.cpp index 989c9fce..75b04f6d 100644 --- a/src/qrcode_scanner_qt6/QrCodeScanDialog.cpp +++ b/src/qrcode_scanner_qt6/QrCodeScanDialog.cpp @@ -7,23 +7,99 @@ #include #include #include +#include +#include +#include QrCodeScanDialog::QrCodeScanDialog(QWidget *parent) : QDialog(parent) , ui(new Ui::QrCodeScanDialog) { ui->setupUi(this); + this->setWindowTitle("Scan QR code"); - m_camera.reset(new QCamera(QMediaDevices::defaultVideoInput())); - m_captureSession.setCamera(m_camera.data()); + QPixmap pixmap = QPixmap(":/assets/images/warning.png"); + ui->icon_warning->setPixmap(pixmap.scaledToWidth(32, Qt::SmoothTransformation)); + + const QList cameras = QMediaDevices::videoInputs(); + for (const auto &camera : cameras) { + ui->combo_camera->addItem(camera.description()); + } + + connect(ui->combo_camera, QOverload::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 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) { + QMessageBox::warning(this, tr("Camera Error"), m_camera->errorString()); + } +} - const QList availableCameras = QMediaDevices::videoInputs(); +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(); + } } \ No newline at end of file diff --git a/src/qrcode_scanner_qt6/QrCodeScanDialog.h b/src/qrcode_scanner_qt6/QrCodeScanDialog.h index 8ea94ff4..fdcc0d73 100644 --- a/src/qrcode_scanner_qt6/QrCodeScanDialog.h +++ b/src/qrcode_scanner_qt6/QrCodeScanDialog.h @@ -8,6 +8,9 @@ #include #include #include +#include + +#include "QrScanThread.h" namespace Ui { class QrCodeScanDialog; @@ -21,9 +24,23 @@ 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; + QrScanThread *m_thread; + QImageCapture *m_imageCapture; + QTimer m_imageTimer; QScopedPointer m_camera; QMediaCaptureSession m_captureSession; }; diff --git a/src/qrcode_scanner_qt6/QrCodeScanDialog.ui b/src/qrcode_scanner_qt6/QrCodeScanDialog.ui index 0f371e47..9cda9994 100644 --- a/src/qrcode_scanner_qt6/QrCodeScanDialog.ui +++ b/src/qrcode_scanner_qt6/QrCodeScanDialog.ui @@ -6,8 +6,8 @@ 0 0 - 816 - 688 + 490 + 422 @@ -15,18 +15,86 @@ - + + + + 0 + 0 + + + - - - Qt::Horizontal + + + QFrame::StyledPanel - - QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + QFrame::Raised + + + + + + 0 + 0 + + + + icon + + + + + + + Qt::Horizontal + + + QSizePolicy::Preferred + + + + 55 + 0 + + + + + + + + Lost connection to camera. Please restart scan dialog. + + + true + + + + + + + + + + + 0 + 0 + + + + Camera: + + + + + + + + @@ -38,38 +106,5 @@ - - - buttonBox - accepted() - QrCodeScanDialog - accept() - - - 248 - 254 - - - 157 - 274 - - - - - buttonBox - rejected() - QrCodeScanDialog - reject() - - - 316 - 260 - - - 286 - 274 - - - - + diff --git a/src/qrcode_scanner_qt6/QrScanThread.cpp b/src/qrcode_scanner_qt6/QrScanThread.cpp new file mode 100644 index 00000000..3b3db0a4 --- /dev/null +++ b/src/qrcode_scanner_qt6/QrScanThread.cpp @@ -0,0 +1,96 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2022 The Monero Project + +#include "QrScanThread.h" +#include +#include + +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(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()); + } + } +} \ No newline at end of file diff --git a/src/qrcode_scanner_qt6/QrScanThread.h b/src/qrcode_scanner_qt6/QrScanThread.h new file mode 100644 index 00000000..16bee3b1 --- /dev/null +++ b/src/qrcode_scanner_qt6/QrScanThread.h @@ -0,0 +1,42 @@ +// SPDX-License-Identifier: BSD-3-Clause +// SPDX-FileCopyrightText: 2020-2022 The Monero Project + +#ifndef _QRSCANTHREAD_H_ +#define _QRSCANTHREAD_H_ + +#include +#include +#include +#include +#include +#include + +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 m_image; + bool m_running; + QMutex m_mutex; + QWaitCondition m_waitCondition; + QList m_queue; +}; +#endif \ No newline at end of file diff --git a/src/qrcode_scanner/QrCodeUtils.cpp b/src/qrcode_utils/QrCodeUtils.cpp similarity index 100% rename from src/qrcode_scanner/QrCodeUtils.cpp rename to src/qrcode_utils/QrCodeUtils.cpp diff --git a/src/qrcode_scanner/QrCodeUtils.h b/src/qrcode_utils/QrCodeUtils.h similarity index 100% rename from src/qrcode_scanner/QrCodeUtils.h rename to src/qrcode_utils/QrCodeUtils.h diff --git a/src/widgets/PayToEdit.cpp b/src/widgets/PayToEdit.cpp index 2c46674d..6ceb8ea0 100644 --- a/src/widgets/PayToEdit.cpp +++ b/src/widgets/PayToEdit.cpp @@ -12,7 +12,7 @@ #include "libwalletqt/WalletManager.h" #include "model/ModelUtils.h" -#include "qrcode_scanner/QrCodeUtils.h" +#include "qrcode_utils/QrCodeUtils.h" PayToEdit::PayToEdit(QWidget *parent) : QPlainTextEdit(parent) {