1use num_traits::NumCast;
5use wide::f32x4;
6
7macro_rules! gen_channel_accessors {
8 ($rgba:ident, $r:ident, $g:ident, $b:ident, $a:ident) => {
9 pub fn as_array(&self) -> &[f32; 4] {
10 self.$rgba.as_array_ref()
11 }
12
13 pub const fn new($r: f32, $g: f32, $b: f32, $a: f32) -> Self {
14 Self {
15 $rgba: f32x4::new([$r, $g, $b, $a]),
16 }
17 }
18
19 pub fn $r(&self) -> f32 {
20 self.as_array()[0]
21 }
22
23 pub fn $g(&self) -> f32 {
24 self.as_array()[1]
25 }
26
27 pub fn $b(&self) -> f32 {
28 self.as_array()[2]
29 }
30
31 pub fn $a(&self) -> f32 {
32 self.as_array()[3]
33 }
34 };
35}
36
37pub fn mat4_x_vec4(l: f32x4, r: [f32x4; 4]) -> f32x4 {
38 let v = l.as_array_ref();
39 ((r[0]) * f32x4::splat(v[0]))
40 + ((r[1]) * f32x4::splat(v[1]))
41 + ((r[2]) * f32x4::splat(v[2]))
42 + ((r[3]) * f32x4::splat(v[3]))
43}
44
45fn fconv<T: num_traits::ToPrimitive, U: NumCast>(v: T) -> U {
46 U::from(v).unwrap()
47}
48
49pub fn srgb_to_linear<T: num_traits::Float>(c: T) -> T {
50 if c <= fconv(0.04045) {
51 c / fconv(12.92)
52 } else {
53 ((c + fconv(0.055)) / fconv(1.055)).powf(fconv(2.4))
54 }
55}
56
57pub fn linear_to_srgb<T: num_traits::Float>(c: T) -> T {
60 if c < fconv(0.0031308) {
61 c * fconv(12.92)
62 } else {
63 (c.powf(<T as NumCast>::from(1.0).unwrap() / fconv(2.4)) * fconv(1.055)) - fconv(0.055)
64 }
65}
66
67pub fn map_color(c: f32x4, f: impl Fn(f32) -> f32) -> f32x4 {
70 let v = c.to_array();
71 f32x4::new([f(v[0]), f(v[1]), f(v[2]), v[3]])
72}
73
74pub trait ColorSpace: Premultiplied {
75 fn xyz(&self) -> XYZ;
79
80 fn oklab(&self) -> OkLab {
83 let xyz = self.xyz();
84
85 let mut lms = mat4_x_vec4(xyz.xyza, XYZ_OKLAB_M1);
86
87 let v = lms.as_array_mut();
88 for v in v.iter_mut().take(3) {
89 *v = v.powf(1.0 / 3.0);
90 }
91
92 OkLab {
93 laba: mat4_x_vec4(lms, XYZ_OKLAB_M2),
94 }
95 }
96
97 fn srgb(&self) -> Raw_sRGB<false, false> {
101 let srgb = self.linear_srgb();
102
103 Raw_sRGB {
104 rgba: map_color(srgb.rgba, linear_to_srgb),
105 }
106 }
107
108 fn linear_srgb(&self) -> Raw_sRGB<true, false> {
111 let xyz = self.xyz();
112
113 let linear_rgba = mat4_x_vec4(xyz.xyza, XYZ_SRGB);
114 Raw_sRGB { rgba: linear_rgba }
115 }
116}
117
118pub trait Premultiplied {
121 fn srgb_pre(&self) -> Raw_sRGB<false, true> {
126 let srgb = self.linear_srgb_pre();
127
128 Raw_sRGB {
129 rgba: map_color(srgb.rgba, linear_to_srgb),
130 }
131 }
132
133 fn linear_srgb_pre(&self) -> Raw_sRGB<true, true>;
134}
135
136#[derive(Debug, Default, Clone, Copy, PartialEq)]
137pub struct XYZ {
138 xyza: f32x4,
139}
140
141impl XYZ {
142 gen_channel_accessors! {xyza, x, y, z, a}
143}
144
145impl ColorSpace for XYZ {
146 fn xyz(&self) -> XYZ {
147 *self
148 }
149}
150
151impl Premultiplied for XYZ {
152 fn linear_srgb_pre(&self) -> Raw_sRGB<true, true> {
153 self.linear_srgb().premultiply()
154 }
155}
156
157const XYZ_OKLAB_M1: [f32x4; 4] = [
158 f32x4::new([0.818_933, 0.361_866_74, -0.128_859_71, 0.0]),
159 f32x4::new([0.032_984_544, 0.929_311_9, 0.036_145_64, 0.0]),
160 f32x4::new([0.048_200_3, 0.264_366_27, 0.633_851_7, 0.0]),
161 f32x4::new([0.0, 0.0, 0.0, 1.0]),
162];
163
164const OKLAB_XYZ_M1: [f32x4; 4] = [
165 f32x4::new([1.22701, -0.5578, 0.281256, 0.0]),
166 f32x4::new([-0.0405802, 1.11226, -0.0716767, 0.0]),
167 f32x4::new([-0.0763813, -0.421482, 1.58616, 0.0]),
168 f32x4::new([0.0, 0.0, 0.0, 1.0]),
169];
170
171const XYZ_OKLAB_M2: [f32x4; 4] = [
172 f32x4::new([0.210_454_26, 0.793_617_8, -0.004_072_047, 0.0]),
173 f32x4::new([1.977_998_5, -2.428_592_2, 0.450_593_7, 0.0]),
174 f32x4::new([0.025904037, 0.782_771_77, -0.808_675_77, 0.0]),
175 f32x4::new([0.0, 0.0, 0.0, 1.0]),
176];
177
178const OKLAB_XYZ_M2: [f32x4; 4] = [
179 f32x4::new([1.0, 0.396338, 0.215804, 0.0]),
180 f32x4::new([1.0, -0.105561, -0.0638542, 0.0]),
181 f32x4::new([1.0, -0.0894842, -1.29149, 0.0]),
182 f32x4::new([0.0, 0.0, 0.0, 1.0]),
183];
184
185#[derive(Debug, Default, Clone, Copy, PartialEq)]
186pub struct OkLab {
187 laba: f32x4,
188}
189
190impl OkLab {
191 gen_channel_accessors! {laba, l, c, h, a}
192}
193
194impl ColorSpace for OkLab {
195 fn xyz(&self) -> XYZ {
196 let mut lms = mat4_x_vec4(self.laba, OKLAB_XYZ_M2);
197
198 let v = lms.as_array_mut();
199 for v in v.iter_mut().take(3) {
200 *v = v.powf(3.0);
201 }
202
203 XYZ {
204 xyza: mat4_x_vec4(lms, OKLAB_XYZ_M1),
205 }
206 }
207
208 fn oklab(&self) -> OkLab {
209 *self
210 }
211
212 fn linear_srgb(&self) -> Raw_sRGB<true, false> {
214 let v = self.laba.as_array_ref();
215 let l_ = v[0] + 0.396_337_78 * v[1] + 0.215_803_76 * v[2];
216 let m_ = v[0] - 0.105_561_346 * v[1] - 0.063_854_17 * v[2];
217 let s_ = v[0] - 0.089_484_18 * v[1] - 1.291_485_5 * v[2];
218
219 let l = l_ * l_ * l_;
220 let m = m_ * m_ * m_;
221 let s = s_ * s_ * s_;
222
223 Raw_sRGB {
224 rgba: f32x4::new([
225 4.076_741_7 * l - 3.307_711_6 * m + 0.230_969_94 * s,
226 -1.268_438 * l + 2.609_757_4 * m - 0.341_319_38 * s,
227 -0.0041960863 * l - 0.703_418_6 * m + 1.707_614_7 * s,
228 v[3],
229 ]),
230 }
231 }
232}
233
234impl Premultiplied for OkLab {
235 fn linear_srgb_pre(&self) -> Raw_sRGB<true, true> {
236 self.linear_srgb().premultiply()
237 }
238}
239
240const XYZ_SRGB: [f32x4; 4] = [
241 f32x4::new([3.2404542, -1.5371385, -0.4985314, 0.0]),
242 f32x4::new([-0.969_266, 1.8760108, 0.0415560, 0.0]),
243 f32x4::new([0.0556434, -0.2040259, 1.0572252, 0.0]),
244 f32x4::new([0.0, 0.0, 0.0, 1.0]),
245];
246const SRGB_XYZ: [f32x4; 4] = [
247 f32x4::new([0.4124564, 0.3575761, 0.1804375, 0.0]),
248 f32x4::new([0.2126729, 0.7151522, 0.0721750, 0.0]),
249 f32x4::new([0.0193339, 0.119_192, 0.9503041, 0.0]),
250 f32x4::new([0.0, 0.0, 0.0, 1.0]),
251];
252
253#[derive(Debug, Default, Clone, Copy, PartialEq)]
254#[allow(non_camel_case_types)]
255pub struct Raw_sRGB<const LINEAR: bool, const PREMULTIPLY: bool> {
256 pub rgba: f32x4,
257}
258
259impl<const LINEAR: bool, const PREMULTIPLY: bool> Raw_sRGB<LINEAR, PREMULTIPLY> {
260 gen_channel_accessors! {rgba, r, g, b, a}
261
262 pub const fn transparent() -> Self {
266 Self { rgba: f32x4::ZERO }
267 }
268
269 pub const fn black() -> Self {
271 Self {
272 rgba: f32x4::new([0.0, 0.0, 0.0, 1.0]),
273 }
274 }
275
276 pub const fn white() -> Self {
278 Self { rgba: f32x4::ONE }
279 }
280}
281
282impl Raw_sRGB<false, false> {
283 pub fn as_8bit(&self) -> [u8; 4] {
289 self.as_array().map(|x| (x * 255.0).round() as u8)
290 }
291
292 pub fn as_32bit(&self) -> sRGB32 {
295 sRGB32 {
296 rgba: u32::from_be_bytes(self.as_8bit()),
297 }
298 }
299}
300
301impl Raw_sRGB<false, true> {
302 pub fn as_bgra(&self) -> [u8; 4] {
306 let mut rgba = self.as_array().map(|x| (x * 255.0).round() as u8);
307 rgba.swap(0, 2);
308 rgba
309 }
310}
311
312impl<const LINEAR: bool, const PREMULTIPLY: bool> From<[f32; 4]> for Raw_sRGB<LINEAR, PREMULTIPLY> {
313 fn from(value: [f32; 4]) -> Self {
314 Self {
315 rgba: f32x4::new(value),
316 }
317 }
318}
319
320impl ColorSpace for Raw_sRGB<false, false> {
321 fn xyz(&self) -> XYZ {
322 self.linear_srgb().xyz()
323 }
324
325 fn srgb(&self) -> Raw_sRGB<false, false> {
326 *self
327 }
328
329 fn linear_srgb(&self) -> Raw_sRGB<true, false> {
330 Raw_sRGB {
331 rgba: map_color(self.rgba, srgb_to_linear),
332 }
333 }
334}
335
336impl Premultiplied for Raw_sRGB<false, false> {
337 fn linear_srgb_pre(&self) -> Raw_sRGB<true, true> {
338 self.linear_srgb().premultiply()
339 }
340}
341
342impl ColorSpace for Raw_sRGB<true, false> {
343 fn xyz(&self) -> XYZ {
344 XYZ {
347 xyza: mat4_x_vec4(self.rgba, SRGB_XYZ),
348 }
349 }
350
351 fn oklab(&self) -> OkLab {
352 let v = self.as_array();
353 let l = 0.412_221_46 * v[0] + 0.536_332_55 * v[1] + 0.051_445_995 * v[2];
354 let m = 0.211_903_5 * v[0] + 0.680_699_5 * v[1] + 0.107_396_96 * v[2];
355 let s = 0.088_302_46 * v[0] + 0.281_718_85 * v[1] + 0.629_978_7 * v[2];
356
357 let l_ = l.powf(1.0 / 3.0);
358 let m_ = m.powf(1.0 / 3.0);
359 let s_ = s.powf(1.0 / 3.0);
360
361 OkLab {
362 laba: f32x4::new([
363 0.210_454_26 * l_ + 0.793_617_8 * m_ - 0.004_072_047 * s_,
364 1.977_998_5 * l_ - 2.428_592_2 * m_ + 0.450_593_7 * s_,
365 0.025_904_037 * l_ + 0.782_771_77 * m_ - 0.808_675_77 * s_,
366 v[3],
367 ]),
368 }
369 }
370
371 fn linear_srgb(&self) -> Raw_sRGB<true, false> {
372 *self
373 }
374}
375
376impl Premultiplied for Raw_sRGB<true, false> {
377 fn linear_srgb_pre(&self) -> Raw_sRGB<true, true> {
378 self.premultiply()
379 }
380}
381
382impl Raw_sRGB<true, false> {
383 fn premultiply(&self) -> Raw_sRGB<true, true> {
385 let a = self.a();
386 Raw_sRGB {
387 rgba: map_color(self.rgba, |x| x * a),
388 }
389 }
390}
391
392impl Premultiplied for Raw_sRGB<false, true> {
393 fn srgb_pre(&self) -> Raw_sRGB<false, true> {
394 *self
395 }
396
397 fn linear_srgb_pre(&self) -> Raw_sRGB<true, true> {
398 Raw_sRGB {
399 rgba: map_color(self.rgba, srgb_to_linear),
400 }
401 }
402}
403
404impl Premultiplied for Raw_sRGB<true, true> {
405 fn linear_srgb_pre(&self) -> Raw_sRGB<true, true> {
406 *self
407 }
408}
409
410#[allow(non_camel_case_types)]
412pub type sRGB = Raw_sRGB<false, false>;
413#[allow(non_camel_case_types)]
415pub type Linear_sRGB = Raw_sRGB<true, false>;
416#[allow(non_camel_case_types)]
418pub type Pre_sRGB = Raw_sRGB<false, true>;
419#[allow(non_camel_case_types)]
421pub type PreLinear_sRGB = Raw_sRGB<true, true>;
422
423impl From<sRGB> for cosmic_text::Color {
426 fn from(val: sRGB) -> Self {
427 let v = val.as_8bit();
428 cosmic_text::Color::rgba(v[0], v[1], v[2], v[3])
429 }
430}
431
432#[allow(non_camel_case_types)]
434#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
435pub struct sRGB32 {
436 pub rgba: u32,
437}
438
439impl sRGB32 {
440 pub const fn as_array(&self) -> [u8; 4] {
441 self.rgba.to_be_bytes()
442 }
443
444 pub const fn new(r: u8, g: u8, b: u8, a: u8) -> Self {
445 Self {
446 rgba: u32::from_be_bytes([r, g, b, a]),
447 }
448 }
449
450 pub const fn transparent() -> Self {
452 Self { rgba: 0 }
453 }
454
455 pub const fn black() -> Self {
457 Self {
458 rgba: u32::from_be_bytes([0, 0, 0, 255]),
459 }
460 }
461
462 pub const fn white() -> Self {
464 Self { rgba: u32::MAX }
465 }
466
467 pub const fn from_alpha(alpha: u8) -> Self {
468 Self {
469 rgba: u32::from_be_bytes([255, 255, 255, alpha]),
470 }
471 }
472
473 pub const fn r(&self) -> u8 {
474 self.as_array()[0]
475 }
476
477 pub const fn g(&self) -> u8 {
478 self.as_array()[1]
479 }
480
481 pub const fn b(&self) -> u8 {
482 self.as_array()[2]
483 }
484
485 pub const fn a(&self) -> u8 {
486 self.as_array()[3]
487 }
488
489 pub fn as_f32(&self) -> sRGB {
490 sRGB {
491 rgba: f32x4::new(self.as_array().map(|x| x as f32 / 255.0)),
492 }
493 }
494}
495
496#[allow(non_camel_case_types)]
499#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
500pub struct sRGB64 {
501 pub rgba: u64,
502}
503
504impl sRGB64 {
505 pub const fn as_array(&self) -> [u16; 4] {
506 [
507 ((self.rgba >> 48) & 0xFFFF) as u16,
508 ((self.rgba >> 32) & 0xFFFF) as u16,
509 ((self.rgba >> 16) & 0xFFFF) as u16,
510 (self.rgba & 0xFFFF) as u16,
511 ]
512 }
513
514 pub const fn new(r: u16, g: u16, b: u16, a: u16) -> Self {
515 Self {
516 rgba: ((r as u64) << 48) | ((g as u64) << 32) | ((b as u64) << 16) | (a as u64),
517 }
518 }
519
520 pub const fn transparent() -> Self {
522 Self { rgba: 0 }
523 }
524
525 pub const fn black() -> Self {
527 Self {
528 rgba: 0x000000000000FFFF,
529 }
530 }
531
532 pub const fn white() -> Self {
534 Self { rgba: u64::MAX }
535 }
536
537 pub const fn from_alpha(alpha: u16) -> Self {
538 Self {
539 rgba: 0xFFFFFFFFFFFF0000 | alpha as u64,
540 }
541 }
542
543 pub const fn r(&self) -> u16 {
544 self.as_array()[0]
545 }
546
547 pub const fn g(&self) -> u16 {
548 self.as_array()[1]
549 }
550
551 pub const fn b(&self) -> u16 {
552 self.as_array()[2]
553 }
554
555 pub const fn a(&self) -> u16 {
556 self.as_array()[3]
557 }
558
559 pub fn as_f32(&self) -> sRGB {
560 sRGB {
561 rgba: f32x4::new(self.as_array().map(|x| x as f32 / 65535.0)),
562 }
563 }
564
565 pub fn as_f32_scaled(&self, scale: f32) -> sRGB {
568 sRGB {
569 rgba: f32x4::new(self.as_array().map(|x| x as f32 / (65535.0 / scale))),
570 }
571 }
572}