gamut_webp/vp8l/
color_cache.rs1use gamut_core::{Error, Result};
10
11const HASH_MULTIPLIER: u32 = 0x1e35_a7bd;
13
14const MIN_CACHE_BITS: u32 = 1;
16const MAX_CACHE_BITS: u32 = 11;
17
18#[derive(Debug, Clone)]
20pub struct ColorCache {
21 bits: u32,
23 entries: Vec<u32>,
25}
26
27impl ColorCache {
28 pub fn new(bits: u32) -> Result<Self> {
34 if !(MIN_CACHE_BITS..=MAX_CACHE_BITS).contains(&bits) {
35 return Err(Error::InvalidInput("VP8L: color cache bits out of range"));
36 }
37 Ok(Self {
38 bits,
39 entries: vec![0u32; 1usize << bits],
40 })
41 }
42
43 #[must_use]
45 pub fn size(&self) -> usize {
46 self.entries.len()
47 }
48
49 #[inline]
51 #[must_use]
52 pub fn slot(&self, color: u32) -> usize {
53 (HASH_MULTIPLIER.wrapping_mul(color) >> (32 - self.bits)) as usize
54 }
55
56 pub fn insert(&mut self, color: u32) {
58 let slot = self.slot(color);
59 if let Some(entry) = self.entries.get_mut(slot) {
60 *entry = color;
61 }
62 }
63
64 #[must_use]
67 pub fn lookup(&self, index: u32) -> u32 {
68 self.entries.get(index as usize).copied().unwrap_or(0)
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 #[test]
77 fn rejects_out_of_range_bits() {
78 assert!(ColorCache::new(0).is_err());
79 assert!(ColorCache::new(12).is_err());
80 assert!(ColorCache::new(1).is_ok());
81 assert!(ColorCache::new(11).is_ok());
82 }
83
84 #[test]
85 fn size_is_two_to_the_bits() {
86 assert_eq!(ColorCache::new(1).unwrap().size(), 2);
87 assert_eq!(ColorCache::new(10).unwrap().size(), 1024);
88 }
89
90 #[test]
91 fn insert_then_lookup_round_trips_a_color() {
92 let mut cache = ColorCache::new(8).unwrap();
93 let color = 0xdead_beef;
94 let slot = cache.slot(color);
95 assert_eq!(cache.lookup(slot as u32), 0); cache.insert(color);
97 assert_eq!(cache.lookup(slot as u32), color);
98 }
99
100 #[test]
101 fn hash_matches_spec_formula() {
102 let cache = ColorCache::new(6).unwrap();
103 let color = 0x1234_5678;
104 let expected = (HASH_MULTIPLIER.wrapping_mul(color) >> (32 - 6)) as usize;
105 assert_eq!(cache.slot(color), expected);
106 assert!(cache.slot(color) < cache.size());
107 }
108
109 #[test]
110 fn lookup_out_of_range_is_zero() {
111 let cache = ColorCache::new(1).unwrap();
112 assert_eq!(cache.lookup(99), 0);
113 }
114}