use alloc::vec::Vec;
#[cfg(any(feature = "encode", feature = "decode"))]
use rgb::alt::{BGR8, BGRA8};
#[cfg(any(feature = "encode", feature = "decode"))]
use rgb::{RGB8, RGBA8};
use whereat::*;
#[cfg(any(feature = "encode", feature = "decode"))]
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PixelLayout {
Rgba,
Bgra,
Rgb,
Bgr,
}
#[cfg(any(feature = "encode", feature = "decode"))]
impl PixelLayout {
#[must_use]
pub const fn bytes_per_pixel(self) -> usize {
match self {
PixelLayout::Rgba | PixelLayout::Bgra => 4,
PixelLayout::Rgb | PixelLayout::Bgr => 3,
}
}
#[must_use]
pub const fn has_alpha(self) -> bool {
matches!(self, PixelLayout::Rgba | PixelLayout::Bgra)
}
}
#[cfg(feature = "encode")]
#[doc(hidden)]
pub trait EncodePixel: Copy + 'static + private::Sealed {
const LAYOUT: PixelLayout;
}
#[cfg(feature = "encode")]
impl EncodePixel for RGBA8 {
const LAYOUT: PixelLayout = PixelLayout::Rgba;
}
#[cfg(feature = "encode")]
impl EncodePixel for BGRA8 {
const LAYOUT: PixelLayout = PixelLayout::Bgra;
}
#[cfg(feature = "encode")]
impl EncodePixel for RGB8 {
const LAYOUT: PixelLayout = PixelLayout::Rgb;
}
#[cfg(feature = "encode")]
impl EncodePixel for BGR8 {
const LAYOUT: PixelLayout = PixelLayout::Bgr;
}
#[cfg(feature = "decode")]
#[doc(hidden)]
pub trait DecodePixel: Copy + 'static + private::Sealed {
const LAYOUT: PixelLayout;
#[doc(hidden)]
fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)>;
#[doc(hidden)]
unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool;
}
#[cfg(feature = "decode")]
impl DecodePixel for RGBA8 {
const LAYOUT: PixelLayout = PixelLayout::Rgba;
fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)> {
let mut width: i32 = 0;
let mut height: i32 = 0;
let ptr = unsafe {
libwebp_sys::WebPDecodeRGBA(data.as_ptr(), data.len(), &mut width, &mut height)
};
if ptr.is_null() {
None
} else {
Some((ptr, width, height))
}
}
unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool {
let result = unsafe {
libwebp_sys::WebPDecodeRGBAInto(data.as_ptr(), data.len(), output, output_len, stride)
};
!result.is_null()
}
}
#[cfg(feature = "decode")]
impl DecodePixel for BGRA8 {
const LAYOUT: PixelLayout = PixelLayout::Bgra;
fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)> {
let mut width: i32 = 0;
let mut height: i32 = 0;
let ptr = unsafe {
libwebp_sys::WebPDecodeBGRA(data.as_ptr(), data.len(), &mut width, &mut height)
};
if ptr.is_null() {
None
} else {
Some((ptr, width, height))
}
}
unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool {
let result = unsafe {
libwebp_sys::WebPDecodeBGRAInto(data.as_ptr(), data.len(), output, output_len, stride)
};
!result.is_null()
}
}
#[cfg(feature = "decode")]
impl DecodePixel for RGB8 {
const LAYOUT: PixelLayout = PixelLayout::Rgb;
fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)> {
let mut width: i32 = 0;
let mut height: i32 = 0;
let ptr = unsafe {
libwebp_sys::WebPDecodeRGB(data.as_ptr(), data.len(), &mut width, &mut height)
};
if ptr.is_null() {
None
} else {
Some((ptr, width, height))
}
}
unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool {
let result = unsafe {
libwebp_sys::WebPDecodeRGBInto(data.as_ptr(), data.len(), output, output_len, stride)
};
!result.is_null()
}
}
#[cfg(feature = "decode")]
impl DecodePixel for BGR8 {
const LAYOUT: PixelLayout = PixelLayout::Bgr;
fn decode_new(data: &[u8]) -> Option<(*mut u8, i32, i32)> {
let mut width: i32 = 0;
let mut height: i32 = 0;
let ptr = unsafe {
libwebp_sys::WebPDecodeBGR(data.as_ptr(), data.len(), &mut width, &mut height)
};
if ptr.is_null() {
None
} else {
Some((ptr, width, height))
}
}
unsafe fn decode_into(data: &[u8], output: *mut u8, output_len: usize, stride: i32) -> bool {
let result = unsafe {
libwebp_sys::WebPDecodeBGRInto(data.as_ptr(), data.len(), output, output_len, stride)
};
!result.is_null()
}
}
#[cfg(any(feature = "encode", feature = "decode"))]
mod private {
use super::*;
pub trait Sealed {}
impl Sealed for RGBA8 {}
impl Sealed for BGRA8 {}
impl Sealed for RGB8 {}
impl Sealed for BGR8 {}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[non_exhaustive]
pub struct ImageInfo {
pub width: u32,
pub height: u32,
pub has_alpha: bool,
pub has_animation: bool,
pub frame_count: u32,
pub format: BitstreamFormat,
}
impl ImageInfo {
pub fn from_webp(data: &[u8]) -> crate::Result<Self> {
let mut width: i32 = 0;
let mut height: i32 = 0;
let result =
unsafe { libwebp_sys::WebPGetInfo(data.as_ptr(), data.len(), &mut width, &mut height) };
if result == 0 {
return Err(at!(crate::Error::InvalidWebP));
}
let mut features = core::mem::MaybeUninit::<libwebp_sys::WebPBitstreamFeatures>::zeroed();
let status = unsafe {
libwebp_sys::WebPGetFeatures(data.as_ptr(), data.len(), features.as_mut_ptr())
};
if status != libwebp_sys::VP8StatusCode::VP8_STATUS_OK {
return Err(at!(crate::Error::DecodeFailed(
crate::error::DecodingError::from(status as i32),
)));
}
let features = unsafe { features.assume_init() };
let format = match features.format {
0 => BitstreamFormat::Undefined,
1 => BitstreamFormat::Lossy,
2 => BitstreamFormat::Lossless,
_ => BitstreamFormat::Undefined,
};
Ok(ImageInfo {
width: width as u32,
height: height as u32,
has_alpha: features.has_alpha != 0,
has_animation: features.has_animation != 0,
frame_count: if features.has_animation != 0 { 0 } else { 1 }, format,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum BitstreamFormat {
#[default]
Undefined,
Lossy,
Lossless,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum ColorMode {
#[default]
Rgba,
Bgra,
Argb,
Rgb,
Bgr,
Yuv420,
Yuva420,
}
impl ColorMode {
pub fn bytes_per_pixel(self) -> Option<usize> {
match self {
ColorMode::Rgba | ColorMode::Bgra | ColorMode::Argb => Some(4),
ColorMode::Rgb | ColorMode::Bgr => Some(3),
ColorMode::Yuv420 | ColorMode::Yuva420 => None, }
}
pub fn has_alpha(self) -> bool {
matches!(
self,
ColorMode::Rgba | ColorMode::Bgra | ColorMode::Argb | ColorMode::Yuva420
)
}
pub fn is_yuv(self) -> bool {
matches!(self, ColorMode::Yuv420 | ColorMode::Yuva420)
}
}
#[derive(Debug, Clone)]
pub struct YuvPlanes {
pub y: Vec<u8>,
pub y_stride: usize,
pub u: Vec<u8>,
pub u_stride: usize,
pub v: Vec<u8>,
pub v_stride: usize,
pub a: Option<Vec<u8>>,
pub a_stride: usize,
pub width: u32,
pub height: u32,
}
impl YuvPlanes {
#[must_use]
pub fn new_checked(width: u32, height: u32, with_alpha: bool) -> Option<Self> {
const MAX_DIMENSION: u32 = 16383;
if width == 0 || height == 0 || width > MAX_DIMENSION || height > MAX_DIMENSION {
return None;
}
let y_stride = width as usize;
let uv_stride = (width as usize).div_ceil(2);
let uv_height = (height as usize).div_ceil(2);
let y_size = y_stride.saturating_mul(height as usize);
let uv_size = uv_stride.saturating_mul(uv_height);
Some(Self {
y: alloc::vec![0u8; y_size],
y_stride,
u: alloc::vec![0u8; uv_size],
u_stride: uv_stride,
v: alloc::vec![0u8; uv_size],
v_stride: uv_stride,
a: if with_alpha {
Some(alloc::vec![0u8; y_size])
} else {
None
},
a_stride: y_stride,
width,
height,
})
}
pub fn new(width: u32, height: u32, with_alpha: bool) -> Self {
Self::new_checked(width, height, with_alpha)
.expect("YuvPlanes::new: dimensions out of range (1..=16383)")
}
pub fn uv_dimensions(&self) -> (u32, u32) {
(self.width.div_ceil(2), self.height.div_ceil(2))
}
}
#[derive(Debug, Clone, Copy)]
pub struct YuvPlanesRef<'a> {
pub y: &'a [u8],
pub y_stride: usize,
pub u: &'a [u8],
pub u_stride: usize,
pub v: &'a [u8],
pub v_stride: usize,
pub a: Option<&'a [u8]>,
pub a_stride: usize,
pub width: u32,
pub height: u32,
}
impl<'a> From<&'a YuvPlanes> for YuvPlanesRef<'a> {
fn from(planes: &'a YuvPlanes) -> Self {
Self {
y: &planes.y,
y_stride: planes.y_stride,
u: &planes.u,
u_stride: planes.u_stride,
v: &planes.v,
v_stride: planes.v_stride,
a: planes.a.as_deref(),
a_stride: planes.a_stride,
width: planes.width,
height: planes.height,
}
}
}
pub struct WebPData {
ptr: *mut u8,
len: usize,
}
unsafe impl Send for WebPData {}
unsafe impl Sync for WebPData {}
impl WebPData {
#[cfg(feature = "encode")]
#[must_use]
pub(crate) unsafe fn from_raw(ptr: *mut u8, len: usize) -> Self {
Self { ptr, len }
}
#[must_use]
pub fn len(&self) -> usize {
self.len
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.len == 0
}
#[must_use]
pub fn as_slice(&self) -> &[u8] {
if self.ptr.is_null() || self.len == 0 {
&[]
} else {
unsafe { core::slice::from_raw_parts(self.ptr, self.len) }
}
}
}
impl Drop for WebPData {
fn drop(&mut self) {
if !self.ptr.is_null() {
unsafe {
libwebp_sys::WebPFree(self.ptr as *mut core::ffi::c_void);
}
}
}
}
impl core::ops::Deref for WebPData {
type Target = [u8];
fn deref(&self) -> &Self::Target {
self.as_slice()
}
}
impl AsRef<[u8]> for WebPData {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl From<WebPData> for Vec<u8> {
fn from(data: WebPData) -> Self {
data.as_slice().to_vec()
}
}
impl core::fmt::Debug for WebPData {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.debug_struct("WebPData")
.field("len", &self.len)
.finish_non_exhaustive()
}
}