File indexing completed on 2025-01-05 03:29:15
0001 /* 0002 * SPDX-FileCopyrightText: (C) 2020 Carl Schwan <carl@carlschwan.eu> 0003 * SPDX-FileCopyrightText: (C) 2022 Vlad Rakhmanin <vladimir.rakhmanin@ucdconnect.ie> 0004 * 0005 * SPDX-License-Identifier: GPL-3.0-or-later 0006 */ 0007 0008 #include "kontrast.h" 0009 0010 #include <QDebug> 0011 #include <QRandomGenerator> 0012 #include <QtMath> 0013 0014 #include <KLocalizedString> 0015 0016 #ifdef QT_DBUS_LIB 0017 #include <QDBusConnection> 0018 #include <QDBusMessage> 0019 #include <QDBusMetaType> 0020 #include <QDBusObjectPath> 0021 #include <QDBusPendingCall> 0022 #include <QDBusPendingCallWatcher> 0023 #include <QDBusPendingReply> 0024 0025 QDBusArgument &operator<<(QDBusArgument &arg, const Kontrast::ColorRGB &color) 0026 { 0027 arg.beginStructure(); 0028 arg << color.red << color.green << color.blue; 0029 arg.endStructure(); 0030 return arg; 0031 } 0032 0033 const QDBusArgument &operator>>(const QDBusArgument &arg, Kontrast::ColorRGB &color) 0034 { 0035 double red, green, blue; 0036 arg.beginStructure(); 0037 arg >> red >> green >> blue; 0038 color.red = red; 0039 color.green = green; 0040 color.blue = blue; 0041 arg.endStructure(); 0042 0043 return arg; 0044 } 0045 #endif 0046 0047 Kontrast::Kontrast(QObject *parent) 0048 : QObject(parent) 0049 , m_fontSize(12) 0050 { 0051 setObjectName(QStringLiteral("Kontrast")); 0052 0053 #ifdef QT_DBUS_LIB 0054 qDBusRegisterMetaType<ColorRGB>(); 0055 #endif 0056 } 0057 0058 QColor Kontrast::textColor() const 0059 { 0060 return m_textColor; 0061 } 0062 0063 void Kontrast::setTextColor(const QColor textColor) 0064 { 0065 if (textColor == m_textColor) { 0066 return; 0067 } 0068 0069 m_textColor = textColor; 0070 Q_EMIT textColorChanged(); 0071 Q_EMIT contrastChanged(); 0072 Q_EMIT fontSizeChanged(); 0073 } 0074 0075 int Kontrast::textHue() const 0076 { 0077 return qBound(0, m_textColor.hslHue(), 359); 0078 } 0079 0080 void Kontrast::setTextHue(int hue) 0081 { 0082 if (m_textColor.hslHue() == hue) { 0083 return; 0084 } 0085 m_textColor.setHsl(hue, m_textColor.hslSaturation(), m_textColor.lightness()); 0086 Q_EMIT textColorChanged(); 0087 Q_EMIT contrastChanged(); 0088 Q_EMIT fontSizeChanged(); 0089 } 0090 0091 int Kontrast::textLightness() const 0092 { 0093 return m_textColor.lightness(); 0094 } 0095 0096 void Kontrast::setTextLightness(int lightness) 0097 { 0098 if (m_textColor.lightness() == lightness) { 0099 return; 0100 } 0101 m_textColor.setHsl(m_textColor.hslHue(), m_textColor.hslSaturation(), lightness); 0102 Q_EMIT textColorChanged(); 0103 Q_EMIT contrastChanged(); 0104 Q_EMIT fontSizeChanged(); 0105 } 0106 0107 int Kontrast::textSaturation() const 0108 { 0109 return m_textColor.saturation(); 0110 } 0111 0112 void Kontrast::setTextSaturation(int saturation) 0113 { 0114 if (m_textColor.saturation() == saturation) { 0115 return; 0116 } 0117 m_textColor.setHsl(m_textColor.hslHue(), saturation, m_textColor.lightness()); 0118 Q_EMIT textColorChanged(); 0119 Q_EMIT contrastChanged(); 0120 Q_EMIT fontSizeChanged(); 0121 } 0122 0123 int Kontrast::fontSize() const 0124 { 0125 return m_fontSize; 0126 } 0127 0128 void Kontrast::setFontSize(int fontSize) 0129 { 0130 if (m_fontSize == fontSize) { 0131 return; 0132 } 0133 m_fontSize = fontSize; 0134 Q_EMIT fontSizeChanged(); 0135 } 0136 0137 QColor Kontrast::backgroundColor() const 0138 { 0139 return m_backgroundColor; 0140 } 0141 0142 void Kontrast::setBackgroundColor(const QColor backgroundColor) 0143 { 0144 if (backgroundColor == m_backgroundColor) { 0145 return; 0146 } 0147 0148 m_backgroundColor = backgroundColor; 0149 Q_EMIT backgroundColorChanged(); 0150 Q_EMIT contrastChanged(); 0151 Q_EMIT fontSizeChanged(); 0152 } 0153 0154 int Kontrast::backgroundHue() const 0155 { 0156 return qBound(0, m_backgroundColor.hslHue(), 359); 0157 } 0158 0159 void Kontrast::setBackgroundHue(int hue) 0160 { 0161 if (m_backgroundColor.hslHue() == hue) { 0162 return; 0163 } 0164 m_backgroundColor.setHsl(hue, m_backgroundColor.hslSaturation(), m_backgroundColor.lightness()); 0165 Q_EMIT backgroundColorChanged(); 0166 Q_EMIT contrastChanged(); 0167 Q_EMIT fontSizeChanged(); 0168 } 0169 0170 int Kontrast::backgroundLightness() const 0171 { 0172 return m_backgroundColor.lightness(); 0173 } 0174 0175 void Kontrast::setBackgroundLightness(int lightness) 0176 { 0177 if (m_backgroundColor.lightness() == lightness) { 0178 return; 0179 } 0180 m_backgroundColor.setHsl(m_backgroundColor.hslHue(), m_backgroundColor.hslSaturation(), lightness); 0181 Q_EMIT backgroundColorChanged(); 0182 Q_EMIT contrastChanged(); 0183 Q_EMIT fontSizeChanged(); 0184 } 0185 0186 int Kontrast::backgroundSaturation() const 0187 { 0188 return m_backgroundColor.saturation(); 0189 } 0190 0191 void Kontrast::setBackgroundSaturation(int saturation) 0192 { 0193 if (m_backgroundColor.saturation() == saturation) { 0194 return; 0195 } 0196 m_backgroundColor.setHsl(m_backgroundColor.hslHue(), saturation, m_backgroundColor.lightness()); 0197 Q_EMIT backgroundColorChanged(); 0198 Q_EMIT contrastChanged(); 0199 Q_EMIT fontSizeChanged(); 0200 } 0201 0202 qreal luminosity(const QColor color) 0203 { 0204 // http://www.w3.org/TR/WCAG20/#relativeluminancedef 0205 const qreal red = color.redF(); 0206 const qreal green = color.greenF(); 0207 const qreal blue = color.blueF(); 0208 0209 const qreal redLum = (red <= 0.03928) ? red / 12.92 : qPow(((red + 0.055) / 1.055), 2.4); 0210 const qreal greenLum = (green <= 0.03928) ? green / 12.92 : qPow(((green + 0.055) / 1.055), 2.4); 0211 const qreal blueLum = (blue <= 0.03928) ? blue / 12.92 : qPow(((blue + 0.055) / 1.055), 2.4); 0212 0213 return 0.2126 * redLum + 0.7152 * greenLum + 0.0722 * blueLum; 0214 } 0215 0216 qreal Kontrast::contrast() const 0217 { 0218 const qreal lum1 = luminosity(m_textColor); 0219 const qreal lum2 = luminosity(m_backgroundColor); 0220 0221 if (lum1 > lum2) { 0222 return (lum1 + 0.05) / (lum2 + 0.05); 0223 } 0224 0225 return (lum2 + 0.05) / (lum1 + 0.05); 0226 } 0227 0228 void Kontrast::random() 0229 { 0230 do { 0231 m_textColor = QColor::fromRgb(QRandomGenerator::global()->generate()); 0232 m_backgroundColor = QColor::fromRgb(QRandomGenerator::global()->generate()); 0233 } while (contrast() < 3.5); 0234 Q_EMIT backgroundColorChanged(); 0235 Q_EMIT textColorChanged(); 0236 Q_EMIT contrastChanged(); 0237 Q_EMIT fontSizeChanged(); 0238 } 0239 0240 void Kontrast::reverse() 0241 { 0242 const QColor temp = m_textColor; 0243 m_textColor = m_backgroundColor; 0244 m_backgroundColor = temp; 0245 Q_EMIT backgroundColorChanged(); 0246 Q_EMIT textColorChanged(); 0247 Q_EMIT contrastChanged(); 0248 Q_EMIT fontSizeChanged(); 0249 } 0250 0251 QColor Kontrast::displayTextColor() const 0252 { 0253 if (contrast() > 3.0) { 0254 return m_textColor; 0255 } 0256 0257 if (luminosity(m_backgroundColor) > 0.5) { 0258 return Qt::black; 0259 } 0260 return Qt::white; 0261 } 0262 0263 QColor Kontrast::pixelAt(const QImage &image, int x, int y) const 0264 { 0265 return image.pixelColor(x, y); 0266 } 0267 0268 void Kontrast::grabColor() 0269 { 0270 #ifdef QT_DBUS_LIB 0271 QDBusMessage message = QDBusMessage::createMethodCall(QLatin1String("org.freedesktop.portal.Desktop"), 0272 QLatin1String("/org/freedesktop/portal/desktop"), 0273 QLatin1String("org.freedesktop.portal.Screenshot"), 0274 QLatin1String("PickColor")); 0275 message << QLatin1String("x11:") << QVariantMap{}; 0276 QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message); 0277 QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingCall); 0278 connect(watcher, &QDBusPendingCallWatcher::finished, [this](QDBusPendingCallWatcher *watcher) { 0279 QDBusPendingReply<QDBusObjectPath> reply = *watcher; 0280 if (reply.isError()) { 0281 qWarning() << "Couldn't get reply"; 0282 qWarning() << "Error: " << reply.error().message(); 0283 } else { 0284 QDBusConnection::sessionBus().connect(QString(), 0285 reply.value().path(), 0286 QLatin1String("org.freedesktop.portal.Request"), 0287 QLatin1String("Response"), 0288 this, 0289 SLOT(gotColorResponse(uint, QVariantMap))); 0290 } 0291 }); 0292 #endif 0293 } 0294 0295 QColor Kontrast::grabbedColor() const 0296 { 0297 return m_grabbedColor; 0298 } 0299 0300 void Kontrast::gotColorResponse(uint response, const QVariantMap &results) 0301 { 0302 #ifdef QT_DBUS_LIB 0303 if (!response) { 0304 if (results.contains(QLatin1String("color"))) { 0305 auto color = qdbus_cast<ColorRGB>(results.value(QLatin1String("color"))); 0306 m_grabbedColor = QColor(color.red * 256, color.green * 256, color.blue * 256); 0307 Q_EMIT grabbedColorChanged(); 0308 } 0309 } else { 0310 qWarning() << "Failed to take screenshot"; 0311 } 0312 #else 0313 Q_UNUSED(response); 0314 Q_UNUSED(results); 0315 #endif 0316 } 0317 0318 Kontrast::ContrastQualities Kontrast::getContrastQualities() const 0319 { 0320 const qreal contrast = Kontrast::contrast(); 0321 0322 if (contrast > 7.0) { 0323 return ContrastQualities{Perfect, Perfect, Perfect}; 0324 } else if (contrast > 4.5) { 0325 return ContrastQualities{Good, Good, Perfect}; 0326 } else if (contrast > 3.0) { 0327 return ContrastQualities{Bad, Bad, Good}; 0328 } else { 0329 return ContrastQualities{Bad, Bad, Bad}; 0330 } 0331 } 0332 0333 QString Kontrast::getFontSizeQualityLabel() 0334 { 0335 const auto currentQualities = getContrastQualities(); 0336 0337 Quality currentQuality; 0338 if (m_fontSize >= 18) { 0339 currentQuality = currentQualities.large; 0340 } else if (m_fontSize > 13) { 0341 currentQuality = currentQualities.medium; 0342 } else { 0343 currentQuality = currentQualities.small; 0344 } 0345 0346 QString fontSizeQualityLabel; 0347 switch (currentQuality) { 0348 case Bad: 0349 fontSizeQualityLabel = i18n("Font size %1px is bad with the current contrast", m_fontSize); 0350 break; 0351 case Good: 0352 fontSizeQualityLabel = i18n("Font size %1px is good with the current contrast", m_fontSize); 0353 break; 0354 case Perfect: 0355 fontSizeQualityLabel = i18n("Font size %1px is perfect with the current contrast", m_fontSize); 0356 break; 0357 } 0358 return fontSizeQualityLabel; 0359 } 0360 0361 #include "moc_kontrast.cpp"