File indexing completed on 2024-05-12 05:04:17

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