color/
cache_key.rs

1// Copyright 2024 the Color Authors
2// SPDX-License-Identifier: Apache-2.0 OR MIT
3
4//! Hashing and other caching utilities for Color types.
5//!
6//! In this crate, colors are implemented using `f32`.
7//! This means that color types aren't `Hash` and `Eq` for good reasons:
8//!
9//! - Equality on these types is not reflexive (consider [NaN](f32::NAN)).
10//! - Certain values have two representations (`-0` and `+0` are both zero).
11//!
12//! However, it is still useful to create caches which key off these values.
13//! These are caches which don't have any semantic meaning, but instead
14//! are used to avoid redundant calculations or storage.
15//!
16//! Color supports creating these caches by using [`CacheKey<T>`] as the key in
17//! your cache.
18//! `T` is the key type (i.e. a color) which you want to use as the key.
19//! This `T` must implement both [`BitHash`] and [`BitEq`], which are
20//! versions of the standard `Hash` and `Eq` traits which support implementations
21//! for floating point numbers which might be unexpected outside of a caching context.
22
23use core::hash::{Hash, Hasher};
24
25/// A key usable in a hashmap to compare the bit representation
26/// types containing colors.
27///
28/// See the [module level docs](self) for more information.
29#[derive(Debug, Copy, Clone)]
30#[repr(transparent)]
31pub struct CacheKey<T>(pub T);
32
33impl<T> CacheKey<T> {
34    /// Create a new `CacheKey`.
35    ///
36    /// All fields are public, so the struct constructor can also be used.
37    pub fn new(value: T) -> Self {
38        Self(value)
39    }
40
41    /// Get the inner value.
42    pub fn into_inner(self) -> T {
43        self.0
44    }
45}
46
47// This module exists for these implementations:
48
49// `BitEq` is an equivalence relation, just maybe not the one you'd expect.
50impl<T: BitEq> Eq for CacheKey<T> {}
51impl<T: BitEq> PartialEq for CacheKey<T> {
52    fn eq(&self, other: &Self) -> bool {
53        self.0.bit_eq(&other.0)
54    }
55}
56// If we implement Eq, BitEq's implementation matches that of the hash.
57impl<T: BitHash> Hash for CacheKey<T> {
58    fn hash<H: Hasher>(&self, state: &mut H) {
59        self.0.bit_hash(state);
60    }
61}
62
63/// A hash implementation for types which normally wouldn't have one,
64/// implemented using a hash of the bitwise equivalent types when needed.
65///
66/// If a type is `BitHash` and `BitEq`, then it is important that the following property holds:
67///
68/// ```text
69/// k1 biteq k2 -> bithash(k1) == bithash(k2)
70/// ```
71///
72/// See the docs on [`Hash`] for more information.
73///
74/// Useful for creating caches based on exact values.
75/// See the [module level docs](self) for more information.
76pub trait BitHash {
77    /// Feeds this value into the given [`Hasher`].
78    fn bit_hash<H: Hasher>(&self, state: &mut H);
79    // Intentionally no hash_slice for simplicity.
80}
81
82impl BitHash for f32 {
83    fn bit_hash<H: Hasher>(&self, state: &mut H) {
84        self.to_bits().hash(state);
85    }
86}
87impl<T: BitHash, const N: usize> BitHash for [T; N] {
88    fn bit_hash<H: Hasher>(&self, state: &mut H) {
89        self[..].bit_hash(state);
90    }
91}
92
93impl<T: BitHash> BitHash for [T] {
94    fn bit_hash<H: Hasher>(&self, state: &mut H) {
95        // In theory, we should use `write_length_prefix`, which is unstable:
96        // https://github.com/rust-lang/rust/issues/96762
97        // We could do that by (unsafely) casting to `[CacheKey<T>]`, then
98        // using `Hash::hash` on the resulting slice.
99        state.write_usize(self.len());
100        for piece in self {
101            piece.bit_hash(state);
102        }
103    }
104}
105
106impl<T: BitHash> BitHash for &T {
107    fn bit_hash<H: Hasher>(&self, state: &mut H) {
108        T::bit_hash(*self, state);
109    }
110}
111
112// Don't BitHash tuples, not that important
113
114/// An equivalence relation for types which normally wouldn't have
115/// one, implemented using a bitwise comparison for floating point
116/// values.
117///
118/// See the docs on [`Eq`] for more information.
119///
120/// Useful for creating caches based on exact values.
121/// See the [module level docs](self) for more information.
122pub trait BitEq {
123    /// Returns `true` if `self` is equal to `other`.
124    ///
125    /// This need not use the semantically natural comparison operation
126    /// for the type; indeed floating point types should implement this
127    /// by comparing bit values.
128    fn bit_eq(&self, other: &Self) -> bool;
129    // Intentionally no bit_ne as would be added complexity for little gain
130}
131
132impl BitEq for f32 {
133    fn bit_eq(&self, other: &Self) -> bool {
134        self.to_bits() == other.to_bits()
135    }
136}
137
138impl<T: BitEq, const N: usize> BitEq for [T; N] {
139    fn bit_eq(&self, other: &Self) -> bool {
140        for i in 0..N {
141            if !self[i].bit_eq(&other[i]) {
142                return false;
143            }
144        }
145        true
146    }
147}
148
149impl<T: BitEq> BitEq for [T] {
150    fn bit_eq(&self, other: &Self) -> bool {
151        if self.len() != other.len() {
152            return false;
153        }
154        for (a, b) in self.iter().zip(other) {
155            if !a.bit_eq(b) {
156                return false;
157            }
158        }
159        true
160    }
161}
162
163impl<T: BitEq> BitEq for &T {
164    fn bit_eq(&self, other: &Self) -> bool {
165        T::bit_eq(*self, *other)
166    }
167}
168
169// Don't BitEq tuples, not that important
170
171// Ideally we'd also have these implementations, but they cause conflicts
172// (in case std ever went mad and implemented Eq for f32, for example).
173// impl<T: Hash> BitHash for T {...}
174// impl<T: PartialEq + Eq> BitEq for T {...}
175
176#[cfg(test)]
177mod tests {
178    extern crate std;
179    use super::CacheKey;
180    use crate::{parse_color, DynamicColor};
181    use std::collections::HashMap;
182
183    #[test]
184    fn bit_eq_hashmap() {
185        let mut map: HashMap<CacheKey<f32>, i32> = HashMap::new();
186        // The implementation for f32 is the base case.
187        assert!(map.insert(CacheKey(0.0), 0).is_none());
188        assert!(map.insert(CacheKey(-0.0), -1).is_none());
189        assert!(map.insert(CacheKey(1.0), 1).is_none());
190        assert!(map.insert(CacheKey(0.5), 5).is_none());
191
192        assert_eq!(map.get(&CacheKey(1.0)).unwrap(), &1);
193        assert_eq!(map.get(&CacheKey(0.0)).unwrap(), &0);
194        assert_eq!(map.remove(&CacheKey(-0.0)).unwrap(), -1);
195        assert!(!map.contains_key(&CacheKey(-0.0)));
196        assert_eq!(map.get(&CacheKey(0.5)).unwrap(), &5);
197    }
198    #[test]
199    fn bit_eq_color_hashmap() {
200        let mut map: HashMap<CacheKey<DynamicColor>, i32> = HashMap::new();
201
202        let red = parse_color("red").unwrap();
203        let red2 = parse_color("red").unwrap();
204        let other = parse_color("oklab(0.4 0.2 0.6)").unwrap();
205        assert!(map.insert(CacheKey(red), 10).is_none());
206        assert_eq!(map.insert(CacheKey(red2), 5).unwrap(), 10);
207        assert!(map.insert(CacheKey(other), 15).is_none());
208        assert_eq!(map.get(&CacheKey(other)).unwrap(), &15);
209    }
210}