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"