1use crate::exif::ExifReader;
17use crate::types::{ExifError, Result, TagValue};
18use tracing::{debug, trace};
19
20#[derive(Debug, Clone, PartialEq)]
23pub enum NikonAfSystem {
24 Points11,
26 Points39,
28 Points51,
30 Points153,
32 Points105,
34 Points405,
36 Unknown,
38}
39
40impl NikonAfSystem {
41 pub fn from_camera_model(model: &str) -> Self {
44 if model.contains("Z 8") || model.contains("Z 9") {
45 NikonAfSystem::Points405
46 } else if model.contains("D6") {
47 NikonAfSystem::Points105
48 } else if model.contains("D500") || model.contains("D850") {
49 NikonAfSystem::Points153
50 } else if model.contains("D7100") || model.contains("D7200") || model.contains("D750") {
51 NikonAfSystem::Points51
52 } else if model.contains("D7000") {
53 NikonAfSystem::Points39
54 } else {
55 NikonAfSystem::Points11
57 }
58 }
59
60 pub fn point_count(&self) -> usize {
62 match self {
63 NikonAfSystem::Points11 => 11,
64 NikonAfSystem::Points39 => 39,
65 NikonAfSystem::Points51 => 51,
66 NikonAfSystem::Points153 => 153,
67 NikonAfSystem::Points105 => 105,
68 NikonAfSystem::Points405 => 405,
69 NikonAfSystem::Unknown => 0,
70 }
71 }
72}
73
74pub fn process_nikon_af_info(
77 reader: &mut ExifReader,
78 data: &[u8],
79 camera_model: &str,
80) -> Result<()> {
81 debug!("Processing Nikon AF Info for camera: {}", camera_model);
82
83 if data.len() < 4 {
84 return Err(ExifError::ParseError(format!(
85 "AFInfo data too short: {} bytes",
86 data.len()
87 )));
88 }
89
90 let version = u16::from_be_bytes([data[0], data[1]]);
93 let tag_source = reader.create_tag_source_info("Nikon");
95 reader.store_tag_with_precedence(0x0088, TagValue::U16(version), tag_source);
96
97 debug!("AF Info version: 0x{:04x}", version);
98
99 let af_system = NikonAfSystem::from_camera_model(camera_model);
101 debug!(
102 "Detected AF system: {:?} ({} points)",
103 af_system,
104 af_system.point_count()
105 );
106
107 match version {
109 0x0100 => process_af_info_v0100(reader, data, &af_system),
110 0x0102 => process_af_info_v0102(reader, data, &af_system),
111 0x0103 => process_af_info_v0103(reader, data, &af_system),
112 0x0106 => process_af_info_v0106(reader, data, &af_system),
113 0x0107 => process_af_info_v0107(reader, data, &af_system),
114 0x0300 => process_af_info_v0300(reader, data, &af_system),
115 _ => {
116 debug!("Unknown AF Info version: 0x{:04x}", version);
117 let tag_source = reader.create_tag_source_info("Nikon");
118 reader.store_tag_with_precedence(
119 0x0088,
120 TagValue::string(format!("Unknown version 0x{version:04x}")),
121 tag_source,
122 );
123 Ok(())
124 }
125 }
126}
127
128fn process_af_info_v0100(
131 reader: &mut ExifReader,
132 data: &[u8],
133 af_system: &NikonAfSystem,
134) -> Result<()> {
135 debug!("Processing AF Info v0100 for {:?}", af_system);
136
137 if data.len() < 8 {
138 return Err(ExifError::ParseError(format!(
139 "AFInfo v0100 data too short: {} bytes",
140 data.len()
141 )));
142 }
143
144 let af_point = data[4];
147 let tag_source = reader.create_tag_source_info("Nikon");
148 reader.store_tag_with_precedence(0x0089, TagValue::U8(af_point), tag_source.clone());
149
150 if af_point > 0 && af_point <= af_system.point_count() as u8 {
151 reader.store_tag_with_precedence(
152 0x008A,
153 TagValue::string(format!("Point {af_point}")),
154 tag_source,
155 );
156 }
157
158 Ok(())
159}
160
161fn process_af_info_v0102(
164 reader: &mut ExifReader,
165 data: &[u8],
166 af_system: &NikonAfSystem,
167) -> Result<()> {
168 debug!("Processing AF Info v0102 for {:?}", af_system);
169
170 if data.len() < 16 {
171 return Err(ExifError::ParseError(format!(
172 "AFInfo v0102 data too short: {} bytes",
173 data.len()
174 )));
175 }
176
177 let af_area_mode = data[2];
179 let tag_source = reader.create_tag_source_info("Nikon");
180 reader.store_tag_with_precedence(0x008B, TagValue::U8(af_area_mode), tag_source.clone());
181
182 let af_point_in_focus = data[3];
184 reader.store_tag_with_precedence(0x008C, TagValue::U8(af_point_in_focus), tag_source.clone());
185
186 if data.len() >= 5 {
188 let af_point_selected = data[4];
189 reader.store_tag_with_precedence(0x008D, TagValue::U8(af_point_selected), tag_source);
190 }
191
192 Ok(())
193}
194
195fn process_af_info_v0103(
198 reader: &mut ExifReader,
199 data: &[u8],
200 af_system: &NikonAfSystem,
201) -> Result<()> {
202 debug!("Processing AF Info v0103 for {:?}", af_system);
203
204 if data.len() < 16 {
205 return Err(ExifError::ParseError(format!(
206 "AFInfo v0103 data too short: {} bytes",
207 data.len()
208 )));
209 }
210
211 let af_area_mode = data[2];
213 let tag_source = reader.create_tag_source_info("Nikon");
214 reader.store_tag_with_precedence(0x008E, TagValue::U8(af_area_mode), tag_source.clone());
215
216 let af_point_in_focus = data[3];
218 reader.store_tag_with_precedence(0x008F, TagValue::U8(af_point_in_focus), tag_source.clone());
219
220 if data.len() >= 6 {
222 let af_points_used = u16::from_be_bytes([data[4], data[5]]);
223 reader.store_tag_with_precedence(0x0090, TagValue::U16(af_points_used), tag_source.clone());
224
225 let af_points_readable = print_af_points_bitmask(af_points_used, af_system);
227 reader.store_tag_with_precedence(0x0091, TagValue::String(af_points_readable), tag_source);
228 }
229
230 Ok(())
231}
232
233fn process_af_info_v0106(
236 reader: &mut ExifReader,
237 data: &[u8],
238 af_system: &NikonAfSystem,
239) -> Result<()> {
240 debug!("Processing AF Info v0106 for {:?}", af_system);
241
242 if data.len() < 20 {
243 return Err(ExifError::ParseError(format!(
244 "AFInfo v0106 data too short: {} bytes",
245 data.len()
246 )));
247 }
248
249 if data.len() >= 4 {
251 let phase_detect_af = data[2];
252 let tag_source = reader.create_tag_source_info("Nikon");
253 reader.store_tag_with_precedence(0x0092, TagValue::U8(phase_detect_af), tag_source);
254 }
255
256 if data.len() >= 5 {
258 let contrast_detect_af = data[3];
259 let tag_source = reader.create_tag_source_info("Nikon");
260 reader.store_tag_with_precedence(0x0093, TagValue::U8(contrast_detect_af), tag_source);
261 }
262
263 if data.len() >= 8 {
265 let af_area_mode = data[6];
266 let tag_source = reader.create_tag_source_info("Nikon");
267 reader.store_tag_with_precedence(0x0094, TagValue::U8(af_area_mode), tag_source);
268 }
269
270 if data.len() >= 9 {
272 let af_point_in_focus = data[7];
273 let tag_source = reader.create_tag_source_info("Nikon");
274 reader.store_tag_with_precedence(0x0095, TagValue::U8(af_point_in_focus), tag_source);
275 }
276
277 Ok(())
278}
279
280fn process_af_info_v0107(
283 reader: &mut ExifReader,
284 data: &[u8],
285 af_system: &NikonAfSystem,
286) -> Result<()> {
287 debug!("Processing AF Info v0107 for {:?}", af_system);
288
289 if data.len() < 30 {
290 return Err(ExifError::ParseError(format!(
291 "AFInfo v0107 data too short: {} bytes",
292 data.len()
293 )));
294 }
295
296 if data.len() >= 4 {
299 let af_area_mode = data[2];
300 let tag_source = reader.create_tag_source_info("Nikon");
301 reader.store_tag_with_precedence(0x0094, TagValue::U8(af_area_mode), tag_source);
302 }
303
304 if data.len() >= 5 {
306 let primary_af_point = data[3];
307 let tag_source = reader.create_tag_source_info("Nikon");
308 reader.store_tag_with_precedence(0x0096, TagValue::U8(primary_af_point), tag_source);
309 }
310
311 if data.len() >= 10
313 && matches!(
314 af_system,
315 NikonAfSystem::Points51 | NikonAfSystem::Points153
316 )
317 {
318 let af_points_bytes = &data[4..10];
319 let af_points_readable = print_af_points_extended(af_points_bytes, af_system);
320 let tag_source = reader.create_tag_source_info("Nikon");
321 reader.store_tag_with_precedence(0x0097, TagValue::String(af_points_readable), tag_source);
322 }
323
324 Ok(())
325}
326
327fn process_af_info_v0300(
330 reader: &mut ExifReader,
331 data: &[u8],
332 af_system: &NikonAfSystem,
333) -> Result<()> {
334 debug!("Processing AF Info v0300 (Z-series) for {:?}", af_system);
335
336 if data.len() < 50 {
337 return Err(ExifError::ParseError(format!(
338 "AFInfo v0300 data too short: {} bytes",
339 data.len()
340 )));
341 }
342
343 if data.len() >= 6 {
346 let subject_detection = data[4];
347 let tag_source = reader.create_tag_source_info("Nikon");
348 reader.store_tag_with_precedence(
349 0x0098,
350 TagValue::U8(subject_detection),
351 tag_source.clone(),
352 );
353
354 let detection_readable = match subject_detection {
356 0 => "Off",
357 1 => "Human",
358 2 => "Animal",
359 3 => "Vehicle",
360 _ => "Unknown",
361 };
362 reader.store_tag_with_precedence(
363 0x0099,
364 TagValue::string(detection_readable.to_string()),
365 tag_source,
366 );
367 }
368
369 if data.len() >= 8 {
371 let af_area_mode = data[6];
372 let tag_source = reader.create_tag_source_info("Nikon");
373 reader.store_tag_with_precedence(0x0094, TagValue::U8(af_area_mode), tag_source);
374 }
375
376 if matches!(af_system, NikonAfSystem::Points405) && data.len() >= 20 {
378 let _ = process_z_series_af_grid(reader, &data[10..20]);
379 }
380
381 Ok(())
382}
383
384fn process_z_series_af_grid(reader: &mut ExifReader, grid_data: &[u8]) -> Result<()> {
387 debug!("Processing Z-series AF grid coordinates");
388
389 if grid_data.len() < 8 {
390 return Ok(());
391 }
392
393 let af_x_position = i16::from_be_bytes([grid_data[0], grid_data[1]]);
395 let af_y_position = i16::from_be_bytes([grid_data[2], grid_data[3]]);
396
397 let tag_source = reader.create_tag_source_info("Nikon");
398 reader.store_tag_with_precedence(0x009A, TagValue::I16(af_x_position), tag_source.clone());
399 reader.store_tag_with_precedence(0x009B, TagValue::I16(af_y_position), tag_source.clone());
400
401 let readable_position = format!("({af_x_position}, {af_y_position})");
403 reader.store_tag_with_precedence(0x009C, TagValue::String(readable_position), tag_source);
404
405 trace!("AF grid position: ({}, {})", af_x_position, af_y_position);
406
407 Ok(())
408}
409
410fn print_af_points_bitmask(bitmask: u16, af_system: &NikonAfSystem) -> String {
413 let mut active_points = Vec::new();
414
415 for i in 0..af_system.point_count() {
417 if bitmask & (1 << i) != 0 {
418 active_points.push(format!("Point {}", i + 1));
419 }
420 }
421
422 if active_points.is_empty() {
423 "None".to_string()
424 } else if active_points.len() == 1 {
425 active_points[0].clone()
426 } else {
427 format!("Multiple ({})", active_points.join(", "))
428 }
429}
430
431fn print_af_points_extended(af_data: &[u8], af_system: &NikonAfSystem) -> String {
434 if af_data.len() < 6 {
435 return "Unknown".to_string();
436 }
437
438 let mut active_points = Vec::new();
440
441 match af_system {
443 NikonAfSystem::Points51 => {
444 for (byte_idx, &byte_val) in af_data.iter().enumerate().take(7) {
446 for bit in 0..8 {
447 if byte_val & (1 << bit) != 0 {
448 let point_num = byte_idx * 8 + bit + 1;
449 if point_num <= 51 {
450 active_points.push(format!("Point {point_num}"));
451 }
452 }
453 }
454 }
455 }
456 NikonAfSystem::Points153 => {
457 let primary_point = af_data[0];
459 if primary_point > 0 && primary_point <= 153 {
460 active_points.push(format!("Point {primary_point} (of 153)"));
461 }
462 }
463 _ => {
464 return format!("Unknown AF system: {af_system:?}");
466 }
467 }
468
469 if active_points.is_empty() {
470 "None".to_string()
471 } else if active_points.len() == 1 {
472 active_points[0].clone()
473 } else if active_points.len() <= 5 {
474 active_points.join(", ")
475 } else {
476 format!("{} points active", active_points.len())
477 }
478}
479
480#[cfg(test)]
481mod tests {
482 use super::*;
483 use crate::exif::ExifReader;
484
485 #[test]
486 fn test_af_system_detection() {
487 assert_eq!(
488 NikonAfSystem::from_camera_model("NIKON Z 9"),
489 NikonAfSystem::Points405
490 );
491 assert_eq!(
492 NikonAfSystem::from_camera_model("NIKON Z 8"),
493 NikonAfSystem::Points405
494 );
495 assert_eq!(
496 NikonAfSystem::from_camera_model("NIKON D6"),
497 NikonAfSystem::Points105
498 );
499 assert_eq!(
500 NikonAfSystem::from_camera_model("NIKON D850"),
501 NikonAfSystem::Points153
502 );
503 assert_eq!(
504 NikonAfSystem::from_camera_model("NIKON D500"),
505 NikonAfSystem::Points153
506 );
507 assert_eq!(
508 NikonAfSystem::from_camera_model("NIKON D7200"),
509 NikonAfSystem::Points51
510 );
511 assert_eq!(
512 NikonAfSystem::from_camera_model("NIKON D7000"),
513 NikonAfSystem::Points39
514 );
515 assert_eq!(
516 NikonAfSystem::from_camera_model("NIKON D3"),
517 NikonAfSystem::Points11
518 );
519 }
520
521 #[test]
522 fn test_af_system_point_count() {
523 assert_eq!(NikonAfSystem::Points405.point_count(), 405);
524 assert_eq!(NikonAfSystem::Points153.point_count(), 153);
525 assert_eq!(NikonAfSystem::Points51.point_count(), 51);
526 assert_eq!(NikonAfSystem::Points39.point_count(), 39);
527 assert_eq!(NikonAfSystem::Points11.point_count(), 11);
528 assert_eq!(NikonAfSystem::Unknown.point_count(), 0);
529 }
530
531 #[test]
532 fn test_af_info_version_extraction() {
533 let mut reader = ExifReader::new();
534 let af_data = [0x01, 0x00, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07];
535
536 let result = process_nikon_af_info(&mut reader, &af_data, "NIKON D850");
537 assert!(result.is_ok());
538
539 assert!(reader.extracted_tags.contains_key(&0x0088));
541 }
542
543 #[test]
544 fn test_af_points_bitmask() {
545 let af_system = NikonAfSystem::Points11;
546
547 let result = print_af_points_bitmask(0b00000001, &af_system);
549 assert_eq!(result, "Point 1");
550
551 let result = print_af_points_bitmask(0b00000101, &af_system);
553 assert!(result.contains("Multiple"));
554 assert!(result.contains("Point 1"));
555 assert!(result.contains("Point 3"));
556
557 let result = print_af_points_bitmask(0, &af_system);
559 assert_eq!(result, "None");
560 }
561
562 #[test]
563 fn test_af_info_insufficient_data() {
564 let mut reader = ExifReader::new();
565 let short_data = [0x01]; let result = process_nikon_af_info(&mut reader, &short_data, "NIKON D850");
568 assert!(result.is_err());
569 }
570
571 #[test]
572 fn test_z_series_subject_detection() {
573 let af_system = NikonAfSystem::Points405;
574 let mut reader = ExifReader::new();
575
576 let mut af_data = vec![0x03, 0x00]; af_data.extend_from_slice(&[0x00, 0x00]); af_data.push(1); af_data.extend_from_slice(&[0x00, 0x02]); af_data.resize(50, 0); let result = process_af_info_v0300(&mut reader, &af_data, &af_system);
584 assert!(result.is_ok());
585
586 assert!(reader.extracted_tags.contains_key(&0x0098));
588 assert!(reader.extracted_tags.contains_key(&0x0099));
589 }
590}