forked from quickshell/quickshell
fix: address review requests
This commit is contained in:
parent
f17bc07da3
commit
b7eb562abc
|
@ -1,32 +1,33 @@
|
||||||
#include "colorquantizer.hpp"
|
#include "colorquantizer.hpp"
|
||||||
|
|
||||||
#include <qcolor.h>
|
#include <qcolor.h>
|
||||||
|
#include <qlogging.h>
|
||||||
|
#include <qloggingcategory.h>
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
#include <qqmllist.h>
|
#include <qqmllist.h>
|
||||||
#include <qthreadpool.h>
|
#include <qthreadpool.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
|
||||||
|
namespace {
|
||||||
|
Q_LOGGING_CATEGORY(logColorQuantizer, "quickshell.colorquantizer", QtWarningMsg);
|
||||||
|
}
|
||||||
|
|
||||||
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
|
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
|
||||||
: source(source)
|
: source(source)
|
||||||
, maxDepth(depth)
|
, maxDepth(depth)
|
||||||
, rescaleSize(rescaleSize) {
|
, rescaleSize(rescaleSize) {
|
||||||
mColors = QList<QColor>();
|
setAutoDelete(false);
|
||||||
|
colors = QList<QColor>();
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizerOperation::run() {
|
void ColorQuantizerOperation::quantizeImage(const QAtomicInteger<bool>& shouldCancel) {
|
||||||
quantizeImage();
|
if (shouldCancel.loadAcquire()) return;
|
||||||
|
|
||||||
emit done(mColors);
|
colors.clear();
|
||||||
}
|
|
||||||
|
|
||||||
void ColorQuantizerOperation::quantizeImage() {
|
if (source->isEmpty()) return;
|
||||||
mColors.clear();
|
|
||||||
|
|
||||||
if (source->isEmpty()) {
|
auto image = QImage(source->toLocalFile());
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
QImage image(source->toLocalFile());
|
|
||||||
|
|
||||||
if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
|
if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
|
||||||
image = image.scaled(
|
image = image.scaled(
|
||||||
|
@ -38,48 +39,52 @@ void ColorQuantizerOperation::quantizeImage() {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (image.isNull()) {
|
if (image.isNull()) {
|
||||||
qWarning() << "Failed to load image from" << source;
|
qCWarning(logColorQuantizer) << "Failed to load image from" << source;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QColor> pixels;
|
QList<QColor> pixels;
|
||||||
for (int y = 0; y < image.height(); ++y) {
|
for (int y = 0; y != image.height(); ++y) {
|
||||||
for (int x = 0; x < image.width(); ++x) {
|
for (int x = 0; x != image.width(); ++x) {
|
||||||
QRgb pixel = image.pixel(x, y);
|
auto pixel = image.pixel(x, y);
|
||||||
if (qAlpha(pixel) == 0) {
|
if (qAlpha(pixel) == 0) continue;
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
pixels.append(QColor::fromRgb(pixel));
|
pixels.append(QColor::fromRgb(pixel));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QDateTime startTime = QDateTime::currentDateTime();
|
auto startTime = QDateTime::currentDateTime();
|
||||||
|
|
||||||
mColors = quantization(pixels, 0);
|
colors = quantization(pixels, 0);
|
||||||
|
|
||||||
QDateTime endTime = QDateTime::currentDateTime();
|
auto endTime = QDateTime::currentDateTime();
|
||||||
qint64 milliseconds = startTime.msecsTo(endTime);
|
auto milliseconds = startTime.msecsTo(endTime);
|
||||||
qDebug() << "Color Quantization took: " << milliseconds << "ms";
|
qDebug() << "Color Quantization took: " << milliseconds << "ms";
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QColor> ColorQuantizerOperation::quantization(QList<QColor>& rgbValues, qreal depth) {
|
QList<QColor> ColorQuantizerOperation::quantization(
|
||||||
|
QList<QColor>& rgbValues,
|
||||||
|
qreal depth,
|
||||||
|
const QAtomicInteger<bool>& shouldCancel
|
||||||
|
) {
|
||||||
|
if (shouldCancel.loadAcquire()) return QList<QColor>();
|
||||||
|
|
||||||
if (depth >= maxDepth || rgbValues.isEmpty()) {
|
if (depth >= maxDepth || rgbValues.isEmpty()) {
|
||||||
if (rgbValues.isEmpty()) {
|
if (rgbValues.isEmpty()) return QList<QColor>();
|
||||||
return QList<QColor>();
|
|
||||||
}
|
|
||||||
|
|
||||||
int totalR = 0;
|
auto totalR = 0;
|
||||||
int totalG = 0;
|
auto totalG = 0;
|
||||||
int totalB = 0;
|
auto totalB = 0;
|
||||||
|
|
||||||
|
for (const auto& color: rgbValues) {
|
||||||
|
if (shouldCancel.loadAcquire()) return QList<QColor>();
|
||||||
|
|
||||||
for (const QColor& color: rgbValues) {
|
|
||||||
totalR += color.red();
|
totalR += color.red();
|
||||||
totalG += color.green();
|
totalG += color.green();
|
||||||
totalB += color.blue();
|
totalB += color.blue();
|
||||||
}
|
}
|
||||||
|
|
||||||
QColor avgColor(
|
auto avgColor = QColor(
|
||||||
qRound(totalR / static_cast<double>(rgbValues.size())),
|
qRound(totalR / static_cast<double>(rgbValues.size())),
|
||||||
qRound(totalG / static_cast<double>(rgbValues.size())),
|
qRound(totalG / static_cast<double>(rgbValues.size())),
|
||||||
qRound(totalB / static_cast<double>(rgbValues.size()))
|
qRound(totalB / static_cast<double>(rgbValues.size()))
|
||||||
|
@ -88,17 +93,17 @@ QList<QColor> ColorQuantizerOperation::quantization(QList<QColor>& rgbValues, qr
|
||||||
return QList<QColor>() << avgColor;
|
return QList<QColor>() << avgColor;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString dominantChannel = findBiggestColorRange(rgbValues);
|
auto dominantChannel = findBiggestColorRange(rgbValues);
|
||||||
std::ranges::sort(rgbValues, [dominantChannel](const QColor& a, const QColor& b) {
|
std::ranges::sort(rgbValues, [dominantChannel](const auto& a, const auto& b) {
|
||||||
if (dominantChannel == "r") return a.red() < b.red();
|
if (dominantChannel == "r") return a.red() < b.red();
|
||||||
else if (dominantChannel == "g") return a.green() < b.green();
|
else if (dominantChannel == "g") return a.green() < b.green();
|
||||||
return a.blue() < b.blue();
|
return a.blue() < b.blue();
|
||||||
});
|
});
|
||||||
|
|
||||||
qsizetype mid = rgbValues.size() / 2;
|
auto mid = rgbValues.size() / 2;
|
||||||
|
|
||||||
QList<QColor> leftHalf = rgbValues.mid(0, mid);
|
auto leftHalf = rgbValues.mid(0, mid);
|
||||||
QList<QColor> rightHalf = rgbValues.mid(mid + 1);
|
auto rightHalf = rgbValues.mid(mid);
|
||||||
|
|
||||||
QList<QColor> result;
|
QList<QColor> result;
|
||||||
result.append(quantization(leftHalf, depth + 1));
|
result.append(quantization(leftHalf, depth + 1));
|
||||||
|
@ -107,17 +112,17 @@ QList<QColor> ColorQuantizerOperation::quantization(QList<QColor>& rgbValues, qr
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
QString ColorQuantizerOperation::findBiggestColorRange(const QList<QColor>& rgbValues) {
|
QChar ColorQuantizerOperation::findBiggestColorRange(const QList<QColor>& rgbValues) {
|
||||||
if (rgbValues.isEmpty()) return "r";
|
if (rgbValues.isEmpty()) return 'r';
|
||||||
|
|
||||||
int rMin = 255;
|
auto rMin = 255;
|
||||||
int gMin = 255;
|
auto gMin = 255;
|
||||||
int bMin = 255;
|
auto bMin = 255;
|
||||||
int rMax = 0;
|
auto rMax = 0;
|
||||||
int gMax = 0;
|
auto gMax = 0;
|
||||||
int bMax = 0;
|
auto bMax = 0;
|
||||||
|
|
||||||
for (const QColor& color: rgbValues) {
|
for (const auto& color: rgbValues) {
|
||||||
rMin = qMin(rMin, color.red());
|
rMin = qMin(rMin, color.red());
|
||||||
gMin = qMin(gMin, color.green());
|
gMin = qMin(gMin, color.green());
|
||||||
bMin = qMin(bMin, color.blue());
|
bMin = qMin(bMin, color.blue());
|
||||||
|
@ -127,56 +132,101 @@ QString ColorQuantizerOperation::findBiggestColorRange(const QList<QColor>& rgbV
|
||||||
bMax = qMax(bMax, color.blue());
|
bMax = qMax(bMax, color.blue());
|
||||||
}
|
}
|
||||||
|
|
||||||
int rRange = rMax - rMin;
|
auto rRange = rMax - rMin;
|
||||||
int gRange = gMax - gMin;
|
auto gRange = gMax - gMin;
|
||||||
int bRange = bMax - bMin;
|
auto bRange = bMax - bMin;
|
||||||
|
|
||||||
int biggestRange = qMax(rRange, qMax(gRange, bRange));
|
auto biggestRange = qMax(rRange, qMax(gRange, bRange));
|
||||||
if (biggestRange == rRange) {
|
if (biggestRange == rRange) {
|
||||||
return "r";
|
return 'r';
|
||||||
} else if (biggestRange == gRange) {
|
} else if (biggestRange == gRange) {
|
||||||
return "g";
|
return 'g';
|
||||||
} else {
|
} else {
|
||||||
return "b";
|
return 'b';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
QList<QColor> ColorQuantizer::colors() { return mColors; }
|
void ColorQuantizerOperation::finishRun() {
|
||||||
|
QMetaObject::invokeMethod(this, &ColorQuantizerOperation::finished, Qt::QueuedConnection);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorQuantizerOperation::finished() {
|
||||||
|
emit this->done(colors);
|
||||||
|
delete this;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorQuantizerOperation::run() {
|
||||||
|
if (!this->shouldCancel) {
|
||||||
|
this->quantizeImage();
|
||||||
|
|
||||||
|
if (this->shouldCancel.loadAcquire()) {
|
||||||
|
qCDebug(logColorQuantizer) << "Color quantization" << this << "cancelled";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this->finishRun();
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorQuantizerOperation::tryCancel() { this->shouldCancel.storeRelease(true); }
|
||||||
|
|
||||||
|
void ColorQuantizer::componentComplete() {
|
||||||
|
componentCompleted = true;
|
||||||
|
if (!mSource.isEmpty()) quantizeAsync();
|
||||||
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setSource(const QUrl& source) {
|
void ColorQuantizer::setSource(const QUrl& source) {
|
||||||
if (mSource != source) {
|
if (mSource != source) {
|
||||||
mSource = source;
|
mSource = source;
|
||||||
emit sourceChanged();
|
emit this->sourceChanged();
|
||||||
quantizeAsync();
|
|
||||||
|
if (this->componentCompleted && !mSource.isEmpty()) quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setDepth(qreal depth) {
|
void ColorQuantizer::setDepth(qreal depth) {
|
||||||
if (mDepth != depth) {
|
if (mDepth != depth) {
|
||||||
mDepth = depth;
|
mDepth = depth;
|
||||||
emit depthChanged();
|
emit this->depthChanged();
|
||||||
|
|
||||||
|
if (this->componentCompleted) quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
||||||
if (mRescaleSize != rescaleSize) {
|
if (mRescaleSize != rescaleSize) {
|
||||||
mRescaleSize = rescaleSize;
|
mRescaleSize = rescaleSize;
|
||||||
emit rescaleSizeChanged();
|
emit this->rescaleSizeChanged();
|
||||||
|
|
||||||
|
if (this->componentCompleted) quantizeAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
|
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
|
||||||
mColors = result;
|
bColors = result;
|
||||||
emit colorsChanged();
|
emit this->colorsChanged();
|
||||||
isProcessing = false;
|
this->liveOperation = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ColorQuantizer::quantizeAsync() {
|
void ColorQuantizer::quantizeAsync() {
|
||||||
if (isProcessing) return;
|
if (this->liveOperation) this->cancelAsync();
|
||||||
|
|
||||||
qDebug() << "Starting color quantization asynchronously";
|
qCDebug(logColorQuantizer) << "Starting color quantization asynchronously";
|
||||||
isProcessing = true;
|
this->liveOperation = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
|
||||||
auto* task = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
|
QObject::connect(
|
||||||
QObject::connect(task, &ColorQuantizerOperation::done, this, &ColorQuantizer::operationFinished);
|
this->liveOperation,
|
||||||
QThreadPool::globalInstance()->start(task);
|
&ColorQuantizerOperation::done,
|
||||||
|
this,
|
||||||
|
&ColorQuantizer::operationFinished
|
||||||
|
);
|
||||||
|
QThreadPool::globalInstance()->start(this->liveOperation);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ColorQuantizer::cancelAsync() {
|
||||||
|
if (!this->liveOperation) return;
|
||||||
|
|
||||||
|
this->liveOperation->tryCancel();
|
||||||
|
QThreadPool::globalInstance()->waitForDone();
|
||||||
|
|
||||||
|
QObject::disconnect(this->liveOperation, nullptr, this, nullptr);
|
||||||
|
this->liveOperation = nullptr;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,9 +1,10 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <qlist.h>
|
#include <qlist.h>
|
||||||
#include <qmutex.h>
|
|
||||||
#include <qobject.h>
|
#include <qobject.h>
|
||||||
|
#include <qproperty.h>
|
||||||
#include <qqmlintegration.h>
|
#include <qqmlintegration.h>
|
||||||
|
#include <qqmlparserstatus.h>
|
||||||
#include <qrunnable.h>
|
#include <qrunnable.h>
|
||||||
#include <qtmetamacros.h>
|
#include <qtmetamacros.h>
|
||||||
#include <qtypes.h>
|
#include <qtypes.h>
|
||||||
|
@ -14,22 +15,33 @@ class ColorQuantizerOperation
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
|
explicit ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
|
||||||
|
|
||||||
void run() override;
|
void run() override;
|
||||||
|
void tryCancel();
|
||||||
|
|
||||||
signals:
|
signals:
|
||||||
void done(QList<QColor> colors);
|
void done(QList<QColor> colors);
|
||||||
|
|
||||||
|
private slots:
|
||||||
|
void finished();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
QList<QColor> mColors;
|
static QChar findBiggestColorRange(const QList<QColor>& rgbValues);
|
||||||
|
|
||||||
|
void quantizeImage(const QAtomicInteger<bool>& shouldCancel = false);
|
||||||
|
QList<QColor> quantization(
|
||||||
|
QList<QColor>& rgbValues,
|
||||||
|
qreal depth,
|
||||||
|
const QAtomicInteger<bool>& shouldCancel = false
|
||||||
|
);
|
||||||
|
void finishRun();
|
||||||
|
|
||||||
|
QAtomicInteger<bool> shouldCancel = false;
|
||||||
|
QList<QColor> colors;
|
||||||
QUrl* source;
|
QUrl* source;
|
||||||
qreal maxDepth;
|
qreal maxDepth;
|
||||||
qreal rescaleSize;
|
qreal rescaleSize;
|
||||||
|
|
||||||
void quantizeImage();
|
|
||||||
QList<QColor> quantization(QList<QColor>& rgbValues, qreal depth);
|
|
||||||
static QString findBiggestColorRange(const QList<QColor>& rgbValues);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
///! Color Quantization Utility
|
///! Color Quantization Utility
|
||||||
|
@ -45,13 +57,17 @@ private:
|
||||||
/// rescaleSize: 64 // Rescale to 64x64 for faster processing
|
/// rescaleSize: 64 // Rescale to 64x64 for faster processing
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
|
class ColorQuantizer
|
||||||
|
: public QObject
|
||||||
|
, public QQmlParserStatus {
|
||||||
|
|
||||||
class ColorQuantizer: public QObject {
|
|
||||||
Q_OBJECT;
|
Q_OBJECT;
|
||||||
|
QML_ELEMENT;
|
||||||
|
Q_INTERFACES(QQmlParserStatus);
|
||||||
/// Access the colors resulting from the color quantization performed.
|
/// Access the colors resulting from the color quantization performed.
|
||||||
/// > [!NOTE] The amount of colors returned from the quantization is determined by
|
/// > [!NOTE] The amount of colors returned from the quantization is determined by
|
||||||
/// > the property depth, specifically 2ⁿ where n is the depth.
|
/// > the property depth, specifically 2ⁿ where n is the depth.
|
||||||
Q_PROPERTY(QList<QColor> colors READ colors NOTIFY colorsChanged);
|
Q_PROPERTY(QList<QColor> colors READ default BINDABLE bindableColors);
|
||||||
|
|
||||||
/// Path to the image you'd like to run the color quantization on.
|
/// Path to the image you'd like to run the color quantization on.
|
||||||
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged);
|
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged);
|
||||||
|
@ -64,14 +80,21 @@ class ColorQuantizer: public QObject {
|
||||||
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
|
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
|
||||||
/// > reccommended to rescale, otherwise the quantization process will take much longer.
|
/// > reccommended to rescale, otherwise the quantization process will take much longer.
|
||||||
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
|
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
|
||||||
QML_ELEMENT;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
QList<QColor> colors();
|
explicit ColorQuantizer(QObject* parent = nullptr): QObject(parent) {}
|
||||||
|
|
||||||
|
void componentComplete() override;
|
||||||
|
void classBegin() override {}
|
||||||
|
|
||||||
|
[[nodiscard]] QBindable<QList<QColor>> bindableColors() { return &this->bColors; }
|
||||||
|
|
||||||
[[nodiscard]] QUrl source() const { return mSource; }
|
[[nodiscard]] QUrl source() const { return mSource; }
|
||||||
void setSource(const QUrl& source);
|
void setSource(const QUrl& source);
|
||||||
|
|
||||||
[[nodiscard]] qreal depth() const { return mDepth; }
|
[[nodiscard]] qreal depth() const { return mDepth; }
|
||||||
void setDepth(qreal depth);
|
void setDepth(qreal depth);
|
||||||
|
|
||||||
[[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
|
[[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
|
||||||
void setRescaleSize(int rescaleSize);
|
void setRescaleSize(int rescaleSize);
|
||||||
|
|
||||||
|
@ -85,11 +108,19 @@ public slots:
|
||||||
void operationFinished(const QList<QColor>& result);
|
void operationFinished(const QList<QColor>& result);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool isProcessing = false;
|
void quantizeAsync();
|
||||||
QList<QColor> mColors;
|
void cancelAsync();
|
||||||
|
|
||||||
|
bool componentCompleted = false;
|
||||||
|
ColorQuantizerOperation* liveOperation = nullptr;
|
||||||
QUrl mSource;
|
QUrl mSource;
|
||||||
qreal mDepth = 0;
|
qreal mDepth = 0;
|
||||||
qreal mRescaleSize = 0;
|
qreal mRescaleSize = 0;
|
||||||
|
|
||||||
void quantizeAsync();
|
Q_OBJECT_BINDABLE_PROPERTY(
|
||||||
|
ColorQuantizer,
|
||||||
|
QList<QColor>,
|
||||||
|
bColors,
|
||||||
|
&ColorQuantizer::colorsChanged
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
Loading…
Reference in a new issue