Add back end for QR code scanning

Co-authored-by: Melvin Keskin <melvo@olomono.de>
Co-authored-by: Jonah Brüchert <jbb@kaidan.im>
master
Linus Jahn 2019-07-05 19:07:16 +02:00
parent 024a3ece88
commit 6ee280bfd6
20 changed files with 975 additions and 24 deletions

View File

@ -82,6 +82,7 @@ android:
- sudo apt -y install inkscape optipng
- GIT_EXTRA="--branch ${KF5_VERSION}" /opt/helpers/build-kde-dependencies --withProject kirigami
- /opt/helpers/build-cmake qxmpp https://github.com/qxmpp-project/qxmpp.git -DBUILD_EXAMPLES=OFF -DBUILD_TESTS=OFF
- /opt/helpers/build-cmake zxing-cpp https://github.com/nu-book/zxing-cpp.git
- GIT_EXTRA="--branch ${CI_COMMIT_REF_NAME} --recursive" /opt/helpers/build-cmake ${CI_PROJECT_NAME} ${CI_REPOSITORY_URL} -DQTANDROID_EXPORTED_TARGET=kaidan -DANDROID_APK_DIR=${CI_PROJECT_DIR}/src/${CI_PROJECT_NAME}/misc/android -DI18N=1 -DUSE_KNOTIFICATIONS=OFF
- ${CI_PROJECT_DIR}/src/${CI_PROJECT_NAME}/utils/render-logos.sh
- /opt/helpers/create-apk ${CI_PROJECT_NAME}

3
.gitmodules vendored
View File

@ -7,3 +7,6 @@
[submodule "3rdparty/qxmpp"]
path = 3rdparty/qxmpp
url = https://github.com/qxmpp-project/qxmpp
[submodule "3rdparty/zxing-cpp"]
path = 3rdparty/zxing-cpp
url = https://github.com/nu-book/zxing-cpp

1
3rdparty/zxing-cpp vendored Submodule

@ -0,0 +1 @@
Subproject commit baecef0d69b81ace79cd44035ac859bc201f1847

View File

@ -55,8 +55,9 @@ kde_enable_exceptions()
# Find packages
find_package(PkgConfig REQUIRED)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Qml Quick Svg Sql QuickControls2 Xml)
find_package(Qt5 ${QT_MIN_VERSION} REQUIRED NO_MODULE COMPONENTS Core Qml Quick Svg Sql QuickControls2 Xml Multimedia)
find_package(KF5Kirigami2 REQUIRED)
find_package(ZXing REQUIRED COMPONENTS Core)
pkg_search_module(QXmpp REQUIRED qxmpp>=1.0)
# Optional QtQuickCompiler
@ -167,6 +168,8 @@ target_link_libraries(${PROJECT_NAME}
Qt5::Svg
Qt5::Network
Qt5::Xml
Qt5::Multimedia
ZXing::Core
${__Qt5Widgets_LIBRARIES}
${__KF5Notifications_LIBRARIES}
# currently needs to be hardcoded for windows builds

18
LICENSE
View File

@ -61,6 +61,11 @@ Files: src/EmojiModel.cpp
Copyright: 2017, Konstantinos Sideris <siderisk@auth.gr>
License: GPL-3+
Files: src/QrCodeVideoFrame.h
src/QrCodeVideoFrame.cpp
Copyright: 2017, QZXing authors
License: apache-2.0
Files: data/images/message_checkmark.svg
Copyright: 2014, Michael Kurz <betheg@bitcloner.org>
License: GPL-3+
@ -169,3 +174,16 @@ License: MIT-Apple
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE.
License: apache-2.0
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
.
http://www.apache.org/licenses/LICENSE-2.0
.
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -39,10 +39,11 @@ how to do that:
### Dependencies
Here are the general dependencies of Kaidan listed:
* [Qt](https://doc.qt.io/qt-5/build-sources.html) (Core Qml Quick Svg Sql QuickControls2) (>= 5.10.0)
* [Qt](https://doc.qt.io/qt-5/build-sources.html) (Core Qml Quick Svg Sql QuickControls2 Multimedia) (>= 5.10.0)
* [QXmpp][qxmpp] (>= 1.0.0)
* [Kirigami 2](https://phabricator.kde.org/source/kirigami/) (>= 5.42.0)
* [ECM (extra-cmake-modules)](https://api.kde.org/ecm/manual/ecm.7.html)
* [ZXing-cpp](https://github.com/nu-book/zxing-cpp)
* [KNotifications][knotif] (`-DUSE_KNOTIFICATIONS=OFF` to disable)
### Build instructions

View File

@ -20,6 +20,19 @@
"-DCMAKE_INSTALL_PREFIX=../../../bin/ubuntu-touch/tmp"
],
"postbuild": "make install"
},
"zxing-cpp": {
"template": "cmake",
"src_dir": "3rdparty/zxing-cpp",
"make_jobs": 2,
"build_args": [
"-DCMAKE_BUILD_TYPE=Release",
"-DBUILD_SHARED_LIBRARY=ON",
"-DLINK_CPP_STATICALLY=OFF",
"-DCMAKE_PREFIX_PATH=../../../bin/ubuntu-touch/tmp",
"-DCMAKE_INSTALL_PREFIX=../../../bin/ubuntu-touch/tmp"
],
"postbuild": "make install"
}
}
}

View File

@ -26,6 +26,9 @@ set(KAIDAN_SOURCES
src/DownloadManager.cpp
src/QmlUtils.cpp
src/Utils.cpp
src/QrCodeDecoder
src/QrCodeScannerFilter.cpp
src/QrCodeVideoFrame.cpp
# needed to trigger moc generation / to be displayed in IDEs
src/Enums.h

View File

@ -37,7 +37,7 @@
#include <QThread>
// QXmpp
#include "qxmpp-exts/QXmppColorGenerator.h"
#include <QXmppClient.h>
#include "qxmpp-exts/QXmppUri.h"
// Kaidan
#include "AvatarFileStorage.h"
#include "Database.h"
@ -152,17 +152,19 @@ void Kaidan::mainDisconnect(bool openLogInPage)
void Kaidan::setConnectionState(QXmppClient::State state)
{
this->connectionState = static_cast<ConnectionState>(state);
emit connectionStateChanged();
if (this->connectionState != static_cast<ConnectionState>(state)) {
this->connectionState = static_cast<ConnectionState>(state);
emit connectionStateChanged();
// Open the possibly cached URI when connected.
// This is needed because the XMPP URIs can't be opened when Kaidan is not connected.
if (connectionState == ConnectionState::StateConnected && !openUriCache.isEmpty()) {
// delay is needed because sometimes the RosterPage needs to be loaded first
QTimer::singleShot(300, [=] () {
emit xmppUriReceived(openUriCache);
openUriCache = "";
});
// Open the possibly cached URI when connected.
// This is needed because the XMPP URIs can't be opened when Kaidan is not connected.
if (connectionState == ConnectionState::StateConnected && !openUriCache.isEmpty()) {
// delay is needed because sometimes the RosterPage needs to be loaded first
QTimer::singleShot(300, [=] () {
emit xmppUriReceived(openUriCache);
openUriCache = "";
});
}
}
}
@ -177,6 +179,7 @@ void Kaidan::setJid(const QString &jid)
creds.jid = jid;
// credentials were modified -> first try
creds.isFirstTry = true;
emit jidChanged();
}
void Kaidan::setJidResource(const QString &jidResource)
@ -186,6 +189,7 @@ void Kaidan::setJidResource(const QString &jidResource)
creds.jidResource = jidResource;
m_caches->settings->setValue(KAIDAN_SETTINGS_AUTH_RESOURCE, jidResource);
emit jidResourceChanged();
}
void Kaidan::setPassword(const QString &password)
@ -193,6 +197,7 @@ void Kaidan::setPassword(const QString &password)
creds.password = password;
// credentials were modified -> first try
creds.isFirstTry = true;
emit passwordChanged();
}
quint8 Kaidan::getDisconnReason() const
@ -200,22 +205,59 @@ quint8 Kaidan::getDisconnReason() const
return static_cast<quint8>(disconnReason);
}
void Kaidan::addOpenUri(const QByteArray &uri)
void Kaidan::addOpenUri(const QString &uri)
{
qDebug() << "[main]" << uri;
if (!uri.startsWith("xmpp:") || !uri.contains("@"))
if (!QXmppUri::isXmppUri(uri))
return;
if (connectionState == ConnectionState::StateConnected) {
emit xmppUriReceived(QString::fromUtf8(uri));
emit xmppUriReceived(uri);
} else {
//: The link is an XMPP-URI (i.e. 'xmpp:kaidan@muc.kaidan.im?join' for joining a chat)
emit passiveNotificationRequested(tr("The link will be opened after you have connected."));
openUriCache = QString::fromUtf8(uri);
openUriCache = uri;
}
}
void Kaidan::loginByUri(const QString &uri)
{
// input does not start with 'xmpp:'
if (!QXmppUri::isXmppUri(uri)) {
notifyLoginUriNotFound();
return;
}
// parse
QXmppUri parsedUri(uri);
// no JID provided
if (parsedUri.jid().isEmpty()) {
notifyLoginUriNotFound();
return;
}
setJid(parsedUri.jid());
// URI has no login action or no password
if (!parsedUri.hasAction(QXmppUri::Action::Login) || parsedUri.password().isEmpty()) {
// reset password
setPassword(QString());
emit passiveNotificationRequested(tr("No password found. Please enter it."));
return;
}
setPassword(parsedUri.password());
// try to connect
mainConnect();
}
void Kaidan::notifyLoginUriNotFound()
{
qWarning() << "[main]" << "No valid login URI found.";
emit passiveNotificationRequested(tr("No valid login QR code found."));
}
Kaidan *Kaidan::instance()
{
return s_instance;

View File

@ -195,7 +195,14 @@ public:
/**
* Adds XMPP URI to open as soon as possible
*/
void addOpenUri(const QByteArray &uri);
void addOpenUri(const QString &uri);
/**
* Connects to the server by the parsed credentials (bare JID and password)
* from a given XMPP URI (e.g. from scanning a QR code)
* like "xmpp:user@example.org?login;password=abc"
*/
Q_INVOKABLE void loginByUri(const QString &uri);
/**
* Returns whether an HTTP File Upload service has been found
@ -385,6 +392,11 @@ public slots:
private:
void connectDatabases();
/**
* Notifies if no login URI was found
*/
void notifyLoginUriNotFound();
QmlUtils *m_utils;
Database *m_database;
QThread *m_dbThrd;

91
src/QrCodeDecoder.cpp Normal file
View File

@ -0,0 +1,91 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#include "QrCodeDecoder.h"
// Qt
#include <QColor>
#include <QImage>
// ZXing-cpp
#include <ZXing/BarcodeFormat.h>
#include <ZXing/DecodeHints.h>
#include <ZXing/GenericLuminanceSource.h>
#include <ZXing/HybridBinarizer.h>
#include <ZXing/LuminanceSource.h>
#include <ZXing/MultiFormatReader.h>
#include <ZXing/Result.h>
#include <ZXing/TextUtfEncoding.h>
using namespace ZXing;
QrCodeDecoder::QrCodeDecoder(QObject *parent)
: QObject(parent)
{
}
void QrCodeDecoder::decodeImage(const QImage &image)
{
// options for decoding
DecodeHints decodeHints;
// Advise the decoder to also decode rotated QR codes.
decodeHints.setShouldTryRotate(true);
// Advise the decoder to only decode QR codes.
std::vector<BarcodeFormat> allowedFormats;
allowedFormats.emplace_back(BarcodeFormat::QR_CODE);
decodeHints.setPossibleFormats(allowedFormats);
MultiFormatReader reader(decodeHints);
// Create an image source to be decoded later.
GenericLuminanceSource source(
image.width(),
image.height(),
image.bits(),
image.width(),
1,
0,
1,
2
);
// Create an image source specific for decoding black data on white background.
HybridBinarizer binImage(std::shared_ptr<LuminanceSource>(&source, [](void*) {}));
// Decode the specific image source.
auto result = reader.read(binImage);
// If a QR code could be found and decoded, emit a signal with the decoded string.
// Otherwise, emit a signal for failed decoding.
if (result.isValid())
emit decodingSucceeded(QString::fromStdString(TextUtfEncoding::ToUtf8(result.text())));
else
emit decodingFailed();
}

76
src/QrCodeDecoder.h Normal file
View File

@ -0,0 +1,76 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QRCODEDECODER_H
#define QRCODEDECODER_H
#include <QObject>
/**
* Decoder for QR codes. This is a backend for \c QrCodeScanner .
*/
class QrCodeDecoder : public QObject
{
Q_OBJECT
public:
/**
* Instantiates a QR code decoder.
*
* @param parent parent object
*/
explicit QrCodeDecoder(QObject *parent = nullptr);
signals:
/**
* Emitted when the decoding failed.
*/
void decodingFailed();
/**
* Emitted when the decoding succeeded.
*
* @param tag string which was decoded by the QR code decoder
*/
void decodingSucceeded(const QString &tag);
public slots:
/**
* Tries to decode the QR code from the given image. When decoding has
* finished @c decodingFinished() will be emitted. In case a QR code was found,
* also @c tagFound() will be emitted.
*
* @param image image which may contain a QR code to decode to a string.
* It needs to be in grayscale format (one byte per pixel).
*/
void decodeImage(const QImage &image);
};
#endif // QRCODEDECODER_H

117
src/QrCodeScannerFilter.cpp Normal file
View File

@ -0,0 +1,117 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#include "QrCodeScannerFilter.h"
#include <QDebug>
#include <QtConcurrent/QtConcurrent>
QrCodeScannerFilter::QrCodeScannerFilter(QObject *parent)
: QAbstractVideoFilter(parent),
m_decoder(new QrCodeDecoder(this))
{
connect(m_decoder, &QrCodeDecoder::decodingFailed,
this, &QrCodeScannerFilter::scanningFailed);
connect(m_decoder, &QrCodeDecoder::decodingSucceeded,
this, &QrCodeScannerFilter::scanningSucceeded);
}
QrCodeScannerFilter::~QrCodeScannerFilter()
{
if (!m_processThread.isFinished()) {
m_processThread.cancel();
m_processThread.waitForFinished();
}
}
QrCodeDecoder *QrCodeScannerFilter::decoder()
{
return m_decoder;
}
QVideoFilterRunnable *QrCodeScannerFilter::createFilterRunnable()
{
return new QrCodeScannerFilterRunnable(this);
}
QrCodeScannerFilterRunnable::QrCodeScannerFilterRunnable(QrCodeScannerFilter *filter)
: QObject(nullptr),
m_filter(filter)
{
}
QVideoFrame QrCodeScannerFilterRunnable::run(
QVideoFrame *input,
const QVideoSurfaceFormat &,
RunFlags
) {
// Only one frame is processed at a time.
if (input == nullptr
|| !input->isValid()
|| !m_filter->m_processThread.isFinished()) {
return *input;
}
// Copy the data to be filtered.
m_filter->m_frame.setData(*input);
// Run a separate thread for processing the data.
m_filter->m_processThread = QtConcurrent::run(
this,
&QrCodeScannerFilterRunnable::processVideoFrameProbed,
m_filter->m_frame
);
return *input;
}
void QrCodeScannerFilterRunnable::processVideoFrameProbed(QrCodeVideoFrame &videoFrame)
{
// Return if the frame is empty.
if (videoFrame.data().length() < 1)
return;
// Create an image from the frame.
const QImage *image = videoFrame.toGrayscaleImage();
// Return if no conversion from the frame to the image failed.
if (image->isNull()) {
qDebug() << "QrCodeScannerFilterRunnable error: Cannot create image file to process.";
qDebug() << "Maybe it was a format conversion problem.";
qDebug() << "VideoFrame format:" << videoFrame.pixelFormat();
qDebug() << "Image corresponding format:"
<< QVideoFrame::imageFormatFromPixelFormat(videoFrame.pixelFormat());
return;
}
// Decode the image.
m_filter->decoder()->decodeImage(*image);
delete image;
}

117
src/QrCodeScannerFilter.h Normal file
View File

@ -0,0 +1,117 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef QRCODESCANNERFILTER_H
#define QRCODESCANNERFILTER_H
#include <QObject>
#include <QAbstractVideoFilter>
#include <QFuture>
#include <ZXing/DecodeHints.h>
#include "QrCodeDecoder.h"
#include "QrCodeVideoFrame.h"
/**
* video filter to be registered in C++, instantiated and attached in QML
*/
class QrCodeScannerFilter : public QAbstractVideoFilter
{
friend class QrCodeScannerFilterRunnable;
Q_OBJECT
Q_PROPERTY(QrCodeDecoder* decoder READ decoder)
public:
/**
* Instantiates a QR code scanner filter.
*
* @param parent parent object
*/
explicit QrCodeScannerFilter(QObject *parent = nullptr);
~QrCodeScannerFilter() override;
/**
* @return decoder for decoding a video frame
*/
QrCodeDecoder *decoder();
QVideoFilterRunnable *createFilterRunnable();
signals:
/**
* Emitted when the scanning of an image did not succeed, i.e. no valid QR code was found.
*/
void scanningFailed();
/**
* Emitted when the scanning of an image succeeded, i.e. a valid QR code was found and decoded.
*
* @param result decoded content of the QR code
*/
void scanningSucceeded(const QString& result);
private:
QrCodeDecoder *m_decoder;
/**
* frame of the video which may contain a QR code
*/
QrCodeVideoFrame m_frame;
QFuture<void> m_processThread;
};
/**
* runnable which is created everytime the filter gets a new frame
*/
class QrCodeScannerFilterRunnable : public QObject, public QVideoFilterRunnable
{
Q_OBJECT
public:
explicit QrCodeScannerFilterRunnable(QrCodeScannerFilter *m_filter);
/**
* Runs the decoding in a new thread whenever a new frame is taken by the camera.
*/
QVideoFrame run(QVideoFrame *input, const QVideoSurfaceFormat &surfaceFormat, RunFlags flags);
/**
* Converts a given frame, which may contain a QR code, to an image and then tries to decode it.
*
* @param videoFrame frame to be converted and which may contain a QR code to be decoded
*/
void processVideoFrameProbed(QrCodeVideoFrame &videoFrame);
private:
QrCodeScannerFilter *m_filter;
};
#endif // QRCODESCANNERFILTER_H

290
src/QrCodeVideoFrame.cpp Normal file
View File

@ -0,0 +1,290 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright 2017 QZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "QrCodeVideoFrame.h"
#include <QImage>
#include <cmath>
/**
* rectangle of the video frame which may contain a QR code
*/
struct CaptureRect
{
CaptureRect(const QRect& captureRect, int sourceWidth, int sourceHeight)
: isValid(captureRect.x() >= 0 && captureRect.y() >= 0 && captureRect.isValid()),
sourceWidth(sourceWidth),
sourceHeight(sourceHeight),
startX(isValid ? captureRect.x() : 0),
targetWidth(isValid ? captureRect.width() : sourceWidth),
endX(startX + targetWidth),
startY(isValid ? captureRect.y() : 0),
targetHeight(isValid ? captureRect.height() : sourceHeight),
endY(startY + targetHeight)
{}
bool isValid;
int sourceWidth;
int sourceHeight;
int startX;
int targetWidth;
int endX;
int startY;
int targetHeight;
int endY;
};
uchar gray(uchar r, uchar g, uchar b)
{
return (306 * (r & 0xFF) +
601 * (g & 0xFF) +
117 * (b & 0xFF) +
0x200) >> 10;
}
uchar yuvToGray(uchar Y, uchar U, uchar V)
{
const int C = int(Y) - 16;
const int D = int(U) - 128;
const int E = int(V) - 128;
return gray(
qBound<uchar>(0, uchar((298 * C + 409 * E + 128) >> 8), 255),
qBound<uchar>(0, uchar((298 * C - 100 * D - 208 * E + 128) >> 8), 255),
qBound<uchar>(0, uchar((298 * C + 516 * D + 128) >> 8), 255)
);
}
uchar yuvToGray2(uchar y, uchar u, uchar v)
{
double rD = y + 1.4075 * (v - 128);
double gD = y - 0.3455 * (u - 128) - (0.7169 * (v - 128));
double bD = y + 1.7790 * (u - 128);
return gray(
qBound<uchar>(0, uchar(floor(rD)), 255),
qBound<uchar>(0, uchar(floor(gD)), 255),
qBound<uchar>(0, uchar(floor(bD)), 255)
);
}
static QImage* rgbDataToGrayscale(
const uchar* data,
const CaptureRect &captureRect,
const int alpha,
const int red,
const int green,
const int blue,
const bool isPremultiplied = false
) {
const int stride = (alpha < 0) ? 3 : 4;
const int endX = captureRect.sourceWidth - captureRect.startX - captureRect.targetWidth;
const int skipX = (endX + captureRect.startX) * stride;
QImage *image_ptr = new QImage(captureRect.targetWidth, captureRect.targetHeight, QImage::Format_Grayscale8);
uchar* pixelInit = image_ptr->bits();
data += (captureRect.startY * captureRect.sourceWidth + captureRect.startX) * stride;
for (int y = 1; y <= captureRect.targetHeight; ++y) {
// Quick fix for iOS devices. Will be handled better in the future
#ifdef Q_OS_IOS
uchar* pixel = pixelInit + (y - 1) * captureRect.targetWidth;
#else
uchar* pixel = pixelInit + (captureRect.targetHeight - y) * captureRect.targetWidth;
#endif
for (int x = 0; x < captureRect.targetWidth; ++x) {
uchar r = data[red];
uchar g = data[green];
uchar b = data[blue];
if (isPremultiplied) {
uchar a = data[alpha];
r = uchar((uint(r) * 255) / a);
g = uchar((uint(g) * 255) / a);
b = uchar((uint(b) * 255) / a);
}
*pixel = gray(r, g, b);
++pixel;
data += stride;
}
data += skipX;
}
return image_ptr;
}
void QrCodeVideoFrame::setData(QVideoFrame &frame)
{
frame.map(QAbstractVideoBuffer::ReadOnly);
// Copy video frame bytes to this.data.
// This is made to try to get a better performance (less memory allocation, faster unmap)
// Any other task is performed in a QFuture task, as we want to leave the UI thread asap.
if (m_data.size() != frame.mappedBytes()) {
m_data.resize(frame.mappedBytes());
}
memcpy(m_data.data(), frame.bits(), frame.mappedBytes());
m_size = frame.size();
m_pixelFormat = frame.pixelFormat();
frame.unmap();
}
QImage *QrCodeVideoFrame::toGrayscaleImage()
{
const CaptureRect captureRect(QRect(), m_size.width(), m_size.height());
const auto* data = reinterpret_cast<const uchar *>(m_data.constData());
const auto *yuvPtr = reinterpret_cast<const uint32_t *>(data);
uchar *pixel;
int wh;
int w_2;
int wh_54;
QImage *image;
switch (m_pixelFormat) {
case QVideoFrame::Format_RGB32:
image = rgbDataToGrayscale(data, captureRect, 0, 1, 2, 3);
break;
case QVideoFrame::Format_ARGB32:
image = rgbDataToGrayscale(data, captureRect, 0, 1, 2, 3);
break;
case QVideoFrame::Format_ARGB32_Premultiplied:
image = rgbDataToGrayscale(data, captureRect, 0, 1, 2, 3, true);
break;
case QVideoFrame::Format_BGRA32:
image = rgbDataToGrayscale(data, captureRect, 3, 2, 1, 0);
break;
case QVideoFrame::Format_BGRA32_Premultiplied:
image = rgbDataToGrayscale(data, captureRect, 3, 2, 1, 0, true);
break;
case QVideoFrame::Format_BGR32:
image = rgbDataToGrayscale(data, captureRect, 3, 2, 1, 0);
break;
case QVideoFrame::Format_BGR24:
image = rgbDataToGrayscale(data, captureRect, -1, 2, 1, 0);
break;
case QVideoFrame::Format_BGR555:
/// This is a forced "conversion", colors end up swapped.
image = new QImage(data, m_size.width(), m_size.height(), QImage::Format_RGB555);
break;
case QVideoFrame::Format_BGR565:
/// This is a forced "conversion", colors end up swapped.
image = new QImage(data, m_size.width(), m_size.height(), QImage::Format_RGB16);
break;
case QVideoFrame::Format_YUV420P:
case QVideoFrame::Format_NV12:
/// nv12 format, encountered on macOS
image = new QImage(captureRect.targetWidth, captureRect.targetHeight, QImage::Format_Grayscale8);
pixel = image->bits();
wh = m_size.width() * m_size.height();
w_2 = m_size.width() / 2;
wh_54 = wh * 5 / 4;
for (int y = captureRect.startY; y < captureRect.endY; y++) {
const int Y_offset = y * m_size.width();
const int y_2 = y / 2;
const int U_offset = y_2 * w_2 + wh;
const int V_offset = y_2 * w_2 + wh_54;
for (int x = captureRect.startX; x < captureRect.endX; x++) {
const int x_2 = x / 2;
const uchar Y = data[Y_offset + x];
const uchar U = data[U_offset + x_2];
const uchar V = data[V_offset + x_2];
*pixel = yuvToGray(Y, U, V);
++pixel;
}
}
break;
case QVideoFrame::Format_YUYV:
image = new QImage(captureRect.targetWidth, captureRect.targetHeight, QImage::Format_Grayscale8);
pixel = image->bits();
for (int y = captureRect.startY; y < captureRect.endY; y++){
const uint32_t *row = &yuvPtr[y*(m_size.width()/2)];
int end = captureRect.startX/2 + (captureRect.endX - captureRect.startX)/2;
for (int x = captureRect.startX/2; x < end; x++){
const uint8_t *pxl = reinterpret_cast<const uint8_t *>(&row[x]);
const uint8_t y0 = pxl[0];
const uint8_t u = pxl[1];
const uint8_t v = pxl[3];
const uint8_t y1 = pxl[2];
*pixel = yuvToGray2(y0, u, v);
++pixel;
*pixel = yuvToGray2(y1, u, v);
++pixel;
}
}
break;
/// TODO: Handle (create QImages from) YUV formats.
default:
image = new QImage(
data,
m_size.width(),
m_size.height(),
QVideoFrame::imageFormatFromPixelFormat(m_pixelFormat)
);
break;
}
return image;
}
QByteArray QrCodeVideoFrame::data() const
{
return m_data;
}
QSize QrCodeVideoFrame::size() const
{
return m_size;
}
QVideoFrame::PixelFormat QrCodeVideoFrame::pixelFormat() const
{
return m_pixelFormat;
}

104
src/QrCodeVideoFrame.h Normal file
View File

@ -0,0 +1,104 @@
/*
* Kaidan - A user-friendly XMPP client for every device!
*
* Copyright (C) 2016-2019 Kaidan developers and contributors
* (see the LICENSE file for a full list of copyright authors)
*
* Kaidan 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 3 of the License, or
* (at your option) any later version.
*
* In addition, as a special exception, the author of Kaidan gives
* permission to link the code of its release with the OpenSSL
* project's "OpenSSL" library (or with modified versions of it that
* use the same license as the "OpenSSL" library), and distribute the
* linked executables. You must obey the GNU General Public License in
* all respects for all of the code used other than "OpenSSL". If you
* modify this file, you may extend this exception to your version of
* the file, but you are not obligated to do so. If you do not wish to
* do so, delete this exception statement from your version.
*
* Kaidan 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 Kaidan. If not, see <http://www.gnu.org/licenses/>.
*/
/*
* Copyright 2017 QZXing authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef QRCODEVIDEOFRAME_H
#define QRCODEVIDEOFRAME_H
#include <QByteArray>
#include <QSize>
#include <QVideoFrame>
class QImage;
/**
* video frame which may contain a QR code
*/
class QrCodeVideoFrame
{
public:
/**
* Instantiates an empty video frame
*/
QrCodeVideoFrame()
: m_size{0,0},
m_pixelFormat{QVideoFrame::Format_Invalid}
{}
/**
* Sets the frame.
*
* @param frame frame to be set
*/
void setData(QVideoFrame &frame);
/**
* Converts a given image to a grayscale image.
*
* @return grayscale image
*/
QImage *toGrayscaleImage();
/**
* @return content of the frame which may contain a QR code
*/
QByteArray data() const;
/**
* @return size of the frame
*/
QSize size() const;
/**
* @return format of the frame
*/
QVideoFrame::PixelFormat pixelFormat() const;
private:
QByteArray m_data;
QSize m_size;
QVideoFrame::PixelFormat m_pixelFormat;
};
#endif // QRCODEVIDEOFRAME_H

View File

@ -57,6 +57,9 @@
#include "RosterModel.h"
#include "StatusBar.h"
#include "UploadManager.h"
#include "EmojiModel.h"
#include "Utils.h"
#include "QrCodeScannerFilter.h"
#ifdef STATIC_BUILD
#include "static_plugins.h"
@ -177,6 +180,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qRegisterMetaType<QHash<QString,RosterItem>>("QHash<QString,RosterItem>");
qRegisterMetaType<std::function<void(RosterItem&)>>("std::function<void(RosterItem&)>");
qRegisterMetaType<std::function<void(Message&)>>("std::function<void(Message&)>");
qRegisterMetaType<ClientWorker::Credentials>("ClientWorker::Credentials");
// Enums for c++ member calls using enums
qRegisterMetaType<Enums::ConnectionState>();
qRegisterMetaType<Enums::DisconnectionReason>();
@ -246,7 +250,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
// open the XMPP-URI/link (if given)
if (!parser.positionalArguments().isEmpty())
kaidan.addOpenUri(parser.positionalArguments().first().toUtf8());
kaidan.addOpenUri(parser.positionalArguments().first());
//
// QML-GUI
@ -274,6 +278,7 @@ Q_DECL_EXPORT int main(int argc, char *argv[])
qmlRegisterType<StatusBar>("StatusBar", 0, 1, "StatusBar");
qmlRegisterType<EmojiModel>("EmojiModel", 0, 1, "EmojiModel");
qmlRegisterType<EmojiProxyModel>("EmojiModel", 0, 1, "EmojiProxyModel");
qmlRegisterType<QrCodeScannerFilter>(APPLICATION_ID, 1, 0, "QrCodeScannerFilter");
qmlRegisterUncreatableType<QAbstractItemModel>("EmojiModel", 0, 1, "QAbstractItemModel", "Used by proxy models");
qmlRegisterUncreatableType<Emoji>("EmojiModel", 0, 1, "Emoji", "Used by emoji models");

View File

@ -31,7 +31,7 @@
#include "QXmppUri.h"
#include <QUrlQuery>
/// actions, e.g. "join" in "group@example.org?join" for joining a groupchat
/// actions, e.g. "join" in "xmpp:group@example.org?join" for joining a group chat
const QStringList ACTION_STRINGS = QStringList()
<< ""

View File

@ -14,6 +14,7 @@ BUILD_TYPE="${BUILD_TYPE:-Debug}"
KAIDAN_SOURCES=$(dirname "$(readlink -f "${0}")")/..
KIRIGAMI_BUILD=/tmp/kirigami-mac-build
QXMPP_BUILD=/tmp/qxmpp-mac-build
ZXING_BUILD=/tmp/zxing-mac-build
OSXCROSS_TARGET="x86_64-apple-darwin15"
echo "-- Starting $BUILD_TYPE build of Kaidan --"
@ -32,6 +33,11 @@ if [ ! -e "$KAIDAN_SOURCES/3rdparty/qxmpp/" ]; then
git clone https://github.com/qxmpp-project/qxmpp.git 3rdparty/qxmpp
fi
if [ ! -e "$KAIDAN_SOURCES/3rdparty/zxing-cpp/" ]; then
echo "Cloning ZXing"
git clone https://github.com/nu-book/zxing-cpp.git 3rdparty/zxing-cpp
fi
cdnew() {
if [ -d "$1" ]; then
rm -rf "$1"
@ -77,6 +83,24 @@ echo "*****************************************"
}
fi
if [ ! -f "$ZXING_BUILD/lib/libZXingCore.dylib" ]; then
echo "*****************************************"
echo "Building ZXing"
echo "*****************************************"
{
cdnew $KAIDAN_SOURCES/3rdparty/zxing-cpp/build
${OSXCROSS_TARGET}-cmake .. \
-DCMAKE_PREFIX_PATH=$QT_MACOS \
-DBUILD_SHARED_LIBRARY=ON \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE -DCMAKE_INSTALL_PREFIX=$ZXING_BUILD
make -j$(nproc)
make install
rm -rf $KAIDAN_SOURCES/3rdparty/zxing-cpp/build
}
fi
if [ ! -f "$KAIDAN_SOURCES/misc/macos/kaidan.icns" ]; then
echo "*****************************************"
echo "Rendering logos"
@ -120,6 +144,7 @@ echo "*****************************************"
-DECM_DIR=/usr/local/share/ECM/cmake \
-DCMAKE_PREFIX_PATH=$QT_MACOS\;$KIRIGAMI_BUILD\;$QXMPP_BUILD \
-DKF5Kirigami2_DIR=$KIRIGAMI_BUILD/lib/cmake/KF5Kirigami2 \
-DZXing_DIR=$ZXING_BUILD/lib/cmake/ZXing \
-DI18N=1 \
-DUSE_KNOTIFICATIONS=OFF \
-DCMAKE_BUILD_TYPE=$BUILD_TYPE \
@ -134,7 +159,7 @@ echo "Macdeployqt"
echo "*****************************************"
{
cd $KAIDAN_SOURCES/build
export LD_LIBRARY_PATH=$QT_MACOS/lib/:$KIRIGAMI_BUILD/lib:$LD_LIBRARY_PATH
export LD_LIBRARY_PATH=$QT_MACOS/lib/:$KIRIGAMI_BUILD/lib:$ZXING_BUILD/lib:$LD_LIBRARY_PATH
export PATH=$QT_MACOS/bin/:$PATH
# FIXME: Use `macdeployqt -qmlimport` when QTBUG-70977 is fixed
@ -143,6 +168,11 @@ echo "*****************************************"
ln -s $KIRIGAMI_BUILD/lib/qml/org/kde/kirigami.2 $QT_MACOS/qml/org/kde/kirigami.2
fi
macdeployqt bin/kaidan.app -qmlimport=$QT_MACOS/qml -qmlimport=$KIRIGAMI_BUILD/lib/qml/ -qmldir=$KAIDAN_SOURCES/src/qml/ -libpath=$KIRIGAMI_BUILD/lib/ -libpath=$QXMPP_BUILD/lib -libpath=$QT_MACOS/lib/ -appstore-compliant -verbose=3
# FIXME: Why does this assume /usr
if [ ! -f /usr/lib/libZXingCore.1.dylib ]; then
ln -s $ZXING_BUILD/lib/libZXingCore.1.dylib /usr/lib/libZXingCore.1.dylib
fi
macdeployqt bin/kaidan.app -qmlimport=$QT_MACOS/qml -qmlimport=$KIRIGAMI_BUILD/lib/qml/ -qmldir=$KAIDAN_SOURCES/src/qml/ -libpath=$KIRIGAMI_BUILD/lib/ -libpath=$QXMPP_BUILD/lib -libpath=$QT_MACOS/lib/ -appstore-compliant -verbose=1
${OSXCROSS_TARGET}-install_name_tool -add_rpath @executable_path/../Frameworks bin/kaidan.app/Contents/MacOS/kaidan
}

View File

@ -50,6 +50,7 @@ REPLACE_USER_IDS = [
# These user ids will be excluded from any targets
EXCLUDE_USER_IDS = [
"Weblate <noreply@weblate.org>",
"Hosted Weblate <hosted@weblate.org>",
"anonymous <> <None>",
"Kaidan Translations <translations@kaidan.im>",
"Kaidan translations <translations@kaidan.im>",
@ -136,6 +137,18 @@ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
THE POSSIBILITY OF SUCH DAMAGE."""
APACHE2_LICENSE = """Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License."""
class CopyrightAuthor:
def __init__(self, score = 0, dates = None, years = "", uid = ""):
self.dates = dates or list([]);
@ -292,6 +305,13 @@ def main():
"Konstantinos Sideris <siderisk@auth.gr>": CopyrightAuthor(years = "2017"),
},
),
CopyrightTarget(
files = ["src/QrCodeVideoFrame.h", "src/QrCodeVideoFrame.cpp"],
licenseName = "apache-2.0",
authorList = {
"QZXing authors": CopyrightAuthor(years = "2017"),
},
),
CopyrightTarget(
files = ["data/images/message_checkmark.svg"],
licenseName = "GPL-3+",
@ -341,6 +361,10 @@ def main():
LicenseTarget(
name = "MIT-Apple",
content = MIT_APPLE_LICENSE
),
LicenseTarget(
name = "apache-2.0",
content = APACHE2_LICENSE
)
];