paracletics-hypercube 0.1.0

General-purpose paracletic hyper cube compression toolkit.
Documentation
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(())
}