use alloc::vec::Vec;
use core::ops::Deref;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ColorMode {
Rgb,
#[default]
Rgba,
Bgra,
Bgr,
}
impl ColorMode {
pub fn size(&self) -> usize {
match self {
ColorMode::Rgb | ColorMode::Bgr => 3,
ColorMode::Rgba | ColorMode::Bgra => 4,
}
}
}
impl From<ColorMode> for crate::ColorMode {
fn from(mode: ColorMode) -> Self {
match mode {
ColorMode::Rgb => crate::ColorMode::Rgb,
ColorMode::Rgba => crate::ColorMode::Rgba,
ColorMode::Bgra => crate::ColorMode::Bgra,
ColorMode::Bgr => crate::ColorMode::Bgr,
}
}
}
#[derive(Debug)]
pub struct WebPData(Vec<u8>);
impl WebPData {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
}
impl Deref for WebPData {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<[u8]> for WebPData {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug, PartialEq)]
pub enum Error {
EncoderCreateFailed,
EncoderAddFailed,
EncoderAssmebleFailed,
DecodeFailed,
BufferSizeFailed(usize, usize),
TimestampMustBeHigherThanPrevious(i32, i32),
NoFramesAdded,
DimensionsMustbePositive,
}
impl core::fmt::Display for Error {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Error::EncoderCreateFailed => write!(f, "Encoder creation failed"),
Error::EncoderAddFailed => write!(f, "Frame add failed"),
Error::EncoderAssmebleFailed => write!(f, "Encoder assembly failed"),
Error::DecodeFailed => write!(f, "Decode failed"),
Error::BufferSizeFailed(got, expected) => {
write!(
f,
"Buffer size mismatch: got {}, expected {}",
got, expected
)
}
Error::TimestampMustBeHigherThanPrevious(ts, prev) => {
write!(f, "Timestamp {} must be higher than previous {}", ts, prev)
}
Error::NoFramesAdded => write!(f, "No frames added"),
Error::DimensionsMustbePositive => write!(f, "Dimensions must be positive"),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct EncoderOptions {
pub kmin: i32,
pub kmax: i32,
pub encoding_config: Option<EncodingConfig>,
}
#[derive(Debug, Clone)]
pub struct EncodingConfig {
pub quality: f32,
pub encoding_type: EncodingType,
}
impl Default for EncodingConfig {
fn default() -> Self {
Self {
quality: 75.0,
encoding_type: EncodingType::Lossy(LossyEncodingConfig::default()),
}
}
}
#[derive(Debug, Clone)]
pub enum EncodingType {
Lossy(LossyEncodingConfig),
Lossless,
}
#[derive(Debug, Clone, Default)]
pub struct LossyEncodingConfig {
pub segments: i32,
pub alpha_compression: bool,
}
#[derive(Debug, Clone)]
pub struct Frame {
data: Vec<u8>,
width: u32,
height: u32,
timestamp: i32,
color_mode: ColorMode,
}
impl Frame {
pub fn dimensions(&self) -> (u32, u32) {
(self.width, self.height)
}
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn timestamp(&self) -> i32 {
self.timestamp
}
pub fn color_mode(&self) -> ColorMode {
self.color_mode
}
}
#[derive(Debug, Clone, Default)]
pub struct DecoderOptions {
pub use_threads: bool,
pub color_mode: ColorMode,
}
#[cfg(feature = "animation")]
pub struct Encoder {
inner: crate::AnimationEncoder,
previous_timestamp: i32,
frame_count: u32,
}
#[cfg(feature = "animation")]
impl Encoder {
pub fn new(dimensions: (u32, u32)) -> Result<Self, Error> {
Self::new_with_options(dimensions, EncoderOptions::default())
}
pub fn new_with_options(
dimensions: (u32, u32),
options: EncoderOptions,
) -> Result<Self, Error> {
let (width, height) = dimensions;
if width == 0 || height == 0 {
return Err(Error::DimensionsMustbePositive);
}
let mut inner =
crate::AnimationEncoder::new(width, height).map_err(|_| Error::EncoderCreateFailed)?;
if let Some(config) = &options.encoding_config {
inner.set_quality(config.quality);
if matches!(config.encoding_type, EncodingType::Lossless) {
inner.set_lossless(true);
}
}
Ok(Self {
inner,
previous_timestamp: -1,
frame_count: 0,
})
}
pub fn add_frame(&mut self, data: &[u8], timestamp_ms: i32) -> Result<(), Error> {
if timestamp_ms <= self.previous_timestamp {
return Err(Error::TimestampMustBeHigherThanPrevious(
timestamp_ms,
self.previous_timestamp,
));
}
self.inner
.add_frame_rgba(data, timestamp_ms)
.map_err(|_| Error::EncoderAddFailed)?;
self.previous_timestamp = timestamp_ms;
self.frame_count += 1;
Ok(())
}
pub fn finalize(self, end_timestamp_ms: i32) -> Result<WebPData, Error> {
if self.frame_count == 0 {
return Err(Error::NoFramesAdded);
}
let data = self
.inner
.finish(end_timestamp_ms)
.map_err(|_| Error::EncoderAssmebleFailed)?;
Ok(WebPData(data))
}
}
#[cfg(feature = "animation")]
pub struct Decoder<'a> {
data: &'a [u8],
options: DecoderOptions,
}
#[cfg(feature = "animation")]
impl<'a> Decoder<'a> {
pub fn new(data: &'a [u8]) -> Result<Self, Error> {
Self::new_with_options(data, DecoderOptions::default())
}
pub fn new_with_options(data: &'a [u8], options: DecoderOptions) -> Result<Self, Error> {
if data.is_empty() {
return Err(Error::DecodeFailed);
}
Ok(Self { data, options })
}
pub fn decode(&self) -> Result<Vec<Frame>, Error> {
let mut decoder = crate::AnimationDecoder::with_options(
self.data,
self.options.color_mode.into(),
self.options.use_threads,
)
.map_err(|_| Error::DecodeFailed)?;
let mut frames = Vec::new();
while let Some(frame) = decoder.next_frame().map_err(|_| Error::DecodeFailed)? {
frames.push(Frame {
data: frame.data,
width: frame.width,
height: frame.height,
timestamp: frame.timestamp_ms,
color_mode: self.options.color_mode,
});
}
Ok(frames)
}
}
#[cfg(feature = "animation")]
impl<'a> IntoIterator for Decoder<'a> {
type Item = Frame;
type IntoIter = DecoderIterator;
fn into_iter(self) -> Self::IntoIter {
let inner = crate::AnimationDecoder::with_options(
self.data,
self.options.color_mode.into(),
self.options.use_threads,
)
.ok();
DecoderIterator {
inner,
color_mode: self.options.color_mode,
}
}
}
#[cfg(feature = "animation")]
pub struct DecoderIterator {
inner: Option<crate::AnimationDecoder>,
color_mode: ColorMode,
}
#[cfg(feature = "animation")]
impl Iterator for DecoderIterator {
type Item = Frame;
fn next(&mut self) -> Option<Self::Item> {
let decoder = self.inner.as_mut()?;
let frame = decoder.next_frame().ok()??;
Some(Frame {
data: frame.data,
width: frame.width,
height: frame.height,
timestamp: frame.timestamp_ms,
color_mode: self.color_mode,
})
}
}
pub mod prelude {
pub use super::ColorMode;
#[cfg(feature = "animation")]
pub use super::{Decoder, DecoderOptions, Encoder, EncoderOptions};
#[cfg(feature = "animation")]
pub use super::{EncodingConfig, EncodingType, LossyEncodingConfig};
}
#[cfg(all(test, feature = "animation"))]
mod tests {
use super::*;
#[test]
fn test_encode_decode_animation() {
let frame1 = vec![255u8; 4 * 4 * 4]; let frame2 = vec![0u8; 4 * 4 * 4];
let mut encoder = Encoder::new((4, 4)).expect("create encoder");
encoder.add_frame(&frame1, 0).expect("add frame 1");
encoder.add_frame(&frame2, 100).expect("add frame 2");
let webp = encoder.finalize(200).expect("finalize");
assert!(!webp.is_empty());
let decoder = Decoder::new(&webp).expect("create decoder");
let frames: Vec<_> = decoder.into_iter().collect();
assert_eq!(frames.len(), 2);
assert_eq!(frames[0].dimensions(), (4, 4));
}
#[test]
fn test_timestamp_ordering() {
let frame = vec![0u8; 4 * 4 * 4];
let mut encoder = Encoder::new((4, 4)).expect("create encoder");
encoder.add_frame(&frame, 100).expect("add frame");
let result = encoder.add_frame(&frame, 50); assert!(matches!(
result,
Err(Error::TimestampMustBeHigherThanPrevious(50, 100))
));
}
}