image/
metadata.rs

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    pub fn from_exif(exif_orientation: u8) -> Option<Self> {
36        match exif_orientation {
37            1 => Some(Self::NoTransforms),
38            2 => Some(Self::FlipHorizontal),
39            3 => Some(Self::Rotate180),
40            4 => Some(Self::FlipVertical),
41            5 => Some(Self::Rotate90FlipH),
42            6 => Some(Self::Rotate90),
43            7 => Some(Self::Rotate270FlipH),
44            8 => Some(Self::Rotate270),
45            0 | 9.. => None,
46        }
47    }
48
49    /// Converts into [Exif orientation](https://web.archive.org/web/20200412005226/https://www.impulseadventure.com/photo/exif-orientation.html)
50    pub fn to_exif(self) -> u8 {
51        match self {
52            Self::NoTransforms => 1,
53            Self::FlipHorizontal => 2,
54            Self::Rotate180 => 3,
55            Self::FlipVertical => 4,
56            Self::Rotate90FlipH => 5,
57            Self::Rotate90 => 6,
58            Self::Rotate270FlipH => 7,
59            Self::Rotate270 => 8,
60        }
61    }
62
63    pub(crate) fn from_exif_chunk(chunk: &[u8]) -> Option<Self> {
64        let mut reader = Cursor::new(chunk);
65
66        let mut magic = [0; 4];
67        reader.read_exact(&mut magic).ok()?;
68
69        match magic {
70            [0x49, 0x49, 42, 0] => {
71                let ifd_offset = reader.read_u32::<LittleEndian>().ok()?;
72                reader.set_position(ifd_offset as u64);
73                let entries = reader.read_u16::<LittleEndian>().ok()?;
74                for _ in 0..entries {
75                    let tag = reader.read_u16::<LittleEndian>().ok()?;
76                    let format = reader.read_u16::<LittleEndian>().ok()?;
77                    let count = reader.read_u32::<LittleEndian>().ok()?;
78                    let value = reader.read_u16::<LittleEndian>().ok()?;
79                    let _padding = reader.read_u16::<LittleEndian>().ok()?;
80                    if tag == 0x112 && format == 3 && count == 1 {
81                        return Self::from_exif(value.min(255) as u8);
82                    }
83                }
84            }
85            [0x4d, 0x4d, 0, 42] => {
86                let ifd_offset = reader.read_u32::<BigEndian>().ok()?;
87                reader.set_position(ifd_offset as u64);
88                let entries = reader.read_u16::<BigEndian>().ok()?;
89                for _ in 0..entries {
90                    let tag = reader.read_u16::<BigEndian>().ok()?;
91                    let format = reader.read_u16::<BigEndian>().ok()?;
92                    let count = reader.read_u32::<BigEndian>().ok()?;
93                    let value = reader.read_u16::<BigEndian>().ok()?;
94                    let _padding = reader.read_u16::<BigEndian>().ok()?;
95                    if tag == 0x112 && format == 3 && count == 1 {
96                        return Self::from_exif(value.min(255) as u8);
97                    }
98                }
99            }
100            _ => {}
101        }
102        None
103    }
104}