1use std::collections::HashMap;
8use std::fmt;
9use crate::tiff::constants::{field_types, tags};
10use log::{debug, info, trace};
11use crate::utils::tag_utils;
12
13#[derive(Debug, Clone)]
19pub struct IFD {
20 pub entries: Vec<IFDEntry>,
22 pub number: usize,
24 pub offset: u64,
26 tag_map: HashMap<u16, IFDEntry>,
28}
29
30#[derive(Debug, Clone)]
36pub struct IFDEntry {
37 pub tag: u16,
39 pub field_type: u16,
41 pub count: u64,
43 pub value_offset: u64,
45}
46
47impl IFDEntry {
48 pub fn new(tag: u16, field_type: u16, count: u64, value_offset: u64) -> Self {
54 let tag_name = tag_utils::get_tag_name(tag);
55 let field_type_name = tag_utils::get_field_type_name(field_type);
56
57 debug!("Creating new IFD entry: tag={} ({}), type={} ({}), count={}, offset/value={}",
58 tag, tag_name, field_type, field_type_name, count, value_offset);
59
60 Self {
61 tag,
62 field_type,
63 count,
64 value_offset,
65 }
66 }
67
68 pub fn get_field_type_size(&self) -> usize {
73 match self.field_type {
74 field_types::BYTE | field_types::ASCII | field_types::SBYTE | field_types::UNDEFINED => 1,
75 field_types::SHORT | field_types::SSHORT => 2,
76 field_types::LONG | field_types::SLONG | field_types::FLOAT => 4,
77 field_types::RATIONAL | field_types::SRATIONAL | field_types::DOUBLE => 8,
78 field_types::LONG8 | field_types::SLONG8 | field_types::IFD8 => 8,
79 _ => {
80 debug!("Unknown field type: {}, assuming 1 byte", self.field_type);
81 1 }
83 }
84 }
85
86 pub fn is_value_inline(&self, is_big_tiff: bool) -> bool {
93 let total_size = self.get_field_type_size() * self.count as usize;
94 let inline_size = if is_big_tiff { 8 } else { 4 };
95
96 let is_inline = total_size <= inline_size;
97 let tag_name = tag_utils::get_tag_name(self.tag);
98
99 trace!("Tag {} ({}) value storage: {}bytes, {} inline (max {}bytes)",
100 self.tag, tag_name, total_size,
101 if is_inline { "is" } else { "not" }, inline_size);
102
103 is_inline
104 }
105
106 pub fn description(&self) -> String {
110 let tag_name = tag_utils::get_tag_name(self.tag);
111 let field_type_name = tag_utils::get_field_type_name(self.field_type);
112
113 let value_display = match self.tag {
115 tags::COMPRESSION => format!("{} ({})",
116 self.value_offset,
117 tag_utils::get_compression_name(self.value_offset)),
118
119 tags::PHOTOMETRIC_INTERPRETATION => format!("{} ({})",
120 self.value_offset,
121 tag_utils::get_photometric_name(self.value_offset)),
122
123 _ => self.value_offset.to_string()
124 };
125
126 format!("Tag: {} ({}), Type: {} ({}), Count: {}, Value/Offset: {}",
127 self.tag, tag_name, self.field_type, field_type_name, self.count, value_display)
128 }
129}
130
131impl IFD {
132 pub fn new(number: usize, offset: u64) -> Self {
137 info!("Creating new IFD #{} at offset {}", number, offset);
138
139 Self {
140 entries: Vec::new(),
141 number,
142 offset,
143 tag_map: HashMap::new(),
144 }
145 }
146
147 pub fn add_entry(&mut self, entry: IFDEntry) {
152 trace!("Adding entry to IFD #{}: {}", self.number, entry.description());
153
154 self.tag_map.insert(entry.tag, entry.clone());
155 self.entries.push(entry);
156 }
157
158 pub fn get_tag_value(&self, tag: u16) -> Option<u64> {
163 let value = self.tag_map.get(&tag).map(|entry| entry.value_offset);
164 let tag_name = tag_utils::get_tag_name(tag);
165
166 if let Some(val) = value {
167 trace!("Found tag {} ({}) in IFD #{}: value/offset={}", tag, tag_name, self.number, val);
168 } else {
169 trace!("Tag {} ({}) not found in IFD #{}", tag, tag_name, self.number);
170 }
171
172 value
173 }
174
175 pub fn has_tag(&self, tag: u16) -> bool {
179 let has_tag = self.tag_map.contains_key(&tag);
180 let tag_name = tag_utils::get_tag_name(tag);
181
182 trace!("Checking if IFD #{} has tag {} ({}): {}",
183 self.number, tag, tag_name, has_tag);
184
185 has_tag
186 }
187
188 pub fn get_entry(&self, tag: u16) -> Option<&IFDEntry> {
192 let entry = self.tag_map.get(&tag);
193 let tag_name = tag_utils::get_tag_name(tag);
194
195 if entry.is_some() {
196 trace!("Retrieved entry for tag {} ({}) from IFD #{}", tag, tag_name, self.number);
197 }
198
199 entry
200 }
201
202 pub fn get_dimensions(&self) -> Option<(u64, u64)> {
206 let width = self.get_tag_value(tags::IMAGE_WIDTH)?;
207 let height = self.get_tag_value(tags::IMAGE_LENGTH)?;
208
209 debug!("Image dimensions from IFD #{}: {}x{}", self.number, width, height);
210
211 Some((width, height))
212 }
213
214 pub fn get_samples_per_pixel(&self) -> u64 {
219 let samples = self.get_tag_value(tags::SAMPLES_PER_PIXEL).unwrap_or(1);
220 debug!("Samples per pixel from IFD #{}: {}", self.number, samples);
221 samples
222 }
223
224 pub fn get_entries(&self) -> &Vec<IFDEntry> {
228 &self.entries
229 }
230
231 pub fn entry_count(&self) -> usize {
235 self.entries.len()
236 }
237}
238
239impl fmt::Display for IFD {
240 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
241 writeln!(f, "IFD #{} (offset: {})", self.number, self.offset)?;
242 writeln!(f, " Number of entries: {}", self.entries.len())?;
243
244 if let Some((width, height)) = self.get_dimensions() {
245 writeln!(f, " Dimensions: {}x{}", width, height)?;
246 }
247
248 writeln!(f, " Samples per pixel: {}", self.get_samples_per_pixel())?;
249
250 writeln!(f, " Tags:")?;
252 for entry in &self.entries {
253 let tag_name = tag_utils::get_tag_name(entry.tag);
254 let field_type_name = tag_utils::get_field_type_name(entry.field_type);
255
256 let value_display = match entry.tag {
258 tags::COMPRESSION => format!("{} ({})",
259 entry.value_offset,
260 tag_utils::get_compression_name(entry.value_offset)),
261
262 tags::PHOTOMETRIC_INTERPRETATION => format!("{} ({})",
263 entry.value_offset,
264 tag_utils::get_photometric_name(entry.value_offset)),
265
266 _ => entry.value_offset.to_string()
267 };
268
269 writeln!(f, " {} ({}): {} [{}]",
270 entry.tag, tag_name, value_display, field_type_name)?;
271 }
272
273 Ok(())
274 }
275}