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    #[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}