qualia_core_db/render/
spectral.rs1#[inline]
7fn fract_sigma(sigma: f32) -> f32 {
8 sigma - sigma.floor()
9}
10
11#[inline]
12pub fn sigma_to_cie_xyz(sigma: f32) -> [f32; 3] {
13 let s = fract_sigma(sigma);
14 let lambda = 400.0 + s * 300.0;
15
16 let gauss = |lambda: f32, center: f32, width: f32| -> f32 {
17 let d = (lambda - center) / width;
18 (-0.5 * d * d).exp()
19 };
20
21 let x1 = 1.056 * gauss(lambda, 599.8, 43.2);
22 let x2 = 0.362 * gauss(lambda, 442.0, 32.0);
23 let x3 = -0.065 * gauss(lambda, 501.1, 20.4);
24 let x = x1 + x2 + x3;
25
26 let y1 = 0.821 * gauss(lambda, 568.8, 46.9);
27 let y2 = 0.286 * gauss(lambda, 530.9, 16.3);
28 let y = y1 + y2;
29
30 let z1 = 1.217 * gauss(lambda, 437.0, 11.8);
31 let z2 = 0.681 * gauss(lambda, 459.0, 26.0);
32 let z = z1 + z2;
33
34 [x, y, z]
35}
36
37#[inline]
38pub fn xyz_to_linear_srgb(xyz: [f32; 3]) -> [f32; 3] {
39 let r = 3.2404542 * xyz[0] - 1.5371385 * xyz[1] - 0.4985314 * xyz[2];
40 let g = -0.9692660 * xyz[0] + 1.8760108 * xyz[1] + 0.0415560 * xyz[2];
41 let b = 0.0556434 * xyz[0] - 0.2040259 * xyz[1] + 1.0572252 * xyz[2];
42 [r.max(0.0), g.max(0.0), b.max(0.0)]
43}
44
45#[inline]
46pub fn sigma_to_linear_rgb(sigma: f32) -> [f32; 3] {
47 xyz_to_linear_srgb(sigma_to_cie_xyz(sigma))
48}
49
50#[inline]
51fn linear_to_srgb_channel(c: f32) -> f32 {
52 if c <= 0.0031308 {
53 12.92 * c
54 } else {
55 1.055 * c.powf(1.0 / 2.4) - 0.055
56 }
57}
58
59#[inline]
61pub fn sigma_to_display_rgb(sigma: f32) -> (u8, u8, u8) {
62 let linear = sigma_to_linear_rgb(sigma);
63 let scale = 1.0 / linear.iter().copied().fold(0.0_f32, f32::max).max(1e-6);
64 let norm = [linear[0] * scale, linear[1] * scale, linear[2] * scale];
65 (
66 (linear_to_srgb_channel(norm[0].min(1.0)) * 255.0).round() as u8,
67 (linear_to_srgb_channel(norm[1].min(1.0)) * 255.0).round() as u8,
68 (linear_to_srgb_channel(norm[2].min(1.0)) * 255.0).round() as u8,
69 )
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn same_sigma_same_linear_rgb() {
78 let a = sigma_to_linear_rgb(0.42);
79 let b = sigma_to_linear_rgb(0.42);
80 assert_eq!(a, b);
81 }
82
83 #[test]
84 fn sigma_wraps_via_fract() {
85 let a = sigma_to_linear_rgb(0.25);
86 let b = sigma_to_linear_rgb(1.25);
87 assert_eq!(a, b);
88 }
89
90 #[test]
91 fn green_band_dominates_mid_sigma() {
92 let rgb = sigma_to_linear_rgb(0.5);
93 assert!(rgb[1] >= rgb[0]);
94 assert!(rgb[1] >= rgb[2]);
95 }
96
97 #[test]
98 fn linear_components_non_negative() {
99 for i in 0..=10 {
100 let s = i as f32 / 10.0;
101 let rgb = sigma_to_linear_rgb(s);
102 assert!(rgb[0] >= 0.0 && rgb[1] >= 0.0 && rgb[2] >= 0.0);
103 }
104 }
105}