File indexing completed on 2024-04-28 15:27:42
0001 /* 0002 * Copyright 2020 Marco Martin <mart@kde.org> 0003 * 0004 * This program is free software; you can redistribute it and/or modify 0005 * it under the terms of the GNU General Public License as published by 0006 * the Free Software Foundation; either version 2 of the License, or 0007 * (at your option) any later version. 0008 * 0009 * This program is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 0012 * GNU General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU General Public License 0015 * along with this program; if not, write to the Free Software 0016 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. 0017 */ 0018 0019 #pragma once 0020 0021 #include "colorutils.h" 0022 0023 #include <QColor> 0024 #include <QFuture> 0025 #include <QImage> 0026 #include <QObject> 0027 #include <QPointer> 0028 #include <QQuickItem> 0029 #include <QQuickItemGrabResult> 0030 #include <QQuickWindow> 0031 0032 class QTimer; 0033 0034 struct ImageData { 0035 struct colorStat { 0036 QList<QRgb> colors; 0037 QRgb centroid = 0; 0038 qreal ratio = 0; 0039 }; 0040 0041 struct colorSet { 0042 QColor average; 0043 QColor text; 0044 QColor background; 0045 QColor highlight; 0046 }; 0047 0048 QList<QRgb> m_samples; 0049 QList<colorStat> m_clusters; 0050 QVariantList m_palette; 0051 0052 bool m_darkPalette = true; 0053 QColor m_dominant = Qt::transparent; 0054 QColor m_dominantContrast; 0055 QColor m_average; 0056 QColor m_highlight; 0057 0058 QColor m_closestToBlack; 0059 QColor m_closestToWhite; 0060 }; 0061 0062 /** 0063 * @brief Helps extract specific colors from an element or image 0064 * (e.g., its dominant colors). 0065 */ 0066 class ImageColors : public QObject 0067 { 0068 Q_OBJECT 0069 /** 0070 * @brief This property holds the source from which colors should be extracted. 0071 * 0072 * The following values are allowed: 0073 * * QtQuick.Item 0074 * * QtGui.QImage 0075 * * QtGui.QIcon 0076 * * @link Icon::source Icon.name @endlink 0077 * 0078 * @note An Item's color palette will only be extracted once unless you * call `update()`, regardless of how the item hanges. 0079 */ 0080 Q_PROPERTY(QVariant source READ source WRITE setSource NOTIFY sourceChanged) 0081 0082 /** 0083 * @brief This property holds a list of colors and related information about them. 0084 * 0085 * Each list item has the following properties: 0086 * * `color`: The color of the list item. 0087 * * `ratio`: How dominant the color is in the source image. 0088 * * `contrastingColor`: The color from the source image that's closest to the inverse of `color`. 0089 * 0090 * The list is sorted by `ratio`; the first element is the most 0091 * dominant color in the source image and the last element is the 0092 * least dominant color of the image. 0093 * 0094 * @note K-means clustering is used to extract these colors; see https://en.wikipedia.org/wiki/K-means_clustering. 0095 */ 0096 Q_PROPERTY(QVariantList palette READ palette NOTIFY paletteChanged) 0097 0098 /** 0099 * @brief This property specifies whether the palette is a light or dark color scheme. 0100 */ 0101 Q_PROPERTY(ColorUtils::Brightness paletteBrightness READ paletteBrightness NOTIFY paletteChanged) 0102 0103 /** 0104 * @brief This property holds the average color of the source image. 0105 */ 0106 Q_PROPERTY(QColor average READ average NOTIFY paletteChanged) 0107 0108 /** 0109 * @brief This property holds the dominant color of the source image. 0110 * 0111 * The dominant color of the image is the color of the largest 0112 * cluster in the image. 0113 * 0114 * @see https://en.wikipedia.org/wiki/K-means_clustering 0115 */ 0116 Q_PROPERTY(QColor dominant READ dominant NOTIFY paletteChanged) 0117 0118 /** 0119 * @brief Ths property holds the suggested "contrasting" color to the dominant one. 0120 * 0121 * It's the color in the palette nearest to the negative of the dominant. 0122 */ 0123 Q_PROPERTY(QColor dominantContrast READ dominantContrast NOTIFY paletteChanged) 0124 0125 /** 0126 * @brief This property holds an accent color extracted from the source image. 0127 * 0128 * The accent color is the color cluster with the highest CIELAB 0129 * chroma in the source image. 0130 * 0131 * @see https://en.wikipedia.org/wiki/Colorfulness#Chroma 0132 */ 0133 Q_PROPERTY(QColor highlight READ highlight NOTIFY paletteChanged) 0134 0135 /** 0136 * @brief This property holds the color suitable for rendering text and other foreground 0137 * over the source image. 0138 * 0139 * On dark items, this will be the color closest to white in 0140 * the image if it's light enough, or a bright gray otherwise. 0141 * On light items, this will be the color closest to black in 0142 * the image if it's dark enough, or a dark gray otherwise. 0143 */ 0144 Q_PROPERTY(QColor foreground READ foreground NOTIFY paletteChanged) 0145 0146 /** 0147 * @brief This property holds a color that is suitable as a 0148 * a background behind the source image. 0149 * 0150 * There are two possible outcomes: 0151 * * On dark items, this will be the color closest to black in the 0152 * image if it's dark enough, or a dark gray otherwise. 0153 * * On light items, this will be the color closest to white 0154 * in the image if it's light enough, or a bright gray otherwise. 0155 */ 0156 Q_PROPERTY(QColor background READ background NOTIFY paletteChanged) 0157 0158 /** 0159 * @brief This property holds the lightest color of the source image. 0160 */ 0161 Q_PROPERTY(QColor closestToWhite READ closestToWhite NOTIFY paletteChanged) 0162 0163 /** 0164 * @brief This property holds the darkest color of the source image. 0165 */ 0166 Q_PROPERTY(QColor closestToBlack READ closestToBlack NOTIFY paletteChanged) 0167 0168 /** 0169 * @brief This property holds the value to return when the palette is not 0170 * available. 0171 * 0172 * @note This may happen when ImageColors is still computing or the image 0173 * source is invalid. 0174 */ 0175 Q_PROPERTY(QVariantList fallbackPalette MEMBER m_fallbackPalette NOTIFY fallbackPaletteChanged) 0176 0177 /** 0178 * @brief This property holds the value to return instead when 0179 * paletteBrightness is not available. 0180 * 0181 * @note This may happen when ImageColors is still computing or the image 0182 * source is invalid. 0183 */ 0184 Q_PROPERTY(ColorUtils::Brightness fallbackPaletteBrightness MEMBER m_fallbackPaletteBrightness NOTIFY fallbackPaletteBrightnessChanged) 0185 0186 /** 0187 * @brief This property holds the value to return instead when average is 0188 * not available. 0189 * 0190 * @note This may happen when ImageColors is still computing or the image 0191 * source is invalid. 0192 */ 0193 Q_PROPERTY(QColor fallbackAverage MEMBER m_fallbackAverage NOTIFY fallbackAverageChanged) 0194 0195 /** 0196 * @brief This property holds the value to return instead when dominant is 0197 * not available. 0198 * 0199 * @note This may happen when ImageColors is still computing or the image 0200 * source is invalid. 0201 */ 0202 Q_PROPERTY(QColor fallbackDominant MEMBER m_fallbackDominant NOTIFY fallbackDominantChanged) 0203 0204 /** 0205 * @brief This property holds the value of the palette to return instead 0206 * when dominantContrasting is not available. 0207 * 0208 * @note This may happen when ImageColors is still computing or the image 0209 * source is invalid. 0210 */ 0211 Q_PROPERTY(QColor fallbackDominantContrasting MEMBER m_fallbackDominantContrasting NOTIFY fallbackDominantContrastingChanged) 0212 0213 /** 0214 * @brief This property holds the value to return instead when highlight is 0215 * not available. 0216 * 0217 * @note This may happen when ImageColors is still computing or the image 0218 * source is invalid. 0219 */ 0220 Q_PROPERTY(QColor fallbackHighlight MEMBER m_fallbackHighlight NOTIFY fallbackHighlightChanged) 0221 0222 /** 0223 * @brief This property holds the value to return instead when foreground is 0224 * not available. 0225 * 0226 * @note This may happen when ImageColors is still computing or the image 0227 * source is invalid. 0228 */ 0229 Q_PROPERTY(QColor fallbackForeground MEMBER m_fallbackForeground NOTIFY fallbackForegroundChanged) 0230 0231 /** 0232 * @brief This property holds the value to return instead when background is 0233 * not available. 0234 * 0235 * @note This may happen when ImageColors is still computing or the image 0236 * source is invalid. 0237 */ 0238 Q_PROPERTY(QColor fallbackBackground MEMBER m_fallbackBackground NOTIFY fallbackBackgroundChanged) 0239 0240 public: 0241 explicit ImageColors(QObject *parent = nullptr); 0242 ~ImageColors() override; 0243 0244 void setSource(const QVariant &source); 0245 QVariant source() const; 0246 0247 void setSourceImage(const QImage &image); 0248 QImage sourceImage() const; 0249 0250 void setSourceItem(QQuickItem *source); 0251 QQuickItem *sourceItem() const; 0252 0253 Q_INVOKABLE void update(); 0254 0255 QVariantList palette() const; 0256 ColorUtils::Brightness paletteBrightness() const; 0257 QColor average() const; 0258 QColor dominant() const; 0259 QColor dominantContrast() const; 0260 QColor highlight() const; 0261 QColor foreground() const; 0262 QColor background() const; 0263 QColor closestToWhite() const; 0264 QColor closestToBlack() const; 0265 0266 Q_SIGNALS: 0267 void sourceChanged(); 0268 void paletteChanged(); 0269 void fallbackPaletteChanged(); 0270 void fallbackPaletteBrightnessChanged(); 0271 void fallbackAverageChanged(); 0272 void fallbackDominantChanged(); 0273 void fallbackDominantContrastingChanged(); 0274 void fallbackHighlightChanged(); 0275 void fallbackForegroundChanged(); 0276 void fallbackBackgroundChanged(); 0277 0278 private: 0279 static inline void positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters); 0280 ImageData generatePalette(const QImage &sourceImage) const; 0281 0282 double getClusterScore(const ImageData::colorStat &stat) const; 0283 void postProcess(ImageData &imageData) const; 0284 0285 // Arbitrary number that seems to work well 0286 static const int s_minimumSquareDistance = 32000; 0287 QPointer<QQuickWindow> m_window; 0288 QVariant m_source; 0289 QPointer<QQuickItem> m_sourceItem; 0290 QSharedPointer<QQuickItemGrabResult> m_grabResult; 0291 QImage m_sourceImage; 0292 QFutureWatcher<QImage> *m_futureSourceImageData = nullptr; 0293 0294 QTimer *m_imageSyncTimer; 0295 0296 QFutureWatcher<ImageData> *m_futureImageData = nullptr; 0297 ImageData m_imageData; 0298 0299 QVariantList m_fallbackPalette; 0300 ColorUtils::Brightness m_fallbackPaletteBrightness; 0301 QColor m_fallbackAverage; 0302 QColor m_fallbackDominant; 0303 QColor m_fallbackDominantContrasting; 0304 QColor m_fallbackHighlight; 0305 QColor m_fallbackForeground; 0306 QColor m_fallbackBackground; 0307 };