exif_oxide/raw/formats/panasonic.rs
1//! Panasonic RAW format handler
2
3#![allow(dead_code, unused_variables)]
4//!
5//! This module implements ExifTool's PanasonicRaw.pm processing logic exactly.
6//! Panasonic RW2/RWL files are TIFF-based formats with manufacturer-specific tags
7//! and entry-based offset handling for some data blocks.
8//!
9//! ExifTool Reference: lib/Image/ExifTool/PanasonicRaw.pm (956 lines total)
10//! Processing: Standard TIFF IFD processing with specialized entry-based offsets
11
12use crate::exif::ExifReader;
13use crate::raw::offset::{
14 EntryBasedOffsetProcessor, OffsetBase, OffsetExtractionRule, OffsetField,
15};
16use crate::raw::RawFormatHandler;
17use crate::tiff_types::TiffHeader;
18use crate::types::{DirectoryInfo, ExifError, Result, TagSourceInfo, TagValue};
19use std::collections::HashMap;
20use tracing;
21
22/// Panasonic RAW format handler
23/// ExifTool: lib/Image/ExifTool/PanasonicRaw.pm - TIFF-based with entry-based offsets
24pub struct PanasonicRawHandler {
25 /// Main binary data processor for standard Panasonic tags
26 /// ExifTool: %Image::ExifTool::PanasonicRaw::Main hash (lines 70-380)
27 binary_processor: PanasonicBinaryProcessor,
28
29 /// Entry-based offset processor for special data blocks
30 /// ExifTool: Tags like JpgFromRaw (0x002e) use entry-based offsets
31 offset_processor: EntryBasedOffsetProcessor,
32}
33
34/// Binary data processor for Panasonic main tags
35/// ExifTool: %Image::ExifTool::PanasonicRaw::Main hash (lines 70-380)
36struct PanasonicBinaryProcessor {
37 /// Tag definitions for main Panasonic IFD
38 tag_definitions: HashMap<u16, PanasonicTagDef>,
39}
40
41/// Panasonic tag definition
42/// ExifTool: Individual entries in %Image::ExifTool::PanasonicRaw::Main
43#[derive(Debug, Clone)]
44struct PanasonicTagDef {
45 /// Tag name
46 /// ExifTool: Name field in tag hash
47 name: String,
48
49 /// Data format
50 /// ExifTool: Format field or inferred from type
51 format: PanasonicFormat,
52
53 /// Value conversion
54 /// ExifTool: ValueConv field
55 value_conv: Option<PanasonicValueConv>,
56
57 /// Print conversion
58 /// ExifTool: PrintConv field
59 print_conv: Option<PanasonicPrintConv>,
60
61 /// Groups assignment
62 /// ExifTool: Groups field override
63 groups: Option<PanasonicGroups>,
64
65 /// Writability
66 /// ExifTool: Writable field
67 writable: bool,
68}
69
70/// Panasonic data formats
71/// ExifTool: Format specifications in tag definitions
72#[derive(Debug, Clone)]
73enum PanasonicFormat {
74 /// Undefined binary data
75 /// ExifTool: undef format
76 Undef,
77
78 /// 16-bit unsigned integer
79 /// ExifTool: int16u format
80 Int16u,
81
82 /// 32-bit unsigned integer
83 /// ExifTool: int32u format
84 Int32u,
85
86 /// String data
87 /// ExifTool: string format
88 String,
89
90 /// Array of int16u values
91 /// ExifTool: int16u[N] format
92 Int16uArray(usize),
93}
94
95/// Panasonic value conversion types
96/// ExifTool: ValueConv fields in tag definitions
97#[derive(Debug, Clone)]
98enum PanasonicValueConv {
99 /// Division by constant
100 /// ExifTool: '$val / 256' pattern
101 DivideBy256,
102
103 /// Multiplication by constant
104 /// ExifTool: '$val * 100' pattern
105 MultiplyBy100,
106}
107
108/// Panasonic print conversion types
109/// ExifTool: PrintConv fields in tag definitions
110#[derive(Debug, Clone)]
111enum PanasonicPrintConv {
112 /// CFA pattern lookup
113 /// ExifTool: lines 96-102 - CFA pattern hash
114 CfaPattern,
115
116 /// Compression type lookup
117 /// ExifTool: lines 109-114 - Compression hash
118 Compression,
119
120 /// Orientation lookup (reuses EXIF orientation)
121 /// ExifTool: line 250 - \%Image::ExifTool::Exif::orientation
122 Orientation,
123
124 /// PrintConv hash lookup
125 /// ExifTool: Static hash with key-value mappings
126 HashLookup(HashMap<u32, String>),
127}
128
129/// Panasonic group assignments
130/// ExifTool: Groups field overrides default groups
131#[derive(Debug, Clone)]
132struct PanasonicGroups {
133 /// Group 0 (family)
134 /// ExifTool: Groups => { 0 => '...' }
135 group0: Option<String>,
136
137 /// Group 1 (location)
138 /// ExifTool: Groups => { 1 => '...' }
139 group1: Option<String>,
140
141 /// Group 2 (category)
142 /// ExifTool: Groups => { 2 => '...' }
143 group2: Option<String>,
144}
145
146impl Default for PanasonicRawHandler {
147 fn default() -> Self {
148 Self::new()
149 }
150}
151
152impl PanasonicRawHandler {
153 /// Process entry-based offsets for Panasonic-specific data blocks
154 /// ExifTool: PanasonicRaw.pm applies additional processing for tags like JpgFromRaw (0x002e)
155 fn process_entry_based_offsets(&self, reader: &mut ExifReader, data: &[u8]) -> Result<()> {
156 // Get the currently extracted tags from TIFF IFD processing
157 // Clone the tags to avoid borrowing issues
158 let extracted_tags: Vec<(u16, TagValue)> = reader
159 .get_extracted_tags()
160 .iter()
161 .map(|(tag_id, tag_value)| (*tag_id, tag_value.clone()))
162 .collect();
163
164 // Process each tag that has entry-based offset rules
165 for (tag_id, tag_value) in extracted_tags {
166 if let Some(rule) = self.offset_processor.get_rule(tag_id) {
167 // Extract offset from the tag value based on the rule
168 let offset = match rule.offset_field {
169 OffsetField::ActualValue => {
170 // The tag value itself is the offset
171 match tag_value.as_u32() {
172 Some(val) => val as u64,
173 None => continue, // Skip if not convertible to u32
174 }
175 }
176 OffsetField::ValueOffset => {
177 // This would be for more complex offset extraction
178 // For now, treat as actual value
179 match tag_value.as_u32() {
180 Some(val) => val as u64,
181 None => continue,
182 }
183 }
184 };
185
186 // Calculate the final offset based on the rule's base
187 let final_offset = match rule.base {
188 OffsetBase::FileStart => offset + rule.additional_offset as u64,
189 OffsetBase::IfdStart => {
190 // Would need IFD start position - for now use file start
191 offset + rule.additional_offset as u64
192 }
193 OffsetBase::MakerNoteStart => {
194 // Would need maker note start - for now use file start
195 offset + rule.additional_offset as u64
196 }
197 OffsetBase::DataPosition => offset + rule.additional_offset as u64,
198 };
199
200 // Extract data at the calculated offset
201 // ExifTool: PanasonicRaw.pm extracts additional data blocks at these offsets
202 if let Some(extracted_data) =
203 self.extract_data_at_offset(data, final_offset, tag_id)
204 {
205 // Process the extracted data based on the tag type
206 self.process_offset_data(reader, tag_id, &extracted_data)?;
207 }
208 }
209 }
210
211 Ok(())
212 }
213
214 /// Extract data at a specific offset for entry-based processing
215 /// ExifTool: PanasonicRaw.pm extracts data blocks at calculated offsets
216 fn extract_data_at_offset(&self, data: &[u8], offset: u64, tag_id: u16) -> Option<Vec<u8>> {
217 let offset = offset as usize;
218
219 // Bounds check
220 if offset >= data.len() {
221 return None;
222 }
223
224 // Determine data size based on tag type
225 // ExifTool: Different tags extract different amounts of data
226 let size = match tag_id {
227 0x002e => 4, // JpgFromRaw - typically a 4-byte offset
228 0x0127 => 4, // JpgFromRaw2 - typically a 4-byte offset
229 _ => 4, // Default size for unknown entry-based tags
230 };
231
232 // Extract the data block
233 if offset + size <= data.len() {
234 Some(data[offset..offset + size].to_vec())
235 } else {
236 // Extract remaining data if partial block available
237 Some(data[offset..].to_vec())
238 }
239 }
240
241 /// Process data extracted at calculated offsets
242 /// ExifTool: PanasonicRaw.pm processes different types of offset data
243 fn process_offset_data(
244 &self,
245 _reader: &mut ExifReader,
246 tag_id: u16,
247 data: &[u8],
248 ) -> Result<()> {
249 // Process based on the specific tag type
250 match tag_id {
251 0x002e => {
252 // JpgFromRaw - points to embedded JPEG data
253 // For now, just note that we found the offset
254 if data.len() >= 4 {
255 let jpeg_offset = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
256 // Could extract JPEG metadata here in the future
257 // ExifTool: PanasonicRaw.pm extracts embedded JPEG EXIF data
258 tracing::debug!("Found JpgFromRaw offset: {:#x}", jpeg_offset);
259 }
260 }
261 0x0127 => {
262 // JpgFromRaw2 - alternate JPEG data pointer
263 if data.len() >= 4 {
264 let jpeg_offset = u32::from_le_bytes([data[0], data[1], data[2], data[3]]);
265 tracing::debug!("Found JpgFromRaw2 offset: {:#x}", jpeg_offset);
266 }
267 }
268 _ => {
269 // Unknown entry-based tag - log for debugging
270 tracing::debug!(
271 "Processed entry-based offset for tag {:#x}: {} bytes",
272 tag_id,
273 data.len()
274 );
275 }
276 }
277
278 Ok(())
279 }
280 /// Create new Panasonic RAW handler with processors
281 /// ExifTool: %Image::ExifTool::PanasonicRaw::Main hash construction
282 pub fn new() -> Self {
283 Self {
284 binary_processor: PanasonicBinaryProcessor::new(),
285 offset_processor: Self::create_offset_processor(),
286 }
287 }
288
289 /// Create entry-based offset processor with Panasonic rules
290 /// ExifTool: PanasonicRaw.pm entry-based offset handling
291 fn create_offset_processor() -> EntryBasedOffsetProcessor {
292 let mut offset_rules = HashMap::new();
293
294 // JpgFromRaw tag at 0x002e uses entry-based offset
295 // ExifTool: line 198 - 0x2e => { Name => 'JpgFromRaw', ... }
296 // The offset is stored in the IFD entry value and points to JPEG data
297 offset_rules.insert(
298 0x002e,
299 OffsetExtractionRule {
300 tag_id: 0x002e,
301 offset_field: OffsetField::ActualValue,
302 base: OffsetBase::FileStart,
303 additional_offset: 0,
304 },
305 );
306
307 // JpgFromRaw2 tag at 0x0127 (newer models)
308 // ExifTool: line 301 - 0x127 => { Name => 'JpgFromRaw2', ... }
309 offset_rules.insert(
310 0x0127,
311 OffsetExtractionRule {
312 tag_id: 0x0127,
313 offset_field: OffsetField::ActualValue,
314 base: OffsetBase::FileStart,
315 additional_offset: 0,
316 },
317 );
318
319 EntryBasedOffsetProcessor::new(offset_rules)
320 }
321}
322
323impl RawFormatHandler for PanasonicRawHandler {
324 /// Process Panasonic RW2/RWL data
325 /// ExifTool: Standard TIFF processing with Panasonic-specific handling
326 fn process_raw(&self, reader: &mut ExifReader, data: &[u8]) -> Result<()> {
327 // Panasonic RW2/RWL files are TIFF-based, so we parse the TIFF header first
328 // ExifTool: lib/Image/ExifTool/PanasonicRaw.pm uses standard TIFF processing
329
330 // Step 1: Parse TIFF header to get IFD structure
331 let header = TiffHeader::parse(data)?;
332 reader.set_test_header(header.clone());
333 reader.set_test_data(data.to_vec());
334
335 // Step 2: Process the main IFD using existing TIFF infrastructure
336 // ExifTool: PanasonicRaw.pm processes TIFF IFDs first, then applies special handling
337 let dir_info = DirectoryInfo {
338 name: "PanasonicRaw".to_string(),
339 dir_start: header.ifd0_offset as usize,
340 dir_len: 0, // Will be calculated by IFD processing
341 base: 0, // Standard TIFF base
342 data_pos: 0, // No additional data position offset
343 allow_reprocess: false,
344 };
345
346 // Process the TIFF IFD to extract real tag entries
347 reader.process_subdirectory(&dir_info)?;
348
349 // Step 3: Apply entry-based offset processing to extracted IFD entries
350 // ExifTool: PanasonicRaw.pm applies additional processing for specific tags like JpgFromRaw
351 self.process_entry_based_offsets(reader, data)?;
352
353 Ok(())
354 }
355
356 fn name(&self) -> &'static str {
357 "PanasonicRaw"
358 }
359
360 fn validate_format(&self, data: &[u8]) -> bool {
361 // ExifTool: PanasonicRaw.pm validation logic - TIFF-based format
362 super::super::detector::validate_panasonic_rw2_magic(data)
363 }
364}
365
366impl PanasonicBinaryProcessor {
367 /// Create new Panasonic binary processor with tag definitions
368 /// ExifTool: %Image::ExifTool::PanasonicRaw::Main hash (lines 70-380)
369 fn new() -> Self {
370 let mut tag_definitions = HashMap::new();
371
372 // PanasonicRawVersion at 0x01
373 // ExifTool: line 76 - 0x01 => { Name => 'PanasonicRawVersion', Writable => 'undef' }
374 tag_definitions.insert(
375 0x01,
376 PanasonicTagDef {
377 name: "PanasonicRawVersion".to_string(),
378 format: PanasonicFormat::Undef,
379 value_conv: None,
380 print_conv: None,
381 groups: None,
382 writable: true,
383 },
384 );
385
386 // SensorWidth at 0x02
387 // ExifTool: line 80 - 0x02 => 'SensorWidth'
388 tag_definitions.insert(
389 0x02,
390 PanasonicTagDef {
391 name: "SensorWidth".to_string(),
392 format: PanasonicFormat::Int16u,
393 value_conv: None,
394 print_conv: None,
395 groups: None,
396 writable: false,
397 },
398 );
399
400 // SensorHeight at 0x03
401 // ExifTool: line 81 - 0x03 => 'SensorHeight'
402 tag_definitions.insert(
403 0x03,
404 PanasonicTagDef {
405 name: "SensorHeight".to_string(),
406 format: PanasonicFormat::Int16u,
407 value_conv: None,
408 print_conv: None,
409 groups: None,
410 writable: false,
411 },
412 );
413
414 // SensorTopBorder at 0x04
415 // ExifTool: line 82 - 0x04 => 'SensorTopBorder'
416 tag_definitions.insert(
417 0x04,
418 PanasonicTagDef {
419 name: "SensorTopBorder".to_string(),
420 format: PanasonicFormat::Int16u,
421 value_conv: None,
422 print_conv: None,
423 groups: None,
424 writable: false,
425 },
426 );
427
428 // SensorLeftBorder at 0x05
429 // ExifTool: line 83 - 0x05 => 'SensorLeftBorder'
430 tag_definitions.insert(
431 0x05,
432 PanasonicTagDef {
433 name: "SensorLeftBorder".to_string(),
434 format: PanasonicFormat::Int16u,
435 value_conv: None,
436 print_conv: None,
437 groups: None,
438 writable: false,
439 },
440 );
441
442 // SensorBottomBorder at 0x06
443 // ExifTool: line 84 - 0x06 => 'SensorBottomBorder'
444 tag_definitions.insert(
445 0x06,
446 PanasonicTagDef {
447 name: "SensorBottomBorder".to_string(),
448 format: PanasonicFormat::Int16u,
449 value_conv: None,
450 print_conv: None,
451 groups: None,
452 writable: false,
453 },
454 );
455
456 // SensorRightBorder at 0x07
457 // ExifTool: line 85 - 0x07 => 'SensorRightBorder'
458 tag_definitions.insert(
459 0x07,
460 PanasonicTagDef {
461 name: "SensorRightBorder".to_string(),
462 format: PanasonicFormat::Int16u,
463 value_conv: None,
464 print_conv: None,
465 groups: None,
466 writable: false,
467 },
468 );
469
470 // CFAPattern at 0x09
471 // ExifTool: line 92 - 0x09 => { Name => 'CFAPattern', ... PrintConv => {...} }
472 let mut cfa_pattern_lookup = HashMap::new();
473 cfa_pattern_lookup.insert(0, "n/a".to_string());
474 cfa_pattern_lookup.insert(1, "[Red,Green][Green,Blue]".to_string());
475 cfa_pattern_lookup.insert(2, "[Green,Red][Blue,Green]".to_string());
476 cfa_pattern_lookup.insert(3, "[Green,Blue][Red,Green]".to_string());
477 cfa_pattern_lookup.insert(4, "[Blue,Green][Green,Red]".to_string());
478
479 tag_definitions.insert(
480 0x09,
481 PanasonicTagDef {
482 name: "CFAPattern".to_string(),
483 format: PanasonicFormat::Int16u,
484 value_conv: None,
485 print_conv: Some(PanasonicPrintConv::HashLookup(cfa_pattern_lookup)),
486 groups: None,
487 writable: true,
488 },
489 );
490
491 // BitsPerSample at 0x0a
492 // ExifTool: line 104 - 0x0a => { Name => 'BitsPerSample', Writable => 'int16u', Protected => 1 }
493 tag_definitions.insert(
494 0x0a,
495 PanasonicTagDef {
496 name: "BitsPerSample".to_string(),
497 format: PanasonicFormat::Int16u,
498 value_conv: None,
499 print_conv: None,
500 groups: None,
501 writable: true,
502 },
503 );
504
505 // Compression at 0x0b
506 // ExifTool: line 105 - 0x0b => { Name => 'Compression', ... PrintConv => {...} }
507 let mut compression_lookup = HashMap::new();
508 compression_lookup.insert(34316, "Panasonic RAW 1".to_string());
509 compression_lookup.insert(34826, "Panasonic RAW 2".to_string());
510 compression_lookup.insert(34828, "Panasonic RAW 3".to_string());
511 compression_lookup.insert(34830, "Panasonic RAW 4".to_string());
512
513 tag_definitions.insert(
514 0x0b,
515 PanasonicTagDef {
516 name: "Compression".to_string(),
517 format: PanasonicFormat::Int16u,
518 value_conv: None,
519 print_conv: Some(PanasonicPrintConv::HashLookup(compression_lookup)),
520 groups: None,
521 writable: true,
522 },
523 );
524
525 // RedBalance at 0x11
526 // ExifTool: line 122 - 0x11 => { Name => 'RedBalance', Writable => 'int16u', ValueConv => '$val / 256', ... }
527 tag_definitions.insert(
528 0x11,
529 PanasonicTagDef {
530 name: "RedBalance".to_string(),
531 format: PanasonicFormat::Int16u,
532 value_conv: Some(PanasonicValueConv::DivideBy256),
533 print_conv: None,
534 groups: None,
535 writable: true,
536 },
537 );
538
539 // BlueBalance at 0x12
540 // ExifTool: line 129 - 0x12 => { Name => 'BlueBalance', Writable => 'int16u', ValueConv => '$val / 256', ... }
541 tag_definitions.insert(
542 0x12,
543 PanasonicTagDef {
544 name: "BlueBalance".to_string(),
545 format: PanasonicFormat::Int16u,
546 value_conv: Some(PanasonicValueConv::DivideBy256),
547 print_conv: None,
548 groups: None,
549 writable: true,
550 },
551 );
552
553 // ISO at 0x17
554 // ExifTool: line 139 - 0x17 => { Name => 'ISO', Writable => 'int16u' }
555 tag_definitions.insert(
556 0x17,
557 PanasonicTagDef {
558 name: "ISO".to_string(),
559 format: PanasonicFormat::Int16u,
560 value_conv: None,
561 print_conv: None,
562 groups: None,
563 writable: true,
564 },
565 );
566
567 Self { tag_definitions }
568 }
569
570 /// Process Panasonic binary data
571 /// ExifTool: ProcessBinaryData equivalent for Panasonic tags
572 fn process(&self, reader: &mut ExifReader, data: &[u8]) -> Result<()> {
573 // Since RW2/RWL are TIFF-based, we need proper TIFF IFD processing
574 // For now, we'll extract what we can from the beginning of the data
575 // TODO: Implement proper TIFF IFD parsing integration
576
577 // For basic demonstration, process a few tags if data is available
578 if data.len() < 20 {
579 return Ok(()); // Not enough data for meaningful processing
580 }
581
582 // This is a simplified placeholder - real implementation would:
583 // 1. Parse TIFF header to determine byte order
584 // 2. Read IFD entries
585 // 3. Extract values based on tag definitions
586 // 4. Apply value/print conversions
587
588 // Store a basic tag to show the handler is working
589 let source_info =
590 TagSourceInfo::new("EXIF".to_string(), "IFD0".to_string(), "Image".to_string());
591
592 // Placeholder: create a basic tag entry
593 reader.extracted_tags.insert(
594 0x0001, // PanasonicRawVersion equivalent
595 TagValue::String("Panasonic RW2/RWL".to_string()),
596 );
597 reader.tag_sources.insert(0x0001, source_info);
598
599 Ok(())
600 }
601
602 /// Extract value from data based on format
603 /// ExifTool: Format-specific value extraction
604 #[allow(dead_code)]
605 fn extract_value(
606 &self,
607 data: &[u8],
608 offset: usize,
609 format: &PanasonicFormat,
610 byte_order: ByteOrder,
611 ) -> Result<TagValue> {
612 match format {
613 PanasonicFormat::Int16u => {
614 if offset + 2 > data.len() {
615 return Err(ExifError::ParseError(format!(
616 "Int16u at offset {offset:#x} extends beyond data bounds"
617 )));
618 }
619 let value = match byte_order {
620 ByteOrder::BigEndian => u16::from_be_bytes([data[offset], data[offset + 1]]),
621 ByteOrder::LittleEndian => u16::from_le_bytes([data[offset], data[offset + 1]]),
622 };
623 Ok(TagValue::U16(value))
624 }
625 PanasonicFormat::Int32u => {
626 if offset + 4 > data.len() {
627 return Err(ExifError::ParseError(format!(
628 "Int32u at offset {offset:#x} extends beyond data bounds"
629 )));
630 }
631 let value = match byte_order {
632 ByteOrder::BigEndian => u32::from_be_bytes([
633 data[offset],
634 data[offset + 1],
635 data[offset + 2],
636 data[offset + 3],
637 ]),
638 ByteOrder::LittleEndian => u32::from_le_bytes([
639 data[offset],
640 data[offset + 1],
641 data[offset + 2],
642 data[offset + 3],
643 ]),
644 };
645 Ok(TagValue::U32(value))
646 }
647 PanasonicFormat::String => {
648 // For string, we'd need to know the length or find null terminator
649 // This is a simplified implementation
650 let mut end = offset;
651 while end < data.len() && data[end] != 0 {
652 end += 1;
653 }
654 let string_value = String::from_utf8_lossy(&data[offset..end]).to_string();
655 Ok(TagValue::String(string_value))
656 }
657 PanasonicFormat::Undef => {
658 // Return as binary data
659 Ok(TagValue::Binary(data[offset..].to_vec()))
660 }
661 PanasonicFormat::Int16uArray(count) => {
662 let total_bytes = count * 2;
663 if offset + total_bytes > data.len() {
664 return Err(ExifError::ParseError(format!(
665 "Int16u array at offset {offset:#x} extends beyond data bounds"
666 )));
667 }
668
669 let mut values = Vec::new();
670 for i in 0..*count {
671 let item_offset = offset + i * 2;
672 let value = match byte_order {
673 ByteOrder::BigEndian => {
674 u16::from_be_bytes([data[item_offset], data[item_offset + 1]])
675 }
676 ByteOrder::LittleEndian => {
677 u16::from_le_bytes([data[item_offset], data[item_offset + 1]])
678 }
679 };
680 values.push(value);
681 }
682 Ok(TagValue::U16Array(values))
683 }
684 }
685 }
686
687 /// Apply value conversion to extracted value
688 /// ExifTool: ValueConv logic from tag definitions
689 #[allow(dead_code)]
690 fn apply_value_conversion(
691 &self,
692 value: &TagValue,
693 conversion: &PanasonicValueConv,
694 ) -> TagValue {
695 match conversion {
696 PanasonicValueConv::DivideBy256 => match value {
697 TagValue::U16(val) => TagValue::F64(*val as f64 / 256.0),
698 TagValue::U32(val) => TagValue::F64(*val as f64 / 256.0),
699 _ => value.clone(),
700 },
701 PanasonicValueConv::MultiplyBy100 => match value {
702 TagValue::U16(val) => TagValue::U32(*val as u32 * 100),
703 TagValue::U32(val) => TagValue::U32(*val * 100),
704 _ => value.clone(),
705 },
706 }
707 }
708
709 /// Apply print conversion to value
710 /// ExifTool: PrintConv logic from tag definitions
711 #[allow(dead_code)]
712 fn apply_print_conversion(
713 &self,
714 value: &TagValue,
715 conversion: &PanasonicPrintConv,
716 ) -> TagValue {
717 match conversion {
718 PanasonicPrintConv::HashLookup(lookup) => {
719 if let Some(key) = value.as_u16().map(|v| v as u32).or_else(|| value.as_u32()) {
720 if let Some(description) = lookup.get(&key) {
721 return TagValue::String(description.clone());
722 }
723 }
724 value.clone()
725 }
726 PanasonicPrintConv::CfaPattern => {
727 // Handled by HashLookup in practice
728 value.clone()
729 }
730 PanasonicPrintConv::Compression => {
731 // Handled by HashLookup in practice
732 value.clone()
733 }
734 PanasonicPrintConv::Orientation => {
735 // Would use EXIF orientation lookup
736 value.clone()
737 }
738 }
739 }
740}
741
742/// Byte order for data processing
743/// ExifTool: TIFF byte order from header
744#[derive(Debug, Clone, Copy)]
745enum ByteOrder {
746 /// Big-endian (MM)
747 BigEndian,
748 /// Little-endian (II)
749 LittleEndian,
750}
751
752/// Get Panasonic RW2 tag name by ID
753/// ExifTool: lib/Image/ExifTool/PanasonicRaw.pm tag definitions
754pub fn get_panasonic_tag_name(tag_id: u16) -> Option<&'static str> {
755 match tag_id {
756 0x01 => Some("PanasonicRawVersion"),
757 0x02 => Some("SensorWidth"),
758 0x03 => Some("SensorHeight"),
759 0x04 => Some("SensorTopBorder"),
760 0x05 => Some("SensorLeftBorder"),
761 0x06 => Some("SensorBottomBorder"),
762 0x07 => Some("SensorRightBorder"),
763 0x08 => Some("SamplesPerPixel"),
764 0x09 => Some("CFAPattern"),
765 0x0a => Some("BitsPerSample"),
766 0x0b => Some("Compression"),
767 0x11 => Some("RedBalance"),
768 0x12 => Some("BlueBalance"),
769 0x17 => Some("ISO"),
770 0x002e => Some("JpgFromRaw"),
771 0x0127 => Some("JpgFromRaw2"),
772 _ => None,
773 }
774}
775
776#[cfg(test)]
777mod tests {
778 use super::*;
779
780 #[test]
781 fn test_panasonic_handler_creation() {
782 let handler = PanasonicRawHandler::new();
783 assert_eq!(handler.name(), "PanasonicRaw");
784 }
785
786 #[test]
787 fn test_get_panasonic_tag_name() {
788 // Test known tag names
789 assert_eq!(get_panasonic_tag_name(0x01), Some("PanasonicRawVersion"));
790 assert_eq!(get_panasonic_tag_name(0x02), Some("SensorWidth"));
791 assert_eq!(get_panasonic_tag_name(0x09), Some("CFAPattern"));
792 assert_eq!(get_panasonic_tag_name(0x17), Some("ISO"));
793 assert_eq!(get_panasonic_tag_name(0x002e), Some("JpgFromRaw"));
794
795 // Test unknown tag
796 assert_eq!(get_panasonic_tag_name(0x9999), None);
797 }
798
799 #[test]
800 fn test_format_validation() {
801 let handler = PanasonicRawHandler::new();
802
803 // Test valid TIFF magic (big-endian)
804 let valid_be_data = b"MM\x00\x2A\x00\x00\x00\x08test_data";
805 assert!(handler.validate_format(valid_be_data));
806
807 // Test valid TIFF magic (little-endian)
808 let valid_le_data = b"II\x2A\x00\x08\x00\x00\x00test_data";
809 assert!(handler.validate_format(valid_le_data));
810
811 // Test invalid magic
812 let invalid_data = b"XX\x2A\x00\x08\x00\x00\x00test_data";
813 assert!(!handler.validate_format(invalid_data));
814
815 // Test insufficient data
816 let short_data = b"MM\x00";
817 assert!(!handler.validate_format(short_data));
818 }
819
820 #[test]
821 fn test_entry_based_offset_rules() {
822 let handler = PanasonicRawHandler::new();
823
824 // Test that entry-based offset processor has the expected rules
825 let configured_tags = handler.offset_processor.get_configured_tags();
826 assert!(configured_tags.contains(&0x002e)); // JpgFromRaw
827 assert!(configured_tags.contains(&0x0127)); // JpgFromRaw2
828
829 // Test rule retrieval
830 let rule_002e = handler.offset_processor.get_rule(0x002e);
831 assert!(rule_002e.is_some());
832 let rule = rule_002e.unwrap();
833 assert_eq!(rule.tag_id, 0x002e);
834 assert!(matches!(rule.offset_field, OffsetField::ActualValue));
835 assert!(matches!(rule.base, OffsetBase::FileStart));
836 }
837
838 #[test]
839 fn test_binary_processor_creation() {
840 let processor = PanasonicBinaryProcessor::new();
841
842 // Test that processor has expected tag definitions
843 assert!(processor.tag_definitions.contains_key(&0x01)); // PanasonicRawVersion
844 assert!(processor.tag_definitions.contains_key(&0x02)); // SensorWidth
845 assert!(processor.tag_definitions.contains_key(&0x09)); // CFAPattern
846 assert!(processor.tag_definitions.contains_key(&0x17)); // ISO
847
848 // Test tag definition details
849 let sensor_width_def = &processor.tag_definitions[&0x02];
850 assert_eq!(sensor_width_def.name, "SensorWidth");
851 assert!(matches!(sensor_width_def.format, PanasonicFormat::Int16u));
852 assert!(!sensor_width_def.writable);
853
854 let cfa_pattern_def = &processor.tag_definitions[&0x09];
855 assert_eq!(cfa_pattern_def.name, "CFAPattern");
856 assert!(cfa_pattern_def.print_conv.is_some());
857 assert!(cfa_pattern_def.writable);
858 }
859}