use crate::foundation::consts::{
YCBCR_B_TO_CB, YCBCR_B_TO_CR, YCBCR_B_TO_Y, YCBCR_G_TO_CB, YCBCR_G_TO_CR, YCBCR_G_TO_Y,
YCBCR_R_TO_CB, YCBCR_R_TO_CR, YCBCR_R_TO_Y,
};
const CHROMA_OFFSET: f32 = 128.0;
#[inline]
fn linear_to_srgb(x: f32) -> f32 {
linear_srgb::default::linear_to_srgb(x)
}
#[inline]
pub fn linear_u16_to_srgb_255(value: u16) -> f32 {
let linear = value as f32 / 65535.0;
linear_to_srgb_255(linear)
}
#[inline]
pub fn linear_rgb16_to_ycbcr(r: u16, g: u16, b: u16) -> (f32, f32, f32) {
let r = linear_u16_to_srgb_255(r);
let g = linear_u16_to_srgb_255(g);
let b = linear_u16_to_srgb_255(b);
let y = YCBCR_R_TO_Y.mul_add(r, YCBCR_G_TO_Y.mul_add(g, YCBCR_B_TO_Y * b));
let cb = YCBCR_R_TO_CB.mul_add(r, YCBCR_G_TO_CB.mul_add(g, YCBCR_B_TO_CB * b)) + CHROMA_OFFSET;
let cr = YCBCR_R_TO_CR.mul_add(r, YCBCR_G_TO_CR.mul_add(g, YCBCR_B_TO_CR * b)) + CHROMA_OFFSET;
(y, cb, cr)
}
#[inline]
pub fn linear_to_srgb_255(x: f32) -> f32 {
if x <= 0.0 {
return 0.0;
}
let x = if x > 1.0 { x / (1.0 + x) } else { x };
linear_to_srgb(x) * 255.0
}
#[inline]
#[allow(dead_code)]
pub fn linear_to_srgb_fast(x: f32) -> f32 {
linear_to_srgb_255(x) / 255.0
}
#[inline]
pub fn linear_f32_to_srgb_255_fast(x: f32) -> f32 {
linear_to_srgb_255(x)
}
#[inline]
pub fn linear_f32_to_srgb_255_lut(x: f32) -> f32 {
linear_to_srgb_255(x)
}
#[inline]
pub fn linear_rgbf32_to_ycbcr_fast(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
let r = linear_to_srgb_255(r);
let g = linear_to_srgb_255(g);
let b = linear_to_srgb_255(b);
let y = YCBCR_R_TO_Y.mul_add(r, YCBCR_G_TO_Y.mul_add(g, YCBCR_B_TO_Y * b));
let cb = YCBCR_R_TO_CB.mul_add(r, YCBCR_G_TO_CB.mul_add(g, YCBCR_B_TO_CB * b)) + CHROMA_OFFSET;
let cr = YCBCR_R_TO_CR.mul_add(r, YCBCR_G_TO_CR.mul_add(g, YCBCR_B_TO_CR * b)) + CHROMA_OFFSET;
(y, cb, cr)
}
#[inline]
#[allow(dead_code)]
pub fn linear_rgbf32_to_ycbcr_lut(r: f32, g: f32, b: f32) -> (f32, f32, f32) {
linear_rgbf32_to_ycbcr_fast(r, g, b)
}
#[inline(always)]
pub fn linear_to_srgb_255_x8(x: &[f32; 8]) -> [f32; 8] {
let mut buf = [0.0f32; 8];
for i in 0..8 {
let v = x[i].max(0.0);
buf[i] = if v > 1.0 { v / (1.0 + v) } else { v };
}
linear_srgb::default::linear_to_srgb_slice(&mut buf);
for i in 0..8 {
buf[i] *= 255.0;
}
buf
}
#[inline(always)]
pub fn linear_u16_to_srgb_255_x8(values: &[u16; 8]) -> [f32; 8] {
let mut buf = [0.0f32; 8];
for i in 0..8 {
buf[i] = values[i] as f32 / 65535.0;
}
linear_srgb::default::linear_to_srgb_slice(&mut buf);
for i in 0..8 {
buf[i] *= 255.0;
}
buf
}
#[inline(always)]
pub fn linear_rgb16_to_ycbcr_x8(
r: &[u16; 8],
g: &[u16; 8],
b: &[u16; 8],
) -> ([f32; 8], [f32; 8], [f32; 8]) {
let r = linear_u16_to_srgb_255_x8(r);
let g = linear_u16_to_srgb_255_x8(g);
let b = linear_u16_to_srgb_255_x8(b);
rgb_to_ycbcr_x8(&r, &g, &b)
}
#[inline(always)]
pub fn linear_rgbf32_to_ycbcr_x8(
r: &[f32; 8],
g: &[f32; 8],
b: &[f32; 8],
) -> ([f32; 8], [f32; 8], [f32; 8]) {
let r = linear_to_srgb_255_x8(r);
let g = linear_to_srgb_255_x8(g);
let b = linear_to_srgb_255_x8(b);
rgb_to_ycbcr_x8(&r, &g, &b)
}
#[inline(always)]
fn rgb_to_ycbcr_x8(r: &[f32; 8], g: &[f32; 8], b: &[f32; 8]) -> ([f32; 8], [f32; 8], [f32; 8]) {
let mut y = [0.0f32; 8];
let mut cb = [0.0f32; 8];
let mut cr = [0.0f32; 8];
for i in 0..8 {
y[i] = YCBCR_R_TO_Y * r[i] + YCBCR_G_TO_Y * g[i] + YCBCR_B_TO_Y * b[i];
cb[i] = YCBCR_R_TO_CB * r[i] + YCBCR_G_TO_CB * g[i] + YCBCR_B_TO_CB * b[i] + CHROMA_OFFSET;
cr[i] = YCBCR_R_TO_CR * r[i] + YCBCR_G_TO_CR * g[i] + YCBCR_B_TO_CR * b[i] + CHROMA_OFFSET;
}
(y, cb, cr)
}
#[inline]
#[allow(dead_code)]
pub fn linear_to_srgb_reference(x: f32) -> f32 {
if x <= 0.0 {
return 0.0;
}
let x = if x > 1.0 { x / (1.0 + x) } else { x };
if x <= 0.003_130_8 {
x * 12.92
} else {
1.055 * x.powf(1.0 / 2.4) - 0.055
}
}
#[inline]
#[allow(dead_code)]
pub fn linear_f32_to_srgb_255_reference(x: f32) -> f32 {
linear_to_srgb_reference(x) * 255.0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_u16_endpoints() {
assert!((linear_u16_to_srgb_255(0) - 0.0).abs() < 0.001);
assert!((linear_u16_to_srgb_255(65535) - 255.0).abs() < 0.001);
let mid = linear_u16_to_srgb_255(32768);
assert!(mid > 180.0 && mid < 195.0, "mid gray = {}", mid);
}
#[test]
fn test_u16_matches_reference() {
for i in (0..65536).step_by(256) {
let linear = i as f32 / 65535.0;
let result = linear_u16_to_srgb_255(i as u16);
let ref_val = linear_f32_to_srgb_255_reference(linear);
let diff = (result - ref_val).abs();
assert!(
diff < 0.1,
"u16 mismatch at {}: result={}, ref={}, diff={}",
i,
result,
ref_val,
diff
);
}
}
#[test]
fn test_f32_fast_accuracy() {
let mut max_error = 0.0f32;
let mut max_error_at = 0.0f32;
for i in 0..1000 {
let linear = i as f32 / 999.0;
let fast = linear_f32_to_srgb_255_fast(linear);
let reference = linear_f32_to_srgb_255_reference(linear);
let error = (fast - reference).abs();
if error > max_error {
max_error = error;
max_error_at = linear;
}
}
println!("Fast max error: {} at linear={}", max_error, max_error_at);
assert!(
max_error < 0.1,
"Fast approximation error too high: {} at {}",
max_error,
max_error_at
);
}
#[test]
fn test_ycbcr_conversion_u16() {
let (y, cb, cr) = linear_rgb16_to_ycbcr(32768, 32768, 32768);
assert!(y > 100.0 && y < 200.0, "Y = {}", y);
assert!(cb > 120.0 && cb < 136.0, "Cb = {}", cb);
assert!(cr > 120.0 && cr < 136.0, "Cr = {}", cr);
let (y, cb, cr) = linear_rgb16_to_ycbcr(65535, 0, 0);
assert!(y > 50.0 && y < 100.0, "Red Y = {}", y);
assert!(cb < 128.0, "Red Cb = {}", cb); assert!(cr > 200.0, "Red Cr = {}", cr); }
#[test]
fn test_hdr_handling() {
let hdr_2 = linear_f32_to_srgb_255_fast(2.0);
let hdr_10 = linear_f32_to_srgb_255_fast(10.0);
assert!(hdr_2 < 255.0 && hdr_2 > 200.0, "HDR 2.0 = {}", hdr_2);
assert!(hdr_10 < 255.0 && hdr_10 > hdr_2, "HDR 10.0 = {}", hdr_10);
assert!(hdr_10 > hdr_2);
}
#[test]
fn test_negative_handling() {
assert_eq!(linear_f32_to_srgb_255_fast(-0.5), 0.0);
assert_eq!(linear_f32_to_srgb_255_lut(-0.5), 0.0);
assert_eq!(linear_u16_to_srgb_255(0), 0.0);
}
#[test]
fn bench_conversion_methods() {
use std::time::Instant;
const ITERATIONS: usize = 1_000_000;
let test_values: Vec<f32> = (0..1000).map(|i| i as f32 / 999.0).collect();
let test_u16: Vec<u16> = (0..1000).map(|i| (i * 65) as u16).collect();
for &v in &test_values {
let _ = linear_f32_to_srgb_255_reference(v);
let _ = linear_f32_to_srgb_255_fast(v);
}
for &v in &test_u16 {
let _ = linear_u16_to_srgb_255(v);
}
let start = Instant::now();
let mut sum = 0.0f32;
for _ in 0..ITERATIONS / 1000 {
for &v in &test_values {
sum += linear_f32_to_srgb_255_reference(v);
}
}
let ref_time = start.elapsed();
println!("Reference (powf): {:?}, sum={}", ref_time, sum);
let start = Instant::now();
let mut sum = 0.0f32;
for _ in 0..ITERATIONS / 1000 {
for &v in &test_values {
sum += linear_f32_to_srgb_255_fast(v);
}
}
let fast_time = start.elapsed();
println!("Fast (linear-srgb): {:?}, sum={}", fast_time, sum);
let start = Instant::now();
let mut sum = 0.0f32;
for _ in 0..ITERATIONS / 1000 {
for &v in &test_u16 {
sum += linear_u16_to_srgb_255(v);
}
}
let u16_time = start.elapsed();
println!("U16 conversion: {:?}, sum={}", u16_time, sum);
let ref_ns = ref_time.as_nanos() as f64;
println!(
"\nSpeedups vs reference:\n Fast: {:.1}x\n U16: {:.1}x",
ref_ns / fast_time.as_nanos() as f64,
ref_ns / u16_time.as_nanos() as f64
);
}
}