1#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
18pub enum Orientation {
19 #[default]
21 Normal,
22 FlipHorizontal,
24 Rotate180,
26 FlipVertical,
28 Transpose,
30 Rotate90,
32 Transverse,
34 Rotate270,
36}
37
38impl Orientation {
39 #[must_use]
42 pub const fn from_exif(value: u16) -> Option<Self> {
43 Some(match value {
44 1 => Self::Normal,
45 2 => Self::FlipHorizontal,
46 3 => Self::Rotate180,
47 4 => Self::FlipVertical,
48 5 => Self::Transpose,
49 6 => Self::Rotate90,
50 7 => Self::Transverse,
51 8 => Self::Rotate270,
52 _ => return None,
53 })
54 }
55
56 #[must_use]
58 pub const fn exif(self) -> u8 {
59 match self {
60 Self::Normal => 1,
61 Self::FlipHorizontal => 2,
62 Self::Rotate180 => 3,
63 Self::FlipVertical => 4,
64 Self::Transpose => 5,
65 Self::Rotate90 => 6,
66 Self::Transverse => 7,
67 Self::Rotate270 => 8,
68 }
69 }
70
71 #[must_use]
74 pub const fn from_parts(turns: u8, mirrored: bool) -> Self {
75 match (turns % 4, mirrored) {
76 (0, false) => Self::Normal,
77 (0, true) => Self::FlipHorizontal,
78 (1, false) => Self::Rotate90,
79 (1, true) => Self::Transpose,
82 (2, false) => Self::Rotate180,
83 (2, true) => Self::FlipVertical,
86 (3, false) => Self::Rotate270,
87 _ => Self::Transverse,
88 }
89 }
90
91 #[must_use]
93 pub const fn clockwise_turns(self) -> u8 {
94 match self {
95 Self::Normal | Self::FlipHorizontal => 0,
96 Self::Rotate90 | Self::Transpose => 1,
97 Self::Rotate180 | Self::FlipVertical => 2,
98 Self::Rotate270 | Self::Transverse => 3,
99 }
100 }
101
102 #[must_use]
104 pub const fn mirrored(self) -> bool {
105 matches!(
106 self,
107 Self::FlipHorizontal | Self::FlipVertical | Self::Transpose | Self::Transverse
108 )
109 }
110
111 #[must_use]
113 pub const fn transposes(self) -> bool {
114 self.clockwise_turns() % 2 == 1
115 }
116
117 #[must_use]
128 pub fn from_exif_block(exif: &[u8]) -> Option<Self> {
129 let tiff = exif.strip_prefix(b"Exif\0\0").unwrap_or(exif);
130
131 let big_endian = match tiff.get(..2)? {
132 b"MM" => true,
133 b"II" => false,
134 _ => return None,
135 };
136 let short = |at: usize| -> Option<u16> {
137 let bytes = [*tiff.get(at)?, *tiff.get(at.checked_add(1)?)?];
138 Some(if big_endian {
139 u16::from_be_bytes(bytes)
140 } else {
141 u16::from_le_bytes(bytes)
142 })
143 };
144 let long = |at: usize| -> Option<u32> {
145 let bytes = tiff.get(at..at.checked_add(4)?)?;
146 let bytes = [
147 *bytes.first()?,
148 *bytes.get(1)?,
149 *bytes.get(2)?,
150 *bytes.get(3)?,
151 ];
152 Some(if big_endian {
153 u32::from_be_bytes(bytes)
154 } else {
155 u32::from_le_bytes(bytes)
156 })
157 };
158
159 if short(2)? != 42 {
160 return None;
161 }
162 let ifd = usize::try_from(long(4)?).ok()?;
163 let entries = short(ifd)?;
164 for entry in 0..usize::from(entries) {
165 let at = ifd.checked_add(2)?.checked_add(entry.checked_mul(12)?)?;
166 if short(at)? == 0x0112 {
169 return Self::from_exif(short(at.checked_add(8)?)?);
170 }
171 }
172 None
173 }
174
175 pub fn write_to_exif_block(self, exif: &mut [u8]) -> bool {
183 let start = if exif.starts_with(b"Exif\0\0") { 6 } else { 0 };
184 let Some(tiff) = exif.get_mut(start..) else {
185 return false;
186 };
187 let Some(at) = orientation_entry(tiff) else {
188 return false;
189 };
190 let big_endian = tiff.first() == Some(&b'M');
191 let value = u16::from(self.exif());
192 let bytes = if big_endian {
193 value.to_be_bytes()
194 } else {
195 value.to_le_bytes()
196 };
197 match tiff.get_mut(at + 8..at + 10) {
198 Some(slot) => {
199 slot.copy_from_slice(&bytes);
200 true
201 }
202 None => false,
203 }
204 }
205}
206
207fn orientation_entry(tiff: &[u8]) -> Option<usize> {
209 let big_endian = match tiff.get(..2)? {
210 b"MM" => true,
211 b"II" => false,
212 _ => return None,
213 };
214 let short = |at: usize| -> Option<u16> {
215 let bytes = [*tiff.get(at)?, *tiff.get(at.checked_add(1)?)?];
216 Some(if big_endian {
217 u16::from_be_bytes(bytes)
218 } else {
219 u16::from_le_bytes(bytes)
220 })
221 };
222 let long = |at: usize| -> Option<u32> {
223 let b = tiff.get(at..at.checked_add(4)?)?;
224 let bytes = [*b.first()?, *b.get(1)?, *b.get(2)?, *b.get(3)?];
225 Some(if big_endian {
226 u32::from_be_bytes(bytes)
227 } else {
228 u32::from_le_bytes(bytes)
229 })
230 };
231 if short(2)? != 42 {
232 return None;
233 }
234 let ifd = usize::try_from(long(4)?).ok()?;
235 for entry in 0..usize::from(short(ifd)?) {
236 let at = ifd.checked_add(2)?.checked_add(entry.checked_mul(12)?)?;
237 if short(at)? == 0x0112 {
238 tiff.get(at..at.checked_add(12)?)?;
239 return Some(at);
240 }
241 }
242 None
243}
244
245#[cfg(test)]
246#[allow(
247 clippy::unwrap_used,
248 clippy::indexing_slicing,
249 reason = "tests assert on known-good values"
250)]
251mod tests {
252 use super::*;
253
254 const ALL: [Orientation; 8] = [
255 Orientation::Normal,
256 Orientation::FlipHorizontal,
257 Orientation::Rotate180,
258 Orientation::FlipVertical,
259 Orientation::Transpose,
260 Orientation::Rotate90,
261 Orientation::Transverse,
262 Orientation::Rotate270,
263 ];
264
265 fn by_parts(orientation: Orientation, w: usize, h: usize) -> (Vec<usize>, usize, usize) {
268 let mut grid: Vec<usize> = (0..w * h).collect();
269 let (mut w, mut h) = (w, h);
270 for _ in 0..orientation.clockwise_turns() {
271 let mut turned = vec![0; w * h];
273 for y in 0..w {
274 for x in 0..h {
275 turned[y * h + x] = grid[(h - 1 - x) * w + y];
276 }
277 }
278 grid = turned;
279 (w, h) = (h, w);
280 }
281 if orientation.mirrored() {
282 for row in grid.chunks_mut(w) {
283 row.reverse();
284 }
285 }
286 (grid, w, h)
287 }
288
289 fn by_definition(orientation: Orientation, w: usize, h: usize) -> (Vec<usize>, usize, usize) {
292 let (ow, oh) = if orientation.transposes() {
293 (h, w)
294 } else {
295 (w, h)
296 };
297 let mut grid = vec![0; w * h];
298 for y in 0..oh {
299 for x in 0..ow {
300 let (sx, sy) = match orientation {
301 Orientation::Normal => (x, y),
302 Orientation::FlipHorizontal => (w - 1 - x, y),
303 Orientation::Rotate180 => (w - 1 - x, h - 1 - y),
304 Orientation::FlipVertical => (x, h - 1 - y),
305 Orientation::Transpose => (y, x),
306 Orientation::Rotate90 => (y, h - 1 - x),
307 Orientation::Transverse => (w - 1 - y, h - 1 - x),
308 Orientation::Rotate270 => (w - 1 - y, x),
309 };
310 grid[y * ow + x] = sy * w + sx;
311 }
312 }
313 (grid, ow, oh)
314 }
315
316 #[test]
317 fn the_parts_decomposition_matches_the_exif_definitions() {
318 for orientation in ALL {
319 assert_eq!(
320 by_parts(orientation, 3, 2),
321 by_definition(orientation, 3, 2),
322 "{orientation:?}"
323 );
324 }
325 }
326
327 #[test]
328 fn parts_and_exif_values_round_trip() {
329 for orientation in ALL {
330 assert_eq!(
331 Orientation::from_parts(orientation.clockwise_turns(), orientation.mirrored()),
332 orientation
333 );
334 assert_eq!(
335 Orientation::from_exif(u16::from(orientation.exif())),
336 Some(orientation)
337 );
338 }
339 assert_eq!(Orientation::from_parts(5, false), Orientation::Rotate90);
340 assert_eq!(Orientation::from_exif(0), None);
341 assert_eq!(Orientation::from_exif(9), None);
342 }
343
344 #[test]
345 fn exif_orientation_is_read_from_both_byte_orders() {
346 let little = b"Exif\0\0II*\0\x08\0\0\0\x01\0\x12\x01\x03\0\x01\0\0\0\x06\0\0\0";
348 assert_eq!(
349 Orientation::from_exif_block(little),
350 Some(Orientation::Rotate90)
351 );
352
353 let big = b"Exif\0\0MM\0*\0\0\0\x08\0\x01\x01\x12\0\x03\0\0\0\x01\0\x03\0\0";
354 assert_eq!(
355 Orientation::from_exif_block(big),
356 Some(Orientation::Rotate180)
357 );
358 }
359
360 #[test]
361 fn the_exif_identifier_is_optional() {
362 let bare = b"II*\0\x08\0\0\0\x01\0\x12\x01\x03\0\x01\0\0\0\x08\0\0\0";
363 assert_eq!(
364 Orientation::from_exif_block(bare),
365 Some(Orientation::Rotate270)
366 );
367 }
368
369 #[test]
370 fn the_orientation_tag_is_rewritten_in_place() {
371 let mut little =
372 b"Exif\0\0II*\0\x08\0\0\0\x01\0\x12\x01\x03\0\x01\0\0\0\x06\0\0\0".to_vec();
373 assert!(Orientation::Normal.write_to_exif_block(&mut little));
374 assert_eq!(
375 Orientation::from_exif_block(&little),
376 Some(Orientation::Normal)
377 );
378 let mut big = b"MM\0*\0\0\0\x08\0\x01\x01\x12\0\x03\0\0\0\x01\0\x03\0\0".to_vec();
379 assert!(Orientation::Rotate90.write_to_exif_block(&mut big));
380 assert_eq!(
381 Orientation::from_exif_block(&big),
382 Some(Orientation::Rotate90)
383 );
384 let mut untagged = b"II*\0\x08\0\0\0\x01\0\x10\x01\x03\0\x01\0\0\0\x06\0\0\0".to_vec();
386 let before = untagged.clone();
387 assert!(!Orientation::Normal.write_to_exif_block(&mut untagged));
388 assert_eq!(untagged, before);
389 assert!(!Orientation::Normal.write_to_exif_block(&mut b"II*\0\xff\0\0\0".to_vec()));
390 }
391
392 #[test]
393 fn broken_exif_yields_no_orientation_rather_than_an_error() {
394 assert_eq!(
395 Orientation::from_exif_block(b"Exif\0\0XX*\0\x08\0\0\0"),
396 None
397 );
398 assert_eq!(Orientation::from_exif_block(b"Exif\0\0II*\0"), None);
399 assert_eq!(Orientation::from_exif_block(b"not exif at all"), None);
400 assert_eq!(Orientation::from_exif_block(b""), None);
401 assert_eq!(Orientation::from_exif_block(b"II*\0\xff\xff\xff\xff"), None);
403 assert_eq!(
405 Orientation::from_exif_block(b"II*\0\x08\0\0\0\xff\xff"),
406 None
407 );
408 let bogus = b"Exif\0\0II*\0\x08\0\0\0\x01\0\x12\x01\x03\0\x01\0\0\0\x09\0\0\0";
410 assert_eq!(Orientation::from_exif_block(bogus), None);
411 }
412
413 #[test]
414 fn a_block_without_the_tag_has_no_orientation() {
415 let other = b"II*\0\x08\0\0\0\x01\0\x00\x01\x03\0\x01\0\0\0\x06\0\0\0";
417 assert_eq!(Orientation::from_exif_block(other), None);
418 }
419}