const YMASK: u32 = 0x00FF_0000;
const UMASK: u32 = 0x0000_FF00;
const VMASK: u32 = 0x0000_00FF;
const TR_Y: i32 = 0x0030_0000;
const TR_U: i32 = 0x0000_0700;
const TR_V: i32 = 0x0000_0006;
const MASK_RGB: u32 = 0x00FF_FFFF;
#[inline]
#[must_use]
pub(crate) fn rgb_to_yuv(c: u32) -> u32 {
let c = c & MASK_RGB;
let r = f64::from((c >> 16) & 0xFF);
let g = f64::from((c >> 8) & 0xFF);
let b = f64::from(c & 0xFF);
let y = b.mul_add(0.114, r.mul_add(0.299, 0.587 * g)) as u32;
let u = (b.mul_add(0.5, r.mul_add(-0.169, -0.331 * g)) as u32).wrapping_add(128);
let v = (b.mul_add(-0.081, r.mul_add(0.5, -0.419 * g)) as u32).wrapping_add(128);
(y << 16).wrapping_add(u << 8).wrapping_add(v)
}
#[inline]
#[must_use]
pub(crate) fn yuv_diff(yuv1: u32, yuv2: u32) -> bool {
let dy = (yuv1 & YMASK).wrapping_sub(yuv2 & YMASK) as i32;
let du = (yuv1 & UMASK).wrapping_sub(yuv2 & UMASK) as i32;
let dv = (yuv1 & VMASK).wrapping_sub(yuv2 & VMASK) as i32;
dy.wrapping_abs() > TR_Y || du.wrapping_abs() > TR_U || dv.wrapping_abs() > TR_V
}
#[inline]
#[must_use]
pub(crate) fn colors_differ(c1: u32, c2: u32) -> bool {
yuv_diff(rgb_to_yuv(c1), rgb_to_yuv(c2))
}
#[cfg(test)]
mod tests {
use super::*;
const TABLE_FNV64: u64 = 0x3636_1769_3F45_5EF9;
#[test]
fn full_conversion_matches_checksum() {
let mut h: u64 = 0xcbf2_9ce4_8422_2325;
for c in 0u32..16_777_216 {
let v = rgb_to_yuv(c);
for s in (0..32).step_by(8) {
h ^= u64::from((v >> s) & 0xFF);
h = h.wrapping_mul(0x0000_0100_0000_01b3);
}
}
assert_eq!(h, TABLE_FNV64, "conversion checksum mismatch");
}
#[test]
fn named_colors_convert_exactly() {
let cases = [
(0x000000u32, 0x0000_8080u32),
(0xFFFFFF, 0x00FF_8080),
(0xFF0000, 0x004C_80FF),
(0x00FF00, 0x0095_8080),
(0x0000FF, 0x001D_FF80),
(0x808080, 0x007F_8080),
(0x123456, 0x002D_9680),
(0x0E571F, 0x003A_8080),
(0x244A27, 0x003A_8080),
];
for (rgb, want) in cases {
assert_eq!(rgb_to_yuv(rgb), want, "rgb {rgb:06X}");
}
}
#[test]
fn alpha_is_ignored_by_conversion() {
for alpha in [0x00u32, 0x40, 0x80, 0xFF] {
let c = (alpha << 24) | 0x123456;
assert_eq!(rgb_to_yuv(c), rgb_to_yuv(0x123456));
}
}
#[test]
fn negative_chroma_saturates_to_bias() {
let red = rgb_to_yuv(0xFF0000);
assert_eq!((red >> 8) & 0xFF, 0x80, "U field of pure red");
let green = rgb_to_yuv(0x00FF00);
assert_eq!(green & 0xFF, 0x80, "V field of pure green");
}
#[test]
fn diff_is_false_for_equal_colors() {
assert!(!yuv_diff(0x003A_8080, 0x003A_8080));
}
#[test]
fn diff_respects_each_threshold() {
let base = 0x0000_0000u32;
assert!(!yuv_diff(base, 0x0030_0000));
assert!(yuv_diff(base, 0x0031_0000));
assert!(!yuv_diff(base, 0x0000_0700));
assert!(yuv_diff(base, 0x0000_0800));
assert!(!yuv_diff(base, 0x0000_0006));
assert!(yuv_diff(base, 0x0000_0007));
}
#[test]
fn diff_uses_in_place_field_subtraction() {
let a = 0x0E571F;
let b = 0x244A27;
assert_eq!(rgb_to_yuv(a), rgb_to_yuv(b));
assert!(!colors_differ(a, b));
}
}