Skip to main content

webp_anim/
transcode.rs

1use std::{error::Error, fmt, time::Duration};
2
3use crate::{
4    AnimationDecoder, AnimationEncoder, AnimationEncoderOptions, AnimationInfo,
5    AnimationMuxOverrides, CanvasSize, DecodeError, DecodeLimits, EncodeError,
6    EncoderConfigOverrides, ResizeError, ResizeOptions, ResizePlan,
7};
8
9/// Compression and resize inputs for [`transcode_animated_webp`].
10///
11/// The source animation's loop count and raw background color are always
12/// copied to the output. Product-level quality defaults, output-selection
13/// policies, and process-wide resource budgets belong to the caller.
14#[derive(Clone, Debug)]
15pub struct AnimationTranscodeOptions {
16    /// Limits for the one input animation sequence.
17    pub decode_limits: DecodeLimits,
18    /// Full-canvas resize operation derived from the source animation canvas.
19    pub resize: ResizeOptions,
20    /// Explicit per-frame libwebp configuration overrides.
21    pub encoder_config: EncoderConfigOverrides,
22    /// Explicit libwebp animation-mux configuration overrides.
23    pub animation: AnimationMuxOverrides,
24}
25
26impl AnimationTranscodeOptions {
27    /// Creates a request with the supplied resize operation and no compression
28    /// overrides beyond libwebp's initialized defaults.
29    pub fn new(resize: ResizeOptions) -> Self {
30        Self {
31            decode_limits: DecodeLimits::default(),
32            resize,
33            encoder_config: EncoderConfigOverrides::default(),
34            animation: AnimationMuxOverrides::default(),
35        }
36    }
37}
38
39/// Result of a sequential animated-WebP transcode.
40#[derive(Clone, Debug, PartialEq)]
41pub struct TranscodedAnimation {
42    /// Newly encoded animated WebP bytes.
43    pub bytes: Vec<u8>,
44    /// Metadata read from the stored source sequence and retained by encoding.
45    pub input: AnimationInfo,
46    /// Canvas dimensions of the encoded sequence.
47    pub output_canvas: CanvasSize,
48    /// Number of frames decoded and encoded.
49    pub frame_count: u32,
50    /// Sum of the source frame durations passed through to the encoder.
51    pub total_duration: Duration,
52}
53
54/// Decodes, resizes, and re-encodes exactly one stored animated WebP sequence.
55///
56/// Frames are decoded and encoded in stored order. The operation holds one
57/// decoded frame and one reusable resize destination at a time; it does not
58/// buffer the whole animation. This convenience API always encodes output,
59/// including when the resize operation is a no-op. Callers decide whether a
60/// source should be passed through or whether an encoded result should replace
61/// it.
62pub fn transcode_animated_webp(
63    input: &[u8],
64    options: AnimationTranscodeOptions,
65) -> Result<TranscodedAnimation, TranscodeError> {
66    let mut decoder =
67        AnimationDecoder::new(input, options.decode_limits).map_err(TranscodeError::Decode)?;
68    let source = *decoder.info();
69    let resize = ResizePlan::new(source.canvas, options.resize).map_err(TranscodeError::Resize)?;
70    let mut workspace = resize.workspace().map_err(TranscodeError::Resize)?;
71
72    let mut encoder_options = AnimationEncoderOptions::from_animation_info(source);
73    encoder_options.config = options.encoder_config;
74    encoder_options.animation = options.animation;
75    let mut encoder = AnimationEncoder::new(resize.destination(), encoder_options)
76        .map_err(TranscodeError::Encode)?;
77
78    let mut frame_count = 0_u32;
79    let mut total_duration = Duration::ZERO;
80    while let Some(frame) = decoder.next_frame().map_err(TranscodeError::Decode)? {
81        total_duration = total_duration
82            .checked_add(frame.duration)
83            .ok_or(TranscodeError::DurationOverflow)?;
84        let mut rgba = frame.rgba;
85        workspace
86            .transform_rgba(&mut rgba)
87            .map_err(TranscodeError::Resize)?;
88        encoder
89            .add_rgba(workspace.pixels(), frame.duration)
90            .map_err(TranscodeError::Encode)?;
91        frame_count = frame_count
92            .checked_add(1)
93            .ok_or(TranscodeError::FrameCountOverflow)?;
94    }
95    if frame_count != source.frame_count {
96        return Err(TranscodeError::FrameCountMismatch {
97            decoded: frame_count,
98            declared: source.frame_count,
99        });
100    }
101
102    let bytes = encoder.finish().map_err(TranscodeError::Encode)?;
103    Ok(TranscodedAnimation {
104        bytes,
105        input: source,
106        output_canvas: resize.destination(),
107        frame_count,
108        total_duration,
109    })
110}
111
112/// Failure while transcoding an animated WebP sequence.
113#[derive(Clone, Debug, PartialEq)]
114pub enum TranscodeError {
115    /// The source animation could not be decoded.
116    Decode(DecodeError),
117    /// The source or destination resize operation could not be created/applied.
118    Resize(ResizeError),
119    /// The destination encoder could not be created or finished.
120    Encode(EncodeError),
121    /// The accumulated frame durations overflowed [`Duration`].
122    DurationOverflow,
123    /// The number of processed frames overflowed `u32`.
124    FrameCountOverflow,
125    /// The decoder produced a different number of frames than the source metadata declared.
126    FrameCountMismatch {
127        /// Number of frames actually produced by the decoder.
128        decoded: u32,
129        /// Number of frames declared by the source metadata.
130        declared: u32,
131    },
132}
133
134impl fmt::Display for TranscodeError {
135    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
136        match self {
137            Self::Decode(error) => write!(f, "animated WebP decode failed: {error}"),
138            Self::Resize(error) => write!(f, "animated WebP resize failed: {error}"),
139            Self::Encode(error) => write!(f, "animated WebP encode failed: {error}"),
140            Self::DurationOverflow => f.write_str("animated WebP duration overflows Duration"),
141            Self::FrameCountOverflow => f.write_str("animated WebP frame count overflows u32"),
142            Self::FrameCountMismatch { decoded, declared } => write!(
143                f,
144                "decoder produced {decoded} frames; source declared {declared}"
145            ),
146        }
147    }
148}
149
150impl Error for TranscodeError {
151    fn source(&self) -> Option<&(dyn Error + 'static)> {
152        match self {
153            Self::Decode(error) => Some(error),
154            Self::Resize(error) => Some(error),
155            Self::Encode(error) => Some(error),
156            Self::DurationOverflow | Self::FrameCountOverflow | Self::FrameCountMismatch { .. } => {
157                None
158            }
159        }
160    }
161}