use crate::convert::ColorSpace;
use crate::yuv_simd;
#[derive(Clone, Copy)]
pub struct YuvMatrix {
pub kr: f32,
pub kb: f32,
pub limited: bool,
}
impl YuvMatrix {
pub const BT601: Self = Self {
kr: 0.299,
kb: 0.114,
limited: true,
};
pub const BT709: Self = Self {
kr: 0.2126,
kb: 0.0722,
limited: true,
};
pub const BT2020: Self = Self {
kr: 0.2627,
kb: 0.0593,
limited: true,
};
pub fn with_range(mut self, limited: bool) -> Self {
self.limited = limited;
self
}
pub fn from_color_space(cs: ColorSpace) -> Self {
match cs {
ColorSpace::Bt601Limited => Self::BT601.with_range(true),
ColorSpace::Bt601Full => Self::BT601.with_range(false),
ColorSpace::Bt709Limited => Self::BT709.with_range(true),
ColorSpace::Bt709Full => Self::BT709.with_range(false),
ColorSpace::Bt2020Limited => Self::BT2020.with_range(true),
ColorSpace::Bt2020Full => Self::BT2020.with_range(false),
}
}
}
pub(crate) const FP_SHIFT: i32 = 15;
pub(crate) const FP_ONE: i32 = 1 << FP_SHIFT;
pub(crate) const FP_HALF: i32 = 1 << (FP_SHIFT - 1);
#[derive(Clone, Copy, Debug)]
pub(crate) struct EncodeParams {
pub cy_r: i32,
pub cy_g: i32,
pub cy_b: i32,
pub y_bias: i32,
pub cb_r: i32,
pub cb_g: i32,
pub cb_b: i32,
pub cr_r: i32,
pub cr_g: i32,
pub cr_b: i32,
pub c_bias: i32,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct DecodeParams {
pub y_scale: i32,
pub y_off: i32,
pub cr_r: i32,
pub cb_b: i32,
pub cg_cr: i32, pub cg_cb: i32, }
fn q15(f: f32) -> i32 {
let v = f * FP_ONE as f32;
if v >= 0.0 {
(v + 0.5) as i32
} else {
-((-v + 0.5) as i32)
}
}
impl YuvMatrix {
pub(crate) fn encode_params(&self) -> EncodeParams {
let kr = self.kr;
let kb = self.kb;
let kg = 1.0 - kr - kb;
let (ys, cs, y_off, c_off) = if self.limited {
(219.0 / 255.0, 224.0 / 255.0, 16.0, 128.0)
} else {
(1.0, 1.0, 0.0, 128.0)
};
let cy_r = q15(kr * ys);
let cy_g = q15(kg * ys);
let cy_b = q15(kb * ys);
let cb_scale = cs / (2.0 * (1.0 - kb));
let cb_r = q15(cb_scale * -kr);
let cb_g = q15(cb_scale * -kg);
let cb_b = q15(cb_scale * (1.0 - kb));
let cr_scale = cs / (2.0 * (1.0 - kr));
let cr_r = q15(cr_scale * (1.0 - kr));
let cr_g = q15(cr_scale * -kg);
let cr_b = q15(cr_scale * -kb);
let y_bias = ((y_off * FP_ONE as f32).round() as i32) + FP_HALF;
let c_bias = ((c_off * FP_ONE as f32).round() as i32) + FP_HALF;
EncodeParams {
cy_r,
cy_g,
cy_b,
y_bias,
cb_r,
cb_g,
cb_b,
cr_r,
cr_g,
cr_b,
c_bias,
}
}
pub(crate) fn decode_params(&self) -> DecodeParams {
let kr = self.kr;
let kb = self.kb;
let kg = 1.0 - kr - kb;
if self.limited {
let y_scale = q15(255.0 / 219.0);
let cr_r = q15(2.0 * (1.0 - kr) * (255.0 / 224.0));
let cb_b = q15(2.0 * (1.0 - kb) * (255.0 / 224.0));
let cg_cr = q15((2.0 * kr * (1.0 - kr) / kg) * (255.0 / 224.0));
let cg_cb = q15((2.0 * kb * (1.0 - kb) / kg) * (255.0 / 224.0));
DecodeParams {
y_scale,
y_off: 16,
cr_r,
cb_b,
cg_cr,
cg_cb,
}
} else {
let y_scale = FP_ONE;
let cr_r = q15(2.0 * (1.0 - kr));
let cb_b = q15(2.0 * (1.0 - kb));
let cg_cr = q15(2.0 * kr * (1.0 - kr) / kg);
let cg_cb = q15(2.0 * kb * (1.0 - kb) / kg);
DecodeParams {
y_scale,
y_off: 0,
cr_r,
cb_b,
cg_cr,
cg_cb,
}
}
}
}
#[inline]
pub(crate) fn clamp_u8_i32(v: i32) -> u8 {
if v < 0 {
0
} else if v > 255 {
255
} else {
v as u8
}
}
pub fn rgb_to_yuv(r: u8, g: u8, b: u8, matrix: YuvMatrix) -> (u8, u8, u8) {
let p = matrix.encode_params();
rgb_to_yuv_fp(r, g, b, &p)
}
#[inline]
pub(crate) fn rgb_to_yuv_fp(r: u8, g: u8, b: u8, p: &EncodeParams) -> (u8, u8, u8) {
let ri = r as i32;
let gi = g as i32;
let bi = b as i32;
let y = (p.cy_r * ri + p.cy_g * gi + p.cy_b * bi + p.y_bias) >> FP_SHIFT;
let cb = (p.cb_r * ri + p.cb_g * gi + p.cb_b * bi + p.c_bias) >> FP_SHIFT;
let cr = (p.cr_r * ri + p.cr_g * gi + p.cr_b * bi + p.c_bias) >> FP_SHIFT;
(clamp_u8_i32(y), clamp_u8_i32(cb), clamp_u8_i32(cr))
}
pub fn yuv_to_rgb(y: u8, cb: u8, cr: u8, matrix: YuvMatrix) -> (u8, u8, u8) {
let d = matrix.decode_params();
yuv_to_rgb_fp(y, cb, cr, &d)
}
#[inline]
pub(crate) fn yuv_to_rgb_fp(y: u8, cb: u8, cr: u8, d: &DecodeParams) -> (u8, u8, u8) {
let yv = (y as i32 - d.y_off) * d.y_scale;
let cbv = cb as i32 - 128;
let crv = cr as i32 - 128;
let r = (yv + d.cr_r * crv + FP_HALF) >> FP_SHIFT;
let b = (yv + d.cb_b * cbv + FP_HALF) >> FP_SHIFT;
let g = (yv - d.cg_cr * crv - d.cg_cb * cbv + FP_HALF) >> FP_SHIFT;
(clamp_u8_i32(r), clamp_u8_i32(g), clamp_u8_i32(b))
}
pub(crate) fn yuv444_to_rgb24_scalar(
yp: &[u8],
up: &[u8],
vp: &[u8],
dst: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
let d = matrix.decode_params();
for row in 0..h {
let yrow = &yp[row * w..row * w + w];
let urow = &up[row * w..row * w + w];
let vrow = &vp[row * w..row * w + w];
let drow = &mut dst[row * w * 3..row * w * 3 + w * 3];
for col in 0..w {
let (r, g, b) = yuv_to_rgb_fp(yrow[col], urow[col], vrow[col], &d);
drow[col * 3] = r;
drow[col * 3 + 1] = g;
drow[col * 3 + 2] = b;
}
}
}
pub(crate) fn yuv422_to_rgb24_scalar(
yp: &[u8],
up: &[u8],
vp: &[u8],
dst: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
let cw = w.div_ceil(2);
let d = matrix.decode_params();
for row in 0..h {
let yrow = &yp[row * w..row * w + w];
let urow = &up[row * cw..row * cw + cw];
let vrow = &vp[row * cw..row * cw + cw];
let drow = &mut dst[row * w * 3..row * w * 3 + w * 3];
for col in 0..w {
let cc = col >> 1;
let (r, g, b) = yuv_to_rgb_fp(yrow[col], urow[cc], vrow[cc], &d);
drow[col * 3] = r;
drow[col * 3 + 1] = g;
drow[col * 3 + 2] = b;
}
}
}
pub(crate) fn yuv420_to_rgb24_scalar(
yp: &[u8],
up: &[u8],
vp: &[u8],
dst: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
let cw = w.div_ceil(2);
let d = matrix.decode_params();
for row in 0..h {
let cr = row >> 1;
let yrow = &yp[row * w..row * w + w];
let urow = &up[cr * cw..cr * cw + cw];
let vrow = &vp[cr * cw..cr * cw + cw];
let drow = &mut dst[row * w * 3..row * w * 3 + w * 3];
for col in 0..w {
let cc = col >> 1;
let (r, g, b) = yuv_to_rgb_fp(yrow[col], urow[cc], vrow[cc], &d);
drow[col * 3] = r;
drow[col * 3 + 1] = g;
drow[col * 3 + 2] = b;
}
}
}
pub(crate) fn rgb24_to_yuv444_scalar(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
let p = matrix.encode_params();
for row in 0..h {
encode444_row_from(
&src[row * w * 3..(row + 1) * w * 3],
&mut yp[row * w..(row + 1) * w],
&mut up[row * w..(row + 1) * w],
&mut vp[row * w..(row + 1) * w],
w,
0,
&p,
);
}
}
#[inline]
pub(crate) fn encode444_row_from(
srow: &[u8],
yrow: &mut [u8],
urow: &mut [u8],
vrow: &mut [u8],
w: usize,
x0: usize,
p: &EncodeParams,
) {
for col in x0..w {
let o = col * 3;
let (y, u, v) = rgb_to_yuv_fp(srow[o], srow[o + 1], srow[o + 2], p);
yrow[col] = y;
urow[col] = u;
vrow[col] = v;
}
}
pub(crate) fn rgb24_to_yuv422_scalar(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
let cw = w.div_ceil(2);
let p = matrix.encode_params();
for row in 0..h {
encode422_row_from(
&src[row * w * 3..(row + 1) * w * 3],
&mut yp[row * w..(row + 1) * w],
&mut up[row * cw..(row + 1) * cw],
&mut vp[row * cw..(row + 1) * cw],
w,
0,
&p,
);
}
}
#[inline]
pub(crate) fn encode422_row_from(
srow: &[u8],
yrow: &mut [u8],
urow: &mut [u8],
vrow: &mut [u8],
w: usize,
x0: usize,
p: &EncodeParams,
) {
debug_assert_eq!(x0 % 2, 0);
let full = w / 2;
for cc in x0 / 2..full {
let o = cc * 6;
let (y0, u0, v0) = rgb_to_yuv_fp(srow[o], srow[o + 1], srow[o + 2], p);
let (y1, u1, v1) = rgb_to_yuv_fp(srow[o + 3], srow[o + 4], srow[o + 5], p);
yrow[cc * 2] = y0;
yrow[cc * 2 + 1] = y1;
urow[cc] = ((u0 as i32 + u1 as i32 + 1) / 2) as u8;
vrow[cc] = ((v0 as i32 + v1 as i32 + 1) / 2) as u8;
}
if w % 2 == 1 && x0 < w {
let o = (w - 1) * 3;
let (y, u, v) = rgb_to_yuv_fp(srow[o], srow[o + 1], srow[o + 2], p);
yrow[w - 1] = y;
urow[full] = ((2 * u as i32 + 1) / 2) as u8;
vrow[full] = ((2 * v as i32 + 1) / 2) as u8;
}
}
pub(crate) fn rgb24_to_yuv420_scalar(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let p = matrix.encode_params();
for cr in 0..ch {
let row_a = cr * 2;
let urow = &mut up[cr * cw..(cr + 1) * cw];
let vrow = &mut vp[cr * cw..(cr + 1) * cw];
let sa = &src[row_a * w * 3..(row_a + 1) * w * 3];
if row_a + 1 < h {
let sb = &src[(row_a + 1) * w * 3..(row_a + 2) * w * 3];
let (ya, yb) = yp[row_a * w..(row_a + 2) * w].split_at_mut(w);
encode420_rows_from(sa, Some((sb, yb)), ya, urow, vrow, w, 0, &p);
} else {
let ya = &mut yp[row_a * w..(row_a + 1) * w];
encode420_rows_from(sa, None, ya, urow, vrow, w, 0, &p);
}
}
}
#[inline]
#[allow(clippy::too_many_arguments)]
pub(crate) fn encode420_rows_from(
sa: &[u8],
second: Option<(&[u8], &mut [u8])>,
ya: &mut [u8],
urow: &mut [u8],
vrow: &mut [u8],
w: usize,
x0: usize,
p: &EncodeParams,
) {
debug_assert_eq!(x0 % 2, 0);
let cw = w.div_ceil(2);
match second {
Some((sb, yb)) => {
for cc in x0 / 2..cw {
let c0 = cc * 2;
let c1 = (c0 + 1).min(w - 1);
let (y0, u0, v0) = rgb_to_yuv_fp(sa[c0 * 3], sa[c0 * 3 + 1], sa[c0 * 3 + 2], p);
let (y1, u1, v1) = rgb_to_yuv_fp(sa[c1 * 3], sa[c1 * 3 + 1], sa[c1 * 3 + 2], p);
let (y2, u2, v2) = rgb_to_yuv_fp(sb[c0 * 3], sb[c0 * 3 + 1], sb[c0 * 3 + 2], p);
let (y3, u3, v3) = rgb_to_yuv_fp(sb[c1 * 3], sb[c1 * 3 + 1], sb[c1 * 3 + 2], p);
ya[c0] = y0;
ya[c1] = y1;
yb[c0] = y2;
yb[c1] = y3;
urow[cc] = ((u0 as i32 + u1 as i32 + u2 as i32 + u3 as i32 + 2) / 4) as u8;
vrow[cc] = ((v0 as i32 + v1 as i32 + v2 as i32 + v3 as i32 + 2) / 4) as u8;
}
}
None => {
for cc in x0 / 2..cw {
let c0 = cc * 2;
let c1 = (c0 + 1).min(w - 1);
let (y0, u0, v0) = rgb_to_yuv_fp(sa[c0 * 3], sa[c0 * 3 + 1], sa[c0 * 3 + 2], p);
let (y1, u1, v1) = rgb_to_yuv_fp(sa[c1 * 3], sa[c1 * 3 + 1], sa[c1 * 3 + 2], p);
ya[c0] = y0;
ya[c1] = y1;
urow[cc] = ((2 * u0 as i32 + 2 * u1 as i32 + 2) / 4) as u8;
vrow[cc] = ((2 * v0 as i32 + 2 * v1 as i32 + 2) / 4) as u8;
}
}
}
}
pub fn yuv444_to_rgb24(
yp: &[u8],
up: &[u8],
vp: &[u8],
dst: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
debug_assert!(dst.len() >= w * h * 3);
yuv_simd::yuv444_to_rgb24(yp, up, vp, dst, w, h, matrix);
}
pub fn yuv422_to_rgb24(
yp: &[u8],
up: &[u8],
vp: &[u8],
dst: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
yuv_simd::yuv422_to_rgb24(yp, up, vp, dst, w, h, matrix);
}
pub fn yuv420_to_rgb24(
yp: &[u8],
up: &[u8],
vp: &[u8],
dst: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
yuv_simd::yuv420_to_rgb24(yp, up, vp, dst, w, h, matrix);
}
pub fn rgb24_to_yuv444(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
yuv_simd::rgb24_to_yuv444(src, yp, up, vp, w, h, matrix);
}
pub fn rgb24_to_yuv422(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
yuv_simd::rgb24_to_yuv422(src, yp, up, vp, w, h, matrix);
}
pub fn rgb24_to_yuv420(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
yuv_simd::rgb24_to_yuv420(src, yp, up, vp, w, h, matrix);
}
#[inline]
fn pair_average_row(src: &[u8], dst: &mut [u8], w: usize) {
let full = w / 2;
for (d, pair) in dst[..full].iter_mut().zip(src[..full * 2].chunks_exact(2)) {
*d = ((pair[0] as u16 + pair[1] as u16).div_ceil(2)) as u8;
}
if w % 2 == 1 {
dst[full] = src[w - 1];
}
}
pub fn chroma_444_to_422(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
for row in 0..h {
pair_average_row(
&src[row * w..row * w + w],
&mut dst[row * cw..row * cw + cw],
w,
);
}
}
pub fn chroma_422_to_444(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
for row in 0..h {
dup_bytes_2x_row(
&src[row * cw..row * cw + cw],
&mut dst[row * w..row * w + w],
w,
);
}
}
pub fn chroma_444_to_420(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
for cr in 0..ch {
let row_a = cr * 2;
let row_b = (cr * 2 + 1).min(h - 1);
let ra = &src[row_a * w..row_a * w + w];
let rb = &src[row_b * w..row_b * w + w];
let drow = &mut dst[cr * cw..cr * cw + cw];
let full = w / 2;
for cc in 0..full {
let s = ra[cc * 2] as u32
+ ra[cc * 2 + 1] as u32
+ rb[cc * 2] as u32
+ rb[cc * 2 + 1] as u32;
drow[cc] = ((s + 2) / 4) as u8;
}
if w % 2 == 1 {
let s = 2 * ra[w - 1] as u32 + 2 * rb[w - 1] as u32;
drow[full] = ((s + 2) / 4) as u8;
}
}
}
pub fn chroma_420_to_444(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
for row in 0..h {
let cr = row / 2;
dup_bytes_2x_row(
&src[cr * cw..cr * cw + cw],
&mut dst[row * w..row * w + w],
w,
);
}
}
#[inline]
fn dup_bytes_2x_row(src: &[u8], dst: &mut [u8], w: usize) {
let full = w / 2;
dup_bytes_2x(src, dst, full);
if w % 2 == 1 {
dst[w - 1] = src[full];
}
}
#[inline]
fn dup_bytes_2x(src: &[u8], dst: &mut [u8], n: usize) {
debug_assert!(src.len() >= n && dst.len() >= n * 2);
if crate::simd_dispatch::has_avx2() {
#[cfg(target_arch = "x86_64")]
unsafe {
dup_bytes_2x_avx2(src, dst, n);
return;
}
}
for i in 0..n {
dst[i * 2] = src[i];
dst[i * 2 + 1] = src[i];
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn dup_bytes_2x_avx2(src: &[u8], dst: &mut [u8], n: usize) {
use core::arch::x86_64::*;
let chunks = n / 16;
for c in 0..chunks {
let off = c * 16;
let v = _mm_loadu_si128(src.as_ptr().add(off) as *const __m128i);
let lo = _mm_unpacklo_epi8(v, v);
let hi = _mm_unpackhi_epi8(v, v);
_mm_storeu_si128(dst.as_mut_ptr().add(off * 2) as *mut __m128i, lo);
_mm_storeu_si128(dst.as_mut_ptr().add(off * 2 + 16) as *mut __m128i, hi);
}
let tail = chunks * 16;
for i in tail..n {
dst[i * 2] = src[i];
dst[i * 2 + 1] = src[i];
}
}
pub fn chroma_422_to_420(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
for cr in 0..ch {
let row_a = cr * 2;
let row_b = (cr * 2 + 1).min(h - 1);
for cc in 0..cw {
let a = src[row_a * cw + cc] as u16;
let b = src[row_b * cw + cc] as u16;
dst[cr * cw + cc] = (a + b).div_ceil(2) as u8;
}
}
}
pub fn chroma_420_to_422(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
for row in 0..h {
let cr = row / 2;
dst[row * cw..row * cw + cw].copy_from_slice(&src[cr * cw..cr * cw + cw]);
}
}
pub fn chroma_444_to_440(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let ch = h.div_ceil(2);
for cr in 0..ch {
let row_a = cr * 2;
let row_b = (cr * 2 + 1).min(h - 1);
for cc in 0..w {
let a = src[row_a * w + cc] as u16;
let b = src[row_b * w + cc] as u16;
dst[cr * w + cc] = (a + b).div_ceil(2) as u8;
}
}
}
pub fn chroma_440_to_444(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
for row in 0..h {
let cr = row / 2;
dst[row * w..row * w + w].copy_from_slice(&src[cr * w..cr * w + w]);
}
}
pub fn chroma_444_to_411(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w / 4;
debug_assert_eq!(w, cw * 4, "chroma_444_to_411: width must be /4");
for row in 0..h {
for cc in 0..cw {
let base = row * w + cc * 4;
let s = src[base] as u32
+ src[base + 1] as u32
+ src[base + 2] as u32
+ src[base + 3] as u32;
dst[row * cw + cc] = ((s + 2) / 4) as u8;
}
}
}
pub fn chroma_411_to_444(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w / 4;
debug_assert_eq!(w, cw * 4, "chroma_411_to_444: width must be /4");
for row in 0..h {
for cc in 0..cw {
let v = src[row * cw + cc];
let off = row * w + cc * 4;
dst[off] = v;
dst[off + 1] = v;
dst[off + 2] = v;
dst[off + 3] = v;
}
}
}
pub fn chroma_422_to_411(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let src_cw = w / 2;
let dst_cw = w / 4;
debug_assert_eq!(w, dst_cw * 4, "chroma_422_to_411: width must be /4");
for row in 0..h {
for cc in 0..dst_cw {
let base = row * src_cw + cc * 2;
let s = src[base] as u16 + src[base + 1] as u16;
dst[row * dst_cw + cc] = s.div_ceil(2) as u8;
}
}
}
pub fn chroma_411_to_422(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let src_cw = w / 4;
let dst_cw = w / 2;
debug_assert_eq!(w, src_cw * 4, "chroma_411_to_422: width must be /4");
for row in 0..h {
for cc in 0..src_cw {
let v = src[row * src_cw + cc];
let off = row * dst_cw + cc * 2;
dst[off] = v;
dst[off + 1] = v;
}
}
}
pub fn chroma_420_to_411(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let src_cw = w / 2;
let dst_cw = w / 4;
debug_assert_eq!(w, dst_cw * 4, "chroma_420_to_411: width must be /4");
for row in 0..h {
let src_row = row / 2;
for cc in 0..dst_cw {
let base = src_row * src_cw + cc * 2;
let s = src[base] as u16 + src[base + 1] as u16;
dst[row * dst_cw + cc] = s.div_ceil(2) as u8;
}
}
}
pub fn chroma_411_to_420(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let src_cw = w / 4;
let dst_cw = w / 2;
let dst_ch = h.div_ceil(2);
debug_assert_eq!(w, src_cw * 4, "chroma_411_to_420: width must be /4");
for cr in 0..dst_ch {
let row_a = cr * 2;
let row_b = (cr * 2 + 1).min(h - 1);
for cc in 0..src_cw {
let a = src[row_a * src_cw + cc] as u16;
let b = src[row_b * src_cw + cc] as u16;
let v = (a + b).div_ceil(2) as u8;
let off = cr * dst_cw + cc * 2;
dst[off] = v;
dst[off + 1] = v;
}
}
}
const FP16_SHIFT: i64 = 30;
const FP16_ONE: i64 = 1 << FP16_SHIFT;
const FP16_HALF: i64 = 1 << (FP16_SHIFT - 1);
fn q30(f: f64) -> i64 {
(f * FP16_ONE as f64).round() as i64
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct EncodeParams16 {
pub cy_r: i64,
pub cy_g: i64,
pub cy_b: i64,
pub y_bias: i64,
pub cb_r: i64,
pub cb_g: i64,
pub cb_b: i64,
pub cr_r: i64,
pub cr_g: i64,
pub cr_b: i64,
pub c_bias: i64,
}
#[derive(Clone, Copy, Debug)]
pub(crate) struct DecodeParams16 {
pub y_scale: i64,
pub y_off: i64,
pub cr_r: i64,
pub cb_b: i64,
pub cg_cr: i64,
pub cg_cb: i64,
}
impl YuvMatrix {
pub(crate) fn encode_params16(&self) -> EncodeParams16 {
let kr = self.kr as f64;
let kb = self.kb as f64;
let kg = 1.0 - kr - kb;
let (ys, cs, y_off) = if self.limited {
(56064.0 / 65535.0, 57344.0 / 65535.0, 4096.0)
} else {
(1.0, 1.0, 0.0)
};
let cb_scale = cs / (2.0 * (1.0 - kb));
let cr_scale = cs / (2.0 * (1.0 - kr));
EncodeParams16 {
cy_r: q30(kr * ys),
cy_g: q30(kg * ys),
cy_b: q30(kb * ys),
y_bias: (y_off as i64) * FP16_ONE + FP16_HALF,
cb_r: q30(cb_scale * -kr),
cb_g: q30(cb_scale * -kg),
cb_b: q30(cb_scale * (1.0 - kb)),
cr_r: q30(cr_scale * (1.0 - kr)),
cr_g: q30(cr_scale * -kg),
cr_b: q30(cr_scale * -kb),
c_bias: 32768 * FP16_ONE + FP16_HALF,
}
}
pub(crate) fn decode_params16(&self) -> DecodeParams16 {
let kr = self.kr as f64;
let kb = self.kb as f64;
let kg = 1.0 - kr - kb;
let (ys, cs, y_off) = if self.limited {
(65535.0 / 56064.0, 65535.0 / 57344.0, 4096)
} else {
(1.0, 1.0, 0)
};
DecodeParams16 {
y_scale: q30(ys),
y_off,
cr_r: q30(2.0 * (1.0 - kr) * cs),
cb_b: q30(2.0 * (1.0 - kb) * cs),
cg_cr: q30((2.0 * kr * (1.0 - kr) / kg) * cs),
cg_cb: q30((2.0 * kb * (1.0 - kb) / kg) * cs),
}
}
}
#[inline]
fn clamp_u16_i64(v: i64) -> u16 {
if v < 0 {
0
} else if v > 65535 {
65535
} else {
v as u16
}
}
pub fn rgb48_pixel_to_yuv16(r: u16, g: u16, b: u16, matrix: YuvMatrix) -> (u16, u16, u16) {
rgb48_to_yuv16_fp(r, g, b, &matrix.encode_params16())
}
#[inline]
pub(crate) fn rgb48_to_yuv16_fp(r: u16, g: u16, b: u16, p: &EncodeParams16) -> (u16, u16, u16) {
let ri = r as i64;
let gi = g as i64;
let bi = b as i64;
let y = (p.cy_r * ri + p.cy_g * gi + p.cy_b * bi + p.y_bias) >> FP16_SHIFT;
let cb = (p.cb_r * ri + p.cb_g * gi + p.cb_b * bi + p.c_bias) >> FP16_SHIFT;
let cr = (p.cr_r * ri + p.cr_g * gi + p.cr_b * bi + p.c_bias) >> FP16_SHIFT;
(clamp_u16_i64(y), clamp_u16_i64(cb), clamp_u16_i64(cr))
}
pub fn yuv16_pixel_to_rgb48(y: u16, cb: u16, cr: u16, matrix: YuvMatrix) -> (u16, u16, u16) {
yuv16_to_rgb48_fp(y, cb, cr, &matrix.decode_params16())
}
#[inline]
pub(crate) fn yuv16_to_rgb48_fp(y: u16, cb: u16, cr: u16, d: &DecodeParams16) -> (u16, u16, u16) {
let yv = (y as i64 - d.y_off) * d.y_scale;
let cbv = cb as i64 - 32768;
let crv = cr as i64 - 32768;
let r = (yv + d.cr_r * crv + FP16_HALF) >> FP16_SHIFT;
let b = (yv + d.cb_b * cbv + FP16_HALF) >> FP16_SHIFT;
let g = (yv - d.cg_cr * crv - d.cg_cb * cbv + FP16_HALF) >> FP16_SHIFT;
(clamp_u16_i64(r), clamp_u16_i64(g), clamp_u16_i64(b))
}
pub fn yuv444p16_to_rgb48(
yp: &[u8],
up: &[u8],
vp: &[u8],
dst: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
let d = matrix.decode_params16();
for i in 0..w * h {
let (r, g, b) = yuv16_to_rgb48_fp(
c16_get(yp, i) as u16,
c16_get(up, i) as u16,
c16_get(vp, i) as u16,
&d,
);
c16_put(dst, i * 3, r as u32);
c16_put(dst, i * 3 + 1, g as u32);
c16_put(dst, i * 3 + 2, b as u32);
}
}
pub fn rgb48_to_yuv444p16(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
matrix: YuvMatrix,
) {
let p = matrix.encode_params16();
for i in 0..w * h {
let (y, cb, cr) = rgb48_to_yuv16_fp(
c16_get(src, i * 3) as u16,
c16_get(src, i * 3 + 1) as u16,
c16_get(src, i * 3 + 2) as u16,
&p,
);
c16_put(yp, i, y as u32);
c16_put(up, i, cb as u32);
c16_put(vp, i, cr as u32);
}
}
#[inline]
pub(crate) fn rgb16_to_luma16_fp(r: u16, g: u16, b: u16, p: &EncodeParams16) -> u16 {
let y = (p.cy_r * r as i64 + p.cy_g * g as i64 + p.cy_b * b as i64 + p.y_bias) >> FP16_SHIFT;
clamp_u16_i64(y)
}
#[inline]
fn packed16_to_luma16(src: &[u8], comps: usize, dst: &mut [u8], pixels: usize, p: &EncodeParams16) {
for i in 0..pixels {
let r = c16_get(src, i * comps) as i64;
let g = c16_get(src, i * comps + 1) as i64;
let b = c16_get(src, i * comps + 2) as i64;
let y = (p.cy_r * r + p.cy_g * g + p.cy_b * b + p.y_bias) >> FP16_SHIFT;
c16_put(dst, i, clamp_u16_i64(y) as u32);
}
}
pub fn rgb48le_to_gray16le(src: &[u8], dst: &mut [u8], pixels: usize, matrix: YuvMatrix) {
packed16_to_luma16(src, 3, dst, pixels, &matrix.encode_params16());
}
pub fn rgba64le_to_gray16le(src: &[u8], dst: &mut [u8], pixels: usize, matrix: YuvMatrix) {
packed16_to_luma16(src, 4, dst, pixels, &matrix.encode_params16());
}
#[inline]
fn c16_get(buf: &[u8], i: usize) -> u32 {
(buf[i * 2] as u32) | ((buf[i * 2 + 1] as u32) << 8)
}
#[inline]
fn c16_put(buf: &mut [u8], i: usize, v: u32) {
buf[i * 2] = (v & 0xFF) as u8;
buf[i * 2 + 1] = (v >> 8) as u8;
}
pub fn chroma16le_444_to_422(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
for row in 0..h {
pair_average_row16(
&src[row * w * 2..(row * w + w) * 2],
&mut dst[row * cw * 2..(row * cw + cw) * 2],
w,
);
}
}
#[inline]
fn pair_average_row16(src: &[u8], dst: &mut [u8], w: usize) {
let full = w / 2;
for cc in 0..full {
let a = c16_get(src, cc * 2);
let b = c16_get(src, cc * 2 + 1);
c16_put(dst, cc, (a + b).div_ceil(2));
}
if w % 2 == 1 {
c16_put(dst, full, c16_get(src, w - 1));
}
}
#[inline]
fn dup_words_2x_row(src: &[u8], dst: &mut [u8], w: usize) {
let full = w / 2;
for cc in 0..full {
let v = c16_get(src, cc);
c16_put(dst, cc * 2, v);
c16_put(dst, cc * 2 + 1, v);
}
if w % 2 == 1 {
c16_put(dst, w - 1, c16_get(src, full));
}
}
pub fn chroma16le_422_to_444(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
for row in 0..h {
dup_words_2x_row(
&src[row * cw * 2..(row * cw + cw) * 2],
&mut dst[row * w * 2..(row * w + w) * 2],
w,
);
}
}
pub fn chroma16le_444_to_420(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
for cr in 0..ch {
let row_a = cr * 2;
let row_b = (cr * 2 + 1).min(h - 1);
let full = w / 2;
for cc in 0..full {
let s = c16_get(src, row_a * w + cc * 2)
+ c16_get(src, row_a * w + cc * 2 + 1)
+ c16_get(src, row_b * w + cc * 2)
+ c16_get(src, row_b * w + cc * 2 + 1);
c16_put(dst, cr * cw + cc, (s + 2) / 4);
}
if w % 2 == 1 {
let s = 2 * c16_get(src, row_a * w + w - 1) + 2 * c16_get(src, row_b * w + w - 1);
c16_put(dst, cr * cw + full, (s + 2) / 4);
}
}
}
pub fn chroma16le_420_to_444(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
for row in 0..h {
let cr = row / 2;
dup_words_2x_row(
&src[cr * cw * 2..(cr * cw + cw) * 2],
&mut dst[row * w * 2..(row * w + w) * 2],
w,
);
}
}
pub fn chroma16le_422_to_420(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
for cr in 0..ch {
let row_a = cr * 2;
let row_b = (cr * 2 + 1).min(h - 1);
for cc in 0..cw {
let a = c16_get(src, row_a * cw + cc);
let b = c16_get(src, row_b * cw + cc);
c16_put(dst, cr * cw + cc, (a + b).div_ceil(2));
}
}
}
pub fn chroma16le_420_to_422(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let cw = w.div_ceil(2);
for row in 0..h {
let cr = row / 2;
dst[row * cw * 2..(row * cw + cw) * 2]
.copy_from_slice(&src[cr * cw * 2..(cr * cw + cw) * 2]);
}
}
pub fn chroma16le_444_to_440(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
let ch = h.div_ceil(2);
for cr in 0..ch {
let row_a = cr * 2;
let row_b = (cr * 2 + 1).min(h - 1);
for cc in 0..w {
let a = c16_get(src, row_a * w + cc);
let b = c16_get(src, row_b * w + cc);
c16_put(dst, cr * w + cc, (a + b).div_ceil(2));
}
}
}
pub fn chroma16le_440_to_444(src: &[u8], dst: &mut [u8], w: usize, h: usize) {
for row in 0..h {
let cr = row / 2;
dst[row * w * 2..(row + 1) * w * 2].copy_from_slice(&src[cr * w * 2..(cr + 1) * w * 2]);
}
}
pub fn nv12_uv_split(uv: &[u8], up: &mut [u8], vp: &mut [u8], cw: usize, ch: usize) {
deinterleave_u8_pair(uv, up, vp, cw * ch);
}
pub fn nv21_vu_split(vu: &[u8], up: &mut [u8], vp: &mut [u8], cw: usize, ch: usize) {
deinterleave_u8_pair(vu, vp, up, cw * ch);
}
#[inline]
fn deinterleave_u8_pair(src: &[u8], a: &mut [u8], b: &mut [u8], n: usize) {
debug_assert!(src.len() >= n * 2 && a.len() >= n && b.len() >= n);
if crate::simd_dispatch::has_avx2() {
#[cfg(target_arch = "x86_64")]
unsafe {
deinterleave_u8_pair_avx2(src, a, b, n);
return;
}
}
for i in 0..n {
a[i] = src[i * 2];
b[i] = src[i * 2 + 1];
}
}
#[cfg(target_arch = "x86_64")]
#[target_feature(enable = "avx2")]
unsafe fn deinterleave_u8_pair_avx2(src: &[u8], a: &mut [u8], b: &mut [u8], n: usize) {
use core::arch::x86_64::*;
const MASK_EVEN: [u8; 16] = [
0, 2, 4, 6, 8, 10, 12, 14, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
];
const MASK_ODD: [u8; 16] = [
1, 3, 5, 7, 9, 11, 13, 15, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80,
];
let m_even = _mm_loadu_si128(MASK_EVEN.as_ptr() as *const __m128i);
let m_odd = _mm_loadu_si128(MASK_ODD.as_ptr() as *const __m128i);
let chunks = n / 16;
for c in 0..chunks {
let soff = c * 32;
let doff = c * 16;
let v0 = _mm_loadu_si128(src.as_ptr().add(soff) as *const __m128i);
let v1 = _mm_loadu_si128(src.as_ptr().add(soff + 16) as *const __m128i);
let a0 = _mm_shuffle_epi8(v0, m_even);
let b0 = _mm_shuffle_epi8(v0, m_odd);
let a1 = _mm_shuffle_epi8(v1, m_even);
let b1 = _mm_shuffle_epi8(v1, m_odd);
let a_comb = _mm_unpacklo_epi64(a0, a1);
let b_comb = _mm_unpacklo_epi64(b0, b1);
_mm_storeu_si128(a.as_mut_ptr().add(doff) as *mut __m128i, a_comb);
_mm_storeu_si128(b.as_mut_ptr().add(doff) as *mut __m128i, b_comb);
}
let tail = chunks * 16;
for i in tail..n {
a[i] = src[i * 2];
b[i] = src[i * 2 + 1];
}
}
pub fn nv12_uv_merge(up: &[u8], vp: &[u8], uv: &mut [u8], cw: usize, ch: usize) {
for i in 0..cw * ch {
uv[i * 2] = up[i];
uv[i * 2 + 1] = vp[i];
}
}
pub fn nv21_vu_merge(up: &[u8], vp: &[u8], vu: &mut [u8], cw: usize, ch: usize) {
for i in 0..cw * ch {
vu[i * 2] = vp[i];
vu[i * 2 + 1] = up[i];
}
}
pub fn yuyv422_to_yuv422p(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
) {
debug_assert!(w % 2 == 0, "YUYV requires even width");
let cw = w / 2;
debug_assert!(src.len() >= w * h * 2);
debug_assert!(yp.len() >= w * h);
debug_assert!(up.len() >= cw * h);
debug_assert!(vp.len() >= cw * h);
for row in 0..h {
let s = &src[row * w * 2..row * w * 2 + w * 2];
let y_row = &mut yp[row * w..row * w + w];
let u_row = &mut up[row * cw..row * cw + cw];
let v_row = &mut vp[row * cw..row * cw + cw];
for cc in 0..cw {
y_row[cc * 2] = s[cc * 4];
u_row[cc] = s[cc * 4 + 1];
y_row[cc * 2 + 1] = s[cc * 4 + 2];
v_row[cc] = s[cc * 4 + 3];
}
}
}
pub fn uyvy422_to_yuv422p(
src: &[u8],
yp: &mut [u8],
up: &mut [u8],
vp: &mut [u8],
w: usize,
h: usize,
) {
debug_assert!(w % 2 == 0, "UYVY requires even width");
let cw = w / 2;
debug_assert!(src.len() >= w * h * 2);
debug_assert!(yp.len() >= w * h);
debug_assert!(up.len() >= cw * h);
debug_assert!(vp.len() >= cw * h);
for row in 0..h {
let s = &src[row * w * 2..row * w * 2 + w * 2];
let y_row = &mut yp[row * w..row * w + w];
let u_row = &mut up[row * cw..row * cw + cw];
let v_row = &mut vp[row * cw..row * cw + cw];
for cc in 0..cw {
u_row[cc] = s[cc * 4];
y_row[cc * 2] = s[cc * 4 + 1];
v_row[cc] = s[cc * 4 + 2];
y_row[cc * 2 + 1] = s[cc * 4 + 3];
}
}
}
pub fn yuv422p_to_yuyv422(yp: &[u8], up: &[u8], vp: &[u8], dst: &mut [u8], w: usize, h: usize) {
debug_assert!(w % 2 == 0, "YUYV requires even width");
let cw = w / 2;
debug_assert!(yp.len() >= w * h);
debug_assert!(up.len() >= cw * h);
debug_assert!(vp.len() >= cw * h);
debug_assert!(dst.len() >= w * h * 2);
for row in 0..h {
let y_row = &yp[row * w..row * w + w];
let u_row = &up[row * cw..row * cw + cw];
let v_row = &vp[row * cw..row * cw + cw];
let d = &mut dst[row * w * 2..row * w * 2 + w * 2];
for cc in 0..cw {
d[cc * 4] = y_row[cc * 2];
d[cc * 4 + 1] = u_row[cc];
d[cc * 4 + 2] = y_row[cc * 2 + 1];
d[cc * 4 + 3] = v_row[cc];
}
}
}
pub fn yuv422p_to_uyvy422(yp: &[u8], up: &[u8], vp: &[u8], dst: &mut [u8], w: usize, h: usize) {
debug_assert!(w % 2 == 0, "UYVY requires even width");
let cw = w / 2;
debug_assert!(yp.len() >= w * h);
debug_assert!(up.len() >= cw * h);
debug_assert!(vp.len() >= cw * h);
debug_assert!(dst.len() >= w * h * 2);
for row in 0..h {
let y_row = &yp[row * w..row * w + w];
let u_row = &up[row * cw..row * cw + cw];
let v_row = &vp[row * cw..row * cw + cw];
let d = &mut dst[row * w * 2..row * w * 2 + w * 2];
for cc in 0..cw {
d[cc * 4] = u_row[cc];
d[cc * 4 + 1] = y_row[cc * 2];
d[cc * 4 + 2] = v_row[cc];
d[cc * 4 + 3] = y_row[cc * 2 + 1];
}
}
}
pub fn yuyv_uyvy_swap(buf: &mut [u8]) {
debug_assert!(
buf.len() % 4 == 0,
"packed 4:2:2 buffer must be a multiple of 4 bytes"
);
let mut i = 0;
while i + 3 < buf.len() {
buf.swap(i, i + 1);
buf.swap(i + 2, i + 3);
i += 4;
}
}
pub fn limited_to_full_luma(plane: &mut [u8]) {
const SCALE: i32 = ((255 * FP_ONE as i64) / 219) as i32;
for b in plane.iter_mut() {
let v = (*b as i32 - 16) * SCALE + FP_HALF;
let v = v >> FP_SHIFT;
*b = clamp_u8_i32(v);
}
}
pub fn limited_to_full_chroma(plane: &mut [u8]) {
const SCALE: i32 = ((255 * FP_ONE as i64) / 224) as i32;
for b in plane.iter_mut() {
let v = (*b as i32 - 128) * SCALE + (128 << FP_SHIFT) + FP_HALF;
let v = v >> FP_SHIFT;
*b = clamp_u8_i32(v);
}
}
pub fn full_to_limited_luma(plane: &mut [u8]) {
const SCALE: i32 = ((219 * FP_ONE as i64) / 255) as i32;
for b in plane.iter_mut() {
let v = (*b as i32) * SCALE + (16 << FP_SHIFT) + FP_HALF;
let v = v >> FP_SHIFT;
*b = clamp_u8_i32(v);
}
}
pub fn full_to_limited_chroma(plane: &mut [u8]) {
const SCALE: i32 = ((224 * FP_ONE as i64) / 255) as i32;
for b in plane.iter_mut() {
let v = (*b as i32 - 128) * SCALE + (128 << FP_SHIFT) + FP_HALF;
let v = v >> FP_SHIFT;
*b = clamp_u8_i32(v);
}
}
pub fn depth_down_le16_plane(src: &[u8], dst: &mut [u8], count: usize, bits: u32) {
debug_assert!((9..=16).contains(&bits), "depth_down: bits out of range");
let shift = bits - 8;
let mask: u32 = (1u32 << bits) - 1;
for (d, w) in dst[..count]
.iter_mut()
.zip(src[..count * 2].chunks_exact(2))
{
let v = u16::from_le_bytes([w[0], w[1]]) as u32 & mask;
*d = (v >> shift) as u8;
}
}
pub fn depth_up_8_to_le16_plane(src: &[u8], dst: &mut [u8], count: usize, bits: u32) {
debug_assert!((9..=16).contains(&bits), "depth_up: bits out of range");
let shift = bits - 8;
for i in 0..count {
let b = src[i] as u32;
let v = (b << shift) | (b >> (8 - shift));
dst[i * 2] = (v & 0xFF) as u8;
dst[i * 2 + 1] = (v >> 8) as u8;
}
}
pub fn rgb24_to_gray8(src: &[u8], dst: &mut [u8], pixels: usize, matrix: YuvMatrix) {
let p = matrix.encode_params();
for (px, d) in src[..pixels * 3]
.chunks_exact(3)
.zip(dst[..pixels].iter_mut())
{
let r = px[0] as i32;
let g = px[1] as i32;
let b = px[2] as i32;
let y = (p.cy_r * r + p.cy_g * g + p.cy_b * b + p.y_bias) >> FP_SHIFT;
*d = clamp_u8_i32(y);
}
}
pub fn depth_rescale_le16_plane(
src: &[u8],
dst: &mut [u8],
count: usize,
src_bits: u32,
dst_bits: u32,
) {
debug_assert!(
(9..=16).contains(&src_bits) && (9..=16).contains(&dst_bits),
"depth_rescale: bits out of range"
);
let mask: u32 = ((1u64 << src_bits) - 1) as u32;
let src_pairs = src[..count * 2].chunks_exact(2);
let dst_pairs = dst[..count * 2].chunks_exact_mut(2);
if dst_bits > src_bits {
let d = dst_bits - src_bits;
let r = src_bits - d;
for (s, o) in src_pairs.zip(dst_pairs) {
let v = (((s[1] as u32) << 8) | s[0] as u32) & mask;
let out = (v << d) | (v >> r);
o[0] = (out & 0xFF) as u8;
o[1] = (out >> 8) as u8;
}
} else {
let d = src_bits - dst_bits; for (s, o) in src_pairs.zip(dst_pairs) {
let v = (((s[1] as u32) << 8) | s[0] as u32) & mask;
let out = v >> d;
o[0] = (out & 0xFF) as u8;
o[1] = (out >> 8) as u8;
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn depth_rescale_widen_narrow_roundtrip_exact() {
for (b, wide) in [(10u32, 12u32), (10, 16), (12, 16)] {
let n = 1usize << b;
let mut src = vec![0u8; n * 2];
for (i, v) in (0..n as u32).enumerate() {
src[i * 2] = (v & 0xFF) as u8;
src[i * 2 + 1] = (v >> 8) as u8;
}
let mut up = vec![0u8; n * 2];
let mut back = vec![0u8; n * 2];
depth_rescale_le16_plane(&src, &mut up, n, b, wide);
let last = (up[(n - 1) * 2] as u32) | ((up[(n - 1) * 2 + 1] as u32) << 8);
assert_eq!(last, (1u32 << wide) - 1, "{b} → {wide} peak");
depth_rescale_le16_plane(&up, &mut back, n, wide, b);
assert_eq!(src, back, "{b} → {wide} → {b} round-trip");
}
}
#[test]
fn depth_plane_roundtrip_all_8bit_values_10_and_12() {
for bits in [10u32, 12] {
let src: Vec<u8> = (0u16..=255).map(|v| v as u8).collect();
let mut hi = vec![0u8; src.len() * 2];
let mut back = vec![0u8; src.len()];
depth_up_8_to_le16_plane(&src, &mut hi, src.len(), bits);
depth_down_le16_plane(&hi, &mut back, src.len(), bits);
assert_eq!(src, back, "round-trip failed at bits={bits}");
}
}
#[test]
fn depth_up_reaches_full_scale() {
for bits in [10u32, 12] {
let src = [0x00u8, 0xFF];
let mut hi = vec![0u8; 4];
depth_up_8_to_le16_plane(&src, &mut hi, 2, bits);
let zero = u16::from_le_bytes([hi[0], hi[1]]);
let peak = u16::from_le_bytes([hi[2], hi[3]]);
assert_eq!(zero, 0, "0x00 must map to 0");
assert_eq!(peak, (1u16 << bits) - 1, "0xFF must reach full scale");
}
}
#[test]
fn bt709_limited_known_vectors() {
let m = YuvMatrix::BT709.with_range(true);
let cases = [
(0u8, 0u8, 0u8, 16u8, 128u8, 128u8),
(255, 255, 255, 235, 128, 128),
(255, 0, 0, 63, 102, 240),
(0, 255, 0, 173, 42, 26),
(0, 0, 255, 32, 240, 118),
(128, 128, 128, 126, 128, 128),
];
for (r, g, b, ey, eu, ev) in cases {
let (y, u, v) = rgb_to_yuv(r, g, b, m);
assert!(
(y as i32 - ey as i32).abs() <= 1,
"Y mismatch for ({r},{g},{b}): got {y}, want {ey}"
);
assert!(
(u as i32 - eu as i32).abs() <= 1,
"U mismatch for ({r},{g},{b}): got {u}, want {eu}"
);
assert!(
(v as i32 - ev as i32).abs() <= 1,
"V mismatch for ({r},{g},{b}): got {v}, want {ev}"
);
}
}
#[test]
fn yuyv_uyvy_swap_pins_byte_positions() {
let mut buf = vec![10u8, 20, 30, 40, 50, 60, 70, 80];
yuyv_uyvy_swap(&mut buf);
assert_eq!(buf, vec![20u8, 10, 40, 30, 60, 50, 80, 70]);
yuyv_uyvy_swap(&mut buf);
assert_eq!(buf, vec![10u8, 20, 30, 40, 50, 60, 70, 80]);
}
#[test]
fn yuyv422_to_yuv422p_pins_byte_positions() {
let src = [1u8, 2, 3, 4, 5, 6, 7, 8];
let mut yp = [0u8; 4];
let mut up = [0u8; 2];
let mut vp = [0u8; 2];
yuyv422_to_yuv422p(&src, &mut yp, &mut up, &mut vp, 4, 1);
assert_eq!(yp, [1, 3, 5, 7]);
assert_eq!(up, [2, 6]);
assert_eq!(vp, [4, 8]);
let mut out = [0u8; 8];
yuv422p_to_yuyv422(&yp, &up, &vp, &mut out, 4, 1);
assert_eq!(out, src);
}
#[test]
fn uyvy422_to_yuv422p_pins_byte_positions() {
let src = [2u8, 1, 4, 3, 6, 5, 8, 7];
let mut yp = [0u8; 4];
let mut up = [0u8; 2];
let mut vp = [0u8; 2];
uyvy422_to_yuv422p(&src, &mut yp, &mut up, &mut vp, 4, 1);
assert_eq!(yp, [1, 3, 5, 7]);
assert_eq!(up, [2, 6]);
assert_eq!(vp, [4, 8]);
let mut out = [0u8; 8];
yuv422p_to_uyvy422(&yp, &up, &vp, &mut out, 4, 1);
assert_eq!(out, src);
}
#[test]
fn bt709_limited_decode_vectors() {
let m = YuvMatrix::BT709.with_range(true);
let rgbs = [
(0u8, 0u8, 0u8),
(255, 255, 255),
(255, 0, 0),
(0, 255, 0),
(0, 0, 255),
(128, 128, 128),
];
for (r, g, b) in rgbs {
let (y, u, v) = rgb_to_yuv(r, g, b, m);
let (r2, g2, b2) = yuv_to_rgb(y, u, v, m);
assert!(
(r2 as i32 - r as i32).abs() <= 2,
"R mismatch: ({r},{g},{b}) → ({r2},{g2},{b2})"
);
assert!(
(g2 as i32 - g as i32).abs() <= 2,
"G mismatch: ({r},{g},{b}) → ({r2},{g2},{b2})"
);
assert!(
(b2 as i32 - b as i32).abs() <= 2,
"B mismatch: ({r},{g},{b}) → ({r2},{g2},{b2})"
);
}
}
}