4
4
//
5
5
// Original code by Thomas Lochmatter
6
6
7
+ use std:: ops:: { Index , IndexMut } ;
8
+
7
9
use crate :: gamma:: GammaCorrection ;
8
10
9
- pub struct ColorSpace {
10
- rgb : [ f64 ; 9 ] ,
11
- xyz : [ f64 ; 9 ] ,
12
- gamma : GammaCorrection ,
13
- }
11
+ #[ derive( Clone , Debug ) ]
12
+ pub struct Matrix3 ( pub [ f64 ; 3 * 3 ] ) ;
13
+
14
+ impl Matrix3 {
15
+ #[ must_use]
16
+ pub const fn identity ( ) -> Self {
17
+ Self ( [
18
+ 1.0 , 0.0 , 0.0 , //
19
+ 0.0 , 1.0 , 0.0 , //
20
+ 0.0 , 0.0 , 1.0 , //
21
+ ] )
22
+ }
23
+
24
+ #[ must_use]
25
+ pub fn inverted ( & self ) -> Option < Self > {
26
+ let mut current = self . clone ( ) ;
27
+ let mut inverse = Self :: identity ( ) ;
28
+
29
+ // Gaussian elimination (part 1)
30
+ for i in 0 ..3 {
31
+ // Get the diagonal term
32
+ let mut d = current[ [ i, i] ] ;
33
+
34
+ // If it is 0, there must be at least one row with a non-zero element (otherwise, the matrix is not invertible)
35
+ if d == 0.0 {
36
+ let mut r = i + 1 ;
37
+
38
+ while r < 3 && ( current[ [ r, i] ] ) . abs ( ) < 1e-10 {
39
+ r += 1 ;
40
+ }
41
+
42
+ if r == 3 {
43
+ return None ;
44
+ } // i is the rank
45
+
46
+ for c in 0 ..3 {
47
+ current[ [ i, c] ] += current[ [ r, c] ] ;
48
+ inverse[ [ i, c] ] += inverse[ [ r, c] ] ;
49
+ }
50
+
51
+ d = current[ [ i, i] ] ;
52
+ }
53
+
54
+ // Divide the row by the diagonal term
55
+ let inv = 1.0 / d;
56
+ for c in 0 ..3 {
57
+ current[ [ i, c] ] *= inv;
58
+ inverse[ [ i, c] ] *= inv;
59
+ }
60
+
61
+ // Divide all subsequent rows with a non-zero coefficient, and subtract the row
62
+ for r in i + 1 ..3 {
63
+ let p = current. 0 [ r * 3 + i] ;
64
+ if p != 0.0 {
65
+ for c in 0 ..3 {
66
+ current[ [ r, c] ] -= current[ [ i, c] ] * p;
67
+ inverse[ [ r, c] ] -= inverse[ [ i, c] ] * p;
68
+ }
69
+ }
70
+ }
71
+ }
72
+
73
+ // Gaussian elimination (part 2)
74
+ for i in ( 0 ..3 ) . rev ( ) {
75
+ for r in 0 ..i {
76
+ let d = current[ [ r, i] ] ;
77
+ for c in 0 ..3 {
78
+ current[ [ r, c] ] -= current[ [ i, c] ] * d;
79
+ inverse[ [ r, c] ] -= inverse[ [ i, c] ] * d;
80
+ }
81
+ }
82
+ }
83
+
84
+ Some ( inverse)
85
+ }
14
86
15
- impl ColorSpace {
16
87
#[ allow( clippy:: suboptimal_flops) ]
17
88
#[ must_use]
18
- fn mult ( d : [ f64 ; 3 ] , m : & [ f64 ; 9 ] ) -> [ f64 ; 3 ] {
89
+ fn mult ( & self , d : [ f64 ; 3 ] ) -> [ f64 ; 3 ] {
90
+ let m = self . 0 ;
19
91
let cx = d[ 0 ] * m[ 0 ] + d[ 1 ] * m[ 1 ] + d[ 2 ] * m[ 2 ] ;
20
92
let cy = d[ 0 ] * m[ 3 ] + d[ 1 ] * m[ 4 ] + d[ 2 ] * m[ 5 ] ;
21
93
let cz = d[ 0 ] * m[ 6 ] + d[ 1 ] * m[ 7 ] + d[ 2 ] * m[ 8 ] ;
22
94
[ cx, cy, cz]
23
95
}
96
+ }
97
+
98
+ impl Index < [ usize ; 2 ] > for Matrix3 {
99
+ type Output = f64 ;
100
+
101
+ fn index ( & self , index : [ usize ; 2 ] ) -> & Self :: Output {
102
+ & self . 0 [ index[ 0 ] * 3 + index[ 1 ] ]
103
+ }
104
+ }
24
105
106
+ impl IndexMut < [ usize ; 2 ] > for Matrix3 {
107
+ fn index_mut ( & mut self , index : [ usize ; 2 ] ) -> & mut Self :: Output {
108
+ & mut self . 0 [ index[ 0 ] * 3 + index[ 1 ] ]
109
+ }
110
+ }
111
+
112
+ pub struct ColorSpace {
113
+ rgb : Matrix3 ,
114
+ xyz : Matrix3 ,
115
+ gamma : GammaCorrection ,
116
+ }
117
+
118
+ impl ColorSpace {
25
119
#[ must_use]
26
120
pub fn xyz_to_rgb ( & self , x : f64 , y : f64 , z : f64 ) -> [ f64 ; 3 ] {
27
- Self :: mult ( [ x, y, z] , & self . rgb ) . map ( |q| self . gamma . transform ( q) )
121
+ self . rgb . mult ( [ x, y, z] ) . map ( |q| self . gamma . transform ( q) )
28
122
}
29
123
30
124
#[ allow( non_snake_case) ]
@@ -36,7 +130,7 @@ impl ColorSpace {
36
130
37
131
#[ must_use]
38
132
pub fn rgb_to_xyz ( & self , r : f64 , g : f64 , b : f64 ) -> [ f64 ; 3 ] {
39
- Self :: mult ( [ r, g, b] . map ( |q| self . gamma . inverse ( q) ) , & self . xyz )
133
+ self . xyz . mult ( [ r, g, b] . map ( |q| self . gamma . inverse ( q) ) )
40
134
}
41
135
42
136
#[ allow( clippy:: many_single_char_names) ]
@@ -74,45 +168,92 @@ impl ColorSpace {
74
168
75
169
/// Wide gamut color space
76
170
pub const WIDE : ColorSpace = ColorSpace {
77
- rgb : [
171
+ rgb : Matrix3 ( [
78
172
1.4625 , -0.1845 , -0.2734 , //
79
- -0.5228 , 1.4479 , 0.0681 , //
173
+ -0.5229 , 1.4479 , 0.0681 , //
80
174
0.0346 , -0.0958 , 1.2875 , //
81
- ] ,
82
- xyz : [
175
+ ] ) ,
176
+ xyz : Matrix3 ( [
83
177
0.7164 , 0.1010 , 0.1468 , //
84
178
0.2587 , 0.7247 , 0.0166 , //
85
179
0.0000 , 0.0512 , 0.7740 , //
86
- ] ,
180
+ ] ) ,
87
181
gamma : GammaCorrection :: NONE ,
88
182
} ;
89
183
90
184
/// sRGB color space
91
185
pub const SRGB : ColorSpace = ColorSpace {
92
- rgb : [
93
- 3.2405 , -1.5371 , -0.4985 , //
94
- -0.9693 , 1.8760 , 0.0416 , //
95
- 0.0556 , -0.2040 , 1.0572 , //
96
- ] ,
97
- xyz : [
186
+ rgb : Matrix3 ( [
187
+ 3.2401 , -1.5370 , -0.4983 , //
188
+ -0.9693 , 1.8760 , 0.0415 , //
189
+ 0.0558 , -0.2040 , 1.0572 , //
190
+ ] ) ,
191
+ xyz : Matrix3 ( [
98
192
0.4125 , 0.3576 , 0.1804 , //
99
193
0.2127 , 0.7152 , 0.0722 , //
100
194
0.0193 , 0.1192 , 0.9503 , //
101
- ] ,
195
+ ] ) ,
102
196
gamma : GammaCorrection :: SRGB ,
103
197
} ;
104
198
105
199
/// Adobe RGB color space
106
200
pub const ADOBE : ColorSpace = ColorSpace {
107
- rgb : [
201
+ rgb : Matrix3 ( [
108
202
2.0416 , -0.5652 , -0.3447 , //
109
203
-0.9695 , 1.8763 , 0.0415 , //
110
204
0.0135 , -0.1184 , 1.0154 , //
111
- ] ,
112
- xyz : [
205
+ ] ) ,
206
+ xyz : Matrix3 ( [
113
207
0.5767 , 0.1856 , 0.1882 , //
114
208
0.2974 , 0.6273 , 0.0753 , //
115
209
0.0270 , 0.0707 , 0.9911 , //
116
- ] ,
210
+ ] ) ,
117
211
gamma : GammaCorrection :: NONE ,
118
212
} ;
213
+
214
+ #[ cfg( test) ]
215
+ mod tests {
216
+ use std:: iter:: zip;
217
+
218
+ use crate :: colorspace:: { ColorSpace , ADOBE , SRGB , WIDE } ;
219
+
220
+ macro_rules! compare {
221
+ ( $expr: expr, $value: expr) => {
222
+ let a = $expr;
223
+ let b = $value;
224
+ eprintln!( "{a} vs {b:.4}" ) ;
225
+ assert!( ( a - b) . abs( ) < 1e-4 ) ;
226
+ } ;
227
+ }
228
+
229
+ fn verify_matrix ( cs : & ColorSpace ) {
230
+ let xyz = & cs. xyz ;
231
+ let rgb = & cs. rgb ;
232
+
233
+ let xyzi = xyz. inverted ( ) . unwrap ( ) ;
234
+ let rgbi = rgb. inverted ( ) . unwrap ( ) ;
235
+
236
+ zip ( xyz. 0 , rgbi. 0 ) . for_each ( |( a, b) | {
237
+ compare ! ( a, b) ;
238
+ } ) ;
239
+
240
+ zip ( rgb. 0 , xyzi. 0 ) . for_each ( |( a, b) | {
241
+ compare ! ( a, b) ;
242
+ } ) ;
243
+ }
244
+
245
+ #[ test]
246
+ fn iverse_wide ( ) {
247
+ verify_matrix ( & WIDE ) ;
248
+ }
249
+
250
+ #[ test]
251
+ fn iverse_srgb ( ) {
252
+ verify_matrix ( & SRGB ) ;
253
+ }
254
+
255
+ #[ test]
256
+ fn iverse_adobe ( ) {
257
+ verify_matrix ( & ADOBE ) ;
258
+ }
259
+ }
0 commit comments