use gamut_core::{Error, Result};
const HASH_MULTIPLIER: u32 = 0x1e35_a7bd;
const MIN_CACHE_BITS: u32 = 1;
const MAX_CACHE_BITS: u32 = 11;
#[derive(Debug, Clone)]
pub struct ColorCache {
bits: u32,
entries: Vec<u32>,
}
impl ColorCache {
pub fn new(bits: u32) -> Result<Self> {
if !(MIN_CACHE_BITS..=MAX_CACHE_BITS).contains(&bits) {
return Err(Error::InvalidInput("VP8L: color cache bits out of range"));
}
Ok(Self {
bits,
entries: vec![0u32; 1usize << bits],
})
}
#[must_use]
pub fn size(&self) -> usize {
self.entries.len()
}
#[inline]
#[must_use]
pub fn slot(&self, color: u32) -> usize {
(HASH_MULTIPLIER.wrapping_mul(color) >> (32 - self.bits)) as usize
}
pub fn insert(&mut self, color: u32) {
let slot = self.slot(color);
if let Some(entry) = self.entries.get_mut(slot) {
*entry = color;
}
}
#[must_use]
pub fn lookup(&self, index: u32) -> u32 {
self.entries.get(index as usize).copied().unwrap_or(0)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rejects_out_of_range_bits() {
assert!(ColorCache::new(0).is_err());
assert!(ColorCache::new(12).is_err());
assert!(ColorCache::new(1).is_ok());
assert!(ColorCache::new(11).is_ok());
}
#[test]
fn size_is_two_to_the_bits() {
assert_eq!(ColorCache::new(1).unwrap().size(), 2);
assert_eq!(ColorCache::new(10).unwrap().size(), 1024);
}
#[test]
fn insert_then_lookup_round_trips_a_color() {
let mut cache = ColorCache::new(8).unwrap();
let color = 0xdead_beef;
let slot = cache.slot(color);
assert_eq!(cache.lookup(slot as u32), 0); cache.insert(color);
assert_eq!(cache.lookup(slot as u32), color);
}
#[test]
fn hash_matches_spec_formula() {
let cache = ColorCache::new(6).unwrap();
let color = 0x1234_5678;
let expected = (HASH_MULTIPLIER.wrapping_mul(color) >> (32 - 6)) as usize;
assert_eq!(cache.slot(color), expected);
assert!(cache.slot(color) < cache.size());
}
#[test]
fn lookup_out_of_range_is_zero() {
let cache = ColorCache::new(1).unwrap();
assert_eq!(cache.lookup(99), 0);
}
}