use zenpixels_convert::RowConverter;
use zenpixels_convert::{AlphaMode, ChannelLayout, ChannelType, PixelDescriptor, TransferFunction};
#[test]
fn srgb_u8_to_linear_f32_accuracy() {
let from = PixelDescriptor::RGB8_SRGB;
let to = PixelDescriptor::new(
ChannelType::F32,
ChannelLayout::Rgb,
None,
TransferFunction::Linear,
);
let mut conv = RowConverter::new(from, to).unwrap();
let width = 3u32;
let src: Vec<u8> = vec![
0, 0, 0, 128, 128, 128, 255, 255, 255, ];
let mut dst = vec![0u8; width as usize * 3 * 4];
conv.convert_row(&src, &mut dst, width);
let f32_vals: &[f32] = bytemuck::cast_slice(&dst);
assert!((f32_vals[0] - 0.0).abs() < 1e-6, "black R: {}", f32_vals[0]);
assert!(
(f32_vals[3] - 0.2158).abs() < 0.01,
"mid-gray R: {} (expected ~0.2158)",
f32_vals[3]
);
assert!((f32_vals[6] - 1.0).abs() < 1e-6, "white R: {}", f32_vals[6]);
}
#[test]
fn linear_f32_to_srgb_u8_accuracy() {
let from = PixelDescriptor::new(
ChannelType::F32,
ChannelLayout::Rgb,
None,
TransferFunction::Linear,
);
let to = PixelDescriptor::RGB8_SRGB;
let mut conv = RowConverter::new(from, to).unwrap();
let width = 3u32;
let src_f32: Vec<f32> = vec![
0.0, 0.0, 0.0, 0.2158, 0.2158, 0.2158, 1.0, 1.0, 1.0, ];
let src: Vec<u8> = bytemuck::cast_slice(&src_f32).to_vec();
let mut dst = vec![0u8; width as usize * 3];
conv.convert_row(&src, &mut dst, width);
assert_eq!(dst[0], 0, "black");
assert!(
(dst[3] as i32 - 128).unsigned_abs() <= 2,
"mid-gray: {} (expected ~128)",
dst[3]
);
assert_eq!(dst[6], 255, "white");
}
#[test]
fn srgb_roundtrip_accuracy() {
let srgb_u8 = PixelDescriptor::RGB8_SRGB;
let linear_f32 = PixelDescriptor::new(
ChannelType::F32,
ChannelLayout::Rgb,
None,
TransferFunction::Linear,
);
let mut to_linear = RowConverter::new(srgb_u8, linear_f32).unwrap();
let mut to_srgb = RowConverter::new(linear_f32, srgb_u8).unwrap();
let width = 256u32;
let mut src = Vec::with_capacity(width as usize * 3);
for i in 0..256u16 {
src.push(i as u8);
src.push(128);
src.push(64);
}
let mut f32_buf = vec![0u8; width as usize * 3 * 4];
let mut back = vec![0u8; width as usize * 3];
to_linear.convert_row(&src, &mut f32_buf, width);
to_srgb.convert_row(&f32_buf, &mut back, width);
let mut max_drift = 0i32;
for i in 0..src.len() {
let drift = (src[i] as i32 - back[i] as i32).abs();
max_drift = max_drift.max(drift);
}
assert!(
max_drift <= 1,
"sRGB roundtrip max drift: {max_drift} (should be ≤1)"
);
}
#[test]
fn unknown_transfer_uses_naive() {
let from = PixelDescriptor::new(
ChannelType::U8,
ChannelLayout::Rgb,
None,
TransferFunction::Unknown,
);
let to = PixelDescriptor::new(
ChannelType::F32,
ChannelLayout::Rgb,
None,
TransferFunction::Unknown,
);
let mut conv = RowConverter::new(from, to).unwrap();
let width = 1u32;
let src: Vec<u8> = vec![128, 128, 128];
let mut dst = vec![0u8; 3 * 4];
conv.convert_row(&src, &mut dst, width);
let f32_vals: &[f32] = bytemuck::cast_slice(&dst);
assert!(
(f32_vals[0] - 0.50196).abs() < 0.001,
"naive u8→f32: {} (expected ~0.502, not ~0.216)",
f32_vals[0]
);
}
#[test]
fn srgb_rgba_to_linear_preserves_alpha_semantics() {
let from = PixelDescriptor::new(
ChannelType::U8,
ChannelLayout::Rgba,
Some(AlphaMode::Straight),
TransferFunction::Srgb,
);
let to = PixelDescriptor::new(
ChannelType::F32,
ChannelLayout::Rgba,
Some(AlphaMode::Straight),
TransferFunction::Linear,
);
let mut conv = RowConverter::new(from, to).unwrap();
let width = 1u32;
let src: Vec<u8> = vec![128, 128, 128, 128];
let mut dst = vec![0u8; 4 * 4];
conv.convert_row(&src, &mut dst, width);
let f32_vals: &[f32] = bytemuck::cast_slice(&dst);
assert!(
(f32_vals[0] - 0.2158).abs() < 0.01,
"R should be linearized: {}",
f32_vals[0]
);
}