1use super::error::{JpegError, Result};
11
12#[derive(Debug, Clone)]
14pub struct Component {
15 pub id: u8,
17 pub h_sampling: u8,
19 pub v_sampling: u8,
21 pub quant_table_id: u8,
23}
24
25#[derive(Debug, Clone)]
27pub struct FrameInfo {
28 pub precision: u8,
30 pub height: u16,
32 pub width: u16,
34 pub components: Vec<Component>,
36 pub max_h_sampling: u8,
38 pub max_v_sampling: u8,
40 pub mcu_width: u16,
42 pub mcu_height: u16,
44 pub mcus_wide: u16,
46 pub mcus_tall: u16,
48 pub is_progressive: bool,
50}
51
52impl FrameInfo {
53 pub fn blocks_wide(&self, comp_idx: usize) -> usize {
55 let comp = &self.components[comp_idx];
56 (self.mcus_wide as usize) * (comp.h_sampling as usize)
57 }
58
59 pub fn blocks_tall(&self, comp_idx: usize) -> usize {
61 let comp = &self.components[comp_idx];
62 (self.mcus_tall as usize) * (comp.v_sampling as usize)
63 }
64}
65
66pub fn parse_sof(data: &[u8]) -> Result<FrameInfo> {
69 parse_sof_ext(data, false)
70}
71
72pub fn parse_sof_ext(data: &[u8], progressive: bool) -> Result<FrameInfo> {
74 if data.len() < 6 {
75 return Err(JpegError::UnexpectedEof);
76 }
77
78 let precision = data[0];
79 if precision != 8 {
80 return Err(JpegError::UnsupportedPrecision(precision));
81 }
82
83 let height = u16::from_be_bytes([data[1], data[2]]);
84 let width = u16::from_be_bytes([data[3], data[4]]);
85 let num_components = data[5] as usize;
86
87 if width == 0 || height == 0 {
88 return Err(JpegError::InvalidDimensions);
89 }
90 if data.len() < 6 + num_components * 3 {
91 return Err(JpegError::UnexpectedEof);
92 }
93
94 let mut components = Vec::with_capacity(num_components);
95 let mut max_h = 0u8;
96 let mut max_v = 0u8;
97
98 for i in 0..num_components {
99 let offset = 6 + i * 3;
100 let id = data[offset];
101 let sampling = data[offset + 1];
102 let h_sampling = sampling >> 4;
103 let v_sampling = sampling & 0x0F;
104 let quant_table_id = data[offset + 2];
105
106 if h_sampling == 0 || v_sampling == 0 || h_sampling > 4 || v_sampling > 4 {
107 return Err(JpegError::InvalidDimensions);
108 }
109 if quant_table_id > 3 {
110 return Err(JpegError::InvalidQuantTableId(quant_table_id));
111 }
112
113 max_h = max_h.max(h_sampling);
114 max_v = max_v.max(v_sampling);
115
116 components.push(Component {
117 id,
118 h_sampling,
119 v_sampling,
120 quant_table_id,
121 });
122 }
123
124 let mcu_width = (max_h as u16) * 8;
125 let mcu_height = (max_v as u16) * 8;
126 let mcus_wide = width.div_ceil(mcu_width);
127 let mcus_tall = height.div_ceil(mcu_height);
128
129 Ok(FrameInfo {
130 precision,
131 height,
132 width,
133 components,
134 max_h_sampling: max_h,
135 max_v_sampling: max_v,
136 mcu_width,
137 mcu_height,
138 mcus_wide,
139 mcus_tall,
140 is_progressive: progressive,
141 })
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147
148 #[test]
149 fn parse_ycbcr_420() {
150 let data = [
155 8, 1, 0xE0, 2, 0x80, 3, 1, 0x22, 0, 2, 0x11, 1, 3, 0x11, 1, ];
160
161 let fi = parse_sof(&data).unwrap();
162 assert_eq!(fi.precision, 8);
163 assert_eq!(fi.height, 480);
164 assert_eq!(fi.width, 640);
165 assert_eq!(fi.components.len(), 3);
166 assert_eq!(fi.max_h_sampling, 2);
167 assert_eq!(fi.max_v_sampling, 2);
168 assert_eq!(fi.mcu_width, 16);
169 assert_eq!(fi.mcu_height, 16);
170 assert_eq!(fi.mcus_wide, 40); assert_eq!(fi.mcus_tall, 30); assert_eq!(fi.blocks_wide(0), 80);
175 assert_eq!(fi.blocks_tall(0), 60);
176 assert_eq!(fi.blocks_wide(1), 40);
178 assert_eq!(fi.blocks_tall(1), 30);
179 }
180
181 #[test]
182 fn parse_grayscale() {
183 let data = [
184 8, 0, 64, 0, 64, 1, 1, 0x11, 0, ];
187 let fi = parse_sof(&data).unwrap();
188 assert_eq!(fi.components.len(), 1);
189 assert_eq!(fi.mcus_wide, 8); assert_eq!(fi.mcus_tall, 8);
191 }
192
193 #[test]
194 fn parse_non_mcu_aligned() {
195 let data = [
197 8, 0, 10, 0, 10, 1,
198 1, 0x11, 0,
199 ];
200 let fi = parse_sof(&data).unwrap();
201 assert_eq!(fi.mcus_wide, 2); assert_eq!(fi.mcus_tall, 2);
203 }
204
205 #[test]
206 fn reject_12bit() {
207 let data = [12, 0, 8, 0, 8, 1, 1, 0x11, 0];
208 assert!(matches!(
209 parse_sof(&data),
210 Err(JpegError::UnsupportedPrecision(12))
211 ));
212 }
213}