use super::{CompressionError, Compressor};
const SMALL_MIN: i16 = -8;
const SMALL_MAX: i16 = 7;
const SMALL_RUN_MAX: usize = 8;
const MID_MIN: i16 = -32;
const MID_MAX: i16 = 31;
const LITERAL_RUN_MAX: usize = 64;
pub struct Chromoharmonic;
impl Compressor for Chromoharmonic {
fn name(&self) -> &'static str {
"chromoharmonic"
}
fn compress(&self, input: &[u8]) -> Vec<u8> {
if input.is_empty() {
return Vec::new();
}
let mut out = Vec::with_capacity(input.len());
out.push(input[0]);
let mut i = 1usize;
while i < input.len() {
let delta = input[i] as i16 - input[i - 1] as i16;
if (SMALL_MIN..=SMALL_MAX).contains(&delta) {
let mut run = 1usize;
while run < SMALL_RUN_MAX && i + run < input.len() {
let probe_delta = input[i + run] as i16 - input[i + run - 1] as i16;
if probe_delta != delta {
break;
}
run += 1;
}
let delta_code = (delta - SMALL_MIN) as u8;
let token = (delta_code << 3) | (run as u8 - 1);
out.push(token);
i += run;
continue;
}
if (MID_MIN..=MID_MAX).contains(&delta) {
let token = 0xC0 | (delta - MID_MIN) as u8;
out.push(token);
i += 1;
continue;
}
let start = i;
i += 1;
while i < input.len() && (i - start) < LITERAL_RUN_MAX {
let probe_delta = input[i] as i16 - input[i - 1] as i16;
if (MID_MIN..=MID_MAX).contains(&probe_delta) {
break;
}
i += 1;
}
let run = i - start;
out.push(0x80 | (run as u8 - 1));
out.extend_from_slice(&input[start..i]);
}
out
}
fn decompress(&self, input: &[u8]) -> Result<Vec<u8>, CompressionError> {
if input.is_empty() {
return Ok(Vec::new());
}
let mut out = Vec::with_capacity(input.len());
out.push(input[0]);
let mut i = 1usize;
while i < input.len() {
let token = input[i];
i += 1;
if token < 0x80 {
let delta_code = (token >> 3) as i16;
let delta = delta_code + SMALL_MIN;
let run = (token & 0x07) as usize + 1;
for _ in 0..run {
push_delta(&mut out, delta)?;
}
continue;
}
if token < 0xC0 {
let run = (token & 0x3F) as usize + 1;
if i + run > input.len() {
return Err(CompressionError::CorruptStream(
"literal run exceeded input length",
));
}
out.extend_from_slice(&input[i..i + run]);
i += run;
continue;
}
let delta = (token & 0x3F) as i16 + MID_MIN;
push_delta(&mut out, delta)?;
}
Ok(out)
}
}
fn push_delta(out: &mut Vec<u8>, delta: i16) -> Result<(), CompressionError> {
let prev = *out.last().ok_or(CompressionError::CorruptStream(
"delta token missing seed byte",
))? as i16;
let next = prev + delta;
if !(0..=255).contains(&next) {
return Err(CompressionError::ValueOverflow);
}
out.push(next as u8);
Ok(())
}