use super::*;
const HSV_SHIFT: u32 = 12;
const HSV_RND: i32 = 1 << (HSV_SHIFT - 1);
const SDIV_TABLE: [i32; 256] = {
let mut t = [0i32; 256];
let mut i = 1usize;
while i < 256 {
let n: i32 = 255 << HSV_SHIFT;
t[i] = (n + (i as i32) / 2) / (i as i32);
i += 1;
}
t
};
const HDIV_TABLE: [i32; 256] = {
let mut t = [0i32; 256];
let mut i = 1usize;
while i < 256 {
let n: i32 = 30 << HSV_SHIFT;
t[i] = (n + (i as i32) / 2) / (i as i32);
i += 1;
}
t
};
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn rgb_to_hsv_row(
rgb: &[u8],
h_out: &mut [u8],
s_out: &mut [u8],
v_out: &mut [u8],
width: usize,
) {
debug_assert!(rgb.len() >= width * 3, "rgb row too short");
debug_assert!(h_out.len() >= width, "H row too short");
debug_assert!(s_out.len() >= width, "S row too short");
debug_assert!(v_out.len() >= width, "V row too short");
for x in 0..width {
let r = rgb[x * 3] as i32;
let g = rgb[x * 3 + 1] as i32;
let b = rgb[x * 3 + 2] as i32;
let (h, s, v) = rgb_to_hsv_pixel(r, g, b);
h_out[x] = h;
s_out[x] = s;
v_out[x] = v;
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) const fn luma_coefficients_q15(matrix: ColorMatrix) -> (i32, i32, i32) {
match matrix {
ColorMatrix::Bt601 | ColorMatrix::Fcc => (9798, 19235, 3735),
ColorMatrix::Bt709 => (6966, 23436, 2366),
ColorMatrix::Bt2020Ncl => (8607, 22217, 1944),
ColorMatrix::Smpte240m => (6947, 22971, 2851),
ColorMatrix::YCgCo => (8192, 16384, 8192),
_ => (6966, 23436, 2366),
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn rgb_to_luma_row(
rgb: &[u8],
luma_out: &mut [u8],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
debug_assert!(rgb.len() >= width * 3, "rgb row too short");
debug_assert!(luma_out.len() >= width, "luma row too short");
let (k_r, k_g, k_b) = luma_coefficients_q15(matrix);
const RND: i32 = 1 << 14;
if full_range {
for x in 0..width {
let r = rgb[x * 3] as i32;
let g = rgb[x * 3 + 1] as i32;
let b = rgb[x * 3 + 2] as i32;
let y = (k_r * r + k_g * g + k_b * b + RND) >> 15;
luma_out[x] = y.clamp(0, 255) as u8;
}
} else {
const LIMITED_SCALE_Q15: i32 = 28142;
for x in 0..width {
let r = rgb[x * 3] as i32;
let g = rgb[x * 3 + 1] as i32;
let b = rgb[x * 3 + 2] as i32;
let y_full = (k_r * r + k_g * g + k_b * b + RND) >> 15;
let y_full_clamped = y_full.clamp(0, 255);
let y_lim = 16 + ((y_full_clamped * LIMITED_SCALE_Q15 + RND) >> 15);
luma_out[x] = y_lim.clamp(0, 255) as u8;
}
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
pub(crate) fn rgb_to_luma_u16_row(
rgb: &[u8],
luma_out: &mut [u16],
width: usize,
matrix: ColorMatrix,
full_range: bool,
) {
debug_assert!(rgb.len() >= width * 3, "rgb row too short");
debug_assert!(luma_out.len() >= width, "luma row too short");
let (k_r, k_g, k_b) = luma_coefficients_q15(matrix);
const RND: i32 = 1 << 14;
if full_range {
for x in 0..width {
let r = rgb[x * 3] as i32;
let g = rgb[x * 3 + 1] as i32;
let b = rgb[x * 3 + 2] as i32;
let y = (k_r * r + k_g * g + k_b * b + RND) >> 15;
luma_out[x] = y.clamp(0, 255) as u16;
}
} else {
const LIMITED_SCALE_Q15: i32 = 28142;
for x in 0..width {
let r = rgb[x * 3] as i32;
let g = rgb[x * 3 + 1] as i32;
let b = rgb[x * 3 + 2] as i32;
let y_full = (k_r * r + k_g * g + k_b * b + RND) >> 15;
let y_full_clamped = y_full.clamp(0, 255);
let y_lim = 16 + ((y_full_clamped * LIMITED_SCALE_Q15 + RND) >> 15);
luma_out[x] = y_lim.clamp(0, 255) as u16;
}
}
}
#[cfg_attr(not(tarpaulin), inline(always))]
fn rgb_to_hsv_pixel(r: i32, g: i32, b: i32) -> (u8, u8, u8) {
let v = r.max(g.max(b));
let min = r.min(g.min(b));
let delta = v - min;
let s = ((delta * SDIV_TABLE[v as usize]) + HSV_RND) >> HSV_SHIFT;
let h = if delta == 0 {
0
} else if v == r {
let diff = g - b;
let h_raw = ((diff * HDIV_TABLE[delta as usize]) + HSV_RND) >> HSV_SHIFT;
if h_raw < 0 { h_raw + 180 } else { h_raw }
} else if v == g {
let diff = b - r;
(((diff * HDIV_TABLE[delta as usize]) + HSV_RND) >> HSV_SHIFT) + 60
} else {
let diff = r - g;
(((diff * HDIV_TABLE[delta as usize]) + HSV_RND) >> HSV_SHIFT) + 120
};
(h.clamp(0, 179) as u8, s.clamp(0, 255) as u8, v as u8)
}