use std::{error::Error, fmt, time::Duration};
use crate::{
AnimationDecoder, AnimationEncoder, AnimationEncoderOptions, AnimationInfo,
AnimationMuxOverrides, CanvasSize, DecodeError, DecodeLimits, EncodeError,
EncoderConfigOverrides, ResizeError, ResizeOptions, ResizePlan,
};
#[derive(Clone, Debug)]
pub struct AnimationTranscodeOptions {
pub decode_limits: DecodeLimits,
pub resize: ResizeOptions,
pub encoder_config: EncoderConfigOverrides,
pub animation: AnimationMuxOverrides,
}
impl AnimationTranscodeOptions {
pub fn new(resize: ResizeOptions) -> Self {
Self {
decode_limits: DecodeLimits::default(),
resize,
encoder_config: EncoderConfigOverrides::default(),
animation: AnimationMuxOverrides::default(),
}
}
}
#[derive(Clone, Debug, PartialEq)]
pub struct TranscodedAnimation {
pub bytes: Vec<u8>,
pub input: AnimationInfo,
pub output_canvas: CanvasSize,
pub frame_count: u32,
pub total_duration: Duration,
}
pub fn transcode_animated_webp(
input: &[u8],
options: AnimationTranscodeOptions,
) -> Result<TranscodedAnimation, TranscodeError> {
let mut decoder =
AnimationDecoder::new(input, options.decode_limits).map_err(TranscodeError::Decode)?;
let source = *decoder.info();
let resize = ResizePlan::new(source.canvas, options.resize).map_err(TranscodeError::Resize)?;
let mut workspace = resize.workspace().map_err(TranscodeError::Resize)?;
let mut encoder_options = AnimationEncoderOptions::from_animation_info(source);
encoder_options.config = options.encoder_config;
encoder_options.animation = options.animation;
let mut encoder = AnimationEncoder::new(resize.destination(), encoder_options)
.map_err(TranscodeError::Encode)?;
let mut frame_count = 0_u32;
let mut total_duration = Duration::ZERO;
while let Some(frame) = decoder.next_frame().map_err(TranscodeError::Decode)? {
total_duration = total_duration
.checked_add(frame.duration)
.ok_or(TranscodeError::DurationOverflow)?;
let mut rgba = frame.rgba;
workspace
.transform_rgba(&mut rgba)
.map_err(TranscodeError::Resize)?;
encoder
.add_rgba(workspace.pixels(), frame.duration)
.map_err(TranscodeError::Encode)?;
frame_count = frame_count
.checked_add(1)
.ok_or(TranscodeError::FrameCountOverflow)?;
}
if frame_count != source.frame_count {
return Err(TranscodeError::FrameCountMismatch {
decoded: frame_count,
declared: source.frame_count,
});
}
let bytes = encoder.finish().map_err(TranscodeError::Encode)?;
Ok(TranscodedAnimation {
bytes,
input: source,
output_canvas: resize.destination(),
frame_count,
total_duration,
})
}
#[derive(Clone, Debug, PartialEq)]
pub enum TranscodeError {
Decode(DecodeError),
Resize(ResizeError),
Encode(EncodeError),
DurationOverflow,
FrameCountOverflow,
FrameCountMismatch {
decoded: u32,
declared: u32,
},
}
impl fmt::Display for TranscodeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Decode(error) => write!(f, "animated WebP decode failed: {error}"),
Self::Resize(error) => write!(f, "animated WebP resize failed: {error}"),
Self::Encode(error) => write!(f, "animated WebP encode failed: {error}"),
Self::DurationOverflow => f.write_str("animated WebP duration overflows Duration"),
Self::FrameCountOverflow => f.write_str("animated WebP frame count overflows u32"),
Self::FrameCountMismatch { decoded, declared } => write!(
f,
"decoder produced {decoded} frames; source declared {declared}"
),
}
}
}
impl Error for TranscodeError {
fn source(&self) -> Option<&(dyn Error + 'static)> {
match self {
Self::Decode(error) => Some(error),
Self::Resize(error) => Some(error),
Self::Encode(error) => Some(error),
Self::DurationOverflow | Self::FrameCountOverflow | Self::FrameCountMismatch { .. } => {
None
}
}
}
}