1mod compositor;
23
24use std::collections::BTreeMap;
25use std::io::Cursor;
26use std::sync::{Arc, Mutex, MutexGuard, PoisonError};
27
28use alpha2_time::{MediaTime, TICKS_PER_SECOND};
29
30use alpha2_model::ProviderSource;
31
32use crate::api::{
33 Diagnostic, ExtensionError, Format, FramePixels, MAX_NORMALIZED_DELAY_MS, NORMALIZED_DELAY_MS,
34 ProbeReport, ResourceResolver, Timing, VisualFrame, VisualProvider, VisualSource, premultiply,
35};
36use crate::budget::{
37 DEFAULT_FRAME_BUDGET, MAX_ANIMATION_FRAMES, MAX_ANIMATION_PIXELS, MAX_CANVAS_PIXELS,
38 check_canvas,
39};
40use compositor::{Blend, Compositor, Dispose, PartialFrame};
41
42pub struct AnimatedImageProvider;
45
46impl VisualProvider for AnimatedImageProvider {
47 fn name(&self) -> &'static str {
48 "animated-image"
49 }
50
51 fn probe(&self, bytes: &[u8]) -> Option<Result<ProbeReport, ExtensionError>> {
52 let format = sniff(bytes)?;
53 Some(index(format, bytes).map(|index| index.report(format)))
54 .filter(|report| !matches!(report, Ok(report) if report.timing.frame_count() < 2))
55 }
56
57 fn open(
58 &self,
59 bytes: Arc<[u8]>,
60 _source: &ProviderSource,
61 _resolver: &dyn ResourceResolver,
62 ) -> Result<Box<dyn VisualSource>, ExtensionError> {
63 Ok(Box::new(AnimatedSource::open(bytes, DEFAULT_FRAME_BUDGET)?))
64 }
65}
66
67fn sniff(bytes: &[u8]) -> Option<Format> {
70 if bytes.starts_with(b"GIF87a") || bytes.starts_with(b"GIF89a") {
71 return Some(Format::Gif);
72 }
73 if bytes.starts_with(b"\x89PNG\r\n\x1a\n") {
74 return png_is_animated(bytes).then_some(Format::Apng);
75 }
76 if bytes.len() >= 21 && &bytes[..4] == b"RIFF" && &bytes[8..12] == b"WEBP" {
77 return (&bytes[12..16] == b"VP8X" && bytes[20] & 0x02 != 0).then_some(Format::Webp);
79 }
80 None
81}
82
83fn png_is_animated(bytes: &[u8]) -> bool {
87 let mut at = 8_usize;
88 while bytes.len().saturating_sub(at) >= 8 {
89 let length = u32::from_be_bytes([bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]]);
90 let kind = &bytes[at + 4..at + 8];
91 if kind == b"acTL" {
92 return true;
93 }
94 if kind == b"IDAT" {
95 return false;
96 }
97 let Some(next) = usize::try_from(length)
98 .ok()
99 .and_then(|length| at.checked_add(12)?.checked_add(length))
100 else {
101 return false;
102 };
103 at = next;
104 }
105 false
106}
107
108struct Index {
111 width: u32,
112 height: u32,
113 loop_count: Option<u32>,
114 frames: Vec<Delay>,
116 pixels: u64,
118}
119
120impl Index {
121 fn new(width: u32, height: u32) -> Result<Self, ExtensionError> {
123 check_canvas(width, height)?;
124 Ok(Self {
125 width,
126 height,
127 loop_count: None,
128 frames: Vec::new(),
129 pixels: 0,
130 })
131 }
132
133 fn push(&mut self, width: u32, height: u32, delay: Delay) -> Result<(), ExtensionError> {
136 if self.frames.len() == MAX_ANIMATION_FRAMES {
137 return Err(ExtensionError::Budget(format!(
138 "more than {MAX_ANIMATION_FRAMES} frames"
139 )));
140 }
141 let pixels = u64::from(width) * u64::from(height);
142 if pixels > MAX_CANVAS_PIXELS {
143 return Err(ExtensionError::Budget(format!(
144 "a {width}x{height} frame is larger than {MAX_CANVAS_PIXELS} pixels"
145 )));
146 }
147 self.pixels += pixels;
148 if self.pixels > MAX_ANIMATION_PIXELS {
149 return Err(ExtensionError::Budget(format!(
150 "the frames together cover more than {MAX_ANIMATION_PIXELS} pixels"
151 )));
152 }
153 self.frames.push(delay);
154 Ok(())
155 }
156
157 fn report(&self, format: Format) -> ProbeReport {
158 let mut diagnostics = Vec::new();
159 let durations = self
160 .frames
161 .iter()
162 .enumerate()
163 .map(|(frame, delay)| {
164 if delay.ticks <= i64::from(MAX_NORMALIZED_DELAY_MS) * TICKS_PER_MS {
165 diagnostics.push(Diagnostic::NormalizedDelay {
166 frame,
167 declared_ms: delay.ms,
168 });
169 i64::from(NORMALIZED_DELAY_MS) * TICKS_PER_MS
170 } else {
171 delay.ticks
172 }
173 })
174 .collect();
175 ProbeReport {
176 format,
177 width: self.width,
178 height: self.height,
179 timing: Timing::Frames { durations },
180 loop_count: self.loop_count,
181 items: Vec::new(),
182 item: None,
183 color: None,
184 diagnostics,
185 }
186 }
187}
188
189fn index(format: Format, bytes: &[u8]) -> Result<Index, ExtensionError> {
191 let index = match format {
192 Format::Gif => gif_index(bytes)?,
193 Format::Apng => apng_index(bytes)?,
194 Format::Webp => WebpLayout::read(bytes)?.index()?,
195 other => {
196 return Err(ExtensionError::Unsupported(format!(
197 "{other:?} is not an animation"
198 )));
199 }
200 };
201 if index.frames.is_empty() {
202 return Err(ExtensionError::Malformed(
203 "the animation has no frames".into(),
204 ));
205 }
206 Ok(index)
207}
208
209fn gif_index(bytes: &[u8]) -> Result<Index, ExtensionError> {
210 let mut options = gif_options();
211 options.skip_frame_decoding(true);
212 let mut decoder = options.read_info(Cursor::new(bytes)).map_err(malformed)?;
213 let mut index = Index::new(u32::from(decoder.width()), u32::from(decoder.height()))?;
214 while let Some(frame) = decoder.next_frame_info().map_err(malformed)? {
215 let (width, height) = (u32::from(frame.width), u32::from(frame.height));
216 let delay = Delay {
218 ticks: i64::from(frame.delay) * 10 * TICKS_PER_MS,
219 ms: u32::from(frame.delay) * 10,
220 };
221 index.push(width, height, delay)?;
222 }
223 index.loop_count = Some(match decoder.repeat() {
227 gif::Repeat::Infinite => 0,
228 gif::Repeat::Finite(count) => u32::from(count) + 1,
229 });
230 Ok(index)
231}
232
233fn apng_index(bytes: &[u8]) -> Result<Index, ExtensionError> {
234 let mut reader = png::Decoder::new(Cursor::new(bytes))
235 .read_info()
236 .map_err(malformed)?;
237 let info = reader.info();
238 let mut index = Index::new(info.width, info.height)?;
239 index.loop_count = info.animation_control.map(|control| control.num_plays);
240 if let Some(control) = info.frame_control {
242 index.push(control.width, control.height, apng_delay(&control))?;
243 }
244 loop {
245 let control = match reader.next_frame_info() {
247 Ok(control) => *control,
248 Err(png::DecodingError::Parameter(_)) => break,
249 Err(error) => return Err(malformed(error)),
250 };
251 index.push(control.width, control.height, apng_delay(&control))?;
252 }
253 Ok(index)
254}
255
256fn apng_delay(control: &png::FrameControl) -> Delay {
261 let den = if control.delay_den == 0 {
262 100
263 } else {
264 i64::from(control.delay_den)
265 };
266 let num = i64::from(control.delay_num);
267 let rounded = |per_second: i64| (num * per_second + den / 2) / den;
268 Delay {
269 ticks: rounded(TICKS_PER_SECOND),
270 ms: u32::try_from(rounded(1_000)).unwrap_or(u32::MAX),
271 }
272}
273
274#[derive(Clone, Copy)]
276struct Delay {
277 ticks: i64,
279 ms: u32,
281}
282
283const TICKS_PER_MS: i64 = TICKS_PER_SECOND / 1_000;
285
286struct Kept {
289 frames: BTreeMap<usize, (Arc<[u8]>, u64)>,
291 by_use: BTreeMap<u64, usize>,
293 clock: u64,
294 frame_bytes: usize,
295 budget: usize,
296}
297
298impl Kept {
299 fn new(frame_bytes: usize, budget: usize) -> Self {
300 Self {
301 frames: BTreeMap::new(),
302 by_use: BTreeMap::new(),
303 clock: 0,
304 frame_bytes,
305 budget,
306 }
307 }
308
309 fn capacity(&self) -> usize {
311 self.budget / self.frame_bytes.max(1)
312 }
313
314 fn bytes(&self) -> usize {
315 self.frames.len() * self.frame_bytes
316 }
317
318 fn contains(&self, index: usize) -> bool {
319 self.frames.contains_key(&index)
320 }
321
322 fn get(&mut self, index: usize) -> Option<Arc<[u8]>> {
324 self.clock += 1;
325 let (canvas, used) = self.frames.get_mut(&index)?;
326 self.by_use.remove(used);
327 *used = self.clock;
328 self.by_use.insert(self.clock, index);
329 Some(Arc::clone(canvas))
330 }
331
332 fn insert(&mut self, index: usize, canvas: Arc<[u8]>) {
335 if self.capacity() == 0 {
336 return;
337 }
338 if let Some((_, used)) = self.frames.remove(&index) {
339 self.by_use.remove(&used);
340 }
341 while self.frames.len() >= self.capacity() {
342 let Some((_, oldest)) = self.by_use.pop_first() else {
343 break;
344 };
345 self.frames.remove(&oldest);
346 }
347 self.clock += 1;
348 self.frames.insert(index, (canvas, self.clock));
349 self.by_use.insert(self.clock, index);
350 }
351}
352
353fn decode_whole(
356 format: Format,
357 bytes: &Arc<[u8]>,
358 index: &Index,
359 budget: usize,
360) -> Result<Kept, ExtensionError> {
361 let mut cursor = open_cursor(format, bytes)?;
362 let mut kept = Kept::new(index.width as usize * index.height as usize * 4, budget);
363 for at in 0..index.frames.len() {
364 if !cursor.advance()? {
365 return Err(ExtensionError::Malformed(
366 "the animation ended before a frame it listed".into(),
367 ));
368 }
369 crate::progress::progressed();
370 if at < kept.capacity() {
371 kept.insert(at, premultiplied(cursor.canvas()));
372 }
373 }
374 Ok(kept)
375}
376
377struct Animation {
380 format: Format,
381 bytes: Arc<[u8]>,
382 report: ProbeReport,
383 frames: Mutex<Kept>,
386}
387
388impl Animation {
389 fn kept(&self) -> Result<MutexGuard<'_, Kept>, ExtensionError> {
392 self.frames.lock().map_err(|_| {
393 ExtensionError::Malformed(
394 "another reader of this animation failed while keeping its frames".into(),
395 )
396 })
397 }
398
399 fn kept_bytes(&self) -> usize {
401 self.frames
402 .lock()
403 .unwrap_or_else(PoisonError::into_inner)
404 .bytes()
405 }
406}
407
408struct AnimatedSource {
411 shared: Arc<Animation>,
412 cursor: Option<(Box<dyn FrameCursor>, usize)>,
414}
415
416impl AnimatedSource {
417 fn open(bytes: Arc<[u8]>, budget: usize) -> Result<Self, ExtensionError> {
419 let format = sniff(&bytes)
420 .ok_or_else(|| ExtensionError::Mismatch("not an animated GIF, WebP, or APNG".into()))?;
421 let index = index(format, &bytes)?;
422 if index.frames.len() < 2 {
423 return Err(ExtensionError::Mismatch(
424 "a still image, which the host's image decoders read".into(),
425 ));
426 }
427 let frames = decode_whole(format, &bytes, &index, budget)?;
428 Ok(Self {
429 shared: Arc::new(Animation {
430 format,
431 bytes,
432 report: index.report(format),
433 frames: Mutex::new(frames),
434 }),
435 cursor: None,
436 })
437 }
438
439 fn frame(&mut self, index: usize) -> Result<Arc<[u8]>, ExtensionError> {
446 let shared = Arc::clone(&self.shared);
447 let capacity = {
448 let mut kept = shared.kept()?;
449 if let Some(frame) = kept.get(index) {
450 return Ok(frame);
451 }
452 kept.capacity()
453 };
454 let restart = self.cursor.as_ref().is_none_or(|(_, next)| *next > index);
455 if restart {
456 self.cursor = Some((open_cursor(shared.format, &shared.bytes)?, 0));
457 }
458 let window = (index + 1).saturating_sub(capacity);
459 loop {
460 let (cursor, next) = self.cursor.as_mut().ok_or_else(|| {
461 ExtensionError::Malformed("the animation's decoder is gone".into())
462 })?;
463 if !cursor.advance()? {
464 return Err(ExtensionError::Malformed(
465 "the animation ended before a frame it listed".into(),
466 ));
467 }
468 crate::progress::progressed();
469 let at = *next;
470 *next += 1;
471 if at != index && (at < window || shared.kept()?.contains(at)) {
473 continue;
474 }
475 let canvas = premultiplied(cursor.canvas());
476 shared.kept()?.insert(at, Arc::clone(&canvas));
477 if at == index {
478 return Ok(canvas);
479 }
480 }
481 }
482}
483
484impl VisualSource for AnimatedSource {
485 fn report(&self) -> &ProbeReport {
486 &self.shared.report
487 }
488
489 fn render(&mut self, time: MediaTime, _fit: (u32, u32)) -> Result<VisualFrame, ExtensionError> {
490 let (index, start, end) = self.shared.report.timing.frame_at(time);
491 let pixels = self.frame(index)?;
492 Ok(VisualFrame {
493 width: self.shared.report.width,
494 height: self.shared.report.height,
495 pixels: FramePixels::Rgba8(pixels),
496 index,
497 start,
498 end,
499 diagnostics: Vec::new(),
500 })
501 }
502
503 fn resident_bytes(&self) -> usize {
504 let canvas = self.shared.report.width as usize * self.shared.report.height as usize * 4;
505 self.shared_bytes() + self.cursor.as_ref().map_or(0, |_| canvas)
508 }
509
510 fn reader(&self) -> Option<Box<dyn VisualSource>> {
513 Some(Box::new(Self {
514 shared: Arc::clone(&self.shared),
515 cursor: None,
516 }))
517 }
518
519 fn shared_bytes(&self) -> usize {
521 self.shared.bytes.len() + self.shared.kept_bytes()
522 }
523}
524
525fn premultiplied(canvas: &[u8]) -> Arc<[u8]> {
527 let mut shared = Arc::<[u8]>::from(canvas);
528 if let Some(pixels) = Arc::get_mut(&mut shared) {
529 premultiply(pixels);
530 }
531 shared
532}
533
534trait FrameCursor: Send {
536 fn advance(&mut self) -> Result<bool, ExtensionError>;
538 fn canvas(&self) -> &[u8];
540}
541
542fn open_cursor(format: Format, bytes: &Arc<[u8]>) -> Result<Box<dyn FrameCursor>, ExtensionError> {
545 let bytes = Arc::clone(bytes);
546 match format {
547 Format::Gif => Ok(Box::new(GifCursor::new(bytes)?)),
548 Format::Apng => Ok(Box::new(ApngCursor::new(bytes)?)),
549 Format::Webp => Ok(Box::new(WebpCursor::new(bytes)?)),
550 other => Err(ExtensionError::Unsupported(format!(
551 "{other:?} is not an animation"
552 ))),
553 }
554}
555
556fn gif_options() -> gif::DecodeOptions {
559 let mut options = gif::DecodeOptions::new();
560 options.set_color_output(gif::ColorOutput::RGBA);
561 options.set_memory_limit(gif::MemoryLimit::Bytes(
562 std::num::NonZeroU64::new(MAX_CANVAS_PIXELS * 4).unwrap_or(std::num::NonZeroU64::MIN),
563 ));
564 options
565}
566
567fn malformed(error: impl std::fmt::Display) -> ExtensionError {
568 ExtensionError::Malformed(error.to_string())
569}
570
571struct GifCursor {
572 decoder: gif::Decoder<Cursor<Arc<[u8]>>>,
573 compositor: Compositor,
574}
575
576impl GifCursor {
577 fn new(bytes: Arc<[u8]>) -> Result<Self, ExtensionError> {
578 let decoder = gif_options()
579 .read_info(Cursor::new(bytes))
580 .map_err(malformed)?;
581 let compositor = Compositor::new(u32::from(decoder.width()), u32::from(decoder.height()))?;
582 Ok(Self {
583 decoder,
584 compositor,
585 })
586 }
587}
588
589impl FrameCursor for GifCursor {
590 fn canvas(&self) -> &[u8] {
591 self.compositor.canvas()
592 }
593
594 fn advance(&mut self) -> Result<bool, ExtensionError> {
595 let Some(frame) = self.decoder.read_next_frame().map_err(malformed)? else {
596 return Ok(false);
597 };
598 let partial = PartialFrame {
599 x: u32::from(frame.left),
600 y: u32::from(frame.top),
601 width: u32::from(frame.width),
602 height: u32::from(frame.height),
603 pixels: frame.buffer.to_vec(),
604 blend: Blend::Over,
606 dispose: match frame.dispose {
607 gif::DisposalMethod::Background => Dispose::Background,
608 gif::DisposalMethod::Previous => Dispose::Previous,
609 _ => Dispose::None,
610 },
611 };
612 self.compositor.draw(&partial)?;
613 Ok(true)
614 }
615}
616
617struct ApngCursor {
618 reader: png::Reader<Cursor<Arc<[u8]>>>,
619 compositor: Compositor,
620 skip_default: bool,
622 first: bool,
624 buffer: Vec<u8>,
625}
626
627impl ApngCursor {
628 fn new(bytes: Arc<[u8]>) -> Result<Self, ExtensionError> {
629 let mut decoder = png::Decoder::new(Cursor::new(bytes));
630 decoder.set_transformations(
631 png::Transformations::EXPAND
632 | png::Transformations::STRIP_16
633 | png::Transformations::ALPHA,
634 );
635 let reader = decoder.read_info().map_err(malformed)?;
636 let info = reader.info();
637 let compositor = Compositor::new(info.width, info.height)?;
638 let skip_default = info.frame_control.is_none();
639 let size = reader
640 .output_buffer_size()
641 .ok_or_else(|| ExtensionError::Budget("the PNG is too large".into()))?;
642 Ok(Self {
643 reader,
644 compositor,
645 skip_default,
646 first: true,
647 buffer: vec![0; size],
648 })
649 }
650}
651
652impl FrameCursor for ApngCursor {
653 fn canvas(&self) -> &[u8] {
654 self.compositor.canvas()
655 }
656
657 fn advance(&mut self) -> Result<bool, ExtensionError> {
658 loop {
659 let output = match self.reader.next_frame(&mut self.buffer) {
660 Ok(output) => output,
661 Err(png::DecodingError::Parameter(_)) => return Ok(false),
662 Err(error) => return Err(malformed(error)),
663 };
664 if std::mem::take(&mut self.skip_default) {
665 continue;
666 }
667 let control = self.reader.info().frame_control.ok_or_else(|| {
668 ExtensionError::Malformed("an APNG frame has no frame control".into())
669 })?;
670 let pixels = crate::raster::to_rgba8(
671 &self.buffer[..output.buffer_size()],
672 output.color_type,
673 output.width,
674 output.height,
675 output.line_size,
676 )?;
677 let first = std::mem::take(&mut self.first);
678 let partial = PartialFrame {
679 x: control.x_offset,
680 y: control.y_offset,
681 width: control.width,
682 height: control.height,
683 pixels,
684 blend: match control.blend_op {
685 png::BlendOp::Over => Blend::Over,
686 png::BlendOp::Source => Blend::Source,
687 },
688 dispose: match control.dispose_op {
689 png::DisposeOp::None => Dispose::None,
690 png::DisposeOp::Background => Dispose::Background,
691 png::DisposeOp::Previous if first => Dispose::Background,
693 png::DisposeOp::Previous => Dispose::Previous,
694 },
695 };
696 self.compositor.draw(&partial)?;
697 return Ok(true);
698 }
699 }
700}
701
702struct WebpCursor {
705 bytes: Arc<[u8]>,
706 frames: Vec<WebpFrame>,
707 next: usize,
708 compositor: Compositor,
709}
710
711struct WebpLayout {
713 width: u32,
714 height: u32,
715 loop_count: Option<u32>,
717 frames: Vec<WebpFrame>,
718}
719
720struct WebpFrame {
722 x: u32,
723 y: u32,
724 width: u32,
725 height: u32,
726 duration_ms: u32,
727 blend: Blend,
728 dispose: Dispose,
729 data: std::ops::Range<usize>,
730}
731
732fn riff_chunks(bytes: &[u8], mut at: usize, end: usize) -> Vec<([u8; 4], std::ops::Range<usize>)> {
734 let mut chunks = Vec::new();
735 while at + 8 <= end {
736 let kind = [bytes[at], bytes[at + 1], bytes[at + 2], bytes[at + 3]];
737 let size = u32::from_le_bytes([bytes[at + 4], bytes[at + 5], bytes[at + 6], bytes[at + 7]])
738 as usize;
739 let start = at + 8;
740 let Some(stop) = start.checked_add(size).filter(|stop| *stop <= end) else {
741 break;
742 };
743 chunks.push((kind, start..stop));
744 at = stop + (size & 1);
745 }
746 chunks
747}
748
749fn le24(bytes: &[u8]) -> u32 {
750 u32::from(bytes[0]) | u32::from(bytes[1]) << 8 | u32::from(bytes[2]) << 16
751}
752
753impl WebpLayout {
754 fn read(bytes: &[u8]) -> Result<Self, ExtensionError> {
755 let end = bytes.len();
756 let chunks = riff_chunks(bytes, 12, end);
757 let header = chunks
758 .iter()
759 .find(|(kind, _)| kind == b"VP8X")
760 .filter(|(_, range)| range.len() >= 10)
761 .ok_or_else(|| ExtensionError::Malformed("the WebP has no extended header".into()))?;
762 let width = le24(&bytes[header.1.start + 4..]) + 1;
763 let height = le24(&bytes[header.1.start + 7..]) + 1;
764 let loop_count = chunks
765 .iter()
766 .find(|(kind, range)| kind == b"ANIM" && range.len() >= 6)
767 .map(|(_, range)| {
768 u32::from(u16::from_le_bytes([
769 bytes[range.start + 4],
770 bytes[range.start + 5],
771 ]))
772 });
773 let frames = chunks
774 .iter()
775 .filter(|(kind, range)| kind == b"ANMF" && range.len() >= 16)
776 .map(|(_, range)| {
777 let at = range.start;
778 let flags = bytes[at + 15];
779 WebpFrame {
780 x: le24(&bytes[at..]) * 2,
781 y: le24(&bytes[at + 3..]) * 2,
782 width: le24(&bytes[at + 6..]) + 1,
783 height: le24(&bytes[at + 9..]) + 1,
784 duration_ms: le24(&bytes[at + 12..]),
785 blend: if flags & 0x02 == 0 {
786 Blend::OverWebp
787 } else {
788 Blend::Source
789 },
790 dispose: if flags & 0x01 == 0 {
791 Dispose::None
792 } else {
793 Dispose::Background
794 },
795 data: at + 16..range.end,
796 }
797 })
798 .collect();
799 Ok(Self {
800 width,
801 height,
802 loop_count,
803 frames,
804 })
805 }
806
807 fn index(&self) -> Result<Index, ExtensionError> {
809 let mut index = Index::new(self.width, self.height)?;
810 index.loop_count = self.loop_count;
811 for frame in &self.frames {
812 if frame.x + frame.width > self.width || frame.y + frame.height > self.height {
813 return Err(ExtensionError::Malformed(format!(
814 "a {}x{} frame at ({}, {}) lies outside the {}x{} canvas",
815 frame.width, frame.height, frame.x, frame.y, self.width, self.height
816 )));
817 }
818 let delay = Delay {
819 ticks: i64::from(frame.duration_ms) * TICKS_PER_MS,
820 ms: frame.duration_ms,
821 };
822 index.push(frame.width, frame.height, delay)?;
823 }
824 Ok(index)
825 }
826}
827
828impl WebpCursor {
829 fn new(bytes: Arc<[u8]>) -> Result<Self, ExtensionError> {
830 let layout = WebpLayout::read(&bytes)?;
831 let compositor = Compositor::new(layout.width, layout.height)?;
832 Ok(Self {
833 bytes,
834 frames: layout.frames,
835 next: 0,
836 compositor,
837 })
838 }
839
840 fn picture(&self, frame: &WebpFrame) -> Result<Vec<u8>, ExtensionError> {
843 let chunks = riff_chunks(&self.bytes, frame.data.start, frame.data.end);
844 let wanted = |kind: &[u8; 4]| chunks.iter().find(|(chunk, _)| chunk == kind);
845 let alpha = wanted(b"ALPH");
846 let lossy = wanted(b"VP8 ");
847 let lossless = wanted(b"VP8L");
848 let mut body = Vec::new();
849 let mut push = |kind: &[u8; 4], payload: &[u8]| {
850 body.extend_from_slice(kind);
851 body.extend_from_slice(
852 &u32::try_from(payload.len())
853 .unwrap_or(u32::MAX)
854 .to_le_bytes(),
855 );
856 body.extend_from_slice(payload);
857 if payload.len() % 2 == 1 {
858 body.push(0);
859 }
860 };
861 match (lossless, lossy) {
862 (Some((_, range)), _) => push(b"VP8L", &self.bytes[range.clone()]),
863 (None, Some((_, range))) => {
864 let mut header = vec![0_u8; 10];
866 if alpha.is_some() {
867 header[0] = 0x10;
868 }
869 header[4..7].copy_from_slice(&(frame.width - 1).to_le_bytes()[..3]);
870 header[7..10].copy_from_slice(&(frame.height - 1).to_le_bytes()[..3]);
871 push(b"VP8X", &header);
872 if let Some((_, range)) = alpha {
873 push(b"ALPH", &self.bytes[range.clone()]);
874 }
875 push(b"VP8 ", &self.bytes[range.clone()]);
876 }
877 (None, None) => {
878 return Err(ExtensionError::Malformed(
879 "a WebP frame has no picture".into(),
880 ));
881 }
882 }
883 let mut file = b"RIFF".to_vec();
884 file.extend_from_slice(
885 &u32::try_from(body.len() + 4)
886 .unwrap_or(u32::MAX)
887 .to_le_bytes(),
888 );
889 file.extend_from_slice(b"WEBP");
890 file.extend_from_slice(&body);
891 let mut decoder = image_webp::WebPDecoder::new(Cursor::new(file)).map_err(malformed)?;
892 if decoder.dimensions() != (frame.width, frame.height) {
895 let (width, height) = decoder.dimensions();
896 return Err(ExtensionError::Malformed(format!(
897 "a frame declares {}x{} and its picture is {width}x{height}",
898 frame.width, frame.height
899 )));
900 }
901 let mut buffer =
902 vec![
903 0;
904 decoder
905 .output_buffer_size()
906 .ok_or_else(|| ExtensionError::Budget("a WebP frame is too large".into()))?
907 ];
908 decoder.read_image(&mut buffer).map_err(malformed)?;
909 Ok(if decoder.has_alpha() {
910 buffer
911 } else {
912 buffer
913 .as_chunks::<3>()
914 .0
915 .iter()
916 .flat_map(|[red, green, blue]| [*red, *green, *blue, 255])
917 .collect()
918 })
919 }
920}
921
922impl FrameCursor for WebpCursor {
923 fn canvas(&self) -> &[u8] {
924 self.compositor.canvas()
925 }
926
927 fn advance(&mut self) -> Result<bool, ExtensionError> {
928 let Some(frame) = self.frames.get(self.next) else {
929 return Ok(false);
930 };
931 let partial = PartialFrame {
932 x: frame.x,
933 y: frame.y,
934 width: frame.width,
935 height: frame.height,
936 pixels: self.picture(frame)?,
937 blend: frame.blend,
938 dispose: frame.dispose,
939 };
940 self.next += 1;
941 self.compositor.draw(&partial)?;
942 Ok(true)
943 }
944}
945
946#[cfg(test)]
947mod tests;