use icc_profile::cms::transration::{
cmyk_to_lab_lut8, cmyk_to_lab_lut16, cmyk_to_rgb, cmyk_to_rgb_from_profile, lab_to_xyz_wp, xyz_to_rgb,
WhitePoint,
};
use icc_profile::iccprofile::{Data, DecodedICCProfile, ICCNumber};
use icc_profile::utils::{delta_e76, ciede2000};
fn sample(name: &str) -> String {
std::path::Path::new(env!("CARGO_MANIFEST_DIR"))
.join("_test_samples")
.join(name)
.to_str()
.unwrap()
.to_string()
}
fn load_decoded(name: &str) -> DecodedICCProfile {
let icc = icc_profile::utils::load(sample(name)).expect("load failed");
DecodedICCProfile::new(&icc.data).expect("decode failed")
}
#[test]
fn cmyk_all_zero_with_full_k() {
let (r, g, b) = cmyk_to_rgb(0, 0, 0, 255);
assert_eq!((r, g, b), (0, 0, 0));
}
#[test]
fn cmyk_no_overflow_values() {
let (r, g, b) = cmyk_to_rgb(0, 0, 0, 255);
assert_eq!((r, g, b), (0, 0, 0));
}
#[test]
fn japan_color_white_to_rgb() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let (r, g, b) = cmyk_to_rgb_from_profile(0, 0, 0, 0, &decoded);
assert!(r > 150, "white paper R should be bright, got {}", r);
assert!(g > 150, "white paper G should be bright, got {}", g);
assert!(b > 150, "white paper B should be bright, got {}", b);
}
#[test]
fn japan_color_heavy_ink_darkens_rgb() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let (r_heavy, g_heavy, b_heavy) = cmyk_to_rgb_from_profile(200, 200, 200, 200, &decoded);
let (r_white, g_white, b_white) = cmyk_to_rgb_from_profile(0, 0, 0, 0, &decoded);
let heavy = r_heavy as u32 + g_heavy as u32 + b_heavy as u32;
let white = r_white as u32 + g_white as u32 + b_white as u32;
assert!(heavy < white, "heavy ink ({}) should be darker than white ({})", heavy, white);
}
#[test]
fn japan_color_yellow_ink_has_low_blue() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let (r, g, b) = cmyk_to_rgb_from_profile(0, 0, 200, 0, &decoded);
assert!(b < r.max(g), "yellow ink: B({}) should be less than R({}) or G({})", b, r, g);
}
#[test]
fn japan_color_a2b0_type_check() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
let is_lut = matches!(
tag,
Data::Lut8(_) | Data::Lut16(_) | Data::LutAtoB(_)
);
assert!(is_lut, "A2B0 should be a LUT type");
}
#[test]
fn japan_color_cmyk_to_lab_via_lut8_white() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut8(lut8) = tag {
let (l, _a, _b) = cmyk_to_lab_lut8(0, 0, 0, 0, lut8);
assert!(l > 70.0, "white paper L* should be >70, got {}", l);
}
}
#[test]
fn japan_color_cmyk_to_lab_via_lut8_heavy_ink() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut8(lut8) = tag {
let (l_white, _, _) = cmyk_to_lab_lut8(0, 0, 0, 0, lut8);
let (l_dark, _, _) = cmyk_to_lab_lut8(200, 200, 200, 200, lut8);
assert!(l_dark < l_white, "heavy ink L*({}) should be darker than white L*({})", l_dark, l_white);
}
}
#[test]
fn ycck_a2b0_type_detection() {
let decoded = load_decoded("ycck.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
let is_lut16 = matches!(tag, Data::Lut16(_));
let is_lut8 = matches!(tag, Data::Lut8(_));
println!("ycck.icc A2B0 type: Lut16={}, Lut8={}", is_lut16, is_lut8);
assert!(is_lut16 || is_lut8, "A2B0 should be Lut16 or Lut8");
}
#[test]
fn ycck_cmyk_to_lab_via_lut16_white() {
let decoded = load_decoded("ycck.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut16(lut16) = tag {
let (l, a, b) = cmyk_to_lab_lut16(0, 0, 0, 0, lut16);
println!("ycck.icc CMYK(0,0,0,0) → Lab({:.2}, {:.2}, {:.2})", l, a, b);
assert!(l > 60.0, "white paper L* should be >60, got {}", l);
}
}
#[test]
fn ycck_cmyk_to_lab_via_lut16_dark_ink() {
let decoded = load_decoded("ycck.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut16(lut16) = tag {
let (l_white, _, _) = cmyk_to_lab_lut16(0, 0, 0, 0, lut16);
let (l_dark, _, _) = cmyk_to_lab_lut16(200, 200, 200, 200, lut16);
println!("ycck.icc: white L*={:.2}, dark L*={:.2}", l_white, l_dark);
assert!(l_dark < l_white, "heavy ink L* should be darker than white L*");
}
}
#[test]
fn japan_color_cmyk_pipeline_white() {
use icc_profile::cms::transration::cmyk_to_lab_lut8;
let decoded = load_decoded("JapanColor2011Coated.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut8(lut8) = tag {
let wp = WhitePoint::d65();
let (l, a, b) = cmyk_to_lab_lut8(0, 0, 0, 0, lut8);
let (x, y, z) = lab_to_xyz_wp(l, a, b, &wp);
let (r, g, b) = xyz_to_rgb(x, y, z);
assert!(r > 150, "pipeline white R should be bright, got {}", r);
assert!(g > 150, "pipeline white G should be bright, got {}", g);
assert!(b > 150, "pipeline white B should be bright, got {}", b);
}
}
#[test]
fn ycck_profile_has_a2b0() {
let decoded = load_decoded("ycck.icc");
assert!(decoded.tags.contains_key("A2B0"), "ycck must have A2B0");
}
#[test]
fn ycck_white_to_rgb() {
let decoded = load_decoded("ycck.icc");
let (r, g, b) = cmyk_to_rgb_from_profile(0, 0, 0, 0, &decoded);
assert!(r > 150 || g > 150 || b > 150,
"ycck white should be bright: R={} G={} B={}", r, g, b);
}
#[test]
fn srgb_v4_a2b0_is_lut_type() {
use icc_profile::iccprofile::Data;
let decoded = load_decoded("sRGB_v4_ICC_preference.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
let is_lut = matches!(tag, Data::LutAtoB(_));
assert!(is_lut, "sRGB v4 A2B0 should be LutAtoB type");
}
#[test]
fn srgb_v4_b2a0_is_lut_type() {
use icc_profile::iccprofile::Data;
let decoded = load_decoded("sRGB_v4_ICC_preference.icc");
let tag = decoded.tags.get("B2A0").expect("B2A0 must exist");
let is_lut = matches!(tag, Data::LutBtoA(_));
assert!(is_lut, "sRGB v4 B2A0 should be LutBtoA type");
}
#[test]
fn asus_rxyz_tag_exists_and_reasonable() {
let decoded = load_decoded("asus_rog_strix_xg309cm.icm");
let tag = decoded.tags.get("rXYZ").expect("rXYZ must exist");
match tag {
Data::XYZNumberArray(arr) => {
let xyz = arr.first().expect("rXYZ array must not be empty");
let y = xyz.y.as_f64();
assert!(y > 0.05 && y < 0.6, "rXYZ Y should be reasonable, got {}", y);
}
Data::XYZNumber(xyz) => {
let y = xyz.y.as_f64();
assert!(y > 0.05 && y < 0.6, "rXYZ Y should be reasonable, got {}", y);
}
_ => panic!("rXYZ should be XYZNumber or XYZNumberArray, got {:?}", tag),
}
}
#[test]
fn asus_gxyz_tag_green_dominant() {
let decoded = load_decoded("asus_rog_strix_xg309cm.icm");
let tag = decoded.tags.get("gXYZ").expect("gXYZ must exist");
let (x, y) = match tag {
Data::XYZNumberArray(arr) => {
let xyz = arr.first().expect("gXYZ array must not be empty");
(xyz.x.as_f64(), xyz.y.as_f64())
}
Data::XYZNumber(xyz) => (xyz.x.as_f64(), xyz.y.as_f64()),
_ => panic!("gXYZ unexpected type"),
};
assert!(y > x, "green primary Y({}) should exceed X({})", y, x);
}
#[test]
fn asus_bxyz_tag_blue_high_z() {
let decoded = load_decoded("asus_rog_strix_xg309cm.icm");
let tag = decoded.tags.get("bXYZ").expect("bXYZ must exist");
let (y, z) = match tag {
Data::XYZNumberArray(arr) => {
let xyz = arr.first().expect("bXYZ array must not be empty");
(xyz.y.as_f64(), xyz.z.as_f64())
}
Data::XYZNumber(xyz) => (xyz.y.as_f64(), xyz.z.as_f64()),
_ => panic!("bXYZ unexpected type"),
};
assert!(z > y, "blue primary Z({}) should exceed Y({})", z, y);
}
#[test]
fn japan_color_cmyk_to_lab_range_validation() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut8(lut8) = tag {
let (l, a, b) = cmyk_to_lab_lut8(100, 50, 30, 10, lut8);
assert!(l >= 0.0 && l <= 100.0, "L* {} out of range [0,100]", l);
assert!(a >= -127.0 && a <= 127.0, "a* {} out of range [-127,127]", a);
assert!(b >= -127.0 && b <= 127.0, "b* {} out of range [-127,127]", b);
println!("JapanColor CMYK(100,50,30,10) → Lab({:.2}, {:.2}, {:.2})", l, a, b);
}
}
#[test]
fn ycck_cmyk_to_lab_range_validation() {
let decoded = load_decoded("ycck.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut16(lut16) = tag {
let (l, a, b) = cmyk_to_lab_lut16(150, 100, 75, 20, lut16);
assert!(l >= 0.0 && l <= 100.0, "L* {} out of range [0,100]", l);
assert!(a >= -127.0 && a <= 127.0, "a* {} out of range [-127,127]", a);
assert!(b >= -127.0 && b <= 127.0, "b* {} out of range [-127,127]", b);
println!("ycck CMYK(150,100,75,20) → Lab({:.2}, {:.2}, {:.2})", l, a, b);
}
}
#[test]
fn japan_color_white_delta_e_check() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut8(lut8) = tag {
let lab1 = cmyk_to_lab_lut8(0, 0, 0, 0, lut8);
let lab2 = cmyk_to_lab_lut8(0, 0, 0, 0, lut8);
let de = delta_e76(&lab1, &lab2);
assert!(de < 0.001, "Identical CMYK should have ΔE≈0, got {}", de);
}
}
#[test]
fn japan_vs_ycck_cmyk_neutral_de76_comparison() {
let jp = load_decoded("JapanColor2011Coated.icc");
let yk = load_decoded("ycck.icc");
let jp_tag = jp.tags.get("A2B0").expect("JP A2B0");
let yk_tag = yk.tags.get("A2B0").expect("YK A2B0");
if let (Data::Lut8(jp_lut), Data::Lut16(yk_lut)) = (jp_tag, yk_tag) {
let lab_jp = cmyk_to_lab_lut8(50, 50, 50, 0, jp_lut);
let lab_yk = cmyk_to_lab_lut16(50, 50, 50, 0, yk_lut);
let de76 = delta_e76(&lab_jp, &lab_yk);
let de2000 = ciede2000(&lab_jp, &lab_yk);
println!(
"JP CMYK(50,50,50,0) → Lab({:.2}, {:.2}, {:.2}) | YK → Lab({:.2}, {:.2}, {:.2}) | ΔE76={:.2} ΔE2000={:.2}",
lab_jp.0, lab_jp.1, lab_jp.2, lab_yk.0, lab_yk.1, lab_yk.2, de76, de2000
);
assert!(de76 < 15.0, "Different profiles should still be relatively close, got ΔE76={}", de76);
}
}
#[test]
fn cmyk_pipeline_white_to_rgb_brightness() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut8(lut8) = tag {
let (l, a, b) = cmyk_to_lab_lut8(0, 0, 0, 0, lut8);
let wp = WhitePoint::d65();
let (x, y, z) = lab_to_xyz_wp(l, a, b, &wp);
let (r, g, b) = xyz_to_rgb(x, y, z);
let brightness = (r as u32 + g as u32 + b as u32) / 3;
assert!(brightness > 200, "white paper brightness {} should be >200", brightness);
println!("White paper RGB({}, {}, {}) brightness={}", r, g, b, brightness);
}
}
#[test]
fn cmyk_pipeline_dark_to_rgb_darkness() {
let decoded = load_decoded("JapanColor2011Coated.icc");
let tag = decoded.tags.get("A2B0").expect("A2B0 must exist");
if let Data::Lut8(lut8) = tag {
let (l, a, b) = cmyk_to_lab_lut8(200, 200, 200, 200, lut8);
let wp = WhitePoint::d65();
let (x, y, z) = lab_to_xyz_wp(l, a, b, &wp);
let (r, g, b) = xyz_to_rgb(x, y, z);
let brightness = (r as u32 + g as u32 + b as u32) / 3;
assert!(brightness < 100, "heavy ink brightness {} should be <100", brightness);
println!("Heavy ink RGB({}, {}, {}) brightness={}", r, g, b, brightness);
}
}