1pub mod light_source {
12 pub const STANDARD_LIGHT_A: u16 = 17;
14 pub const D55: u16 = 20;
16 pub const D65: u16 = 21;
18 pub const D75: u16 = 22;
20 pub const D50: u16 = 23;
22}
23
24#[derive(Debug, Clone)]
30pub struct CameraCalibration {
31 pub model: &'static str,
33 pub color_matrix_1: Option<[f64; 9]>,
35 pub illuminant_1: Option<u16>,
37 pub color_matrix_2: Option<[f64; 9]>,
39 pub illuminant_2: Option<u16>,
41}
42
43static CAMERA_DB: &[CameraCalibration] = &[
51 CameraCalibration {
54 model: "ILCE-7RM5",
55 color_matrix_1: None,
56 illuminant_1: None,
57 color_matrix_2: Some([
58 0.8200, -0.2976, -0.0719, -0.4296, 1.2053, 0.2532, -0.0429, 0.1282, 0.5774,
59 ]),
60 illuminant_2: Some(light_source::D65),
61 },
62 CameraCalibration {
63 model: "ILCE-7RM4",
64 color_matrix_1: None,
65 illuminant_1: None,
66 color_matrix_2: Some([
67 0.7411, -0.2508, -0.0559, -0.4571, 1.2162, 0.2710, -0.0533, 0.1440, 0.6226,
68 ]),
69 illuminant_2: Some(light_source::D65),
70 },
71 CameraCalibration {
72 model: "ILCE-7M4",
73 color_matrix_1: None,
74 illuminant_1: None,
75 color_matrix_2: Some([
76 0.7460, -0.2365, -0.0588, -0.5687, 1.3442, 0.2474, -0.0624, 0.1156, 0.6584,
77 ]),
78 illuminant_2: Some(light_source::D65),
79 },
80 CameraCalibration {
81 model: "ILCE-7SM3",
82 color_matrix_1: None,
83 illuminant_1: None,
84 color_matrix_2: Some([
85 0.6912, -0.2127, -0.0469, -0.4470, 1.1966, 0.2819, -0.0518, 0.1390, 0.6726,
86 ]),
87 illuminant_2: Some(light_source::D65),
88 },
89 CameraCalibration {
90 model: "ILCE-1",
91 color_matrix_1: None,
92 illuminant_1: None,
93 color_matrix_2: Some([
94 0.7803, -0.2768, -0.0621, -0.5009, 1.2742, 0.2615, -0.0666, 0.1561, 0.6404,
95 ]),
96 illuminant_2: Some(light_source::D65),
97 },
98 CameraCalibration {
99 model: "ILCE-6700",
100 color_matrix_1: None,
101 illuminant_1: None,
102 color_matrix_2: Some([
103 0.6972, -0.2408, -0.0600, -0.4330, 1.2101, 0.2515, -0.0388, 0.1277, 0.5847,
104 ]),
105 illuminant_2: Some(light_source::D65),
106 },
107 CameraCalibration {
110 model: "Canon EOS R5",
111 color_matrix_1: None,
112 illuminant_1: None,
113 color_matrix_2: Some([
114 0.9766, -0.3149, -0.0825, -0.5765, 1.3592, 0.2392, -0.0862, 0.1548, 0.6405,
115 ]),
116 illuminant_2: Some(light_source::D65),
117 },
118 CameraCalibration {
119 model: "Canon EOS R6",
120 color_matrix_1: None,
121 illuminant_1: None,
122 color_matrix_2: Some([
123 0.8616, -0.2350, -0.0791, -0.5765, 1.3592, 0.2392, -0.0862, 0.1548, 0.6405,
124 ]),
125 illuminant_2: Some(light_source::D65),
126 },
127 CameraCalibration {
128 model: "Canon EOS 5D Mark IV",
129 color_matrix_1: None,
130 illuminant_1: None,
131 color_matrix_2: Some([
132 0.6446, -0.0366, -0.0864, -0.4436, 1.2204, 0.2513, -0.0952, 0.2496, 0.6348,
133 ]),
134 illuminant_2: Some(light_source::D65),
135 },
136 CameraCalibration {
137 model: "Canon EOS R3",
138 color_matrix_1: None,
139 illuminant_1: None,
140 color_matrix_2: Some([
141 0.8197, -0.2503, -0.0804, -0.4289, 1.2316, 0.2222, -0.0505, 0.1349, 0.5791,
142 ]),
143 illuminant_2: Some(light_source::D65),
144 },
145 CameraCalibration {
148 model: "Nikon Z 6",
149 color_matrix_1: None,
150 illuminant_1: None,
151 color_matrix_2: Some([
152 0.7872, -0.2439, -0.0966, -0.5811, 1.3589, 0.2480, -0.1197, 0.2268, 0.7116,
153 ]),
154 illuminant_2: Some(light_source::D65),
155 },
156 CameraCalibration {
157 model: "Nikon Z 7",
158 color_matrix_1: None,
159 illuminant_1: None,
160 color_matrix_2: Some([
161 0.7636, -0.2576, -0.1027, -0.5765, 1.3555, 0.2476, -0.1292, 0.2406, 0.6988,
162 ]),
163 illuminant_2: Some(light_source::D65),
164 },
165 CameraCalibration {
166 model: "Nikon D850",
167 color_matrix_1: None,
168 illuminant_1: None,
169 color_matrix_2: Some([
170 1.0405, -0.3755, -0.1270, -0.5461, 1.3787, 0.1793, -0.1040, 0.2015, 0.6785,
171 ]),
172 illuminant_2: Some(light_source::D65),
173 },
174 CameraCalibration {
175 model: "Nikon Z 8",
176 color_matrix_1: None,
177 illuminant_1: None,
178 color_matrix_2: Some([
179 0.8527, -0.2868, -0.0960, -0.5037, 1.2684, 0.2642, -0.0660, 0.1187, 0.5986,
180 ]),
181 illuminant_2: Some(light_source::D65),
182 },
183 CameraCalibration {
184 model: "Nikon Z 9",
185 color_matrix_1: None,
186 illuminant_1: None,
187 color_matrix_2: Some([
188 0.8527, -0.2868, -0.0960, -0.5037, 1.2684, 0.2642, -0.0660, 0.1187, 0.5986,
189 ]),
190 illuminant_2: Some(light_source::D65),
191 },
192 CameraCalibration {
195 model: "Fujifilm X-T5",
196 color_matrix_1: None,
197 illuminant_1: None,
198 color_matrix_2: Some([
199 1.1210, -0.4957, -0.0988, -0.3603, 1.1710, 0.2177, -0.0426, 0.1143, 0.5851,
200 ]),
201 illuminant_2: Some(light_source::D65),
202 },
203 CameraCalibration {
204 model: "Fujifilm X-H2",
205 color_matrix_1: None,
206 illuminant_1: None,
207 color_matrix_2: Some([
208 1.1210, -0.4957, -0.0988, -0.3603, 1.1710, 0.2177, -0.0426, 0.1143, 0.5851,
209 ]),
210 illuminant_2: Some(light_source::D65),
211 },
212 CameraCalibration {
213 model: "Fujifilm X-T4",
214 color_matrix_1: None,
215 illuminant_1: None,
216 color_matrix_2: Some([
217 1.0862, -0.4721, -0.0860, -0.3310, 1.1261, 0.2325, -0.0379, 0.1082, 0.5765,
218 ]),
219 illuminant_2: Some(light_source::D65),
220 },
221 CameraCalibration {
222 model: "Fujifilm X100V",
223 color_matrix_1: None,
224 illuminant_1: None,
225 color_matrix_2: Some([
226 1.1434, -0.5063, -0.1041, -0.3604, 1.1715, 0.2172, -0.0551, 0.1356, 0.5811,
227 ]),
228 illuminant_2: Some(light_source::D65),
229 },
230 CameraCalibration {
233 model: "iPhone 13 Pro Max",
234 color_matrix_1: Some([
235 1.2270, -0.5450, -0.2610, -0.4550, 1.5180, -0.0430, -0.0410, 0.1640, 0.5910,
236 ]),
237 illuminant_1: Some(light_source::STANDARD_LIGHT_A),
238 color_matrix_2: Some([
239 0.9150, -0.3220, -0.1260, -0.4290, 1.3100, 0.0950, -0.1060, 0.2350, 0.4310,
240 ]),
241 illuminant_2: Some(light_source::D65),
242 },
243 CameraCalibration {
244 model: "iPhone 14 Pro Max",
245 color_matrix_1: Some([
246 1.2610, -0.5780, -0.2550, -0.4420, 1.5000, -0.0380, -0.0450, 0.1710, 0.5820,
247 ]),
248 illuminant_1: Some(light_source::STANDARD_LIGHT_A),
249 color_matrix_2: Some([
250 0.9320, -0.3420, -0.1320, -0.4180, 1.2980, 0.0960, -0.0980, 0.2250, 0.4410,
251 ]),
252 illuminant_2: Some(light_source::D65),
253 },
254 CameraCalibration {
255 model: "iPhone 15 Pro Max",
256 color_matrix_1: Some([
257 1.2850, -0.6020, -0.2430, -0.4310, 1.4900, -0.0300, -0.0380, 0.1450, 0.6120,
258 ]),
259 illuminant_1: Some(light_source::STANDARD_LIGHT_A),
260 color_matrix_2: Some([
261 0.9450, -0.3610, -0.1350, -0.4100, 1.2930, 0.0870, -0.0960, 0.2100, 0.4590,
262 ]),
263 illuminant_2: Some(light_source::D65),
264 },
265 CameraCalibration {
266 model: "iPhone 16 Pro Max",
267 color_matrix_1: Some([
268 1.3092, -0.6653, -0.2359, -0.4257, 1.4791, -0.0241, -0.0360, 0.1377, 0.6341,
269 ]),
270 illuminant_1: Some(light_source::STANDARD_LIGHT_A),
271 color_matrix_2: Some([
272 0.9564, -0.3793, -0.1339, -0.4043, 1.2963, 0.0853, -0.0940, 0.2064, 0.4659,
273 ]),
274 illuminant_2: Some(light_source::D65),
275 },
276 CameraCalibration {
277 model: "iPhone 16 Pro",
278 color_matrix_1: Some([
279 1.3092, -0.6653, -0.2359, -0.4257, 1.4791, -0.0241, -0.0360, 0.1377, 0.6341,
280 ]),
281 illuminant_1: Some(light_source::STANDARD_LIGHT_A),
282 color_matrix_2: Some([
283 0.9564, -0.3793, -0.1339, -0.4043, 1.2963, 0.0853, -0.0940, 0.2064, 0.4659,
284 ]),
285 illuminant_2: Some(light_source::D65),
286 },
287];
288
289#[deprecated(
293 since = "0.2.0",
294 note = "use `find_camera_calibration` which supports substring matching"
295)]
296pub fn get_camera_calibration(model: &str) -> Option<&'static CameraCalibration> {
297 CAMERA_DB.iter().find(|c| c.model == model)
298}
299
300pub fn find_camera_calibration(model: &str) -> Option<&'static CameraCalibration> {
305 CAMERA_DB
306 .iter()
307 .find(|c| model.contains(c.model) || c.model.contains(model))
308}
309
310pub fn all_cameras() -> &'static [CameraCalibration] {
312 CAMERA_DB
313}
314
315#[cfg(test)]
316mod tests {
317 use super::*;
318
319 #[test]
320 fn test_exact_lookup() {
321 let cal = find_camera_calibration("ILCE-7RM5").unwrap();
322 assert_eq!(cal.model, "ILCE-7RM5");
323 assert!(cal.color_matrix_2.is_some());
324 assert_eq!(cal.illuminant_2, Some(light_source::D65));
325 }
326
327 #[test]
328 fn test_substring_lookup() {
329 let cal = find_camera_calibration("Sony ILCE-7M4").unwrap();
330 assert_eq!(cal.model, "ILCE-7M4");
331 }
332
333 #[test]
334 fn test_canon_lookup() {
335 let cal = find_camera_calibration("Canon EOS R5").unwrap();
336 assert!(cal.color_matrix_2.is_some());
337 assert_eq!(cal.illuminant_2, Some(light_source::D65));
338 }
339
340 #[test]
341 fn test_nikon_lookup() {
342 let cal = find_camera_calibration("Nikon D850").unwrap();
343 assert!(cal.color_matrix_2.is_some());
344 }
345
346 #[test]
347 fn test_fujifilm_lookup() {
348 let cal = find_camera_calibration("Fujifilm X-T5").unwrap();
349 assert!(cal.color_matrix_2.is_some());
350 }
351
352 #[test]
353 fn test_iphone_dual_illuminant() {
354 let cal = find_camera_calibration("iPhone 16 Pro Max").unwrap();
355 assert!(cal.color_matrix_1.is_some());
356 assert!(cal.color_matrix_2.is_some());
357 assert_eq!(cal.illuminant_1, Some(light_source::STANDARD_LIGHT_A));
358 assert_eq!(cal.illuminant_2, Some(light_source::D65));
359 }
360
361 #[test]
362 fn test_unknown_camera() {
363 assert!(find_camera_calibration("Unknown Camera").is_none());
364 }
365
366 #[test]
367 fn test_all_cameras() {
368 let cameras = all_cameras();
369 assert!(
370 cameras.len() >= 20,
371 "expected at least 20 cameras, got {}",
372 cameras.len()
373 );
374 }
375
376 #[test]
377 fn test_matrix_values_valid() {
378 for cam in all_cameras() {
379 for (label, matrix) in [
380 ("ColorMatrix1", &cam.color_matrix_1),
381 ("ColorMatrix2", &cam.color_matrix_2),
382 ] {
383 if let Some(m) = matrix {
384 let row0_sum: f64 = m[0..3].iter().sum();
386 let row1_sum: f64 = m[3..6].iter().sum();
387 let row2_sum: f64 = m[6..9].iter().sum();
388 assert!(
389 row0_sum.abs() < 3.0,
390 "{} row 0 sum out of range for {}",
391 label,
392 cam.model
393 );
394 assert!(
395 row1_sum.abs() < 3.0,
396 "{} row 1 sum out of range for {}",
397 label,
398 cam.model
399 );
400 assert!(
401 row2_sum.abs() < 3.0,
402 "{} row 2 sum out of range for {}",
403 label,
404 cam.model
405 );
406 }
407 }
408 }
409 }
410
411 #[test]
412 fn test_lookup_known_make_model() {
413 let cal = find_camera_calibration("ILCE-7RM5");
415 assert!(cal.is_some(), "ILCE-7RM5 should be in the camera database");
416 let cal = cal.unwrap();
417 assert!(
418 cal.color_matrix_2.is_some(),
419 "ILCE-7RM5 should have a color matrix"
420 );
421 }
422
423 #[test]
424 fn test_lookup_unknown_returns_none() {
425 assert!(
426 find_camera_calibration("TOTALLY_FAKE_CAMERA_XYZ").is_none(),
427 "Unknown make/model should return None"
428 );
429 assert!(
432 find_camera_calibration("").is_some(),
433 "Empty string matches any model via substring"
434 );
435 }
436
437 #[test]
438 fn test_color_matrix_has_expected_shape() {
439 for cam in all_cameras() {
441 if let Some(m) = &cam.color_matrix_1 {
442 assert_eq!(m.len(), 9, "ColorMatrix1 for {} is not 3x3", cam.model);
443 }
444 if let Some(m) = &cam.color_matrix_2 {
445 assert_eq!(m.len(), 9, "ColorMatrix2 for {} is not 3x3", cam.model);
446 }
447 }
448 }
449}