Skip to content

Commit

Permalink
added better tests
Browse files Browse the repository at this point in the history
  • Loading branch information
darioalessandro committed Dec 26, 2024
1 parent 8a2f4fc commit ed9d6b5
Show file tree
Hide file tree
Showing 4 changed files with 1,427 additions and 25 deletions.
87 changes: 62 additions & 25 deletions nokhwa-core/src/pixel_format.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@
*/
use crate::error::NokhwaError;
use crate::types::{
buf_mjpeg_to_rgb, buf_nv12_to_rgb, buf_yuyv422_to_rgb, color_frame_formats, frame_formats,
mjpeg_to_rgb, nv12_to_rgb, yuyv422_to_rgb, FrameFormat, Resolution,
buf_bgra_to_rgb, buf_mjpeg_to_rgb, buf_nv12_to_rgb, buf_yuyv422_to_rgb, color_frame_formats, frame_formats, mjpeg_to_rgb, nv12_to_rgb, yuyv422_to_rgb, FrameFormat, Resolution
};
use image::{Luma, LumaA, Pixel, Rgb, Rgba};
use std::fmt::Debug;
Expand Down Expand Up @@ -123,21 +122,7 @@ impl FormatDecoder for RgbFormat {
}
FrameFormat::NV12 => buf_nv12_to_rgb(resolution, data, dest, false),
FrameFormat::BGRA => {
if dest.len() != data.len() / 4 * 3 {
return Err(NokhwaError::ProcessFrameError {
src: fcc,
destination: "BGRA => RGB".to_string(),
error: "Bad buffer length".to_string(),
});
}

data.chunks_exact(4).enumerate().for_each(|(idx, px)| {
let index = idx * 3;
dest[index] = px[2];
dest[index + 1] = px[1];
dest[index + 2] = px[0];
});
Ok(())
buf_bgra_to_rgb(resolution, data, dest)
}
}
}
Expand Down Expand Up @@ -498,9 +483,9 @@ impl FormatDecoder for LumaAFormat {
/// let image: ImageBuffer<Rgb<u8>, Vec<u8>> = buffer.to_image::<YuyvFormat>();
/// ```
#[derive(Copy, Clone, Debug, Default, Hash, Ord, PartialOrd, Eq, PartialEq)]
pub struct I420Format;
pub struct YuyvFormat;

impl FormatDecoder for I420Format {
impl FormatDecoder for YuyvFormat {
// YUV 4:2:0 planar colors. but we need to change the image crate to use this format
type Output = Rgb<u8>;
const FORMATS: &'static [FrameFormat] = color_frame_formats();
Expand All @@ -513,8 +498,14 @@ impl FormatDecoder for I420Format {
) -> Result<Vec<u8>, NokhwaError> {
match fcc {
FrameFormat::YUYV => {
let mut i420 = vec![0u8; resolution.width() as usize * resolution.height() as usize * 3 / 2];
convert_yuyv_to_i420_direct(data, resolution.width() as usize, resolution.height() as usize, &mut i420)?;
let mut i420 =
vec![0u8; resolution.width() as usize * resolution.height() as usize * 3 / 2];
convert_yuyv_to_i420_direct(
data,
resolution.width() as usize,
resolution.height() as usize,
&mut i420,
)?;
Ok(i420)
}
_ => Err(NokhwaError::GeneralError(format!(
Expand Down Expand Up @@ -700,7 +691,25 @@ fn bgra_to_i420(bgra: &[u8], width: usize, height: usize, i420: &mut [u8]) {

#[cfg(test)]
mod tests {
fn assert_i420_equal_with_epsilon(epsilon_y: u8, epsilon_u: u8, epsilon_v: u8, actual: &[u8], expected: &[u8], width: usize, height: usize) {
use std::f32::EPSILON;

use image::ImageReader;

use super::RgbFormat;
use crate::{
pixel_format::{FormatDecoder, YuyvFormat},
types::buf_mjpeg_to_rgb,
};

fn assert_i420_equal_with_epsilon(
epsilon_y: u8,
epsilon_u: u8,
epsilon_v: u8,
actual: &[u8],
expected: &[u8],
width: usize,
height: usize,
) {
assert_eq!(actual.len(), expected.len());
let (actual_y, actual_uv) = actual.split_at(width * height);
let (actual_u, actual_v) = actual_uv.split_at(actual_uv.len() / 2);
Expand Down Expand Up @@ -743,7 +752,7 @@ mod tests {
}

#[test]
fn test_yuyv_to_i420() {
fn yuyv_to_i420() {
let yuyv = include_bytes!("../tests/assets/chichen_itza.yuyv");
let expected_i420 = include_bytes!("../tests/assets/chichen_itza.yuyv_i420");
let width = 1280;
Expand All @@ -755,7 +764,7 @@ mod tests {
}

#[test]
fn test_bgra_to_i420() {
fn bgra_to_i420() {
let bgra = include_bytes!("../tests/assets/chichen_itza.bgra");
let expected_i420 = include_bytes!("../tests/assets/chichen_itza.bgra_i420");
let width = 1280;
Expand All @@ -767,7 +776,7 @@ mod tests {
}

#[test]
fn test_nv12_to_i420() {
fn nv12_to_i420() {
let nv12 = include_bytes!("../tests/assets/chichen_itza.nv12");
let expected_i420 = include_bytes!("../tests/assets/chichen_itza.nv12_i420");
let width = 1280;
Expand All @@ -777,4 +786,32 @@ mod tests {
// I generated the expected I420 data using ffmpeg, so we allow some error in the conversion
assert_i420_equal_with_epsilon(0, 0, 0, &actual, expected_i420, width, height);
}

#[test]
fn buf_bgra_to_rgb() {
let input_bgra = include_bytes!("../tests/assets/chichen_itza.bgra");
let expected_rgb = include_bytes!("../tests/assets/chichen_itza.rgb");
let width = 1280;
let height = 680;
let mut actual = vec![0u8; input_bgra.len() / 4 * 3];

RgbFormat::write_output_buffer(
super::FrameFormat::BGRA,
super::Resolution::new(width as u32, height as u32),
input_bgra,
&mut actual,
)
.unwrap();
assert_eq!(actual.len(), expected_rgb.len());
for (i, (&actual, &expected)) in actual.iter().zip(expected_rgb.iter()).enumerate() {
let epsilon = 0;
assert!(
(actual as i32 - expected as i32).abs() <= epsilon as i32,
"Y plane mismatch at index {}: actual = {:#X}, expected = {:#X}",
i,
actual,
expected
);
}
}
}
54 changes: 54 additions & 0 deletions nokhwa-core/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1821,3 +1821,57 @@ pub fn buf_nv12_to_rgb(

Ok(())
}


#[allow(clippy::similar_names)]
#[inline]
pub fn buf_bgra_to_rgb(
resolution: Resolution,
data: &[u8],
out: &mut [u8],
) -> Result<(), NokhwaError> {
let width = resolution.width();
let height = resolution.height();

if width % 2 != 0 || height % 2 != 0 {
return Err(NokhwaError::ProcessFrameError {
src: FrameFormat::BGRA,
destination: "RGB".to_string(),
error: "bad resolution".to_string(),
});
}

let input_size = (width * height * 4) as usize; // BGRA is 4 bytes per pixel
let output_size = (width * height * 3) as usize; // RGB is 3 bytes per pixel

if data.len() != input_size {
return Err(NokhwaError::ProcessFrameError {
src: FrameFormat::BGRA,
destination: "RGB".to_string(),
error: "bad input buffer size".to_string(),
});
}

if out.len() != output_size {
return Err(NokhwaError::ProcessFrameError {
src: FrameFormat::BGRA,
destination: "RGB".to_string(),
error: "bad output buffer size".to_string(),
});
}

for (idx, chunk) in data.chunks_exact(4).enumerate() {
// BGRA Format: [Blue, Green, Red, Alpha]
let b = chunk[0];
let g = chunk[1];
let r = chunk[2];
// let _a = chunk[3]; // Alpha is ignored for RGB

let out_idx = idx * 3; // 3 bytes per pixel in RGB
out[out_idx] = r; // Red
out[out_idx + 1] = g; // Green
out[out_idx + 2] = b; // Blue
}

Ok(())
}
9 changes: 9 additions & 0 deletions nokhwa-core/tests/assets/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,12 @@ ffmpeg -i chichen_itza.jpg -f rawvideo -pix_fmt nv12 chichen_itza.nv12
ffmpeg -f rawvideo -pix_fmt nv12 -s 1280x680 -i chichen_itza.nv12 -f rawvideo -pix_fmt yuv420p chichen_itza.nv12_i420
```

### JPEG to RGB24
```
ffmpeg -i chichen_itza.jpg -pix_fmt rgb24 chichen_itza.rgb
```

### BGRA to RGB24
```
ffmpeg -f rawvideo -pix_fmt bgra -s 1280x680 -i chichen_itza.bgra -f rawvideo -pix_fmt rgb24 chichen_itza.rgb
```
1,302 changes: 1,302 additions & 0 deletions nokhwa-core/tests/assets/chichen_itza.rgb

Large diffs are not rendered by default.

0 comments on commit ed9d6b5

Please sign in to comment.