use super::coeff_transform::LosslessTransform;
const TAG_ORIENTATION: u16 = 0x0112;
const TIFF_TYPE_SHORT: u16 = 3;
const EXIF_PREFIX_LEN: usize = 6;
const TIFF_HEADER_LEN: usize = 8;
pub fn parse_exif_orientation(exif_data: &[u8]) -> Option<u8> {
if exif_data.len() < EXIF_PREFIX_LEN + TIFF_HEADER_LEN {
return None;
}
if &exif_data[..6] != b"Exif\0\0" {
return None;
}
let tiff = &exif_data[EXIF_PREFIX_LEN..];
let big_endian = match &tiff[0..2] {
b"MM" => true,
b"II" => false,
_ => return None,
};
let magic = read_u16(tiff, 2, big_endian);
if magic != 42 {
return None;
}
let ifd_offset = read_u32(tiff, 4, big_endian) as usize;
if ifd_offset + 2 > tiff.len() {
return None;
}
let entry_count = read_u16(tiff, ifd_offset, big_endian) as usize;
let entries_start = ifd_offset + 2;
for i in 0..entry_count {
let entry_offset = entries_start + i * 12;
if entry_offset + 12 > tiff.len() {
break;
}
let tag = read_u16(tiff, entry_offset, big_endian);
if tag == TAG_ORIENTATION {
let type_ = read_u16(tiff, entry_offset + 2, big_endian);
if type_ != TIFF_TYPE_SHORT {
return None;
}
let value = read_u16(tiff, entry_offset + 8, big_endian);
if (1..=8).contains(&value) {
return Some(value as u8);
}
return None;
}
if tag > TAG_ORIENTATION {
break;
}
}
None
}
pub fn set_exif_orientation(exif_data: &mut [u8], orientation: u8) -> bool {
if exif_data.len() < EXIF_PREFIX_LEN + TIFF_HEADER_LEN {
return false;
}
if &exif_data[..6] != b"Exif\0\0" {
return false;
}
let tiff_start = EXIF_PREFIX_LEN;
let tiff = &exif_data[tiff_start..];
let big_endian = match &tiff[0..2] {
b"MM" => true,
b"II" => false,
_ => return false,
};
let magic = read_u16(tiff, 2, big_endian);
if magic != 42 {
return false;
}
let ifd_offset = read_u32(tiff, 4, big_endian) as usize;
if ifd_offset + 2 > tiff.len() {
return false;
}
let entry_count = read_u16(tiff, ifd_offset, big_endian) as usize;
let entries_start = ifd_offset + 2;
for i in 0..entry_count {
let entry_offset = entries_start + i * 12;
if entry_offset + 12 > tiff.len() {
break;
}
let tag = read_u16(tiff, entry_offset, big_endian);
if tag == TAG_ORIENTATION {
let abs_offset = tiff_start + entry_offset + 8;
if abs_offset + 2 > exif_data.len() {
return false;
}
write_u16(exif_data, abs_offset, orientation as u16, big_endian);
return true;
}
if tag > TAG_ORIENTATION {
break;
}
}
false
}
impl LosslessTransform {
#[must_use]
pub fn from_exif_orientation(orientation: u8) -> Option<Self> {
match orientation {
1 => Some(Self::None),
2 => Some(Self::FlipHorizontal),
3 => Some(Self::Rotate180),
4 => Some(Self::FlipVertical),
5 => Some(Self::Transpose),
6 => Some(Self::Rotate90),
7 => Some(Self::Transverse),
8 => Some(Self::Rotate270),
_ => None,
}
}
}
fn read_u16(data: &[u8], offset: usize, big_endian: bool) -> u16 {
if big_endian {
u16::from_be_bytes([data[offset], data[offset + 1]])
} else {
u16::from_le_bytes([data[offset], data[offset + 1]])
}
}
fn read_u32(data: &[u8], offset: usize, big_endian: bool) -> u32 {
if big_endian {
u32::from_be_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
} else {
u32::from_le_bytes([
data[offset],
data[offset + 1],
data[offset + 2],
data[offset + 3],
])
}
}
fn write_u16(data: &mut [u8], offset: usize, value: u16, big_endian: bool) {
let bytes = if big_endian {
value.to_be_bytes()
} else {
value.to_le_bytes()
};
data[offset] = bytes[0];
data[offset + 1] = bytes[1];
}