1use alloc::vec;
12use alloc::vec::Vec;
13
14pub struct MipLevel {
17 pub width: u32,
19 pub height: u32,
21 pub pixels: Vec<u8>,
23}
24
25pub(crate) fn mip_level_count(width: u32, height: u32) -> u32 {
28 let max_dim = width.max(height).max(1);
29 32 - max_dim.leading_zeros()
30}
31
32pub fn generate_mip_chain(width: u32, height: u32, rgba8: &[u8]) -> Vec<MipLevel> {
37 let count = mip_level_count(width, height);
38 let base_len = width as usize * height as usize * 4;
39 let mut levels: Vec<MipLevel> = Vec::with_capacity(count as usize);
40 levels.push(MipLevel {
41 width,
42 height,
43 pixels: rgba8[..base_len].to_vec(),
44 });
45 for _ in 1..count {
46 let prev = levels.last().unwrap();
47 let dw = (prev.width / 2).max(1);
48 let dh = (prev.height / 2).max(1);
49 let mut pixels = vec![0u8; dw as usize * dh as usize * 4];
50 downsample_box(prev, dw, dh, &mut pixels);
51 levels.push(MipLevel {
52 width: dw,
53 height: dh,
54 pixels,
55 });
56 }
57 levels
58}
59
60fn downsample_box(src: &MipLevel, dw: u32, dh: u32, dst: &mut [u8]) {
64 let sw = src.width as usize;
65 let sh = src.height as usize;
66 for y in 0..dh as usize {
67 let sy0 = (2 * y).min(sh - 1);
68 let sy1 = (2 * y + 1).min(sh - 1);
69 for x in 0..dw as usize {
70 let sx0 = (2 * x).min(sw - 1);
71 let sx1 = (2 * x + 1).min(sw - 1);
72 let i00 = (sy0 * sw + sx0) * 4;
73 let i01 = (sy0 * sw + sx1) * 4;
74 let i10 = (sy1 * sw + sx0) * 4;
75 let i11 = (sy1 * sw + sx1) * 4;
76 let d = (y * dw as usize + x) * 4;
77 for c in 0..4 {
78 let sum = src.pixels[i00 + c] as u32
79 + src.pixels[i01 + c] as u32
80 + src.pixels[i10 + c] as u32
81 + src.pixels[i11 + c] as u32;
82 dst[d + c] = ((sum + 2) / 4) as u8;
84 }
85 }
86 }
87}
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92
93 #[test]
94 fn level_count_matches_floor_log2_plus_one() {
95 assert_eq!(mip_level_count(1, 1), 1);
96 assert_eq!(mip_level_count(2, 2), 2);
97 assert_eq!(mip_level_count(256, 256), 9);
98 assert_eq!(mip_level_count(512, 512), 10);
99 assert_eq!(mip_level_count(640, 384), 10); assert_eq!(mip_level_count(1, 8), 4); }
103
104 #[test]
105 fn chain_dimensions_halve_to_one() {
106 let px = vec![0u8; 4 * 4 * 4];
107 let chain = generate_mip_chain(4, 4, &px);
108 let dims: Vec<(u32, u32)> = chain.iter().map(|m| (m.width, m.height)).collect();
109 assert_eq!(dims, vec![(4, 4), (2, 2), (1, 1)]);
110 assert_eq!(chain.len() as u32, mip_level_count(4, 4));
111 }
112
113 #[test]
114 fn non_square_chain_floors_each_axis_independently() {
115 let px = vec![0u8; 4 * 2 * 4];
116 let chain = generate_mip_chain(4, 2, &px);
117 let dims: Vec<(u32, u32)> = chain.iter().map(|m| (m.width, m.height)).collect();
118 assert_eq!(dims, vec![(4, 2), (2, 1), (1, 1)]);
120 }
121
122 #[test]
123 fn two_by_two_averages_to_single_texel() {
124 let px = vec![
126 0, 0, 0, 0, 4, 4, 4, 4, 8, 8, 8, 8, 12, 12, 12, 12, ];
131 let chain = generate_mip_chain(2, 2, &px);
132 assert_eq!(chain.len(), 2);
133 let mip1 = &chain[1];
134 assert_eq!((mip1.width, mip1.height), (1, 1));
135 assert_eq!(mip1.pixels, vec![6, 6, 6, 6]);
136 }
137
138 #[test]
139 fn rounds_to_nearest() {
140 let dark = vec![0, 0, 0, 255, 0, 0, 0, 255, 0, 0, 0, 255, 1, 1, 1, 255];
142 let c = generate_mip_chain(2, 2, &dark);
143 assert_eq!(&c[1].pixels[0..3], &[0, 0, 0]);
144
145 let bright = vec![0, 0, 0, 255, 1, 1, 1, 255, 1, 1, 1, 255, 1, 1, 1, 255];
146 let c = generate_mip_chain(2, 2, &bright);
147 assert_eq!(&c[1].pixels[0..3], &[1, 1, 1]);
148 }
149}