use alloc::vec::Vec;
#[allow(unused_imports)]
use whereat::at;
use super::demux::{BlendMethod, DisposeMethod, WebPDemuxer};
use super::error::{MuxError, MuxResult};
use crate::decoder::LoopCount;
use crate::encoder::{VecWriter, chunk_size, write_chunk};
#[derive(Debug, Clone)]
pub struct MuxFrame {
pub x_offset: u32,
pub y_offset: u32,
pub width: u32,
pub height: u32,
pub duration_ms: u32,
pub dispose: DisposeMethod,
pub blend: BlendMethod,
pub bitstream: Vec<u8>,
pub alpha_data: Option<Vec<u8>>,
pub is_lossless: bool,
}
pub struct WebPMux {
canvas_width: u32,
canvas_height: u32,
animation: Option<AnimationParams>,
frames: Vec<MuxFrame>,
single_image: Option<MuxFrame>,
icc_profile: Option<Vec<u8>>,
exif: Option<Vec<u8>>,
xmp: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
struct AnimationParams {
background_color: [u8; 4],
loop_count: LoopCount,
}
impl WebPMux {
pub fn new(width: u32, height: u32) -> Self {
Self {
canvas_width: width,
canvas_height: height,
animation: None,
frames: Vec::new(),
single_image: None,
icc_profile: None,
exif: None,
xmp: None,
}
}
#[track_caller]
pub fn from_data(data: &[u8]) -> MuxResult<Self> {
let demuxer = WebPDemuxer::new(data)?;
let mut mux = Self::new(demuxer.canvas_width(), demuxer.canvas_height());
if let Some(icc) = demuxer.icc_profile() {
mux.icc_profile = Some(icc.to_vec());
}
if let Some(exif) = demuxer.exif() {
mux.exif = Some(exif.to_vec());
}
if let Some(xmp) = demuxer.xmp() {
mux.xmp = Some(xmp.to_vec());
}
if demuxer.is_animated() {
mux.animation = Some(AnimationParams {
background_color: demuxer.background_color(),
loop_count: demuxer.loop_count(),
});
for frame in demuxer.frames() {
mux.frames.push(MuxFrame {
x_offset: frame.x_offset,
y_offset: frame.y_offset,
width: frame.width,
height: frame.height,
duration_ms: frame.duration_ms,
dispose: frame.dispose,
blend: frame.blend,
bitstream: frame.bitstream.to_vec(),
alpha_data: frame.alpha_data.map(|d| d.to_vec()),
is_lossless: !frame.is_lossy,
});
}
} else if let Some(frame) = demuxer.frame(1) {
mux.single_image = Some(MuxFrame {
x_offset: 0,
y_offset: 0,
width: frame.width,
height: frame.height,
duration_ms: 0,
dispose: DisposeMethod::None,
blend: BlendMethod::Overwrite,
bitstream: frame.bitstream.to_vec(),
alpha_data: frame.alpha_data.map(|d| d.to_vec()),
is_lossless: !frame.is_lossy,
});
}
Ok(mux)
}
pub fn set_icc_profile(&mut self, data: Vec<u8>) {
self.icc_profile = Some(data);
}
pub fn icc_profile(&self) -> Option<&[u8]> {
self.icc_profile.as_deref()
}
pub fn set_exif(&mut self, data: Vec<u8>) {
self.exif = Some(data);
}
pub fn exif(&self) -> Option<&[u8]> {
self.exif.as_deref()
}
pub fn set_xmp(&mut self, data: Vec<u8>) {
self.xmp = Some(data);
}
pub fn xmp(&self) -> Option<&[u8]> {
self.xmp.as_deref()
}
pub fn clear_icc_profile(&mut self) {
self.icc_profile = None;
}
pub fn clear_exif(&mut self) {
self.exif = None;
}
pub fn clear_xmp(&mut self) {
self.xmp = None;
}
pub fn set_animation(&mut self, background_color: [u8; 4], loop_count: LoopCount) {
self.animation = Some(AnimationParams {
background_color,
loop_count,
});
}
#[track_caller]
pub fn push_frame(&mut self, frame: MuxFrame) -> MuxResult<()> {
if !frame.x_offset.is_multiple_of(2) || !frame.y_offset.is_multiple_of(2) {
return Err(whereat::at!(MuxError::OddFrameOffset {
x: frame.x_offset,
y: frame.y_offset,
}));
}
if frame.width == 0 || frame.height == 0 || frame.width > 16384 || frame.height > 16384 {
return Err(whereat::at!(MuxError::InvalidDimensions {
width: frame.width,
height: frame.height,
}));
}
if frame.x_offset + frame.width > self.canvas_width
|| frame.y_offset + frame.height > self.canvas_height
{
return Err(whereat::at!(MuxError::FrameOutsideCanvas {
x: frame.x_offset,
y: frame.y_offset,
width: frame.width,
height: frame.height,
canvas_width: self.canvas_width,
canvas_height: self.canvas_height,
}));
}
self.frames.push(frame);
Ok(())
}
pub fn set_image(&mut self, frame: MuxFrame) {
self.single_image = Some(frame);
}
pub fn num_frames(&self) -> u32 {
self.frames.len() as u32
}
#[track_caller]
pub fn assemble(&self) -> MuxResult<Vec<u8>> {
if self.animation.is_some() {
self.assemble_animated()
} else {
self.assemble_single()
}
}
fn assemble_single(&self) -> MuxResult<Vec<u8>> {
let frame = self
.single_image
.as_ref()
.ok_or_else(|| at!(MuxError::NoFrames))?;
let frame_chunk = if frame.is_lossless { b"VP8L" } else { b"VP8 " };
let has_alpha = frame.alpha_data.is_some() || frame.is_lossless;
let needs_extended = self.icc_profile.is_some()
|| self.exif.is_some()
|| self.xmp.is_some()
|| frame.alpha_data.is_some();
if !needs_extended {
let mut out = Vec::new();
out.write_all(b"RIFF");
out.write_u32_le(chunk_size(frame.bitstream.len()) + 4);
out.write_all(b"WEBP");
write_chunk(&mut out, frame_chunk, &frame.bitstream);
return Ok(out);
}
let mut total = 4u32 + chunk_size(10);
if let Some(icc) = &self.icc_profile {
total += chunk_size(icc.len());
}
if let Some(alpha) = &frame.alpha_data {
total += chunk_size(alpha.len());
}
total += chunk_size(frame.bitstream.len());
if let Some(exif) = &self.exif {
total += chunk_size(exif.len());
}
if let Some(xmp) = &self.xmp {
total += chunk_size(xmp.len());
}
let mut out = Vec::with_capacity(total as usize + 8);
out.write_all(b"RIFF");
out.write_u32_le(total);
out.write_all(b"WEBP");
let mut flags = 0u8;
if self.xmp.is_some() {
flags |= 1 << 2;
}
if self.exif.is_some() {
flags |= 1 << 3;
}
if has_alpha {
flags |= 1 << 4;
}
if self.icc_profile.is_some() {
flags |= 1 << 5;
}
let mut vp8x = Vec::with_capacity(10);
vp8x.push(flags);
vp8x.write_all(&[0; 3]); vp8x.write_u24_le(self.canvas_width - 1);
vp8x.write_u24_le(self.canvas_height - 1);
write_chunk(&mut out, b"VP8X", &vp8x);
if let Some(icc) = &self.icc_profile {
write_chunk(&mut out, b"ICCP", icc);
}
if let Some(alpha) = &frame.alpha_data {
write_chunk(&mut out, b"ALPH", alpha);
}
write_chunk(&mut out, frame_chunk, &frame.bitstream);
if let Some(exif) = &self.exif {
write_chunk(&mut out, b"EXIF", exif);
}
if let Some(xmp) = &self.xmp {
write_chunk(&mut out, b"XMP ", xmp);
}
Ok(out)
}
fn assemble_animated(&self) -> MuxResult<Vec<u8>> {
if self.frames.is_empty() {
return Err(at!(MuxError::NoFrames));
}
let anim = self.animation.as_ref().unwrap();
let mut total = 4u32 + chunk_size(10); if let Some(icc) = &self.icc_profile {
total += chunk_size(icc.len());
}
total += chunk_size(6);
for frame in &self.frames {
let anmf_payload_size = self.anmf_payload_size(frame);
total += chunk_size(anmf_payload_size);
}
if let Some(exif) = &self.exif {
total += chunk_size(exif.len());
}
if let Some(xmp) = &self.xmp {
total += chunk_size(xmp.len());
}
let mut out = Vec::with_capacity(total as usize + 8);
out.write_all(b"RIFF");
out.write_u32_le(total);
out.write_all(b"WEBP");
let has_alpha = self
.frames
.iter()
.any(|f| f.alpha_data.is_some() || f.is_lossless);
let mut flags = 0u8;
if self.xmp.is_some() {
flags |= 1 << 2;
}
if self.exif.is_some() {
flags |= 1 << 3;
}
if has_alpha {
flags |= 1 << 4;
}
if self.icc_profile.is_some() {
flags |= 1 << 5;
}
flags |= 1 << 1;
let mut vp8x = Vec::with_capacity(10);
vp8x.push(flags);
vp8x.write_all(&[0; 3]);
vp8x.write_u24_le(self.canvas_width - 1);
vp8x.write_u24_le(self.canvas_height - 1);
write_chunk(&mut out, b"VP8X", &vp8x);
if let Some(icc) = &self.icc_profile {
write_chunk(&mut out, b"ICCP", icc);
}
let mut anim_data = Vec::with_capacity(6);
anim_data.write_all(&anim.background_color);
let lc = match anim.loop_count {
LoopCount::Forever => 0u16,
LoopCount::Times(n) => n.get(),
};
anim_data.write_u16_le(lc);
write_chunk(&mut out, b"ANIM", &anim_data);
for frame in &self.frames {
self.write_anmf(&mut out, frame);
}
if let Some(exif) = &self.exif {
write_chunk(&mut out, b"EXIF", exif);
}
if let Some(xmp) = &self.xmp {
write_chunk(&mut out, b"XMP ", xmp);
}
Ok(out)
}
fn anmf_payload_size(&self, frame: &MuxFrame) -> usize {
let mut size = 16usize; if let Some(alpha) = &frame.alpha_data {
size += 8 + alpha.len() + (alpha.len() & 1); }
let frame_chunk_tag = if frame.is_lossless { b"VP8L" } else { b"VP8 " };
let _ = frame_chunk_tag; size += 8 + frame.bitstream.len() + (frame.bitstream.len() & 1); size
}
fn write_anmf(&self, out: &mut Vec<u8>, frame: &MuxFrame) {
let payload_size = self.anmf_payload_size(frame);
out.write_all(b"ANMF");
out.write_u32_le(payload_size as u32);
out.write_u24_le(frame.x_offset / 2);
out.write_u24_le(frame.y_offset / 2);
out.write_u24_le(frame.width - 1);
out.write_u24_le(frame.height - 1);
out.write_u24_le(frame.duration_ms);
let mut flags = 0u8;
if matches!(frame.dispose, DisposeMethod::Background) {
flags |= 1;
}
if matches!(frame.blend, BlendMethod::Overwrite) {
flags |= 2;
}
out.push(flags);
if let Some(alpha) = &frame.alpha_data {
write_chunk(out, b"ALPH", alpha);
}
let frame_chunk = if frame.is_lossless { b"VP8L" } else { b"VP8 " };
write_chunk(out, frame_chunk, &frame.bitstream);
}
}