#![allow(dead_code)]
use crate::error::Result;
use crate::foundation::alloc::{checked_size, checked_size_2d, try_alloc_zeroed};
use crate::foundation::consts::{
YCBCR_B_TO_CB, YCBCR_B_TO_CR, YCBCR_B_TO_Y, YCBCR_CB_TO_B, YCBCR_CB_TO_G, YCBCR_CB_TO_R,
YCBCR_CR_TO_B, YCBCR_CR_TO_G, YCBCR_CR_TO_R, YCBCR_G_TO_CB, YCBCR_G_TO_CR, YCBCR_G_TO_Y,
YCBCR_R_TO_CB, YCBCR_R_TO_CR, YCBCR_R_TO_Y, YCBCR_Y_TO_B, YCBCR_Y_TO_G, YCBCR_Y_TO_R,
};
use crate::types::PixelFormat;
use multiversed::multiversed;
use wide::{f32x4, f32x8};
#[inline]
#[must_use]
pub fn rgb_to_ycbcr(r: u8, g: u8, b: u8) -> (u8, u8, u8) {
let rf = r as f32;
let gf = g as f32;
let bf = b as f32;
let y = YCBCR_R_TO_Y.mul_add(rf, YCBCR_G_TO_Y.mul_add(gf, YCBCR_B_TO_Y * bf));
let cb = YCBCR_R_TO_CB.mul_add(
rf,
YCBCR_G_TO_CB.mul_add(gf, YCBCR_B_TO_CB.mul_add(bf, 128.0)),
);
let cr = YCBCR_R_TO_CR.mul_add(
rf,
YCBCR_G_TO_CR.mul_add(gf, YCBCR_B_TO_CR.mul_add(bf, 128.0)),
);
(
y.round().clamp(0.0, 255.0) as u8,
cb.round().clamp(0.0, 255.0) as u8,
cr.round().clamp(0.0, 255.0) as u8,
)
}
#[inline]
#[must_use]
pub fn ycbcr_to_rgb(y: u8, cb: u8, cr: u8) -> (u8, u8, u8) {
let yf = y as f32;
let cbf = cb as f32 - 128.0;
let crf = cr as f32 - 128.0;
let r = YCBCR_Y_TO_R.mul_add(yf, YCBCR_CB_TO_R.mul_add(cbf, YCBCR_CR_TO_R * crf));
let g = YCBCR_Y_TO_G.mul_add(yf, YCBCR_CB_TO_G.mul_add(cbf, YCBCR_CR_TO_G * crf));
let b = YCBCR_Y_TO_B.mul_add(yf, YCBCR_CB_TO_B.mul_add(cbf, YCBCR_CR_TO_B * crf));
(
r.round().clamp(0.0, 255.0) as u8,
g.round().clamp(0.0, 255.0) as u8,
b.round().clamp(0.0, 255.0) as u8,
)
}
#[inline]
#[must_use]
pub fn rgb_to_ycbcr_f32(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let y = YCBCR_R_TO_Y.mul_add(r, YCBCR_G_TO_Y.mul_add(g, YCBCR_B_TO_Y * b));
let cb = YCBCR_R_TO_CB.mul_add(r, YCBCR_G_TO_CB.mul_add(g, YCBCR_B_TO_CB.mul_add(b, 128.0)));
let cr = YCBCR_R_TO_CR.mul_add(r, YCBCR_G_TO_CR.mul_add(g, YCBCR_B_TO_CR.mul_add(b, 128.0)));
(y, cb, cr)
}
#[inline]
#[must_use]
pub fn ycbcr_to_rgb_f32(y: f32, cb: f32, cr: f32) -> (f32, f32, f32) {
let cbf = cb - 128.0;
let crf = cr - 128.0;
let r = YCBCR_Y_TO_R * y + YCBCR_CB_TO_R * cbf + YCBCR_CR_TO_R * crf;
let g = YCBCR_Y_TO_G * y + YCBCR_CB_TO_G * cbf + YCBCR_CR_TO_G * crf;
let b = YCBCR_Y_TO_B * y + YCBCR_CB_TO_B * cbf + YCBCR_CR_TO_B * crf;
(r, g, b)
}
pub fn convert_rgb_to_ycbcr_buffer(buffer: &mut [u8]) {
assert!(buffer.len() % 3 == 0, "Buffer length must be multiple of 3");
for chunk in buffer.chunks_exact_mut(3) {
let (y, cb, cr) = rgb_to_ycbcr(chunk[0], chunk[1], chunk[2]);
chunk[0] = y;
chunk[1] = cb;
chunk[2] = cr;
}
}
pub fn convert_ycbcr_to_rgb_buffer(buffer: &mut [u8]) {
assert!(buffer.len() % 3 == 0, "Buffer length must be multiple of 3");
for chunk in buffer.chunks_exact_mut(3) {
let (r, g, b) = ycbcr_to_rgb(chunk[0], chunk[1], chunk[2]);
chunk[0] = r;
chunk[1] = g;
chunk[2] = b;
}
}
mod simd {
use super::*;
#[inline]
pub fn rgb_to_ycbcr_x4(r: [u8; 4], g: [u8; 4], b: [u8; 4]) -> ([u8; 4], [u8; 4], [u8; 4]) {
let rf = f32x4::from([r[0] as f32, r[1] as f32, r[2] as f32, r[3] as f32]);
let gf = f32x4::from([g[0] as f32, g[1] as f32, g[2] as f32, g[3] as f32]);
let bf = f32x4::from([b[0] as f32, b[1] as f32, b[2] as f32, b[3] as f32]);
let r_to_y = f32x4::splat(YCBCR_R_TO_Y);
let g_to_y = f32x4::splat(YCBCR_G_TO_Y);
let b_to_y = f32x4::splat(YCBCR_B_TO_Y);
let r_to_cb = f32x4::splat(YCBCR_R_TO_CB);
let g_to_cb = f32x4::splat(YCBCR_G_TO_CB);
let b_to_cb = f32x4::splat(YCBCR_B_TO_CB);
let r_to_cr = f32x4::splat(YCBCR_R_TO_CR);
let g_to_cr = f32x4::splat(YCBCR_G_TO_CR);
let b_to_cr = f32x4::splat(YCBCR_B_TO_CR);
let offset_128 = f32x4::splat(128.0);
let y = r_to_y.mul_add(rf, g_to_y.mul_add(gf, b_to_y * bf));
let cb = r_to_cb.mul_add(rf, g_to_cb.mul_add(gf, b_to_cb.mul_add(bf, offset_128)));
let cr = r_to_cr.mul_add(rf, g_to_cr.mul_add(gf, b_to_cr.mul_add(bf, offset_128)));
let y_arr = y.to_array();
let cb_arr = cb.to_array();
let cr_arr = cr.to_array();
let clamp = |v: f32| v.round().clamp(0.0, 255.0) as u8;
(
[
clamp(y_arr[0]),
clamp(y_arr[1]),
clamp(y_arr[2]),
clamp(y_arr[3]),
],
[
clamp(cb_arr[0]),
clamp(cb_arr[1]),
clamp(cb_arr[2]),
clamp(cb_arr[3]),
],
[
clamp(cr_arr[0]),
clamp(cr_arr[1]),
clamp(cr_arr[2]),
clamp(cr_arr[3]),
],
)
}
#[inline]
pub fn ycbcr_to_rgb_x4(y: [u8; 4], cb: [u8; 4], cr: [u8; 4]) -> ([u8; 4], [u8; 4], [u8; 4]) {
let yf = f32x4::from([y[0] as f32, y[1] as f32, y[2] as f32, y[3] as f32]);
let cbf = f32x4::from([cb[0] as f32, cb[1] as f32, cb[2] as f32, cb[3] as f32])
- f32x4::splat(128.0);
let crf = f32x4::from([cr[0] as f32, cr[1] as f32, cr[2] as f32, cr[3] as f32])
- f32x4::splat(128.0);
let y_to_r = f32x4::splat(YCBCR_Y_TO_R);
let cb_to_r = f32x4::splat(YCBCR_CB_TO_R);
let cr_to_r = f32x4::splat(YCBCR_CR_TO_R);
let y_to_g = f32x4::splat(YCBCR_Y_TO_G);
let cb_to_g = f32x4::splat(YCBCR_CB_TO_G);
let cr_to_g = f32x4::splat(YCBCR_CR_TO_G);
let y_to_b = f32x4::splat(YCBCR_Y_TO_B);
let cb_to_b = f32x4::splat(YCBCR_CB_TO_B);
let cr_to_b = f32x4::splat(YCBCR_CR_TO_B);
let r = y_to_r.mul_add(yf, cb_to_r.mul_add(cbf, cr_to_r * crf));
let g = y_to_g.mul_add(yf, cb_to_g.mul_add(cbf, cr_to_g * crf));
let b = y_to_b.mul_add(yf, cb_to_b.mul_add(cbf, cr_to_b * crf));
let r_arr = r.to_array();
let g_arr = g.to_array();
let b_arr = b.to_array();
let clamp = |v: f32| v.round().clamp(0.0, 255.0) as u8;
(
[
clamp(r_arr[0]),
clamp(r_arr[1]),
clamp(r_arr[2]),
clamp(r_arr[3]),
],
[
clamp(g_arr[0]),
clamp(g_arr[1]),
clamp(g_arr[2]),
clamp(g_arr[3]),
],
[
clamp(b_arr[0]),
clamp(b_arr[1]),
clamp(b_arr[2]),
clamp(b_arr[3]),
],
)
}
}
pub fn rgb_to_ycbcr_planes(
rgb: &[u8],
width: usize,
height: usize,
) -> Result<(Vec<u8>, Vec<u8>, Vec<u8>)> {
let num_pixels = checked_size_2d(width, height)?;
let expected_len = checked_size(width, height, 3)?;
assert_eq!(rgb.len(), expected_len);
let mut y_plane = try_alloc_zeroed(num_pixels, "YCbCr Y plane")?;
let mut cb_plane = try_alloc_zeroed(num_pixels, "YCbCr Cb plane")?;
let mut cr_plane = try_alloc_zeroed(num_pixels, "YCbCr Cr plane")?;
let chunks = num_pixels / 4;
for chunk in 0..chunks {
let base = chunk * 4;
let rgb_base = base * 3;
let r = [
rgb[rgb_base],
rgb[rgb_base + 3],
rgb[rgb_base + 6],
rgb[rgb_base + 9],
];
let g = [
rgb[rgb_base + 1],
rgb[rgb_base + 4],
rgb[rgb_base + 7],
rgb[rgb_base + 10],
];
let b = [
rgb[rgb_base + 2],
rgb[rgb_base + 5],
rgb[rgb_base + 8],
rgb[rgb_base + 11],
];
let (y, cb, cr) = simd::rgb_to_ycbcr_x4(r, g, b);
y_plane[base..base + 4].copy_from_slice(&y);
cb_plane[base..base + 4].copy_from_slice(&cb);
cr_plane[base..base + 4].copy_from_slice(&cr);
}
for i in (chunks * 4)..num_pixels {
let (y, cb, cr) = rgb_to_ycbcr(rgb[i * 3], rgb[i * 3 + 1], rgb[i * 3 + 2]);
y_plane[i] = y;
cb_plane[i] = cb;
cr_plane[i] = cr;
}
Ok((y_plane, cb_plane, cr_plane))
}
pub fn ycbcr_planes_to_rgb(
y_plane: &[u8],
cb_plane: &[u8],
cr_plane: &[u8],
width: usize,
height: usize,
) -> Result<Vec<u8>> {
let num_pixels = checked_size_2d(width, height)?;
assert_eq!(y_plane.len(), num_pixels);
assert_eq!(cb_plane.len(), num_pixels);
assert_eq!(cr_plane.len(), num_pixels);
let rgb_size = checked_size(width, height, 3)?;
let mut rgb = try_alloc_zeroed(rgb_size, "RGB output buffer")?;
let chunks = num_pixels / 4;
for chunk in 0..chunks {
let base = chunk * 4;
let rgb_base = base * 3;
let y = [
y_plane[base],
y_plane[base + 1],
y_plane[base + 2],
y_plane[base + 3],
];
let cb = [
cb_plane[base],
cb_plane[base + 1],
cb_plane[base + 2],
cb_plane[base + 3],
];
let cr = [
cr_plane[base],
cr_plane[base + 1],
cr_plane[base + 2],
cr_plane[base + 3],
];
let (r, g, b) = simd::ycbcr_to_rgb_x4(y, cb, cr);
rgb[rgb_base] = r[0];
rgb[rgb_base + 1] = g[0];
rgb[rgb_base + 2] = b[0];
rgb[rgb_base + 3] = r[1];
rgb[rgb_base + 4] = g[1];
rgb[rgb_base + 5] = b[1];
rgb[rgb_base + 6] = r[2];
rgb[rgb_base + 7] = g[2];
rgb[rgb_base + 8] = b[2];
rgb[rgb_base + 9] = r[3];
rgb[rgb_base + 10] = g[3];
rgb[rgb_base + 11] = b[3];
}
for i in (chunks * 4)..num_pixels {
let (r, g, b) = ycbcr_to_rgb(y_plane[i], cb_plane[i], cr_plane[i]);
rgb[i * 3] = r;
rgb[i * 3 + 1] = g;
rgb[i * 3 + 2] = b;
}
Ok(rgb)
}
#[multiversed]
pub fn ycbcr_planes_f32_to_rgb_u8(
y_plane: &[f32],
cb_plane: &[f32],
cr_plane: &[f32],
rgb: &mut [u8],
) {
debug_assert_eq!(y_plane.len(), cb_plane.len());
debug_assert_eq!(y_plane.len(), cr_plane.len());
debug_assert_eq!(rgb.len(), y_plane.len() * 3);
let num_pixels = y_plane.len();
const CR_TO_R: f32 = 1.402;
const CB_TO_G: f32 = -0.344136;
const CR_TO_G: f32 = -0.714136;
const CB_TO_B: f32 = 1.772;
let cr_to_r = f32x8::splat(CR_TO_R);
let cb_to_g = f32x8::splat(CB_TO_G);
let cr_to_g = f32x8::splat(CR_TO_G);
let cb_to_b = f32x8::splat(CB_TO_B);
let offset = f32x8::splat(128.0);
let zero = f32x8::splat(0.0);
let max_val = f32x8::splat(255.0);
let y_chunks = y_plane.chunks_exact(8);
let cb_chunks = cb_plane.chunks_exact(8);
let cr_chunks = cr_plane.chunks_exact(8);
let rgb_chunks = rgb.chunks_exact_mut(24);
let y_remainder = y_chunks.remainder();
let cb_remainder = cb_chunks.remainder();
let cr_remainder = cr_chunks.remainder();
for (((y_chunk, cb_chunk), cr_chunk), rgb_chunk) in
y_chunks.zip(cb_chunks).zip(cr_chunks).zip(rgb_chunks)
{
let y = f32x8::from(<[f32; 8]>::try_from(y_chunk).unwrap());
let cb = f32x8::from(<[f32; 8]>::try_from(cb_chunk).unwrap());
let cr = f32x8::from(<[f32; 8]>::try_from(cr_chunk).unwrap());
let r = cr_to_r.mul_add(cr, y + offset).max(zero).min(max_val);
let g = cb_to_g
.mul_add(cb, cr_to_g.mul_add(cr, y + offset))
.max(zero)
.min(max_val);
let b = cb_to_b.mul_add(cb, y + offset).max(zero).min(max_val);
let r_arr: [f32; 8] = r.into();
let g_arr: [f32; 8] = g.into();
let b_arr: [f32; 8] = b.into();
rgb_chunk[0] = r_arr[0] as u8;
rgb_chunk[1] = g_arr[0] as u8;
rgb_chunk[2] = b_arr[0] as u8;
rgb_chunk[3] = r_arr[1] as u8;
rgb_chunk[4] = g_arr[1] as u8;
rgb_chunk[5] = b_arr[1] as u8;
rgb_chunk[6] = r_arr[2] as u8;
rgb_chunk[7] = g_arr[2] as u8;
rgb_chunk[8] = b_arr[2] as u8;
rgb_chunk[9] = r_arr[3] as u8;
rgb_chunk[10] = g_arr[3] as u8;
rgb_chunk[11] = b_arr[3] as u8;
rgb_chunk[12] = r_arr[4] as u8;
rgb_chunk[13] = g_arr[4] as u8;
rgb_chunk[14] = b_arr[4] as u8;
rgb_chunk[15] = r_arr[5] as u8;
rgb_chunk[16] = g_arr[5] as u8;
rgb_chunk[17] = b_arr[5] as u8;
rgb_chunk[18] = r_arr[6] as u8;
rgb_chunk[19] = g_arr[6] as u8;
rgb_chunk[20] = b_arr[6] as u8;
rgb_chunk[21] = r_arr[7] as u8;
rgb_chunk[22] = g_arr[7] as u8;
rgb_chunk[23] = b_arr[7] as u8;
}
let chunks_processed = (num_pixels / 8) * 8;
let rgb_start = chunks_processed * 3;
for (i, ((y, cb), cr)) in y_remainder
.iter()
.zip(cb_remainder.iter())
.zip(cr_remainder.iter())
.enumerate()
{
let r = CR_TO_R.mul_add(*cr, *y);
let g = CB_TO_G.mul_add(*cb, CR_TO_G.mul_add(*cr, *y));
let b_val = CB_TO_B.mul_add(*cb, *y);
let idx = rgb_start + i * 3;
rgb[idx] = (r + 128.0).clamp(0.0, 255.0) as u8;
rgb[idx + 1] = (g + 128.0).clamp(0.0, 255.0) as u8;
rgb[idx + 2] = (b_val + 128.0).clamp(0.0, 255.0) as u8;
}
}
#[inline(never)]
pub fn ycbcr_planes_f32_to_rgb_f32(
y_plane: &[f32],
cb_plane: &[f32],
cr_plane: &[f32],
rgb: &mut [f32],
) {
debug_assert_eq!(y_plane.len(), cb_plane.len());
debug_assert_eq!(y_plane.len(), cr_plane.len());
debug_assert_eq!(rgb.len(), y_plane.len() * 3);
let num_pixels = y_plane.len();
const CR_TO_R: f32 = 1.402;
const CB_TO_G: f32 = -0.344136;
const CR_TO_G: f32 = -0.714136;
const CB_TO_B: f32 = 1.772;
{
let cr_to_r = f32x8::splat(CR_TO_R);
let cb_to_g = f32x8::splat(CB_TO_G);
let cr_to_g = f32x8::splat(CR_TO_G);
let cb_to_b = f32x8::splat(CB_TO_B);
let offset = f32x8::splat(128.0);
let scale = f32x8::splat(1.0 / 255.0);
let zero = f32x8::splat(0.0);
let one = f32x8::splat(1.0);
let chunks = num_pixels / 8;
for chunk in 0..chunks {
let base = chunk * 8;
let y = f32x8::from(<[f32; 8]>::try_from(&y_plane[base..base + 8]).unwrap());
let cb = f32x8::from(<[f32; 8]>::try_from(&cb_plane[base..base + 8]).unwrap());
let cr = f32x8::from(<[f32; 8]>::try_from(&cr_plane[base..base + 8]).unwrap());
let r = (cr_to_r.mul_add(cr, y + offset) * scale).max(zero).min(one);
let g = (cb_to_g.mul_add(cb, cr_to_g.mul_add(cr, y + offset)) * scale)
.max(zero)
.min(one);
let b = (cb_to_b.mul_add(cb, y + offset) * scale).max(zero).min(one);
let r_arr: [f32; 8] = r.into();
let g_arr: [f32; 8] = g.into();
let b_arr: [f32; 8] = b.into();
for j in 0..8 {
let idx = (base + j) * 3;
rgb[idx] = r_arr[j];
rgb[idx + 1] = g_arr[j];
rgb[idx + 2] = b_arr[j];
}
}
for i in (chunks * 8)..num_pixels {
let y = y_plane[i];
let cb = cb_plane[i];
let cr = cr_plane[i];
let r = CR_TO_R.mul_add(cr, y);
let g = CB_TO_G.mul_add(cb, CR_TO_G.mul_add(cr, y));
let b = CB_TO_B.mul_add(cb, y);
let idx = i * 3;
rgb[idx] = ((r + 128.0) / 255.0).clamp(0.0, 1.0);
rgb[idx + 1] = ((g + 128.0) / 255.0).clamp(0.0, 1.0);
rgb[idx + 2] = ((b + 128.0) / 255.0).clamp(0.0, 1.0);
}
}
{
for i in 0..num_pixels {
let y = y_plane[i];
let cb = cb_plane[i];
let cr = cr_plane[i];
let r = CR_TO_R.mul_add(cr, y);
let g = CB_TO_G.mul_add(cb, CR_TO_G.mul_add(cr, y));
let b = CB_TO_B.mul_add(cb, y);
let idx = i * 3;
rgb[idx] = ((r + 128.0) / 255.0).clamp(0.0, 1.0);
rgb[idx + 1] = ((g + 128.0) / 255.0).clamp(0.0, 1.0);
rgb[idx + 2] = ((b + 128.0) / 255.0).clamp(0.0, 1.0);
}
}
}
#[inline(never)]
pub fn gray_f32_to_rgb_u8(y_plane: &[f32], rgb: &mut [u8]) {
debug_assert_eq!(rgb.len(), y_plane.len() * 3);
let num_pixels = y_plane.len();
{
let offset = f32x8::splat(128.0);
let zero = f32x8::splat(0.0);
let max_val = f32x8::splat(255.0);
let chunks = num_pixels / 8;
for chunk in 0..chunks {
let base = chunk * 8;
let y = f32x8::from(<[f32; 8]>::try_from(&y_plane[base..base + 8]).unwrap());
let val = (y + offset).max(zero).min(max_val);
let arr: [f32; 8] = val.into();
for j in 0..8 {
let idx = (base + j) * 3;
let v = arr[j] as u8;
rgb[idx] = v;
rgb[idx + 1] = v;
rgb[idx + 2] = v;
}
}
for i in (chunks * 8)..num_pixels {
let val = (y_plane[i] + 128.0).clamp(0.0, 255.0) as u8;
let idx = i * 3;
rgb[idx] = val;
rgb[idx + 1] = val;
rgb[idx + 2] = val;
}
}
{
for (i, &y) in y_plane.iter().enumerate() {
let val = (y + 128.0).clamp(0.0, 255.0) as u8;
let idx = i * 3;
rgb[idx] = val;
rgb[idx + 1] = val;
rgb[idx + 2] = val;
}
}
}
#[inline(never)]
pub fn gray_f32_to_rgb_f32(y_plane: &[f32], rgb: &mut [f32]) {
debug_assert_eq!(rgb.len(), y_plane.len() * 3);
let num_pixels = y_plane.len();
{
let offset = f32x8::splat(128.0);
let scale = f32x8::splat(1.0 / 255.0);
let zero = f32x8::splat(0.0);
let one = f32x8::splat(1.0);
let chunks = num_pixels / 8;
for chunk in 0..chunks {
let base = chunk * 8;
let y = f32x8::from(<[f32; 8]>::try_from(&y_plane[base..base + 8]).unwrap());
let val = ((y + offset) * scale).max(zero).min(one);
let arr: [f32; 8] = val.into();
for j in 0..8 {
let idx = (base + j) * 3;
rgb[idx] = arr[j];
rgb[idx + 1] = arr[j];
rgb[idx + 2] = arr[j];
}
}
for i in (chunks * 8)..num_pixels {
let val = ((y_plane[i] + 128.0) / 255.0).clamp(0.0, 1.0);
let idx = i * 3;
rgb[idx] = val;
rgb[idx + 1] = val;
rgb[idx + 2] = val;
}
}
{
for (i, &y) in y_plane.iter().enumerate() {
let val = ((y + 128.0) / 255.0).clamp(0.0, 1.0);
let idx = i * 3;
rgb[idx] = val;
rgb[idx + 1] = val;
rgb[idx + 2] = val;
}
}
}
#[inline(never)]
pub fn gray_f32_to_gray_u8(y_plane: &[f32], output: &mut [u8]) {
debug_assert_eq!(y_plane.len(), output.len());
let num_pixels = y_plane.len();
{
let offset = f32x8::splat(128.0);
let zero = f32x8::splat(0.0);
let max_val = f32x8::splat(255.0);
let chunks = num_pixels / 8;
for chunk in 0..chunks {
let base = chunk * 8;
let y = f32x8::from(<[f32; 8]>::try_from(&y_plane[base..base + 8]).unwrap());
let val = (y + offset).max(zero).min(max_val);
let arr: [f32; 8] = val.into();
for j in 0..8 {
output[base + j] = arr[j] as u8;
}
}
for i in (chunks * 8)..num_pixels {
output[i] = (y_plane[i] + 128.0).clamp(0.0, 255.0) as u8;
}
}
{
for (y, out) in y_plane.iter().zip(output.iter_mut()) {
*out = (*y + 128.0).clamp(0.0, 255.0) as u8;
}
}
}
#[inline(never)]
pub fn gray_f32_to_gray_f32(y_plane: &[f32], output: &mut [f32]) {
debug_assert_eq!(y_plane.len(), output.len());
let num_pixels = y_plane.len();
{
let offset = f32x8::splat(128.0);
let scale = f32x8::splat(1.0 / 255.0);
let zero = f32x8::splat(0.0);
let one = f32x8::splat(1.0);
let chunks = num_pixels / 8;
for chunk in 0..chunks {
let base = chunk * 8;
let y = f32x8::from(<[f32; 8]>::try_from(&y_plane[base..base + 8]).unwrap());
let val = ((y + offset) * scale).max(zero).min(one);
let arr: [f32; 8] = val.into();
output[base..base + 8].copy_from_slice(&arr);
}
for i in (chunks * 8)..num_pixels {
output[i] = ((y_plane[i] + 128.0) / 255.0).clamp(0.0, 1.0);
}
}
{
for (y, out) in y_plane.iter().zip(output.iter_mut()) {
*out = ((*y + 128.0) / 255.0).clamp(0.0, 1.0);
}
}
}
#[inline]
pub fn bgr_to_rgb(bgr: &[u8; 3]) -> [u8; 3] {
[bgr[2], bgr[1], bgr[0]]
}
#[inline]
pub fn bgra_to_rgba(bgra: &[u8; 4]) -> [u8; 4] {
[bgra[2], bgra[1], bgra[0], bgra[3]]
}
#[inline]
#[must_use]
pub fn cmyk_to_rgb(c: u8, m: u8, y: u8, k: u8) -> (u8, u8, u8) {
let c = c as f32 / 255.0;
let m = m as f32 / 255.0;
let y = y as f32 / 255.0;
let k = k as f32 / 255.0;
let r = 255.0 * (1.0 - c) * (1.0 - k);
let g = 255.0 * (1.0 - m) * (1.0 - k);
let b = 255.0 * (1.0 - y) * (1.0 - k);
(
r.round().clamp(0.0, 255.0) as u8,
g.round().clamp(0.0, 255.0) as u8,
b.round().clamp(0.0, 255.0) as u8,
)
}
#[inline]
#[must_use]
pub fn rgb_to_cmyk(r: u8, g: u8, b: u8) -> (u8, u8, u8, u8) {
let r = r as f32 / 255.0;
let g = g as f32 / 255.0;
let b = b as f32 / 255.0;
let k = 1.0 - r.max(g).max(b);
if k >= 1.0 {
return (0, 0, 0, 255);
}
let c = (1.0 - r - k) / (1.0 - k);
let m = (1.0 - g - k) / (1.0 - k);
let y = (1.0 - b - k) / (1.0 - k);
(
(c * 255.0).round() as u8,
(m * 255.0).round() as u8,
(y * 255.0).round() as u8,
(k * 255.0).round() as u8,
)
}
pub fn extract_channel(data: &[u8], format: PixelFormat, channel: usize) -> Result<Vec<u8>> {
let bpp = format.bytes_per_pixel();
let num_pixels = data.len() / bpp;
let mut result = try_alloc_zeroed(num_pixels, "channel extraction buffer")?;
for i in 0..num_pixels {
result[i] = data[i * bpp + channel];
}
Ok(result)
}
const Y_CF_INT: i32 = 16384; const CR_TO_R_INT: i32 = 22970; const CB_TO_B_INT: i32 = 29032; const CR_TO_G_INT: i32 = -11700; const CB_TO_G_INT: i32 = -5638; const YUV_ROUND: i32 = 8192;
#[inline]
pub fn ycbcr_to_rgb_i16_x16(
y: &[i16; 16],
cb: &[i16; 16],
cr: &[i16; 16],
rgb: &mut [u8],
offset: &mut usize,
) {
#[cfg(all(
feature = "unsafe_simd",
any(target_arch = "x86", target_arch = "x86_64")
))]
{
if is_x86_feature_detected!("avx2") {
unsafe {
ycbcr_to_rgb_i16_x16_avx2(y, cb, cr, rgb, offset);
}
return;
}
}
ycbcr_to_rgb_i16_x16_scalar(y, cb, cr, rgb, offset);
}
#[inline]
fn ycbcr_to_rgb_i16_x16_scalar(
y: &[i16; 16],
cb: &[i16; 16],
cr: &[i16; 16],
rgb: &mut [u8],
offset: &mut usize,
) {
for i in 0..16 {
let y_val = i32::from(y[i]);
let cb_val = i32::from(cb[i]) - 128;
let cr_val = i32::from(cr[i]) - 128;
let y_scaled = y_val * Y_CF_INT + YUV_ROUND;
let r = (y_scaled + cr_val * CR_TO_R_INT) >> 14;
let g = (y_scaled + cr_val * CR_TO_G_INT + cb_val * CB_TO_G_INT) >> 14;
let b = (y_scaled + cb_val * CB_TO_B_INT) >> 14;
let idx = *offset + i * 3;
rgb[idx] = r.clamp(0, 255) as u8;
rgb[idx + 1] = g.clamp(0, 255) as u8;
rgb[idx + 2] = b.clamp(0, 255) as u8;
}
*offset += 48;
}
#[cfg(all(
feature = "unsafe_simd",
any(target_arch = "x86", target_arch = "x86_64")
))]
#[target_feature(enable = "avx2")]
unsafe fn ycbcr_to_rgb_i16_x16_avx2(
y: &[i16; 16],
cb: &[i16; 16],
cr: &[i16; 16],
rgb: &mut [u8],
offset: &mut usize,
) {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
let y_vec = _mm256_loadu_si256(y.as_ptr().cast());
let cb_vec = _mm256_loadu_si256(cb.as_ptr().cast());
let cr_vec = _mm256_loadu_si256(cr.as_ptr().cast());
let bias = _mm256_set1_epi16(128);
let cb_centered = _mm256_sub_epi16(cb_vec, bias);
let cr_centered = _mm256_sub_epi16(cr_vec, bias);
let y_coeff = _mm256_set1_epi32(Y_CF_INT);
let rounding = _mm256_set1_epi32(YUV_ROUND);
let zero = _mm256_setzero_si256();
let y_lo = _mm256_unpacklo_epi16(y_vec, zero);
let y_hi = _mm256_unpackhi_epi16(y_vec, zero);
let y_scaled_lo = _mm256_add_epi32(_mm256_mullo_epi32(y_lo, y_coeff), rounding);
let y_scaled_hi = _mm256_add_epi32(_mm256_mullo_epi32(y_hi, y_coeff), rounding);
let cb_sign = _mm256_srai_epi16(cb_centered, 15); let cr_sign = _mm256_srai_epi16(cr_centered, 15);
let cb_lo = _mm256_unpacklo_epi16(cb_centered, cb_sign);
let cb_hi = _mm256_unpackhi_epi16(cb_centered, cb_sign);
let cr_lo = _mm256_unpacklo_epi16(cr_centered, cr_sign);
let cr_hi = _mm256_unpackhi_epi16(cr_centered, cr_sign);
let r_lo = _mm256_srai_epi32(
_mm256_add_epi32(
y_scaled_lo,
_mm256_mullo_epi32(cr_lo, _mm256_set1_epi32(CR_TO_R_INT)),
),
14,
);
let r_hi = _mm256_srai_epi32(
_mm256_add_epi32(
y_scaled_hi,
_mm256_mullo_epi32(cr_hi, _mm256_set1_epi32(CR_TO_R_INT)),
),
14,
);
let g_lo = _mm256_srai_epi32(
_mm256_add_epi32(
y_scaled_lo,
_mm256_add_epi32(
_mm256_mullo_epi32(cr_lo, _mm256_set1_epi32(CR_TO_G_INT)),
_mm256_mullo_epi32(cb_lo, _mm256_set1_epi32(CB_TO_G_INT)),
),
),
14,
);
let g_hi = _mm256_srai_epi32(
_mm256_add_epi32(
y_scaled_hi,
_mm256_add_epi32(
_mm256_mullo_epi32(cr_hi, _mm256_set1_epi32(CR_TO_G_INT)),
_mm256_mullo_epi32(cb_hi, _mm256_set1_epi32(CB_TO_G_INT)),
),
),
14,
);
let b_lo = _mm256_srai_epi32(
_mm256_add_epi32(
y_scaled_lo,
_mm256_mullo_epi32(cb_lo, _mm256_set1_epi32(CB_TO_B_INT)),
),
14,
);
let b_hi = _mm256_srai_epi32(
_mm256_add_epi32(
y_scaled_hi,
_mm256_mullo_epi32(cb_hi, _mm256_set1_epi32(CB_TO_B_INT)),
),
14,
);
let r_16 = _mm256_packs_epi32(r_lo, r_hi);
let g_16 = _mm256_packs_epi32(g_lo, g_hi);
let b_16 = _mm256_packs_epi32(b_lo, b_hi);
let r_8 = _mm256_packus_epi16(r_16, _mm256_setzero_si256());
let g_8 = _mm256_packus_epi16(g_16, _mm256_setzero_si256());
let b_8 = _mm256_packus_epi16(b_16, _mm256_setzero_si256());
let r_8 = _mm256_permute4x64_epi64(r_8, 0b11_01_10_00);
let g_8 = _mm256_permute4x64_epi64(g_8, 0b11_01_10_00);
let b_8 = _mm256_permute4x64_epi64(b_8, 0b11_01_10_00);
let sh_r = _mm256_setr_epi8(
0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14,
9, 4, 15, 10, 5,
);
let sh_g = _mm256_setr_epi8(
5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3,
14, 9, 4, 15, 10,
);
let sh_b = _mm256_setr_epi8(
10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8,
3, 14, 9, 4, 15,
);
let r0 = _mm256_shuffle_epi8(r_8, sh_r);
let g0 = _mm256_shuffle_epi8(g_8, sh_g);
let b0 = _mm256_shuffle_epi8(b_8, sh_b);
let m0 = _mm256_setr_epi8(
0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1,
0, 0, -1, 0, 0,
);
let m1 = _mm256_setr_epi8(
0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0,
-1, 0, 0, -1, 0,
);
let p0 = _mm256_blendv_epi8(_mm256_blendv_epi8(r0, g0, m0), b0, m1);
let p1 = _mm256_blendv_epi8(_mm256_blendv_epi8(g0, b0, m0), r0, m1);
let p2 = _mm256_blendv_epi8(_mm256_blendv_epi8(b0, r0, m0), g0, m1);
let rgb0 = _mm256_permute2x128_si256(p0, p1, 0x20);
let rgb1 = _mm256_permute2x128_si256(p2, p0, 0x30);
let out_ptr = rgb.as_mut_ptr().add(*offset);
_mm256_storeu_si256(out_ptr.cast(), rgb0);
_mm_storeu_si128(out_ptr.add(32).cast(), _mm256_castsi256_si128(rgb1));
*offset += 48;
}
#[multiversion::multiversion(targets(
"x86_64+avx2+fma",
"x86_64+avx",
"x86_64+sse4.1",
"aarch64+neon"
))]
fn ycbcr_to_rgb_planes_autovec(
y_plane: &[i16],
cb_plane: &[i16],
cr_plane: &[i16],
r_out: &mut [u8],
g_out: &mut [u8],
b_out: &mut [u8],
) {
let len = y_plane.len();
for i in 0..len {
let y_val = i32::from(y_plane[i]);
let cb_val = i32::from(cb_plane[i]) - 128;
let cr_val = i32::from(cr_plane[i]) - 128;
let y_scaled = y_val * Y_CF_INT + YUV_ROUND;
let r_raw = (y_scaled + cr_val * CR_TO_R_INT) >> 14;
let g_raw = (y_scaled + cr_val * CR_TO_G_INT + cb_val * CB_TO_G_INT) >> 14;
let b_raw = (y_scaled + cb_val * CB_TO_B_INT) >> 14;
r_out[i] = r_raw.clamp(0, 255) as u8;
g_out[i] = g_raw.clamp(0, 255) as u8;
b_out[i] = b_raw.clamp(0, 255) as u8;
}
}
#[multiversion::multiversion(targets(
"x86_64+avx2+fma",
"x86_64+avx",
"x86_64+sse4.1",
"aarch64+neon"
))]
fn interleave_rgb_planes(r: &[u8], g: &[u8], b: &[u8], rgb: &mut [u8]) {
let len = r.len();
for i in 0..len {
let out_idx = i * 3;
rgb[out_idx] = r[i];
rgb[out_idx + 1] = g[i];
rgb[out_idx + 2] = b[i];
}
}
pub fn ycbcr_planes_i16_to_rgb_u8(
y_plane: &[i16],
cb_plane: &[i16],
cr_plane: &[i16],
rgb: &mut [u8],
) {
debug_assert_eq!(y_plane.len(), cb_plane.len());
debug_assert_eq!(y_plane.len(), cr_plane.len());
debug_assert_eq!(rgb.len(), y_plane.len() * 3);
let len = y_plane.len();
#[cfg(all(
feature = "unsafe_simd",
any(target_arch = "x86", target_arch = "x86_64")
))]
{
if is_x86_feature_detected!("avx2") {
unsafe {
ycbcr_planes_i16_to_rgb_u8_avx2(y_plane, cb_plane, cr_plane, rgb);
}
return;
}
}
for i in 0..len {
let y_val = i32::from(y_plane[i]);
let cb_val = i32::from(cb_plane[i]) - 128;
let cr_val = i32::from(cr_plane[i]) - 128;
let y_scaled = y_val * Y_CF_INT + YUV_ROUND;
let r = (y_scaled + cr_val * CR_TO_R_INT) >> 14;
let g = (y_scaled + cr_val * CR_TO_G_INT + cb_val * CB_TO_G_INT) >> 14;
let b = (y_scaled + cb_val * CB_TO_B_INT) >> 14;
let idx = i * 3;
rgb[idx] = r.clamp(0, 255) as u8;
rgb[idx + 1] = g.clamp(0, 255) as u8;
rgb[idx + 2] = b.clamp(0, 255) as u8;
}
}
#[cfg(all(
feature = "unsafe_simd",
any(target_arch = "x86", target_arch = "x86_64")
))]
#[target_feature(enable = "avx2")]
unsafe fn ycbcr_planes_i16_to_rgb_u8_avx2(
y_plane: &[i16],
cb_plane: &[i16],
cr_plane: &[i16],
rgb: &mut [u8],
) {
#[cfg(target_arch = "x86")]
use core::arch::x86::*;
#[cfg(target_arch = "x86_64")]
use core::arch::x86_64::*;
let len = y_plane.len();
let chunks = len / 16;
let bias = _mm256_set1_epi16(128);
let y_coeff = _mm256_set1_epi32(Y_CF_INT);
let rounding = _mm256_set1_epi32(YUV_ROUND);
let cr_to_r = _mm256_set1_epi32(CR_TO_R_INT);
let cr_to_g = _mm256_set1_epi32(CR_TO_G_INT);
let cb_to_g = _mm256_set1_epi32(CB_TO_G_INT);
let cb_to_b = _mm256_set1_epi32(CB_TO_B_INT);
let zero = _mm256_setzero_si256();
let sh_r = _mm256_setr_epi8(
0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14,
9, 4, 15, 10, 5,
);
let sh_g = _mm256_setr_epi8(
5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3,
14, 9, 4, 15, 10,
);
let sh_b = _mm256_setr_epi8(
10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8, 3, 14, 9, 4, 15, 10, 5, 0, 11, 6, 1, 12, 7, 2, 13, 8,
3, 14, 9, 4, 15,
);
let m0 = _mm256_setr_epi8(
0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1,
0, 0, -1, 0, 0,
);
let m1 = _mm256_setr_epi8(
0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0,
-1, 0, 0, -1, 0,
);
let y_ptr = y_plane.as_ptr();
let cb_ptr = cb_plane.as_ptr();
let cr_ptr = cr_plane.as_ptr();
let rgb_ptr = rgb.as_mut_ptr();
for chunk in 0..chunks {
let in_offset = chunk * 16;
let out_offset = chunk * 48;
let y_vec = _mm256_loadu_si256(y_ptr.add(in_offset).cast());
let cb_vec = _mm256_loadu_si256(cb_ptr.add(in_offset).cast());
let cr_vec = _mm256_loadu_si256(cr_ptr.add(in_offset).cast());
let cb_centered = _mm256_sub_epi16(cb_vec, bias);
let cr_centered = _mm256_sub_epi16(cr_vec, bias);
let y_lo = _mm256_unpacklo_epi16(y_vec, zero);
let y_hi = _mm256_unpackhi_epi16(y_vec, zero);
let y_scaled_lo = _mm256_add_epi32(_mm256_mullo_epi32(y_lo, y_coeff), rounding);
let y_scaled_hi = _mm256_add_epi32(_mm256_mullo_epi32(y_hi, y_coeff), rounding);
let cb_sign = _mm256_srai_epi16(cb_centered, 15);
let cr_sign = _mm256_srai_epi16(cr_centered, 15);
let cb_lo = _mm256_unpacklo_epi16(cb_centered, cb_sign);
let cb_hi = _mm256_unpackhi_epi16(cb_centered, cb_sign);
let cr_lo = _mm256_unpacklo_epi16(cr_centered, cr_sign);
let cr_hi = _mm256_unpackhi_epi16(cr_centered, cr_sign);
let r_lo = _mm256_srai_epi32(
_mm256_add_epi32(y_scaled_lo, _mm256_mullo_epi32(cr_lo, cr_to_r)),
14,
);
let r_hi = _mm256_srai_epi32(
_mm256_add_epi32(y_scaled_hi, _mm256_mullo_epi32(cr_hi, cr_to_r)),
14,
);
let g_lo = _mm256_srai_epi32(
_mm256_add_epi32(
y_scaled_lo,
_mm256_add_epi32(
_mm256_mullo_epi32(cr_lo, cr_to_g),
_mm256_mullo_epi32(cb_lo, cb_to_g),
),
),
14,
);
let g_hi = _mm256_srai_epi32(
_mm256_add_epi32(
y_scaled_hi,
_mm256_add_epi32(
_mm256_mullo_epi32(cr_hi, cr_to_g),
_mm256_mullo_epi32(cb_hi, cb_to_g),
),
),
14,
);
let b_lo = _mm256_srai_epi32(
_mm256_add_epi32(y_scaled_lo, _mm256_mullo_epi32(cb_lo, cb_to_b)),
14,
);
let b_hi = _mm256_srai_epi32(
_mm256_add_epi32(y_scaled_hi, _mm256_mullo_epi32(cb_hi, cb_to_b)),
14,
);
let r_16 = _mm256_packs_epi32(r_lo, r_hi);
let g_16 = _mm256_packs_epi32(g_lo, g_hi);
let b_16 = _mm256_packs_epi32(b_lo, b_hi);
let r_8 = _mm256_permute4x64_epi64(_mm256_packus_epi16(r_16, zero), 0b11_01_10_00);
let g_8 = _mm256_permute4x64_epi64(_mm256_packus_epi16(g_16, zero), 0b11_01_10_00);
let b_8 = _mm256_permute4x64_epi64(_mm256_packus_epi16(b_16, zero), 0b11_01_10_00);
let r0 = _mm256_shuffle_epi8(r_8, sh_r);
let g0 = _mm256_shuffle_epi8(g_8, sh_g);
let b0 = _mm256_shuffle_epi8(b_8, sh_b);
let p0 = _mm256_blendv_epi8(_mm256_blendv_epi8(r0, g0, m0), b0, m1);
let p1 = _mm256_blendv_epi8(_mm256_blendv_epi8(g0, b0, m0), r0, m1);
let p2 = _mm256_blendv_epi8(_mm256_blendv_epi8(b0, r0, m0), g0, m1);
let rgb0 = _mm256_permute2x128_si256(p0, p1, 0x20);
let rgb1 = _mm256_permute2x128_si256(p2, p0, 0x30);
let out_ptr = rgb_ptr.add(out_offset);
_mm256_storeu_si256(out_ptr.cast(), rgb0);
_mm_storeu_si128(out_ptr.add(32).cast(), _mm256_castsi256_si128(rgb1));
}
let remainder_start = chunks * 16;
for i in remainder_start..len {
let y_val = i32::from(y_plane[i]);
let cb_val = i32::from(cb_plane[i]) - 128;
let cr_val = i32::from(cr_plane[i]) - 128;
let y_scaled = y_val * Y_CF_INT + YUV_ROUND;
let r = (y_scaled + cr_val * CR_TO_R_INT) >> 14;
let g = (y_scaled + cr_val * CR_TO_G_INT + cb_val * CB_TO_G_INT) >> 14;
let b = (y_scaled + cb_val * CB_TO_B_INT) >> 14;
let idx = i * 3;
rgb[idx] = r.clamp(0, 255) as u8;
rgb[idx + 1] = g.clamp(0, 255) as u8;
rgb[idx + 2] = b.clamp(0, 255) as u8;
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_rgb_ycbcr_roundtrip() {
let test_colors = [
(0u8, 0u8, 0u8), (255u8, 255u8, 255u8), (255u8, 0u8, 0u8), (0u8, 255u8, 0u8), (0u8, 0u8, 255u8), (128u8, 128u8, 128u8), ];
for (r, g, b) in test_colors {
let (y, cb, cr) = rgb_to_ycbcr(r, g, b);
let (r2, g2, b2) = ycbcr_to_rgb(y, cb, cr);
assert!(
(r as i16 - r2 as i16).abs() <= 1,
"R mismatch for ({},{},{})",
r,
g,
b
);
assert!(
(g as i16 - g2 as i16).abs() <= 1,
"G mismatch for ({},{},{})",
r,
g,
b
);
assert!(
(b as i16 - b2 as i16).abs() <= 1,
"B mismatch for ({},{},{})",
r,
g,
b
);
}
}
#[test]
fn test_gray_ycbcr() {
for gray in [0u8, 64, 128, 192, 255] {
let (y, cb, cr) = rgb_to_ycbcr(gray, gray, gray);
assert_eq!(y, gray);
assert!((cb as i16 - 128).abs() <= 1);
assert!((cr as i16 - 128).abs() <= 1);
}
}
#[test]
fn test_cmyk_rgb_roundtrip() {
let (r, g, b) = cmyk_to_rgb(0, 0, 0, 0);
assert_eq!((r, g, b), (255, 255, 255));
let (r, g, b) = cmyk_to_rgb(255, 255, 255, 255);
assert_eq!((r, g, b), (0, 0, 0)); }
#[test]
fn test_bgr_conversion() {
assert_eq!(bgr_to_rgb(&[1, 2, 3]), [3, 2, 1]);
assert_eq!(bgra_to_rgba(&[1, 2, 3, 4]), [3, 2, 1, 4]);
}
#[test]
fn test_simd_rgb_to_ycbcr_matches_scalar() {
let test_colors = [
(0u8, 0u8, 0u8),
(255u8, 255u8, 255u8),
(255u8, 0u8, 0u8),
(0u8, 255u8, 0u8),
(0u8, 0u8, 255u8),
(128u8, 128u8, 128u8),
(100u8, 150u8, 200u8),
(33u8, 66u8, 99u8),
];
for chunk in test_colors.chunks(4) {
if chunk.len() < 4 {
continue;
}
let r = [chunk[0].0, chunk[1].0, chunk[2].0, chunk[3].0];
let g = [chunk[0].1, chunk[1].1, chunk[2].1, chunk[3].1];
let b = [chunk[0].2, chunk[1].2, chunk[2].2, chunk[3].2];
let (y_simd, cb_simd, cr_simd) = simd::rgb_to_ycbcr_x4(r, g, b);
for i in 0..4 {
let (y_scalar, cb_scalar, cr_scalar) = rgb_to_ycbcr(r[i], g[i], b[i]);
assert_eq!(y_simd[i], y_scalar, "Y mismatch at {}", i);
assert_eq!(cb_simd[i], cb_scalar, "Cb mismatch at {}", i);
assert_eq!(cr_simd[i], cr_scalar, "Cr mismatch at {}", i);
}
}
}
#[test]
fn test_simd_ycbcr_to_rgb_matches_scalar() {
let test_ycbcr = [
(0u8, 128u8, 128u8), (255u8, 128u8, 128u8), (76u8, 85u8, 255u8), (150u8, 44u8, 21u8), (29u8, 255u8, 107u8), (128u8, 128u8, 128u8), ];
for chunk in test_ycbcr.chunks(4) {
if chunk.len() < 4 {
continue;
}
let y = [chunk[0].0, chunk[1].0, chunk[2].0, chunk[3].0];
let cb = [chunk[0].1, chunk[1].1, chunk[2].1, chunk[3].1];
let cr = [chunk[0].2, chunk[1].2, chunk[2].2, chunk[3].2];
let (r_simd, g_simd, b_simd) = simd::ycbcr_to_rgb_x4(y, cb, cr);
for i in 0..4 {
let (r_scalar, g_scalar, b_scalar) = ycbcr_to_rgb(y[i], cb[i], cr[i]);
assert_eq!(r_simd[i], r_scalar, "R mismatch at {}", i);
assert_eq!(g_simd[i], g_scalar, "G mismatch at {}", i);
assert_eq!(b_simd[i], b_scalar, "B mismatch at {}", i);
}
}
}
#[test]
fn test_rgb_to_ycbcr_f32() {
let (y, cb, cr) = rgb_to_ycbcr_f32(255.0, 0.0, 0.0); assert!((y - 76.0).abs() < 1.0);
assert!((cb - 85.0).abs() < 1.0);
assert!((cr - 255.0).abs() < 1.0);
let (y, _cb, _cr) = rgb_to_ycbcr_f32(0.0, 255.0, 0.0); assert!((y - 150.0).abs() < 1.0);
let (y, _cb, _cr) = rgb_to_ycbcr_f32(0.0, 0.0, 255.0); assert!((y - 29.0).abs() < 1.0);
}
#[test]
fn test_ycbcr_to_rgb_f32() {
let (r, g, b) = ycbcr_to_rgb_f32(128.0, 128.0, 128.0); assert!((r - 128.0).abs() < 1.0);
assert!((g - 128.0).abs() < 1.0);
assert!((b - 128.0).abs() < 1.0);
}
#[test]
fn test_convert_rgb_to_ycbcr_buffer() {
let mut buffer = [255, 0, 0, 0, 255, 0, 0, 0, 255]; convert_rgb_to_ycbcr_buffer(&mut buffer);
assert!((buffer[0] as i16 - 76).abs() <= 1);
}
#[test]
fn test_convert_ycbcr_to_rgb_buffer() {
let mut buffer = [128, 128, 128, 128, 128, 128]; convert_ycbcr_to_rgb_buffer(&mut buffer);
assert!((buffer[0] as i16 - 128).abs() <= 1);
assert!((buffer[1] as i16 - 128).abs() <= 1);
assert!((buffer[2] as i16 - 128).abs() <= 1);
}
#[test]
fn test_rgb_to_ycbcr_planes() {
let rgb = vec![255, 0, 0, 0, 255, 0, 0, 0, 255, 128, 128, 128]; let (y, cb, cr) = rgb_to_ycbcr_planes(&rgb, 2, 2).unwrap();
assert_eq!(y.len(), 4);
assert_eq!(cb.len(), 4);
assert_eq!(cr.len(), 4);
assert!((y[0] as i16 - 76).abs() <= 1);
}
#[test]
fn test_ycbcr_planes_to_rgb() {
let y = vec![128u8, 128, 128, 128];
let cb = vec![128u8, 128, 128, 128];
let cr = vec![128u8, 128, 128, 128];
let rgb = ycbcr_planes_to_rgb(&y, &cb, &cr, 2, 2).unwrap();
assert_eq!(rgb.len(), 12); for i in 0..4 {
assert!((rgb[i * 3] as i16 - 128).abs() <= 1);
}
}
#[test]
fn test_ycbcr_planes_f32_to_rgb_u8() {
let y = vec![0.0f32; 4];
let cb = vec![0.0f32; 4];
let cr = vec![0.0f32; 4];
let mut rgb = vec![0u8; 12];
ycbcr_planes_f32_to_rgb_u8(&y, &cb, &cr, &mut rgb);
for i in 0..4 {
assert_eq!(rgb[i * 3], 128);
assert_eq!(rgb[i * 3 + 1], 128);
assert_eq!(rgb[i * 3 + 2], 128);
}
}
#[test]
fn test_ycbcr_planes_f32_to_rgb_f32() {
let y = vec![0.0f32; 4];
let cb = vec![0.0f32; 4];
let cr = vec![0.0f32; 4];
let mut rgb = vec![0.0f32; 12];
ycbcr_planes_f32_to_rgb_f32(&y, &cb, &cr, &mut rgb);
for i in 0..4 {
assert!((rgb[i * 3] - 0.502).abs() < 0.01);
}
}
#[test]
fn test_gray_f32_to_rgb_u8() {
let y = vec![0.0f32, 127.0, -128.0]; let mut rgb = vec![0u8; 9];
gray_f32_to_rgb_u8(&y, &mut rgb);
assert_eq!(rgb[0], 128); assert_eq!(rgb[1], 128); assert_eq!(rgb[2], 128); assert_eq!(rgb[3], 255); assert_eq!(rgb[6], 0); }
#[test]
fn test_gray_f32_to_rgb_f32() {
let y = vec![0.0f32; 2];
let mut rgb = vec![0.0f32; 6];
gray_f32_to_rgb_f32(&y, &mut rgb);
for v in &rgb {
assert!((*v - 0.502).abs() < 0.01);
}
}
#[test]
fn test_gray_f32_to_gray_u8() {
let y = vec![0.0f32, 127.0, -128.0];
let mut output = vec![0u8; 3];
gray_f32_to_gray_u8(&y, &mut output);
assert_eq!(output[0], 128);
assert_eq!(output[1], 255);
assert_eq!(output[2], 0);
}
#[test]
fn test_gray_f32_to_gray_f32() {
let y = vec![0.0f32, 127.0];
let mut output = vec![0.0f32; 2];
gray_f32_to_gray_f32(&y, &mut output);
assert!((output[0] - 0.502).abs() < 0.01);
assert!((output[1] - 1.0).abs() < 0.01);
}
#[test]
fn test_rgb_to_cmyk() {
let (c, m, y, k) = rgb_to_cmyk(255, 255, 255);
assert_eq!((c, m, y, k), (0, 0, 0, 0));
let (_c, _m, _y, k) = rgb_to_cmyk(0, 0, 0);
assert_eq!(k, 255);
let (c, _, _, _) = rgb_to_cmyk(255, 0, 0);
assert_eq!(c, 0);
}
#[test]
fn test_extract_channel() {
let data = vec![10, 20, 30, 40, 50, 60]; let red = extract_channel(&data, PixelFormat::Rgb, 0).unwrap();
assert_eq!(red, vec![10, 40]);
let green = extract_channel(&data, PixelFormat::Rgb, 1).unwrap();
assert_eq!(green, vec![20, 50]);
let blue = extract_channel(&data, PixelFormat::Rgb, 2).unwrap();
assert_eq!(blue, vec![30, 60]);
}
#[test]
fn test_extract_channel_rgba() {
let data = vec![10, 20, 30, 255, 40, 50, 60, 128]; let alpha = extract_channel(&data, PixelFormat::Rgba, 3).unwrap();
assert_eq!(alpha, vec![255, 128]);
}
#[test]
fn test_ycbcr_to_rgb_i16_scalar() {
let test_cases = [
(128i16, 128i16, 128i16), (76i16, 85i16, 255i16), (150i16, 44i16, 21i16), (29i16, 255i16, 107i16), ];
for (y, cb, cr) in test_cases {
let (r_f32, g_f32, b_f32) = ycbcr_to_rgb(y as u8, cb as u8, cr as u8);
let y_arr = [y; 16];
let cb_arr = [cb; 16];
let cr_arr = [cr; 16];
let mut rgb = vec![0u8; 48];
let mut offset = 0;
ycbcr_to_rgb_i16_x16_scalar(&y_arr, &cb_arr, &cr_arr, &mut rgb, &mut offset);
assert!(
(rgb[0] as i16 - r_f32 as i16).abs() <= 2,
"R mismatch: {} vs {} for Y={}, Cb={}, Cr={}",
rgb[0],
r_f32,
y,
cb,
cr
);
assert!(
(rgb[1] as i16 - g_f32 as i16).abs() <= 2,
"G mismatch: {} vs {} for Y={}, Cb={}, Cr={}",
rgb[1],
g_f32,
y,
cb,
cr
);
assert!(
(rgb[2] as i16 - b_f32 as i16).abs() <= 2,
"B mismatch: {} vs {} for Y={}, Cb={}, Cr={}",
rgb[2],
b_f32,
y,
cb,
cr
);
}
}
#[test]
fn test_ycbcr_planes_i16_to_rgb_u8() {
let y_plane: Vec<i16> = (0..32).map(|i| 128 + (i % 5) as i16).collect();
let cb_plane: Vec<i16> = (0..32).map(|i| 128 + (i % 3) as i16).collect();
let cr_plane: Vec<i16> = (0..32).map(|i| 128 + (i % 7) as i16).collect();
let mut rgb = vec![0u8; 96];
ycbcr_planes_i16_to_rgb_u8(&y_plane, &cb_plane, &cr_plane, &mut rgb);
for i in 0..32 {
let (r_ref, g_ref, b_ref) =
ycbcr_to_rgb(y_plane[i] as u8, cb_plane[i] as u8, cr_plane[i] as u8);
assert!(
(rgb[i * 3] as i16 - r_ref as i16).abs() <= 2,
"R mismatch at {}: {} vs {}",
i,
rgb[i * 3],
r_ref
);
assert!(
(rgb[i * 3 + 1] as i16 - g_ref as i16).abs() <= 2,
"G mismatch at {}: {} vs {}",
i,
rgb[i * 3 + 1],
g_ref
);
assert!(
(rgb[i * 3 + 2] as i16 - b_ref as i16).abs() <= 2,
"B mismatch at {}: {} vs {}",
i,
rgb[i * 3 + 2],
b_ref
);
}
}
}