1use super::indexed_image::{IndexedImage, pixel_count};
4use crate::{Result, error::Error};
5use serde::Serialize;
6
7#[derive(Debug, Clone, Serialize)]
8pub struct AnmPage {
9 pub base_record: u16,
10 pub record_count: u16,
11 pub data_size: u16,
12}
13
14#[derive(Debug, Clone)]
15pub struct AnmFile<'a> {
16 pub declared_frame_count: u32,
17 pub frames_per_second: u16,
18 pub last_delta_valid: bool,
19 pub pages: Vec<AnmPage>,
20 pub records: Vec<&'a [u8]>,
21 pub loop_delta: Option<&'a [u8]>,
22 pub frames: Vec<IndexedImage>,
23}
24
25fn word(data: &[u8], i: usize) -> u16 {
26 u16::from_le_bytes([data[i], data[i + 1]])
27}
28
29fn dword(data: &[u8], i: usize) -> u32 {
30 u32::from_le_bytes([data[i], data[i + 1], data[i + 2], data[i + 3]])
31}
32
33pub fn parse_anm_file(data: &[u8]) -> Result<AnmFile<'_>> {
36 if data.len() < 1280 || &data[..4] != b"LPF " || &data[16..20] != b"ANIM" {
37 return Err(Error::Parse("ANM: invalid or truncated LPF header".into()));
38 }
39 let page_count = usize::from(word(data, 6));
40 if word(data, 4) != 256
41 || page_count == 0
42 || page_count > 256
43 || data[24] != 0
44 || data[26] > 1
45 || data[27] > 1
46 || data[28] != 0
47 || data[29] != 1
48 || data[30] != 0
49 || data[31] != 1
50 {
51 return Err(Error::Parse("ANM: unsupported LPF layout or codec".into()));
52 }
53 let width = word(data, 20);
54 let height = word(data, 22);
55 let count = pixel_count(width, height)?;
56 let record_count = usize::try_from(dword(data, 8))
57 .map_err(|_| Error::Parse("ANM: record count overflow".into()))?;
58 let frame_count = record_count
59 .checked_sub(usize::from(data[26]))
60 .filter(|&n| n > 0 && n <= data.len() / 7 && n <= (256 * 1024 * 1024) / count)
61 .ok_or_else(|| Error::Parse("ANM: invalid or excessive frame count".into()))?;
62 let fps = word(data, 68);
63 if fps == 0 {
64 return Err(Error::Parse("ANM: zero frame rate".into()));
65 }
66 let table_offset = usize::from(word(data, 14));
67 if table_offset < 1280 {
68 return Err(Error::Parse("ANM: page table overlaps palette".into()));
69 }
70 let table = data
71 .get(table_offset..table_offset + 256 * 6)
72 .ok_or_else(|| Error::Parse("ANM: truncated page table".into()))?;
73 let mut pages = Vec::with_capacity(page_count);
74 let mut records = vec![None; record_count];
75 for i in 0..page_count {
76 let descriptor = &table[i * 6..i * 6 + 6];
77 let page = AnmPage {
78 base_record: word(descriptor, 0),
79 record_count: word(descriptor, 2),
80 data_size: word(descriptor, 4),
81 };
82 if page.record_count & 0xc000 != 0 || page.record_count == 0 {
83 return Err(Error::Parse("ANM: empty page or split record".into()));
84 }
85 let n = usize::from(page.record_count);
86 let base = usize::from(page.base_record);
87 if base + n > record_count {
88 return Err(Error::Parse("ANM: page record range outside index".into()));
89 }
90 let start = table_offset + 256 * 6 + i * 65536;
91 let end = start + 8 + n * 2 + usize::from(page.data_size);
92 if end > start + 65536 {
93 return Err(Error::Parse("ANM: page exceeds 64 KiB".into()));
94 }
95 let payload = data
96 .get(start..end)
97 .ok_or_else(|| Error::Parse("ANM: truncated page".into()))?;
98 if &payload[..6] != descriptor {
99 return Err(Error::Parse("ANM: page header disagrees with table".into()));
100 }
101 let mut cursor = 8 + n * 2;
102 for j in 0..n {
103 let size = usize::from(word(payload, 8 + j * 2));
104 let record = payload
105 .get(cursor..cursor + size)
106 .filter(|r| r.len() >= 7)
107 .ok_or_else(|| Error::Parse("ANM: truncated bitmap record".into()))?;
108 if records[base + j].replace(record).is_some() {
109 return Err(Error::Parse("ANM: overlapping record ranges".into()));
110 }
111 cursor += size;
112 }
113 if cursor != payload.len() {
114 return Err(Error::Parse(
115 "ANM: page size disagrees with record sizes".into(),
116 ));
117 }
118 pages.push(page);
119 }
120 let records: Vec<&[u8]> = records
121 .into_iter()
122 .map(|r| r.ok_or_else(|| Error::Parse("ANM: missing record".into())))
123 .collect::<Result<_>>()?;
124 let palette = std::array::from_fn(|i| {
125 [
126 data[256 + i * 4 + 2],
127 data[256 + i * 4 + 1],
128 data[256 + i * 4],
129 ]
130 });
131 let mut pixels = vec![0; count];
132 let mut frames = Vec::with_capacity(frame_count);
133 for record in &records[..frame_count] {
134 decode_record(record, &mut pixels)?;
135 frames.push(IndexedImage {
136 width,
137 height,
138 palette,
139 pixels: pixels.clone(),
140 transparent_index: None,
141 });
142 }
143 let loop_delta = if data[26] != 0 {
144 records.last().copied()
145 } else {
146 None
147 };
148 if data[27] != 0
149 && let Some(record) = loop_delta
150 {
151 decode_record(record, &mut pixels)?;
152 }
153 Ok(AnmFile {
154 declared_frame_count: dword(data, 64),
155 frames_per_second: fps,
156 last_delta_valid: data[27] != 0,
157 pages,
158 records,
159 loop_delta,
160 frames,
161 })
162}
163
164fn decode_record(record: &[u8], pixels: &mut [u8]) -> Result<()> {
165 if record.len() < 7 || record[0] != b'B' || record[1] != 0 {
166 return Err(Error::Parse("ANM: unsupported bitmap record".into()));
167 }
168 let mut input = 4;
169 let mut output = 0;
170 loop {
171 let control = *record
172 .get(input)
173 .ok_or_else(|| Error::Parse("ANM: missing record terminator".into()))?;
174 input += 1;
175 let (kind, count) = match control {
176 0 => {
177 let count = *record
178 .get(input)
179 .ok_or_else(|| Error::Parse("ANM: truncated short run".into()))?;
180 input += 1;
181 (3, usize::from(count))
182 }
183 1..=127 => (2, usize::from(control)),
184 129..=255 => (0, usize::from(control & 0x7f)),
185 128 => {
186 let bytes = record
187 .get(input..input + 2)
188 .ok_or_else(|| Error::Parse("ANM: truncated long operation".into()))?;
189 input += 2;
190 let value = u16::from_le_bytes([bytes[0], bytes[1]]);
191 if value == 0 {
192 return Ok(());
193 }
194 let kind = value >> 14;
195 let count = usize::from(value & 0x3fff);
196 if kind == 2 && count == 0 {
197 return Err(Error::Parse("ANM: invalid long operation".into()));
198 }
199 if kind != 0 && count == 0 {
200 continue;
201 }
202 (kind, count + if kind == 1 { 0x4000 } else { 0 })
203 }
204 };
205 let end = output + count;
206 let target = pixels
207 .get_mut(output..end)
208 .ok_or_else(|| Error::Parse("ANM: operation exceeds frame bounds".into()))?;
209 if kind == 2 {
210 let source = record
211 .get(input..input + count)
212 .ok_or_else(|| Error::Parse("ANM: truncated literal data".into()))?;
213 target.copy_from_slice(source);
214 input += count;
215 } else if kind == 3 {
216 let value = *record
217 .get(input)
218 .ok_or_else(|| Error::Parse("ANM: missing run value".into()))?;
219 input += 1;
220 target.fill(value);
221 }
222 output = end;
223 }
224}