1/*
2 * SPDX-FileCopyrightText: 2020 Marco Martin <mart@kde.org>
3 * SPDX-FileCopyrightText: 2024 ivan tkachenko <me@ratijas.tk>
4 *
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 */
7
8#pragma once
9
10#include <QColor>
11#include <QFuture>
12#include <QImage>
13#include <QObject>
14#include <QPointer>
15#include <QQuickItem>
16#include <QQuickItemGrabResult>
17#include <QQuickWindow>
18
19#include <platform/colorutils.h>
20
21/*!
22 * \qmlvaluetype imageColorsPaletteSwatch
23 * \inqmlmodule org.kde.kirigami
24 */
25struct PaletteSwatch {
26 Q_GADGET
27 QML_VALUE_TYPE(imageColorsPaletteSwatch)
28
29 /*!
30 * \qmlproperty real imageColorsPaletteSwatch::ratio
31 *
32 * How dominant the color is in the source image.
33 */
34 Q_PROPERTY(qreal ratio READ ratio FINAL)
35
36 /*!
37 * \qmlproperty color imageColorsPaletteSwatch::color
38 *
39 * The color of the list item
40 */
41 Q_PROPERTY(QColor color READ color FINAL)
42
43 /*!
44 * \qmlproperty color imageColorsPaletteSwatch::contrastColor
45 *
46 * The color from the source image that's closest to the inverse of color.
47 */
48 Q_PROPERTY(QColor contrastColor READ contrastColor FINAL)
49
50public:
51 explicit PaletteSwatch();
52 explicit PaletteSwatch(qreal ratio, const QColor &color, const QColor &contrastColor);
53
54 qreal ratio() const;
55 const QColor &color() const;
56 const QColor &contrastColor() const;
57
58 bool operator==(const PaletteSwatch &other) const;
59
60private:
61 qreal m_ratio;
62 QColor m_color;
63 QColor m_contrastColor;
64};
65
66struct ImageData {
67 struct colorStat {
68 QList<QRgb> colors;
69 QRgb centroid = 0;
70 qreal ratio = 0;
71 };
72
73 struct colorSet {
74 QColor average;
75 QColor text;
76 QColor background;
77 QColor highlight;
78 };
79
80 QList<QRgb> m_samples;
81 QList<colorStat> m_clusters;
82 QList<PaletteSwatch> m_palette;
83
84 bool m_darkPalette = true;
85 QColor m_dominant = Qt::transparent;
86 QColor m_dominantContrast;
87 QColor m_average;
88 QColor m_highlight;
89
90 QColor m_closestToBlack;
91 QColor m_closestToWhite;
92};
93
94/*!
95 * \qmltype ImageColors
96 * \inqmlmodule org.kde.kirigami
97 *
98 * \brief Extracts the dominant colors from an element or an image and exports it to a color palette.
99 */
100class ImageColors : public QObject
101{
102 Q_OBJECT
103 QML_ELEMENT
104 /*!
105 * \qmlproperty var ImageColors::source
106 *
107 * The source from which colors should be extracted from.
108 *
109 * source can be one of the following:
110 * \list
111 * \li Item
112 * \li QImage
113 * \li QIcon
114 * \li Icon name
115 * \endlist
116 *
117 * Note that an Item's color palette will only be extracted once unless you
118 * call update(), regardless of how the item hanges.
119 */
120 Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged FINAL)
121
122 /*!
123 * \qmlproperty list<imageColorsPaletteSwatch> ImageColors::palette
124 *
125 * A list of colors and related information about then.
126 *
127 * Each list item has the following properties:
128 * \list
129 * \li color: The color of the list item.
130 * \li ratio: How dominant the color is in the source image.
131 * \li contrastingColor: The color from the source image that's closest to the inverse of color.
132 * \endlist
133 *
134 * The list is sorted by \c ratio; the first element is the most
135 * dominant color in the source image and the last element is the
136 * least dominant color of the image.
137 *
138 * \note K-means clustering is used to extract these colors; see \l {https://en.wikipedia.org/wiki/K-means_clustering} {K-Means Clustering (Wikipedia)}.
139 */
140 Q_PROPERTY(QList<PaletteSwatch> palette READ palette NOTIFY paletteChanged FINAL)
141
142 /*!
143 * \qmlproperty int ImageColors::paletteBrightness
144 *
145 * Information whether the palette is towards a light or dark color
146 * scheme, possible values are:
147 * \list
148 * \li ColorUtils.Light
149 * \li ColorUtils.Dark
150 * \endlist
151 */
152 Q_PROPERTY(ColorUtils::Brightness paletteBrightness READ paletteBrightness NOTIFY paletteChanged FINAL)
153
154 /*!
155 * \qmlproperty color ImageColors::average
156 *
157 * The average color of the source image.
158 */
159 Q_PROPERTY(QColor average READ average NOTIFY paletteChanged FINAL)
160
161 /*!
162 * \qmlproperty color ImageColors::dominant
163 *
164 * The dominant color of the source image.
165 *
166 * The dominant color of the image is the color of the largest
167 * cluster in the image.
168 *
169 * See \l {https://en.wikipedia.org/wiki/K-means_clustering} {K-Means Clustering (Wikipedia)}
170 */
171 Q_PROPERTY(QColor dominant READ dominant NOTIFY paletteChanged FINAL)
172
173 /*!
174 * \qmlproperty color ImageColors::dominantContrast
175 *
176 * Suggested "contrasting" color to the dominant one. It's the color in the palette nearest to the negative of the dominant
177 */
178 Q_PROPERTY(QColor dominantContrast READ dominantContrast NOTIFY paletteChanged FINAL)
179
180 /*!
181 * \qmlproperty color ImageColors::highlight
182 *
183 * An accent color extracted from the source image.
184 *
185 * The accent color is the color cluster with the highest CIELAB
186 * chroma in the source image.
187 *
188 * See \l {https://en.wikipedia.org/wiki/Colorfulness#Chroma} {Chroma (Wikipedia)}
189 */
190 Q_PROPERTY(QColor highlight READ highlight NOTIFY paletteChanged FINAL)
191
192 /*!
193 * \qmlproperty color ImageColors::foreground
194 *
195 * A color suitable for rendering text and other foreground
196 * over the source image.
197 *
198 * On dark items, this will be the color closest to white in
199 * the image if it's light enough, or a bright gray otherwise.
200 * On light items, this will be the color closest to black in
201 * the image if it's dark enough, or a dark gray otherwise.
202 */
203 Q_PROPERTY(QColor foreground READ foreground NOTIFY paletteChanged FINAL)
204
205 /*!
206 * \qmlproperty color ImageColors::background
207 *
208 * A color suitable for rendering a background behind the
209 * source image.
210 *
211 * On dark items, this will be the color closest to black in the
212 * image if it's dark enough, or a dark gray otherwise.
213 * On light items, this will be the color closest to white
214 * in the image if it's light enough, or a bright gray otherwise.
215 */
216 Q_PROPERTY(QColor background READ background NOTIFY paletteChanged FINAL)
217
218 /*!
219 * \qmlproperty color ImageColors::closestToWhite
220 *
221 * The lightest color of the source image.
222 */
223 Q_PROPERTY(QColor closestToWhite READ closestToWhite NOTIFY paletteChanged FINAL)
224
225 /*!
226 * \qmlproperty color ImageColors::closestToBlack
227 *
228 * The darkest color of the source image.
229 */
230 Q_PROPERTY(QColor closestToBlack READ closestToBlack NOTIFY paletteChanged FINAL)
231
232 /*!
233 * \qmlproperty list<imageColorsPaletteSwatch> ImageColors::fallbackPalette
234 *
235 * The value to return when palette is not available, e.g. when
236 * ImageColors is still computing it or the source is invalid.
237 */
238 Q_PROPERTY(QList<PaletteSwatch> fallbackPalette MEMBER m_fallbackPalette NOTIFY fallbackPaletteChanged FINAL)
239
240 /*!
241 * \qmlproperty int ImageColors::fallbackPaletteBrightness
242 *
243 * The value to return when paletteBrightness is not available, e.g. when
244 * ImageColors is still computing it or the source is invalid.
245 */
246 Q_PROPERTY(ColorUtils::Brightness fallbackPaletteBrightness MEMBER m_fallbackPaletteBrightness NOTIFY fallbackPaletteBrightnessChanged FINAL)
247
248 /*!
249 * \qmlproperty color ImageColors::fallbackAverage
250 *
251 * The value to return when average is not available, e.g. when
252 * ImageColors is still computing it or the source is invalid.
253 */
254 Q_PROPERTY(QColor fallbackAverage MEMBER m_fallbackAverage NOTIFY fallbackAverageChanged FINAL)
255
256 /*!
257 * \qmlproperty color ImageColors::fallbackDominant
258 *
259 * The value to return when dominant is not available, e.g. when
260 * ImageColors is still computing it or the source is invalid.
261 */
262 Q_PROPERTY(QColor fallbackDominant MEMBER m_fallbackDominant NOTIFY fallbackDominantChanged FINAL)
263
264 /*!
265 * \qmlproperty color ImageColors::fallbackDominantContrasting
266 *
267 * The value to return when dominantContrasting is not available, e.g. when
268 * ImageColors is still computing it or the source is invalid.
269 */
270 Q_PROPERTY(QColor fallbackDominantContrasting MEMBER m_fallbackDominantContrasting NOTIFY fallbackDominantContrastingChanged FINAL)
271
272 /*!
273 * \qmlproperty color ImageColors::fallbackHighlight
274 *
275 * The value to return when highlight is not available, e.g. when
276 * ImageColors is still computing it or the source is invalid.
277 */
278 Q_PROPERTY(QColor fallbackHighlight MEMBER m_fallbackHighlight NOTIFY fallbackHighlightChanged FINAL)
279
280 /*!
281 * \qmlproperty color ImageColors::fallbackForeground
282 *
283 * The value to return when foreground is not available, e.g. when
284 * ImageColors is still computing it or the source is invalid.
285 */
286 Q_PROPERTY(QColor fallbackForeground MEMBER m_fallbackForeground NOTIFY fallbackForegroundChanged FINAL)
287
288 /*!
289 * \qmlproperty color ImageColors::fallbackBackground
290 *
291 * The value to return when background is not available, e.g. when
292 * ImageColors is still computing it or the source is invalid.
293 */
294 Q_PROPERTY(QColor fallbackBackground MEMBER m_fallbackBackground NOTIFY fallbackBackgroundChanged FINAL)
295
296public:
297 explicit ImageColors(QObject *parent = nullptr);
298 ~ImageColors() override;
299
300 void setSource(const QVariant &source);
301 QVariant source() const;
302
303 void setSourceImage(const QImage &image);
304 QImage sourceImage() const;
305
306 void setSourceItem(QQuickItem *source);
307 QQuickItem *sourceItem() const;
308
309 /*!
310 * \qmlmethod void ImageColors::update()
311 *
312 * Updates the colors
313 */
314 Q_INVOKABLE void update();
315
316 QList<PaletteSwatch> palette() const;
317 ColorUtils::Brightness paletteBrightness() const;
318 QColor average() const;
319 QColor dominant() const;
320 QColor dominantContrast() const;
321 QColor highlight() const;
322 QColor foreground() const;
323 QColor background() const;
324 QColor closestToWhite() const;
325 QColor closestToBlack() const;
326
327Q_SIGNALS:
328 void sourceChanged();
329 void paletteChanged();
330 void fallbackPaletteChanged();
331 void fallbackPaletteBrightnessChanged();
332 void fallbackAverageChanged();
333 void fallbackDominantChanged();
334 void fallbackDominantContrastingChanged();
335 void fallbackHighlightChanged();
336 void fallbackForegroundChanged();
337 void fallbackBackgroundChanged();
338
339private:
340 static inline void positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters);
341 static void positionColorMP(const decltype(ImageData::m_samples) &samples, decltype(ImageData::m_clusters) &clusters, int numCore = 0);
342 static ImageData generatePalette(const QImage &sourceImage);
343
344 static double getClusterScore(const ImageData::colorStat &stat);
345 void postProcess(ImageData &imageData) const;
346
347 // Arbitrary number that seems to work well
348 static const int s_minimumSquareDistance = 32000;
349 QPointer<QQuickWindow> m_window;
350 QVariant m_source;
351 QPointer<QQuickItem> m_sourceItem;
352 QSharedPointer<QQuickItemGrabResult> m_grabResult;
353 QImage m_sourceImage;
354 QFutureWatcher<QImage> *m_futureSourceImageData = nullptr;
355
356 QFutureWatcher<ImageData> *m_futureImageData = nullptr;
357 ImageData m_imageData;
358
359 QList<PaletteSwatch> m_fallbackPalette;
360 ColorUtils::Brightness m_fallbackPaletteBrightness;
361 QColor m_fallbackAverage;
362 QColor m_fallbackDominant;
363 QColor m_fallbackDominantContrasting;
364 QColor m_fallbackHighlight;
365 QColor m_fallbackForeground;
366 QColor m_fallbackBackground;
367};
368

source code of kirigami/src/imagecolors.h