Warning, file /plasma/plasma-workspace/kcms/colors/colorsapplicator.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2021 Dan Leinir Turthra Jensen <admin@leinir.dk> 0003 SPDX-FileCopyrightText: 2021 Benjamin Port <benjamin.port@enioka.com> 0004 0005 SPDX-License-Identifier: LGPL-2.0-only 0006 */ 0007 0008 #include "../kcms-common_p.h" 0009 #include "../krdb/krdb.h" 0010 0011 #include <KColorScheme> 0012 #include <KConfigGroup> 0013 0014 #include <QColorSpace> 0015 #include <QDBusConnection> 0016 #include <QDBusMessage> 0017 #include <QGenericMatrix> 0018 #include <QtMath> 0019 0020 #include "colorsapplicator.h" 0021 0022 float lerp(float a, float b, float f) 0023 { 0024 return (a * (1.0 - f)) + (b * f); 0025 } 0026 0027 qreal cubeRootOf(qreal num) 0028 { 0029 return qPow(num, 1.0 / 3.0); 0030 } 0031 0032 qreal cubed(qreal num) 0033 { 0034 return num * num * num; 0035 } 0036 0037 // a structure representing a colour in the OKlab colour space. 0038 // for tinting, OKlab has some desirable properties: 0039 // - lightness is separated from hue (unlike RGB) 0040 // so we don't make light themes darkish or dark themes lightish 0041 // - allows accurately adjusting hue without affecting perceptual lightness (unlike HSL/V) 0042 // so we keep light themes and dark themes at the same perceptual lightness 0043 // - can be linearly blended 0044 // once we get into oklab, we don't need fancy math to manipulate colours, we can just use a bog-standard 0045 // linear interpolation function on the a and b values 0046 struct LAB { 0047 qreal L = 0; 0048 qreal a = 0; 0049 qreal b = 0; 0050 }; 0051 0052 // precomputed matrices from Björn Ottosson, public domain. or MIT if your country doesn't do that. 0053 /* 0054 SPDX-FileCopyrightText: 2020 Björn Ottosson 0055 0056 SPDX-License-Identifier: MIT 0057 SPDX-License-Identifier: None 0058 */ 0059 0060 LAB linearSRGBToOKLab(const QColor &c) 0061 { 0062 // convert from srgb to linear lms 0063 0064 const auto l = 0.4122214708 * c.redF() + 0.5363325363 * c.greenF() + 0.0514459929 * c.blueF(); 0065 const auto m = 0.2119034982 * c.redF() + 0.6806995451 * c.greenF() + 0.1073969566 * c.blueF(); 0066 const auto s = 0.0883024619 * c.redF() + 0.2817188376 * c.greenF() + 0.6299787005 * c.blueF(); 0067 0068 // convert from linear lms to non-linear lms 0069 0070 const auto l_ = cubeRootOf(l); 0071 const auto m_ = cubeRootOf(m); 0072 const auto s_ = cubeRootOf(s); 0073 0074 // convert from non-linear lms to lab 0075 0076 return LAB{.L = 0.2104542553 * l_ + 0.7936177850 * m_ - 0.0040720468 * s_, 0077 .a = 1.9779984951 * l_ - 2.4285922050 * m_ + 0.4505937099 * s_, 0078 .b = 0.0259040371 * l_ + 0.7827717662 * m_ - 0.8086757660 * s_}; 0079 } 0080 0081 QColor OKLabToLinearSRGB(LAB lab) 0082 { 0083 // convert from lab to non-linear lms 0084 0085 const auto l_ = lab.L + 0.3963377774 * lab.a + 0.2158037573 * lab.b; 0086 const auto m_ = lab.L - 0.1055613458 * lab.a - 0.0638541728 * lab.b; 0087 const auto s_ = lab.L - 0.0894841775 * lab.a - 1.2914855480 * lab.b; 0088 0089 // convert from non-linear lms to linear lms 0090 0091 const auto l = cubed(l_); 0092 const auto m = cubed(m_); 0093 const auto s = cubed(s_); 0094 0095 // convert from linear lms to linear srgb 0096 0097 const auto r = +4.0767416621 * l - 3.3077115913 * m + 0.2309699292 * s; 0098 const auto g = -1.2684380046 * l + 2.6097574011 * m - 0.3413193965 * s; 0099 const auto b = -0.0041960863 * l - 0.7034186147 * m + 1.7076147010 * s; 0100 0101 return QColor::fromRgbF(r, g, b); 0102 } 0103 0104 auto toLinearSRGB = QColorSpace(QColorSpace::SRgb).transformationToColorSpace(QColorSpace::SRgbLinear); 0105 auto fromLinearSRGB = QColorSpace(QColorSpace::SRgbLinear).transformationToColorSpace(QColorSpace::SRgb); 0106 0107 QColor tintColor(const QColor &base, const QColor &with, qreal factor) 0108 { 0109 auto baseLAB = linearSRGBToOKLab(toLinearSRGB.map(base)); 0110 const auto withLAB = linearSRGBToOKLab(toLinearSRGB.map(with)); 0111 baseLAB.a = lerp(baseLAB.a, withLAB.a, factor); 0112 baseLAB.b = lerp(baseLAB.b, withLAB.b, factor); 0113 0114 return fromLinearSRGB.map(OKLabToLinearSRGB(baseLAB)); 0115 } 0116 0117 static void copyEntry(KConfigGroup &from, KConfigGroup &to, const QString &entry, KConfig::WriteConfigFlags writeConfigFlag = KConfig::Normal) 0118 { 0119 if (from.hasKey(entry)) { 0120 to.writeEntry(entry, from.readEntry(entry), writeConfigFlag); 0121 } 0122 } 0123 0124 void applyScheme(const QString &colorSchemePath, KConfig *configOutput, KConfig::WriteConfigFlags writeConfigFlag, std::optional<QColor> accentColor) 0125 { 0126 const auto accent = accentColor.value_or(configOutput->group("General").readEntry("AccentColor", QColor())); 0127 0128 const auto hasAccent = [configOutput, &accent, accentColor]() { 0129 if (accent == QColor(Qt::transparent)) { 0130 return false; 0131 } 0132 0133 return configOutput->group("General").hasKey("AccentColor") 0134 || accentColor.has_value(); // It's obvious that when accentColor.hasValue, it has (non-default/non-transparent) accent. reading configOutput for 0135 // any config is unreliable in this file. 0136 }(); 0137 0138 // Using KConfig::SimpleConfig because otherwise Header colors won't be 0139 // rewritten when a new color scheme is loaded. 0140 KSharedConfigPtr config = KSharedConfig::openConfig(colorSchemePath, KConfig::SimpleConfig); 0141 0142 const auto applyAccentToTitlebar = 0143 config->group("General").readEntry("TitlebarIsAccentColored", config->group("General").readEntry("accentActiveTitlebar", false)); 0144 const auto tintAccent = config->group("General").hasKey("TintFactor"); 0145 const auto tintFactor = config->group("General").readEntry<qreal>("TintFactor", DefaultTintFactor); 0146 0147 const QStringList colorSetGroupList{QStringLiteral("Colors:View"), 0148 QStringLiteral("Colors:Window"), 0149 QStringLiteral("Colors:Button"), 0150 QStringLiteral("Colors:Selection"), 0151 QStringLiteral("Colors:Tooltip"), 0152 QStringLiteral("Colors:Complementary"), 0153 QStringLiteral("Colors:Header")}; 0154 0155 const QStringList colorSetKeyList{QStringLiteral("BackgroundNormal"), 0156 QStringLiteral("BackgroundAlternate"), 0157 QStringLiteral("ForegroundNormal"), 0158 QStringLiteral("ForegroundInactive"), 0159 QStringLiteral("ForegroundActive"), 0160 QStringLiteral("ForegroundLink"), 0161 QStringLiteral("ForegroundVisited"), 0162 QStringLiteral("ForegroundNegative"), 0163 QStringLiteral("ForegroundNeutral"), 0164 QStringLiteral("ForegroundPositive"), 0165 QStringLiteral("DecorationFocus"), 0166 QStringLiteral("DecorationHover")}; 0167 0168 const QStringList accentList{QStringLiteral("ForegroundActive"), 0169 QStringLiteral("ForegroundLink"), 0170 QStringLiteral("DecorationFocus"), 0171 QStringLiteral("DecorationHover")}; 0172 0173 for (const auto &item : colorSetGroupList) { 0174 configOutput->deleteGroup(item); 0175 0176 // Not all color schemes have header colors; in this case we don't want 0177 // to write out any header color data because then various things will think 0178 // the color scheme *does* have header colors, which it mostly doesn't, and 0179 // things will visually break in creative ways 0180 if (item == QStringLiteral("Colors:Header") && !config->hasGroup(QStringLiteral("Colors:Header"))) { 0181 continue; 0182 } 0183 0184 KConfigGroup sourceGroup(config, item); 0185 KConfigGroup targetGroup(configOutput, item); 0186 0187 for (const auto &entry : colorSetKeyList) { 0188 if (hasAccent) { 0189 if (accentList.contains(entry)) { 0190 targetGroup.writeEntry(entry, accent); 0191 } else if (tintAccent) { 0192 auto base = sourceGroup.readEntry<QColor>(entry, QColor()); 0193 targetGroup.writeEntry(entry, tintColor(base, accent, tintFactor)); 0194 } else { 0195 copyEntry(sourceGroup, targetGroup, entry); 0196 } 0197 } else { 0198 copyEntry(sourceGroup, targetGroup, entry); 0199 } 0200 } 0201 0202 if (item == QStringLiteral("Colors:Selection") && hasAccent) { 0203 QColor accentbg = accentBackground(accent, config->group("Colors:View").readEntry<QColor>("BackgroundNormal", QColor())); 0204 for (const auto &entry : {QStringLiteral("BackgroundNormal"), QStringLiteral("BackgroundAlternate")}) { 0205 targetGroup.writeEntry(entry, accentbg); 0206 } 0207 for (const auto &entry : {QStringLiteral("ForegroundNormal"), QStringLiteral("ForegroundInactive")}) { 0208 targetGroup.writeEntry(entry, accentForeground(accentbg, true)); 0209 } 0210 } 0211 0212 if (item == QStringLiteral("Colors:Button") && hasAccent) { 0213 QColor accentbg = accentBackground(accent, config->group("Colors:Button").readEntry<QColor>("BackgroundNormal", QColor())); 0214 for (const auto &entry : {QStringLiteral("BackgroundAlternate")}) { 0215 targetGroup.writeEntry(entry, accentbg); 0216 } 0217 } 0218 0219 if (sourceGroup.hasGroup("Inactive")) { 0220 sourceGroup = sourceGroup.group("Inactive"); 0221 targetGroup = targetGroup.group("Inactive"); 0222 0223 for (const auto &entry : colorSetKeyList) { 0224 if (tintAccent) { 0225 auto base = sourceGroup.readEntry<QColor>(entry, QColor()); 0226 targetGroup.writeEntry(entry, tintColor(base, accent, tintFactor)); 0227 } else { 0228 copyEntry(sourceGroup, targetGroup, entry, writeConfigFlag); 0229 } 0230 } 0231 } 0232 0233 // Header accent colouring 0234 if (item == QStringLiteral("Colors:Header") && hasAccent) { 0235 const auto windowBackground = config->group("Colors:Window").readEntry<QColor>("BackgroundNormal", QColor()); 0236 const auto accentedWindowBackground = accentBackground(accent, windowBackground); 0237 const auto inactiveWindowBackground = tintColor(windowBackground, accent, tintFactor); 0238 0239 if (applyAccentToTitlebar) { 0240 targetGroup = KConfigGroup(configOutput, item); 0241 targetGroup.writeEntry("BackgroundNormal", accentedWindowBackground); 0242 targetGroup.writeEntry("ForegroundNormal", accentForeground(accentedWindowBackground, true)); 0243 0244 targetGroup = targetGroup.group("Inactive"); 0245 targetGroup.writeEntry("BackgroundNormal", inactiveWindowBackground); 0246 targetGroup.writeEntry("ForegroundNormal", accentForeground(inactiveWindowBackground, false)); 0247 } 0248 } 0249 } 0250 0251 KConfigGroup groupWMTheme(config, "WM"); 0252 KConfigGroup groupWMOut(configOutput, "WM"); 0253 KColorScheme inactiveHeaderColorScheme(QPalette::Inactive, KColorScheme::Header, config); 0254 0255 const QStringList colorItemListWM{QStringLiteral("activeBackground"), 0256 QStringLiteral("activeForeground"), 0257 QStringLiteral("inactiveBackground"), 0258 QStringLiteral("inactiveForeground"), 0259 QStringLiteral("activeBlend"), 0260 QStringLiteral("inactiveBlend")}; 0261 0262 const QVector<QColor> defaultWMColors{KColorScheme(QPalette::Normal, KColorScheme::Header, config).background().color(), 0263 KColorScheme(QPalette::Normal, KColorScheme::Header, config).foreground().color(), 0264 inactiveHeaderColorScheme.background().color(), 0265 inactiveHeaderColorScheme.foreground().color(), 0266 KColorScheme(QPalette::Normal, KColorScheme::Header, config).background().color(), 0267 inactiveHeaderColorScheme.background().color()}; 0268 0269 int i = 0; 0270 for (const QString &coloritem : colorItemListWM) { 0271 groupWMOut.writeEntry(coloritem, groupWMTheme.readEntry(coloritem, defaultWMColors.value(i)), writeConfigFlag); 0272 ++i; 0273 } 0274 0275 if (hasAccent && (tintAccent || applyAccentToTitlebar)) { // Titlebar accent colouring 0276 const auto windowBackground = config->group("Colors:Window").readEntry<QColor>("BackgroundNormal", QColor()); 0277 0278 if (tintAccent) { 0279 const auto tintedWindowBackground = tintColor(windowBackground, accent, tintFactor); 0280 if (!applyAccentToTitlebar) { 0281 groupWMOut.writeEntry("activeBackground", tintedWindowBackground, writeConfigFlag); 0282 groupWMOut.writeEntry("activeForeground", accentForeground(tintedWindowBackground, true), writeConfigFlag); 0283 } 0284 groupWMOut.writeEntry("inactiveBackground", tintedWindowBackground, writeConfigFlag); 0285 groupWMOut.writeEntry("inactiveForeground", accentForeground(tintedWindowBackground, false), writeConfigFlag); 0286 } 0287 0288 if (applyAccentToTitlebar) { 0289 const auto accentedWindowBackground = accentBackground(accent, windowBackground); 0290 groupWMOut.writeEntry("activeBackground", accentedWindowBackground, writeConfigFlag); 0291 groupWMOut.writeEntry("activeForeground", accentForeground(accentedWindowBackground, true), writeConfigFlag); 0292 } 0293 } 0294 0295 const QStringList groupNameList{QStringLiteral("ColorEffects:Inactive"), QStringLiteral("ColorEffects:Disabled")}; 0296 0297 const QStringList effectList{QStringLiteral("Enable"), 0298 QStringLiteral("ChangeSelectionColor"), 0299 QStringLiteral("IntensityEffect"), 0300 QStringLiteral("IntensityAmount"), 0301 QStringLiteral("ColorEffect"), 0302 QStringLiteral("ColorAmount"), 0303 QStringLiteral("Color"), 0304 QStringLiteral("ContrastEffect"), 0305 QStringLiteral("ContrastAmount")}; 0306 0307 for (const QString &groupName : groupNameList) { 0308 KConfigGroup groupEffectOut(configOutput, groupName); 0309 KConfigGroup groupEffectTheme(config, groupName); 0310 0311 for (const QString &effect : effectList) { 0312 groupEffectOut.writeEntry(effect, groupEffectTheme.readEntry(effect), writeConfigFlag); 0313 } 0314 } 0315 0316 bool applyToAlien{true}; 0317 { 0318 KConfig cfg(QStringLiteral("kcmdisplayrc"), KConfig::NoGlobals); 0319 KConfigGroup group(configOutput, "General"); 0320 group = KConfigGroup(&cfg, "X11"); 0321 applyToAlien = group.readEntry("exportKDEColors", applyToAlien); 0322 } 0323 runRdb(KRdbExportQtColors | KRdbExportGtkTheme | (applyToAlien ? KRdbExportColors : 0)); 0324 }