Skip to main content

car/
metadata.rs

1//! Stable typed views over CSI TLV metadata.
2//!
3//! The raw TLV structs remain available through [`crate::raw`] for reverse
4//! engineering. This module keeps the common metadata surface small and owned so
5//! callers do not need to depend on binary-layout structs.
6
7use crate::car::CSIItem;
8use crate::model::rendition::{self, RenditionType};
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq)]
11pub enum ExifOrientation {
12    None,
13    Normal,
14    Mirrored,
15    Rotated180,
16    Rotated180Mirrored,
17    Rotated90,
18    Rotated90Mirrored,
19    Rotated270,
20    Rotated270Mirrored,
21    Unknown(u32),
22}
23
24impl ExifOrientation {
25    pub fn from_raw(value: &rendition::EXIFOrientationValue) -> Self {
26        // Keep the stable metadata view aligned with standard EXIF numeric
27        // orientation semantics. The raw enum variant names for ids 5-8 are
28        // legacy labels and do not by themselves describe the full transform.
29        match value {
30            rendition::EXIFOrientationValue::None => Self::None,
31            rendition::EXIFOrientationValue::Normal => Self::Normal,
32            rendition::EXIFOrientationValue::Mirrored => Self::Mirrored,
33            rendition::EXIFOrientationValue::Rotated180 => Self::Rotated180,
34            rendition::EXIFOrientationValue::Rotated180Mirrored => Self::Rotated180Mirrored,
35            rendition::EXIFOrientationValue::Rotated90 => Self::Rotated90Mirrored,
36            rendition::EXIFOrientationValue::Rotated90Mirrored => Self::Rotated90,
37            rendition::EXIFOrientationValue::Rotated270 => Self::Rotated270Mirrored,
38            rendition::EXIFOrientationValue::Rotated270Mirrored => Self::Rotated270,
39            rendition::EXIFOrientationValue::Unknown { tag } => Self::Unknown(*tag),
40        }
41    }
42}
43
44#[derive(Debug, Clone, Copy, PartialEq, Eq)]
45pub struct TlvRect {
46    pub x: u32,
47    pub y: u32,
48    pub width: u32,
49    pub height: u32,
50}
51
52#[derive(Debug, Clone, Copy, PartialEq, Eq)]
53pub struct TlvMetric {
54    pub width: u32,
55    pub height: u32,
56}
57
58#[derive(Debug, Clone, PartialEq, Eq)]
59pub struct TlvMetrics {
60    pub top_right_inset: TlvMetric,
61    pub bottom_left_inset: TlvMetric,
62    pub image_size: TlvMetric,
63}
64
65#[derive(Debug, Clone, PartialEq, Eq)]
66pub struct TlvReference {
67    pub x: u32,
68    pub y: u32,
69    pub width: u32,
70    pub height: u32,
71    pub layout: u16,
72    pub key_byte_len: usize,
73}
74
75#[derive(Debug, Clone, PartialEq, Eq)]
76pub struct UnknownTlv {
77    pub tag: u32,
78    pub byte_len: usize,
79}
80
81#[derive(Debug, Clone, Default, PartialEq, Eq)]
82pub struct TlvMetadata {
83    pub slices: Vec<TlvRect>,
84    pub metrics: Option<TlvMetrics>,
85    pub uti: Option<String>,
86    pub exif_orientation: Option<ExifOrientation>,
87    pub bytes_per_row: Option<u32>,
88    pub reference: Option<TlvReference>,
89    pub unknown: Vec<UnknownTlv>,
90}
91
92impl CSIItem {
93    pub fn tlv_metadata(&self) -> TlvMetadata {
94        let mut metadata = TlvMetadata::default();
95
96        for tlv in &self.header().tlv_data {
97            match tlv {
98                RenditionType::Slices(slice) => {
99                    metadata
100                        .slices
101                        .extend(slice.data.iter().map(|slice| TlvRect {
102                            x: slice.x,
103                            y: slice.y,
104                            width: slice.width,
105                            height: slice.height,
106                        }));
107                }
108                RenditionType::Metrics(metrics) => {
109                    metadata.metrics = Some(TlvMetrics {
110                        top_right_inset: TlvMetric {
111                            width: metrics.top_right_inset.width,
112                            height: metrics.top_right_inset.height,
113                        },
114                        bottom_left_inset: TlvMetric {
115                            width: metrics.bottom_left_inset.width,
116                            height: metrics.bottom_left_inset.height,
117                        },
118                        image_size: TlvMetric {
119                            width: metrics.image_size.width,
120                            height: metrics.image_size.height,
121                        },
122                    });
123                }
124                RenditionType::UTI(uti) => {
125                    metadata.uti = Some(
126                        String::from_utf8_lossy(&uti.string)
127                            .trim_end_matches('\0')
128                            .to_string(),
129                    );
130                }
131                RenditionType::EXIFOrientation(orientation) => {
132                    metadata.exif_orientation =
133                        Some(ExifOrientation::from_raw(&orientation.orientation));
134                }
135                RenditionType::BytesPerRow { bytes_per_row, .. } => {
136                    metadata.bytes_per_row = Some(*bytes_per_row);
137                }
138                RenditionType::Reference(reference) => {
139                    metadata.reference = Some(TlvReference {
140                        x: reference.x,
141                        y: reference.y,
142                        width: reference.width,
143                        height: reference.height,
144                        layout: reference.layout,
145                        key_byte_len: reference.keys.len(),
146                    });
147                }
148                RenditionType::Unknown { tag, data } => {
149                    metadata.unknown.push(UnknownTlv {
150                        tag: *tag,
151                        byte_len: data.data.len(),
152                    });
153                }
154                RenditionType::BlendModeAndOpacity(_) => {}
155            }
156        }
157
158        metadata
159    }
160
161    pub fn exif_orientation(&self) -> Option<ExifOrientation> {
162        self.header().tlv_data.iter().find_map(|tlv| match tlv {
163            RenditionType::EXIFOrientation(orientation) => {
164                Some(ExifOrientation::from_raw(&orientation.orientation))
165            }
166            _ => None,
167        })
168    }
169}
170
171#[cfg(test)]
172mod tests {
173    use super::ExifOrientation;
174    use crate::model::rendition::EXIFOrientationValue;
175
176    #[test]
177    fn exif_orientation_from_raw_maps_standard_exif_5_to_8() {
178        assert_eq!(
179            ExifOrientation::from_raw(&EXIFOrientationValue::Rotated90),
180            ExifOrientation::Rotated90Mirrored
181        );
182        assert_eq!(
183            ExifOrientation::from_raw(&EXIFOrientationValue::Rotated90Mirrored),
184            ExifOrientation::Rotated90
185        );
186        assert_eq!(
187            ExifOrientation::from_raw(&EXIFOrientationValue::Rotated270),
188            ExifOrientation::Rotated270Mirrored
189        );
190        assert_eq!(
191            ExifOrientation::from_raw(&EXIFOrientationValue::Rotated270Mirrored),
192            ExifOrientation::Rotated270
193        );
194    }
195}