TachyonUI 0.1.0
High-performance UI for building tools.
Loading...
Searching...
No Matches
gui_color_conversions.h
Go to the documentation of this file.
1#pragma once
2
3#include <array>
4#include <cmath>
5#include <algorithm>
6
8{
11
12 // ---- sRGB <-> Linear ----
13
14 inline float SRGBToLinear(float c)
15 {
16 if (c <= 0.04045f) return c / 12.92f;
17 return std::pow((c + 0.055f) / 1.055f, 2.4f);
18 }
19
20 inline float LinearToSRGB(float c)
21 {
22 if (c <= 0.0031308f) return c * 12.92f;
23 return 1.055f * std::pow(c, 1.0f / 2.4f) - 0.055f;
24 }
25
26 // ---- RGB <-> HSV ----
27
28 inline void RGBtoHSV(float r, float g, float b, float& h, float& s, float& v)
29 {
30 if (!std::isfinite(r)) r = 0.0f;
31 if (!std::isfinite(g)) g = 0.0f;
32 if (!std::isfinite(b)) b = 0.0f;
33
34 r = std::max(r, 0.0f);
35 g = std::max(g, 0.0f);
36 b = std::max(b, 0.0f);
37
38 float maxc = std::max(r, std::max(g, b));
39 float minc = std::min(r, std::min(g, b));
40
41 float d = maxc - minc;
42 v = maxc;
43 s = (maxc == 0.0f) ? 0.0f : d / maxc;
44 if (d == 0.0f) { h = 0.0f; return; }
45
46 // Since maxc is the max of r,g,b we know maxc MUST be
47 // equal to at least one of them so we can do direct
48 // floating point comparisons here.
49 if (maxc == r) h = (g - b) / d + (g < b ? 6.0f : 0.0f);
50 else if (maxc == g) h = (b - r) / d + 2.0f;
51 else h = (r - g) / d + 4.0f;
52 h /= 6.0f;
53 }
54
55 inline std::array<float, 3> HSVtoRGB(float h, float s, float v)
56 {
57 if (!std::isfinite(h)) h = 0.0f;
58 if (!std::isfinite(s)) s = 0.0f;
59 if (!std::isfinite(v)) v = 0.0f;
60
61 h = std::max(h, 0.0f);
62 s = std::max(s, 0.0f);
63 v = std::max(v, 0.0f);
64
65 if (s == 0.0f) return { v, v, v };
66
67 h = h * 6.0f;
68 int i = static_cast<int>(std::floor(h)) % 6;
69 float f = h - std::floor(h);
70 float p = v * (1.0f - s);
71 float q = v * (1.0f - s * f);
72 float t = v * (1.0f - s * (1.0f - f));
73 switch (i)
74 {
75 case 0: return { v, t, p };
76 case 1: return { q, v, p };
77 case 2: return { p, v, t };
78 case 3: return { p, q, v };
79 case 4: return { t, p, v };
80 case 5: default: return { v, p, q };
81 }
82 }
83
84 // ---- RGB <-> HSL ----
85
86 inline void RGBtoHSL(float r, float g, float b, float& h, float& s, float& l)
87 {
88 if (!std::isfinite(r)) r = 0.0f;
89 if (!std::isfinite(g)) g = 0.0f;
90 if (!std::isfinite(b)) b = 0.0f;
91
92 r = std::max(r, 0.0f);
93 g = std::max(g, 0.0f);
94 b = std::max(b, 0.0f);
95
96 float maxc = std::max(r, std::max(g, b));
97 float minc = std::min(r, std::min(g, b));
98
99 l = 0.5f * (maxc + minc);
100 if (maxc == minc) { h = 0.0f; s = 0.0f; return; }
101
102 float d = maxc - minc;
103 s = (l > 0.5f) ? (d / (2.0f - maxc - minc)) : (d / (maxc + minc));
104
105 // Since maxc is the max of r,g,b we know maxc MUST be
106 // equal to at least one of them so we can do direct
107 // floating point comparisons here.
108 if (maxc == r) h = (g - b) / d + (g < b ? 6.0f : 0.0f);
109 else if (maxc == g) h = (b - r) / d + 2.0f;
110 else h = (r - g) / d + 4.0f;
111 h /= 6.0f;
112 }
113
114 inline std::array<float, 3> HSLtoRGB(float h, float s, float l)
115 {
116 if (!std::isfinite(h)) h = 0.0f;
117 if (!std::isfinite(s)) s = 0.0f;
118 if (!std::isfinite(l)) l = 0.0f;
119
120 h = std::max(h, 0.0f);
121 s = std::max(s, 0.0f);
122 l = std::max(l, 0.0f);
123
124 if (s == 0.0f) return { l, l, l };
125
126 auto hue2rgb = [](float p, float q, float t) -> float
127 {
128 if (t < 0.0f) t += 1.0f;
129 if (t > 1.0f) t -= 1.0f;
130 if (t < 1.0f / 6.0f) return p + (q - p) * 6.0f * t;
131 if (t < 1.0f / 2.0f) return q;
132 if (t < 2.0f / 3.0f) return p + (q - p) * (2.0f / 3.0f - t) * 6.0f;
133 return p;
134 };
135 float q = (l < 0.5f) ? (l * (1.0f + s)) : (l + s - l * s);
136 float p = 2.0f * l - q;
137 return { hue2rgb(p, q, h + 1.0f / 3.0f), hue2rgb(p, q, h), hue2rgb(p, q, h - 1.0f / 3.0f) };
138 }
139
140 // ---- RGB <-> CIE Lab ----
141
142 namespace detail
143 {
144 constexpr float kD65WhiteX = 0.95047f;
145 constexpr float kD65WhiteY = 1.00000f;
146 constexpr float kD65WhiteZ = 1.08883f;
147 constexpr float kLabDelta = 6.0f / 29.0f;
148
149 inline std::array<float, 3> RGBtoXYZ(float r, float g, float b)
150 {
151 float R = SRGBToLinear(r), G = SRGBToLinear(g), B = SRGBToLinear(b);
152 return {
153 R * 0.4124564f + G * 0.3575761f + B * 0.1804375f,
154 R * 0.2126729f + G * 0.7151522f + B * 0.0721750f,
155 R * 0.0193339f + G * 0.1191920f + B * 0.9503041f
156 };
157 }
158
159 inline std::array<float, 3> XYZtoRGB(float X, float Y, float Z)
160 {
161 float R = 3.2404542f * X - 1.5371385f * Y - 0.4985314f * Z;
162 float G = -0.9692660f * X + 1.8760108f * Y + 0.0415560f * Z;
163 float B = 0.0556434f * X - 0.2040259f * Y + 1.0572252f * Z;
164 return { std::clamp(R, 0.0f, 1.0f), std::clamp(G, 0.0f, 1.0f), std::clamp(B, 0.0f, 1.0f) };
165 }
166 }
167
168 inline void RGBtoLab(float r, float g, float b, float& L, float& a, float& bOut)
169 {
170 if (!std::isfinite(r)) r = 0.0f;
171 if (!std::isfinite(g)) g = 0.0f;
172 if (!std::isfinite(b)) b = 0.0f;
173
174 r = std::max(r, 0.0f);
175 g = std::max(g, 0.0f);
176 b = std::max(b, 0.0f);
177
178 auto [X, Y, Z] = detail::RGBtoXYZ(r, g, b);
179 auto f = [](float t) {
181 return std::cbrt(t);
182 return t / (3.0f * detail::kLabDelta * detail::kLabDelta) + 4.0f / 29.0f;
183 };
184 float fx = f(X / detail::kD65WhiteX);
185 float fy = f(Y / detail::kD65WhiteY);
186 float fz = f(Z / detail::kD65WhiteZ);
187 L = 116.0f * fy - 16.0f;
188 a = 500.0f * (fx - fy);
189 bOut = 200.0f * (fy - fz);
190 }
191
192 inline std::array<float, 3> LabToRGB(float L, float a, float b)
193 {
194 if (!std::isfinite(L)) L = 0.0f;
195 if (!std::isfinite(a)) a = 0.0f;
196 if (!std::isfinite(b)) b = 0.0f;
197
198 // a and b are signed opponent-color channels (green<->red, blue<->yellow)
199 // and are legitimately negative - only lightness L is clamped to >= 0.
200 L = std::max(L, 0.0f);
201
202 auto finv = [](float t) {
203 if (t > detail::kLabDelta) return t * t * t;
204 return 3.0f * detail::kLabDelta * detail::kLabDelta * (t - 4.0f / 29.0f);
205 };
206 float fy = (L + 16.0f) / 116.0f;
207 float fx = fy + a / 500.0f;
208 float fz = fy - b / 200.0f;
209 auto [Rl, Gl, Bl] = detail::XYZtoRGB(
210 detail::kD65WhiteX * finv(fx),
211 detail::kD65WhiteY * finv(fy),
212 detail::kD65WhiteZ * finv(fz));
213 return { std::clamp(LinearToSRGB(Rl), 0.0f, 1.0f),
214 std::clamp(LinearToSRGB(Gl), 0.0f, 1.0f),
215 std::clamp(LinearToSRGB(Bl), 0.0f, 1.0f) };
216 }
217
218 // ---- RGB <-> Oklab ----
219
220 inline void RGBtoOklab(float r, float g, float b, float& L, float& a, float& bOut)
221 {
222 if (!std::isfinite(r)) r = 0.0f;
223 if (!std::isfinite(g)) g = 0.0f;
224 if (!std::isfinite(b)) b = 0.0f;
225
226 r = std::max(r, 0.0f);
227 g = std::max(g, 0.0f);
228 b = std::max(b, 0.0f);
229
230 float R = SRGBToLinear(r), G = SRGBToLinear(g), B = SRGBToLinear(b);
231 float l = 0.4122214708f * R + 0.5363325363f * G + 0.0514459929f * B;
232 float m = 0.2119034982f * R + 0.6806995451f * G + 0.1073969566f * B;
233 float s = 0.0883024619f * R + 0.2817188376f * G + 0.6299787005f * B;
234 float l_ = std::cbrt(l), m_ = std::cbrt(m), s_ = std::cbrt(s);
235 L = 0.2104542553f * l_ + 0.7936177850f * m_ - 0.0040720468f * s_;
236 a = 1.9779984951f * l_ - 2.4285922050f * m_ + 0.4505937099f * s_;
237 bOut = 0.0259040371f * l_ + 0.7827717662f * m_ - 0.8086757660f * s_;
238 }
239
240 inline std::array<float, 3> OklabToRGB(float L, float a, float b)
241 {
242 if (!std::isfinite(L)) L = 0.0f;
243 if (!std::isfinite(a)) a = 0.0f;
244 if (!std::isfinite(b)) b = 0.0f;
245
246 // a and b are signed opponent-color channels (green<->red, blue<->yellow)
247 // and are legitimately negative - only lightness L is clamped to >= 0.
248 L = std::max(L, 0.0f);
249
250 float l_ = L + 0.3963377774f * a + 0.2158037573f * b;
251 float m_ = L - 0.1055613458f * a - 0.0638541728f * b;
252 float s_ = L - 0.0894841775f * a - 1.2914855480f * b;
253 float l = l_ * l_ * l_, m = m_ * m_ * m_, s = s_ * s_ * s_;
254 float R = +4.0767416621f * l - 3.3077115913f * m + 0.2309699292f * s;
255 float G = -1.2684380046f * l + 2.6097574011f * m - 0.3413193965f * s;
256 float B = -0.0041960863f * l - 0.7034186147f * m + 1.7076147010f * s;
257 return { std::clamp(LinearToSRGB(std::clamp(R, 0.0f, 1.0f)), 0.0f, 1.0f),
258 std::clamp(LinearToSRGB(std::clamp(G, 0.0f, 1.0f)), 0.0f, 1.0f),
259 std::clamp(LinearToSRGB(std::clamp(B, 0.0f, 1.0f)), 0.0f, 1.0f) };
260 }
261
262}
@ X
Definition gui_scroll_management.h:24
@ Y
Definition gui_scroll_management.h:25
std::array< float, 3 > HSLtoRGB(float h, float s, float l)
Definition gui_color_conversions.h:114
std::array< float, 3 > LabToRGB(float L, float a, float b)
Definition gui_color_conversions.h:192
std::array< float, 3 > HSVtoRGB(float h, float s, float v)
Definition gui_color_conversions.h:55
float SRGBToLinear(float c)
Definition gui_color_conversions.h:14
void RGBtoHSL(float r, float g, float b, float &h, float &s, float &l)
Definition gui_color_conversions.h:86
void RGBtoOklab(float r, float g, float b, float &L, float &a, float &bOut)
Definition gui_color_conversions.h:220
void RGBtoLab(float r, float g, float b, float &L, float &a, float &bOut)
Definition gui_color_conversions.h:168
float LinearToSRGB(float c)
Definition gui_color_conversions.h:20
void RGBtoHSV(float r, float g, float b, float &h, float &s, float &v)
Definition gui_color_conversions.h:28
std::array< float, 3 > OklabToRGB(float L, float a, float b)
Definition gui_color_conversions.h:240
Definition gui_color_conversions.h:143
constexpr float kLabDelta
Definition gui_color_conversions.h:147
constexpr float kD65WhiteZ
Definition gui_color_conversions.h:146
std::array< float, 3 > XYZtoRGB(float X, float Y, float Z)
Definition gui_color_conversions.h:159
std::array< float, 3 > RGBtoXYZ(float r, float g, float b)
Definition gui_color_conversions.h:149
constexpr float kD65WhiteX
Definition gui_color_conversions.h:144
constexpr float kD65WhiteY
Definition gui_color_conversions.h:145
Definition gui_color_conversions.h:8