diff --git a/src/controls/BasicListItem.qml b/src/controls/BasicListItem.qml --- a/src/controls/BasicListItem.qml +++ b/src/controls/BasicListItem.qml @@ -100,7 +100,6 @@ Layout.maximumHeight: size Layout.minimumWidth: size selected: layout.indicateActiveFocus && (listItem.highlighted || listItem.checked || (listItem.pressed && listItem.supportsMouseEvents)) - color: selected ? Theme.highlightedTextColor : listItem.Theme.textColor opacity: 1 } QQC2.Label { diff --git a/src/desktopicon.h b/src/desktopicon.h --- a/src/desktopicon.h +++ b/src/desktopicon.h @@ -97,16 +97,19 @@ void handleFinished(QNetworkAccessManager* qnam, QNetworkReply* reply); void handleReadyRead(QNetworkReply* reply); QIcon::Mode iconMode() const; + bool guessMonochrome(const QImage &img); private: Kirigami::PlatformTheme *m_theme = nullptr; QPointer m_networkReply; + QHash m_monochromeHeuristics; QVariant m_source; bool m_smooth; bool m_changed; bool m_active; bool m_selected; bool m_isMask; + bool m_isMaskHeuristic = false; QImage m_loadedImage; QColor m_color = Qt::transparent; QString m_fallback = QStringLiteral("unknown"); diff --git a/src/desktopicon.cpp b/src/desktopicon.cpp --- a/src/desktopicon.cpp +++ b/src/desktopicon.cpp @@ -160,6 +160,7 @@ } m_source = icon; m_changed = true; + m_monochromeHeuristics.clear(); if (!m_theme) { m_theme = static_cast(qmlAttachedPropertiesObject(this, true)); @@ -171,6 +172,14 @@ }); } + if (icon.type() == QVariant::String) { + const QString iconSource = icon.toString(); + m_isMaskHeuristic = (iconSource.endsWith(QStringLiteral("-symbolic")) + || iconSource.endsWith(QStringLiteral("-symbolic-rtl")) + || iconSource.endsWith(QStringLiteral("-symbolic-ltr"))); + emit isMaskChanged(); + } + if (m_networkReply) { //if there was a network query going on, interrupt it m_networkReply->close(); @@ -229,14 +238,15 @@ } m_isMask = mask; + m_isMaskHeuristic = mask; m_changed = true; update(); emit isMaskChanged(); } bool DesktopIcon::isMask() const { - return m_isMask; + return m_isMask || m_isMaskHeuristic; } void DesktopIcon::setColor(const QColor &color) @@ -330,13 +340,23 @@ img = QImage(size, QImage::Format_Alpha8); img.fill(Qt::transparent); } - if (img.size() != size){ + if (img.size() != size) { // At this point, the image will already be scaled, but we need to output it in // the correct aspect ratio, painted centered in the viewport. So: QRect destination(QPoint(0, 0), img.size().scaled(itemSize, Qt::KeepAspectRatio)); destination.moveCenter(nodeRect.center()); nodeRect = destination; } + + const QColor tintColor = !m_color.isValid() || m_color == Qt::transparent ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor()) : m_color; + + //TODO: initialize m_isMask with icon.isMask() + if (tintColor.alpha() > 0 && (isMask() || guessMonochrome(img))) { + QPainter p(&img); + p.setCompositionMode(QPainter::CompositionMode_SourceIn); + p.fillRect(img.rect(), tintColor); + p.end(); + } } m_changed = false; @@ -481,26 +501,15 @@ } if (!icon.isNull()) { img = icon.pixmap(size, iconMode(), QIcon::On).toImage(); - qreal ratio = 1; - if (window() && window()->screen()) { - ratio = window()->screen()->devicePixelRatio(); - } - const QColor tintColor = !m_color.isValid() || m_color == Qt::transparent ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor()) : m_color; + /*const QColor tintColor = !m_color.isValid() || m_color == Qt::transparent ? (m_selected ? m_theme->highlightedTextColor() : m_theme->textColor()) : m_color; - if (m_isMask || - //this is an heuristic to decide when to tint and when to just draw - //(fullcolor icons) in reality on basic styles the only colored icons should be -symbolic, this heuristic is the most compatible middle ground - icon.isMask() || - //if symbolic color based on tintColor - (iconSource.endsWith(QLatin1String("-symbolic")) && tintColor.isValid() && tintColor != Qt::transparent) || - //if path color based on m_color - (isPath && m_color.isValid() && m_color != Qt::transparent)) { + if (m_isMask || icon.isMask() || iconSource.endsWith(QStringLiteral("-symbolic")) || iconSource.endsWith(QStringLiteral("-symbolic-rtl")) || iconSource.endsWith(QStringLiteral("-symbolic-ltr")) || guessMonochrome(img)) { QPainter p(&img); p.setCompositionMode(QPainter::CompositionMode_SourceIn); p.fillRect(img.rect(), tintColor); p.end(); - } + }*/ } } return img; @@ -518,6 +527,67 @@ return QIcon::Normal; } +bool DesktopIcon::guessMonochrome(const QImage &img) +{ + //don't try for too big images + if (img.width() >= 256 || m_theme->supportsIconColoring()) { + return false; + } + // round size to a standard size. hardcode as we can't use KIconLoader + int stdSize; + if (img.width() <= 16) { + stdSize = 16; + } else if (img.width() <= 22) { + stdSize = 22; + } else if (img.width() <= 24) { + stdSize = 24; + } else if (img.width() <= 32) { + stdSize = 32; + } else if (img.width() <= 48) { + stdSize = 48; + } else if (img.width() <= 64) { + stdSize = 64; + } else { + stdSize = 128; + } + + auto findIt = m_monochromeHeuristics.constFind(stdSize); + if (findIt != m_monochromeHeuristics.constEnd()) { + return findIt.value(); + } + + QHash dist; + int transparentPixels = 0; + int saturatedPixels = 0; + for(int x=0; x < img.width(); x++) { + for(int y=0; y < img.height(); y++) { + QColor color = QColor::fromRgba(qUnpremultiply(img.pixel(x, y))); + if (color.alpha() < 100) { + ++transparentPixels; + continue; + } else if (color.saturation() > 84) { + ++saturatedPixels; + } + dist[qGray(color.rgb())]++; + } + } + + QMultiMap reverseDist; + auto it = dist.constBegin(); + std::vector probabilities(dist.size()); + qreal entropy = 0; + while (it != dist.constEnd()) { + reverseDist.insertMulti(it.value(), it.key()); + qreal probability = qreal(it.value()) / qreal(img.size().width() * img.size().height() - transparentPixels); + entropy -= probability * log(probability) / log(255); + ++it; + } + + // Arbitrarly low values of entropy and colored pixels + m_monochromeHeuristics[stdSize] = saturatedPixels <= (img.size().width()*img.size().height() - transparentPixels) * 0.3 && entropy <= 0.3; + return m_monochromeHeuristics[stdSize]; +} + QString DesktopIcon::fallback() const { return m_fallback; diff --git a/src/libkirigami/platformtheme.h b/src/libkirigami/platformtheme.h --- a/src/libkirigami/platformtheme.h +++ b/src/libkirigami/platformtheme.h @@ -214,6 +214,8 @@ //this will be used by desktopicon to fetch icons with KIconLoader virtual Q_INVOKABLE QIcon iconFromTheme(const QString &name, const QColor &customColor = Qt::transparent); + bool supportsIconColoring() const; + //foreground colors void setCustomTextColor(const QColor &color = QColor()); void setCustomDisabledTextColor(const QColor &color = QColor()); @@ -246,7 +248,8 @@ protected: //Setters, not accessible from QML but from implementations - + void setSupportsIconColoring(bool support); + //foreground colors void setTextColor(const QColor &color); void setDisabledTextColor(const QColor &color); diff --git a/src/libkirigami/platformtheme.cpp b/src/libkirigami/platformtheme.cpp --- a/src/libkirigami/platformtheme.cpp +++ b/src/libkirigami/platformtheme.cpp @@ -93,6 +93,7 @@ QFont font; bool m_inherit = true; bool m_init = true; + bool m_supportsIconColoring = false; static KirigamiPluginFactory *s_pluginFactory; }; @@ -694,12 +695,18 @@ QIcon PlatformTheme::iconFromTheme(const QString &name, const QColor &customColor) { QIcon icon = QIcon::fromTheme(name); - if (!icon.isNull() && (name.endsWith(QLatin1String("-symbolic")) || customColor != Qt::transparent)) { - icon.setIsMask(true); - } return icon; } +bool PlatformTheme::supportsIconColoring() const +{ + return d->m_supportsIconColoring; +} + +void PlatformTheme::setSupportsIconColoring(bool support) +{ + d->m_supportsIconColoring = support; +} PlatformTheme *PlatformTheme::qmlAttachedProperties(QObject *object)