use super::markers::{CDT, CWD, EOC, NLT, PIH, SLH, SOC, WGT};
use super::{JxsError, JxsResult};
fn push_u16(buf: &mut Vec<u8>, v: u16) {
buf.extend_from_slice(&v.to_be_bytes());
}
fn push_u24(buf: &mut Vec<u8>, v: u32) -> JxsResult<()> {
if v > 0x00FF_FFFF {
return Err(JxsError::InvalidHeader(format!(
"u24 field overflow: {v} > 0xFFFFFF"
)));
}
buf.push(((v >> 16) & 0xFF) as u8);
buf.push(((v >> 8) & 0xFF) as u8);
buf.push((v & 0xFF) as u8);
Ok(())
}
pub fn write_soc(buf: &mut Vec<u8>) {
push_u16(buf, SOC);
}
pub fn write_eoc(buf: &mut Vec<u8>) {
push_u16(buf, EOC);
}
#[derive(Debug, Clone, Copy)]
pub struct PihFields {
pub codestream_len: u32,
pub profile: u16,
pub level: u16,
pub width: u16,
pub height: u16,
pub codegroup_width: u16,
pub slice_height: u16,
pub num_components: u8,
pub ganging: u8,
pub bit_depth: u8,
pub bw_ext: u8,
pub fq: u8,
pub bitrate: u32,
pub fsl: u8,
pub ppoc: u8,
pub cpih: u8,
}
pub fn write_pih(buf: &mut Vec<u8>, f: &PihFields) -> JxsResult<()> {
if f.num_components == 0 {
return Err(JxsError::InvalidHeader(
"PIH Nc=0: at least one component required".to_string(),
));
}
if f.bit_depth == 0 || f.bit_depth > 16 {
return Err(JxsError::InvalidHeader(format!(
"PIH Ss={}: bit depth must be 1-16",
f.bit_depth
)));
}
if f.width == 0 || f.height == 0 {
return Err(JxsError::InvalidHeader(format!(
"PIH frame size {}x{}: dimensions must be non-zero",
f.width, f.height
)));
}
push_u16(buf, PIH);
const PIH_PAYLOAD_LEN: u16 = 26;
push_u16(buf, PIH_PAYLOAD_LEN + 2);
push_u24(buf, f.codestream_len)?; push_u16(buf, f.profile); push_u16(buf, f.level); push_u16(buf, f.width); push_u16(buf, f.height); push_u16(buf, f.codegroup_width); push_u16(buf, f.slice_height); buf.push(f.num_components); buf.push(f.ganging); buf.push(f.bit_depth); buf.push(f.bw_ext); buf.push(f.fq); push_u24(buf, f.bitrate)?; buf.push(f.fsl); buf.push(f.ppoc); buf.push(f.cpih); Ok(())
}
#[derive(Debug, Clone, Copy)]
pub struct CdtComponent {
pub bit_depth: u8,
pub sx: u8,
pub sy: u8,
}
pub fn write_cdt(buf: &mut Vec<u8>, components: &[CdtComponent]) -> JxsResult<()> {
if components.is_empty() {
return Err(JxsError::InvalidHeader(
"CDT requires at least one component".to_string(),
));
}
push_u16(buf, CDT);
let payload_len = components.len() * 3;
push_u16(buf, (payload_len + 2) as u16);
for c in components {
buf.push(c.bit_depth);
buf.push(c.sx);
buf.push(c.sy);
}
Ok(())
}
pub fn write_wgt(buf: &mut Vec<u8>, weights: &[u16]) -> JxsResult<()> {
if weights.is_empty() {
return Err(JxsError::InvalidHeader(
"WGT requires at least one weight".to_string(),
));
}
push_u16(buf, WGT);
let payload_len = weights.len() * 2;
push_u16(buf, (payload_len + 2) as u16);
for &w in weights {
push_u16(buf, w);
}
Ok(())
}
pub fn write_nlt(buf: &mut Vec<u8>, tnlt: u8, t1: u16, t2: u16) {
push_u16(buf, NLT);
push_u16(buf, 7); buf.push(tnlt);
push_u16(buf, t1);
push_u16(buf, t2);
}
pub fn write_cwd(buf: &mut Vec<u8>, payload: &[u8]) {
push_u16(buf, CWD);
push_u16(buf, (payload.len() + 2) as u16);
buf.extend_from_slice(payload);
}
pub fn write_slh(buf: &mut Vec<u8>, qp: u16) {
push_u16(buf, SLH);
push_u16(buf, 4); push_u16(buf, qp);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::jpegxs::markers::{parse_headers, PROFILE_MAIN};
fn default_pih(width: u16, height: u16, nc: u8, bit_depth: u8) -> PihFields {
PihFields {
codestream_len: 0,
profile: PROFILE_MAIN,
level: 0,
width,
height,
codegroup_width: width,
slice_height: height,
num_components: nc,
ganging: 0,
bit_depth,
bw_ext: 0,
fq: 0,
bitrate: 0,
fsl: 0,
ppoc: 0,
cpih: 0,
}
}
#[test]
fn pih_write_then_parse_roundtrip() {
let mut buf = Vec::new();
write_soc(&mut buf);
write_pih(&mut buf, &default_pih(640, 480, 3, 8)).unwrap();
write_cdt(
&mut buf,
&[
CdtComponent {
bit_depth: 8,
sx: 1,
sy: 1,
},
CdtComponent {
bit_depth: 8,
sx: 1,
sy: 1,
},
CdtComponent {
bit_depth: 8,
sx: 1,
sy: 1,
},
],
)
.unwrap();
write_eoc(&mut buf);
let (headers, _) = parse_headers(&buf).expect("parse");
assert_eq!(headers.pih.width, 640);
assert_eq!(headers.pih.height, 480);
assert_eq!(headers.pih.slice_height, 480);
assert_eq!(headers.pih.num_components, 3);
assert_eq!(headers.pih.bit_depth, 8);
assert_eq!(headers.pih.profile, PROFILE_MAIN);
assert_eq!(headers.components.len(), 3);
}
#[test]
fn pih_16x16_yuv_fields_match() {
let mut buf = Vec::new();
write_soc(&mut buf);
write_pih(&mut buf, &default_pih(16, 16, 3, 8)).unwrap();
write_cdt(
&mut buf,
&[CdtComponent {
bit_depth: 8,
sx: 1,
sy: 1,
}; 3],
)
.unwrap();
write_eoc(&mut buf);
let (h, _) = parse_headers(&buf).unwrap();
assert_eq!(h.pih.width, 16);
assert_eq!(h.pih.height, 16);
assert_eq!(h.pih.num_components, 3);
assert_eq!(h.pih.bit_depth, 8);
}
#[test]
fn wgt_write_then_parse() {
let mut buf = Vec::new();
write_soc(&mut buf);
write_pih(&mut buf, &default_pih(8, 8, 1, 8)).unwrap();
write_cdt(
&mut buf,
&[CdtComponent {
bit_depth: 8,
sx: 1,
sy: 1,
}],
)
.unwrap();
write_wgt(&mut buf, &[256, 256, 256, 256]).unwrap();
write_eoc(&mut buf);
let (h, _) = parse_headers(&buf).unwrap();
assert_eq!(h.weights, vec![256, 256, 256, 256]);
}
#[test]
fn nlt_write_then_parse_payload() {
use crate::jpegxs::nlt::{parse_nlt_payload, NltType};
let mut buf = Vec::new();
write_soc(&mut buf);
write_pih(&mut buf, &default_pih(8, 8, 1, 8)).unwrap();
write_cdt(
&mut buf,
&[CdtComponent {
bit_depth: 8,
sx: 1,
sy: 1,
}],
)
.unwrap();
write_nlt(&mut buf, 0, 64, 192);
write_eoc(&mut buf);
let (h, _) = parse_headers(&buf).unwrap();
assert!(h.has_nlt);
let payload = h.nlt_payload.expect("nlt payload");
let params = parse_nlt_payload(&payload).unwrap();
assert_eq!(params.nlt_type, NltType::Quadratic);
assert_eq!(params.t1, 64);
assert_eq!(params.t2, 192);
}
#[test]
fn pih_rejects_zero_components() {
let mut buf = Vec::new();
let r = write_pih(&mut buf, &default_pih(8, 8, 0, 8));
assert!(r.is_err());
}
#[test]
fn pih_rejects_bad_bit_depth() {
let mut buf = Vec::new();
let r = write_pih(&mut buf, &default_pih(8, 8, 1, 17));
assert!(r.is_err());
}
#[test]
fn cdt_rejects_empty() {
let mut buf = Vec::new();
assert!(write_cdt(&mut buf, &[]).is_err());
}
#[test]
fn wgt_rejects_empty() {
let mut buf = Vec::new();
assert!(write_wgt(&mut buf, &[]).is_err());
}
#[test]
fn slh_writes_qp_field() {
let mut buf = Vec::new();
write_slh(&mut buf, 0x1234);
assert_eq!(buf, vec![0xFF, 0x19, 0x00, 0x04, 0x12, 0x34]);
}
#[test]
fn cwd_marker_is_skipped_by_parser() {
let mut buf = Vec::new();
write_soc(&mut buf);
write_pih(&mut buf, &default_pih(8, 8, 1, 8)).unwrap();
write_cdt(
&mut buf,
&[CdtComponent {
bit_depth: 8,
sx: 1,
sy: 1,
}],
)
.unwrap();
write_cwd(&mut buf, &[0xDE, 0xAD, 0xBE, 0xEF]);
write_eoc(&mut buf);
let (h, _) = parse_headers(&buf).unwrap();
assert_eq!(h.pih.width, 8);
}
#[test]
fn u24_overflow_rejected() {
let mut buf = Vec::new();
assert!(push_u24(&mut buf, 0x0100_0000).is_err());
}
}