Merge pull request #137 from Nheko-Reborn/blurhash

Experimental Blurhash support
This commit is contained in:
DeepBlueV7.X 2020-03-05 21:07:18 +00:00 committed by GitHub
commit fc2f08a186
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
16 changed files with 739 additions and 49 deletions

38
src/BlurhashProvider.cpp Normal file
View file

@ -0,0 +1,38 @@
#include "BlurhashProvider.h"
#include <algorithm>
#include <QUrl>
#include "blurhash.hpp"
void
BlurhashResponse::run()
{
if (m_requestedSize.width() < 0 || m_requestedSize.height() < 0) {
m_error = QStringLiteral("Blurhash needs size request");
emit finished();
return;
}
if (m_requestedSize.width() == 0 || m_requestedSize.height() == 0) {
m_image = QImage(m_requestedSize, QImage::Format_RGB32);
m_image.fill(QColor(0, 0, 0));
emit finished();
return;
}
auto decoded = blurhash::decode(QUrl::fromPercentEncoding(m_id.toUtf8()).toStdString(),
m_requestedSize.width(),
m_requestedSize.height(),
4);
if (decoded.image.empty()) {
m_error = QStringLiteral("Failed decode!");
emit finished();
return;
}
QImage image(decoded.image.data(), decoded.width, decoded.height, QImage::Format_RGB32);
m_image = image.copy();
emit finished();
}

51
src/BlurhashProvider.h Normal file
View file

@ -0,0 +1,51 @@
#pragma once
#include <QQuickAsyncImageProvider>
#include <QQuickImageResponse>
#include <QImage>
#include <QThreadPool>
class BlurhashResponse
: public QQuickImageResponse
, public QRunnable
{
public:
BlurhashResponse(const QString &id, const QSize &requestedSize)
: m_id(id)
, m_requestedSize(requestedSize)
{
setAutoDelete(false);
}
QQuickTextureFactory *textureFactory() const override
{
return QQuickTextureFactory::textureFactoryForImage(m_image);
}
QString errorString() const override { return m_error; }
void run() override;
QString m_id, m_error;
QSize m_requestedSize;
QImage m_image;
};
class BlurhashProvider
: public QObject
, public QQuickAsyncImageProvider
{
Q_OBJECT
public slots:
QQuickImageResponse *requestImageResponse(const QString &id,
const QSize &requestedSize) override
{
BlurhashResponse *response = new BlurhashResponse(id, requestedSize);
pool.start(response);
return response;
}
private:
QThreadPool pool;
};

View file

@ -47,6 +47,8 @@
#include "popups/UserMentions.h"
#include "timeline/TimelineViewManager.h"
#include "blurhash.hpp"
// TODO: Needs to be updated with an actual secret.
static const std::string STORAGE_SECRET_KEY("secret");
@ -324,9 +326,27 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
}
QSize dimensions;
if (mimeClass == "image")
QString blurhash;
if (mimeClass == "image") {
dimensions = QImageReader(dev.data()).size();
QImage img;
img.loadFromData(bin);
if (img.height() > 200 && img.width() > 360)
img = img.scaled(360, 200, Qt::KeepAspectRatioByExpanding);
std::vector<unsigned char> data;
for (int y = 0; y < img.height(); y++) {
for (int x = 0; x < img.width(); x++) {
auto p = img.pixel(x, y);
data.push_back(static_cast<unsigned char>(qRed(p)));
data.push_back(static_cast<unsigned char>(qGreen(p)));
data.push_back(static_cast<unsigned char>(qBlue(p)));
}
}
blurhash = QString::fromStdString(
blurhash::encode(data.data(), img.width(), img.height(), 4, 3));
}
http::client()->upload(
payload,
encryptedFile ? "application/octet-stream" : mime.name().toStdString(),
@ -339,6 +359,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
mime = mime.name(),
size = payload.size(),
dimensions,
blurhash,
related](const mtx::responses::ContentURI &res, mtx::http::RequestErr err) {
if (err) {
emit uploadFailed(
@ -358,6 +379,7 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
mime,
size,
dimensions,
blurhash,
related);
});
});
@ -366,37 +388,44 @@ ChatPage::ChatPage(QSharedPointer<UserSettings> userSettings, QWidget *parent)
text_input_->hideUploadSpinner();
emit showNotification(msg);
});
connect(
this,
&ChatPage::mediaUploaded,
this,
[this](QString roomid,
QString filename,
std::optional<mtx::crypto::EncryptedFile> encryptedFile,
QString url,
QString mimeClass,
QString mime,
qint64 dsize,
QSize dimensions,
const std::optional<RelatedInfo> &related) {
text_input_->hideUploadSpinner();
connect(this,
&ChatPage::mediaUploaded,
this,
[this](QString roomid,
QString filename,
std::optional<mtx::crypto::EncryptedFile> encryptedFile,
QString url,
QString mimeClass,
QString mime,
qint64 dsize,
QSize dimensions,
QString blurhash,
const std::optional<RelatedInfo> &related) {
text_input_->hideUploadSpinner();
if (encryptedFile)
encryptedFile->url = url.toStdString();
if (encryptedFile)
encryptedFile->url = url.toStdString();
if (mimeClass == "image")
view_manager_->queueImageMessage(
roomid, filename, encryptedFile, url, mime, dsize, dimensions, related);
else if (mimeClass == "audio")
view_manager_->queueAudioMessage(
roomid, filename, encryptedFile, url, mime, dsize, related);
else if (mimeClass == "video")
view_manager_->queueVideoMessage(
roomid, filename, encryptedFile, url, mime, dsize, related);
else
view_manager_->queueFileMessage(
roomid, filename, encryptedFile, url, mime, dsize, related);
});
if (mimeClass == "image")
view_manager_->queueImageMessage(roomid,
filename,
encryptedFile,
url,
mime,
dsize,
dimensions,
blurhash,
related);
else if (mimeClass == "audio")
view_manager_->queueAudioMessage(
roomid, filename, encryptedFile, url, mime, dsize, related);
else if (mimeClass == "video")
view_manager_->queueVideoMessage(
roomid, filename, encryptedFile, url, mime, dsize, related);
else
view_manager_->queueFileMessage(
roomid, filename, encryptedFile, url, mime, dsize, related);
});
connect(room_list_, &RoomList::roomAvatarChanged, this, &ChatPage::updateTopBarAvatar);

View file

@ -114,6 +114,7 @@ signals:
const QString &mime,
qint64 dsize,
const QSize &dimensions,
const QString &blurhash,
const std::optional<RelatedInfo> &related);
void contentLoaded();

View file

@ -134,6 +134,20 @@ struct EventThumbnailUrl
}
};
struct EventBlurhash
{
template<class Content>
using blurhash_t = decltype(Content::info.blurhash);
template<class T>
std::string operator()(const mtx::events::Event<T> &e)
{
if constexpr (is_detected<blurhash_t, T>::value) {
return e.content.info.blurhash;
}
return "";
}
};
struct EventFilename
{
template<class T>
@ -348,6 +362,11 @@ mtx::accessors::thumbnail_url(const mtx::events::collections::TimelineEvents &ev
return std::visit(EventThumbnailUrl{}, event);
}
std::string
mtx::accessors::blurhash(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventBlurhash{}, event);
}
std::string
mtx::accessors::mimetype(const mtx::events::collections::TimelineEvents &event)
{
return std::visit(EventMimeType{}, event);

View file

@ -47,6 +47,8 @@ url(const mtx::events::collections::TimelineEvents &event);
std::string
thumbnail_url(const mtx::events::collections::TimelineEvents &event);
std::string
blurhash(const mtx::events::collections::TimelineEvents &event);
std::string
mimetype(const mtx::events::collections::TimelineEvents &event);
std::string
in_reply_to_event(const mtx::events::collections::TimelineEvents &event);

View file

@ -212,6 +212,7 @@ TimelineModel::roleNames() const
{Timestamp, "timestamp"},
{Url, "url"},
{ThumbnailUrl, "thumbnailUrl"},
{Blurhash, "blurhash"},
{Filename, "filename"},
{Filesize, "filesize"},
{MimeType, "mimetype"},
@ -297,6 +298,8 @@ TimelineModel::data(const QString &id, int role) const
return QVariant(QString::fromStdString(url(event)));
case ThumbnailUrl:
return QVariant(QString::fromStdString(thumbnail_url(event)));
case Blurhash:
return QVariant(QString::fromStdString(blurhash(event)));
case Filename:
return QVariant(QString::fromStdString(filename(event)));
case Filesize:
@ -356,6 +359,7 @@ TimelineModel::data(const QString &id, int role) const
m.insert(names[Timestamp], data(id, static_cast<int>(Timestamp)));
m.insert(names[Url], data(id, static_cast<int>(Url)));
m.insert(names[ThumbnailUrl], data(id, static_cast<int>(ThumbnailUrl)));
m.insert(names[Blurhash], data(id, static_cast<int>(Blurhash)));
m.insert(names[Filename], data(id, static_cast<int>(Filename)));
m.insert(names[Filesize], data(id, static_cast<int>(Filesize)));
m.insert(names[MimeType], data(id, static_cast<int>(MimeType)));

View file

@ -142,6 +142,7 @@ public:
Timestamp,
Url,
ThumbnailUrl,
Blurhash,
Filename,
Filesize,
MimeType,

View file

@ -4,6 +4,7 @@
#include <QPalette>
#include <QQmlContext>
#include "BlurhashProvider.h"
#include "ChatPage.h"
#include "ColorImageProvider.h"
#include "DelegateChooser.h"
@ -69,6 +70,7 @@ TimelineViewManager::userColor(QString id, QColor background)
TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettings, QWidget *parent)
: imgProvider(new MxcImageProvider())
, colorImgProvider(new ColorImageProvider())
, blurhashProvider(new BlurhashProvider())
, settings(userSettings)
{
qmlRegisterUncreatableMetaObject(qml_mtx_events::staticMetaObject,
@ -99,6 +101,7 @@ TimelineViewManager::TimelineViewManager(QSharedPointer<UserSettings> userSettin
updateColorPalette();
view->engine()->addImageProvider("MxcImage", imgProvider);
view->engine()->addImageProvider("colorimage", colorImgProvider);
view->engine()->addImageProvider("blurhash", blurhashProvider);
view->setSource(QUrl("qrc:///qml/TimelineView.qml"));
connect(dynamic_cast<ChatPage *>(parent),
@ -270,11 +273,13 @@ TimelineViewManager::queueImageMessage(const QString &roomid,
const QString &mime,
uint64_t dsize,
const QSize &dimensions,
const QString &blurhash,
const std::optional<RelatedInfo> &related)
{
mtx::events::msg::Image image;
image.info.mimetype = mime.toStdString();
image.info.size = dsize;
image.info.blurhash = blurhash.toStdString();
image.body = filename.toStdString();
image.url = url.toStdString();
image.info.h = dimensions.height();

View file

@ -14,6 +14,7 @@
#include "Utils.h"
class MxcImageProvider;
class BlurhashProvider;
class ColorImageProvider;
class UserSettings;
@ -79,6 +80,7 @@ public slots:
const QString &mime,
uint64_t dsize,
const QSize &dimensions,
const QString &blurhash,
const std::optional<RelatedInfo> &related);
void queueFileMessage(const QString &roomid,
const QString &filename,
@ -112,6 +114,7 @@ private:
MxcImageProvider *imgProvider;
ColorImageProvider *colorImgProvider;
BlurhashProvider *blurhashProvider;
QHash<QString, QSharedPointer<TimelineModel>> models;
TimelineModel *timeline_ = nullptr;