#[cfg(feature = "jpegxs")]
mod tests {
use oximedia_codec::jpegxs::decoder::JpegXsDecoder;
use oximedia_codec::jpegxs::markers::{
build_test_codestream, build_test_codestream_with_nlt, parse_headers, EOC, SOC,
};
#[test]
fn soc_marker_detection() {
let data = [0xFF, 0x10, 0x00, 0x00];
assert!(JpegXsDecoder::is_jpegxs(&data));
}
#[test]
fn non_jpegxs_stream_rejected() {
let data = [0xFF, 0xD8, 0xFF, 0xE0]; assert!(!JpegXsDecoder::is_jpegxs(&data));
}
#[test]
fn av1_stream_rejected() {
let data = [0x0A, 0x0B, 0x00, 0x00]; assert!(!JpegXsDecoder::is_jpegxs(&data));
}
#[test]
fn empty_stream_rejected() {
let result = JpegXsDecoder::decode(&[]);
assert!(result.is_err());
}
#[test]
fn truncated_stream_rejected() {
let data = [0xFF, 0x10]; let result = JpegXsDecoder::decode(&data);
assert!(result.is_err());
}
#[test]
fn wrong_magic_rejected() {
let data = [0xDE, 0xAD, 0xBE, 0xEF];
let result = JpegXsDecoder::decode(&data);
assert!(result.is_err());
}
#[test]
fn parse_minimal_640x480_3comp_8bit() {
let data = build_test_codestream(640, 480, 16, 3, 8);
let (headers, _end) = parse_headers(&data).expect("parse_headers");
assert_eq!(headers.pih.width, 640);
assert_eq!(headers.pih.height, 480);
assert_eq!(headers.pih.slice_height, 16);
assert_eq!(headers.pih.num_components, 3);
assert_eq!(headers.pih.bit_depth, 8);
}
#[test]
fn parse_1920x1080_1comp_10bit() {
let data = build_test_codestream(1920, 1080, 32, 1, 10);
let (headers, _) = parse_headers(&data).expect("parse_headers");
assert_eq!(headers.pih.width, 1920);
assert_eq!(headers.pih.height, 1080);
assert_eq!(headers.pih.num_components, 1);
assert_eq!(headers.pih.bit_depth, 10);
}
#[test]
fn parsed_components_match_num_components() {
let data = build_test_codestream(64, 64, 8, 4, 8);
let (headers, _) = parse_headers(&data).expect("parse_headers");
assert_eq!(headers.components.len(), 4);
for comp in &headers.components {
assert_eq!(comp.sx, 1);
assert_eq!(comp.sy, 1);
}
}
#[test]
fn soc_constant_value() {
assert_eq!(SOC, 0xFF10u16);
}
#[test]
fn eoc_constant_value() {
assert_eq!(EOC, 0xFF11u16);
}
#[test]
fn decode_headers_only_returns_zero_image() {
let data = build_test_codestream(8, 8, 8, 1, 8);
let img = JpegXsDecoder::decode(&data).expect("decode");
assert_eq!(img.width, 8);
assert_eq!(img.height, 8);
assert_eq!(img.num_components, 1);
assert_eq!(img.bit_depth, 8);
assert_eq!(img.samples[0].len(), 64);
}
#[test]
fn decode_multicomponent_correct_plane_count() {
let data = build_test_codestream(16, 16, 16, 3, 8);
let img = JpegXsDecoder::decode(&data).expect("decode");
assert_eq!(img.samples.len(), 3);
for plane in &img.samples {
assert_eq!(plane.len(), 256);
}
}
#[test]
fn decoded_sample_values_within_8bit_range() {
let data = build_test_codestream(4, 4, 4, 1, 8);
let img = JpegXsDecoder::decode(&data).expect("decode");
for &s in &img.samples[0] {
assert!(s <= 255, "sample {s} exceeds 8-bit max");
}
}
#[test]
fn decoded_sample_values_within_10bit_range() {
let data = build_test_codestream(4, 4, 4, 1, 10);
let img = JpegXsDecoder::decode(&data).expect("decode");
let max_val = (1u16 << 10) - 1; for &s in &img.samples[0] {
assert!(s <= max_val, "sample {s} exceeds 10-bit max");
}
}
#[test]
fn decode_with_nlt_quadratic_marker_succeeds() {
let data = build_test_codestream_with_nlt(8, 8, 8, 1, 8, 64, 192);
let img = JpegXsDecoder::decode(&data).expect("decode with NLT should succeed");
assert_eq!(img.width, 8);
assert_eq!(img.height, 8);
assert_eq!(img.num_components, 1);
assert_eq!(img.bit_depth, 8);
assert!(img.samples[0].iter().all(|&v| v == 0));
}
#[test]
fn decode_with_nlt_marker_sample_values_in_range() {
let data = build_test_codestream_with_nlt(4, 4, 4, 1, 8, 32, 200);
let img = JpegXsDecoder::decode(&data).expect("decode");
for &s in &img.samples[0] {
assert!(s <= 255, "sample {s} exceeds 8-bit max after NLT reverse");
}
}
#[test]
fn bitreader_basic() {
use oximedia_codec::jpegxs::bitreader::BitReader;
let data = [0b1010_0011u8, 0b1111_0000u8];
let mut r = BitReader::new(&data);
assert_eq!(r.read_bit().unwrap(), 1);
assert_eq!(r.read_bit().unwrap(), 0);
assert_eq!(r.read_bit().unwrap(), 1);
assert_eq!(r.read_bit().unwrap(), 0);
assert_eq!(r.read_bits_u32(4).unwrap(), 0b0011);
}
#[test]
fn bitreader_read_u16_be() {
use oximedia_codec::jpegxs::bitreader::BitReader;
let data = [0x12u8, 0x34u8];
let mut r = BitReader::new(&data);
assert_eq!(r.read_u16_be().unwrap(), 0x1234);
}
#[test]
fn bitreader_truncated_error() {
use oximedia_codec::jpegxs::bitreader::BitReader;
let data = [0xFFu8];
let mut r = BitReader::new(&data);
let _ = r.read_bits_u32(8).unwrap();
assert!(r.read_bit().is_err());
}
#[test]
fn wavelet_53_2d_constant() {
use oximedia_codec::jpegxs::wavelet::{forward_53_1d, inverse_53_2d};
let n = 4usize;
let constant = 64i32;
let n_low_w = (n + 1) / 2;
let n_high_w = n / 2;
let n_low_h = (n + 1) / 2;
let n_high_h = n / 2;
let image = vec![constant; n * n];
let mut row_lows = vec![vec![0i32; n_low_w]; n];
let mut row_highs = vec![vec![0i32; n_high_w]; n];
for row in 0..n {
let (l, h) = forward_53_1d(&image[row * n..(row + 1) * n]);
row_lows[row] = l;
row_highs[row] = h;
}
let mut ll = vec![0i32; n_low_h * n_low_w];
let mut hl = vec![0i32; n_low_h * n_high_w];
for col in 0..n_low_w {
let col_vals: Vec<i32> = (0..n).map(|r| row_lows[r][col]).collect();
let (l, _) = forward_53_1d(&col_vals);
for (r, &v) in l.iter().enumerate() {
ll[r * n_low_w + col] = v;
}
}
for col in 0..n_high_w {
let col_vals: Vec<i32> = (0..n).map(|r| row_highs[r][col]).collect();
let (l, _) = forward_53_1d(&col_vals);
for (r, &v) in l.iter().enumerate() {
hl[r * n_high_w + col] = v;
}
}
let lh = vec![0i32; n_high_h * n_low_w];
let hh = vec![0i32; n_high_h * n_high_w];
let result = inverse_53_2d(&ll, &hl, &lh, &hh, n, n).unwrap();
assert_eq!(result.len(), n * n);
for &v in &result {
assert_eq!(v, constant, "expected all samples to be {constant}");
}
}
#[test]
fn nlt_none_is_identity() {
use oximedia_codec::jpegxs::nlt::{apply_nlt_reverse, NltParams};
let mut s = vec![10i32, 20, 30];
apply_nlt_reverse(&mut s, &NltParams::none(), 8).unwrap();
assert_eq!(s, [10, 20, 30]);
}
#[test]
fn nlt_quadratic_low_region_identity() {
use oximedia_codec::jpegxs::nlt::{apply_nlt_reverse, NltParams};
let mut s = vec![50i32];
apply_nlt_reverse(&mut s, &NltParams::quadratic(64, 192), 8).unwrap();
assert_eq!(s[0], 50);
}
#[test]
fn nlt_quadratic_succeeds_for_valid_params() {
use oximedia_codec::jpegxs::nlt::{apply_nlt_reverse, NltParams};
let mut s = vec![100i32, 150, 200];
let result = apply_nlt_reverse(&mut s, &NltParams::quadratic(64, 192), 8);
assert!(result.is_ok(), "expected Ok, got {result:?}");
}
#[test]
fn nlt_quadratic_invalid_params_returns_error() {
use oximedia_codec::jpegxs::nlt::{apply_nlt_reverse, NltParams};
let mut s = vec![100i32];
let result = apply_nlt_reverse(&mut s, &NltParams::quadratic(128, 128), 8);
assert!(result.is_err());
}
#[test]
fn vlc_run_table_run0_is_one_zero_bit() {
use oximedia_codec::jpegxs::vlc::default_run_table;
let table = default_run_table();
let result = table.lookup(0x0000_0000u32);
assert!(result.is_some());
let r = result.unwrap();
assert_eq!(r.value, 0);
assert_eq!(r.bits_consumed, 1);
}
#[test]
fn vlc_magnitude_table_level1_is_one_zero_bit() {
use oximedia_codec::jpegxs::vlc::default_magnitude_table;
let table = default_magnitude_table();
let result = table.lookup(0x0000_0000u32);
assert!(result.is_some());
let r = result.unwrap();
assert_eq!(r.value, 1);
assert_eq!(r.bits_consumed, 1);
}
#[test]
fn vlc_significance_table_codes_zero_and_one() {
use oximedia_codec::jpegxs::vlc::default_significance_table;
let table = default_significance_table();
let r0 = table.lookup(0x0000_0000u32).unwrap();
assert_eq!(r0.value, 0);
let r1 = table.lookup(0x8000_0000u32).unwrap();
assert_eq!(r1.value, 1);
}
}