use crate::image::format::ExifData;
pub fn extract_exif(data: &[u8]) -> ExifData {
let mut exif = ExifData::default();
if data.len() > 4 && data[0] == 0xFF && data[1] == 0xD8 {
let mut pos = 2;
while pos + 4 < data.len() {
if data[pos] != 0xFF {
pos += 1;
continue;
}
let marker = data[pos + 1];
let seg_len = if pos + 4 <= data.len() {
u16::from_be_bytes([data[pos + 2], data[pos + 3]]) as usize
} else {
break;
};
if marker == 0xE1 && seg_len >= 8 {
if pos + 10 < data.len() && &data[pos + 4..pos + 10] == b"Exif\x00\x00" {
let tiff_start = pos + 10;
let tiff_data = &data[tiff_start..(tiff_start + seg_len - 6).min(data.len())];
parse_tiff_exif(tiff_data, &mut exif);
break;
}
}
if marker == 0xDA || marker == 0xD9 {
break; }
if marker != 0xD0
&& marker != 0xD1
&& marker != 0xD2
&& marker != 0xD3
&& marker != 0xD4
&& marker != 0xD5
&& marker != 0xD6
&& marker != 0xD7
&& marker != 0xD8
{
pos += seg_len;
} else {
pos += 2;
}
}
}
if data.len() > 8 && (&data[0..4] == b"II\x2a\x00" || &data[0..4] == b"MM\x00\x2a") {
parse_tiff_exif(data, &mut exif);
}
exif
}
fn parse_tiff_exif(data: &[u8], exif: &mut ExifData) {
if data.len() < 8 {
return;
}
let little_endian = &data[0..4] == b"II\x2a\x00";
let ifd_offset = if little_endian {
u32::from_le_bytes([data[4], data[5], data[6], data[7]]) as usize
} else {
u32::from_be_bytes([data[4], data[5], data[6], data[7]]) as usize
};
if ifd_offset + 2 > data.len() {
return;
}
let num_entries = if little_endian {
u16::from_le_bytes([data[ifd_offset], data[ifd_offset + 1]])
} else {
u16::from_be_bytes([data[ifd_offset], data[ifd_offset + 1]])
} as usize;
for i in 0..num_entries {
let entry_off = ifd_offset + 2 + i * 12;
if entry_off + 12 > data.len() {
break;
}
let tag = if little_endian {
u16::from_le_bytes([data[entry_off], data[entry_off + 1]])
} else {
u16::from_be_bytes([data[entry_off], data[entry_off + 1]])
};
let read_u16 = |off: usize| -> u16 {
if little_endian {
u16::from_le_bytes([data[off], data[off + 1]])
} else {
u16::from_be_bytes([data[off], data[off + 1]])
}
};
let read_u32 = |off: usize| -> u32 {
if little_endian {
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]])
}
};
let read_rational = |off: usize| -> Option<f64> {
if off + 8 <= data.len() {
let num = read_u32(off) as f64;
let den = read_u32(off + 4) as f64;
if den != 0.0 {
Some(num / den)
} else {
None
}
} else {
None
}
};
let value_offset = |entry_off: usize| -> usize {
if little_endian {
u32::from_le_bytes([
data[entry_off + 8],
data[entry_off + 9],
data[entry_off + 10],
data[entry_off + 11],
]) as usize
} else {
u32::from_be_bytes([
data[entry_off + 8],
data[entry_off + 9],
data[entry_off + 10],
data[entry_off + 11],
]) as usize
}
};
match tag {
256 => exif.exif_width = Some(read_u32(entry_off + 8)),
257 => exif.exif_height = Some(read_u32(entry_off + 8)),
271 => {
let off = value_offset(entry_off);
if off + 32 <= data.len() {
let end = data[off..].iter().position(|&b| b == 0).unwrap_or(0).min(32);
exif.make = String::from_utf8_lossy(&data[off..off + end]).to_string();
}
}
272 => {
let off = value_offset(entry_off);
if off + 32 <= data.len() {
let end = data[off..].iter().position(|&b| b == 0).unwrap_or(0).min(32);
exif.model = String::from_utf8_lossy(&data[off..off + end]).to_string();
}
}
274 => exif.orientation = Some(read_u16(entry_off + 8) as u8),
306 => {
let off = value_offset(entry_off);
if off + 20 <= data.len() {
let end = data[off..].iter().position(|&b| b == 0).unwrap_or(0).min(20);
exif.date_time =
Some(String::from_utf8_lossy(&data[off..off + end]).to_string());
}
}
33437 => exif.iso = Some(read_u16(entry_off + 8) as u32),
37386 => exif.focal_length = read_rational(entry_off + 8),
33434 => exif.exposure_time = read_rational(entry_off + 8),
37377 => {
exif.aperture = {
let f = read_u32(entry_off + 8) as f64;
Some(f / 100.0)
}
}
_ => {}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_extract_exif_empty() {
let exif = extract_exif(b"");
assert_eq!(exif.make, "");
assert_eq!(exif.model, "");
}
#[test]
fn test_extract_exif_no_exif() {
let data = b"\xFF\xD8\xFF\xE0\x00\x10JFIF\x00\x01\x01\x00\x00\x01\x00\x01\x00\x00\xFF\xD9";
let exif = extract_exif(data);
assert_eq!(exif.make, "");
}
#[test]
fn test_extract_exif_from_jpeg_with_app1() {
let mut jpeg = vec![0xFF, 0xD8];
jpeg.push(0xFF);
jpeg.push(0xE1);
let app1_data_len = 8 + 8 + 8; let seg_len: u16 = 2 + app1_data_len as u16;
jpeg.extend_from_slice(&seg_len.to_be_bytes());
jpeg.extend_from_slice(b"Exif\x00\x00");
jpeg.extend_from_slice(b"II\x2a\x00"); jpeg.extend_from_slice(&8u32.to_le_bytes()); jpeg.extend_from_slice(&0u16.to_le_bytes()); jpeg.extend_from_slice(&[0u8; 4]); jpeg.extend_from_slice(&[0xFF, 0xD9]);
let exif = extract_exif(&jpeg);
assert!(exif.make.is_empty());
}
}