Merge pull request #137 from Nheko-Reborn/blurhash
Experimental Blurhash support
This commit is contained in:
commit
fc2f08a186
16 changed files with 739 additions and 49 deletions
38
src/BlurhashProvider.cpp
Normal file
38
src/BlurhashProvider.cpp
Normal 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
51
src/BlurhashProvider.h
Normal 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;
|
||||
};
|
||||
|
|
@ -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);
|
||||
|
||||
|
|
|
|||
|
|
@ -114,6 +114,7 @@ signals:
|
|||
const QString &mime,
|
||||
qint64 dsize,
|
||||
const QSize &dimensions,
|
||||
const QString &blurhash,
|
||||
const std::optional<RelatedInfo> &related);
|
||||
|
||||
void contentLoaded();
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)));
|
||||
|
|
|
|||
|
|
@ -142,6 +142,7 @@ public:
|
|||
Timestamp,
|
||||
Url,
|
||||
ThumbnailUrl,
|
||||
Blurhash,
|
||||
Filename,
|
||||
Filesize,
|
||||
MimeType,
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue