1use super::riff::{self, invalid};
2use super::{Info, Options, Parsed, Piece, check_image, decode_piece, encode_piece, parse};
3use crate::media::Result;
4use crate::media::Rgba;
5
6pub struct Frame {
7 pub image: Rgba,
8 pub start: u32,
9 pub duration: u32,
10}
11
12pub struct FrameRef<'a> {
13 pub image: &'a Rgba,
14 pub start: u32,
15 pub duration: u32,
16}
17
18pub struct Animation {
19 pub info: Info,
20 pub frames: Vec<Frame>,
21}
22
23pub fn decode_animation(data: &[u8]) -> Result<Animation> {
24 let mut dec = AnimDecoder::new(data)?;
25 let info = dec.info;
26 let mut frames = Vec::with_capacity(info.frames as usize);
27 while let Some(f) = dec.next_frame()? {
28 frames.push(Frame {
29 image: f.image.clone(),
30 start: f.start,
31 duration: f.duration,
32 });
33 }
34 Ok(Animation { info, frames })
35}
36
37pub struct AnimDecoder<'a> {
38 info: Info,
39 pieces: Vec<Piece<'a>>,
40 index: usize,
41 canvas: Rgba,
42 disposed: Rgba,
43 tile: Vec<u8>,
44 start: u32,
45 prev_key: bool,
46}
47
48impl<'a> AnimDecoder<'a> {
49 pub fn new(data: &'a [u8]) -> Result<Self> {
50 Ok(Self::from_parsed(parse(data)?))
51 }
52
53 pub(crate) fn from_parsed(parsed: Parsed<'a>) -> Self {
54 let Parsed { info, pieces } = parsed;
55 AnimDecoder {
56 info,
57 pieces,
58 index: 0,
59 canvas: Rgba::new(info.width, info.height),
60 disposed: Rgba::new(info.width, info.height),
61 tile: Vec::new(),
62 start: 0,
63 prev_key: false,
64 }
65 }
66
67 pub fn info(&self) -> &Info {
68 &self.info
69 }
70
71 pub fn next_frame(&mut self) -> Result<Option<FrameRef<'_>>> {
72 let Some(&p) = self.pieces.get(self.index) else {
73 return Ok(None);
74 };
75 let (cw, ch) = (self.info.width, self.info.height);
76 let full = |q: &Piece| q.width == cw && q.height == ch;
77 let key = self.index == 0 || ((!p.alpha || !p.blend) && full(&p)) || {
78 let prev = &self.pieces[self.index - 1];
79 prev.dispose && (full(prev) || self.prev_key)
80 };
81 if key {
82 self.canvas.data.fill(0);
83 } else {
84 self.canvas.data.copy_from_slice(&self.disposed.data);
85 }
86 self.tile
87 .resize(p.width as usize * p.height as usize * 4, 0);
88 decode_piece(&p, &mut self.tile)?;
89 let stride = cw as usize * 4;
90 let row = p.width as usize * 4;
91 for (y, src) in self.tile.chunks_exact(row).enumerate() {
92 let o = (p.y as usize + y) * stride + p.x as usize * 4;
93 self.canvas.data[o..o + row].copy_from_slice(src);
94 }
95 if self.index > 0 && p.blend && !key {
96 let prev = self.pieces[self.index - 1];
97 for y in 0..p.height {
98 let cy = p.y + y;
99 let mut spans = [(p.x, p.x + p.width), (0, 0)];
100 if prev.dispose && cy >= prev.y && cy < prev.y + prev.height {
101 let (l, r) = (prev.x.max(p.x), (prev.x + prev.width).min(p.x + p.width));
102 if l < r {
103 spans = [(p.x, l), (r, p.x + p.width)];
104 }
105 }
106 for (l, r) in spans {
107 if l >= r {
108 continue;
109 }
110 let o = cy as usize * stride + l as usize * 4;
111 let e = cy as usize * stride + r as usize * 4;
112 blend_row(&mut self.canvas.data[o..e], &self.disposed.data[o..e]);
113 }
114 }
115 }
116 self.disposed.data.copy_from_slice(&self.canvas.data);
117 if p.dispose {
118 for y in p.y..p.y + p.height {
119 let o = y as usize * stride + p.x as usize * 4;
120 self.disposed.data[o..o + row].fill(0);
121 }
122 }
123 self.prev_key = key;
124 self.index += 1;
125 let start = self.start;
126 self.start += p.duration;
127 Ok(Some(FrameRef {
128 image: &self.canvas,
129 start,
130 duration: p.duration,
131 }))
132 }
133}
134
135fn blend_row(src: &mut [u8], dst: &[u8]) {
136 for (s, d) in src.chunks_exact_mut(4).zip(dst.chunks_exact(4)) {
137 let sa = s[3] as u32;
138 if sa == 255 {
139 continue;
140 }
141 if sa == 0 {
142 s.copy_from_slice(d);
143 continue;
144 }
145 let da = (d[3] as u32 * (256 - sa)) >> 8;
146 let ba = sa + da;
147 let scale = (1u32 << 24) / ba;
148 for c in 0..3 {
149 s[c] = (((s[c] as u32 * sa + d[c] as u32 * da) * scale) >> 24) as u8;
150 }
151 s[3] = ba as u8;
152 }
153}
154
155pub struct AnimEncoder {
156 width: u32,
157 height: u32,
158 options: Options,
159 loop_count: u16,
160 background: [u8; 4],
161 prev: Option<Rgba>,
162 jobs: Vec<Job>,
163 pieces: Vec<Encoded>,
164 spare: Vec<Rgba>,
165 batch: usize,
166}
167
168struct Job {
169 head: [u32; 4],
170 duration: u32,
171 blend: bool,
172 image: Rgba,
173 plain: Option<Rgba>,
174}
175
176struct Encoded {
177 head: [u32; 4],
178 duration: u32,
179 blend: bool,
180 alpha: bool,
181 data: Vec<u8>,
182}
183
184impl AnimEncoder {
185 pub fn new(width: u32, height: u32, options: Options, loop_count: u16) -> Result<Self> {
186 check_image(&Rgba::new(width, height))?;
187 let batch = std::thread::available_parallelism()
188 .map_or(1, |n| n.get())
189 .clamp(1, 16);
190 Ok(AnimEncoder {
191 width,
192 height,
193 options,
194 loop_count,
195 background: [0; 4],
196 prev: None,
197 jobs: Vec::new(),
198 pieces: Vec::new(),
199 spare: Vec::new(),
200 batch,
201 })
202 }
203
204 pub fn background(&mut self, rgba: [u8; 4]) {
205 self.background = rgba;
206 }
207
208 pub fn add(&mut self, image: &Rgba, duration: u32) -> Result<()> {
209 if image.width != self.width || image.height != self.height {
210 return Err(invalid("animation frame size differs from the canvas"));
211 }
212 check_image(image)?;
213 let duration = duration.min(0xff_ffff);
214 let mut work = self.spare.pop().unwrap_or_else(|| Rgba::new(0, 0));
215 let job = match &self.prev {
216 None => {
217 work.width = self.width;
218 work.height = self.height;
219 work.data.clear();
220 work.data.extend_from_slice(&image.data);
221 Job {
222 head: [0, 0, self.width, self.height],
223 duration,
224 blend: false,
225 image: work,
226 plain: None,
227 }
228 }
229 Some(prev) => {
230 let Some((x0, y0, x1, y1)) = changed(prev, image) else {
231 self.spare.push(work);
232 let last = match self.jobs.last_mut() {
233 Some(j) => &mut j.duration,
234 None => &mut self.pieces.last_mut().expect("a frame before").duration,
235 };
236 *last = (*last + duration).min(0xff_ffff);
237 return Ok(());
238 };
239 let (x0, y0) = (x0 & !1, y0 & !1);
240 let (w, h) = (x1 - x0, y1 - y0);
241 work.width = w;
242 work.height = h;
243 work.data.clear();
244 let stride = self.width as usize * 4;
245 let row = w as usize * 4;
246 let mut opaque = true;
247 for y in y0..y1 {
248 let o = y as usize * stride + x0 as usize * 4;
249 let src = &image.data[o..o + row];
250 opaque &= src.chunks_exact(4).all(|p| p[3] == 255);
251 work.data.extend_from_slice(src);
252 }
253 let blend = opaque && self.options.lossless;
254 let mut plain = None;
255 if blend {
256 let mut copy = self.spare.pop().unwrap_or_else(|| Rgba::new(0, 0));
257 copy.width = w;
258 copy.height = h;
259 copy.data.clear();
260 copy.data.extend_from_slice(&work.data);
261 plain = Some(copy);
262 for y in y0..y1 {
263 let o = y as usize * stride + x0 as usize * 4;
264 let before = &prev.data[o..o + row];
265 let wo = (y - y0) as usize * row;
266 for (dst, old) in work.data[wo..wo + row]
267 .chunks_exact_mut(4)
268 .zip(before.chunks_exact(4))
269 {
270 if dst == old {
271 dst.copy_from_slice(&[0, 0, 0, 0]);
272 }
273 }
274 }
275 }
276 Job {
277 head: [x0, y0, w, h],
278 duration,
279 blend,
280 image: work,
281 plain,
282 }
283 }
284 };
285 match &mut self.prev {
286 Some(p) => p.data.copy_from_slice(&image.data),
287 None => self.prev = Some(image.clone()),
288 }
289 self.jobs.push(job);
290 if self.jobs.len() >= self.batch {
291 self.encode_jobs()?;
292 }
293 Ok(())
294 }
295
296 fn encode_jobs(&mut self) -> Result<()> {
297 let jobs = std::mem::take(&mut self.jobs);
298 let options = self.options;
299 type Out = Result<(Vec<u8>, bool)>;
300 let results: Vec<(Out, Option<Out>)> = std::thread::scope(|s| {
301 let handles: Vec<_> = jobs
302 .iter()
303 .map(|j| {
304 let image = &j.image;
305 let a = s.spawn(move || encode_piece(image, &options));
306 let b = j
307 .plain
308 .as_ref()
309 .map(|p| s.spawn(move || encode_piece(p, &options)));
310 (a, b)
311 })
312 .collect();
313 let join = |h: std::thread::ScopedJoinHandle<'_, Out>| {
314 h.join()
315 .unwrap_or_else(|_| Err(invalid("frame encode panicked")))
316 };
317 handles
318 .into_iter()
319 .map(|(a, b)| (join(a), b.map(join)))
320 .collect()
321 });
322 for (mut job, (blended, plain)) in jobs.into_iter().zip(results) {
323 let (mut data, mut alpha) = blended?;
324 if let Some(p) = plain {
325 let (pd, pa) = p?;
326 if pd.len() < data.len() {
327 data = pd;
328 alpha = pa;
329 job.blend = false;
330 }
331 }
332 self.pieces.push(Encoded {
333 head: job.head,
334 duration: job.duration,
335 blend: job.blend,
336 alpha,
337 data,
338 });
339 self.spare.push(job.image);
340 if let Some(p) = job.plain {
341 self.spare.push(p);
342 }
343 }
344 Ok(())
345 }
346
347 pub fn finish(mut self) -> Result<Vec<u8>> {
348 self.encode_jobs()?;
349 if self.pieces.is_empty() {
350 return Err(invalid("animation without frames"));
351 }
352 let alpha = self.pieces.iter().any(|p| p.alpha);
353 let mut w = riff::Writer::new();
354 let mut x = Vec::with_capacity(10);
355 x.extend_from_slice(&[0x02 | if alpha { 0x10 } else { 0 }, 0, 0, 0]);
356 riff::put_u24(&mut x, self.width - 1);
357 riff::put_u24(&mut x, self.height - 1);
358 w.chunk(b"VP8X", &x);
359 let b = self.background;
360 let mut anim = vec![b[2], b[1], b[0], b[3]];
361 anim.extend_from_slice(&self.loop_count.to_le_bytes());
362 w.chunk(b"ANIM", &anim);
363 let mut f = Vec::new();
364 for p in &self.pieces {
365 f.clear();
366 riff::put_u24(&mut f, p.head[0] / 2);
367 riff::put_u24(&mut f, p.head[1] / 2);
368 riff::put_u24(&mut f, p.head[2] - 1);
369 riff::put_u24(&mut f, p.head[3] - 1);
370 riff::put_u24(&mut f, p.duration);
371 f.push(if p.blend { 0 } else { 0x02 });
372 f.extend_from_slice(&p.data);
373 w.chunk(b"ANMF", &f);
374 }
375 w.finish()
376 }
377}
378
379fn changed(a: &Rgba, b: &Rgba) -> Option<(u32, u32, u32, u32)> {
380 let w = a.width as usize;
381 let rows_a = a.data.chunks_exact(w * 4);
382 let mut y0 = None;
383 let mut y1 = 0;
384 let mut x0 = w;
385 let mut x1 = 0;
386 for (y, (ra, rb)) in rows_a.zip(b.data.chunks_exact(w * 4)).enumerate() {
387 if ra == rb {
388 continue;
389 }
390 y0.get_or_insert(y);
391 y1 = y + 1;
392 let pa = ra.chunks_exact(4);
393 let first = pa
394 .clone()
395 .zip(rb.chunks_exact(4))
396 .position(|(p, q)| p != q)
397 .unwrap();
398 let last = w - pa
399 .rev()
400 .zip(rb.chunks_exact(4).rev())
401 .position(|(p, q)| p != q)
402 .unwrap();
403 x0 = x0.min(first);
404 x1 = x1.max(last);
405 }
406 y0.map(|y0| (x0 as u32, y0 as u32, x1 as u32, y1 as u32))
407}