File indexing completed on 2024-04-21 14:56:36

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 QColor 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.qColor();
0103 }
0104 
0105 QColor KColorUtils::tint(const QColor &base, const QColor &color, qreal amount)
0106 {
0107     if (amount <= 0.0) {
0108         return base;
0109     }
0110     if (amount >= 1.0) {
0111         return color;
0112     }
0113     if (qIsNaN(amount)) {
0114         return base;
0115     }
0116 
0117     qreal baseLuma = luma(base); // cache value because luma call is expensive
0118     double ri = contrastRatioForLuma(baseLuma, luma(color));
0119     double rg = 1.0 + ((ri + 1.0) * amount * amount * amount);
0120     double u = 1.0;
0121     double l = 0.0;
0122     QColor result;
0123     for (int i = 12; i; --i) {
0124         double a = 0.5 * (l + u);
0125         result = tintHelper(base, baseLuma, color, a);
0126         double ra = contrastRatioForLuma(baseLuma, luma(result));
0127         if (ra > rg) {
0128             u = a;
0129         } else {
0130             l = a;
0131         }
0132     }
0133     return result;
0134 }
0135 
0136 QColor KColorUtils::mix(const QColor &c1, const QColor &c2, qreal bias)
0137 {
0138     if (bias <= 0.0) {
0139         return c1;
0140     }
0141     if (bias >= 1.0) {
0142         return c2;
0143     }
0144     if (qIsNaN(bias)) {
0145         return c1;
0146     }
0147 
0148     qreal a = mixQreal(c1.alphaF(), c2.alphaF(), bias);
0149     if (a <= 0.0) {
0150         return Qt::transparent;
0151     }
0152 
0153     qreal r = qBound(0.0, mixQreal(c1.redF() * c1.alphaF(), c2.redF() * c2.alphaF(), bias), 1.0) / a;
0154     qreal g = qBound(0.0, mixQreal(c1.greenF() * c1.alphaF(), c2.greenF() * c2.alphaF(), bias), 1.0) / a;
0155     qreal b = qBound(0.0, mixQreal(c1.blueF() * c1.alphaF(), c2.blueF() * c2.alphaF(), bias), 1.0) / a;
0156 
0157     return QColor::fromRgbF(r, g, b, a);
0158 }
0159 
0160 QColor KColorUtils::overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp)
0161 {
0162     // This isn't the fastest way, but should be "fast enough".
0163     // It's also the only safe way to use QPainter::CompositionMode
0164     QImage img(1, 1, QImage::Format_ARGB32_Premultiplied);
0165     QPainter p(&img);
0166     QColor start = base;
0167     start.setAlpha(255); // opaque
0168     p.fillRect(0, 0, 1, 1, start);
0169     p.setCompositionMode(comp);
0170     p.fillRect(0, 0, 1, 1, paint);
0171     p.end();
0172     return img.pixel(0, 0);
0173 }