File indexing completed on 2024-05-05 07:55:26

0001 /*  This file is part of the KDE project
0002     SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net>
0003     SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org>
0004     SPDX-FileCopyrightText: 2007 Zack Rusin <zack@kde.org>
0005 
0006     SPDX-License-Identifier: LGPL-2.0-or-later
0007 */
0008 #include "kcolorspaces_p.h"
0009 #include "kguiaddons_colorhelpers_p.h"
0010 #include <kcolorutils.h>
0011 
0012 #include <QColor>
0013 #include <QImage>
0014 #include <QtNumeric> // qIsNaN
0015 
0016 #include <math.h>
0017 
0018 // BEGIN internal helper functions
0019 static inline qreal mixQreal(qreal a, qreal b, qreal bias)
0020 {
0021     return a + (b - a) * bias;
0022 }
0023 // END internal helper functions
0024 
0025 qreal KColorUtils::hue(const QColor &color)
0026 {
0027     return KColorSpaces::KHCY::hue(color);
0028 }
0029 
0030 qreal KColorUtils::chroma(const QColor &color)
0031 {
0032     return KColorSpaces::KHCY::chroma(color);
0033 }
0034 
0035 qreal KColorUtils::luma(const QColor &color)
0036 {
0037     return KColorSpaces::KHCY::luma(color);
0038 }
0039 
0040 void KColorUtils::getHcy(const QColor &color, qreal *h, qreal *c, qreal *y, qreal *a)
0041 {
0042     if (!c || !h || !y) {
0043         return;
0044     }
0045     KColorSpaces::KHCY khcy(color);
0046     *c = khcy.c;
0047     *h = khcy.h + (khcy.h < 0.0 ? 1.0 : 0.0);
0048     *y = khcy.y;
0049     if (a) {
0050         *a = khcy.a;
0051     }
0052 }
0053 
0054 QColor KColorUtils::hcyColor(qreal h, qreal c, qreal y, qreal a)
0055 {
0056     return KColorSpaces::KHCY(h, c, y, a).qColor();
0057 }
0058 
0059 static qreal contrastRatioForLuma(qreal y1, qreal y2)
0060 {
0061     if (y1 > y2) {
0062         return (y1 + 0.05) / (y2 + 0.05);
0063     } else {
0064         return (y2 + 0.05) / (y1 + 0.05);
0065     }
0066 }
0067 
0068 qreal KColorUtils::contrastRatio(const QColor &c1, const QColor &c2)
0069 {
0070     return contrastRatioForLuma(luma(c1), luma(c2));
0071 }
0072 
0073 QColor KColorUtils::lighten(const QColor &color, qreal ky, qreal kc)
0074 {
0075     KColorSpaces::KHCY c(color);
0076     c.y = 1.0 - normalize((1.0 - c.y) * (1.0 - ky));
0077     c.c = 1.0 - normalize((1.0 - c.c) * kc);
0078     return c.qColor();
0079 }
0080 
0081 QColor KColorUtils::darken(const QColor &color, qreal ky, qreal kc)
0082 {
0083     KColorSpaces::KHCY c(color);
0084     c.y = normalize(c.y * (1.0 - ky));
0085     c.c = normalize(c.c * kc);
0086     return c.qColor();
0087 }
0088 
0089 QColor KColorUtils::shade(const QColor &color, qreal ky, qreal kc)
0090 {
0091     KColorSpaces::KHCY c(color);
0092     c.y = normalize(c.y + ky);
0093     c.c = normalize(c.c + kc);
0094     return c.qColor();
0095 }
0096 
0097 static KColorSpaces::KHCY tintHelper(const QColor &base, qreal baseLuma, const QColor &color, qreal amount)
0098 {
0099     KColorSpaces::KHCY result(KColorUtils::mix(base, color, pow(amount, 0.3)));
0100     result.y = mixQreal(baseLuma, result.y, amount);
0101 
0102     return result;
0103 }
0104 
0105 static qreal tintHelperLuma(const QColor &base, qreal baseLuma, const QColor &color, qreal amount)
0106 {
0107     qreal result(KColorUtils::luma(KColorUtils::mix(base, color, pow(amount, 0.3))));
0108     result = mixQreal(baseLuma, result, amount);
0109 
0110     return result;
0111 }
0112 
0113 QColor KColorUtils::tint(const QColor &base, const QColor &color, qreal amount)
0114 {
0115     if (amount <= 0.0) {
0116         return base;
0117     }
0118     if (amount >= 1.0) {
0119         return color;
0120     }
0121     if (qIsNaN(amount)) {
0122         return base;
0123     }
0124 
0125     qreal baseLuma = luma(base); // cache value because luma call is expensive
0126     double ri = contrastRatioForLuma(baseLuma, luma(color));
0127     double rg = 1.0 + ((ri + 1.0) * amount * amount * amount);
0128     double u = 1.0;
0129     double l = 0.0;
0130     double a = 0.5;
0131     for (int i = 12; i; --i) {
0132         a = 0.5 * (l + u);
0133         qreal resultLuma = tintHelperLuma(base, baseLuma, color, a);
0134         double ra = contrastRatioForLuma(baseLuma, resultLuma);
0135         if (ra > rg) {
0136             u = a;
0137         } else {
0138             l = a;
0139         }
0140     }
0141     return tintHelper(base, baseLuma, color, a).qColor();
0142 }
0143 
0144 QColor KColorUtils::mix(const QColor &c1, const QColor &c2, qreal bias)
0145 {
0146     if (bias <= 0.0) {
0147         return c1;
0148     }
0149     if (bias >= 1.0) {
0150         return c2;
0151     }
0152     if (qIsNaN(bias)) {
0153         return c1;
0154     }
0155 
0156     qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias);
0157     if (a <= 0.0) {
0158         return Qt::transparent;
0159     }
0160 
0161     qreal r = qBound(0.0, mixQreal(c1.redF() * c1.alphaF(), c2.redF() * c2.alphaF(), bias), 1.0) / a;
0162     qreal g = qBound(0.0, mixQreal(c1.greenF() * c1.alphaF(), c2.greenF() * c2.alphaF(), bias), 1.0) / a;
0163     qreal b = qBound(0.0, mixQreal(c1.blueF() * c1.alphaF(), c2.blueF() * c2.alphaF(), bias), 1.0) / a;
0164 
0165     return QColor::fromRgbF(r, g, b, a);
0166 }
0167 
0168 QColor KColorUtils::overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp)
0169 {
0170     // This isn't the fastest way, but should be "fast enough".
0171     // It's also the only safe way to use QPainter::CompositionMode
0172     QImage img(1, 1, QImage::Format_ARGB32_Premultiplied);
0173     QPainter p(&img);
0174     QColor start = base;
0175     start.setAlpha(255); // opaque
0176     p.fillRect(0, 0, 1, 1, start);
0177     p.setCompositionMode(comp);
0178     p.fillRect(0, 0, 1, 1, paint);
0179     p.end();
0180     return img.pixel(0, 0);
0181 }