use super::{BitReader, BitWriter, MAX_DYNAMIC_HDR_PAYLOAD};
use crate::error::DecodeError;
use crate::warn::DynamicHdrWarning;
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Hdr10PlusMetadata {
pub application_identifier: u8,
pub application_mode: u8,
pub scene_frame_switching_flag: bool,
pub targeted_system_display_maximum_luminance: u32,
pub targeted_system_display_actual_peak_luminance_flag: bool,
pub targeted_system_display_actual_peak_luminance: Option<ActualPeakLuminance>,
pub windows: Hdr10PlusWindows,
pub mastering_display_actual_peak_luminance_flag: bool,
pub mastering_display_actual_peak_luminance: Option<ActualPeakLuminance>,
pub color_saturation_mapping_flag: bool,
pub color_saturation_weight: Option<u8>,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Hdr10PlusWindows {
pub count: u8,
pub windows: [Hdr10PlusWindow; 3],
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct Hdr10PlusWindow {
pub upper_left_corner_x: u16,
pub upper_left_corner_y: u16,
pub lower_right_corner_x: u16,
pub lower_right_corner_y: u16,
pub center_of_ellipse_x: u16,
pub center_of_ellipse_y: u16,
pub rotation_angle: u8,
pub semimajor_axis_internal_ellipse: u16,
pub semimajor_axis_external_ellipse: u16,
pub semiminor_axis_external_ellipse: u16,
pub overlap_process_option: bool,
pub maxscl: [u32; 3],
pub average_maxrgb: u32,
pub distribution_maxrgb: DistributionMaxrgb,
pub fraction_bright_pixels: u16,
pub tone_mapping_flag: bool,
pub knee_point: Option<KneePoint>,
pub bezier_curve_anchors: BezierAnchors,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct DistributionMaxrgb {
pub count: u8,
pub percentages: [u8; 15],
pub percentiles: [u32; 15],
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct KneePoint {
pub x: u16,
pub y: u16,
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct BezierAnchors {
pub count: u8,
pub anchors: [u16; 9],
}
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, Clone, PartialEq, Eq, Default)]
pub struct ActualPeakLuminance {
pub num_rows: u8,
pub num_cols: u8,
pub entries: [[u8; 25]; 25],
}
impl Hdr10PlusMetadata {
pub fn decode(
payload: &[u8],
push_warning: &mut impl FnMut(DynamicHdrWarning),
) -> Result<Self, DecodeError> {
let mut r = BitReader::new(payload);
let application_identifier = r.read_u8(8)?;
let application_mode = r.read_u8(8)?;
if application_mode > 1 {
push_warning(DynamicHdrWarning::UnknownEnumValue {
field: "application_mode",
raw: application_mode,
});
}
let scene_frame_switching_flag = if application_mode == 1 {
r.read_bool()?
} else {
false
};
for _ in 0..2 {
let byte_idx = r.byte_pos as u8;
let bit_idx = 7 - r.bit_pos; if r.read_bool()? {
push_warning(DynamicHdrWarning::ReservedFieldNonZero {
byte: byte_idx,
bit: bit_idx,
});
}
}
let targeted_system_display_maximum_luminance = r.read_u32(27)?;
let targeted_system_display_actual_peak_luminance_flag = r.read_bool()?;
let targeted_system_display_actual_peak_luminance =
if targeted_system_display_actual_peak_luminance_flag {
Some(Self::decode_actual_peak_luminance(&mut r)?)
} else {
None
};
let num_windows_minus1 = r.read_u8(2)?;
if num_windows_minus1 > 2 {
return Err(DecodeError::MalformedPayload);
}
let num_windows = num_windows_minus1 + 1;
let mut windows = Hdr10PlusWindows {
count: num_windows,
..Default::default()
};
for i in 0..num_windows as usize {
windows.windows[i] = Self::decode_window(&mut r)?;
}
let mastering_display_actual_peak_luminance_flag = r.read_bool()?;
let mastering_display_actual_peak_luminance =
if mastering_display_actual_peak_luminance_flag {
Some(Self::decode_actual_peak_luminance(&mut r)?)
} else {
None
};
for i in 0..num_windows as usize {
let tone_mapping_flag = r.read_bool()?;
windows.windows[i].tone_mapping_flag = tone_mapping_flag;
if tone_mapping_flag {
let x = r.read_u16(12)?;
let y = r.read_u16(12)?;
windows.windows[i].knee_point = Some(KneePoint { x, y });
let num_anchors = r.read_u8(4)?;
if num_anchors > 9 {
return Err(DecodeError::MalformedPayload);
}
windows.windows[i].bezier_curve_anchors.count = num_anchors;
for j in 0..num_anchors as usize {
windows.windows[i].bezier_curve_anchors.anchors[j] = r.read_u16(10)?;
}
}
}
let color_saturation_mapping_flag = r.read_bool()?;
let color_saturation_weight = if color_saturation_mapping_flag {
Some(r.read_u8(6)?)
} else {
None
};
Ok(Hdr10PlusMetadata {
application_identifier,
application_mode,
scene_frame_switching_flag,
targeted_system_display_maximum_luminance,
targeted_system_display_actual_peak_luminance_flag,
targeted_system_display_actual_peak_luminance,
windows,
mastering_display_actual_peak_luminance_flag,
mastering_display_actual_peak_luminance,
color_saturation_mapping_flag,
color_saturation_weight,
})
}
fn decode_actual_peak_luminance(
r: &mut BitReader<'_>,
) -> Result<ActualPeakLuminance, DecodeError> {
let num_rows = r.read_u8(5)?;
let num_cols = r.read_u8(5)?;
let mut entries = [[0u8; 25]; 25];
for row in entries.iter_mut().take(num_rows as usize) {
for entry in row.iter_mut().take(num_cols as usize) {
*entry = r.read_u8(4)?;
}
}
Ok(ActualPeakLuminance {
num_rows,
num_cols,
entries,
})
}
pub(crate) fn encode(&self) -> ([u8; MAX_DYNAMIC_HDR_PAYLOAD], usize) {
let mut w = BitWriter::new();
w.write_u8(self.application_identifier, 8);
w.write_u8(self.application_mode, 8);
if self.application_mode == 1 {
w.write_bool(self.scene_frame_switching_flag);
}
w.write_u8(0, 2);
w.write_u32(self.targeted_system_display_maximum_luminance, 27);
w.write_bool(self.targeted_system_display_actual_peak_luminance_flag);
if let Some(ref lum) = self.targeted_system_display_actual_peak_luminance {
Self::encode_actual_peak_luminance(&mut w, lum);
}
debug_assert!(
(1..=3).contains(&self.windows.count),
"windows.count must be 1–3"
);
w.write_u8(self.windows.count - 1, 2); for win in self.windows.windows[..self.windows.count as usize].iter() {
Self::encode_window(&mut w, win);
}
w.write_bool(self.mastering_display_actual_peak_luminance_flag);
if let Some(ref lum) = self.mastering_display_actual_peak_luminance {
Self::encode_actual_peak_luminance(&mut w, lum);
}
for win in self.windows.windows[..self.windows.count as usize].iter() {
w.write_bool(win.tone_mapping_flag);
if win.tone_mapping_flag {
if let Some(ref kp) = win.knee_point {
w.write_u16(kp.x, 12);
w.write_u16(kp.y, 12);
}
w.write_u8(win.bezier_curve_anchors.count, 4);
for &anchor in win.bezier_curve_anchors.anchors
[..win.bezier_curve_anchors.count as usize]
.iter()
{
w.write_u16(anchor, 10);
}
}
}
w.write_bool(self.color_saturation_mapping_flag);
if let Some(weight) = self.color_saturation_weight {
w.write_u8(weight, 6);
}
w.finish()
}
fn encode_actual_peak_luminance(w: &mut BitWriter, lum: &ActualPeakLuminance) {
w.write_u8(lum.num_rows, 5);
w.write_u8(lum.num_cols, 5);
for row in lum.entries.iter().take(lum.num_rows as usize) {
for &entry in row.iter().take(lum.num_cols as usize) {
w.write_u8(entry, 4);
}
}
}
fn encode_window(w: &mut BitWriter, win: &Hdr10PlusWindow) {
w.write_u16(win.upper_left_corner_x, 16);
w.write_u16(win.upper_left_corner_y, 16);
w.write_u16(win.lower_right_corner_x, 16);
w.write_u16(win.lower_right_corner_y, 16);
w.write_u16(win.center_of_ellipse_x, 16);
w.write_u16(win.center_of_ellipse_y, 16);
w.write_u8(win.rotation_angle, 8);
w.write_u16(win.semimajor_axis_internal_ellipse, 16);
w.write_u16(win.semimajor_axis_external_ellipse, 16);
w.write_u16(win.semiminor_axis_external_ellipse, 16);
w.write_bool(win.overlap_process_option);
for &v in win.maxscl.iter() {
w.write_u32(v, 17);
}
w.write_u32(win.average_maxrgb, 17);
w.write_u8(win.distribution_maxrgb.count, 4);
for i in 0..win.distribution_maxrgb.count as usize {
w.write_u8(win.distribution_maxrgb.percentages[i], 7);
w.write_u32(win.distribution_maxrgb.percentiles[i], 17);
}
w.write_u16(win.fraction_bright_pixels, 10);
}
fn decode_window(r: &mut BitReader<'_>) -> Result<Hdr10PlusWindow, DecodeError> {
let upper_left_corner_x = r.read_u16(16)?;
let upper_left_corner_y = r.read_u16(16)?;
let lower_right_corner_x = r.read_u16(16)?;
let lower_right_corner_y = r.read_u16(16)?;
let center_of_ellipse_x = r.read_u16(16)?;
let center_of_ellipse_y = r.read_u16(16)?;
let rotation_angle = r.read_u8(8)?;
let semimajor_axis_internal_ellipse = r.read_u16(16)?;
let semimajor_axis_external_ellipse = r.read_u16(16)?;
let semiminor_axis_external_ellipse = r.read_u16(16)?;
let overlap_process_option = r.read_bool()?;
let maxscl = [r.read_u32(17)?, r.read_u32(17)?, r.read_u32(17)?];
let average_maxrgb = r.read_u32(17)?;
let num_percentiles = r.read_u8(4)?;
let mut distribution_maxrgb = DistributionMaxrgb {
count: num_percentiles,
..Default::default()
};
for i in 0..num_percentiles as usize {
distribution_maxrgb.percentages[i] = r.read_u8(7)?;
distribution_maxrgb.percentiles[i] = r.read_u32(17)?;
}
let fraction_bright_pixels = r.read_u16(10)?;
Ok(Hdr10PlusWindow {
upper_left_corner_x,
upper_left_corner_y,
lower_right_corner_x,
lower_right_corner_y,
center_of_ellipse_x,
center_of_ellipse_y,
rotation_angle,
semimajor_axis_internal_ellipse,
semimajor_axis_external_ellipse,
semiminor_axis_external_ellipse,
overlap_process_option,
maxscl,
average_maxrgb,
distribution_maxrgb,
fraction_bright_pixels,
tone_mapping_flag: false,
knee_point: None,
bezier_curve_anchors: BezierAnchors::default(),
})
}
}