7 #include "colorutils.h"
9 #include "loggingcategory.h"
15 ColorUtils::ColorUtils(
QObject *parent)
22 auto luma = [](
const QColor &color) {
23 return (0.299 * color.
red() + 0.587 * color.
green() + 0.114 * color.
blue()) / 255;
26 return luma(color) > 0.5 ? ColorUtils::Brightness::Light : ColorUtils::Brightness::Dark;
31 return (0.299 * color.
red() + 0.587 * color.
green() + 0.114 * color.
blue()) / 255;
36 const auto foregroundAlpha = foreground.
alpha();
37 const auto inverseForegroundAlpha = 0xff - foregroundAlpha;
38 const auto backgroundAlpha = background.
alpha();
40 if (foregroundAlpha == 0x00) {
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()),
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()),
62 auto scaleAlpha = [](
const QColor &color,
double factor) {
65 auto linearlyInterpolateDouble = [](
double one,
double two,
double factor) {
66 return one + (two - one) * factor;
70 return scaleAlpha(two, balance);
73 return scaleAlpha(one, 1 - balance);
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));
83 struct ParsedAdjustments {
89 double saturation = 0.0;
95 ParsedAdjustments parseAdjustments(
const QJSValue &value)
97 ParsedAdjustments parsed;
99 auto checkProperty = [](
const QJSValue &value,
const QString &property) {
101 auto val = value.
property(property);
102 if (val.isNumber()) {
109 std::vector<std::pair<QString, double &>> items{{QStringLiteral(
"red"), parsed.red},
110 {QStringLiteral(
"green"), parsed.green},
111 {QStringLiteral(
"blue"), parsed.blue},
113 {QStringLiteral(
"hue"), parsed.hue},
114 {QStringLiteral(
"saturation"), parsed.saturation},
115 {QStringLiteral(
"value"), parsed.value},
116 {QStringLiteral(
"lightness"), parsed.value},
118 {QStringLiteral(
"alpha"), parsed.alpha}};
120 for (
const auto &item : items) {
121 auto val = checkProperty(value, item.first);
123 item.second = val.toDouble();
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.";
136 auto adjusts = parseAdjustments(adjustments);
138 if (qBound(-360.0, adjusts.hue, 360.0) != adjusts.hue) {
139 qCCritical(KirigamiLog) <<
"Hue is out of bounds";
141 if (qBound(-255.0, adjusts.red, 255.0) != adjusts.red) {
142 qCCritical(KirigamiLog) <<
"Red is out of bounds";
144 if (qBound(-255.0, adjusts.green, 255.0) != adjusts.green) {
145 qCCritical(KirigamiLog) <<
"Green is out of bounds";
147 if (qBound(-255.0, adjusts.blue, 255.0) != adjusts.blue) {
148 qCCritical(KirigamiLog) <<
"Green is out of bounds";
150 if (qBound(-255.0, adjusts.saturation, 255.0) != adjusts.saturation) {
151 qCCritical(KirigamiLog) <<
"Saturation is out of bounds";
153 if (qBound(-255.0, adjusts.value, 255.0) != adjusts.value) {
154 qCCritical(KirigamiLog) <<
"Value is out of bounds";
156 if (qBound(-255.0, adjusts.alpha, 255.0) != adjusts.alpha) {
157 qCCritical(KirigamiLog) <<
"Alpha is out of bounds";
163 copy.setAlpha(adjusts.alpha);
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,
182 auto adjusts = parseAdjustments(adjustments);
185 if (qBound(-100.0, adjusts.red, 100.00) != adjusts.red) {
186 qCCritical(KirigamiLog) <<
"Red is out of bounds";
188 if (qBound(-100.0, adjusts.green, 100.00) != adjusts.green) {
189 qCCritical(KirigamiLog) <<
"Green is out of bounds";
191 if (qBound(-100.0, adjusts.blue, 100.00) != adjusts.blue) {
192 qCCritical(KirigamiLog) <<
"Blue is out of bounds";
194 if (qBound(-100.0, adjusts.saturation, 100.00) != adjusts.saturation) {
195 qCCritical(KirigamiLog) <<
"Saturation is out of bounds";
197 if (qBound(-100.0, adjusts.value, 100.00) != adjusts.value) {
198 qCCritical(KirigamiLog) <<
"Value is out of bounds";
200 if (qBound(-100.0, adjusts.alpha, 100.00) != adjusts.alpha) {
201 qCCritical(KirigamiLog) <<
"Alpha is out of bounds";
204 if (adjusts.hue != 0) {
205 qCCritical(KirigamiLog) <<
"Hue cannot be scaled";
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;
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));
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));
229 qreal tintAlpha = tintColor.
alphaF() * alpha;
230 qreal inverseAlpha = 1.0 - tintAlpha;
232 if (qFuzzyCompare(tintAlpha, 1.0)) {
234 }
else if (qFuzzyIsNull(tintAlpha)) {
239 tintColor.
greenF() * tintAlpha + targetColor.
greenF() * inverseAlpha,
240 tintColor.
blueF() * tintAlpha + targetColor.
blueF() * inverseAlpha,
241 tintAlpha + inverseAlpha * targetColor.
alphaF());
244 ColorUtils::XYZColor ColorUtils::colorToXYZ(
const QColor &color)
247 qreal r = color.
redF();
249 qreal b = color.
blueF();
251 auto correct = [](qreal &v) {
253 v = std::pow((v + 0.055) / 1.055, 2.4);
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;
268 return XYZColor{x, y, z};
271 ColorUtils::LabColor ColorUtils::colorToLab(
const QColor &color)
274 const auto xyz = colorToXYZ(color);
277 qreal x = xyz.x / 0.95047;
278 qreal y = xyz.y / 1.0;
279 qreal z = xyz.z / 1.08883;
281 auto pivot = [](qreal &v) {
283 v = std::pow(v, 1.0 / 3.0);
285 v = (7.787 * v) + (16.0 / 116.0);
294 labColor.l = std::max(0.0, (116 * y) - 16);
295 labColor.a = 500 * (x - y);
296 labColor.b = 200 * (y - z);
303 LabColor labColor = colorToLab(color);
306 return sqrt(pow(labColor.a, 2) + pow(labColor.b, 2));
309 qreal ColorUtils::luminance(
const QColor &color)
311 const auto &xyz = colorToXYZ(color);