use image::DynamicImage;
use std::path::Path;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Orientation {
Normal,
FlipHorizontal,
Rotate180,
FlipVertical,
Transpose,
Rotate90,
Transverse,
Rotate270,
}
impl Orientation {
pub fn from_exif_value(value: u16) -> Self {
match value {
1 => Orientation::Normal,
2 => Orientation::FlipHorizontal,
3 => Orientation::Rotate180,
4 => Orientation::FlipVertical,
5 => Orientation::Transpose,
6 => Orientation::Rotate90,
7 => Orientation::Transverse,
8 => Orientation::Rotate270,
_ => Orientation::Normal,
}
}
pub fn apply(self, img: DynamicImage) -> DynamicImage {
match self {
Orientation::Normal => img,
Orientation::FlipHorizontal => img.fliph(),
Orientation::Rotate180 => img.rotate180(),
Orientation::FlipVertical => img.flipv(),
Orientation::Transpose => img.fliph().rotate270(),
Orientation::Rotate90 => img.rotate90(),
Orientation::Transverse => img.fliph().rotate90(),
Orientation::Rotate270 => img.rotate270(),
}
}
}
pub fn read_orientation(path: &Path) -> Orientation {
let file = match std::fs::File::open(path) {
Ok(f) => f,
Err(_) => return Orientation::Normal,
};
let mut reader = std::io::BufReader::new(file);
let exif = match exif::Reader::new().read_from_container(&mut reader) {
Ok(e) => e,
Err(_) => return Orientation::Normal,
};
match exif.get_field(exif::Tag::Orientation, exif::In::PRIMARY) {
Some(field) => {
if let Some(value) = field.value.get_uint(0) {
Orientation::from_exif_value(value as u16)
} else {
Orientation::Normal
}
}
None => Orientation::Normal,
}
}
#[cfg(test)]
mod tests {
use super::*;
use image::{Rgb, RgbImage};
#[test]
fn from_exif_value_maps_known_values() {
assert_eq!(Orientation::from_exif_value(1), Orientation::Normal);
assert_eq!(Orientation::from_exif_value(2), Orientation::FlipHorizontal);
assert_eq!(Orientation::from_exif_value(3), Orientation::Rotate180);
assert_eq!(Orientation::from_exif_value(4), Orientation::FlipVertical);
assert_eq!(Orientation::from_exif_value(5), Orientation::Transpose);
assert_eq!(Orientation::from_exif_value(6), Orientation::Rotate90);
assert_eq!(Orientation::from_exif_value(7), Orientation::Transverse);
assert_eq!(Orientation::from_exif_value(8), Orientation::Rotate270);
}
#[test]
fn from_exif_value_defaults_unknown_to_normal() {
assert_eq!(Orientation::from_exif_value(0), Orientation::Normal);
assert_eq!(Orientation::from_exif_value(9), Orientation::Normal);
assert_eq!(Orientation::from_exif_value(255), Orientation::Normal);
}
#[test]
fn apply_normal_is_identity() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(4, 2, Rgb([128, 64, 32])));
let result = Orientation::Normal.apply(img.clone());
assert_eq!(result.width(), 4);
assert_eq!(result.height(), 2);
}
#[test]
fn apply_rotate90_swaps_dimensions() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(4, 2, Rgb([128, 64, 32])));
let result = Orientation::Rotate90.apply(img);
assert_eq!(result.width(), 2);
assert_eq!(result.height(), 4);
}
#[test]
fn apply_rotate270_swaps_dimensions() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(4, 2, Rgb([128, 64, 32])));
let result = Orientation::Rotate270.apply(img);
assert_eq!(result.width(), 2);
assert_eq!(result.height(), 4);
}
#[test]
fn apply_rotate180_preserves_dimensions() {
let img = DynamicImage::ImageRgb8(RgbImage::from_pixel(4, 2, Rgb([128, 64, 32])));
let result = Orientation::Rotate180.apply(img);
assert_eq!(result.width(), 4);
assert_eq!(result.height(), 2);
}
#[test]
fn read_orientation_returns_normal_for_nonexistent_file() {
let orientation = read_orientation(Path::new("/nonexistent/file.jpg"));
assert_eq!(orientation, Orientation::Normal);
}
#[test]
fn read_orientation_returns_normal_for_png() {
let temp_dir = tempfile::TempDir::new().unwrap();
let temp_path = temp_dir.path().join("test.png");
let img = RgbImage::from_pixel(2, 2, Rgb([128, 128, 128]));
img.save(&temp_path).unwrap();
let orientation = read_orientation(&temp_path);
assert_eq!(orientation, Orientation::Normal);
}
}