use crate::error::Error;
pub const MIN_MATCH_PX: usize = 7;
pub const WINDOW_PX: usize = 1 << 16;
pub const TAG_LITERAL: u8 = 0;
pub const TAG_MATCH: u8 = 1;
const MAX_MATCH_BYTES: usize = 255;
const MAX_CANDIDATES: usize = 16;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct TokenStats {
pub literals: u64,
pub matches: u64,
}
#[inline]
fn key3(b: &[u8]) -> u32 {
u32::from(b[0]) | (u32::from(b[1]) << 8) | (u32::from(b[2]) << 16)
}
pub fn encode_plane(res: &[u8], w: usize, h: usize) -> Result<(Vec<u8>, TokenStats), Error> {
let npx = w
.checked_mul(h)
.ok_or_else(|| Error::fmt_msg("dims overflow"))?;
if res.len() != npx * 3 {
return Err(Error::fmt_msg("token plane size mismatch"));
}
let mut wire = Vec::with_capacity(res.len() / 2);
let mut table: std::collections::HashMap<u32, Vec<u32>> = std::collections::HashMap::new();
let mut stats = TokenStats::default();
let mut pos: usize = 0;
while pos < npx {
let key = key3(&res[pos * 3..pos * 3 + 3]);
let mut best_len = 0usize;
let mut best_dist = 0usize;
let mut ncand = 0usize;
if let Some(chain) = table.get(&key) {
for &q in chain.iter().rev() {
let q = q as usize;
let dist = pos - q;
if dist > WINDOW_PX || ncand >= MAX_CANDIDATES {
break;
}
ncand += 1;
let mut l = 0usize;
while l < MAX_MATCH_BYTES
&& pos * 3 + l < res.len()
&& res[q * 3 + l] == res[pos * 3 + l]
{
l += 1;
}
let lpx = l / 3;
if lpx >= MIN_MATCH_PX && lpx > best_len {
let (dx, dy) = (dist % w, dist / w);
if dx <= 0xFFFF && dy <= 0xFFFF {
best_len = lpx;
best_dist = dist;
}
}
}
}
if best_len >= MIN_MATCH_PX {
wire.push(TAG_MATCH);
let (dx, dy) = (best_dist % w, best_dist / w);
wire.extend_from_slice(&(dx as u16).to_le_bytes());
wire.extend_from_slice(&(dy as u16).to_le_bytes());
wire.push(best_len as u8);
for p in pos..pos + best_len {
let k = key3(&res[p * 3..p * 3 + 3]);
table.entry(k).or_default().push(p as u32);
}
pos += best_len;
stats.matches += 1;
} else {
wire.push(TAG_LITERAL);
wire.extend_from_slice(&res[pos * 3..pos * 3 + 3]);
table.entry(key).or_default().push(pos as u32);
pos += 1;
stats.literals += 1;
}
}
Ok((wire, stats))
}
pub fn decode_plane(wire: &[u8], w: usize, h: usize) -> Result<Vec<u8>, Error> {
let npx = w
.checked_mul(h)
.ok_or_else(|| Error::fmt_msg("dims overflow"))?;
let mut out: Vec<u8> = Vec::with_capacity(npx * 3);
let mut c = 0usize;
while c < wire.len() {
match wire[c] {
TAG_LITERAL => {
if c + 4 > wire.len() {
return Err(Error::fmt_msg("token wire: truncated literal"));
}
out.extend_from_slice(&wire[c + 1..c + 4]);
c += 4;
}
TAG_MATCH => {
if c + 6 > wire.len() {
return Err(Error::fmt_msg("token wire: truncated match"));
}
let dx = u16::from_le_bytes([wire[c + 1], wire[c + 2]]) as usize;
let dy = u16::from_le_bytes([wire[c + 3], wire[c + 4]]) as usize;
let len = wire[c + 5] as usize;
c += 6;
if len < MIN_MATCH_PX {
return Err(Error::fmt_msg("token wire: match below minimum"));
}
let p = out.len() / 3;
let dist = dy * w + dx;
if dist == 0 || dist > p {
return Err(Error::fmt_msg("token wire: match source out of range"));
}
if out.len() + len * 3 > npx * 3 {
return Err(Error::fmt_msg("token wire: match overruns plane"));
}
let q = (p - dist) * 3;
for k in 0..len * 3 {
let b = out[q + k];
out.push(b);
}
}
_ => return Err(Error::fmt_msg("token wire: unknown tag")),
}
}
if out.len() != npx * 3 {
return Err(Error::fmt_msg("token wire: plane size mismatch"));
}
Ok(out)
}