use super::{ColorTransferFunction, OpacityTransferFunction};
#[derive(Debug, Clone)]
pub struct TransferFunctionLut {
rgba: Vec<f32>,
lut_size: u32,
scalar_min: f64,
scalar_max: f64,
}
impl TransferFunctionLut {
#[must_use]
pub fn bake(
ctf: &ColorTransferFunction,
otf: &OpacityTransferFunction,
scalar_min: f64,
scalar_max: f64,
lut_size: u32,
) -> Self {
debug_assert!(lut_size > 0, "lut_size must be > 0");
debug_assert!(scalar_max > scalar_min, "scalar_max must be > scalar_min");
let mut rgba = Vec::with_capacity(lut_size as usize * 4);
let range = scalar_max - scalar_min;
for i in 0..lut_size {
let t = i as f64 / (lut_size - 1).max(1) as f64;
let scalar = scalar_min + t * range;
let [r, g, b] = ctf.evaluate(scalar);
let a = otf.evaluate(scalar);
rgba.push(r as f32);
rgba.push(g as f32);
rgba.push(b as f32);
rgba.push(a as f32);
}
Self {
rgba,
lut_size,
scalar_min,
scalar_max,
}
}
#[must_use]
pub fn as_rgba_f32(&self) -> &[f32] {
&self.rgba
}
#[must_use]
pub fn as_bytes(&self) -> &[u8] {
bytemuck::cast_slice(&self.rgba)
}
#[must_use]
pub fn lut_size(&self) -> u32 {
self.lut_size
}
#[must_use]
pub fn scalar_min(&self) -> f64 {
self.scalar_min
}
#[must_use]
pub fn scalar_max(&self) -> f64 {
self.scalar_max
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::transfer_function::{ColorTransferFunction, OpacityTransferFunction};
use approx::assert_abs_diff_eq;
fn grey_lut() -> TransferFunctionLut {
let ctf = ColorTransferFunction::greyscale(0.0, 1.0);
let otf = OpacityTransferFunction::linear_ramp(0.0, 1.0);
TransferFunctionLut::bake(&ctf, &otf, 0.0, 1.0, 256)
}
#[test]
fn lut_size_matches() {
let lut = grey_lut();
assert_eq!(lut.as_rgba_f32().len(), 256 * 4);
assert_eq!(lut.lut_size(), 256);
}
#[test]
fn first_entry_is_black_transparent() {
let lut = grey_lut();
let d = lut.as_rgba_f32();
assert_abs_diff_eq!(d[0] as f64, 0.0, epsilon = 1e-5);
assert_abs_diff_eq!(d[3] as f64, 0.0, epsilon = 1e-5);
}
#[test]
fn last_entry_is_white_opaque() {
let lut = grey_lut();
let d = lut.as_rgba_f32();
let last = (lut.lut_size() as usize - 1) * 4;
assert_abs_diff_eq!(d[last] as f64, 1.0, epsilon = 1e-5);
assert_abs_diff_eq!(d[last + 3] as f64, 1.0, epsilon = 1e-5);
}
#[test]
fn midpoint_is_mid_grey_half_opaque() {
let lut = grey_lut();
let d = lut.as_rgba_f32();
let mid = 128 * 4;
assert!((d[mid] - 0.5).abs() < 0.01);
assert!((d[mid + 3] - 0.5).abs() < 0.01);
}
#[test]
fn opacity_is_monotone() {
let lut = grey_lut();
let d = lut.as_rgba_f32();
let mut prev = 0.0f32;
for i in 0..lut.lut_size() as usize {
let a = d[i * 4 + 3];
assert!(a >= prev - 1e-6, "LUT opacity not monotone at {i}");
prev = a;
}
}
}