use alloc::borrow::Cow;
use crate::container::anim::{ANMF_HEADER_LEN, AnimChunk, AnmfHeader};
use crate::container::fourcc::FourCc;
use crate::container::reader::{Chunks, body_range, read_chunk_at};
use crate::container::vp8x::Vp8xInfo;
use crate::error::{Error, Result};
use crate::image::{self, Dimensions, Image, Metadata, PixelLayout};
use crate::prelude::*;
use crate::stream::{DecodeOptions, FrameDecoder, FramePayload};
pub use crate::container::anim::{BlendMode, DisposalMode, FrameMeta};
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
pub struct AnimInfo {
pub canvas: Dimensions,
pub background_rgba: [u8; 4],
pub loop_count: u16,
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct Frame {
meta: FrameMeta,
image: Image,
}
impl Frame {
#[must_use]
pub const fn meta(&self) -> FrameMeta {
self.meta
}
#[must_use]
pub const fn image(&self) -> &Image {
&self.image
}
#[must_use]
pub fn into_image(self) -> Image {
self.image
}
}
#[derive(Clone, PartialEq, Eq, Debug)]
pub struct CompositedFrame {
image: Image,
duration_ms: u32,
}
impl CompositedFrame {
#[must_use]
pub const fn duration_ms(&self) -> u32 {
self.duration_ms
}
#[must_use]
pub const fn image(&self) -> &Image {
&self.image
}
#[must_use]
pub fn into_image(self) -> Image {
self.image
}
}
#[derive(Clone, Debug)]
pub struct Frames<'a, D> {
data: Cow<'a, [u8]>,
cursor: usize,
body_end: usize,
anim: AnimInfo,
options: DecodeOptions,
decoder: D,
}
impl<'a, D: FrameDecoder + Clone> Frames<'a, D> {
pub fn new(data: Cow<'a, [u8]>, options: DecodeOptions, decoder: D) -> Result<Self> {
let (mut offset, body_end) = body_range(&data)?;
let body = &data[..body_end];
let mut canvas = None;
let mut anim = None;
let mut first_frame = None;
while offset < body_end {
let Some((chunk, next)) = read_chunk_at(body, offset)? else {
break;
};
match chunk.id {
FourCc::VP8X => {
let info = Vp8xInfo::parse(chunk.data)?;
if !info.flags.is_animated() {
return Err(Error::UnsupportedFeature);
}
canvas = Some(info.canvas);
},
FourCc::ANIM => anim = Some(AnimChunk::parse(chunk.data)?),
FourCc::ANMF => {
first_frame = Some(offset);
break;
},
FourCc::VP8 => return Err(Error::UnsupportedFeature),
_ => {},
}
offset = next;
}
let canvas = canvas.ok_or(Error::UnsupportedFeature)?;
let anim = anim.ok_or(Error::InvalidContainer)?;
let cursor = first_frame.ok_or(Error::MissingImage)?;
let canvas_pixels = canvas.pixel_count();
if let Some(limit) = options.max_pixels.filter(|&l| canvas_pixels > l) {
return Err(Error::LimitExceeded {
pixels: canvas_pixels,
limit,
});
}
let anim = AnimInfo {
canvas,
background_rgba: PixelLayout::Rgba8.pack(anim.background),
loop_count: anim.loop_count,
};
Ok(Self {
data,
cursor,
body_end,
anim,
options,
decoder,
})
}
#[must_use]
pub const fn anim_info(&self) -> AnimInfo {
self.anim
}
#[must_use]
pub fn composited(self) -> CompositedFrames<'a, D> {
let compositor = Compositor::new(self.anim.canvas, self.options.layout);
CompositedFrames {
frames: self,
compositor,
}
}
fn next_raw(&mut self) -> Option<Result<(AnmfHeader, bool, Vec<u32>)>> {
while self.cursor < self.body_end {
let body = &self.data[..self.body_end];
let (chunk_id, chunk_data, next) = match read_chunk_at(body, self.cursor) {
Ok(Some((chunk, next))) => (chunk.id, chunk.data, next),
Ok(None) => return None,
Err(err) => {
self.cursor = self.body_end;
return Some(Err(err));
},
};
self.cursor = next;
if chunk_id == FourCc::ANMF {
return Some(decode_anmf(chunk_data, &self.decoder, &self.options));
}
}
None
}
fn make_frame(&self, header: AnmfHeader, argb: &[u32]) -> Frame {
let pixels = image::pack_pixels(self.options.layout, argb);
let has_alpha = image::argb_has_alpha(argb);
let image = Image::from_parts(
header.dims,
self.options.layout,
pixels,
has_alpha,
Metadata::none(),
);
Frame {
meta: frame_meta(header),
image,
}
}
}
pub fn decode_anmf<D: FrameDecoder>(
data: &[u8],
decoder: &D,
options: &DecodeOptions,
) -> Result<(AnmfHeader, bool, Vec<u32>)> {
let header = AnmfHeader::parse(data)?;
let frame_data = &data[ANMF_HEADER_LEN..];
let mut vp8l = None;
let mut vp8 = None;
let mut alph = None;
for chunk in Chunks::walk(frame_data) {
let chunk = chunk?;
match chunk.id {
FourCc::VP8L if vp8l.is_none() => vp8l = Some(chunk.data),
FourCc::VP8 if vp8.is_none() => vp8 = Some(chunk.data),
FourCc::ALPH if alph.is_none() => alph = Some(chunk.data),
_ => {}, }
}
let payload = FramePayload {
vp8l,
vp8,
alph,
dims: header.dims,
};
let decoded_frame = decoder.decode_frame(payload, options)?;
Ok((header, decoded_frame.alpha_used, decoded_frame.argb))
}
impl<D: FrameDecoder + Clone> Iterator for Frames<'_, D> {
type Item = Result<Frame>;
fn next(&mut self) -> Option<Self::Item> {
match self.next_raw()? {
Ok((header, _alpha_used, argb)) => Some(Ok(self.make_frame(header, &argb))),
Err(err) => Some(Err(err)),
}
}
}
#[derive(Clone, Copy, Debug)]
struct PrevFrame {
rect: (u32, u32, u32, u32),
dispose_background: bool,
full_canvas: bool,
was_key: bool,
}
#[derive(Clone, Debug)]
pub struct CompositedFrames<'a, D> {
frames: Frames<'a, D>,
compositor: Compositor,
}
impl<D: FrameDecoder + Clone> CompositedFrames<'_, D> {
#[must_use]
pub const fn anim_info(&self) -> AnimInfo {
self.frames.anim
}
}
impl<D: FrameDecoder + Clone> Iterator for CompositedFrames<'_, D> {
type Item = Result<CompositedFrame>;
fn next(&mut self) -> Option<Self::Item> {
let (header, alpha_used, argb) = match self.frames.next_raw()? {
Ok(frame) => frame,
Err(err) => return Some(Err(err)),
};
let duration_ms = header.duration_ms;
match self.compositor.paint(header, alpha_used, &argb) {
Ok(image) => Some(Ok(CompositedFrame { image, duration_ms })),
Err(err) => Some(Err(err)),
}
}
}
#[derive(Clone, Debug)]
pub struct Compositor {
canvas_dims: Dimensions,
layout: PixelLayout,
canvas: Vec<u32>,
prev: Option<PrevFrame>,
}
impl Compositor {
#[must_use]
pub fn new(canvas_dims: Dimensions, layout: PixelLayout) -> Self {
let count = usize::try_from(canvas_dims.pixel_count()).unwrap_or(usize::MAX);
Self {
canvas_dims,
layout,
canvas: vec![0u32; count],
prev: None,
}
}
pub fn paint(&mut self, header: AnmfHeader, alpha_used: bool, argb: &[u32]) -> Result<Image> {
let canvas_dims = self.canvas_dims;
let (cw, ch) = (canvas_dims.width(), canvas_dims.height());
let (x, y, w, h) = (
header.x,
header.y,
header.dims.width(),
header.dims.height(),
);
if x + w > cw || y + h > ch {
return Err(Error::InvalidContainer);
}
if argb.len() as u64 != header.dims.pixel_count() {
return Err(Error::InvalidContainer);
}
let frame_has_alpha = alpha_used;
let full_canvas = x == 0 && y == 0 && w == cw && h == ch;
let overwrite = header.flags.do_not_blend();
let dispose_bg = header.flags.dispose_background();
let is_key = self.prev.is_none_or(|prev| {
((!frame_has_alpha || overwrite) && full_canvas)
|| (prev.dispose_background && (prev.full_canvas || prev.was_key))
});
if is_key {
self.canvas.fill(0);
}
let raw_overlap = self
.prev
.filter(|_| !is_key && !overwrite)
.and_then(|p| p.dispose_background.then_some(p.rect));
let (cw_us, w_us) = (cw as usize, w as usize);
for row in 0..h as usize {
let cy = y as usize + row;
let src = &argb[row * w_us..row * w_us + w_us];
let start = cy * cw_us + x as usize;
let dst = &mut self.canvas[start..start + w_us];
for (col, (out, &pixel)) in dst.iter_mut().zip(src).enumerate() {
let cx = x as usize + col;
let in_prev_rect = raw_overlap.is_some_and(|(px, py, pw, ph)| {
(px as usize..(px + pw) as usize).contains(&cx)
&& (py as usize..(py + ph) as usize).contains(&cy)
});
*out = if is_key || overwrite || in_prev_rect {
pixel
} else {
blend_over(pixel, *out)
};
}
}
let layout = self.layout;
let pixels = image::pack_pixels(layout, &self.canvas);
let has_alpha = image::argb_has_alpha(&self.canvas);
let image = Image::from_parts(canvas_dims, layout, pixels, has_alpha, Metadata::none());
if dispose_bg {
for row in 0..h as usize {
let start = (y as usize + row) * cw_us + x as usize;
self.canvas[start..start + w_us].fill(0);
}
}
self.prev = Some(PrevFrame {
rect: (x, y, w, h),
dispose_background: dispose_bg,
full_canvas,
was_key: is_key,
});
Ok(image)
}
}
#[allow(
clippy::cast_possible_truncation,
reason = "blended channels are provably in 0..=255 (libwebp BlendPixelNonPremult); \
the shift/mask arithmetic reproduces the reference bit for bit"
)]
#[must_use]
pub fn blend_over(src: u32, dst: u32) -> u32 {
let src_a = (src >> 24) & 0xff;
if src_a == 0xff {
return src; }
if src_a == 0 {
return dst; }
let dst_a = (dst >> 24) & 0xff;
let dst_factor_a = (dst_a * (256 - src_a)) >> 8;
let blend_a = src_a + dst_factor_a; let scale = (1u32 << 24) / blend_a;
let channel = |shift: u32| -> u32 {
let src_c = (src >> shift) & 0xff;
let dst_c = (dst >> shift) & 0xff;
let unscaled = src_c * src_a + dst_c * dst_factor_a;
(((u64::from(unscaled) * u64::from(scale)) >> 24) as u32) & 0xff
};
(blend_a << 24) | (channel(16) << 16) | (channel(8) << 8) | channel(0)
}
#[must_use]
pub const fn frame_meta(header: AnmfHeader) -> FrameMeta {
FrameMeta {
x: header.x,
y: header.y,
dimensions: header.dims,
duration_ms: header.duration_ms,
blend: if header.flags.do_not_blend() {
BlendMode::Overwrite
} else {
BlendMode::Blend
},
dispose: if header.flags.dispose_background() {
DisposalMode::Background
} else {
DisposalMode::Keep
},
}
}
pub fn decode_frames_with_decoder<'a, D: FrameDecoder + Clone>(
input: &'a [u8],
options: &DecodeOptions,
decoder: D,
) -> Result<Frames<'a, D>> {
Frames::new(Cow::Borrowed(input), options.clone(), decoder)
}