Skip to content

Commit 930f1fd

Browse files
committedOct 18, 2024
Const matrix inversion, remove hardcoded inversions
MSRV 1.82
1 parent 94bc38c commit 930f1fd

File tree

2 files changed

+41
-53
lines changed

2 files changed

+41
-53
lines changed
 

‎src/lib.rs

+40-52
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,39 @@ const JZAZBZ_P: f32 = 1.7 * PQEOTF_M2;
255255

256256
// ### MATRICES ### {{{
257257

258+
/// Matrix determinant using only constant math
259+
const fn det(m: [[f32; 3]; 3]) -> f32 {
260+
m[0][0] * (m[1][1] * m[2][2] - m[1][2] * m[2][1]) - m[0][1] * (m[1][0] * m[2][2] - m[1][2] * m[2][0])
261+
+ m[0][2] * (m[1][0] * m[2][1] - m[1][1] * m[2][0])
262+
}
263+
264+
/// Matrix inversion using only constant math
265+
/// Panics if determinant is zero
266+
const fn inv(m: [[f32; 3]; 3]) -> [[f32; 3]; 3] {
267+
let d = det(m);
268+
if d == 0.0 {
269+
panic!("Matrix is singular and has no inverse")
270+
}
271+
272+
[
273+
[
274+
(m[1][1] * m[2][2] - m[1][2] * m[2][1]) / d,
275+
(m[0][2] * m[2][1] - m[0][1] * m[2][2]) / d,
276+
(m[0][1] * m[1][2] - m[0][2] * m[1][1]) / d,
277+
],
278+
[
279+
(m[1][2] * m[2][0] - m[1][0] * m[2][2]) / d,
280+
(m[0][0] * m[2][2] - m[0][2] * m[2][0]) / d,
281+
(m[0][2] * m[1][0] - m[0][0] * m[1][2]) / d,
282+
],
283+
[
284+
(m[1][0] * m[2][1] - m[1][1] * m[2][0]) / d,
285+
(m[0][1] * m[2][0] - m[0][0] * m[2][1]) / d,
286+
(m[0][0] * m[1][1] - m[0][1] * m[1][0]) / d,
287+
],
288+
]
289+
}
290+
258291
/// Its easier to write matricies visually then transpose them so they can be indexed per vector
259292
/// [X1, X2] -> [X1, Y1]
260293
/// [Y1, Y2] [X2, Y2]
@@ -282,20 +315,6 @@ const XYZ65_MAT: [[f32; 3]; 3] = t([
282315
[0.0193, 0.1192, 0.9505],
283316
]);
284317

285-
// Original commonly used inverted array
286-
// const XYZ65_MAT_INV: [[f32; 3]; 3] = [
287-
// [3.2406, -1.5372, -0.4986],
288-
// [-0.9689, 1.8758, 0.0415],
289-
// [0.0557, -0.2040, 1.0570],
290-
// ];
291-
292-
// Higher precision invert using numpy. Helps with back conversions
293-
const XYZ65_MAT_INV: [[f32; 3]; 3] = t([
294-
[3.2406254773, -1.5372079722, -0.4986285987],
295-
[-0.9689307147, 1.8757560609, 0.0415175238],
296-
[0.0557101204, -0.2040210506, 1.0569959423],
297-
]);
298-
299318
// OKLAB
300319
// They appear to be provided already transposed for code in the blog post
301320
const OKLAB_M1: [[f32; 3]; 3] = [
@@ -308,16 +327,6 @@ const OKLAB_M2: [[f32; 3]; 3] = [
308327
[0.7936177850, -2.4285922050, 0.7827717662],
309328
[-0.0040720468, 0.4505937099, -0.8086757660],
310329
];
311-
const OKLAB_M1_INV: [[f32; 3]; 3] = [
312-
[1.2270138511, -0.0405801784, -0.0763812845],
313-
[-0.5577999807, 1.1122568696, -0.4214819784],
314-
[0.281256149, -0.0716766787, 1.5861632204],
315-
];
316-
const OKLAB_M2_INV: [[f32; 3]; 3] = [
317-
[0.9999999985, 1.0000000089, 1.0000000547],
318-
[0.3963377922, -0.1055613423, -0.0894841821],
319-
[0.2158037581, -0.0638541748, -1.2914855379],
320-
];
321330

322331
// JzAzBz
323332
const JZAZBZ_M1: [[f32; 3]; 3] = t([
@@ -331,17 +340,6 @@ const JZAZBZ_M2: [[f32; 3]; 3] = t([
331340
[0.199076, 1.096799, -1.295875],
332341
]);
333342

334-
const JZAZBZ_M1_INV: [[f32; 3]; 3] = t([
335-
[1.9242264358, -1.0047923126, 0.037651404],
336-
[0.3503167621, 0.7264811939, -0.0653844229],
337-
[-0.090982811, -0.3127282905, 1.5227665613],
338-
]);
339-
const JZAZBZ_M2_INV: [[f32; 3]; 3] = t([
340-
[1., 0.1386050433, 0.0580473162],
341-
[1., -0.1386050433, -0.0580473162],
342-
[1., -0.096019242, -0.8118918961],
343-
]);
344-
345343
// ICtCp
346344
const ICTCP_M1: [[f32; 3]; 3] = t([
347345
[1688. / 4096., 2146. / 4096., 262. / 4096.],
@@ -354,16 +352,6 @@ const ICTCP_M2: [[f32; 3]; 3] = t([
354352
[17933. / 4096., -17390. / 4096., -543. / 4096.],
355353
]);
356354

357-
const ICTCP_M1_INV: [[f32; 3]; 3] = t([
358-
[3.4366066943, -2.5064521187, 0.0698454243],
359-
[-0.7913295556, 1.9836004518, -0.1922708962],
360-
[-0.0259498997, -0.0989137147, 1.1248636144],
361-
]);
362-
const ICTCP_M2_INV: [[f32; 3]; 3] = t([
363-
[1., 0.008609037, 0.111029625],
364-
[1., -0.008609037, -0.111029625],
365-
[1., 0.5600313357, -0.320627175],
366-
]);
367355
// ### MATRICES ### }}}
368356

369357
// ### TRANSFER FUNCTIONS ### {{{
@@ -1216,7 +1204,7 @@ pub fn xyz_to_lrgb<T: DType, const N: usize>(pixel: &mut [T; N])
12161204
where
12171205
Channels<N>: ValidChannels,
12181206
{
1219-
[pixel[0], pixel[1], pixel[2]] = mm(XYZ65_MAT_INV, [pixel[0], pixel[1], pixel[2]])
1207+
[pixel[0], pixel[1], pixel[2]] = mm(inv(XYZ65_MAT), [pixel[0], pixel[1], pixel[2]])
12201208
}
12211209

12221210
/// Convert from CIE LAB to CIE XYZ.
@@ -1251,9 +1239,9 @@ pub fn oklab_to_xyz<T: DType, const N: usize>(pixel: &mut [T; N])
12511239
where
12521240
Channels<N>: ValidChannels,
12531241
{
1254-
let mut lms = mm(OKLAB_M2_INV, [pixel[0], pixel[1], pixel[2]]);
1242+
let mut lms = mm(inv(OKLAB_M2), [pixel[0], pixel[1], pixel[2]]);
12551243
lms.iter_mut().for_each(|c| *c = c.powi(3));
1256-
[pixel[0], pixel[1], pixel[2]] = mm(OKLAB_M1_INV, lms);
1244+
[pixel[0], pixel[1], pixel[2]] = mm(inv(OKLAB_M1), lms);
12571245
}
12581246

12591247
/// Convert JzAzBz to CIE XYZ
@@ -1264,7 +1252,7 @@ where
12641252
Channels<N>: ValidChannels,
12651253
{
12661254
let mut lms = mm(
1267-
JZAZBZ_M2_INV,
1255+
inv(JZAZBZ_M2),
12681256
[
12691257
(pixel[0] + JZAZBZ_D0.to_dt())
12701258
/ (pixel[0] + JZAZBZ_D0.to_dt()).fma(T::ff32(-JZAZBZ_D), T::ff32(1.0 + JZAZBZ_D)),
@@ -1275,7 +1263,7 @@ where
12751263

12761264
lms.iter_mut().for_each(|c| *c = pqz_eotf(*c));
12771265

1278-
[pixel[0], pixel[1], pixel[2]] = mm(JZAZBZ_M1_INV, lms);
1266+
[pixel[0], pixel[1], pixel[2]] = mm(inv(JZAZBZ_M1), lms);
12791267

12801268
pixel[0] = pixel[2].fma((JZAZBZ_B - 1.0).to_dt(), pixel[0]) / JZAZBZ_B.to_dt();
12811269
pixel[1] = pixel[0].fma((JZAZBZ_G - 1.0).to_dt(), pixel[1]) / JZAZBZ_G.to_dt();
@@ -1297,10 +1285,10 @@ where
12971285
Channels<N>: ValidChannels,
12981286
{
12991287
// lms prime
1300-
let mut lms = mm(ICTCP_M2_INV, [pixel[0], pixel[1], pixel[2]]);
1288+
let mut lms = mm(inv(ICTCP_M2), [pixel[0], pixel[1], pixel[2]]);
13011289
// non-prime lms
13021290
lms.iter_mut().for_each(|c| *c = pq_eotf(*c));
1303-
[pixel[0], pixel[1], pixel[2]] = mm(ICTCP_M1_INV, lms);
1291+
[pixel[0], pixel[1], pixel[2]] = mm(inv(ICTCP_M1), lms);
13041292
}
13051293

13061294
/// Retrieves an LAB based space from its cylindrical representation.

‎src/tests.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,7 @@ fn tree_jump() {
298298
convert_space_chunked::<f64, 3>($from_space, $to_space, &mut input);
299299
// strange this is 1e-3 while indiv is 1e-2
300300
// also skip places where hue can wrap
301-
pix_cmp(&input, $to_data, 1e-3, &[0, 1, 7])
301+
pix_cmp(&input, $to_data, 5e-3, &[0, 1, 7])
302302
};
303303
}
304304

0 commit comments

Comments
 (0)
Failed to load comments.