use crate::error::{Error, Result};
use crate::tag::{Tag, TagGroup, TagId};
use crate::value::Value;
fn mk(name: &str, value: Value) -> Tag {
let pv = value.to_display_string();
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "FLIR".into(),
family1: "FLIR".into(),
family2: "Image".into(),
},
raw_value: value,
print_value: pv,
priority: 0,
}
}
fn mk_print(name: &str, value: Value, print: String) -> Tag {
Tag {
id: TagId::Text(name.to_string()),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "FLIR".into(),
family1: "FLIR".into(),
family2: "Image".into(),
},
raw_value: value,
print_value: print,
priority: 0,
}
}
fn read_u16_le(data: &[u8], offset: usize) -> u16 {
if offset + 2 > data.len() {
return 0;
}
u16::from_le_bytes([data[offset], data[offset + 1]])
}
fn read_u32_le(data: &[u8], offset: usize) -> u32 {
if offset + 4 > data.len() {
return 0;
}
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
fn read_f32_le(data: &[u8], offset: usize) -> f32 {
if offset + 4 > data.len() {
return 0.0;
}
f32::from_bits(u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
]))
}
fn read_str(data: &[u8], offset: usize, len: usize) -> String {
if offset + len > data.len() {
return String::new();
}
let s = &data[offset..offset + len];
let end = s.iter().position(|&b| b == 0).unwrap_or(len);
crate::encoding::decode_utf8_or_latin1(&s[..end])
.trim()
.to_string()
}
fn kelvin_to_celsius(k: f32) -> String {
let c = k as f64 - 273.15;
let c = if c == 0.0 { 0.0 } else { c }; format!("{:.1} C", c)
}
pub fn read_fpf(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 892 {
return Err(Error::InvalidData("FPF file too small".into()));
}
if !data.starts_with(b"FPF Public Image Format\0") {
return Err(Error::InvalidData("not a FPF file".into()));
}
let version_raw = read_u32_le(data, 0x20);
if version_raw & 0xffff == 0 {
return Err(Error::InvalidData("FPF big-endian not supported".into()));
}
let mut tags: Vec<Tag> = Vec::new();
let fpf_version = read_u32_le(data, 0x20);
tags.push(mk("FPFVersion", Value::U32(fpf_version)));
let image_data_offset = read_u32_le(data, 0x24);
tags.push(mk("ImageDataOffset", Value::U32(image_data_offset)));
let image_type = read_u16_le(data, 0x28);
let image_type_str = match image_type {
0 => "Temperature",
1 => "Temperature Difference",
2 => "Object Signal",
3 => "Object Signal Difference",
_ => "Unknown",
};
tags.push(mk_print(
"ImageType",
Value::U16(image_type),
image_type_str.to_string(),
));
let pixel_fmt = read_u16_le(data, 0x2a);
let pixel_fmt_str = match pixel_fmt {
0 => "2-byte short integer",
1 => "4-byte long integer",
2 => "4-byte float",
3 => "8-byte double",
_ => "Unknown",
};
tags.push(mk_print(
"ImagePixelFormat",
Value::U16(pixel_fmt),
pixel_fmt_str.to_string(),
));
let width = read_u16_le(data, 0x2c);
tags.push(mk("ImageWidth", Value::U16(width)));
let height = read_u16_le(data, 0x2e);
tags.push(mk("ImageHeight", Value::U16(height)));
let ext_trig = read_u32_le(data, 0x30);
tags.push(mk("ExternalTriggerCount", Value::U32(ext_trig)));
let seq_frame = read_u32_le(data, 0x34);
tags.push(mk("SequenceFrameNumber", Value::U32(seq_frame)));
let camera_model = read_str(data, 0x78, 32);
tags.push(mk("CameraModel", Value::String(camera_model)));
let camera_part = read_str(data, 0x98, 32);
tags.push(mk("CameraPartNumber", Value::String(camera_part)));
let camera_serial = read_str(data, 0xb8, 32);
tags.push(mk("CameraSerialNumber", Value::String(camera_serial)));
let cam_temp_min = read_f32_le(data, 0xd8);
tags.push(mk_print(
"CameraTemperatureRangeMin",
Value::F32(cam_temp_min),
kelvin_to_celsius(cam_temp_min),
));
let cam_temp_max = read_f32_le(data, 0xdc);
tags.push(mk_print(
"CameraTemperatureRangeMax",
Value::F32(cam_temp_max),
kelvin_to_celsius(cam_temp_max),
));
let lens_model = read_str(data, 0xe0, 32);
tags.push(mk("LensModel", Value::String(lens_model)));
let lens_part = read_str(data, 0x100, 32);
tags.push(mk("LensPartNumber", Value::String(lens_part)));
let lens_serial = read_str(data, 0x120, 32);
tags.push(mk("LensSerialNumber", Value::String(lens_serial)));
let filter_model = read_str(data, 0x140, 16);
tags.push(mk("FilterModel", Value::String(filter_model)));
let filter_part = read_str(data, 0x150, 32);
tags.push(mk("FilterPartNumber", Value::String(filter_part)));
let filter_serial = read_str(data, 0x180, 32);
tags.push(mk("FilterSerialNumber", Value::String(filter_serial)));
let emissivity = read_f32_le(data, 0x1e0);
tags.push(mk_print(
"Emissivity",
Value::F32(emissivity),
format!("{:.2}", emissivity),
));
let obj_dist = read_f32_le(data, 0x1e4);
tags.push(mk_print(
"ObjectDistance",
Value::F32(obj_dist),
format!("{:.2} m", obj_dist),
));
let refl_temp = read_f32_le(data, 0x1e8);
tags.push(mk_print(
"ReflectedApparentTemperature",
Value::F32(refl_temp),
kelvin_to_celsius(refl_temp),
));
let atm_temp = read_f32_le(data, 0x1ec);
tags.push(mk_print(
"AtmosphericTemperature",
Value::F32(atm_temp),
kelvin_to_celsius(atm_temp),
));
let rel_hum = read_f32_le(data, 0x1f0);
tags.push(mk_print(
"RelativeHumidity",
Value::F32(rel_hum),
format!("{:.1} %", (rel_hum as f64) * 100.0),
));
let comp_atm = read_f32_le(data, 0x1f4);
tags.push(mk_print(
"ComputedAtmosphericTrans",
Value::F32(comp_atm),
format!("{:.2}", comp_atm),
));
let est_atm = read_f32_le(data, 0x1f8);
tags.push(mk_print(
"EstimatedAtmosphericTrans",
Value::F32(est_atm),
format!("{:.2}", est_atm),
));
let ref_temp = read_f32_le(data, 0x1fc);
tags.push(mk_print(
"ReferenceTemperature",
Value::F32(ref_temp),
kelvin_to_celsius(ref_temp),
));
let irwin_temp = read_f32_le(data, 0x200);
tags.push(mk_print(
"IRWindowTemperature",
Value::F32(irwin_temp),
kelvin_to_celsius(irwin_temp),
));
let irwin_trans = read_f32_le(data, 0x204);
tags.push(mk_print(
"IRWindowTransmission",
Value::F32(irwin_trans),
format!("{:.2}", irwin_trans),
));
if data.len() >= 0x248 + 7 * 4 {
let year = read_u32_le(data, 0x248);
let month = read_u32_le(data, 0x24c);
let day = read_u32_le(data, 0x250);
let hour = read_u32_le(data, 0x254);
let min = read_u32_le(data, 0x258);
let sec = read_u32_le(data, 0x25c);
let ms = read_u32_le(data, 0x260);
let dt = format!(
"{:04}:{:02}:{:02} {:02}:{:02}:{:02}.{:03}",
year, month, day, hour, min, sec, ms
);
tags.push(mk("DateTimeOriginal", Value::String(dt)));
}
let cam_scale_min = read_f32_le(data, 0x2a4);
tags.push(mk_print(
"CameraScaleMin",
Value::F32(cam_scale_min),
format!("{:.1}", cam_scale_min),
));
let cam_scale_max = read_f32_le(data, 0x2a8);
tags.push(mk_print(
"CameraScaleMax",
Value::F32(cam_scale_max),
format!("{:.1}", cam_scale_max),
));
let calc_scale_min = read_f32_le(data, 0x2ac);
tags.push(mk_print(
"CalculatedScaleMin",
Value::F32(calc_scale_min),
format!("{:.1}", calc_scale_min),
));
let calc_scale_max = read_f32_le(data, 0x2b0);
tags.push(mk_print(
"CalculatedScaleMax",
Value::F32(calc_scale_max),
format!("{:.1}", calc_scale_max),
));
let act_scale_min = read_f32_le(data, 0x2b4);
tags.push(mk_print(
"ActualScaleMin",
Value::F32(act_scale_min),
format!("{:.1}", act_scale_min),
));
let act_scale_max = read_f32_le(data, 0x2b8);
tags.push(mk_print(
"ActualScaleMax",
Value::F32(act_scale_max),
format!("{:.1}", act_scale_max),
));
Ok(tags)
}