use super::error::{JpegError, Result};
pub const SOI: u8 = 0xD8;
pub const EOI: u8 = 0xD9;
pub const SOF0: u8 = 0xC0;
pub const SOF2: u8 = 0xC2;
pub const DHT: u8 = 0xC4;
pub const DQT: u8 = 0xDB;
pub const DRI: u8 = 0xDD;
pub const SOS: u8 = 0xDA;
pub const COM: u8 = 0xFE;
#[derive(Debug, Clone)]
pub struct MarkerSegment {
pub marker: u8,
pub data: Vec<u8>,
}
pub struct MarkerEntry {
pub marker: u8,
pub data: Vec<u8>,
pub offset: usize,
}
pub fn iterate_markers(data: &[u8]) -> Result<(Vec<MarkerEntry>, usize)> {
let mut entries = Vec::new();
if data.len() < 2 || data[0] != 0xFF || data[1] != SOI {
return Err(JpegError::InvalidSoi);
}
entries.push(MarkerEntry {
marker: SOI,
data: Vec::new(),
offset: 0,
});
let mut pos = 2;
loop {
while pos < data.len() && data[pos] != 0xFF {
pos += 1;
}
if pos + 1 >= data.len() {
return Err(JpegError::UnexpectedEof);
}
while pos + 1 < data.len() && data[pos + 1] == 0xFF {
pos += 1;
}
if pos + 1 >= data.len() {
return Err(JpegError::UnexpectedEof);
}
let marker_offset = pos;
let marker = data[pos + 1];
pos += 2;
if marker == 0x00 {
continue;
}
if marker == EOI || (0xD0..=0xD7).contains(&marker) {
entries.push(MarkerEntry {
marker,
data: Vec::new(),
offset: marker_offset,
});
if marker == EOI {
return Ok((entries, pos));
}
continue;
}
if is_unsupported(marker) {
return Err(JpegError::UnsupportedMarker(marker));
}
if pos + 2 > data.len() {
return Err(JpegError::UnexpectedEof);
}
let length = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
if length < 2 || pos + length > data.len() {
return Err(JpegError::InvalidMarkerData("invalid segment length"));
}
let segment_data = data[pos + 2..pos + length].to_vec();
entries.push(MarkerEntry {
marker,
data: segment_data,
offset: marker_offset,
});
pos += length;
if marker == SOS {
return Ok((entries, pos));
}
}
}
fn is_unsupported(marker: u8) -> bool {
matches!(
marker,
0xC1 | 0xC3 | 0xC5..=0xC7 | 0xC9..=0xCB | 0xCD..=0xCF )
}
#[derive(Debug, Clone, Copy)]
pub struct SosParams {
pub ss: u8,
pub se: u8,
pub ah: u8,
pub al: u8,
}
pub fn parse_sos(data: &[u8]) -> Result<Vec<(u8, u8, u8)>> {
if data.is_empty() {
return Err(JpegError::InvalidMarkerData("empty SOS"));
}
let num_components = data[0] as usize;
if data.len() < 1 + num_components * 2 + 3 {
return Err(JpegError::UnexpectedEof);
}
let mut selectors = Vec::with_capacity(num_components);
for i in 0..num_components {
let offset = 1 + i * 2;
let comp_id = data[offset];
let td_ta = data[offset + 1];
let dc_id = td_ta >> 4;
let ac_id = td_ta & 0x0F;
selectors.push((comp_id, dc_id, ac_id));
}
Ok(selectors)
}
pub fn parse_sos_params(data: &[u8]) -> Result<SosParams> {
if data.is_empty() {
return Err(JpegError::InvalidMarkerData("empty SOS"));
}
let num_components = data[0] as usize;
let params_offset = 1 + num_components * 2;
if data.len() < params_offset + 3 {
return Err(JpegError::UnexpectedEof);
}
let ss = data[params_offset];
let se = data[params_offset + 1];
let ah_al = data[params_offset + 2];
let ah = ah_al >> 4;
let al = ah_al & 0x0F;
Ok(SosParams { ss, se, ah, al })
}
pub fn skip_scan_data(data: &[u8], mut pos: usize) -> Result<usize> {
while pos < data.len() {
if data[pos] != 0xFF {
pos += 1;
continue;
}
if pos + 1 >= data.len() {
return Err(JpegError::UnexpectedEof);
}
let next = data[pos + 1];
if next == 0x00 {
pos += 2;
continue;
}
if (0xD0..=0xD7).contains(&next) {
pos += 2;
continue;
}
if next == 0xFF {
pos += 1;
continue;
}
return Ok(pos);
}
Err(JpegError::UnexpectedEof)
}
pub fn iterate_markers_all(data: &[u8]) -> Result<(Vec<MarkerEntry>, Vec<usize>)> {
let mut entries = Vec::new();
let mut scan_starts = Vec::new();
if data.len() < 2 || data[0] != 0xFF || data[1] != SOI {
return Err(JpegError::InvalidSoi);
}
entries.push(MarkerEntry {
marker: SOI,
data: Vec::new(),
offset: 0,
});
let mut pos = 2;
loop {
while pos < data.len() && data[pos] != 0xFF {
pos += 1;
}
if pos + 1 >= data.len() {
return Err(JpegError::UnexpectedEof);
}
while pos + 1 < data.len() && data[pos + 1] == 0xFF {
pos += 1;
}
if pos + 1 >= data.len() {
return Err(JpegError::UnexpectedEof);
}
let marker_offset = pos;
let marker = data[pos + 1];
pos += 2;
if marker == 0x00 {
continue;
}
if marker == EOI || (0xD0..=0xD7).contains(&marker) {
entries.push(MarkerEntry {
marker,
data: Vec::new(),
offset: marker_offset,
});
if marker == EOI {
return Ok((entries, scan_starts));
}
continue;
}
if is_unsupported(marker) {
return Err(JpegError::UnsupportedMarker(marker));
}
if pos + 2 > data.len() {
return Err(JpegError::UnexpectedEof);
}
let length = u16::from_be_bytes([data[pos], data[pos + 1]]) as usize;
if length < 2 || pos + length > data.len() {
return Err(JpegError::InvalidMarkerData("invalid segment length"));
}
let segment_data = data[pos + 2..pos + length].to_vec();
entries.push(MarkerEntry {
marker,
data: segment_data,
offset: marker_offset,
});
pos += length;
if marker == SOS {
scan_starts.push(pos);
pos = skip_scan_data(data, pos)?;
}
}
}
pub fn parse_dri(data: &[u8]) -> Result<u16> {
if data.len() < 2 {
return Err(JpegError::UnexpectedEof);
}
Ok(u16::from_be_bytes([data[0], data[1]]))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn iterate_minimal_jpeg() {
let data = [0xFF, 0xD8, 0xFF, 0xD9];
let (entries, end_pos) = iterate_markers(&data).unwrap();
assert_eq!(entries.len(), 2);
assert_eq!(entries[0].marker, SOI);
assert_eq!(entries[1].marker, EOI);
assert_eq!(end_pos, 4);
}
#[test]
fn invalid_soi() {
let data = [0x00, 0x00];
assert!(matches!(iterate_markers(&data), Err(JpegError::InvalidSoi)));
}
#[test]
fn accept_progressive_sof2() {
let data = [
0xFF, 0xD8, 0xFF, 0xC2, 0x00, 0x0B, 8, 0, 8, 0, 8, 1, 1, 0x11, 0, 0xFF, 0xD9, ];
let (entries, _) = iterate_markers(&data).unwrap();
assert!(entries.iter().any(|e| e.marker == SOF2));
}
#[test]
fn reject_lossless() {
let data = [
0xFF, 0xD8, 0xFF, 0xC3, 0x00, 0x02, ];
assert!(matches!(
iterate_markers(&data),
Err(JpegError::UnsupportedMarker(0xC3))
));
}
#[test]
fn parse_sos_header() {
let data = [2, 1, 0x00, 2, 0x11, 0, 63, 0]; let sels = parse_sos(&data).unwrap();
assert_eq!(sels.len(), 2);
assert_eq!(sels[0], (1, 0, 0));
assert_eq!(sels[1], (2, 1, 1));
}
#[test]
fn parse_dri_value() {
let data = [0x00, 0x0A]; assert_eq!(parse_dri(&data).unwrap(), 10);
}
}