use crate::{Error, Result, reconstruct::reconstruct_hdr_image};
use ultrahdr_core::{
ColorGamut, ColorTransfer, GainMap, GainMapMetadata, RawImage, gainmap::HdrOutputFormat,
};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum ChromaSubsampling {
#[default]
Yuv420,
Yuv422,
Yuv444,
Yuv440,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum CompressionEffort {
#[default]
Balanced,
Smallest,
}
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Chromaticity {
pub x: f32,
pub y: f32,
}
#[derive(Debug, Clone, PartialEq)]
pub struct GamutInfo {
pub standard: Option<ColorGamut>,
pub red: Chromaticity,
pub green: Chromaticity,
pub blue: Chromaticity,
pub white: Chromaticity,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct ColorMetadata {
pub icc_profile: Option<Vec<u8>>,
pub gamut: Option<ColorGamut>,
pub gamut_info: Option<GamutInfo>,
pub transfer: Option<ColorTransfer>,
}
#[derive(Debug, Clone, Default, PartialEq)]
pub struct PrimaryMetadata {
pub color: ColorMetadata,
pub exif: Option<Vec<u8>>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum MetadataLocation {
Primary,
GainMap,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GainMapMetadataSource {
Iso21496_1,
Xmp,
}
#[derive(Debug, Clone, Default)]
pub struct UltraHdrMetadata {
pub xmp: Option<String>,
pub xmp_location: Option<MetadataLocation>,
pub iso_21496_1: Option<Vec<u8>>,
pub iso_21496_1_location: Option<MetadataLocation>,
pub gain_map_metadata: Option<GainMapMetadata>,
pub gain_map_metadata_source: Option<GainMapMetadataSource>,
}
#[derive(Debug, Clone)]
pub struct DecodedGainMap {
pub image: RawImage,
pub gain_map: GainMap,
pub metadata: Option<GainMapMetadata>,
pub jpeg_bytes: Option<Vec<u8>>,
}
#[derive(Debug, Clone)]
pub struct DecodedImage {
pub image: RawImage,
pub primary_jpeg: Option<Vec<u8>>,
pub primary_metadata: PrimaryMetadata,
pub ultra_hdr: Option<UltraHdrMetadata>,
pub gain_map: Option<DecodedGainMap>,
}
#[derive(Debug, Clone)]
pub struct Inspection {
pub primary_jpeg_len: usize,
pub gain_map_jpeg_len: Option<usize>,
pub primary_metadata: PrimaryMetadata,
pub ultra_hdr: Option<UltraHdrMetadata>,
}
#[derive(Debug, Clone)]
pub struct ParsedGainMapXmp {
pub metadata: GainMapMetadata,
pub gain_map_jpeg_len: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ContainerKind {
Jpeg,
Mpf,
ConcatenatedJpegs,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct CodestreamLayout {
pub offset: usize,
pub len: usize,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ContainerLayout {
pub kind: ContainerKind,
pub codestreams: Vec<CodestreamLayout>,
pub primary_index: usize,
pub gain_map_index: Option<usize>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DecodeOptions {
pub decode_gain_map: bool,
pub retain_primary_jpeg: bool,
pub retain_gain_map_jpeg: bool,
}
impl Default for DecodeOptions {
fn default() -> Self {
Self {
decode_gain_map: true,
retain_primary_jpeg: false,
retain_gain_map_jpeg: false,
}
}
}
#[derive(Debug, Clone)]
pub struct GainMapBundle {
pub image: RawImage,
pub metadata: GainMapMetadata,
pub quality: u8,
pub progressive: bool,
pub compression: CompressionEffort,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct UltraHdrMetadataEmission {
pub emit_primary_container_xmp: bool,
pub emit_gain_map_xmp: bool,
pub emit_iso_21496_1: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GainMapChannels {
#[default]
Single,
Multi,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub enum GainMapScale {
Full,
#[default]
Default,
Smallest,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ComputeGainMapOptions {
pub channels: GainMapChannels,
pub scale: GainMapScale,
}
#[derive(Debug, Clone)]
pub struct ComputedGainMap {
pub image: RawImage,
pub metadata: GainMapMetadata,
}
#[derive(Debug, Clone, PartialEq)]
pub struct PreparePrimaryOptions {
pub target_gamut: ColorGamut,
pub source_peak_nits: Option<f32>,
pub target_peak_nits: f32,
}
#[derive(Debug, Clone)]
pub struct PreparedPrimary {
pub image: RawImage,
pub metadata: PrimaryMetadata,
}
#[derive(Debug, Clone)]
pub struct EncodeOptions {
pub quality: u8,
pub progressive: bool,
pub compression: CompressionEffort,
pub chroma_subsampling: ChromaSubsampling,
pub primary_metadata: PrimaryMetadata,
pub ultra_hdr_metadata_emission: UltraHdrMetadataEmission,
pub gain_map: Option<GainMapBundle>,
}
#[derive(Debug, Clone)]
pub struct UltraHdrEncodeOptions {
pub primary: EncodeOptions,
pub gain_map: ComputeGainMapOptions,
pub gain_map_quality: u8,
pub gain_map_progressive: bool,
pub gain_map_compression: CompressionEffort,
}
impl Default for EncodeOptions {
fn default() -> Self {
Self {
quality: 90,
progressive: true,
compression: CompressionEffort::Balanced,
chroma_subsampling: ChromaSubsampling::Yuv420,
primary_metadata: PrimaryMetadata::default(),
ultra_hdr_metadata_emission: UltraHdrMetadataEmission::default(),
gain_map: None,
}
}
}
impl Default for UltraHdrMetadataEmission {
fn default() -> Self {
Self {
emit_primary_container_xmp: true,
emit_gain_map_xmp: true,
emit_iso_21496_1: true,
}
}
}
impl Default for UltraHdrEncodeOptions {
fn default() -> Self {
Self {
primary: EncodeOptions::ultra_hdr_defaults(),
gain_map: ComputeGainMapOptions::default(),
gain_map_quality: 90,
gain_map_progressive: false,
gain_map_compression: CompressionEffort::Balanced,
}
}
}
impl Default for PreparePrimaryOptions {
fn default() -> Self {
Self {
target_gamut: ColorGamut::Bt709,
source_peak_nits: None,
target_peak_nits: 203.0,
}
}
}
impl Default for ComputeGainMapOptions {
fn default() -> Self {
Self {
channels: GainMapChannels::Single,
scale: GainMapScale::Default,
}
}
}
impl ColorMetadata {
#[must_use]
pub fn display_p3() -> Self {
Self {
icc_profile: Some(crate::icc::display_p3().to_vec()),
gamut: Some(ColorGamut::DisplayP3),
gamut_info: Some(GamutInfo::from_standard(ColorGamut::DisplayP3)),
transfer: Some(ColorTransfer::Srgb),
}
}
#[must_use]
pub(crate) fn bt709_srgb() -> Self {
Self {
icc_profile: None,
gamut: Some(ColorGamut::Bt709),
gamut_info: Some(GamutInfo::from_standard(ColorGamut::Bt709)),
transfer: Some(ColorTransfer::Srgb),
}
}
}
impl EncodeOptions {
#[must_use]
pub fn ultra_hdr_defaults() -> Self {
Self {
primary_metadata: PrimaryMetadata {
color: ColorMetadata::display_p3(),
exif: None,
},
..Self::default()
}
}
}
impl PreparePrimaryOptions {
#[must_use]
pub fn ultra_hdr_defaults() -> Self {
Self {
target_gamut: ColorGamut::DisplayP3,
..Self::default()
}
}
}
impl ComputedGainMap {
#[must_use]
pub fn into_bundle(
self,
quality: u8,
progressive: bool,
compression: CompressionEffort,
) -> GainMapBundle {
GainMapBundle {
image: self.image,
metadata: self.metadata,
quality,
progressive,
compression,
}
}
}
impl GamutInfo {
#[must_use]
pub(crate) fn from_standard(standard: ColorGamut) -> Self {
let (red, green, blue, white) = match standard {
ColorGamut::Bt709 => (
Chromaticity { x: 0.64, y: 0.33 },
Chromaticity { x: 0.30, y: 0.60 },
Chromaticity { x: 0.15, y: 0.06 },
Chromaticity {
x: 0.3127,
y: 0.3290,
},
),
ColorGamut::DisplayP3 => (
Chromaticity { x: 0.68, y: 0.32 },
Chromaticity { x: 0.265, y: 0.69 },
Chromaticity { x: 0.15, y: 0.06 },
Chromaticity {
x: 0.3127,
y: 0.3290,
},
),
ColorGamut::Bt2100 => (
Chromaticity { x: 0.708, y: 0.292 },
Chromaticity { x: 0.170, y: 0.797 },
Chromaticity { x: 0.131, y: 0.046 },
Chromaticity {
x: 0.3127,
y: 0.3290,
},
),
};
Self {
standard: Some(standard),
red,
green,
blue,
white,
}
}
}
#[derive(Debug, Clone)]
pub struct Encoder {
pub(crate) options: EncodeOptions,
}
impl DecodedImage {
pub(crate) fn reconstruct_hdr_with(
&self,
display_boost: f32,
output_format: HdrOutputFormat,
) -> Result<RawImage> {
let gain_map = self.gain_map.as_ref().ok_or(Error::MissingGainMap)?;
let metadata = gain_map
.metadata
.as_ref()
.or_else(|| {
self.ultra_hdr
.as_ref()
.and_then(|metadata| metadata.gain_map_metadata.as_ref())
})
.ok_or(Error::MissingGainMapMetadata)?;
reconstruct_hdr_image(
&self.image,
&gain_map.gain_map,
metadata,
display_boost,
output_format,
)
}
}