use super::{IccProfile, RenderingIntent};
#[derive(Debug, Clone, Copy, Default)]
pub struct TransformFlags {
pub black_point_compensation: bool,
}
impl TransformFlags {
pub const fn press_default() -> Self {
Self {
black_point_compensation: true,
}
}
}
pub trait IccBackend {
type SrgbTransform;
type CmykRetarget;
type SrgbToCmykTransform;
fn build_srgb_transform(
profile: &IccProfile,
intent: RenderingIntent,
flags: TransformFlags,
) -> Option<Self::SrgbTransform>;
fn convert_cmyk_pixel(transform: &Self::SrgbTransform, cmyk: [u8; 4]) -> Option<[u8; 3]>;
fn convert_cmyk_buffer(transform: &Self::SrgbTransform, cmyk: &[u8]) -> Option<Vec<u8>>;
fn convert_rgb_buffer(transform: &Self::SrgbTransform, rgb: &[u8]) -> Option<Vec<u8>>;
fn convert_gray_buffer(transform: &Self::SrgbTransform, gray: &[u8]) -> Option<Vec<u8>>;
fn build_cmyk_retarget(
src_profile: &IccProfile,
dst_profile: &IccProfile,
intent: RenderingIntent,
flags: TransformFlags,
) -> Option<Self::CmykRetarget>;
fn retarget_cmyk_pixel(transform: &Self::CmykRetarget, cmyk: [f32; 4]) -> [f32; 4];
fn build_srgb_to_cmyk(
dst_profile: &IccProfile,
intent: RenderingIntent,
flags: TransformFlags,
) -> Option<Self::SrgbToCmykTransform>;
fn convert_srgb_to_cmyk_pixel(transform: &Self::SrgbToCmykTransform, rgb: [f32; 3])
-> [f32; 4];
}
#[cfg(feature = "icc-qcms")]
pub struct QcmsBackend;
#[cfg(feature = "icc-qcms")]
mod qcms_impl {
use super::*;
pub struct SrgbTransform {
pub(super) inner: qcms::Transform,
pub(super) source_components: u8,
}
pub struct CmykRetarget(pub(super) core::convert::Infallible);
pub struct SrgbToCmykTransform(pub(super) core::convert::Infallible);
fn qcms_intent(intent: RenderingIntent) -> qcms::Intent {
match intent {
RenderingIntent::Perceptual => qcms::Intent::Perceptual,
RenderingIntent::RelativeColorimetric => qcms::Intent::RelativeColorimetric,
RenderingIntent::Saturation => qcms::Intent::Saturation,
RenderingIntent::AbsoluteColorimetric => qcms::Intent::AbsoluteColorimetric,
}
}
impl IccBackend for QcmsBackend {
type SrgbTransform = SrgbTransform;
type CmykRetarget = CmykRetarget;
type SrgbToCmykTransform = SrgbToCmykTransform;
fn build_srgb_transform(
profile: &IccProfile,
intent: RenderingIntent,
_flags: TransformFlags,
) -> Option<Self::SrgbTransform> {
let src = qcms::Profile::new_from_slice(profile.bytes(), false)?;
let dst = qcms::Profile::new_sRGB();
let src_ty = match profile.n_components() {
1 => qcms::DataType::Gray8,
3 => qcms::DataType::RGB8,
4 => qcms::DataType::CMYK,
_ => return None,
};
qcms::Transform::new_to(&src, &dst, src_ty, qcms::DataType::RGB8, qcms_intent(intent))
.map(|inner| SrgbTransform {
inner,
source_components: profile.n_components(),
})
}
fn convert_cmyk_pixel(transform: &Self::SrgbTransform, cmyk: [u8; 4]) -> Option<[u8; 3]> {
if transform.source_components != 4 {
return None;
}
let mut dst = [0u8; 3];
transform.inner.convert(&cmyk, &mut dst);
Some(dst)
}
fn convert_cmyk_buffer(transform: &Self::SrgbTransform, cmyk: &[u8]) -> Option<Vec<u8>> {
if transform.source_components != 4 {
return None;
}
let pixels = cmyk.len() / 4;
let mut out = vec![0u8; pixels * 3];
transform.inner.convert(cmyk, &mut out);
Some(out)
}
fn convert_rgb_buffer(transform: &Self::SrgbTransform, rgb: &[u8]) -> Option<Vec<u8>> {
if transform.source_components != 3 {
return None;
}
let mut out = vec![0u8; rgb.len()];
transform.inner.convert(rgb, &mut out);
Some(out)
}
fn convert_gray_buffer(transform: &Self::SrgbTransform, gray: &[u8]) -> Option<Vec<u8>> {
if transform.source_components != 1 {
return None;
}
let mut out = vec![0u8; gray.len() * 3];
transform.inner.convert(gray, &mut out);
Some(out)
}
fn build_cmyk_retarget(
_src_profile: &IccProfile,
_dst_profile: &IccProfile,
_intent: RenderingIntent,
_flags: TransformFlags,
) -> Option<Self::CmykRetarget> {
None
}
fn retarget_cmyk_pixel(transform: &Self::CmykRetarget, _cmyk: [f32; 4]) -> [f32; 4] {
match transform.0 {}
}
fn build_srgb_to_cmyk(
_dst_profile: &IccProfile,
_intent: RenderingIntent,
_flags: TransformFlags,
) -> Option<Self::SrgbToCmykTransform> {
None
}
fn convert_srgb_to_cmyk_pixel(
transform: &Self::SrgbToCmykTransform,
_rgb: [f32; 3],
) -> [f32; 4] {
match transform.0 {}
}
}
}
#[cfg(feature = "icc-qcms")]
pub use qcms_impl::{
CmykRetarget as QcmsCmykRetarget, SrgbToCmykTransform as QcmsSrgbToCmykTransform,
SrgbTransform as QcmsSrgbTransform,
};
#[cfg(feature = "icc-lcms2")]
pub struct Lcms2Backend;
#[cfg(feature = "icc-lcms2")]
mod lcms2_impl {
use super::*;
pub struct SrgbTransform {
pub(super) inner: lcms2::Transform<u8, u8, lcms2::GlobalContext, lcms2::DisallowCache>,
pub(super) source_components: u8,
}
pub struct CmykRetarget {
pub(super) inner:
lcms2::Transform<[u8; 4], [u8; 4], lcms2::GlobalContext, lcms2::DisallowCache>,
}
pub struct SrgbToCmykTransform {
pub(super) inner:
lcms2::Transform<[u8; 3], [u8; 4], lcms2::GlobalContext, lcms2::DisallowCache>,
}
fn lcms2_intent(intent: RenderingIntent) -> lcms2::Intent {
match intent {
RenderingIntent::Perceptual => lcms2::Intent::Perceptual,
RenderingIntent::RelativeColorimetric => lcms2::Intent::RelativeColorimetric,
RenderingIntent::Saturation => lcms2::Intent::Saturation,
RenderingIntent::AbsoluteColorimetric => lcms2::Intent::AbsoluteColorimetric,
}
}
fn lcms2_flags(flags: TransformFlags) -> lcms2::Flags<lcms2::DisallowCache> {
if flags.black_point_compensation {
lcms2::Flags::NO_CACHE | lcms2::Flags::BLACKPOINT_COMPENSATION
} else {
lcms2::Flags::NO_CACHE
}
}
fn src_pixel_format(n_components: u8) -> Option<lcms2::PixelFormat> {
match n_components {
1 => Some(lcms2::PixelFormat::GRAY_8),
3 => Some(lcms2::PixelFormat::RGB_8),
4 => Some(lcms2::PixelFormat::CMYK_8),
_ => None,
}
}
impl IccBackend for Lcms2Backend {
type SrgbTransform = SrgbTransform;
type CmykRetarget = CmykRetarget;
type SrgbToCmykTransform = SrgbToCmykTransform;
fn build_srgb_transform(
profile: &IccProfile,
intent: RenderingIntent,
flags: TransformFlags,
) -> Option<Self::SrgbTransform> {
let src = lcms2::Profile::new_icc(profile.bytes()).ok()?;
let dst = lcms2::Profile::new_srgb();
let in_fmt = src_pixel_format(profile.n_components())?;
let out_fmt = lcms2::PixelFormat::RGB_8;
let inner = lcms2::Transform::new_flags_context(
lcms2::GlobalContext::new(),
&src,
in_fmt,
&dst,
out_fmt,
lcms2_intent(intent),
lcms2_flags(flags),
)
.ok()?;
Some(SrgbTransform {
inner,
source_components: profile.n_components(),
})
}
fn convert_cmyk_pixel(transform: &Self::SrgbTransform, cmyk: [u8; 4]) -> Option<[u8; 3]> {
if transform.source_components != 4 {
return None;
}
let src: [u8; 4] = cmyk;
let mut dst = [0u8; 3];
transform.inner.transform_pixels(&src, &mut dst);
Some(dst)
}
fn convert_cmyk_buffer(transform: &Self::SrgbTransform, cmyk: &[u8]) -> Option<Vec<u8>> {
if transform.source_components != 4 {
return None;
}
let pixels = cmyk.len() / 4;
let mut out = vec![0u8; pixels * 3];
transform.inner.transform_pixels(cmyk, &mut out);
Some(out)
}
fn convert_rgb_buffer(transform: &Self::SrgbTransform, rgb: &[u8]) -> Option<Vec<u8>> {
if transform.source_components != 3 {
return None;
}
let mut out = vec![0u8; rgb.len()];
transform.inner.transform_pixels(rgb, &mut out);
Some(out)
}
fn convert_gray_buffer(transform: &Self::SrgbTransform, gray: &[u8]) -> Option<Vec<u8>> {
if transform.source_components != 1 {
return None;
}
let mut out = vec![0u8; gray.len() * 3];
transform.inner.transform_pixels(gray, &mut out);
Some(out)
}
fn build_cmyk_retarget(
src_profile: &IccProfile,
dst_profile: &IccProfile,
intent: RenderingIntent,
flags: TransformFlags,
) -> Option<Self::CmykRetarget> {
if src_profile.n_components() != 4 || dst_profile.n_components() != 4 {
return None;
}
let src = lcms2::Profile::new_icc(src_profile.bytes()).ok()?;
let dst = lcms2::Profile::new_icc(dst_profile.bytes()).ok()?;
if !matches!(src.color_space(), lcms2::ColorSpaceSignature::CmykData) {
return None;
}
if !matches!(dst.color_space(), lcms2::ColorSpaceSignature::CmykData) {
return None;
}
let inner = lcms2::Transform::new_flags_context(
lcms2::GlobalContext::new(),
&src,
lcms2::PixelFormat::CMYK_8,
&dst,
lcms2::PixelFormat::CMYK_8,
lcms2_intent(intent),
lcms2_flags(flags),
)
.ok()?;
Some(CmykRetarget { inner })
}
fn retarget_cmyk_pixel(transform: &Self::CmykRetarget, cmyk: [f32; 4]) -> [f32; 4] {
let src = [[
(cmyk[0].clamp(0.0, 1.0) * 255.0).round() as u8,
(cmyk[1].clamp(0.0, 1.0) * 255.0).round() as u8,
(cmyk[2].clamp(0.0, 1.0) * 255.0).round() as u8,
(cmyk[3].clamp(0.0, 1.0) * 255.0).round() as u8,
]];
let mut dst = [[0u8; 4]; 1];
transform.inner.transform_pixels(&src, &mut dst);
[
dst[0][0] as f32 / 255.0,
dst[0][1] as f32 / 255.0,
dst[0][2] as f32 / 255.0,
dst[0][3] as f32 / 255.0,
]
}
fn build_srgb_to_cmyk(
dst_profile: &IccProfile,
intent: RenderingIntent,
flags: TransformFlags,
) -> Option<Self::SrgbToCmykTransform> {
if dst_profile.n_components() != 4 {
return None;
}
let src = lcms2::Profile::new_srgb();
let dst = lcms2::Profile::new_icc(dst_profile.bytes()).ok()?;
if !matches!(dst.color_space(), lcms2::ColorSpaceSignature::CmykData) {
return None;
}
let inner = lcms2::Transform::new_flags_context(
lcms2::GlobalContext::new(),
&src,
lcms2::PixelFormat::RGB_8,
&dst,
lcms2::PixelFormat::CMYK_8,
lcms2_intent(intent),
lcms2_flags(flags),
)
.ok()?;
Some(SrgbToCmykTransform { inner })
}
fn convert_srgb_to_cmyk_pixel(
transform: &Self::SrgbToCmykTransform,
rgb: [f32; 3],
) -> [f32; 4] {
let src = [[
(rgb[0].clamp(0.0, 1.0) * 255.0).round() as u8,
(rgb[1].clamp(0.0, 1.0) * 255.0).round() as u8,
(rgb[2].clamp(0.0, 1.0) * 255.0).round() as u8,
]];
let mut dst = [[0u8; 4]; 1];
transform.inner.transform_pixels(&src, &mut dst);
[
dst[0][0] as f32 / 255.0,
dst[0][1] as f32 / 255.0,
dst[0][2] as f32 / 255.0,
dst[0][3] as f32 / 255.0,
]
}
}
}
#[cfg(feature = "icc-lcms2")]
pub use lcms2_impl::{
CmykRetarget as Lcms2CmykRetarget, SrgbToCmykTransform as Lcms2SrgbToCmykTransform,
SrgbTransform as Lcms2SrgbTransform,
};
#[cfg(not(any(feature = "icc-qcms", feature = "icc-lcms2")))]
pub struct NoOpBackend;
#[cfg(not(any(feature = "icc-qcms", feature = "icc-lcms2")))]
mod noop_impl {
use super::*;
pub struct SrgbTransform(pub(super) core::convert::Infallible);
pub struct CmykRetarget(pub(super) core::convert::Infallible);
pub struct SrgbToCmykTransform(pub(super) core::convert::Infallible);
impl IccBackend for NoOpBackend {
type SrgbTransform = SrgbTransform;
type CmykRetarget = CmykRetarget;
type SrgbToCmykTransform = SrgbToCmykTransform;
fn build_srgb_transform(
_profile: &IccProfile,
_intent: RenderingIntent,
_flags: TransformFlags,
) -> Option<Self::SrgbTransform> {
None
}
fn convert_cmyk_pixel(transform: &Self::SrgbTransform, _cmyk: [u8; 4]) -> Option<[u8; 3]> {
match transform.0 {}
}
fn convert_cmyk_buffer(transform: &Self::SrgbTransform, _cmyk: &[u8]) -> Option<Vec<u8>> {
match transform.0 {}
}
fn convert_rgb_buffer(transform: &Self::SrgbTransform, _rgb: &[u8]) -> Option<Vec<u8>> {
match transform.0 {}
}
fn convert_gray_buffer(transform: &Self::SrgbTransform, _gray: &[u8]) -> Option<Vec<u8>> {
match transform.0 {}
}
fn build_cmyk_retarget(
_src_profile: &IccProfile,
_dst_profile: &IccProfile,
_intent: RenderingIntent,
_flags: TransformFlags,
) -> Option<Self::CmykRetarget> {
None
}
fn retarget_cmyk_pixel(transform: &Self::CmykRetarget, _cmyk: [f32; 4]) -> [f32; 4] {
match transform.0 {}
}
fn build_srgb_to_cmyk(
_dst_profile: &IccProfile,
_intent: RenderingIntent,
_flags: TransformFlags,
) -> Option<Self::SrgbToCmykTransform> {
None
}
fn convert_srgb_to_cmyk_pixel(
transform: &Self::SrgbToCmykTransform,
_rgb: [f32; 3],
) -> [f32; 4] {
match transform.0 {}
}
}
}
#[cfg(not(any(feature = "icc-qcms", feature = "icc-lcms2")))]
pub use noop_impl::{
CmykRetarget as NoOpCmykRetarget, SrgbToCmykTransform as NoOpSrgbToCmykTransform,
SrgbTransform as NoOpSrgbTransform,
};
#[cfg(feature = "icc-lcms2")]
pub type ActiveIccBackend = Lcms2Backend;
#[cfg(all(feature = "icc-qcms", not(feature = "icc-lcms2")))]
pub type ActiveIccBackend = QcmsBackend;
#[cfg(not(any(feature = "icc-qcms", feature = "icc-lcms2")))]
pub type ActiveIccBackend = NoOpBackend;
pub const fn active_backend_name() -> &'static str {
#[cfg(feature = "icc-lcms2")]
{
"lcms2"
}
#[cfg(all(feature = "icc-qcms", not(feature = "icc-lcms2")))]
{
"qcms"
}
#[cfg(not(any(feature = "icc-qcms", feature = "icc-lcms2")))]
{
"noop"
}
}