use crate::{ColorPrimaries, TransferFunction};
pub const DISPLAY_P3_V4: &[u8] = include_bytes!("profiles/DisplayP3Compat-v4.icc");
pub const DISPLAY_P3_V2: &[u8] = include_bytes!("profiles/DisplayP3Compat-v2-magic.icc");
pub const ADOBE_RGB: &[u8] = include_bytes!("profiles/AdobeCompat-v2.icc");
#[deprecated(
since = "0.2.4",
note = "renamed to ADOBE_RGB (now v2 pure-gamma form)"
)]
pub const ADOBE_RGB_V4: &[u8] = ADOBE_RGB;
#[deprecated(
since = "0.2.4",
note = "ProPhoto removed — TRC too fragmented to pick a canonical form"
)]
pub const PROPHOTO_V4: &[u8] = &[];
pub const REC2020_V4: &[u8] = include_bytes!("profiles/Rec2020Compat-v4.icc");
#[inline]
pub const fn icc_profile_for_primaries(primaries: ColorPrimaries) -> Option<&'static [u8]> {
match primaries {
ColorPrimaries::DisplayP3 => Some(DISPLAY_P3_V4),
ColorPrimaries::Bt2020 => Some(REC2020_V4),
ColorPrimaries::AdobeRgb => Some(ADOBE_RGB),
ColorPrimaries::Bt709 | ColorPrimaries::Unknown | _ => None,
}
}
#[inline]
pub const fn display_p3_icc(prefer_v2: bool) -> &'static [u8] {
if prefer_v2 {
DISPLAY_P3_V2
} else {
DISPLAY_P3_V4
}
}
#[inline]
#[allow(dead_code)] pub(crate) const fn icc_profile_for(
primaries: ColorPrimaries,
transfer: TransferFunction,
) -> Option<&'static [u8]> {
match (primaries, transfer) {
(ColorPrimaries::DisplayP3, TransferFunction::Srgb)
| (ColorPrimaries::DisplayP3, TransferFunction::Bt709) => Some(DISPLAY_P3_V4),
(ColorPrimaries::Bt2020, TransferFunction::Bt709)
| (ColorPrimaries::Bt2020, TransferFunction::Srgb) => Some(REC2020_V4),
(ColorPrimaries::AdobeRgb, TransferFunction::Gamma22) => Some(ADOBE_RGB),
_ => None,
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn v4_profiles_valid_structure() {
let profiles: &[(&[u8], &str)] = &[
(DISPLAY_P3_V4, "Display P3 v4"),
(REC2020_V4, "Rec. 2020 v4"),
];
for (profile, name) in profiles {
assert_eq!(profile.len(), 480, "{name}: expected 480 bytes");
assert_eq!(
&profile[36..40],
b"acsp",
"{name}: missing ICC 'acsp' signature at offset 36"
);
assert_eq!(
&profile[12..16],
b"mntr",
"{name}: expected 'mntr' (monitor) profile class at offset 12"
);
}
}
#[test]
fn adobe_rgb_profile_valid_structure() {
assert_eq!(
ADOBE_RGB.len(),
374,
"Adobe RGB: expected 374 bytes (ICC v2)"
);
assert_eq!(
&ADOBE_RGB[36..40],
b"acsp",
"Adobe RGB: missing ICC 'acsp' signature at offset 36"
);
assert_eq!(
&ADOBE_RGB[12..16],
b"mntr",
"Adobe RGB: expected 'mntr' (monitor) profile class at offset 12"
);
let tag_count = u32::from_be_bytes([
ADOBE_RGB[128],
ADOBE_RGB[129],
ADOBE_RGB[130],
ADOBE_RGB[131],
]) as usize;
let mut found_pure_gamma_trc = false;
for i in 0..tag_count {
let b = 132 + i * 12;
if &ADOBE_RGB[b..b + 4] == b"rTRC" {
let off = u32::from_be_bytes([
ADOBE_RGB[b + 4],
ADOBE_RGB[b + 5],
ADOBE_RGB[b + 6],
ADOBE_RGB[b + 7],
]) as usize;
assert_eq!(
&ADOBE_RGB[off..off + 4],
b"curv",
"Adobe RGB: rTRC must be curveType (pure gamma)"
);
let count = u32::from_be_bytes([
ADOBE_RGB[off + 8],
ADOBE_RGB[off + 9],
ADOBE_RGB[off + 10],
ADOBE_RGB[off + 11],
]);
assert_eq!(
count, 1,
"Adobe RGB: curveType count must be 1 (pure gamma, no toe)"
);
found_pure_gamma_trc = true;
break;
}
}
assert!(found_pure_gamma_trc, "Adobe RGB: rTRC tag not found");
}
#[test]
fn v2_profile_valid_structure() {
assert_eq!(
DISPLAY_P3_V2.len(),
736,
"Display P3 v2: expected 736 bytes"
);
assert_eq!(
&DISPLAY_P3_V2[36..40],
b"acsp",
"Display P3 v2: missing ICC 'acsp' signature at offset 36"
);
assert_eq!(
&DISPLAY_P3_V2[12..16],
b"mntr",
"Display P3 v2: expected 'mntr' (monitor) profile class at offset 12"
);
}
#[test]
fn display_p3_icc_selector() {
assert_eq!(display_p3_icc(false).len(), 480); assert_eq!(display_p3_icc(true).len(), 736); }
#[test]
fn icc_profile_for_primaries_mapping() {
assert_eq!(
icc_profile_for_primaries(ColorPrimaries::DisplayP3),
Some(DISPLAY_P3_V4)
);
assert_eq!(
icc_profile_for_primaries(ColorPrimaries::Bt2020),
Some(REC2020_V4)
);
assert_eq!(
icc_profile_for_primaries(ColorPrimaries::AdobeRgb),
Some(ADOBE_RGB)
);
assert!(icc_profile_for_primaries(ColorPrimaries::Bt709).is_none());
assert!(icc_profile_for_primaries(ColorPrimaries::Unknown).is_none());
}
#[test]
fn icc_profile_for_hits_bundled_combinations() {
assert_eq!(
icc_profile_for(ColorPrimaries::DisplayP3, TransferFunction::Srgb),
Some(DISPLAY_P3_V4)
);
assert_eq!(
icc_profile_for(ColorPrimaries::DisplayP3, TransferFunction::Bt709),
Some(DISPLAY_P3_V4)
);
assert_eq!(
icc_profile_for(ColorPrimaries::Bt2020, TransferFunction::Bt709),
Some(REC2020_V4)
);
assert_eq!(
icc_profile_for(ColorPrimaries::Bt2020, TransferFunction::Srgb),
Some(REC2020_V4)
);
assert_eq!(
icc_profile_for(ColorPrimaries::AdobeRgb, TransferFunction::Gamma22),
Some(ADOBE_RGB)
);
}
#[test]
fn icc_profile_for_rejects_hdr_transfers() {
assert!(icc_profile_for(ColorPrimaries::Bt2020, TransferFunction::Pq).is_none());
assert!(icc_profile_for(ColorPrimaries::Bt2020, TransferFunction::Hlg).is_none());
assert!(icc_profile_for(ColorPrimaries::DisplayP3, TransferFunction::Pq).is_none());
assert!(icc_profile_for(ColorPrimaries::DisplayP3, TransferFunction::Hlg).is_none());
assert!(icc_profile_for(ColorPrimaries::Bt2020, TransferFunction::Linear).is_none());
assert!(icc_profile_for(ColorPrimaries::DisplayP3, TransferFunction::Linear).is_none());
}
#[test]
fn icc_profile_for_rejects_mismatched_trc_on_bundled_primaries() {
assert!(icc_profile_for(ColorPrimaries::AdobeRgb, TransferFunction::Srgb).is_none());
assert!(icc_profile_for(ColorPrimaries::AdobeRgb, TransferFunction::Bt709).is_none());
assert!(icc_profile_for(ColorPrimaries::DisplayP3, TransferFunction::Gamma22).is_none());
assert!(icc_profile_for(ColorPrimaries::Bt2020, TransferFunction::Gamma22).is_none());
}
#[test]
fn icc_profile_for_bt709_returns_none() {
assert!(icc_profile_for(ColorPrimaries::Bt709, TransferFunction::Srgb).is_none());
assert!(icc_profile_for(ColorPrimaries::Bt709, TransferFunction::Bt709).is_none());
assert!(icc_profile_for(ColorPrimaries::Unknown, TransferFunction::Unknown).is_none());
}
}