use alloc::vec::Vec;
use core::ops::Deref;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PixelLayout {
Rgb,
Rgba,
}
impl PixelLayout {
pub fn bytes_per_pixel(&self) -> u8 {
match self {
PixelLayout::Rgb => 3,
PixelLayout::Rgba => 4,
}
}
}
#[derive(Debug)]
pub struct WebPMemory(Vec<u8>);
impl WebPMemory {
pub fn is_empty(&self) -> bool {
self.0.is_empty()
}
pub fn len(&self) -> usize {
self.0.len()
}
}
impl Deref for WebPMemory {
type Target = [u8];
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl AsRef<[u8]> for WebPMemory {
fn as_ref(&self) -> &[u8] {
&self.0
}
}
#[derive(Debug)]
pub struct WebPImage {
data: Vec<u8>,
layout: PixelLayout,
width: u32,
height: u32,
}
impl WebPImage {
pub fn data(&self) -> &[u8] {
&self.data
}
pub fn layout(&self) -> PixelLayout {
self.layout
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
}
#[derive(Debug)]
pub struct BitstreamFeatures {
width: u32,
height: u32,
has_alpha: bool,
has_animation: bool,
}
impl BitstreamFeatures {
pub fn new(data: &[u8]) -> Option<Self> {
crate::ImageInfo::from_webp(data).ok().map(|info| Self {
width: info.width,
height: info.height,
has_alpha: info.has_alpha,
has_animation: info.has_animation,
})
}
pub fn width(&self) -> u32 {
self.width
}
pub fn height(&self) -> u32 {
self.height
}
pub fn has_alpha(&self) -> bool {
self.has_alpha
}
pub fn has_animation(&self) -> bool {
self.has_animation
}
}
#[cfg(feature = "encode")]
pub struct Encoder<'a> {
image: &'a [u8],
layout: PixelLayout,
width: u32,
height: u32,
}
#[cfg(feature = "encode")]
impl<'a> Encoder<'a> {
pub fn new(image: &'a [u8], layout: PixelLayout, width: u32, height: u32) -> Self {
Self {
image,
layout,
width,
height,
}
}
pub fn from_rgb(image: &'a [u8], width: u32, height: u32) -> Self {
Self::new(image, PixelLayout::Rgb, width, height)
}
pub fn from_rgba(image: &'a [u8], width: u32, height: u32) -> Self {
Self::new(image, PixelLayout::Rgba, width, height)
}
pub fn encode(&self, quality: f32) -> WebPMemory {
self.encode_simple(false, quality)
.unwrap_or_else(|_| WebPMemory(Vec::new()))
}
pub fn encode_lossless(&self) -> WebPMemory {
self.encode_simple(true, 75.0)
.unwrap_or_else(|_| WebPMemory(Vec::new()))
}
pub fn encode_simple(&self, lossless: bool, quality: f32) -> crate::Result<WebPMemory> {
use crate::Unstoppable;
let config = crate::EncoderConfig::new()
.quality(quality)
.lossless(lossless);
let data = match self.layout {
PixelLayout::Rgba => {
config.encode_rgba(self.image, self.width, self.height, Unstoppable)?
}
PixelLayout::Rgb => {
config.encode_rgb(self.image, self.width, self.height, Unstoppable)?
}
};
Ok(WebPMemory(data))
}
}
#[cfg(feature = "decode")]
pub struct Decoder<'a> {
data: &'a [u8],
}
#[cfg(feature = "decode")]
impl<'a> Decoder<'a> {
pub fn new(data: &'a [u8]) -> Self {
Self { data }
}
pub fn decode(&self) -> Option<WebPImage> {
let features = BitstreamFeatures::new(self.data)?;
if features.has_animation() {
return None;
}
let (data, width, height) = if features.has_alpha() {
crate::decode_rgba(self.data).ok()?
} else {
crate::decode_rgb(self.data).ok()?
};
let layout = if features.has_alpha() {
PixelLayout::Rgba
} else {
PixelLayout::Rgb
};
Some(WebPImage {
data,
layout,
width,
height,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_encode_decode_roundtrip() {
let rgba = vec![
255, 0, 0, 255, 0, 255, 0, 255, 0, 0, 255, 255, 255, 255, 255, 255,
];
let encoder = Encoder::from_rgba(&rgba, 2, 2);
let webp = encoder.encode_lossless();
assert!(!webp.is_empty());
let decoder = Decoder::new(&webp);
let image = decoder.decode().expect("decode failed");
assert_eq!(image.width(), 2);
assert_eq!(image.height(), 2);
assert!(image.layout() == PixelLayout::Rgba || image.layout() == PixelLayout::Rgb);
}
#[test]
fn test_bitstream_features() {
let rgba = vec![0u8; 4 * 4 * 4];
let encoder = Encoder::from_rgba(&rgba, 4, 4);
let webp = encoder.encode(85.0);
let features = BitstreamFeatures::new(&webp).expect("features");
assert_eq!(features.width(), 4);
assert_eq!(features.height(), 4);
assert!(!features.has_animation());
}
}