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 | |
32 | class QTimer; |
33 | |
34 | struct 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 | */ |
65 | class 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 | |
216 | public: |
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 | |
242 | Q_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 | |
254 | private: |
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 | |