#[inline]
fn clamp01(x: f32) -> f32 {
x.clamp(0.0, 1.0)
}
#[inline]
fn to_u8(x: f32) -> u8 {
(clamp01(x) * 255.0 + 0.5) as u8
}
pub fn greyscale_rgba() -> [[u8; 4]; 256] {
let mut lut = [[0u8; 4]; 256];
for i in 0..256 {
let v = i as u8;
lut[i] = [v, v, v, 255];
}
lut
}
pub fn viridis_rgba() -> [[u8; 4]; 256] {
let mut lut = [[0u8; 4]; 256];
for i in 0..256 {
let t = i as f32 / 255.0;
let r = 0.2777
+ t * (0.1073
+ t * (-0.3300 + t * (1.1609 + t * (-2.7590 + t * (2.8678 - t * 0.8625)))));
let g = 0.0055
+ t * (2.1823
+ t * (-4.3580 + t * (4.3673 + t * (-2.5218 + t * (0.7503 + t * (-0.0955))))));
let b = 0.3298
+ t * (0.8316
+ t * (0.4906 + t * (-4.1400 + t * (7.5226 + t * (-6.3019 + t * 2.0897)))));
lut[i] = [to_u8(r), to_u8(g), to_u8(b), 255];
}
lut
}
pub fn plasma_rgba() -> [[u8; 4]; 256] {
let mut lut = [[0u8; 4]; 256];
for i in 0..256 {
let t = i as f32 / 255.0;
let r = 0.0504
+ t * (3.0109
+ t * (-6.8040 + t * (10.3003 + t * (-9.2634 + t * (4.4022 - t * 0.7295)))));
let g = 0.0285
+ t * (-0.4249
+ t * (3.4517 + t * (-5.7997 + t * (6.3455 + t * (-3.9610 + t * 1.0488)))));
let b = 0.5278
+ t * (1.4896
+ t * (-3.7463 + t * (5.5014 + t * (-5.1827 + t * (2.7329 - t * 0.5988)))));
lut[i] = [to_u8(r), to_u8(g), to_u8(b), 255];
}
lut
}
pub fn coolwarm_rgba() -> [[u8; 4]; 256] {
let mut lut = [[0u8; 4]; 256];
let cold = [59.0 / 255.0_f32, 76.0 / 255.0, 192.0 / 255.0]; let mid = [220.0 / 255.0_f32, 220.0 / 255.0, 220.0 / 255.0]; let warm = [180.0 / 255.0_f32, 4.0 / 255.0, 38.0 / 255.0];
for i in 0..256 {
let t = i as f32 / 255.0;
let (r, g, b) = if t <= 0.5 {
let s = t * 2.0; let h = s * s * (3.0 - 2.0 * s); (
cold[0] + h * (mid[0] - cold[0]),
cold[1] + h * (mid[1] - cold[1]),
cold[2] + h * (mid[2] - cold[2]),
)
} else {
let s = (t - 0.5) * 2.0; let h = s * s * (3.0 - 2.0 * s);
(
mid[0] + h * (warm[0] - mid[0]),
mid[1] + h * (warm[1] - mid[1]),
mid[2] + h * (warm[2] - mid[2]),
)
};
lut[i] = [to_u8(r), to_u8(g), to_u8(b), 255];
}
lut
}
pub fn rainbow_rgba() -> [[u8; 4]; 256] {
let mut lut = [[0u8; 4]; 256];
for i in 0..256 {
let t = i as f32 / 255.0;
let hue = 240.0 * (1.0 - t); let (r, g, b) = hsv_to_rgb(hue, 1.0, 1.0);
lut[i] = [to_u8(r), to_u8(g), to_u8(b), 255];
}
lut
}
fn hsv_to_rgb(h: f32, s: f32, v: f32) -> (f32, f32, f32) {
if s == 0.0 {
return (v, v, v);
}
let h = h % 360.0;
let sector = (h / 60.0).floor() as i32;
let frac = h / 60.0 - sector as f32;
let p = v * (1.0 - s);
let q = v * (1.0 - s * frac);
let t = v * (1.0 - s * (1.0 - frac));
match sector {
0 => (v, t, p),
1 => (q, v, p),
2 => (p, v, t),
3 => (p, q, v),
4 => (t, p, v),
_ => (v, p, q),
}
}
pub fn lerp_colormap_lut(stops: &[(f32, [u8; 4])]) -> [[u8; 4]; 256] {
let mut lut = [[0u8; 4]; 256];
if stops.is_empty() {
return lut;
}
let mut sorted: Vec<(f32, [u8; 4])> = stops.to_vec();
sorted.sort_by(|a, b| a.0.partial_cmp(&b.0).unwrap_or(std::cmp::Ordering::Equal));
for i in 0..256 {
let t = i as f32 / 255.0;
let first = sorted[0];
let last = *sorted.last().unwrap();
if t <= first.0 {
lut[i] = first.1;
continue;
}
if t >= last.0 {
lut[i] = last.1;
continue;
}
let pos = sorted.partition_point(|s| s.0 <= t);
let lo = sorted[pos - 1];
let hi = sorted[pos];
let range = hi.0 - lo.0;
let frac = if range > 1.0e-7 {
(t - lo.0) / range
} else {
0.0
};
lut[i] = [
(lo.1[0] as f32 + (hi.1[0] as f32 - lo.1[0] as f32) * frac + 0.5) as u8,
(lo.1[1] as f32 + (hi.1[1] as f32 - lo.1[1] as f32) * frac + 0.5) as u8,
(lo.1[2] as f32 + (hi.1[2] as f32 - lo.1[2] as f32) * frac + 0.5) as u8,
(lo.1[3] as f32 + (hi.1[3] as f32 - lo.1[3] as f32) * frac + 0.5) as u8,
];
}
lut
}
pub fn parse_paraview_xml_colormap(xml: &str) -> Vec<(String, Vec<(f32, [u8; 4])>)> {
let mut result = Vec::new();
let mut current_name: Option<String> = None;
let mut current_stops: Vec<(f32, [u8; 4])> = Vec::new();
for line in xml.lines() {
let trimmed = line.trim();
if trimmed.starts_with("<ColorMap") {
current_name = attr_value(trimmed, "name").map(|s| s.to_string());
current_stops.clear();
} else if trimmed.starts_with("</ColorMap>") {
if let Some(name) = current_name.take() {
if !current_stops.is_empty() {
result.push((name, current_stops.clone()));
}
}
current_stops.clear();
} else if trimmed.starts_with("<Point") {
if let (Some(x), Some(r), Some(g), Some(b)) = (
attr_f32(trimmed, "x"),
attr_f32(trimmed, "r"),
attr_f32(trimmed, "g"),
attr_f32(trimmed, "b"),
) {
let a = attr_f32(trimmed, "o").unwrap_or(1.0);
let stop = (
x,
[
(r.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
(g.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
(b.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
(a.clamp(0.0, 1.0) * 255.0 + 0.5) as u8,
],
);
current_stops.push(stop);
}
}
}
result
}
pub fn export_paraview_xml_colormap(name: &str, stops: &[(f32, [u8; 4])]) -> String {
let mut out = String::new();
out.push_str("<ColorMaps>\n");
out.push_str(&format!(" <ColorMap name=\"{}\" space=\"RGB\">\n", name));
for &(pos, rgba) in stops {
let r = rgba[0] as f32 / 255.0;
let g = rgba[1] as f32 / 255.0;
let b = rgba[2] as f32 / 255.0;
let o = rgba[3] as f32 / 255.0;
out.push_str(&format!(
" <Point x=\"{:.6}\" r=\"{:.6}\" g=\"{:.6}\" b=\"{:.6}\" o=\"{:.6}\"/>\n",
pos, r, g, b, o
));
}
out.push_str(" </ColorMap>\n");
out.push_str("</ColorMaps>\n");
out
}
fn attr_value<'a>(tag: &'a str, name: &str) -> Option<&'a str> {
let search = format!("{}=\"", name);
let start = tag.find(search.as_str())? + search.len();
let end = tag[start..].find('"')? + start;
Some(&tag[start..end])
}
fn attr_f32(tag: &str, name: &str) -> Option<f32> {
attr_value(tag, name)?.parse().ok()
}