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