use crate::TransferFunction;
pub use zenpixels::hdr::{ContentLightLevel, MasteringDisplay};
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct HdrMetadata {
pub transfer: TransferFunction,
pub content_light_level: Option<ContentLightLevel>,
pub mastering_display: Option<MasteringDisplay>,
}
impl HdrMetadata {
#[must_use]
pub fn is_hdr(&self) -> bool {
matches!(self.transfer, TransferFunction::Pq | TransferFunction::Hlg)
}
#[must_use]
pub fn is_sdr(&self) -> bool {
!self.is_hdr()
}
pub fn hdr10(cll: ContentLightLevel) -> Self {
Self {
transfer: TransferFunction::Pq,
content_light_level: Some(cll),
mastering_display: Some(MasteringDisplay::HDR10_REFERENCE),
}
}
pub fn hlg() -> Self {
Self {
transfer: TransferFunction::Hlg,
content_light_level: None,
mastering_display: None,
}
}
}
#[inline]
#[must_use]
pub fn reinhard_tonemap(v: f32) -> f32 {
v / (1.0 + v)
}
#[inline]
#[must_use]
pub fn reinhard_inverse(v: f32) -> f32 {
if v >= 1.0 {
return f32::MAX;
}
v / (1.0 - v)
}
#[cfg(feature = "std")]
#[inline]
#[must_use]
pub fn exposure_tonemap(v: f32, exposure: f32) -> f32 {
(v * 2.0f32.powf(exposure)).clamp(0.0, 1.0)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn reinhard_boundaries() {
assert_eq!(reinhard_tonemap(0.0), 0.0);
assert!((reinhard_tonemap(1.0) - 0.5).abs() < 1e-6);
assert!(reinhard_tonemap(1000.0) > 0.99);
assert!(reinhard_tonemap(1000.0) < 1.0);
}
#[test]
fn reinhard_roundtrip() {
for &v in &[0.0, 0.1, 0.5, 1.0, 2.0, 10.0, 100.0] {
let mapped = reinhard_tonemap(v);
let unmapped = reinhard_inverse(mapped);
assert!(
(unmapped - v).abs() < 1e-4,
"Reinhard roundtrip failed for {v}: got {unmapped}"
);
}
}
#[test]
fn hdr_metadata_is_hdr() {
assert!(HdrMetadata::hdr10(ContentLightLevel::default()).is_hdr());
assert!(HdrMetadata::hlg().is_hdr());
assert!(
HdrMetadata {
transfer: TransferFunction::Srgb,
content_light_level: None,
mastering_display: None,
}
.is_sdr()
);
}
#[test]
fn hdr10_constructor() {
let cll = ContentLightLevel::new(4000, 1000);
let meta = HdrMetadata::hdr10(cll);
assert!(meta.is_hdr());
assert_eq!(meta.transfer, TransferFunction::Pq);
assert_eq!(meta.content_light_level, Some(cll));
assert!(meta.mastering_display.is_some());
}
#[test]
fn hlg_constructor() {
let meta = HdrMetadata::hlg();
assert!(meta.is_hdr());
assert_eq!(meta.transfer, TransferFunction::Hlg);
assert!(meta.content_light_level.is_none());
assert!(meta.mastering_display.is_none());
}
#[test]
#[cfg(feature = "std")]
fn exposure_tonemap_values() {
assert!((exposure_tonemap(0.5, 0.0) - 0.5).abs() < 1e-6);
assert!((exposure_tonemap(0.25, 1.0) - 0.5).abs() < 1e-5);
assert!((exposure_tonemap(0.5, -1.0) - 0.25).abs() < 1e-5);
assert_eq!(exposure_tonemap(0.8, 1.0), 1.0);
assert_eq!(exposure_tonemap(0.0, 5.0), 0.0);
}
#[test]
fn reinhard_inverse_at_one() {
assert_eq!(reinhard_inverse(1.0), f32::MAX);
}
#[test]
fn hdr_metadata_clone_partial_eq() {
let a = HdrMetadata::hlg();
let b = a;
assert_eq!(a, b);
}
}