File indexing completed on 2024-04-14 03:53:47

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  * Extracts the dominant colors from an element or an image and exports it to a color palette.
0064  */
0065 class ImageColors : public QObject
0066 {
0067     Q_OBJECT
0068     QML_ELEMENT
0069     /**
0070      * The source from which colors should be extracted from.
0071      *
0072      * `source` can be one of the following:
0073      * * Item
0074      * * QImage
0075      * * QIcon
0076      * * Icon name
0077      *
0078      * Note that 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 FINAL)
0081 
0082     /**
0083      * A list of colors and related information about then.
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 FINAL)
0097 
0098     /**
0099      * Information whether the palette is towards a light or dark color
0100      * scheme, possible values are:
0101      * * ColorUtils.Light
0102      * * ColorUtils.Dark
0103      */
0104     Q_PROPERTY(ColorUtils::Brightness paletteBrightness READ paletteBrightness NOTIFY paletteChanged FINAL)
0105 
0106     /**
0107      * The average color of the source image.
0108      */
0109     Q_PROPERTY(QColor average READ average NOTIFY paletteChanged FINAL)
0110 
0111     /**
0112      * The dominant color of the source image.
0113      *
0114      * The dominant color of the image is the color of the largest
0115      * cluster in the image.
0116      *
0117      * \sa https://en.wikipedia.org/wiki/K-means_clustering
0118      */
0119     Q_PROPERTY(QColor dominant READ dominant NOTIFY paletteChanged FINAL)
0120 
0121     /**
0122      * Suggested "contrasting" color to the dominant one. It's the color in the palette nearest to the negative of the dominant
0123      */
0124     Q_PROPERTY(QColor dominantContrast READ dominantContrast NOTIFY paletteChanged FINAL)
0125 
0126     /**
0127      * An accent color extracted from the source image.
0128      *
0129      * The accent color is the color cluster with the highest CIELAB
0130      * chroma in the source image.
0131      *
0132      * \sa https://en.wikipedia.org/wiki/Colorfulness#Chroma
0133      */
0134     Q_PROPERTY(QColor highlight READ highlight NOTIFY paletteChanged FINAL)
0135 
0136     /**
0137      * A color suitable for rendering text and other foreground
0138      * over the source image.
0139      *
0140      * On dark items, this will be the color closest to white in
0141      * the image if it's light enough, or a bright gray otherwise.
0142      * On light items, this will be the color closest to black in
0143      * the image if it's dark enough, or a dark gray otherwise.
0144      */
0145     Q_PROPERTY(QColor foreground READ foreground NOTIFY paletteChanged FINAL)
0146 
0147     /**
0148      * A color suitable for rendering a background behind the
0149      * source image.
0150      *
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 FINAL)
0157 
0158     /**
0159      * The lightest color of the source image.
0160      */
0161     Q_PROPERTY(QColor closestToWhite READ closestToWhite NOTIFY paletteChanged FINAL)
0162 
0163     /**
0164      * The darkest color of the source image.
0165      */
0166     Q_PROPERTY(QColor closestToBlack READ closestToBlack NOTIFY paletteChanged FINAL)
0167 
0168     /**
0169      * The value to return when palette is not available, e.g. when
0170      * ImageColors is still computing it or the source is invalid.
0171      */
0172     Q_PROPERTY(QVariantList fallbackPalette MEMBER m_fallbackPalette NOTIFY fallbackPaletteChanged FINAL)
0173 
0174     /**
0175      * The value to return when paletteBrightness is not available, e.g. when
0176      * ImageColors is still computing it or the source is invalid.
0177      */
0178     Q_PROPERTY(ColorUtils::Brightness fallbackPaletteBrightness MEMBER m_fallbackPaletteBrightness NOTIFY fallbackPaletteBrightnessChanged FINAL)
0179 
0180     /**
0181      * The value to return when average is not available, e.g. when
0182      * ImageColors is still computing it or the source is invalid.
0183      */
0184     Q_PROPERTY(QColor fallbackAverage MEMBER m_fallbackAverage NOTIFY fallbackAverageChanged FINAL)
0185 
0186     /**
0187      * The value to return when dominant is not available, e.g. when
0188      * ImageColors is still computing it or the source is invalid.
0189      */
0190     Q_PROPERTY(QColor fallbackDominant MEMBER m_fallbackDominant NOTIFY fallbackDominantChanged FINAL)
0191 
0192     /**
0193      * The value to return when dominantContrasting is not available, e.g. when
0194      * ImageColors is still computing it or the source is invalid.
0195      */
0196     Q_PROPERTY(QColor fallbackDominantContrasting MEMBER m_fallbackDominantContrasting NOTIFY fallbackDominantContrastingChanged FINAL)
0197 
0198     /**
0199      * The value to return when highlight is not available, e.g. when
0200      * ImageColors is still computing it or the source is invalid.
0201      */
0202     Q_PROPERTY(QColor fallbackHighlight MEMBER m_fallbackHighlight NOTIFY fallbackHighlightChanged FINAL)
0203 
0204     /**
0205      * The value to return when foreground is not available, e.g. when
0206      * ImageColors is still computing it or the source is invalid.
0207      */
0208     Q_PROPERTY(QColor fallbackForeground MEMBER m_fallbackForeground NOTIFY fallbackForegroundChanged FINAL)
0209 
0210     /**
0211      * The value to return when background is not available, e.g. when
0212      * ImageColors is still computing it or the source is invalid.
0213      */
0214     Q_PROPERTY(QColor fallbackBackground MEMBER m_fallbackBackground NOTIFY fallbackBackgroundChanged FINAL)
0215 
0216 public:
0217     explicit ImageColors(QObject *parent = nullptr);
0218     ~ImageColors() override;
0219 
0220     void setSource(const QVariant &source);
0221     QVariant source() const;
0222 
0223     void setSourceImage(const QImage &image);
0224     QImage sourceImage() const;
0225 
0226     void setSourceItem(QQuickItem *source);
0227     QQuickItem *sourceItem() const;
0228 
0229     Q_INVOKABLE void update();
0230 
0231     QVariantList palette() const;
0232     ColorUtils::Brightness paletteBrightness() const;
0233     QColor average() const;
0234     QColor dominant() const;
0235     QColor dominantContrast() const;
0236     QColor highlight() const;
0237     QColor foreground() const;
0238     QColor background() const;
0239     QColor closestToWhite() const;
0240     QColor closestToBlack() const;
0241 
0242 Q_SIGNALS:
0243     void sourceChanged();
0244     void paletteChanged();
0245     void fallbackPaletteChanged();
0246     void fallbackPaletteBrightnessChanged();
0247     void fallbackAverageChanged();
0248     void fallbackDominantChanged();
0249     void fallbackDominantContrastingChanged();
0250     void fallbackHighlightChanged();
0251     void fallbackForegroundChanged();
0252     void fallbackBackgroundChanged();
0253 
0254 private:
0255     static inline void positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters);
0256     static void positionColorMP(const decltype(ImageData::m_samples) &samples, decltype(ImageData::m_clusters) &clusters, int numCore = 0);
0257     ImageData generatePalette(const QImage &sourceImage) const;
0258 
0259     double getClusterScore(const ImageData::colorStat &stat) const;
0260     void postProcess(ImageData &imageData) const;
0261 
0262     // Arbitrary number that seems to work well
0263     static const int s_minimumSquareDistance = 32000;
0264     QPointer<QQuickWindow> m_window;
0265     QVariant m_source;
0266     QPointer<QQuickItem> m_sourceItem;
0267     QSharedPointer<QQuickItemGrabResult> m_grabResult;
0268     QImage m_sourceImage;
0269     QFutureWatcher<QImage> *m_futureSourceImageData = nullptr;
0270 
0271     QTimer *m_imageSyncTimer;
0272 
0273     QFutureWatcher<ImageData> *m_futureImageData = nullptr;
0274     ImageData m_imageData;
0275 
0276     QVariantList m_fallbackPalette;
0277     ColorUtils::Brightness m_fallbackPaletteBrightness;
0278     QColor m_fallbackAverage;
0279     QColor m_fallbackDominant;
0280     QColor m_fallbackDominantContrasting;
0281     QColor m_fallbackHighlight;
0282     QColor m_fallbackForeground;
0283     QColor m_fallbackBackground;
0284 };