1
0
Fork 0

fix: address review requests

This commit is contained in:
kossLAN 2025-01-27 20:30:07 -05:00
parent f17bc07da3
commit b7eb562abc
No known key found for this signature in database
2 changed files with 162 additions and 81 deletions

View file

@ -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;
} }

View file

@ -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
);
}; };