#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[repr(u8)]
pub enum ExifOrientation {
Normal = 1,
FlipHorizontal = 2,
Rotate180 = 3,
FlipVertical = 4,
Rotate90FlipH = 5,
Rotate90 = 6,
Rotate90FlipV = 7,
Rotate270 = 8,
}
impl ExifOrientation {
pub fn from_value(value: u8) -> Option<Self> {
match value {
1 => Some(Self::Normal),
2 => Some(Self::FlipHorizontal),
3 => Some(Self::Rotate180),
4 => Some(Self::FlipVertical),
5 => Some(Self::Rotate90FlipH),
6 => Some(Self::Rotate90),
7 => Some(Self::Rotate90FlipV),
8 => Some(Self::Rotate270),
_ => None,
}
}
pub fn rotation_degrees(&self) -> u16 {
match self {
Self::Normal | Self::FlipHorizontal => 0,
Self::Rotate180 | Self::FlipVertical => 180,
Self::Rotate90 | Self::Rotate90FlipH => 90,
Self::Rotate270 | Self::Rotate90FlipV => 270,
}
}
pub fn flip_horizontal(&self) -> bool {
matches!(self, Self::FlipHorizontal | Self::Rotate90FlipH | Self::FlipVertical | Self::Rotate90FlipV)
}
pub fn oriented_dimensions(&self, width: u32, height: u32) -> (u32, u32) {
match self {
Self::Normal | Self::FlipHorizontal | Self::Rotate180 | Self::FlipVertical => (width, height),
Self::Rotate90 | Self::Rotate90FlipH | Self::Rotate270 | Self::Rotate90FlipV => (height, width),
}
}
}
pub fn extract_orientation(data: &[u8]) -> Option<ExifOrientation> {
if data.len() < 4 || data[0] != 0xFF || data[1] != 0xD8 {
return None;
}
let mut pos = 2;
while pos + 4 <= data.len() {
if data[pos] != 0xFF {
break;
}
let marker = data[pos + 1];
if marker == 0xE1 {
let seg_len = ((data[pos + 2] as usize) << 8) | data[pos + 3] as usize;
let seg_start = pos + 4;
let seg_end = seg_start + seg_len - 2;
if seg_end <= data.len() {
if let Some(orientation) = parse_exif_orientation(&data[seg_start..seg_end]) {
return Some(orientation);
}
}
}
if marker == 0xDA {
break;
}
let seg_len = ((data[pos + 2] as usize) << 8) | data[pos + 3] as usize;
pos += 2 + seg_len;
}
None
}
fn parse_exif_orientation(data: &[u8]) -> Option<ExifOrientation> {
if data.len() < 14 || &data[0..6] != b"Exif\x00\x00" {
return None;
}
let tiff_data = &data[6..];
if tiff_data.len() < 8 {
return None;
}
let big_endian = &tiff_data[0..2] == b"MM";
let little_endian = &tiff_data[0..2] == b"II";
if !big_endian && !little_endian {
return None;
}
let read_u16 = |d: &[u8], off: usize| -> u16 {
if big_endian {
((d[off] as u16) << 8) | d[off + 1] as u16
} else {
d[off] as u16 | ((d[off + 1] as u16) << 8)
}
};
let read_u32 = |d: &[u8], off: usize| -> u32 {
if big_endian {
((d[off] as u32) << 24) | ((d[off + 1] as u32) << 16) | ((d[off + 2] as u32) << 8) | d[off + 3] as u32
} else {
d[off] as u32 | ((d[off + 1] as u32) << 8) | ((d[off + 2] as u32) << 16) | ((d[off + 3] as u32) << 24)
}
};
let magic = read_u16(tiff_data, 2);
if magic != 42 {
return None;
}
let ifd0_offset = read_u32(tiff_data, 4) as usize;
if ifd0_offset + 2 > tiff_data.len() {
return None;
}
let num_entries = read_u16(tiff_data, ifd0_offset) as usize;
let entries_start = ifd0_offset + 2;
for i in 0..num_entries {
let entry_off = entries_start + i * 12;
if entry_off + 12 > tiff_data.len() {
break;
}
let tag = read_u16(tiff_data, entry_off);
if tag == 0x0112 {
let _type = read_u16(tiff_data, entry_off + 2);
let _count = read_u32(tiff_data, entry_off + 4);
let value = tiff_data[entry_off + 8];
return ExifOrientation::from_value(value);
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_orientation_values() {
assert_eq!(ExifOrientation::from_value(1), Some(ExifOrientation::Normal));
assert_eq!(ExifOrientation::from_value(6), Some(ExifOrientation::Rotate90));
assert_eq!(ExifOrientation::from_value(8), Some(ExifOrientation::Rotate270));
assert_eq!(ExifOrientation::from_value(0), None);
assert_eq!(ExifOrientation::from_value(9), None);
}
#[test]
fn test_rotation_degrees() {
assert_eq!(ExifOrientation::Normal.rotation_degrees(), 0);
assert_eq!(ExifOrientation::Rotate90.rotation_degrees(), 90);
assert_eq!(ExifOrientation::Rotate180.rotation_degrees(), 180);
assert_eq!(ExifOrientation::Rotate270.rotation_degrees(), 270);
}
#[test]
fn test_oriented_dimensions() {
assert_eq!(ExifOrientation::Normal.oriented_dimensions(100, 200), (100, 200));
assert_eq!(ExifOrientation::Rotate90.oriented_dimensions(100, 200), (200, 100));
assert_eq!(ExifOrientation::Rotate270.oriented_dimensions(100, 200), (200, 100));
}
#[test]
fn test_extract_orientation_non_jpeg() {
assert_eq!(extract_orientation(&[0, 0, 0, 0]), None);
assert_eq!(extract_orientation(&[]), None);
}
}