1
2#ifndef BASE_COLOR_H
3#define BASE_COLOR_H
4
5#include <base/math.h>
6
7#include <optional>
8#include <type_traits>
9
10/*
11 Title: Color handling
12*/
13/*
14 Function: RgbToHue
15 Determines the hue from RGB values
16*/
17constexpr float RgbToHue(float r, float g, float b)
18{
19 float h_min = minimum(a: r, b: g, c: b);
20 float h_max = maximum(a: r, b: g, c: b);
21
22 float hue = 0.0f;
23 if(h_max != h_min)
24 {
25 float c = h_max - h_min;
26 if(h_max == r)
27 hue = (g - b) / c + (g < b ? 6 : 0);
28 else if(h_max == g)
29 hue = (b - r) / c + 2;
30 else
31 hue = (r - g) / c + 4;
32 }
33
34 return hue / 6.0f;
35}
36
37// Curiously Recurring Template Pattern for type safety
38template<typename DerivedT>
39class color4_base
40{
41public:
42 union
43 {
44 float x, r, h;
45 };
46 union
47 {
48 float y, g, s;
49 };
50 union
51 {
52 float z, b, l, v;
53 };
54 union
55 {
56 float w, a;
57 };
58
59 constexpr color4_base() :
60 x(), y(), z(), a()
61 {
62 }
63
64 constexpr color4_base(float nx, float ny, float nz, float na) :
65 x(nx), y(ny), z(nz), a(na)
66 {
67 }
68
69 constexpr color4_base(float nx, float ny, float nz) :
70 x(nx), y(ny), z(nz), a(1.0f)
71 {
72 }
73
74 constexpr color4_base(unsigned col, bool alpha = false)
75 {
76 a = alpha ? ((col >> 24) & 0xFF) / 255.0f : 1.0f;
77 x = ((col >> 16) & 0xFF) / 255.0f;
78 y = ((col >> 8) & 0xFF) / 255.0f;
79 z = ((col >> 0) & 0xFF) / 255.0f;
80 }
81
82 // Disallow casting between different instantiations of the color4_base template.
83 // The color_cast functions below should be used to convert between colors.
84 template<typename OtherDerivedT>
85 requires(!std::is_same_v<DerivedT, OtherDerivedT>)
86 color4_base(const color4_base<OtherDerivedT> &Other) = delete;
87
88 constexpr float &operator[](int index)
89 {
90 return ((float *)this)[index];
91 }
92
93 constexpr bool operator==(const color4_base &col) const { return x == col.x && y == col.y && z == col.z && a == col.a; }
94 constexpr bool operator!=(const color4_base &col) const { return x != col.x || y != col.y || z != col.z || a != col.a; }
95
96 constexpr unsigned Pack(bool Alpha = true) const
97 {
98 return (Alpha ? ((unsigned)round_to_int(f: a * 255.0f) << 24) : 0) + ((unsigned)round_to_int(f: x * 255.0f) << 16) + ((unsigned)round_to_int(f: y * 255.0f) << 8) + (unsigned)round_to_int(f: z * 255.0f);
99 }
100
101 constexpr unsigned PackAlphaLast(bool Alpha = true) const
102 {
103 if(Alpha)
104 return ((unsigned)round_to_int(f: x * 255.0f) << 24) + ((unsigned)round_to_int(f: y * 255.0f) << 16) + ((unsigned)round_to_int(f: z * 255.0f) << 8) + (unsigned)round_to_int(f: a * 255.0f);
105 return ((unsigned)round_to_int(f: x * 255.0f) << 16) + ((unsigned)round_to_int(f: y * 255.0f) << 8) + (unsigned)round_to_int(f: z * 255.0f);
106 }
107
108 constexpr DerivedT WithAlpha(float alpha) const
109 {
110 DerivedT col(static_cast<const DerivedT &>(*this));
111 col.a = alpha;
112 return col;
113 }
114
115 constexpr DerivedT WithMultipliedAlpha(float alpha) const
116 {
117 DerivedT col(static_cast<const DerivedT &>(*this));
118 col.a *= alpha;
119 return col;
120 }
121
122 template<typename UnpackT>
123 constexpr static UnpackT UnpackAlphaLast(unsigned Color, bool Alpha = true)
124 {
125 UnpackT Result;
126 if(Alpha)
127 {
128 Result.x = ((Color >> 24) & 0xFF) / 255.0f;
129 Result.y = ((Color >> 16) & 0xFF) / 255.0f;
130 Result.z = ((Color >> 8) & 0xFF) / 255.0f;
131 Result.a = ((Color >> 0) & 0xFF) / 255.0f;
132 }
133 else
134 {
135 Result.x = ((Color >> 16) & 0xFF) / 255.0f;
136 Result.y = ((Color >> 8) & 0xFF) / 255.0f;
137 Result.z = ((Color >> 0) & 0xFF) / 255.0f;
138 Result.a = 1.0f;
139 }
140 return Result;
141 }
142};
143
144class ColorHSLA : public color4_base<ColorHSLA>
145{
146public:
147 using color4_base::color4_base;
148 constexpr ColorHSLA() = default;
149
150 constexpr static const float DARKEST_LGT = 0.5f;
151 constexpr static const float DARKEST_LGT7 = 61.0f / 255.0f;
152
153 constexpr ColorHSLA UnclampLighting(float Darkest) const
154 {
155 ColorHSLA col = *this;
156 col.l = Darkest + col.l * (1.0f - Darkest);
157 return col;
158 }
159
160 constexpr unsigned Pack(bool Alpha = true) const
161 {
162 return color4_base::Pack(Alpha);
163 }
164
165 constexpr unsigned Pack(float Darkest, bool Alpha = false) const
166 {
167 ColorHSLA col = *this;
168 col.l = (l - Darkest) / (1 - Darkest);
169 col.l = std::clamp(val: col.l, lo: 0.0f, hi: 1.0f);
170 return col.Pack(Alpha);
171 }
172};
173
174class ColorHSVA : public color4_base<ColorHSVA>
175{
176public:
177 using color4_base::color4_base;
178 constexpr ColorHSVA() = default;
179};
180
181class ColorRGBA : public color4_base<ColorRGBA>
182{
183public:
184 using color4_base::color4_base;
185 constexpr ColorRGBA() = default;
186
187 constexpr ColorRGBA Multiply(const ColorRGBA &Other) const
188 {
189 ColorRGBA Color = *this;
190 Color.r *= Other.r;
191 Color.g *= Other.g;
192 Color.b *= Other.b;
193 Color.a *= Other.a;
194 return Color;
195 }
196
197 template<Numeric T>
198 constexpr ColorRGBA Multiply(const T &Factor) const
199 {
200 ColorRGBA Color = *this;
201 Color.r *= Factor;
202 Color.g *= Factor;
203 Color.b *= Factor;
204 Color.a *= Factor;
205 return Color;
206 }
207};
208
209template<typename T, typename F>
210constexpr T color_cast(const F &) = delete;
211
212template<>
213constexpr ColorHSLA color_cast(const ColorRGBA &rgb)
214{
215 float Min = minimum(a: rgb.r, b: rgb.g, c: rgb.b);
216 float Max = maximum(a: rgb.r, b: rgb.g, c: rgb.b);
217
218 float c = Max - Min;
219 float h = RgbToHue(r: rgb.r, g: rgb.g, b: rgb.b);
220 float l = 0.5f * (Max + Min);
221 float s = (Max != 0.0f && Min != 1.0f) ? (c / (1 - (absolute(a: 2 * l - 1)))) : 0;
222
223 return ColorHSLA(h, s, l, rgb.a);
224}
225
226template<>
227constexpr ColorRGBA color_cast(const ColorHSLA &hsl)
228{
229 float h1 = hsl.h * 6;
230 float c = (1.f - absolute(a: 2 * hsl.l - 1)) * hsl.s;
231 float x = c * (1.f - absolute(a: std::fmod(x: h1, y: 2) - 1.f));
232
233 float r = 0.0f;
234 float g = 0.0f;
235 float b = 0.0f;
236 switch(round_truncate(f: h1))
237 {
238 case 0:
239 r = c;
240 g = x;
241 break;
242 case 1:
243 r = x;
244 g = c;
245 break;
246 case 2:
247 g = c;
248 b = x;
249 break;
250 case 3:
251 g = x;
252 b = c;
253 break;
254 case 4:
255 r = x;
256 b = c;
257 break;
258 case 5:
259 case 6:
260 r = c;
261 b = x;
262 break;
263 }
264
265 float m = hsl.l - (c / 2);
266 return ColorRGBA(r + m, g + m, b + m, hsl.a);
267}
268
269template<>
270constexpr ColorHSLA color_cast(const ColorHSVA &hsv)
271{
272 float l = hsv.v * (1 - hsv.s * 0.5f);
273 return ColorHSLA(hsv.h, (l == 0.0f || l == 1.0f) ? 0 : (hsv.v - l) / minimum(a: l, b: 1 - l), l, hsv.a);
274}
275
276template<>
277constexpr ColorHSVA color_cast(const ColorHSLA &hsl)
278{
279 float v = hsl.l + hsl.s * minimum(a: hsl.l, b: 1 - hsl.l);
280 return ColorHSVA(hsl.h, v == 0.0f ? 0 : 2 - (2 * hsl.l / v), v, hsl.a);
281}
282
283template<>
284constexpr ColorRGBA color_cast(const ColorHSVA &hsv)
285{
286 return color_cast<ColorRGBA>(hsl: color_cast<ColorHSLA>(hsv));
287}
288
289template<>
290constexpr ColorHSVA color_cast(const ColorRGBA &rgb)
291{
292 return color_cast<ColorHSVA>(hsl: color_cast<ColorHSLA>(rgb));
293}
294
295template<typename T>
296constexpr T color_scale(const T &col, float s)
297{
298 return T(col.x * s, col.y * s, col.z * s, col.a * s);
299}
300
301template<typename T>
302constexpr T color_invert(const T &col)
303{
304 return T(1.0f - col.x, 1.0f - col.y, 1.0f - col.z, 1.0f - col.a);
305}
306
307template<typename T>
308std::optional<T> color_parse(const char *pStr);
309
310#endif
311