mirror of
https://github.com/storytold/photocraft.git
synced 2026-11-15 06:34:51 +00:00
test(cms): cover the 8-bit first-stage table with curv table TRCs and the 16-bit path
The exact-core test only used sRGB, whose curves are parametric. Run it over a profile with distinct per-channel curv table TRCs as well, and check convert_u16 against the untabulated evaluation for both. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
c64ca3365b
commit
d61c52e8ae
1 file changed
+39
-6
@@ -857,11 +857,14 @@ fn matrix_trc_blocks_match_per_pixel_eval() {
|
||||
}
|
||||
}
|
||||
|
||||
/// 8-bit conversion through an analytic pipeline (no lookup table, here RGB to Lab) tabulates
|
||||
/// the first stage's curves per code; the result must equal the exact per-pixel evaluation.
|
||||
#[test]
|
||||
fn exact_core_u8_matches_exact_eval() {
|
||||
let t = Transform::new(srgb(), lab(), Intent::RelativeColorimetric, true).unwrap();
|
||||
/// 8-bit and 16-bit conversion through an analytic pipeline (no lookup table, here RGB to Lab)
|
||||
/// must equal the exact per-pixel evaluation. The 8-bit path tabulates the first stage's
|
||||
/// curves per code; `curves_are_tables` selects whether those curves are parametric (their
|
||||
/// `eval` is analytic) or `curv` tables (their `eval` interpolates inside the same loop).
|
||||
fn check_exact_core(src_profile: &Profile, curves_are_tables: bool) {
|
||||
let trc = src_profile.trc.as_ref().expect("matrix/TRC source");
|
||||
assert!(trc.iter().all(|c| matches!(c, photocraft_cms::Curve::Table(_)) == curves_are_tables), "TRC kind");
|
||||
let t = Transform::new(src_profile, lab(), Intent::RelativeColorimetric, true).unwrap();
|
||||
let px = 256usize * 256;
|
||||
let src: Vec<u8> = (0..px as u32).flat_map(|i| [(i & 255) as u8, (i >> 8) as u8, (i.wrapping_mul(2654435761) >> 13) as u8, 200]).collect();
|
||||
let mut out = vec![0u8; px * 4];
|
||||
@@ -871,8 +874,38 @@ fn exact_core_u8_matches_exact_eval() {
|
||||
let mut want = [0.0f32; 3];
|
||||
t.eval(&inp, &mut want);
|
||||
for k in 0..3 {
|
||||
assert_eq!(dp[k], (want[k].clamp(0.0, 1.0) * 255.0 + 0.5) as u8, "{sp:?} ch {k}");
|
||||
assert_eq!(dp[k], (want[k].clamp(0.0, 1.0) * 255.0 + 0.5) as u8, "u8 {sp:?} ch {k}");
|
||||
}
|
||||
assert_eq!(dp[3], 200);
|
||||
}
|
||||
// 16-bit input stays off the 8-bit table and must agree with the untabulated evaluation.
|
||||
let src16: Vec<u16> = (0..px * 4).map(|i| if i % 4 == 3 { 51400 } else { (i.wrapping_mul(2654435761) >> 9) as u16 }).collect();
|
||||
let mut out16 = vec![0u16; px * 4];
|
||||
t.convert_u16(&src16, 4, &mut out16, 4, true);
|
||||
for (sp, dp) in src16.as_chunks::<4>().0.iter().zip(out16.as_chunks::<4>().0) {
|
||||
let inp = [sp[0] as f32 / 65535.0, sp[1] as f32 / 65535.0, sp[2] as f32 / 65535.0];
|
||||
let mut want = [0.0f32; 3];
|
||||
t.eval(&inp, &mut want);
|
||||
for k in 0..3 {
|
||||
assert_eq!(dp[k], (want[k].clamp(0.0, 1.0) * 65535.0 + 0.5) as u16, "u16 {sp:?} ch {k}");
|
||||
}
|
||||
assert_eq!(dp[3], 51400);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn exact_core_u8_matches_exact_eval() {
|
||||
check_exact_core(srgb(), false);
|
||||
}
|
||||
|
||||
/// Same, with `curv` table TRCs: the first-stage table is built from `Curve::eval`'s table
|
||||
/// branch and must still be applied to its own channel.
|
||||
#[test]
|
||||
fn exact_core_u8_matches_exact_eval_with_table_trc() {
|
||||
// Distinct tables per channel, so a first stage applied to the wrong channel shows up.
|
||||
let mut p = srgb_as_v2_tables();
|
||||
let table = |g: f64| photocraft_cms::Curve::Table((0..1024).map(|i| ((i as f64 / 1023.0).powf(g) * 65535.0).round() as f32 / 65535.0).collect());
|
||||
p.trc = Some([table(1.8), table(2.2), table(2.6)]);
|
||||
let table_trc = Profile::parse(&p.with_encoded_bytes().to_bytes()).unwrap();
|
||||
check_exact_core(&table_trc, true);
|
||||
}
|
||||
Reference in new issue
Block a user