use super::error::{JpegError, Result};
#[derive(Debug, Clone)]
pub struct Component {
pub id: u8,
pub h_sampling: u8,
pub v_sampling: u8,
pub quant_table_id: u8,
}
#[derive(Debug, Clone)]
pub struct FrameInfo {
pub precision: u8,
pub height: u16,
pub width: u16,
pub components: Vec<Component>,
pub max_h_sampling: u8,
pub max_v_sampling: u8,
pub mcu_width: u16,
pub mcu_height: u16,
pub mcus_wide: u16,
pub mcus_tall: u16,
pub is_progressive: bool,
}
impl FrameInfo {
pub fn blocks_wide(&self, comp_idx: usize) -> usize {
let comp = &self.components[comp_idx];
(self.mcus_wide as usize) * (comp.h_sampling as usize)
}
pub fn blocks_tall(&self, comp_idx: usize) -> usize {
let comp = &self.components[comp_idx];
(self.mcus_tall as usize) * (comp.v_sampling as usize)
}
}
pub fn parse_sof(data: &[u8]) -> Result<FrameInfo> {
parse_sof_ext(data, false)
}
pub fn parse_sof_ext(data: &[u8], progressive: bool) -> Result<FrameInfo> {
if data.len() < 6 {
return Err(JpegError::UnexpectedEof);
}
let precision = data[0];
if precision != 8 {
return Err(JpegError::UnsupportedPrecision(precision));
}
let height = u16::from_be_bytes([data[1], data[2]]);
let width = u16::from_be_bytes([data[3], data[4]]);
let num_components = data[5] as usize;
if width == 0 || height == 0 {
return Err(JpegError::InvalidDimensions);
}
if data.len() < 6 + num_components * 3 {
return Err(JpegError::UnexpectedEof);
}
let mut components = Vec::with_capacity(num_components);
let mut max_h = 0u8;
let mut max_v = 0u8;
for i in 0..num_components {
let offset = 6 + i * 3;
let id = data[offset];
let sampling = data[offset + 1];
let h_sampling = sampling >> 4;
let v_sampling = sampling & 0x0F;
let quant_table_id = data[offset + 2];
if h_sampling == 0 || v_sampling == 0 || h_sampling > 4 || v_sampling > 4 {
return Err(JpegError::InvalidDimensions);
}
if quant_table_id > 3 {
return Err(JpegError::InvalidQuantTableId(quant_table_id));
}
max_h = max_h.max(h_sampling);
max_v = max_v.max(v_sampling);
components.push(Component {
id,
h_sampling,
v_sampling,
quant_table_id,
});
}
let mcu_width = (max_h as u16) * 8;
let mcu_height = (max_v as u16) * 8;
let mcus_wide = width.div_ceil(mcu_width);
let mcus_tall = height.div_ceil(mcu_height);
Ok(FrameInfo {
precision,
height,
width,
components,
max_h_sampling: max_h,
max_v_sampling: max_v,
mcu_width,
mcu_height,
mcus_wide,
mcus_tall,
is_progressive: progressive,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_ycbcr_420() {
let data = [
8, 1, 0xE0, 2, 0x80, 3, 1, 0x22, 0, 2, 0x11, 1, 3, 0x11, 1, ];
let fi = parse_sof(&data).unwrap();
assert_eq!(fi.precision, 8);
assert_eq!(fi.height, 480);
assert_eq!(fi.width, 640);
assert_eq!(fi.components.len(), 3);
assert_eq!(fi.max_h_sampling, 2);
assert_eq!(fi.max_v_sampling, 2);
assert_eq!(fi.mcu_width, 16);
assert_eq!(fi.mcu_height, 16);
assert_eq!(fi.mcus_wide, 40); assert_eq!(fi.mcus_tall, 30);
assert_eq!(fi.blocks_wide(0), 80);
assert_eq!(fi.blocks_tall(0), 60);
assert_eq!(fi.blocks_wide(1), 40);
assert_eq!(fi.blocks_tall(1), 30);
}
#[test]
fn parse_grayscale() {
let data = [
8, 0, 64, 0, 64, 1, 1, 0x11, 0, ];
let fi = parse_sof(&data).unwrap();
assert_eq!(fi.components.len(), 1);
assert_eq!(fi.mcus_wide, 8); assert_eq!(fi.mcus_tall, 8);
}
#[test]
fn parse_non_mcu_aligned() {
let data = [
8, 0, 10, 0, 10, 1,
1, 0x11, 0,
];
let fi = parse_sof(&data).unwrap();
assert_eq!(fi.mcus_wide, 2); assert_eq!(fi.mcus_tall, 2);
}
#[test]
fn reject_12bit() {
let data = [12, 0, 8, 0, 8, 1, 1, 0x11, 0];
assert!(matches!(
parse_sof(&data),
Err(JpegError::UnsupportedPrecision(12))
));
}
}