use super::JxsError;
pub fn inverse_53_1d(low: &[i32], high: &[i32]) -> Vec<i32> {
let n_low = low.len();
let n_high = high.len();
let n = n_low + n_high;
if n_low == 0 {
return Vec::new();
}
let mut s = low.to_vec();
let mut d = high.to_vec();
for n_idx in 0..n_low {
let d_prev = if n_idx == 0 {
if n_high > 0 {
d[0]
} else {
0
}
} else {
d[n_idx - 1]
};
let d_curr = if n_idx < n_high {
d[n_idx]
} else if n_high > 0 {
d[n_high - 1]
} else {
0
};
s[n_idx] -= (d_prev + d_curr + 2) >> 2;
}
for n_idx in 0..n_high {
let s_curr = s[n_idx];
let s_next = if n_idx + 1 < n_low {
s[n_idx + 1]
} else {
s[n_low - 1]
};
d[n_idx] += (s_curr + s_next) >> 1;
}
let mut out = vec![0i32; n];
for i in 0..n_low {
out[2 * i] = s[i];
}
for i in 0..n_high {
out[2 * i + 1] = d[i];
}
out
}
pub fn forward_53_1d(signal: &[i32]) -> (Vec<i32>, Vec<i32>) {
let n = signal.len();
if n == 0 {
return (Vec::new(), Vec::new());
}
if n == 1 {
return (vec![signal[0]], Vec::new());
}
let n_low = (n + 1) / 2;
let n_high = n / 2;
let mut low: Vec<i32> = signal.iter().step_by(2).copied().collect();
let mut high: Vec<i32> = signal.iter().skip(1).step_by(2).copied().collect();
for i in 0..n_high {
let l_curr = low[i];
let l_next = if i + 1 < n_low {
low[i + 1]
} else {
low[n_low - 1]
};
high[i] -= (l_curr + l_next) >> 1;
}
for i in 0..n_low {
let h_prev = if i == 0 {
if n_high > 0 {
high[0]
} else {
0
}
} else {
high[i - 1]
};
let h_curr = if i < n_high {
high[i]
} else if n_high > 0 {
high[n_high - 1]
} else {
0
};
low[i] += (h_prev + h_curr + 2) >> 2;
}
(low, high)
}
pub fn inverse_53_2d(
ll: &[i32],
hl: &[i32],
lh: &[i32],
hh: &[i32],
width: usize,
height: usize,
) -> Result<Vec<i32>, JxsError> {
if width == 0 || height == 0 {
return Ok(Vec::new());
}
let n_low_w = (width + 1) / 2; let n_high_w = width / 2; let n_low_h = (height + 1) / 2; let n_high_h = height / 2;
let exp_ll = n_low_h * n_low_w;
let exp_hl = n_low_h * n_high_w;
let exp_lh = n_high_h * n_low_w;
let exp_hh = n_high_h * n_high_w;
if ll.len() < exp_ll {
return Err(JxsError::InvalidHeader(format!(
"LL subband too small: expected {exp_ll}, got {}",
ll.len()
)));
}
if hl.len() < exp_hl {
return Err(JxsError::InvalidHeader(format!(
"HL subband too small: expected {exp_hl}, got {}",
hl.len()
)));
}
if lh.len() < exp_lh {
return Err(JxsError::InvalidHeader(format!(
"LH subband too small: expected {exp_lh}, got {}",
lh.len()
)));
}
if hh.len() < exp_hh {
return Err(JxsError::InvalidHeader(format!(
"HH subband too small: expected {exp_hh}, got {}",
hh.len()
)));
}
let mut low_rows = vec![0i32; n_low_h * width];
for row in 0..n_low_h {
let l_row = &ll[row * n_low_w..(row + 1) * n_low_w];
let h_row = if n_high_w > 0 {
&hl[row * n_high_w..(row + 1) * n_high_w]
} else {
&[]
};
let reconstructed = inverse_53_1d(l_row, h_row);
let copy_len = reconstructed.len().min(width);
low_rows[row * width..row * width + copy_len].copy_from_slice(&reconstructed[..copy_len]);
}
let mut high_rows = vec![0i32; n_high_h * width];
for row in 0..n_high_h {
let l_row = &lh[row * n_low_w..(row + 1) * n_low_w];
let h_row = if n_high_w > 0 {
&hh[row * n_high_w..(row + 1) * n_high_w]
} else {
&[]
};
let reconstructed = inverse_53_1d(l_row, h_row);
let copy_len = reconstructed.len().min(width);
high_rows[row * width..row * width + copy_len].copy_from_slice(&reconstructed[..copy_len]);
}
let mut output = vec![0i32; height * width];
for col in 0..width {
let low_col: Vec<i32> = (0..n_low_h).map(|r| low_rows[r * width + col]).collect();
let high_col: Vec<i32> = (0..n_high_h).map(|r| high_rows[r * width + col]).collect();
let col_out = inverse_53_1d(&low_col, &high_col);
let copy_len = col_out.len().min(height);
for (row, &val) in col_out[..copy_len].iter().enumerate() {
output[row * width + col] = val;
}
}
Ok(output)
}
pub fn forward_wavelet_1d(signal: &[i32]) -> (Vec<i32>, Vec<i32>) {
forward_53_1d(signal)
}
pub fn forward_wavelet_2d(
image: &[i32],
width: usize,
height: usize,
) -> Result<(Vec<i32>, Vec<i32>, Vec<i32>, Vec<i32>), JxsError> {
if width == 0 || height == 0 {
return Ok((Vec::new(), Vec::new(), Vec::new(), Vec::new()));
}
if image.len() < width * height {
return Err(JxsError::InvalidHeader(format!(
"forward_wavelet_2d: image too small, expected {}, got {}",
width * height,
image.len()
)));
}
let n_low_w = (width + 1) / 2; let n_high_w = width / 2; let n_low_h = (height + 1) / 2; let n_high_h = height / 2;
let mut low_rows = vec![0i32; n_low_h * width];
let mut high_rows = vec![0i32; n_high_h * width];
for col in 0..width {
let col_vals: Vec<i32> = (0..height).map(|r| image[r * width + col]).collect();
let (low, high) = forward_53_1d(&col_vals);
for (r, &v) in low.iter().enumerate().take(n_low_h) {
low_rows[r * width + col] = v;
}
for (r, &v) in high.iter().enumerate().take(n_high_h) {
high_rows[r * width + col] = v;
}
}
let mut ll = vec![0i32; n_low_h * n_low_w];
let mut hl = vec![0i32; n_low_h * n_high_w];
for row in 0..n_low_h {
let r = &low_rows[row * width..(row + 1) * width];
let (low, high) = forward_53_1d(r);
for (c, &v) in low.iter().enumerate().take(n_low_w) {
ll[row * n_low_w + c] = v;
}
for (c, &v) in high.iter().enumerate().take(n_high_w) {
hl[row * n_high_w + c] = v;
}
}
let mut lh = vec![0i32; n_high_h * n_low_w];
let mut hh = vec![0i32; n_high_h * n_high_w];
for row in 0..n_high_h {
let r = &high_rows[row * width..(row + 1) * width];
let (low, high) = forward_53_1d(r);
for (c, &v) in low.iter().enumerate().take(n_low_w) {
lh[row * n_low_w + c] = v;
}
for (c, &v) in high.iter().enumerate().take(n_high_w) {
hh[row * n_high_w + c] = v;
}
}
Ok((ll, hl, lh, hh))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn roundtrip_1d_even_length() {
let orig = vec![10i32, 20, 30, 40, 50, 60, 70, 80];
let (low, high) = forward_53_1d(&orig);
let recovered = inverse_53_1d(&low, &high);
assert_eq!(
recovered, orig,
"1D round-trip failed for even-length signal"
);
}
#[test]
fn roundtrip_1d_odd_length() {
let orig = vec![3i32, 1, 4, 1, 5, 9, 2];
let (low, high) = forward_53_1d(&orig);
let recovered = inverse_53_1d(&low, &high);
assert_eq!(
recovered, orig,
"1D round-trip failed for odd-length signal"
);
}
#[test]
fn roundtrip_1d_single_element() {
let orig = vec![42i32];
let (low, high) = forward_53_1d(&orig);
assert_eq!(high.len(), 0);
let recovered = inverse_53_1d(&low, &high);
assert_eq!(recovered, orig);
}
#[test]
fn roundtrip_1d_constant_signal_has_zero_high() {
let orig = vec![128i32; 8];
let (low, high) = forward_53_1d(&orig);
for &h in &high {
assert_eq!(h, 0, "detail coefficients must be zero for constant signal");
}
let recovered = inverse_53_1d(&low, &high);
assert_eq!(recovered, orig);
}
#[test]
fn roundtrip_53_1d_constant() {
let orig: Vec<i32> = vec![64; 16];
let (low, high) = forward_53_1d(&orig);
let rec = inverse_53_1d(&low, &high);
assert_eq!(rec, orig);
}
#[test]
fn roundtrip_53_2d_constant() {
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 ll_rows = vec![0i32; n_low_h * n_low_w];
let mut hl_rows = vec![0i32; n_low_h * n_high_w];
let mut row_low = vec![vec![0i32; n_low_w]; n];
let mut row_high = 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_low[row] = l;
row_high[row] = h;
}
for col in 0..n_low_w {
let col_vals: Vec<i32> = (0..n).map(|r| row_low[r][col]).collect();
let (l, _h) = forward_53_1d(&col_vals);
for (r, &v) in l.iter().enumerate() {
ll_rows[r * n_low_w + col] = v;
}
}
for col in 0..n_high_w {
let col_vals: Vec<i32> = (0..n).map(|r| row_high[r][col]).collect();
let (l, _h) = forward_53_1d(&col_vals);
for (r, &v) in l.iter().enumerate() {
hl_rows[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_rows, &hl_rows, &lh, &hh, n, n).unwrap();
assert_eq!(result.len(), n * n);
for (i, &v) in result.iter().enumerate() {
assert_eq!(v, constant, "sample {i}: expected {constant}, got {v}");
}
}
#[test]
fn inverse_53_2d_subband_too_small_returns_error() {
let ll = vec![0i32; 1]; let hl = vec![0i32; 4];
let lh = vec![0i32; 4];
let hh = vec![0i32; 4];
let result = inverse_53_2d(&ll, &hl, &lh, &hh, 4, 4);
assert!(result.is_err());
}
#[test]
fn inverse_53_2d_zero_dimensions_returns_empty() {
let result = inverse_53_2d(&[], &[], &[], &[], 0, 0).unwrap();
assert!(result.is_empty());
}
fn assert_fwd_inv_identity(image: &[i32], w: usize, h: usize) {
let (ll, hl, lh, hh) = forward_wavelet_2d(image, w, h).unwrap();
let rec = inverse_53_2d(&ll, &hl, &lh, &hh, w, h).unwrap();
assert_eq!(rec, image, "forward→inverse 2D not identity for {w}x{h}");
}
#[test]
fn forward_inverse_2d_constant() {
assert_fwd_inv_identity(&vec![64i32; 16], 4, 4);
}
#[test]
fn forward_inverse_2d_gradient_even() {
let (w, h) = (8usize, 8usize);
let img: Vec<i32> = (0..w * h).map(|i| (i % w + i / w) as i32).collect();
assert_fwd_inv_identity(&img, w, h);
}
#[test]
fn forward_inverse_2d_gradient_odd_dims() {
let (w, h) = (7usize, 5usize);
let img: Vec<i32> = (0..w * h).map(|i| (3 * (i % w) + (i / w)) as i32).collect();
assert_fwd_inv_identity(&img, w, h);
}
#[test]
fn forward_inverse_2d_pseudo_random() {
let (w, h) = (16usize, 9usize);
let mut state: u32 = 0x1234_5678;
let img: Vec<i32> = (0..w * h)
.map(|_| {
state = state.wrapping_mul(1_664_525).wrapping_add(1_013_904_223);
((state >> 24) & 0xFF) as i32
})
.collect();
assert_fwd_inv_identity(&img, w, h);
}
#[test]
fn forward_wavelet_1d_matches_forward_53_1d() {
let sig = vec![10i32, 20, 30, 40, 55, 12, 7, 99];
assert_eq!(forward_wavelet_1d(&sig), forward_53_1d(&sig));
}
#[test]
fn forward_wavelet_2d_too_small_errors() {
let img = vec![1i32; 3];
assert!(forward_wavelet_2d(&img, 4, 4).is_err());
}
}