Show blurhashes for image events
This commit is contained in:
19
LICENSES/MIT.txt
Executable file
19
LICENSES/MIT.txt
Executable file
@@ -0,0 +1,19 @@
|
|||||||
|
MIT License Copyright (c) <year> <copyright holders>
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is furnished
|
||||||
|
to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice (including the next
|
||||||
|
paragraph) shall be included in all copies or substantial portions of the
|
||||||
|
Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
||||||
|
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS
|
||||||
|
OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF
|
||||||
|
OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
@@ -11,6 +11,9 @@ ApplicationWindow {
|
|||||||
|
|
||||||
property string filename
|
property string filename
|
||||||
property url localPath
|
property url localPath
|
||||||
|
property string blurhash: ""
|
||||||
|
property int imageWidth: -1
|
||||||
|
property int imageHeight: -1
|
||||||
|
|
||||||
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
flags: Qt.FramelessWindowHint | Qt.WA_TranslucentBackground
|
||||||
visibility: Qt.WindowFullScreen
|
visibility: Qt.WindowFullScreen
|
||||||
@@ -29,22 +32,29 @@ ApplicationWindow {
|
|||||||
}
|
}
|
||||||
|
|
||||||
BusyIndicator {
|
BusyIndicator {
|
||||||
visible: image.status !== Image.Ready
|
visible: image.status !== Image.Ready && root.blurhash === ""
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
running: visible
|
running: visible
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedImage {
|
AnimatedImage {
|
||||||
id: image
|
id: image
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
|
|
||||||
width: Math.min(sourceSize.width, root.width)
|
width: Math.min(root.imageWidth !== -1 ? root.imageWidth : sourceSize.width, root.width)
|
||||||
height: Math.min(sourceSize.height, root.height)
|
height: Math.min(root.imageHeight !== -1 ? root.imageWidth : sourceSize.height, root.height)
|
||||||
|
|
||||||
cache: false
|
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
source: localPath
|
source: localPath
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.centerIn: parent
|
||||||
|
width: image.width
|
||||||
|
height: image.height
|
||||||
|
source: root.blurhash !== "" ? ("image://blurhash/" + root.blurhash) : ""
|
||||||
|
visible: root.blurhash !== "" && parent.status !== Image.Ready
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Button {
|
Button {
|
||||||
|
|||||||
@@ -29,6 +29,12 @@ Image {
|
|||||||
|
|
||||||
source: "image://mxc/" + mediaId
|
source: "image://mxc/" + mediaId
|
||||||
|
|
||||||
|
Image {
|
||||||
|
anchors.fill: parent
|
||||||
|
source: "image://blurhash/" + content.info["xyz.amorgan.blurhash"]
|
||||||
|
visible: parent.status !== Image.Ready
|
||||||
|
}
|
||||||
|
|
||||||
fillMode: Image.PreserveAspectFit
|
fillMode: Image.PreserveAspectFit
|
||||||
|
|
||||||
ToolTip.text: display
|
ToolTip.text: display
|
||||||
|
|||||||
@@ -457,7 +457,7 @@ Kirigami.ScrollablePage {
|
|||||||
acceptedButtons: Qt.LeftButton
|
acceptedButtons: Qt.LeftButton
|
||||||
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
onLongPressed: openFileContext(author, model.display, eventId, toolTip, progressInfo, parent)
|
||||||
onTapped: {
|
onTapped: {
|
||||||
fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId)}).showFullScreen()
|
fullScreenImage.createObject(parent, {"filename": eventId, "localPath": currentRoom.urlToDownload(eventId), "blurhash": model.content.info["xyz.amorgan.blurhash"], "imageWidth": content.info.w, "imageHeight": content.info.h}).showFullScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -34,6 +34,8 @@ add_executable(neochat
|
|||||||
commandmodel.cpp
|
commandmodel.cpp
|
||||||
webshortcutmodel.cpp
|
webshortcutmodel.cpp
|
||||||
spellcheckhighlighter.cpp
|
spellcheckhighlighter.cpp
|
||||||
|
blurhash.cpp
|
||||||
|
blurhashimageprovider.cpp
|
||||||
../res.qrc
|
../res.qrc
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
186
src/blurhash.cpp
Normal file
186
src/blurhash.cpp
Normal file
@@ -0,0 +1,186 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2018 Wolt Enterprises
|
||||||
|
// SPDX-License-Identifiert: MIT
|
||||||
|
|
||||||
|
#include "blurhash.h"
|
||||||
|
|
||||||
|
static char chars[] = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz#$%*+,-.:;=?@[]^_{|}~";
|
||||||
|
|
||||||
|
static 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
static 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline float signPow(float value, float exp)
|
||||||
|
{
|
||||||
|
return copysignf(powf(fabsf(value), exp), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
static inline uint8_t clampToUByte(int *src)
|
||||||
|
{
|
||||||
|
if (*src >= 0 && *src <= 255) {
|
||||||
|
return *src;
|
||||||
|
}
|
||||||
|
return (*src < 0) ? 0 : 255;
|
||||||
|
}
|
||||||
|
|
||||||
|
static 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void decodeDC(int value, float *r, float *g, float *b)
|
||||||
|
{
|
||||||
|
*r = sRGBToLinear(value >> 16);
|
||||||
|
*g = sRGBToLinear((value >> 8) & 255);
|
||||||
|
*b = sRGBToLinear(value & 255);
|
||||||
|
}
|
||||||
|
|
||||||
|
void decodeAC(int value, float maximumValue, float *r, float *g, float *b)
|
||||||
|
{
|
||||||
|
int quantR = (int)floorf(value / (19 * 19));
|
||||||
|
int quantG = (int)floorf(value / 19) % 19;
|
||||||
|
int quantB = (int)value % 19;
|
||||||
|
|
||||||
|
*r = signPow(((float)quantR - 9) / 9, 2.0) * maximumValue;
|
||||||
|
*g = signPow(((float)quantG - 9) / 9, 2.0) * maximumValue;
|
||||||
|
*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;
|
||||||
|
|
||||||
|
float r = 0, g = 0, b = 0;
|
||||||
|
int quantizedMaxValue = decodeToInt(blurhash, 1, 2);
|
||||||
|
if (quantizedMaxValue == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
float maxValue = ((float)(quantizedMaxValue + 1)) / 166;
|
||||||
|
|
||||||
|
int colors_size = numX * numY;
|
||||||
|
float colors[colors_size][3];
|
||||||
|
|
||||||
|
for (iter = 0; iter < colors_size; iter++) {
|
||||||
|
if (iter == 0) {
|
||||||
|
int value = decodeToInt(blurhash, 2, 6);
|
||||||
|
if (value == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
decodeDC(value, &r, &g, &b);
|
||||||
|
colors[iter][0] = r;
|
||||||
|
colors[iter][1] = g;
|
||||||
|
colors[iter][2] = b;
|
||||||
|
} else {
|
||||||
|
int value = decodeToInt(blurhash, 4 + iter * 2, 6 + iter * 2);
|
||||||
|
if (value == -1) {
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
decodeAC(value, maxValue * punch, &r, &g, &b);
|
||||||
|
colors[iter][0] = r;
|
||||||
|
colors[iter][1] = g;
|
||||||
|
colors[iter][2] = b;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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][0] * basics;
|
||||||
|
g += colors[idx][1] * basics;
|
||||||
|
b += colors[idx][2] * 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 NULL;
|
||||||
|
}
|
||||||
|
return pixelArray;
|
||||||
|
}
|
||||||
14
src/blurhash.h
Normal file
14
src/blurhash.h
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2018 Wolt Enterprises
|
||||||
|
// SPDX-License-Identifiert: MIT
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <math.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
uint8_t *decode(const char *blurhash, int width, int height, int punch, int nChannels);
|
||||||
|
|
||||||
|
bool isValidBlurhash(const char *blurhash);
|
||||||
31
src/blurhashimageprovider.cpp
Normal file
31
src/blurhashimageprovider.cpp
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#include "blurhashimageprovider.h"
|
||||||
|
|
||||||
|
#include <QImage>
|
||||||
|
#include <QString>
|
||||||
|
|
||||||
|
#include "blurhash.h"
|
||||||
|
|
||||||
|
BlurhashImageProvider::BlurhashImageProvider()
|
||||||
|
: QQuickImageProvider(QQuickImageProvider::Image)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
QImage BlurhashImageProvider::requestImage(const QString &id, QSize *size, const QSize &requestedSize)
|
||||||
|
{
|
||||||
|
if (id.isEmpty()) {
|
||||||
|
return QImage();
|
||||||
|
}
|
||||||
|
*size = requestedSize;
|
||||||
|
if (size->width() == -1) {
|
||||||
|
size->setWidth(256);
|
||||||
|
}
|
||||||
|
if (size->height() == -1) {
|
||||||
|
size->setHeight(256);
|
||||||
|
}
|
||||||
|
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 image;
|
||||||
|
}
|
||||||
13
src/blurhashimageprovider.h
Normal file
13
src/blurhashimageprovider.h
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
|
||||||
|
// SPDX-License-Identifier: LGPL-2.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <QQuickImageProvider>
|
||||||
|
|
||||||
|
class BlurhashImageProvider : public QQuickImageProvider
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
BlurhashImageProvider();
|
||||||
|
QImage requestImage(const QString &id, QSize *size, const QSize &requestedSize) override;
|
||||||
|
};
|
||||||
@@ -59,6 +59,7 @@
|
|||||||
#include "webshortcutmodel.h"
|
#include "webshortcutmodel.h"
|
||||||
#include "spellcheckhighlighter.h"
|
#include "spellcheckhighlighter.h"
|
||||||
#include "customemojimodel.h"
|
#include "customemojimodel.h"
|
||||||
|
#include "blurhashimageprovider.h"
|
||||||
#ifdef HAVE_COLORSCHEME
|
#ifdef HAVE_COLORSCHEME
|
||||||
#include "colorschemer.h"
|
#include "colorschemer.h"
|
||||||
#endif
|
#endif
|
||||||
@@ -212,6 +213,7 @@ int main(int argc, char *argv[])
|
|||||||
|
|
||||||
engine.addImportPath("qrc:/imports");
|
engine.addImportPath("qrc:/imports");
|
||||||
engine.addImageProvider(QLatin1String("mxc"), new MatrixImageProvider);
|
engine.addImageProvider(QLatin1String("mxc"), new MatrixImageProvider);
|
||||||
|
engine.addImageProvider(QLatin1String("blurhash"), new BlurhashImageProvider);
|
||||||
|
|
||||||
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
|
engine.load(QUrl(QStringLiteral("qrc:/qml/main.qml")));
|
||||||
if (engine.rootObjects().isEmpty()) {
|
if (engine.rootObjects().isEmpty()) {
|
||||||
|
|||||||
Reference in New Issue
Block a user