1 mod blockhash;
2 
3 use {BitSet, HashCtxt, Image};
4 
5 use self::HashAlg::*;
6 use HashVals::*;
7 use CowImage::*;
8 
9 /// Hash algorithms implemented by this crate.
10 ///
11 /// Implemented primarily based on the high-level descriptions on the blog Hacker Factor
12 /// written by Dr. Neal Krawetz: http://www.hackerfactor.com/
13 ///
14 /// Note that `hash_width` and `hash_height` in these docs refer to the parameters of
15 /// [`HasherConfig::hash_size()`](struct.HasherConfig.html#method.hash_size).
16 ///
17 /// ### Choosing an Algorithm
18 /// Each algorithm has different performance characteristics
19 #[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
20 pub enum HashAlg {
21     /// The Mean hashing algorithm.
22     ///
23     /// The image is converted to grayscale, scaled down to `hash_width x hash_height`,
24     /// the mean pixel value is taken, and then the hash bits are generated by comparing
25     /// the pixels of the descaled image to the mean.
26     ///
27     /// This is the most basic hash algorithm supported, resistant only to changes in
28     /// resolution, aspect ratio, and overall brightness.
29     ///
30     /// Further Reading:
31     /// http://www.hackerfactor.com/blog/?/archives/432-Looks-Like-It.html
32     Mean,
33 
34     /// The Gradient hashing algorithm.
35     ///
36     /// The image is converted to grayscale, scaled down to `(hash_width + 1) x hash_height`,
37     /// and then in row-major order the pixels are compared with each other, setting bits
38     /// in the hash for each comparison. The extra pixel is needed to have `hash_width` comparisons
39     /// per row.
40     ///
41     /// This hash algorithm is as fast or faster than Mean (because it only traverses the
42     /// hash data once) and is more resistant to changes than Mean.
43     ///
44     /// Further Reading:
45     /// http://www.hackerfactor.com/blog/index.php?/archives/529-Kind-of-Like-That.html
46     Gradient,
47 
48     /// The Vertical-Gradient hashing algorithm.
49     ///
50     /// Equivalent to [`Gradient`](#variant.Gradient) but operating on the columns of the image
51     /// instead of the rows.
52     VertGradient,
53 
54     /// The Double-Gradient hashing algorithm.
55     ///
56     /// An advanced version of [`Gradient`](#variant.Gradient);
57     /// resizes the grayscaled image to `(width / 2 + 1) x (height / 2 + 1)` and compares columns
58     /// in addition to rows.
59     ///
60     /// This algorithm is slightly slower than `Gradient` (resizing the image dwarfs
61     /// the hash time in most cases) but the extra comparison direction may improve results (though
62     /// you might want to consider increasing
63     /// [`hash_size`](struct.HasherConfig.html#method.hash_size)
64     /// to accommodate the extra comparisons).
65     DoubleGradient,
66 
67     /// The [Blockhash.io](https://blockhash.io) algorithm.
68     ///
69     /// Compared to the other algorithms, this does not require any preprocessing steps and so
70     /// may be significantly faster at the cost of some resilience.
71     ///
72     /// The algorithm is described in a high level here:
73     /// https://github.com/commonsmachinery/blockhash-rfc/blob/master/main.md
74     Blockhash,
75 
76     /// EXHAUSTIVE MATCHING IS NOT RECOMMENDED FOR BACKWARDS COMPATIBILITY REASONS
77     /// New variants may be added in minor (x.[y + 1].z) releases
78     #[doc(hidden)]
79     #[serde(skip)]
80     __Nonexhaustive,
81 }
82 
next_multiple_of_2(x: u32) -> u3283 fn next_multiple_of_2(x: u32) -> u32 { x + 1 & !1 }
next_multiple_of_4(x: u32) -> u3284 fn next_multiple_of_4(x: u32) -> u32 { x + 3 & !3 }
85 
86 impl HashAlg {
hash_image<I, B>(&self, ctxt: &HashCtxt, image: &I) -> B where I: Image, B: BitSet87     pub (crate) fn hash_image<I, B>(&self, ctxt: &HashCtxt, image: &I) -> B
88     where I: Image, B: BitSet {
89         let post_gauss = ctxt.gauss_preproc(image);
90 
91         let HashCtxt { width, height, .. } = *ctxt;
92 
93         if *self == Blockhash {
94             return match post_gauss {
95                 Borrowed(img) => blockhash::blockhash(img, width, height),
96                 Owned(img) => blockhash::blockhash(&img, width, height),
97             };
98         }
99 
100         let grayscale = post_gauss.to_grayscale();
101         let (resize_width, resize_height) = self.resize_dimensions(width, height);
102 
103         let hash_vals = ctxt.calc_hash_vals(&*grayscale, resize_width, resize_height);
104 
105         let rowstride = resize_width as usize;
106 
107         match (*self, hash_vals) {
108             (Mean, Floats(ref floats)) => B::from_bools(mean_hash_f32(floats)),
109             (Mean, Bytes(ref bytes)) => B::from_bools(mean_hash_u8(bytes)),
110             (Gradient, Floats(ref floats)) => B::from_bools(gradient_hash(floats, rowstride)),
111             (Gradient, Bytes(ref bytes)) => B::from_bools(gradient_hash(bytes, rowstride)),
112             (VertGradient, Floats(ref floats)) => B::from_bools(vert_gradient_hash(floats,
113                                                                                    rowstride)),
114             (VertGradient, Bytes(ref bytes)) => B::from_bools(vert_gradient_hash(bytes, rowstride)),
115             (DoubleGradient, Floats(ref floats)) => B::from_bools(double_gradient_hash(floats,
116                                                                                        rowstride)),
117             (DoubleGradient, Bytes(ref bytes)) => B::from_bools(double_gradient_hash(bytes,
118                                                                                      rowstride)),
119             (Blockhash, _) | (__Nonexhaustive, _) => unreachable!(),
120         }
121     }
122 
round_hash_size(&self, width: u32, height: u32) -> (u32, u32)123     pub (crate) fn round_hash_size(&self, width: u32, height: u32) -> (u32, u32) {
124         match *self {
125             DoubleGradient => (next_multiple_of_2(width), next_multiple_of_2(height)),
126             Blockhash => (next_multiple_of_4(width), next_multiple_of_4(height)),
127             _ => (width, height),
128         }
129     }
130 
resize_dimensions(&self, width: u32, height: u32) -> (u32, u32)131     pub (crate) fn resize_dimensions(&self, width: u32, height: u32) -> (u32, u32) {
132         match *self {
133             Mean => (width, height),
134             Blockhash => panic!("Blockhash algorithm does not resize"),
135             Gradient => (width + 1, height),
136             VertGradient => (width, height + 1),
137             DoubleGradient => (width / 2 + 1, height / 2 + 1),
138             __Nonexhaustive => panic!("not a real hash algorithm"),
139         }
140     }
141 }
142 
mean_hash_u8<'a>(luma: &'a [u8]) -> impl Iterator<Item = bool> + 'a143 fn mean_hash_u8<'a>(luma: &'a [u8]) -> impl Iterator<Item = bool> + 'a {
144     let mean = (luma.iter().map(|&l| l as u32).sum::<u32>() / luma.len() as u32) as u8;
145     luma.iter().map(move |&x| x >= mean)
146 }
147 
mean_hash_f32<'a>(luma: &'a [f32]) -> impl Iterator<Item = bool> + 'a148 fn mean_hash_f32<'a>(luma: &'a [f32]) -> impl Iterator<Item = bool> + 'a {
149     let mean = luma.iter().sum::<f32>() / luma.len() as f32;
150     luma.iter().map(move |&x| x >= mean)
151 }
152 
153 /// The guts of the gradient hash separated so we can reuse them
gradient_hash_impl<I>(luma: I) -> impl Iterator<Item = bool> where I: IntoIterator + Clone, <I as IntoIterator>::Item: PartialOrd154 fn gradient_hash_impl<I>(luma: I) -> impl Iterator<Item = bool>
155     where I: IntoIterator + Clone, <I as IntoIterator>::Item: PartialOrd {
156     luma.clone().into_iter().skip(1).zip(luma).map(|(this, last)| last < this)
157 }
158 
gradient_hash<'a, T: PartialOrd>(luma: &'a [T], rowstride: usize) -> impl Iterator<Item = bool> + 'a159 fn gradient_hash<'a, T: PartialOrd>(luma: &'a [T], rowstride: usize) -> impl Iterator<Item = bool> + 'a {
160     luma.chunks(rowstride).flat_map(gradient_hash_impl)
161 }
162 
vert_gradient_hash<'a, T: PartialOrd>(luma: &'a [T], rowstride: usize) -> impl Iterator<Item = bool> + 'a163 fn vert_gradient_hash<'a, T: PartialOrd>(luma: &'a [T], rowstride: usize) -> impl Iterator<Item = bool> + 'a {
164     (0 .. rowstride).map(move |col_start| luma[col_start..].iter().step_by(rowstride))
165         .flat_map(gradient_hash_impl)
166 }
167 
double_gradient_hash<'a, T: PartialOrd>(luma: &'a [T], rowstride: usize) -> impl Iterator<Item = bool> + 'a168 fn double_gradient_hash<'a, T: PartialOrd>(luma: &'a [T], rowstride: usize) -> impl Iterator<Item = bool> + 'a {
169     gradient_hash(luma, rowstride).chain(vert_gradient_hash(luma, rowstride))
170 }
171