use std::collections::HashMap;
use std::fmt;
use crate::tiff::constants::{field_types, tags};
use log::{debug, info, trace};
use crate::utils::tag_utils;
#[derive(Debug, Clone)]
pub struct IFD {
pub entries: Vec<IFDEntry>,
pub number: usize,
pub offset: u64,
tag_map: HashMap<u16, IFDEntry>,
}
#[derive(Debug, Clone)]
pub struct IFDEntry {
pub tag: u16,
pub field_type: u16,
pub count: u64,
pub value_offset: u64,
}
impl IFDEntry {
pub fn new(tag: u16, field_type: u16, count: u64, value_offset: u64) -> Self {
let tag_name = tag_utils::get_tag_name(tag);
let field_type_name = tag_utils::get_field_type_name(field_type);
debug!("Creating new IFD entry: tag={} ({}), type={} ({}), count={}, offset/value={}",
tag, tag_name, field_type, field_type_name, count, value_offset);
Self {
tag,
field_type,
count,
value_offset,
}
}
pub fn get_field_type_size(&self) -> usize {
match self.field_type {
field_types::BYTE | field_types::ASCII | field_types::SBYTE | field_types::UNDEFINED => 1,
field_types::SHORT | field_types::SSHORT => 2,
field_types::LONG | field_types::SLONG | field_types::FLOAT => 4,
field_types::RATIONAL | field_types::SRATIONAL | field_types::DOUBLE => 8,
field_types::LONG8 | field_types::SLONG8 | field_types::IFD8 => 8,
_ => {
debug!("Unknown field type: {}, assuming 1 byte", self.field_type);
1 }
}
}
pub fn is_value_inline(&self, is_big_tiff: bool) -> bool {
let total_size = self.get_field_type_size() * self.count as usize;
let inline_size = if is_big_tiff { 8 } else { 4 };
let is_inline = total_size <= inline_size;
let tag_name = tag_utils::get_tag_name(self.tag);
trace!("Tag {} ({}) value storage: {}bytes, {} inline (max {}bytes)",
self.tag, tag_name, total_size,
if is_inline { "is" } else { "not" }, inline_size);
is_inline
}
pub fn description(&self) -> String {
let tag_name = tag_utils::get_tag_name(self.tag);
let field_type_name = tag_utils::get_field_type_name(self.field_type);
let value_display = match self.tag {
tags::COMPRESSION => format!("{} ({})",
self.value_offset,
tag_utils::get_compression_name(self.value_offset)),
tags::PHOTOMETRIC_INTERPRETATION => format!("{} ({})",
self.value_offset,
tag_utils::get_photometric_name(self.value_offset)),
_ => self.value_offset.to_string()
};
format!("Tag: {} ({}), Type: {} ({}), Count: {}, Value/Offset: {}",
self.tag, tag_name, self.field_type, field_type_name, self.count, value_display)
}
}
impl IFD {
pub fn new(number: usize, offset: u64) -> Self {
info!("Creating new IFD #{} at offset {}", number, offset);
Self {
entries: Vec::new(),
number,
offset,
tag_map: HashMap::new(),
}
}
pub fn add_entry(&mut self, entry: IFDEntry) {
trace!("Adding entry to IFD #{}: {}", self.number, entry.description());
self.tag_map.insert(entry.tag, entry.clone());
self.entries.push(entry);
}
pub fn get_tag_value(&self, tag: u16) -> Option<u64> {
let value = self.tag_map.get(&tag).map(|entry| entry.value_offset);
let tag_name = tag_utils::get_tag_name(tag);
if let Some(val) = value {
trace!("Found tag {} ({}) in IFD #{}: value/offset={}", tag, tag_name, self.number, val);
} else {
trace!("Tag {} ({}) not found in IFD #{}", tag, tag_name, self.number);
}
value
}
pub fn has_tag(&self, tag: u16) -> bool {
let has_tag = self.tag_map.contains_key(&tag);
let tag_name = tag_utils::get_tag_name(tag);
trace!("Checking if IFD #{} has tag {} ({}): {}",
self.number, tag, tag_name, has_tag);
has_tag
}
pub fn get_entry(&self, tag: u16) -> Option<&IFDEntry> {
let entry = self.tag_map.get(&tag);
let tag_name = tag_utils::get_tag_name(tag);
if entry.is_some() {
trace!("Retrieved entry for tag {} ({}) from IFD #{}", tag, tag_name, self.number);
}
entry
}
pub fn get_dimensions(&self) -> Option<(u64, u64)> {
let width = self.get_tag_value(tags::IMAGE_WIDTH)?;
let height = self.get_tag_value(tags::IMAGE_LENGTH)?;
debug!("Image dimensions from IFD #{}: {}x{}", self.number, width, height);
Some((width, height))
}
pub fn get_samples_per_pixel(&self) -> u64 {
let samples = self.get_tag_value(tags::SAMPLES_PER_PIXEL).unwrap_or(1);
debug!("Samples per pixel from IFD #{}: {}", self.number, samples);
samples
}
pub fn get_entries(&self) -> &Vec<IFDEntry> {
&self.entries
}
pub fn entry_count(&self) -> usize {
self.entries.len()
}
}
impl fmt::Display for IFD {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
writeln!(f, "IFD #{} (offset: {})", self.number, self.offset)?;
writeln!(f, " Number of entries: {}", self.entries.len())?;
if let Some((width, height)) = self.get_dimensions() {
writeln!(f, " Dimensions: {}x{}", width, height)?;
}
writeln!(f, " Samples per pixel: {}", self.get_samples_per_pixel())?;
writeln!(f, " Tags:")?;
for entry in &self.entries {
let tag_name = tag_utils::get_tag_name(entry.tag);
let field_type_name = tag_utils::get_field_type_name(entry.field_type);
let value_display = match entry.tag {
tags::COMPRESSION => format!("{} ({})",
entry.value_offset,
tag_utils::get_compression_name(entry.value_offset)),
tags::PHOTOMETRIC_INTERPRETATION => format!("{} ({})",
entry.value_offset,
tag_utils::get_photometric_name(entry.value_offset)),
_ => entry.value_offset.to_string()
};
writeln!(f, " {} ({}): {} [{}]",
entry.tag, tag_name, value_display, field_type_name)?;
}
Ok(())
}
}