#![allow(clippy::too_many_arguments)]
#![allow(dead_code)]
use crate::yuv_convert::{YuvMatrix, YuvRange};
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
use crate::yuv_convert_libyuv_simd;
#[cfg(any(target_arch = "x86_64", target_arch = "aarch64"))]
use archmage::prelude::*;
use imgref::ImgVec;
use rgb::RGB8;
#[allow(dead_code)]
struct YuvConstants {
yg: i32,
ygb: i32,
ub: i32,
ug: i32,
vg: i32,
vr: i32,
bb: i32,
bg: i32,
br: i32,
}
impl YuvConstants {
const BT709_FULL: Self = Self {
yg: 18997, ygb: -1160, ub: -128, ug: 14, vg: 34, vr: -115, bb: -128 * 128 + (-1160), bg: 14 * 128 + 34 * 128 + (-1160), br: -115 * 128 + (-1160), };
const BT709_LIMITED: Self = Self {
yg: 18997,
ygb: -1160,
ub: -128,
ug: 14,
vg: 34,
vr: -115,
bb: -17544,
bg: 4984,
br: -15880,
};
const BT601_FULL: Self = Self {
yg: 18997,
ygb: -1160,
ub: -132, ug: 52, vg: 104, vr: -102, bb: -132 * 128 + (-1160), bg: 52 * 128 + 104 * 128 + (-1160), br: -102 * 128 + (-1160), };
const BT601_LIMITED: Self = Self::BT601_FULL; }
fn get_constants(matrix: YuvMatrix, range: YuvRange) -> Option<&'static YuvConstants> {
match (matrix, range) {
(YuvMatrix::Bt709, YuvRange::Full) => Some(&YuvConstants::BT709_FULL),
(YuvMatrix::Bt709, YuvRange::Limited) => Some(&YuvConstants::BT709_LIMITED),
(YuvMatrix::Bt601, YuvRange::Full) => Some(&YuvConstants::BT601_FULL),
(YuvMatrix::Bt601, YuvRange::Limited) => Some(&YuvConstants::BT601_LIMITED),
_ => None, }
}
#[inline(always)]
fn yuv_pixel_with_constants(y: u8, u: u8, v: u8, c: &YuvConstants) -> RGB8 {
let y1 = ((y as u32) * 0x0101 * (c.yg as u32)) >> 16;
let y1 = y1 as i32;
let b_raw = (-((u as i32) * c.ub) + y1 + c.bb) >> 6;
let g_raw = (-((u as i32) * c.ug + (v as i32) * c.vg) + y1 + c.bg) >> 6;
let r_raw = (-((v as i32) * c.vr) + y1 + c.br) >> 6;
RGB8 {
r: r_raw.clamp(0, 255) as u8,
g: g_raw.clamp(0, 255) as u8,
b: b_raw.clamp(0, 255) as u8,
}
}
pub fn yuv420_to_rgb8(
y_plane: &[u8],
y_stride: usize,
u_plane: &[u8],
u_stride: usize,
v_plane: &[u8],
v_stride: usize,
width: usize,
height: usize,
range: YuvRange,
matrix: YuvMatrix,
) -> Option<ImgVec<RGB8>> {
#[cfg(target_arch = "x86_64")]
#[allow(clippy::collapsible_if)]
if matches!((range, matrix), (YuvRange::Full, YuvMatrix::Bt709)) {
if let Some(token) = Desktop64::summon() {
return yuv_convert_libyuv_simd::yuv420_to_rgb8_simd(
token, y_plane, y_stride, u_plane, u_stride, v_plane, v_stride, width, height,
range, matrix,
);
}
}
#[cfg(target_arch = "aarch64")]
#[allow(clippy::collapsible_if)]
if matches!((range, matrix), (YuvRange::Full, YuvMatrix::Bt709)) {
if let Some(token) = NeonToken::summon() {
return yuv_convert_libyuv_simd::yuv420_to_rgb8_simd_neon(
token, y_plane, y_stride, u_plane, u_stride, v_plane, v_stride, width, height,
range, matrix,
);
}
}
let c = get_constants(matrix, range)?;
let mut out = vec![RGB8::default(); width * height];
for y in 0..height {
let chroma_y = y / 2;
for x in 0..width {
let chroma_x = x / 2;
let y_val = y_plane[y * y_stride + x];
let u_val = u_plane[chroma_y * u_stride + chroma_x];
let v_val = v_plane[chroma_y * v_stride + chroma_x];
out[y * width + x] = yuv_pixel_with_constants(y_val, u_val, v_val, c);
}
}
Some(ImgVec::new(out, width, height))
}
pub fn yuv422_to_rgb8(
y_plane: &[u8],
y_stride: usize,
u_plane: &[u8],
u_stride: usize,
v_plane: &[u8],
v_stride: usize,
width: usize,
height: usize,
range: YuvRange,
matrix: YuvMatrix,
) -> Option<ImgVec<RGB8>> {
let c = get_constants(matrix, range)?;
let mut out = vec![RGB8::default(); width * height];
for y in 0..height {
for x in 0..width {
let chroma_x = x / 2;
let y_val = y_plane[y * y_stride + x];
let u_val = u_plane[y * u_stride + chroma_x];
let v_val = v_plane[y * v_stride + chroma_x];
out[y * width + x] = yuv_pixel_with_constants(y_val, u_val, v_val, c);
}
}
Some(ImgVec::new(out, width, height))
}
pub fn yuv444_to_rgb8(
y_plane: &[u8],
y_stride: usize,
u_plane: &[u8],
u_stride: usize,
v_plane: &[u8],
v_stride: usize,
width: usize,
height: usize,
range: YuvRange,
matrix: YuvMatrix,
) -> Option<ImgVec<RGB8>> {
let c = get_constants(matrix, range)?;
let mut out = vec![RGB8::default(); width * height];
for y in 0..height {
for x in 0..width {
let y_val = y_plane[y * y_stride + x];
let u_val = u_plane[y * u_stride + x];
let v_val = v_plane[y * v_stride + x];
out[y * width + x] = yuv_pixel_with_constants(y_val, u_val, v_val, c);
}
}
Some(ImgVec::new(out, width, height))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_bt709_full() {
let width = 4;
let height = 4;
let y_plane = vec![180u8; width * height];
let u_plane = vec![100u8; (width / 2) * (height / 2)];
let v_plane = vec![150u8; (width / 2) * (height / 2)];
let result = yuv420_to_rgb8(
&y_plane,
width,
&u_plane,
width / 2,
&v_plane,
width / 2,
width,
height,
YuvRange::Full,
YuvMatrix::Bt709,
)
.unwrap();
for pixel in result.buf() {
assert_eq!(pixel.r, 230);
assert_eq!(pixel.g, 185);
assert_eq!(pixel.b, 135);
}
}
#[test]
fn test_bt601_supported() {
let width = 4;
let height = 4;
let y_plane = vec![128u8; width * height];
let u_plane = vec![128u8; (width / 2) * (height / 2)];
let v_plane = vec![128u8; (width / 2) * (height / 2)];
let result = yuv420_to_rgb8(
&y_plane,
width,
&u_plane,
width / 2,
&v_plane,
width / 2,
width,
height,
YuvRange::Full,
YuvMatrix::Bt601,
);
assert!(result.is_some(), "BT.601 should be supported");
}
}