all: split reusable components into workspace crates
This commit is contained in:
parent
2a079ed800
commit
657e7ce2f7
225 changed files with 7422 additions and 17602 deletions
87
cmm/src/cmm_description.rs
Normal file
87
cmm/src/cmm_description.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm_eotf::Eotf,
|
||||
cmm_luminance::{Luminance, TargetLuminance, white_balance},
|
||||
cmm_manager::Shared,
|
||||
cmm_primaries::{NamedPrimaries, Primaries},
|
||||
cmm_render_intent::RenderIntent,
|
||||
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
|
||||
},
|
||||
jay_utils::ordered_float::F64,
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
linear_ids!(LinearColorDescriptionIds, LinearColorDescriptionId, u64);
|
||||
linear_ids!(ColorDescriptionIds, ColorDescriptionId, u64);
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LinearColorDescription {
|
||||
pub id: LinearColorDescriptionId,
|
||||
pub primaries: Primaries,
|
||||
pub xyz_from_local: ColorMatrix<Xyz, Local>,
|
||||
pub local_from_xyz: ColorMatrix<Local, Xyz>,
|
||||
pub luminance: Luminance,
|
||||
pub target_primaries: Primaries,
|
||||
pub target_luminance: TargetLuminance,
|
||||
pub max_cll: Option<F64>,
|
||||
pub max_fall: Option<F64>,
|
||||
pub(super) shared: Rc<Shared>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ColorDescription {
|
||||
pub id: ColorDescriptionId,
|
||||
pub linear: Rc<LinearColorDescription>,
|
||||
pub named_primaries: Option<NamedPrimaries>,
|
||||
pub eotf: Eotf,
|
||||
pub(super) shared: Rc<Shared>,
|
||||
}
|
||||
|
||||
impl LinearColorDescription {
|
||||
pub fn color_transform(&self, target: &Self, intent: RenderIntent) -> ColorMatrix {
|
||||
let mut mat = target.local_from_xyz;
|
||||
if self.luminance != target.luminance {
|
||||
mat *= white_balance(
|
||||
&self.luminance,
|
||||
&target.luminance,
|
||||
target.primaries.wp,
|
||||
intent,
|
||||
);
|
||||
}
|
||||
if self.primaries.wp != target.primaries.wp && intent.bradford_adjustment() {
|
||||
mat *= bradford_adjustment(self.primaries.wp, target.primaries.wp);
|
||||
}
|
||||
mat * self.xyz_from_local
|
||||
}
|
||||
|
||||
pub fn embeds_into(&self, target: &Self) -> bool {
|
||||
if self.id == target.id {
|
||||
return true;
|
||||
}
|
||||
if self.primaries != target.primaries {
|
||||
return false;
|
||||
}
|
||||
if self.luminance != target.luminance {
|
||||
return false;
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorDescription {
|
||||
pub fn embeds_into(&self, target: &Self) -> bool {
|
||||
self.eotf == target.eotf && self.linear.embeds_into(&target.linear)
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for LinearColorDescription {
|
||||
fn drop(&mut self) {
|
||||
self.shared.dead_linear.fetch_add(1);
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ColorDescription {
|
||||
fn drop(&mut self) {
|
||||
self.shared.dead_complete.fetch_add(1);
|
||||
}
|
||||
}
|
||||
60
cmm/src/cmm_eotf.rs
Normal file
60
cmm/src/cmm_eotf.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use jay_utils::ordered_float::F32;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum Eotf {
|
||||
Linear,
|
||||
St2084Pq,
|
||||
Bt1886(F32),
|
||||
Gamma22,
|
||||
Gamma24,
|
||||
Gamma28,
|
||||
St240,
|
||||
Log100,
|
||||
Log316,
|
||||
St428,
|
||||
Pow(EotfPow),
|
||||
CompoundPower24,
|
||||
}
|
||||
|
||||
const MUL: u32 = 10_000;
|
||||
const MUL_F32: f32 = MUL as f32;
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash, Ord, PartialOrd)]
|
||||
pub struct EotfPow(pub u32);
|
||||
|
||||
impl EotfPow {
|
||||
pub const MIN: Self = Self(10_000);
|
||||
pub const LINEAR: Self = Self(10_000);
|
||||
pub const GAMMA22: Self = Self(22_000);
|
||||
pub const GAMMA24: Self = Self(24_000);
|
||||
pub const GAMMA28: Self = Self(28_000);
|
||||
pub const MAX: Self = Self(100_000);
|
||||
|
||||
pub fn eotf_f32(self) -> f32 {
|
||||
self.0 as f32 / MUL_F32
|
||||
}
|
||||
|
||||
pub fn inv_eotf_f32(self) -> f32 {
|
||||
MUL_F32 / self.0 as f32
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bt1886_eotf_args(c: F32) -> [f32; 4] {
|
||||
let c = c.0;
|
||||
let gamma = 1.0 / 2.4;
|
||||
let a1 = 1.0 / (1.0 - c);
|
||||
let a2 = 1.0 - c.powf(gamma);
|
||||
let a3 = c.powf(gamma);
|
||||
let a4 = c;
|
||||
[a1, a2, a3, a4]
|
||||
}
|
||||
|
||||
pub fn bt1886_inv_eotf_args(c: F32) -> [f32; 4] {
|
||||
let c = c.0;
|
||||
let gamma = 1.0 / 2.4;
|
||||
let a1 = 1.0 / (1.0 - c.powf(gamma));
|
||||
let a2 = 1.0 - c;
|
||||
let a3 = c;
|
||||
let a4 = c.powf(gamma);
|
||||
[a1, a2, a3, a4]
|
||||
}
|
||||
94
cmm/src/cmm_luminance.rs
Normal file
94
cmm/src/cmm_luminance.rs
Normal file
|
|
@ -0,0 +1,94 @@
|
|||
use crate::{
|
||||
cmm_render_intent::RenderIntent,
|
||||
cmm_transform::{ColorMatrix, Xyz},
|
||||
};
|
||||
use jay_utils::ordered_float::F64;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Luminance {
|
||||
pub min: F64,
|
||||
pub max: F64,
|
||||
pub white: F64,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct TargetLuminance {
|
||||
pub min: F64,
|
||||
pub max: F64,
|
||||
}
|
||||
|
||||
impl Luminance {
|
||||
pub const SRGB: Self = Self {
|
||||
min: F64(0.2),
|
||||
max: F64(80.0),
|
||||
white: F64(80.0),
|
||||
};
|
||||
|
||||
pub const BT1886: Self = Self {
|
||||
min: F64(0.01),
|
||||
max: F64(100.0),
|
||||
white: F64(100.0),
|
||||
};
|
||||
|
||||
pub const ST2084_PQ: Self = Self {
|
||||
min: F64(0.0),
|
||||
max: F64(10000.0),
|
||||
white: F64(203.0),
|
||||
};
|
||||
|
||||
pub const HLG: Self = Self {
|
||||
min: F64(0.005),
|
||||
max: F64(1000.0),
|
||||
white: F64(203.0),
|
||||
};
|
||||
|
||||
pub const WINDOWS_SCRGB: Self = Self {
|
||||
min: Self::ST2084_PQ.min,
|
||||
max: Self::ST2084_PQ.max,
|
||||
// This causes the white balance formula (with target ST2084_PQ) to simplify to
|
||||
// `Y * 80 / 10000`, meaning that sRGB pure white maps to a luminance of
|
||||
// 80 cd/m^2.
|
||||
white: F64(Self::ST2084_PQ.white.0 / 80.0 * Self::ST2084_PQ.max.0),
|
||||
};
|
||||
}
|
||||
|
||||
impl Luminance {
|
||||
pub fn to_target(&self) -> TargetLuminance {
|
||||
TargetLuminance {
|
||||
min: self.min,
|
||||
max: self.max,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Luminance {
|
||||
fn default() -> Self {
|
||||
Self::SRGB
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(non_snake_case)]
|
||||
pub fn white_balance(
|
||||
from: &Luminance,
|
||||
to: &Luminance,
|
||||
w_to: (F64, F64),
|
||||
intent: RenderIntent,
|
||||
) -> ColorMatrix<Xyz, Xyz> {
|
||||
let a = ((from.max - from.min) / (to.max - to.min) * (to.white - to.min)
|
||||
/ (from.white - from.min))
|
||||
.0;
|
||||
let d = match intent.black_point_compensation() {
|
||||
true => 0.0,
|
||||
false => ((from.min - to.min) / (to.max - to.min)).0,
|
||||
};
|
||||
let s = a - d;
|
||||
let (F64(x_to), F64(y_to)) = w_to;
|
||||
let X_to = x_to / y_to;
|
||||
let Y_to = 1.0;
|
||||
let Z_to = (1.0 - x_to - y_to) / y_to;
|
||||
ColorMatrix::new([
|
||||
[s, 0.0, 0.0, d * X_to],
|
||||
[0.0, s, 0.0, d * Y_to],
|
||||
[0.0, 0.0, s, d * Z_to],
|
||||
])
|
||||
}
|
||||
251
cmm/src/cmm_manager.rs
Normal file
251
cmm/src/cmm_manager.rs
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm_description::{
|
||||
ColorDescription, ColorDescriptionIds, LinearColorDescription,
|
||||
LinearColorDescriptionId, LinearColorDescriptionIds,
|
||||
},
|
||||
cmm_eotf::Eotf,
|
||||
cmm_luminance::{Luminance, TargetLuminance},
|
||||
cmm_primaries::{NamedPrimaries, Primaries},
|
||||
},
|
||||
jay_utils::{copyhashmap::CopyHashMap, numcell::NumCell, ordered_float::F64},
|
||||
std::rc::{Rc, Weak},
|
||||
};
|
||||
|
||||
pub struct ColorManager {
|
||||
linear_ids: LinearColorDescriptionIds,
|
||||
linear_descriptions: CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>,
|
||||
complete_descriptions: CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
|
||||
shared: Rc<Shared>,
|
||||
srgb_gamma22: Rc<ColorDescription>,
|
||||
srgb_linear: Rc<ColorDescription>,
|
||||
windows_scrgb: Rc<ColorDescription>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(super) struct Shared {
|
||||
pub(super) dead_linear: NumCell<usize>,
|
||||
pub(super) dead_complete: NumCell<usize>,
|
||||
pub(super) complete_ids: ColorDescriptionIds,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct LinearDescriptionKey {
|
||||
primaries: Primaries,
|
||||
luminance: Luminance,
|
||||
target_primaries: Primaries,
|
||||
target_luminance: TargetLuminance,
|
||||
max_cll: Option<F64>,
|
||||
max_fall: Option<F64>,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct CompleteDescriptionKey {
|
||||
linear: LinearColorDescriptionId,
|
||||
named_primaries: Option<NamedPrimaries>,
|
||||
eotf: Eotf,
|
||||
}
|
||||
|
||||
impl ColorManager {
|
||||
pub fn new() -> Rc<Self> {
|
||||
let linear_ids = LinearColorDescriptionIds::default();
|
||||
let linear_descriptions = CopyHashMap::default();
|
||||
let complete_descriptions = CopyHashMap::default();
|
||||
let shared = Rc::new(Shared::default());
|
||||
let _ = shared.complete_ids.next();
|
||||
let srgb_gamma22 = get_description(
|
||||
&shared,
|
||||
&linear_descriptions,
|
||||
&complete_descriptions,
|
||||
&linear_ids,
|
||||
Some(NamedPrimaries::Srgb),
|
||||
Primaries::SRGB,
|
||||
Luminance::SRGB,
|
||||
Eotf::Gamma22,
|
||||
Primaries::SRGB,
|
||||
Luminance::SRGB.to_target(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let srgb_linear = get_description2(
|
||||
&shared,
|
||||
&srgb_gamma22.linear,
|
||||
&complete_descriptions,
|
||||
Some(NamedPrimaries::Srgb),
|
||||
Eotf::Linear,
|
||||
);
|
||||
let windows_scrgb = get_description(
|
||||
&shared,
|
||||
&linear_descriptions,
|
||||
&complete_descriptions,
|
||||
&linear_ids,
|
||||
Some(NamedPrimaries::Srgb),
|
||||
Primaries::SRGB,
|
||||
Luminance::WINDOWS_SCRGB,
|
||||
Eotf::Linear,
|
||||
Primaries::BT2020,
|
||||
Luminance::ST2084_PQ.to_target(),
|
||||
None,
|
||||
None,
|
||||
);
|
||||
Rc::new(Self {
|
||||
linear_ids,
|
||||
linear_descriptions,
|
||||
complete_descriptions,
|
||||
shared,
|
||||
srgb_gamma22,
|
||||
srgb_linear,
|
||||
windows_scrgb,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn srgb_gamma22(&self) -> &Rc<ColorDescription> {
|
||||
&self.srgb_gamma22
|
||||
}
|
||||
|
||||
pub fn srgb_linear(&self) -> &Rc<ColorDescription> {
|
||||
&self.srgb_linear
|
||||
}
|
||||
|
||||
pub fn windows_scrgb(&self) -> &Rc<ColorDescription> {
|
||||
&self.windows_scrgb
|
||||
}
|
||||
|
||||
pub fn get_description(
|
||||
self: &Rc<Self>,
|
||||
named_primaries: Option<NamedPrimaries>,
|
||||
primaries: Primaries,
|
||||
luminance: Luminance,
|
||||
eotf: Eotf,
|
||||
target_primaries: Primaries,
|
||||
target_luminance: TargetLuminance,
|
||||
max_cll: Option<F64>,
|
||||
max_fall: Option<F64>,
|
||||
) -> Rc<ColorDescription> {
|
||||
get_description(
|
||||
&self.shared,
|
||||
&self.linear_descriptions,
|
||||
&self.complete_descriptions,
|
||||
&self.linear_ids,
|
||||
named_primaries,
|
||||
primaries,
|
||||
luminance,
|
||||
eotf,
|
||||
target_primaries,
|
||||
target_luminance,
|
||||
max_cll,
|
||||
max_fall,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn get_with_tf(
|
||||
self: &Rc<Self>,
|
||||
cd: &Rc<ColorDescription>,
|
||||
eotf: Eotf,
|
||||
) -> Rc<ColorDescription> {
|
||||
get_description2(
|
||||
&self.shared,
|
||||
&cd.linear,
|
||||
&self.complete_descriptions,
|
||||
cd.named_primaries,
|
||||
eotf,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn get_description(
|
||||
shared: &Rc<Shared>,
|
||||
linear_descriptions: &CopyHashMap<LinearDescriptionKey, Weak<LinearColorDescription>>,
|
||||
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
|
||||
linear_ids: &LinearColorDescriptionIds,
|
||||
named_primaries: Option<NamedPrimaries>,
|
||||
primaries: Primaries,
|
||||
luminance: Luminance,
|
||||
eotf: Eotf,
|
||||
target_primaries: Primaries,
|
||||
target_luminance: TargetLuminance,
|
||||
max_cll: Option<F64>,
|
||||
max_fall: Option<F64>,
|
||||
) -> Rc<ColorDescription> {
|
||||
macro_rules! gc {
|
||||
($d:ident, $i:expr) => {
|
||||
if $d.len() > 16 && $i.get() * 2 > $d.len() {
|
||||
$d.lock().retain(|_, d| d.strong_count() > 0);
|
||||
$i.set(0);
|
||||
}
|
||||
};
|
||||
}
|
||||
gc!(linear_descriptions, &shared.dead_linear);
|
||||
gc!(complete_descriptions, &shared.dead_complete);
|
||||
let key = LinearDescriptionKey {
|
||||
primaries,
|
||||
luminance,
|
||||
target_primaries,
|
||||
target_luminance,
|
||||
max_cll,
|
||||
max_fall,
|
||||
};
|
||||
if let Some(d) = linear_descriptions.get(&key) {
|
||||
if let Some(d) = d.upgrade() {
|
||||
return get_description2(shared, &d, complete_descriptions, named_primaries, eotf);
|
||||
}
|
||||
shared.dead_linear.fetch_sub(1);
|
||||
}
|
||||
let (xyz_from_local, local_from_xyz) = primaries.matrices();
|
||||
let d = Rc::new(LinearColorDescription {
|
||||
id: linear_ids.next(),
|
||||
primaries,
|
||||
xyz_from_local,
|
||||
local_from_xyz,
|
||||
luminance,
|
||||
target_primaries,
|
||||
target_luminance,
|
||||
max_cll,
|
||||
max_fall,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
linear_descriptions.set(key, Rc::downgrade(&d));
|
||||
let key = CompleteDescriptionKey {
|
||||
linear: d.id,
|
||||
named_primaries,
|
||||
eotf,
|
||||
};
|
||||
let d = Rc::new(ColorDescription {
|
||||
id: shared.complete_ids.next(),
|
||||
linear: d,
|
||||
named_primaries,
|
||||
eotf,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
complete_descriptions.set(key, Rc::downgrade(&d));
|
||||
d
|
||||
}
|
||||
|
||||
fn get_description2(
|
||||
shared: &Rc<Shared>,
|
||||
ld: &Rc<LinearColorDescription>,
|
||||
complete_descriptions: &CopyHashMap<CompleteDescriptionKey, Weak<ColorDescription>>,
|
||||
named_primaries: Option<NamedPrimaries>,
|
||||
eotf: Eotf,
|
||||
) -> Rc<ColorDescription> {
|
||||
let key = CompleteDescriptionKey {
|
||||
linear: ld.id,
|
||||
named_primaries,
|
||||
eotf,
|
||||
};
|
||||
if let Some(d) = complete_descriptions.get(&key) {
|
||||
if let Some(d) = d.upgrade() {
|
||||
return d;
|
||||
}
|
||||
shared.dead_complete.fetch_sub(1);
|
||||
}
|
||||
let d = Rc::new(ColorDescription {
|
||||
id: shared.complete_ids.next(),
|
||||
linear: ld.clone(),
|
||||
named_primaries,
|
||||
eotf,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
complete_descriptions.set(key, Rc::downgrade(&d));
|
||||
d
|
||||
}
|
||||
111
cmm/src/cmm_primaries.rs
Normal file
111
cmm/src/cmm_primaries.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
use {jay_utils::ordered_float::F64, std::hash::Hash};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum NamedPrimaries {
|
||||
Srgb,
|
||||
PalM,
|
||||
Pal,
|
||||
Ntsc,
|
||||
GenericFilm,
|
||||
Bt2020,
|
||||
Cie1931Xyz,
|
||||
DciP3,
|
||||
DisplayP3,
|
||||
AdobeRgb,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Primaries {
|
||||
pub r: (F64, F64),
|
||||
pub g: (F64, F64),
|
||||
pub b: (F64, F64),
|
||||
pub wp: (F64, F64),
|
||||
}
|
||||
|
||||
impl Primaries {
|
||||
pub const SRGB: Self = Self {
|
||||
r: (F64(0.64), F64(0.33)),
|
||||
g: (F64(0.3), F64(0.6)),
|
||||
b: (F64(0.15), F64(0.06)),
|
||||
wp: (F64(0.3127), F64(0.3290)),
|
||||
};
|
||||
|
||||
pub const PAL_M: Self = Self {
|
||||
r: (F64(0.67), F64(0.33)),
|
||||
g: (F64(0.21), F64(0.71)),
|
||||
b: (F64(0.14), F64(0.08)),
|
||||
wp: (F64(0.310), F64(0.316)),
|
||||
};
|
||||
|
||||
pub const PAL: Self = Self {
|
||||
r: (F64(0.64), F64(0.33)),
|
||||
g: (F64(0.29), F64(0.60)),
|
||||
b: (F64(0.15), F64(0.06)),
|
||||
wp: (F64(0.3127), F64(0.3290)),
|
||||
};
|
||||
|
||||
pub const NTSC: Self = Self {
|
||||
r: (F64(0.630), F64(0.340)),
|
||||
g: (F64(0.310), F64(0.595)),
|
||||
b: (F64(0.155), F64(0.070)),
|
||||
wp: (F64(0.3127), F64(0.3290)),
|
||||
};
|
||||
|
||||
pub const GENERIC_FILM: Self = Self {
|
||||
r: (F64(0.681), F64(0.319)),
|
||||
g: (F64(0.243), F64(0.692)),
|
||||
b: (F64(0.145), F64(0.049)),
|
||||
wp: (F64(0.310), F64(0.316)),
|
||||
};
|
||||
|
||||
pub const BT2020: Self = Self {
|
||||
r: (F64(0.708), F64(0.292)),
|
||||
g: (F64(0.170), F64(0.797)),
|
||||
b: (F64(0.131), F64(0.046)),
|
||||
wp: (F64(0.3127), F64(0.3290)),
|
||||
};
|
||||
|
||||
pub const CIE1931_XYZ: Self = Self {
|
||||
r: (F64(1.0), F64(0.0)),
|
||||
g: (F64(0.0), F64(1.0)),
|
||||
b: (F64(0.0), F64(0.0)),
|
||||
wp: (F64(1.0 / 3.0), F64(1.0 / 3.0)),
|
||||
};
|
||||
|
||||
pub const DCI_P3: Self = Self {
|
||||
r: (F64(0.680), F64(0.320)),
|
||||
g: (F64(0.265), F64(0.690)),
|
||||
b: (F64(0.150), F64(0.060)),
|
||||
wp: (F64(0.314), F64(0.351)),
|
||||
};
|
||||
|
||||
pub const DISPLAY_P3: Self = Self {
|
||||
r: (F64(0.680), F64(0.320)),
|
||||
g: (F64(0.265), F64(0.690)),
|
||||
b: (F64(0.150), F64(0.060)),
|
||||
wp: (F64(0.3127), F64(0.3290)),
|
||||
};
|
||||
|
||||
pub const ADOBE_RGB: Self = Self {
|
||||
r: (F64(0.64), F64(0.33)),
|
||||
g: (F64(0.21), F64(0.71)),
|
||||
b: (F64(0.15), F64(0.06)),
|
||||
wp: (F64(0.3127), F64(0.3290)),
|
||||
};
|
||||
}
|
||||
impl NamedPrimaries {
|
||||
pub const fn primaries(self) -> Primaries {
|
||||
match self {
|
||||
NamedPrimaries::Srgb => Primaries::SRGB,
|
||||
NamedPrimaries::PalM => Primaries::PAL_M,
|
||||
NamedPrimaries::Pal => Primaries::PAL,
|
||||
NamedPrimaries::Ntsc => Primaries::NTSC,
|
||||
NamedPrimaries::GenericFilm => Primaries::GENERIC_FILM,
|
||||
NamedPrimaries::Bt2020 => Primaries::BT2020,
|
||||
NamedPrimaries::Cie1931Xyz => Primaries::CIE1931_XYZ,
|
||||
NamedPrimaries::DciP3 => Primaries::DCI_P3,
|
||||
NamedPrimaries::DisplayP3 => Primaries::DISPLAY_P3,
|
||||
NamedPrimaries::AdobeRgb => Primaries::ADOBE_RGB,
|
||||
}
|
||||
}
|
||||
}
|
||||
28
cmm/src/cmm_render_intent.rs
Normal file
28
cmm/src/cmm_render_intent.rs
Normal file
|
|
@ -0,0 +1,28 @@
|
|||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)]
|
||||
pub enum RenderIntent {
|
||||
#[default]
|
||||
Perceptual,
|
||||
Relative,
|
||||
RelativeBpc,
|
||||
AbsoluteNoAdaptation,
|
||||
}
|
||||
|
||||
impl RenderIntent {
|
||||
pub fn black_point_compensation(self) -> bool {
|
||||
match self {
|
||||
RenderIntent::Perceptual => true,
|
||||
RenderIntent::RelativeBpc => true,
|
||||
RenderIntent::Relative => false,
|
||||
RenderIntent::AbsoluteNoAdaptation => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn bradford_adjustment(self) -> bool {
|
||||
match self {
|
||||
RenderIntent::Perceptual => true,
|
||||
RenderIntent::RelativeBpc => true,
|
||||
RenderIntent::Relative => true,
|
||||
RenderIntent::AbsoluteNoAdaptation => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
214
cmm/src/cmm_tests.rs
Normal file
214
cmm/src/cmm_tests.rs
Normal file
|
|
@ -0,0 +1,214 @@
|
|||
mod matrices {
|
||||
use {crate::cmm_primaries::Primaries, jay_utils::ordered_float::F64};
|
||||
|
||||
fn check(primaries: Primaries, expected: [[f64; 4]; 3]) {
|
||||
let (ltg, gtl) = primaries.matrices();
|
||||
println!("{:#?}", ltg);
|
||||
assert!((ltg.0[0][0].0 - expected[0][0]).abs() < 0.001);
|
||||
assert!((ltg.0[0][1].0 - expected[0][1]).abs() < 0.001);
|
||||
assert!((ltg.0[0][2].0 - expected[0][2]).abs() < 0.001);
|
||||
assert!((ltg.0[0][3].0 - expected[0][3]).abs() < 0.001);
|
||||
assert!((ltg.0[1][0].0 - expected[1][0]).abs() < 0.001);
|
||||
assert!((ltg.0[1][1].0 - expected[1][1]).abs() < 0.001);
|
||||
assert!((ltg.0[1][2].0 - expected[1][2]).abs() < 0.001);
|
||||
assert!((ltg.0[1][3].0 - expected[1][3]).abs() < 0.001);
|
||||
assert!((ltg.0[2][0].0 - expected[2][0]).abs() < 0.001);
|
||||
assert!((ltg.0[2][1].0 - expected[2][1]).abs() < 0.001);
|
||||
assert!((ltg.0[2][2].0 - expected[2][2]).abs() < 0.001);
|
||||
assert!((ltg.0[2][3].0 - expected[2][3]).abs() < 0.001);
|
||||
let roundtrip = gtl * ltg;
|
||||
assert!((roundtrip.0[0][0].0 - 1.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[0][1].0 - 0.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[0][2].0 - 0.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[0][3].0 - 0.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[1][0].0 - 0.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[1][1].0 - 1.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[1][2].0 - 0.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[1][3].0 - 0.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[2][0].0 - 0.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[2][1].0 - 0.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[2][2].0 - 1.0).abs() < 0.001);
|
||||
assert!((roundtrip.0[2][3].0 - 0.0).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn srgb() {
|
||||
check(
|
||||
Primaries::SRGB,
|
||||
[
|
||||
[0.4124564, 0.3575761, 0.1804375, 0.0],
|
||||
[0.2126729, 0.7151522, 0.0721750, 0.0],
|
||||
[0.0193339, 0.1191920, 0.9503041, 0.0],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cie1931_xyz() {
|
||||
check(
|
||||
Primaries::CIE1931_XYZ,
|
||||
[
|
||||
[1.0, 0.0, 0.0, 0.0],
|
||||
[0.0, 1.0, 0.0, 0.0],
|
||||
[0.0, 0.0, 1.0, 0.0],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn adobe_rgb() {
|
||||
check(
|
||||
Primaries::ADOBE_RGB,
|
||||
[
|
||||
[0.5767309, 0.1855540, 0.1881852, 0.0],
|
||||
[0.2973769, 0.6273491, 0.0752741, 0.0],
|
||||
[0.0270343, 0.0706872, 0.9911085, 0.0],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn apple_rgb() {
|
||||
check(
|
||||
Primaries {
|
||||
r: (F64(0.625), F64(0.34)),
|
||||
g: (F64(0.28), F64(0.595)),
|
||||
b: (F64(0.155), F64(0.07)),
|
||||
wp: (F64(0.31271), F64(0.32902)),
|
||||
},
|
||||
[
|
||||
[0.4497288, 0.3162486, 0.1844926, 0.0],
|
||||
[0.2446525, 0.6720283, 0.0833192, 0.0],
|
||||
[0.0251848, 0.1411824, 0.9224628, 0.0],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bt2020() {
|
||||
check(
|
||||
Primaries::BT2020,
|
||||
[
|
||||
[0.636958, 0.144617, 0.168881, 0.0],
|
||||
[0.262700, 0.677998, 0.059302, 0.0],
|
||||
[0.000000, 0.028073, 1.060985, 0.0],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pal() {
|
||||
check(
|
||||
Primaries::PAL,
|
||||
[
|
||||
[0.4306190, 0.3415419, 0.1783091, 0.0],
|
||||
[0.2220379, 0.7066384, 0.0713236, 0.0],
|
||||
[0.0201853, 0.1295504, 0.9390944, 0.0],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dci_p3() {
|
||||
check(
|
||||
Primaries::DCI_P3,
|
||||
[
|
||||
[0.445170, 0.277134, 0.172283, 0.0],
|
||||
[0.209492, 0.721595, 0.068913, 0.0],
|
||||
[-0.000000, 0.047061, 0.907355, 0.0],
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn display_p3() {
|
||||
check(
|
||||
Primaries::DISPLAY_P3,
|
||||
[
|
||||
[0.486571, 0.265668, 0.198217, 0.0],
|
||||
[0.228975, 0.691739, 0.079287, 0.0],
|
||||
[-0.000000, 0.045113, 1.043944, 0.0],
|
||||
],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod transforms {
|
||||
use crate::{
|
||||
cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager,
|
||||
cmm_primaries::Primaries, cmm_render_intent::RenderIntent,
|
||||
};
|
||||
|
||||
fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) {
|
||||
let manager = ColorManager::new();
|
||||
let d = |p| {
|
||||
manager.get_description(
|
||||
None,
|
||||
p,
|
||||
Luminance::SRGB,
|
||||
Eotf::Linear,
|
||||
p,
|
||||
Luminance::SRGB.to_target(),
|
||||
None,
|
||||
None,
|
||||
)
|
||||
};
|
||||
let d1 = d(p1);
|
||||
let d2 = d(p2);
|
||||
let m = d1
|
||||
.linear
|
||||
.color_transform(&d2.linear, RenderIntent::Perceptual);
|
||||
println!("{:#?}", m);
|
||||
assert!((m.0[0][0].0 - expected[0][0]).abs() < 0.001);
|
||||
assert!((m.0[0][1].0 - expected[0][1]).abs() < 0.001);
|
||||
assert!((m.0[0][2].0 - expected[0][2]).abs() < 0.001);
|
||||
assert!((m.0[0][3].0 - expected[0][3]).abs() < 0.001);
|
||||
assert!((m.0[1][0].0 - expected[1][0]).abs() < 0.001);
|
||||
assert!((m.0[1][1].0 - expected[1][1]).abs() < 0.001);
|
||||
assert!((m.0[1][2].0 - expected[1][2]).abs() < 0.001);
|
||||
assert!((m.0[1][3].0 - expected[1][3]).abs() < 0.001);
|
||||
assert!((m.0[2][0].0 - expected[2][0]).abs() < 0.001);
|
||||
assert!((m.0[2][1].0 - expected[2][1]).abs() < 0.001);
|
||||
assert!((m.0[2][2].0 - expected[2][2]).abs() < 0.001);
|
||||
assert!((m.0[2][3].0 - expected[2][3]).abs() < 0.001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn srgb_to_bt2020() {
|
||||
check(
|
||||
Primaries::SRGB,
|
||||
Primaries::BT2020,
|
||||
[
|
||||
[0.627404, 0.329283, 0.043313, 0.0],
|
||||
[0.069097, 0.919540, 0.011362, 0.0],
|
||||
[0.016391, 0.088013, 0.895595, 0.0],
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bt2020_to_srgb() {
|
||||
check(
|
||||
Primaries::BT2020,
|
||||
Primaries::SRGB,
|
||||
[
|
||||
[1.660491, -0.587641, -0.072850, 0.0],
|
||||
[-0.124550, 1.132900, -0.008349, 0.0],
|
||||
[-0.018151, -0.100579, 1.118730, 0.0],
|
||||
],
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn srgb_to_dci_p3() {
|
||||
check(
|
||||
Primaries::SRGB,
|
||||
Primaries::DCI_P3,
|
||||
[
|
||||
[0.868580, 0.128919, 0.002501, 0.0],
|
||||
[0.034540, 0.961811, 0.003648, 0.0],
|
||||
[0.016771, 0.071040, 0.912189, 0.0],
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
235
cmm/src/cmm_transform.rs
Normal file
235
cmm/src/cmm_transform.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
use {
|
||||
crate::cmm_primaries::Primaries,
|
||||
jay_utils::ordered_float::F64,
|
||||
std::{
|
||||
fmt,
|
||||
fmt::{Debug, Formatter},
|
||||
hash::{Hash, Hasher},
|
||||
marker::PhantomData,
|
||||
ops::{Mul, MulAssign},
|
||||
},
|
||||
};
|
||||
|
||||
pub struct ColorMatrix<To = Local, From = Local>(pub [[F64; 4]; 3], PhantomData<(To, From)>);
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Local;
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Xyz;
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Bradford;
|
||||
|
||||
impl<T, U> Copy for ColorMatrix<T, U> {}
|
||||
|
||||
impl<T, U> Clone for ColorMatrix<T, U> {
|
||||
fn clone(&self) -> Self {
|
||||
*self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> PartialEq<Self> for ColorMatrix<T, U> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0 == other.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Eq for ColorMatrix<T, U> {}
|
||||
|
||||
impl<T, U> Hash for ColorMatrix<T, U> {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Debug for ColorMatrix<T, U> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_tuple("ColorMatrix")
|
||||
.field(&format_matrix(&self.0))
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
fn format_matrix<'a>(m: &'a [[F64; 4]; 3]) -> impl Debug + use<'a> {
|
||||
fmt::from_fn(move |f| {
|
||||
let iter = m
|
||||
.iter()
|
||||
.copied()
|
||||
.chain(Some([F64(0.0), F64(0.0), F64(0.0), F64(1.0)]))
|
||||
.enumerate();
|
||||
if f.alternate() {
|
||||
for (idx, row) in iter {
|
||||
if idx > 0 {
|
||||
f.write_str("\n")?;
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"{:7.4} {:7.4} {:7.4} {:7.4}",
|
||||
row[0], row[1], row[2], row[3]
|
||||
)?;
|
||||
}
|
||||
} else {
|
||||
f.write_str("[")?;
|
||||
for (idx, row) in iter {
|
||||
if idx > 0 {
|
||||
f.write_str(", ")?;
|
||||
}
|
||||
write!(
|
||||
f,
|
||||
"[{:.4}, {:.4}, {:.4}, {:.4}]",
|
||||
row[0], row[1], row[2], row[3]
|
||||
)?;
|
||||
}
|
||||
f.write_str("]")?;
|
||||
}
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
impl<T, U, V> Mul<ColorMatrix<U, T>> for ColorMatrix<V, U> {
|
||||
type Output = ColorMatrix<V, T>;
|
||||
|
||||
fn mul(self, rhs: ColorMatrix<U, T>) -> Self::Output {
|
||||
let a = &self.0;
|
||||
let b = &rhs.0;
|
||||
macro_rules! mul {
|
||||
($ar:expr, $bc:expr) => {
|
||||
a[$ar][0] * b[0][$bc] + a[$ar][1] * b[1][$bc] + a[$ar][2] * b[2][$bc]
|
||||
};
|
||||
}
|
||||
let m = [
|
||||
[mul!(0, 0), mul!(0, 1), mul!(0, 2), mul!(0, 3) + a[0][3]],
|
||||
[mul!(1, 0), mul!(1, 1), mul!(1, 2), mul!(1, 3) + a[1][3]],
|
||||
[mul!(2, 0), mul!(2, 1), mul!(2, 2), mul!(2, 3) + a[2][3]],
|
||||
];
|
||||
ColorMatrix(m, PhantomData)
|
||||
}
|
||||
}
|
||||
|
||||
impl<U, V> MulAssign<ColorMatrix<U, U>> for ColorMatrix<V, U> {
|
||||
fn mul_assign(&mut self, rhs: ColorMatrix<U, U>) {
|
||||
*self = *self * rhs;
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> Mul<[f64; 3]> for ColorMatrix<T, U> {
|
||||
type Output = [f64; 3];
|
||||
|
||||
fn mul(self, rhs: [f64; 3]) -> Self::Output {
|
||||
let a = &self.0;
|
||||
macro_rules! mul {
|
||||
($ar:expr) => {
|
||||
a[$ar][0].0 * rhs[0] + a[$ar][1].0 * rhs[1] + a[$ar][2].0 * rhs[2]
|
||||
};
|
||||
}
|
||||
[mul!(0), mul!(1), mul!(2)]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, U> ColorMatrix<T, U> {
|
||||
pub const fn new(m: [[f64; 4]; 3]) -> Self {
|
||||
let m = [
|
||||
[F64(m[0][0]), F64(m[0][1]), F64(m[0][2]), F64(m[0][3])],
|
||||
[F64(m[1][0]), F64(m[1][1]), F64(m[1][2]), F64(m[1][3])],
|
||||
[F64(m[2][0]), F64(m[2][1]), F64(m[2][2]), F64(m[2][3])],
|
||||
];
|
||||
Self(m, PhantomData)
|
||||
}
|
||||
|
||||
pub const fn to_f32(&self) -> [[f32; 4]; 4] {
|
||||
let m = &self.0;
|
||||
macro_rules! map {
|
||||
($r:expr, $c:expr) => {
|
||||
m[$r][$c].0 as f32
|
||||
};
|
||||
}
|
||||
[
|
||||
[map!(0, 0), map!(0, 1), map!(0, 2), map!(0, 3)],
|
||||
[map!(1, 0), map!(1, 1), map!(1, 2), map!(1, 3)],
|
||||
[map!(2, 0), map!(2, 1), map!(2, 2), map!(2, 3)],
|
||||
[0.0, 0.0, 0.0, 1.0],
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
impl ColorMatrix<Bradford, Xyz> {
|
||||
const BFD: Self = Self::new([
|
||||
[0.8951, 0.2664, -0.1614, 0.0],
|
||||
[-0.7502, 1.7135, 0.0367, 0.0],
|
||||
[0.0389, -0.0685, 1.0296, 0.0],
|
||||
]);
|
||||
}
|
||||
|
||||
impl ColorMatrix<Xyz, Bradford> {
|
||||
const BFD_INV: Self = Self::new([
|
||||
[0.9870, -0.1471, 0.1600, 0.0],
|
||||
[0.4323, 0.5184, 0.0493, 0.0],
|
||||
[-0.0085, 0.04, 0.9685, 0.0],
|
||||
]);
|
||||
}
|
||||
|
||||
#[expect(non_snake_case)]
|
||||
pub fn bradford_adjustment(w_from: (F64, F64), w_to: (F64, F64)) -> ColorMatrix<Xyz, Xyz> {
|
||||
let (F64(x_from), F64(y_from)) = w_from;
|
||||
let (F64(x_to), F64(y_to)) = w_to;
|
||||
let X_from = x_from / y_from;
|
||||
let Z_from = (1.0 - x_from - y_from) / y_from;
|
||||
let X_to = x_to / y_to;
|
||||
let Z_to = (1.0 - x_to - y_to) / y_to;
|
||||
let [R_from, G_from, B_from] = ColorMatrix::BFD * [X_from, 1.0, Z_from];
|
||||
let [R_to, G_to, B_to] = ColorMatrix::BFD * [X_to, 1.0, Z_to];
|
||||
let adj = ColorMatrix::new([
|
||||
[R_to / R_from, 0.0, 0.0, 0.0],
|
||||
[0.0, G_to / G_from, 0.0, 0.0],
|
||||
[0.0, 0.0, B_to / B_from, 0.0],
|
||||
]);
|
||||
ColorMatrix::BFD_INV * adj * ColorMatrix::BFD
|
||||
}
|
||||
|
||||
impl Primaries {
|
||||
#[expect(non_snake_case)]
|
||||
pub const fn matrices(&self) -> (ColorMatrix<Xyz, Local>, ColorMatrix<Local, Xyz>) {
|
||||
let (F64(xw), F64(yw)) = self.wp;
|
||||
let Xw = xw / yw;
|
||||
let Zw = (1.0 - xw - yw) / yw;
|
||||
let (F64(xr), F64(yr)) = self.r;
|
||||
let (F64(xg), F64(yg)) = self.g;
|
||||
let (F64(xb), F64(yb)) = self.b;
|
||||
let zr = 1.0 - xr - yr;
|
||||
let zg = 1.0 - xg - yg;
|
||||
let zb = 1.0 - xb - yb;
|
||||
let srx = yg * zb - zg * yb;
|
||||
let sry = zg * xb - xg * zb;
|
||||
let srz = xg * yb - yg * xb;
|
||||
let sgx = zr * yb - yr * zb;
|
||||
let sgz = yr * xb - xr * yb;
|
||||
let sgy = xr * zb - zr * xb;
|
||||
let sbx = yr * zg - zr * yg;
|
||||
let sby = zr * xg - xr * zg;
|
||||
let sbz = xr * yg - yr * xg;
|
||||
let det = srz + sgz + sbz;
|
||||
let sr = srx * Xw + sry + srz * Zw;
|
||||
let sg = sgx * Xw + sgy + sgz * Zw;
|
||||
let sb = sbx * Xw + sby + sbz * Zw;
|
||||
let det_inv = 1.0 / det;
|
||||
let sr_inv = 1.0 / sr;
|
||||
let sg_inv = 1.0 / sg;
|
||||
let sb_inv = 1.0 / sb;
|
||||
let srp = sr * det_inv;
|
||||
let sgp = sg * det_inv;
|
||||
let sbp = sb * det_inv;
|
||||
let XYZ_from_local = [
|
||||
[srp * xr, sgp * xg, sbp * xb, 0.0],
|
||||
[srp * yr, sgp * yg, sbp * yb, 0.0],
|
||||
[srp * zr, sgp * zg, sbp * zb, 0.0],
|
||||
];
|
||||
let local_from_XYZ = [
|
||||
[srx * sr_inv, sry * sr_inv, srz * sr_inv, 0.0],
|
||||
[sgx * sg_inv, sgy * sg_inv, sgz * sg_inv, 0.0],
|
||||
[sbx * sb_inv, sby * sb_inv, sbz * sb_inv, 0.0],
|
||||
];
|
||||
(
|
||||
ColorMatrix::new(XYZ_from_local),
|
||||
ColorMatrix::new(local_from_XYZ),
|
||||
)
|
||||
}
|
||||
}
|
||||
53
cmm/src/lib.rs
Normal file
53
cmm/src/lib.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
|||
macro_rules! linear_ids {
|
||||
($ids:ident, $id:ident, $ty:ty $(,)?) => {
|
||||
#[derive(Debug)]
|
||||
pub struct $ids {
|
||||
next: jay_utils::numcell::NumCell<$ty>,
|
||||
}
|
||||
|
||||
impl Default for $ids {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
next: jay_utils::numcell::NumCell::new(1),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl $ids {
|
||||
pub fn next(&self) -> $id {
|
||||
$id(self.next.fetch_add(1))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Ord, PartialOrd)]
|
||||
pub struct $id($ty);
|
||||
|
||||
impl $id {
|
||||
#[allow(dead_code)]
|
||||
pub fn raw(&self) -> $ty {
|
||||
self.0
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn from_raw(id: $ty) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for $id {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
std::fmt::Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub mod cmm_description;
|
||||
pub mod cmm_eotf;
|
||||
pub mod cmm_luminance;
|
||||
pub mod cmm_manager;
|
||||
pub mod cmm_primaries;
|
||||
pub mod cmm_render_intent;
|
||||
#[cfg(test)]
|
||||
mod cmm_tests;
|
||||
pub mod cmm_transform;
|
||||
Loading…
Add table
Add a link
Reference in a new issue