const OLL_LEN: usize = 2; const OLL_FIRST: usize = 3;
const LL_CURVERSION: i16 = -100; const LL_CURHDRLEN: i16 = 7;
const LL_NREFS: usize = 0; const LL_HDRLEN: usize = 1; const LL_VERSION: usize = 2; const LL_LENLO: usize = 3; const LL_LENHI: usize = 4;
const I_SHIFT: i32 = 4096;
const I_DATAMAX: i32 = 4095;
const I_ZN: i32 = 0; const I_HN: i32 = 4; const I_PN: i32 = 5; const I_SH: i32 = 1; const I_IH: i32 = 2; const I_DH: i32 = 3; const I_IS: i32 = 6; const I_DS: i32 = 7;
const M_SH: i32 = 4096;
const M_IH: i32 = 8192;
const M_DH: i32 = 12288;
const M_HN: i32 = 16384;
const M_PN: i32 = 20480;
const M_MOVE: i16 = 16384;
pub fn pl_p2li_max_len(npix: usize) -> usize {
3 * npix + LL_CURHDRLEN as usize
}
fn room(lldst: &[i16], op: usize, n: usize) -> bool {
lldst.len() >= op + n
}
pub fn pl_p2li(pxsrc: &[i32], xs: i32, lldst: &mut [i16], npix: usize) -> Option<usize> {
let mut v;
let mut dv: i32;
let mut np: i32;
let mut nv: i32 = 0;
let mut nz: i32;
if npix == 0 {
return Some(0);
}
if !room(lldst, 0, LL_CURHDRLEN as usize) {
return None;
}
lldst[LL_VERSION] = LL_CURVERSION;
lldst[LL_HDRLEN] = LL_CURHDRLEN;
lldst[LL_NREFS] = 0;
lldst[5] = 0;
lldst[6] = 0;
let xe = xs + (npix as i32) - 1;
let mut op: usize = (LL_CURHDRLEN) as usize;
let zero: i32 = 0;
let mut pv: i32 = i32::max(zero, pxsrc[xs as usize]); let mut x1: i32 = xs; let mut iz: i32 = xs; let mut hi: i32 = 1;
for ip in xs..=xe {
if ip < xe {
nv = i32::max(zero, pxsrc[(ip + 1) as usize]);
if nv == pv {
continue;
}
if pv == 0 {
pv = nv;
x1 = ip + 1;
continue;
}
} else if pv == 0 {
x1 = xe + 1;
}
np = ip - x1 + 1;
nz = x1 - iz;
if pv > 0 {
dv = pv - hi;
if dv != 0 {
hi = pv;
if dv.abs() > I_DATAMAX {
if !room(lldst, op, 2) {
return None;
}
lldst[op] = ((pv & I_DATAMAX) + M_SH) as i16;
op += 1;
lldst[op] = (pv / I_SHIFT) as i16;
op += 1;
} else {
if !room(lldst, op, 1) {
return None;
}
if dv < 0 {
lldst[op] = (-dv + M_DH) as i16;
} else {
lldst[op] = (dv + M_IH) as i16;
}
op += 1;
if np == 1 && nz == 0 {
v = lldst[op - 1];
lldst[op - 1] = v | M_MOVE;
np = 0; }
}
}
}
if nz > 0 {
while nz > 0 {
if !room(lldst, op, 1) {
return None;
}
lldst[op] = i32::min(I_DATAMAX - 1, nz) as i16;
op += 1;
nz -= I_DATAMAX - 1
}
if np == 1 && pv > 0 {
lldst[op - 1] = lldst[op - 1] + (M_PN as i16) + 1;
np = 0; }
}
while np > 0 {
if !room(lldst, op, 1) {
return None;
}
lldst[op] = (i32::min(I_DATAMAX, np) + M_HN) as i16;
op += 1;
np -= I_DATAMAX;
}
x1 = ip + 1;
iz = x1;
pv = nv;
}
lldst[LL_LENLO] = (op % 32768) as i16;
lldst[LL_LENHI] = (op / 32768) as i16;
Some(op)
}
pub fn pl_l2pi(ll_src: &[i16], xs: i32, px_dst: &mut [i32], npix: usize) -> Option<usize> {
let mut data;
let mut otop: usize;
let lllen: i32;
let mut i1: i32;
let mut i2: i32;
let mut x2: i32;
let mut np: i32;
let mut opcode: i32;
let llfirt: i32;
if ll_src.len() <= LL_VERSION {
return None; }
if ll_src[LL_VERSION] > 0 {
lllen = ll_src[OLL_LEN] as i32;
llfirt = OLL_FIRST as i32;
} else {
if ll_src.len() <= LL_LENHI {
return None;
}
lllen = ((ll_src[LL_LENHI] as i32) << 15) + ll_src[LL_LENLO] as i32; llfirt = (ll_src[LL_HDRLEN]) as i32; }
if npix == 0 || lllen <= 0 {
return Some(0);
}
let xe: i32 = xs + (npix as i32);
let mut skipwd: bool = false;
let mut op: usize = 0;
let mut x1: i32 = 1;
let mut pv: i32 = 1;
for ip in llfirt..lllen {
if skipwd {
skipwd = false;
continue;
}
if ip < 0 || ip as usize >= ll_src.len() {
return None;
}
opcode = (ll_src[ip as usize] / 4096) as i32; data = (ll_src[ip as usize] & 4095) as i32;
let mut putpix = false;
match opcode {
I_ZN | I_HN | I_PN => {
x2 = x1 + data - 1;
i1 = i32::max(x1, xs);
i2 = i32::min(x2, xe);
np = i2 - i1 + 1;
if np > 0 {
otop = ((op as i32) + np - 1) as usize;
if opcode == I_HN {
#[allow(clippy::needless_range_loop)]
for idx in op..=otop {
px_dst[idx] = pv;
}
} else {
#[allow(clippy::needless_range_loop)]
for idx in op..=otop {
px_dst[idx] = 0;
}
if opcode == I_PN && i2 == x2 {
px_dst[otop] = pv;
}
}
op = otop + 1;
}
x1 = x2 + 1;
}
I_SH => {
if (ip + 1) as usize >= ll_src.len() {
return None;
}
pv = ((ll_src[(ip + 1) as usize] as i32) << 12) + data;
skipwd = true;
}
I_IH => {
pv += data;
}
I_DH => {
pv -= data;
}
I_IS => {
pv += data;
putpix = true;
}
I_DS => {
pv -= data;
putpix = true;
}
_ => (),
}
if putpix {
if x1 >= xs && x1 <= xe {
px_dst[op] = pv;
op += 1;
}
x1 += 1;
}
if x1 > xe {
break;
}
}
#[allow(clippy::needless_range_loop)]
for idx in op..npix {
px_dst[idx] = 0;
}
Some(npix)
}
#[cfg(test)]
mod tests {
use super::*;
fn encode(input: &[i32]) -> Vec<i16> {
let mut ll = vec![0i16; pl_p2li_max_len(input.len())];
let n = pl_p2li(input, 0, &mut ll, input.len())
.expect("a pl_p2li_max_len buffer is always big enough");
ll.truncate(n);
ll
}
fn worst_case(npix: usize) -> Vec<i32> {
(0..npix)
.map(|i| if i % 2 == 0 { 5000 } else { 1 })
.collect()
}
fn round_trip(input: &[i32]) -> Vec<i32> {
let ll = encode(input);
let mut out = vec![0i32; input.len()];
let n = pl_l2pi(&ll, 0, &mut out, input.len()).expect("list fits in the source");
assert_eq!(n, input.len());
out
}
#[test]
fn it_works() {
let input: [i32; 9] = [3, 56, 3343, 22225, 3, 66, 3, 3, 3];
let xs = 0;
let mut compressed: [i16; 200] = [0; 200];
let npix = 9;
let res = pl_p2li(&input, xs, &mut compressed, npix).unwrap();
println!("Compressed items: {res}");
assert!(res <= compressed.len());
let mut uncompressed: [i32; 10] = [0; 10];
let res2 = pl_l2pi(&compressed[..res], xs, &mut uncompressed, npix)
.expect("list fits in the source");
println!("Uncompressed items: {res2}");
assert_eq!(res2, npix);
assert_eq!(&uncompressed[..npix], &input[..]);
}
#[test]
fn ll_len_header_is_the_word_count() {
for input in [
vec![7],
vec![3, 56, 3343, 22225, 3, 66, 3, 3, 3],
vec![0, 0, 0, 5, 5, 0, 9, 0],
(0..500).map(|i| (i * 37) % 4096).collect::<Vec<i32>>(),
] {
let ll = encode(&input);
let ll_len = ((ll[LL_LENHI] as i32) << 15) + ll[LL_LENLO] as i32;
assert_eq!(
ll_len as usize,
ll.len(),
"LL_LEN must equal the number of words written, for {:?}...",
&input[..input.len().min(6)]
);
assert_eq!(ll[LL_HDRLEN], LL_CURHDRLEN);
assert_eq!(ll[LL_VERSION], LL_CURVERSION);
}
}
#[test]
fn encoding_matches_the_reference_c() {
let cases: &[(&[i32], &[i16])] = &[
(&[7], &[0, 7, -100, 8, 0, 0, 0, 24582]),
(&[1, 2, 3], &[0, 7, -100, 10, 0, 0, 0, 16385, 24577, 24577]),
(
&[0, 0, 0, 5, 5, 0, 9, 0],
&[0, 7, -100, 13, 0, 0, 0, 8196, 3, 16386, 8196, 20482, 1],
),
(
&[3, 56, 3343, 22225, 3, 66, 3, 3, 3],
&[
0, 7, -100, 19, 0, 0, 0, 24578, 24629, 27863, 5841, 5, 16385, 4099, 0, 16385,
24639, 12351, 16387,
],
),
];
for (input, expected) in cases {
assert_eq!(&encode(input)[..], *expected, "encoding of {input:?}");
}
}
#[test]
fn decoding_matches_the_reference_c() {
let cases: &[(&[i16], &[i32])] = &[
(&[0, 7, -100, 8, 0, 0, 0, 24582], &[7]),
(&[0, 7, -100, 10, 0, 0, 0, 16385, 24577, 24577], &[1, 2, 3]),
(
&[0, 7, -100, 13, 0, 0, 0, 8196, 3, 16386, 8196, 20482, 1],
&[0, 0, 0, 5, 5, 0, 9, 0],
),
(
&[
0, 7, -100, 19, 0, 0, 0, 24578, 24629, 27863, 5841, 5, 16385, 4099, 0, 16385,
24639, 12351, 16387,
],
&[3, 56, 3343, 22225, 3, 66, 3, 3, 3],
),
];
for (ll, expected) in cases {
let mut out = vec![0i32; expected.len()];
let n = pl_l2pi(ll, 0, &mut out, expected.len()).expect("list fits in the source");
assert_eq!(n, expected.len());
assert_eq!(&out[..], *expected, "decoding of {ll:?}");
}
}
#[test]
fn long_zero_runs_do_not_overflow_the_opcode_field() {
for n in [4093usize, 4094, 4095, 4096, 8190, 12000] {
let mut input = vec![0i32; n];
input.push(5);
let out = round_trip(&input);
assert_eq!(
out, input,
"{n} zeros followed by a pixel did not round trip"
);
for w in &encode(&input)[LL_CURHDRLEN as usize..] {
let opcode = (*w as i32) / 4096;
assert!(
matches!(
opcode,
I_ZN | I_HN | I_PN | I_SH | I_IH | I_DH | I_IS | I_DS
),
"word {w} decodes to unknown opcode {opcode}"
);
}
}
}
#[test]
fn encode_fits_in_max_len() {
for npix in 1..=300usize {
let input = worst_case(npix);
let max = pl_p2li_max_len(npix);
let mut ll = vec![0i16; max];
let n = pl_p2li(&input, 0, &mut ll, npix)
.unwrap_or_else(|| panic!("{npix} pixels did not fit in pl_p2li_max_len({npix})"));
assert_eq!(n, max, "{npix} pixels wrote {n} words, bound is {max}");
let mut out = vec![0i32; npix];
assert_eq!(pl_l2pi(&ll[..n], 0, &mut out, npix), Some(npix));
assert_eq!(out, input, "worst-case {npix} pixels did not round trip");
}
}
#[test]
fn max_len_is_tight() {
let mut ll = [0i16; 10];
assert_eq!(pl_p2li_max_len(1), 10);
assert_eq!(pl_p2li(&[5000], 0, &mut ll, 1), Some(10));
}
#[test]
fn undersized_buffer_returns_none() {
let input = worst_case(50);
let needed = encode(&input).len();
let mut ll = vec![0i16; needed - 1];
assert_eq!(pl_p2li(&input, 0, &mut ll, input.len()), None);
let mut tiny = [0i16; LL_CURHDRLEN as usize - 1];
assert_eq!(pl_p2li(&[1], 0, &mut tiny, 1), None);
}
#[test]
fn truncated_source_returns_none() {
let input = worst_case(50);
let ll = encode(&input);
let mut out = vec![0i32; input.len()];
assert_eq!(
pl_l2pi(&ll, 0, &mut out, input.len()),
Some(input.len()),
"the untruncated list should decode"
);
for n in 0..ll.len() {
assert_eq!(
pl_l2pi(&ll[..n], 0, &mut out, input.len()),
None,
"a {n}-word source should not decode a {}-word list",
ll.len()
);
}
let mut sh_at_end = ll.clone();
let last = sh_at_end.len() - 1;
sh_at_end[last] = (M_SH + 1) as i16;
assert_eq!(pl_l2pi(&sh_at_end, 0, &mut out, input.len()), None);
}
#[test]
fn two_words_per_pixel_is_not_enough() {
for npix in 2..=300usize {
let input = worst_case(npix);
let mut ll = vec![0i16; npix * 2 + 8];
assert_eq!(
pl_p2li(&input, 0, &mut ll, npix),
None,
"{npix} pixels should not fit in the old 2-words-per-pixel buffer"
);
}
assert_eq!(pl_p2li_max_len(2), 13);
assert_eq!(pl_p2li(&worst_case(1), 0, &mut [0i16; 10], 1), Some(10));
}
#[test]
fn round_trips() {
let cases: Vec<Vec<i32>> = vec![
vec![],
vec![0],
vec![1],
vec![32767],
vec![0, 0, 0, 0],
vec![5; 40],
vec![0; 40],
vec![1, 0, 1, 0, 1, 0, 1, 0],
vec![4095, 4096, 4097],
vec![32767, 0, 32767],
vec![0; 20]
.into_iter()
.chain([9])
.chain(vec![0; 20])
.collect(),
(0..1000).map(|i| (i * 7919) % 32768).collect(),
(0..1000)
.map(|i| if i % 3 == 0 { 0 } else { 12345 })
.collect(),
];
for input in cases {
if input.is_empty() {
let mut ll = [0i16; 32];
assert_eq!(
pl_p2li(&input, 0, &mut ll, 0),
Some(0),
"empty input encodes to nothing"
);
continue;
}
assert_eq!(
round_trip(&input),
input,
"round trip of {} pixels",
input.len()
);
}
}
}