From 42d2b10d5d53ecc92531491fdf2e27399da08d84 Mon Sep 17 00:00:00 2001 From: Nicolas Werner Date: Sat, 14 Aug 2021 17:17:50 +0200 Subject: [PATCH] Round images in the image provider --- resources/qml/Avatar.qml | 16 +----- resources/qml/MessageView.qml | 1 - resources/qml/Root.qml | 1 - resources/qml/TimelineView.qml | 1 - src/MxcImageProvider.cpp | 96 ++++++++++++++++++++++++++++------ src/MxcImageProvider.h | 7 ++- 6 files changed, 87 insertions(+), 35 deletions(-) diff --git a/resources/qml/Avatar.qml b/resources/qml/Avatar.qml index 9685dde1..c3e8acdb 100644 --- a/resources/qml/Avatar.qml +++ b/resources/qml/Avatar.qml @@ -3,7 +3,6 @@ // SPDX-License-Identifier: GPL-3.0-or-later import "./ui" -import QtGraphicalEffects 1.0 import QtQuick 2.6 import QtQuick.Controls 2.3 import im.nheko 1.0 @@ -50,8 +49,7 @@ Rectangle { smooth: true sourceSize.width: avatar.width sourceSize.height: avatar.height - layer.enabled: true - source: avatar.url + ((avatar.crop || !avatar.url) ? "" : "?scale") + source: avatar.url ? (avatar.url + "?radius=" + radius + ((avatar.crop) ? "" : "&scale")) : "" MouseArea { id: mouseArea @@ -65,18 +63,6 @@ Rectangle { } - layer.effect: OpacityMask { - cached: true - - maskSource: Rectangle { - anchors.fill: parent - width: avatar.width - height: avatar.height - radius: Settings.avatarCircles ? height / 2 : 3 - } - - } - } Rectangle { diff --git a/resources/qml/MessageView.qml b/resources/qml/MessageView.qml index 79cbd700..e5c6b4ec 100644 --- a/resources/qml/MessageView.qml +++ b/resources/qml/MessageView.qml @@ -6,7 +6,6 @@ import "./delegates" import "./emoji" import "./ui" import Qt.labs.platform 1.1 as Platform -import QtGraphicalEffects 1.0 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.2 diff --git a/resources/qml/Root.qml b/resources/qml/Root.qml index 79f12bbf..cc7d32ea 100644 --- a/resources/qml/Root.qml +++ b/resources/qml/Root.qml @@ -8,7 +8,6 @@ import "./dialogs" import "./emoji" import "./voip" import Qt.labs.platform 1.1 as Platform -import QtGraphicalEffects 1.0 import QtQuick 2.15 import QtQuick.Controls 2.15 import QtQuick.Layouts 1.3 diff --git a/resources/qml/TimelineView.qml b/resources/qml/TimelineView.qml index 104da160..c8ac6bc7 100644 --- a/resources/qml/TimelineView.qml +++ b/resources/qml/TimelineView.qml @@ -9,7 +9,6 @@ import "./emoji" import "./ui" import "./voip" import Qt.labs.platform 1.1 as Platform -import QtGraphicalEffects 1.0 import QtQuick 2.9 import QtQuick.Controls 2.5 import QtQuick.Layouts 1.3 diff --git a/src/MxcImageProvider.cpp b/src/MxcImageProvider.cpp index b8648269..58078a3b 100644 --- a/src/MxcImageProvider.cpp +++ b/src/MxcImageProvider.cpp @@ -11,6 +11,8 @@ #include #include #include +#include +#include #include #include "Logging.h" @@ -22,14 +24,26 @@ QHash infos; QQuickImageResponse * MxcImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize) { - auto id_ = id; - bool crop = true; - if (id.endsWith("?scale")) { - crop = false; - id_.remove("?scale"); + auto id_ = id; + bool crop = true; + double radius = 0; + + auto queryStart = id.lastIndexOf('?'); + if (queryStart != -1) { + id_ = id.left(queryStart); + auto query = id.midRef(queryStart + 1); + auto queryBits = query.split('&'); + + for (auto b : queryBits) { + if (b == "scale") { + crop = false; + } else if (b.startsWith("radius=")) { + radius = b.mid(7).toDouble(); + } + } } - MxcImageResponse *response = new MxcImageResponse(id_, crop, requestedSize); + MxcImageResponse *response = new MxcImageResponse(id_, crop, radius, requestedSize); pool.start(response); return response; } @@ -53,14 +67,35 @@ MxcImageResponse::run() } emit finished(); }, - m_crop); + m_crop, + m_radius); +} + +static QImage +clipRadius(QImage img, double radius) +{ + QImage out(img.size(), QImage::Format_ARGB32_Premultiplied); + out.fill(Qt::transparent); + + QPainter painter(&out); + painter.setRenderHint(QPainter::Antialiasing, true); + painter.setRenderHint(QPainter::SmoothPixmapTransform, true); + + QPainterPath ppath; + ppath.addRoundedRect(img.rect(), radius, radius); + + painter.setClipPath(ppath); + painter.drawImage(img.rect(), img); + + return out; } void MxcImageProvider::download(const QString &id, const QSize &requestedSize, std::function then, - bool crop) + bool crop, + double radius) { std::optional encryptionInfo; auto temp = infos.find("mxc://" + id); @@ -69,12 +104,13 @@ MxcImageProvider::download(const QString &id, if (requestedSize.isValid() && !encryptionInfo) { QString fileName = - QString("%1_%2x%3_%4") + QString("%1_%2x%3_%4_radius%5") .arg(QString::fromUtf8(id.toUtf8().toBase64(QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) .arg(requestedSize.width()) .arg(requestedSize.height()) - .arg(crop ? "crop" : "scale"); + .arg(crop ? "crop" : "scale") + .arg(radius); QFileInfo fileInfo(QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/media_cache", fileName); @@ -86,6 +122,10 @@ MxcImageProvider::download(const QString &id, image = image.scaled( requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + if (!image.isNull()) { then(id, requestedSize, image, fileInfo.absoluteFilePath()); return; @@ -100,8 +140,8 @@ MxcImageProvider::download(const QString &id, opts.method = crop ? "crop" : "scale"; http::client()->get_thumbnail( opts, - [fileInfo, requestedSize, then, id](const std::string &res, - mtx::http::RequestErr err) { + [fileInfo, requestedSize, radius, then, id](const std::string &res, + mtx::http::RequestErr err) { if (err || res.empty()) { then(id, QSize(), {}, ""); @@ -113,6 +153,10 @@ MxcImageProvider::download(const QString &id, if (!image.isNull()) { image = image.scaled( requestedSize, Qt::KeepAspectRatio, Qt::SmoothTransformation); + + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } } image.setText("mxc url", "mxc://" + id); if (image.save(fileInfo.absoluteFilePath(), "png")) @@ -126,8 +170,12 @@ MxcImageProvider::download(const QString &id, }); } else { try { - QString fileName = QString::fromUtf8(id.toUtf8().toBase64( - QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals)); + QString fileName = + QString("%1_radius%2") + .arg(QString::fromUtf8(id.toUtf8().toBase64( + QByteArray::Base64UrlEncoding | QByteArray::OmitTrailingEquals))) + .arg(radius); + QFileInfo fileInfo( QStandardPaths::writableLocation(QStandardPaths::CacheLocation) + "/media_cache", @@ -148,6 +196,11 @@ MxcImageProvider::download(const QString &id, QImage image = utils::readImage(data); image.setText("mxc url", "mxc://" + id); if (!image.isNull()) { + if (radius != 0) { + image = + clipRadius(std::move(image), radius); + } + then(id, requestedSize, image, @@ -158,6 +211,11 @@ MxcImageProvider::download(const QString &id, QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); if (!image.isNull()) { + if (radius != 0) { + image = + clipRadius(std::move(image), radius); + } + then(id, requestedSize, image, @@ -169,7 +227,7 @@ MxcImageProvider::download(const QString &id, http::client()->download( "mxc://" + id.toStdString(), - [fileInfo, requestedSize, then, id, encryptionInfo]( + [fileInfo, requestedSize, then, id, radius, encryptionInfo]( const std::string &res, const std::string &, const std::string &originalFilename, @@ -195,6 +253,10 @@ MxcImageProvider::download(const QString &id, auto data = QByteArray(tempData.data(), (int)tempData.size()); QImage image = utils::readImage(data); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + image.setText("original filename", QString::fromStdString(originalFilename)); image.setText("mxc url", "mxc://" + id); @@ -205,6 +267,10 @@ MxcImageProvider::download(const QString &id, QImage image = utils::readImageFromFile(fileInfo.absoluteFilePath()); + if (radius != 0) { + image = clipRadius(std::move(image), radius); + } + image.setText("original filename", QString::fromStdString(originalFilename)); image.setText("mxc url", "mxc://" + id); diff --git a/src/MxcImageProvider.h b/src/MxcImageProvider.h index 61d82852..6de83c0e 100644 --- a/src/MxcImageProvider.h +++ b/src/MxcImageProvider.h @@ -19,10 +19,11 @@ class MxcImageResponse , public QRunnable { public: - MxcImageResponse(const QString &id, bool crop, const QSize &requestedSize) + MxcImageResponse(const QString &id, bool crop, double radius, const QSize &requestedSize) : m_id(id) , m_requestedSize(requestedSize) , m_crop(crop) + , m_radius(radius) { setAutoDelete(false); } @@ -39,6 +40,7 @@ public: QSize m_requestedSize; QImage m_image; bool m_crop; + double m_radius; }; class MxcImageProvider @@ -54,7 +56,8 @@ public slots: static void download(const QString &id, const QSize &requestedSize, std::function then, - bool crop = true); + bool crop = true, + double radius = 0); private: QThreadPool pool;