Compare commits
1 Commits
master
...
work/redst
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7fee8e6c71 |
@@ -12,8 +12,6 @@ add_library(neochat STATIC
|
|||||||
models/userdirectorylistmodel.h
|
models/userdirectorylistmodel.h
|
||||||
notificationsmanager.cpp
|
notificationsmanager.cpp
|
||||||
notificationsmanager.h
|
notificationsmanager.h
|
||||||
blurhash.cpp
|
|
||||||
blurhash.h
|
|
||||||
blurhashimageprovider.cpp
|
blurhashimageprovider.cpp
|
||||||
blurhashimageprovider.h
|
blurhashimageprovider.h
|
||||||
windowcontroller.cpp
|
windowcontroller.cpp
|
||||||
|
|||||||
@@ -1,198 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2018 Wolt Enterprises
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#include "blurhash.h"
|
|
||||||
|
|
||||||
#include <math.h>
|
|
||||||
#include <stdbool.h>
|
|
||||||
#include <stdlib.h>
|
|
||||||
#include <string.h>
|
|
||||||
#include <vector>
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
const char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
|
|
||||||
|
|
||||||
struct Color {
|
|
||||||
float r = 0;
|
|
||||||
float g = 0;
|
|
||||||
float b = 0;
|
|
||||||
};
|
|
||||||
|
|
||||||
inline int linearTosRGB(float value)
|
|
||||||
{
|
|
||||||
float v = fmaxf(0, fminf(1, value));
|
|
||||||
if (v <= 0.0031308)
|
|
||||||
return v * 12.92 * 255 + 0.5;
|
|
||||||
else
|
|
||||||
return (1.055 * powf(v, 1 / 2.4) - 0.055) * 255 + 0.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float sRGBToLinear(int value)
|
|
||||||
{
|
|
||||||
float v = (float)value / 255;
|
|
||||||
if (v <= 0.04045)
|
|
||||||
return v / 12.92;
|
|
||||||
else
|
|
||||||
return powf((v + 0.055) / 1.055, 2.4);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline float signPow(float value, float exp)
|
|
||||||
{
|
|
||||||
return copysignf(powf(fabsf(value), exp), value);
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint8_t clampToUByte(int *src)
|
|
||||||
{
|
|
||||||
if (*src >= 0 && *src <= 255) {
|
|
||||||
return *src;
|
|
||||||
}
|
|
||||||
return (*src < 0) ? 0 : 255;
|
|
||||||
}
|
|
||||||
|
|
||||||
inline uint8_t *createByteArray(int size)
|
|
||||||
{
|
|
||||||
return (uint8_t *)malloc(size * sizeof(uint8_t));
|
|
||||||
}
|
|
||||||
|
|
||||||
int decodeToInt(const char *string, int start, int end)
|
|
||||||
{
|
|
||||||
int value = 0;
|
|
||||||
for (int iter1 = start; iter1 < end; iter1++) {
|
|
||||||
int index = -1;
|
|
||||||
for (int iter2 = 0; iter2 < 83; iter2++) {
|
|
||||||
if (chars[iter2] == string[iter1]) {
|
|
||||||
index = iter2;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (index == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
value = value * 83 + index;
|
|
||||||
}
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
void decodeDC(int value, Color *color)
|
|
||||||
{
|
|
||||||
color->r = sRGBToLinear(value >> 16);
|
|
||||||
color->g = sRGBToLinear((value >> 8) & 255);
|
|
||||||
color->b = sRGBToLinear(value & 255);
|
|
||||||
}
|
|
||||||
|
|
||||||
void decodeAC(int value, float maximumValue, Color *color)
|
|
||||||
{
|
|
||||||
int quantR = (int)floorf(value / (19 * 19));
|
|
||||||
int quantG = (int)floorf(value / 19) % 19;
|
|
||||||
int quantB = (int)value % 19;
|
|
||||||
|
|
||||||
color->r = signPow(((float)quantR - 9) / 9, 2.0) * maximumValue;
|
|
||||||
color->g = signPow(((float)quantG - 9) / 9, 2.0) * maximumValue;
|
|
||||||
color->b = signPow(((float)quantB - 9) / 9, 2.0) * maximumValue;
|
|
||||||
}
|
|
||||||
|
|
||||||
int decodeToArray(const char *blurhash, int width, int height, int punch, int nChannels, uint8_t *pixelArray)
|
|
||||||
{
|
|
||||||
if (!isValidBlurhash(blurhash)) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
if (punch < 1) {
|
|
||||||
punch = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sizeFlag = decodeToInt(blurhash, 0, 1);
|
|
||||||
int numY = (int)floorf(sizeFlag / 9) + 1;
|
|
||||||
int numX = (sizeFlag % 9) + 1;
|
|
||||||
int iter = 0;
|
|
||||||
|
|
||||||
Color color;
|
|
||||||
int quantizedMaxValue = decodeToInt(blurhash, 1, 2);
|
|
||||||
if (quantizedMaxValue == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
const float maxValue = ((float)(quantizedMaxValue + 1)) / 166;
|
|
||||||
|
|
||||||
const int colors_size = numX * numY;
|
|
||||||
|
|
||||||
std::vector<Color> colors(colors_size, {0, 0, 0});
|
|
||||||
|
|
||||||
for (iter = 0; iter < colors_size; iter++) {
|
|
||||||
if (iter == 0) {
|
|
||||||
int value = decodeToInt(blurhash, 2, 6);
|
|
||||||
if (value == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
decodeDC(value, &color);
|
|
||||||
colors[iter] = color;
|
|
||||||
} else {
|
|
||||||
int value = decodeToInt(blurhash, 4 + iter * 2, 6 + iter * 2);
|
|
||||||
if (value == -1) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
decodeAC(value, maxValue * punch, &color);
|
|
||||||
colors[iter] = color;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
int bytesPerRow = width * nChannels;
|
|
||||||
int x = 0, y = 0, i = 0, j = 0;
|
|
||||||
int intR = 0, intG = 0, intB = 0;
|
|
||||||
|
|
||||||
for (y = 0; y < height; y++) {
|
|
||||||
for (x = 0; x < width; x++) {
|
|
||||||
float r = 0, g = 0, b = 0;
|
|
||||||
|
|
||||||
for (j = 0; j < numY; j++) {
|
|
||||||
for (i = 0; i < numX; i++) {
|
|
||||||
float basics = cos((M_PI * x * i) / width) * cos((M_PI * y * j) / height);
|
|
||||||
int idx = i + j * numX;
|
|
||||||
r += colors[idx].r * basics;
|
|
||||||
g += colors[idx].g * basics;
|
|
||||||
b += colors[idx].b * basics;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
intR = linearTosRGB(r);
|
|
||||||
intG = linearTosRGB(g);
|
|
||||||
intB = linearTosRGB(b);
|
|
||||||
|
|
||||||
pixelArray[nChannels * x + 0 + y * bytesPerRow] = clampToUByte(&intR);
|
|
||||||
pixelArray[nChannels * x + 1 + y * bytesPerRow] = clampToUByte(&intG);
|
|
||||||
pixelArray[nChannels * x + 2 + y * bytesPerRow] = clampToUByte(&intB);
|
|
||||||
|
|
||||||
if (nChannels == 4) {
|
|
||||||
pixelArray[nChannels * x + 3 + y * bytesPerRow] = 255;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels)
|
|
||||||
{
|
|
||||||
int bytesPerRow = width * nChannels;
|
|
||||||
uint8_t *pixelArray = createByteArray(bytesPerRow * height);
|
|
||||||
|
|
||||||
if (decodeToArray(blurhash, width, height, punch, nChannels, pixelArray) == -1) {
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
return pixelArray;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool isValidBlurhash(const char *blurhash)
|
|
||||||
{
|
|
||||||
const int hashLength = strlen(blurhash);
|
|
||||||
|
|
||||||
if (!blurhash || strlen(blurhash) < 6) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
int sizeFlag = decodeToInt(blurhash, 0, 1);
|
|
||||||
int numY = (int)floorf(sizeFlag / 9) + 1;
|
|
||||||
int numX = (sizeFlag % 9) + 1;
|
|
||||||
|
|
||||||
return hashLength == 4 + 2 * numX * numY;
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: 2018 Wolt Enterprises
|
|
||||||
// SPDX-License-Identifier: MIT
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <stdint.h>
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Returns the pixel array of the result image given the blurhash string.
|
|
||||||
*
|
|
||||||
* @param blurhash a string representing the blurhash to be decoded.
|
|
||||||
* @param width the width of the resulting image.
|
|
||||||
* @param height the height of the resulting image.
|
|
||||||
* @param punch the factor to improve the contrast, default = 1.
|
|
||||||
* @param nChannels the number of channels in the resulting image array, 3 = RGB, 4 = RGBA.
|
|
||||||
*
|
|
||||||
* @return A pointer to memory region where pixels are stored in (H, W, C) format.
|
|
||||||
*/
|
|
||||||
uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Checks if the Blurhash is valid or not.
|
|
||||||
*
|
|
||||||
* @param blurhash a string representing the blurhash.
|
|
||||||
*
|
|
||||||
* @return A bool (true if it is a valid blurhash, else false).
|
|
||||||
*/
|
|
||||||
bool isValidBlurhash(const char *blurhash);
|
|
||||||
@@ -1,31 +1,97 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
|
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#include "blurhashimageprovider.h"
|
#include "blurhashimageprovider.h"
|
||||||
|
|
||||||
#include <QImage>
|
#include <Quotient/blurhash.h>
|
||||||
#include <QString>
|
|
||||||
|
|
||||||
#include "blurhash.h"
|
/*
|
||||||
|
* Qt unfortunately re-encodes the base83 string in QML.
|
||||||
|
* The only special ASCII characters used in the blurhash base83 string are:
|
||||||
|
* #$%*+,-.:;=?@[]^_{|}~
|
||||||
|
* QUrl::fromPercentEncoding is too greedy, and spits out invalid characters
|
||||||
|
* for parts of valid base83 like %14.
|
||||||
|
*/
|
||||||
|
// clang-format off
|
||||||
|
static const QMap<QLatin1String, QLatin1String> knownEncodings = {
|
||||||
|
{QLatin1String("%23A"), QLatin1String(":")},
|
||||||
|
{QLatin1String("%3F"), QLatin1String("?")},
|
||||||
|
{QLatin1String("%23"), QLatin1String("#")},
|
||||||
|
{QLatin1String("%5B"), QLatin1String("[")},
|
||||||
|
{QLatin1String("%5D"), QLatin1String("]")},
|
||||||
|
{QLatin1String("%40"), QLatin1String("@")},
|
||||||
|
{QLatin1String("%24"), QLatin1String("$")},
|
||||||
|
{QLatin1String("%2A"), QLatin1String("*")},
|
||||||
|
{QLatin1String("%2B"), QLatin1String("+")},
|
||||||
|
{QLatin1String("%2C"), QLatin1String(",")},
|
||||||
|
{QLatin1String("%2D"), QLatin1String("-")},
|
||||||
|
{QLatin1String("%2E"), QLatin1String(".")},
|
||||||
|
{QLatin1String("%3D"), QLatin1String("=")},
|
||||||
|
{QLatin1String("%25"), QLatin1String("%")},
|
||||||
|
{QLatin1String("%5E"), QLatin1String("^")},
|
||||||
|
{QLatin1String("%7C"), QLatin1String("|")},
|
||||||
|
{QLatin1String("%7B"), QLatin1String("{")},
|
||||||
|
{QLatin1String("%7D"), QLatin1String("}")},
|
||||||
|
{QLatin1String("%7E"), QLatin1String("~")},
|
||||||
|
};
|
||||||
|
// clang-format on
|
||||||
|
|
||||||
BlurhashImageProvider::BlurhashImageProvider()
|
class AsyncImageResponseRunnable : public QObject, public QRunnable
|
||||||
: QQuickImageProvider(QQuickImageProvider::Image)
|
|
||||||
{
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
Q_SIGNALS:
|
||||||
|
void done(QImage image);
|
||||||
|
|
||||||
|
public:
|
||||||
|
AsyncImageResponseRunnable(const QString &id, const QSize &requestedSize)
|
||||||
|
: m_id(id)
|
||||||
|
, m_requestedSize(requestedSize)
|
||||||
|
{
|
||||||
|
if (m_requestedSize.width() == -1)
|
||||||
|
m_requestedSize.setWidth(64);
|
||||||
|
if (m_requestedSize.height() == -1)
|
||||||
|
m_requestedSize.setHeight(64);
|
||||||
|
}
|
||||||
|
|
||||||
|
void run() override
|
||||||
|
{
|
||||||
|
if (m_id.isEmpty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
QString decodedId = m_id;
|
||||||
|
for (auto i = knownEncodings.constBegin(); i != knownEncodings.constEnd(); ++i)
|
||||||
|
decodedId.replace(i.key(), i.value());
|
||||||
|
|
||||||
|
Q_EMIT done(Quotient::BlurHash::decode(decodedId, m_requestedSize));
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
QString m_id;
|
||||||
|
QSize m_requestedSize;
|
||||||
|
};
|
||||||
|
|
||||||
|
AsyncImageResponse::AsyncImageResponse(const QString &id, const QSize &requestedSize, QThreadPool *pool)
|
||||||
|
{
|
||||||
|
const auto runnable = new AsyncImageResponseRunnable(id, requestedSize);
|
||||||
|
connect(runnable, &AsyncImageResponseRunnable::done, this, &AsyncImageResponse::handleDone);
|
||||||
|
pool->start(runnable);
|
||||||
}
|
}
|
||||||
|
|
||||||
QImage BlurhashImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
|
void AsyncImageResponse::handleDone(QImage image)
|
||||||
{
|
{
|
||||||
if (id.isEmpty()) {
|
m_image = std::move(image);
|
||||||
return QImage();
|
Q_EMIT finished();
|
||||||
}
|
}
|
||||||
*size = requestedSize;
|
|
||||||
if (size->width() == -1) {
|
QQuickTextureFactory *AsyncImageResponse::textureFactory() const
|
||||||
size->setWidth(256);
|
{
|
||||||
}
|
return QQuickTextureFactory::textureFactoryForImage(m_image);
|
||||||
if (size->height() == -1) {
|
}
|
||||||
size->setHeight(256);
|
|
||||||
}
|
QQuickImageResponse *BlurHashImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
|
||||||
auto data = decode(QUrl::fromPercentEncoding(id.toLatin1()).toLatin1().data(), size->width(), size->height(), 1, 3);
|
{
|
||||||
QImage image(data, size->width(), size->height(), size->width() * 3, QImage::Format_RGB888, free, data);
|
return new AsyncImageResponse(id, requestedSize, &pool);
|
||||||
return image;
|
}
|
||||||
}
|
|
||||||
|
#include "blurhashimageprovider.moc"
|
||||||
|
|||||||
@@ -1,26 +1,25 @@
|
|||||||
// SPDX-FileCopyrightText: 2021 Tobias Fella <tobias.fella@kde.org>
|
// SPDX-FileCopyrightText: 2024 Joshua Goins <josh@redstrate.com>
|
||||||
// SPDX-License-Identifier: LGPL-2.0-or-later
|
// SPDX-License-Identifier: MIT
|
||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <QQuickImageProvider>
|
#include <QQuickAsyncImageProvider>
|
||||||
|
#include <QThreadPool>
|
||||||
|
|
||||||
/**
|
class AsyncImageResponse final : public QQuickImageResponse
|
||||||
* @class BlurhashImageProvider
|
|
||||||
*
|
|
||||||
* A QQuickImageProvider for blurhashes.
|
|
||||||
*
|
|
||||||
* @sa QQuickImageProvider
|
|
||||||
*/
|
|
||||||
class BlurhashImageProvider : public QQuickImageProvider
|
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
BlurhashImageProvider();
|
AsyncImageResponse(const QString &id, const QSize &requestedSize, QThreadPool *pool);
|
||||||
|
void handleDone(QImage image);
|
||||||
/**
|
QQuickTextureFactory *textureFactory() const override;
|
||||||
* @brief Return an image for a given ID.
|
QImage m_image;
|
||||||
*
|
};
|
||||||
* @sa QQuickImageProvider::requestImage
|
|
||||||
*/
|
class BlurHashImageProvider : public QQuickAsyncImageProvider
|
||||||
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
|
{
|
||||||
|
public:
|
||||||
|
QQuickImageResponse *requestImageResponse(const QString &id, const QSize &requestedSize) override;
|
||||||
|
|
||||||
|
private:
|
||||||
|
QThreadPool pool;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -292,7 +292,7 @@ int main(int argc, char *argv[])
|
|||||||
ShareHandler::instance().setText(parser.value(shareOption));
|
ShareHandler::instance().setText(parser.value(shareOption));
|
||||||
}
|
}
|
||||||
|
|
||||||
engine.addImageProvider(u"blurhash"_s, new BlurhashImageProvider);
|
engine.addImageProvider(u"blurhash"_s, new BlurHashImageProvider);
|
||||||
|
|
||||||
engine.loadFromModule("org.kde.neochat", "Main");
|
engine.loadFromModule("org.kde.neochat", "Main");
|
||||||
|
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include <qcoro/qcorosignal.h>
|
#include <qcoro/qcorosignal.h>
|
||||||
|
|
||||||
#include <Quotient/avatar.h>
|
#include <Quotient/avatar.h>
|
||||||
|
#include <Quotient/blurhash.h>
|
||||||
#include <Quotient/connection.h>
|
#include <Quotient/connection.h>
|
||||||
#include <Quotient/csapi/account-data.h>
|
#include <Quotient/csapi/account-data.h>
|
||||||
#include <Quotient/csapi/directory.h>
|
#include <Quotient/csapi/directory.h>
|
||||||
@@ -241,7 +242,7 @@ QCoro::Task<void> NeoChatRoom::doUploadFile(QUrl url, QString body, std::optiona
|
|||||||
EventContent::FileContentBase *content;
|
EventContent::FileContentBase *content;
|
||||||
if (mime.name().startsWith("image/"_L1)) {
|
if (mime.name().startsWith("image/"_L1)) {
|
||||||
QImage image(url.toLocalFile());
|
QImage image(url.toLocalFile());
|
||||||
content = new EventContent::ImageContent(url, fileInfo.size(), mime, image.size(), fileInfo.fileName());
|
content = new EventContent::ImageContent(url, fileInfo.size(), mime, image.size(), fileInfo.fileName(), BlurHash::encode(image));
|
||||||
} else if (mime.name().startsWith("audio/"_L1)) {
|
} else if (mime.name().startsWith("audio/"_L1)) {
|
||||||
content = new EventContent::AudioContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
content = new EventContent::AudioContent(url, fileInfo.size(), mime, fileInfo.fileName());
|
||||||
} else if (mime.name().startsWith("video/"_L1)) {
|
} else if (mime.name().startsWith("video/"_L1)) {
|
||||||
|
|||||||
Reference in New Issue
Block a user