use crate::color::{Cicp, MatrixCoefficients};
use crate::fmt::{BitDepth, ChromaFormat, ImageBuffer};
const Q13: u32 = 13;
const Q13_ONE: i64 = 1 << Q13; const Q13_ROUND: i64 = 1 << (Q13 - 1); const Q13_KY_LIMITED: i64 = 9539;
const Q13_KUV_LIMITED: i64 = 9326;
pub(crate) struct YuvPlanes {
pub(crate) y: Vec<u16>,
pub(crate) cb: Vec<u16>,
pub(crate) cr: Vec<u16>,
pub(crate) width: usize, pub(crate) height: usize,
pub(crate) chroma: ChromaFormat,
pub(crate) bit_depth: BitDepth,
}
pub(crate) fn yuv_to_rgb_with_color(
yuv: &YuvPlanes,
dw: usize,
dh: usize,
color: &Cicp,
) -> ImageBuffer {
if yuv.chroma.is_monochrome() {
let y_black = if color.full_range {
0i64
} else {
16i64 << yuv.bit_depth.minus8()
};
let k_y: i64 = if color.full_range {
Q13_ONE
} else {
Q13_KY_LIMITED
};
let max_val = yuv.bit_depth.max_val() as i64;
return if yuv.bit_depth == BitDepth::Eight {
let mut out = vec![0u8; dw * dh];
for (y, dst_row) in out.chunks_exact_mut(dw).enumerate() {
let row = y.min(yuv.height - 1);
let src_row = &yuv.y[row * yuv.width..][..yuv.width];
let copy_w = dw.min(yuv.width);
let (dst_copy, dst_edge) = dst_row.split_at_mut(copy_w);
for (dst, &luma) in dst_copy.iter_mut().zip(src_row.iter()) {
*dst = ((k_y * (luma as i64 - y_black) + Q13_ROUND) >> Q13).clamp(0, 255) as u8;
}
if let (Some(&last), false) = (src_row.last(), dst_edge.is_empty()) {
let last =
((k_y * (last as i64 - y_black) + Q13_ROUND) >> Q13).clamp(0, 255) as u8;
dst_edge.fill(last);
}
}
ImageBuffer::Luma8(out)
} else {
let mut out = vec![0u16; dw * dh];
for (y, dst_row) in out.chunks_exact_mut(dw).enumerate() {
let row = y.min(yuv.height - 1);
let src_row = &yuv.y[row * yuv.width..][..yuv.width];
let copy_w = dw.min(yuv.width);
let (dst_copy, dst_edge) = dst_row.split_at_mut(copy_w);
for (dst, &luma) in dst_copy.iter_mut().zip(src_row.iter()) {
*dst = ((k_y * (luma as i64 - y_black) + Q13_ROUND) >> Q13).clamp(0, max_val)
as u16;
}
if let (Some(&last), false) = (src_row.last(), dst_edge.is_empty()) {
let last = ((k_y * (last as i64 - y_black) + Q13_ROUND) >> Q13)
.clamp(0, max_val) as u16;
dst_edge.fill(last);
}
}
ImageBuffer::Luma16(out)
};
}
if color.matrix == MatrixCoefficients::YCgCo {
return ycgco_to_rgb(yuv, dw, dh);
}
let max_val = yuv.bit_depth.max_val() as i64;
let scale = 1i64 << (yuv.bit_depth.minus8());
let sub_w = yuv.chroma.sub_w();
let sub_h = yuv.chroma.sub_h();
let cw = yuv.width.div_ceil(sub_w);
let y_black = if color.full_range { 0 } else { 16 * scale };
let neutral = 128 * scale;
let k_y: i64 = if color.full_range {
Q13_ONE
} else {
Q13_KY_LIMITED
};
let k_uv: i64 = if color.full_range {
Q13_ONE
} else {
Q13_KUV_LIMITED
};
let (cr_r0, cb_g0, cr_g0, cb_b0) = match color.matrix {
MatrixCoefficients::Bt470Bg | MatrixCoefficients::Smpte170m => {
(11485i64, -2819i64, -5851i64, 14516i64)
}
MatrixCoefficients::Bt2020Ncl => (12080i64, -1348i64, -4681i64, 17546i64),
MatrixCoefficients::Identity => (0, 0, 0, 0),
_ => (12901i64, -1534i64, -3835i64, 15201i64), };
let cr_to_r = (cr_r0 * k_uv + Q13_ROUND) >> Q13;
let cb_to_g = (cb_g0 * k_uv + Q13_ROUND) >> Q13;
let cr_to_g = (cr_g0 * k_uv + Q13_ROUND) >> Q13;
let cb_to_b = (cb_b0 * k_uv + Q13_ROUND) >> Q13;
let pixel = |y_pix: usize, x_pix: usize| -> (i64, i64, i64) {
let y_row = y_pix.min(yuv.height - 1);
let c_row = (y_pix / sub_h).min(yuv.height.div_ceil(sub_h) - 1);
let x_col = x_pix.min(yuv.width - 1);
let c_col = (x_pix / sub_w).min(cw - 1);
let luma_raw = yuv.y[y_row * yuv.width + x_col] as i64;
if color.matrix == MatrixCoefficients::Identity {
let y_scaled = (k_y * (luma_raw - y_black) + Q13_ROUND) >> Q13;
(y_scaled, y_scaled, y_scaled)
} else {
let cb_raw = yuv.cb[c_row * cw + c_col] as i64;
let cr_raw = yuv.cr[c_row * cw + c_col] as i64;
let y_term = k_y * (luma_raw - y_black);
let cb_c = cb_raw - neutral;
let cr_c = cr_raw - neutral;
let r = (y_term + cr_to_r * cr_c + Q13_ROUND) >> Q13;
let g = (y_term + cb_to_g * cb_c + cr_to_g * cr_c + Q13_ROUND) >> Q13;
let b = (y_term + cb_to_b * cb_c + Q13_ROUND) >> Q13;
(r, g, b)
}
};
let fast = dw <= yuv.width && dh <= yuv.height;
let ch = yuv.height.div_ceil(sub_h);
if fast && color.matrix != MatrixCoefficients::Identity {
macro_rules! convert_loop {
($T:ty, $clampmax:expr, $variant:path) => {{
let mut rgb = vec![0 as $T; dw * dh * 3];
for (y_pix, row_out) in rgb.chunks_exact_mut(dw * 3).enumerate() {
let luma_base = y_pix * yuv.width;
let c_base = (y_pix / sub_h) * cw;
let luma_row = &yuv.y[luma_base..][..dw];
let cb_row = &yuv.cb[c_base..][..cw];
let cr_row = &yuv.cr[c_base..][..cw];
for (x_pix, (dst, &luma_raw)) in row_out
.as_chunks_mut::<3>()
.0
.iter_mut()
.zip(luma_row.iter())
.enumerate()
{
let c_col = x_pix / sub_w;
let cb_c = cb_row[c_col] as i64 - neutral;
let cr_c = cr_row[c_col] as i64 - neutral;
let yv = k_y * (luma_raw as i64 - y_black);
let r = (yv + cr_to_r * cr_c + Q13_ROUND) >> Q13;
let g = (yv + cb_to_g * cb_c + cr_to_g * cr_c + Q13_ROUND) >> Q13;
let b = (yv + cb_to_b * cb_c + Q13_ROUND) >> Q13;
dst[0] = r.clamp(0, $clampmax) as $T;
dst[1] = g.clamp(0, $clampmax) as $T;
dst[2] = b.clamp(0, $clampmax) as $T;
}
}
return $variant(rgb);
}};
}
if yuv.bit_depth == BitDepth::Eight {
convert_loop!(u8, 255, ImageBuffer::Rgb8);
} else {
convert_loop!(u16, max_val, ImageBuffer::Rgb16);
}
}
let _ = ch;
if yuv.bit_depth == BitDepth::Eight {
let mut rgb = vec![0u8; dw * dh * 3];
for (y_pix, row_out) in rgb.chunks_exact_mut(dw * 3).enumerate() {
for (x_pix, out) in row_out.as_chunks_mut::<3>().0.iter_mut().enumerate() {
let (r, g, b) = pixel(y_pix, x_pix);
out[0] = r.clamp(0, 255) as u8;
out[1] = g.clamp(0, 255) as u8;
out[2] = b.clamp(0, 255) as u8;
}
}
ImageBuffer::Rgb8(rgb)
} else {
let mut rgb = vec![0u16; dw * dh * 3];
for (y_pix, row_out) in rgb.chunks_exact_mut(dw * 3).enumerate() {
for (x_pix, out) in row_out.as_chunks_mut::<3>().0.iter_mut().enumerate() {
let (r, g, b) = pixel(y_pix, x_pix);
out[0] = r.clamp(0, max_val) as u16;
out[1] = g.clamp(0, max_val) as u16;
out[2] = b.clamp(0, max_val) as u16;
}
}
ImageBuffer::Rgb16(rgb)
}
}
pub(crate) fn ycgco_to_rgb(yuv: &YuvPlanes, dw: usize, dh: usize) -> ImageBuffer {
let scale = 1i64 << yuv.bit_depth.minus8();
let neutral = 128 * scale; let max_val = yuv.bit_depth.max_val() as i64;
let sub_w = yuv.chroma.sub_w();
let sub_h = yuv.chroma.sub_h();
let cw = yuv.width.div_ceil(sub_w);
let ch = yuv.height.div_ceil(sub_h);
macro_rules! run {
($T: ty, $cmax: expr, $variant: path) => {{
let mut rgb = vec![0 as $T; dw * dh * 3];
for (y_pix, row_out) in rgb.chunks_exact_mut(dw * 3).enumerate() {
let y_row = y_pix.min(yuv.height - 1);
let c_row = (y_pix / sub_h).min(ch - 1);
let l_row = &yuv.y[y_row * yuv.width..][..yuv.width];
let cb_row = &yuv.cb[c_row * cw..][..cw];
let cr_row = &yuv.cr[c_row * cw..][..cw];
for (x_pix, dst) in row_out.as_chunks_mut::<3>().0.iter_mut().enumerate() {
let x_col = x_pix.min(yuv.width - 1);
let c_col = (x_pix / sub_w).min(cw - 1);
let y = l_row[x_col] as i64;
let cg = cb_row[c_col] as i64 - neutral;
let co = cr_row[c_col] as i64 - neutral;
let t = y - cg;
dst[0] = (t + co).clamp(0, $cmax) as $T; dst[1] = (y + cg).clamp(0, $cmax) as $T; dst[2] = (t - co).clamp(0, $cmax) as $T; }
}
$variant(rgb)
}};
}
if yuv.bit_depth == BitDepth::Eight {
run!(u8, 255, ImageBuffer::Rgb8)
} else {
run!(u16, max_val, ImageBuffer::Rgb16)
}
}