use serde::{Deserialize, Serialize};
use crate::RangaError;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
#[non_exhaustive]
pub enum PixelFormat {
Rgba8,
Argb8,
Rgb8,
Yuv420p,
Nv12,
RgbaF32,
}
impl std::fmt::Display for PixelFormat {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Rgba8 => write!(f, "RGBA8"),
Self::Argb8 => write!(f, "ARGB8"),
Self::Rgb8 => write!(f, "RGB8"),
Self::Yuv420p => write!(f, "YUV420p"),
Self::Nv12 => write!(f, "NV12"),
Self::RgbaF32 => write!(f, "RgbaF32"),
}
}
}
impl PixelFormat {
#[must_use]
#[inline]
pub fn checked_buffer_size(self, width: u32, height: u32) -> Option<usize> {
let w = width as usize;
let h = height as usize;
match self {
Self::Rgba8 | Self::Argb8 => w.checked_mul(h)?.checked_mul(4),
Self::Rgb8 => w.checked_mul(h)?.checked_mul(3),
Self::Yuv420p => {
let y = w.checked_mul(h)?;
let chroma = w.div_ceil(2).checked_mul(h.div_ceil(2))?.checked_mul(2)?;
y.checked_add(chroma)
}
Self::Nv12 => {
let y = w.checked_mul(h)?;
let chroma = w.div_ceil(2).checked_mul(h.div_ceil(2))?.checked_mul(2)?;
y.checked_add(chroma)
}
Self::RgbaF32 => w.checked_mul(h)?.checked_mul(16),
}
}
#[must_use]
#[inline]
pub fn buffer_size(self, width: u32, height: u32) -> usize {
self.checked_buffer_size(width, height)
.expect("buffer size overflow")
}
}
#[derive(Debug, Clone)]
pub struct PixelBuffer {
pub(crate) data: Vec<u8>,
pub(crate) width: u32,
pub(crate) height: u32,
pub(crate) format: PixelFormat,
}
impl PixelBuffer {
#[must_use = "returns a new pixel buffer"]
pub fn new(
data: Vec<u8>,
width: u32,
height: u32,
format: PixelFormat,
) -> Result<Self, RangaError> {
let expected =
format
.checked_buffer_size(width, height)
.ok_or(RangaError::BufferTooSmall {
need: usize::MAX,
have: data.len(),
})?;
if data.len() != expected {
return Err(RangaError::DimensionMismatch {
expected,
actual: data.len(),
});
}
Ok(Self {
data,
width,
height,
format,
})
}
#[must_use]
pub fn zeroed(width: u32, height: u32, format: PixelFormat) -> Self {
let size = format.buffer_size(width, height);
Self {
data: vec![0u8; size],
width,
height,
format,
}
}
#[must_use]
#[inline]
pub fn data(&self) -> &[u8] {
&self.data
}
#[inline]
pub fn data_mut(&mut self) -> &mut [u8] {
&mut self.data
}
#[must_use]
#[inline]
pub fn into_data(self) -> Vec<u8> {
self.data
}
#[must_use]
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[must_use]
#[inline]
pub fn format(&self) -> PixelFormat {
self.format
}
#[must_use]
#[inline]
pub fn pixel_count(&self) -> usize {
self.width as usize * self.height as usize
}
#[must_use = "returns a row iterator"]
pub fn rows(&self) -> impl Iterator<Item = &[u8]> {
let stride = match self.format {
PixelFormat::Rgba8 | PixelFormat::Argb8 => self.width as usize * 4,
PixelFormat::Rgb8 => self.width as usize * 3,
PixelFormat::RgbaF32 => self.width as usize * 16,
PixelFormat::Yuv420p | PixelFormat::Nv12 => {
debug_assert!(
false,
"rows()/rows_mut() not supported for planar formats; access data() directly"
);
self.width as usize
}
};
self.data.chunks_exact(stride)
}
#[must_use = "returns a mutable row iterator"]
pub fn rows_mut(&mut self) -> impl Iterator<Item = &mut [u8]> {
let stride = match self.format {
PixelFormat::Rgba8 | PixelFormat::Argb8 => self.width as usize * 4,
PixelFormat::Rgb8 => self.width as usize * 3,
PixelFormat::RgbaF32 => self.width as usize * 16,
PixelFormat::Yuv420p | PixelFormat::Nv12 => {
debug_assert!(
false,
"rows()/rows_mut() not supported for planar formats; access data() directly"
);
self.width as usize
}
};
self.data.chunks_exact_mut(stride)
}
#[must_use]
#[inline]
pub fn get_rgba(&self, x: u32, y: u32) -> Option<[u8; 4]> {
if self.format != PixelFormat::Rgba8 || x >= self.width || y >= self.height {
return None;
}
let i = (y as usize * self.width as usize + x as usize) * 4;
Some([
self.data[i],
self.data[i + 1],
self.data[i + 2],
self.data[i + 3],
])
}
#[must_use = "returns whether the pixel was set successfully"]
#[inline]
pub fn set_rgba(&mut self, x: u32, y: u32, pixel: [u8; 4]) -> bool {
if self.format != PixelFormat::Rgba8 || x >= self.width || y >= self.height {
return false;
}
let i = (y as usize * self.width as usize + x as usize) * 4;
self.data[i..i + 4].copy_from_slice(&pixel);
true
}
#[must_use]
pub fn from_view(view: &PixelView<'_>) -> Self {
Self {
data: view.data().to_vec(),
width: view.width(),
height: view.height(),
format: view.format(),
}
}
#[must_use]
pub fn as_view(&self) -> PixelView<'_> {
PixelView {
data: &self.data,
width: self.width,
height: self.height,
format: self.format,
}
}
#[must_use]
pub fn as_view_mut(&mut self) -> PixelViewMut<'_> {
PixelViewMut {
data: &mut self.data,
width: self.width,
height: self.height,
format: self.format,
}
}
}
#[derive(Debug)]
pub struct PixelView<'a> {
data: &'a [u8],
width: u32,
height: u32,
format: PixelFormat,
}
impl<'a> PixelView<'a> {
#[must_use = "returns a new pixel view"]
pub fn new(
data: &'a [u8],
width: u32,
height: u32,
format: PixelFormat,
) -> Result<Self, RangaError> {
let expected = format.buffer_size(width, height);
if data.len() != expected {
return Err(RangaError::DimensionMismatch {
expected,
actual: data.len(),
});
}
Ok(Self {
data,
width,
height,
format,
})
}
#[must_use]
#[inline]
pub fn data(&self) -> &[u8] {
self.data
}
#[must_use]
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[must_use]
#[inline]
pub fn format(&self) -> PixelFormat {
self.format
}
#[must_use]
#[inline]
pub fn pixel_count(&self) -> usize {
self.width as usize * self.height as usize
}
}
#[derive(Debug)]
pub struct PixelViewMut<'a> {
data: &'a mut [u8],
width: u32,
height: u32,
format: PixelFormat,
}
impl<'a> PixelViewMut<'a> {
#[must_use = "returns a new mutable pixel view"]
pub fn new(
data: &'a mut [u8],
width: u32,
height: u32,
format: PixelFormat,
) -> Result<Self, RangaError> {
let expected = format.buffer_size(width, height);
if data.len() != expected {
return Err(RangaError::DimensionMismatch {
expected,
actual: data.len(),
});
}
Ok(Self {
data,
width,
height,
format,
})
}
#[must_use]
#[inline]
pub fn data(&self) -> &[u8] {
self.data
}
#[inline]
pub fn data_mut(&mut self) -> &mut [u8] {
self.data
}
#[must_use]
#[inline]
pub fn width(&self) -> u32 {
self.width
}
#[must_use]
#[inline]
pub fn height(&self) -> u32 {
self.height
}
#[must_use]
#[inline]
pub fn format(&self) -> PixelFormat {
self.format
}
#[must_use]
#[inline]
pub fn pixel_count(&self) -> usize {
self.width as usize * self.height as usize
}
}
#[derive(Debug)]
pub struct BufferPool {
pool: Vec<Vec<u8>>,
max_buffers: usize,
}
impl BufferPool {
#[must_use]
pub fn new(max_buffers: usize) -> Self {
Self {
pool: Vec::new(),
max_buffers,
}
}
#[must_use]
pub fn acquire(&mut self, size: usize) -> Vec<u8> {
if let Some(pos) = self
.pool
.iter()
.enumerate()
.filter(|(_, b)| b.capacity() >= size)
.min_by_key(|(_, b)| b.capacity())
.map(|(i, _)| i)
{
let mut buf = self.pool.swap_remove(pos);
buf.clear();
buf.resize(size, 0);
buf
} else {
vec![0u8; size]
}
}
pub fn release(&mut self, buf: Vec<u8>) {
if self.pool.len() < self.max_buffers {
self.pool.push(buf);
}
}
#[must_use]
pub fn len(&self) -> usize {
self.pool.len()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.pool.is_empty()
}
pub fn clear(&mut self) {
self.pool.clear();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rgba_buffer_size() {
assert_eq!(PixelFormat::Rgba8.buffer_size(1920, 1080), 1920 * 1080 * 4);
}
#[test]
fn yuv420p_buffer_size() {
assert_eq!(
PixelFormat::Yuv420p.buffer_size(320, 240),
320 * 240 + 2 * 160 * 120
);
}
#[test]
fn new_validates_length() {
let result = PixelBuffer::new(vec![0; 100], 10, 10, PixelFormat::Rgba8);
assert!(result.is_err());
let result = PixelBuffer::new(vec![0; 400], 10, 10, PixelFormat::Rgba8);
assert!(result.is_ok());
}
#[test]
fn zeroed_creates_correct_size() {
let buf = PixelBuffer::zeroed(64, 64, PixelFormat::Rgb8);
assert_eq!(buf.data.len(), 64 * 64 * 3);
}
#[test]
fn pixel_view_from_buffer() {
let buf = PixelBuffer::zeroed(8, 8, PixelFormat::Rgba8);
let view = buf.as_view();
assert_eq!(view.width(), 8);
assert_eq!(view.pixel_count(), 64);
}
#[test]
fn pixel_view_from_slice() {
let data = vec![0u8; 4 * 4 * 4];
let view = PixelView::new(&data, 4, 4, PixelFormat::Rgba8).unwrap();
assert_eq!(view.data().len(), 64);
}
#[test]
fn pixel_view_mut_modifies_original() {
let mut buf = PixelBuffer::zeroed(4, 4, PixelFormat::Rgba8);
{
let mut view = buf.as_view_mut();
view.data_mut()[0] = 42;
}
assert_eq!(buf.data[0], 42);
}
#[test]
fn buffer_pool_reuse() {
let mut pool = BufferPool::new(4);
let buf = pool.acquire(1024);
assert_eq!(buf.len(), 1024);
pool.release(buf);
assert_eq!(pool.len(), 1);
let buf2 = pool.acquire(512); assert_eq!(buf2.len(), 512);
assert_eq!(pool.len(), 0);
}
#[test]
fn buffer_pool_max_limit() {
let mut pool = BufferPool::new(2);
pool.release(vec![0; 100]);
pool.release(vec![0; 200]);
pool.release(vec![0; 300]); assert_eq!(pool.len(), 2);
}
#[test]
fn buffer_pool_zero_filled() {
let mut pool = BufferPool::new(4);
let mut buf = pool.acquire(16);
buf.iter_mut().for_each(|b| *b = 0xFF);
pool.release(buf);
let buf2 = pool.acquire(16);
assert!(
buf2.iter().all(|&b| b == 0),
"reused buffer should be zeroed"
);
}
}