use crate::formats::RGBSource;
use crate::formats::rgb::RGB8Source;
use crate::formats::rgb2yuv::{write_yuv_by_pixel, write_yuv_scalar};
pub trait YUVSource {
#[must_use]
fn dimensions_i32(&self) -> (i32, i32) {
let (w, h) = self.dimensions();
(w as i32, h as i32)
}
#[must_use]
fn dimensions(&self) -> (usize, usize);
#[must_use]
fn strides(&self) -> (usize, usize, usize);
#[must_use]
fn strides_i32(&self) -> (i32, i32, i32) {
let (y, u, v) = self.strides();
(y as i32, u as i32, v as i32)
}
#[must_use]
fn y(&self) -> &[u8];
#[must_use]
fn u(&self) -> &[u8];
#[must_use]
fn v(&self) -> &[u8];
#[must_use]
fn rgb8_len(&self) -> usize {
let (w, h) = self.dimensions();
w * h * 3
}
#[must_use]
fn rgba8_len(&self) -> usize {
let (w, h) = self.dimensions();
w * h * 4
}
}
#[must_use]
pub struct YUVBuffer {
yuv: Vec<u8>,
width: usize,
height: usize,
}
impl YUVBuffer {
pub fn from_vec(yuv: Vec<u8>, width: usize, height: usize) -> Self {
assert_eq!(width % 2, 0, "width needs to be a multiple of 2");
assert_eq!(height % 2, 0, "height needs to be a multiple of 2");
assert_eq!(yuv.len(), (3 * (width * height)) / 2, "YUV buffer needs to be properly sized");
Self { yuv, width, height }
}
pub fn new(width: usize, height: usize) -> Self {
assert_eq!(width % 2, 0, "width needs to be a multiple of 2");
assert_eq!(height % 2, 0, "height needs to be a multiple of 2");
Self {
yuv: vec![0u8; (3 * (width * height)) / 2],
width,
height,
}
}
pub fn from_rgb_source(rgb: impl RGBSource) -> Self {
let mut rval = Self::new(rgb.dimensions().0, rgb.dimensions().1);
rval.read_rgb(rgb);
rval
}
pub fn from_rgb8_source(rgb: impl RGB8Source) -> Self {
let mut rval = Self::new(rgb.dimensions().0, rgb.dimensions().1);
rval.read_rgb8(rgb);
rval
}
#[allow(clippy::similar_names)]
pub fn read_rgb(&mut self, rgb: impl RGBSource) {
let dimensions = self.dimensions();
let u_base = self.width * self.height;
let v_base = u_base / 4;
let (y_buf, uv_buf) = self.yuv.split_at_mut(u_base);
let (u_buf, v_buf) = uv_buf.split_at_mut(v_base);
write_yuv_by_pixel(rgb, dimensions, y_buf, u_buf, v_buf);
}
#[allow(clippy::similar_names)]
pub fn read_rgb8(&mut self, rgb: impl RGB8Source) {
let dimensions = self.dimensions();
let u_base = self.width * self.height;
let v_base = u_base / 4;
let (y_buf, uv_buf) = self.yuv.split_at_mut(u_base);
let (u_buf, v_buf) = uv_buf.split_at_mut(v_base);
write_yuv_scalar(rgb, dimensions, y_buf, u_buf, v_buf);
}
}
impl YUVSource for YUVBuffer {
fn dimensions(&self) -> (usize, usize) {
(self.width, self.height)
}
fn strides(&self) -> (usize, usize, usize) {
(self.width, self.width / 2, self.width / 2)
}
fn y(&self) -> &[u8] {
&self.yuv[0..self.width * self.height]
}
fn u(&self) -> &[u8] {
let base_u = self.width * self.height;
&self.yuv[base_u..base_u + base_u / 4]
}
fn v(&self) -> &[u8] {
let base_u = self.width * self.height;
let base_v = base_u + base_u / 4;
&self.yuv[base_v..]
}
}
#[must_use]
#[derive(Clone, Copy, Debug)]
pub struct YUVSlices<'a> {
dimensions: (usize, usize),
yuv: (&'a [u8], &'a [u8], &'a [u8]),
strides: (usize, usize, usize),
}
impl<'a> YUVSlices<'a> {
pub fn new(yuv: (&'a [u8], &'a [u8], &'a [u8]), dimensions: (usize, usize), strides: (usize, usize, usize)) -> Self {
assert!(strides.0 >= dimensions.0);
assert!(strides.1 >= dimensions.0 / 2);
assert!(strides.2 >= dimensions.0 / 2);
assert_eq!(dimensions.1 * strides.0, yuv.0.len());
assert_eq!((dimensions.1 / 2) * strides.1, yuv.1.len());
assert_eq!((dimensions.1 / 2) * strides.2, yuv.2.len());
Self {
dimensions,
yuv,
strides,
}
}
}
impl YUVSource for YUVSlices<'_> {
fn dimensions(&self) -> (usize, usize) {
self.dimensions
}
fn strides(&self) -> (usize, usize, usize) {
self.strides
}
fn y(&self) -> &[u8] {
self.yuv.0
}
fn u(&self) -> &[u8] {
self.yuv.1
}
fn v(&self) -> &[u8] {
self.yuv.2
}
}
#[cfg(test)]
mod tests {
use super::{YUVBuffer, YUVSlices};
use crate::formats::yuv2rgb::{write_rgb8_f32x8, write_rgb8_scalar};
use crate::formats::{RgbSliceU8, YUVSource};
use rand::prelude::IteratorRandom;
use rand::rngs::ThreadRng;
#[test]
fn rgb_to_yuv_conversion_black_2x2() {
let rgb_source = RgbSliceU8::new(&[0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8, 0u8], (2, 2));
let yuv = YUVBuffer::from_rgb_source(rgb_source);
assert_eq!(yuv.y(), [16u8, 16u8, 16u8, 16u8]);
assert_eq!(yuv.u(), [128u8]);
assert_eq!(yuv.v(), [128u8]);
assert_eq!(yuv.strides_i32().0, 2);
assert_eq!(yuv.strides_i32().1, 1);
assert_eq!(yuv.strides_i32().2, 1);
}
#[test]
fn rgb_to_yuv_conversion_white_4x2() {
let data = &[
255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8, 255u8,
];
let rgb_source = RgbSliceU8::new(data, (4, 2));
let yuv = YUVBuffer::from_rgb_source(rgb_source);
assert_eq!(yuv.y(), [235u8, 235u8, 235u8, 235u8, 235u8, 235u8, 235u8, 235u8]);
assert_eq!(yuv.u(), [128u8, 128u8]);
assert_eq!(yuv.v(), [128u8, 128u8]);
assert_eq!(yuv.strides_i32().0, 4);
assert_eq!(yuv.strides_i32().1, 2);
assert_eq!(yuv.strides_i32().2, 2);
}
#[test]
fn rgb_to_yuv_conversion_red_2x4() {
let data = &[
255u8, 0u8, 0u8, 255u8, 0u8, 0u8, 255u8, 0u8, 0u8, 255u8, 0u8, 0u8, 255u8, 0u8, 0u8, 255u8, 0u8, 0u8, 255u8, 0u8,
0u8, 255u8, 0u8, 0u8,
];
let rgb_source = RgbSliceU8::new(data, (4, 2));
let yuv = YUVBuffer::from_rgb_source(rgb_source);
assert_eq!(yuv.y(), [81u8, 81u8, 81u8, 81u8, 81u8, 81u8, 81u8, 81u8]);
assert_eq!(yuv.u(), [90u8, 90u8]);
assert_eq!(yuv.v(), [239u8, 239u8]);
assert_eq!(yuv.strides_i32().0, 4);
assert_eq!(yuv.strides_i32().1, 2);
assert_eq!(yuv.strides_i32().2, 2);
}
#[test]
#[should_panic = "strides.0 >= dimensions.0"]
fn test_new_stride_less_than_width() {
let y = vec![0u8; 10];
let u = vec![0u8; 5];
let v = vec![0u8; 5];
let _ = YUVSlices::new((&y, &u, &v), (10, 1), (9, 5, 5));
}
#[test]
#[should_panic = "strides.1 >= dimensions.0 / 2"]
fn test_new_u_stride_less_than_half_width() {
let y = vec![0u8; 20];
let u = vec![0u8; 5];
let v = vec![0u8; 5];
let _ = YUVSlices::new((&y, &u, &v), (10, 2), (10, 4, 5));
}
#[test]
#[should_panic = "strides.2 >= dimensions.0 / 2"]
fn test_new_v_stride_less_than_half_width() {
let y = vec![0u8; 20];
let u = vec![0u8; 5];
let v = vec![0u8; 5];
let _ = YUVSlices::new((&y, &u, &v), (10, 2), (10, 5, 4));
}
#[test]
#[should_panic = "assertion `left == right` failed"]
fn test_new_y_length_not_matching() {
let y = vec![0u8; 19];
let u = vec![0u8; 5];
let v = vec![0u8; 5];
let _ = YUVSlices::new((&y, &u, &v), (10, 2), (10, 5, 5));
}
#[test]
#[should_panic = "assertion `left == right` failed"]
fn test_new_u_length_not_matching() {
let y = vec![0u8; 20];
let u = vec![0u8; 4];
let v = vec![0u8; 5];
let _ = YUVSlices::new((&y, &u, &v), (10, 2), (10, 5, 5));
}
#[test]
#[should_panic = "assertion `left == right` failed"]
fn test_new_v_length_not_matching() {
let y = vec![0u8; 20];
let u = vec![0u8; 5];
let v = vec![0u8; 4];
let _ = YUVSlices::new((&y, &u, &v), (10, 2), (10, 5, 5));
}
#[test]
fn test_new_valid() {
let y = vec![0u8; 20];
let u = vec![0u8; 5];
let v = vec![0u8; 5];
let _ = YUVSlices::new((&y, &u, &v), (10, 2), (10, 5, 5));
}
#[test]
fn test_write_rgb8_f32x8_spectrum() {
let mut rng = ThreadRng::default();
let dim = (8, 2);
let strides = (8, 4, 4);
for y in (0..=255u8).choose_multiple(&mut rng, 10) {
for u in (0..=255u8).choose_multiple(&mut rng, 10) {
for v in (0..=255u8).choose_multiple(&mut rng, 10) {
let (y_plane, u_plane, v_plane) = (vec![y; 16], vec![u; 4], vec![v; 4]);
let mut target = vec![0; dim.0 * dim.1 * 3];
write_rgb8_scalar(&y_plane, &u_plane, &v_plane, dim, strides, &mut target);
let mut target2 = vec![0; dim.0 * dim.1 * 3];
write_rgb8_f32x8(&y_plane, &u_plane, &v_plane, dim, strides, &mut target2);
for i in 0..3 {
let diff = (i32::from(target[i]) - i32::from(target2[i])).abs();
assert!(diff <= 1, "YUV: {:?} yielded different results", (y, u, v));
}
}
}
}
}
}