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 #include "imagecolors.h"
0020 
0021 #include <QDebug>
0022 #include <QFutureWatcher>
0023 #include <QGuiApplication>
0024 #include <QTimer>
0025 #include <QtConcurrentRun>
0026 
0027 #include "loggingcategory.h"
0028 #include <cmath>
0029 #include <vector>
0030 
0031 #include "config-OpenMP.h"
0032 #if HAVE_OpenMP
0033 #include <omp.h>
0034 #endif
0035 
0036 #include "platform/platformtheme.h"
0037 
0038 #define return_fallback(value)                                                                                                                                 \
0039     if (m_imageData.m_samples.size() == 0) {                                                                                                                   \
0040         return value;                                                                                                                                          \
0041     }
0042 
0043 #define return_fallback_finally(value, finally)                                                                                                                \
0044     if (m_imageData.m_samples.size() == 0) {                                                                                                                   \
0045         return value.isValid()                                                                                                                                 \
0046             ? value                                                                                                                                            \
0047             : static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true))->finally();         \
0048     }
0049 
0050 ImageColors::ImageColors(QObject *parent)
0051     : QObject(parent)
0052 {
0053     m_imageSyncTimer = new QTimer(this);
0054     m_imageSyncTimer->setSingleShot(true);
0055     m_imageSyncTimer->setInterval(100);
0056     /* connect(m_imageSyncTimer, &QTimer::timeout, this, [this]() {
0057         generatePalette();
0058      });*/
0059 }
0060 
0061 ImageColors::~ImageColors()
0062 {
0063 }
0064 
0065 void ImageColors::setSource(const QVariant &source)
0066 {
0067     if (m_futureSourceImageData) {
0068         m_futureSourceImageData->cancel();
0069         m_futureSourceImageData->deleteLater();
0070         m_futureSourceImageData = nullptr;
0071     }
0072 
0073     if (source.canConvert<QQuickItem *>()) {
0074         setSourceItem(source.value<QQuickItem *>());
0075     } else if (source.canConvert<QImage>()) {
0076         setSourceImage(source.value<QImage>());
0077     } else if (source.canConvert<QIcon>()) {
0078         setSourceImage(source.value<QIcon>().pixmap(128, 128).toImage());
0079     } else if (source.canConvert<QString>()) {
0080         const QString sourceString = source.toString();
0081 
0082         if (QIcon::hasThemeIcon(sourceString)) {
0083             setSourceImage(QIcon::fromTheme(sourceString).pixmap(128, 128).toImage());
0084         } else {
0085             QFuture<QImage> future = QtConcurrent::run([sourceString]() {
0086                 if (auto url = QUrl(sourceString); url.isLocalFile()) {
0087                     return QImage(url.toLocalFile());
0088                 }
0089                 return QImage(sourceString);
0090             });
0091             m_futureSourceImageData = new QFutureWatcher<QImage>(this);
0092             connect(m_futureSourceImageData, &QFutureWatcher<QImage>::finished, this, [this, source]() {
0093                 const QImage image = m_futureSourceImageData->future().result();
0094                 m_futureSourceImageData->deleteLater();
0095                 m_futureSourceImageData = nullptr;
0096                 setSourceImage(image);
0097                 m_source = source;
0098                 Q_EMIT sourceChanged();
0099             });
0100             m_futureSourceImageData->setFuture(future);
0101             return;
0102         }
0103     } else {
0104         return;
0105     }
0106 
0107     m_source = source;
0108     Q_EMIT sourceChanged();
0109 }
0110 
0111 QVariant ImageColors::source() const
0112 {
0113     return m_source;
0114 }
0115 
0116 void ImageColors::setSourceImage(const QImage &image)
0117 {
0118     if (m_window) {
0119         disconnect(m_window.data(), nullptr, this, nullptr);
0120     }
0121     if (m_sourceItem) {
0122         disconnect(m_sourceItem.data(), nullptr, this, nullptr);
0123     }
0124     if (m_grabResult) {
0125         disconnect(m_grabResult.data(), nullptr, this, nullptr);
0126         m_grabResult.clear();
0127     }
0128 
0129     m_sourceItem.clear();
0130 
0131     m_sourceImage = image;
0132     update();
0133 }
0134 
0135 QImage ImageColors::sourceImage() const
0136 {
0137     return m_sourceImage;
0138 }
0139 
0140 void ImageColors::setSourceItem(QQuickItem *source)
0141 {
0142     if (m_sourceItem == source) {
0143         return;
0144     }
0145 
0146     if (m_window) {
0147         disconnect(m_window.data(), nullptr, this, nullptr);
0148     }
0149     if (m_sourceItem) {
0150         disconnect(m_sourceItem, nullptr, this, nullptr);
0151     }
0152     m_sourceItem = source;
0153     update();
0154 
0155     if (m_sourceItem) {
0156         auto syncWindow = [this]() {
0157             if (m_window) {
0158                 disconnect(m_window.data(), nullptr, this, nullptr);
0159             }
0160             m_window = m_sourceItem->window();
0161             if (m_window) {
0162                 connect(m_window, &QWindow::visibleChanged, this, &ImageColors::update);
0163             }
0164         };
0165 
0166         connect(m_sourceItem, &QQuickItem::windowChanged, this, syncWindow);
0167         syncWindow();
0168     }
0169 }
0170 
0171 QQuickItem *ImageColors::sourceItem() const
0172 {
0173     return m_sourceItem;
0174 }
0175 
0176 void ImageColors::update()
0177 {
0178     if (m_futureImageData) {
0179         m_futureImageData->cancel();
0180         m_futureImageData->deleteLater();
0181         m_futureImageData = nullptr;
0182     }
0183     auto runUpdate = [this]() {
0184         QFuture<ImageData> future = QtConcurrent::run([this]() {
0185             return generatePalette(m_sourceImage);
0186         });
0187         m_futureImageData = new QFutureWatcher<ImageData>(this);
0188         connect(m_futureImageData, &QFutureWatcher<ImageData>::finished, this, [this]() {
0189             if (!m_futureImageData) {
0190                 return;
0191             }
0192             m_imageData = m_futureImageData->future().result();
0193             m_futureImageData->deleteLater();
0194             m_futureImageData = nullptr;
0195 
0196             Q_EMIT paletteChanged();
0197         });
0198         m_futureImageData->setFuture(future);
0199     };
0200 
0201     if (!m_sourceItem) {
0202         if (!m_sourceImage.isNull()) {
0203             runUpdate();
0204         } else {
0205             m_imageData = {};
0206             Q_EMIT paletteChanged();
0207         }
0208         return;
0209     }
0210 
0211     if (m_grabResult) {
0212         disconnect(m_grabResult.data(), nullptr, this, nullptr);
0213         m_grabResult.clear();
0214     }
0215 
0216     m_grabResult = m_sourceItem->grabToImage(QSize(128, 128));
0217 
0218     if (m_grabResult) {
0219         connect(m_grabResult.data(), &QQuickItemGrabResult::ready, this, [this, runUpdate]() {
0220             m_sourceImage = m_grabResult->image();
0221             m_grabResult.clear();
0222             runUpdate();
0223         });
0224     }
0225 }
0226 
0227 inline int squareDistance(QRgb color1, QRgb color2)
0228 {
0229     // https://en.wikipedia.org/wiki/Color_difference
0230     // Using RGB distance for performance, as CIEDE2000 is too complicated
0231     if (qRed(color1) - qRed(color2) < 128) {
0232         return 2 * pow(qRed(color1) - qRed(color2), 2) //
0233             + 4 * pow(qGreen(color1) - qGreen(color2), 2) //
0234             + 3 * pow(qBlue(color1) - qBlue(color2), 2);
0235     } else {
0236         return 3 * pow(qRed(color1) - qRed(color2), 2) //
0237             + 4 * pow(qGreen(color1) - qGreen(color2), 2) //
0238             + 2 * pow(qBlue(color1) - qBlue(color2), 2);
0239     }
0240 }
0241 
0242 void ImageColors::positionColor(QRgb rgb, QList<ImageData::colorStat> &clusters)
0243 {
0244     for (auto &stat : clusters) {
0245         if (squareDistance(rgb, stat.centroid) < s_minimumSquareDistance) {
0246             stat.colors.append(rgb);
0247             return;
0248         }
0249     }
0250 
0251     ImageData::colorStat stat;
0252     stat.colors.append(rgb);
0253     stat.centroid = rgb;
0254     clusters << stat;
0255 }
0256 
0257 void ImageColors::positionColorMP(const decltype(ImageData::m_samples) &samples, decltype(ImageData::m_clusters) &clusters, int numCore)
0258 {
0259 #if HAVE_OpenMP
0260     if (samples.size() < 65536 /* 256^2 */ || numCore < 2) {
0261 #else
0262     if (true) {
0263 #endif
0264         // Fall back to single thread
0265         for (auto color : samples) {
0266             positionColor(color, clusters);
0267         }
0268         return;
0269     }
0270 #if HAVE_OpenMP
0271     // Split the whole samples into multiple parts
0272     const int numSamplesPerThread = samples.size() / numCore;
0273     std::vector<decltype(ImageData::m_clusters)> tempClusters(numCore, decltype(ImageData::m_clusters){});
0274 #pragma omp parallel for
0275     for (int i = 0; i < numCore; ++i) {
0276         decltype(ImageData::m_samples) samplePart;
0277         const auto beginIt = std::next(samples.begin(), numSamplesPerThread * i);
0278         const auto endIt = i < numCore - 1 ? std::next(samples.begin(), numSamplesPerThread * (i + 1)) : samples.end();
0279 
0280         for (auto it = beginIt; it != endIt; it = std::next(it)) {
0281             positionColor(*it, tempClusters[omp_get_thread_num()]);
0282         }
0283     } // END omp parallel for
0284 
0285     // Restore clusters
0286     // Don't use std::as_const as memory will grow significantly
0287     for (const auto &clusterPart : tempClusters) {
0288         clusters << clusterPart;
0289     }
0290     for (int i = 0; i < clusters.size() - 1; ++i) {
0291         auto &clusterA = clusters[i];
0292         if (clusterA.colors.empty()) {
0293             continue; // Already merged
0294         }
0295         for (int j = i + 1; j < clusters.size(); ++j) {
0296             auto &clusterB = clusters[j];
0297             if (clusterB.colors.empty()) {
0298                 continue; // Already merged
0299             }
0300             if (squareDistance(clusterA.centroid, clusterB.centroid) < s_minimumSquareDistance) {
0301                 // Merge colors in clusterB into clusterA
0302                 clusterA.colors.append(clusterB.colors);
0303                 clusterB.colors.clear();
0304             }
0305         }
0306     }
0307 
0308     auto removeIt = std::remove_if(clusters.begin(), clusters.end(), [](const ImageData::colorStat &stat) {
0309         return stat.colors.empty();
0310     });
0311     clusters.erase(removeIt, clusters.end());
0312 #endif
0313 }
0314 
0315 ImageData ImageColors::generatePalette(const QImage &sourceImage) const
0316 {
0317     ImageData imageData;
0318 
0319     if (sourceImage.isNull() || sourceImage.width() == 0) {
0320         return imageData;
0321     }
0322 
0323     imageData.m_clusters.clear();
0324     imageData.m_samples.clear();
0325 
0326 #if HAVE_OpenMP
0327     static const int numCore = std::min(8, omp_get_num_procs());
0328     omp_set_num_threads(numCore);
0329 #else
0330     constexpr int numCore = 1;
0331 #endif
0332     int r = 0;
0333     int g = 0;
0334     int b = 0;
0335     int c = 0;
0336 
0337 #pragma omp parallel for collapse(2) reduction(+ : r) reduction(+ : g) reduction(+ : b) reduction(+ : c)
0338     for (int x = 0; x < sourceImage.width(); ++x) {
0339         for (int y = 0; y < sourceImage.height(); ++y) {
0340             const QColor sampleColor = sourceImage.pixelColor(x, y);
0341             if (sampleColor.alpha() == 0) {
0342                 continue;
0343             }
0344             if (ColorUtils::chroma(sampleColor) < 20) {
0345                 continue;
0346             }
0347             QRgb rgb = sampleColor.rgb();
0348             ++c;
0349             r += qRed(rgb);
0350             g += qGreen(rgb);
0351             b += qBlue(rgb);
0352 #pragma omp critical
0353             imageData.m_samples << rgb;
0354         }
0355     } // END omp parallel for
0356 
0357     if (imageData.m_samples.isEmpty()) {
0358         return imageData;
0359     }
0360 
0361     positionColorMP(imageData.m_samples, imageData.m_clusters, numCore);
0362 
0363     imageData.m_average = QColor(r / c, g / c, b / c, 255);
0364 
0365     for (int iteration = 0; iteration < 5; ++iteration) {
0366 #pragma omp parallel for private(r, g, b, c)
0367         for (int i = 0; i < imageData.m_clusters.size(); ++i) {
0368             auto &stat = imageData.m_clusters[i];
0369             r = 0;
0370             g = 0;
0371             b = 0;
0372             c = 0;
0373 
0374             for (auto color : std::as_const(stat.colors)) {
0375                 c++;
0376                 r += qRed(color);
0377                 g += qGreen(color);
0378                 b += qBlue(color);
0379             }
0380             r = r / c;
0381             g = g / c;
0382             b = b / c;
0383             stat.centroid = qRgb(r, g, b);
0384             stat.ratio = qreal(stat.colors.count()) / qreal(imageData.m_samples.count());
0385             stat.colors = QList<QRgb>({stat.centroid});
0386         } // END omp parallel for
0387 
0388         positionColorMP(imageData.m_samples, imageData.m_clusters, numCore);
0389     }
0390 
0391     std::sort(imageData.m_clusters.begin(), imageData.m_clusters.end(), [this](const ImageData::colorStat &a, const ImageData::colorStat &b) {
0392         return getClusterScore(a) > getClusterScore(b);
0393     });
0394 
0395     // compress blocks that became too similar
0396     auto sourceIt = imageData.m_clusters.end();
0397     // Use index instead of iterator, because QList::erase may invalidate iterator.
0398     std::vector<int> itemsToDelete;
0399     while (sourceIt != imageData.m_clusters.begin()) {
0400         sourceIt--;
0401         for (auto destIt = imageData.m_clusters.begin(); destIt != imageData.m_clusters.end() && destIt != sourceIt; destIt++) {
0402             if (squareDistance((*sourceIt).centroid, (*destIt).centroid) < s_minimumSquareDistance) {
0403                 const qreal ratio = (*sourceIt).ratio / (*destIt).ratio;
0404                 const int r = ratio * qreal(qRed((*sourceIt).centroid)) + (1 - ratio) * qreal(qRed((*destIt).centroid));
0405                 const int g = ratio * qreal(qGreen((*sourceIt).centroid)) + (1 - ratio) * qreal(qGreen((*destIt).centroid));
0406                 const int b = ratio * qreal(qBlue((*sourceIt).centroid)) + (1 - ratio) * qreal(qBlue((*destIt).centroid));
0407                 (*destIt).ratio += (*sourceIt).ratio;
0408                 (*destIt).centroid = qRgb(r, g, b);
0409                 itemsToDelete.push_back(std::distance(imageData.m_clusters.begin(), sourceIt));
0410                 break;
0411             }
0412         }
0413     }
0414     for (auto i : std::as_const(itemsToDelete)) {
0415         imageData.m_clusters.removeAt(i);
0416     }
0417 
0418     imageData.m_highlight = QColor();
0419     imageData.m_dominant = QColor(imageData.m_clusters.first().centroid);
0420     imageData.m_closestToBlack = Qt::white;
0421     imageData.m_closestToWhite = Qt::black;
0422 
0423     imageData.m_palette.clear();
0424 
0425     bool first = true;
0426 
0427 #pragma omp parallel for ordered
0428     for (int i = 0; i < imageData.m_clusters.size(); ++i) {
0429         const auto &stat = imageData.m_clusters[i];
0430         QVariantMap entry;
0431         const QColor color(stat.centroid);
0432         entry[QStringLiteral("color")] = color;
0433         entry[QStringLiteral("ratio")] = stat.ratio;
0434 
0435         QColor contrast = QColor(255 - color.red(), 255 - color.green(), 255 - color.blue());
0436         contrast.setHsl(contrast.hslHue(), //
0437                         contrast.hslSaturation(), //
0438                         128 + (128 - contrast.lightness()));
0439         QColor tempContrast;
0440         int minimumDistance = 4681800; // max distance: 4*3*2*3*255*255
0441         for (const auto &stat : std::as_const(imageData.m_clusters)) {
0442             const int distance = squareDistance(contrast.rgb(), stat.centroid);
0443 
0444             if (distance < minimumDistance) {
0445                 tempContrast = QColor(stat.centroid);
0446                 minimumDistance = distance;
0447             }
0448         }
0449 
0450         if (imageData.m_clusters.size() <= 3) {
0451             if (qGray(imageData.m_dominant.rgb()) < 120) {
0452                 contrast = QColor(230, 230, 230);
0453             } else {
0454                 contrast = QColor(20, 20, 20);
0455             }
0456             // TODO: replace m_clusters.size() > 3 with entropy calculation
0457         } else if (squareDistance(contrast.rgb(), tempContrast.rgb()) < s_minimumSquareDistance * 1.5) {
0458             contrast = tempContrast;
0459         } else {
0460             contrast = tempContrast;
0461             contrast.setHsl(contrast.hslHue(),
0462                             contrast.hslSaturation(),
0463                             contrast.lightness() > 128 ? qMin(contrast.lightness() + 20, 255) : qMax(0, contrast.lightness() - 20));
0464         }
0465 
0466         entry[QStringLiteral("contrastColor")] = contrast;
0467 #pragma omp ordered
0468         { // BEGIN omp ordered
0469             if (first) {
0470                 imageData.m_dominantContrast = contrast;
0471                 imageData.m_dominant = color;
0472             }
0473             first = false;
0474 
0475             if (!imageData.m_highlight.isValid() || ColorUtils::chroma(color) > ColorUtils::chroma(imageData.m_highlight)) {
0476                 imageData.m_highlight = color;
0477             }
0478 
0479             if (qGray(color.rgb()) > qGray(imageData.m_closestToWhite.rgb())) {
0480                 imageData.m_closestToWhite = color;
0481             }
0482             if (qGray(color.rgb()) < qGray(imageData.m_closestToBlack.rgb())) {
0483                 imageData.m_closestToBlack = color;
0484             }
0485             imageData.m_palette << entry;
0486         } // END omp ordered
0487     }
0488 
0489     postProcess(imageData);
0490 
0491     return imageData;
0492 }
0493 
0494 double ImageColors::getClusterScore(const ImageData::colorStat &stat) const
0495 {
0496     return stat.ratio * ColorUtils::chroma(QColor(stat.centroid));
0497 }
0498 
0499 void ImageColors::postProcess(ImageData &imageData) const
0500 {
0501     constexpr short unsigned WCAG_NON_TEXT_CONTRAST_RATIO = 3;
0502     constexpr qreal WCAG_TEXT_CONTRAST_RATIO = 4.5;
0503 
0504     auto platformTheme = qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, false);
0505     if (!platformTheme) {
0506         return;
0507     }
0508 
0509     const QColor backgroundColor = static_cast<Kirigami::Platform::PlatformTheme *>(platformTheme)->backgroundColor();
0510     const qreal backgroundLum = ColorUtils::luminance(backgroundColor);
0511     qreal lowerLum, upperLum;
0512     // 192 is from kcm_colors
0513     if (qGray(backgroundColor.rgb()) < 192) {
0514         // (lowerLum + 0.05) / (backgroundLum + 0.05) >= 3
0515         lowerLum = WCAG_NON_TEXT_CONTRAST_RATIO * (backgroundLum + 0.05) - 0.05;
0516         upperLum = 0.95;
0517     } else {
0518         // For light themes, still prefer lighter colors
0519         // (lowerLum + 0.05) / (textLum + 0.05) >= 4.5
0520         const QColor textColor =
0521             static_cast<Kirigami::Platform::PlatformTheme *>(qmlAttachedPropertiesObject<Kirigami::Platform::PlatformTheme>(this, true))->textColor();
0522         const qreal textLum = ColorUtils::luminance(textColor);
0523         lowerLum = WCAG_TEXT_CONTRAST_RATIO * (textLum + 0.05) - 0.05;
0524         upperLum = backgroundLum;
0525     }
0526 
0527     auto adjustSaturation = [](QColor &color) {
0528         // Adjust saturation to make the color more vibrant
0529         if (color.hsvSaturationF() < 0.5) {
0530             const qreal h = color.hsvHueF();
0531             const qreal v = color.valueF();
0532             color.setHsvF(h, 0.5, v);
0533         }
0534     };
0535     adjustSaturation(imageData.m_dominant);
0536     adjustSaturation(imageData.m_highlight);
0537     adjustSaturation(imageData.m_average);
0538 
0539     auto adjustLightness = [lowerLum, upperLum](QColor &color) {
0540         short unsigned colorOperationCount = 0;
0541         const qreal h = color.hslHueF();
0542         const qreal s = color.hslSaturationF();
0543         const qreal l = color.lightnessF();
0544         while (ColorUtils::luminance(color.rgb()) < lowerLum && colorOperationCount++ < 10) {
0545             color.setHslF(h, s, std::min(1.0, l + colorOperationCount * 0.03));
0546         }
0547         while (ColorUtils::luminance(color.rgb()) > upperLum && colorOperationCount++ < 10) {
0548             color.setHslF(h, s, std::max(0.0, l - colorOperationCount * 0.03));
0549         }
0550     };
0551     adjustLightness(imageData.m_dominant);
0552     adjustLightness(imageData.m_highlight);
0553     adjustLightness(imageData.m_average);
0554 }
0555 
0556 QVariantList ImageColors::palette() const
0557 {
0558     if (m_futureImageData) {
0559         qCWarning(KirigamiLog) << m_futureImageData->future().isFinished();
0560     }
0561     return_fallback(m_fallbackPalette) return m_imageData.m_palette;
0562 }
0563 
0564 ColorUtils::Brightness ImageColors::paletteBrightness() const
0565 {
0566     /* clang-format off */
0567     return_fallback(m_fallbackPaletteBrightness)
0568 
0569     return qGray(m_imageData.m_dominant.rgb()) < 128 ? ColorUtils::Dark : ColorUtils::Light;
0570     /* clang-format on */
0571 }
0572 
0573 QColor ImageColors::average() const
0574 {
0575     /* clang-format off */
0576     return_fallback_finally(m_fallbackAverage, linkBackgroundColor)
0577 
0578     return m_imageData.m_average;
0579     /* clang-format on */
0580 }
0581 
0582 QColor ImageColors::dominant() const
0583 {
0584     /* clang-format off */
0585     return_fallback_finally(m_fallbackDominant, linkBackgroundColor)
0586 
0587     return m_imageData.m_dominant;
0588     /* clang-format on */
0589 }
0590 
0591 QColor ImageColors::dominantContrast() const
0592 {
0593     /* clang-format off */
0594     return_fallback_finally(m_fallbackDominantContrasting, linkBackgroundColor)
0595 
0596     return m_imageData.m_dominantContrast;
0597     /* clang-format on */
0598 }
0599 
0600 QColor ImageColors::foreground() const
0601 {
0602     /* clang-format off */
0603     return_fallback_finally(m_fallbackForeground, textColor)
0604 
0605     if (paletteBrightness() == ColorUtils::Dark)
0606     {
0607         if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
0608             return QColor(230, 230, 230);
0609         }
0610         return m_imageData.m_closestToWhite;
0611     } else {
0612         if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
0613             return QColor(20, 20, 20);
0614         }
0615         return m_imageData.m_closestToBlack;
0616     }
0617     /* clang-format on */
0618 }
0619 
0620 QColor ImageColors::background() const
0621 {
0622     /* clang-format off */
0623     return_fallback_finally(m_fallbackBackground, backgroundColor)
0624 
0625     if (paletteBrightness() == ColorUtils::Dark) {
0626         if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
0627             return QColor(20, 20, 20);
0628         }
0629         return m_imageData.m_closestToBlack;
0630     } else {
0631         if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
0632             return QColor(230, 230, 230);
0633         }
0634         return m_imageData.m_closestToWhite;
0635     }
0636     /* clang-format on */
0637 }
0638 
0639 QColor ImageColors::highlight() const
0640 {
0641     /* clang-format off */
0642     return_fallback_finally(m_fallbackHighlight, linkColor)
0643 
0644     return m_imageData.m_highlight;
0645     /* clang-format on */
0646 }
0647 
0648 QColor ImageColors::closestToWhite() const
0649 {
0650     /* clang-format off */
0651     return_fallback(Qt::white)
0652     if (qGray(m_imageData.m_closestToWhite.rgb()) < 200) {
0653         return QColor(230, 230, 230);
0654     }
0655     /* clang-format on */
0656 
0657     return m_imageData.m_closestToWhite;
0658 }
0659 
0660 QColor ImageColors::closestToBlack() const
0661 {
0662     /* clang-format off */
0663     return_fallback(Qt::black)
0664     if (qGray(m_imageData.m_closestToBlack.rgb()) > 80) {
0665         return QColor(20, 20, 20);
0666     }
0667     /* clang-format on */
0668     return m_imageData.m_closestToBlack;
0669 }
0670 
0671 #include "moc_imagecolors.cpp"