#![forbid(unsafe_code)]
use crate::core::candidate::{Candidate, CandidateContext, Encoder};
use crate::core::cost::ByteSplit;
use crate::core::representation::Representation;
use crate::entropy::rank::{RankError, rank_multinomial};
#[derive(Debug, Default)]
pub struct PaletteEncoder;
impl Encoder for PaletteEncoder {
fn name(&self) -> &'static str {
"PALETTE"
}
fn encode(&self, input: &[u8], ctx: &CandidateContext<'_>) -> Vec<Candidate> {
let n = input.len() as u64;
if n == 0 || n > ctx.limits.max_chunk_size {
return Vec::new();
}
let mut hist = [0u32; 256];
for &b in input {
hist[b as usize] += 1;
}
let m = hist.iter().filter(|&&c| c > 0).count();
if m < 2 || m > ctx.limits.max_palette {
return Vec::new();
}
let palette: Vec<u8> = (0..256u16)
.filter(|&i| hist[i as usize] > 0)
.map(|i| i as u8)
.collect();
let counts: Vec<u32> = palette.iter().map(|&s| hist[s as usize]).collect();
let mut seq = Vec::with_capacity(input.len());
for &b in input {
let idx = palette.iter().position(|&s| s == b).unwrap() as u8;
seq.push(idx);
}
let rank = match rank_multinomial(&seq, n, &counts) {
Ok(r) => r,
Err(RankError::SpaceOverflow) | Err(RankError::Overflow) => return Vec::new(),
Err(_) => return Vec::new(),
};
let rep = Representation::Palette {
palette: palette.clone(),
counts: counts.clone(),
rank,
len: n,
};
if rep.validate(ctx.limits).is_err() {
return Vec::new();
}
let split = ByteSplit {
configurational: 16,
..Default::default()
};
let cost = crate::core::cost::estimate(&rep, &split, 0);
vec![Candidate {
representation: rep,
objects: Vec::new(),
cost,
content_id: ctx.content_id,
}]
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::candidate::CandidateContext;
use crate::core::cost::Policy;
use crate::core::extent::ChunkId;
use crate::core::limits::Limits;
use crate::core::materialize::materialize_to_vec;
use crate::tests::helpers::MemResolver;
#[test]
fn palette_encoder_roundtrip() {
let mut input = Vec::with_capacity(64);
for i in 0..64 {
input.push(match i % 10 {
0..=5 => 0x10,
6..=8 => 0x20,
_ => 0x30,
});
}
let enc = PaletteEncoder;
let ctx = CandidateContext {
limits: &Limits::default(),
policy: &Policy::default(),
content_id: ChunkId::of(&input),
bases: &[],
dedup: None,
};
let cands = enc.encode(&input, &ctx);
assert_eq!(cands.len(), 1);
let resolver = MemResolver::empty();
let out =
materialize_to_vec(&cands[0].representation, &resolver, &Limits::default()).unwrap();
assert_eq!(out, input);
}
#[test]
fn palette_skips_overflowing_state_space() {
let mut input = Vec::with_capacity(8192);
for i in 0..8192 {
input.push(match i % 3 {
0 => 0x11,
1 => 0x22,
_ => 0x33,
});
}
let enc = PaletteEncoder;
let ctx = CandidateContext {
limits: &Limits::default(),
policy: &Policy::default(),
content_id: ChunkId::of(&input),
bases: &[],
dedup: None,
};
assert!(enc.encode(&input, &ctx).is_empty());
}
#[test]
fn palette_skips_high_cardinality() {
let input: Vec<u8> = (0..256u32).map(|i| (i % 251) as u8).collect();
let enc = PaletteEncoder;
let ctx = CandidateContext {
limits: &Limits::default(),
policy: &Policy::default(),
content_id: ChunkId::of(&input),
bases: &[],
dedup: None,
};
assert!(enc.encode(&input, &ctx).is_empty());
}
}