Perceptual Color

rgbcolor.cpp
1// SPDX-FileCopyrightText: Lukas Sommer <sommerluk@gmail.com>
2// SPDX-License-Identifier: BSD-2-Clause OR MIT
3
4// Own header
5#include "rgbcolor.h"
6
7#include "helperqttypes.h"
8#include <qglobal.h>
9#include <type_traits>
10
11namespace PerceptualColor
12{
13
14// TODO xxx Decide on the commented-out static_assert statements. Ideally
15// get them working!
16
17// static_assert(std::is_trivially_copyable_v<RgbColor>);
18// static_assert(std::is_trivial_v<RgbColor>);
19
20static_assert(std::is_standard_layout_v<RgbColor>);
21
22static_assert(std::is_default_constructible_v<RgbColor>);
23// static_assert(std::is_trivially_default_constructible_v<RgbColor>);
24// static_assert(std::is_nothrow_default_constructible_v<RgbColor>);
25
26static_assert(std::is_copy_constructible_v<RgbColor>);
27// static_assert(std::is_trivially_copy_constructible_v<RgbColor>);
28// static_assert(std::is_nothrow_copy_constructible_v<RgbColor>);
29
30// static_assert(std::is_move_constructible_v<RgbColor>);
31// static_assert(std::is_trivially_move_constructible_v<RgbColor>);
32// static_assert(std::is_nothrow_move_constructible_v<RgbColor>);
33
34RgbColor::RgbColor()
35{
36}
37
38/** @brief Set all member variables.
39 *
40 * @param color The new color as <tt>QColor</tt> object. Might be of any
41 * <tt>QColor::Spec</tt>.
42 * @param hue When empty, the hue is calculated automatically. Otherwise,
43 * this value is used instead. Valid range: [0, 360[
44 *
45 * @post @ref hsl, @ref hsv, @ref hwb, @ref rgb255 and @ref rgbQColor are
46 * set. */
47void RgbColor::fillAll(QColor color, std::optional<double> hue)
48{
49 rgb255 = GenericColor(static_cast<double>(color.redF() * 255), //
50 static_cast<double>(color.greenF() * 255), //
51 static_cast<double>(color.blueF() * 255));
52
53 rgbQColor = color.toRgb();
54
55 // The hue is identical for HSL, HSV and HWB.
56 const double hueDegree = hue.value_or( //
57 qBound(0., rgbQColor.hueF() * 360, 360.));
58
59 // HSL
60 const double hslSaturationPercentage = //
61 qBound(0., static_cast<double>(color.hslSaturationF()) * 100, 100.);
62 const double hslLightnessPercentage = //
63 qBound(0., static_cast<double>(color.lightnessF()) * 100, 100.);
64 hsl = GenericColor(hueDegree, //
65 hslSaturationPercentage, //
66 hslLightnessPercentage);
67
68 // HSV
69 const double hsvSaturationPercentage = //
70 qBound(0.0, static_cast<double>(color.hsvSaturationF()) * 100, 100.0);
71 const double hsvValuePercentage = //
72 qBound<double>(0.0, static_cast<double>(color.valueF()) * 100, 100.0);
73 hsv = GenericColor(hueDegree, //
74 hsvSaturationPercentage, //
75 hsvValuePercentage);
76
77 const double hwbWhitenessPercentage = //
78 qBound(0.0, (1 - color.hsvSaturationF()) * color.valueF() * 100, 100.0);
79 const double hwbBlacknessPercentage = //
80 qBound(0.0, (1 - color.valueF()) * 100, 100.0);
81 hwb = GenericColor(hueDegree, //
82 hwbWhitenessPercentage, //
83 hwbBlacknessPercentage);
84}
85
86/** @brief Static convenience function that returns a @ref RgbColor
87 * constructed from the given color.
88 *
89 * @param color Original color. Valid range: [0, 255]
90 * @param hue If not empty, this value is used instead of the actually
91 * calculated hue value. Valid range: [0, 360[
92 * @returns A @ref RgbColor object representing this color. */
93RgbColor RgbColor::fromRgb255(const GenericColor &color, std::optional<double> hue)
94{
95 RgbColor result;
96 const auto red = static_cast<QColorFloatType>(color.first / 255.0);
97 const auto green = static_cast<QColorFloatType>(color.second / 255.0);
98 const auto blue = static_cast<QColorFloatType>(color.third / 255.0);
99 constexpr auto zero = static_cast<QColorFloatType>(0);
100 constexpr auto one = static_cast<QColorFloatType>(1);
101 const QColor newRgbQColor = QColor::fromRgbF(qBound(zero, red, one), //
102 qBound(zero, green, one), //
103 qBound(zero, blue, one));
104 result.fillAll(newRgbQColor, hue);
105 result.rgb255 = color;
106
107 return result;
108}
109
110/** @brief Static convenience function that returns a @ref RgbColor
111 * constructed from the given color.
112 *
113 * @param color Original color.
114 * @returns A @ref RgbColor object representing this color. */
115RgbColor RgbColor::fromRgbQColor(const QColor &color)
116{
117 RgbColor result;
118 result.fillAll(color, std::optional<double>());
119
120 return result;
121}
122
123/** @brief Static convenience function that returns a @ref RgbColor
124 * constructed from the given color.
125 *
126 * @param color Original color.
127 * @returns A @ref RgbColor object representing this color. */
128RgbColor RgbColor::fromHsl(const GenericColor &color)
129{
130 RgbColor result;
131
132 constexpr auto zero = static_cast<QColorFloatType>(0);
133 constexpr auto one = static_cast<QColorFloatType>(1);
134 const auto hslHue = //
135 qBound(zero, static_cast<QColorFloatType>(color.first / 360.0), one);
136 const auto hslSaturation = //
137 qBound(zero, static_cast<QColorFloatType>(color.second / 100.0), one);
138 const auto hslLightness = //
139 qBound(zero, static_cast<QColorFloatType>(color.third / 100.0), one);
140 const QColor newRgbQColor = //
141 QColor::fromHslF(hslHue, hslSaturation, hslLightness).toRgb();
142 result.fillAll(newRgbQColor, color.first);
143 // Override again with the original value:
144 result.hsl = color;
145 if (result.hsl.third == 0) {
146 // Color is black. So neither changing HSV-saturation or changing
147 // HSL-saturation will change the color itself. To give a better
148 // user experience, we synchronize both values.
149 result.hsv.second = result.hsl.second;
150 }
151
152 return result;
153}
154
155/** @brief Static convenience function that returns a @ref RgbColor
156 * constructed from the given color.
157 *
158 * @param color Original color.
159 * @returns A @ref RgbColor object representing this color. */
160RgbColor RgbColor::fromHsv(const GenericColor &color)
161{
162 RgbColor result;
163 constexpr auto zero = static_cast<QColorFloatType>(0);
164 constexpr auto one = static_cast<QColorFloatType>(1);
165 const auto hsvHue = //
166 qBound(zero, static_cast<QColorFloatType>(color.first / 360.0), one);
167 const auto hsvSaturation = //
168 qBound(zero, static_cast<QColorFloatType>(color.second / 100.0), one);
169 const auto hsvValue = //
170 qBound(zero, static_cast<QColorFloatType>(color.third / 100.0), one);
171 const QColor newRgbQColor = //
172 QColor::fromHsvF(hsvHue, hsvSaturation, hsvValue);
173 result.fillAll(newRgbQColor, color.first);
174 // Override again with the original value:
175 result.hsv = color;
176 if (result.hsv.third == 0) {
177 // Color is black. So neither changing HSV-saturation or changing
178 // HSL-saturation will change the color itself. To give a better
179 // user experience, we synchronize both values.
180 result.hsl.second = result.hsv.second;
181 }
182
183 return result;
184}
185
186/** @brief Static convenience function that returns a @ref RgbColor
187 * constructed from the given color.
188 *
189 * @param color Original color.
190 * @returns A @ref RgbColor object representing this color. */
191RgbColor RgbColor::fromHwb(const GenericColor &color)
192{
193 RgbColor result;
194 GenericColor normalizedHwb = color;
195 const auto whitenessBlacknessSum = //
196 normalizedHwb.second + normalizedHwb.third;
197 if (whitenessBlacknessSum > 100) {
198 normalizedHwb.second *= 100 / whitenessBlacknessSum;
199 normalizedHwb.third *= 100 / whitenessBlacknessSum;
200 }
201
202 const double quotient = (100 - normalizedHwb.third);
203 const auto newHsvSaturation = //
204 (quotient == 0) // This is only the case for pure black.
205 ? 0 // Avoid division by 0 in the formula below. Instead, set
206 // an arbitrary (in-range) value, because the HSV saturation
207 // is meaningless when value/brightness is 0, which is the case
208 // for black.
209 : qBound<double>(0, 100 - normalizedHwb.second / quotient * 100, 100);
210 const auto newHsvValue = qBound<double>(0, 100 - normalizedHwb.third, 100);
211 const GenericColor newHsv = GenericColor(normalizedHwb.first, //
212 newHsvSaturation, //
213 newHsvValue);
214 const QColor newRgbQColor = //
216 static_cast<QColorFloatType>(newHsv.first / 360), //
217 static_cast<QColorFloatType>(newHsv.second / 100), //
218 static_cast<QColorFloatType>(newHsv.third / 100));
219 result.fillAll(newRgbQColor, normalizedHwb.first);
220 // Override again with the original value:
221 result.hsv = newHsv;
222 result.hwb = color; // Intentionally not normalized, but original value.
223
224 return result;
225}
226
227/** @brief Equal operator
228 *
229 * @param other The object to compare with.
230 *
231 * @returns <tt>true</tt> if all data members have exactly the same
232 * coordinates. <tt>false</tt> otherwise. */
233bool RgbColor::operator==(const RgbColor &other) const
234{
235 // Test equality for all data members
236 return (hsl == other.hsl) //
237 && (hsv == other.hsv) //
238 && (hwb == other.hwb) //
239 && (rgb255 == other.rgb255) //
240 && (rgbQColor == other.rgbQColor);
241}
242
243/** @internal
244 *
245 * @brief Adds QDebug() support for data type
246 * @ref PerceptualColor::RgbColor
247 *
248 * @param dbg Existing debug object
249 * @param value Value to stream into the debug object
250 * @returns Debug object with value streamed in */
251QDebug operator<<(QDebug dbg, const PerceptualColor::RgbColor &value)
252{
253 dbg.nospace() //
254 << "RgbColor(\n"
255 << " - hsl: " << value.hsl << "\n"
256 << " - hsv: " << value.hsv << "\n"
257 << " - hwb: " << value.hwb << "\n"
258 << " - rgb: " << value.rgb255 << "\n"
259 << " - rgbQColor: " << value.rgbQColor << "\n"
260 << ")";
261 return dbg.maybeSpace();
262}
263
264static_assert(std::is_standard_layout_v<RgbColor>);
265
266} // namespace PerceptualColor
KGUIADDONS_EXPORT qreal hue(const QColor &)
QDebug operator<<(QDebug dbg, const DcrawInfoContainer &c)
The namespace of this library.
float blueF() const const
QColor fromHslF(float h, float s, float l, float a)
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 hslSaturationF() const const
float hsvSaturationF() const const
float hueF() const const
float lightnessF() const const
float redF() const const
QColor toRgb() const const
float valueF() const const
QDebug & maybeSpace()
QDebug & nospace()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Mon Nov 18 2024 12:18:38 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.