Skip to main content

pito_codecs/webp/
anim.rs

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}