volren_core/transfer_function/
lut.rs1use super::{ColorTransferFunction, OpacityTransferFunction};
4
5#[derive(Debug, Clone)]
17pub struct TransferFunctionLut {
18 rgba: Vec<f32>,
20 lut_size: u32,
22 scalar_min: f64,
24 scalar_max: f64,
26}
27
28impl TransferFunctionLut {
29 #[must_use]
37 pub fn bake(
38 ctf: &ColorTransferFunction,
39 otf: &OpacityTransferFunction,
40 scalar_min: f64,
41 scalar_max: f64,
42 lut_size: u32,
43 ) -> Self {
44 debug_assert!(lut_size > 0, "lut_size must be > 0");
45 debug_assert!(scalar_max > scalar_min, "scalar_max must be > scalar_min");
46
47 let mut rgba = Vec::with_capacity(lut_size as usize * 4);
48 let range = scalar_max - scalar_min;
49
50 for i in 0..lut_size {
51 let t = i as f64 / (lut_size - 1).max(1) as f64;
52 let scalar = scalar_min + t * range;
53 let [r, g, b] = ctf.evaluate(scalar);
54 let a = otf.evaluate(scalar);
55 rgba.push(r as f32);
56 rgba.push(g as f32);
57 rgba.push(b as f32);
58 rgba.push(a as f32);
59 }
60
61 Self {
62 rgba,
63 lut_size,
64 scalar_min,
65 scalar_max,
66 }
67 }
68
69 #[must_use]
73 pub fn as_rgba_f32(&self) -> &[f32] {
74 &self.rgba
75 }
76
77 #[must_use]
79 pub fn as_bytes(&self) -> &[u8] {
80 bytemuck::cast_slice(&self.rgba)
81 }
82
83 #[must_use]
85 pub fn lut_size(&self) -> u32 {
86 self.lut_size
87 }
88
89 #[must_use]
91 pub fn scalar_min(&self) -> f64 {
92 self.scalar_min
93 }
94
95 #[must_use]
97 pub fn scalar_max(&self) -> f64 {
98 self.scalar_max
99 }
100}
101
102#[cfg(test)]
105mod tests {
106 use super::*;
107 use crate::transfer_function::{ColorTransferFunction, OpacityTransferFunction};
108 use approx::assert_abs_diff_eq;
109
110 fn grey_lut() -> TransferFunctionLut {
111 let ctf = ColorTransferFunction::greyscale(0.0, 1.0);
112 let otf = OpacityTransferFunction::linear_ramp(0.0, 1.0);
113 TransferFunctionLut::bake(&ctf, &otf, 0.0, 1.0, 256)
114 }
115
116 #[test]
117 fn lut_size_matches() {
118 let lut = grey_lut();
119 assert_eq!(lut.as_rgba_f32().len(), 256 * 4);
120 assert_eq!(lut.lut_size(), 256);
121 }
122
123 #[test]
124 fn first_entry_is_black_transparent() {
125 let lut = grey_lut();
126 let d = lut.as_rgba_f32();
127 assert_abs_diff_eq!(d[0] as f64, 0.0, epsilon = 1e-5);
128 assert_abs_diff_eq!(d[3] as f64, 0.0, epsilon = 1e-5);
129 }
130
131 #[test]
132 fn last_entry_is_white_opaque() {
133 let lut = grey_lut();
134 let d = lut.as_rgba_f32();
135 let last = (lut.lut_size() as usize - 1) * 4;
136 assert_abs_diff_eq!(d[last] as f64, 1.0, epsilon = 1e-5);
137 assert_abs_diff_eq!(d[last + 3] as f64, 1.0, epsilon = 1e-5);
138 }
139
140 #[test]
141 fn midpoint_is_mid_grey_half_opaque() {
142 let lut = grey_lut();
143 let d = lut.as_rgba_f32();
144 let mid = 128 * 4;
145 assert!((d[mid] - 0.5).abs() < 0.01);
147 assert!((d[mid + 3] - 0.5).abs() < 0.01);
148 }
149
150 #[test]
151 fn opacity_is_monotone() {
152 let lut = grey_lut();
153 let d = lut.as_rgba_f32();
154 let mut prev = 0.0f32;
155 for i in 0..lut.lut_size() as usize {
156 let a = d[i * 4 + 3];
157 assert!(a >= prev - 1e-6, "LUT opacity not monotone at {i}");
158 prev = a;
159 }
160 }
161}