cmm: add color-management module
This commit is contained in:
parent
ed1955d3d1
commit
82085a3858
27 changed files with 1182 additions and 20 deletions
|
|
@ -1,7 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
cli::{GlobalArgs, color::parse_color, duration::parse_duration},
|
||||
theme::TransferFunction,
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
tools::tool_client::{ToolClient, with_tool_client},
|
||||
wire::jay_damage_tracking::{SetVisualizerColor, SetVisualizerDecay, SetVisualizerEnabled},
|
||||
},
|
||||
|
|
|
|||
8
src/cmm.rs
Normal file
8
src/cmm.rs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
pub mod cmm_description;
|
||||
pub mod cmm_luminance;
|
||||
pub mod cmm_manager;
|
||||
pub mod cmm_primaries;
|
||||
#[cfg(test)]
|
||||
mod cmm_tests;
|
||||
pub mod cmm_transfer_function;
|
||||
pub mod cmm_transform;
|
||||
81
src/cmm/cmm_description.rs
Normal file
81
src/cmm/cmm_description.rs
Normal file
|
|
@ -0,0 +1,81 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::{
|
||||
cmm_luminance::{Luminance, white_balance},
|
||||
cmm_manager::Shared,
|
||||
cmm_primaries::{NamedPrimaries, Primaries},
|
||||
cmm_transfer_function::TransferFunction,
|
||||
cmm_transform::{ColorMatrix, Local, Xyz, bradford_adjustment},
|
||||
},
|
||||
utils::free_list::FreeList,
|
||||
},
|
||||
std::rc::Rc,
|
||||
};
|
||||
|
||||
linear_ids!(LinearColorDescriptionIds, LinearColorDescriptionId, u64);
|
||||
|
||||
pub type ColorDescriptionIds = FreeList<ColorDescriptionId, 3>;
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct ColorDescriptionId(u32);
|
||||
|
||||
impl From<u32> for ColorDescriptionId {
|
||||
fn from(value: u32) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<ColorDescriptionId> for u32 {
|
||||
fn from(value: ColorDescriptionId) -> Self {
|
||||
value.0
|
||||
}
|
||||
}
|
||||
|
||||
#[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(super) shared: Rc<Shared>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ColorDescription {
|
||||
pub id: ColorDescriptionId,
|
||||
#[expect(dead_code)]
|
||||
pub linear: Rc<LinearColorDescription>,
|
||||
#[expect(dead_code)]
|
||||
pub named_primaries: Option<NamedPrimaries>,
|
||||
#[expect(dead_code)]
|
||||
pub transfer_function: TransferFunction,
|
||||
pub(super) shared: Rc<Shared>,
|
||||
}
|
||||
|
||||
impl LinearColorDescription {
|
||||
#[expect(dead_code)]
|
||||
pub fn color_transform(&self, target: &Self) -> ColorMatrix {
|
||||
let mut mat = target.local_from_xyz;
|
||||
if self.luminance != target.luminance {
|
||||
mat *= white_balance(&self.luminance, &target.luminance, target.primaries.wp);
|
||||
}
|
||||
if self.primaries.wp != target.primaries.wp {
|
||||
mat *= bradford_adjustment(self.primaries.wp, target.primaries.wp);
|
||||
}
|
||||
mat * self.xyz_from_local
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
self.shared.complete_ids.release(self.id);
|
||||
}
|
||||
}
|
||||
72
src/cmm/cmm_luminance.rs
Normal file
72
src/cmm/cmm_luminance.rs
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
use crate::{
|
||||
cmm::cmm_transform::{ColorMatrix, Xyz},
|
||||
utils::ordered_float::F64,
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub struct Luminance {
|
||||
pub min: F64,
|
||||
pub max: F64,
|
||||
pub white: F64,
|
||||
}
|
||||
|
||||
impl Luminance {
|
||||
pub const SRGB: Self = Self {
|
||||
min: F64(0.2),
|
||||
max: F64(80.0),
|
||||
white: F64(80.0),
|
||||
};
|
||||
|
||||
#[expect(dead_code)]
|
||||
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),
|
||||
};
|
||||
|
||||
#[expect(dead_code)]
|
||||
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 Default for Luminance {
|
||||
fn default() -> Self {
|
||||
Self::SRGB
|
||||
}
|
||||
}
|
||||
|
||||
#[expect(non_snake_case)]
|
||||
pub fn white_balance(from: &Luminance, to: &Luminance, w_to: (F64, F64)) -> ColorMatrix<Xyz, Xyz> {
|
||||
let a = ((from.max - from.min) / (to.max - to.min) * (to.white - from.min)
|
||||
/ (from.white - from.min))
|
||||
.0;
|
||||
let d = ((from.min - to.min) / (to.max - to.min)).0.max(0.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],
|
||||
])
|
||||
}
|
||||
204
src/cmm/cmm_manager.rs
Normal file
204
src/cmm/cmm_manager.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::{
|
||||
cmm_description::{
|
||||
ColorDescription, ColorDescriptionIds, LinearColorDescription,
|
||||
LinearColorDescriptionId, LinearColorDescriptionIds,
|
||||
},
|
||||
cmm_luminance::Luminance,
|
||||
cmm_primaries::{NamedPrimaries, Primaries},
|
||||
cmm_transfer_function::TransferFunction,
|
||||
},
|
||||
utils::{copyhashmap::CopyHashMap, numcell::NumCell},
|
||||
},
|
||||
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_srgb: 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,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
struct CompleteDescriptionKey {
|
||||
linear: LinearColorDescriptionId,
|
||||
named_primaries: Option<NamedPrimaries>,
|
||||
transfer_function: TransferFunction,
|
||||
}
|
||||
|
||||
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.acquire();
|
||||
let srgb_srgb = get_description(
|
||||
&shared,
|
||||
&linear_descriptions,
|
||||
&complete_descriptions,
|
||||
&linear_ids,
|
||||
Some(NamedPrimaries::Srgb),
|
||||
Primaries::SRGB,
|
||||
Luminance::SRGB,
|
||||
TransferFunction::Srgb,
|
||||
);
|
||||
let srgb_linear = get_description(
|
||||
&shared,
|
||||
&linear_descriptions,
|
||||
&complete_descriptions,
|
||||
&linear_ids,
|
||||
Some(NamedPrimaries::Srgb),
|
||||
Primaries::SRGB,
|
||||
Luminance::SRGB,
|
||||
TransferFunction::Linear,
|
||||
);
|
||||
let windows_scrgb = get_description(
|
||||
&shared,
|
||||
&linear_descriptions,
|
||||
&complete_descriptions,
|
||||
&linear_ids,
|
||||
Some(NamedPrimaries::Srgb),
|
||||
Primaries::SRGB,
|
||||
Luminance::WINDOWS_SCRGB,
|
||||
TransferFunction::Linear,
|
||||
);
|
||||
Rc::new(Self {
|
||||
linear_ids,
|
||||
linear_descriptions,
|
||||
complete_descriptions,
|
||||
shared,
|
||||
srgb_srgb,
|
||||
srgb_linear,
|
||||
windows_scrgb,
|
||||
})
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn srgb_srgb(&self) -> &Rc<ColorDescription> {
|
||||
&self.srgb_srgb
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn srgb_linear(&self) -> &Rc<ColorDescription> {
|
||||
&self.srgb_linear
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn windows_scrgb(&self) -> &Rc<ColorDescription> {
|
||||
&self.windows_scrgb
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
pub fn get_description(
|
||||
self: &Rc<Self>,
|
||||
named_primaries: Option<NamedPrimaries>,
|
||||
primaries: Primaries,
|
||||
luminance: Luminance,
|
||||
transfer_function: TransferFunction,
|
||||
) -> Rc<ColorDescription> {
|
||||
get_description(
|
||||
&self.shared,
|
||||
&self.linear_descriptions,
|
||||
&self.complete_descriptions,
|
||||
&self.linear_ids,
|
||||
named_primaries,
|
||||
primaries,
|
||||
luminance,
|
||||
transfer_function,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
transfer_function: TransferFunction,
|
||||
) -> 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,
|
||||
};
|
||||
if let Some(d) = linear_descriptions.get(&key) {
|
||||
if let Some(d) = d.upgrade() {
|
||||
let key = CompleteDescriptionKey {
|
||||
linear: d.id,
|
||||
named_primaries,
|
||||
transfer_function,
|
||||
};
|
||||
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.acquire(),
|
||||
linear: d,
|
||||
named_primaries,
|
||||
transfer_function,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
complete_descriptions.set(key, Rc::downgrade(&d));
|
||||
return d;
|
||||
}
|
||||
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,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
linear_descriptions.set(key, Rc::downgrade(&d));
|
||||
let key = CompleteDescriptionKey {
|
||||
linear: d.id,
|
||||
named_primaries,
|
||||
transfer_function,
|
||||
};
|
||||
let d = Rc::new(ColorDescription {
|
||||
id: shared.complete_ids.acquire(),
|
||||
linear: d,
|
||||
named_primaries,
|
||||
transfer_function,
|
||||
shared: shared.clone(),
|
||||
});
|
||||
complete_descriptions.set(key, Rc::downgrade(&d));
|
||||
d
|
||||
}
|
||||
121
src/cmm/cmm_primaries.rs
Normal file
121
src/cmm/cmm_primaries.rs
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
use {crate::utils::ordered_float::F64, std::hash::Hash};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)]
|
||||
pub enum NamedPrimaries {
|
||||
Srgb,
|
||||
#[expect(dead_code)]
|
||||
PalM,
|
||||
#[expect(dead_code)]
|
||||
Pal,
|
||||
#[expect(dead_code)]
|
||||
Ntsc,
|
||||
#[expect(dead_code)]
|
||||
GenericFilm,
|
||||
#[expect(dead_code)]
|
||||
Bt2020,
|
||||
#[expect(dead_code)]
|
||||
Cie1931Xyz,
|
||||
#[expect(dead_code)]
|
||||
DciP3,
|
||||
#[expect(dead_code)]
|
||||
DisplayP3,
|
||||
#[expect(dead_code)]
|
||||
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 {
|
||||
#[expect(dead_code)]
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
201
src/cmm/cmm_tests.rs
Normal file
201
src/cmm/cmm_tests.rs
Normal file
|
|
@ -0,0 +1,201 @@
|
|||
mod matrices {
|
||||
use crate::{cmm::cmm_primaries::Primaries, 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::{
|
||||
cmm_luminance::Luminance, cmm_manager::ColorManager, cmm_primaries::Primaries,
|
||||
cmm_transfer_function::TransferFunction,
|
||||
};
|
||||
|
||||
fn check(p1: Primaries, p2: Primaries, expected: [[f64; 4]; 3]) {
|
||||
let manager = ColorManager::new();
|
||||
let d = |p| manager.get_description(None, p, Luminance::SRGB, TransferFunction::Linear);
|
||||
let d1 = d(p1);
|
||||
let d2 = d(p2);
|
||||
let m = d1.linear.color_transform(&d2.linear);
|
||||
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],
|
||||
],
|
||||
)
|
||||
}
|
||||
}
|
||||
5
src/cmm/cmm_transfer_function.rs
Normal file
5
src/cmm/cmm_transfer_function.rs
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
|
||||
pub enum TransferFunction {
|
||||
Srgb,
|
||||
Linear,
|
||||
}
|
||||
258
src/cmm/cmm_transform.rs
Normal file
258
src/cmm/cmm_transform.rs
Normal file
|
|
@ -0,0 +1,258 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::{cmm_primaries::Primaries, cmm_transfer_function::TransferFunction},
|
||||
theme::Color,
|
||||
utils::{debug_fn::debug_fn, ordered_float::F64},
|
||||
},
|
||||
std::{
|
||||
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> {
|
||||
debug_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> Mul<Color> for ColorMatrix<T, U> {
|
||||
type Output = Color;
|
||||
|
||||
fn mul(self, rhs: Color) -> Self::Output {
|
||||
let mut rgba = rhs.to_array(TransferFunction::Linear);
|
||||
let a = rgba[3];
|
||||
if a < 1.0 && a > 0.0 {
|
||||
for c in &mut rgba[..3] {
|
||||
*c /= a;
|
||||
}
|
||||
}
|
||||
let [r, g, b] = self * [rgba[0] as f64, rgba[1] as f64, rgba[2] as f64];
|
||||
let mut color = Color::new(TransferFunction::Linear, r as f32, g as f32, b as f32);
|
||||
if a < 1.0 {
|
||||
color = color * a;
|
||||
}
|
||||
color
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
}
|
||||
|
||||
#[expect(dead_code)]
|
||||
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),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -12,6 +12,7 @@ use {
|
|||
cli::{CliBackend, GlobalArgs, RunArgs},
|
||||
client::{ClientId, Clients},
|
||||
clientmem::{self, ClientMemError},
|
||||
cmm::cmm_manager::ColorManager,
|
||||
config::ConfigProxy,
|
||||
cpu_worker::{CpuWorker, CpuWorkerError},
|
||||
damage::{DamageVisualizer, visualize_damage},
|
||||
|
|
@ -284,6 +285,7 @@ fn start_compositor2(
|
|||
data_control_device_ids: Default::default(),
|
||||
workspace_managers: Default::default(),
|
||||
color_management_enabled: Cell::new(false),
|
||||
color_manager: ColorManager::new(),
|
||||
});
|
||||
state.tracker.register(ClientId::from_raw(0));
|
||||
create_dummy_output(&state);
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ use {
|
|||
self, ConnectorId, DrmDeviceId, InputDeviceAccelProfile, InputDeviceCapability,
|
||||
InputDeviceId,
|
||||
},
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
compositor::MAX_EXTENTS,
|
||||
config::ConfigProxy,
|
||||
format::config_formats,
|
||||
|
|
@ -14,7 +15,7 @@ use {
|
|||
output_schedule::map_cursor_hz,
|
||||
scale::Scale,
|
||||
state::{ConnectorData, DeviceHandlerData, DrmDevData, OutputData, State},
|
||||
theme::{Color, ThemeSized, TransferFunction},
|
||||
theme::{Color, ThemeSized},
|
||||
tree::{
|
||||
ContainerNode, ContainerSplit, FloatNode, Node, NodeVisitorBase, OutputNode,
|
||||
TearingMode, VrrMode, WsMoveConfig, move_ws_to_output,
|
||||
|
|
|
|||
|
|
@ -67,6 +67,7 @@ macro_rules! dynload {
|
|||
|
||||
use {
|
||||
crate::{
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
gfx_api::{
|
||||
AcquireSync, CopyTexture, FillRect, GfxApiOpt, GfxContext, GfxError, GfxTexture,
|
||||
ReleaseSync, SyncFile,
|
||||
|
|
@ -84,7 +85,7 @@ use {
|
|||
GL_TRIANGLE_STRIP, GL_TRIANGLES,
|
||||
},
|
||||
},
|
||||
theme::{Color, TransferFunction},
|
||||
theme::Color,
|
||||
utils::{errorfmt::ErrorFmt, rc_eq::rc_eq, vecstorage::VecStorage},
|
||||
video::{
|
||||
dmabuf::DMA_BUF_SYNC_READ,
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
format::Format,
|
||||
gfx_api::{
|
||||
AcquireSync, AsyncShmGfxTextureCallback, GfxApiOpt, GfxBlendBuffer, GfxError,
|
||||
|
|
@ -18,7 +19,7 @@ use {
|
|||
sys::{GL_ONE, GL_ONE_MINUS_SRC_ALPHA},
|
||||
},
|
||||
rect::Region,
|
||||
theme::{Color, TransferFunction},
|
||||
theme::Color,
|
||||
},
|
||||
std::{
|
||||
cell::Cell,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
use {
|
||||
crate::{
|
||||
async_engine::{AsyncEngine, SpawnedFuture},
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
cpu_worker::PendingJob,
|
||||
format::XRGB8888,
|
||||
gfx_api::{
|
||||
|
|
@ -30,7 +31,7 @@ use {
|
|||
},
|
||||
io_uring::IoUring,
|
||||
rect::{Rect, Region},
|
||||
theme::{Color, TransferFunction},
|
||||
theme::Color,
|
||||
utils::{copyhashmap::CopyHashMap, errorfmt::ErrorFmt, numcell::NumCell, stack::Stack},
|
||||
video::dmabuf::{DMA_BUF_SYNC_READ, DMA_BUF_SYNC_WRITE, dma_buf_export_sync_file},
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use {
|
||||
crate::{
|
||||
client::{CAP_JAY_COMPOSITOR, Client, ClientCaps, ClientError},
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
globals::{Global, GlobalName},
|
||||
leaks::Tracker,
|
||||
object::{Object, Version},
|
||||
theme::{Color, TransferFunction},
|
||||
theme::Color,
|
||||
wire::{
|
||||
JayCompositorId,
|
||||
jay_damage_tracking::{
|
||||
|
|
|
|||
|
|
@ -1,10 +1,11 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
it::{
|
||||
test_error::TestResult, test_ifs::test_buffer::TestBuffer, test_object::TestObject,
|
||||
test_transport::TestTransport,
|
||||
},
|
||||
theme::{Color, TransferFunction},
|
||||
theme::Color,
|
||||
wire::{WpSinglePixelBufferManagerV1Id, wp_single_pixel_buffer_manager_v1::*},
|
||||
},
|
||||
std::{cell::Cell, rc::Rc},
|
||||
|
|
|
|||
|
|
@ -185,10 +185,10 @@ macro_rules! shared_ids {
|
|||
}
|
||||
|
||||
macro_rules! linear_ids {
|
||||
($ids:ident, $id:ident) => {
|
||||
($ids:ident, $id:ident $(,)?) => {
|
||||
linear_ids!($ids, $id, u32);
|
||||
};
|
||||
($ids:ident, $id:ident, $ty:ty) => {
|
||||
($ids:ident, $id:ident, $ty:ty $(,)?) => {
|
||||
pub struct $ids {
|
||||
next: crate::utils::numcell::NumCell<$ty>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,8 @@
|
|||
clippy::manual_is_ascii_check,
|
||||
clippy::needless_borrow,
|
||||
clippy::unnecessary_cast,
|
||||
clippy::manual_flatten
|
||||
clippy::manual_flatten,
|
||||
clippy::manual_bits
|
||||
)]
|
||||
#![warn(clippy::allow_attributes, unsafe_op_in_unsafe_fn)]
|
||||
|
||||
|
|
@ -54,6 +55,7 @@ mod bugs;
|
|||
mod cli;
|
||||
mod client;
|
||||
mod clientmem;
|
||||
mod cmm;
|
||||
mod compositor;
|
||||
mod config;
|
||||
mod cpu_worker;
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
format::ARGB8888,
|
||||
gfx_api::{GfxContext, GfxTexture},
|
||||
pango::{
|
||||
|
|
@ -7,7 +8,7 @@ use {
|
|||
consts::{CAIRO_FORMAT_ARGB32, CAIRO_OPERATOR_SOURCE},
|
||||
},
|
||||
rect::Rect,
|
||||
theme::{Color, TransferFunction},
|
||||
theme::Color,
|
||||
},
|
||||
std::{ops::Neg, rc::Rc, sync::Arc},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
gfx_api::{AcquireSync, GfxApiOpt, ReleaseSync, SampleRect},
|
||||
ifs::wl_surface::{
|
||||
SurfaceBuffer, WlSurface,
|
||||
|
|
@ -11,7 +12,7 @@ use {
|
|||
renderer::renderer_base::RendererBase,
|
||||
scale::Scale,
|
||||
state::State,
|
||||
theme::{Color, TransferFunction},
|
||||
theme::Color,
|
||||
tree::{
|
||||
ContainerNode, DisplayNode, FloatNode, OutputNode, PlaceholderNode, ToplevelData,
|
||||
ToplevelNodeBase, WorkspaceNode,
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use {
|
|||
cli::RunArgs,
|
||||
client::{Client, ClientId, Clients, NUM_CACHED_SERIAL_RANGES, SerialRange},
|
||||
clientmem::ClientMemOffset,
|
||||
cmm::cmm_manager::ColorManager,
|
||||
compositor::LIBEI_SOCKET,
|
||||
config::ConfigProxy,
|
||||
cpu_worker::CpuWorker,
|
||||
|
|
@ -234,6 +235,8 @@ pub struct State {
|
|||
pub data_control_device_ids: DataControlDeviceIds,
|
||||
pub workspace_managers: WorkspaceManagerState,
|
||||
pub color_management_enabled: Cell<bool>,
|
||||
#[expect(dead_code)]
|
||||
pub color_manager: Rc<ColorManager>,
|
||||
}
|
||||
|
||||
// impl Drop for State {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
use {
|
||||
crate::{
|
||||
cmm::cmm_transfer_function::TransferFunction,
|
||||
cpu_worker::{AsyncCpuWork, CpuJob, CpuWork, CpuWorker, PendingJob},
|
||||
format::ARGB8888,
|
||||
gfx_api::{
|
||||
|
|
@ -14,7 +15,7 @@ use {
|
|||
},
|
||||
},
|
||||
rect::{Rect, Region},
|
||||
theme::{Color, TransferFunction},
|
||||
theme::Color,
|
||||
utils::{
|
||||
clonecell::CloneCell, double_buffered::DoubleBuffered, on_drop_event::OnDropEvent,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -1,14 +1,8 @@
|
|||
use {
|
||||
crate::utils::clonecell::CloneCell,
|
||||
crate::{cmm::cmm_transfer_function::TransferFunction, utils::clonecell::CloneCell},
|
||||
std::{cell::Cell, cmp::Ordering, ops::Mul, sync::Arc},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
pub enum TransferFunction {
|
||||
Srgb,
|
||||
Linear,
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, PartialEq)]
|
||||
pub struct Color {
|
||||
r: f32,
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ pub mod double_click_state;
|
|||
pub mod errorfmt;
|
||||
pub mod event_listener;
|
||||
pub mod fdcloser;
|
||||
pub mod free_list;
|
||||
pub mod geometric_decay;
|
||||
pub mod gfx_api_ext;
|
||||
pub mod hash_map_ext;
|
||||
|
|
@ -37,6 +38,7 @@ pub mod opaque;
|
|||
pub mod opaque_cell;
|
||||
pub mod opt;
|
||||
pub mod option_ext;
|
||||
pub mod ordered_float;
|
||||
pub mod oserror;
|
||||
pub mod page_size;
|
||||
pub mod pending_serial;
|
||||
|
|
|
|||
93
src/utils/free_list.rs
Normal file
93
src/utils/free_list.rs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use {
|
||||
crate::utils::ptr_ext::MutPtrExt,
|
||||
std::{
|
||||
array,
|
||||
cell::UnsafeCell,
|
||||
fmt::{Debug, Formatter},
|
||||
marker::PhantomData,
|
||||
},
|
||||
};
|
||||
|
||||
type Seg = usize;
|
||||
const SEG_SIZE: usize = size_of::<Seg>() * 8;
|
||||
|
||||
pub struct FreeList<T, const N: usize> {
|
||||
levels: UnsafeCell<[Vec<Seg>; N]>,
|
||||
_phantom: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Default for FreeList<T, N> {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
levels: UnsafeCell::new(array::from_fn(|_| Vec::new())),
|
||||
_phantom: Default::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> Debug for FreeList<T, N> {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("FreeList")
|
||||
.field("levels", self.get())
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, const N: usize> FreeList<T, N> {
|
||||
fn get(&self) -> &mut [Vec<Seg>; N] {
|
||||
unsafe { self.levels.get().deref_mut() }
|
||||
}
|
||||
|
||||
pub fn release(&self, n: T)
|
||||
where
|
||||
T: Into<u32>,
|
||||
{
|
||||
let mut ext = n.into() as usize;
|
||||
let mut int;
|
||||
let levels = self.get();
|
||||
assert!(ext / SEG_SIZE < levels[0].len());
|
||||
for level in self.get() {
|
||||
int = ext % SEG_SIZE;
|
||||
ext /= SEG_SIZE;
|
||||
unsafe {
|
||||
*level.get_unchecked_mut(ext) |= 1 << int;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn acquire(&self) -> T
|
||||
where
|
||||
u32: Into<T>,
|
||||
{
|
||||
let mut ext = 'last: {
|
||||
let level = &mut self.get()[N - 1];
|
||||
for (idx, &seg) in level.iter().enumerate() {
|
||||
if seg != 0 {
|
||||
break 'last idx;
|
||||
}
|
||||
}
|
||||
level.len()
|
||||
};
|
||||
for level in self.get().iter_mut().rev() {
|
||||
if ext == level.len() {
|
||||
level.push(!0);
|
||||
}
|
||||
let seg = unsafe { level.get_unchecked(ext) };
|
||||
ext = SEG_SIZE * ext + seg.trailing_zeros() as usize;
|
||||
}
|
||||
let id = ext as u32;
|
||||
for level in self.get().iter_mut() {
|
||||
let int = ext % SEG_SIZE;
|
||||
ext /= SEG_SIZE;
|
||||
let seg = unsafe { level.get_unchecked_mut(ext) };
|
||||
*seg &= !(1 << int);
|
||||
if *seg != 0 {
|
||||
break;
|
||||
}
|
||||
}
|
||||
id.into()
|
||||
}
|
||||
}
|
||||
40
src/utils/free_list/tests.rs
Normal file
40
src/utils/free_list/tests.rs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
use crate::utils::free_list::FreeList;
|
||||
|
||||
#[test]
|
||||
fn test() {
|
||||
let list = FreeList::<u32, 3>::default();
|
||||
for i in 0..4097 {
|
||||
assert_eq!(list.acquire(), i);
|
||||
}
|
||||
list.release(100);
|
||||
assert_eq!(list.acquire(), 100);
|
||||
assert_eq!(list.acquire(), 4097);
|
||||
for i in 1..22 {
|
||||
list.release(i);
|
||||
}
|
||||
for i in 1..22 {
|
||||
assert_eq!(list.acquire(), i);
|
||||
}
|
||||
assert_eq!(list.acquire(), 4098);
|
||||
for i in 64..128 {
|
||||
list.release(i);
|
||||
}
|
||||
for i in 64..128 {
|
||||
assert_eq!(list.acquire(), i);
|
||||
}
|
||||
assert_eq!(list.acquire(), 4099);
|
||||
for i in 0..4100 {
|
||||
list.release(i);
|
||||
}
|
||||
for i in 0..4101 {
|
||||
assert_eq!(list.acquire(), i);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn release_out_of_bounds() {
|
||||
let list = FreeList::<u32, 3>::default();
|
||||
list.acquire();
|
||||
list.release(500);
|
||||
}
|
||||
67
src/utils/ordered_float.rs
Normal file
67
src/utils/ordered_float.rs
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
use std::{
|
||||
fmt::{Debug, Display, Formatter},
|
||||
hash::{Hash, Hasher},
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
#[repr(transparent)]
|
||||
pub struct F64(pub f64);
|
||||
|
||||
impl Eq for F64 {}
|
||||
|
||||
impl PartialEq for F64 {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.0.to_bits() == other.0.to_bits()
|
||||
}
|
||||
}
|
||||
|
||||
impl Hash for F64 {
|
||||
fn hash<H: Hasher>(&self, state: &mut H) {
|
||||
self.0.to_bits().hash(state);
|
||||
}
|
||||
}
|
||||
|
||||
impl Add<F64> for F64 {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: F64) -> Self::Output {
|
||||
Self(self.0 + rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub<F64> for F64 {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: F64) -> Self::Output {
|
||||
Self(self.0 - rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<F64> for F64 {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: F64) -> Self::Output {
|
||||
Self(self.0 * rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<F64> for F64 {
|
||||
type Output = Self;
|
||||
|
||||
fn div(self, rhs: F64) -> Self::Output {
|
||||
Self(self.0 / rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for F64 {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Display::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for F64 {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
Debug::fmt(&self.0, f)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue