1fn be16(d: &[u8], at: usize) -> Option<usize> {
7 Some(u16::from_be_bytes(d.get(at..at + 2)?.try_into().ok()?) as usize)
8}
9
10pub fn chunks<'a>(data: &'a [u8], kind: &[u8]) -> Option<Vec<(&'a [u8], &'a [u8])>> {
12 if !data.starts_with(b"FORM") || data.get(8..12)? != kind {
13 return None;
14 }
15 let mut out = Vec::new();
16 let mut at = 12;
17 while at + 8 <= data.len() {
18 let id = &data[at..at + 4];
19 let len = u32::from_be_bytes(data[at + 4..at + 8].try_into().ok()?) as usize;
20 let body = data.get(at + 8..(at + 8 + len).min(data.len()))?;
21 out.push((id, body));
22 at += 8 + len + (len & 1);
23 }
24 Some(out)
25}
26
27fn unpack(src: &[u8], want: usize) -> Vec<u8> {
30 let mut out = Vec::with_capacity(want);
31 let mut i = 0;
32 while out.len() < want && i < src.len() {
33 let n = src[i] as i8;
34 i += 1;
35 if n >= 0 {
36 let end = (i + n as usize + 1).min(src.len());
37 out.extend_from_slice(&src[i..end]);
38 i = end;
39 } else if n != -128 {
40 if let Some(&b) = src.get(i) {
41 out.extend(std::iter::repeat_n(b, (-(n as i32) + 1) as usize));
42 }
43 i += 1;
44 }
45 }
46 out.resize(want, 0);
47 out
48}
49
50pub fn ilbm(data: &[u8]) -> Option<(usize, usize, Vec<u8>)> {
52 let chunks = chunks(data, b"ILBM")?;
53 let bmhd = chunks.iter().find(|(id, _)| *id == b"BMHD")?.1;
54 let body = chunks.iter().find(|(id, _)| *id == b"BODY")?.1;
55 let (w, h) = (be16(bmhd, 0)?, be16(bmhd, 2)?);
56 let (planes, masking, compression) = (*bmhd.get(8)? as usize, *bmhd.get(9)?, *bmhd.get(10)?);
57 if w == 0 || h == 0 || w > 4096 || h > 4096 || !(1..=8).contains(&planes) {
58 return None;
59 }
60 let row = w.div_ceil(16) * 2;
61 let stored = planes + usize::from(masking == 1);
62 let bits = if compression == 1 { unpack(body, row * stored * h) } else { body.to_vec() };
63 let mut pens = vec![0u8; w * h];
64 for y in 0..h {
65 for p in 0..planes {
66 let start = (y * stored + p) * row;
67 let Some(line) = bits.get(start..start + row) else { break };
68 for x in 0..w {
69 if line[x / 8] & (0x80 >> (x % 8)) != 0 {
70 pens[y * w + x] |= 1 << p;
71 }
72 }
73 }
74 }
75 Some((w, h, pens))
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn decodes_planes_compressed_or_not() {
84 let form = |compression: u8, body: &[u8]| {
85 let mut bmhd = vec![0, 4, 0, 2, 0, 0, 0, 0, 2, 0, compression, 0, 0, 0, 10, 11, 0, 4, 0, 2];
86 bmhd.truncate(20);
87 let mut f = b"FORM\0\0\0\0ILBM".to_vec();
88 for (id, d) in [(b"BMHD", &bmhd[..]), (b"BODY", body)] {
89 f.extend(id);
90 f.extend((d.len() as u32).to_be_bytes());
91 f.extend(d);
92 if d.len() % 2 == 1 {
93 f.push(0);
94 }
95 }
96 f
97 };
98 let raw = [0b1010_0000, 0, 0b0110_0000, 0, 0xF0, 0, 0xF0, 0];
100 let want = vec![1, 2, 3, 0, 3, 3, 3, 3];
101 assert_eq!(ilbm(&form(0, &raw)), Some((4, 2, want.clone())));
102 let packed = [3, 0b1010_0000, 0, 0b0110_0000, 0, 1, 0xF0, 0, 1, 0xF0, 0];
104 assert_eq!(ilbm(&form(1, &packed)), Some((4, 2, want)));
105 assert_eq!(unpack(&[(-2i8) as u8, 7, 0, 9], 4), [7, 7, 7, 9]);
106 assert!(ilbm(b"FORM\0\0\0\x04ILBM").is_none());
107 }
108}