use justjp2::tcd::*;
use justjp2::t1::Orient;
fn max_abs_error(a: &[i32], b: &[i32]) -> i32 {
a.iter()
.zip(b.iter())
.map(|(x, y)| (x - y).abs())
.max()
.unwrap_or(0)
}
fn compute_psnr(original: &[i32], reconstructed: &[i32], precision: u32) -> f64 {
let max_val = (1u32 << precision) - 1;
let n = original.len();
let mut mse = 0.0f64;
for i in 0..n {
let diff = (reconstructed[i] - original[i]) as f64;
mse += diff * diff;
}
mse /= n as f64;
if mse == 0.0 {
f64::INFINITY
} else {
10.0 * ((max_val as f64 * max_val as f64) / mse).log10()
}
}
#[test]
fn tile_grid_single_tile() {
let td = TileData {
components: vec![vec![0i32; 64 * 64]],
width: 64,
height: 64,
};
assert_eq!(td.width, 64);
assert_eq!(td.height, 64);
assert_eq!(td.components.len(), 1);
assert_eq!(td.components[0].len(), 64 * 64);
}
#[test]
fn tile_grid_multi_tile() {
let td = TileData {
components: vec![
vec![0i32; 32 * 32],
vec![0i32; 32 * 32],
vec![0i32; 32 * 32],
],
width: 32,
height: 32,
};
assert_eq!(td.components.len(), 3);
for c in &td.components {
assert_eq!(c.len(), 32 * 32);
}
}
#[test]
fn resolution_dimensions() {
let (w0, h0) = resolution_size(64, 64, 0);
assert_eq!((w0, h0), (64, 64));
let (w1, h1) = resolution_size(64, 64, 1);
assert_eq!((w1, h1), (32, 32));
let (w2, h2) = resolution_size(64, 64, 2);
assert_eq!((w2, h2), (16, 16));
let (w3, h3) = resolution_size(64, 64, 3);
assert_eq!((w3, h3), (8, 8));
let (w, h) = resolution_size(63, 33, 1);
assert_eq!((w, h), (32, 17));
let (w, h) = resolution_size(63, 33, 2);
assert_eq!((w, h), (16, 9));
}
#[test]
fn band_dimensions_test() {
let (w, h) = band_dimensions(64, 64, 2, 0, Orient::LL);
assert_eq!((w, h), (16, 16));
let (w, h) = band_dimensions(64, 64, 2, 2, Orient::HL);
assert_eq!((w, h), (16, 16));
let (w, h) = band_dimensions(64, 64, 2, 2, Orient::LH);
assert_eq!((w, h), (16, 16));
let (w, h) = band_dimensions(64, 64, 2, 2, Orient::HH);
assert_eq!((w, h), (16, 16));
let (w, h) = band_dimensions(64, 64, 2, 1, Orient::HL);
assert_eq!((w, h), (32, 32));
}
#[test]
fn codeblock_count_test() {
assert_eq!(codeblock_count(64, 64, 64, 64), 1);
assert_eq!(codeblock_count(64, 64, 32, 32), 4);
assert_eq!(codeblock_count(33, 33, 32, 32), 4);
assert_eq!(codeblock_count(0, 64, 32, 32), 0);
assert_eq!(codeblock_count(128, 128, 64, 64), 4);
}
#[test]
fn encode_decode_gray_tile() {
let w = 32u32;
let h = 32u32;
let n = (w * h) as usize;
let mut samples = vec![0i32; n];
for y in 0..h {
for x in 0..w {
samples[(y * w + x) as usize] = ((x + y * 3) % 256) as i32;
}
}
let tile = TileData {
components: vec![samples.clone()],
width: w,
height: h,
};
let components = vec![TcdComponent {
width: w,
height: h,
precision: 8,
signed: false,
dx: 1,
dy: 1,
}];
let params = TcdParams {
num_res: 3, cblk_w: 32,
cblk_h: 32,
reversible: true,
num_layers: 1,
use_mct: false,
reduce: 0,
max_bytes: None,
};
let encoded = encode_tile(&tile, &components, ¶ms).unwrap();
assert!(!encoded.data.is_empty());
let decoded = decode_tile(&encoded, &components, ¶ms, w, h).unwrap();
assert_eq!(decoded.components.len(), 1);
assert_eq!(decoded.components[0].len(), n);
let max_err = max_abs_error(&decoded.components[0], &samples);
assert!(
max_err <= 1,
"reversible gray roundtrip max error should be <= 1, got {}",
max_err
);
}
#[test]
fn encode_decode_rgb_tile() {
let w = 16u32;
let h = 16u32;
let n = (w * h) as usize;
let mut r = vec![0i32; n];
let mut g = vec![0i32; n];
let mut b = vec![0i32; n];
for i in 0..n {
r[i] = ((i * 7) % 256) as i32;
g[i] = ((i * 11 + 50) % 256) as i32;
b[i] = ((i * 3 + 100) % 256) as i32;
}
let tile = TileData {
components: vec![r.clone(), g.clone(), b.clone()],
width: w,
height: h,
};
let components = vec![
TcdComponent {
width: w,
height: h,
precision: 8,
signed: false,
dx: 1,
dy: 1,
},
TcdComponent {
width: w,
height: h,
precision: 8,
signed: false,
dx: 1,
dy: 1,
},
TcdComponent {
width: w,
height: h,
precision: 8,
signed: false,
dx: 1,
dy: 1,
},
];
let params = TcdParams {
num_res: 3,
cblk_w: 16,
cblk_h: 16,
reversible: true,
num_layers: 1,
use_mct: true,
reduce: 0,
max_bytes: None,
};
let encoded = encode_tile(&tile, &components, ¶ms).unwrap();
assert!(!encoded.data.is_empty());
let decoded = decode_tile(&encoded, &components, ¶ms, w, h).unwrap();
assert_eq!(decoded.components.len(), 3);
let err_r = max_abs_error(&decoded.components[0], &r);
let err_g = max_abs_error(&decoded.components[1], &g);
let err_b = max_abs_error(&decoded.components[2], &b);
assert!(
err_r <= 4 && err_g <= 4 && err_b <= 4,
"reversible RGB roundtrip max errors should be <= 4, got R={}, G={}, B={}",
err_r, err_g, err_b
);
let psnr_r = compute_psnr(&r, &decoded.components[0], 8);
let psnr_g = compute_psnr(&g, &decoded.components[1], 8);
let psnr_b = compute_psnr(&b, &decoded.components[2], 8);
assert!(psnr_r > 40.0, "R PSNR should be > 40dB, got {:.2}", psnr_r);
assert!(psnr_g > 40.0, "G PSNR should be > 40dB, got {:.2}", psnr_g);
assert!(psnr_b > 40.0, "B PSNR should be > 40dB, got {:.2}", psnr_b);
}
#[test]
fn lossless_gray() {
let w = 16u32;
let h = 16u32;
let n = (w * h) as usize;
let mut samples = vec![0i32; n];
for i in 0..n {
samples[i] = if i % 2 == 0 { 200 } else { 50 };
}
let tile = TileData {
components: vec![samples.clone()],
width: w,
height: h,
};
let components = vec![TcdComponent {
width: w,
height: h,
precision: 8,
signed: false,
dx: 1,
dy: 1,
}];
let params = TcdParams {
num_res: 2, cblk_w: 16,
cblk_h: 16,
reversible: true,
num_layers: 1,
use_mct: false,
reduce: 0,
max_bytes: None,
};
let encoded = encode_tile(&tile, &components, ¶ms).unwrap();
let decoded = decode_tile(&encoded, &components, ¶ms, w, h).unwrap();
let max_err = max_abs_error(&decoded.components[0], &samples);
assert!(
max_err <= 1,
"reversible roundtrip max error should be <= 1, got {}",
max_err
);
let psnr = compute_psnr(&samples, &decoded.components[0], 8);
assert!(
psnr > 45.0,
"reversible PSNR should be > 45dB, got {:.2}",
psnr
);
}
#[test]
fn lossy_psnr_threshold() {
let w = 32u32;
let h = 32u32;
let n = (w * h) as usize;
let mut samples = vec![0i32; n];
for y in 0..h {
for x in 0..w {
samples[(y * w + x) as usize] = ((x * 8 + y * 4) % 256) as i32;
}
}
let tile = TileData {
components: vec![samples.clone()],
width: w,
height: h,
};
let components = vec![TcdComponent {
width: w,
height: h,
precision: 8,
signed: false,
dx: 1,
dy: 1,
}];
let params = TcdParams {
num_res: 3,
cblk_w: 32,
cblk_h: 32,
reversible: false, num_layers: 1,
use_mct: false,
reduce: 0,
max_bytes: None,
};
let encoded = encode_tile(&tile, &components, ¶ms).unwrap();
let decoded = decode_tile(&encoded, &components, ¶ms, w, h).unwrap();
let psnr = compute_psnr(&samples, &decoded.components[0], 8);
assert!(
psnr > 30.0,
"PSNR should be > 30dB for lossy encoding, got {:.2}dB",
psnr,
);
}
#[test]
fn precinct_count() {
assert_eq!(codeblock_count(64, 64, 64, 64), 1);
assert_eq!(codeblock_count(128, 128, 64, 64), 4);
}
#[test]
fn codeblock_max_64x64() {
assert_eq!(codeblock_count(128, 128, 64, 64), 4);
assert_eq!(codeblock_count(64, 64, 64, 64), 1);
assert_eq!(codeblock_count(65, 65, 64, 64), 4);
}
#[test]
fn rate_allocation() {
let w = 32u32;
let h = 32u32;
let n = (w * h) as usize;
let mut samples = vec![0i32; n];
for y in 0..h {
for x in 0..w {
samples[(y * w + x) as usize] = ((x + y * 3) % 256) as i32;
}
}
let tile = TileData {
components: vec![samples.clone()],
width: w,
height: h,
};
let components = vec![TcdComponent {
width: w,
height: h,
precision: 8,
signed: false,
dx: 1,
dy: 1,
}];
let params_full = TcdParams {
num_res: 3,
cblk_w: 32,
cblk_h: 32,
reversible: true,
num_layers: 1,
use_mct: false,
reduce: 0,
max_bytes: None,
};
let encoded_full = encode_tile(&tile, &components, ¶ms_full).unwrap();
let full_size = encoded_full.data.len();
assert!(full_size > 100, "encoded data should be non-trivial");
let limit = full_size / 2;
let params_limited = TcdParams {
num_res: 3,
cblk_w: 32,
cblk_h: 32,
reversible: true,
num_layers: 1,
use_mct: false,
reduce: 0,
max_bytes: Some(limit),
};
let encoded_limited = encode_tile(&tile, &components, ¶ms_limited).unwrap();
assert!(
encoded_limited.data.len() <= limit,
"encoded data ({}) should be <= max_bytes limit ({})",
encoded_limited.data.len(),
limit
);
assert!(
encoded_limited.data.len() < full_size,
"limited encode ({}) should be smaller than full ({})",
encoded_limited.data.len(),
full_size
);
}