1//! Types describing image metadata
2
3use std::io::{Cursor, Read};
4
5use byteorder_lite::{BigEndian, LittleEndian, ReadBytesExt};
6
7/// Describes the transformations to be applied to the image.
8/// Compatible with [Exif orientation](https://web.archive.org/web/20200412005226/https://www.impulseadventure.com/photo/exif-orientation.html).
9///
10/// Orientation is specified in the file's metadata, and is often written by cameras.
11///
12/// You can apply it to an image via [`DynamicImage::apply_orientation`](crate::DynamicImage::apply_orientation).
13#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
14pub enum Orientation {
15 /// Do not perform any transformations.
16 NoTransforms,
17 /// Rotate by 90 degrees clockwise.
18 Rotate90,
19 /// Rotate by 180 degrees. Can be performed in-place.
20 Rotate180,
21 /// Rotate by 270 degrees clockwise. Equivalent to rotating by 90 degrees counter-clockwise.
22 Rotate270,
23 /// Flip horizontally. Can be performed in-place.
24 FlipHorizontal,
25 /// Flip vertically. Can be performed in-place.
26 FlipVertical,
27 /// Rotate by 90 degrees clockwise and flip horizontally.
28 Rotate90FlipH,
29 /// Rotate by 270 degrees clockwise and flip horizontally.
30 Rotate270FlipH,
31}
32
33impl Orientation {
34 /// Converts from [Exif orientation](https://web.archive.org/web/20200412005226/https://www.impulseadventure.com/photo/exif-orientation.html)
35 #[must_use]
36 pub fn from_exif(exif_orientation: u8) -> Option<Self> {
37 match exif_orientation {
38 1 => Some(Self::NoTransforms),
39 2 => Some(Self::FlipHorizontal),
40 3 => Some(Self::Rotate180),
41 4 => Some(Self::FlipVertical),
42 5 => Some(Self::Rotate90FlipH),
43 6 => Some(Self::Rotate90),
44 7 => Some(Self::Rotate270FlipH),
45 8 => Some(Self::Rotate270),
46 0 | 9.. => None,
47 }
48 }
49
50 /// Converts into [Exif orientation](https://web.archive.org/web/20200412005226/https://www.impulseadventure.com/photo/exif-orientation.html)
51 #[must_use]
52 pub fn to_exif(self) -> u8 {
53 match self {
54 Self::NoTransforms => 1,
55 Self::FlipHorizontal => 2,
56 Self::Rotate180 => 3,
57 Self::FlipVertical => 4,
58 Self::Rotate90FlipH => 5,
59 Self::Rotate90 => 6,
60 Self::Rotate270FlipH => 7,
61 Self::Rotate270 => 8,
62 }
63 }
64
65 pub(crate) fn from_exif_chunk(chunk: &[u8]) -> Option<Self> {
66 let mut reader = Cursor::new(chunk);
67
68 let mut magic = [0; 4];
69 reader.read_exact(&mut magic).ok()?;
70
71 match magic {
72 [0x49, 0x49, 42, 0] => {
73 let ifd_offset = reader.read_u32::<LittleEndian>().ok()?;
74 reader.set_position(u64::from(ifd_offset));
75 let entries = reader.read_u16::<LittleEndian>().ok()?;
76 for _ in 0..entries {
77 let tag = reader.read_u16::<LittleEndian>().ok()?;
78 let format = reader.read_u16::<LittleEndian>().ok()?;
79 let count = reader.read_u32::<LittleEndian>().ok()?;
80 let value = reader.read_u16::<LittleEndian>().ok()?;
81 let _padding = reader.read_u16::<LittleEndian>().ok()?;
82 if tag == 0x112 && format == 3 && count == 1 {
83 return Self::from_exif(value.min(255) as u8);
84 }
85 }
86 }
87 [0x4d, 0x4d, 0, 42] => {
88 let ifd_offset = reader.read_u32::<BigEndian>().ok()?;
89 reader.set_position(u64::from(ifd_offset));
90 let entries = reader.read_u16::<BigEndian>().ok()?;
91 for _ in 0..entries {
92 let tag = reader.read_u16::<BigEndian>().ok()?;
93 let format = reader.read_u16::<BigEndian>().ok()?;
94 let count = reader.read_u32::<BigEndian>().ok()?;
95 let value = reader.read_u16::<BigEndian>().ok()?;
96 let _padding = reader.read_u16::<BigEndian>().ok()?;
97 if tag == 0x112 && format == 3 && count == 1 {
98 return Self::from_exif(value.min(255) as u8);
99 }
100 }
101 }
102 _ => {}
103 }
104 None
105 }
106}
107