14#include "kirigamiplatform_logging.h"
16ColorUtils::ColorUtils(
QObject *parent)
23 auto luma = [](
const QColor &color) {
24 return (0.299 * color.
red() + 0.587 * color.
green() + 0.114 * color.
blue()) / 255;
32 return (0.299 * color.
red() + 0.587 * color.
green() + 0.114 * color.
blue()) / 255;
37 const auto foregroundAlpha = foreground.
alpha();
38 const auto inverseForegroundAlpha = 0xff - foregroundAlpha;
39 const auto backgroundAlpha = background.
alpha();
41 if (foregroundAlpha == 0x00) {
45 if (backgroundAlpha == 0xff) {
46 return QColor::fromRgb((foregroundAlpha * foreground.
red()) + (inverseForegroundAlpha * background.
red()),
47 (foregroundAlpha * foreground.
green()) + (inverseForegroundAlpha * background.
green()),
48 (foregroundAlpha * foreground.
blue()) + (inverseForegroundAlpha * background.
blue()),
51 const auto inverseBackgroundAlpha = (backgroundAlpha * inverseForegroundAlpha) / 255;
52 const auto finalAlpha = foregroundAlpha + inverseBackgroundAlpha;
53 Q_ASSERT(finalAlpha != 0x00);
54 return QColor::fromRgb((foregroundAlpha * foreground.
red()) + (inverseBackgroundAlpha * background.
red()),
55 (foregroundAlpha * foreground.
green()) + (inverseBackgroundAlpha * background.
green()),
56 (foregroundAlpha * foreground.
blue()) + (inverseBackgroundAlpha * background.
blue()),
63 auto linearlyInterpolateDouble = [](
double one,
double two,
double factor) {
64 return one + (two - one) * factor;
71 auto sourceHue = std::max(one.
hueF() > 0.0 ? one.
hueF() : two.
hueF(), 0.0f);
72 auto targetHue = std::max(two.
hueF() > 0.0 ? two.
hueF() : one.
hueF(), 0.0f);
74 auto hue = std::fmod(linearlyInterpolateDouble(sourceHue, targetHue, balance), 1.0);
75 auto saturation = std::clamp(linearlyInterpolateDouble(one.
saturationF(), two.
saturationF(), balance), 0.0, 1.0);
76 auto value = std::clamp(linearlyInterpolateDouble(one.
valueF(), two.
valueF(), balance), 0.0, 1.0);
77 auto alpha = std::clamp(linearlyInterpolateDouble(one.
alphaF(), two.
alphaF(), balance), 0.0, 1.0);
83struct ParsedAdjustments {
89 double saturation = 0.0;
95ParsedAdjustments 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},
117 {QStringLiteral(
"alpha"), parsed.alpha}};
119 for (
const auto &item : items) {
120 auto val = checkProperty(value, item.first);
122 item.second = val.toDouble();
126 if ((parsed.red || parsed.green || parsed.blue) && (parsed.hue || parsed.saturation || parsed.value)) {
127 qCCritical(KirigamiPlatform) <<
"It is an error to have both RGB and HSV values in an adjustment.";
135 auto adjusts = parseAdjustments(adjustments);
137 if (qBound(-360.0, adjusts.hue, 360.0) != adjusts.hue) {
138 qCCritical(KirigamiPlatform) <<
"Hue is out of bounds";
140 if (qBound(-255.0, adjusts.red, 255.0) != adjusts.red) {
141 qCCritical(KirigamiPlatform) <<
"Red is out of bounds";
143 if (qBound(-255.0, adjusts.green, 255.0) != adjusts.green) {
144 qCCritical(KirigamiPlatform) <<
"Green is out of bounds";
146 if (qBound(-255.0, adjusts.blue, 255.0) != adjusts.blue) {
147 qCCritical(KirigamiPlatform) <<
"Green is out of bounds";
149 if (qBound(-255.0, adjusts.saturation, 255.0) != adjusts.saturation) {
150 qCCritical(KirigamiPlatform) <<
"Saturation is out of bounds";
152 if (qBound(-255.0, adjusts.value, 255.0) != adjusts.value) {
153 qCCritical(KirigamiPlatform) <<
"Value is out of bounds";
155 if (qBound(-255.0, adjusts.alpha, 255.0) != adjusts.alpha) {
156 qCCritical(KirigamiPlatform) <<
"Alpha is out of bounds";
162 copy.setAlpha(qBound(0.0, copy.alpha() + adjusts.alpha, 255.0));
165 if (adjusts.red || adjusts.green || adjusts.blue) {
166 copy.setRed(qBound(0.0, copy.red() + adjusts.red, 255.0));
167 copy.setGreen(qBound(0.0, copy.green() + adjusts.green, 255.0));
168 copy.setBlue(qBound(0.0, copy.blue() + adjusts.blue, 255.0));
169 }
else if (adjusts.hue || adjusts.saturation || adjusts.value) {
170 copy.setHsv(std::fmod(copy.hue() + adjusts.hue, 360.0),
171 qBound(0.0, copy.saturation() + adjusts.saturation, 255.0),
172 qBound(0.0, copy.value() + adjusts.value, 255.0),
181 auto adjusts = parseAdjustments(adjustments);
184 if (qBound(-100.0, adjusts.red, 100.00) != adjusts.red) {
185 qCCritical(KirigamiPlatform) <<
"Red is out of bounds";
187 if (qBound(-100.0, adjusts.green, 100.00) != adjusts.green) {
188 qCCritical(KirigamiPlatform) <<
"Green is out of bounds";
190 if (qBound(-100.0, adjusts.blue, 100.00) != adjusts.blue) {
191 qCCritical(KirigamiPlatform) <<
"Blue is out of bounds";
193 if (qBound(-100.0, adjusts.saturation, 100.00) != adjusts.saturation) {
194 qCCritical(KirigamiPlatform) <<
"Saturation is out of bounds";
196 if (qBound(-100.0, adjusts.value, 100.00) != adjusts.value) {
197 qCCritical(KirigamiPlatform) <<
"Value is out of bounds";
199 if (qBound(-100.0, adjusts.alpha, 100.00) != adjusts.alpha) {
200 qCCritical(KirigamiPlatform) <<
"Alpha is out of bounds";
203 if (adjusts.hue != 0) {
204 qCCritical(KirigamiPlatform) <<
"Hue cannot be scaled";
207 auto shiftToAverage = [](
double current,
double factor) {
208 auto scale = qBound(-100.0, factor, 100.0) / 100;
209 return current + (scale > 0 ? 255 - current : current) * scale;
213 copy.setAlpha(qBound(0.0, shiftToAverage(copy.alpha(), adjusts.alpha), 255.0));
216 if (adjusts.red || adjusts.green || adjusts.blue) {
217 copy.setRed(qBound(0.0, shiftToAverage(copy.red(), adjusts.red), 255.0));
218 copy.setGreen(qBound(0.0, shiftToAverage(copy.green(), adjusts.green), 255.0));
219 copy.setBlue(qBound(0.0, shiftToAverage(copy.blue(), adjusts.blue), 255.0));
221 copy.setHsv(copy.hue(),
222 qBound(0.0, shiftToAverage(copy.saturation(), adjusts.saturation), 255.0),
223 qBound(0.0, shiftToAverage(copy.value(), adjusts.value), 255.0),
232 qreal tintAlpha = tintColor.
alphaF() * alpha;
233 qreal inverseAlpha = 1.0 - tintAlpha;
235 if (qFuzzyCompare(tintAlpha, 1.0)) {
237 }
else if (qFuzzyIsNull(tintAlpha)) {
242 tintColor.
greenF() * tintAlpha + targetColor.
greenF() * inverseAlpha,
243 tintColor.
blueF() * tintAlpha + targetColor.
blueF() * inverseAlpha,
244 tintAlpha + inverseAlpha * targetColor.
alphaF());
247ColorUtils::XYZColor ColorUtils::colorToXYZ(
const QColor &color)
250 qreal r = color.
redF();
252 qreal b = color.
blueF();
254 auto correct = [](qreal &v) {
256 v = std::pow((v + 0.055) / 1.055, 2.4);
267 const qreal x = r * 0.4124 + g * 0.3576 + b * 0.1805;
268 const qreal y = r * 0.2126 + g * 0.7152 + b * 0.0722;
269 const qreal z = r * 0.0193 + g * 0.1192 + b * 0.9505;
271 return XYZColor{x, y, z};
274ColorUtils::LabColor ColorUtils::colorToLab(
const QColor &color)
277 const auto xyz = colorToXYZ(color);
280 qreal x = xyz.x / 0.95047;
281 qreal y = xyz.y / 1.0;
282 qreal z = xyz.z / 1.08883;
284 auto pivot = [](qreal &v) {
286 v = std::pow(v, 1.0 / 3.0);
288 v = (7.787 * v) + (16.0 / 116.0);
297 labColor.l = std::max(0.0, (116 * y) - 16);
298 labColor.a = 500 * (x - y);
299 labColor.b = 200 * (y - z);
306 LabColor labColor = colorToLab(color);
309 return sqrt(pow(labColor.a, 2) + pow(labColor.b, 2));
312qreal ColorUtils::luminance(
const QColor &color)
314 const auto &xyz = colorToXYZ(color);
319#include "moc_colorutils.cpp"
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...
Q_INVOKABLE QColor adjustColor(const QColor &color, const QJSValue &adjustments)
Increases or decreases the properties of color by fixed amounts.
Q_INVOKABLE QColor alphaBlend(const QColor &foreground, const QColor &background)
Returns the result of overlaying the foreground color on the background color.
Q_INVOKABLE QColor scaleColor(const QColor &color, const QJSValue &adjustments)
Smoothly scales colors.
static Q_INVOKABLE qreal chroma(const QColor &color)
Returns the CIELAB chroma of the given color.
Q_INVOKABLE QColor tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha)
Tint a color using a separate alpha value.
Q_INVOKABLE QColor linearInterpolation(const QColor &one, const QColor &two, double balance)
Returns a linearly interpolated color between color one and color two.
Q_INVOKABLE ColorUtils::Brightness brightnessForColor(const QColor &color)
Returns whether a color is bright or dark.
Brightness
Describes the contrast of an item.
@ Light
The item is light and requires a dark foreground color to achieve readable contrast.
@ Dark
The item is dark and requires a light foreground color to achieve readable contrast.
float alphaF() const const
float blueF() const const
QColor fromHsvF(float h, float s, float v, float a)
QColor fromRgbF(float r, float g, float b, float a)
float greenF() const const
float saturationF() const const
float valueF() const const
bool hasProperty(const QString &name) const const
QJSValue property(const QString &name) const const
QVariant fromValue(T &&value)