use crate::{BitDepth, ChromaFormat, Yuv};
#[inline]
pub(crate) fn rgb_to_ycgco_y(r: i32, g: i32, b: i32) -> i32 {
let hg0 = g >> 1;
hg0 + ((r + b) >> 2)
}
#[inline]
pub(crate) fn rgb_to_ycgco_cg(r: i32, g: i32, b: i32, neutral: i32) -> i32 {
((g >> 1) - ((r + b) >> 2)) + neutral
}
#[inline]
pub(crate) fn rgb_to_ycgco_co(r: i32, _g: i32, b: i32, neutral: i32) -> i32 {
((r - b) >> 1) + neutral
}
pub(crate) fn rgb_to_ycgco(
rgb: &[u16],
width: u32,
height: u32,
chroma: ChromaFormat,
bit_depth: BitDepth,
) -> Yuv {
let w = width as usize;
let h = height as usize;
let maxv = bit_depth.max_val() as i32;
let neutral = bit_depth.neutral() as i32;
if chroma.is_monochrome() {
let channels = rgb.len() / (w * h);
let y_plane: Vec<u16> = if channels == 1 {
rgb.to_vec()
} else if channels == 4 {
rgb.as_chunks::<4>()
.0
.iter()
.map(|px| {
let (r, g, b) = (px[0] as i32, px[1] as i32, px[2] as i32);
rgb_to_ycgco_y(r, g, b).clamp(0, maxv) as u16
})
.collect()
} else if channels == 3 {
rgb.as_chunks::<3>()
.0
.iter()
.map(|px| {
let (r, g, b) = (px[0] as i32, px[1] as i32, px[2] as i32);
rgb_to_ycgco_y(r, g, b).clamp(0, maxv) as u16
})
.collect()
} else {
unimplemented!(
"Amount of channels {} in 'rgb_to_ycgco' is not supported",
channels
)
};
return Yuv {
y: y_plane,
cb: Vec::new(),
cr: Vec::new(),
width,
height,
display_w: width,
display_h: height,
chroma,
bit_depth,
};
}
let sw = chroma.sub_w();
let sh = chroma.sub_h();
let cw = w.div_ceil(sw);
let ch = h.div_ceil(sh);
let mut y_plane = vec![0u16; w * h];
let mut cg_plane = vec![0u16; cw * ch]; let mut co_plane = vec![0u16; cw * ch];
let process_row =
|src: &[u16], y_dst: &mut [u16], cg_dst: Option<&mut [u16]>, co_dst: Option<&mut [u16]>| {
for (y_out, px) in y_dst.iter_mut().zip(src.as_chunks::<3>().0.iter()) {
let (r, g, b) = (px[0] as i32, px[1] as i32, px[2] as i32);
*y_out = rgb_to_ycgco_y(r, g, b).clamp(0, maxv) as u16;
}
if let (Some(cg_out), Some(co_out)) = (cg_dst, co_dst) {
let pairs = src.as_chunks::<6>();
let remainder = pairs.1;
for ((cg_out, co_out), pair) in
cg_out.iter_mut().zip(co_out.iter_mut()).zip(pairs.0.iter())
{
let (r0, g0, b0) = (pair[0] as i32, pair[1] as i32, pair[2] as i32);
let (r1, g1, b1) = (pair[3] as i32, pair[4] as i32, pair[5] as i32);
*cg_out = ((rgb_to_ycgco_cg(r0, g0, b0, neutral)
+ rgb_to_ycgco_cg(r1, g1, b1, neutral)
+ 1)
>> 1)
.clamp(0, maxv) as u16;
*co_out = ((rgb_to_ycgco_co(r0, g0, b0, neutral)
+ rgb_to_ycgco_co(r1, g1, b1, neutral)
+ 1)
>> 1)
.clamp(0, maxv) as u16;
}
if !remainder.is_empty() {
let (r, g, b) = (
remainder[0] as i32,
remainder[1] as i32,
remainder[2] as i32,
);
if let Some(cg) = cg_out.last_mut() {
*cg = rgb_to_ycgco_cg(r, g, b, neutral).clamp(0, maxv) as u16;
}
if let Some(co) = co_out.last_mut() {
*co = rgb_to_ycgco_co(r, g, b, neutral).clamp(0, maxv) as u16;
}
}
}
};
let blend_chroma_row = |src: &[u16], cg_row: &mut [u16], co_row: &mut [u16]| {
let pairs = src.as_chunks::<6>();
let remainder = pairs.1;
for ((cg_out, co_out), pair) in cg_row.iter_mut().zip(co_row.iter_mut()).zip(pairs.0.iter())
{
let (r0, g0, b0) = (pair[0] as i32, pair[1] as i32, pair[2] as i32);
let (r1, g1, b1) = (pair[3] as i32, pair[4] as i32, pair[5] as i32);
let cg1 =
((rgb_to_ycgco_cg(r0, g0, b0, neutral) + rgb_to_ycgco_cg(r1, g1, b1, neutral) + 1)
>> 1)
.clamp(0, maxv);
let co1 =
((rgb_to_ycgco_co(r0, g0, b0, neutral) + rgb_to_ycgco_co(r1, g1, b1, neutral) + 1)
>> 1)
.clamp(0, maxv);
*cg_out = ((*cg_out as i32 + cg1 + 1) >> 1) as u16;
*co_out = ((*co_out as i32 + co1 + 1) >> 1) as u16;
}
if !remainder.is_empty() {
let (r, g, b) = (
remainder[0] as i32,
remainder[1] as i32,
remainder[2] as i32,
);
if let Some(cg_out) = cg_row.last_mut() {
let cg1 = rgb_to_ycgco_cg(r, g, b, neutral).clamp(0, maxv);
*cg_out = ((*cg_out as i32 + cg1 + 1) >> 1) as u16;
}
if let Some(co_out) = co_row.last_mut() {
let co1 = rgb_to_ycgco_co(r, g, b, neutral).clamp(0, maxv);
*co_out = ((*co_out as i32 + co1 + 1) >> 1) as u16;
}
}
};
match chroma {
ChromaFormat::Yuv444 => {
for (row, ((y_row, cg_row), co_row)) in y_plane
.chunks_exact_mut(w)
.zip(cg_plane.chunks_exact_mut(cw))
.zip(co_plane.chunks_exact_mut(cw))
.enumerate()
{
let src = &rgb[row * w * 3..(row + 1) * w * 3];
for (((y_out, cg_out), co_out), px) in y_row
.iter_mut()
.zip(cg_row.iter_mut())
.zip(co_row.iter_mut())
.zip(src.as_chunks::<3>().0.iter())
{
let (r, g, b) = (px[0] as i32, px[1] as i32, px[2] as i32);
*y_out = rgb_to_ycgco_y(r, g, b).clamp(0, maxv) as u16;
*cg_out = rgb_to_ycgco_cg(r, g, b, neutral).clamp(0, maxv) as u16;
*co_out = rgb_to_ycgco_co(r, g, b, neutral).clamp(0, maxv) as u16;
}
}
}
ChromaFormat::Yuv422 => {
for (row, ((y_row, cg_row), co_row)) in y_plane
.chunks_exact_mut(w)
.zip(cg_plane.chunks_exact_mut(cw))
.zip(co_plane.chunks_exact_mut(cw))
.enumerate()
{
let src = &rgb[row * w * 3..(row + 1) * w * 3];
process_row(src, y_row, Some(cg_row), Some(co_row));
}
}
ChromaFormat::Yuv420 => {
let full_pairs = h / 2;
for chroma_row in 0..full_pairs {
let luma_row0 = chroma_row * 2;
let luma_row1 = luma_row0 + 1;
let src0 = &rgb[luma_row0 * w * 3..luma_row1 * w * 3];
let src1 = &rgb[luma_row1 * w * 3..(luma_row1 + 1) * w * 3];
let y_dst = &mut y_plane[luma_row0 * w..(luma_row1 + 1) * w];
let (y_row0, y_row1) = y_dst.split_at_mut(w);
let cg_row = &mut cg_plane[chroma_row * cw..(chroma_row + 1) * cw];
let co_row = &mut co_plane[chroma_row * cw..(chroma_row + 1) * cw];
process_row(src0, y_row0, Some(cg_row), Some(co_row));
process_row(src1, y_row1, None, None);
blend_chroma_row(src1, cg_row, co_row);
}
if h & 1 != 0 {
let last_row = h - 1;
let last_chroma = ch - 1;
let src = &rgb[last_row * w * 3..(last_row + 1) * w * 3];
let y_row = &mut y_plane[last_row * w..last_row * w + w];
let cg_row = &mut cg_plane[last_chroma * cw..last_chroma * cw + cw];
let co_row = &mut co_plane[last_chroma * cw..last_chroma * cw + cw];
process_row(src, y_row, Some(cg_row), Some(co_row));
}
}
ChromaFormat::Monochrome => unreachable!("handled above"),
}
Yuv {
y: y_plane,
cb: cg_plane, cr: co_plane, width,
height,
display_w: width,
display_h: height,
chroma,
bit_depth,
}
}