diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt
index 6778e984..eca7270d 100644
--- a/src/core/CMakeLists.txt
+++ b/src/core/CMakeLists.txt
@@ -37,6 +37,7 @@ qt_add_library(quickshell-core STATIC
 	common.cpp
 	iconprovider.cpp
 	scriptmodel.cpp
+	colorquantizer.cpp
 )
 
 qt_add_qml_module(quickshell-core
diff --git a/src/core/colorquantizer.cpp b/src/core/colorquantizer.cpp
new file mode 100644
index 00000000..d08f87b4
--- /dev/null
+++ b/src/core/colorquantizer.cpp
@@ -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);
+}
diff --git a/src/core/colorquantizer.hpp b/src/core/colorquantizer.hpp
new file mode 100644
index 00000000..1c0435de
--- /dev/null
+++ b/src/core/colorquantizer.hpp
@@ -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();
+};
diff --git a/src/core/module.md b/src/core/module.md
index c8b17ab9..b9404ea9 100644
--- a/src/core/module.md
+++ b/src/core/module.md
@@ -29,5 +29,6 @@ headers = [
 	"qsmenuanchor.hpp",
 	"clock.hpp",
 	"scriptmodel.hpp",
+	"colorquantizer.hpp",
 ]
 -----