use alloc::boxed::Box;
use alloc::format;
use alloc::sync::Arc;
use moxcms::{
BarycentricWeightScale, ColorProfile, InterpolationMethod, Layout, TransformExecutor,
TransformOptions,
};
use crate::cms::{ColorPriority, RenderingIntent};
pub fn transform_opts(priority: ColorPriority, intent: RenderingIntent) -> TransformOptions {
TransformOptions {
rendering_intent: match intent {
RenderingIntent::Perceptual => moxcms::RenderingIntent::Perceptual,
RenderingIntent::RelativeColorimetric => moxcms::RenderingIntent::RelativeColorimetric,
RenderingIntent::Saturation => moxcms::RenderingIntent::Saturation,
RenderingIntent::AbsoluteColorimetric => moxcms::RenderingIntent::AbsoluteColorimetric,
},
allow_use_cicp_transfer: matches!(priority, ColorPriority::PreferCicp),
barycentric_weight_scale: BarycentricWeightScale::High,
interpolation_method: InterpolationMethod::Tetrahedral,
..Default::default()
}
}
#[deprecated(
since = "0.2.3",
note = "use transform_opts(ColorPriority::PreferIcc, RenderingIntent::RelativeColorimetric) instead"
)]
pub fn lut_transform_opts() -> TransformOptions {
transform_opts(
ColorPriority::PreferIcc,
RenderingIntent::RelativeColorimetric,
)
}
#[deprecated(
since = "0.2.3",
note = "use transform_opts(ColorPriority::PreferCicp, RenderingIntent::RelativeColorimetric) instead"
)]
pub fn cicp_transform_opts() -> TransformOptions {
transform_opts(
ColorPriority::PreferCicp,
RenderingIntent::RelativeColorimetric,
)
}
use crate::cms::{ColorManagement, RowTransform};
use crate::{ChannelType, Cicp, PixelFormat};
#[derive(Debug, Clone, Copy, Default)]
pub struct MoxCms;
fn pixel_format_to_layout(format: PixelFormat) -> Option<Layout> {
match format {
PixelFormat::Rgb8 | PixelFormat::Rgb16 | PixelFormat::RgbF32 => Some(Layout::Rgb),
PixelFormat::Rgba8 | PixelFormat::Rgba16 | PixelFormat::RgbaF32 => Some(Layout::Rgba),
PixelFormat::Gray8 | PixelFormat::Gray16 | PixelFormat::GrayF32 => Some(Layout::Gray),
PixelFormat::GrayA8 | PixelFormat::GrayA16 | PixelFormat::GrayAF32 => {
Some(Layout::GrayAlpha)
}
_ => None,
}
}
enum MoxTransformInner {
U8(Arc<dyn TransformExecutor<u8> + Send + Sync>),
U16(Arc<dyn TransformExecutor<u16> + Send + Sync>),
F32(Arc<dyn TransformExecutor<f32> + Send + Sync>),
}
struct MoxRowTransform {
inner: MoxTransformInner,
}
impl RowTransform for MoxRowTransform {
fn transform_row(&self, src: &[u8], dst: &mut [u8], _width: u32) {
match &self.inner {
MoxTransformInner::U8(xform) => {
xform
.transform(src, dst)
.expect("moxcms u8 transform: buffer size mismatch");
}
MoxTransformInner::U16(xform) => {
let src_u16: &[u16] = bytemuck::cast_slice(src);
let dst_u16: &mut [u16] = bytemuck::cast_slice_mut(dst);
xform
.transform(src_u16, dst_u16)
.expect("moxcms u16 transform: buffer size mismatch");
}
MoxTransformInner::F32(xform) => {
let src_f32: &[f32] = bytemuck::cast_slice(src);
let dst_f32: &mut [f32] = bytemuck::cast_slice_mut(dst);
xform
.transform(src_f32, dst_f32)
.expect("moxcms f32 transform: buffer size mismatch");
}
}
}
}
fn build_transform_inner(
src_profile: &ColorProfile,
dst_profile: &ColorProfile,
src_format: PixelFormat,
dst_format: PixelFormat,
) -> Result<Box<dyn RowTransform>, MoxCmsError> {
let src_layout = pixel_format_to_layout(src_format).unwrap_or(Layout::Rgb);
let dst_layout = pixel_format_to_layout(dst_format).unwrap_or(Layout::Rgb);
let opts = transform_opts(ColorPriority::PreferIcc, RenderingIntent::default());
let depth = src_format.channel_type();
let inner = match depth {
ChannelType::U8 => {
let xform = src_profile
.create_transform_8bit(src_layout, dst_profile, dst_layout, opts)
.map_err(|e| MoxCmsError(format!("failed to create u8 transform: {e}")))?;
MoxTransformInner::U8(xform)
}
ChannelType::U16 => {
let xform = src_profile
.create_transform_16bit(src_layout, dst_profile, dst_layout, opts)
.map_err(|e| MoxCmsError(format!("failed to create u16 transform: {e}")))?;
MoxTransformInner::U16(xform)
}
ChannelType::F16 | ChannelType::F32 | _ => {
let xform = src_profile
.create_transform_f32(src_layout, dst_profile, dst_layout, opts)
.map_err(|e| MoxCmsError(format!("failed to create f32 transform: {e}")))?;
MoxTransformInner::F32(xform)
}
};
Ok(Box::new(MoxRowTransform { inner }))
}
impl ColorManagement for MoxCms {
type Error = MoxCmsError;
fn build_transform(
&self,
src_icc: &[u8],
dst_icc: &[u8],
) -> Result<Box<dyn RowTransform>, Self::Error> {
self.build_transform_for_format(src_icc, dst_icc, PixelFormat::Rgb8, PixelFormat::Rgb8)
}
fn build_transform_for_format(
&self,
src_icc: &[u8],
dst_icc: &[u8],
src_format: PixelFormat,
dst_format: PixelFormat,
) -> Result<Box<dyn RowTransform>, Self::Error> {
let src_profile = ColorProfile::new_from_slice(src_icc)
.map_err(|e| MoxCmsError(format!("failed to parse source ICC profile: {e}")))?;
let dst_profile = ColorProfile::new_from_slice(dst_icc)
.map_err(|e| MoxCmsError(format!("failed to parse destination ICC profile: {e}")))?;
build_transform_inner(&src_profile, &dst_profile, src_format, dst_format)
}
fn build_transform_from_cicp(
&self,
src_cicp: Cicp,
dst_icc: &[u8],
src_format: PixelFormat,
dst_format: PixelFormat,
) -> Result<Box<dyn RowTransform>, Self::Error> {
let src_profile = ColorProfile::new_from_cicp(moxcms::CicpProfile {
color_primaries: moxcms::CicpColorPrimaries::try_from(src_cicp.color_primaries)
.unwrap_or(moxcms::CicpColorPrimaries::Bt709),
transfer_characteristics: moxcms::TransferCharacteristics::try_from(
src_cicp.transfer_characteristics,
)
.unwrap_or(moxcms::TransferCharacteristics::Srgb),
matrix_coefficients: moxcms::MatrixCoefficients::try_from(src_cicp.matrix_coefficients)
.unwrap_or(moxcms::MatrixCoefficients::Identity),
full_range: src_cicp.full_range,
});
let dst_profile = ColorProfile::new_from_slice(dst_icc)
.map_err(|e| MoxCmsError(format!("failed to parse destination ICC profile: {e}")))?;
build_transform_inner(&src_profile, &dst_profile, src_format, dst_format)
}
fn identify_profile(&self, icc: &[u8]) -> Option<Cicp> {
let profile = ColorProfile::new_from_slice(icc).ok()?;
if let Some(cicp) = &profile.cicp {
return Some(Cicp::new(
cicp.color_primaries as u8,
cicp.transfer_characteristics as u8,
cicp.matrix_coefficients as u8,
cicp.full_range,
));
}
identify_by_colorants(&profile)
}
}
fn identify_by_colorants(profile: &ColorProfile) -> Option<Cicp> {
struct KnownProfile {
primaries_code: u8,
rx: f64,
ry: f64,
gx: f64,
gy: f64,
bx: f64,
by: f64,
}
const KNOWN: &[KnownProfile] = &[
KnownProfile {
primaries_code: 1,
rx: 0.4361,
ry: 0.2225,
gx: 0.3851,
gy: 0.7169,
bx: 0.1431,
by: 0.0606,
},
KnownProfile {
primaries_code: 12,
rx: 0.5151,
ry: 0.2412,
gx: 0.2919,
gy: 0.6922,
bx: 0.1572,
by: 0.0666,
},
KnownProfile {
primaries_code: 9,
rx: 0.6734,
ry: 0.2790,
gx: 0.1656,
gy: 0.6753,
bx: 0.1251,
by: 0.0456,
},
];
let r = &profile.red_colorant;
let g = &profile.green_colorant;
let b = &profile.blue_colorant;
const TOL: f64 = 0.003;
for known in KNOWN {
let matches = (r.x - known.rx).abs() < TOL
&& (r.y - known.ry).abs() < TOL
&& (g.x - known.gx).abs() < TOL
&& (g.y - known.gy).abs() < TOL
&& (b.x - known.bx).abs() < TOL
&& (b.y - known.by).abs() < TOL;
if matches {
let transfer = match known.primaries_code {
1 | 12 => 13, _ => 1, };
return Some(Cicp::new(
known.primaries_code,
transfer,
0, true,
));
}
}
None
}
#[derive(Debug, Clone)]
pub struct MoxCmsError(pub String);
impl core::fmt::Display for MoxCmsError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
f.write_str(&self.0)
}
}