1/*
2 * Copyright 2020 Marco Martin <mart@kde.org>
3 *
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version.
8 *
9 * This program is distributed in the hope that it will be useful,
10 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12 * GNU General Public License for more details.
13 *
14 * You should have received a copy of the GNU General Public License
15 * along with this program; if not, write to the Free Software
16 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA.
17 */
18
19#pragma once
20
21#include "colorutils.h"
22
23#include <QColor>
24#include <QFuture>
25#include <QImage>
26#include <QObject>
27#include <QPointer>
28#include <QQuickItem>
29#include <QQuickItemGrabResult>
30#include <QQuickWindow>
31
32class QTimer;
33
34struct ImageData {
35 struct colorStat {
36 QList<QRgb> colors;
37 QRgb centroid = 0;
38 qreal ratio = 0;
39 };
40
41 struct colorSet {
42 QColor average;
43 QColor text;
44 QColor background;
45 QColor highlight;
46 };
47
48 QList<QRgb> m_samples;
49 QList<colorStat> m_clusters;
50 QVariantList m_palette;
51
52 bool m_darkPalette = true;
53 QColor m_dominant = Qt::transparent;
54 QColor m_dominantContrast;
55 QColor m_average;
56 QColor m_highlight;
57
58 QColor m_closestToBlack;
59 QColor m_closestToWhite;
60};
61
62/**
63 * Extracts the dominant colors from an element or an image and exports it to a color palette.
64 */
65class ImageColors : public QObject
66{
67 Q_OBJECT
68 QML_ELEMENT
69 /**
70 * The source from which colors should be extracted from.
71 *
72 * `source` can be one of the following:
73 * * Item
74 * * QImage
75 * * QIcon
76 * * Icon name
77 *
78 * Note that an Item's color palette will only be extracted once unless you * call `update()`, regardless of how the item hanges.
79 */
80 Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged FINAL)
81
82 /**
83 * A list of colors and related information about then.
84 *
85 * Each list item has the following properties:
86 * * `color`: The color of the list item.
87 * * `ratio`: How dominant the color is in the source image.
88 * * `contrastingColor`: The color from the source image that's closest to the inverse of `color`.
89 *
90 * The list is sorted by `ratio`; the first element is the most
91 * dominant color in the source image and the last element is the
92 * least dominant color of the image.
93 *
94 * \note K-means clustering is used to extract these colors; see https://en.wikipedia.org/wiki/K-means_clustering.
95 */
96 Q_PROPERTY(QVariantList palette READ palette NOTIFY paletteChanged FINAL)
97
98 /**
99 * Information whether the palette is towards a light or dark color
100 * scheme, possible values are:
101 * * ColorUtils.Light
102 * * ColorUtils.Dark
103 */
104 Q_PROPERTY(ColorUtils::Brightness paletteBrightness READ paletteBrightness NOTIFY paletteChanged FINAL)
105
106 /**
107 * The average color of the source image.
108 */
109 Q_PROPERTY(QColor average READ average NOTIFY paletteChanged FINAL)
110
111 /**
112 * The dominant color of the source image.
113 *
114 * The dominant color of the image is the color of the largest
115 * cluster in the image.
116 *
117 * \sa https://en.wikipedia.org/wiki/K-means_clustering
118 */
119 Q_PROPERTY(QColor dominant READ dominant NOTIFY paletteChanged FINAL)
120
121 /**
122 * Suggested "contrasting" color to the dominant one. It's the color in the palette nearest to the negative of the dominant
123 */
124 Q_PROPERTY(QColor dominantContrast READ dominantContrast NOTIFY paletteChanged FINAL)
125
126 /**
127 * An accent color extracted from the source image.
128 *
129 * The accent color is the color cluster with the highest CIELAB
130 * chroma in the source image.
131 *
132 * \sa https://en.wikipedia.org/wiki/Colorfulness#Chroma
133 */
134 Q_PROPERTY(QColor highlight READ highlight NOTIFY paletteChanged FINAL)
135
136 /**
137 * A color suitable for rendering text and other foreground
138 * over the source image.
139 *
140 * On dark items, this will be the color closest to white in
141 * the image if it's light enough, or a bright gray otherwise.
142 * On light items, this will be the color closest to black in
143 * the image if it's dark enough, or a dark gray otherwise.
144 */
145 Q_PROPERTY(QColor foreground READ foreground NOTIFY paletteChanged FINAL)
146
147 /**
148 * A color suitable for rendering a background behind the
149 * source image.
150 *
151 * On dark items, this will be the color closest to black in the
152 * image if it's dark enough, or a dark gray otherwise.
153 * On light items, this will be the color closest to white
154 * in the image if it's light enough, or a bright gray otherwise.
155 */
156 Q_PROPERTY(QColor background READ background NOTIFY paletteChanged FINAL)
157
158 /**
159 * The lightest color of the source image.
160 */
161 Q_PROPERTY(QColor closestToWhite READ closestToWhite NOTIFY paletteChanged FINAL)
162
163 /**
164 * The darkest color of the source image.
165 */
166 Q_PROPERTY(QColor closestToBlack READ closestToBlack NOTIFY paletteChanged FINAL)
167
168 /**
169 * The value to return when palette is not available, e.g. when
170 * ImageColors is still computing it or the source is invalid.
171 */
172 Q_PROPERTY(QVariantList fallbackPalette MEMBER m_fallbackPalette NOTIFY fallbackPaletteChanged FINAL)
173
174 /**
175 * The value to return when paletteBrightness is not available, e.g. when
176 * ImageColors is still computing it or the source is invalid.
177 */
178 Q_PROPERTY(ColorUtils::Brightness fallbackPaletteBrightness MEMBER m_fallbackPaletteBrightness NOTIFY fallbackPaletteBrightnessChanged FINAL)
179
180 /**
181 * The value to return when average is not available, e.g. when
182 * ImageColors is still computing it or the source is invalid.
183 */
184 Q_PROPERTY(QColor fallbackAverage MEMBER m_fallbackAverage NOTIFY fallbackAverageChanged FINAL)
185
186 /**
187 * The value to return when dominant is not available, e.g. when
188 * ImageColors is still computing it or the source is invalid.
189 */
190 Q_PROPERTY(QColor fallbackDominant MEMBER m_fallbackDominant NOTIFY fallbackDominantChanged FINAL)
191
192 /**
193 * The value to return when dominantContrasting is not available, e.g. when
194 * ImageColors is still computing it or the source is invalid.
195 */
196 Q_PROPERTY(QColor fallbackDominantContrasting MEMBER m_fallbackDominantContrasting NOTIFY fallbackDominantContrastingChanged FINAL)
197
198 /**
199 * The value to return when highlight is not available, e.g. when
200 * ImageColors is still computing it or the source is invalid.
201 */
202 Q_PROPERTY(QColor fallbackHighlight MEMBER m_fallbackHighlight NOTIFY fallbackHighlightChanged FINAL)
203
204 /**
205 * The value to return when foreground is not available, e.g. when
206 * ImageColors is still computing it or the source is invalid.
207 */
208 Q_PROPERTY(QColor fallbackForeground MEMBER m_fallbackForeground NOTIFY fallbackForegroundChanged FINAL)
209
210 /**
211 * The value to return when background is not available, e.g. when
212 * ImageColors is still computing it or the source is invalid.
213 */
214 Q_PROPERTY(QColor fallbackBackground MEMBER m_fallbackBackground NOTIFY fallbackBackgroundChanged FINAL)
215
216public:
217 explicit ImageColors(QObject *parent = nullptr);
218 ~ImageColors() override;
219
220 void setSource(const QVariant &source);
221 QVariant source() const;
222
223 void setSourceImage(const QImage &image);
224 QImage sourceImage() const;
225
226 void setSourceItem(QQuickItem *source);
227 QQuickItem *sourceItem() const;
228
229 Q_INVOKABLE void update();
230
231 QVariantList palette() const;
232 ColorUtils::Brightness paletteBrightness() const;
233 QColor average() const;
234 QColor dominant() const;
235 QColor dominantContrast() const;
236 QColor highlight() const;
237 QColor foreground() const;
238 QColor background() const;
239 QColor closestToWhite() const;
240 QColor closestToBlack() const;
241
242Q_SIGNALS:
243 void sourceChanged();
244 void paletteChanged();
245 void fallbackPaletteChanged();
246 void fallbackPaletteBrightnessChanged();
247 void fallbackAverageChanged();
248 void fallbackDominantChanged();
249 void fallbackDominantContrastingChanged();
250 void fallbackHighlightChanged();
251 void fallbackForegroundChanged();
252 void fallbackBackgroundChanged();
253
254private:
255 static inline void positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters);
256 static void positionColorMP(const decltype(ImageData::m_samples) &samples, decltype(ImageData::m_clusters) &clusters, int numCore = 0);
257 ImageData generatePalette(const QImage &sourceImage) const;
258
259 double getClusterScore(const ImageData::colorStat &stat) const;
260 void postProcess(ImageData &imageData) const;
261
262 // Arbitrary number that seems to work well
263 static const int s_minimumSquareDistance = 32000;
264 QPointer<QQuickWindow> m_window;
265 QVariant m_source;
266 QPointer<QQuickItem> m_sourceItem;
267 QSharedPointer<QQuickItemGrabResult> m_grabResult;
268 QImage m_sourceImage;
269 QFutureWatcher<QImage> *m_futureSourceImageData = nullptr;
270
271 QTimer *m_imageSyncTimer;
272
273 QFutureWatcher<ImageData> *m_futureImageData = nullptr;
274 ImageData m_imageData;
275
276 QVariantList m_fallbackPalette;
277 ColorUtils::Brightness m_fallbackPaletteBrightness;
278 QColor m_fallbackAverage;
279 QColor m_fallbackDominant;
280 QColor m_fallbackDominantContrasting;
281 QColor m_fallbackHighlight;
282 QColor m_fallbackForeground;
283 QColor m_fallbackBackground;
284};
285

source code of kirigami/src/imagecolors.h