plugin/cutils: add getAverageLuminance
Fixes stutters in wallpaper list Also fix crash when saveItem target doesn't have a window
This commit is contained in:
parent
c2d6849d42
commit
d66f4ca0f3
3 changed files with 227 additions and 66 deletions
|
|
@ -39,6 +39,11 @@ void CUtils::saveItem(QQuickItem* target, const QUrl& path, const QRect& rect, Q
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!target->window()) {
|
||||||
|
qWarning() << "CUtils::saveItem: unable to save target" << target << "without a window";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
auto scaledRect = rect;
|
auto scaledRect = rect;
|
||||||
if (rect.isValid()) {
|
if (rect.isValid()) {
|
||||||
qreal scale = target->window()->devicePixelRatio();
|
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());
|
return QFile::copy(source.toLocalFile(), target.toLocalFile());
|
||||||
}
|
}
|
||||||
|
|
||||||
void CUtils::getDominantColour(QQuickItem* item, QJSValue callback) const {
|
void CUtils::getDominantColour(QQuickItem* item, QJSValue callback) {
|
||||||
this->getDominantColour(item, 128, 128, 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) {
|
if (!item) {
|
||||||
qWarning() << "CUtils::getDominantColour: an item is required";
|
qWarning() << "CUtils::getDominantColour: an item is required";
|
||||||
return;
|
return;
|
||||||
|
|
@ -116,64 +121,205 @@ void CUtils::getDominantColour(QQuickItem* item, int targetWidth, int targetHeig
|
||||||
QSharedPointer<QQuickItemGrabResult> grabResult = item->grabToImage();
|
QSharedPointer<QQuickItemGrabResult> grabResult = item->grabToImage();
|
||||||
|
|
||||||
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this,
|
QObject::connect(grabResult.data(), &QQuickItemGrabResult::ready, this,
|
||||||
[grabResult, item, targetWidth, targetHeight, callback, this]() {
|
[grabResult, rescaleSize, callback, this]() {
|
||||||
QImage image = grabResult->image();
|
QImage image = grabResult->image();
|
||||||
|
|
||||||
if (image.width() > targetWidth && image.height() > targetHeight) {
|
QThreadPool::globalInstance()->start([grabResult, image, rescaleSize, callback, this]() {
|
||||||
image = image.scaled(targetWidth, targetHeight);
|
QColor color = this->findDominantColour(image, rescaleSize);
|
||||||
} else if (image.width() > targetWidth) {
|
|
||||||
image = image.scaledToWidth(targetWidth);
|
|
||||||
} else if (image.height() > targetHeight) {
|
|
||||||
image = image.scaledToHeight(targetHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (image.format() != QImage::Format_ARGB32) {
|
if (callback.isCallable()) {
|
||||||
image = image.convertToFormat(QImage::Format_ARGB32);
|
QMetaObject::invokeMethod(this, [color, callback, this]() {
|
||||||
}
|
callback.call({ qmlEngine(this)->toScriptValue(QVariant::fromValue(color)) });
|
||||||
|
}, Qt::QueuedConnection);
|
||||||
std::unordered_map<uint32_t, int> colours;
|
|
||||||
const uchar* data = image.bits();
|
|
||||||
int width = image.width();
|
|
||||||
int height = image.height();
|
|
||||||
int bytesPerLine = image.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;
|
|
||||||
uchar r = pixel[0];
|
|
||||||
uchar g = pixel[1];
|
|
||||||
uchar b = pixel[2];
|
|
||||||
uchar a = pixel[3];
|
|
||||||
|
|
||||||
if (a == 0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
r &= 0xF8;
|
|
||||||
g &= 0xF8;
|
|
||||||
b &= 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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::getDominantColour(const QString& path, QJSValue callback) {
|
||||||
|
this->getDominantColour(path, 128, callback);
|
||||||
|
}
|
||||||
|
|
||||||
|
void CUtils::getDominantColour(const QString& path, int rescaleSize, QJSValue callback) {
|
||||||
|
if (path.isEmpty()) {
|
||||||
|
qWarning() << "CUtils::getDominantColour: given path is empty";
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QThreadPool::globalInstance()->start([path, rescaleSize, callback, this]() {
|
||||||
|
QImage image(path);
|
||||||
|
|
||||||
|
if (image.isNull()) {
|
||||||
|
qWarning() << "CUtils::getDominantColour: failed to load image" << path;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QColor color = this->findDominantColour(image, rescaleSize);
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -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) const;
|
||||||
Q_INVOKABLE bool copyFile(const QUrl& source, const QUrl& target, bool overwrite) 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, QJSValue callback);
|
||||||
Q_INVOKABLE void getDominantColour(QQuickItem* item, int width, int height, QJSValue callback) const;
|
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;
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ pragma ComponentBehavior: Bound
|
||||||
|
|
||||||
import qs.config
|
import qs.config
|
||||||
import qs.utils
|
import qs.utils
|
||||||
|
import Caelestia
|
||||||
import Quickshell
|
import Quickshell
|
||||||
import Quickshell.Io
|
import Quickshell.Io
|
||||||
import QtQuick
|
import QtQuick
|
||||||
|
|
@ -21,7 +22,7 @@ Singleton {
|
||||||
readonly property M3Palette current: M3Palette {}
|
readonly property M3Palette current: M3Palette {}
|
||||||
readonly property M3Palette preview: M3Palette {}
|
readonly property M3Palette preview: M3Palette {}
|
||||||
readonly property Transparency transparency: Transparency {}
|
readonly property Transparency transparency: Transparency {}
|
||||||
readonly property color wallColour: quantizer.colors[0] ?? "black"
|
property real wallLuminance
|
||||||
|
|
||||||
function getLuminance(c: color): real {
|
function getLuminance(c: color): real {
|
||||||
if (c.r == 0 && c.g == 0 && c.b == 0)
|
if (c.r == 0 && c.g == 0 && c.b == 0)
|
||||||
|
|
@ -30,7 +31,6 @@ Singleton {
|
||||||
}
|
}
|
||||||
|
|
||||||
function alterColour(c: color, a: real, layer: int): color {
|
function alterColour(c: color, a: real, layer: int): color {
|
||||||
const wallLuminance = getLuminance(wallColour);
|
|
||||||
const luminance = getLuminance(c);
|
const luminance = getLuminance(c);
|
||||||
|
|
||||||
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
|
const offset = (!light || layer == 1 ? 1 : -layer / 2) * (light ? 0.2 : 0.3) * (1 - transparency.base) * (1 + wallLuminance * (light ? (layer == 1 ? 3 : 1) : 2.5));
|
||||||
|
|
@ -85,12 +85,16 @@ Singleton {
|
||||||
onLoaded: root.load(text(), false)
|
onLoaded: root.load(text(), false)
|
||||||
}
|
}
|
||||||
|
|
||||||
ColorQuantizer {
|
Connections {
|
||||||
id: quantizer
|
target: Wallpapers
|
||||||
|
|
||||||
source: Qt.resolvedUrl(Wallpapers.current)
|
function onCurrentChanged(): void {
|
||||||
depth: 0
|
const current = Wallpapers.current;
|
||||||
rescaleSize: 128
|
CUtils.getAverageLuminance(current, l => {
|
||||||
|
if (Wallpapers.current == current)
|
||||||
|
root.wallLuminance = l;
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
component Transparency: QtObject {
|
component Transparency: QtObject {
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue