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