#![warn(unused)] #![deny(trivial_casts)] #![deny(trivial_numeric_casts)] #![deny(unsafe_code)] #![deny(unstable_features)] #![deny(unused_import_braces)] #![deny( clippy::complexity, clippy::correctness, clippy::perf, clippy::style, clippy::pedantic )] #![allow(clippy::too_many_lines)] // This requires effort to handle use dcp::{convert_image, ColorSpace, ErrorKind, ImageFormat, PixelFormat}; use dcv_color_primitives as dcp; use itertools::iproduct; const PIXEL_FORMATS: &[PixelFormat; 9] = &[ PixelFormat::Argb, PixelFormat::Bgra, PixelFormat::Bgr, PixelFormat::Rgba, PixelFormat::Rgb, PixelFormat::I444, PixelFormat::I422, PixelFormat::I420, PixelFormat::Nv12, ]; const COLOR_SPACES: &[ColorSpace; 5] = &[ ColorSpace::Rgb, ColorSpace::Bt601, ColorSpace::Bt709, ColorSpace::Bt601FR, ColorSpace::Bt709FR, ]; const PIXEL_FORMAT_I444: u32 = PixelFormat::I444 as u32; const COLOR_SPACE_RGB: u32 = ColorSpace::Rgb as u32; const PIXEL_FORMAT_ARGB: u32 = PixelFormat::Argb as u32; const PIXEL_FORMAT_BGRA: u32 = PixelFormat::Bgra as u32; const PIXEL_FORMAT_BGR: u32 = PixelFormat::Bgr as u32; const PIXEL_FORMAT_NV12: u32 = PixelFormat::Nv12 as u32; const PIXEL_FORMAT_RGB: u32 = PixelFormat::Rgb as u32; const PIXEL_FORMAT_RGBA: u32 = PixelFormat::Rgba as u32; macro_rules! set_expected { ($var:ident, $pred:expr, $status:path) => { if $var.is_ok() && $pred { $var = Err($status); } }; } fn is_valid_format(format: &ImageFormat, width: u32, height: u32) -> bool { match format.pixel_format { PixelFormat::I444 => format.num_planes != 3, PixelFormat::I422 | PixelFormat::I420 => { format.num_planes != 3 || (width & 1) == 1 || (height & 1) == 1 } PixelFormat::Nv12 => { (format.num_planes < 1 || format.num_planes > 2) || (width & 1) == 1 || (height & 1) == 1 } _ => format.num_planes != 1, } } fn get_depth(pixel_format: PixelFormat) -> usize { match pixel_format { PixelFormat::Bgra | PixelFormat::Argb => 4, _ => 3, } } fn check_err(result: ErrorKind, err: ErrorKind) { assert_eq!(result as u32, err as u32); } fn check_bounds( width: u32, height: u32, src_format: &ImageFormat, src_buffers: &[&[u8]], dst_format: &ImageFormat, dst_buffers: &mut [&mut [u8]], ) { if width == 0 || height == 0 { return; } check_err( convert_image( width, height, src_format, None, &src_buffers[..src_format.num_planes as usize - 1], dst_format, None, dst_buffers, ) .unwrap_err(), ErrorKind::NotEnoughData, ); check_err( convert_image( width, height, src_format, None, src_buffers, dst_format, None, &mut dst_buffers[..dst_format.num_planes as usize - 1], ) .unwrap_err(), ErrorKind::NotEnoughData, ); // Overflow for source plane data for i in 0..src_format.num_planes as usize { let mut src_vec = Vec::new(); for (c, v) in src_buffers.iter().enumerate() { if c == i { src_vec.push(&v[..0]); } else { src_vec.push(&v[..]); } } check_err( convert_image( width, height, src_format, None, &src_vec, dst_format, None, dst_buffers, ) .unwrap_err(), ErrorKind::NotEnoughData, ); } // Overflow for destination plane data for i in 0..dst_format.num_planes as usize { let mut dst_vec = Vec::new(); for (c, v) in dst_buffers.iter_mut().enumerate() { if c == i { dst_vec.push(&mut v[..0]); } else { dst_vec.push(&mut v[..]); } } check_err( convert_image( width, height, src_format, None, src_buffers, dst_format, None, &mut dst_vec[..], ) .unwrap_err(), ErrorKind::NotEnoughData, ); } } fn rgb_conversion_errors(src_pixel_format: PixelFormat, dst_pixel_format: PixelFormat) { const WIDTH: u32 = 33; const HEIGHT: u32 = 1; let src_depth = get_depth(src_pixel_format); let dst_depth = get_depth(dst_pixel_format); let src_stride: usize = (WIDTH as usize) * src_depth; let dst_stride: usize = (WIDTH as usize) * dst_depth; let src_size: usize = src_stride * (HEIGHT as usize); let dst_size: usize = dst_stride * (HEIGHT as usize); let src_image = vec![0_u8; src_size].into_boxed_slice(); let mut dst_image = vec![0_u8; dst_size].into_boxed_slice(); for num_planes in 0..4 { let src_format = ImageFormat { pixel_format: src_pixel_format, color_space: ColorSpace::Rgb, num_planes: 1, }; let dst_format = ImageFormat { pixel_format: dst_pixel_format, color_space: ColorSpace::Rgb, num_planes, }; let src_buffers = &[&src_image[..]]; let dst_buffers = &mut [&mut dst_image[..]]; let mut expected = Ok(()); set_expected!( expected, is_valid_format(&src_format, WIDTH, HEIGHT), ErrorKind::InvalidValue ); set_expected!( expected, is_valid_format(&dst_format, WIDTH, HEIGHT), ErrorKind::InvalidValue ); let status = convert_image( WIDTH, HEIGHT, &src_format, Some(&[src_stride; 1]), src_buffers, &dst_format, Some(&[dst_stride; 1]), dst_buffers, ); assert_eq!(expected.is_ok(), status.is_ok()); match status { Ok(()) => check_bounds( WIDTH, HEIGHT, &src_format, src_buffers, &dst_format, dst_buffers, ), Err(err) => check_err(err, expected.unwrap_err()), } } } fn rgb_to_yuv_errors(pixel_format: PixelFormat) { let (w, h) = match pixel_format { PixelFormat::I444 => (33, 3), _ => (34, 2), }; let cw = match pixel_format { PixelFormat::I420 => w / 2, _ => w, } as usize; let ch = match pixel_format { PixelFormat::I444 => h, _ => h / 2, } as usize; let y_size = (w as usize) * (h as usize); let uv_size = cw * ch; let slices = &[0, y_size + uv_size, y_size, 0]; let mut y_plane = match pixel_format { PixelFormat::Nv12 => vec![0_u8; y_size + uv_size].into_boxed_slice(), _ => vec![0_u8; y_size].into_boxed_slice(), }; let mut u_plane = vec![0_u8; uv_size].into_boxed_slice(); let mut v_plane = vec![0_u8; uv_size].into_boxed_slice(); for src_pixel_format in PIXEL_FORMATS { let src_stride = get_depth(*src_pixel_format) * (w as usize); let src_size = src_stride * (h as usize); let src_image = vec![0_u8; src_size].into_boxed_slice(); for (num_planes, src_color_space, dst_color_space) in iproduct!(0..4, COLOR_SPACES, COLOR_SPACES) { let src_format = ImageFormat { pixel_format: *src_pixel_format, color_space: *src_color_space, num_planes: 1, }; let dst_format = ImageFormat { pixel_format, color_space: *dst_color_space, num_planes, }; let src_buffers = &[&src_image[..]]; let mut dst_strides = Vec::with_capacity(1); let mut dst_buffers = Vec::with_capacity(1); dst_strides.push(w as usize); if let PixelFormat::Nv12 = pixel_format { dst_buffers.push(&mut y_plane[..slices[num_planes as usize]]); dst_buffers.push(&mut u_plane[..]); if num_planes > 1 { dst_strides.push(cw); } } else { dst_strides.push(cw); dst_strides.push(cw); dst_buffers.push(&mut y_plane[..]); dst_buffers.push(&mut u_plane[..]); dst_buffers.push(&mut v_plane[..]); } let mut expected = Ok(()); let src_pf = *src_pixel_format as u32; let src_cs = *src_color_space as u32; let dst_cs = *dst_color_space as u32; let src_pf_rgb = src_pf < PIXEL_FORMAT_I444; let src_cs_rgb = src_cs == COLOR_SPACE_RGB; let dst_cs_rgb = dst_cs == COLOR_SPACE_RGB; set_expected!(expected, !src_pf_rgb && src_cs_rgb, ErrorKind::InvalidValue); set_expected!(expected, src_pf_rgb && !src_cs_rgb, ErrorKind::InvalidValue); set_expected!(expected, dst_cs_rgb, ErrorKind::InvalidValue); set_expected!( expected, is_valid_format(&src_format, w, h), ErrorKind::InvalidValue ); set_expected!( expected, is_valid_format(&dst_format, w, h), ErrorKind::InvalidValue ); set_expected!( expected, src_pf != PIXEL_FORMAT_ARGB && src_pf != PIXEL_FORMAT_BGRA && src_pf != PIXEL_FORMAT_BGR, ErrorKind::InvalidOperation ); set_expected!( expected, src_cs != COLOR_SPACE_RGB, ErrorKind::InvalidOperation ); let status = convert_image( w, h, &src_format, None, src_buffers, &dst_format, Some(&dst_strides), &mut dst_buffers, ); assert_eq!(expected.is_ok(), status.is_ok()); match status { Ok(()) => check_bounds( w, h, &src_format, src_buffers, &dst_format, &mut dst_buffers, ), Err(err) => check_err(err, expected.unwrap_err()), } } } } fn yuv_to_rgb_errors(pixel_format: PixelFormat) { let (w, h) = match pixel_format { PixelFormat::I444 => (33, 3), _ => (34, 2), }; let cw = match pixel_format { PixelFormat::I420 => w / 2, _ => w, } as usize; let ch = match pixel_format { PixelFormat::I444 => h, _ => h / 2, } as usize; let y_size = (w as usize) * (h as usize); let uv_size = cw * ch; let dst_size = (w as usize) * (h as usize) * 4; let slices = &[0, y_size + uv_size, y_size, 0]; let y_plane = match pixel_format { PixelFormat::Nv12 => vec![0_u8; y_size + uv_size].into_boxed_slice(), _ => vec![0_u8; y_size].into_boxed_slice(), }; let u_plane = vec![0_u8; uv_size].into_boxed_slice(); let v_plane = vec![0_u8; uv_size].into_boxed_slice(); let mut dst_image = vec![0_u8; dst_size].into_boxed_slice(); for (num_planes, dst_pixel_format, dst_color_space, src_color_space) in iproduct!(0..4, PIXEL_FORMATS, COLOR_SPACES, COLOR_SPACES) { let src_format = ImageFormat { pixel_format, color_space: *src_color_space, num_planes, }; let dst_format = ImageFormat { pixel_format: *dst_pixel_format, color_space: *dst_color_space, num_planes: 1, }; let mut src_strides = Vec::with_capacity(1); let mut src_buffers = Vec::with_capacity(1); let dst_buffers = &mut [&mut dst_image[..]]; src_strides.push(w as usize); if let PixelFormat::Nv12 = pixel_format { src_buffers.push(&y_plane[..slices[num_planes as usize]]); src_buffers.push(&u_plane[..]); if num_planes > 1 { src_strides.push(cw); } } else { src_strides.push(cw); src_strides.push(cw); src_buffers.push(&y_plane[..]); src_buffers.push(&u_plane[..]); src_buffers.push(&v_plane[..]); } let mut expected = Ok(()); let src_pf = pixel_format as u32; let dst_pf = *dst_pixel_format as u32; let dst_cs = *dst_color_space as u32; let src_cs = *src_color_space as u32; let dst_pf_rgb = dst_pf < PIXEL_FORMAT_I444; let dst_cs_rgb = dst_cs == COLOR_SPACE_RGB; let src_cs_rgb = src_cs == COLOR_SPACE_RGB; set_expected!(expected, src_cs_rgb, ErrorKind::InvalidValue); set_expected!(expected, !dst_pf_rgb && dst_cs_rgb, ErrorKind::InvalidValue); set_expected!(expected, dst_pf_rgb && !dst_cs_rgb, ErrorKind::InvalidValue); set_expected!( expected, is_valid_format(&src_format, w, h), ErrorKind::InvalidValue ); set_expected!( expected, is_valid_format(&dst_format, w, h), ErrorKind::InvalidValue ); set_expected!( expected, !(dst_pf == PIXEL_FORMAT_BGRA || dst_pf == PIXEL_FORMAT_RGBA || (src_pf == PIXEL_FORMAT_NV12 && dst_pf == PIXEL_FORMAT_RGB)), ErrorKind::InvalidOperation ); set_expected!( expected, dst_cs != COLOR_SPACE_RGB, ErrorKind::InvalidOperation ); let status = convert_image( w, h, &src_format, Some(&src_strides), &src_buffers, &dst_format, None, dst_buffers, ); assert_eq!(expected.is_ok(), status.is_ok()); match status { Ok(()) => check_bounds(w, h, &src_format, &src_buffers, &dst_format, dst_buffers), Err(err) => check_err(err, expected.unwrap_err()), } } } #[cfg(test)] mod errors { use super::{rgb_conversion_errors, rgb_to_yuv_errors, yuv_to_rgb_errors, PixelFormat}; #[cfg(target_arch = "wasm32")] use wasm_bindgen_test::wasm_bindgen_test as test; #[test] fn nv12_to_rgb() { yuv_to_rgb_errors(PixelFormat::Nv12); } #[test] fn i420_to_rgb() { yuv_to_rgb_errors(PixelFormat::I420); } #[test] fn i444_to_rgb() { yuv_to_rgb_errors(PixelFormat::I444); } #[test] fn rgb_to_nv12() { rgb_to_yuv_errors(PixelFormat::Nv12); } #[test] fn rgb_to_i420() { rgb_to_yuv_errors(PixelFormat::I420); } #[test] fn rgb_to_i444() { rgb_to_yuv_errors(PixelFormat::I444); } #[test] fn rgb_to_bgra() { rgb_conversion_errors(PixelFormat::Rgb, PixelFormat::Bgra); } #[test] fn bgra_to_rgb() { rgb_conversion_errors(PixelFormat::Bgra, PixelFormat::Rgb); } #[test] fn bgr_to_rgb() { rgb_conversion_errors(PixelFormat::Bgr, PixelFormat::Rgb); } }