19#include "imagecolors.h"
20#include "platformtheme.h"
24#include <QtConcurrent>
28#define return_fallback(value) \
29 if (m_imageData.m_samples.size() == 0) { \
33#define return_fallback_finally(value, finally) \
34 if (m_imageData.m_samples.size() == 0) { \
35 return value.isValid() ? value : static_cast<Maui::PlatformTheme *>(qmlAttachedPropertiesObject<Maui::PlatformTheme>(this, true))->finally(); \
38ImageColors::ImageColors(
QObject *parent)
41 m_imageSyncTimer =
new QTimer(
this);
42 m_imageSyncTimer->setSingleShot(
true);
43 m_imageSyncTimer->setInterval(100);
49ImageColors::~ImageColors()
53void ImageColors::setSource(
const QVariant &source)
55 if (
source.canConvert<QQuickItem *>()) {
56 qDebug() <<
"can convert to item";
57 setSourceItem(
source.value<QQuickItem *>());
58 }
else if (
source.canConvert<QImage>()) {
59 qDebug() <<
"can convert to image";
61 setSourceImage(
source.value<QImage>());
62 }
else if (
source.canConvert<QIcon>()) {
63 qDebug() <<
"can convert to icon";
65 setSourceImage(
source.value<QIcon>().pixmap(128, 128).toImage());
66 }
else if (
source.canConvert<QString>()) {
67 qDebug() <<
"can convert to string";
68 if(
source.toString().isEmpty())
73 if(
source.toString().startsWith(
"qrc:"))
75 qDebug() <<
"SET IMAGE FROM QRC IMAGE COLORS" <<
source.toString();
76 setSourceImage(QImage(
source.toString().replace(
"qrc",
"")));
95void ImageColors::setSourceImage(
const QImage &image)
98 disconnect(m_window.data(),
nullptr,
this,
nullptr);
101 disconnect(m_sourceItem.data(),
nullptr,
this,
nullptr);
104 disconnect(m_grabResult.data(),
nullptr,
this,
nullptr);
105 m_grabResult.clear();
108 m_sourceItem.
clear();
110 m_sourceImage = image;
114QImage ImageColors::sourceImage()
const
116 return m_sourceImage;
119void ImageColors::setSourceItem(
QQuickItem *source)
121 if (m_sourceItem ==
source) {
126 disconnect(m_window.data(),
nullptr,
this,
nullptr);
129 disconnect(m_sourceItem,
nullptr,
this,
nullptr);
135 auto syncWindow = [
this]() {
137 disconnect(m_window.data(),
nullptr,
this,
nullptr);
139 m_window = m_sourceItem->window();
155void ImageColors::update()
157 if (m_futureImageData) {
158 m_futureImageData->cancel();
159 m_futureImageData->deleteLater();
161 auto runUpdate = [
this]() {
163 return generatePalette(m_sourceImage);
165 m_futureImageData =
new QFutureWatcher<ImageData>(
this);
167 if (!m_futureImageData) {
170 m_imageData = m_futureImageData->future().result();
171 m_futureImageData->deleteLater();
172 m_futureImageData =
nullptr;
176 m_futureImageData->setFuture(future);
179 if (!m_sourceItem || !m_window) {
180 if (!m_sourceImage.isNull()) {
187 disconnect(m_grabResult.data(),
nullptr,
this,
nullptr);
188 m_grabResult.clear();
191 m_grabResult = m_sourceItem->
grabToImage(QSize(128, 128));
195 m_sourceImage = m_grabResult->image();
196 m_grabResult.clear();
202inline int squareDistance(QRgb color1, QRgb color2)
206 if (qRed(color1) - qRed(color2) < 128) {
207 return 2 * pow(qRed(color1) - qRed(color2), 2)
208 + 4 * pow(qGreen(color1) - qGreen(color2), 2)
209 + 3 * pow(qBlue(color1) - qBlue(color2), 2);
211 return 3 * pow(qRed(color1) - qRed(color2), 2)
212 + 4 * pow(qGreen(color1) - qGreen(color2), 2)
213 + 2 * pow(qBlue(color1) - qBlue(color2), 2);
219 for (
auto &stat : clusters) {
220 if (squareDistance(rgb,
stat.centroid) < s_minimumSquareDistance) {
221 stat.colors.append(rgb);
226 ImageData::colorStat
stat;
227 stat.colors.append(rgb);
232ImageData ImageColors::generatePalette(
const QImage &sourceImage)
236 if (sourceImage.isNull() || sourceImage.width() == 0) {
240 imageData.m_clusters.
clear();
241 imageData.m_samples.
clear();
248 for (
int x = 0; x < sourceImage.width(); ++x) {
249 for (
int y = 0; y < sourceImage.height(); ++y) {
250 sampleColor = sourceImage.pixelColor(x, y);
251 if (sampleColor.
alpha() == 0) {
254 QRgb rgb = sampleColor.
rgb();
259 imageData.m_samples << rgb;
260 positionColor(rgb, imageData.m_clusters);
264 if (imageData.m_samples.
isEmpty()) {
268 imageData.m_average = QColor(r / c, g / c, b / c, 255);
270 for (
int iteration = 0; iteration < 5; ++iteration) {
271 for (
auto &stat : imageData.m_clusters) {
277 for (
auto color : std::as_const(
stat.colors)) {
286 stat.centroid = qRgb(r, g, b);
287 stat.ratio = qreal(
stat.colors.count()) / qreal(imageData.m_samples.
count());
288 stat.colors = QList<QRgb>({
stat.centroid});
291 for (
auto color : std::as_const(imageData.m_samples)) {
292 positionColor(color, imageData.m_clusters);
296 std::sort(imageData.m_clusters.
begin(), imageData.m_clusters.
end(), [](
const ImageData::colorStat &a,
const ImageData::colorStat &b) {
297 return a.colors.size() > b.colors.size();
301 auto sourceIt = imageData.m_clusters.
end();
302 QList<QList<ImageData::colorStat>::iterator> itemsToDelete;
303 while (sourceIt != imageData.m_clusters.
begin()) {
305 for (
auto destIt = imageData.m_clusters.
begin(); destIt != imageData.m_clusters.
end() && destIt != sourceIt; destIt++) {
306 if (squareDistance((*sourceIt).centroid, (*destIt).centroid) < s_minimumSquareDistance) {
307 const qreal ratio = (*sourceIt).ratio / (*destIt).ratio;
308 const int r = ratio * qreal(qRed((*sourceIt).centroid)) + (1 - ratio) * qreal(qRed((*destIt).centroid));
309 const int g = ratio * qreal(qGreen((*sourceIt).centroid)) + (1 - ratio) * qreal(qGreen((*destIt).centroid));
310 const int b = ratio * qreal(qBlue((*sourceIt).centroid)) + (1 - ratio) * qreal(qBlue((*destIt).centroid));
311 (*destIt).ratio += (*sourceIt).ratio;
312 (*destIt).centroid = qRgb(r, g, b);
313 itemsToDelete << sourceIt;
318 for (
const auto &i : std::as_const(itemsToDelete)) {
319 imageData.m_clusters.
erase(i);
322 imageData.m_highlight = QColor();
323 imageData.m_dominant = QColor(imageData.m_clusters.
first().centroid);
327 imageData.m_palette.clear();
331 for (
const auto &stat : std::as_const(imageData.m_clusters)) {
333 const QColor color(
stat.centroid);
334 entry[QStringLiteral(
"color")] = color;
335 entry[QStringLiteral(
"ratio")] =
stat.ratio;
337 QColor contrast = QColor(255 - color.red(), 255 - color.green(), 255 - color.blue());
342 int minimumDistance = 4681800;
343 for (
const auto &stat : std::as_const(imageData.m_clusters)) {
346 if (distance < minimumDistance) {
347 tempContrast = QColor(
stat.centroid);
352 if (imageData.m_clusters.
size() <= 3) {
353 if (qGray(imageData.m_dominant.
rgb()) < 120) {
354 contrast = QColor(230, 230, 230);
356 contrast = QColor(20, 20, 20);
359 }
else if (squareDistance(contrast.
rgb(), tempContrast.
rgb()) < s_minimumSquareDistance * 1.5) {
360 contrast = tempContrast;
362 contrast = tempContrast;
368 entry[QStringLiteral(
"contrastColor")] = contrast;
371 imageData.m_dominantContrast = contrast;
372 imageData.m_dominant = color;
377 imageData.m_highlight = color;
380 if (qGray(color.rgb()) > qGray(imageData.m_closestToWhite.
rgb())) {
381 imageData.m_closestToWhite = color;
383 if (qGray(color.rgb()) < qGray(imageData.m_closestToBlack.
rgb())) {
384 imageData.m_closestToBlack = color;
386 imageData.m_palette << entry;
394 if (m_futureImageData) {
395 qWarning() << m_futureImageData->future().isFinished();
397 return_fallback(m_fallbackPalette)
return m_imageData.m_palette;
403 return_fallback(m_fallbackPaletteBrightness)
412 return_fallback_finally(m_fallbackAverage, linkBackgroundColor)
414 return m_imageData.m_average;
421 return_fallback_finally(m_fallbackDominant, linkBackgroundColor)
423 return m_imageData.m_dominant;
430 return_fallback_finally(m_fallbackDominantContrasting, linkBackgroundColor)
432 return m_imageData.m_dominantContrast;
439 return_fallback_finally(m_fallbackForeground, textColor)
443 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
444 return QColor(230, 230, 230);
446 return m_imageData.m_closestToWhite;
448 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
449 return QColor(20, 20, 20);
451 return m_imageData.m_closestToBlack;
459 return_fallback_finally(m_fallbackBackground, backgroundColor)
462 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
463 return QColor(20, 20, 20);
465 return m_imageData.m_closestToBlack;
467 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
468 return QColor(230, 230, 230);
470 return m_imageData.m_closestToWhite;
478 return_fallback_finally(m_fallbackHighlight, linkColor)
480 return m_imageData.m_highlight;
488 if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
489 return QColor(230, 230, 230);
493 return m_imageData.m_closestToWhite;
500 if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
501 return QColor(20, 20, 20);
504 return m_imageData.m_closestToBlack;
507#include "moc_imagecolors.cpp"
static Q_INVOKABLE qreal chroma(const QColor &color)
Returns the CIELAB chroma of the given color.
Brightness
Describes the contrast of an item.
@ Light
The item is light and requires a dark foreground color to achieve readable contrast.
@ Dark
The item is dark and requires a light foreground color to achieve readable contrast.
QML_ELEMENTQVariant source
The source from which colors should be extracted from.
QColor foreground
A color suitable for rendering text and other foreground over the source image.
QColor closestToWhite
The lightest color of the source image.
QColor dominant
The dominant color of the source image.
QVariantList palette
A list of colors and related information about then.
QColor closestToBlack
The darkest color of the source image.
QColor dominantContrast
Suggested "contrasting" color to the dominant one.
ColorUtils::Brightness paletteBrightness
Information whether the palette is towards a light or dark color scheme, possible values are:
QColor average
The average color of the source image.
QColor background
A color suitable for rendering a background behind the source image.
QColor highlight
An accent color extracted from the source image.
KIOCORE_EXPORT StatJob * stat(const QUrl &url, JobFlags flags=DefaultFlags)
KOSM_EXPORT double distance(const std::vector< const OSM::Node * > &path, Coordinate coord)
int hslSaturation() const const
bool isValid() const const
int lightness() const const
void setHsl(int h, int s, int l, int a)
QPixmap pixmap(QWindow *window, const QSize &size, Mode mode, State state) const const
QIcon fromTheme(const QString &name)
qsizetype count() const const
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool disconnect(const QMetaObject::Connection &connection)
QImage toImage() const const
QSharedPointer< QQuickItemGrabResult > grabToImage(const QSize &targetSize)
void windowChanged(QQuickWindow *window)
QFuture< T > run(Function function,...)
void visibleChanged(bool arg)