use teksilo_canvas::resample::downsample_half;
pub(crate) fn build_mip_chain(pixels: &[u8], width: u32, height: u32) -> Vec<(u32, u32, Vec<u8>)> {
if width == 0 || height == 0 || pixels.len() < (width as usize * height as usize * 4) {
return Vec::new();
}
let mut levels = Vec::new();
let mut src_w = width;
let mut src_h = height;
let mut src: Vec<u8> = pixels[..(width as usize * height as usize * 4)].to_vec();
while src_w > 1 || src_h > 1 {
let (dst_w, dst_h, dst) = downsample_half(&src, src_w, src_h);
levels.push((dst_w, dst_h, dst.clone()));
src = dst;
src_w = dst_w;
src_h = dst_h;
}
levels
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn chain_halves_to_one_by_one() {
let px = vec![255u8; 512 * 512 * 4];
let chain = build_mip_chain(&px, 512, 512);
let dims: Vec<(u32, u32)> = chain.iter().map(|(w, h, _)| (*w, *h)).collect();
assert_eq!(
dims,
vec![
(256, 256),
(128, 128),
(64, 64),
(32, 32),
(16, 16),
(8, 8),
(4, 4),
(2, 2),
(1, 1)
]
);
for (w, h, data) in &chain {
assert_eq!(data.len(), (*w as usize) * (*h as usize) * 4);
}
}
#[test]
fn no_levels_below_a_single_texel() {
assert!(build_mip_chain(&[255, 0, 0, 255], 1, 1).is_empty());
assert!(build_mip_chain(&[], 0, 0).is_empty());
assert!(build_mip_chain(&[255, 0, 0, 255], 4, 4).is_empty());
}
#[test]
fn averages_in_linear_space_not_srgb() {
let mut px = Vec::new();
for byte in [0u8, 255, 255, 0] {
px.extend_from_slice(&[byte, byte, byte, 255]);
}
let chain = build_mip_chain(&px, 2, 2);
let (_, _, level1) = &chain[0];
let v = level1[0];
assert!(
(186..=190).contains(&v),
"50% linear luminance should re-encode to sRGB ~188, got {v} \
(128 means the bytes were averaged in sRGB space)"
);
assert_eq!(level1[3], 255, "alpha must stay opaque");
}
#[test]
fn transparent_texels_do_not_darken_their_neighbours() {
let mut px = Vec::new();
px.extend_from_slice(&[255, 0, 0, 255]); px.extend_from_slice(&[0, 0, 0, 0]); px.extend_from_slice(&[0, 0, 0, 0]);
px.extend_from_slice(&[0, 0, 0, 0]);
let chain = build_mip_chain(&px, 2, 2);
let (_, _, level1) = &chain[0];
assert_eq!(
&level1[0..3],
&[255, 0, 0],
"the surviving color must stay pure red, not be dragged toward black"
);
assert_eq!(
level1[3], 64,
"alpha is the plain average of the four texels (255/4)"
);
}
#[test]
fn odd_dimensions_halve_and_clamp() {
let px = vec![128u8; 3 * 5 * 4];
let chain = build_mip_chain(&px, 3, 5);
let dims: Vec<(u32, u32)> = chain.iter().map(|(w, h, _)| (*w, *h)).collect();
assert_eq!(dims, vec![(1, 2), (1, 1)]);
}
#[test]
fn a_flat_color_survives_the_round_trip() {
let px: Vec<u8> = std::iter::repeat_n([37u8, 150, 190, 255], 4 * 4)
.flatten()
.collect();
let chain = build_mip_chain(&px, 4, 4);
for (_, _, data) in &chain {
for texel in data.as_chunks::<4>().0 {
assert!(
(texel[0] as i32 - 37).abs() <= 1
&& (texel[1] as i32 - 150).abs() <= 1
&& (texel[2] as i32 - 190).abs() <= 1
&& texel[3] == 255,
"flat color drifted to {texel:?}"
);
}
}
}
}