IceShard 1
A personal game engine project, with development focused on 2D/2.5D games.
Loading...
Searching...
No Matches
color_data.hxx
Go to the documentation of this file.
1
3
4#pragma once
6
7namespace ice::detail
8{
9
10 template<ColorFormat Format>
11 struct ColorData;
12
13 template<>
21
22 template<>
24 {
29
30 constexpr auto gammut_clipped() const noexcept -> ColorData<ColorFormat::LinearRGB>;
31
32 constexpr auto to_oklab() const noexcept -> ColorData<ColorFormat::OkLAB>;
33 inline auto to_oklch() const noexcept -> ColorData<ColorFormat::OkLCH>;
34
35 constexpr auto to_srgb() const noexcept -> ColorData<ColorFormat::StandardRGB>;
36 constexpr auto to_u8() const noexcept -> ColorData<ColorFormat::LinearRGBu8>;
37 };
38
39 template<>
41 {
46
47 constexpr auto to_lrgb() const noexcept -> ColorData<ColorFormat::LinearRGB>;
48 };
49
50 template<>
52 {
57
58 constexpr auto gammut_corrected(
60 ice::f32 coefficient = 0.5
61 ) const noexcept -> ColorData<ColorFormat::OkLAB>;
62
63 constexpr auto to_lrgb() const noexcept -> ColorData<ColorFormat::LinearRGB>;
64 inline auto to_oklch() const noexcept -> ColorData<ColorFormat::OkLCH>;
65 };
66
67 template<>
69 {
70 ice::u8 hue128_lightness; // 0 - 100 [7 bits]
71 ice::u8 chroma; // 0 - 0.41 (maximum depends on lightness, 47 is general max for rc2020, 37 for P3, 32 for sRGB)
72 ice::u8 hue; // 0 - 359 (0 == 360) [9 bits]
74
75 constexpr auto to_f32() const noexcept -> ColorData<ColorFormat::OkLCH>;
76 };
77
78 template<>
80 {
81 ice::f32 lightness; // 0 - 100 [7 bits]
82 ice::f32 chroma; // 0 - 0.41 (maximum depends on lightness, 47 is general max for rc2020, 37 for P3, 32 for sRGB)
83 ice::deg32 hue; // 0 - 359 (0 == 360) [9 bits]
85
86 // Helpers
87 constexpr auto with_hue(ice::deg32 hue) const noexcept -> ColorData<ColorFormat::OkLCH>;
88 constexpr auto brightened(ice::f32 step = 0.05f) const noexcept -> ColorData<ColorFormat::OkLCH>;
89 constexpr auto darkened(ice::f32 step = 0.05f) const noexcept -> ColorData<ColorFormat::OkLCH>;
90 constexpr auto saturated(ice::f32 step = 0.02f, ice::f32 max_chroma = 0.32f) const noexcept -> ColorData<ColorFormat::OkLCH>;
91 constexpr auto desaturated(ice::f32 step = 0.02f, ice::f32 max_chroma = 0.32f) const noexcept -> ColorData<ColorFormat::OkLCH>;
92
93 constexpr auto gammut_corrected(
94 ice::ColorSpace color_space = ColorSpace::SRGB,
95 ice::f32 coefficient = 0.5
96 ) const noexcept -> ColorData<ColorFormat::OkLCH>;
97
98 // Conversions
99 constexpr auto to_lrgb() const noexcept -> ColorData<ColorFormat::LinearRGB>;
100 constexpr auto to_oklab() const noexcept -> ColorData<ColorFormat::OkLAB>;
101
102 constexpr auto to_u8() const noexcept -> ColorData<ColorFormat::OkLCHu8>;
103 };
104
105 // LINEAR RGB - BEGIN
106
108 {
109 return {
110 std::clamp(this->red, 0.0f, 1.0f),
111 std::clamp(this->green, 0.0f, 1.0f),
112 std::clamp(this->blue, 0.0f, 1.0f),
113 this->alpha,
114 };
115 }
116
118 {
119 ice::f32 const L = 0.4122214708f * this->red + 0.5363325363f * this->green + 0.0514459929f * this->blue;
120 ice::f32 const M = 0.2119034982f * this->red + 0.6806995451f * this->green + 0.1073969566f * this->blue;
121 ice::f32 const S = 0.0883024619f * this->red + 0.2817188376f * this->green + 0.6299787005f * this->blue;
122
123 ice::f32 const l3r = ice::math::cbrt(L);
124 ice::f32 const m3r = ice::math::cbrt(M);
125 ice::f32 const s3r = ice::math::cbrt(S);
126
127 ice::f32 const oklab_lightness = 0.2104542553f * l3r + 0.7936177850f * m3r - 0.0040720468f * s3r;
128 ice::f32 const oklab_a = 1.9779984951f * l3r - 2.4285922050f * m3r + 0.4505937099f * s3r;
129 ice::f32 const oklab_b = 0.0259040371f * l3r + 0.7827717662f * m3r - 0.8086757660f * s3r;
130
131 return { oklab_lightness, oklab_a, oklab_b, this->alpha };
132 }
133
135 {
136 return to_oklab().to_oklch();
137 }
138
140 {
141 ice::f32 const r = ice::math::clamp(ice::detail::linear_to_srgb(this->red), 0.0, 1.0);
142 ice::f32 const g = ice::math::clamp(ice::detail::linear_to_srgb(this->green), 0.0, 1.0);
143 ice::f32 const b = ice::math::clamp(ice::detail::linear_to_srgb(this->blue), 0.0, 1.0);
144 return { r, g, b, this->alpha };
145 }
146
148 {
150 return {
151 ice::u8(clipped.red * 255.f + 0.5f),
152 ice::u8(clipped.green * 255.f + 0.5f),
153 ice::u8(clipped.blue * 255.f + 0.5f),
154 ice::u8(clipped.alpha * 255.f + 0.5f),
155 };
156 }
157
158 // LINEAR RGB - END
159 //
160 // STANDARD RGB - BEGIN
161
163 {
164 ice::f32 const r = ice::detail::srgb_to_linear(this->red);
165 ice::f32 const g = ice::detail::srgb_to_linear(this->green);
166 ice::f32 const b = ice::detail::srgb_to_linear(this->blue);
167 return { r, g, b, this->alpha };
168 }
169
170 // STANDARD RGB - END
171 //
172 // OKLAB - BEGIN
173
175 ice::ColorSpace color_space,
176 ice::f32 coefficient
177 ) const noexcept -> ColorData<ColorFormat::OkLAB>
178 {
179 // TODO: Other color spaces. SRGB by default for all
180
181 ice::f32 const L = this->lightness;
182 ice::f32 const C = std::max(ice::f32_eps, ice::math::sqrt(this->a * this->a + this->b * this->b));
183 ice::f32 const a_ = this->a / C;
184 ice::f32 const b_ = this->b / C;
185
186 ice::detail::OkLCH_HueCusp const cusp = find_cusp(a_, b_);
187
188 ice::f32 const Ld = L - 0.5f;
189 ice::f32 const e1 = 0.5f + ice::math::abs(Ld) + coefficient * C;
190 ice::f32 const L0 = 0.5f * (1.f + ice::math::sgn(Ld)*(e1 - ice::math::sqrt(e1*e1 - 2.f * ice::math::abs(Ld))));
191
192 ice::f32 const t = ice::detail::find_gamut_intersection(cusp, L, C, L0);
193 ice::f32 const L_clipped = L0 * (1.f - t) + t * L;
194 ice::f32 const C_clipped = t * C;
195 if (C_clipped >= C)
196 {
197 return *this;
198 }
199 return { L_clipped, C_clipped * a_, C_clipped * b_, this->alpha };
200 }
201
203 {
204 // OKLab → LMS
205 ice::f32 const l2 = (this->lightness * 1.0f) + (this->a * 0.3963377774f) + (this->b * 0.2158037573f);
206 ice::f32 const m2 = (this->lightness * 1.0f) + (this->a * -0.1055613458f) + (this->b * -0.0638541728f);
207 ice::f32 const s2 = (this->lightness * 1.0f) + (this->a * -0.0894841775f) + (this->b * -1.291485548f);
208
209 // Cube
210 ice::f32 const l3 = l2 * l2 * l2;
211 ice::f32 const m3 = m2 * m2 * m2;
212 ice::f32 const s3 = s2 * s2 * s2;
213
214 ice::f32 const r_lin = (+4.0767416621f * l3) - (3.3077115913f * m3) + (0.2309699292f * s3);
215 ice::f32 const g_lin = (-1.2684380046f * l3) + (2.6097574011f * m3) - (0.3413193965f * s3);
216 ice::f32 const b_lin = (-0.0041960863f * l3) - (0.7034186147f * m3) + (1.7076147010f * s3);
217
218 // Oklch to Oklab
219 return { r_lin, g_lin, b_lin, this->alpha };
220 }
221
223 {
224 ice::f32 const oklch_lightness = this->lightness;
225 ice::f32 const oklch_c = ice::math::sqrt(this->a * this->a + this->b * this->b);
226 ice::f32 const oklch_ht = ice::math::atan2(this->b, this->a) * (180.0f / 3.14159265358979323846f);
227 ice::f32 const oklch_h = oklch_ht < 0.0f ? oklch_ht + 360.f : oklch_ht;
228 return { oklch_lightness, ice::max(ice::f32_eps, oklch_c), oklch_h, this->alpha };
229 }
230
231 // OKLAB - END
232 //
233 // OKLCH (compressed) - BEGIN
234
236 {
237 ice::f32 const lightness_f32 = ice::f32((hue128_lightness & 0x7f) * 0.01f);
238 ice::f32 const hue_f32 = ice::f32(hue) + ice::f32(ice::u16(hue128_lightness & 0x80) << 1);
239 return {
240 lightness_f32,
241 ice::f32(chroma / 510.f), // We only need to max up to 0.5 (for safety we go above the max of 0.47) so we multiply by 2 x 255
242 hue_f32,
243 ice::f32(alpha / 255.f)
244 };
245 }
246
247 // OKLCH (compressed) - END
248 //
249 // OKLCH - BEGIN
250
252 {
253 ice::u32 const floored = ice::u32(new_hue._value);
254 ice::f32 const reminder = new_hue._value - ice::f32(floored);
255 return { lightness, chroma, ice::f32(floored % 360) + reminder, alpha };
256 }
257
259 {
260 return { std::clamp(lightness + step, 0.f, 1.0f), chroma, hue, alpha };
261 }
262
264 {
265 return brightened(-step);
266 }
267
269 {
270 return { lightness, std::clamp(chroma + step, ice::f32_eps, max_chroma), hue, alpha };
271 }
272
274 {
275 return saturated(-step, max_chroma);
276 }
277
278
280 ice::ColorSpace color_space,
281 ice::f32 coefficient
282 ) const noexcept -> ColorData<ColorFormat::OkLCH>
283 {
284 // TODO: Other color spaces. SRGB by default for all
285
286 ice::f32 const L = this->lightness;
287 ice::f32 const C = std::max(ice::f32_eps, this->chroma);
288
289 ice::detail::OkLCH_HueCusp const ab_cusp = find_cusp_ch(this->chroma, ice::radians(this->hue));
290
291 ice::f32 const Ld = L - 0.5f;
292 ice::f32 const e1 = 0.5f + ice::math::abs(Ld) + coefficient * C;
293 ice::f32 const L0 = 0.5f * (1.f + ice::math::sgn(Ld) * (e1 - ice::math::sqrt(e1 * e1 - 2.f * ice::math::abs(Ld))));
294
295 ice::f32 const t = ice::detail::find_gamut_intersection(ab_cusp, L, C, L0);
296 ice::f32 const L_clipped = L0 * (1.f - t) + t * L;
297 ice::f32 const C_clipped = t * C;
298
299 if (C_clipped >= C)
300 {
301 return *this;
302 }
303 return { L_clipped, C_clipped, this->hue, this->alpha };
304 }
305
307 {
308 return to_oklab().to_lrgb();
309 }
310
312 {
313 ice::f32 const loc_chroma = std::max<ice::f32>(ice::f32_eps, this->chroma);
314 ice::f32 const oklab_lightness = this->lightness;
315 ice::f32 const oklab_a = loc_chroma * ice::math::cos(rad{ this->hue.to_rad32() });
316 ice::f32 const oklab_b = loc_chroma * ice::math::sin(rad{ this->hue.to_rad32() });
317 return { oklab_lightness, oklab_a, oklab_b, this->alpha };
318 }
319
321 {
322 ice::u16 const hue_16 = ice::u16(hue.raw_value()) & 0x01'ff;
323 ice::u8 const lightness_u8 = ice::u8(lightness * 100) | (ice::u8(hue_16 >> 1) & 0x80);
324 return {
325 lightness_u8,
326 ice::u8(chroma * 510.f + 0.5f), // We only need to max up to 0.5 (for safety we go above the max of 0.47) so we multiply by 2 x 255
327 ice::u8(hue_16),
328 ice::u8(alpha * 255.f + 0.5f)
329 };
330 }
331
332 // OKLCH - END
333
334} // namespace ice
Definition hashmap_details.hxx:13
constexpr auto find_cusp(ice::f32 a, ice::f32 b) noexcept -> ice::detail::OkLCH_HueCusp
Finds L_cusp and C_cusp for a given 'a' and 'b' values of OKLAB color.
Definition color_details.hxx:168
constexpr auto find_cusp_ch(ice::f32 chroma, ice::rad hue) noexcept -> ice::detail::OkLCH_HueCusp
Finds L_cusp and C_cusp for a given 'a' and 'b' values of OKLAB color.
Definition color_details.hxx:186
constexpr auto linear_to_srgb(ice::f32 x) noexcept -> ice::f32
Definition color_details.hxx:29
constexpr auto srgb_to_linear(ice::f32 x) noexcept -> ice::f32
Definition color_details.hxx:45
constexpr auto find_gamut_intersection(ice::detail::OkLCH_HueCusp cusp, ice::f32 L1, ice::f32 C1, ice::f32 L0) noexcept -> ice::f32
Definition color_details.hxx:234
rad32 rad
Definition angles.hxx:170
constexpr auto max(arr_t< Size, T > left, arr_t< Size, U > right) noexcept -> arr_t< Size, T >
Definition array_operations.hxx:49
auto atan2(f32 x, f32 y) noexcept -> f32
Definition common.hxx:230
constexpr auto cbrt(f32 val) noexcept -> f32
Definition common.hxx:68
constexpr auto sgn(f32 val) noexcept -> f32
Definition common.hxx:53
constexpr auto sin(rad radians) noexcept -> f32
Definition common.hxx:156
static constexpr ice::f32 f32_eps
Definition constants.hxx:10
constexpr auto cos(rad radians) noexcept -> f32
Definition common.hxx:186
constexpr auto sqrt(f32 val) noexcept -> f32
Definition common.hxx:93
constexpr auto abs(f32 val) noexcept -> f32
Definition common.hxx:58
constexpr auto radians(deg degrees) noexcept -> rad
Definition common.hxx:33
constexpr auto clamp(f32 val, f32 min, f32 max) noexcept -> f32
Definition common.hxx:28
SPDX-License-Identifier: MIT.
Definition array.hxx:12
ColorFormat
Definition color_enums.hxx:18
@ LinearRGBu8
Definition color_enums.hxx:27
@ OkLAB
Definition color_enums.hxx:21
@ OkLCHu8
Definition color_enums.hxx:26
@ StandardRGB
Definition color_enums.hxx:23
@ LinearRGB
Definition color_enums.hxx:22
@ OkLCH
Definition color_enums.hxx:20
ColorSpace
Definition color_enums.hxx:11
@ SRGB
Definition color_enums.hxx:12
std::uint16_t u16
Definition types.hxx:25
std::uint32_t u32
Definition types.hxx:26
float f32
Definition types.hxx:16
std::uint8_t u8
Definition types.hxx:24
constexpr auto gammut_clipped() const noexcept -> ColorData< ColorFormat::LinearRGB >
Definition color_data.hxx:107
constexpr auto to_oklab() const noexcept -> ColorData< ColorFormat::OkLAB >
Definition color_data.hxx:117
constexpr auto to_srgb() const noexcept -> ColorData< ColorFormat::StandardRGB >
Definition color_data.hxx:139
ice::f32 alpha
Definition color_data.hxx:28
ice::f32 blue
Definition color_data.hxx:27
auto to_oklch() const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:134
ice::f32 red
Definition color_data.hxx:25
ice::f32 green
Definition color_data.hxx:26
constexpr auto to_u8() const noexcept -> ColorData< ColorFormat::LinearRGBu8 >
Definition color_data.hxx:147
ice::u8 alpha
Definition color_data.hxx:19
ice::u8 green
Definition color_data.hxx:17
ice::u8 blue
Definition color_data.hxx:18
ice::u8 red
Definition color_data.hxx:16
ice::f32 b
Definition color_data.hxx:55
ice::f32 lightness
Definition color_data.hxx:53
constexpr auto to_lrgb() const noexcept -> ColorData< ColorFormat::LinearRGB >
Definition color_data.hxx:202
auto to_oklch() const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:222
ice::f32 a
Definition color_data.hxx:54
ice::f32 alpha
Definition color_data.hxx:56
constexpr auto gammut_corrected(ice::ColorSpace color_space=ColorSpace::SRGB, ice::f32 coefficient=0.5) const noexcept -> ColorData< ColorFormat::OkLAB >
Definition color_data.hxx:174
constexpr auto saturated(ice::f32 step=0.02f, ice::f32 max_chroma=0.32f) const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:268
constexpr auto darkened(ice::f32 step=0.05f) const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:263
ice::deg32 hue
Definition color_data.hxx:83
ice::f32 chroma
Definition color_data.hxx:82
constexpr auto to_oklab() const noexcept -> ColorData< ColorFormat::OkLAB >
Definition color_data.hxx:311
constexpr auto brightened(ice::f32 step=0.05f) const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:258
constexpr auto gammut_corrected(ice::ColorSpace color_space=ColorSpace::SRGB, ice::f32 coefficient=0.5) const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:279
constexpr auto desaturated(ice::f32 step=0.02f, ice::f32 max_chroma=0.32f) const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:273
constexpr auto to_lrgb() const noexcept -> ColorData< ColorFormat::LinearRGB >
Definition color_data.hxx:306
ice::f32 alpha
Definition color_data.hxx:84
constexpr auto to_u8() const noexcept -> ColorData< ColorFormat::OkLCHu8 >
Definition color_data.hxx:320
constexpr auto with_hue(ice::deg32 hue) const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:251
ice::f32 lightness
Definition color_data.hxx:81
ice::u8 chroma
Definition color_data.hxx:71
constexpr auto to_f32() const noexcept -> ColorData< ColorFormat::OkLCH >
Definition color_data.hxx:235
ice::u8 hue128_lightness
Definition color_data.hxx:70
ice::u8 alpha
Definition color_data.hxx:73
ice::u8 hue
Definition color_data.hxx:72
ice::f32 red
Definition color_data.hxx:42
ice::f32 green
Definition color_data.hxx:43
ice::f32 alpha
Definition color_data.hxx:45
ice::f32 blue
Definition color_data.hxx:44
constexpr auto to_lrgb() const noexcept -> ColorData< ColorFormat::LinearRGB >
Definition color_data.hxx:162
Definition color_data.hxx:11
Holds information where the 'lightness' and 'chroma' are at the highest point of the OkLCH color curv...
Definition color_details.hxx:19
Definition angles.hxx:46
constexpr auto to_rad32(this deg32 self) noexcept -> rad32
Definition angles.hxx:108