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 };