exif_oxide/raw/mod.rs
1//! RAW Image Format Support Module
2//!
3//! This module implements comprehensive RAW metadata extraction for all mainstream manufacturers.
4//! It provides a unified interface for processing manufacturer-specific RAW formats while
5//! following the Trust ExifTool principle exactly.
6//!
7//! ## Supported Formats (Milestone 17 Implementation)
8//!
9//! - **Kyocera** (.raw) - Milestone 17a: Simple ProcessBinaryData format (173 lines)
10//! - **Canon** (CR2, CR3) - Future: Complex TIFF-based with maker notes
11//! - **Nikon** (NEF, NRW) - Future: Integration with existing Nikon implementation
12//! - **Sony** (ARW, SR2, SRF) - Future: Advanced offset management
13//! - **Olympus** (ORF) - Future: Multiple IFD navigation
14//! - **Panasonic** (RW2) - Future: Entry-based offset handling
15//! - **Fujifilm** (RAF) - Future: Non-TIFF format
16//!
17//! ## Architecture
18//!
19//! The RAW processing system uses a hybrid approach:
20//! - **Format Detection**: FileTypeDetector identifies RAW file types
21//! - **RAW Processor**: Central dispatcher routes to manufacturer handlers
22//! - **Handler Traits**: Manufacturer-specific processing implementations
23//! - **TIFF Foundation**: Leverages existing TIFF infrastructure for TIFF-based formats
24//!
25//! ## Trust ExifTool Compliance
26//!
27//! All implementations strictly follow ExifTool's processing logic:
28//! - Exact offset calculations and data parsing
29//! - Identical tag naming and grouping
30//! - Preserved quirks and manufacturer-specific handling
31//! - No "improvements" or "optimizations" to the original logic
32
33pub mod detector;
34pub mod offset;
35pub mod processor;
36
37// Re-export main types for convenience
38pub use detector::{detect_raw_format, RawFormat};
39pub use offset::{EntryBasedOffsetProcessor, OffsetContext, SimpleOffsetProcessor};
40pub use processor::{RawFormatHandler, RawProcessor};
41
42// Import format-specific handlers (will expand as we add more formats)
43pub mod formats {
44 //! Manufacturer-specific RAW format handlers
45
46 pub mod kyocera;
47 pub mod minolta;
48 pub mod panasonic;
49
50 // Future format handlers will be added here:
51 // pub mod canon;
52 // pub mod nikon;
53 // pub mod sony;
54 // pub mod olympus;
55 // pub mod fujifilm;
56}
57
58// Re-export format handlers and utility functions
59pub use formats::kyocera::get_kyocera_tag_name;
60pub use formats::minolta::get_minolta_tag_name;
61pub use formats::panasonic::get_panasonic_tag_name;
62
63pub mod utils {
64 //! RAW processing utilities and helper functions
65
66 /// Reverse a byte string (Kyocera-specific utility)
67 /// ExifTool: KyoceraRaw.pm ReverseString function
68 /// Kyocera stores strings in byte-reversed format for unknown reasons
69 pub fn reverse_string(input: &[u8]) -> String {
70 let reversed_bytes: Vec<u8> = input.iter().copied().rev().collect();
71 String::from_utf8_lossy(&reversed_bytes)
72 .trim_start_matches('\0')
73 .trim_end_matches('\0')
74 .to_string()
75 }
76
77 /// Calculate exposure time from Kyocera-specific encoding
78 /// ExifTool: KyoceraRaw.pm ExposureTime calculation
79 /// Formula: 2^(val/8) / 16000
80 pub fn kyocera_exposure_time(val: u32) -> f64 {
81 if val == 0 {
82 return 0.0;
83 }
84 let exponent = val as f64 / 8.0;
85 2_f64.powf(exponent) / 16000.0
86 }
87
88 /// Calculate F-number from Kyocera-specific encoding
89 /// ExifTool: KyoceraRaw.pm FNumber calculation
90 /// Formula: 2^(val/16)
91 pub fn kyocera_fnumber(val: u32) -> f64 {
92 if val == 0 {
93 return 0.0;
94 }
95 let exponent = val as f64 / 16.0;
96 2_f64.powf(exponent)
97 }
98
99 /// Convert Kyocera internal ISO values to standard ISO speeds
100 /// ExifTool: KyoceraRaw.pm %isoLookup hash
101 /// Maps internal values 7-19 to ISO speeds 25-400
102 pub fn kyocera_iso_lookup(val: u32) -> Option<u32> {
103 match val {
104 7 => Some(25),
105 8 => Some(32),
106 9 => Some(40),
107 10 => Some(50),
108 11 => Some(64),
109 12 => Some(80),
110 13 => Some(100),
111 14 => Some(125),
112 15 => Some(160),
113 16 => Some(200),
114 17 => Some(250),
115 18 => Some(320),
116 19 => Some(400),
117 _ => None,
118 }
119 }
120}
121
122#[cfg(test)]
123mod tests {
124 use super::utils::*;
125
126 #[test]
127 fn test_reverse_string() {
128 let input = b"KYOCERA\0";
129 let result = reverse_string(input);
130 assert_eq!(result, "ARECOYK");
131
132 // Test with null termination
133 let input2 = b"TEST\0\0\0";
134 let result2 = reverse_string(input2);
135 assert_eq!(result2, "TSET");
136 }
137
138 #[test]
139 fn test_kyocera_exposure_time() {
140 // Test some known values
141 assert!((kyocera_exposure_time(0) - 0.0).abs() < f64::EPSILON);
142
143 // Test calculation: 2^(val/8) / 16000
144 let val = 64; // Should be 2^8 / 16000 = 256 / 16000 = 0.016
145 let expected = 2_f64.powf(64.0 / 8.0) / 16000.0;
146 assert!((kyocera_exposure_time(val) - expected).abs() < 0.0001);
147 }
148
149 #[test]
150 fn test_kyocera_fnumber() {
151 // Test some known values
152 assert!((kyocera_fnumber(0) - 0.0).abs() < f64::EPSILON);
153
154 // Test calculation: 2^(val/16)
155 let val = 32; // Should be 2^2 = 4.0
156 let expected = 2_f64.powf(32.0 / 16.0);
157 assert!((kyocera_fnumber(val) - expected).abs() < 0.0001);
158 }
159
160 #[test]
161 fn test_kyocera_iso_lookup() {
162 // Test known mappings
163 assert_eq!(kyocera_iso_lookup(7), Some(25));
164 assert_eq!(kyocera_iso_lookup(13), Some(100));
165 assert_eq!(kyocera_iso_lookup(19), Some(400));
166
167 // Test invalid values
168 assert_eq!(kyocera_iso_lookup(6), None);
169 assert_eq!(kyocera_iso_lookup(20), None);
170 assert_eq!(kyocera_iso_lookup(100), None);
171 }
172}