use crate::RangaError;
use crate::pixel::{PixelBuffer, PixelFormat};
fn compute_y_row(rgba: &[u8], y_out: &mut [u8]) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
unsafe { compute_y_row_simd_sse2(rgba, y_out, 77, 150, 29) };
}
#[cfg(all(feature = "simd", target_arch = "aarch64"))]
{
unsafe { compute_y_row_simd_neon(rgba, y_out, 77, 150, 29) };
}
#[cfg(not(all(feature = "simd", any(target_arch = "x86_64", target_arch = "aarch64"))))]
compute_y_row_scalar(rgba, y_out, 77, 150, 29);
}
fn compute_y_row_bt709(rgba: &[u8], y_out: &mut [u8]) {
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
unsafe { compute_y_row_simd_sse2(rgba, y_out, 54, 183, 19) };
}
#[cfg(all(feature = "simd", target_arch = "aarch64"))]
{
unsafe { compute_y_row_simd_neon(rgba, y_out, 54, 183, 19) };
}
#[cfg(not(all(feature = "simd", any(target_arch = "x86_64", target_arch = "aarch64"))))]
compute_y_row_scalar(rgba, y_out, 54, 183, 19);
}
fn compute_y_row_scalar(rgba: &[u8], y_out: &mut [u8], cr: u16, cg: u16, cb: u16) {
for (pixel, y) in rgba.chunks_exact(4).zip(y_out.iter_mut()) {
*y = ((cr * pixel[0] as u16 + cg * pixel[1] as u16 + cb * pixel[2] as u16) >> 8) as u8;
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "sse2")]
unsafe fn compute_y_row_simd_sse2(rgba: &[u8], y_out: &mut [u8], cr: u16, cg: u16, cb: u16) {
use std::arch::x86_64::*;
let pixel_count = rgba.len() / 4;
let simd_pixels = pixel_count / 2 * 2;
unsafe {
let zero = _mm_setzero_si128();
let coeffs = _mm_setr_epi16(
cr as i16, cg as i16, cb as i16, 0, cr as i16, cg as i16, cb as i16, 0,
);
let mut i = 0usize;
let mut oi = 0usize;
while oi + 2 <= simd_pixels {
let px = _mm_loadl_epi64(rgba.as_ptr().add(i * 4) as *const __m128i);
let px16 = _mm_unpacklo_epi8(px, zero);
let products = _mm_madd_epi16(px16, coeffs);
let shuffled = _mm_shuffle_epi32(products, 0b10_11_00_01);
let sums = _mm_add_epi32(products, shuffled);
let y_vals = _mm_srli_epi32(sums, 8);
y_out[oi] = _mm_extract_epi16(y_vals, 0) as u8;
y_out[oi + 1] = _mm_extract_epi16(y_vals, 4) as u8;
i += 2;
oi += 2;
}
}
if simd_pixels < pixel_count {
compute_y_row_scalar(
&rgba[simd_pixels * 4..],
&mut y_out[simd_pixels..],
cr,
cg,
cb,
);
}
}
#[cfg(all(feature = "simd", target_arch = "aarch64"))]
unsafe fn compute_y_row_simd_neon(rgba: &[u8], y_out: &mut [u8], cr: u16, cg: u16, cb: u16) {
use std::arch::aarch64::*;
let pixel_count = rgba.len() / 4;
let simd_pixels = pixel_count / 8 * 8;
unsafe {
debug_assert!(cr <= 255, "NEON Y coefficient cr={cr} exceeds u8");
debug_assert!(cg <= 255, "NEON Y coefficient cg={cg} exceeds u8");
debug_assert!(cb <= 255, "NEON Y coefficient cb={cb} exceeds u8");
let vcr = vdup_n_u8(cr as u8);
let vcg = vdup_n_u8(cg as u8);
let vcb = vdup_n_u8(cb as u8);
let mut i = 0usize;
let mut oi = 0usize;
while oi + 8 <= simd_pixels {
let px = vld4_u8(rgba.as_ptr().add(i));
let mut acc = vmull_u8(px.0, vcr);
acc = vmlal_u8(acc, px.1, vcg);
acc = vmlal_u8(acc, px.2, vcb);
let y = vshrn_n_u16(acc, 8);
vst1_u8(y_out.as_mut_ptr().add(oi), y);
i += 32;
oi += 8;
}
}
if simd_pixels < pixel_count {
compute_y_row_scalar(
&rgba[simd_pixels * 4..],
&mut y_out[simd_pixels..],
cr,
cg,
cb,
);
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "sse2")]
#[allow(clippy::too_many_arguments, clippy::needless_range_loop)]
unsafe fn yuv_row_to_rgba_sse2(
y_row: &[u8],
u_row: &[u8],
v_row: &[u8],
rgba_out: &mut [u8],
cr_v: i16,
cg_u: i16,
cg_v: i16,
cb_u: i16,
) {
use std::arch::x86_64::*;
let width = y_row.len();
let simd_width = width / 8 * 8;
unsafe {
let zero = _mm_setzero_si128();
let alpha = _mm_set1_epi16(255);
let v_cr_v = _mm_set1_epi16(cr_v);
let v_cg_u = _mm_set1_epi16(cg_u);
let v_cg_v = _mm_set1_epi16(cg_v);
let v_cb_u = _mm_set1_epi16(cb_u);
let bias = _mm_set1_epi16(128);
let mut x = 0usize;
while x + 8 <= simd_width {
let y8 = _mm_loadl_epi64(y_row.as_ptr().add(x) as *const __m128i);
let y16 = _mm_unpacklo_epi8(y8, zero);
let cx = x / 2;
let u4 = _mm_cvtsi32_si128(i32::from_ne_bytes([
u_row[cx],
u_row[cx + 1],
u_row[cx + 2],
u_row[cx + 3],
]));
let v4 = _mm_cvtsi32_si128(i32::from_ne_bytes([
v_row[cx],
v_row[cx + 1],
v_row[cx + 2],
v_row[cx + 3],
]));
let u_dup = _mm_unpacklo_epi8(u4, u4); let v_dup = _mm_unpacklo_epi8(v4, v4);
let u16 = _mm_sub_epi16(_mm_unpacklo_epi8(u_dup, zero), bias);
let v16 = _mm_sub_epi16(_mm_unpacklo_epi8(v_dup, zero), bias);
let r16 = _mm_add_epi16(y16, _mm_srai_epi16(_mm_mullo_epi16(v_cr_v, v16), 8));
let r_clamped = _mm_unpacklo_epi8(_mm_packus_epi16(r16, zero), zero);
let gu = _mm_mullo_epi16(v_cg_u, u16);
let gv = _mm_mullo_epi16(v_cg_v, v16);
let g16 = _mm_sub_epi16(y16, _mm_srai_epi16(_mm_add_epi16(gu, gv), 8));
let g_clamped = _mm_unpacklo_epi8(_mm_packus_epi16(g16, zero), zero);
let b16 = _mm_add_epi16(y16, _mm_srai_epi16(_mm_mullo_epi16(v_cb_u, u16), 8));
let b_clamped = _mm_unpacklo_epi8(_mm_packus_epi16(b16, zero), zero);
let r8 = _mm_packus_epi16(r_clamped, zero);
let g8 = _mm_packus_epi16(g_clamped, zero);
let b8 = _mm_packus_epi16(b_clamped, zero);
let a8 = _mm_packus_epi16(alpha, zero);
let rg_lo = _mm_unpacklo_epi8(r8, g8); let ba_lo = _mm_unpacklo_epi8(b8, a8);
let rgba_0 = _mm_unpacklo_epi16(rg_lo, ba_lo); let rgba_1 = _mm_unpackhi_epi16(rg_lo, ba_lo);
let out_ptr = rgba_out.as_mut_ptr().add(x * 4);
_mm_storeu_si128(out_ptr as *mut __m128i, rgba_0);
_mm_storeu_si128(out_ptr.add(16) as *mut __m128i, rgba_1);
x += 8;
}
}
for x in simd_width..width {
let cx = (x / 2).min(u_row.len().saturating_sub(1));
let yi = y_row[x] as i16;
let u = u_row[cx] as i16 - 128;
let v = v_row[cx] as i16 - 128;
let oi = x * 4;
rgba_out[oi] = (yi + ((cr_v * v) >> 8)).clamp(0, 255) as u8;
rgba_out[oi + 1] = (yi - ((cg_u * u + cg_v * v) >> 8)).clamp(0, 255) as u8;
rgba_out[oi + 2] = (yi + ((cb_u * u) >> 8)).clamp(0, 255) as u8;
rgba_out[oi + 3] = 255;
}
}
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
#[target_feature(enable = "sse2")]
#[allow(clippy::needless_range_loop)]
unsafe fn yuv_row_to_rgba_nv12_sse2(
y_row: &[u8],
uv_row: &[u8],
rgba_out: &mut [u8],
cr_v: i16,
cg_u: i16,
cg_v: i16,
cb_u: i16,
) {
use std::arch::x86_64::*;
let width = y_row.len();
let simd_width = width / 8 * 8;
unsafe {
let zero = _mm_setzero_si128();
let alpha = _mm_set1_epi16(255);
let v_cr_v = _mm_set1_epi16(cr_v);
let v_cg_u = _mm_set1_epi16(cg_u);
let v_cg_v = _mm_set1_epi16(cg_v);
let v_cb_u = _mm_set1_epi16(cb_u);
let bias = _mm_set1_epi16(128);
let even_mask = _mm_set1_epi16(0x00FF);
let mut x = 0usize;
while x + 8 <= simd_width {
let y8 = _mm_loadl_epi64(y_row.as_ptr().add(x) as *const __m128i);
let y16 = _mm_unpacklo_epi8(y8, zero);
let cx = x / 2;
let uv8 = _mm_loadl_epi64(uv_row.as_ptr().add(cx * 2) as *const __m128i);
let u_bytes = _mm_and_si128(uv8, even_mask); let v_bytes = _mm_srli_epi16(uv8, 8);
let u4 = _mm_packus_epi16(u_bytes, zero); let v4 = _mm_packus_epi16(v_bytes, zero);
let u_dup = _mm_unpacklo_epi8(u4, u4); let v_dup = _mm_unpacklo_epi8(v4, v4);
let u16 = _mm_sub_epi16(_mm_unpacklo_epi8(u_dup, zero), bias);
let v16 = _mm_sub_epi16(_mm_unpacklo_epi8(v_dup, zero), bias);
let r16 = _mm_add_epi16(y16, _mm_srai_epi16(_mm_mullo_epi16(v_cr_v, v16), 8));
let r_clamped = _mm_unpacklo_epi8(_mm_packus_epi16(r16, zero), zero);
let gu = _mm_mullo_epi16(v_cg_u, u16);
let gv = _mm_mullo_epi16(v_cg_v, v16);
let g16 = _mm_sub_epi16(y16, _mm_srai_epi16(_mm_add_epi16(gu, gv), 8));
let g_clamped = _mm_unpacklo_epi8(_mm_packus_epi16(g16, zero), zero);
let b16 = _mm_add_epi16(y16, _mm_srai_epi16(_mm_mullo_epi16(v_cb_u, u16), 8));
let b_clamped = _mm_unpacklo_epi8(_mm_packus_epi16(b16, zero), zero);
let r8 = _mm_packus_epi16(r_clamped, zero);
let g8 = _mm_packus_epi16(g_clamped, zero);
let b8 = _mm_packus_epi16(b_clamped, zero);
let a8 = _mm_packus_epi16(alpha, zero);
let rg_lo = _mm_unpacklo_epi8(r8, g8);
let ba_lo = _mm_unpacklo_epi8(b8, a8);
let rgba_0 = _mm_unpacklo_epi16(rg_lo, ba_lo);
let rgba_1 = _mm_unpackhi_epi16(rg_lo, ba_lo);
let out_ptr = rgba_out.as_mut_ptr().add(x * 4);
_mm_storeu_si128(out_ptr as *mut __m128i, rgba_0);
_mm_storeu_si128(out_ptr.add(16) as *mut __m128i, rgba_1);
x += 8;
}
}
let cw = width.div_ceil(2);
for x in simd_width..width {
let cx = (x / 2).min(cw.saturating_sub(1));
let yi = y_row[x] as i16;
let u = uv_row[cx * 2] as i16 - 128;
let v = uv_row[cx * 2 + 1] as i16 - 128;
let oi = x * 4;
rgba_out[oi] = (yi + ((cr_v * v) >> 8)).clamp(0, 255) as u8;
rgba_out[oi + 1] = (yi - ((cg_u * u + cg_v * v) >> 8)).clamp(0, 255) as u8;
rgba_out[oi + 2] = (yi + ((cb_u * u) >> 8)).clamp(0, 255) as u8;
rgba_out[oi + 3] = 255;
}
}
#[must_use = "returns a new YUV420p buffer"]
pub fn rgba_to_yuv420p(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Rgba8 {
return Err(RangaError::InvalidFormat(format!(
"rgba_to_yuv420p: expected Rgba8, got {:?}",
buf.format
)));
}
let w = buf.width as usize;
let h = buf.height as usize;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let mut yuv = vec![0u8; w * h + 2 * cw * ch];
for y in 0..h {
let row_start = y * w * 4;
compute_y_row(
&buf.data[row_start..row_start + w * 4],
&mut yuv[y * w..y * w + w],
);
}
let u_off = w * h;
let v_off = u_off + cw * ch;
for y in (0..ch * 2).step_by(2) {
for x in (0..cw * 2).step_by(2) {
let i = (y * w + x) * 4;
let r = buf.data[i] as i32;
let g = buf.data[i + 1] as i32;
let b = buf.data[i + 2] as i32;
let ci = (y / 2) * cw + (x / 2);
yuv[u_off + ci] = ((-43 * r - 85 * g + 128 * b + 128 * 256) >> 8).clamp(0, 255) as u8;
yuv[v_off + ci] = ((128 * r - 107 * g - 21 * b + 128 * 256) >> 8).clamp(0, 255) as u8;
}
}
PixelBuffer::new(yuv, buf.width, buf.height, PixelFormat::Yuv420p)
}
#[must_use = "returns a new RGBA buffer"]
pub fn yuv420p_to_rgba(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Yuv420p {
return Err(RangaError::InvalidFormat(format!(
"yuv420p_to_rgba: expected Yuv420p, got {:?}",
buf.format
)));
}
let w = buf.width as usize;
let h = buf.height as usize;
let cw = w.div_ceil(2);
let u_off = w * h;
let v_off = u_off + cw * (h.div_ceil(2));
let ch = h.div_ceil(2);
let mut rgba = vec![0u8; w * h * 4];
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
for y in 0..h {
let cy = (y / 2).min(ch.saturating_sub(1));
let y_start = y * w;
let u_start = u_off + cy * cw;
let v_start = v_off + cy * cw;
let rgba_start = y * w * 4;
unsafe {
yuv_row_to_rgba_sse2(
&buf.data[y_start..y_start + w],
&buf.data[u_start..u_start + cw],
&buf.data[v_start..v_start + cw],
&mut rgba[rgba_start..rgba_start + w * 4],
359,
88,
183,
454,
);
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
{
for y in 0..h {
let cy = (y / 2).min(ch.saturating_sub(1));
for x in 0..w {
let cx = (x / 2).min(cw.saturating_sub(1));
let yi = buf.data[y * w + x] as i16;
let u = buf.data[u_off + cy * cw + cx] as i16 - 128;
let v = buf.data[v_off + cy * cw + cx] as i16 - 128;
let oi = (y * w + x) * 4;
rgba[oi] = (yi + ((359 * v) >> 8)).clamp(0, 255) as u8;
rgba[oi + 1] = (yi - ((88 * u + 183 * v) >> 8)).clamp(0, 255) as u8;
rgba[oi + 2] = (yi + ((454 * u) >> 8)).clamp(0, 255) as u8;
rgba[oi + 3] = 255;
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
}
#[must_use = "returns a new NV12 buffer"]
pub fn argb_to_nv12(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Argb8 {
return Err(RangaError::InvalidFormat(format!(
"argb_to_nv12: expected Argb8, got {:?}",
buf.format
)));
}
let w = buf.width as usize;
let h = buf.height as usize;
let mut nv12 = vec![0u8; w * h + (w.div_ceil(2)) * (h.div_ceil(2)) * 2];
for y in 0..h {
for x in 0..w {
let i = (y * w + x) * 4;
let r = buf.data[i + 1] as u16;
let g = buf.data[i + 2] as u16;
let b = buf.data[i + 3] as u16;
nv12[y * w + x] = ((77 * r + 150 * g + 29 * b) >> 8) as u8;
}
}
let uv_off = w * h;
let nv12_cw = w.div_ceil(2);
let nv12_ch = h.div_ceil(2);
for y in (0..nv12_ch * 2).step_by(2) {
for x in (0..nv12_cw * 2).step_by(2) {
let i = (y * w + x) * 4;
let r = buf.data[i + 1] as i32;
let g = buf.data[i + 2] as i32;
let b = buf.data[i + 3] as i32;
let ci = (y / 2) * nv12_cw * 2 + x;
nv12[uv_off + ci] = ((-43 * r - 85 * g + 128 * b + 128 * 256) >> 8).clamp(0, 255) as u8;
nv12[uv_off + ci + 1] =
((128 * r - 107 * g - 21 * b + 128 * 256) >> 8).clamp(0, 255) as u8;
}
}
PixelBuffer::new(nv12, buf.width, buf.height, PixelFormat::Nv12)
}
#[must_use = "returns a new YUV420p BT.709 buffer"]
pub fn rgba_to_yuv420p_bt709(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Rgba8 {
return Err(RangaError::InvalidFormat(format!(
"rgba_to_yuv420p_bt709: expected Rgba8, got {:?}",
buf.format
)));
}
let w = buf.width as usize;
let h = buf.height as usize;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let mut yuv = vec![0u8; w * h + 2 * cw * ch];
for y in 0..h {
let row_start = y * w * 4;
compute_y_row_bt709(
&buf.data[row_start..row_start + w * 4],
&mut yuv[y * w..y * w + w],
);
}
let u_off = w * h;
let v_off = u_off + cw * ch;
for y in (0..ch * 2).step_by(2) {
for x in (0..cw * 2).step_by(2) {
let i = (y * w + x) * 4;
let r = buf.data[i] as i32;
let g = buf.data[i + 1] as i32;
let b = buf.data[i + 2] as i32;
let ci = (y / 2) * cw + (x / 2);
yuv[u_off + ci] = ((-29 * r - 99 * g + 128 * b + 128 * 256) >> 8).clamp(0, 255) as u8;
yuv[v_off + ci] = ((128 * r - 116 * g - 12 * b + 128 * 256) >> 8).clamp(0, 255) as u8;
}
}
PixelBuffer::new(yuv, buf.width, buf.height, PixelFormat::Yuv420p)
}
#[must_use = "returns a new RGBA BT.709 buffer"]
pub fn yuv420p_to_rgba_bt709(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Yuv420p {
return Err(RangaError::InvalidFormat(format!(
"yuv420p_to_rgba_bt709: expected Yuv420p, got {:?}",
buf.format
)));
}
let w = buf.width as usize;
let h = buf.height as usize;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let u_off = w * h;
let v_off = u_off + cw * ch;
let mut rgba = vec![0u8; w * h * 4];
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
for y in 0..h {
let cy = (y / 2).min(ch.saturating_sub(1));
let y_start = y * w;
let u_start = u_off + cy * cw;
let v_start = v_off + cy * cw;
let rgba_start = y * w * 4;
unsafe {
yuv_row_to_rgba_sse2(
&buf.data[y_start..y_start + w],
&buf.data[u_start..u_start + cw],
&buf.data[v_start..v_start + cw],
&mut rgba[rgba_start..rgba_start + w * 4],
403,
48,
120,
475,
);
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
{
for y in 0..h {
let cy = (y / 2).min(ch.saturating_sub(1));
for x in 0..w {
let cx = (x / 2).min(cw.saturating_sub(1));
let yi = buf.data[y * w + x] as i16;
let u = buf.data[u_off + cy * cw + cx] as i16 - 128;
let v = buf.data[v_off + cy * cw + cx] as i16 - 128;
let oi = (y * w + x) * 4;
rgba[oi] = (yi + ((403 * v) >> 8)).clamp(0, 255) as u8;
rgba[oi + 1] = (yi - ((48 * u + 120 * v) >> 8)).clamp(0, 255) as u8;
rgba[oi + 2] = (yi + ((475 * u) >> 8)).clamp(0, 255) as u8;
rgba[oi + 3] = 255;
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
}
fn compute_y_row_bt2020(rgba: &[u8], y_out: &mut [u8]) {
for (pixel, y) in rgba.chunks_exact(4).zip(y_out.iter_mut()) {
*y = ((67 * pixel[0] as u16 + 174 * pixel[1] as u16 + 15 * pixel[2] as u16) >> 8) as u8;
}
}
#[must_use = "returns a new YUV420p BT.2020 buffer"]
pub fn rgba_to_yuv420p_bt2020(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Rgba8 {
return Err(RangaError::InvalidFormat(format!(
"rgba_to_yuv420p_bt2020: expected Rgba8, got {:?}",
buf.format
)));
}
let w = buf.width as usize;
let h = buf.height as usize;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let mut yuv = vec![0u8; w * h + 2 * cw * ch];
for y in 0..h {
let row_start = y * w * 4;
compute_y_row_bt2020(
&buf.data[row_start..row_start + w * 4],
&mut yuv[y * w..y * w + w],
);
}
let u_off = w * h;
let v_off = u_off + cw * ch;
for y in (0..ch * 2).step_by(2) {
for x in (0..cw * 2).step_by(2) {
let i = (y * w + x) * 4;
let r = buf.data[i] as i32;
let g = buf.data[i + 1] as i32;
let b = buf.data[i + 2] as i32;
let ci = (y / 2) * cw + (x / 2);
yuv[u_off + ci] = ((-18 * r - 46 * g + 64 * b + 128 * 256) >> 8).clamp(0, 255) as u8;
yuv[v_off + ci] = ((64 * r - 58 * g - 6 * b + 128 * 256) >> 8).clamp(0, 255) as u8;
}
}
PixelBuffer::new(yuv, buf.width, buf.height, PixelFormat::Yuv420p)
}
#[must_use = "returns a new RGBA BT.2020 buffer"]
pub fn yuv420p_to_rgba_bt2020(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Yuv420p {
return Err(RangaError::InvalidFormat(format!(
"yuv420p_to_rgba_bt2020: expected Yuv420p, got {:?}",
buf.format
)));
}
let w = buf.width as usize;
let h = buf.height as usize;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let u_off = w * h;
let v_off = u_off + cw * ch;
let mut rgba = vec![0u8; w * h * 4];
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
for y in 0..h {
let cy = (y / 2).min(ch.saturating_sub(1));
let y_start = y * w;
let u_start = u_off + cy * cw;
let v_start = v_off + cy * cw;
let rgba_start = y * w * 4;
unsafe {
yuv_row_to_rgba_sse2(
&buf.data[y_start..y_start + w],
&buf.data[u_start..u_start + cw],
&buf.data[v_start..v_start + cw],
&mut rgba[rgba_start..rgba_start + w * 4],
377,
42,
146,
481,
);
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
{
for y in 0..h {
let cy = (y / 2).min(ch.saturating_sub(1));
for x in 0..w {
let cx = (x / 2).min(cw.saturating_sub(1));
let yi = buf.data[y * w + x] as i16;
let u = buf.data[u_off + cy * cw + cx] as i16 - 128;
let v = buf.data[v_off + cy * cw + cx] as i16 - 128;
let oi = (y * w + x) * 4;
rgba[oi] = (yi + ((377 * v) >> 8)).clamp(0, 255) as u8;
rgba[oi + 1] = (yi - ((42 * u + 146 * v) >> 8)).clamp(0, 255) as u8;
rgba[oi + 2] = (yi + ((481 * u) >> 8)).clamp(0, 255) as u8;
rgba[oi + 3] = 255;
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
}
#[must_use = "returns a new RGBA buffer"]
pub fn nv12_to_rgba(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Nv12 {
return Err(RangaError::InvalidFormat(format!(
"nv12_to_rgba: expected Nv12, got {:?}",
buf.format
)));
}
let w = buf.width as usize;
let h = buf.height as usize;
let cw = w.div_ceil(2);
let ch = h.div_ceil(2);
let uv_off = w * h;
let uv_stride = cw * 2; let mut rgba = vec![0u8; w * h * 4];
#[cfg(all(feature = "simd", target_arch = "x86_64"))]
{
for y in 0..h {
let cy = (y / 2).min(ch.saturating_sub(1));
let y_start = y * w;
let uv_start = uv_off + cy * uv_stride;
let rgba_start = y * w * 4;
unsafe {
yuv_row_to_rgba_nv12_sse2(
&buf.data[y_start..y_start + w],
&buf.data[uv_start..uv_start + uv_stride],
&mut rgba[rgba_start..rgba_start + w * 4],
359,
88,
183,
454,
);
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
#[cfg(not(all(feature = "simd", target_arch = "x86_64")))]
{
for y in 0..h {
let cy = (y / 2).min(ch.saturating_sub(1));
for x in 0..w {
let cx = (x / 2).min(cw.saturating_sub(1));
let yi = buf.data[y * w + x] as i16;
let uv_idx = uv_off + cy * uv_stride + cx * 2;
let u = buf.data[uv_idx] as i16 - 128;
let v = buf.data[uv_idx + 1] as i16 - 128;
let oi = (y * w + x) * 4;
rgba[oi] = (yi + ((359 * v) >> 8)).clamp(0, 255) as u8;
rgba[oi + 1] = (yi - ((88 * u + 183 * v) >> 8)).clamp(0, 255) as u8;
rgba[oi + 2] = (yi + ((454 * u) >> 8)).clamp(0, 255) as u8;
rgba[oi + 3] = 255;
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
}
#[must_use = "returns a new RGBA8 buffer"]
pub fn rgb8_to_rgba8(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Rgb8 {
return Err(RangaError::InvalidFormat(format!(
"rgb8_to_rgba8: expected Rgb8, got {:?}",
buf.format
)));
}
let n = buf.pixel_count();
let mut rgba = vec![0u8; n * 4];
for i in 0..n {
rgba[i * 4] = buf.data[i * 3];
rgba[i * 4 + 1] = buf.data[i * 3 + 1];
rgba[i * 4 + 2] = buf.data[i * 3 + 2];
rgba[i * 4 + 3] = 255;
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
#[must_use = "returns a new RGB8 buffer"]
pub fn rgba8_to_rgb8(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Rgba8 {
return Err(RangaError::InvalidFormat(format!(
"rgba8_to_rgb8: expected Rgba8, got {:?}",
buf.format
)));
}
let n = buf.pixel_count();
let mut rgb = vec![0u8; n * 3];
for i in 0..n {
rgb[i * 3] = buf.data[i * 4];
rgb[i * 3 + 1] = buf.data[i * 4 + 1];
rgb[i * 3 + 2] = buf.data[i * 4 + 2];
}
PixelBuffer::new(rgb, buf.width, buf.height, PixelFormat::Rgb8)
}
#[must_use = "returns a new RGBA8 buffer"]
pub fn argb8_to_rgba8(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Argb8 {
return Err(RangaError::InvalidFormat(format!(
"argb8_to_rgba8: expected Argb8, got {:?}",
buf.format
)));
}
let mut rgba = vec![0u8; buf.data.len()];
for (src, dst) in buf.data.chunks_exact(4).zip(rgba.chunks_exact_mut(4)) {
dst[0] = src[1];
dst[1] = src[2];
dst[2] = src[3];
dst[3] = src[0];
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
#[must_use = "returns a new ARGB8 buffer"]
pub fn rgba8_to_argb8(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Rgba8 {
return Err(RangaError::InvalidFormat(format!(
"rgba8_to_argb8: expected Rgba8, got {:?}",
buf.format
)));
}
let mut argb = vec![0u8; buf.data.len()];
for (src, dst) in buf.data.chunks_exact(4).zip(argb.chunks_exact_mut(4)) {
dst[0] = src[3];
dst[1] = src[0];
dst[2] = src[1];
dst[3] = src[2];
}
PixelBuffer::new(argb, buf.width, buf.height, PixelFormat::Argb8)
}
#[must_use = "returns a new RGBA8 buffer"]
pub fn rgbaf32_to_rgba8(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::RgbaF32 {
return Err(RangaError::InvalidFormat(format!(
"rgbaf32_to_rgba8: expected RgbaF32, got {:?}",
buf.format
)));
}
let n = buf.pixel_count();
let mut rgba = vec![0u8; n * 4];
for i in 0..n {
let base = i * 16;
for c in 0..4 {
let bytes = [
buf.data[base + c * 4],
buf.data[base + c * 4 + 1],
buf.data[base + c * 4 + 2],
buf.data[base + c * 4 + 3],
];
let v = f32::from_ne_bytes(bytes);
rgba[i * 4 + c] = (v * 255.0 + 0.5).clamp(0.0, 255.0) as u8;
}
}
PixelBuffer::new(rgba, buf.width, buf.height, PixelFormat::Rgba8)
}
#[must_use = "returns a new RgbaF32 buffer"]
pub fn rgba8_to_rgbaf32(buf: &PixelBuffer) -> Result<PixelBuffer, RangaError> {
if buf.format != PixelFormat::Rgba8 {
return Err(RangaError::InvalidFormat(format!(
"rgba8_to_rgbaf32: expected Rgba8, got {:?}",
buf.format
)));
}
let n = buf.pixel_count();
let mut f32data = vec![0u8; n * 16];
for i in 0..n {
for c in 0..4 {
let v = buf.data[i * 4 + c] as f32 / 255.0;
let base = i * 16 + c * 4;
f32data[base..base + 4].copy_from_slice(&v.to_ne_bytes());
}
}
PixelBuffer::new(f32data, buf.width, buf.height, PixelFormat::RgbaF32)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rgba_to_yuv_white() {
let buf = PixelBuffer::new(vec![255; 4 * 4 * 4], 4, 4, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p(&buf).unwrap();
assert_eq!(yuv.format, PixelFormat::Yuv420p);
assert!(yuv.data[0] > 250);
}
#[test]
fn rgba_to_yuv_black() {
let buf = PixelBuffer::new(vec![0; 4 * 4 * 4], 4, 4, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p(&buf).unwrap();
assert_eq!(yuv.data[0], 0);
}
#[test]
fn yuv_to_rgba_roundtrip() {
let rgba = PixelBuffer::new(vec![128; 8 * 8 * 4], 8, 8, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p(&rgba).unwrap();
let back = yuv420p_to_rgba(&yuv).unwrap();
assert!((back.data[0] as i16 - 128).unsigned_abs() < 10);
}
#[test]
fn wrong_format_rejected() {
let buf = PixelBuffer::new(vec![0; 4 * 4 * 3], 4, 4, PixelFormat::Rgb8).unwrap();
assert!(rgba_to_yuv420p(&buf).is_err());
}
#[test]
fn bt709_white() {
let buf = PixelBuffer::new(vec![255; 4 * 4 * 4], 4, 4, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p_bt709(&buf).unwrap();
assert!(yuv.data[0] > 250);
}
#[test]
fn bt709_roundtrip() {
let rgba = PixelBuffer::new(vec![128; 8 * 8 * 4], 8, 8, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p_bt709(&rgba).unwrap();
let back = yuv420p_to_rgba_bt709(&yuv).unwrap();
assert!((back.data[0] as i16 - 128).unsigned_abs() < 10);
}
#[test]
fn bt709_different_from_bt601() {
let red: Vec<u8> = [255, 0, 0, 255].repeat(16);
let buf = PixelBuffer::new(red, 4, 4, PixelFormat::Rgba8).unwrap();
let y601 = rgba_to_yuv420p(&buf).unwrap().data[0];
let y709 = rgba_to_yuv420p_bt709(&buf).unwrap().data[0];
assert_ne!(y601, y709, "BT.601 and BT.709 should differ for red");
}
#[test]
fn nv12_to_rgba_roundtrip() {
let argb_data: Vec<u8> = [255, 128, 128, 128].repeat(16);
let argb = PixelBuffer::new(argb_data, 4, 4, PixelFormat::Argb8).unwrap();
let nv12 = argb_to_nv12(&argb).unwrap();
let rgba = nv12_to_rgba(&nv12).unwrap();
assert_eq!(rgba.format, PixelFormat::Rgba8);
assert!((rgba.data[0] as i16 - 128).unsigned_abs() < 10);
}
#[test]
fn rgb8_rgba8_roundtrip() {
let rgb =
PixelBuffer::new(vec![100, 150, 200, 50, 75, 25], 2, 1, PixelFormat::Rgb8).unwrap();
let rgba = rgb8_to_rgba8(&rgb).unwrap();
assert_eq!(rgba.data[3], 255);
let back = rgba8_to_rgb8(&rgba).unwrap();
assert_eq!(back.data, rgb.data);
}
#[test]
fn argb8_rgba8_roundtrip() {
let argb = PixelBuffer::new(vec![200, 100, 50, 25], 1, 1, PixelFormat::Argb8).unwrap();
let rgba = argb8_to_rgba8(&argb).unwrap();
assert_eq!(rgba.data, vec![100, 50, 25, 200]);
let back = rgba8_to_argb8(&rgba).unwrap();
assert_eq!(back.data, argb.data);
}
#[test]
fn rgbaf32_rgba8_roundtrip() {
let rgba = PixelBuffer::new(vec![128, 64, 200, 255], 1, 1, PixelFormat::Rgba8).unwrap();
let f32buf = rgba8_to_rgbaf32(&rgba).unwrap();
let back = rgbaf32_to_rgba8(&f32buf).unwrap();
for i in 0..4 {
assert!(
(rgba.data[i] as i16 - back.data[i] as i16).unsigned_abs() <= 1,
"channel {i}"
);
}
}
#[test]
fn yuv420p_odd_dimensions_no_panic() {
let buf = PixelBuffer::new(vec![128; 5 * 3 * 4], 5, 3, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p(&buf).unwrap();
let _back = yuv420p_to_rgba(&yuv).unwrap();
}
#[test]
fn yuv420p_bt709_odd_dimensions_no_panic() {
let buf = PixelBuffer::new(vec![128; 7 * 5 * 4], 7, 5, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p_bt709(&buf).unwrap();
let _back = yuv420p_to_rgba_bt709(&yuv).unwrap();
}
#[test]
fn nv12_odd_dimensions_no_panic() {
let buf = PixelBuffer::new(vec![128; 5 * 3 * 4], 5, 3, PixelFormat::Argb8).unwrap();
let nv12 = argb_to_nv12(&buf).unwrap();
let _rgba = nv12_to_rgba(&nv12).unwrap();
}
#[test]
fn bt2020_white() {
let buf = PixelBuffer::new(vec![255; 4 * 4 * 4], 4, 4, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p_bt2020(&buf).unwrap();
assert!(yuv.data[0] > 250);
}
#[test]
fn bt2020_roundtrip() {
let rgba = PixelBuffer::new(vec![128; 8 * 8 * 4], 8, 8, PixelFormat::Rgba8).unwrap();
let yuv = rgba_to_yuv420p_bt2020(&rgba).unwrap();
let back = yuv420p_to_rgba_bt2020(&yuv).unwrap();
assert!((back.data[0] as i16 - 128).unsigned_abs() < 10);
}
#[test]
fn bt2020_different_from_bt709() {
let red: Vec<u8> = [255, 0, 0, 255].repeat(16);
let buf = PixelBuffer::new(red, 4, 4, PixelFormat::Rgba8).unwrap();
let y709 = rgba_to_yuv420p_bt709(&buf).unwrap().data[0];
let y2020 = rgba_to_yuv420p_bt2020(&buf).unwrap().data[0];
assert_ne!(y709, y2020, "BT.709 and BT.2020 should differ for red");
}
#[test]
fn yuv420p_1x1_no_panic() {
let buf = PixelBuffer::new(vec![128; 4], 1, 1, PixelFormat::Rgba8).unwrap();
let _yuv = rgba_to_yuv420p(&buf).unwrap();
}
}