Kirigami2

colorutils.cpp
1 /*
2  * SPDX-FileCopyrightText: 2020 Carson Black <[email protected]>
3  *
4  * SPDX-License-Identifier: LGPL-2.0-or-later
5  */
6 
7 #include "colorutils.h"
8 
9 #include "loggingcategory.h"
10 #include <QIcon>
11 #include <QtMath>
12 #include <cmath>
13 #include <map>
14 
15 ColorUtils::ColorUtils(QObject *parent)
16  : QObject(parent)
17 {
18 }
19 
21 {
22  auto luma = [](const QColor &color) {
23  return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;
24  };
25 
26  return luma(color) > 0.5 ? ColorUtils::Brightness::Light : ColorUtils::Brightness::Dark;
27 }
28 
29 qreal ColorUtils::grayForColor(const QColor &color)
30 {
31  return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255;
32 }
33 
34 QColor ColorUtils::alphaBlend(const QColor &foreground, const QColor &background)
35 {
36  const auto foregroundAlpha = foreground.alpha();
37  const auto inverseForegroundAlpha = 0xff - foregroundAlpha;
38  const auto backgroundAlpha = background.alpha();
39 
40  if (foregroundAlpha == 0x00) {
41  return background;
42  }
43 
44  if (backgroundAlpha == 0xff) {
45  return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseForegroundAlpha * background.red()),
46  (foregroundAlpha * foreground.green()) + (inverseForegroundAlpha * background.green()),
47  (foregroundAlpha * foreground.blue()) + (inverseForegroundAlpha * background.blue()),
48  0xff);
49  } else {
50  const auto inverseBackgroundAlpha = (backgroundAlpha * inverseForegroundAlpha) / 255;
51  const auto finalAlpha = foregroundAlpha + inverseBackgroundAlpha;
52  Q_ASSERT(finalAlpha != 0x00);
53  return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseBackgroundAlpha * background.red()),
54  (foregroundAlpha * foreground.green()) + (inverseBackgroundAlpha * background.green()),
55  (foregroundAlpha * foreground.blue()) + (inverseBackgroundAlpha * background.blue()),
56  finalAlpha);
57  }
58 }
59 
60 QColor ColorUtils::linearInterpolation(const QColor &one, const QColor &two, double balance)
61 {
62  auto scaleAlpha = [](const QColor &color, double factor) {
63  return QColor::fromRgb(color.red(), color.green(), color.blue(), color.alpha() * factor);
64  };
65  auto linearlyInterpolateDouble = [](double one, double two, double factor) {
66  return one + (two - one) * factor;
67  };
68 
69  if (one == Qt::transparent) {
70  return scaleAlpha(two, balance);
71  }
72  if (two == Qt::transparent) {
73  return scaleAlpha(one, 1 - balance);
74  }
75 
76  return QColor::fromHsv(std::fmod(linearlyInterpolateDouble(one.hue(), two.hue(), balance), 360.0),
77  qBound(0.0, linearlyInterpolateDouble(one.saturation(), two.saturation(), balance), 255.0),
78  qBound(0.0, linearlyInterpolateDouble(one.value(), two.value(), balance), 255.0),
79  qBound(0.0, linearlyInterpolateDouble(one.alpha(), two.alpha(), balance), 255.0));
80 }
81 
82 // Some private things for the adjust, change, and scale properties
83 struct ParsedAdjustments {
84  double red = 0.0;
85  double green = 0.0;
86  double blue = 0.0;
87 
88  double hue = 0.0;
89  double saturation = 0.0;
90  double value = 0.0;
91 
92  double alpha = 0.0;
93 };
94 
95 ParsedAdjustments parseAdjustments(const QJSValue &value)
96 {
97  ParsedAdjustments parsed;
98 
99  auto checkProperty = [](const QJSValue &value, const QString &property) {
100  if (value.hasProperty(property)) {
101  auto val = value.property(property);
102  if (val.isNumber()) {
103  return QVariant::fromValue(val.toNumber());
104  }
105  }
106  return QVariant();
107  };
108 
109  std::vector<std::pair<QString, double &>> items{{QStringLiteral("red"), parsed.red},
110  {QStringLiteral("green"), parsed.green},
111  {QStringLiteral("blue"), parsed.blue},
112  //
113  {QStringLiteral("hue"), parsed.hue},
114  {QStringLiteral("saturation"), parsed.saturation},
115  {QStringLiteral("value"), parsed.value},
116  {QStringLiteral("lightness"), parsed.value},
117  //
118  {QStringLiteral("alpha"), parsed.alpha}};
119 
120  for (const auto &item : items) {
121  auto val = checkProperty(value, item.first);
122  if (val.isValid()) {
123  item.second = val.toDouble();
124  }
125  }
126 
127  if ((parsed.red || parsed.green || parsed.blue) && (parsed.hue || parsed.saturation || parsed.value)) {
128  qCCritical(KirigamiLog) << "It is an error to have both RGB and HSL values in an adjustment.";
129  }
130 
131  return parsed;
132 }
133 
134 QColor ColorUtils::adjustColor(const QColor &color, const QJSValue &adjustments)
135 {
136  auto adjusts = parseAdjustments(adjustments);
137 
138  if (qBound(-360.0, adjusts.hue, 360.0) != adjusts.hue) {
139  qCCritical(KirigamiLog) << "Hue is out of bounds";
140  }
141  if (qBound(-255.0, adjusts.red, 255.0) != adjusts.red) {
142  qCCritical(KirigamiLog) << "Red is out of bounds";
143  }
144  if (qBound(-255.0, adjusts.green, 255.0) != adjusts.green) {
145  qCCritical(KirigamiLog) << "Green is out of bounds";
146  }
147  if (qBound(-255.0, adjusts.blue, 255.0) != adjusts.blue) {
148  qCCritical(KirigamiLog) << "Green is out of bounds";
149  }
150  if (qBound(-255.0, adjusts.saturation, 255.0) != adjusts.saturation) {
151  qCCritical(KirigamiLog) << "Saturation is out of bounds";
152  }
153  if (qBound(-255.0, adjusts.value, 255.0) != adjusts.value) {
154  qCCritical(KirigamiLog) << "Value is out of bounds";
155  }
156  if (qBound(-255.0, adjusts.alpha, 255.0) != adjusts.alpha) {
157  qCCritical(KirigamiLog) << "Alpha is out of bounds";
158  }
159 
160  auto copy = color;
161 
162  if (adjusts.alpha) {
163  copy.setAlpha(adjusts.alpha);
164  }
165 
166  if (adjusts.red || adjusts.green || adjusts.blue) {
167  copy.setRed(copy.red() + adjusts.red);
168  copy.setGreen(copy.green() + adjusts.green);
169  copy.setBlue(copy.blue() + adjusts.blue);
170  } else if (adjusts.hue || adjusts.saturation || adjusts.value) {
171  copy.setHsl(std::fmod(copy.hue() + adjusts.hue, 360.0), //
172  copy.saturation() + adjusts.saturation, //
173  copy.value() + adjusts.value,
174  copy.alpha());
175  }
176 
177  return copy;
178 }
179 
180 QColor ColorUtils::scaleColor(const QColor &color, const QJSValue &adjustments)
181 {
182  auto adjusts = parseAdjustments(adjustments);
183  auto copy = color;
184 
185  if (qBound(-100.0, adjusts.red, 100.00) != adjusts.red) {
186  qCCritical(KirigamiLog) << "Red is out of bounds";
187  }
188  if (qBound(-100.0, adjusts.green, 100.00) != adjusts.green) {
189  qCCritical(KirigamiLog) << "Green is out of bounds";
190  }
191  if (qBound(-100.0, adjusts.blue, 100.00) != adjusts.blue) {
192  qCCritical(KirigamiLog) << "Blue is out of bounds";
193  }
194  if (qBound(-100.0, adjusts.saturation, 100.00) != adjusts.saturation) {
195  qCCritical(KirigamiLog) << "Saturation is out of bounds";
196  }
197  if (qBound(-100.0, adjusts.value, 100.00) != adjusts.value) {
198  qCCritical(KirigamiLog) << "Value is out of bounds";
199  }
200  if (qBound(-100.0, adjusts.alpha, 100.00) != adjusts.alpha) {
201  qCCritical(KirigamiLog) << "Alpha is out of bounds";
202  }
203 
204  if (adjusts.hue != 0) {
205  qCCritical(KirigamiLog) << "Hue cannot be scaled";
206  }
207 
208  auto shiftToAverage = [](double current, double factor) {
209  auto scale = qBound(-100.0, factor, 100.0) / 100;
210  return current + (scale > 0 ? 255 - current : current) * scale;
211  };
212 
213  if (adjusts.red || adjusts.green || adjusts.blue) {
214  copy.setRed(qBound(0.0, shiftToAverage(copy.red(), adjusts.red), 255.0));
215  copy.setGreen(qBound(0.0, shiftToAverage(copy.green(), adjusts.green), 255.0));
216  copy.setBlue(qBound(0.0, shiftToAverage(copy.blue(), adjusts.blue), 255.0));
217  } else {
218  copy.setHsl(copy.hue(),
219  qBound(0.0, shiftToAverage(copy.saturation(), adjusts.saturation), 255.0),
220  qBound(0.0, shiftToAverage(copy.value(), adjusts.value), 255.0),
221  qBound(0.0, shiftToAverage(copy.alpha(), adjusts.alpha), 255.0));
222  }
223 
224  return copy;
225 }
226 
227 QColor ColorUtils::tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha)
228 {
229  qreal tintAlpha = tintColor.alphaF() * alpha;
230  qreal inverseAlpha = 1.0 - tintAlpha;
231 
232  if (qFuzzyCompare(tintAlpha, 1.0)) {
233  return tintColor;
234  } else if (qFuzzyIsNull(tintAlpha)) {
235  return targetColor;
236  }
237 
238  return QColor::fromRgbF(tintColor.redF() * tintAlpha + targetColor.redF() * inverseAlpha,
239  tintColor.greenF() * tintAlpha + targetColor.greenF() * inverseAlpha,
240  tintColor.blueF() * tintAlpha + targetColor.blueF() * inverseAlpha,
241  tintAlpha + inverseAlpha * targetColor.alphaF());
242 }
243 
244 ColorUtils::XYZColor ColorUtils::colorToXYZ(const QColor &color)
245 {
246  // http://wiki.nuaj.net/index.php/Color_Transforms#RGB_.E2.86.92_XYZ
247  qreal r = color.redF();
248  qreal g = color.greenF();
249  qreal b = color.blueF();
250  // Apply gamma correction (i.e. conversion to linear-space)
251  auto correct = [](qreal &v) {
252  if (v > 0.04045) {
253  v = std::pow((v + 0.055) / 1.055, 2.4);
254  } else {
255  v = v / 12.92;
256  }
257  };
258 
259  correct(r);
260  correct(g);
261  correct(b);
262 
263  // Observer. = 2°, Illuminant = D65
264  const qreal x = r * 0.4124 + g * 0.3576 + b * 0.1805;
265  const qreal y = r * 0.2126 + g * 0.7152 + b * 0.0722;
266  const qreal z = r * 0.0193 + g * 0.1192 + b * 0.9505;
267 
268  return XYZColor{x, y, z};
269 }
270 
271 ColorUtils::LabColor ColorUtils::colorToLab(const QColor &color)
272 {
273  // First: convert to XYZ
274  const auto xyz = colorToXYZ(color);
275 
276  // Second: convert from XYZ to L*a*b
277  qreal x = xyz.x / 0.95047; // Observer= 2°, Illuminant= D65
278  qreal y = xyz.y / 1.0;
279  qreal z = xyz.z / 1.08883;
280 
281  auto pivot = [](qreal &v) {
282  if (v > 0.008856) {
283  v = std::pow(v, 1.0 / 3.0);
284  } else {
285  v = (7.787 * v) + (16.0 / 116.0);
286  }
287  };
288 
289  pivot(x);
290  pivot(y);
291  pivot(z);
292 
293  LabColor labColor;
294  labColor.l = std::max(0.0, (116 * y) - 16);
295  labColor.a = 500 * (x - y);
296  labColor.b = 200 * (y - z);
297 
298  return labColor;
299 }
300 
301 qreal ColorUtils::chroma(const QColor &color)
302 {
303  LabColor labColor = colorToLab(color);
304 
305  // Chroma is hypotenuse of a and b
306  return sqrt(pow(labColor.a, 2) + pow(labColor.b, 2));
307 }
308 
309 qreal ColorUtils::luminance(const QColor &color)
310 {
311  const auto &xyz = colorToXYZ(color);
312  // Luminance is equal to Y
313  return xyz.y;
314 }
qreal greenF() const const
Q_INVOKABLE qreal grayForColor(const QColor &color)
Same Algorithm as brightnessForColor but returns a 0 to 1 value for an estimate of the equivalent gra...
Definition: colorutils.cpp:29
qreal redF() const const
QVariant fromValue(const T &value)
qreal blueF() const const
int red() const const
bool hasProperty(const QString &name) const const
Q_INVOKABLE ColorUtils::Brightness brightnessForColor(const QColor &color)
Returns whether a color is bright or dark.
Definition: colorutils.cpp:20
QColor fromRgbF(qreal r, qreal g, qreal b, qreal a)
Q_INVOKABLE QColor alphaBlend(const QColor &foreground, const QColor &background)
Returns the result of overlaying the foreground color on the background color.
Definition: colorutils.cpp:34
QColor fromHsv(int h, int s, int v, int a)
int alpha() const const
int green() const const
Q_INVOKABLE QColor adjustColor(const QColor &color, const QJSValue &adjustments)
Increases or decreases the properties of color by fixed amounts.
Definition: colorutils.cpp:134
Q_INVOKABLE QColor scaleColor(const QColor &color, const QJSValue &adjustments)
Smoothly scales colors.
Definition: colorutils.cpp:180
QColor fromRgb(QRgb rgb)
qreal alphaF() const const
static Q_INVOKABLE qreal chroma(const QColor &color)
Returns the CIELAB chroma of the given color.
Definition: colorutils.cpp:301
Q_INVOKABLE QColor tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha)
Tint a color using a separate alpha value.
Definition: colorutils.cpp:227
int blue() const const
Q_INVOKABLE QColor linearInterpolation(const QColor &one, const QColor &two, double balance)
Returns a linearly interpolated color between color one and color two.
Definition: colorutils.cpp:60
QJSValue property(const QString &name) const const
Brightness
Describes the contrast of an item.
Definition: colorutils.h:25
transparent
This file is part of the KDE documentation.
Documentation copyright © 1996-2023 The KDE developers.
Generated on Sun Jan 29 2023 04:11:03 by doxygen 1.8.17 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.