forked from quickshell/quickshell
fix: resolve merge conflict due to addition of color quantizer
This commit is contained in:
parent
4f2610dece
commit
f17bc07da3
|
@ -37,6 +37,7 @@ qt_add_library(quickshell-core STATIC
|
|||
common.cpp
|
||||
iconprovider.cpp
|
||||
scriptmodel.cpp
|
||||
colorquantizer.cpp
|
||||
)
|
||||
|
||||
qt_add_qml_module(quickshell-core
|
||||
|
|
182
src/core/colorquantizer.cpp
Normal file
182
src/core/colorquantizer.cpp
Normal file
|
@ -0,0 +1,182 @@
|
|||
#include "colorquantizer.hpp"
|
||||
|
||||
#include <qcolor.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmllist.h>
|
||||
#include <qthreadpool.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
ColorQuantizerOperation::ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize)
|
||||
: source(source)
|
||||
, maxDepth(depth)
|
||||
, rescaleSize(rescaleSize) {
|
||||
mColors = QList<QColor>();
|
||||
}
|
||||
|
||||
void ColorQuantizerOperation::run() {
|
||||
quantizeImage();
|
||||
|
||||
emit done(mColors);
|
||||
}
|
||||
|
||||
void ColorQuantizerOperation::quantizeImage() {
|
||||
mColors.clear();
|
||||
|
||||
if (source->isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QImage image(source->toLocalFile());
|
||||
|
||||
if ((image.width() > rescaleSize || image.height() > rescaleSize) && rescaleSize > 0) {
|
||||
image = image.scaled(
|
||||
static_cast<int>(rescaleSize),
|
||||
static_cast<int>(rescaleSize),
|
||||
Qt::KeepAspectRatio,
|
||||
Qt::SmoothTransformation
|
||||
);
|
||||
}
|
||||
|
||||
if (image.isNull()) {
|
||||
qWarning() << "Failed to load image from" << source;
|
||||
return;
|
||||
}
|
||||
|
||||
QList<QColor> pixels;
|
||||
for (int y = 0; y < image.height(); ++y) {
|
||||
for (int x = 0; x < image.width(); ++x) {
|
||||
QRgb pixel = image.pixel(x, y);
|
||||
if (qAlpha(pixel) == 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pixels.append(QColor::fromRgb(pixel));
|
||||
}
|
||||
}
|
||||
|
||||
QDateTime startTime = QDateTime::currentDateTime();
|
||||
|
||||
mColors = quantization(pixels, 0);
|
||||
|
||||
QDateTime endTime = QDateTime::currentDateTime();
|
||||
qint64 milliseconds = startTime.msecsTo(endTime);
|
||||
qDebug() << "Color Quantization took: " << milliseconds << "ms";
|
||||
}
|
||||
|
||||
QList<QColor> ColorQuantizerOperation::quantization(QList<QColor>& rgbValues, qreal depth) {
|
||||
if (depth >= maxDepth || rgbValues.isEmpty()) {
|
||||
if (rgbValues.isEmpty()) {
|
||||
return QList<QColor>();
|
||||
}
|
||||
|
||||
int totalR = 0;
|
||||
int totalG = 0;
|
||||
int totalB = 0;
|
||||
|
||||
for (const QColor& color: rgbValues) {
|
||||
totalR += color.red();
|
||||
totalG += color.green();
|
||||
totalB += color.blue();
|
||||
}
|
||||
|
||||
QColor avgColor(
|
||||
qRound(totalR / static_cast<double>(rgbValues.size())),
|
||||
qRound(totalG / static_cast<double>(rgbValues.size())),
|
||||
qRound(totalB / static_cast<double>(rgbValues.size()))
|
||||
);
|
||||
|
||||
return QList<QColor>() << avgColor;
|
||||
}
|
||||
|
||||
QString dominantChannel = findBiggestColorRange(rgbValues);
|
||||
std::ranges::sort(rgbValues, [dominantChannel](const QColor& a, const QColor& b) {
|
||||
if (dominantChannel == "r") return a.red() < b.red();
|
||||
else if (dominantChannel == "g") return a.green() < b.green();
|
||||
return a.blue() < b.blue();
|
||||
});
|
||||
|
||||
qsizetype mid = rgbValues.size() / 2;
|
||||
|
||||
QList<QColor> leftHalf = rgbValues.mid(0, mid);
|
||||
QList<QColor> rightHalf = rgbValues.mid(mid + 1);
|
||||
|
||||
QList<QColor> result;
|
||||
result.append(quantization(leftHalf, depth + 1));
|
||||
result.append(quantization(rightHalf, depth + 1));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
QString ColorQuantizerOperation::findBiggestColorRange(const QList<QColor>& rgbValues) {
|
||||
if (rgbValues.isEmpty()) return "r";
|
||||
|
||||
int rMin = 255;
|
||||
int gMin = 255;
|
||||
int bMin = 255;
|
||||
int rMax = 0;
|
||||
int gMax = 0;
|
||||
int bMax = 0;
|
||||
|
||||
for (const QColor& color: rgbValues) {
|
||||
rMin = qMin(rMin, color.red());
|
||||
gMin = qMin(gMin, color.green());
|
||||
bMin = qMin(bMin, color.blue());
|
||||
|
||||
rMax = qMax(rMax, color.red());
|
||||
gMax = qMax(gMax, color.green());
|
||||
bMax = qMax(bMax, color.blue());
|
||||
}
|
||||
|
||||
int rRange = rMax - rMin;
|
||||
int gRange = gMax - gMin;
|
||||
int bRange = bMax - bMin;
|
||||
|
||||
int biggestRange = qMax(rRange, qMax(gRange, bRange));
|
||||
if (biggestRange == rRange) {
|
||||
return "r";
|
||||
} else if (biggestRange == gRange) {
|
||||
return "g";
|
||||
} else {
|
||||
return "b";
|
||||
}
|
||||
}
|
||||
|
||||
QList<QColor> ColorQuantizer::colors() { return mColors; }
|
||||
|
||||
void ColorQuantizer::setSource(const QUrl& source) {
|
||||
if (mSource != source) {
|
||||
mSource = source;
|
||||
emit sourceChanged();
|
||||
quantizeAsync();
|
||||
}
|
||||
}
|
||||
|
||||
void ColorQuantizer::setDepth(qreal depth) {
|
||||
if (mDepth != depth) {
|
||||
mDepth = depth;
|
||||
emit depthChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ColorQuantizer::setRescaleSize(int rescaleSize) {
|
||||
if (mRescaleSize != rescaleSize) {
|
||||
mRescaleSize = rescaleSize;
|
||||
emit rescaleSizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void ColorQuantizer::operationFinished(const QList<QColor>& result) {
|
||||
mColors = result;
|
||||
emit colorsChanged();
|
||||
isProcessing = false;
|
||||
}
|
||||
|
||||
void ColorQuantizer::quantizeAsync() {
|
||||
if (isProcessing) return;
|
||||
|
||||
qDebug() << "Starting color quantization asynchronously";
|
||||
isProcessing = true;
|
||||
auto* task = new ColorQuantizerOperation(&mSource, mDepth, mRescaleSize);
|
||||
QObject::connect(task, &ColorQuantizerOperation::done, this, &ColorQuantizer::operationFinished);
|
||||
QThreadPool::globalInstance()->start(task);
|
||||
}
|
95
src/core/colorquantizer.hpp
Normal file
95
src/core/colorquantizer.hpp
Normal file
|
@ -0,0 +1,95 @@
|
|||
#pragma once
|
||||
|
||||
#include <qlist.h>
|
||||
#include <qmutex.h>
|
||||
#include <qobject.h>
|
||||
#include <qqmlintegration.h>
|
||||
#include <qrunnable.h>
|
||||
#include <qtmetamacros.h>
|
||||
#include <qtypes.h>
|
||||
|
||||
class ColorQuantizerOperation
|
||||
: public QObject
|
||||
, public QRunnable {
|
||||
Q_OBJECT;
|
||||
|
||||
public:
|
||||
ColorQuantizerOperation(QUrl* source, qreal depth, qreal rescaleSize);
|
||||
|
||||
void run() override;
|
||||
|
||||
signals:
|
||||
void done(QList<QColor> colors);
|
||||
|
||||
private:
|
||||
QList<QColor> mColors;
|
||||
QUrl* source;
|
||||
qreal maxDepth;
|
||||
qreal rescaleSize;
|
||||
|
||||
void quantizeImage();
|
||||
QList<QColor> quantization(QList<QColor>& rgbValues, qreal depth);
|
||||
static QString findBiggestColorRange(const QList<QColor>& rgbValues);
|
||||
};
|
||||
|
||||
///! Color Quantization Utility
|
||||
/// A color quantization utility used for getting prevalent colors in an image, by
|
||||
/// averaging out the image's color data recursively.
|
||||
///
|
||||
/// #### Example
|
||||
/// ```qml
|
||||
/// ColorQuantizer {
|
||||
/// id: colorQuantizer
|
||||
/// source: Qt.resolvedUrl("./yourImage.png")
|
||||
/// depth: 3 // Will produce 8 colors (2³)
|
||||
/// rescaleSize: 64 // Rescale to 64x64 for faster processing
|
||||
/// }
|
||||
/// ```
|
||||
|
||||
class ColorQuantizer: public QObject {
|
||||
Q_OBJECT;
|
||||
/// Access the colors resulting from the color quantization performed.
|
||||
/// > [!NOTE] The amount of colors returned from the quantization is determined by
|
||||
/// > the property depth, specifically 2ⁿ where n is the depth.
|
||||
Q_PROPERTY(QList<QColor> colors READ colors NOTIFY colorsChanged);
|
||||
|
||||
/// Path to the image you'd like to run the color quantization on.
|
||||
Q_PROPERTY(QUrl source READ source WRITE setSource NOTIFY sourceChanged);
|
||||
|
||||
/// Max depth for the color quantization. Each level of depth represents another
|
||||
/// binary split of the color space
|
||||
Q_PROPERTY(qreal depth READ depth WRITE setDepth NOTIFY depthChanged);
|
||||
|
||||
/// The size to rescale the image to, when rescaleSize is 0 then no scaling will be done.
|
||||
/// > [!NOTE] Results from color quantization doesn't suffer much when rescaling, it's
|
||||
/// > reccommended to rescale, otherwise the quantization process will take much longer.
|
||||
Q_PROPERTY(qreal rescaleSize READ rescaleSize WRITE setRescaleSize NOTIFY rescaleSizeChanged);
|
||||
QML_ELEMENT;
|
||||
|
||||
public:
|
||||
QList<QColor> colors();
|
||||
[[nodiscard]] QUrl source() const { return mSource; }
|
||||
void setSource(const QUrl& source);
|
||||
[[nodiscard]] qreal depth() const { return mDepth; }
|
||||
void setDepth(qreal depth);
|
||||
[[nodiscard]] qreal rescaleSize() const { return mRescaleSize; }
|
||||
void setRescaleSize(int rescaleSize);
|
||||
|
||||
signals:
|
||||
void colorsChanged();
|
||||
void sourceChanged();
|
||||
void depthChanged();
|
||||
void rescaleSizeChanged();
|
||||
|
||||
public slots:
|
||||
void operationFinished(const QList<QColor>& result);
|
||||
|
||||
private:
|
||||
bool isProcessing = false;
|
||||
QList<QColor> mColors;
|
||||
QUrl mSource;
|
||||
qreal mDepth = 0;
|
||||
qreal mRescaleSize = 0;
|
||||
|
||||
void quantizeAsync();
|
||||
};
|
|
@ -29,5 +29,6 @@ headers = [
|
|||
"qsmenuanchor.hpp",
|
||||
"clock.hpp",
|
||||
"scriptmodel.hpp",
|
||||
"colorquantizer.hpp",
|
||||
]
|
||||
-----
|
||||
|
|
Loading…
Reference in a new issue