1use crate::types::Orientation;
15
16#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
18#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
19#[non_exhaustive]
20pub enum OrientationSource {
21 Exif,
24 MakerNote,
26 CameraProfileOverride,
28 #[default]
30 Default,
31}
32
33impl OrientationSource {
34 pub fn name(self) -> &'static str {
36 match self {
37 OrientationSource::Exif => "exif",
38 OrientationSource::MakerNote => "maker-note",
39 OrientationSource::CameraProfileOverride => "camera-profile",
40 OrientationSource::Default => "default",
41 }
42 }
43}
44
45#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
47#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
48#[non_exhaustive]
49pub enum OrientationPolicy {
50 #[default]
52 ExifOnly,
53 MakerNotePreferred,
55 MakerNoteWhenExifNormal,
58 CanonAutoRotateAware,
65}
66
67impl OrientationPolicy {
68 pub fn name(self) -> &'static str {
70 match self {
71 OrientationPolicy::ExifOnly => "exif-only",
72 OrientationPolicy::MakerNotePreferred => "makernote-preferred",
73 OrientationPolicy::MakerNoteWhenExifNormal => "makernote-when-exif-normal",
74 OrientationPolicy::CanonAutoRotateAware => "canon-auto-rotate-aware",
75 }
76 }
77
78 pub fn parse(s: &str) -> Option<Self> {
80 Some(match s.trim() {
81 "exif-only" => OrientationPolicy::ExifOnly,
82 "makernote-preferred" => OrientationPolicy::MakerNotePreferred,
83 "makernote-when-exif-normal" => OrientationPolicy::MakerNoteWhenExifNormal,
84 "canon-auto-rotate-aware" => OrientationPolicy::CanonAutoRotateAware,
85 _ => return None,
86 })
87 }
88}
89
90#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
92#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
93#[non_exhaustive]
94pub struct OrientationMetadata {
95 pub exif: Option<Orientation>,
98 pub maker_note: Option<Orientation>,
100 pub auto_rotate: Option<bool>,
102 pub resolved: Orientation,
104 pub source: OrientationSource,
106 pub policy: OrientationPolicy,
108}
109
110impl OrientationMetadata {
111 pub fn resolve(
113 exif: Option<Orientation>,
114 maker_note: Option<Orientation>,
115 auto_rotate: Option<bool>,
116 policy: OrientationPolicy,
117 ) -> Self {
118 use OrientationSource as S;
119 let normal = Orientation::Normal;
120 let from_exif = || exif.map(|o| (o, S::Exif));
121 let from_mn = || maker_note.map(|o| (o, S::MakerNote));
122 let chosen = match policy {
123 OrientationPolicy::ExifOnly => from_exif(),
124 OrientationPolicy::MakerNotePreferred => from_mn().or_else(from_exif),
125 OrientationPolicy::MakerNoteWhenExifNormal => match exif {
126 Some(o) if o != normal => from_exif(),
127 _ => from_mn().or_else(from_exif),
128 },
129 OrientationPolicy::CanonAutoRotateAware => match (exif, maker_note) {
130 (Some(o), _) if o != normal => from_exif(),
131 (None, _) => from_mn(),
132 (Some(_), Some(m)) if m != normal && auto_rotate == Some(true) => from_mn(),
133 (Some(_), _) => from_exif(),
134 },
135 };
136 let (resolved, source) = chosen.unwrap_or((normal, S::Default));
137 OrientationMetadata { exif, maker_note, auto_rotate, resolved, source, policy }
138 }
139
140 pub fn from_container(orientation: Orientation) -> Self {
143 let exif = (orientation != Orientation::Normal).then_some(orientation);
144 Self::resolve(exif, None, None, OrientationPolicy::ExifOnly)
145 }
146
147 pub fn camera_override(mut self, orientation: Orientation) -> Self {
149 self.resolved = orientation;
150 self.source = OrientationSource::CameraProfileOverride;
151 self
152 }
153}
154
155#[cfg(test)]
156mod tests {
157 use super::*;
158 use Orientation::*;
159 use OrientationPolicy as P;
160 use OrientationSource as S;
161
162 fn r(e: Option<Orientation>, m: Option<Orientation>, a: Option<bool>, p: P) -> (Orientation, S) {
163 let o = OrientationMetadata::resolve(e, m, a, p);
164 assert_eq!((o.exif, o.maker_note, o.auto_rotate, o.policy), (e, m, a, p));
165 (o.resolved, o.source)
166 }
167
168 #[test]
169 fn exif_only_ignores_vendor_records() {
170 assert_eq!(r(Some(Normal), Some(Rotate90), None, P::ExifOnly), (Normal, S::Exif));
171 assert_eq!(r(Some(Rotate270), Some(Rotate90), None, P::ExifOnly), (Rotate270, S::Exif));
172 assert_eq!(r(None, Some(Rotate90), None, P::ExifOnly), (Normal, S::Default));
173 assert_eq!(r(None, None, None, P::ExifOnly), (Normal, S::Default));
174 }
175
176 #[test]
177 fn maker_note_preferred() {
178 assert_eq!(r(Some(Rotate270), Some(Rotate90), None, P::MakerNotePreferred), (Rotate90, S::MakerNote));
179 assert_eq!(r(Some(Rotate270), Some(Normal), None, P::MakerNotePreferred), (Normal, S::MakerNote));
180 assert_eq!(r(Some(Rotate180), None, None, P::MakerNotePreferred), (Rotate180, S::Exif));
181 assert_eq!(r(None, None, None, P::MakerNotePreferred), (Normal, S::Default));
182 }
183
184 #[test]
185 fn maker_note_when_exif_normal() {
186 let p = P::MakerNoteWhenExifNormal;
187 assert_eq!(r(Some(Normal), Some(Rotate90), None, p), (Rotate90, S::MakerNote));
188 assert_eq!(r(None, Some(Rotate270), None, p), (Rotate270, S::MakerNote));
189 assert_eq!(r(Some(Rotate180), Some(Rotate90), None, p), (Rotate180, S::Exif));
190 assert_eq!(r(Some(Normal), None, None, p), (Normal, S::Exif));
191 assert_eq!(r(Some(Normal), Some(Normal), None, p), (Normal, S::MakerNote));
192 }
193
194 #[test]
195 fn canon_auto_rotate_aware() {
196 let p = P::CanonAutoRotateAware;
197 assert_eq!(r(Some(Rotate270), Some(Rotate270), Some(true), p), (Rotate270, S::Exif));
199 assert_eq!(r(Some(Rotate90), Some(Normal), None, p), (Rotate90, S::Exif));
200 assert_eq!(r(Some(Normal), Some(Rotate270), Some(false), p), (Normal, S::Exif));
202 assert_eq!(r(Some(Normal), Some(Rotate90), None, p), (Normal, S::Exif));
204 assert_eq!(r(Some(Normal), Some(Rotate90), Some(true), p), (Rotate90, S::MakerNote));
206 assert_eq!(r(None, Some(Rotate270), None, p), (Rotate270, S::MakerNote));
208 assert_eq!(r(None, Some(Normal), Some(false), p), (Normal, S::MakerNote));
209 assert_eq!(r(None, None, None, p), (Normal, S::Default));
210 }
211
212 #[test]
213 fn container_and_override() {
214 let c = OrientationMetadata::from_container(Rotate90);
215 assert_eq!((c.exif, c.resolved, c.source), (Some(Rotate90), Rotate90, S::Exif));
216 let c = OrientationMetadata::from_container(Normal);
217 assert_eq!((c.exif, c.resolved, c.source), (None, Normal, S::Default));
218 let o = c.camera_override(Rotate90);
219 assert_eq!((o.resolved, o.source), (Rotate90, S::CameraProfileOverride));
220 }
221
222 #[test]
223 fn policy_names_round_trip() {
224 for p in [P::ExifOnly, P::MakerNotePreferred, P::MakerNoteWhenExifNormal, P::CanonAutoRotateAware] {
225 assert_eq!(P::parse(p.name()), Some(p));
226 }
227 assert_eq!(P::parse("bogus"), None);
228 }
229}