use crate::error::{Error, Result};
use crate::metadata::ExifReader;
use crate::tag::{Tag, TagGroup, TagId};
use crate::value::Value;
pub fn read_tiff(data: &[u8]) -> Result<Vec<Tag>> {
if data.len() < 8 {
return Err(Error::InvalidData("file too small for TIFF".into()));
}
let is_le = data[0] == b'I' && data[1] == b'I';
let is_be = data[0] == b'M' && data[1] == b'M';
if !is_le && !is_be {
return Err(Error::InvalidData("not a TIFF file".into()));
}
let magic = if is_le {
u16::from_le_bytes([data[2], data[3]])
} else {
u16::from_be_bytes([data[2], data[3]])
};
let mut tags = match magic {
42 => ExifReader::read(data)?,
43 => {
if data.len() >= 16 {
let ifd_offset = if is_le {
u64::from_le_bytes([
data[8], data[9], data[10], data[11],
data[12], data[13], data[14], data[15],
])
} else {
u64::from_be_bytes([
data[8], data[9], data[10], data[11],
data[12], data[13], data[14], data[15],
])
} as usize;
read_bigtiff_ifd(data, ifd_offset, is_le)?
} else {
vec![]
}
}
0x55 => {
read_rw2(data, is_le)?
}
_ => return Err(Error::InvalidData(format!("unknown TIFF magic: 0x{:04X}", magic))),
};
process_geotiff(&mut tags);
Ok(tags)
}
fn rw2_u16(data: &[u8], off: usize, le: bool) -> u16 {
if off + 2 > data.len() { return 0; }
if le { u16::from_le_bytes([data[off], data[off+1]]) }
else { u16::from_be_bytes([data[off], data[off+1]]) }
}
fn rw2_i16(data: &[u8], off: usize, le: bool) -> i16 {
rw2_u16(data, off, le) as i16
}
fn rw2_u32(data: &[u8], off: usize, le: bool) -> u32 {
if off + 4 > data.len() { return 0; }
if le { u32::from_le_bytes([data[off], data[off+1], data[off+2], data[off+3]]) }
else { u32::from_be_bytes([data[off], data[off+1], data[off+2], data[off+3]]) }
}
fn panasonic_raw_tag(tag: u16) -> Option<(&'static str, &'static str)> {
Some(match tag {
0x0001 => ("PanasonicRawVersion", "Image"),
0x0002 => ("SensorWidth", "Image"),
0x0003 => ("SensorHeight", "Image"),
0x0004 => ("SensorTopBorder", "Image"),
0x0005 => ("SensorLeftBorder", "Image"),
0x0006 => ("SensorBottomBorder", "Image"),
0x0007 => ("SensorRightBorder", "Image"),
0x0008 => ("SamplesPerPixel", "Image"),
0x0009 => ("CFAPattern", "Image"),
0x000a => ("BitsPerSample", "Image"),
0x000b => ("Compression", "Image"),
0x000e => ("LinearityLimitRed", "Image"),
0x000f => ("LinearityLimitGreen", "Image"),
0x0010 => ("LinearityLimitBlue", "Image"),
0x0011 => ("RedBalance", "Camera"),
0x0012 => ("BlueBalance", "Camera"),
0x0017 => ("ISO", "Image"),
0x0018 => ("HighISOMultiplierRed", "Image"),
0x0019 => ("HighISOMultiplierGreen", "Image"),
0x001a => ("HighISOMultiplierBlue", "Image"),
0x001b => ("NoiseReductionParams", "Image"),
0x001c => ("BlackLevelRed", "Image"),
0x001d => ("BlackLevelGreen", "Image"),
0x001e => ("BlackLevelBlue", "Image"),
0x0024 => ("WBRedLevel", "Image"),
0x0025 => ("WBGreenLevel", "Image"),
0x0026 => ("WBBlueLevel", "Image"),
0x002d => ("RawFormat", "Image"),
0x002f => ("CropTop", "Image"),
0x0030 => ("CropLeft", "Image"),
0x0031 => ("CropBottom", "Image"),
0x0032 => ("CropRight", "Image"),
0x010f => ("Make", "Camera"),
0x0110 => ("Model", "Camera"),
0x0111 => ("StripOffsets", "Image"),
0x0112 => ("Orientation", "Image"),
0x0115 => ("SamplesPerPixel","Image"),
0x0116 => ("RowsPerStrip", "Image"),
0x0117 => ("StripByteCounts","Image"),
0x0118 => ("RawDataOffset", "Image"),
0x011a => ("XResolution", "Image"),
0x011b => ("YResolution", "Image"),
0x011c => ("Gamma", "Image"),
0x0128 => ("ResolutionUnit", "Image"),
0x0131 => ("Software", "Image"),
0x0132 => ("ModifyDate", "Time"),
0x013b => ("Artist", "Author"),
0x0213 => ("YCbCrPositioning","Image"),
0x8298 => ("Copyright", "Author"),
_ => return None,
})
}
fn panasonic_raw_print_conv(tag: u16, value: &Value) -> Option<String> {
match tag {
0x0009 => { if let Some(v) = value.as_u64() {
return Some(match v {
0 => "n/a",
1 => "[Red,Green][Green,Blue]",
2 => "[Green,Red][Blue,Green]",
3 => "[Green,Blue][Red,Green]",
4 => "[Blue,Green][Green,Red]",
_ => return None,
}.to_string());
}
}
0x000b => { if let Some(v) = value.as_u64() {
return Some(match v {
34316 => "Panasonic RAW 1",
34826 => "Panasonic RAW 2",
34828 => "Panasonic RAW 3",
34830 => "Panasonic RAW 4",
_ => return None,
}.to_string());
}
}
0x0112 => { if let Some(v) = value.as_u64() {
return Some(match v {
1 => "Horizontal (normal)",
2 => "Mirror horizontal",
3 => "Rotate 180",
4 => "Mirror vertical",
5 => "Mirror horizontal and rotate 270 CW",
6 => "Rotate 90 CW",
7 => "Mirror horizontal and rotate 90 CW",
8 => "Rotate 270 CW",
_ => return None,
}.to_string());
}
}
0x0128 => { if let Some(v) = value.as_u64() {
return Some(match v {
1 => "No Absolute Unit",
2 => "inches",
3 => "centimeters",
_ => return None,
}.to_string());
}
}
0x0213 => { if let Some(v) = value.as_u64() {
return Some(match v {
1 => "Centered",
2 => "Co-sited",
_ => return None,
}.to_string());
}
}
_ => {}
}
None
}
fn rw2_read_value(data: &[u8], dtype: u16, count: u32, inline_data: &[u8; 4], value_offset: u32, le: bool) -> Option<Value> {
let elem_size: usize = match dtype {
1 | 2 | 6 | 7 => 1,
3 | 8 => 2,
4 | 9 | 11 | 13 => 4,
5 | 10 | 12 => 8,
_ => return None,
};
let total = elem_size * count as usize;
let value_data: &[u8] = if total <= 4 {
&inline_data[..total.min(4)]
} else {
let off = value_offset as usize;
if off + total > data.len() { return None; }
&data[off..off + total]
};
Some(match dtype {
1 => if count == 1 { Value::U8(value_data[0]) }
else { Value::List(value_data.iter().map(|&b| Value::U8(b)).collect()) },
2 => Value::String(String::from_utf8_lossy(value_data).trim_end_matches('\0').to_string()),
3 => if count == 1 { Value::U16(rw2_u16(value_data, 0, le)) }
else { Value::List((0..count as usize).map(|i| Value::U16(rw2_u16(value_data, i*2, le))).collect()) },
4 | 13 => if count == 1 { Value::U32(rw2_u32(value_data, 0, le)) }
else { Value::List((0..count as usize).map(|i| Value::U32(rw2_u32(value_data, i*4, le))).collect()) },
5 => if count == 1 {
let n = rw2_u32(value_data, 0, le);
let d = rw2_u32(value_data, 4, le);
Value::URational(n, d)
} else {
Value::List((0..count as usize).map(|i| {
Value::URational(rw2_u32(value_data, i*8, le), rw2_u32(value_data, i*8+4, le))
}).collect())
},
6 => if count == 1 { Value::I16(value_data[0] as i8 as i16) }
else { Value::List(value_data.iter().map(|&b| Value::I16(b as i8 as i16)).collect()) },
7 => Value::Undefined(value_data.to_vec()),
8 => if count == 1 { Value::I16(rw2_i16(value_data, 0, le)) }
else { Value::List((0..count as usize).map(|i| Value::I16(rw2_i16(value_data, i*2, le))).collect()) },
9 => if count == 1 {
let v = rw2_u32(value_data, 0, le) as i32;
Value::I32(v)
} else {
Value::List((0..count as usize).map(|i| Value::I32(rw2_u32(value_data, i*4, le) as i32)).collect())
},
10 => {
let n = rw2_u32(value_data, 0, le) as i32;
let d = rw2_u32(value_data, 4, le) as i32;
if count == 1 { Value::IRational(n, d) }
else { Value::List((0..count as usize).map(|i| {
Value::IRational(rw2_u32(value_data, i*8, le) as i32, rw2_u32(value_data, i*8+4, le) as i32)
}).collect()) }
},
11 => {
let bits = rw2_u32(value_data, 0, le);
if count == 1 { Value::F32(f32::from_bits(bits)) }
else { Value::List((0..count as usize).map(|i| {
Value::F32(f32::from_bits(rw2_u32(value_data, i*4, le)))
}).collect()) }
},
12 => {
let bits = if value_data.len() >= 8 {
if le { u64::from_le_bytes(value_data[..8].try_into().unwrap_or([0;8])) }
else { u64::from_be_bytes(value_data[..8].try_into().unwrap_or([0;8])) }
} else { 0 };
if count == 1 { Value::F64(f64::from_bits(bits)) }
else { Value::List((0..count as usize).map(|i| {
let off = i * 8;
let b = if off + 8 <= value_data.len() {
if le { u64::from_le_bytes(value_data[off..off+8].try_into().unwrap_or([0;8])) }
else { u64::from_be_bytes(value_data[off..off+8].try_into().unwrap_or([0;8])) }
} else { 0 };
Value::F64(f64::from_bits(b))
}).collect()) }
},
_ => return None,
})
}
fn parse_wb_info2(data: &[u8], le: bool) -> Vec<Tag> {
if data.len() < 2 { return vec![]; }
let mut tags = Vec::new();
let num = rw2_u16(data, 0, le) as usize;
tags.push(Tag {
id: TagId::Text("NumWBEntries".into()),
name: "NumWBEntries".into(),
description: "Num WB Entries".into(),
group: TagGroup { family0: "PanasonicRaw".into(), family1: "PanasonicRaw".into(), family2: "Image".into() },
raw_value: Value::U16(num as u16),
print_value: num.to_string(),
priority: 0,
});
static WB_TYPE_NAMES: &[(&str, &str)] = &[
("0", "Unknown"), ("1", "Daylight"), ("2", "Cloudy"), ("3", "Tungsten"),
("4", "Flash (FZ7)"), ("5", "Flash"), ("6", "n/a"), ("7", "n/a"),
("8", "Custom"), ("9", "Fine Weather"), ("10", "Cloudy"), ("11", "Shade"),
("12", "Kelvin"), ("16", "AWBc"), ("20", "D55"), ("24", "ISO Studio Tungsten"),
];
let wb_type_print_conv = |v: u16| -> String {
match v {
0 => "Unknown".to_string(),
1 => "Daylight".to_string(),
2 => "Fluorescent".to_string(),
3 => "Tungsten (Incandescent)".to_string(),
4 => "Flash".to_string(),
9 => "Fine Weather".to_string(),
10 => "Cloudy".to_string(),
11 => "Shade".to_string(),
12 => "Daylight Fluorescent".to_string(),
13 => "Day White Fluorescent".to_string(),
14 => "Cool White Fluorescent".to_string(),
15 => "White Fluorescent".to_string(),
17 => "Standard Light A".to_string(),
18 => "Standard Light B".to_string(),
19 => "Standard Light C".to_string(),
20 => "D55".to_string(),
21 => "D65".to_string(),
22 => "D75".to_string(),
23 => "D50".to_string(),
24 => "ISO Studio Tungsten".to_string(),
255 => "Other".to_string(),
_ => v.to_string(),
}
};
let _ = WB_TYPE_NAMES;
for i in 0..num.min(7) {
let type_off = 2 * (1 + i * 4);
let rgb_off = 2 * (2 + i * 4);
if type_off + 2 > data.len() || rgb_off + 6 > data.len() { break; }
let wbt = rw2_u16(data, type_off, le);
let r = rw2_u16(data, rgb_off, le);
let g = rw2_u16(data, rgb_off+2, le);
let b = rw2_u16(data, rgb_off+4, le);
let n = i + 1;
let wbt_s = wb_type_print_conv(wbt);
tags.push(Tag {
id: TagId::Text(format!("WBType{}", n)),
name: format!("WBType{}", n),
description: format!("WB Type {}", n),
group: TagGroup { family0: "PanasonicRaw".into(), family1: "PanasonicRaw".into(), family2: "Image".into() },
raw_value: Value::U16(wbt),
print_value: wbt_s,
priority: 0,
});
tags.push(Tag {
id: TagId::Text(format!("WB_RGBLevels{}", n)),
name: format!("WB_RGBLevels{}", n),
description: format!("WB RGB Levels {}", n),
group: TagGroup { family0: "PanasonicRaw".into(), family1: "PanasonicRaw".into(), family2: "Image".into() },
raw_value: Value::List(vec![Value::U16(r), Value::U16(g), Value::U16(b)]),
print_value: format!("{} {} {}", r, g, b),
priority: 0,
});
}
tags
}
fn parse_distortion_info(data: &[u8], le: bool) -> Vec<Tag> {
let read_i16 = |idx: usize| -> i16 {
let off = idx * 2;
if off + 2 > data.len() { return 0; }
rw2_i16(data, off, le)
};
let mk = |name: &'static str, desc: &'static str, raw: Value, print: String| -> Tag {
Tag {
id: TagId::Text(name.into()),
name: name.into(),
description: desc.into(),
group: TagGroup { family0: "PanasonicRaw".into(), family1: "PanasonicRaw".into(), family2: "Image".into() },
raw_value: raw,
print_value: print,
priority: 0,
}
};
let mut tags = Vec::new();
if data.len() < 6 { return tags; }
let v2 = read_i16(2);
let f2 = v2 as f64 / 32768.0;
tags.push(mk("DistortionParam02", "Distortion Param 02",
Value::F64(f2), crate::value::format_g15(f2)));
if data.len() >= 10 {
let v4 = read_i16(4);
let f4 = v4 as f64 / 32768.0;
tags.push(mk("DistortionParam04", "Distortion Param 04",
Value::F64(f4), crate::value::format_g15(f4)));
let v5 = read_i16(5);
let f5 = 1.0 / (1.0 + v5 as f64 / 32768.0);
tags.push(mk("DistortionScale", "Distortion Scale",
Value::F64(f5), crate::value::format_g15(f5)));
}
if data.len() >= 16 {
let v7 = read_i16(7);
let masked = (v7 as i16 & 0x0f) as i64;
let pv = match masked { 0 => "Off", 1 => "On", _ => "Unknown" };
tags.push(mk("DistortionCorrection", "Distortion Correction",
Value::I32(masked as i32), pv.to_string()));
if data.len() >= 18 {
let v8 = read_i16(8);
let f8 = v8 as f64 / 32768.0;
tags.push(mk("DistortionParam08", "Distortion Param 08",
Value::F64(f8), crate::value::format_g15(f8)));
}
if data.len() >= 20 {
let v9 = read_i16(9);
let f9 = v9 as f64 / 32768.0;
tags.push(mk("DistortionParam09", "Distortion Param 09",
Value::F64(f9), crate::value::format_g15(f9)));
}
if data.len() >= 24 {
let v11 = read_i16(11);
let f11 = v11 as f64 / 32768.0;
tags.push(mk("DistortionParam11", "Distortion Param 11",
Value::F64(f11), crate::value::format_g15(f11)));
}
}
tags
}
struct RW2IfdEntry {
tag: u16,
dtype: u16,
count: u32,
value_offset: u32,
inline_data: [u8; 4],
}
fn rw2_parse_entry(data: &[u8], off: usize, le: bool) -> Option<RW2IfdEntry> {
if off + 12 > data.len() { return None; }
let tag = rw2_u16(data, off, le);
let dtype = rw2_u16(data, off+2, le);
let count = rw2_u32(data, off+4, le);
let value_offset = rw2_u32(data, off+8, le);
let mut inline_data = [0u8; 4];
inline_data.copy_from_slice(&data[off+8..off+12]);
Some(RW2IfdEntry { tag, dtype, count, value_offset, inline_data })
}
fn read_rw2(data: &[u8], le: bool) -> crate::error::Result<Vec<Tag>> {
if data.len() < 8 { return Ok(vec![]); }
let ifd0_off = rw2_u32(data, 4, le) as usize;
if ifd0_off + 2 > data.len() { return Ok(vec![]); }
let mut tags: Vec<Tag> = Vec::new();
let bo_str = if le { "Little-endian (Intel, II)" } else { "Big-endian (Motorola, MM)" };
tags.push(Tag {
id: TagId::Text("ExifByteOrder".into()),
name: "ExifByteOrder".into(),
description: "Exif Byte Order".into(),
group: TagGroup { family0: "EXIF".into(), family1: "IFD0".into(), family2: "ExifTool".into() },
raw_value: Value::String(bo_str.to_string()),
print_value: bo_str.to_string(),
priority: 0,
});
let entry_count = rw2_u16(data, ifd0_off, le) as usize;
let entries_start = ifd0_off + 2;
let entry_count = entry_count.min((data.len().saturating_sub(entries_start)) / 12).min(200);
let mut jpg_from_raw: Option<Vec<u8>> = None;
let mut wb_info2_data: Option<Vec<u8>> = None;
let mut distortion_data: Option<Vec<u8>> = None;
let mut thumb_offset: Option<u64> = None;
let mut thumb_length: Option<u64> = None;
for i in 0..entry_count {
let eoff = entries_start + i * 12;
let e = match rw2_parse_entry(data, eoff, le) {
Some(e) => e,
None => break,
};
match e.tag {
0x002e => {
let dtype = e.dtype;
let count = e.count as usize;
let elem = match dtype { 1|2|6|7 => 1, 3|8 => 2, 4|9|11|13 => 4, 5|10|12 => 8, _ => 1 };
let total = elem * count;
if total > 4 {
let off = e.value_offset as usize;
if off + total <= data.len() {
jpg_from_raw = Some(data[off..off+total].to_vec());
}
}
tags.push(Tag {
id: TagId::Numeric(0x002e),
name: "JpgFromRaw".into(),
description: "Jpg From Raw".into(),
group: TagGroup { family0: "EXIF".into(), family1: "IFD0".into(), family2: "Preview".into() },
raw_value: Value::Binary(Vec::new()), print_value: format!("(Binary data {} bytes, use -b option to extract)", total),
priority: 0,
});
continue;
}
0x0027 => {
let dtype = e.dtype;
let count = e.count as usize;
let elem = match dtype { 1|2|6|7 => 1, 3|8 => 2, 4|9|11|13 => 4, 5|10|12 => 8, _ => 1 };
let total = elem * count;
let bytes = if total <= 4 {
e.inline_data[..total.min(4)].to_vec()
} else {
let off = e.value_offset as usize;
if off + total <= data.len() {
data[off..off+total].to_vec()
} else { continue; }
};
wb_info2_data = Some(bytes);
continue;
}
0x0119 => {
let dtype = e.dtype;
let count = e.count as usize;
let elem = match dtype { 1|2|6|7 => 1, 3|8 => 2, 4|9|11|13 => 4, 5|10|12 => 8, _ => 1 };
let total = elem * count;
let bytes = if total <= 4 {
e.inline_data[..total.min(4)].to_vec()
} else {
let off = e.value_offset as usize;
if off + total <= data.len() {
data[off..off+total].to_vec()
} else { continue; }
};
distortion_data = Some(bytes);
continue;
}
0x0013 | 0x0120 | 0x02bc | 0x83bb | 0x8769 | 0x8825 => continue,
_ => {}
}
let tag_info = panasonic_raw_tag(e.tag);
if tag_info.is_none() {
continue;
}
let (name, family2) = tag_info.unwrap();
let value = match rw2_read_value(data, e.dtype, e.count, &e.inline_data, e.value_offset, le) {
Some(v) => v,
None => continue,
};
let (final_value, print_value) = match e.tag {
0x0001 => {
let s = match &value {
Value::Undefined(b) => String::from_utf8_lossy(b).to_string(),
Value::String(s) => s.clone(),
_ => value.to_display_string(),
};
(value, s)
}
0x001b => {
let bytes = match &value {
Value::Undefined(b) => b.clone(),
_ => vec![],
};
let n = bytes.len() / 2;
let vals: Vec<i64> = (0..n)
.map(|i| rw2_u16(&bytes, i*2, le) as i64)
.collect();
let s = vals.iter().map(|v| v.to_string()).collect::<Vec<_>>().join(" ");
let raw = Value::List(vals.iter().map(|&v| Value::U16(v as u16)).collect());
(raw, s)
}
_ => {
let pv = panasonic_raw_print_conv(e.tag, &value)
.or_else(|| crate::tags::exif::print_conv("IFD0", e.tag, &value))
.unwrap_or_else(|| value.to_display_string());
(value, pv)
}
};
if e.tag == 0x0111 || e.tag == 0x0201 { thumb_offset = final_value.as_u64();
}
if e.tag == 0x0117 || e.tag == 0x0202 { thumb_length = final_value.as_u64();
}
tags.push(Tag {
id: TagId::Numeric(e.tag),
name: name.to_string(),
description: name.to_string(),
group: TagGroup {
family0: "EXIF".into(),
family1: "IFD0".into(),
family2: family2.to_string(),
},
raw_value: final_value,
print_value,
priority: 0,
});
}
if let Some(wb_data) = wb_info2_data {
tags.extend(parse_wb_info2(&wb_data, le));
}
if let Some(dist_data) = distortion_data {
tags.extend(parse_distortion_info(&dist_data, le));
}
if let Some(jpg_data) = jpg_from_raw {
if let Ok(jpg_tags) = crate::formats::jpeg::read_jpeg(&jpg_data) {
let existing_names: std::collections::HashSet<String> = tags.iter()
.map(|t| t.name.clone())
.collect();
for t in jpg_tags {
if t.group.family0 == "File" {
match t.name.as_str() {
"EncodingProcess" | "ColorComponents" | "YCbCrSubSampling" => {
}
_ => continue,
}
}
if t.group.family0 == "Composite" { continue; }
if t.name == "ExifByteOrder" { continue; }
if t.name == "PanasonicTitle" || t.name == "PanasonicTitle2" {
let is_empty = match &t.raw_value {
Value::Undefined(b) | Value::Binary(b) => b.iter().all(|&x| x == 0),
Value::String(s) => s.trim_end_matches('\0').is_empty(),
_ => false,
};
if is_empty { continue; }
}
if t.group.family1 == "IFD0" && existing_names.contains(&t.name) {
continue;
}
tags.push(t);
}
}
}
let entries_end = entries_start + entry_count * 12;
if entries_end + 4 <= data.len() {
let next_ifd = rw2_u32(data, entries_end, le) as usize;
if next_ifd > 0 && next_ifd + 2 <= data.len() {
let ifd1_count = rw2_u16(data, next_ifd, le) as usize;
let ifd1_start = next_ifd + 2;
let ifd1_count = ifd1_count.min((data.len().saturating_sub(ifd1_start)) / 12).min(50);
for i in 0..ifd1_count {
let eoff = ifd1_start + i * 12;
let e = match rw2_parse_entry(data, eoff, le) {
Some(e) => e,
None => break,
};
match e.tag {
0x0201 => { if let Some(v) = rw2_read_value(data, e.dtype, e.count, &e.inline_data, e.value_offset, le) {
thumb_offset = v.as_u64();
tags.push(Tag {
id: TagId::Numeric(e.tag),
name: "ThumbnailOffset".into(),
description: "Thumbnail Offset".into(),
group: TagGroup { family0: "EXIF".into(), family1: "IFD1".into(), family2: "Image".into() },
raw_value: v,
print_value: thumb_offset.unwrap_or(0).to_string(),
priority: 0,
});
}
}
0x0202 => { if let Some(v) = rw2_read_value(data, e.dtype, e.count, &e.inline_data, e.value_offset, le) {
thumb_length = v.as_u64();
tags.push(Tag {
id: TagId::Numeric(e.tag),
name: "ThumbnailLength".into(),
description: "Thumbnail Length".into(),
group: TagGroup { family0: "EXIF".into(), family1: "IFD1".into(), family2: "Image".into() },
raw_value: v,
print_value: thumb_length.unwrap_or(0).to_string(),
priority: 0,
});
}
}
_ => {}
}
}
}
}
if let (Some(off), Some(len)) = (thumb_offset, thumb_length) {
let off = off as usize;
let len = len as usize;
if off > 0 && len > 0 && off + len <= data.len() {
tags.push(Tag {
id: TagId::Text("ThumbnailImage".into()),
name: "ThumbnailImage".into(),
description: "Thumbnail Image".into(),
group: TagGroup { family0: "EXIF".into(), family1: "IFD1".into(), family2: "Image".into() },
raw_value: Value::Binary(data[off..off+len].to_vec()),
print_value: format!("(Binary data {} bytes, use -b option to extract)", len),
priority: 0,
});
}
}
Ok(tags)
}
fn process_geotiff(tags: &mut Vec<Tag>) {
let dir_vals: Vec<u16> = {
let tag = tags.iter().find(|t| t.name == "GeoTiffDirectory");
match tag {
Some(t) => extract_u16_list(&t.raw_value),
None => return, }
};
let double_vals: Vec<f64> = {
let tag = tags.iter().find(|t| t.name == "GeoTiffDoubleParams");
match tag {
Some(t) => extract_f64_list(&t.raw_value),
None => vec![],
}
};
let ascii_val: String = {
let tag = tags.iter().find(|t| t.name == "GeoTiffAsciiParams");
match tag {
Some(t) => match &t.raw_value {
Value::String(s) => s.clone(),
_ => String::new(),
},
None => String::new(),
}
};
if dir_vals.len() < 4 {
return;
}
let version = dir_vals[0];
let revision = dir_vals[1];
let minor_rev = dir_vals[2];
let num_entries = dir_vals[3] as usize;
if dir_vals.len() < 4 + num_entries * 4 {
return;
}
let mut geo_tags: Vec<Tag> = Vec::new();
let version_str = format!("{}.{}.{}", version, revision, minor_rev);
geo_tags.push(make_geotiff_tag(1, "GeoTiffVersion", "GeoTiff Version",
Value::String(version_str.clone()), version_str));
for i in 0..num_entries {
let base = 4 + i * 4;
let key_id = dir_vals[base];
let location = dir_vals[base + 1];
let count = dir_vals[base + 2] as usize;
let offset = dir_vals[base + 3] as usize;
let (name, description) = geotiff_key_name(key_id);
if name.is_empty() {
continue; }
let (raw_val, print_val) = match location {
0 => {
let v = offset as u16;
let raw = Value::U16(v);
let print = geotiff_print_conv(key_id, v as i64)
.unwrap_or_else(|| v.to_string());
(raw, print)
}
0x87B0 => {
if offset + count > double_vals.len() {
continue;
}
if count == 1 {
let v = double_vals[offset];
let s = format_g15(v);
(Value::F64(v), s)
} else {
let vals: Vec<Value> = double_vals[offset..offset + count]
.iter().map(|&v| Value::F64(v)).collect();
let s = double_vals[offset..offset + count]
.iter().map(|&v| format_g15(v)).collect::<Vec<_>>().join(" ");
(Value::List(vals), s)
}
}
0x87B1 => {
let start = offset;
let end = (start + count).min(ascii_val.len());
let mut s = ascii_val[start..end].to_string();
if s.ends_with('|') || s.ends_with('\0') {
s.pop();
}
(Value::String(s.clone()), s)
}
0x87AF => {
if offset + count > dir_vals.len() {
continue;
}
if count == 1 {
let v = dir_vals[offset];
let raw = Value::U16(v);
let print = geotiff_print_conv(key_id, v as i64)
.unwrap_or_else(|| v.to_string());
(raw, print)
} else {
let vals: Vec<Value> = dir_vals[offset..offset + count]
.iter().map(|&v| Value::U16(v)).collect();
let s = vals.iter().map(|v| v.to_display_string()).collect::<Vec<_>>().join(" ");
(Value::List(vals), s)
}
}
_ => continue, };
geo_tags.push(make_geotiff_tag(key_id, name, description, raw_val, print_val));
}
tags.retain(|t| {
t.name != "GeoTiffDirectory"
&& t.name != "GeoTiffDoubleParams"
&& t.name != "GeoTiffAsciiParams"
});
tags.extend(geo_tags);
}
fn make_geotiff_tag(key_id: u16, name: &str, description: &str, raw_val: Value, print_val: String) -> Tag {
Tag {
id: TagId::Numeric(key_id),
name: name.to_string(),
description: description.to_string(),
group: TagGroup {
family0: "EXIF".to_string(),
family1: "IFD0".to_string(),
family2: "Location".to_string(),
},
raw_value: raw_val,
print_value: print_val,
priority: 0,
}
}
fn format_g15(v: f64) -> String {
crate::value::format_g15(v)
}
fn extract_u16_list(value: &Value) -> Vec<u16> {
match value {
Value::U16(v) => vec![*v],
Value::List(items) => items.iter().filter_map(|item| {
match item {
Value::U16(v) => Some(*v),
Value::U8(v) => Some(*v as u16),
_ => None,
}
}).collect(),
_ => vec![],
}
}
fn extract_f64_list(value: &Value) -> Vec<f64> {
match value {
Value::F64(v) => vec![*v],
Value::F32(v) => vec![*v as f64],
Value::List(items) => items.iter().filter_map(|item| {
match item {
Value::F64(v) => Some(*v),
Value::F32(v) => Some(*v as f64),
_ => None,
}
}).collect(),
_ => vec![],
}
}
fn geotiff_key_name(key_id: u16) -> (&'static str, &'static str) {
match key_id {
1024 => ("GTModelType", "GT Model Type"),
1025 => ("GTRasterType", "GT Raster Type"),
1026 => ("GTCitation", "GT Citation"),
2048 => ("GeographicType", "Geographic Type"),
2049 => ("GeogCitation", "Geog Citation"),
2050 => ("GeogGeodeticDatum", "Geog Geodetic Datum"),
2051 => ("GeogPrimeMeridian", "Geog Prime Meridian"),
2052 => ("GeogLinearUnits", "Geog Linear Units"),
2053 => ("GeogLinearUnitSize", "Geog Linear Unit Size"),
2054 => ("GeogAngularUnits", "Geog Angular Units"),
2055 => ("GeogAngularUnitSize", "Geog Angular Unit Size"),
2056 => ("GeogEllipsoid", "Geog Ellipsoid"),
2057 => ("GeogSemiMajorAxis", "Geog Semi Major Axis"),
2058 => ("GeogSemiMinorAxis", "Geog Semi Minor Axis"),
2059 => ("GeogInvFlattening", "Geog Inv Flattening"),
2060 => ("GeogAzimuthUnits", "Geog Azimuth Units"),
2061 => ("GeogPrimeMeridianLong", "Geog Prime Meridian Long"),
2062 => ("GeogToWGS84", "Geog To WGS84"),
3072 => ("ProjectedCSType", "Projected CS Type"),
3073 => ("PCSCitation", "PCS Citation"),
3074 => ("Projection", "Projection"),
3075 => ("ProjCoordTrans", "Proj Coord Trans"),
3076 => ("ProjLinearUnits", "Proj Linear Units"),
3077 => ("ProjLinearUnitSize", "Proj Linear Unit Size"),
3078 => ("ProjStdParallel1", "Proj Std Parallel 1"),
3079 => ("ProjStdParallel2", "Proj Std Parallel 2"),
3080 => ("ProjNatOriginLong", "Proj Nat Origin Long"),
3081 => ("ProjNatOriginLat", "Proj Nat Origin Lat"),
3082 => ("ProjFalseEasting", "Proj False Easting"),
3083 => ("ProjFalseNorthing", "Proj False Northing"),
3084 => ("ProjFalseOriginLong", "Proj False Origin Long"),
3085 => ("ProjFalseOriginLat", "Proj False Origin Lat"),
3086 => ("ProjFalseOriginEasting", "Proj False Origin Easting"),
3087 => ("ProjFalseOriginNorthing", "Proj False Origin Northing"),
3088 => ("ProjCenterLong", "Proj Center Long"),
3089 => ("ProjCenterLat", "Proj Center Lat"),
3090 => ("ProjCenterEasting", "Proj Center Easting"),
3091 => ("ProjCenterNorthing", "Proj Center Northing"),
3092 => ("ProjScaleAtNatOrigin", "Proj Scale At Nat Origin"),
3093 => ("ProjScaleAtCenter", "Proj Scale At Center"),
3094 => ("ProjAzimuthAngle", "Proj Azimuth Angle"),
3095 => ("ProjStraightVertPoleLong", "Proj Straight Vert Pole Long"),
4096 => ("VerticalCSType", "Vertical CS Type"),
4097 => ("VerticalCitation", "Vertical Citation"),
4098 => ("VerticalDatum", "Vertical Datum"),
4099 => ("VerticalUnits", "Vertical Units"),
_ => ("", ""),
}
}
fn geotiff_print_conv(key_id: u16, value: i64) -> Option<String> {
if let Some(s) = crate::tags::print_conv_generated::print_conv("GeoTiff", key_id, value) {
return Some(s.to_string());
}
match key_id {
2048 => geotiff_epsg_gcs(value),
2050 => geotiff_epsg_datum(value),
3072 => geotiff_epsg_pcs(value),
3074 => geotiff_epsg_proj(value),
_ => None,
}
}
fn geotiff_epsg_gcs(value: i64) -> Option<String> {
let s = match value {
4001 => "Airy 1830", 4002 => "Airy Modified 1849",
4003 => "Australian National Spheroid", 4004 => "Bessel 1841",
4005 => "Bessel Modified", 4006 => "Bessel Namibia",
4007 => "Clarke 1858", 4008 => "Clarke 1866",
4009 => "Clarke 1866 Michigan", 4010 => "Clarke 1880 Benoit",
4011 => "Clarke 1880 IGN", 4012 => "Clarke 1880 RGS",
4013 => "Clarke 1880 Arc", 4014 => "Clarke 1880 SGA 1922",
4015 => "Everest 1830 1937 Adjustment", 4016 => "Everest 1830 1967 Definition",
4017 => "Everest 1830 1975 Definition", 4018 => "Everest 1830 Modified",
4019 => "GRS 1980", 4020 => "Helmert 1906",
4021 => "Indonesian National Spheroid", 4022 => "International 1924",
4023 => "International 1967", 4024 => "Krassowsky 1940",
4025 => "NWL9D", 4026 => "NWL10D", 4027 => "Plessis 1817",
4028 => "Struve 1860", 4029 => "War Office", 4030 => "WGS84",
4031 => "GEM10C", 4032 => "OSU86F", 4033 => "OSU91A",
4034 => "Clarke 1880", 4035 => "Sphere",
4120 => "Greek", 4121 => "GGRS87", 4123 => "KKJ", 4124 => "RT90",
4133 => "EST92", 4815 => "Greek Athens",
4201 => "Adindan", 4202 => "AGD66", 4203 => "AGD84",
4204 => "Ain el Abd", 4205 => "Afgooye", 4206 => "Agadez",
4267 => "NAD27", 4269 => "NAD83", 4277 => "OSGB 1936",
4278 => "OSGB70", 4279 => "OS SN 1980",
4283 => "GDA94", 4289 => "Amersfoort",
4291 => "SAD69", 4292 => "Sapper Hill 1943",
4293 => "Schwarzeck", 4297 => "Moznet",
4298 => "Indian 1954", 4300 => "TM65",
4301 => "Tokyo", 4302 => "Trinidad 1903",
4303 => "TC 1948", 4304 => "Voirol 1875",
4306 => "Bern 1938", 4307 => "Nord Sahara 1959",
4308 => "Stockholm 1938", 4309 => "Yacare",
4310 => "Yoff", 4311 => "Zanderij",
4312 => "MGI", 4313 => "Belge 1972",
4314 => "DHDN", 4315 => "Conakry 1905",
4317 => "Dealul Piscului 1970", 4318 => "NGN",
4319 => "KUDAMS", 4322 => "WGS 72",
4324 => "WGS 72BE", 4326 => "WGS 84",
32767 => "User Defined",
_ => return None,
};
Some(s.to_string())
}
fn geotiff_epsg_datum(value: i64) -> Option<String> {
let s = match value {
6001 => "Airy 1830", 6002 => "Airy Modified 1849",
6003 => "Australian National Spheroid", 6004 => "Bessel 1841",
6005 => "Bessel Modified", 6006 => "Bessel Namibia",
6007 => "Clarke 1858", 6008 => "Clarke 1866",
6009 => "Clarke 1866 Michigan", 6010 => "Clarke 1880 Benoit",
6011 => "Clarke 1880 IGN", 6012 => "Clarke 1880 RGS",
6013 => "Clarke 1880 Arc", 6014 => "Clarke 1880 SGA 1922",
6015 => "Everest 1830 1937 Adjustment", 6016 => "Everest 1830 1967 Definition",
6017 => "Everest 1830 1975 Definition", 6018 => "Everest 1830 Modified",
6019 => "GRS 1980", 6020 => "Helmert 1906",
6021 => "Indonesian National Spheroid", 6022 => "International 1924",
6023 => "International 1967", 6024 => "Krassowsky 1960",
6025 => "NWL9D", 6026 => "NWL10D", 6027 => "Plessis 1817",
6028 => "Struve 1860", 6029 => "War Office", 6030 => "WGS84",
6031 => "GEM10C", 6032 => "OSU86F", 6033 => "OSU91A",
6034 => "Clarke 1880", 6035 => "Sphere",
6201 => "Adindan", 6202 => "AGD66", 6203 => "AGD84",
6204 => "Ain el Abd", 6205 => "Afgooye", 6206 => "Agadez",
6267 => "NAD27", 6269 => "NAD83", 6277 => "OSGB 1936",
6278 => "OSGB70", 6279 => "OS SN 1980",
6283 => "GDA94", 6289 => "Amersfoort",
6291 => "SAD69", 6301 => "Tokyo",
6314 => "DHDN", 6322 => "WGS 72",
6324 => "WGS 72BE", 6326 => "WGS 84",
32767 => "User Defined",
_ => return None,
};
Some(s.to_string())
}
fn geotiff_epsg_pcs(value: i64) -> Option<String> {
if value >= 32601 && value <= 32660 {
return Some(format!("WGS84 UTM zone {}N", value - 32600));
}
if value >= 32701 && value <= 32760 {
return Some(format!("WGS84 UTM zone {}S", value - 32700));
}
let s = match value {
20137 => "Adindan UTM zone 37N", 20138 => "Adindan UTM zone 38N",
20248 => "AGD66 AMG zone 48", 20249 => "AGD66 AMG zone 49",
20250 => "AGD66 AMG zone 50", 20251 => "AGD66 AMG zone 51",
20252 => "AGD66 AMG zone 52", 20253 => "AGD66 AMG zone 53",
20254 => "AGD66 AMG zone 54", 20255 => "AGD66 AMG zone 55",
20256 => "AGD66 AMG zone 56", 20257 => "AGD66 AMG zone 57",
20258 => "AGD66 AMG zone 58",
26701 => "NAD27 UTM zone 1N", 26702 => "NAD27 UTM zone 2N",
26703 => "NAD27 UTM zone 3N", 26704 => "NAD27 UTM zone 4N",
26705 => "NAD27 UTM zone 5N", 26706 => "NAD27 UTM zone 6N",
26707 => "NAD27 UTM zone 7N", 26708 => "NAD27 UTM zone 8N",
26709 => "NAD27 UTM zone 9N", 26710 => "NAD27 UTM zone 10N",
26711 => "NAD27 UTM zone 11N", 26712 => "NAD27 UTM zone 12N",
26713 => "NAD27 UTM zone 13N", 26714 => "NAD27 UTM zone 14N",
26715 => "NAD27 UTM zone 15N", 26716 => "NAD27 UTM zone 16N",
26717 => "NAD27 UTM zone 17N", 26718 => "NAD27 UTM zone 18N",
26719 => "NAD27 UTM zone 19N", 26720 => "NAD27 UTM zone 20N",
26721 => "NAD27 UTM zone 21N", 26722 => "NAD27 UTM zone 22N",
26729 => "NAD27 Alabama East", 26730 => "NAD27 Alabama West",
26903 => "NAD83 UTM zone 3N", 26904 => "NAD83 UTM zone 4N",
26905 => "NAD83 UTM zone 5N", 26906 => "NAD83 UTM zone 6N",
26907 => "NAD83 UTM zone 7N", 26908 => "NAD83 UTM zone 8N",
26909 => "NAD83 UTM zone 9N", 26910 => "NAD83 UTM zone 10N",
26911 => "NAD83 UTM zone 11N", 26912 => "NAD83 UTM zone 12N",
26913 => "NAD83 UTM zone 13N", 26914 => "NAD83 UTM zone 14N",
26915 => "NAD83 UTM zone 15N", 26916 => "NAD83 UTM zone 16N",
26917 => "NAD83 UTM zone 17N", 26918 => "NAD83 UTM zone 18N",
26919 => "NAD83 UTM zone 19N", 26920 => "NAD83 UTM zone 20N",
26921 => "NAD83 UTM zone 21N", 26922 => "NAD83 UTM zone 22N",
26923 => "NAD83 UTM zone 23N",
32767 => "User Defined",
_ => return None,
};
Some(s.to_string())
}
fn geotiff_epsg_proj(value: i64) -> Option<String> {
let s = match value {
10101 => "Alabama CS27 East", 10102 => "Alabama CS27 West",
10131 => "Alabama CS83 East", 10132 => "Alabama CS83 West",
16001 => "UTM zone 1N", 16002 => "UTM zone 2N",
16003 => "UTM zone 3N", 16004 => "UTM zone 4N",
16005 => "UTM zone 5N", 16006 => "UTM zone 6N",
16007 => "UTM zone 7N", 16008 => "UTM zone 8N",
16009 => "UTM zone 9N", 16010 => "UTM zone 10N",
16011 => "UTM zone 11N", 16012 => "UTM zone 12N",
16013 => "UTM zone 13N", 16014 => "UTM zone 14N",
16015 => "UTM zone 15N", 16016 => "UTM zone 16N",
16017 => "UTM zone 17N", 16018 => "UTM zone 18N",
16019 => "UTM zone 19N", 16020 => "UTM zone 20N",
16021 => "UTM zone 21N", 16022 => "UTM zone 22N",
16023 => "UTM zone 23N", 16024 => "UTM zone 24N",
16025 => "UTM zone 25N", 16026 => "UTM zone 26N",
16027 => "UTM zone 27N", 16028 => "UTM zone 28N",
16029 => "UTM zone 29N", 16030 => "UTM zone 30N",
16031 => "UTM zone 31N", 16032 => "UTM zone 32N",
16033 => "UTM zone 33N", 16034 => "UTM zone 34N",
16035 => "UTM zone 35N", 16036 => "UTM zone 36N",
16037 => "UTM zone 37N", 16038 => "UTM zone 38N",
16039 => "UTM zone 39N", 16040 => "UTM zone 40N",
16041 => "UTM zone 41N", 16042 => "UTM zone 42N",
16043 => "UTM zone 43N", 16044 => "UTM zone 44N",
16045 => "UTM zone 45N", 16046 => "UTM zone 46N",
16047 => "UTM zone 47N", 16048 => "UTM zone 48N",
16049 => "UTM zone 49N", 16050 => "UTM zone 50N",
16051 => "UTM zone 51N", 16052 => "UTM zone 52N",
16053 => "UTM zone 53N", 16054 => "UTM zone 54N",
16055 => "UTM zone 55N", 16056 => "UTM zone 56N",
16057 => "UTM zone 57N", 16058 => "UTM zone 58N",
16059 => "UTM zone 59N", 16060 => "UTM zone 60N",
16101 => "UTM zone 1S", 16102 => "UTM zone 2S",
16103 => "UTM zone 3S", 16104 => "UTM zone 4S",
16105 => "UTM zone 5S", 16106 => "UTM zone 6S",
16107 => "UTM zone 7S", 16108 => "UTM zone 8S",
16109 => "UTM zone 9S", 16110 => "UTM zone 10S",
16111 => "UTM zone 11S", 16112 => "UTM zone 12S",
16113 => "UTM zone 13S", 16114 => "UTM zone 14S",
16115 => "UTM zone 15S", 16116 => "UTM zone 16S",
16117 => "UTM zone 17S", 16118 => "UTM zone 18S",
16119 => "UTM zone 19S", 16120 => "UTM zone 20S",
16121 => "UTM zone 21S", 16122 => "UTM zone 22S",
16123 => "UTM zone 23S", 16124 => "UTM zone 24S",
16125 => "UTM zone 25S", 16126 => "UTM zone 26S",
16127 => "UTM zone 27S", 16128 => "UTM zone 28S",
16129 => "UTM zone 29S", 16130 => "UTM zone 30S",
16131 => "UTM zone 31S", 16132 => "UTM zone 32S",
16133 => "UTM zone 33S", 16134 => "UTM zone 34S",
16135 => "UTM zone 35S", 16136 => "UTM zone 36S",
16137 => "UTM zone 37S", 16138 => "UTM zone 38S",
16139 => "UTM zone 39S", 16140 => "UTM zone 40S",
16141 => "UTM zone 41S", 16142 => "UTM zone 42S",
16143 => "UTM zone 43S", 16144 => "UTM zone 44S",
16145 => "UTM zone 45S", 16146 => "UTM zone 46S",
16147 => "UTM zone 47S", 16148 => "UTM zone 48S",
16149 => "UTM zone 49S", 16150 => "UTM zone 50S",
16151 => "UTM zone 51S", 16152 => "UTM zone 52S",
16153 => "UTM zone 53S", 16154 => "UTM zone 54S",
16155 => "UTM zone 55S", 16156 => "UTM zone 56S",
16157 => "UTM zone 57S", 16158 => "UTM zone 58S",
16159 => "UTM zone 59S", 16160 => "UTM zone 60S",
32767 => "User Defined",
_ => return None,
};
Some(s.to_string())
}
fn read_bigtiff_ifd(data: &[u8], ifd_offset: usize, is_le: bool) -> Result<Vec<Tag>> {
use crate::tags::exif as exif_tags;
if ifd_offset + 8 > data.len() {
return Ok(vec![]);
}
let entry_count = btf_read_u64(data, ifd_offset, is_le) as usize;
let entries_start = ifd_offset + 8;
let entry_size = 20usize; let max_entries = (data.len().saturating_sub(entries_start)) / entry_size;
let entry_count = entry_count.min(max_entries).min(1000);
let mut tags = Vec::new();
for i in 0..entry_count {
let eoff = entries_start + i * entry_size;
if eoff + entry_size > data.len() { break; }
let tag = btf_read_u16(data, eoff, is_le);
let dtype = btf_read_u16(data, eoff+2, is_le);
let count = btf_read_u64(data, eoff+4, is_le);
let raw_offset_bytes = &data[eoff+12..eoff+20];
let elem_size: usize = match dtype {
1 | 2 | 6 | 7 => 1,
3 | 8 => 2,
4 | 9 | 11 | 13 => 4,
5 | 10 | 12 => 8,
_ => continue,
};
let total_size = elem_size.saturating_mul(count as usize);
if total_size == 0 { continue; }
let value_slice: Vec<u8> = if total_size <= 8 {
raw_offset_bytes[..total_size.min(8)].to_vec()
} else {
let offset = btf_read_u64(raw_offset_bytes, 0, is_le) as usize;
if offset + total_size > data.len() { continue; }
data[offset..offset + total_size].to_vec()
};
let value = match bigtiff_parse_value(&value_slice, dtype, count as usize, is_le) {
Some(v) => v,
None => continue,
};
let (name, description) = {
match exif_tags::lookup("IFD0", tag) {
Some(i) => (i.name.to_string(), i.description.to_string()),
None => match exif_tags::lookup_generated(tag) {
Some((n, d)) => (n.to_string(), d.to_string()),
None => (format!("Tag0x{:04X}", tag), format!("Unknown 0x{:04X}", tag)),
}
}
};
let print_value = exif_tags::print_conv("IFD0", tag, &value)
.or_else(|| value.as_u64().and_then(|v|
crate::tags::print_conv_generated::print_conv_by_name(&name, v as i64))
.map(|s| s.to_string()))
.unwrap_or_else(|| value.to_display_string());
tags.push(Tag {
id: TagId::Numeric(tag),
name,
description,
group: TagGroup {
family0: "EXIF".into(),
family1: "IFD0".into(),
family2: "Image".into(),
},
raw_value: value,
print_value,
priority: 0,
});
}
Ok(tags)
}
fn bigtiff_parse_value(data: &[u8], dtype: u16, count: usize, is_le: bool) -> Option<Value> {
match dtype {
1 => {
if data.is_empty() { return None; }
if count == 1 { Some(Value::U8(data[0])) }
else { Some(Value::List(data.iter().map(|&b| Value::U8(b)).collect())) }
}
2 => Some(Value::String(String::from_utf8_lossy(data).trim_end_matches('\0').to_string())),
3 => {
if count == 1 { Some(Value::U16(btf_read_u16(data, 0, is_le))) }
else { Some(Value::List((0..count).map(|i| Value::U16(btf_read_u16(data, i*2, is_le))).collect())) }
}
4 | 13 => {
if count == 1 { Some(Value::U32(btf_read_u32(data, 0, is_le))) }
else { Some(Value::List((0..count).map(|i| Value::U32(btf_read_u32(data, i*4, is_le))).collect())) }
}
5 => {
if count == 1 {
Some(Value::URational(btf_read_u32(data, 0, is_le), btf_read_u32(data, 4, is_le)))
} else {
Some(Value::List((0..count).map(|i| {
Value::URational(btf_read_u32(data, i*8, is_le), btf_read_u32(data, i*8+4, is_le))
}).collect()))
}
}
7 => Some(Value::Undefined(data.to_vec())),
_ => None,
}
}
fn btf_read_u16(d: &[u8], off: usize, is_le: bool) -> u16 {
if off + 2 > d.len() { return 0; }
if is_le { u16::from_le_bytes([d[off], d[off+1]]) }
else { u16::from_be_bytes([d[off], d[off+1]]) }
}
fn btf_read_u32(d: &[u8], off: usize, is_le: bool) -> u32 {
if off + 4 > d.len() { return 0; }
if is_le { u32::from_le_bytes([d[off], d[off+1], d[off+2], d[off+3]]) }
else { u32::from_be_bytes([d[off], d[off+1], d[off+2], d[off+3]]) }
}
fn btf_read_u64(d: &[u8], off: usize, is_le: bool) -> u64 {
if off + 8 > d.len() { return 0; }
if is_le { u64::from_le_bytes([d[off], d[off+1], d[off+2], d[off+3], d[off+4], d[off+5], d[off+6], d[off+7]]) }
else { u64::from_be_bytes([d[off], d[off+1], d[off+2], d[off+3], d[off+4], d[off+5], d[off+6], d[off+7]]) }
}