diff options
| author | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-08-31 14:34:22 +1000 |
|---|---|---|
| committer | 2 * r + 2 * t <61896496+soramanew@users.noreply.github.com> | 2025-08-31 14:34:22 +1000 |
| commit | d66f4ca0f37d585a86145c0a27e79e89c3a815c8 (patch) | |
| tree | b5cebc4457e9c02f4eb20ff23abbe0613375b21e /plugin/src/Caelestia | |
| parent | [CI] chore: update flake (diff) | |
| download | caelestia-shell-d66f4ca0f37d585a86145c0a27e79e89c3a815c8.tar.gz caelestia-shell-d66f4ca0f37d585a86145c0a27e79e89c3a815c8.tar.bz2 caelestia-shell-d66f4ca0f37d585a86145c0a27e79e89c3a815c8.zip | |
plugin/cutils: add getAverageLuminance
Fixes stutters in wallpaper list
Also fix crash when saveItem target doesn't have a window
Diffstat (limited to '')
| -rw-r--r-- | plugin/src/Caelestia/cutils.cpp | 244 | ||||
| -rw-r--r-- | plugin/src/Caelestia/cutils.hpp | 15 |
2 files changed, 208 insertions, 51 deletions
diff --git a/plugin/src/Caelestia/cutils.cpp b/plugin/src/Caelestia/cutils.cpp index f6ca0bf..50ac312 100644 --- a/plugin/src/Caelestia/cutils.cpp +++ b/plugin/src/Caelestia/cutils.cpp @@ -39,6 +39,11 @@ void CUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q return; } + if (!target->window()) { + qWarning() << "CUtils::saveItem: unable to save target" << target << "without a window"; + return; + } + auto scaledRect = rect; if (rect.isValid()) { qreal scale = target->window()->devicePixelRatio(); @@ -98,11 +103,11 @@ bool CUtils::copyFile(const QUrl& source, const QUrl& target, bool overwrite) co return QFile::copy(source.toLocalFile(), target.toLocalFile()); } -void CUtils::getDominantColour(QQuickItem* item, QJSValue callback) const { - this->getDominantColour(item, 128, 128, callback); +void CUtils::getDominantColour(QQuickItem* item, QJSValue callback) { + this->getDominantColour(item, 128, callback); } -void CUtils::getDominantColour(QQuickItem* item, int targetWidth, int targetHeight, QJSValue callback) const { +void CUtils::getDominantColour(QQuickItem* item, int rescaleSize, QJSValue callback) { if (!item) { qWarning() << "CUtils::getDominantColour: an item is required"; return; @@ -116,64 +121,205 @@ void CUtils::getDominantColour(QQuickItem* item, int targetWidth, int targetHeig QSharedPointer<QQuickItemGrabResult> grabResult = item->grabToImage(); QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, - [grabResult, item, targetWidth, targetHeight, callback, this]() { + [grabResult, rescaleSize, callback, this]() { QImage image = grabResult->image(); - if (image.width() > targetWidth && image.height() > targetHeight) { - image = image.scaled(targetWidth, targetHeight); - } else if (image.width() > targetWidth) { - image = image.scaledToWidth(targetWidth); - } else if (image.height() > targetHeight) { - image = image.scaledToHeight(targetHeight); - } + QThreadPool::globalInstance()->start([grabResult, image, rescaleSize, callback, this]() { + QColor color = this->findDominantColour(image, rescaleSize); - if (image.format() != QImage::Format_ARGB32) { - image = image.convertToFormat(QImage::Format_ARGB32); - } + if (callback.isCallable()) { + QMetaObject::invokeMethod(this, [color, callback, this]() { + callback.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(color)) }); + }, Qt::QueuedConnection); + } + }); + } + ); +} + +void CUtils::getDominantColour(const QString& path, QJSValue callback) { + this->getDominantColour(path, 128, callback); +} - std::unordered_map<uint32_t, int> colours; - const uchar* data = image.bits(); - int width = image.width(); - int height = image.height(); - int bytesPerLine = image.bytesPerLine(); +void CUtils::getDominantColour(const QString& path, int rescaleSize, QJSValue callback) { + if (path.isEmpty()) { + qWarning() << "CUtils::getDominantColour: given path is empty"; + return; + } - for (int y = 0; y < height; ++y) { - const uchar* line = data + y * bytesPerLine; - for (int x = 0; x < width; ++x) { - const uchar* pixel = line + x * 4; - uchar r = pixel[0]; - uchar g = pixel[1]; - uchar b = pixel[2]; - uchar a = pixel[3]; + QThreadPool::globalInstance()->start([path, rescaleSize, callback, this]() { + QImage image(path); - if (a == 0) { - continue; - } + if (image.isNull()) { + qWarning() << "CUtils::getDominantColour: failed to load image" << path; + return; + } - r &= 0xF8; - g &= 0xF8; - b &= 0xF8; + QColor color = this->findDominantColour(image, rescaleSize); - uint32_t colour = (r << 16) | (g << 8) | b; - ++colours[colour]; - } + if (callback.isCallable()) { + QMetaObject::invokeMethod(this, [color, callback, this]() { + callback.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(color)) }); + }, Qt::QueuedConnection); + } + }); +} + +QColor CUtils::findDominantColour(const QImage& image, int rescaleSize) const { + if (image.isNull()) { + qWarning() << "CUtils::findDominantColour: image is null"; + return QColor(); + } + + QImage img = image; + + if (rescaleSize > 0 && (img.width() > rescaleSize || img.height() > rescaleSize)) { + img = img.scaled(rescaleSize, rescaleSize, Qt::KeepAspectRatio, Qt::FastTransformation); + } + + if (img.format() != QImage::Format_ARGB32) { + img = img.convertToFormat(QImage::Format_ARGB32); + } + + std::unordered_map<uint32_t, int> colours; + const uchar* data = img.bits(); + int width = img.width(); + int height = img.height(); + int bytesPerLine = img.bytesPerLine(); + + for (int y = 0; y < height; ++y) { + const uchar* line = data + y * bytesPerLine; + for (int x = 0; x < width; ++x) { + const uchar* pixel = line + x * 4; + + if (pixel[3] == 0) { + continue; } - uint32_t dominantColour = 0; - int maxCount = 0; - for (const auto& [colour, count] : colours) { - if (count > maxCount) { - dominantColour = colour; - maxCount = count; + uchar r = pixel[0] & 0xF8; + uchar g = pixel[1] & 0xF8; + uchar b = pixel[2] & 0xF8; + + uint32_t colour = (r << 16) | (g << 8) | b; + ++colours[colour]; + } + } + + uint32_t dominantColour = 0; + int maxCount = 0; + for (const auto& [colour, count] : colours) { + if (count > maxCount) { + dominantColour = colour; + maxCount = count; + } + } + + return QColor((0xFF << 24) | dominantColour); +} + +void CUtils::getAverageLuminance(QQuickItem* item, QJSValue callback) { + this->getAverageLuminance(item, 128, callback); +} + +void CUtils::getAverageLuminance(QQuickItem* item, int rescaleSize, QJSValue callback) { + if (!item) { + qWarning() << "CUtils::getAverageLuminance: an item is required"; + return; + } + + if (!item->window()) { + // Fail silently to avoid warning + return; + } + + QSharedPointer<QQuickItemGrabResult> grabResult = item->grabToImage(); + + QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this, + [grabResult, rescaleSize, callback, this]() { + QImage image = grabResult->image(); + + QThreadPool::globalInstance()->start([grabResult, image, rescaleSize, callback, this]() { + qreal luminance = this->findAverageLuminance(image, rescaleSize); + + if (callback.isCallable()) { + QMetaObject::invokeMethod(this, [luminance, callback, this]() { + callback.call({ QJSValue(luminance) }); + }, Qt::QueuedConnection); } - } + }); + } + ); +} - const QColor colour = QColor((0xFF << 24) | dominantColour); - if (callback.isCallable()) { - QMetaObject::invokeMethod(item, [item, callback, colour, this]() { - callback.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(colour)) }); - }, Qt::QueuedConnection); +void CUtils::getAverageLuminance(const QString& path, QJSValue callback) { + this->getAverageLuminance(path, 128, callback); +} + +void CUtils::getAverageLuminance(const QString& path, int rescaleSize, QJSValue callback) { + if (path.isEmpty()) { + qWarning() << "CUtils::getAverageLuminance: given path is empty"; + return; + } + + QThreadPool::globalInstance()->start([path, rescaleSize, callback, this]() { + QImage image(path); + + if (image.isNull()) { + qWarning() << "CUtils::getAverageLuminance: failed to load image" << path; + return; + } + + qreal luminance = this->findAverageLuminance(image, rescaleSize); + + if (callback.isCallable()) { + QMetaObject::invokeMethod(this, [luminance, callback, this]() { + callback.call({ QJSValue(luminance) }); + }, Qt::QueuedConnection); + } + }); +} + +qreal CUtils::findAverageLuminance(const QImage& image, int rescaleSize) const { + if (image.isNull()) { + qWarning() << "CUtils::findAverageLuminance: image is null"; + return 0.0; + } + + QImage img = image; + + if (rescaleSize > 0 && (img.width() > rescaleSize || img.height() > rescaleSize)) { + img = img.scaled(rescaleSize, rescaleSize, Qt::KeepAspectRatio, Qt::FastTransformation); + } + + if (img.format() != QImage::Format_ARGB32) { + img = img.convertToFormat(QImage::Format_ARGB32); + } + + const uchar* data = img.bits(); + int width = img.width(); + int height = img.height(); + int bytesPerLine = img.bytesPerLine(); + + qreal totalLuminance = 0.0; + int count = 0; + + for (int y = 0; y < height; ++y) { + const uchar* line = data + y * bytesPerLine; + for (int x = 0; x < width; ++x) { + const uchar* pixel = line + x * 4; + + if (pixel[3] == 0) { + continue; } + + qreal r = pixel[0] / 255.0; + qreal g = pixel[1] / 255.0; + qreal b = pixel[2] / 255.0; + + totalLuminance += std::sqrt(0.299 * r * r + 0.587 * g * g + 0.114 * b * b); + ++count; } - ); + } + + return count == 0 ? 0.0 : totalLuminance / count; } diff --git a/plugin/src/Caelestia/cutils.hpp b/plugin/src/Caelestia/cutils.hpp index 08fad41..048a2ff 100644 --- a/plugin/src/Caelestia/cutils.hpp +++ b/plugin/src/Caelestia/cutils.hpp @@ -20,6 +20,17 @@ public: Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target) const; Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite) const; - Q_INVOKABLE void getDominantColour(QQuickItem* item, QJSValue callback) const; - Q_INVOKABLE void getDominantColour(QQuickItem* item, int width, int height, QJSValue callback) const; + Q_INVOKABLE void getDominantColour(QQuickItem* item, QJSValue callback); + Q_INVOKABLE void getDominantColour(QQuickItem* item, int rescaleSize, QJSValue callback); + Q_INVOKABLE void getDominantColour(const QString& path, QJSValue callback); + Q_INVOKABLE void getDominantColour(const QString& path, int rescaleSize, QJSValue callback); + + Q_INVOKABLE void getAverageLuminance(QQuickItem* item, QJSValue callback); + Q_INVOKABLE void getAverageLuminance(QQuickItem* item, int rescaleSize, QJSValue callback); + Q_INVOKABLE void getAverageLuminance(const QString& path, QJSValue callback); + Q_INVOKABLE void getAverageLuminance(const QString& path, int rescaleSize, QJSValue callback); + +private: + QColor findDominantColour(const QImage& image, int rescaleSize) const; + qreal findAverageLuminance(const QImage& image, int rescaleSize) const; }; |