Skip to content

Commit 24885e7

Browse files
authored
Merge pull request #1105 from mahkoh/jorth/cp-cmm
cmm: color pipeline preparations
2 parents fca9da9 + 0a4062b commit 24885e7

10 files changed

Lines changed: 259 additions & 20 deletions

File tree

algorithms/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,4 +7,5 @@
77

88
pub mod qoi;
99
pub mod rect;
10+
pub mod triangles;
1011
mod windows;

algorithms/src/triangles.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
pub fn triangle_contains_points(triangle: [[f64; 2]; 3], points: [[f64; 2]; 3]) -> bool {
2+
let triangle_vectors = [
3+
[
4+
triangle[1][0] - triangle[0][0],
5+
triangle[1][1] - triangle[0][1],
6+
],
7+
[
8+
triangle[2][0] - triangle[1][0],
9+
triangle[2][1] - triangle[1][1],
10+
],
11+
[
12+
triangle[0][0] - triangle[2][0],
13+
triangle[0][1] - triangle[2][1],
14+
],
15+
];
16+
let mut class = [0u64; 3];
17+
for i in 0..3 {
18+
let point = points[i];
19+
let point_vectors = [
20+
[point[0] - triangle[0][0], point[1] - triangle[0][1]],
21+
[point[0] - triangle[1][0], point[1] - triangle[1][1]],
22+
[point[0] - triangle[2][0], point[1] - triangle[2][1]],
23+
];
24+
for j in 0..3 {
25+
let triangle_vector = triangle_vectors[j];
26+
let point_vector = point_vectors[j];
27+
let cross_product =
28+
triangle_vector[0] * point_vector[1] - triangle_vector[1] * point_vector[0];
29+
if cross_product != 0.0 {
30+
// 0b01 = positive
31+
// 0b10 = negative
32+
// 0b11 = mixed positive and negative
33+
class[i] |= (cross_product.to_bits() >> 63) + 1;
34+
}
35+
}
36+
}
37+
let mut bad = false;
38+
for i in 0..3 {
39+
bad |= class[i] == 0b11;
40+
}
41+
!bad
42+
}

src/cmm/cmm_description.rs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,8 @@ use {
1010
},
1111
utils::ordered_float::F64,
1212
},
13-
std::rc::Rc,
13+
jay_algorithms::triangles::triangle_contains_points,
14+
std::{cell::OnceCell, rc::Rc},
1415
};
1516

1617
linear_ids!(LinearColorDescriptionIds, LinearColorDescriptionId, u64);
@@ -25,6 +26,7 @@ pub struct LinearColorDescription {
2526
pub luminance: Luminance,
2627
pub target_primaries: Primaries,
2728
pub target_luminance: TargetLuminance,
29+
pub target_contained_in_primary: OnceCell<bool>,
2830
pub max_cll: Option<F64>,
2931
pub max_fall: Option<F64>,
3032
pub(super) shared: Rc<Shared>,
@@ -56,23 +58,41 @@ impl LinearColorDescription {
5658
mat * self.xyz_from_local
5759
}
5860

59-
pub fn embeds_into(&self, target: &Self) -> bool {
61+
pub fn embeds_into(&self, target: &Self, intent: RenderIntent) -> bool {
6062
if self.id == target.id {
6163
return true;
6264
}
63-
if self.primaries != target.primaries {
65+
if !self.primaries.about_equal(&target.primaries) {
6466
return false;
6567
}
66-
if self.luminance != target.luminance {
68+
if !self.luminance.embeds_into(&target.luminance, intent) {
6769
return false;
6870
}
6971
true
7072
}
73+
74+
#[cfg_attr(not(test), expect(dead_code))]
75+
pub fn target_contained_in_primary(&self) -> bool {
76+
*self.target_contained_in_primary.get_or_init(|| {
77+
if self.target_luminance.min.0 < self.luminance.min.0
78+
|| self.target_luminance.max.0 > self.luminance.max.0
79+
{
80+
return false;
81+
}
82+
#[rustfmt::skip]
83+
let extract = |p: &Primaries| [
84+
[p.r.0.0, p.r.1.0],
85+
[p.g.0.0, p.g.1.0],
86+
[p.b.0.0, p.b.1.0],
87+
];
88+
triangle_contains_points(extract(&self.primaries), extract(&self.target_primaries))
89+
})
90+
}
7191
}
7292

7393
impl ColorDescription {
74-
pub fn embeds_into(&self, target: &Self) -> bool {
75-
self.eotf == target.eotf && self.linear.embeds_into(&target.linear)
94+
pub fn embeds_into(&self, target: &Self, intent: RenderIntent) -> bool {
95+
self.eotf == target.eotf && self.linear.embeds_into(&target.linear, intent)
7696
}
7797
}
7898

src/cmm/cmm_luminance.rs

Lines changed: 31 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -51,12 +51,12 @@ impl Luminance {
5151
};
5252

5353
pub const WINDOWS_SCRGB: Self = Self {
54-
min: Self::ST2084_PQ.min,
55-
max: Self::ST2084_PQ.max,
54+
min: F64(0.0),
55+
max: F64(80.0),
5656
// This causes the white balance formula (with target ST2084_PQ) to simplify to
5757
// `Y * 80 / 10000`, meaning that sRGB pure white maps to a luminance of
5858
// 80 cd/m^2.
59-
white: F64(Self::ST2084_PQ.white.0 / 80.0 * Self::ST2084_PQ.max.0),
59+
white: F64(203.0),
6060
};
6161
}
6262

@@ -67,6 +67,19 @@ impl Luminance {
6767
max: self.max,
6868
}
6969
}
70+
71+
pub fn embeds_into(&self, other: &Self, intent: RenderIntent) -> bool {
72+
const EPSILON: f64 = 0.001;
73+
let scaler = scaler(self, other);
74+
if (scaler - 1.0).abs() > EPSILON {
75+
return false;
76+
}
77+
let offset = offset(self, other, intent);
78+
if offset.abs() > EPSILON {
79+
return false;
80+
}
81+
true
82+
}
7083
}
7184

7285
impl Default for Luminance {
@@ -82,13 +95,8 @@ pub fn white_balance(
8295
w_to: (F64, F64),
8396
intent: RenderIntent,
8497
) -> ColorMatrix<Xyz, Xyz> {
85-
let a = ((from.max - from.min) / (to.max - to.min) * (to.white - to.min)
86-
/ (from.white - from.min))
87-
.0;
88-
let d = match intent.black_point_compensation() {
89-
true => 0.0,
90-
false => ((from.min - to.min) / (to.max - to.min)).0,
91-
};
98+
let a = scaler(from, to);
99+
let d = offset(from, to, intent);
92100
let s = a - d;
93101
let (F64(x_to), F64(y_to)) = w_to;
94102
let X_to = x_to / y_to;
@@ -100,3 +108,16 @@ pub fn white_balance(
100108
[0.0, 0.0, s, d * Z_to],
101109
])
102110
}
111+
112+
fn scaler(from: &Luminance, to: &Luminance) -> f64 {
113+
let a =
114+
(from.max - from.min) / (to.max - to.min) * (to.white - to.min) / (from.white - from.min);
115+
a.0
116+
}
117+
118+
fn offset(from: &Luminance, to: &Luminance, intent: RenderIntent) -> f64 {
119+
match intent.black_point_compensation() {
120+
true => 0.0,
121+
false => ((from.min - to.min) / (to.max - to.min)).0,
122+
}
123+
}

src/cmm/cmm_manager.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,7 @@ fn get_description(
225225
luminance,
226226
target_primaries,
227227
target_luminance,
228+
target_contained_in_primary: Default::default(),
228229
max_cll,
229230
max_fall,
230231
shared: shared.clone(),

src/cmm/cmm_primaries.rs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,17 @@ impl Primaries {
9494
b: (F64(0.15), F64(0.06)),
9595
wp: (F64(0.3127), F64(0.3290)),
9696
};
97+
98+
pub fn about_equal(&self, other: &Self) -> bool {
99+
const EPSILON: f64 = 0.001;
100+
macro_rules! ae {
101+
($field:ident) => {
102+
(self.$field.0.0 - other.$field.0.0).abs() < EPSILON
103+
&& (self.$field.1.0 - other.$field.1.0).abs() < EPSILON
104+
};
105+
}
106+
ae!(r) && ae!(g) && ae!(b) && ae!(wp)
107+
}
97108
}
98109
impl NamedPrimaries {
99110
pub const fn primaries(self) -> Primaries {

src/cmm/cmm_tests.rs

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
use crate::cmm::{
2+
cmm_eotf::Eotf, cmm_luminance::Luminance, cmm_manager::ColorManager, cmm_primaries::Primaries,
3+
};
4+
15
mod matrices {
26
use crate::{cmm::cmm_primaries::Primaries, utils::ordered_float::F64};
37

@@ -212,3 +216,133 @@ mod transforms {
212216
)
213217
}
214218
}
219+
220+
#[test]
221+
fn target_contained() {
222+
let cm = ColorManager::new();
223+
assert!(cm.srgb_gamma22().linear.target_contained_in_primary());
224+
assert!(!cm.windows_scrgb().linear.target_contained_in_primary());
225+
assert!(cm.windows_bt2100().linear.target_contained_in_primary());
226+
227+
let get_description =
228+
|p, l, tp, tl| cm.get_description(None, p, l, Eotf::Linear, tp, tl, None, None);
229+
230+
{
231+
let mut srgb_modified = Primaries::SRGB;
232+
srgb_modified.r.0.0 += 0.001;
233+
let desc = get_description(
234+
Primaries::SRGB,
235+
Luminance::SRGB,
236+
srgb_modified,
237+
Luminance::SRGB.to_target(),
238+
);
239+
assert!(!desc.linear.target_contained_in_primary());
240+
}
241+
242+
{
243+
let mut srgb_modified = Primaries::SRGB;
244+
srgb_modified.r.0.0 -= 0.001;
245+
let desc = get_description(
246+
Primaries::SRGB,
247+
Luminance::SRGB,
248+
srgb_modified,
249+
Luminance::SRGB.to_target(),
250+
);
251+
assert!(desc.linear.target_contained_in_primary());
252+
}
253+
254+
{
255+
let desc = get_description(
256+
Primaries::DCI_P3,
257+
Luminance::SRGB,
258+
Primaries::SRGB,
259+
Luminance::SRGB.to_target(),
260+
);
261+
assert!(desc.linear.target_contained_in_primary());
262+
}
263+
264+
{
265+
let mut srgb_modified = Primaries::SRGB;
266+
srgb_modified.r = Primaries::BT2020.r;
267+
let desc = get_description(
268+
Primaries::DCI_P3,
269+
Luminance::SRGB,
270+
srgb_modified,
271+
Luminance::SRGB.to_target(),
272+
);
273+
assert!(!desc.linear.target_contained_in_primary());
274+
}
275+
276+
{
277+
let mut bt2020_modified = Primaries::BT2020;
278+
bt2020_modified.r.1.0 *= -1.0;
279+
bt2020_modified.g.1.0 *= -1.0;
280+
bt2020_modified.b.1.0 *= -1.0;
281+
let mut srgb_modified = Primaries::SRGB;
282+
srgb_modified.r.1.0 *= -1.0;
283+
srgb_modified.g.1.0 *= -1.0;
284+
srgb_modified.b.1.0 *= -1.0;
285+
let desc = get_description(
286+
Primaries::BT2020,
287+
Luminance::SRGB,
288+
Primaries::SRGB,
289+
Luminance::SRGB.to_target(),
290+
);
291+
assert!(desc.linear.target_contained_in_primary());
292+
let desc = get_description(
293+
bt2020_modified,
294+
Luminance::SRGB,
295+
srgb_modified,
296+
Luminance::SRGB.to_target(),
297+
);
298+
assert!(desc.linear.target_contained_in_primary());
299+
}
300+
301+
{
302+
let mut srgb_modified = Luminance::SRGB;
303+
srgb_modified.min.0 -= 0.001;
304+
let desc = get_description(
305+
Primaries::SRGB,
306+
Luminance::SRGB,
307+
Primaries::SRGB,
308+
srgb_modified.to_target(),
309+
);
310+
assert!(!desc.linear.target_contained_in_primary());
311+
}
312+
313+
{
314+
let mut srgb_modified = Luminance::SRGB;
315+
srgb_modified.min.0 += 0.001;
316+
let desc = get_description(
317+
Primaries::SRGB,
318+
Luminance::SRGB,
319+
Primaries::SRGB,
320+
srgb_modified.to_target(),
321+
);
322+
assert!(desc.linear.target_contained_in_primary());
323+
}
324+
325+
{
326+
let mut srgb_modified = Luminance::SRGB;
327+
srgb_modified.max.0 -= 0.001;
328+
let desc = get_description(
329+
Primaries::SRGB,
330+
Luminance::SRGB,
331+
Primaries::SRGB,
332+
srgb_modified.to_target(),
333+
);
334+
assert!(desc.linear.target_contained_in_primary());
335+
}
336+
337+
{
338+
let mut srgb_modified = Luminance::SRGB;
339+
srgb_modified.max.0 += 0.001;
340+
let desc = get_description(
341+
Primaries::SRGB,
342+
Luminance::SRGB,
343+
Primaries::SRGB,
344+
srgb_modified.to_target(),
345+
);
346+
assert!(!desc.linear.target_contained_in_primary());
347+
}
348+
}

src/cmm/cmm_transform.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,15 @@ fn format_matrix<'a>(m: &'a [[F64; 4]; 3]) -> impl Debug + use<'a> {
8989
})
9090
}
9191

92+
impl<T, U> ColorMatrix<T, U> {
93+
#[expect(dead_code)]
94+
pub const IDENTITY: Self = Self::new([
95+
[1.0, 0.0, 0.0, 0.0],
96+
[0.0, 1.0, 0.0, 0.0],
97+
[0.0, 0.0, 1.0, 0.0],
98+
]);
99+
}
100+
92101
impl<T, U, V> Mul<ColorMatrix<U, T>> for ColorMatrix<V, U> {
93102
type Output = ColorMatrix<V, T>;
94103

src/gfx_api.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1458,12 +1458,12 @@ impl GfxRenderPass {
14581458
// Direct scanout requires premultiplied electrical alpha.
14591459
return None;
14601460
}
1461-
if !ct.cd.embeds_into(cd) {
1461+
if !ct.cd.embeds_into(cd, ct.render_intent) {
14621462
// Direct scanout requires embeddable color descriptions.
14631463
return None;
14641464
}
14651465
let has_alpha = ct.tex.format().has_alpha;
1466-
if has_alpha && !ct.opaque && !ct.cd.embeds_into(blend_cd) {
1466+
if has_alpha && !ct.opaque && !ct.cd.embeds_into(blend_cd, ct.render_intent) {
14671467
// Blending changes the appearance of translucent buffers.
14681468
return None;
14691469
}

src/gfx_apis/vulkan/renderer.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2243,7 +2243,7 @@ impl VulkanRenderer {
22432243
if blend_buffer.is_none() {
22442244
return;
22452245
}
2246-
if bb_cd.embeds_into(fb_cd) {
2246+
if bb_cd.embeds_into(fb_cd, RenderIntent::Perceptual) {
22472247
*blend_buffer = None;
22482248
}
22492249
}
@@ -2651,7 +2651,7 @@ impl ColorTransforms {
26512651
dst: &ColorDescription,
26522652
intent: RenderIntent,
26532653
) -> Option<&mut ColorTransform> {
2654-
if src.embeds_into(&dst.linear) {
2654+
if src.embeds_into(&dst.linear, intent) {
26552655
return None;
26562656
}
26572657
let ct = match self.map.entry(([src.id, dst.linear.id], intent)) {

0 commit comments

Comments
 (0)