1pub(crate) mod cicp;
3
4use core::num::NonZeroU32;
5
6pub use self::cicp::{
7 Cicp, CicpColorPrimaries, CicpMatrixCoefficients, CicpTransferCharacteristics, CicpTransform,
8 CicpVideoFullRangeFlag,
9};
10
11#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19pub enum Orientation {
20 NoTransforms,
22 Rotate90,
24 Rotate180,
26 Rotate270,
28 FlipHorizontal,
30 FlipVertical,
32 Rotate90FlipH,
34 Rotate270FlipH,
36}
37
38impl Orientation {
39 #[must_use]
41 pub fn from_exif(exif_orientation: u8) -> Option<Self> {
42 match exif_orientation {
43 1 => Some(Self::NoTransforms),
44 2 => Some(Self::FlipHorizontal),
45 3 => Some(Self::Rotate180),
46 4 => Some(Self::FlipVertical),
47 5 => Some(Self::Rotate90FlipH),
48 6 => Some(Self::Rotate90),
49 7 => Some(Self::Rotate270FlipH),
50 8 => Some(Self::Rotate270),
51 0 | 9.. => None,
52 }
53 }
54
55 #[must_use]
57 pub fn to_exif(self) -> u8 {
58 match self {
59 Self::NoTransforms => 1,
60 Self::FlipHorizontal => 2,
61 Self::Rotate180 => 3,
62 Self::FlipVertical => 4,
63 Self::Rotate90FlipH => 5,
64 Self::Rotate90 => 6,
65 Self::Rotate270FlipH => 7,
66 Self::Rotate270 => 8,
67 }
68 }
69
70 #[must_use]
79 pub fn from_exif_chunk(chunk: &[u8]) -> Option<Self> {
80 Self::from_exif_chunk_inner(chunk).map(|res| res.0)
81 }
82
83 #[must_use]
91 pub fn remove_from_exif_chunk(chunk: &mut [u8]) -> Option<Self> {
92 if let Some((orientation, offset, endian)) = Self::from_exif_chunk_inner(chunk) {
93 let off = offset as usize;
94 let no_orientation: u16 = Self::NoTransforms.to_exif().into();
95 match endian {
96 ExifEndian::Big => {
97 let bytes = no_orientation.to_be_bytes();
98 chunk[off..off + 2].copy_from_slice(&bytes);
99 }
100 ExifEndian::Little => {
101 let bytes = no_orientation.to_le_bytes();
102 chunk[off..off + 2].copy_from_slice(&bytes);
103 }
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 if chunk.len() < 4 {
115 return None;
116 }
117 let magic = &chunk[..4];
118
119 match magic {
120 [0x49, 0x49, 42, 0] => {
121 return Self::locate_orientation_entry(chunk, ExifEndian::Little)
122 .map(|(orient, offset)| (orient, offset, ExifEndian::Little));
123 }
124 [0x4d, 0x4d, 0, 42] => {
125 return Self::locate_orientation_entry(chunk, ExifEndian::Big)
126 .map(|(orient, offset)| (orient, offset, ExifEndian::Big));
127 }
128 _ => {}
129 }
130 None
131 }
132
133 fn read_u16_at(chunk: &[u8], offset: usize, endian: ExifEndian) -> Option<u16> {
134 let bytes = chunk.get(offset..offset + 2)?;
135 Some(match endian {
136 ExifEndian::Big => u16::from_be_bytes([bytes[0], bytes[1]]),
137 ExifEndian::Little => u16::from_le_bytes([bytes[0], bytes[1]]),
138 })
139 }
140
141 fn read_u32_at(chunk: &[u8], offset: usize, endian: ExifEndian) -> Option<u32> {
142 let bytes = chunk.get(offset..offset + 4)?;
143 Some(match endian {
144 ExifEndian::Big => u32::from_be_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
145 ExifEndian::Little => u32::from_le_bytes([bytes[0], bytes[1], bytes[2], bytes[3]]),
146 })
147 }
148
149 fn locate_orientation_entry(chunk: &[u8], endian: ExifEndian) -> Option<(Self, u64)> {
151 let ifd_offset = Self::read_u32_at(chunk, 4, endian)? as usize;
152 let entries = Self::read_u16_at(chunk, ifd_offset, endian)?;
153 let mut pos = ifd_offset + 2;
154 for _ in 0..entries {
155 let tag = Self::read_u16_at(chunk, pos, endian)?;
156 let format = Self::read_u16_at(chunk, pos + 2, endian)?;
157 let count = Self::read_u32_at(chunk, pos + 4, endian)?;
158 let value = Self::read_u16_at(chunk, pos + 8, endian)?;
159 pos += 12; if tag == 0x112 && format == 3 && count == 1 {
161 let offset = (pos - 4) as u64; let orientation = Self::from_exif(value.min(255) as u8);
163 return orientation.map(|orient| (orient, offset));
164 }
165 }
166 None
168 }
169}
170
171#[derive(Debug, Copy, Clone)]
172enum ExifEndian {
173 Big,
174 Little,
175}
176
177#[derive(Clone, Copy)]
179pub enum LoopCount {
180 Infinite,
182 Finite(NonZeroU32),
184}
185
186#[cfg(all(test, feature = "jpeg"))]
187mod tests {
188 use crate::{codecs::jpeg::JpegDecoder, ImageDecoder as _};
189 use std::io::Cursor;
190
191 use super::*;
194
195 const TEST_IMAGE: &[u8] = include_bytes!("../tests/images/jpg/portrait_2.jpg");
196
197 #[test] fn test_extraction_and_clearing() {
199 let reader = Cursor::new(TEST_IMAGE);
200 let mut decoder = JpegDecoder::new(reader).expect("Failed to decode test image");
201 let mut exif_chunk = decoder
202 .exif_metadata()
203 .expect("Failed to extract Exif chunk")
204 .expect("No Exif chunk found in test image");
205
206 let orientation = Orientation::from_exif_chunk(&exif_chunk)
207 .expect("Failed to extract orientation from Exif chunk");
208 assert_eq!(orientation, Orientation::FlipHorizontal);
209
210 let orientation = Orientation::remove_from_exif_chunk(&mut exif_chunk)
211 .expect("Failed to remove orientation from Exif chunk");
212 assert_eq!(orientation, Orientation::FlipHorizontal);
213 let orientation = Orientation::from_exif_chunk(&exif_chunk)
215 .expect("Failed to extract orientation from Exif chunk after clearing it");
216 assert_eq!(orientation, Orientation::NoTransforms);
217 }
218}