1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
/// Euclidian distance squared between colors /// /// Uses the 3-dimensional euclidian distance formula to get the distance /// between two colors. This function returns an [`i32`] because the /// intermediate calculations would overflow [`u8`]. It is guaranteed to not /// overflow for slices with a length at most 33,025, but if you reach this /// limit, I guarantee you're doing something wrong. /// /// ``` /// # use image_go_nord::utils::delta; /// /// let a = [173, 87, 119, 255]; // a pinkish color /// let b = [0, 255, 255]; // cyan, my favorite color /// /// // `a` is rbga, but it doesn't matter. `delta` will only take components up to the length of /// //the smallest slice /// assert_eq!(delta(&a, &b), 76649); /// ``` /// /// `delta` takes two slices and zips them to calculate their component-wise /// distance. This is safe because it will only take elements up to the last of /// the shortest slice, which usually comes from a palette color (which is an /// array). /// /// This function can be proven not to overflow with some simple set theory. If /// $d$ is the function `delta`, $B \subset \Z^+$ is the set of unsigned 8-bit /// integers (`u8`), and $I \subset \Z$ is the set of signed 32-bit integers, /// and we know $B \subset I$, then we can prove, for two arbitrary 8-bit /// subpixel colors $x = (r_1, g_1, b_1)$ and $y = (r_2, g_2, b_2)$ (where $r /// \in B \and g \in B \and b \in B$), that $\forall x \forall y, d(x, y) \in /// I$. /// /// $d$ can be defined as follows (where $n = min(|x|, |y|)$). /// /// $$ /// d(x, y) = \sum_{i=1}^n (x_i - y_i)^2 /// $$ /// /// Since $d : B \rightarrow I$ and $min(A) < min(B) < max(B) < max(A)$, we just /// need to prove the range of $d$ does not exceed the minimum or maximum of its /// codomain. pub fn delta(a: &[u8], b: &[u8]) -> i32 { a.iter() .zip(b.iter()) .map(|(&a_c, &b_c)| (a_c as i32 - b_c as i32).pow(2)) .sum() } #[cfg(test)] pub fn setup_tinycross() -> Result<assert_fs::TempDir, assert_fs::fixture::FixtureError> { use std::path::Path; use assert_fs::{ fixture::{FileWriteFile, PathChild}, TempDir, }; let temp = TempDir::new()?; let input_file = temp.child("tinycross.png"); input_file.write_file(&Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/tinycross.png"))?; Ok(temp) } #[cfg(test)] mod tests { use crate::utils::delta; /// This covers the most common use case #[test] fn test_delta_overflow_3() { assert!(delta(&[u8::MAX; 3], &[0; 3]) < i32::MAX) } #[test] fn test_delta_overflow_max() { const LEN: usize = 33025; assert!(delta(&[u8::MAX; LEN], &[0; LEN]) < i32::MAX) } #[test] #[should_panic] fn test_delta_overflow_over_max() { const LEN: usize = 33026; assert!(delta(&[u8::MAX; LEN], &[0; LEN]) > i32::MAX) } }