1pub(crate) mod cicp;
3
4use std::io::{Cursor, Read};
5
6use byteorder_lite::{BigEndian, LittleEndian, ReadBytesExt, WriteBytesExt};
7
8pub use self::cicp::{
9 Cicp, CicpColorPrimaries, CicpMatrixCoefficients, CicpTransferCharacteristics, CicpTransform,
10 CicpVideoFullRangeFlag,
11};
12
13#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
20#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
21pub enum Orientation {
22 NoTransforms,
24 Rotate90,
26 Rotate180,
28 Rotate270,
30 FlipHorizontal,
32 FlipVertical,
34 Rotate90FlipH,
36 Rotate270FlipH,
38}
39
40impl Orientation {
41 #[must_use]
43 pub fn from_exif(exif_orientation: u8) -> Option<Self> {
44 match exif_orientation {
45 1 => Some(Self::NoTransforms),
46 2 => Some(Self::FlipHorizontal),
47 3 => Some(Self::Rotate180),
48 4 => Some(Self::FlipVertical),
49 5 => Some(Self::Rotate90FlipH),
50 6 => Some(Self::Rotate90),
51 7 => Some(Self::Rotate270FlipH),
52 8 => Some(Self::Rotate270),
53 0 | 9.. => None,
54 }
55 }
56
57 #[must_use]
59 pub fn to_exif(self) -> u8 {
60 match self {
61 Self::NoTransforms => 1,
62 Self::FlipHorizontal => 2,
63 Self::Rotate180 => 3,
64 Self::FlipVertical => 4,
65 Self::Rotate90FlipH => 5,
66 Self::Rotate90 => 6,
67 Self::Rotate270FlipH => 7,
68 Self::Rotate270 => 8,
69 }
70 }
71
72 #[must_use]
81 pub fn from_exif_chunk(chunk: &[u8]) -> Option<Self> {
82 Self::from_exif_chunk_inner(chunk).map(|res| res.0)
83 }
84
85 #[must_use]
93 pub fn remove_from_exif_chunk(chunk: &mut [u8]) -> Option<Self> {
94 if let Some((orientation, offset, endian)) = Self::from_exif_chunk_inner(chunk) {
95 let mut writer = Cursor::new(chunk);
96 writer.set_position(offset);
97 let no_orientation: u16 = Self::NoTransforms.to_exif().into();
98 match endian {
99 ExifEndian::Big => writer.write_u16::<BigEndian>(no_orientation).unwrap(),
100 ExifEndian::Little => writer.write_u16::<LittleEndian>(no_orientation).unwrap(),
101 }
102 Some(orientation)
103 } else {
104 None
105 }
106 }
107
108 #[must_use]
110 fn from_exif_chunk_inner(chunk: &[u8]) -> Option<(Self, u64, ExifEndian)> {
111 let mut reader = Cursor::new(chunk);
112
113 let mut magic = [0; 4];
114 reader.read_exact(&mut magic).ok()?;
115
116 match magic {
117 [0x49, 0x49, 42, 0] => {
118 return Self::locate_orientation_entry::<LittleEndian>(&mut reader)
119 .map(|(orient, offset)| (orient, offset, ExifEndian::Little));
120 }
121 [0x4d, 0x4d, 0, 42] => {
122 return Self::locate_orientation_entry::<BigEndian>(&mut reader)
123 .map(|(orient, offset)| (orient, offset, ExifEndian::Big));
124 }
125 _ => {}
126 }
127 None
128 }
129
130 fn locate_orientation_entry<B>(reader: &mut Cursor<&[u8]>) -> Option<(Self, u64)>
132 where
133 B: byteorder_lite::ByteOrder,
134 {
135 let ifd_offset = reader.read_u32::<B>().ok()?;
136 reader.set_position(u64::from(ifd_offset));
137 let entries = reader.read_u16::<B>().ok()?;
138 for _ in 0..entries {
139 let tag = reader.read_u16::<B>().ok()?;
140 let format = reader.read_u16::<B>().ok()?;
141 let count = reader.read_u32::<B>().ok()?;
142 let value = reader.read_u16::<B>().ok()?;
143 let _padding = reader.read_u16::<B>().ok()?;
144 if tag == 0x112 && format == 3 && count == 1 {
145 let offset = reader.position() - 4; let orientation = Self::from_exif(value.min(255) as u8);
147 return orientation.map(|orient| (orient, offset));
148 }
149 }
150 None
152 }
153}
154
155#[derive(Debug, Copy, Clone)]
156enum ExifEndian {
157 Big,
158 Little,
159}
160
161#[cfg(all(test, feature = "jpeg"))]
162mod tests {
163 use crate::{codecs::jpeg::JpegDecoder, ImageDecoder as _};
164
165 use super::*;
168
169 const TEST_IMAGE: &[u8] = include_bytes!("../tests/images/jpg/portrait_2.jpg");
170
171 #[test] fn test_extraction_and_clearing() {
173 let reader = Cursor::new(TEST_IMAGE);
174 let mut decoder = JpegDecoder::new(reader).expect("Failed to decode test image");
175 let mut exif_chunk = decoder
176 .exif_metadata()
177 .expect("Failed to extract Exif chunk")
178 .expect("No Exif chunk found in test image");
179
180 let orientation = Orientation::from_exif_chunk(&exif_chunk)
181 .expect("Failed to extract orientation from Exif chunk");
182 assert_eq!(orientation, Orientation::FlipHorizontal);
183
184 let orientation = Orientation::remove_from_exif_chunk(&mut exif_chunk)
185 .expect("Failed to remove orientation from Exif chunk");
186 assert_eq!(orientation, Orientation::FlipHorizontal);
187 let orientation = Orientation::from_exif_chunk(&exif_chunk)
189 .expect("Failed to extract orientation from Exif chunk after clearing it");
190 assert_eq!(orientation, Orientation::NoTransforms);
191 }
192}