File indexing completed on 2024-05-12 16:28:12

0001 // SPDX-FileCopyrightText: 2021 Tobias Fella <fella@posteo.de>
0002 // SPDX-License-Identifier: LGPL-2.0-or-later
0003 
0004 #include "blurhashimageprovider.h"
0005 
0006 #include <QImage>
0007 #include <QString>
0008 
0009 #include "blurhash.hpp"
0010 
0011 /*
0012  * Qt unfortunately re-encodes the base83 string in QML.
0013  * The only special ASCII characters used in the blurhash base83 string are:
0014  * #$%*+,-.:;=?@[]^_{|}~
0015  * QUrl::fromPercentEncoding is too greedy, and spits out invalid characters
0016  * for parts of valid base83 like %14.
0017  */
0018 // clang-format off
0019 static const QMap<QLatin1String, QLatin1String> knownEncodings = {
0020     {QLatin1String("%23A"), QLatin1String(":")},
0021     {QLatin1String("%3F"), QLatin1String("?")},
0022     {QLatin1String("%23"), QLatin1String("#")},
0023     {QLatin1String("%5B"), QLatin1String("[")},
0024     {QLatin1String("%5D"), QLatin1String("]")},
0025     {QLatin1String("%40"), QLatin1String("@")},
0026     {QLatin1String("%24"), QLatin1String("$")},
0027     {QLatin1String("%2A"), QLatin1String("*")},
0028     {QLatin1String("%2B"), QLatin1String("+")},
0029     {QLatin1String("%2C"), QLatin1String(",")},
0030     {QLatin1String("%2D"), QLatin1String("-")},
0031     {QLatin1String("%2E"), QLatin1String(".")},
0032     {QLatin1String("%3D"), QLatin1String("=")},
0033     {QLatin1String("%25"), QLatin1String("%")},
0034     {QLatin1String("%5E"), QLatin1String("^")},
0035     {QLatin1String("%7C"), QLatin1String("|")},
0036     {QLatin1String("%7B"), QLatin1String("{")},
0037     {QLatin1String("%7D"), QLatin1String("}")},
0038     {QLatin1String("%7E"), QLatin1String("~")},
0039 };
0040 // clang-format on
0041 
0042 class AsyncImageResponseRunnable : public QObject, public QRunnable
0043 {
0044     Q_OBJECT
0045 
0046 Q_SIGNALS:
0047     void done(QImage image);
0048 
0049 public:
0050     AsyncImageResponseRunnable(const QString &id, const QSize &requestedSize)
0051         : m_id(id)
0052         , m_requestedSize(requestedSize)
0053     {
0054         if (m_requestedSize.width() == -1) {
0055             m_requestedSize.setWidth(64);
0056         }
0057         if (m_requestedSize.height() == -1) {
0058             m_requestedSize.setHeight(64);
0059         }
0060     }
0061 
0062     void run() override
0063     {
0064         if (m_id.isEmpty()) {
0065             return;
0066         }
0067 
0068         QString decodedId = m_id;
0069 
0070         QMap<QLatin1String, QLatin1String>::const_iterator i;
0071         for (i = knownEncodings.constBegin(); i != knownEncodings.constEnd(); ++i)
0072             decodedId.replace(i.key(), i.value());
0073 
0074         auto data = blurhash::decode(decodedId.toLatin1().constData(), m_requestedSize.width(), m_requestedSize.height());
0075         QImage image(data.image.data(), static_cast<int>(data.width), static_cast<int>(data.height), static_cast<int>(data.width * 3), QImage::Format_RGB888);
0076 
0077         Q_EMIT done(image.convertToFormat(QImage::Format_RGB32));
0078     }
0079 
0080 private:
0081     QString m_id;
0082     QSize m_requestedSize;
0083 };
0084 
0085 AsyncImageResponse::AsyncImageResponse(const QString &id, const QSize &requestedSize, QThreadPool *pool)
0086 {
0087     auto runnable = new AsyncImageResponseRunnable(id, requestedSize);
0088     connect(runnable, &AsyncImageResponseRunnable::done, this, &AsyncImageResponse::handleDone);
0089     pool->start(runnable);
0090 }
0091 
0092 void AsyncImageResponse::handleDone(QImage image)
0093 {
0094     m_image = image;
0095     Q_EMIT finished();
0096 }
0097 
0098 QQuickTextureFactory *AsyncImageResponse::textureFactory() const
0099 {
0100     return QQuickTextureFactory::textureFactoryForImage(m_image);
0101 }
0102 
0103 QQuickImageResponse *BlurhashImageProvider::requestImageResponse(const QString &id, const QSize &requestedSize)
0104 {
0105     return new AsyncImageResponse(id, requestedSize, &pool);
0106 }
0107 
0108 #include "blurhashimageprovider.moc"