use drmkit_core::{Device, PropertyBlob, Result as CoreResult};
use crate::color::ColorimetryInfo;
pub const HDR_METADATA_SIZE: usize = 32;
const INFOFRAME: usize = 4;
const STATIC_METADATA_TYPE1: u8 = 0;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum TransferFunction {
#[default]
TraditionalGammaSdr,
TraditionalGammaHdr,
SmpteSt2084Pq,
Bt2100Hlg,
}
impl TransferFunction {
#[must_use]
pub const fn as_u8(self) -> u8 {
match self {
Self::TraditionalGammaSdr => 0,
Self::TraditionalGammaHdr => 1,
Self::SmpteSt2084Pq => 2,
Self::Bt2100Hlg => 3,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default)]
#[allow(
clippy::exhaustive_structs,
reason = "built by callers with `..Default::default()`, which a new field does not break"
)]
pub struct HdrSourceMetadata {
pub eotf: TransferFunction,
pub display_primaries: ColorimetryInfo,
pub max_display_mastering_luminance: u16,
pub min_display_mastering_luminance: u16,
pub max_content_light_level: u16,
pub max_frame_average_light_level: u16,
}
fn cie_to_u16(v: f32) -> u16 {
if v.is_nan() || v < 0.0 {
return 0;
}
let scaled = (v * 50000.0).round();
if scaled >= 65535.0 {
return u16::MAX;
}
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
{
scaled as u16
}
}
#[must_use]
pub fn serialize_hdr_metadata(src: &HdrSourceMetadata) -> [u8; HDR_METADATA_SIZE] {
let mut out = [0u8; HDR_METADATA_SIZE];
out[0..4].copy_from_slice(&u32::from(STATIC_METADATA_TYPE1).to_ne_bytes());
out[INFOFRAME] = src.eotf.as_u8();
out[INFOFRAME + 1] = STATIC_METADATA_TYPE1;
let p = &src.display_primaries;
let mut put = |offset: usize, value: u16| {
out[offset..offset + 2].copy_from_slice(&value.to_ne_bytes());
};
put(INFOFRAME + 2, cie_to_u16(p.green.x));
put(INFOFRAME + 4, cie_to_u16(p.green.y));
put(INFOFRAME + 6, cie_to_u16(p.blue.x));
put(INFOFRAME + 8, cie_to_u16(p.blue.y));
put(INFOFRAME + 10, cie_to_u16(p.red.x));
put(INFOFRAME + 12, cie_to_u16(p.red.y));
put(INFOFRAME + 14, cie_to_u16(p.white.x));
put(INFOFRAME + 16, cie_to_u16(p.white.y));
put(INFOFRAME + 18, src.max_display_mastering_luminance);
put(INFOFRAME + 20, src.min_display_mastering_luminance);
put(INFOFRAME + 22, src.max_content_light_level);
put(INFOFRAME + 24, src.max_frame_average_light_level);
out
}
const fn fnv1a_64(data: &[u8]) -> u64 {
let mut hash: u64 = 0xcbf2_9ce4_8422_2325;
let mut i = 0;
while i < data.len() {
hash ^= data[i] as u64;
hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
i += 1;
}
hash
}
#[must_use]
pub fn hdr_metadata_hash(src: &HdrSourceMetadata) -> u64 {
fnv1a_64(&serialize_hdr_metadata(src))
}
pub trait HdrBlob {
fn blob_id(&self) -> u32;
fn forget(self);
}
#[derive(Debug)]
pub struct HdrMetadataBlob<'a> {
blob: PropertyBlob<'a>,
content_hash: u64,
}
impl<'a> HdrMetadataBlob<'a> {
pub fn create(device: &'a Device, src: &HdrSourceMetadata) -> CoreResult<Self> {
let bytes = serialize_hdr_metadata(src);
Ok(Self {
blob: device.create_property_blob(&bytes[..])?,
content_hash: fnv1a_64(&bytes),
})
}
#[must_use]
pub const fn content_hash(&self) -> u64 {
self.content_hash
}
}
impl HdrBlob for HdrMetadataBlob<'_> {
fn blob_id(&self) -> u32 {
u32::try_from(self.blob.id()).unwrap_or(0)
}
fn forget(self) {
self.blob.forget();
}
}