#![allow(
clippy::cast_possible_truncation,
reason = "cache keys are < 1<<cache_bits (<= 2047, fit u16), copy lengths are \
<= MAX_COPY_LENGTH and distances <= WINDOW_SIZE, and pixel positions are \
bounded by width*height (each dimension <= 2^14, so the count is <= 2^28 \
< u32::MAX); every narrowing cast here is therefore value-preserving"
)]
use crate::lossless::color_cache::ColorCache;
use crate::lossless::constants::{
HASH_BITS_LZ77, MAX_CHAIN, MAX_COPY_LENGTH, MIN_MATCH, NUM_DISTANCE_CODES, NUM_LITERAL_CODES,
WINDOW_SIZE,
};
use crate::lossless::histogram::{Histogram, fixed_log2};
use crate::lossless::lz77::{PlaneCodeMap, prefix_encode};
use crate::lossless::prelude::*;
use crate::lossless::work::work;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum Token {
Literal(u32),
Copy {
length: u32,
distance: u32,
},
}
#[derive(Clone, Copy, Debug)]
pub(crate) enum Resolved {
Literal(u32),
Copy {
length_symbol: u32,
length_extra: (u32, u32),
dist_symbol: u32,
dist_extra: (u32, u32),
},
Cache(u16),
}
pub(crate) fn parse(pixels: &[u32], use_lz77: bool) -> Vec<Token> {
if !use_lz77 || pixels.len() < MIN_MATCH as usize {
return pixels.iter().map(|&p| Token::Literal(p)).collect();
}
let chain = HashChain::build(pixels);
parse_with_chain(pixels, &chain)
}
pub(crate) fn parse_lz77(pixels: &[u32]) -> (Vec<Token>, HashChain) {
let chain = HashChain::build(pixels);
let tokens = if pixels.len() < MIN_MATCH as usize {
pixels.iter().map(|&p| Token::Literal(p)).collect()
} else {
parse_with_chain(pixels, &chain)
};
(tokens, chain)
}
fn parse_with_chain(pixels: &[u32], chain: &HashChain) -> Vec<Token> {
let n = pixels.len();
let mut tokens = Vec::new();
let mut i = 0usize;
let mut here = chain.find(pixels, i);
while i < n {
let ahead = if i + 1 < n {
chain.find(pixels, i + 1)
} else {
None
};
let matched = here.filter(|&(_, len)| ahead.is_none_or(|(_, next)| next <= len));
if let Some((distance, length)) = matched {
tokens.push(Token::Copy {
length: length as u32,
distance: distance as u32,
});
i += length;
here = chain.find(pixels, i); } else {
tokens.push(Token::Literal(pixels[i]));
i += 1;
here = ahead; }
}
tokens
}
pub(crate) fn resolve(
tokens: &[Token],
pixels: &[u32],
cache_bits: u32,
width: u32,
mut sink: impl FnMut(usize, Resolved),
) {
work!(ResolveWalk, tokens.len() as u64);
let mut cache = (cache_bits > 0).then(|| {
work!(ColorCacheAlloc);
ColorCache::new(cache_bits)
});
let plane_map = PlaneCodeMap::new(width);
let mut pos = 0usize;
for &token in tokens {
match token {
Token::Literal(argb) => {
let unit = cache.as_mut().map_or(Resolved::Literal(argb), |cache| {
let key = ColorCache::index(argb, cache_bits);
let unit = if cache.get(key) == argb {
Resolved::Cache(key as u16)
} else {
Resolved::Literal(argb)
};
cache.insert(argb);
unit
});
sink(pos, unit);
pos += 1;
},
Token::Copy { length, distance } => {
let (length_symbol, length_bits, length_value) = prefix_encode(length);
let plane_code = plane_map.plane_code(distance);
let (dist_symbol, dist_bits, dist_value) = prefix_encode(plane_code);
sink(
pos,
Resolved::Copy {
length_symbol,
length_extra: (length_value, length_bits),
dist_symbol,
dist_extra: (dist_value, dist_bits),
},
);
if let Some(cache) = cache.as_mut() {
for pixel in &pixels[pos..pos + length as usize] {
cache.insert(*pixel);
}
}
pos += length as usize;
},
}
}
}
const NONE: u32 = u32::MAX;
const HASH_MUL_LO: u32 = 0x5bd1_e996;
const HASH_MUL_HI: u32 = 0xc6a4_a793;
pub(crate) struct HashChain {
prev: Vec<u32>,
}
impl HashChain {
fn build(pixels: &[u32]) -> Self {
let n = pixels.len();
let hash_bits = effective_hash_bits(n);
let mut head = vec![NONE; 1usize << hash_bits];
let mut prev = vec![NONE; n];
for (i, slot) in prev.iter_mut().enumerate() {
let h = pair_hash(pixels, i, hash_bits);
*slot = head[h];
head[h] = i as u32;
}
Self { prev }
}
fn find(&self, pixels: &[u32], i: usize) -> Option<(usize, usize)> {
let max_len = (MAX_COPY_LENGTH as usize).min(pixels.len() - i);
if max_len < MIN_MATCH as usize {
return None;
}
let window_min = i.saturating_sub(WINDOW_SIZE as usize);
let (mut best_len, mut best_dist) = (0usize, 0usize);
let mut guard = pixels[i];
let mut candidate = self.prev[i];
let mut hops = 0u32;
while candidate != NONE && candidate as usize >= window_min && hops < MAX_CHAIN {
work!(MatchHop);
let pos = candidate as usize;
if pixels[pos + best_len] == guard {
let len = match_length(pixels, pos, i, max_len);
if len > best_len {
best_len = len;
best_dist = i - pos;
if best_len >= max_len {
break; }
guard = pixels[i + best_len]; }
}
candidate = self.prev[pos];
hops += 1;
}
(best_len >= MIN_MATCH as usize).then_some((best_dist, best_len))
}
fn find_candidates(
&self,
pixels: &[u32],
i: usize,
out: &mut Vec<(u32, u32)>,
memo: &mut ShiftMemo,
) {
out.clear();
let max_len = (MAX_COPY_LENGTH as usize).min(pixels.len() - i);
if max_len < MIN_MATCH as usize {
return;
}
let window_min = i.saturating_sub(WINDOW_SIZE as usize);
let mut best_len = 0usize;
let mut guard = pixels[i];
let mut candidate = self.prev[i];
let mut hops = 0u32;
while candidate != NONE && candidate as usize >= window_min && hops < MAX_CHAIN {
work!(MatchHop);
let pos = candidate as usize;
if pixels[pos + best_len] == guard {
let dist = (i - pos) as u32;
let len = length_at(memo, pixels, pos, i, max_len, dist);
if len > best_len {
best_len = len;
if len >= MIN_MATCH as usize {
out.push((dist, len as u32));
}
if best_len >= max_len {
break; }
guard = pixels[i + best_len]; }
}
candidate = self.prev[pos];
hops += 1;
}
}
}
#[derive(Default)]
struct ShiftMemo {
read: Vec<(u32, u32)>,
write: Vec<(u32, u32)>,
}
impl ShiftMemo {
fn child(&self, dist: u32) -> Option<u32> {
self.read
.iter()
.find(|&&(d, _)| d == dist)
.map(|&(_, len)| len)
}
fn record(&mut self, dist: u32, len: u32) {
self.write.push((dist, len));
}
fn advance(&mut self) {
core::mem::swap(&mut self.read, &mut self.write);
self.write.clear();
}
}
fn length_at(
memo: &mut ShiftMemo,
pixels: &[u32],
src: usize,
dst: usize,
max_len: usize,
distance: u32,
) -> usize {
let len = match memo.child(distance) {
Some(child) if pixels[src] == pixels[dst] => {
work!(MatchCompare, 1);
(child as usize + 1).min(max_len)
},
Some(_) => 0,
None => match_length(pixels, src, dst, max_len),
};
memo.record(distance, len as u32);
len
}
fn match_length(pixels: &[u32], src: usize, dst: usize, max_len: usize) -> usize {
let mut len = 0usize;
while len < max_len && pixels[src + len] == pixels[dst + len] {
len += 1;
}
work!(MatchCompare, len as u64);
len
}
fn pair_hash(pixels: &[u32], i: usize, hash_bits: u32) -> usize {
let a = pixels[i];
let b = pixels.get(i + 1).copied().unwrap_or(0);
let key = a
.wrapping_mul(HASH_MUL_LO)
.wrapping_add(b.wrapping_mul(HASH_MUL_HI));
(key >> (32 - hash_bits)) as usize
}
fn effective_hash_bits(n: usize) -> u32 {
let ceil_log2 = usize::BITS - n.next_power_of_two().leading_zeros() - 1;
ceil_log2.clamp(1, HASH_BITS_LZ77)
}
const COST_FRAC_BITS: u32 = 16;
pub(crate) struct CostModel {
green: Vec<u64>,
red: [u64; NUM_LITERAL_CODES],
blue: [u64; NUM_LITERAL_CODES],
alpha: [u64; NUM_LITERAL_CODES],
dist: [u64; NUM_DISTANCE_CODES],
}
impl CostModel {
pub(crate) fn from_histogram(h: &Histogram) -> Self {
Self {
green: channel_costs(h.green()),
red: channel_costs_arr(h.red()),
blue: channel_costs_arr(h.blue()),
alpha: channel_costs_arr(h.alpha()),
dist: channel_costs_arr(h.dist()),
}
}
fn literal_cost(&self, argb: u32) -> u64 {
self.green[((argb >> 8) & 0xff) as usize]
+ self.red[((argb >> 16) & 0xff) as usize]
+ self.blue[(argb & 0xff) as usize]
+ self.alpha[((argb >> 24) & 0xff) as usize]
}
fn copy_cost(
&self,
length_symbol: u32,
length_bits: u32,
dist_symbol: u32,
dist_bits: u32,
) -> u64 {
self.green[NUM_LITERAL_CODES + length_symbol as usize]
+ (u64::from(length_bits) << COST_FRAC_BITS)
+ self.dist[dist_symbol as usize]
+ (u64::from(dist_bits) << COST_FRAC_BITS)
}
}
fn channel_costs(counts: &[u32]) -> Vec<u64> {
let total: u64 = counts.iter().map(|&c| u64::from(c)).sum();
if total == 0 {
return vec![fixed_log2(counts.len() as u64); counts.len()];
}
let total_log2 = fixed_log2(total);
counts
.iter()
.map(|&c| total_log2.saturating_sub(fixed_log2(u64::from(c))))
.collect()
}
fn channel_costs_arr<const N: usize>(counts: &[u32]) -> [u64; N] {
debug_assert_eq!(counts.len(), N);
let total: u64 = counts.iter().map(|&c| u64::from(c)).sum();
if total == 0 {
return [fixed_log2(N as u64); N];
}
let total_log2 = fixed_log2(total);
core::array::from_fn(|s| total_log2.saturating_sub(fixed_log2(u64::from(counts[s]))))
}
#[derive(Clone, Copy)]
enum Choice {
Literal,
Copy { len: u32, dist: u32 },
}
pub(crate) fn parse_optimal(
pixels: &[u32],
width: u32,
greedy_tokens: &[Token],
chain: &HashChain,
) -> Vec<Token> {
let n = pixels.len();
if n < MIN_MATCH as usize {
return greedy_tokens.to_vec();
}
let choice = {
let histogram =
crate::lossless::vp8l::encode::RefModel::new(greedy_tokens, pixels, width).histogram(0);
let cost_model = CostModel::from_histogram(&histogram);
let plane_map = PlaneCodeMap::new(width);
let mut cost = vec![0u64; n + 1];
let mut choice = vec![Choice::Literal; n];
let mut cands: Vec<(u32, u32)> = Vec::new();
let mut memo = ShiftMemo::default();
for i in (0..n).rev() {
work!(OptimalDpStep);
let mut best = cost_model
.literal_cost(pixels[i])
.saturating_add(cost[i + 1]);
let mut best_choice = Choice::Literal;
chain.find_candidates(pixels, i, &mut cands, &mut memo);
for &(dist, len) in &cands {
let (length_symbol, length_bits, _) = prefix_encode(len);
let plane_code = plane_map.plane_code(dist);
let (dist_symbol, dist_bits, _) = prefix_encode(plane_code);
let c = cost_model
.copy_cost(length_symbol, length_bits, dist_symbol, dist_bits)
.saturating_add(cost[i + len as usize]);
if c < best {
best = c;
best_choice = Choice::Copy { len, dist };
}
}
cost[i] = best;
choice[i] = best_choice;
memo.advance();
}
choice
};
let mut tokens = Vec::new();
let mut i = 0usize;
while i < n {
match choice[i] {
Choice::Literal => {
tokens.push(Token::Literal(pixels[i]));
i += 1;
},
Choice::Copy { len, dist } => {
tokens.push(Token::Copy {
length: len,
distance: dist,
});
i += len as usize;
},
}
}
tokens
}
#[cfg(test)]
mod tests {
use super::{Resolved, Token, parse, parse_lz77, parse_optimal, resolve};
use crate::lossless::color_cache::ColorCache;
use proptest::prelude::*;
fn decode_tokens(tokens: &[Token]) -> Vec<u32> {
let mut out = Vec::new();
for &token in tokens {
match token {
Token::Literal(argb) => out.push(argb),
Token::Copy { length, distance } => {
let start = out.len() - distance as usize;
for k in 0..length as usize {
out.push(out[start + k]);
}
},
}
}
out
}
fn reference_parse(pixels: &[u32], use_lz77: bool) -> Vec<Token> {
use crate::lossless::constants::MIN_MATCH;
if !use_lz77 || pixels.len() < MIN_MATCH as usize {
return pixels.iter().map(|&p| Token::Literal(p)).collect();
}
let chain = super::HashChain::build(pixels);
let n = pixels.len();
let mut tokens = Vec::new();
let mut i = 0usize;
while i < n {
let matched = chain.find(pixels, i).filter(|&(_, len)| {
!(i + 1 < n
&& chain
.find(pixels, i + 1)
.is_some_and(|(_, next)| next > len))
});
if let Some((distance, length)) = matched {
tokens.push(Token::Copy {
length: length as u32,
distance: distance as u32,
});
i += length;
} else {
tokens.push(Token::Literal(pixels[i]));
i += 1;
}
}
tokens
}
fn find_reference(
chain: &super::HashChain,
pixels: &[u32],
i: usize,
) -> Option<(usize, usize)> {
use crate::lossless::constants::{MAX_CHAIN, MAX_COPY_LENGTH, MIN_MATCH, WINDOW_SIZE};
let max_len = (MAX_COPY_LENGTH as usize).min(pixels.len() - i);
if max_len < MIN_MATCH as usize {
return None;
}
let window_min = i.saturating_sub(WINDOW_SIZE as usize);
let (mut best_len, mut best_dist) = (0usize, 0usize);
let mut candidate = chain.prev[i];
let mut hops = 0u32;
while candidate != super::NONE && candidate as usize >= window_min && hops < MAX_CHAIN {
let pos = candidate as usize;
if pixels[pos + best_len] == pixels[i + best_len] {
let len = super::match_length(pixels, pos, i, max_len);
if len > best_len {
best_len = len;
best_dist = i - pos;
if best_len >= max_len {
break;
}
}
}
candidate = chain.prev[pos];
hops += 1;
}
(best_len >= MIN_MATCH as usize).then_some((best_dist, best_len))
}
#[test]
fn literal_only_parse_is_all_literals() {
let pixels = [1u32, 2, 3, 4, 5];
let tokens = parse(&pixels, false);
assert_eq!(tokens.len(), 5);
assert!(tokens.iter().all(|t| matches!(t, Token::Literal(_))));
assert_eq!(decode_tokens(&tokens), pixels);
}
#[test]
fn repeated_tile_produces_a_copy() {
let tile = [10u32, 20, 30, 40];
let pixels: Vec<u32> = tile.iter().cycle().take(16).copied().collect();
let tokens = parse(&pixels, true);
assert!(
tokens.iter().any(|t| matches!(t, Token::Copy { .. })),
"a repeated tile must emit at least one copy"
);
assert_eq!(decode_tokens(&tokens), pixels);
}
#[test]
fn solid_run_becomes_a_dist_one_copy() {
let pixels = [7u32; 12];
let tokens = parse(&pixels, true);
assert!(
tokens
.iter()
.any(|t| matches!(t, Token::Copy { distance: 1, .. })),
"a solid run must exploit a distance-1 (RLE) copy"
);
assert_eq!(decode_tokens(&tokens), pixels);
}
#[test]
fn resolve_emits_a_cache_hit_for_a_repeated_color() {
let color = 0x1122_3344u32;
let pixels = [color, color];
let tokens = vec![Token::Literal(color), Token::Literal(color)];
let mut units = Vec::new();
resolve(&tokens, &pixels, 8, 2, |_pos, r| units.push(r));
assert!(matches!(units[0], Resolved::Literal(_)));
match units[1] {
Resolved::Cache(key) => {
assert_eq!(usize::from(key), ColorCache::index(color, 8));
},
other => panic!("second identical pixel must be a cache hit, got {other:?}"),
}
}
#[test]
fn resolve_treats_a_fresh_zero_slot_as_a_hit() {
let pixels = [0u32];
let tokens = vec![Token::Literal(0)];
let mut units = Vec::new();
resolve(&tokens, &pixels, 4, 1, |_pos, r| units.push(r));
assert!(matches!(units[0], Resolved::Cache(0)));
}
#[test]
fn resolve_passes_unit_start_pos() {
let a = 0xff_00_00_00u32;
let b = 0xff_11_22_33u32;
let pixels = [a, a, a, a, b];
let tokens = [
Token::Literal(a),
Token::Copy {
length: 3,
distance: 1,
},
Token::Literal(b),
];
let mut starts = Vec::new();
resolve(&tokens, &pixels, 0, 5, |pos, _unit| starts.push(pos));
assert_eq!(starts, vec![0, 1, 4]);
}
fn seeded_pixels(seed: u64, len: usize, palette: u32) -> Vec<u32> {
let mut state = seed | 1;
(0..len)
.map(|_| {
state = state
.wrapping_mul(6_364_136_223_846_793_005)
.wrapping_add(1_442_695_040_888_963_407);
(state >> 40) as u32 % palette
})
.collect()
}
fn find_candidates_reference(
chain: &super::HashChain,
pixels: &[u32],
i: usize,
out: &mut Vec<(u32, u32)>,
) {
use crate::lossless::constants::{MAX_CHAIN, MAX_COPY_LENGTH, MIN_MATCH, WINDOW_SIZE};
out.clear();
let max_len = (MAX_COPY_LENGTH as usize).min(pixels.len() - i);
if max_len < MIN_MATCH as usize {
return;
}
let window_min = i.saturating_sub(WINDOW_SIZE as usize);
let mut best_len = 0usize;
let mut candidate = chain.prev[i];
let mut hops = 0u32;
while candidate != super::NONE && candidate as usize >= window_min && hops < MAX_CHAIN {
let pos = candidate as usize;
if pixels[pos + best_len] == pixels[i + best_len] {
let len = super::match_length(pixels, pos, i, max_len);
if len > best_len {
best_len = len;
if len >= MIN_MATCH as usize {
out.push(((i - pos) as u32, len as u32));
}
if best_len >= max_len {
break;
}
}
}
candidate = chain.prev[pos];
hops += 1;
}
}
#[test]
fn find_candidates_matches_reference_across_the_cap() {
let solid = vec![7u32; 5000];
let periodic: Vec<u32> = (0u32..5000).map(|k| k % 3).collect();
let distinct: Vec<u32> = (0u32..5000).collect();
for pixels in [&solid, &periodic, &distinct] {
let chain = super::HashChain::build(pixels);
let mut memo = super::ShiftMemo::default();
let (mut got, mut want) = (Vec::new(), Vec::new());
for i in (0..pixels.len()).rev() {
chain.find_candidates(pixels, i, &mut got, &mut memo);
find_candidates_reference(&chain, pixels, i, &mut want);
assert_eq!(got, want, "candidate mismatch at position {i}");
memo.advance();
}
}
}
fn parse_optimal_reference(pixels: &[u32], width: u32, greedy: &[Token]) -> Vec<Token> {
let chain = super::HashChain::build(pixels);
parse_optimal(pixels, width, greedy, &chain)
}
#[test]
fn parse_optimal_shared_chain_edge_cases() {
let empty: Vec<u32> = Vec::new();
let one = vec![0x1122_3344u32];
let solid = vec![7u32; 40];
let palette: Vec<u32> = (0u32..40).map(|k| k % 3).collect();
let tile: Vec<u32> = [10u32, 20, 30, 40]
.iter()
.cycle()
.take(40)
.copied()
.collect();
let alpha: Vec<u32> = (0u32..40).map(|k| 0xff00_0000 | (k % 5)).collect();
for pixels in [&empty, &one, &solid, &palette, &tile, &alpha] {
for width in [1u32, 2, 5, 8] {
let (greedy, chain) = parse_lz77(pixels);
let shared = parse_optimal(pixels, width, &greedy, &chain);
let reference = parse_optimal_reference(pixels, width, &greedy);
assert_eq!(shared, reference, "shared-chain mismatch at width {width}");
}
}
}
#[test]
fn literal_cost_is_the_exact_four_channel_sum() {
let cm = super::CostModel {
green: (0u64..280).map(|i| 100 + i).collect(),
red: core::array::from_fn(|i| 200 + i as u64),
blue: core::array::from_fn(|i| 300 + i as u64),
alpha: core::array::from_fn(|i| 400 + i as u64),
dist: core::array::from_fn(|i| 20_000 + i as u64),
};
assert_eq!(cm.literal_cost(0x1122_3344), 1170);
}
#[test]
fn copy_cost_is_the_exact_symbol_and_extra_bit_sum() {
let cm = super::CostModel {
green: (0u64..280).map(|i| 10_000 + i).collect(),
red: [0; 256],
blue: [0; 256],
alpha: [0; 256],
dist: core::array::from_fn(|i| 20_000 + i as u64),
};
assert_eq!(cm.copy_cost(5, 3, 7, 2), 357_948);
}
#[test]
fn channel_costs_switches_on_a_zero_total() {
assert_eq!(super::channel_costs(&[1, 3])[0], 131_072);
assert_eq!(super::channel_costs(&[0, 0]), vec![65_536, 65_536]);
}
#[test]
fn pair_hash_mixes_both_pixels_of_the_pair() {
assert_eq!(super::pair_hash(&[1u32, 2], 0, 8), 233);
}
#[test]
fn effective_hash_bits_is_clamped_ceil_log2() {
assert_eq!(super::effective_hash_bits(16), 4);
assert_eq!(super::effective_hash_bits(1usize << 20), 18);
}
#[test]
fn length_at_short_circuits_on_a_pixel_mismatch() {
let pixels = [10u32, 20, 30, 40];
let mut memo = super::ShiftMemo::default();
memo.record(1, 5); memo.advance(); assert_eq!(super::length_at(&mut memo, &pixels, 0, 1, 4, 1), 0);
}
#[test]
fn find_returns_none_without_an_earlier_match() {
let pixels = [10u32, 20, 30, 40, 50];
let chain = super::HashChain::build(&pixels);
for i in 0..pixels.len() {
assert_eq!(chain.find(&pixels, i), None, "position {i}");
}
}
#[test]
fn find_selects_the_nearest_longest_match() {
let pixels = [7u32; 8];
let chain = super::HashChain::build(&pixels);
assert_eq!(chain.find(&pixels, 1), Some((1, 7)));
}
#[test]
fn find_stops_at_the_max_chain_hop_limit() {
let mut pixels = vec![1u32, 2, 3, 4]; for _ in 0..64 {
pixels.extend_from_slice(&[1, 2, 5]); }
pixels.extend_from_slice(&[1, 2, 3, 4]); let i = pixels.len() - 4;
let chain = super::HashChain::build(&pixels);
assert_eq!(chain.find(&pixels, i), None);
}
#[test]
fn find_candidates_stops_at_the_max_chain_hop_limit() {
let mut pixels = vec![1u32, 2, 3, 4]; for _ in 0..64 {
pixels.extend_from_slice(&[1, 2, 5]); }
pixels.extend_from_slice(&[1, 2, 3, 4]); let i = pixels.len() - 4;
let chain = super::HashChain::build(&pixels);
let mut memo = super::ShiftMemo::default();
let mut out = Vec::new();
chain.find_candidates(&pixels, i, &mut out, &mut memo);
assert!(
out.is_empty(),
"the staircase must be empty at the hop limit, got {out:?}"
);
}
#[test]
fn parse_optimal_replaces_a_suboptimal_greedy_stream() {
let pixels = [1u32, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1];
let greedy = parse(&pixels, true);
let chain = super::HashChain::build(&pixels);
let optimal = parse_optimal(&pixels, 8, &greedy, &chain);
assert_eq!(
optimal,
vec![
Token::Literal(1),
Token::Literal(0),
Token::Literal(1),
Token::Literal(1),
Token::Literal(0),
Token::Literal(1),
Token::Copy {
length: 6,
distance: 4,
},
]
);
assert_ne!(optimal, greedy);
}
#[test]
fn parse_optimal_uses_a_strict_less_than_decision() {
let pixels = [1u32, 0, 1, 1, 0, 1, 1, 1];
let greedy = parse(&pixels, true);
let chain = super::HashChain::build(&pixels);
let optimal = parse_optimal(&pixels, 1, &greedy, &chain);
assert_eq!(
optimal,
vec![
Token::Literal(1),
Token::Literal(0),
Token::Literal(1),
Token::Copy {
length: 4,
distance: 3,
},
Token::Literal(1),
]
);
}
#[test]
fn parse_optimal_tiebreak_direction_is_pinned() {
let pixels = [
1u32, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1, 0, 0, 1, 0, 0, 0, 1, 0,
];
let greedy = parse(&pixels, true);
let chain = super::HashChain::build(&pixels);
let optimal = parse_optimal(&pixels, 1, &greedy, &chain);
assert_eq!(
optimal,
vec![
Token::Literal(1),
Token::Literal(0),
Token::Literal(1),
Token::Literal(1),
Token::Literal(0),
Token::Literal(1),
Token::Copy {
length: 7,
distance: 4,
},
Token::Literal(0),
Token::Literal(1),
Token::Literal(0),
Token::Copy {
length: 4,
distance: 4,
},
]
);
}
#[test]
fn parse_optimal_early_out_is_below_min_match() {
let a = 0x1234_5678u32;
let pixels = [a, a, a, a];
let chain = super::HashChain::build(&pixels);
let fake_greedy = vec![
Token::Literal(a),
Token::Copy {
length: 3,
distance: 1,
},
];
let optimal = parse_optimal(&pixels, 1, &fake_greedy, &chain);
assert_eq!(optimal, vec![Token::Literal(a); 4]);
}
proptest! {
#[test]
fn parse_round_trips(
seed in any::<u64>(),
len in 1usize..=200,
palette in 1u32..=6,
) {
let pixels = seeded_pixels(seed, len, palette);
prop_assert_eq!(&decode_tokens(&parse(&pixels, false)), &pixels);
prop_assert_eq!(&decode_tokens(&parse(&pixels, true)), &pixels);
}
#[test]
fn find_matches_reference(
seed in any::<u64>(),
len in 1usize..=400,
palette in 1u32..=4,
) {
let pixels = seeded_pixels(seed, len, palette);
let chain = super::HashChain::build(&pixels);
for i in 0..pixels.len() {
prop_assert_eq!(
chain.find(&pixels, i),
find_reference(&chain, &pixels, i),
"find mismatch at position {}", i
);
}
}
#[test]
fn parse_matches_reference(
seed in any::<u64>(),
len in 1usize..=200,
palette in 1u32..=6,
) {
let pixels = seeded_pixels(seed, len, palette);
prop_assert_eq!(parse(&pixels, false), reference_parse(&pixels, false));
prop_assert_eq!(parse(&pixels, true), reference_parse(&pixels, true));
}
#[test]
fn optimal_parse_round_trips(
seed in any::<u64>(),
len in 1usize..=200,
palette in 1u32..=6,
width in 1u32..=16,
) {
let pixels = seeded_pixels(seed, len, palette);
let greedy = parse(&pixels, true);
let chain = super::HashChain::build(&pixels);
let optimal = parse_optimal(&pixels, width, &greedy, &chain);
prop_assert_eq!(&decode_tokens(&optimal), &pixels);
}
#[test]
fn parse_optimal_shared_chain_matches_reference(
seed in any::<u64>(),
len in 1usize..=200,
palette in 1u32..=6,
width in 1u32..=16,
) {
let pixels = seeded_pixels(seed, len, palette);
let (greedy, chain) = parse_lz77(&pixels);
let shared = parse_optimal(&pixels, width, &greedy, &chain);
let reference = parse_optimal_reference(&pixels, width, &greedy);
prop_assert_eq!(shared, reference);
}
#[test]
fn find_candidates_matches_reference(
seed in any::<u64>(),
len in 1usize..=400,
palette in 1u32..=4,
) {
let pixels = seeded_pixels(seed, len, palette);
let chain = super::HashChain::build(&pixels);
let mut memo = super::ShiftMemo::default();
let (mut got, mut want) = (Vec::new(), Vec::new());
for i in (0..pixels.len()).rev() {
chain.find_candidates(&pixels, i, &mut got, &mut memo);
find_candidates_reference(&chain, &pixels, i, &mut want);
prop_assert_eq!(&got, &want, "candidate mismatch at position {}", i);
memo.advance();
}
}
}
}