1#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
27#[repr(u16)]
28pub enum Orientation {
29 #[default]
31 Normal = 1,
32 FlipHorizontal = 2,
34 Rotate180 = 3,
36 FlipVertical = 4,
38 Transpose = 5,
40 Rotate90 = 6,
42 Transverse = 7,
44 Rotate270 = 8,
46}
47
48#[derive(Debug, Clone)]
56pub enum Exif {
57 Raw(Vec<u8>),
59 Fields(ExifFields),
61}
62
63impl Exif {
64 #[must_use]
69 pub fn raw(bytes: impl Into<Vec<u8>>) -> Self {
70 Exif::Raw(bytes.into())
71 }
72
73 #[must_use]
78 pub fn build() -> ExifFields {
79 ExifFields::default()
80 }
81
82 #[must_use]
86 pub fn to_bytes(&self) -> Option<Vec<u8>> {
87 match self {
88 Exif::Raw(bytes) => Some(bytes.clone()),
89 Exif::Fields(fields) => fields.to_bytes(),
90 }
91 }
92}
93
94impl From<ExifFields> for Exif {
95 fn from(fields: ExifFields) -> Self {
96 Exif::Fields(fields)
97 }
98}
99
100#[derive(Debug, Clone, Default)]
106pub struct ExifFields {
107 orientation: Option<Orientation>,
108 copyright: Option<String>,
109}
110
111impl ExifFields {
112 #[must_use]
116 pub fn orientation(mut self, orientation: Orientation) -> Self {
117 self.orientation = Some(orientation);
118 self
119 }
120
121 #[must_use]
125 pub fn copyright(mut self, copyright: impl Into<String>) -> Self {
126 self.copyright = Some(copyright.into());
127 self
128 }
129
130 #[must_use]
134 pub fn to_bytes(&self) -> Option<Vec<u8>> {
135 if self.orientation.is_none() && self.copyright.is_none() {
136 return None;
137 }
138 Some(build_exif_tiff(self.orientation, self.copyright.as_deref()))
139 }
140}
141
142fn build_exif_tiff(orientation: Option<Orientation>, copyright: Option<&str>) -> Vec<u8> {
147 let mut entry_count: u16 = 0;
149 if orientation.is_some() {
150 entry_count += 1;
151 }
152 if copyright.is_some() {
153 entry_count += 1;
154 }
155
156 if entry_count == 0 {
157 return Vec::new();
158 }
159
160 let ifd_size = 2 + 12 * entry_count as usize + 4;
164 let header_and_ifd = 8 + ifd_size;
165
166 let copyright_bytes = copyright.map(|s| {
168 let mut bytes = s.as_bytes().to_vec();
169 bytes.push(0); bytes
171 });
172 let copyright_len = copyright_bytes.as_ref().map(|b| b.len()).unwrap_or(0);
173 let copyright_inline = copyright_len <= 4;
174
175 let total_size = if copyright_inline {
176 header_and_ifd
177 } else {
178 header_and_ifd + copyright_len
179 };
180
181 let mut exif = Vec::with_capacity(total_size);
182
183 exif.extend_from_slice(b"II");
186 exif.extend_from_slice(&42u16.to_le_bytes());
188 exif.extend_from_slice(&8u32.to_le_bytes());
190
191 exif.extend_from_slice(&entry_count.to_le_bytes());
194
195 let value_offset = header_and_ifd as u32;
197
198 if let Some(orient) = orientation {
200 write_ifd_entry(
201 &mut exif,
202 0x0112, 3, 1, orient as u32, );
207 }
208
209 if let Some(ref bytes) = copyright_bytes {
211 let count = bytes.len() as u32;
212 let value_or_offset = if copyright_inline {
213 let mut val = [0u8; 4];
215 val[..bytes.len()].copy_from_slice(bytes);
216 u32::from_le_bytes(val)
217 } else {
218 value_offset
220 };
221
222 write_ifd_entry(
223 &mut exif,
224 0x8298, 2, count,
227 value_or_offset,
228 );
229 }
230
231 exif.extend_from_slice(&0u32.to_le_bytes());
233
234 if !copyright_inline {
236 if let Some(bytes) = copyright_bytes {
237 exif.extend_from_slice(&bytes);
238 }
239 }
240
241 exif
242}
243
244fn write_ifd_entry(buf: &mut Vec<u8>, tag: u16, type_: u16, count: u32, value: u32) {
246 buf.extend_from_slice(&tag.to_le_bytes());
247 buf.extend_from_slice(&type_.to_le_bytes());
248 buf.extend_from_slice(&count.to_le_bytes());
249 buf.extend_from_slice(&value.to_le_bytes());
250}
251
252#[cfg(test)]
253mod tests {
254 use super::*;
255
256 #[test]
257 fn test_orientation_only() {
258 let exif = Exif::build().orientation(Orientation::Rotate90);
259 let bytes = exif.to_bytes().expect("should produce bytes");
260
261 assert!(bytes.len() >= 8 + 2 + 12 + 4); assert_eq!(&bytes[0..2], b"II"); assert_eq!(&bytes[2..4], &42u16.to_le_bytes()); assert_eq!(&bytes[8..10], &1u16.to_le_bytes());
270
271 assert_eq!(&bytes[10..12], &0x0112u16.to_le_bytes()); assert_eq!(&bytes[12..14], &3u16.to_le_bytes()); assert_eq!(&bytes[14..18], &1u32.to_le_bytes()); assert_eq!(&bytes[18..20], &6u16.to_le_bytes()); }
277
278 #[test]
279 fn test_copyright_short() {
280 let exif = Exif::build().copyright("AB");
281 let bytes = exif.to_bytes().expect("should produce bytes");
282
283 assert_eq!(bytes.len(), 8 + 2 + 12 + 4); assert_eq!(&bytes[10..12], &0x8298u16.to_le_bytes()); assert_eq!(&bytes[12..14], &2u16.to_le_bytes()); assert_eq!(&bytes[14..18], &3u32.to_le_bytes()); }
291
292 #[test]
293 fn test_copyright_long() {
294 let long_copyright = "Copyright 2024 Example Corp";
295 let exif = Exif::build().copyright(long_copyright);
296 let bytes = exif.to_bytes().expect("should produce bytes");
297
298 let expected_len = 8 + 2 + 12 + 4 + long_copyright.len() + 1;
300 assert_eq!(bytes.len(), expected_len);
301
302 let string_start = 8 + 2 + 12 + 4;
304 assert_eq!(
305 &bytes[string_start..string_start + long_copyright.len()],
306 long_copyright.as_bytes()
307 );
308 }
309
310 #[test]
311 fn test_both_fields() {
312 let exif = Exif::build()
313 .orientation(Orientation::Rotate180)
314 .copyright("Test");
315 let bytes = exif.to_bytes().expect("should produce bytes");
316
317 assert_eq!(&bytes[8..10], &2u16.to_le_bytes());
319
320 assert_eq!(&bytes[10..12], &0x0112u16.to_le_bytes());
323 assert_eq!(&bytes[22..24], &0x8298u16.to_le_bytes());
324 }
325
326 #[test]
327 fn test_empty_fields() {
328 let exif = Exif::build();
329 assert!(exif.to_bytes().is_none(), "empty fields should return None");
330 }
331
332 #[test]
333 fn test_raw_bytes() {
334 let raw = vec![1u8, 2, 3, 4, 5];
335 let exif = Exif::raw(raw.clone());
336 let bytes = exif.to_bytes().expect("should produce bytes");
337 assert_eq!(bytes, raw);
338 }
339
340 #[test]
341 fn test_chaining_preserves_both() {
342 let exif = Exif::build()
344 .orientation(Orientation::Rotate90)
345 .copyright("Test");
346
347 let bytes = exif.to_bytes().expect("should produce bytes");
348
349 assert_eq!(&bytes[8..10], &2u16.to_le_bytes());
351 }
352}