Skip to main content

exif_oxide/implementations/nikon/
af_processing.rs

1//! Nikon AF (AutoFocus) system processing
2//!
3//! **Trust ExifTool**: This code translates ExifTool's Nikon AF processing verbatim.
4//!
5//! ExifTool Reference: lib/Image/ExifTool/Nikon.pm AF-related functions
6//!
7//! Nikon cameras have sophisticated AF systems ranging from 11-point (older cameras)
8//! to 405-point (Z8/Z9) with various detection modes and grid layouts.
9//!
10//! This module handles:
11//! - AF point extraction from binary data
12//! - AF area mode processing
13//! - Version-specific AF format handling
14//! - Grid-based AF coordinate mapping (newer cameras)
15
16use crate::exif::ExifReader;
17use crate::types::{ExifError, Result, TagValue};
18use tracing::{debug, trace};
19
20/// AF system types for different Nikon camera generations
21/// ExifTool: Various AF point hash references in Nikon.pm
22#[derive(Debug, Clone, PartialEq)]
23pub enum NikonAfSystem {
24    /// 11-point AF system (older DSLRs)
25    Points11,
26    /// 39-point AF system (D7000 series)
27    Points39,
28    /// 51-point AF system (D7100, D7200, D750, etc.)
29    Points51,
30    /// 153-point AF system (D500, D850)
31    Points153,
32    /// 105-point AF system (D6)
33    Points105,
34    /// 405-point AF system (Z8, Z9)
35    Points405,
36    /// Unknown or unsupported AF system
37    Unknown,
38}
39
40impl NikonAfSystem {
41    /// Determine AF system from camera model
42    /// ExifTool: Model-specific AF system detection logic
43    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            // Default to 11-point for older cameras
56            NikonAfSystem::Points11
57        }
58    }
59
60    /// Get the number of AF points for this system
61    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
74/// Process Nikon AF Info data from maker notes
75/// ExifTool: Nikon.pm AFInfo processing functions
76pub 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    // AF Info version detection
91    // ExifTool: AFInfo version from first 2 bytes
92    let version = u16::from_be_bytes([data[0], data[1]]);
93    // Store AF Info version tag with proper tag source
94    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    // Determine AF system from camera model
100    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    // Process version-specific AF info
108    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
128/// Process AF Info version 0100 (legacy cameras)
129/// ExifTool: Nikon.pm AFInfo version 0100 processing
130fn 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    // Basic AF information for older cameras
145    // ExifTool: Simple AF point extraction for legacy systems
146    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
161/// Process AF Info version 0102 (D70, D50, etc.)
162/// ExifTool: Nikon.pm AFInfo version 0102 processing
163fn 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    // AF area mode
178    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    // AF point in focus
183    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    // AF point selected
187    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
195/// Process AF Info version 0103 (D2X, D2Xs, D2H, D2Hs)
196/// ExifTool: Nikon.pm AFInfo version 0103 processing
197fn 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    // AF area mode
212    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    // AF point in focus
217    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    // AF points used (bitmask)
221    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        // Convert bitmask to readable format
226        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
233/// Process AF Info version 0106 (D40, D40x, D80, D200)
234/// ExifTool: Nikon.pm AFInfo version 0106 processing
235fn 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    // Phase detection AF
250    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    // Contrast detect AF
257    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    // AF area mode
264    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    // AF point in focus
271    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
280/// Process AF Info version 0107 (newer DSLRs)
281/// ExifTool: Nikon.pm AFInfo version 0107 processing
282fn 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    // Advanced AF information for newer cameras
297    // AF area mode (enhanced)
298    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    // Primary AF point
305    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    // AF points in focus (extended bitmask for 51-point systems)
312    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
327/// Process AF Info version 0300 (Z-series mirrorless)
328/// ExifTool: Nikon.pm AFInfo version 0300 processing
329fn 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    // Z-series specific AF processing
344    // Subject detection mode
345    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        // Convert to readable format
355        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    // AF area mode for Z-series
370    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    // Grid-based AF coordinates for 405-point system
377    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
384/// Process Z-series AF grid coordinates
385/// ExifTool: Z-series AF coordinate processing
386fn 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    // AF area position (grid coordinates)
394    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    // Convert to human-readable position
402    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
410/// Convert AF points bitmask to human-readable format
411/// ExifTool: PrintAFPoints function equivalent
412fn print_af_points_bitmask(bitmask: u16, af_system: &NikonAfSystem) -> String {
413    let mut active_points = Vec::new();
414
415    // Basic AF point mapping for legacy systems
416    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
431/// Convert extended AF points data to human-readable format
432/// ExifTool: Extended AF point processing for 51+ point systems
433fn 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    // For advanced AF systems, process multiple bytes of AF point data
439    let mut active_points = Vec::new();
440
441    // Process AF point bytes based on system type
442    match af_system {
443        NikonAfSystem::Points51 => {
444            // 51-point system uses 7 bytes of AF data
445            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            // 153-point system - simplified representation
458            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            // Fallback for other systems
465            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        // Check that version was extracted (tag 0x0088)
540        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        // Test single point
548        let result = print_af_points_bitmask(0b00000001, &af_system);
549        assert_eq!(result, "Point 1");
550
551        // Test multiple points
552        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        // Test no points
558        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]; // Too short
566
567        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        // Mock AF Info v0300 data with subject detection
577        let mut af_data = vec![0x03, 0x00]; // Version 0x0300
578        af_data.extend_from_slice(&[0x00, 0x00]); // Reserved
579        af_data.push(1); // Subject detection: Human
580        af_data.extend_from_slice(&[0x00, 0x02]); // AF area mode
581        af_data.resize(50, 0); // Pad to minimum size
582
583        let result = process_af_info_v0300(&mut reader, &af_data, &af_system);
584        assert!(result.is_ok());
585
586        // Check subject detection was processed (tags 0x0098, 0x0099)
587        assert!(reader.extracted_tags.contains_key(&0x0098));
588        assert!(reader.extracted_tags.contains_key(&0x0099));
589    }
590}