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"