use alloc::sync::Arc;
use crate::cms::ColorManagement;
use crate::error::ConvertError;
use crate::hdr::HdrMetadata;
use crate::{
Cicp, ColorAuthority, ColorOrigin, ColorPrimaries, PixelBuffer, PixelDescriptor, PixelFormat,
PixelSlice, TransferFunction,
};
use whereat::{At, ResultAtExt};
#[derive(Clone, Debug)]
#[non_exhaustive]
pub enum OutputProfile {
SameAsOrigin,
Named(Cicp),
Icc(Arc<[u8]>),
}
#[derive(Clone, Debug)]
#[non_exhaustive]
pub struct OutputMetadata {
pub icc: Option<Arc<[u8]>>,
pub cicp: Option<Cicp>,
pub hdr: Option<HdrMetadata>,
}
#[non_exhaustive]
pub struct EncodeReady {
pixels: PixelBuffer,
metadata: OutputMetadata,
}
impl EncodeReady {
pub fn pixels(&self) -> PixelSlice<'_> {
self.pixels.as_slice()
}
pub fn metadata(&self) -> &OutputMetadata {
&self.metadata
}
pub fn into_parts(self) -> (PixelBuffer, OutputMetadata) {
(self.pixels, self.metadata)
}
}
#[track_caller]
pub fn finalize_for_output<C: ColorManagement>(
buffer: &PixelBuffer,
origin: &ColorOrigin,
target: OutputProfile,
pixel_format: PixelFormat,
cms: &C,
) -> Result<EncodeReady, At<ConvertError>> {
let source_desc = buffer.descriptor();
let target_desc = pixel_format.descriptor();
let (metadata, needs_cms_transform) = match &target {
OutputProfile::SameAsOrigin => {
let metadata = OutputMetadata {
icc: origin.icc.clone(),
cicp: origin.cicp,
hdr: None,
};
(metadata, false)
}
OutputProfile::Named(cicp) => {
let metadata = OutputMetadata {
icc: None,
cicp: Some(*cicp),
hdr: None,
};
(metadata, false)
}
OutputProfile::Icc(icc) => {
let metadata = OutputMetadata {
icc: Some(icc.clone()),
cicp: None,
hdr: None,
};
(metadata, true)
}
};
if needs_cms_transform
&& let Some(transform) =
build_cms_transform(origin, &metadata, &source_desc, pixel_format, cms)?
{
let src_slice = buffer.as_slice();
let mut out = PixelBuffer::try_new(buffer.width(), buffer.height(), target_desc)
.map_err(|_| whereat::at!(ConvertError::AllocationFailed))?;
{
let mut dst_slice = out.as_slice_mut();
for y in 0..buffer.height() {
let src_row = src_slice.row(y);
let dst_row = dst_slice.row_mut(y);
transform.transform_row(src_row, dst_row, buffer.width());
}
}
return Ok(EncodeReady {
pixels: out,
metadata,
});
}
let target_desc_full = target_desc
.with_transfer(resolve_transfer(&target, &source_desc))
.with_primaries(resolve_primaries(&target, &source_desc));
if source_desc.layout_compatible(target_desc_full)
&& descriptors_match(&source_desc, &target_desc_full)
{
let src_slice = buffer.as_slice();
let bytes = src_slice.contiguous_bytes();
let out = PixelBuffer::from_vec(
bytes.into_owned(),
buffer.width(),
buffer.height(),
target_desc_full,
)
.map_err(|_| whereat::at!(ConvertError::AllocationFailed))?;
return Ok(EncodeReady {
pixels: out,
metadata,
});
}
let mut converter = crate::RowConverter::new(source_desc, target_desc_full).at()?;
let src_slice = buffer.as_slice();
let mut out = PixelBuffer::try_new(buffer.width(), buffer.height(), target_desc_full)
.map_err(|_| whereat::at!(ConvertError::AllocationFailed))?;
{
let mut dst_slice = out.as_slice_mut();
for y in 0..buffer.height() {
let src_row = src_slice.row(y);
let dst_row = dst_slice.row_mut(y);
converter.convert_row(src_row, dst_row, buffer.width());
}
}
Ok(EncodeReady {
pixels: out,
metadata,
})
}
fn build_cms_transform<C: ColorManagement>(
origin: &ColorOrigin,
metadata: &OutputMetadata,
source_desc: &PixelDescriptor,
dst_format: PixelFormat,
cms: &C,
) -> Result<Option<alloc::boxed::Box<dyn crate::cms::RowTransform>>, At<ConvertError>> {
let src_format = source_desc.format;
let Some(ref dst_icc) = metadata.icc else {
return Ok(None);
};
let try_icc = |src_icc: &[u8]| -> Result<Option<_>, At<ConvertError>> {
let transform = cms
.build_transform_for_format(src_icc, dst_icc, src_format, dst_format)
.map_err(|e| whereat::at!(ConvertError::CmsError(alloc::format!("{e:?}"))))?;
Ok(Some(transform))
};
match origin.color_authority {
ColorAuthority::Icc => {
if let Some(ref src_icc) = origin.icc {
return try_icc(src_icc);
}
Ok(None)
}
ColorAuthority::Cicp => {
if let Some(ref src_icc) = origin.icc {
return try_icc(src_icc);
}
Ok(None)
}
}
}
fn resolve_transfer(target: &OutputProfile, source: &PixelDescriptor) -> TransferFunction {
match target {
OutputProfile::SameAsOrigin => source.transfer(),
OutputProfile::Named(cicp) => TransferFunction::from_cicp(cicp.transfer_characteristics)
.unwrap_or(TransferFunction::Unknown),
OutputProfile::Icc(_) => TransferFunction::Unknown,
}
}
fn resolve_primaries(target: &OutputProfile, source: &PixelDescriptor) -> ColorPrimaries {
match target {
OutputProfile::SameAsOrigin => source.primaries,
OutputProfile::Named(cicp) => {
ColorPrimaries::from_cicp(cicp.color_primaries).unwrap_or(ColorPrimaries::Unknown)
}
OutputProfile::Icc(_) => ColorPrimaries::Unknown,
}
}
fn descriptors_match(a: &PixelDescriptor, b: &PixelDescriptor) -> bool {
a.format == b.format
&& a.transfer == b.transfer
&& a.primaries == b.primaries
&& a.signal_range == b.signal_range
}