1 | /* This file is part of the KDE project |
2 | SPDX-FileCopyrightText: 2007 Matthew Woehlke <mw_triad@users.sourceforge.net> |
3 | SPDX-FileCopyrightText: 2007 Thomas Zander <zander@kde.org> |
4 | SPDX-FileCopyrightText: 2007 Zack Rusin <zack@kde.org> |
5 | |
6 | SPDX-License-Identifier: LGPL-2.0-or-later |
7 | */ |
8 | #include "kcolorspaces_p.h" |
9 | #include "kguiaddons_colorhelpers_p.h" |
10 | #include <kcolorutils.h> |
11 | |
12 | #include <QColor> |
13 | #include <QImage> |
14 | #include <QtNumeric> // qIsNaN |
15 | |
16 | #include <math.h> |
17 | |
18 | // BEGIN internal helper functions |
19 | static inline qreal mixQreal(qreal a, qreal b, qreal bias) |
20 | { |
21 | return a + (b - a) * bias; |
22 | } |
23 | // END internal helper functions |
24 | |
25 | qreal KColorUtils::hue(const QColor &color) |
26 | { |
27 | return KColorSpaces::KHCY::hue(color); |
28 | } |
29 | |
30 | qreal KColorUtils::chroma(const QColor &color) |
31 | { |
32 | return KColorSpaces::KHCY::chroma(color); |
33 | } |
34 | |
35 | qreal KColorUtils::luma(const QColor &color) |
36 | { |
37 | return KColorSpaces::KHCY::luma(color); |
38 | } |
39 | |
40 | void KColorUtils::getHcy(const QColor &color, qreal *h, qreal *c, qreal *y, qreal *a) |
41 | { |
42 | if (!c || !h || !y) { |
43 | return; |
44 | } |
45 | KColorSpaces::KHCY khcy(color); |
46 | *c = khcy.c; |
47 | *h = khcy.h + (khcy.h < 0.0 ? 1.0 : 0.0); |
48 | *y = khcy.y; |
49 | if (a) { |
50 | *a = khcy.a; |
51 | } |
52 | } |
53 | |
54 | QColor KColorUtils::hcyColor(qreal h, qreal c, qreal y, qreal a) |
55 | { |
56 | return KColorSpaces::KHCY(h, c, y, a).qColor(); |
57 | } |
58 | |
59 | static qreal contrastRatioForLuma(qreal y1, qreal y2) |
60 | { |
61 | if (y1 > y2) { |
62 | return (y1 + 0.05) / (y2 + 0.05); |
63 | } else { |
64 | return (y2 + 0.05) / (y1 + 0.05); |
65 | } |
66 | } |
67 | |
68 | qreal KColorUtils::contrastRatio(const QColor &c1, const QColor &c2) |
69 | { |
70 | return contrastRatioForLuma(y1: luma(color: c1), y2: luma(color: c2)); |
71 | } |
72 | |
73 | QColor KColorUtils::lighten(const QColor &color, qreal ky, qreal kc) |
74 | { |
75 | KColorSpaces::KHCY c(color); |
76 | c.y = 1.0 - normalize(a: (1.0 - c.y) * (1.0 - ky)); |
77 | c.c = 1.0 - normalize(a: (1.0 - c.c) * kc); |
78 | return c.qColor(); |
79 | } |
80 | |
81 | QColor KColorUtils::darken(const QColor &color, qreal ky, qreal kc) |
82 | { |
83 | KColorSpaces::KHCY c(color); |
84 | c.y = normalize(a: c.y * (1.0 - ky)); |
85 | c.c = normalize(a: c.c * kc); |
86 | return c.qColor(); |
87 | } |
88 | |
89 | QColor KColorUtils::shade(const QColor &color, qreal ky, qreal kc) |
90 | { |
91 | KColorSpaces::KHCY c(color); |
92 | c.y = normalize(a: c.y + ky); |
93 | c.c = normalize(a: c.c + kc); |
94 | return c.qColor(); |
95 | } |
96 | |
97 | static KColorSpaces::KHCY tintHelper(const QColor &base, qreal baseLuma, const QColor &color, qreal amount) |
98 | { |
99 | KColorSpaces::KHCY result(KColorUtils::mix(c1: base, c2: color, bias: pow(x: amount, y: 0.3))); |
100 | result.y = mixQreal(a: baseLuma, b: result.y, bias: amount); |
101 | |
102 | return result; |
103 | } |
104 | |
105 | static qreal tintHelperLuma(const QColor &base, qreal baseLuma, const QColor &color, qreal amount) |
106 | { |
107 | qreal result(KColorUtils::luma(color: KColorUtils::mix(c1: base, c2: color, bias: pow(x: amount, y: 0.3)))); |
108 | result = mixQreal(a: baseLuma, b: result, bias: amount); |
109 | |
110 | return result; |
111 | } |
112 | |
113 | QColor KColorUtils::tint(const QColor &base, const QColor &color, qreal amount) |
114 | { |
115 | if (amount <= 0.0) { |
116 | return base; |
117 | } |
118 | if (amount >= 1.0) { |
119 | return color; |
120 | } |
121 | if (qIsNaN(d: amount)) { |
122 | return base; |
123 | } |
124 | |
125 | qreal baseLuma = luma(color: base); // cache value because luma call is expensive |
126 | double ri = contrastRatioForLuma(y1: baseLuma, y2: luma(color)); |
127 | double rg = 1.0 + ((ri + 1.0) * amount * amount * amount); |
128 | double u = 1.0; |
129 | double l = 0.0; |
130 | double a = 0.5; |
131 | for (int i = 12; i; --i) { |
132 | a = 0.5 * (l + u); |
133 | qreal resultLuma = tintHelperLuma(base, baseLuma, color, amount: a); |
134 | double ra = contrastRatioForLuma(y1: baseLuma, y2: resultLuma); |
135 | if (ra > rg) { |
136 | u = a; |
137 | } else { |
138 | l = a; |
139 | } |
140 | } |
141 | return tintHelper(base, baseLuma, color, amount: a).qColor(); |
142 | } |
143 | |
144 | QColor KColorUtils::mix(const QColor &c1, const QColor &c2, qreal bias) |
145 | { |
146 | if (bias <= 0.0) { |
147 | return c1; |
148 | } |
149 | if (bias >= 1.0) { |
150 | return c2; |
151 | } |
152 | if (qIsNaN(d: bias)) { |
153 | return c1; |
154 | } |
155 | |
156 | qreal a = mixQreal(a: c1.alphaF(), b: c2.alphaF(), bias); |
157 | if (a <= 0.0) { |
158 | return Qt::transparent; |
159 | } |
160 | |
161 | qreal r = qBound(min: 0.0, val: mixQreal(a: c1.redF() * c1.alphaF(), b: c2.redF() * c2.alphaF(), bias), max: 1.0) / a; |
162 | qreal g = qBound(min: 0.0, val: mixQreal(a: c1.greenF() * c1.alphaF(), b: c2.greenF() * c2.alphaF(), bias), max: 1.0) / a; |
163 | qreal b = qBound(min: 0.0, val: mixQreal(a: c1.blueF() * c1.alphaF(), b: c2.blueF() * c2.alphaF(), bias), max: 1.0) / a; |
164 | |
165 | return QColor::fromRgbF(r, g, b, a); |
166 | } |
167 | |
168 | QColor KColorUtils::overlayColors(const QColor &base, const QColor &paint, QPainter::CompositionMode comp) |
169 | { |
170 | // This isn't the fastest way, but should be "fast enough". |
171 | // It's also the only safe way to use QPainter::CompositionMode |
172 | QImage img(1, 1, QImage::Format_ARGB32_Premultiplied); |
173 | QPainter p(&img); |
174 | QColor start = base; |
175 | start.setAlpha(255); // opaque |
176 | p.fillRect(x: 0, y: 0, w: 1, h: 1, b: start); |
177 | p.setCompositionMode(comp); |
178 | p.fillRect(x: 0, y: 0, w: 1, h: 1, b: paint); |
179 | p.end(); |
180 | return img.pixel(x: 0, y: 0); |
181 | } |
182 | |