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